<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title>svnscha - dotnet</title>
    <subtitle>automating annoying tasks, sharing tips, and embracing less frustration</subtitle>
    <link rel="self" type="application/atom+xml" href="https://svnscha.de/tags/dotnet/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://svnscha.de"/>
    <generator uri="https://astro.build/">Astro</generator>
    <updated>2026-04-15T00:00:00+00:00</updated>
    <id>https://svnscha.de/tags/dotnet/atom.xml</id>
    <entry xml:lang="en">
        <title>The Joy of Writing a Debugger Adapter for Visual Studio Code</title>
        <published>2026-04-15T00:00:00+00:00</published>
        <updated>2026-04-15T00:00:00+00:00</updated>
        <author>
          <name>Sven Scharmentke</name>
        </author>
        <link rel="alternate" type="text/html" href="https://svnscha.de/posts/the-joy-of-writing-a-debugger-adapter-for-visual-studio-code/"/>
        <id>https://svnscha.de/posts/the-joy-of-writing-a-debugger-adapter-for-visual-studio-code/</id>
        <summary type="html">I only wanted a faster remote debugging workflow for a service running in a VM. Instead, I ended up learning DAP, wrapping WinDbg, and building a working first proof of concept debugger adapter for VS Code.</summary>
        <content type="html" xml:base="https://svnscha.de/posts/the-joy-of-writing-a-debugger-adapter-for-visual-studio-code/">&lt;h2 id=&quot;current-state&quot;&gt;Current State&lt;/h2&gt;
&lt;p&gt;Before I get into the story, here is the current result: Visual Studio Code debugging a C++ application with WinDbg under the hood through my first proof of concept integration.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/screenshots/2026-04-15-dap-windbg.png&quot; alt=&quot;WinDbg running through the VS Code debugger adapter&quot;&gt;&lt;/p&gt;
&lt;p&gt;Right now this is still an early POC, not a finished debugger experience. It proves the architecture works and that VS Code can drive WinDbg through a custom adapter, but it still needs a lot more iteration before I would call it really useful.&lt;/p&gt;
&lt;p&gt;One detail I already like is that the debugger command window is not a fake simplified console. I can type regular WinDbg commands straight into it.&lt;/p&gt;
&lt;h2 id=&quot;why&quot;&gt;Why&lt;/h2&gt;
&lt;p&gt;Because I do what I always do when a workflow annoys me: I build a tool until the annoyance goes away.&lt;/p&gt;
&lt;p&gt;This time the pain point was remote debugging a service application running inside a VM. My ideal flow was simple:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Compile locally&lt;/li&gt;
&lt;li&gt;Deploy to the VM&lt;/li&gt;
&lt;li&gt;Start or restart the service&lt;/li&gt;
&lt;li&gt;Attach a debugger&lt;/li&gt;
&lt;li&gt;Get back to work without ten minutes of clicking around&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That sounded perfectly reasonable: a small helper extension for Visual Studio Code, a bit of automation, some launch configuration glue, and done.&lt;/p&gt;
&lt;p&gt;That was the plan.&lt;/p&gt;
&lt;p&gt;It did not stay the plan.&lt;/p&gt;
&lt;h2 id=&quot;removing-friction&quot;&gt;Removing Friction&lt;/h2&gt;
&lt;p&gt;I started with the pragmatic part first: automate the annoying steps.&lt;/p&gt;
&lt;p&gt;I wired up a helper extension that could compile the service, copy the binaries to the VM, and prepare the remote machine for debugging. This is my favorite kind of productivity work, because the payoff is immediate. Every bit of friction you remove saves time again and again.&lt;/p&gt;
&lt;p&gt;Once the plumbing was in place, I expected the final step to be easy: tell VS Code to remote debug the service and call it a day.&lt;/p&gt;
&lt;p&gt;Instead, I hit a wall.&lt;/p&gt;
&lt;h2 id=&quot;vs-code-is-not-visual-studio&quot;&gt;VS Code Is Not Visual Studio&lt;/h2&gt;
&lt;p&gt;At first glance, this feels like something that should already exist. Visual Studio has mature debugging capabilities. VS Code has debugging. Microsoft builds both. Surely there must be a supported path to do classic remote Windows debugging from VS Code, right?&lt;/p&gt;
&lt;p&gt;Not really.&lt;/p&gt;
&lt;p&gt;The more I dug into it, the clearer it became that the debugging stack in VS Code is a different world. VS Code speaks the Debug Adapter Protocol, or DAP. It expects debugger extensions to implement that protocol and sit between the editor and the actual debugger engine.&lt;/p&gt;
&lt;p&gt;That part is good. DAP is a clean idea.&lt;/p&gt;
&lt;p&gt;The frustrating bit was this: the specific native Windows remote-debugging flow I wanted was not available through a supported public route in VS Code. The Visual Studio native debugger stack is not something you can just plug into from your own VS Code extension and say, &quot;Thanks, I will take remote debugging from here.&quot; And the VS Code side does not magically inherit Visual Studio's remote debugging capabilities just because both products come from Microsoft.&lt;/p&gt;
&lt;p&gt;So after spending time exploring options, the conclusion was simple:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;There was no supported way to get the workflow I wanted with the existing pieces.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;building-it-anyway&quot;&gt;Building It Anyway&lt;/h2&gt;
&lt;p&gt;Once I accepted that I would not be wiring together existing public APIs, the problem changed shape.&lt;/p&gt;
&lt;p&gt;The question was no longer, &quot;How do I enable remote debugging in VS Code?&quot;&lt;/p&gt;
&lt;p&gt;It became:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&quot;What debugger engine can I control myself, and how do I make VS Code talk to it properly?&quot;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;That is where WinDbg entered the story.&lt;/p&gt;
&lt;p&gt;WinDbg already knows how to do the important parts. User-mode debugging. Remote debugging. Kernel debugging. Crash dump analysis. It is a serious tool with decades of capability behind it.&lt;/p&gt;
&lt;p&gt;But again, my tool of choice did not give me the VS Code experience I wanted out of the box.&lt;/p&gt;
&lt;p&gt;So the next step was clear:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Build a debugger adapter for WinDbg.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id=&quot;learning-dap&quot;&gt;Learning DAP&lt;/h2&gt;
&lt;p&gt;If you want to integrate a debugger with VS Code, DAP is the contract. Once I sat down and read it properly, a lot of things clicked.&lt;/p&gt;
&lt;p&gt;The official home for it is the &lt;a href=&quot;https://microsoft.github.io/debug-adapter-protocol/&quot;&gt;Debug Adapter Protocol website&lt;/a&gt;, and that site is worth bookmarking because it has both the overview and the actual specification. At the time I was working through this, the important mental model was simple: VS Code and the adapter exchange JSON messages with a very predictable shape, and the protocol defines what those messages are supposed to mean.&lt;/p&gt;
&lt;p&gt;The protocol is straightforward. VS Code sends requests like &lt;code&gt;initialize&lt;/code&gt;, &lt;code&gt;launch&lt;/code&gt;, &lt;code&gt;attach&lt;/code&gt;, &lt;code&gt;setBreakpoints&lt;/code&gt;, &lt;code&gt;stackTrace&lt;/code&gt;, &lt;code&gt;variables&lt;/code&gt;, &lt;code&gt;continue&lt;/code&gt;, &lt;code&gt;next&lt;/code&gt;, and so on, and your adapter answers them while emitting events like &lt;code&gt;initialized&lt;/code&gt;, &lt;code&gt;stopped&lt;/code&gt;, &lt;code&gt;continued&lt;/code&gt;, and &lt;code&gt;terminated&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;That sounds simple enough until you remember that a debugger is mostly state, timing, and edge cases.&lt;/p&gt;
&lt;p&gt;Still, the protocol gave me exactly what I needed: a structured way to make VS Code think in terms of debugging, while I figured out how to translate that into WinDbg behavior.&lt;/p&gt;
&lt;p&gt;This was the moment where the project stopped being a helper extension and became a real debugger adapter.&lt;/p&gt;
&lt;p&gt;To make that a bit more concrete, a minimal launch payload for the current POC looks roughly like this:&lt;/p&gt;
&lt;p&gt;The adapter currently points at the debugging engine directly, so the &lt;code&gt;windbg&lt;/code&gt; setting names the WinDbg stack even though the configured path is &lt;code&gt;dbgeng.dll&lt;/code&gt; rather than &lt;code&gt;windbg.exe&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;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;type&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;dapwdbg&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;request&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;launch&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;name&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Launch through WinDbg&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;windbg&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;C:/Program Files (x86)/Windows Kits/10/Debuggers/x64/dbgeng.dll&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;program&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;${workspaceFolder}/build/Debug/testapp.exe&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;stopAtEntry&quot;&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:#D4D4D4&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That &lt;code&gt;stopAtEntry&lt;/code&gt; flag is a small but useful quality-of-life feature. I added it because it is a familiar debugger option and useful to have. By default it is &lt;code&gt;false&lt;/code&gt;, so the adapter continues after &lt;code&gt;configurationDone&lt;/code&gt;. If you set it to &lt;code&gt;true&lt;/code&gt;, VS Code stays paused at entry and waits for the next command.&lt;/p&gt;
&lt;p&gt;And on the wire, the messages are simple, which is exactly what you want from a protocol. A stop event coming back from the adapter looks roughly like this:&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;seq&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#B5CEA8&quot;&gt;17&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;type&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;event&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;event&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;stopped&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;body&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;reason&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;entry&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;description&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Paused at process entry.&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;threadId&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &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&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;		&quot;allThreadsStopped&quot;&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:#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;That might not look exciting, but it is the important part: once VS Code receives something in that shape at the right time, it knows how to show the stack, scopes, variables, and stepping UI around it.&lt;/p&gt;
&lt;h2 id=&quot;step-1-parse-the-schemas-instead-of-hand-writing-everything&quot;&gt;Step 1: Parse the Schemas Instead of Hand-Writing Everything&lt;/h2&gt;
&lt;p&gt;One thing became obvious very quickly: I did not want to manually model the entire protocol by hand in C#.&lt;/p&gt;
&lt;p&gt;That is the kind of task that feels fine for the first ten message types and becomes a waste of time by the next fifty.&lt;/p&gt;
&lt;p&gt;DAP is defined by schemas. Machines should read schemas. Machines should generate code. Humans should not write all of that by hand.&lt;/p&gt;
&lt;p&gt;So one of the first pieces I built was a small toolchain around the protocol definitions.&lt;/p&gt;
&lt;p&gt;The idea was straightforward:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Parse the protocol schemas&lt;/li&gt;
&lt;li&gt;Convert them into C# types&lt;/li&gt;
&lt;li&gt;Use generated models for requests, responses, events, and payloads&lt;/li&gt;
&lt;li&gt;Stop wasting time on repetitive plumbing&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This bought me two things immediately:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Better consistency with the protocol&lt;/li&gt;
&lt;li&gt;Faster iteration whenever I needed to expand coverage&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It also reduced a whole category of annoying bugs where hand-written DTOs silently drift away from the spec.&lt;/p&gt;
&lt;p&gt;That generator phase was not glamorous, but it kept paying off. Every time I added another request handler later, I was very happy not to be hand-authoring another stack of protocol classes.&lt;/p&gt;
&lt;p&gt;The nice part is that the generated code stays very literal. For example, the generated &lt;code&gt;LaunchRequest&lt;/code&gt; model is basically just a strongly typed wrapper around the DAP shape, with &lt;code&gt;Command = &quot;launch&quot;&lt;/code&gt; baked in and a typed &lt;code&gt;Arguments&lt;/code&gt; property. A generated event model like &lt;code&gt;StoppedEvent&lt;/code&gt; then points at a generated &lt;code&gt;StoppedEventBody&lt;/code&gt; class with fields such as &lt;code&gt;Reason&lt;/code&gt;, &lt;code&gt;Description&lt;/code&gt;, &lt;code&gt;ThreadId&lt;/code&gt;, and &lt;code&gt;AllThreadsStopped&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;That is exactly the level of abstraction I wanted. The protocol layer should be boring and explicit so the interesting work can happen one layer above it.&lt;/p&gt;
&lt;h2 id=&quot;step-2-a-python-generator-that-produces-c&quot;&gt;Step 2: A Python Generator That Produces C#&lt;/h2&gt;
&lt;p&gt;Yes, the adapter is in C#.&lt;/p&gt;
&lt;p&gt;Yes, I used Python to generate part of it.&lt;/p&gt;
&lt;p&gt;That combination made sense for the job. Python is still one of the fastest ways to build a transformation tool, especially when the task is basically: read structured input, normalize it, and generate source code.&lt;/p&gt;
&lt;p&gt;So I built a Python tool that parsed the protocol definitions and generated the corresponding C# code.&lt;/p&gt;
&lt;p&gt;That generator handled the boring but important parts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Request and response classes&lt;/li&gt;
&lt;li&gt;Event payloads&lt;/li&gt;
&lt;li&gt;Shared protocol types&lt;/li&gt;
&lt;li&gt;Optional fields and enum-like shapes&lt;/li&gt;
&lt;li&gt;Enough structure to keep serialization predictable&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This turned out to be one of the highest leverage steps in the whole project. Instead of spending my time on protocol plumbing, I could focus on the parts that actually matter: session management, command execution, state transitions, and debugger behavior.&lt;/p&gt;
&lt;p&gt;In other words, I automated the boring part so I could focus on the interesting part.&lt;/p&gt;
&lt;h2 id=&quot;step-3-wrap-windbg-through-dbgeng&quot;&gt;Step 3: Wrap WinDbg Through dbgeng&lt;/h2&gt;
&lt;p&gt;Once the protocol layer was under control, the next question was how to talk to the debugger engine itself.&lt;/p&gt;
&lt;p&gt;For that I went with &lt;code&gt;dbgeng&lt;/code&gt;, the Windows debugging engine behind the debugger family. That is where the real power is. It is also where some of the complexity is.&lt;/p&gt;
&lt;p&gt;On the package side, the current codebase still reflects some iteration. The wrapper layer currently leans on the public DbgEng interop exposed through the ClrMD packages &lt;code&gt;Microsoft.Diagnostics.Runtime&lt;/code&gt; and &lt;code&gt;Microsoft.Diagnostics.Runtime.Utilities&lt;/code&gt;. Separately, the broader adapter codebase also references &lt;code&gt;Microsoft.Debugging.Platform.DbgEng&lt;/code&gt; and &lt;code&gt;Microsoft.Debugging.Platform.DbgX&lt;/code&gt;, which are part of the newer Microsoft debugging platform stack. That split is real in the code today, but I do not want to pretend it is already the final architecture.&lt;/p&gt;
&lt;p&gt;In the wrapper itself, the core interfaces in use right now are &lt;code&gt;IDebugClient&lt;/code&gt;, &lt;code&gt;IDebugControl&lt;/code&gt;, &lt;code&gt;IDebugSymbols&lt;/code&gt;, and &lt;code&gt;IDebugSystemObjects&lt;/code&gt;. For callbacks, I register &lt;code&gt;IDebugEventCallbacks&lt;/code&gt; and &lt;code&gt;IDebugOutputCallbacks&lt;/code&gt;. That gives me the essential surface area I need to launch or attach, execute commands, inspect symbols, enumerate threads, and react to breakpoints, exceptions, exits, and debugger output.&lt;/p&gt;
&lt;p&gt;This wrapper layer became the core of the adapter. Its job was to expose the debugger in a way that fit the expectations of DAP rather than the expectations of a human typing commands into a debugger console.&lt;/p&gt;
&lt;p&gt;That means translating concepts like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Launching or attaching to a target&lt;/li&gt;
&lt;li&gt;Resuming and breaking execution&lt;/li&gt;
&lt;li&gt;Enumerating threads&lt;/li&gt;
&lt;li&gt;Building stack traces&lt;/li&gt;
&lt;li&gt;Reading locals and variables&lt;/li&gt;
&lt;li&gt;Handling modules and symbols&lt;/li&gt;
&lt;li&gt;Surfacing exceptions, exits, and other stop reasons&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you have ever worked with debugger APIs, you know they do not hide much complexity. You need to be explicit. You need to think about ownership, state, timing, callbacks, and edge cases that often show up only once things are already going wrong.&lt;/p&gt;
&lt;p&gt;One small example was line stepping. I had already enabled source line support and saw source locations show up, so for a moment I thought &lt;code&gt;step over&lt;/code&gt; was working.&lt;/p&gt;
&lt;p&gt;It did not.&lt;/p&gt;
&lt;p&gt;What I had actually proven was that WinDbg could show me line information. That is not the same thing as telling it to do source-based stepping. I had forgotten to enable that part as well. The fix was one extra command:&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;c#&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; void&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; EnableSourceLineSupport&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:#DCDCAA&quot;&gt;	ThrowIfDisposed&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:#DCDCAA&quot;&gt;	ExecuteDebuggerCommand&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;.lines -e&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Could not enable source line support&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:#DCDCAA&quot;&gt;	ExecuteDebuggerCommand&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;l+t&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Could not enable source-based stepping&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;That was a very debugger-adapter kind of bug. Everything looked correct for just long enough to be misleading.&lt;/p&gt;
&lt;h2 id=&quot;step-4-implement-the-real-requests-and-iterate&quot;&gt;Step 4: Implement the Real Requests and Iterate&lt;/h2&gt;
&lt;p&gt;With the generated protocol types on one side and the WinDbg wrapper on the other, I could finally work on the adapter loop itself.&lt;/p&gt;
&lt;p&gt;At this point, the adapter was useful for actual debugging.&lt;/p&gt;
&lt;p&gt;I began with the core requests: &lt;code&gt;initialize&lt;/code&gt;, &lt;code&gt;launch&lt;/code&gt;, &lt;code&gt;attach&lt;/code&gt;, &lt;code&gt;setBreakpoints&lt;/code&gt;, &lt;code&gt;configurationDone&lt;/code&gt;, &lt;code&gt;threads&lt;/code&gt;, &lt;code&gt;stackTrace&lt;/code&gt;, &lt;code&gt;scopes&lt;/code&gt;, &lt;code&gt;variables&lt;/code&gt;, &lt;code&gt;continue&lt;/code&gt;, &lt;code&gt;pause&lt;/code&gt;, &lt;code&gt;next&lt;/code&gt;, &lt;code&gt;stepIn&lt;/code&gt;, &lt;code&gt;stepOut&lt;/code&gt;, &lt;code&gt;disconnect&lt;/code&gt;, ...&lt;/p&gt;
&lt;p&gt;On paper that looks like a clean checklist. In reality, each of those requests is a conversation between at least three parties:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;VS Code and its expectations&lt;/li&gt;
&lt;li&gt;The adapter and its internal state machine&lt;/li&gt;
&lt;li&gt;The debugger engine and its current state&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So the implementation loop looked roughly like this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Add one request&lt;/li&gt;
&lt;li&gt;Run it in VS Code&lt;/li&gt;
&lt;li&gt;Watch it fail in an interesting way&lt;/li&gt;
&lt;li&gt;Fix the translation layer&lt;/li&gt;
&lt;li&gt;Hit the next state mismatch&lt;/li&gt;
&lt;li&gt;Repeat until the experience works the way it should&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is also where you learn that &quot;technically works&quot; and &quot;feels right to use&quot; are not the same thing.&lt;/p&gt;
&lt;p&gt;It is not enough to answer the request. You have to answer it in the right order, at the right time, and with enough fidelity that the editor can build a coherent UI around it.&lt;/p&gt;
&lt;p&gt;That was one of the more interesting parts of the project.&lt;/p&gt;
&lt;p&gt;Here is a tiny slice of what that translation layer looks like today. After launch, the adapter records whether it should remain paused at entry or continue once VS Code finishes sending its setup 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;c#&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;_launchAwaitingConfigurationDone&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:#9CDCFE&quot;&gt;_launchStopAtEntry&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; = &lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;DapArgumentReader&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;TryGetBoolean&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;arguments&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;stopAtEntry&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:#9CDCFE&quot;&gt;DapArgumentReader&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;TryGetBoolean&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;arguments&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;stopOnEntry&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:#569CD6&quot;&gt;false&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 is the kind of code that looks harmless until you realize it decides whether the whole session feels smooth or broken.&lt;/p&gt;
&lt;h2 id=&quot;when-it-worked&quot;&gt;When It Worked&lt;/h2&gt;
&lt;p&gt;At some point the pieces clicked together.&lt;/p&gt;
&lt;p&gt;I could launch a debugging session from VS Code, talk to WinDbg through the adapter, and use the editor as if this had always been a normal supported workflow.&lt;/p&gt;
&lt;p&gt;It also stopped being just about the original VM service scenario. Because the adapter sits on top of WinDbg, it opened the door to more than the first use case:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Local launch and local attach already work in the adapter&lt;/li&gt;
&lt;li&gt;Dump-file attach is wired in&lt;/li&gt;
&lt;li&gt;Remote attach and kernel scenarios are the next serious end-to-end targets&lt;/li&gt;
&lt;li&gt;Crash dump debugging is now a realistic extension of the same architecture&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That was the point where the project started to feel solid.&lt;/p&gt;
&lt;h2 id=&quot;why-this-was-fun&quot;&gt;Why This Was Fun&lt;/h2&gt;
&lt;p&gt;This was fun for the same reason good tooling work is always fun: it changes how you work.&lt;/p&gt;
&lt;p&gt;You remove friction once, then benefit from it every day after that. And along the way, you learn where the real product boundaries are. Visual Studio and VS Code may look adjacent from the outside, but their debugging plumbing is very different. Once that became clear, the path forward became clear too.&lt;/p&gt;
&lt;h2 id=&quot;what-i-learned&quot;&gt;What I Learned&lt;/h2&gt;
&lt;p&gt;This project taught me a few things very clearly:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If a workflow matters enough, it is often worth productizing it for yourself&lt;/li&gt;
&lt;li&gt;DAP is a really good abstraction for debugger integration&lt;/li&gt;
&lt;li&gt;Code generation is the right answer when a protocol brings lots of repetitive structure&lt;/li&gt;
&lt;li&gt;Debugger adapters are mostly about state, timing, and translation&lt;/li&gt;
&lt;li&gt;WinDbg becomes much more approachable when you put a good UX in front of it&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Also: if you think you fixed stepping, make sure you actually fixed it and did not just make the debugger show line numbers.&lt;/p&gt;
&lt;p&gt;Most importantly, I was reminded again that some of the best side projects start as practical attempts to make tomorrow less annoying than today.&lt;/p&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;
&lt;p&gt;I only wanted a better remote debugging workflow for a service running in a VM.&lt;/p&gt;
&lt;p&gt;That turned into DAP, schema parsing, code generation, &lt;code&gt;dbgeng&lt;/code&gt;, and a long series of state-machine bugs.&lt;/p&gt;
&lt;p&gt;The result so far is a working first proof of concept: a WinDbg-backed debugger adapter for Visual Studio Code with local &lt;code&gt;launch&lt;/code&gt;, local attach, dump-file attach, stack traces, scopes, variables, breakpoints, and step commands working well enough to prove the architecture.&lt;/p&gt;
&lt;p&gt;That matters because it means I finally have a base I can build on. I have not implemented remote debugging or kernel debugging yet, but I now feel ready to take a real shot at both without getting lost in the basics again.&lt;/p&gt;
&lt;h2 id=&quot;next-steps&quot;&gt;Next Steps&lt;/h2&gt;
&lt;p&gt;The next step is to use this base for the work I actually set out to do:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Harden the current adapter and clean up rough edges in the state machine&lt;/li&gt;
&lt;li&gt;Start implementing the actual remote debugging flow for the VM scenario&lt;/li&gt;
&lt;li&gt;Take a first real implementation shot at kernel debugging too&lt;/li&gt;
&lt;li&gt;Keep the scope under control so the project does not drift again like it did at the start&lt;/li&gt;
&lt;li&gt;Open source the project once the foundation is stable enough&lt;/li&gt;
&lt;li&gt;Keep turning WinDbg power into something that feels normal inside VS Code&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And yes, this all started with &quot;I just want to remote debug a service on a VM.&quot;&lt;/p&gt;
&lt;p&gt;That is usually how the best tools start.&lt;/p&gt;
</content>
    </entry>
    <entry xml:lang="en">
        <title>Meet Ralph: The AI Coding Agent That Just Keeps Going</title>
        <published>2026-01-26T00:00:00+00:00</published>
        <updated>2026-01-26T00:00:00+00:00</updated>
        <author>
          <name>Sven Scharmentke</name>
        </author>
        <link rel="alternate" type="text/html" href="https://svnscha.de/posts/meet-ralph/"/>
        <id>https://svnscha.de/posts/meet-ralph/</id>
        <summary type="html">I built an autonomous AI agent that works from a PRD, verifies its own work, and ships code while I sleep. Here's Ralph.</summary>
        <content type="html" xml:base="https://svnscha.de/posts/meet-ralph/">&lt;p&gt;📺 &lt;strong&gt;Watch Ralph in Action:&lt;/strong&gt; &lt;a href=&quot;https://www.youtube.com/watch?v=dfZb2LPdkMk&quot;&gt;Ralph implementing its own features&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;🔗 &lt;strong&gt;Links:&lt;/strong&gt; &lt;a href=&quot;https://github.com/svnscha/ralph&quot;&gt;GitHub Repository&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Shoutout to &lt;a href=&quot;https://x.com/mattpocockuk&quot;&gt;Matt Pocock&lt;/a&gt; whose &lt;a href=&quot;https://x.com/mattpocockuk/status/2007924876548637089&quot;&gt;viral tweet about &quot;Ralph Wiggum&quot;&lt;/a&gt; sparked this whole thing.&lt;/p&gt;
&lt;h2 id=&quot;why-you-ask&quot;&gt;Why, You Ask?&lt;/h2&gt;
&lt;p&gt;Because AI coding assistants have a dirty little secret: they forget.&lt;/p&gt;
&lt;p&gt;You start strong. &quot;Refactor this auth module.&quot; The AI gets it. Code looks good. You're feeling productive.&lt;/p&gt;
&lt;p&gt;Then 15 minutes later... it forgets what you discussed. It starts solving problems you never asked about. It rewrites code you specifically said to leave alone.&lt;/p&gt;
&lt;p&gt;Sound familiar?&lt;/p&gt;
&lt;h2 id=&quot;the-problem&quot;&gt;The Problem&lt;/h2&gt;
&lt;p&gt;Most AI coding tools are built for short bursts. Quick questions, small fixes, code snippets. Try to run them for hours on a bigger task? Good luck. Context degrades. Focus drifts. You spend more time course-correcting than coding.&lt;/p&gt;
&lt;p&gt;I always wanted something different. An AI that could work autonomously on well-defined tasks. Start it before bed, wake up to working code. The missing piece? An agent that you can programmatically control.&lt;/p&gt;
&lt;p&gt;Now, with the GitHub Copilot SDK and the Ralph loop, this has become too easy not to do.&lt;/p&gt;
&lt;p&gt;So I built yet another Ralph.&lt;/p&gt;
&lt;h2 id=&quot;what-is-ralph&quot;&gt;What Is Ralph?&lt;/h2&gt;
&lt;p&gt;Ralph is a .NET CLI tool that treats AI as an autonomous agent, not a chat buddy. The key insight: &lt;strong&gt;structure breeds autonomy.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Here's how it works:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;You describe what you want.&lt;/strong&gt; Ralph asks clarifying questions, then generates a structured PRD with prioritized user stories.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Ralph executes autonomously.&lt;/strong&gt; Picks up one story at a time, implements it, runs verification scripts to confirm it works, commits, moves on.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Repeat until done.&lt;/strong&gt; You come back to find working, tested code.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;the-secret-sauce-the-prd&quot;&gt;The Secret Sauce: The PRD&lt;/h2&gt;
&lt;p&gt;At the heart of Ralph is a simple JSON file - the Product Requirements Document:&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;project&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;my-app&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;taskName&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;add-auth&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;userStories&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:#9CDCFE&quot;&gt;      &quot;id&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;US-001&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;title&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Create login endpoint&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;acceptanceCriteria&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:#CE9178&quot;&gt;        &quot;POST /api/auth/login accepts email and password&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:#CE9178&quot;&gt;        &quot;Returns JWT token on success&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:#CE9178&quot;&gt;        &quot;ralph_verify(Build) passes&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:#CE9178&quot;&gt;        &quot;ralph_verify(Test) passes&quot;&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;      &quot;priority&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &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&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;      &quot;passes&quot;&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:#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;Each story is small enough to complete in one go. Ralph picks the next one where &lt;code&gt;passes: false&lt;/code&gt;, implements it, runs verification, and marks it complete. If the build fails, it sees the errors and fixes them. If tests fail, same thing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The magic is in the &lt;code&gt;ralph_verify()&lt;/code&gt; calls.&lt;/strong&gt; These run actual C# verification scripts - compiled at runtime with Roslyn - that confirm the work is correct before moving on.&lt;/p&gt;
&lt;h2 id=&quot;verification-scripts&quot;&gt;Verification Scripts&lt;/h2&gt;
&lt;p&gt;Traditional AI assistants produce code and hope for the best. Ralph takes a different approach: &lt;strong&gt;every story must pass verification before it's marked complete&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;ralph_verify()&lt;/code&gt; function in acceptance criteria tells Ralph which scripts to run. When Ralph sees &lt;code&gt;ralph_verify(Build) passes&lt;/code&gt;, it executes the Build script and confirms it succeeds before moving on.&lt;/p&gt;
&lt;p&gt;Ralph ships with Build and Test scripts out of the box. They're C# classes compiled at runtime with Roslyn:&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;csharp&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; Ralph&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Core&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Abstractions&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;namespace&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; Ralph&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;.&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;Scripts&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:#D4D4D4&quot;&gt;[&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;VerificationScript&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Build&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Verifies solution compiles&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;public&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; class&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; Build&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; : &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;IVerificationScript&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:#4EC9B0&quot;&gt; VerificationResult&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; Execute&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:#6A9955&quot;&gt;        // Spawn dotnet build, capture output, return pass/fail&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 key insight: &lt;strong&gt;Ralph loops until all verification scripts pass&lt;/strong&gt;. If Build fails, Ralph sees the compiler errors and attempts to fix them. If Test fails, Ralph sees which tests failed and why. This self-correcting loop is what enables hours of autonomous work.&lt;/p&gt;
&lt;p&gt;Why custom scripts instead of just telling the AI &quot;run &lt;code&gt;dotnet build&lt;/code&gt;&quot;? Context efficiency. A successful build returns &quot;Build succeeded&quot; - not the entire compiler output. Only on failure does Ralph see the errors it needs to fix. This keeps the context window lean and focused on what matters.&lt;/p&gt;
&lt;p&gt;Want custom verification? Drop a script in &lt;code&gt;.ralph/scripts/&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;csharp&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 style=&quot;color:#4EC9B0&quot;&gt;VerificationScript&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Lint&quot;&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;Runs dotnet format to check code style&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;public&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; class&lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt; Lint&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; : &lt;/span&gt;&lt;span style=&quot;color:#4EC9B0&quot;&gt;IVerificationScript&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:#4EC9B0&quot;&gt; VerificationResult&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; Execute&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:#6A9955&quot;&gt;        // Run dotnet format --verify-no-changes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;        // Return pass/fail based on exit code&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;Now you can use &lt;code&gt;ralph_verify(Lint) passes&lt;/code&gt; in your acceptance criteria. Security scans, API tests, whatever you need.&lt;/p&gt;
&lt;h2 id=&quot;behind-the-scenes&quot;&gt;Behind the Scenes&lt;/h2&gt;
&lt;p&gt;Every AI model has a context window-the amount of text it can &quot;see&quot; at once. Ralph works within these limits by keeping each story self-contained.&lt;/p&gt;
&lt;p&gt;A story must fit entirely within a single context window:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The system prompt (Ralph's instructions)&lt;/li&gt;
&lt;li&gt;The current story's details and acceptance criteria&lt;/li&gt;
&lt;li&gt;Relevant source files Ralph needs to read&lt;/li&gt;
&lt;li&gt;Space for Ralph to &quot;think&quot; and generate code&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Practical guidance:&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Story Size&lt;/th&gt;
&lt;th&gt;Files Touched&lt;/th&gt;
&lt;th&gt;Recommendation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Small&lt;/td&gt;
&lt;td&gt;1-3 files&lt;/td&gt;
&lt;td&gt;✅ Ideal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;4-8 files&lt;/td&gt;
&lt;td&gt;✅ Good&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Large&lt;/td&gt;
&lt;td&gt;9-15 files&lt;/td&gt;
&lt;td&gt;⚠️ Consider splitting&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Too Large&lt;/td&gt;
&lt;td&gt;15+ files&lt;/td&gt;
&lt;td&gt;❌ Split required&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Ralph doesn't have persistent memory across stories. Each time it picks one up, it starts fresh. This isn't a bug-it's a feature that forces clean design. Each story is a complete, verifiable unit of work.&lt;/p&gt;
&lt;p&gt;&quot;Add user authentication&quot; becomes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create User entity and migration&lt;/li&gt;
&lt;li&gt;Implement password hashing service&lt;/li&gt;
&lt;li&gt;Create login endpoint&lt;/li&gt;
&lt;li&gt;Add JWT token generation&lt;/li&gt;
&lt;li&gt;Implement authentication middleware&lt;/li&gt;
&lt;li&gt;Create registration endpoint&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Each story is small enough to complete in one iteration, yet together they deliver the full feature.&lt;/p&gt;
&lt;h3 id=&quot;state-management&quot;&gt;State Management&lt;/h3&gt;
&lt;p&gt;Ralph maintains state at two levels:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;PRD state&lt;/strong&gt; (persistent) - which stories exist, which have passed, notes from completed work. This file persists across sessions and gets committed to your repo.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Session state&lt;/strong&gt; (ephemeral) - current working story, in-progress changes, retry attempts. Lives only for one execution.&lt;/p&gt;
&lt;p&gt;If Ralph crashes mid-story, that story remains &lt;code&gt;passes: false&lt;/code&gt; and will be retried on next run. Completed stories are never redone.&lt;/p&gt;
&lt;h2 id=&quot;getting-started&quot;&gt;Getting Started&lt;/h2&gt;
&lt;p&gt;You'll need .NET 10 SDK and &lt;a href=&quot;https://docs.github.com/en/copilot/github-copilot-in-the-cli&quot;&gt;GitHub Copilot CLI&lt;/a&gt; installed and authenticated.&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:#6A9955&quot;&gt;# Build and pack&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; build&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; pack&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; src/Ralph/Ralph.csproj&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;# Install globally&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; tool&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; install&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; --global&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; --add-source&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; src/Ralph/nupkg&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; Ralph&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then in your project:&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:#6A9955&quot;&gt;# Option A: Plan and implement in one go&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;ralph&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;# Option B: Plan first, then implement separately&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;ralph&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; plan&lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;    # Create the PRD interactively&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;ralph&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; run&lt;/span&gt;&lt;span style=&quot;color:#6A9955&quot;&gt;     # Implement all stories from the PRD&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's it. Ralph asks &quot;What would you like to accomplish?&quot;, creates a PRD from your description, and implements all stories. Go grab coffee.&lt;/p&gt;
&lt;h2 id=&quot;why-this-works&quot;&gt;Why This Works&lt;/h2&gt;
&lt;p&gt;Traditional AI assistants are designed for conversation. Ralph is designed for execution.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Traditional AI&lt;/th&gt;
&lt;th&gt;Ralph&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Duration&lt;/td&gt;
&lt;td&gt;Minutes&lt;/td&gt;
&lt;td&gt;Hours&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Context&lt;/td&gt;
&lt;td&gt;Degrades over time&lt;/td&gt;
&lt;td&gt;Fresh per story&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Verification&lt;/td&gt;
&lt;td&gt;You check manually&lt;/td&gt;
&lt;td&gt;Scripts verify automatically&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Human involvement&lt;/td&gt;
&lt;td&gt;Constant supervision&lt;/td&gt;
&lt;td&gt;Set and forget&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The AI model is the same. The difference is the workflow architecture around it.&lt;/p&gt;
&lt;h2 id=&quot;the-catch&quot;&gt;The Catch&lt;/h2&gt;
&lt;p&gt;Stories need to be small enough to fit in a context window. If a story requires understanding 50 files at once, it'll struggle. Keep them focused: 1-5 files per story, one concern each.&lt;/p&gt;
&lt;p&gt;Also, this isn't magic. Ralph still needs good acceptance criteria. Vague requirements produce vague results. But that's true of any engineering work.&lt;/p&gt;
&lt;h2 id=&quot;summary&quot;&gt;Summary&lt;/h2&gt;
&lt;p&gt;AI coding assistants are great at short bursts but fall apart on longer tasks. Ralph fixes that by giving AI what it needs: structure. A PRD breaks work into discrete stories. Verification scripts confirm each one works. The result? An agent that can run for hours without wandering off course.&lt;/p&gt;
&lt;p&gt;The shift from &quot;AI assistant&quot; to &quot;AI agent&quot; isn't just semantic. It's the difference between babysitting and delegating.&lt;/p&gt;
&lt;h2 id=&quot;next-steps&quot;&gt;Next Steps&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Watch the demo&lt;/strong&gt; - See Ralph implement its own features in the &lt;a href=&quot;https://www.youtube.com/watch?v=dfZb2LPdkMk&quot;&gt;YouTube video&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Clone the repo&lt;/strong&gt; - &lt;a href=&quot;https://github.com/svnscha/ralph&quot;&gt;github.com/svnscha/ralph&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Try it on something small&lt;/strong&gt; - Pick a well-defined feature, let Ralph work on it overnight, come back to commits&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That moment when you return to working, tested code you didn't write line by line? That's when it clicks.&lt;/p&gt;
</content>
    </entry>
    <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>
