AI 角色的事实接地:幻觉按供给链路分类,不按「模型不行」归因
背景:角色的幻觉是产品事故,不是模型缺陷
在一个 AI 角色养成产品里,角色有人设、有记忆、有自己的日程。线上出过一个典型事故:下午 16:08 的请求,角色按 14:30 回答,还把自己日程表里的午休迁移成了劝用户去睡午觉。这类问题按「模型幻觉」归因就修不动——真正的病灶是 prompt 里同时存在多份互相竞争的事实:当前时间、日历锚点、日程的绝对时间窗各自独立注入,模型只是在多个权威里选错了一个。治理下来的经验是,事实类 bug 要按供给链路分成四类,各修各的:时效(事实何时取的)、权威(多份事实谁说了算)、归属(事实属于谁)、出口(模型被允许写什么)。
回合级事实胶囊:同回合,同源,同快照
修权威问题的做法是收编:每个请求编译一份带属主边界(世界 / 角色 / 用户)的事实胶囊,在最新用户回合附近注入一次,同时删掉散落各处的时钟扩展和独立的日程注释——不再有第二个地方能说「现在几点」。日程的开始时间渲染成相对时长而不是绝对时刻,防止「14:30」这种显著数字被模型提升成当前时间。胶囊之外只留一层薄的确定性兜底:显式说错当前时间或星期就重试一次,同一个谓词复用在流式、持久化和主动消息的 fail-closed 路径上。判断句是:同一回合的所有事实必须同源同快照,「谁拥有这个事实」要写进结构里,而不是写进措辞里。
发送时接地:把生成收缩成选择
角色主动找用户说话的链路,时间差是幻觉温床:决策时刻和发送时刻之间隔着调度和审核。就算注入了权威时钟,只挡「显式说错时间」的守卫也拦不住隐式时间宣称——「刚追剧追到半夜」「快迟到了」这类话没有一个数字可校验,却全是时间断言。修法是收权:模型不再自由撰写消息文本,改成从服务端持有的候选里选一个 candidate_id。中性候选不含任何时钟与活动宣称;日程候选携带日程快照指纹和一个两分钟的租约;审核之后、持久化之前,在发送时刻重新校验时间窗、租约和快照是否仍然成立。并发侧用数据库原子认领把每个用户-角色的机会槽锁成单次决策,落库记录观察时间、实际发送时间和接地来源。判断句:校验挡不住的隐式宣称,就不让模型有机会写出来——把生成收缩成选择,事实约束就从「提示」变成了「结构」。
语义回忆:模型输出引用,代码验证引用
「你还记得那个刚搬来的大夫吗」查不到存档里的「新来的医生」,字面话题匹配漏掉改述,产生假的「没有记录」——比幻觉更伤,因为角色明明记得却说忘了。兜底是一个有界的语义证据选择器:只在字面匹配落空后启动,模型只允许从已过滤的当前会话消息里输出高置信的 message ID,代码侧重新校验每个 ID、只向相邻上下文扩展,全部落空就关闭回落到原有的「没找到」回答;候选集因截断而不完整时,回答「不可用」而不是斩钉截铁的否认。判断句:允许模型参与检索,但让它输出引用而不是内容——引用可以被代码验证,内容不能。
PE 沙盒:每次修复先可复现,再上线
以上每一步都依赖同一件基建:一个无副作用的 Prompt A/B 沙盒。它用真实的生产 prompt 组装逻辑和当前角色上下文,但不产生任何消息、线程、亲密度事件和记忆写入:草稿有规范化 hash,快照不可变共享,工具走只读白名单,逐变体留 trace,带响应预算和取消,只在 staging 开放。16:08 事故的修复就是先在沙盒里用同一个问题跑新旧两种注入方式,复现出旧方式答「现在是中午 12 点多,午休时间到呢」,确认差异后才上线。判断句:没有无副作用的对比沙盒,事实接地的任何修复都只能靠信仰上线。
可迁移的判断
角色一致性问题大多不是「模型不行」,是事实供给链路不行。四类病灶对应四种修法:权威靠回合级胶囊统一,时效靠发送时刻重新接地,隐式宣称靠收缩生成空间而不是靠事后校验,记忆检索让模型输出可验证的引用。这套分类的价值在于止住「加一条 prompt 规则」的条件反射——规则修不了链路,只会把下一个幻觉推迟到规则覆盖不到的措辞里。