74 lines
3 KiB
Python
74 lines
3 KiB
Python
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"]
|