ai-chat-ui/server/adapters/glm_adapter.py

216 lines
7.5 KiB
Python

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