Update OpenTelemetry gen-ai conventions through v1.41#7497
Update OpenTelemetry gen-ai conventions through v1.41#7497jeffhandley merged 16 commits intodotnet:mainfrom
Conversation
Add a multi-mode Copilot skill for analyzing OpenTelemetry semantic-conventions releases with gen-ai changes and producing compensating change plans for dotnet/extensions. The skill supports 7 modes: 1. Audit current implementation against latest conventions 2. Autopilot one-shot plan and implementation 3. Generate CCA prompt for delegating to Copilot on github.com 4. CCA implementation from a prompt referencing this skill 5. Generate local plan with SQL-tracked todos 6. Local implementation after plan generation 7. Review of convention change PRs Includes reference files for: - File inventory of all OTel instrumentation files - Change classification taxonomy - Implementation code patterns - Testing guide with assertion patterns - Review checklist from past PR feedback - CCA prompt template - Historical release-to-PR mapping Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Update the realtime OpenTelemetry client documentation to reference semantic conventions v1.40 and add the cache creation input token constant introduced by the v1.40 GenAI conventions. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Map OpenAI-specific response semantic convention attributes in the OpenAI provider package and document the provider-specific pattern in the OTel gen-ai skill. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Emit v1.41 streaming and reasoning token attributes for chat and realtime telemetry, update tool definition serialization behavior, and bump GenAI semantic convention references. Refresh the update-otel-genai-conventions skill guidance for durable review and testing patterns without adding release-specific details. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Update the update-otel-genai-conventions skill so audit and local-plan workflows search open dotnet/extensions PRs for likely matching GenAI/OpenTelemetry convention updates before proceeding. If a matching PR exists, the skill reports the PR information and stops instead of producing a duplicate audit or plan. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR updates the .NET Extensions GenAI OpenTelemetry instrumentation to align with OpenTelemetry semantic conventions through v1.41, including new streaming/time-to-first-chunk and reasoning-token attributes, plus OpenAI-specific response attribute mappings. It also adds a reusable repository skill under .github/skills/ to guide future GenAI semconv update work (audit/plan/implement/review).
Changes:
- Emit/validate new GenAI attributes:
gen_ai.request.stream,gen_ai.response.time_to_first_chunk, andgen_ai.usage.reasoning.output_tokens. - Update tool-definition serialization to include tool
nameconsistently while gating sensitive fields (description,parameters) behindEnableSensitiveData. - Add OpenAI response telemetry mappings (
openai.response.service_tier,openai.response.system_fingerprint) and update OpenAI tests for streaming vs non-streaming outputs.
Reviewed changes
Copilot reviewed 22 out of 22 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| test/Libraries/Microsoft.Extensions.AI.Tests/Realtime/OpenTelemetryRealtimeClientTests.cs | Adds assertions for streaming flag, time-to-first-chunk, and reasoning token usage; updates tool definition expectations. |
| test/Libraries/Microsoft.Extensions.AI.Tests/ChatCompletion/OpenTelemetryChatClientTests.cs | Adds assertions for gen_ai.request.stream, time-to-first-chunk (streaming only), reasoning tokens, and updated tool-definition JSON. |
| test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIResponseClientTests.cs | Updates test payloads to differentiate streaming vs non-streaming Responses API outputs; asserts openai.response.service_tier. |
| test/Libraries/Microsoft.Extensions.AI.OpenAI.Tests/OpenAIChatClientTests.cs | Updates streaming/non-streaming ChatCompletions payloads; asserts openai.response.service_tier and openai.response.system_fingerprint. |
| src/Libraries/Microsoft.Extensions.AI/TextToSpeech/OpenTelemetryTextToSpeechClient.cs | Bumps semconv version reference to v1.41 in doc comment. |
| src/Libraries/Microsoft.Extensions.AI/SpeechToText/OpenTelemetrySpeechToTextClient.cs | Bumps semconv version reference to v1.41 in doc comment. |
| src/Libraries/Microsoft.Extensions.AI/Embeddings/OpenTelemetryEmbeddingGenerator.cs | Bumps semconv version reference to v1.41 in doc comment. |
| src/Libraries/Microsoft.Extensions.AI/ChatCompletion/OpenTelemetryImageGenerator.cs | Bumps semconv version reference to v1.41 in doc comment. |
| src/Libraries/Microsoft.Extensions.AI/Realtime/OpenTelemetryRealtimeClientSession.cs | Adds streaming request tag, time-to-first-chunk span attribute, reasoning token usage attribute, and updates tool-definition serialization behavior. |
| src/Libraries/Microsoft.Extensions.AI/OpenTelemetryConsts.cs | Adds constants for gen_ai.request.stream, gen_ai.response.time_to_first_chunk, gen_ai.usage.reasoning.output_tokens, and gen_ai.usage.cache_creation.input_tokens. |
| src/Libraries/Microsoft.Extensions.AI/ChatCompletion/OpenTelemetryChatClient.cs | Adds streaming request tag, emits time-to-first-chunk as a span attribute for streaming calls, adds reasoning token usage, and updates tool-definition serialization. |
| src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIClientExtensions.cs | Adds helper to attach OpenAI response attributes to the current “chat” span. |
| src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIChatClient.cs | Populates OpenAI response attributes during streaming updates and non-streaming responses. |
| src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIResponsesChatClient.cs | Populates openai.response.service_tier for Responses API paths. |
| .github/skills/update-otel-genai-conventions/SKILL.md | Introduces a Copilot skill for auditing/planning/implementing/reviewing GenAI semconv updates. |
| .github/skills/update-otel-genai-conventions/references/testing-guide.md | Adds guidance on updating/augmenting tests for GenAI semconv changes. |
| .github/skills/update-otel-genai-conventions/references/review-checklist.md | Adds a review checklist capturing recurring GenAI semconv update pitfalls. |
| .github/skills/update-otel-genai-conventions/references/implementation-patterns.md | Documents implementation patterns for constants/attributes/metrics/events/serialization/sensitive-data gating. |
| .github/skills/update-otel-genai-conventions/references/prompt-template.md | Provides a structured prompt template for delegating updates to Copilot Coding Agent. |
| .github/skills/update-otel-genai-conventions/references/file-inventory.md | Lists common files impacted by GenAI semconv updates. |
| .github/skills/update-otel-genai-conventions/references/change-classification.md | Defines taxonomy for classifying upstream semconv changes and required actions. |
| .github/skills/update-otel-genai-conventions/references/historical-releases.md | Adds historical mapping of semconv releases to prior dotnet/extensions PRs. |
Update the update-otel-genai-conventions skill with reusable PR title and description guidance, including a version-grouped classification table for semantic-convention changes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When _timeToFirstChunkHistogram and _timePerOutputChunkHistogram are both disabled but a streaming activity is being recorded, the per-chunk loop only needs to capture imeToFirstChunk once for the activity tag. Split the loop branch so the activity-only path reads stopwatch.Elapsed exactly once and skips the per-chunk delta/lastChunkElapsed bookkeeping that was only relevant to histogram recording. The trackChunkTimes path is byte-for-byte identical to before. Addresses PR dotnet#7497 review feedback from copilot-pull-request-reviewer (dotnet#7497 (comment)). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Address review feedback from eiriktsarpalis on PR dotnet#7497 (dotnet#7497 (review)): - Collapse Mode 4 (CCA Implementation) to a thin step that defers to a new shared Implementation Procedure used by Modes 2, 4, and 5. Removes the duplicated implementation steps eirik called out as not actually CCA-specific. - Merge Mode 5 (Generate Local Plan) and Mode 6 (Local Implementation) into a single Mode 5: Plan-then-Implement with explicit Phase A (plan) / Phase B (implement) and a user checkpoint between them. Drop SQL-todo prescriptiveness; the runtime decides how to track work items. - Replace Windows-only validation commands with cross-platform guidance via a new references/build-commands.md. Use single-dash arg form throughout (./build.sh -vs AI -build -test, .\build.cmd -vs AI -nolaunch -build -test) — works on both platforms via Arcade's bash --/- normalization, and avoids the PowerShell Param() failure mode of double-dashes through build.cmd. - Drop the Authorship Pattern Evolution section from historical-releases.md (the gold-standard CCA prompt exemplars dotnet#7379/dotnet#7382/dotnet#7322 are already cited in references/prompt-template.md). - Lift PR Title/Description Guidance and Implementation Procedure into their own reference files to keep SKILL.md at the recommended ~200 LoC ceiling. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
tarekgh
left a comment
There was a problem hiding this comment.
Modulo the open comments, LGTM!
- Remove orphan CacheCreationInputTokens const from OpenTelemetryConsts.cs (no emission site exists; defer until a PR adds one)
- Tighten CreateOtelToolDefinition comment to clarify only Description and Parameters are gated by EnableSensitiveData
- Soften OpenTelemetryRealtimeClientSession <summary> to match the <remarks> ('follows ... where applicable')
- Extract GetCurrentChatActivity helper in OpenAIClientExtensions to remove duplicated chat/'chat {name}' activity-name checks
- Rename the streaming-response local trackChunkTimes to recordChunkHistograms; the flag only gates per-chunk histogram recording, while time-to-first-chunk is now also captured for the activity tag in the else branch
- Harden update-otel-genai-conventions skill with rules that would have caught the orphan constant and code-duplication issues during review
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
OpenAI streaming responses repeat ServiceTier and SystemFingerprint on nearly every chunk. The original parameterless AddOpenAIResponseAttributes helper looked up the chat activity and called Activity.SetTag for each value on every chunk, even after the values had already been recorded. Add a streaming-friendly overload that takes the two tag values plus per- stream ref-string caches. Each tag is gated independently so a stream that never reports one of the two values still captures the other on its first non-null arrival; once both are settled the helper returns before performing the activity lookup. Wire the new overload into the two streaming hot paths (FromOpenAIStreamingChatCompletionAsync and the UpdateConversationId local function in FromOpenAIStreamingResponseUpdatesAsync). The non-streaming call sites continue to use the existing 2-arg overload, which now serves one-shot responses exclusively. Addresses dotnet#7497 (comment) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
OpenTelemetryChatClient and OpenTelemetryRealtimeClientSession each carried a private CreateOtelToolDefinition(AITool) helper plus a parallel function-tool POCO (OtelFunction vs RealtimeOtelFunction) whose layout was byte-identical. The helpers were 17 lines apart from a single sensitive-data gating difference (chat gates Description and Parameters via EnableSensitiveData; realtime did the same). Move OtelFunction into a single Common/OtelFunction.cs and add a static OtelFunction.Create(AITool, bool includeOptionalProperties) factory. Both clients now serialize their tool definitions via options.Tools.Select(t => OtelFunction.Create(t, EnableSensitiveData)) and the per-client JsonSerializerContext partials register the shared OtelFunction type. Addresses dotnet#7497 reviewer feedback: dotnet#7497 (comment) dotnet#7497 (comment) No behavioral change. Build clean, 669/669 tests pass per TFM. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…eParts.cs The chat and realtime clients each carried their own copies of OTel message-part POCOs whose layout was byte-identical. Move those to a single shared file and let both per-client JsonSerializerContext partials reference them by their unprefixed names. The previously-extracted Common/OtelFunction.cs is folded into Common/OtelMessageParts.cs as part of this consolidation. Consolidated (9, alongside the previously consolidated OtelFunction): - OtelGenericPart, OtelBlobPart, OtelUriPart, OtelFilePart - OtelToolCallResponsePart - OtelServerToolCallPart<T>, OtelServerToolCallResponsePart<T> - OtelMcpToolCallResponse - OtelMcpToolCall (using IReadOnlyDictionary<string, object?>? for Arguments; the chat-side serialization emission now uses the same `mstcc.Arguments as IReadOnlyDictionary<...> ?? mstcc.Arguments?.ToDictionary(...)` conversion the realtime emission already used) Left split with cross-reference comments in each file: - OtelMessage vs RealtimeOtelMessage (chat adds Name + FinishReason) - OtelToolCallRequestPart vs RealtimeOtelToolCallPart (distinct names) - Six chat-only payload shapes (code interpreter, image generation, MCP approval) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…across clients Merge the per-client `OtelContext` source-gen partials into a single `Common/OtelContext.cs` that registers the union of all message-part types serialized by both the chat and realtime clients. Chat-only and realtime-only serialization POCOs that the merged context must reference are hoisted from nested private types to top-level `internal sealed` classes (still co-located with their owning client file via an SA1402 pragma). Move the chat-side serialization helpers — `SerializeChatMessages`, `_defaultOptions`, `_emptyObject`, `CreateDefaultOptions`, `DeriveModalityFromMediaType`, and `ExtractCodeFromInputs` — into `Common/OtelMessageSerializer.cs`. `OpenTelemetryImageGenerator` and `OpenTelemetrySpeechToTextClient` now call the shared serializer directly rather than reaching into `OpenTelemetryChatClient` for it. `OpenTelemetryRealtimeClientSession` also drops its near-duplicate `DeriveModalityFromMediaType` and routes its three blob/uri/file emission sites through `OtelMessageSerializer.DeriveModalityFromMediaType`. The two implementations were already functionally equivalent across all inputs (null, empty, no slash, malformed, with parameters); the kept span-based form runs in a single pass instead of three sequential `StartsWith` checks. Add `OpenTelemetryLog.RecordOperationError(Activity?, ILogger?, Exception?)` in `Common/` to consolidate the eight-line "set error tag, set error status, log via ILogger" block that was previously duplicated across all seven OpenTelemetry* clients. `OpenTelemetryHostedFileClient.SetErrorStatus` reduces to a one-line expression-bodied delegator. No behavioral change. Build is clean across all TFMs and all 669 tests pass per TFM. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The six OpenTelemetry* gen-ai clients (chat, image, embedding, realtime, speech-to-text, text-to-speech) each created the gen_ai.client.token.usage and gen_ai.client.operation.duration histograms with an identical 12-line block. Factor those into two methods on a new internal Common/OtelMetricHelpers class and reduce each constructor block to two lines. The hosted file client is intentionally excluded: it uses a distinct metric name (files.client.operation.duration) and description, and has no token-usage histogram. No behavioral change. The chat-specific time-to-first-chunk and time-per-output-chunk histograms remain in OpenTelemetryChatClient. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
@stephentoub @tarekgh I pushed some additional commits that address the style and code duplication feedback. The duplication was following the pattern already in place of keeping the chat- and realtime- POCOs, serialization, and helpers separated; so after applying feedback for this PR, I also proceeded with commits 7ddadfa, b0574d3, and f1432e3 -- those do a bunch more de-duplication across chat and realtime. I also edited the skill to reflect this guidance so its own implementation and review capabilities are consistent with the outcomes after applying this feedback. These are scoped to the cases where the chat and realtime sides were truly identical (or trivially so); intentional divergences stay put. Each commit builds and passes tests on its own.
If you'd prefer I dial this PR back by removing those 3 extra commits, happy to do so. |
|
One inconsistency note, |
…gGenerator The other six OpenTelemetry* clients call OpenTelemetryLog.RecordOperationError unconditionally, which logs the exception via ILogger regardless of whether an ActivitySource listener is attached. The embedding generator was the lone holdout, gating the call inside an `if (activity is not null)` block so that the exception log was silently dropped when the consumer wired up logging but not tracing. Move the call out of that block so embedding errors are logged consistently with the other clients; the activity-only tag writes that follow remain inside the gate. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Thanks, @tarekgh. I went ahead and fixed that inconsistency. |
* Add update-otel-genai-conventions skill Add a multi-mode Copilot skill for analyzing OpenTelemetry semantic-conventions releases with gen-ai changes and producing compensating change plans for dotnet/extensions. The skill supports 7 modes: 1. Audit current implementation against latest conventions 2. Autopilot one-shot plan and implementation 3. Generate CCA prompt for delegating to Copilot on github.com 4. CCA implementation from a prompt referencing this skill 5. Generate local plan with SQL-tracked todos 6. Local implementation after plan generation 7. Review of convention change PRs Includes reference files for: - File inventory of all OTel instrumentation files - Change classification taxonomy - Implementation code patterns - Testing guide with assertion patterns - Review checklist from past PR feedback - CCA prompt template - Historical release-to-PR mapping Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Align OpenTelemetry GenAI instrumentation to v1.40 Update the realtime OpenTelemetry client documentation to reference semantic conventions v1.40 and add the cache creation input token constant introduced by the v1.40 GenAI conventions. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Emit OpenAI response telemetry attributes Map OpenAI-specific response semantic convention attributes in the OpenAI provider package and document the provider-specific pattern in the OTel gen-ai skill. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Update GenAI telemetry for v1.41 conventions Emit v1.41 streaming and reasoning token attributes for chat and realtime telemetry, update tool definition serialization behavior, and bump GenAI semantic convention references. Refresh the update-otel-genai-conventions skill guidance for durable review and testing patterns without adding release-specific details. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Check existing PRs before OTel convention audits Update the update-otel-genai-conventions skill so audit and local-plan workflows search open dotnet/extensions PRs for likely matching GenAI/OpenTelemetry convention updates before proceeding. If a matching PR exists, the skill reports the PR information and stops instead of producing a duplicate audit or plan. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Document PR metadata for OTel convention updates Update the update-otel-genai-conventions skill with reusable PR title and description guidance, including a version-grouped classification table for semantic-convention changes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Skip per-chunk delta math when only tracing is enabled When _timeToFirstChunkHistogram and _timePerOutputChunkHistogram are both disabled but a streaming activity is being recorded, the per-chunk loop only needs to capture imeToFirstChunk once for the activity tag. Split the loop branch so the activity-only path reads stopwatch.Elapsed exactly once and skips the per-chunk delta/lastChunkElapsed bookkeeping that was only relevant to histogram recording. The trackChunkTimes path is byte-for-byte identical to before. Addresses PR #7497 review feedback from copilot-pull-request-reviewer (#7497 (comment)). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Restructure update-otel-genai-conventions skill per review feedback Address review feedback from eiriktsarpalis on PR #7497 (#7497 (review)): - Collapse Mode 4 (CCA Implementation) to a thin step that defers to a new shared Implementation Procedure used by Modes 2, 4, and 5. Removes the duplicated implementation steps eirik called out as not actually CCA-specific. - Merge Mode 5 (Generate Local Plan) and Mode 6 (Local Implementation) into a single Mode 5: Plan-then-Implement with explicit Phase A (plan) / Phase B (implement) and a user checkpoint between them. Drop SQL-todo prescriptiveness; the runtime decides how to track work items. - Replace Windows-only validation commands with cross-platform guidance via a new references/build-commands.md. Use single-dash arg form throughout (./build.sh -vs AI -build -test, .\build.cmd -vs AI -nolaunch -build -test) — works on both platforms via Arcade's bash --/- normalization, and avoids the PowerShell Param() failure mode of double-dashes through build.cmd. - Drop the Authorship Pattern Evolution section from historical-releases.md (the gold-standard CCA prompt exemplars #7379/#7382/#7322 are already cited in references/prompt-template.md). - Lift PR Title/Description Guidance and Implementation Procedure into their own reference files to keep SKILL.md at the recommended ~200 LoC ceiling. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address stylistic PR feedback - Remove orphan CacheCreationInputTokens const from OpenTelemetryConsts.cs (no emission site exists; defer until a PR adds one) - Tighten CreateOtelToolDefinition comment to clarify only Description and Parameters are gated by EnableSensitiveData - Soften OpenTelemetryRealtimeClientSession <summary> to match the <remarks> ('follows ... where applicable') - Extract GetCurrentChatActivity helper in OpenAIClientExtensions to remove duplicated chat/'chat {name}' activity-name checks - Rename the streaming-response local trackChunkTimes to recordChunkHistograms; the flag only gates per-chunk histogram recording, while time-to-first-chunk is now also captured for the activity tag in the else branch - Harden update-otel-genai-conventions skill with rules that would have caught the orphan constant and code-duplication issues during review Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Dedup per-chunk service_tier and system_fingerprint tag writes OpenAI streaming responses repeat ServiceTier and SystemFingerprint on nearly every chunk. The original parameterless AddOpenAIResponseAttributes helper looked up the chat activity and called Activity.SetTag for each value on every chunk, even after the values had already been recorded. Add a streaming-friendly overload that takes the two tag values plus per- stream ref-string caches. Each tag is gated independently so a stream that never reports one of the two values still captures the other on its first non-null arrival; once both are settled the helper returns before performing the activity lookup. Wire the new overload into the two streaming hot paths (FromOpenAIStreamingChatCompletionAsync and the UpdateConversationId local function in FromOpenAIStreamingResponseUpdatesAsync). The non-streaming call sites continue to use the existing 2-arg overload, which now serves one-shot responses exclusively. Addresses #7497 (comment) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Consolidate CreateOtelToolDefinition across chat and realtime clients OpenTelemetryChatClient and OpenTelemetryRealtimeClientSession each carried a private CreateOtelToolDefinition(AITool) helper plus a parallel function-tool POCO (OtelFunction vs RealtimeOtelFunction) whose layout was byte-identical. The helpers were 17 lines apart from a single sensitive-data gating difference (chat gates Description and Parameters via EnableSensitiveData; realtime did the same). Move OtelFunction into a single Common/OtelFunction.cs and add a static OtelFunction.Create(AITool, bool includeOptionalProperties) factory. Both clients now serialize their tool definitions via options.Tools.Select(t => OtelFunction.Create(t, EnableSensitiveData)) and the per-client JsonSerializerContext partials register the shared OtelFunction type. Addresses #7497 reviewer feedback: #7497 (comment) #7497 (comment) No behavioral change. Build clean, 669/669 tests pass per TFM. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Consolidate identical OTel serialization POCOs into Common/OtelMessageParts.cs The chat and realtime clients each carried their own copies of OTel message-part POCOs whose layout was byte-identical. Move those to a single shared file and let both per-client JsonSerializerContext partials reference them by their unprefixed names. The previously-extracted Common/OtelFunction.cs is folded into Common/OtelMessageParts.cs as part of this consolidation. Consolidated (9, alongside the previously consolidated OtelFunction): - OtelGenericPart, OtelBlobPart, OtelUriPart, OtelFilePart - OtelToolCallResponsePart - OtelServerToolCallPart<T>, OtelServerToolCallResponsePart<T> - OtelMcpToolCallResponse - OtelMcpToolCall (using IReadOnlyDictionary<string, object?>? for Arguments; the chat-side serialization emission now uses the same `mstcc.Arguments as IReadOnlyDictionary<...> ?? mstcc.Arguments?.ToDictionary(...)` conversion the realtime emission already used) Left split with cross-reference comments in each file: - OtelMessage vs RealtimeOtelMessage (chat adds Name + FinishReason) - OtelToolCallRequestPart vs RealtimeOtelToolCallPart (distinct names) - Six chat-only payload shapes (code interpreter, image generation, MCP approval) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Consolidate OTel serialization helpers and operation-error recording across clients Merge the per-client `OtelContext` source-gen partials into a single `Common/OtelContext.cs` that registers the union of all message-part types serialized by both the chat and realtime clients. Chat-only and realtime-only serialization POCOs that the merged context must reference are hoisted from nested private types to top-level `internal sealed` classes (still co-located with their owning client file via an SA1402 pragma). Move the chat-side serialization helpers — `SerializeChatMessages`, `_defaultOptions`, `_emptyObject`, `CreateDefaultOptions`, `DeriveModalityFromMediaType`, and `ExtractCodeFromInputs` — into `Common/OtelMessageSerializer.cs`. `OpenTelemetryImageGenerator` and `OpenTelemetrySpeechToTextClient` now call the shared serializer directly rather than reaching into `OpenTelemetryChatClient` for it. `OpenTelemetryRealtimeClientSession` also drops its near-duplicate `DeriveModalityFromMediaType` and routes its three blob/uri/file emission sites through `OtelMessageSerializer.DeriveModalityFromMediaType`. The two implementations were already functionally equivalent across all inputs (null, empty, no slash, malformed, with parameters); the kept span-based form runs in a single pass instead of three sequential `StartsWith` checks. Add `OpenTelemetryLog.RecordOperationError(Activity?, ILogger?, Exception?)` in `Common/` to consolidate the eight-line "set error tag, set error status, log via ILogger" block that was previously duplicated across all seven OpenTelemetry* clients. `OpenTelemetryHostedFileClient.SetErrorStatus` reduces to a one-line expression-bodied delegator. No behavioral change. Build is clean across all TFMs and all 669 tests pass per TFM. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Extract gen-ai histogram creation to a shared helper The six OpenTelemetry* gen-ai clients (chat, image, embedding, realtime, speech-to-text, text-to-speech) each created the gen_ai.client.token.usage and gen_ai.client.operation.duration histograms with an identical 12-line block. Factor those into two methods on a new internal Common/OtelMetricHelpers class and reduce each constructor block to two lines. The hosted file client is intentionally excluded: it uses a distinct metric name (files.client.operation.duration) and description, and has no token-usage histogram. No behavioral change. The chat-specific time-to-first-chunk and time-per-output-chunk histograms remain in OpenTelemetryChatClient. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Always log gen_ai.client.operation.exception in OpenTelemetryEmbeddingGenerator The other six OpenTelemetry* clients call OpenTelemetryLog.RecordOperationError unconditionally, which logs the exception via ILogger regardless of whether an ActivitySource listener is attached. The embedding generator was the lone holdout, gating the call inside an `if (activity is not null)` block so that the exception log was silently dropped when the consumer wired up logging but not tracing. Move the call out of that block so embedding errors are logged consistently with the other clients; the activity-only tag writes that follow remain inside the gate. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Summary
update-otel-genai-conventionsCopilot skill for auditing, planning, implementing, and reviewing GenAI semantic-convention updatesAfter implementing the Copilot skill, I used it to:
/update-otel-genai-conventions to ensure everything is aligned to v1.40.0, which resulted in ec3c81b/update-otel-genai-conventions Audit all past releases of opentelemetry semantic conventions (up to v1.40) looking for any discrepancies, which resulted in c1a9949 (catching the missing openai entries)/update-otel-genai-conventions to v1.41.0producing 30e8197Semantic-conventions changes
gen_ai.openai.response.service_tierintroduced for OpenAI response telemetryopenai.response.service_tierattribute from the OpenAI provider package for Chat Completions and Responses API telemetry.gen_ai.openai.response.system_fingerprintintroduced for OpenAI response telemetryopenai.response.system_fingerprintattribute from Chat Completions telemetry. The Responses API mapping is omitted because the current SDK response type does not expose it.gen_ai.openai.*toopenai.*openai.*names for the new OpenAI response mappings.gen_ai.usage.cache_creation.input_tokensaddedgen_ai.tool.definitionscapture behavior enhancedgen_ai.usage.reasoning.output_tokensaddedUsageDetails.ReasoningTokenCount.gen_ai.request.streamandgen_ai.response.time_to_first_chunkadded for streaming inference spansgen_ai.client.operation.exceptionevent definedOpenTelemetryLog.OperationException; no additional changes needed.invoke_workflowoperation name addedgen_ai.client.operation.time_to_first_chunkandgen_ai.client.operation.time_per_output_chunkmetrics addedgen_ai.response.modeladded to embeddings spansOpenTelemetryEmbeddingGenerator; no additional changes needed.gen_ai.agent.versionincluded in theinvoke_agentsplit