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:
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user