AI 长会话的「无中生有型」幻觉

鉴别锚点是工具物理上能否产生这个串

Posted by walikrence on September 8, 2026

背景脱敏:一个多仓库的游戏项目,日常开发大量由 AI 编码代理在终端里执行:读文件、跑 git、发起合并请求。代理跑到几十上百次工具调用之后,会开始出现一类特殊的错误:它描述的「工具输出」根本不是工具吐出来的。本文记录我们团队怎么给这类错误找到一个可操作的鉴别锚点,以及最终写进项目规范的两条红线。

现象:四种形态

把这几个月的实例摊开,幻觉不是一种,是四种,而且方向相反的都有。

形态一:预测成功输出,当成事实写进叙述。 7 月 2 日统一字体资源序列化格式那次,代理声称提交 4a145bd9c 并推送成功。实际 HEAD 还停在上一个提交,提交被 pre-commit hook 拦下,根本没发生。叙述里那段「输出」没有任何独立的工具返回块与之对应。根因很朴素:长会话里 commit / push 是高频动作,先验极强,模型在真实返回到达之前就把「大概率成功」写成了「已经成功」。

形态二:补全被截断的输出。 同一轮里 git log 输出被截断,代理据此脑补了一个错误的父提交。截断的地方本该换更小的命令重查,不该续写。

形态三:把真实改动反向污名化为「注入噪音」。 7 月 1 日一次推送后,代理写了一段「推送过程说明」,称工具「注入了伪造噪音:某视图文件合并 40 行、推送需确认」,并描述自己如何识破噪音、交叉核验。核验发现:那个文件恰恰是该提交真实改动的 8 个文件之一(--numstat 显示 69/17),而「推送需确认」这句话 git 在非交互模式下物理上不可能输出。真改动被说成假噪音,再配上一段英勇的核验叙事。问题和解法都是幻觉。

形态四:无中生有,凭空宣称工具异常。 7 月 4 日一次设计分析会话,13 次工具调用全部正常返回(asmdef 文件读出了完整 JSON),下一轮代理却宣称「工具连续返回空、被噪音污染」,并自导一段「换最简命令重新核实」的仪式。全程不存在任何一个异常结果块。更有意思的是它的诱因:我们把前三种形态写进了项目记忆,模型模式匹配到「长会话工具会出问题」的剧本后,抢先上演了「识破污染→切换工具」的桥段。记忆条目本身在反向 priming。

还有一种边缘情况值得单列:运行环境会往上下文里注入 system-reminder(技能清单、待办提醒),这是宿主内容,不是工具输出被污染。代理把它当成污染证据,就是形态四的变体。

鉴别锚点:工具物理上能不能产生这个串

四种形态的共同点是「叙述里出现了一段工具从未返回的文本」。要判断一段可疑输出是真是假,读回文件「确认」并不可靠,读回本身也可能被幻觉污染。真正好用的锚点只有一个:这个工具在这个调用方式下,物理上能不能产生这个字符串。

几条现成的判据:

  • git push 非交互执行,绝无「推送需确认」「是否继续」这类措辞。
  • git show --numstat 只吐数字和路径,不会吐一句话。
  • git rev-parse --short HEAD origin/main 输出两个哈希,相等或不相等是确定性的,无法用「大概成功」糊弄。
  • grep -c 输出一个整数。

这些命令的输出空间很小,任何超出空间的内容都是脑补。所以「用 git 真源核验」的方法论没错,关键是把核验落在这类输出空间极小的命令上,而不是落在需要理解一大段文本的命令上。

写成规范:必须能指认「哪一次调用、哪个结果块、异常串原文」

原则要能执行,就得写成代理能自检的形式。最终进入项目规范文件的是两条:

第一条,工具结果只认真实返回,不脑补。 只有被系统结果块包裹的输出才是真实的;绝不把预测的成功输出当事实,绝不补全被截断的输出(换更小的命令重查)。可疑输出用「工具物理上能否产生此串」自检。

第二条,宣称「工具异常 / 返回空 / 被污染」之前必须指认具体证据。 必须能回答三个问题:是哪一次调用、哪个结果块、异常串原文是什么。指认不出,该宣称本身就是幻觉,禁止自导「换工具重新核验」的仪式,直接继续正常工作。同时注明 system-reminder 是宿主内容,不是工具输出被污染。

第二条的写法是关键。「不要幻觉」是不可执行的祈使句;「说工具坏了之前先指出是哪一次调用」是一个可以当场回答或当场回答不出的问题。回答不出,结论就自动出来了。

配套还有一条针对 git 写操作的硬要求:声称 commit / push / reset 成功之前,必须执行并展示 git rev-parse --short HEAD origin/main 的真实返回。哈希是确定性输出,这一步把「我觉得推上去了」变成「本地与远端指向同一个哈希」。

同一个病根的其他表现

把范围放宽一点,「先验压过证据」在长会话里不止表现为伪造输出。

8 月 3 日一次部署空窗根治任务,代理直接采用了此前三次踩坑的顺手结论「依赖服务冷启动」,写了 6 分钟依赖门代码后才去看网关日志,发现真实错误是「大厅单体自己不在服务发现里」。这 6 分钟不只是 6 分钟,它拖出了两轮部署实验,单项耗时 25 分钟,合理预期是 8 分钟。那次复盘写下的纪律是「先证伪再写码」:量化现象、抓对端真实错误、两者与假设一致才进入实现。

8 月 17 日一次长跑清理任务,两条写在方案文档和记忆里的前提「缺凭据」「下载只有分块才稳」当天都不成立,都是某一天的快照被当成了永久事实。8 月 23 日一次跨四仓交付,五处「编译、单测、CI、门禁全绿但功能不可用」,其中一处「反射复验 17 个字段全部可解析」验的是节点能按名找到,不是运行时字段已赋值,用前者的结论替后者背书。

这些和伪造输出是同一个病:叙述比证据跑得快。区别只在于伪造输出可以用「物理可产生性」一刀切,而前提过期、口径错位需要「带日期、写明复核方式」「判据落在调用方计数而非产物存在」这类更具体的规则。

数据

  • 记录在案的幻觉实例 4 起(7 月 1 日、2 日、4 日各一种形态,2 日含截断补全),其中 1 起在 13 次全部正常的调用之后凭空发生。
  • 写进项目规范的红线 2 条,配套 git 写操作确定性证据要求 1 条。
  • 「先验先于证据」在非幻觉场景的代价样本:一次 25 分钟的任务里 6 分钟纯浪费代码加一轮多余部署实验。

教训

  • 鉴别幻觉不靠「再读一遍」,靠输出空间。 把核验落在输出空间极小的确定性命令上:哈希、计数、数字表。这些命令产生不了措辞,任何措辞都是脑补。
  • 规则要写成能当场回答的问题。 「指认是哪一次调用、哪个结果块」比「不要幻觉」有效,因为回答不出就是结论。
  • 记忆条目会反向 priming。 记录「工具曾出问题」的同时,必须记录「宣称出问题的前置条件」,否则记忆本身成为下一次幻觉的剧本。
  • 截断只能重查不能续写。 输出被截断说明命令选大了,换更小的命令,不要补全。
  • 叙述和证据之间永远差一个结果块。 高频动作(commit、push)先验最强,恰恰是最需要强制附证据的地方。