import httpx import openai from openai.types import CompletionUsage from config.logger import setup_logging from core.utils.util import check_model_key from core.providers.llm.base import LLMProviderBase from urllib.parse import urlparse TAG = __name__ logger = setup_logging() # 需要禁用思考模式的平台域名及其对应参数(默认关闭思考模式) THINKING_DISABLED_DOMAINS = { "aliyuncs.com": {"enable_thinking": False}, "deepseek.com": {"thinking": {"type": "disabled"}}, "bigmodel.cn": {"thinking": {"type": "disabled"}}, "moonshot.cn": {"thinking": {"type": "disabled"}}, "volces.com": {"thinking": {"type": "disabled"}}, } class LLMProvider(LLMProviderBase): def __init__(self, config): self.model_name = config.get("model_name") self.api_key = config.get("api_key") if "base_url" in config: self.base_url = config.get("base_url") else: self.base_url = config.get("url") timeout_config = config.get("timeout") if isinstance(timeout_config, dict): # 细粒度超时配置 custom_timeout = httpx.Timeout( pool=timeout_config.get("pool", 2.0), connect=timeout_config.get("connect", 3.0), write=timeout_config.get("write", 5.0), read=timeout_config.get("read", 60.0) ) elif isinstance(timeout_config, (int, float)) and timeout_config > 0: # 兼容旧的单一超时配置(整数或浮点数) custom_timeout = httpx.Timeout(timeout_config) else: # 未配置或配置无效,使用默认值 custom_timeout = httpx.Timeout(300) param_defaults = { "max_tokens": int, "temperature": lambda x: round(float(x), 1), "top_p": lambda x: round(float(x), 1), "frequency_penalty": lambda x: round(float(x), 1), } for param, converter in param_defaults.items(): value = config.get(param) try: setattr( self, param, converter(value) if value not in (None, "") else None, ) except (ValueError, TypeError): setattr(self, param, None) logger.debug( f"意图识别参数初始化: {self.temperature}, {self.max_tokens}, {self.top_p}, {self.frequency_penalty}" ) model_key_msg = check_model_key("LLM", self.api_key) if model_key_msg: logger.bind(tag=TAG).error(model_key_msg) self.client = openai.OpenAI(api_key=self.api_key, base_url=self.base_url, timeout=custom_timeout) @staticmethod def normalize_dialogue(dialogue): """自动修复 dialogue 中缺失 content 的消息""" for msg in dialogue: if "role" in msg and "content" not in msg: msg["content"] = "" return dialogue def _apply_thinking_disabled(self, request_params: dict): """根据域名自动禁用思考模式""" parsed_url = urlparse(self.base_url) domain = parsed_url.netloc for disabled_domain, params in THINKING_DISABLED_DOMAINS.items(): if disabled_domain in domain: request_params.setdefault("extra_body", {}).update(params) logger.bind(tag=TAG).info(f"为域名 {domain} 禁用思考模式,参数: {params}") break def response(self, session_id, dialogue, **kwargs): dialogue = self.normalize_dialogue(dialogue) request_params = { "model": self.model_name, "messages": dialogue, "stream": True, } # 添加可选参数,只有当参数不为None时才添加 optional_params = { "max_tokens": kwargs.get("max_tokens", self.max_tokens), "temperature": kwargs.get("temperature", self.temperature), "top_p": kwargs.get("top_p", self.top_p), "frequency_penalty": kwargs.get("frequency_penalty", self.frequency_penalty), } for key, value in optional_params.items(): if value is not None: request_params[key] = value # 禁用思考模式 self._apply_thinking_disabled(request_params) responses = self.client.chat.completions.create(**request_params) is_active = True try: for chunk in responses: try: delta = chunk.choices[0].delta if getattr(chunk, "choices", None) else None content = getattr(delta, "content", "") if delta else "" except IndexError: content = "" if content: if "" in content: is_active = False content = content.split("")[0] if "" in content: is_active = True content = content.split("")[-1] if is_active: yield content finally: responses.close() def response_with_functions(self, session_id, dialogue, functions=None, **kwargs): dialogue = self.normalize_dialogue(dialogue) request_params = { "model": self.model_name, "messages": dialogue, "stream": True, "tools": functions, } optional_params = { "max_tokens": kwargs.get("max_tokens", self.max_tokens), "temperature": kwargs.get("temperature", self.temperature), "top_p": kwargs.get("top_p", self.top_p), "frequency_penalty": kwargs.get("frequency_penalty", self.frequency_penalty), } for key, value in optional_params.items(): if value is not None: request_params[key] = value # 禁用思考模式 self._apply_thinking_disabled(request_params) stream = self.client.chat.completions.create(**request_params) try: for chunk in stream: if getattr(chunk, "choices", None): delta = chunk.choices[0].delta content = getattr(delta, "content", "") tool_calls = getattr(delta, "tool_calls", None) yield content, tool_calls elif isinstance(getattr(chunk, "usage", None), CompletionUsage): usage_info = getattr(chunk, "usage", None) logger.bind(tag=TAG).info( f"Token 消耗:输入 {getattr(usage_info, 'prompt_tokens', '未知')}," f"输出 {getattr(usage_info, 'completion_tokens', '未知')}," f"共计 {getattr(usage_info, 'total_tokens', '未知')}" ) finally: stream.close()