想象你走进一家餐厅,点一份“西湖醋鱼”。一位“记忆型厨师”从不看冰箱,张嘴就讲做法,可一旦这道菜他没见过,就会用相似的菜名拼凑出一个“听起来很像”的流程——就像传统大模型凭记忆硬编答案,编着编着就开始胡说八道。另一位“检索型店员”则相反,你问什么都给你抱来一堆菜谱,让你自己对着找——就像关键词匹配问答系统,答非所问还冷冰冰。而一家真正的老店会怎么做?店员先确认你的需求,去冰箱里找到最新鲜的草鱼和醋,再交给厨师按标准菜谱下锅,上桌时还告诉你这条鱼来自哪个供货商。 这就是检索增强生成(RAG)的日常:让问答系统先“查资料”再“作答”,让每一个答案都能在知识库里找到出处。RAG = 检索(Retrieval)+ 生成(Generation),它把传统问答“只会背”和“只会编”的两大毛病缝合在一起,用知识库作为答案的“食材仓库”,用检索作为“选料”环节,用大模型作为“掌勺”的厨师。这样一套组合拳,让系统告别张口就来的尴尬,也让你在企业里能交付“敢拍胸脯保证有依据”的问答产品。
对AI从业者而言,RAG是目前企业落地大模型最实用的技能之一:掌握它,你就能快速搭建出有据可查、可溯源、可兜底的问答系统,直接把大模型从“玩具”变成“生产力工具”。
目录
可视化演示
展示编码器将输入序列编码为上下文向量,解码器基于上下文逐步生成多步预测输出的完整流程。切换模式观察各阶段动态。
本节配有交互动效(进入视口后自动加载,约 3KB)。
'+captions.encode+'
1. RAG 核心概览:两条腿走路,才不胡说八道
想象一家餐厅里有一位“全凭记忆”的主厨。你问他某道私房菜怎么做,他张嘴就能说出一套流程;可一旦遇到没学过的菜,他就会用相似的菜名拼凑出一个“听起来很像”的做法。另一位“只会查菜单”的服务员则相反,你问什么都给你搬来一堆菜谱,让你自己对着找。这两类人正是传统问答系统的写照——一个天生话痨爱编造,一个只会翻书不会组织回答。
RAG 要做的事情,就是让“查资料”和“组织回答”配合起来。它的全称是 Retrieval-Augmented Generation,检索增强生成。整个链路可以拆成三步:先构建知识库索引,把外部文档切分、向量化后存起来;再检索,拿到用户问题后从库里找回最相关的几段文本;最后生成,把问题和这些文本片段一起交给大语言模型,让它在“有据可依”的前提下组织答案。
这个过程的本质可以用一个概率公式来理解:模型不再拍脑袋生成答案,而是先依赖一个检索分布挑选出证据文档,再在证据文档的条件下生成答案。
回头再看餐厅的比喻,步骤其实一一对应:$D_{ret}$ 是厨师从食材仓库里挑出的那一把草鱼和醋;$P(d \mid q)$ 是挑食材时的相关度判断——草鱼合适,猪肉不行;$P(a \mid q, d)$ 则是炉灶上真正做菜的过程。正因为每一步都有据可依,RAG 才得以告别“凭印象说话”的坏毛病。
- 候选证据集合$D_{ret}$ 表示从知识库中检索回来的文档集合,它是后续生成的全部“依据”,是 RAG 与纯生成最本质的差别。
- 检索条件概率$P(d \mid q)$ 量化了文档 d 与问题 q 的相关程度,检索器就是要让相关文档的这项概率尽量高。
- 生成条件概率$P(a \mid q, d)$ 表示模型在看到问题 q 和文档 d 后,生成答案 a 的整体概率,这就是最后“炒菜”的步骤。
2. 知识库索引:把冰箱整理成食材仓库
知识库里的原始文档就像刚从菜市场买回来的一整扇猪肉、一整条鱼。直接整块丢进冰箱,等要做菜时再找,既难化冻又占地方。聪明的厨师会先把肉切成丝、鱼片成片,按用途分装在保鲜盒里,贴好“排骨、里脊、鱼片”的标签摆上货架。这一步,对应 RAG 的“文档预处理”。
RAG 的索引构建包含三个动作:切分(Chunking)、向量化(Embedding)、建索引(Indexing)。切分把长文档切成若干语义完整的文本块;向量化把每个文本块变成一个固定长度的实数向量;建索引把这些向量组织成可快速检索的数据结构,同时保留来源、时间等元数据。
切分不是越细越好。块太大,向量语义被稀释,检索不精准;块太小,语义不完整,模型看了也难理解。实践中常采用“滑动窗口式”切分,相邻块之间保留一段重叠内容,避免关键句子恰好落在切口上被拦腰斩断。切分数量可以用下面的公式估算:
切分完成以后,每一小段文本都要经过嵌入模型变成向量。你可以把向量想象成每盒食材上的“营养标签”——它用一堆数字概括了文本的语义。所有标签汇集起来,就成了这个知识库的“货架地图”,接下来所有检索动作都在这张地图上完成。冰箱整理得越清楚,出菜就越快越准。
- 变量定义L 是文档总长度(token 数),C 是每块的目标大小,S 是相邻两块之间的重叠长度。
- 每次实际推进量因为上一块的末尾 S 个 token 会在下一块开头复用,所以每一块真正带来的“新内容”只有 C - S。
- 向上取整总长度按 Δ 分段,最后一段哪怕只有一个 token 也要单独成块,所以用天花板函数取整。
3. 查询处理:听懂顾客的“黑话”
老顾客走进店里,只说一句“老样子”,后厨根本不知道该做哪道菜。有经验的服务员不会直接把“老样子”传给后厨,而是先笑着确认:“还是上次的红烧肉加米饭吧?”——把省略的信息补全,再写单传给后厨。用户输入问题给问答系统时,也会出现大量类似的“黑话”。
在 RAG 中,这个“补全黑话”的环节叫 Query 处理 / Query 改写。它的目标是把用户原始问句改写成更适合检索的完整问题。常见操作包括:指代消解(“这个怎么弄”→“这个报销流程怎么弄”)、同义替换(“咋”→“怎么”)、口语转书面语(“多少钱”→“价格”),以及多轮场景下拼接对话历史。
改写之后,为了提升召回的容错率,还可以做“查询扩展”:不只检索一个句子,而是生成多个等价问法一起检索,再把结果合并排序。假设原始查询是 q,扩展后得到了一组候选表达 q1 到 qm,最终参与检索的查询集合就是:
别忘了,Query 改写看起来只是“多问一句”,却常常是检索质量的分水岭。就像服务员如果图省事直接把“老样子”传进后厨,端上来的菜多半不是顾客心里想的那道;而一句确认,就能让后续所有环节少走弯路。
- 原始查询q 是用户输入的原话,通常很短,甚至包含指代和省略,直接拿去检索效果差。
- 扩展查询集q1 到 qm 是通过改写、同义替换等方式生成的一个或多个等价问法,用来弥补原始表达的信息缺失。
- 联合检索Q' 是原句和扩展句的并集,检索时对 Q' 中每个元素分别查找,最后去重合并。
4. 检索召回:精准找到对的食材
食材已经分装好、标签已经贴好,现在顾客点了“鱼香肉丝”,厨师要做的就是从一冰柜的料理盒里快速找到木耳、肉丝和泡椒。他不会把整个冰柜都抱到灶台上,而是扫一眼标签,挑出最相关的那两三盒。
检索环节正是干这件事。系统会把改写后的查询也变成一个向量,然后在知识库向量集合中,找出与查询向量距离最近、语义最相似的前 K 个文本片段。衡量两个向量相似程度最常用的指标是余弦相似度:
实际工业系统中,单纯靠向量检索还不够。因为向量适合捕捉“语义相近”,却容易漏掉“字面上必须一致”的关键信息,比如工单号、邮箱、型号。所以很多系统采用混合检索:向量检索 + 关键词检索并行,再把两路结果合并,经过重排序(Rerank)模型精打分,最终选出 TopK 片段送入下游。
这一步决定了“灶台上放什么”。如果厨师抱来的两盒食材跟鱼香肉丝毫无关系,后面再厉害的炒功也白搭。检索召回就是 RAG 的“选料关”,这一关守不住,生成阶段再努力也只能“巧妇难为无米之炊”。
- 点积$\mathbf{q} \cdot \mathbf{v}$ 把两个向量对应位置的数字相乘再求和,反映两个方向上的共同分量。
- 模长归一化$\|\mathbf{q}\|$ 和 $\|\mathbf{v}\|$ 是两个向量的长度,除以它们的乘积可以消除文本长短对分数的影响。
- 相似度取值余弦值落在 [-1,1],对非负向量则落在 [0,1]。值越大,语义越接近,越应该被召回。
5. 生成融合:炒出一盘有依据的菜
食材备齐了,接下来就是真正上灶炒菜。但厨师不会闭着眼睛乱炒——他得看一眼标准菜谱:先爆香蒜末,再下肉丝,最后放木耳和泡椒,大火翻炒两分钟。菜谱和食材按顺序摆在一起,才能做出一道形神兼备的鱼香肉丝。
在 RAG 的生成阶段,“菜谱”就是 Prompt 模板,“食材”就是上一步检索到的 TopK 片段。系统把这些片段和用户问题一起组装进 Prompt,再交给大语言模型生成答案。为了让模型更专注地使用证据,Prompt 通常会明确声明:“请仅依据以下资料回答问题”。检索片段通常带编号,模型回答时可直接引用。
从建模角度看,生成器是一个条件语言模型。它不再单独依赖问题 q,而是把检索集合 $D_{ret}$ 作为额外的条件,每一步生成都同时参考问题、已有输出和证据文档:
同样一筐食材,有没有菜谱、有没有约束,做出来的菜完全不同。生成融合就是在“开放创作”和“照本宣科”之间找到了中间道路:既给模型充分的连接语义的能力,又用检索证据和 Prompt 规则把它的想象力牢牢拴在事实的缰绳上。
- 序列分解答案 a 被分解为 T 个 token,整体概率等于每个 token 在给定上文条件下的概率连乘。
- 自回归条件$a_{<t}$ 表示已经生成的前 t-1 个 token,模型每步只在前文基础上继续,保证句子连贯。
- 证据贯穿全程q 和 $D_{ret}$ 出现在每一步的条件中,确保答案始终贴着问题和证据走,而不是天马行空。
6. 质量保障:没把握就别硬上,还要说明出处
再老练的厨师也有打烊前发现冰柜空空如也的时候。这时候如果你催他“随便炒个菜应付下”,他可能会端出一盘不伦不类的东西。一位负责任的厨师会直接告诉你:“这道菜今天做不了,要不您换个菜?”——坦诚,反而赢了顾客的信任。
问答系统也一样。模型并不总是能检索到高质量证据,即使检索到了,也可能因为证据互相矛盾而拿不准。生产级 RAG 必须在生成之后加一道“质检关”:对无答案场景、低置信度场景做出明确处置,而不是硬编一个答案糊弄用户。常见的做法是给答案打一个置信度分数,再与预设阈值比较。
决策逻辑可以用一个分段函数来表达:
除了置信度,生产系统还会做引用溯源:模型在答案的每个关键句后面标注“来源[1]”“来源[2]”,对应到具体的检索片段。这就像菜单上写下每道菜的食材来自哪个供货商——万一顾客吃了不舒服,可以立刻追溯到原料批次。质检关加上溯源机制,问答系统才算真正“对说的话负责”。
- 置信度函数$\mathrm{Conf}(a \mid q)$ 是系统对“答案 a 是问题 q 的正确回答”的信心,可由检索分数、生成概率、验证模型综合得到。
- 阈值比较θ 是人工设定的门槛。门槛过高会答得少而精,门槛过低会答得多而糙,需要根据业务场景权衡。
- 双分支决策达标输出答案;不达标进入兜底流程——回复“没有找到可靠资料”、建议用户补充条件,或者转人工客服。
AI 应用场景
企业客服机器人(FAQ 问答)
阶段:问答推理与答案生成
解决的问题:传统 FAQ 机器人靠关键词匹配,用户换个问法(“运费谁出”“包邮吗”“邮费怎么算”)就命中不同规则,甚至给出互相矛盾的答案。RAG 直接把《售后政策》《物流规则》等文档切块入库,无论哪种问法都能检索到同一条规则,并附上出处。
具体案例:某电商平台在客服机器人中接入 RAG 后,用户问“退货谁承担运费”,机器人返回“退货由买家承担运费,质量问题的运费由商家承担”,并附上政策原文片段链接;用户追问“你是说邮费吗”,也有模块通过同义改写检索到同一条文档。上线后,客服转人工率下降了约 40%。
企业内部知识库问答(员工助手)
阶段:检索召回与内容生成
解决的问题:员工问“年假有几天”“VPN 怎么登录”,答案分散在 HR 制度、IT 服务指南等多份文档里。AI 工程师用 RAG 把全部内部文档切块入库,用自然语言问答取代原来的行政答疑群,减少重复提问。
具体案例:某科技公司为员工搭建内部问答助手,知识库覆盖行政、财务、IT 制度。员工问“VPN 怎么登录”,助手检索到 IT 指引片段,直接给出登录地址和密码修改流程,并标注“来源:IT服务指南 2025版”。员工点击来源即可核验,极大降低了对 IT 工单的依赖。
Python 演示
import mathimport refrom collections import Counter class MiniRAG: """极简RAG演示: 分块 -> 向量化 -> 检索 -> 组装带引用答案""" def __init__(self, docs, chunk_size=30, overlap=5): self.chunks = [] self.vectors = [] self.chunk_size = chunk_size self.overlap = overlap for doc in docs: self._add_document(doc) def _tokenize(self, text): return re.findall(r'[\u4e00-\u9fa5]|[a-z0-9]+', text.lower()) def _char_bigrams(self, text): chars = re.findall(r'[\u4e00-\u9fa5]', text) return [chars[i] + chars[i+1] for i in range(len(chars) - 1)] def _add_document(self, doc): tokens = self._tokenize(doc) start = 0 while start < len(tokens): end = min(start + self.chunk_size, len(tokens)) chunk_text = ''.join(tokens[start:end]) self.chunks.append(chunk_text) self.vectors.append(self._vectorize(chunk_text)) if end == len(tokens): break start = end - self.overlap if start >= end: break def _vectorize(self, text): vec = Counter(self._tokenize(text)) for bg in self._char_bigrams(text): vec[bg] += 1 return vec def _cosine(self, vec_a, vec_b): common = set(vec_a.keys()) & set(vec_b.keys()) dot = sum(vec_a[k] * vec_b[k] for k in common) norm_a = math.sqrt(sum(v * v for v in vec_a.values())) norm_b = math.sqrt(sum(v * v for v in vec_b.values())) if norm_a == 0 or norm_b == 0: return 0.0 return dot / (norm_a * norm_b) def _rewrite(self, query): synonyms = { '咋': '怎么', '咋样': '怎么', '多少钱': '价格', '费用': '价格', '报销': '报销流程', } for k, v in synonyms.items(): query = query.replace(k, v) return query def retrieve(self, query, top_k=2): q_vec = self._vectorize(self._rewrite(query)) scored = [] for i, vec in enumerate(self.vectors): scored.append((self._cosine(q_vec, vec), i)) scored.sort(key=lambda x: -x[0]) return [(self.chunks[i], score) for score, i in scored[:top_k]] def answer(self, query): hits = self.retrieve(query) if not hits or hits[0][1] < 0.05: return '抱歉, 知识库中没有找到可靠信息.' segments = [f'[{i+1}] {chunk}' for i, (chunk, score) in enumerate(hits)] context = '\n'.join(segments) main = hits[0][0] return f'根据资料: {main} (来源: {context})' if __name__ == '__main__': docs = [ '公司VPN登录地址是vpn.example.com, 首次登录需要修改初始密码, 离职后账号自动注销.', '员工报销需在OA系统提交申请, 发票拍照上传, 经部门审批后三个工作日内到账.', '申请年假需要提前三天在OA提交, 每年10天, 未休完的可以结转至次年3月.', '公司内部培训每月最后一个周五举行, 报名请在企业微信联系培训专员.', ] rag = MiniRAG(docs) test_queries = ['报销怎么弄?', '年假有多少天?', 'VPN咋登录?', '这周五有会议室吗?'] for q in test_queries: print('问:', q) print('答:', rag.answer(q)) print()
chunk_text = ''.join(tokens[start:end])按固定窗口切分文本,overlap 让上下文衔接不中断。self.vectors.append(self._vectorize(chunk_text))每个块都被转成词频向量,模拟真实系统中的嵌入向量。vec[bg] += 1把中文相邻二字组合加入特征,增强短语级语义表达。dot = sum(vec_a[k] * vec_b[k] for k in common)只计算两个向量共同出现的词,加速余弦相似度计算。return dot / (norm_a * norm_b)归一化点积,得到落在 [0,1] 的相似度分数。query = query.replace(k, v)同义词改写,把“咋”“多少钱”等口语替换为更完整的表达。scored.sort(key=lambda x: -x[0])按相似度从高到低排序,选出 TopK 片段。if not hits or hits[0][1] < 0.05:置信度门槛,检索分数过低时拒绝作答。return f'根据资料: {main} (来源: {context})'组装答案并附带来源片段编号,实现引用溯源。
常见误区
误区感觉 RAG 没啥技术含量,不就是把知识库文本塞到 prompt 前面给模型看吗?随便做做就能减少幻觉。
正解RAG 的成败取决于“能否检索到最相关的片段”。如果检索回来的是无关文本,模型反而会被带偏,比不检索更糟。就像给厨师拿错食材,再好的菜谱也做不出好菜。切分策略、向量质量、重排序环环相扣,缺一不可。
为何常见:最终效果看起来都是“模型看到了额外文本”,让人容易忽略中间的检索工程。
误区检索时把整篇文档甚至整本书都丢给模型,以为上下文越长信息越多,答案越准。
正解过长上下文会带来两个问题:一是大模型注意力容易分散,关键信息被淹没(即“Lost in the Middle”现象);二是成本与延迟上升。RAG 需要把文档切成合理大小的语义块,只送入相关信息。就像下锅的菜要切丁切片,一整棵白菜直接扔进锅里很难炒熟。
为何常见:人们本能地认为“信息越多越好”,忽略了模型的信息处理瓶颈。
误区部署了 RAG 之后,模型应该百分之百正确,再也不会胡说八道。
正解RAG 能大幅减少由“记忆缺失”导致的幻觉,但模型仍可能在“有证据但理解错了”甚至“检索到相关但错误使用”时产生错误答案。它不能完全消除幻觉,仍然需要置信度判断、答案校验和人工兜底。就像厨师有了正确的食材,仍可能放错调料。
为何常见:宣传中常说 RAG“解决幻觉”,让人误以为彻底解决了,实际是“缓解”而非“杜绝”。
误区检索时向量相似度分数最高的块,内容就是最权威最正确的。
正解向量相似度衡量的是语义相关性,不是事实正确性。一段相关但过时或错误的文本也可以有很高的相似度。所以生产系统还要结合更新时间、文档来源、人工标注等做质量过滤。就像“看起来像那么回事”的资料不等于“正确”的资料。
为何常见:把“相似”和“正确”混为一谈是常见的直觉误区。
误区用户怎么问就怎么查,反正向量检索能理解语义,改写纯属多余。
正解用户口语常常包含指代、省略和同义表达,比如“这个怎么弄”“上次那个”,底层向量模型不一定能处理好。通过改写补全能显著提升召回率,尤其在专有名词、缩写场景。“老顾客说老样子,服务员也要确认一下”这句确认,往往就决定了检索的命中质量。
为何常见:向量检索的“语义理解”能力被神化了,实际对复杂指代和超短 query 依然吃力。
面试题
Q1. RAG 中“R”和“G”分别代表什么?
- A.Retrieval 和 Generation← 正确答案
- B.Random 和 Generation
- C.Ranking 和 Grounding
- D.Retrieval 和 Gradient
答案:A。RAG 的全称是 Retrieval-Augmented Generation,即检索增强生成。A 正确。B 的 Random、C 的 Ranking、D 的 Gradient 都不是 RAG 的标准构成。
Q2. 为什么要对知识库文档做分块处理?
- A.减少模型上下文的负担
- B.让检索片段更符合语义单元← 正确答案
- C.让知识库存储成本升高
- D.让索引查询耗时显著增加
答案:B。分块(Chunking)能让检索命中到更精准的语义单元,同时避免把整篇长文档塞进 Prompt 造成注意力稀释。A 是分块带来的好处之一,B 更能体现“语义单元”这一核心理由,C 和 D 都是负面效果,不是目的。
Q3. 下列哪一项是 Query 改写要解决的问题?
- A.文档切分的粒度过小
- B.用户口语的指代与省略← 正确答案
- C.索引的存储空间不足
- D.模型参数量过于庞大
答案:B。用户会问“这个怎么弄”“那个流程呢”,这些指代和省略若不补全,检索很难命中。Query 改写负责把这类问题补成完整可检索的句子。A/C/D 分别属于分块、存储和模型部署问题,与用户表达无关。
Q4. 余弦相似度的取值范围是?
- A.[-1, 1]← 正确答案
- B.[0, 1]
- C.[-∞, +∞]
- D.[0, +∞]
答案:A。余弦相似度是两个向量夹角的余弦值,天然落在 [-1,1];若向量元素非负(常见于嵌入向量),则实际落在 [0,1]。选项 A 最准确,B 只在非负向量时成立,C/D 均错误。
Q5. RAG 生成阶段中,检索片段主要融入到哪里?
- A.模型的损失函数约束
- B.Prompt 的上下文区域← 正确答案
- C.大规模训练数据集
- D.模型底层的词表
答案:B。生成阶段的做法是将检索到的 TopK 片段放进 Prompt 上下文,让模型参考这些证据作答。A 属于训练优化手段,C 是预训练阶段的事,D 是词表设计,都不对。
Q6. 当检索置信度低于阈值时,生产系统通常应该?
- A.随便编一个答案
- B.拒绝回答并引导澄清← 正确答案
- C.直接返回空字符串
- D.重复一遍用户问题
答案:B。低置信度场景应拒答或请求澄清,避免“硬答”造成的幻觉。A 正是幻觉的源头,C 对用户体验伤害大,D 对解决问题毫无帮助。
Q7. 关于 RAG 中的答案引用溯源,下列说法正确的是?
- A.引用会降低答案可信度
- B.只需在最后列出文献即可
- C.答案应标注具体检索片段← 正确答案
- D.引用只对开发者有意义
答案:C。成熟的 RAG 系统会在答案中标注来源片段编号,用户可点击核验,提升可信度与可排查性。A 说法相反,B 只在文末列文献远不够,D 忽视了用户核验的价值。
Q8. 以下哪些策略能提升 RAG 的检索召回效果?
- A.Query改写补全指代省略← 正确答案
- B.混合检索兼顾语义与字面← 正确答案
- C.对TopK结果进行重排序← 正确答案
- D.将所有停用词全部删除
答案:ABC。A 改写查询可以提升召回,B 混合检索(向量+关键词)能兼顾语义与字面匹配,C 重排序能精筛 TopK。D 停用词删除并非提升召回的标准策略,过度删除反而可能损失关键信息。
Q9. 检索片段进入 Prompt 时,应注意哪些问题?
- A.控制片段长度防止注意力稀释← 正确答案
- B.给片段标注编号方便引用← 正确答案
- C.放入不相关的内容扩充上下文
- D.在提示词中指示基于片段作答← 正确答案
答案:ABD。片段长度要适当,过长会丢失重点(A);给片段编号让模型引用更清楚(B);D 让模型“只能根据资料回答”可抑制发挥。C 放入不相关内容会把模型带偏,是典型错误。
Q10. 关于 RAG 减少幻觉的原理,正确的是?
- A.生成过程绑定外部证据← 正确答案
- B.检索结果也可能带来错误← 正确答案
- C.已经能完全杜绝幻觉
- D.拒答能降低硬答风险← 正确答案
答案:ABD。RAG 让模型从“背知识”变成“查资料后作答”,大幅减少记忆型幻觉(A);但检索结果本身可能过时或有误,从而带来新错误(B),所以不能完全杜绝幻觉(C 错误);无答案拒答策略能降低“硬答”带来的风险(D)。