生产环境RAG中的十种隐蔽检索缺陷,以及每种缺陷的修复方法。
从块大小与重叠度到混合搜索、RRF、重排序、嵌入模型选择及置信度评估:导致错误RAG答案的十种检索错误及其解决方案。
有一个客服聊天机器人曾用优美的文笔解释了错误的退款政策。检索系统找出了一个使用了四次“refund”一词的段落。该段落主题相关,且描述的是已停产的产品线。大型语言模型只是按照指示,根据所提供的上下文自信地作答,因为没人告诉它这些上下文是错误的。
这就是大多数需要通过调试解决的RAG系统故障的现状。团队花费两周时间更换模型、重写系统提示语,还用粗体标出“仅依据上下文作答”,但幻觉率几乎没有任何变化。问题从来不在生成环节,而是在模型接收到的证据本身就已经错误,而它却仍基于这些错误信息进行正确推理。
在实际应用的RAG系统中,有十种特定的错误会降低检索质量。这些错误通常会按固定顺序出现:首先是分块错误,因为它位于所有流程的最前端;接着是检索设计问题;然后是重排序与嵌入算法的选择;最后是衡量标准上的缺陷,导致这些问题在系统上线时未被发现。下文各部分将分别说明每种错误产生的原因、带来的后果以及应采取的改进措施,对于研究结果存在矛盾的地方,则会如实说明。
为何检索环节是RAG系统出问题的关键
2026年关于生产环境RAG系统的实践报告始终得出了相同的结论:大多数错误答案都源于检索环节。要么返回了错误的文档,要么遗漏了正确的文档,又或者这些文档在排序中的位置太靠后而无法被使用。普遍估计这一比例在70%甚至更高。该数值会随报告内容和领域不同而变化,因此仅能作为粗略参考,但不同来源的观点一致。当RAG系统出现错误时,最可能的原因是大语言模型从未看到过正确的证据,因此首要步骤是在修改提示词之前先检查检索器返回的内容。
这十种错误可归为四类,分别对应不同的处理流程环节。第1至4类错误会影响输入向量存储的数据质量;第5至7类错误与检索架构有关;第8和第9类错误出现在排序与嵌入阶段;第10类错误则是整个流程缺乏相应的衡量标准。
以下术语说明
- 分块指的是在建立索引之前将源文档分割成若干段落。与处理整个文件相比,嵌入模型和上下文窗口在处理较短、目标明确的单元时表现更好。
- 密集检索(向量搜索)将查询内容与各分块置于同一个嵌入空间中,进而返回最接近的匹配项。该方法对意译内容的匹配效果较好,但对原文字符串的匹配能力较弱。
- 稀疏检索如BM25则是根据查询词与分块中的共享词来对分块进行排序,同时给予稀有词更高的权重。代码、名称及专业术语的匹配效果较好,而同义词则不然。
- 混合搜索则是同时运行两种检索方法,并将得到的两个排序列表合并,最常见的合并方式是使用互反排名融合(RRF)。RRF采用排名位置而非原始得分来进行融合,从而解决了BM25数值与余弦相似度无法直接比较的问题。
分块中的四个错误
1. 默认采用固定大小的分割方式,从不尝试其他方法
教程中首先介绍的方法是无论内容如何都将每个文档分割成500个标记的窗口。这种方法只需五分钟,足以用于演示,因此团队们往往不再对其进行调整就直接投入使用。
相关研究结果并不一致,坦诚说明差异比假装达成共识更有意义。多项2026年的对比研究认为,固定长度分割或递归令牌分割优于语义分块。在一项针对多种策略的大规模基准测试中,递归的512令牌分割方式胜出,部分原因在于语义分块平均产生的片段仅有43个令牌,太小而无法承载上下文信息。其他领域的研究则得出了相反结论:一项经过同行评审的临床决策支持评估显示,自适应的、与主题匹配的分块方法准确率为87%,而固定基准方法的准确率为50%(p = 0.001)。另一项对比研究则发现,经过良好调优的固定长度分割方法在真实数据集上的表现可与语义分割方法相媲美甚至更优,这与原始RAG论文中使用的固定100词段落结果一致。
没有哪种策略能在所有情况下都奏效。所有资料都认同的问题在于,错误并不在于选择了固定大小的划分方式,而在于在没有针对实际语料库对其他方案进行测试的情况下就选定了某种策略。
实际的应对方法应是通过实验来验证,而非仅凭理论推测。选取20到50组具有代表性的问答对,用两到三种候选策略对同一语料库进行索引处理,然后比较@5召回率和上下文精确度。对于那些本身具有结构的内容,如API文档、法律文件和财务报告,若先按照其结构进行划分,通常会取得更好的效果,因为这些单元本身就已经具有连贯性。
from langchain_text_splitters import RecursiveCharacterTextSplitter
# Recursive splitting respects structure first, falls back to
# character boundaries only when a section is still too large.
splitter = RecursiveCharacterTextSplitter(
chunk_size=512, # tokens, not characters, if you swap in a token-aware splitter
chunk_overlap=75, # ~15% overlap, a reasonable starting point
separators=["\n## ", "\n### ", "\n\n", "\n", ". ", " "],
)
chunks = splitter.split_text(document_text)
2. 每种文档类型使用相同的块大小
会提前根据经验法则选定一个固定大小,然后将其应用于混合集合中的所有内容。API参考资料、冗长的政策文件以及聊天记录都会使用相同的块大小。
过大的块会削弱相关性信号。一个包含五个主题、长度为2,000个标记的块所产生的向量只是这五个主题的模糊平均值,无法准确匹配任何单一主题。过小的块则会丢失读者所需的上下文,就好比没有参数说明的函数签名,或是没有说明其所修改部分的条款一样。
大小应与内容密度相匹配。技术文档的词元数量通常在256到512之间,这样才能让单个概念保持清晰。而叙事性或政策性文本则需要1,024到2,048个词元,以便保留上下文信息。这些只是参考数值,实际数值还需根据嵌入模型以及用户提问的方式来确定。
3. 忽视重叠问题
重叠处理一旦设置后就不再调整,或者因为被认为会浪费存储空间而被跳过。如果没有重叠处理,跨越分块边界的句子就会被拆成两半——一个分块以内容开头,另一个分块以结尾开始,这样整个句子就无法作为一个完整的思想被检索出来。这是人们常抱怨“答案就在那里,但模型找不到它”的常见原因,而且这一问题完全可以避免。
10%到20%的片段重叠度是合理的默认值:对于500个标记的片段,重叠度为50到100个标记,与它所避免的错误相比成本很低。更多并不总是更好。过高的重叠度主要会增加存储开销和噪声,至少有一项使用SPLADE检索进行的2026年系统性分析发现,在该设置下重叠度并无可测量的好处。应当以最优配置来确定标准,而非依赖默认值。
4. 忽视文档自身的结构
将每个文件视为一个整体更为简单,无论是Markdown API参考文档、包含编号条款的合同,还是充斥表格的财务报告,但忽视结构的拆分方式常常会导致标题与所属章节分离,或表格与解释它的段落分离。最常见的后果是函数描述与其参数列表分开,这样一来查询该函数时只能得到一半的答案。
解决办法是首先按照结构进行分割,即依据标题、章节、列表和表格等,仅在那些过大的章节内部才采用基于大小的分割方式。正因如此,到2026年中大多数生产环境中的分块处理流程都是混合型的:先按结构分割,对较长的非结构化内容再做语义分割,最后才作为不得已的手段使用固定大小的分割方式,且不会对混合语料库强制使用某一种单一方法。
检索架构中的三个错误
5. 仅部署向量搜索功能
入门模板默认使用向量搜索。它只需一次API调用,在演示中,当测试查询为对话式的同义表述时,其效果极为出色,而这正是密集嵌入模型最擅长的领域。
真实流量与演示流量有所不同。用户输入的产品代码、错误信息、标志名称、工单编号以及专有名词都属于简短的字面符号。那些经过训练用于聚类意义的密集模型往往会将这些符号与相似但错误的邻近符号混淆。各项基准测试都反复显示出这一差距:在电子商务排名测试中,经过优化的混合模型优于单独使用任一方法的结果;而实际应用基准测试则显示,仅依赖向量的召回率在@10阈值下远低于经过恰当融合的混合模型,主要原因在于无法识别出完全匹配的标识符。不同语料库中的数据数值会有所差异,但在所有测试场景中这一趋势都保持一致。每当查询同时包含自然语言风格和精确匹配风格时——而这几乎总是如此——混合模型总能胜出。
这一变更是在向量索引之外新增 BM25 或 SPLADE 索引。Weaviate、Elasticsearch、OpenSearch、Qdrant 和 Milvus 现在都支持原生混合查询,而 LangChain 和 LlamaIndex 也提供了用于实现该功能的融合检索器:
from langchain.retrievers import EnsembleRetriever
from langchain_community.retrievers import BM25Retriever
bm25_retriever = BM25Retriever.from_texts(chunk_texts)
bm25_retriever.k = 20
vector_retriever = vectorstore.as_retriever(search_kwargs={"k": 20})
# weights are a starting point for RRF-style blending; tune against your eval set
hybrid_retriever = EnsembleRetriever(
retrievers=[bm25_retriever, vector_retriever],
weights=[0.4, 0.6],
)
results = hybrid_retriever.invoke(user_query)
6. 采用加权平均而非RRF进行合并
在加入BM25之后,直观的做法是将两个得分列表都标准化到0到1的范围内,然后通过权重将它们混合起来。在电子表格中这样做似乎很合理。但BM25得分与余弦相似度源自形态各异、无界分布的数据,而最小-最大标准化方法存在缺陷。如果有一份文档重复出现查询词数十次,经过标准化处理后,它就会把其他所有BM25得分都压向零,从而以难以察觉的方式扭曲最终排序结果。
互斥排名融合通过使用每篇文档在各个列表中的排名位置并完全忽略分数来避免这一问题。正因如此,在2026年的工程文献中提到的几乎所有实际应用的混合算法中,采用的都是互斥排名融合而非加权平均法。标准公式为每篇文档在其出现的所有列表中计算1 / (k + 排名)的值之和,其中k=60是常用的默认值,可用于削弱极端排名的影响。
7. 每个阶段使用一个k值
Top-k值通常是一个全局常量,在第一阶段检索、信息融合以及最终提示生成时都会重复使用。当第一阶段仅获取提示所能容纳的少量片段时,重排序器还来不及采取任何措施,召回率就已经下降:向量搜索中排名14的片段根本无法被纳入提示中。另一种错误则是将数百个候选项直接交给重排序器而不进行任何筛选,这不仅耗费时间和金钱,还会让提示充斥着质量低下的内容。在如此大的负载下,大语言模型的注意力机制性能会下降(即“中间丢失效应”),从而抵消重排序带来的好处。
可以将k视为一个漏斗。在第一阶段选取较广泛的候选集,大约20到100个候选项,这样重排模型就有空间纠正早期的排序错误,之后再筛选出大约3到8个最终候选项用于提示词处理。需针对评估集分别调整每个阶段的k值,因为单一数值无法同时满足两种需求。
排序与嵌入中的两种错误
8. 忽略重排步骤
重排需要额外的网络调用或推理步骤,会产生延迟,对于托管服务而言还会增加费用。在截止日期的压力下,重排往往是首先被削减的环节,第一阶段的排序结果便被视为最终结果。
第一阶段向量搜索依赖于双编码器,它在任何查询出现之前就将文本压缩为一个固定向量。这正是其能够在大规模处理中保持高速的原因,但同时也限制了其精度,因为特定查询与特定文本之间的精细交互信息会丢失。交叉编码器则通过同时读取两者来恢复这些信息。2026年的多项独立研究报告指出,对于已具备较好第一阶段检索能力的系统而言,使用交叉编码器进行重排序是最佳的改进方式:通常可使精度或NDCG提升数个百分点,在词汇难度较高的数据集上这一提升幅度甚至可达20个百分点左右,而且在对向量池规模进行合理设置的情况下,仅需额外的不到200毫秒延迟。有个咨询项目记录显示,通过重排序,该项目的精度提升了23个百分点,这一提升幅度超过了任何嵌入模型或分块大小调整所带来的效果。一旦混合搜索方案可行,接下来就该尝试这种方法了。
这一变更是在融合阶段与最终提示词之间加入一个重新排序阶段。对于无需按次付费的自托管场景,bge-reranker-v2-m3是一款广受欢迎的开放式跨编码器,具备良好的多语言支持能力。如果不想使用GPU基础设施,Cohere的Rerank API则是常见的托管替代方案。这两种工具都能与LangChain和LlamaIndex集成:
from FlagEmbedding import FlagReranker
reranker = FlagReranker("BAAI/bge-reranker-v2-m3", use_fp16=True)
def rerank(query: str, candidates: list[str], top_k: int = 5) -> list[str]:
pairs = [[query, c] for c in candidates]
scores = reranker.compute_score(pairs, normalize=True)
ranked = sorted(zip(candidates, scores), key=lambda x: x[1], reverse=True)
return [text for text, _ in ranked[:top_k]]
final_context = rerank(user_query, fused_candidates, top_k=5)
9. 基于专业语料库的通用嵌入模型
框架附带的嵌入模型通常会直接应用于法律、医学或金融领域的语料库,仅通过少量人工抽查来验证其效果。而通用模型则是从广泛的网页和百科全书文本中学习,因此相比那些在专业领域数据上训练的模型,它们对专业术语的区分能力较弱。这种缺陷在风险最高的场景中危害最为严重。在受监管的领域里,如果出现错误,比如给出的是标准流程的相关内容而非用户询问的特定例外情况,那么得到的答案虽然看似合理,实则完全错误,这种情况比明显的失败更为糟糕。
在最终确定之前,至少要用一个针对特定领域优化或微调过的嵌入模型在黄金数据集上与通用默认模型进行对比测试。如果无法对嵌入模型进行微调,那么在少量带标签的领域查询数据集上训练的交叉编码器重排器通常能更快、更经济地缩小差距,因为它只需修正第一阶段的错误,而无需重新对整个语料库进行嵌入处理。
掩盖其他问题的错误
10. 按展示次数计费
在开发功能时,评估基础设施往往被视为额外的负担,只要演示效果良好,稍作检查输出结果似乎就足够了。但实际上,它带来的后续成本最高,因为其他九类问题都被掩盖了。那些基于检索到的上下文产生的错误内容、顺序错误的文档,以及切割位置不当的文本片段,仅通过查看输出是无法可靠发现的;等到用户反馈时,这些问题早已投入实际使用中。
解决方案是一组包含50到200对典型问答的“黄金集”,涵盖了表述模糊、需要两篇文档的多跳查询以及恶意改写等边缘情况。该集合会通过由两半组成的标准评估面板进行评分:检索器的评分标准是上下文精确度与上下文召回率,而生成器的评分标准则是答案的忠实度及其相关性。目前有两个主流框架主导着这一领域,各团队通常会将它们结合使用。Ragas是用于查看各项指标的常用工具,其内置的生成器可以从语料库中生成初始的“黄金集”,供人工进一步优化。DeepEval基于pytest构建,是实现自动化评估并作为持续集成关卡的常用方法。常见的分工是:用Ragas进行指标探索和数据生成,用DeepEval对每个拉取请求进行评估:
from deepeval import assert_test
from deepeval.metrics import FaithfulnessMetric, ContextualPrecisionMetric
from deepeval.test_case import LLMTestCase
def test_refund_policy_query():
test_case = LLMTestCase(
input="What is the refund window for the Pro plan?",
actual_output=rag_pipeline.run("What is the refund window for the Pro plan?"),
retrieval_context=rag_pipeline.last_retrieved_chunks,
expected_output="Pro plan purchases can be refunded within 14 days of purchase.",
)
faithfulness = FaithfulnessMetric(threshold=0.8)
precision = ContextualPrecisionMetric(threshold=0.7)
assert_test(test_case, [faithfulness, precision])
将这样的功能集成到持续集成流程中,这样一旦出现分割调整、嵌入替换或知识库更新导致检索效果下降,就能在影响客户使用之前就中断构建过程。
不同场景下错误的差异
- 企业内部搜索与文档问答是最常见的应用场景,它严重依赖混合检索方式。员工会将工单编号、政策代码和SKU与通俗语言的问题一起输入,而仅基于文本的搜索则无法捕捉到这些具体信息。
- 面向客户的支持机器人最能从重新排序功能中受益。如果机器人给出的回复虽然流利但错误,那就会导致客户提交工单或账户丢失,而非只是得到同事的敷衍回应。
选择下一步行动
在产品发布前阶段,或者仍在测试RAG是否可行的时候,仅使用向量搜索是一个合适的起点:它的构建速度最快,能够在一天之内回答有关架构设计的问题。一旦出现真实用户的查询,就需要在日志中查找产品代码、工单编号以及配置值等完全匹配的模式。如果发现了这类信息,应首先采用BM25与RRF融合的技术,因为相关资料表明,这是提升检索效果最有效的单一方法,且优势极为明显。之后,当混合搜索方案可行且目标是从精确度而非单纯的召回率出发时,再进行重排序处理。这种方案的成本相对较低,且无需重建索引。至于SPLADE或针对特定领域优化的嵌入模型,建议那些已经拥有成熟评估流程的团队再考虑使用,因为这些方法需要更多的投入和维护工作,而且在没有基准指标的情况下很难证明其必要性。
人数和预算决定了最佳方案。仅有两名工程师时,使用托管式的重排序 API(通常选择 Cohere)比自行搭建跨编码器更划算:在流量较低的情况下,相关费用远低于 GPU 服务器的运行成本。如果已有 MLOps 人员和 GPU,或者流量足够大以至于需要按请求计费,那么托管如 bge-reranker-v2-m3 这样的开源模型更为合理。
优质流程仍需付出的成本
RAG并非完美的解决方案。它的核心优势在于有据可依:回复可以引用检索到的资料,而无需仅依赖模型记忆的内容,而且知识库无需重新训练即可更新。不过,这些额外的机制确实会增加运营成本。需要保持同步的两个索引、用于监测偏差的重新排序器、作为核心产出物而需维护的评估流程,以及当文档类型发生变化时需要重新调整的分块方案,都增加了工作量。
RAG也无法彻底消除幻觉问题。它只能减少因知识缺失而产生的编造内容,但对于大语言模型错误解读或误用正确提供的上下文的情况则无能为力。之所以需要可靠性指标,是因为“基于检索到的上下文”与“正确”是两种不同的属性,而成熟的处理流程会同时衡量这两方面。
生产就绪性
- 持续评估。应对每个提示词、检索器或知识库的变更都运行Ragas和DeepEval测试,而非仅在系统启动时检查一次。
- 索引同步。应同时或按照统一的时间表更新BM25索引与向量索引。当过时的稀疏索引与最新的嵌入向量一同使用时,检索结果会出现难以察觉的偏差。
- 可观测性。需记录每次查询所检索到的内容片段,而不仅仅是最终回复。这样无需猜测即可判断不良答案是源于检索环节还是生成环节。
- 凭证管理。应将重新排序及嵌入向量相关的API密钥存放在独立于代码仓库的位置,并通过常规的密钥管理系统进行轮换。
核心要点
在更改提示词或模型之前,先看看检索系统返回了什么结果。十个错误中有九个(分块不恰当、未使用混合搜索、融合方式过于简单、候选项数量不合适、跳过了重新排序步骤、使用的嵌入模型未经验证)都是检索相关的问题,且有对应的解决方案。而第十个错误——缺乏评估——正是导致其他九个错误常常被忽视的原因。
这些改进方案都不需要组建研究团队。在现有的向量存储旁添加BM25、用RRF替代分数融合方法,或在融合与生成步骤之间取消重新排序操作,这些都属于工程实现层面的调整而非彻底重新设计,而且即便不同数据源的数值存在差异,这些方法都能获得一致的支持。一个可行的起步方式是:提取两周内的真实用户查询,统计其中包含那些会被向量搜索模糊处理的精确标识符的查询数量,再从现有的失败案例中挑选50个查询组成测试集。这个规模其实比想象中要小,同时也能帮助确定十种错误中哪些造成的损失最大。
相关阅读
- 调试悄无声息的RAG故障:检索、分块、过滤与评估 — RAG生成的答案听起来可能正确,但实际上却是错误的。这份以Python为思路的指南,帮助追踪检索、分块、元数据过滤、评估、混合搜索及结果关联等环节的问题。
- 用于法律文本的混合RAG系统:分块、BM25、BGE与重排序 — 基于印度宪法构建的法律RAG处理流程:父子级分块、BM25与BGE结合的检索方式、排名融合、交叉编码器重排序及查询扩展,已在多个基准测试中得到验证。