<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title>svnscha - llm</title>
    <subtitle>automating annoying tasks, sharing tips, and embracing less frustration</subtitle>
    <link rel="self" type="application/atom+xml" href="https://svnscha.de/tags/llm/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://svnscha.de"/>
    <generator uri="https://astro.build/">Astro</generator>
    <updated>2025-12-31T00:00:00+00:00</updated>
    <id>https://svnscha.de/tags/llm/atom.xml</id>
    <entry xml:lang="en">
        <title>DGX Spark: Hello World</title>
        <published>2025-12-31T00:00:00+00:00</published>
        <updated>2025-12-31T00:00:00+00:00</updated>
        <author>
          <name>Sven Scharmentke</name>
        </author>
        <link rel="alternate" type="text/html" href="https://svnscha.de/posts/dgx-spark-hello-word/"/>
        <id>https://svnscha.de/posts/dgx-spark-hello-word/</id>
        <summary type="html">My first experiments with NVIDIA's DGX Spark, including local models with Ollama, LibreChat, and ComfyUI.</summary>
        <content type="html" xml:base="https://svnscha.de/posts/dgx-spark-hello-word/">&lt;h2 id=&quot;what-better-way-to-end-2025&quot;&gt;What Better Way to End 2025?&lt;/h2&gt;
&lt;p&gt;So here's how I'm closing out the year: playing with an NVIDIA DGX Spark. Yes, &lt;em&gt;that&lt;/em&gt; DGX Spark - the &quot;personal AI supercomputer&quot; that makes my wallet cry but my inner tech nerd do a happy dance.&lt;/p&gt;
&lt;p&gt;I have already spent some time testing LibreChat and ComfyUI on it. The courses at &lt;a href=&quot;https://stable-diffusion-art.com/&quot;&gt;stable-diffusion-art.com&lt;/a&gt; also helped me get started with image-generation workflows.&lt;/p&gt;
&lt;p&gt;And what better way to ring in the new year than with 128GB of unified memory and a Blackwell GPU sitting on my desk? I mean, some people do fireworks. I do neural networks.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(Yes, I'm aware that's possibly the nerdiest New Year's joke ever.)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Let me walk you through my first-day adventures getting this beautiful beast up and running. Fair warning: there may have been moments of childlike excitement, some creative problem-solving (read: frantically searching for a USB-C keyboard), and an unhealthy amount of &quot;let's try this and see what happens.&quot;&lt;/p&gt;
&lt;h2 id=&quot;the-unboxing&quot;&gt;The Unboxing&lt;/h2&gt;
&lt;p&gt;There's something special about unboxing new hardware. But unboxing a &lt;em&gt;DGX&lt;/em&gt;? That hits different.&lt;/p&gt;
&lt;p&gt;The packaging screams premium - NVIDIA clearly understands that when you're dropping serious money on a personal AI supercomputer, presentation matters. Opening the box felt like a tech ritual. Inside, nestled in protective foam, sat this compact powerhouse that somehow packs a Blackwell GPU and 128GB of unified memory into a form factor that actually fits on a desk.&lt;/p&gt;
&lt;div class=&quot;image-gallery&quot;&gt;
  &lt;img src=&quot;/screenshots/2025-12-22-dgx-spark-unbox-1.jpg&quot; alt=&quot;DGX Spark unboxing - first look&quot;&gt;
  &lt;img src=&quot;/screenshots/2025-12-22-dgx-spark-unbox-2.jpg&quot; alt=&quot;DGX Spark - the unit&quot;&gt;
  &lt;img src=&quot;/screenshots/2025-12-22-dgx-spark-unbox-3.jpg&quot; alt=&quot;DGX Spark - ready to go&quot;&gt;
  &lt;img src=&quot;/screenshots/25-12-22-dgx-spark-unbox-4.jpg&quot; alt=&quot;DGX Spark - booting&quot;&gt;
&lt;/div&gt;
&lt;p&gt;The unit itself is surprisingly compact. I expected something massive and loud, but NVIDIA engineered this thing to be almost civilized. Almost. We'll see how that holds up once I start pushing it with larger models. Everything was included - power cables, documentation, and that unmistakable feeling of &lt;em&gt;&quot;I'm about to have way too much fun with this.&quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Alright, enough admiring the hardware. Time to make it do things.&lt;/p&gt;
&lt;h2 id=&quot;the-setup-experience&quot;&gt;The Setup Experience&lt;/h2&gt;
&lt;p&gt;I went with the local installation method - straightforward enough. Well, mostly. Here's a fun discovery: do you own a keyboard with USB Type-C? No? Neither did I. Fortunately, a laptop docking station saved the day. Crisis averted.&lt;/p&gt;
&lt;p&gt;Once the initial setup was complete, first things first - let's give this machine a proper identity:&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;sudo&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; hostnamectl&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; set-hostname&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; spark-1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; reboot&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Why &lt;code&gt;spark-1&lt;/code&gt;? Because who knows if there'll be a &lt;code&gt;spark-2&lt;/code&gt; someday. Better to be prepared. Future me will thank present me for this foresight. Or curse me for the temptation.&lt;/p&gt;
&lt;h2 id=&quot;installing-ollama&quot;&gt;Installing Ollama&lt;/h2&gt;
&lt;p&gt;After updating the system, I started with something familiar. &lt;a href=&quot;https://ollama.com/&quot;&gt;Ollama&lt;/a&gt; has become my go-to for local LLM deployment - it's absurdly simple to set up:&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;curl&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; -fsSL&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; https://ollama.com/install.sh&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; | &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;sh&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, I know what you're thinking - &lt;em&gt;&quot;But what about vLLM? What about llama.cpp? What about those fancy self-compiled versions optimized for Blackwell's SM120 architecture?&quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Yes, I've seen those posts. Yes, I'll explore them. But that's a future adventure. For now, let's start with the classics and make sure everything actually works before we go down the optimization rabbit hole.&lt;/p&gt;
&lt;p&gt;With Ollama ready, time to pull a model:&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;ollama&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; pull&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; gpt-oss:20b&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Could I run the massive 120B parameter models? Absolutely - that's kind of the whole point of having this hardware. But let's walk before we run. Download a smaller model to verify everything works, then let the big ones download overnight while I sleep. Strategy.&lt;/p&gt;
&lt;h2 id=&quot;setting-up-librechat&quot;&gt;Setting Up LibreChat&lt;/h2&gt;
&lt;p&gt;Running models from the CLI is fine for testing, but I wanted a web interface. I previously used &lt;a href=&quot;/posts/self-hosted-llm/&quot;&gt;Open WebUI&lt;/a&gt; for my self-hosted LLM setup; this time I chose &lt;a href=&quot;https://github.com/danny-avila/LibreChat&quot;&gt;LibreChat&lt;/a&gt;, an open-source chat UI that supports local models.&lt;/p&gt;
&lt;p&gt;Docker was pre-installed on the DGX Spark (nice touch, NVIDIA). Though my user wasn't in the docker group - but I'll assume you can figure that one out.&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;mkdir&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; -p&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; ~/workspaces&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; ~/workspaces/&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&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;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;p&gt;After some experimentation, I settled on these configuration changes:&lt;/p&gt;
&lt;h3 id=&quot;environment-configuration&quot;&gt;Environment Configuration&lt;/h3&gt;
&lt;p&gt;In &lt;code&gt;.env&lt;/code&gt;, the key change:&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:#9CDCFE&quot;&gt;ENDPOINTS&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;custom&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 a &lt;code&gt;docker-compose.override.yml&lt;/code&gt; for your custom settings, and a &lt;code&gt;librechat.yaml&lt;/code&gt; for the LibreChat-specific configuration.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;docker-compose.override.yml&lt;/strong&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;yml&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;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;librechat.yaml&lt;/strong&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;yml&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:#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;Ollama&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;ollama&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:11434/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&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;          &quot;gpt-oss:20b&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:#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;true&lt;/span&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;current_model&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;      summaryModel&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;: &lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;&quot;current_model&quot;&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;Ollama&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And that's... almost it.&lt;/p&gt;
&lt;h2 id=&quot;the-reverse-proxy-dance&quot;&gt;The Reverse Proxy Dance&lt;/h2&gt;
&lt;p&gt;Here's where it gets slightly more involved. LibreChat enforces secure cookies by default - great for security, annoying when you're hitting F5 every five seconds during development and don't want to re-authenticate each time.&lt;/p&gt;
&lt;p&gt;The solution? Set up a reverse proxy with HTTPS. I use &lt;a href=&quot;https://caddyserver.com/&quot;&gt;Caddy&lt;/a&gt; in my home infrastructure because life's too short to manage SSL certificates manually.&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;chat.int.domain.tld {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    @lan remote_ip 172.16.29.0/24&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    handle {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        reverse_proxy @lan http://172.16.2.123:3080&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;    handle {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        templates&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        respond &quot;Access denied, {{.RemoteIP}}&quot; 403&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;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This configuration does a few things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Only allows access from my local network (the &lt;code&gt;172.16.29.0/24&lt;/code&gt; range)&lt;/li&gt;
&lt;li&gt;Proxies requests to the DGX Spark running LibreChat on port 3080&lt;/li&gt;
&lt;li&gt;Returns a 403 for anyone trying to access from outside&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It's not always open from outside but when it is (to refresh certificates) this is a great and easy way to deal with this.&lt;/p&gt;
&lt;p&gt;I've also configured a custom DNS zone in my router to make &lt;code&gt;chat.int.domain.tld&lt;/code&gt; resolve correctly, but that's infrastructure stuff for another post.&lt;/p&gt;
&lt;h2 id=&quot;first-impressions&quot;&gt;First Impressions&lt;/h2&gt;
&lt;p&gt;And just like that - we're live. Quick setup, everything working, ready to chat with local models running on actual Blackwell hardware. The response times are snappy, the interface is clean, and I can already tell this is going to be a fun playground.&lt;/p&gt;
&lt;h2 id=&quot;next-steps&quot;&gt;Next Steps&lt;/h2&gt;
&lt;p&gt;Now that the basics are running, it's time to explore what this hardware can actually do. On my list:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Document Understanding&lt;/strong&gt;: Feed it files and see how well it comprehends them&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Voice integration&lt;/strong&gt;: Testing spoken conversations with local models&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Image generation with ComfyUI&lt;/strong&gt;: Exploring image-generation workflows on the Blackwell GPU. The &lt;a href=&quot;https://stable-diffusion-art.com/&quot;&gt;stable-diffusion-art.com&lt;/a&gt; courses have been a useful starting point.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These are just the fundamentals to get comfortable with the platform. But honestly? Between LibreChat's slick interface for chatting with local models and ComfyUI's node-based wizardry for image generation, I've already had more fun than should be legal.&lt;/p&gt;
&lt;p&gt;It was a good way to end 2025: experimenting with new hardware and learning what it can do.&lt;/p&gt;
&lt;p&gt;Here's to closing out the year with new adventures, and to many more in 2026! 🎉&lt;/p&gt;
&lt;p&gt;I will share more results as I continue testing the DGX Spark.&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>
    <entry xml:lang="en">
        <title>Exploring AI: Self Hosted LLM</title>
        <published>2025-03-17T00:00:00+00:00</published>
        <updated>2025-03-17T00:00:00+00:00</updated>
        <author>
          <name>Sven Scharmentke</name>
        </author>
        <link rel="alternate" type="text/html" href="https://svnscha.de/posts/self-hosted-llm/"/>
        <id>https://svnscha.de/posts/self-hosted-llm/</id>
        <summary type="html">Because running your own AI is the real power move.</summary>
        <content type="html" xml:base="https://svnscha.de/posts/self-hosted-llm/">&lt;h2 id=&quot;why-you-ask&quot;&gt;Why, You Ask?&lt;/h2&gt;
&lt;p&gt;So, I decided to self-host an LLM. Why? Every time you use an online AI model, you're handing over your data to some company. Whether it's casual conversations, coding snippets, or business-related queries, everything you type is potentially being logged, analyzed, or even used to train future models. No thanks.&lt;/p&gt;
&lt;p&gt;Instead, I prefer to keep things local. Also, I have an RTX ADA 4000 with 20GB of memory sitting here, so why not put it to good use?&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://ollama.com/&quot;&gt;Ollama&lt;/a&gt; makes it easy to run LLMs locally. I combine it with &lt;a href=&quot;https://github.com/open-webui/open-webui&quot;&gt;Open WebUI&lt;/a&gt; for the user interface and Nginx for remote access.&lt;/p&gt;
&lt;p&gt;Here is the setup.&lt;/p&gt;
&lt;h3 id=&quot;step-1-install-ollama&quot;&gt;Step 1: Install Ollama&lt;/h3&gt;
&lt;p&gt;Ollama makes deploying LLMs locally ridiculously simple. Here's how to install 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;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;curl&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; -fsSL&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; https://ollama.com/install.sh&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt; | &lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;sh&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will install Ollama and set up everything you need to start running models locally. Want to make sure it's working? Just 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;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;ollama&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; run&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; codellama:13b&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you see an interactive prompt, congrats - you've got a local LLM running!&lt;/p&gt;
&lt;h3 id=&quot;step-2-install-open-webui&quot;&gt;Step 2: Install Open WebUI&lt;/h3&gt;
&lt;p&gt;Ollama is great, but a web interface makes it even better. That's where Open WebUI comes in. It gives you a sleek, chat-like interface to interact with your models.&lt;/p&gt;
&lt;p&gt;To install Open WebUI manually without Docker, follow these steps:&lt;/p&gt;
&lt;h4 id=&quot;1-create-a-virtual-environment&quot;&gt;1. Create a Virtual Environment&lt;/h4&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;python3&lt;/span&gt;&lt;span style=&quot;color:#569CD6&quot;&gt; -m&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; venv&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; ~/openwebui-venv&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;source&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; ~/openwebui-venv/bin/activate&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;2-install-open-webui&quot;&gt;2. Install Open WebUI&lt;/h4&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;pip&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; install&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; open-webui&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;3-create-a-systemd-service&quot;&gt;3. Create a Systemd Service&lt;/h4&gt;
&lt;p&gt;To make sure Open WebUI runs on startup, create a systemd service file:&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;sudo&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; nano&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; /etc/systemd/system/openwebui.service&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Paste the following content:&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:#D4D4D4&quot;&gt;[Unit]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Description&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;Open&lt;/span&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt; WebUI&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; Service&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;After&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;network.target&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;[Service]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;User&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;$USER&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Group&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;$USER&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;WorkingDirectory&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;/home/&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;$USER&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;/openwebui-venv&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;ExecStart&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;/home/&lt;/span&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;$USER&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;/openwebui-venv/bin/open-webui&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;Restart&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;always&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;[Install]&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#9CDCFE&quot;&gt;WantedBy&lt;/span&gt;&lt;span style=&quot;color:#D4D4D4&quot;&gt;=&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt;multi-user.target&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Save and exit, then reload systemd and enable the service:&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;sudo&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; systemctl&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; daemon-reload&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; systemctl&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; enable&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; openwebui.service&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span style=&quot;color:#DCDCAA&quot;&gt;sudo&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; systemctl&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; start&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; openwebui.service&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;step-3-reverse-proxy-with-nginx&quot;&gt;Step 3: Reverse Proxy with Nginx&lt;/h3&gt;
&lt;p&gt;Now, let's make accessing our LLM easier by setting up an Nginx reverse proxy. This way, we can reach Open WebUI without exposing it directly.&lt;/p&gt;
&lt;p&gt;Here's a basic Nginx 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;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;server {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    listen 443 ssl;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    server_name chat.example.com;&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;    location / {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        proxy_pass http://localhost:8080;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        proxy_set_header Host $host;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        proxy_set_header X-Real-IP $remote_addr;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;&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;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Reload Nginx with:&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;sudo&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; systemctl&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; restart&lt;/span&gt;&lt;span style=&quot;color:#CE9178&quot;&gt; nginx&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, you can access your self-hosted LLM via &lt;code&gt;https://chat.example.com&lt;/code&gt;. Fancy.&lt;/p&gt;
&lt;h3 id=&quot;you-own-your-data-now&quot;&gt;You Own Your Data Now&lt;/h3&gt;
&lt;p&gt;One of the biggest advantages of self-hosting an LLM? Your data stays with you.&lt;/p&gt;
&lt;p&gt;No sending queries to an external API, no third-party tracking what you're asking, no potential leaks of sensitive information. It's all running on your hardware, fully under your control. Whether you're experimenting with code, processing confidential documents, or just having fun chatting with AI, everything stays local.&lt;/p&gt;
&lt;h3 id=&quot;model-sizes--performance&quot;&gt;Model Sizes &amp;amp; Performance&lt;/h3&gt;
&lt;p&gt;Of course, different models come with different memory requirements. Here's what I'm running on my RTX ADA 4000 and how much VRAM they use:&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;NAME             ID              SIZE      PROCESSOR    UNTIL              &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;codellama:7b     8fdf8f752f6e    9.4 GB    100% GPU     2 minutes from now    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;codellama:13b    9f438cb9cd58    15 GB     100% GPU     4 minutes from now    &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;gemma3:12b       6fd036cefda5    13 GB     100% GPU     4 minutes from now    &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This means I can comfortably run mid-sized models like &lt;code&gt;codellama:13b&lt;/code&gt; while keeping things snappy.&lt;/p&gt;
&lt;h3 id=&quot;choosing-the-right-gpu&quot;&gt;Choosing the Right GPU&lt;/h3&gt;
&lt;p&gt;Picking the right GPU is all about balancing performance, VRAM, and cost - because, let's be honest, unless you're running an AI research lab, you're not dropping $30,000 on an H100.&lt;/p&gt;
&lt;p&gt;Here's a quick breakdown of solid options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;NVIDIA RTX 3090 - Powerful, with 24 GB of VRAM, but it's last-gen. ($$)&lt;/li&gt;
&lt;li&gt;NVIDIA RTX 4090 - Even more powerful, with 24 GB of VRAM and better efficiency. ($$$)&lt;/li&gt;
&lt;li&gt;NVIDIA RTX 5090 - Even more and more powerful, with 24 GB of VRAM and better efficiency. ($$$$)&lt;/li&gt;
&lt;li&gt;NVIDIA RTX 4000 ADA Generation - Less powerful, but 20 GB of VRAM, and a low-profile card. ($)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now, why the ADA 4000? While the RTX 5090 is the fastest in raw compute power, VRAM is king for training AI models. The ADA 4000's 20 GB VRAM gives you enough room for Stable Diffusion training, larger batch sizes, and AI experiments, without hitting the limits of other consumer GPUs such as 3080 (10 GB).&lt;/p&gt;
&lt;p&gt;Performance-wise, the 4090 and 5090 has more horsepower, but for training workloads where memory matters more than raw speed, the ADA 4000 is the more practical and cost-efficient choice. Plus, lower power consumption makes it a better long-term option if you're running AI workloads frequently. Also, the physical size - I mean it's a low profile card. Small, fits perfectly into any case.&lt;/p&gt;
&lt;p&gt;For my needs, the RTX 4000 Ada offered a useful balance of VRAM, price, power use, and size.&lt;/p&gt;
&lt;h3 id=&quot;example&quot;&gt;Example&lt;/h3&gt;
&lt;h4 id=&quot;asking-codellama13b&quot;&gt;Asking codellama:13b&lt;/h4&gt;
&lt;p&gt;Prompt: &lt;code&gt;implement fibonacci in python and also some unit tests using pytest&lt;/code&gt;&lt;/p&gt;
&lt;video class=&quot;cast&quot; src=&quot;/casts/codellama-13b-fibonacci.webm&quot; controls&gt;
  Your browser does not support the video tag.
&lt;/video&gt;
&lt;h4 id=&quot;asking-gemma312b&quot;&gt;Asking gemma3:12b&lt;/h4&gt;
&lt;p&gt;Prompt: &lt;code&gt;implement fibonacci in python and also some unit tests using pytest&lt;/code&gt;&lt;/p&gt;
&lt;video class=&quot;cast&quot; src=&quot;/casts/gemma3-12b-fibonacci.webm&quot; controls&gt;
  Your browser does not support the video tag.
&lt;/video&gt;
&lt;h3 id=&quot;summary&quot;&gt;Summary&lt;/h3&gt;
&lt;p&gt;With an RTX ADA 4000, Ollama, Open WebUI, and an Nginx reverse proxy, I now have an AI-powered assistant running entirely on my own hardware. No subscriptions, no cloud dependencies, just raw, local AI power. If you're serious about AI and privacy, setting this up is a no-brainer. Give it a try, and let your GPU do some work.&lt;/p&gt;
</content>
    </entry>
</feed>
