第 10 章 评估:让 Agent 质量可量化
第 10 章 评估:让 Agent 质量可量化
传统软件开发有单元测试、集成测试,pass/fail 清清楚楚。但 Agent 是概率性的——同一个问题,模型每次回答可能不一样。你怎么知道"这个 Agent 是好的"?怎么知道"这次改动没把它改坏"?这一章,我们用 ADK 的评估框架,给 Agent 装上质量标尺。
10.1 为什么评估是 Agent 框架的分水岭
10.1.1 传统测试在 Agent 面前失灵
传统软件测试的前提是确定性:同样的输入,同样的输出,所以可以写断言。但 LLM Agent 是概率性的:
- 同一个用户问题,模型可能用不同的话回答
- 工具调用顺序可能不同,但结果都对
- 有些回答"语义对但字面不同"
确定性断言通常不适用。你需要的是评估"最终输出质量"以及"达到结果的轨迹(trajectory)"——Agent 做了一系列什么样的决策、走了什么样的路径。
10.1.2 评估什么
ADK 的评估分两大部分:
- 评估轨迹与工具使用(Trajectory and Tool Use):分析 Agent 达到解决方案的步骤——工具选择、策略、路径效率。把实际轨迹与期望轨迹对比。
- 评估最终响应(Final Response):评估最终输出的质量、相关性和正确性。
10.1.3 四种运行方式
ADK 评估可以通过四种方式运行:
| 方式 | 命令/API | 适用 |
|---|---|---|
| Web UI | adk web 的 Eval 标签页 |
交互式评估、检查行为 |
| pytest | AgentEvaluator.evaluate(...) |
集成到测试管道 |
| 命令行 | adk eval |
直接运行 eval set 文件 |
| Conformance | adk conformance test |
对基线做回归测试 |
10.2 用 AgentEvaluator 跑评估
10.2.1 测试文件(Test Files)
最简单的评估单元是测试文件——每个文件代表一个简单的 agent-模型交互(相当于单元测试)。每个 turn 包含:用户查询、期望的工具调用轨迹、期望的中间响应、期望的最终响应。
10.2.2 pytest 程序化评估
from google.adk.evaluation.agent_evaluator import AgentEvaluator
import pytest
@pytest.mark.asyncio
async def test_yunxiao_basic():
"""Test the agent's basic ability via a session file."""
await AgentEvaluator.evaluate(
agent_module="yunxiao_agent",
eval_dataset_file_path_or_dir="tests/integration/fixture/yunxiao_agent/simple_test.test.json",
)10.2.3 CLI 评估
adk eval \
samples_for_testing/hello_world \
samples_for_testing/hello_world/hello_world_eval_set_001.evalset.jsonadk eval 接受 agent 目录路径 + eval set 文件路径。配置文件用 --config_file_path,详细结果用 --print_detailed_results。
10.2.4 测试文件结构
一个 .test.json 文件的关键结构:
{
"eval_set_id": "yunxiao_agent_refund_set",
"eval_cases": [
{
"eval_id": "case_refund_eligible",
"conversation": [
{
"user_content": {
"parts": [ { "text": "我想退这个电子产品" } ],
"role": "user"
},
"final_response": {
"parts": [ { "text": "电子产品支持 7 天无理由退货,条件是商品未拆封。" } ],
"role": "model"
},
"intermediate_data": {
"tool_uses": [
{
"args": { "product_category": "electronics" },
"name": "get_refund_policy"
}
],
"intermediate_responses": []
}
}
]
}
]
}发现没有——这里既检查最终响应(final_response),也检查工具轨迹(intermediate_data.tool_uses)。这就是"评估轨迹 + 评估响应"的具体落地。
10.3 内置评估标准(Criteria)
ADK 提供一整套内置评估标准,分三类:基于参考的(需要期望数据)、基于 rubric 的(需要自定义评分规则)、LLM 裁判的。
10.3.1 常用内置标准
| 标准 | 描述 | 类型 |
|---|---|---|
tool_trajectory_avg_score |
工具调用轨迹精确匹配 | 参考 |
response_match_score |
与参考响应 ROUGE-1 相似度 | 参考 |
final_response_match_v2 |
LLM 裁判语义匹配 | LLM 裁判 |
rubric_based_final_response_quality_v1 |
自定义 rubric 的响应质量 | rubric |
rubric_based_tool_use_quality_v1 |
自定义 rubric 的工具使用 | rubric |
hallucinations_v1 |
幻觉检测(groundedness) | LLM 裁判 |
safety_v1 |
响应安全性/无害性 | LLM 裁判 |
multi_turn_task_success_v1 |
多轮任务是否达成 | LLM 裁判 |
10.3.2 工具轨迹匹配
tool_trajectory_avg_score 支持三种匹配类型:
EXACT:实际与期望工具调用完全匹配,无多余/缺失IN_ORDER:期望的调用都以相同顺序出现,允许中间插入其他调用ANY_ORDER:期望的调用都出现、顺序不限
配置(默认 EXACT,阈值 1.0 表示要求 100% 匹配):
{
"criteria": {
"tool_trajectory_avg_score": {
"threshold": 1.0,
"match_type": "IN_ORDER"
}
}
}10.3.3 响应匹配
response_match_score 用 ROUGE-1(unigram 重叠)量化输出与期望的内容重叠:
{
"criteria": {
"response_match_score": 0.8
}
}10.3.4 LLM 裁判:final_response_match_v2
final_response_match_v2 让一个裁判 LLM 判断 agent 响应是否与期望语义匹配:
{
"criteria": {
"final_response_match_v2": {
"threshold": 0.8,
"judge_model_options": {
"judge_model": "gemini-flash-latest",
"num_samples": 5
}
}
}
}num_samples 表示多次采样后多数表决,提高判断稳定性。
10.3.5 基于 rubric:rubric_based_final_response_quality_v1
当你想定义"好的响应"的具体属性时,用 rubric 标准:
{
"criteria": {
"rubric_based_final_response_quality_v1": {
"threshold": 0.8,
"judge_model_options": {
"judge_model": "gemini-flash-latest",
"num_samples": 5
},
"rubrics": [
{
"rubric_id": "conciseness",
"rubric_content": {
"text_property": "The agent's response is direct and to the point."
}
},
{
"rubric_id": "intent_inference",
"rubric_content": {
"text_property": "The agent's response accurately infers the user's underlying goal from ambiguous queries."
}
}
]
}
}
}每个 rubric 由裁判 LLM 判定 yes/no,多次采样多数表决,最终分数为所有 rubric 的平均。
10.3.6 幻觉检测:hallucinations_v1
hallucinations_v1 检查响应相对上下文的 groundedness——两步:Segmenter(把响应切分句子)→ Sentence Validator(逐句评估,标签为 supported/unsupported/contradictory):
{
"criteria": {
"hallucinations_v1": {
"threshold": 0.8,
"judge_model_options": {
"judge_model": "gemini-flash-latest"
},
"evaluate_intermediate_nl_responses": true
}
}
}10.3.7 选择标准的建议
| 场景 | 推荐标准 |
|---|---|
| CI/CD 回归测试 | tool_trajectory_avg_score + response_match_score(快速、可预测) |
| 有可信参考、要语义等价 | final_response_match_v2 |
| 无参考、评估响应质量 | rubric_based_final_response_quality_v1 |
| 验证工具使用正确性 | rubric_based_tool_use_quality_v1 |
| 检查响应是否 grounded | hallucinations_v1 |
| 检查有害内容 | safety_v1 |
| 多轮任务完成 | multi_turn_task_success_v1 |
10.4 自定义指标:写你自己的评估器
内置标准不够用时,ADK 允许你定义自定义指标。自定义指标就是一个 Python 函数,签名固定:
from typing import Optional
from google.adk.evaluation.eval_case import Invocation
from google.adk.evaluation.eval_metrics import EvalMetric
from google.adk.evaluation.conversation_scenarios import ConversationScenario
from google.adk.evaluation.evaluator import EvaluationResult
def my_custom_metric(
eval_metric: EvalMetric,
actual_invocations: list[Invocation],
expected_invocations: Optional[list[Invocation]],
conversation_scenario: Optional[ConversationScenario],
) -> EvaluationResult:
...10.4.1 完整示例:检查最终响应完全匹配
import statistics
from typing import Optional
from google.adk.evaluation.conversation_scenarios import ConversationScenario
from google.adk.evaluation.eval_case import Invocation
from google.adk.evaluation.eval_metrics import EvalMetric, EvalStatus
from google.adk.evaluation.evaluator import EvaluationResult, PerInvocationResult
def check_final_response_exact_match(
eval_metric: EvalMetric,
actual_invocations: list[Invocation],
expected_invocations: Optional[list[Invocation]],
conversation_scenario: Optional[ConversationScenario],
) -> EvaluationResult:
"""Checks if the final response matches the expected response exactly."""
if not expected_invocations:
return EvaluationResult(overall_score=0.0, overall_eval_status=EvalStatus.NOT_EVALUATED)
per_invocation_results = []
for actual, expected in zip(actual_invocations, expected_invocations):
actual_text = "".join([part.text for part in actual.final_response.parts])
expected_text = "".join([part.text for part in expected.final_response.parts])
score = 1.0 if actual_text == expected_text else 0.0
eval_status = EvalStatus.PASSED if score else EvalStatus.FAILED
per_invocation_results.append(PerInvocationResult(
actual_invocation=actual,
expected_invocation=expected,
score=score,
eval_status=eval_status
))
average_score = statistics.mean(result.score for result in per_invocation_results)
threshold = eval_metric.criterion.threshold
overall_eval_status = (
EvalStatus.PASSED if average_score >= threshold else EvalStatus.FAILED
)
return EvaluationResult(
overall_score=average_score,
overall_eval_status=overall_eval_status,
per_invocation_results=per_invocation_results,
)10.4.2 在 EvalConfig 中使用自定义指标
{
"criteria": {
"my_check_final_response_exact_match": {
"threshold": 0.8
},
"tool_trajectory_avg_score": {
"threshold": 1.0
}
},
"custom_metrics": {
"my_check_final_response_exact_match": {
"code_config": {
"name": "my_agent.metrics.check_final_response_exact_match"
}
}
}
}运行:adk eval --config_file_path=<path_to_this_config>。ADK 会对每个 eval case 执行你的函数,分数 >= 阈值则通过。
10.5 用户模拟:让 LLM 扮演用户
固定用户提示不够真实——真实对话可能以意料之外的方式进行。ADK 支持用生成式 AI 动态模拟用户。
10.5.1 ConversationScenario
核心是 ConversationScenario,定义用户与 agent 对话的目标:
{
"starting_prompt": "What can you do for me?",
"conversation_plan": "Ask the agent to check the status of my order ORD-20260901-001. Then ask if it can be returned.",
"user_persona": "NOVICE"
}starting_prompt:用户启动对话的固定初始提示conversation_plan:用户必须达到目标的高级指导原则user_persona:用户特质(技术熟练度、语言风格)
10.5.2 预置 Persona
ADK 提供三个预置用户画像:EXPERT(专家)、NOVICE(新手)、EVALUATOR(评估者)。比如 NOVICE 是目标导向的(等被问才提供细节)、对话式语气、不纠正 agent 错误。
10.5.3 自定义 Persona
你还可以完全自定义用户画像:
{
"starting_prompt": "I need help with my account.",
"conversation_plan": "Ask the agent to help you refund an order.",
"user_persona": {
"id": "IMPATIENT_USER",
"description": "A user who is in a rush and gets easily frustrated.",
"behaviors": [
{
"name": "Short responses",
"description": "The user should provide very short, sometimes incomplete responses.",
"behavior_instructions": [
"Keep your responses under 10 words.",
"Omit polite phrases."
],
"violation_rubrics": [
"The user response is over 10 words.",
"The user response is overly polite."
]
}
]
}
}10.5.4 把场景加入 EvalSet
# 创建 EvalSet
adk eval_set create \
contributing/samples/core/hello_world \
eval_set_with_scenarios
# 把场景加入 EvalSet
adk eval_set add_eval_case \
contributing/samples/core/hello_world \
eval_set_with_scenarios \
--scenarios_file conversation_scenarios.json \
--session_input_file session_input.json注意:动态场景下不能用需要期望响应的标准(如
tool_trajectory_avg_score、response_match_score)——因为没有"标准答案"。改用hallucinations_v1、safety_v1这类不依赖参考的标准。
10.6 环境模拟:隔离外部依赖
10.6.1 为什么需要环境模拟
评估依赖外部 API 的 agent 时,实时运行工具可能慢、贵或不可靠。环境模拟让你在 agent 执行期间安全地拦截工具调用,替换为受控、确定性的响应,无需修改 agent 本身。
10.6.2 注入错误:测试 agent 的容错
用 EnvironmentSimulationFactory + 注入配置,测试 agent 如何应对 API 故障:
from google.adk.agents import LlmAgent
from google.adk.tools.environment_simulation import EnvironmentSimulationFactory
from google.adk.tools.environment_simulation.environment_simulation_config import (
EnvironmentSimulationConfig,
InjectedError,
InjectionConfig,
ToolSimulationConfig,
)
config = EnvironmentSimulationConfig(
tool_simulation_configs=[
ToolSimulationConfig(
tool_name="get_user_profile",
injection_configs=[
InjectionConfig(
injected_error=InjectedError(
injected_http_error_code=503,
error_message="Service temporarily unavailable.",
)
)
],
)
]
)
agent = LlmAgent(
name="my_agent",
model="gemini-flash-latest",
tools=[get_user_profile],
before_tool_callback=EnvironmentSimulationFactory.create_callback(config),
)这样,get_user_profile 在评估时会被替换成"总是返回 503 错误"——你可以测试 agent 面对服务故障时的表现。
10.6.3 三种注入模式
注入固定成功响应:
InjectionConfig(
injected_response={"status": "ok", "order_id": "ORD-9999"}
)按参数条件注入(只有特定参数才触发):
InjectionConfig(
match_args={"item_id": "ITEM-404"},
injected_error=InjectedError(
injected_http_error_code=404,
error_message="Item not found.",
),
)概率注入(30% 概率 500 错误,可复现):
InjectionConfig(
injection_probability=0.3,
random_seed=42,
injected_error=InjectedError(
injected_http_error_code=500,
error_message="Internal server error.",
),
)10.6.4 Mock 策略:自动生成逼真响应
除了注入,环境模拟还可以用 LLM 自动生成逼真的 mock 响应(基于工具 schema + 状态上下文)。模拟器会:分析工具 schema、识别有状态依赖(如 create_order 产生 order_id 供 get_order 消费)、生成与当前状态一致的响应。
config = EnvironmentSimulationConfig(
tool_simulation_configs=[
ToolSimulationConfig(
tool_name="get_order_status",
mock_strategy_type=MockStrategy.MOCK_STRATEGY_TOOL_SPEC,
),
],
)10.7 评估接入 CI:让回归自动化
评估的终极价值是作为质量门禁。把评估接进 CI,每次代码改动自动跑回归:
# test_yunxiao_quality.py
from google.adk.evaluation.agent_evaluator import AgentEvaluator
import pytest
@pytest.mark.asyncio
async def test_yunxiao_full_eval_set():
await AgentEvaluator.evaluate(
agent_module="yunxiao_agent",
eval_dataset_file_path_or_dir="tests/eval/yunxiao.evalset.json",
config_file_path="tests/eval/eval_config.json",
)配合 GitHub Actions / GitLab CI,每次 PR 自动跑评估。Agent 的质量从此可回归——改动会不会让质量下降,机器说了算,不再靠感觉。
主线项目:给云销客服建评估集
目标
给云销客服建一套评估集,覆盖三类场景:
- 正常订单查询(期望工具轨迹:
get_order_status) - 退款政策咨询(期望工具轨迹:
get_refund_policy) - 边界刁难问题(如"我东西呢?"这种模糊问题,评估意图理解)
评估配置
{
"criteria": {
"tool_trajectory_avg_score": {
"threshold": 1.0,
"match_type": "IN_ORDER"
},
"response_match_score": 0.8,
"rubric_based_final_response_quality_v1": {
"threshold": 0.8,
"judge_model_options": {
"judge_model": "gemini-flash-latest",
"num_samples": 5
},
"rubrics": [
{
"rubric_id": "no_fabrication",
"rubric_content": {
"text_property": "The agent's response does not fabricate order or refund information that it did not obtain from its tools."
}
},
{
"rubric_id": "helpful_tone",
"rubric_content": {
"text_property": "The agent's response is helpful, professional, and polite."
}
}
]
}
}
}这个配置组合了:工具轨迹匹配(1.0 严格)、响应文本匹配(0.8)、以及两个 rubric(不编造信息 + 友好语气)——轨迹 + 响应 + 质量,三层把关。
「为什么 ADK 这样设计」:评估是 Agent 框架的分水岭
为什么说评估能力决定了框架的层次?
回顾第 1 章的五维坐标系,很多框架在"可控性、可观测性"上能装点门面,但评估是硬功夫——它需要一整套基础设施:测试数据格式、评估器、LLM 裁判、用户模拟、环境模拟、CI 集成。这是框架作者对"Agent 到底该怎么交付"的真实理解深度的体现。
ADK 的评估设计有几个深思熟虑的点:
轨迹 + 响应双评估:不只关心"结果对不对",还关心"路径对不对"。因为工具调用的副作用(真的发了消息、真的扣了钱)比文本结果更重要。
多层级标准:从快速确定性的
tool_trajectory(适合 CI),到需要 LLM 裁判的rubric和hallucinations(适合质量评估),按场景选尺子。模拟一切:用户可以模拟(用户模拟)、环境可以模拟(环境模拟)——把不可控的东西变得可控,这是 Agent 测试的核心难题。
接入 CI:评估不只是一个工具,而是可以进入开发流程成为门禁。
一句话:ADK 把评估做成了框架的原生能力,因为 Agent 这种概率性系统,没有评估就无从谈质量、无从谈迭代、无从谈生产。这就是为什么我们说"评估是 Agent 框架的分水岭"。
本章小结
- 评估为什么难:Agent 是概率性的,确定性断言不适用,需要评估轨迹 + 响应
- 四种运行方式:Web UI、pytest(AgentEvaluator)、CLI(adk eval)、Conformance 回归
- 内置标准三类:基于参考(tool_trajectory/response_match)、rubric(响应质量/工具使用)、LLM 裁判(final_response_match/hallucinations/safety)
- 工具轨迹三种匹配:EXACT / IN_ORDER / ANY_ORDER
- 自定义指标:就是一个固定签名的 Python 函数,返回 EvaluationResult
- 用户模拟:ConversationScenario + UserPersona,LLM 动态扮演用户(预置 EXPERT/NOVICE/EVALUATOR)
- 环境模拟:拦截工具调用,注入错误/响应/延迟,或用 LLM mock,隔离外部依赖
- 接入 CI:评估成为质量门禁,改动可回归
练习
- 建一个测试文件:给云销客服写一个
.test.json,验证"查订单"的正确工具轨迹和响应。 - 跑一次评估:用
adk eval跑你的 eval set,观察各标准的分数,理解每个标准的含义。 - 写自定义指标:写一个"检查响应是否包含订单号"的自定义指标,接进 EvalConfig。
- 环境模拟实战:给云销客服的
get_order_status注入 503 错误,看 agent 如何应对服务故障。
下一章预告:第 11 章,评估保证"质量",安全保证"不出事"。提示词注入、数据泄露、越权操作——Agent 的安全威胁比传统系统更复杂。