Day 2 · 通读项目 AI 搜索完整链路
| 属性 | 值 |
|---|---|
| 日期 | 2026-08-13(周四) |
| 周次 | 第1周:双轨认知框架建立 |
| 轨 | 轨2 · 项目 Agent 落地调研 |
| 时间 | 1 小时 |
| 状态 | ✅ 已完成 |
📋 Checklist
- 阅读 AI 搜索前端页面 ai-search/(index.vue + result.vue)
- 阅读后端 SearchServiceImpl(搜索逻辑 + AI 调用)
- 阅读 OpenAiUtils.java(OpenAI 调用封装)
- 阅读 AiSearchProductResp(返回数据结构)
- 画出「用户输入 → AI 回复 → 商品展示」的完整数据流
- 记录:当前 AI 搜索的局限性(为什么它不是 Agent?)
📖 阅读材料
| 材料 | 路径 |
|---|---|
| 前端 AI 搜索首页 | smallrig-shop-mall/pc/src/pages/ai-search/index.vue |
| 前端 AI 搜索结果 | smallrig-shop-mall/pc/src/pages/ai-search/result.vue |
| 后端搜索服务 | smallrig-mall-product/.../search/impl/SearchServiceImpl.java |
| OpenAI 工具类 | smallrig-mall-plugin-starter/.../util/OpenAiUtils.java |
| 返回数据结构 | smallrig-mall-common/.../response/.../AiSearchProductResp.java |
| 前端 M 端 AI 搜索 | smallrig-shop-mall/mobile/src/pages/ai-search/ |
✍️ 学习笔记
在此记录你的笔记...
数据流图
第 1 步:前端
用户输入 "A7III 兔笼" → POST /webapi/search/aiSearchProduct
请求体:{aiKeyWorks: ["A7III 兔笼"], pageSize: 20}
第 2 步:后端 SearchServiceImpl.aiSearchProduct()
│
├─ 2a. 调用 OpenAI Chat API(意图分析)
│ System Prompt 让模型分析用户意图
│ 模型返回 JSON:{"searchTerm": "A7III cage", "aiReply": "为你找到..."}
│ 关键代码:OpenAiUtils.searchAnalyzeIntent() → line 230
│
├─ 2b. 调用 OpenAI Embedding API(文本转向量)
│ 把 searchTerm 转成向量,用于 ES 语义搜索
│ 关键代码:OpenAiUtils.textToVector() → line 51
│
├─ 2c. Elasticsearch 向量搜索
│ 用向量匹配商品库,返回相似商品列表
│
└─ 2d. 组装返回
AiSearchProductResp {
searchTerm: "A7III cage",
aiReply: "为你找到适配 A7III 的配件...",
productList: [{id:101, name:"全包兔笼",...}, ...]
}
第 3 步:前端渲染
AiSearchResultReply 组件展示 aiReply 文本
ProductBaseCard 组件展示商品卡片列表
当前 AI 搜索的局限性(为什么它不是 Agent?)
用 Day 1 学到的 Agent 框架衡量:
| Agent 核心要素 | 当前 AI 搜索 | 差距 |
|---|---|---|
| 模型自主决策 | ❌ 没有。每次都调同一个 API,模型不判断要不要调 | 差一步 |
| tool_call 循环 | ❌ 没有。模型只输出一次 JSON(searchTerm + aiReply),不是 tool_call 格式 | 差一步 |
| 多轮对话 | ❌ 没有。用户输入 → 一次返回 → 结束。没有追问、没有多轮 | 差一步 |
| tool_response | ❌ 没有对应的概念。后端把 AI 返回的 searchTerm 当向量搜索的关键词,但这不是 tool_call/tool_response 循环 | 格式不同 |
本质区别:当前 AI 搜索是「把 AI 当翻译器用」——用户输入 → AI 提取关键词 + 生成文案 → 后端用关键词搜商品。AI 没有决策权,它只是管线里的一个环节。
对比图:
当前 AI 搜索:
user → [AI 意图分析] → [向量搜索] → [返回结果] → 展示
↑ 一次调用,AI 只是管线里的一个步骤
真正的 Agent:
user → 模型判断 → tool_call → 系统执行 → tool_response → 模型再判断 → 回复
↑ 模型是决策中枢,自主决定调不调、调哪个、调几次
❓ 疑问
📤 产出
- 数据流图
- 局限性分析
🔧 Agent 改造方案:当前代码 vs Agent 格式
第一步:定义 Tool(System Prompt 里新增)
<tools>
[
{
"name": "search_product",
"description": "搜索 SmallRig 商品库,按关键词和品类查找商品",
"parameters": {
"query": "搜索关键词,如 'A7III cage'",
"category": "商品品类:cage/tripod/monitor/mic/other",
"max_price": "最高预算(可选数字)",
"language": "语言代码:en/de/ja/ko/zh"
}
}
]
</tools>
第二步:模型输出 tool_call(替代当前代码的 searchTerm)
<|im_start|>assistant
<tool_call>
{
"name": "search_product",
"arguments": {
"query": "A7III cage",
"category": "cage",
"max_price": null,
"language": "en"
}
}
</tool_call>
<|im_end|>
对比当前代码:这替代了
OpenAiUtils.searchAnalyzeIntent()返回的searchTerm字段。但区别是——这是模型自主决定要调用 search_product 工具,而不是后端硬编码的管线。
第三步:后端路由到 handler(替代当前代码的硬编码搜索)
// 当前代码:SearchServiceImpl.line 227
List<ProductSearchResp> prdList = searchEsProductList(...);
// 改造后:这个函数变成 search_product 这个 Tool 的 handler
// 后端收到 tool_call 时,根据 name="search_product" 路由到这里
第四步:后端返回 tool_response(替代当前代码的 productList)
<|im_start|>tool
<tool_response>
{
"name": "search_product",
"content": {
"products": [
{"id": 101, "name": "A7III Full Cage", "price": 899},
{"id": 102, "name": "A7III Half Cage", "price": 599}
],
"total": 2,
"query": "A7III cage"
}
}
</tool_response>
<|im_end|>
对比当前代码:这替代了
AiSearchProductResp的productList字段。但区别是——这是 Tool 执行结果的通用格式,不绑定到特定的搜索 API。
第五步:模型读 tool_response,生成最终回复(替代当前代码的 aiReply)
<|im_start|>assistant
I found 2 cages compatible with your Sony A7III, both within your budget:
1. **A7III Full Cage** - $899
Full cage design, better protection for professional use.
2. **A7III Half Cage** - $599
Half cage design, lightweight and portable.
Would you like me to compare the detailed specs of these two?
<|im_end|>
对比当前代码:这替代了
OpenAiUtils.searchAnalyzeIntent()返回的aiReply字段。区别是——这是模型读了真实搜索结果后生成的回答,而不是在搜索前就提前生成的文案。
一张图看懂改造前后
改造前(当前代码):
┌───────────────────────┐
│ 一次 API 调用 │
用户输入 ───────→│ OpenAI 分析意图 │──────→ searchTerm → ES 搜索
│ 返回 searchTerm │ aiReply → 直接展示
│ + aiReply │
└───────────────────────┘
↑ 模型不知道后面有搜索 ↑ aiReply 是提前生成的,不是基于真实结果
改造后(Agent):
┌─────────────────┐
用户输入 ────────────────→│ 模型判断 │
│ 需要查商品吗? │
└────────┬────────┘
│ 是
↓
┌─────────────────┐
│ tool_call │──────→ 后端执行 search_product
│ search_product │ ES 搜索(和当前代码一样)
└─────────────────┘
│
↓
┌─────────────────┐
│ tool_response │←────── 返回真实商品列表
│ {products: [...]}│
└─────────────────┘
│
↓
┌─────────────────┐
│ 模型读结果, │
│ 生成最终回复 │──────→ 用户看到的结果
└─────────────────┘
↑ aiReply 是基于真实搜索结果的
核心差异总结
| 维度 | 当前代码 | Agent 改造后 |
|---|---|---|
| 模型输出 | {searchTerm, aiReply} |
<tool_call>{"name":"search_product","arguments":{...}} |
| 搜索由谁触发 | 后端硬编码(管线) | 模型自主决策(Tool Call) |
| 搜索时机 | 和 AI 调用同时发生 | 模型决定后才发生 |
| aiReply 生成时机 | 搜索前(提前生成文案) | 搜索后(基于真实结果生成) |
| 后端代码 | 写死管线,无法复用 | 通用 Tool 路由,任何 Tool 走同一套逻辑 |
最关键的一句话:当前代码的 searchTerm 和 aiReply 是在一次 API 调用里同时生成的,模型「猜」用户会搜到什么。Agent 改造后,模型先决策、再搜索、再回复——aiReply 是基于真实搜索结果的,不是猜的。
🛡️ 异常处理:模型参数不准确怎么办?
三层机制
第一层:参数描述里的值是示意,不是约束
"category": "商品品类:cage/tripod/monitor/mic/other"
这里的 cage/tripod/monitor/mic/other 是给模型看的提示,告诉它「品类大概长这样」,但模型不一定会严格按这个列表取值。模型可能输出:
✅ "category": "cage" ← 在提示范围内
✅ "category": "tripod" ← 在提示范围内
⚠️ "category": "stabilizer" ← 不在提示范围内,但模型觉得合理
⚠️ "category": "" ← 模型觉得不需要限定品类
第二层:后端不做判断,只如实返回结果
模型输出:tool_call { "category": "stabilizer" }
↓
后端 handler 执行:SELECT * FROM products WHERE category = "stabilizer"
↓
结果:返回 0 条数据
↓
tool_response:{ "products": [], "total": 0, "message": "未找到匹配商品" }
后端不管 category 值是否合理,只管执行。查不到就返回空结果。
第三层:模型读空结果后,自己决定怎么办
tool_response:{ "products": [], "total": 0 }
模型读后判断:
"stabilizer 品类没搜到结果,可能这个品类名不对。
我应该换个方式搜索——不用品类限制,直接用关键词搜。"
模型再次输出 tool_call:
{
"name": "search_product",
"arguments": {
"query": "stabilizer 稳定器",
"category": "", ← 这次不限定品类
"max_price": null
}
}
后端再次搜索 → 返回 5 条结果
模型读结果 → 生成最终回复
核心认知:后端只负责执行,不负责判断。查不到就返回空,模型自己决定要不要重试、换参数、换工具。这就是 Agent 和传统管线最大的区别——管线里异常是 bug,Agent 里异常是模型自主处理的一环。
🧪 Day 2 自测题
第 1 题:链路识别
当前 AI 搜索的流程中,模型(OpenAI)扮演了什么角色?
A. 决策中枢——自主判断要不要调用工具、调哪个
B. 翻译器——把用户输入转成关键词 + 生成文案
C. 数据库——返回商品列表
点击看答案
B。 当前流程中,模型只是把用户输入翻译成 searchTerm 和 aiReply,没有决策权。它只是管线里的一个环节,不是 Agent 里的决策中枢。
第 2 题:对比判断
以下哪个是当前 AI 搜索和 Agent 的核心区别?
A. 当前 AI 搜索用了 OpenAI,Agent 只能用 Hermes
B. 当前 AI 搜索是单次调用,Agent 有模型决策 → tool_call → tool_response 的循环
C. 当前 AI 搜索没有前端界面,Agent 有聊天界面
点击看答案
B。 核心区别是「有没有模型自主决策的循环」。当前 AI 搜索是「用户输入 → 一次 API 调用 → 返回」,Agent 是「模型判断 → 调工具 → 读结果 → 再判断」的循环。
第 3 题:代码定位
在 SearchServiceImpl.java 的 aiSearchProduct() 方法中,哪一步对应了「AI 意图分析」?
点击看答案
// 第 213 行
AiSearchDto aiSearchDto = this.buildVectorQueryParam(req);
// 第 370 行(buildVectorQueryParam 内部)
AiSearchDto aiSearchDto = OpenAiUtils.searchAnalyzeIntent(...)
这个方法调用了 OpenAiUtils.searchAnalyzeIntent(),后者调用 OpenAI Chat API,让模型分析用户意图,返回 {searchTerm, aiReply}。
第 4 题:改造思路
如果要把当前 AI 搜索改造成 Agent,第一步应该做什么?
点击看答案
第一步:把模型输出从 {searchTerm, aiReply} 改成 <tool_call> 格式。
当前模型输出的是「翻译结果」(关键词 + 文案),Agent 模型应该输出的是「决策指令」(我要调用 search_product 工具,参数是...)。这样后端就有了一个通用的 Tool 路由机制,而不是写死管线。
⏭️ 完成后:更新 当前进度 为 Day 3