216 lines
7.5 KiB
Python
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 |