返回文章列表

RAG 索引优化:不是把向量存进去,而是给知识准备多条能被找到的路

从父子块、多表示索引到结构化入口,系统梳理 RAG 索引优化如何提升召回与上下文质量。

RAG 索引优化:不是把向量存进去,而是给知识准备多条能被找到的路

上一篇讲检索前处理时,我们已经看到一个问题:

用户问出来的话,往往和资料里写出来的话不是同一种表达。

所以我们会做查询改写、子问题拆分、HyDE、metadata filter、查询路由,让“用户的问题”尽量变成更适合检索系统理解的形状。

但这里还有另一半问题:

就算查询变好了,如果索引本身只有一条很单薄的路,系统还是可能找不到答案。

如果只看基础版本,索引就是一条很直的链路:

文档
-> 分块
-> embedding
-> 存进向量数据库

这当然能跑通最小版本的 RAG。

但到了真实业务里,你很快会遇到一些更麻烦的问题:

  • 答案在文档里,但 chunk 太碎,召回后上下文不完整。
  • 用户问的是一句人话,原文写的是一段制度语言,语义匹配不稳。
  • 文档有标题、表格、章节、版本、生效日期,但普通向量索引没有充分利用这些结构。
  • 一篇长文里有很多层级,系统直接在所有 chunk 里找,既慢又容易被局部片段带偏。
  • 检索结果里有答案,但排在第 8 位,最终没有进入模型上下文。

这时问题已经不是“有没有向量库”,而是:

知识被放进系统以后,有没有被组织成容易被找到、容易被解释、容易被组合的样子。

这就是索引优化要解决的事。

还是用公司制度问答助手里的那个问题来看。用户会问:

我去上海出差,高铁二等座和酒店每天最多能报多少?

这个问题看起来只是一次普通查询,但它会考验索引的很多能力:

  • 能不能找到“上海”对应的城市级别?
  • 能不能找到“高铁二等座”的交通标准?
  • 能不能找到“酒店每天上限”的住宿标准?
  • 能不能区分普通员工、主管、高管的不同额度?
  • 能不能保留制度版本和生效日期?
  • 能不能把表格行、章节说明和特殊条款一起带回来?

如果索引只是把所有文本 chunk 平铺进向量库,系统可能能回答一些简单问题,但在这种组合型问题面前就会变得不稳定。

一、故事要从“普通向量索引不够用”开始

最基础的 RAG 索引通常长这样:

原始文档
-> 文档解析
-> 文本分块
-> chunk embedding
-> 向量数据库

查询时:

用户问题
-> query embedding
-> 向量相似度检索
-> top_k chunks
-> 放入模型上下文生成答案

这套流程能跑通最小闭环,也适合 demo 和简单知识库。

但它有一个默认假设:

用户问题和答案 chunk 在语义空间里足够接近。

真实情况经常不是这样。

比如制度原文写的是:

一类城市住宿费标准:普通员工不超过 500 元/人/晚。

用户问的是:

我去上海出差,酒店每天最多能报多少?

这里面至少有三层转换:

上海 -> 一类城市
酒店每天最多能报多少 -> 住宿费标准
我 -> 需要识别人员级别或默认普通员工

如果只靠原文 chunk 的向量相似度,系统可能召回“住宿费标准”,也可能召回一堆“出差申请流程”“酒店发票要求”“一类城市名单”之类的片段。

答案也许在候选里,但不一定排在前面。

更麻烦的是,答案往往不在一个 chunk 里。

这个问题可能需要同时查:

城市级别表:上海属于一类城市
交通标准表:高铁二等座可报销
住宿标准表:一类城市普通员工 500 元/晚
制度正文:报销需要事前审批和合规发票

普通向量索引把这些内容当成一堆平铺的文本片段。它缺少一种能力:

知道哪些片段是同一份证据链上的不同部分。

所以索引优化的第一步,不是急着换更贵的模型,而是重新审视索引到底承担什么职责。

索引不是“把文本存起来”。

索引更像一张知识地图:

用户可能怎么问
-> 系统应该先去哪一层找
-> 找到小片段后要不要回填大上下文
-> 原文、摘要、问题形式、表格、元数据之间如何关联
-> 哪些候选应该优先进入模型上下文

这张地图画得越好,检索越稳。

二、索引优化到底在优化什么

索引优化 手绘图 1:给知识准备多条入口

在 RAG 里,索引优化主要不是优化“存储”本身,而是在优化三件事:

召回率:答案证据能不能被找回来
精确率:找回来的内容噪声多不多
上下文质量:交给模型的内容是否完整、可信、可回答

这三个目标经常互相拉扯。

如果你把 chunk 切得很小,检索会更精确,但上下文容易断。

如果你把 chunk 切得很大,上下文更完整,但向量语义会被稀释,召回会变粗。

如果 top_k 设得很大,答案更可能被召回,但噪声也会变多,生成阶段更容易被干扰。

如果只召回最相似的几个片段,回答会更干净,但复杂问题可能缺证据。

所以索引优化不是一个单点技巧,而是一组围绕“证据如何被找到”的设计。

可以把它理解成这条链:

基础 chunk 索引
-> 父子块索引
-> 上下文扩展
-> 层次索引
-> 多表示索引
-> 结构化索引
-> 混合检索入口
-> 选修:向量数据库索引参数
-> 重排与评估闭环

后面的每一步,都是在修补前一步留下的问题。

三、父子块索引:小块负责找,大块负责答

索引优化 手绘图 2:父子块索引的小块找大块答

上一篇分块技术里提过一个矛盾:

检索希望 chunk 小一点
生成希望上下文大一点

检索阶段需要精准命中。

用户问“上海酒店报销上限”,最容易匹配的可能是一小段:

一类城市住宿费标准:普通员工不超过 500 元/人/晚。

但生成阶段只看到这句话还不够。模型可能还需要知道:

  • 一类城市包括哪些城市?
  • 这条标准适用于什么人员级别?
  • 是否需要审批?
  • 是否有生效日期?
  • 是否有特殊例外?

于是父子块索引出现了。

它的思路很简单:

子块:切小一点,用来做精准检索
父块:保留大一点,用来补充生成上下文

在公司制度问答助手里,可以这样组织:

父块:差旅制度 / 住宿标准章节
  子块 1:城市级别说明
  子块 2:一类城市住宿标准
  子块 3:二类城市住宿标准
  子块 4:特殊审批规则

检索时先用子块匹配用户问题:

用户问:上海酒店每天最多能报多少?
-> 命中子块:一类城市住宿标准

然后系统不只把这个子块交给模型,而是回填它所属的父块:

子块命中
-> 找到父块
-> 把住宿标准章节一起放入上下文

这样做的好处是:

召回时足够精确
生成时足够完整

父子块索引解决的是“切小了会丢上下文”的问题。

但它还有一个边界。

它假设答案仍然在同一个父块附近。如果问题需要跨章节、跨表格、跨文档组合,单靠父子块还不够。

比如用户问:

我去上海出差,高铁二等座和酒店每天最多能报多少?

交通标准和住宿标准可能不在同一个父块里。

这就需要下一步:上下文扩展。

四、上下文扩展:命中一个点,带回它周围的证据

上下文扩展解决的是另一个常见问题:

检索命中了一个正确片段,但它旁边的关键信息没有一起回来。

比如系统命中了这一句:

普通员工不超过 500 元/人/晚。

但这句话前面一段写的是:

一类城市包括北京、上海、广州、深圳。

后面一段写的是:

超出标准部分需由部门负责人提前审批。

如果只看命中的那一句,模型能回答额度,但可能解释不清适用城市和审批边界。

上下文扩展的做法是:命中一个 chunk 后,把它的邻居也带回来。

常见方式有几种:

前后窗口扩展:带上命中 chunk 前后的 N 个 chunk
章节扩展:带上同一标题下的所有相关片段
父块扩展:带上命中子块所属的父块
引用扩展:带上表格说明、脚注、附件、版本信息

在公司制度问答助手里,它可能长这样:

命中:一类城市住宿标准
扩展:
- 城市级别说明
- 一类城市名单
- 住宿标准表头
- 特殊审批规则
- 生效日期

这样模型拿到的就不是孤零零的一行数字,而是一组可以回答问题的证据。

但上下文扩展也不能无限扩。

扩得太少,证据不完整。

扩得太多,噪声又回来了。

所以这里有一个实用原则:

扩展的目标不是“多给模型一点”,而是“补齐回答这个问题所需的最小证据链”。

如果每次命中一个 chunk 都把整章、整篇、整份制度塞进去,那就退回到“不分块”的老问题了。

上下文扩展解决的是“局部证据不完整”的问题。

但如果资料本身很长,比如一本几百页的手册,直接在所有 chunk 里平铺检索依然会很粗。

这就引出层次索引。

五、层次索引:先找章节,再找片段

人查长文档时,通常不会从每一句话开始找。

你会先看目录:

员工手册
-> 第 4 章:考勤与请假
-> 第 7 章:差旅与报销
-> 第 9 章:绩效与晋升

如果问题是“上海出差酒店报销”,你会先进入“差旅与报销”,再找“住宿标准”。

层次索引就是把这种查找路径放进 RAG。

它会把文档组织成多个层级:

文档级摘要
-> 章节级摘要
-> 小节级摘要
-> 段落或 chunk
-> 原文证据

查询时,系统可以先粗后细:

用户问题
-> 先匹配文档或章节摘要
-> 定位到可能相关的章节
-> 再在章节内部检索具体 chunk

对于长文档,这样做有两个好处。

第一个好处是减少搜索空间。

系统不需要在所有制度、所有段落里盲搜,而是先定位大范围。

第二个好处是保留文档结构。

模型不仅知道“这句话相似”,还知道它属于哪份制度、哪个章节、哪个小节。

在公司制度问答助手里,层次索引可能是:

公司制度库
  员工手册
    请假制度
    考勤制度
  报销制度
    差旅申请
    交通标准
    住宿标准
    餐补标准
  财务制度
    发票要求
    审批流程

用户问“上海出差酒店每天最多能报多少”,系统可以先定位:

报销制度 -> 住宿标准

再进入具体表格或条款。

层次索引特别适合:

  • 长文档
  • 课程教材
  • 企业制度
  • 法律合同
  • 技术手册
  • 多章节报告

但它也有风险。

如果上层摘要写得太粗或写错了,查询可能一开始就被路由到错误章节。

比如“差旅费用”摘要里没有提到“住宿”,系统可能错过住宿标准。

所以层次索引通常要配合两个机制:

摘要质量检查:确保上层摘要能代表下层内容
横向补召回:不要只相信一条路,必要时仍然从全局 chunk 里补一些候选

层次索引解决的是“长文档平铺检索太粗”的问题。

但它还没有解决“用户问法和原文写法差异太大”的问题。

这就需要多表示索引。

六、多表示索引:同一份知识,准备多个入口

很多 RAG 召回失败,并不是因为答案不存在,而是因为用户的问法和原文的写法对不上。

原文可能写:

住宿费标准按城市类别和员工职级执行。

用户可能问:

上海住酒店一天能报多少钱?

再比如原文写:

一类城市普通员工住宿上限为 500 元/人/晚。

用户可能问:

普通员工去一线城市出差住酒店,公司最多给报多少?

这些表达都在问同一件事,但字面差异很大。

多表示索引的核心思路是:

不要只把原文作为唯一入口,而要为同一份知识建立多个可检索表示。

同一条制度,可以建立这些表示:

原文 chunk:
一类城市住宿费标准:普通员工不超过 500 元/人/晚。

摘要表示:
普通员工在一类城市出差时,住宿费上限为每晚 500 元。

问题表示:
普通员工去上海出差酒店能报销多少?
一类城市住宿费标准是多少?
酒店报销上限怎么按城市分类?

关键词表示:
上海,一类城市,住宿费,酒店,报销上限,普通员工,500 元

元数据表示:
制度=差旅报销制度
章节=住宿标准
城市级别=一类城市
人员级别=普通员工
费用类型=住宿
生效日期=2025-01-01

用户不管从哪个角度问,都更容易撞上其中一个入口。

这就是多表示索引的价值:

原文负责保真
摘要负责概括
问题形式负责贴近用户问法
关键词负责精确术语
元数据负责过滤和约束

它特别适合这些场景:

  • 原文很正式,用户问法很口语。
  • 原文是表格,用户问的是自然语言。
  • 原文里有大量缩写、编号、术语、产品名。
  • 同一内容可能被用户从多个角度提问。
  • 需要同时兼顾语义相似和关键词精确匹配。

在实现上,多表示索引通常不是简单复制多份原文,而是让多个表示都指回同一个原始证据。

可以理解成:

多个入口
-> 指向同一条证据
-> 最终给模型看的仍然是可信原文

这样可以避免一个问题:

摘要或问题表示只是帮助召回,不能替代原文成为最终事实依据。

比如系统可以用“普通员工去上海酒店能报多少”这个问题表示召回,但最终回答时,应该引用原制度里的住宿标准、城市级别和生效日期。

多表示索引解决的是“用户问法和资料写法不一致”的问题。

但如果资料里有大量表格、字段、关系,只做文本表示还不够。

这就引出结构化索引。

七、结构化索引:把表格、字段和关系也纳入检索

RAG 里有一类资料特别容易被普通文本索引处理坏:结构化资料。

比如差旅标准 CSV:

城市级别, 城市, 住宿上限, 交通标准, 人员级别
一类, 上海, 500, 高铁二等座, 普通员工
一类, 北京, 500, 高铁二等座, 普通员工
二类, 成都, 400, 高铁二等座, 普通员工

如果把这张表粗暴转成一大段文本,再做 embedding,很多结构信息会变模糊:

  • 哪个数字属于哪个字段?
  • 上海和 500 是什么关系?
  • 500 是住宿上限还是交通补贴?
  • 普通员工和高管是否有不同标准?
  • 表头是否被保留下来了?

结构化索引的目标是:

让系统不仅能检索文本,还能理解字段、行列、实体和关系。

对于表格,可以做这些处理:

按行索引:
上海 一类城市 普通员工 住宿上限 500 元 高铁二等座

按字段索引:
城市=上海
城市级别=一类
费用类型=住宿
人员级别=普通员工
上限=500

按表格摘要索引:
这张表记录不同城市级别、员工级别下的交通和住宿报销标准。

按问题形式索引:
上海酒店报销上限是多少?
普通员工去上海出差能坐什么交通工具?
一类城市住宿费标准是多少?

对于制度文本,也可以抽取结构化信息:

制度名称:差旅报销制度
条款类型:住宿标准
适用对象:普通员工
城市范围:一类城市
金额上限:500 元/人/晚
审批要求:超标需提前审批

这样用户问问题时,系统不只做语义相似度,还可以用结构化条件缩小范围:

城市=上海
费用类型=住宿
人员级别=普通员工

结构化索引非常适合:

  • 表格
  • FAQ
  • 产品参数
  • 合同条款
  • 政策规则
  • 数据库 schema
  • 知识图谱实体关系

它解决的是“文本相似度无法稳定表达结构关系”的问题。

但结构化索引也有边界。

抽取字段会引入额外成本,也可能抽错。字段设计太细,会让维护变重;字段设计太粗,又起不到过滤作用。

所以结构化索引要从高价值字段开始,而不是一上来追求全量结构化。

比如公司制度问答助手里,优先结构化这些字段就很有用:

制度名称
章节
费用类型
城市
城市级别
人员级别
金额上限
生效日期
审批要求

这些字段直接影响回答正确性。

八、混合检索:语义相似和关键词精确匹配要一起用

前面讲多表示索引时,我们已经看到一个事实:

用户问题和资料原文不一定在同一种表达空间里。

这也是为什么生产 RAG 里经常会引入混合检索。

混合检索的基本思路是:

密集向量检索:负责语义相似
稀疏/关键词检索:负责精确词、编号、术语、代码、金额、条款号
融合策略:把两路结果合并成一个候选列表

在公司制度问答助手里,用户问:

上海出差酒店每天最多能报多少?

密集向量检索擅长理解:

酒店 -> 住宿
最多能报多少 -> 报销上限

但关键词检索更擅长抓住:

上海
500 元
一类城市
高铁二等座
2025 版

如果只用密集向量,系统可能语义上找到了“差旅报销”,但错过具体城市、金额和条款号。

如果只用关键词,系统可能抓住“上海”和“酒店”,但不理解“最多能报多少”其实是在问“住宿费标准”。

所以更稳的做法不是二选一,而是让两类检索互相补位。

常见链路是:

用户问题
-> dense query embedding
-> sparse / BM25 query
-> 两路各自召回 top_k
-> 去重
-> 融合排序
-> 必要时再重排

这里要注意,混合检索不是“开了就一定更好”的魔法开关。

它至少有三个参数要评估:

dense 和 sparse 的权重怎么分
两路各自召回多少候选
融合后是否还需要 reranker

比如专有名词、编号、产品型号、法规条款很多的知识库,关键词一路通常很重要。

但如果用户主要问抽象概念、总结类问题,密集向量一路可能更重要。

所以混合检索更适合被放进评估闭环,而不是作为固定答案。

可以用问题集把问题分成几类:

精确事实题:上海酒店上限是多少?
术语变体题:酒店报销标准怎么规定?
编号条款题:第 3.2 条说了什么?
综合比较题:上海和成都出差标准有什么不同?
总结类问题:差旅制度大概有哪些限制?

然后分别看:

纯 dense 是否召回
纯 sparse 是否召回
hybrid 是否召回
融合后正确证据是否排在前面

这样你会更清楚:混合检索到底在帮哪类问题,而不是只看平均分。

九、选修:向量数据库里的 Index,主要优化速度、内存和召回率

索引优化 手绘图 3:向量数据库索引方法的取舍地图

这一节可以当作选修。

如果你现在只是在学习 RAG 的基本链路,还没有真的遇到百万级、千万级向量检索的延迟和成本问题,可以先知道它和前面的索引优化不是同一层,后面用到 Milvus、Qdrant、Weaviate、Faiss、pgvector 这类工具时再细看。

还有一个概念容易混淆:

RAG 文章里说的“索引优化”,和向量数据库里说的 “index type”,不是同一层东西。

前面讲的父子块、层次索引、多表示索引、结构化索引,主要是在回答:

知识应该被组织成什么形状,才更容易被找到?

向量数据库里的 HNSW、IVF、PQ、SCANN、FLAT 这类索引,主要是在回答:

向量数量变多以后,怎么在可接受的速度、内存和准确率之间做工程权衡?

如果只有几千条 chunk,用精确搜索也许还能接受。

但当知识库变成几十万、几百万、几千万条向量时,逐个计算相似度会越来越慢。

于是向量数据库会用近似最近邻搜索,也就是 ANN。

ANN 的核心思路可以先粗暴理解成:

不一定每次都找数学上 100% 最精确的最近邻,而是在可接受的召回损失下,大幅提高查询速度。

常见类型大概可以这样记:

FLAT:
精确搜索,召回最好,但数据大了会慢。

IVF:
先把向量分桶,查询时只进部分桶里找,速度更快,但可能漏掉桶外的正确答案。

HNSW:
用图结构做近似搜索,查询通常很快,召回也高,但更吃内存。

PQ / SQ:
对向量做压缩,节省内存和存储,但会牺牲一部分精度。

再稍微展开一点。

1. FLAT:最容易理解,也最不适合大规模

FLAT 基本就是暴力搜索:

查询向量
-> 和库里每个向量都算一遍相似度
-> 排序
-> 返回 top_k

它的好处是结果最直接,不会因为近似搜索漏掉候选。

坏处也很明显:数据量越大,搜索时间越接近线性增长。

所以 FLAT 更适合:

小数据集
评估基线
需要极高召回率的小范围过滤后搜索

如果你要判断某个 ANN index 有没有损失召回,FLAT 很适合做对照组。

2. IVF:先分桶,再只搜几个桶

IVF 可以理解成先把向量空间划成很多“桶”:

所有向量
-> 聚类成 nlist 个桶
-> 查询时找到离 query 最近的几个桶
-> 只在这些桶里算相似度

它的核心收益是减少搜索范围。

比如一共有 1000 个桶,查询时只搜其中 10 个桶,就不用扫全库。

但代价也在这里:

搜的桶太少:更快,但可能漏掉正确答案
搜的桶更多:召回更高,但延迟上升

所以 IVF 常见调参方向是:

nlist:建多少个桶
nprobe:查询时探测多少个桶

如果 nprobe 太低,系统可能很快,但正确证据进不了 top_k。

如果 nprobe 太高,它又会越来越接近 FLAT,速度优势下降。

3. HNSW:用图来跳着找近邻

HNSW 是很多向量数据库默认或常用的图索引。

它可以粗略理解成:

每个向量是一个点
相似的向量之间连边
图有多层
上层稀疏,负责快速导航
下层密集,负责精细查找

查询时,系统不是从头扫所有向量,而是从高层图开始,逐层往更接近 query 的节点走。

它的优点通常是:

查询快
低 top_k 场景表现好
召回率可以做得比较高
适合高维向量

它的代价是:

内存占用更高
构建索引更重
更新和删除会带来维护成本
参数调大后更准,但更慢、更占内存

HNSW 常见调参方向是:

M:每个节点保留多少连接,越大通常召回越好,但内存越高
efConstruction:构建索引时搜索候选的宽度,越大构建越慢但图质量更好
efSearch / ef:查询时搜索候选的宽度,越大召回越高但查询越慢

所以如果你看到一个系统“换 HNSW 后变快了”,还要继续问:

recall@k 有没有下降?
p95 / p99 延迟是多少?
内存是否还能接受?
索引构建和增量更新是否还能接受?

4. PQ / SQ:压缩向量,换取内存和速度

PQ 和 SQ 更像是压缩技术。

它们的目标不是改变知识结构,而是减少向量存储和距离计算成本。

可以先这样理解:

SQ:把每个维度用更少 bit 表示,比如从 FP32 压到 8-bit。
PQ:把一个高维向量拆成多个子向量,每个子向量用码本近似表示。

压缩以后,内存会明显下降,距离计算也可能更快。

但压缩是有损的。

直白地说,它可能把两个原本距离有细微差别的向量压得更难区分,从而影响排序和召回。

所以 PQ / SQ 更适合:

数据量大
内存紧张
可以接受轻微召回损失
会配合 refinement / rerank 重新精排候选

有些向量数据库会把图索引和量化组合起来,比如 HNSW_PQ;也会把 IVF 和 PQ 组合起来,比如 IVF_PQ。

这类组合的共同目标是:

先用近似结构快速缩小范围
再用压缩表示降低内存和计算
必要时用原始向量或更高精度向量做 refinement

5. 怎么给 RAG 读者选型

如果只从 RAG 应用视角看,可以先用这张简单表:

小知识库 / 本地 demo:
先用 FLAT 或默认索引,重点放在分块、元数据和评估集。

中大型知识库 / 低延迟问答:
优先考虑 HNSW,关注 efSearch、M、内存和 p95 延迟。

数据很大 / 内存敏感:
考虑 IVF_PQ、HNSW_PQ、SQ、DiskANN 或 mmap 类方案,接受一定召回权衡。

过滤条件很多:
除了 vector index,还要给 metadata / payload / scalar field 建索引。

极高准确率场景:
用 FLAT 做评估基线,ANN 结果必须和 recall@k 一起看。

在公司制度问答助手里,早期最该优化的通常不是 HNSW 参数,而是:

分块是否按制度结构切
表格字段有没有保留
城市、人员级别、费用类型有没有 metadata
是否有问题表示和关键词表示
评估问题是否覆盖常见问法

只有当这些基础做好了,数据量又真的把查询延迟、内存或构建成本顶上来时,才进入这一节。

这类优化非常工程化,调的是:

延迟
吞吐
内存
索引构建时间
召回率
成本

它不能替代前面的知识组织。

如果你的 chunk 切错了、元数据缺了、表格结构丢了,就算 HNSW、IVF、PQ 调得再好,也只是更快、更省地找回不够好的候选。

反过来,如果知识组织得很好,但向量库索引参数太激进,也可能因为 ANN 召回损失,把正确证据漏掉。

所以生产 RAG 里要把两类指标分开看:

知识组织指标:
答案证据是否被建进索引?
是否有父子关系、标题路径、元数据、多表示入口?
用户换问法时是否还能召回?

向量库工程指标:
同一问题下 recall@k 是否下降?
p95 / p99 延迟是否可接受?
内存和存储成本是否可接受?
索引构建和增量更新是否能承受?

这样可以避免一个常见误判:

检索效果不好,就去调 HNSW 参数。

有些问题确实是 ANN 参数导致的。

但更多时候,先要确认答案证据有没有被正确解析、分块、标注、表示和链接。

向量数据库索引优化解决的是“找得快不快、成本高不高、近似搜索漏不漏”的问题。

RAG 索引优化解决的是“正确证据有没有以合适的形状进入候选池”的问题。

这两层都重要,但不要混在一起调。

十、索引优化和检索后处理不是一回事

讲到这里,容易有一个混淆:

索引优化、检索优化、重排、上下文压缩,到底有什么区别?

可以先用一句话区分:

索引优化:在检索之前,把知识组织得更容易被找到。
检索后处理:在检索之后,把候选结果整理得更适合生成。

索引优化更偏“入库前和入库时”:

怎么分块
怎么建立父子关系
怎么生成摘要
怎么生成问题表示
怎么保留元数据
怎么构建层次结构
怎么索引表格和实体关系

检索后处理更偏“取出来以后”:

怎么重排
怎么去重
怎么压缩上下文
怎么过滤低质量片段
怎么融合多路召回结果
怎么控制最终上下文预算

但它们不是割裂的。

一个好的 RAG 系统通常是这样工作的:

索引阶段:
为知识准备多个入口和结构关系

召回阶段:
从向量、关键词、元数据、结构化索引多路召回候选

重排阶段:
把最相关、最完整、最可信的证据排到前面

生成阶段:
让模型基于证据回答,而不是凭感觉补全

所以索引优化不是替代重排。

它是在帮重排拿到更好的候选。

如果第一阶段召回完全没把答案找回来,后面的 reranker 再强也没用。因为重排只能在候选里重新排序,不能凭空创造没被召回的证据。

这也是生产 RAG 里一个很重要的判断:

答案没有进入候选集:优先查数据导入、分块、索引、查询构建。
答案进入候选集但排名低:优先查融合、重排、评分策略。
上下文里有答案但模型答错:优先查 prompt、证据表达、生成约束。

不要把所有问题都怪到模型头上。

很多时候,模型只是最后一个暴露问题的环节。

十一、怎么判断该用哪种索引优化

索引优化方法很多,但不要一开始全上。

更实用的方式是从问题反推。

1. 答案总是差一点上下文

表现:

召回的片段有关键词
但缺少前后解释、表头、条件或例外

优先考虑:

父子块索引
上下文窗口扩展
章节级扩展
表头和脚注回填

例子:

系统召回了“500 元/晚”,但没有带回“一类城市”和“普通员工”。

这时不是先换 embedding 模型,而是先检查上下文扩展。

2. 长文档检索不稳定

表现:

文档很长
相似片段很多
系统经常召回同主题但不回答问题的内容

优先考虑:

层次索引
章节摘要
标题路径
先粗召回再细检索

例子:

员工手册有几百页,用户问“产假工资怎么算”,系统召回了“请假流程”,但没有进入“薪酬计算”小节。

这时层次索引会比单纯调 top_k 更有效。

3. 用户问法和原文差异很大

表现:

答案明明存在
但用户换一种问法就召回失败

优先考虑:

多表示索引
FAQ 式问题生成
摘要索引
关键词索引
同义词和领域术语表

例子:

原文写“住宿费标准”,用户问“酒店能报多少”。

这时多表示索引能给同一条知识补多个入口。

4. 表格和规则类内容经常答错

表现:

数字、字段、条件、适用范围容易混

优先考虑:

结构化索引
元数据过滤
表格行级索引
实体关系抽取

例子:

系统把“交通补贴 200”当成“住宿上限 200”,或者把“高管标准”套到“普通员工”身上。

这时要保留字段关系,而不是只靠文本相似。

5. 专有名词、编号和金额经常漏召回

表现:

语义上相关的内容能找到
但具体条款号、城市名、产品编号、金额数字不稳定

优先考虑:

混合检索
BM25 / sparse 检索
dense + sparse 融合
关键词字段 boost

例子:

用户问“3.2 条里上海住宿标准是多少”,纯向量检索可能找到“住宿标准”相关段落,但漏掉“3.2”这个精确定位条件。

这时要让关键词检索参与召回,而不是只靠 embedding。

6. 数据量变大后延迟或召回突然变差

表现:

小数据集表现正常
数据变大后查询变慢
或者切换向量库索引后,正确证据不再进入 top_k

优先考虑:

向量库 index type
ANN 参数
recall@k 对比
p95 / p99 延迟
内存和索引构建成本

例子:

从 FLAT 切到 HNSW 或 IVF 后,查询变快了,但某些评估题的正确证据不再被召回。

这时要同时看工程指标和检索质量,不要只看延迟。

7. 候选里有答案,但最终没用上

表现:

top_20 里有答案
top_5 里没有
最终上下文没有正确证据

优先考虑:

重排
多路召回融合
上下文压缩
候选去重

这部分更接近下一篇“检索后处理”的内容。

但它和索引优化强相关,因为索引阶段准备的表示越好,后处理阶段越容易判断哪些证据重要。

十二、索引优化必须用评估闭环驱动

索引优化 手绘图 4:评估驱动的索引优化闭环

索引优化最容易遇到的问题,是凭感觉调。

比如:

chunk size 从 500 改成 800
top_k 从 5 改成 10
overlap 从 50 改成 100
加了摘要索引
又加了问题索引

每一步看起来都有道理,但最后到底有没有变好,很难说。

所以生产 RAG 里,索引优化必须配一组问题集。

至少要记录这些指标:

答案证据是否被召回
答案证据在候选中的排名
最终上下文是否包含完整证据
上下文里噪声是否过多
回答是否引用了正确证据
回答是否遗漏条件或例外

对于公司制度问答助手,可以准备这样的评估问题:

上海出差酒店每天最多能报多少?
普通员工去北京能坐商务座吗?
请病假三天需要什么证明?
超过住宿标准还能报销吗?
2025 年新版差旅制度什么时候生效?
实习生能不能申请差旅报销?

每个问题都要标注标准答案和证据来源:

问题:上海出差酒店每天最多能报多少?
标准答案:普通员工一类城市住宿上限为 500 元/人/晚。
证据:
- 差旅报销制度 / 城市级别表 / 上海属于一类城市
- 差旅报销制度 / 住宿标准表 / 一类城市普通员工 500 元/晚

然后看不同索引策略下的表现:

普通 chunk 索引:是否召回两条证据?
父子块索引:是否补齐表头和适用条件?
多表示索引:换问法后是否仍能召回?
结构化索引:是否正确过滤城市和人员级别?
混合检索:专有名词、编号、金额是否召回更稳?
向量库索引:延迟下降后 recall@k 是否还能接受?
重排后:正确证据是否进入最终上下文?

这样你才能知道优化到底发生在哪里。

否则很容易出现一种错觉:

我加了很多高级技巧,所以系统应该更强。

不一定。

如果摘要质量差,多表示索引会引入噪声。

如果父块太大,上下文扩展会把无关内容带回来。

如果结构化字段抽错,metadata filter 会稳定地过滤掉正确答案。

如果层次索引摘要太粗,查询会从第一步就走错路。

所以索引优化的最后一步,一定是评估。

十三、把索引优化放回 RAG 全链路

现在把这篇文章收束一下。索引优化站在分块和检索之间:

分块之后
检索之前
把知识组织成更容易被找到的结构

可以用这条线记住:

原始资料
-> 数据导入
-> 分块
-> 索引优化
   -> 父子块:小块找,大块答
   -> 上下文扩展:命中一点,补齐周围证据
   -> 层次索引:先找章节,再找片段
   -> 多表示索引:同一知识,多个入口
   -> 结构化索引:字段、表格、关系参与检索
   -> 混合检索:dense + sparse 共同召回
   -> 向量库索引:速度、内存、召回率权衡
-> 多路召回
-> 重排与压缩
-> 生成答案

如果只记一句话,可以这样记:

RAG 索引优化不是把向量存得更漂亮,而是让知识在不同问法、不同粒度、不同结构下都能被稳定找到。

普通向量索引解决的是“能不能搜”。

索引优化解决的是“复杂问题下还能不能搜得准、搜得全、搜得可解释”。

十四、一个实用的索引优化顺序

如果你正在从零做一个 RAG 系统,不建议一开始就上所有高级索引。

可以按这个顺序来:

第一步:先跑通基础 chunk + embedding + top_k 检索
第二步:用问题集评估哪些答案召回失败
第三步:针对上下文不完整,引入父子块和窗口扩展
第四步:针对长文档,引入标题路径和层次索引
第五步:针对问法差异,引入摘要、问题、关键词等多表示索引
第六步:针对表格和规则,引入结构化字段和 metadata filter
第七步:针对专有名词、编号、金额,引入 dense + sparse 混合检索
第八步:数据规模变大后,再系统调向量库 index type 和 ANN 参数
第九步:针对排序问题,引入重排、融合和上下文压缩
第十步:持续用评估集回归,避免优化一个问题又打坏另一个问题

这条顺序的重点是:

先让系统可评估,再让系统变复杂。

因为索引优化一旦做深,系统就会从一条简单链路变成多路召回、多层结构、多种表示的组合系统。

没有评估,你会很难判断到底是哪一层在变好,哪一层在添乱。

小结

这一篇讲的是 RAG 里非常容易被低估的一层:索引优化。

它不是简单的“建索引”,也不只是“存向量”。

它真正关心的是:

用户会怎么问?
答案藏在哪种结构里?
系统应该用什么粒度去找?
命中后要补哪些上下文?
同一份知识要不要准备多个入口?
表格、字段、关系要不要参与检索?
最终怎么证明它真的变好了?

从问题演化看,整条链是这样的:

普通向量索引能跑通 RAG
-> 但 chunk 太小会丢上下文
-> 所以需要父子块和上下文扩展
-> 长文档平铺检索太粗
-> 所以需要层次索引
-> 用户问法和原文写法不一致
-> 所以需要多表示索引
-> 表格、字段和规则容易混
-> 所以需要结构化索引
-> 专有名词、编号、金额需要精确匹配
-> 所以需要混合检索
-> 数据规模变大后延迟、内存和召回率开始拉扯
-> 所以需要调向量数据库索引
-> 候选召回后还要排序和压缩
-> 所以下一篇进入检索后处理

索引优化做得好,后面的检索、重排、生成都会轻松很多。

索引优化做得差,后面就会一直被动补救:调 prompt、换模型、加 top_k、加 reranker,看起来很忙,但根因可能只是知识一开始就没有被组织好。

下一篇我们继续往后走,讲“检索后处理”:

当系统已经召回一堆候选片段之后,如何通过重排、去重、融合和上下文压缩,把最值得交给模型的证据挑出来。