support-bot-saas/backend/app/services/llm.py
ed0ss cca3457f52
Some checks are pending
CI / lint-backend (push) Waiting to run
CI / lint-frontend (push) Waiting to run
CI / build-backend (push) Blocked by required conditions
CI / build-frontend (push) Blocked by required conditions
docs, proxy support, k8s fixes, test scripts
2026-06-29 15:57:30 +03:00

74 lines
3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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