ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

RAG技术全解析:从原理到调优,解决大模型知识库落地难题

RAG技术全解析:从原理到调优,解决大模型知识库落地难题

说实话,和大模型打交道的这几年,我身边团队的分化越来越明显:一种还在为模型一本正经地胡说八道头疼,另一种已经安安静静地把内部知识库的问答准确率拉到了九成以上。区别往往不在于用了多强大的基座模型,而在于有没有真正吃透 RAG(Retrieval-Augmented Generation,检索增强生成)这套技术。

RAG 这个技术,概念上并不复杂,网上满屏都是"外挂知识库"之类的说法。但真到自己动手搭一套能用的系统,你会发现切分粒度、召回精度、重排序策略、上下文拼装,每一个环节都可能让最终效果打对折。这篇文章不重复概念,而是从一条完整的技术拆解线出发,把 RAG 的原理、工作流、选型思路、调优手段和翻车场景一次讲透。不管你是刚接触大模型应用开发的新手,还是已经被检索效果折磨过一阵子的老手,都能在里头找到可以直接抄作业的部分。

1. 大模型的"知识边界"困境:它能写诗,却答不出昨天刚发生的事情

1.1 静态知识的死穴到底在哪

先说一个我在很多场合反复提过的观点:大模型本质上是一个"记忆能力很强但记忆力很差"的静态知识容器。强在它的参数里压缩了海量的人类公共知识,你问它统计学概念、历史事件、文学典故,它都能给你说出个一二三。差在它所有的知识都定格在训练数据截止的那一刻,之后的世界对它来说是完全未知的。

你问它"我们公司上个月刚发布的内部报销制度",它不知道;你问它"2026年3月发布的最新行业报告核心结论",它也不知道。更麻烦的是,它并不会老老实实告诉你"我不知道",而是会基于自己的语言习惯编造一个听起来非常合理、实际上完全错误的答案。这就是所谓的幻觉问题。

这个问题的根源并不是模型不够聪明,而是知识获取机制的结构性缺陷。模型在预训练阶段见过什么,它就只能知道什么,没有任何机制让它去"查阅"一份新文档、一个新数据库或者一个实时更新的网页。所以当你试图把大模型直接用到企业内部知识问答、私有文档查询这类场景时,静态知识的边界就成了绕不开的墙。

1.2 微调和长上下文为什么都是"半截子方案"

很多人第一反应是:既然模型不知道,那我就微调它,把它不知道的知识喂进去。这个思路方向没错,但落地时你会发现它更像是"给房子换地基"而不是"加一扇窗户"。

微调的成本高在三个地方。第一,每一次知识更新都需要重新训练或增量训练,企业知识库每天都在变,你不可能每周都去微调一次模型。第二,微调的本质是让模型"记住"训练样本的分布,但如果样本量不足或者内容有冲突,模型很容易出现灾难性遗忘——学了一部分新知识,把一部分旧能力给忘了。第三,微调后的模型依然无法给出知识来源,你没法向用户证明"这段答案出自哪一份文档",在严肃的业务场景里这几乎是致命的。

另一个被寄予厚望的方案是长上下文。有些模型已经把上下文窗口做到了百万甚至千万 token 级别,理论上你可以把所有文档一股脑塞进 prompt 里。但实际操作过的人都知道,上下文窗口长和检索能力强是两回事。权威的信息在长上下文中会被"淹没",模型在几千行文档里定位关键段落的表现远比你想象中差。而且输入 token 数线性增长,成本也线性增长,每问一次问题都要把整个文档库的 token 都算一遍,这在规模化场景下根本不现实。

这两条路各有适用场景,但在"知识动态更新 + 答案可溯源 + 成本可控"这组需求面前,它们都不是最优解。这也是 RAG 能站上舞台的原因:它不试图改变模型的记忆,而是给模型配一个永不毕业的"资料检索员"。

2. RAG 的完整工作流:从文档入库到答案生成的四个关键环节

2.1 离线索引管道:先把知识库变成模型能查的格式

RAG 系统的工作流可以拆成两条管道。第一条是离线管道,负责处理静态文档;第二条是在线管道,负责处理用户查询。听起来像软件工程里的"编译期"和"运行期",这么理解就顺了。

离线管道的第一步是文档解析。PDF、Word、Markdown、HTML 这些格式要先转成纯文本,同时尽量保留标题层级、表格结构等语义信息。这一步的坑是隐蔽的,因为如果你解析出来的文本是乱的,后面所有的切分和向量化都是建立在垃圾之上。

第二步是文本切分,也就是常说的 chunking。一块文本不能太长也不能太短:太长了,向量化之后语义被稀释,检索时容易召回一堆模糊相关内容;太短了,语义不完整,又容易切碎关键信息。常见的做法是固定长度切分、递归字符切分、按标题结构切分,或者更高级的语义切分。

第三步是向量化。每一段文本块都会通过一个 Embedding 模型转成一个高维向量,这个向量要能体现文本的语义含义。向量之间的余弦相似度/内积相似度,就代表了语义上的接近程度。

第四步是写入向量数据库。向量数据库一方面承担向量的存储,更重要的是要做近似最近邻检索(ANN),能在毫秒级别从几十万、几百万条向量里找到与查询向量最相似的 Top-K 条。主流选项包括 FAISS、Milvus、Qdrant、Chroma、pgvector 等。

2.2 在线查询管道:检索、拼装、生成的完整链路

在线管道的流程是:用户输入一个问题 → 同样经过 Embedding 模型转成向量 → 在向量库中检索最相似的 K 个文本块 → 把这些文本块作为上下文,连同原始问题一起拼进 prompt → 交给大模型生成答案。

这里有一个很多人初次实现时会忽略的细节:查询向量和文档向量必须由同一个 Embedding 模型生成,否则向量空间不一致,检索结果会完全错乱。我在不少项目里见过有人文档用 A 模型向量化、查询用 B 模型向量化,结果检索到的文本块和问题文不对题,还以为是模型能力不行。

再往细看,prompt 拼装是有讲究的。不是简单把检索结果一股脑塞进去就完事,而是要明确告诉模型哪些内容是参考资料、哪些内容是用户问题、如果参考资料里没有答案该如何回答。"基于下面提供的资料回答用户问题,如果资料中没有相关信息,请明确说不知道"——这种指令设计虽然简单,但对降低幻觉率的帮助非常直接。

我把这两条管道的核心逻辑用一段伪代码整理了一下,方便大家理解全貌:

# 离线索引管道(一次性执行) documents = load_and_parse("知识库目录/") chunks = chunk_documents(documents, strategy="recursive", chunk_size=512) vectors = [embedding_model.encode(chunk) for chunk in chunks] vector_store.add(vectors, metadata=chunks) # 在线查询管道(每次用户提问执行) query = "公司最新的报销制度是什么?" query_vector = embedding_model.encode(query) top_k_chunks = vector_store.search(query_vector, top_k=5) context = "\n\n".join(top_k_chunks) prompt = f"基于以下资料回答问题:\n\n{context}\n\n问题:{query}\n如果资料中没有相关信息,请明确说不知道。" answer = llm.generate(prompt)

2.3 为什么说 RAG 的核心价值不只是"补充知识"

RAG 带来的价值往往被概括成"让模型知道更多",但我认为这只是最表面的一层。它真正改变的东西有两个:一个是时效性,你可以让模型回答训练数据截止之后才发生的事情,只要那件事被你写进了知识库;另一个是可溯源性,你可以把答案关联到具体的文档片段,用户能自己去查证,这对企业场景至关重要。

第三个隐性价值是成本结构。相比微调,RAG 不需要训练;相比长上下文,RAG 每次只检索 Top-K 个文本块进入 prompt,输入 token 数被压缩在很小的范围内。这意味着同样的预算可以服务更多的查询量,或者使用更强但更贵的基座模型。

3. 检索质量决定一切:Embedding 模型、切分策略与向量库选型

3.1 Embedding 模型怎么选:开源还是闭源,听劝

在整个 RAG 链路里,检索质量是所有下游效果的地基。而检索质量的第一决定因素就是 Embedding 模型。

现在主流的做法不外乎两种:调用商业 Embedding API,或者部署开源 Embedding 模型。商业 API 的优势是省心、性能稳定、中文语义效果好,缺点是数据要过第三方服务,对于很多企业内部项目来说存在合规风险。开源模型的主要优势是私有化部署,数据不出内网,配合本地推理资源可以做到完全自主可控。

具体模型上,开源社区里 bge 系列和 e5 系列在国内外的中英文场景中都是比较稳妥的选择。选择时重点看两个维度:一是 MTEB 或 C-MTEB 等榜单上的检索任务得分,二是模型支持的序列长度是否匹配你的 chunk 大小。另外,模型本身也有不同尺寸版本,像 bge-large、bge-base、bge-small 对应不同的性能消耗,我的建议是除非资源极其有限,否则不要为了省显存去用小尺寸模型,检索精度下降带来的损失远大于那点资源节省。

一个实操经验:如果你手里有标注好的业务问答对,强烈建议在这个基础上做一遍领域适配微调——用几十到几百对有代表性的(问题,相关文档片段)数据去微调 Embedding 模型,检索命中率往往能提升一到两个台阶。这是我做过多次、每次都有效的手段。

3.2 切分策略的细节:chunk_size 和 overlap 的取舍

很多教程会告诉你"chunk_size 设 512,overlap 设 50",然后就没有然后了。但真实的切分决策远没有这么简单。

chunk_size 的选择本质上是一个语义完整性和检索精确度之间的权衡。块越大,语义越完整,但向量表达越"平均化",检索时容易把与问题部分相关的大块文本召回,反而稀释了关键信息;块越小,检索越精准,但每块的信息量不够,容易丢失上下文,生成阶段会因为缺乏背景而产出片面答案。

我的经验是先从 400 到 800 这个区间起步,然后根据文档类型做调整。技术手册、合同文件这种结构严谨的文档,按标题和段落边界切最好;长篇小说、新闻报道这种连贯叙述型文本,固定大小加 overlap 会更合适。overlap 的作用是防止切分边界恰好把一段紧密的逻辑关系切断,一般设置为块大小的 10% 到 20% 就有明显效果。

还有一个不容易注意到的点:切分时尽量把元数据(文档名称、章节标题、更新时间、来源链接)附着到每个 chunk 上。这些元数据在后续做过滤、做引用展示、做权限控制的时候都会用到。你前期偷懒省掉的这一步,后期要花十倍力气补回来。

3.3 向量数据库横向对比:按场景选型而不是追流行

向量数据库的选择是另一个高频问题。我把几个主流方案的核心特征列在下面,方便对照:

方案部署方式核心优势适配场景
FAISS库嵌入轻量、快、适合离线构建与小规模检索原型验证、单机项目
Chroma库嵌入/服务上手快、Python 友好学习实验、小体量应用
Milvus 系分布式服务大规模向量管理、高并发、丰富的索引类型生产环境、千万级向量
Qdrant服务性能稳定、过滤能力强、Rust 实现中大规模生产环境
pgvector数据库扩展直接复用 PostgreSQL 生态已有 PostgreSQL 技术栈的团队

我的建议是:如果是做 Demo 或者内部小工具,先用 FAISS 或 Chroma 跑通验证,不要在这上面花太多时间。等验证完业务价值、确定了要上生产,再根据数据量和并发量评估 Milvus 或 Qdrant。如果你是那种"数据库必须统一管理"的团队,pgvector 也能用,只是向量检索的极致性能会打些折扣。

索引类型的选择同样有讲究。HNSW(分层可导航小世界图)是默认的稳妥选项,召回率高、查询速度快,缺点是构建索引需要较长时间、内存消耗较大。IVF(倒排文件索引)的构建速度快、内存占用小,但需要调 nprobe 参数来权衡召回和速度。只要资源不是特别紧张,我一般直接用 HNSW。

4. 让答案更精准:混合检索、重排序与查询改写三板斧

4.1 混合检索:向量召回和关键词召回一起上

纯向量检索有一个天然弱点:它擅长捕捉语义相似,但在精确匹配上反而会吃亏。比如你搜一个产品型号"X-Pro 3000",向量检索可能把"高端设备 X 系列"这种语义相近的文本召回,却漏掉精确包含"X-Pro 3000"这个字符串的文档片段。而关键词检索(如 BM25)恰好擅长这种精确匹配。

所以成熟一点的 RAG 系统几乎都会做混合检索:用 BM25(稀疏检索)和 Embedding(稠密检索)并行召回,再把两路结果做融合。融合方式可以很简单,比如给两路的 Top-10 都赋予分数后加权合并,也可以用更精细的 RRF(Reciprocal Rank Fusion)算法:把每路结果按排名取倒数分求和,然后按总分排序。

RRF 的写法非常简单,但效果立竿见影:

def reciprocal_rank_fusion(result_lists, k=60): fused_scores = {} for results in result_lists: for rank, doc_id in enumerate(results): fused_scores[doc_id] = fused_scores.get(doc_id, 0) + 1.0 / (k + rank + 1) return sorted(fused_scores.items(), key=lambda x: x[1], reverse=True)

混合检索是"保底"策略。它不保证每一条查询都能比纯向量检索好,但它能显著减少那些"明明知识库里有答案,却怎么都检索不出来"的遗憾情况。

4.2 重排序:用更精细的模型给召回结果重新打分

检索阶段召回 Top-K 的时候,为了保证召回率,K 通常会设得比较大,比如 20 到 50。这些候选中必然混着不少不相关的结果。如果你直接把这个庞大且嘈杂的上下文塞给大模型,效果反而比塞精准的 3 到 5 条还差。这时候就要上重排序模型。

重排序模型和检索用的双塔结构(Embedding 模型)不同,它通常是 cross-encoder 结构,把问题和文档拼接在一起输入模型,一次性计算它们的相关度。这种方式能利用更深层的交互信息,准确度远高于双塔,但计算成本也高,所以要把它放在召回之后、进入 prompt 之前,对 Top-50 的候选重新打分,只保留相关性最高的 Top-5。

在开源重排序模型里,bge-reranker 系列是比较常见的选择,很多商用 API 也提供 rerank 能力。一个值得提及的操作细节是:重排序阶段的 K 和生成阶段的 K 要分开设置。召回阶段用大 K 保召回,重排序阶段用小 K 提精度,这样既不会漏掉正确答案,也不会把噪声喂给大模型。

4.3 查询改写:多轮对话里最容易被忽略的坑

很多 RAG 系统处理单轮问答没什么问题,一进入多轮对话就崩。原因在于用户后续提问往往依赖上下文,比如用户先问"我们公司的报销标准是什么",再追问"超过一万怎么办",第二个问题里的"一万"指的是报销金额,但如果你只拿"超过一万怎么办"这六个字去做检索,向量空间里根本找不到对应的文档。

解决思路是查询改写。一种常见做法是用大模型把多轮对话的历史和当前问题组合起来,改写成一个完整的独立问题,再拿去检索:

rewrite_prompt = f""" 用户与助手在多轮对话。请根据对话历史将用户最新问题改写为一个不依赖上下文、含义完整的独立问题。 对话历史: {conversation_history} 用户最新问题:{current_query} 请只输出改写后的独立问题。 """

另一种思路是 HyDE(Hypothetical Document Embeddings):让大模型先基于问题生成一个假想的答案文档,然后用这个假想文档的向量去做检索。它的逻辑是"假设答案是放在文档库里的,那这篇文档大概长什么样",在领域知识强相关的场景里效果非常好,但需要大模型的生成质量靠谱,否则假想文档跑偏了,检索也会跟着偏。

5. 不是搭好就能用:RAG 系统常见的翻车场景与优化手段

5.1 场景一:PDF 表格和扫描件把解析管道直接击穿

做过企业知识库的人都懂,知识库里最让人头疼的文档格式不是 txt 也不是 Word,而是各种花式 PDF。有的 PDF 是扫描件,本质上是图片;有的 PDF 里表格嵌套表格,解析工具提取出来就是一堆乱码数字;有的 PDF 是两栏排版,文本流按列切得七零八落。

针对表格型文档,我在实践中摸索出的经验是:不要强求用通用解析库处理,而是要分而治之。表格区域单独抽取转成 Markdown 表格或 HTML 表格再向量化;扫描件先接一层 OCR,把图片里的文字提取出来。任何"一把梭"的解析方案在真实文档面前都不够用。

另外,有些工具提供版面分析能力,比如识别标题、段落、图片、表格区域后按结构输出。这类能力在复杂文档上的效果比纯文本抽取好很多,但也要留意它对中文文档的兼容性,经常需要先跑一个小样本测试再决定要不要全量启用。

5.2 场景二:检索出来的内容"貌似相关实则无用"

这种翻车方式更隐蔽。系统确实检索出了几条文本块,每个块看起来都和问题沾边,但加起来根本组织不成一个完整答案。比如用户问"产品的退换货期限是多久",召回的三条分别讲"质检标准""售后流程""质保范围",每条都不直接回答期限问题。

这类问题的根源通常出在切分粒度或向量质量上。切分粒度太大容易让精确定位的信息被大块上下文淹没;向量质量不够则无法精准区分"讨论过这个话题"和"回答了这个问题"的本质差别。处理手段有两个,一个是从数据侧出发,把常见 FAQ 型问题和答案单独建成 QA 对索引,确保"问题对问题"的检索匹配度最高;另一个是从流程侧出发,调高重排序模型对"直接回答型段落"的权重,或者干脆训练一个判断"这段文本是否直接回答问题"的小分类器。

5.3 场景三:知识库里根本没有答案,模型还在硬编

这可能是最容易被忽视的一个问题。RAG 系统的准确率不仅取决于"有答案时能不能找到",还取决于"没答案时能不能闭嘴"。很多团队只优化了前者,忽略了后者,导致模型在检索不到相关内容时依然凭借自身知识生成一段看似合理的回答,用户根本分不清这是来自知识库还是模型幻觉。

我的解决方案是在 prompt 体系里明确设计"拒答机制",同时把检索的相似度分数暴露出来,设定一个阈值。低于阈值的查询直接返回"知识库中暂未找到相关信息",不让它流到大模型生成环节。这种方法会牺牲一部分边缘 case 的召回,但换来的是整体可信度的大幅提升。在某些合规要求特别高的行业场景里,这种"宁可不说,不能说错"的策略几乎是标配。

另外,"不知道"本身也要分情况处理。有些查询是文档里没有信息,有些查询是用户问的问题本身就不受支持,比如让它读一段 PDF 里的图。系统设计时可以把这些问题按错误类型分类返回,而不是统一丢一句"我不知道",体验会好很多。

5.4 效果评估:不要凭感觉判断"好像变好了"

RAG 系统的调优很容易变成一场感觉游戏。今天改了切分策略感觉好了点,明天换了重排序模型又感觉好了点,但到底好了多少、哪些 case 变好了哪些变差了,完全说不清楚。要想系统性调优,还是得建立一套评估流程。

检索阶段最核心的指标是召回率和命中率:给定一组标准问答对,检查标准答案所在的文档是否出现在 Top-K 召回结果里。生成阶段则关注答案的忠实度(是否忠于检索到的资料而非模型自创)、相关性和完整性。实操中可以用大模型裁判(LLM-as-a-judge)对答案进行自动打分,再配合人工抽检校准。

一个值得长期建设的习惯是维护一个评测集:每个业务领域选 100 到 300 个有代表性的问答对,固定下来,每次改动链路后全量跑一遍评测集,对比指标变化。没有这个基准盘,你所谓的优化很可能只是对个别测试问题的过度拟合。

6. RAG、微调和长上下文的选型边界:到底该用哪个

6.1 三个方案的适用条件对比

我见过不少团队在技术选型上陷入"我全都要"的误区,其实这三个方案各有明确的适用边界。下面这张表是我在实际项目里总结出的判断框架:

维度RAG微调长上下文
知识更新频率高,随手可更新低,每次都要重训高,每次提交新内容进 prompt
可溯源性强,答案可关联文档弱,知识内化在参数里中,理论上可定位
计算成本低,只检索 Top-K 片断高,训练成本大线性增长,长上下文价格贵
单次回答延迟中,多了检索环节低,直接生成中,输入 token 多
适合场景企业知识库、实时信息问答风格迁移、领域术语、输出格式约束单次处理超大文档、长文档问答

核心判断标准我认为就一条:你的知识是否频繁变化、是否需要证明出处。如果是,RAG 是首选;如果知识相对固定、你更在意模型的表达风格和格式,那微调有它的价值;如果是一次性处理一份几百页的合同或者一本书,长上下文反而更直接。

6.2 实践中更常见的组合打法

实际项目里这三个方案常常是组合使用,而不是互斥的。一个比较成熟的知识库问答系统可以是这样的架构:用 RAG 承担知识检索和答案溯源,用微调让模型具备领域术语的表达能力和特定的输出结构,再用长上下文处理那些超出单块粒度的大文件分析需求。

组合的关键是分层。RAG 是主力,微调是辅助,长上下文是兜底。千万别一开始就把三个方案都堆上来,链路越复杂,排查问题的成本越高。我建议的路径是:先用最简单的 RAG 管线跑通一个最小闭环,把评估集建起来,再根据评估结果决定哪些环节需要微调、哪些场景需要长上下文兜底。技术方案是为业务效果服务的,不是为了显得先进。

6.3 下一步:从 Naive RAG 到 GraphRAG 与 Agent 化

RAG 演进到今天已经分出了明显的代际。Naive RAG 就是最基础的"检索-拼接-生成"流程。Advanced RAG 在前后加了查询改写、重排序、混合检索等优化,也就是前两章讲的内容。再往上的 Modular RAG 把检索、记忆、融合、验证等都模块化,允许自由组合。

在复杂关系型知识场景里,GraphRAG 是一个值得关注的方向。它把实体和关系抽取成图结构,能回答那些涉及实体关联的问题——比如"哪些供应商同时参与了项目和 A 和项目 B"。纯向量检索对这类问题基本无能为力,但图结构天然擅长表达这种多跳关系。

Agent 化 RAG 则是让模型具备主动决策能力:遇到复杂查询时,它可以自己决定拆成几个子问题、分几次检索、是否要调用外部工具。这个方向还比较前沿,但已经能看到"系统自己决定什么时候该查什么"的雏形。这些都是 RAG 体系外延的可能性,当你的基础链路足够扎实之后,再往这个方向探索会顺利很多。

做了这么多 RAG 项目,我个人最大的体会是:这套技术并不存在一个万能配方,文档格式、领域术语、查询分布差异,都会直接影响环节上的选择。如果你刚开始搭建,先从最朴素的全流程跑通一轮,再对照本文提到的几个优化点逐一实验,用评测集衡量效果,进步会快很多。而且每次改动尽量只动一个变量,不然出了问题你根本不知道是哪一步引起的。希望这篇拆解能帮你少走一些弯路,早点把大模型从"什么都懂"变成"懂该懂的"。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进