Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions docs/design/gen-ai-arms-field-alignment.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ This design aligns the first set of Qwen Code span attributes whose names,
types, and meanings agree between OpenTelemetry GenAI semantic conventions and
Alibaba Cloud ARMS LLM Trace. It does not change span names, span kinds,
parenting, or retry topology.
It also documents the opt-in ARMS-only end-user identity extension.

The OpenTelemetry GenAI convention is still Development status. This change is
pinned to commit
Expand Down Expand Up @@ -184,6 +185,25 @@ Qwen Code does not inject that vendor-specific resource attribute or
`gen_ai.span.kind`. ARMS can infer LLM, Tool, and Agent roles from
`gen_ai.operation.name`.

### ARMS end-user identity extension

`gen_ai.user.id` is an ARMS Span common attribute, not part of the pinned
OpenTelemetry GenAI baseline above. Qwen Code emits it only when the operator
explicitly configures `telemetry.userId` or `QWEN_TELEMETRY_USER_ID`. The value
is placed on the interaction Span at creation and propagated through the
existing in-process context to LLM, Tool, and Agent spans, including linked-root
fork/background agents. Tool-result continuations resolve the same logical
interaction by prompt ID without changing Span parenting; that minimal identity
entry expires with the existing 30-minute Span safety-net TTL.

The value is never inferred, generated, written to Resource/logs/metrics, or
placed in outbound Baggage. Qwen Code does not dual-write `enduser.id` or
`user.id`. A previous `telemetry.resourceAttributes.user.id` remains a generic
Resource dimension and must be removed explicitly when migrating. Because the
setting is process-wide, it is supported only when one process represents one
end user; request-scoped identity for shared daemon and channel deployments is
deferred until their trusted caller identity can be wired end to end.

## Deferred work

- `seed` and `top_k` have incompatible ARMS and GenAI types in the baselines.
Expand Down
2 changes: 1 addition & 1 deletion docs/developers/daemon/17-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ The daemon constructs each workspace runtime from that workspace's merged settin
| `context.fileName` | string | Overrides `getCurrentGeminiMdFilename()` through `BridgeOptions.contextFilename`. |
| `tools.disabled` | string[] | Tools disabled for the next ACP child spawn. Normalized through `normalizeDisabledToolList()` (`packages/cli/src/config/normalizeDisabledTools.ts`): non-array becomes `[]`, non-string entries are skipped, whitespace is trimmed, empty entries are dropped, and duplicates are removed while preserving first occurrence. Boot and `restartMcpServer` settings refresh both run through this function. `ToolRegistry.has(name)` is exact and case-sensitive. `POST /workspace/tools/:name/enable` and `tool_toggled` update this key. |
| `tools.approvalMode` | `'default' \| 'auto' \| ...` | Default session approval mode; `POST /session/:id/approval-mode` writes here when `persist: true`. |
| `telemetry` | object | OTel config. Keys include `enabled`, `otlpEndpoint`, `otlpProtocol`, `otlpTracesEndpoint`, `otlpLogsEndpoint`, `otlpMetricsEndpoint`, `target`, `outfile`, `includeSensitiveSpanAttributes`, `sensitiveSpanAttributeMaxLength`, `resourceAttributes`, and `metrics.includeSessionId`. `resolveTelemetrySettings()` reads it at boot and initializes `initializeTelemetry()`. |
| `telemetry` | object | OTel config. Keys include `enabled`, `otlpEndpoint`, `otlpProtocol`, `otlpTracesEndpoint`, `otlpLogsEndpoint`, `otlpMetricsEndpoint`, `target`, `outfile`, `userId`, `includeSensitiveSpanAttributes`, `sensitiveSpanAttributeMaxLength`, `resourceAttributes`, and `metrics.includeSessionId`. `resolveTelemetrySettings()` reads it at boot and initializes `initializeTelemetry()`. `userId` is process-wide and must not be configured as end-user identity when the daemon serves multiple users. |

## `ServeOptions` (programmatic embedding)

Expand Down
40 changes: 36 additions & 4 deletions docs/developers/development/telemetry.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ These settings can be overridden by environment variables or CLI flags.
| `otlpMetricsEndpoint` | `QWEN_TELEMETRY_OTLP_METRICS_ENDPOINT` | - | Per-signal endpoint override for metrics (HTTP only) | URL string | - |
| `outfile` | `QWEN_TELEMETRY_OUTFILE` | `--telemetry-outfile <path>` | Save telemetry to file (overrides OTLP export) | file path | - |
| `logPrompts` | `QWEN_TELEMETRY_LOG_PROMPTS` | `--telemetry-log-prompts` / `--no-telemetry-log-prompts` | Include prompts in telemetry logs | `true`/`false` | `true` |
| `userId` | `QWEN_TELEMETRY_USER_ID` | - | Stable end-user identifier written to GenAI spans as the ARMS extension `gen_ai.user.id`; prefer a pseudonymous value | string | - |
| `includeSensitiveSpanAttributes` | `QWEN_TELEMETRY_INCLUDE_SENSITIVE_SPAN_ATTRIBUTES` | - | Include standard GenAI messages, instructions, tool definitions, tool arguments, and successful tool results as native span attributes | `true`/`false` | `false` |
| `sensitiveSpanAttributeMaxLength` | `QWEN_TELEMETRY_SENSITIVE_SPAN_ATTRIBUTE_MAX_LENGTH` | - | Maximum compact JSON string length for each sensitive native span attribute. Set lower if your backend rejects large attributes. | `1..104857600` | `1048576` |
| `resourceAttributes` | `OTEL_RESOURCE_ATTRIBUTES` (+ `OTEL_SERVICE_NAME`) | - | Static resource attributes attached to every exported span / log / metric. See [Resource attributes](#resource-attributes) below. | `key=value,…` | `{}` |
Expand Down Expand Up @@ -159,6 +160,17 @@ OpenTelemetry names: `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT`,
`OTEL_EXPORTER_OTLP_LOGS_ENDPOINT`, `OTEL_EXPORTER_OTLP_METRICS_ENDPOINT`.
The `QWEN_TELEMETRY_OTLP_*` variants take precedence over the `OTEL_*` variants.

**End-user identity:** `telemetry.userId` and
`QWEN_TELEMETRY_USER_ID` are explicit opt-ins for the ARMS span attribute
`gen_ai.user.id`. The environment variable takes precedence after both values
are trimmed; a blank environment value falls back to settings. The identifier
is written only to interaction, LLM, Tool, and Agent spans. It is not a Resource
attribute, log or metric attribute, outbound Baggage value, or current
OpenTelemetry GenAI standard field. Prefer a stable pseudonymous identifier.
The value is resolved at startup, so configuration changes require a restart.
Do not configure a process-wide value on a daemon or channel instance serving
multiple end users.

For detailed information about all configuration options, see the
[Configuration Guide](../../users/configuration/settings.md).

Expand Down Expand Up @@ -446,6 +458,26 @@ sent to Alibaba Cloud.
> backend uses different paths, use per-signal endpoint overrides as
> shown in Option B.

To populate ARMS Session Analysis `User ID`, add a stable pseudonymous
identity as a span-level setting:

```json
{
"telemetry": {
"userId": "user-079458",
"resourceAttributes": {
"acs.arms.service.feature": "genai_app"
}
}
}
```

For container deployments, set
`QWEN_TELEMETRY_USER_ID=user-079458` instead. A custom
`telemetry.resourceAttributes.user.id` remains an unrelated Resource
dimension and does not populate ARMS Session Analysis; remove it when
migrating to the span-level setting.

2. If your Alibaba Cloud endpoint requires authentication, provide OTLP
headers through standard OpenTelemetry environment variables such as
`OTEL_EXPORTER_OTLP_HEADERS` (or the signal-specific variants). Qwen
Expand Down Expand Up @@ -824,16 +856,16 @@ The daemon process (long-running HTTP server mode) exposes its own metrics.
Distributed tracing spans form a tree rooted at `qwen-code.interaction`. Each interaction is a trace root with its own `traceId`; cross-prompt correlation uses the `session.id` attribute.

- `qwen-code.interaction`: Root span for each user prompt turn.
- **Attributes**: `session.id`, `qwen-code.prompt_id`, `qwen-code.message_type`, `qwen-code.model`, `qwen-code.approval_mode`, `interaction.sequence`, `interaction.duration_ms`, `qwen-code.turn_status` ("ok"/"error"/"cancelled")
- **Attributes**: `session.id`, optional ARMS extension `gen_ai.user.id`, `qwen-code.prompt_id`, `qwen-code.message_type`, `qwen-code.model`, `qwen-code.approval_mode`, `interaction.sequence`, `interaction.duration_ms`, `qwen-code.turn_status` ("ok"/"error"/"cancelled")

- `qwen-code.llm_request`: Wraps a single LLM API call.
- **GenAI attributes**: `gen_ai.operation.name`, `gen_ai.provider.name`, `gen_ai.conversation.id`, `gen_ai.request.model`, `gen_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`, optional `gen_ai.output.type`, `gen_ai.response.id`, `gen_ai.response.model`, `gen_ai.response.finish_reasons`, `gen_ai.usage.input_tokens`, `gen_ai.usage.output_tokens`, `gen_ai.usage.cache_read.input_tokens`, `gen_ai.usage.cache_creation.input_tokens`
- **GenAI attributes**: `gen_ai.operation.name`, `gen_ai.provider.name`, `gen_ai.conversation.id`, optional ARMS extension `gen_ai.user.id`, `gen_ai.request.model`, `gen_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`, optional `gen_ai.output.type`, `gen_ai.response.id`, `gen_ai.response.model`, `gen_ai.response.finish_reasons`, `gen_ai.usage.input_tokens`, `gen_ai.usage.output_tokens`, `gen_ai.usage.cache_read.input_tokens`, `gen_ai.usage.cache_creation.input_tokens`
- **Compatibility attributes**: `session.id`, `qwen-code.prompt_id`, `llm_request.context` ("subagent"/"interaction"/"standalone"), `duration_ms`, `ttft_ms`, `request_setup_ms`, `attempt`, `retry_total_delay_ms`, `sampling_ms`, `output_tokens_per_second`, `success`, `error`, `finish_reason`, `thoughts_token_count`, `subagent_name`, `error_type`, `error_status_code`
- Standard response fields come from the provider response. Standard token fields are emitted only for provider-reported non-negative safe integers. If the provider reports only a total token count, input/output usage is omitted rather than estimated.
- Standard request-parameter fields come from the first provider-final SDK request object after adapter defaults, overrides, unsupported-field removal, and output-window clamps. Qwen Code does not infer SDK or server defaults.

- `qwen-code.tool`: Wraps the full tool lifecycle (approval wait + execution).
- **Attributes**: `session.id`, `gen_ai.operation.name` (`execute_tool`), `gen_ai.tool.name`, `gen_ai.tool.type` (`function`), `gen_ai.tool.call.id`, `tool.call_id`, `duration_ms`, `success`, `error`
- **Attributes**: `session.id`, optional ARMS extension `gen_ai.user.id`, `gen_ai.operation.name` (`execute_tool`), `gen_ai.tool.name`, `gen_ai.tool.type` (`function`), `gen_ai.tool.call.id`, `tool.call_id`, `duration_ms`, `success`, `error`

- `qwen-code.tool.execution`: Wraps the tool execution phase (after approval).
- **Attributes**: `session.id`, `duration_ms`, `success`, `error`
Expand All @@ -845,7 +877,7 @@ Distributed tracing spans form a tree rooted at `qwen-code.interaction`. Each in
- **Attributes**: `session.id`, `hook_event` ("PreToolUse"/"PostToolUse"/"PostToolUseFailure"/"PostToolBatch"), `tool.name`, `tool.use_id` (optional), `is_interrupt` (boolean, optional), `duration_ms`, `success`, `should_proceed` (optional), `should_stop` (optional), `block_type` (optional), `error` (optional)

- `qwen-code.subagent`: Wraps a single subagent invocation.
- **Attributes**: `gen_ai.operation.name` (`invoke_agent`), `gen_ai.agent.name`, `gen_ai.agent.description`, `gen_ai.conversation.id`, optional `gen_ai.request.model`, `qwen-code.subagent.id`, `qwen-code.subagent.name`, `qwen-code.subagent.invocation_kind` ("foreground"/"fork"/"background"), `qwen-code.subagent.is_built_in`, `qwen-code.subagent.depth`, `qwen-code.subagent.status`, `qwen-code.subagent.terminate_reason`, `qwen-code.subagent.duration_ms`
- **Attributes**: `gen_ai.operation.name` (`invoke_agent`), `gen_ai.agent.name`, `gen_ai.agent.description`, `gen_ai.conversation.id`, optional ARMS extension `gen_ai.user.id`, optional `gen_ai.request.model`, `qwen-code.subagent.id`, `qwen-code.subagent.name`, `qwen-code.subagent.invocation_kind` ("foreground"/"fork"/"background"), `qwen-code.subagent.is_built_in`, `qwen-code.subagent.depth`, `qwen-code.subagent.status`, `qwen-code.subagent.terminate_reason`, `qwen-code.subagent.duration_ms`

#### GenAI field migration and ARMS recognition

Expand Down
Loading
Loading