refactor: 移除 search_products 工具,统一使用 search_spu_products

## 修改内容

### 1. 简化 Product Agent prompt
- 移除 `search_products` 工具说明
- 移除工具选择警告和说明
- 只保留 `search_spu_products` 作为唯一商品搜索工具
- 调整工具序号 1-5

### 2. 添加工具名自动映射
**位置**:第 195-201 行(非 JSON 格式),第 221-227 行(JSON 格式)

**功能**:
- 自动将 `search_products` 转换为 `search_spu_products`
- 防止 LLM 缓存或习惯导致的旧工具调用
- 添加日志记录映射操作

**示例**:
```python
# LLM 返回
{"tool_name": "search_products", "arguments": {"query": "ring"}}

# 自动转换为
{"tool_name": "search_spu_products", "arguments": {"keyword": "ring"}}
```

### 3. 添加参数自动映射
**位置**:第 240-246 行

**功能**:
- 自动将 `query` 参数转换为 `keyword` 参数
- 兼容 LLM 使用旧参数名的情况

**示例**:
```python
# LLM 返回
{"arguments": {"query": "ring"}}

# 自动转换为
{"arguments": {"keyword": "ring"}}
```

## 优势

1. **简化逻辑**:LLM 只有一个搜索工具可选,不会选错
2. **向后兼容**:即使 LLM 调用旧工具,也能自动转换
3. **参数兼容**:支持旧参数名 `query`,自动转为 `keyword`
4. **可观测性**:所有映射操作都有日志记录

## 预期效果
- LLM 调用 `search_spu_products`(Mall API)
- 返回商品卡片到 Chatwoot
- 即使调用旧工具也能正常工作

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
wangliang
2026-01-26 18:19:12 +08:00
parent e58c3f0caf
commit 1aeb17fcce

View File

@@ -19,46 +19,27 @@ PRODUCT_AGENT_PROMPT = """你是一个专业的 B2B 商品顾问助手。
- 库存查询 - 库存查询
- 商品详情 - 商品详情
## ⚠️ 重要:商品搜索工具选择
**商品搜索必须优先使用 `search_spu_products` 工具!**
- ✅ **search_spu_products**:使用 Mall API支持用户认证返回精美卡片展示推荐
- ⚠️ **search_products**:仅用于高级搜索(需要复杂过滤条件时)
**普通商品搜索(如 "ring""手机""iPhone")必须使用 `search_spu_products`**
## 可用工具 ## 可用工具
1. **search_spu_products** - 搜索商品(使用 Mall API推荐 1. **search_spu_products** - 搜索商品
- keyword: 搜索关键词(商品名称、编号等) - keyword: 搜索关键词(商品名称、编号等)
- page_size: 每页数量(默认 60最大 100 - page_size: 每页数量(默认 60最大 100
- page: 页码(默认 1 - page: 页码(默认 1
- 说明:此工具使用 Mall API 搜索商品 SPU支持用户 token 认证,返回卡片格式展示 - 说明:此工具使用 Mall API 搜索商品 SPU支持用户 token 认证,返回卡片格式展示
- **适用于所有普通商品搜索请求**
2. **search_products** - 搜索商品(使用 Hyperf API 2. **get_product_detail** - 获取商品详情
- query: 搜索关键词
- filters: 过滤条件category, price_range, brand 等)
- sort: 排序方式price_asc/price_desc/sales/latest
- page: 页码
- page_size: 每页数量
- 说明:此工具用于高级搜索,支持多维度过滤
- **仅在需要复杂过滤条件时使用**
3. **get_product_detail** - 获取商品详情
- product_id: 商品ID - product_id: 商品ID
4. **recommend_products** - 智能推荐 3. **recommend_products** - 智能推荐
- context: 推荐上下文(可包含当前查询、浏览历史等) - context: 推荐上下文(可包含当前查询、浏览历史等)
- limit: 推荐数量 - limit: 推荐数量
5. **get_quote** - B2B 询价 4. **get_quote** - B2B 询价
- product_id: 商品ID - product_id: 商品ID
- quantity: 采购数量 - quantity: 采购数量
- delivery_address: 收货地址(可选,用于计算运费) - delivery_address: 收货地址(可选,用于计算运费)
6. **check_inventory** - 库存查询 5. **check_inventory** - 库存查询
- product_ids: 商品ID列表 - product_ids: 商品ID列表
- warehouse: 仓库(可选) - warehouse: 仓库(可选)
@@ -88,6 +69,18 @@ PRODUCT_AGENT_PROMPT = """你是一个专业的 B2B 商品顾问助手。
} }
``` ```
用户说:"查找手机"
返回:
```json
{
"action": "call_tool",
"tool_name": "search_spu_products",
"arguments": {
"keyword": "手机"
}
}
```
当需要向用户询问更多信息时: 当需要向用户询问更多信息时:
```json ```json
{ {
@@ -199,6 +192,14 @@ async def product_agent(state: AgentState) -> AgentState:
tool_name = lines[0].strip() tool_name = lines[0].strip()
args_json = lines[1].strip() if len(lines) > 1 else '{}' args_json = lines[1].strip() if len(lines) > 1 else '{}'
# Map old tool name to new tool name
if tool_name == "search_products":
tool_name = "search_spu_products"
logger.info(
"Tool name mapped: search_products -> search_spu_products",
conversation_id=state["conversation_id"]
)
try: try:
arguments = json.loads(args_json) if args_json else {} arguments = json.loads(args_json) if args_json else {}
result = { result = {
@@ -217,6 +218,14 @@ async def product_agent(state: AgentState) -> AgentState:
# Standard JSON format # Standard JSON format
result = json.loads(content) result = json.loads(content)
# Auto-map search_products to search_spu_products in JSON format
if result.get("tool_name") == "search_products":
result["tool_name"] = "search_spu_products"
logger.info(
"Tool name mapped: search_products -> search_spu_products",
conversation_id=state["conversation_id"]
)
action = result.get("action") action = result.get("action")
if action == "call_tool": if action == "call_tool":
@@ -228,6 +237,14 @@ async def product_agent(state: AgentState) -> AgentState:
arguments["user_id"] = state["user_id"] arguments["user_id"] = state["user_id"]
arguments["account_id"] = state["account_id"] arguments["account_id"] = state["account_id"]
# Map "query" parameter to "keyword" for compatibility
if "query" in arguments and "keyword" not in arguments:
arguments["keyword"] = arguments.pop("query")
logger.info(
"Parameter mapped: query -> keyword",
conversation_id=state["conversation_id"]
)
# Inject context for recommendation # Inject context for recommendation
if result["tool_name"] == "recommend_products": if result["tool_name"] == "recommend_products":
arguments["user_id"] = state["user_id"] arguments["user_id"] = state["user_id"]