你有没有遇到过这种情况:在某个主流大模型上问一个并不复杂的事实类问题,它给出了一段逻辑完整、语气笃定的回答,细节丰富到时间地点人名全都有,结果你去查手里的资料,发现关键信息全是编的。更麻烦的是,它自己完全意识不到这一点。这就是圈内常说的AI幻觉,或者说“一本正经地胡说八道”。今天想聊的解决办法,叫RAG,也就是检索增强生成。这篇文章会用尽量通俗的方式拆清楚:大模型为什么会有这个毛病,RAG到底是靠什么逻辑把问题治掉的,以及当你自己动手给系统挂上RAG时,真正值得注意的那些坑。适合正在做大模型应用、做知识库问答,或者只是被幻觉坑过、想搞明白背后原理的开发者。
1. 先搞清楚:大模型为什么“一本正经地胡说八道”
1.1 不是bug,是训练目标决定的
很多人第一次遇到幻觉时,第一反应是“这模型是不是坏了”。其实不是,这是大模型的底层训练目标决定的必然结果。
大模型训练时干的事情,本质上是一个“接龙游戏”:给它一段上文,让它预测下一个词概率最高的是什么。从技术角度说,它学到的不是“人类知识体系”,而是“文本之间的统计规律”。它追求的是回答读起来流畅、自然、像人话,事实性只是这项工程中捎带出来的副产品。
举个我经常拿来举例的现象:你问一个模型“某本书里有一章讲的是什么”,哪怕这章根本不存在,模型也常常不会说“没有这一章”,而是顺着你的问题直接把它编出来。原因就是训练语料里充满了“用户问什么,答案就给什么”的配对,模型学到的是“被问了就要给个像样的答案”,而不是“不知道时应该承认不知道”。承认无知的样本太少,它自然很难学会说“我不知道”。
1.2 知识是有“保质期”的
关于幻觉还有一个常见误解:以为模型知识是全的。实际上,任何一个预训练模型都有知识截止时间,它只认识训练数据截止日之前的信息。你问它截止日期之后的新产品、新规则、新事件,它根本没有对应的记忆,只能靠已有的模式去“猜测一个最像的答案”。
哪怕问的是它训练期内的旧知识,也一样会有偏差。道理很简单:互联网语料中,同一件事有无数种说法,有的正确有的错误,模型学到的是这些说法的“混合平均”。某个冷门信息如果在语料里就出现过一两次,那它的记忆就非常模糊,被问到时它只能调用相似的记忆碎片拼凑。
更扎心的是,模型对自己“知道什么、不知道什么”完全没有感知。它内部没有一个开关告诉你“这条我不会”。它给出的每条回答都裹着同样的自信语气,这也是幻觉最难防的地方。
1.3 参数压缩:整个互联网塞不进一块硬盘
还有一个非常关键的背景,就是参数化知识的压缩损耗。大模型的能力来自海量训练数据,但它的参数总量是有限的。训练过程就好像把一整本百科全书压缩成一张摘要卡片,主干可以保留,细节注定丢失。
当你问到某个细枝末节的偏门话题,模型命中了模糊的记忆区域,它能想起来的大概率不是原文,而是“这类问题通常长什么样”。于是它按照统计规律补全细节,编出一个结构合理、但内容存疑的答案。
我把这个状态比作“一个记忆力一般的人被迫复述一个只听过一遍的故事”:他能把情节、氛围、人物关系编得像模像样,但每一处细节都不可信。这就是为什么幻觉问题不能靠“再训大一点”彻底解决,因为压缩过程本身就在丢信息,出路在于改变它回答问题时的信息来源。
2. RAG是什么:给AI递一张“开卷考试”的小抄
2.1 一个最朴素的生活类比
RAG,全称是检索增强生成,核心思路一句话就能说清:在模型回答之前,先从外部知识库里把相关的资料检索出来,拼到它的输入里,让它基于这些现成资料来回答。
我最喜欢用开卷考试来打比方。裸奔状态的大模型,就像一个闭卷考试的学生,一切靠脑子里记的那些东西,记不住就硬编。挂了RAG之后,相当于允许它开卷考试:旁边放着一本参考资料,而且给它配了一个翻书很快的助手,它先查到相关内容,再决定怎么组织答案。
还要纠正一个常见误解:RAG不是让AI“上网搜”。上网搜到的是全网通用信息,而RAG真正解决的是“你的私有数据、你的最新资料、你的业务文档”如何被模型用起来的问题。你把自己的资料库建好索引,模型才可能在回答时引用它们。
2.2 RAG的工作流程拆解
RAG的完整链路可以拆成四步:索引、检索、增强、生成。
第一步,索引。你把一堆原始文档,比如PDF、Word、Markdown,先做解析清洗,再按策略切成一个个小片段,然后用嵌入模型把每个片段转换成一个向量,也就是一串数字,最后把这些向量连同原文一起存进向量数据库。
第二步,检索。用户问出一个问题,你先用同一个嵌入模型把这个问题也变成向量,然后去向量数据库里做相似度搜索。计算机通过计算向量之间的距离,找出跟你这个问题“语义最接近”的若干片段,这一步一般取前几个结果,叫Top-K。
第三步,增强。把检索到的这些片段按顺序拼进提示词,作为给模型的参考材料。这一步骤的核心是让模型“看到”资料,而不只是凭记忆发挥。
第四步,生成。大模型读取完整提示词,根据参考材料组织最终答案。因为答案的素材来源被限定在了这些片段里,它“自由发挥”的空间就被压缩了。
之所以叫“检索增强生成”,是因为生成这件事仍然由大模型完成,检索只是增强它的事实依据。模型还是一个文笔不错但记忆不可靠的写手,RAG的作用是给它递上素材,让它的“写作”有了可靠的依据。
2.3 为什么是RAG而不是微调
很多人会问:出问题了为什么不直接微调?微调确实能改变模型行为,但和RAG是两条不同的路。
微调是在更新模型自身的权重,相当于让模型“重新学习”一部分知识。它有很贵的前提:要准备高质量的训练集,要花算力跑训练,训练完知识还是沉淀在参数里,下次知识更新了又得重新训一遍,而且你很难追溯答案是“从哪条资料来的”。
RAG不动模型权重,它改变的是每次问答时给模型的输入。它有三个很突出的优势:成本低,不用训练;知识更新即换即用,改资料库就行;每个回答都能追溯到具体的资料片段,方便查验。
我见过不少团队一开始就上微调,最后发现知识类的需求根本喂不满。做企业内部知识库问答、产品文档问答这类场景,优先上RAG是性价比最高的选择。微调可以作为补充手段,等RAG把“事实来源”问题解决了,再利用微调去调模型的语气、风格和指令遵循能力。
3. 什么样的问题适合用RAG治
3.1 RAG最擅长解决的场景
先说说RAG的舒适区。我做过、也见过不少典型场景,基本都落在下面这四类:
企业内部知识库问答。公司里几十万字的制度文档、操作手册、流程文件,模型在预训练时绝对没看过。你把文档灌进RAG,员工就能用自然语言提问,它能从文档中找到对应条款并给出出处。
客服问答。售后政策、产品说明书、FAQ这类内容变化快,传统FAQ页面一多用户就找不到,RAG可以直接把最相关的几条解答找出来,再让模型整理成口语化的回答。
文档审查和合同问答。把合同模板或历史案例建库,问某个条款怎么约定的、某类事故是怎么处理的,RAG能做到有据可依,这比让模型凭感觉回答可靠太多。
时效性内容问答。政策、行情、行业规范等经常更新的信息,直接依赖预训练知识必然过时,RAG让你可以把最新的官方文件放进去,回答就能跟上节奏。
这些场景的共同点是:知识是结构化的、范围可控的、有明确标准答案的,并且你需要“答案能溯源”。
3.2 别把RAG当万能药
RAG不是万能解法,以下几种情况它帮不上什么忙。
创造性的内容生成不合适。你让AI写一篇短篇小说、拟一个创意广告文案,这时候给它绑一堆资料反而碍事,它应该在更大空间里发散,而不是被限定在几个片段里。
对深度推理能力的要求高,也不是RAG擅长的方向。数学证明、复杂的逻辑链、需要多步推理的物理题,RAG只是提供素材,模型自身的推理能力不够时,给再多资料也推不对。
代码生成、风格转换这类任务,跟知识库关系也不大。它更多依赖模型自身的能力和对指令的理解,RAG引入的资料反而可能造成干扰。
一句话,RAG适合治“没知识”的病,不适合治“能力弱”的病。判断标准很简单:如果这个问题的正确答案确实在你的资料库里能找到,那你上RAG一定有帮助;如果答案根本不在库里,或者这个问题需要模型具备更强的思维能力,那RAG也救不了。
3.3 什么时候应该倒回去考虑微调
有些情况下,微调比RAG更合适,或者说两者应该结合使用。我梳理一下我遇到过的情况。
输出风格要高度统一时。比如客服机器人的回复语气要有固定模板,带问候语、带编号、带情绪安抚节奏,这种稳定性靠提示词控制不够,微调可以让模型学会这个固定风格。
需要模型掌握一种稳定的领域能力时。比如某类医疗术语的翻译规则、某种代码风格的生成规范,这些不是“查资料”能解决的,而是模型要内化成能力。
资料检索怎么调都救不回来的场景。有时候你会发现,哪怕参考材料就在面前,小模型就是没法从中准确摘取并组织答案。这时与其纠结检索,不如换一个能力更强的大模型,或者对它做任务专项微调。
实操中最常见的高效组合是:先用微调让模型理解你的业务语言和指令格式,再用RAG给它不断添加最新的知识。这样分工清晰,效果往往比单一方案好得多。
4. 从零搭一套:一次完整的RAG落地实录
4.1 整体选型:核心组件怎么定
RAG系统的技术栈其实没有想象中复杂,核心由五块组成:文档解析、切块、嵌入模型、向量数据库、大模型。
文档解析这一层,要处理PDF、Word、Markdown、扫描件。PDF要区分文本型和扫描型,扫描型还需要OCR,这一步容易被人忽略,但解析质量直接决定后续效果。我的经验是先把所有文档统一转成纯文本或Markdown,清洗掉页眉页脚、目录、无关表格。
嵌入模型的选择有三个关键点:第一,中文效果要重点测,不能只看榜单分数;第二,向量维度会影响内存和检索速度,维度越高精度通常越好,但成本也高;第三,优先选轻量的开源模型。做嵌入不需要很大的模型,效果好的模型,在普通GPU上都能跑得动。
向量数据库的选型跟你数据量有关。数据量在几十万条片段以内,用本地文件型或内存型就够了,没必要一开始就上分布式集群。数据量巨大、并发高的时候,再考虑分布式的方案。
大模型的选择上,如果你追求效果,优先看指令遵循能力强的模型;如果数据不能出网,要考虑本地部署;如果成本受限,可以考虑用更小的模型,但要做好它提取信息能力较弱的准备。
市面上有一些编排框架能把这几步串起来,但你并不一定需要依赖它们。先理解了原理,再用代码把流程写清楚,反而更容易定位问题。
4.2 预处理:文档切块是第一个大坑
切块是RAG里最容易被低估的环节,也是我踩得最深的一步。切块的核心矛盾在于:块太大,噪声多,相似度被稀释,检索精度下降;块太小,语义不完整,一个完整知识点被切碎,检索经常漏掉。
这里给出我实测过比较稳的三种策略:
按结构切分。如果文档有清晰的标题层级,比如Markdown的章节、Word的一级二级标题,优先按标题结构切。把每一节作为一块,保留标题作为上下文。这种方式的检索效果通常比固定长度切好很多。
固定长度加重叠窗口。对于没有结构的文档,按固定字数切分,并让相邻块之间有大约十分之一到八分之一的重叠,避免关键信息恰好落在切缝上。中文场景下,我常用每块500到800字,重叠100字左右作为起点。
语义切分。用段落或句子边界去切,尽量保证一个块内是一个完整话题。实现上可以先用简单的句号、换行做粗切,再按长度合并。这种方式效果接近按结构切分,且不依赖文档格式。
切完之后一定要做验证。我的做法是每次切完都挑20个高频问题,人工检查“每个问题的标准答案,是否完整落进了某个块里”。如果经常性找不到,问题多半出现在切块上,而不是后面的检索。
4.3 检索与生成:给模型写好“考试规则”
检索层面的两个关键参数是Top-K和相似度阈值。
Top-K决定每次召回多少个片段给模型。K值太小容易漏掉正确答案,K值太大又会引入大量噪声。我一般从5开始试,问题涉及多个方面时放宽到10。
相似度阈值用来过滤明显无关的片段。不同嵌入模型的分数分布差异很大,不要照搬别人的阈值,需要先实际打印一些检索结果看分数。一般从0.5到0.7这个区间开始调,以“误伤少、噪声少”为准。
真正决定RAG成败的,有时候反而不是检索,而是提示词。模型拿到资料后,如果没有被明确约束,它仍然会按自己的习惯自由发挥,把那点资料当成灵感而不是依据。我整理了一个比较通用的提示词模板:
你是知识库问答助手。请严格依据下面提供的参考资料回答问题。 要求: 1. 只使用参考资料中明确出现的信息。 2. 如果参考资料不足以回答,请直接说“找不到相关信息”,不要编造。 3. 回答时用编号标注信息来源,例如“[1]”。 4. 不要添加资料中没有的细节。 参考资料: [1] 内容片段一 [2] 内容片段二 ... 用户问题:... 你的回答:把“不允许编造”和“无法回答就直接说不知道”直接写进系统提示词,效果立竿见影。这一步做的和不做的差距,比换嵌入模型还明显。
那套最简单的RAG流水线,核心逻辑就是这样一段伪代码:
# 伪代码:最小可用的RAG流程 # 1. 索引阶段:文档 -> 切块 -> 向量化 -> 入库 for doc in all_documents: chunks = split_document(doc, chunk_size=600, overlap=80) for chunk in chunks: vector = embedding_model.encode(chunk) vector_db.insert(vector, metadata={"text": chunk, "source": doc.name}) # 2. 检索阶段:问题 -> 向量化 -> 查相似 query_vec = embedding_model.encode(user_question) results = vector_db.search(query_vec, top_k=5, score_threshold=0.6) # 3. 增强阶段:拼接参考资料和提示词 context = "\n".join([f"[{i+1}] {r.metadata['text']}" for i, r in enumerate(results)]) prompt = build_prompt(context, user_question) # 4. 生成阶段:调用大模型 answer = llm.chat(prompt)这里最重要的心法就一句话:RAG的效果上限,等于检索质量乘以模型的服从能力。检索回来的资料再准,模型不照着读也没用;模型能力再强,检索回来一堆噪声,它也组织不出好答案。两头的钱都得花。
5. 上线后的噩梦与调试实录
5.1 检索不到,答非所问
上线之后第一个常撞见的问题是:用户问了一个明确的问题,模型却支支吾吾说不知道,或者答了一个完全不相干的内容。这时候别急着怪模型,先去看检索质量。
我在调试时第一步永远是打印检索日志。把用户问题对应的Top-K结果、每条的相似度分数、片段内容全部打出来。如果发现返回片段的分数普遍低于阈值,说明根本没有相关内容被检索回来。
造成这种情况的常见原因有三个:切块太碎,把关键信息切散了;用户查询的措辞跟文档里的表述差别太大,向量相似度匹配不上;嵌入模型本身不适合这个专业领域,术语识别能力弱。
对症下药就好说。切块问题就调整切块策略;措辞差异问题,可以尝试在查询阶段做同义改写,或者用混合检索配合关键词匹配;嵌入模型不适配,可以换一个领域表现更好的模型,测上20个典型问题对比。
如果问题是“检索到了一个高度相关的片段,但是模型答错了”,那就是下一节要聊的生成侧问题。
5.2 找回来了,答案还是不对
这类问题最迷惑:我看到检索结果里明明有正确片段,模型答出来却还是跑偏了。经历几次之后,我总结出了几种典型原因。
第一种,提示词约束不够。模型把检索回来的内容当成“参考方向”,顺着又接了一段自由发挥。这种情况把提示词里加一句“如果资料中没有,必须直接说不知道”,通常能立刻改善。
第二种,正确信息被噪声淹没。K值取太大,返回片段里混了一条高相似但内容矛盾的文档,模型会采用那条错误信息。这种情况降低K值,或提高相似度阈值,把低相关的片段过滤掉。
第三种,模型自身能力不足。有些小模型面对好几段长资料,提取关键信息的能力不够,东拉西扯,锚定不住重点。这种情况要么换更大的模型,要么在检索后加一道重排序:先用向量库粗召回更多的候选,再用一个专门的重排序模型或大模型做精细排序,然后把得分最高的几个传给最终问答模型。
还有一个实用技巧,就是强制答案带引用编号。让模型回答时标注“根据[1]”,这样一旦答案不对,你马上能知道它用的是哪条资料,排查效率高很多。
5.3 性能与成本怎么平衡
RAG系统的延迟比单纯模型问答要高,这是必须接受的现实,因为多了一段检索的耗时。但延迟花得不值就让人头疼了。
我做过几次优化之后,效果比较明显的做法有这些:给向量索引加内存常驻,避免每次请求都从磁盘加载;嵌入模型用轻量级推理服务,单独部署,避免跟主模型争资源;把检索结果做缓存,同类型问题短期内直接复用,能省掉很多重复计算。
成本方面,嵌入计算是一次性的,真正持续烧钱的是每次问答的大模型调用。所以另一个思路是把检索做准,减少用户反复追问的次数。检索准确率上去一点,模型调用次数就能降下来,成本自然跟着降。
5.4 我的实用排查清单
我把实战中遇到的常见问题整理成了一张速查表,每次系统出问题我基本都按这个顺序过一遍:
| 症状 | 优先排查方向 | 推荐动作 |
|---|---|---|
| 答案完全跑偏 | 检索是否有命中 | 打印Top-K日志,人工判断片段相关性 |
| 回答风格通顺但编造细节 | 提示词约束不到位 | 强化“只依据资料,不许编造”的指令 |
| 有时对有时错 | 资料质量不一致 | 抽查切块后的文本,清理矛盾或损坏的文档 |
| 答案对但引用了错误来源 | 重排序或K值不合适 | 降低K值,引入重排序 |
| 延迟明显偏高 | 向量检索或模型调用耗时 | 缓存索引,优化嵌入模型部署 |
这套清单的价值在于让人快速定位问题出在检索侧还是生成侧,而不是在那里盲目调参。
我踩过几次坑之后,慢慢养成一个习惯:每次上线前都做一轮“幻觉压力测试”。专门准备十几二十个知识库里肯定没有答案的问题,拿去问系统,看它是不是老实说“不知道”。如果系统在这类问题上还是硬答,那我宁可不上线也不会放它出去坑用户。后来我把这套测试也发给同行朋友用,大家都说这是花小钱省大钱的一道关卡。
RAG落地这件事,真正决定系统上限的往往不是模型本身,而是你的知识库靠不靠谱、切块切得好不好、提示词管不管得住。按这条思路走下去,挂上RAG之后你会发现,AI那个“一本正经胡说八道”的老毛病,能改掉大半。