mirror of
https://github.com/zhayujie/chatgpt-on-wechat.git
synced 2026-03-18 12:40:06 +08:00
fix: support glm-4.7
This commit is contained in:
@@ -6,14 +6,14 @@ import os
|
|||||||
from typing import Optional, List
|
from typing import Optional, List
|
||||||
|
|
||||||
from agent.protocol import Agent, LLMModel, LLMRequest
|
from agent.protocol import Agent, LLMModel, LLMRequest
|
||||||
from models.openai_compatible_bot import OpenAICompatibleBot
|
from bridge.agent_event_handler import AgentEventHandler
|
||||||
|
from bridge.agent_initializer import AgentInitializer
|
||||||
from bridge.bridge import Bridge
|
from bridge.bridge import Bridge
|
||||||
from bridge.context import Context
|
from bridge.context import Context
|
||||||
from bridge.reply import Reply, ReplyType
|
from bridge.reply import Reply, ReplyType
|
||||||
from bridge.agent_event_handler import AgentEventHandler
|
|
||||||
from bridge.agent_initializer import AgentInitializer
|
|
||||||
from common import const
|
from common import const
|
||||||
from common.log import logger
|
from common.log import logger
|
||||||
|
from models.openai_compatible_bot import OpenAICompatibleBot
|
||||||
|
|
||||||
|
|
||||||
def add_openai_compatible_support(bot_instance):
|
def add_openai_compatible_support(bot_instance):
|
||||||
@@ -22,9 +22,12 @@ def add_openai_compatible_support(bot_instance):
|
|||||||
|
|
||||||
This allows any bot to gain tool calling capability without modifying its code,
|
This allows any bot to gain tool calling capability without modifying its code,
|
||||||
as long as it uses OpenAI-compatible API format.
|
as long as it uses OpenAI-compatible API format.
|
||||||
|
|
||||||
|
Note: Some bots like ZHIPUAIBot have native tool calling support and don't need enhancement.
|
||||||
"""
|
"""
|
||||||
if hasattr(bot_instance, 'call_with_tools'):
|
if hasattr(bot_instance, 'call_with_tools'):
|
||||||
# Bot already has tool calling support
|
# Bot already has tool calling support (e.g., ZHIPUAIBot)
|
||||||
|
logger.info(f"[AgentBridge] {type(bot_instance).__name__} already has native tool calling support")
|
||||||
return bot_instance
|
return bot_instance
|
||||||
|
|
||||||
# Create a temporary mixin class that combines the bot with OpenAI compatibility
|
# Create a temporary mixin class that combines the bot with OpenAI compatibility
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
{
|
{
|
||||||
"channel_type": "web",
|
"channel_type": "web",
|
||||||
"model": "claude-sonnet-4-5",
|
"model": "claude-sonnet-4-5",
|
||||||
"open_ai_api_key": "",
|
|
||||||
"open_ai_api_base": "https://api.openai.com/v1",
|
|
||||||
"claude_api_key": "",
|
"claude_api_key": "",
|
||||||
"claude_api_base": "https://api.anthropic.com/v1",
|
"claude_api_base": "https://api.anthropic.com/v1",
|
||||||
|
"open_ai_api_key": "",
|
||||||
|
"open_ai_api_base": "https://api.openai.com/v1",
|
||||||
"gemini_api_key": "",
|
"gemini_api_key": "",
|
||||||
"gemini_api_base": "https://generativelanguage.googleapis.com",
|
"gemini_api_base": "https://generativelanguage.googleapis.com",
|
||||||
|
"zhipu_ai_api_key": "",
|
||||||
"voice_to_text": "openai",
|
"voice_to_text": "openai",
|
||||||
"text_to_voice": "openai",
|
"text_to_voice": "openai",
|
||||||
"voice_reply_voice": false,
|
"voice_reply_voice": false,
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ from config import conf
|
|||||||
|
|
||||||
class ZhipuAIImage(object):
|
class ZhipuAIImage(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
from zhipuai import ZhipuAI
|
from zai import ZhipuAiClient
|
||||||
self.client = ZhipuAI(api_key=conf().get("zhipu_ai_api_key"))
|
self.client = ZhipuAiClient(api_key=conf().get("zhipu_ai_api_key"))
|
||||||
|
|
||||||
def create_img(self, query, retry_count=0, api_key=None, api_base=None):
|
def create_img(self, query, retry_count=0, api_key=None, api_base=None):
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
# encoding:utf-8
|
# encoding:utf-8
|
||||||
|
|
||||||
import time
|
import time
|
||||||
|
import json
|
||||||
|
|
||||||
import openai
|
|
||||||
import openai.error
|
|
||||||
from models.bot import Bot
|
from models.bot import Bot
|
||||||
from models.zhipuai.zhipu_ai_session import ZhipuAISession
|
from models.zhipuai.zhipu_ai_session import ZhipuAISession
|
||||||
from models.zhipuai.zhipu_ai_image import ZhipuAIImage
|
from models.zhipuai.zhipu_ai_image import ZhipuAIImage
|
||||||
@@ -12,7 +11,7 @@ from bridge.context import ContextType
|
|||||||
from bridge.reply import Reply, ReplyType
|
from bridge.reply import Reply, ReplyType
|
||||||
from common.log import logger
|
from common.log import logger
|
||||||
from config import conf, load_config
|
from config import conf, load_config
|
||||||
from zhipuai import ZhipuAI
|
from zai import ZhipuAiClient
|
||||||
|
|
||||||
|
|
||||||
# ZhipuAI对话模型API
|
# ZhipuAI对话模型API
|
||||||
@@ -25,7 +24,7 @@ class ZHIPUAIBot(Bot, ZhipuAIImage):
|
|||||||
"temperature": conf().get("temperature", 0.9), # 值在(0,1)之间(智谱AI 的温度不能取 0 或者 1)
|
"temperature": conf().get("temperature", 0.9), # 值在(0,1)之间(智谱AI 的温度不能取 0 或者 1)
|
||||||
"top_p": conf().get("top_p", 0.7), # 值在(0,1)之间(智谱AI 的 top_p 不能取 0 或者 1)
|
"top_p": conf().get("top_p", 0.7), # 值在(0,1)之间(智谱AI 的 top_p 不能取 0 或者 1)
|
||||||
}
|
}
|
||||||
self.client = ZhipuAI(api_key=conf().get("zhipu_ai_api_key"))
|
self.client = ZhipuAiClient(api_key=conf().get("zhipu_ai_api_key"))
|
||||||
|
|
||||||
def reply(self, query, context=None):
|
def reply(self, query, context=None):
|
||||||
# acquire reply content
|
# acquire reply content
|
||||||
@@ -49,17 +48,13 @@ class ZHIPUAIBot(Bot, ZhipuAIImage):
|
|||||||
session = self.sessions.session_query(query, session_id)
|
session = self.sessions.session_query(query, session_id)
|
||||||
logger.debug("[ZHIPU_AI] session query={}".format(session.messages))
|
logger.debug("[ZHIPU_AI] session query={}".format(session.messages))
|
||||||
|
|
||||||
api_key = context.get("openai_api_key") or openai.api_key
|
|
||||||
model = context.get("gpt_model")
|
model = context.get("gpt_model")
|
||||||
new_args = None
|
new_args = None
|
||||||
if model:
|
if model:
|
||||||
new_args = self.args.copy()
|
new_args = self.args.copy()
|
||||||
new_args["model"] = model
|
new_args["model"] = model
|
||||||
# if context.get('stream'):
|
|
||||||
# # reply in stream
|
|
||||||
# return self.reply_text_stream(query, new_query, session_id)
|
|
||||||
|
|
||||||
reply_content = self.reply_text(session, api_key, args=new_args)
|
reply_content = self.reply_text(session, args=new_args)
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"[ZHIPU_AI] new_query={}, session_id={}, reply_cont={}, completion_tokens={}".format(
|
"[ZHIPU_AI] new_query={}, session_id={}, reply_cont={}, completion_tokens={}".format(
|
||||||
session.messages,
|
session.messages,
|
||||||
@@ -90,21 +85,17 @@ class ZHIPUAIBot(Bot, ZhipuAIImage):
|
|||||||
reply = Reply(ReplyType.ERROR, "Bot不支持处理{}类型的消息".format(context.type))
|
reply = Reply(ReplyType.ERROR, "Bot不支持处理{}类型的消息".format(context.type))
|
||||||
return reply
|
return reply
|
||||||
|
|
||||||
def reply_text(self, session: ZhipuAISession, api_key=None, args=None, retry_count=0) -> dict:
|
def reply_text(self, session: ZhipuAISession, args=None, retry_count=0) -> dict:
|
||||||
"""
|
"""
|
||||||
call openai's ChatCompletion to get the answer
|
Call ZhipuAI API to get the answer
|
||||||
:param session: a conversation session
|
:param session: a conversation session
|
||||||
:param session_id: session id
|
:param args: request arguments
|
||||||
:param retry_count: retry count
|
:param retry_count: retry count
|
||||||
:return: {}
|
:return: {}
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# if conf().get("rate_limit_chatgpt") and not self.tb4chatgpt.get_token():
|
|
||||||
# raise openai.error.RateLimitError("RateLimitError: rate limit exceeded")
|
|
||||||
# if api_key == None, the default openai.api_key will be used
|
|
||||||
if args is None:
|
if args is None:
|
||||||
args = self.args
|
args = self.args
|
||||||
# response = openai.ChatCompletion.create(api_key=api_key, messages=session.messages, **args)
|
|
||||||
response = self.client.chat.completions.create(messages=session.messages, **args)
|
response = self.client.chat.completions.create(messages=session.messages, **args)
|
||||||
# logger.debug("[ZHIPU_AI] response={}".format(response))
|
# logger.debug("[ZHIPU_AI] response={}".format(response))
|
||||||
# logger.info("[ZHIPU_AI] reply={}, total_tokens={}".format(response.choices[0]['message']['content'], response["usage"]["total_tokens"]))
|
# logger.info("[ZHIPU_AI] reply={}, total_tokens={}".format(response.choices[0]['message']['content'], response["usage"]["total_tokens"]))
|
||||||
@@ -117,23 +108,26 @@ class ZHIPUAIBot(Bot, ZhipuAIImage):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
need_retry = retry_count < 2
|
need_retry = retry_count < 2
|
||||||
result = {"completion_tokens": 0, "content": "我现在有点累了,等会再来吧"}
|
result = {"completion_tokens": 0, "content": "我现在有点累了,等会再来吧"}
|
||||||
if isinstance(e, openai.error.RateLimitError):
|
error_str = str(e).lower()
|
||||||
|
|
||||||
|
# Check error type by error message content
|
||||||
|
if "rate" in error_str and "limit" in error_str:
|
||||||
logger.warn("[ZHIPU_AI] RateLimitError: {}".format(e))
|
logger.warn("[ZHIPU_AI] RateLimitError: {}".format(e))
|
||||||
result["content"] = "提问太快啦,请休息一下再问我吧"
|
result["content"] = "提问太快啦,请休息一下再问我吧"
|
||||||
if need_retry:
|
if need_retry:
|
||||||
time.sleep(20)
|
time.sleep(20)
|
||||||
elif isinstance(e, openai.error.Timeout):
|
elif "timeout" in error_str or "timed out" in error_str:
|
||||||
logger.warn("[ZHIPU_AI] Timeout: {}".format(e))
|
logger.warn("[ZHIPU_AI] Timeout: {}".format(e))
|
||||||
result["content"] = "我没有收到你的消息"
|
result["content"] = "我没有收到你的消息"
|
||||||
if need_retry:
|
if need_retry:
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
elif isinstance(e, openai.error.APIError):
|
elif "api" in error_str and ("error" in error_str or "gateway" in error_str):
|
||||||
logger.warn("[ZHIPU_AI] Bad Gateway: {}".format(e))
|
logger.warn("[ZHIPU_AI] APIError: {}".format(e))
|
||||||
result["content"] = "请再问我一次"
|
result["content"] = "请再问我一次"
|
||||||
if need_retry:
|
if need_retry:
|
||||||
time.sleep(10)
|
time.sleep(10)
|
||||||
elif isinstance(e, openai.error.APIConnectionError):
|
elif "connection" in error_str or "network" in error_str:
|
||||||
logger.warn("[ZHIPU_AI] APIConnectionError: {}".format(e))
|
logger.warn("[ZHIPU_AI] ConnectionError: {}".format(e))
|
||||||
result["content"] = "我连接不到你的网络"
|
result["content"] = "我连接不到你的网络"
|
||||||
if need_retry:
|
if need_retry:
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
@@ -144,6 +138,325 @@ class ZHIPUAIBot(Bot, ZhipuAIImage):
|
|||||||
|
|
||||||
if need_retry:
|
if need_retry:
|
||||||
logger.warn("[ZHIPU_AI] 第{}次重试".format(retry_count + 1))
|
logger.warn("[ZHIPU_AI] 第{}次重试".format(retry_count + 1))
|
||||||
return self.reply_text(session, api_key, args, retry_count + 1)
|
return self.reply_text(session, args, retry_count + 1)
|
||||||
else:
|
else:
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
def call_with_tools(self, messages, tools=None, stream=False, **kwargs):
|
||||||
|
"""
|
||||||
|
Call ZhipuAI API with tool support for agent integration
|
||||||
|
|
||||||
|
This method handles:
|
||||||
|
1. Format conversion (Claude format → ZhipuAI format)
|
||||||
|
2. System prompt injection
|
||||||
|
3. API calling with ZhipuAI SDK
|
||||||
|
4. Tool stream support (tool_stream=True for GLM-4.7)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
messages: List of messages (may be in Claude format from agent)
|
||||||
|
tools: List of tool definitions (may be in Claude format from agent)
|
||||||
|
stream: Whether to use streaming
|
||||||
|
**kwargs: Additional parameters (max_tokens, temperature, system, etc.)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Formatted response or generator for streaming
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Convert messages from Claude format to ZhipuAI format
|
||||||
|
messages = self._convert_messages_to_zhipu_format(messages)
|
||||||
|
|
||||||
|
# Convert tools from Claude format to ZhipuAI format
|
||||||
|
if tools:
|
||||||
|
tools = self._convert_tools_to_zhipu_format(tools)
|
||||||
|
|
||||||
|
# Handle system prompt
|
||||||
|
system_prompt = kwargs.get('system')
|
||||||
|
if system_prompt:
|
||||||
|
# Add system message at the beginning if not already present
|
||||||
|
if not messages or messages[0].get('role') != 'system':
|
||||||
|
messages = [{"role": "system", "content": system_prompt}] + messages
|
||||||
|
else:
|
||||||
|
# Replace existing system message
|
||||||
|
messages[0] = {"role": "system", "content": system_prompt}
|
||||||
|
|
||||||
|
# Build request parameters
|
||||||
|
request_params = {
|
||||||
|
"model": kwargs.get("model", self.args.get("model", "glm-4")),
|
||||||
|
"messages": messages,
|
||||||
|
"temperature": kwargs.get("temperature", self.args.get("temperature", 0.9)),
|
||||||
|
"top_p": kwargs.get("top_p", self.args.get("top_p", 0.7)),
|
||||||
|
"stream": stream
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add max_tokens if specified
|
||||||
|
if kwargs.get("max_tokens"):
|
||||||
|
request_params["max_tokens"] = kwargs["max_tokens"]
|
||||||
|
|
||||||
|
# Add tools if provided
|
||||||
|
if tools:
|
||||||
|
request_params["tools"] = tools
|
||||||
|
# GLM-4.7 with zai-sdk supports tool_stream for streaming tool calls
|
||||||
|
if stream:
|
||||||
|
request_params["tool_stream"] = kwargs.get("tool_stream", True)
|
||||||
|
|
||||||
|
# Add thinking parameter for deep thinking mode (GLM-4.7)
|
||||||
|
thinking = kwargs.get("thinking")
|
||||||
|
if thinking:
|
||||||
|
request_params["thinking"] = thinking
|
||||||
|
elif "glm-4.7" in request_params["model"]:
|
||||||
|
# Enable thinking by default for GLM-4.7
|
||||||
|
request_params["thinking"] = {"type": "enabled"}
|
||||||
|
|
||||||
|
# Make API call with ZhipuAI SDK
|
||||||
|
if stream:
|
||||||
|
return self._handle_stream_response(request_params)
|
||||||
|
else:
|
||||||
|
return self._handle_sync_response(request_params)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
error_msg = str(e)
|
||||||
|
logger.error(f"[ZHIPU_AI] call_with_tools error: {error_msg}")
|
||||||
|
if stream:
|
||||||
|
def error_generator():
|
||||||
|
yield {
|
||||||
|
"error": True,
|
||||||
|
"message": error_msg,
|
||||||
|
"status_code": 500
|
||||||
|
}
|
||||||
|
return error_generator()
|
||||||
|
else:
|
||||||
|
return {
|
||||||
|
"error": True,
|
||||||
|
"message": error_msg,
|
||||||
|
"status_code": 500
|
||||||
|
}
|
||||||
|
|
||||||
|
def _handle_sync_response(self, request_params):
|
||||||
|
"""Handle synchronous ZhipuAI API response"""
|
||||||
|
try:
|
||||||
|
response = self.client.chat.completions.create(**request_params)
|
||||||
|
|
||||||
|
# Convert ZhipuAI response to OpenAI-compatible format
|
||||||
|
return {
|
||||||
|
"id": response.id,
|
||||||
|
"object": "chat.completion",
|
||||||
|
"created": response.created,
|
||||||
|
"model": response.model,
|
||||||
|
"choices": [{
|
||||||
|
"index": 0,
|
||||||
|
"message": {
|
||||||
|
"role": response.choices[0].message.role,
|
||||||
|
"content": response.choices[0].message.content,
|
||||||
|
"tool_calls": self._convert_tool_calls_to_openai_format(
|
||||||
|
getattr(response.choices[0].message, 'tool_calls', None)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
"finish_reason": response.choices[0].finish_reason
|
||||||
|
}],
|
||||||
|
"usage": {
|
||||||
|
"prompt_tokens": response.usage.prompt_tokens,
|
||||||
|
"completion_tokens": response.usage.completion_tokens,
|
||||||
|
"total_tokens": response.usage.total_tokens
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"[ZHIPU_AI] sync response error: {e}")
|
||||||
|
return {
|
||||||
|
"error": True,
|
||||||
|
"message": str(e),
|
||||||
|
"status_code": 500
|
||||||
|
}
|
||||||
|
|
||||||
|
def _handle_stream_response(self, request_params):
|
||||||
|
"""Handle streaming ZhipuAI API response"""
|
||||||
|
try:
|
||||||
|
stream = self.client.chat.completions.create(**request_params)
|
||||||
|
|
||||||
|
# Stream chunks to caller, converting to OpenAI format
|
||||||
|
for chunk in stream:
|
||||||
|
if not chunk.choices:
|
||||||
|
continue
|
||||||
|
|
||||||
|
delta = chunk.choices[0].delta
|
||||||
|
|
||||||
|
# Convert to OpenAI-compatible format
|
||||||
|
openai_chunk = {
|
||||||
|
"id": chunk.id,
|
||||||
|
"object": "chat.completion.chunk",
|
||||||
|
"created": chunk.created,
|
||||||
|
"model": chunk.model,
|
||||||
|
"choices": [{
|
||||||
|
"index": 0,
|
||||||
|
"delta": {},
|
||||||
|
"finish_reason": chunk.choices[0].finish_reason
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add role if present
|
||||||
|
if hasattr(delta, 'role') and delta.role:
|
||||||
|
openai_chunk["choices"][0]["delta"]["role"] = delta.role
|
||||||
|
|
||||||
|
# Add content if present
|
||||||
|
if hasattr(delta, 'content') and delta.content:
|
||||||
|
openai_chunk["choices"][0]["delta"]["content"] = delta.content
|
||||||
|
|
||||||
|
# Add reasoning_content if present (GLM-4.7 specific)
|
||||||
|
if hasattr(delta, 'reasoning_content') and delta.reasoning_content:
|
||||||
|
# Store reasoning in content or metadata
|
||||||
|
if "content" not in openai_chunk["choices"][0]["delta"]:
|
||||||
|
openai_chunk["choices"][0]["delta"]["content"] = ""
|
||||||
|
# Prepend reasoning to content
|
||||||
|
openai_chunk["choices"][0]["delta"]["content"] = delta.reasoning_content + openai_chunk["choices"][0]["delta"].get("content", "")
|
||||||
|
|
||||||
|
# Add tool_calls if present
|
||||||
|
if hasattr(delta, 'tool_calls') and delta.tool_calls:
|
||||||
|
# For streaming, tool_calls need special handling
|
||||||
|
openai_tool_calls = []
|
||||||
|
for tc in delta.tool_calls:
|
||||||
|
tool_call_dict = {
|
||||||
|
"index": getattr(tc, 'index', 0),
|
||||||
|
"id": getattr(tc, 'id', None),
|
||||||
|
"type": "function",
|
||||||
|
"function": {}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add function name if present
|
||||||
|
if hasattr(tc, 'function') and hasattr(tc.function, 'name') and tc.function.name:
|
||||||
|
tool_call_dict["function"]["name"] = tc.function.name
|
||||||
|
|
||||||
|
# Add function arguments if present
|
||||||
|
if hasattr(tc, 'function') and hasattr(tc.function, 'arguments') and tc.function.arguments:
|
||||||
|
tool_call_dict["function"]["arguments"] = tc.function.arguments
|
||||||
|
|
||||||
|
openai_tool_calls.append(tool_call_dict)
|
||||||
|
|
||||||
|
openai_chunk["choices"][0]["delta"]["tool_calls"] = openai_tool_calls
|
||||||
|
|
||||||
|
yield openai_chunk
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"[ZHIPU_AI] stream response error: {e}")
|
||||||
|
yield {
|
||||||
|
"error": True,
|
||||||
|
"message": str(e),
|
||||||
|
"status_code": 500
|
||||||
|
}
|
||||||
|
|
||||||
|
def _convert_tools_to_zhipu_format(self, tools):
|
||||||
|
"""
|
||||||
|
Convert tools from Claude format to ZhipuAI format
|
||||||
|
|
||||||
|
Claude format: {name, description, input_schema}
|
||||||
|
ZhipuAI format: {type: "function", function: {name, description, parameters}}
|
||||||
|
"""
|
||||||
|
if not tools:
|
||||||
|
return None
|
||||||
|
|
||||||
|
zhipu_tools = []
|
||||||
|
for tool in tools:
|
||||||
|
# Check if already in ZhipuAI/OpenAI format
|
||||||
|
if 'type' in tool and tool['type'] == 'function':
|
||||||
|
zhipu_tools.append(tool)
|
||||||
|
else:
|
||||||
|
# Convert from Claude format
|
||||||
|
zhipu_tools.append({
|
||||||
|
"type": "function",
|
||||||
|
"function": {
|
||||||
|
"name": tool.get("name"),
|
||||||
|
"description": tool.get("description"),
|
||||||
|
"parameters": tool.get("input_schema", {})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return zhipu_tools
|
||||||
|
|
||||||
|
def _convert_messages_to_zhipu_format(self, messages):
|
||||||
|
"""
|
||||||
|
Convert messages from Claude format to ZhipuAI format
|
||||||
|
|
||||||
|
Claude uses content blocks with types like 'tool_use', 'tool_result'
|
||||||
|
ZhipuAI uses 'tool_calls' in assistant messages and 'tool' role for results
|
||||||
|
"""
|
||||||
|
if not messages:
|
||||||
|
return []
|
||||||
|
|
||||||
|
zhipu_messages = []
|
||||||
|
|
||||||
|
for msg in messages:
|
||||||
|
role = msg.get("role")
|
||||||
|
content = msg.get("content")
|
||||||
|
|
||||||
|
# Handle string content (already in correct format)
|
||||||
|
if isinstance(content, str):
|
||||||
|
zhipu_messages.append(msg)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Handle list content (Claude format with content blocks)
|
||||||
|
if isinstance(content, list):
|
||||||
|
# Check if this is a tool result message (user role with tool_result blocks)
|
||||||
|
if role == "user" and any(block.get("type") == "tool_result" for block in content):
|
||||||
|
# Convert each tool_result block to a separate tool message
|
||||||
|
for block in content:
|
||||||
|
if block.get("type") == "tool_result":
|
||||||
|
zhipu_messages.append({
|
||||||
|
"role": "tool",
|
||||||
|
"tool_call_id": block.get("tool_use_id"),
|
||||||
|
"content": block.get("content", "")
|
||||||
|
})
|
||||||
|
|
||||||
|
# Check if this is an assistant message with tool_use blocks
|
||||||
|
elif role == "assistant":
|
||||||
|
# Separate text content and tool_use blocks
|
||||||
|
text_parts = []
|
||||||
|
tool_calls = []
|
||||||
|
|
||||||
|
for block in content:
|
||||||
|
if block.get("type") == "text":
|
||||||
|
text_parts.append(block.get("text", ""))
|
||||||
|
elif block.get("type") == "tool_use":
|
||||||
|
tool_calls.append({
|
||||||
|
"id": block.get("id"),
|
||||||
|
"type": "function",
|
||||||
|
"function": {
|
||||||
|
"name": block.get("name"),
|
||||||
|
"arguments": json.dumps(block.get("input", {}))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
# Build ZhipuAI format assistant message
|
||||||
|
zhipu_msg = {
|
||||||
|
"role": "assistant",
|
||||||
|
"content": " ".join(text_parts) if text_parts else None
|
||||||
|
}
|
||||||
|
|
||||||
|
if tool_calls:
|
||||||
|
zhipu_msg["tool_calls"] = tool_calls
|
||||||
|
|
||||||
|
zhipu_messages.append(zhipu_msg)
|
||||||
|
else:
|
||||||
|
# Other list content, keep as is
|
||||||
|
zhipu_messages.append(msg)
|
||||||
|
else:
|
||||||
|
# Other formats, keep as is
|
||||||
|
zhipu_messages.append(msg)
|
||||||
|
|
||||||
|
return zhipu_messages
|
||||||
|
|
||||||
|
def _convert_tool_calls_to_openai_format(self, tool_calls):
|
||||||
|
"""Convert ZhipuAI tool_calls to OpenAI format"""
|
||||||
|
if not tool_calls:
|
||||||
|
return None
|
||||||
|
|
||||||
|
openai_tool_calls = []
|
||||||
|
for tool_call in tool_calls:
|
||||||
|
openai_tool_calls.append({
|
||||||
|
"id": tool_call.id,
|
||||||
|
"type": "function",
|
||||||
|
"function": {
|
||||||
|
"name": tool_call.function.name,
|
||||||
|
"arguments": tool_call.function.arguments
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return openai_tool_calls
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ broadscope_bailian
|
|||||||
google-generativeai
|
google-generativeai
|
||||||
|
|
||||||
# zhipuai
|
# zhipuai
|
||||||
zhipuai>=2.0.1
|
zai-sdk
|
||||||
|
|
||||||
# tongyi qwen new sdk
|
# tongyi qwen new sdk
|
||||||
dashscope
|
dashscope
|
||||||
|
|||||||
Reference in New Issue
Block a user