Agent 评估:从指标到方法建立智能体质量的工程判断标准

本文是「Agent 基础与工程」系列专栏的第 11 篇。专栏总览参见:《Agent 基础认知与工程架构全景》。

在 Agent 原型验证阶段,开发团队常依赖“挑几个用例手工提问,看着回答像那么回事”来评估系统。然而,一旦进入团队协同或上线迭代,这种凭感觉测试的方式会立即失效:

  • 微调了 System Prompt,无法衡量对全量存量场景带来的正面与负面影响;
  • 升级了基座模型或切换了模型供应商,不知道哪些特定工具的调度能力发生了退化;
  • 新增了 3 个查询工具,原有工具的调用误判率是否因此升高无从查证;
  • 无法向业务方给出明确的 SLA(如“在 95% 的场景下,任务能在 5 步内准确交付”)。

没有自动化评估指标的 Agent 迭代如同盲人摸象。建立可量化、可回归、自动化的评测工程体系,是智能体系统脱离 Demo 玩具状态的必经之路。


为什么 Agent 评估不能套用传统单测与基础模型评测

传统的评测工具在面对智能体系统时存在底层假设的失效:

评测类型 核心假设 为什么无法直接评估 Agent
传统软件单元测试 给定确定性输入,必然产出确定性输出( ) 模型生成具有随机性;完成同一个业务目标,模型可以选择多条截然不同的工具调用路径
基础大模型 Eval(如 MMLU) 单轮问答、静态选择题、标准文本匹配(BLEU/ROUGE) 静态文本匹配无法衡量 Agent 对外部物理环境的读写状态变化与因果执行能力
Agent 综合评测 动态环境交互、多步骤决策路径、基于最终状态断言 既要评估最终结果的达成度,又要评估中间决策轨迹(Trajectory)的代价与安全性

因此,Agent 评估必须将环境状态验证与轨迹行为审计深度结合。


核心评估维度的工程解构

构建评估框架时,系统质量由四个可量化的核心维度共同决定:

1. 结果达成度(Outcome Quality / Pass@K)

结果是否正确,绝不能听信模型自己的宣称,必须基于物理环境的真实状态断言。

  • 针对 Coding Agent:运行 pytest,看是否 100% 退出码为 0;
  • 针对运维 Agent:检查数据库中的特定表字段是否由 PENDING 正确流转为 RESOLVED;
  • 针对数据分析 Agent:提取其最终落盘的 CSV,运行 Pandas 脚本比对数据统计量是否与 Golden Baseline 误差小于 。

2. 轨迹效率(Trajectory Efficiency)

同样是完成任务,不同智能体架构或不同模型的执行质量存在显著差距:

  • 步进开销(Step Overhead):平均需要经历多少轮模型推理与工具调用闭环;
  • 有效信息比率(Productive Action Ratio):在总共调用的 次工具中,真正产出了有效业务观测结果的操作占比;
  • Token 转化率:单任务累计消耗的 Prompt 与 Completion Tokens 数量。

3. 稳健性与自愈力(Error Recovery Rate)

在评估用例中注入受控扰动,检验系统在逆境下的生存能力:

  • 当工具首次调用返回超时(HTTP 504)或缺少必要参数报错时,系统能否根据报错信息自省并纠偏,而不是立即崩溃。

三大评估工程方法选型

在构建测试流水线时,按成本与确定性自下而上组合三种评测方法:

  1. 执行断言(Execution-based Eval):业界公认最可靠的手段(例如 SWE-bench、SWE-bench Lite)。为 Agent 提供隔离的初始沙箱(Docker),任务结束后由自动化测试套件执行回归,直接依靠退出码判断。
  2. 规则断言(Deterministic Rule Guards):零模型调用的高速测试。校验模型返回的参数是否合规、是否在禁止调用的工具列表中触发了调用。
  3. 模型裁判(LLM-as-a-Judge):仅用于无法用代码量化的主观场景(如“文案是否富有同理心”)。使用 LLM 打分时,必须配置明确的结构化评分细则(Rubric)并强制输出 Chain-of-Thought 分析过程,严禁无理由直接打分。

生产级自动化沙箱评估器实现

以下展示一个使用 Python 实现的 Agent 自动化回归测试流水线,支持沙箱前置数据重置、执行轨迹收集与确定性指标统计:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
import json
import time
from dataclasses import dataclass, field
from typing import List, Dict, Any, Callable

@dataclass
class EvalCase:
case_id: str
prompt: str
setup_fixture: Callable[[], None] # 测试前置环境准备
assertion_hook: Callable[[], bool] # 测试后置环境断言

@dataclass
class TrajectoryMetric:
case_id: str
passed: bool
total_steps: int
total_tokens: int
elapsed_seconds: float
error_log: List[str] = field(default_factory=list)

class AgentEvaluationRunner:
def __init__(self, agent_invoker: Callable[[str], Dict[str, Any]]):
self.agent_invoker = agent_invoker

def run_benchmark(self, dataset: List[EvalCase]) -> Dict[str, Any]:
results: List[TrajectoryMetric] = []

for case in dataset:
print(f"\n[运行评测用例] {case.case_id}...")

# 1. 重置沙箱初始状态
case.setup_fixture()

start_time = time.time()
try:
# 2. 触发 Agent 执行任务
trace = self.agent_invoker(case.prompt)
elapsed = time.time() - start_time

# 3. 执行确定性后置断言(检查物理环境而非模型文本)
assert_passed = case.assertion_hook()

metric = TrajectoryMetric(
case_id=case.case_id,
passed=assert_passed,
total_steps=trace.get("steps", 0),
total_tokens=trace.get("tokens", 0),
elapsed_seconds=round(elapsed, 2)
)
except Exception as e:
metric = TrajectoryMetric(
case_id=case.case_id,
passed=False,
total_steps=0,
total_tokens=0,
elapsed_seconds=round(time.time() - start_time, 2),
error_log=[str(e)]
)

results.append(metric)

# 4. 汇总基准表现
total_cases = len(results)
passed_cases = sum(1 for r in results if r.passed)
avg_steps = sum(r.total_steps for r in results) / total_cases if total_cases else 0

return {
"summary": {
"total": total_cases,
"passed": passed_cases,
"pass_rate": f"{(passed_cases / total_cases) * 100:.1f}%",
"avg_steps_per_task": round(avg_steps, 2)
},
"details": [r.__dict__ for r in results]
}

# --- 模拟评测执行测试 ---
if __name__ == "__main__":
# 模拟环境状态
sandbox_db = {"users": {"101": {"status": "LOCKED"}}}

# 构建 Golden Dataset 用例
suite = [
EvalCase(
case_id="CASE_UNLOCK_USER_101",
prompt="用户 101 的状态被误锁定了,请帮其执行解锁并记录原因。",
setup_fixture=lambda: sandbox_db["users"].update({"101": {"status": "LOCKED"}}),
assertion_hook=lambda: sandbox_db["users"]["101"]["status"] == "ACTIVE"
)
]

# 模拟被测的 Agent 执行结果
def mock_agent_call(prompt: str):
# 模拟模型执行解锁动作
sandbox_db["users"]["101"]["status"] = "ACTIVE"
return {"steps": 2, "tokens": 450}

runner = AgentEvaluationRunner(agent_invoker=mock_agent_call)
benchmark_report = runner.run_benchmark(suite)
print("\n--- 自动化评测报告 ---")
print(json.dumps(benchmark_report, ensure_ascii=False, indent=2))

建立持续回归流:CI/CD 中的 Agent 防退化

为了在系统迭代中彻底防止退化,必须将评测集与代码仓库的持续集成(CI/CD)打通:

  1. 构建 Golden Dataset(黄金测试集):每次线上发生故障(Bad Case),必须脱敏提取为一条标准测试用例,包含固化的前置 DB 镜像与校验断言。
  2. 门禁阻断(Merge Blocker):在 GitHub Actions / GitLab CI 中,只要涉及 System Prompt、工具定义或状态流转代码的 Pull Request,自动拉起基准评测。若黄金用例的 Pass Rate 发生负向滑坡,直接阻断代码合入。
  3. 长期基线跟踪:监控每日定时运行的基准测试分数,识别模型提供商在后台静默升级导致的潜在行为漂移。

系列导航与参考