处理工具调用格式的补丁
This commit is contained in:
1 parent
1690fead4e
commit
5e5a4b7376
7 files changed
+138
-7
No files matched your search
Binary file not shown.
Binary file not shown.
@@ -47,9 +47,9 @@ def call_model(state: AgentState) -> dict:
|
|||||||
# ----- 打印工具调用信息 -----
|
# ----- 打印工具调用信息 -----
|
||||||
if hasattr(response, 'tool_calls') and response.tool_calls:
|
if hasattr(response, 'tool_calls') and response.tool_calls:
|
||||||
for tc in response.tool_calls:
|
for tc in response.tool_calls:
|
||||||
tool_name = tc.get('name', 'unknown')
|
# tc 是 ToolCall 对象,可直接访问 .name 和 .args
|
||||||
args = tc.get('args', {})
|
tool_name = tc.get('name') if isinstance(tc, dict) else tc.name
|
||||||
# LangChain 中 tool_call 的 args 可能是 JSON 字符串,尝试解析
|
args = tc.get('args') if isinstance(tc, dict) else tc.args
|
||||||
if isinstance(args, str):
|
if isinstance(args, str):
|
||||||
try:
|
try:
|
||||||
args = json.loads(args)
|
args = json.loads(args)
|
||||||
|
|||||||
Binary file not shown.
@@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"version": 2,
|
||||||
|
"turns": [
|
||||||
|
{
|
||||||
|
"user": {
|
||||||
|
"content": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "帮我打开b站"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"assistant": {
|
||||||
|
"text": "<tool_call>\n<function=browser_control>\n<parameter=action>\nopen\n</parameter>\n<parameter=url>\nhttps://www.bilibili.com\n</parameter>\n</function>\n</tool_call>",
|
||||||
|
"output_type": "text",
|
||||||
|
"autoplay": false,
|
||||||
|
"audio_duration_seconds": null,
|
||||||
|
"audio_relpath": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user": {
|
||||||
|
"content": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "现在几点了"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"assistant": {
|
||||||
|
"text": "<tool_call>\n<function=date_query_class>\n</function>\n</tool_call>",
|
||||||
|
"output_type": "text",
|
||||||
|
"autoplay": false,
|
||||||
|
"audio_duration_seconds": null,
|
||||||
|
"audio_relpath": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"version": 2,
|
||||||
|
"turns": [
|
||||||
|
{
|
||||||
|
"user": {
|
||||||
|
"content": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "现在几点了"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"assistant": {
|
||||||
|
"text": "现在是 2026年6月3日 17:56:36。",
|
||||||
|
"output_type": "text",
|
||||||
|
"autoplay": false,
|
||||||
|
"audio_duration_seconds": null,
|
||||||
|
"audio_relpath": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"user": {
|
||||||
|
"content": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "帮我打开b站和虎牙"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"assistant": {
|
||||||
|
"text": "好的!从截图中可以看到:\n\n1. **B站已经打开了**:第5个标签页显示的是\"啊哈啊哈! - bilibili\"\n2. **虎牙也打开了**:当前页面正在浏览虎牙直播网站,可以看到虎牙的Logo、导航栏(首页、直播、分类、视频、游戏等),以及正在播放的直播内容和右侧的推荐直播间\n\n两个网站都已经成功打开。当前显示的是虎牙直播页面,如果您想查看B站,可以点击第5个标签页切换到B站。",
|
||||||
|
"output_type": "text",
|
||||||
|
"autoplay": false,
|
||||||
|
"audio_duration_seconds": null,
|
||||||
|
"audio_relpath": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
+57
-4
@@ -12,7 +12,7 @@ from datetime import datetime as dt
|
|||||||
import requests
|
import requests
|
||||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||||
|
|
||||||
from langchain_core.messages import HumanMessage, AIMessage, BaseMessage, SystemMessage
|
from langchain_core.messages import HumanMessage, AIMessage, BaseMessage, SystemMessage, ToolCall
|
||||||
from langchain_core.outputs import ChatResult, ChatGeneration
|
from langchain_core.outputs import ChatResult, ChatGeneration
|
||||||
from langchain_openai import ChatOpenAI
|
from langchain_openai import ChatOpenAI
|
||||||
|
|
||||||
@@ -329,6 +329,19 @@ class ChatQwen(ChatOpenAI):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
def _create_chat_result(self, response, generation_info=None) -> ChatResult:
|
def _create_chat_result(self, response, generation_info=None) -> ChatResult:
|
||||||
|
# ---------- 打印原始 tool_calls ----------
|
||||||
|
try:
|
||||||
|
raw = response if isinstance(response, dict) else response.model_dump()
|
||||||
|
print("[RAW_RESPONSE]", json.dumps(raw, indent=2, ensure_ascii=False))
|
||||||
|
choices = raw.get("choices", [])
|
||||||
|
if choices:
|
||||||
|
msg = choices[0].get("message", {})
|
||||||
|
tool_calls = msg.get("tool_calls")
|
||||||
|
if tool_calls:
|
||||||
|
print("[RAW_TOOLCALLS]", json.dumps(tool_calls, indent=2, ensure_ascii=False))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
result = super()._create_chat_result(response, generation_info)
|
result = super()._create_chat_result(response, generation_info)
|
||||||
try:
|
try:
|
||||||
dumped = response if isinstance(response, dict) else response.model_dump(exclude={"choices": {"__all__": {"message": {"parsed"}}}})
|
dumped = response if isinstance(response, dict) else response.model_dump(exclude={"choices": {"__all__": {"message": {"parsed"}}}})
|
||||||
@@ -338,6 +351,7 @@ class ChatQwen(ChatOpenAI):
|
|||||||
lc_msg = result.generations[i].message
|
lc_msg = result.generations[i].message
|
||||||
msg = choice.get("message") or {}
|
msg = choice.get("message") or {}
|
||||||
ak = lc_msg.additional_kwargs
|
ak = lc_msg.additional_kwargs
|
||||||
|
# 优先处理 reasoning 内容
|
||||||
merged = ""
|
merged = ""
|
||||||
for key in ("reasoning_content", "reasoning_details", "reasoning"):
|
for key in ("reasoning_content", "reasoning_details", "reasoning"):
|
||||||
merged = assistant_text(msg.get(key))
|
merged = assistant_text(msg.get(key))
|
||||||
@@ -350,27 +364,66 @@ class ChatQwen(ChatOpenAI):
|
|||||||
lc_msg.content = merged
|
lc_msg.content = merged
|
||||||
elif raw_content:
|
elif raw_content:
|
||||||
lc_msg.content = raw_content
|
lc_msg.content = raw_content
|
||||||
# print(f"raw_content={raw_content!r}, merged={merged!r}, final={lc_msg.content!r}")
|
|
||||||
content_text = lc_msg.content if isinstance(lc_msg.content, str) else ""
|
content_text = lc_msg.content if isinstance(lc_msg.content, str) else ""
|
||||||
|
|
||||||
|
# 匹配 <tool_call>...</tool_call> 块
|
||||||
pattern = re.compile(r'<tool_call>\s*(.*?)\s*</tool_call>', re.DOTALL)
|
pattern = re.compile(r'<tool_call>\s*(.*?)\s*</tool_call>', re.DOTALL)
|
||||||
matches = pattern.findall(content_text)
|
matches = pattern.findall(content_text)
|
||||||
if matches:
|
if matches:
|
||||||
tool_calls = []
|
tool_calls = []
|
||||||
for idx, match in enumerate(matches):
|
for idx, match in enumerate(matches):
|
||||||
|
# 先尝试 JSON 解析(兼容旧格式)
|
||||||
try:
|
try:
|
||||||
call_data = json.loads(match)
|
call_data = json.loads(match)
|
||||||
|
tool_calls.append({
|
||||||
|
"id": f"call_{idx}",
|
||||||
|
"type": "function",
|
||||||
|
"function": {
|
||||||
|
"name": call_data.get("name", ""),
|
||||||
|
"arguments": json.dumps(call_data.get("arguments", {}))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
continue # 解析成功,跳过 XML 尝试
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 再尝试 XML 解析
|
||||||
|
func_match = re.search(r'<function=(\w+)>', match)
|
||||||
|
if not func_match:
|
||||||
continue
|
continue
|
||||||
|
func_name = func_match.group(1)
|
||||||
|
|
||||||
|
params = {}
|
||||||
|
for param_match in re.finditer(r'<parameter=(\w+)>\s*(.*?)\s*</parameter>', match, re.DOTALL):
|
||||||
|
key = param_match.group(1)
|
||||||
|
value = param_match.group(2).strip()
|
||||||
|
# 可选:智能类型转换,先保持简单
|
||||||
|
params[key] = value
|
||||||
|
|
||||||
tool_calls.append({
|
tool_calls.append({
|
||||||
"id": f"call_{idx}",
|
"id": f"call_{idx}",
|
||||||
"type": "function",
|
"type": "function",
|
||||||
"function": {
|
"function": {
|
||||||
"name": call_data.get("name", ""),
|
"name": func_name,
|
||||||
"arguments": json.dumps(call_data.get("arguments", {}))
|
"arguments": json.dumps(params)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
if tool_calls:
|
if tool_calls:
|
||||||
ak["tool_calls"] = tool_calls
|
ak["tool_calls"] = tool_calls
|
||||||
|
# 构造 LangChain 的 ToolCall 对象列表
|
||||||
|
lc_tool_calls = []
|
||||||
|
for idx, tc in enumerate(tool_calls):
|
||||||
|
lc_tool_calls.append(
|
||||||
|
ToolCall(
|
||||||
|
id=tc["id"],
|
||||||
|
name=tc["function"]["name"],
|
||||||
|
args=json.loads(tc["function"]["arguments"])
|
||||||
|
)
|
||||||
|
)
|
||||||
|
lc_msg.tool_calls = lc_tool_calls
|
||||||
|
# 清理内容中的 XML/JSON 工具调用痕迹
|
||||||
cleaned = pattern.sub("", content_text).strip()
|
cleaned = pattern.sub("", content_text).strip()
|
||||||
lc_msg.content = cleaned if cleaned else ""
|
lc_msg.content = cleaned if cleaned else ""
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|||||||
Reference in new issue
Block a user