fix: 修复 JSON 解析导致的 tool_name 丢失问题

## 问题
商品搜索时工具名丢失,导致 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 <noreply@anthropic.com>
This commit is contained in:
wangliang
2026-01-26 18:36:27 +08:00
parent 74c28eb838
commit 54eefba6f8

View File

@@ -185,6 +185,8 @@ async def product_agent(state: AgentState) -> AgentState:
content = content.split("```")[1] content = content.split("```")[1]
if content.startswith("json"): if content.startswith("json"):
content = content[4:] content = content[4:]
# Remove leading/trailing whitespace after removing code block markers
content = content.strip()
# Handle non-JSON format: "tool_name\n{args}" # Handle non-JSON format: "tool_name\n{args}"
if '\n' in content and not content.startswith('{'): if '\n' in content and not content.startswith('{'):
@@ -214,9 +216,17 @@ async def product_agent(state: AgentState) -> AgentState:
if action == "call_tool": if action == "call_tool":
arguments = result.get("arguments", {}) 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) # 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_token"] = state.get("user_token")
arguments["user_id"] = state["user_id"] arguments["user_id"] = state["user_id"]
arguments["account_id"] = state["account_id"] arguments["account_id"] = state["account_id"]
@@ -230,12 +240,12 @@ async def product_agent(state: AgentState) -> AgentState:
) )
# Inject context for recommendation # Inject context for recommendation
if result["tool_name"] == "recommend_products": if tool_name == "recommend_products":
arguments["user_id"] = state["user_id"] arguments["user_id"] = state["user_id"]
arguments["account_id"] = state["account_id"] arguments["account_id"] = state["account_id"]
# Inject context for quote # Inject context for quote
if result["tool_name"] == "get_quote": if tool_name == "get_quote":
arguments["account_id"] = state["account_id"] arguments["account_id"] = state["account_id"]
# Use entity if available # Use entity if available
@@ -247,7 +257,7 @@ async def product_agent(state: AgentState) -> AgentState:
state = add_tool_call( state = add_tool_call(
state, state,
tool_name=result["tool_name"], tool_name=tool_name,
arguments=arguments, arguments=arguments,
server="product" server="product"
) )