最近节日
正在加载日历...----
按 RAG 两阶段流程整理 10 种检索增强生成策略:语义分块、命题分块、领域 Embedding、元数据索引、Graph RAG、查询改写、HyDE、混合检索、重排序与 Agentic RAG,并给出新项目默认选型、实现骨架与升级触发条件。

本文按”准备阶段 → 检索阶段 → 范式升级”的顺序整理 10 种 RAG(Retrieval-Augmented Generation,检索增强生成)策略,覆盖分块、嵌入、索引、查询处理、混合检索、重排序与 Agentic RAG。每种策略统一按”问题 / 做法 / 原理 / 收益 / 局限 / 代价 / 结论”划分段落(按需取用),有论文来源的标注出处。
大模型回答不了个人笔记、企业内部资料相关的问题,这些内容不在训练数据里,接入外部知识是刚需。RAG 的做法是先检索相关片段,再交给模型生成回答。
2024 年以来,百万 token 级上下文窗口的模型逐步普及,“把资料全塞进上下文,RAG 已死”的说法随之出现。这个判断在三个约束下站不住:
1. 成本。 输入 token 按量计费。10 万 token 的文档全量输入与 2000 token 的相关片段相比,单次请求的输入成本差 50 倍;每次提问都携带整个文档库时,倍数随库规模继续放大。检索方案的成本只取决于本次召回多少片段,与文档库总量无关。提示缓存(prompt caching)能摊薄重复前缀的开销,但只在文档集固定、复用率高时生效,对每次命中不同片段的场景无效。
2. 延迟。 输入越长,prefill 阶段的计算量越大,模型开始输出越慢,首 token 延迟(TTFT)随之上涨,交互式问答对这一延迟敏感。
3. 准确性。 长上下文不是无损容器。Liu et al. 的《Lost in the Middle》(TACL 2024)实验显示:关键信息位于上下文开头或结尾时模型表现最好,位于中部时准确率明显下降,曲线呈 U 形。塞入的资料越多,关键信息落在中部被淹没的概率越大。
结论:在成本、延迟、准确性的约束下,只检索必要内容仍然优于全部塞进上下文。长上下文抬高的是 RAG 的容错下限(召回不够精准时还有兜底空间),不取消 RAG 本身。问题只剩怎么做好。
RAG 分两大阶段:准备阶段离线执行(建库时一次,或随文档更新执行),检索阶段在每次提问时执行。十种策略在流程上的落位:
各策略解决的问题:
| 环节 | 策略 | 解决的问题 |
|---|---|---|
| 分块 | 策略 1 语义分块、策略 2 命题分块 | 固定长度切分破坏语义 |
| 嵌入 | 策略 3 领域特化 Embedding | 通用模型分不开专业术语 |
| 索引 | 策略 4 元数据索引、策略 5 Graph RAG | 结构化过滤、跨块关系丢失 |
| 查询处理 | 策略 6 查询改写、策略 7 假设文档检索 | 查询与文档表达不一致 |
| 检索 | 策略 8 混合检索、策略 9 重排序 | 纯向量检索区分不了相似关键词 |
| 范式 | 策略 10 Agentic RAG | 单次检索召回不足 |
自适应路由(Adaptive Routing)是调度层,负责在上述策略间动态分流,不计入十种。固定长度分块、通用 Embedding、纯向量检索是各环节的基线选项,作为对照在下文一并说明。
问题:基线是固定长度切分,每 N 个 token 切一刀,配一段 overlap(常见取值为 chunk 512-1024 token、overlap 10%-20%)。实现简单,但切口落在句子中间时会产生碎块,半句话既难以被 embedding 准确表达,也难以被 LLM 利用。
做法:逐句向量化,相邻句子相似度高于阈值则合并进同一块,低于阈值则在句间断开。阈值一般不写死,而是按分布取:相似度落差排在最末 5% 的位置断开(百分位法),或用标准差倍数、四分位距,避免固定阈值在文档长度与题材差异下失效。判定时通常还会把前后各 1-2 句纳入缓冲窗口,降低单句噪声的影响。
局限:一是分块依据只有向量相似度,两句向量距离远、逻辑上强关联(例如结论句与相隔较远的依据句)时仍会拆开,提升的是边界质量,不解决关联内容分散。二是收益未必覆盖成本,Qu et al.《Is Semantic Chunking Worth the Computational Cost?》(arXiv 2410.13070,2024)在多个检索与问答数据集上对比后认为,语义分块相对固定长度切分没有稳定一致的增益,而预处理开销(每句一次向量化,而非每块一次)是确定发生的。
结论:结构化文档优先按标题层级切分,成本最低且边界最可靠;无结构长文本再考虑语义分块,并用自己的评测集验证增益是否真实存在。
针对”关联内容分散”这条局限,Anthropic 在 2024 年 9 月提出上下文增强,作为分块环节的增强手段,不占编号,可与语义分块、命题分块叠加。
做法:入库前用 LLM 为每个块生成一段前缀,说明它在全文中的位置与指代(“本段出自 2023 年 Q2 财报,讨论的是 ACME 公司的营收情况”),把前缀拼在块正文之前再做向量化与关键词索引。
收益:官方公布的数据(top-20 召回失败率,基线 5.7%):仅做上下文嵌入降至 3.7%(相对下降 35%);叠加上下文 BM25 降至 2.9%(49%);再加重排序降至 1.9%(67%)。
代价:与命题分块同源,全库每个块各需一次 LLM 调用。前缀生成只依赖原文,可以用提示缓存把重复读取整篇文档的开销压下来。
出自《Dense X Retrieval: What Retrieval Granularity Should We Use?》(Chen et al., EMNLP 2024)。论文提出了一种新的检索单元:命题(proposition),即原子化、自包含、封装单一事实的自然语言陈述。
做法:先按任意方式粗切,再把每个粗块拆解为命题,每个命题独立索引。论文没有靠提示词硬拆,而是训练了专用的拆解模型(Propositionizer),在 Wikipedia 语料上批量生成命题级索引。
拆分示例,原句:
《民法典》第五百八十六条规定,定金合同自实际交付定金时成立,定金数额不得超过主合同标的额的百分之二十。
拆为两条命题:
- 定金合同自实际交付定金时成立。
- 定金数额不得超过主合同标的额的 20%。
两条命题都补齐了主语,并去掉了”《民法典》第五百八十六条规定”这类需要跨句解析的引导成分。自包含是命题的硬要求,拆完仍带着”该条""前述”这类悬空指代的,等于没拆。
收益:检索粒度从段落细化到事实,命中更精确。论文结论是命题级检索单元在下游问答任务上达到或优于段落级。
局限:命题太短,单条往往不足以支撑生成。通行解法是”小块检索、大块生成”(small-to-big / parent document retrieval):用命题做索引与匹配,命中后按父块 ID 回溯它所属的原始段落,把段落而非命题交给 LLM,索引粒度与生成粒度由此解耦。
代价:预处理引入全量 LLM 调用,成本随文档库规模上涨。
结论:适合准确性要求高、文档库相对稳定的场景(法规、合同、手册),不适合频繁变更的海量语料。
通用 embedding 模型的代表:
dimensions 参数按 Matryoshka 表示学习截断维度,用精度换存储与检索速度问题:通用模型对专业术语的分辨率不足。法律场景的典型例子:“定金”(担保性质,适用定金罚则,违约方可能丧失定金)与”订金”(预付款性质,原则上可退)在法律上是两回事,但两个词在通用模型的向量空间里距离很近,检索结果互相污染。
做法:术语密集的领域选用领域特化模型(如法律检索的 voyage-law-2),或在领域语料上微调通用底座。
代价:这项决策必须在建库前定。向量维度与语义空间由模型绑定,换 embedding 模型意味着全库重新向量化并重建索引,开销与首次建库同级,没有”先上通用模型,不行再换”的余地。
结论:判断标准是术语是否密集、术语间差异是否细微,满足则领域模型优先;通用语料下通用模型够用,省掉选型与维护成本。MTEB 名次不能直接当选型依据:榜单任务分布未必匹配自己的语料,落地前用自己的评测集跑一遍召回对比。
做法:存储选型按规模分两档:
| 规模 | 方案 | 索引形态 |
|---|---|---|
| 小型项目(单机、嵌入式) | SQLite + sqlite-vec | 暴力扫描,万级到十万级块可接受,支持元数据列与分区键 |
| 中大型项目 | PostgreSQL + pgvector | HNSW(查询快、召回高,建索引慢且吃内存)或 IVFFlat(建索引快、占用小,召回依赖 lists 与 probes 调参) |
选型注意:旧教程里常见的 sqlite-vss 已被作者停止维护(基于 Faiss 的 C++ 绑定存在集成问题),2024 年起由纯 C 实现、零外部依赖的 sqlite-vec 接替,新项目直接用 sqlite-vec。
每条记录存三样东西:向量、原文、元数据(日期、来源、类型、版本、父块 ID 等)。“只搜 2024 年之后、来源为 A 系统的合同条款”这类需求,向量相似度做不到,靠元数据过滤实现。
问题:元数据过滤与 ANN 索引存在冲突。近似最近邻索引按向量距离组织数据,不认识 WHERE 条件,过滤条件命中率低时,两种执行顺序都会出问题:
pgvector 0.8.0 引入迭代索引扫描(iterative index scan)缓解后过滤的问题:结果不足时自动继续遍历索引,由 hnsw.iterative_scan / ivfflat.iterative_scan 与 hnsw.max_scan_tuples 等参数控制。上线前要用真实的过滤条件分布压一遍召回率,不要只测无过滤的场景。
局限:向量库以块为单位独立存储,块之间的引用关系丢失。合同第十二条引用第五条时,普通向量库不保留这条跨块引用,检索命中第十二条也无法自动带上第五条。
做法:在块之外,用 LLM 从文档中抽取实体(条款、人物、概念)与关系(引用、因果、从属),存成图结构,节点是实体,边是关系。检索时先定位命中实体,再沿边扩展关联节点,召回一个相关上下文簇。跨块引用关系在图里天然保留,查第十二条时能顺着边走到第五条。
收益:图的价值不止局部检索。微软的 GraphRAG(Edge et al., 2024,《From Local to Global》)在图上做社区检测(Leiden 算法),对聚出的社区逐层生成摘要,据此回答全局性问题(“这批文档的核心主题是什么”)。这类问题向量检索答不了:它只能召回与查询局部相似的块,而全局主题不存在于任何单个块里。GraphRAG 因此区分两种检索模式:局部搜索从命中实体出发扩展邻域,全局搜索走社区摘要做 map-reduce 式汇总。
代价:实体关系抽取需要 LLM 全量处理文档库,开销与命题分块同级或更高(每个块通常不止一次调用);抽取质量受限于 LLM,错误的实体消歧与关系会污染检索结果;文档更新会触发局部乃至全图的社区重算,维护负担持续存在。
结论:只在关系密集、跨块引用是核心查询模式的文档(合同、规范、结构化知识库)上采用,先在单个文档集上试点,用评测集量出与”向量 + 元数据”基线的召回差距,确认收益后再全量铺开。若只需要全局摘要能力、不要求完整图谱,可评估微软后续的 LazyGraphRAG,它把大部分 LLM 开销从建库期推迟到查询期,官方给出的索引成本约为完整 GraphRAG 的 0.1%。
Graph RAG 属于高收益高风险策略。默认场景用策略 4 的向量库 + 元数据;确认跨块关系查询或全局摘要是刚需后,再引入图结构。
问题:用户原始 query 往往口语化、缺主语、一词多义,或是多个子问题拼成的复合问题。
做法:改写有三个方向:
代价:每次提问增加一轮 LLM 调用,延迟与成本同步上涨;多查询扩展还会把检索次数放大 3-5 倍。时延敏感的场景只保留多轮对话的指代消解,其余预算留给重排序。
出自《Precise Zero-Shot Dense Retrieval without Relevance Labels》(Gao et al., ACL 2023)。
做法:先让 LLM 针对问题生成一篇假设答案,不要求事实正确,只要像一篇真正的回答;再用假设答案的向量去检索,替代原始 query 的向量。论文的具体做法是生成多篇假设文档,把它们的向量与原 query 向量一起取平均,用平均向量检索,摊薄单篇幻觉的影响。
原理:query 与目标文档之间存在表达鸿沟:问题短、口语化,文档长、书面化,两者在向量空间中的分布本就不重合。假设答案与真实文档同为”文档”,分布更接近,检索更准。论文结论:HyDE 的零样本检索效果优于当时的无监督稠密检索器 Contriever。
局限:冷门领域 LLM 缺乏背景知识,生成的假设文档偏离事实,会把检索方向带偏:召回内容与问题完全不相关,且从最终答案表面不易察觉(模型会基于错误上下文流畅作答)。应对:冷门或专有领域慎用;启用时与混合检索的关键词通道并行,保留原始 query 的检索结果做兜底;在评测集上分别记录开启与关闭 HyDE 的召回率,按语料实测决定。
问题:纯向量检索语义匹配强、精确区分弱。“1 型糖尿病”与”2 型糖尿病”在向量空间距离很近,语义检索可能把两者混着返回,对医学检索来说就是事实性错误。
做法:同时跑两路:
工程上有两条路:主流存储都已提供混合检索能力(pgvector 配合 PostgreSQL 全文检索、Elasticsearch / OpenSearch、Qdrant、Milvus);也可以用 bge-m3 这类单模型同时产出稠密与稀疏表示,省掉维护两套索引的负担。
原理:两路的分数不能直接相加:余弦相似度落在 [-1, 1],BM25 无上界,量纲不同。加权融合要先做分数归一化,而归一化对分数分布敏感、跨查询不稳定。RRF(Reciprocal Rank Fusion)绕开了这个问题,它只用排名不用分数:
是各路检索器的集合, 是文档 在第 路结果中的排名, 是平滑常数(原论文取 60,用于抑制头部排名之间的权重落差)。无需调参、无需归一化,这是 RRF 成为默认融合方案的原因。确实需要给不同通道加权时,再换加权融合并自行处理归一化。
结论:收益最大的语料是专有名词、编号、术语密集的类型(法规条文号、零件型号、药品名)。
做法:初筛与精排用不同架构的模型:
| 架构 | 编码方式 | 速度 | 精度 | 职责 |
|---|---|---|---|---|
| 双编码器(bi-encoder) | query 与文档分别编码,文档向量可离线预计算 | 快,支持 ANN 索引 | 一般 | 初筛:从全库召回 top 100 |
| 迟交互(late interaction,ColBERT 类) | 文档按 token 预计算多向量,查询时做 token 级匹配 | 中,需额外存储 | 较高 | 初筛或中间层,也可直接充当重排 |
| 交叉编码器(cross-encoder) | query 与候选拼成一对,联合编码打分 | 慢,无法预计算 | 高 | 精排:把 top 100 排成 top 5 |
交叉编码器对每一对(query, 候选)都要完整前向计算,全库跑一遍成本不可接受。工程惯例是”先召后排”:bi-encoder 或混合检索做初筛,cross-encoder 只对几十条候选精排,把费用与延迟锁在候选层。ColBERT(Khattab & Zaharia, SIGIR 2020)代表的迟交互是两者之间的折中:文档侧仍可预计算,匹配保留 token 粒度,代价是每篇文档存一组向量、存储明显放大,bge-m3 的多向量模式属于这一路。
模型选择:商用 API 的代表是 Cohere Rerank;自托管常用 BGE-Reranker 或 Jina Reranker 系列。另有让 LLM 对候选列表整体排序的做法(listwise,如 RankGPT),精度上限更高,但延迟与成本随候选数量线性上涨,通常只用于离线评测或候选极少的场景。
结论:真正影响效果的参数是两个数:初筛候选数(决定召回上限,取小了重排无从挑选)与重排后送入 LLM 的条数。后者不是越多越好:受 Lost in the Middle 的位置效应影响,塞 20 条往往不如精准的 5 条。这两个数要在评测集上一起调。
单次 pipeline 隐含的假设是一次检索就能召回全部所需信息,而实际的查资料过程往往不是这样:查到一半发现缺一块,换个关键词再查。
做法:把单次 pipeline 换成循环,由 agent 决定每一轮做什么:
Agent 的工具箱不限于语义检索:关键词检索、执行代码、读取原文、查元数据都可以作为工具挂载,多轮调用直至信息足以支撑回答。工具描述的质量直接决定调用准确率,写清楚每个工具的适用场景与参数含义,比增加工具数量更有效。
收益:召回侧,多轮探索能补上单次检索遗漏的信息;工程侧,传统 pipeline 需要硬编码策略分支(什么时候改写、什么时候拆问题),Agentic 模式把这些决策交给 agent 判断,主流程只剩一个循环。
局限:最大的风险是循环不收敛,agent 反复用近似的 query 检索同一批内容,token 消耗与延迟随轮数线性上涨,极端情况下打满上下文窗口后失败,成本无法预测、做不了容量规划。防护要做硬:
结论:简单问题跑 agent 循环不经济,查一个 API 用法用不着探索三轮。
做法:在入口处用轻量分类器评估问题难度,简单问题走传统 pipeline(混合检索 + 重排序),复杂问题升级为 Agentic RAG。
论文出处:Adaptive-RAG(Jeong et al., NAACL 2024)。原始设计是三档路由:简单问题不检索、由 LLM 直接回答;中等复杂度单步检索;复杂问题多步迭代检索。分类器不依赖人工标注,而是用各档位的实际问答结果做弱监督训练(哪一档最先答对,样本就归到那一档)。工程上常简化为两档:简单走 pipeline,复杂走 agent 循环。
局限:误判是主要风险,尤其是把复杂问题判成简单:复杂问题走单次检索,召回不足直接答错,且路由决策发生在检索之前,后续环节没有纠正机会。对策:分类阈值向”判复杂”一侧倾斜(误判为复杂只是多花成本,误判为简单会答错);生成后加一道自检,答案置信度低或引用不足时回退到 agent 路径。
| # | 策略 | 环节 | 解决的问题 | 主要代价 |
|---|---|---|---|---|
| 1 | 语义分块 | 准备 | 固定切分破坏语义 | 逐句向量化,且增益不稳定 |
| 2 | 命题分块 | 准备 | 检索粒度粗 | LLM 全量处理,成本高 |
| 3 | 领域特化 Embedding | 准备 | 专业术语分不开 | 选型与维护成本,换模型需全库重建 |
| 4 | 元数据索引 | 准备 | 无法结构化过滤 | 过滤与 ANN 索引冲突,跨块关系丢失 |
| 5 | Graph RAG | 准备 | 跨块引用与全局主题问题 | 构建与更新昂贵 |
| 6 | 查询改写 | 检索 | query 表达质量差 | 多一轮 LLM 调用,检索次数放大 |
| 7 | 假设文档检索 | 检索 | query 与文档向量分布不一致 | 冷门领域可能带偏检索 |
| 8 | 混合检索 | 检索 | 相似关键词分不清 | 维护两套索引,或换单模型方案 |
| 9 | 重排序 | 检索 | 初筛排序粗糙 | 交叉编码器计算贵 |
| 10 | Agentic RAG | 范式 | 单次召回不足 | 多轮调用,成本与延迟不可预测 |
| — | 上下文增强分块 | 准备(增强) | 块脱离全文后指代不明 | 每块一次 LLM 调用 |
| — | 自适应路由 | 调度 | 简单问题被过度处理 | 分类器存在误判 |
新项目默认配置,按顺序落地:
第 4 步是整套里最值得先做对的部分。骨架如下,省略了检索器与重排模型的具体实现:
def rrf_fuse(rankings: list[list[str]], k: int = 60) -> list[str]: """按 RRF 融合多路检索结果,输入为各路的有序 chunk_id 列表。""" scores: dict[str, float] = {} for ranking in rankings: for rank, chunk_id in enumerate(ranking, start=1): scores[chunk_id] = scores.get(chunk_id, 0.0) + 1.0 / (k + rank) return sorted(scores, key=lambda cid: scores[cid], reverse=True)
def retrieve(query: str, *, filters: dict, top_k: int = 5) -> list[str]: # 1. 两路初筛,候选数取远大于 top_k 的值,给重排留出挑选空间 dense_hits = dense_search(query, filters=filters, limit=100) sparse_hits = bm25_search(query, filters=filters, limit=100)
# 2. RRF 融合,只用排名,不做分数归一化 candidates = rrf_fuse([dense_hits, sparse_hits])[:60]
# 3. 交叉编码器精排,计算成本锁在候选层 reranked = cross_encoder_rerank(query, candidates)[:top_k]
# 4. 命中的是命题或小块时,回溯父段落再交给 LLM(small-to-big) return [load_parent_text(cid) for cid in reranked]三个数字需要在评测集上一起调:初筛 limit(决定召回上限)、送进重排的候选数(决定重排成本)、最终 top_k(受位置效应约束)。
升级触发条件:
成本与延迟:
准确性与上下文稀释:
评测要分层,检索层与生成层分开测量,否则出问题定位不到环节:
| 层 | 指标 | 说明 |
|---|---|---|
| 检索 | Recall@k | 期望文档是否出现在 top k,衡量召回上限 |
| 检索 | nDCG@k / MRR | 期望文档排得够不够前,衡量排序质量与重排收益 |
| 生成 | 忠实度(faithfulness) | 答案是否只依据召回内容,用于定位幻觉 |
| 生成 | 答案相关性 | 答案是否回答了问题,与检索质量解耦 |
固定一批问题与期望召回的文档做评测集,每次调整策略跑一遍。规模不必大,50-100 条覆盖典型查询类型即可起效;关键是每次只动一个变量,否则无法归因。检索指标能用脚本自动算,生成指标可用 RAGAS 一类框架做 LLM-as-judge,但要抽样人工校准判分标准。没有评测集的策略调优是在赌方向。
维护与迭代:
RAG 解决的是”如何召回信息”这一个问题。十种策略分布在准备与检索两个阶段,各管一段,没有通吃的那一种。
其中只有两件事值得无条件先做:混合检索加重排序(收益稳定、成本锁在候选层),以及在动手调策略之前先有评测集。其余都是带触发条件的加法:语义分块的增益争议、HyDE 在冷门领域的反作用、Graph RAG 的维护成本,都得靠自己的语料实测才知道,不要因为某个策略看起来更先进就默认启用。
检索只是 agent 的地基,往上还有 memory 设计、行为评估、数据闭环,那是另一套问题。