""" 智谱 GLM 适配器 使用 OpenAI SDK 调用智谱 OpenAI 兼容 API """ import json import os from typing import Any, Dict, List, Optional from .base import ChatCompletionRequest, ModelInfo from .unified_adapter import UnifiedOpenAIAdapter from .plugins import get_web_search_mode, build_glm_search_tool from core import get_logger logger = get_logger() # GLM 模型配置 GLM_MODELS = [ ModelInfo( id="glm-5", name="GLM-5", description="Coding与长程Agent能力SOTA", max_tokens=128000, provider="ZhipuAI", supports_thinking=True, supports_web_search=True, supports_vision=False, supports_files=False, ), ModelInfo( id="glm-4.6v", name="GLM-4.6V", description="最新旗舰模型,支持文本/图像/文档/深度思考", max_tokens=128000, provider="ZhipuAI", supports_thinking=True, supports_web_search=False, supports_vision=True, supports_files=True, ), ModelInfo( id="glm-4-flash", name="GLM-4 Flash", description="高性价比文本模型", max_tokens=128000, provider="ZhipuAI", supports_thinking=False, supports_web_search=True, supports_vision=False, supports_files=False, ), ModelInfo( id="glm-4v-plus-0111", name="GLM-4V Plus", description="图像 + PDF/DOCX 原生多模态", max_tokens=128000, provider="ZhipuAI", supports_thinking=False, supports_web_search=False, supports_vision=True, supports_files=True, ), ModelInfo( id="glm-z1-flash", name="GLM-Z1 Flash", description="深度思考推理模型,默认开启深度思考", max_tokens=128000, provider="ZhipuAI", supports_thinking=True, supports_web_search=True, supports_vision=False, supports_files=False, ), ] # 从 GLM_MODELS 自动计算 VISION_MODELS = {m.id.lower() for m in GLM_MODELS if m.supports_vision} THINKING_MODELS = {m.id.lower() for m in GLM_MODELS if m.supports_thinking} class GLMAdapter(UnifiedOpenAIAdapter): """智谱 GLM 平台适配器""" _provider_type = "zhipu" @property def provider_name(self) -> str: return "glm" def list_models(self) -> List[ModelInfo]: return GLM_MODELS def _supports_thinking(self, model: str) -> bool: """检查模型是否支持深度思考""" return model.lower() in THINKING_MODELS def _build_messages(self, request: ChatCompletionRequest) -> List[Dict]: """ 构建 GLM 格式的消息 处理文件附件和多模态内容 """ messages = [] has_vision = False has_files = bool(request.files) for msg in request.messages: role = msg.get("role", "user") content = msg.get("content", "") if isinstance(content, str): if content.strip(): messages.append({"role": role, "content": content}) elif isinstance(content, list): glm_content = [] for item in content: if isinstance(item, dict): item_type = item.get("type", "") if item_type == "text": text = item.get("text", "") if text: glm_content.append({"type": "text", "text": text}) elif item_type == "image_url": img_url = self._extract_image_url(item) if img_url: glm_content.append( {"type": "image_url", "image_url": {"url": img_url}} ) has_vision = True if glm_content: messages.append({"role": role, "content": glm_content}) # 处理文件附件 if request.files: file_content = self._build_file_content(request.files) if messages and messages[-1]["role"] == "user": if isinstance(messages[-1]["content"], list): messages[-1]["content"].extend(file_content) else: messages[-1]["content"] = [ {"type": "text", "text": messages[-1]["content"]}, *file_content, ] else: messages.append({"role": "user", "content": file_content}) return messages def _extract_image_url(self, item: Dict) -> Optional[str]: """提取图片 URL""" img_val = item.get("image_url", "") if isinstance(img_val, str): return img_val elif isinstance(img_val, dict): return img_val.get("url", "") return None def _build_file_content(self, files: List[str]) -> List[Dict]: """构建文件内容""" content = [] for file_url in files: if file_url.startswith(("http://", "https://")): content.append({"type": "file_url", "file_url": {"url": file_url}}) return content def _resolve_model(self, model: str, has_vision: bool, has_files: bool = False) -> str: """解析实际使用的模型""" model_lower = model.lower() if (has_vision or has_files) and model_lower not in VISION_MODELS: logger.info(f"[GLM] 检测到图片或文件附件,切换模型: {model} -> glm-4.6v") return "glm-4.6v" return model def _get_extra_params(self, request: ChatCompletionRequest) -> Dict[str, Any]: """ 获取 GLM 特殊参数 - 深度思考: extra_body={"thinking": {"type": "enabled/disabled"}} - 联网搜索: tools=[{"type": "web_search", ...}] """ extra_params = {} # 检测是否有多模态内容,决定最终使用的模型 messages = self._build_messages(request) has_vision = any( isinstance(m.get("content"), list) and any(c.get("type") == "image_url" for c in m.get("content", [])) for m in messages ) has_files = bool(request.files) actual_model = self._resolve_model(request.model, has_vision, has_files) # 更新请求中的模型(如果有变化) if actual_model != request.model: extra_params["model"] = actual_model # 联网搜索 web_search_mode = get_web_search_mode(request) if web_search_mode: extra_params["tools"] = [build_glm_search_tool(web_search_mode)] extra_params["tool_choice"] = "auto" logger.info(f"[GLM] 联网搜索已启用: mode={web_search_mode}") # 深度思考 - 始终传递,明确启用或禁用 logger.info(f"[GLM] 深度思考请求: deep_thinking={request.deep_thinking}, actual_model={actual_model}") # 判断是否支持深度思考 supports_thinking = self._supports_thinking(actual_model) logger.info(f"[GLM] 模型 {actual_model} 支持深度思考: {supports_thinking}") # 只有前端请求启用 且 模型支持时才启用 thinking_enabled = request.deep_thinking and supports_thinking thinking_type = "enabled" if thinking_enabled else "disabled" extra_params["extra_body"] = {"thinking": {"type": thinking_type}} logger.info(f"[GLM] 深度思考最终状态: {thinking_type}") return extra_params