The Microsoft.Agents.A365.Observability.Extensions.OpenAI package provides OpenTelemetry tracing integration for the OpenAI SDK. It includes a span processor that intercepts OpenAI API telemetry and extension methods for working with tool calls.
Microsoft.Agents.A365.Observability.Extensions.OpenAI
├── Public API
│ ├── BuilderExtensions # Extension method for Builder
│ └── ChatToolCallExtensions # Tool call helper extensions
├── Internal
│ ├── OpenAISpanProcessor # Span processing logic
│ └── OpenAITelemetryConstants # Telemetry constants
Source: BuilderExtensions.cs
Provides the WithOpenAI() extension method for the observability Builder.
// Configure observability with OpenAI support
new Builder(services, configuration)
.WithOpenAI()
.Build();
// Without related sources (manual configuration)
new Builder(services, configuration)
.WithOpenAI(enableRelatedSources: false)
.Build();Implementation:
public static Builder WithOpenAI(this Builder builder, bool enableRelatedSources = true)
{
if (enableRelatedSources)
{
AppContext.SetSwitch("OpenAI.Experimental.EnableOpenTelemetry", true);
builder.Services.AddOpenTelemetry()
.WithTracing(tracing => tracing
.AddSource(OpenAITelemetryConstants.OpenAISourceWildcard)
.AddProcessor(new OpenAISpanProcessor()));
}
return builder;
}Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
enableRelatedSources |
bool |
true |
Enable OpenTelemetry for OpenAI SDK and add related sources |
Source: OpenAISpanProcessor.cs
A BaseProcessor<Activity> that processes spans from the OpenAI SDK activity source (OpenAI.*).
Processed Operations:
chat- Chat completions
internal class OpenAISpanProcessor : BaseProcessor<Activity>
{
private static readonly string TargetSourceName = OpenAITelemetryConstants.OpenAISource;
public override void OnStart(Activity activity)
{
}
public override void OnEnd(Activity activity)
{
if (activity.Source.Name.StartsWith(TargetSourceName))
{
var tags = activity.Tags.ToDictionary(kv => kv.Key, kv => kv.Value);
if (tags.TryGetValue(OpenTelemetryConstants.GenAiOperationNameKey, out var operationName))
{
switch (operationName)
{
case OpenAITelemetryConstants.ChatOperation:
// Span emitted by OpenAI SDK follows Microsoft Agent 365 schema,
// so no modification needed.
// Placeholder for any plumbing if needed in the future.
break;
}
}
}
}
}Source: ChatToolCallExtensions.cs
Extension methods for working with OpenAI ChatToolCall objects in telemetry contexts.
// Extract telemetry data from tool calls
var toolCall = response.ToolCalls.First();
var telemetryData = toolCall.ToTelemetryData();
// Record tool call in current span
Activity.Current?.RecordToolCall(toolCall);Extension Methods:
| Method | Description |
|---|---|
ToTelemetryData() |
Converts tool call to telemetry-friendly format |
RecordToolCall() |
Records tool call details in Activity |
GetToolName() |
Extracts tool name from call |
GetToolArguments() |
Extracts tool arguments as dictionary |
Source: OpenAITelemetryConstants.cs
Constants for OpenAI telemetry integration.
internal static class OpenAITelemetryConstants
{
// Operation Names
public const string ChatOperation = "chat";
// Activity Source Names
public const string OpenAISource = "OpenAI";
public const string OpenAISourceWildcard = "OpenAI.*";
}The package extends the core Builder and enables OpenTelemetry for the OpenAI SDK:
public static class BuilderExtensions
{
public static Builder WithOpenAI(this Builder builder, bool enableRelatedSources = true)
{
if (enableRelatedSources)
{
// Enable OpenAI SDK's experimental OpenTelemetry support
AppContext.SetSwitch("OpenAI.Experimental.EnableOpenTelemetry", true);
builder.Services.AddOpenTelemetry()
.WithTracing(tracing => tracing
.AddSource(OpenAITelemetryConstants.OpenAISourceWildcard)
.AddProcessor(new OpenAISpanProcessor()));
}
return builder;
}
}The processor monitors OpenAI spans and can enrich them if needed:
internal class OpenAISpanProcessor : BaseProcessor<Activity>
{
public override void OnEnd(Activity activity)
{
if (activity.Source.Name.StartsWith("OpenAI"))
{
// OpenAI SDK already follows Agent365 schema
// Processor provides extension point for future modifications
}
}
}┌─────────────────────────────┐
│ Azure OpenAI SDK │
│ │
│ ChatClient.CompleteAsync() │
│ EmbeddingClient.Generate() │
└──────────────┬──────────────┘
│
▼ Activity Source Events
┌─────────────────────────────┐
│ Azure.AI.OpenAI │
│ │
│ Creates Activity/Span │
│ with OpenAI-specific tags │
└──────────────┬──────────────┘
│
▼ OnEnd callback
┌─────────────────────────────┐
│ OpenAISpanProcessor │
│ │
│ 1. Check if OpenAI source │
│ 2. Normalize tag names │
│ 3. Extract token usage │
│ 4. Process response data │
└──────────────┬──────────────┘
│
▼ Enriched Activity
┌─────────────────────────────┐
│ OpenTelemetry Exporter │
│ │
│ Spans follow Agent365 │
│ schema standards │
└─────────────────────────────┘
| OpenAI SDK Tag | Agent365 Tag |
|---|---|
prompt_tokens |
gen_ai.usage.input_tokens |
completion_tokens |
gen_ai.usage.output_tokens |
model |
gen_ai.request.model |
finish_reason |
gen_ai.response.finish_reasons |
src/Observability/Extensions/OpenAI/
├── BuilderExtensions.cs # Builder extension
├── OpenAISpanProcessor.cs # Span processor
├── OpenAITelemetryConstants.cs # Constants
├── ChatToolCallExtensions.cs # Tool call extensions
├── Microsoft.Agents.A365.Observability.Extensions.OpenAI.csproj
└── docs/
└── design.md # This file
| Package | Purpose |
|---|---|
Microsoft.Agents.A365.Observability.Runtime |
Core observability |
Azure.AI.OpenAI |
Azure OpenAI SDK |
// Program.cs
builder.Services.AddOpenTelemetry()
.WithTracing(tracerBuilder =>
{
new Builder(builder.Services, builder.Configuration)
.WithOpenAI()
.Build();
});var client = new AzureOpenAIClient(
new Uri(endpoint),
new AzureKeyCredential(apiKey)
);
var chatClient = client.GetChatClient(deploymentName);
// Spans are automatically captured and processed
var response = await chatClient.CompleteChatAsync(messages);var response = await chatClient.CompleteChatAsync(messages, options);
foreach (var toolCall in response.Value.ToolCalls)
{
// Record tool call in telemetry
Activity.Current?.RecordToolCall(toolCall);
// Execute tool
var result = await ExecuteToolAsync(toolCall);
// Add result to messages for next completion
messages.Add(new ToolChatMessage(toolCall.Id, result));
}// Use multiple framework extensions together
new Builder(services, configuration)
.WithAgentFramework()
.WithSemanticKernel()
.WithOpenAI()
.Build();