第 10 章 Google ADK评估Evaluation

第 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 的评估分两大部分:

  1. 评估轨迹与工具使用(Trajectory and Tool Use):分析 Agent 达到解决方案的步骤——工具选择、策略、路径效率。把实际轨迹与期望轨迹对比。
  2. 评估最终响应(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.json

adk 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_scoreresponse_match_score)——因为没有"标准答案"。改用 hallucinations_v1safety_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_idget_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 的质量从此可回归——改动会不会让质量下降,机器说了算,不再靠感觉。

主线项目:给云销客服建评估集

目标

给云销客服建一套评估集,覆盖三类场景:

  1. 正常订单查询(期望工具轨迹:get_order_status
  2. 退款政策咨询(期望工具轨迹:get_refund_policy
  3. 边界刁难问题(如"我东西呢?"这种模糊问题,评估意图理解)

评估配置

{
  "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 的评估设计有几个深思熟虑的点:

  1. 轨迹 + 响应双评估:不只关心"结果对不对",还关心"路径对不对"。因为工具调用的副作用(真的发了消息、真的扣了钱)比文本结果更重要。

  2. 多层级标准:从快速确定性的 tool_trajectory(适合 CI),到需要 LLM 裁判的 rubrichallucinations(适合质量评估),按场景选尺子。

  3. 模拟一切:用户可以模拟(用户模拟)、环境可以模拟(环境模拟)——把不可控的东西变得可控,这是 Agent 测试的核心难题。

  4. 接入 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:评估成为质量门禁,改动可回归

练习

  1. 建一个测试文件:给云销客服写一个 .test.json,验证"查订单"的正确工具轨迹和响应。
  2. 跑一次评估:用 adk eval 跑你的 eval set,观察各标准的分数,理解每个标准的含义。
  3. 写自定义指标:写一个"检查响应是否包含订单号"的自定义指标,接进 EvalConfig。
  4. 环境模拟实战:给云销客服的 get_order_status 注入 503 错误,看 agent 如何应对服务故障。

下一章预告:第 11 章,评估保证"质量",安全保证"不出事"。提示词注入、数据泄露、越权操作——Agent 的安全威胁比传统系统更复杂。