deepeval 的 Google ADK 追踪 Schema 快照:基于真实 Trace 的集成测试与再生成工作流
【免费下载链接】deepevalThe LLM Evaluation Framework项目地址: https://gitcode.com/GitHub_Trending/de/deepeval
本篇指南围绕 schemas/README.md 展开,讲解 deepeval 如何为 Google ADK(Agent Development Kit)集成维护一套「活捕获」的 Trace JSON 结构快照,并借助这些快照对 Gemini 真实执行产生的 OTel span 做结构化断言。读完你将掌握:schema 快照与测试方法的一一映射、
GENERATE_SCHEMAS=true的再生成命令、何时应当批量再生成、提交前的三项自检,以及 OpenInference 属性到confident.*属性的底层翻译机制。
一、什么是 Google ADK Trace Schema 快照
在 deepeval 的 Google ADK 集成测试中,tests/test_integrations/test_googleadk/schemas/ 目录存放着一批*_schema.json文件。它们不是手工编写的"预期值",而是真实运行 ADK Agent 后捕获下来的 Trace JSON 快照,作为每个测试方法的结构化固件(structural fixture)。
其工作方式如下:
- 测试通过 ADK 的
InMemoryRunner驱动真实 Agent(底层调用 Gemini)产生 Trace; - 测试装饰器将实时 Trace 与对应的 schema 文件做对比;
- 对比使用的是 tests/test_integrations/utils.py 中定义的宽松结构匹配器(relaxed structural matcher),即
assert_trace_json——它容忍顺序差异、容忍 LangChain v1.x 风格的usage_metadata/response_metadata漂移,只对结构形状做断言。
也就是说,schema 文件锁定的不是某个字符串,而是 Trace 的结构形状:有哪些 agent span、LLM span、tool span,各自的父子关系、类型、关键属性是否齐备。
一个直观的实例是 googleadk_simple_schema.json,它记录了无工具问候 Agent 的完整 Trace:顶层agentSpans包含invocation [deepeval-googleadk-simple]与simple_assistant两个 agent span,llmSpans包含call_llm,trace 级字段(name、metadata、tags、environment、threadId、userId、status等)也一并固化其中。
二、Schema 覆盖矩阵:十个快照与它们的测试
原文档以一张表格列出了目录中全部 schema 与测试方法的对应关系,这里完整保留并补充实现细节:
| Schema 文件 | 来源测试 | 覆盖场景 |
|---|---|---|
googleadk_simple_schema.json | test_sync.py::TestSimpleApp::test_simple_greeting | 问候 Agent,只有 agent + LLM span,无工具 |
googleadk_tool_schema.json | test_sync.py::TestToolApp::test_tool_calculation | 单次 calculator 工具调用 |
googleadk_tool_metric_collection_schema.json | test_sync.py::TestToolApp::test_tool_metric_collection | 与tool形状相同,但通过next_tool_span(metric_collection=...)在 tool span 上写入confident.span.metric_collection |
googleadk_multiple_tools_weather_schema.json | test_sync.py::TestMultipleToolsApp::test_multiple_tools_weather_only | 多工具 Agent 中只调用get_weather |
googleadk_multiple_tools_time_schema.json | test_sync.py::TestMultipleToolsApp::test_multiple_tools_time_only | 同一多工具 Agent 中只调用get_time |
googleadk_parallel_tools_schema.json | test_sync.py::TestMultipleToolsApp::test_parallel_tool_calls | 同一城市同时调用get_weather+get_time,span / 工具调用顺序由匹配器按无序处理 |
googleadk_features_sync.json | test_sync.py::TestDeepEvalFeatures::test_full_features_sync | POC 迁移后的全功能叠加:trace 级metric_collection覆盖、next_agent_span(metrics=[...])、next_llm_span(metric_collection=...),以及special_tool内部调用update_current_span(metric_collection=...) |
googleadk_async_simple_schema.json | test_async.py::TestAsyncSimpleApp::test_async_simple_greeting | 通过runner.run_async(...)的异步路径 |
googleadk_async_tool_schema.json | test_async.py::TestAsyncToolApp::test_async_tool_calculation | 异步工具调用 |
googleadk_async_parallel_tools_schema.json | test_async.py::TestAsyncMultipleToolsApp::test_async_parallel_tool_calls | 异步并行工具调用 |
googleadk_features_async.json | test_async.py::TestDeepEvalFeaturesAsync::test_full_features_async | 上述全功能场景的异步等价版本 |
注:原文档表格列出 10 个条目,目录中实际存在 11 个文件——
googleadk_features_sync.json与googleadk_features_async.json属于同一"全功能"主题的同步/异步双版本。
2.1 同步与异步两条测试线
- 同步线test_sync.py:按
TestSimpleApp→TestToolApp→TestMultipleToolsApp→TestDeepEvalFeatures组织,每个测试方法都通过@trace_test("...json")装饰,内部调用init_*_googleadk(...)完成 trace 级配置(name、tags、metadata、thread_id、user_id)后驱动 Agent 执行。例如test_tool_calculation断言返回结果必须包含"56"(7×8 的计算结果)。 - 异步线test_async.py:类布局与同步线一一对应,测试方法额外带
@pytest.mark.asyncio,通过ainvoke_*_agent走runner.run_async(...),专门验证 OpenInference instrumentor 的异步 span 发射路径。
两条线都在模块级设置了pytestmark = pytest.mark.skipif(not os.getenv("GOOGLE_API_KEY"), ...)——没有GOOGLE_API_KEY时整个模块会被跳过,因为底层 Gemini 调用无法通过认证。
2.2 全功能测试:span 级配置的调用点迁移
TestDeepEvalFeatures是理解"OTel POC 迁移"后新写法的样板(test_sync.py):
invoke_func = init_evals_googleadk( name="googleadk-full-features-sync", tags=["googleadk", "features", "sync"], metadata={"env": "testing", "priority": "high"}, thread_id="thread-sync-features-001", user_id="user-sync-001", metric_collection="trace_metrics_override_v1", # trace 级字段仍留在 init 边界 ) with next_agent_span( metric_collection="agent_metrics_v1", metrics=[AnswerRelevancyMetric()], ), next_llm_span(metric_collection="llm_metrics_v1"): result = invoke_evals_agent( "Use the special_tool to process 'Sync Data'", invoke_func=invoke_func, )关键点在于:init_evals_googleadk(...)只接受 trace 级参数;agent / LLM / tool 的 metric collection 与BaseMetric实例全部下沉到调用点,用with next_*_span(...)堆叠包裹(apps/googleadk_eval_app.py 的 docstring 对此有明确说明)。而special_tool工具则在函数体内通过update_current_span(metric_collection="special_tool_v1")为自己所在 tool span 写入属性——这正是"从工具内部反向修改当前 span"的端到端验证。
2.3 组件级评估测试(不产 schema,但验证 metric 传输路径)
test_evaluate_agent.py 不走 schema 断言,而是通过dataset.evals_iterator驱动一个 golden 穿过 Google ADK Agent,再由AnswerRelevancyMetric打分:
dataset = EvaluationDataset(goldens=[Golden(input="What's 7 multiplied by 8?")]) async def run_agent(prompt: str): with next_agent_span(metrics=[answer_relevancy_metric]): return await ainvoke_evals_agent(prompt, invoke_func=invoke_func) for golden in dataset.evals_iterator( async_config=AsyncConfig(run_async=True), metrics=[answer_relevancy_metric], ): task = asyncio.create_task(run_agent(golden.input)) dataset.evaluate(task) assert answer_relevancy_metric.score is not None assert answer_relevancy_metric.score > 0.0该测试需要GOOGLE_API_KEY(Gemini 调用)与OPENAI_API_KEY(指标评分器)两个密钥。它的核心价值在于验证了evals_iterator设置trace_manager.is_evaluating=True后触发的两条链路:ContextAwareSpanProcessor切换到 REST 路由(span 经trace_manager流转而非 OTLP),以及stash_pending_metrics被门控放行,使BaseMetric实例能随 OTel 传输到达ConfidentSpanExporter并重新挂到重建后的 AgentSpan 上。
三、再生成工作流:让快照跟上真实执行
schema 文件是**活捕获(LIVE-CAPTURED)**的,原文档明确要求"永远不要手工编辑"(never hand-edit)。需要更新时,重新运行测试并让框架自动覆写:
GOOGLE_API_KEY=... GENERATE_SCHEMAS=true \ poetry run pytest tests/test_integrations/test_googleadk/test_sync.py \ tests/test_integrations/test_googleadk/test_async.pyGENERATE_SCHEMAS=true环境变量会把trace_test(...)从assert_trace_json切换到generate_trace_json——前者断言,后者把捕获的 trace dict 直接写入 schema 路径。无论哪种模式,每个测试仍然端到端地跑完 Gemini,因此 schema 反映的是一次真实 ADK 执行,而不是理想化的手工模板。
3.1 分发逻辑:conftest 中的 trace_test 装饰器工厂
再生成与断言的分发由 tests/test_integrations/test_googleadk/conftest.py 统一封装:
def trace_test(schema_name: str): """Resolve to ``generate_trace_json`` or ``assert_trace_json``.""" schema_path = os.path.join(_schemas_dir, schema_name) if is_generate_mode(): return generate_trace_json(schema_path) else: return assert_trace_json(schema_path)这个装饰器工厂把 schema 路径解析、模式判断集中在一处,四个测试模块(test_sync.py、test_async.py、test_span_interceptor.py、test_evaluate_agent.py)无需各自重复这五行逻辑。而is_generate_mode()、generate_trace_json、assert_trace_json三个工具函数都定义在 tests/test_integrations/utils.py 中,为所有 OpenInference 系集成(AgentCore、Strands 等)共用。
generate_trace_json的实现(utils.py)会设置trace_testing_manager.test_name = json_path,执行测试函数后通过trace_testing_manager.wait_for_test_dict()等待 trace dict 就绪,再json.dump(actual_dict, f, indent=2)落盘,最后在finally中清理测试状态。同步与异步两个 wrapper 共用同一套逻辑。
3.2 evals iterator 测试的单独再生成
对于 evals iterator 测试,需要单独执行(它不写 schema,但跑通它能确认 metric stash 路径):
GOOGLE_API_KEY=... OPENAI_API_KEY=... \ poetry run pytest tests/test_integrations/test_googleadk/test_evaluate_agent.py四、何时需要再生成:触发条件与漂移信号
原文档给出了三类明确的再生成触发条件:
- OpenInference Google ADK instrumentor 的属性命名空间发生变化(例如 semconv-genai 迁移):此时所有
*_schema.json会同节奏漂移,应当对整个目录批量再生成; OpenInferenceSpanInterceptor的_serialize_framework_attrs新增 / 重命名了confident.*属性:同样需要整体再生成;- Google ADK 增加新的事件类型或 span 形状(例如在
LlmAgent外再包一层chain):需要再生成以固化新形状。
需要特别强调的是原文档的排查建议:如果只有单个测试漂移而其他测试正常,几乎总是应该先调查测试本身,而不是盲目再生成。schema 漂移本身就是一种早期预警——它说明 trace 形状以匹配器无法吸收的方式发生了变化。匹配器已经容忍了 LangChain v1.x 风格的usage_metadata/response_metadata漂移、span / 工具调用列表的无序,如果漂移发生在这些容忍范围之外,那通常是上游发生了真实的结构变更。
五、提交前自检:三条必须扫描的 Diff 信号
再生成之后、提交之前,原文档要求逐一扫描 diff 中的三类问题:
5.1 空 Trace:{}即路由故障
一个变成{}(或近乎为空)的*_schema.json意味着trace_testing_manager.wait_for_test_dict()超时了——span 很可能被路由到了 OTLP 而非 REST。需要重新确认:
- 测试是否运行在
@observe/evals_iterator上下文之外; - 集成的
ContextAwareSpanProcessor是否正确挂载。
assert_trace_json内置了针对此问题的守卫_assert_trace_capture_succeeded(utils.py):一旦actual_dict == {},无论期望内容是什么都直接抛AssertionError,并提示最可能的成因与再生成命令。它的设计动机是:空期望文件 + 空实际 trace 会让结构对比"平凡通过",从而给出虚假的安全感。
5.2 缺失confident.span.tools_called
工具调用丢失,可能来自两个方向:OpenInference instrumentor 不再在 LLM output messages 上发射工具调用,或_extract_tool_calls与 OpenInference 的消息形状发生漂移。
从 deepeval/integrations/openinference/instrumentator.py 的_serialize_framework_attrs可以看到tools_called的两条提取路径:
- 工具 span 自身(
span_type == TOOL):从tool.name/tool.parameters构造单元素tools_called列表,同时把参数 JSON 写入confident.span.input; - agent / LLM span:从 LLM output messages 中嵌套的
llm.output_messages.{idx}.message.tool_calls.{tc}.tool_call.function.{name,arguments}逐层遍历提取(对应_extract_tool_calls)。
提取结果以[t.model_dump_json() for t in tools_called]形式写入 OTel 属性,供 exporter 反序列化重建ToolCall。
5.3type与spanType翻转
deepeval 序列化器对 span 类型的关键字是已知的兼容性门槛。匹配器虽然容忍小范围翻转,但整体性翻转意味着上游版本升级导致了大范围属性变更——此时需要按第四节的条件触发全量再生成。
span 类型的分类依据来自 OpenInference 的openinference.span.kind(大写形式),test_span_interceptor.py 中的合成 span 单测给出了完整的映射规则:
AGENT/CHAIN→agent(CHAIN 对 deepeval 而言也表现为 AgentSpan);LLM→llm;TOOL→tool;RETRIEVER→retriever;- 其他未知值 →
custom; - 缺失
openinference.span.kind→ 不写入confident.span.type(避免重建出畸形 span)。
六、底层机制:OpenInference 属性到 confident 属性的翻译
理解 schema 里字段的来源,需要知道OpenInferenceSpanInterceptor(instrumentator.py)在 span 生命周期on_start/on_end做了什么。它是所有 OpenInference 系集成共享的 span 处理器,Google ADK 是 deepeval 侧的第一个使用者,因此 test_span_interceptor.py 成为合成 span 覆盖的规范样例(无需安装google-adk与openinference-instrumentation-google-adk,直接用MagicMock构造 OTel span 驱动)。
核心的框架属性翻译(_serialize_framework_attrs,instrumentator.py)遵循setdefault语义——占位符序列化器先运行,用户通过update_current_span(...)的写入优先于框架写入。映射关系如下:
| OpenInference 属性 | confident 属性 | 说明 |
|---|---|---|
openinference.span.kind | confident.span.type | 分类为 agent / llm / tool / retriever / custom |
llm.input_messages.{idx}.message.content | confident.span.input | 遍历下标直到空洞,取最后一个 content |
llm.output_messages.{idx}.message.content | confident.span.output | 同上 |
llm.output_messages.{idx}.message.tool_calls.{tc}.tool_call.function.{name,arguments} | confident.span.tools_called | JSON 解析 arguments 后产出ToolCall列表 |
tool.name/tool.parameters | confident.span.tools_called(单元素)+confident.span.input | 工具 span 场景 |
llm.token_count.prompt/llm.token_count.completion | confident.llm.input_token_count/confident.llm.output_token_count | 转 int 后写入 |
llm.model_name | confident.llm.model | 并用于推断 provider |
agent span 的input.value/output.value | confident.trace.input/confident.trace.output | 根 span 的 I/O 同步上抛到 trace 级,便于 trace 卡片直接展示 |
此外,拦截器还承担了 trace 级读取、span 占位符压栈/弹栈(保证工具体内update_current_span(...)能在on_end时序列化回confident.span.*)、隐式 Trace 占位符(无外层@observe/with trace(...)的裸 ADK 调用者)、以及confident.span.parent_uuid父桥接(OTel 根 span 嵌套在真实 deepeval span 内时,为其打上父 uuid 以便 exporter 重新挂接)。
七、迁移约束:被移除的 span 级 kwargs
OTel POC 迁移的一个硬性约束是:span 级 kwargs 已从instrument_google_adk(...)与OpenInferenceInstrumentationSettings构造器中移除,传入即抛TypeError且错误信息会点名违规参数(google_adk/otel.py、instrumentator.py)。被移除的完整清单:
is_test_mode、agent_metric_collection、llm_metric_collection、tool_metric_collection_map、trace_metric_collection、agent_metrics、confident_prompt
对应的替代方案是:trace 级字段继续留在instrument_google_adk(...),span 级字段一律改到调用点,用with next_agent_span(...)/next_llm_span(...)/next_tool_span(...)包裹,或从工具体内部用update_current_span(...)写入。test_span_interceptor.py 对每个被移除的 kwarg 在两个入口(settings 构造器与instrument_google_adk)都做了参数化单测,且 kwarg 检查发生在GoogleADKInstrumentorimport 之前,因此即使未安装 instrumentor 依赖,这些守卫测试也能运行。
另外需要注意:OpenInferenceInstrumentationSettings的api_key是可选的。未提供且环境变量缺失时,构造器不报错,OTel 管道照常本地接线,只有出站认证头会因缺少 key 而被门控(由ContextAwareSpanProcessor处理,而非构造器)。这在 test_span_interceptor.py 中有对应单测。
八、关键源码路径速查
- 本指南对应的维护文档:schemas/README.md
- 分发装饰器(断言/再生成切换):conftest.py
- 断言与生成工具函数(含空 trace 守卫):tests/test_integrations/utils.py
- 同步 / 异步端到端测试:test_sync.py、test_async.py
- 组件级评估(metric stash 路径):test_evaluate_agent.py
- 合成 span 单测(框架属性翻译的规范覆盖):test_span_interceptor.py
- 测试用 ADK Agent 固件:apps/(simple / tool / multiple-tools / eval 四个应用)
- 集成入口:deepeval/integrations/google_adk/otel.py
- span 拦截器与设置类:deepeval/integrations/openinference/instrumentator.py
九、小结
Google ADK Trace Schema 快照是 deepeval 集成测试中"以真实执行锁定结构"的实践:schema 文件即测试固件,GENERATE_SCHEMAS=true让再生成变成一条可复现的命令,而_assert_trace_capture_succeeded守卫与三条提交前自检把最常见的三类故障(空 trace、工具调用丢失、span 类型翻转)变成显式信号。理解这套工作流,既能帮你维护集成测试基线,也能在 trace 形状漂移时快速定位是"测试问题"还是"上游变更"。
【免费下载链接】deepevalThe LLM Evaluation Framework项目地址: https://gitcode.com/GitHub_Trending/de/deepeval
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考