<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Suresh Kumar Pallapothu</title>
    <description>The latest articles on DEV Community by Suresh Kumar Pallapothu (@suresh_kumar_de3920bedd1c).</description>
    <link>https://dev.to/suresh_kumar_de3920bedd1c</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4155639%2Fba5bc6f9-6f9b-45f1-a010-b32ca0fc2751.png</url>
      <title>DEV Community: Suresh Kumar Pallapothu</title>
      <link>https://dev.to/suresh_kumar_de3920bedd1c</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/suresh_kumar_de3920bedd1c"/>
    <language>en</language>
    <item>
      <title>Day 22: Autonomous Agents — From Passive LLMs to Goal-Driven Execution</title>
      <dc:creator>Suresh Kumar Pallapothu</dc:creator>
      <pubDate>Thu, 22 Oct 2026 18:08:44 +0000</pubDate>
      <link>https://dev.to/suresh_kumar_de3920bedd1c/day-22-autonomous-agents-from-passive-llms-to-goal-driven-execution-1ch6</link>
      <guid>https://dev.to/suresh_kumar_de3920bedd1c/day-22-autonomous-agents-from-passive-llms-to-goal-driven-execution-1ch6</guid>
      <description>&lt;p&gt;Early generative AI was &lt;strong&gt;passive&lt;/strong&gt;: you send a prompt, the model returns text, and a human decides what to do next. That is useful for drafting, but it cannot run a business workflow on its own. The next step is the &lt;strong&gt;autonomous agent&lt;/strong&gt;: a system that can plan, check its own progress and carry out a multi-step goal with little human intervention.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;In plain terms:&lt;/strong&gt; A chatbot is a colleague who answers your question and then waits. An agent is a colleague you give a task to, who comes back when it is done, or when they are stuck.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; A passive LLM returns one text answer and a human decides the next step. An autonomous agent takes a goal and plans, acts, checks the result and repeats with little human intervention. That makes it useful for workflows and also riskier, so guardrails matter. &lt;a href="https://sureshpallapothu.in/blog/day-22-autonomous-agents" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  1. The core agentic loop
&lt;/h2&gt;

&lt;p&gt;An agent runs a repeating cycle, borrowed from classic control systems.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Perceive:&lt;/strong&gt; take in the goal, the current state and any context.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plan:&lt;/strong&gt; break an abstract goal, such as "audit this quarter's server error logs and flag failing optics", into an ordered list of concrete tasks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Act:&lt;/strong&gt; pick a capability and use it: call an API, run a database query, read a file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reflect:&lt;/strong&gt; compare the result with the goal. If something went wrong, adjust and try again.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; An agent loops through four stages: perceive the goal and context, plan the tasks, act by calling a tool, and reflect on the result. It repeats until the goal is met or a limit such as a step budget is reached. &lt;a href="https://sureshpallapothu.in/blog/day-22-autonomous-agents" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Reflection is what makes an agent more than a script. If a query returns no rows, the agent can notice why, correct its approach and run it again. In practice you always cap the number of steps and the cost, because an agent that keeps retrying can loop forever.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; After an action the agent observes the result. When a query returns no rows it reflects, notices the wrong column name, corrects the query and tries again. The example is illustrative, and a step limit prevents endless retries. &lt;a href="https://sureshpallapothu.in/blog/day-22-autonomous-agents" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  2. Memory: short-term and long-term
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Working memory&lt;/strong&gt; is the context window: the current plan, intermediate values and recent tool outputs. It is fast but limited, and it disappears when the run ends.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Episodic memory&lt;/strong&gt; is a record of past runs kept in a vector database. When the agent meets a similar problem, it can retrieve how it solved a related one before.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; Working memory is the context window holding the current plan, state and recent tool outputs. Episodic memory stores past runs in a vector database so an agent can recall how it fixed a similar problem before. &lt;a href="https://sureshpallapothu.in/blog/day-22-autonomous-agents" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here is the loop in a few lines of Python. The &lt;code&gt;llm&lt;/code&gt; and &lt;code&gt;tools&lt;/code&gt; pieces are placeholders for your model call and your real functions, so treat this as the shape of an agent, not a finished one.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;MAX_STEPS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;                      &lt;span class="c1"&gt;# a hard stop so the loop cannot run forever
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run_agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;goal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;history&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Goal: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;goal&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MAX_STEPS&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# Plan + act: the model chooses the next tool call, or says it is done
&lt;/span&gt;        &lt;span class="n"&gt;decision&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;          &lt;span class="c1"&gt;# e.g. {"tool": "run_sql", "args": {...}} or {"done": "..."}
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;done&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;decision&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;decision&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;done&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;decision&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tool&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]](&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;decision&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;args&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;      &lt;span class="c1"&gt;# act
&lt;/span&gt;
        &lt;span class="c1"&gt;# Observe + reflect: put the result back so the model can adjust
&lt;/span&gt;        &lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Called &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;decision&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;tool&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; -&amp;gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Stopped: step limit reached. A human should review.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Turning a language model into an agent shifts AI from an assistant into something that executes. That power is why the next lessons are about tools, teams of agents, testing and safety.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coming Up Next
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Day 23:&lt;/strong&gt; Tool use and function calling: connecting LLMs to APIs, databases and shells.&lt;/p&gt;

&lt;h1&gt;
  
  
  AIAgents #AutonomousAI #EnterpriseAutomation #AgenticWorkflows #SoftwareEngineering
&lt;/h1&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://sureshpallapothu.in/blog/day-22-autonomous-agents" rel="noopener noreferrer"&gt;https://sureshpallapothu.in/blog/day-22-autonomous-agents&lt;/a&gt;, where this post includes animated diagrams.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>30dayaiseries</category>
    </item>
    <item>
      <title>Day 21: High-Performance Networking — Ingress and Egress for AI Clusters</title>
      <dc:creator>Suresh Kumar Pallapothu</dc:creator>
      <pubDate>Wed, 21 Oct 2026 18:13:21 +0000</pubDate>
      <link>https://dev.to/suresh_kumar_de3920bedd1c/day-21-high-performance-networking-ingress-and-egress-for-ai-clusters-55c4</link>
      <guid>https://dev.to/suresh_kumar_de3920bedd1c/day-21-high-performance-networking-ingress-and-egress-for-ai-clusters-55c4</guid>
      <description>&lt;p&gt;AI traffic does not look like ordinary web traffic. Requests carry multi-megabyte documents, and replies &lt;strong&gt;stream&lt;/strong&gt; token by token over long-lived connections (Server-Sent Events). Network settings built for short page loads break that pattern. This lesson covers three places to tune: traffic coming in, traffic between services, and traffic going out.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;In plain terms:&lt;/strong&gt; Think of the cluster as a building. The front door must stay open long enough for a slow conversation, the corridors between rooms must not jam, and the exits must be locked except for the few doors you approve.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  1. Ingress: keep long streams alive
&lt;/h2&gt;

&lt;p&gt;An ingress controller (NGINX, Traefik or Envoy, for example) sits at the front door. Its defaults are tuned for quick requests and break AI streaming in two ways.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Timeouts.&lt;/strong&gt; Default proxy read and send timeouts are often around 30 seconds, which can cut a connection while a model works through a large context. Raise them to several minutes for your slowest replies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Buffering.&lt;/strong&gt; If the proxy buffers the response, it waits for the entire reply before sending anything, which ruins real-time token streaming. Turn buffering off.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; If an ingress proxy buffers the response it waits for the whole LLM reply before sending anything to the user, which ruins real-time token streaming. Turning buffering off lets each token through as it is generated, over Server-Sent Events. &lt;a href="https://sureshpallapothu.in/blog/day-21-ai-cluster-networking" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; Default proxy timeouts of about 30 seconds cut off long generations over big contexts. Raising the read and send timeouts to several minutes keeps the connection open until the model finishes. &lt;a href="https://sureshpallapothu.in/blog/day-21-ai-cluster-networking" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  2. East-west traffic: eBPF and Cilium
&lt;/h2&gt;

&lt;p&gt;Standard Kubernetes networking uses &lt;code&gt;kube-proxy&lt;/code&gt;, which builds chains of Linux &lt;strong&gt;iptables&lt;/strong&gt; rules. As services multiply, evaluating those chains becomes a bottleneck for chatty traffic such as embeddings moving between gateways, vector stores and inference engines.&lt;/p&gt;

&lt;p&gt;An &lt;strong&gt;eBPF&lt;/strong&gt;-based network plugin such as &lt;strong&gt;Cilium&lt;/strong&gt; makes routing decisions with small programs running inside the Linux kernel and direct lookups, and it can replace &lt;code&gt;kube-proxy&lt;/code&gt;. In high-throughput datacenters, specialised network cards and kernel-bypass techniques can push latency lower still. How much you gain depends on your cluster, so measure before and after you switch.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; kube-proxy builds long chains of iptables rules that get slow to evaluate as services multiply. An eBPF-based network plugin such as Cilium makes routing decisions with kernel programs and direct lookups, which helps heavy service-to-service traffic. Gains depend on the cluster, so measure first. &lt;a href="https://sureshpallapothu.in/blog/day-21-ai-cluster-networking" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  3. Egress: stop data leaving
&lt;/h2&gt;

&lt;p&gt;AI workloads add a new risk: an autonomous agent or a compromised model runtime may try to send internal context to an outside server. Use a &lt;strong&gt;NetworkPolicy&lt;/strong&gt; with &lt;strong&gt;default-deny egress&lt;/strong&gt;, so AI Pods can reach only the internal endpoints you list, and everything else, including the open internet, is blocked. Policies are only enforced if your network plugin supports them, and Cilium and Calico do.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; A NetworkPolicy with default-deny egress lets an AI Pod reach only approved destinations such as the vector database, plus cluster DNS, and blocks everything else, including unauthorised internet traffic. It needs a network plugin that enforces policies, such as Cilium or Calico. &lt;a href="https://sureshpallapothu.in/blog/day-21-ai-cluster-networking" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here are the two pieces of configuration. They assume the NGINX ingress controller and a namespace called &lt;code&gt;ai&lt;/code&gt;, with a vector database Pod labelled &lt;code&gt;app: qdrant&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Ingress: long timeouts and no buffering, for streaming replies&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;networking.k8s.io/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Ingress&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;model-ingress&lt;/span&gt;
  &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;nginx.ingress.kubernetes.io/proxy-read-timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;300"&lt;/span&gt;    &lt;span class="c1"&gt;# seconds&lt;/span&gt;
    &lt;span class="na"&gt;nginx.ingress.kubernetes.io/proxy-send-timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;300"&lt;/span&gt;
    &lt;span class="na"&gt;nginx.ingress.kubernetes.io/proxy-buffering&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;off"&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ingressClassName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;llm.example.com&lt;/span&gt;
      &lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/&lt;/span&gt;
            &lt;span class="na"&gt;pathType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Prefix&lt;/span&gt;
            &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;model-svc&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;number&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;80&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="c1"&gt;# Egress: AI Pods may talk only to the vector DB and cluster DNS&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;networking.k8s.io/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NetworkPolicy&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ai-default-deny-egress&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ai&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;podSelector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;                 &lt;span class="c1"&gt;# every Pod in the namespace&lt;/span&gt;
  &lt;span class="na"&gt;policyTypes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;Egress&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;egress&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;podSelector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;qdrant&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;                          &lt;span class="c1"&gt;# allow DNS, or nothing will resolve&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;namespaceSelector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;
          &lt;span class="na"&gt;podSelector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;k8s-app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;kube-dns&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;UDP&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;53&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;TCP&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;53&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Get the allow-list right and your AI infrastructure handles big payloads with low latency, while keeping strict boundaries around what can leave. That wraps up Week 3. Next week moves to enterprise workflows, starting with autonomous agents.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coming Up Next
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Day 22:&lt;/strong&gt; Autonomous agents: from passive LLMs to goal-driven execution.&lt;/p&gt;

&lt;h1&gt;
  
  
  KubernetesNetworking #eBPF #Cilium #NetworkSecurity #DevSecOps
&lt;/h1&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://sureshpallapothu.in/blog/day-21-ai-cluster-networking" rel="noopener noreferrer"&gt;https://sureshpallapothu.in/blog/day-21-ai-cluster-networking&lt;/a&gt;, where this post includes animated diagrams.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>kubernetes</category>
      <category>networking</category>
      <category>30dayaiseries</category>
    </item>
    <item>
      <title>Day 20: Persistent Storage in Kubernetes — Managing State, Weights and Vector Indices</title>
      <dc:creator>Suresh Kumar Pallapothu</dc:creator>
      <pubDate>Tue, 20 Oct 2026 18:05:45 +0000</pubDate>
      <link>https://dev.to/suresh_kumar_de3920bedd1c/day-20-persistent-storage-in-kubernetes-managing-state-weights-and-vector-indices-3cef</link>
      <guid>https://dev.to/suresh_kumar_de3920bedd1c/day-20-persistent-storage-in-kubernetes-managing-state-weights-and-vector-indices-3cef</guid>
      <description>&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/7enX8Yxe8To" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Containers are &lt;strong&gt;ephemeral&lt;/strong&gt;. When a Pod crashes or Kubernetes moves it, everything on its local disk is gone. For a web server that is fine. For AI it is costly: vector indices take time to rebuild, and model weights are many gigabytes that you do not want to download again every restart.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;In plain terms:&lt;/strong&gt; A container's disk is a whiteboard that gets wiped whenever the room is reassigned. A persistent volume is a filing cabinet in the corridor that every new room can open.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; A container's local filesystem is discarded when its Pod crashes or is rescheduled. Vector indices and multi-gigabyte model weights must outlive the Pod, so they need persistent volumes. &lt;a href="https://sureshpallapothu.in/blog/day-20-kubernetes-storage" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  1. The storage abstraction
&lt;/h2&gt;

&lt;p&gt;Kubernetes separates the storage from the application with two resources.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;PersistentVolume (PV):&lt;/strong&gt; a piece of real storage in the cluster, such as local NVMe disks, an NFS share or a SAN. It records the size, access modes and backing hardware.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PersistentVolumeClaim (PVC):&lt;/strong&gt; the application engineer's request for storage. The Pod references the claim, and Kubernetes binds it to a matching volume. Many clusters also have a &lt;strong&gt;StorageClass&lt;/strong&gt; that creates a volume on demand when a claim arrives.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; A PersistentVolume is a piece of real storage in the cluster. A PersistentVolumeClaim is the application's request for storage. The Pod references the claim, which binds to a matching volume, or a StorageClass can create one on demand. &lt;a href="https://sureshpallapothu.in/blog/day-20-kubernetes-storage" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  2. Access modes for AI workloads
&lt;/h2&gt;

&lt;p&gt;The access mode decides how Pods may use the volume.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ReadWriteOnce (RWO):&lt;/strong&gt; mounted read-write by a single node. This is the usual choice for a stateful vector database such as Milvus or Qdrant, where uncoordinated writers could corrupt the index.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ReadOnlyMany (ROX):&lt;/strong&gt; mounted read-only by many nodes at once. This suits model weights: keep one copy of, say, 15 GB of quantized weights on a shared, fast network mount, and let many stateless inference Pods attach to it instead of each holding its own copy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Check that your storage backend actually supports the mode you ask for. Shared read-only mounts typically come from NFS or a similar network filesystem, while plain block disks are usually single-node.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; ReadWriteOnce mounts a volume read-write on a single node, which suits a vector database that must not have uncoordinated writers. ReadOnlyMany mounts read-only on many nodes at once, so one copy of the model weights can serve many inference Pods. The storage backend has to support the mode you request. &lt;a href="https://sureshpallapothu.in/blog/day-20-kubernetes-storage" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  3. StatefulSets for databases
&lt;/h2&gt;

&lt;p&gt;For a vector database, a standard Deployment is usually the wrong tool. A &lt;strong&gt;StatefulSet&lt;/strong&gt; gives each Pod a stable name (&lt;code&gt;db-0&lt;/code&gt;, &lt;code&gt;db-1&lt;/code&gt;), starts them in order, and binds each Pod to its own persistent volume that follows it across restarts. That keeps replication between the database Pods consistent through cluster restarts.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; A StatefulSet gives each Pod a stable name such as db-0 and db-1, starts them in order, and binds each Pod to its own persistent volume that follows it across restarts. That is why stateful databases use StatefulSets instead of Deployments. &lt;a href="https://sureshpallapothu.in/blog/day-20-kubernetes-storage" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here is an example. It assumes your cluster has a default StorageClass, and the Qdrant image and sizes are illustrative.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# 1. Shared, read-only model weights (needs a backend that supports ReadOnlyMany)&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PersistentVolumeClaim&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;model-weights&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;accessModes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;ReadOnlyMany&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;storage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;20Gi&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="c1"&gt;# 2. A vector database with its own volume per Pod&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;StatefulSet&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;qdrant&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;serviceName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;qdrant&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;qdrant&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;qdrant&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;qdrant&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;qdrant/qdrant:v1.9.0&lt;/span&gt;        &lt;span class="c1"&gt;# pin a version you have tested&lt;/span&gt;
          &lt;span class="na"&gt;volumeMounts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;/qdrant/storage&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;volumeClaimTemplates&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;                       &lt;span class="c1"&gt;# one PVC is created per Pod: data-qdrant-0, ...&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;data&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;accessModes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;ReadWriteOnce&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
        &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;storage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;50Gi&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Done well, this eliminates repeated weight downloads, preserves your database indices and protects the system's memory across cluster updates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coming Up Next
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Day 21:&lt;/strong&gt; High-performance networking: ingress and egress for AI clusters.&lt;/p&gt;

&lt;h1&gt;
  
  
  KubernetesStorage #DataEngineering #CloudNative #VectorDatabase #StatefulSet
&lt;/h1&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://sureshpallapothu.in/blog/day-20-kubernetes-storage" rel="noopener noreferrer"&gt;https://sureshpallapothu.in/blog/day-20-kubernetes-storage&lt;/a&gt;, where this post includes animated diagrams.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>kubernetes</category>
      <category>30dayaiseries</category>
    </item>
    <item>
      <title>Day 19: Kubernetes for AI 101 — Pods, Deployments and Services for Model Serving</title>
      <dc:creator>Suresh Kumar Pallapothu</dc:creator>
      <pubDate>Mon, 19 Oct 2026 17:40:09 +0000</pubDate>
      <link>https://dev.to/suresh_kumar_de3920bedd1c/day-19-kubernetes-for-ai-101-pods-deployments-and-services-for-model-serving-8h4</link>
      <guid>https://dev.to/suresh_kumar_de3920bedd1c/day-19-kubernetes-for-ai-101-pods-deployments-and-services-for-model-serving-8h4</guid>
      <description>&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/WUOAJmTuv5U" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Docker Compose coordinates services on &lt;strong&gt;one machine&lt;/strong&gt;. Enterprise AI needs more: traffic surges, nodes fail, and upgrades must happen with zero downtime. &lt;strong&gt;Kubernetes&lt;/strong&gt; (often written K8s) is the industry-standard control plane for that. Learning it starts with three building blocks.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;In plain terms:&lt;/strong&gt; Compose is a manager running one kitchen. Kubernetes is a head office running a chain of kitchens: it opens a new branch when the queue grows, replaces a branch that burns down, and rolls out a new menu one branch at a time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; Docker Compose runs services on one host. Kubernetes is a control plane over many nodes: it reschedules workloads after failures, scales on demand and performs rolling updates without downtime. &lt;a href="https://sureshpallapothu.in/blog/day-19-kubernetes-for-ai" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  1. Pods: the atomic unit
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;Pod&lt;/strong&gt; is the smallest thing Kubernetes schedules. It wraps one or more tightly coupled containers that share a network address and can share storage volumes. An inference Pod usually pairs the model server with a &lt;strong&gt;sidecar&lt;/strong&gt; container that handles logging, telemetry or request sanitising.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; A Pod is the smallest unit Kubernetes schedules. An inference Pod usually pairs the model server with a sidecar container for logging or telemetry. The containers share one network address and can share volumes. &lt;a href="https://sureshpallapothu.in/blog/day-19-kubernetes-for-ai" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  2. Deployments: updates and self-healing
&lt;/h2&gt;

&lt;p&gt;You rarely create Pods by hand. A &lt;strong&gt;Deployment&lt;/strong&gt; declares what you want, for example "three replicas of this model server image", and a controller keeps reality matching that.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Rolling updates.&lt;/strong&gt; When you change the model version or a prompt template, the Deployment starts new Pods, waits for their readiness checks to pass, then retires the old ones, so in-flight requests are not dropped.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Self-healing.&lt;/strong&gt; If a heavy context pushes a container past its memory limit and it is killed (&lt;code&gt;OOMKilled&lt;/code&gt;), Kubernetes restarts or replaces it to bring the count back to what you declared.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; A Deployment keeps the declared number of Pod replicas running. For an update it starts new Pods, waits for their readiness checks, then stops old ones so in-flight requests are not dropped. If a Pod is killed for running out of memory, Kubernetes restarts or replaces it to restore the desired count. &lt;a href="https://sureshpallapothu.in/blog/day-19-kubernetes-for-ai" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  3. Services: one stable address
&lt;/h2&gt;

&lt;p&gt;Pods come and go, and each new Pod gets a new IP. A &lt;strong&gt;Service&lt;/strong&gt; sits in front of them with a stable virtual IP and DNS name.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ClusterIP&lt;/strong&gt; exposes the model only inside the cluster, so internal applications can reach it while it stays hidden from the public internet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Readiness-aware routing.&lt;/strong&gt; The Service only sends traffic to Pods that pass their readiness probe. If a Pod is busy and its readiness check starts failing, new requests go to its idle siblings. Tune the probe to your model server, since a probe that is too strict can take every Pod out of rotation at once.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; A Service gives a changing set of Pods one stable virtual IP and DNS name. With a ClusterIP the model is reachable only inside the cluster. The Service sends traffic only to Pods that pass their readiness probe, so a Pod that reports itself busy is skipped. &lt;a href="https://sureshpallapothu.in/blog/day-19-kubernetes-for-ai" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here is a compact example. It assumes an image called &lt;code&gt;model-api&lt;/code&gt; that serves a &lt;code&gt;/health&lt;/code&gt; endpoint on port 8000. Adjust the names, resources and probe to your own server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;model-api&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;model-api&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;rollingUpdate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;maxUnavailable&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;0&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;maxSurge&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;1&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;   &lt;span class="c1"&gt;# never drop capacity during an update&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;model-api&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;model-api&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;registry.example.com/model-api:1.4.0&lt;/span&gt;
          &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[{&lt;/span&gt; &lt;span class="nv"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;8000&lt;/span&gt; &lt;span class="pi"&gt;}]&lt;/span&gt;
          &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;8Gi&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
            &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;   &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;8Gi&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;                   &lt;span class="c1"&gt;# exceeding this gets the container OOMKilled&lt;/span&gt;
          &lt;span class="na"&gt;readinessProbe&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;httpGet&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;/health&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;8000&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
            &lt;span class="na"&gt;periodSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;model-svc&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ClusterIP&lt;/span&gt;            &lt;span class="c1"&gt;# reachable only inside the cluster&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;model-api&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[{&lt;/span&gt; &lt;span class="nv"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;80&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;targetPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;8000&lt;/span&gt; &lt;span class="pi"&gt;}]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Apply it with &lt;code&gt;kubectl apply -f model.yaml&lt;/code&gt;, and inside the cluster your application calls &lt;code&gt;http://model-svc&lt;/code&gt;. These three abstractions are the baseline for scaling AI microservices with automated resilience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coming Up Next
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Day 20:&lt;/strong&gt; Persistent storage in Kubernetes: managing state, weights and vector indices.&lt;/p&gt;

&lt;h1&gt;
  
  
  Kubernetes #K8s #MLOps #DevOps #CloudNative
&lt;/h1&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://sureshpallapothu.in/blog/day-19-kubernetes-for-ai" rel="noopener noreferrer"&gt;https://sureshpallapothu.in/blog/day-19-kubernetes-for-ai&lt;/a&gt;, where this post includes animated diagrams.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>kubernetes</category>
      <category>30dayaiseries</category>
    </item>
    <item>
      <title>Day 18: Docker Compose for Multi-Container AI Stacks</title>
      <dc:creator>Suresh Kumar Pallapothu</dc:creator>
      <pubDate>Sun, 18 Oct 2026 16:47:19 +0000</pubDate>
      <link>https://dev.to/suresh_kumar_de3920bedd1c/day-18-docker-compose-for-multi-container-ai-stacks-2e6j</link>
      <guid>https://dev.to/suresh_kumar_de3920bedd1c/day-18-docker-compose-for-multi-container-ai-stacks-2e6j</guid>
      <description>&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/k8flk6LqdFs" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;A serious AI application is a small distributed system. A RAG pipeline cannot be one monolithic process, because inference, data indexing and the user interface have different needs and change at different speeds. &lt;strong&gt;Docker Compose&lt;/strong&gt; lets you describe all of them in one file and start them together on a single host.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;In plain terms:&lt;/strong&gt; Compose is a seating plan for a dinner party. You say who sits where, who can talk to whom, and who must arrive before the others, and Docker sets the table.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The core topology: a 3-tier RAG stack
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; A self-contained RAG deployment has three isolated services: an application layer that orchestrates, a vector database that holds the embeddings, and a model server such as vLLM or Ollama that generates the answer. Docker Compose starts and connects them on a single host. &lt;a href="https://sureshpallapothu.in/blog/day-18-docker-compose" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Model server (vLLM or Ollama).&lt;/strong&gt; A dedicated compute container that manages GPU or system memory and exposes inference over an internal port.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vector database (Qdrant, Milvus or pgvector).&lt;/strong&gt; An independent, stateful service that holds the embeddings and answers nearest-neighbour queries.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Application layer (FastAPI, plus Streamlit or Next.js).&lt;/strong&gt; The orchestrator: it takes the user's question, fetches matching context from the vector database, builds the prompt and asks the model server for the answer.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Three configuration practices
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. A private bridge network.&lt;/strong&gt; Put every container on one user-defined network. Docker then resolves service names automatically, so the app can call &lt;code&gt;http://ollama:11434&lt;/code&gt; or &lt;code&gt;http://qdrant:6333&lt;/code&gt;. Publish only the app's port to the host, and the database and model server stay unreachable from outside.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; Containers on a user-defined bridge network find each other by service name through Docker's built-in DNS, for example &lt;a href="http://ollama:11434" rel="noopener noreferrer"&gt;http://ollama:11434&lt;/a&gt; or &lt;a href="http://qdrant:6333" rel="noopener noreferrer"&gt;http://qdrant:6333&lt;/a&gt;. Only the app's port is published to the host, so the database and model server are not reachable from outside. &lt;a href="https://sureshpallapothu.in/blog/day-18-docker-compose" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;2. Volumes for state.&lt;/strong&gt; Mount separate volumes for the vector database storage and the model cache (&lt;code&gt;/root/.ollama&lt;/code&gt;, or &lt;code&gt;/root/.cache/huggingface&lt;/code&gt; for Hugging Face models). Containers can then be updated, restarted or rebuilt without losing the index or downloading the weights again.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; Mounting separate volumes for the vector database storage and the model cache lets you rebuild, restart or upgrade containers without losing the index or downloading the model weights again. &lt;a href="https://sureshpallapothu.in/blog/day-18-docker-compose" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;3. Healthchecks and start order.&lt;/strong&gt; Add a &lt;code&gt;healthcheck&lt;/code&gt; to each service and use &lt;code&gt;depends_on&lt;/code&gt; with &lt;code&gt;condition: service_healthy&lt;/code&gt;, so the app never queries a database or model server that is not listening yet.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; With a healthcheck on each service and depends_on with condition service_healthy, the application container waits until the database and model server are actually listening, so it never queries a service that is not ready. &lt;a href="https://sureshpallapothu.in/blog/day-18-docker-compose" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here is a compact example. It assumes your app lives in &lt;code&gt;./app&lt;/code&gt; with a Dockerfile like yesterday's, and that the healthcheck commands suit the image versions you use. Check each image's documentation, since minimal images do not always include the tools a check needs. In production, pin a specific image tag instead of &lt;code&gt;latest&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./app&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8000:8000"&lt;/span&gt;            &lt;span class="c1"&gt;# the only port published to the host&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;OLLAMA_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://ollama:11434&lt;/span&gt;
      &lt;span class="na"&gt;QDRANT_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://qdrant:6333&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;ollama&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service_healthy&lt;/span&gt;
      &lt;span class="na"&gt;qdrant&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service_healthy&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;ai-network&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

  &lt;span class="na"&gt;qdrant&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;qdrant/qdrant:latest&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;qdrant_data:/qdrant/storage&lt;/span&gt;
    &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CMD-SHELL"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bash&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-c&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;':&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;/dev/tcp/127.0.0.1/6333'&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;||&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;exit&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;1"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;
      &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;ai-network&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

  &lt;span class="na"&gt;ollama&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ollama/ollama:latest&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ollama_models:/root/.ollama&lt;/span&gt;
    &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CMD"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ollama"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;list"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;
      &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;ai-network&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="c1"&gt;# For an NVIDIA GPU, add a device reservation:&lt;/span&gt;
    &lt;span class="c1"&gt;# deploy:&lt;/span&gt;
    &lt;span class="c1"&gt;#   resources:&lt;/span&gt;
    &lt;span class="c1"&gt;#     reservations:&lt;/span&gt;
    &lt;span class="c1"&gt;#       devices:&lt;/span&gt;
    &lt;span class="c1"&gt;#         - driver: nvidia&lt;/span&gt;
    &lt;span class="c1"&gt;#           count: all&lt;/span&gt;
    &lt;span class="c1"&gt;#           capabilities: [gpu]&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;qdrant_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ollama_models&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ai-network&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start it with &lt;code&gt;docker compose up -d&lt;/code&gt;, and stop it with &lt;code&gt;docker compose down&lt;/code&gt; (add &lt;code&gt;-v&lt;/code&gt; only if you want the volumes deleted too). Decoupling the stack this way means any one component can be upgraded, scaled or replaced without destabilizing the rest.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coming Up Next
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Day 19:&lt;/strong&gt; Kubernetes for AI 101: pods, deployments and services for model serving.&lt;/p&gt;

&lt;h1&gt;
  
  
  DockerCompose #Microservices #RAG #SystemDesign #DevOps
&lt;/h1&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://sureshpallapothu.in/blog/day-18-docker-compose" rel="noopener noreferrer"&gt;https://sureshpallapothu.in/blog/day-18-docker-compose&lt;/a&gt;, where this post includes animated diagrams.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>dockercompose</category>
      <category>30dayaiseries</category>
    </item>
    <item>
      <title>Day 17: Containerizing AI Workloads — Production Dockerfiles for Python APIs</title>
      <dc:creator>Suresh Kumar Pallapothu</dc:creator>
      <pubDate>Sat, 17 Oct 2026 16:37:15 +0000</pubDate>
      <link>https://dev.to/suresh_kumar_de3920bedd1c/day-17-containerizing-ai-workloads-production-dockerfiles-for-python-apis-3a3n</link>
      <guid>https://dev.to/suresh_kumar_de3920bedd1c/day-17-containerizing-ai-workloads-production-dockerfiles-for-python-apis-3a3n</guid>
      <description>&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/XbRygXS37lU" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Moving a model from a notebook to a service means putting it in a container. But AI images are not like a Node.js or Go service. Deep learning frameworks bring heavy &lt;strong&gt;CUDA shared libraries&lt;/strong&gt;, &lt;strong&gt;C++ build tools&lt;/strong&gt; and multi-gigabyte wheel files, and a careless Dockerfile can balloon past 10 GB.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;In plain terms:&lt;/strong&gt; Imagine shipping a finished chair. A naive Dockerfile mails the chair together with the whole workshop: saws, sawdust and offcuts. A multi-stage build ships only the chair.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; A naive Dockerfile that installs everything in one image ends up carrying compilers and build caches along with the large CUDA and PyTorch libraries. A multi-stage build ships only the runtime pieces. The bar widths are illustrative. &lt;a href="https://sureshpallapothu.in/blog/day-17-dockerfiles" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The anatomy of a bloated image
&lt;/h2&gt;

&lt;p&gt;A naive Dockerfile that runs &lt;code&gt;RUN pip install torch transformers fastapi&lt;/code&gt; in a default base image causes three operational problems.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; A naive AI image duplicates build caches inside its layers, leaves compilers in production which widens the attack surface, and is so large that new pods start slowly when traffic spikes. &lt;a href="https://sureshpallapothu.in/blog/day-17-dockerfiles" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Layer duplication.&lt;/strong&gt; Build caches and downloaded wheels stay trapped in read-only image layers, which slows registry pushes and pulls.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security exposure.&lt;/strong&gt; Leaving &lt;code&gt;gcc&lt;/code&gt;, &lt;code&gt;g++&lt;/code&gt; and &lt;code&gt;make&lt;/code&gt; in the production image widens the attack surface.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slow deployments.&lt;/strong&gt; Huge images saturate the network during autoscaling, so new pods start late exactly when traffic spikes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Three production patterns
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Multi-stage builds.&lt;/strong&gt; Split the Dockerfile in two. The builder stage has the compilers; it downloads and builds dependencies into a virtual environment at &lt;code&gt;/opt/venv&lt;/code&gt;. The runtime stage starts from a clean, minimal base such as &lt;code&gt;python:3.11-slim&lt;/code&gt; and copies in only that environment.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; In stage one, the builder stage has compilers and installs every dependency into a virtual environment. In stage two, a clean slim runtime image receives only that virtual environment, and everything else from stage one is discarded. &lt;a href="https://sureshpallapothu.in/blog/day-17-dockerfiles" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Wheel caching.&lt;/strong&gt; A BuildKit cache mount (&lt;code&gt;--mount=type=cache,target=/root/.cache/pip&lt;/code&gt;) keeps pip's download cache outside the image, so repeated CI builds do not download the heavy packages again.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Non-root security.&lt;/strong&gt; Never run the process as root. Create a dedicated user and start the ASGI server as that user.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; A production-ready AI image uses a multi-stage build, caches pip downloads with a BuildKit cache mount, runs the server as a non-root user and pins its dependency versions, so builds are reproducible and auditable. &lt;a href="https://sureshpallapothu.in/blog/day-17-dockerfiles" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here is a complete example for a FastAPI model server. It assumes a &lt;code&gt;requirements.txt&lt;/code&gt; with pinned versions and an &lt;code&gt;app/&lt;/code&gt; folder with a &lt;code&gt;main.py&lt;/code&gt;, and it is a template to adapt rather than a tuned production file. A GPU image would use a CUDA-enabled base and PyTorch build instead.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# syntax=docker/dockerfile:1&lt;/span&gt;

&lt;span class="c"&gt;# ---- Stage 1: builder (has compilers, gets thrown away) ----&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;python:3.11-slim&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;python &lt;span class="nt"&gt;-m&lt;/span&gt; venv /opt/venv
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PATH="/opt/venv/bin:$PATH"&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; requirements.txt .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nt"&gt;--mount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cache,target&lt;span class="o"&gt;=&lt;/span&gt;/root/.cache/pip &lt;span class="se"&gt;\
&lt;/span&gt;    pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt

&lt;span class="c"&gt;# ---- Stage 2: runtime (clean and minimal) ----&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python:3.11-slim&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /opt/venv /opt/venv&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PATH="/opt/venv/bin:$PATH"&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;useradd &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; 1000 appuser
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --chown=appuser:appuser app/ ./app/&lt;/span&gt;
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; appuser&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 8000&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Build it with &lt;code&gt;DOCKER_BUILDKIT=1 docker build -t model-api .&lt;/code&gt;. Writing lean, deterministic Dockerfiles turns chaotic research dependencies into reproducible, auditable artifacts that a cluster scheduler can start quickly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coming Up Next
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Day 18:&lt;/strong&gt; Docker Compose for multi-container AI stacks.&lt;/p&gt;

&lt;h1&gt;
  
  
  Docker #Containerization #DevOps #AIEngineering #FastAPI
&lt;/h1&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://sureshpallapothu.in/blog/day-17-dockerfiles" rel="noopener noreferrer"&gt;https://sureshpallapothu.in/blog/day-17-dockerfiles&lt;/a&gt;, where this post includes animated diagrams.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>docker</category>
      <category>30dayaiseries</category>
    </item>
    <item>
      <title>Day 16: Local Model Execution — Running Open-Weight LLMs Securely with Ollama and vLLM</title>
      <dc:creator>Suresh Kumar Pallapothu</dc:creator>
      <pubDate>Fri, 16 Oct 2026 14:10:02 +0000</pubDate>
      <link>https://dev.to/suresh_kumar_de3920bedd1c/day-16-local-model-execution-running-open-weight-llms-securely-with-ollama-and-vllm-212i</link>
      <guid>https://dev.to/suresh_kumar_de3920bedd1c/day-16-local-model-execution-running-open-weight-llms-securely-with-ollama-and-vllm-212i</guid>
      <description>&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/YfoDhDu4Za0" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Relying only on commercial cloud APIs carries real exposure: &lt;strong&gt;vendor lock-in&lt;/strong&gt;, recurring cost for every token, &lt;strong&gt;rate limits&lt;/strong&gt;, and the need to send sensitive intellectual property across public networks. Running an open-weight model such as Llama 3, Mistral or Gemma 2 on your own infrastructure addresses each of those. The price is that you now operate the hardware, so choosing the right runtime matters.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;In plain terms:&lt;/strong&gt; Ordering from a restaurant is easy, but you pay per dish, you queue at busy hours, and the restaurant sees what you ate. Cooking at home means buying the stove, and in return the menu, the speed and the privacy are yours.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; Relying only on commercial cloud APIs brings vendor lock-in, recurring costs, rate limits and the transmission of sensitive data over public networks. Running open-weight models on your own infrastructure addresses each of these, at the price of operating the hardware yourself. &lt;a href="https://sureshpallapothu.in/blog/day-16-local-models" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  1. Prototyping and edge: Ollama and llama.cpp
&lt;/h2&gt;

&lt;p&gt;On a workstation, a laptop or an edge box with limited memory (a 16 GB machine is a typical target), a plain Python script adds overhead you do not want. &lt;strong&gt;Ollama&lt;/strong&gt; is built on &lt;strong&gt;llama.cpp&lt;/strong&gt;, a lean C and C++ engine, so it avoids most of that Python overhead.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GGUF quantization:&lt;/strong&gt; Ollama runs models stored in the GGUF format, usually at 4-bit or 8-bit precision. That shrinks multi-gigabyte weights enough to fit in ordinary RAM or a modest GPU, with a small loss in quality for most tasks. Check the quality on your own prompts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Operational simplicity:&lt;/strong&gt; it runs a local service and handles downloading models, spreading them across GPU and CPU memory, and formatting prompts. It exposes an OpenAI-compatible REST API at &lt;code&gt;http://localhost:11434&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. High-throughput serving: vLLM
&lt;/h2&gt;

&lt;p&gt;When hundreds of users hit the same model at once, handling requests one after another creates a queue. &lt;strong&gt;vLLM&lt;/strong&gt; is a serving engine built for that case, with two key ideas.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PagedAttention.&lt;/strong&gt; Older engines reserve one contiguous block of KV-cache memory per request, sized for the longest possible reply, so much of it sits empty. The vLLM authors measured 60 to 80% waste in earlier systems. PagedAttention borrows the idea of virtual-memory paging from operating systems and splits the cache into small blocks that are handed out as a request grows.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; Older inference engines reserve one contiguous block of KV-cache memory per request, sized for the maximum length, and much of it sits empty. vLLM's PagedAttention splits the cache into small blocks allocated as a request grows, like paging in an operating system. The vLLM authors reported 60 to 80% waste in earlier systems. &lt;a href="https://sureshpallapothu.in/blog/day-16-local-models" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Continuous batching.&lt;/strong&gt; A static batch must finish completely before new requests are accepted, so slots sit idle while the longest reply completes. vLLM slips new requests into the running batch at the token level. Reported gains are commonly 2x to 4x higher throughput than basic pipelines, but the real figure depends on your model, hardware and traffic.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; With static batching, a whole batch must finish before new requests are accepted, so slots sit idle while the longest reply finishes. Continuous batching slips new requests into free slots at the token level, which commonly raises throughput by 2x to 4x depending on the model, hardware and traffic. &lt;a href="https://sureshpallapothu.in/blog/day-16-local-models" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Which one should you pick?
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; Ollama, built on llama.cpp, suits prototyping and edge machines with limited memory and runs quantized GGUF models with a single command. vLLM suits GPU servers handling many users at once. Both expose an OpenAI-style API. &lt;a href="https://sureshpallapothu.in/blog/day-16-local-models" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here is how little your application code changes. Both engines speak an OpenAI-style API, so you can point the same client at either one. This assumes you have installed Ollama and pulled a model, or started a vLLM server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Ollama: pull and chat with a quantized 8B model in one command&lt;/span&gt;
ollama run llama3.1:8b

&lt;span class="c"&gt;# vLLM: serve a model with an OpenAI-compatible API on port 8000 (needs an NVIDIA GPU)&lt;/span&gt;
vllm serve Qwen/Qwen2.5-7B-Instruct
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAI&lt;/span&gt;

&lt;span class="c1"&gt;# Same client, different base_url. Ollama listens on 11434, vLLM on 8000.
&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:11434/v1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;not-needed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;reply&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;llama3.1:8b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Summarise RAG in one sentence.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Self-hosting turns an LLM from an outside cost center into a private utility inside your own network boundary. The next lessons package it properly, starting with containers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coming Up Next
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Day 17:&lt;/strong&gt; Containerizing AI workloads: writing production Dockerfiles for Python APIs.&lt;/p&gt;

&lt;h1&gt;
  
  
  LocalAI #vLLM #Ollama #OpenSourceAI #DataSovereignty
&lt;/h1&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://sureshpallapothu.in/blog/day-16-local-models" rel="noopener noreferrer"&gt;https://sureshpallapothu.in/blog/day-16-local-models&lt;/a&gt;, where this post includes animated diagrams.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>ollama</category>
      <category>vllm</category>
      <category>30dayaiseries</category>
    </item>
    <item>
      <title>Day 15: Introduction to AI Infrastructure — Hardware, Runtimes and Compute</title>
      <dc:creator>Suresh Kumar Pallapothu</dc:creator>
      <pubDate>Thu, 15 Oct 2026 14:12:30 +0000</pubDate>
      <link>https://dev.to/suresh_kumar_de3920bedd1c/day-15-introduction-to-ai-infrastructure-hardware-runtimes-and-compute-1le2</link>
      <guid>https://dev.to/suresh_kumar_de3920bedd1c/day-15-introduction-to-ai-infrastructure-hardware-runtimes-and-compute-1le2</guid>
      <description>&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/Wd2kZIuwlF0" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Until now we called AI over an API. Moving to &lt;strong&gt;self-hosted models&lt;/strong&gt; is how you guarantee your data never leaves your network, and it also means you now own the hardware, the runtime and the operating system. This week starts with the three building blocks.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;In plain terms:&lt;/strong&gt; Hosting your own model is like running your own kitchen instead of ordering delivery. You get control and privacy, but you now need the right stove, the right pans and a kitchen that stays up.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  1. The hardware constraint: VRAM
&lt;/h2&gt;

&lt;p&gt;A model has to fit in the memory of the GPU, called &lt;strong&gt;VRAM&lt;/strong&gt;, before it can run. The parameter count sets the requirement: memory is roughly the number of parameters times the bytes used per parameter. Shrinking the numbers (quantization) is how you fit bigger models on smaller cards.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; The weights of an 8-billion-parameter model take about 16 GB at 16-bit precision, 8 GB at 8-bit and 4 GB at 4-bit. The KV cache and runtime add roughly 1 to 3 GB, so a 4-bit 8B model needs roughly 6 to 8 GB of VRAM in practice. &lt;a href="https://sureshpallapothu.in/blog/day-15-ai-infrastructure" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The figures are for the weights alone. The KV cache, which holds the conversation so far, and the runtime itself take extra space, so plan for a little headroom. A quantized 8-billion-parameter model needs roughly 6 to 8 GB of VRAM in practice.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Inference runtimes
&lt;/h2&gt;

&lt;p&gt;You do not run a raw Python script in production. You use a dedicated, optimized runtime. &lt;strong&gt;llama.cpp&lt;/strong&gt; is a lean C++ runtime that runs quantized models well on laptops and small servers. &lt;strong&gt;vLLM&lt;/strong&gt; is built for GPU servers and serves many requests at once with smart batching and memory management. Same model, far more speed from the same hardware.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; You do not run raw Python scripts in production. Dedicated runtimes such as llama.cpp and vLLM squeeze much more speed from limited hardware using optimized code, batching and efficient memory use. &lt;a href="https://sureshpallapothu.in/blog/day-15-ai-infrastructure" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  3. The hosting OS
&lt;/h2&gt;

&lt;p&gt;Linux is the standard host for production AI. A &lt;strong&gt;headless Ubuntu Server&lt;/strong&gt;, which means no desktop environment at all, is a common choice. It leaves every bit of memory and CPU for your workloads, and it passes GPU resources cleanly into Docker containers and Kubernetes pods.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; Headless Ubuntu Server has no desktop overhead, passes GPU drivers cleanly to Docker and Kubernetes, and offers long-term support releases that are easy to automate, which is why Linux is the standard host for production AI. &lt;a href="https://sureshpallapothu.in/blog/day-15-ai-infrastructure" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Put the pieces together and you get a stack that is built from the bottom up.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; A self-hosted AI server is built bottom up: GPU hardware, a headless Ubuntu Server with the GPU driver, the NVIDIA container toolkit that exposes the GPU to containers, Docker or Kubernetes pods, and finally the model running in an optimized runtime. &lt;a href="https://sureshpallapothu.in/blog/day-15-ai-infrastructure" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once the driver and the container toolkit are in place, you can check the GPU from inside a container. The commands below are the standard checks, and they need an NVIDIA GPU on the host.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# On the host: is the GPU visible to the driver?&lt;/span&gt;
nvidia-smi

&lt;span class="c"&gt;# Inside a container: is the GPU passed through?&lt;/span&gt;
docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;--gpus&lt;/span&gt; all ubuntu nvidia-smi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the second command prints the same GPU table as the first, your containers can use the GPU. The next lessons build on that: local models with Ollama and vLLM, then Dockerfiles and Kubernetes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coming Up Next
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Day 16:&lt;/strong&gt; Local model execution: running open-weight LLMs securely with Ollama and vLLM.&lt;/p&gt;

&lt;h1&gt;
  
  
  AIInfrastructure #Hardware #Ubuntu #Docker #SelfHostedAI
&lt;/h1&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://sureshpallapothu.in/blog/day-15-ai-infrastructure" rel="noopener noreferrer"&gt;https://sureshpallapothu.in/blog/day-15-ai-infrastructure&lt;/a&gt;, where this post includes animated diagrams.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>infrastructure</category>
      <category>30dayaiseries</category>
    </item>
    <item>
      <title>Day 14: RAG Evaluation and Hallucination Guardrails</title>
      <dc:creator>Suresh Kumar Pallapothu</dc:creator>
      <pubDate>Wed, 14 Oct 2026 14:07:03 +0000</pubDate>
      <link>https://dev.to/suresh_kumar_de3920bedd1c/day-14-rag-evaluation-and-hallucination-guardrails-321c</link>
      <guid>https://dev.to/suresh_kumar_de3920bedd1c/day-14-rag-evaluation-and-hallucination-guardrails-321c</guid>
      <description>&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/hnnjP4RF5B0" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;A RAG pipeline is powerful, but it adds new places to fail. The model is only as good as what you hand it, and when it is handed the wrong thing it does not say "I'm confused". It answers confidently.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;In plain terms:&lt;/strong&gt; An open-book exam only works if the student opens the right book. Hand them the wrong chapter and they will write a polished, convincing, wrong answer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; If the retrieval step pulls the wrong document, the LLM will still write a fluent answer from it. The result is confident and wrong, so a hallucination can start in retrieval and not in the model. &lt;a href="https://sureshpallapothu.in/blog/day-14-rag-evaluation" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  1. Measure the two halves
&lt;/h2&gt;

&lt;p&gt;You evaluate a RAG system by scoring its two halves separately, because they fail in different ways.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Context relevance&lt;/strong&gt; asks whether the retrieved documents actually contain the answer to the user's question. This checks the retrieval step.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Faithfulness&lt;/strong&gt; asks whether the generated response is strictly derived from the retrieved context, or whether the model brought in outside information. This checks the generation step.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; Context relevance checks whether the retrieved documents actually contain the answer. Faithfulness checks whether the generated answer is strictly derived from the retrieved context instead of invented. &lt;a href="https://sureshpallapothu.in/blog/day-14-rag-evaluation" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  2. Spot an unfaithful answer
&lt;/h2&gt;

&lt;p&gt;Faithfulness is checked claim by claim. If the context says the REIT paid a dividend in Q3 and the answer adds that its yield is 7.4%, that second claim is not in the context. It was invented, and that is a hallucination, even though it sounds perfectly plausible.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; A faithfulness check compares each claim in the answer with the retrieved context. A claim the context supports passes. A claim that is not in the context, such as an invented yield percentage, is flagged as a hallucination. The example text is illustrative. &lt;a href="https://sureshpallapothu.in/blog/day-14-rag-evaluation" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  3. Guardrails: an LLM as judge
&lt;/h2&gt;

&lt;p&gt;You can automate that check. A secondary, smaller LLM acts as a &lt;strong&gt;judge&lt;/strong&gt; in the pipeline. Before the final answer reaches the user, the judge compares it with the retrieved text and decides whether to show it or block it. When a check fails you can retry retrieval, or return "I don't know". Judges can be wrong too, so sample their decisions and review them.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; A secondary, smaller LLM acts as a judge. Before the user sees the answer, it compares the answer to the retrieved text and either lets it through or blocks it. Judges can be wrong too, so teams sample and review their decisions. &lt;a href="https://sureshpallapothu.in/blog/day-14-rag-evaluation" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here is the idea as a short Python sketch. The &lt;code&gt;llm_judge&lt;/code&gt; function stands in for a call to a small model that you prompt to compare an answer against a context.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;guarded_answer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;chunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;embed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;top_k&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;answer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Judge: is every claim in the answer supported by the retrieved text?
&lt;/span&gt;    &lt;span class="n"&gt;verdict&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;llm_judge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Reply SUPPORTED or UNSUPPORTED. Is every claim in the answer &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;backed by the context?&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Context: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s"&gt;Answer: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;verdict&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SUPPORTED&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;I could not find a reliable answer in the documents.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep a small test set of real questions with known answers, and run it every time you change chunking, the embedding model or the prompt. That is how you catch a regression before your users do.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coming Up Next
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Day 15:&lt;/strong&gt; Introduction to AI infrastructure: hardware, runtimes and compute.&lt;/p&gt;

&lt;h1&gt;
  
  
  AIQuality #MLOps #Hallucination #TechLeadership #DataGovernance
&lt;/h1&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://sureshpallapothu.in/blog/day-14-rag-evaluation" rel="noopener noreferrer"&gt;https://sureshpallapothu.in/blog/day-14-rag-evaluation&lt;/a&gt;, where this post includes animated diagrams.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>rag</category>
      <category>30dayaiseries</category>
    </item>
    <item>
      <title>Day 13: Retrieval-Augmented Generation (RAG) — Chatting with Proprietary Documents</title>
      <dc:creator>Suresh Kumar Pallapothu</dc:creator>
      <pubDate>Tue, 13 Oct 2026 13:56:20 +0000</pubDate>
      <link>https://dev.to/suresh_kumar_de3920bedd1c/day-13-retrieval-augmented-generation-rag-chatting-with-proprietary-documents-48eh</link>
      <guid>https://dev.to/suresh_kumar_de3920bedd1c/day-13-retrieval-augmented-generation-rag-chatting-with-proprietary-documents-48eh</guid>
      <description>&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/PFyfzfXsg4g" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;A language model only knows what it was trained on, and that stopped at a fixed date. It has never read your internal wiki, your tickets or this morning's incident report. &lt;strong&gt;Retrieval-Augmented Generation (RAG)&lt;/strong&gt; is the architecture that lets it answer questions about your own, up-to-date documents, &lt;strong&gt;without retraining the model&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;In plain terms:&lt;/strong&gt; An open-book exam. Instead of making the student memorise the whole library, you let them look up the right pages first, then write the answer from those pages.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; A language model is frozen at its training date and has never seen your private documents. RAG looks them up when a question arrives and gives them to the model, so no retraining is needed and updated documents change the answers. &lt;a href="https://sureshpallapothu.in/blog/day-13-rag" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;RAG chains together the two previous lessons: embeddings give every paragraph a position by meaning, and a vector database finds the closest ones fast. Then the LLM does what it is best at, which is writing a clear answer.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; RAG has three steps: ingest your documents by chunking, embedding and storing them, retrieve the most relevant paragraphs for each question, then generate an answer from that retrieved context. &lt;a href="https://sureshpallapothu.in/blog/day-13-rag" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Step 1: Ingestion
&lt;/h2&gt;

&lt;p&gt;Ahead of time, your private documents are split into &lt;strong&gt;chunks&lt;/strong&gt; (paragraph-sized pieces), each chunk is converted into an embedding, and the vectors are stored in a vector database. Chunk size is a real design decision. Chunks that are too big blur the meaning, and chunks that are too small lose the surrounding context.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; Ingestion splits your documents into chunks, converts each chunk into an embedding, and stores the vectors in a vector database. Chunk size is a trade-off: large chunks blur meaning and tiny chunks lose context. &lt;a href="https://sureshpallapothu.in/blog/day-13-rag" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Step 2: Retrieval
&lt;/h2&gt;

&lt;p&gt;When a user asks a question, that question is embedded as well. The vector database runs a similarity search and returns the top few most relevant paragraphs. Three is a common starting point, and you tune it for your documents.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Augmented generation
&lt;/h2&gt;

&lt;p&gt;The retrieved paragraphs are injected into the LLM's prompt, together with an instruction: &lt;em&gt;"Answer the user's question using ONLY the following retrieved context."&lt;/em&gt; The model writes its answer from the supplied facts instead of from memory.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; The retrieved paragraphs are injected into the prompt with an instruction to answer using only that context. The model then writes its answer from the supplied facts instead of from memory. The example text is illustrative. &lt;a href="https://sureshpallapothu.in/blog/day-13-rag" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here is the whole loop in Python. The &lt;code&gt;embed&lt;/code&gt;, &lt;code&gt;search&lt;/code&gt; and &lt;code&gt;llm&lt;/code&gt; functions stand in for your embedding model, your vector database and your LLM API, so you can plug in the ones you use.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# 2. Retrieval: embed the question and fetch the 3 closest chunks
&lt;/span&gt;    &lt;span class="n"&gt;query_vector&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;embed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;chunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query_vector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top_k&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# 3. Augmented generation: put the chunks in the prompt
&lt;/span&gt;    &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;- &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Answer the question using ONLY the context below. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;If the context does not contain the answer, say you don&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t know.&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Context:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s"&gt;Question: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# 1. Ingestion (run ahead of time, and again whenever documents change)
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;split_into_chunks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;load_documents&lt;/span&gt;&lt;span class="p"&gt;()):&lt;/span&gt;
    &lt;span class="nf"&gt;store&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;embed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the "say you don't know" rule. It gives the model a safe way out when retrieval finds nothing useful. Tomorrow's lesson covers what happens when retrieval goes wrong, and how to measure it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coming Up Next
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Day 14:&lt;/strong&gt; RAG evaluation and hallucination guardrails.&lt;/p&gt;

&lt;h1&gt;
  
  
  RAG #GenerativeAI #EnterpriseAI #SoftwareEngineering #AIImplementation
&lt;/h1&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://sureshpallapothu.in/blog/day-13-rag" rel="noopener noreferrer"&gt;https://sureshpallapothu.in/blog/day-13-rag&lt;/a&gt;, where this post includes animated diagrams.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>rag</category>
      <category>30dayaiseries</category>
    </item>
    <item>
      <title>Day 12: Vector Databases — Storing and Searching Enterprise Knowledge</title>
      <dc:creator>Suresh Kumar Pallapothu</dc:creator>
      <pubDate>Mon, 12 Oct 2026 14:01:56 +0000</pubDate>
      <link>https://dev.to/suresh_kumar_de3920bedd1c/day-12-vector-databases-storing-and-searching-enterprise-knowledge-3ead</link>
      <guid>https://dev.to/suresh_kumar_de3920bedd1c/day-12-vector-databases-storing-and-searching-enterprise-knowledge-3ead</guid>
      <description>&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/ndw25VSwh74" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Yesterday you turned text into embeddings. Now you have a new problem: where do millions of those vectors &lt;strong&gt;live&lt;/strong&gt;, and how do you find the closest ones to a question &lt;strong&gt;fast&lt;/strong&gt;?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;In plain terms:&lt;/strong&gt; A normal database is a filing cabinet with labelled folders. You ask for the folder called "open" and it hands it over. A vector database is a librarian who understands what you mean and brings the books that are &lt;em&gt;closest&lt;/em&gt; to your idea, even if no label matches.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; A relational database answers exact-match questions. A vector database answers the question closest to this meaning by ranking stored embeddings by distance, which standard databases are not built to index. &lt;a href="https://sureshpallapothu.in/blog/day-12-vector-databases" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  1. The vector store
&lt;/h2&gt;

&lt;p&gt;Standard relational databases such as PostgreSQL or MySQL are built to search for &lt;strong&gt;exact matches&lt;/strong&gt;: &lt;code&gt;WHERE status = 'open'&lt;/code&gt;. They have no efficient way to answer "which stored vectors are closest to this one?" That job needs geometry. A &lt;strong&gt;vector database&lt;/strong&gt; such as Milvus, Qdrant or Pinecone is engineered to store and index millions of embeddings and to answer that question efficiently.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Approximate nearest neighbour (ANN)
&lt;/h2&gt;

&lt;p&gt;You could compare your question against every stored vector, but at a million documents that is far too slow. Vector databases build an index instead. &lt;strong&gt;HNSW&lt;/strong&gt; (Hierarchical Navigable Small World) is a popular one: it links vectors into a layered graph, so a search starts at a coarse layer and hops closer and closer to the answer. The trade is a little accuracy for a lot of speed, which is why it is called &lt;em&gt;approximate&lt;/em&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; Comparing a query against every vector is too slow at scale. Approximate nearest neighbour indexes such as HNSW hop through a layered graph to reach close matches in milliseconds, trading a little accuracy for a lot of speed. The comparison counts are illustrative. &lt;a href="https://sureshpallapothu.in/blog/day-12-vector-databases" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  3. pgvector: no new system required
&lt;/h2&gt;

&lt;p&gt;If your team already runs PostgreSQL and does not want another database to operate, the &lt;strong&gt;pgvector&lt;/strong&gt; extension adds a vector column type and similarity search to it. Your embeddings sit right next to your normal relational data, so one query can filter by customer and rank by meaning.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; You can store embeddings in a dedicated vector database such as Milvus, Qdrant or Pinecone, in PostgreSQL with the pgvector extension next to your regular data, or in a local library for small experiments. &lt;a href="https://sureshpallapothu.in/blog/day-12-vector-databases" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here is a complete pgvector example. The three-number vectors are only there to keep it short; real embeddings have hundreds of dimensions, and the &lt;code&gt;vector(3)&lt;/code&gt; size would match your model's output.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;EXTENSION&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;docs&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt;        &lt;span class="nb"&gt;serial&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;body&lt;/span&gt;      &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;          &lt;span class="c1"&gt;-- use your model's size, e.g. vector(1536)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;docs&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;embedding&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'High latency on the web tier'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="s1"&gt;'[0.82, -0.11, 0.47]'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Packets dropped at the switch'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="s1"&gt;'[0.79, -0.08, 0.51]'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Quarterly revenue forecast'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="s1"&gt;'[-0.12, 0.55, 0.20]'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- An index makes this fast at scale (HNSW with cosine distance):&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;docs&lt;/span&gt; &lt;span class="k"&gt;USING&lt;/span&gt; &lt;span class="n"&gt;hnsw&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="n"&gt;vector_cosine_ops&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- The 2 paragraphs closest in meaning to the question's vector.&lt;/span&gt;
&lt;span class="c1"&gt;-- &amp;lt;=&amp;gt; is cosine distance: smaller means closer.&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;docs&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'[0.80, -0.10, 0.49]'&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Put it together and a query looks like this: embed the question, ask the vector database for the nearest vectors, and get the matching paragraphs back.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; A question is turned into a vector by the embedding model, the vector database finds the nearest stored vectors, and the matching paragraphs come back ranked by closeness. &lt;a href="https://sureshpallapothu.in/blog/day-12-vector-databases" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Coming Up Next
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Day 13:&lt;/strong&gt; Retrieval-Augmented Generation (RAG): chatting with proprietary documents.&lt;/p&gt;

&lt;h1&gt;
  
  
  VectorDatabase #DataScience #DataArchitecture #BackendEngineering #AI
&lt;/h1&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://sureshpallapothu.in/blog/day-12-vector-databases" rel="noopener noreferrer"&gt;https://sureshpallapothu.in/blog/day-12-vector-databases&lt;/a&gt;, where this post includes animated diagrams.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>vectordatabase</category>
      <category>30dayaiseries</category>
    </item>
    <item>
      <title>Day 11: Embeddings and Vector Representations — How Machines Map Meaning</title>
      <dc:creator>Suresh Kumar Pallapothu</dc:creator>
      <pubDate>Sun, 11 Oct 2026 13:47:50 +0000</pubDate>
      <link>https://dev.to/suresh_kumar_de3920bedd1c/day-11-embeddings-and-vector-representations-how-machines-map-meaning-2e2m</link>
      <guid>https://dev.to/suresh_kumar_de3920bedd1c/day-11-embeddings-and-vector-representations-how-machines-map-meaning-2e2m</guid>
      <description>&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/kNe71VwpkhY" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;To make an AI work with your own company data, you first have to turn that data into something a machine can &lt;strong&gt;measure&lt;/strong&gt;. Words are not measurable. Numbers are. The bridge between the two is the &lt;strong&gt;embedding&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;In plain terms:&lt;/strong&gt; Imagine a giant map where every sentence in your company has a pin. Sentences about the same topic are pinned close together, and unrelated ones are far apart. An embedding is the pin's coordinates.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; An embedding model maps a chunk of text to a list of numbers. Texts about similar things, like network latency and packet drop, get similar numbers. Real embeddings have hundreds or thousands of values, and the ones shown are illustrative. &lt;a href="https://sureshpallapothu.in/blog/day-11-embeddings" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  1. The mathematical map
&lt;/h2&gt;

&lt;p&gt;An &lt;strong&gt;embedding model&lt;/strong&gt; reads a chunk of text and outputs a &lt;strong&gt;dense vector&lt;/strong&gt;: a list of numbers, usually 768 or 1,536 of them. You never read these numbers yourself. What matters is how they relate to each other. The numbers are produced so that text with a similar meaning gets a similar list.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Semantic closeness
&lt;/h2&gt;

&lt;p&gt;Because similar meanings get similar numbers, they cluster together when you plot them. The vectors for "network latency" and "packet drop" sit close together. The vector for "financial forecast" sits far away from both. A question you type also becomes a vector, and it lands next to the text that answers it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; Embeddings place texts on a map where meaning is distance. Network terms cluster together, finance terms cluster elsewhere, and a question lands near the text that answers it. The map is a flattened, illustrative view of a space with hundreds of dimensions. &lt;a href="https://sureshpallapothu.in/blog/day-11-embeddings" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  3. The power of distance
&lt;/h2&gt;

&lt;p&gt;Once all your documents are vectors, finding the right one becomes arithmetic. The common measure is &lt;strong&gt;cosine similarity&lt;/strong&gt;, which compares the &lt;em&gt;direction&lt;/em&gt; of two vectors. A score near 1 means they point the same way, so the texts mean much the same thing. A score near 0 means they are unrelated.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; Cosine similarity measures the angle between two vectors. Vectors pointing the same way score close to 1, meaning similar meaning, and unrelated ones score near 0. The scores shown are computed from the four-number example vectors, which are illustrative. &lt;a href="https://sureshpallapothu.in/blog/day-11-embeddings" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is why embedding search beats keyword search. Ask "Why is the site slow?" and a keyword search finds nothing if no document contains those words. An embedding search finds the paragraph about high latency on the web tier, because the &lt;em&gt;meaning&lt;/em&gt; is close.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Diagram:&lt;/strong&gt; Keyword search needs the same words. Embedding search compares meaning, so a question about a slow site can find a paragraph about high latency on the web tier even though the words differ. &lt;a href="https://sureshpallapothu.in/blog/day-11-embeddings" rel="noopener noreferrer"&gt;See the animated version&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here is the arithmetic in Python, using the same four-number example vectors from the diagrams. Real embedding models return far longer vectors, and you would call one from a library or an API.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;numpy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;cosine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linalg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;norm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linalg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;norm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;

&lt;span class="n"&gt;latency&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0.82&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.47&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.05&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;   &lt;span class="c1"&gt;# "network latency"
&lt;/span&gt;&lt;span class="n"&gt;drop&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0.79&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.08&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.51&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.02&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;   &lt;span class="c1"&gt;# "packet drop"
&lt;/span&gt;&lt;span class="n"&gt;forecast&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;0.12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.55&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.78&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;   &lt;span class="c1"&gt;# "financial forecast"
&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;cosine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;latency&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;drop&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;      &lt;span class="c1"&gt;# 1.0   -&amp;gt; almost the same meaning
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;cosine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;latency&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;forecast&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# -0.03 -&amp;gt; unrelated
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The numbers in this example are made up to show the idea. In a real system you embed every document once, store the vectors, and compare each new question against them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Coming Up Next
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Day 12:&lt;/strong&gt; Vector databases: storing and searching enterprise knowledge.&lt;/p&gt;

&lt;h1&gt;
  
  
  VectorEmbeddings #MachineLearning #NLP #DataEngineering #AIArchitecture
&lt;/h1&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://sureshpallapothu.in/blog/day-11-embeddings" rel="noopener noreferrer"&gt;https://sureshpallapothu.in/blog/day-11-embeddings&lt;/a&gt;, where this post includes animated diagrams.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>embeddings</category>
      <category>30dayaiseries</category>
    </item>
  </channel>
</rss>
