Writing · 实操

agent eval 工程实操:给服装设计 agent 搭一把标尺

最近搓设计的 agent,正在调它的 system prompt,调起来非常非常不方便。

因为这个 agent 不是单次推理,套了好几个环节点,我改上游 prompt 的一句话,下游相关节点的输出都会跟着变。端到端跑完一次,我手里只有最终的一组结果——到底是哪个环节变好了、哪个环节其实变差了但被后面环节掩盖了,根本看不清。

所以我停下来,花了些时间搭一套 eval harness。目标只有一个:把"哪个环节贡献了多少质量"和"这版改完到底是变好还是变差"这两件事,从模糊感觉变成可读、可量化。下面把整个过程拆开讲。

一、eval harness 到底是什么

开始时,肯定是先过单测。单测能测出"格式对不对""字段缺没缺""字数有没有超"这些硬约束,但写不出"这个方案设计得好不好""趋势提炼得到不到位"。单测测的是链路通没通,没法告诉你变好了多少、哪里还能再提升。等到深水区,涉及输出质量和稳定性保障时,还是需要 eval 工程。

eval harness 听起来挺玄,其实就是一套给 LLM 输出打分、并把分数沉淀下来横向对比的评估系统。我自己的实现里它由三块组成:

跟程序员熟悉的单测做个类比:单测是"代码改完跑一下,看绿不绿";eval 是"prompt 改完跑一组样本,看一组数字往哪个方向走"。但 eval 跟单测有三条本质区别,写代码之前要先明确:

第一,单测看 pass/fail,eval 看分布。LLM 输出是随机变量,同一个 prompt 跑 10 次会得到 10 组数字,你要看的是均值、方差、有没有坍缩到全 5 分或全 10 分这种极端。

第二,单测对单一版本,eval 是多版本横向比较。eval 永远在拿不同 version 比,每个指标涨没涨、跌了多少。

第三,单测不关心成本,eval 必须把质量和成本放一张表。v3 让审计 4/4 通过,但 token 多花 50%、延迟翻倍,这笔账你立刻就要知道——不然你以为是优化,其实是用钱换分。

用一台机器的质检来比喻:单测是齿轮的验收,eval harness 服务的是整机的验收。在 agent 项目中,eval 是你启动 agent 建设前就该搭好的脚手架。从我的实际经验看,eval 本质上也是一场全局视角下的 tradeoff——大部分时候没有所有 metrics 都正向,多数是在不同 metric 之间做取舍和判断。

二、设计 eval 之前我想清楚的 5 件事

先明确 evaluate 的准则——所谓准则就是行动的 guidance,类似纪律和底线要求:

  1. 先量化,再精修。LLM 输出是分布不是常量,没基线就没改进。"看上去好像更好了"是最大的认知陷阱,今天觉得好明天再看可能就是错觉。
  2. 能用 Python 直接算的指标优先做完。模型自评看着省事,但它不稳定、有偏好、还贵。基础原则是:能用 len()re.match()Counter() 算的东西全榨干,再考虑模型评分。
  3. 同一组 metric 函数必须能测两版。旧版单 prompt 输出和新版 agent 输出,JSON schema 保持兼容是有意为之——就是为了同一组 metric 同时打分两边。很多人重构时随手改 schema,结果新旧版本根本对不上号,evals 直接报废。
  4. 质量和成本必须放一张表。单看质量会得出"v3 比 v2 好",但如果 v3 token 翻倍、延迟翻倍,那叫"用三倍预算换 5% 提升",要拒绝。同表看的是 Pareto 前沿——同等成本下质量最高的版本,或同等质量下成本最低的版本。
  5. 指标定义本身也要版本化。迭代到第八九版时你大概率会想加新指标,到时候老数据要么重算、要么标记 schema_version 隔离开。我现在每个 metric 都带一个版本号。

当然,每个人的 guidelines 肯定不同,我这 5 条是踩过类似坑之后蒸馏出来的,不一定适用于所有 agent。

三、指标体系设计:大类和子项

落到我这个项目,我设计了 6 大类,分别指向不同的业务要求,下分共 30 多项子指标:

类别例子算法
A 结构完整性应得方案数 / 实得方案数len() + 字段非空
B 审计 4 项跨面占比、A 档使用、位置多样性、面分布复用审计逻辑
C 字段填充质量七段式 prompt 完整率、≤50 字硬约束正则 + 字符计数
D 比例合规性别比、风格比是否符合用户要求Counter 对比
E 弱信号质量模型自评适配度均值与方差字段读取 + 统计
F 成本输入/输出 token、延迟、调用次数meta 字段

A 到 D 是硬指标,算出来什么就是什么;E 是弱信号——模型自评不能当主决策,但能抓异常(如果一个版本全场打 10 分、另一个全场打 5 分,这种坍缩本身有信息量);F 是成本,跟质量同表。

这里有个关键决策:为什么先不做 LLM-as-judge。其一,judge 模型本身有偏好,自家模型评自家输出会偏高,这是证实过的系统性偏差;其二,不稳定,同一个输入打两次分都不一样;最重要的是——贵!每条方案一次评分,跑一次完整 eval 几美元,迭代成本顶不住。不过我有计划引入模型评测:等 deterministic 指标饱和、开始出现"两个版本数字接近但视觉感受不同"的时候,才考虑。

四、eval/ 模块结构,和一个中途的反思

代码组织上我用了一个独立的 eval/ 包:

prompt-refine-agent/
└── eval/
    ├── metrics.py    # 所有 metric 函数,纯函数
    ├── runner.py     # 模型输出 → 扁平 dict 一行
    ├── compare.py    # 多 row 横向对比表
    ├── store.py      # CSV / SQLite 落盘
    └── README.md     # schema_version 约定

Claude 一开始给的 metrics.py 顶部第一行写的是 from orchestrator.audit import run_audits——直接复用项目原本的审计代码,"DRY 原则,一份逻辑两处用,多干净"。

但我对 eval 模块的要求是:eval 的运行不应该依赖被它评判的代码。如果哪天 orchestrator 里的 audit 实现悄悄有了 bug,eval 也会跟着错,但你看到的指标依然光鲜亮丽——eval 的全部价值就在于它必须是独立的判官,不能跟被告穿一条裤子。

所以我把 4 条审计逻辑 inline 到 eval/_audit.py,让 eval 自给自足。代价是多了几十行重复代码,但换来 eval 的独立性。这种取舍很多人本能上会拒绝("复用更好不是吗?"),但在 eval 这个场景里,独立性 > DRY。

五、用 legacy baseline 验证 eval 跑通

eval 代码写完,下一步不是立刻烧 API 跑新版,而是先用项目里已有的旧版输出验证 eval 代码本身没 bug。我不想等花几美元跑完一组真 API,结果发现数字是 eval 代码自己算错的——这种返工是双倍的痛。

用 legacy 输出(旧版单节点完成全部端到端推理的 prompt 跑出来的真实结果)直接当 eval 的输入,跑通后我拿到了 baseline:

维度数值
审计 4 项4/4 通过
跨面方案占比9/9(100%)
字段填充率1.0
性别比合规男 4 / 女 4 / 中 1 ✓
自评适配度7.67/10,σ=0.47
总成本198 秒,109k tokens,1 次调用

这就是 baseline。从这一刻起,任何新版本的 prompt 改动都要用这组数字当起跑线。

六、这条 baseline 真正告诉我的事

表面上看,这组数字证明了旧 prompt 调得不错。但 baseline 的真正价值并非"证明旧版好",而是用来做后面所有改动的对照基准。

我的优化决策是拆 agent 的推理节点,把单次推理拆成多节点。拆完之后,至少要打平这些数字才算等价,而且还得关注成本上涨是否值得。如果跨面占比从 9/9 跌到 6/9,说明拆分在"方案规划"环节漏掉了某条约束;如果 token 从 109k 涨到 300k,说明串行调用引入了上下文重复发送;如果字段填充率从 1.0 跌到 0.8,说明某个子节点的 schema 不够严。

所以尽管我的假设是"拆分能带来稳定性和可观测",但要时刻保持怀疑:拆分不一定更好。多节点 agent 看起来"更工程化、更模块化、更易维护",但这三件事都是工程层面的描述,跟"输出质量更高"是两码事。很多人重构系统凭信仰("分层一定比单体好""agent 一定比单 prompt 好"),而真正的判断,发生在能不能在 day 1 就量出"重构后是否退化"——这决定了重构是工程升级还是自嗨。

七、第一次"有据可依"的优化

拿到 baseline 之后,我跑了一版 agent 拆分实现,预期"模块化会更可控"。三个核心指标对比:

维度LegacyAgentic v1Δ
审计通过4/43/4↓ 1 项
跨面方案占比9/95/9↓ 44 pt
字段填充率1.00.78↓ 22 pt
Token 总量109k287k↑ 2.6×

三项质量指标跌了、成本翻 2.6 倍——拆分版全面退化。没有 eval 系统的话,大概率会"凭感觉"调几条 prompt,跑一次看看。有了 eval,每一处跌点都能精确归因到具体节点:

改完再跑:

维度LegacyAgentic v1Agentic v2
审计通过4/43/44/4
跨面占比9/95/99/9
字段填充1.00.781.0
Token109k287k142k

质量回到 baseline,token 比 baseline 高 30%——这是 agent 拆分固有的开销,可以接受。整个调试过程没有"凭感觉",每一处改动背后都有一个数字指着具体的节点和具体的问题。

八、一些 tips 和 tradeoff

这里有个有趣的"小信号",不注意容易漏掉。以下是 V3 版本的质量维度,看起来明显提升:

维度legacyagentic_v3解读
4 项审计全过全过拆分没掉硬性规则
跨面方案9/99/9完全一致
位置多样性18 种类似量级完全一致
字段填充100%100%完全一致
男/女/中4/4/14/4/1完全一致
面分布前6/后8/侧8前7/后7/侧6agentic 更均匀
适配度均值7.678.93↑1.26(但要警惕自评偏差)

但适配度均值很可疑:标准差从 0.47 → 0.12,而且 agentic 几乎所有色号都给 8.7–9.1 分。有两种可能——真的好(每次拆分调用都拿到精确的累积状态约束,模型有充分依据评分),或坍缩了(每次只看单色,缺乏多色相对对比,倾向打高分)。这正是弱信号该做的事:抓异常,而不是当主决策。

再看 V3 的成本,是之前的 3.5 倍,明显退化,但我依然判断这是一次正向迭代:

指标legacyagentic退化倍数
总 token109k380k3.5×
LLM 调用12020×
Wall-clock 耗时198s716s3.6×
估算 $(gpt-5.5)$0.10$0.353.5×

因为这是为可观测性付出的"token 税"——我把一次性的多维度推理,换成拆 7 个节点独立推理,是为了换 fork-from-node、prompt 独立版本管理、trace 可视化,token 多一些是预期代价。而且 fork-from-node 的杠杆价值远大于这 3.5× 成本:step 4 调整 prompt 只需重跑 1–2 个色号(约 60–120 秒),比之前 step2 整跑(200s)更快、更便宜。

总而言之

eval harness 真正的价值:在你动手改 prompt / agent loop 前,告诉你该往哪儿改。当然,不同阶段的 agent,harness 的价值侧重也不同——路线可行性、prompt 优化、成本优化、风险控制,都是需要考量的。

续集:《eval harness 落地续集:给 Prompt 调试搭一个驾驶舱》 —— 把这套算分引擎扩成一个完整工作台。


← 返回全部写作