)
Qwen Code 可观测性实战GenAI 语义约定与阿里云 ARMS 字段对齐Span 属性契约与实现解析【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code导读本文基于 Qwen Code 仓库中的设计文档 gen-ai-arms-field-alignment.md系统讲解 Qwen Code 如何将首批发往 OpenTelemetry / ARMS 的 LLM Trace span 属性对齐到 GenAI 语义约定semantic conventions包括 span 字段契约、私有属性迁移、Provider 与操作解析、敏感内容采集与 ARMS 接入配置。读完本文你将掌握 Qwen Code 遥测数据中gen_ai.*属性的确切含义、各属性从哪个阶段、哪个请求对象取值以及如何为 ARMS LLM Trace 一键接入并开启端用户身份扩展。设计范围与标准基线Qwen Code 的遥测实现遵循两份基线OpenTelemetry GenAI 语义约定与阿里云 ARMS LLM Trace 字段定义。本次对齐是第一波只统一名称、类型、语义完全一致的那部分 span 属性框架自定义的 span 名称与 kind 保持不变。其中主 Agent 扩展让已有的 interaction span 成为完整 tool-continuation工具续跑拓扑的父 spanARMS 专属扩展可选的端用户身份属性gen_ai.user.idopt-in默认不开启。由于 OpenTelemetry GenAI 约定仍处于 Development 状态文档将基线固定pin到具体 commitInference spans、Agent spans、GenAI registry 固定到 commit2e994c6d59a93bb4fc1752c5378eedb9b8e14d6b主 Agent 调用与错误状态行为额外参考 commit8d3e4a0f3c34a46f6edb9c71e8666e02e6bf3958的 Agent span 与 recording-errors 文档流式属性是窄补充固定到 OpenTelemetry Semantic Conventions v1.41.0且只采纳gen_ai.request.stream与gen_ai.response.time_to_first_chunk两个字段不是对基线的整体升级ARMS 基线为 LLM Trace 字段定义。任何一方基线升级都必须重新生成并评审这张字段矩阵——这是后续维护该契约的第一原则。Span 字段契约Field contract设计文档用一张矩阵定义了本阶段每个 span 类型发射的标准属性、来源与省略规则这是全文最核心的内容完整复刻如下Span本阶段发射的标准属性来源与省略规则LLMgen_ai.operation.name、gen_ai.provider.name、gen_ai.conversation.id、gen_ai.request.modelspan 创建时写入conversation ID 即现有 session IDLLM requestgen_ai.request.choice.count、gen_ai.request.max_tokens、gen_ai.request.temperature、gen_ai.request.top_p、gen_ai.request.frequency_penalty、gen_ai.request.presence_penalty、gen_ai.request.stop_sequences从第一个 provider-final SDK 请求对象读取无效或不可用值一律省略不推断任何 SDK 或服务端默认值LLM streamgen_ai.request.stream、gen_ai.response.time_to_first_chunk流式请求发射true非流式省略标准流标记首块耗时在首个归一化响应到达后以秒为单位发射LLM inputgen_ai.input.messages、gen_ai.system_instructions、gen_ai.tool.definitions敏感紧凑 JSON来自同一个第一个 provider-final 请求每个完整值独立校验无效或超限则整体省略LLM responsegen_ai.response.id、gen_ai.response.model、gen_ai.response.finish_reasons仅 provider 响应数据缺失响应模型时省略而非回填请求模型所有候选的 finish reason 按候选索引排序LLM outputgen_ai.output.type、gen_ai.output.messages输出类型仅对受支持的 Gemini/Vertex 请求设置发射敏感输出消息来自最终物理请求尝试并保留每个候选LLM usagegen_ai.usage.input_tokens、gen_ai.usage.output_tokens、gen_ai.usage.cache_read.input_tokens、gen_ai.usage.cache_creation.input_tokens仅 provider 上报的非负安全整数显式 0 保留只报总数时不估算、不拆分Toolgen_ai.operation.nameexecute_tool、条件性gen_ai.agent.name、gen_ai.tool.name、gen_ai.tool.description、gen_ai.tool.typefunction、gen_ai.tool.call.id、gen_ai.tool.call.arguments、gen_ai.tool.call.resultAgent 名从实际父 Agent 复制描述是静态元数据敏感参数反映实际执行调用result 仅成功时采集Main agentgen_ai.operation.nameinvoke_agent、gen_ai.agent.nameqwen-code、gen_ai.conversation.id、可选gen_ai.output.typejson、敏感gen_ai.input.messages、敏感gen_ai.output.messages复用现有 interaction span输入是一次原始用户提示投影输出是一次最终用户可见回答请求模型、provider、Agent ID/版本/描述、指令与聚合用量全部省略Subagentgen_ai.operation.nameinvoke_agent、gen_ai.agent.name、gen_ai.agent.description、gen_ai.conversation.id、可选gen_ai.request.model描述被限制为 1024 个 UTF-16 码元内部调用 ID 保持私有私有属性迁移与移除清单没有标准等价物的私有属性默认保留除非在下方移除清单中显式列出有精确标准等价的私有别名与无效 GenAI 别名则无过渡期直接移除、不做双写被移除的属性替代方案LLMqwen-code.modelgen_ai.request.model主 Agent interaction 保留qwen-code.model并省略标准请求模型因为调用过程中模型选择可能变化LLMresponse_idgen_ai.response.idAPI 响应/错误日志保留原有response_idschemaLLMinput_tokensprovider 上报输入拆分时用gen_ai.usage.input_tokensLLMoutput_tokensprovider 上报输出拆分时用gen_ai.usage.output_tokensLLMcached_input_tokensprovider 上报缓存读取时用gen_ai.usage.cache_read.input_tokensqwen-code.toolSpantool.namegen_ai.tool.nameblocked-on-user 与 hook span 继续使用tool.namegen_ai.usage.cached_tokensprovider 上报缓存读取时用gen_ai.usage.cache_read.input_tokensLLMllm_request.streamgen_ai.request.stream流式发射true非流式按约定省略gen_ai.server.time_to_first_token不再发射与标准首块属性不等价gen_ai.usage.reasoning_tokens本基线无 ARMS/GenAI 公共属性继续查询私有thoughts_token_countLLMsystem_prompt*gen_ai.system_instructionsOpenAI system/developer 消息表示在gen_ai.input.messages中LLMtools、tool_schemaeventsgen_ai.tool.definitionsLLMresponse.model_output*gen_ai.output.messagesTooltool_input*gen_ai.tool.call.argumentsTooltool_result*gen_ai.tool.call.resulttools_count、hash/preview/length/truncation 元数据无标准等价物移除查询迁移注意事项gen_ai.response.finish_reasons现在保留 provider 的原始字符串全部候选不再使用旧的 Gemini 归一化值。已有按STOP、MAX_TOKENS过滤的查询必须迁移到 provider 值例如stop、length、tool_calls、end_turn。流式 span 查询应将llm_request.streamtrue替换为gen_ai.request.streamtrue非流式 span 以缺失gen_ai.request.stream识别旧的llm_request.streamfalse过滤现在匹配 0 行。gen_ai.response.time_to_first_chunk与 span 的ttft_ms是两个独立指标前者是标准化的首个归一化 chunk 耗时秒后者仍是首个用户可见输出耗时毫秒。因此duration_ms - gen_ai.response.time_to_first_chunk * 1000并不等于sampling_ms。首块耗时time_to_first_chunk的测量语义gen_ai.response.time_to_first_chunk使用单调计时器从包装 provider 调用之前立即开始到LoggingContentGenerator观察到首个归一化GenerateContentResponse为止。这里有三个容易踩坑的细节适配器可能过滤/合并原始协议帧例如 OpenAI 管线会丢弃空响应帧因此被适配器丢弃的帧不参与计时记录值可能晚于真实首网络帧仅元数据或仅用量的归一化响应在适配器过滤后存活下来的也计作 chunk流式请求后续失败、被 abort 或超时时属性依然保留没有任何 chunk 到达时属性被省略。同时内部ttftMs计时器继续承担首个用户可见输出延迟继续驱动ApiResponseEvent.ttft_ms、sampling_ms、output_tokens_per_second与 API 请求拆分指标。在源码中这两者的区分有明确注释session-tracing.ts 指出ttftMs是从流包装器既有墙钟起点到首个含用户可见内容text/functionCall/inlineData/executableCode/thought的 chunk而gen_ai.response.time_to_first_chunk使用单调请求计时器、记录首个归一化 chunk无论内容。Provider 与操作解析Provider 解析是作用于有效 content-generator 配置的纯函数绝不返回 URL、凭据、任意代理主机名或从模型名推断的值。解析顺序Qwen OAuth 且与DASHSCOPE_PROXY_BASE_URL精确匹配 →dashscope边界安全boundary-safe的主机名匹配识别阿里云 Model Studio 端点与内部阿里网关、Azure OpenAI以及受支持的第三方端点DeepSeek、xAI、Mistral、MiniMax、Z.AI、ModelScope、MiMo、OpenRouter、Requesty主机未知时已知的apiKeyEnvKey识别配置的 provider主机身份冲突时主机胜出未知端点回退到协议 provideropenai、anthropic、gcp.gemini或gcp.vertex_ai。操作operation解析OpenAI-compatible、Anthropic、Qwen OAuth 请求使用chatGemini 与 Vertex AI 请求使用generate_content。这些规则在测试中有大量佐证例如 gen-ai-provider.test.ts 覆盖https://dashscope.aliyuncs.com/compatible-mode/v1、https://DASHSCOPE-INTL.ALIYUNCS.COM/v1/、https://user:secretcn-hongkong.dashscope.aliyuncs.com/v1、https://coding.dashscope.aliyuncs.com/v1以及内部网关https://idealab.alibaba-inc.com/api/openai/v1、https://gateway.alibaba-inc.com/dashscope/v1等均解析为dashscope同时验证了https://dashscope.aliyuncs.com.attacker.example/v1这类伪装域名不会被误判为 dashscope边界安全匹配的体现。测试还确认OPENROUTER_API_KEY等apiKeyEnvKey可作为 provider 识别依据BAILIAN_CODING_PLAN_API_KEY、BAILIAN_TOKEN_PLAN_API_KEY、DASHSCOPE_API_KEY、IDEALAB_API_KEY均归入dashscope。请求参数Request parameters的采集时机与取值映射请求属性在适配器应用完默认值、覆盖、不支持的字段移除与输出窗口钳制之后、调用 provider SDK 之前采集。这是 Qwen Code 可见的最终 SDK 请求对象不是原始逻辑配置也不是序列化后的 HTTP body。一个逻辑 LLM span 只记录第一个这样的请求快照。各 provider 的字段映射标准属性OpenAI-compatible / Qwen OAuthAnthropicGemini / Vertex AIgen_ai.request.choice.countn不适用config.candidateCountgen_ai.request.max_tokensmax_tokens、max_completion_tokens或max_new_tokensmax_tokensconfig.maxOutputTokensgen_ai.request.temperaturetemperaturetemperatureconfig.temperaturegen_ai.request.top_ptop_ptop_pconfig.topPgen_ai.request.frequency_penaltyfrequency_penalty当前不发送config.frequencyPenaltygen_ai.request.presence_penaltypresence_penalty当前不发送config.presencePenaltygen_ai.request.stop_sequencesstopstop_sequencesconfig.stopSequences取值规则源码 gen-ai-request.ts 中通过finiteNumber/safeInteger/stopSequences/outputBudget辅助函数实现有限数与安全整数精确保留包括失败请求上的 0 与负值choice count 为 1 时省略stop sequences 必须是完整字符串数组OpenAI 的单字符串形式归一化为单元素数组空数组保留混合数组整体省略而非过滤显式适配器默认值会记录隐式 SDK 或服务端默认值不推断当多个 OpenAI-compatible 输出预算别名同时存在时仅当所有现值都是有效安全整数且相等才发射标准最大值冲突则省略因为兼容端点没有公共优先级规则。实现上OpenAI 请求提取函数extractOpenAiRequestAttributes对n做安全整数校验并在不等于 1 时写入gen_ai.request.choice.count再通过outputBudget统一处理max_tokens/max_completion_tokens/max_new_tokens三个别名Anthropic 与 Gemini 各有独立的extractAnthropicRequestAttributes、extractLlmRequestAttributes映射关系与文档表完全一致。内容与工具载荷Content and tool payloads敏感内容开关与序列化边界敏感的 GenAI 内容仅在开启telemetry.includeSensitiveSpanAttributes时采集。Qwen Code不读取OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT因此全仓库只有一个内容采集开关。OpenAI-compatible、Anthropic、Gemini、Vertex 适配器将 provider-final SDK 请求与原始响应结构转换成本设计固定的 JSON schema。关键语义输入来自第一次物理请求尝试input messages、system instructions、tool definitions响应是 generation-bound 的provider 回退或 required-thinking 重试会启动新的响应累加器旧尝试的迟到 chunk 被忽略流式累加器保留规范化部件而非原始 chunk部分失败会用error标记未完成的候选成功响应中某个候选缺少显式 finish reason 时省略完整的 output-message 属性主 Agent interaction 使用独立的投影而非 provider 累加器输入是一条可靠的原始用户文本模型上下文扩展之前输出是工具、重试、回退、Hook、Steer、next-speaker 续跑全部尘埃落定后的唯一最终用户可见文本ACP channel 投递保留其独立的全文本缓冲不受遥测限制截断结构化输出是紧凑 JSON 文本且finish_reasontool_call。每个 JSON 属性紧凑序列化并独立受telemetry.sensitiveSpanAttributeMaxLength限制。无效、循环、不完整或超限的属性值整体省略JSON 永不截断。具体到gen_ai.tool.definitionstype与name是必需身份字段身份无效则省略整个属性parameters在标准 schema 中可选当 provider 提供的参数 schema 无法归一化到 Draft-07 时只省略该可选属性保留有序的工具身份列表provider 显式发送或返回的空数组/空对象会保留。尺寸上限的量化边界默认 1 MiB 限制下源码常量见 constants.tsDEFAULT_SENSITIVE_SPAN_ATTRIBUTE_MAX_LENGTH 1024 * 1024上限SENSITIVE_SPAN_ATTRIBUTE_MAX_LENGTH_LIMIT 100 * 1024 * 1024应用侧的理论最大值约为每个 LLM span 约 4 MiB 敏感属性每个 Tool span 约 2 MiB每个 interactionAgent input Agent output 兼容性new_context属性约 3 MiB。Collector 与后端可以施加更低的限制。配置解析实现在 config.ts环境变量或配置值必须是不大于 100 MiB 的正整数否则抛出FatalConfigErrorfail closed不静默回退。工具参数与结果的采集时机工具参数在执行之前、权限与编辑 hook之后从最终调用参数采集工具结果仅在调用成功且后处理成功后从返回给模型的最终FunctionResponse.response对象采集两个根都必须是 JSON 对象gen_ai.tool.description来自静态注册表描述、非敏感限制 4096 个 UTF-16 码元保留代理对surrogate pairs缩短时追加…[truncated]Agent 描述与 span 错误保持 1024 码元限制。响应与用量溯源Response and usage provenanceProvider 转换器用WeakMap给归一化的 Gemini usage 对象附加内部溯源信息记录cache-read 字段是否真实存在以及 Anthropic cache-creation tokens。这样既保持公共响应 JSON 形状不变又让垃圾回收可以跟随归一化 usage 对象不泄漏、不修改公共结构。当 OpenAI-compatible provider 只上报total_tokens时归一化 total 仍可供现有内部消费者使用但不合成input/output 拆分两个标准 usage 属性都不发射。OpenAI 的response.model/chunk.model与 Anthropic 的 message model 被保留为modelVersion缺失的 provider 模型在 tracing 中保持缺失请求模型回退仅限现有 API 日志与 UI 行为。流合并stream merging把最后一个已知的 provider 模型与 usage 溯源带入终态响应Anthropic 的message_startinput 与 cache usage 挂到首个后续产出的 chunk上这样部分流失败也能保留 provider 上报的用量而不合成输出计数。ARMS 接入配置启用 ARMS 自动 GenAI 应用识别ARMS 自动识别 GenAI 应用需要以下 resource attribute写在~/.qwen/settings.json或等价配置的telemetry段{ telemetry: { resourceAttributes: { acs.arms.service.feature: genai_app } } }Qwen Code 不会自动注入该厂商专属 resource attribute也不会注入gen_ai.span.kind。ARMS 完全可以从gen_ai.operation.name推断 LLM、Tool、Agent 三类角色因此接入方只需要在上游配置好这一条属性即可。ARMS 端用户身份扩展opt-ingen_ai.user.id是 ARMS Span 公共属性不属于上面钉住的 OpenTelemetry GenAI 基线。Qwen Code 仅在操作者显式配置telemetry.userId或环境变量QWEN_TELEMETRY_USER_ID时发射配置解析见 config.ts值必须是字符串空串视为未配置。传播路径值在 interaction Span 创建时写入并通过既有进程内 context 传播到 LLM、Tool、Agent span包括 linked-root 的 fork/background agents工具结果续跑tool-result continuation按精确 prompt ID 解析同一个活动 interaction并保持为其子 span活动注册表与保留的身份条目随现有 30 分钟 Span 安全网 TTL 一起过期。边界与约束该值永不推断、永不生成不写入 Resource/logs/metrics也不放入出站 BaggageQwen Code 不双写enduser.id或user.id之前配置的telemetry.resourceAttributes.user.id仍是通用 Resource 维度迁移时必须显式移除该设置是进程级的因此只支持一个进程代表一个端用户的场景共享 daemon 与 channel 部署下的请求级身份要等可信调用方身份端到端打通后再做。延期工作Deferred work文档明确列出了四个不在本阶段范围内的项理解它们有助于避免误用seed与top_k两个基线的类型不兼容暂不对齐Embedding需要先有正确的 requested-model 生命周期之后才能做 tracingTTFT 与首块耗时ARMS 的 time-to-first-token 与 OpenTelemetry 的 time-to-first-chunk 在名称、单位、含义上均不同。Qwen Code 发射标准的gen_ai.response.time_to_first_chunk同时保留私有ttft_ms但不承诺自动填充 ARMS 首 token 看板完整 GenAI span 命名、CLIENT span kind 与逻辑重试拓扑属于另一个独立的合规项目。小结本次字段对齐为 Qwen Code 的遥测数据建立了一份契约、两套基线的稳定模型以 OpenTelemetry GenAI 语义约定为字段权威、以 ARMS LLM Trace 为目标消费端通过精确的取值时机provider-final 请求对象、物理响应、最终工具结果与严格的省略规则不推断默认值、不估算拆分、JSON 永不截断保证字段的准确性与敏感性可控。读者在接入 ARMS 或自建 GenAI 可观测体系时可以gen_ai.operation.name为锚点区分 LLM / Tool / Agent 三类 span并注意time_to_first_chunk与ttft_ms两套时延指标的差异相关实现细节可继续阅读 gen-ai-request.ts、gen-ai-content.ts 与 gen-ai-provider.test.ts 等源码与测试。【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考