大家好,我是Zack。
10 月 5 号晚上我让 agent 批量回 24 条评论,跑完一数:重发了 1 条,漏了 6 条,24 条目标总共跑了三十几次。
我第一反应是模型不行。
后来翻了一遍自己写的 prompt,问题在我这边 —— 里面有两个字:尽量。
一、原来的做法:写完 prompt 就直接跑
那批回复的 prompt 我是这么写的(大意):
去我主页下面找最近 24 条评论,尽量都回复一遍。
尽量找到回复按钮,找不到就跳过。
语气尽量自然一点。
跑完看 queue/reply_batch.log,长这样:
#7 @jasonjz23 OK
#8 @Tsui1264 FAIL_NO_REPLY_BTN
#9 @taiyanghere FAIL_NO_DIALOG
#10 @Junhao88888 FAIL_MISMATCH
#15 @EricTanvo ERR page.goto failed: net::ERR_ABORTED
#24 @Lu202409Lu FAIL_NO_REPLY_BTN
6 条挂了,还分三种不同的挂法。
我当时的做法是:把报错贴回去,让它再跑一遍。
结果第二轮有的从 FAIL 翻成 OK,有的又冒出新的 FAIL_MISMATCH。
两轮加起来 40 分钟,其中差不多 20 分钟是在修重发和补跑。
二、真正卡住的地方
我盯着那三行 尽量 看了一会儿,发现问题很直白:
「尽量找到回复按钮」这句话,模型没有一个可以核对的标准。
它点到了就算做到,没点到也可以说「我尽力了」。
同样的 prompt 跑两次,第一次点开对话框、第二次没点开,结果就不一样 —— 这不是模型抽风,是任务本身就没定义清楚。
10 月 5 号我还踩了同一类坑的另一个版本:verify_state.js 连跑两轮,第一轮 #4 = ALREADY_POSTED,第二轮同一条翻成 UNKNOWN,5 条里确认数从 1 掉到 0。
我一开始以为是脚本 bug,最后发现根因还是同一件事:判定标准是「页面上有没有出现某句文案」,而那句文案会渲染延迟。
标准不稳,结果就飘。
三、换成的做法:三处把模糊换成可数
1. prompt 里的形容词,换成能数出来的约束
我把那三行改成了这样:
只处理 queue/reply_targets.json 里列出的 24 条,不要新增、不要跳过。
点回复按钮的条件:页面上存在 aria-label 为 "Reply" 的按钮;不存在就记 FAIL_NO_REPLY_BTN 并跳过。
语气:一句话,不超过 30 字,不用表情,不提产品。
改之前和改之后的对照:
| 我原来写的 | 现在写的 |
|---|---|
| 尽量少改文件 | 只允许改这三个文件:a.js、b.js、c.py |
| 尽量保留原格式 | 不含 TODO 的行一个字都不动 |
| 合适的时候合并 | 两个文件改动都 ≤ 5 行时才合并 |
| 尽量找到回复按钮 | 存在 aria-label="Reply" 才点,否则记 FAIL_NO_REPLY_BTN |
| 跑快点 | 单条超时 15 秒,超时记 FAIL_TIMEOUT |
判断标准就一条:改完之后,能不能让一个没看过上下文的人,只凭这句话判断 agent 做对了没有。
判断不了,说明它还是模糊的,继续改。
我现在的习惯是写完 prompt 先数一遍:里面有几个「尽量」「合适」「适当」「一些」「相关」。
数出来 5 个以上,这遍大概率要白跑。
懒得数的,把 prompt 存成文件跑这一行:
grep -o -E "尽量|合适|适当|一些|相关|差不多|尽可能" prompt.txt | sort | uniq -c | sort -rn
输出会直接告诉你哪个词出现了几次。我自己那条 24 条回复的 prompt,尽量 命中 3 次。
2. 批量脚本第一次跑,固定加 --dry-run
让 agent 写批量处理脚本的时候,我在要求里写死一句:
脚本必须支持 --dry-run:只打印将要改动的文件清单和改动内容,不落盘。
跑的时候先干跑一遍:
$ node scripts/rename_batch.js --dry-run
[DRY-RUN] 将改动 3 个文件:
- src/a.js 行 12: get_user -> get_user_info
- src/b.js 行 8: get_user -> get_user_info
- src/c.py 行 41: get_user -> get_user_info
[DRY-RUN] 未匹配 0 个,跳过 0 个
清单对得上,再去掉参数真跑。
匹配规则最容易写宽。我有一次规则写得太松,把测试文件一起扫进去了,回滚花了半小时。
多花一分钟看清单,省的是回滚那半小时。
3. 报错要原文,别让它总结
这条我现在直接写进 prompt,一个字不改地复制过去:
报错处理要求:
- 原始报错整段贴回来,包含 exit code、行号、文件路径
- 不要改写成「构建失败」「网络错误」这类总结句
- 判断不出原因就说判断不了,不要猜
为什么值得单独写三条?
因为总结会丢掉排查真正靠得住的三样东西:行号、exit code、文件路径。
同一个事故,两种报法的区别:
总结版:页面操作超时了
原文版:ERR target 8F471FB996720379DFA9C2A3CF46E2B4 is already page p6
看到 p6 我立刻知道是浏览器标签页串了,跟超时一点关系没有。
让模型总结,它越想帮你省事,越容易把关键信息省掉。
四、前后对比
还是 10 月 5 号那批活,改完之后的数字:
| 改之前 | 改之后 | |
|---|---|---|
| prompt 里的模糊词 | 5 个 | 0 个 |
| 24 条回复跑了几轮 | 3 轮 | 2 轮 |
| 耗时 | 约 40 分钟 | 约 25 分钟 |
| 漏发 / 重发 | 6 条 | 1 条 |
| 报错定位 | 平均追问 3 轮 | 1 次拿到行号 |
诚实说一句:这是我两天的个人记录,样本小,别当统计结论看。
而且改完也没到零失误 —— 第二轮还剩 1 条 FAIL_MISMATCH,那个是回复目标和评论人对不上,属于数据问题,换约束解决不了。
这三招治的是「指令含糊导致的返工」,治不了「数据源本身就是脏的」。
五、两条能迁移到自己工作里的原则
第一条:验收标准要在 agent 动手之前就写得出来。
写不出来,说明不是 agent 的问题,是我自己还没想清楚要什么。
我现在写完 prompt 会多花两分钟问自己一句:它做完了,我拿什么判断它做对了?
第二条:凡是「跑完才知道对不对」的步骤,都想办法变成「跑之前能看一眼」。
--dry-run 的清单是一种,报错原文是一种,可数的约束是第三种。
它们的共同点是:把判断提前,把回滚的成本后置。
你最近一次让 agent 返工,最后发现卡在哪个词上?
欢迎在留言里说说,我一条条看。
公众号:Zack说AI
大厂程序员,现独立开发者,分享 AI 技术与过程。每篇讲一个能落地的提效做法。