76 lines
2.5 KiB
Python
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
|