fix: 改进错误处理和清理测试代码

## 主要修复

### 1. JSON 解析错误处理
- 修复所有 Agent 的 LLM 响应解析失败时返回原始内容的问题
- 当 JSON 解析失败时,返回友好的兜底消息而不是原始文本
- 影响文件: customer_service.py, order.py, product.py, aftersale.py

### 2. FAQ 快速路径修复
- 修复 customer_service.py 中变量定义顺序问题
- has_faq_query 在使用前未定义导致 NameError
- 添加详细的错误日志记录

### 3. Chatwoot 集成改进
- 添加响应内容调试日志
- 改进错误处理和日志记录

### 4. 订单查询优化
- 将订单列表默认返回数量从 10 条改为 5 条
- 统一 MCP 工具层和 Mall Client 层的默认值

### 5. 代码清理
- 删除所有测试代码和示例文件
- 刋试文件包括: test_*.py, test_*.html, test_*.sh
- 删除测试目录: tests/, agent/tests/, agent/examples/

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
wangliang
2026-01-27 13:15:58 +08:00
parent f4e77f39ce
commit 0f13102a02
21 changed files with 603 additions and 1697 deletions

View File

@@ -66,7 +66,47 @@ async def customer_service_agent(state: AgentState) -> AgentState:
# Get detected language
locale = state.get("detected_language", "en")
# Auto-detect category and query FAQ
# Check if we have already queried FAQ
tool_calls = state.get("tool_calls", [])
has_faq_query = any(tc.get("tool_name") in ["query_faq", "search_knowledge_base"] for tc in tool_calls)
# ========== ROUTING: Use sub_intent from router if available ==========
# Router already classified the intent, use it for direct FAQ query
sub_intent = state.get("sub_intent")
# Map sub_intent to FAQ category
sub_intent_to_category = {
"register_inquiry": "register",
"order_inquiry": "order",
"payment_inquiry": "payment",
"shipment_inquiry": "shipment",
"return_inquiry": "return",
"policy_inquiry": "return", # Policy queries use return FAQ
}
# Check if we should auto-query FAQ based on sub_intent
if sub_intent in sub_intent_to_category and not has_faq_query:
category = sub_intent_to_category[sub_intent]
logger.info(
f"Auto-querying FAQ based on sub_intent: {sub_intent} -> category: {category}",
conversation_id=state["conversation_id"]
)
state = add_tool_call(
state,
tool_name="query_faq",
arguments={
"category": category,
"locale": locale,
"limit": 5
},
server="strapi"
)
state["state"] = ConversationState.TOOL_CALLING.value
return state
# ========================================================================
# Auto-detect category and query FAQ (fallback if sub_intent not available)
message_lower = state["current_message"].lower()
# 定义分类关键词支持多语言en, nl, de, es, fr, it, tr, zh
@@ -163,17 +203,13 @@ async def customer_service_agent(state: AgentState) -> AgentState:
],
}
# 检测分类
# 检测分类(仅在未通过 sub_intent 匹配时使用)
detected_category = None
for category, keywords in category_keywords.items():
if any(keyword in message_lower for keyword in keywords):
detected_category = category
break
# 检查是否已经查询过 FAQ
tool_calls = state.get("tool_calls", [])
has_faq_query = any(tc.get("tool_name") in ["query_faq", "search_knowledge_base"] for tc in tool_calls)
# 如果检测到分类且未查询过 FAQ自动查询
if detected_category and not has_faq_query:
logger.info(
@@ -232,44 +268,73 @@ async def customer_service_agent(state: AgentState) -> AgentState:
try:
llm = get_llm_client()
response = await llm.chat(messages, temperature=0.7)
# Log raw response for debugging
logger.info(
"Customer service LLM response",
conversation_id=state["conversation_id"],
response_preview=response.content[:300] if response.content else "EMPTY",
response_length=len(response.content) if response.content else 0
)
# Parse response
content = response.content.strip()
# Handle markdown code blocks
if content.startswith("```"):
content = content.split("```")[1]
if content.startswith("json"):
content = content[4:]
result = json.loads(content)
action = result.get("action")
if action == "call_tool":
# Add tool call to state
state = add_tool_call(
state,
tool_name=result["tool_name"],
arguments=result.get("arguments", {}),
server="strapi"
parts = content.split("```")
if len(parts) >= 2:
content = parts[1]
if content.startswith("json"):
content = content[4:]
content = content.strip()
try:
result = json.loads(content)
action = result.get("action")
if action == "call_tool":
# Add tool call to state
state = add_tool_call(
state,
tool_name=result["tool_name"],
arguments=result.get("arguments", {}),
server="strapi"
)
state["state"] = ConversationState.TOOL_CALLING.value
elif action == "respond":
state = set_response(state, result["response"])
state["state"] = ConversationState.GENERATING.value
elif action == "handoff":
state["requires_human"] = True
state["handoff_reason"] = result.get("reason", "User request")
else:
# Unknown action, treat as plain text response
logger.warning(
"Unknown action in LLM response",
action=action,
conversation_id=state["conversation_id"]
)
state = set_response(state, response.content)
return state
except json.JSONDecodeError as e:
# JSON parsing failed
logger.error(
"Failed to parse LLM response as JSON",
error=str(e),
raw_content=content[:500],
conversation_id=state["conversation_id"]
)
state["state"] = ConversationState.TOOL_CALLING.value
elif action == "respond":
state = set_response(state, result["response"])
state["state"] = ConversationState.GENERATING.value
elif action == "handoff":
state["requires_human"] = True
state["handoff_reason"] = result.get("reason", "User request")
return state
except json.JSONDecodeError:
# LLM returned plain text, use as response
state = set_response(state, response.content)
return state
# Don't use raw content as response - use fallback instead
state = set_response(state, "抱歉,我无法理解您的请求。请尝试重新表述或联系人工客服。")
return state
except Exception as e:
logger.error("Customer service agent failed", error=str(e))
logger.error("Customer service agent failed", error=str(e), exc_info=True)
state["error"] = str(e)
return state