feat(llm): llm_client.py — HTTP-клиент к aillm.ru
ask(prompt, model, max_tokens) → {text, model, usage} | {error}
OpenAI-совместимый формат, temperature=0.1 для стабильности.
Переменные: LLM_API_URL, LLM_API_KEY.
This commit is contained in:
@@ -8,3 +8,7 @@ DB_NAME=contracts
|
|||||||
DB_USER=contracts
|
DB_USER=contracts
|
||||||
DB_PASS=
|
DB_PASS=
|
||||||
DB_SSLMODE=disable
|
DB_SSLMODE=disable
|
||||||
|
|
||||||
|
# ── LLM API ──
|
||||||
|
LLM_API_URL=https://api.aillm.ru/v1/chat/completions
|
||||||
|
LLM_API_KEY=sk-...
|
||||||
|
|||||||
@@ -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}"}
|
||||||
Reference in New Issue
Block a user