import logging import httpx from app.config import settings logger = logging.getLogger(__name__) # Increase log level for httpx to reduce noise logging.getLogger("httpx").setLevel(logging.WARNING) OPENROUTER_BASE = "https://openrouter.ai/api/v1" _client: httpx.AsyncClient | None = None def get_client() -> httpx.AsyncClient: global _client if _client is None: kwargs: dict = { "base_url": OPENROUTER_BASE, "headers": { "Authorization": f"Bearer {settings.openrouter_api_key}", "Content-Type": "application/json", }, "timeout": 60, } if settings.proxy_url: proxy = settings.proxy_url if not proxy.startswith(("http://", "https://")): proxy = "http://" + proxy if settings.proxy_login and settings.proxy_pass: proxy = proxy.replace("://", f"://{settings.proxy_login}:{settings.proxy_pass}@") kwargs["proxy"] = proxy _client = httpx.AsyncClient(**kwargs) return _client SYSTEM_PROMPT = """Ты — специалист техподдержки. Отвечай клиенту, используя ТОЛЬКО информацию из переданных тикетов (контекст). Правила: - Если контекст содержит подходящее решение — напиши ответ своими словами, адаптируя под вопрос - Если контекст не относится к вопросу — напиши: «Недостаточно информации в истории обращений» - НЕ придумывай ответы, НЕ используй общие знания - НЕ говори «обратитесь в службу поддержки» — ты сам и есть поддержка - Укажи в конце: «Основано на тикете №...»""" async def generate_answer(query: str, context: str) -> str | None: client = get_client() response = await client.post( "/chat/completions", json={ "model": settings.openrouter_llm_model, "messages": [ {"role": "system", "content": SYSTEM_PROMPT}, { "role": "user", "content": f"Контекст (история обращений):\n{context}\n\nВопрос клиента:\n{query}", }, ], "temperature": 0.1, "max_tokens": 2000, }, ) if response.status_code != 200: body = await response.aread() logger.error("LLM API error [%s]: %s", response.status_code, body) return f"LLM API error [{response.status_code}]: {body.decode(errors='replace')}" data = response.json() if "choices" not in data or not data["choices"]: logger.error("LLM response missing choices: %s", data.get("error", data)) return f"LLM response error: {data.get('error', data)}" return data["choices"][0]["message"]["content"]