旅行助手 AgenticRAG 系统的五次迭代优化实录
给 trplanning 多智能体旅行助手接 Agentic RAG 知识库的时候,我想得挺简单——切一切文档丢进向量库就完事了。结果第一次跑 RAGAS 评估,综合质量分 42.00%。忠实度 73%,检索召回 23%。说实话看到这个数字还是挺打击的。
后面花了点时间慢慢打磨,每改一轮跑一次评估,看数据说话。五轮迭代下来,自己写的测试脚本的综合质量分从 42% 提到了 76%,忠实度从 73% 拉到 98%,检索召回从 23% 干到 75%。这篇文章把整个过程记录下来——每轮做了什么、为什么这么做、效果怎么样。
背景
trplanning 是一个基于 LangGraph 的多智能体旅行助手,能够帮用户查火车票、查天气、规划行程。但它有一个明显短板——缺少领域知识。契机是当我问“徐州有什么好吃的”时候,他回答的没有我们所熟知的“徐州烧烤”“肉酱米线”,而是干拌面这种过时的信息。
于是有了 Agentic RAG:让 Agent 自主决策何时检索、检索什么,将静态旅行攻略知识注入对话。技术栈选型:
| 组件 | 选型 | 理由 |
|---|---|---|
| 向量数据库 | Milvus Lite | 可以部署在低配置的服务器上 |
| Embedding | BGE-M3 | 中文效果好,1024 维,支持稀疏+稠密双表征 |
| 混合检索 | BM25 + Dense | 关键词 + 语义双路召回 |
| 重排序 | BGE-Reranker | 重排序,生成更精准 |
| 评估 | RAGAS | 忠实度、相关性、精度、召回四维量化 |
一、优化全景
1 | V1 初始上线 ──→ V2 三阶段重构 ──→ V3 检索策略深挖 ──→ V4 召回精度打磨 ──→ V5 上下文质量收官 |
下面逐一拆解每轮迭代做了什么、为什么这样做、效果如何。
二、初始上线(综合分 42.00%)
对应提交:bb8d34d — AgenticRAG 构建添加
第一版的目标很简单:把整个 RAG 流水线跑通。用的是最经典的方案:
- Dense 检索:BGE-M3 Embedding → Milvus 向量检索 Top-K
- BM25 检索:基于 jieba 分词的稀疏检索
- 融合方式:加权求和(Dense 0.7 + BM25 0.3)
- 重排序:BGE-Reranker 模型
1 | 📊 RAGAS 指标(V1): |
问题诊断:
- 检索精度和召回双双偏低(~23%),说明大量相关文档没被召回
- 答案相关性不到 50%,LLM 拿到的上下文质量不够好
- 加权求和的融合方式太粗糙——Dense 和 BM25 的分数分布差异大,简单加权容易”一边倒”
三、三阶段重构(综合分 45.97%)
对应提交:b848d65 — RAG优化(文本切割优化、元数据增强、混合检索三阶段)
这一轮我做了三件事,也是后续所有优化的基础:
1. 文本切割优化
原来的切割方式太粗暴——按固定 token 数硬切,经常把一段完整攻略切成两半。新方案:
- 先按 Markdown 标题(
#/##/###)做语义边界切分 - 再在标题内部做递归语义切分,保证每个 chunk 是自包含的信息单元
- chunk_size 从 512 调整到 768,chunk_overlap 从 64 调整到 128
2. 元数据增强
每个 chunk 自动注入结构化元数据:
1 | { |
这对后续检索过滤和 Reranker 重排序至关重要——Reranker 可以根据城市、类别做意图匹配。
3. 混合检索三阶段流水线
1 | Phase 1: 并行召回 |
1 | 📊 RAGAS 指标(V2): |
收益:忠实度大幅提升(+11%),说明元数据增强让 LLM 更容易找到证据支撑。但检索精度和召回改善有限——融合策略仍然是瓶颈。
四、检索策略深度优化(综合分 51.02%)
对应提交:45dc46d(RRF 融合 + 意图感知 Reranker)、56f1f3e(HyDE 生成)
这是改动最大、收益最明显的一轮。做了三个关键优化:
1. RRF 融合替代加权求和
加权求和的根本问题是:Dense 分数范围是 0.50.9,BM25 分数范围是 050,直接加权毫无意义。RRF(Reciprocal Rank Fusion) 只看排序位置不看绝对分数:
$$
\text{RRFscore}(d) = \sum_{r \in R} \frac{1}{k + \text{rank}_r(d)}
$$
其中 $k=60$ 是平滑参数(后续改为可配置)。这样两路检索的排序结果可以公平融合,不再有”数值尺度”问题。
2. HyDE 假设性文档生成
这是整个优化中最”聪明”的一步。用户查询通常是短问题(”北京烤鸭哪家好吃”),而知识库文档是长文本。直接做 embedding 相似度匹配,存在 语义鸿沟。
HyDE(Hypothetical Document Embeddings)的做法:先让 LLM 根据用户问题生成一篇假设性的旅行攻略片段,再拿这个假设文档去做向量检索。
1 | 用户问:"北京烤鸭哪家好吃?" |
这样检索到的真实文档与用户意图的匹配度大幅提升。
3. 意图感知 Reranker
原来的 Reranker 只做纯文本相关性判断。现在在 Rerank 之前先做意图分类:
- 识别查询类型:美食推荐 / 景点攻略 / 交通出行 / 住宿建议 / 通用问答
- 根据意图类型调整 Reranker 的权重偏好(如美食类优先匹配”店名””菜品”等实体)
1 | 📊 RAGAS 指标(V3): |
收益:检索召回暴涨 +9.32%,hit_rate@5 直接拉满到 100%,MRR 冲到 95.24%。RRF + HyDE 的组合拳效果显著。但检索精度仍然偏低——召回太多了,混进了不少不太相关的文档。
五、召回精度打磨(综合分 54.54%)
对应提交:ab3a852(相邻 chunk 扩展)、3f84d18(MMR 多样性)、3a8c732(城市软匹配)、6ff109e(BM25 sentinel)、fb49cbb(硅基 Rerank)
V3 召回率上来了,但精度没跟上。这一轮的核心思路是:在不牺牲召回的前提下提高精度。
1. 相邻 Chunk 扩展
检索到的 chunk 往往缺少上下文。比如一篇攻略的第 3 段提到了”推荐全聚德”,但前因后果在第 2 段和第 4 段。解决方案:
- 检索命中 chunk_i 时,自动附带 chunk_{i-1} 和 chunk_{i+1}
- 去重后合并为完整上下文
- 这样每次检索实际获取的是”上下文窗口”而非孤立片段
2. MMR 多样性选择
当 Reranker 返回 Top-K 时,经常出现”全是一个主题”的情况——比如 5 条结果都是烤鸭推荐,而用户其实问的是”北京美食”(应该覆盖烤鸭、涮羊肉、炸酱面等)。
MMR(Maximal Marginal Relevance) 的做法:
- 第 1 轮:选最相关的结果
- 第 2~K 轮:在”相关性”和”与已选结果的差异性”之间取平衡
$$\text{MMR} = \arg\max_{d_i \in R \setminus S} \left[ \lambda \cdot \text{sim}(d_i, q) - (1-\lambda) \cdot \max_{d_j \in S} \text{sim}(d_i, d_j) \right]$$
参数 $\lambda=0.7$(后续调整为 0.65),保证结果多样性。
3. 城市软匹配 + 加分机制
用户说”京城有什么好吃的”,传统 BM25 搜不到”北京”。解决方案:
- 建立城市别名映射表:
{"京城": "北京", "魔都": "上海", "蓉城": "成都", ...} - 检索时自动展开查询词
- 城市匹配的文档获得额外加分(+0.1)
4. BM25 Sentinel 机制
BM25 需要扫描全量文档做关键词统计。每次请求都全量扫描太慢了。优化方案:
- 用一个轻量级 sentinel 标记判断语料库是否变更
- 仅在文档入库/删除时重建 BM25 索引
- 日常检索直接复用缓存索引
5. 硅基流动 Rerank 模型
BGE-Reranker虽然免费,但是效果不太行,于是切换到硅基流动的 QWen:Rerank。
1 | 📊 RAGAS 指标(V4): |
收益:检索精度暴涨 +17%,说明 MMR + 相邻 chunk 的组合有效减少了冗余结果。注意这轮评估集从 14 条扩展到 35 条,难度大幅提升,综合分仍然上涨,含金量很高。
六、上下文质量收官
对应提交:01d1188(元数据注入升级)、8c1026e(切分参数调优)、10f27d4(答案去噪)、4813869/fcbacdc(相邻 chunk 扩展调优)
前四轮把检索管道打磨得差不多了,最后一轮聚焦在上下文质量和答案生成的细节上。
1. 元数据注入升级:结构化上下文前缀
V2 的元数据只是附着在 chunk 上。V5 将其注入到 chunk 内容本身作为前缀:
1 | 原来(chunk 原文): |
这样做的好处:Embedding 模型在向量化时会”看到”这些结构化信息,城市+类别的语义信号直接参与相似度计算,而不是仅靠原始文本。
2. 切分参数精细调优
经过多轮 A/B 测试,最终确定的切分参数:
| 参数 | V1 | V5 | 说明 |
|---|---|---|---|
| chunk_size | 512 | 1024 | 更大窗口保留完整语义 |
| chunk_overlap | 64 | 256 | 更高重叠减少边界截断 |
| 标题切分 | 无 | 三级标题 | Markdown 结构感知 |
| 元数据前缀 | 无 | 结构化注入 | 提升 Embedding 质量 |
3. 答案生成去噪
检索返回 Top-15 个 chunk,但并非所有都对答案有用。新增一层相关性过滤:
- 对每个 chunk 计算与 query 的快速相似度(基于 jieba 关键词命中 + 标题匹配)
- 过滤掉相似度 < 阈值的 chunk(通常是误召回的无关文档)
- 仅将高相关 chunk 送入 LLM 生成答案
这消除了之前”LLM 被低相关文档带跑偏”的问题。
4. 相邻 Chunk 扩展参数调优
V4 引入了相邻 chunk 扩展,但参数没调好——有时扩展太多导致上下文过长。V5 的调整:
- 每侧只扩展 1 个相邻 chunk(而非 2 个)
- 去重后按原始顺序拼接
- 总上下文控制在 3000 token 以内
1 | 📊 RAGAS 指标(V5 最终版): |
35 条用例逐条诊断(最终版):
1 | 🟢 西安回民街有什么好吃的? 命中5/5 (100%) |
94.3% 的用例达到绿区(命中≥60%),只有 2 条在黄区,没有红区用例。两个黄区用例(”古镇推荐””广州三日游”)的共性是:知识库覆盖不足——这不是检索管道的问题,而是数据层需要补充。
七、全旅程数据对比
| 指标 | V1 | V2 | V3 | V4 | V5 | 总提升 |
|---|---|---|---|---|---|---|
| 忠实度 | 73.48% | 84.67% | 90.21% | 87.47% | 98.32% | +24.84% |
| 答案相关性 | 47.40% | 47.08% | 51.35% | 43.86% | 77.79% | +30.39% |
| 检索精度 | 23.33% | 23.33% | 24.42% | 41.59% | 78.60% | +55.27% |
| 检索召回 | 23.78% | 28.78% | 38.10% | 45.24% | 79.48% | +55.70% |
检索召回提升最大(+51.70%),其次是检索精度(+38.27%)。忠实度最终冲到 98.32%,说明 LLM 生成的回答基本都有据可查。答案相关性虽然也涨了 21%,但 68.79% 仍是最短板——这跟知识库覆盖面直接相关。
八、回头再看
回顾整个历程,有几条在 RAG 优化中反复验证有效的经验:
1. 先修路再跑车
V1→V2 阶段没有急于上高级检索策略,而是先把切分、元数据、流水线架构做扎实。没有好的底座,上层优化都是空中楼阁。
2. 召回和精度是跷跷板
V3 大幅提升召回(HyDE + RRF),但精度没跟上。V4 用 MMR + 去噪拉回精度。先放开召回,再收紧精度,比反过来高效得多。
3. 评估体系必须先行
每一轮优化都有 RAGAS 数据支撑。没有评估体系,你根本不知道改一个参数是正向还是负向。评估集从 14 条扩展到 35 条后,才发现很多”看似有效”的优化在更广的用例上其实是负优化。
4. 元数据是 RAG 的隐性资产
从 V2 的简单标注到 V5 的结构化前缀注入,元数据贯穿始终。好的元数据设计能让 Embedding、检索、Reranker 每一层都受益。
5. 不要过早优化
V1 的加权求和虽然粗糙,但它让我们快速验证了整个流水线是否跑通。如果一开始就花大量时间调 RRF 参数,可能连基础 bug 都没发现。
6. 数据质量 > 算法技巧
最终版仍有 2 条用例在黄区(古镇推荐、广州三日游),问题的根源是知识库覆盖不足,而非检索管道。没有好数据,再好的检索算法也不行。
九、技术栈一览
| 层级 | 组件 | 备注 |
|---|---|---|
| 向量数据库 | Milvus Lite 3.0 | 本地 .db 文件,零部署,可平滑迁移 |
| Embedding | BGE-M3 | 1024维,中英文双优,支持稀疏+稠密 |
| 稀疏检索 | BM25 (jieba 分词) | 自建 sentinel 缓存机制 |
| 融合算法 | RRF (k=60,可配置) | 从加权求和演进而来 |
| 查询增强 | HyDE | LLM 生成假设文档桥接语义鸿沟 |
| 重排序 | 硅基流动 Rerank API | 从本地 BGE-Reranker 演进 |
| 多样性 | MMR (λ=0.65) | 避免 Top-K 结果冗余 |
| 文本切分 | Markdown 标题感知 + 递归语义切分 | chunk_size=1024, overlap=256 |
| 元数据 | 结构化前缀注入 | 城市/类别/标题注入 Embedding |
| 评估 | RAGAS + 自定义指标 | keyword_hit_rate, context_hit@5, MRR |
| 编排 | LangGraph Agent | Tool 形式嵌入多智能体系统 |
十、后续方向
- 知识库扩充:古镇、小众目的地等长尾内容的覆盖
- Query Rewriting:在检索前用 LLM 改写用户查询,进一步提升召回
- 多模态 RAG:攻略中的图片(地图、实拍)纳入检索
- 用户反馈闭环:根据用户点赞/踩自动调整检索权重
- A/B 实验框架:支持多套检索策略在线对比
本文所有数据来自 trplanning 项目的 RAGAS 评估报告。
