Skip to content

.NET — Microsoft.Extensions.AI

Microsoft.Extensions.AI natively implements new semconv v1.37. No opt-in needed.

Before you start

These examples export to a local OpenTelemetry Collector over OTLP/gRPC. Deploy the collector first — see Code examples → Deploy an OpenTelemetry Collector.

Install

dotnet new console -n OtelGenAiDotnet && cd OtelGenAiDotnet
dotnet add package Microsoft.Extensions.AI.OpenAI --version 10.5.1
dotnet add package Microsoft.Extensions.AI --version 10.5.2
dotnet add package OpenTelemetry.Exporter.OpenTelemetryProtocol --version 1.15.3
dotnet add package OpenTelemetry.Extensions.Hosting --version 1.15.3

Environment variables

export OPENAI_API_KEY="sk-..."
export CX_OTEL_ENDPOINT="http://<COLLECTOR_HOST>:4317"

Script (Program.cs)

using Microsoft.Extensions.AI;
using OpenAI;
// --- OTel imports ---
using OpenTelemetry;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;

var cxEndpoint = Environment.GetEnvironmentVariable("CX_OTEL_ENDPOINT")
    ?? "http://<COLLECTOR_HOST>:4317";
var openAiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY")
    ?? throw new InvalidOperationException("OPENAI_API_KEY required");

// --- OTel setup: configure tracer provider with OTLP exporter ---
const string sourceName = "GenAI.Demo";

using var tracerProvider = Sdk.CreateTracerProviderBuilder()
    .SetResourceBuilder(ResourceBuilder.CreateDefault()
        .AddService("dotnet-genai-demo")
        .AddAttributes(new Dictionary<string, object> {
            ["cx.application.name"] = "my-genai-app",
            ["cx.subsystem.name"] = "my-service",
        }))
    .AddSource(sourceName)
    .AddOtlpExporter(opts => {
        opts.Endpoint = new Uri(cxEndpoint);
        opts.Protocol = OpenTelemetry.Exporter.OtlpExportProtocol.Grpc;
    })
    .Build();

// --- Your app logic: create chat client with OTel auto-instrumentation ---
IChatClient chatClient = new ChatClientBuilder(
    new OpenAIClient(openAiKey).GetChatClient("gpt-4o-mini").AsIChatClient())
    .UseOpenTelemetry(sourceName: sourceName,           // OTel: enables GenAI span emission
        configure: c => c.EnableSensitiveData = true)   // OTel: captures message content
    .Build();

var response = await chatClient.GetResponseAsync(
    new List<ChatMessage> {
        new(ChatRole.System, "You are a concise assistant."),
        new(ChatRole.User, "What is OpenTelemetry in one sentence?"),
    },
    new ChatOptions { MaxOutputTokens = 100, Temperature = 0.7f });

Console.WriteLine($"Response: {response.Text}");
Console.WriteLine($"Tokens: {response.Usage?.InputTokenCount} in, {response.Usage?.OutputTokenCount} out");

Tip

EnableSensitiveData = true is required for message content. The IChatClient abstraction works with any provider (Azure OpenAI, Ollama, etc.).

Next steps

Look up which open-source library to use for your provider in Compatibility matrix.