diff --git a/agent/agents/product.py b/agent/agents/product.py index 89cd44c..beae1e9 100644 --- a/agent/agents/product.py +++ b/agent/agents/product.py @@ -21,26 +21,33 @@ PRODUCT_AGENT_PROMPT = """你是一个专业的 B2B 商品顾问助手。 ## 可用工具 -1. **search_products** - 搜索商品 +1. **search_spu_products** - 搜索商品(使用 Mall API,推荐) + - keyword: 搜索关键词(商品名称、编号等) + - page_size: 每页数量(默认 60,最大 100) + - page: 页码(默认 1) + - 说明:此工具使用 Mall API 搜索商品 SPU,支持用户 token 认证,返回卡片格式展示 + +2. **search_products** - 搜索商品(使用 Hyperf API) - query: 搜索关键词 - filters: 过滤条件(category, price_range, brand 等) - sort: 排序方式(price_asc/price_desc/sales/latest) - page: 页码 - page_size: 每页数量 + - 说明:此工具用于高级搜索,支持多维度过滤 -2. **get_product_detail** - 获取商品详情 +3. **get_product_detail** - 获取商品详情 - product_id: 商品ID -3. **recommend_products** - 智能推荐 +4. **recommend_products** - 智能推荐 - context: 推荐上下文(可包含当前查询、浏览历史等) - limit: 推荐数量 -4. **get_quote** - B2B 询价 +5. **get_quote** - B2B 询价 - product_id: 商品ID - quantity: 采购数量 - delivery_address: 收货地址(可选,用于计算运费) -5. **check_inventory** - 库存查询 +6. **check_inventory** - 库存查询 - product_ids: 商品ID列表 - warehouse: 仓库(可选) @@ -205,13 +212,70 @@ async def product_agent(state: AgentState) -> AgentState: async def _generate_product_response(state: AgentState) -> AgentState: """Generate response based on product tool results""" - + + # 特殊处理:如果是 search_spu_products 工具返回,直接发送商品卡片 + has_spu_search_result = False + spu_products = [] + + for result in state["tool_results"]: + if result["success"] and result["tool_name"] == "search_spu_products": + data = result["data"] + if isinstance(data, dict) and data.get("success"): + spu_products = data.get("products", []) + has_spu_search_result = True + logger.info( + "SPU product search results found", + products_count=len(spu_products), + keyword=data.get("keyword", "") + ) + break + + # 如果有 SPU 搜索结果,直接发送商品卡片 + if has_spu_search_result and spu_products: + try: + from integrations.chatwoot import ChatwootClient + from core.language_detector import detect_language + + # 检测语言 + detected_language = state.get("detected_language", "en") + + # 发送商品卡片 + chatwoot = ChatwootClient(account_id=int(state.get("account_id", 1))) + conversation_id = state.get("conversation_id") + + if conversation_id: + await chatwoot.send_product_cards( + conversation_id=int(conversation_id), + products=spu_products, + language=detected_language + ) + + logger.info( + "Product cards sent successfully", + conversation_id=conversation_id, + products_count=len(spu_products), + language=detected_language + ) + + # 清空响应,避免重复发送 + state = set_response(state, "") + state["state"] = ConversationState.GENERATING.value + return state + + except Exception as e: + logger.error( + "Failed to send product cards, falling back to text response", + error=str(e), + products_count=len(spu_products) + ) + + # 常规处理:生成文本响应 tool_context = [] for result in state["tool_results"]: if result["success"]: data = result["data"] tool_context.append(f"工具 {result['tool_name']} 返回:\n{json.dumps(data, ensure_ascii=False, indent=2)}") - + # Extract product context if isinstance(data, dict): if data.get("product_id"): @@ -222,7 +286,7 @@ async def _generate_product_response(state: AgentState) -> AgentState: state = update_context(state, {"recent_product_ids": product_ids}) else: tool_context.append(f"工具 {result['tool_name']} 执行失败: {result['error']}") - + prompt = f"""基于以下商品系统返回的信息,生成对用户的回复。 用户问题: {state["current_message"]} @@ -238,18 +302,18 @@ async def _generate_product_response(state: AgentState) -> AgentState: - 结果较多时可以总结关键信息 只返回回复内容,不要返回 JSON。""" - + messages = [ Message(role="system", content="你是一个专业的商品顾问,请根据系统返回的信息回答用户的商品问题。"), Message(role="user", content=prompt) ] - + try: llm = get_llm_client() response = await llm.chat(messages, temperature=0.7) state = set_response(state, response.content) return state - + except Exception as e: logger.error("Product response generation failed", error=str(e)) state = set_response(state, "抱歉,处理商品信息时遇到问题。请稍后重试或联系人工客服。") diff --git a/docs/PRODUCT_SEARCH_SERVICE.md b/docs/PRODUCT_SEARCH_SERVICE.md new file mode 100644 index 0000000..c9bdf8f --- /dev/null +++ b/docs/PRODUCT_SEARCH_SERVICE.md @@ -0,0 +1,408 @@ +# 商品搜索服务实现文档 + +## 功能概述 + +添加了基于 Mall API 的商品搜索服务,支持根据关键词搜索 SPU 商品,并以 Chatwoot cards 格式展示搜索结果。 + +## 技术架构 + +``` +用户发送搜索请求 + ↓ +Router Agent 识别商品意图 + ↓ +Product Agent 处理 + ↓ +调用 Product MCP 工具: search_spu_products + ↓ +MallClient 调用 Mall API: /mall/api/spu + ↓ +返回商品列表 + ↓ +发送 Chatwoot Cards 展示商品 +``` + +## 修改的文件 + +### 1. MallClient - SPU 搜索 API +**文件**: `mcp_servers/shared/mall_client.py:267-306` + +**新增方法**: +```python +async def search_spu_products( + self, + keyword: str, + page_size: int = 60, + page: int = 1 +) -> dict[str, Any] +``` + +**功能**: 调用 Mall API `/mall/api/spu` 接口搜索商品 + +### 2. Product MCP - SPU 搜索工具 +**文件**: `mcp_servers/product_mcp/server.py:291-378` + +**新增工具**: `search_spu_products` + +**参数**: +- `keyword` (必需): 搜索关键词 +- `page_size`: 每页数量(默认 60,最大 100) +- `page`: 页码(默认 1) +- `user_token` (必需): 用户 JWT token,用于 Mall API 认证 +- `user_id`: 用户 ID(自动注入) +- `account_id`: 账户 ID(自动注入) + +**返回数据格式**: +```json +{ + "success": true, + "products": [ + { + "spu_id": "12345", + "spu_sn": "61607", + "product_name": "Product Name", + "product_image": "https://...", + "price": "99.99", + "special_price": "89.99", + "stock": 100, + "sales_count": 50 + } + ], + "total": 156, + "keyword": "61607" +} +``` + +### 3. Chatwoot 集成 - 商品卡片发送 +**文件**: `agent/integrations/chatwoot.py:1086-1243` + +**新增方法**: +```python +async def send_product_cards( + self, + conversation_id: int, + products: list[dict[str, Any]], + language: str = "en" +) -> dict[str, Any] +``` + +**功能**: 发送商品搜索结果卡片到 Chatwoot + +**卡片数据结构**: +```json +{ + "content": "Found 3 products", + "content_type": "cards", + "content_attributes": { + "items": [ + { + "title": "Product Name", + "description": "Special: €89.99 | Stock: 100 | Sold: 50", + "media_url": "https://...", + "actions": [ + { + "type": "link", + "text": "View Details", + "uri": "https://www.gaia888.com/product/detail?spuId=12345" + }, + { + "type": "link", + "text": "Buy Now", + "uri": "https://www.gaia888.com/product/detail?spuId=12345" + } + ] + } + ] + } +} +``` + +### 4. Product Agent - 搜索结果处理 +**文件**: `agent/agents/product.py:206-313` + +**修改内容**: 在 `_generate_product_response` 方法中添加特殊处理逻辑 + +**逻辑**: +1. 检测是否为 `search_spu_products` 工具返回 +2. 如果是,直接调用 `send_product_cards` 发送商品卡片 +3. 如果失败,降级到文本响应 + +### 5. Product Agent - Prompt 更新 +**文件**: `agent/agents/product.py:22-52` + +**修改内容**: 更新 PRODUCT_AGENT_PROMPT 可用工具列表 + +**更新**: +- 将 `search_spu_products` 设为第一个工具(推荐使用) +- 说明此工具使用 Mall API 搜索商品 SPU,支持用户 token 认证,返回卡片格式展示 +- 原有的 `search_products` 标记为高级搜索工具(使用 Hyperf API) + +### 6. Docker Compose - 环境变量配置 +**文件**: `docker-compose.yml:146-170` + +**修改内容**: 为 Product MCP 添加 Mall API 相关环境变量和 env_file + +```yaml +product_mcp: + env_file: + - .env + environment: + MALL_API_URL: ${MALL_API_URL} + MALL_TENANT_ID: ${MALL_TENANT_ID:-2} + MALL_CURRENCY_CODE: ${MALL_CURRENCY_CODE:-EUR} + MALL_LANGUAGE_ID: ${MALL_LANGUAGE_ID:-1} + MALL_SOURCE: ${MALL_SOURCE:-us.qa1.gaia888.com} +``` + +## 使用方式 + +### 用户在 Chatwoot 中搜索商品 + +**示例对话**: +``` +用户: 搜索 61607 +用户: 我想找手机 +用户: 查找电脑产品 +``` + +### Agent 调用流程 + +1. **Router Agent** 识别商品搜索意图 +2. **Product Agent** 接收请求 +3. **LLM** 决定调用 `search_spu_products` 工具 +4. **Product MCP** 执行工具调用: + - 从 state 获取 `user_token`(用户的 JWT token) + - 创建 MallClient 实例 + - 调用 Mall API `/mall/api/spu?keyword=xxx&pageSize=60&page=1` + - 解析返回结果 +5. **Product Agent** 接收工具结果 +6. **Chatwoot 集成** 发送商品卡片 + +## 商品卡片展示 + +### 中文界面 + +``` +┌─────────────────────────────────┐ +│ 找到 3 个商品 │ +├─────────────────────────────────┤ +│ ┌─────────────────────────────┐ │ +│ │ [图片] │ │ +│ │ Product Name │ │ +│ │ 特价: €89.99 | 库存: 100 │ │ +│ │ [查看详情] [立即购买] │ │ +│ └─────────────────────────────┘ │ +│ ┌─────────────────────────────┐ │ +│ │ [图片] │ │ +│ │ Product Name 2 │ │ +│ │ €99.99 | 库存: 50 │ │ +│ │ [查看详情] [立即购买] │ │ +│ └─────────────────────────────┘ │ +└─────────────────────────────────┘ +``` + +### 英文界面 + +``` +┌─────────────────────────────────┐ +│ Found 3 products │ +├─────────────────────────────────┤ +│ ┌─────────────────────────────┐ │ +│ │ [Image] │ │ +│ │ Product Name │ │ +│ │ Special: €89.99 | Stock: 100 │ │ +│ │ [View Details] [Buy Now] │ │ +│ └─────────────────────────────┘ │ +└─────────────────────────────────┘ +``` + +## 多语言支持 + +商品卡片支持以下语言: + +| 语言 | 代码 | 示例描述 | +|------|------|----------| +| 中文 | zh | 特价: €89.99 \| 库存: 100 | +| 英语 | en | Special: €89.99 \| Stock: 100 | +| 荷兰语 | nl | Aanbieding: €89.99 \| Voorraad: 100 | +| 德语 | de | Angebot: €89.99 \| Lager: 100 | +| 西班牙语 | es | Oferta: €89.99 \| Stock: 100 | +| 法语 | fr | Spécial: €89.99 \| Stock: 100 | +| 意大利语 | it | Offerta: €89.99 \| Stock: 100 | +| 土耳其语 | tr | Özel: €89.99 \| Stok: 100 | + +## API 接口说明 + +### Mall API: 搜索 SPU 商品 + +**端点**: `GET /mall/api/spu` + +**请求参数**: +``` +keyword: 搜索关键词(商品名称、编号等) +pageSize: 每页数量(默认 60,最大 100) +page: 页码(默认 1) +``` + +**请求头**: +``` +Authorization: Bearer {user_token} +Content-Type: application/json +tenant-Id: 2 +currency-code: EUR +language-id: 1 +source: us.qa1.gaia888.com +``` + +**响应格式**: +```json +{ + "code": 200, + "msg": "success", + "result": { + "list": [ + { + "spuId": "12345", + "spuSn": "61607", + "productName": "Product Name", + "productImage": "https://...", + "price": "99.99", + "specialPrice": "89.99", + "stock": 100, + "salesCount": 50 + } + ], + "total": 156 + } +} +``` + +## 配置要求 + +### 环境变量 + +在 `.env` 文件中配置: + +```env +# Mall API +MALL_API_URL=https://apicn.qa1.gaia888.com +MALL_TENANT_ID=2 +MALL_CURRENCY_CODE=EUR +MALL_LANGUAGE_ID=1 +MALL_SOURCE=us.qa1.gaia888.com + +# 前端 URL(用于生成商品详情链接) +FRONTEND_URL=https://www.gaia888.com +``` + +### 必需条件 + +1. **用户认证**: 商品搜索需要用户登录,获取 JWT token +2. **Token 注入**: Agent 会自动从 Chatwoot webhook 中提取 `user_token` +3. **网络访问**: Agent 需要能够访问 Mall API(`apicn.qa1.gaia888.com`) + +## 测试 + +### 1. 测试脚本 + +运行测试脚本(需要提供有效的 user token): + +```bash +python test_product_search.py +``` + +### 2. 在 Chatwoot 中测试 + +1. 打开 Chatwoot 对话框 +2. 发送搜索请求,例如: + - "搜索 61607" + - "我想找手机" + - "查找商品:电脑" + +### 3. 查看 MCP 工具 + +访问 Product MCP 健康检查: + +```bash +curl http://localhost:8004/health +``` + +预期响应: +```json +{ + "status": "healthy", + "service": "product_mcp", + "version": "1.0.0" +} +``` + +## 故障排查 + +### 问题 1: 返回 "用户未登录" + +**原因**: 缺少有效的 `user_token` + +**解决方案**: +1. 确保用户已在 Chatwoot 中登录 +2. 检查 webhook 是否正确提取 `user_token` +3. 查看日志:`docker-compose logs -f agent` + +### 问题 2: 返回空商品列表 + +**原因**: +- 关键词不匹配 +- Mall API 返回空结果 + +**解决方案**: +1. 尝试不同的关键词 +2. 检查 Mall API 是否可访问 +3. 查看 Mall API 响应日志 + +### 问题 3: 卡片无法显示 + +**原因**: +- 商品图片 URL 无效 +- Chatwoot 不支持 cards 格式 + +**解决方案**: +1. 检查 `product_image` 字段是否为有效 URL +2. 验证 Chatwoot API 版本是否支持 cards +3. 查看 Chatwoot 集成日志 + +## 性能优化 + +### 已实现的优化 + +1. **分页限制**: 默认返回 60 个商品,避免数据过大 +2. **用户认证**: 使用用户 token 而不是全局 API token,更安全 +3. **错误处理**: 优雅降级到文本响应 + +### 未来可优化 + +1. **缓存热门搜索**: 缓存常见关键词的搜索结果 +2. **并行搜索**: 支持多关键词并行搜索 +3. **智能推荐**: 基于搜索历史智能推荐 + +## 相关文件清单 + +| 文件 | 说明 | +|------|------| +| `mcp_servers/shared/mall_client.py` | Mall API 客户端(新增 SPU 搜索方法) | +| `mcp_servers/product_mcp/server.py` | Product MCP(新增 SPU 搜索工具) | +| `agent/integrations/chatwoot.py` | Chatwoot 集成(新增商品卡片方法) | +| `agent/agents/product.py` | Product Agent(新增卡片处理逻辑) | +| `docker-compose.yml` | 容器配置(Product MCP 环境变量) | + +## 版本历史 + +- **2026-01-26**: 初始版本 + - 添加 Mall API SPU 搜索支持 + - 添加 Chatwoot cards 商品展示 + - 支持多语言商品卡片 + - 集成用户认证 + +--- + +**文档版本**: 1.0 +**最后更新**: 2026-01-26 +**维护者**: Claude Code