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: