CMU AI AGENTS / 同一个循环,进入三种真实环境 / 第 24 课

修一个报错,Agent 为什么先搜文件而不是直接改代码?

代码任务通常需要定位、理解依赖、修改与运行验证。仓库级观察决定修改位置;单函数补全能力不能直接代表仓库修复能力。

完整讲解 · 条件对照 · 理解检查所在单元与学习顺序官方课件全页浏览

修一个报错,Agent 为什么先搜文件而不是直接改代码?

前面我们已经知道如何发出动作、收回结果,再决定下一步。把库存文件换成代码仓库,这条循环仍然成立;变化在于,程序真正执行的行为可能经过多个文件。报错出现的位置,不一定就是造成错误的位置。本课用原课的重试次数例子走完定位、修改、验证,不讨论大型仓库索引的全部实现。

先让错误在一个小输入上出现

用户设置 retries: 0,希望禁用重试,程序却重试了三次。最容易想到的修法是找到屏幕上打印“重试 3 次”的那一行,把数字改成 0。这会改变显示,未必改变网络请求。另一种快修是把默认值 3 改成 0;它让当前例子看起来正确,却可能让没有配置重试次数的用户也失去默认重试。

先写清行为约定:缺少配置或值为 None 时使用默认 3;明确给出 0 时保留 0;给出 2 时保留 2。现在不是泛泛地说“有 bug”,而是知道四种输入应该产生四个可核对的输出。这个约定也帮助 Agent 判断哪些文件和测试与任务有关,避免被报错附近的无关文字带走。

官方原课件 · Lecture 6 · PDF 第 34 页原课先定位表达式,再复现零被改成三
第六讲PDF第34页:rg找到config.get(retries) or 3,测试test_zero出现3不等于0。

原图左侧搜索 retries,找到默认值表达式;右侧只运行零值的最小测试,得到 3 != 0。值得留意的是顺序:搜索提供候选原因,运行提供可以被否定的观察。原课没有让 Agent 把测试期待值改成 3 来消除失败。

沿着值走,比沿着文件名猜更可靠

把这次数据流摊开:命令行读到 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。这个修复没有靠记住报错文本,而是调整了分支判断的语义。

官方原课件 · Lecture 6 · PDF 第 33 页定位—编辑—验证构成闭环
第六讲PDF第33页列出仓库文件、定位零在哪里变默认值、仅在None时默认、检查零和默认与调用者回归。

沿图中的三个动作读:Localize 问值在哪里改变,Edit 改默认规则,Verify 同时检查零、默认和调用者。验证失败会返回新的假设,而不是永远向前走。文件列表只是观察入口,真正贯穿图的是同一个行为问题。

改一个位置,看结果为什么不同

下面是根据原课机制构造的三条教学轨迹,不是一次真实仓库执行日志。每条轨迹都用同样的四项行为约定判断,不能只看“测试命令退出了”。先预测:改输出文字、改默认值、改缺失条件,哪种能同时保住四种输入?

观察过程 · 改变条件

三种修法,哪一种修到了原因?

先预测四种输入的输出,再逐步查看每条轨迹。

教学构造场景 · 按下方规则逐步回放;不调用真实模型,不代表实际成功率。

这一轮的输入

输入依次为缺失、None、0、2;期望3、3、0、2

  1. 1. 定位到日志

    把输出中的重试3次替换成0次。

  2. 2. 核心规则未变

    config.get(retries) or 3 仍将零映射为三。

  3. 3. 重新观察执行

    实际输出3、3、3、2;零值案例仍失败。

定位到日志

把输出中的重试3次替换成0次。

1 / 3
先预测,再核对结果与原因四项只满足三项。

显示变化没有改变执行规则。

两个容易混淆的边界

第一,搜索结果是候选证据,不是最终结论。仓库可能有同名配置、测试夹具、旧版本或生成文件。找到一处 retries 后,还要确认运行路径实际调用它。可以跟调用点、导入关系和最小复现联系起来;如果改完没有改变观察,应该检查假设,而不是扩大替换范围碰碰运气。

第二,最小复现不等于完整验收。最小测试让一次观察更便宜、更清晰,但它刻意去掉了很多上下文。修复后应重新接回调用者和原有测试。假如 CLI 把空字符串当成缺失,配置函数对 None 的修复可能正确,CLI 仍需要自己的边界检查。不要把某一层的正确性自动推广到整条路径。

你要拍板的不是使用哪条搜索命令,而是这次修改的因果解释是否成立:哪条输入触发错误,哪个状态转换违背约定,改动为何只影响这个转换,用什么反例检验没有破坏其他行为。可交付的修复报告应把这些联系起来,并给出运行的测试结果,避免只有“已优化代码”之类无法核对的话。

自己定位一次

日志显示重试了三次,为什么不能直接把日志中的3改成0?

日志是行为的观察,不一定控制实际重试。要追踪重试次数怎样进入执行循环;改显示可能只是让错误不可见。先复现实际调用次数,再确认值变化位置。

为什么把默认值改成0不能算完整修复?

因为行为约定要求缺少配置时仍返回3。它解决了零值案例,却破坏默认案例。正确修复区分缺失与明确的零,而不是把所有输入都导向零。

修改后test_zero通过,但调用者仍重试三次,下一步是什么?

重新沿值追踪调用路径,确认测试和调用者是否使用同一个实现、有没有覆盖配置或缓存旧值。新观察否定了“这一处修改足以修好整条路径”的假设,不应该靠删除失败检查来接受它。

本课的锚点是:先确定值在哪里变错,再改变那条规则。下一课的问题随之出现:即使找对了规则,怎样把修改可靠地应用到正在变化的文件?