环节一:文档解析——召回失败的隐形元凶
多数团队在 RAG 效果不达标时,第一反应是换向量模型或调检索参数。但如果你做过系统性的 bad case 归因,会发现一个不那么直觉的事实:大量召回失败的根因发生在入库阶段,而非检索阶段。典型的失败模式包括——PDF 表格被解析成乱序文本、扫描件 OCR 输出夹杂乱码、Markdown 标题层级在转换中丢失导致上下文断裂。这些问题无论后续检索链路多精巧,都无法补救:候选池里根本不存在质量合格的片段。
一个实用的排查原则是:当 Top-50 粗召回结果中找不到正确答案时,不要急着加 Rerank 或调向量模型,先回头检查文档解析和分块是否把关键信息破坏了。Rerank 只能对已经进入候选池的片段重新排序,它救不了一个从源头就丢失的答案。
三类文档的解析策略
不同文档类型需要不同的处理管线,盲目用同一套流程处理所有格式是常见的工程失误:
| 文档类型 | 核心风险 | 推荐处理方式 | 质量门控 |
|---|---|---|---|
| 纯文本 / 结构良好的 Markdown | 风险低,标题层级偶尔丢失 | 直接入库,保留标题层级作为元数据 | 抽检标题树完整性 |
| 含表格、列表、代码块的 PDF/Word | 表格行列错乱、列表项合并为单段 | 结构化解析器提取边界,输出带类型标注的 JSON | 人工抽检表格还原度 |
| 扫描件 / 图片型 PDF | OCR 识别错误、公式/特殊符号乱码 | OCR 引擎处理后按字符置信度过滤 | 置信度低于阈值的段落标记为"待人工校验",不直接入库 |
工程实现要点
结构化解析的目标不是"把 PDF 转成文本",而是"把文档转成带边界标注的结构化数据"。具体来说:
- 表格独立成块:将表格作为独立片段保留,附带表头信息作为元数据。表格一旦被拆散混入正文,语义完整性不可恢复。
- 列表保持原子性:有序列表的每个条目如果包含独立条件或结论,应保持在同一个 chunk 内,避免条件与结论被切分到不同片段。
- 代码块标记类型:代码段应标注语言类型并作为完整单元入库,不参与后续的文本分块逻辑。
- 元数据随片段走:每个输出片段携带来源文件名、页码/章节路径、文档类型、解析置信度等字段,供后续检索时做过滤和溯源。
开源工具链中,Unstructured 和 MinerU 都能输出带元素类型标注的结构化结果。选型时关注两点:一是对你业务中高频文档格式的表格还原准确率,二是是否支持自定义后处理管线(比如把低置信度段落路由到人工审核队列)。
落地建议
在项目初期投入一到两天做"入库质量审计"——随机抽取 50 个文档片段,人工比对原文与入库后的文本,统计信息丢失率。这个动作的 ROI 远高于在检索层反复调参。如果信息丢失率超过 10%,后续所有优化都建立在一个有缺陷的地基上。先把地基修好,再谈上层建筑。
环节二:分块策略——可直接抄的参数组合
分块是 RAG 管线里最容易被低估的环节。很多团队把文档丢进去,用默认的固定长度切一刀,chunk_size 设 1000、overlap 设 0,然后花大量时间调 prompt——方向搞反了。切片质量直接决定了检索精度的上限,后面的向量模型和 Rerank 只能在这个上限内做优化。
固定切分为什么不够用
固定长度切分的核心问题是它对文本结构完全无感知。一段完整的操作步骤可能被从中间劈开,一个代码块的前半段和后半段落入不同 chunk,用户提问时只能召回残缺片段。行业评测普遍显示,这种朴素方案的检索准确率大致在六成左右,将近一半的查询拿不到有效答案。而切片粒度不当(过碎或过大)造成的精度损失,幅度可以达到三到五成——这不是微调能补回来的。
语义感知切分:按结构边界下刀
改进思路很直接:让切分点落在文本的自然边界上——段落结束、标题切换、代码块闭合。这种语义感知策略不需要复杂模型,用规则就能实现大部分收益。多个开源评测的复现结果表明,仅这一步调整就能将召回率提升约 15%–20%。
更进一步,可以引入句子级 Embedding 相似度来判断主题是否发生了切换:
- 对相邻句子计算余弦相似度
- 相似度 < 0.5 → 判定为主题转折,在此处切断
- 相似度 ≥ 0.5 → 判定为同一话题,继续合并
这个 0.5 阈值是中文场景下的经验起点。英文文本由于句间语义连续性更强,阈值可酌情上浮。实际部署时建议拿标注好的 bad case 做一轮阈值扫描,步长 0.05,通常两三轮就能收敛。
推荐参数组合:Parent-Child 双层结构
单层 chunk 始终面临一个矛盾:粒度细了检索准,但送进 LLM 的上下文碎片化;粒度粗了上下文完整,但检索噪声大。Parent-Child 策略把这个矛盾解耦:
| 层级 | Token 长度 | Overlap | 用途 |
|---|---|---|---|
| 子 Chunk(检索用) | 300–400 Token | 50–100 字符 | 做向量化和相似度匹配 |
| 父 Chunk(上下文用) | 1200–2000 Token | 约 200 字符 | 命中子块后,把父块送入 LLM |
工程实现上,每个子 chunk 存储时记录其父 chunk ID。检索阶段用子 chunk 做 top-K 召回,去重父 chunk 后将完整父段落拼入 prompt。这样既保证了检索阶段的精度(小粒度匹配),又保证了生成阶段的上下文完整性(大粒度输入)。
Overlap 的作用是避免切分边界处的信息丢失。50–100 字符的重叠窗口足以覆盖一个完整短句,防止关键信息被劈裂到两个 chunk 的边缘而两边都召不回来。
元数据增强:给每个 chunk 加一层"检索外衣"
原始文本切好之后,还有一步低成本高回报的操作:为每个 chunk 生成一段精简的概括标题(控制在 50 字以内)加上关键词,拼接到检索文本中一起做向量化。
为什么有效?因为用户的提问通常是高度概括性的(比如"怎么配置 SSL 证书"),而原始 chunk 里可能通篇都是具体操作步骤,没有出现"配置 SSL 证书"这几个字。概括标题相当于在检索空间里给 chunk 多建了一个语义入口。行业实测普遍显示,这种增强手段能在语义切分的基础上再带来显著的准确率提升。
实现建议:用轻量模型(或规则提取 TF-IDF 前 10 个多字词)生成关键词,用 LLM 一次性批量生成标题。标题和关键词拼在 chunk 正文前面,中间用换行分隔,整体作为 embedding 输入。注意这段增强文本只用于向量化,不用于最终送入 LLM 的上下文——避免引入噪声。
决策路径总结
- 第一步:把固定切分换成按段落/标题/代码块边界的规则切分,零成本拿到基础收益
- 第二步:引入句子 Embedding 相似度做主题边界检测,阈值从 0.5 开始调
- 第三步:上 Parent-Child 双层结构,子块 300–400 Token 检索、父块 1200–2000 Token 送生成
- 第四步:为每个 chunk 追加概括标题和关键词,增强检索召回面
这四步是递进关系,每一步都能独立验证效果。建议每做一步就跑一轮 evaluation set,确认召回率确实在涨再往下走——避免过度工程化。
环节三:向量模型选型——决策树与参数速查
向量模型是检索链路中替换成本最高的组件——换一次模型意味着全量文档重新向量化,索引重建,线上灰度切流。相比之下,调整分块参数只需重跑 pipeline 的后半段。所以选型决策要在项目初期做对,而不是靠后期"试试换个模型"来兜底。
选型决策树
把场景收敛到三条路径,逐条判断即可:
| 场景特征 | 推荐模型 | 输出维度 | 最大输入 Token | 核心取舍 |
|---|---|---|---|---|
| 中文为主,文档块控制在 512 Token 以内 | 中文专用嵌入模型 | — | — | 本地部署成本低,中文语义表征成熟 |
| 中英混合语料,或存在超长段落需要大窗口 | 多语言嵌入模型 | — | — | 同时产出稠密与稀疏表征,天然适配混合检索 |
| 精度优先,可接受 API 调用延迟与成本 | 商业高维嵌入模型 | — | — | 高维空间区分度强,支持按需降维压缩存储 |
判断顺序:先确认语言分布和文档长度,再看部署约束(能否调外部 API、有无 GPU 资源)。多数企业内部知识库以中文为主且分块后不超过 512 Token,第一条路径就能覆盖。
关键参数速查
- 维度:768 / 1024 / 3072 三档。维度越高,细粒度语义区分能力越强,但索引体积和检索延迟线性增长。实际项目中 1024 维是性价比拐点——再往上提升的边际收益需要用你自己的评估集验证。
- 最大输入长度:512 Token 的模型要求上游分块严格控制长度,超出部分会被截断而非报错,这是隐性召回损失。如果分块策略偏长(800+ Token),必须选 8192 窗口的模型。
- 归一化方式:多数模型默认输出 L2 归一化向量,此时余弦相似度等价于内积,检索时用 Inner Product 即可。注意部分模型(如早期 M3E)需要手动加归一化步骤,否则相似度分数不可比。
- 量化:FP16 是部署基线。进一步压缩到 INT8,索引体积减半,业界实测精度损失通常在可忽略的范围内。但量化收益取决于数据分布,建议在自己的评估集上对比 Recall 再决定是否上线。
选型原则:评估先行,换模型是最后手段
正确的工作流是:
- 先用当前模型 + 当前分块跑一轮 Recall@50(取 Top-50 候选看目标文档是否在列),建立基线。
- 如果 Recall@50 已经足够高但最终回答不准,瓶颈大概率在 Rerank 或上下文组装,而非向量模型。
- 只有当 Recall@50 明显不足,且调整分块策略、增加元数据过滤后仍无改善,再考虑换模型。
换模型的隐性成本容易被低估:全量文档重新 Embedding 的计算开销、新旧索引并存期间的存储翻倍、上下游 pipeline 的维度参数联动修改。对于百万级文档库,一次模型切换的工程周期通常以周计。把这个成本和"多试几组 chunk 参数"的成本对比,优先级就很清晰了。
一条实用经验:如果你的评估集还没建好,先别纠结模型选型。用 BGE-large-zh 或 BGE-M3 起步,把精力花在构建 50–100 条标注好的 Query-Document 对上。有了评估集,所有决策都能用数据说话;没有评估集,换什么模型都是盲调。
环节四:混合检索——向量 + BM25 双路召回工程实现
纯向量检索有一个结构性盲区:它依赖语义相似度打分,但对错误码(如 ERR_0x80070005)、SKU 编号、软件版本号这类精确标识符,embedding 模型往往把它们映射到相近的向量空间区域,导致召回结果张冠李戴。工程上的应对不是换更大的模型,而是补一路 BM25 关键词检索做精确匹配兜底。
标准双路召回流程
工程实现的骨架很直观:
- 向量检索取 Top-50 候选
- BM25 关键词检索取 Top-50 候选
- 两路结果合并去重,候选池上限约 100 条
- 用 RRF(Reciprocal Rank Fusion)对合并结果做融合排序
- 融合后的有序列表交给下游 Rerank 模块做精排
RRF 的计算逻辑简单:对每条文档,在每路结果中取其排名 rank,按 1/(k + rank) 求和作为最终分数。参数 k 控制排名衰减的平滑程度,实践中多数团队从一个中间值开始调,这个值在多个主流框架中也是默认项。k 值在一定范围内对最终排序的影响不算剧烈,初期不必花太多精力调它,把重心放在两路召回各自的质量上收益更大。
Query Rewrite:必须保留原始查询
Query Rewrite 是改善用户模糊提问的常见手段,但它引入了一个容易被忽视的风险:改写模型本身可能误解用户意图。一旦改写偏了,后续整条链路——检索、排序、生成——全部跟着偏移,而且这种偏移很隐蔽,因为系统"看起来正常运行"只是答非所问。
工程上的防御策略很简单:永远用原始查询和改写查询同时做召回,两路结果再融合。这样即使改写出错,原始查询的召回结果仍然在候选池里兜底。实现成本几乎为零——只是多发一次检索请求——但能有效避免改写失误导致的全链路崩塌。
Multi-Query:一个问题拆成多个检索方向
用户提出一个复合问题时,单一查询往往只能命中部分相关文档。Multi-Query 的思路是让 LLM 把一个问题展开为 3-5 个不同角度的子查询,分别检索后合并去重。
举个例子,用户问"你们的 SLA 具体是什么",可以展开为:
- 服务可用性承诺比例
- 故障响应与恢复时间要求
- 违约赔偿条款
- 服务等级的具体分级定义
四路检索各自取 Top-N,合并去重后候选池的覆盖面远超单次查询。这对知识库中信息分散在多个文档的场景尤其有效。
工程落地注意事项
| 关注点 | 建议 |
|---|---|
| BM25 索引维护 | 与向量索引同步更新,文档变更时两路都要重建,避免数据不一致 |
| 去重策略 | 按 chunk_id 去重即可;同一文档不同分块视为不同候选 |
| Multi-Query 子查询数量 | 3-5 条为宜,过多会拉高检索延迟且收益递减 |
| Query Rewrite 模型选择 | 不需要最强模型,轻量级模型(如 GPT-3.5 级别)足够,重点是保留原始查询兜底 |
| 延迟控制 | 多路检索可并行发起,总延迟取决于最慢的一路而非累加 |
混合检索的核心价值不在于"比纯向量多了一路",而在于它用工程手段对冲了单一检索范式的系统性缺陷。语义理解和精确匹配是两种互补能力,在企业知识库场景下几乎没有只需要其中一种的情况。把两路召回做扎实,给下游 Rerank 提供一个高质量候选池,才是整条 RAG 链路能跑出好效果的前提。
环节五:Rerank 重排序——从粗筛到精排的关键一跳
向量检索的本质是双塔架构:Query 和 Chunk 各自独立编码成向量,再算余弦相似度。这个架构的天然缺陷是——两段文本之间的细粒度交互信息在编码阶段就丢了。比如 Query 问"合同终止后保密义务存续多久",向量检索能把包含"保密""合同终止"的段落都捞回来,但哪一条真正回答了"存续期限"这个核心意图,单靠向量距离排不准。
Cross-Encoder 解决的就是这个问题:把 (Query, Chunk) 拼接成一个序列喂给 Transformer,让模型在每一层 Attention 中充分交叉计算两段文本的语义关系,输出一个精细相关性分数。代价是不能预计算——每对都要过一遍模型,所以只能用在候选池已经缩小之后。
分层 Top-K:粗召回→精排→入 Prompt 三级漏斗
工程上最常见的失误是只设一个 Top-K 参数。合理做法是把检索链拆成三级:
| 阶段 | 候选数量 | 作用 |
|---|---|---|
| 粗召回(向量 + BM25 融合后) | 30–100 条 | 保证 Recall,把正确答案兜进来 |
| Rerank 精排后保留 | 5–10 条 | 按语义相关度重排,砍掉噪声 |
| 最终进入 Prompt | 3–6 条 | 控制 Token 开销,减少模型被无关段落干扰的概率 |
这条漏斗的收益是双重的:一方面精排后 Top 位置的准确率会有显著提升;另一方面,因为最终只喂少量高质量片段进 Prompt,送入生成模型的 Token 量大幅缩减,既降低延迟又节省成本。
什么时候该上 Rerank,什么时候不该
判断标准很简单——先看粗召回阶段(Top-50)里有没有正确答案:
- 正确片段已在候选池中,但排名靠后(比如在第 15–40 位):这是 Rerank 的典型适用场景。问题出在排序精度不够,Cross-Encoder 能把它提上来。
- 正确片段根本不在候选池中:Rerank 无法凭空创造答案。这时候应该回头排查上游——文档解析是否丢了内容、分块是否把关键信息切断、元数据过滤是否过于激进、Query 改写是否偏离了原始意图。在这些环节修好之前,加 Rerank 只是在垃圾堆里精心排序。
一个简单的诊断方法:对评测集跑 Recall@50,如果已经达到 90% 以上但 Precision@5 不理想,直接加 Rerank;如果 Recall@50 本身就低于 70%,优先排查召回链路。
工程实现要点
- 模型选择:中文场景下 bge-reranker-v2-m3、BAAI/bge-reranker-large 都是经过验证的选项;英文场景 ms-marco 系列的 Cross-Encoder 仍然是稳定基线。
- 候选池规模与延迟的权衡:Cross-Encoder 的推理耗时与候选条数线性相关。候选池控制在 50 条以内时,配合 GPU 推理,单次 Rerank 的延迟在工程可接受范围内;超过 100 条建议先做一轮轻量预筛再送精排。
- 分数阈值 vs 固定条数:实践中建议两者结合——先取 Top-N(如 5 条),再设一个最低分数门槛过滤掉明显不相关的。避免出现"Top-5 里最后两条分数极低但仍被塞进 Prompt"的情况。
Rerank 是整条检索链路中投入产出比最高的单点优化之一:不需要重建索引、不需要改分块策略,只在推理时加一步精排,就能把最终进入生成环节的上下文质量拉高一个台阶。但它的前提是粗召回阶段已经把正确答案兜住了——漏斗的第一层如果漏水,后面再精细的筛子也没用。
环节六:上下文组装与生成约束——最后一公里的工程细节
前面五个环节把召回和排序都调好了,如果最后拼给模型的上下文本身有问题,之前的功夫会打折扣。这一节讲的是"检索结果到 Prompt"之间那段容易被忽略的工程细节:传什么、传多少、怎么约束模型别瞎编。
父子块检索:检索要精准,喂给模型要完整
做过分块调优的人都遇到过这个矛盾:块切小了,检索命中精准,但内容碎片化,模型看到的只是半句话;块切大了,语义完整,但检索时容易被无关信息稀释,命中率下降。业内常见的两种上下文增强思路,一种是句子窗口扩展——命中一个句子后,把它前后各扩展几句再传给模型;另一种是父子块(父文档)检索。综合评测来看,父子块方案在检索精度和上下文完整性两个维度上通常都表现更好,是更值得优先落地的方案。
做法是建两级索引:切分时用小块(比如几百字一个)去建向量索引,检索匹配也用小块;但小块命中后,系统实际返回给 LLM 的不是这个小块本身,而是包含它的那个更大的父块(可能是整篇文档或整个章节)。举个例子,一份三千字的产品手册切成六个五百字的子块分别建索引,用户提问命中第三个子块后,最终传给模型的是这份手册的完整原文,而不是那孤立的五百字。这样检索阶段享受小块带来的精准匹配,生成阶段又不丢失上下文,两头都不吃亏。工程上需要维护子块到父块的映射关系,检索层和生成层分离处理,实现成本不算高,是性价比较高的一步优化。
Context 压缩:用一次额外的 LLM 调用换更干净的上下文
父子块解决的是"要不要传完整段落"的问题,Context 压缩解决的是"传进去的内容里有多少是噪声"的问题。具体做法是在正式生成答案之前,先让 LLM(或者一个更轻量的模型)读一遍检索到的 chunk,把和用户问题真正相关的句子提取出来,无关的背景信息、重复表述直接丢掉,再把精简后的内容拼进最终 Prompt。
这个方法能明显降低上下文里的噪声占比,尤其是在 chunk 偏大、里面掺杂了大量非相关内容的场景下效果明显。代价是多了一次 LLM 调用,意味着额外的延迟和成本。所以这个环节不建议无差别启用,比较合适的场景是对答案准确性要求很高、同时用户能接受多等一两秒的场景,比如法律条款核查、合同审阅这类任务;如果是面向 C 端的即时问答,多一次调用带来的延迟未必划得来,可以先只在父子块检索之后按需触发,而不是全链路默认打开。
生成阶段的两个约束:低温度 + 引用格式
上下文组装好之后,剩下的是对生成本身做约束,这两点成本低、见效快,几乎没有理由不做。
- Temperature 建议压到 0.1–0.3 这个低值区间。RAG 场景要的是"忠实转述检索到的信息",不是让模型自由发挥,温度调低能明显减少答案的随机漂移,同一个问题多问几次给出的答案会更一致,这对需要复现和调试的生产环境很重要。
- 在 Prompt 里明确约束引用格式,比如要求模型每给出一个结论都要标注来源于第几个检索片段、对应哪份文档。这一条不是为了美观,而是为了排查问题——一旦答案出错,能直接定位是检索环节召回错了,还是生成环节读错了正确的检索结果,两种问题的修复方向完全不同。没有这层约束,出了问题只能靠人工翻检索日志一条条对,效率很低。
综合来看,这几步的价值在哪
单独看每一个环节,父子块检索、Context 压缩、低温度设置、引用约束,每一项带来的提升都不算惊艳。但从实际落地的案例反馈来看,把语义切片、混合检索、重排序、上下文压缩、Prompt 约束这几个环节串起来一起做,整体效果的提升是叠加的:召回率能从不足五成的水平提升到接近满分区间,准确率同步从六成出头提升到九成以上,响应时间能缩短近一半,token 消耗也能省下一部分。这也是为什么这类优化不建议只挑一两个环节做——单点优化容易遇到瓶颈,链路一起调整才能看到质变级别的效果。
需要提醒的是,这些环节不是越多越好,压缩和 Rerank 都会引入额外延迟,具体上到什么程度取决于业务对准确率和响应时间的权衡,下一节的排查 SOP 会给出更具体的优先级判断。
落地路径:排查 SOP 与迭代优先级
RAG 系统的优化不是一次性调参,而是逐环节收敛的工程过程。实践中最常见的失败模式是:跳过基础数据治理,直接堆 Rerank 和 Prompt 技巧,结果花了大量精力却发现问题出在解析层丢了关键段落。下面给出一条经过验证的落地顺序和排查路径。
推荐迭代顺序
按投入产出比从高到低排列,每一步都应量化 Recall@K 和端到端准确率的变化,确认收益后再进入下一步:
| 阶段 | 动作 | 核心目标 |
|---|---|---|
| 1 | 数据治理 | 确保文档完整入库,解析无丢段、无乱码、表格/图片已处理 |
| 2 | 构建最小评估集 | 建立可量化的基线,终结"感觉变好了"的主观判断 |
| 3 | Chunk 策略对比 | 用评估集跑 A/B,选出当前语料最优的分块参数 |
| 4 | Hybrid Search | 向量 + BM25 双路互补,堵住单路召回的盲区 |
| 5 | Query Rewrite | 处理口语化、指代不清、多意图等查询质量问题 |
| 6 | Rerank 重排序 | 在候选池已有正确证据的前提下,把它推到 Top 位置 |
| 7 | 上下文压缩 | 减少噪声 chunk 送入生成模型,降低幻觉概率和 Token 消耗 |
| 8 | 生成约束 | Prompt 层兜底:引用标注、拒答规则、格式控制 |
| 9 | 灰度监控 | 线上持续收集 Bad Case,回到阶段 2 形成闭环 |
这个顺序的逻辑是:前四步解决"正确答案能不能进入候选池",后五步解决"进了候选池能不能被正确使用"。顺序颠倒会导致大量无效调优。
最小评估集:怎么构建
起步规模 50–100 条即可,关键是覆盖四类场景:
- 高频问题——线上日志中出现最多的 Top 查询,代表基本盘
- 已知失败 Case——用户反馈过"回答错误"或"没找到"的问题,代表当前短板
- 精确匹配问题——涉及错误码、版本号、SKU 编号等必须逐字正确的查询
- 应拒答问题——知识库中确实没有答案的问题,验证系统是否会幻觉作答
每条评估数据包含三个字段:question(用户原始提问)、golden_context(期望命中的源文档片段)、golden_answer(标准答案)。golden_context 的存在让你可以单独度量检索层的 Recall,而不是把检索问题和生成问题混在一起诊断。
Bad Case 排查决策树
当某条评估未通过时,按以下路径定位瓶颈:
- 第一步:检查粗召回候选池(Top-50)是否包含正确证据
- 如果不包含——问题在检索层上游。依次排查:文档是否入库 → 解析是否丢段 → 分块是否把答案切碎 → Metadata 过滤是否过严 → Query 是否需要改写 → 是否需要补充 BM25 通道
- 如果包含——问题在排序或生成层。依次排查:Rerank 是否把正确 chunk 排低了 → 送入 LLM 的上下文是否被截断 → Prompt 是否缺少引用约束或拒答规则
这条规则的核心原则:候选池里没有答案时,绝不要先上 Rerank——Rerank 只能调整已有候选的顺序,无法凭空召回缺失的内容。把精力花在让正确证据先进池子,再考虑怎么把它排上去。
逐步收敛的度量纪律
每完成一个阶段的改动,重新跑一遍评估集,记录两个核心指标:Recall@5(Top-5 是否包含 golden_context)和端到端准确率(最终回答是否匹配 golden_answer)。只有当某一步带来可观测的指标提升时才固化该改动,否则回滚。这种"改一步量一步"的纪律能避免多个变量同时变化时无法归因的困境。
常见问题
chunk_size 到底设多大最合适?
没有普适最优值,但有可操作的选择框架:先看语料特征。技术文档通常段落较长、上下文依赖强,chunk 偏大(500–700 token 区间)能保持语义完整;FAQ 和客服对话本身就是短文本,chunk 偏小(200–350 token)更贴合原始粒度;法律合同等长段落文本可以放到 800 token 以上,因为拆碎反而破坏条款完整性。确定候选区间后,用评估集跑对比实验才是终局答案——不要相信任何"通用最佳值"。
已经用了向量检索,还有必要加 BM25 吗?
几乎总是有必要。向量检索擅长语义匹配(同义词、换一种说法问同一件事),但在精确关键词场景下表现不稳定——用户查一个错误码、产品型号或专有名词时,向量模型可能把它映射到语义相近但事实不同的 chunk。BM25 的词频匹配恰好补这个短板。工程上两路独立召回再用 RRF 融合的成本很低,而对精确匹配类查询的召回提升往往非常显著。如果你的评估集中有"错误码/版本号"类问题且单路向量检索表现不佳,加 BM25 基本是确定性收益。
Rerank 会不会拖慢响应速度?
取决于候选池大小和模型选择。Cross-Encoder 对每一对 (query, chunk) 做一次前向推理,候选池控制在 20–50 条时,主流 Rerank 模型的延迟对整体响应的影响在可接受范围内。如果对延迟极度敏感(如对话场景要求 P99 低于 2 秒),可以缩小候选池到 20 条,或选用轻量级 Rerank 模型。另一个工程技巧是异步预取:在用户输入过程中就开始触发粗召回,等 Rerank 执行时用户感知到的等待时间会更短。总之 Rerank 带来的准确率提升通常值得这点延迟代价,真正的问题是候选池有没有正确答案,而不是排序快不快。
怎么判断当前系统该优先优化哪个环节?
回到 Bad Case 排查决策树。抽取最近 20–30 条失败 Case,逐条标注失败原因归属到哪个环节(解析丢失、分块过碎、召回未命中、排序靠后、生成幻觉、应拒未拒)。哪个环节的失败计数最高,就优先攻克那个环节。这比凭直觉选方向可靠得多。如果你还没有评估集——那么第一优先级就是构建评估集本身,没有度量就没有优化。