昨天晚上睡觉前,我写了一份长长的 plan,让 Agent 照着实现。今天早上起来验收,发现它被一个有 bug 的脚本卡了六个多小时……

这已经不是第一次了。最近几个月,这个问题一直阴魂不散。我经常不得不手动打断 Agent,然后教育它:“你要把超时时间设短一点!这样是要等到什么时候?”我甚至写了一个让长命令自动转入后台的插件 pi-bg,拦截设置了较长 timeout 的调用,把任务放到后台运行。

可模型还是动不动就设一个超大的 timeout,然后在那里傻等。

和同事聊起这件事,大家觉得,可能是模型对物理世界里的时间没什么概念吧。

让英雄查英雄,让好汉查好汉

真是这样吗?它到底为什么要设这么久?

上午到公司之后,我决定直接问问模型。结果它给了我一个挺意外的解释。

模型解释 Pi bash timeout 的秒与毫秒混淆

它很可能把一个以毫秒计的数值,填进了一个按秒解释的参数里。

Claude Code 的 Bash timeout 单位是毫秒,Pi 的则是秒。60000 在前者是一分钟,到了后者就成了约 16.7 小时。即使 Pi 在 tool schema 里明确写了 seconds,模型有时还是会传入一个只有按毫秒理解才合理的大数。Claude Code 的超时配置、Pi 的 bash 实现

这和模型怎样学会用工具写代码有关。在后训练阶段,一种做法是让模型在沙箱里完成编程任务:读取仓库、修改代码、调用终端跑测试,再根据执行结果继续修改。训练系统用测试是否通过等反馈评价表现,通过强化学习更新模型。Qwen3-Coder 就公开介绍过这类多轮工具交互训练。Qwen3-Coder 的后训练介绍

模型在这个过程中学到的,也可能包括具体工具的使用习惯。如果它熟悉的环境按毫秒计时,换到 Pi 之后,就可能沿用那套数值。具体模型用了多少 Claude Code 相关训练数据,外部很难确认;但这种单位习惯的迁移,确实能解释日志里那些看起来离谱的 timeout。

有点像开惯了左舵车的人,突然换成右舵车。规则知道了,手上的习惯却未必马上改得过来。

不要跟模型较劲

Pi 社区早就有人遇到过类似问题。我翻了 Issue 区,#9785 直接讨论了秒与毫秒混淆,#1335 则建议增加默认超时,目前都已关闭。维护者在后者的回复里表示,很难为所有任务选一个合适的默认值,应该由 Agent 自己设置,也可以通过 AGENTS.md 引导它。

这个理由回答了“默认超时该设多少”,但单位误用还是会发生。我担心后来的人还会继续踩坑。顺便吐槽一句:新贡献者提的 Issue 默认会被自动关闭,再由维护者筛选是否重开,这套流程确实给我一种“高攀不起”的感觉。

这让我开始重新考虑 Agent 里工具接口的设计。

你当然可以说:接口定义已经写得清清楚楚了,是秒,模型应该照着用。但当同一种错误反反复复出现,是不是也该回头看看接口设计?

设计工具时,我会优先考虑模型已经熟悉的用法。如果它在训练中反复使用过类似工具,很多操作习惯就已经学进了参数里,没有必要每换一个 harness,就要求它重新适应一套约定。

比如 Claude Managed Agents 的 memory,会把记忆存储挂载成沙箱里的一个目录,让 Agent 用普通文件工具读写。官方文档介绍了这种实现。这个选择就很顺手:模型本来就熟悉文件系统,读取、搜索和编辑文件的能力可以直接用在管理记忆上。

如果确实需要设计一个陌生工具,tool schema 就得把职责、参数含义和返回结果讲清楚,最好再给几个使用示例。模型要靠当前上下文弄明白这个工具怎么用。

我更警惕的是 Pi timeout 这种情况:工具名和参数名都很熟悉,里面却藏着一个不同的约定。模型很容易照着已有习惯调用,出错后还不一定马上得到反馈。设计新接口时,把单位直接写进 timeout_ms 或 timeout_seconds 这样的参数名里,就能少藏一层歧义;已有接口也可以在数值可疑时给出明确提示,让 Agent 有机会纠正。

不要跟模型较劲。模型已有的习惯,也应该是工具设计的输入条件。

别一边强迫左撇子用右手打球,一边嫌它打得不好。