Claude Code 是如何降低大模型幻觉的

8 阅读12分钟

Claude Code 并没有“消灭幻觉”,它做的是把模型尽量放进一个受约束、可验证、可回退的执行环境里,让模型少靠猜、多靠读、少空想、多试错。

这和普通聊天式大模型最大的区别在于:

  • 普通聊天:模型主要靠参数记忆和上下文推断
  • Claude Code:模型被要求先读真实文件、执行真实命令、拿到真实结果,再继续下一步

所以它的抗幻觉能力,本质上不是来自“模型更聪明”,而是来自系统设计


一、先理解什么叫“代码场景里的幻觉”

在编程场景里,常见幻觉主要有 5 类:

1. 假装知道项目结构

例如模型没读代码,就说:

  • “你这里用的是 Express”
  • “这个函数在 utils 目录里”
  • “测试框架是 Jest”

但实际上项目可能根本不是这样。

2. 假装知道文件内容

例如没真正读文件,就编造:

  • 某个函数签名
  • 某个类型定义
  • 某个配置项名字

3. 假装知道命令执行结果

例如没跑测试,就说:

  • “测试已经通过”
  • “构建没问题”
  • “这个命令输出说明依赖缺失”

4. 假装编辑成功

例如模型说“我已经改好了”,但实际上:

  • 没改到正确位置
  • old_string 不匹配
  • 文件根本没写成功

5. 假装自己理解了全局上下文

例如只看了一小段代码,就对整个系统架构下结论。

Claude Code 的设计重点,基本都在对抗这 5 类问题。


claude-code-anti-hallucination-01-overview.svg

二、Claude Code 的核心思路:不要让模型直接“相信自己”

它主要通过 6 类机制降低幻觉:

  1. 工具优先,而不是记忆优先
  2. 读-改-验闭环,而不是一次性生成
  3. 局部编辑协议,而不是整文件想象式重写
  4. 上下文压缩和预算控制,减少错误记忆污染
  5. 权限和执行结果显式反馈,避免“假装成功”
  6. transcript / tool_result 持久化,保证后续轮次基于真实历史

下面逐个讲。


三、先读真实世界,再继续推理

这是最核心的一点。

Claude Code 不是鼓励模型“直接回答代码问题”,而是鼓励模型先调用工具:

  • Read
  • Grep
  • Glob
  • Bash

它的工作方式更像:

  1. 先看文件
  2. 再看搜索结果
  3. 再决定怎么改
  4. 再执行验证
  5. 再根据真实反馈修正

例子:避免“假装知道路由框架”

用户说:

“帮我加一个 /health 接口。”

普通聊天模型很可能直接假设:

  • 项目是 Express
  • 入口文件叫 server.ts
  • 路由写法是 app.get('/health', ...)

这就容易幻觉。

Claude Code 更合理的流程是:

  1. Glob("src/**")
  2. Read("package.json")
  3. Grep("express|fastify|koa|router", "src")
  4. Read 命中的文件片段

这样模型不是靠猜,而是靠真实文件决定:

  • 到底是 Express 还是 Fastify
  • 入口文件在哪里
  • 路由风格是什么

这里的反幻觉本质:把“我以为”变成“我看见了”。


四、多轮 agent loop,让模型必须面对真实反馈

Claude Code 的核心不是单次生成,而是一个循环:

  1. 模型提出工具调用
  2. 系统执行工具
  3. 工具结果回灌给模型
  4. 模型再决定下一步

这很重要,因为很多幻觉来自“模型说完就结束”,没人逼它面对现实。

claude-code-anti-hallucination-02-agent-loop.svg

例子:避免“假装测试通过”

用户说:

“帮我修这个测试。”

普通聊天模型可能只看报错描述,就直接给出一个修复建议,然后说“应该可以了”。

Claude Code 的更可靠路径是:

  1. Read 测试文件
  2. Read 被测实现
  3. Edit 代码
  4. Bash("npm test")
  5. 把真实测试结果作为 tool_result 回给模型
  6. 如果失败,再继续修

也就是说,模型不能只说“我觉得这样能过”,它必须看到:

  • 真正执行后的 stdout/stderr
  • 哪个断言还在失败
  • 哪个模块没有导出
  • 哪个类型不兼容

这里的反幻觉本质:让模型对现实结果负责。


五、编辑不是“重写整文件”,而是局部替换

Claude Code 的 Edit 工具不是让模型直接输出一个新文件全文,而是要求它提供:

  • file_path
  • old_string
  • new_string
  • replace_all

这套协议非常重要。

为什么这能降低幻觉

因为它逼模型回答两个问题:

  1. 你到底要改哪一段?
  2. 你准备把它改成什么?

如果模型其实没读懂文件,它就很容易在这里暴露:

  • old_string 在文件里找不到
  • 找到多个位置,不够唯一
  • 缩进不对
  • 上下文不匹配

工具层会直接报错,而不是“默默接受一个想象中的修改”。

例子:避免“假装知道函数实现位置”

用户说:

“把这个 service 的超时时间从 5 秒改成 10 秒。”

如果模型只靠猜,可能会直接说:

  • “已修改常量 TIMEOUT_MS 为 10000”

但如果它没读文件,实际可能:

  • 常量名不是这个
  • 它在配置文件里
  • 有多个 5000
  • 实际单位是毫秒还是秒都不确定

在 Claude Code 里,如果它真要改,就得给出明确的 old_string
一旦 old_string 不存在,编辑就失败,模型必须回去继续读文件。

这里的反幻觉本质:把“抽象上的修改意图”变成“精确的文本定位”。


六、Read 工具有局部读取和大小限制,逼模型精确定位

这点看起来像性能优化,实际上也是反幻觉设计。

Read 不鼓励无脑整文件读取,而是支持:

  • offset
  • limit

同时对过大的文件有:

  • 字节大小限制
  • token 限制

这带来两个效果。

1. 减少“读了一堆没用内容后误解上下文”

上下文太大时,模型容易:

  • 丢掉局部细节
  • 混淆不同位置的相似代码
  • 错把旧逻辑当新逻辑

局部读取反而更清晰。

2. 逼模型先定位,再阅读

当文件很大时,模型通常得先:

  • Grep
  • Read(offset, limit)

这会让它形成“先找位置,再看内容”的习惯,而不是凭印象下结论。

例子:6000 行文件里的 bug 修复

假设一个文件有 6200 行,用户说:

“修一下 websocket 重连逻辑。”

更容易幻觉的做法是:

  • 把前面几百行看一遍
  • 然后假设重连逻辑写法
  • 开始改代码

Claude Code 更合理的做法是:

  1. Grep("reconnect|retry|websocket|backoff", file)
  2. 根据命中位置 Read(offset=3000, limit=120)
  3. 如果还需要上下文,再补读附近片段

这样模型看到的是“真正的相关代码”,不是整文件里一堆相似概念。

这里的反幻觉本质:减少无关上下文,提升定位精度。


七、工具结果不是随便说,而是结构化回灌

Claude Code 里,工具执行结果会被编码成结构化 tool_result,再进入下一轮上下文。

这意味着下一轮模型看到的不是“我刚才可能做了什么”,而是:

  • 某个文件真实内容
  • 某条命令真实输出
  • 某次编辑真实成功或失败
  • 某次权限被拒绝

例子:避免“把失败当成功”

比如模型发起一次 Edit,如果编辑失败,系统不会假装继续,而是把失败信息喂回去:

  • String to replace not found
  • Found multiple matches
  • Cannot create new file - file already exists

下一轮模型必须基于这个失败继续决策。

这比很多简单 agent 强很多。很多 agent 框架会让模型“以为”工具成功了,或者把失败吞掉,后面就一路幻觉下去。

这里的反幻觉本质:真实结果必须显式进入后续推理。


八、权限系统让“做不到”变成显式事实

Claude Code 不是让模型默认拥有无限能力。工具可能被:

  • allow
  • ask
  • deny

一旦权限不允许,系统会明确产生拒绝结果。

这能降低一类非常常见的幻觉:

  • “我已经修改好了”
  • “我已经运行了命令”
  • “我已经读了某个文件”

实际上可能只是权限没过。

Claude Code 会把这些拒绝记录下来,并在最终结果里携带 permission_denials

例子:避免“假装已经修改系统文件”

用户说:

“帮我改一下 /etc/hosts。”

如果权限不允许,Claude Code 不会假装改了。
它只能得到一个明确事实:这次 edit 被拒绝。

于是模型后续更可能诚实地说:

  • 当前权限不足
  • 需要用户授权
  • 可以先给出修改建议,但不能宣称已经完成

这里的反幻觉本质:把能力边界显式写进执行环境。


九、transcript 持久化,让后续轮次基于真实历史而不是模糊记忆

Claude Code 会把消息、工具结果、compact 边界等记录到 transcript。

这有两个反幻觉价值。

1. 中断恢复时不丢真实历史

如果系统中途停了,恢复时不是靠模型“重新回忆”,而是靠 transcript 继续。

2. 后续轮次依赖的是可回放历史

不是“我印象里之前改过”,而是“系统记录里确实改过/读过/跑过”。

例子:避免恢复后说错上下文

如果一个长任务中途停止,普通聊天模型恢复后很容易:

  • 忘了刚才读过哪些文件
  • 忘了测试失败在哪一步
  • 忘了已经尝试过哪些修复

Claude Code 因为有 transcript 和相关状态,恢复后更容易延续真实执行轨迹,而不是重新猜。

这里的反幻觉本质:让历史来自记录,而不是来自模型记忆。


十、上下文压缩不是简单总结,而是尽量保留可运行事实

Claude Code 会做上下文压缩,但不是粗暴地“总结一下前文”。

它会分层处理:

  • 先缩小 tool result
  • 再 snip / microcompact
  • 最后才做 autocompact

并且压缩后还会补回运行时附件。

这对反幻觉的意义是:

尽量不要让模型只依赖模糊摘要继续做精确编程任务。

如果压缩做得太粗糙,模型后面就容易:

  • 混淆之前看过的代码
  • 错记某次编辑内容
  • 错记某个测试输出

Claude Code 的压缩策略,本质上是在“节省上下文”和“保持事实精度”之间做平衡。


十一、一个完整例子:Claude Code 如何降低“修 bug”过程中的幻觉

假设用户说:

“这个登录接口 500 了,你帮我修一下,并验证通过。”

一个容易幻觉的模型可能会这样:

  1. 猜问题在数据库连接
  2. 猜项目用 Express
  3. 猜测试命令是 npm test
  4. 给出一个修改方案
  5. 说“应该修好了”

Claude Code 更可能这样做:

第一步:定位

  • Grep("login|signin|auth", src)
  • Read 命中的 controller / service / route 片段
  • Read(package.json) 看脚本和依赖

第二步:确认错误来源

  • Bash 跑用户给的复现命令或测试命令
  • 拿到真实 500 堆栈或测试输出

第三步:修改

  • Edit 精确替换相关逻辑
  • 如果 old_string 不匹配,立即失败并重试

第四步:验证

  • 再次 Bash 跑测试或启动命令
  • 读取真实输出

第五步:必要时继续修

  • 如果还有错误,模型必须基于真实日志继续

这个过程中,模型几乎每一步都被现实约束:

  • 先读
  • 再改
  • 再验
  • 失败就回退

所以它不像在“凭空写答案”,更像在“操作一个真实系统”。

claude-code-anti-hallucination-03-login-example.svg


十二、但要诚实一点:Claude Code 不是不幻觉,只是更难幻觉

它依然会出错,常见残留问题包括:

1. 读了局部后,对全局做过度推断

比如只看一个函数,就猜整个模块意图。

2. grep 命中不准,导致读错位置

这属于定位偏差,不是完全消除的。

3. old_string 选得不够稳

尤其在重复代码多的文件里。

4. 测试覆盖不完整

即使跑通了一个命令,也不代表整体没问题。

5. 压缩后仍可能丢部分细节

尤其在超长复杂任务中。

所以更准确的说法不是:

  • “Claude Code 解决了幻觉”

而是:

  • Claude Code 把幻觉从“随时随地乱猜”压缩成“在受约束环境里的局部判断错误”

这已经是巨大的进步。


十三、如果把 Claude Code 的抗幻觉能力总结成一句话

我会这样总结:

Claude Code 降低幻觉的核心,不是让模型更自信,而是让模型更少有机会靠想象完成任务。

它通过:

  • 真实读取
  • 结构化工具调用
  • 局部编辑协议
  • 真实命令执行
  • 明确权限边界
  • transcript 持久化
  • 分层上下文治理

把模型从“回答机器”变成了“在真实反馈回路中工作的执行代理”。


十四、最值得借鉴的设计原则

如果你想自己做一个 agent 系统,我建议直接记这 6 条:

  • 不要让模型直接猜项目结构,先读文件
  • 不要让模型直接宣布成功,先跑验证
  • 不要让模型整文件重写,优先局部替换
  • 不要把失败吞掉,失败也要进入上下文
  • 不要让超大上下文长期堆积,及时做预算和压缩
  • 不要依赖模型记忆会话,重要历史要持久化

claude-code-anti-hallucination-04-defense-layers.svg