llm.py 1.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152
  1. from __future__ import annotations
  2. import json
  3. from typing import Any
  4. import httpx
  5. from .config import settings
  6. class LLMUnavailable(RuntimeError):
  7. pass
  8. class OpenAICompatibleClient:
  9. @property
  10. def configured(self) -> bool:
  11. return bool(settings.llm_api_key and settings.llm_base_url and settings.llm_model)
  12. async def chat(self, system: str, user: str, temperature: float = 0.2) -> str:
  13. if not self.configured:
  14. raise LLMUnavailable("未配置 LLM_API_KEY / LLM_BASE_URL / LLM_MODEL")
  15. async with httpx.AsyncClient(timeout=60) as client:
  16. response = await client.post(
  17. f"{settings.llm_base_url}/chat/completions",
  18. headers={"Authorization": f"Bearer {settings.llm_api_key}"},
  19. json={
  20. "model": settings.llm_model,
  21. "temperature": temperature,
  22. "messages": [
  23. {"role": "system", "content": system},
  24. {"role": "user", "content": user},
  25. ],
  26. },
  27. )
  28. response.raise_for_status()
  29. data = response.json()
  30. return data["choices"][0]["message"]["content"]
  31. async def json(self, system: str, user: str) -> dict[str, Any]:
  32. raw = await self.chat(system, user, temperature=0.1)
  33. cleaned = raw.strip()
  34. if "```" in cleaned:
  35. cleaned = cleaned.replace("```json", "").replace("```", "").strip()
  36. start, end = cleaned.find("{"), cleaned.rfind("}")
  37. if start >= 0 and end > start:
  38. cleaned = cleaned[start : end + 1]
  39. return json.loads(cleaned)
  40. llm = OpenAICompatibleClient()