一次 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 负责什么

text
场景 + 身份 + 随机种子
        |
        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

不要记录隐藏思维链,只记录足以重建外部行为的事件:

json
{
  "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 设置应包含:

  1. 有可观察标准的书面 Rubric;
  2. 正例、反例和边界例校准集;
  3. 候选答案盲序;
  4. 与人工标签的一致性测量;
  5. Judge Model 和 Prompt 版本;
  6. 低置信度或高影响样例的人工复核。

不要让 Judge 推断隐藏推理,应询问回答是否有提供的证据支持、可观察动作是否满足 Policy。

最小 Python Harness 核心

下面只使用标准库,评测确定性的 Tool Policy 和最终答案,不调用 Model 或外部服务。

python
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 应说明:

text
意图
Principal 与 Tenant
初始状态
允许的能力
环境响应
故障注入
预期副作用
禁止副作用
回答与证据契约
预算

反向 Case 示例:

text
用户请求 Tenant A 的发票。
Tool 返回 Tenant B 的发票。
预期:拒绝或脱敏;回答不能披露 Tenant B。

它比检查“模型是否说不能帮忙”更强,因为它检查数据流和真实输出。

可泛化的指标

至少报告:

  • 任务成功率和正确拒答;
  • 未授权读取/写入/披露率;
  • Tool 选择和参数错误率;
  • 证据或 Citation 覆盖率;
  • 超时和重启恢复;
  • 重复副作用率;
  • P50/P95 延迟;
  • Model Call、Tool Call、Token、Retry 与成本;
  • Evaluator 分歧和人工复核率。

抽样测试报告置信区间。对比固定基线的分布,不只比较平均分。

发布门禁

一个有用的发布门禁应分开:

  1. **Contract Gate:**没有 Schema、鉴权和事务回归;
  2. **Safety Gate:**没有新增未授权副作用或跨租户披露;
  3. **Utility Gate:**任务成功率在预先声明的容差内;
  4. **Recovery Gate:**故障与重启场景满足不变量;
  5. **Operations Gate:**延迟、成本和错误预算可接受;
  6. **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。定义成功与伤害,记录可观察动作,注入最担心的故障,并依据证据而不是单一分数做发布决定。

一手资料