1. 从一条日志说起:为什么我要给自养Agent做“认错线”
先把这个标题拆开看。“自养Agent日志”说的是我自己搭的一套Agent运行记录系统,不是某个平台提供的托管服务,而是从exec_agent.py到config/problems.json全部自己维护的那种。“6个样本”指的是这一轮回归测试里我实际跑通的用例数量,样本量确实小得可怜。“|r|=0.997”是这6个样本上某个指标和预期值之间的皮尔逊相关系数绝对值,接近1,看起来漂亮得不像话。而“认错线全触发了”才是重点——我给自己设了一组阈值,一旦指标越过这些线,系统就必须承认“这轮结果不可信”,而不是拿着0.997去邀功。
这套东西解决的是一个很具体的问题:自养Agent在迭代过程中,最容易骗的不是别人,是自己。样本少、指标好看、日志干净,三件事凑在一起,人就会开始相信Agent真的变强了。我踩过这个坑,所以后来在exec_agent.py里硬编码了几条“认错线”,专门用来在结果过于完美的时候泼冷水。
这篇文章适合两类人看。一类是正在自己搭Agent、写执行脚本、维护问题集的开发者,你可能已经有一版能跑的exec_agent.py,但还没想清楚怎么判断它到底靠不靠谱。另一类是对Agent评测、Agent记忆、Agent编排感兴趣,想看看一个真实的自养循环里,那些“看起来很美”的数字背后藏着什么。我会把ret_ratio、useful这些指标怎么算、认错线怎么设、6个样本为什么不能信、以及我实际排查时踩过的坑,全部摊开讲。
需要先说明的是,下面涉及的所有项目名、文件名、指标名都来自我自己的实践约定,你可以直接抄结构,但具体阈值一定要按你自己的问题集重新标定,别照搬。
2. 自养Agent的整体设计与思路拆解
2.1 什么叫“自养”,和托管式Agent差在哪
“自养”这个词是我自己叫顺口的,核心意思是:Agent的执行循环、问题集、评分逻辑、日志格式全部由我自己掌控,不依赖某个平台的托管运行时。你搜Agent开发、Agent架构这些词,出来的大多是框架选型和编排方案,比如LangChain、Dify、CrewAI这类。它们解决的是“怎么把工具、模型、记忆串起来”,但很少解决“串起来之后,我怎么知道它这轮是真的进步了”。
自养的关键差异在于三点。第一,执行入口是我自己的脚本,也就是exec_agent.py,它负责读取问题、调用模型、收集输出、写日志,整个链路可见可改。第二,问题集是本地文件,config/problems.json里存着每个用例的输入、期望行为、评分维度,不依赖云端数据集。第三,评分和阈值是我自己定的,包括ret_ratio和useful这两个核心指标,以及那几条“认错线”。
这么做的代价是维护成本高,问题集要自己攒,评分逻辑要自己写,阈值要自己标。但好处是,当结果异常时,我能一路追到具体是哪条用例、哪次调用、哪个字段出了问题,而不是对着一个黑盒面板干瞪眼。
2.2 为什么核心指标选ret_ratio和useful
ret_ratio我定义为“有效返回比”,粗略讲就是Agent给出的结果里,真正被判定为可用、且与问题意图对齐的比例。它的计算不是简单的对错计数,而是带权重的:每个用例在config/problems.json里有一个weight字段,ret_ratio = Σ(命中权重) / Σ(总权重)。这样设计是因为不同问题的重要性不一样,一个核心链路的用例错了,比十个边缘用例错了更严重。
useful则是更偏主观的一个维度,我把它理解成“这次输出对下一步有没有用”。有些结果严格来说不算错,但它是废话、是重复、是没有信息量的兜底话术,这种在ret_ratio里可能算命中,但在useful里要扣分。两个指标一起看,才能区分“答对了但没用”和“答错了但给了线索”这两种情况。
选这两个指标而不是准确率、F1那一套,是因为Agent的输出往往不是分类问题,而是生成问题。生成问题里,“对”和“有用”是两回事。我试过只用准确率,结果Agent学会了一堆安全但空洞的回复,指标很好看,实际没法用。加上useful之后,这种投机行为会被压下去。
2.3 认错线的设计逻辑:让系统主动说不信
认错线是我这套设计里最得意也最容易被质疑的部分。它的逻辑很简单:当结果好到不真实,或者样本少到不足以支撑结论时,系统必须主动标记“本轮不可信”。具体触发条件我设了几条,后面会详细展开,但核心思想是——宁可错杀一个漂亮结果,也不要让一个假阳性进入下一轮迭代。
为什么要有这个?因为自养Agent的迭代是闭环的,这一轮的输出会影响下一轮的问题集调整、提示词修改、甚至模型选择。如果这一轮的数字是虚高的,下一轮就会建立在错误的基础上,越滚越偏。我吃过这个亏:有一次ret_ratio冲到0.95,我兴冲冲改了提示词,结果下一轮直接掉到0.6,回头一看,上一轮的高分是因为问题集里有一半用例的期望行为写得太宽松。
所以认错线不是谦虚,是工程上的防御机制。它承认一个事实:在小样本、自评分、自迭代的闭环里,过高的指标本身就是异常信号。
3. 核心细节解析与实操要点
3.1 exec_agent.py的执行骨架
exec_agent.py是整个自养循环的入口,我把它拆成四个阶段:加载、执行、评分、落盘。加载阶段读config/problems.json,把用例、权重、期望行为、评分维度全部读进来。执行阶段对每个用例调用Agent,拿到原始输出。评分阶段计算ret_ratio和useful,并逐条记录命中情况。落盘阶段把本轮结果写成带时间戳的日志,同时更新一个累积的对比文件。
这里有个细节值得说:执行阶段我强制串行,不并发。很多人为了快会并发调用,但并发会引入顺序不确定性,导致日志里用例和输出的对应关系变乱,排查时非常痛苦。6个样本串行跑也就几秒钟的事,没必要为了这点速度牺牲可追溯性。如果你的问题集上百条,可以并发,但一定要给每个用例打上唯一ID,并在日志里保留ID到输出的映射。
另一个细节是原始输出必须原样落盘,不能只存评分后的结果。我见过有人只存“命中/未命中”,结果出了问题想复现都复现不了。原始输出里往往藏着模型的口癖、截断、格式漂移这些线索,评分只是对这些线索的二次加工。
3.2 config/problems.json的结构设计
问题集文件我用了比较扁平的结构,每个用例是一个对象,关键字段包括id、input、expect、weight、tags。expect不是精确字符串匹配,而是一组判定规则,比如“必须包含某类信息”“不能出现某类兜底话术”“长度在某个区间”。这样设计是为了适配生成式输出的模糊性,精确匹配在Agent场景里几乎不可用。
weight字段是ret_ratio加权的基础,我一般把核心链路用例设成3,边缘用例设成1。tags用来做分组统计,比如按“记忆”“工具调用”“多轮”分组,方便定位是哪一类能力在退化。
注意:
expect里的判定规则一定要写成可执行的,不要写成自然语言描述。我早期偷懒写过“回答要合理”这种,结果评分脚本根本没法执行,最后只能人工看,自养循环直接断掉。
3.3 ret_ratio的计算过程与陷阱
ret_ratio的计算看起来简单,但有几个坑。第一个坑是权重归一化:如果这一轮只跑了部分用例,分母不能是全部用例的总权重,而应该是本轮实际执行用例的总权重,否则指标会莫名其妙偏低。第二个坑是部分命中:有些用例的expect有多条规则,命中一半怎么算?我的做法是给每条规则也设权重,用例得分是规则得分的加权和,再乘以用例权重进入总分。
第三个坑最隐蔽:评分脚本本身的bug会伪装成Agent的退步。我有一次发现ret_ratio突然掉了0.2,排查半天以为是模型问题,最后发现是评分脚本里一个正则写错了,把正常输出判成了未命中。从那以后我养成了一个习惯:每次改评分脚本,先用一组已知结果的固定样本跑一遍,确认评分逻辑没变,再跑真实用例。
3.4 useful的判定与主观性控制
useful比ret_ratio难量化,因为它涉及“有没有信息量”这种主观判断。我的做法是把它拆成几个可观察的信号:输出是否包含具体信息而非泛泛而谈、是否引用了上下文中的内容、是否给出了可执行的下一步、是否避免了重复上一轮的话术。每个信号给一个分数,加权得到useful。
为了控制主观性,我尽量让判定规则机械化,比如“包含具体信息”定义为“输出中出现了输入里没有的名词或数字”。这当然不完美,但比纯人工打分可复现。如果你有预算,可以引入第二个模型做交叉评分,两个分数差异过大就标记为待人工复核。
提示:
useful的阈值不要设太高。生成式输出里,真正“有用”的比例天然不会特别高,如果useful常年0.9以上,大概率是判定规则太松,而不是Agent太强。
4. 实操过程与核心环节实现
4.1 从零跑通一轮自养循环
假设你已经有了exec_agent.py和config/problems.json,跑一轮的流程是这样的。先确认问题集文件格式正确,我一般用一个小的校验脚本检查每个用例是否包含必需字段、weight是否为正数、expect规则是否可解析。然后执行python exec_agent.py --config config/problems.json --out logs/,脚本会串行跑完所有用例,输出一份带时间戳的日志。
跑完之后不要急着看总分,先看逐条结果。我习惯先扫一遍未命中的用例,确认是真的没命中,还是评分脚本误判。然后再看命中的用例里,useful低的那些,这些往往是“答对了但没用”的典型。最后才看汇总指标,而且看的时候一定结合样本量。
6个样本这个量级,说实话,任何指标都只能当参考,不能当结论。我之所以还跑,是因为它是快速回归,用来发现“明显退化”,而不是用来证明“明显进步”。证明进步需要更大的样本集和更严格的对照设计,那是另一个话题。
4.2 认错线的具体触发条件
回到标题里的“认错线全触发了”。我设的认错线大概有这么几条,这一轮6个样本、|r|=0.997的情况下,它们确实全亮了。
第一条是样本量下限线:本轮执行用例数少于某个阈值(我设的是20),直接标记“样本不足,结论不可信”。6个样本远低于这条线,所以第一盏灯就亮了。
第二条是指标过高线:ret_ratio或useful超过某个上限(我设的是0.95),标记“指标异常偏高,疑似评分过松或用例过易”。0.997的相关系数虽然不是ret_ratio本身,但它反映的是“预测值和实际值高度一致”,这种一致性在小样本上极可能是过拟合,所以也触发。
第三条是方差过低线:如果逐条得分的方差低于某个值,说明用例之间区分度不够,可能全是简单题。6个样本如果得分都很接近,方差自然低,这条也会亮。
第四条是相关性异常线:就是我标题里那个|r|。我本来用相关系数来衡量“Agent输出和期望行为的对齐程度”,但相关系数在样本少的时候极不稳定,6个点算出0.997完全不奇怪,甚至可以说是必然。所以我把“样本少且|r|高”单独设为一条认错线。
这四条线全触发,系统就会在日志头部打上“UNRELIABLE”标记,并且拒绝把本轮指标写入累积对比文件。也就是说,这一轮的结果不会污染历史趋势。
4.3 参数选择与阈值标定过程
阈值不是拍脑袋定的,我是这么标定的。先攒一个足够大的基准问题集,比如100条以上,跑若干轮,记录ret_ratio和useful的分布。然后看正常波动范围,把“过高线”设在正常上沿之上一点,“过低线”设在正常下沿之下一点。样本量下限线则是根据统计功效反推的,样本太少时置信区间宽得没有意义,具体数值你可以用自助法重采样估一下。
|r|这条线比较特殊,因为相关系数本身对样本量敏感。我的做法是:样本量低于30时,不管|r|多少,都标记为“相关性结论不可信”。样本量足够时,再看|r|是否显著偏离历史水平。
注意:阈值标定一定要用你自己的数据,别抄别人的。不同问题集、不同Agent、不同评分逻辑,正常波动范围完全不同。抄来的阈值只会让你要么过度报警,要么该报警时不报。
4.4 日志格式与可追溯性设计
日志我用了JSON Lines格式,每行一个用例的结果,包含用例ID、原始输出、逐条规则命中情况、用例得分、时间戳。文件头部有一行元信息,记录本轮配置、样本量、汇总指标、认错线触发情况。这样设计的好处是,既能逐条查,也能整体统计,还能用命令行工具快速过滤。
我特别保留了“原始输出”字段,哪怕它很长。排查问题时,原始输出是唯一能还原现场的东西。评分结果可以重算,原始输出丢了就真没了。
5. 常见问题与排查技巧实录
5.1 指标虚高但实际不可用
这是最常见的坑。表现是ret_ratio很高,但你把输出拿出来一看,全是正确的废话。原因通常是expect规则太宽松,或者useful判定没起作用。排查方法是随机抽几条高分用例,人工读一遍输出,如果读起来觉得“这说了等于没说”,那就是评分逻辑的问题,不是Agent的问题。
解决办法是收紧expect,增加对具体信息的要求,同时把useful的权重提上来。我一般会让useful在总分里占至少三成,这样空洞输出拉不高总分。
5.2 评分脚本改动导致指标跳变
前面提过,评分脚本的bug会伪装成Agent退步。排查方法是维护一组“金标准”样本,它们的输出和正确评分是固定的,每次改脚本先跑这组,确认评分不变。如果金标准样本的评分变了,说明是脚本问题,不是Agent问题。
5.3 小样本上的相关性幻觉
6个样本算出0.997,这不是Agent的功劳,是数学的必然。样本越少,相关系数的方差越大,出现极端值的概率越高。排查方法很简单:把样本量翻倍再算,如果|r|大幅下降,说明之前的高相关就是噪声。更严谨的做法是算置信区间,样本少时区间宽得能装下整个实数轴,这种相关性没有任何解释力。
5.4 认错线频繁触发怎么办
如果认错线经常全亮,先别急着调低阈值。频繁触发说明你的评测流程本身不稳定,可能是样本量长期不足,可能是评分逻辑波动大,可能是问题集区分度不够。这时候应该先扩充问题集、稳定评分逻辑,而不是把报警关掉。把报警关掉等于把体温计砸了说没发烧。
5.5 常见问题速查表
| 现象 | 可能原因 | 排查方向 | 处理建议 |
|---|---|---|---|
| ret_ratio虚高 | expect过松、useful权重低 | 抽读高分输出 | 收紧规则、提高useful权重 |
| 指标突然跳变 | 评分脚本bug | 跑金标准样本 | 修复脚本后重跑 |
| 小样本高相关 | 样本量不足 | 翻倍样本重算 | 样本<30不采信相关性 |
| 认错线频繁触发 | 流程不稳定 | 检查样本量与评分波动 | 扩样本、稳评分,勿关报警 |
| useful长期偏高 | 判定规则太松 | 人工复核高分项 | 收紧useful判定信号 |
5.6 我踩过的几个具体坑
第一个坑是用例权重全设成1。早期我图省事,所有用例权重一样,结果核心链路的问题被边缘问题稀释,指标看着平稳,实际核心能力已经退化。后来把核心用例权重提到3,问题立刻暴露出来。
第二个坑是日志只存汇总不存明细。有一次指标异常,我想查是哪条用例拖后腿,结果日志里只有总分,只能重跑。重跑还不一定能复现,因为模型输出有随机性。从那以后明细必存。
第三个坑是认错线设得太晚。我一开始没有认错线,被虚高指标骗了好几轮,改了一堆不该改的东西。后来加上认错线,虽然有时候会误报,但至少不会让我在错误方向上狂奔。
第四个坑是把useful当次要指标。有段时间我只盯ret_ratio,useful随便看看,结果Agent学会了输出安全但无用的内容。把useful提上来之后,输出质量明显改善。
6. 自养Agent的迭代节奏与扩展方向
6.1 小样本回归和大样本评估的分工
6个样本这种量级,我只用来做快速回归,目的是发现“明显退化”,比如某个核心用例突然全错。它不能用来证明进步,也不能用来做模型选型。大样本评估我另有一套流程,问题集上百条,跑多轮取统计量,还会做交叉验证。两套流程分工明确,小样本快但不准,大样本准但慢,各司其职。
6.2 认错线机制的推广
认错线这套思路不只适用于Agent评测。任何自评自迭代的系统,只要样本少、指标自算、闭环反馈,都有虚高的风险。把“结果太好时主动不信”做成机制,比靠人的自觉可靠得多。我后来把这套逻辑也搬到了提示词迭代和问题集扩充上,效果不错。
6.3 后续可以扩展的点
一个方向是引入交叉评分,用第二个模型给输出打分,和主评分对比,差异大的标记待复核。另一个方向是动态阈值,根据历史波动自动调整认错线,而不是用固定值。还有一个方向是用例自动生成,从失败案例里派生新用例,扩充问题集。这些我都在试,但目前还没到能稳定分享的阶段。
最后分享一个我自己的习惯:每次看到漂亮指标,先问一句“这6个样本配得上这个数字吗”。大多数时候,答案是不配。认错线全触发不是坏事,它说明防御机制在工作。真正危险的是那些指标漂亮、认错线安静、你还特别开心的时刻。