""" 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}"}