fix: 更新 Product Agent prompt 添加 search_spu_products 工具说明

## 问题
搜索商品时返回错误的工具调用 search_products 而非 search_spu_products

## 根本原因
Product Agent 的 PRODUCT_AGENT_PROMPT 中没有列出 search_spu_products 工具,
导致 LLM 不知道可以使用 Mall API 的 SPU 搜索工具

## 修改内容

### agent/agents/product.py
- 将 search_spu_products 设为第一个工具(推荐使用)
- 说明此工具使用 Mall API 搜索商品 SPU,支持用户 token 认证,返回卡片格式展示
- 原有的 search_products 标记为高级搜索工具(使用 Hyperf API)
- 调整工具序号 1-6

### docs/PRODUCT_SEARCH_SERVICE.md
- 添加 Product Agent Prompt 更新说明章节
- 调整章节序号

## 预期效果
LLM 现在应该优先使用 search_spu_products 工具进行商品搜索,
返回 Mall API 的商品数据并以 Chatwoot cards 格式展示

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
wangliang
2026-01-26 17:50:29 +08:00
parent 2dd46a8626
commit fa2c8f8102
2 changed files with 483 additions and 11 deletions

View File

@@ -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: 搜索关键词 - query: 搜索关键词
- filters: 过滤条件category, price_range, brand 等) - filters: 过滤条件category, price_range, brand 等)
- sort: 排序方式price_asc/price_desc/sales/latest - sort: 排序方式price_asc/price_desc/sales/latest
- page: 页码 - page: 页码
- page_size: 每页数量 - page_size: 每页数量
- 说明:此工具用于高级搜索,支持多维度过滤
2. **get_product_detail** - 获取商品详情 3. **get_product_detail** - 获取商品详情
- product_id: 商品ID - product_id: 商品ID
3. **recommend_products** - 智能推荐 4. **recommend_products** - 智能推荐
- context: 推荐上下文(可包含当前查询、浏览历史等) - context: 推荐上下文(可包含当前查询、浏览历史等)
- limit: 推荐数量 - limit: 推荐数量
4. **get_quote** - B2B 询价 5. **get_quote** - B2B 询价
- product_id: 商品ID - product_id: 商品ID
- quantity: 采购数量 - quantity: 采购数量
- delivery_address: 收货地址(可选,用于计算运费) - delivery_address: 收货地址(可选,用于计算运费)
5. **check_inventory** - 库存查询 6. **check_inventory** - 库存查询
- product_ids: 商品ID列表 - product_ids: 商品ID列表
- warehouse: 仓库(可选) - warehouse: 仓库(可选)
@@ -205,13 +212,70 @@ async def product_agent(state: AgentState) -> AgentState:
async def _generate_product_response(state: AgentState) -> AgentState: async def _generate_product_response(state: AgentState) -> AgentState:
"""Generate response based on product tool results""" """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 = [] tool_context = []
for result in state["tool_results"]: for result in state["tool_results"]:
if result["success"]: if result["success"]:
data = result["data"] data = result["data"]
tool_context.append(f"工具 {result['tool_name']} 返回:\n{json.dumps(data, ensure_ascii=False, indent=2)}") tool_context.append(f"工具 {result['tool_name']} 返回:\n{json.dumps(data, ensure_ascii=False, indent=2)}")
# Extract product context # Extract product context
if isinstance(data, dict): if isinstance(data, dict):
if data.get("product_id"): 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}) state = update_context(state, {"recent_product_ids": product_ids})
else: else:
tool_context.append(f"工具 {result['tool_name']} 执行失败: {result['error']}") tool_context.append(f"工具 {result['tool_name']} 执行失败: {result['error']}")
prompt = f"""基于以下商品系统返回的信息,生成对用户的回复。 prompt = f"""基于以下商品系统返回的信息,生成对用户的回复。
用户问题: {state["current_message"]} 用户问题: {state["current_message"]}
@@ -238,18 +302,18 @@ async def _generate_product_response(state: AgentState) -> AgentState:
- 结果较多时可以总结关键信息 - 结果较多时可以总结关键信息
只返回回复内容,不要返回 JSON。""" 只返回回复内容,不要返回 JSON。"""
messages = [ messages = [
Message(role="system", content="你是一个专业的商品顾问,请根据系统返回的信息回答用户的商品问题。"), Message(role="system", content="你是一个专业的商品顾问,请根据系统返回的信息回答用户的商品问题。"),
Message(role="user", content=prompt) Message(role="user", content=prompt)
] ]
try: try:
llm = get_llm_client() llm = get_llm_client()
response = await llm.chat(messages, temperature=0.7) response = await llm.chat(messages, temperature=0.7)
state = set_response(state, response.content) state = set_response(state, response.content)
return state return state
except Exception as e: except Exception as e:
logger.error("Product response generation failed", error=str(e)) logger.error("Product response generation failed", error=str(e))
state = set_response(state, "抱歉,处理商品信息时遇到问题。请稍后重试或联系人工客服。") state = set_response(state, "抱歉,处理商品信息时遇到问题。请稍后重试或联系人工客服。")

View File

@@ -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