☰
RAG系统调优的六大分水岭:从切块到评估的工程实践
2026/10/7 6:46:06 网站建设 项目流程

RAG 这个词现在确实被说烂了。打开任何一个技术社区,搜"RAG 实战",能刷出几百篇教你用 LangChain 加个向量库、把文档切一切塞进去、接个 LLM 就完事的教程。我去年帮三个团队做过 RAG 系统的调优,每次接手前对方都说"我们已经有 RAG 了",结果一看,检索召回率不到 40%,答非所问是常态,用户问一个跨文档的对比问题直接歇菜。问题出在哪?不是 RAG 这个范式不行,是绝大多数人只搭了那条最粗的流水线——切块、嵌入、向量检索、拼 prompt——然后就以为完事了。

真正决定一个 RAG 系统能不能用的,从来不是这条流水线本身,而是藏在它背后、大多数人根本没意识到的几个分水岭。这篇文章我想把这六处分水岭掰开揉碎讲清楚:它们分别是什么、为什么它们才是决定成败的关键、以及每一处你具体该怎么落地。不管你是刚准备搭第一个 RAG 知识库,还是手里已经有一个跑得半死不活的系统想抢救,这六处都值得你对照着过一遍。我会尽量用大白话加实际案例来讲,涉及参数和操作的地方给出可复现的细节,让你看完能直接动手改。

1. 分水岭一:切块策略决定了检索的天花板

1.1 为什么固定长度切块是原罪

几乎所有人搭 RAG 的第一步都是RecursiveCharacterTextSplitter,chunk_size 设个 500 或 1000,overlap 设个 50 或 100,然后就开始跑。这个做法不是错,是太粗糙。固定长度切块最大的问题是它完全无视文本的语义边界。一个完整的论证可能被从中间劈开,前半段进了 chunk A,后半段进了 chunk B,检索的时候你只能召回其中一半,LLM 拿到的是残缺的上下文,回答自然缺胳膊少腿。

我见过最离谱的案例是一个法律合同问答系统,一份合同里的"违约责任"条款被切成了三段,用户问"违约了要赔多少",系统召回了第一段(讲违约定义)和第三段(讲赔偿上限),偏偏漏掉了中间那段(讲具体赔偿比例)。结果回答里说"合同约定了赔偿上限但未明确比例",实际上原文写得清清楚楚。这就是切块把语义切碎了导致的。

固定长度切块的另一个隐性代价是:它让 embedding 的质量大打折扣。一个 chunk 里如果混了两个不相关的主题,它的向量表示就是两个主题的"平均",既不偏向 A 也不偏向 B,检索时对 A 和 B 的查询都匹配不好。你可以把 embedding 想象成给一段文字拍一张"语义照片",如果这段文字本身是拼凑的,照片就是糊的。

1.2 语义切块与结构感知切块怎么选

正确的做法是让切块跟着文本的语义结构走。具体分两个层次:

结构感知切块适用于有明确格式的文档,比如 Markdown、HTML、代码文件。Markdown 就按标题层级切,一个二级标题下的内容作为一个 chunk,如果太长再按三级标题细分。代码就按函数或类切。这种切法能保证每个 chunk 是一个完整的语义单元。LangChain 里的MarkdownHeaderTextSplitter就是干这个的,用起来很简单:

from langchain.text_splitter import MarkdownHeaderTextSplitter headers_to_split_on = [ ("#", "Header 1"), ("##", "Header 2"), ("###", "Header 3"), ] splitter = MarkdownHeaderTextSplitter(headers_to_split_on=headers_to_split_on) chunks = splitter.split_text(markdown_doc)

语义切块适用于没有明显结构的纯文本,比如会议纪要、访谈记录。做法是先按句子切分,然后计算相邻句子的 embedding 相似度,相似度骤降的地方就是语义边界。这个思路在semantic-text-splitter这类库里有实现。代价是要多跑一遍 embedding,慢一些,但对长文档质量提升明显。

我的经验是:优先用结构感知切块,没有结构再用语义切块,实在不行才退回固定长度,但要把 chunk_size 调大一些(800-1200),overlap 给足(100-200),尽量减少语义被切断的概率。

1.3 父子块与小块检索大块生成

还有一个进阶技巧值得单独说:小块检索、大块生成。具体做法是把文档切成两层,小的子块(200-300 字)用来做 embedding 和检索,因为它语义聚焦、匹配精准;检索命中后,返回它所属的父块(1000-2000 字)给 LLM 做上下文。这样既保证了检索精度,又保证了生成时有足够的上下文。

这个模式在 LlamaIndex 里叫SentenceWindowNodeParser或HierarchicalNodeParser,实现起来不复杂,但效果提升很明显。我实测过一个技术文档问答场景,从固定切块换成父子块之后,答案完整度(人工评估)从 62% 提到了 81%。原因很简单:小块让检索更准,大块让生成更全。

提示:父子块的父块大小不要超过 LLM 上下文窗口的 1/4,否则几个块一拼就爆了。一般父块控制在 1500 字以内比较稳妥。

2. 分水岭二:检索质量不是向量库能救的

2.1 纯向量检索的三个死穴

很多人以为选个好点的向量库(比如 Milvus、Qdrant、Weaviate)检索质量就上去了。这是个误解。向量库只是存储和检索的引擎,它不负责理解你的查询意图。纯向量检索有三个绕不过去的死穴:

第一,对精确匹配无能为力。用户问"GPT-4 的上下文窗口是多少",向量检索可能召回一堆讲"大模型上下文"的泛泛内容,但就是漏掉那个明确写着"128K"的段落。因为向量相似度衡量的是语义相近,不是关键词命中。

第二,对否定和限定词不敏感。"不含糖的饮料"和"含糖的饮料"在向量空间里可能非常接近,因为大部分词是一样的。用户问"哪些方案不支持分布式",向量检索很可能召回一堆讲"支持分布式"的文档。

第三,对长尾查询效果差。用户问一个非常具体、训练数据里少见的问题,query 的 embedding 可能落在向量空间的稀疏区域,召回质量断崖式下跌。

2.2 混合检索:BM25 加向量的正确配比

解法是混合检索:把 BM25(关键词检索)和向量检索的结果融合。BM25 负责精确匹配和关键词命中,向量负责语义泛化,两者互补。融合的常用算法是 RRF(Reciprocal Rank Fusion),它不需要调权重,直接把两路结果的排名做倒数加权:

def rrf_fusion(vector_results, bm25_results, k=60): scores = {} for rank, doc in enumerate(vector_results): scores[doc.id] = scores.get(doc.id, 0) + 1 / (k + rank + 1) for rank, doc in enumerate(bm25_results): scores[doc.id] = scores.get(doc.id, 0) + 1 / (k + rank + 1) return sorted(scores.items(), key=lambda x: x[1], reverse=True)

RRF 的好处是不用调参,k 取 60 是论文里的经验值,实测下来很稳。如果你想要更精细的控制,可以用加权融合,给向量和 BM25 各一个权重,但这个权重得在你的数据上试出来,没有通用值。

我一般建议:如果你的知识库是技术文档、法律条文、产品手册这类术语密集的内容,BM25 的权重给高一些(0.5-0.6);如果是客服对话、经验分享这类口语化内容,向量权重给高一些(0.6-0.7)。

2.3 Rerank 模型:把粗排结果精修一遍

混合检索解决的是"召回"问题,但召回回来的 top-20 里,真正相关的可能只有 5 个,而且排序不一定对。这时候需要Rerank 模型做精排。Rerank 模型(比如 BGE-Reranker、Cohere Rerank)的工作方式是:把 query 和每个候选文档拼在一起,用一个 cross-encoder 算相关性分数,然后重新排序。

它比向量检索准得多,因为 cross-encoder 能同时看到 query 和文档的全部内容,做的是真正的交互式匹配,而不是各自算 embedding 再比距离。代价是慢,所以只能用在召回后的少量候选上(一般 20-50 个)。

标准流程是:混合检索召回 top-50 → Rerank 精排取 top-5 → 送给 LLM 生成。我实测过一个场景,加了 Rerank 之后,top-5 的命中率从 58% 提到了 84%,提升非常显著。Rerank 模型建议用 BGE-Reranker-v2-m3,中文效果好,本地部署也方便。

注意:Rerank 不是万能的。如果召回阶段压根没把正确文档捞回来,Rerank 也救不了。所以混合检索和 Rerank 是配套的,不能只做一个。

3. 分水岭三:Contextual Retrieval 让每个块自带背景

3.1 孤立块的信息缺失问题

这是 Anthropic 提出的一个思路,我觉得是这两年 RAG 领域最实用的改进之一。问题背景是这样的:你把文档切成块之后,每个块是孤立的。比如一个块写着"该方案的成本比上一版降低了 30%",但"上一版"是什么、"该方案"指哪个,块里没说。检索命中这个块之后,LLM 拿到这句话也是一头雾水。

传统解法是加大 overlap,让相邻块有重叠内容,但这治标不治本,而且浪费存储。Contextual Retrieval 的思路是:在把每个块存入向量库之前,先用 LLM 给这个块生成一段简短的背景说明,然后把背景说明拼在块前面一起做 embedding。

比如上面那个块,LLM 生成的背景可能是:"本段出自 XX 产品 2024 年 Q2 的成本优化报告,讨论的是方案 B 相对于方案 A 的成本变化。" 拼上之后,这个块的 embedding 就带上了完整的语境,检索时更容易被正确命中,LLM 拿到后也更容易理解。

3.2 用 LLM 给块补背景的具体做法

实现起来不复杂,核心是一个 prompt:

context_prompt = """以下是一份文档的全文(或摘要): <document> {document} </document> 以下是文档中的一个片段: <chunk> {chunk} </chunk> 请用一到两句话说明这个片段在文档中的背景和位置,帮助读者理解这个片段在讲什么。只输出背景说明,不要重复片段内容。"""

然后对每个块跑一遍,把生成的背景拼在块前面。代价是要多跑一遍 LLM,成本上去了,但效果提升明显。Anthropic 的测试数据显示,加了 Contextual Retrieval 之后,检索失败率降低了 35%,如果再叠加 Rerank,能降低 49%。

我的实操建议是:不要对全库都做,优先对"孤立块比例高"的文档做。怎么判断?简单粗暴的办法是随机抽 20 个块,人工看有多少块脱离上下文就读不懂。如果超过 30%,就值得做 Contextual Retrieval。另外,背景说明不要超过 100 字,太长了会稀释块本身的语义。

3.3 成本与收益的平衡点

Contextual Retrieval 的成本主要在 LLM 调用上。假设你有 10 万个块,每个块生成背景要 500 token 输入加 100 token 输出,用便宜点的模型(比如 Haiku 级别),总成本大概几十美元。一次性投入,之后增量更新时只对新块做,成本可控。

收益方面,除了检索准确率提升,还有一个隐性好处:它让 chunk 变得"自解释",即使检索错了,LLM 拿到一个带背景的块,也更容易判断这个块是否相关,从而在生成时主动忽略不相关内容。这一点在 Agentic RAG 场景下尤其重要,因为 Agent 需要自己判断检索结果的质量。

4. 分水岭四:GraphRAG 解决的是关系型问题

4.1 什么时候向量检索彻底失效

有一类问题,向量检索从原理上就解决不了:需要跨多个文档、多个实体做关系推理的问题。比如"公司 A 的 CEO 和公司 B 的 CTO 之前在哪家公司共事过?"这种问题,答案分散在多个文档里,需要先找到 A 的 CEO 是谁、B 的 CTO 是谁,再查两人的履历,找交集。向量检索只能召回"看起来相关"的块,但它没有"关系"的概念,无法做这种多跳推理。

这就是 GraphRAG 要解决的问题。它的核心思路是:从文档里抽取实体和关系,构建一个知识图谱,然后基于图谱做检索和推理。检索时不是找相似的块,而是找相关的实体和它们之间的关系路径。

4.2 实体抽取与图谱构建的工程细节

GraphRAG 的落地分三步:

第一步,实体和关系抽取。用 LLM 从每个块里抽实体(人、公司、产品、事件)和关系(任职于、投资了、合作过)。这一步的 prompt 设计很关键,要明确告诉 LLM 抽什么类型的实体、什么类型的关系。微软的 GraphRAG 项目里有现成的 prompt 可以参考。

第二步,图谱构建和社区检测。把抽出来的实体和关系建成图,然后用 Leiden 算法做社区检测,把关系紧密的实体聚成社区。每个社区生成一个摘要,这个摘要在做全局性问题(比如"这个领域的主要玩家有哪些")时特别有用。

第三步,图检索。查询时先定位到相关实体,然后沿关系边扩展,把相关的实体、关系、社区摘要一起送给 LLM。

4.3 GraphRAG 的适用边界与成本陷阱

GraphRAG 不是银弹,它的成本和复杂度都比向量 RAG 高一个量级。实体抽取要跑一遍全量 LLM,图谱构建和社区检测也有计算开销。而且它对抽取质量非常敏感,抽错了实体或关系,整个图谱就是歪的。

我的判断标准是:如果你的查询里超过 20% 是关系型、多跳型问题,才值得上 GraphRAG。如果大部分查询还是"XX 是什么""XX 怎么做"这种单跳问题,向量 RAG 加混合检索就够了,上 GraphRAG 是杀鸡用牛刀。

另外,GraphRAG 和向量 RAG 不是二选一,可以混合。常见做法是:先用向量检索召回相关块,再从这些块里定位实体,然后沿图谱扩展。这样既利用了向量的召回能力,又利用了图谱的关系推理能力。

5. 分水岭五:Agentic RAG 把检索变成决策

5.1 从"检索一次"到"检索多次"

传统 RAG 是"检索一次,生成一次"的线性流程。但真实问题往往需要多轮检索:先查 A,根据 A 的结果决定下一步查 B,再根据 B 的结果决定要不要查 C。这种"边查边想"的模式就是 Agentic RAG。

举个例子,用户问"我们公司去年在华东区的销售额是多少,和华南区比怎么样?"传统 RAG 可能一次检索召回一堆销售数据,但 LLM 要自己从里面挑出华东和华南的数字做对比,容易出错。Agentic RAG 的做法是:Agent 先规划——我需要华东数据和华南数据;然后分别检索;拿到数据后判断是否完整;不完整就再检索;最后做对比生成答案。

5.2 查询改写与子问题拆解

Agentic RAG 的第一个能力是查询改写。用户的原始 query 往往不适合直接检索,需要改写成更适合检索的形式。比如"那个新出的模型怎么样"这种指代不清的 query,Agent 要先结合对话历史改写成"XX 模型(2024 年发布)的性能评价"。

第二个能力是子问题拆解。复杂问题拆成多个子问题,分别检索,最后综合。这个在 LlamaIndex 的SubQuestionQueryEngine里有实现。拆解的好处是每个子问题都聚焦,检索精度高,而且可以并行检索提速。

5.3 自我反思与检索结果校验

Agentic RAG 最关键的能力是自我反思:拿到检索结果后,Agent 要判断这些结果是否足以回答问题。如果不够,要能识别缺什么,然后发起新的检索。这个能力靠的是给 Agent 一个明确的判断 prompt:

reflection_prompt = """用户问题:{question} 已检索到的信息:{retrieved_context} 请判断:这些信息是否足以完整回答用户问题? 如果足以回答,输出 "SUFFICIENT"。 如果不足,输出 "INSUFFICIENT",并说明还缺什么信息。"""

这个反思循环可以跑多轮,直到信息足够或达到最大轮数。实测下来,加了反思循环之后,复杂问题的回答完整度提升很明显,但延迟也上去了。所以要根据场景权衡:对延迟敏感的场景(比如实时客服),反思循环限制在 1-2 轮;对质量敏感的场景(比如研究报告生成),可以放宽到 3-5 轮。

提示:Agentic RAG 的调试比传统 RAG 难得多,因为流程是非线性的。建议在每一步都打日志,记录 Agent 的决策、检索的 query、召回的结果,方便回溯问题。

6. 分水岭六:评估体系决定你能不能持续优化

6.1 没有评估就没有优化

这是最容易被忽视、但最重要的一处。我见过太多团队,RAG 系统上线后,用户反馈"答得不好",但具体哪里不好、是检索问题还是生成问题、改了什么有没有变好,完全说不清。没有评估体系,优化就是盲人摸象。

RAG 的评估要分两层:检索层和生成层。检索层看召回率、命中率、MRR(平均倒数排名);生成层看答案的忠实度(faithfulness,答案是否基于检索内容)、相关性(relevance)、完整度(completeness)。

6.2 检索层与生成层的关键指标

检索层的核心指标是Recall@K:正确文档出现在 top-K 里的比例。这个指标直接决定了生成层的上限。如果 Recall@5 只有 60%,那生成层再强也只能在 60% 的问题上答对。我一般建议 Recall@5 至少做到 85% 以上,才值得去优化生成层。

生成层的核心指标是Faithfulness:答案里的每一句话是否都能在检索内容里找到依据。这个指标衡量的是幻觉程度。可以用 LLM 做裁判来评估:把答案和检索内容一起给 LLM,让它判断答案是否有依据。

faithfulness_prompt = """检索内容:{context} 生成的答案:{answer} 请判断:答案中的每一句话是否都能在检索内容中找到依据? 输出格式:{{"faithful": true/false, "unsupported_claims": ["..."]}}"""

6.3 用 RAGAS 搭建自动化评估流水线

手动评估不现实,要用工具。RAGAS是目前最成熟的 RAG 评估框架,它内置了 faithfulness、answer_relevancy、context_precision、context_recall 等指标,输入是问题、答案、检索上下文、标准答案(可选),输出是各项分数。

搭建评估流水线的关键是测试集。测试集要覆盖你的真实查询分布:简单事实查询、多跳推理查询、否定查询、模糊查询各占一定比例。测试集不用大,50-100 条就能反映问题,但一定要有代表性。我一般建议从真实用户日志里采样,然后人工标注标准答案。

有了评估流水线之后,每次改动(换切块策略、加 Rerank、调 prompt)都跑一遍评估,看指标变化。这样才能知道改动是有效还是无效,避免拍脑袋优化。

注意:评估指标不是越高越好,要结合业务场景看。比如客服场景,faithfulness 比 completeness 重要,宁可答得少也不能答错;而研究场景,completeness 更重要,宁可多给信息让用户自己判断。

7. 六处之外:那些容易被忽略的工程细节

7.1 元数据过滤:被低估的检索加速器

前面讲的六处都是核心分水岭,但还有几个工程细节,做了能让系统更稳。第一个是元数据过滤。每个块除了文本内容,还应该带上元数据:来源文档、创建时间、文档类型、权限标签等。检索时可以先按元数据过滤,再在过滤后的子集里做向量检索。这样既提速又提准。

比如用户问"2024 年的销售政策",你可以先按year=2024过滤,再检索,避免召回 2023 年的旧政策。权限标签则能保证用户只能检索到自己有权限的文档,这在企业场景下是刚需。

7.2 增量更新与版本管理

第二个是增量更新。知识库不是静态的,文档会新增、修改、删除。如果每次更新都全量重建索引,成本高且不可持续。正确做法是维护文档到块的映射,文档更新时只重建这个文档对应的块,删除时只删对应的块。向量库一般支持按 ID 删除,所以关键是维护好 ID 映射。

版本管理也很重要。有时候新版本反而效果差,要能快速回滚。建议每次索引更新都打一个版本号,保留最近几个版本,出问题能切回去。

7.3 缓存与降级策略

第三个是缓存。相同或相似的 query 会重复出现,缓存检索结果和生成结果能大幅降低延迟和成本。缓存 key 可以用 query 的 embedding 做近似匹配,相似度超过阈值就命中缓存。降级策略则是:当检索服务或 LLM 服务不可用时,要有兜底方案,比如返回缓存结果、返回"暂时无法回答"而不是报错。

这些工程细节不性感,但决定了系统能不能在生产环境稳定跑下去。我见过太多 demo 很惊艳、一上生产就崩的 RAG 系统,问题往往就出在这些地方。

8. 我的实操路线建议

如果你现在要从零搭一个 RAG 系统,或者要抢救一个现有系统,我建议按这个顺序推进:

第一阶段,把基础打牢。切块用结构感知或语义切块,检索用混合检索(BM25 加向量),加 Rerank 精排。这三件事做完,系统就能达到"能用"的水平。别急着上 GraphRAG 或 Agentic RAG,基础不牢,上层越复杂越乱。

第二阶段,补语境和评估。对孤立块多的文档做 Contextual Retrieval,同时搭建 RAGAS 评估流水线。有了评估,你才知道下一步该优化哪里。

第三阶段,按需上高级能力。关系型问题多就上 GraphRAG,多跳问题多就上 Agentic RAG。但一定要有评估数据支撑,别为了技术而技术。

最后分享一个我踩过的坑:不要一次性把所有优化都堆上去。我曾经在一个项目里同时改了切块、加了 Rerank、换了 embedding 模型,结果指标反而降了,但根本不知道是哪个改动导致的。后来学乖了,每次只改一个变量,跑评估,确认有效再改下一个。RAG 优化是个细活,急不得。

这套东西我在三个不同规模的团队里都落地过,从几万文档的小知识库到百万级文档的企业系统,核心逻辑是一样的:分水岭不在流水线本身,而在流水线之外的这些决策点上。把这几处想清楚、做到位,你的 RAG 就能甩开那些"烂大街"的系统一大截。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询