3164 字
约 10 分钟
15
S20-A05 · RAG 架构取舍:什么时候该换检索方案

"prompt 改到第七版还是不准确",是 RAG 项目里最典型的岔路。此时继续调提示词,等于在最后一层修前面四层的错。RAG 的误差沿着切分 → 向量化 → 检索 → 拼装 → 生成逐层放大,而排错的顺序必须反过来:先证明文档没被切坏,再证明召回里有正确片段,最后才谈生成。跳过前两步换检索方案,通常只是把问题挪了个位置。


1. 五层诊断:先定位,再换方案

L1 切分   同一段答案是否完整落在单个 chunk 内?     验证:抽 20 个问题,肉眼对齐原文
L2 向量化 该 chunk 是否与其他 chunk 明显可分?       验证:算问题与目标 chunk 的相似度排名
L3 检索   目标 chunk 是否进了 Top-K?                验证:只看召回,不看生成
L4 拼装   进入 prompt 的片段是否被截断/去重/排序错? 验证:打印最终 prompt 原文
L5 生成   有正确片段时模型是否用得对?               验证:把答案片段直接贴进 prompt 重跑
层 失效信号 修法 换检索方案能解决吗
切分 答案跨 chunk 断裂、表格被切散 改切分粒度/按结构切 不能
向量化 目标片段排名在 50 名开外 换 embedding、加关键词路 部分能
检索 Top-K 内没有、过滤条件写错 混合检索、rerank、改过滤 能
拼装 片段被 token 上限截掉 重排、压缩、去重 不能
生成 片段正确但答案跑偏 改指令、约束引用 不能

硬规矩:打印出真实 prompt 之前,不改任何检索参数。多数"检索不准"是拼装阶段把最相关片段截掉了。

2. 两种检索方式各自的失效面

查询类型 纯向量 纯关键词
精确标识(订单号、错误码、SKU) 差,语义近但字面不对 强
长尾实体(生僻人名、内部代号) 差,训练分布里没有 中,需分词正确
同义表达("退款" vs "退回款项") 强 差
跨语言 强(多语模型) 差
否定语义("不含酒精的") 弱,常召回相反含义 中,取决于分词与停用词
短查询(2–3 字) 弱,向量信息不足 中

收益取决于查询分布里精确项的比例:全是自然语言长问句时,加 BM25 只增加复杂度;编号、型号、代码密集的库,不加就是系统性漏召回。

3. 融合方式与它的成本

# RRF:只用排名,不用分数,避免两路分数量纲不可比
def rrf(rank_lists, k=60):
    score = {}
    for ranks in rank_lists:
        for pos, doc_id in enumerate(ranks, start=1):
            score[doc_id] = score.get(doc_id, 0.0) + 1.0 / (k + pos)
    return sorted(score.items(), key=lambda x: -x[1])
融合方式 优点 代价
分数归一后加权 可调权重、能表达偏好 两路分数量纲不同,min-max 归一受异常值影响
RRF(排名倒数) 无需标定、稳健 丢掉分数信息,头部差异被抹平
学习式融合 上限最高 需要标注集与持续训练

RRF 的 k=60 是起点不是最优值,它决定头部排名有多重要;抄走常量从不验证,是最常见的假配置。

4. rerank:只在"召回有、排序错"时有用

现状 rerank 的作用 判断方法
目标片段在 Top-50 内,但不在 Top-5 收益最大 算 Top-50 命中率
目标片段根本不在 Top-50 无用,先修召回 同样算命中率
Top-5 已命中 无用,纯增延迟 抽样看现状
长文档、语义细微差别 收益明显 对比 rerank 前后 NDCG

rerank 是排序器,不是召回器。上线前先量 Top-50 命中率:它低于目标时,rerank 只是把错误排得更整齐,代价是多一次模型前向的延迟。

5. 分块策略由内容类型决定

内容类型 推荐切法 常见错误
FAQ / 问答对 一问一块,问题作元数据 按固定字数切,把问答拆开
长文档/规范 按标题层级切,保留父标题路径 用固定窗口,章节语义断裂
表格 整表一块 + 行级摘要 按字符切,表头丢失
代码 按函数/类切,带文件路径 按行数切,函数体被劈开
会议记录 按议题切,带时间与发言人 全文一块,检索粒度太粗
chunk = {
    "text": text,
    "title_path": ["退款政策", "跨境订单"],   # 父级路径:拼装时可补上下文
    "type": "faq",
    "source_id": "doc-1042",
    "updated_at": "2026-03-01",
}

块大小是一对反向成本:太小语义不完整,太大向量被稀释。判据是答案的平均长度——块应略大于一个典型答案,而不是等于段落机械长度。

6. 多路召回与查询改写

手段 解决的问题 引入的风险
多路召回(向量 + BM25 + 结构化) 不同查询类型互补 融合权重难调、成本上升
查询改写(扩展同义词) 用词不一致 改写跑偏,召回无关内容
HyDE(先生成假答案再检索) 短查询语义不足 假答案带偏方向,延迟翻倍
子问题拆解 复合问题 拆错则全错,合并易漏

改写与 HyDE 都属于"用一个模型输出喂给检索",它们的失效模式和生成阶段一样:看不出自己错。必须留一条"不改写"基线,否则分不清收益与评估噪声。

7. 图结构检索的代价

维度 向量检索 图结构检索
构建 一次性 embedding 抽取实体关系 + 建图,需人工校验
更新 增量 upsert 实体消歧、边失效、社区重算
查询 一次近邻搜索 多跳遍历,复杂度随跳数上升
擅长 语义相似、模糊匹配 多跳关系、全局归纳类问题

图的判据很窄:问题需跨文档做关系推理,且实体边界清晰可抽。"文档很多"不构成理由——硬抽无结构文本得到噪声图,查询还更贵。

8. 权限过滤必须在检索之前

# 正确:过滤条件下推到检索,候选集本身就不含越权内容
results = index.search(query_vec, top_k=20, filter={"tenant_id": tid, "acl": {"$in": roles}})

# 错误:先检索再过滤,Top-K 被无关内容占满
results = [d for d in index.search(query_vec, top_k=20) if can_read(user, d)]
位置 后果
检索前置(元数据过滤) 正确,且不浪费召回名额
生成后过滤 越权内容已进 prompt,等于已泄露
检索后过滤 名义安全,实际 Top-K 被挤占,召回质量下降

这是硬约束:内容一旦进过上下文,就无法主张没被看到。多租户索引必须让租户维度成为过滤字段,而不是事后筛。

9. 成本结构

项 触发时机 量级感受
索引构建 首次全量、模型升级、切分变更 一次性,但与文档量成正比
向量存储 常驻 与向量维度、副本数成正比
每次查询 每请求 嵌入 + 检索 + (可选)rerank + 生成
重排与改写 每请求,可关 直接加延迟与调用次数

成本杠杆不在向量库选型,而在每次查询调用几次模型:rerank 与改写都开,单次查询可能从一次变三次,高频场景里这就是预算表第一行。

10. 评估:检索与答案分开评

检索侧   Recall@K / MRR / NDCG    需要"哪个 chunk 是正确的"标注
答案侧   正确性 / 引用一致性 / 拒答   需要参考答案或无答案样本
样本 占比 作用
主路径问题 多数 守住基本盘
长尾实体 一部分 暴露关键词路缺失
无答案问题 必须有 检验是否会编
跨文档多跳 少量 检验图/多路召回是否值得

没有无答案样本,系统就永远在"必须给个答案"。拒答率异常低通常不是能力强,是评测集缺了这类。

11. 增量更新与双索引切换

index:
  alias_read: rag-live        # 查询永远读别名,不读具体索引
  build: rag-build-20260301   # 重建写入新索引
  swap:                         # 校验通过后原子切换
    - step: verify_count        # 文档数与预期一致
    - step: verify_sample       # 抽样比对旧索引命中
    - step: alias_swap          # 原子切换读别名
    - step: keep_old_7d         # 保留旧索引以便回滚
变更类型 重建范围
新增/修改文档 只重算受影响 chunk
切分策略变更 全量重建
embedding 模型更换 全量重建,且必须双索引并行
元数据/权限变更 只更新 payload,不重算向量

最常见工程债是"改切分直接原地覆盖":索引不可回滚,切分实验就得赌线上效果。别名 + 双索引成本很小,换来的是回滚能力。

12. 排错表

现象 常见原因 处理
答案缺关键数字 表格被切散、表头丢失 整表一块,行级摘要
明明有文档却答"未提及" 目标 chunk 未进 Top-K 算 Recall@50,补 BM25 路
引用了不存在的段落 生成层编造 强制引用 ID,拼装时校验
换了 embedding 后效果下降 旧索引未重建,向量空间混用 全量重建并双索引切换
权限不同的用户看到同一答案 过滤在生成后 过滤前置到检索
rerank 上线后延迟翻倍、准确率不变 Top-5 本已命中 抽样验证后再决定是否保留

13. 边界

情形 判断 理由
知识量很小(几十条) 全量塞进上下文 检索的复杂度换不来收益
查询模式单一(固定几类) 规则路由 + 少量检索 用不上通用检索栈
强实时(秒级数据变化) 检索 + 实时接口混用 索引永远追不上写入
内容高频变动 缩短链路、接受短窗口滞后 重建成本会吃掉收益
答案需精确计算 走工具/查询,不走检索 检索给的是文本,不是结果

RAG 擅长"找到说法",不擅长"算出数值":聚合计算类问答塞进检索链路,换任何方案都不会准。

14. 自测

  1. 你能打印出线上真实进入 prompt 的完整片段吗?截断发生在哪一步?
  2. 目标 chunk 在 Top-50 里的命中率是多少?这个数决定 rerank 是否值得。
  3. 你的评估集里有多少"知识库没有答案"的样本?
  4. 权限过滤写在检索的哪个参数上,还是写在结果列表推导式里?
  5. 换 embedding 模型时,旧索引怎么处理,回滚路径是什么?
  6. 你的查询里精确标识(编号、错误码)占多大比例,关键词路缺席的代价是多少?
S20-A05 · RAG 架构取舍:什么时候该换检索方案
https://www.clxhxhhr.top/posts/6098/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。