"""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