← 课程地图
第 02 章 · 实战之旅
一行修复的一生
从一个真实的 GitHub bug 报告,到一道 SWE-bench 题目,再到 Agent 的轨迹与训练数据——全程真实数据,无一虚构。
主角:sympy__sympy-20590 · 按 → 开始 · 建议先看完第 01 章 · 节点可点击直达
概念不熟?先过第 01 章 · 概念骨架 →
第 1 幕 · PR 怎么变成任务
2020 年 10 月,一个 bug 报告
github.com/sympy/sympy · issue #20590 · 原文摘录
Symbol instances have __dict__ since 1.7?
In version 1.6.2 Symbol instances had no __dict__ attribute:
>>> sympy.Symbol('s').__slots__ # 1.6.2
('name',)
>>> sympy.Symbol('s').__dict__ # 1.7 → 多出来了!
1
用户发现:升级 1.7 后 Symbol 多了 __dict__
每个实例多背一个字典——百万级符号运算时内存显著膨胀
2
危害:实例内存膨胀,违背 不可变设计
Symbol 本应像数字一样轻量不可变——带 dict 就可能被塞私有属性
3
关键:报告者只知道现象,不知道根因
这正是好题面的特征——解题者必须自己定位问题源头
这段话将原封不动成为题目的 problem_statement
第 1 幕 · PR 怎么变成任务
有人修了它:一行代码 + 一个测试
该 PR 的 diff(两处改动 · 真实内容)
--- a/sympy/core/_print_helpers.py (修复)
+ __slots__ = () ← mixin 声明空 slots,子类不再被迫带 __dict__
--- a/sympy/core/tests/test_basic.py (新增测试)
+def test_immutable():
+ assert not hasattr(b1, '__dict__')
+ with raises(AttributeError):
+ b1.x = 1
1
根因:Printable mixin 没声明 __slots__,拖累全部子类
Python 的继承规则:父类无 slots,子类实例就会带上 __dict__
2
修复:__slots__ = () 一行,附两行注释
真正的根因修复往往极短——难在找到它,不在写它
3
PR 自带测试 test_immutable——裁判也来了
断言实例无 __dict__、塞属性必抛错——把「修好了」变成可判定的事实
题面、答案、判分——一道题的三要素凑齐了
第 1 幕 · PR 怎么变成任务
提炼:机器从 PR 抠出 4 个字段
| PR 里的东西 | 变成字段 | 角色 |
| issue 正文 | problem_statement | 题面——Agent 读到的一切 |
| PR 代码 diff | patch | 答案——评分对照,不给 Agent 看 |
| PR 测试 diff | test_patch | 评测——判分合约,也不给看 |
| 合并基点 | base_commit | 起点——仓库快照,hash 锁死 |
5
还要过机器筛选:修前测试必须真的挂、题面不得泄漏答案
并非每个 PR 都能出题——「真题」是一道道筛出来的
四个字段 + 两道筛子——一道 SWE-bench 题的诞生
第 1 幕 · PR 怎么变成任务
这道题的 jsonl 真身
swe-bench-lite.jsonl 中的一行(字段截断展示)
{
"instance_id": "sympy__sympy-20590",
"repo": "sympy/sympy",
"base_commit": "cffd4e0f86fe...",
"problem_statement": "Symbol instances have __dict__ since 1.7?...",
"patch": "diff --git a/sympy/core/_print_helpers.py...",
"test_patch": "diff --git a/sympy/core/tests/test_basic.py...",
"FAIL_TO_PASS": ["test_immutable"],
"PASS_TO_PASS": ["test__aresame", "test_structure", ... 共 21 个]
}
1
F2P ×1:修好前挂、修好后必须过——证明修对了
就是屏 2 新增的 test_immutable——这道题唯一的「新裁判」
2
P2P ×21:原有测试一个不能挂——证明没改坏
test_structure、test_equality 等 21 个旧测试全部在场——防回归网
第 2 幕:把这道题交给 Agent——一场闭卷考试
第 2 幕 · Agent 解题与轨迹
Agent 拿到什么 · 看不到什么
拿到 ✓
考卷与工具
- problem_statement——issue 原文,一字不差
- base_commit 时刻的完整仓库——可读可改可跑
- 一套工具:读文件、改文件、跑命令
看不到 ✗
答案与裁判
- patch——开发者的修复,评分对照
- test_patch——新增测试,判分合约
- 目标只有一个:让隐藏测试变绿
像闭卷考试——考卷只有题面,没有答案
第 2 幕 · Agent 解题与轨迹
解题循环真跑起来
本题典型轨迹(示意 · 与真实记录同构)
① read sympy/core/symbol.py → 找 Symbol 定义
② grep "Printable|__slots__" 全仓 → 锁定 mixin
③ read sympy/core/_print_helpers.py → Printable 无 __slots__
④ think 根因 = mixin 未声明 slots,子类被迫带 __dict__
⑤ edit _print_helpers.py → 添加 __slots__ = ()
⑥ bash pytest test_basic.py -k immutable
test_immutable PASSED ✓
1
看 → 想 → 做,循环推进直至测试变绿
每一步的输入输出都被记进轨迹——这就是「过程」的原材料
2
好轨迹:6 步直达根因,一次改对
定位准(一次 grep 锁定)、改动小(1 文件 1 行)、验证闭环(跑测试确认)
这是顺的一版——绕 60 步的版本同样存在
第 2 幕 · Agent 解题与轨迹
轨迹里记了什么
1
每一步:调了什么工具、什么参数、返回什么
read 了哪个路径、edit 了哪几行、bash 输出了什么——全程留痕
2
每一步:token 与耗时——成本可审计
哪一步烧了多少钱、卡了多久,事后一目了然
3
结尾:model_patch(Agent 的答卷)+ %Resolved
答卷与判分封存在轨迹尾部——结果维度与过程维度在此汇合
4
全程 append-only——只增不改,像飞机黑匣子
不能事后美化、不能删除污点——分析才有可信的事实基础
过程维度的所有分析,都从这份「黑匣子」出发
第 2 幕 · Agent 解题与轨迹
同一道题,两条轨迹
轨迹 A · 6 步直达
一次改对
- 定位准:一次 grep 锁定 Printable
- 改动小:1 个文件、1 行代码
- token 少、耗时短、验证闭环
- resolved ✓——可作示范教材
轨迹 B · 60 步绕路
试错后蒙对
- 反复读同一批文件,重复检查
- 改错多处又回滚,最后凑巧改对
- token 数十倍、耗时数十倍
- 也 resolved——但不可示范、不可复用
结果维度 A = B(都对);过程维度 A ≫ B——训练该学谁?
第 3 幕 · 轨迹变训练数据
原始轨迹 ≠ 训练数据
1
工具输出动辄几万 token——直接训练会爆上下文
一次 read 可能带回整份源文件;跑一次测试输出几十屏日志
2
失败轨迹混在里面——全学就是学会失败
未 resolved 的轨迹若无区分地进训练集,模型会模仿坏习惯
3
死循环、重复、跑偏——噪声先洗掉
同一文件读 8 遍、无效命令来回试——这些段落学进去就是浪费
4
训练要的是 messages 数组——格式也对不上
轨迹是事件流,训练要对话样本——中间隔一道转换工序
所以有一道「手工处理 + 标注」的工序
第 3 幕 · 轨迹变训练数据
SFT:轨迹 → messages 数组
清洗后的一条 SFT 样本(OpenAI 风格 · 截断)
[
{"role": "system", "content": "你是代码修复 Agent..."},
{"role": "user", "content": "Symbol instances have __dict__ ..."},
{"role": "assistant", "content": "先看 Symbol 的定义",
"tool_calls": [{"function": {"name": "read",
"arguments": {"path": "sympy/core/symbol.py"}}}]},
{"role": "tool", "content": "class Symbol(Atom, ...): ..."},
{"role": "assistant", "content": "根因在 Printable mixin,加 __slots__ = ()"}
]
1
截断超长工具输出,只保留关键行
几十屏 pytest 日志压成「PASSED / FAILED + 关键报错」
2
删掉死循环与重复段,保留有效推理
读 8 遍同一文件只留 1 遍;关键判断一步都不能少
3
只留 resolved 轨迹,或 A/B 对比保留
对比保留能让模型同时学「好路径」与「该避开的路径」
这份手工活,决定模型学到的是「节奏」还是「垃圾」
第 3 幕 · 轨迹变训练数据
标注:给轨迹贴上标签
结果标签 · 机器判
resolved 与否
%Resolved 说了算——跑一遍判分 harness 即得,无需人工
过程标签 · 人工 / LLM judge
哪步关键、哪步浪费
逐步打标:关键转折、无效动作、重复检查——训练信号更细
质量分级 · 三档
A / B / C
A 直达 · B 绕路但成 · C 失败但有学习价值——不同档不同用法
反作弊 · 人工抽查
有没有骗绿
改测试文件、特判输入绕过判分——P2P 网之外的最后防线
本仓 analysis 六子模块,做的正是后三类
第 3 幕 · 轨迹变训练数据
RL:轨迹 = rollout
1
轨迹就是 rollout——Agent 自己采样自己试
同一道题采样 N 条轨迹,天然形成对比组
2
reward 不用人工标:%Resolved 是可验证 reward
测试跑出来就是客观信号——不像写作那样需要主观打分
3
P2P ×21 在防 reward hacking——改测试骗绿会被逮住
只把新测试改绿不行:21 个旧测试任何一个挂掉就判负
4
人工的价值,移到「出题」与「反作弊」
判分自动化后,人去做机器做不了的两端
SFT 学「怎么做」,RL 学「哪条路更好」
终章 · 闭环
这就是 Agent 自我演化
评测 → 分析 → 构造 → 训练 → 再评测 · 本平台三条主线的「自我演化」闭环
下一章 · 上下文工程 →
1 / 14
← → 翻页 · 深挖 ↗ 新标签 · Esc 退出
专注模式 · 第 02 章 实战之旅 · 生成时间:2026-09-11(v2.1 章节化重构) · agentsoft-research-platform teaching-web-platform