""" brain/client.py — LLM-клиент для диагностики авто. Отправляет запросы к OpenAI-совместимому API (api.aillm.ru). Используется сервером для анализа данных диагностики. ## Использование from brain.client import Diagnoser d = Diagnoser(api_key="sk-...", model="gpt-oss-120b") answer = d.diagnose(system_prompt, user_prompt) ## Модель gpt-oss-120b — основная (через api.aillm.ru) qwen3-6-27b-fp8 — быстрая (но CoT leak bug) """ import logging import requests logger = logging.getLogger("brain.client") DEFAULT_BASE = "https://api.aillm.ru/v1" DEFAULT_MODEL = "gpt-oss-120b" DEFAULT_TIMEOUT = 180 class LLMError(Exception): """Ошибка LLM API с безопасным для клиента сообщением.""" pass class Diagnoser: """LLM-клиент для OpenAI-совместимого API.""" def __init__(self, api_key: str, model: str = DEFAULT_MODEL, base_url: str = DEFAULT_BASE, timeout: int = DEFAULT_TIMEOUT): self.api_key = api_key self.model = model self.base_url = base_url.rstrip("/") self.timeout = timeout def ask(self, messages: list[dict]) -> str: """Отправляет сообщения в LLM API, возвращает текст ответа.""" try: resp = requests.post( f"{self.base_url}/chat/completions", headers={ "Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json", }, json={ "model": self.model, "messages": messages, "temperature": 0.3, "max_tokens": 4096, }, timeout=self.timeout, ) resp.raise_for_status() data = resp.json() return data["choices"][0]["message"]["content"] except requests.Timeout: logger.warning(f"LLM timeout after {self.timeout}s") raise LLMError("LLM не ответил вовремя. Попробуйте позже.") except requests.HTTPError as e: status = e.response.status_code if e.response is not None else 0 logger.warning(f"LLM HTTP {status}: {e}") if status == 429: raise LLMError("Слишком много запросов. Подождите минуту.") elif 500 <= status < 600: raise LLMError("LLM временно недоступен. Попробуйте позже.") else: raise LLMError("Ошибка LLM. Попробуйте позже.") except Exception as e: logger.error(f"LLM unexpected: {e}") raise LLMError("LLM временно недоступен.") def diagnose( self, system: str, user_prompt: str, history: list[dict] | None = None, ) -> str: """Полный цикл: system + история + user_prompt → ответ.""" messages = [{"role": "system", "content": system}] if history: messages.extend(history) messages.append({"role": "user", "content": user_prompt}) return self.ask(messages)