Skip to content

Update OpenTelemetry gen-ai conventions through v1.41#7497

Merged
jeffhandley merged 16 commits intodotnet:mainfrom
jeffhandley:jeffhandley/otel-genai-1.41.0
May 1, 2026
Merged

Update OpenTelemetry gen-ai conventions through v1.41#7497
jeffhandley merged 16 commits intodotnet:mainfrom
jeffhandley:jeffhandley/otel-genai-1.41.0

Conversation

@jeffhandley
Copy link
Copy Markdown
Member

@jeffhandley jeffhandley commented Apr 29, 2026

Summary

  • Add the update-otel-genai-conventions Copilot skill for auditing, planning, implementing, and reviewing GenAI semantic-convention updates
  • Align Microsoft.Extensions.AI OpenTelemetry GenAI instrumentation through semantic-conventions v1.41
  • Emit new streaming and reasoning-token attributes and update tool-definition serialization behavior
  • Add OpenAI-specific response telemetry mappings in the OpenAI provider package

After implementing the Copilot skill, I used it to:

  1. /update-otel-genai-conventions to ensure everything is aligned to v1.40.0, which resulted in ec3c81b
  2. /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)
  3. /update-otel-genai-conventions to v1.41.0 producing 30e8197

Semantic-conventions changes

Version Indicator Semantic-conventions change Classification Compensating change / rationale
v1.28 🔴 gen_ai.openai.response.service_tier introduced for OpenAI response telemetry New required provider-specific attribute Emits the current openai.response.service_tier attribute from the OpenAI provider package for Chat Completions and Responses API telemetry.
v1.29 🔴 gen_ai.openai.response.system_fingerprint introduced for OpenAI response telemetry New provider-specific attribute Emits the current openai.response.system_fingerprint attribute from Chat Completions telemetry. The Responses API mapping is omitted because the current SDK response type does not expose it.
v1.37 🔴 OpenAI-specific attributes renamed from gen_ai.openai.* to openai.* Attribute rename Uses the current openai.* names for the new OpenAI response mappings.
v1.40 🟢 gen_ai.usage.cache_creation.input_tokens added Constant not yet emitted No constant added and no emission site introduced because the current usage abstraction exposes cache-read tokens but not cache-creation tokens.
v1.41 🔴 gen_ai.tool.definitions capture behavior enhanced Schema / behavioral change Emits simplified tool definitions when sensitive data capture is disabled and includes optional description/schema details only when sensitive data capture is enabled.
v1.41 🔴 gen_ai.usage.reasoning.output_tokens added New usage token attribute Emits reasoning output token counts for chat and realtime spans from UsageDetails.ReasoningTokenCount.
v1.41 🔴 gen_ai.request.stream and gen_ai.response.time_to_first_chunk added for streaming inference spans New required/recommended streaming attributes Emits streaming request and time-to-first-chunk span attributes for chat and realtime streaming operations.
v1.41 🟢 gen_ai.client.operation.exception event defined Already implemented Already emitted via OpenTelemetryLog.OperationException; no additional changes needed.
v1.41 🟢 invoke_workflow operation name added Already implemented Already supported by the existing function invocation telemetry; no additional changes needed.
v1.41 🟢 gen_ai.client.operation.time_to_first_chunk and gen_ai.client.operation.time_per_output_chunk metrics added Already implemented Already implemented for chat streaming metrics; no additional changes needed.
v1.41 🟢 gen_ai.response.model added to embeddings spans Already implemented Already emitted by OpenTelemetryEmbeddingGenerator; no additional changes needed.
v1.41 🟢 gen_ai.agent.version included in the invoke_agent split No local source No local source exists to populate agent version, so no unused constant or emission was added.

jeffhandley and others added 5 commits April 28, 2026 22:38
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>
@jeffhandley jeffhandley requested a review from a team as a code owner April 29, 2026 07:46
Copilot AI review requested due to automatic review settings April 29, 2026 07:46
@jeffhandley jeffhandley requested a review from a team as a code owner April 29, 2026 07:46
@github-actions github-actions Bot added the area-ai Microsoft.Extensions.AI libraries label Apr 29, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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, and gen_ai.usage.reasoning.output_tokens.
  • Update tool-definition serialization to include tool name consistently while gating sensitive fields (description, parameters) behind EnableSensitiveData.
  • 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.

Comment thread src/Libraries/Microsoft.Extensions.AI/ChatCompletion/OpenTelemetryChatClient.cs Outdated
jeffhandley and others added 2 commits April 29, 2026 01:25
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>
Comment thread .github/skills/update-otel-genai-conventions/SKILL.md Outdated
Comment thread .github/skills/update-otel-genai-conventions/SKILL.md
Comment thread .github/skills/update-otel-genai-conventions/SKILL.md Outdated
Comment thread .github/skills/update-otel-genai-conventions/references/historical-releases.md Outdated
Comment thread .github/skills/update-otel-genai-conventions/SKILL.md Outdated
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>
Comment thread src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIClientExtensions.cs Outdated
Comment thread src/Libraries/Microsoft.Extensions.AI/ChatCompletion/OpenTelemetryChatClient.cs Outdated
Comment thread src/Libraries/Microsoft.Extensions.AI/ChatCompletion/OpenTelemetryChatClient.cs Outdated
Comment thread src/Libraries/Microsoft.Extensions.AI/OpenTelemetryConsts.cs Outdated
Comment thread src/Libraries/Microsoft.Extensions.AI.OpenAI/OpenAIChatClient.cs Outdated
Copy link
Copy Markdown
Member

@tarekgh tarekgh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Modulo the open comments, LGTM!

jeffhandley and others added 6 commits April 30, 2026 18:23
- 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>
@jeffhandley
Copy link
Copy Markdown
Member Author

jeffhandley commented May 1, 2026

@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.

  • 10 byte-identical OTel POCOs unified into Common/OtelMessageParts.cs (the just-extracted OtelFunction is folded in there too); OtelMcpToolCall.Arguments standardized to
    IReadOnlyDictionary.
  • SerializeChatMessages, DeriveModalityFromMediaType, and RecordOperationError (the 8-line AddTag(Error.Type) + SetStatus + OperationException block that was repeated in
    all 6 gen-ai clients) now live in Common/OtelMessageSerializer.cs / Common/OtelMetricHelpers.cs.
  • Token-usage and operation-duration Histogram creation factored into OtelMetricHelpers factories — every gen-ai client constructor used the same name/unit/description/advice.
  • Minor behavioral note for realtime telemetry: realtime serialization now goes through the shared JsonSerializerOptions (with UnsafeRelaxedJsonEscaping), matching chat. Previously realtime was serializing against a bare JsonSerializerContext, so <, >, &, and non-ASCII characters in realtime message content were HTML-entity-escaped in telemetry output while chat's was not. Output now matches chat.

If you'd prefer I dial this PR back by removing those 3 extra commits, happy to do so.

@jeffhandley jeffhandley requested review from stephentoub and tarekgh May 1, 2026 07:59
Copy link
Copy Markdown
Member

@tarekgh tarekgh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The delta commits LGTM

@tarekgh
Copy link
Copy Markdown
Member

tarekgh commented May 1, 2026

One inconsistency note, OpenTelemetryEmbeddingGenerator gates error logging on activity is not null while others don't. This existed before this PR.

…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>
@jeffhandley
Copy link
Copy Markdown
Member Author

Thanks, @tarekgh. I went ahead and fixed that inconsistency.

@jeffhandley jeffhandley enabled auto-merge (squash) May 1, 2026 21:52
@jeffhandley jeffhandley merged commit 6daeea1 into dotnet:main May 1, 2026
6 checks passed
@jeffhandley jeffhandley deleted the jeffhandley/otel-genai-1.41.0 branch May 1, 2026 21:57
jeffhandley added a commit that referenced this pull request May 1, 2026
* 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-ai Microsoft.Extensions.AI libraries

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants