第一次接触 Agent Harness 和 Eval 时,我最困惑的是:应该先接模型、写工具,还是收集评测数据?看到成熟项目里的插件、权限、沙箱和评估器,又容易觉得每一块都得先做好。
后来我采用了一条比较容易推进的路径:先用一个假模型和一条用例,跑通“执行—记录—评分—报告”,再逐步接入真实模型、发布门禁和失败回流。
这篇文章以我的 sugarFreeHarnessAndEvals 项目为例,介绍这条入门路径。你只需要了解基础 Python 和 JSON,就可以先把离线链路跑起来;第一阶段不需要 API Key。
实现可以交给 Codex / Claude Code ,你负责明确目标、约束和验收标准。下面每一步都附有可直接复制的指令,按顺序发给 Agent,验收当前步骤后再继续;已有仓库时,让它先检查实现并补齐缺项。代码和命令示例用于理解与验证,不必逐行手敲。
1. 先确定要验证什么
从一个具体任务开始:
查询苹果的单价,再用计算器计算买六个苹果的总价。
假设单价是 7,最终答案应该是 42。但“答案出现 42”只是其中一个要求。我们还希望知道:模型是否真的查询了价格?计算器收到的参数是否正确?是否循环调用了很多次?工具失败后,任务还能不能结束?
这就是 Harness 和 Eval 的分工:
- Harness 是运行底座:管理消息、调用模型、执行工具、处理错误和结束任务,同时记录执行证据。
- Eval 是质量判断:为任务定义预期,检查运行结果,并输出通过或失败的理由。
它们不一定是某个特定框架。一个几十行的执行循环,加上几条明确的断言,也可以成为最小起点。
初学时,可以只做到图中的“评测报告”。等这一小段稳定了,再继续扩展。
可以这样告诉 Coding Agent:
请先检查当前工作区,以“查询苹果单价,再计算六个苹果总价”为例,列出答案、工具选择、参数、调用顺序和执行预算的验收标准,给出最小 Harness 与 Eval 的模块划分和分步实施方案,先把范围限定到离线执行与评测报告。
2. 第一步:搭建最小 Harness
先只选一个简单工具,例如计算器。然后让 Agent 支持两种模型响应:直接回答,或者请求调用工具。
可以这样告诉 Coding Agent:
请用 Python 实现最小 Agent Harness:统一模型接口、消息结构、工具注册与参数校验,先提供安全的基础算术计算器;Agent 支持直接回答和工具调用,将工具结果或错误放回上下文,设置最大步数,并返回答案、步骤数、工具调用数和明确终止原因,给出离线验证方式。
执行过程可以写成下面的伪代码:
# 展示执行顺序的伪代码,不是项目的完整实现。 messages = [user_message(prompt)] for step in range(max_steps): response = model.complete(messages, tool_specs) messages.append(response) if not response.tool_calls: return completed(response.content) for call in response.tool_calls: result = validate_and_execute(call) messages.append(tool_message(call.id, result)) return stopped("max_steps")这里有三个值得先弄明白的设计。
第一,工具结果需要重新进入消息历史。模型通过下一轮请求看到执行结果,才能决定继续调用还是回答。工具报错也应当作为结构化结果返回,而不是直接让整个进程崩溃。
第二,要设置最大步数。在这个项目中,一步对应一次模型请求;一次请求可能返回多个工具调用,所以“步骤数”与“工具调用数”不是同一个指标。
第三,要明确终止原因。例如completed、max_steps、model_error。后续再加入model_timeout、tool_timeout、tool_denied等原因,Eval 才能区分任务完成和异常退出。
我的实现位于src/sugarfree/core/agent.py。模型通过统一的complete(request)接口接入,工具由注册表管理。这样更换模型供应商时,Agent Loop 不需要跟着重写。
3. 第二步:用假模型跑通,而不是立即调用 API
为了验证刚写好的执行循环,我使用了ScriptedModel:提前准备每轮模型响应,运行时按顺序返回。
可以这样告诉 Coding Agent:
请实现按顺序返回预设响应的 ScriptedModel,并保存收到的模型请求;用“调用计算器计算18 + 24,再回答42”验证 Harness,检查工具确实执行、下一轮请求包含工具结果、步骤数为2且正常结束,全程不联网,并在脚本耗尽时返回明确的模型错误。
例如计算任务可以预设两轮:
第一轮:调用 calculator,参数 expression = "18 + 24" 第二轮:回答 "The result is 42."这样可以稳定检查 Harness 是否执行了工具、是否将结果加入上下文、是否在第二轮正常结束。工具仍然真实执行,只有模型响应被替换。
假模型测试验证的是运行底座,不是模型的推理能力。预设第二轮回答“42”,不能证明模型理解了工具结果;要检查上下文是否正确,应查看请求记录或增加相应测试。
这种区分能让排查变得清楚:执行循环有问题,就在离线环境修复;底座稳定后,再验证真实模型是否会做出正确决策。
想先体验项目,可以直接运行:
git clone https://github.com/sugarFreeWT/sugarFreeHarnessAndEvals.git cd sugarFreeHarnessAndEvals python scripts/run_eval.py evals/architecture-smoke.json环境要求是 Python 3.11 或更高版本。上述脚本会加载本地源码,不需要先接入模型服务,也不会调用外部 API。
4. 第三步:写一条真正可以评分的用例
任务描述只是输入,还需要把“怎样算通过”写出来。
可以这样告诉 Coding Agent:
请设计 JSON 评测用例格式和离线 Runner,包含 case ID、标签、Prompt、模型脚本及预期;先生成计算器用例,再补齐直接回答、多工具串联、错误恢复和最大步数场景,运行后输出逐 case 的 report.json,保留各项判断理由,并校验重复 ID 和非法配置。
下面是一份可运行的最小评测集。将内容保存为evals/tutorial-smoke.json:
{ "name": "tutorial-smoke", "mode": "scripted", "cases": [ { "id": "calculator_addition", "tags": ["core", "tool"], "prompt": "Use calculator to compute 18 + 24.", "model_script": [ { "tool_calls": [ { "id": "calc-add", "name": "calculator", "arguments": {"expression": "18 + 24"} } ] }, {"content": "The result is 42."} ], "expect": { "termination_reason": "completed", "answer_contains": ["42"], "required_tool_calls": [ { "name": "calculator", "arguments": {"expression": "18 + 24"} } ], "required_events": ["tool.completed"], "max_tool_calls": 1, "max_steps": 2 } } ] }运行:
python scripts/run_eval.py evals/tutorial-smoke.json --output-dir runs/tutorial-smoke这一条用例同时表达了答案、工具参数、成功事件和执行预算。运行后,先打开runs/tutorial-smoke/report.json,查看每个评分器的判断。
入门时不用一次设计几十条用例。可以先补齐五种场景:直接回答、单工具调用、多工具串联、错误恢复、达到最大步数。每条用例都应该回答一个明确问题。
5. 第四步:把断言拆成独立评估器
如果所有判断都放进一个大函数,新增评测维度会越来越困难。我把它们拆成独立的 Scorer:
可以这样告诉 Coding Agent:
请把 Runner 中的判断拆成独立 Scorer,分别检查答案片段、终止原因、工具及参数顺序、调用与步骤上限、必需和禁止事件;每项返回名称、通过状态和诊断详情,未配置的规则跳过,全部适用规则通过才算 case 通过,并用反例验证评分器确实能检出错误,后续再接工作区变化评分。
| 维度 | 检查内容 | 例子 |
|---|---|---|
| 答案 | 是否包含预期片段 | 包含 42 |
| 终止状态 | 是否按预期结束 | completed 或 max_steps |
| 工具调用 | 工具名称、参数和顺序是否符合预期 | 先 lookup,再 calculator |
| 执行预算 | 步骤和调用次数是否超限 | 最多两次调用 |
| 事件 | 必需事件和禁止事件 | 必须出现 tool.failed |
| 文件变化 | 文件产物及副作用 | 只新增指定文件 |
评分结果不只返回一个布尔值,还带上诊断信息:
{ "scorer": "max_tool_calls", "passed": false, "detail": "limit 1, got 3" }这里有两个容易误解的细节。
answer_contains只是片段匹配。“答案不是42”也包含“42”,因此它适合简单 Smoke,不能代表完整语义正确性。严格格式任务可以增加精确匹配;复杂任务则需要结构化校验、人工标注或经过校准的模型裁判。
工具请求也不等于工具成功。当前required_tools根据tool.requested判断名称是否出现;如果要求成功执行,还要检查tool.completed或更具体的结果。
项目当前有 23 条确定性 Regression 用例,使用core、tool、recovery、workspace、security等标签统计覆盖和通过率。标签可以重叠,不能把标签数量相加当成总用例数。
6. 第五步:记录轨迹,让失败可以被解释
当一条用例失败时,最终答案往往不足以定位原因。需要看到中间发生了什么。
可以这样告诉 Coding Agent:
请为 Harness 增加结构化事件和逐 case JSONL 轨迹,再实现不调用外部模型的 ReplayModel,复用记录的模型响应并重新执行工具,严格比较每一步模型请求;用正常回放和修改请求后的失败场景验证一致性,演示时仅使用本地确定性工具。
我给 Harness 增加了事件记录,每个 case 输出一份 JSONL 文件:每行一个事件,包含运行 ID、顺序号、时间、事件类型和载荷。
一个缺少参数后恢复的场景,可以观察到这样的关键事件:
model.responded:请求 calculator,但缺少 expression tool.requested tool.failed:参数校验失败 model.responded:重新提供正确参数 tool.requested tool.completed model.responded:返回最终答案 run.completed这里能验证 Harness 是否正确暴露错误、是否允许继续执行、是否最终正常结束。脚本模型中的“修正”是我们预置的;真实模型能否自主修正,需要另做在线验证。
有轨迹后,就可以实现 ReplayModel:读取记录中的模型响应,重新运行 Agent 和工具。项目的严格回放还会逐步比较模型请求,检测消息历史或工具定义是否发生变化。
python scripts/run_replay.py evals/tutorial-smoke.json ` --trajectories runs/tutorial-smoke/trajectories ` --output-dir runs/tutorial-smoke-replay回放复现的是保存下来的响应,不是让模型再生成一次答案。工具仍会执行,所以外部 API、时间相关数据或有副作用的操作,需要固定环境、替身或隔离措施;不要默认所有轨迹都能安全重放。
7. 第六步:接入真实模型,验证决策能力
离线链路稳定后,再接 DeepSeek 等真实模型。在线用例不再设置model_script,由模型自主选择工具、生成参数和回答。
可以这样告诉 Coding Agent:
请实现 OpenAI 兼容模型适配器和 live 评测入口,复用现有 Harness 与 Scorer,密钥只从环境变量读取,日志及错误信息脱敏;先准备少量真实用例和配置检查,确认配置可用后先跑1轮,再累计3轮到不同目录,生成通过率、步骤、调用数、Token和耗时对比,鉴权失败时停止多轮调用。
在本项目里,配置当前 PowerShell 的环境变量后即可运行:
$env:DEEPSEEK_API_KEY = "你的密钥" python scripts/run_live_eval.py evals/deepseek-smoke.json ` --temperature 0 ` --output-dir runs/deepseek-tutorial-run1密钥通过环境变量读取,不写入用例文件。首次接入时先确认鉴权成功,再做多轮验证,避免重复消耗时间和额度。
在线评测重点检查模型是否理解任务、遵循 Prompt、选择合适工具,以及利用工具结果完成任务。JD 中的 Skills 在本项目里主要对应工具与插件能力,尚未实现独立的 Skill 文档运行时。
多轮运行应使用不同输出目录,再对比报告:
python scripts/run_benchmark.py ` runs/deepseek-tutorial-run1/report.json ` runs/deepseek-tutorial-run2/report.json ` runs/deepseek-tutorial-run3/report.json ` --output runs/deepseek-tutorial-benchmark.json该命令只读取已有报告。后两份报告需要先分别运行在线评测生成。比较时尽量固定用例、Prompt、工具定义和模型配置,记录版本与评测集哈希,才能解释差异。
我使用deepseek-chat、temperature=0做了三轮验证:每轮 10/10 通过,平均步骤数均为 1.80,平均工具调用数均为 0.90。这只是当前样本下未观察到失败;即使温度为零,也不能据此保证所有运行都确定或所有场景都稳定。
8. 第七步:让评测成为发布门禁
当回归集稳定后,就可以让测试结果约束版本变更。
可以这样告诉 Coding Agent:
请基于现有离线报告实现可配置的发布门禁,检查通过率、最小用例数、核心case、关键标签、回放结果一致性、基线用例删除、新增失败及步骤和工具调用增长;输出诊断报告,失败返回非零退出码,接入离线CI,并用删除用例和新增失败等反例验证阻断,不自动更新基线来绕过失败。
先设置最简单的规则:必需用例全部通过,失败就返回非零退出码。再逐步增加覆盖和退化检查:
- 用例总数不能低于要求,核心 case 必须存在;
- 关键能力标签必须达到通过率阈值;
- 当前报告与严格回放报告的 case 结果一致;
- 基线用例不能被静默删除,不允许新增失败;
- 平均步骤数和工具调用数不能明显增加。
项目的gates/offline-release.json要求至少 23 条用例、整体通过率为100%,并将平均步骤和工具调用增长上限设为0.25。当前配置展开后共20项检查。
阈值要跟场景相适应。对于小型确定性核心回归,要求100%合理;面对有概率波动的真实模型,应考虑重复采样、容忍区间和人工复核。耗时也受网络影响,不宜直接照搬确定性门禁。
完整离线流程可以一条命令执行:
python scripts/run_ci_checks.py --output-dir runs/ci它包含组件测试、架构评测、Regression、严格回放、基准比较和发布门禁。组件测试使用 Python 标准库unittest;GitHub Actions 在 Python 3.11 和3.13上运行相同入口。
基线也需要审核:只有明确知道预期为什么改变,才更新它。否则一次错误的基线更新就可能把真实退化变成“新的正常状态”。
9. 第八步:让真实失败变成回归资产
最后一个问题是:发现失败之后怎么办?
直接把每个失败都加入正式评测集,会混入鉴权错误、网络问题、评估器误判和偶发噪声。因此我先把失败收集到候选 Inbox,再审核:
可以这样告诉 Coding Agent:
请实现失败Inbox与collect、review、promote、reconcile命令,按套件、case、失败评分器和终止原因去重,重复收集同一报告不增计数,并对保存信息脱敏;候选须经人工审核,正式回归用例经人工确认后关联,只有关联case在验证报告中通过才关闭问题,用模拟报告验证幂等性和状态流转。
项目根据“套件、case、失败评分器、终止原因”计算 SHA-256 指纹。重复收集同一报告不会重复计数;不同运行中的同类失败会合并并累计出现次数。
这个指纹合并的是相同失败形态,并不是自动识别根因。人工仍需看轨迹判断问题性质,必要时拆分候选。
有效问题审核通过后,手工编写有明确预期的确定性用例,再用promote关联;修复后运行 Regression,通过reconcile检查关联用例,确认通过才将状态变为resolved。
# 从一次真实评测报告收集失败;全部通过时不会产生候选。 python scripts/manage_failures.py collect ` runs/deepseek-tutorial-run1/report.json ` --inbox runs/failure-inbox.json我遇到过的真实问题是 API 401。它属于鉴权故障,审核后没有提升为 Agent 行为回归。但错误响应中出现了掩码密钥线索,这又促使我完善脱敏逻辑。失败可以带来新的工程改进,但需要先判断它究竟是什么问题。
目前闭环机制已有测试验证;最近三轮真实评测全部通过,因此没有人为制造“真实模型缺陷已修复”的案例。
10. 如果今天开始,你可以按这个顺序做
| 阶段 | 先完成什么 | 怎样确认做到了 |
|---|---|---|
| 最小 Harness | 一个假模型、一个工具、最大步数 | 调用工具后能正常结束 |
| 最小 Eval | 五种基础场景、独立评分器 | 每次失败都有具体原因 |
| 可复现诊断 | 事件记录、JSONL、严格回放 | 离线复盘并发现请求变化 |
| 真实验证 | 少量在线用例、多轮对比 | 区分框架问题和模型行为问题 |
| 发布约束 | 版本化基线、门禁、CI | 注入回归时检查确实失败 |
| 持续改进 | 失败 Inbox、审核、回归关联 | 问题关闭有验证报告支撑 |
这套项目当前的规模是23条离线 Regression、10条真实 Smoke。2026年10月8日上传前的本地验证运行了67项测试,其中1项因Windows环境限制跳过,其余通过;Regression及回放均为23/23,门禁20/20。
这些数字说明当前实现可以运行和验证,不代表覆盖已经充分。后续更值得投入的是业务任务、Prompt变体、更准确的语义评估,以及模型版本变更时的对照实验。
如果你还不知道该给 Coding Agent 下达第一个什么任务,可以从本文的计算器用例开始:让它实现最小循环、运行用例、留下证据并评分,再要求它用错误参数验证失败诊断。你要掌握的是每一步为什么做、怎样验收,再让 Agent 完成实现。这就是一套 Harness 与 Eval 的起点。