.NET SDK

Updated on Jun 16, 2026

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:

  • IChatClient from Microsoft.Extensions.AI (OpenAI, Azure OpenAI, and others)
  • IAgent from 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

bash
dotnet add <YourProject.csproj> package Progress.Observability.Instrumentation

From a local NuGet source:

bash
# 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:

cs
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():

cs
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

PropertyTypeDefaultDescription
AppNamestringAgentApplication name shown in the platform
ApiKeystringemptyYour API key (required)
Endpointstringhttps://collector.observability.progress.com:443Collector endpoint URL
RecordInputsbooltrueInclude request messages in spans
RecordOutputsbooltrueInclude response content in spans
DebugboolfalseEnable debug logging
AdditionalAttributesDictionary<string, object>emptyCustom attributes on all spans
AdditionalTagsListemptyTags for filtering (max 200 chars)

Environment Variables

PowerShell:

bash
# 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
# 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"
VariableOverrides
PROGRESS__OBSERVABILITY__APPNAMEAppName
PROGRESS__OBSERVABILITY__APIKEYApiKey
PROGRESS__OBSERVABILITY__ENDPOINTEndpoint

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:

cs
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:

cs
// 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

cs
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:

cs
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:

cs
// 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:

cs
// 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.

See Also