diff --git a/src/agent/__pycache__/chatbot_agent.cpython-312.pyc b/src/agent/__pycache__/chatbot_agent.cpython-312.pyc
index 17813a5..5669ab9 100644
Binary files a/src/agent/__pycache__/chatbot_agent.cpython-312.pyc and b/src/agent/__pycache__/chatbot_agent.cpython-312.pyc differ
diff --git a/src/agent/__pycache__/llm.cpython-312.pyc b/src/agent/__pycache__/llm.cpython-312.pyc
index 968a909..16446df 100644
Binary files a/src/agent/__pycache__/llm.cpython-312.pyc and b/src/agent/__pycache__/llm.cpython-312.pyc differ
diff --git a/src/agent/chatbot_agent.py b/src/agent/chatbot_agent.py
index c964e88..53bd378 100644
--- a/src/agent/chatbot_agent.py
+++ b/src/agent/chatbot_agent.py
@@ -47,9 +47,9 @@ def call_model(state: AgentState) -> dict:
# ----- 打印工具调用信息 -----
if hasattr(response, 'tool_calls') and response.tool_calls:
for tc in response.tool_calls:
- tool_name = tc.get('name', 'unknown')
- args = tc.get('args', {})
- # LangChain 中 tool_call 的 args 可能是 JSON 字符串,尝试解析
+ # tc 是 ToolCall 对象,可直接访问 .name 和 .args
+ tool_name = tc.get('name') if isinstance(tc, dict) else tc.name
+ args = tc.get('args') if isinstance(tc, dict) else tc.args
if isinstance(args, str):
try:
args = json.loads(args)
diff --git a/src/agent/history/data_base/chat_history.db b/src/agent/history/data_base/chat_history.db
index 8127b2b..5972071 100644
Binary files a/src/agent/history/data_base/chat_history.db and b/src/agent/history/data_base/chat_history.db differ
diff --git a/src/agent/history/multi_inputs/20260603_172211/ui_messages.json b/src/agent/history/multi_inputs/20260603_172211/ui_messages.json
new file mode 100644
index 0000000..c995483
--- /dev/null
+++ b/src/agent/history/multi_inputs/20260603_172211/ui_messages.json
@@ -0,0 +1,39 @@
+{
+ "version": 2,
+ "turns": [
+ {
+ "user": {
+ "content": [
+ {
+ "type": "text",
+ "text": "帮我打开b站"
+ }
+ ]
+ },
+ "assistant": {
+ "text": "\n\n\nopen\n\n\nhttps://www.bilibili.com\n\n\n",
+ "output_type": "text",
+ "autoplay": false,
+ "audio_duration_seconds": null,
+ "audio_relpath": null
+ }
+ },
+ {
+ "user": {
+ "content": [
+ {
+ "type": "text",
+ "text": "现在几点了"
+ }
+ ]
+ },
+ "assistant": {
+ "text": "\n\n\n",
+ "output_type": "text",
+ "autoplay": false,
+ "audio_duration_seconds": null,
+ "audio_relpath": null
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/agent/history/multi_inputs/20260603_175632/ui_messages.json b/src/agent/history/multi_inputs/20260603_175632/ui_messages.json
new file mode 100644
index 0000000..999dab1
--- /dev/null
+++ b/src/agent/history/multi_inputs/20260603_175632/ui_messages.json
@@ -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
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/agent/llm.py b/src/agent/llm.py
index 3554e0e..6f29272 100644
--- a/src/agent/llm.py
+++ b/src/agent/llm.py
@@ -12,7 +12,7 @@ from datetime import datetime as dt
import requests
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_openai import ChatOpenAI
@@ -329,6 +329,19 @@ class ChatQwen(ChatOpenAI):
return result
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)
try:
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
msg = choice.get("message") or {}
ak = lc_msg.additional_kwargs
+ # 优先处理 reasoning 内容
merged = ""
for key in ("reasoning_content", "reasoning_details", "reasoning"):
merged = assistant_text(msg.get(key))
@@ -350,27 +364,66 @@ class ChatQwen(ChatOpenAI):
lc_msg.content = merged
elif 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 ""
+
+ # 匹配 ... 块
pattern = re.compile(r'\s*(.*?)\s*', re.DOTALL)
matches = pattern.findall(content_text)
if matches:
tool_calls = []
for idx, match in enumerate(matches):
+ # 先尝试 JSON 解析(兼容旧格式)
try:
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:
+ pass
+
+ # 再尝试 XML 解析
+ func_match = re.search(r'', match)
+ if not func_match:
continue
+ func_name = func_match.group(1)
+
+ params = {}
+ for param_match in re.finditer(r'\s*(.*?)\s*', match, re.DOTALL):
+ key = param_match.group(1)
+ value = param_match.group(2).strip()
+ # 可选:智能类型转换,先保持简单
+ params[key] = value
+
tool_calls.append({
"id": f"call_{idx}",
"type": "function",
"function": {
- "name": call_data.get("name", ""),
- "arguments": json.dumps(call_data.get("arguments", {}))
+ "name": func_name,
+ "arguments": json.dumps(params)
}
})
+
if 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()
lc_msg.content = cleaned if cleaned else ""
except Exception: