feat: ai letter + disabled fl
This commit is contained in:
80
app/ai.py
Normal file
80
app/ai.py
Normal file
@@ -0,0 +1,80 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from pathlib import Path
|
||||
|
||||
import requests
|
||||
|
||||
from app.models import Offer
|
||||
|
||||
|
||||
class OfferReplyGenerator:
|
||||
def __init__(
|
||||
self,
|
||||
api_base_url: str,
|
||||
api_key: str,
|
||||
model: str,
|
||||
system_prompt_file: Path,
|
||||
timeout_seconds: int,
|
||||
) -> None:
|
||||
self._api_base_url = api_base_url.rstrip("/")
|
||||
self._api_key = api_key
|
||||
self._model = model
|
||||
self._system_prompt = system_prompt_file.read_text(encoding="utf-8").strip()
|
||||
self._timeout_seconds = timeout_seconds
|
||||
|
||||
async def generate_reply(self, offer: Offer) -> str:
|
||||
return await asyncio.to_thread(self._generate_reply_sync, offer)
|
||||
|
||||
def _generate_reply_sync(self, offer: Offer) -> str:
|
||||
response = requests.post(
|
||||
f"{self._api_base_url}/chat/completions",
|
||||
headers={
|
||||
"Authorization": f"Bearer {self._api_key}",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
json={
|
||||
"model": self._model,
|
||||
"messages": [
|
||||
{"role": "system", "content": self._system_prompt},
|
||||
{"role": "user", "content": _build_offer_prompt(offer)},
|
||||
],
|
||||
},
|
||||
timeout=self._timeout_seconds,
|
||||
)
|
||||
response.raise_for_status()
|
||||
payload = response.json()
|
||||
choices = payload.get("choices") or []
|
||||
if not choices:
|
||||
raise ValueError("OpenAI-compatible API returned no choices")
|
||||
|
||||
message = choices[0].get("message") or {}
|
||||
content = message.get("content")
|
||||
if isinstance(content, str) and content.strip():
|
||||
return content.strip()
|
||||
|
||||
if isinstance(content, list):
|
||||
text_parts = [item.get("text", "") for item in content if isinstance(item, dict)]
|
||||
text = "\n".join(part.strip() for part in text_parts if part and part.strip()).strip()
|
||||
if text:
|
||||
return text
|
||||
|
||||
raise ValueError("OpenAI-compatible API returned empty content")
|
||||
|
||||
|
||||
def _build_offer_prompt(offer: Offer) -> str:
|
||||
seller_line = f"Исполнитель: @{offer.seller_username}\n" if offer.seller_username else ""
|
||||
price_amount_line = f"Числовой бюджет: {offer.price_amount}\n" if offer.price_amount is not None else ""
|
||||
return (
|
||||
f"Подготовь сообщение для отклика на этот оффер.\n"
|
||||
f"Источник: {offer.source}\n"
|
||||
f"ID: {offer.external_id}\n"
|
||||
f"Заголовок: {offer.title}\n"
|
||||
f"Категория: {offer.category_id}\n"
|
||||
f"Бюджет: {offer.price}\n"
|
||||
f"{price_amount_line}"
|
||||
f"Опубликовано: {offer.published_at}\n"
|
||||
f"{seller_line}"
|
||||
f"Описание:\n{offer.description}\n\n"
|
||||
f"Ссылка: {offer.url}"
|
||||
)
|
||||
Reference in New Issue
Block a user