Files

76 lines
2.5 KiB
Python

"""OpenAI 兼容异步客户端:JSON 决策与自由文本发言,自动降级重试。"""
from __future__ import annotations
import asyncio
import json
import re
from ...config import CONFIG, LLMConfig
def parse_json_obj(text: str) -> dict | None:
if not text:
return None
text = text.strip()
text = re.sub(r"^```(?:json)?\s*|\s*```$", "", text, flags=re.S)
start, end = text.find("{"), text.rfind("}")
if start == -1 or end <= start:
return None
try:
data = json.loads(text[start:end + 1])
return data if isinstance(data, dict) else None
except json.JSONDecodeError:
return None
class LLMClient:
def __init__(self, cfg: LLMConfig) -> None:
from openai import AsyncOpenAI
self.model = cfg.model
self.temperature = cfg.temperature
self._client = AsyncOpenAI(
base_url=cfg.base_url, api_key=cfg.api_key, timeout=30, max_retries=0)
async def _create(self, system: str, user: str, *, json_mode: bool, timeout: float) -> str:
kwargs: dict = {
"model": self.model,
"temperature": self.temperature,
"messages": [
{"role": "system", "content": system},
{"role": "user", "content": user},
],
"timeout": timeout,
}
if json_mode:
kwargs["response_format"] = {"type": "json_object"}
try:
resp = await self._client.chat.completions.create(**kwargs)
return resp.choices[0].message.content or ""
except Exception:
kwargs.pop("response_format", None) # 部分兼容端点不支持 response_format
resp = await self._client.chat.completions.create(**kwargs)
return resp.choices[0].message.content or ""
async def chat_json(self, system: str, user: str, *, timeout: float = 22.0) -> dict:
last = ""
for _ in range(2):
text = await self._create(system, user, json_mode=True, timeout=timeout)
data = parse_json_obj(text)
if data is not None:
return data
last = text
raise ValueError(f"LLM 未返回有效 JSON: {last[:120]}")
async def chat_text(self, system: str, user: str, *, timeout: float = 40.0) -> str:
return (await self._create(system, user, json_mode=False, timeout=timeout)).strip()
_client: LLMClient | None = None
def get_llm_client() -> LLMClient:
global _client
if _client is None:
_client = LLMClient(CONFIG.llm)
return _client