adk-python 评估数据文件解析:.test.json与.evalset.json的同源异用
【免费下载链接】adk-pythonAn open-source, code-first Python toolkit for building, evaluating, and deploying sophisticated AI agents with flexibility and control.项目地址: https://gitcode.com/GitHub_Trending/ad/adk-python
导读
在 Google ADK(Agent Development Kit)的 Python 实现(adk-python)中,.test.json与.evalset.json其实承载的是同一套EvalSetPydantic 数据模型,二者由同一个加载函数按 Schema 校验、由同一条adk eval命令驱动。本文以仓库中 test_file_vs_evalset 示例为骨架,结合src/google/adk/evaluation下的源码实现,讲清两种文件后缀的命名约定、文件结构、命令行运行方式、评估指标配置(IN_ORDER轨迹匹配与 ROUGE-1 文本匹配),以及如何让同一份数据同时被pytest复用。读完本文,你将能够为任意 Agent 编写、配置并运行单元级与集成级的离线评估。
同一个 EvalSet:扩展名只是命名约定
一切始于EvalSetSchema
无论是.test.json还是.evalset.json,文件内容都会被解析为同一个EvalSet模型。该模型定义在 src/google/adk/evaluation/eval_set.py,核心字段包括:
| 字段 | 类型 | 含义 |
|---|---|---|
eval_set_id | str(必填) | 评估集的唯一标识 |
name | Optional[str] | 数据集名称 |
description | Optional[str] | 数据集描述 |
eval_cases | list[EvalCase] | 评估用例列表,每个用例代表一次待评估的交互 |
creation_timestamp | float(默认0.0) | 数据集创建时间 |
EvalCase模型位于 src/google/adk/evaluation/eval_case.py,每个用例包含eval_id、conversation(静态多轮对话)或conversation_scenario(交给 UserSimulator 动态生成,二者二选一)、session_input(会话初始化输入,如app_name、user_id、初始state)等字段。
conversation中的每一轮是一个Invocation(见 eval_case.py),由以下部分组成:
invocation_id:本轮调用的唯一标识;user_content:用户输入内容;final_response:期望的 Agent 最终回复(ground truth);intermediate_data:期望的中间过程数据,其中tool_uses记录了按时间顺序排列的期望工具调用轨迹(工具名 + 参数)。
load_eval_set_from_file:按 Schema 校验,而非按扩展名
adk eval之所以能用同一条命令加载两种后缀的文件,关键在于加载函数不关心文件扩展名。在 src/google/adk/evaluation/local_eval_sets_manager.py 中:
def load_eval_set_from_file(eval_set_file_path: str, eval_set_id: str) -> EvalSet: with open(eval_set_file_path, "r", encoding="utf-8") as f: content = f.read() try: return EvalSet.model_validate_json(content) except ValidationError: # 若新 Schema 校验失败,则假设数据为旧格式并尝试转换 return convert_eval_set_to_pydantic_schema(eval_set_id, json.loads(content))函数首先用EvalSet.model_validate_json直接按 Pydantic Schema 校验 JSON;只有当新格式校验失败时,才会回退到旧版 eval 数据格式的转换逻辑(convert_eval_set_to_pydantic_schema,同样位于该文件中,负责把query/reference/expected_tool_use等旧字段映射到新的Invocation结构)。因此,扩展名对加载结果没有任何影响——foo.test.json、foo.evalset.json甚至foo.json都能被正常加载,只要内容满足 Schema。
两种后缀的定位:单元测试 vs 集成测试
既然 Schema 相同,两种扩展名便只是一种命名约定,用于表达数据的规模与意图:
.test.json—— 单元测试约定:包含一个简单、聚焦的 session,保持小而精,对应单个单元测试;.evalset.json—— 集成测试约定:将多个更长、更多轮次的 session 组织在一起,对应集成测试。
这种约定的价值在于团队协作与 CI 语义:扫描代码库时,.test.json可以让开发者一眼识别出轻量级回归用例,.evalset.json则标识出覆盖完整业务链路的场景集。
样例资产剖析:一个 Agent,两份评估数据
本示例针对共享的智能家居 Agent(home_automation_agent)各提供一份数据文件。该 Agent 由内存字典模拟设备状态,所有工具(get_device_info、set_device_info、get_temperature、set_temperature、list_devices)都是确定性的,保证评估轨迹可复现;模块内的reset_data()会被adk eval在每个 eval case 之间调用,重置状态,避免用例间相互污染。
single_turn.test.json:单轮单元测试
文件位于 contributing/samples/evaluation/test_file_vs_evalset/single_turn.test.json,仅包含 1 个 session、1 轮对话:
- 用户提问:
What's the temperature in the Kitchen? - 期望最终回复:
The temperature in the Kitchen is 24 degrees Celsius. - 期望工具调用:
get_temperature,参数{"location": "Kitchen"}
对应eval_id为kitchen_temperature,session_input中app_name为home_automation_agent、user_id为user、state为空。这就是典型的"一个断言点"的单元级用例:验证 Agent 收到特定问题后会调用正确的工具并给出正确的回复。
multi_session.evalset.json:多会话集成测试
文件位于 contributing/samples/evaluation/test_file_vs_evalset/multi_session.evalset.json,包含 2 个 session:
list_then_turn_off(两轮):- 第 1 轮:
Which devices are on?→ 期望调用list_devices,参数{"status": "ON"},期望回复列出开启的设备; - 第 2 轮:
Turn that one off.→ 期望调用set_device_info,参数{"device_id": "device_1", "status": "OFF"},期望回复确认已关闭。
- 第 1 轮:
set_bedroom_temperature(单轮):Set the Bedroom to 21 degrees.→ 期望调用set_temperature,参数{"location": "Bedroom", "temperature": 21}。
这个文件示范了集成测试的两个关键特征:多会话(两个独立场景)与多轮上下文依赖(第二轮"Turn that one off."依赖第一轮返回的设备信息,验证 Agent 的上下文记忆与指代消解能力)。
运行评估:同一条adk eval命令
命令与参数
两种文件使用完全相同的adk eval命令,唯一变化的是评估数据路径。在仓库根目录下执行:
运行.test.json:
adk eval contributing/samples/evaluation/home_automation_agent \ contributing/samples/evaluation/test_file_vs_evalset/single_turn.test.json \ --config_file_path contributing/samples/evaluation/test_file_vs_evalset/eval_config.json \ --print_detailed_results运行.evalset.json:
adk eval contributing/samples/evaluation/home_automation_agent \ contributing/samples/evaluation/test_file_vs_evalset/multi_session.evalset.json \ --config_file_path contributing/samples/evaluation/test_file_vs_evalset/eval_config.json \ --print_detailed_results两个位置参数分别为:
agent_module_file_path:Agent 模块所在目录(必须是包含agent.py的目录,且模块中定义root_agent或get_agent_async);eval_set_file_path_or_id:可接受一个或多个评估数据文件路径,或已注册的 eval set id(文件路径与 id 不可混用)。
--print_detailed_results会在控制台打印Actual-vs-Expected 对照表,逐轮对比 Agent 真实的工具调用与回复和文件中的期望值。从 src/google/adk/cli/cli_eval.py 的pretty_print_eval_result实现可以看到,该表格以 DataFrame 形式输出,每行包含prompt、expected_response、actual_response、expected_tool_calls、actual_tool_calls,并为每个指标附加Status与Score列。
命令的底层调用链
从 src/google/adk/cli/cli_tools_click.py 可以看到cli_eval的完整流程:
- 解析参数后,
parse_and_get_evals_to_run将位置参数解析为{文件路径或 eval set id: [要运行的 eval_id 列表]}的映射; - 若第一个参数是已存在的文件,则切换到
InMemoryEvalSetsManager,对每个文件调用load_eval_set_from_file加载为EvalSet,再逐个把eval_cases灌入内存管理器; - 通过
get_evaluation_criteria_or_default读取--config_file_path指定的评估配置(见下文); - 构建
InferenceRequest交给LocalEvalService执行推理与指标计算。
顺带一提,如果省略--config_file_path且只传入单个eval 文件,CLI 会自动回退到test_config.json作为配置(见 cli_tools_click.py 的_resolve_eval_config_file_path)——这与AgentEvaluator的约定保持一致。
只运行部分用例
如果只想运行某个文件中的特定 eval case,可以在文件路径后追加:和逗号分隔的 eval id,例如:
adk eval contributing/samples/evaluation/home_automation_agent \ contributing/samples/evaluation/test_file_vs_evalset/multi_session.evalset.json:list_then_turn_off \ --config_file_path contributing/samples/evaluation/test_file_vs_evalset/eval_config.json \ --print_detailed_results评估配置深入:eval_config.json
示例的评估配置位于 contributing/samples/evaluation/test_file_vs_evalset/eval_config.json:
{ "criteria": { "tool_trajectory_avg_score": {"threshold": 1.0, "match_type": "IN_ORDER"}, "response_match_score": 0.6 } }配置的加载与解析在 src/google/adk/evaluation/eval_config.py 中完成:若指定路径存在则以EvalConfig.model_validate_json解析;否则回退到内置默认配置——tool_trajectory_avg_score: 1.0、response_match_score: 0.8(见 eval_config.py)。也就是说,省略--config_file_path时评估仍可运行,只是文本匹配阈值会更严格(0.8)。
tool_trajectory_avg_score:工具轨迹匹配
该指标比较 Agent 实际工具调用轨迹与文件中期望的工具调用轨迹(工具名 + 参数)。MatchType枚举定义在 src/google/adk/evaluation/eval_metrics.py,共有三种取值:
EXACT(默认):要求实际工具调用与期望完全一致;IN_ORDER:期望的工具调用必须按给定顺序出现,允许中间穿插额外工具调用;ANY_ORDER:期望的工具调用只需全部出现,不关心顺序(适合"发起多次同类搜索"等场景)。
示例配置选择了IN_ORDER:期望调用必须按序出现,但实际执行中插入的额外调用会被容忍。例如期望[T1, T2, T3],实际为[T1, T1.1, T2, T2.1, T2.2, T3, T3.1]即满足条件;而一旦某个期望调用缺失(如期望 4 个只出现 3 个),则该指标失败。threshold设为1.0,意味着每条期望调用(名称 + 参数)都必须命中一次真实调用。
此外,ToolTrajectoryCriterion还提供ignore_args字段(默认False):设为True时只比较工具名、忽略参数(见 eval_metrics.py),适用于参数值本身不重要、只关心是否调用了正确工具的断言。
response_match_score:ROUGE-1 文本匹配
response_match_score使用ROUGE-1 unigram 重叠分数来比较 Agent 的真实回复与期望回复。之所以使用模糊匹配而非精确匹配,是为了容忍真实推理(live inference)带来的措辞变化——只要关键词重叠度足够,语义一致的回复即可通过。
实现位于 src/google/adk/evaluation/final_response_match_v1.py 的RougeEvaluator,判分规则为score >= threshold即PASSED(见 final_response_match_v1.py)。值得注意的是,该项目使用自定义的_UnicodeAwareTokenizer(见 final_response_match_v1.py):默认的rouge_score分词器会丢弃非[a-z0-9]字符,导致中文、泰文等非拉丁文字文本永远得 0 分;该分词器通过 NFKC 归一化、按字符切分 CJK、按字素簇聚合泰文等逻辑,使 ROUGE-1 在非拉丁语言上依然有效。
示例将response_match_score阈值设为0.6:期望回复与真实回复的 ROUGE-1 分数达到 0.6 即视为通过。
与pytest复用:AgentEvaluator.evaluate自动发现.test.json
除了adk evalCLI,同一份.test.json文件还可以直接由 Python 测试驱动。AgentEvaluator定义在 src/google/adk/evaluation/agent_evaluator.py,其静态方法evaluate有以下行为:
- 传入目录时,会递归扫描该目录下所有以
.test.json结尾的文件并逐一评估(见 agent_evaluator.py); - 传入单个文件时,直接评估该文件;
- 配置文件的自动查找遵循约定:在 test 文件同目录下寻找
test_config.json(find_config_for_test_file,见 agent_evaluator.py); - 评估结束时,若有失败会抛出断言(
assert not failures),天然适配 pytest 的失败语义。
因此,一个典型的 pytest 测试可以这样写(示例本身只用adk eval,此用法来自源码契约):
from google.adk.evaluation.agent_evaluator import AgentEvaluator def test_home_automation_agent(): AgentEvaluator.evaluate( agent_module="contributing/samples/evaluation/home_automation_agent", eval_dataset_file_path_or_dir="contributing/samples/evaluation/test_file_vs_evalset/single_turn.test.json", print_detailed_results=True, )这种设计意味着你只需维护一份评估数据,既能在 CI 中通过adk eval批量跑,也能嵌入 pytest 做针对性回归。
实践建议:如何选择两种约定
- 首选
.test.json:当验证目标是"给定输入,Agent 是否调用正确工具、给出正确回复"这样的单一、原子化行为时。文件保持小而聚焦,配合AgentEvaluator.evaluate的目录扫描能力,可以按目录组织成一组"单元测试套件"。 - 首选
.evalset.json:当验证目标是覆盖完整业务链路(如多轮指代消解、跨轮状态保持、多场景组合)时。把多个相关场景聚合到一个文件中,运行一次命令即可获得整体通过率。 - 评估配置始终独立:无论数据文件用哪种后缀,评估标准(轨迹匹配方式、文本匹配阈值)都由
eval_config.json(或默认配置)统一控制,做到"数据与断言分离"。
总结
.test.json与.evalset.json是 adk-python 评估体系中一对"形异而神同"的文件约定:底层共享同一套EvalSet/EvalCasePydantic Schema,由load_eval_set_from_file按 Schema(而非扩展名)加载,由同一条adk eval命令执行,并可被AgentEvaluator.evaluate无缝接入 pytest。区别仅在于数据规模与语义定位——前者是单元测试,后者是集成测试。理解这一约定,再配合IN_ORDER/ANY_ORDER轨迹匹配与 ROUGE-1 文本匹配的灵活配置,你就能为 Agent 构建一套既能快速回归、又能深度验证的离线评估体系。
【免费下载链接】adk-pythonAn open-source, code-first Python toolkit for building, evaluating, and deploying sophisticated AI agents with flexibility and control.项目地址: https://gitcode.com/GitHub_Trending/ad/adk-python
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考