From 44e53edc7272e85dd988ca331b3d74c1cf228464 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CNaeel=E2=80=9D?= Date: Sun, 14 Jun 2026 07:00:04 +0400 Subject: [PATCH] =?UTF-8?q?feat(llm):=20llm=5Fclient.py=20=E2=80=94=20HTTP?= =?UTF-8?q?-=D0=BA=D0=BB=D0=B8=D0=B5=D0=BD=D1=82=20=D0=BA=20aillm.ru?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ask(prompt, model, max_tokens) → {text, model, usage} | {error} OpenAI-совместимый формат, temperature=0.1 для стабильности. Переменные: LLM_API_URL, LLM_API_KEY. --- .env.example | 4 +++ site/llm_client.py | 80 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 site/llm_client.py diff --git a/.env.example b/.env.example index 6dcfd5f..c87a5bb 100644 --- a/.env.example +++ b/.env.example @@ -8,3 +8,7 @@ DB_NAME=contracts DB_USER=contracts DB_PASS= DB_SSLMODE=disable + +# ── LLM API ── +LLM_API_URL=https://api.aillm.ru/v1/chat/completions +LLM_API_KEY=sk-... diff --git a/site/llm_client.py b/site/llm_client.py new file mode 100644 index 0000000..7d299af --- /dev/null +++ b/site/llm_client.py @@ -0,0 +1,80 @@ +""" +llm_client.py — Тонкий HTTP-клиент к LLM API. + +Только отправить промпт → получить ответ. Никакой бизнес-логики. +Бизнес-логика (промпты, парсинг ответа) → extractor.py. + +Использует: + LLM_API_URL — URL API (по умолчанию https://api.aillm.ru/v1/chat/completions) + LLM_API_KEY — ключ авторизации +""" + +import os +import requests + +# ── Конфигурация ──────────────────────────────────────────────── + +DEFAULT_URL = "https://api.aillm.ru/v1/chat/completions" +DEFAULT_MODEL = "deepseek-chat" # DeepSeek V3 на aillm.ru (120B эквивалент) + + +def ask(prompt: str, model: str = None, max_tokens: int = 4000) -> dict: + """ + Отправить промпт к LLM API. + + Args: + prompt: текст промпта + model: модель (None = DEFAULT_MODEL) + max_tokens: максимум токенов в ответе + + Returns: + {"text": "ответ модели", "model": "...", "usage": {...}} + или + {"error": "описание ошибки"} + """ + api_url = os.getenv("LLM_API_URL", DEFAULT_URL) + api_key = os.getenv("LLM_API_KEY", "") + + if not api_key: + return {"error": "LLM_API_KEY not set"} + + model = model or DEFAULT_MODEL + + payload = { + "model": model, + "messages": [ + {"role": "user", "content": prompt}, + ], + "max_tokens": max_tokens, + "temperature": 0.1, # низкая температура = стабильный формат ответа + } + + headers = { + "Authorization": f"Bearer {api_key}", + "Content-Type": "application/json", + } + + try: + resp = requests.post(api_url, json=payload, headers=headers, timeout=120) + resp.raise_for_status() + data = resp.json() + + # OpenAI-совместимый формат ответа + choice = data.get("choices", [{}])[0] + message = choice.get("message", {}) + text = message.get("content", "") + + return { + "text": text, + "model": data.get("model", model), + "usage": data.get("usage", {}), + } + + except requests.exceptions.Timeout: + return {"error": "LLM timeout (120s)"} + except requests.exceptions.HTTPError as e: + return {"error": f"LLM HTTP {e.response.status_code}: {e.response.text[:500]}"} + except requests.exceptions.RequestException as e: + return {"error": f"LLM request failed: {e}"} + except Exception as e: + return {"error": f"LLM unexpected: {e}"}