62 lines
2.3 KiB
Python
62 lines
2.3 KiB
Python
import asyncio
|
|
import json
|
|
import logging
|
|
import time
|
|
from collections.abc import Sequence
|
|
|
|
from openai import APIConnectionError, APIStatusError, AsyncOpenAI, RateLimitError
|
|
|
|
from app.llm.prompts import SYSTEM_PROMPT
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class LLMUnavailable(RuntimeError):
|
|
pass
|
|
|
|
|
|
class DeepSeekClient:
|
|
def __init__(
|
|
self, api_key: str, base_url: str, model: str, timeout: float = 45, retries: int = 3
|
|
):
|
|
self.client = AsyncOpenAI(
|
|
api_key=api_key, base_url=base_url, timeout=timeout, max_retries=0
|
|
)
|
|
self.model, self.retries = model, retries
|
|
|
|
async def complete(
|
|
self, messages: Sequence[dict[str, str]], *, temperature: float = 0.1
|
|
) -> str:
|
|
started = time.monotonic()
|
|
payload = [{"role": "system", "content": SYSTEM_PROMPT}, *messages]
|
|
for attempt in range(self.retries):
|
|
try:
|
|
response = await self.client.chat.completions.create(
|
|
model=self.model, messages=list(payload), temperature=temperature
|
|
)
|
|
content = response.choices[0].message.content or ""
|
|
logger.info(
|
|
"llm_complete duration_ms=%d tokens=%s",
|
|
int((time.monotonic() - started) * 1000),
|
|
getattr(response.usage, "total_tokens", "unknown"),
|
|
)
|
|
return content
|
|
except (RateLimitError, APIConnectionError, APIStatusError) as error:
|
|
if (
|
|
isinstance(error, APIStatusError)
|
|
and error.status_code < 500
|
|
and error.status_code != 429
|
|
):
|
|
break
|
|
if attempt + 1 < self.retries:
|
|
await asyncio.sleep(2**attempt)
|
|
logger.warning("llm_unavailable duration_ms=%d", int((time.monotonic() - started) * 1000))
|
|
raise LLMUnavailable("Модель временно недоступна. Попробуйте ещё раз позже.")
|
|
|
|
async def json(self, messages: Sequence[dict[str, str]]) -> dict[str, object]:
|
|
content = await self.complete(messages, temperature=0)
|
|
try:
|
|
return json.loads(content.removeprefix("```json").removesuffix("```").strip())
|
|
except json.JSONDecodeError:
|
|
return {}
|