修一个报错,Agent 为什么先搜文件而不是直接改代码?
前面我们已经知道如何发出动作、收回结果,再决定下一步。把库存文件换成代码仓库,这条循环仍然成立;变化在于,程序真正执行的行为可能经过多个文件。报错出现的位置,不一定就是造成错误的位置。本课用原课的重试次数例子走完定位、修改、验证,不讨论大型仓库索引的全部实现。
先让错误在一个小输入上出现
用户设置 retries: 0,希望禁用重试,程序却重试了三次。最容易想到的修法是找到屏幕上打印“重试 3 次”的那一行,把数字改成 0。这会改变显示,未必改变网络请求。另一种快修是把默认值 3 改成 0;它让当前例子看起来正确,却可能让没有配置重试次数的用户也失去默认重试。
先写清行为约定:缺少配置或值为 None 时使用默认 3;明确给出 0 时保留 0;给出 2 时保留 2。现在不是泛泛地说“有 bug”,而是知道四种输入应该产生四个可核对的输出。这个约定也帮助 Agent 判断哪些文件和测试与任务有关,避免被报错附近的无关文字带走。
沿着值走,比沿着文件名猜更可靠
把这次数据流摊开:命令行读到 0,解析器把它转为整数,配置函数决定默认值,调用者按照返回值安排重试。Agent 要检查在哪一个边界,0 第一次变成 3。如果解析器已经错误地丢掉 0,修改最后的打印函数不会修复原因;如果解析器保留了 0,就应该继续向配置函数走。
原课找到的表达式是 config.get("retries") or 3。在 Python 中,整数 0 是假值,所以表达式选择右边的 3。它区分的是“值是否为真”,而任务需要区分的是“是否缺失”。两个问题看起来接近,实际把零这个合法值分到了错误分支。定位成功的标志不是找到关键词,而是能把输入经过该表达式后的输出算出来。
改进后的规则是先取出 value,仅当它为 None 时返回 3,否则保留原值。代入 0:不是 None,输出 0;代入 2:不是 None,输出 2;代入 None:输出 3。这个修复没有靠记住报错文本,而是调整了分支判断的语义。
改一个位置,看结果为什么不同
下面是根据原课机制构造的三条教学轨迹,不是一次真实仓库执行日志。每条轨迹都用同样的四项行为约定判断,不能只看“测试命令退出了”。先预测:改输出文字、改默认值、改缺失条件,哪种能同时保住四种输入?
三种修法,哪一种修到了原因?
先预测四种输入的输出,再逐步查看每条轨迹。
教学构造场景 · 按下方规则逐步回放;不调用真实模型,不代表实际成功率。
输入依次为缺失、None、0、2;期望3、3、0、2
- 1. 定位到日志
把输出中的重试3次替换成0次。
- 2. 核心规则未变
config.get(retries) or 3 仍将零映射为三。
- 3. 重新观察执行
实际输出3、3、3、2;零值案例仍失败。
把输出中的重试3次替换成0次。
先预测,再核对结果与原因
四项只满足三项。显示变化没有改变执行规则。
两个容易混淆的边界
第一,搜索结果是候选证据,不是最终结论。仓库可能有同名配置、测试夹具、旧版本或生成文件。找到一处 retries 后,还要确认运行路径实际调用它。可以跟调用点、导入关系和最小复现联系起来;如果改完没有改变观察,应该检查假设,而不是扩大替换范围碰碰运气。
第二,最小复现不等于完整验收。最小测试让一次观察更便宜、更清晰,但它刻意去掉了很多上下文。修复后应重新接回调用者和原有测试。假如 CLI 把空字符串当成缺失,配置函数对 None 的修复可能正确,CLI 仍需要自己的边界检查。不要把某一层的正确性自动推广到整条路径。
你要拍板的不是使用哪条搜索命令,而是这次修改的因果解释是否成立:哪条输入触发错误,哪个状态转换违背约定,改动为何只影响这个转换,用什么反例检验没有破坏其他行为。可交付的修复报告应把这些联系起来,并给出运行的测试结果,避免只有“已优化代码”之类无法核对的话。
自己定位一次
日志显示重试了三次,为什么不能直接把日志中的3改成0?
日志是行为的观察,不一定控制实际重试。要追踪重试次数怎样进入执行循环;改显示可能只是让错误不可见。先复现实际调用次数,再确认值变化位置。
为什么把默认值改成0不能算完整修复?
因为行为约定要求缺少配置时仍返回3。它解决了零值案例,却破坏默认案例。正确修复区分缺失与明确的零,而不是把所有输入都导向零。
修改后test_zero通过,但调用者仍重试三次,下一步是什么?
重新沿值追踪调用路径,确认测试和调用者是否使用同一个实现、有没有覆盖配置或缓存旧值。新观察否定了“这一处修改足以修好整条路径”的假设,不应该靠删除失败检查来接受它。
本课的锚点是:先确定值在哪里变错,再改变那条规则。下一课的问题随之出现:即使找对了规则,怎样把修改可靠地应用到正在变化的文件?