<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>GenAI on dotnetuniversity</title>
        <link>https://dotnetuniversity.com/tags/genai/</link>
        <description>Recent content in GenAI on dotnetuniversity</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>en-us</language>
        <lastBuildDate>Sun, 05 Apr 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://dotnetuniversity.com/tags/genai/index.xml" rel="self" type="application/rss+xml" /><item>
        <title>GenAI Observability in .NET: Logging, Tracing, and Metrics with Langfuse</title>
        <link>https://dotnetuniversity.com/genai-observability-in-.net-logging-tracing-and-metrics-with-langfuse/</link>
        <pubDate>Sun, 05 Apr 2026 00:00:00 +0000</pubDate>
        
        <guid>https://dotnetuniversity.com/genai-observability-in-.net-logging-tracing-and-metrics-with-langfuse/</guid>
        <description>&lt;img src="https://dotnetuniversity.com/genai-observability-in-.net-logging-tracing-and-metrics-with-langfuse/dushane-white-l_rEeuvc4qo-unsplash.jpg" alt="Featured image of post GenAI Observability in .NET: Logging, Tracing, and Metrics with Langfuse" /&gt;&lt;p&gt;When you build a .NET application that calls an LLM, the hard part is rarely the first API call. The hard part starts after you deploy. How many tokens did each user consume? Which prompts are failing? How long do chat completions take? Why did a tool-calling chain produce the wrong result for one user but not another?&lt;/p&gt;
&lt;p&gt;Traditional observability tools give you generic HTTP spans and CPU metrics, but they don&amp;rsquo;t understand LLM-specific signals like token usage, model name, temperature, prompt text, tool calls, or finish reason. That&amp;rsquo;s where &lt;strong&gt;Langfuse&lt;/strong&gt; comes in.&lt;/p&gt;
&lt;p&gt;&lt;a class=&#34;link&#34; href=&#34;https://langfuse.com&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;Langfuse&lt;/a&gt; is an open-source LLM engineering platform that provides observability, evaluation, prompt management, and analytics. It supports OpenTelemetry natively, which means you can instrument your .NET GenAI application once and get structured LLM traces, metrics, and logs in one dashboard.&lt;/p&gt;
&lt;p&gt;In this post, I&amp;rsquo;ll show how to instrument a .NET Web API that uses &lt;strong&gt;Semantic Kernel&lt;/strong&gt; for chat completions via DeepSeek (OpenAI-compatible API), with Langfuse as the observability backend. The sample uses:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Microsoft.SemanticKernel&lt;/strong&gt; — orchestration layer for LLM calls and tool invocation&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DeepSeek&lt;/strong&gt; — chat model via OpenAI-compatible endpoint&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;LangfuseDotnet&lt;/strong&gt; — OpenTelemetry exporter for Langfuse&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OpenTelemetry&lt;/strong&gt; — traces, metrics, and structured logs&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Custom meters&lt;/strong&gt; — LLM-specific metrics (token count, latency, request rate)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;what-langfuse-gives-us-for-genai-workloads&#34;&gt;What Langfuse Gives Us for GenAI Workloads
&lt;/h2&gt;&lt;p&gt;Standard observability backends treat all spans equally. A chat completion request looks like any other &lt;code&gt;POST /api/v1/chat&lt;/code&gt; span. You lose the GenAI-specific context that matters for debugging and optimization.&lt;/p&gt;
&lt;p&gt;Langfuse bridges that gap. It follows OpenTelemetry GenAI semantic conventions, so the SDK knows how to structure LLM telemetry:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Traces&lt;/strong&gt; — full trace of a chat request with nested spans for tool calls, retrievals, and generations&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Generations&lt;/strong&gt; — LLM-specific observations with model name, provider, temperature, max tokens, input/output messages, token counts, and finish reason&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Metrics&lt;/strong&gt; — request counts, latency histograms, per-model token usage&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Logs&lt;/strong&gt; — structured application logs correlated with traces via trace IDs&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All of this works through standard OpenTelemetry exporters. The Langfuse exporter is just another OTLP destination that understands GenAI conventions.&lt;/p&gt;
&lt;h2 id=&#34;getting-started-package-setup&#34;&gt;Getting Started: Package Setup
&lt;/h2&gt;&lt;p&gt;We need these packages in the project:&lt;/p&gt;
&lt;div class=&#34;copilot-prompt text-code-block&#34;&gt;&lt;div class=&#34;copilot-prompt__header&#34;&gt;&lt;span class=&#34;text-code-block__language&#34;&gt;xml&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;copilot-prompt__body&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;
&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt; 1
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt; 2
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt; 3
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt; 4
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt; 5
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt; 6
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt; 7
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt; 8
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt; 9
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;10
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-xml&#34; data-lang=&#34;xml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6c7086;font-style:italic&#34;&gt;&amp;lt;!-- Semantic Kernel --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#cba6f7&#34;&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span style=&#34;color:#89b4fa&#34;&gt;Include=&lt;/span&gt;&lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;Microsoft.SemanticKernel&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#89b4fa&#34;&gt;Version=&lt;/span&gt;&lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;1.35.0&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#cba6f7&#34;&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#cba6f7&#34;&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span style=&#34;color:#89b4fa&#34;&gt;Include=&lt;/span&gt;&lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;Microsoft.SemanticKernel.Connectors.OpenAI&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#89b4fa&#34;&gt;Version=&lt;/span&gt;&lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;1.35.0&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#cba6f7&#34;&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#6c7086;font-style:italic&#34;&gt;&amp;lt;!-- Langfuse + OpenTelemetry --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#cba6f7&#34;&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span style=&#34;color:#89b4fa&#34;&gt;Include=&lt;/span&gt;&lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;zborek.LangfuseDotnet&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#89b4fa&#34;&gt;Version=&lt;/span&gt;&lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;0.10.0&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#cba6f7&#34;&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#cba6f7&#34;&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span style=&#34;color:#89b4fa&#34;&gt;Include=&lt;/span&gt;&lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;OpenTelemetry.Extensions.Hosting&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#89b4fa&#34;&gt;Version=&lt;/span&gt;&lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;1.15.3&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#cba6f7&#34;&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#cba6f7&#34;&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span style=&#34;color:#89b4fa&#34;&gt;Include=&lt;/span&gt;&lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;OpenTelemetry.Instrumentation.AspNetCore&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#89b4fa&#34;&gt;Version=&lt;/span&gt;&lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;1.15.2&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#cba6f7&#34;&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#cba6f7&#34;&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span style=&#34;color:#89b4fa&#34;&gt;Include=&lt;/span&gt;&lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;OpenTelemetry.Instrumentation.Http&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#89b4fa&#34;&gt;Version=&lt;/span&gt;&lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;1.15.1&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#cba6f7&#34;&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#cba6f7&#34;&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span style=&#34;color:#89b4fa&#34;&gt;Include=&lt;/span&gt;&lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;OpenTelemetry.Instrumentation.Runtime&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#89b4fa&#34;&gt;Version=&lt;/span&gt;&lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;1.15.1&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#cba6f7&#34;&gt;/&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The Langfuse SDK (&lt;code&gt;zborek.Langfuse&lt;/code&gt;) bundles the OpenTelemetry exporter, GenAI semantic convention helpers, and the &lt;code&gt;IOtelLangfuseTrace&lt;/code&gt; service for scoped tracing.&lt;/p&gt;
&lt;p&gt;Semantic Kernel preview warnings are suppressed so the build stays clean:&lt;/p&gt;
&lt;div class=&#34;copilot-prompt text-code-block&#34;&gt;&lt;div class=&#34;copilot-prompt__header&#34;&gt;&lt;span class=&#34;text-code-block__language&#34;&gt;xml&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;copilot-prompt__body&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;
&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-xml&#34; data-lang=&#34;xml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#cba6f7&#34;&gt;&amp;lt;NoWarn&amp;gt;&lt;/span&gt;SKEXP0010,SKEXP0050,SKEXP0001,SKEXP0055&lt;span style=&#34;color:#cba6f7&#34;&gt;&amp;lt;/NoWarn&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&#34;running-langfuse-locally-with-docker-compose&#34;&gt;Running Langfuse Locally with Docker Compose
&lt;/h2&gt;&lt;p&gt;Langfuse is an open-source platform, so you can run it locally instead of relying on a managed cloud instance. The sample repository includes a &lt;a class=&#34;link&#34; href=&#34;https://github.com/meysamhadeli/blog-samples/tree/main/src/langfuse-genai-observability-sample/docker-compose.yml&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;docker-compose.yml&lt;/a&gt; that starts everything needed for local development:&lt;/p&gt;
&lt;div class=&#34;copilot-prompt text-code-block&#34;&gt;&lt;div class=&#34;copilot-prompt__header&#34;&gt;&lt;span class=&#34;text-code-block__language&#34;&gt;powershell&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;copilot-prompt__body&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;
&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-powershell&#34; data-lang=&#34;powershell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;docker compose up -d&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The compose file starts Postgres, ClickHouse, MinIO, Redis, the Langfuse worker, and the Langfuse web UI. The web UI is exposed on &lt;code&gt;http://localhost:3001&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;It also seeds a default organization, project, and user account:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Email&lt;/strong&gt;: &lt;code&gt;admin@example.com&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Password&lt;/strong&gt;: &lt;code&gt;LangfuseDemo2026!&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The sample&amp;rsquo;s &lt;code&gt;appsettings.json&lt;/code&gt; already points to the seeded local project, so no manual key configuration is needed. The compose file sets both &lt;code&gt;LANGFUSE_INIT_ORG_ID&lt;/code&gt; and &lt;code&gt;LANGFUSE_INIT_PROJECT_ID&lt;/code&gt;; without these IDs Langfuse skips the seeding step.&lt;/p&gt;
&lt;div class=&#34;copilot-prompt text-code-block&#34;&gt;&lt;div class=&#34;copilot-prompt__header&#34;&gt;&lt;span class=&#34;text-code-block__language&#34;&gt;json&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;copilot-prompt__body&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;
&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;1
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;2
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;3
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;4
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;5
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;6
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;7
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#cba6f7&#34;&gt;&amp;#34;Langfuse&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#cba6f7&#34;&gt;&amp;#34;Url&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;http://localhost:3001&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#cba6f7&#34;&gt;&amp;#34;PublicKey&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;pk-lf-local-public-key&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#cba6f7&#34;&gt;&amp;#34;SecretKey&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;sk-lf-local-secret-key&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#cba6f7&#34;&gt;&amp;#34;OnlyGenAiActivities&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#fab387&#34;&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The application checks for the Langfuse keys at startup. If they are empty, the exporter is not registered and the API still runs normally — it just does not send traces to Langfuse. This is useful for local development without the compose stack.&lt;/p&gt;
&lt;p&gt;No Aspire orchestration is required. The sample is a plain ASP.NET Core project, and the compose stack is self-contained.&lt;/p&gt;
&lt;h2 id=&#34;configuring-langfuse-with-opentelemetry&#34;&gt;Configuring Langfuse with OpenTelemetry
&lt;/h2&gt;&lt;p&gt;The wiring happens in &lt;code&gt;Program.cs&lt;/code&gt;. We register OpenTelemetry with the Langfuse exporter, and then register the scoped trace service:&lt;/p&gt;
&lt;div class=&#34;copilot-prompt text-code-block&#34;&gt;&lt;div class=&#34;copilot-prompt__header&#34;&gt;&lt;span class=&#34;text-code-block__language&#34;&gt;csharp&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;copilot-prompt__body&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;
&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt; 1
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt; 2
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt; 3
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt; 4
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt; 5
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt; 6
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt; 7
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt; 8
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt; 9
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;10
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;11
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;12
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;13
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;14
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;15
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;16
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;17
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;18
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;19
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;20
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;21
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;22
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;23
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;24
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;25
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;26
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;27
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;28
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;29
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;30
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;31
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;32
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;33
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;34
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;35
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;36
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;37
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#cba6f7&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#fab387&#34;&gt;zborek.Langfuse.OpenTelemetry&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f38ba8&#34;&gt;var&lt;/span&gt; builder = WebApplication.CreateBuilder(args);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f38ba8&#34;&gt;var&lt;/span&gt; langfuseSection = builder.Configuration.GetSection(&lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;Langfuse&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f38ba8&#34;&gt;var&lt;/span&gt; hasLangfuseKeys = !&lt;span style=&#34;color:#f38ba8&#34;&gt;string&lt;/span&gt;.IsNullOrEmpty(langfuseSection[&lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;PublicKey&amp;#34;&lt;/span&gt;])
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;amp;&amp;amp; !&lt;span style=&#34;color:#f38ba8&#34;&gt;string&lt;/span&gt;.IsNullOrEmpty(langfuseSection[&lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;SecretKey&amp;#34;&lt;/span&gt;]);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;builder.Services.AddOpenTelemetry()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    .ConfigureResource(resource =&amp;gt; resource
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        .AddService(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            serviceName: &lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;LangfuseGenAI&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            serviceVersion: &lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;1.0.0&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            serviceInstanceId: Environment.MachineName
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    .WithTracing(tracing =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        tracing
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            .AddAspNetCoreInstrumentation()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            .AddHttpClientInstrumentation()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            .AddSource(&lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;LangfuseGenAI.WeatherPlugin&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#cba6f7&#34;&gt;if&lt;/span&gt; (hasLangfuseKeys)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            tracing.AddLangfuseExporter(langfuseSection);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    })
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    .WithMetrics(metrics =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        metrics
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            .AddAspNetCoreInstrumentation()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            .AddHttpClientInstrumentation()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            .AddRuntimeInstrumentation()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            .AddMeter(&lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;LangfuseGenAI.Meters&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#cba6f7&#34;&gt;if&lt;/span&gt; (hasLangfuseKeys)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    builder.Services.AddLangfuseTracing();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;AddLangfuseExporter&lt;/code&gt; call reads these settings from &lt;code&gt;appsettings.json&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;copilot-prompt text-code-block&#34;&gt;&lt;div class=&#34;copilot-prompt__header&#34;&gt;&lt;span class=&#34;text-code-block__language&#34;&gt;json&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;copilot-prompt__body&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;
&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;1
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;2
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;3
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;4
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;5
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;6
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;7
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#cba6f7&#34;&gt;&amp;#34;Langfuse&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#cba6f7&#34;&gt;&amp;#34;Url&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;http://localhost:3001&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#cba6f7&#34;&gt;&amp;#34;PublicKey&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;pk-...&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#cba6f7&#34;&gt;&amp;#34;SecretKey&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;sk-...&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#cba6f7&#34;&gt;&amp;#34;OnlyGenAiActivities&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#fab387&#34;&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Setting &lt;code&gt;OnlyGenAiActivities&lt;/code&gt; to &lt;code&gt;false&lt;/code&gt; keeps infrastructure spans (HTTP client, ASP.NET Core, and our custom tool-call activity source) visible alongside GenAI spans. If you set it to &lt;code&gt;true&lt;/code&gt;, Langfuse filters out non-GenAI spans, which hides the tool-call child observations.&lt;/p&gt;
&lt;h2 id=&#34;setting-up-semantic-kernel-with-deepseek&#34;&gt;Setting Up Semantic Kernel with DeepSeek
&lt;/h2&gt;&lt;p&gt;Semantic Kernel handles the chat completion service, the plugin registry, and the automatic tool-calling loop. The DeepSeek API is OpenAI-compatible, so we use the OpenAI connector with a custom endpoint:&lt;/p&gt;
&lt;div class=&#34;copilot-prompt text-code-block&#34;&gt;&lt;div class=&#34;copilot-prompt__header&#34;&gt;&lt;span class=&#34;text-code-block__language&#34;&gt;csharp&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;copilot-prompt__body&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;
&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt; 1
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt; 2
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt; 3
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt; 4
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt; 5
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt; 6
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt; 7
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt; 8
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt; 9
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;10
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;11
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;12
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;13
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;14
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;15
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;16
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f38ba8&#34;&gt;var&lt;/span&gt; apiKey = Environment.GetEnvironmentVariable(&lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;APP_API_KEY&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ?? builder.Configuration[&lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;AI:ApiKey&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ?? &lt;span style=&#34;color:#cba6f7&#34;&gt;throw&lt;/span&gt; &lt;span style=&#34;color:#cba6f7&#34;&gt;new&lt;/span&gt; InvalidOperationException(&lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;AI API key is required.&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f38ba8&#34;&gt;var&lt;/span&gt; endpoint = aiConfig[&lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;Endpoint&amp;#34;&lt;/span&gt;] ?? &lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;https://api.deepseek.com&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f38ba8&#34;&gt;var&lt;/span&gt; chatModel = aiConfig[&lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;ChatModel&amp;#34;&lt;/span&gt;] ?? &lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;deepseek-chat&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f38ba8&#34;&gt;var&lt;/span&gt; kernelBuilder = builder.Services.AddKernel();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;kernelBuilder.AddOpenAIChatCompletion(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    modelId: chatModel,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    apiKey: apiKey,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    endpoint: &lt;span style=&#34;color:#cba6f7&#34;&gt;new&lt;/span&gt; Uri(endpoint));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;kernelBuilder.Plugins.AddFromType&amp;lt;WeatherPlugin&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;builder.Services.AddScoped&amp;lt;ChatService&amp;gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The same pattern works for any OpenAI-compatible provider — just change the &lt;code&gt;Endpoint&lt;/code&gt; URL and model name. Because Semantic Kernel owns the abstraction, switching providers is a one-line change.&lt;/p&gt;
&lt;h2 id=&#34;system-architecture&#34;&gt;System Architecture
&lt;/h2&gt;&lt;p&gt;Here is how the components fit together:&lt;/p&gt;
&lt;div class=&#34;mermaid&#34;&gt;%%{init: {&#34;theme&#34;:&#34;base&#34;, &#34;themeVariables&#34;: {
  &#34;primaryColor&#34;:&#34;#A855F7&#34;,
  &#34;primaryTextColor&#34;:&#34;#FFFFFF&#34;,
  &#34;primaryBorderColor&#34;:&#34;#7C3AED&#34;,
  &#34;lineColor&#34;:&#34;#6B7280&#34;,
  &#34;secondaryColor&#34;:&#34;#F59E0B&#34;,
  &#34;secondaryTextColor&#34;:&#34;#FFFFFF&#34;,
  &#34;secondaryBorderColor&#34;:&#34;#D97706&#34;,
  &#34;tertiaryColor&#34;:&#34;#06B6D4&#34;,
  &#34;tertiaryTextColor&#34;:&#34;#FFFFFF&#34;
}}}%%

flowchart TD
    classDef client fill:#a855f7,stroke:#7e22ce,color:#fff,font-size:14px
    classDef service fill:#06b6d4,stroke:#0891b2,color:#fff,font-size:14px
    classDef ai fill:#f59e0b,stroke:#d97706,color:#fff,font-size:14px
    classDef obs fill:#10b981,stroke:#059669,color:#fff,font-size:14px
    classDef metric fill:#8b5cf6,stroke:#6d28d9,color:#fff,font-size:14px

    subgraph Client[&#34;🖥️ Client&#34;]
        C1[&#34;HTTP Client&#34;]
    end

    C1 --&gt; API

    subgraph App[&#34;📦 .NET GenAI API&#34;]
        API[&#34;ASP.NET Core \nMinimal API&#34;]
        CS[&#34;ChatService&#34;]
        SK[&#34;Semantic Kernel\n+ WeatherPlugin&#34;]
        OTel[&#34;OpenTelemetry \nSDK&#34;]
        Meters[&#34;Custom Meters \n📊 Histogram/Counter&#34;]

        API --&gt; CS
        CS --&gt; SK
        CS --&gt; OTel
        CS --&gt; Meters
        Meters --&gt; OTel
    end

    subgraph AI[&#34;🧠 AI Provider&#34;]
        DS[&#34;DeepSeek API\ndeepseek-chat&#34;]
    end

    SK --&gt; DS

    subgraph Observability[&#34;🔭 Observability&#34;]
        LF[&#34;Langfuse&#34;]
        direction LR
    end

    OTel --&gt;|&#34;OTLP Traces (GenAI)&#34;| LF

    class C1 client
    class API,CS,SK service
    class DS ai
    class LF,OTel obs
    class Meters metric&lt;/div&gt;
&lt;p&gt;The flow is straightforward: HTTP requests hit the minimal API endpoints, which delegate to &lt;code&gt;ChatService&lt;/code&gt;. That service calls DeepSeek through Semantic Kernel and emits OpenTelemetry spans, metrics, and logs. The Langfuse exporter picks up GenAI-specific spans and sends them to Langfuse.&lt;/p&gt;
&lt;h2 id=&#34;instrumenting-chat-completions-with-langfuse&#34;&gt;Instrumenting Chat Completions with Langfuse
&lt;/h2&gt;&lt;p&gt;The &lt;code&gt;ChatService&lt;/code&gt; integrates Langfuse tracing alongside Semantic Kernel calls. Here is the full pattern:&lt;/p&gt;
&lt;div class=&#34;copilot-prompt text-code-block&#34;&gt;&lt;div class=&#34;copilot-prompt__header&#34;&gt;&lt;span class=&#34;text-code-block__language&#34;&gt;csharp&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;copilot-prompt__body&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;
&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt; 1
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt; 2
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt; 3
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt; 4
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt; 5
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt; 6
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt; 7
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt; 8
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt; 9
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;10
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;11
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;12
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;13
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;14
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;15
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;16
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;17
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;18
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;19
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;20
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;21
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;22
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;23
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;24
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;25
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;26
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;27
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;28
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;29
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;30
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;31
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;32
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;33
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;34
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;35
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;36
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;37
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;38
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;39
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;40
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;41
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;42
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;43
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;44
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;45
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;46
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;47
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;48
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;49
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;50
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;51
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;52
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;53
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;54
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;55
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;56
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;57
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;58
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;59
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;60
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;61
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;62
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;63
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;64
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;65
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;66
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;67
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;68
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;69
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;70
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;71
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;72
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;73
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;74
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;75
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;76
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;77
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;78
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;79
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;80
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;81
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;82
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;83
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;84
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;85
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;86
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;87
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;88
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;89
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;90
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;91
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;92
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;93
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;94
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;95
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f38ba8&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#f38ba8&#34;&gt;sealed&lt;/span&gt; &lt;span style=&#34;color:#cba6f7&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#f9e2af&#34;&gt;WeatherPlugin&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f38ba8&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#f38ba8&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#cba6f7&#34;&gt;readonly&lt;/span&gt; ActivitySource ActivitySource = &lt;span style=&#34;color:#cba6f7&#34;&gt;new&lt;/span&gt;(&lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;LangfuseGenAI.WeatherPlugin&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#89b4fa&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#89b4fa&#34;&gt;    [KernelFunction(&amp;#34;get_weather&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#89b4fa&#34;&gt;    [Description(&amp;#34;Get the current weather for a city&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f38ba8&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#f38ba8&#34;&gt;string&lt;/span&gt; GetWeather(&lt;span style=&#34;color:#f38ba8&#34;&gt;string&lt;/span&gt; city)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#cba6f7&#34;&gt;using&lt;/span&gt; &lt;span style=&#34;color:#fab387&#34;&gt;var&lt;/span&gt; activity = ActivitySource.StartActivity(&lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;get_weather&amp;#34;&lt;/span&gt;, ActivityKind.Internal);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        activity?.SetTag(&lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;tool.name&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;get_weather&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        activity?.SetTag(&lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;tool.city&amp;#34;&lt;/span&gt;, city);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f38ba8&#34;&gt;var&lt;/span&gt; result = city &lt;span style=&#34;color:#cba6f7&#34;&gt;switch&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;Tokyo&amp;#34;&lt;/span&gt; =&amp;gt; &lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;Sunny, 22°C&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;Paris&amp;#34;&lt;/span&gt; =&amp;gt; &lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;Rainy, 15°C&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;New York&amp;#34;&lt;/span&gt; =&amp;gt; &lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;Cloudy, 18°C&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;London&amp;#34;&lt;/span&gt; =&amp;gt; &lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;Foggy, 12°C&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            _ =&amp;gt; &lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;Sunny, 20°C&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        activity?.SetTag(&lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;tool.weather&amp;#34;&lt;/span&gt;, result);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#cba6f7&#34;&gt;return&lt;/span&gt; result;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f38ba8&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#f38ba8&#34;&gt;sealed&lt;/span&gt; &lt;span style=&#34;color:#cba6f7&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#f9e2af&#34;&gt;ChatService&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Kernel kernel,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ILogger&amp;lt;ChatService&amp;gt; logger,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    IServiceProvider serviceProvider)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f38ba8&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#f38ba8&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#cba6f7&#34;&gt;readonly&lt;/span&gt; Meter ChatMeter = &lt;span style=&#34;color:#cba6f7&#34;&gt;new&lt;/span&gt;(&lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;LangfuseGenAI.Meters&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f38ba8&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#f38ba8&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#cba6f7&#34;&gt;readonly&lt;/span&gt; Counter&amp;lt;&lt;span style=&#34;color:#f38ba8&#34;&gt;int&lt;/span&gt;&amp;gt; ChatRequestCounter =
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        ChatMeter.CreateCounter&amp;lt;&lt;span style=&#34;color:#f38ba8&#34;&gt;int&lt;/span&gt;&amp;gt;(&lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;genai.chat.requests&amp;#34;&lt;/span&gt;, description: &lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;Total chat requests&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f38ba8&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#f38ba8&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#cba6f7&#34;&gt;readonly&lt;/span&gt; Histogram&amp;lt;&lt;span style=&#34;color:#f38ba8&#34;&gt;double&lt;/span&gt;&amp;gt; ChatLatency =
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        ChatMeter.CreateHistogram&amp;lt;&lt;span style=&#34;color:#f38ba8&#34;&gt;double&lt;/span&gt;&amp;gt;(&lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;genai.chat.latency_ms&amp;#34;&lt;/span&gt;, description: &lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;Chat request latency in ms&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f38ba8&#34;&gt;private&lt;/span&gt; &lt;span style=&#34;color:#f38ba8&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#cba6f7&#34;&gt;readonly&lt;/span&gt; Counter&amp;lt;&lt;span style=&#34;color:#f38ba8&#34;&gt;int&lt;/span&gt;&amp;gt; TokenCounter =
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        ChatMeter.CreateCounter&amp;lt;&lt;span style=&#34;color:#f38ba8&#34;&gt;int&lt;/span&gt;&amp;gt;(&lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;genai.chat.tokens&amp;#34;&lt;/span&gt;, description: &lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;Total tokens used&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f38ba8&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#f38ba8&#34;&gt;async&lt;/span&gt; Task&amp;lt;&lt;span style=&#34;color:#f38ba8&#34;&gt;string&lt;/span&gt;&amp;gt; ProcessChatAsync(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f38ba8&#34;&gt;string&lt;/span&gt; userMessage,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f38ba8&#34;&gt;string?&lt;/span&gt; sessionId = &lt;span style=&#34;color:#fab387&#34;&gt;null&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f38ba8&#34;&gt;string?&lt;/span&gt; userId = &lt;span style=&#34;color:#fab387&#34;&gt;null&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        CancellationToken cancellationToken = &lt;span style=&#34;color:#cba6f7&#34;&gt;default&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#f38ba8&#34;&gt;var&lt;/span&gt; sw = Stopwatch.StartNew();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        ChatRequestCounter.Add(&lt;span style=&#34;color:#fab387&#34;&gt;1&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        serviceProvider.GetService&amp;lt;IOtelLangfuseTrace&amp;gt;()?.SetPublic(&lt;span style=&#34;color:#fab387&#34;&gt;true&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        logger.LogInformation(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;Processing chat request. SessionId: {SessionId}, UserId: {UserId}&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            sessionId, userId);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#cba6f7&#34;&gt;try&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f38ba8&#34;&gt;var&lt;/span&gt; chatCompletion = kernel.GetRequiredService&amp;lt;IChatCompletionService&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f38ba8&#34;&gt;var&lt;/span&gt; chatHistory = &lt;span style=&#34;color:#cba6f7&#34;&gt;new&lt;/span&gt; ChatHistory();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            chatHistory.AddSystemMessage(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;You are a weather assistant. For any weather question, you must call the get_weather tool for each requested city and use the tool results in your answer. Do not guess weather.&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            chatHistory.AddUserMessage(userMessage);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f38ba8&#34;&gt;var&lt;/span&gt; executionSettings = &lt;span style=&#34;color:#cba6f7&#34;&gt;new&lt;/span&gt; OpenAIPromptExecutionSettings
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                Temperature = &lt;span style=&#34;color:#fab387&#34;&gt;0.7f&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                MaxTokens = &lt;span style=&#34;color:#fab387&#34;&gt;1000&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f38ba8&#34;&gt;var&lt;/span&gt; result = &lt;span style=&#34;color:#cba6f7&#34;&gt;await&lt;/span&gt; chatCompletion.GetChatMessageContentAsync(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                chatHistory,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                executionSettings: executionSettings,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                kernel: kernel,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                cancellationToken: cancellationToken);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            sw.Stop();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            ChatLatency.Record(sw.ElapsedMilliseconds);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#f38ba8&#34;&gt;var&lt;/span&gt; outputTokens = result.Content?.Length / &lt;span style=&#34;color:#fab387&#34;&gt;4&lt;/span&gt; ?? &lt;span style=&#34;color:#fab387&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            TokenCounter.Add(outputTokens);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            logger.LogInformation(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;Chat completed in {ElapsedMs}ms. Output tokens (est): {OutputTokens}&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                sw.ElapsedMilliseconds, outputTokens);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#cba6f7&#34;&gt;return&lt;/span&gt; result.Content ?? &lt;span style=&#34;color:#f38ba8&#34;&gt;string&lt;/span&gt;.Empty;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#cba6f7&#34;&gt;catch&lt;/span&gt; (Exception ex)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            logger.LogError(ex, &lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;Chat request failed for session {SessionId}&amp;#34;&lt;/span&gt;, sessionId);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#cba6f7&#34;&gt;throw&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The pattern covers all three pillars of observability:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Traces&lt;/strong&gt; — Semantic Kernel&amp;rsquo;s built-in instrumentation emits a trace with nested generation observations, and the custom &lt;code&gt;ActivitySource&lt;/code&gt; in &lt;code&gt;WeatherPlugin&lt;/code&gt; emits child spans for each tool call.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Metrics&lt;/strong&gt; — custom counters track request rate and token usage. A histogram tracks latency distribution. These can be scraped by Prometheus and visualized in Grafana for operational dashboards.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Logs&lt;/strong&gt; — structured logs with session IDs and user IDs make it easy to correlate log entries with specific traces.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;adding-tool-calling-with-semantic-kernel&#34;&gt;Adding Tool Calling with Semantic Kernel
&lt;/h2&gt;&lt;p&gt;Most real-world GenAI applications need more than text generation — they need the model to query databases, call APIs, or fetch live data. Semantic Kernel handles this with plugins and &lt;code&gt;ToolCallBehavior&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The pattern has three parts:&lt;/p&gt;
&lt;h3 id=&#34;1-define-a-plugin-with-kernelfunction&#34;&gt;1. Define a plugin with &lt;code&gt;KernelFunction&lt;/code&gt;
&lt;/h3&gt;&lt;p&gt;Use a public method decorated with &lt;code&gt;[KernelFunction]&lt;/code&gt; and &lt;code&gt;[Description]&lt;/code&gt; to expose a callable tool. The method&amp;rsquo;s parameters and return type are automatically described to the model:&lt;/p&gt;
&lt;div class=&#34;copilot-prompt text-code-block&#34;&gt;&lt;div class=&#34;copilot-prompt__header&#34;&gt;&lt;span class=&#34;text-code-block__language&#34;&gt;csharp&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;copilot-prompt__body&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;
&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt; 1
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt; 2
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt; 3
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt; 4
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt; 5
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt; 6
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt; 7
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt; 8
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt; 9
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;10
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;11
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;12
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;13
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;14
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f38ba8&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#f38ba8&#34;&gt;sealed&lt;/span&gt; &lt;span style=&#34;color:#cba6f7&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#f9e2af&#34;&gt;WeatherPlugin&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#89b4fa&#34;&gt;    [KernelFunction(&amp;#34;get_weather&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#89b4fa&#34;&gt;    [Description(&amp;#34;Get the current weather for a city&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f38ba8&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#f38ba8&#34;&gt;string&lt;/span&gt; GetWeather(&lt;span style=&#34;color:#f38ba8&#34;&gt;string&lt;/span&gt; city)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#cba6f7&#34;&gt;return&lt;/span&gt; city &lt;span style=&#34;color:#cba6f7&#34;&gt;switch&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;Tokyo&amp;#34;&lt;/span&gt; =&amp;gt; &lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;Sunny, 22°C&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;Paris&amp;#34;&lt;/span&gt; =&amp;gt; &lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;Rainy, 15°C&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            _ =&amp;gt; &lt;span style=&#34;color:#a6e3a1&#34;&gt;&amp;#34;Sunny, 20°C&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&#34;2-register-the-plugin-in-programcs&#34;&gt;2. Register the plugin in &lt;code&gt;Program.cs&lt;/code&gt;
&lt;/h3&gt;&lt;p&gt;Add the plugin to the kernel:&lt;/p&gt;
&lt;div class=&#34;copilot-prompt text-code-block&#34;&gt;&lt;div class=&#34;copilot-prompt__header&#34;&gt;&lt;span class=&#34;text-code-block__language&#34;&gt;csharp&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;copilot-prompt__body&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;
&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;1
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f38ba8&#34;&gt;var&lt;/span&gt; kernelBuilder = builder.Services.AddKernel();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;kernelBuilder.Plugins.AddFromType&amp;lt;WeatherPlugin&amp;gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&#34;3-enable-auto-invocation-in-execution-settings&#34;&gt;3. Enable auto-invocation in execution settings
&lt;/h3&gt;&lt;p&gt;Tell Semantic Kernel to automatically call the tool when the model requests it:&lt;/p&gt;
&lt;div class=&#34;copilot-prompt text-code-block&#34;&gt;&lt;div class=&#34;copilot-prompt__header&#34;&gt;&lt;span class=&#34;text-code-block__language&#34;&gt;csharp&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;copilot-prompt__body&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;
&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;1
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;2
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;3
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;4
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;5
&lt;/span&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f38ba8&#34;&gt;var&lt;/span&gt; executionSettings = &lt;span style=&#34;color:#cba6f7&#34;&gt;new&lt;/span&gt; OpenAIPromptExecutionSettings
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Temperature = &lt;span style=&#34;color:#fab387&#34;&gt;0.7f&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    MaxTokens = &lt;span style=&#34;color:#fab387&#34;&gt;1000&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;When the model decides it needs weather data, the flow is:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Model returns a &lt;code&gt;get_weather&lt;/code&gt; function call request instead of a text response&lt;/li&gt;
&lt;li&gt;Semantic Kernel invokes the &lt;code&gt;WeatherPlugin.GetWeather&lt;/code&gt; method&lt;/li&gt;
&lt;li&gt;The result is sent back to the model in a second round&lt;/li&gt;
&lt;li&gt;Model produces the final text response using the tool output&lt;/li&gt;
&lt;li&gt;The whole exchange appears as one trace with nested tool-call observations&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Langfuse captures the tool call as a separate child observation. You can see the tool inputs (city name) and outputs (weather string) alongside the final model response — all correlated in one trace.&lt;/p&gt;
&lt;h2 id=&#34;tool-calling-in-the-langfuse-trace&#34;&gt;Tool Calling in the Langfuse Trace
&lt;/h2&gt;&lt;p&gt;This is the dashboard view readers should focus on: one root chat request, one or more generation spans, and &lt;code&gt;get_weather&lt;/code&gt; child span(s) under the same trace.&lt;/p&gt;
&lt;p&gt;Below is the Langfuse traces list. Each row is one request; the observation count tells you whether tool calls were captured as separate spans.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://dotnetuniversity.com/genai-observability-in-.net-logging-tracing-and-metrics-with-langfuse/langfuse-traces-list.png&#34;
	width=&#34;1280&#34;
	height=&#34;720&#34;
	srcset=&#34;https://dotnetuniversity.com/genai-observability-in-.net-logging-tracing-and-metrics-with-langfuse/langfuse-traces-list_hu_35b155ebe70a3bbb.png 480w, https://dotnetuniversity.com/genai-observability-in-.net-logging-tracing-and-metrics-with-langfuse/langfuse-traces-list_hu_6c497a0a4555bdeb.png 1024w&#34;
	loading=&#34;lazy&#34;
	
		alt=&#34;Langfuse traces list showing chat-completion traces with multiple observations&#34;
	
	
		class=&#34;gallery-image&#34; 
		data-flex-grow=&#34;177&#34;
		data-flex-basis=&#34;426px&#34;
	
&gt;&lt;/p&gt;
&lt;p&gt;Opening the newest trace shows the full tree: the root &lt;code&gt;POST /api/v1/chat/&lt;/code&gt; span, the &lt;code&gt;chat-completion&lt;/code&gt; span, the HTTP &lt;code&gt;POST&lt;/code&gt; spans to DeepSeek, and the nested &lt;code&gt;get_weather&lt;/code&gt; tool-call activity.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://dotnetuniversity.com/genai-observability-in-.net-logging-tracing-and-metrics-with-langfuse/langfuse-trace-detail-with-toolcalls.png&#34;
	width=&#34;1060&#34;
	height=&#34;599&#34;
	srcset=&#34;https://dotnetuniversity.com/genai-observability-in-.net-logging-tracing-and-metrics-with-langfuse/langfuse-trace-detail-with-toolcalls_hu_b7e32533ec093808.png 480w, https://dotnetuniversity.com/genai-observability-in-.net-logging-tracing-and-metrics-with-langfuse/langfuse-trace-detail-with-toolcalls_hu_90eb4b60ac1ed4d6.png 1024w&#34;
	loading=&#34;lazy&#34;
	
		alt=&#34;Langfuse trace detail showing root span, chat-completion, HTTP posts, and get_weather tool-call observations&#34;
	
	
		class=&#34;gallery-image&#34; 
		data-flex-grow=&#34;176&#34;
		data-flex-basis=&#34;424px&#34;
	
&gt;&lt;/p&gt;
&lt;p&gt;The Observations view lists every individual span across all traces, making it easy to filter for tool calls or generations.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://dotnetuniversity.com/genai-observability-in-.net-logging-tracing-and-metrics-with-langfuse/langfuse-observations.png&#34;
	width=&#34;1060&#34;
	height=&#34;599&#34;
	srcset=&#34;https://dotnetuniversity.com/genai-observability-in-.net-logging-tracing-and-metrics-with-langfuse/langfuse-observations_hu_46890649c37fea98.png 480w, https://dotnetuniversity.com/genai-observability-in-.net-logging-tracing-and-metrics-with-langfuse/langfuse-observations_hu_b1167be001492aba.png 1024w&#34;
	loading=&#34;lazy&#34;
	
		alt=&#34;Langfuse observations list showing SPAN and GENERATION rows for chat requests&#34;
	
	
		class=&#34;gallery-image&#34; 
		data-flex-grow=&#34;176&#34;
		data-flex-basis=&#34;424px&#34;
	
&gt;&lt;/p&gt;
&lt;h2 id=&#34;what-you-get-in-the-langfuse-dashboard&#34;&gt;What You Get in the Langfuse Dashboard
&lt;/h2&gt;&lt;p&gt;After running the sample and sending a few chat requests, Langfuse shows:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Traces view&lt;/strong&gt;: Each chat request appears as a trace with the full input/output, model metadata, token counts, latency, and nested tool-call observations. When weather tool calling is active, the trace shows the &lt;code&gt;get_weather&lt;/code&gt; child span under the root request.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Observations view&lt;/strong&gt;: Every span is listed independently. You can filter by type (SPAN, GENERATION), name, or time range to inspect tool calls, HTTP calls, or model generations.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sessions view&lt;/strong&gt;: By passing &lt;code&gt;sessionId&lt;/code&gt; in the request, Langfuse groups all traces from a single user session. This helps debug multi-turn conversations and identify UX issues.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://dotnetuniversity.com/genai-observability-in-.net-logging-tracing-and-metrics-with-langfuse/langfuse-sessions.png&#34;
	width=&#34;1060&#34;
	height=&#34;599&#34;
	srcset=&#34;https://dotnetuniversity.com/genai-observability-in-.net-logging-tracing-and-metrics-with-langfuse/langfuse-sessions_hu_cf02aecf1d35623a.png 480w, https://dotnetuniversity.com/genai-observability-in-.net-logging-tracing-and-metrics-with-langfuse/langfuse-sessions_hu_d74c8307c0dba03b.png 1024w&#34;
	loading=&#34;lazy&#34;
	
		alt=&#34;Langfuse sessions view grouping traces by session ID&#34;
	
	
		class=&#34;gallery-image&#34; 
		data-flex-grow=&#34;176&#34;
		data-flex-basis=&#34;424px&#34;
	
&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Users view&lt;/strong&gt;: Passing &lt;code&gt;userId&lt;/code&gt; lets you see per-user activity, total events, and estimated cost over any time range.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;https://dotnetuniversity.com/genai-observability-in-.net-logging-tracing-and-metrics-with-langfuse/langfuse-users.png&#34;
	width=&#34;1060&#34;
	height=&#34;599&#34;
	srcset=&#34;https://dotnetuniversity.com/genai-observability-in-.net-logging-tracing-and-metrics-with-langfuse/langfuse-users_hu_af72b2cefb5a0b51.png 480w, https://dotnetuniversity.com/genai-observability-in-.net-logging-tracing-and-metrics-with-langfuse/langfuse-users_hu_92ca6d0fe9cd62fe.png 1024w&#34;
	loading=&#34;lazy&#34;
	
		alt=&#34;Langfuse users view showing demo-user activity and event counts&#34;
	
	
		class=&#34;gallery-image&#34; 
		data-flex-grow=&#34;176&#34;
		data-flex-basis=&#34;424px&#34;
	
&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Metrics&lt;/strong&gt;: Custom metrics like request count, latency histograms, and token counters integrate with Prometheus for alerting. Set an alert when token consumption exceeds a threshold or when p95 latency goes above 5 seconds.&lt;/p&gt;
&lt;h2 id=&#34;connecting-logs-to-traces&#34;&gt;Connecting Logs to Traces
&lt;/h2&gt;&lt;p&gt;The structured logs emitted by &lt;code&gt;ILogger&amp;lt;ChatService&amp;gt;&lt;/code&gt; contain &lt;code&gt;SessionId&lt;/code&gt; and &lt;code&gt;UserId&lt;/code&gt; properties. When combined with OpenTelemetry logging, each log record inherits the trace ID from the current activity context. This means you can jump from a Langfuse trace directly to the correlating log entries in Loki or Elasticsearch, even when the trace includes tool calls.&lt;/p&gt;
&lt;p&gt;To enable OpenTelemetry logging, add the OTLP log exporter in &lt;code&gt;Program.cs&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;copilot-prompt text-code-block&#34;&gt;&lt;div class=&#34;copilot-prompt__header&#34;&gt;&lt;span class=&#34;text-code-block__language&#34;&gt;csharp&lt;/span&gt;&lt;/div&gt;&lt;div class=&#34;copilot-prompt__body&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;div style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;
&lt;table style=&#34;border-spacing:0;padding:0;margin:0;border:0;&#34;&gt;&lt;tr&gt;&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code&gt;&lt;span style=&#34;white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f849c&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td style=&#34;vertical-align:top;padding:0;margin:0;border:0;;width:100%&#34;&gt;
&lt;pre tabindex=&#34;0&#34; style=&#34;color:#cdd6f4;background-color:#1e1e2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-csharp&#34; data-lang=&#34;csharp&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;builder.Logging.AddOpenTelemetry(logging =&amp;gt; logging.AddOtlpExporter());&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;With that in place, every &lt;code&gt;LogInformation&lt;/code&gt; call is enriched with &lt;code&gt;TraceId&lt;/code&gt;, &lt;code&gt;SpanId&lt;/code&gt;, and resource attributes automatically.&lt;/p&gt;
&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion
&lt;/h2&gt;&lt;p&gt;LLM observability needs more than generic HTTP tracing. Langfuse gives .NET developers a purpose-built platform that understands GenAI semantics — model names, token counts, prompt/response pairs, temperature, finish reasons, and tool-call spans — all through standard OpenTelemetry exporters.&lt;/p&gt;
&lt;p&gt;The integration with &lt;strong&gt;Semantic Kernel&lt;/strong&gt; means you don&amp;rsquo;t need to choose between clean abstractions and deep observability. You get both: the kernel handles chat completions and the tool-calling loop, while OpenTelemetry and Langfuse capture the full execution graph in the dashboard.&lt;/p&gt;
&lt;h2 id=&#34;you-can-find-the-sample-code-in-this-repository&#34;&gt;You can find the sample code in this repository:
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://github.com/meysamhadeli/blog-samples/tree/main/src/langfuse-genai-observability-sample&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;https://github.com/meysamhadeli/blog-samples/tree/main/src/langfuse-genai-observability-sample&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;reference&#34;&gt;Reference
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://langfuse.com/docs&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;https://langfuse.com/docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://github.com/lukaszzborek/langfuse-dotnet&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;https://github.com/lukaszzborek/langfuse-dotnet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://learn.microsoft.com/en-us/semantic-kernel/overview/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;https://learn.microsoft.com/en-us/semantic-kernel/overview/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://opentelemetry.io/docs/specs/semconv/gen-ai/&#34;  target=&#34;_blank&#34; rel=&#34;noopener&#34;
    &gt;https://opentelemetry.io/docs/specs/semconv/gen-ai/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
        </item>
        
    </channel>
</rss>
