support-bot-saas/backend/app/services/llm.py
ed0ss 14025ae30d
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
fix: передача реальной ошибки LLM в ответ (вместо null)
2026-06-25 12:15:45 +03:00

66 lines
2.6 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:
_client = httpx.AsyncClient(
base_url=OPENROUTER_BASE,
headers={
"Authorization": f"Bearer {settings.openrouter_api_key}",
"Content-Type": "application/json",
},
timeout=60,
)
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"]