助手 · RAG 从关键词到向量(现状 → 效果 → 升级路径)
日期: 2026-08-18 | 作者: sunxin(+ Cursor AI 结对讲解) 关联代码:
KeywordKnowledgeRetrievalAdapter·IKnowledgeRetrievalPort·KnowledgeSnippet·resources/assistant-knowledge/destinations.json·AssistantApplicationService.appendKnowledge关联里程碑:北极星与终局蓝图 M4a· 建设路线图 M4a 关联决策: ADR-039 姊妹篇: 幻觉与 grounding(RAG 的引用/拒答是防幻觉手段)· 向量检索新手科普(embedding / 切块 / 内存索引)
TL;DR
- RAG(Retrieval-Augmented Generation,检索增强生成)= 先「检索」到相关知识文本,再把它「塞回模型上下文」让模型据此「生成」答案。 核心是「生成」二字,且生成时有真实资料兜底、能拒答、能引用。
- 本项目现在是「退化版 RAG」:
KeywordKnowledgeRetrievalAdapter从本地destinations.json(仅 6 条)按关键词重叠打分检索,命中就把内容直接字符串拼接成一句「小贴士」——只检索、不生成、无语义、无引用、无拒答。 - 它离真 RAG 还差三步:内容太少且不结构化 → 检索是关键词非语义(错别字/别名命不中)→ 检索结果不回喂模型(没有「生成」这步)。
- 升级路径 L0→L4:现状(关键词拼接) → 扩内容+切块 → 向量语义检索 → 回喂模型生成 + 引用 + 拒答(=真 RAG / M4a) → 混合检索+rerank+防注入。
IKnowledgeRetrievalPort已把检索隔离:关键词换向量不动业务代码,只换 Adapter 实现。原文档可放 OSS,但「向量索引」是另一层(进向量库或内存),别混。
目录
- 1. 什么是 RAG(以及什么不是)
- 2. 现状:本项目的「退化版 RAG」做到哪了
- 3. 效果与短板
- 4. 升级路径 L0 → L4
- 5. 存储:向量库 / Embedding / OSS 怎么摆
- 6. 在 TourMate 落地「景点攻略库」的建议
- 7. 成本与隔离
- 8. 附录
1. 什么是 RAG(以及什么不是)
RAG 三步:
用户问题 → ①检索(Retrieval):从知识库找相关片段
→ ②增强(Augment):把片段拼进 Prompt 上下文
→ ③生成(Generation):模型据此生成答案(带出处、无资料则拒答)它解决的问题:大模型的知识是训练时冻结的、且不含你的私有资料。RAG 让模型「开卷考试」——答题前先翻你给的资料,从而回答私有/最新/垂直领域问题,且减少幻觉(有据可依)。
什么不是 RAG(重要澄清):
| 场景 | 是不是 RAG | 为什么 |
|---|---|---|
助手查平台活动(searchActivitiesAdvanced) | ❌ 不是 | 检索结果直接当卡片展示,从不回喂模型;模型只在入口抽意图。这是 Tool Use / 结构化查询 |
| 命中后拼「小贴士」(现状) | 🟡 退化版 | 检索到了,但直接字符串拼接、不回喂模型生成。只有「检索」没有「生成」 |
| 「退款规则/景点攻略」问答,检索资料→喂模型→生成带引用答案(M4a) | ✅ 真 RAG | 检索的知识回喂模型据此生成 |
一句话分界:检索到的东西要不要回喂给模型再生成? 要 = RAG;不要(直接展示)= Tool Use。
2. 现状:本项目的「退化版 RAG」做到哪了
2.1 组件
| 部件 | 位置 | 作用 |
|---|---|---|
| 出站 Port | domain/assistant/port/out/IKnowledgeRetrievalPort | retrieve(query, topK) 契约,隔离检索实现 |
| 关键词实现 | infrastructure/.../assistant/knowledge/KeywordKnowledgeRetrievalAdapter | 从本地 JSON 按关键词打分检索 |
| 知识库文件 | resources/assistant-knowledge/destinations.json | 6 条:赛里木湖 / 新疆 / 青海 / 北京 / 成都 / 户外徒步 |
| 片段模型 | domain/assistant/model/KnowledgeSnippet | title / content / source / score |
| 调用点 | AssistantApplicationService.appendKnowledge | 命中活动后追加「小贴士」 |
2.2 检索算法(关键词重叠打分)
// 命中的关键词越长/越多,得分越高(长词更具体,权重更大)
for (String kw : entry.getKeywords()) {
if (query.contains(kw)) score += kw.length();
}例:destinations.json 里赛里木湖条目的 keywords 是 ["赛里木湖","赛里木","博尔塔拉","博乐"],用户 query 里出现哪个就加几分,取 top-1。
2.3 触发条件(很窄)
只有 SEARCH_ACTIVITIES + 有目的地 + 命中活动≥1 条 时,才去检索并在回复末尾拼一句:
已按「赛里木湖 / ≤2000元」为你找到 4 个活动:
(4 张真活动卡片)
小贴士 · 赛里木湖:6-9 月是最佳季节,7 月环湖野花盛开;海拔约 2070m 早晚温差大… ← 这句是拼接来的推荐 / 拒答 / 追问 / 0 条这几条路都不补贴士。
3. 效果与短板
达到的效果:命中目的地时,回复多一句静态、可信、本地维护的小贴士,轻量增强体验;命不中就什么都不加(失败优雅降级)。无 Key、离线、可测——很适合起步。
短板(正是升级方向):
| 短板 | 表现 | 影响 |
|---|---|---|
| 只检索、不生成 | 内容原样拼接,模型没参与 | 严格说不算 RAG,也没发挥模型「组织成人话」的能力 |
| 无语义 | 关键词字符串精确包含才命中 | 「赛里目湖 / 塞木屋河」这种错别字/别名命不中(对应 M1 遗留技术债①) |
| 内容太少 | 就 6 条,且非结构化 | 覆盖面窄,问细节答不上 |
| 无引用、无拒答 | 不标出处、命不中静默 | 到了知识问答场景(问「海拔多少」)无法可信作答 |
4. 升级路径 L0 → L4
每一级都可停、可交付。IKnowledgeRetrievalPort 的存在保证升级只换实现、不动业务。
| 级别 | 能力 | 关键动作 | 里程碑 |
|---|---|---|---|
| L0 现状 | 关键词检索 + 直接拼接 | 已完成(KeywordKnowledgeRetrievalAdapter) | M0 骨架 |
| L1 内容工程 | 扩充结构化攻略库 + 切块 + 元数据 | destinations.json → 每景点结构化(最佳季节/交通/注意/玩法/周边),长文切块 chunk + 打标签(省份/类型) | M4a 前置 |
| L2 向量语义检索 | Embedding + 向量相似度 | 文本转 embedding 存向量库;query 也转 embedding 算余弦相似度 top-K。解决别名/错别字/近义 | M4a |
| L3 真 RAG(生成) ⭐ | 检索片段回喂 LLM 生成 + 出处引用 + 无资料拒答 | 把 top-K 片段拼进 Prompt,让模型据此生成答案并标来源;检索不到就拒答 | M4a 达成 |
| L4 生产级检索 | 混合检索 + rerank + 防注入 | 关键词 + 向量混合召回,再用 rerank 模型精排;检索内容当「数据」不当「指令」(防提示注入) | M4a+ |
L1→L2 是「检索」的质变(关键词→语义);L2→L3 是「有没有 RAG」的质变(加上了「生成」)。别跳级——先把内容和检索做扎实,再上生成。
5. 存储:向量库 / Embedding / OSS 怎么摆
三个常被混为一谈的概念,分清楚:
| 概念 | 是什么 | 放哪 |
|---|---|---|
| 原始文档 | 攻略 markdown / JSON 原文 | resources(起步)或 OSS(量大/要热更新) |
| Embedding | 把一段文本转成的一串数字向量(语义指纹) | 由 embedding 模型算出,存进向量库 |
| 向量索引 / 向量库 | 存 embedding + 支持相似度检索的库 | pgvector / ES / Milvus / Qdrant 等 |
关键澄清(回答「知识库是不是存向量库、能不能放 OSS」):
- 原文档可以放 OSS,启动时或定时拉下来切块、算 embedding、灌进向量库。OSS 只是「原文仓库」,方便运营改攻略不用发版。
- 但检索要查的是「向量索引」,不是直接查 OSS 文件。所以「放 OSS」和「上向量库」不是二选一,是两层:OSS 存原文 + 向量库存索引。
- 起步不必上向量库:现在关键词版连向量都不用(直接读 resources JSON)。真要上语义(L2)再引向量库。小规模甚至可以「内存向量」(启动时把几百条 embedding 加载进内存算余弦),省一个中间件。
选型建议(到 L2 时再定):项目已用 MySQL,pgvector 需要 Postgres(不匹配);可考虑 ES 向量字段(若已有 ES)或轻量 Qdrant/Milvus;规模很小时先「内存向量」。这属于 M4a 的决策点,到时单独出 ADR。
6. 在 TourMate 落地「景点攻略库」的建议
你想自建景点攻略库(而不是用 WebSearch 实时查)——方向对,理由见《幻觉与 grounding》:护城河是真实资料、可控、便宜、不飘。落地建议:
- 内容先行(L1):把攻略库从 6 条扩成有价值的量(热门目的地各一篇结构化:最佳季节 / 怎么去 / 注意事项 / 玩法 / 周边)。内容质量 > 检索花哨——这步不需要任何新技术,却决定 80% 的体验。
- 顺手解决技术债①:攻略条目的
keywords里补别名/常见错别字(「赛里木/赛里木胡/乌市」),关键词版立刻能扛一部分别名;语义层面的彻底解决等 L2 向量。 - 两个消费场景分清:
- 给活动补「小贴士」(现状,SEARCH 命中后拼接)→ 保持轻量,不必上生成。
- 给「出行计划 / 知识问答」当素材(MAKE_TRAVEL_PLAN / M4a)→ 这才需要 L3「回喂模型生成 + 引用 + 拒答」。
- 别一上来就向量库:先 L1 把内容做厚、L0 关键词继续用,等「知识问答」真要做了再上 L2/L3。
7. 成本与隔离
- 检索本身几乎零成本:关键词版读本地文件;向量版主要成本是「算 embedding」(一次性建库 + 每次 query 一次 embedding 调用,很便宜)。
- 贵的是 L3 的「生成」那次 LLM 调用——和 MAKE_TRAVEL_PLAN 一样属于生成型,要配成本闸门(ADR-040)。
- 隔离纪律(ADR-039):所有 RAG 代码只在
assistant包内;IKnowledgeRetrievalPort是唯一契约,Domain 不认向量库/embedding 具体类型。将来剥离助手 = 删包即可,业务域零改。
8. 附录
8.1 术语对照
| 术语 | 中文 | 说明 |
|---|---|---|
| RAG | 检索增强生成 | 检索资料 → 喂模型 → 生成答案 |
| Retrieval | 检索 | 从知识库找相关片段 |
| Embedding | 嵌入向量 | 文本的数字化语义指纹 |
| Vector DB | 向量数据库 | 存 embedding + 相似度检索 |
| Chunk | 切块 | 把长文切成适合检索的小段 |
| Rerank | 重排精排 | 对召回结果再用模型精排 |
| Citation | 引用 | 答案标明出处,可追溯 |
| Prompt Injection | 提示注入 | 资料/输入里藏指令篡改模型行为 |
8.2 常见疑问
Q:查数据库活动算 RAG 吗? 不算。活动查询结果直接展示、不回喂模型,是 Tool Use。RAG 的关键是「检索到的知识回喂模型再生成」。
Q:一定要上向量库才算 RAG 吗? 不是。向量是「检索」的一种升级(语义)。就算用关键词检索,只要把结果回喂模型生成 + 引用 + 拒答,也是 RAG(只是检索精度低)。反过来,用了向量但只拼接不生成,仍不是真 RAG。
Q:原文放 OSS,检索快吗? 检索不查 OSS,查的是向量索引(或内存)。OSS 只存原文,供建库时读取和运营热更新。
8.3 项目内关联
- 防幻觉(RAG 的引用/拒答):
助手-大模型幻觉与grounding - 意图识别(检索的上游触发):
助手-意图识别是怎么做到的 - 里程碑 M4a:
北极星与终局蓝图 §6 - RAG 现状 FAQ:
出行助手全场景流程图 FAQ Q4