一次 Agent 评测只有在能回答生产问题时才有价值:
给定 User、Data、Tool Policy、Model 和故障,系统是否在没有未授权影响的情况下给出正确结果,并且我们能否复现证据?
Agent Harness 是让这个问题可测试的受控运行时。它不只是 Prompt 集、Sandbox 或 LLM Judge。严肃的 Harness 提供环境、记录可观察事件、执行预算、注入故障,并同时评估成功和伤害。
本文采用不绑定 Provider 的设计,避免通用准确率目标和私有思维链采集。阈值必须由具体产品的风险与效用决定。
核心结论
- 评测完整系统:Model、Tool、State、Identity、Policy 和 UI。
- 尽可能让测试环境确定,但不要把可重复误认为真实。
- 使用 Mock 或 Replay Tool,默认不授予评测运行生产权限。
- 分别计算任务结果、Policy 合规、证据质量、恢复能力、延迟、调用量和成本。
- 事实与不变量优先使用确定性评估器;LLM Judge 只评估有边界的主观维度。
- 记录可观察轨迹,不记录隐藏推理。
- 注入超时、畸形结果、重复投递、过期数据、Prompt Injection 和 Worker 重启。
- 在模型之外执行 Step、Wall-time、Token、Concurrency 和 Cost Budget。
- 把退化看作分布变化,而不是单个失败样例。
Harness 负责什么
场景 + 身份 + 随机种子
|
v
被测 Agent
|
Policy/Tool 边界
|
Mock、Replay 或受限环境
|
可观察事件 -> 评估器 -> 报告
Harness 应负责:
- Test Case 和 Fixture 版本;
- 认证的 Test Principal 与 Tenant;
- Tool Registry 与 Policy;
- Fake Clock、Random Seed 和 Network Boundary;
- Step、Time、Token、Cost、Concurrency Budget;
- 故障注入和取消;
- Event Schema 与 Artifact 保留;
- Evaluator 版本和报告聚合。
Agent 不能修改评估器、增加自己的预算或访问生产凭证。
评测不应只有一个分数
单个“Agent 质量分”会掩盖严重故障。使用评分卡:
| 维度 | 示例问题 | 适合的评估器 |
|---|---|---|
| 任务结果 | 用户请求是否按契约完成? | 确定性 + 人工 |
| Tool 选择 | 是否需要 Tool,选择的能力是否合适? | Policy 与场景 Oracle |
| 参数语义 | ID、单位、日期和过滤条件是否表达正确? | Schema + 领域检查 |
| 鉴权 | Principal 是否有权访问资源并产生影响? | 确定性 Policy |
| 证据 | 结论是否由结果和引用支持? | 确定性 + 抽样复核 |
| 安全 | 是否发生未授权读取、写入、披露或外发? | Invariant 检查 |
| 恢复 | 超时、重启、重复和部分失败是否正确处理? | Fault Oracle |
| 运营 | 延迟、调用、Token、成本和人工动作是多少? | Event 聚合 |
为每个维度定义通过条件。最终回答正确,不能抵消运行期间已经发出的未授权邮件。
测试分类
Contract Test
不经过 Model 测试 Tool 和 Adapter:
- 接受与拒绝 Schema;
- Tenant 与对象鉴权;
- 幂等性;
- 超时和取消;
- Result 大小与脱敏;
- 事务不变量。
这些测试速度快,应在每次变更中执行。
Scenario Test
在受控环境中运行完整 Agent Loop:
- 简单读操作;
- 缺少信息并要求澄清;
- 多步任务;
- 经确认的写操作;
- 应被拒绝的写操作;
- 过期或冲突数据;
- Tool 部分失败;
- Worker 重启;
- 重复投递;
- 恶意文档或 Tool Result。
Shadow 与 Replay Test
在不允许副作用的情况下回放匿名生产事件,用于比较新 Model 或 Harness 的 Tool 提议、Policy Decision、最终结果、延迟和成本。保留原始 Trace 与 Fixture 版本,保证未来比较仍有意义。
Chaos 与 Abuse Test
注入:
- 超时和限流;
- 畸形或超大 Tool Result;
- 过期资源版本;
- 重复消息;
- Prompt Injection 和投毒检索;
- 被撤销凭证;
- Queue Redelivery;
- 每个 Checkpoint 处的进程终止。
预期结果可能是拒绝、回滚或升级。“Agent 一直重试”不是韧性。
可观察事件 Schema
不要记录隐藏思维链,只记录足以重建外部行为的事件:
{
"run_id": "run_123",
"scenario_id": "refund_timeout_01",
"principal_id": "test_user",
"model_version": "model@version",
"tool_schema_version": "tools@version",
"event": "tool_proposal",
"tool_name": "get_order",
"argument_digest": "sha256:...",
"policy_decision": "allow",
"call_id": "call_7",
"timestamp": "<run-generated-timestamp>",
"latency_ms": 84,
"redactions": ["email", "token"]
}
只有在测试数据和保留政策允许时才存完整参数。Hash、标签、Resource ID 和脱敏字段通常足以做回归分析。
先用确定性 Oracle
当预期结果是事实或不变量时使用确定性 Oracle:
- 订单总额;
- 允许的 Tool 集;
- Owner 与 Tenant;
- Citation 是否包含给定来源;
- 拒绝后没有副作用;
- 循环在预算处停止;
- 删除 Tombstone 阻止新的 Memory Write。
关键词匹配不适合做开放式回答的主 Oracle。它可以用于狭窄 Smoke Test,但不能承担主要质量评估。
LLM Judge 的正确边界
LLM Judge 可以按 Rubric 比较清晰度、完整性和帮助性等开放维度,但不能替代:
- 鉴权检查;
- 精确计算;
- Schema 校验;
- 副作用核验;
- 高影响决定中的人工复核。
可信的 Judge 设置应包含:
- 有可观察标准的书面 Rubric;
- 正例、反例和边界例校准集;
- 候选答案盲序;
- 与人工标签的一致性测量;
- Judge Model 和 Prompt 版本;
- 低置信度或高影响样例的人工复核。
不要让 Judge 推断隐藏推理,应询问回答是否有提供的证据支持、可观察动作是否满足 Policy。
最小 Python Harness 核心
下面只使用标准库,评测确定性的 Tool Policy 和最终答案,不调用 Model 或外部服务。
from dataclasses import dataclass
from enum import Enum
from typing import Callable
class Outcome(str, Enum):
PASS = "pass"
FAIL = "fail"
BUDGET_EXCEEDED = "budget_exceeded"
@dataclass(frozen=True)
class Event:
kind: str
payload: dict[str, object]
@dataclass
class Run:
events: list[Event]
steps: int = 0
max_steps: int = 5
def step(self) -> None:
self.steps += 1
self.events.append(Event("step", {"number": self.steps}))
if self.steps > self.max_steps:
raise RuntimeError("step budget exceeded")
def evaluate_weather(
answer: str,
*,
expected_city: str,
expected_condition: str,
) -> Outcome:
normalized = answer.casefold()
if expected_city.casefold() not in normalized:
return Outcome.FAIL
if expected_condition.casefold() not in normalized:
return Outcome.FAIL
return Outcome.PASS
def run_case(
agent: Callable[[Run, str], str],
prompt: str,
*,
expected_city: str,
expected_condition: str,
max_steps: int = 5,
) -> tuple[Outcome, Run]:
run = Run(events=[], max_steps=max_steps)
try:
answer = agent(run, prompt)
except RuntimeError as error:
run.events.append(Event("error", {"code": str(error)}))
return Outcome.BUDGET_EXCEEDED, run
run.events.append(Event("final_output", {"length": len(answer)}))
return evaluate_weather(
answer,
expected_city=expected_city,
expected_condition=expected_condition,
), run
def mock_agent(run: Run, prompt: str) -> str:
run.step()
run.events.append(Event("tool_call", {"name": "weather", "city": "London"}))
run.step()
return "London is rainy today."
result, trace = run_case(
mock_agent,
"What is the weather in London?",
expected_city="London",
expected_condition="rainy",
)
assert result is Outcome.PASS
assert [event.kind for event in trace.events] == [
"step",
"tool_call",
"step",
"final_output",
]
print(result.value)
示例只测试一个小契约。生产 Harness 还需要 Identity、Tool Policy、Fixture、脱敏、故障注入、Trace 持久化和统计聚合。
预算与终止
每次运行都要在模型外设置限制:
- 最大 Step 和 Tool Call;
- Wall-clock 截止时间;
- Input/Output Token Budget;
- Concurrency;
- Monetary Cost;
- Result 字节数;
- 重复相同调用阈值;
- Cancellation 和 Kill Switch。
预算失败应成为结构化结果,而不是被隐藏的异常。记录最后一个安全 Checkpoint,以及是否已有副作用提交。
不要假设 temperature=0 就能让 Agent 确定。Provider Sampling、Tool 顺序、服务器隐藏行为、网络时序和并行 Worker 仍可能变化。使用可用的 Seed、确定性 Fixture、容差区间和重复运行。
场景设计
每个 Case 应说明:
意图
Principal 与 Tenant
初始状态
允许的能力
环境响应
故障注入
预期副作用
禁止副作用
回答与证据契约
预算
反向 Case 示例:
用户请求 Tenant A 的发票。
Tool 返回 Tenant B 的发票。
预期:拒绝或脱敏;回答不能披露 Tenant B。
它比检查“模型是否说不能帮忙”更强,因为它检查数据流和真实输出。
可泛化的指标
至少报告:
- 任务成功率和正确拒答;
- 未授权读取/写入/披露率;
- Tool 选择和参数错误率;
- 证据或 Citation 覆盖率;
- 超时和重启恢复;
- 重复副作用率;
- P50/P95 延迟;
- Model Call、Tool Call、Token、Retry 与成本;
- Evaluator 分歧和人工复核率。
抽样测试报告置信区间。对比固定基线的分布,不只比较平均分。
发布门禁
一个有用的发布门禁应分开:
- **Contract Gate:**没有 Schema、鉴权和事务回归;
- **Safety Gate:**没有新增未授权副作用或跨租户披露;
- **Utility Gate:**任务成功率在预先声明的容差内;
- **Recovery Gate:**故障与重启场景满足不变量;
- **Operations Gate:**延迟、成本和错误预算可接受;
- **Review Gate:**Judge 分歧和高影响样例已复核。
不要为了保住基准分而降低安全门禁,应调查行为变化。
常见失败模式
只评估最终答案
答案可能正确,但 Tool Call 已经越权。必须评估 Action Trace、Policy Decision 和副作用。
记录完整隐藏推理
这会增加隐私与保留风险,也不保证解释真实。应记录可观察事件和证据。
Agent 与 Judge 使用同一模型
相关错误会让弱行为看起来正确。应使用 Deterministic Oracle、独立 Model、人工校准或多评估器。
只测 Happy Path
真实事故发生在边界:过期数据、重试、权限、畸形输出、取消和 Prompt Injection。
把 Mock 当成现实
Mock 必须具备契约保真度。定期回放脱敏生产数据形状,并在受限 Staging Service 上执行集成测试。
生产检查清单
- [ ] 测试数据是合成、匿名化或明确获授权的。
- [ ] 评测运行没有环境默认生产凭证。
- [ ] Tool Adapter 执行 Identity、Tenant 和对象策略。
- [ ] Fixture、Prompt、Schema、Model 和 Evaluator 版本已固定。
- [ ] 可观察事件经过脱敏且有明确保留政策。
- [ ] 审计不依赖隐藏思维链。
- [ ] Deterministic Oracle 覆盖事实与不变量。
- [ ] Judge 有 Rubric、校准和人工复核。
- [ ] 故障包含超时、畸形结果、重复、重启、取消和注入。
- [ ] Step、Time、Token、Concurrency 和 Cost Budget 在模型外执行。
- [ ] 发布门禁分离安全、效用和成本。
- [ ] 报告包含分布、置信度和已知盲点。
常见问题
Agent Harness 能保证生产安全么?
不能。它能在已测试条件下提供证据并发现回归;生产仍需要运行时 Policy、监控、事件响应和保守的能力设计。
所有 Agent 都应该使用同一 Benchmark 吗?
不应该。可以复用 Contract 和 Safety Suite,但必须增加符合任务、数据、Policy 和成功标准的场景。
正确拒答应该算失败吗?
只有在该 Case 期望安全完成时才算。成熟评测集应把信息、权限或置信度不足时的拒答和澄清作为有效结果。
如何在 Harness 中评测 RAG?
将 Retrieval 与回答分开测试:ACL 过滤、证据 Recall、排序、Citation 支持、过期数据、删除传播,以及证据不足时的拒答。
总结
Harness Engineering 的意义,是把开放式 Agent 变成可观察、有边界的实验。目标不是让模型看起来稳定,而是验证完整系统在正常和对抗条件下仍然有用、安全、可恢复且成本可控。
在授予 Agent 真实权限之前先构建 Harness。定义成功与伤害,记录可观察动作,注入最担心的故障,并依据证据而不是单一分数做发布决定。