前阵子一个做内部知识库问答项目的朋友跟我吐槽:他给公司知识库接了大模型,结果业务同事问“报销需要哪些材料”,模型一本正经地列出了“需提供房产证明、婚姻状况说明”这类完全不存在的内容。业务方当场就炸了,差点把项目叫停。那一刻我突然意识到,RAG(检索增强生成)这个词虽然已经被讲烂了,但很多人其实没真正想明白它到底在解决什么问题,更没搞懂为什么“检索”这个环节比“生成”更决定效果上限。
这篇文章我打算从问题根源开始,把 RAG 从原理到实践完整拆一遍,包含可落地的选型建议、代码实现、检索优化思路,以及进阶形态的选型判断。适合三类人看:一是刚转大模型应用开发、被“幻觉”折磨的新人;二是负责给企业做知识库问答、搜索增强的工程师;三是技术负责人,需要评估 RAG 方案到底值不值得投入、边界在哪。
1. 拆解“胡说八道”:大模型幻觉的根源与 RAG 的应对逻辑
1.1 “幻觉”的本质是这个模型在预测概率,不是在查证事实
很多人把大模型想成一台超级数据库,以为它能像搜索引擎一样命中事实后返回答案。这是个非常深的误解。语言模型的核心任务是给定前文预测下一个词的概率分布,它的训练目标函数从来就不是“保证答案和外部事实一致”,而是“让生成的文本在语言分布上最自然”。
这意味着当用户问到一个训练数据里没有覆盖、或者覆盖很少的细节问题时,模型会走一条高概率路径,把符合语言习惯但毫无事实依据的内容补全出来。这就是幻觉的根源,它不是偶发错误,而是概率机制的必然结果。举个例子,训练数据里如果根本没有你公司的人事制度,模型就只能根据“通常公司会有报销制度”这类泛化知识来编,编出来自然五花八门。
知识截止日期也加剧了这个问题。模型训练完成后知识就冻结了,今年新发布的政策、新调整的流程、刚上线的产品参数,它根本不知道。很多人把企业知识库问答做失败,就是因为拿一个截止日期在去年的模型,去回答今年才更新的制度文档,不胡说才怪。
1.2 RAG 为什么有效:把闭卷考试变成开卷考试
RAG 的思路其实非常朴素:既然模型自己记不住事实,那就在它每次回答之前先查资料,然后把查到的资料塞进上下文里,让它基于资料作答。
整个过程的本质,是把语言模型从一个“记忆能力有限的闭卷考生”,改造成一个“可以随时翻书的开卷考生”。它不需要记住新知识,只需要理解查到的材料,然后组织语言输出。这带来几个关键优势:
- 事实来源可追溯。模型被要求只依据上下文作答,即使它又习惯性编造,你也能看出来它没忠实于材料,便于拦截。
- 知识实时可更新。知识库里的文档变了,直接更新索引,不需要重新训练模型。
- 领域知识注入成本低。给企业做个问答,只需要把内部文档切分、向量化,模型本身的参数不需要动。
1.3 RAG 的边界:它不解决所有问题
我得提前泼盆冷水。RAG 不是银弹,它解决的是“私域知识缺乏”和“知识过期”带来的幻觉问题。但如果幻觉来自模型推理能力不足、或者问题本身需要强逻辑链推理,RAG 的效果就会打折。比如你问一道复杂的数学应用题,检索回来的材料可能根本包含不了完整的推导线索,模型该算错还是算错。
还有一类情况是检索本身就没做好,导致返回的上下文根本不含关键答案。这种问题不是 RAG 方案的问题,而是检索链路的问题,后面我会花大篇幅讲。先记住一句结论:RAG 系统的效果下限由检索决定,上限由生成模型决定。绝大多数项目做得烂,问题都出在检索,不在生成。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. RAG 的三阶段工作流:索引、检索、生成各自在干嘛
2.1 索引阶段:把“文档”变成“可检索的向量”
RAG 的第一步就是把知识库文档变成机器能快速查找的索引结构。很多人第一次接触时会困惑:为什么需要向量化,直接用关键词搜不行吗?
关键词搜索(BM25 这类)确实可以,但它有一个致命问题:处理不了“语义匹配”。用户问“怎么报销差旅费”,如果文档里写的是“出差产生的交通住宿费用如何申请补贴”,关键词几乎对不上,传统检索就失效了。向量检索解决的就是这个语义鸿沟。
向量化做的事情,简单说就是通过 Embedding 模型把一段文本映射成一个几百维到几千维的浮点数数组,这个数组就是向量。向量设计上的关键特性是:语义相近的文本,向量在空间里的距离也相近。于是检索问题就变成了数学问题,给定一个查询向量,在向量库里找距离最近的 K 个向量。
这里的“距离”有几种常见算法,我在实际项目里最常用的是余弦相似度和内积。余弦相似度关注方向一致性,对向量的绝对长度不敏感,适合大多数文本检索场景;内积则受向量长度影响更大,一般在向量做过归一化处理后使用。选择哪个,往往取决于 Embedding 模型本身的推荐配置和向量库的索引类型。
2.2 检索阶段:把“用户问题”变成“查询条件”并召回
检索阶段看起来简单:把用户问题也用同一个 Embedding 模型向量化,然后查向量库。这里有两个非常容易踩坑的点。
第一个坑是,Embedding 模型不一致。用户问题用的模型、文档切分用的模型、向量库索引时用的模型,三者必须是同一个,否则向量空间都不对齐,检索结果就是噪声。哪怕是一个模型的不同版本,都会导致向量分布漂移。
第二个坑是,Top-K 的选择直接决定生成质量。K 太小,召回信息不足;K 太大,上下文充满无关碎片,模型反而被干扰。我见过很多人拍脑袋设个 K=4 就不管了,结果项目上线后效果不稳定。后面讲评估时我会说我自己的调法。
2.3 生成阶段:把“检索结果和用户问题”一起交给 LLM
生成阶段不是简单地把检索结果拼在用户问题前面就完事。Prompt 里必须明确约束模型的行为规则。我在工程上至少会包含三层指令:
- 角色与任务设定:告诉模型你是一个基于给定资料回答问题的助手。
- 忠实性约束:明确要求只依据资料内容作答,资料中没有的内容要直接说不知道,不得编造。
- 格式要求:按用户需要的格式输出,比如列表、段落、JSON。
一个典型的生成 Prompt 模板长这
