基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
AIHT
内容
精选全部 AI 动态热点榜AI 日报收藏
分类
模型行业编程产品技术观点开源
更多
Agent 接入
返回首页开发者导航
AIHT
精选 / 资讯详情
8.0
热点
AI SCORE
技术实践2026-11-01 20:31

ADK 遥测升级后,分析查询为何悄悄失效

dev.to · AI#可观测性#Agent#数据迁移
Editor brief · 编辑速览

作者升级 Google ADK 并切换至遵循 OpenTelemetry GenAI 语义约定的遥测 v2 后,原有 BigQuery 查询因 span 名称变化返回零行。文章梳理迁移中遇到的六类陷阱,并说明 Cloud Trace 到 BigQuery 的查询链路。

文章思维导图
Knowledge map
拖拽缩放
AI deep read

AI 深度解读

Google ADK 从 2.7 升到 2.13,部署正常,Trace 也有数据,BigQuery 分析却全部查出零行。作者同时切换了 telemetry schema v2,旧 SQL 依赖的 span 名称和 JSON 属性已经变了,而查询引擎没有报错。

这次事故值得读的地方,是它把一次升级串出了几个常见盲区:配置还在但不再生效、JSON 路径写错却照常执行、测试替身污染其他文件,以及把指标缺失误判成业务没有发生。对依赖 AI 调用记录做排障、成本分析的团队,这些比升级命令更需要检查。

SQL 执行成功,为什么分析已经失效

作者的链路是 Cloud Run 上的 Agent 生成 OpenTelemetry spans,经 SpanProcessor 包装器处理后导出到 Cloud Trace,再通过 google_observability_link 关联 BigQuery。它没有经过 Logging sink。

查询入口 cloud_trace_spans._AllSpans 是 VIEW,没有复制数据,保留期跟随 Cloud Trace,默认只有 30 天。数据集位于 us,执行命令需要带 bq query --location=us,否则会提示数据集不存在。关联配置还给 observability service agent 授予了 roles/bigquery.admin。

真正导致静默故障的是属性存储方式:所有 span attributes 都在一个叫 attributes 的 JSON 列里。普通列名写错,BigQuery 会报错;JSON_VALUE 指向不存在的键,只返回 NULL。

开启 ADK_TELEMETRY_SCHEMA_VERSION_OPT_IN=2 后,schema v2 转向 OpenTelemetry GenAI semantic conventions,原来塞在大 JSON 中的内容按含义拆分,名称也改变了。旧查询的 WHERE name = 'call_llm' 因此不再命中新数据。

迁移期间,要先让查询覆盖两种 LLM span:

WHERE name = 'call_llm' OR STARTS_WITH(name, 'generate_content ')

关键在 STARTS_WITH:v2 的名称还带有后续内容,不能只拿固定名称做等值匹配。

两种记录的内部结构也不同。v1 请求使用 contents[last].parts[].text,v2 使用消息数组里的 parts[].content;助手角色从 model 变成 assistant。

作者提醒,COALESCE 只处理当前行的空值,不能自动解决不同 span 行、不同 JSON 结构的迁移。它可以选出当前行已有的属性,但还得配合 span 筛选和分支解析。工具调用则不同:execute_tool <name> 保持不变,参数字段可以在同一行用 COALESCE 兼容 gcp.vertex.agent.tool_call_args 和 gen_ai.tool.call.arguments。

归属关系也不能随手按 trace_id 关联。一次 trace 里可能同时有 router 和 AgentTool 子 Agent,容易把调用算到另一个 Agent 头上。作者优先读取 gen_ai.agent.name,缺失时沿 parent_span_id 查父节点、祖父节点。其采样中的 v2 generate_content 已直接携带名称,向上遍历主要兼容 v1。

内容采集和大小限制,要分别验证

schema 切换成功,不代表正文采集也成功。v1 的 ADK_CAPTURE_MESSAGE_CONTENT_IN_SPANS=true 对 v2 不再适用,需要配置 OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT。

作者的纯 Trace 链路选择 SPAN_ONLY。看起来更全面的 SPAN_AND_EVENT 涉及事件导出,而 ADK 只有存在 log record processors 时才创建 LoggerProvider。单独调用 get_gcp_exporters(enable_cloud_tracing=True),并没有提供这条事件通道。

内容采集恢复后,还有大小问题。作者遇到的 GCP Telemetry API 限制是单个属性不能超过 64 KiB,超限会让整个批次返回 HTTP 400,而非只丢弃那个属性。v2 拆开了字段,但 gen_ai.system_instructions 和 gen_ai.tool.definitions 仍可能很大。

OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT 也不能直接解决:它按字符截断,无法保证 UTF-8 字节数,还可能把 JSON 截成无法解析的半段。

作者用 SpanAttributeLimitSpanProcessor 包装现有 processors,再通过 OTelHooks 和 maybe_set_otel_providers 接回 ADK,以保留原生批处理。裁剪优先级是 grounding_metadata、logprobs_result 等附带信息,然后是工具定义、系统指令、最早的对话,并加上 __adk_span_truncated__ 标记。

这套实现有实测,也有明显缺陷。一次 generate_content gemini-3.8-flash 的系统指令原始大小为 63,427 bytes,处理后剩 27,826 字符,保留尾部上下文,作者称此后没有再遇到 400。但在 30 parts × 2.1 KB、总计 63,520 bytes 的复现中,仅超过其处理预算约 3.5 KB,就退化到只剩 47 bytes 的标记。

这里不能混用字节和字符;原文也没有交代内部预算为何低于 64 KiB。它证明结构化裁剪有必要,同时证明当前算法还不够稳。

升级复现失败,也可能与生产代码无关

另外两处问题出在依赖和测试环境。

uv.lock 包含 exclude-newer = "0001-01-01T00:00:00Z" 和 exclude-newer-span = "P1W",限制使用发布时间过新的包。ADK 2.13.0 比截止时间晚约五个半小时,于是安装器提示只能找到 google-adk<=2.12.0。

作者本机的 ~/.config/uv/uv.toml 还有 exclude-newer = "1 week",按其描述覆盖了 PR 的锁文件行为,导致切到分支运行 uv sync 仍安装 2.7.0。排查时还需要清除旧的 VIRTUAL_ENV,避免 uv pip list 查看错误环境。原文用 UV_EXCLUDE_NEWER=2026-12-31T00:00:00Z uv sync 调整截止时间,这属于诊断操作,不宜直接变成团队默认配置。

CI 失败则来自测试脚手架。某测试文件在导入时把伪造的 google.genai、google.genai.types 写进 sys.modules,又调用 _build_stub_modules(),没有清理。后续测试拿到的是替身模块,导入顺序因此影响结果。另一处测试仍读取 contents.types,这个成员在 ADK 2.13 已不存在。

排除有问题的测试文件后,其余 1,243 个测试通过。作者提炼出的线索很实用:python -c 导入正常、pytest 收集阶段失败时,应检查其他测试是否污染了进程级模块缓存。

全是 NULL,不能证明缓存从未工作

作者看到 ADK 2.13 提示 router 没有 context_cache_config,便查询缓存 token,结果全为 NULL。他起初据此认定缓存从未生效,后来发现自己查错了键。

错误路径是 gen_ai.usage.cached_input_tokens,正确路径是 gen_ai.usage.cache_read.input_tokens;缓存创建量对应 gen_ai.usage.cache_creation.input_tokens。改正后发现,Vertex AI 的隐式缓存从第三次调用就已经命中。

在解释指标前,可以先确认路径是否曾经读到值:

SELECT COUNTIF(JSON_VALUE(attributes, '$."gen_ai.usage.cache_read.input_tokens"') IS NOT NULL) AS has_attr,
       COUNT(*) AS n
FROM spans WHERE name LIKE 'generate_content%'

先看 has_attr。如果它是 0,应先核对属性名、span 类型和采集逻辑。

ADK 只在值不是 None 时写入该 usage 属性。严格说,NULL 也不能直接等同于数值零:显式写入的 0 可以被读到,字段缺失才会返回 NULL。

补上显式缓存后,作者观察到命中率从 2/4 变成 4/5。准确结论是已有缓存变得更稳定,不能说缓存从此才开始工作。门槛也要结合模型判断:原文引用的 ContextCacheConfig 文档规定,Gemini 2.5 需要 2,048 tokens,Gemini 3 需要 4,096 tokens;router 的 2,875–3,010 tokens 达不到后者。这个长度不能脱离模型单独判断。

我的判断

这次升级最有价值的教训,是把遥测数据也当成需要验收的接口。

作者对 JSON 静默失配、父子 span 归属和测试污染的判断,都有明确故障过程支持。但这里没有完整的 schema 映射表,读者不能把几段兼容 SQL 当成迁移手册。gen_ai.tool.definitions 曾被作者误写成 gen_ai.tool_definitions,也说明属性名应核对 semconv 常量,而非凭记忆抄写。

裁剪方案比普通字符串截断更适合保留可查询的 JSON,不过它的多片段退化已经被作者复现,不能直接视作成熟通用组件。缓存命中率只有 2/4、4/5 两组小样本,没有统一负载、成本和延迟对照,只能支持这次观察。

同样,1,243 个测试通过有助于定位故障范围,却不能代替遥测查询验证。应用运行、SDK 导出、分析 SQL,分别需要证据。

能拿来做什么

  • 升级 AI SDK 或遥测 schema 前,保存同一次实际调用的升级前后 spans,对照名称、JSON 路径、角色和父子关系,再修改分析 SQL。
  • 给关键查询增加属性存在率检查;发现零行或全 NULL 时,先检查采集配置和字段路径,再判断调用量、缓存量是否真的为零。
  • 在 Agent 服务导出遥测前按 UTF-8 字节检查体积,保留结构化裁剪标记,并用多段中等长度内容验证裁剪行为。
  • 遇到本地与 CI 结果不同时,同时检查 uv 配置来源、虚拟环境和测试替身清理;只在 pytest 中出现的导入故障,要追查 sys.modules 的修改。
Original source

本文由 AI 通读原文后重新编写,观点部分为站内判断,细节以原文为准,来源 dev.to · AI,原文版权归原作者所有。

阅读原文
上一篇
本地语音模型实测:速度指标必须结合运行环境
下一篇
AI 生成的三维点云,如何变成可打印模型

资讯速览

dev.to · AI · 2026-11-01 20:31

AI 评分 8.0 / 10

相关标签

#可观测性#Agent#数据迁移

继续阅读

  • 跨模块修复先做架构规划是否更有效
  • 本地语音模型实测:速度指标必须结合运行环境
  • AI 生成的三维点云,如何变成可打印模型
  • 把 AI 审查接入 PR:难点在工程细节
  • 给AI生成的代码变更补上审查说明