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