.NET: Bugfix Emit execute_tool spans when Auto Wiring via Agent UseOpenTelemetry#6667
Merged
peibekwe merged 1 commit intoJun 22, 2026
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
This PR fixes missing execute_tool <name> spans when .NET agents use UseOpenTelemetry() with in-proc tools by changing the decorator ordering so OpenTelemetry runs below FunctionInvokingChatClient (FICC). It does so by introducing an inert “slot” chat-client decorator that can be activated once, ensuring tool spans are emitted on the agent ActivitySource and parented under invoke_agent.
Changes:
- Added
DeferredOpenTelemetryChatClient, an inertDelegatingChatClientthat can be activated to insertOpenTelemetryChatClientbelow FICC. - Updated
WithDefaultAgentMiddlewareto always place the deferred slot as the innermost decorator (directly above the leaf chat client). - Updated
OpenTelemetryAgentto activate the deferred slot at construction time (and removed the previous per-run ChatClientFactory wrapping), plus added/adjusted unit tests including coverage forexecute_toolspans.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| dotnet/src/Microsoft.Agents.AI/OpenTelemetryAgent.cs | Activates the below-FICC OTel slot once during construction; removes per-run wiring so options flow through unchanged. |
| dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientExtensions.cs | Ensures the deferred OTel slot is always present as the innermost decorator in the default agent chat-client pipeline. |
| dotnet/src/Microsoft.Agents.AI/ChatClient/DeferredOpenTelemetryChatClient.cs | New inert/activatable decorator that swaps its target to an OpenTelemetryChatClient wrapper when activated. |
| dotnet/tests/Microsoft.Agents.AI.UnitTests/OpenTelemetryAgentTests.cs | Updates expectations around options passing and adds tests proving execute_tool spans are emitted + slot activation behavior. |
e2656b0 to
5d25408
Compare
…InvokingChatClient OpenTelemetryAgent auto-wired OpenTelemetryChatClient above FICC, producing OTel(FICC(leaf)). FICC resolved its ActivitySource at construction time as null, so execute_tool spans were never emitted for tool-calling agents. This repositions OTel below FICC, producing FICC(OTel(leaf)), via a deferred NoOp slot pre-placed as the innermost decorator in WithDefaultAgentMiddleware and activated once at the agent level. - Add internal DeferredOpenTelemetryChatClient: inert DelegatingChatClient whose Activate(sourceName) swaps its target to inner.AsBuilder().UseOpenTelemetry().Build(). - WithDefaultAgentMiddleware always registers the slot innermost so it lands below FICC. - OpenTelemetryAgent activates the slot once in its constructor and forwards run options straight through, removing the per-run ChatClientFactory outer wrap. - Add and update unit tests, including a proof that execute_tool spans are emitted on the agent source and parented under invoke_agent.
5d25408 to
d26fe0c
Compare
peibekwe
approved these changes
Jun 22, 2026
westey-m
approved these changes
Jun 22, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation & Context
When an agent is instrumented with
UseOpenTelemetry()and invokes in-proc tools, noexecute_tool <name>span was emitted. The auto-wire placedOpenTelemetryChatClientaboveFunctionInvokingChatClient(FICC), producingOTel(FICC(leaf)). With OpenTelemetry above FICC, thechatspan stays open during the tool loop, so FICC sees noinvoke_agentsource and skips the tool span. Tool calls only showed up insidegen_ai.output.messages, never as their own span, breaking parity with the GenAI semantic conventions and with Python.This repositions OpenTelemetry below FICC, producing
FICC(OTel(leaf)), so the tool span is emitted on the agent source.Description & Review Guide
What are the major changes?
DeferredOpenTelemetryChatClient: an inertDelegatingChatClientslot whoseActivate(sourceName)swaps its target toinner.AsBuilder().UseOpenTelemetry(sourceName).Build(). Idempotent and lock guarded.WithDefaultAgentMiddlewarealways registers this slot as the innermost decorator, so it lands directly below FICC.OpenTelemetryAgentactivates the slot once at construction (same guards as before: auto wire enabled, innerChatClientAgentreachable, notUseProvidedChatClientAsIs, not already instrumented) and forwards run options straight through. The previous per-runChatClientFactoryouter wrap was removed.execute_toolis emitted on the agent source, parented underinvoke_agent, plus a focused test for the inert/active slot.What is the impact of these changes?
execute_tool <name>spans now appear for tool-calling agents that useUseOpenTelemetry().FICC(OpenTelemetry(leaf))pipeline (verified across placements: auto wire, bring your own OpenTelemetry chat client, and hand-wired agent all produceinvoke_agentoverchat+execute_toolwith no doubled spans).chatspan now fires per model call rather than once per run. Bringing your own OpenTelemetry chat client does not double the chat span; the slot stays inert when anOpenTelemetryChatClientis already present.Why below FICC? FICC resolves the tool span source per call as
invokeAgentActivity?.Source ?? _activitySource. With OpenTelemetry below FICC thechatspan closes before the tool loop, soActivity.Currentis theinvoke_agentspan and its source drivesexecute_tool.Related Issue
Fixes #6666
Contribution Checklist