.NET SDK
Package: Progress.Observability.Instrumentation
Overview
The .NET SDK instruments AI agents built with IChatClient from Microsoft.Extensions.AI or IAgent from the Microsoft Agent Framework. It integrates directly with the .NET Activity/OpenTelemetry pipeline.
Use this SDK when your agent is built in C# and uses:
IChatClientfrom Microsoft.Extensions.AI (OpenAI, Azure OpenAI, and others)IAgentfrom the Microsoft Agent Framework- MCP tools via
McpClientTool
Core capabilities:
- Automatic tracing of LLM chat completions and streaming responses
- Tool call instrumentation via
AddToolObservability() - Custom spans with
ObservabilityActivitySource - Tag support for filtering in the platform
- Environment variable overrides for deployment flexibility
Unlike the Python SDK, the .NET SDK does not auto-instrument third-party libraries. Instead, you explicitly wrap your IChatClient or IAgent with observability.
Installation
dotnet add <YourProject.csproj> package Progress.Observability.Instrumentation
From a local NuGet source:
# Copy the .nupkg file to your local source folder (for example, c:\packages)
dotnet nuget add source c:\packages --name LocalFeed
# Verify the source exists
dotnet nuget list source
# Add the package from the local source
dotnet add package Progress.Observability.Instrumentation --source LocalFeed
Initialization
The initialization approach depends on whether you use IChatClient or IAgent.
IChatClient
Call AddObservability() on your chat client:
using Microsoft.Extensions.AI;
using Progress.Observability.Extensions.AI;
IChatClient chatClient = new OpenAI.Chat.ChatClient("gpt-4.1-mini", openAIApiKey)
.AsIChatClient();
// Add Progress Observability for LLM calls
chatClient = chatClient.AddObservability(options => {
options.AppName = "My Agent";
options.ApiKey = "ac_p_001_.....";
});
IAgent
Call ObservabilityTracer.Initialize() and then UseOpenTelemetry():
using Progress.Observability.Extensions.AI;
var agentName = "Awesome AI Agent";
// Initialize the observability tracer
ObservabilityTracer.Initialize(new ObservabilityOptions()
{
AppName = agentName,
ApiKey = "ac_p_001_.....",
AdditionalAttributes = new Dictionary<string, object>()
{
{ "customer.id", Guid.NewGuid().ToString() },
{ "customer.quota", 2026 }
},
AdditionalTags = new List<string>()
{
"customer.id:12345",
"NewCustomer"
}
});
// Create tools (optionally add tool observability)
List<AITool> tools = [AIFunctionFactory.Create(MyToolFunction)];
// Build the agent with OpenTelemetry
var aiAgent = new AzureOpenAIClient(new(endpoint), new AzureKeyCredential(key))
.GetChatClient("gpt-4.1")
.AsAIAgent(
instructions: "You are a helpful assistant.",
name: agentName,
tools: tools)
.AsBuilder()
.UseOpenTelemetry(
sourceName: ObservabilityTracer.SourceName,
configure: agent => {
agent.EnableSensitiveData = true;
})
.Build();
Configuration
Options
| Property | Type | Default | Description |
|---|---|---|---|
| AppName | string | Agent | Application name shown in the platform |
| ApiKey | string | empty | Your API key (required) |
| Endpoint | string | https://collector.observability.progress.com:443 | Collector endpoint URL |
| RecordInputs | bool | true | Include request messages in spans |
| RecordOutputs | bool | true | Include response content in spans |
| Debug | bool | false | Enable debug logging |
| AdditionalAttributes | Dictionary<string, object> | empty | Custom attributes on all spans |
| AdditionalTags | List | empty | Tags for filtering (max 200 chars) |
Environment Variables
PowerShell:
# PowerShell
$env:PROGRESS__OBSERVABILITY__APPNAME="Weather Agent"
$env:PROGRESS__OBSERVABILITY__APIKEY="ac_p_001_....."
$env:PROGRESS__OBSERVABILITY__ENDPOINT="https://collector.observability.progress.com:443"
Bash:
# Bash / macOS / Linux
export PROGRESS__OBSERVABILITY__APPNAME="Weather Agent"
export PROGRESS__OBSERVABILITY__APIKEY="ac_p_001_....."
export PROGRESS__OBSERVABILITY__ENDPOINT="https://collector.observability.progress.com:443"
| Variable | Overrides |
|---|---|
| PROGRESS__OBSERVABILITY__APPNAME | AppName |
| PROGRESS__OBSERVABILITY__APIKEY | ApiKey |
| PROGRESS__OBSERVABILITY__ENDPOINT | Endpoint |
The double-underscore (__) separator follows the standard .NET configuration binding convention. Do not use single underscores.
IChatClient Usage
Full setup with tools and all configuration options:
using Microsoft.Extensions.AI;
using Progress.Observability.Extensions.AI;
IChatClient chatClient = new OpenAI.Chat.ChatClient("gpt-4.1-mini", openAIApiKey)
.AsIChatClient();
chatClient = chatClient.AddObservability(options => {
options.AppName = "Weather Agent";
options.ApiKey = "ac_p_001_.....";
options.Endpoint = "https://collector.observability.progress.com:443";
options.RecordInputs = true; // include request messages (default: true)
options.RecordOutputs = true; // include response content (default: true)
options.Debug = false; // enable debug logging (default: false)
options.AdditionalAttributes = new Dictionary<string, object>
{
{ "project.id", 2182374 },
{ "environment", "dev" }
};
options.AdditionalTags = new List<string>
{
"customer.id:12345",
"NewCustomer"
};
});
// Enable function invocation wrapping
chatClient = new ChatClientBuilder(chatClient)
.UseFunctionInvocation()
.Build();
Tool Observability
Call AddToolObservability() on your ChatOptions to instrument tool invocations:
// Set up tools and enable tool observability
IList<McpClientTool> tools = await mcpClient.ListToolsAsync();
ChatOptions options = new() { Tools = [..tools] };
// Add Progress Observability for tool calls
options.AddToolObservability();
var response = await chatClient.GetResponseAsync(
"What is the weather in Paris?", options
);
This wraps each AIFunction tool so every invocation creates a span with the tool name, input arguments, and output result.
IAgent Usage
using Progress.Observability.Extensions.AI;
var agentName = "Awesome AI Agent";
// Initialize the observability tracer
ObservabilityTracer.Initialize(new ObservabilityOptions()
{
AppName = agentName,
ApiKey = "ac_p_001_.....",
AdditionalAttributes = new Dictionary<string, object>()
{
{ "customer.id", Guid.NewGuid().ToString() },
{ "customer.quota", 2026 }
},
AdditionalTags = new List<string>()
{
"customer.id:12345",
"NewCustomer"
}
});
// Create tools (optionally add tool observability)
List<AITool> tools = [AIFunctionFactory.Create(MyToolFunction)];
// Build the agent with OpenTelemetry
var aiAgent = new AzureOpenAIClient(new(endpoint), new AzureKeyCredential(key))
.GetChatClient("gpt-4.1")
.AsAIAgent(
instructions: "You are a helpful assistant.",
name: agentName,
tools: tools)
.AsBuilder()
.UseOpenTelemetry(
sourceName: ObservabilityTracer.SourceName,
configure: agent => {
agent.EnableSensitiveData = true;
})
.Build();
Custom Spans
Use ObservabilityActivitySource to create your own spans for operations that are not automatically instrumented:
using Progress.Observability.Extensions.AI;
// Create custom activities for fine-grained observability
using (var activity = ObservabilityActivitySource.Instance.StartActivity("weather agent call"))
{
// Add custom attributes visible in the dashboard
activity?.SetTag("date", DateTime.Now.ToString("yyyy-MM-dd"));
activity?.SetTag("region", "europe");
var response = await chatClient.GetResponseAsync(
"What is the weather in Paris?", options
);
}
Custom spans appear in the trace tree alongside auto-instrumented LLM and tool spans.
Tags
Tags are set during initialization and apply to all spans:
// Tags set during initialization apply to all spans
ObservabilityTracer.Initialize(new ObservabilityOptions()
{
AppName = "My Agent",
ApiKey = "ac_p_001_.....",
AdditionalTags = new List<string>()
{
"environment:production",
"team:platform"
}
});
Tags are sanitized automatically: the SDK removes empty strings and trims each tag to a maximum of 200 characters.
The .NET SDK does not currently support scoped tag propagation like Python's propagate_attributes. Tags must be set globally at initialization time.
Shutdown
Always call Shutdown() before your process exits to flush pending telemetry:
// Always call Shutdown() before exiting to flush pending telemetry
try
{
// Run your agent
await RunAgent();
}
finally
{
ObservabilityTracer.Shutdown();
}
For IChatClient usage, the SDK manages the tracer lifecycle internally, but call Shutdown() explicitly to ensure all spans are sent.