让大模型修 bug 时,它脑子里到底发生了什么
你给大模型贴了这样一段,然后问”这个空指针怎么回事”:
public class OrderService {
private OrderMapper mapper;
public String getOrderName(Long id) {
return mapper.selectById(id).getName(); // 这里抛 NPE
}
}
外加一小截日志:
java.lang.NullPointerException
at com.example.OrderService.getOrderName(OrderService.java:44)
几秒钟后,它回了你一段:selectById(id) 可能返回 null,直接 .getName() 就空指针了,建议先判空或返回 Optional。
然后你脑子里闪过一个直觉:它是不是把我的代码在脑子里”跑”了一遍?是不是读懂了 JVM 的引用语义?
这篇文章就回答这个问题。结论可能和直觉相反:
模型没有”运行”你的代码,也没有”读懂”日志——它做的是:把”问题 + 代码 + 日志”全当成一长串字,然后猜”下一段最像正确答案的话”是什么。
它的诊断,本质是统计关联 + 模式匹配,不是真正的执行验证。下面用后端工程师听得懂的方式,把这件事从头拆到尾。
一、你喂进去的,是一锅”字”的大杂烩
1.1 三种东西,在模型眼里是同一种东西
你贴了三样东西:你的问题(”这个空指针怎么回事”)、代码(OrderService.java)、日志(NullPointerException 栈)。在你的认知里,这是三个完全不同类别的信息,处理方式应该不同。
但模型不区分它们。对模型来说,这三样统统是一回事:一长串 token(字/词片段)。
[问题:这个空指针怎么回事?]
[代码:public class OrderService { ... .getName(); }]
[日志:NullPointerException at OrderService.java:44]
[补充:帮我修一下]
↓ 全部切成 token
[这][个][空][指针][怎么][回事][?][public][class][Order][Service][{][...][.][get][Name][(][)][;][Null][Pointer][Exception][at][...][44][...]
代码里的 {、.、;,日志里的 at、行号 44,和你打的字一样,都是 token。“这是代码”这件事,是外面那层客户端告诉它的(通过 system prompt / 上下文格式),不是它自己”看”出来的。
1.2 类比:一次消息序列化
后端工程师可以这么理解:这就像你把一个”诊断请求”序列化成了 JSON 发给下游——
{
"question": "这个空指针怎么回事?",
"code": "public class OrderService { ... }",
"stacktrace": "NullPointerException at OrderService.java:44"
}
模型服务端收到这份 JSON 后,把它当一个整体去算,而不是分门别类地”读代码”“读日志”。它做的是对整份序列化结果的一次前向计算(上一篇讲过:token 进 → 层层矩阵乘法 → 概率分布 → 采样出下一个 token)。它不会”先看代码、再看日志、再下结论”,而是把一切都摊平在一条序列里,一次性关联起来。
二、它在”读”的时候,注意力在”对焦”
2.1 自注意力:每写一个字,都对全句做一次”加权扫描”
模型生成回答是一个字一个字往外挤的。每挤一个字之前,它都要做一件事:把整锅 token 里的每一个,和”当前要写的这个字”算一遍相关度,然后只重点看最相关的几处。
这就是上一篇拆过的自注意力。放到这个场景里,它的作用非常具体——当模型要写 “第 44 行 mapper.selectById(id) 可能返回 null” 这句话时,它脑子里(权重上)在发生这样的对焦:
要生成 token "null" 之前,注意力扫过整段上下文:
├─ "NullPointerException" → 权重给高(问题本身)
├─ "mapper.selectById(id)" → 权重给高(疑似来源)
├─ ".getName()" → 权重给高(解引用动作)
├─ "空指针"(你的问题) → 权重给高
└─ 其他无关代码/词汇 → 权重给低(几乎忽略)
它把 NullPointerException、selectById、.getName()、null 这几个 token 关联到了一起。
2.2 类比:一次”软权重”的全表 JOIN
翻译成后端的话,这就是上一篇那句老话——一次全表 JOIN + 加权聚合:
| SQL 里的 JOIN | 模型的自注意力 |
|---|---|
ON 条件 写死:a.user_id = b.id |
关联强度是训练学出来的”软权重”,可大可小 |
| 命中就关联,没命中就不关联 | 每个 token 都和其他 token 有关联,只是权重大小不同 |
| 结果是一行完整记录 | 结果是”这个词的新表示”,混入了相关词的信息 |
区别只在一点:SQL 的 ON 是硬条件,要么关联要么不关联;模型的注意力是软权重——”空指针”和”44 行”关联 0.8,”空指针”和”public”关联 0.01,但都算关联。这个”软”正是它能做模糊匹配、能”灵光一现”的原因,也是它偶尔”看岔”的原因(后面讲幻觉时会再碰到)。
三、它靠什么”看起来懂”:训练时见过几百万个同样的坑
3.1 关键真相:它不是现场理解了 Java,是”背过”这类题
这是全篇最重要的一节。模型之所以能秒懂这是 NPE、还知道该判空或用 Optional,不是因为它现场理解了 Java 的引用语义,而是因为它在训练时见过海量的这类配对。
回想上一篇讲的训练方式:猜下一个词。全世界互联网上的文本——Stack Overflow 的问答、GitHub 的 issue 和 commit、技术博客、官方文档——被切成了”挡住最后一个词,让模型猜”的题目。而这里面天然充斥着同一种模式:
"NullPointerException at OrderService.java:44" → "先判空 / 返回 Optional"
"cannot read property of undefined" → "加默认值 / 可选链"
"connection reset by peer" → "检查 keep-alive / 超时"
"Deadlock found when trying to get lock" → "检查加锁顺序 / 事务边界"
训练阶段,模型为了”猜中下一个词”,被迫把这些“症状 → 处置”的关联模式压进了权重。于是现在你给它一个新症状,它做的不是”诊断”,而是:
照着学过的模式,续写一段”最像标准答案”的话。
3.2 类比:会背案例库的老法师,而不是刚下机房的排障员
这里有个非常贴切的比喻,能帮你精确把握它的能力边界:
- 一个真·排障工程师:跑代码、下断点、看真实返回值、二分定位,靠”现场验证”得出结论。
- 一个大模型:像一个背熟了全公司历史故障案例库、但从不亲自去机房看的新人。你一说症状,他立刻能报出”这跟我背过的第 382 号案例一模一样,当时的处置是 XXX”。
他快、他广、他能一眼认出常见故障——因为这类题他背过无数遍;但他没去现场,所以一旦遇到案例库之外的新状况,他就开始”编”。
这就是它”分析 bug”的全部秘密:不是执行,是检索 + 续写。 那些让你惊叹”它居然懂”的时刻,本质是”你的 bug 恰好长在它见过的海量样本里”。
四、它”分析”和你”debug”,是两件完全不同的事
把两套流程并排看,差异会非常扎眼:
| 维度 | 你 debug | 大模型”分析 bug” |
|---|---|---|
| 有没有运行代码 | 有:真实执行、下断点 | 没有:代码只是被当成文本 |
| 有没有真实值 | 有:能看变量的实际取值 | 没有:只有字面,没有运行时状态 |
| 结论来源 | 现场观察 + 逻辑推理 | 训练数据里的”症状→修复”模式续写 |
| 能不能证明 | 能:跑个测试复现 | 不能:它只是”说得像”,无法自证 |
| 面对冷门 bug | 慢慢查,能查出真相 | 硬套最像的模板,可能编造 |
一句话:你在”查”,它在”猜”。 这两者都能产出”诊断结论”,但可靠性天差地别。你的结论是”验证出来的”,它的结论是”续写出来的”。
五、为什么有时”一本正经地胡说”(幻觉)
既然它优化的是”生成最顺、最像答案的话”,那就必然埋着一个漏洞:
它优化的是”说得像真的”,而不是”真的是真的”。
它没有执行、没有返回值、没有真实环境反馈。于是翻车的模式就出来了,而且每种都特别有迷惑性:
5.1 四种典型的翻车
- 冷门 bug 硬套模板:你遇到一个训练数据里几乎没见过的 bug,它不会说”我不会”,而是硬套一个最像的模板,头头是道,但全是错的。
- 看漏变量名 / 行号:你贴的代码里某个变量名,注意力没给够权重,它照样能流畅地编出”第 XX 行有问题”,但那一行根本不存在,或者压根不是那么回事。
- 引用不存在的字段 / 配置:日志里没有的东西,它也可能一本正经地引用,因为它”猜”这里应该有个
timeout参数。 - 编造 API / 版本行为:比如凭空捏造一个 “Java 8 里
Optional.map会 XXX” 的细节,说得有鼻子有眼,其实查无此事。
5.2 为什么这种错最坑人
因为它非常流畅、非常自信。一个逻辑错误的回答,和一个正确答案,在”语法通顺、语气笃定”上几乎一模一样。你很难靠”读起来像不像”来分辨,只能靠”验证”来分辨。
这正好呼应了前面的老法师比喻:会背案例库但不下机房的新人,最危险的不是他不懂,而是他遇到没背过的题时,会自信地编一个出来。
六、可靠修 bug 的正确姿势:猜 + 验 两层
那大模型就不可靠、不能用了吗?不是。关键在于别让它”裸奔”,要给它接上能验证的工具。
6.1 模型负责”猜”,工具负责”验”
回顾第一篇《从大模型到 Agent》的结论:模型是”只会想、不会做”的大脑,Agent 是”大脑 + 手脚 + 记忆 + 目标”。修 bug 这件事,天然适合这个结构:
模型(猜): "我怀疑 selectById 返回 null 导致 NPE,建议第 44 行判空。"
↓ 这只是个猜想,不保证对
工具(验): read_file 真正读文件 → bash 真的跑一遍单测 → 看真实返回值
↓ 真实结果喂回模型
模型(再猜): "测试还是挂,但这次是 mapper 为 null,不是返回值……"
↓ 再验
…… 循环,直到工具验证通过
6.2 走一遍完整闭环
以开头那个 NPE 为例,一个接上工具的大模型(比如 Cursor / 各类 Agent)实际是这样工作的:
1. 模型提猜想:怀疑 selectById(id) 返回 null → .getName() 崩了
2. 调 read_file 工具:真实读到 OrderService.java 第 44 行的完整上下文
3. 调 bash 工具:真实跑一个测试 / 或打印 mapper 的类型,看它到底是 null 还是返回值是 null
4. 工具返回真实结果 → 模型据此修正:哦,mapper 本身是 null(没注入),根因变了
5. 模型再提修正方案 → 再验证 → 直到测试真的绿了
注意第 4 步的价值:真实反馈把”猜”拉回了”验”的轨道。 模型第一次猜的是”返回值 null”,但工具一跑,发现是”mapper 没注入导致 mapper 为 null”——这个修正,是模型靠”猜”永远猜不出来的,只有真实执行能暴露。
6.3 一句话划清边界
模型 = 一个看过海量案例、极会”猜答案”的资深专家;但它没有手,不能真去现场验证。 Agent 那层工具 = 它的手脚,负责把”猜”变成”验”。
所以”模型分析 bug”这件事,真正的价值判断标准不是”它答得对不对”,而是”它有没有被允许去验证“。裸聊的模型只能猜,接上工具的模型才能查。
七、心法:什么时候信它,什么时候必须自己验
落到日常使用,给你几条能直接用的判断线:
7.1 它很可靠的部分
- 常见 bug:NPE、越界、并发写冲突、连接池耗尽、SQL 慢查询——这类”样本多”的题,它的模式库厚,命中率高。
- 语法 / API 正确性:某个方法怎么拼、某个注解什么作用,这类纯知识题,它背得最熟。
- 解释栈 / 日志翻译:把一坨
Caused by栈翻译成人话、指出”根因在哪一层”,它非常擅长,因为训练数据里全是这类配对。 - 代码模式识别:一眼看出”这段是策略模式”“这里违反了单一职责”,属于它的强项。
7.2 它容易翻车的部分
- 罕见 / 私有系统 bug:你公司内部框架特有的坑,训练数据里没有,它只能硬套,最危险。
- 依赖真实运行时状态:某个变量在你的环境里此刻到底是什么值,它不知道,只能猜。
- 精确的时序 / 内存问题:GC 停顿、死锁时序、内存泄漏的精确位置——这类需要”现场证据”的,它纯靠猜,命中率低。
7.3 一条总原则
让它提猜想,让它列排查清单,让它写复现脚本——但”最终结论”一定要过一遍真实执行的验证。
具体到操作:让它跑单测、跑复现脚本、打印真实值,把结果喂回来;而不是让它只凭你贴的一截代码就”拍板”说”问题在 X”。
总结
一句话心智模型
你贴的代码 + 日志 + 问题
↓ 全被切成 token,摊平成一条序列
模型做一次前向计算:
自注意力把"报错信息"和"可疑代码"软关联
↓
照着训练时背过的海量"症状 → 修复"模板
↓
续写出一段"最像诊断"的话
用一段话记住:
它把你的代码和日志当成一串字,用注意力把”报错”和”可疑代码”关联起来,再照着背过的海量案例,续写一段最像诊断的话。它快、它广,但它不执行、不验证——真要可靠,得给它接上能真实运行的工具,让”猜”落地成”验”。
系列导航(这一篇在其中的位置)
| 篇 | 主题 | 回答的问题 |
|---|---|---|
| 1《大模型无状态》 | 客户端怎么反复重发历史、上下文窗口 | 它为什么”记得”又不”记得” |
| 2《从大模型到 Agent》 | 大脑 / 手脚 / 记忆 / 目标怎么拼 | 它怎么从一个”只会说”的大脑变成能干活的 Agent |
| 3《模型怎么跑起来、怎么变聪明》 | 推理的前向计算 + 训练的反向传播 | 它内部到底怎么算、怎么被练出来 |
| 4(本篇)《让大模型修 bug 时它脑子里发生了什么》 | 注意力对焦 + 模式续写 + 猜/验两层 | 它”分析 bug”这件事,底层到底在干嘛 |
四篇连起来是一条完整的链:无状态(壳怎么调用)→ Agent(壳怎么变人)→ 内核(权重怎么算、怎么练)→ 落地(修 bug 时它到底在做什么)。 读完这一串,你对”大模型”这个词,应该已经从”黑盒”变成了”一张能拆开的图”。