feat(core): Align GenAI content telemetry fields - #7667
Conversation
Capture provider-final GenAI messages and tool payloads with the shared ARMS/OpenTelemetry field contract, and retire equivalent private content aliases. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
GenAI content telemetry E2EScopeValidate the P2 standard content attributes across an OpenAI-compatible
Also verify that sensitive attributes are absent when capture is disabled and Baseline
Local verification
Result on 2026-07-24: passed with the local bundle. The permanent integration External verification
|
doudouOUC
left a comment
There was a problem hiding this comment.
Review — GenAI content telemetry alignment
Reviewed the full diff at 496a37f, read gen-ai-content.ts / gen-ai-request.ts end to end, and ran the touched suites locally against the PR branch:
gen-ai-content, gen-ai-exchange, gen-ai-request, detailed-span-attributes,
session-tracing, coreToolScheduler, loggingContentGenerator,
openaiContentGenerator/pipeline, anthropicContentGenerator, geminiContentGenerator
→ 838 tests, all passing
The architecture holds up. Context-keyed handles with a generation counter isolate retries and concurrent requests correctly, every telemetry path is try/catch-wrapped so it cannot throw into the request path, jsonValue builds objects via Object.defineProperty so a __proto__ key in a provider payload cannot pollute, and the fixtures + docs are unusually thorough for a change this size.
My concerns are about undeclared behaviour changes rather than the design. Both Criticals share one shape: they change what is exported for users running with includeSensitiveSpanAttributes=false, which is the default and the population the PR describes as unaffected.
C=2, S=3, N=2 — details inline.
Minor, not filed as a finding: gen-ai-exchange.test.ts (402 lines) tests gen-ai-request.ts, and there is no gen-ai-exchange.ts. gen-ai-request.ts is now 568 lines spanning request-attribute extraction, the exchange controller, the context plumbing and twelve report* facades — a natural split point if you touch it again.
CI
ubuntu-latest / Java 11 and windows-latest / Java 21 fail on DaemonSessionClientTest.rejectsClientWidePromptCapacityBeforeMutation (IOException: HTTP/1.1 header parser received no bytes). This diff touches no Java, the same job passes on ubuntu Java 17/21 and macOS Java 21, and the workflow is green on the last five main runs — flaky, worth a re-run rather than an investigation.
Keep ACP tool telemetry best-effort, improve diagnostics and sensitive-off coverage, and document finish-reason and tool-description behavior. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
|
Addressed the latest review against
Validation on the exact commit: build, bundle, typecheck, lint, core content tests (27/27), ACP Session tests (414/414), and OpenAI fake-server integration (2/2) all passed. Two prior Java SDK matrix failures were independently diagnosed as unrelated timing/connection flakes; this PR does not modify Java, and the new commit has triggered fresh checks. |
doudouOUC
left a comment
There was a problem hiding this comment.
Re-review — 5230bd8f4
Re-ran the suites on the updated branch: core 838 + ACP Session.test.ts 414, all passing. CI is now fully green, including ubuntu Java 11 and windows Java 21 — confirming those were the flake they looked like.
Round-1 outcomes
- Fixed: ACP telemetry isolation, the
reserve()documentation, and the sensitive-off tool-span coverage. - Accepted as your call: always-on
gen_ai.tool.description, provider-rawfinish_reasons, and whole-attribute omission on a missing finish reason. All three are now documented where a query author would look, and I am not re-litigating them. - Withdrawn — two of my findings were wrong, both corrected in their threads.
erroris in the pinnedFinishReasonenum, so the partial-failure fallback was schema-valid all along. AndrefreshLateUsageMetadatais load-bearing and was covered by tests in the same commit — I asserted otherwise without checking the file's own test suite, and removing the calls fails exactly the two tests you named.
One new finding
C=0, S=1. Not a regression from 5230bd8f4 — it is in gen-ai-content.ts from the first commit and I missed it in round 1. Details inline.
|
Thanks for the PR! Template looks good ✓ — all required sections are present with substantive content, including a bilingual translation. Problem: This is a Direction: Aligned. Standardizing content attributes on the OTel GenAI conventions is the right call — it lets ARMS and other backends query the same fields without dual-write aliases. Claude Code's CHANGELOG shows similar OTel content attribute work ( Size: This PR touches core paths (
At 2,608 production lines this exceeds both the 500-line maintainer-awareness threshold and the 1,000-line large-PR advisory. Since this is a Approach: The architecture is sound. Capturing at provider boundaries (after adapter finalization) is the right design point. The One question worth considering: the 中文说明感谢贡献! 模板完整 ✓ —— 所有必填章节均已填写,包含中文翻译。 问题: 这是一个 方向: 对齐。将内容属性标准化到 OTel GenAI 规范是正确的方向——让 ARMS 和其他 backend 可以查询同一组字段而无需双写 alias。Claude Code 的 CHANGELOG 显示了类似的 OTel 内容属性工作( 规模: 本 PR 大量触及核心路径(
2,608 行生产代码超过了 500 行维护者知悉阈值和 1,000 行大 PR 建议阈值。由于这是 方案: 架构合理。在 provider 边界(adapter 完成之后)采集是正确的设计点。 一个值得考虑的问题: — Qwen Code · qwen3.8-max-preview Reviewed at |
Code ReviewIndependent proposal: Given the goal of capturing provider-final content for OTel GenAI alignment, I would have: (1) added a content extraction function per provider that converts the SDK request/response to a canonical JSON schema, (2) extended the existing Comparison with the PR: The PR's approach matches and exceeds this baseline. The No critical blockers found. The code is well-structured and follows project conventions (ESM,
sequenceDiagram
participant P1 as LoggingContentGenerator
participant P2 as GenAiExchangeController
participant P3 as Provider (OpenAI/Anthropic/Gemini)
participant P4 as GenAiOutputAccumulator
participant P5 as LLM Request Span
P1->>P2: createGenAiExchange(span, options)
P1->>P3: generateContent / generateContentStream
P3->>P2: reportXxxRequest(sdkRequest) — beginRequest
P2->>P5: setAttributes(gen_ai.input.messages, system_instructions, tool.definitions)
P3->>P2: reportXxxResponse / reportXxxChunk
P2->>P4: record(handle, update)
P1->>P2: finalize(success)
P2->>P4: finalize(success) — serialize output
P2->>P5: setAttribute(gen_ai.output.messages)
P2-->>P1: finishReasons
Files changed (30 of 34 shown)
TestingCI evidence (from the PR's own checks at
All CI checks pass. The PR includes 941 lines of new unit tests for Not verified: real-provider behavior (OpenAI endpoint, ARMS staging) — the author notes credentials were unavailable. Windows and Linux were not tested locally by the author (macOS only). This is a non-UI telemetry change, so tmux real-scenario testing is N/A. 中文说明代码审查独立方案: 给定目标是为 OTel GenAI 对齐采集 provider-final 内容,我会:(1) 为每个 provider 添加内容提取函数,将 SDK 请求/响应转换为规范 JSON Schema;(2) 扩展现有 与 PR 的比较: PR 的方案匹配并超越了这一基线。 未发现关键阻塞问题。代码结构良好,遵循项目约定(ESM、
序列图展示了 exchange 生命周期的关键路径:LoggingContentGenerator 创建 exchange → provider 报告请求 → provider 报告响应/chunk → LoggingContentGenerator 调用 finalize → span 获得 gen_ai.output.messages。 测试CI 证据(来自 PR 自身在 所有 CI 检查通过。PR 包含 941 行 未验证:真实 provider 行为(OpenAI endpoint、ARMS staging)——作者注明没有凭据。作者未在本地测试 Windows 和 Linux(仅 macOS)。这是非 UI 遥测变更,tmux 真实场景测试不适用。 — Qwen Code · qwen3.8-max-preview Reviewed at |
|
Confidence: 3/5 The code quality is high and the direction is right — standardizing content telemetry on the OTel GenAI conventions is well-motivated and well-executed. The exchange controller lifecycle, bounded streaming accumulator, and consistent sensitive-data gating are all sound. CI is green and test coverage is comprehensive (2,900+ lines of test code). What keeps this at 3 rather than higher:
Verdict: No critical blockers found in the code. The implementation is careful, well-tested, and follows project conventions. Escalating to the maintainer for direction approval on the telemetry architecture changes and the breaking-change scope before this can be approved. @doudouOUC — solid work. The exchange controller design and the provider-specific content extraction are well done. The main thing holding this back from approval is the telemetry-infrastructure scope, which needs a maintainer's architectural sign-off. 中文说明置信度:3/5 代码质量高,方向正确——将内容遥测标准化到 OTel GenAI 规范动机充分且执行良好。Exchange controller 生命周期、有界流式累加器和一致的敏感数据门控都是合理的。CI 全绿,测试覆盖全面(2,900+ 行测试代码)。 置信度为 3 而非更高的原因:
结论: 代码中未发现关键阻塞问题。实现仔细、测试充分、遵循项目约定。转交维护者 进行遥测架构变更和 breaking change 范围的方向审批,之后才能批准。 @doudouOUC —— 出色的工作。Exchange controller 设计和 provider 特定的内容提取做得很好。阻止批准的主要原因是遥测基础设施的范围,需要维护者的架构签核。 — Qwen Code · qwen3.8-max-preview Reviewed at |
Document that invalid optional parameter schemas are omitted while required tool identities remain ordered and complete. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
|
Addressed the latest review against The new tool-schema suggestion was partially accepted. I did not change runtime behavior because the pinned OTel schema requires only The review did expose an ambiguity in the design wording. The design doc and bilingual PR description now distinguish attribute-level whole-value omission from the optional Validation on the exact commit: |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed. Not reviewed: chunk 1, chunk 3, chunk 4, chunk 26, chunk 18, chunk 14, chunk 8, chunk 24, chunk 15, chunk 13, chunk 7, chunk 11, chunk 9, chunk 20, chunk 5, chunk 17, chunk 16, chunk 19, chunk 2, chunk 22, chunk 23, chunk 12, chunk 25, chunk 21, chunk 10, chunk 6 — launched with a prompt that is not the one the CLI built. Not reviewed: Test coverage matrix (whole-diff), Agent 1b: Removed-behavior audit, Agent 1c: Cross-file tracer — its prompt was built, but no agent on record was launched with it. Not reviewed: reverse audit — its prompt was built, but no agent was launched with it — the pass that hunts what the rest of the review missed ran, if at all, without the method its brief carries, and cannot be certified.
— qwen3.7-max via Qwen Code /review
Code Review — #7667
|
Local build & real-test verification — #7667
|
| Scope | Command | Result |
|---|---|---|
| 10 changed core suites (gen-ai-content/exchange/request, detailed-span-attributes, session-tracing, logging/anthropic/gemini/openai generators, coreToolScheduler) | vitest run |
822 / 822 ✓ |
| ACP tool-telemetry helpers | Session.test.ts |
414 / 414 ✓ |
E2E — real dist/cli.js vs in-process fake OpenAI, LLM → Tool → LLM |
gen-ai-telemetry.test.ts |
2 / 2 ✓ (sensitive ON + OFF) |
2 · Independent span inspection (raw telemetry.log, both capture modes)
Parsed the exported spans directly and confirmed the seven standard fields behave exactly as specified:
- Sensitive ON —
gen_ai.input.messages(the real ~45 KB system+user),gen_ai.output.messages= therun_shell_commandtool_call with parsed args{"command":"pwd"}/finish_reason: tool_calls; the multi-candidate final turn preserves both choices in provider index order (["stop","stop"]);gen_ai.tool.definitionsDraft-07 normalized; tool span carriesgen_ai.tool.description(exactly 4108 = 4096 +…[truncated], surrogate-safe),gen_ai.tool.call.arguments= final invocation params{"command":"pwd"}, andgen_ai.tool.call.result= the model-facing{"output":…}. - Sensitive OFF — every content field absent;
gen_ai.tool.descriptionstill present (non-sensitive metadata, per OTel);arguments/resultabsent;choice.countomitted forn=1. - Removed legacy surface — verified absent across all 20 spans in both runs:
system_prompt*,tools/tools_count,response.model_output*,tool_input*,tool_result*, and thetool_schemaevent (0 emitted).
3 · A/B discrimination (the tests are load-bearing)
Ran the same PR test files against a bundle rebuilt from base source vs the PR:
- base
f8014652→ 2 failed (content fields absent →gen_ai.tool.descriptionisundefined, LLM span omits the aligned content) - PR
cde441897→ 2 passed
Base already carries #7635's gen_ai.request.*, so the failure isolates precisely this PR's content contribution — the E2E is not vacuous.
Verdict
From a build-and-run standpoint this is merge-ready — 1,238 unit tests + the wire-vs-span E2E all pass, the raw spans match the pinned schema on both capture paths, and the legacy attributes/tool_schema event are genuinely gone. No functional issue surfaced. The only "please decide consciously" item remains the one already flagged in the code review above: gen_ai.tool.description now emits by default (sensitive-capture OFF), re-emitted per call without dedup — defensible under OTel, but a deliberate privacy/volume call for Qwen's dynamic MCP/skill descriptions.
中文说明
本地构建 + 真实测试验证 —— #7667 feat(core): Align GenAI content telemetry fields
在上面的静态 code review 基础上补充一轮实测:在隔离 worktree 从干净 npm ci 构建 PR head,跑完全部改动测试面,用真实 bundle CLI 对 fake OpenAI server 做端到端 LLM → Tool → LLM,并且亲自解析导出的原始 OTel span(直接读 telemetry.log,不依赖 PR 自带断言)。全部通过。
环境: detached worktree @ cde441897(PR head),干净 npm ci → esbuild bundle(dist/cli.js),macOS,Node 22,merge-base f8014652。
1 · 测试套件全绿
- 10 个改动的 core 套件(gen-ai-content/exchange/request、detailed-span-attributes、session-tracing、logging/anthropic/gemini/openai 生成器、coreToolScheduler):822 / 822 ✓
- ACP 工具遥测 helper
Session.test.ts:414 / 414 ✓ - E2E 真实
dist/cli.js对 fake OpenAI(LLM → Tool → LLM)gen-ai-telemetry.test.ts:2 / 2 ✓(敏感开 + 关)
2 · 独立 span 检查(原始 telemetry.log,两种模式)
- 敏感开:
input.messages(真实约 45 KB system+user);output.messages=run_shell_command的 tool_call,参数已解析为{"command":"pwd"}、finish_reason: tool_calls;多候选最终轮按 provider index 顺序保留两个 choice(["stop","stop"]);tool.definitions已归一化为 Draft-07;tool span 带tool.description(恰为 4108 = 4096 +…[truncated],代理对安全)、tool.call.arguments= 最终执行参数{"command":"pwd"}、tool.call.result= 返回给模型的{"output":…}。 - 敏感关:所有内容字段缺席;
tool.description仍在(非敏感元数据);arguments/result缺席;n=1时省略choice.count。 - 移除的旧字段:两次运行、全部 20 个 span 中均确认缺席:
system_prompt*、tools/tools_count、response.model_output*、tool_input*、tool_result*,以及tool_schema事件(0 个)。
3 · A/B 判别(测试确实“承重”)
同一批 PR 测试文件,对 base 源码重建的 bundle vs PR:base f8014652 → 2 失败(内容字段缺席,tool.description 为 undefined);PR cde441897 → 2 通过。base 已含 #7635 的 gen_ai.request.*,因此失败精确隔离本 PR 的内容字段贡献。
结论: 从构建与运行角度可合并——1,238 个单测 + wire-vs-span E2E 全过,原始 span 在两条采集路径上都符合固定 schema,旧属性与 tool_schema 事件确已移除,未发现功能性问题。唯一需要 maintainer 有意识拍板的仍是 review 里已标注的第 (1) 点:tool.description 现在默认输出(敏感采集关闭时),且每次调用重复输出无去重——在 OTel 下可接受,但对 Qwen 动态 MCP/skill 描述是一个刻意的隐私/体积取舍。
Document the changed semantics of deprecated helpers and the reason the fallback context shadows key operations. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
|
@wenshao Thanks for the thorough review. I independently rechecked the two optional points and accepted both in
There are no runtime changes. Validation on the exact commit: the three relevant telemetry suites passed 58/58, Prettier and diff checks passed, and two clean audit passes found no additional issue. The three maintainer-sign-off points remain intentional and documented: always-on bounded Tool descriptions, provider-raw finish reasons, and omission of only an invalid optional Tool |
Review —
|
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
No issues found. LGTM! ✅
中文说明
未发现问题。LGTM!✅
— qwen3.7-max via Qwen Code /review
|
Released in v0.21.1. |
|
|


What this PR does
This PR aligns seven content and Tool span attributes shared by the pinned OpenTelemetry GenAI conventions and Alibaba Cloud ARMS:
gen_ai.input.messages,gen_ai.output.messages,gen_ai.system_instructions,gen_ai.tool.definitions,gen_ai.tool.description,gen_ai.tool.call.arguments, andgen_ai.tool.call.result.LLM inputs are captured from the first provider-final SDK request, while outputs are captured from the final physical request attempt. OpenAI-compatible, Anthropic, Gemini, and Vertex payloads are converted to the pinned standard JSON schemas, including multi-candidate and streaming responses, without retaining raw chunk arrays.
Tool spans now record the static registry description, the final invocation parameters immediately before execution, and the successful model-facing result. Exact-equivalent private content attributes and the
tool_schemaevent are no longer emitted; deprecated public helpers remain as thin standard-field adapters.Sensitive JSON remains opt-in through
telemetry.includeSensitiveSpanAttributes, is serialized compactly, and is omitted as a complete value when invalid, cyclic, incomplete, or over the configured per-attribute limit. Tool definitiontypeandnameare required identities, while an optionalparametersvalue that cannot be normalized to Draft-07 is omitted without discarding the ordered identity list. Tool descriptions are non-sensitive and use a surrogate-safe 4096 UTF-16-code-unit limit.Why it's needed
Qwen Code already emits the non-content GenAI fields shared by OpenTelemetry and ARMS, but its content observability still used private, provider-independent snapshots taken before adapters had finalized the SDK request. That made dashboards depend on non-standard keys and could diverge from the payload actually sent to a provider.
Capturing at provider boundaries makes the telemetry truthful after adapter defaults, overrides, field removal, and fallback behavior. Standard schemas also let ARMS and other OpenTelemetry backends query the same fields without dual-write aliases, while the existing sensitive-data switch and size limits keep content capture explicit and bounded.
Reviewer Test Plan
How to verify
Enable telemetry with the file exporter and
includeSensitiveSpanAttributes=true, then run an OpenAI-compatible LLM → Tool → LLM turn. Confirm that each LLM span contains schema-valid standard input/output content matching the provider-final request and raw response, that all response choices and finish reasons are preserved in provider index order, and that the Tool span contains its static description, final executed arguments, and successful model-facing result.Repeat with sensitive capture disabled. Confirm that messages, instructions, definitions, arguments, and results are absent while non-sensitive GenAI fields and the Tool description remain. Also confirm that the removed
system_prompt*,tools,tools_count,response.model_output*,tool_input*,tool_result*, andtool_schemaevent are not emitted.The permanent fake OpenAI integration test performs both scenarios and compares exported span JSON with the request bodies received by the server. Provider unit tests cover OpenAI-compatible, Anthropic, Gemini, and Vertex non-streaming/streaming boundaries, retries and fallback attempts, cancellation/failure finalization, concurrent context isolation, complete-value size omission, and final Tool argument/result timing.
Evidence (Before & After)
N/A — this is a non-UI telemetry change.
Tested on
Environment (optional)
macOS with Node.js v22.22.3 and npm 10.9.8. Verified with the locally built and bundled CLI, the file exporter, and the local fake OpenAI server without sandboxing.
Risk & Scope
seed,top_k, SpanKind/span-name migration, and retry topology changes remain out of scope.tool_schemaevent stop emitting without a dual-write period. Queries should migrate to the seven standardgen_ai.*fields above.gen_ai.response.finish_reasonsnow preserves provider-raw values such asstop,length,tool_calls, andend_turninstead of Gemini-normalized values such asSTOPandMAX_TOKENS. Deprecated helper exports remain source-compatible, and sensitive content remains disabled by default.Linked Issues
N/A
中文说明
本 PR 做了什么
本 PR 对齐固定版本 OpenTelemetry GenAI 规范与阿里云 ARMS 共有的 7 个内容及 Tool Span 属性:
gen_ai.input.messages、gen_ai.output.messages、gen_ai.system_instructions、gen_ai.tool.definitions、gen_ai.tool.description、gen_ai.tool.call.arguments和gen_ai.tool.call.result。LLM 输入来自第一次 provider-final SDK 请求,输出来自最后一次物理请求 attempt。OpenAI 兼容、Anthropic、Gemini 和 Vertex 的 payload 会转换为固定版本的标准 JSON Schema,包括多候选和流式响应,同时不会保存原始 chunk 数组。
Tool Span 现在记录 registry 中的静态描述、执行前最终的 invocation 参数,以及成功后最终返回给模型的结果。与标准字段完全等价的私有内容属性和
tool_schema事件停止写入;已废弃的公共 helper 继续保留,但只作为标准字段的薄适配器。敏感 JSON 仍只在
telemetry.includeSensitiveSpanAttributes开启时采集,使用紧凑序列化;当值非法、循环引用、不完整或超过单属性配置上限时整项省略。Tool definition 的type和name是必填身份;可选parameters无法归一化为 Draft-07 时,只省略该可选属性,并保留有序的 Tool 身份列表。Tool 描述为非敏感字段,使用代理对安全的 4096 个 UTF-16 code unit 上限。为什么需要
Qwen Code 已经输出 OpenTelemetry 与 ARMS 共有的非内容 GenAI 字段,但内容可观测仍使用私有字段,而且快照取自 provider adapter 完成 SDK 请求处理之前。这使查询依赖非标准 key,也可能与实际发给 provider 的 payload 不一致。
在 provider 边界采集可以真实反映 adapter 默认值、覆盖值、字段删除和 fallback 之后的请求。标准 Schema 也让 ARMS 和其他 OpenTelemetry backend 可以查询同一组字段而无需双写 alias;现有敏感数据开关和大小限制则保证内容采集是显式且有界的。
Reviewer 测试计划
如何验证
启用 telemetry file exporter 和
includeSensitiveSpanAttributes=true,执行一次 OpenAI 兼容的 LLM → Tool → LLM 流程。确认每个 LLM Span 都包含符合 Schema 的标准输入/输出内容,并与 provider-final 请求及原始响应一致;确认所有 response choice 和 finish reason 按 provider index 顺序保留;确认 Tool Span 包含静态描述、最终执行参数和成功后返回给模型的结果。关闭敏感内容采集后重复执行。确认 messages、instructions、definitions、arguments 和 result 均不存在,同时非敏感 GenAI 字段与 Tool description 仍然保留。还应确认已删除的
system_prompt*、tools、tools_count、response.model_output*、tool_input*、tool_result*和tool_schema事件不再输出。永久 fake OpenAI 集成测试覆盖以上两个场景,并将导出的 Span JSON 与服务端实际收到的请求体比较。Provider 单元测试覆盖 OpenAI 兼容、Anthropic、Gemini 和 Vertex 的非流式/流式边界、retry 与 fallback attempt、取消/失败 finalize、并发 Context 隔离、完整值超限省略,以及 Tool 最终参数/结果的采集时机。
证据(Before & After)
N/A——这是非 UI telemetry 变更。
测试平台
环境(可选)
macOS,Node.js v22.22.3,npm 10.9.8。使用本地 build 和 bundle 后的 CLI、file exporter 以及不启用 sandbox 的本地 fake OpenAI server 完成验证。
风险与范围
seed、top_k、SpanKind/Span name 迁移和 retry topology 变更仍不在本 PR 范围内。tool_schema事件停止写入,不提供双写期。查询应迁移到上面的 7 个标准gen_ai.*字段。gen_ai.response.finish_reasons现在保留 provider 原始值,例如stop、length、tool_calls和end_turn,不再使用STOP、MAX_TOKENS等 Gemini 归一化值。已废弃的 helper 导出保持源码兼容,敏感内容默认仍关闭。关联 Issue
N/A