From 54eefba6f8f710b78f1169c108b6f898abec3696 Mon Sep 17 00:00:00 2001 From: wangliang Date: Mon, 26 Jan 2026 18:36:27 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20JSON=20=E8=A7=A3?= =?UTF-8?q?=E6=9E=90=E5=AF=BC=E8=87=B4=E7=9A=84=20tool=5Fname=20=E4=B8=A2?= =?UTF-8?q?=E5=A4=B1=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 问题 商品搜索时工具名丢失,导致 404 错误: ``` HTTP Request: POST http://product_mcp:8004/tools/ "HTTP/1.1 404 Not Found" ``` URL 应该是 `/tools/search_products` 但实际是 `/tools/`(工具名丢失) ## 根本原因 当 LLM 返回带 ```json``` 代码块格式的 JSON 时: ``` ```json { "action": "call_tool", "tool_name": "search_products", "arguments": {"keyword": "ring"} } ``` ``` 解析逻辑处理后: 1. 移除 ```` → 得到 `json\n{\n...` 2. 移除 `json` → 得到 `\n{\n...` 3. 内容以换行符开头,不是 `{` 4. 被误判为非 JSON 格式(`tool_name\n{args}`) 5. 按换行符分割,第一行为空 → `tool_name = ""` ## 解决方案 **第 189 行**:添加 `content.strip()` 去除前后空白 ```python if content.startswith("```"): content = content.split("```")[1] if content.startswith("json"): content = content[4:] # Remove leading/trailing whitespace after removing code block markers content = content.strip() # ← 新增 ``` ## 额外改进 **第 217-224 行**:添加工具调用日志 ```python logger.info( "Product agent calling tool", tool_name=tool_name, arguments=arguments, conversation_id=state["conversation_id"] ) ``` 便于调试工具调用问题。 ## 测试验证 修复前: ``` tool_name = "" (空字符串) URL: /tools/ (缺少工具名) ``` 修复后: ``` tool_name = "search_products" (正确) URL: /tools/search_products (完整路径) ``` Co-Authored-By: Claude Sonnet 4.5 --- agent/agents/product.py | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/agent/agents/product.py b/agent/agents/product.py index ae05210..f2ba0f7 100644 --- a/agent/agents/product.py +++ b/agent/agents/product.py @@ -185,6 +185,8 @@ async def product_agent(state: AgentState) -> AgentState: content = content.split("```")[1] if content.startswith("json"): content = content[4:] + # Remove leading/trailing whitespace after removing code block markers + content = content.strip() # Handle non-JSON format: "tool_name\n{args}" if '\n' in content and not content.startswith('{'): @@ -214,9 +216,17 @@ async def product_agent(state: AgentState) -> AgentState: if action == "call_tool": arguments = result.get("arguments", {}) + tool_name = result.get("tool_name", "") + + logger.info( + "Product agent calling tool", + tool_name=tool_name, + arguments=arguments, + conversation_id=state["conversation_id"] + ) # Inject context for product search (Mall API) - if result["tool_name"] == "search_products": + if tool_name == "search_products": arguments["user_token"] = state.get("user_token") arguments["user_id"] = state["user_id"] arguments["account_id"] = state["account_id"] @@ -230,24 +240,24 @@ async def product_agent(state: AgentState) -> AgentState: ) # Inject context for recommendation - if result["tool_name"] == "recommend_products": + if tool_name == "recommend_products": arguments["user_id"] = state["user_id"] arguments["account_id"] = state["account_id"] # Inject context for quote - if result["tool_name"] == "get_quote": + if tool_name == "get_quote": arguments["account_id"] = state["account_id"] - + # Use entity if available if "product_id" not in arguments and state["entities"].get("product_id"): arguments["product_id"] = state["entities"]["product_id"] - + if "quantity" not in arguments and state["entities"].get("quantity"): arguments["quantity"] = state["entities"]["quantity"] - + state = add_tool_call( state, - tool_name=result["tool_name"], + tool_name=tool_name, arguments=arguments, server="product" )