上个月 dev.to 有篇讲 bug 的文章,转得挺开。
作者叫 Archana,写她那个 agent 里的五个 bug。五个 bug 有个共同点,看完有点凉。没有一个抛异常。
简单说,程序从头到尾都是"正常"的。测试跑过,demo 跑过,日志干净。但里面好几个零件在空转。
项目是个读代码评审历史、记住既有约定的 agent。数据是 pandas 的 86,321 条评论,提炼出 298 条约定。开发用了两周半。
这期只收这一条。五个 bug,值五条。
一个引号,绕过"不知道就拒答"
她的 agent 有个核心保证,不知道就拒答。靠模型返回的 JSON 里一个 answered 字段撑着。
用 result.get("answered") 判断的时候,字符串 "false" 是非空字符串。非空即真。
于是拒绝回答被当成有效回答。整条防线,一个引号攻破。
这不是 LLM 的问题,是 Python 的问题。但它挑了一个最贵的地方犯。任何"让模型替我判断对错"的设计,第一件要写清楚的事永远是类型。
修第一个 bug 修出来的
提取规则的提示要求 rationale 只在维护者给了理由时填。维护者直接陈述约定时,模型返回 null。
null 是合法 JSON,意思是"没有理由"。
但 DraftedRule 里 rationale 声明成 str,默认空串。Pydantic 不收 null。验证又是全有或全无,整个响应被丢掉,换成默认值。
下游把默认值理解成"模型拒绝回答"。一条真实的维护者修正,就这样被记成 ignored,理由栏写着 no convention stated。
她原话是六次复现六次失败。这类 bug 难的地方在于,失败长得跟成功一模一样。
79 条规则,68 条匹配不到任何文件
规则存的是文件匹配模式,她用 fnmatch 匹配。模型生成的是 SQL LIKE 风格的模式,pandas/core/% 这种。
% 不是 fnmatch 的通配符。pandas/core/frame.py 对 pandas/core/% 的结果是 False。
79 条路径规则,68 条匹配不到东西。agent 实际只靠两条写成 pandas/**/*.py 的规则活着。这两条覆盖仓库约 63% 的文件。问题一周内看不出来。
修复只要一行,把 % 换成 *。
模型不知道自己在写哪种通配符语法。提示里没要求 SQL LIKE,文档里也没写。但它知道这些规则最后要写进数据库,大概是从那儿推的。这类猜测通常对,这次错了。
没有阈值可用
这条我觉得是全文最值得看的。
agent 给 PR 评论,靠嵌入变更描述、找最近的规则来挑约定。它评论了 40 个 PR 里的 38 个。
作者一开始以为是阈值没调好。调了才发现,阈值 1.05 覆盖 95% 的 PR,0.95 只覆盖 18%。改一个参数,行为差六倍,这本身就不对劲。
她换了个办法,不看阈值,看距离分布。
对 25 个真实 PR,量最近规则和第十近规则的距离差多少。四种提问写法都试了。差距中位数是 0.097、0.100、0.105、0.095。
最近的规则并不比第十近的明显更近。这套嵌入在这批数据上没有区分度。任何一个阈值都是假的。
她现在的做法是用路径匹配这种事实性条件先筛,相似度只负责给筛出来的候选排序。这个思路可以直接抄。相似度拿来排序,别拿来当门槛。
字段没错,是含义飘走了
GitHub 的 author_association 描述的是"现在有没有写权限",不是"评论那会儿是什么身份"。
一位 pandas 评审者在 2012 到 2025 年间写了 74,077 条评论,是第二名的两倍多。人已经离开组织,所以这些评论全部返回 CONTRIBUTOR。
用这个字段过滤维护者评论,会丢掉约三分之一的语料,包括项目里最有经验的评审者。全程没有报错,没有警告。只表现为一个看起来挺合理的、小一点的数字。
这条跟 LLM 没关系。字段还是那个字段,是它的含义在时间轴上漂了。
作者自己怎么收口
五个 bug 没一个抛异常。其中四个是靠数数发现的。发言频率、规则命中数、距离差距。
她对比了库用错和模型用错的区别。库会明确报错。模型会给你一个格式规整、语气确定的输出,内容跟你的意图对不上。
她的建议是,给系统定几个"正常时应该成立"的指标,然后真的去看。检查的重点从"有没有输出"挪到"agent 的发言比例对不对、引用能不能解析出来"。
以前调 bug 是看哪里崩了,现在得先问哪里该动、有没有动。
评论区那条我只懂一半
Vinh Nguyen 指出,Pydantic 2.13.5 里 answered: bool 会把 "false"、"False"、"no"、"0" 都转成 False,但 "unknown" 或者散文式的模糊回答会触发 ValidationError,整个对象像 null rationale 那样被丢掉。
作者确认了测试结果。answered=null 加 answer
