Sub / app /utils /llm_client.py
Himankpro's picture
Upload 93 files
3a6c182 verified
Raw History Blame Contribute Delete
3.4 kB
"""
LLM客户端封装
统一使用OpenAI格式调用
"""
import json
import re
from typing import Optional, Dict, Any, List
from openai import OpenAI
from ..config import Config
from .api_key_runtime import call_with_limit_rotation, get_llm_key
class LLMClient:
"""LLM客户端"""
def __init__(
self,
api_key: Optional[str] = None,
base_url: Optional[str] = None,
model: Optional[str] = None
):
self._fixed_api_key = (api_key or "").strip() or None
self.base_url = base_url or Config.LLM_BASE_URL
self.model = model or Config.LLM_MODEL_NAME
if not self._fixed_api_key and not Config.get_llm_api_keys():
raise ValueError("LLM API key not configured (set LLM_1 or LLM_API_KEY)")
def chat(
self,
messages: List[Dict[str, str]],
temperature: float = 0.7,
max_tokens: int = 4096,
response_format: Optional[Dict] = None
) -> str:
"""
发送聊天请求
Args:
messages: 消息列表
temperature: 温度参数
max_tokens: 最大token数
response_format: 响应格式(如JSON模式)
Returns:
模型响应文本
"""
kwargs = {
"model": self.model,
"messages": messages,
"temperature": temperature,
"max_tokens": max_tokens,
}
if response_format:
kwargs["response_format"] = response_format
def _create():
key = (self._fixed_api_key or get_llm_key() or "").strip()
if not key:
raise ValueError("LLM API key not configured (set LLM_1 / LLM_2 or LLM_API_KEY)")
client = OpenAI(api_key=key, base_url=self.base_url)
return client.chat.completions.create(**kwargs)
response = (
_create()
if self._fixed_api_key
else call_with_limit_rotation(_create)
)
content = response.choices[0].message.content
# 部分模型(如MiniMax M2.5)会在content中包含<think>思考内容,需要移除
content = re.sub(r'<think>[\s\S]*?</think>', '', content).strip()
return content
def chat_json(
self,
messages: List[Dict[str, str]],
temperature: float = 0.3,
max_tokens: int = 4096
) -> Dict[str, Any]:
"""
发送聊天请求并返回JSON
Args:
messages: 消息列表
temperature: 温度参数
max_tokens: 最大token数
Returns:
解析后的JSON对象
"""
response = self.chat(
messages=messages,
temperature=temperature,
max_tokens=max_tokens,
response_format={"type": "json_object"}
)
# 清理markdown代码块标记
cleaned_response = response.strip()
cleaned_response = re.sub(r'^```(?:json)?\s*\n?', '', cleaned_response, flags=re.IGNORECASE)
cleaned_response = re.sub(r'\n?```\s*$', '', cleaned_response)
cleaned_response = cleaned_response.strip()
try:
return json.loads(cleaned_response)
except json.JSONDecodeError:
raise ValueError(f"LLM返回的JSON格式无效: {cleaned_response}")