Set up a .NET application w/th OpenTelemetry, and trace/evaluate in Langfuse A developer at Tiger Tribe Heineken detailed a method for instrumenting .NET AI applications with OpenTelemetry and exporting traces to Langfuse, which currently lacks an official SDK for ingesting OpenTelemetry data. The approach uses the official OpenTelemetry SDKs and the emerging Generative AI semantic conventions to send telemetry from a Weather Station Agent and Weather MCP Server to both an Aspire dashboard and Langfuse via OTLP. The team shortlisted Langfuse and Microsoft Foundry as evaluation platforms after testing Arize Phoenix and others. Currently, we are working on many products infused with AI across the company https://www.linkedin.com/company/tiger-tribe-heineken . Instead of relying solely on a harness https://dev.to/thangchung/my-claw-on-microsoft-foundry-hosted-agents-3b9 to provide AI agents with tools, context, memory, and other capabilities to make them smarter and more aware of the environments in which they operate, we also need the ability to evaluate these agents and provide feedback so they can continuously improve. We have researched and experimented with several AI evaluation platforms, including Arize Phoenix https://github.com/arize-ai/phoenix , Microsoft Foundry https://learn.microsoft.com/en-us/azure/foundry/how-to/evaluate-generative-ai-app , and Langfuse https://langfuse.com/ . After evaluating them against our requirements and roadmap, we shortlisted Microsoft Foundry and Langfuse because they fit well with our company's direction. Let's start with Langfuse in this post, and we will cover Microsoft Foundry in a future post. With Langfuse, there is currently no official SDK https://github.com/orgs/langfuse/discussions/9281 specifically designed to instrument OpenTelemetry data and ingest it into Langfuse. Fortunately, the OpenTelemetry community is working actively to extend the existing specification with Generative AI semantic conventions https://github.com/open-telemetry/semantic-conventions-genai . This is significant for us because it means we can use the official OpenTelemetry SDKs https://github.com/open-telemetry for different languages — such as Node.js, .NET, Java, Rust, and Go — to instrument our applications and send telemetry data to our chosen destination, in this case, Langfuse. However, we still need a few tips and tricks to make the integration work properly. These are the details we will cover in this post. Look at the Dashboard Aspire https://aspire.dev/ and Langfuse components at the bottom of the above picture; we notice that the code in Weather Station Agent and Weather MCP Server needs to push telemetry info to both Dashboard and Langfuse. And to make it work, we need to modify the ingest code: js var langfuseOtlpEndpoint = Environment.GetEnvironmentVariable "LANGFUSE OTLP ENDPOINT" ; var langfuseOtlpHeaders = Environment.GetEnvironmentVariable "LANGFUSE OTLP HEADERS" ?? ""; builder.Services.AddOpenTelemetry .ConfigureResource r = r.AddService "weather-station-agent" .WithTracing t = { t.AddSource "WeatherStationAgent" .AddSource "Experimental.ModelContextProtocol" .AddSource "Experimental.Microsoft.Extensions.AI" .AddHttpClientInstrumentation // ← keep outgoing HTTP spans .AddOtlpExporter ; // → Aspire dashboard OTEL EXPORTER OTLP env vars if string.IsNullOrEmpty langfuseOtlpEndpoint t.AddOtlpExporter o = { o.Endpoint = new Uri langfuseOtlpEndpoint.TrimEnd '/' + "/v1/traces" ; o.Headers = langfuseOtlpHeaders; o.Protocol = OtlpExportProtocol.HttpProtobuf; } ; } ; Then, in the apphost.cs , we need to set up some environment variables: js var weatherStationAgent = builder.AddProject "weather-station-agent", "MafLangfuseClient/MafLangfuseClient.csproj" .WithHttpEndpoint port: 5002 .WithEnvironment "ASPNETCORE ENVIRONMENT", "Development" ; var langfuseHost = GetEnv dotEnv, "LANGFUSE HOST", "http://localhost:3000" ; var otlpEndpoint = $"{langfuseHost.TrimEnd '/' }/api/public/otel"; var langfuseAuth = Convert.ToBase64String System.Text.Encoding.UTF8.GetBytes $"{GetEnv dotEnv, "LANGFUSE PUBLIC KEY", "" }:{GetEnv dotEnv, "LANGFUSE SECRET KEY", "" }" ; var otlpHeaders = $"Authorization=Basic {langfuseAuth},x-langfuse-ingestion-version=4"; // ... weatherStationAgent .WithReference weatherMcp .WithEnvironment "MCP ENDPOINT", weatherMcp.GetEndpoint "http" .WithEnvironment "LANGFUSE OTLP ENDPOINT", otlpEndpoint .WithEnvironment "LANGFUSE OTLP HEADERS", otlpHeaders .WithEnvironment "OTEL SERVICE NAME", "weather-station-agent" .WithEnvironment "OTEL RESOURCE ATTRIBUTES", "service.name=weather-station-agent" .WithEnvironment "OPENAI BASE URL", GetEnv dotEnv, "OPENAI BASE URL", "http://localhost:4000/v1" .WithEnvironment "OPENAI API KEY", GetEnv dotEnv, "OPENAI API KEY", "placeholder-key" .WithEnvironment "OPENAI MODEL", GetEnv dotEnv, "OPENAI MODEL", "gpt-4o-mini" ; builder.Build .Run ; With some of the official OpenTelemetry NuGet packages: