跳到主要内容《Agent Runtime 工程化》第十章 Eval Harness 与 CI 回归 | 极客日志编程语言Agent Runtime工程化可观测性陈堂会
《Agent Runtime 工程化》第十章 Eval Harness 与 CI 回归
说 Agent “变好了”很容易。证明它变好了,要靠 eval。 Agent 产品很容易陷入“今天试了几个问题,感觉不错”的幻觉。工程系统不能靠感觉上线。Eval Harness 的任务,是把真实任务变成可重复的测试,把 trace 变成回归样本,把 prompt、工具、上下文、权限和 runtime 策略的变化变成可比较的数据。 截至 2026 年 8 月
站点编辑3 浏览

书名:《Agent Runtime 工程化:从工具调用循环到可恢复执行系统》
作者:陈堂会
平台:极客日志(https://zeeklog.com)
X:@Megick_com
联系邮箱:[email protected]
章节:第十章 Eval Harness 与 CI 回归
第十章 Eval Harness 与 CI 回归
说 Agent '变好了'很容易。证明它变好了,要靠 eval。
Agent 产品很容易陷入'今天试了几个问题,感觉不错'的幻觉。工程系统不能靠感觉上线。Eval Harness 的任务,是把真实任务变成可重复的测试,把 trace 变成回归样本,把 prompt、工具、上下文、权限和 runtime 策略的变化变成可比较的数据。
截至 2026 年 8 月,软件工程 Agent 已经有 SWE-bench、SWE-bench Verified 等公开基准可供参考。它们的价值在于提醒我们:Agent 评测不应只看最终回答,而要看它是否能在真实仓库、真实 issue、真实测试中完成任务。本章不复刻公共基准,而是教你建立自己的 runtime eval。

图 10-1:Eval Harness 不只是一批题目。它包含 fixture、mock replay、真实模型 smoke、行为断言、成本门禁、失败归因和报告输出。越靠近 CI 底层,越要确定性;越靠近产品体验,越要允许统计和抽查。
10.1 Eval 不是 benchmark 排行榜
Benchmark 适合横向比较模型或系统,Eval Harness 适合保护你自己的产品行为。二者有关联,但目的不同。
| 类型 | 主要问题 | 典型输出 |
|---|
| 公共 benchmark | 我的系统和别人相比如何 | 排名、通过率、任务集结果 |
| 内部 eval | 我的系统这次改动有没有退步 | CI 报告、失败归因、PR 门禁 |
| 事故 replay | 线上失败是否已经修复 | 可复现 case、回归测试 |
| 安全 eval | Agent 是否越权或泄露 | must-not-call、拒绝记录、脱敏检查 |
对 Agent Runtime 工程来说,内部 eval 更重要。你不是为了在每个公共榜单上刷分,而是为了让团队敢修改 prompt、工具描述、上下文策略和权限策略。
10.2 Golden task 的定义
Golden task 是一组有代表性的任务。对 coding agent 来说,至少要覆盖:
- 文件阅读任务;
- 代码搜索任务;
- 单文件编辑任务;
- 多文件编辑任务;
- 测试修复任务;
- 权限拒绝任务;
- 工具失败恢复任务;
- 上下文压缩任务;
- checkpoint/resume 任务;
- MCP 工具不可用任务。
{
"id": "edit-bigint-001",
"title": "add 支持 BigInt",
"prompt": "把 src/math.ts 的 add 改成支持 BigInt,并补测试。",
"workspaceFixture": "fixtures/math-lib",
"policy": "interactive_mock",
"model": "scripted",
"expected": {
"filesChanged": ["src/math.ts", "test/math.test.ts"],
"commandsPass": ["npm test"],
"mustNotCall": ["run_command:rm", "run_command:curl"],
"maxSteps": 12,
"maxEstimatedUsd": 0.2
}
}
不要只测最终文本答案。Agent Runtime 的关键行为是路径、工具、权限、文件变更和恢复。一个 Agent 可以写出看似漂亮的总结,但从未运行测试;也可以完成测试,却用了危险命令。Eval 要把这些都看见。
10.3 Fixture 要可复原
Eval 的第一原则是可重复。每个任务都应在干净 workspace fixture 中运行。
evals/
tasks/
edit-bigint-001.json
fixtures/
math-lib/
package.json
src/math.ts
test/math.test.ts
expected/
edit-bigint-001.snapshot.json
reports/
2026-08-14-smoke.html
运行前复制 fixture 到临时目录,运行后收集 diff、命令结果、trace 和成本报告。不要直接在 fixture 原地执行,否则一次失败 eval 会污染下一次结果。
| 方式 | 优点 | 风险 |
|---|
| 目录复制 | 简单直观 | 大仓库慢 |
| git worktree | 适合真实仓库 | 需要处理依赖缓存 |
| 容器快照 | 隔离强 | 成本较高,调试不便 |
初学阶段用目录复制即可。产品化后,再把依赖缓存、git worktree 和容器隔离纳入执行器。
10.4 Deterministic mock model
真实模型有随机性、网络波动和供应商变化。Eval 必须分两层。
第一层用 deterministic mock model 测 runtime 逻辑。它按照脚本返回固定 tool calls,用来验证 schema 校验、权限、checkpoint、trace、停止条件。
第二层用真实模型测端到端能力。它更接近用户体验,但结果会有波动,需要统计和人工抽查。
type ScriptedModelStep =
| { type: "message"; content: string }
| { type: "tool_call"; name: string; args: unknown; id: string };
class ScriptedModel implements ModelProvider {
constructor(private steps: ScriptedModelStep[][]) {}
async *generate(): AsyncGenerator<ModelEvent> {
const next = this.steps.shift();
if (!next) throw new Error("No scripted response");
for (const item of next) {
if (item.type === "message") {
yield { type: "text_delta", text: item.content };
} else {
yield {
type: "tool_call",
toolCallId: item.id,
name: item.name,
args: item.args,
};
}
}
}
}
如果没有 mock model,你很难判断一次 eval 失败是模型变了,还是 runtime 改坏了。Runtime 的核心不应被真实模型随机性绑架。
10.5 行为断言优先于字面匹配
Agent 输出有自然语言,字面匹配很脆。更好的断言是行为断言。
| 断言类型 | 示例 |
|---|
| 文件断言 | src/math.ts 被修改,README.md 未被修改 |
| diff 断言 | diff 行数不超过阈值,不包含无关格式化 |
| 命令断言 | npm test 通过,npm install 未被调用 |
| trace 断言 | 出现 context.build,没有 permission_denied 后继续写入 |
| 安全断言 | 没有调用 rm -rf、没有明文 secret |
| 成本断言 | 总 token、总耗时、估算成本低于阈值 |
| 恢复断言 | crash 后 resume 不重复执行高风险工具 |
type EvalAssertion =
| { kind: "file_changed"; path: string }
| { kind: "file_unchanged"; path: string }
| { kind: "command_passed"; command: string }
| { kind: "must_not_call"; tool: string; pattern?: string }
| { kind: "trace_has_span"; name: string }
| { kind: "cost_under"; estimatedUsd: number }
| { kind: "max_steps"; count: number };
对代码编辑任务,最终标准仍应落到测试、静态检查或 snapshot 上。自然语言 judge 可以辅助评价解释质量,但不要让它成为唯一门禁。
10.6 从 trace 生成 replay
一次线上失败,如果不能转化为 eval,它就会再次发生。建议流程:
- 从 trace 中提取用户目标、关键上下文、工具调用和失败结果;
- 去除 secret、用户隐私和客户专有内容;
- 固化 workspace fixture;
- 写 expected behavior;
- 加入 CI;
- 修复 runtime;
- 证明 eval 通过。
Trace 到 eval 的转换不必一开始全自动。半自动工具已经很有价值:生成 eval 草稿,让工程师审阅。
| replay 类型 | 使用场景 |
|---|
| model replay | 固定模型事件,验证 runtime 逻辑 |
| environment replay | 固定 workspace 和工具结果,验证策略是否改变 |
如果失败来自上下文构建,优先 replay context.build。如果失败来自权限绕过,优先 replay permission span。不要把所有问题都压到端到端大模型评测上,那样又慢又难定位。
10.7 指标:不要只看成功率
- 平均 step 数;
- 平均 tool call 数;
- 平均 token;
- 平均耗时;
- 权限请求次数;
- 用户拒绝后是否停止;
- 相同错误重复次数;
- 输出是否引用证据;
- 文件 diff 是否最小;
- 回滚是否成功;
- 估算成本;
- 输出截断次数。
一个 runtime 策略可能成功率略高,但成本翻倍、工具调用翻倍、危险审批增加。这不一定是好策略。
| 指标组 | 看什么 |
|---|
| 能力指标 | pass rate、测试通过、文件正确性 |
| 效率指标 | step、tool call、latency、token、cost |
| 边界指标 | approval、must-not-call、secret、rollback、resume |
真正的产品决策通常发生在这三组指标之间。比如'通过率提升 2%,成本增加 80%,危险工具审批增加 4 倍',这不是一个可以轻易发布的改动。
10.8 Flaky eval 的处理
Agent eval 天生有波动。处理 flaky 的方法:
- runtime 单元测试使用 mock model;
- 真实模型 eval 多跑几次取统计;
- 用行为断言代替字面匹配;
- 把'必须不发生'的危险行为单独作为硬门禁;
- 把人工 judge 与程序化断言分开;
- 对不稳定 case 标记 quarantine,但不能永久忽略;
- 报告中保留每次 run 的 traceId,方便回看。
Quarantine 不是垃圾桶。每个 quarantine case 都要有 owner、原因和复查日期。否则 eval 套件会慢慢变成'失败但大家假装没看见'的仓库。
10.9 CI 分层
| 层级 | 运行频率 | 内容 | 失败处理 |
|---|
| unit | 每次提交 | 工具校验、权限、context builder | 必须阻断 |
| replay | 每次提交 | mock model trace replay | 必须阻断 |
| safety | 每次提交 | must-not-call、secret、路径越界 | 必须阻断 |
| smoke | 每日或合并前 | 小规模真实模型 | 可人工复核 |
| benchmark | 发布前 | 大规模真实任务集 | 发布门禁 |
| canary | 发布后 | 线上采样、失败聚类 | 自动回滚或告警 |
agent-eval run \
--suite replay \
--model scripted \
--report build/evals/replay.html \
--fail-on safety,regression
CI 输出不应只有'失败'。至少要列出失败 task、第一条错误 span、失败归因、相关 diff、artifact 链接和重跑命令。
10.10 Prompt A/B 与 runtime 策略对比
Eval 不只是测模型,也测 runtime 策略。例如:
maxSteps=10 vs maxSteps=20;
- 工具描述短版 vs 长版;
- 旧历史保留 4 轮 vs 8 轮;
- 搜索结果返回 20 条 vs 50 条;
- 写入前是否强制计划;
- 工具失败后是否允许自动重试;
- context compression 阈值 70% vs 85%;
- MCP 工具按全量暴露 vs 按任务筛选。
每次对比只改一个关键变量。否则你不知道提升来自哪里。
{
"experiment": "context-tail-4-vs-8",
"baseline": "tail4",
"candidate": "tail8",
"tasks": 60,
"baselinePassRate": 0.78,
"candidatePassRate": 0.82,
"costDeltaPct": 21.4,
"latencyDeltaPct": 18.9,
"safetyRegressions": 0,
"decision": "manual_review"
}
不要让'更聪明'这种词进入发布结论。发布结论要写数据:通过率、成本、延迟、安全回归、失败样本。
10.11 Judge 的使用边界
LLM-as-judge 可以用,但要小心。它适合评价解释是否清楚、摘要是否覆盖证据、最终回答是否引用了相关文件;不适合替代测试命令、权限策略和 secret 扫描。
| 场景 | 是否适合 judge |
|---|
| 答案是否清晰 | 适合 |
| 是否引用了证据 | 适合,但要给证据列表 |
| 代码是否编译 | 不适合,用编译器 |
| 测试是否通过 | 不适合,用测试命令 |
| 是否越权 | 不适合,用策略和 trace |
| 是否泄露 secret | 不适合,用扫描器和脱敏规则 |
Judge 也要有版本。prompt、模型、温度、评分 rubric 改了,历史分数就不能直接比较。
10.12 成本治理进入 eval
Agent Runtime 的成本不是只有模型 token。还包括:
- 模型输入输出 token;
- 工具调用时间;
- 外部 API 费用;
- 浏览器或容器资源;
- 重试成本;
- 长上下文导致的延迟;
- 人工审批等待成本。
Eval 报告中至少记录估算模型成本、总耗时、平均 step 和 p95 latency。成本门禁可以先从简单阈值开始:
{
"costPolicy": {
"maxEstimatedUsdPerTask": 0.25,
"maxStepsPerTask": 20,
"maxToolCallsPerTask": 40,
"maxP95LatencyMs": 120000
}
}
成本门禁不是为了让 Agent 变得吝啬,而是为了防止 runtime 策略无意中把一次简单任务拖成昂贵长跑。
10.13 动手任务
- 10 个文件编辑任务;
- 10 个工具调用任务;
- 10 个失败恢复任务;
- 每个任务有 fixture、prompt、expected、policy;
- CI 中运行 mock replay;
- safety suite 每次提交必跑;
- 成功率低于阈值则失败;
- 输出报告包含成功率、平均 step、平均 token、估算成本、失败类型;
- 从第九章的一条失败 trace 生成 replay case。
agent-eval init
agent-eval run --suite replay
agent-eval report --input .agent/eval-runs/latest.json
10.14 验收标准
- 不再靠感觉判断 Agent 是否变好;
- 能比较两个 prompt 或两个 runtime 策略;
- 一次线上失败能进入回归集;
- 危险行为有硬门禁;
- 失败报告能定位到 trace span;
- 成本、延迟和安全边界能进入发布判断。
到这里,Agent Runtime 才有了团队协作所需的反馈机制:改动可以被验证,失败可以被复现,质量可以被持续维护。
系列文章目录
相关免费在线工具
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
- Markdown转HTML
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
- HTML转Markdown
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
- JSON 压缩
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
- JSON美化和格式化
将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online