<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title>svnscha - csharp</title>
    <subtitle>automating annoying tasks, sharing tips, and embracing less frustration</subtitle>
    <link rel="self" type="application/atom+xml" href="https://svnscha.de/tags/csharp/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://svnscha.de"/>
    <generator uri="https://astro.build/">Astro</generator>
    <updated>2026-01-04T00:00:00+00:00</updated>
    <id>https://svnscha.de/tags/csharp/atom.xml</id>
    <entry xml:lang="en">
        <title>AI Agents in .NET: Beyond DevUI - LibreChat Integration</title>
        <published>2026-01-04T00:00:00+00:00</published>
        <updated>2026-01-04T00:00:00+00:00</updated>
        <author>
          <name>Sven Scharmentke</name>
        </author>
        <link rel="alternate" type="text/html" href="https://svnscha.de/posts/ai-agents-dotnet-part-2/"/>
        <id>https://svnscha.de/posts/ai-agents-dotnet-part-2/</id>
        <summary type="html">Exposing .NET agents through OpenAI-compatible endpoints and connecting them to LibreChat.</summary>
        <content type="html" xml:base="https://svnscha.de/posts/ai-agents-dotnet-part-2/">&lt;p&gt;Welcome back to the AI Agents in .NET series. In the &lt;a href=&quot;/posts/ai-agents-dotnet-intro/&quot;&gt;introduction&lt;/a&gt;, I built a basic conversational agent. In &lt;a href=&quot;/posts/ai-agents-dotnet-part-1/&quot;&gt;Part 1&lt;/a&gt;, I added database persistence, embeddings, and semantic search capabilities.&lt;/p&gt;
&lt;p&gt;Today I'm breaking free from the built-in DevUI. Time to escape the developer dungeon.&lt;/p&gt;
&lt;p&gt;Don't get me wrong - DevUI is fantastic for development and debugging.&lt;/p&gt;
&lt;p&gt;This post connects those agents to LibreChat through OpenAI-compatible endpoints.&lt;/p&gt;
&lt;p&gt;The first version failed when a tool-calling agent was used through a downstream client. The solution was a reusable middleware pattern called &lt;code&gt;ToolCallFilterAgent&lt;/code&gt;, which I explain below.&lt;/p&gt;
&lt;h2 id=&quot;the-problem-with-devui&quot;&gt;The Problem with DevUI&lt;/h2&gt;
&lt;p&gt;DevUI served me well. It let me test agents, inspect tool calls, and debug issues. But it has limitations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Single-purpose&lt;/strong&gt;: It's a development tool, not a production interface&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No multi-user support&lt;/strong&gt;: One conversation at a time&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Limited customization&lt;/strong&gt;: You get what you get&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For real-world use, I need something more flexible. And rather than building my own chat UI (been there, done that in early 2025, don't want to repeat), let's leverage existing open-source solutions.&lt;/p&gt;
&lt;h2 id=&quot;why-librechat&quot;&gt;Why LibreChat?&lt;/h2&gt;
&lt;p&gt;I mentioned LibreChat in my &lt;a href=&quot;/posts/dgx-spark-hello-word/&quot;&gt;DGX Spark post&lt;/a&gt;, and I really enjoy it. Like, really like it.&lt;/p&gt;
&lt;p&gt;It's:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;OpenAI API compatible&lt;/strong&gt;: Speaks the same language as ChatGPT&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Self-hosted&lt;/strong&gt;: Your data stays yours (take &lt;em&gt;that&lt;/em&gt;, cloud overlords)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Feature-rich&lt;/strong&gt;: File uploads, conversations, presets, the whole shebang&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Beautiful&lt;/strong&gt;: Actually looks like something you'd want to use - not like a developer accidentally vomited JSON onto a webpage&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The key insight? LibreChat doesn't care what's behind the API. It just needs something that speaks OpenAI's protocol. And guess what the Microsoft Agent Framework already supports?&lt;/p&gt;
&lt;h2 id=&quot;the-plan&quot;&gt;The Plan&lt;/h2&gt;
&lt;p&gt;Here's what I'm building:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;%%{init: {&quot;theme&quot;: &quot;dark&quot;}}%%
flowchart LR
    LC[LibreChat] --&amp;gt;|OpenAI API| K[Knowledge API]
    K --&amp;gt;|Agent Framework| A[Agents]
    A --&amp;gt; KA[Knowledge]
    A --&amp;gt; KSA[KnowledgeSearch]
    A --&amp;gt; KTA[KnowledgeTitle]
    KSA --&amp;gt;|Vector Search| PG[(PostgreSQL)]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;LibreChat talks to our Knowledge API using the standard OpenAI chat completions format. Our API routes requests to our agents based on the &lt;code&gt;model&lt;/code&gt; parameter. From LibreChat's perspective, it's just another OpenAI-compatible endpoint with multiple models to choose from.&lt;/p&gt;
&lt;h2 id=&quot;removing-devui-embracing-swagger&quot;&gt;Removing DevUI, Embracing Swagger&lt;/h2&gt;
&lt;p&gt;First things first - I'm removing the DevUI dependency and redirecting to Swagger for API exploration. DevUI is great for debugging, but for an API-first approach, Swagger makes more sense. Plus, Swagger has that nice &quot;I'm a real API&quot; energy.&lt;/p&gt;
&lt;p&gt;The home page now redirects to Swagger:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cs&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;app&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;MapGet&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, () =&amp;gt; &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Results&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Redirect&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;/swagger&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;));&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Clean and simple. The API is now focused on being a backend service. Goodbye, training wheels.&lt;/p&gt;
&lt;h2 id=&quot;exposing-openai-compatible-endpoints&quot;&gt;Exposing OpenAI-Compatible Endpoints&lt;/h2&gt;
&lt;p&gt;The Agent Framework provides a beautiful &lt;code&gt;MapOpenAIChatCompletions&lt;/code&gt; method that exposes agents as OpenAI-compatible endpoints. Each agent gets its own endpoint path. It's almost too easy (foreshadowing...).&lt;/p&gt;
&lt;p&gt;I collect agent builders during registration and map them after building the app:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cs&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Collect agent builders for endpoint mapping&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; agentBuilders&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; List&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;IHostedAgentBuilder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;builder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;ConfigureKnowledgeDefaults&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;settings&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;logger&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) =&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    // ... OpenAI client setup ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    // Register agents and collect builders for endpoint mapping&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    builder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Services&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;AddSingleton&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;KnowledgeSearchAgent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    agentBuilders&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Add&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;builder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;AddAIAgent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Knowledge&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;services&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) =&amp;gt; &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        AgentFactory&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;CreateKnowledgeAgent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;chatClient&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;services&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    agentBuilders&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Add&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;builder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;AddAIAgent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;KnowledgeSearch&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;services&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) =&amp;gt; &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        AgentFactory&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;CreateKnowledgeSearchAgent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;chatClient&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;services&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; app&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;builder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Build&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// ... middleware setup ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Map OpenAI chat completions endpoint for each registered agent&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;foreach&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; agentBuilder&lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt; in&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; agentBuilders&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    app&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;MapOpenAIChatCompletions&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;agentBuilder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now each agent has its own endpoint:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/Knowledge/v1/chat/completions&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/KnowledgeSearch/v1/chat/completions&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Pretty slick, right?&lt;/p&gt;
&lt;h2 id=&quot;setting-up-librechat&quot;&gt;Setting Up LibreChat&lt;/h2&gt;
&lt;p&gt;Before I go further, let me get LibreChat running so I can test this integration. If you don't have LibreChat running yet, here's the quick setup. (If you do, feel free to skip ahead and judge my configuration choices.)&lt;/p&gt;
&lt;h3 id=&quot;docker-compose&quot;&gt;Docker Compose&lt;/h3&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;mkdir&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; -p&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; ~/librechat&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;cd&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; ~/librechat&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;git&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; clone&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; https://github.com/danny-avila/LibreChat.git&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; .&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;cp&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; .env.example&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; .env&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;the-librechat-configuration&quot;&gt;The LibreChat Configuration&lt;/h3&gt;
&lt;p&gt;Create &lt;code&gt;librechat.yaml&lt;/code&gt; and point LibreChat at the API:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;yaml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;version&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1.2.8&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;cache&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;endpoints&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  custom&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    - &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Agent Framework&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      apiKey&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;not-used-but-required&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      baseURL&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;http://host.docker.internal:5000/v1&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      models&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        default&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: [&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Knowledge&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;KnowledgeSearch&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        fetch&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;false&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      titleConvo&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;false&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      summarize&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;false&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      forcePrompt&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;false&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      modelDisplayLabel&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Agent Framework&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      iconURL&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;https://svnscha.de/svnscha.webp&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;docker-compose-override&quot;&gt;Docker Compose Override&lt;/h3&gt;
&lt;p&gt;Create &lt;code&gt;docker-compose.override.yml&lt;/code&gt; to mount the config:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;yaml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;services&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  api&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    volumes&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;type&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;bind&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        source&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;./librechat.yaml&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        target&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;/app/librechat.yaml&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    extra_hosts&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;      - &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;host.docker.internal:host-gateway&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;extra_hosts&lt;/code&gt; line is crucial - it lets containers reach the host machine where the Knowledge API runs.&lt;/p&gt;
&lt;h3 id=&quot;fire-it-up&quot;&gt;Fire It Up&lt;/h3&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;docker&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; compose&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; up&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; -d&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Navigate to &lt;code&gt;http://localhost:3080&lt;/code&gt;, create an account (it's local, don't worry), and you should see &quot;Agent Framework&quot; in the endpoint list.&lt;/p&gt;
&lt;p&gt;But wait - if you select &quot;Knowledge&quot; and send a message, LibreChat hits &lt;code&gt;/v1/chat/completions&lt;/code&gt; with &lt;code&gt;&quot;model&quot;: &quot;Knowledge&quot;&lt;/code&gt;. My per-agent endpoints live at &lt;code&gt;/Knowledge/v1/chat/completions&lt;/code&gt; instead. Houston, we have a routing problem.&lt;/p&gt;
&lt;h2 id=&quot;the-model-routing-middleware&quot;&gt;The Model Routing Middleware&lt;/h2&gt;
&lt;p&gt;LibreChat (and most OpenAI clients) expect a single endpoint at &lt;code&gt;/v1/chat/completions&lt;/code&gt; where the &lt;code&gt;model&lt;/code&gt; parameter determines the underlying model but in this scenario it should determines routing:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;json&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  &quot;model&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Knowledge&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;  &quot;messages&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: [{&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;&quot;role&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;user&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;&quot;content&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;What's on your mind?&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I need middleware that reads the &lt;code&gt;model&lt;/code&gt; from the request body and rewrites the path to the agent-specific endpoint. Nothing fancy, just some good old path mangling:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cs&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Rewrite /v1/chat/completions to /{model}/v1/chat/completions based on request body&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;app&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Use&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;context&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;next&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) =&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; path&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;context&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Request&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Path&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Value&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;path&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;?.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Equals&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;/v1/chat/completions&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;StringComparison&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;OrdinalIgnoreCase&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) == &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        context&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Request&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;EnableBuffering&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        using&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; reader&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; StreamReader&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;context&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Request&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Body&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;leaveOpen&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; body&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; reader&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;ReadToEndAsync&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        context&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Request&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Body&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Position&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        string&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;? &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;model&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (!&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;IsNullOrEmpty&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;body&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;            try&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;                var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; json&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;System&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Text&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Json&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;JsonDocument&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Parse&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;body&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;                if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;json&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;RootElement&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;TryGetProperty&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;model&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;out&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; modelElement&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;                {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;                    model&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;modelElement&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;GetString&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;                }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;            catch&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;System&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Text&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Json&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;JsonException&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;IsNullOrWhiteSpace&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;model&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;            var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; logger&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;context&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;RequestServices&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;GetRequiredService&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;ILogger&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Program&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;gt;&amp;gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;            logger&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;LogWarning&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Chat completions request missing required 'model' field&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;            context&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Response&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;StatusCode&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;StatusCodes&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Status400BadRequest&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;            context&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Response&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;ContentType&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;application/json&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;            await&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; context&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Response&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;WriteAsJsonAsync&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;                error&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;                    message&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;The 'model' field is required&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;                    type&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;invalid_request_error&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;                    param&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;model&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;                    code&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;missing_required_parameter&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;                } &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;            return&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        context&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Request&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Path&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;$&quot;/&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;model&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;/v1/chat/completions&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    await&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; next&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The key points:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Enable buffering&lt;/strong&gt;: I need to read the body to extract &lt;code&gt;model&lt;/code&gt;, then reset it for the actual handler&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Path rewriting&lt;/strong&gt;: Transform &lt;code&gt;/v1/chat/completions&lt;/code&gt; → &lt;code&gt;/{model}/v1/chat/completions&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Error handling&lt;/strong&gt;: Return a proper OpenAI-style error if &lt;code&gt;model&lt;/code&gt; is missing&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now when LibreChat sends a request, it gets routed to the right agent automatically. Back to LibreChat - select &quot;Knowledge&quot; from the dropdown, send a message, and... it works! Streaming responses, proper formatting, everything.&lt;/p&gt;
&lt;p&gt;At this point, I thought I was done. Celebrated with a coffee. Time to test &lt;code&gt;KnowledgeSearch&lt;/code&gt;...&lt;/p&gt;
&lt;h2 id=&quot;the-tool-call-disaster-and-how-i-fixed-it&quot;&gt;The Tool Call Disaster (And How I Fixed It)&lt;/h2&gt;
&lt;p&gt;I switched to the &lt;code&gt;KnowledgeSearch&lt;/code&gt; model in LibreChat, asked it to search for something, and... nothing. The request just hung. Then cancelled. No error message. No response. Just the cold, judgmental silence of broken software.&lt;/p&gt;
&lt;p&gt;I dug through LibreChat's source code, traced the request flow, added logging everywhere. Console.WriteLine debugging like it's 2005.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note: We should get to Observability very soon...&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;the-problem&quot;&gt;The Problem&lt;/h3&gt;
&lt;p&gt;My &lt;code&gt;KnowledgeSearch&lt;/code&gt; agent uses tools - specifically the &lt;code&gt;SearchConversationHistory&lt;/code&gt; function. When the agent executes a tool, the response stream includes &lt;code&gt;FunctionCallContent&lt;/code&gt; and &lt;code&gt;FunctionResultContent&lt;/code&gt; alongside the text. This is how the Agent Framework communicates &quot;I'm calling a tool&quot; and &quot;here's what the tool returned.&quot;&lt;/p&gt;
&lt;p&gt;LibreChat receives the stream and interprets the function call as something it should execute. That fails because the function exists in the backend rather than in LibreChat, so LibreChat cancels the request.&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[Our Agent] → &quot;I'll search for that&quot; → FunctionCallContent{SearchConversationHistory}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[Our Agent] → [executes tool internally]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[Our Agent] → FunctionResultContent{results...}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[Our Agent] → &quot;Based on the search, here's what I found...&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          ↓&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[LibreChat] → Sees FunctionCallContent → &quot;I need to call this function!&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[LibreChat] → Can't find function → Cancels request&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          ↓&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;[User] → &quot;Why isn't anything happening?&quot; 😤&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The frustrating part? You can't disable this behavior in LibreChat. It's doing the right thing for its use case - if &lt;em&gt;it&lt;/em&gt; sends a function call to an upstream model, it expects to handle the response. But I'm the upstream model, and I've already handled my own tool calls. We're both right, and that's the most annoying kind of bug.&lt;/p&gt;
&lt;h3 id=&quot;the-solution&quot;&gt;The Solution&lt;/h3&gt;
&lt;p&gt;Don't send tool call content downstream. Filter it out before it leaves the API. If LibreChat never sees the tool calls, it can't get confused by them. &lt;em&gt;taps forehead&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Meet &lt;code&gt;ToolCallFilterAgent&lt;/code&gt; - a delegating agent that wraps any agent and strips &lt;code&gt;FunctionCallContent&lt;/code&gt; and &lt;code&gt;FunctionResultContent&lt;/code&gt; from responses. It's like a bouncer for your API responses:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cs&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;/// &lt;/span&gt;&lt;span style=&quot;color:#808080&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;summary&lt;/span&gt;&lt;span style=&quot;color:#808080&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;/// A delegating agent that filters out tool call content from responses.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;/// This prevents downstream consumers from seeing FunctionCallContent and FunctionResultContent&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;/// that they cannot execute.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;/// &lt;/span&gt;&lt;span style=&quot;color:#808080&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;summary&lt;/span&gt;&lt;span style=&quot;color:#808080&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; sealed&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; class&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; ToolCallFilterAgent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; : &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;DelegatingAIAgent&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    public&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; ToolCallFilterAgent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;AIAgent&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; innerAgent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) : &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;base&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;innerAgent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) { }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    public&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; override&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; async&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; Task&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;AgentRunResponse&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;gt; &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;RunAsync&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;        IEnumerable&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;ChatMessage&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;gt; &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;messages&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;        AgentThread&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;? &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;thread&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;        AgentRunOptions&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;? &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;options&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;        CancellationToken&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; cancellationToken&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; response&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; InnerAgent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;RunAsync&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;messages&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;thread&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;options&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;cancellationToken&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        response&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Messages&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;FilterToolCalls&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;response&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Messages&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; response&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    public&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; override&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; async&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; IAsyncEnumerable&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;AgentRunResponseUpdate&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;gt; &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;RunStreamingAsync&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;        IEnumerable&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;ChatMessage&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;gt; &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;messages&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;        AgentThread&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;? &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;thread&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;        AgentRunOptions&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;? &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;options&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        [&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;EnumeratorCancellation&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;] &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;CancellationToken&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; cancellationToken&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        await&lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt; foreach&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; update&lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt; in&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; InnerAgent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;RunStreamingAsync&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;messages&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;thread&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;options&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;cancellationToken&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;            yield&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; return&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; FilterToolCalls&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;update&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    private&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; static&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; IList&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;ChatMessage&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;gt; &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;FilterToolCalls&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;IEnumerable&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;ChatMessage&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;gt; &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;messages&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) =&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        messages&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Select&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;m&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; =&amp;gt; &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; ChatMessage&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;m&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Role&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;            m&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Contents&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Where&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;c&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; =&amp;gt; &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;c&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; is&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; FunctionCallContent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &amp;amp;&amp;amp; &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;c&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; is&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; FunctionResultContent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;ToList&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        )).&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;ToList&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    private&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; static&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; AgentRunResponseUpdate&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; FilterToolCalls&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;AgentRunResponseUpdate&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; update&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) =&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        new&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;update&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Role&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;update&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Contents&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Where&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;c&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; =&amp;gt; &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;c&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; is&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; FunctionCallContent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &amp;amp;&amp;amp; &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;c&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; is&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; not&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; FunctionResultContent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;ToList&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;());&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now in the &lt;code&gt;AgentFactory&lt;/code&gt;, I wrap &lt;code&gt;KnowledgeSearchAgent&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cs&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; static&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; AIAgent&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; CreateKnowledgeSearchAgent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;IChatClient&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; chatClient&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;IServiceProvider&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; services&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; key&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; searchAgent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;services&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;GetRequiredService&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;KnowledgeSearchAgent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; agent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;chatClient&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;CreateAIAgent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; ChatClientAgentOptions&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        Id&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        Name&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        ChatOptions&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; ChatOptions&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;            ConversationId&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;global&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;            Instructions&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;KnowledgeSearchSystemPrompt&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;            Tools&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = [&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;AIFunctionFactory&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Create&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;searchAgent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;SearchConversationHistoryAsync&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;SearchConversationHistory&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;            ToolMode&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;ChatToolMode&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Auto&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    // Wrap with filter to prevent downstream consumers from seeing tool calls they can't execute&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;    return&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; ToolCallFilterAgent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;agent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The agent still uses tools internally, but clients only see the final text response. Clean and transparent. What happens in the backend stays in the backend.&lt;/p&gt;
&lt;p&gt;Back to LibreChat - &lt;code&gt;KnowledgeSearch&lt;/code&gt; now works! The agent searches, finds results, and responds - all without LibreChat ever knowing tools were involved. It's like magic, except it's just careful filtering.&lt;/p&gt;
&lt;h2 id=&quot;title-generation-with-knowledgetitleagent&quot;&gt;Title Generation with KnowledgeTitleAgent&lt;/h2&gt;
&lt;p&gt;LibreChat has a feature called &lt;code&gt;titleConvo&lt;/code&gt; - it automatically generates titles for conversations using the AI. But my main agents have tools and complex system prompts that are overkill for simple title generation. It's like using a flamethrower to light a candle.&lt;/p&gt;
&lt;p&gt;The solution: a dedicated title agent. It's intentionally simple - no tools, no embeddings, just a focused system prompt:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cs&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;/// &lt;/span&gt;&lt;span style=&quot;color:#808080&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;summary&lt;/span&gt;&lt;span style=&quot;color:#808080&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;/// Simple agent for generating conversation titles.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;/// No tools, no embeddings - just a basic helpful assistant.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;/// Designed for use with LibreChat's title generation feature.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;/// &lt;/span&gt;&lt;span style=&quot;color:#808080&quot;&gt;&amp;lt;/&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;summary&lt;/span&gt;&lt;span style=&quot;color:#808080&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; static&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; class&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; KnowledgeTitleAgent&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    private&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; const&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; TitleSystemPrompt&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;@&quot;You are a helpful assistant that generates concise, descriptive titles for conversations.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;When given conversation content, create a brief title (3-7 words) that captures the main topic or purpose.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;Be specific and informative. Avoid generic titles like 'Chat' or 'Conversation'.&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    public&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; static&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; ChatClientAgent&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; Create&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;IChatClient&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; chatClient&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;IServiceProvider&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; services&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; key&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        return&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; chatClient&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;CreateAIAgent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; ChatClientAgentOptions&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;            Id&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;            Name&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;            ChatOptions&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; ChatOptions&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;                ConversationId&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;global&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;                Instructions&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;TitleSystemPrompt&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Register it alongside the other agents:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cs&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;agentBuilders&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Add&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;builder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;AddAIAgent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;KnowledgeTitle&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;services&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) =&amp;gt; &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    AgentFactory&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;CreateKnowledgeTitleAgent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;chatClient&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;services&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)));&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now update &lt;code&gt;librechat.yaml&lt;/code&gt; to use the title agent:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;yaml&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;version&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1.2.8&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;cache&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;endpoints&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  custom&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    - &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;name&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Agent Framework&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      apiKey&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;not-used-but-required&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      baseURL&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;http://host.docker.internal:5000/v1&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      models&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        default&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: [&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Knowledge&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;KnowledgeSearch&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;KnowledgeTitle&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        fetch&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;false&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      titleConvo&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      titleModel&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;KnowledgeTitle&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      summarize&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;false&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      forcePrompt&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;false&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      modelDisplayLabel&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Agent Framework&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;      iconURL&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;https://svnscha.de/svnscha.webp&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now LibreChat uses &lt;code&gt;KnowledgeTitle&lt;/code&gt; specifically for generating conversation titles, while &lt;code&gt;Knowledge&lt;/code&gt; and &lt;code&gt;KnowledgeSearch&lt;/code&gt; handle the actual conversations.&lt;/p&gt;
&lt;h2 id=&quot;the-complete-programcs&quot;&gt;The Complete Program.cs&lt;/h2&gt;
&lt;p&gt;Here's what &lt;code&gt;Program.cs&lt;/code&gt; looks like after all these changes:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cs&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;using&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; System&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;ClientModel&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;using&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; Knowledge&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Services&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;using&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; Knowledge&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Shared&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Agents&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;using&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; Knowledge&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Shared&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Extensions&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;using&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; Microsoft&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Agents&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;AI&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Hosting&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;using&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; Microsoft&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Extensions&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;AI&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;using&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; OpenAI&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; builder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;WebApplication&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;CreateBuilder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;args&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Collect agent builders for endpoint mapping&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; agentBuilders&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; List&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;IHostedAgentBuilder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;builder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;ConfigureKnowledgeDefaults&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;settings&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;logger&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) =&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;IsNullOrWhiteSpace&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;settings&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;ApiKey&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        logger&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;LogWarning&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;No API key configured. Set Knowledge:ApiKey in user secrets or environment.&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; options&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; OpenAIClientOptions&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (!&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;IsNullOrEmpty&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;settings&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;ApiEndpoint&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        options&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Endpoint&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; Uri&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;settings&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;ApiEndpoint&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; client&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; OpenAIClient&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; ApiKeyCredential&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;settings&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;ApiKey&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;), &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;options&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; chatClient&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;client&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;GetChatClient&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;gpt-4.1&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;AsIChatClient&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    // Configure embedding service&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; embeddingClient&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;client&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;GetEmbeddingClient&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;text-embedding-3-small&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;AsIEmbeddingGenerator&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    builder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Services&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;AddEmbeddingService&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;embeddingClient&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    // Register agents and collect builders for endpoint mapping&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    builder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Services&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;AddSingleton&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;KnowledgeSearchAgent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    agentBuilders&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Add&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;builder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;AddAIAgent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Knowledge&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;services&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) =&amp;gt; &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        AgentFactory&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;CreateKnowledgeAgent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;chatClient&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;services&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    agentBuilders&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Add&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;builder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;AddAIAgent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;KnowledgeSearch&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;services&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) =&amp;gt; &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        AgentFactory&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;CreateKnowledgeSearchAgent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;chatClient&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;services&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    agentBuilders&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Add&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;builder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;AddAIAgent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;KnowledgeTitle&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;services&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) =&amp;gt; &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        AgentFactory&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;CreateKnowledgeTitleAgent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;chatClient&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;services&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Register background service for embedding processing&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;builder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Services&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;AddHostedService&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;EmbeddingBackgroundService&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;builder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Services&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;AddOpenAIResponses&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;builder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Services&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;AddOpenAIConversations&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; app&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;builder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Build&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Rewrite /v1/chat/completions to /{model}/v1/chat/completions based on request body&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;app&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Use&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;async&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;context&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;next&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) =&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; path&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;context&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Request&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Path&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Value&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;    if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;path&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;?.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Equals&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;/v1/chat/completions&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;StringComparison&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;OrdinalIgnoreCase&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) == &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        context&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Request&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;EnableBuffering&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        using&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; reader&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; StreamReader&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;context&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Request&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Body&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;leaveOpen&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;true&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; body&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; reader&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;ReadToEndAsync&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        context&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Request&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Body&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Position&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;        string&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;? &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;model&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (!&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;IsNullOrEmpty&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;body&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;            try&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;                var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; json&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;System&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Text&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Json&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;JsonDocument&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Parse&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;body&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;                if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;json&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;RootElement&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;TryGetProperty&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;model&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;out&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; modelElement&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;                {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;                    model&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;modelElement&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;GetString&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;                }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;            catch&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;System&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Text&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Json&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;JsonException&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) { }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;        if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;IsNullOrWhiteSpace&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;model&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;            var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; logger&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;context&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;RequestServices&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;GetRequiredService&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;ILogger&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Program&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;gt;&amp;gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;            logger&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;LogWarning&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Chat completions request missing required 'model' field&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;            context&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Response&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;StatusCode&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;StatusCodes&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Status400BadRequest&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;            context&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Response&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;ContentType&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;application/json&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;            await&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; context&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Response&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;WriteAsJsonAsync&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;                error&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;                    message&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;The 'model' field is required&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;                    type&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;invalid_request_error&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;                    param&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;model&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;                    code&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;missing_required_parameter&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;                } &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;            });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;            return&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        context&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Request&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Path&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;$&quot;/&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;model&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;/v1/chat/completions&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    await&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; next&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;});&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;app&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;UseRouting&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;app&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;ConfigureKnowledgePipeline&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Map OpenAI chat completions endpoint for each registered agent&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;foreach&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; agentBuilder&lt;/span&gt;&lt;span style=&quot;color:#C586C0&quot;&gt; in&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; agentBuilders&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    app&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;MapOpenAIChatCompletions&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;agentBuilder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;app&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;MapGet&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, () =&amp;gt; &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Results&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Redirect&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;/swagger&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;app&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;LogStartupComplete&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;app&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Run&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's it. No CORS configuration needed (LibreChat makes server-side requests). No custom request/response models. No manual streaming code. The framework does the heavy lifting - I just wire it up. Sometimes the best code is the code you don't have to write.&lt;/p&gt;
&lt;p&gt;The key insights:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Each agent gets its own endpoint&lt;/strong&gt; via &lt;code&gt;MapOpenAIChatCompletions&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Inline middleware handles routing&lt;/strong&gt; from &lt;code&gt;/v1/chat/completions&lt;/code&gt; based on &lt;code&gt;model&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ToolCallFilterAgent hides internal tool execution&lt;/strong&gt; from downstream clients&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;KnowledgeTitleAgent handles title generation&lt;/strong&gt; without tool complexity&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;librechat-test&quot;&gt;LibreChat Test&lt;/h3&gt;
&lt;p&gt;Open LibreChat, select &quot;Agent Framework&quot; as the endpoint, choose a model from the dropdown (&lt;code&gt;Knowledge&lt;/code&gt;, &lt;code&gt;KnowledgeSearch&lt;/code&gt;, or &lt;code&gt;KnowledgeTitle&lt;/code&gt;), and start chatting. You should see:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Streaming responses (that satisfying typing effect)&lt;/li&gt;
&lt;li&gt;Tool calls working transparently (KnowledgeSearch uses tools, but you only see the results)&lt;/li&gt;
&lt;li&gt;Automatic title generation (thanks to KnowledgeTitle)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/screenshots/2026-01-04-agent-filter-toolcalls.png&quot; alt=&quot;Image&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;architecture-overview&quot;&gt;Architecture Overview&lt;/h2&gt;
&lt;p&gt;Let me step back and look at what I've built (and maybe pat myself on the back a little):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;%%{init: {&quot;theme&quot;: &quot;dark&quot;}}%%
flowchart TB
    subgraph Clients
        LC[LibreChat]
        API[Direct API Calls]
    end

    subgraph &quot;Knowledge API&quot;
        MR[Model Router Middleware]
        E1[&quot;/Knowledge/v1/chat/completions&quot;]
        E2[&quot;/KnowledgeSearch/v1/chat/completions&quot;]
        E3[&quot;/KnowledgeTitle/v1/chat/completions&quot;]
    end

    subgraph Agents
        KA[Knowledge Agent]
        KSA[KnowledgeSearch Agent]
        KTA[KnowledgeTitle Agent]
        TCF[ToolCallFilterAgent]
    end

    subgraph Storage
        PG[(PostgreSQL)]
    end

    LC --&amp;gt;|&quot;/v1/chat/completions&quot;| MR
    API --&amp;gt; MR
    MR --&amp;gt;|&quot;model: Knowledge&quot;| E1
    MR --&amp;gt;|&quot;model: KnowledgeSearch&quot;| E2
    MR --&amp;gt;|&quot;model: KnowledgeTitle&quot;| E3
    E1 --&amp;gt; KA
    E2 --&amp;gt; TCF
    TCF --&amp;gt; KSA
    E3 --&amp;gt; KTA
    KSA --&amp;gt;|Vector Search| PG
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The flow is clean:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Request comes in to &lt;code&gt;/v1/chat/completions&lt;/code&gt; with &lt;code&gt;&quot;model&quot;: &quot;KnowledgeSearch&quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Middleware rewrites path to &lt;code&gt;/KnowledgeSearch/v1/chat/completions&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Framework routes to the agent, which is wrapped in &lt;code&gt;ToolCallFilterAgent&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Agent executes tools internally, filter strips tool content from response&lt;/li&gt;
&lt;li&gt;Client sees clean text output&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Multiple clients, multiple agents, one simple routing pattern. It's almost... elegant?&lt;/p&gt;
&lt;p&gt;Swagger is still available at &lt;code&gt;/swagger&lt;/code&gt; for API exploration and testing.&lt;/p&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;
&lt;p&gt;I've taken the agents from a development-only DevUI to something that can serve real users through a proper chat interface. And I did it with surprisingly little code - plus one hard-won debugging lesson that I'm still a bit salty about.&lt;/p&gt;
&lt;p&gt;The main points are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;MapOpenAIChatCompletions&lt;/code&gt; does the heavy lifting&lt;/strong&gt; - one line per agent, full OpenAI compatibility&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Each agent gets its own endpoint&lt;/strong&gt; - clean separation, easy to test directly&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Simple inline middleware handles routing&lt;/strong&gt; - LibreChat sends to &lt;code&gt;/v1/chat/completions&lt;/code&gt;, I rewrite to &lt;code&gt;/{model}/v1/chat/completions&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tool-calling agents need filtering&lt;/strong&gt; - the &lt;code&gt;ToolCallFilterAgent&lt;/code&gt; pattern is essential when exposing agents to downstream clients that don't understand your internal tool calls.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dedicated agents for specific tasks&lt;/strong&gt; - &lt;code&gt;KnowledgeTitleAgent&lt;/code&gt; for titles, &lt;code&gt;KnowledgeSearch&lt;/code&gt; for search, &lt;code&gt;Knowledge&lt;/code&gt; for general chat&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;next-steps&quot;&gt;Next Steps&lt;/h2&gt;
&lt;p&gt;I've got a solid foundation now - persistence, embeddings, semantic search, a proper UI, and automatic title generation. But there's more to explore:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Observability&lt;/strong&gt;: OpenTelemetry, Jaeger, understanding what's happening at scale&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Agent Loop&lt;/strong&gt;: Exploring the magic behind automated AI Agents&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Authentication&lt;/strong&gt;: Proper API key validation for production deployments (because &quot;meh, whatever&quot; isn't a security strategy)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But first, I'm letting this settle. Play with LibreChat, see how your agents perform with real conversations, and notice what's missing. The best features come from actual use - not from staring at code and imagining what users might want.&lt;/p&gt;
&lt;p&gt;See you in the next post. 🚀&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;The companion repository has been updated with the &lt;code&gt;part/02-connect-librechat&lt;/code&gt; branch containing all the code from this post. &lt;a href=&quot;https://github.com/svnscha/knowledge/tree/part/02-connect-librechat&quot;&gt;Check it out on GitHub&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</content>
    </entry>
    <entry xml:lang="en">
        <title>AI Agents in .NET: Building Agentic RAG</title>
        <published>2025-12-28T00:00:00+00:00</published>
        <updated>2025-12-28T00:00:00+00:00</updated>
        <author>
          <name>Sven Scharmentke</name>
        </author>
        <link rel="alternate" type="text/html" href="https://svnscha.de/posts/ai-agents-dotnet-part-1/"/>
        <id>https://svnscha.de/posts/ai-agents-dotnet-part-1/</id>
        <summary type="html">Taking our agent from 'hello world' to useful - with database persistence, embeddings, vector search, and tools that let it reason over documentation and conversation history.</summary>
        <content type="html" xml:base="https://svnscha.de/posts/ai-agents-dotnet-part-1/">&lt;p&gt;Welcome back. In the &lt;a href=&quot;/posts/ai-agents-dotnet-intro/&quot;&gt;first post&lt;/a&gt;, we set up a basic conversational agent. It worked. It responded. It was... fine.&lt;/p&gt;
&lt;p&gt;But let's be honest - an agent that can only chat is just a very expensive echo chamber. Today we're giving our agent actual knowledge. It will remember past conversations, search through its own message history, and - most importantly - decide &lt;em&gt;when&lt;/em&gt; to use that knowledge.&lt;/p&gt;
&lt;p&gt;This is going to be a long one. Grab coffee. Let's build the next piece of the puzzle.&lt;/p&gt;
&lt;h2 id=&quot;what-were-building&quot;&gt;What We're Building&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/screenshots/2025-12-28-knowledge-search.png&quot; alt=&quot;Knowledge Search Agent&quot;&gt;&lt;/p&gt;
&lt;p&gt;By the end of this post, we'll have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;A PostgreSQL database&lt;/strong&gt; for persisting messages and embeddings&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A second agent&lt;/strong&gt; (KnowledgeSearch) that searches conversation history&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tool calling&lt;/strong&gt; so our main agent can invoke KnowledgeSearch when needed&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Autonomous decision-making&lt;/strong&gt; about when to retrieve information vs. just respond&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That last point is the key difference between classic RAG and &lt;em&gt;agentic&lt;/em&gt; RAG. We're not blindly stuffing context into every prompt. The agent chooses when retrieval helps.&lt;/p&gt;
&lt;h2 id=&quot;a-quick-word-on-rag&quot;&gt;A Quick Word on RAG&lt;/h2&gt;
&lt;p&gt;RAG - Retrieval-Augmented Generation - has been the go-to pattern for giving LLMs access to external knowledge. The classic approach:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;User asks a question&lt;/li&gt;
&lt;li&gt;System searches a knowledge base&lt;/li&gt;
&lt;li&gt;Retrieved documents get stuffed into the prompt&lt;/li&gt;
&lt;li&gt;LLM generates a response using that context&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;It works. But it's also... dumb? Every query triggers retrieval, whether needed or not. Ask &quot;What's the capital of France?&quot; and you're still embedding the question, searching vectors, retrieving documents. Wasteful.&lt;/p&gt;
&lt;p&gt;Agentic RAG flips this. The agent has &lt;em&gt;tools&lt;/em&gt; for retrieval and decides when to use them. Simple questions get simple answers. Complex questions trigger the agent to go digging. Much more elegant.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Heads up&lt;/strong&gt;: There's a whole universe of RAG variations out there - hybrid RAG combining vector search with full-text search (FTS), reranking strategies, query expansion, and more. If you go down the rabbit hole, you'll find dozens of ways to improve retrieval quality. We're intentionally skipping all of that here to focus on the &lt;em&gt;agentic&lt;/em&gt; part - how an agent decides when and how to retrieve. Once you grok that, feel free to explore the retrieval optimizations.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Enough theory. Let's write some code.&lt;/p&gt;
&lt;h2 id=&quot;database-foundation&quot;&gt;Database Foundation&lt;/h2&gt;
&lt;p&gt;Our agent needs a place to store things - messages, embeddings, knowledge. PostgreSQL with pgvector gives us a solid foundation.&lt;/p&gt;
&lt;h3 id=&quot;the-schema&quot;&gt;The Schema&lt;/h3&gt;
&lt;p&gt;We're keeping it simple: messages for chat history, embeddings for semantic search.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;%%{init: {
  &quot;theme&quot;: &quot;dark&quot;,
  &quot;themeVariables&quot;: {
    &quot;fontFamily&quot;: &quot;Jetbrains Mono&quot;,
  },
  &quot;themeCSS&quot;: [
    &quot;.er.relationshipLine { stroke: #858585; }&quot;,
    &quot;.er.relationshipLabel { fill: #bbbbbb; }&quot;,
    &quot;.er.relationshipLabelBox { fill: transparent; }&quot;,
    &quot;.er.entityBox { fill: #252526; stroke: #303031; }&quot;,
    &quot;[id^=entity-messages] .er.entityBox { fill: #1e3a5f; stroke: #5abae0; }&quot;,
    &quot;[id^=entity-embeddings] .er.entityBox { fill: #451a03; stroke: #f59e0b; }&quot;
    ]
}}%%
erDiagram
    messages ||--o| embeddings : &quot;has&quot;
    messages {
        uuid id PK
        uuid conversation_id
        uuid embedding_id FK
        varchar role
        varchar author_name
        text content
        bigint sequence_number
        timestamp created_at
    }
    embeddings {
        uuid id PK
        varchar source_type
        uuid source_id
        text content
        vector vector_1536
        timestamp created_at
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A few notes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;conversation_id&lt;/strong&gt;: Groups messages by conversation/session - indexed for fast retrieval&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;role&lt;/strong&gt;: Maps to &lt;code&gt;ChatRole.Value&lt;/code&gt; from Microsoft.Extensions.AI (&lt;code&gt;user&lt;/code&gt;, &lt;code&gt;assistant&lt;/code&gt;, &lt;code&gt;system&lt;/code&gt;, &lt;code&gt;tool&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;sequence_number&lt;/strong&gt;: Auto-assigned by a database trigger - ensures correct ordering&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;source_type&lt;/strong&gt;: Polymorphic pattern - can reference &lt;code&gt;Message&lt;/code&gt; or any future entity (e.g., &lt;code&gt;DocumentChunk&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;vector_1536&lt;/strong&gt;: 1536-dimension embedding from OpenAI's &lt;code&gt;text-embedding-3-small&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;the-dbcontext&quot;&gt;The DbContext&lt;/h3&gt;
&lt;p&gt;Entity Framework Core handles the mapping. The key thing we need is pgvector support:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cs&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// KnowledgeDbContext.cs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;modelBuilder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;HasPostgresExtension&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;vector&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This single line enables the &lt;code&gt;vector&lt;/code&gt; data type in PostgreSQL. Without it, EF Core won't know what to do with our embedding vectors.&lt;/p&gt;
&lt;p&gt;For the &lt;code&gt;Embedding&lt;/code&gt; entity, we tell EF Core the exact column type:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cs&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// KnowledgeDbContext.cs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;entity&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Property&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; =&amp;gt; &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Vector&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;      .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;HasColumnType&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;vector(1536)&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The rest is standard EF Core configuration - indexes on &lt;code&gt;ConversationId&lt;/code&gt; (our primary query pattern), composite index on &lt;code&gt;ConversationId + SequenceNumber&lt;/code&gt; for ordered retrieval, and &lt;code&gt;SourceType + SourceId&lt;/code&gt; on embeddings for fast lookups.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Don't skip the indexes.&lt;/strong&gt; Without them, you're doing full table scans.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;wiring-it-up&quot;&gt;Wiring It Up&lt;/h3&gt;
&lt;p&gt;Database registration goes into our extension method. The important choice: &lt;code&gt;AddDbContextFactory&lt;/code&gt; instead of &lt;code&gt;AddDbContext&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cs&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// ServiceCollectionExtensions.cs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;services&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;AddDbContextFactory&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;KnowledgeDbContext&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;options&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    options&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;UseNpgsql&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;connectionString&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;npgsqlOptions&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; =&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        npgsqlOptions&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;UseVector&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();  &lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Enable pgvector&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    });&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Why the factory? Our message store and agents live outside the normal HTTP request lifecycle. The factory pattern lets each operation spin up its own short-lived context.&lt;/p&gt;
&lt;h3 id=&quot;migration-time&quot;&gt;Migration Time&lt;/h3&gt;
&lt;p&gt;EF Core migrations version our schema:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;cd&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; src/Knowledge&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;# Create the migration&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;dotnet&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; ef&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; migrations&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; add&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; InitialCreate&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; --project&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; ../Knowledge.Shared&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; --output-dir&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; Migrations&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;# Apply it&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;dotnet&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; ef&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; database&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; update&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; --project&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; ../Knowledge.Shared&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We also add a trigger migration for auto-incrementing sequence numbers:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cs&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// 20251224000001_AddCustomTriggers.cs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;migrationBuilder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Sql&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;&quot;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    CREATE OR REPLACE FUNCTION assign_message_sequence_number()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    RETURNS TRIGGER AS $$&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    BEGIN&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;        SELECT COALESCE(MAX(sequence_number), 0) + 1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;        INTO NEW.sequence_number&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;        FROM messages&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;        WHERE conversation_id = NEW.conversation_id;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;        RETURN NEW;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    END;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    $$ LANGUAGE plpgsql;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    &quot;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;migrationBuilder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Sql&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;&quot;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    CREATE TRIGGER trg_messages_sequence_number&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    BEFORE INSERT ON messages&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    FOR EACH ROW&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    WHEN (NEW.sequence_number = 0 OR NEW.sequence_number IS NULL)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    EXECUTE FUNCTION assign_message_sequence_number();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    &quot;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This assigns sequence numbers per conversation. The &lt;code&gt;WHERE conversation_id = NEW.conversation_id&lt;/code&gt; ensures each conversation has its own sequence. For high-concurrency production use, consider using a proper sequence/identity instead of &lt;code&gt;MAX()&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;the-conversation-workaround&quot;&gt;The Conversation Workaround&lt;/h3&gt;
&lt;p&gt;Before we get to the message store, there's a wrinkle. The framework's DevUI doesn't pass &lt;code&gt;AgentThread&lt;/code&gt; information to custom &lt;code&gt;ChatMessageStore&lt;/code&gt; implementations. No thread ID means no way to group messages by conversation using the framework's intended abstractions.&lt;/p&gt;
&lt;p&gt;We could give up on persistence entirely. Or we could be pragmatic.&lt;/p&gt;
&lt;p&gt;The workaround is dead simple: generate a conversation ID once at startup.&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cs&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// ConversationWorkaround.cs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; static&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; class&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; ConversationWorkaround&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;    public&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; static&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; Guid&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; CurrentConversationId&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;; } = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Guid&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;NewGuid&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One GUID, initialized once when the application starts, shared for the lifetime of the process. Every message gets tagged with that ID. Restart the app? New ID, new conversation.&lt;/p&gt;
&lt;p&gt;We add a corresponding &lt;code&gt;ConversationId&lt;/code&gt; column to our &lt;code&gt;Message&lt;/code&gt; entity - just a &lt;code&gt;Guid&lt;/code&gt; property that gets persisted alongside the role, content, and sequence number.&lt;/p&gt;
&lt;h3 id=&quot;the-message-store&quot;&gt;The Message Store&lt;/h3&gt;
&lt;p&gt;With our workaround in place, the &lt;code&gt;ChatMessageStore&lt;/code&gt; implementation becomes straightforward. The framework calls &lt;code&gt;AddMessagesAsync&lt;/code&gt; after each exchange, and &lt;code&gt;GetMessagesAsync&lt;/code&gt; when loading history.&lt;/p&gt;
&lt;p&gt;The key insight: we use &lt;code&gt;IDbContextFactory&amp;lt;KnowledgeDbContext&amp;gt;&lt;/code&gt; instead of injecting a &lt;code&gt;DbContext&lt;/code&gt; directly. Why? The store lives outside the normal HTTP request lifecycle. The factory pattern lets each operation spin up its own short-lived context - no connection leaks, no stale entity tracking.&lt;/p&gt;
&lt;p&gt;When adding messages, we tag each one with &lt;code&gt;ConversationWorkaround.CurrentConversationId&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cs&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// KnowledgeChatMessageStore.cs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; message&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; Message&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    Id&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Guid&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;TryParse&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;chatMessage&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;MessageId&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;out&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; id&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) ? &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;id&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; : &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Guid&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;NewGuid&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    ConversationId&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;ConversationWorkaround&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;CurrentConversationId&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    Role&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;chatMessage&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Role&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Value&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    AuthorName&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;chatMessage&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;AuthorName&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    Content&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;chatMessage&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Text&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; ?? &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Empty&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    SequenceNumber&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,  &lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Trigger auto-assigns&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    CreatedAt&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;chatMessage&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;CreatedAt&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;?.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;UtcDateTime&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; ?? &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;DateTime&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;UtcNow&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When retrieving, we filter by that same ID:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cs&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// KnowledgeChatMessageStore.cs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; messages&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; dbContext&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Messages&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Where&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;m&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; =&amp;gt; &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;m&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;ConversationId&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; == &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;conversationId&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;OrderBy&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;m&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; =&amp;gt; &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;m&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;SequenceNumber&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;ToListAsync&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;cancellationToken&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This keeps each app instance isolated. Messages from yesterday's debugging session don't pollute today's conversation.&lt;/p&gt;
&lt;h3 id=&quot;why-this-workaround&quot;&gt;Why This Workaround?&lt;/h3&gt;
&lt;p&gt;The ideal solution would use the framework's &lt;code&gt;AgentThread&lt;/code&gt; abstraction - each thread gets its own conversation, the UI manages thread creation, everything Just Works™. But DevUI doesn't pass thread information to custom stores yet.&lt;/p&gt;
&lt;p&gt;Good news: our &lt;code&gt;ChatMessageStore&lt;/code&gt; &lt;em&gt;does&lt;/em&gt; get called. Messages persist. The agent loads context on startup. The workaround gives us:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Per-instance conversations&lt;/strong&gt;: Each app restart starts fresh&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Persistent context&lt;/strong&gt;: Within a session, the agent remembers everything&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Future-proof schema&lt;/strong&gt;: When DevUI supports threads, we swap &lt;code&gt;ConversationWorkaround.CurrentConversationId&lt;/code&gt; for the real thread ID&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We've opened &lt;a href=&quot;https://github.com/microsoft/agent-framework/issues/3000&quot;&gt;issue #3000&lt;/a&gt; to track proper thread support. For now, this gets the job done.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;embeddings&quot;&gt;Embeddings&lt;/h2&gt;
&lt;p&gt;Before building the KnowledgeSearch agent, it helps to understand embeddings, which are the basis of semantic search. If you already know how they work, you can skip this section.&lt;/p&gt;
&lt;p&gt;This section covers the theory, but we're not just hand-waving. As we go, we'll be building toward our second agent that can search through conversation history and return relevant context.&lt;/p&gt;
&lt;h3 id=&quot;the-big-reveal&quot;&gt;The Big Reveal&lt;/h3&gt;
&lt;p&gt;An embedding is just a list of numbers. That's it. That's the tweet.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;%%{init: {&quot;theme&quot;: &quot;dark&quot;}}%%
flowchart LR
    A[&quot;🐱 cat&quot;] --&amp;gt; B[&quot;[0.021, -0.034, 0.089, 0.012, ..., -0.045]&quot;]
    style A fill:#064e3b,stroke:#10b981,color:#fff
    style B fill:#252526,stroke:#303031,color:#bbbbbb
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When you feed text into an embedding model, it spits out a vector - 1536 floating-point numbers for OpenAI's &lt;code&gt;text-embedding-3-small&lt;/code&gt;. These numbers encode the &lt;em&gt;meaning&lt;/em&gt; of the text. Not the letters, not the spelling - the actual semantic content.&lt;/p&gt;
&lt;p&gt;The magic? Similar meanings produce similar numbers.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;%%{init: {&quot;theme&quot;: &quot;dark&quot;}}%%
flowchart LR
    A[&quot;🐱 cat&quot;] --&amp;gt; V1[&quot;[0.021, -0.034, 0.089, ...]&quot;]
    B[&quot;🐈 kitten&quot;] --&amp;gt; V2[&quot;[0.019, -0.031, 0.092, ...]&quot;]
    C[&quot;🗳️ democracy&quot;] --&amp;gt; V3[&quot;[-0.067, 0.142, -0.023, ...]&quot;]

    style A fill:#064e3b,stroke:#10b981,color:#fff
    style B fill:#064e3b,stroke:#10b981,color:#fff
    style C fill:#451a03,stroke:#f59e0b,color:#fff
    style V1 fill:#252526,stroke:#303031,color:#bbbbbb
    style V2 fill:#252526,stroke:#303031,color:#bbbbbb
    style V3 fill:#252526,stroke:#303031,color:#bbbbbb
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;See how &quot;cat&quot; and &quot;kitten&quot; have similar-ish numbers, while &quot;democracy&quot; is completely different? That's semantic similarity, encoded as math.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The &quot;magic&quot; demystified&lt;/strong&gt;: This is the whole secret behind AI &quot;understanding&quot; - it's just numbers. There's no mystical intelligence, no consciousness pondering the nature of cats. Just insanely clever linear algebra operating in 1536-dimensional space. Once you see it, you can't unsee it. Beautiful, really.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;where-things-live-in-vector-space&quot;&gt;Where Things Live in Vector Space&lt;/h3&gt;
&lt;p&gt;Imagine plotting these vectors in space. The real vectors have 1,536 dimensions, but a three-dimensional sketch is enough to show the idea of clusters and similarity scores.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;%%{init: {
  &quot;theme&quot;: &quot;dark&quot;
}}%%
flowchart LR
    subgraph Pets[&quot; &quot;]
        A[cat]
        B[kitten]
    end
    subgraph Civics[&quot; &quot;]
        D[democracy]
        E[parliament]
    end

    A ---|&quot;0.94&quot;| B
    A -.-|&quot;0.31&quot;| D
    D ---|&quot;0.89&quot;| E

    style A fill:#064e3b,stroke:#10b981,color:#fff
    style B fill:#064e3b,stroke:#10b981,color:#fff
    style D fill:#451a03,stroke:#f59e0b,color:#fff
    style E fill:#451a03,stroke:#f59e0b,color:#fff
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Similar concepts cluster together. Words about pets huddle in one corner, political terms in another. In the real 1536-dimensional space, these relationships become incredibly nuanced - the model captures categories, analogies, context, even vibes. The scores on the edges are cosine similarities: higher numbers mean those meanings live closer together.&lt;/p&gt;
&lt;p&gt;This is why semantic search works. Ask &quot;how do I create an agent?&quot; and the system finds documents about &quot;instantiating agents&quot; and &quot;agent initialization&quot; - different words, same neighborhood in vector space.&lt;/p&gt;
&lt;h3 id=&quot;how-close-are-two-vectors&quot;&gt;How &quot;Close&quot; Are Two Vectors?&lt;/h3&gt;
&lt;p&gt;We use &lt;strong&gt;cosine similarity&lt;/strong&gt; - basically measuring the angle between two arrows in space.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Similarity = 1&lt;/strong&gt;: Identical meaning (same direction)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Similarity = 0&lt;/strong&gt;: Unrelated (perpendicular)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Similarity = -1&lt;/strong&gt;: Opposite meaning (opposite direction)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You don't need to understand the math. Just know: &lt;strong&gt;higher number = more similar meaning&lt;/strong&gt;. When we search our knowledge base, we're finding the vectors that point in roughly the same direction as the query.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-mermaid&quot;&gt;%%{init: {&quot;theme&quot;: &quot;dark&quot;}}%%
flowchart LR
    subgraph Query
        Q[&quot;How do I build an agent?&quot;]
    end
    subgraph Results[&quot;Top Matches (by similarity)&quot;]
        R1[&quot;0.94 - Creating your first agent&quot;]
        R2[&quot;0.91 - Agent initialization guide&quot;]
        R3[&quot;0.87 - Setting up agent tools&quot;]
        R4[&quot;0.34 - PostgreSQL connection strings&quot;]
    end
    Q --&amp;gt; R1
    Q --&amp;gt; R2
    Q --&amp;gt; R3
    Q -.-&amp;gt; R4

    style Q fill:#1e3a5f,stroke:#5abae0,color:#fff
    style R1 fill:#064e3b,stroke:#10b981,color:#fff
    style R2 fill:#064e3b,stroke:#10b981,color:#fff
    style R3 fill:#064e3b,stroke:#10b981,color:#fff
    style R4 fill:#451a03,stroke:#f59e0b,color:#fff
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The PostgreSQL doc isn't &lt;em&gt;wrong&lt;/em&gt;, it's just pointing in a completely different direction. Low similarity score, doesn't make the cut.&lt;/p&gt;
&lt;h3 id=&quot;the-model-well-use&quot;&gt;The Model We'll Use&lt;/h3&gt;
&lt;p&gt;OpenAI offers several embedding models:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Dimensions&lt;/th&gt;
&lt;th&gt;Use Case&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;text-embedding-3-small&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1536&lt;/td&gt;
&lt;td&gt;Most applications&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;text-embedding-3-large&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;3072&lt;/td&gt;
&lt;td&gt;When you need more context&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;text-embedding-ada-002&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1536&lt;/td&gt;
&lt;td&gt;Legacy&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;We'll use &lt;code&gt;text-embedding-3-small&lt;/code&gt;. It's cheap, fast, and good enough for most use cases. The larger model is more accurate but costs more - classic tradeoff. Start small, upgrade if you need to.&lt;/p&gt;
&lt;p&gt;Now the question becomes: where do we store 1536 floating-point numbers per piece of text, and how do we search them efficiently?&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;pgvector-setup&quot;&gt;pgvector Setup&lt;/h2&gt;
&lt;p&gt;PostgreSQL with pgvector gives us a proper vector database without leaving our existing stack. No separate Pinecone or Weaviate instance to manage.&lt;/p&gt;
&lt;h3 id=&quot;enabling-the-extension&quot;&gt;Enabling the Extension&lt;/h3&gt;
&lt;p&gt;First, we need pgvector enabled in our database. Connect to PostgreSQL and run:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;CREATE&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; EXTENSION &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;IF&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; NOT&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; EXISTS&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; vector&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This installs the vector data type and operators. You only need to do this once per database. If you're using my dev container template, this is already handled in the database initialization scripts.&lt;/p&gt;
&lt;p&gt;Verify it worked:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; * &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; pg_extension &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; extname = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;'vector'&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;-- Should return one row showing the vector extension&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;the-embedding-entity&quot;&gt;The Embedding Entity&lt;/h3&gt;
&lt;p&gt;We want a generic approach - store embeddings for any content type (messages, documents, whatever). The key property:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cs&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Embedding.cs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; Vector&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; Vector&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;; } = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;!;  &lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// pgvector's native type&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We use pgvector's native &lt;code&gt;Vector&lt;/code&gt; type instead of &lt;code&gt;float[]&lt;/code&gt;. The &lt;code&gt;Pgvector.EntityFrameworkCore&lt;/code&gt; package handles the mapping between C# and PostgreSQL.&lt;/p&gt;
&lt;p&gt;The entity also tracks &lt;em&gt;what&lt;/em&gt; was embedded via &lt;code&gt;SourceType&lt;/code&gt; + &lt;code&gt;SourceId&lt;/code&gt; - a poor man's polymorphic association. Not elegant, but flexible. We can add new source types (documents, code snippets) without schema changes.&lt;/p&gt;
&lt;p&gt;We also store the original &lt;code&gt;Content&lt;/code&gt; alongside the vector. Why? When we retrieve matches, we need the actual text to show. And if embeddings get stale, we can re-generate from the stored content.&lt;/p&gt;
&lt;h3 id=&quot;ef-core-configuration&quot;&gt;EF Core Configuration&lt;/h3&gt;
&lt;p&gt;We already covered the key line in Section 1 - &lt;code&gt;HasColumnType(&quot;vector(1536)&quot;)&lt;/code&gt;. The dimension must match your embedding model. OpenAI's &lt;code&gt;text-embedding-3-small&lt;/code&gt; produces 1536 dimensions, so that's what we use.&lt;/p&gt;
&lt;h3 id=&quot;indexing-for-speed&quot;&gt;Indexing for Speed&lt;/h3&gt;
&lt;p&gt;Without an index, similarity search scans every row - fine for 1,000 embeddings, catastrophic for 1,000,000. HNSW (Hierarchical Navigable Small World) gives us logarithmic search time.&lt;/p&gt;
&lt;p&gt;We create the index via a migration (we include this in our &lt;code&gt;AddCustomTriggers&lt;/code&gt; migration):&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cs&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// 20251224000001_AddCustomTriggers.cs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;migrationBuilder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Sql&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;&quot;&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    CREATE INDEX IF NOT EXISTS ix_embeddings_vector_cosine&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    ON embeddings&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    USING hnsw (vector vector_cosine_ops);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;    &quot;&quot;&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Breaking this down:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;hnsw&lt;/code&gt;&lt;/strong&gt;: The index algorithm - builds a graph structure for fast nearest neighbor search&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;vector_cosine_ops&lt;/code&gt;&lt;/strong&gt;: Use cosine distance (1 - cosine similarity) as the metric&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Creation time&lt;/strong&gt;: ~1-2 seconds per 10,000 vectors on my laptop&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The tradeoff: HNSW uses more memory and takes longer to build, but queries are dramatically faster. For a RAG system, this is always worth it.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;embedding-messages&quot;&gt;Embedding Messages&lt;/h2&gt;
&lt;p&gt;Time to give our agent some actual knowledge. Instead of indexing external documentation, we're embedding our own conversation history - every message becomes searchable.&lt;/p&gt;
&lt;h3 id=&quot;why-messages&quot;&gt;Why Messages?&lt;/h3&gt;
&lt;p&gt;We need &lt;em&gt;something&lt;/em&gt; to embed, and chat messages are already there - no external datasets, no document ingestion pipelines, no extra setup. It's demo content that generates itself as you use the agent.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Important distinction&lt;/strong&gt;: This is &lt;em&gt;not&lt;/em&gt; the same as &lt;code&gt;Microsoft.Agents.AI.Memory.ChatHistoryMemoryProvider&lt;/code&gt;, which provides memorable context during agent invocation (think: short-term working memory for the current conversation). What we're building is long-term semantic search over historical content. Different problems, different solutions.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;the-chunking-problem-or-not&quot;&gt;The Chunking Problem (Or Not)&lt;/h3&gt;
&lt;p&gt;For large documents, chunking is essential - split into smaller pieces, embed each one. But messages are already bite-sized. A typical chat message is well under our embedding model's context limit.&lt;/p&gt;
&lt;p&gt;We'll embed messages as-is. No chunking needed. If you later want to add document indexing (PDFs, markdown files, whatever), you'd apply chunking:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Fixed size&lt;/strong&gt;: Every chunk is N tokens. Simple but might split mid-sentence.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Paragraph-based&lt;/strong&gt;: Split on natural boundaries. Preserves context but uneven sizes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Semantic&lt;/strong&gt;: Use the LLM to identify logical sections. Expensive but smart.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For documents, &lt;strong&gt;fixed size with overlap&lt;/strong&gt; (500 tokens, 50 token overlap) is the standard. But for messages, we skip this entirely.&lt;/p&gt;
&lt;h3 id=&quot;the-embedding-service&quot;&gt;The Embedding Service&lt;/h3&gt;
&lt;p&gt;We wrap &lt;code&gt;IEmbeddingGenerator&amp;lt;string, Embedding&amp;lt;float&amp;gt;&amp;gt;&lt;/code&gt; from Microsoft.Extensions.AI in a simple service. The key conversion:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cs&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// EmbeddingService.cs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; result&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; _embeddingGenerator&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;GenerateAsync&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;text&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;cancellationToken&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; new&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; Vector&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;result&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Vector&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;ToArray&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;());&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Text goes in, pgvector &lt;code&gt;Vector&lt;/code&gt; comes out. The service handles the conversion from the AI library's float array to pgvector's native type. Nothing fancy - just a thin wrapper to keep the embedding generation consistent across the app.&lt;/p&gt;
&lt;h3 id=&quot;background-processing&quot;&gt;Background Processing&lt;/h3&gt;
&lt;p&gt;We could embed messages synchronously when they're saved, but that blocks the chat response. Instead, a background service picks up unprocessed messages every 10 seconds.&lt;/p&gt;
&lt;p&gt;The trick is using &lt;code&gt;Message.EmbeddingId&lt;/code&gt; as both a foreign key &lt;em&gt;and&lt;/em&gt; a processing flag:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cs&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// EmbeddingBackgroundService.cs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; pendingMessages&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; dbContext&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Messages&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Where&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;m&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; =&amp;gt; &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;m&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;EmbeddingId&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; == &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;null&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)  &lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Not yet embedded&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Where&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;m&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; =&amp;gt; !&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;IsNullOrEmpty&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;m&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Content&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;OrderBy&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;m&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; =&amp;gt; &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;m&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;SequenceNumber&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Take&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;10&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;ToListAsync&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;cancellationToken&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For each message, we generate the embedding and link it back. Once &lt;code&gt;EmbeddingId&lt;/code&gt; is set, that message won't be picked up again.&lt;/p&gt;
&lt;p&gt;No chunking, no complexity. Message goes in, vector comes out, gets stored. The &lt;code&gt;SourceType = &quot;Message&quot;&lt;/code&gt; links back to the original - we'll use this when searching.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;the-knowledgesearch-agent&quot;&gt;The KnowledgeSearch Agent&lt;/h2&gt;
&lt;p&gt;Once the embeddings are generated, the KnowledgeSearch agent can use them to search the conversation history by meaning.&lt;/p&gt;
&lt;h3 id=&quot;why-an-agent-instead-of-a-simple-tool&quot;&gt;Why an Agent Instead of a Simple Tool?&lt;/h3&gt;
&lt;p&gt;You could just wire up a tool function. But wrapping it in an agent brings benefits:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Clear separation&lt;/strong&gt;: Search logic lives in its own class, testable in isolation&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dependency injection&lt;/strong&gt;: Proper lifetime management for database contexts and services&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Composability&lt;/strong&gt;: Later, we can add more specialized agents (DocSearch, WebSearch, etc.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is the &quot;agentic&quot; part of agentic RAG - agents with tools, each specialized for their task.&lt;/p&gt;
&lt;h3 id=&quot;the-implementation&quot;&gt;The Implementation&lt;/h3&gt;
&lt;p&gt;The flow is straightforward: embed the query, search pgvector, format results.&lt;/p&gt;
&lt;p&gt;First, we embed the search query using the same model that embedded our messages:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cs&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// KnowledgeSearchAgent.cs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; queryVector&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; _embeddingService&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;EmbedAsync&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;query&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The vector search uses pgvector:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cs&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// KnowledgeSearchAgent.cs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; results&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;await&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; dbContext&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Embeddings&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Where&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; =&amp;gt; &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;SourceType&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; == &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Message&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Select&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; =&amp;gt; &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        Embedding&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        Distance&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;e&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Vector&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;CosineDistance&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;queryVector&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    })&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Where&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;x&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; =&amp;gt; &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;x&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Distance&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; &amp;lt;= (&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;1&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; - &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;DefaultMinScore&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;))  &lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// DefaultMinScore = 0.40&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;OrderBy&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;x&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; =&amp;gt; &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;x&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Distance&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Take&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;DefaultTopK&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)  &lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// DefaultTopK = 10&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    .&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;ToListAsync&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A few notes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;CosineDistance()&lt;/code&gt;&lt;/strong&gt; returns distance (0 = identical, 2 = opposite), not similarity. Lower is better.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Threshold of &lt;code&gt;(1 - 0.40)&lt;/code&gt;&lt;/strong&gt; means we want at least 40% similarity. Tweak &lt;code&gt;DefaultMinScore&lt;/code&gt; based on your use case.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;IDbContextFactory&lt;/code&gt;&lt;/strong&gt; gives us a fresh context per search - the agent is a singleton, but each query gets its own connection.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Finally, we fetch the actual messages and format them with timestamps. The timestamps matter - they help the LLM understand recency.&lt;/p&gt;
&lt;h3 id=&quot;the-pgvector-query&quot;&gt;The pgvector Query&lt;/h3&gt;
&lt;p&gt;EF Core translates this LINQ query to:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;sql&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;SELECT&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; e.*, e.vector &amp;lt;=&amp;gt; @queryVector &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;AS&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; distance&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;FROM&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; embeddings e&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;WHERE&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; e.source_type = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;'Message'&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;  AND&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; e.vector &amp;lt;=&amp;gt; @queryVector &amp;lt;= &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;0&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;6&lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;  -- 1 - DefaultMinScore&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;ORDER BY&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; e.vector &amp;lt;=&amp;gt; @queryVector&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;LIMIT&lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt; 10&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;&amp;lt;=&amp;gt;&lt;/code&gt; operator is pgvector's cosine distance. Lower distance = better match. The HNSW index we created earlier makes this logarithmic instead of linear - critical at scale.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;wiring-it-up-1&quot;&gt;Wiring It Up&lt;/h2&gt;
&lt;p&gt;Now we connect everything in &lt;code&gt;AgentFactory&lt;/code&gt; and &lt;code&gt;Program.cs&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;the-agent-factory&quot;&gt;The Agent Factory&lt;/h3&gt;
&lt;p&gt;Wiring the tool is the key step. We use &lt;code&gt;AIFunctionFactory.Create()&lt;/code&gt; to turn our instance method into something the agent can call:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cs&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// AgentFactory.cs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; searchAgent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;services&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;GetRequiredService&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;KnowledgeSearchAgent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;return&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; chatClient&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;CreateAIAgent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; ChatClientAgentOptions&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    Id&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    Name&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;key&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    ChatOptions&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; ChatOptions&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        ConversationId&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;global&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        Instructions&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;KnowledgeSearchSystemPrompt&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        Tools&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = [&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;AIFunctionFactory&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Create&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;searchAgent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;SearchConversationHistoryAsync&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;)],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;        ToolMode&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;ChatToolMode&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Auto&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;[Description]&lt;/code&gt; attributes on our method and parameters become the tool's schema - the LLM sees them and knows what the tool does and what arguments it needs.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ToolMode = ChatToolMode.Auto&lt;/code&gt; is important. It tells the model to decide when to use tools, rather than always using them or never using them.&lt;/p&gt;
&lt;p&gt;The system prompt is equally crucial. We tell the LLM &lt;em&gt;when&lt;/em&gt; to use the tool:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;DECISION FRAMEWORK:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;- For general knowledge (math, common facts): Answer directly without tools&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;- For recall questions ('did we discuss X?'): Use SearchConversationHistory&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;- When unsure if something was discussed: Search first rather than guessing&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Without this guidance, models either never use tools or use them for everything. The prompt teaches restraint.&lt;/p&gt;
&lt;h3 id=&quot;registration-in-programcs&quot;&gt;Registration in Program.cs&lt;/h3&gt;
&lt;p&gt;The key registration:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cs&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;// Program.cs&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;builder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Services&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;AddSingleton&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;KnowledgeSearchAgent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;&amp;gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Why singleton? The agent factory resolves from the root provider during startup. This is safe because &lt;code&gt;KnowledgeSearchAgent&lt;/code&gt; only depends on singletons (&lt;code&gt;IEmbeddingService&lt;/code&gt;, &lt;code&gt;ILogger&lt;/code&gt;) and factories (&lt;code&gt;IDbContextFactory&lt;/code&gt;) - no scoped services leaking through.&lt;/p&gt;
&lt;p&gt;The rest follows the same pattern we established: &lt;code&gt;AddAIAgent&lt;/code&gt; with factory delegates, &lt;code&gt;AddHostedService&lt;/code&gt; for the background embedding processor.&lt;/p&gt;
&lt;h3 id=&quot;before-and-after&quot;&gt;Before and After&lt;/h3&gt;
&lt;p&gt;Let's see the difference:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Without RAG:&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;User: &quot;What did we discuss about database indexes?&quot;
Agent: &quot;I don't have memory of our past conversations...&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;With KnowledgeSearch:&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;User: &quot;What did we discuss about database indexes?&quot;
Agent: &lt;em&gt;calls SearchConversationHistory(&quot;database indexes&quot;)&lt;/em&gt;
Agent: &quot;Based on our earlier conversation, we discussed adding indexes for ConversationId and the composite index for ordered retrieval...&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Night and day.&lt;/p&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;
&lt;p&gt;We've covered a lot of ground today, but we're still just scratching the surface.&lt;/p&gt;
&lt;p&gt;We learned how to take abstract concepts like &lt;strong&gt;embeddings&lt;/strong&gt; and store them in an &lt;strong&gt;actual database&lt;/strong&gt; using &lt;code&gt;pgvector&lt;/code&gt;. We saw how to &lt;strong&gt;consume that knowledge&lt;/strong&gt; by building a specialized agent that can perform semantic searches over our own conversation history.&lt;/p&gt;
&lt;p&gt;Along the way, we touched on the importance of &lt;strong&gt;database persistence&lt;/strong&gt;, the nuances of &lt;strong&gt;tool calling&lt;/strong&gt; with &lt;code&gt;AIFunctionFactory&lt;/code&gt;, and how to manage agent lifetimes in a .NET application. We also explored &lt;strong&gt;chunking&lt;/strong&gt; and more advanced &lt;strong&gt;RAG strategies&lt;/strong&gt; like hybrid search and reranking - topics that are definitely worth exploring as you dive deeper into the world of AI.&lt;/p&gt;
&lt;p&gt;The full code is on the &lt;a href=&quot;https://github.com/svnscha/knowledge-private/tree/part/01-agentic-rag&quot;&gt;&lt;code&gt;part/01-agentic-rag&lt;/code&gt;&lt;/a&gt; branch. Clone it, run it, break it, and most importantly, make it your own.&lt;/p&gt;
&lt;p&gt;Microsoft's official examples are also worth reading. The framework implements some features, including search, with higher-level abstractions such as &lt;code&gt;TextSearchProvider&lt;/code&gt;. I used the lower-level approach here to make each step visible.&lt;/p&gt;
&lt;p&gt;Here are some examples worth checking in the &lt;a href=&quot;https://github.com/microsoft/agent-framework&quot;&gt;Microsoft Agent Framework&lt;/a&gt; repository:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/microsoft/agent-framework/tree/main/dotnet/samples/GettingStarted&quot;&gt;Getting Started on GitHub&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Agent_Step03_UsingFunctionTools/Program.cs&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Agent_Step06_PersistedConversations/Program.cs&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Agent_Step07_3rdPartyThreadStorage/Program.cs&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Agent_Step12_AsFunctionTool/Program.cs&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;AgentWithRAG/*&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;next-steps&quot;&gt;Next Steps&lt;/h2&gt;
&lt;p&gt;We now have an agent that can remember and retrieve earlier conversations.&lt;/p&gt;
&lt;p&gt;The next step is the &lt;strong&gt;Agent Loop&lt;/strong&gt;. Instead of a single request and response, the agent can act, observe the result, and adjust its next step until the task is complete. This pattern is the basis for more autonomous coding assistants.&lt;/p&gt;
&lt;p&gt;As these agents become more autonomous, we also need to know exactly what they're thinking. That's why we'll be diving into &lt;strong&gt;Observability&lt;/strong&gt; and tracing so we can peek inside the &quot;black box&quot; and see every tool call, every reasoning step, and every decision the agent makes in real-time.&lt;/p&gt;
&lt;p&gt;We're moving from &quot;chatbots&quot; to &quot;collaborators.&quot; I'll see you in the next one as we start closing the loop. 🚀&lt;/p&gt;
</content>
    </entry>
    <entry xml:lang="en">
        <title>AI Agents in .NET: Let's Build Something Smart</title>
        <published>2025-12-21T00:00:00+00:00</published>
        <updated>2025-12-21T00:00:00+00:00</updated>
        <author>
          <name>Sven Scharmentke</name>
        </author>
        <link rel="alternate" type="text/html" href="https://svnscha.de/posts/ai-agents-dotnet-intro/"/>
        <id>https://svnscha.de/posts/ai-agents-dotnet-intro/</id>
        <summary type="html">Introducing the Knowledge repository - a hands-on companion for learning how to build AI agents with the Microsoft Agent Framework in C#.</summary>
        <content type="html" xml:base="https://svnscha.de/posts/ai-agents-dotnet-intro/">&lt;p&gt;Welcome to a new blog series about building AI agents in .NET.&lt;/p&gt;
&lt;p&gt;Yes, you read that right - I just said &lt;code&gt;.NET&lt;/code&gt;. Not Python. C#. Curly braces. Strong typing. With drums and trumpets.&lt;/p&gt;
&lt;p&gt;Over the coming posts, we'll explore the &lt;a href=&quot;https://learn.microsoft.com/en-us/agent-framework/overview/agent-framework-overview&quot;&gt;Microsoft Agent Framework&lt;/a&gt;, build actual agentic systems, and - most importantly - have some fun doing it. This first post sets the stage: why I'm doing this, what we're building, and how to get started with the companion repository.&lt;/p&gt;
&lt;p&gt;Here is how to get started.&lt;/p&gt;
&lt;h2 id=&quot;why-you-ask&quot;&gt;Why, You Ask?&lt;/h2&gt;
&lt;p&gt;Honestly? I got a little restless. Every AI agent tutorial out there follows the same well-worn path:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;&quot;Build a weather agent!&quot;&lt;/em&gt;  -  Revolutionary stuff. Though I hear windows also exist.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&quot;Spam detection with AI!&quot;&lt;/em&gt;  -  Gmail's been doing this since 2004.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&quot;Customer service chatbot!&quot;&lt;/em&gt;  -  Oh good, we definitely need more of those.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These examples are useful for learning the basics, but I wanted to build something closer to a real application.&lt;/p&gt;
&lt;p&gt;This series is about building agents that are genuinely smart, genuinely fun, or ideally both. No boring examples. No wrapping API calls in agent clothing and calling it intelligent.&lt;/p&gt;
&lt;p&gt;As for the language choice - I've always been a C-family person. Python is lovely for what it does, but give me curly braces and strong typing any day. When Microsoft released the Agent Framework for .NET? Yeah, that got me genuinely excited. First-class AI agents in C#. Native support, proper tooling, the whole package.&lt;/p&gt;
&lt;h2 id=&quot;the-microsoft-agent-framework&quot;&gt;The Microsoft Agent Framework&lt;/h2&gt;
&lt;p&gt;So what exactly are we working with? The framework comes with everything you need to build proper agentic systems:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Native C# support (obviously)&lt;/li&gt;
&lt;li&gt;A beautiful &lt;a href=&quot;https://learn.microsoft.com/en-us/agent-framework/user-guide/devui/?pivots=programming-language-csharp&quot;&gt;DevUI&lt;/a&gt; for testing and debugging your agents - still in preview and missing some Python-side features, but it gets the job done&lt;/li&gt;
&lt;li&gt;Built-in patterns for common agentic scenarios (the fun stuff)&lt;/li&gt;
&lt;li&gt;Integration with the broader .NET ecosystem (finally!)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And that's what this blog series is all about.&lt;/p&gt;
&lt;h3 id=&quot;about-devui&quot;&gt;About DevUI&lt;/h3&gt;
&lt;p&gt;Speaking of DevUI - if you head over to Microsoft's documentation and switch to the C# tab, you're greeted with this gem:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;&quot;DevUI documentation for C# is coming soon. Please check back later or refer to the Python documentation for conceptual guidance.&quot;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Ah yes, the classic &quot;refer to Python&quot; move. You know, the language we &lt;em&gt;specifically&lt;/em&gt; chose not to use for this series? The irony isn't lost on me. But hey, I'm not bitter - I'm just impressed they managed to ship a working DevUI before the documentation. That's actually kind of bold.&lt;/p&gt;
&lt;p&gt;The DevUI is a useful web interface for chatting with agents and inspecting their behavior during development. It does not replace proper tracing with tools such as OpenTelemetry and Jaeger, but it works well for quick iteration and debugging. I will cover observability separately in a later post.&lt;/p&gt;
&lt;h2 id=&quot;the-knowledge-repository&quot;&gt;The Knowledge Repository&lt;/h2&gt;
&lt;p&gt;To support this series, I've created a companion repository called &lt;a href=&quot;https://github.com/svnscha/knowledge&quot;&gt;Knowledge&lt;/a&gt;. I briefly considered &lt;em&gt;&quot;AI-Stuff-I-Throw-Together-At-2AM&quot;&lt;/em&gt; but that didn't seem like a good fit. So I thought about it - I'm deepening my own knowledge about AI as I create this series, and hopefully you'll gain some knowledge along the way too. Yeah, let's face it: &lt;em&gt;Knowledge&lt;/em&gt; is simple. Sometimes simple works.&lt;/p&gt;
&lt;p&gt;The repository is structured as a progressive learning path. Each branch builds on the previous one, taking you from zero to building sophisticated agentic systems. Here's what we're working with:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Branch&lt;/th&gt;
&lt;th&gt;Topic&lt;/th&gt;
&lt;th&gt;What You'll Learn&lt;/th&gt;
&lt;th&gt;Codespace&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;main&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Repository &amp;amp; Hello World Agent&lt;/td&gt;
&lt;td&gt;Project setup, DevUI, your first conversational agent&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://codespaces.new/svnscha/knowledge/tree/main&quot;&gt;&lt;img src=&quot;https://github.com/codespaces/badge.svg&quot; alt=&quot;Open in Codespaces&quot;&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;More branches will be added as the series progresses. And honestly? That Codespaces button is kind of magical - click it, grab a coffee, and come back to a fully configured dev environment. No setup, no dependencies, just ready to go. I still find that ridiculously cool.&lt;/p&gt;
&lt;p&gt;Star the repo if you want to follow along!&lt;/p&gt;
&lt;h2 id=&quot;development-environment&quot;&gt;Development Environment&lt;/h2&gt;
&lt;p&gt;You've got two options here - pick whichever fits your workflow.&lt;/p&gt;
&lt;h3 id=&quot;dev-container-setup&quot;&gt;Dev Container Setup&lt;/h3&gt;
&lt;p&gt;First things first: reproducible development environments. The repository includes a &lt;code&gt;.devcontainer&lt;/code&gt; configuration with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;.NET 10&lt;/li&gt;
&lt;li&gt;PostgreSQL with pgvector for when we get into embeddings and semantic search&lt;/li&gt;
&lt;li&gt;All the tooling pre-configured&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Just open the repo in VS Code, click &quot;Reopen in Container,&quot; and you're ready to go. The classic &quot;works on my machine&quot; problem? Solved.&lt;/p&gt;
&lt;p&gt;You can also &lt;a href=&quot;https://codespaces.new/svnscha/knowledge/tree/main&quot;&gt;open it in a Codespace&lt;/a&gt; and work entirely in the browser without a local setup.&lt;/p&gt;
&lt;h3 id=&quot;classic&quot;&gt;Classic&lt;/h3&gt;
&lt;p&gt;And for those of you rolling your eyes at all this container and cloud stuff - fair enough. Install the &lt;a href=&quot;https://dotnet.microsoft.com/download&quot;&gt;.NET 10 SDK&lt;/a&gt;, fire up VS Code or Visual Studio, clone the repo, and just go. Sometimes the old ways are the best ways. That said, you'll want to get PostgreSQL with pgvector set up sooner rather than later - we'll need it for embeddings and semantic search in upcoming posts.&lt;/p&gt;
&lt;h2 id=&quot;whats-on-the-main-branch&quot;&gt;What's on the Main Branch?&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/screenshots/2025-12-15-devui-empty.png&quot; alt=&quot;The DevUI in action&quot;&gt;&lt;/p&gt;
&lt;p&gt;Let me walk you through what you'll find when you clone the repo. The &lt;code&gt;main&lt;/code&gt; branch sets up everything you need to hit the ground running with AI agent development.&lt;/p&gt;
&lt;h3 id=&quot;project-structure&quot;&gt;Project Structure&lt;/h3&gt;
&lt;p&gt;The solution is organized into two projects:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Knowledge&lt;/strong&gt; - The main web application that hosts our agents and the DevUI&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Knowledge.Shared&lt;/strong&gt; - Shared configuration, extensions, and utilities&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This separation keeps things clean as we add more complexity in future posts.&lt;/p&gt;
&lt;h3 id=&quot;configuration-done-right&quot;&gt;Configuration Done Right&lt;/h3&gt;
&lt;p&gt;One thing I'm particular about is handling configuration properly. In &lt;code&gt;Knowledge.Shared&lt;/code&gt;, we have a &lt;code&gt;KnowledgeSettings&lt;/code&gt; class that gives us strongly-typed access to our config values.&lt;/p&gt;
&lt;p&gt;The important bits are these two properties:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cs&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; ApiKey&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;; } = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Empty&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;public&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; string&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; ApiEndpoint&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; { &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;get&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;; &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;set&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;; } = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Empty&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;ApiKey&lt;/code&gt; is where your OpenAI (or compatible) API key goes. &lt;code&gt;ApiEndpoint&lt;/code&gt; is optional - leave it empty and we'll hit OpenAI's default endpoint, or set it to point at Azure OpenAI, a local model, whatever you need.&lt;/p&gt;
&lt;p&gt;Now, you definitely don't want to hardcode API keys in your source code. That's how keys end up on GitHub and suddenly you're funding a stranger's LLM experiments. Instead, we use &lt;a href=&quot;https://learn.microsoft.com/en-us/aspnet/core/security/app-secrets&quot;&gt;.NET User Secrets&lt;/a&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;cd&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; src/Knowledge&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;dotnet&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; user-secrets&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; set&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;Knowledge:ApiKey&quot;&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; &quot;your-api-key-here&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Your key stays on your machine, outside of source control. Sleep well at night.&lt;/p&gt;
&lt;h3 id=&quot;wiring-up-the-agent&quot;&gt;Wiring Up the Agent&lt;/h3&gt;
&lt;p&gt;The following sections walk through &lt;code&gt;Program.cs&lt;/code&gt; piece by piece.&lt;/p&gt;
&lt;p&gt;First, the imports - nothing too surprising here:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cs&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;using&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; System&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;ClientModel&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;using&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; Knowledge&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Shared&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Extensions&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;using&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; Microsoft&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Agents&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;AI&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;DevUI&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;using&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; Microsoft&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Agents&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;AI&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Hosting&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;using&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; Microsoft&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Extensions&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;AI&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;using&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; OpenAI&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We're pulling in the Agent Framework (&lt;code&gt;Microsoft.Agents.AI.*&lt;/code&gt;), Microsoft's AI abstractions (&lt;code&gt;Microsoft.Extensions.AI&lt;/code&gt;), and the official OpenAI client. Standard .NET web app stuff otherwise.&lt;/p&gt;
&lt;p&gt;Next, we create our builder and call into our configuration extension:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cs&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; builder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;WebApplication&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;CreateBuilder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;args&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;builder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;ConfigureKnowledgeDefaults&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;((&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;settings&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;logger&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;) =&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    // ...&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;ConfigureKnowledgeDefaults&lt;/code&gt; is a helper method in our shared project. It handles loading &lt;code&gt;appsettings.json&lt;/code&gt;, binding it to our &lt;code&gt;KnowledgeSettings&lt;/code&gt; type, and then gives us a callback where we can access both the settings and a logger. This keeps &lt;code&gt;Program.cs&lt;/code&gt; clean while still giving us full control.&lt;/p&gt;
&lt;p&gt;Inside that callback, we set up OpenAI. First, a sanity check:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cs&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;IsNullOrWhiteSpace&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;settings&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;ApiKey&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    logger&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;LogWarning&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;No API key configured. Set Knowledge:ApiKey in user secrets.&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Better to warn loudly than fail silently.&lt;/p&gt;
&lt;p&gt;Then we create the OpenAI client options. If a custom endpoint is configured, we use it:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cs&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; options&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; OpenAIClientOptions&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#C586C0&quot;&gt;if&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; (!&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;string&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;IsNullOrEmpty&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;settings&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;ApiEndpoint&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;    options&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Endpoint&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; Uri&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;settings&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;ApiEndpoint&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is what lets you swap between OpenAI, Azure OpenAI, or even a local model running through something like Ollama with an OpenAI-compatible API.&lt;/p&gt;
&lt;p&gt;Now we create the actual client and register it with dependency injection:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cs&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; client&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; OpenAIClient&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;new&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; ApiKeyCredential&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;settings&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;ApiKey&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;), &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;options&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;builder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Services&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;AddChatClient&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;client&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;GetChatClient&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;gpt-4.1&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;).&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;AsIChatClient&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;());&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;GetChatClient(&quot;gpt-4.1&quot;)&lt;/code&gt; gets us a chat client for that specific model. &lt;code&gt;.AsIChatClient()&lt;/code&gt; wraps it in Microsoft's &lt;code&gt;IChatClient&lt;/code&gt; abstraction, which is what the Agent Framework expects. Using this abstraction means we could swap out OpenAI for any other provider without changing our agent code.&lt;/p&gt;
&lt;p&gt;And finally - the actual agent registration:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cs&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;builder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;AddAIAgent&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Knowledge&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;You are a helpful agent named Knowledge.&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's it. One line. We give it a name and a system prompt, and the framework handles the rest. We'll make this more sophisticated in future posts, but for a &quot;hello world&quot; agent, this is all you need.&lt;/p&gt;
&lt;h3 id=&quot;finishing-the-pipeline&quot;&gt;Finishing the Pipeline&lt;/h3&gt;
&lt;p&gt;After our configuration callback, we register a few more services:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cs&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;builder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Services&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;AddOpenAIResponses&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;builder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Services&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;AddOpenAIConversations&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These set up the Agent Framework's handlers for OpenAI's responses and conversations API patterns.&lt;/p&gt;
&lt;p&gt;Then we build the app and configure the HTTP pipeline:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cs&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#569CD6&quot;&gt;var&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt; app&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;builder&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Build&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;app&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;ConfigureKnowledgePipeline&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;ConfigureKnowledgePipeline&lt;/code&gt; is another shared helper - it sets up logging, error handling, and other middleware that we'll want across all our experiments.&lt;/p&gt;
&lt;p&gt;Finally, we map our endpoints:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;cs&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;app&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;MapOpenAIResponses&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;app&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;MapOpenAIConversations&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;app&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;MapDevUI&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;app&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;MapGet&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, () =&amp;gt; &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Results&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Redirect&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;/devui/&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;));&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;app&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;Run&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;MapOpenAIResponses()&lt;/code&gt; and &lt;code&gt;MapOpenAIConversations()&lt;/code&gt; expose endpoints that follow OpenAI's API patterns. &lt;code&gt;MapDevUI()&lt;/code&gt; enables the Agent Framework's built-in developer interface.&lt;/p&gt;
&lt;p&gt;That last &lt;code&gt;MapGet&lt;/code&gt; just redirects the root URL to the DevUI. Because when you're developing agents, that's where you want to be.&lt;/p&gt;
&lt;h3 id=&quot;running-the-application&quot;&gt;Running the Application&lt;/h3&gt;
&lt;p&gt;Once configured, getting started is simple:&lt;/p&gt;
&lt;pre class=&quot;astro-code dark-plus&quot; style=&quot;background-color:#1E1E1E;color:#D4D4D4; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;dotnet&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; run&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; --project&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; src/Knowledge&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Navigate to &lt;code&gt;http://localhost:5000/devui&lt;/code&gt; and you'll see the DevUI - a beautiful interface for interacting with your agents, inspecting their reasoning, and debugging when things inevitably go sideways.&lt;/p&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;
&lt;p&gt;We've covered a lot of ground in this introductory post. You now have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A companion repository with a reproducible dev container setup&lt;/li&gt;
&lt;li&gt;A working &quot;hello world&quot; agent using the Microsoft Agent Framework&lt;/li&gt;
&lt;li&gt;Proper configuration with user secrets (no API keys in source control!)&lt;/li&gt;
&lt;li&gt;The DevUI for testing and debugging your agents&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The foundation is solid. Now comes the fun part.&lt;/p&gt;
&lt;h2 id=&quot;next-steps&quot;&gt;Next Steps&lt;/h2&gt;
&lt;p&gt;We've got the foundation in place - a working agent, DevUI, proper configuration. But let's be real: a &quot;hello world&quot; agent isn't going to impress anyone, least of all ourselves.&lt;/p&gt;
&lt;p&gt;Next, I will add tools so the agent can perform actions, explore multi-agent scenarios, and use PostgreSQL with pgvector for embeddings and semantic search. A later post will also add proper observability.&lt;/p&gt;
&lt;p&gt;But first things first. Step by step.&lt;/p&gt;
&lt;p&gt;If you're a .NET developer who's been watching the AI agent space from the sidelines, wondering when C# would get some love - welcome. If you're just someone who's bored of the same old tutorials and wants to see something different - also welcome.&lt;/p&gt;
&lt;p&gt;Clone the repo, star it if you're feeling generous, and let's build something that's actually worth building.&lt;/p&gt;
&lt;p&gt;See you in the next post. 🚀&lt;/p&gt;
</content>
    </entry>
</feed>
