Harness Engineering 03|Eval & Trace Harness:验证和追溯的工程组织
activity-dev-harness 在第三周遇到一个典型事故:Prompt 微调后,团队手试了 5 个 case 感觉"更好了",直接上线。三天后用户反馈质量下降,拉全量回归一看------通过率从 78% 跌到 61%。
追查过程更痛苦:回归能看到"跌了",但看不到"从哪一步开始跌的"。是上下文变了?工具调用变了?还是模型决策路径变了?没有执行轨迹,全靠猜。
最后花了两天才定位到根因:Prompt 微调改变了 Developer Agent 的工具选择倾向,原本优先用 grep 精确定位的行为,变成了先用 file_read 大面积扫描,导致 token 消耗膨胀、上下文质量下降。
这次事故暴露了两个缺失:没有自动化回归(Eval 缺失),没有行为级追溯(Trace 缺失)。 分别补上后,类似问题再没发生过。
手记系列讲了"什么是评测和观测",这篇讲"Harness 怎么组织它们"
手记 03 已经覆盖了评测和可观测性的基础概念:AI 测试金字塔、非确定性回归、5 层可观测模型。
这篇不重复那些内容。它要回答的是一个更具体的工程问题:当你有一套 Agent 系统,Eval 和 Trace 应该怎么组织成闭环,才能真正防住质量退化?
手记 03 的视角 本篇的视角
────────────── ──────────────────
什么是 Eval Eval 在 Harness 里怎么组织
什么是可观测性 Trace 记什么、怎么用、服务谁
评测集怎么分类 Eval + Trace 怎么串成闭环
非确定性回归的原理 真实系统里的 Eval 流水线长什么样
Eval Harness:不是"跑几个 case",是验证流水线
大多数团队的"评测"停留在手动阶段:改完 Prompt → 手试几条 → 感觉不错 → 上线。
问题在于,Agent 系统的变化维度太多,手动试验完全覆盖不了:
❌ 手动评测的覆盖盲区
你手试了 5 个 case,感觉"更好了"。但全量 35 个 case 里:
case 类型 手试覆盖 实际表现
──────────── ──────── ────────
简单 case(20个) 3/20 ✓ 18/20 PASS(确实更好了)
边界 case(10个) 2/10 ✓ 5/10 PASS(退化了,但你没测到)
历史故障(5个) 0/5 1/5 PASS(严重退化,完全没覆盖)
你的结论:更好了 ✓
真实情况:主流程更好了,边界和历史故障退化了 ✗
Harness 视角下的 Eval 不是"跑 case",是一条自动化验证流水线:
┌──────────────────────────────────────────────────────────────────────┐
│ Eval Harness 验证流水线 │
├──────────────────────────────────────────────────────────────────────┤
│ │
│ 触发条件 │
│ ├── Prompt 变更 │
│ ├── 模型版本升级 │
│ ├── 工具/检索策略变更 │
│ └── 定时回归(每日/每周) │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────────────────┐ │
│ │ 评测集(分层) │ │
│ │ ├── gold case × 3 基准验证,必须全 PASS │ │
│ │ ├── regression × 25 回归覆盖,统计通过率 │ │
│ │ └── failure × 7 历史故障回收,防重复犯错 │ │
│ └────────────────────────────┬──────────────────────┘ │
│ ▼ │
│ ┌───────────────────────────────────────────────────┐ │
│ │ 评估函数(三层) │ │
│ │ ├── 结果层:SH-01~10 确定性检查(PASS/FAIL) │ │
│ │ ├── 行为层:DC-01~07 深度一致性检查 │ │
│ │ └── 稳定层:同 case 跑 5 次,波动率 < 15% │ │
│ └────────────────────────────┬──────────────────────┘ │
│ ▼ │
│ ┌───────────────────────────────────────────────────┐ │
│ │ 决策 │ │
│ │ ├── gold case 任一 FAIL → 自动阻断,不允许上线 │ │
│ │ ├── 回归通过率 < 基线 5pp → 黄灯,需人工审查 │ │
│ │ └── 回归通过率 ≥ 基线 → 绿灯,可上线 │ │
│ └────────────────────────────┬──────────────────────┘ │
│ ▼ │
│ ┌───────────────────────────────────────────────────┐ │
│ │ 回收 │ │
│ │ 线上故障 case → 加入 failure 评测集 → 下次回归覆盖 │ │
│ └───────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────────┘
评测三层:结果、行为、稳定性
只评"结果对不对"远远不够。Harness 视角下的评测至少三层:
评测层 评什么 怎么评 为什么重要
────── ──────────── ────────────────── ──────────────────
结果层 最终输出是否正确 SH 脚本:文件存在、 最基础的质量底线
空壳检测、参数一致
行为层 过程是否合理 DC 脚本:逻辑一致性、 防止"结果对了但过程
边界检查、依赖完整 不可接受"
稳定层 多次运行是否一致 同 case 跑 N 次, 防止"这次对了下次
计算波动率 就不对了"
activity-dev-harness 的真实评测数据:
评测集 case 数 评估函数 基线通过率 当前通过率
────────────── ──────── ───────────── ────────── ──────────
gold case 3 SH×10 + DC×7 100% 100%
regression set 25 SH×10 + DC×7 72% 78% ↑
failure recovery 7 SH×10 + DC×7 43% 71% ↑↑
────────────────────────────────────────────────────────────────────────
加权通过率 68% 79%
稳定性评测(gold case,每 case 跑 5 次):
case-001: 5/5 PASS 波动率 0%
case-002: 4/5 PASS 波动率 20% → 需要调查
case-003: 5/5 PASS 波动率 0%
Trace Harness:不是"多打日志",是行为级追溯
Eval 告诉你"跌了"。但"从哪一步开始跌的",只有 Trace 能回答。
日志 vs Trace 的关键区别:
普通日志 Trace
──────── ──────
[INFO] tool_call: grep ┌─ Task: case-017 暴击伤害修复
[INFO] tool_result: 3 files found │
[INFO] tool_call: file_write ├─ Step 1: 意图解析
[ERROR] test failed │ 意图: 修复 calculateDamage 暴击倍率
│ 约束: 只改 combat/ 目录
记录事件 │
无因果关系 ├─ Step 2: 环境探索
无法回答"为什么选了这个工具" │ grep "damage" → 6 results
│ 选择: combat/damage.lua (置信度高)
│ 跳过: utils/calc.lua (旧版,标记为 archive)
│
├─ Step 3: 代码修改
│ 修改: combat/damage.lua:42-48
│ diff: +3 -1 lines
│ 边界检查: 修改在 combat/ 内 ✓
│
├─ Step 4: 验证
│ pytest tests/test_damage.py → PASS
│ SH-01~03 → ALL PASS
│
└─ Result: PASS
耗时: 22s | tokens: 6.2K | 成本: $0.08
记录因果链
能回答"为什么选了 combat/damage.lua 而不是 utils/calc.lua"
能回答"跌了的话是从第几步开始跌的"
Trace 五类必记信息
一条能用于排障、评测和治理的 Trace,至少记录五类信息:
类别 记什么 用于什么
────── ──────────────────── ──────────────────
1. 任务起点 任务 ID、目标、约束、成功标准 归因:是不是意图就不对
2. 环境快照 读了哪些文件、检索了什么、 归因:是不是看到了错误信息
加载了什么上下文
3. 决策节点 选了哪个工具、为什么选、 归因:是不是决策逻辑变了
还有什么候选
4. 动作结果 调了什么、参数、返回值、 归因:是不是工具出了问题
成功/失败/异常
5. 反馈终局 测试结果、评测分数、 归因:是不是反馈链断了
人工审核结论、接受/拒绝原因
真实案例:Trace 如何在 3 分钟内定位 Prompt 退化
回到开头的故事。Prompt 微调后通过率从 78% 跌到 61%。有了 Trace 之后的排查过程:
排查步骤 发现 耗时
────────────── ────────────────────── ──────
1. 拉退化 case 的 Trace 全部退化 case 都在 Step 2 出现异常 30s
2. 对比 Step 2 的决策节点 旧版:优先 grep 精确搜索 1min
新版:优先 file_read 大面积扫描
3. 对比 token 消耗 旧版 Step 2: 平均 2.1K tokens 30s
新版 Step 2: 平均 6.8K tokens(+224%)
4. 定位根因 Prompt 微调增加了"先全面了解再动手" 1min
的引导语,改变了工具选择倾向
总排查时间:3 分钟
没有 Trace 时的排查时间:2 天
Eval + Trace 闭环:从"跌了"到"知道为什么跌"
Eval 和 Trace 不是两个独立工具,而是闭环的两个环节:
┌───────────────┐
│ 变更发生 │ Prompt / 模型 / 工具 / 检索策略
└───────┬───────┘
▼
┌───────────────┐
│ Eval 回归 │ 自动跑评测集,对比基线
└───────┬───────┘
│
┌───────────┼───────────┐
▼ ▼
┌──────────────┐ ┌──────────────┐
│ 通过率 ≥ 基线 │ │ 通过率 < 基线 │
│ → 绿灯放行 │ │ → 触发排查 │
└──────────────┘ └──────┬───────┘
▼
┌───────────────┐
│ Trace 对比 │ 拉退化 case 的 Trace
│ │ vs 基线版本的 Trace
└───────┬───────┘
▼
┌───────────────┐
│ 定位退化层 │ 决策变了?工具变了?
│ │ 上下文变了?环境变了?
└───────┬───────┘
▼
┌───────────────┐
│ 修复 + 回收 │ 修复退化 + 加入
│ │ failure 评测集
└───────────────┘
没有 Eval,你不知道跌了。没有 Trace,你知道跌了但不知道为什么。两者串起来,才是完整的质量防线。
性能基准:Eval 管质量,Benchmark 管容量
除了质量评测,Harness 还需要性能基准。因为"答对了"不等于"跑得起来":
activity-dev-harness 性能基准数据:
指标 单 case 35-case 全量 高峰期(并发×3)
────────────── ────────── ────────── ────────────────
端到端时延(中位数) 22s --- ---
端到端时延(P95) 48s --- ---
单 case 成本 $0.08 $2.80 $3.50(+25%重试)
推理排队等待(中位数) 0.3s 1.2s 4.8s
推理排队等待(P95) 1.1s 5.6s 18.2s ← 不可接受
通过率 82% 78% 71%(退化 7pp)
发现的问题:
1. P95 时延是中位数的 2.2 倍 → 复杂 case 时延爆炸
2. 并发×3 时推理排队从 1.2s 涨到 4.8s → 推理服务是瓶颈
3. 高峰期通过率下降 7pp → 排队导致上下文超时,重试增加
性能基准的三层结构:
层级 测什么 发现什么
────── ──────────────── ────────────────────────
组件基准 单次推理延迟 Sonnet TTFT: 0.8s, 全量: 4.2s
单次工具调用 pytest: 3.1s, grep: 0.2s
单次检索 向量检索: 120ms, Rerank: 80ms
链路基准 一个完整 case 从头到尾 Developer 生成: 8s
各阶段耗时占比 Developer 自测: 5s × 3 = 15s
Reviewer 审查: 6s
总计: 29s(理想路径)
工作负载基准 35 case 全跑 并发度: 3-5
高峰期模拟 推理服务排队情况
尾延迟分布 P99 是否可控
早期团队的最小 Eval + Trace 方案
阶段 做什么 成本
────── ──────────────────────── ──────
Week 1 固定 3 个 gold case + 基线结果 半天
Week 2 每次重要变更后跑 gold case 回归 10 分钟/次
Week 3 补 Trace:记录任务起点 + 决策节点 1 天
+ 动作结果
Week 4 补 failure 评测集(回收线上问题) 持续
Month 2 自动化:变更触发 → Eval → 报告 2 天
Month 3 Trace 对比:退化 case 自动拉两版 3 天
Trace 差异
不要先做什么:
❌ 评测平台(先用脚本跑)
❌ 可视化仪表盘(先用文本报告)
❌ 全量 Trace 存储(先记关键节点)
❌ 实时告警(先做每日回归)
Eval & Trace 在不同项目里的组织差异
项目 Eval 重点 Trace 重点
──────────── ────────────────── ──────────────────────
activity-dev 多轮循环通过率 + 每轮 Developer/Reviewer
harness 稳定性(同 case 5 次) 的决策路径 + 工具选择变化
AIReview 规则命中精度 + 检索召回 → Rerank 排序 →
漏报率/误报率 最终引用的完整链路
配置表风险 批量准确率 + 每张表的风险判定依据 +
评估 高风险召回率 RAG 命中的规则版本
Crashsight 根因分析准确率 + 历史相似 crash 匹配 →
与人工分析的一致性 根因推理路径
Eval & Trace Harness 的核心判断:Eval 防退化,Trace 解释退化。没有 Eval 的系统靠直觉迭代,没有 Trace 的系统靠运气排障。两者串成闭环,才是 Harness 视角下的质量验证体系------不是"有评测"就行,而是"变更 → 回归 → 归因 → 回收"这条完整链路必须自动化运转。