Agent 产物的自检要看内容特征,不是看文件名
我给那个写公众号文章的 Agent 定了一套交付物形态:每跑完一次任务,在成果目录里留下六个文件,从原始素材、备选方向、确认后的提纲、补充资料、正文,一直到最后一个固定叫 06-cover.png 的封面图。这套目录是我自己拟的,文件名是提前定死的,每次都一样。
有一次这个 06-cover.png 里装的其实是一张 JPEG。文件头写着 JFIF,只有文件名后缀是 .png。
现象:目录看上去完全正常
从目录列表上看不出任何异样。六个文件齐全,顺序对,名字对,最后那个封面能打开、能看、也确实是这次任务该有的那张图。保存这一步没有报错,之后每一次去翻这个目录,看到的都是一个”跑完了”的完整产物。
这是这类问题最难受的地方:它不长成错误的样子。一次任务失败到一半,目录里会缺文件、会有半截文本,你扫一眼就知道出事了。而这一次,所有能被眼睛检查到的信号全是正常的——文件在、名字对、图能看。名和实之间那点不一致,恰好落在肉眼看不到、也没有任何一步会主动去看的位置上。
当时是怎么发现的:不是靠看,是靠下一个环节的入口
这一段是我这次复盘里最想留下的。发现它的不是”我检查产物的时候发现的”,而是”它要被送去下一个地方用的时候被拦住的”。
本地这一侧从头到尾风平浪静:写盘不报错,打开不报错,重跑一次还是不报错。真正让它现形的,是准备把这张封面接进下游接口那一步——那一步对格式有要求,而它检查的是内容,不是名字。
事后我回去确认的动作也很朴素:不去看后缀,直接读文件开头那几个字节写的是什么。这个动作跟”打开图片看一眼”完全是两件事。看一眼,看到的是内容渲染出来的样子;读文件头,看到的是这份字节流自己声明的身份。前者永远不会告诉你名字错了,因为它压根不关心名字。
所以我从这里带走的第一个东西不是结论,是一个提醒:如果一个问题只有在”交给别人用”的时候才会暴露,那说明我自己这一侧的检查全是绕过它的。 这不是运气不好,是检查项本身没覆盖到。
为什么会这样:名字是我给的,内容是外面给的
我事后的理解是这样的。
这个目录的六个文件名,是我在设计交付物形态时就定死的。定死是有理由的——我要让每一个人工确认节点的输入和输出都单独落盘,方向存成一个文件、提纲存成一个文件,闸门处做过的决定要固化成文件,而不是留在对话记录里。名字统一,才能一眼知道哪个文件对应哪个环节。
代价是:这个名字先于内容存在。 走到封面这一步的时候,06-cover.png 这个名字已经在那儿等着了,等的是一段还没生成出来的字节流。生成封面用的是一个外部能力,它按自己的方式产出图,然后我把拿回来的东西写进这个早就取好名字的壳里。
于是名和实来自两个不同的源头,中间没有任何一方在负责对齐:产出那一端不知道我打算把它叫什么;写盘这一端只负责把字节放进去,不负责问它是什么。两边都没做错自己那份事,合起来就是错的。
这也是我不把这类检查写成”生成封面时请保存为 PNG 格式”这种要求的原因。那句话是写给生成那一步的,而问题恰恰出在生成那一步之外——它管不到实际交回来的是什么。要求提得再清楚,也只是又多了一个没人核对的约定。
改成了什么:把”封面合格”拆成两层
我现在把封面这一步的”合格”分成两层看,两层的判据不一样,检查的位置也不一样。
一层是内容层:这张图的表现形式可以夸张、可以用隐喻,但核心事实和内容承诺不能偏离;涉及知名人物或公司时,避免直接使用真实肖像或商标。这一层是判断题,得有人(或者按写死的规则)去看图本身。
另一层是容器层:这个文件到底是不是它名字说的那个东西。这一层不需要看图,也不该由看图的那一步顺手带过——它是个纯机械的比对,把文件头的内容特征和扩展名对一遍,不一致就当成没通过。
拆开之后,位置就清楚了:容器层的校验属于落盘之后的自检项,跟交付前的检查清单放在一起,而不是塞进封面质量的评判标准里。我给这两类东西划的界是——凡是”人得动脑子判断”的,放质量标准;凡是”机器一比就知道”的,放自检清单。 混在一起写,机械项会被当成主观项一起跳过,因为主观项本来就允许”看着差不多就行”。
顺带说一句我在改法上的取舍。发现不一致之后有两条路:一条是补救,比如顺手转码成名字说的那个格式,让流程继续往下跑;另一条是直接停下来。我更愿意选停下来。补救听着更”自动化”,但它是在我并不确知这段字节到底是什么的前提下,继续把它往下传,只不过换了个名字往下传;停下来虽然粗暴,至少下一步拿到的东西是它自己声称的那个东西。这个取舍的分歧点其实不在”要不要多写几行代码”,而在于我认不认这一步的产出还需要我看一眼。
可迁移的判断:本地不报错不是通过,只是没人检查
我这套流程里更早就写下过一条判据:只要这个动作的结果会被下一步当作前提使用,就必须回查。 我原先只把它用在接口调用这类动作上——调用返回成功,不等于事情办成了,得回头查一遍状态。
这次的收获是,这条判据同样管一个文件名。封面就是下一步的前提,前提一旦是假的,后面所有步骤都会在这个假设之上继续跑,而且跑得很顺,最后交出一份看起来完整的错误产物。这比中途报错要糟得多,因为报错至少是个信号。
具体到落盘这件事,我现在的说法是:产物的自检要校验内容特征,不是校验文件名。 文件名是我写上去的,它只反映我以为它是什么;内容特征是这份数据自己带的,它反映它实际是什么。当这两者的来源不是同一方时,一致性就默认没人负责——除非我显式地加一步去负责。
反过来,这条也有它不该被滥用的边界。回查本身有成本,也可能自己失败。如果一个动作的结果没有下一步在用,比如只是往本地丢一份临时中间件,失败了立刻就能看见,那么给它配一套内容特征校验就是过度设计。判据不是”什么都要查”,而是那句更窄的:这个结果会不会被下一步当作前提。
最后一句我想留给”本地全绿”这四个字。这次的封面在本地是全绿的,绿得毫无破绽。我后来意识到,本地全绿有两种截然不同的含义:一种是”检查过了,通过”,另一种是”没有任何一项检查碰到过它”。这两种在界面上、在日志里长得一模一样,只有在它被送出去用的那一刻才分得开。让 Agent 自己交付产物的时候,我更愿意先假设是后一种。
这篇写的是我自己那套流程里的一次实际情况,不是通行做法。你的场景、工具和团队规模不同,结论未必适用,判断方式可能比结论更值得拿走。
延伸阅读
- 上一篇(跑闭环):Agent 不报错也不超时的失败,要怎么才能发现
- 下一篇(跑闭环):承认单个 Agent 的成功率明显低于 1
- 这个专题的主论点:Agent 调用没报错,不等于这件事办成了
- 专题导读与七个阶段的地图:把 Agent 当成一个要上岗的员工来带:这个专题讲什么