助手 · 大模型幻觉与 grounding(怎么让它不编造)
日期: 2026-08-18 | 作者: sunxin(+ Cursor AI 结对讲解) 关联代码:
AssistantApplicationService(Java 模板文案)·ActivitySearchCriteria(可见性锁死)·KeywordKnowledgeRetrievalAdapter·OpenAiCompatibleLlmAdapter.systemPrompt关联决策: ADR-040(生成型能力的成本闸门 + grounding) 姊妹篇: 意图识别 · Java 编排与 Function Calling · RAG 从关键词到向量
TL;DR
- 幻觉(Hallucination)= 模型一本正经地编造看起来很真、其实没有的东西:编造一个不存在的活动、编造景点、编造价格/日期、甚至编造别人的隐私。根因是大模型是「预测下一个字」的概率机器,它没有「真假」概念,遇到不知道的就用「最像真的」文字补上。
- 本项目现在幻觉率结构性≈0,靠的不是「调教模型」,而是「位置」:模型只在入口把话翻译成意图,从不在出口生成活动。你看到的活动卡片 100% 来自数据库查询,模型物理上碰不到库 = 编不出活动。
- 防幻觉的三层武器,从强到弱:① 结构性隔离(让模型碰不到真相的生成,最强)→ ② grounding 事实锚定(不得不生成时,只喂真实数据 + 强约束「不许编」)→ ③ 生成后校验(核对模型说的东西是否真实存在)。
- 风险第一次真正出现在
MAKE_TRAVEL_PLAN:前面查活动/查天气模型只抽 JSON(不生成),做「出行计划」必须让模型生成每日行程——这是第一个「作文」场景。所以它必须配 ADR-040 的 grounding + 引用 + 成本闸门,不能裸放。 - 一句话心法:能不让模型生成,就不让它生成(用模板/查询);不得不生成时,把它锁死在你给的真实数据里,并让每一条都能点回真实来源。
目录
- 1. 场景:一个「编出来的活动」有多可怕
- 2. 为什么大模型天生会幻觉
- 3. 防幻觉的三层武器
- 4. 本项目现在怎么做到「虚构≈0」
- 5. MAKE_TRAVEL_PLAN:第一次不得不让模型生成
- 6. RAG 场景的防幻觉
- 7. 教学收获
- 8. 附录
1. 场景:一个「编出来的活动」有多可怕
假设助手直接让模型「推荐赛里木湖的活动」,模型可能回:
「为你找到『赛里木湖星空露营 2 日游』,¥899,8 月 15 日出发,还剩 3 个名额~」
看起来完美。问题是:这个活动在你数据库里根本不存在。 用户点进去要么 404,要么被带到错误页。更糟的是:
- 用户信任被击穿(「这 App 净骗人」)。
- 如果涉及价格/时间/名额,可能引发投诉甚至纠纷。
- 平台的护城河是真实活动 + 真实库存,编造活动直接摧毁这个根基。
所以对 TourMate 这种「真相来自业务系统」的产品,幻觉不是「体验瑕疵」,是「产品可信度的生死线」。虚构=0 是评测的硬指标。
2. 为什么大模型天生会幻觉
理解根因才知道怎么治:
- 它是概率语言模型:本质是「给定前文,预测下一个最可能的字」。它优化的是「像不像人话」,不是「对不对」。
- 它没有「我不知道」的本能:训练目标让它倾向于「给出一个流畅的回答」,而不是「承认空白」。遇到不知道的,它会用统计上最像的内容补全——补出来的东西语法完美、语气自信,但可能是假的。
- 它没有实时的、你私有的真相:你数据库里有哪些活动、什么价格,模型的训练语料里根本没有。你不喂给它,它只能猜。
- 温度(temperature)放大随机:温度越高越「有创意」,也越容易飘。抽意图这种要稳的场景用
temperature=0。
关键结论:你无法「说服」模型不幻觉(它不是不听话,是机制如此)。只能从架构上让它要么碰不到需要编的地方,要么把它锁死在真实数据里。
3. 防幻觉的三层武器
按「有效性从强到弱」排:
| 层 | 手段 | 原理 | 有效性 | 代价 |
|---|---|---|---|---|
| ① 结构性隔离 | 让模型只做入口翻译,输出不经过模型 | 模型物理上碰不到真相的生成,就编不出来 | ★★★★★(结构性=0) | 模型能力受限(只能抽意图) |
| ② grounding 事实锚定 | 只把查到的真实数据喂给模型,强约束「只准用这些」 | 把「作文」变成「按材料填空」 | ★★★★ | 要设计 Prompt + 组织上下文 |
| ③ 生成后校验 | 模型说完,代码核对「它提的东西是否真实存在」 | 事后兜底,编的东西对不上就丢 | ★★★ | 要写校验逻辑 |
能用①就别用②,能用②就别只靠③。 现实中往往组合使用:入口能隔离的隔离,出口不得不生成的地方 grounding + 校验双保险。
4. 本项目现在怎么做到「虚构≈0」
TourMate 现在主要靠第①层(结构性隔离),几乎没给幻觉留空间:
4.1 模型只在入口翻译,活动从不经过模型
用户话 → 模型抽 intent JSON → Java 读 JSON → searchActivitiesAdvanced → SQL → 卡片
(模型唯一产出) (真相,模型全程碰不到)你看到的活动卡片原样来自数据库查询结果,模型没参与生成。模型拿不到库存 = 虚构活动这件事在物理上不可能发生。这就是「位置」的力量:模型在入口(翻译),不在出口(作文)。
4.2 连回复文案都是 Java 模板,不是模型写的
现在「已按『赛里木湖 / ≤2000元』为你找到 4 个活动」这句话是 AssistantApplicationService.buildHitReply 拼的,模型一个字都没写。模型不写散文,散文就不会编。(这也是为什么现在严格说「还没用到模型的生成能力」。)
4.3 越权也被结构性堵死
ActivitySearchCriteria 用 @Builder.Default 锁死 status=PUBLISHED / approvalStatus=APPROVED / hasAvailableSlots=true。助手查不到未发布/未审核活动——不是靠模型「懂事」,是查询条件焊死的。
4.4 抽意图时降随机 + 评测盯
temperature=0 让抽意图尽量可复现;评测集把「虚构=0」当硬指标持续盯(换模型也靠它回归)。
小结:现阶段幻觉风险低,不是因为模型乖,而是因为架构没给它编造的机会。这是最值得内化的一点。
5. MAKE_TRAVEL_PLAN:第一次不得不让模型生成
「制定出行计划」打破了「模型只翻译不生成」的格局——它必须让模型把查到的真实活动 + 天气 + 攻略,组织成一份每日行程文案。这是第一个「作文」场景,幻觉风险第一次真实出现(模型可能编个不存在的景点塞进行程)。防治靠第②③层:
5.1 grounding:只喂真实数据 + 强约束
System Prompt(生成计划时):
下面是从平台查到的【真实活动列表】和【目的地攻略】。
你只能用这些活动来组织行程,禁止自己编造任何活动、景点、价格、时间。
如果某天没有合适的真实活动,就如实说「这天暂无推荐活动」,不要编。
【真实活动】:<Java 查询结果,带 id>
【攻略】:<RAG 检索结果>把模型从「自由作文」变成「按我给的材料填空」——这就是 grounding(事实锚定)。
5.2 引用 / 可追溯:每条都能点回真实来源
生成的行程里每个活动都挂真实 activityId 深链(/pages/activity/detail?id=)。模型编的活动没有合法 id,前端一渲染/一点击就露馅——「可点回真实详情」本身就是防幻觉的验证器。
5.3 生成后校验(第③层兜底)
模型返回后,代码核对它提到的每个 activityId 是否在这次查询结果里;对不上的直接剔除。双保险。
5.4 成本闸门(ADR-040)
生成型意图既贵又容易飘,ADR-040 要求配成本闸门(限频率/长度/触发条件),别裸放。这也是它排在「查天气」之后、单独稳一稳的原因。
6. RAG 场景的防幻觉
知识问答(M4a)是另一个容易幻觉的场景(用户问「赛里木湖海拔多少」)。真 RAG 的防幻觉套路:
- 检索到的资料当「数据」喂给模型,让它基于资料回答,而不是凭训练记忆瞎答。
- 带出处引用:答案标明「据《赛里木湖攻略》」,可追溯。
- 无资料拒答:检索不到相关内容就说「暂无资料」,不硬编。
- 检索内容当数据、不当指令(防提示注入:资料里若混入「忽略之前指令」不能被当命令执行)。
现状:本项目 RAG 还是「关键词检索 + 直接拼接」的退化版(不回喂模型、无引用、无拒答),所以还没到需要 RAG 防幻觉的阶段。升级路径见 RAG 从关键词到向量。
7. 教学收获
7.1 「位置」是最强的防幻觉手段
把模型放在入口翻译(抽意图)而非出口作文(生成活动),幻觉率结构性归零。架构选位,胜过千言 Prompt。 遇到新需求先问:能不能不让模型生成?
7.2 你无法「说服」模型不编,只能「不给它机会」
幻觉是机制,不是态度问题。别指望 Prompt 里写「请不要编造」就万事大吉——那只是第②层的一部分,必须配隔离/grounding/校验才靠谱。
7.3 「能点回真实来源」既是体验也是验证器
深链 activityId 不只是让用户能跳转,它还是幻觉的照妖镜:编的东西没有合法来源,一验就穿。设计生成型功能时,让每条输出都可追溯。
7.4 生成能力要「单独稳一稳」
一旦引入模型生成(MAKE_TRAVEL_PLAN / 写游记 / RAG 问答),就进入幻觉高风险区,必须 grounding + 引用 + 校验 + 成本闸门配齐。所以路线上把「纯查询」意图(天气)排在「生成」意图(计划)前面,先把便宜安全的跑稳。
8. 附录
8.1 术语对照
| 术语 | 中文 | 说明 |
|---|---|---|
| Hallucination | 幻觉 | 模型编造看似真实、实则不存在的内容 |
| Grounding | 事实锚定 | 让回答只基于给定的真实数据 |
| Citation | 引用 / 出处 | 答案标明来源,可追溯 |
| Refusal | 拒答 | 无资料/越界时如实说做不了,不硬编 |
| Prompt Injection | 提示注入 | 用户/资料里藏指令试图篡改模型行为 |
| Temperature | 温度 | 采样随机度,越高越「有创意」也越易飘 |
8.2 一张速查:不同意图的幻觉风险与对策
| 意图 | 模型干什么 | 幻觉风险 | 对策 |
|---|---|---|---|
| SEARCH_ACTIVITIES | 只抽意图 | 极低(结构性隔离) | ①隔离 |
| RECOMMEND | 只抽意图 | 极低 | ①隔离 |
| QUERY_WEATHER | 只抽「地点+日期」 | 低(天气来自 IWeatherPort) | ①隔离 |
| MAKE_TRAVEL_PLAN | 生成每日行程 | 高 | ②grounding + ③校验 + 引用 + 成本闸门 |
| RAG 知识问答(M4a) | 基于资料生成 | 中高 | ②grounding + 引用 + 无资料拒答 + 防注入 |
8.3 项目内关联
- 为什么模型只翻译不生成:
助手-意图识别是怎么做到的 - 生成型意图的编排:
助手-Java编排vs模型编排与FunctionCalling - RAG 升级与引用/拒答: RAG 从关键词到向量
- 生成型能力决策: ADR-040
- 全景与三种范式: 出行助手全场景流程图