做AI应用开发的人,这两年恐怕没人能绕开"幻觉"这两个字。明明给模型喂了资料,它还是能一本正经地编出根本不存在的结论,而且编得越自信,越让人头疼。RAG(检索增强生成)是被寄予厚望的方案之一,但我在实际项目中试过之后发现,它远远不是终点——固定流程的检索拼接在面对复杂问题时照样翻车。于是我开始把Agent(智能体)引进来,让模型自己决定什么时候查、查什么、怎么用查到的信息,这一套组合下来,幻觉率才真正被压到了可控范围。
这篇文章不打算讲那种"RAG+Agent很强大"的空话,而是把我从方案选型、架构设计、代码实现到线上排障的完整过程拆开来讲。如果你正在做一个对答案准确性要求很高的AI应用,或者你已经被幻觉问题折磨得想摔键盘,这篇文章应该能给你几条真正能落地的思路。
1. 先说清楚:幻觉到底是怎么来的,为什么RAG单独搞不定
1.1 幻觉的三大来源
要想治幻觉,先得知道它从哪来。我的理解里,生成式大模型的幻觉问题其实有三个独立来源,它们混在一起,造成了我们看到的"一本正经胡说八道"。
第一个来源是训练阶段的知识截断。任何模型的知识都停留在训练数据截止那一刻,之后发生的事它一概不知。你问它某个新政策、新产品的参数,它根本没有对应的知识实体,只能靠语言习惯强行生成一个"看起来合理"的回答。这属于"知识缺位型幻觉",最典型的表现是回答流畅但细节全错。
第二个来源是生成阶段的概率采样。大模型本质上是逐token预测下一个词的概率分布,它选择高概率词并不等于选择正确词。当某个话题在训练语料里出现频率足够高,模型会对"编造"这件事产生极高的置信度——它见过一万次类似的问法,就敢给你编一个从来没存在过的答案。这种"自信型幻觉"最可怕,因为答案的措辞越顺滑,用户越难分辨真假。
第三个来源是检索阶段的语义错配。即使在RAG系统里,检索器返回的内容也未必和用户问题真正相关。向量检索只负责"语义相似",它把问题和小段落映射到向量空间算距离,但语义相似不等于事实正确,更不等于逻辑完整。如果检索回来的内容是错的、过时的、或者只是"看起来相关",生成模型就会无意识地把它缝进答案里,这就形成了"污染型幻觉"。
1.2 单靠RAG解决不了的三类场景
传统的RAG流程很直白:用户提问,系统拿问题去向量库检索top-k段落,拼到上下文里让模型生成。这个流程在"单轮事实问答"场景下效果不错,但一旦遇到下面三类场景就会失灵。
第一类是"需要多步推理才能回答"的问题。比如用户问"某公司今年在A城市发布了哪些支持B功能的产品?"——你得先检索出该公司今年的所有产品,再筛选出A城市发布的,再匹配B功能,最后汇总。传统的"一次检索+一次生成"根本没法完成这种链式推理,因为中间的每一步都依赖上一步的结果。
第二类是"检索结果本身不够用"的问题。当知识库里没有直接答案时,标准RAG会强行用残碎片段生成回答,编造自然发生。合理的做法是先判断"知识缺口在哪",然后再补一次检索,或者明确告诉用户"资料不足"。但固定流程的RAG没有这种判断能力。
第三类是"回答之后没人检查"的问题。标准RAG生成完就结束了,输出的内容是否严格来自检索段落、引用是否对得上,完全没人验证。模型把上下文里一段无关文字当作论据用了,系统也不会有任何察觉。
1.3 引入Agent的真正动机
Agent的价值恰恰体现在上面三个盲区。它给模型装上了"元认知":在回答之前想想要不要查资料,回答之后回头检查一下引用对不对。这种"计划-执行-验证"的能力,才是把幻觉压下去的关键。
我见过很多人把Agent理解成"会调用工具的聊天机器人",这个理解太窄了。Agent的核心在于ReAct循环——推理(Reason)驱动行动(Act),观察行动结果后再推理。每一步行动的结果都会影响下一步决策,这就让检索从"一次性操作"变成了"可迭代的决策过程"。
更重要的是,Agent天然支持"验证"这个动作。你可以在生成结束后让模型自己检查:我引用的文档标号是否真实存在?我的回答是否严格基于检索片段?如果有不确定的内容,要不要再查一次?这种自我反思机制,是传统RAG结构上就不具备的。
2. 整体架构:从"固定检索流程"到"自主决策智能体"
2.1 系统三层结构
我最后落地的方案,整体上分了三层:路由层、检索层、生成验证层。这个分层不是为了好看,而是为了把"要不要查""怎么查""怎么证明回答是对的"三个问题彻底分开处理。
路由层解决"要不要查"。用户的问题不是每一个都需要检索的,聊家常、问常识、让模型做头脑风暴,这种场景应该直接用模型自身能力回答,检索反而会引入噪音。我用一个轻量级意图分类器来判定:这个问题是闲聊、是常识问答、是内部知识问答、还是需要实时数据查询。只有落到后两类才触发检索流程。
检索层解决"怎么查"。它不是一个孤立的向量库,而是挂载了多个数据源的工具集:本地文档知识库、外部搜索API、结构化数据库查询接口。Agent的核心决策逻辑在这一层集中体现——它要根据问题的需求,决定检索哪一个或哪几个工具,以及检索之后怎么整合结果。
生成验证层解决"怎么证明回答是对的"。这一步我要求模型在回答时标注引用来源,并在输出前执行一轮自我检查。检查不通过就触发新一轮检索,形成闭环。后面我会展开讲这个验证机制的具体设计。
2.2 主控循环设计
在主控循环里,我用的是ReAct模式的改进版。每次循环包含四个步骤:思考当前状态、选择下一个动作、执行动作并获取观测结果、把结果追加到上下文。这个循环一直持续到模型认为问题已经被回答且验证通过,或者触发了最大迭代限制。
这里有一个非常关键的设计:观察结果必须被当作"事实"对待,而不是"参考"。我要求模型在回答中引用对应的[文档编号],这些编号必须是观测结果里真实存在的。如果模型引用了一个虚构的编号,验证阶段就能立刻发现。这相当于给模型戴了一个"必须说人话"的枷锁,让它的输出始终锚定在检索证据上。
主控循环的停止条件也很重要。我设置了两个出口:一是模型在思考阶段明确输出"信息充分,可以作答",二是达到最大步数上限。在调试阶段,我建议把最大步数上限设置成5-6轮,太少了复杂问题查不完,太多了延迟和成本都会失控。
2.3 工具集规划
工具集的规划有一个容易被忽略的原则:工具的粒度要配合模型的选择能力。你把工具划分得太粗,比如只给一个"查询知识库",模型就无法表达"我要查A产品的参数"和"我要查B产品的销量"这样的精细意图;划分得太细,又会让模型在选择时反复犹豫,反而拉长决策链。
我实际用的工具划分方案是:某个产品知识文档的向量检索工具、实时订单数据的SQL查询工具、外部公开信息的网页检索工具、以及一个"无操作"出口。前三个覆盖不同的数据类型,第四个给模型一个"不用调用工具"的合法选择。
每个工具的描述字段要写得足够具体,这一点我踩过坑。模型选择工具靠的是工具描述里的语义匹配,描述太笼统它就会选错。比如"查询产品知识"和"查询实时销量"如果描述含糊,模型在复杂问题上很容易两个都选或者选错。我的经验是描述里带上典型的用法示例:"传入产品型号,返回该型号支持的参数列表"——模型一看就懂这是干什么的。
3. 核心模块的实操要点:路由、检索、验证一个都不能少
3.1 路由与意图识别
路由层看似简单,但它决定了整个系统的效率上限。如果一个闲聊问题被错误路由到了检索流程,白白浪费一次工具调用,还把返回的无关内容污染进上下文。我在路由设计上用了两个关键策略。
第一个策略是给意图分类器加上明确的few-shot示例。只告诉模型"你有四类意图"远远不够,它会对边界情况感到困惑。我给了它每个意图两到三个典型问题示例,覆盖了常规表述和模糊表述。比如"内部知识问答"类下面,既写了"我们公司的报销流程是什么",也写了"还记得之前在项目纪要里定的排期吗"——后者就是典型的模糊表达。
第二个策略是加置信度阈值。分类器输出四类意图的置信度分布,只有最高置信度超过阈值(我一般设0.85)才进入对应流程;如果置信度不够,就不触发检索,直接用模型原回答,同时附上一句"这是根据通用知识做出的回答"。这个兜底策略虽然保守,但避免了路由层主动制造污染。
提示:路由层的模型选择尽量用快一点的轻量模型。路由不需要多深的推理,抢的是延迟。我用一个大模型做路由的时候,延迟直接多加了600毫秒,体感非常明显。
3.2 多路检索与重排序
进入检索层后,首要任务是保证"查回来的东西真的有用"。向量检索本身是召回的思路,它只负责把语义相近的内容拎回来,并不判断质量。我处理的方法是所有检索结果统一过一个重排序环节。
重排序我使用的是交叉编码器方案。它把问题和候选文档拼在一起,用一个专门的模型计算相关性分数,比向量相似度准确得多。实际使用的分数阈值可以这样算:先用一批好问题和坏问题对这个平台调参,挑出误杀率和误放率平衡点的临界分数;我目前线上采用的是0.35,低于它的直接丢弃,绝不进上下文。这一步显著降低了"污染型幻觉"的产生概率。
多路检索还有一个组合细节。当Agent决定"既要查产品文档,又要查实时销量"时,两路返回的结果会合在一起重排序。这里我加了来源标识符,每个段落前面[Doc A]-这种前缀是必须有的。这是为了让生成阶段更容易正确引用,也让后端的溯源检查有据可依。没有来源标识的检索结果,在验证阶段会被直接判定为非法证据。
3.3 生成、溯源与自我验证
生成阶段是我改造最重的地方。我给模型的指令里加了一条严格约束:答案的每一句话必须至少对应一个[文档编号],没有编号支撑的内容一律不许出现在回答里。这句约束看似简单,实际上把模型的随机生成空间大幅压缩了。
为了进一步降低自由发挥空间,我把生成温度调到了0.1。加了引用约束再把温度调低,模型的输出会更保守,更倾向用检索片段里的原话来组织答案。代价是回答的措辞会显得有点"死板",不够活泛。但在事实问答场景里,准确性的优先级远高于文采,这个取舍是值得的。
自我验证我用的是"LLM as Judge"的思路,但不是让模型自己给自己打分——自答自判会存在系统性偏差。我的做法是让一个独立的验证模型检查三个东西:每个编号是否真实存在于观测结果里;每个编号的内容是否真的支撑了对应的句子;有没有核心问题点被跳过了。如果验证模型的结论是"有编号不存在"或"支撑不足",就触发一轮新的检索与修正循环。
注意:验证模型的角色要和生成模型分开。同一个模型既生成又检查,它倾向于为自己的输出辩护,检查形同虚设。哪怕用同一个基座模型,也要在提示词里把角色定义彻底区分开。
3.4 关键参数配置参考
很多人在这个环节最需要一份可以抄作业的参数表,我把我目前线上运行比较稳定的一套参数列出来,注意它依赖数据规模和业务场景,照搬时要测自己的数据。
| 参数 | 推荐值 | 设计意图 |
|---|---|---|
| 温度 temperature | 0.1 | 压缩生成多样性,紧贴检索证据 |
| 检索候选数 top_k | 20 | 保证召回率,让重排序有足够选择空间 |
| 重排序阈值 | 0.35 | 过滤低相关片段,防止上下文污染 |
| 最终上下文引用数 | 3-5 | 控制上下文长度,避免模型注意力分散 |
| 最大ReAct步数 | 6 | 平衡复杂问题处理能力和延迟成本 |
| 路由置信度阈值 | 0.85 | 防止误路由和上下文污染 |
| 验证循环上限 | 2 | 防止无休止的自我怀疑和延迟膨胀 |
还有一个参数容易被忽略:每轮检索返回给模型的段落数量上限。我每轮最多给4段,因为这个数量下的注意力分配最稳。给太多段落,模型会把精力花在无关细节上,反而更容易产生"缝合错误"——把两个不同来源的内容缝到一起,编出一个两边都不支持的结论。
4. 我在实际项目中踩过的坑:高频故障与排查实录
4.1 检索命中率低导致的"硬编造"
上线初期,我遇到的第一个大坑就是检索命中率低。案例分析发现,用户经常问产品参数里有的内容,但向量检索就是召不回相关段落。排查到最后,问题出在切片策略上:我把长文档按固定长度切片,一段500个字符,没有考虑语义边界,结果把完整的产品参数表从中间切断了。
对切片策略调整后,命中率有明显上升:先识别文档里的标题层级,按章节边界切分;章节过长的再按段落边界细分;段落中途遇到表格、列表这种结构化内容,单独把表格作为一个检索块。这么改完之后,同一问题的检索命中率提升了一截,模型需要"编"的情况自然就少了。
4.2 反思循环失控
另一个高频故障是反思循环的出入口。某一次版本上线后,系统在部分复杂问题上反复触发"重新检索-验证不通过-再次检索",直到触达最大步数才停止,单次请求耗时直接飙到十几秒。抓日志一看,原来是提示词里对"支撑不足"的定义有歧义,验证器经常把"答案缺少文档编号"当作"证据不足",于是死不放手。
最后我把验证条件改成用显式的检查清单:第一看标号是否存在,第二看编号内容是否覆盖核心论点,第三看是否有未作答的子问题。每一步都要输出"是/否",只有全部为"是"才允许结束循环。这个改动让平均循环次数从4.3次降到了2.1次,延迟随之回落。验证循环用布尔判断代替直觉判断,是根治"反思瘫痪"的关键。
4.3 上下文窗口的隐性膨胀
这个问题很隐蔽,是我在追踪token消耗时发现的。每轮ReAct循环都会把上一步的轨迹(思考+动作+观测)追加进上下文,看起来每轮只有几百token,但连续6轮之后,老轨迹占了大量窗口,导致真正承载答案生成的空间被挤压,生成质量反而下降。
我的解法是定期压缩历史轨迹:每轮开始前,把上一轮的"思考"部分丢弃,只保留"动作+观测结果"。思考部分对后续决策的影响远小于观测结果,丢弃它既不损害决策质量,又大幅节省token。这个压缩动作让我在同等预算下能把最大步数从6提高到8,而不会撑爆上下文。
4.4 工具调用格式不稳定
工具调用的格式问题也是必经之劫。我一开始用的是纯文本格式让模型输出"要调用的工具名和参数",但模型偶尔会漏掉参数、多出前缀、或者用自然语言绕圈子,一旦解析失败整个循环就卡死。
后来我强制要求输出严格的JSON对象格式,并加了重复解析的容错逻辑:解析失败时不再让模型补充说明,而是把报错信息和原问题打包,要求模型再次输出符合JSON格式的动作。这个"格式纠偏"机制上线后,工具调用的解析成功率从91%提到了99%以上。
5. 这套方案到底值不值得上?评估门禁与适用边界
5.1 成本与延迟账要算清楚
把Agent引入RAG,性能和准确率确实上来了,但代价也一样明显。我在真实项目里做过一个统计对比:同样一批问题,传统RAG单次请求平均消耗token约2200,而RAG+Agent的方案平均消耗约4700,翻了一倍多;延迟也从平均1.2秒涨到了2.8秒。如果你当前应用对延迟和成本有硬约束,这个账必须在动手前就算明白。
这并不代表该放弃Agent方案。我的思路是给系统加一道"难度评估门禁":简单问题直接走轻量RAG流程,只有系统判定为复杂问题才升级到Agent流程。这个分级策略让日常请求的平均成本只增加了25%,代价不过是多一次路由判断。
5.2 什么样的场景最适合这套方案
根据我的实践经验,RAG+Agent的组合在不同场景下的收益差异非常大。我做了一个适用度评估表,供参考:
| 场景类型 | 传统RAG效果 | RAG+Agent效果 | 是否值得 |
|---|---|---|---|
| 单轮事实问答 | 良好 | 良好 | 收益有限 |
| 需要多步推理的问答 | 偏差 | 良好 | 非常值得 |
| 多条件交叉筛选 | 偏差 | 良好 | 非常值得 |
| 实时数据+静态文档混合查询 | 差 | 良好 | 必须上 |
| 高吞吐客服场景 | 良好 | 一般 | 需加分级策略 |
如果你做的是高吞吐、问题模式高度重复的场景,Agent带来的额外推理成本会显得非常扎眼;但如果你做的是知识的深度问答、跨数据源的复杂查询这类场景,Agent带来的准确性提升是压倒性的。
5.3 上线前必须评估的三个指标
把系统做出来只是第一步,上线前我强烈建议团队先盯死三个指标。
第一个是引用准确性。抽查验证回答里的引用编号是不是真的指向了正确段落。引用对齐抽查在最初的两周里非常有必要,它能直接反映整套检索链路是不是真的在起作用。我验证过几次,模型回答看着合理但引用的内容是张冠李戴的情况,比例不低。
第二个是无引用断言率。统计回答里有百分之多少的陈述找不到对应的引用来源。这个指标超过3%就要警惕了,说明模型还在偷偷"自由发挥"。
第三个是兜底话术触发率。当检索内容不足以支撑回答时,系统有没有老老实实说"资料不足",还是硬着头皮胡编。兜底话术的触发率可以不高,但只要触发时说的是真话,系统的可信度就能立住。
按照我个人做几个项目的经验,这三个指标里面,引用准确性的权重最大。因为很多幻觉问题,表面上是回答错误,根源却是引用错乱。把引用链路管住了,幻觉问题的七成就算治好了。
最后再分享两个判断
我踩过不少轮子之后,最大的感受是:RAG与Agent的结合,本质上不是给模型"加功能",而是给模型设置了一套"可以自证清白的思考流程"。它不再是被动地把检索内容塞进上下文,而是主动地找证据、用证据、查证据。这套机制不能从根本上消除概率模型的随机性,但它能保证模型每一次的自由发挥都处在可控范围内。
如果你正在为幻觉问题焦头烂额,我的建议是不要试图一次性把Agent系统做完美。先架构出一个最小闭环——路由、一次检索、引用生成、一次验证——跑起来之后,再慢慢加反思轮次、多路检索、复杂工具集。方案的价值需要在真实问题的检验中逐步释放,步子太急系统容易乱,反而误事。