<?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: Zachary Huang</title>
    <description>The latest articles on DEV Community by Zachary Huang (@zachary62).</description>
    <link>https://dev.to/zachary62</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2910046%2F2ee06e7a-ad97-4d6e-b643-14ede3da429d.jpeg</url>
      <title>DEV Community: Zachary Huang</title>
      <link>https://dev.to/zachary62</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/zachary62"/>
    <language>en</language>
    <item>
      <title>Reinforcement Learning: Multi-Armed Bandits</title>
      <dc:creator>Zachary Huang</dc:creator>
      <pubDate>Sun, 10 Aug 2025 23:17:30 +0000</pubDate>
      <link>https://dev.to/zachary62/reinforcement-learning-multi-armed-bandits-4c5l</link>
      <guid>https://dev.to/zachary62/reinforcement-learning-multi-armed-bandits-4c5l</guid>
      <description>&lt;h3&gt;
  
  
  Part 1: The Big Idea - What's Your Casino Strategy?
&lt;/h3&gt;

&lt;p&gt;Before we dive in, let's talk about the big idea that separates Reinforcement Learning (RL) from other types of machine learning.&lt;/p&gt;

&lt;p&gt;Most of the time, when we teach a machine, we give it &lt;strong&gt;instructions&lt;/strong&gt;. This is called &lt;em&gt;supervised learning&lt;/em&gt;. It's like having a math teacher who shows you the correct answer (&lt;code&gt;5 + 5 = 10&lt;/code&gt;) and tells you to memorize it. The feedback is &lt;strong&gt;instructive&lt;/strong&gt;: "This is the right way to do it."&lt;/p&gt;

&lt;p&gt;Reinforcement Learning is different. It learns from &lt;strong&gt;evaluation&lt;/strong&gt;. It's like a critic watching you perform. After you take an action, the critic just tells you how good or bad that action was—a score. It doesn't tell you what you &lt;em&gt;should have&lt;/em&gt; done. The feedback is &lt;strong&gt;evaluative&lt;/strong&gt;: "That was a 7/10." This creates a problem: to find the best actions, you must actively search for them yourself.&lt;/p&gt;

&lt;p&gt;This need to search for good behavior is what we're going to explore, using a classic problem that makes it crystal clear.&lt;/p&gt;

&lt;h4&gt;
  
  
  The k-Armed Bandit: A Casino Dilemma
&lt;/h4&gt;

&lt;p&gt;Imagine you walk into a casino and see a row of &lt;code&gt;k&lt;/code&gt; slot machines. In our lingo, this is a &lt;strong&gt;&lt;code&gt;k&lt;/code&gt;-armed bandit&lt;/strong&gt;. Each machine (or "arm") is an &lt;strong&gt;action&lt;/strong&gt; you can take.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  You have a limited number of tokens, say 1000.&lt;/li&gt;
&lt;li&gt;  Each machine has a different, hidden probability of paying out a jackpot. This hidden average payout is the machine's true &lt;strong&gt;value&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;  Your goal is simple: &lt;strong&gt;Walk away with the most money possible.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;How do you play? This isn't just a brain teaser; it’s the perfect analogy for the most important trade-off in reinforcement learning.&lt;/p&gt;

&lt;h4&gt;
  
  
  Formalizing the Problem (The Simple Math)
&lt;/h4&gt;

&lt;p&gt;Let's put some labels on our casino game. Don't worry, the math is just a way to be precise.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  An &lt;strong&gt;action&lt;/strong&gt; &lt;code&gt;a&lt;/code&gt; is the choice of which of the &lt;code&gt;k&lt;/code&gt; levers to pull.&lt;/li&gt;
&lt;li&gt;  The action you choose at time step &lt;code&gt;t&lt;/code&gt; (e.g., your first pull, &lt;code&gt;t=1&lt;/code&gt;) is called &lt;code&gt;A_t&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;  The &lt;strong&gt;reward&lt;/strong&gt; you get from that pull is &lt;code&gt;R_t&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;  Each action &lt;code&gt;a&lt;/code&gt; has a true mean reward, which we call its &lt;strong&gt;value&lt;/strong&gt;, denoted as &lt;code&gt;q*(a)&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This value is the reward we &lt;em&gt;expect&lt;/em&gt; to get on average from that machine. Formally, it's written as:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;q*(a) = E[R_t | A_t = a]&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;In plain English, this means: "&lt;strong&gt;The true value of an action &lt;code&gt;a&lt;/code&gt; is the expected (average) reward you'll get, given you've selected that action.&lt;/strong&gt;"&lt;/p&gt;

&lt;p&gt;The catch? &lt;strong&gt;You don't know the true values &lt;code&gt;q*(a)&lt;/code&gt;!&lt;/strong&gt; You have to discover them by playing the game.&lt;/p&gt;

&lt;h4&gt;
  
  
  The Core Conflict: Exploration vs. Exploitation
&lt;/h4&gt;

&lt;p&gt;This is where the dilemma hits. With every token you spend, you face a choice:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Exploitation:&lt;/strong&gt; You've tried a few machines, and one of them seems to be paying out more than the others. Exploitation means you stick with that machine because, based on your current knowledge, it's your best bet to maximize your reward &lt;em&gt;right now&lt;/em&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Exploration:&lt;/strong&gt; You deliberately try a different machine—one that seems worse, or one you haven't even tried yet. Why? Because it &lt;em&gt;might&lt;/em&gt; be better than your current favorite. You are exploring to improve your knowledge of the world.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The Conflict:&lt;/strong&gt; You cannot explore and exploit with the same token. Every time you explore a potentially worse machine, you give up a guaranteed good-ish reward from your current favorite. But if you only ever exploit, you might get stuck on a decent machine, never discovering the true jackpot next to it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is the &lt;strong&gt;exploration-exploitation dilemma&lt;/strong&gt;. It is arguably the most important foundational concept in reinforcement learning. Finding a good strategy to balance this trade-off is the key to creating intelligent agents.&lt;/p&gt;

&lt;p&gt;In the next section, we'll look at a simple but flawed strategy for solving this problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  Part 2: A Simple (But Flawed) Strategy - The "Greedy" Approach
&lt;/h3&gt;

&lt;p&gt;So, how would most people play the slot machine game? The most straightforward strategy is to be "greedy."&lt;/p&gt;

&lt;p&gt;A greedy strategy works in two phases:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Estimate:&lt;/strong&gt; Keep a running average of the rewards you've gotten from each machine.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Exploit:&lt;/strong&gt; Always pull the lever of the machine that has the highest average so far.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This sounds reasonable, right? You're using the data you've collected to make the most profitable choice at every step. Let's formalize this.&lt;/p&gt;

&lt;h4&gt;
  
  
  How to Estimate Action Values
&lt;/h4&gt;

&lt;p&gt;Since we don't know the true value &lt;code&gt;q*(a)&lt;/code&gt; of a machine, we have to estimate it. We'll call our estimate at time step &lt;code&gt;t&lt;/code&gt; &lt;strong&gt;&lt;code&gt;Q_t(a)&lt;/code&gt;&lt;/strong&gt;. The simplest way to do this is the &lt;strong&gt;sample-average method&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Q_t(a) = (sum of rewards when action a was taken before time t) / (number of times action a was taken before time t)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This is just a simple average. If you pulled lever 1 three times and got rewards of 5, 7, and 3, your estimated value for lever 1 would be &lt;code&gt;(5+7+3)/3 = 5&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  The Greedy Action Selection Rule
&lt;/h4&gt;

&lt;p&gt;The greedy rule is to always select the action with the highest estimated value. We write this as:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;A_t = argmax_a Q_t(a)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;argmax_a&lt;/code&gt; part looks fancy, but it just means "&lt;strong&gt;find the action &lt;code&gt;a&lt;/code&gt; that maximizes the value of &lt;code&gt;Q_t(a)&lt;/code&gt;&lt;/strong&gt;." If two machines are tied for the best, you can just pick one of them randomly.&lt;/p&gt;

&lt;h4&gt;
  
  
  Why the Greedy Strategy Fails
&lt;/h4&gt;

&lt;p&gt;The greedy method has a fatal flaw: &lt;strong&gt;it's too quick to judge and never looks back.&lt;/strong&gt; It gets stuck on the first "good enough" option it finds, even if it's not the &lt;em&gt;best&lt;/em&gt; option.&lt;/p&gt;

&lt;p&gt;Let's see this in action with a minimal example.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Setup:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  A 3-armed bandit problem.&lt;/li&gt;
&lt;li&gt;  The true (hidden) values are:

&lt;ul&gt;
&lt;li&gt;  Machine 1: &lt;code&gt;q*(1) = 1&lt;/code&gt; (A dud)&lt;/li&gt;
&lt;li&gt;  Machine 2: &lt;code&gt;q*(2) = 5&lt;/code&gt; (Pretty good)&lt;/li&gt;
&lt;li&gt;  Machine 3: &lt;code&gt;q*(3) = 10&lt;/code&gt; (The real jackpot!)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;  To start, our agent needs some data, so let's say it tries each machine once.&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The Game:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Pull 1:&lt;/strong&gt; The agent tries &lt;strong&gt;Machine 1&lt;/strong&gt;. It's a dud, and the reward is &lt;code&gt;R_1 = 1&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Our estimate is now &lt;code&gt;Q(1) = 1&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Pull 2:&lt;/strong&gt; The agent tries &lt;strong&gt;Machine 2&lt;/strong&gt;. The reward is a lucky &lt;code&gt;R_2 = 7&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Our estimate is now &lt;code&gt;Q(2) = 7&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Pull 3:&lt;/strong&gt; The agent tries &lt;strong&gt;Machine 3&lt;/strong&gt; (the true jackpot). By pure bad luck, this one pull gives a disappointing reward of &lt;code&gt;R_3 = 4&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Our estimate is now &lt;code&gt;Q(3) = 4&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;The Trap:&lt;/strong&gt;&lt;br&gt;
After these three pulls, our agent's estimates are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;Q(1) = 1&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;Q(2) = 7&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;Q(3) = 4&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, the greedy strategy kicks in. From this point forward, which machine will the agent choose? It will always choose the &lt;code&gt;argmax&lt;/code&gt;, which is &lt;strong&gt;Machine 2&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The agent will pull the lever for Machine 2 forever. It will never go back to Machine 3, because based on its one unlucky experience, it "believes" Machine 2 is better. It got stuck exploiting a suboptimal action and will &lt;strong&gt;never discover the true jackpot machine&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This is why pure exploitation fails. We need a way to force the agent to keep exploring, just in case its initial estimates were wrong. That brings us to our first real solution.&lt;/p&gt;

&lt;h3&gt;
  
  
  Part 3: A Smarter Strategy - The ε-Greedy (Epsilon-Greedy) Method
&lt;/h3&gt;

&lt;p&gt;The greedy strategy failed because it was too stubborn. Once it found a "good enough" option, it never looked back. The ε-Greedy (pronounced "epsilon-greedy") method fixes this with a very simple and clever rule.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The Big Idea:&lt;/strong&gt; "Be greedy most of the time, but every once in a while, do something completely random."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Think of it like choosing a restaurant for dinner.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Exploitation (Greed):&lt;/strong&gt; You go to your favorite pizza place because you know it's good.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Exploration (Randomness):&lt;/strong&gt; Once a month, you ignore your favorites and just pick a random restaurant from Google Maps. You might end up at a terrible place, but you might also discover a new favorite!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This small dose of randomness is the key. It forces the agent to keep exploring all the options, preventing it from getting stuck.&lt;/p&gt;

&lt;h4&gt;
  
  
  The ε-Greedy Rule
&lt;/h4&gt;

&lt;p&gt;Here's how it works. We pick a small probability, called &lt;strong&gt;epsilon (ε)&lt;/strong&gt;, usually a value like 0.1 (which means 10%).&lt;/p&gt;

&lt;p&gt;At every time step, the agent does the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Generate a random number between 0 and 1.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;If the number is greater than ε:&lt;/strong&gt; &lt;strong&gt;Exploit&lt;/strong&gt;. Choose the action with the highest estimated value, just like the greedy method.

&lt;ul&gt;
&lt;li&gt;  This happens with probability &lt;code&gt;1 - ε&lt;/code&gt; (e.g., 90% of the time).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;If the number is less than or equal to ε:&lt;/strong&gt; &lt;strong&gt;Explore&lt;/strong&gt;. Choose an action completely at random from &lt;em&gt;all&lt;/em&gt; available actions, with equal probability.

&lt;ul&gt;
&lt;li&gt;  This happens with probability &lt;code&gt;ε&lt;/code&gt; (e.g., 10% of the time).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Why ε-Greedy Works
&lt;/h4&gt;

&lt;p&gt;Let's revisit our "Greedy Trap" from the previous section. Our agent was stuck forever pulling the lever for Machine 2, never realizing Machine 3 was the true jackpot.&lt;/p&gt;

&lt;p&gt;How would an ε-Greedy agent with &lt;code&gt;ε = 0.1&lt;/code&gt; handle this?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;90% of the time&lt;/strong&gt;, it would look at its estimates (&lt;code&gt;Q(1)=1&lt;/code&gt;, &lt;code&gt;Q(2)=7&lt;/code&gt;, &lt;code&gt;Q(3)=4&lt;/code&gt;) and greedily choose &lt;strong&gt;Machine 2&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;But 10% of the time&lt;/strong&gt;, it would ignore its estimates and pick a machine at random. This means it has a chance of picking Machine 1, Machine 2, or &lt;strong&gt;Machine 3&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Eventually, that 10% chance will cause it to try &lt;strong&gt;Machine 3&lt;/strong&gt; again. And again. And again. As it gets more samples from Machine 3, its estimated value &lt;code&gt;Q(3)&lt;/code&gt; will slowly climb from that unlucky &lt;code&gt;4&lt;/code&gt; towards the true value of &lt;code&gt;10&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Once &lt;code&gt;Q(3)&lt;/code&gt; becomes greater than &lt;code&gt;Q(2)&lt;/code&gt;, the agent's "greedy" choice will switch! Now, 90% of the time, it will exploit the &lt;em&gt;correct&lt;/em&gt; jackpot machine.&lt;/p&gt;

&lt;h4&gt;
  
  
  The Guarantee
&lt;/h4&gt;

&lt;p&gt;The advantage of this method is huge: in the long run, as the number of plays increases, every single machine will be sampled many, many times. Because of this, the &lt;strong&gt;Law of Large Numbers&lt;/strong&gt; tells us that our estimated values &lt;code&gt;Q_t(a)&lt;/code&gt; will eventually converge to the true values &lt;code&gt;q*(a)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This guarantees that the agent will eventually figure out which action is best and will select it most of the time. It solves the "getting stuck" problem completely. Now, let's see exactly how this works with a step-by-step example.&lt;/p&gt;

&lt;h3&gt;
  
  
  Part 4: Let's Play! A Step-by-Step Walkthrough
&lt;/h3&gt;

&lt;p&gt;Seeing is believing. We're going to simulate a few turns of an ε-Greedy agent to watch its "brain" update.&lt;/p&gt;

&lt;h4&gt;
  
  
  The Setup
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;The Game:&lt;/strong&gt; A 3-armed bandit problem.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;The Hidden Truth:&lt;/strong&gt; The true average payouts (&lt;code&gt;q*(a)&lt;/code&gt;) are:

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;q*(1) = 2&lt;/code&gt; (Dud)&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;q*(2) = 6&lt;/code&gt; (The Jackpot!)&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;q*(3) = 4&lt;/code&gt; (Decent)&lt;/li&gt;
&lt;li&gt;  &lt;em&gt;The agent does not know these numbers.&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;  &lt;strong&gt;Our Agent's Strategy:&lt;/strong&gt; ε-Greedy with &lt;code&gt;ε = 0.1&lt;/code&gt; (10% chance to explore).&lt;/li&gt;

&lt;li&gt;  &lt;strong&gt;Initial State:&lt;/strong&gt; The agent starts with no knowledge. Its estimated values (&lt;code&gt;Q&lt;/code&gt;) and pull counts (&lt;code&gt;N&lt;/code&gt;) for each arm are all zero.

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;Q(1)=0, Q(2)=0, Q(3)=0&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;N(1)=0, N(2)=0, N(3)=0&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h4&gt;
  
  
  The Incremental Update Formula
&lt;/h4&gt;

&lt;p&gt;As we get new rewards, we need to update our &lt;code&gt;Q&lt;/code&gt; values efficiently. We won't re-calculate the average from scratch every time. Instead, we use a simple incremental formula.&lt;/p&gt;

&lt;p&gt;When we choose action &lt;code&gt;A&lt;/code&gt; and get reward &lt;code&gt;R&lt;/code&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; First, increment the count for that action: &lt;code&gt;N(A) = N(A) + 1&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Then, update the value estimate with this formula:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Q(A) = Q(A) + (1/N(A)) * [R - Q(A)]&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's break this down:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;[R - Q(A)]&lt;/code&gt; is the &lt;strong&gt;error&lt;/strong&gt;: the difference between the new reward and what we expected.&lt;/li&gt;
&lt;li&gt;  We take a "step" to correct this error, with the step size &lt;code&gt;1/N(A)&lt;/code&gt;. Notice that as we sample an action more (&lt;code&gt;N(A)&lt;/code&gt; gets bigger), the step size gets smaller. This means our estimates become more stable and less affected by single random rewards over time.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  The Game Begins
&lt;/h4&gt;

&lt;p&gt;Let's follow the agent for the first 7 pulls. We will track everything in a table.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step (t)&lt;/th&gt;
&lt;th&gt;Agent's Decision&lt;/th&gt;
&lt;th&gt;Action (A_t)&lt;/th&gt;
&lt;th&gt;Reward (R_t)&lt;/th&gt;
&lt;th&gt;Agent's Updated Brain: N(a) and Q(a)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Start&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;---&lt;/td&gt;
&lt;td&gt;---&lt;/td&gt;
&lt;td&gt;---&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;N=(0,0,0)&lt;/code&gt;, &lt;code&gt;Q=(0,0,0)&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;All Qs are 0, must pick randomly.&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Arm 1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;R=1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;N=(1,0,0)&lt;/code&gt;, &lt;code&gt;Q=(1, 0, 0)&lt;/code&gt;&lt;br&gt;&lt;em&gt;&lt;code&gt;Q(1) = 0 + 1/1 * (1-0) = 1&lt;/code&gt;&lt;/em&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;2&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;argmax&lt;/code&gt; is Arm 1. Roll is &amp;gt; 0.1 -&amp;gt; &lt;strong&gt;EXPLOIT&lt;/strong&gt;.&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Arm 1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;R=3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;N=(2,0,0)&lt;/code&gt;, &lt;code&gt;Q=(2, 0, 0)&lt;/code&gt;&lt;br&gt;&lt;em&gt;&lt;code&gt;Q(1) = 1 + 1/2 * (3-1) = 2&lt;/code&gt;&lt;/em&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;3&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;argmax&lt;/code&gt; is Arm 1. Roll is &amp;lt; 0.1 -&amp;gt; &lt;strong&gt;EXPLORE&lt;/strong&gt;. Picks randomly.&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Arm 3&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;R=5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;N=(2,0,1)&lt;/code&gt;, &lt;code&gt;Q=(2, 0, 5)&lt;/code&gt;&lt;br&gt;&lt;em&gt;&lt;code&gt;Q(3) = 0 + 1/1 * (5-0) = 5&lt;/code&gt;&lt;/em&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;4&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;argmax&lt;/code&gt; is now Arm 3. Roll &amp;gt; 0.1 -&amp;gt; &lt;strong&gt;EXPLOIT&lt;/strong&gt;.&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Arm 3&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;R=3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;N=(2,0,2)&lt;/code&gt;, &lt;code&gt;Q=(2, 0, 4)&lt;/code&gt;&lt;br&gt;&lt;em&gt;&lt;code&gt;Q(3) = 5 + 1/2 * (3-5) = 4&lt;/code&gt;&lt;/em&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;5&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;argmax&lt;/code&gt; is still Arm 3. Roll &amp;gt; 0.1 -&amp;gt; &lt;strong&gt;EXPLOIT&lt;/strong&gt;.&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Arm 3&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;R=6&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;N=(2,0,3)&lt;/code&gt;, &lt;code&gt;Q=(2, 0, 4.67)&lt;/code&gt;&lt;br&gt;&lt;em&gt;&lt;code&gt;Q(3) = 4 + 1/3 * (6-4) = 4.67&lt;/code&gt;&lt;/em&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;6&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;argmax&lt;/code&gt; is still Arm 3. Roll &amp;lt; 0.1 -&amp;gt; &lt;strong&gt;EXPLORE&lt;/strong&gt;. Picks randomly.&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Arm 2&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;R=8&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;N=(2,1,3)&lt;/code&gt;, &lt;code&gt;Q=(2, 8, 4.67)&lt;/code&gt;&lt;br&gt;&lt;em&gt;&lt;code&gt;Q(2) = 0 + 1/1 * (8-0) = 8&lt;/code&gt;&lt;/em&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;7&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;code&gt;argmax&lt;/code&gt; is now Arm 2!&lt;/strong&gt; Roll &amp;gt; 0.1 -&amp;gt; &lt;strong&gt;EXPLOIT&lt;/strong&gt;.&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Arm 2&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;R=5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;N=(2,2,3)&lt;/code&gt;, &lt;code&gt;Q=(2, 6.5, 4.67)&lt;/code&gt;&lt;br&gt;&lt;em&gt;&lt;code&gt;Q(2) = 8 + 1/2 * (5-8) = 6.5&lt;/code&gt;&lt;/em&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h4&gt;
  
  
  Let's Analyze What Happened
&lt;/h4&gt;

&lt;p&gt;This short sequence shows the power of ε-Greedy in action:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Initial Belief:&lt;/strong&gt; After two pulls, the agent thought Arm 1 was best (&lt;code&gt;Q(1)=2&lt;/code&gt;). A purely greedy agent would have gotten stuck here.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Discovery through Exploration:&lt;/strong&gt; In &lt;strong&gt;Step 3&lt;/strong&gt;, a random exploratory action forced the agent to try Arm 3. It got a good reward (&lt;code&gt;R=5&lt;/code&gt;), and its belief about the best arm changed.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Another Discovery:&lt;/strong&gt; The agent was happily exploiting Arm 3 until &lt;strong&gt;Step 6&lt;/strong&gt;, when another random exploration forced it to try the last unknown, Arm 2. It got a very high reward (&lt;code&gt;R=8&lt;/code&gt;), and its belief changed again!&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Nearing the Truth:&lt;/strong&gt; After only 7 pulls, the agent's estimates are &lt;code&gt;Q=(2, 6.5, 4.67)&lt;/code&gt;. These are getting much closer to the true values of &lt;code&gt;q*=(2, 6, 4)&lt;/code&gt;. Its greedy choice is now correctly focused on the best arm, Arm 2.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the learning process. The agent starts with no idea, forms a belief, and then uses exploration to challenge and refine that belief. Over thousands of steps, this simple mechanism allows it to zero in on the best actions in its environment.&lt;/p&gt;

&lt;h3&gt;
  
  
  Part 5: Two More "Clever Tricks" for Exploration
&lt;/h3&gt;

&lt;p&gt;The ε-Greedy method is simple and effective, but its exploration is &lt;em&gt;random&lt;/em&gt;. It doesn't care if it's exploring a machine it has tried 100 times or one it has never touched. Can we be smarter about how we explore? Yes.&lt;/p&gt;

&lt;p&gt;Here are two popular techniques that add a bit more intelligence to the exploration process.&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Optimistic Initial Values: The Power of Positive Thinking
&lt;/h4&gt;

&lt;p&gt;This is a wonderfully simple trick that encourages a burst of exploration right at the start of learning.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Idea:&lt;/strong&gt; Instead of initializing your value estimates &lt;code&gt;Q(a)&lt;/code&gt; to 0, initialize them to a "wildly optimistic" high number. For example, if you know the maximum possible reward from any machine is 10, you might set all your initial &lt;code&gt;Q&lt;/code&gt; values to 20.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Q_1(a) = 20&lt;/code&gt; for all actions &lt;code&gt;a&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How it Works:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; On the first step, all actions look equally amazing (&lt;code&gt;Q=20&lt;/code&gt;). The agent picks one, let's say Arm 1.&lt;/li&gt;
&lt;li&gt; It gets a real reward, say &lt;code&gt;R=5&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; The agent updates its estimate &lt;code&gt;Q(1)&lt;/code&gt;. The new &lt;code&gt;Q(1)&lt;/code&gt; will now be a value much lower than 20.&lt;/li&gt;
&lt;li&gt; Now, the agent looks at its options again. Arm 1 looks "disappointing" compared to all the other arms, which it still believes have a value of 20.&lt;/li&gt;
&lt;li&gt; So, for its next turn, the greedy agent will naturally pick a &lt;em&gt;different&lt;/em&gt; arm.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This process continues. Every time an arm is tried, its value drops from the optimistic high, making it look "disappointing" and encouraging the agent to try all the other arms it hasn't touched yet. It’s a self-correcting system that drives the agent to explore everything at least a few times before it starts to settle on the true best option.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key Takeaway:&lt;/strong&gt; By being optimistic, a purely greedy agent is tricked into exploring.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Upper-Confidence-Bound (UCB): The "Smart Exploration" Method
&lt;/h4&gt;

&lt;p&gt;UCB is a more sophisticated approach. It addresses a key question: if we're going to explore, which arm is the &lt;em&gt;most useful&lt;/em&gt; one to try?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Idea:&lt;/strong&gt; The best arm to explore is one that is both:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Potentially high-value&lt;/strong&gt; (its current &lt;code&gt;Q(a)&lt;/code&gt; is high).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Highly uncertain&lt;/strong&gt; (we haven't tried it much, so &lt;code&gt;Q(a)&lt;/code&gt; could be very wrong).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;UCB combines these two factors into a single score. Instead of just picking the &lt;code&gt;argmax&lt;/code&gt; of &lt;code&gt;Q(t)&lt;/code&gt;, it picks the &lt;code&gt;argmax&lt;/code&gt; of a special formula:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;A_t = argmax_a [ Q_t(a) + c * sqrt(ln(t) / N_t(a)) ]&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Let's break that down without fear:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;Q_t(a)&lt;/code&gt; is our standard value estimate. This is the &lt;strong&gt;exploitation&lt;/strong&gt; part.&lt;/li&gt;
&lt;li&gt;  The second part, &lt;code&gt;c * sqrt(ln(t) / N_t(a))&lt;/code&gt;, is the &lt;strong&gt;exploration bonus&lt;/strong&gt; or &lt;strong&gt;uncertainty term&lt;/strong&gt;.

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;t&lt;/code&gt; is the total number of pulls so far. As &lt;code&gt;t&lt;/code&gt; increases, this term slowly grows, encouraging exploration over time.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;N_t(a)&lt;/code&gt; is the number of times we've pulled arm &lt;code&gt;a&lt;/code&gt;. This is the important part: &lt;strong&gt;as &lt;code&gt;N_t(a)&lt;/code&gt; increases, the uncertainty bonus shrinks.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;c&lt;/code&gt; is a constant that controls how much you favor exploration. A bigger &lt;code&gt;c&lt;/code&gt; means more exploring.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;How it Works:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  If an arm has a good &lt;code&gt;Q&lt;/code&gt; value but has been tried many times (&lt;code&gt;N_t(a)&lt;/code&gt; is large), its uncertainty bonus will be small. It's a known quantity.&lt;/li&gt;
&lt;li&gt;  If an arm has a mediocre &lt;code&gt;Q&lt;/code&gt; value but has been tried only a few times (&lt;code&gt;N_t(a)&lt;/code&gt; is small), its uncertainty bonus will be very large. This makes it an attractive candidate for exploration because its true value could be much higher than we think.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;UCB naturally balances exploration and exploitation. It favors arms it is uncertain about, and as it tries them, its uncertainty decreases, and the &lt;code&gt;Q&lt;/code&gt; value starts to matter more. It's a more directed and often more efficient way to explore than the random approach of ε-Greedy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Part 6: So What? Why Bandits Matter
&lt;/h3&gt;

&lt;p&gt;We’ve journeyed through the k-armed bandit problem, starting with a simple casino analogy and exploring several strategies to solve it. So, what’s the big takeaway?&lt;/p&gt;

&lt;h4&gt;
  
  
  Summary: The Heart of the Problem
&lt;/h4&gt;

&lt;p&gt;The multi-armed bandit problem is not really about slot machines. It is a simplified, pure version of the core challenge in all of reinforcement learning: the &lt;strong&gt;exploration-exploitation dilemma&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;We saw that simple strategies can have major flaws:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  The &lt;strong&gt;Greedy&lt;/strong&gt; method gets stuck, failing to find the best option because it never looks back.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And we saw how to fix it by intelligently balancing the trade-off:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;ε-Greedy&lt;/strong&gt; is a simple and robust solution: it acts greedily most of the time but takes a random exploratory action with a small probability &lt;code&gt;ε&lt;/code&gt;, ensuring it never gets stuck.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Optimistic Initial Values&lt;/strong&gt; is a clever trick that uses a purely greedy agent but encourages a natural burst of exploration at the beginning by assuming everything is amazing.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Upper-Confidence-Bound (UCB)&lt;/strong&gt; is a more sophisticated method that explores strategically, prioritizing actions that are both promising and highly uncertain.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of these methods provides a way to gather information (explore) while trying to maximize rewards with the information you have (exploit).&lt;/p&gt;

&lt;h4&gt;
  
  
  The Bridge to Real-World Reinforcement Learning
&lt;/h4&gt;

&lt;p&gt;This "bandit" framework is a fundamental building block. The same principles apply to much more complex problems, both in technology and in real life.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;A/B Testing on Websites:&lt;/strong&gt; Which version of a headline or button color (&lt;code&gt;actions&lt;/code&gt;) will get the most clicks (&lt;code&gt;reward&lt;/code&gt;)? A company can use bandit algorithms to automatically explore different versions and quickly exploit the one that works best, maximizing user engagement in real-time.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Clinical Trials:&lt;/strong&gt; Doctors want to find the most effective treatment (&lt;code&gt;action&lt;/code&gt;) for a disease. Each patient's outcome is a &lt;code&gt;reward&lt;/code&gt;. Bandit algorithms can help balance giving patients the current best-known treatment (exploit) with trying new, experimental ones that might be even better (explore), potentially saving more lives in the long run.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The Full RL Problem:&lt;/strong&gt; The problems we've discussed so far are "non-associative," meaning the best action is always the same. But what if the best action depends on the &lt;strong&gt;situation&lt;/strong&gt; or &lt;strong&gt;context&lt;/strong&gt;?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  For a self-driving car, the best action (steer, brake) depends on the situation (red light, green light, pedestrian).&lt;/li&gt;
&lt;li&gt;  For a game-playing AI, the best move depends on the state of the board.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;This is called &lt;strong&gt;associative search&lt;/strong&gt; or &lt;strong&gt;contextual bandits&lt;/strong&gt;, and it's the next step towards the full reinforcement learning problem. The agent must learn not just the best action overall, but the best action &lt;em&gt;for each specific situation&lt;/em&gt;. The methods we learned here—like ε-greedy and UCB—are used as the core decision-making components inside these more advanced AIs.&lt;/p&gt;

&lt;p&gt;By understanding the simple trade-off in the k-armed bandit problem, you have grasped the essential challenge that every reinforcement learning agent must solve to learn and act intelligently in a complex world.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>LLM Agents &amp; Prompt Engineering: A Warrior's Guide to Clear Commands</title>
      <dc:creator>Zachary Huang</dc:creator>
      <pubDate>Mon, 28 Jul 2025 21:29:01 +0000</pubDate>
      <link>https://dev.to/zachary62/llm-agents-prompt-engineering-a-warriors-guide-to-clear-commands-5g82</link>
      <guid>https://dev.to/zachary62/llm-agents-prompt-engineering-a-warriors-guide-to-clear-commands-5g82</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;You've forged a legendary warrior with the perfect arsenal and taught them to navigate any dungeon. But what happens when your commands are as clear as mud? "Attack the thing!" you shout. Which thing? With what weapon? Your warrior stands confused. In this guide, you'll master the final art—giving crystal-clear battle commands that turn hesitation into decisive action.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  1. Introduction: The Confused Warrior
&lt;/h2&gt;

&lt;p&gt;Picture this: You've spent weeks creating the ultimate digital warrior. In &lt;a href="https://pocketflow.substack.com/p/llm-agents-arsenal-a-beginners-guide" rel="noopener noreferrer"&gt;previous posts&lt;/a&gt;, you've given them a razor-sharp sword (perfect actions). You've taught them to carry a &lt;a href="https://pocketflow.substack.com/p/llm-agents-and-context-a-warriors" rel="noopener noreferrer"&gt;lightweight but complete backpack&lt;/a&gt; (smart context management), and they're standing right in front of the treasure chest. Victory is one command away.&lt;/p&gt;

&lt;p&gt;"Get the treasure!" you command confidently.&lt;/p&gt;

&lt;p&gt;Your warrior looks at you. Looks at the chest. Looks back at you. Then proceeds to attack the chest with their sword, smashing it to pieces and destroying the treasure inside. &lt;/p&gt;

&lt;p&gt;"What are you DOING?!" you scream.&lt;/p&gt;

&lt;p&gt;The warrior shrugs. "You said 'get' the treasure. I thought you meant destroy the chest to get to it. Maybe you should have said 'carefully open the chest and retrieve the treasure inside.'"&lt;/p&gt;

&lt;p&gt;This is the final challenge in building effective LLM agents. You can have the most &lt;a href="https://pocketflow.substack.com/p/llm-agent-internal-as-a-graph-tutorial" rel="noopener noreferrer"&gt;sophisticated graph structure&lt;/a&gt;, the &lt;a href="https://pocketflow.substack.com/p/llm-agents-arsenal-a-beginners-guide" rel="noopener noreferrer"&gt;deadliest arsenal of tools&lt;/a&gt;, and the &lt;a href="https://pocketflow.substack.com/p/llm-agents-and-context-a-warriors" rel="noopener noreferrer"&gt;smartest context management&lt;/a&gt;—but if your commands are vague, your agent becomes a confused mess.&lt;/p&gt;

&lt;p&gt;The good news? &lt;strong&gt;Prompt engineering isn't magic.&lt;/strong&gt; It's not about finding secret incantations that "unlock" your LLM's hidden powers. It's simply the art of giving clear, unambiguous instructions. Think of it as the final sharpening of your blade—not the forging itself, but the careful honing that turns a good sword into a legendary one.&lt;/p&gt;

&lt;p&gt;In this guide, we'll strip away the mystique and show you three simple laws that will transform your prompts from confusing mumbles into crystal-clear battle commands. By the end, your warrior won't just be powerful—they'll actually understand what you want them to do.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. How Agents Work: The Battle Plan Recap
&lt;/h2&gt;

&lt;p&gt;Before we sharpen our commands, let's quickly remind ourselves how our warrior actually operates. As we discovered in our &lt;a href="https://pocketflow.substack.com/p/llm-agent-internal-as-a-graph-tutorial" rel="noopener noreferrer"&gt;previous adventures&lt;/a&gt;, every LLM agent—no matter how complex it appears—follows a devastatingly simple loop:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Assess → Strike → Repeat&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In the &lt;strong&gt;PocketFlow&lt;/strong&gt; framework, this loop manifests as a graph:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwxl66lg0agcwmjme6o4i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwxl66lg0agcwmjme6o4i.png" alt=" " width="800" height="379"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;DecideNode&lt;/code&gt; is our warrior's brain—the battle tactician that looks at the situation and chooses the next move. But here's the crucial part: &lt;strong&gt;how does it think?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The answer is simpler than you might expect. The entire "thinking" process is just a prompt. Here's the general structure inside any DecideNode:&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;prompt&lt;/span&gt; &lt;span class="o"&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;
### YOUR TASK
&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;task_instruction&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;

### CURRENT STATE
&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="s"&gt;

### AVAILABLE ACTIONS
&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;action_space&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;

### YOUR NEXT MOVE
Based on your task and current state, choose the best action.
Format your response as:

```yaml
thinking: |
    &amp;lt;your reasoning&amp;gt;
action: &amp;lt;chosen action&amp;gt;
parameters: &amp;lt;any required parameters&amp;gt;
```&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;This is the prompt we're going to improve.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Notice how this universal template has three main components:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Task Instruction&lt;/strong&gt;: What the agent is trying to accomplish&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context&lt;/strong&gt;: The current state (from the &lt;code&gt;shared&lt;/code&gt; store we discussed)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Action Space&lt;/strong&gt;: The available tools (the arsenal we forged)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We've already mastered &lt;a href="https://pocketflow.substack.com/p/llm-agents-arsenal-a-beginners-guide" rel="noopener noreferrer"&gt;forging the perfect &lt;strong&gt;action space&lt;/strong&gt;&lt;/a&gt; (giving our warrior the right weapons) and &lt;a href="https://pocketflow.substack.com/p/llm-agents-and-context-a-warriors" rel="noopener noreferrer"&gt;managing &lt;strong&gt;context&lt;/strong&gt;&lt;/a&gt; (keeping the backpack light and relevant). Now it's time to focus on that final piece: the &lt;strong&gt;task instruction&lt;/strong&gt;—the actual commands we give our warrior.&lt;/p&gt;

&lt;p&gt;Look at it carefully. Everything the agent knows comes from these three sections. And since we've already perfected the action space and context management in our previous guides, the quality of your agent now hinges on that task instruction—the specific commands and guidelines you provide.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The Myth of "Prompt Engineering"
&lt;/h2&gt;

&lt;p&gt;First, let's clear the air. The term "prompt engineering" sounds intimidating, and the internet is full of hype about finding secret phrases that "unlock" an LLM's true potential. People share "magic prompts" like ancient spells, promising that adding "let's think step by step" or "you are an expert" will transform your results.&lt;/p&gt;

&lt;p&gt;The reality is much simpler and, frankly, more empowering. Effective prompt engineering is not about tricking the LLM or finding secret incantations. It's about &lt;strong&gt;clarity and structure&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;Your goal is simple: write an instruction manual so clear, so unambiguous, that the LLM can't possibly misinterpret it. It's not magic—it's just good communication.&lt;/p&gt;

&lt;p&gt;Here's the most important principle for any agent builder:&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;The 90/10 Rule: Spend 90% of your time designing actions and context. Prompting is the final 10%.&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Think of it this way:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Actions (The Arsenal)&lt;/strong&gt;: If your warrior doesn't have the right weapons, no amount of yelling will help&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context (The Backpack)&lt;/strong&gt;: If your warrior is carrying 500 pounds of junk, they can't move effectively&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prompts (The Commands)&lt;/strong&gt;: Only after the first two are solid should you polish your instructions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your agent is failing, resist the urge to immediately tweak prompts. Instead, ask:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Does it have the right tools for this job?&lt;/li&gt;
&lt;li&gt;Is its context clear and focused?&lt;/li&gt;
&lt;li&gt;Only then: Are my instructions clear?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is why prompt engineering is the "sharpening, not the forging." You can't sharpen a blade that doesn't exist. You can't polish a sword made of rubber. But once you have a well-forged weapon and a clear battlefield, a sharp command makes all the difference.&lt;/p&gt;

&lt;p&gt;Let's learn how to sharpen that blade.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. The 3 Sacred Laws of Command
&lt;/h2&gt;

&lt;p&gt;Now that we understand prompt engineering is about clarity, not magic, let's dive into the three fundamental techniques that will transform your vague mumbles into crystal-clear battle commands.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.1. Law #1: Demand the Battle Plan (Chain of Thought)
&lt;/h3&gt;

&lt;p&gt;Imagine two different commanders:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Commander A:&lt;/strong&gt; "Attack the fortress!"&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Commander B:&lt;/strong&gt; "First, tell me your plan for attacking the fortress. Then execute it."&lt;/p&gt;

&lt;p&gt;Which warrior is more likely to succeed? The one who charges blindly, or the one who thinks through their approach first?&lt;/p&gt;

&lt;p&gt;This is the power of Chain of Thought (CoT). By explicitly asking your agent to explain its reasoning BEFORE taking action, you dramatically improve its decision quality. It's like the difference between a warrior who swings wildly and one who calculates each strike.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before (No Chain of Thought):&lt;/strong&gt;&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;prompt&lt;/span&gt; &lt;span class="o"&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;### YOUR TASK
&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;task_instruction&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;

### YOUR NEXT MOVE
Choose an action and provide parameters.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;After (With Chain of Thought):&lt;/strong&gt;&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;prompt&lt;/span&gt; &lt;span class="o"&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;### YOUR TASK
&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;task_instruction&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;

### YOUR NEXT MOVE
First, explain your step-by-step plan for completing this task.
Then choose an action and provide parameters.

Format your response as:
```yaml
thinking: |
    &amp;lt;your step-by-step reasoning process&amp;gt;
action: &amp;lt;chosen action&amp;gt;
parameters: &amp;lt;required parameters&amp;gt;
```&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The difference is profound. With the second prompt, your agent might think:&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;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;thinking: |
    1. I need to find grocery spending for last month
    2. First, I should identify what &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;last month&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; means (get current date)
    3. Then search for transactions in that date range
    4. Filter for grocery-related merchants
    5. Sum up the amounts
    Starting with getting the current date...
action: get_current_date
&lt;/span&gt;&lt;span class="gp"&gt;...&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without CoT, it might have jumped straight to searching transactions without properly defining the date range. &lt;strong&gt;Chain of Thought forces your warrior to strategize before striking.&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Pro Tip:&lt;/strong&gt; If you're using a thinking model (like o1 or Claude with thinking mode), you get this for free! These models automatically think through problems step-by-step without explicit prompting. It's like having a warrior who's already trained to strategize before every move.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  4.2. Law #2: Speak Like a General, Not a Poet (Be Specific)
&lt;/h3&gt;

&lt;p&gt;Your warrior stands before two doors. You could say:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Poetic:&lt;/strong&gt; "Choose wisely, for one path leads to glory."&lt;br&gt;&lt;br&gt;
&lt;strong&gt;General:&lt;/strong&gt; "Take the left door. It leads to the armory. Avoid the right door—it's trapped with poison darts."&lt;/p&gt;

&lt;p&gt;Which instruction leads to success? The specific one, every time.&lt;/p&gt;

&lt;p&gt;The biggest prompt engineering mistake is using vague, open-ended language when you actually have specific requirements. Let's look at common examples:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vague → Specific Transformations:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;❌ "Search for relevant information"
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✅ "Search for peer-reviewed studies published after 2020 about renewable energy costs"&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;❌ "Summarize this appropriately"  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✅ "Create a 3-paragraph summary with: (1) main finding, (2) methodology, (3) implications"&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;❌ "Handle errors gracefully"  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;✅ "If a search returns no results, try broadening the query by removing adjectives. If it still fails, return 'No information found' and explain what was searched"&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The Power of Constraints:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Constraints aren't limitations—they're guideposts that lead to better results. Consider these improvements:&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="c1"&gt;# Vague prompt
&lt;/span&gt;&lt;span class="n"&gt;prompt_vague&lt;/span&gt; &lt;span class="o"&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;### TASK (Vague)
Research the topic and provide information&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="c1"&gt;# Specific prompt
&lt;/span&gt;&lt;span class="n"&gt;prompt_specific&lt;/span&gt; &lt;span class="o"&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;### TASK (Specific)
Research electric vehicles with these constraints:
- Focus on: battery technology and charging infrastructure  
- Time period: 2022-2024 developments only
- Sources: Prioritize industry reports and government data
- Length: Provide 3-5 key findings, each in 1-2 sentences
- Format: Bullet points with source citations&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The specific version removes all ambiguity. Your warrior knows exactly what victory looks like.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.3. Law #3: Show, Don't Just Tell (Few-Shot Examples)
&lt;/h3&gt;

&lt;p&gt;Sometimes, even the clearest words aren't enough. This is when you need to demonstrate, not just describe. Think of it as the difference between explaining sword techniques with words versus actually showing the movements.&lt;/p&gt;

&lt;p&gt;Few-shot examples are perfect when you need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A specific writing style&lt;/li&gt;
&lt;li&gt;A particular output format
&lt;/li&gt;
&lt;li&gt;Complex patterns that are hard to describe&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Without Examples (Frustrating):&lt;/strong&gt;&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;prompt&lt;/span&gt; &lt;span class="o"&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;### TASK
Write a customer support response in our company&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s friendly, solution-focused style

&amp;lt;context and customer message here&amp;gt;
&lt;/span&gt;&lt;span class="gp"&gt;...&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your agent thinks: "What does 'friendly' mean? How solution-focused? What tone?"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;With Examples (Crystal Clear):&lt;/strong&gt;&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;prompt&lt;/span&gt; &lt;span class="o"&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;### TASK
Write a customer support response following our style:

Example 1:
Customer: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;My order hasn&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t arrived and it&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s been a week!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;
Response: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;I completely understand your frustration - a week is definitely too long to wait! Let me track that down for you right away. I can see your order #12345 left our warehouse on Monday. I&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ll contact the carrier now and have an update for you within 2 hours. In the meantime, I&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ve credited your account with 20% off your next order for the inconvenience.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;

Example 2:
Customer: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;This product broke after one day&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;
Response: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Oh no! That&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s definitely not the experience we want you to have. I&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;m starting a replacement order for you right now - it&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ll ship today with express delivery at no charge. No need to return the broken item. Your new one should arrive by Thursday. Is there anything else I can help make right?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;

Now respond to this customer following the same style:
Customer: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;customer_message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice how the examples teach the style without explicit rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Start with empathy/acknowledgment&lt;/li&gt;
&lt;li&gt;Take immediate action (no "we'll look into it")&lt;/li&gt;
&lt;li&gt;Provide specific next steps and timelines&lt;/li&gt;
&lt;li&gt;Offer something extra for the inconvenience&lt;/li&gt;
&lt;li&gt;End with an open offer to help more&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;When to Use Examples:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Writing style&lt;/strong&gt;: "Match our blog voice" → Show 2-3 actual blog excerpts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Response formats&lt;/strong&gt;: "Format like our reports" → Show a complete sample&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tone matching&lt;/strong&gt;: "Professional but warm" → Demonstrate with real examples&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Edge cases&lt;/strong&gt;: Show how to handle tricky situations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Examples are particularly powerful because they bypass the ambiguity of language. Instead of trying to define "friendly but professional," you simply show what it looks like in action.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Conclusion: From Confused Grunt to Elite Soldier
&lt;/h2&gt;

&lt;p&gt;And so, our journey is complete. We've transformed a simple graph into a legendary warrior:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The Graph&lt;/strong&gt;: Every agent is just Assess → Strike → Repeat&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Arsenal&lt;/strong&gt;: Sharp, purposeful tools for every task&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Context&lt;/strong&gt;: A light backpack with only what's needed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Commands&lt;/strong&gt;: Crystal-clear orders that eliminate confusion&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Remember the 90/10 rule: If your agent fails, check the arsenal and context first. Prompt engineering is just the final polish. But when you need that polish, your three laws are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Demand the battle plan (Chain of Thought)&lt;/li&gt;
&lt;li&gt;Speak like a general (Be Specific)
&lt;/li&gt;
&lt;li&gt;Show, don't tell (Few-Shot Examples)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your warrior no longer smashes treasure chests when you meant "open them carefully." They understand. They execute. They win.&lt;/p&gt;

&lt;p&gt;The next time someone shares "secret prompt tricks," smile knowingly. You understand it's not about magic—it's about clarity.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Ready to build agents that actually understand? Check out &lt;a href="https://github.com/the-pocket/PocketFlow" rel="noopener noreferrer"&gt;PocketFlow on GitHub&lt;/a&gt; and start commanding your digital warriors!&lt;/em&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>LLM Agents &amp; Context: A Warrior's Guide to Navigating the Dungeon</title>
      <dc:creator>Zachary Huang</dc:creator>
      <pubDate>Thu, 03 Jul 2025 17:43:26 +0000</pubDate>
      <link>https://dev.to/zachary62/llm-agents-context-a-warriors-guide-to-navigating-the-dungeon-4edk</link>
      <guid>https://dev.to/zachary62/llm-agents-context-a-warriors-guide-to-navigating-the-dungeon-4edk</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Your agent has a legendary sword and a powerful spellbook. But what good are weapons if your warrior is lost in a sprawling dungeon, unable to remember which rooms are cleared and which hold treasure? In this guide, you'll learn the three master navigation techniques of agent memory—the Scrying Spell, the Grand Strategy, and the Cautious Explorer's Path. It's time to teach our warrior not just how to fight, but how to think.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  1. Introduction: The Warrior Enters the Dungeon
&lt;/h2&gt;

&lt;p&gt;In our previous adventures, we learned the secret that all agents are just simple graphs and forged our warrior in &lt;a href="https://pocketflow.substack.com/p/llm-agent-internal-as-a-graph-tutorial" rel="noopener noreferrer"&gt;LLM Agents are simply Graph — Tutorial For Dummies&lt;/a&gt;. Then, we equipped it with a deadly arsenal of actions in &lt;a href="https://pocketflow.substack.com/p/llm-agents-arsenal-a-beginners-guide" rel="noopener noreferrer"&gt;LLM Agents &amp;amp; Their Arsenal: A Beginner's Guide&lt;/a&gt;. But now, our warrior faces its greatest challenge yet: the environment itself.&lt;/p&gt;

&lt;p&gt;The agent's battle isn't on an open field; it's in a dark, complex dungeon—a large codebase, a multi-step research task, or a complex dataset. Here, the biggest danger isn't the monsters (the individual tasks), but getting lost, forgetting where you've been, and losing sight of the treasure at the end.&lt;/p&gt;

&lt;p&gt;This brings us to the most critical, and often botched, aspect of agent design: context management. Think of it as the warrior's &lt;strong&gt;Cognitive Backpack&lt;/strong&gt;. In our &lt;a href="https://github.com/the-pocket/PocketFlow" rel="noopener noreferrer"&gt;&lt;strong&gt;PocketFlow&lt;/strong&gt;&lt;/a&gt; framework, this is the simple &lt;code&gt;shared&lt;/code&gt; dictionary that each Node reads from and writes to. The naive approach is to stuff everything inside. Imagine loading the entire dungeon map, every monster's stat block, every rumor of treasure, and the history of the last three adventurers who failed into the warrior's backpack right at the start. They'd collapse under the weight before they even took their first step.&lt;/p&gt;

&lt;p&gt;Smart context management isn't about giving the agent &lt;em&gt;more&lt;/em&gt; memory; it's about giving it the &lt;strong&gt;right&lt;/strong&gt; memory at the &lt;strong&gt;right&lt;/strong&gt; time. In this guide, we'll stop treating our agent's memory like a junk drawer and start treating it like a high-tech utility belt. We will learn three master-level navigation techniques to keep the backpack light, the warrior agile, and the path to victory clear.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The Warrior's Battle Plan: A Quick Recap
&lt;/h2&gt;

&lt;p&gt;Before we teach our warrior new navigation tricks, let's refresh our memory of the battle plan. As we learned, every agent, no matter how complex, follows a simple, relentless loop: &lt;code&gt;Assess -&amp;gt; Strike -&amp;gt; Repeat&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In the &lt;strong&gt;PocketFlow&lt;/strong&gt; framework, this elegant loop is visualized as a graph:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuwur15948cmxpgio4ald.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuwur15948cmxpgio4ald.png" alt="Image description" width="800" height="275"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;DecideNode&lt;/code&gt; is the warrior's brain—the battle tactician. It assesses the situation and chooses the next move. The &lt;code&gt;ActionNodes&lt;/code&gt; are the specialist soldiers who carry out a specific command. And the loop back is the report, bringing new information from the battlefield back to the tactician.&lt;/p&gt;

&lt;p&gt;But how does the &lt;code&gt;DecideNode&lt;/code&gt; &lt;em&gt;actually&lt;/em&gt; think? How does it "assess" the situation? This isn't magic; it's a carefully crafted prompt. The node's entire worldview comes from the &lt;code&gt;shared&lt;/code&gt; dictionary, which is formatted and injected directly into its brain.&lt;/p&gt;

&lt;p&gt;The prompt inside our &lt;code&gt;DecideNode&lt;/code&gt; looks something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;### CURRENT SITUATION
You are a research assistant. Here is what you know right now:
{context_from_shared_store}

### AVAILABLE ACTIONS
[1] search_web(query: str)
  Description: Search for new information online.
[2] write_file(filename: str, content: str)
  Description: Save information to a file.
[3] finish_task(reason: str)
  Description: Complete the mission because the goal is met.

## YOUR NEXT MOVE
Based **only** on the CURRENT SITUATION, choose the single best action to take next and provide the required parameters.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The content of our &lt;code&gt;shared&lt;/code&gt; dictionary—our cognitive backpack—is dropped directly into the &lt;code&gt;{context_from_shared_store}&lt;/code&gt; placeholder. This is the &lt;strong&gt;only thing the LLM sees&lt;/strong&gt;. Its entire universe of knowledge for making a decision is contained within that block.&lt;/p&gt;

&lt;p&gt;This reveals a critical truth: &lt;strong&gt;the quality of the agent's decisions is 100% dependent on the quality of the information in the &lt;code&gt;shared&lt;/code&gt; store.&lt;/strong&gt; This simple dictionary is the most important part of the agent's "brain." So, the central question becomes: what is the right way to manage it?&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The Overwhelmed Warrior: Why Dumping All Context Fails
&lt;/h2&gt;

&lt;p&gt;Imagine our warrior at the dungeon entrance. We, as the benevolent master, decide to "help" by giving them &lt;em&gt;everything&lt;/em&gt;. We cram the entire 500-page dungeon history, every blueprint, every monster's family tree, and a transcript of every conversation ever had about the dungeon into their backpack. "Good luck!" we say, as the warrior stumbles forward, unable to even lift their sword under the crushing weight.&lt;/p&gt;

&lt;p&gt;They enter the first room, which has a simple pressure plate on the floor. To solve it, they need to find the small, one-ounce stone they picked up just a moment ago. But to find it, they have to rummage through the 200-pound bag of useless junk we gave them. They get distracted by a map of a different dungeon wing, start reading about the goblin king's third cousin, and forget about the pressure plate entirely. They are paralyzed by information overload.&lt;/p&gt;

&lt;p&gt;This is &lt;em&gt;exactly&lt;/em&gt; what happens when you dump your entire &lt;code&gt;shared&lt;/code&gt; history into an LLM's prompt on every turn. A cluttered backpack doesn't create a genius warrior; it creates an ineffective one. Here’s why:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Diluted Attention:&lt;/strong&gt; LLMs have a finite attention span. When you give them a massive context, the critical piece of information—the "signal"—gets lost in a sea of irrelevant data—the "noise." The model might struggle to find the single most important fact ("the user just asked to search for &lt;em&gt;this&lt;/em&gt;") when it's buried in ten pages of previous search results. This is often called the "lost in the middle" problem, where information in the center of a large prompt is frequently ignored.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Sky-High Costs &amp;amp; Latency:&lt;/strong&gt; Every token in your prompt costs money and processing time. A cluttered backpack slows your warrior to a crawl and empties your coin purse. An agent that sends a 100,000-token context on every loop is not only breathtakingly expensive but also painfully slow, making any real-time interaction impossible.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Increased Hallucinations:&lt;/strong&gt; When an LLM is given too much loosely related information, it starts to "cross the wires." It might grab a detail from an early, now-irrelevant step and incorrectly apply it to the current situation. It's the equivalent of our warrior trying to use a recipe for a health potion to disarm a magical trap—a confident but catastrophically wrong decision.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The takeaway is simple: &lt;strong&gt;a bigger context does not equal a smarter agent.&lt;/strong&gt; Our goal is not to build the biggest backpack, but the most efficient one. We need to stop being hoarders and start being strategists, ensuring our warrior carries only what they need for the immediate fight.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Forging the Cognitive Backpack: Three Master Navigation Techniques
&lt;/h2&gt;

&lt;p&gt;If stuffing everything into the backpack is the path to failure, what is the path to victory? The answer lies in transforming the backpack from a static, heavy burden into a dynamic, intelligent system. A master warrior doesn't carry every tool for every possible situation. They carry a few versatile tools that allow them to adapt to &lt;em&gt;any&lt;/em&gt; situation.&lt;/p&gt;

&lt;p&gt;Here are the three master techniques for forging your agent's cognitive backpack.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.1. Technique #1: The Scrying Spell (Context On-Demand)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The Metaphor:&lt;/strong&gt; Our warrior enters the dungeon with a nearly empty backpack. Instead of a map, they carry a magical "scrying orb." When they reach a fork in the path, they don't guess. They hold up the orb and ask, "What lies down the left corridor?" The orb shows them a brief vision of the next room. They now have a small, relevant piece of information. They add this "vision" to their mental map and then use the orb again to scout the right corridor. They build their map piece by piece, only gathering the information they need, precisely when they need it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Pattern:&lt;/strong&gt; This is an &lt;strong&gt;exploratory, on-demand&lt;/strong&gt; context strategy. Instead of feeding the agent a massive, pre-filled context, you give it the tools to build its own context. The agent's primary actions are not to solve the final problem, but to ask questions about the environment. The &lt;code&gt;shared&lt;/code&gt; store starts small and grows incrementally, populated only by the answers to the agent's own, self-directed inquiries. It's a &lt;strong&gt;pull model&lt;/strong&gt; of information gathering, not a push model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Concrete Example: The AI Data Analyst&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Imagine an agent tasked with: "Find the total revenue from our top 5 customers last quarter."&lt;/p&gt;

&lt;p&gt;A naive approach would be to dump the entire database schema into the initial prompt. The Scrying Spell approach is far more elegant:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Loop 1: Assess the Landscape&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;shared&lt;/code&gt; store:&lt;/strong&gt; &lt;code&gt;{"goal": "Find revenue of top 5 customers last quarter"}&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;DecideNode&lt;/code&gt; thinks:&lt;/strong&gt; "I have no idea what tables are in the database. I need to look."&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Action:&lt;/strong&gt; It calls &lt;code&gt;list_tables()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Update &lt;code&gt;shared&lt;/code&gt;:&lt;/strong&gt; The list of tables (&lt;code&gt;['customers', 'orders', 'products']&lt;/code&gt;) is added to the &lt;code&gt;shared&lt;/code&gt; store.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Loop 2: Zoom in on a Clue&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;shared&lt;/code&gt; store:&lt;/strong&gt; Now contains the goal and the list of tables.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;DecideNode&lt;/code&gt; thinks:&lt;/strong&gt; "Okay, 'orders' and 'customers' seem relevant. I need to know what columns are in the 'orders' table to find revenue and dates."&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Action:&lt;/strong&gt; It calls &lt;code&gt;get_table_schema(table_name='orders')&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Update &lt;code&gt;shared&lt;/code&gt;:&lt;/strong&gt; The schema for the &lt;code&gt;orders&lt;/code&gt; table (&lt;code&gt;{'order_id': 'INT', 'customer_id': 'INT', 'order_date': 'DATE', 'total_amount': 'DECIMAL'}&lt;/code&gt;) is added.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Loop 3: Formulate the Attack&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;shared&lt;/code&gt; store:&lt;/strong&gt; Contains the goal, table names, and the &lt;code&gt;orders&lt;/code&gt; schema.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;DecideNode&lt;/code&gt; thinks:&lt;/strong&gt; "Perfect. Now I have everything I need to write a precise SQL query to get the answer."&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Action:&lt;/strong&gt; It calls &lt;code&gt;execute_sql(query="SELECT ...")&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Notice the difference. The context was never overwhelming. It was built intelligently, step-by-step, by the agent itself. We didn't give it a map; we gave it a scrying orb and trusted it to find its own way.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.2. Technique #2: The Grand Strategy (Map-Reduce)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The Metaphor:&lt;/strong&gt; The warrior now faces not a dungeon, but an entire fortress. Trying to map it room by room would take forever. Instead, they first send a hawk into the sky. The hawk returns with a high-level sketch of the fortress: the barracks, the keep, and the treasury (&lt;code&gt;Map&lt;/code&gt; phase). The warrior decides to tackle the keep first. They leave the main map behind and take &lt;em&gt;only the detailed blueprint of the keep&lt;/em&gt; with them (&lt;code&gt;Subtask Execution&lt;/code&gt;). After conquering the keep and taking its treasure, they return to the starting point, drop off the loot, and then take &lt;em&gt;only the blueprint for the treasury&lt;/em&gt; for their next mission. Once all wings are cleared, they have all the treasure in one place (&lt;code&gt;Reduce&lt;/code&gt; phase).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Pattern:&lt;/strong&gt; This is the classic &lt;strong&gt;divide-and-conquer&lt;/strong&gt; strategy, perfectly suited for tasks that are too large for a single context window. The process is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Map:&lt;/strong&gt; A high-level planning step where the agent breaks a large problem down into smaller, independent subtasks or "chapters."&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Subtask Execution (in parallel or sequence):&lt;/strong&gt; For each subtask, the agent is run with a &lt;strong&gt;hermetically sealed context&lt;/strong&gt;. It is &lt;em&gt;only&lt;/em&gt; given the information relevant to that one subtask, completely ignorant of the others. This keeps the context small and focused.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Reduce:&lt;/strong&gt; A final step where the results from all the independent subtasks are gathered and synthesized into a final, coherent output.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Concrete Example: The AI Codebase Knowledge Builder&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the exact strategy used in our &lt;a href="https://github.com/The-Pocket/Tutorial-Codebase-Knowledge/" rel="noopener noreferrer"&gt;Codebase Knowledge Builder&lt;/a&gt; project, which turns an entire GitHub repository into a friendly tutorial. Stuffing a whole codebase into a prompt is impossible. Here's how the Grand Strategy makes it work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Map Phase: The Hawk's View&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  The &lt;code&gt;IdentifyAbstractions&lt;/code&gt; and &lt;code&gt;OrderChapters&lt;/code&gt; nodes act as the hawk. They scan the file structure and code at a high level (without reading every line) to create a plan.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;shared&lt;/code&gt; store output:&lt;/strong&gt; A list of core concepts and a recommended chapter order, like: &lt;code&gt;["1. BaseNode", "2. Flow", "3. SharedMemory"]&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Subtask Execution Phase: Conquering the Keep&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  The &lt;code&gt;WriteChapters&lt;/code&gt; &lt;code&gt;BatchNode&lt;/code&gt; in PocketFlow executes this phase perfectly. It iterates through the plan.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;For Chapter 1 ("BaseNode"):&lt;/strong&gt; Its &lt;code&gt;prep&lt;/code&gt; method intelligently scans the &lt;code&gt;shared['codebase']&lt;/code&gt; and gathers &lt;em&gt;only the code files relevant to &lt;code&gt;BaseNode&lt;/code&gt;&lt;/em&gt;. It then calls the LLM with a tiny, focused prompt: "Write a chapter on BaseNode using &lt;em&gt;only this specific code&lt;/em&gt;." The LLM is completely unaware of the code for &lt;code&gt;Flow&lt;/code&gt; or &lt;code&gt;SharedMemory&lt;/code&gt;, preventing confusion.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;For Chapter 2 ("Flow"):&lt;/strong&gt; The process repeats, but this time with a &lt;em&gt;completely different, isolated context&lt;/em&gt; containing only the code relevant to &lt;code&gt;Flow&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Reduce Phase: Gathering the Loot&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  The &lt;code&gt;CombineTutorial&lt;/code&gt; node acts as the final organizer. It takes all the individually written chapter outputs from the &lt;code&gt;shared&lt;/code&gt; store (which now contains the completed text for each chapter) and assembles them into a single, polished tutorial document with a table of contents and navigation.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Without this strategy, the task would be impossible. With it, we can conquer a fortress of any size, one well-planned, focused assault at a time.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.3. Technique #3: The Cautious Explorer (Backtracking with Verification)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The Metaphor:&lt;/strong&gt; Our warrior enters a room with a suspicious-looking lever. A reckless warrior pulls it and hopes for the best. A cautious warrior pulls it (&lt;code&gt;Apply Change&lt;/code&gt;), but keeps one foot in the doorway, ready to jump back. They listen intently. Do they hear the satisfying &lt;em&gt;click&lt;/em&gt; of a hidden door opening, or the terrifying &lt;em&gt;rumble&lt;/em&gt; of a ceiling collapse? (&lt;code&gt;Verify&lt;/code&gt;). If it's the rumble, they immediately let go of the lever, which springs back into place (&lt;code&gt;Revert Change&lt;/code&gt;), and they proceed to look for a different solution. They are allowed to make mistakes, as long as they can observe the consequences and undo them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Pattern:&lt;/strong&gt; This is a &lt;strong&gt;trial-and-error with a safety net&lt;/strong&gt; strategy, essential for agents that modify their environment, like coding agents. The flow is cyclical:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Apply Change:&lt;/strong&gt; The agent performs an action that alters the state (e.g., writes to a file).&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Verify:&lt;/strong&gt; A special node captures the &lt;em&gt;consequence&lt;/em&gt; of that action. This isn't just the action's output; it's an observation of the new world state (e.g., a &lt;code&gt;git diff&lt;/code&gt;, linter output, or a failing test result).&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Decide &amp;amp; Revert:&lt;/strong&gt; This verification result is fed back to the &lt;code&gt;DecideNode&lt;/code&gt;. The LLM is then prompted: "You tried X, and the result was Y. Was this successful? If not, should we revert and try something else?" If it decides to revert, a special action restores the previous state.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Concrete Example: The AI Coding Agent&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Imagine an agent tasked with fixing a bug in a Python file.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Loop 1: The Attempt&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;DecideNode&lt;/code&gt; thinks:&lt;/strong&gt; "Based on the bug report, I think the error is on line 52. I will change &lt;code&gt;x &amp;gt; 5&lt;/code&gt; to &lt;code&gt;x &amp;gt;= 5&lt;/code&gt;."&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Action:&lt;/strong&gt; The &lt;code&gt;write_file&lt;/code&gt; node modifies the Python file.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Loop 2: The Verification&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  The &lt;code&gt;post&lt;/code&gt; hook of the &lt;code&gt;write_file&lt;/code&gt; node is a special &lt;code&gt;verify_code&lt;/code&gt; function. It doesn't just return "success." It runs the project's linter and unit tests.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Verification Result:&lt;/strong&gt; The linter passes, but a unit test now fails with a new &lt;code&gt;AssertionError&lt;/code&gt;. This full error message is the output.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Update &lt;code&gt;shared&lt;/code&gt;:&lt;/strong&gt; The &lt;code&gt;shared&lt;/code&gt; store is updated with: &lt;code&gt;{"last_attempt": "Changed line 52 to x &amp;gt;= 5", "verification_log": "Linter: OK. Tests: FAILED - AssertionError: Test case for x=5 failed."}&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Loop 3: The Reassessment&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  The &lt;code&gt;DecideNode&lt;/code&gt; sees the previous attempt and the failed test.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;DecideNode&lt;/code&gt; thinks:&lt;/strong&gt; "My last change was wrong. It broke a different test case. I must revert the change and try a different approach. The logic must be more complex than a simple comparison."&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Action:&lt;/strong&gt; It calls &lt;code&gt;revert_last_change()&lt;/code&gt;, followed by a new &lt;code&gt;write_file&lt;/code&gt; with a completely different solution.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;This loop of &lt;code&gt;Apply -&amp;gt; Verify -&amp;gt; Revert&lt;/code&gt; allows the agent to safely explore the solution space without permanently breaking things. It can make hypotheses, test them, and backtrack if they prove false—a much more robust and realistic way to solve complex problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Conclusion: A Smart Warrior Navigates, a Fool Memorizes
&lt;/h2&gt;

&lt;p&gt;And so, the secrets of the dungeon are yours. We've moved beyond simply forging a powerful warrior; we've now taught it how to navigate the most treacherous and complex environments. You now understand that an agent's true intelligence isn't measured by the size of its brain (the LLM) or the sharpness of its weapons (the action space), but by the wisdom of its &lt;strong&gt;context management&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A cluttered, unmanaged &lt;code&gt;shared&lt;/code&gt; store—our warrior's cognitive backpack—is a recipe for a slow, confused, and expensive agent. But a well-managed one is the key to a focused, efficient, and surprisingly clever digital warrior.&lt;/p&gt;

&lt;p&gt;You've learned the three master navigation techniques, transforming you from a mere agent blacksmith into a grand strategist:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;The Scrying Spell (Context On-Demand):&lt;/strong&gt; The ultimate tool for exploration, allowing your agent to build its own map of the unknown, piece by piece, without ever getting overwhelmed.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;The Grand Strategy (Map-Reduce):&lt;/strong&gt; Your weapon against overwhelming complexity, enabling your agent to conquer massive challenges like entire codebases by breaking them down into small, focused, and manageable battles.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;The Cautious Explorer (Backtracking with Verification):&lt;/strong&gt; The safety net that empowers your agent to make bold moves and try new things, secure in the knowledge that it can observe the consequences and gracefully retreat from any dead ends.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The next time you build an agent, don't just ask, "What can it do?" Instead, ask, "How will it think? How will it manage its focus?" By thoughtfully designing your agent's cognitive backpack, you are no longer just coding a workflow; you are imparting wisdom. You are creating a smart warrior that doesn't just memorize the map, but navigates the dungeon with purpose, clarity, and skill.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Ready to forge your own intelligent navigators? Dive into the code, experiment with these context strategies, and see how a well-managed backpack can transform your agents. Check out &lt;a href="https://github.com/the-pocket/PocketFlow" rel="noopener noreferrer"&gt;PocketFlow on GitHub&lt;/a&gt; and start building smarter today!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>programming</category>
      <category>python</category>
      <category>beginners</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>I just wrote a tutorial on the most overlooked part of building powerful LLM agents: the Action Space. https://dev.to/zachary62/llm-agents-arsenal-a-beginners-guide-to-the-action-space-n75</title>
      <dc:creator>Zachary Huang</dc:creator>
      <pubDate>Tue, 01 Jul 2025 05:00:27 +0000</pubDate>
      <link>https://dev.to/zachary62/i-just-wrote-a-tutorial-on-the-most-overlooked-part-of-building-powerful-llm-agents-the-action-m49</link>
      <guid>https://dev.to/zachary62/i-just-wrote-a-tutorial-on-the-most-overlooked-part-of-building-powerful-llm-agents-the-action-m49</guid>
      <description></description>
    </item>
    <item>
      <title>LLM Agent's Arsenal: A Beginner's Guide to the Action Space</title>
      <dc:creator>Zachary Huang</dc:creator>
      <pubDate>Tue, 01 Jul 2025 04:44:52 +0000</pubDate>
      <link>https://dev.to/zachary62/llm-agents-arsenal-a-beginners-guide-to-the-action-space-n75</link>
      <guid>https://dev.to/zachary62/llm-agents-arsenal-a-beginners-guide-to-the-action-space-n75</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Ever sent your AI agent into the "battle" of a complex task, only to watch it fumble with a blunt sword or use the wrong weapon for the fight? When an agent fails, our first instinct is to blame its "brain" (the LLM). But the real culprit is often the arsenal we equipped it with—the collection of weapons was dull, confusing, or simply not right for the job.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In our previous tutorial, &lt;a href="https://pocketflow.substack.com/p/llm-agent-internal-as-a-graph-tutorial" rel="noopener noreferrer"&gt;LLM Agents are simply Graph — Tutorial For Dummies&lt;/a&gt;, we revealed that every agent is like a warrior following a simple battle plan: &lt;code&gt;Assess -&amp;gt; Strike -&amp;gt; Repeat&lt;/code&gt;. We showed how the 'assessing' happens in a decision node that plans the next move. Now, it's time to forge the weapons used for the &lt;strong&gt;Strike&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That 'Strike' is powered by the agent's &lt;strong&gt;Arsenal&lt;/strong&gt;—the official set of weapons, tools, and spells it can draw upon. In technical terms, this is its &lt;strong&gt;Action Space&lt;/strong&gt;. This isn't just a list of functions; it is the very soul of your agent's power. A well-forged arsenal, where every blade is sharp and serves a unique purpose, is the difference between an agent that is defeated by the first obstacle and one that conquers any challenge.&lt;/p&gt;

&lt;p&gt;In this guide, you are the master blacksmith. Using the transparent and powerful &lt;a href="https://github.com/The-Pocket/PocketFlow" rel="noopener noreferrer"&gt;&lt;strong&gt;PocketFlow&lt;/strong&gt;&lt;/a&gt; framework as your forge, we will teach you how to craft an arsenal of actions that will turn your agent from a clumsy squire into a legendary warrior.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The Battle Tactician: How an Agent Chooses Its Weapon&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;So, we have an arsenal. But how does the agent, our digital warrior, know when to draw a longsword for a close-quarters fight versus firing a bow from a distance?&lt;/p&gt;

&lt;p&gt;This critical decision happens in the &lt;strong&gt;&lt;code&gt;DecideAction&lt;/code&gt; node&lt;/strong&gt;—the agent's battle tactician. At its core, every agent is just a simple loop that consults its tactician, who then chooses an action from the arsenal. The chosen action is performed, and the results are reported back to the tactician to plan the next move.&lt;/p&gt;

&lt;p&gt;Visually, the battle plan looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbnfcxxdr2e1pt5wcs604.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbnfcxxdr2e1pt5wcs604.png" alt="Image description" width="800" height="266"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this diagram:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;&lt;code&gt;DecideAction&lt;/code&gt; (The Tactician):&lt;/strong&gt; This is the brain. It analyzes the battlefield (the user's request and current data).&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;The Arrows (The Commands):&lt;/strong&gt; Based on its analysis, the tactician issues a command: &lt;code&gt;search_web&lt;/code&gt;, &lt;code&gt;write_file&lt;/code&gt;, or &lt;code&gt;answer_question&lt;/code&gt;. This is the branch in the graph.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;The Action Nodes (The Specialists):&lt;/strong&gt; Each command goes to a specialist soldier who executes that one task.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;The Loop Back (The Report):&lt;/strong&gt; After the specialist completes their task, they report back to the tactician with new information, and the cycle begins again.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;"But what magic happens inside that &lt;code&gt;DecideAction&lt;/code&gt; node?" you ask. "How does it &lt;em&gt;actually&lt;/em&gt; think?"&lt;/p&gt;

&lt;p&gt;This is the most misunderstood part of agent design, and the secret is shockingly simple. &lt;strong&gt;It's just a prompt.&lt;/strong&gt; There's no complex algorithm, just a carefully written set of instructions for the LLM.&lt;/p&gt;

&lt;p&gt;The tactician's "brain" is a prompt that looks something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;### CONTEXT
You are a research assistant. Here is the current situation:
Question: {the user's original question}
Previous Actions: {a log of what has been done so far}
Current Information: {any data gathered from previous actions}

### ARSENAL (Available Actions)
Here are the weapons you can use. Choose one.

[1] search_web
  Description: Search the internet for up-to-date information.
  Parameters:
    - query (str): The specific topic to search for.

[2] write_file
  Description: Save text into a local file.
  Parameters:
    - filename (str): The name of the file to create.
    - content (str): The text content to write into the file.

[3] answer_question
  Description: Provide the final answer to the user.
  Parameters:
    - answer (str): The complete, final answer.

## YOUR NEXT COMMAND
Review the CONTEXT and choose the single best ACTION from your ARSENAL to proceed.
Format your response as a YAML block.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it! The agent's entire decision-making process boils down to this: the LLM reads the description of the situation and the "user manual" for every weapon in its arsenal, and then it picks the one that makes the most sense.&lt;/p&gt;

&lt;p&gt;The quality of its choice is &lt;strong&gt;100% dependent on how clearly you describe its weapons.&lt;/strong&gt; A sharp, well-defined arsenal in your prompt leads to a smart, effective agent. A vague, confusing one leads to a warrior who brings a knife to a dragon fight.&lt;/p&gt;

&lt;p&gt;Now, let's learn how to forge these weapons, from simple daggers to god-tier magic spells.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Level Up Your Arsenal: The Three Tiers of Weapon Complexity&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;As a master blacksmith, you wouldn't forge just one type of weapon. You need a full range, from simple daggers for quick jabs to powerful, enchanted swords for epic battles. The same is true for your agent's arsenal. Actions can be designed with varying levels of power and complexity. Let's explore the three tiers.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Level 1: The Simple Dagger (The "Button" Action)&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;A simple dagger is a no-frills weapon. You draw it, you use it. It does one thing, and it does it reliably. These are actions that require &lt;strong&gt;no parameters&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Think of them as on/off switches or simple commands.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In the Forge (Code):&lt;/strong&gt;&lt;br&gt;
An action like &lt;code&gt;request_human_help&lt;/code&gt; or &lt;code&gt;finish_task&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In the Arsenal (Prompt Description):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[1] request_human_help
  Description: If you are stuck or need clarification, use this action to pause and ask the human user for guidance.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;When to Use It:&lt;/strong&gt;&lt;br&gt;
For clear, binary decisions. When the agent needs to signal a state change, like "I'm finished," "I'm stuck," or "I've failed." They are perfect for controlling the overall flow of the battle plan.&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;Level 2: The Sharpshooter's Bow (The Parameterized Tool)&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;A bow is useless without an arrow and a target. This weapon requires input to be effective. These are the most common and versatile actions in an agent's arsenal—actions that require &lt;strong&gt;specific parameters&lt;/strong&gt; to function.&lt;/p&gt;

&lt;p&gt;To use these weapons, the agent must not only choose the bow but also aim it by providing the correct inputs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In the Forge (Code):&lt;/strong&gt;&lt;br&gt;
An action like &lt;code&gt;search_web(query)&lt;/code&gt; or &lt;code&gt;send_email(to, subject, body)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In the Arsenal (Prompt Description):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[2] search_web
  Description: Searches the public internet for a given text string.
  Parameters:
    - query (str): The precise search term to look up. Must be a focused string.

[3] send_email
  Description: Composes and sends an email to a recipient.
  Parameters:
    - to (str): The email address of the recipient.
    - subject (str): The subject line of the email.
    - body (str): The main content of the email.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The Crucial Link to Your Blacksmithing Skills:&lt;/strong&gt;&lt;br&gt;
How does the agent provide these parameters? This is where your skill in &lt;strong&gt;structured output&lt;/strong&gt; becomes critical. As we covered in our guide, &lt;a href="https://pocketflow.substack.com/p/structured-output-for-beginners-3" rel="noopener noreferrer"&gt;Structured Output for Beginners&lt;/a&gt;, you must instruct the LLM to format its response in a structured way (like YAML or JSON) so your program can easily parse the action &lt;em&gt;and&lt;/em&gt; its parameters.&lt;/p&gt;

&lt;p&gt;Without this skill, you've given your agent a powerful bow but no way to nock an arrow.&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;Level 3: The Spellbook of Creation (The Programmable Action)&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;This is the ultimate weapon: a spellbook that doesn't contain a list of spells but teaches the agent how to &lt;em&gt;write its own&lt;/em&gt;. These are &lt;strong&gt;programmable actions&lt;/strong&gt; where the agent generates code or complex instructions on the fly.&lt;/p&gt;

&lt;p&gt;This gives the agent god-like flexibility to solve novel problems you never explicitly trained it for.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In the Forge (Code):&lt;/strong&gt;&lt;br&gt;
An action like &lt;code&gt;execute_sql(query)&lt;/code&gt; or &lt;code&gt;run_python_code(code)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In the Arsenal (Prompt Description):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[4] execute_sql
  Description: Write and run a SQL query against the company's sales database. The database contains tables named 'customers', 'orders', and 'products'.
  Parameters:
    - sql_query (str): A valid SQL query string to execute.

[5] run_python_code
  Description: Write and execute a sandboxed Python script for complex calculations, data manipulation, or interacting with APIs.
  Parameters:
    - code (str): A string containing the Python code to run.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The Power and the Peril:&lt;/strong&gt;&lt;br&gt;
A spellbook is the most powerful weapon in your arsenal, but it's also the most dangerous.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Power:&lt;/strong&gt; Your agent can solve almost any problem that can be expressed in code. It's no longer limited to pre-defined tools.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Peril:&lt;/strong&gt; It's much more likely to make a mistake (e.g., writing buggy code). More importantly, it opens up massive security risks if not handled carefully (e.g., executing malicious code like &lt;code&gt;os.remove("important_file.txt")&lt;/code&gt;). Always run such code in a secure, sandboxed environment.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Mastering these three tiers allows you to build a balanced and effective arsenal, equipping your agent for any challenge it might face.&lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;strong&gt;Forging the Perfect Arsenal: 3 Golden Rules for Your Weapon Inventory&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;A legendary warrior doesn't just carry a random assortment of weapons. Their arsenal is carefully curated—each item is perfectly crafted, serves a distinct purpose, and is instantly accessible. As the master blacksmith for your agent, you must apply the same discipline. Here are the three golden rules for forging a world-class action space.&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;Golden Rule #1: Engrave a Crystal-Clear User Manual (Clarity is King)&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The descriptions for your actions and their parameters are not notes for yourself; they are the &lt;strong&gt;user manual for the LLM&lt;/strong&gt;. If the manual is vague, the LLM will misuse the tool. Be painfully, relentlessly explicit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A Dull Blade (Bad Description):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;search: searches for stuff
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent sees this and thinks, "What stuff? How? What do I provide?" The result is a wild guess, like &lt;code&gt;search(query="who won the 2024 Nobel Prize in Physics and what were their contributions in detail and also list prior winners")&lt;/code&gt;, a query too broad to be effective.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A Sharpened Katana (Good Description):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;search_web(query: str):
  Description: Searches the public internet for up-to-date information on a single, specific topic. Returns the top 5 text snippets.
  Parameters:
    - query (str): A simple and focused search query, typically 3-5 words long.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the agent understands its constraints. It knows the tool is for &lt;em&gt;one topic&lt;/em&gt; and the query should be &lt;em&gt;short&lt;/em&gt;. It will correctly generate a command like &lt;code&gt;search_web(query: "2024 Nobel Prize Physics winner")&lt;/code&gt;, leading to a much better outcome.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Golden Rule #2: Don't Burden Your Warrior with a Junk Drawer (Keep it Concise)&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;A warrior grabbing a weapon in the heat of battle can't afford to sift through a hundred options. They need a small, elite set of choices. Overwhelming the LLM with too many actions leads to confusion, slower decision-making (more tokens to process), and a higher chance of picking the wrong tool.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The Blacksmith's Guideline:&lt;/strong&gt; An arsenal of &lt;strong&gt;10 weapons is formidable. An arsenal of 100 is a junk drawer.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If your action space is growing too large, it's a sign that your tools are too granular. Instead of creating &lt;code&gt;read_json_file&lt;/code&gt;, &lt;code&gt;read_csv_file&lt;/code&gt;, and &lt;code&gt;read_text_file&lt;/code&gt;, forge a single, more powerful weapon: &lt;code&gt;read_file(filename: str)&lt;/code&gt;. Your code can handle the internal logic of parsing different file types. Keep the agent's choices clean and high-level.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Golden Rule #3: Make Every Weapon Unique (Slay Redundancy)&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Every weapon in the arsenal should have a unique purpose. If the agent has two tools that do similar things, it will get confused about which one to use. This is called a lack of "orthogonality."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Confusing Arsenal (Bad Design):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;read_csv_from_disk(file_path: str)&lt;/code&gt;: Reads customer data from a local CSV file.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;query_database(sql: str)&lt;/code&gt;: Queries the live customer database.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The agent is asked to "find the total sales for new customers from this quarter." Which tool should it use? The data might be in the CSV, or it might be in the database. The agent doesn't know and might make the wrong choice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Pro-Gamer Move: Simplify the Battlefield&lt;/strong&gt;&lt;br&gt;
A true master blacksmith doesn't just forge weapons; they shape the battlefield to their advantage. Instead of giving the agent two ambiguous tools, do the work for it behind the scenes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Decisive Arsenal (Good Design):&lt;/strong&gt;&lt;br&gt;
Before the agent even starts, run a script that &lt;strong&gt;loads the CSV data into a temporary table in the database.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now, the agent's arsenal is clean and unambiguous:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;query_database(sql: str)&lt;/code&gt;: Queries the customer database, which contains all known customer data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The ambiguity is gone. The agent has one, and only one, tool for retrieving customer data. You've eliminated redundancy and made the agent's decision trivial, guaranteeing it makes the right choice every time.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Conclusion: An Agent is Only as Sharp as its Arsenal&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;And so, the secrets of the forge are yours. You now understand that the true power of an LLM agent doesn't come from some mysterious, hidden algorithm. It comes from the thoughtful, disciplined, and creative process of crafting its &lt;strong&gt;Action Space&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You've learned that agents are just warriors in a &lt;strong&gt;loop with branches&lt;/strong&gt;, making decisions based on a prompt that serves as their battle plan. And you've seen how to stock their arsenal for any challenge:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  With &lt;strong&gt;Simple Daggers&lt;/strong&gt; for quick, decisive commands.&lt;/li&gt;
&lt;li&gt;  With &lt;strong&gt;Sharpshooter's Bows&lt;/strong&gt; for precise, targeted actions.&lt;/li&gt;
&lt;li&gt;  With reality-bending &lt;strong&gt;Spellbooks of Creation&lt;/strong&gt; for ultimate flexibility.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most importantly, you now hold the three golden rules of the master blacksmith:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Engrave a Clear Manual:&lt;/strong&gt; Your descriptions are the agent's guide to victory.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Avoid the Junk Drawer:&lt;/strong&gt; A curated, concise arsenal is deadlier than a cluttered one.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Slay Redundancy:&lt;/strong&gt; Make every weapon unique to ensure the agent never hesitates.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The next time you see a complex agent framework with thousands of lines of code, you won't be intimidated. You'll know to look past the noise and ask the fundamental questions: "What's in the arsenal? How is it described? Is it sharp, concise, and unique?"&lt;/p&gt;

&lt;p&gt;Armed with this knowledge, you are no longer just a coder; you are an &lt;strong&gt;agent blacksmith&lt;/strong&gt;. You have the power to forge not just tools, but intelligent, reliable, and effective digital warriors.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Ready to light the forge? Dive into the code and explore these principles in action by checking out &lt;a href="https://github.com/the-pocket/PocketFlow" rel="noopener noreferrer"&gt;PocketFlow on GitHub&lt;/a&gt;!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>programming</category>
      <category>ai</category>
      <category>llm</category>
      <category>python</category>
    </item>
    <item>
      <title>I just wrote a tutorial showing how to build an AI chatbot for your website that just works. https://dev.to/zachary62/the-easiest-way-to-build-an-ai-chatbot-for-your-website-full-dev-tutorial-37kp</title>
      <dc:creator>Zachary Huang</dc:creator>
      <pubDate>Thu, 19 Jun 2025 03:15:20 +0000</pubDate>
      <link>https://dev.to/zachary62/i-just-wrote-a-tutorial-showing-how-to-build-an-ai-chatbot-for-your-website-that-just-works-5eg9</link>
      <guid>https://dev.to/zachary62/i-just-wrote-a-tutorial-showing-how-to-build-an-ai-chatbot-for-your-website-that-just-works-5eg9</guid>
      <description></description>
      <category>ai</category>
      <category>howto</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
    <item>
      <title>The Easiest Way to Build an AI Chatbot for Your Website (Full Dev Tutorial)</title>
      <dc:creator>Zachary Huang</dc:creator>
      <pubDate>Thu, 19 Jun 2025 03:05:57 +0000</pubDate>
      <link>https://dev.to/zachary62/the-easiest-way-to-build-an-ai-chatbot-for-your-website-full-dev-tutorial-37kp</link>
      <guid>https://dev.to/zachary62/the-easiest-way-to-build-an-ai-chatbot-for-your-website-full-dev-tutorial-37kp</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Want to build an AI chatbot for your website, but worried about the complexity? Are you picturing a maintenance nightmare of endless data updates and complex pipelines? Good news. This tutorial shows you how to build a lightweight AI chatbot that learns directly from your live website. No vector databases, no manual updates—just a chatbot that works. The project is &lt;a href="https://github.com/The-Pocket/PocketFlow-Tutorial-Website-Chatbot" rel="noopener noreferrer"&gt;open-sourced on GitHub&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  1. That "Simple" Chatbot Project... Isn't
&lt;/h2&gt;

&lt;p&gt;So, you want to build an AI chatbot for your website. It sounds easy enough. You call an API, write a clever prompt, and you're basically done, right?&lt;/p&gt;

&lt;p&gt;Except for one tiny, soul-crushing detail: Your brand-new AI knows... &lt;em&gt;nothing&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;It has no idea what your company sells, what your return policy is, or who you are. It's just an empty brain in a box. To make it useful, you have to feed it knowledge. And that's where the "simple" project becomes a total nightmare.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Old, Broken Way to Build a Chatbot's Brain
&lt;/h3&gt;

&lt;p&gt;Here’s the standard, painful process everyone seems to follow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The Scavenger Hunt.&lt;/strong&gt; First, you go on a company-wide scavenger hunt, digging through folders and old emails to find every PDF, FAQ, and policy document you can.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The Data Janitor Job.&lt;/strong&gt; Then, you become a data janitor. You write a bunch of tedious scripts to chop all that messy information into clean little "chunks" the AI can understand.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The Expensive Brain Surgery.&lt;/strong&gt; Finally, you perform some expensive brain surgery. You set up a complicated (and often pricey) "vector database" and shove all those data chunks into it.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After all that, you &lt;em&gt;finally&lt;/em&gt; have a chatbot that knows things. For about a day.&lt;/p&gt;

&lt;h3&gt;
  
  
  And Now... Your Chatbot Is a Liar
&lt;/h3&gt;

&lt;p&gt;The moment your bot goes live, it starts to rot.&lt;/p&gt;

&lt;p&gt;The marketing team updates the pricing page. The engineers release a new feature. Suddenly, your chatbot is confidently telling customers the wrong price. It's a walking, talking liability. You didn't build a smart AI assistant. You built a manual-syncing, high-maintenance chore that you have to babysit forever.&lt;/p&gt;

&lt;p&gt;But what if this entire approach is wrong? What if the knowledge base wasn't some clunky database you have to constantly update? What if... &lt;em&gt;the website itself&lt;/em&gt; was the brain? That’s the chatbot we’re building today. A bot so simple, it feels like cheating.&lt;/p&gt;

&lt;p&gt;This project is powered by &lt;a href="https://github.com/the-pocket/PocketFlow" rel="noopener noreferrer"&gt;PocketFlow&lt;/a&gt;, a tiny but mighty AI framework that makes building this kind of intelligent, looping agent incredibly straightforward. Forget vector databases and manual updates. Let's build a chatbot that just works.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Our Solution: A "Dumb" Crawler That's Actually Smart
&lt;/h2&gt;

&lt;p&gt;Let's throw that entire, complicated process in the trash. We are not going to hunt for documents, clean up data, or set up a single database.&lt;/p&gt;

&lt;p&gt;Instead, our chatbot will get its information directly from the source: your live website. Think of it like this. The old way is like printing a map once a year and hoping the roads don't change. Our new way is like using Google Maps on your phone—it's always live, always current.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Master Plan: Let the Bot Read
&lt;/h3&gt;

&lt;p&gt;Our chatbot works like a very fast, very focused intern. When a user asks a question, the bot doesn't look up the answer in some dusty old database. Instead, it visits your website and starts reading, right then and there.&lt;/p&gt;

&lt;p&gt;Let's imagine your website has a realistic structure. A user asks a question that requires information from multiple places: &lt;strong&gt;"How do I get a refund for Product A?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The bot needs to be smart. It has to navigate the site to find &lt;em&gt;all&lt;/em&gt; the relevant pieces of the puzzle. In the diagram below, the lines show all the possible links. The &lt;strong&gt;dashed line&lt;/strong&gt; shows the &lt;em&gt;exact path&lt;/em&gt; our bot takes to find the answer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ferp8aknnhwf95x64kluv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ferp8aknnhwf95x64kluv.png" alt="Image description" width="800" height="384"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's a play-by-play of the bot's clever thought process:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;It starts on the Homepage.&lt;/strong&gt; It sees both "refund" and "Product A" in the question. It decides to find the product page first to confirm the product's details.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;It navigates to the "Product A" page.&lt;/strong&gt; It reads the content and finds key info, like a "30-day warranty," but it doesn't find the &lt;em&gt;process&lt;/em&gt; for actually getting a refund.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;It intelligently changes course.&lt;/strong&gt; It realizes the refund steps aren't on the product page. So, it thinks like a human would: "Okay, I need to find the general company policies." It navigates back to the site's main "Support" section to find the official information. It doesn't need a direct link; it understands the site's structure.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;It finds the final piece of the puzzle.&lt;/strong&gt; On the Support page, it sees a link to "Shipping &amp;amp; Returns Policy," reads it, and learns the exact steps to submit a refund request.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now, it combines the "30-day warranty" from the product page with the "how-to steps" from the returns policy to give a perfect, comprehensive answer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why This is So Much Better
&lt;/h3&gt;

&lt;p&gt;The beauty of this approach is its simplicity.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Your Knowledge is Always Fresh:&lt;/strong&gt; You change your pricing? The bot knows instantly. You update your team bio? The bot knows that too. There is no sync step. There is no "stale data." Ever.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;There is Zero Maintenance:&lt;/strong&gt; You never have to tell the bot about updates. Just update your website like you normally would, and the chatbot takes care of the rest.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But what stops it from wandering off your site and crawling the entire internet? Simple. We give it a leash. We provide a list of approved website domains (like &lt;code&gt;yourwebsite.com&lt;/code&gt;) and tell it: "You are only allowed to visit links on these sites. Don't go anywhere else."&lt;/p&gt;

&lt;p&gt;This all sounds great, but building an agent that can make decisions and get stuck in a loop sounds complicated, right? You'd think you need a massive, heavy framework to manage that kind of logic.&lt;/p&gt;

&lt;p&gt;Actually, you don't. And that’s where PocketFlow comes in.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. PocketFlow: The Tiny Engine That Powers Our Bot
&lt;/h2&gt;

&lt;p&gt;You wouldn't use a bulldozer to plant a single flower. In the same way, we don't need a massive, heavyweight AI framework for our straightforward crawling task. We need something small, fast, and built for exactly this kind of job.&lt;/p&gt;

&lt;p&gt;That's why we're using &lt;strong&gt;&lt;a href="https://github.com/the-pocket/PocketFlow" rel="noopener noreferrer"&gt;PocketFlow&lt;/a&gt;&lt;/strong&gt;. PocketFlow is a minimalist AI framework that's just 100 lines of code. It has zero dependencies and zero fluff. Let's look at its three core ideas.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Node: A Specialist Worker
&lt;/h3&gt;

&lt;p&gt;In PocketFlow, each task is a &lt;strong&gt;Node&lt;/strong&gt;. A Node is like a specialist worker who is a pro at &lt;em&gt;one specific thing&lt;/em&gt;. Here’s what a Node looks like in the actual PocketFlow code:&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;class&lt;/span&gt; &lt;span class="nc"&gt;BaseNode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;successors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;prep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;pass&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prep_res&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;pass&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prep_res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exec_res&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;pass&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don't worry if &lt;code&gt;__init__&lt;/code&gt; or &lt;code&gt;self&lt;/code&gt; look weird; they're just Python things! The important bit is the &lt;code&gt;prep -&amp;gt; exec -&amp;gt; post&lt;/code&gt; cycle:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;code&gt;prep(shared)&lt;/code&gt;: "Hey, I'm about to start. What info do I need from the &lt;code&gt;shared&lt;/code&gt; whiteboard?"&lt;/li&gt;
&lt;li&gt; &lt;code&gt;exec(data_from_prep)&lt;/code&gt;: "Okay, I have my info. Now I'll do my main job!" (Like calling an AI).&lt;/li&gt;
&lt;li&gt; &lt;code&gt;post(shared, ..., ...)&lt;/code&gt;: "Job's done! I'll write my results to the &lt;code&gt;shared&lt;/code&gt; whiteboard and tell the manager what to do next by returning a signal (like a keyword, e.g., &lt;code&gt;"explore"&lt;/code&gt; or &lt;code&gt;"answer"&lt;/code&gt;)."&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The Shared Store: The Central Whiteboard
&lt;/h3&gt;

&lt;p&gt;This is just a plain old Python dictionary (we'll call it &lt;code&gt;shared&lt;/code&gt;). All our Node workers can read from it and write to it. It's how they pass information—like the user's question or the list of URLs to visit—to each other.&lt;/p&gt;

&lt;p&gt;For our chatbot, it might look like this initially:&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;shared&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;user_question&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;How do I get a refund?&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;urls_to_process&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://example.com&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;visited_urls&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;final_answer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As Nodes do their work, they'll update this &lt;code&gt;shared&lt;/code&gt; dictionary.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Flow: The Workshop Manager
&lt;/h3&gt;

&lt;p&gt;A &lt;code&gt;Flow&lt;/code&gt; object is the manager of your workshop. You tell it which Node to start with, and it handles the rest. When you &lt;code&gt;run&lt;/code&gt; a Flow, it just keeps doing one thing over and over:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Run the current Node.&lt;/li&gt;
&lt;li&gt; The Node finishes and returns a &lt;em&gt;signal&lt;/em&gt; (just a string, like &lt;code&gt;"explore"&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt; The Flow looks at the Node's connections to see where that signal leads, and moves to the next Node.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here's how tiny the &lt;code&gt;Flow&lt;/code&gt; manager class actually is in PocketFlow:&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;class&lt;/span&gt; &lt;span class="nc"&gt;Flow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseNode&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; 
        &lt;span class="c1"&gt;# ... a bit of init code ...
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;orch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;curr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;curr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# ... a bit of setup code ...
&lt;/span&gt;            &lt;span class="n"&gt;signal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;curr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;curr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;curr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;successors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;signal&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;default&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it! It starts a &lt;code&gt;while&lt;/code&gt; loop, runs a node, gets a signal, and finds the next node. If there's no next node for that signal, the loop ends.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tiny Math Example: PocketFlow in Action!
&lt;/h3&gt;

&lt;p&gt;Let's build a super-tiny workflow: take a number, add 5, then multiply by 2.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Worker 1: The Adder Node&lt;/strong&gt;&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;class&lt;/span&gt; &lt;span class="nc"&gt;AddFive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseNode&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;prep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shared&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;shared&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;number_to_process&lt;/span&gt;&lt;span class="sh"&gt;"&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="c1"&gt;# Read from whiteboard
&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;current_number&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;current_number&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="c1"&gt;# Do the work
&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prep_res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;addition_result&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;intermediate_result&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;addition_result&lt;/span&gt; &lt;span class="c1"&gt;# Write to whiteboard
&lt;/span&gt;        &lt;span class="nf"&gt;print&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;AddFive Node: Added 5, result is &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;addition_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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Notice &lt;code&gt;post&lt;/code&gt; doesn't return anything? PocketFlow automatically treats that as the signal &lt;code&gt;"default"&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Worker 2: The Multiplier Node&lt;/strong&gt;&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;class&lt;/span&gt; &lt;span class="nc"&gt;MultiplyByTwo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseNode&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;prep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shared&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;shared&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;intermediate_result&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;# Read from whiteboard
&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;current_number&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;current_number&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="c1"&gt;# Do the work
&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prep_res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;multiplication_result&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;final_answer&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;multiplication_result&lt;/span&gt; &lt;span class="c1"&gt;# Write to whiteboard
&lt;/span&gt;        &lt;span class="nf"&gt;print&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;MultiplyByTwo Node: Multiplied, final answer is &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;multiplication_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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Connecting the Workers and Running the Flow:&lt;/strong&gt;&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="c1"&gt;# Create our specialist worker Nodes
&lt;/span&gt;&lt;span class="n"&gt;adder_node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AddFive&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;multiplier_node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MultiplyByTwo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Connect them: after adder_node is done, run multiplier_node
&lt;/span&gt;&lt;span class="n"&gt;adder_node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_successor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;multiplier_node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 

&lt;span class="c1"&gt;# Create the Flow manager
&lt;/span&gt;&lt;span class="n"&gt;math_flow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Flow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;adder_node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Create the whiteboard with our starting number
&lt;/span&gt;&lt;span class="n"&gt;shared_math_data&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;number_to_process&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Starting math game with: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;shared_math_data&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="c1"&gt;# Run the flow!
&lt;/span&gt;&lt;span class="n"&gt;math_flow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shared_math_data&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Math game finished. Whiteboard looks like: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;shared_math_data&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you run this, you get exactly what you'd expect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Starting math game with: {'number_to_process': 10}
AddFive Node: Added 5, result is 15
MultiplyByTwo Node: Multiplied, final answer is 30
Math game finished. Whiteboard looks like: {'number_to_process': 10, 'intermediate_result': 15, 'final_answer': 30}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See? Each Node is simple. The &lt;code&gt;shared&lt;/code&gt; dictionary carries the data. The &lt;code&gt;Flow&lt;/code&gt; manager makes sure &lt;code&gt;AddFive&lt;/code&gt; runs, then &lt;code&gt;MultiplyByTwo&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now, just swap our math workers for chatbot workers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;AddFive&lt;/code&gt; becomes &lt;code&gt;CrawlAndExtract&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;MultiplyByTwo&lt;/code&gt; becomes &lt;code&gt;AgentDecision&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;  And instead of just a &lt;code&gt;"default"&lt;/code&gt; signal, &lt;code&gt;AgentDecision&lt;/code&gt; will return &lt;code&gt;"explore"&lt;/code&gt; to loop back or &lt;code&gt;"answer"&lt;/code&gt; to move forward.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The pattern is exactly the same. Now that we have our blueprint, let's build the three "workers" that make our chatbot come to life.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Building the Brain: A Look Under the Hood
&lt;/h2&gt;

&lt;p&gt;Alright, theory's over. Let's look at the actual code that makes our chatbot's brain tick. By the end of this section, you'll understand the entire backend, from the high-level workflow down to the individual "workers."&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Note: We've simplified the code below to focus on the core ideas. For the complete, unabridged version, you can view the full code in the &lt;a href="https://github.com/The-Pocket/Website-AI-Chatbot" rel="noopener noreferrer"&gt;project on GitHub&lt;/a&gt;.)&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Game Plan
&lt;/h3&gt;

&lt;p&gt;First, let's look at our workflow diagram. This is the entire brain of our operation: a simple loop.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4vacqyqtwytj7683i6h3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4vacqyqtwytj7683i6h3.png" alt="Image description" width="800" height="138"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Assembly Line Instructions (&lt;code&gt;flow.py&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;Before we build the individual workers, let's look at the instructions that tell them how to work together. This is our &lt;code&gt;flow.py&lt;/code&gt; file, and it's the "manager" that directs the assembly line.&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="c1"&gt;# From flow.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pocketflow&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flow&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;nodes&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;CrawlAndExtract&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AgentDecision&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DraftAnswer&lt;/span&gt;

&lt;span class="c1"&gt;# 1. Create an instance of each "worker" node
&lt;/span&gt;&lt;span class="n"&gt;crawl_node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CrawlAndExtract&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;agent_node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AgentDecision&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;draft_answer_node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;DraftAnswer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# 2. Define the connections and signals
&lt;/span&gt;&lt;span class="n"&gt;crawl_node&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;agent_node&lt;/span&gt;                 &lt;span class="c1"&gt;# After crawling, always go to the agent to decide.
&lt;/span&gt;&lt;span class="n"&gt;agent_node&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;explore&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;crawl_node&lt;/span&gt;     &lt;span class="c1"&gt;# If the agent says "explore", loop back to the crawler.
&lt;/span&gt;&lt;span class="n"&gt;agent_node&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;answer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;draft_answer_node&lt;/span&gt; &lt;span class="c1"&gt;# If the agent says "answer", move to the writer.
&lt;/span&gt;
&lt;span class="c1"&gt;# 3. Create the final flow, telling it where to start
&lt;/span&gt;&lt;span class="n"&gt;support_bot_flow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Flow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;crawl_node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the entire orchestration logic. It's a simple, readable blueprint for our agent's behavior.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Shared Whiteboard (&lt;code&gt;shared&lt;/code&gt; dictionary)
&lt;/h3&gt;

&lt;p&gt;Next, our workers need a central place to read and write information. This is just a simple Python dictionary that holds the state of our operation.&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;shared&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;user_question&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;How do I get a refund?&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;urls_to_process&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="c1"&gt;# A "to-do" list of URL indices to crawl
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;visited_urls&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;  &lt;span class="c1"&gt;# A set of URL indices it has already crawled
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url_content&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="c1"&gt;# Where it stores the text from each URL
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;all_discovered_urls&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://example.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="c1"&gt;# Master list of every URL
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;final_answer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Workers (&lt;code&gt;nodes.py&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;Now let's look at the simplified code for our three specialist nodes.&lt;/p&gt;

&lt;h4&gt;
  
  
  1. &lt;code&gt;CrawlAndExtract&lt;/code&gt;: The Librarian
&lt;/h4&gt;

&lt;p&gt;This &lt;code&gt;BatchNode&lt;/code&gt; efficiently processes a list of URLs. Its job is to read a page and return its text and any new links it finds.&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;class&lt;/span&gt; &lt;span class="nc"&gt;CrawlAndExtract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BatchNode&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;prep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shared&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;shared&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;urls_to_process&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url_index&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;all_discovered_urls&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;url_index&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;span class="n"&gt;new_links&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;crawl_webpage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url_index&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;url_index&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="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;new_links&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;new_links&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prep_res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;all_results&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;result&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;all_results&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url_index&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url_content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&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="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;visited_urls&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;idx&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;link_url&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;new_links&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;link_url&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;all_discovered_urls&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
                    &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;all_discovered_urls&lt;/span&gt;&lt;span class="sh"&gt;"&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="n"&gt;link_url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;urls_to_process&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="p"&gt;[]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;In English:&lt;/strong&gt; It crawls each page on its to-do list, stores the content, and adds any new, unique links to the master URL list.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. &lt;code&gt;AgentDecision&lt;/code&gt;: The Brain
&lt;/h4&gt;

&lt;p&gt;This node looks at what we've learned and decides what to do next, returning a signal to the Flow.&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;class&lt;/span&gt; &lt;span class="nc"&gt;AgentDecision&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;prep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;knowledge&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="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url_content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="n"&gt;unvisited_urls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;all_discovered_urls&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;i&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;visited_urls&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="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;question&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;shared&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_question&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;knowledge&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;knowledge&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unvisited_urls&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;unvisited_urls&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prepared_data&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
        User Question: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;prepared_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;question&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;
        Knowledge I have: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;prepared_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;knowledge&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;
        URLs to explore next: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;prepared_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;unvisited_urls&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;

        Should I &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;answer&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; or &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;explore&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;? If exploring, which URLs are best?
        Respond in YAML:
        decision: [answer/explore]
        selected_urls: [...]
        &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="n"&gt;response_yaml&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;call_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="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;parse_yaml&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response_yaml&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prep_res&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="k"&gt;if&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;decision&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;explore&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;selected_indices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;all_discovered_urls&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&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;url&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;selected_urls&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
            &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;urls_to_process&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;selected_indices&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;explore&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="c1"&gt;# This signal matches our flow.py instruction
&lt;/span&gt;        &lt;span class="k"&gt;else&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;answer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="c1"&gt;# This signal also matches flow.py
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;In English:&lt;/strong&gt; It asks the AI for a strategy (&lt;code&gt;answer&lt;/code&gt; or &lt;code&gt;explore&lt;/code&gt;) and returns that exact signal to the Flow, which knows what to do next.&lt;/p&gt;

&lt;h4&gt;
  
  
  3. &lt;code&gt;DraftAnswer&lt;/code&gt;: The Writer
&lt;/h4&gt;

&lt;p&gt;Once the Brain says &lt;code&gt;"answer"&lt;/code&gt;, this node crafts the final response.&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;class&lt;/span&gt; &lt;span class="nc"&gt;DraftAnswer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;prep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;knowledge&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="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url_content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;question&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;shared&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_question&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;knowledge&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;knowledge&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prepared_data&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
        Answer this question: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;prepared_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;question&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;
        Using ONLY this information:
        &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;prepared_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;knowledge&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;
        &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;call_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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prep_res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;final_answer_from_ai&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;final_answer&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;final_answer_from_ai&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;In English:&lt;/strong&gt; It gathers all the text we found, gives it to the AI, and asks it to write a beautiful, helpful response.&lt;/p&gt;

&lt;p&gt;And that's the core of the system. Three simple nodes, each with a clear job, passing data through a simple dictionary.&lt;/p&gt;

&lt;p&gt;Now that the magic is revealed (and you see it's not so magical after all), let's give our chatbot a pretty face so you can put it on your website.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Giving Our Bot a Face: From Terminal to Website
&lt;/h2&gt;

&lt;p&gt;Okay, we have a functional AI brain that runs in the terminal. That's a great start, but it's not very useful for your website visitors.&lt;/p&gt;

&lt;p&gt;Let's connect that brain to a user-friendly chat bubble. This is a classic web development pattern with two simple parts: a &lt;strong&gt;backend&lt;/strong&gt; (our Python script) and a &lt;strong&gt;frontend&lt;/strong&gt; (the chat bubble on a website).&lt;/p&gt;

&lt;h3&gt;
  
  
  The Architecture: A Brain and a Face
&lt;/h3&gt;

&lt;p&gt;Think of it like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;The Backend (The Brain):&lt;/strong&gt; This is our Python script, &lt;code&gt;server.py&lt;/code&gt;. Its only job is to wait for a question, run our PocketFlow logic to find the answer, and send the answer back. It's the powerhouse that does all the heavy lifting.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;The Frontend (The Face):&lt;/strong&gt; This is a small piece of JavaScript, &lt;code&gt;chatbot.js&lt;/code&gt;, that you add to your website. It creates the chat icon and the chat window. When a user types a question, the JavaScript simply sends it to our backend for processing.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;They communicate over the network. The frontend asks a question, and the backend provides the answer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8w5k7mg29ey5nx45n7dt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8w5k7mg29ey5nx45n7dt.png" alt="Image description" width="800" height="117"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's look at the minimal code that makes each part work.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Backend: &lt;code&gt;server.py&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;We use a lightweight Python framework called &lt;strong&gt;FastAPI&lt;/strong&gt; to create a simple web server. Its job is to expose a single "endpoint" (like a URL) that the frontend can send questions to.&lt;/p&gt;

&lt;p&gt;Here’s the core logic in &lt;code&gt;server.py&lt;/code&gt;:&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;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;flow&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;support_bot_flow&lt;/span&gt; &lt;span class="c1"&gt;# Our PocketFlow brain
&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nd"&gt;@app.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/get-answer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# An endpoint to receive questions
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_answer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# 1. Get the question and URL from the frontend
&lt;/span&gt;    &lt;span class="n"&gt;question&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;question&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;start_urls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;urls&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# 2. Set up the shared dictionary for our flow
&lt;/span&gt;    &lt;span class="n"&gt;shared_state&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;user_question&lt;/span&gt;&lt;span class="sh"&gt;"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;all_discovered_urls&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;start_urls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...}&lt;/span&gt;

    &lt;span class="c1"&gt;# 3. Run the PocketFlow brain to find the answer
&lt;/span&gt;    &lt;span class="n"&gt;support_bot_flow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shared_state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# 4. Return the final answer as a response
&lt;/span&gt;    &lt;span class="k"&gt;return&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&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;shared_state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;final_answer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;In English:&lt;/strong&gt; The server waits for a POST request at &lt;code&gt;/get-answer&lt;/code&gt;. When it gets one, it runs the same PocketFlow we built before and sends the result back.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Frontend: &lt;code&gt;chatbot.js&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This is the JavaScript that lives on your website. It listens for the user to click "send," then makes a simple web request to our Python backend.&lt;/p&gt;

&lt;p&gt;Here's the simplified logic from &lt;code&gt;chatbot.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleSendClick&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userInput&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;chat-input&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;siteUrls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://your-site.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="c1"&gt;// The URLs for the bot to crawl&lt;/span&gt;

    &lt;span class="c1"&gt;// 1. Send the user's question to our Python backend&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/get-answer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userInput&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;siteUrls&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// 2. Get the answer back from the server&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;botAnswer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// 3. Display the bot's answer in the chat window&lt;/span&gt;
    &lt;span class="nf"&gt;displayMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;botAnswer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;In English:&lt;/strong&gt; When the user sends a message, it packages up the question and sends it to the &lt;code&gt;/get-answer&lt;/code&gt; endpoint on our server. When the server responds, it displays the answer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Running It Live
&lt;/h3&gt;

&lt;p&gt;Now the process is clear:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Start the Backend:&lt;/strong&gt; First, you need to run the brain. In your terminal, run &lt;code&gt;python server.py&lt;/code&gt;. This starts the web server and gets it ready to answer questions.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Add the Frontend to a Page:&lt;/strong&gt; Next, you add the &lt;code&gt;&amp;lt;script src="chatbot.js"&amp;gt;&amp;lt;/script&amp;gt;&lt;/code&gt; tag to your website's HTML. This makes the chat bubble appear.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To make testing easy, the project includes a sample &lt;code&gt;static/chatbot.html&lt;/code&gt; file that already has the script included. Once your server is running, just open that file in your browser to see your live, interactive chatbot in action&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Conclusion: Simple, Maintainable, and Live
&lt;/h2&gt;

&lt;p&gt;Let's take a step back. We just built a fully-functional AI chatbot that can intelligently answer questions about any website.&lt;/p&gt;

&lt;p&gt;And we did it without touching a single vector database, writing a complex data-syncing script, or worrying about our information ever going stale. Its brain is your live website, which means its knowledge is always up-to-date.&lt;/p&gt;

&lt;p&gt;This isn't just another chatbot. This is a better, simpler way to build one. Here’s why this approach wins:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Always Up-to-Date.&lt;/strong&gt; Your bot’s knowledge is never stale. When you update your website, you've instantly updated your chatbot. There is no sync step, ever.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Practically Zero-Maintenance.&lt;/strong&gt; You can finally "set it and forget it." Your only job is to keep your website current—something you were already doing anyway.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Incredibly Simple Code.&lt;/strong&gt; Because the entire system is built on PocketFlow and a few straightforward Python scripts, the logic is easy to read and modify. There are no black boxes to fight with.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The days of babysitting your AI are over. You now have the blueprint for a system that’s not only intelligent but also practical and sustainable.&lt;/p&gt;

&lt;p&gt;Ready to add a real-time brain to your own website?&lt;/p&gt;




&lt;p&gt;&lt;em&gt;The complete, open-source code for this chatbot is waiting for you on GitHub. It's powered by the 100-line &lt;strong&gt;&lt;a href="https://github.com/the-pocket/PocketFlow" rel="noopener noreferrer"&gt;PocketFlow&lt;/a&gt;&lt;/strong&gt; framework. Dive in, experiment, and see for yourself how easy building a truly smart chatbot can be! &lt;a href="https://github.com/The-Pocket/PocketFlow-Tutorial-Website-Chatbot" rel="noopener noreferrer"&gt;Get the AI Website Chatbot Code on GitHub&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>programming</category>
      <category>llm</category>
      <category>beginners</category>
      <category>python</category>
    </item>
    <item>
      <title>Build an LLM Web App in Python from Scratch: Part 4 (FastAPI, Background Tasks &amp; SSE)</title>
      <dc:creator>Zachary Huang</dc:creator>
      <pubDate>Thu, 12 Jun 2025 01:44:07 +0000</pubDate>
      <link>https://dev.to/zachary62/build-an-llm-web-app-in-python-from-scratch-part-4-fastapi-background-tasks-sse-21g4</link>
      <guid>https://dev.to/zachary62/build-an-llm-web-app-in-python-from-scratch-part-4-fastapi-background-tasks-sse-21g4</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Ever asked an AI to write a whole blog post, only to stare at a loading spinner for five minutes, wondering if your browser crashed? We've all been there. Today, we're fixing that. We'll build an AI web app that takes on heavy-duty tasks—like writing a full article—without freezing up. You'll see live progress updates in real-time, so you always know what the AI is up to. Ready to make long-running AI tasks feel fast and interactive? Let's get building!&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  1. The Spinner of Doom: Why Long AI Tasks Break Your App 😫
&lt;/h2&gt;

&lt;p&gt;Imagine your new AI app is a genius content writer. You ask it to "write an article about space exploration," hit enter, and... the dreaded loading spinner appears. The whole page is frozen. You can't click anything. After a minute, your browser might even give you a "This page is unresponsive" error. Yikes.&lt;/p&gt;

&lt;p&gt;This is the classic problem with long-running tasks on the web. Standard web apps work like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;You ask for something.&lt;/strong&gt; (e.g., "Generate my article!")&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;The server works on it.&lt;/strong&gt; (e.g., Calls the AI, which takes 2-3 minutes)&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;You wait... and wait...&lt;/strong&gt; (The connection is held open, your browser is stuck)&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Finally, you get the result.&lt;/strong&gt; (Or a timeout error!)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is a terrible user experience. Users need to know their request is being handled and see that progress is being made.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Our Solution: The Smart Restaurant Analogy&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Think of it like ordering food at a restaurant:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The Bad Way (Standard Web Request):&lt;/strong&gt; You order a steak. The waiter stands at your table, staring at you without moving, for the entire 15 minutes it takes to cook. Awkward, right? You can't even ask for more water.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The Good Way (Our Approach):&lt;/strong&gt; You order a steak. The waiter says, "Excellent choice! I'll put that in with the chef," and walks away (&lt;strong&gt;Background Task&lt;/strong&gt;). A few minutes later, they bring you some bread ("Making progress!"). A bit later, your drink arrives ("Almost ready!"). You're happy and informed while the main course is being prepared in the background.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's exactly what we're building today. Our app will:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Instantly confirm the user's request.&lt;/li&gt;
&lt;li&gt; Offload the heavy AI work to a &lt;strong&gt;background task&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt; Stream live progress updates back to the user with &lt;strong&gt;Server-Sent Events (SSE)&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Our toolkit for this mission:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;🔧 FastAPI &lt;code&gt;BackgroundTasks&lt;/code&gt;&lt;/strong&gt;: For running the AI job without freezing the app.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;🔧 Server-Sent Events (SSE)&lt;/strong&gt;: A simple way to push live updates from the server to the browser.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;🔧 &lt;a href="https://github.com/The-Pocket/PocketFlow" rel="noopener noreferrer"&gt;PocketFlow&lt;/a&gt;&lt;/strong&gt;: To organize our multi-step article writing process.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's dive into the tools that make this magic possible.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;You can find the complete code for the app we're building today in the PocketFlow cookbook: &lt;a href="https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-fastapi-background" rel="noopener noreferrer"&gt;FastAPI Background Jobs with Real-time Progress&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Our Tools for the Job: Background Tasks &amp;amp; Server-Sent Events (SSE) 🛠️
&lt;/h2&gt;

&lt;p&gt;To build our responsive AI article writer, we need two key pieces of technology: one to handle the work behind the scenes and another to report on its progress.&lt;/p&gt;

&lt;h3&gt;
  
  
  FastAPI &lt;code&gt;BackgroundTasks&lt;/code&gt;: The "Work in the Back" Crew
&lt;/h3&gt;

&lt;p&gt;Think of &lt;code&gt;BackgroundTasks&lt;/code&gt; as giving a job to a helper who works independently. You tell FastAPI, "Hey, after you tell the user I got their request, please run this other function in the background."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The magic is that the user gets an immediate response.&lt;/strong&gt; They don't have to wait for the background work to finish.&lt;/p&gt;

&lt;p&gt;It's like ordering from Amazon. You get the "Order Confirmed!" email instantly. The actual process of picking, packing, and shipping your item happens &lt;em&gt;later&lt;/em&gt;, in the background.&lt;/p&gt;

&lt;p&gt;Here's a simple example: sending a welcome email after a user signs up.&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;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;BackgroundTasks&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# This is our slow task (e.g., calling an email service)
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;send_welcome_email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# Simulate a 5-second delay
&lt;/span&gt;    &lt;span class="nf"&gt;print&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;Email sent to &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;! (This prints in the server console)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@app.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/signup&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;user_signup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&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="n"&gt;background_tasks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;BackgroundTasks&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Add the slow email task to the background
&lt;/span&gt;    &lt;span class="n"&gt;background_tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;send_welcome_email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Return a response to the user IMMEDIATELY
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&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;Thanks for signing up, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;! Check your inbox soon.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What's happening?&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; When you send a request to &lt;code&gt;/signup&lt;/code&gt;, FastAPI sees &lt;code&gt;background_tasks.add_task(...)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; It immediately sends back the &lt;code&gt;{"message": "Thanks..."}&lt;/code&gt; response. Your browser is happy and responsive.&lt;/li&gt;
&lt;li&gt; &lt;em&gt;After&lt;/em&gt; sending the response, FastAPI runs &lt;code&gt;send_welcome_email()&lt;/code&gt; in the background.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is perfect for our AI article generator! We'll use it to run the entire AI writing process.&lt;/p&gt;

&lt;h3&gt;
  
  
  Server-Sent Events (SSE): Your Live Progress Ticker
&lt;/h3&gt;

&lt;p&gt;Okay, so the work is happening in the background. But how do we tell the user what's going on? That's where &lt;strong&gt;Server-Sent Events (SSE)&lt;/strong&gt; come in.&lt;/p&gt;

&lt;p&gt;SSE is a super simple way for a server to push updates to a browser over a single, one-way connection. It's like a live news ticker: the server sends new headlines as they happen, and your browser just listens.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why not use WebSockets again?&lt;/strong&gt;&lt;br&gt;
WebSockets (from Part 3) are awesome for two-way chat. But for just sending one-way progress updates, they're a bit like using a walkie-talkie when all you need is a pager. SSE is simpler, lighter, and designed for exactly this "server-to-client" push scenario.&lt;/p&gt;

&lt;p&gt;Here's how simple an SSE endpoint is in FastAPI:&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;asyncio&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi.responses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;StreamingResponse&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;progress_generator&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;i&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# In a real app, this would be a progress update from our AI
&lt;/span&gt;        &lt;span class="k"&gt;yield&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;data: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;progress&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;step&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&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;Step &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="n"&gt;done&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# Wait 1 second
&lt;/span&gt;
&lt;span class="nd"&gt;@app.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/stream-progress&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;stream_progress&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;StreamingResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;progress_generator&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;media_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text/event-stream&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And on the browser side, the JavaScript is just as easy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"progressStatus"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Waiting for progress...&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;progressStatus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;progressStatus&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;eventSource&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;EventSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/stream-progress&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Connect to our stream!&lt;/span&gt;

    &lt;span class="nx"&gt;eventSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onmessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;progressStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`Progress: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;progress&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;% - &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you open this page, &lt;code&gt;eventSource&lt;/code&gt; connects to our endpoint, and the &lt;code&gt;progressStatus&lt;/code&gt; div will update every second. Simple and effective!&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The AI's To-Do List: A PocketFlow Workflow 📝
&lt;/h2&gt;

&lt;p&gt;So, how does our AI actually write an article? It doesn't happen in one go. We need to give it a step-by-step plan, like a recipe. This is where &lt;strong&gt;&lt;a href="https://github.com/The-Pocket/PocketFlow" rel="noopener noreferrer"&gt;PocketFlow&lt;/a&gt;&lt;/strong&gt; helps us. It breaks the big job of "write an article" into small, manageable &lt;code&gt;Nodes&lt;/code&gt; (or steps).&lt;/p&gt;

&lt;h3&gt;
  
  
  The Central Hub: Our &lt;code&gt;shared&lt;/code&gt; Dictionary
&lt;/h3&gt;

&lt;p&gt;Before we dive into the nodes, let's look at the brain of our operation: a simple Python dictionary. All our nodes will read from and write to this central &lt;code&gt;shared&lt;/code&gt; data hub. It's how they pass information to each other.&lt;/p&gt;

&lt;p&gt;Here's what it looks like at the start of a job:&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;shared_data&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;topic&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;The user&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s article topic&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;sse_queue&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# The "mailbox" for progress messages
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sections&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="c1"&gt;# Will be filled by the Outline node
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;draft&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="p"&gt;,&lt;/span&gt;       &lt;span class="c1"&gt;# Will be filled by the Content node
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;final_article&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="c1"&gt;# The final result!
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;sse_queue&lt;/code&gt; is our "mailbox." Each node will drop progress updates into it, and our FastAPI server will read from it to update the user.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: &lt;code&gt;GenerateOutline&lt;/code&gt; Node
&lt;/h3&gt;

&lt;p&gt;This node's only job is to create the article's structure. (We'll assume a &lt;code&gt;call_llm&lt;/code&gt; function exists that talks to the AI).&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;class&lt;/span&gt; &lt;span class="nc"&gt;GenerateOutline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;prep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shared&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;shared&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;topic&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;topic&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Create 3 section titles for an article on &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;topic&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;call_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;# e.g., "Intro,Main Points,Conclusion"
&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;outline_str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;sections&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;outline_str&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sections&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;sections&lt;/span&gt;

        &lt;span class="n"&gt;progress&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;step&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;outline&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;progress&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;33&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;sections&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sse_queue&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;put_nowait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;progress&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What's happening here:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;prep&lt;/code&gt; grabs the user's &lt;code&gt;topic&lt;/code&gt; from the shared dictionary.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;exec&lt;/code&gt; takes that &lt;code&gt;topic&lt;/code&gt; and asks the AI for an outline.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;post&lt;/code&gt; saves the outline for the next step and—most importantly—&lt;strong&gt;drops a progress message into the mailbox&lt;/strong&gt;. This tells the frontend, "Hey, I'm 33% done!"&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 2: &lt;code&gt;WriteContent&lt;/code&gt; Node
&lt;/h3&gt;

&lt;p&gt;This node takes the outline and writes the content for each section.&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;class&lt;/span&gt; &lt;span class="nc"&gt;WriteContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;prep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shared&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;shared&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sections&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sse_queue&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prep_result&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;sections&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;queue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;prep_result&lt;/span&gt;
        &lt;span class="n"&gt;full_draft&lt;/span&gt; &lt;span class="o"&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;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;section&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sections&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Write a paragraph for the section: &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;section&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="n"&gt;paragraph&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;call_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="n"&gt;full_draft&lt;/span&gt; &lt;span class="o"&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;&amp;lt;h2&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;section&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;paragraph&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

            &lt;span class="n"&gt;progress&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;step&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;writing&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;progress&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;33&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
            &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put_nowait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;progress&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;full_draft&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;full_draft&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;draft&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;full_draft&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why this is cool:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Inside the &lt;code&gt;exec&lt;/code&gt; loop, after each paragraph is written, we immediately send another progress update.&lt;/li&gt;
&lt;li&gt;  This means the user will see the progress bar jump forward multiple times during this step, making the app feel very responsive.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 3: &lt;code&gt;ApplyStyle&lt;/code&gt; Node
&lt;/h3&gt;

&lt;p&gt;This is the final touch. It takes the combined draft and asks the AI to polish it.&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;class&lt;/span&gt; &lt;span class="nc"&gt;ApplyStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;prep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shared&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;shared&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;draft&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;draft&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Rewrite this draft in an engaging style: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;call_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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;final_article&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;final_article&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;final_article&lt;/span&gt;

        &lt;span class="n"&gt;progress&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;step&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;complete&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;progress&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;final_article&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sse_queue&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;put_nowait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;progress&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The grand finale:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  This node does the final rewrite.&lt;/li&gt;
&lt;li&gt;  Crucially, it sends the &lt;code&gt;complete&lt;/code&gt; message to the mailbox with &lt;code&gt;progress: 100&lt;/code&gt;. This tells our frontend that the job is finished and the final article is ready!&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Tying It All Together with a &lt;code&gt;Flow&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Finally, we just need to tell PocketFlow the order of our to-do list.&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;from&lt;/span&gt; &lt;span class="n"&gt;pocketflow&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flow&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_article_flow&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;outline_node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;GenerateOutline&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;content_node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;WriteContent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;style_node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ApplyStyle&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# Define the sequence: outline -&amp;gt; write -&amp;gt; style
&lt;/span&gt;    &lt;span class="n"&gt;outline_node&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;content_node&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;style_node&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Flow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start_node&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;outline_node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is super readable: &lt;code&gt;outline_node&lt;/code&gt; runs, then &lt;code&gt;content_node&lt;/code&gt;, then &lt;code&gt;style_node&lt;/code&gt;. This &lt;code&gt;Flow&lt;/code&gt; object is what our FastAPI background task will ultimately run.&lt;/p&gt;

&lt;p&gt;Here is a visual summary of the entire process:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fguzdtma0dktz4gzqywnh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fguzdtma0dktz4gzqywnh.png" alt="Image description" width="800" height="78"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With our AI's "to-do list" ready, let's connect it to our FastAPI backend.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Connecting the Dots: The FastAPI Backend 🔗
&lt;/h2&gt;

&lt;p&gt;Okay, our AI has its "to-do list" from PocketFlow. Now, let's build the web server that acts as the project manager. It will take requests from users, give the work to our AI, and report on the progress.&lt;/p&gt;

&lt;p&gt;We'll walk through the main &lt;code&gt;main.py&lt;/code&gt; file piece by piece.&lt;/p&gt;

&lt;h3&gt;
  
  
  Part 1: The Job Center
&lt;/h3&gt;

&lt;p&gt;First, we need a place to keep track of all the article-writing jobs that are currently running. A simple Python dictionary is perfect for this.&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="c1"&gt;# A dictionary to hold our active jobs
# Key: A unique job_id (string)
# Value: The "mailbox" (asyncio.Queue) for that job's messages
&lt;/span&gt;&lt;span class="n"&gt;active_jobs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Think of &lt;code&gt;active_jobs&lt;/code&gt; as the front desk of an office. When a new job comes in, we give it a ticket number (&lt;code&gt;job_id&lt;/code&gt;) and a dedicated mailbox (&lt;code&gt;asyncio.Queue&lt;/code&gt;) for all its internal memos.&lt;/p&gt;

&lt;h3&gt;
  
  
  Part 2: Kicking Off the Job
&lt;/h3&gt;

&lt;p&gt;This is the first thing a user interacts with. They send their article topic to our &lt;code&gt;/start-job&lt;/code&gt; endpoint, which kicks off the whole process.&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="nd"&gt;@app.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/start-job&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;start_job&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;background_tasks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;BackgroundTasks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Form&lt;/span&gt;&lt;span class="p"&gt;(...)):&lt;/span&gt;
    &lt;span class="n"&gt;job_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uuid4&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

    &lt;span class="c1"&gt;# Create a new, empty mailbox for this specific job
&lt;/span&gt;    &lt;span class="n"&gt;sse_queue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Queue&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;active_jobs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;job_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sse_queue&lt;/span&gt;

    &lt;span class="c1"&gt;# Tell FastAPI: "Run this function in the background"
&lt;/span&gt;    &lt;span class="n"&gt;background_tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run_article_workflow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;job_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# IMMEDIATELY send a response back to the user
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;job_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;job_id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Let's break that down:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Get a Ticket Number:&lt;/strong&gt; &lt;code&gt;job_id = str(uuid.uuid4())&lt;/code&gt; creates a unique ID for this request.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Create the Mailbox:&lt;/strong&gt; &lt;code&gt;sse_queue = asyncio.Queue()&lt;/code&gt; creates the message queue. We then store it in our &lt;code&gt;active_jobs&lt;/code&gt; dictionary using the &lt;code&gt;job_id&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Hand off the Work:&lt;/strong&gt; &lt;code&gt;background_tasks.add_task(...)&lt;/code&gt; is the magic. It tells FastAPI, "Don't wait! After you send the response, start running the &lt;code&gt;run_article_workflow&lt;/code&gt; function."&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Instant Reply:&lt;/strong&gt; The &lt;code&gt;return {"job_id": job_id}&lt;/code&gt; is sent back to the user's browser right away. The user's page doesn't freeze!&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Part 3: The Background Worker
&lt;/h3&gt;

&lt;p&gt;This is the function that runs behind the scenes. It's the project manager that actually runs our PocketFlow to-do list.&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;run_article_workflow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job_id&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="n"&gt;topic&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="n"&gt;sse_queue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;active_jobs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;job_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;shared&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;topic&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sse_queue&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;sse_queue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# Here's where we pass the mailbox in!
&lt;/span&gt;        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sections&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;draft&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="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;final_article&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="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;flow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;create_article_flow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;flow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# Start the PocketFlow!
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Here's the crucial connection:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  It gets the correct &lt;code&gt;sse_queue&lt;/code&gt; (mailbox) for this job from our &lt;code&gt;active_jobs&lt;/code&gt; dictionary.&lt;/li&gt;
&lt;li&gt;  It creates the &lt;code&gt;shared&lt;/code&gt; data dictionary and &lt;strong&gt;puts the &lt;code&gt;sse_queue&lt;/code&gt; inside it&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;  When &lt;code&gt;flow.run(shared)&lt;/code&gt; is called, our PocketFlow nodes now have access to this queue and can drop their progress messages into it!&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Part 4: Streaming the Progress Updates
&lt;/h3&gt;

&lt;p&gt;While the background task is running, the user's browser connects to our &lt;code&gt;/progress/{job_id}&lt;/code&gt; endpoint to listen for updates.&lt;/p&gt;

&lt;p&gt;This function looks a bit complex, but it's just a loop that checks the mailbox.&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="nd"&gt;@app.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/progress/{job_id}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_progress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job_id&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="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;event_stream&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="c1"&gt;# First, find the right mailbox for this job
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;job_id&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;active_jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;data: {&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Job not found&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;

        &lt;span class="n"&gt;sse_queue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;active_jobs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;job_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# Wait for a new message to arrive in the mailbox
&lt;/span&gt;            &lt;span class="n"&gt;progress_msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;sse_queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;yield&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;data: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;progress_msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

            &lt;span class="c1"&gt;# If the message says "complete", we're done!
&lt;/span&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;progress_msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;step&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;complete&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;del&lt;/span&gt; &lt;span class="n"&gt;active_jobs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;job_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;# Clean up the job
&lt;/span&gt;                &lt;span class="k"&gt;break&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;StreamingResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;event_stream&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;media_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text/event-stream&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The logic is simple:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;code&gt;event_stream&lt;/code&gt; is a special &lt;code&gt;async&lt;/code&gt; generator that can send (&lt;code&gt;yield&lt;/code&gt;) data over time.&lt;/li&gt;
&lt;li&gt; It finds the correct mailbox (&lt;code&gt;sse_queue&lt;/code&gt;) for the &lt;code&gt;job_id&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; The &lt;code&gt;while True:&lt;/code&gt; loop starts.&lt;/li&gt;
&lt;li&gt; &lt;code&gt;await sse_queue.get()&lt;/code&gt; &lt;strong&gt;pauses and waits&lt;/strong&gt; until a message appears in the mailbox.&lt;/li&gt;
&lt;li&gt; As soon as a PocketFlow node drops a message in, this line wakes up, grabs the message, and sends it to the browser with &lt;code&gt;yield&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; It keeps doing this until it sees the &lt;code&gt;"step": "complete"&lt;/code&gt; message, at which point it cleans up and closes the connection.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And that's the whole system! It's a clean loop: the user starts a job, a background worker runs it while dropping messages into a mailbox, and a streamer reads from that mailbox to keep the user updated.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Mission Complete! Your App Now Handles the Heavy Lifting 💪
&lt;/h2&gt;

&lt;p&gt;You did it! You've successfully built a web app that can handle long, complex AI tasks without breaking a sweat or frustrating your users. They get an instant response and live progress updates, making the whole experience feel smooth, interactive, and professional.&lt;/p&gt;

&lt;p&gt;No more dreaded loading spinners or "page unresponsive" errors. Your app now works like a modern, intelligent assistant: it acknowledges your request, works on it diligently in the background, and keeps you informed every step of the way.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What you conquered today:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;No More Freezing UIs:&lt;/strong&gt; Used FastAPI's &lt;code&gt;BackgroundTasks&lt;/code&gt; to offload heavy AI work so your app stays responsive.&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Live Progress Updates:&lt;/strong&gt; Mastered Server-Sent Events (SSE) to stream status updates from the server to the browser in real-time.&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Clean, Organized Logic:&lt;/strong&gt; Structured a complex, multi-step AI job with PocketFlow, keeping your AI logic separate from your web code.&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Tied It All Together:&lt;/strong&gt; Used an &lt;code&gt;asyncio.Queue&lt;/code&gt; as a simple "mailbox" to let your background task communicate with your web server.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This architecture is a game-changer for building serious AI applications. You now have the skills to create tools that can generate reports, analyze data, or perform any other time-intensive task, all while keeping your users happy and engaged.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Our "Build an LLM Web App" Journey is Complete!&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://pocketflow.substack.com/p/build-an-llm-web-app-in-python-from" rel="noopener noreferrer"&gt;Part 1&lt;/a&gt;:&lt;/strong&gt; Command-line AI tools ✅&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://pocketflow.substack.com/p/build-an-llm-web-app-in-python-from-b11" rel="noopener noreferrer"&gt;Part 2&lt;/a&gt;:&lt;/strong&gt; Interactive web apps with Streamlit ✅&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://pocketflow.substack.com/p/build-an-llm-web-app-in-python-from-b33" rel="noopener noreferrer"&gt;Part 3&lt;/a&gt;:&lt;/strong&gt; Real-time streaming chat with WebSockets ✅&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Part 4 (You just crushed it!):&lt;/strong&gt; Background tasks for heavy AI work ✅&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;&lt;strong&gt;Ready to see it all in action?&lt;/strong&gt; Grab the complete code, including the HTML and PocketFlow nodes, from our cookbook: &lt;strong&gt;&lt;a href="https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-fastapi-background" rel="noopener noreferrer"&gt;FastAPI Background Jobs with Real-time Progress&lt;/a&gt;&lt;/strong&gt;. You've leveled up your AI dev skills in a big way. Now go build something amazing! 🚀&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>beginners</category>
      <category>ai</category>
    </item>
    <item>
      <title>Build an LLM Web App in Python from Scratch: Part 3 (FastAPI &amp; WebSockets)</title>
      <dc:creator>Zachary Huang</dc:creator>
      <pubDate>Sun, 08 Jun 2025 02:01:56 +0000</pubDate>
      <link>https://dev.to/zachary62/build-an-llm-web-app-in-python-from-scratch-part-3-fastapi-websockets-1hp5</link>
      <guid>https://dev.to/zachary62/build-an-llm-web-app-in-python-from-scratch-part-3-fastapi-websockets-1hp5</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Ever watched ChatGPT type back to you word by word, like it's actually thinking out loud? That's &lt;strong&gt;streaming AI&lt;/strong&gt; in action, and it makes web apps feel incredibly alive! Today, we're building exactly that: a real-time &lt;strong&gt;AI chatbot web app&lt;/strong&gt; where responses flow in instantly. No more staring at loading spinners! We'll use &lt;strong&gt;FastAPI&lt;/strong&gt; for lightning-fast backends, &lt;strong&gt;WebSockets&lt;/strong&gt; for live chat magic, and &lt;strong&gt;&lt;a href="https://github.com/The-Pocket/PocketFlow" rel="noopener noreferrer"&gt;PocketFlow&lt;/a&gt;&lt;/strong&gt; to keep things organized. Ready to make your web app feel like a real conversation? You can find the complete code for this part in the &lt;a href="https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-fastapi-websocket" rel="noopener noreferrer"&gt;FastAPI WebSocket Chat Cookbook&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  1. Why Your AI Web App Should Stream (It's a Game Changer!) 🚀
&lt;/h2&gt;

&lt;p&gt;Picture this: You ask an AI a question, then... you wait. And wait. Finally, BOOM – a wall of text appears all at once. Feels clunky, right?&lt;/p&gt;

&lt;p&gt;Now imagine this instead: You ask your question, and the AI starts "typing" back immediately – word by word, just like texting with a friend. &lt;strong&gt;That's the magic of streaming for AI web apps.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why streaming rocks:&lt;/strong&gt; It feels lightning fast, keeps users engaged, and creates natural conversation flow. No more "is this thing broken?" moments!&lt;/p&gt;

&lt;p&gt;We're creating a &lt;strong&gt;live AI chatbot web app&lt;/strong&gt; that streams responses in real-time. You'll type a message, and watch the AI respond word by word, just like the pros do it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Our toolkit:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;🔧 FastAPI&lt;/strong&gt; – Blazing fast Python web framework&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;🔧 WebSockets&lt;/strong&gt; – The secret sauce for live, two-way chat&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;🔧 &lt;a href="https://github.com/The-Pocket/PocketFlow" rel="noopener noreferrer"&gt;PocketFlow&lt;/a&gt;&lt;/strong&gt; – Our LLM framework in 100 lines&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Quick catch-up on our series:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://pocketflow.substack.com/p/build-an-llm-web-app-in-python-from" rel="noopener noreferrer"&gt;Part 1&lt;/a&gt;:&lt;/strong&gt; Built command-line AI tools ✅&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://pocketflow.substack.com/p/build-an-llm-web-app-in-python-from-b11" rel="noopener noreferrer"&gt;Part 2&lt;/a&gt;:&lt;/strong&gt; Created interactive web apps with Streamlit ✅
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Part 3 (You are here!):&lt;/strong&gt; Real-time streaming web apps 🚀&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Part 4 (Coming next!):&lt;/strong&gt; Background tasks for heavy AI work&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Want to see streaming in action without the web complexity first? Check out our simpler guide: "&lt;a href="https://pocketflow.substack.com/p/streaming-llm-responses-tutorial" rel="noopener noreferrer"&gt;Streaming LLM Responses — Tutorial For Dummies&lt;/a&gt;".&lt;/p&gt;

&lt;p&gt;Ready to make your AI web app feel like magic? Let's dive in!&lt;/p&gt;

&lt;h2&gt;
  
  
  2. FastAPI + WebSockets = Real-Time Magic ⚡
&lt;/h2&gt;

&lt;p&gt;To build our streaming chatbot, we need two key pieces: &lt;strong&gt;FastAPI&lt;/strong&gt; for a blazing-fast backend and &lt;strong&gt;WebSockets&lt;/strong&gt; for live, two-way chat.&lt;/p&gt;

&lt;h3&gt;
  
  
  FastAPI: Your Speed Demon Backend
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;FastAPI&lt;/strong&gt; is like the sports car of Python web frameworks – fast, modern, and async-ready. Perfect for AI apps that need to handle multiple conversations at once.&lt;/p&gt;

&lt;p&gt;Most web apps work like old-school mail: Browser sends request → Server processes → Sends back response → Done. Here's a basic FastAPI example:&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;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nd"&gt;@app.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/hello&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;say_hello&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;greeting&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;Hi there!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What's happening here?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;app = FastAPI()&lt;/code&gt; – Creates your web server&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@app.get("/hello")&lt;/code&gt; – Says "when someone visits &lt;code&gt;/hello&lt;/code&gt;, run the function below"&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;async def say_hello()&lt;/code&gt; – The function that handles the request&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;return {"greeting": "Hi there!"}&lt;/code&gt; – Sends back JSON data to the browser&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When you visit &lt;code&gt;http://localhost:8000/hello&lt;/code&gt;, you'll see &lt;code&gt;{"greeting": "Hi there!"}&lt;/code&gt; in your browser!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your First FastAPI App Flow:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuakxlvyg0p4lvj2ycs44.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuakxlvyg0p4lvj2ycs44.png" alt="Image description" width="596" height="577"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Simple enough, but for chatbots we need something more interactive...&lt;/p&gt;

&lt;h3&gt;
  
  
  WebSockets: Live Chat Superpowers
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;WebSockets&lt;/strong&gt; turn your web app into a live phone conversation. Instead of sending messages back and forth, you open a connection that stays live for instant back-and-forth chat.&lt;/p&gt;

&lt;p&gt;Here's a simple echo server that repeats whatever you say:&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;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;WebSocket&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nd"&gt;@app.websocket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/chat&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;chat_endpoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;websocket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;WebSocket&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;websocket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;accept&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;# Pick up the call!
&lt;/span&gt;    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;websocket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;receive_text&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;# Listen
&lt;/span&gt;        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;websocket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send_text&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;You said: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;message&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="c1"&gt;# Reply
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The browser side&lt;/strong&gt; is just as simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"messageInput"&lt;/span&gt; &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"Say something..."&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;onclick=&lt;/span&gt;&lt;span class="s"&gt;"sendMessage()"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Send&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"chatLog"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ws&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WebSocket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ws://localhost:8000/chat&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;chatLog&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;chatLog&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onmessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;chatLog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;p&amp;gt;Server: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/p&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;sendMessage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;messageInput&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;chatLog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;p&amp;gt;You: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/p&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;messageInput&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;WebSocket Chat Flow:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fptdbb582030sa20xttr4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fptdbb582030sa20xttr4.png" alt="Image description" width="732" height="878"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's it! You now have live, real-time communication between browser and server. Perfect foundation for our streaming AI chatbot!&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Adding AI to the Mix: Why Async Matters 🤖
&lt;/h2&gt;

&lt;p&gt;Great! We have live chat working. But here's the thing: calling an AI like ChatGPT takes time (sometimes 3-5 seconds). If our server just sits there waiting, our whole web app freezes. Not good!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; Normal code is like a single-lane road. When the AI is thinking, everything else stops.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The solution:&lt;/strong&gt; Async code is like a highway with multiple lanes. While AI is thinking in one lane, other users can chat in other lanes!&lt;/p&gt;

&lt;h3&gt;
  
  
  PocketFlow Goes Async
&lt;/h3&gt;

&lt;p&gt;Remember &lt;strong&gt;&lt;a href="https://github.com/The-Pocket/PocketFlow" rel="noopener noreferrer"&gt;PocketFlow&lt;/a&gt;&lt;/strong&gt; from our earlier tutorials? It helps break down complex tasks into simple steps. For web apps, we need the async version:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;AsyncNode&lt;/code&gt;&lt;/strong&gt; – Each step can wait for AI without blocking others&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;AsyncFlow&lt;/code&gt;&lt;/strong&gt; – Manages the whole conversation workflow&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's the magic difference:&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="c1"&gt;# ❌ This blocks everything
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call_ai&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;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;openai&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="c1"&gt;# Everyone waits!
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;

&lt;span class="c1"&gt;# ✅ This lets others keep chatting
&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call_ai_async&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;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;openai&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="c1"&gt;# Just this task waits
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Streaming Chat Node: The Star of the Show
&lt;/h3&gt;

&lt;p&gt;Our &lt;code&gt;StreamingChatNode&lt;/code&gt; does three things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Prep:&lt;/strong&gt; Add user message to chat history&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Execute:&lt;/strong&gt; Call AI and stream response word-by-word via WebSocket
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Post:&lt;/strong&gt; Save AI's complete response to history
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StreamingChatNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AsyncNode&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;prep_async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# Add user message to history
&lt;/span&gt;        &lt;span class="n"&gt;history&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;conversation_history&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="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="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="n"&gt;shared&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_message&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;history&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;websocket&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;exec_async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prep_result&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;websocket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;prep_result&lt;/span&gt;

        &lt;span class="c1"&gt;# Stream AI response word by word
&lt;/span&gt;        &lt;span class="n"&gt;full_response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;
        &lt;span class="k"&gt;async&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;stream_llm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;full_response&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;websocket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&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="n"&gt;chunk&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;full_response&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;post_async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prep_res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exec_res&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# Save complete AI response
&lt;/span&gt;        &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;conversation_history&lt;/span&gt;&lt;span class="sh"&gt;"&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="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;assistant&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="n"&gt;exec_res&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it! The node streams AI responses live while keeping chat history. Next, let's see how this all connects together!&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Putting It All Together: The Complete Streaming Flow 🔄
&lt;/h2&gt;

&lt;p&gt;Time to connect all the pieces! Here's how a user message flows through our streaming chatbot:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Journey of a Message:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4oqloomfs5cns0n29wj7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4oqloomfs5cns0n29wj7.png" alt="Image description" width="800" height="84"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;User sends message → FastAPI receives it → PocketFlow handles AI logic → Streams response back live!&lt;/p&gt;

&lt;h3&gt;
  
  
  The FastAPI WebSocket Handler
&lt;/h3&gt;

&lt;p&gt;Here's the main FastAPI code that ties everything together:&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="nd"&gt;@app.websocket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/ws&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;websocket_endpoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;websocket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;WebSocket&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;websocket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;accept&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;chat_memory&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;websocket&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;websocket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;conversation_history&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="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# Get user message
&lt;/span&gt;            &lt;span class="n"&gt;user_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;websocket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;receive_text&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# {"content": "Hello!"}
&lt;/span&gt;            &lt;span class="n"&gt;chat_memory&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_message&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;message&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="c1"&gt;# Run our PocketFlow
&lt;/span&gt;            &lt;span class="n"&gt;chat_flow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;create_streaming_chat_flow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;chat_flow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run_async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chat_memory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;WebSocketDisconnect&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;User left the chat&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_streaming_chat_flow&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;AsyncFlow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start_node&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;StreamingChatNode&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What happens:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Accept WebSocket connection&lt;/li&gt;
&lt;li&gt;Wait for user messages in a loop&lt;/li&gt;
&lt;li&gt;For each message, run our &lt;code&gt;StreamingChatNode&lt;/code&gt; &lt;/li&gt;
&lt;li&gt;The node handles AI calling + streaming automatically!&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Each WebSocket connection gets its own &lt;code&gt;chat_memory&lt;/code&gt; dictionary with the live connection, latest message, and full conversation history. This lets each user have independent conversations while the AI remembers context.&lt;/p&gt;

&lt;h3&gt;
  
  
  Frontend: The Streaming Magic in JavaScript
&lt;/h3&gt;

&lt;p&gt;On the browser side, we need just a few lines to make streaming work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"aiResponse"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"userInput"&lt;/span&gt; &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"Type your message..."&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;onclick=&lt;/span&gt;&lt;span class="s"&gt;"sendMessage()"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Send&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ws&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WebSocket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ws://localhost:8000/ws&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;aiResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;aiResponse&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// The magic: append each chunk as it arrives&lt;/span&gt;
&lt;span class="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onmessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;aiResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Stream word by word!&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;sendMessage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;userInput&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;aiResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Clear for new response&lt;/span&gt;
    &lt;span class="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;}));&lt;/span&gt;
    &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The streaming happens in &lt;code&gt;ws.onmessage&lt;/code&gt;&lt;/strong&gt; – each time the server sends a text chunk, we append it to the display. That's how you get the "typing" effect!&lt;/p&gt;

&lt;p&gt;Pretty neat, right? You now have all the pieces for a real-time streaming AI chatbot!&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Mission Accomplished! You Built a Real-Time AI Chatbot 🎉
&lt;/h2&gt;

&lt;p&gt;Boom! You just built a &lt;strong&gt;streaming AI chatbot web app&lt;/strong&gt; that feels like magic. No more waiting around – your AI responds word by word, just like the pros!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What you crushed today:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;⚡ &lt;strong&gt;FastAPI + WebSockets&lt;/strong&gt; – Live, two-way chat that never gets old&lt;/li&gt;
&lt;li&gt;🔄 &lt;strong&gt;Async PocketFlow&lt;/strong&gt; – AI calls that don't freeze your app
&lt;/li&gt;
&lt;li&gt;🚀 &lt;strong&gt;Streaming responses&lt;/strong&gt; – Watch the AI "type" in real-time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You've officially joined the ranks of developers building modern, responsive AI web apps. Pretty cool, right?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's next in our series:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://pocketflow.substack.com/p/build-an-llm-web-app-in-python-from" rel="noopener noreferrer"&gt;Part 1&lt;/a&gt;:&lt;/strong&gt; Command-line AI tools ✅&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://pocketflow.substack.com/p/build-an-llm-web-app-in-python-from-b11" rel="noopener noreferrer"&gt;Part 2&lt;/a&gt;:&lt;/strong&gt; Interactive web apps with Streamlit ✅
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Part 3 (You just finished!):&lt;/strong&gt; Real-time streaming ✅&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Part 4 (Coming up!):&lt;/strong&gt; Background tasks for heavy AI work&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Ready for the big leagues?&lt;/strong&gt; Part 4 will tackle those marathon AI tasks – think generating reports or complex analyses that take minutes, not seconds. We'll explore background processing and Server-Sent Events to keep users happy even during the heavy lifting.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;&lt;strong&gt;Want to try this yourself?&lt;/strong&gt; Grab the complete code from the PocketFlow cookbook: &lt;a href="https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-fastapi-websocket" rel="noopener noreferrer"&gt;FastAPI WebSocket Chat Example&lt;/a&gt; You're building some serious AI web development skills! See you in Part 4! 🚀&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>python</category>
      <category>ai</category>
      <category>beginners</category>
    </item>
    <item>
      <title>I just wrote a simple tutorial showing how to build your own Python LLM web app using Streamlit from scratch! https://dev.to/zachary62/build-an-llm-web-app-in-python-from-scratch-part-2-streamlit-fsm-277g</title>
      <dc:creator>Zachary Huang</dc:creator>
      <pubDate>Thu, 05 Jun 2025 07:31:44 +0000</pubDate>
      <link>https://dev.to/zachary62/i-just-wrote-a-simple-tutorial-showing-how-to-build-your-own-python-llm-web-app-using-streamlit-1ahp</link>
      <guid>https://dev.to/zachary62/i-just-wrote-a-simple-tutorial-showing-how-to-build-your-own-python-llm-web-app-using-streamlit-1ahp</guid>
      <description></description>
    </item>
    <item>
      <title>Build an LLM Web App in Python from Scratch: Part 2 (Streamlit &amp; FSM)</title>
      <dc:creator>Zachary Huang</dc:creator>
      <pubDate>Thu, 05 Jun 2025 07:11:26 +0000</pubDate>
      <link>https://dev.to/zachary62/build-an-llm-web-app-in-python-from-scratch-part-2-streamlit-fsm-277g</link>
      <guid>https://dev.to/zachary62/build-an-llm-web-app-in-python-from-scratch-part-2-streamlit-fsm-277g</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Ever wanted to create your own AI-powered image generator, where you call the shots on the final masterpiece? That's exactly what we're building today! We'll craft an interactive web application that lets users generate images from text prompts and then approve or regenerate them – all within a user-friendly interface. We'll use &lt;a href="https://github.com/The-Pocket/PocketFlow" rel="noopener noreferrer"&gt;PocketFlow&lt;/a&gt; for workflow management, and you can check out the &lt;a href="https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-streamlit-fsm" rel="noopener noreferrer"&gt;complete example&lt;/a&gt; we're building.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  1. Want to Build Your Own AI Art Director? Let's Do It! 🎨
&lt;/h2&gt;

&lt;p&gt;Imagine you're an artist, and you have a super-smart assistant (an AI) who can paint anything you describe. You tell it, "Paint a cat and an otter on a sofa!" The AI quickly paints a picture. But maybe the cat looks a bit grumpy, or the otter is on the floor instead of the sofa. Wouldn't it be great if you could tell the assistant, "Make the cat look friendlier," or "Put the otter on the sofa next to the cat," and it would repaint it?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;That's where you come in as the director.&lt;/strong&gt; You get to say "Nope, try again!" or "Perfect, I love it!" This back-and-forth between you and the AI is called &lt;strong&gt;Human-in-the-Loop (HITL)&lt;/strong&gt;, and it's exactly what we're building today.&lt;/p&gt;

&lt;p&gt;We're creating a web app where you can: (1) Type what you want (like "a robot eating pizza on Mars"), (2) See what the AI creates, (3) Approve it or ask for a do-over, and (4) Keep the final masterpiece.&lt;/p&gt;

&lt;p&gt;Don't worry – we're keeping it simple with just a few Python tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;🔧 Streamlit&lt;/strong&gt; – Turns your Python code into a web app. No HTML/CSS needed!&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;🔧 &lt;a href="https://github.com/The-Pocket/PocketFlow" rel="noopener noreferrer"&gt;PocketFlow&lt;/a&gt;&lt;/strong&gt; – Organizes our AI tasks like a recipe (we used this in Part 1)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;🔧 Finite State Machine (FSM)&lt;/strong&gt; – Keeps track of where we are in the process (typing → generating → reviewing → done)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is part 2 of our 4-part journey of LLM web app tutorial:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Part 1:&lt;/strong&gt; Built the basic HITL system in the command line ✅&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Part 2 (You are here!):&lt;/strong&gt; Making it a real web app with Streamlit 🚀&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Part 3:&lt;/strong&gt; Adding real-time chat features&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Part 4:&lt;/strong&gt; Handling long AI tasks in the background&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ready to turn your Python skills into a real web app? Let's dive in!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Want to see the final result? Check out the &lt;a href="https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-streamlit-fsm" rel="noopener noreferrer"&gt;complete code example&lt;/a&gt; we're building.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Streamlit 101: A Web App in a Single Python File!
&lt;/h2&gt;

&lt;p&gt;So we've picked &lt;strong&gt;Streamlit&lt;/strong&gt; to build our AI Image Generator. Why is this perfect for Python folks who don't want to mess with HTML and CSS? Streamlit gives you a full package of UI components (buttons, text boxes, sliders, charts) right out of the box – no web design skills required! Plus, it has this brilliantly simple approach called the "rerun" model.&lt;/p&gt;

&lt;h3&gt;
  
  
  Streamlit's Big Idea: Just Rerun It! 🔄
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Here's the wild part – and it's dumb simple:&lt;/strong&gt; Every time a user does something (clicks a button, types text), Streamlit runs your &lt;em&gt;entire Python script&lt;/em&gt; from top to bottom. Again. Every single time.&lt;/p&gt;

&lt;p&gt;"Wait, the whole script? Isn't that... slow?" you might think. Nope! Streamlit is crazy fast at this, and it actually makes everything simpler. Think of it like a chef who makes your entire meal fresh every time you ask for extra salt – except this chef works at lightning speed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why this "rerun everything" approach rocks:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Feels natural:&lt;/strong&gt; You write normal Python code, step by step&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No complicated UI updates:&lt;/strong&gt; Streamlit automatically redraws everything based on your latest script run&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Easy to understand:&lt;/strong&gt; Your code flows from top to bottom, just like you'd expect&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Your First Streamlit App: "Hello, Clicks!" 👋
&lt;/h3&gt;

&lt;p&gt;Let's see this in action with a simple click counter. Create a file called &lt;code&gt;hello_clicks.py&lt;/code&gt;:&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;streamlit&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;

&lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;title&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello, Clicks!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Think of st.session_state as Streamlit's memory notebook
&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;click_count&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session_state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session_state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;click_count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Click Me!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session_state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;click_count&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

&lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&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;Button has been clicked: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session_state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;click_count&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; times&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Run it with:&lt;/strong&gt; &lt;code&gt;streamlit run hello_clicks.py&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What Happens When You Click? (The Rerun Dance!)
&lt;/h3&gt;

&lt;p&gt;Let's follow what happens when you click the button for the first time, like steps in a dance:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foqr05nyfr10tyy4lnsm8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foqr05nyfr10tyy4lnsm8.png" alt="Image description" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First Time: Setting the Stage (Grey Box):&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You open the app in your &lt;strong&gt;Web Page&lt;/strong&gt; (Browser)&lt;/li&gt;
&lt;li&gt;The Python &lt;strong&gt;Script&lt;/strong&gt; (&lt;code&gt;hello_clicks.py&lt;/code&gt; – our play) runs for the very first time&lt;/li&gt;
&lt;li&gt;It looks at the &lt;strong&gt;SessionState&lt;/strong&gt; (our whiteboard) and sees &lt;code&gt;'click_count'&lt;/code&gt; isn't there&lt;/li&gt;
&lt;li&gt;So, it writes &lt;code&gt;st.session_state.click_count = 0&lt;/code&gt; on the whiteboard&lt;/li&gt;
&lt;li&gt;The script then tells the &lt;strong&gt;Web Page&lt;/strong&gt; to show the button and the text "Count: 0"&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;User Clicks Button:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The &lt;strong&gt;User&lt;/strong&gt; clicks the "Click Me!" button on the web page&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Button Click: Perform the Play Again! (Blue Box):&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Streamlit sees the click and tells the &lt;strong&gt;Script&lt;/strong&gt; (the play) to run all over again, from the first line to the last&lt;/li&gt;
&lt;li&gt;This time, when the script checks the &lt;strong&gt;SessionState&lt;/strong&gt; (whiteboard), it finds &lt;code&gt;'click_count'&lt;/code&gt; (its value is &lt;code&gt;0&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;The line &lt;code&gt;if st.button("Click Me!"):&lt;/code&gt; is true (because &lt;em&gt;this specific&lt;/em&gt; button click is what caused the rerun). So, the script updates &lt;code&gt;st.session_state.click_count&lt;/code&gt; to &lt;code&gt;1&lt;/code&gt; (0 + 1)&lt;/li&gt;
&lt;li&gt;The script finishes, and the &lt;strong&gt;Web Page&lt;/strong&gt; shows the updated UI with "Count: 1"&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Every time you click again, the steps in the "Button Click Rerun" (blue box) repeat. The &lt;code&gt;st.session_state.click_count&lt;/code&gt; keeps going up because &lt;strong&gt;SessionState&lt;/strong&gt; (the whiteboard) remembers its value between each rerun of the play.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;st.session_state&lt;/code&gt;: Your App's Memory Bank 🧠
&lt;/h3&gt;

&lt;p&gt;Since your script reruns from scratch each time, regular Python variables would forget everything. That's where &lt;code&gt;st.session_state&lt;/code&gt; comes in – it's like a personal notepad that Streamlit gives each user to remember important stuff.&lt;/p&gt;

&lt;p&gt;What goes in this memory bank? Anything your app needs to remember: user inputs, calculation results, which screen you're on, and for our image generator – the user's prompt, the generated image, and where we are in the process.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Connecting to Part 1:&lt;/strong&gt; Remember the &lt;code&gt;shared_data&lt;/code&gt; dictionary from our command-line app? &lt;code&gt;st.session_state&lt;/code&gt; is exactly that, but for web apps. Instead of passing data between PocketFlow nodes in an in-mem dictionary, we'll store it in &lt;code&gt;st.session_state&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now that you get how Streamlit works, let's plan out our image generation workflow!&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Planning Our AI Image Generator (The PocketFlow Blueprint)
&lt;/h2&gt;

&lt;p&gt;Time to plan our masterpiece! Before we start clicking buttons and typing prompts, let's map out what our AI Image Generator actually needs to do. Think of this like sketching before you paint – we want to get the big picture right first.&lt;/p&gt;

&lt;p&gt;We'll use &lt;strong&gt;&lt;a href="https://github.com/The-Pocket/PocketFlow" rel="noopener noreferrer"&gt;PocketFlow&lt;/a&gt;&lt;/strong&gt; to organize our workflow. Remember from Part 1? It's our trusty tool for connecting different steps together in a logical sequence.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Basic Journey: From Idea to Image
&lt;/h3&gt;

&lt;p&gt;Our app has a simple but powerful flow: (1) User types what they want, (2) AI generates an image, (3) User reviews it and decides if it's good, and (4) Either keep it or try again. That's it! Here's how it looks:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi168ox1h83o8w92inw7c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi168ox1h83o8w92inw7c.png" alt="Image description" width="529" height="678"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  PocketFlow Nodes: The Building Blocks
&lt;/h3&gt;

&lt;p&gt;In PocketFlow, each step is a "Node" – think of them as LEGO blocks that do specific jobs. Every Node has three parts:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🔧 &lt;code&gt;prep&lt;/code&gt;&lt;/strong&gt; – Grabs what it needs from &lt;code&gt;st.session_state&lt;/code&gt; (our memory bank)&lt;br&gt;
&lt;strong&gt;⚡ &lt;code&gt;exec&lt;/code&gt;&lt;/strong&gt; – Does the actual work (like calling the AI or showing a button)&lt;br&gt;
&lt;strong&gt;📝 &lt;code&gt;post&lt;/code&gt;&lt;/strong&gt; – Saves results back to &lt;code&gt;st.session_state&lt;/code&gt; and signals what's next&lt;/p&gt;

&lt;p&gt;Let's see these in action:&lt;/p&gt;
&lt;h3&gt;
  
  
  Node 1: Getting the User's Idea
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GetPromptNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prep_res&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="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;What do you want to see?&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;prompt&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prep_res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exec_res_prompt&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;task_input&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;exec_res_prompt&lt;/span&gt;  &lt;span class="c1"&gt;# Save to memory
&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;prompt_ready&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;  &lt;span class="c1"&gt;# Signal: we're ready for the next step!
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Node 2: AI Creates the Magic
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GenerateImageNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;prep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shared&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;shared&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;task_input&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Get the prompt from memory
&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prep_res_prompt&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;image_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generate_image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prep_res_prompt&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;image_data&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prep_res_prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exec_res_image_data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;generated_image&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;exec_res_image_data&lt;/span&gt;  &lt;span class="c1"&gt;# Save image
&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;image_generated&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;  &lt;span class="c1"&gt;# Signal: image is ready!
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Node 3: User Reviews the Result
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ReviewImageNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;prep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;display&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;generated_image&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prep_res&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;choice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Approve (a) or Regenerate (r)?&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;choice&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prep_res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exec_res_choice&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;exec_res_choice&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;final_result&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;shared&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;generated_image&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;approved&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;else&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;regenerate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;  &lt;span class="c1"&gt;# Try again!
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Node 4: Celebrating the Final Result
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ShowFinalNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;prep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;display&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;generated_image&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prep_res&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;choice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Start Over (s)?&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;choice&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prep_res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exec_res_choice&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;exec_res_choice&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# Clear memory for fresh start
&lt;/span&gt;            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;task_input&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;generated_image&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;final_result&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
                &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&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;start_over&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Connecting the Dots
&lt;/h3&gt;

&lt;p&gt;PocketFlow makes connecting these steps super clean:&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;create_image_generator_flow&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="c1"&gt;# Create our building blocks
&lt;/span&gt;    &lt;span class="n"&gt;get_prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;GetPromptNode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;generate_image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;GenerateImageNode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;review_image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ReviewImageNode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;show_final&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ShowFinalNode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# Connect them with signals
&lt;/span&gt;    &lt;span class="n"&gt;get_prompt&lt;/span&gt;     &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prompt_ready&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;    &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;generate_image&lt;/span&gt;
    &lt;span class="n"&gt;generate_image&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;image_generated&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;review_image&lt;/span&gt;
    &lt;span class="n"&gt;review_image&lt;/span&gt;   &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;approved&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;        &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;show_final&lt;/span&gt;
    &lt;span class="n"&gt;review_image&lt;/span&gt;   &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;regenerate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;      &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;generate_image&lt;/span&gt;  &lt;span class="c1"&gt;# Loop back!
&lt;/span&gt;    &lt;span class="n"&gt;show_final&lt;/span&gt;     &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;start_over&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;      &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;get_prompt&lt;/span&gt;      &lt;span class="c1"&gt;# Start fresh!
&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Flow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start_node&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;get_prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Web App Challenge: No More &lt;code&gt;input()&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Here's the thing: command-line apps can pause and wait with &lt;code&gt;input("What's next?")&lt;/code&gt;. Web apps can't do that! A web server needs to stay responsive for all users, not freeze while waiting for one person to click a button.&lt;/p&gt;

&lt;p&gt;That's where we need a different approach – and that's exactly what we'll solve with Finite State Machines in the next section!&lt;/p&gt;

&lt;h2&gt;
  
  
  4. FSM to the Rescue: Managing Interactive Web States
&lt;/h2&gt;

&lt;p&gt;Remember the challenge we just hit? Command-line apps can pause and wait with &lt;code&gt;input()&lt;/code&gt;, but web apps need to stay responsive. That's where &lt;strong&gt;Finite State Machines (FSMs)&lt;/strong&gt; come to the rescue!&lt;/p&gt;

&lt;p&gt;Think of an FSM like a roadmap for your app. Instead of getting lost wondering "where am I in the process?", your app always knows exactly which "room" it's in and what should happen next.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;You can see the complete working example at: &lt;a href="https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-streamlit-fsm" rel="noopener noreferrer"&gt;PocketFlow Streamlit FSM Example&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What's a Finite State Machine? 🤔
&lt;/h3&gt;

&lt;p&gt;An FSM is just a fancy way to organize your app into different "modes" or "screens":&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🏠 States&lt;/strong&gt; – Different rooms your app can be in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;initial_input&lt;/code&gt;: "Hey, tell me what you want to see!"&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;user_feedback&lt;/code&gt;: "Here's your image. Like it or want me to try again?"&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;final&lt;/code&gt;: "Awesome! Here's your approved masterpiece!"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;🚪 Transitions&lt;/strong&gt; – How you move between rooms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User clicks "Generate" → move from &lt;code&gt;initial_input&lt;/code&gt; to &lt;code&gt;user_feedback&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;User clicks "Approve" → move from &lt;code&gt;user_feedback&lt;/code&gt; to &lt;code&gt;final&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;User clicks "Start Over" → move from &lt;code&gt;final&lt;/code&gt; back to &lt;code&gt;initial_input&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Visualizing Our App's States
&lt;/h3&gt;

&lt;p&gt;Here's how our image generator flows between states:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbyblsaxw6bustw7reqom.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbyblsaxw6bustw7reqom.png" alt="Image description" width="530" height="713"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  How FSM + Streamlit + PocketFlow Work Together
&lt;/h3&gt;

&lt;p&gt;Here's the beautiful part: &lt;code&gt;st.session_state&lt;/code&gt; becomes our master control center. It tracks both &lt;strong&gt;which state we're in&lt;/strong&gt; AND &lt;strong&gt;all our PocketFlow data&lt;/strong&gt;.&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="c1"&gt;# Initialize our app's memory
&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;state&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session_state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session_state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;initial_input&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;  &lt;span class="c1"&gt;# Start here
&lt;/span&gt;    &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session_state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;task_input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;
    &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session_state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;generated_image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;
    &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session_state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;final_result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we use simple &lt;code&gt;if/elif&lt;/code&gt; blocks to show different screens based on the current state:&lt;/p&gt;

&lt;h3&gt;
  
  
  The Magic: State-Based UI
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# State 1: Getting user input
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session_state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;initial_input&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;🎨 What do you want to see?&lt;/span&gt;&lt;span class="sh"&gt;"&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="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text_area&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Describe your image:&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;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Generate Image&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session_state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;task_input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;

        &lt;span class="c1"&gt;# Run PocketFlow node
&lt;/span&gt;        &lt;span class="n"&gt;image_node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;GenerateImageNode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;image_node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session_state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Move to next state
&lt;/span&gt;        &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session_state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user_feedback&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rerun&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# State 2: User reviews the image
&lt;/span&gt;&lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session_state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user_feedback&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;🖼️ Here&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s your image!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session_state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;generated_image&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;col1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;col2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;columns&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="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;col1&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;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;👍 Approve&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session_state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;final_result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session_state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;generated_image&lt;/span&gt;
            &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session_state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;final&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rerun&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;col2&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;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;🔄 Try Again&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="c1"&gt;# Run PocketFlow node again
&lt;/span&gt;            &lt;span class="n"&gt;image_node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;GenerateImageNode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;image_node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session_state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="c1"&gt;# Stay in same state, just refresh
&lt;/span&gt;            &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rerun&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# State 3: Show final approved image
&lt;/span&gt;&lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session_state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;final&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;🎉 Your Masterpiece!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Image approved!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session_state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;final_result&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;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;🆕 Start Over&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# Reset everything
&lt;/span&gt;        &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session_state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;task_input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;
        &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session_state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;generated_image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;
        &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session_state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;final_result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;
        &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session_state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;initial_input&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rerun&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Power of This Approach
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;🔄 No More Confusion:&lt;/strong&gt; Your app always knows exactly where it is and what should happen next.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;🎯 Clean Code:&lt;/strong&gt; Each state handles its own UI and logic – no messy spaghetti code!&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;🚀 Streamlit Friendly:&lt;/strong&gt; Works perfectly with Streamlit's rerun model.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;🔧 PocketFlow Integration:&lt;/strong&gt; Nodes run smoothly within state transitions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;👥 User-Friendly:&lt;/strong&gt; Each state shows exactly what the user needs to see and do.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This FSM approach transforms our PocketFlow workflow from a linear command-line script into an interactive web experience. Users can bounce between states naturally, and your code stays organized and predictable.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Mission Accomplished! What's Next? 🚀
&lt;/h2&gt;

&lt;p&gt;Boom! We just built a fully interactive AI Image Generator web app. Look what we achieved: (1) Streamlit handles the UI magic, (2) FSM keeps our app states organized, (3) PocketFlow manages our AI workflow, and (4) Users get a smooth, intuitive experience.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What we learned:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;FSMs make interactive web apps way easier to manage&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;st.session_state&lt;/code&gt; is perfect for both FSM states and PocketFlow data&lt;/li&gt;
&lt;li&gt;Streamlit + FSM + PocketFlow = a powerful combo for AI apps&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Our journey so far:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Part 1:&lt;/strong&gt; Built HITL logic with PocketFlow (command line) ✅&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Part 2 (This part!):&lt;/strong&gt; Created an interactive web app with Streamlit + FSM ✅&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Part 3 (Coming up!):&lt;/strong&gt; Real-time features with FastAPI &amp;amp; WebSockets&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Part 4:&lt;/strong&gt; Background processing and progress updates&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ready to add real-time superpowers to your AI apps? Part 3 will show you how FastAPI and WebSockets can create instant, live interactions – think real-time chat with AI or live image generation updates!&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Want to try this out yourself? You can find the complete, runnable code in the PocketFlow cookbook here: &lt;a href="https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-streamlit-fsm" rel="noopener noreferrer"&gt;PocketFlow Streamlit FSM Example&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>python</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Build an LLM Web App in Python from Scratch: Part 1 (Local CLI)</title>
      <dc:creator>Zachary Huang</dc:creator>
      <pubDate>Sun, 01 Jun 2025 22:33:45 +0000</pubDate>
      <link>https://dev.to/zachary62/build-an-llm-web-app-in-python-from-scratch-part-1-local-cli-4824</link>
      <guid>https://dev.to/zachary62/build-an-llm-web-app-in-python-from-scratch-part-1-local-cli-4824</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Ever thought about sprinkling some AI magic into your web app? This is Part 1 of our journey from a basic command-line tool to a full-blown AI web app. Today, we're building a "Human-in-the-Loop" (HITL) chatbot. Think of it as an AI that politely asks for your "OK" before it says anything. We'll use a tiny, super-simple tool called &lt;strong&gt;&lt;a href="https://github.com/The-Pocket/PocketFlow" rel="noopener noreferrer"&gt;PocketFlow&lt;/a&gt;&lt;/strong&gt; – it's just 100 lines of code!&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  So, You Want to Add AI to Your Web App?
&lt;/h2&gt;

&lt;p&gt;Adding AI (especially those smart Large Language Models or LLMs) to web apps can make them super powerful. Imagine smart chatbots, instant content creation, or even coding help. But hold on, it's not just "plug and play." You'll bump into questions like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Where does the AI brain actually live? In the user's browser? On your server?&lt;/li&gt;
&lt;li&gt;  How do you handle AI tasks that need multiple steps?&lt;/li&gt;
&lt;li&gt;  How can users tell the AI what to do or give feedback?&lt;/li&gt;
&lt;li&gt;  What's the deal with remembering conversation history?&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Most importantly:&lt;/strong&gt; How do you stop the AI from saying something totally weird or wrong?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This tutorial series is your guide to building LLM web apps in Python, and we'll tackle these questions head-on! &lt;strong&gt;Here's our 4-part adventure:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Part 1 (That's Today!):&lt;/strong&gt; We'll build a basic Human-in-the-Loop (HITL) chatbot that runs in your computer's command line. We'll use PocketFlow to nail down the core logic, no fancy UI to distract us.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Part 2:&lt;/strong&gt; Let's get this HITL bot on the web! We'll build a user interface using &lt;strong&gt;Streamlit&lt;/strong&gt; (or maybe &lt;strong&gt;Flask&lt;/strong&gt;), and learn how to manage user interactions smoothly.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Part 3:&lt;/strong&gt; Time for real-time action! We'll upgrade our web app with &lt;strong&gt;WebSockets&lt;/strong&gt; (using &lt;strong&gt;FastAPI&lt;/strong&gt;) for instant messages and a slicker chat feel.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Part 4:&lt;/strong&gt; What about AI tasks that take a while? We'll set up &lt;strong&gt;background processing&lt;/strong&gt; and use &lt;strong&gt;Server-Sent Events (SSE)&lt;/strong&gt; with &lt;strong&gt;FastAPI&lt;/strong&gt;. This lets us show users live progress updates, making our app feel truly pro.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To make this journey smooth, we'll use &lt;strong&gt;&lt;a href="https://github.com/The-Pocket/PocketFlow" rel="noopener noreferrer"&gt;PocketFlow&lt;/a&gt;&lt;/strong&gt;. It's not just for simple AI calls; PocketFlow helps you build complex AI workflows that you can actually control. And because it's so small (seriously, just 100 lines for the core framework), you'll always know what's going on. No black boxes!&lt;/p&gt;

&lt;p&gt;The secret sauce to control and quality? A pattern called &lt;strong&gt;Human-in-the-Loop (HITL)&lt;/strong&gt;. With PocketFlow, HITL is easy. It means your AI is like a helpful assistant: it drafts stuff, but &lt;strong&gt;you&lt;/strong&gt; (the human co-pilot) get the final say. Approve, edit, or reject – you're in control before anything goes live. Total quality control, phew!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;You can try out the code for the command-line HITL bot discussed in this part at: &lt;a href="https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-cli-hitl" rel="noopener noreferrer"&gt;PocketFlow Command-Line HITL Example&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Your First HITL App: The "Are You Sure?" Bot
&lt;/h2&gt;

&lt;p&gt;Let's build the most basic HITL app: a chatbot where you approve every single AI response. Imagine it's an AI trying to tell jokes. AI can be funny, but sometimes its jokes might be... well, not funny, or maybe even a little weird or inappropriate. That's where &lt;em&gt;you&lt;/em&gt; come in!&lt;/p&gt;

&lt;h3&gt;
  
  
  The Basic Idea (It's Super Simple!)
&lt;/h3&gt;

&lt;p&gt;Think of a tiny script. The AI suggests a joke, and you, the human, give it a thumbs-up or thumbs-down before it's told. That's HITL in action! If the AI suggests, "Why did the chicken cross the playground? To get to the other slide!" and you think it's a winner, you say "yes!" If it suggests something that makes no sense, or isn't right for your audience, you say "nope!"&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;user_topic_request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Tell me a joke about cats.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;ai_suggested_joke&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;call_llm_for_joke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_topic_request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# You get to approve!
&lt;/span&gt;&lt;span class="n"&gt;approval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;input&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;AI suggests: &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ai_suggested_joke&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;. Tell this joke? (y/n): &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;approval&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;y&lt;/span&gt;&lt;span class="sh"&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;To Audience: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ai_suggested_joke&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;else&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Human said: Nope! That joke isn&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t hitting the mark.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the core! AI suggests a joke, human (&lt;code&gt;input()&lt;/code&gt;) approves, then it's (maybe) told. This simple check is crucial for joke quality and appropriateness. Everything else we build is about making this core idea robust for more complex apps. Visually, it's a little loop, especially for our joke bot:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnnsjlxq2uyot60gos39d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnnsjlxq2uyot60gos39d.png" alt="Image description" width="800" height="651"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's See It Go!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You: Tell me a programming joke.
AI suggests: 'Why do programmers prefer dark mode? Because light attracts bugs!'
Approve? (y/n): y
To Audience: Why do programmers prefer dark mode? Because light attracts bugs!

You: Tell me a joke about vegetables.
AI suggests: 'Why did the tomato turn red? Because it saw the salad dressing! But also, all vegetables are boring.'
Approve? (y/n): n
Human said: Nope! That joke isn't hitting the mark. (And the comment about vegetables is a bit much!)
Regenerating... (Imagine we have logic for this)
AI suggests: 'What do you call a sad strawberry? A blueberry!'
Approve? (y/n): y
To Audience: What do you call a sad strawberry? A blueberry!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See? The human caught that slightly off-kilter joke and the unnecessary comment. That's HITL making sure the AI comedian doesn't bomb too hard!&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Just Plain Python Gets Tangled Fast
&lt;/h3&gt;

&lt;p&gt;When you try to build something more than a quick demo with plain Python &lt;code&gt;while&lt;/code&gt; loops and &lt;code&gt;if&lt;/code&gt; statements, things can turn into a bowl of spaghetti code REAL quick. The main headaches are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;🍝 Spaghetti Code:&lt;/strong&gt; Add features like conversation history, letting users edit, or trying different AI prompts, and your simple loop becomes a monster of nested &lt;code&gt;if/else&lt;/code&gt; blocks. It's tough to read and a nightmare to fix.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;🧱 Not Very Mix-and-Match:&lt;/strong&gt; Your logic for getting input, calling the AI, getting approval, and sending the response all gets jumbled together. Want to test just one piece? Or reuse your "AI calling" bit in another app? Good luck untangling that!&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;🕸️ Hard to Change or Grow:&lt;/strong&gt; Want to add a new step, like checking for bad words before the human sees it? Or offer &lt;em&gt;three&lt;/em&gt; ways to react instead of just "yes/no"? In plain Python, these changes mean carefully rewiring everything, and you'll probably break something.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These problems make it super hard to build AI workflows that are robust and ready for real users.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Enter PocketFlow&lt;/strong&gt; - Think of it as LEGO blocks for your AI workflows.&lt;/p&gt;

&lt;h2&gt;
  
  
  PocketFlow: HITL Workflows, Super Simple!
&lt;/h2&gt;

&lt;p&gt;Trying to build complex AI steps with plain Python is like building a giant LEGO castle without instructions – you'll end up with a wobbly mess. &lt;strong&gt;&lt;a href="https://github.com/The-Pocket/PocketFlow" rel="noopener noreferrer"&gt;PocketFlow&lt;/a&gt;&lt;/strong&gt; is your friendly instruction manual and a set of perfectly fitting LEGO bricks. It helps you build AI workflows in just 100 lines of actual framework code!&lt;/p&gt;

&lt;p&gt;Imagine PocketFlow as running a little workshop:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You have &lt;strong&gt;Nodes&lt;/strong&gt;: These are your specialist workers, each good at one job.&lt;/li&gt;
&lt;li&gt;You have a &lt;strong&gt;Shared Store&lt;/strong&gt;: This is like a central whiteboard where everyone shares notes.&lt;/li&gt;
&lt;li&gt;You have a &lt;strong&gt;Flow&lt;/strong&gt;: This is the manager who tells the workers what to do and in what order.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Node Pattern: Your Specialist Workers
&lt;/h3&gt;

&lt;p&gt;In PocketFlow, each main task is a &lt;strong&gt;Node&lt;/strong&gt;. A Node is like a specialist worker who's a pro at &lt;em&gt;one specific thing&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Here's what a Node looks like in PocketFlow:&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;class&lt;/span&gt; &lt;span class="nc"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;prep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;pass&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prep_res&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;pass&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prep_res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exec_res&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;pass&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don't worry if &lt;code&gt;__init__&lt;/code&gt; or &lt;code&gt;self&lt;/code&gt; look weird; they're just Python things! The important bit is the &lt;code&gt;prep -&amp;gt; exec -&amp;gt; post&lt;/code&gt; cycle:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;code&gt;prep(shared)&lt;/code&gt;: "Hey, I'm about to start. What info do I need from the &lt;code&gt;shared&lt;/code&gt; whiteboard?"&lt;/li&gt;
&lt;li&gt; &lt;code&gt;exec(data_from_prep)&lt;/code&gt;: "Okay, I have my info. Now I'll do my main job!" (Like calling an AI).&lt;/li&gt;
&lt;li&gt; &lt;code&gt;post(shared, prep_res, exec_res)&lt;/code&gt;: "Job's done! I'll write my results back to the &lt;code&gt;shared&lt;/code&gt; whiteboard and tell the manager (the Flow) what happened by returning a simple signal (like a keyword, e.g., &lt;code&gt;"done"&lt;/code&gt; or &lt;code&gt;"needs_approval"&lt;/code&gt;)."&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The Shared Store: The Central Whiteboard (Just a Python Dictionary!)
&lt;/h3&gt;

&lt;p&gt;This is just a plain old Python dictionary (let's call it &lt;code&gt;shared_store&lt;/code&gt;). All our Node workers can read from it and write to it. It's how they pass info—like the user's question, the AI's draft answer, or conversation history—to each other.&lt;/p&gt;

&lt;p&gt;For a math problem, it might start like this:&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;shared_store&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;number_to_process&lt;/span&gt;&lt;span class="sh"&gt;"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;intermediate_result&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;final_answer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As Nodes do their work, they'll update this &lt;code&gt;shared_store&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Flow: The Workshop Manager
&lt;/h3&gt;

&lt;p&gt;A &lt;code&gt;Flow&lt;/code&gt; object is like the manager of your workshop. You tell it which Node kicks things off. When you &lt;code&gt;run&lt;/code&gt; a Flow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It runs that first Node.&lt;/li&gt;
&lt;li&gt;The Node finishes and returns a &lt;em&gt;signal&lt;/em&gt; (just a text string, like &lt;code&gt;"user_approves"&lt;/code&gt; or &lt;code&gt;"try_again"&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;The Flow checks a little map on that Node (called &lt;code&gt;successors&lt;/code&gt;) that says: "If the signal is &lt;code&gt;"user_approves"&lt;/code&gt;, go to the &lt;code&gt;SendResponseNode&lt;/code&gt; next. If it's &lt;code&gt;"try_again"&lt;/code&gt;, go back to the &lt;code&gt;GenerateAINode&lt;/code&gt;."&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You build this map with simple connections:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;node_a &amp;gt;&amp;gt; node_b&lt;/code&gt;: This is a shortcut. If &lt;code&gt;node_a&lt;/code&gt; finishes and gives the usual "all good" signal (PocketFlow calls this &lt;code&gt;"default"&lt;/code&gt;), then &lt;code&gt;node_b&lt;/code&gt; runs next.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;node_a - "custom_signal" &amp;gt;&amp;gt; node_c&lt;/code&gt;: This means if &lt;code&gt;node_a&lt;/code&gt; finishes and shouts &lt;code&gt;"custom_signal"&lt;/code&gt;, then &lt;code&gt;node_c&lt;/code&gt; is up.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Flow keeps this going: run a Node, get its signal, find the next Node. If it gets a signal and can't find a next step, the flow for that path just ends. Easy!&lt;br&gt;
This lets you make workflows that branch off in different directions based on what happens. Like a choose-your-own-adventure for your AI!&lt;/p&gt;

&lt;p&gt;Here's how tiny the &lt;code&gt;Flow&lt;/code&gt; manager class actually is in PocketFlow:&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;class&lt;/span&gt; &lt;span class="nc"&gt;Flow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start_node&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start_node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;start_node&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shared_store&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;current_node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start_node&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;current_node&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;signal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;current_node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shared_store&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;current_node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;current_node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;successors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it! It just needs a &lt;code&gt;start_node&lt;/code&gt; and then it keeps running nodes and following their signals until there's no next step defined for a signal.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tiny Math Example: PocketFlow in Action!
&lt;/h3&gt;

&lt;p&gt;Let's build a super-tiny workflow: take a number, add 5, then multiply by 2.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Worker 1: The Adder Node&lt;/strong&gt;&lt;br&gt;
This Node's job is to add 5.&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;class&lt;/span&gt; &lt;span class="nc"&gt;AddFive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;prep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shared&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;shared&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;number_to_process&lt;/span&gt;&lt;span class="sh"&gt;"&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;current_number&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;current_number&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prep_res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;addition_result&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;intermediate_result&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;addition_result&lt;/span&gt;
        &lt;span class="nf"&gt;print&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;AddFive Node: Added 5, result is &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;addition_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;default&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="c1"&gt;# Signal "all good, continue"
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;prep&lt;/code&gt;: Grabs &lt;code&gt;"number_to_process"&lt;/code&gt; from &lt;code&gt;shared_store&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;exec&lt;/code&gt;: Adds 5.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;post&lt;/code&gt;: Saves the new number as &lt;code&gt;"intermediate_result"&lt;/code&gt; and says &lt;code&gt;"default"&lt;/code&gt; (meaning "continue to the next step in line").&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Worker 2: The Multiplier Node&lt;/strong&gt;&lt;br&gt;
This Node's job is to multiply by 2.&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;class&lt;/span&gt; &lt;span class="nc"&gt;MultiplyByTwo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;prep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shared&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;shared&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;intermediate_result&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;current_number&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;current_number&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prep_res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;multiplication_result&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;final_answer&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;multiplication_result&lt;/span&gt;
        &lt;span class="nf"&gt;print&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;MultiplyByTwo Node: Multiplied, final answer is &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;multiplication_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;done&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="c1"&gt;# Signal "all finished with this path"
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;prep&lt;/code&gt;: Grabs &lt;code&gt;"intermediate_result"&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;exec&lt;/code&gt;: Multiplies by 2.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;post&lt;/code&gt;: Saves it as &lt;code&gt;"final_answer"&lt;/code&gt; and says &lt;code&gt;"done"&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Connecting the Workers (The Assembly Line):&lt;/strong&gt;&lt;br&gt;
Now, let's tell PocketFlow how these Nodes connect.&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="c1"&gt;# First, make our specialist worker Nodes
&lt;/span&gt;&lt;span class="n"&gt;adder_node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AddFive&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;multiplier_node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MultiplyByTwo&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;adder_node&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;multiplier_node&lt;/span&gt;

&lt;span class="c1"&gt;# Create the Flow manager
&lt;/span&gt;&lt;span class="n"&gt;math_flow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Flow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start_node&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;adder_node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Let's get some data for them to work on
&lt;/span&gt;&lt;span class="n"&gt;shared_store_for_math&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;number_to_process&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Starting math game with: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;shared_store_for_math&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="c1"&gt;# Run the flow!
&lt;/span&gt;&lt;span class="n"&gt;math_flow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shared_store_for_math&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Math game finished. Whiteboard looks like: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;shared_store_for_math&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And if you run this, you'd see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Starting math game with: {'number_to_process': 10}
AddFive Node: Added 5, result is 15
MultiplyByTwo Node: Multiplied, final answer is 30
Math game finished. Whiteboard looks like: {'number_to_process': 10, 'intermediate_result': 15, 'final_answer': 30}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See? Each Node (worker) is simple. The &lt;code&gt;shared_store&lt;/code&gt; (whiteboard) carries the number through. The &lt;code&gt;Flow&lt;/code&gt; (manager) made sure &lt;code&gt;AddFive&lt;/code&gt; ran, then &lt;code&gt;MultiplyByTwo&lt;/code&gt;, because of the &lt;code&gt;"default"&lt;/code&gt; signal. If &lt;code&gt;MultiplyByTwo&lt;/code&gt; had other signals for different outcomes, the flow could branch off!&lt;/p&gt;

&lt;p&gt;Now we know how PocketFlow uses Nodes, a Shared Store, and a Flow to handle steps and pass data. Let's use these exact same ideas for our HITL approval chatbot!&lt;/p&gt;

&lt;h2&gt;
  
  
  Building Your HITL "Approval Bot" Workflow
&lt;/h2&gt;

&lt;p&gt;Alright, enough theory! Let's get our hands dirty and build that HITL workflow with PocketFlow for our joke bot. This time, we'll start by thinking about what information our Nodes will need to share.&lt;/p&gt;

&lt;h3&gt;
  
  
  First, Design the Shared Store (Our Whiteboard)
&lt;/h3&gt;

&lt;p&gt;For our interactive joke generator, the &lt;code&gt;shared_store&lt;/code&gt; dictionary (our central whiteboard) needs to keep track of a few key things as the conversation flows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;topic&lt;/code&gt;: What kind_of_joke the user wants (e.g., "cats", "programming"). This will be filled in by our first Node.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;current_joke&lt;/code&gt;: The latest joke the AI cooked up. This will be updated by the joke generation Node.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;disliked_jokes&lt;/code&gt;: A list of jokes about the current &lt;code&gt;topic&lt;/code&gt; that the user already said "no" to. This helps the AI avoid telling the same bad joke twice. It will be updated by our feedback Node.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;user_feedback&lt;/code&gt;: The user's latest decision (e.g., &lt;code&gt;"approve"&lt;/code&gt; or &lt;code&gt;"disapprove"&lt;/code&gt;). Also updated by the feedback Node.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's a peek at what it might look like while the bot is running:&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;shared_store&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;topic&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;dogs&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;current_joke&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;What do you call a dog magician? A labracadabrador!&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;disliked_jokes&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Why did the dog cross the road? To get to the barking lot!&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_feedback&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each Node we design will read the information it needs from this &lt;code&gt;shared_store&lt;/code&gt; and write its results back here for other Nodes to use. This way, everyone's on the same page!&lt;/p&gt;

&lt;h3&gt;
  
  
  The Three Core Nodes: Our Specialist Joke Crafters
&lt;/h3&gt;

&lt;p&gt;We'll use three main Nodes for our joke-making machine:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;GetTopicNode&lt;/strong&gt;: Asks the user what they want a joke about.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;GenerateJokeNode&lt;/strong&gt;: Cooks up a joke using the AI.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;GetFeedbackNode&lt;/strong&gt;: Asks the user if the joke was a hit or a miss.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's build them one by one!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. GetTopicNode - The Idea Catcher&lt;/strong&gt; 🎤&lt;/p&gt;

&lt;p&gt;This Node's only job is to ask the user for a joke topic.&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;class&lt;/span&gt; &lt;span class="nc"&gt;GetTopicNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;prep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;topic&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="bp"&gt;None&lt;/span&gt;
        &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;current_joke&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="bp"&gt;None&lt;/span&gt;
        &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;disliked_jokes&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="p"&gt;[]&lt;/span&gt; 
        &lt;span class="n"&gt;shared&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_feedback&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="bp"&gt;None&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, the &lt;code&gt;prep&lt;/code&gt; method. Think of this as setting the table before a meal. It just cleans up any old information from a previous joke in our &lt;code&gt;shared&lt;/code&gt; whiteboard so we start fresh for the new topic.&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;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_prep_res&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;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;What topic shall I jest about today? &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, &lt;code&gt;exec&lt;/code&gt; does the main work: it simply asks the user for a topic using &lt;code&gt;input()&lt;/code&gt; and returns whatever they type.&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;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_prep_res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;topic_input&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;topic&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;topic_input&lt;/span&gt;
        &lt;span class="nf"&gt;print&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;Alright, a joke about &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;topic_input&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;! Coming right up...&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;generate_joke&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, &lt;code&gt;post&lt;/code&gt; takes the &lt;code&gt;topic_input&lt;/code&gt; from &lt;code&gt;exec&lt;/code&gt;, saves it to our &lt;code&gt;shared&lt;/code&gt; whiteboard under the key &lt;code&gt;"topic"&lt;/code&gt;, prints a little message, and then returns the signal &lt;code&gt;"generate_joke"&lt;/code&gt;. This signal tells the Flow manager, "Okay, we have a topic, now go to the node that generates jokes!"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. GenerateJokeNode - The AI Comedy Chef&lt;/strong&gt; 🤖&lt;/p&gt;

&lt;p&gt;This Node grabs the topic (and any jokes the user &lt;em&gt;didn't&lt;/em&gt; like about it) and tells the AI to whip up a new joke.&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;class&lt;/span&gt; &lt;span class="nc"&gt;GenerateJokeNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;prep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;topic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;topic&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;something random&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;disliked&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;disliked_jokes&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="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&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;Tell me a short, funny joke about: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;topic&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;disliked&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;avoid_these&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;; &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;disliked&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; Please try to make it different from these: [&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;avoid_these&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;].&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In &lt;code&gt;prep&lt;/code&gt;, this Node looks at the &lt;code&gt;shared&lt;/code&gt; whiteboard for the &lt;code&gt;"topic"&lt;/code&gt; and any &lt;code&gt;"disliked_jokes"&lt;/code&gt;. It then crafts a &lt;code&gt;prompt&lt;/code&gt; (a set of instructions) for our AI. If there &lt;em&gt;are&lt;/em&gt; disliked jokes, it cleverly tells the AI, "Hey, avoid jokes like these ones the user didn't like!"&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;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;joke_prompt&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;call_llm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;joke_prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, &lt;code&gt;exec&lt;/code&gt; is where the AI magic would happen. We take the &lt;code&gt;joke_prompt&lt;/code&gt; from &lt;code&gt;prep&lt;/code&gt; and send it to our &lt;code&gt;call_llm&lt;/code&gt; function. This function would be responsible for talking to a real AI service (like OpenAI, Anthropic, etc.) and returning its response.&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;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_prep_res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ai_joke&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;current_joke&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;ai_joke&lt;/span&gt;
        &lt;span class="nf"&gt;print&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;🤖 AI Suggests: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ai_joke&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;get_feedback&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And in &lt;code&gt;post&lt;/code&gt;, we save the &lt;code&gt;ai_joke&lt;/code&gt; to our &lt;code&gt;shared&lt;/code&gt; whiteboard as &lt;code&gt;"current_joke"&lt;/code&gt;, print it out for the user to see, and then return the signal &lt;code&gt;"get_feedback"&lt;/code&gt;. This tells the Flow manager, "Joke's ready! Go to the node that gets the user's opinion."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. GetFeedbackNode - The Joke Judge&lt;/strong&gt; 🤔&lt;/p&gt;

&lt;p&gt;This Node shows the joke and asks the user: thumbs up or thumbs down? Based on their answer, it decides if we should try another joke on the same topic or if we're done.&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;class&lt;/span&gt; &lt;span class="nc"&gt;GetFeedbackNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;prep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;prep&lt;/code&gt; is super simple here. &lt;code&gt;GenerateJokeNode&lt;/code&gt; already showed the joke, so there's nothing to set up. We just pass &lt;code&gt;None&lt;/code&gt;.&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;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_prep_res&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&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;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Did you like this joke? (yes/no): &lt;/span&gt;&lt;span class="sh"&gt;"&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;lower&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;decision&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;yes&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;y&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;no&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;n&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;decision&lt;/span&gt; 
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hmm, I didn&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t catch that. Please type &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;yes&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; or &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;no&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;exec&lt;/code&gt; asks the user if they liked the joke. It waits for a clear "yes" (or "y") or "no" (or "n") before moving on.&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;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_prep_res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_decision&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;user_decision&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;yes&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;y&lt;/span&gt;&lt;span class="sh"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;🎉 Hooray! Glad you liked it!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;shared&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_feedback&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;approve&lt;/span&gt;&lt;span class="sh"&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;joke_approved&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;else&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;😅 Oops! My circuits must be crossed. Let me try again...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;shared&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_feedback&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;disapprove&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="n"&gt;current_joke&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;current_joke&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;current_joke&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; 
                &lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setdefault&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;disliked_jokes&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;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current_joke&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;regenerate_joke&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, &lt;code&gt;post&lt;/code&gt; looks at the &lt;code&gt;user_decision&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  If it's a "yes", we celebrate, store &lt;code&gt;"approve"&lt;/code&gt; in &lt;code&gt;shared["user_feedback"]&lt;/code&gt;, and return the signal &lt;code&gt;"joke_approved"&lt;/code&gt;. This means we're done with &lt;em&gt;this&lt;/em&gt; joke topic.&lt;/li&gt;
&lt;li&gt;  If it's a "no", we apologize, store &lt;code&gt;"disapprove"&lt;/code&gt;, add the failed &lt;code&gt;current_joke&lt;/code&gt; to our &lt;code&gt;shared["disliked_jokes"]&lt;/code&gt; list (so &lt;code&gt;GenerateJokeNode&lt;/code&gt; knows not to repeat it), and return the signal &lt;code&gt;"regenerate_joke"&lt;/code&gt;. This tells the Flow manager: "Back to the joke drawing board (the &lt;code&gt;GenerateJokeNode&lt;/code&gt;) for the same topic!"&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Connecting the Flow: Drawing the Joke Map &amp;amp; Running It! 🗺️
&lt;/h3&gt;

&lt;p&gt;Now we tell PocketFlow how to get from one Node to another using the signals we just defined, and then we'll run it.&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="c1"&gt;# 1. Make our specialist Node workers
&lt;/span&gt;&lt;span class="n"&gt;get_topic_node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;GetTopicNode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;generate_joke_node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;GenerateJokeNode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;get_feedback_node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;GetFeedbackNode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# 2. Draw the paths for the Flow manager
&lt;/span&gt;&lt;span class="n"&gt;get_topic_node&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;generate_joke&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;generate_joke_node&lt;/span&gt;
&lt;span class="n"&gt;generate_joke_node&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;get_feedback&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;get_feedback_node&lt;/span&gt;
&lt;span class="n"&gt;get_feedback_node&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;regenerate_joke&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;generate_joke_node&lt;/span&gt;

&lt;span class="c1"&gt;# 3. Let's Run Our HITL Joke Bot!
&lt;/span&gt;&lt;span class="n"&gt;shared&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;topic&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;current_joke&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;disliked_jokes&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user_feedback&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;hitl_joke_flow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Flow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start_node&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;get_topic_node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;hitl_joke_flow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shared&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;🎤 Joke session over! Here&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s what happened: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;shared&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is like drawing a map for our Flow manager and then telling it to start the journey:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; We create our three Node workers.&lt;/li&gt;
&lt;li&gt; We use the &lt;code&gt;node - "signal" &amp;gt;&amp;gt; next_node&lt;/code&gt; pattern to define the paths.&lt;/li&gt;
&lt;li&gt; We set up our &lt;code&gt;shared&lt;/code&gt; whiteboard.&lt;/li&gt;
&lt;li&gt; We create our &lt;code&gt;Flow&lt;/code&gt; manager (using the &lt;code&gt;Flow&lt;/code&gt; class we saw earlier), telling it to kick things off with &lt;code&gt;get_topic_node&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; We call &lt;code&gt;hitl_joke_flow.run(shared)&lt;/code&gt;. The Flow manager now takes over! It runs the Nodes, listens for their signals, and follows the map. The &lt;code&gt;shared&lt;/code&gt; dictionary gets updated live.&lt;/li&gt;
&lt;li&gt; When the flow naturally ends (because &lt;code&gt;"joke_approved"&lt;/code&gt; has no next step), the &lt;code&gt;run&lt;/code&gt; method finishes, and we print out the final state of our whiteboard.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And that's it! You've built a Human-in-the-Loop chatbot using PocketFlow. Each piece is small, understandable, and they all work together to create a flexible workflow where the human is always in control.&lt;/p&gt;

&lt;h2&gt;
  
  
  From CLI to Web App: What's Next &amp;amp; Key Takeaways
&lt;/h2&gt;

&lt;p&gt;You've built a cool command-line bot where you're the boss! But most people don't hang out in command prompts, right? They use web apps! The great news is that the HITL logic you've built with PocketFlow is the &lt;em&gt;engine&lt;/em&gt; that can power a web UI too.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Journey Ahead:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Part 2 (Web UI):&lt;/strong&gt; We'd take this exact HITL flow and hook it up to something like &lt;strong&gt;Streamlit&lt;/strong&gt; or &lt;strong&gt;Flask&lt;/strong&gt;. Instead of &lt;code&gt;input()&lt;/code&gt;, users would click buttons (&lt;code&gt;st.button("👍 Approve")&lt;/code&gt;). The underlying PocketFlow logic remains largely the same!&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Part 3 (Real-Time):&lt;/strong&gt; Time for real-time action! We'll upgrade our web app with &lt;strong&gt;WebSockets&lt;/strong&gt; (using &lt;strong&gt;FastAPI&lt;/strong&gt;) for instant messages and a slicker chat feel. The core HITL decision points (AI drafts, human approves) are still there.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Part 4 (Background Tasks &amp;amp; Progress Updates):&lt;/strong&gt; What about AI tasks that take a while? We'll set up &lt;strong&gt;background processing&lt;/strong&gt; and use &lt;strong&gt;Server-Sent Events (SSE)&lt;/strong&gt; with &lt;strong&gt;FastAPI&lt;/strong&gt;. This lets us show users live progress updates, making our app feel truly pro. Your PocketFlow &lt;code&gt;Flow&lt;/code&gt; is a key part of each user's session.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In Part 2, we'll take our HITL bot to the web using Streamlit! This means building a proper user interface where users can interact with buttons and see responses. We'll even explore how such a UI could handle more than just text, like displaying images, making our AI interactions richer.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Want to try out this exact command-line HITL bot yourself? You can find the complete, runnable code in the PocketFlow cookbook here: &lt;a href="https://github.com/The-Pocket/PocketFlow/tree/main/cookbook/pocketflow-cli-hitl" rel="noopener noreferrer"&gt;PocketFlow Command-Line HITL Example&lt;/a&gt;. Go ahead, build your own HITL apps!&lt;/em&gt; &lt;/p&gt;

</description>
      <category>webdev</category>
      <category>beginners</category>
      <category>ai</category>
      <category>python</category>
    </item>
  </channel>
</rss>
