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

@@ -141,8 +141,15 @@ async def aftersale_agent(state: AgentState) -> AgentState:
return state
except json.JSONDecodeError:
state = set_response(state, response.content)
except json.JSONDecodeError as e:
logger.error(
"Failed to parse aftersale agent LLM response as JSON",
error=str(e),
conversation_id=state.get("conversation_id"),
raw_content=response.content[:500] if response.content else "EMPTY"
)
# Don't use raw content as response - use fallback instead
state = set_response(state, "抱歉,我无法理解您的请求。请尝试重新表述或联系人工客服。")
return state
except Exception as e:

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(
@@ -233,13 +269,27 @@ async def customer_service_agent(state: AgentState) -> AgentState:
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]
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")
@@ -260,16 +310,31 @@ async def customer_service_agent(state: AgentState) -> AgentState:
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:
# LLM returned plain text, use as response
state = set_response(state, response.content)
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"]
)
# 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

View File

@@ -64,8 +64,8 @@ ORDER_AGENT_PROMPT = """你是一个专业的 B2B 订单服务助手。
2. **get_mall_order_list** - 从商城 API 查询订单列表(推荐使用)
- user_token: 用户 token自动注入
- page: 页码(可选,默认 1
- limit: 每页数量(可选,默认 10
- 说明:查询用户的所有订单,按时间倒序排列
- limit: 每页数量(可选,默认 5
- 说明:查询用户的所有订单,按时间倒序排列,返回最近的 5 个订单
3. **get_logistics** - 从商城 API 查询物流信息
- order_id: 订单号(必需)
@@ -339,8 +339,8 @@ async def order_agent(state: AgentState) -> AgentState:
error=str(e),
content_preview=content[:500]
)
# 如果解析失败,尝试将原始内容作为直接回复
state = set_response(state, response.content)
# Don't use raw content as response - use fallback instead
state = set_response(state, "抱歉,我无法理解您的请求。请尝试重新表述或联系人工客服。")
return state
action = result.get("action")
@@ -381,6 +381,14 @@ async def order_agent(state: AgentState) -> AgentState:
if tool_name in ["get_mall_order", "get_logistics", "query_order"]:
arguments["order_id"] = state["entities"]["order_id"]
# Force limit=5 for order list queries (unless explicitly set)
if tool_name == "get_mall_order_list" and "limit" not in arguments:
arguments["limit"] = 5
logger.debug(
"Forced limit=5 for order list query",
conversation_id=state["conversation_id"]
)
state = add_tool_call(
state,
tool_name=result["tool_name"],
@@ -730,8 +738,11 @@ def _parse_mall_order_data(data: dict[str, Any]) -> dict[str, Any]:
if actual_order_data.get("remark") or actual_order_data.get("user_remark"):
order_data["remark"] = actual_order_data.get("remark", actual_order_data.get("user_remark", ""))
# 物流信息(如果有)
if actual_order_data.get("parcels") and len(actual_order_data.get("parcels", [])) > 0:
# 物流信息(如果有)- 添加 has_parcels 标记用于判断是否显示物流按钮
has_parcels = actual_order_data.get("parcels") and len(actual_order_data.get("parcels", [])) > 0
order_data["has_parcels"] = has_parcels
if has_parcels:
# parcels 是一个数组,包含物流信息
first_parcel = actual_order_data["parcels"][0] if isinstance(actual_order_data["parcels"], list) else actual_order_data["parcels"]
if isinstance(first_parcel, dict):

View File

@@ -23,7 +23,7 @@ PRODUCT_AGENT_PROMPT = """你是一个专业的 B2B 商品顾问助手。
1. **search_products** - 搜索商品
- keyword: 搜索关键词(商品名称、编号等)
- page_size: 每页数量(默认 60,最大 100
- page_size: 每页数量(默认 5,最大 100
- page: 页码(默认 1
- 说明:此工具使用 Mall API 搜索商品 SPU支持用户 token 认证,返回卡片格式展示
@@ -231,6 +231,14 @@ async def product_agent(state: AgentState) -> AgentState:
arguments["user_id"] = state["user_id"]
arguments["account_id"] = state["account_id"]
# Set default page_size if not provided
if "page_size" not in arguments:
arguments["page_size"] = 5
# Set default page if not provided
if "page" not in arguments:
arguments["page"] = 1
# Map "query" parameter to "keyword" for compatibility
if "query" in arguments and "keyword" not in arguments:
arguments["keyword"] = arguments.pop("query")
@@ -273,8 +281,15 @@ async def product_agent(state: AgentState) -> AgentState:
return state
except json.JSONDecodeError:
state = set_response(state, response.content)
except json.JSONDecodeError as e:
logger.error(
"Failed to parse product agent LLM response as JSON",
error=str(e),
conversation_id=state.get("conversation_id"),
raw_content=response.content[:500] if response.content else "EMPTY"
)
# Don't use raw content as response - use fallback instead
state = set_response(state, "抱歉,我无法理解您的请求。请尝试重新表述或联系人工客服。")
return state
except Exception as e:

View File

@@ -106,7 +106,7 @@ async def classify_intent(state: AgentState) -> AgentState:
content = response.content.strip()
# Log raw response for debugging
logger.debug(
logger.info(
"LLM response for intent classification",
response_preview=content[:500] if content else "EMPTY",
content_length=len(content) if content else 0

View File

@@ -154,7 +154,8 @@ class ZhipuLLMClient:
)
# Determine if reasoning mode should be used
use_reasoning = enable_reasoning if enable_reasoning is not None else self._should_use_reasoning(formatted_messages)
# 强制禁用深度思考模式以提升响应速度2026-01-26
use_reasoning = False # Override all settings to disable thinking mode
if use_reasoning:
logger.info("Reasoning mode enabled for this request")

View File

@@ -155,6 +155,109 @@ def get_field_label(field_key: str, language: str = "en") -> str:
return ORDER_FIELD_LABELS[language].get(field_key, ORDER_FIELD_LABELS["en"].get(field_key, field_key))
# 订单状态多语言映射
ORDER_STATUS_LABELS = {
"zh": { # 中文
"0": "已取消",
"1": "待支付",
"2": "已支付",
"3": "已发货",
"4": "已签收",
"15": "已完成",
"100": "超时取消",
"unknown": "未知"
},
"en": { # English
"0": "Cancelled",
"1": "Pending Payment",
"2": "Paid",
"3": "Shipped",
"4": "Delivered",
"15": "Completed",
"100": "Timeout Cancelled",
"unknown": "Unknown"
},
"nl": { # Dutch (荷兰语)
"0": "Geannuleerd",
"1": "Wachtend op betaling",
"2": "Betaald",
"3": "Verzonden",
"4": "Geleverd",
"15": "Voltooid",
"100": "Time-out geannuleerd",
"unknown": "Onbekend"
},
"de": { # German (德语)
"0": "Storniert",
"1": "Zahlung ausstehend",
"2": "Bezahlt",
"3": "Versandt",
"4": "Zugestellt",
"15": "Abgeschlossen",
"100": "Zeitüberschreitung storniert",
"unknown": "Unbekannt"
},
"es": { # Spanish (西班牙语)
"0": "Cancelado",
"1": "Pago pendiente",
"2": "Pagado",
"3": "Enviado",
"4": "Entregado",
"15": "Completado",
"100": "Cancelado por tiempo límite",
"unknown": "Desconocido"
},
"fr": { # French (法语)
"0": "Annulé",
"1": "En attente de paiement",
"2": "Payé",
"3": "Expédié",
"4": "Livré",
"15": "Terminé",
"100": "Annulé pour expiration",
"unknown": "Inconnu"
},
"it": { # Italian (意大利语)
"0": "Annullato",
"1": "In attesa di pagamento",
"2": "Pagato",
"3": "Spedito",
"4": "Consegnato",
"15": "Completato",
"100": "Annullato per timeout",
"unknown": "Sconosciuto"
},
"tr": { # Turkish (土耳其语)
"0": "İptal edildi",
"1": "Ödeme bekleniyor",
"2": "Ödendi",
"3": "Kargolandı",
"4": "Teslim edildi",
"15": "Tamamlandı",
"100": "Zaman aşımı iptal edildi",
"unknown": "Bilinmiyor"
}
}
def get_status_label(status_code: str, language: str = "en") -> str:
"""获取指定语言的订单状态标签
Args:
status_code: 状态码(如 "0", "1", "2" 等)
language: 语言代码(默认 "en"
Returns:
对应语言的状态标签
"""
if language not in ORDER_STATUS_LABELS:
language = "en" # 默认使用英文
return ORDER_STATUS_LABELS[language].get(
str(status_code),
ORDER_STATUS_LABELS["en"].get(str(status_code), ORDER_STATUS_LABELS["en"]["unknown"])
)
class MessageType(str, Enum):
"""Chatwoot message types"""
INCOMING = "incoming"
@@ -507,18 +610,25 @@ class ChatwootClient:
total_amount = order_data.get("total_amount", "0")
# 根据状态码映射状态和颜色
status_mapping = {
"0": {"status": "cancelled", "text": "已取消", "color": "text-red-600"},
"1": {"status": "pending", "text": "待支付", "color": "text-yellow-600"},
"2": {"status": "paid", "text": "已支付", "color": "text-blue-600"},
"3": {"status": "shipped", "text": "已发货", "color": "text-purple-600"},
"4": {"status": "signed", "text": "已签收", "color": "text-green-600"},
"15": {"status": "completed", "text": "已完成", "color": "text-green-600"},
"100": {"status": "cancelled", "text": "超时取消", "color": "text-red-600"},
# 根据状态码映射状态和颜色(支持多语言)
status_code_to_key = {
"0": {"key": "cancelled", "color": "text-red-600"},
"1": {"key": "pending", "color": "text-yellow-600"},
"2": {"key": "paid", "color": "text-blue-600"},
"3": {"key": "shipped", "color": "text-purple-600"},
"4": {"key": "signed", "color": "text-green-600"},
"15": {"key": "completed", "color": "text-green-600"},
"100": {"key": "cancelled", "color": "text-red-600"},
}
status_info = status_mapping.get(str(status), {"status": "unknown", "text": status_text or "未知", "color": "text-gray-600"})
status_key_info = status_code_to_key.get(str(status), {"key": "unknown", "color": "text-gray-600"})
status_label = get_status_label(str(status), language)
status_info = {
"status": status_key_info["key"],
"text": status_label,
"color": status_key_info["color"]
}
# 构建商品列表
items = order_data.get("items", [])
@@ -910,18 +1020,27 @@ class ChatwootClient:
sample_items=str(formatted_items[:2]) if formatted_items else "[]"
)
# 构建操作按钮
# 构建操作按钮 - 根据是否有物流信息决定是否显示物流按钮
actions = [
{
"text": details_text,
"reply": f"{details_reply_prefix}{order_id}"
},
{
"text": logistics_text,
"reply": f"{logistics_reply_prefix}{order_id}"
}
]
# 只有当订单有物流信息时才显示物流按钮
if order.get("has_parcels", False):
actions.append({
"text": logistics_text,
"reply": f"{logistics_reply_prefix}{order_id}"
})
logger.debug(
f"Built {len(actions)} actions for order {order_id}",
has_parcels=order.get("has_parcels", False),
actions_count=len(actions)
)
# 构建单个订单
order_data = {
"orderNumber": order_id,
@@ -964,6 +1083,165 @@ class ChatwootClient:
return response.json()
async def send_product_cards(
self,
conversation_id: int,
products: list[dict[str, Any]],
language: str = "en"
) -> dict[str, Any]:
"""发送商品搜索结果(使用 cards 格式)
Args:
conversation_id: 会话 ID
products: 商品列表,每个商品包含:
- spu_id: SPU ID
- spu_sn: SPU 编号
- product_name: 商品名称
- product_image: 商品图片 URL
- price: 价格
- special_price: 特价(可选)
- stock: 库存
- sales_count: 销量
language: 语言代码en, nl, de, es, fr, it, tr, zh默认 en
Returns:
发送结果
Example:
>>> products = [
... {
... "spu_id": "12345",
... "product_name": "Product A",
... "product_image": "https://...",
... "price": "99.99",
... "stock": 100
... }
... ]
>>> await chatwoot.send_product_cards(123, products, language="zh")
"""
# 获取前端 URL
frontend_url = settings.frontend_url.rstrip('/')
# 构建商品卡片
cards = []
for product in products:
spu_id = product.get("spu_id", "")
spu_sn = product.get("spu_sn", "")
product_name = product.get("product_name", "Unknown Product")
product_image = product.get("product_image", "")
price = product.get("price", "0")
special_price = product.get("special_price")
stock = product.get("stock", 0)
sales_count = product.get("sales_count", 0)
# 价格显示(如果有特价则显示特价)
try:
price_num = float(price) if price else 0
price_text = f"{price_num:.2f}"
except (ValueError, TypeError):
price_text = str(price) if price else "€0.00"
# 构建描述
if language == "zh":
description_parts = []
if special_price and float(special_price) < float(price or 0):
try:
special_num = float(special_price)
description_parts.append(f"特价: €{special_num:.2f}")
except:
pass
if stock is not None:
description_parts.append(f"库存: {stock}")
if sales_count:
description_parts.append(f"已售: {sales_count}")
description = " | ".join(description_parts) if description_parts else "暂无详细信息"
else:
description_parts = []
if special_price and float(special_price) < float(price or 0):
try:
special_num = float(special_price)
description_parts.append(f"Special: €{special_num:.2f}")
except:
pass
if stock is not None:
description_parts.append(f"Stock: {stock}")
if sales_count:
description_parts.append(f"Sold: {sales_count}")
description = " | ".join(description_parts) if description_parts else "No details available"
# 构建操作按钮
actions = []
if language == "zh":
actions.append({
"type": "link",
"text": "查看详情",
"uri": f"{frontend_url}/product/detail?spuId={spu_id}"
})
if stock and stock > 0:
actions.append({
"type": "link",
"text": "立即购买",
"uri": f"{frontend_url}/product/detail?spuId={spu_id}"
})
else:
actions.append({
"type": "link",
"text": "View Details",
"uri": f"{frontend_url}/product/detail?spuId={spu_id}"
})
if stock and stock > 0:
actions.append({
"type": "link",
"text": "Buy Now",
"uri": f"{frontend_url}/product/detail?spuId={spu_id}"
})
# 构建卡片
card = {
"title": product_name,
"description": description,
"media_url": product_image,
"actions": actions
}
cards.append(card)
# 发送 cards 类型消息
client = await self._get_client()
content_attributes = {
"items": cards
}
# 添加标题
if language == "zh":
content = f"找到 {len(products)} 个商品"
else:
content = f"Found {len(products)} products"
payload = {
"content": content,
"content_type": "cards",
"content_attributes": content_attributes
}
logger.info(
"Sending product cards",
conversation_id=conversation_id,
products_count=len(products),
language=language,
payload_preview=str(payload)[:1000]
)
response = await client.post(
f"/conversations/{conversation_id}/messages",
json=payload
)
response.raise_for_status()
return response.json()
# ============ Conversations ============
async def get_conversation(self, conversation_id: int) -> dict[str, Any]:

View File

@@ -51,6 +51,8 @@ system_prompt: |
## Output Format
⚠️ **CRITICAL**: You MUST return a valid JSON object. Do NOT chat with the user. Do NOT provide explanations outside the JSON.
Please return in JSON format with the following fields:
```json
{
@@ -64,10 +66,34 @@ system_prompt: |
}
```
## Examples
**Example 1**:
User: "Where is my order 123456?"
Response:
```json
{"intent": "order", "confidence": 0.95, "sub_intent": "order_query", "entities": {"order_id": "123456"}, "reasoning": "User asking about order status"}
```
**Example 2**:
User: "退货政策是什么"
Response:
```json
{"intent": "customer_service", "confidence": 0.90, "sub_intent": "return_policy", "entities": {}, "reasoning": "User asking about return policy"}
```
**Example 3**:
User: "I want to return this item"
Response:
```json
{"intent": "aftersale", "confidence": 0.85, "sub_intent": "return_request", "entities": {}, "reasoning": "User wants to return an item"}
```
## Notes
- If intent is unclear, confidence should be lower
- If unable to determine intent, return "unknown"
- Entity extraction should be accurate, don't fill in fields that don't exist
- **ALWAYS return JSON, NEVER return plain text**
tool_descriptions:
classify: "Classify user intent and extract entities"

View File

@@ -1,55 +0,0 @@
"""
测试端点 - 用于测试退货 FAQ
"""
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
from core.graph import process_message
router = APIRouter(prefix="/test", tags=["test"])
class TestRequest(BaseModel):
"""测试请求"""
conversation_id: str
user_id: str
account_id: str
message: str
history: list = []
context: dict = {}
@router.post("/faq")
async def test_faq(request: TestRequest):
"""测试 FAQ 回答
简化的测试端点,用于测试退货相关 FAQ
"""
try:
# 调用处理流程
result = await process_message(
conversation_id=request.conversation_id,
user_id=request.user_id,
account_id=request.account_id,
message=request.message,
history=request.history,
context=request.context
)
return {
"success": True,
"response": result.get("response"),
"intent": result.get("intent"),
"tool_calls": result.get("tool_calls", []),
"step_count": result.get("step_count", 0)
}
except Exception as e:
import traceback
traceback.print_exc()
return {
"success": False,
"error": str(e),
"response": None
}

View File

@@ -350,6 +350,15 @@ async def handle_incoming_message(payload: ChatwootWebhookPayload, cookie_token:
if response is None:
response = "抱歉,我暂时无法处理您的请求。请稍后重试或联系人工客服。"
# Log the response content for debugging
logger.info(
"Preparing to send response to Chatwoot",
conversation_id=conversation_id,
response_length=len(response) if response else 0,
response_preview=response[:200] if response else None,
has_response=bool(response)
)
# Create Chatwoot client已在前面创建这里不需要再次创建
# chatwoot 已在 try 块之前创建
@@ -359,6 +368,10 @@ async def handle_incoming_message(payload: ChatwootWebhookPayload, cookie_token:
conversation_id=conversation.id,
content=response
)
logger.info(
"Response sent to Chatwoot successfully",
conversation_id=conversation_id
)
# 关闭 typing status隐藏"正在输入..."
try:

View File

@@ -1,337 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>B2B AI 助手 - 调试版本</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
max-width: 1400px;
margin: 0 auto;
padding: 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
}
.container {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
}
.left-panel, .right-panel {
background: white;
border-radius: 10px;
padding: 20px;
box-shadow: 0 10px 40px rgba(0,0,0,0.1);
}
h1 {
color: #333;
text-align: center;
margin-bottom: 10px;
grid-column: 1 / -1;
}
.subtitle {
text-align: center;
color: #666;
margin-bottom: 30px;
grid-column: 1 / -1;
}
h2 {
color: #667eea;
border-bottom: 2px solid #667eea;
padding-bottom: 10px;
margin-top: 0;
}
.log-container {
background: #1e1e1e;
color: #00ff00;
padding: 15px;
border-radius: 5px;
height: 400px;
overflow-y: auto;
font-family: 'Courier New', monospace;
font-size: 12px;
}
.log-entry {
margin: 5px 0;
padding: 3px 0;
}
.log-info { color: #00ff00; }
.log-warn { color: #ffaa00; }
.log-error { color: #ff4444; }
.log-success { color: #44ff44; }
.status-box {
background: #f8f9fa;
border-left: 4px solid #667eea;
padding: 15px;
margin: 10px 0;
border-radius: 5px;
}
.status-item {
display: flex;
justify-content: space-between;
padding: 8px 0;
border-bottom: 1px solid #ddd;
}
.status-item:last-child {
border-bottom: none;
}
.status-label {
font-weight: bold;
color: #667eea;
}
.status-value {
color: #333;
}
.test-buttons {
display: flex;
gap: 10px;
flex-wrap: wrap;
margin: 15px 0;
}
.test-btn {
background: #667eea;
color: white;
border: none;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
transition: background 0.3s;
}
.test-btn:hover {
background: #5568d3;
}
.test-btn:disabled {
background: #ccc;
cursor: not-allowed;
}
.clear-btn {
background: #ff4444;
}
.clear-btn:hover {
background: #dd3333;
}
</style>
</head>
<body>
<h1>🤖 B2B AI 智能客服助手 - 调试面板</h1>
<p class="subtitle">实时监控 Widget 状态和消息流</p>
<div class="container">
<div class="left-panel">
<h2>📊 连接状态</h2>
<div class="status-box">
<div class="status-item">
<span class="status-label">Chatwoot 服务:</span>
<span class="status-value" id="chatwootStatus">检查中...</span>
</div>
<div class="status-item">
<span class="status-label">Widget SDK:</span>
<span class="status-value" id="widgetStatus">未加载</span>
</div>
<div class="status-item">
<span class="status-label">WebSocket:</span>
<span class="status-value" id="wsStatus">未连接</span>
</div>
<div class="status-item">
<span class="status-label">当前会话:</span>
<span class="status-value" id="conversationId"></span>
</div>
<div class="status-item">
<span class="status-label">Website Token:</span>
<span class="status-value" id="websiteToken">39PNCMvbMk3NvB7uaDNucc6o</span>
</div>
</div>
<h2>🧪 测试操作</h2>
<div class="test-buttons">
<button class="test-btn" onclick="checkChatwootService()">检查服务</button>
<button class="test-btn" onclick="refreshWidget()">刷新 Widget</button>
<button class="test-btn" onclick="getConversationInfo()">获取会话信息</button>
<button class="test-btn clear-btn" onclick="clearLogs()">清除日志</button>
</div>
<h2>📝 快速测试问题(点击复制到剪贴板)</h2>
<div class="test-buttons">
<button class="test-btn" onclick="sendTestMessage('你好')">👋 你好</button>
<button class="test-btn" onclick="sendTestMessage('查询订单 202071324')">📦 查询订单</button>
<button class="test-btn" onclick="sendTestMessage('如何退货?')">❓ 如何退货</button>
<button class="test-btn" onclick="sendTestMessage('营业时间')">🕐 营业时间</button>
</div>
<p style="color: #666; font-size: 14px; margin-top: 10px;">
💡 提示:点击按钮后,在右下角聊天窗口中按 Ctrl+V 粘贴并发送
</p>
</div>
<div class="right-panel">
<h2>📋 实时日志</h2>
<div class="log-container" id="logContainer">
<div class="log-entry log-info">[系统] 日志系统已启动...</div>
</div>
</div>
</div>
<script>
const logContainer = document.getElementById('logContainer');
function addLog(message, type = 'info') {
const timestamp = new Date().toLocaleTimeString();
const logEntry = document.createElement('div');
logEntry.className = `log-entry log-${type}`;
logEntry.textContent = `[${timestamp}] ${message}`;
logContainer.appendChild(logEntry);
logContainer.scrollTop = logContainer.scrollHeight;
}
function clearLogs() {
logContainer.innerHTML = '<div class="log-entry log-info">[系统] 日志已清除</div>';
}
// 检查 Chatwoot 服务
async function checkChatwootService() {
addLog('检查 Chatwoot 服务状态...', 'info');
const statusEl = document.getElementById('chatwootStatus');
try {
const response = await fetch('http://localhost:3000', { mode: 'no-cors' });
statusEl.textContent = '✅ 运行中';
statusEl.style.color = '#28a745';
addLog('✅ Chatwoot 服务运行正常', 'success');
} catch (error) {
statusEl.textContent = '❌ 无法访问';
statusEl.style.color = '#dc3545';
addLog(`❌ 无法连接到 Chatwoot: ${error.message}`, 'error');
}
}
// 发送测试消息 - 直接复制到剪贴板
function sendTestMessage(message) {
addLog(`📋 已复制消息到剪贴板: "${message}"`, 'info');
addLog('→ 请在右下角聊天窗口中粘贴并发送', 'warn');
// 复制到剪贴板
navigator.clipboard.writeText(message).then(() => {
// 可选:自动打开 Widget
if (window.$chatwoot && window.$chatwoot.toggle) {
try {
window.$chatwoot.toggle('open');
addLog('✅ 聊天窗口已打开', 'success');
} catch (e) {
addLog('⚠️ 无法自动打开聊天窗口', 'warn');
}
}
}).catch(err => {
addLog(`❌ 复制失败: ${err.message}`, 'error');
});
}
// 刷新 Widget
function refreshWidget() {
addLog('刷新 Widget...', 'info');
location.reload();
}
// 获取会话信息
function getConversationInfo() {
if (window.$chatwoot) {
try {
const info = window.$chatwoot.getConversationInfo();
addLog(`会话信息: ${JSON.stringify(info)}`, 'info');
} catch (error) {
addLog(`无法获取会话信息: ${error.message}`, 'warn');
}
}
}
// 页面加载时检查服务
window.addEventListener('load', function() {
setTimeout(checkChatwootService, 1000);
});
// ==================== Chatwoot Widget 配置 ====================
window.chatwootSettings = {
"position": "right",
"type": "expanded_bubble",
"launcherTitle": "Chat with us"
};
(function(d,t) {
var BASE_URL = "http://localhost:3000";
var g = d.createElement(t), s = d.getElementsByTagName(t)[0];
g.src = BASE_URL + "/packs/js/sdk.js";
g.async = true;
g.onload = function() {
addLog('Chatwoot SDK 文件已加载', 'success');
document.getElementById('widgetStatus').textContent = '✅ 已加载';
window.chatwootSDK.run({
websiteToken: '39PNCMvbMk3NvB7uaDNucc6o',
baseUrl: BASE_URL
});
addLog('Website Token: 39PNCMvbMk3NvB7uaDNucc6o', 'info');
addLog('Base URL: ' + BASE_URL, 'info');
// 监听 Widget 就绪事件
setTimeout(function() {
if (window.$chatwoot) {
addLog('✅ Chatwoot Widget 已初始化', 'success');
document.getElementById('wsStatus').textContent = '✅ 已连接';
// 设置用户信息(可选)
window.$chatwoot.setUser('debug_user_' + Date.now(), {
email: 'debug@example.com',
name: 'Debug User'
});
addLog('用户信息已设置', 'info');
} else {
addLog('❌ Widget 初始化失败', 'error');
document.getElementById('widgetStatus').textContent = '❌ 初始化失败';
}
}, 2000);
};
g.onerror = function() {
addLog('❌ Chatwoot SDK 加载失败', 'error');
document.getElementById('widgetStatus').textContent = '❌ 加载失败';
};
s.parentNode.insertBefore(g, s);
})(document, "script");
// 监听网络错误
window.addEventListener('error', function(e) {
if (e.message.includes('404')) {
addLog(`⚠️ 404 错误: ${e.filename}`, 'warn');
}
});
// 拦截 fetch 请求
const originalFetch = window.fetch;
window.fetch = function(...args) {
const url = args[0];
// 记录发送到 Chatwoot API 的请求
if (typeof url === 'string' && url.includes('localhost:3000')) {
const method = args[1]?.method || 'GET';
addLog(`API 请求: ${method} ${url}`, 'info');
}
return originalFetch.apply(this, args).then(response => {
// 记录错误响应
if (!response.ok && url.includes('localhost:3000')) {
addLog(`API 响应: ${response.status} ${response.statusText} - ${url}`, 'error');
}
return response;
});
};
addLog('调试系统已初始化', 'success');
</script>
<!-- Chatwoot Widget 会自动加载 -->
</body>
</html>

View File

@@ -1,262 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>B2B AI 助手 - 测试页面</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
}
.container {
background: white;
border-radius: 10px;
padding: 40px;
box-shadow: 0 10px 40px rgba(0,0,0,0.1);
}
h1 {
color: #333;
text-align: center;
margin-bottom: 10px;
}
.subtitle {
text-align: center;
color: #666;
margin-bottom: 30px;
}
.info-box {
background: #f8f9fa;
border-left: 4px solid #667eea;
padding: 15px 20px;
margin: 20px 0;
border-radius: 5px;
}
.info-box h3 {
margin-top: 0;
color: #667eea;
}
.feature-list {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
margin: 30px 0;
}
.feature-card {
background: #f8f9fa;
padding: 20px;
border-radius: 8px;
text-align: center;
}
.feature-card h4 {
color: #667eea;
margin-bottom: 10px;
}
.test-questions {
background: #fff3cd;
border: 1px solid #ffc107;
padding: 20px;
border-radius: 5px;
margin: 20px 0;
}
.test-questions h3 {
margin-top: 0;
color: #856404;
}
.question-list {
list-style: none;
padding: 0;
}
.question-list li {
background: white;
margin: 10px 0;
padding: 12px 15px;
border-radius: 5px;
cursor: pointer;
transition: all 0.3s;
border: 1px solid #ddd;
}
.question-list li:hover {
background: #667eea;
color: white;
transform: translateX(5px);
}
.status {
text-align: center;
padding: 15px;
border-radius: 5px;
margin: 20px 0;
font-weight: bold;
}
.status.online {
background: #d4edda;
color: #155724;
}
.status.testing {
background: #fff3cd;
color: #856404;
}
</style>
</head>
<body>
<div class="container">
<h1>🤖 B2B AI 智能客服助手</h1>
<p class="subtitle">基于 LangGraph + MCP 的智能客服系统</p>
<div class="status online">
✅ 系统状态:所有服务运行正常
</div>
<div id="tokenStatus" class="status testing" style="display: none;">
🍪 Token 状态:检测中...
</div>
<div class="info-box">
<h3>📝 如何测试</h3>
<ol>
<li>点击右下角的聊天图标打开对话窗口</li>
<li>输入你的名字开始对话</li>
<li>尝试下面的问题测试 AI 能力</li>
<li>查看 AI 如何理解并回答你的问题</li>
</ol>
</div>
<div class="test-questions">
<h3>💬 推荐测试问题</h3>
<p style="color: #666; margin-bottom: 15px;">点击以下问题直接复制到聊天窗口:</p>
<ul class="question-list">
<li onclick="copyQuestion(this.textContent)">🕐 你们的营业时间是什么?</li>
<li onclick="copyQuestion(this.textContent)">📦 我的订单 202071324 怎么样了?</li>
<li onclick="copyQuestion(this.textContent)">🔍 查询订单 202071324</li>
<li onclick="copyQuestion(this.textContent)">📞 如何联系客服?</li>
<li onclick="copyQuestion(this.textContent)">🛍️ 我想退换货</li>
<li onclick="copyQuestion(this.textContent)">📦 订单 202071324 的物流信息</li>
</ul>
</div>
<div class="feature-list">
<div class="feature-card">
<h4>🎯 智能意图识别</h4>
<p>自动识别客户需求并分类</p>
</div>
<div class="feature-card">
<h4>📚 知识库查询</h4>
<p>快速检索 FAQ 和政策文档</p>
</div>
<div class="feature-card">
<h4>📦 订单管理</h4>
<p>查询订单、售后等服务</p>
</div>
<div class="feature-card">
<h4>🔄 多轮对话</h4>
<p>支持上下文理解的连续对话</p>
</div>
</div>
<div class="info-box">
<h3>🔧 技术栈</h3>
<ul>
<li><strong>前端:</strong>Chatwoot 客户支持平台</li>
<li><strong>AI 引擎:</strong>LangGraph + 智谱 AI (GLM-4.5)</li>
<li><strong>知识库:</strong>Strapi CMS + MCP</li>
<li><strong>业务系统:</strong>Hyperf PHP API</li>
<li><strong>缓存:</strong>Redis</li>
<li><strong>容器:</strong>Docker Compose</li>
</ul>
</div>
</div>
<script>
function copyQuestion(text) {
// 移除表情符号
const cleanText = text.replace(/^[^\s]+\s*/, '');
navigator.clipboard.writeText(cleanText).then(() => {
alert('问题已复制!请粘贴到聊天窗口中发送。');
});
}
// ==================== Cookie Token 读取 ====================
function getCookie(name) {
const value = `; ${document.cookie}`;
const parts = value.split(`; ${name}=`);
if (parts.length === 2) return parts.pop().split(";").shift();
return null;
}
function checkToken() {
const token = getCookie('token');
const statusDiv = document.getElementById('tokenStatus');
if (token) {
statusDiv.style.display = 'block';
statusDiv.className = 'status online';
statusDiv.innerHTML = `✅ Token 已找到 | 长度: ${token.length} 字符 | 前缀: ${token.substring(0, 20)}...`;
// 存储到 window 供后续使用
window._chatwootUserToken = token;
console.log('✅ Token 已从 Cookie 读取');
} else {
statusDiv.style.display = 'block';
statusDiv.className = 'status testing';
statusDiv.innerHTML = '⚠️ 未找到 Token | 请确保已登录商城 | Cookie 名称: token';
console.warn('⚠️ 未找到 Token订单查询功能可能无法使用');
}
}
// 页面加载时检查 Token
window.addEventListener('load', function() {
setTimeout(checkToken, 1000);
});
</script>
<!-- Chatwoot Widget - 官方集成方式 -->
<script>
(function(d,t) {
var BASE_URL="http://localhost:3000";
var g=d.createElement(t),s=d.getElementsByTagName(t)[0];
g.src=BASE_URL+"/packs/js/sdk.js";
g.async = true;
s.parentNode.insertBefore(g,s);
g.onload=function(){
window.chatwootSDK.run({
websiteToken: '39PNCMvbMk3NvB7uaDNucc6o',
baseUrl: BASE_URL,
locale: 'zh_CN',
userIdentifier: getCookie('token') || 'web_user_' + Date.now()
});
const userToken = getCookie('token');
console.log('✅ Chatwoot Widget 已加载 (官方集成方式)');
console.log('Base URL:', BASE_URL);
console.log('Website Token: 39PNCMvbMk3NvB7uaDNucc6o');
console.log('Locale: zh_CN');
console.log('User Identifier:', userToken || 'web_user_' + Date.now());
// 设置用户信息(可选)
setTimeout(function() {
const token = getCookie('token');
if (token && window.$chatwoot) {
window.$chatwoot.setUser('user_' + Date.now(), {
email: 'user@example.com',
name: 'Website User',
phone_number: ''
});
console.log('✅ 用户信息已设置');
} else if (!token) {
console.warn('⚠️ 未找到 Token');
}
}, 2000);
}
g.onerror=function(){
console.error('❌ Chatwoot SDK 加载失败');
console.error('请确保 Chatwoot 运行在: ' + BASE_URL);
}
})(document,"script");
</script>
</body>
</html>

View File

@@ -1,425 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>会话 ID 检查工具</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
background: #f5f5f5;
}
.container {
background: white;
border-radius: 10px;
padding: 30px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
h1 {
color: #333;
text-align: center;
}
.info-box {
background: #e7f3ff;
border-left: 4px solid #2196F3;
padding: 15px 20px;
margin: 20px 0;
border-radius: 5px;
}
.info-box h3 {
margin-top: 0;
color: #2196F3;
}
.data-display {
background: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 5px;
padding: 15px;
margin: 15px 0;
font-family: 'Courier New', monospace;
font-size: 14px;
}
.data-label {
font-weight: bold;
color: #495057;
margin-bottom: 10px;
}
.data-value {
color: #212529;
background: white;
padding: 10px;
border-radius: 3px;
word-break: break-all;
}
button {
background: #2196F3;
color: white;
border: none;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
margin: 5px;
}
button:hover {
background: #0b7dda;
}
button.danger {
background: #dc3545;
}
button.danger:hover {
background: #c82333;
}
.instructions {
background: #fff3cd;
border-left: 4px solid #ffc107;
padding: 15px 20px;
margin: 20px 0;
border-radius: 5px;
}
.instructions ol {
margin: 10px 0;
padding-left: 20px;
}
.instructions li {
margin: 8px 0;
line-height: 1.6;
}
</style>
</head>
<body>
<div class="container">
<h1>🔍 Chatwoot 会话 ID 检查工具</h1>
<div class="instructions">
<h3>📝 使用说明</h3>
<ol>
<li>打开浏览器开发者工具(按 F12</li>
<li>切换到 Console控制台标签</li>
<li>点击下面的"显示会话信息"按钮</li>
<li>在 Console 中查看当前的 conversation_id</li>
<li>将这个 ID 与 Agent 日志中的 conversation_id 对比</li>
</ol>
</div>
<div class="info-box">
<h3>🎯 操作按钮</h3>
<button onclick="showConversationInfo()">显示会话信息</button>
<button onclick="checkWidgetStatus()">检查 Widget 状态</button>
<button onclick="checkToken()">检查 Token</button>
<button onclick="testOrderAPI()">测试订单 API</button>
<button onclick="clearLocalStorage()" class="danger">清除本地存储(重新开始)</button>
</div>
<div class="info-box">
<h3>📊 信息显示</h3>
<div class="data-display">
<div class="data-label">Widget SDK 状态:</div>
<div class="data-value" id="widgetStatus">未初始化</div>
</div>
<div class="data-display">
<div class="data-label">当前会话 ID:</div>
<div class="data-value" id="conversationId">未知</div>
</div>
<div class="data-display">
<div class="data-label">Token 状态:</div>
<div class="data-value" id="tokenStatus">未检查</div>
</div>
<div class="data-display">
<div class="data-label">订单 API 测试结果:</div>
<div class="data-value" id="orderApiResult">未测试</div>
</div>
<div class="data-display">
<div class="data-label">本地存储数据:</div>
<div class="data-value" id="localStorageData"></div>
</div>
</div>
<div class="instructions">
<h3>💡 问题排查</h3>
<p><strong>如果看不到 AI 回复:</strong></p>
<ol>
<li>点击"清除本地存储"按钮</li>
<li>刷新页面Ctrl+Shift+R</li>
<li>在右下角聊天窗口重新发送消息</li>
<li>查看 Agent 日志: <code>docker logs ai_agent --tail 50</code></li>
<li>对比 Console 中的 conversation_id 与日志中的是否一致</li>
</ol>
</div>
</div>
<script>
// 获取 Cookie 中的 token
function getCookie(name) {
const value = `; ${document.cookie}`;
const parts = value.split(`; ${name}=`);
if (parts.length === 2) return parts.pop().split(";").shift();
return null;
}
// 检查 Token
function checkToken() {
console.log('======================================');
console.log('Token 检查');
console.log('======================================');
const token = getCookie('token');
const tokenStatusDiv = document.getElementById('tokenStatus');
if (token) {
console.log('✅ Token 已找到');
console.log('Token 长度:', token.length);
console.log('Token 前缀:', token.substring(0, 50) + '...');
tokenStatusDiv.textContent = `✅ 已找到 | 长度: ${token.length} | 前缀: ${token.substring(0, 30)}...`;
tokenStatusDiv.style.color = '#28a745';
} else {
console.log('❌ 未找到 Token');
console.log('Cookie 名称: token');
tokenStatusDiv.textContent = '❌ 未找到 | Cookie 名称: token';
tokenStatusDiv.style.color = '#dc3545';
}
console.log('所有 Cookie:', document.cookie);
console.log('======================================');
}
// 测试订单 API
async function testOrderAPI() {
console.log('======================================');
console.log('测试订单 API');
console.log('======================================');
const token = getCookie('token');
const resultDiv = document.getElementById('orderApiResult');
if (!token) {
console.error('❌ 未找到 Token无法调用 API');
resultDiv.textContent = '❌ 未找到 Token';
resultDiv.style.color = '#dc3545';
alert('❌ 未找到 Token请先确保已登录商城');
return;
}
const orderId = '202071324';
const apiUrl = `https://apicn.qa1.gaia888.com/mall/api/order/show?orderId=${orderId}`;
console.log('API URL:', apiUrl);
console.log('Authorization:', `Bearer ${token.substring(0, 30)}...`);
resultDiv.textContent = '🔄 请求中...';
resultDiv.style.color = '#ffc107';
try {
const response = await fetch(apiUrl, {
method: 'GET',
headers: {
'accept': 'application/json, text/plain, */*',
'accept-language': 'zh-CN,zh;q=0.9',
'authorization': `Bearer ${token}`,
'currency-code': 'EUR',
'device-type': 'pc',
'language-id': '1',
'origin': 'https://www.qa1.gaia888.com',
'referer': 'https://www.qa1.gaia888.com/',
'sec-fetch-dest': 'empty',
'sec-fetch-mode': 'cors',
'sec-fetch-site': 'same-site',
'source': 'us.qa1.gaia888.com',
'tenant-id': '2'
}
});
console.log('响应状态:', response.status);
console.log('响应头:', Object.fromEntries(response.headers.entries()));
if (response.ok) {
const data = await response.json();
console.log('✅ API 调用成功');
console.log('响应数据:', data);
resultDiv.textContent = `✅ 成功 (HTTP ${response.status}) | 订单 ${orderId}`;
resultDiv.style.color = '#28a745';
alert(`✅ 订单 API 调用成功!\n\n订单 ID: ${orderId}\n状态码: ${response.status}\n\n详细数据请查看控制台`);
} else {
const errorText = await response.text();
console.error('❌ API 调用失败');
console.error('状态码:', response.status);
console.error('响应内容:', errorText);
resultDiv.textContent = `❌ 失败 (HTTP ${response.status})`;
resultDiv.style.color = '#dc3545';
alert(`❌ 订单 API 调用失败\n\n状态码: ${response.status}\n错误: ${errorText}`);
}
} catch (error) {
console.error('❌ 网络错误:', error);
resultDiv.textContent = `❌ 网络错误: ${error.message}`;
resultDiv.style.color = '#dc3545';
alert(`❌ 网络错误\n\n${error.message}`);
}
console.log('======================================');
}
function showConversationInfo() {
console.log('======================================');
console.log('Chatwoot Widget 会话信息');
console.log('======================================');
if (window.$chatwoot) {
try {
// 尝试获取会话信息
const info = window.$chatwoot.getConversationInfo();
console.log('✅ 会话信息:', info);
document.getElementById('conversationId').textContent =
info && info.conversationId ? info.conversationId : '无法获取';
} catch (e) {
console.log('⚠️ 无法获取会话信息:', e.message);
document.getElementById('conversationId').textContent = '无法获取';
}
} else {
console.log('❌ Widget 未初始化');
document.getElementById('conversationId').textContent = 'Widget 未初始化';
}
// 显示本地存储
const storage = {};
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key.includes('chatwoot') || key.includes('widget')) {
storage[key] = localStorage.getItem(key);
}
}
console.log('本地存储 (Chatwoot 相关):', storage);
document.getElementById('localStorageData').textContent =
Object.keys(storage).length > 0 ? JSON.stringify(storage, null, 2) : '无';
console.log('======================================');
}
function checkWidgetStatus() {
console.log('======================================');
console.log('Widget 状态检查');
console.log('======================================');
console.log('window.$chatwoot:', window.$chatwoot);
console.log('window.chatwootSDK:', window.chatwootSDK);
if (window.$chatwoot) {
console.log('✅ Widget 已加载');
console.log('可用方法:', Object.getOwnPropertyNames(window.$chatwoot));
document.getElementById('widgetStatus').textContent = '✅ 已加载';
document.getElementById('widgetStatus').style.color = '#28a745';
} else {
console.log('❌ Widget 未加载');
document.getElementById('widgetStatus').textContent = '❌ 未加载';
document.getElementById('widgetStatus').style.color = '#dc3545';
}
console.log('======================================');
}
function clearLocalStorage() {
if (confirm('确定要清除所有本地存储吗?这将重置会话。')) {
// 清除 Chatwoot 相关的本地存储
const keysToRemove = [];
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key.includes('chatwoot') || key.includes('widget')) {
keysToRemove.push(key);
}
}
keysToRemove.forEach(key => localStorage.removeItem(key));
console.log(`✅ 已清除 ${keysToRemove.length} 个本地存储项`);
console.log('清除的键:', keysToRemove);
alert(`✅ 已清除 ${keysToRemove.length} 个本地存储项\n\n页面将重新加载以创建新的会话。`);
location.reload();
}
}
// 页面加载时显示本地存储和检查 Token
window.addEventListener('load', function() {
// 显示本地存储
const storage = {};
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key.includes('chatwoot') || key.includes('widget')) {
storage[key] = localStorage.getItem(key);
}
}
if (Object.keys(storage).length > 0) {
document.getElementById('localStorageData').textContent = JSON.stringify(storage, null, 2);
}
// 自动检查 Token
setTimeout(checkToken, 500);
});
</script>
<!-- Chatwoot Widget -->
<script>
(function(d,t) {
var BASE_URL="http://localhost:3000";
var g=d.createElement(t),s=d.getElementsByTagName(t)[0];
g.src=BASE_URL+"/packs/js/sdk.js";
g.async = true;
s.parentNode.insertBefore(g,s);
g.onload=function(){
// 获取 token 函数
function getCookie(name) {
const value = `; ${document.cookie}`;
const parts = value.split(`; ${name}=`);
if (parts.length === 2) return parts.pop().split(";").shift();
return null;
}
const token = getCookie('token');
// 初始化配置
const widgetConfig = {
websiteToken: '39PNCMvbMk3NvB7uaDNucc6o',
baseUrl: BASE_URL,
locale: 'zh_CN',
userIdentifier: token || 'web_user_' + Date.now()
};
window.chatwootSDK.run(widgetConfig);
// 等待 widget 加载完成后设置用户属性
setTimeout(() => {
if (token && window.chatwootSDK.setUser) {
window.chatwootSDK.setUser(
token || 'web_user_' + Date.now(),
{
jwt_token: token,
mall_token: token
}
);
console.log('✅ 已通过 setUser 设置用户属性');
} else if (token && window.$chatwoot) {
// 备用方案:使用 $chatwoot.setCustomAttributes
window.$chatwoot.setCustomAttributes({
jwt_token: token,
mall_token: token
});
console.log('✅ 已通过 $chatwoot.setCustomAttributes 设置用户属性');
}
}, 1000);
console.log('✅ Chatwoot Widget 已加载');
console.log('Locale: zh_CN');
console.log('User Identifier:', token || 'web_user_' + Date.now());
document.getElementById('widgetStatus').textContent = '✅ 已加载';
document.getElementById('widgetStatus').style.color = '#28a745';
}
})(document,"script");
</script>
</body>
</html>

View File

@@ -1,261 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>B2B AI 助手 - 简化测试</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
}
.container {
background: white;
border-radius: 10px;
padding: 40px;
box-shadow: 0 10px 40px rgba(0,0,0,0.1);
}
h1 {
color: #333;
text-align: center;
margin-bottom: 10px;
}
.subtitle {
text-align: center;
color: #666;
margin-bottom: 30px;
}
.info-box {
background: #f8f9fa;
border-left: 4px solid #667eea;
padding: 15px 20px;
margin: 20px 0;
border-radius: 5px;
}
.info-box h3 {
margin-top: 0;
color: #667eea;
}
.test-questions {
background: #fff3cd;
border: 1px solid #ffc107;
padding: 20px;
border-radius: 5px;
margin: 20px 0;
}
.test-questions h3 {
margin-top: 0;
color: #856404;
}
.question-list {
list-style: none;
padding: 0;
}
.question-list li {
background: white;
margin: 10px 0;
padding: 12px 15px;
border-radius: 5px;
cursor: pointer;
transition: all 0.3s;
border: 1px solid #ddd;
}
.question-list li:hover {
background: #667eea;
color: white;
transform: translateX(5px);
}
.status {
text-align: center;
padding: 15px;
border-radius: 5px;
margin: 20px 0;
font-weight: bold;
}
.status.online {
background: #d4edda;
color: #155724;
}
.instructions {
background: #e7f3ff;
border-left: 4px solid #2196F3;
padding: 15px 20px;
margin: 20px 0;
border-radius: 5px;
}
.instructions ol {
margin: 10px 0;
padding-left: 20px;
}
.instructions li {
margin: 8px 0;
line-height: 1.6;
}
.feature-list {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
margin: 30px 0;
}
.feature-card {
background: #f8f9fa;
padding: 20px;
border-radius: 8px;
text-align: center;
}
.feature-card h4 {
color: #667eea;
margin-bottom: 10px;
}
</style>
</head>
<body>
<div class="container">
<h1>🤖 B2B AI 智能客服助手</h1>
<p class="subtitle">简化测试页面 - Chatwoot 官方集成方式</p>
<div class="status online">
✅ 系统状态:使用官方标准集成
</div>
<div class="instructions">
<h3>📝 使用说明</h3>
<ol>
<li><strong>点击右下角的聊天图标</strong>打开 Chatwoot 对话窗口</li>
<li><strong>输入消息</strong>开始与 AI 对话</li>
<li><strong>或者</strong>点击下面的测试问题,复制后在聊天窗口粘贴发送</li>
<li><strong>查看 AI 如何理解和回答</strong>你的问题</li>
</ol>
</div>
<div class="test-questions">
<h3>💬 推荐测试问题</h3>
<p style="color: #666; margin-bottom: 15px;">点击以下问题复制到剪贴板然后在聊天窗口粘贴Ctrl+V并发送</p>
<ul class="question-list">
<li onclick="copyQuestion(this.textContent)">🕐 你们的营业时间是什么?</li>
<li onclick="copyQuestion(this.textContent)">📦 我的订单 202071324 怎么样了?</li>
<li onclick="copyQuestion(this.textContent)">🔍 查询订单 202071324</li>
<li onclick="copyQuestion(this.textContent)">📞 如何联系客服?</li>
<li onclick="copyQuestion(this.textContent)">🛍️ 我想退换货</li>
<li onclick="copyQuestion(this.textContent)">📦 订单 202071324 的物流信息</li>
</ul>
</div>
<div class="info-box">
<h3>🔧 技术栈</h3>
<ul>
<li><strong>前端:</strong>Chatwoot 客户支持平台(官方 Widget SDK</li>
<li><strong>AI 引擎:</strong>LangGraph + 智谱 AI (GLM-4.5)</li>
<li><strong>知识库:</strong>Strapi CMS + MCP</li>
<li><strong>业务系统:</strong>Hyperf PHP API</li>
<li><strong>缓存:</strong>Redis</li>
<li><strong>容器:</strong>Docker Compose</li>
</ul>
</div>
<div class="feature-list">
<div class="feature-card">
<h4>🎯 智能意图识别</h4>
<p>自动识别客户需求并分类</p>
</div>
<div class="feature-card">
<h4>📚 知识库查询</h4>
<p>快速检索 FAQ 和政策文档</p>
</div>
<div class="feature-card">
<h4>📦 订单管理</h4>
<p>查询订单、售后等服务</p>
</div>
<div class="feature-card">
<h4>🔄 多轮对话</h4>
<p>支持上下文理解的连续对话</p>
</div>
</div>
<div class="info-box">
<h3>📊 系统信息</h3>
<p><strong>Chatwoot 服务:</strong>http://localhost:3000</p>
<p><strong>Website Token</strong>39PNCMvbMk3NvB7uaDNucc6o</p>
<p><strong>集成方式:</strong>Chatwoot 官方 SDK</p>
</div>
</div>
<script>
function copyQuestion(text) {
// 移除表情符号
const cleanText = text.replace(/^[^\s]+\s*/, '');
navigator.clipboard.writeText(cleanText).then(() => {
alert('✅ 问题已复制到剪贴板!\n\n请在聊天窗口中按 Ctrl+V 粘贴并发送。');
}).catch(err => {
console.error('复制失败:', err);
alert('❌ 复制失败,请手动复制问题文本。');
});
}
// ==================== Cookie Token 读取 ====================
function getCookie(name) {
const value = `; ${document.cookie}`;
const parts = value.split(`; ${name}=`);
if (parts.length === 2) return parts.pop().split(";").shift();
return null;
}
// 页面加载时检查 Token
window.addEventListener('load', function() {
const token = getCookie('token');
if (token) {
console.log('✅ Token 已从 Cookie 读取');
console.log('Token 长度:', token.length);
} else {
console.warn('⚠️ 未找到 Token订单查询功能可能无法使用');
}
});
</script>
<!-- Chatwoot Widget - 官方集成方式 -->
<script>
(function(d,t) {
var BASE_URL="http://localhost:3000";
var g=d.createElement(t),s=d.getElementsByTagName(t)[0];
g.src=BASE_URL+"/packs/js/sdk.js";
g.async = true;
s.parentNode.insertBefore(g,s);
g.onload=function(){
window.chatwootSDK.run({
websiteToken: '39PNCMvbMk3NvB7uaDNucc6o',
baseUrl: BASE_URL
});
console.log('✅ Chatwoot Widget 已加载 (官方集成方式)');
console.log('Base URL:', BASE_URL);
console.log('Website Token: 39PNCMvbMk3NvB7uaDNucc6o');
// 设置用户信息(可选)
setTimeout(function() {
const token = getCookie('token');
if (token && window.$chatwoot) {
window.$chatwoot.setUser('user_' + Date.now(), {
email: 'user@example.com',
name: 'Website User',
phone_number: ''
});
console.log('✅ 用户信息已设置');
} else if (!token) {
console.warn('⚠️ 未找到 Token');
}
}, 2000);
}
g.onerror=function(){
console.error('❌ Chatwoot SDK 加载失败');
console.error('请确保 Chatwoot 运行在: ' + BASE_URL);
}
})(document,"script");
</script>
</body>
</html>

View File

@@ -494,7 +494,7 @@ async def get_mall_order_list(
date_added: Optional[str] = None,
date_end: Optional[str] = None,
no: Optional[str] = None,
status: Optional[int] = None,
status: int = 10000,
is_drop_shopping: int = 0
) -> dict:
"""Query order list from Mall API with filters
@@ -517,7 +517,7 @@ async def get_mall_order_list(
date_added: 开始日期,格式 YYYY-MM-DD (default: None)
date_end: 结束日期,格式 YYYY-MM-DD (default: None)
no: 订单号筛选 (default: None)
status: 订单状态筛选 (default: None, None表示全部状态)
status: 订单状态筛选 (default: 10000, 10000表示全部状态)
is_drop_shopping: 是否代发货 (default: 0)
Returns:
@@ -673,9 +673,14 @@ if __name__ == "__main__":
tool_obj = _tools[tool_name]
# Call the tool with arguments
# Filter arguments to only include parameters expected by the tool
# Get parameter names from tool's parameters schema
tool_params = tool_obj.parameters.get('properties', {})
filtered_args = {k: v for k, v in arguments.items() if k in tool_params}
# Call the tool with filtered arguments
# FastMCP FunctionTool.run() takes a dict of arguments
tool_result = await tool_obj.run(arguments)
tool_result = await tool_obj.run(filtered_args)
# Extract content from ToolResult
# ToolResult.content is a list of TextContent objects with a 'text' attribute

View File

@@ -249,11 +249,8 @@ async def get_categories() -> dict:
@mcp.tool()
async def search_products(
keyword: str,
page_size: int = 60,
page: int = 1,
user_token: str = None,
user_id: str = None,
account_id: str = None
page_size: int = 5,
page: int = 1
) -> dict:
"""Search products from Mall API
@@ -261,46 +258,33 @@ async def search_products(
Args:
keyword: 搜索关键词(商品名称、编号等)
page_size: 每页数量 (default: 60, max 100)
page_size: 每页数量 (default: 5, max 100)
page: 页码 (default: 1)
user_token: 用户 JWT token必需用于 Mall API 认证)
user_id: 用户 ID自动注入
account_id: 账户 ID自动注入
Returns:
商品列表,包含 SPU 信息、商品图片、价格等
Product list including SPU ID, name, image, price, etc.
"""
if not user_token:
return {
"success": False,
"error": "用户未登录,请先登录账户以搜索商品",
"products": [],
"total": 0,
"require_login": True
}
try:
from shared.mall_client import MallClient
import logging
logger = logging.getLogger(__name__)
logger.info(
f"search_products called with keyword={keyword}, "
f"user_token_prefix={user_token[:20] if user_token else None}..."
)
print(f"[DEBUG] search_products called: keyword={keyword}, user_token={user_token[:20] if user_token else None}...")
logger.info(f"search_products called with keyword={keyword}")
print(f"[DEBUG] search_products called: keyword={keyword}")
# 使用用户 token 创建 Mall 客户端
# 创建 Mall 客户端(无需 token
mall = MallClient(
api_url=settings.mall_api_url,
api_token=user_token,
api_token=None, # 不需要 token
tenant_id=settings.mall_tenant_id,
currency_code=settings.mall_currency_code,
language_id=settings.mall_language_id,
source=settings.mall_source
)
print(f"[DEBUG] Calling Mall API: keyword={keyword}, page_size={page_size}, page={page}")
result = await mall.search_spu_products(
keyword=keyword,
page_size=page_size,
@@ -310,9 +294,10 @@ async def search_products(
logger.info(
f"Mall API returned: result_type={type(result).__name__}, "
f"result_keys={list(result.keys()) if isinstance(result, dict) else 'not a dict'}, "
f"result={result}"
f"total={result.get('total', 'N/A') if isinstance(result, dict) else 'N/A'}, "
f"data_length={len(result.get('data', {}).get('data', [])) if isinstance(result, dict) and isinstance(result.get('data'), dict) else 'N/A'}"
)
print(f"[DEBUG] Mall API returned: {result}")
print(f"[DEBUG] Mall API returned: total={result.get('total', 'N/A')}, data_keys={list(result.get('data', {}).keys()) if isinstance(result.get('data'), dict) else 'N/A'}")
# 解析返回结果
# Mall API 返回结构: {"total": X, "data": {"data": [...], ...}}
@@ -328,12 +313,21 @@ async def search_products(
formatted_products.append({
"spu_id": product.get("spuId"),
"spu_sn": product.get("spuSn"),
"product_name": product.get("productName"),
"product_image": product.get("productImage"),
"product_name": product.get("spuName"), # 修正字段名
"product_image": product.get("masterImage"), # 修正字段名
"price": product.get("price"),
"special_price": product.get("specialPrice"),
"stock": product.get("stock"),
"sales_count": product.get("salesCount", 0)
"stock": product.get("stockDescribe"), # 修正字段名
"sales_count": product.get("salesCount", 0),
# 额外有用字段
"href": product.get("href"),
"spu_type": product.get("spuType"),
"spu_type_name": product.get("spuTypeName"),
"min_price": product.get("minPrice"),
"max_price": product.get("maxPrice"),
"price_with_currency": product.get("priceWithCurrency"),
"mark_price": product.get("markPrice"),
"skus_count": len(product.get("skus", []))
})
return {
@@ -382,18 +376,32 @@ if __name__ == "__main__":
# Get arguments from request body
arguments = await request.json()
print(f"[DEBUG HTTP] Tool: {tool_name}, Args: {arguments}")
# Get tool function from registry
if tool_name not in _tools:
print(f"[ERROR] Tool '{tool_name}' not found in registry")
return JSONResponse({
"success": False,
"error": f"Tool '{tool_name}' not found"
}, status_code=404)
tool_obj = _tools[tool_name]
print(f"[DEBUG HTTP] Tool object: {tool_obj}, type: {type(tool_obj)}")
# Call the tool with arguments
# Filter arguments to only include parameters expected by the tool
# Get parameter names from tool's parameters schema
tool_params = tool_obj.parameters.get('properties', {})
filtered_args = {k: v for k, v in arguments.items() if k in tool_params}
if len(filtered_args) < len(arguments):
print(f"[DEBUG HTTP] Filtered arguments: {arguments} -> {filtered_args}")
# Call the tool with filtered arguments
# FastMCP FunctionTool.run() takes a dict of arguments
tool_result = await tool_obj.run(arguments)
print(f"[DEBUG HTTP] Calling tool.run()...")
tool_result = await tool_obj.run(filtered_args)
print(f"[DEBUG HTTP] Tool result: {tool_result}")
# Extract content from ToolResult
# ToolResult.content is a list of TextContent objects with a 'text' attribute
@@ -414,11 +422,17 @@ if __name__ == "__main__":
})
except TypeError as e:
print(f"[ERROR] TypeError: {e}")
import traceback
traceback.print_exc()
return JSONResponse({
"success": False,
"error": f"Invalid arguments: {str(e)}"
}, status_code=400)
except Exception as e:
print(f"[ERROR] Exception: {e}")
import traceback
traceback.print_exc()
return JSONResponse({
"success": False,
"error": str(e)

View File

@@ -55,7 +55,6 @@ class MallClient:
"""
if self._client is None:
default_headers = {
"Authorization": f"Bearer {self.api_token}",
"Content-Type": "application/json",
"Accept": "application/json, text/plain, */*",
"Device-Type": "pc",
@@ -70,6 +69,10 @@ class MallClient:
"DNT": "1",
}
# 只有在有 token 时才添加 Authorization header
if self.api_token:
default_headers["Authorization"] = f"Bearer {self.api_token}"
# 合并额外的 headers用于 Authorization2 等)
if extra_headers:
default_headers.update(extra_headers)
@@ -131,6 +134,14 @@ class MallClient:
json=json,
headers=request_headers
)
# Debug logging for product search
if "/spu" in endpoint:
print(f"[DEBUG MallClient] Request: {method} {endpoint}")
print(f"[DEBUG MallClient] Params: {params}")
print(f"[DEBUG MallClient] Response URL: {response.url}")
print(f"[DEBUG MallClient] Response Status: {response.status_code}")
response.raise_for_status()
data = response.json()
@@ -197,14 +208,14 @@ class MallClient:
async def get_order_list(
self,
page: int = 1,
limit: int = 10,
limit: int = 5,
customer_id: int = 0,
order_types: Optional[list[int]] = None,
shipping_status: int = 10000,
date_added: Optional[str] = None,
date_end: Optional[str] = None,
no: Optional[str] = None,
status: Optional[int] = None,
status: int = 10000,
is_drop_shopping: int = 0
) -> dict[str, Any]:
"""Query order list with filters
@@ -213,14 +224,14 @@ class MallClient:
Args:
page: 页码 (default: 1)
limit: 每页数量 (default: 10)
limit: 每页数量 (default: 5)
customer_id: 客户ID (default: 0)
order_types: 订单类型数组,如 [1, 2] (default: None)
shipping_status: 物流状态 (default: 10000)
shipping_status: 物流状态 (default: 10000, 10000表示全部状态)
date_added: 开始日期,格式 YYYY-MM-DD (default: None)
date_end: 结束日期,格式 YYYY-MM-DD (default: None)
no: 订单号 (default: None)
status: 订单状态 (default: None)
status: 订单状态 (default: 10000, 10000表示全部状态)
is_drop_shopping: 是否代发货 (default: 0)
Returns:
@@ -239,6 +250,7 @@ class MallClient:
"limit": limit,
"customerId": customer_id,
"shippingStatus": shipping_status,
"status": status,
"isDropShopping": is_drop_shopping
}
@@ -253,8 +265,6 @@ class MallClient:
params["dateEnd"] = date_end
if no:
params["no"] = no
if status is not None:
params["status"] = status
result = await self.get(
"/mall/api/order/list",
@@ -265,6 +275,47 @@ class MallClient:
except Exception as e:
raise Exception(f"查询订单列表失败 (Query order list failed): {str(e)}")
# ============ Product APIs ============
async def search_spu_products(
self,
keyword: str,
page_size: int = 5,
page: int = 1
) -> dict[str, Any]:
"""Search SPU products by keyword
根据关键词搜索商品 SPU
Args:
keyword: 搜索关键词(商品名称、编号等)
page_size: 每页数量 (default: 5, max 100)
page: 页码 (default: 1)
Returns:
商品列表,包含 SPU 信息、商品图片、价格等
Product list including SPU info, images, prices, etc.
Example:
>>> client = MallClient()
>>> result = await client.search_spu_products("61607", page_size=60, page=1)
>>> print(f"找到 {len(result.get('list', []))} 个商品")
"""
try:
params = {
"pageSize": min(page_size, 100), # 限制最大 100
"page": page,
"keyword": keyword
}
result = await self.get(
"/mall/api/spu",
params=params
)
return result
except Exception as e:
raise Exception(f"搜索商品失败 (Search SPU products failed): {str(e)}")
# Global Mall client instance
mall_client: Optional[MallClient] = None

View File

@@ -1,74 +0,0 @@
#!/bin/bash
# 测试所有 FAQ 分类
echo "=========================================="
echo "🧪 测试所有 FAQ 分类"
echo "=========================================="
echo ""
# 定义测试用例
declare -A TEST_CASES=(
["订单相关"]="How do I place an order?"
["支付相关"]="What payment methods do you accept?"
["运输相关"]="What are the shipping options?"
["退货相关"]="I received a defective item, what should I do?"
["账号相关"]="I forgot my password, now what?"
["营业时间"]="What are your opening hours?"
)
# 测试每个分类
for category in "${!TEST_CASES[@]}"; do
question="${TEST_CASES[$category]}"
conv_id="test_${category}___$(date +%s)"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "📋 分类: $category"
echo "📝 问题: $question"
echo "⏳ 处理中..."
echo ""
# 调用 API
RESPONSE=$(docker exec ai_agent curl -s -X POST 'http://localhost:8000/api/agent/query' \
-H 'Content-Type: application/json' \
-d "{\"conversation_id\":\"$conv_id\",\"user_id\":\"test_user\",\"account_id\":\"2\",\"message\":\"$question\"}")
# 解析并显示结果
echo "$RESPONSE" | python3 << PYTHON
import json
import sys
try:
data = json.load(sys.stdin)
# 提取响应
response = data.get("response", "")
intent = data.get("intent", "")
if response:
# 清理 HTML 标签(如果有)
import re
clean_response = re.sub(r'<[^<]+?>', '', response)
clean_response = clean_response.strip()
# 截断过长响应
if len(clean_response) > 300:
clean_response = clean_response[:300] + "..."
print(f"🎯 意图: {intent}")
print(f"🤖 回答: {clean_response}")
else:
print("❌ 未获得回答")
print(f"调试信息: {json.dumps(data, indent=2, ensure_ascii=False)}")
except Exception as e:
print(f"❌ 解析错误: {e}")
print(f"原始响应: {sys.stdin.read()}")
PYTHON
echo ""
sleep 2 # 间隔 2 秒
done
echo "=========================================="
echo "✅ 所有测试完成"
echo "=========================================="

View File

@@ -1,103 +0,0 @@
"""
测试商城订单查询接口
Usage:
python test_mall_order_query.py <order_id>
Example:
python test_mall_order_query.py 202071324
"""
import asyncio
import sys
import os
# Add mcp_servers to path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "mcp_servers"))
from shared.mall_client import MallClient
async def test_order_query(order_id: str, token: str):
"""测试订单查询
Args:
order_id: 订单号
token: JWT Token
"""
print(f"\n{'='*60}")
print(f"测试商城订单查询接口")
print(f"{'='*60}")
print(f"订单号 (Order ID): {order_id}")
print(f"API URL: https://apicn.qa1.gaia888.com")
print(f"{'='*60}\n")
# 创建客户端
client = MallClient(
api_url="https://apicn.qa1.gaia888.com",
api_token=token,
tenant_id="2",
currency_code="EUR",
language_id="1",
source="us.qa1.gaia888.com"
)
try:
# 调用订单查询接口
result = await client.get_order_by_id(order_id)
# 打印结果
print("✅ 查询成功 (Query Success)!")
print(f"\n返回数据 (Response Data):")
print("-" * 60)
import json
print(json.dumps(result, ensure_ascii=False, indent=2))
print("-" * 60)
# 提取关键信息
if isinstance(result, dict):
print(f"\n关键信息 (Key Information):")
print(f" 订单号 (Order ID): {result.get('order_id') or result.get('orderId') or order_id}")
print(f" 订单状态 (Status): {result.get('status') or result.get('order_status') or 'N/A'}")
print(f" 订单金额 (Amount): {result.get('total_amount') or result.get('amount') or 'N/A'}")
# 商品信息
items = result.get('items') or result.get('order_items') or result.get('products')
if items:
print(f" 商品数量 (Items): {len(items)}")
except Exception as e:
print(f"❌ 查询失败 (Query Failed): {str(e)}")
import traceback
traceback.print_exc()
finally:
await client.close()
def main():
"""主函数"""
if len(sys.argv) < 2:
print("Usage: python test_mall_order_query.py <order_id> [token]")
print("\nExample:")
print(' python test_mall_order_query.py 202071324')
print(' python test_mall_order_query.py 202071324 "your_jwt_token_here"')
sys.exit(1)
order_id = sys.argv[1]
# 从命令行获取 token如果没有提供则使用默认的测试 token
if len(sys.argv) >= 3:
token = sys.argv[2]
else:
# 使用用户提供的示例 token
token = "eyJ0eXAiOiJqd3QifQ.eyJzdWIiOiIxIiwiaXNzIjoiaHR0cDpcL1wvOiIsImV4cCI6MTc3MDUyMDY2MSwiaWF0IjoxNzY3OTI4NjYxLCJuYmYiOjE3Njc5Mjg2NjEsInVzZXJJZCI6MTAxNDMyLCJ0eXBlIjoyLCJ0ZW5hbnRJZCI6MiwidWlkIjoxMDE0MzIsInMiOiJkM0tZMjMiLCJqdGkiOiI3YjcwYTI2MzYwYjJmMzA3YmQ4YTYzNDAxOGVlNjlmZSJ9.dwiqln19-yAQSJd1w5bxZFrRgyohdAkHa1zW3W7Ov2I"
print("⚠️ 使用默认的测试 token可能已过期")
print(" 如需测试,请提供有效的 token:")
print(f' python {sys.argv[0]} {order_id} "your_jwt_token_here"\n')
# 运行异步测试
asyncio.run(test_order_query(order_id, token))
if __name__ == "__main__":
main()

View File

@@ -1,63 +0,0 @@
"""
测试退货相关 FAQ 回答
"""
import asyncio
import sys
import os
# 添加 agent 目录到路径
sys.path.insert(0, '/app')
from agents.customer_service import customer_service_agent
from core.state import AgentState
async def test_return_faq():
"""测试退货相关 FAQ"""
# 测试问题列表
test_questions = [
"I received a defective item, what should I do?",
"How do I return a product?",
"What is your return policy?",
"I want to get a refund for my order",
]
for question in test_questions:
print(f"\n{'='*60}")
print(f"📝 问题: {question}")
print(f"{'='*60}")
# 初始化状态
state = AgentState(
conversation_id="test_return_001",
user_id="test_user",
account_id="2",
message=question,
history=[],
context={}
)
try:
# 调用客服 Agent
final_state = await customer_service_agent(state)
# 获取响应
response = final_state.get("response", "无响应")
tool_calls = final_state.get("tool_calls", [])
intent = final_state.get("intent")
print(f"\n🎯 意图识别: {intent}")
print(f"\n🤖 AI 回答:")
print(response)
print(f"\n📊 调用的工具: {tool_calls}")
except Exception as e:
print(f"\n❌ 错误: {e}")
import traceback
traceback.print_exc()
if __name__ == "__main__":
print("🧪 测试退货相关 FAQ 回答\n")
asyncio.run(test_return_faq())