<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[The Computist Journal: 💻 Coding for Nerds]]></title><description><![CDATA[Articles on algorithms, coding techniques, software, etc.]]></description><link>https://blog.apiad.net/s/code</link><image><url>https://substackcdn.com/image/fetch/$s_!qNGT!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F582c72c0-c120-4ea8-ae6b-376a025250bb_1024x1024.png</url><title>The Computist Journal: 💻 Coding for Nerds</title><link>https://blog.apiad.net/s/code</link></image><generator>Substack</generator><lastBuildDate>Mon, 27 Apr 2026 13:11:46 GMT</lastBuildDate><atom:link href="https://blog.apiad.net/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Alejandro Piad Morffis]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[apiad@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[apiad@substack.com]]></itunes:email><itunes:name><![CDATA[Alejandro Piad Morffis]]></itunes:name></itunes:owner><itunes:author><![CDATA[Alejandro Piad Morffis]]></itunes:author><googleplay:owner><![CDATA[apiad@substack.com]]></googleplay:owner><googleplay:email><![CDATA[apiad@substack.com]]></googleplay:email><googleplay:author><![CDATA[Alejandro Piad Morffis]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[Realtime 3D in Pure Python + Numpy]]></title><description><![CDATA[Just a fun side-project that went way too far-as it happens.]]></description><link>https://blog.apiad.net/p/realtime-3d-in-pure-python-numpy</link><guid isPermaLink="false">https://blog.apiad.net/p/realtime-3d-in-pure-python-numpy</guid><dc:creator><![CDATA[Alejandro Piad Morffis]]></dc:creator><pubDate>Tue, 07 Apr 2026 15:59:45 GMT</pubDate><enclosure url="https://images.unsplash.com/photo-1682846133858-c0599bc99874?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxMHx8M2R8ZW58MHx8fHwxNzc1NTc3NTM2fDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://images.unsplash.com/photo-1682846133858-c0599bc99874?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxMHx8M2R8ZW58MHx8fHwxNzc1NTc3NTM2fDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://images.unsplash.com/photo-1682846133858-c0599bc99874?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxMHx8M2R8ZW58MHx8fHwxNzc1NTc3NTM2fDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1682846133858-c0599bc99874?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxMHx8M2R8ZW58MHx8fHwxNzc1NTc3NTM2fDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1682846133858-c0599bc99874?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxMHx8M2R8ZW58MHx8fHwxNzc1NTc3NTM2fDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1682846133858-c0599bc99874?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxMHx8M2R8ZW58MHx8fHwxNzc1NTc3NTM2fDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1456w" sizes="100vw"><img src="https://images.unsplash.com/photo-1682846133858-c0599bc99874?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxMHx8M2R8ZW58MHx8fHwxNzc1NTc3NTM2fDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" width="3456" height="2234" data-attrs="{&quot;src&quot;:&quot;https://images.unsplash.com/photo-1682846133858-c0599bc99874?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxMHx8M2R8ZW58MHx8fHwxNzc1NTc3NTM2fDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:2234,&quot;width&quot;:3456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;a sculpture of a person on top of a ball&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="a sculpture of a person on top of a ball" title="a sculpture of a person on top of a ball" srcset="https://images.unsplash.com/photo-1682846133858-c0599bc99874?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxMHx8M2R8ZW58MHx8fHwxNzc1NTc3NTM2fDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1682846133858-c0599bc99874?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxMHx8M2R8ZW58MHx8fHwxNzc1NTc3NTM2fDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1682846133858-c0599bc99874?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxMHx8M2R8ZW58MHx8fHwxNzc1NTc3NTM2fDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1682846133858-c0599bc99874?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxMHx8M2R8ZW58MHx8fHwxNzc1NTc3NTM2fDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption"><em>Just a random, completely unrelated stock photo, because I&#8217;m cool now.</em> Photo by <a href="https://unsplash.com/@rozetsky">Ant Rozetsky</a> on <a href="https://unsplash.com">Unsplash</a></figcaption></figure></div><p>Today I want to tell you a different kind of story. It&#8217;s not about machine learning, large language models, algorithms, or theory of computer science.</p><p>It&#8217;s about a side project that I&#8217;ve been building for a couple of weeks that made me fall in love again with an ancient love of mine. The quick and easy way to explain it is this: a performance-focused graphics engine for data-driven visualizations in Python.</p><p>But that&#8217;s only the surface. If you want to see the coold demos and the technical description, feel free to scroll down. But if you want to know the story behind it, let me start from the beginning.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://blog.apiad.net/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://blog.apiad.net/subscribe?"><span>Subscribe now</span></a></p><h2>The Origin Story</h2><p>So, this starts back in undergrad, before I did anything related to machine learning or optimization or statistics. My first love was actually computer graphics. I had &#8220;learned to code&#8221; like, I don&#8217;t know, at 11 or 12, and for the first five years or so, before getting to college and actually learning to code, all my &#8220;coding&#8221; was basically tiny games. It was RPG Maker back then&#8212;who remembers that?</p><p>I always wanted to be a game developer, as you may imagine, and I think that&#8217;s probably the main motivation why I studied Computer Science. There are two kind of people who want to study Computer Science, as a matter of fact. One is people who love games&#8212;and the other is, of course, people who hate games; there are no in-betweens.</p><p>I was the loving-games kind and all I wanted to build games for a life. So when I was in first year, after actually learning some real coding, my first kind of large project was a game engine. This was before Unity, before even XNA&#8212;who remembers that?&#8212;this was when .NET was getting started, and I wrote a quick and dirty game engine in pure C# that talked native DirectX 11.</p><p>It was very cheap, a disaster of architecture almost surely, but it taught me the basics of how to construct a scene graph, how to animate a camera, how to do lighting, how to write very basic shaders. I learned a ton and basically fell in love with computer graphics.</p><p>I ended up doing my diploma thesis in computer graphics&#8212;screen-based global illumination, a couple of years before NVIDIA came up with ray tracing on the GPU, which basically killed that whole area of research. And I also did my Master&#8217;s on global illumination and some data structures for the GPU, but after graduation I quickly switched research towards machine learning and AI, which, you can imagine, this 2014, and deep learning was just on the rise. The rest is history, as they say.</p><p>And then, here I was this past week thinking about old projects that I used to have fun with when I was in college, and trying to remember what it felt like to code back then, no LLMs, no internet for the most part even. The time where I&#8217;ve been the most fun was probably when I dabbed into procedural generation of cities, mountains, lakes, and&#8230; stuff, in the late 2012. This was at the early era of PCG, and I never got to do anything with that other than a few tutorials and a few lessons that I taught at University.</p><p>I played with Unity for a couple of years, but nothing too serious&#8211;I think I was actually one of the first people in my University to even install Unity, and I even taught a couple of Master&#8217;s courses on it. I participated in a couple of game jams, but after 2017 or so I stopped doing graphics all together. And I&#8217;ve been doing machine learning since.</p><p>But, in any case, I kind of forgot about computer graphics all along. At least during day-worked. So there was I last weeek, remembering that and asking how hard would it be to actually make a graphics engine in Python, some quick hack like my undergrad projects. I did a bit of research and I discovered that Python is, as of 2026, in a very good position to build a graphics engine, and not just a crapy one, but one that is actually fast. We have WGPU now&#8212;the spiritual and practical succesor of OpenGL (who remembers that?), which has native suppotr for GPU-accelerated graphics in Linux.</p><p>I basically did a plan and sat for three days to hack this thing.</p><h2>The Engine</h2><p>My first idea was to have a Rust backend for all the graphics engine stuff&#8212;the rendering loop, materials, lights&#8212;but I quickly decided to drop that idea because getting Rust and Python to talk to each other was becoming increasingly harder and harder, and I really wanted to finally see a damn cube rendering on my screen.</p><p>So I decided to switch completely to Python. But since I&#8217;m a grown-up now, I have to find some kind of serious objective for making something like this. I decided I didn&#8217;t want to make a typical graphics engine where you have a scene graph with hierarchies of entities and properties, and you simply render all of them. No, that is way too 2000s.</p><p>I decided I wanted to do a very fast, data-driven visualization tool purely based on the Entity-Component-System (ECS) paradigm and make it extremely performant, so it would focus on big data-driven simulations like N-body simulations, chemical and physics experiments, AI pathfinding and agents, you know, grown-up stuff like that.</p><p><em>(But actually, all I wanted was to play with WGPU and draw some cubes in Python. Wink, wink.)</em></p><p>This framing gave me two things, though. My solution doesn&#8217;t have to be very fancy as a game engine, we don&#8217;t need to be able to like load skeletal animations or stuff like that. It&#8217;s not actually a game engine; it&#8217;s a graphics engine with at best some interaction logic. But it still lets you do some cool stuff, even if all you can render is blocks and spheres. When you can render thousands of them running very fast on the GPU, you can do some cool stuff. So this is the motivation, and now let me show you what I have.</p><h3>Deep Dive</h3><p>So here is <strong>manifold</strong>&#8212;short Manifold Graphics if you want. It&#8217;s a Python library built on top of WGPU, a graphics engine based on the Entity-Component-System paradigm.</p><p>If you have never heard about it, ECS is a completely different way of writing code that is especially tailored for video games, but it is very little known outside of the game development world. And its awesome.</p><p>In a typical business code, you have entities who own their data, and you usually have behavior associated to entities; so entities also own their behavior&#8212;this is the basic Object-Oriented Programming paradigm where objects own their data and their methods. And if you want to do something with an object, you have to call methods on the object so the object guarantees the instance invariances.</p><p>Since OOP was basically the ONE programming paradigm of the 90s and early 2000s&#8212;when the videogame industry really exploded&#8212;it is only normal that we started writing games like this. But there is a problem with OOP (well, many problems, but one in particular that matters for our discussion).</p><p>When you have 10,000 objects, each of them with more or less the same structure, e.g., they are physical particles bouncing with each other, or little zerlings comming to your base, you simply <em>cannot</em> update them fast enough. For example, making a physics simulation out of this is extremely slow if you have to go to each particle and update its velocity, its scale, its rotation, etc. <strong>You</strong>&#8217;ll end up doing thousands of tiny method calls, thrashing your cache, and issuing lots of super small copies to GPU for drawing.</p><p>What you want is to vectorize this operation. You would like to have all of the objects&#8217; data in a single NumPy matrix, and you want to write a very, very efficient vectorized code that doesn&#8217;t do any loop and just updates everything at once. THen copy all the data to the GPU and issue a single draw call that renders all objects parameterized by their positions, rotations, etc. Chef kiss.</p><p>This is the Entity-Component-System paradigm at its core. It completely flips the responsibilities from standard OOP The <strong>components</strong> are just flat storage of data (rows in a matrix) and the <strong>entities</strong> are just pointers to a row where all of their data lives. Then the systems are methods that act on a subset of entities using heavily vectorized code, because each system deals with a large number of equally-structured entities, and they don&#8217;t care which is which.</p><p>In <strong>manifoldx</strong>, each system is a Python method that receives a subset of entities that have some combination of components. For example, if you want to process all of the particles in a simulation, you write a system that receives entities that have the <code>Particle</code> component, perhaps also a <code>Transform</code> component. In the transform component, you will have the position, rotation, scale, and the particle component will store simulation-specific data like velocity, temperature, momentum, etc.</p><p>The key to high performance in ECS is to avoid looping as much as possible. You assume all of the components of the entities in a system have exactly the same layout, so what you get is really a view of a matrix, and you write vectorized code. You add something to all them, you multiply all them by something, or in general you compute some matrix operations on them. All at once.</p><p>And if you can write your code like this, then you get a very, very fast rendering loop because instead of making one method invocation per entity, you make one method invocation per <em>archetype</em>, that is, per combination of components, which is a couple of order of magnitude less that your entities count.</p><p>Here&#8217;s a minimal example showing how the ECS works in <strong>manifoldx</strong>:</p><pre><code><code>import manifoldx as mx
import numpy as np

engine = mx.Engine("Cubes")
engine.camera.zoom(0.1)

# These are all static things that are created
# and stored in memory once
mesh = mx.geometry.sphere(1)
material = mx.material.phong(mx.colors.BLUE)

# Custom component, gets registered in engine to keep track
# Only used for reflection on the values
@engine.component
class Particle:
    velocity: mx.Vector3
    angular: mx.Vector3
    life: mx.Float

# This runs every frame
@engine.system
def particle_lifecycle(query: mx.Query[Particle, Transform], dt: float):
    query[Particle].life -= dt  # Single vectorial operation
    query[Transform].position += query[Particle].velocity * dt
    query[Transform].rotation += Transform.rotation(euler=query[Particle].angular * dt)
    query[Transform].scale = query[Particle].life / 10.0

    # Destroy all dead particles at once
    engine.destroy(query[Particle].life &lt;= 0)

    # Now we create lots of particles
    N = int(100 * dt)

    # This will in principle reuse the buffers for dead entities,
    # but will expand the buffer if necessary
    engine.spawn(
        Mesh(mesh),
        Material(material),
        Transform(pos=(0, 0, 0), scale=(1, 1, 1)),
        Particle(
            velocity=np.random.uniform(-5, 5, (N, 3)),
            angular=np.random.uniform(-2, 2, (N, 3)),
            life=np.random.rand(N) * 10,
        ),
        n=N,
    )

    # Update camera
    engine.camera.orbit(45 * dt, 0)</code></code></pre><p>That&#8217;s it. A single line of code to update all positions at once. Notice the <code>query</code> argument that defines which entities you get (all entities with both a <code>Transform</code> and a <code>Particle</code> component). And here is how that looks like:</p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;5c6ae9e4-c010-46dd-a47d-6b5b87679965&quot;,&quot;duration&quot;:null}"></div><p>In a real simulation, you can have, say 10 systems, but you have 10,000 or 100,000 entities, and you know you can do very fast vectorized updates in NumPy for all them, 10 times each frame.</p><p>For example, if you have 500 particles and you want to do N-body simulation, computing the 500-squared gravity interactions 60 times per second in Python is suicide. But if you do it in NumPy, then you get something that runs in a few milliseconds. A quarter million interactions computed 60 times per second. In Python.</p><p>To make it really efficient, you need to also avoid copying or moving data; it&#8217;s all masking and clever NumPy layout that keeps all of the memory in one place, and you are just seeing fragments of that memory in each system.</p><p>The other key idea is that you don&#8217;t modify anything in a system. That line where position is set, doesn&#8217;t really write back to the matrix. All it does is compute the right-hand side and then you issue a command that will be run at the end of all the systems, before frame rendering happens. This allows to write pure threaded parallelism, because you can run several systems in different threads&#8212;they are all reading the same data, but they aren&#8217;t writing to the buffers, which is great since Python has real support for multi-threading now in 2026 (after 35 years!).</p><h2>Showcase</h2><p>That is the basic idea. Now lets see some examples. AS of today, version 0.2, <strong>manifoldx</strong> has some basic shapes like cubes, spheres, and planes, and support for basic PBR lighting, camera controls, and that&#8217;s basically it.</p><p>All the engine realy does is set up this somewhat clever inversion of logic that forces you to write very efficient code, and the magic is in what you do inside the systems.</p><p>So let me show you three examples.</p><h3>1. N-Body Gravitational Simulation</h3><p>The first is an N-body simulation. All gravity computation happens in a single NumPy block with no Python loops. The only relevant part of the code is the gravity system, that looks something like this.</p><pre><code><code>@engine.system
def nbody_gravity(query, dt):
    pos = query[Transform].pos.data  # (N, 3)

    # All-pairs position differences: (N, N, 3)
    diff = pos[None, :] - pos[:, None]
    dist = np.linalg.norm(diff, axis=2)

    # Force magnitude: G * m_i * m_j / r&#178;
    force_mag = G * mass_prod / np.maximum(dist, SOFTENING)**2

    # Net force = sum over all other bodies
    net_force = (force_mag[:, :, None] * diff / dist[:, :, None]).sum(axis=1)

    velocities += (net_force / masses[:, None]) * dt
    query[Transform].pos += velocities * dt</code></code></pre><p>This runs 500 bodies with 250,000 force pair computations at 60fps.</p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;3587e1ae-4af3-49a9-b39e-a9d495d24bc1&quot;,&quot;duration&quot;:null}"></div><h3>2. Ideal Gas Simulation</h3><p>The second example is an ideal gas with elastic collisions inside a bounding box. Again, all running without a single for loop. Collision detection and impact resolution in vectorizednumpy operations.</p><pre><code><code>@engine.system
def gas_physics(query, dt):
    pos = query[Transform].pos.data

    # Wall collisions: vectorized mask
    below = (pos + velocities * dt) &lt; -BOX_HALF
    above = (pos + velocities * dt) &gt; BOX_HALF

    # Here we avoid branching and use masking instead
    velocities[below] = np.abs(velocities[below]) * RESTITUTION
    velocities[above] = -np.abs(velocities[above]) * RESTITUTION

    # Particle collisions: find overlapping pairs
    diff = pos[None, :] - pos[:, None]
    dist = np.linalg.norm(diff, axis=2)
    overlap = dist &lt; 2 * PARTICLE_RADIUS
    i_idx, j_idx = np.where(np.triu(overlap))

    # Resolve collisions with impulse
    # ... (collision resolution code)
    # ... (also vectorized)

    query[Transform].pos += velocities * dt</code></code></pre><p>Here&#8217;s how that looks like.</p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;86436879-ae58-4d3d-9616-fac2e5dfcf7c&quot;,&quot;duration&quot;:null}"></div><h3>3. Boids Flocking</h3><p>The third example is a Boids simulation with emergent flocking behavior. This is the one that strikes me the most because boids simulation is often compute-heavy. Each individual entity must keep track of a subset of neighbors and adjust behavior based on them, not the whole set of entities. But again, a bit of numpy magic lets us vectorize the crap out of this and simulate 300 boids at 60 frames per second.</p><pre><code><code>@engine.system
def boids_physics(query, dt):
    # Separation, alignment, cohesion as vectorized tensor ops
    diff = pos[None, :] - pos[:, None]  # (N, N, 3)
    dist_sq = (diff * diff).sum(axis=2)

    neighbors = dist_sq &lt; PERCEPTION_SQ

    # Separation (1/dist&#178; weighted)
    sep = (-diff * (neighbors[:,:,None] * inv_dsq[:,:,None])).sum(axis=1)

    # Alignment (average neighbor velocity)
    avg_vel = (vel[None,:] * neighbors[:,:,None]).sum(axis=1) / safe_count

    # Cohesion (steer toward center of mass)
    center = (pos[None,:] * neighbors[:,:,None]).sum(axis=1) / safe_count

    # Plus predator avoidance and boundary steering...
    # That one is easy.</code></code></pre><p>Here goes a sneak peek.</p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;704ce0e0-34f9-46d4-a8d5-438d5074f70a&quot;,&quot;duration&quot;:null}"></div><div><hr></div><p>You can check all the examples in the <a href="https://github.com/apiad/manifoldx">Github</a> repository to see the full code, but the bulk of the implementation is these cleverly vectorized system methods.</p><h2>Future Directions</h2><p>And that&#8217;s it. This is my pure Python (well, you know what I mean) graphics engine for serious, grown-up stuff that is surely, definitely, not a weekend side-project meant to procrastinate on actual work... I mean, what?!</p><p>Where I will go with this? I don&#8217;t know. I always write these things mostly as a learning exercise and I&#8217;ve learned a lot about graphics in Python. I&#8217;ve updated my view of modern graphics and I think I&#8217;ve paid my debt of the last seven years in graphics computation. I&#8217;m kind of happy now that I know how to do this in 2026. Mission accomplished, I guess.</p><p>There are some places this engine can go to, like some custom shaders when you need stuff like lighting effects. But it is not going to become a traditional, full-blown game engine. I will not add support for lots of game engine-like features including, I don&#8217;t know, skeletal animations, level of detail, scene management, or, god forbids, visual scripting and nonsense like that.</p><p>Now two areas I&#8217;d like to explore in the future. One is extending the engine towards the kind of behavior you need to write for AI simulations. If you want to run some sort of agent simulation or ant colony optimization or stuff like that, that code doesn&#8217;t look that much as a frame-by-frame update, but like an asynchronous event-loop&#8212;which is also something that is not usual in game engines. And the other direction is towards procedural generation of meshes and content in general, which is an area I left five or six years ago and would pretty much love to come back to it.</p><p>And that&#8217;s it for this week. This is not production-ready at all&#8212;it&#8217;s mostly a toy at the moment&#8212;but you can take it apart and hack your way into some cool physical or mathematical simulation. The code is on <a href="https://github.com/apiad/manifoldx">GitHub</a> if you want to try it yourself, and I&#8217;d love to see what you build with it.</p><p>Until next week, stay curious.</p>]]></content:encoded></item><item><title><![CDATA[How I'm Using AI Today]]></title><description><![CDATA[Yes, another take on this. I promise you this one is a bit different.]]></description><link>https://blog.apiad.net/p/how-im-using-ai-today</link><guid isPermaLink="false">https://blog.apiad.net/p/how-im-using-ai-today</guid><dc:creator><![CDATA[Alejandro Piad Morffis]]></dc:creator><pubDate>Mon, 02 Mar 2026 21:13:04 GMT</pubDate><enclosure url="https://images.unsplash.com/reserve/oIpwxeeSPy1cnwYpqJ1w_Dufer%20Collateral%20test.jpg?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxfHx0b29sc3xlbnwwfHx8fDE3NzI0NTU2Mzl8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://images.unsplash.com/reserve/oIpwxeeSPy1cnwYpqJ1w_Dufer%20Collateral%20test.jpg?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxfHx0b29sc3xlbnwwfHx8fDE3NzI0NTU2Mzl8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://images.unsplash.com/reserve/oIpwxeeSPy1cnwYpqJ1w_Dufer%20Collateral%20test.jpg?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxfHx0b29sc3xlbnwwfHx8fDE3NzI0NTU2Mzl8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/reserve/oIpwxeeSPy1cnwYpqJ1w_Dufer%20Collateral%20test.jpg?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxfHx0b29sc3xlbnwwfHx8fDE3NzI0NTU2Mzl8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/reserve/oIpwxeeSPy1cnwYpqJ1w_Dufer%20Collateral%20test.jpg?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxfHx0b29sc3xlbnwwfHx8fDE3NzI0NTU2Mzl8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/reserve/oIpwxeeSPy1cnwYpqJ1w_Dufer%20Collateral%20test.jpg?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxfHx0b29sc3xlbnwwfHx8fDE3NzI0NTU2Mzl8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1456w" sizes="100vw"><img src="https://images.unsplash.com/reserve/oIpwxeeSPy1cnwYpqJ1w_Dufer%20Collateral%20test.jpg?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxfHx0b29sc3xlbnwwfHx8fDE3NzI0NTU2Mzl8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" width="5663" height="4599" data-attrs="{&quot;src&quot;:&quot;https://images.unsplash.com/reserve/oIpwxeeSPy1cnwYpqJ1w_Dufer%20Collateral%20test.jpg?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxfHx0b29sc3xlbnwwfHx8fDE3NzI0NTU2Mzl8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:4599,&quot;width&quot;:5663,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;clothes iron, hammer, axe, flashlight and pitcher on brown wooden table&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="clothes iron, hammer, axe, flashlight and pitcher on brown wooden table" title="clothes iron, hammer, axe, flashlight and pitcher on brown wooden table" srcset="https://images.unsplash.com/reserve/oIpwxeeSPy1cnwYpqJ1w_Dufer%20Collateral%20test.jpg?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxfHx0b29sc3xlbnwwfHx8fDE3NzI0NTU2Mzl8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/reserve/oIpwxeeSPy1cnwYpqJ1w_Dufer%20Collateral%20test.jpg?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxfHx0b29sc3xlbnwwfHx8fDE3NzI0NTU2Mzl8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/reserve/oIpwxeeSPy1cnwYpqJ1w_Dufer%20Collateral%20test.jpg?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxfHx0b29sc3xlbnwwfHx8fDE3NzI0NTU2Mzl8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/reserve/oIpwxeeSPy1cnwYpqJ1w_Dufer%20Collateral%20test.jpg?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxfHx0b29sc3xlbnwwfHx8fDE3NzI0NTU2Mzl8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Photo by <a href="https://unsplash.com/@toddquackenbush">Todd Quackenbush</a> on <a href="https://unsplash.com">Unsplash</a></figcaption></figure></div><p>I know, this is the probably the twentieth article you&#8217;ve read this week on &#8220;how I am using AI to automate my whole life/work/whatever&#8221;. Me too. But please, give me a couple of minutes to tell why this article might be different, and thus interesting to you.</p><p>But even if you don&#8217;t have two minutes to spare, please check the <a href="https://github.com/apiad/starter">repository</a> where all I&#8217;m going to tell you about is implemented, ready for your taking. Clone it and play with it, then if you like it, come back and read the rationale behind it.</p><p>Done? Ok, here we go.</p><p>So yes, this is another article trying to explain to you how I use AI coding agents (specifically Gemini CLI, but the specifics don&#8217;t matter) to enhance my workflows. Here are a couple of reasons why I think you might be interested&#8212;and why this article might be different to so many lookalikes out there.</p><p>First, I&#8217;m not an enthusiastic techbro who just discovered AI. If you&#8217;ve read this blog before, you know I&#8217;m a longtime researcher in AI&#8212;way before LLMs were a thing&#8212;and also a self-proclaimed AI anti-hypist. I&#8217;m not just overexcited about this shiny new toy. I&#8217;ve been using generative AI since day one for everything, and I&#8217;ve been telling you exactly how it sucks at almost anything important since day one. Coding has been the same until very recently. I can tell you the change in productivity is real, provided you are responsible and considerate.</p><p>Second, my approach to incorporating AI into my workflows is very careful and grounded in a large dose of healthy skepticism. I know firsthand how these things fail, so my approach attempts to be very robust to hallucinations and context drift and all the plagues of even the most powerful LLMs.</p><p>Third, I have a thing for systems. What I&#8217;m going to show is not just a set of hacks or clever prompts or productivity tips. It&#8217;s a principled system to go from ideation to research and planning to execution at the fastest responsible speed, without sacrificing on safety or maintainability.</p><p>And fourth, I have kind of a unique position in that I&#8217;m both heavily invested into coding as well as technical writing. I&#8217;m a college professor, so I do a lot of research, writing, and editing; but I also run a small AI startup, small enough that I get to do a large part of the coding. So my system attempts to bridge these two facets&#8212;code and prose creation&#8212;with the same unified principles of careful deliberation and planning, and robust tracking of the project evolution.</p><p>If you&#8217;re curious already, let me start by exposing the overall principles behind this approach, and then we&#8217;ll dive (not delve, but close) into the details.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://blog.apiad.net/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://blog.apiad.net/subscribe?"><span>Subscribe now</span></a></p><h2>Principles of Effective AI-Assisted Work</h2><p>By far the most pressing limitation of modern, top-tier LLMs for my line of work is context saturation. What I mean by this, is no matter how many tokens your model can fit (Gemini 3 claims to chug as much as 1 million tokens), when you work for a considerable amount of time on a single project, you will exhaust this context. And even if the context window isn&#8217;t full, the model will quickly lose the capability to keep track of the important bits of context, and will start to deviate from your instructions and make up its own agenda. Not out of evilness but simple probabilities.</p><p>The way you see this problem when using Claude Code, Gemini CLI, Codex, Copilot, or anything similar is twofold. The model will either forget midtask what is was supposed to do, but it won&#8217;t simply fail; it will reinterpret the task based on its faulty, lossy, blurry viewpoint given the available context and it will do something close, but not quite what you wanted. Or, the model will make faulty assumptions and forget to ask relevant questions, again behaving close but not quite exactly as you intended.</p><p>The result is always the same, you get frustrated that you achieved 95% of what you needed, but the remaining 5% is harder to fix than to just try again. And what could have been a happy working session where you get important and difficult things done quickly derails into a session of arguing with an LLM trying to convince it (him/her/pick your side) to do things the way you want.</p><p>Barring any fundamental paradigm shifts in the near future, under the assumption that LLMs will keep working as they are, the only way to fix this is to be very conscious and careful about the context&#8212;what has been dubbed context engineering&#8212;in two senses: First, do not pollute the context with unnecessary details. And second, re-inject into the context whatever is relevant for any given task, so it doesn&#8217;t get forgotten.</p><p>I know, kind of contradictory, but tradeoffs are what engineering is all about. In my system, I&#8217;ve applied three principles to help me manage these tradeoffs effectively.</p><ol><li><p>The important things should be made explicit.</p></li><li><p>Resist the urge to guess.</p></li><li><p>Delegate, delegate, delegate (yeah, three times).</p></li></ol><p>Here&#8217;s how that works. For principle one, we will keep track of everything important in markdown files in the repository. This means ideas are committed to plans in markdown files before acting on them, research is summarized and stored in real time, and everything that changes in the project is logged to a long-lived journal, so the model remembers why we made some decision months ago.</p><p>For principle two, we will favor using explicit commands that are translated into explicit prompts, instead of relying on implicitly activated skills that you have little control of. So if you want the model to make a plan, you will prompt it with &#8220;/plan lets design feature X&#8221;, and the <code>/plan</code> command will invoke a carefully crafted prompt that says how plans work, where they are stored, etc.</p><p>And principle three means using sub-agents a lot. This is a Gemini CLI specific feature&#8212;but every other coding agent has a similar thing&#8212;where you can launch a complicated task as a &#8220;sub-agent&#8221;&#8212;which basically means a custom prompt&#8212;but here is the important part: All the context of that sub-agent is kept private, not shared with the main agent, so the internal reasoning the sub-agent needed to run to find 20 different sources in Google does not pollute the main context. We only receive back the summarized responses. This allows running very long tasks (my record is a 30 minutes long research loop, involving hundreds of retrieved web pages) on a single agent turn, without exhausting the context.</p><p>I use four sub-agents in different commands. The <code>planner</code> is the lead architect. It&#8217;s a read-only agent that walks through your codebase and reads everything necessary to understand architecture, design decisions, etc., given a specific task. It then produces a detailed Markdown plan in the <code>plans/</code> directory&#8212;a physical source of truth that you can review before any code is touched, and that the main agent will follow step-by-step. This separation prevents the system from &#8220;guessing&#8221; its way through your codebase.</p><p>When I need external knowledge&#8212;like a library&#8217;s latest API or a specific technical specification&#8212;the <code>researcher</code> agent takes over. It scours the web to fetch relevant documentation, which it then synthesizes into granular summaries in the <code>research/</code> directory. This raw data is then handed off to the main agent to build an executive report annotated and linked to all relevant sources, again all stored already in your repository.</p><p>And there are two more agents, specifically designed for technical writing. The <code>reporter</code> agent takes an outline, and a folder of content, and it will write section by section, a detailed account of what the outline requested.</p><p>Unlike a standard LLM that might provide a high-level summary, the <code>reporter</code> is trained to expand specific placeholders with deep, evidence-based paragraphs. It draws directly from your <code>research/</code> files and the project <code>journal/</code> to ensure every sentence is grounded in the project&#8217;s actual state. Finally, the <code>editor</code> provides the final polish, auditing the draft for structural gaps and linguistic tics. It is grounded in a customizable style guide to make sure it always respects your style.</p><p>This distributed intelligence is held together by a central nervous system of context files, as per principle one. A <code>journal/</code> directory provides a chronological record of decisions and progress, acting as a long-term memory for the project. The <code>plans/</code> directory stores the strategic intent, while a <code>TASKS.md</code> file provides a high-level overview of the project&#8217;s current status. This structured environment allows the subagents to maintain a high degree of situational awareness without needing to ingest the entire repository in every turn.</p><p>Now that we have the key pieces in place, you can start to see why I think this approach is powerful. It is very extensible&#8212;you can add new agents triggered by specific commands to customize any kind of workflow&#8212;and it mostly solves the main pain point of modern LLMs, which is precisely the brittleness of long contexts.</p><h2>Workflow Details</h2><p>With all that, let me show you the specific commands and workflows I have currently implemented, but keep in mind what follows is but one example of the kind of powerful workflows we can start to automate.</p><p>I will divide the rest of the article into a few major areas, and explain the commands and agents that I use in each case, and a bit of the high-level instructions given to each of them.</p><h3>Discovery &amp; Strategy</h3><p>The most critical phase of any project occurs before I write a single line of code. I call this the &#8220;Discovery and Strategy&#8221; phase, powered by the <code>/research</code> and <code>/plan</code> commands. By formalizing this process, I&#8217;ve moved away from impulsive execution toward a deliberate, architected approach.</p><p>The <code>/research</code> command is my primary tool for external knowledge. When triggered, the <code>researcher</code> scours the web for technical documentation and relevant case studies, synthesizing them into granular summaries in the <code>research/</code> directory. These files become a persistent knowledge base, allowing me to reference verified facts without leaving my terminal.</p><p>Complementing this is the <code>/plan</code> command, which focuses on internal strategy. The <code>planner</code> conducts a thorough analysis of the codebase and the <code>journal/</code> to understand the system&#8217;s current state. After an interactive dialogue to resolve any ambiguity, it produces a comprehensive Markdown plan in the <code>plans/</code> directory. This document maps out the technical territory and provides a step-by-step execution roadmap.</p><p>The strength of this workflow lies in the synergy between these two tools. A <code>/plan</code> operation might reveal a gap in my understanding of a specific library, prompting a targeted <code>/research</code> session. Conversely, a new research finding might shift my technical direction, leading to a refined plan.</p><h3>Software Development</h3><p>Once I have a solid strategy in my <code>plans/</code> directory, I can move into execution. Let&#8217;s focus now on software development. I&#8217;ve designed four core commands&#8212;<code>/issues</code>, <code>/task</code>, <code>/commit</code>, and <code>/release</code>&#8212;to eliminate the friction of context-switching between my IDE and my terminal.</p><p>The cycle starts with <code>/issues</code> and <code>/task</code>. The <code>/issues</code> command acts as an expert project lead, interfacing directly with the GitHub CLI to analyze open issues and recommend what to tackle next based on strategic impact. For roadmap tracking, the <code>/task</code> command manages a living <code>TASKS.md</code> document. It assesses the value of pending work to ensure my efforts are always aligned with the project&#8217;s goals.</p><p>As I translate the plan into code, the <code>/commit</code> command brings order to my workspace. Instead of a monolithic &#8220;wip&#8221; commit that hides the logic of my changes, the system analyzes the <code>git diff</code> and logically groups modifications into cohesive units. It separates a core feature update from a documentation tweak, then proposes a series of atomic, Conventional Commits for my approval. This keeps my version history pristine and easy to navigate.</p><p>The final stage is deployment. Manual releases are fragile processes fraught with repetitive checklists: bumping versions and running tests before managing tags. The <code>/release</code> command automates this entire sequence. It verifies the workspace integrity by ensuring a clean git tree and passing tests via <code>make</code>. It then analyzes the commit history to propose the next version bump, drafts a <code>CHANGELOG.md</code> entry, and publishes the final tag to GitHub. This transforms a tedious afternoon of housekeeping into a single-command operation.</p><p>But, as you&#8217;ve seen, everything happens in tandem with those principles. No important action is taken without my confirmation, and everything gets logged into the filesystem, so all future decisions are grounded in past experience.</p><h3>Content Creation</h3><p>Now let&#8217;s focus on writing high-quality documentation and long-form articles. This is perhaps the most sensible part of the article (and the system) because people are <em>very</em> sensitive today with the topic of AI writing&#8212;and rightly so. Again, my intention here is to enhance how I work and get stuff done. If you&#8217;re writing for the pleasure of doing it, that&#8217;s totally fine, you probably don&#8217;t want any help there.</p><p>Anyway, the approach is built on the same cognitive foundation as the development path: the research and plans gathered during the discovery phase should serve as grounding for writing.</p><p>It starts with the <code>/draft</code> command. In its initial phase, the system performs a deep scan of the <code>research/</code> and <code>plans/</code> directories to identify the key themes relevant to the requested topic. If the foundation is too thin, the system will pause and suggest a <code>/research</code> or <code>/plan</code> cycle to ensure the draft has sufficient substance. Once the context is validated, the workflow enters an interactive &#8220;Outline Creation&#8221; phase. Rather than guessing at a structure, the system proposes a detailed Markdown outline. This collaborative step allows me to set the narrative arc and logical flow that I want, iterating on the high-level structure of, say, a technical article, before committing on the details.</p><p>Once the outline is locked, the <code>/draft</code> process initializes a skeleton file&#8212;complete with section headers and strategic placeholders&#8212;and then moves into an iterative, section-by-section expansion. Here, the <code>reporter</code> subagent takes the lead. Guided by the specific context of each section, the <code>reporter</code> weaves together research summaries and technical specifications into professional prose, all grounded on a style guide document.</p><p>Because the expansion happens in granular steps, the system maintains a high level of detail that a single-shot generation would inevitably lose. The result is a first draft that is structurally sound and rich with technical depth.</p><p>However, a first draft is rarely the final word. It will always sound AI-ish, and for many other reasons, it is rarely good enough. To achieve professional quality, I use the <code>/revise</code> command, which runs a structural and linguistic audit powered by the <code>editor</code> subagent following the same style guide.</p><p>Unlike a simple &#8220;check my writing&#8221; prompt, the <code>editor</code> performs a deep analysis of the document&#8217;s flow and tone. It identifies logical gaps where more evidence might be needed and highlights awkward phrasing that could obscure my intent. And crucially, this isn&#8217;t an automated &#8220;fix-all&#8221; tool; it&#8217;s an interactive process. The system presents its findings and proposes specific improvements, which I can then review or approve.</p><p>This collaborative refinement process ensures the final output maintains a consistent, professional voice while benefiting from the speed of the AI. By using <code>/revise</code>, I can surgically improve the text to enhance clarity and impact without losing control over the narrative.</p><p>But, in any case, I always find necessary a manual review and editing after all the AI enhancements. It shouldn&#8217;t be a surprise to you that this article is written in this way, but what you&#8217;re reading now is probably 80% different to what the final <code>/revise</code> iteration gave me. There is only so much you can prompt an AI, and that final human touch is not part of it.</p><p>But that&#8217;s good. This automates the first 80% or so of compiling a gazillion sources into a coherent narrative, and leaves the remaining 80% of polishing for me, which is the part I actually enjoy about writing.</p><h3>Background Tasks</h3><p>But there&#8217;s more. All of the above is what happens during, let&#8217;s say, the work day. That&#8217;s me sitting in front of the terminal, typing commands, approving stuff, fixing and redirecting, etc. Being an orchestrator.</p><p>But the real magic of AI-assisted development is what happens when you&#8217;re not looking. How you can leave you AI assistant working through the night, compiling sources, fixing bugs and proposing pull requests, enhancing the test suite, burning tokens your behalf.</p><p>To achieve this, I built an automation layer via the <code>/cron</code> command. The heart of this automation is the <code>cron.toml</code> file. This configuration file allows me to define scheduled tasks with a simple, declarative syntax. Each task specifies a name, an execution schedule, and a natural language prompt for the AI to execute.</p><p>For instance, I can schedule a task to perform &#8220;Background Research&#8221; every midnight on the unfinished tasks, scouring the web for new developments in a specific technical niche or finding specific sources to deal with the recently discovered bugs. By offloading these repetitive tasks, I ensure the knowledge base remains fresh and the project&#8217;s momentum never stalls. When the morning arrives, we have a lot of new context to start planning the day&#8217;s bugfixes and feature developments.</p><h2>Maintenance &amp; Refactoring</h2><p>Now, for the final touch, here&#8217;s how I deal with technical debt and feature rot. As a project evolves&#8212;and especially, as fast as AI-powered projects evolve&#8212;it accumulates technical debt&#8212;outdated implementations, untested paths, and plain old useless features&#8212;but also, contextual debt&#8211;&#8212;outdated plans and completed tasks that clutter the roadmap, and research we never acted upon.</p><p>Without deliberate intervention, this noise degrades the AI&#8217;s performance, leading to context rot. The <code>/maintenance</code> command is my primary defense against this entropy. It treats the development environment as a living instrument that I must regularly tune and sharpen to maintain its efficiency.</p><p>The <code>/maintenance</code> workflow follows the same plan-first architecture as the rest of the system. When invoked, the AI performs a comprehensive audit of the codebase, focusing on improvements like code readability and performance optimization. It identifies opportunities to apply the DRY (Don&#8217;t Repeat Yourself) principle and ensures that every function is documented with high-quality docstrings. But it also fixes deviations between the documentation and the actual implementation.</p><p>Crucially, this is an interactive process: the system presents a detailed refactoring plan for my approval before making any changes. This ensures that I remain in control while the machine handles the labor of cleaning the code.</p><p>Beyond code refactoring, I maintain system health through disciplined repository hygiene. A key component is the management of the <code>TASKS.md</code> file. By regularly moving completed items into the &#8220;Archive&#8221; section, I ensure that my primary operational view remains focused on what is relevant. This simple act of archiving prevents the &#8220;Active Tasks&#8221; list from becoming a source of distraction.</p><p>The goal of these maintenance practices is to provide the AI with the cleanest possible line of sight into the project&#8217;s state. When the repository is cluttered with stale research, the subagents are forced to sift through irrelevant data, increasing the risk of hallucinations. By treating maintenance as a first-class citizen, I ensure that every interaction, whether a <code>/plan</code> or a <code>/draft</code>, is grounded in a precise context.</p><h2>Conclusion</h2><p>This system is far from done, and as models improve in capabilities I&#8217;m sure we&#8217;ll unlock new areas for automation and augmentation that we cannot think about today. But for me, the key principles will remain valid for a long time. These are principles of robust engineering and management, after all. You can read them thinking of a completely human-based organization, and it&#8217;s all valid:</p><ol><li><p>The important things should be made explicit.</p></li><li><p>Resist the urge to guess.</p></li><li><p>Delegate, delegate, delegate (yeah, three times).</p></li></ol><p>And this is the key insight for me. Good AI users are basically good managers. All the science and engineering behind good practices for people management also apply to good AI management. And then there are of course technical considerations because AIs are not people, and perhaps never will.</p><p>So this is perhaps the most philosophical take-away from this article. Sorry to have made you read so long for this!</p><p>Now, on the technical side, please do check the <a href="https://github.com/apiad/starter">repository</a> and play with it. There are a couple extra goodies I haven&#8217;t tell you about, like a <code>/scaffold</code> command that creates new projects from scratch, and an <code>/onboard</code> command that explains the whole repository in detail. Those are particularly useful if you&#8217;re reusing this repository as a template to start your own project.</p><p>Ultimately, this repository is not a one-size-fits-all solution. It is a starting point. The commands and subagents provided here represent a particular opinion on how modern development should look, but they are not the only way. The power of this framework lies in its extensibility. Every system prompt for agents and commands is a living document, meant to be tweaked and rewritten to suit your unique mental model.</p><p>So if you do try it out, please let me know in the comments. And if you have a different (or similar) system set up for yourself, please share with all us your experience and your thoughts. We are all learners in this era of AI, and we can only help each other.</p><p>Stay curious.</p>]]></content:encoded></item><item><title><![CDATA[Drawing (not so) Beautiful Diagrams with Pure Python]]></title><description><![CDATA[Ups, I did it again :)]]></description><link>https://blog.apiad.net/p/drawing-not-so-beautiful-diagrams</link><guid isPermaLink="false">https://blog.apiad.net/p/drawing-not-so-beautiful-diagrams</guid><dc:creator><![CDATA[Alejandro Piad Morffis]]></dc:creator><pubDate>Fri, 13 Feb 2026 14:06:17 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/21624634-7c05-40c2-98f0-46ed20132c3d_663x359.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Over the past couple of weeks, I&#8217;ve been working on a pure Python library for rendering SVGs. In a spur of unbridled inspiration, I called it <strong><a href="https://apiad.github.io/tesserax">Tesserax</a></strong>. Long story short, it got <em>waaayyyy</em> beyond control, as it happens. </p><p>This article is a quick recap of why I did it, how I went down the rabbit hole of implementing yet-another-drawing-library, and a small showcase of what it can do it can do, in case you want to give it a shot.</p><p>But before moving one, here is a minimal example of what you can do with a few lines of Python.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!R3IB!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93a97f55-54b7-49a8-9c5f-f2f21a862d7e_320x78.gif" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!R3IB!,w_424,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93a97f55-54b7-49a8-9c5f-f2f21a862d7e_320x78.gif 424w, https://substackcdn.com/image/fetch/$s_!R3IB!,w_848,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93a97f55-54b7-49a8-9c5f-f2f21a862d7e_320x78.gif 848w, https://substackcdn.com/image/fetch/$s_!R3IB!,w_1272,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93a97f55-54b7-49a8-9c5f-f2f21a862d7e_320x78.gif 1272w, https://substackcdn.com/image/fetch/$s_!R3IB!,w_1456,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93a97f55-54b7-49a8-9c5f-f2f21a862d7e_320x78.gif 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!R3IB!,w_1456,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93a97f55-54b7-49a8-9c5f-f2f21a862d7e_320x78.gif" width="320" height="78" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/93a97f55-54b7-49a8-9c5f-f2f21a862d7e_320x78.gif&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:78,&quot;width&quot;:320,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!R3IB!,w_424,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93a97f55-54b7-49a8-9c5f-f2f21a862d7e_320x78.gif 424w, https://substackcdn.com/image/fetch/$s_!R3IB!,w_848,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93a97f55-54b7-49a8-9c5f-f2f21a862d7e_320x78.gif 848w, https://substackcdn.com/image/fetch/$s_!R3IB!,w_1272,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93a97f55-54b7-49a8-9c5f-f2f21a862d7e_320x78.gif 1272w, https://substackcdn.com/image/fetch/$s_!R3IB!,w_1456,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F93a97f55-54b7-49a8-9c5f-f2f21a862d7e_320x78.gif 1456w" sizes="100vw" fetchpriority="high"></picture><div></div></div></a></figure></div><p>Are you curious now? Then read on!</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://blog.apiad.net/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://blog.apiad.net/subscribe?"><span>Subscribe now</span></a></p><h2>The Case for Tesserax</h2><p>There are a gazillion tools out there for drawing mathematical diagrams in Python, from the ancient, trustable <strong>matplotlib</strong> to the new cool kid in the park <strong>manim</strong>. There are a ton of charting libraries (those intended to draw bars, pies, and doughnuts) like <strong>altair</strong> and <strong>plotly</strong>; and a ton of high-level diagram makers like <strong>graphviz</strong> for graphs and networks, and <strong>mermaid</strong> for, well, whatever the mermaid devs think is worth drawing.</p><p>The problem with all these is that, one, <em>there weren&#8217;t made by me</em>. Now, jokes apart, I do believe there is a lot of value in reinventing the wheel, if only because you get to learn a lot about wheel&#8212;and boy have I learned more than I ever wanted to about SVG in the last couple of weeks. But also, you may find that your wheel fits your cart slightly better than all others, just because you know precisely what you need (and enough with wheel analogies).</p><p>So, I made Tesserax to cover a sweet spot that I couldn&#8217;t find anywhere: a lightweight library (literally zero dependencies, not even <strong>numpy</strong>) that renders web-native content (so everything scales and layouts perfectly in Jupyter / Quarto) and has both a very powerful low-level engine for when you want pixel-perfect control, and a very comfortable high-level engine for the most typical workflows.</p><p>Also, it should support <em>animations</em>. But I&#8217;m getting ahead of myself.</p><h2>What Can Tesserax Do</h2><blockquote><p>I won&#8217;t put any code in this article because, one, Substack sucks at code. And two, it would only make the article harder to follow. All of these examples are fully described in the <a href="https://apiad.github.io/tesserax">online documentation</a>.</p></blockquote><p>At its core, <strong>Tesserax</strong> is a library for defining an SVG scene. You create a <strong>Canvas</strong>, and some <strong>Shapes</strong> to it, define some attributes, and render it as an SVG file. Simple enough<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a>. </p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!67lT!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1c88048f-2c2c-4f68-a567-b01f783e12f4_399x128.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!67lT!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1c88048f-2c2c-4f68-a567-b01f783e12f4_399x128.png 424w, https://substackcdn.com/image/fetch/$s_!67lT!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1c88048f-2c2c-4f68-a567-b01f783e12f4_399x128.png 848w, https://substackcdn.com/image/fetch/$s_!67lT!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1c88048f-2c2c-4f68-a567-b01f783e12f4_399x128.png 1272w, https://substackcdn.com/image/fetch/$s_!67lT!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1c88048f-2c2c-4f68-a567-b01f783e12f4_399x128.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!67lT!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1c88048f-2c2c-4f68-a567-b01f783e12f4_399x128.png" width="399" height="128" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1c88048f-2c2c-4f68-a567-b01f783e12f4_399x128.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:128,&quot;width&quot;:399,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:4287,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://blog.apiad.net/i/187845763?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1c88048f-2c2c-4f68-a567-b01f783e12f4_399x128.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!67lT!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1c88048f-2c2c-4f68-a567-b01f783e12f4_399x128.png 424w, https://substackcdn.com/image/fetch/$s_!67lT!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1c88048f-2c2c-4f68-a567-b01f783e12f4_399x128.png 848w, https://substackcdn.com/image/fetch/$s_!67lT!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1c88048f-2c2c-4f68-a567-b01f783e12f4_399x128.png 1272w, https://substackcdn.com/image/fetch/$s_!67lT!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1c88048f-2c2c-4f68-a567-b01f783e12f4_399x128.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>Beyond basic primitives, you can of course draw arbitrary paths and style them easily. Here is a <strong>Polyline</strong> primitive that allows controlling the curvature with a <strong>smoothness</strong> factor.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!YaVP!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8960d96f-f48b-4c2c-af1c-0f4bcfb8b579_314x301.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!YaVP!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8960d96f-f48b-4c2c-af1c-0f4bcfb8b579_314x301.png 424w, https://substackcdn.com/image/fetch/$s_!YaVP!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8960d96f-f48b-4c2c-af1c-0f4bcfb8b579_314x301.png 848w, https://substackcdn.com/image/fetch/$s_!YaVP!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8960d96f-f48b-4c2c-af1c-0f4bcfb8b579_314x301.png 1272w, https://substackcdn.com/image/fetch/$s_!YaVP!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8960d96f-f48b-4c2c-af1c-0f4bcfb8b579_314x301.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!YaVP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8960d96f-f48b-4c2c-af1c-0f4bcfb8b579_314x301.png" width="282" height="270.3248407643312" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8960d96f-f48b-4c2c-af1c-0f4bcfb8b579_314x301.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:301,&quot;width&quot;:314,&quot;resizeWidth&quot;:282,&quot;bytes&quot;:5697,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://blog.apiad.net/i/187845763?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8960d96f-f48b-4c2c-af1c-0f4bcfb8b579_314x301.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!YaVP!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8960d96f-f48b-4c2c-af1c-0f4bcfb8b579_314x301.png 424w, https://substackcdn.com/image/fetch/$s_!YaVP!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8960d96f-f48b-4c2c-af1c-0f4bcfb8b579_314x301.png 848w, https://substackcdn.com/image/fetch/$s_!YaVP!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8960d96f-f48b-4c2c-af1c-0f4bcfb8b579_314x301.png 1272w, https://substackcdn.com/image/fetch/$s_!YaVP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8960d96f-f48b-4c2c-af1c-0f4bcfb8b579_314x301.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>You can control and distort these shapes in any form you want, including fully procedural warping of the edges.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ifdW!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1226efa9-359b-4b1a-a986-19eed93df165_474x175.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ifdW!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1226efa9-359b-4b1a-a986-19eed93df165_474x175.png 424w, https://substackcdn.com/image/fetch/$s_!ifdW!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1226efa9-359b-4b1a-a986-19eed93df165_474x175.png 848w, https://substackcdn.com/image/fetch/$s_!ifdW!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1226efa9-359b-4b1a-a986-19eed93df165_474x175.png 1272w, https://substackcdn.com/image/fetch/$s_!ifdW!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1226efa9-359b-4b1a-a986-19eed93df165_474x175.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ifdW!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1226efa9-359b-4b1a-a986-19eed93df165_474x175.png" width="412" height="152.10970464135022" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1226efa9-359b-4b1a-a986-19eed93df165_474x175.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:175,&quot;width&quot;:474,&quot;resizeWidth&quot;:412,&quot;bytes&quot;:9834,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://blog.apiad.net/i/187845763?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1226efa9-359b-4b1a-a986-19eed93df165_474x175.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ifdW!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1226efa9-359b-4b1a-a986-19eed93df165_474x175.png 424w, https://substackcdn.com/image/fetch/$s_!ifdW!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1226efa9-359b-4b1a-a986-19eed93df165_474x175.png 848w, https://substackcdn.com/image/fetch/$s_!ifdW!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1226efa9-359b-4b1a-a986-19eed93df165_474x175.png 1272w, https://substackcdn.com/image/fetch/$s_!ifdW!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1226efa9-359b-4b1a-a986-19eed93df165_474x175.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>And, of course, since no mathematical drawing library is worth a penny without a sketchy mode, you can also do that (albeit with some constraints).</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!3DwH!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd62b252a-8212-4144-a857-d7e8d9944767_493x210.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!3DwH!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd62b252a-8212-4144-a857-d7e8d9944767_493x210.png 424w, https://substackcdn.com/image/fetch/$s_!3DwH!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd62b252a-8212-4144-a857-d7e8d9944767_493x210.png 848w, https://substackcdn.com/image/fetch/$s_!3DwH!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd62b252a-8212-4144-a857-d7e8d9944767_493x210.png 1272w, https://substackcdn.com/image/fetch/$s_!3DwH!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd62b252a-8212-4144-a857-d7e8d9944767_493x210.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!3DwH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd62b252a-8212-4144-a857-d7e8d9944767_493x210.png" width="403" height="171.6632860040568" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d62b252a-8212-4144-a857-d7e8d9944767_493x210.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:210,&quot;width&quot;:493,&quot;resizeWidth&quot;:403,&quot;bytes&quot;:11596,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://blog.apiad.net/i/187845763?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd62b252a-8212-4144-a857-d7e8d9944767_493x210.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!3DwH!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd62b252a-8212-4144-a857-d7e8d9944767_493x210.png 424w, https://substackcdn.com/image/fetch/$s_!3DwH!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd62b252a-8212-4144-a857-d7e8d9944767_493x210.png 848w, https://substackcdn.com/image/fetch/$s_!3DwH!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd62b252a-8212-4144-a857-d7e8d9944767_493x210.png 1272w, https://substackcdn.com/image/fetch/$s_!3DwH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd62b252a-8212-4144-a857-d7e8d9944767_493x210.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>To make positioning and layout easy, Tesserax comes a set of builtin layouts (that of course can be extended in any way you want, it&#8217;s Python) for common patterns like rows, columns, grids&#8230;</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!tEXd!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60ca74b0-8ca0-4522-93b2-046664db44e3_349x210.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!tEXd!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60ca74b0-8ca0-4522-93b2-046664db44e3_349x210.png 424w, https://substackcdn.com/image/fetch/$s_!tEXd!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60ca74b0-8ca0-4522-93b2-046664db44e3_349x210.png 848w, https://substackcdn.com/image/fetch/$s_!tEXd!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60ca74b0-8ca0-4522-93b2-046664db44e3_349x210.png 1272w, https://substackcdn.com/image/fetch/$s_!tEXd!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60ca74b0-8ca0-4522-93b2-046664db44e3_349x210.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!tEXd!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60ca74b0-8ca0-4522-93b2-046664db44e3_349x210.png" width="349" height="210" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/60ca74b0-8ca0-4522-93b2-046664db44e3_349x210.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:210,&quot;width&quot;:349,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:858,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://blog.apiad.net/i/187845763?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60ca74b0-8ca0-4522-93b2-046664db44e3_349x210.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!tEXd!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60ca74b0-8ca0-4522-93b2-046664db44e3_349x210.png 424w, https://substackcdn.com/image/fetch/$s_!tEXd!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60ca74b0-8ca0-4522-93b2-046664db44e3_349x210.png 848w, https://substackcdn.com/image/fetch/$s_!tEXd!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60ca74b0-8ca0-4522-93b2-046664db44e3_349x210.png 1272w, https://substackcdn.com/image/fetch/$s_!tEXd!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F60ca74b0-8ca0-4522-93b2-046664db44e3_349x210.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>And a couple of complex layouts for things like trees and arbitrary graphs.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!KodJ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5dc8f82b-67f7-424f-9f0b-abe7e1fd0085_192x333.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!KodJ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5dc8f82b-67f7-424f-9f0b-abe7e1fd0085_192x333.png 424w, https://substackcdn.com/image/fetch/$s_!KodJ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5dc8f82b-67f7-424f-9f0b-abe7e1fd0085_192x333.png 848w, https://substackcdn.com/image/fetch/$s_!KodJ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5dc8f82b-67f7-424f-9f0b-abe7e1fd0085_192x333.png 1272w, https://substackcdn.com/image/fetch/$s_!KodJ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5dc8f82b-67f7-424f-9f0b-abe7e1fd0085_192x333.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!KodJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5dc8f82b-67f7-424f-9f0b-abe7e1fd0085_192x333.png" width="192" height="333" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5dc8f82b-67f7-424f-9f0b-abe7e1fd0085_192x333.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:333,&quot;width&quot;:192,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:7101,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://blog.apiad.net/i/187845763?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5dc8f82b-67f7-424f-9f0b-abe7e1fd0085_192x333.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!KodJ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5dc8f82b-67f7-424f-9f0b-abe7e1fd0085_192x333.png 424w, https://substackcdn.com/image/fetch/$s_!KodJ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5dc8f82b-67f7-424f-9f0b-abe7e1fd0085_192x333.png 848w, https://substackcdn.com/image/fetch/$s_!KodJ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5dc8f82b-67f7-424f-9f0b-abe7e1fd0085_192x333.png 1272w, https://substackcdn.com/image/fetch/$s_!KodJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5dc8f82b-67f7-424f-9f0b-abe7e1fd0085_192x333.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>But things start to get really interesting when you discover that if you can render an image from code, then you can render as many as you want! And that leads to&#8230; animations!</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!5ssP!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff23b3b8e-d4af-4a9b-ac8b-b09474037e25_160x100.gif" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!5ssP!,w_424,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff23b3b8e-d4af-4a9b-ac8b-b09474037e25_160x100.gif 424w, https://substackcdn.com/image/fetch/$s_!5ssP!,w_848,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff23b3b8e-d4af-4a9b-ac8b-b09474037e25_160x100.gif 848w, https://substackcdn.com/image/fetch/$s_!5ssP!,w_1272,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff23b3b8e-d4af-4a9b-ac8b-b09474037e25_160x100.gif 1272w, https://substackcdn.com/image/fetch/$s_!5ssP!,w_1456,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff23b3b8e-d4af-4a9b-ac8b-b09474037e25_160x100.gif 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!5ssP!,w_1456,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff23b3b8e-d4af-4a9b-ac8b-b09474037e25_160x100.gif" width="172" height="107.5" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f23b3b8e-d4af-4a9b-ac8b-b09474037e25_160x100.gif&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:100,&quot;width&quot;:160,&quot;resizeWidth&quot;:172,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!5ssP!,w_424,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff23b3b8e-d4af-4a9b-ac8b-b09474037e25_160x100.gif 424w, https://substackcdn.com/image/fetch/$s_!5ssP!,w_848,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff23b3b8e-d4af-4a9b-ac8b-b09474037e25_160x100.gif 848w, https://substackcdn.com/image/fetch/$s_!5ssP!,w_1272,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff23b3b8e-d4af-4a9b-ac8b-b09474037e25_160x100.gif 1272w, https://substackcdn.com/image/fetch/$s_!5ssP!,w_1456,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff23b3b8e-d4af-4a9b-ac8b-b09474037e25_160x100.gif 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>Tesserax animations are procedural, meaning you write code that defines how each object changes in time. But there is a very high-level API for defining and composing animations declaratively, which means you seldom need the full power of tweaking each shape properties (but that power is there when you need it, as it should).</p><p>And, of course, once you grok animations, the next immediate idea is <em>physically-based animations</em>. (This is where things got way out of control, as you may imagine). Tesserax comes with a very small but fully-fledged pure Python 2D physics engines&#8212;that was a mouthful. </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!xao8!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07c8667d-af5a-47ec-8471-6941baeb543b_120x250.gif" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!xao8!,w_424,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07c8667d-af5a-47ec-8471-6941baeb543b_120x250.gif 424w, https://substackcdn.com/image/fetch/$s_!xao8!,w_848,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07c8667d-af5a-47ec-8471-6941baeb543b_120x250.gif 848w, https://substackcdn.com/image/fetch/$s_!xao8!,w_1272,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07c8667d-af5a-47ec-8471-6941baeb543b_120x250.gif 1272w, https://substackcdn.com/image/fetch/$s_!xao8!,w_1456,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07c8667d-af5a-47ec-8471-6941baeb543b_120x250.gif 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!xao8!,w_1456,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07c8667d-af5a-47ec-8471-6941baeb543b_120x250.gif" width="126" height="262.5" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/07c8667d-af5a-47ec-8471-6941baeb543b_120x250.gif&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:250,&quot;width&quot;:120,&quot;resizeWidth&quot;:126,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!xao8!,w_424,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07c8667d-af5a-47ec-8471-6941baeb543b_120x250.gif 424w, https://substackcdn.com/image/fetch/$s_!xao8!,w_848,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07c8667d-af5a-47ec-8471-6941baeb543b_120x250.gif 848w, https://substackcdn.com/image/fetch/$s_!xao8!,w_1272,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07c8667d-af5a-47ec-8471-6941baeb543b_120x250.gif 1272w, https://substackcdn.com/image/fetch/$s_!xao8!,w_1456,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F07c8667d-af5a-47ec-8471-6941baeb543b_120x250.gif 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>It can resolve collisions among arbitrary objects (using circles and rotated boxes as approximate colliders) and simulate fixed-step rigid body mechanics. Physic animations are baked&#8212;which means you compute the animation once and then render it. So, no, no 2D games for you! What were you thinking about? This is a graphing library, for Turing&#8217;s sake!</p><p>The purpose of this physics engine is, once again, didactic&#8212;e.g., to explain physics concepts in a Jupyter notebook with a bit of shizzass. </p><h2>Final Words</h2><p>I started writing Tesserax to help me create diagrams and animations for my lectures and articles. But when you do that with <em>a fully-fledged programming language</em> things start to get really interesting, because now you can leverage your well-honed programming skills to create complex diagrams with the minimum necessary code. You can encapsulate repeatable patterns into classes and methods, and build impressive diagrams bottom-up. You can abstract common patterns into drawable concepts like trees, automatons, etc.</p><p>For example, in the docs you will find a bit of code to implement a blob-like simulation by creating a<strong> ConvexHull</strong> component that automatically tracks inner shapes and builds the convex surrounding path around them. Tied with some simple physics (a bunch of balls interconnected with springs) gives you this ugly-looking blob of dread that I hope haunts your dreams for the next week or so&#8212;it sure will mine.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!FD07!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7820fc03-0d3d-43bb-8c37-0126e27b5652_620x388.gif" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!FD07!,w_424,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7820fc03-0d3d-43bb-8c37-0126e27b5652_620x388.gif 424w, https://substackcdn.com/image/fetch/$s_!FD07!,w_848,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7820fc03-0d3d-43bb-8c37-0126e27b5652_620x388.gif 848w, https://substackcdn.com/image/fetch/$s_!FD07!,w_1272,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7820fc03-0d3d-43bb-8c37-0126e27b5652_620x388.gif 1272w, https://substackcdn.com/image/fetch/$s_!FD07!,w_1456,c_limit,f_webp,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7820fc03-0d3d-43bb-8c37-0126e27b5652_620x388.gif 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!FD07!,w_1456,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7820fc03-0d3d-43bb-8c37-0126e27b5652_620x388.gif" width="436" height="272.8516129032258" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7820fc03-0d3d-43bb-8c37-0126e27b5652_620x388.gif&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:388,&quot;width&quot;:620,&quot;resizeWidth&quot;:436,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!FD07!,w_424,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7820fc03-0d3d-43bb-8c37-0126e27b5652_620x388.gif 424w, https://substackcdn.com/image/fetch/$s_!FD07!,w_848,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7820fc03-0d3d-43bb-8c37-0126e27b5652_620x388.gif 848w, https://substackcdn.com/image/fetch/$s_!FD07!,w_1272,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7820fc03-0d3d-43bb-8c37-0126e27b5652_620x388.gif 1272w, https://substackcdn.com/image/fetch/$s_!FD07!,w_1456,c_limit,f_auto,q_auto:good,fl_lossy/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7820fc03-0d3d-43bb-8c37-0126e27b5652_620x388.gif 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>On a more serious note, the next step for Tesserax is building a library of reusable math and computer science concepts, like animated arrays, trees, graphs, etc., that can be used to explain complex CS topics with the much need visual assistance. As the great Donald Knuth once said, an algorithm must be seen to be believed. Tesserax is here to help you see them.</p><p>I&#8217;d really love if you guys would give Tesserax a try, now that is a bit more polished, and shoot me all your questions and suggestions. Special thanks to <span class="mention-wrap" data-attrs="{&quot;name&quot;:&quot;Wyrd Smythe&quot;,&quot;id&quot;:195807185,&quot;type&quot;:&quot;user&quot;,&quot;url&quot;:null,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3f88cc94-56a6-4453-b3dd-c8ccb9194c15_2316x2316.png&quot;,&quot;uuid&quot;:&quot;45140a44-685f-4df2-95a6-819f2f652900&quot;}" data-component-name="MentionToDOM"></span> for many insightful comments and suggestions (although I&#8217;m not sure I&#8217;ve totally addressed all of them :)</p><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p>Sadly, among all the stupid things that Substack cannot do for the sake of being annoying, it seems rendering SVG is one of them. So all drawings in this article are dumb screenshots of what otherwise would be pixel-perfect, infinitely scalable SVGs.</p></div></div>]]></content:encoded></item><item><title><![CDATA[How to Train your Chatbot - Chapter One]]></title><description><![CDATA[Hello AI]]></description><link>https://blog.apiad.net/p/how-to-train-your-chatbot-chapter-4d8</link><guid isPermaLink="false">https://blog.apiad.net/p/how-to-train-your-chatbot-chapter-4d8</guid><dc:creator><![CDATA[Alejandro Piad Morffis]]></dc:creator><pubDate>Wed, 22 Oct 2025 11:01:13 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!ydor!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbb1006c3-cc3a-4419-8b64-350f8977683b_1538x721.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote><p>The following is the first draft of Chapter 1 of <strong>How to Train your Chatbot</strong>, a developer-centric book I&#8217;m writing in public. Subscribe to get all chapters in your inbox, as they come out, for free.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://blog.apiad.net/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://blog.apiad.net/subscribe?"><span>Subscribe now</span></a></p></blockquote><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ydor!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbb1006c3-cc3a-4419-8b64-350f8977683b_1538x721.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ydor!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbb1006c3-cc3a-4419-8b64-350f8977683b_1538x721.png 424w, https://substackcdn.com/image/fetch/$s_!ydor!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbb1006c3-cc3a-4419-8b64-350f8977683b_1538x721.png 848w, https://substackcdn.com/image/fetch/$s_!ydor!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbb1006c3-cc3a-4419-8b64-350f8977683b_1538x721.png 1272w, https://substackcdn.com/image/fetch/$s_!ydor!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbb1006c3-cc3a-4419-8b64-350f8977683b_1538x721.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ydor!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbb1006c3-cc3a-4419-8b64-350f8977683b_1538x721.png" width="1456" height="683" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/bb1006c3-cc3a-4419-8b64-350f8977683b_1538x721.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:683,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:82009,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://blog.apiad.net/i/176766314?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbb1006c3-cc3a-4419-8b64-350f8977683b_1538x721.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ydor!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbb1006c3-cc3a-4419-8b64-350f8977683b_1538x721.png 424w, https://substackcdn.com/image/fetch/$s_!ydor!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbb1006c3-cc3a-4419-8b64-350f8977683b_1538x721.png 848w, https://substackcdn.com/image/fetch/$s_!ydor!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbb1006c3-cc3a-4419-8b64-350f8977683b_1538x721.png 1272w, https://substackcdn.com/image/fetch/$s_!ydor!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbb1006c3-cc3a-4419-8b64-350f8977683b_1538x721.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>In this first chapter we will build a bare-bones conversational application&#8211;a chatbot&#8211;that is capable of maintaining a coherent conversation with a single user. We will learn how to connect with an LLM provider, pass messages, and stream the responses back.</p><p>In this chapter we won&#8217;t use any additional libraries beyond the Python <a href="https://github.com/openai/openai">OpenAI</a> wrapper to communicate with an LLM backend. This is on purpose, so we understand the whole lifecycle of a typical conversation.</p><p>Then, in the next chapter, we will redo the core logic using <a href="https://github.com/gia-uh/argo">ARGO</a> to simplify the chat management and introduce some modularization, and we will add a database to store conversations and make our interactions persistent.</p><p>The whole source for this chapter (and this entire book) can be found in this <a href="https://github.com/apiad/chatbot">Github repository</a>. This specific chapter is in the <a href="https://github.com/apiad/chatbot/blob/main/basic.py">basic.py</a> file.</p><h2>Setting up the environment</h2><p>The first step is to setup a development environment. At the moment, the community preferred way of doing this with Python is using a package and virtual environment manager, and by far the best in the world is <a href="">uv</a>. So let&#8217;s begin by installing thatl, and once ready, just run:</p><pre><code><code>mkdir chatbot # or whichever folder name you prefer
cd chatbot
uv init .</code></code></pre><p>This will create a <code>pyproject.toml</code> file in the <code>chatbot</code> folder. Now let&#8217;s add <code>streamlit</code> and <code>openai</code>.</p><pre><code><code>uv add streamlit openai</code></code></pre><p>After a few seconds downloading the necessary packages, you will be ready to start coding.</p><h3>Get the source code</h3><p>The complete source code for this book is available in <a href="https://github.com/apiad/chatbots">Github</a> with an MIT license. Feel free to fork it and use it as you see fit.</p><h2>The basic chatbot</h2><p>Let&#8217;s start outlining our first chatbot. We will use Streamlit&#8217;s builtin functionality to create a pretty decent chatbot UI in less than 10 lines of code. Here is the boilerplate code.</p><pre><code><code>import streamlit as st

# import and setup OpenAI
&lt;&lt;setup_openai&gt;&gt;

# regenerate the conversation history
&lt;&lt;render_history&gt;&gt;

msg = st.chat_input()

if not msg:
    st.stop()

with st.chat_message(&#8221;user&#8221;):
    st.write(msg)

# code to actually call the LLM
&lt;&lt;invoke_llm&gt;&gt;

with st.chat_message(&#8221;assistant&#8221;):
    response = st.write(reply(msg))

# save the llm response
&lt;&lt;save_llm_response&gt;&gt;</code></code></pre><h3>About Literate Programming</h3><p>You may have noticed the code above uses a somewhat weird notation, like <code>&lt;&lt;setup_openai&gt;&gt;</code>, which is not valid Python. This is because we are using a tool called <code>illiterate</code> to generate automatically the source code from the book content. This notation is how we reference snippets of code that will be defined later on.</p><p>This is a paradigm called <em>Literate Programming</em> that emphasizes narrative documentatiopn as the primary source, and the actual source is derived automatically, so that your documentation (this book) and your code are always synchronized</p><p>Don&#8217;t worry too much about it, just know that all these snippets will be explained in the remainder of this chapter. You&#8217;ll get the hang of it.</p><p>This completes the basic layout of a typical chatbot app. We still haven&#8217;t specified how to actually call the LLM provider, and how to manage the conversation history. We will do that in the remaining of this chapter.</p><h2>Setting up an LLM provider</h2><p>For all practical purposes, when you code a chatbot you consider the LLM as a black-box component that is, almost always, encapsulated behind a REST API. This lets you focus on building the app and forget about the extremely complex problem of serving, scaling, and monitoring an LLM in a production environment.</p><p>This is pretty much the same as when you use a production database, like MySQL, Postgre, or MongoDB, you almost never directly run the database server daemon. Instead, you almost certainly provision a managed server from Amazon, Azure, Google, or any other of the myriad cloud providers. In the same way, most of the time you won&#8217;t run an LLM locally, but instead use a cloud provider which has far better infrastructure.</p><p>By far, the most common API design used by all cloud LLM providers is the OpenAI API, which has become a de-facto standard. So, even if you don&#8217;t use OpenAI&#8217;s models directly (like GPT-5), you will almost certainly use the <code>opena</code> Python package to communicate with your LLM cloud provider, whoever they are.</p><p>In this book I will recommend <a href="https://openrouter.ai">OpenRouter</a>, because they provide access to a huge range of models, and they deal with automatically routing to the optimal cloud provider. But you are free to choose whatever LLM provider you prefer, and as long as they give you an OpenAI-compatible API (and they all do), the rest of this book will work exactly the same for you.</p><p>Whatever you choose, you will need three things from your LLM provider:</p><ul><li><p>A base URL (e.g., <code>https://openrouter.ai/api/v1</code>)</p></li><li><p>An API key (e.g., <code>sk-12345678...</code>)</p></li><li><p>A model identifier (e.g., <code>meta-llama/llama-4-maverick:free</code>)</p></li></ul><p>Of these three, the API Key is the most important to keep safe (and secret) because it&#8217;s what the LLM provider will use to identify you (and thus charge you) for using their service. If this API Key ends up in a Github repository or somewhere public, someone might use it to impersonate you and thus deplete your credits or put you in a significant debt with your LLM provider.</p><div class="pullquote"><p>TL;DR: Keep your API Key secret!</p></div><p>Once you have identified your LLM provider, you need to provide <code>streamlit</code> with the credentials and metadata indicated above. The best way to do this for a <code>streamlit</code> app is to use its native secrets management. You need to create a <code>.streamlit</code> folder, and place a <code>secrets.toml</code> file inside. There you can add the entries just as if they where environment variables.</p><pre><code><code># .streamlit/secrets.toml
api_key=&#8221;sk-12345678&#8221;
base_url=&#8221;https://openrouter.ai/api/v1&#8221;
model=&#8221;meta-llama/llama-4-maverick:free&#8221;</code></code></pre><p>Needless to say, this <code>secrets.toml</code> file should be the first line in your <code>.gitignore</code>!</p><p>For the purpose of development and testing, you can use one of the many free models OpenRouter hosts, which give you enough daily uses for development purposes. Read more on the <a href="https://openrouter.ai/docs/faq#how-are-rate-limits-calculated">OpenRouter documentation</a>. As a perhaps obvious disclaimer, I&#8217;m not associated with OpenRouter nor any part of this book is supported by them. I just think they provide a wonderful service for the AI developer community.</p><p>Once you&#8217;ve gotten your API key, you can use it to create an OpenAI client to interact with the LLM.</p><pre><code><code>from openai import OpenAI

client = OpenAI(
    base_url=st.secrets.base_url,
    api_key=st.secrets.api_key,
)</code></code></pre><p>And we&#8217;re ready to start sending some messages.</p><h2>Calling the LLM</h2><p>OpenAI&#8217;s client makes it very easy to invoke an LLM. We just need to pass in a list of messages and stream the result back to our Streamlit app. But before, we need to understand one key thing about LLM providers, and is the fact that they are <strong>stateless</strong>. This means you <em>always have to pass the whole conversation</em> to the API, because neither the actual model nor the server itself &#8220;remember&#8221; the conversation.</p><p>So, we will need to store the conversation in our application state. I&#8217;ll show you how to do this in just a second, but let&#8217;s assume we have it and move on to actually talking to the LLM.</p><p>In our app, we need to include the following method, which will stream the response from the LLM to the user.</p><pre><code><code>&lt;&lt;store_conversation&gt;&gt;

def reply(msg: str):
    # retrieve the conversation list
    conversation = st.session_state.conversation

    for chunk in client.chat.completions.create(
        model=st.secrets.model,
        messages=conversation,
        stream=True,
    ):
        if msg := chunk.choices[0].delta.content:
            yield msg</code></code></pre><p>That&#8217;s it. We just invoke <code>client.chat.completions.create</code> with the model name and conversation (a list of messages we will see right away) and stream the results back in the form of a Python generator. The <code>st.write</code> method in our main app is smart enough to produce a pretty typewriter animation as we receive the response one chunk at a time.</p><p>The conversation is actually stored in Streamlit&#8217;s builtin session storage, which is a dictionary-like object unique to each user session. This means that as long as we stay in the same browser tab, the conversation persists. Once we open another tab or hit F5, the session storage is cleaned up and we have a new conversation. Kind of brute, but for now it will do.</p><p>And this is the missing snippet to actually keep track of the conversation. We need to store the user message just after we get from the chat input widget:</p><pre><code><code>if &#8220;conversation&#8221; not in st.session_state:
    st.session_state.conversation = []

st.session_state.conversation.append(dict(role=&#8221;user&#8221;, content=msg))</code></code></pre><p>And we need to store the assistant reply message in the main loop after streaming it. Fortunately, the <code>st.write</code> method is smart enough so that it not only prints the message as it is streamed, but also returns the full concatenated message once the stream is closed.</p><pre><code><code>st.session_state.conversation.append(
    dict(role=&#8221;assistant&#8221;, content=response)
)</code></code></pre><p>As you&#8217;ve seen, we&#8217;re storing the messages in the format that the OpenAI API expects. This is a list of dictionaries, where each dictionary contains a <code>role</code> and a <code>content</code> key. The role (for now) can be either <code>user</code> or <code>assistant</code>, and it&#8217;s important we respect it because the models are trained to behave as if they are replying from the &#8220;assistant&#8221; perspective.</p><h2>Rebuilding the conversation history</h2><p>The final step to make our app a fully-fledged chatbot is to actually render the whole conversation in the UI. This is necessary because Streamlit works with an immediate execution paradigm, which effectively means every interaction cleans the whole screen and redraws the entire UI. Thus, every time the user enters a message, the UI restarts and we lose the previously rendered interaction.</p><p>Fortunately, as we have the conversation stored in the session state, we can simply iterate over it and render each message.</p><pre><code><code>for msg in st.session_state.get(&#8217;conversation&#8217;, []):
    with st.chat_message(msg[&#8217;role&#8217;]):
        st.write(msg[&#8217;content&#8217;])</code></code></pre><p>We just need a bit of care the first time we run the app because the <code>conversation</code> key won&#8217;t exist, that&#8217;s why we use <code>get</code> instead of a direct indexer.</p><h2>Closing up</h2><p>Phew! There you go, in around 50 lines of code (including comments and whitespace) we have a fully working chatbot. It&#8217;s very crude for now, all it does is send some messages to an LLM provider and stream the response back, but this is the foundation we can build upon to create all sorts of cool stuff.</p><p>In the next chapter we will rebuild this application from scratch but using an LLM framework that will give us much more flexibility later on.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://blog.apiad.net/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://blog.apiad.net/subscribe?"><span>Subscribe now</span></a></p><p></p>]]></content:encoded></item><item><title><![CDATA[How to Train your Chatbot - Chapter Zero]]></title><description><![CDATA[Announcing a new series on building practical AI applications]]></description><link>https://blog.apiad.net/p/how-to-train-your-chatbot-chapter</link><guid isPermaLink="false">https://blog.apiad.net/p/how-to-train-your-chatbot-chapter</guid><dc:creator><![CDATA[Alejandro Piad Morffis]]></dc:creator><pubDate>Mon, 20 Oct 2025 11:22:51 GMT</pubDate><enclosure url="https://images.unsplash.com/photo-1577563908411-5077b6dc7624?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxjaGF0fGVufDB8fHx8MTc2MDg5NDczNXww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote><p><strong>TL;DR:</strong> In this upcoming series, we'll build an autonomous LLM-based agent from scratch, focusing on the fundamentals. We'll go from zero to fully autonomous deep research, one feature at a time, staying as close to the metal as possible. Subscribe to receive all posts in your inbox for free.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://blog.apiad.net/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://blog.apiad.net/subscribe?"><span>Subscribe now</span></a></p></blockquote><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://images.unsplash.com/photo-1577563908411-5077b6dc7624?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxjaGF0fGVufDB8fHx8MTc2MDg5NDczNXww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://images.unsplash.com/photo-1577563908411-5077b6dc7624?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxjaGF0fGVufDB8fHx8MTc2MDg5NDczNXww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1577563908411-5077b6dc7624?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxjaGF0fGVufDB8fHx8MTc2MDg5NDczNXww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1577563908411-5077b6dc7624?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxjaGF0fGVufDB8fHx8MTc2MDg5NDczNXww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1577563908411-5077b6dc7624?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxjaGF0fGVufDB8fHx8MTc2MDg5NDczNXww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1456w" sizes="100vw"><img src="https://images.unsplash.com/photo-1577563908411-5077b6dc7624?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxjaGF0fGVufDB8fHx8MTc2MDg5NDczNXww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" width="5472" height="3648" data-attrs="{&quot;src&quot;:&quot;https://images.unsplash.com/photo-1577563908411-5077b6dc7624?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxjaGF0fGVufDB8fHx8MTc2MDg5NDczNXww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:3648,&quot;width&quot;:5472,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;three crumpled yellow papers on green surface surrounded by yellow lined papers&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="three crumpled yellow papers on green surface surrounded by yellow lined papers" title="three crumpled yellow papers on green surface surrounded by yellow lined papers" srcset="https://images.unsplash.com/photo-1577563908411-5077b6dc7624?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxjaGF0fGVufDB8fHx8MTc2MDg5NDczNXww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1577563908411-5077b6dc7624?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxjaGF0fGVufDB8fHx8MTc2MDg5NDczNXww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1577563908411-5077b6dc7624?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxjaGF0fGVufDB8fHx8MTc2MDg5NDczNXww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1577563908411-5077b6dc7624?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxjaGF0fGVufDB8fHx8MTc2MDg5NDczNXww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Photo by <a href="https://unsplash.com/@lunarts">Volodymyr Hryshchenko</a> on <a href="https://unsplash.com">Unsplash</a></figcaption></figure></div><p>Have you looked at the latest AI breakthroughs&#8212;agents that can plan, code, and research&#8212;and thought, "That's cool and all but&#8230; how the hell do one actually build something like that?"</p><p>If you're just getting started with Python development, or even if you&#8217;re a seasoned expert, you've probably felt this gap too. Maybe you've played with the demos, called an API or two, and seen just enough of the magic to get excited.</p><p>But moving from just prompting an LLM to building intelligent, autonomous agents is a different beast entirely. Things start to break pretty soon as you stack up more and more complexity. And it seems every tutorial out there is oblivious to this. Anyone can show you how to code a simple chatbot maybe with some RAG, but very few resources take you from that humble beginnings to something that actually looks like a modern multi-agent system that works autonomously.</p><p>This series is my answer to that problem.</p><p>Over the next 10 or so posts&#8212;the next couple of months&#8212;we are going to build a comprehensive, autonomous AI agent. And we're going to do it incrementally.</p><p>We'll start with the simplest possible application: the basic chatbot loop that stores conversation history. Why? Well, because even if everyone has already seen this, we need proper foundations to build on top. We need to see the full flow&#8212;how messages are stored, how a system prompt works, how to interact with the LLM, and what's really happening under the hood.</p><p>But from there we'll level up pretty fast, adding new capabilities piece by piece until we have a personal agent capable of doing deep research and generate long, coherent reports all on its own.</p><h2>Tools of the Trade</h2><p>Now, you might be thinking, why build from scratch and not just use LangChain, or LlamaIndex, or some other well known framework?</p><p>Here's my thesis. When you start with those massive, professional frameworks, you learn more about the framework than you do about the fundamentals. Those tools are powerful, but they are also black boxes. They hide the logic and complexity behind layers of abstraction. You end up learning their way of doing things, their design patterns, and their API. And frameworks change, but fundamentals remain.</p><p>I believe it's far more valuable to learn how to do things as close to the metal as possible. Our goal is to write the least amount of boilerplate code possible, but&#8212;and this is the key&#8212;without hiding the any of core logic. We will build this entire thing ourselves, understanding every single piece of the puzzle.</p><p>We'll use a tech stack that gets out of our way. Only three tools:</p><p>For the UI we&#8217;ll use <strong>Streamlit</strong>. For those who don&#8217;t know it, Streamlit is a radically simple application framework that turns any Python script into a fully fledged web app. The key here is what it doesn't make you do: you write zero boilerplate, zero layout, zero presentation, and zero state management code. You just write core logic, and a good enough web UI appears.</p><p>For the actual chatbot we'll use the <strong>ARGO</strong> framework. I like to call it FastAPI for AI agents because it has that same lightweight, decorator-based feel. It is the simplest, most Pythonic agent framework you&#8217;ve seen, and I promise you&#8217;ll fall in love with it. Despite being super simple, it forces you to build the logic using a very clean and lightweight skills-based pattern for modularizing our agent's intelligence.</p><p>And for data we&#8217;ll <strong>BeaverDB</strong>. This is a lightweight wrapper on top of SQLite that gives you everything you could ever want from a modern database. But it is not a modern database server. It's just a single, embedded binary file on your disk. There's no Docker, no authentication, no connection strings, no schemas, no boilerplate. Yet, on top of this simplicity, BeaverDB gives us a very Pythonic, very comfortable API for a document database with vector and full text search, and cool features like persistent dictionaries, lists, priority queues, and even pub/sub. It makes building something like a RAG pipeline extremely simple without any complex setup.</p><p>That's it. With just these three basic libraries, we will build a fully autonomous agent.</p><h2>The Journey Ahead</h2><p>Here's the roadmap I've thought so far. We'll add a new level of intelligence in each post.</p><ul><li><p><strong>Level 0: The Conversationalist</strong>. The foundation. A simple Streamlit UI with a stateful chat loop that understands conversation history and system prompts.</p></li><li><p> <strong>Level 1: The Assistant</strong>. We'll give our agent long-term memory. We'll build a full Retrieval-Augmented Generation (RAG) pipeline from scratch using BeaverDB so it can answer questions based on a private knowledge base.</p></li><li><p><strong>Level 2: The Researcher</strong>. We'll break our agent out of its box and give it a tool to search the web, allowing it to access up-to-the-minute information.</p></li><li><p><strong>Level 3: The Analyst</strong>. This is where it gets really fun. We'll give the agent the ability to write and execute its own Python code in a safe sandbox to solve problems, analyze data, and even debug its own mistakes.</p></li><li><p><strong>Level 4: The Editor</strong>. We'll upgrade our UI from a simple chat to a persistent, shared canvas, where you and the agent can work side-by-side to co-write documents and refine plans.</p></li><li><p><strong>Level 5: The Scientist</strong>. The finale. We'll integrate all previous capabilities, giving the agent a master prompt that allows it to take a high-level goal, create a multi-step plan, and execute it independently from start to finish.</p></li></ul><p>So here is my promise. It may take us more than 5 posts, but we are going to learn how to build a chatbot by building a chatbot, step by step. No unnecessary theory, just working code and clear intuitions. </p><p>I'll be providing a public GitHub repository with all the code, and each post will be a direct, straightforward Python script that you can type in half an hour.</p><p>When you're done with this series, it won't matter if you want to keep using ARGO and BeaverDB&#8212;which I hope you will&#8212;or move on to more &#8220;professional&#8221; tools. You'll be ready anyway, because these tools are so thin, what you'll have learned isn't how to use a specific library. You will have learned how the logic works underneath.</p><p>You'll understand how to orchestrate the different components involved in a typical chatbot, how to modularize the agent's brain, and more generally, how to think about building capable, intelligent, LLM-based applications. That's the real takeaway.</p><p>In the next post, we'll jump straight into <strong>Level 0: The Conversationalist</strong>. So hit that subscribe button and you'll get all posts in your inbox, 100% free.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://blog.apiad.net/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://blog.apiad.net/subscribe?"><span>Subscribe now</span></a></p><p>Ready to build?</p>]]></content:encoded></item><item><title><![CDATA[How I Built the Database of my Dreams]]></title><description><![CDATA[And how you can use it to build AI apps 100x faster]]></description><link>https://blog.apiad.net/p/how-i-built-the-database-of-my-dreams</link><guid isPermaLink="false">https://blog.apiad.net/p/how-i-built-the-database-of-my-dreams</guid><dc:creator><![CDATA[Alejandro Piad Morffis]]></dc:creator><pubDate>Sat, 20 Sep 2025 21:27:31 GMT</pubDate><enclosure url="https://images.unsplash.com/photo-1656528181090-0808f91fa894?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxiZWF2ZXJzfGVufDB8fHx8MTc1ODQwMzM2Mnww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://images.unsplash.com/photo-1656528181090-0808f91fa894?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxiZWF2ZXJzfGVufDB8fHx8MTc1ODQwMzM2Mnww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://images.unsplash.com/photo-1656528181090-0808f91fa894?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxiZWF2ZXJzfGVufDB8fHx8MTc1ODQwMzM2Mnww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1656528181090-0808f91fa894?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxiZWF2ZXJzfGVufDB8fHx8MTc1ODQwMzM2Mnww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1656528181090-0808f91fa894?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxiZWF2ZXJzfGVufDB8fHx8MTc1ODQwMzM2Mnww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1656528181090-0808f91fa894?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxiZWF2ZXJzfGVufDB8fHx8MTc1ODQwMzM2Mnww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1456w" sizes="100vw"><img src="https://images.unsplash.com/photo-1656528181090-0808f91fa894?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxiZWF2ZXJzfGVufDB8fHx8MTc1ODQwMzM2Mnww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" width="6363" height="4242" data-attrs="{&quot;src&quot;:&quot;https://images.unsplash.com/photo-1656528181090-0808f91fa894?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxiZWF2ZXJzfGVufDB8fHx8MTc1ODQwMzM2Mnww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:4242,&quot;width&quot;:6363,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;a bug on a log in the water&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="a bug on a log in the water" title="a bug on a log in the water" srcset="https://images.unsplash.com/photo-1656528181090-0808f91fa894?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxiZWF2ZXJzfGVufDB8fHx8MTc1ODQwMzM2Mnww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1656528181090-0808f91fa894?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxiZWF2ZXJzfGVufDB8fHx8MTc1ODQwMzM2Mnww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1656528181090-0808f91fa894?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxiZWF2ZXJzfGVufDB8fHx8MTc1ODQwMzM2Mnww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1656528181090-0808f91fa894?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxiZWF2ZXJzfGVufDB8fHx8MTc1ODQwMzM2Mnww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Photo by <a href="https://unsplash.com/@derekotway">Derek Otway</a> on <a href="https://unsplash.com">Unsplash</a></figcaption></figure></div><p>I really love to code. That much I&#8217;m sure you can tell. So I have this very common problem, that I&#8217;m sure many of you have. A gazillion ideas in my head wanting to burst out, and very little time to try them all. To try and get around this, I like to prototype really fast and dirty implementations of anything that makes me curious, just to see if it leads somewhere. (This is why I have hundreds of unfinished projects in my Github.)</p><p>So, where was I? Yeah, this is one of the reasons I&#8217;ve made Python, and specifically <code>streamlit</code>, a key part of my toolkit. It lets me build the very simplest semblance of an app extremely fast, without considering anything remotely unnecessary (for a first prototype) like authentication and user roles, or worrying about the user interface.</p><p>Now, here is the thing. Recently, I&#8217;ve been building <em>a lot</em> of AI stuff. Chatbots galore for all sorts of tasks from general question answering to specific things like storytelling, evolutionary computation, automated coding, you name it. And I keep finding a specific pain point that grinds me to a halt in the most exciting part of prototyping: <em>the database</em>.</p><p>Very soon, my simple AI prototype needs a vector database for RAG or a key-value store for simple configurations, or a message queue for background tasks and a persistent list to store conversation history. And this is before anything remotely close to a production-ready feature is in the horizon. This is just the second thing you&#8217;ll need right after streaming the first few tokens from OpenAI. It&#8217;s unescapable for anything but the simplest toys.</p><p>So, before I know it, I&#8217;m wrestling with two or three containers&#8212;or worse, juggling credentials for three different cloud services&#8212;and writing boilerplate code just to glue it all together. I&#8217;ve become an accidental, and very grumpy, DevOps engineer. My worst nightmare!</p><p>What I want is something in the spirit of <code>streamlit</code> but for managing data. A minimalistic, no-bullshit database that just supports the basic modalities we all need in modern applications, like, dunno, JSON storage, maybe? And vector search? Yeah, that, but also combined with full-text and fuzzy, persistent lists and queues&#8230; hell, even a decent pub-sub system if we&#8217;re asking! Is it too much to ask for someone to make SQLite but for modern data!</p><p>Well&#8230; I guess if you want something done, you might as well do it yourself.</p><p>So this is the story of how I built BeaverDB&#8212;a Pythonic and multi-modal interface on top of SQLite that just works out of the box for rapid prototyping. And it scales to medium sized projects just fine (SQLite is damn fast!). BeaverDB my attempt to build the tool I wish I had&#8212;a library that provides the high-level data structures modern AI applications need, without sacrificing the radical simplicity of a single .db file.</p><h2>Introducing Beaver</h2><p>The guiding principle behind Beaver&#8217;s API is a minimal surface area with a fluent interface. You only ever need to use two classes. Everything else flows naturally from the main database instance, returning dedicated wrapper objects with a rich, Pythonic interface.</p><p>One key idea for this design is that it should just work. Zero configuration, just sensible out of the box decisions. So, no schema, everything indexed by default, no need to declare tables or entities or anything before hand, collections just get created when first used, you know, that kind of thing. It should be as easy as instantiating a class and calling two methods.</p><p>Let&#8217;s take a quick tour on what you can today with it.</p><p><strong>Key-Value &amp; Caching</strong></p><p>Need to store configuration, user profiles, or cache expensive API calls? The namespaced dictionary is your go-to. It behaves just like a Python dict but is backed by the database, with optional TTL support.</p><pre><code><code># Use a dictionary for caching API calls for 1 hour
api_cache = db.dict("api_cache")
api_cache.set("weather", {"temp": "15C"}, ttl_seconds=3600)
print(f"Cached weather: {api_cache.get('weather')}")</code></code></pre><p><strong>Persistent Lists</strong></p><p>This is the most straightforward way to manage ordered sequences. For a chatbot, it&#8217;s the perfect way to maintain the turn-by-turn history of a conversation. It works like a Redis list, with all bells and whistles, implementing a full Pythonic list interface, but backed in the DB. It supports appending and removing from the head and tail, as well as inserting or removing anywhere in between, blazingly fast, because all operations are indexed.</p><pre><code><code># Manage a conversation with a user
chat_history = db.list("conversation_with_user_123")
chat_history.push({"role": "user", "content": "Hello, Beaver!"})
chat_history.push({"role": "assistant", "content": "Hello! How can I help?"})
print(f"Conversation length: {len(chat_history)}")</code></code></pre><p><strong>Priority Queues</strong></p><p>A priority queue is the essential tool for orchestrating an autonomous agent. It ensures the agent always works on the most critical task first, regardless of when it was added. The API is extremely simplified on purpose. For anything more complicated, use a full-featured list.</p><pre><code><code># An AI agent's task list
tasks = db.queue("agent_tasks")
tasks.put({"action": "summarize_news"}, priority=10)
tasks.put({"action": "respond_to_user"}, priority=1) # Higher priority

# Agent always gets the most important task first
next_task = tasks.get() # -&gt; Returns the "respond_to_user" task</code></code></pre><p><strong>Real-time Pub/Sub</strong></p><p>Need to build a decoupled, event-driven system? The pub/sub channel allows different parts of your application&#8212;or even different processes&#8212;to communicate in real-time. Beautifully designed with an extremely simple fluent API, but extremely performant, thread-safe, even works across different processes. Plus, it comes with an optional <code>async</code> interface if you&#8217;re feeling fancy.</p><pre><code><code># In one process, a monitor publishes an event
system_events = db.channel("system_events")
system_events.publish({"event": "user_login", "user": "alice"})

# In another, a logger subscribes and receives the message
with db.channel("system_events").subscribe() as listener:
    for message in listener.listen(timeout=1):
        print(f"Event received: {message}")</code></code></pre><p><strong>Collections &amp; Hybrid Search</strong></p><p>This is the core component for any Retrieval-Augmented Generation task. It&#8217;s a multi-modal collection of structured documents that understands vectors, and text, allowing you to combine search strategies for the best results. It also performs fuzzy search on demand with a very clever indexing strategy that I&#8217;ll tell you all about in the next section.</p><pre><code><code>from beaver import BeaverDB, Document
from beaver.collections import rerank

docs = db.collection("articles")
doc = Document(embedding=[...], content="Python is fast.")
docs.index(doc, fuzzy=True)

# Combine vector and full-text/fuzzy search for better results
vector_results = docs.search(vector=[0.15, 0.85, 0.15])
text_results = docs.match(query="pthon", fuzziness=1)
best_context = rerank(vector_results, text_results)</code></code></pre><p>And there&#8217;s a lot more. You can connect documents with relations and build a knowledge graph that you can later query to find similar documents or implement graph-based recommender system.</p><p>All in one freaking database file. No docker. No servers. No headache.</p><h2>A Peek Under the Hood</h2><p>Beaver is built on a series of pragmatic design decisions intended to leverage SQLite&#8217;s native capabilities to the fullest, avoiding slow application-layer logic wherever possible.</p><p>For one, it never creates new tables when storing stuff. Everything is stored and indexed in cleverly designed global tables that are created at startup time (only the very first time the DB file is created). This also means you get virtually infinite lists, dicts, queues, collections, etc., because these aren&#8217;t different tables (which would be a pain in the arsenal to maintain). And&#8230; (roll drums)&#8230; no migrations!</p><p>I want to highlight two specific features to tell you a bit about the underlying implementation details, so you can see the lengths it goes to try and be efficient out of the box.</p><p><strong>The Pub/Sub System</strong></p><p>The pub/sub system is the greatest example of efficiency by design. It&#8217;s built on a single, append-only log table with an index for the channel name.</p><p>For each channel, a single background thread polls this table for new entries and fans them out to any number of in-memory queues, one for each subscriber. The key insight here is that because the database is only ever touched by one polling thread per channel, adding a second, third, or hundredth subscriber adds <em>zero</em> additional load to the database. From the database&#8217;s perspective, new listeners are basically free.</p><p>Even better, this polling is only activated when there is at least one listener, and stops immediately after the last listener disconnects. This means we only ever use the minimum resources necessary.</p><p><strong>Hybrid Text Search: The Two-Stage Filter</strong></p><p>The other feature I think is kinda beautiful is the fuzzy search. Calculating the Levenshtein distance between two strings is computationally expensive, and there is no way to build an index beforehand without a combinatorial explosion in storage size. But running it across every document in a collection would be unusable for anything beyond a few thousand entries.</p><p>Beaver solves this with a two-stage process. First, it uses a pre-computed trigram index in SQLite to instantly find a very small set of candidate documents that share a high percentage of 3-character &#8220;chunks&#8221; with the query. This is a very fast SQL query that already gives us a very good approximation to the response. Then, we run the expensive Levenshtein algorithm <em>only</em> on this small, pre-filtered set of candidates in memory.</p><h2>The Vector Store Dilemma</h2><p>Vector storage is my biggest concern at the moment. The current implementation is, I think, pretty good for a typical use case of infrequent indexing and fast retrieval. But it can be much improved.</p><p>Right now, Beaver uses an in-memory k-d tree, which provides excellent search speed once the index is loaded into RAM. However, the index is ephemeral&#8212;it lives and dies with the application process. This creates a significant bottleneck: every time your application starts, the <em>entire</em> index must be rebuilt from scratch by reading all vectors from the database. Furthermore, indexing a new document requires a full, blocking rebuild of the entire tree.</p><p>As long as you index documents in a background process, infrequently&#8212;which is what most RAG-based applications do&#8212;this works just fine. But I&#8217;m not satisfied with this implementation, so here is my plan for its update.</p><p>The roadmap goes through integrating a state-of-the-art ANN library like <code>faiss</code>. This will enable a persistent, on-disk index that can be loaded instantly. But more importantly, we need to support incremental additions, which means newly added documents don&#8217;t rebuild the entire index. The key to achieve this is to have a persistent, large index, and a small, in-memory, temporal index of new additions that gets added to the persistent index from time to time. Keep these two in sync is far from trivial, but I already have a somewhat detailed plan.</p><h2>Conclusion</h2><p>Beaver is the result of a couple of weekends of focused frustration, and it&#8217;s already the backbone of my own AI prototypes. Even if it never reaches any level of maturity, I&#8217;m extremely satisfied with it because it already works for my use cases, and I&#8217;ve learned so much building it. Damn, who knew building databases could be so fun!</p><p>Now, what&#8217;s in it for you? Well, Beaver is not meant to replace your production PostgreSQL cluster. That&#8217;s unthinkable. But it might be the &#8220;good enough&#8221; database that lets you go from an idea to a working prototype in minutes, not hours. So, stop being an accidental DevOps engineer and go build something cool.</p><p>You can get started with a simple <code>pip install beaver-db</code>. Check out the <a href="https://github.com/apiad/beaver">GitHub repository</a> for a lot of examples. And leave me any questions or comments either here or in the repository issues.</p><p>Have fun!</p>]]></content:encoded></item><item><title><![CDATA[Simplifying Configuration Management in Pure Python]]></title><link>https://blog.apiad.net/p/simplifying-configuration-management</link><guid isPermaLink="false">https://blog.apiad.net/p/simplifying-configuration-management</guid><dc:creator><![CDATA[Alejandro Piad Morffis]]></dc:creator><pubDate>Fri, 21 Feb 2025 22:32:23 GMT</pubDate><enclosure url="https://images.unsplash.com/photo-1494059980473-813e73ee784b?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxOXx8cmFuZG9tfGVufDB8fHx8MTc0MDE0NjU1MXww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://images.unsplash.com/photo-1494059980473-813e73ee784b?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxOXx8cmFuZG9tfGVufDB8fHx8MTc0MDE0NjU1MXww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://images.unsplash.com/photo-1494059980473-813e73ee784b?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxOXx8cmFuZG9tfGVufDB8fHx8MTc0MDE0NjU1MXww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1494059980473-813e73ee784b?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxOXx8cmFuZG9tfGVufDB8fHx8MTc0MDE0NjU1MXww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1494059980473-813e73ee784b?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxOXx8cmFuZG9tfGVufDB8fHx8MTc0MDE0NjU1MXww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1494059980473-813e73ee784b?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxOXx8cmFuZG9tfGVufDB8fHx8MTc0MDE0NjU1MXww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 1456w" sizes="100vw"><img src="https://images.unsplash.com/photo-1494059980473-813e73ee784b?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxOXx8cmFuZG9tfGVufDB8fHx8MTc0MDE0NjU1MXww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080" width="1200" height="801.6032064128257" data-attrs="{&quot;src&quot;:&quot;https://images.unsplash.com/photo-1494059980473-813e73ee784b?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxOXx8cmFuZG9tfGVufDB8fHx8MTc0MDE0NjU1MXww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:false,&quot;imageSize&quot;:&quot;large&quot;,&quot;height&quot;:4000,&quot;width&quot;:5988,&quot;resizeWidth&quot;:1200,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;stack of jigsaw puzzle pieces&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-large" alt="stack of jigsaw puzzle pieces" title="stack of jigsaw puzzle pieces" srcset="https://images.unsplash.com/photo-1494059980473-813e73ee784b?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxOXx8cmFuZG9tfGVufDB8fHx8MTc0MDE0NjU1MXww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1494059980473-813e73ee784b?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxOXx8cmFuZG9tfGVufDB8fHx8MTc0MDE0NjU1MXww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1494059980473-813e73ee784b?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxOXx8cmFuZG9tfGVufDB8fHx8MTc0MDE0NjU1MXww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1494059980473-813e73ee784b?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwxOXx8cmFuZG9tfGVufDB8fHx8MTc0MDE0NjU1MXww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Photo by <a href="true">Hans-Peter Gauster</a> on <a href="https://unsplash.com">Unsplash</a></figcaption></figure></div><blockquote><p><em>Hey! Alejandro here, from <a href="https://blog.apiad.net/">Mostly Harmless Ideas</a>. Welcome to <strong>Coding Lessons</strong>, a section of MHI where I sparingly post short, coding-focused articles. This one came out after a couple of days struggling to find the right balance for managing complexity in a recent project. Hope you enjoy it. </em></p></blockquote><p>If you&#8217;ve ever worked on any application with a development and a production environment, you&#8217;ve dealt with this problem: managing configurations and feature flags. A centralized, easy-to-modify location for configurable options that vary across different deployment environments is crucial for maintaining flexibility and enabling rapid iterations. I&#8217;ve been recently working on a mid-sized chatbot project that requires a way to toggle specific features in different environments (think enabling different kinds of tools or skills for the chatbot).</p><p>After struggling with environment variables and looking over some really over-engineered solutions, I found a straightforward, pure Python approach that works perfectly for small teams that want to iterate fast. This article is a short presentation of this approach. We will explore the motivation behind this method, provide implementation details, and highlight potential pitfalls to avoid.</p><h2><strong>Motivation</strong></h2><p>As applications grow, so do their configurations. Development, staging, and production environments often require distinct settings. If not appropriately managed, managing these configurations can become cumbersome. Traditional approaches involve plain text files like a <code>.env</code> or <code>config.toml</code> file&#8212;that are not expressive enough&#8212;, using structured formats like a <code>config.json</code> or <code>config.yaml</code> file&#8212;which are more expressive but harder to read and maintain&#8212;or worse, relying on external libraries to manage configurations, leading to complexity and confusion.</p><p>The proposed method simplifies this by utilizing separate Python files for each environment. By dynamically loading the appropriate configuration based on a single environment variable, we can keep our code clean, organized, and easily managed.</p><p>An example of a well-established framework that follows this practice is Django. Django projects often separate environment-specific configurations into distinct Python files, such as <code>development.py</code> and <code>production.py</code>, to manage settings for different environments effectively. This approach is widely recommended in the Django community as it simplifies transitions between environments and allows for better organization of configuration settings.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://blog.apiad.net/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Subscribe for free to receive new articles about Computer Science, AI, Software Development, and related topics directly in your inbox.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h2><strong>Implementation Details</strong></h2><p>The core idea of this method is to put configuration options and feature flags in a Python file so that, once imported, you can access all those variables as module members. The only real trick is to dynamically import the correct file based on an environment variable, so you get development, staging, testing, or production configuration (or whatever contrived scenario you have) without the rest of your code knowing what environment is running on.</p><p>Here&#8217;s a plain step-by-step process.</p><h3><strong>Step 1: Create Environment-Specific Configuration Files</strong></h3><p>We will create separate configuration files for each environment (e.g., <code>production.config.py</code>, <code>staging.config.py</code>, <code>development.config.py</code>). Each file will define global variables for feature flags and other configuration options.</p><h4><strong>Example: </strong><code>production.config.py</code></h4><pre><code><code>NEW_FEATURE = False 
EXPERIMENTAL_FEATURE = False 
DATABASE_URL = "https://prod.database.url"`</code></code></pre><h4><strong>Example: </strong><code>staging.config.py</code></h4><pre><code><code>NEW_FEATURE = True 
EXPERIMENTAL_FEATURE = False 
DATABASE_URL = "https://staging.database.url"`</code></code></pre><h4><strong>Example: </strong><code>development.config.py</code></h4><pre><code><code>NEW_FEATURE = True 
EXPERIMENTAL_FEATURE = True 
DATABASE_URL = "https://dev.database.url"`</code></code></pre><h3><strong>Step 2: Create a Loader Function</strong></h3><p>Next, we will create a <code>config.py</code> file that contains a function to load the appropriate configuration based on the <code>ENVIRONMENT</code> variable.</p><pre><code><code>import os
import importlib

def load_config():
    environment = os.getenv("ENVIRONMENT", "DEVELOPMENT").upper()

    try:
        return importlib.import_module(f"{environment.lower()}.config")
    except ImportError:
        raise Exception(f"Configuration for environment '{environment}' not found.")</code></code></pre><h3><strong>Step 3: Access Configuration in Your Application</strong></h3><p>Finally, we can access the loaded configuration in our application by calling the <code>load_config</code> function.</p><pre><code><code>from config import load_config

def main():
    config = load_config()

    if config.NEW_FEATURE:
        print("New Feature is enabled!")
    else:
        print("New Feature is disabled.")

    print(f"Database URL: {config.DATABASE_URL}")

if __name__ == "__main__":
    main()</code></code></pre><h3><strong>Step 4: Set the Environment Variable</strong></h3><p>Before running your application, set the <code>ENVIRONMENT</code> variable in your terminal (or in a .env file if you are already using it):</p><pre><code><code>export ENVIRONMENT=production python main.py
# or staging or development</code></code></pre><h2><strong>Advantages and Limitations</strong></h2><p>When managing application configurations and feature flags, developers often face a choice between using environment-specific Python configuration files and traditional <code>.env</code> files that are most commonplace. Each approach has its own set of advantages and limitations. Here, we will explore these aspects in detail.</p><h3><strong>Advantages of Python Config Files</strong></h3><ol><li><p><strong>Version Control</strong>: Python configuration files can be checked into version control systems (like Git) without exposing sensitive information. This allows teams to track changes, collaborate effectively, and maintain a history of configuration adjustments across different environments.</p></li><li><p><strong>Expressivity: </strong>Python is far more expressive than any plain-text or structured format. You can have options that are lists, dictionaries, or even instances of custom classes. You shouldn&#8217;t abuse this power, though.</p></li><li><p><strong>Computed Values</strong>: Python files allow for the inclusion of computed values and logic. This means you can dynamically generate configurations based on other parameters or conditions, providing greater flexibility than static key-value pairs in <code>.env</code> files.</p></li><li><p><strong>Modular Design</strong>: Common configuration values can be refactored into separate modules imported by relevant configuration files. This modularity promotes code reuse and reduces redundancy, making maintaining configurations across multiple environments easier.</p></li></ol><h3><strong>Limitations of Python Config Files</strong></h3><ol><li><p><strong>Security Concerns</strong>: Unlike <code>.env</code> files, which are often excluded from version control (ensuring sensitive information remains private), Python configuration files may inadvertently expose sensitive data if not managed properly. Developers must exercise caution to avoid committing sensitive information to the repository.</p></li><li><p><strong>Risk of Misconfiguration</strong>: The flexibility of Python configuration files can lead to developers unintentionally modifying production settings during development or testing phases, potentially causing disruptions in live environments.</p></li><li><p><strong>Learning Curve</strong>: This approach may not be as familiar to new developers as the widely adopted <code>.env</code> file convention. As a result, onboarding new team members may require additional training or documentation to understand the custom setup.</p></li></ol><h3><strong>Advantages of .env Files</strong></h3><ul><li><p><strong>Simplicity</strong>: <code>.env</code> files are straightforward text files that store key-value pairs, making them easy to read and edit.</p></li><li><p><strong>Common Practice</strong>: The use of <code>.env</code> files is a well-established practice in many development communities, making it easier for new developers to adapt.</p></li></ul><h3><strong>Limitations of .env Files</strong></h3><ul><li><p><strong>No Security by Default</strong>: While <code>.env</code> files can be excluded from version control, they are still plain text files that can be easily accessed if not adequately secured.</p></li><li><p><strong>Limited Functionality</strong>: <code>.env</code> files do not support computed values or complex logic, which can limit their usability in more dynamic applications. However, this may be considered a strength in reducing configuration complexity.</p></li></ul><h3><strong>Striking a Sane Balance</strong></h3><p>There is no free lunch, or so the saying goes. However, you may strike a sane balance in this case by externalizing sensitive and static configuration options to <code>.env</code> files and keeping more dynamic options (e.g., feature flags, option lists, or computable values) in Python files. This way, you maintain flexibility for rapidly changing or very development-specific configuration variables and leave the most sensitive, project-wide configurations for maintainers or DevOps engineers to handle.</p><h2><strong>Obvious Pitfalls to Avoid</strong></h2><p>While this approach offers a clean and organized way to manage configurations, there are some pitfalls to be aware of:</p><ol><li><p><strong>Security Risks</strong>: I already said it, but to reiterate, <strong>DO NOT STORE</strong> sensitive information (e.g., API keys and database credentials) in plain text within your configuration files. Instead, consider using environment variables or, even better, secret management tools.</p></li><li><p><strong>Version Control</strong>: As a follow-up to the above, ensure that sensitive information (e.g., what&#8217;s included in <code>.env</code> files) is excluded from version control (e.g., using <code>.gitignore</code>). This helps prevent accidental exposure of secrets.</p></li><li><p><strong>Error Handling</strong>: Implement robust error handling when loading configurations. If a specified configuration file does not exist or fails to import, your application should gracefully handle this in upstream code rather than crash unexpectedly.</p></li><li><p><strong>Documentation</strong>: Clearly document each configuration file and its purpose.  You have Python comments, for Turing&#8217;s sake! This will help team members understand the differences between environments and make necessary modifications.</p></li><li><p><strong>Testing</strong>: Regularly test your application in all environments to ensure that the configurations are correctly applied and that features behave as expected.</p></li></ol><h2><strong>Conclusion</strong></h2><p>Managing feature flags and configurations in Python can be streamlined using environment-specific configuration files loaded dynamically based on a <code>ENVIRONMENT</code> variable. This approach enhances organization, simplifies edits, and provides clarity when dealing with multiple environments. By being mindful of security risks and pitfalls, you can implement this strategy effectively in your projects, leading to smoother development cycles and more robust applications.</p><p>Choosing between Python configuration files and <code>.env</code> files depends on your project's needs and the team's familiarity with each approach. While Python configuration files offer greater flexibility and modularity, they come with risks that require careful management. On the other hand, <code>.env</code> files provide simplicity and are widely recognized but lack built-in security features and advanced capabilities.</p><p>Ultimately, understanding the advantages and limitations of each method will help you make an informed decision that aligns with your development practices and security requirements.</p><p>Now, I&#8217;m interested in reading your thoughts. Have you ever used Python configuration files? If not, why not? If yes, what was your experience?</p>]]></content:encoded></item><item><title><![CDATA[Building a Perplexity AI clone]]></title><description><![CDATA[Combining RAG with Google Search to build a bot that knows about everything.]]></description><link>https://blog.apiad.net/p/building-a-perplexity-ai-clone</link><guid isPermaLink="false">https://blog.apiad.net/p/building-a-perplexity-ai-clone</guid><dc:creator><![CDATA[Alejandro Piad Morffis]]></dc:creator><pubDate>Sat, 18 May 2024 10:06:41 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!t3q_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82529ea8-04f4-4754-8dc3-d931e46f137f_1152x640.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote><p><strong>Note: </strong>This article is part of my in-progress ebook, <strong>How to Train your Chatbot</strong>, a jam-packed handbook on how to use LLMs to build all sorts of cool stuff. You can get early access to the ebook (PDF and EPUB) as well as the full source code of all demos in the following link:</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://apiad.gumroad.com/l/chatbots/gttjcez&quot;,&quot;text&quot;:&quot;How to Train your Chatbot - 50% off&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://apiad.gumroad.com/l/chatbots/gttjcez"><span>How to Train your Chatbot - 50% off</span></a></p></blockquote><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!t3q_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82529ea8-04f4-4754-8dc3-d931e46f137f_1152x640.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!t3q_!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82529ea8-04f4-4754-8dc3-d931e46f137f_1152x640.jpeg 424w, https://substackcdn.com/image/fetch/$s_!t3q_!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82529ea8-04f4-4754-8dc3-d931e46f137f_1152x640.jpeg 848w, https://substackcdn.com/image/fetch/$s_!t3q_!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82529ea8-04f4-4754-8dc3-d931e46f137f_1152x640.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!t3q_!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82529ea8-04f4-4754-8dc3-d931e46f137f_1152x640.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!t3q_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82529ea8-04f4-4754-8dc3-d931e46f137f_1152x640.jpeg" width="1152" height="640" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/82529ea8-04f4-4754-8dc3-d931e46f137f_1152x640.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:&quot;normal&quot;,&quot;height&quot;:640,&quot;width&quot;:1152,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!t3q_!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82529ea8-04f4-4754-8dc3-d931e46f137f_1152x640.jpeg 424w, https://substackcdn.com/image/fetch/$s_!t3q_!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82529ea8-04f4-4754-8dc3-d931e46f137f_1152x640.jpeg 848w, https://substackcdn.com/image/fetch/$s_!t3q_!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82529ea8-04f4-4754-8dc3-d931e46f137f_1152x640.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!t3q_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82529ea8-04f4-4754-8dc3-d931e46f137f_1152x640.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">No artists were tortured in the making of this image.</figcaption></figure></div><p>In this article, we will continue our exploration of retrieval augmented generation, but zooming out to the widest possible scale. Instead of answering questions from a restricted domain, like a PDF, let&#8217;s tackle the opposite problem: <em>a bot that can answer anything at all!</em> </p><p>The problem is our bot doesn&#8217;t know about everything that has happened in the world, and especially it doesn&#8217;t know about anything that has happened since its base pretraining.</p><p>But you know who does? Google! Well, not exactly everything, but a giant search engine like Google is the closest we have to a repository of the world&#8217;s knowledge. However, as everyone who&#8217;s ever used a traditional search engine knows, it is often not trivial to find the answer to your question. You have to sift through several of the search results and skim through their content.</p><p>On the other hand, LLMs, even the most powerful and recent ones, are by definition static, in the sense that you cannot update their knowledge without some costly retraining. And even if you could, there is still a limit to how many raw facts they could learn and then retrieve flawlessly. As we&#8217;ve seen, LLMs are lousy at<sup> </sup>remembering minute details like dates and are prone to subtle hallucinations.</p><p>So, how can we combine the language understanding and synthesis power of LLMs with the recency and breadth of knowledge of search engines?</p><p>With RAG, of course!</p><blockquote><p><em><strong>Note: </strong>If this is the first article of this series for you, make sure to check the previous articles <a href="https://blog.apiad.net/p/chatgpt-clone">here</a> and <a href="https://blog.apiad.net/p/pdf-bot">here</a>, and my guide to <a href="https://blog.apiad.net/p/what-is-retrieval-augmented-generation">retrieval augmented generation</a>. This article bases heavily on previously discussed concepts.</em></p></blockquote><h2><strong>The plan</strong></h2><p>Keeping in line with the previous article, I will first explain at a broad level what is the workflow and the necessary functionalities for this app, and then we&#8217;ll go into implementation details. Since much of this demo builds on previous functionality, I won&#8217;t show you the whole code, only the novel parts.</p><blockquote><p>You can check a working version of this demo online at <a href="https://llm-book-search.streamlit.app">https://llm-book-search.streamlit.app</a>. This demo is provided on a best-effort basis and might be taken offline at any moment.</p></blockquote><p>At a high level, this demo looks like a poor man&#8217;s search engine. We&#8217;ll have a simple search bar where the user can type a query. But actually, this isn&#8217;t just a search engine, it&#8217;s an answer engine! Instead of listing the matching websites, we&#8217;ll take it a step further and crawl those websites, read them, find a relevant snippet that answers the user query, and compute a natural language response with citations and all. Furthermore, we won&#8217;t even pass the exact user query to Google, but instead ask the LLM to suggest a small set of relevant queries that are more suitable for search.</p><p>Here is a screenshot of the final result, showing our answering engine talking about recent news (the launch of GPT-4o) which happened way after its training:</p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;a580c3a4-ab75-4b71-ae64-c0c98abf36e7&quot;,&quot;duration&quot;:null}"></div><p>Here is the general workflow:</p><ol><li><p>The user types a question.</p></li><li><p>The LLM provides a set of relevant related queries.</p></li><li><p>For each query, we obtain a set of search results from Google.</p></li><li><p>For each search result, we crawl the webpage (if possible) and store the raw text.</p></li><li><p>Given the original user question and the crawled text, we build a suitable context.</p></li><li><p>The LLM answers the question with the provided context.</p></li></ol><p>There are several problems we must solve to implement this workflow, and we will go over them one at a time.</p><h2><strong>Getting the relevant queries</strong></h2><p>The first step is to compute a set of relevant queries related to the user question. Why? Well, first, because the user question might not be the type of query that search engines are good at solving. If you&#8217;re using Google, this might be less of an issue. But if you apply this strategy to self-hosted traditional search engines, like ElasticSearch, they instead just use some fancy keyword-based retrieval, with no semantic analysis.</p><p>But more importantly, there simply might not be a single best document (or webpage) with an answer to the user question. For example, if the user asks about a comparison between two products, but there is no actual comparison in the data. All you have is separate documents describing each product. In this case, the best strategy is to perform separate queries for each product and let the LLM do the comparison, effectively leveraging the best of both paradigms.</p><p>Here is a prompt to achieve this. It&#8217;s a simple but effective combination of prompting strategies. At its core, this is a zero-shot instruction, but we also include a brief chain of thought to nudge the model into a more coherent answer structure. Finally, we instruct the model to produce a structured response so we can parse it more easily.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!d3Pj!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76d0cc94-4363-4cdf-a3df-9f5a1b0be517_1032x555.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!d3Pj!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76d0cc94-4363-4cdf-a3df-9f5a1b0be517_1032x555.png 424w, https://substackcdn.com/image/fetch/$s_!d3Pj!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76d0cc94-4363-4cdf-a3df-9f5a1b0be517_1032x555.png 848w, https://substackcdn.com/image/fetch/$s_!d3Pj!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76d0cc94-4363-4cdf-a3df-9f5a1b0be517_1032x555.png 1272w, https://substackcdn.com/image/fetch/$s_!d3Pj!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76d0cc94-4363-4cdf-a3df-9f5a1b0be517_1032x555.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!d3Pj!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76d0cc94-4363-4cdf-a3df-9f5a1b0be517_1032x555.png" width="1032" height="555" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/76d0cc94-4363-4cdf-a3df-9f5a1b0be517_1032x555.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:555,&quot;width&quot;:1032,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:76702,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!d3Pj!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76d0cc94-4363-4cdf-a3df-9f5a1b0be517_1032x555.png 424w, https://substackcdn.com/image/fetch/$s_!d3Pj!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76d0cc94-4363-4cdf-a3df-9f5a1b0be517_1032x555.png 848w, https://substackcdn.com/image/fetch/$s_!d3Pj!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76d0cc94-4363-4cdf-a3df-9f5a1b0be517_1032x555.png 1272w, https://substackcdn.com/image/fetch/$s_!d3Pj!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76d0cc94-4363-4cdf-a3df-9f5a1b0be517_1032x555.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>From this prompt, any reasonably tuned model will produce something like the following, for the query &#8220;<em>what is gpt4-o and how does it compare to gpt4</em>&#8221;:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!0gC3!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7425c5f6-bcc1-4674-b63e-e7aa30e7ea24_1026x388.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!0gC3!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7425c5f6-bcc1-4674-b63e-e7aa30e7ea24_1026x388.png 424w, https://substackcdn.com/image/fetch/$s_!0gC3!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7425c5f6-bcc1-4674-b63e-e7aa30e7ea24_1026x388.png 848w, https://substackcdn.com/image/fetch/$s_!0gC3!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7425c5f6-bcc1-4674-b63e-e7aa30e7ea24_1026x388.png 1272w, https://substackcdn.com/image/fetch/$s_!0gC3!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7425c5f6-bcc1-4674-b63e-e7aa30e7ea24_1026x388.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!0gC3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7425c5f6-bcc1-4674-b63e-e7aa30e7ea24_1026x388.png" width="1026" height="388" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7425c5f6-bcc1-4674-b63e-e7aa30e7ea24_1026x388.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:388,&quot;width&quot;:1026,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:50376,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!0gC3!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7425c5f6-bcc1-4674-b63e-e7aa30e7ea24_1026x388.png 424w, https://substackcdn.com/image/fetch/$s_!0gC3!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7425c5f6-bcc1-4674-b63e-e7aa30e7ea24_1026x388.png 848w, https://substackcdn.com/image/fetch/$s_!0gC3!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7425c5f6-bcc1-4674-b63e-e7aa30e7ea24_1026x388.png 1272w, https://substackcdn.com/image/fetch/$s_!0gC3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7425c5f6-bcc1-4674-b63e-e7aa30e7ea24_1026x388.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Notice that, from the interpretation, we can see the model &#8220;knows&#8221; what GPT-4 is but not GPT-4o. Yet, it produced pretty sensible search queries, including one individual query for GPT4-o, which is very close to what we would do if we were researching this subject on our own.</p><p>Implementing this structured response prompt in our system takes a bit more effort than just passing the prompt. The reason is we want to force the model as much as possible to produce a well-formed JSON object and, while careful prompting can get us pretty far, it is still possible for the model to deviate from just producing a JSON object, and adding some chatty messsages like &#8220;<em>Of course, here is your JSON object:</em>&#8221; which would make it harder to parse the response.</p><p>For this purpose, most OpenAI-compatible APIs implement something called <em>JSON mode</em> which forces the response to be a parseable JSON object. It won&#8217;t guarantee that you get the JSON structure you asked for (this is, in general, not solvable without modifying the sampling method) but it will guarantee that, if the model responds at all, it will be a well-formed JSON.</p><p>To take advantage of this API feature in our implementation, we will add a JSON mode method to our <code>Chatbot</code> class, which also skips the conversation history workflow, because we usually don&#8217;t want these messages to be part of a traditional conversation, but rather use them for one-off instructions.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!nIIF!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc042e75b-69b5-4938-8120-7f4f7e17eece_956x1058.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!nIIF!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc042e75b-69b5-4938-8120-7f4f7e17eece_956x1058.png 424w, https://substackcdn.com/image/fetch/$s_!nIIF!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc042e75b-69b5-4938-8120-7f4f7e17eece_956x1058.png 848w, https://substackcdn.com/image/fetch/$s_!nIIF!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc042e75b-69b5-4938-8120-7f4f7e17eece_956x1058.png 1272w, https://substackcdn.com/image/fetch/$s_!nIIF!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc042e75b-69b5-4938-8120-7f4f7e17eece_956x1058.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!nIIF!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc042e75b-69b5-4938-8120-7f4f7e17eece_956x1058.png" width="956" height="1058" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c042e75b-69b5-4938-8120-7f4f7e17eece_956x1058.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1058,&quot;width&quot;:956,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:127893,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!nIIF!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc042e75b-69b5-4938-8120-7f4f7e17eece_956x1058.png 424w, https://substackcdn.com/image/fetch/$s_!nIIF!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc042e75b-69b5-4938-8120-7f4f7e17eece_956x1058.png 848w, https://substackcdn.com/image/fetch/$s_!nIIF!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc042e75b-69b5-4938-8120-7f4f7e17eece_956x1058.png 1272w, https://substackcdn.com/image/fetch/$s_!nIIF!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc042e75b-69b5-4938-8120-7f4f7e17eece_956x1058.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>In the main application loop, we simply call this method and access the <code>queries</code> key to find the queries.</p><h2><strong>Searching online</strong></h2><p>This is probably the easiest part of the demo. We can use any number of Google Search wrappers to obtain a list of web pages given a set of queries. In this case, I&#8217;m using <code>googlesearch-python</code>, which is one of the libraries with most Github stars, but feel free to experiment.</p><p>The first step is to combine all the results from the different queries into a single set so that we don&#8217;t crawl a web page twice.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!kAv7!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F656568e9-9544-4936-bc9d-308d1c2ceb4f_1032x484.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!kAv7!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F656568e9-9544-4936-bc9d-308d1c2ceb4f_1032x484.png 424w, https://substackcdn.com/image/fetch/$s_!kAv7!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F656568e9-9544-4936-bc9d-308d1c2ceb4f_1032x484.png 848w, https://substackcdn.com/image/fetch/$s_!kAv7!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F656568e9-9544-4936-bc9d-308d1c2ceb4f_1032x484.png 1272w, https://substackcdn.com/image/fetch/$s_!kAv7!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F656568e9-9544-4936-bc9d-308d1c2ceb4f_1032x484.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!kAv7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F656568e9-9544-4936-bc9d-308d1c2ceb4f_1032x484.png" width="1032" height="484" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/656568e9-9544-4936-bc9d-308d1c2ceb4f_1032x484.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:484,&quot;width&quot;:1032,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:46210,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!kAv7!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F656568e9-9544-4936-bc9d-308d1c2ceb4f_1032x484.png 424w, https://substackcdn.com/image/fetch/$s_!kAv7!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F656568e9-9544-4936-bc9d-308d1c2ceb4f_1032x484.png 848w, https://substackcdn.com/image/fetch/$s_!kAv7!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F656568e9-9544-4936-bc9d-308d1c2ceb4f_1032x484.png 1272w, https://substackcdn.com/image/fetch/$s_!kAv7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F656568e9-9544-4936-bc9d-308d1c2ceb4f_1032x484.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><blockquote><p>This isn&#8217;t the exact code in our application, because we&#8217;d have some streamlit-specific instructions in there to print some status messages while crawling.</p></blockquote><p>The next step is to crawl each of these results, skipping the ones that fail (because they are either not HTML content, or take too long to load, etc.). We use BeautifulSoup to parse the HTML and obtain a blob of continuous text extracted from every HTML node that has any text at all. This isn&#8217;t the prettiest or most robust way to parse an HTML file, especially if you want to show it to a human, but for our purposes, it works pretty well because the LLM will be able to sift through it and extract the relevant parts.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!WZpP!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b77e5d3-414b-4f1d-a522-4f3a96a72826_1032x717.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!WZpP!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b77e5d3-414b-4f1d-a522-4f3a96a72826_1032x717.png 424w, https://substackcdn.com/image/fetch/$s_!WZpP!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b77e5d3-414b-4f1d-a522-4f3a96a72826_1032x717.png 848w, https://substackcdn.com/image/fetch/$s_!WZpP!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b77e5d3-414b-4f1d-a522-4f3a96a72826_1032x717.png 1272w, https://substackcdn.com/image/fetch/$s_!WZpP!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b77e5d3-414b-4f1d-a522-4f3a96a72826_1032x717.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!WZpP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b77e5d3-414b-4f1d-a522-4f3a96a72826_1032x717.png" width="1032" height="717" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6b77e5d3-414b-4f1d-a522-4f3a96a72826_1032x717.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:717,&quot;width&quot;:1032,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:71959,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!WZpP!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b77e5d3-414b-4f1d-a522-4f3a96a72826_1032x717.png 424w, https://substackcdn.com/image/fetch/$s_!WZpP!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b77e5d3-414b-4f1d-a522-4f3a96a72826_1032x717.png 848w, https://substackcdn.com/image/fetch/$s_!WZpP!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b77e5d3-414b-4f1d-a522-4f3a96a72826_1032x717.png 1272w, https://substackcdn.com/image/fetch/$s_!WZpP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b77e5d3-414b-4f1d-a522-4f3a96a72826_1032x717.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Notice that, at the same time we&#8217;re parsing the HTML, we split the resulting text in chunks of, say, 256 words, and store each chunk separately. You can probably already guess why, right?</p><h2><strong>Finding the right context</strong></h2><p>The next problem we have to solve is giving the LLM a reasonably short fragment of the relevant web pages where the answer to the user query might be. Depending on how you configure this demo, the search step might have extracted hundreds of chunks with thousands of words in total, many of which might be irrelevant. For example, you may ask for a specific event date and get a whole Wikipedia page where that event is passingly mentioned in one of the paragraphs.</p><p>To solve this problem we will resort again to the most effective augmentation strategy, retrieval augmented generation, and our old friend the <code>VectorStore</code> class. We will index the extracted chunks on the fly and immediately extract the most relevant ones.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!xKVE!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21d256d6-d0df-4b9d-b270-d0d0db080558_1029x277.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!xKVE!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21d256d6-d0df-4b9d-b270-d0d0db080558_1029x277.png 424w, https://substackcdn.com/image/fetch/$s_!xKVE!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21d256d6-d0df-4b9d-b270-d0d0db080558_1029x277.png 848w, https://substackcdn.com/image/fetch/$s_!xKVE!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21d256d6-d0df-4b9d-b270-d0d0db080558_1029x277.png 1272w, https://substackcdn.com/image/fetch/$s_!xKVE!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21d256d6-d0df-4b9d-b270-d0d0db080558_1029x277.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!xKVE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21d256d6-d0df-4b9d-b270-d0d0db080558_1029x277.png" width="1029" height="277" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/21d256d6-d0df-4b9d-b270-d0d0db080558_1029x277.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:277,&quot;width&quot;:1029,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:32698,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!xKVE!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21d256d6-d0df-4b9d-b270-d0d0db080558_1029x277.png 424w, https://substackcdn.com/image/fetch/$s_!xKVE!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21d256d6-d0df-4b9d-b270-d0d0db080558_1029x277.png 848w, https://substackcdn.com/image/fetch/$s_!xKVE!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21d256d6-d0df-4b9d-b270-d0d0db080558_1029x277.png 1272w, https://substackcdn.com/image/fetch/$s_!xKVE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21d256d6-d0df-4b9d-b270-d0d0db080558_1029x277.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The result of this snippet is a <code>chunks</code> list containing a small number of relevant chunks, formatted as Python dictionaries with the following structure:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!haSg!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2866cada-8608-4c62-853f-fcc07b015cda_1025x214.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!haSg!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2866cada-8608-4c62-853f-fcc07b015cda_1025x214.png 424w, https://substackcdn.com/image/fetch/$s_!haSg!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2866cada-8608-4c62-853f-fcc07b015cda_1025x214.png 848w, https://substackcdn.com/image/fetch/$s_!haSg!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2866cada-8608-4c62-853f-fcc07b015cda_1025x214.png 1272w, https://substackcdn.com/image/fetch/$s_!haSg!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2866cada-8608-4c62-853f-fcc07b015cda_1025x214.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!haSg!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2866cada-8608-4c62-853f-fcc07b015cda_1025x214.png" width="1025" height="214" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2866cada-8608-4c62-853f-fcc07b015cda_1025x214.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:214,&quot;width&quot;:1025,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:18038,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!haSg!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2866cada-8608-4c62-853f-fcc07b015cda_1025x214.png 424w, https://substackcdn.com/image/fetch/$s_!haSg!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2866cada-8608-4c62-853f-fcc07b015cda_1025x214.png 848w, https://substackcdn.com/image/fetch/$s_!haSg!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2866cada-8608-4c62-853f-fcc07b015cda_1025x214.png 1272w, https://substackcdn.com/image/fetch/$s_!haSg!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2866cada-8608-4c62-853f-fcc07b015cda_1025x214.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>The <code>id</code> field, computed as an incremental index, will be useful later on for printing explicit references.</p><h2><strong>Building the final response</strong></h2><p>All that&#8217;s left is formatting a proper RAG-enabled prompt and injecting the relevant content extracted from the previous step. Here is the prompt we&#8217;re using:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!BVzp!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbb6c0385-4351-41d8-b67c-2073ddcb9211_1033x432.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!BVzp!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbb6c0385-4351-41d8-b67c-2073ddcb9211_1033x432.png 424w, https://substackcdn.com/image/fetch/$s_!BVzp!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbb6c0385-4351-41d8-b67c-2073ddcb9211_1033x432.png 848w, https://substackcdn.com/image/fetch/$s_!BVzp!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbb6c0385-4351-41d8-b67c-2073ddcb9211_1033x432.png 1272w, https://substackcdn.com/image/fetch/$s_!BVzp!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbb6c0385-4351-41d8-b67c-2073ddcb9211_1033x432.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!BVzp!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbb6c0385-4351-41d8-b67c-2073ddcb9211_1033x432.png" width="1033" height="432" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/bb6c0385-4351-41d8-b67c-2073ddcb9211_1033x432.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:432,&quot;width&quot;:1033,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:53614,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!BVzp!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbb6c0385-4351-41d8-b67c-2073ddcb9211_1033x432.png 424w, https://substackcdn.com/image/fetch/$s_!BVzp!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbb6c0385-4351-41d8-b67c-2073ddcb9211_1033x432.png 848w, https://substackcdn.com/image/fetch/$s_!BVzp!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbb6c0385-4351-41d8-b67c-2073ddcb9211_1033x432.png 1272w, https://substackcdn.com/image/fetch/$s_!BVzp!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbb6c0385-4351-41d8-b67c-2073ddcb9211_1033x432.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Notice how we explicitly instruct the model to produce square-bracketed references whenever possible. The <code>chunks</code> we inject in the context are pretty printed JSON objects from the previous section that contain a convenient <code>id</code> field.</p><p>And that&#8217;s it, all that remains is a few streamlit-specific bells and whistles here and there to get this demo up and running. For example, we add a few numeric inputs to let the user play around with the parameters of the search (how many queries to perform, how many chunks to extract, etc.)</p><p>And, if you check <a href="https://apiad.gumroad.com/l/chatbots/gttjcez">the full source code</a>, we also have a few instructions here and there to make this semi-fancy layout in two columns with a search bar at the top.</p><h2><strong>Conclusions</strong></h2><p>Retrieval augmented generation is an extremely versatile paradigm. In the previous article in this series, we saw the standard approach, using just vector-based retrieval from a static text source. In this chapter, we use a generic search engine as the data source, taking advantage of the massive complexities hidden behind what looks like a simple Google search. We&#8217;re leveraging years and years of innovation in collection, storage, and lightning-fast retrieval from billions of web pages. We&#8217;re standing on the shoulders of giants, quite literally.</p><p>You can adapt this demo to any search engine that provides a text search interface. A common use case is getting all your institutional knowledge in a self-hosted search-enabled database like ElasticSearch, and using it to build an in-house Q&amp;A bot.</p><p>By now, you should be starting to see a pattern in this integration of large language models with other traditional technologies. We will use the model to transform the user input into something structured to feed our underlying tool, compute some results, and then use the model again to produce a natural language response. This is why we talk about LLMs as a <em>natural language user interface</em>. It&#8217;s basically a wrapper for any traditional computational tool that adds a very convenient conversational input and output, but the tool still performs the basic computation.</p><p>This combination of LLMs with other tools helps bridge the gap between the incredible versatility of language models and the efficiency and robustness of more traditional tools, while minimizing (although not entirely eliminating) many of the inherent limitations of LLMs like hallucinations and biases.</p><p>In future articles, we will stretch this paradigm to its limits, making our LLM interact with all sorts of APIs and even produce its own executable code.</p>]]></content:encoded></item><item><title><![CDATA[How to Train your Chatbot - The PDF Bot]]></title><description><![CDATA[The second lesson in our journey to master LLMs.]]></description><link>https://blog.apiad.net/p/pdf-bot</link><guid isPermaLink="false">https://blog.apiad.net/p/pdf-bot</guid><dc:creator><![CDATA[Alejandro Piad Morffis]]></dc:creator><pubDate>Fri, 03 May 2024 18:58:10 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc2b621db-62a6-452b-b433-ccc0f5c4f0b3_1024x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote><p>This article is part of my upcoming ebook, <strong>How to Train your Chatbot</strong>, jam-packed with practical advice and working code examples to build all sorts of coll stuff using state-of-the-art LLMS.</p><p>You can get the latest draft, plus access to all future updates, and all the Python source code in the following link, with a very special offer:</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://apiad.gumroad.com/l/chatbots/gttjcez&quot;,&quot;text&quot;:&quot;How to Train your Chatbot - 50% off&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://apiad.gumroad.com/l/chatbots/gttjcez"><span>How to Train your Chatbot - 50% off</span></a></p></blockquote><h1>The PDF Bot</h1><p>In this article, we will build our first augmented chatbot. We will use the simplest form of augmentation, called RAG (retrieval augmented generation). In this case, we will inject extra information in the prompt that might be relevant to the user query. That information will be extracted from a user-provided PDF file. This will allow the chatbot to provide concrete answers to questions about the content of the PDF file, presumably questions that couldn&#8217;t be answered otherwise.</p><p>Since we will be building on top of the basic chatbot architecture presented in <a href="https://blog.apiad.net/p/chatgpt-clone">a previous article</a>, we won&#8217;t go into much detail regarding the conversation workflow, storing the history, maintaining a conversation context, etc. I&#8217;m assuming you already know how to do all of that, so we will focus on the new stuff. Likewise, instead of slowly building and refactoring functionalities, we&#8217;ll be more direct and go straight to the final implementation.</p><blockquote><p><strong>NOTE: </strong>This post may be cut in your email. I know, I know, what is this, the 80s!? <a href="https://blog.apiad.net/p/pdf-bot">Read it online here</a>.</p></blockquote><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://blog.apiad.net/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Mostly Harmless Ideas is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div><hr></div><h2>General workflow</h2><p>The workflow of this demo will be the following:</p><ol><li><p>The user uploads a PDF file.</p></li><li><p>The file gets indexed in a suitable format.</p></li><li><p>The user makes a query.</p></li><li><p>Given that query, an appropriate subset of the PDF document is extracted.</p></li><li><p>The model is given the context and the user query to respond.</p></li></ol><p>Here is a screenshot of the final result we want to achieve.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!2t0k!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b235911-182a-42e0-b06d-8658d42efa53_1893x802.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!2t0k!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b235911-182a-42e0-b06d-8658d42efa53_1893x802.png 424w, https://substackcdn.com/image/fetch/$s_!2t0k!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b235911-182a-42e0-b06d-8658d42efa53_1893x802.png 848w, https://substackcdn.com/image/fetch/$s_!2t0k!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b235911-182a-42e0-b06d-8658d42efa53_1893x802.png 1272w, https://substackcdn.com/image/fetch/$s_!2t0k!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b235911-182a-42e0-b06d-8658d42efa53_1893x802.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!2t0k!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b235911-182a-42e0-b06d-8658d42efa53_1893x802.png" width="1456" height="617" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4b235911-182a-42e0-b06d-8658d42efa53_1893x802.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:617,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:182924,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!2t0k!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b235911-182a-42e0-b06d-8658d42efa53_1893x802.png 424w, https://substackcdn.com/image/fetch/$s_!2t0k!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b235911-182a-42e0-b06d-8658d42efa53_1893x802.png 848w, https://substackcdn.com/image/fetch/$s_!2t0k!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b235911-182a-42e0-b06d-8658d42efa53_1893x802.png 1272w, https://substackcdn.com/image/fetch/$s_!2t0k!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b235911-182a-42e0-b06d-8658d42efa53_1893x802.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>For this workflow to work, we need to solve two basic problems:</p><ol><li><p>how to index the PDF document such that we can retrieve the relevant fragments for a given query, and</p></li><li><p>how to provide that context in an appropriate prompt for the model.</p></li></ol><p>First, let&#8217;s tackle those problems at a conceptual level, and then we&#8217;ll see how to code the whole thing.</p><h2>Indexing and retrieval</h2><p>There are many ways to store a large document for retrieval. One of the most common is called a <em>vector store</em> (or vector database). In this approach, we split the document into meaningful chunks of text (say, each paragraph) and compute an embedding for each chunk. Then, the user query is also embedded and compared with each of the chunks&#8217; vectors. The closest ones represent those fragments of text that are more closely related (semantically) with the query&#8211;if your embeddings are good, that is.</p><p>This is the simplest approach, and it works good enough for most applications. However, the embedding of the query might not be the most useful key to that index. This happens, for example, if the query is something very abstract like &#8220;What are the main topics in this document&#8221;. If the document happens to contain a chunk that mentions &#8220;the main topics&#8221;, that will work. But oftentimes, these types of general questions need further processing to summarize and abstract the content of the document.</p><p>Although we will not deal with these issues in this chapter, the general workflow we will develop here can be easily adapted to more complex scenarios. We&#8217;ll say more about these extensions at the end of the chapter.</p><h2>Giving context to the chatbot</h2><p>Once we have identified the, say, top 3 chunks of text that most likely contain relevant information to answer a user query, we need to give that context to the chatbot.</p><p>Enter prompt engineering.</p><p>So far, we have provided the chatbot with the exact user query, but there is nothing that forces us to do that. On the contrary, we can augment that query with any additional information we have that might enhance the conversation. The simplest way to do so is to construct a prompt that contains the user query and extra instructions or information.</p><p>Here is one example:</p><pre><code><code>The following is a relevant extract of a PDF document
from which I will ask you a question.

## Extract

... [chunks extracted from the vector store] ...

## Query

Given the previous extract, answer the following query:
... [user query] ...</code></code></pre><p>We then feed this message as the user prompt, interpolating the right context. From the user perspective, they only asked one simple question, but from the chatbot perspective, the question comes with a big chunk of text from where to extract the answer. That is the magic of prompt engineering.</p><h2>Implementing the vector store</h2><p>An efficient and scalable implementation of a vector store must be able to retrieve the closest <code>k</code> vectors to a given input as fast as possible. Professional implementations of this functionality require advanced data structures like kd-trees and many low-level tricks to scale to search in millions of vectors.</p><p>However, for illustrative purposes, we&#8217;ll build our own, extremely inefficient and vector store. This has the advantage you won&#8217;t need to install anything to try this code, and you can always later switch it for a production-ready solution.</p><p>Our vector store is a simple collection of paired texts and <code>numpy</code> vectors.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!2iov!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4289994e-db01-4288-9ffa-f101868865fc_1020x434.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!2iov!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4289994e-db01-4288-9ffa-f101868865fc_1020x434.png 424w, https://substackcdn.com/image/fetch/$s_!2iov!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4289994e-db01-4288-9ffa-f101868865fc_1020x434.png 848w, https://substackcdn.com/image/fetch/$s_!2iov!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4289994e-db01-4288-9ffa-f101868865fc_1020x434.png 1272w, https://substackcdn.com/image/fetch/$s_!2iov!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4289994e-db01-4288-9ffa-f101868865fc_1020x434.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!2iov!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4289994e-db01-4288-9ffa-f101868865fc_1020x434.png" width="1020" height="434" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4289994e-db01-4288-9ffa-f101868865fc_1020x434.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:434,&quot;width&quot;:1020,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:47244,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!2iov!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4289994e-db01-4288-9ffa-f101868865fc_1020x434.png 424w, https://substackcdn.com/image/fetch/$s_!2iov!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4289994e-db01-4288-9ffa-f101868865fc_1020x434.png 848w, https://substackcdn.com/image/fetch/$s_!2iov!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4289994e-db01-4288-9ffa-f101868865fc_1020x434.png 1272w, https://substackcdn.com/image/fetch/$s_!2iov!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4289994e-db01-4288-9ffa-f101868865fc_1020x434.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>To compute embeddings, we will resort to another method in the Mistral.ai API, but basically, any embedding model will do.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!KsTP!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12408f06-6974-4694-bd59-80a43c90a22e_1020x529.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!KsTP!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12408f06-6974-4694-bd59-80a43c90a22e_1020x529.png 424w, https://substackcdn.com/image/fetch/$s_!KsTP!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12408f06-6974-4694-bd59-80a43c90a22e_1020x529.png 848w, https://substackcdn.com/image/fetch/$s_!KsTP!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12408f06-6974-4694-bd59-80a43c90a22e_1020x529.png 1272w, https://substackcdn.com/image/fetch/$s_!KsTP!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12408f06-6974-4694-bd59-80a43c90a22e_1020x529.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!KsTP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12408f06-6974-4694-bd59-80a43c90a22e_1020x529.png" width="1020" height="529" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/12408f06-6974-4694-bd59-80a43c90a22e_1020x529.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:529,&quot;width&quot;:1020,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:80470,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!KsTP!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12408f06-6974-4694-bd59-80a43c90a22e_1020x529.png 424w, https://substackcdn.com/image/fetch/$s_!KsTP!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12408f06-6974-4694-bd59-80a43c90a22e_1020x529.png 848w, https://substackcdn.com/image/fetch/$s_!KsTP!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12408f06-6974-4694-bd59-80a43c90a22e_1020x529.png 1272w, https://substackcdn.com/image/fetch/$s_!KsTP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12408f06-6974-4694-bd59-80a43c90a22e_1020x529.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><blockquote><p><strong>Note: </strong>If you notice something weird in the <code>embed</code> method, is because we are being a bit lazy. The embedding endpoint has a maximum token lenght, and will return an error if we send too large a batch.</p><p>We want to send the largest batch possible to save time, so to avoid computing precise token counts in the client, we simply try with all the texts, and if that fails, we split them into two subsets and try again, until we find the sweet spot.</p><p>This has the obvious downside of being slower, but for illustrative purposes, it is good enough.</p></blockquote><p>For searching, we will simply compute the Euclidean distance between an input vector and the whole index.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Sg7V!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F50eaef49-2863-4899-89cb-cbcb2dafc20a_1020x487.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Sg7V!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F50eaef49-2863-4899-89cb-cbcb2dafc20a_1020x487.png 424w, https://substackcdn.com/image/fetch/$s_!Sg7V!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F50eaef49-2863-4899-89cb-cbcb2dafc20a_1020x487.png 848w, https://substackcdn.com/image/fetch/$s_!Sg7V!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F50eaef49-2863-4899-89cb-cbcb2dafc20a_1020x487.png 1272w, https://substackcdn.com/image/fetch/$s_!Sg7V!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F50eaef49-2863-4899-89cb-cbcb2dafc20a_1020x487.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Sg7V!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F50eaef49-2863-4899-89cb-cbcb2dafc20a_1020x487.png" width="1020" height="487" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/50eaef49-2863-4899-89cb-cbcb2dafc20a_1020x487.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:487,&quot;width&quot;:1020,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:69585,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Sg7V!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F50eaef49-2863-4899-89cb-cbcb2dafc20a_1020x487.png 424w, https://substackcdn.com/image/fetch/$s_!Sg7V!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F50eaef49-2863-4899-89cb-cbcb2dafc20a_1020x487.png 848w, https://substackcdn.com/image/fetch/$s_!Sg7V!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F50eaef49-2863-4899-89cb-cbcb2dafc20a_1020x487.png 1272w, https://substackcdn.com/image/fetch/$s_!Sg7V!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F50eaef49-2863-4899-89cb-cbcb2dafc20a_1020x487.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>And that&#8217;s it. In little more than 30 lines of code, we have a barebones vector store that we can use for small documents. Again, it doesn&#8217;t make sense to optimize this any further, as there are plenty of production-ready vector databases out there. Just google it.</p><h2>Managing the prompt</h2><p>The final piece of the puzzle is to build that augmented prompt which includes the relevant chunks for our indexed document. The actual prompt template I&#8217;m using in this demo is the following:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!clNI!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa98cdd09-bcb0-4e5d-9717-afb4bae61354_1020x429.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!clNI!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa98cdd09-bcb0-4e5d-9717-afb4bae61354_1020x429.png 424w, https://substackcdn.com/image/fetch/$s_!clNI!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa98cdd09-bcb0-4e5d-9717-afb4bae61354_1020x429.png 848w, https://substackcdn.com/image/fetch/$s_!clNI!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa98cdd09-bcb0-4e5d-9717-afb4bae61354_1020x429.png 1272w, https://substackcdn.com/image/fetch/$s_!clNI!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa98cdd09-bcb0-4e5d-9717-afb4bae61354_1020x429.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!clNI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa98cdd09-bcb0-4e5d-9717-afb4bae61354_1020x429.png" width="1020" height="429" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a98cdd09-bcb0-4e5d-9717-afb4bae61354_1020x429.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:429,&quot;width&quot;:1020,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:42266,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!clNI!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa98cdd09-bcb0-4e5d-9717-afb4bae61354_1020x429.png 424w, https://substackcdn.com/image/fetch/$s_!clNI!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa98cdd09-bcb0-4e5d-9717-afb4bae61354_1020x429.png 848w, https://substackcdn.com/image/fetch/$s_!clNI!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa98cdd09-bcb0-4e5d-9717-afb4bae61354_1020x429.png 1272w, https://substackcdn.com/image/fetch/$s_!clNI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa98cdd09-bcb0-4e5d-9717-afb4bae61354_1020x429.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The <code>extract</code> argument will interpolate the extracted chunks, and the <code>input</code> argument will contain the actual user query.</p><p>Now, the first idea that might come to your mind is simply submitting the prompt (with the interpolated content) to our chatbot. And while this works, it has an ugly downside.</p><p>You see, our <code>Chatbot</code> implementation (taken directly from <a href="http://localhost:7944/part3/chatbot.html">Chapter&nbsp;7</a>) does all the conversation management automatically. This means that whatever we give it as a user message, will come back to us in the conversation history. Thus, if we send the full augmented prompt as the user message, our chat history will become polluted with all those injected chunks of text. Ugly!</p><p>To solve this, we&#8217;ll make the <code>Chatbot</code> instance aware of our prompt engineering. We&#8217;ll supply a user template at initialization.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!0k58!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9ce459a-874c-4d59-af23-9a5d4260964e_1020x471.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!0k58!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9ce459a-874c-4d59-af23-9a5d4260964e_1020x471.png 424w, https://substackcdn.com/image/fetch/$s_!0k58!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9ce459a-874c-4d59-af23-9a5d4260964e_1020x471.png 848w, https://substackcdn.com/image/fetch/$s_!0k58!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9ce459a-874c-4d59-af23-9a5d4260964e_1020x471.png 1272w, https://substackcdn.com/image/fetch/$s_!0k58!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9ce459a-874c-4d59-af23-9a5d4260964e_1020x471.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!0k58!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9ce459a-874c-4d59-af23-9a5d4260964e_1020x471.png" width="1020" height="471" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c9ce459a-874c-4d59-af23-9a5d4260964e_1020x471.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:471,&quot;width&quot;:1020,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:58495,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!0k58!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9ce459a-874c-4d59-af23-9a5d4260964e_1020x471.png 424w, https://substackcdn.com/image/fetch/$s_!0k58!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9ce459a-874c-4d59-af23-9a5d4260964e_1020x471.png 848w, https://substackcdn.com/image/fetch/$s_!0k58!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9ce459a-874c-4d59-af23-9a5d4260964e_1020x471.png 1272w, https://substackcdn.com/image/fetch/$s_!0k58!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9ce459a-874c-4d59-af23-9a5d4260964e_1020x471.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>In the <code>submit</code> method, we&#8217;ll include any optional arguments, which presumably contain whatever content the template expects.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!4sEl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0af68295-9112-4730-a846-8e48f837c1d9_1020x608.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!4sEl!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0af68295-9112-4730-a846-8e48f837c1d9_1020x608.png 424w, https://substackcdn.com/image/fetch/$s_!4sEl!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0af68295-9112-4730-a846-8e48f837c1d9_1020x608.png 848w, https://substackcdn.com/image/fetch/$s_!4sEl!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0af68295-9112-4730-a846-8e48f837c1d9_1020x608.png 1272w, https://substackcdn.com/image/fetch/$s_!4sEl!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0af68295-9112-4730-a846-8e48f837c1d9_1020x608.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!4sEl!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0af68295-9112-4730-a846-8e48f837c1d9_1020x608.png" width="1020" height="608" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0af68295-9112-4730-a846-8e48f837c1d9_1020x608.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:608,&quot;width&quot;:1020,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:86630,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!4sEl!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0af68295-9112-4730-a846-8e48f837c1d9_1020x608.png 424w, https://substackcdn.com/image/fetch/$s_!4sEl!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0af68295-9112-4730-a846-8e48f837c1d9_1020x608.png 848w, https://substackcdn.com/image/fetch/$s_!4sEl!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0af68295-9112-4730-a846-8e48f837c1d9_1020x608.png 1272w, https://substackcdn.com/image/fetch/$s_!4sEl!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0af68295-9112-4730-a846-8e48f837c1d9_1020x608.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>This way, the <code>Chatbot</code> class itself will perform the interpolation, but store in the conversation history only the clean user input.</p><h2>The final application</h2><p>With all the pieces in place, it&#8217;s time to build the final puzzle. Since we have dutifully encapsulated all the important functionality, our application will be a very straightforward Streamlit app.</p><p>We begin, as usual, with the preamble.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!HZwr!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33cb9c4d-1add-48e4-a594-876f60d80241_1020x190.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!HZwr!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33cb9c4d-1add-48e4-a594-876f60d80241_1020x190.png 424w, https://substackcdn.com/image/fetch/$s_!HZwr!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33cb9c4d-1add-48e4-a594-876f60d80241_1020x190.png 848w, https://substackcdn.com/image/fetch/$s_!HZwr!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33cb9c4d-1add-48e4-a594-876f60d80241_1020x190.png 1272w, https://substackcdn.com/image/fetch/$s_!HZwr!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33cb9c4d-1add-48e4-a594-876f60d80241_1020x190.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!HZwr!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33cb9c4d-1add-48e4-a594-876f60d80241_1020x190.png" width="1020" height="190" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/33cb9c4d-1add-48e4-a594-876f60d80241_1020x190.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:190,&quot;width&quot;:1020,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:30645,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!HZwr!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33cb9c4d-1add-48e4-a594-876f60d80241_1020x190.png 424w, https://substackcdn.com/image/fetch/$s_!HZwr!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33cb9c4d-1add-48e4-a594-876f60d80241_1020x190.png 848w, https://substackcdn.com/image/fetch/$s_!HZwr!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33cb9c4d-1add-48e4-a594-876f60d80241_1020x190.png 1272w, https://substackcdn.com/image/fetch/$s_!HZwr!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F33cb9c4d-1add-48e4-a594-876f60d80241_1020x190.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>Next, we set up the file upload widget. The app will stop at this point until the user submits a file.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!DFvy!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0cfa33f9-38ed-49d1-b5bf-215096d19abc_1020x201.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!DFvy!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0cfa33f9-38ed-49d1-b5bf-215096d19abc_1020x201.png 424w, https://substackcdn.com/image/fetch/$s_!DFvy!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0cfa33f9-38ed-49d1-b5bf-215096d19abc_1020x201.png 848w, https://substackcdn.com/image/fetch/$s_!DFvy!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0cfa33f9-38ed-49d1-b5bf-215096d19abc_1020x201.png 1272w, https://substackcdn.com/image/fetch/$s_!DFvy!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0cfa33f9-38ed-49d1-b5bf-215096d19abc_1020x201.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!DFvy!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0cfa33f9-38ed-49d1-b5bf-215096d19abc_1020x201.png" width="1020" height="201" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0cfa33f9-38ed-49d1-b5bf-215096d19abc_1020x201.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:201,&quot;width&quot;:1020,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:25891,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!DFvy!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0cfa33f9-38ed-49d1-b5bf-215096d19abc_1020x201.png 424w, https://substackcdn.com/image/fetch/$s_!DFvy!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0cfa33f9-38ed-49d1-b5bf-215096d19abc_1020x201.png 848w, https://substackcdn.com/image/fetch/$s_!DFvy!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0cfa33f9-38ed-49d1-b5bf-215096d19abc_1020x201.png 1272w, https://substackcdn.com/image/fetch/$s_!DFvy!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0cfa33f9-38ed-49d1-b5bf-215096d19abc_1020x201.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>With a proper PDF file at hand, we can initialize our vector store. We are using <code>st.cache_data</code> to ensure that as long as the file doesn&#8217;t change, we won&#8217;t recompute embeddings every single time.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!NeP4!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b5efced-4886-45c6-9540-4d6dffbd6aa6_1020x339.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!NeP4!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b5efced-4886-45c6-9540-4d6dffbd6aa6_1020x339.png 424w, https://substackcdn.com/image/fetch/$s_!NeP4!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b5efced-4886-45c6-9540-4d6dffbd6aa6_1020x339.png 848w, https://substackcdn.com/image/fetch/$s_!NeP4!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b5efced-4886-45c6-9540-4d6dffbd6aa6_1020x339.png 1272w, https://substackcdn.com/image/fetch/$s_!NeP4!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b5efced-4886-45c6-9540-4d6dffbd6aa6_1020x339.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!NeP4!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b5efced-4886-45c6-9540-4d6dffbd6aa6_1020x339.png" width="1020" height="339" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5b5efced-4886-45c6-9540-4d6dffbd6aa6_1020x339.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:339,&quot;width&quot;:1020,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:55922,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!NeP4!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b5efced-4886-45c6-9540-4d6dffbd6aa6_1020x339.png 424w, https://substackcdn.com/image/fetch/$s_!NeP4!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b5efced-4886-45c6-9540-4d6dffbd6aa6_1020x339.png 848w, https://substackcdn.com/image/fetch/$s_!NeP4!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b5efced-4886-45c6-9540-4d6dffbd6aa6_1020x339.png 1272w, https://substackcdn.com/image/fetch/$s_!NeP4!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b5efced-4886-45c6-9540-4d6dffbd6aa6_1020x339.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Next, we initialize our bot and set up the restart conversation functionality, just like in the previous chapter. Notice that this is the moment we configure the user prompt template.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!mAnX!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa378cfb1-187d-4779-b571-6f22ef44a4be_1020x576.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!mAnX!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa378cfb1-187d-4779-b571-6f22ef44a4be_1020x576.png 424w, https://substackcdn.com/image/fetch/$s_!mAnX!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa378cfb1-187d-4779-b571-6f22ef44a4be_1020x576.png 848w, https://substackcdn.com/image/fetch/$s_!mAnX!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa378cfb1-187d-4779-b571-6f22ef44a4be_1020x576.png 1272w, https://substackcdn.com/image/fetch/$s_!mAnX!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa378cfb1-187d-4779-b571-6f22ef44a4be_1020x576.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!mAnX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa378cfb1-187d-4779-b571-6f22ef44a4be_1020x576.png" width="1020" height="576" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a378cfb1-187d-4779-b571-6f22ef44a4be_1020x576.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:576,&quot;width&quot;:1020,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:68826,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!mAnX!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa378cfb1-187d-4779-b571-6f22ef44a4be_1020x576.png 424w, https://substackcdn.com/image/fetch/$s_!mAnX!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa378cfb1-187d-4779-b571-6f22ef44a4be_1020x576.png 848w, https://substackcdn.com/image/fetch/$s_!mAnX!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa378cfb1-187d-4779-b571-6f22ef44a4be_1020x576.png 1272w, https://substackcdn.com/image/fetch/$s_!mAnX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa378cfb1-187d-4779-b571-6f22ef44a4be_1020x576.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The usual drill comes next: recreate the conversation history, and get the user input.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!MVzo!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F261faea5-008b-4574-a70d-b3c19557f8b8_1020x376.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!MVzo!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F261faea5-008b-4574-a70d-b3c19557f8b8_1020x376.png 424w, https://substackcdn.com/image/fetch/$s_!MVzo!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F261faea5-008b-4574-a70d-b3c19557f8b8_1020x376.png 848w, https://substackcdn.com/image/fetch/$s_!MVzo!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F261faea5-008b-4574-a70d-b3c19557f8b8_1020x376.png 1272w, https://substackcdn.com/image/fetch/$s_!MVzo!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F261faea5-008b-4574-a70d-b3c19557f8b8_1020x376.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!MVzo!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F261faea5-008b-4574-a70d-b3c19557f8b8_1020x376.png" width="1020" height="376" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/261faea5-008b-4574-a70d-b3c19557f8b8_1020x376.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:376,&quot;width&quot;:1020,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:42678,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!MVzo!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F261faea5-008b-4574-a70d-b3c19557f8b8_1020x376.png 424w, https://substackcdn.com/image/fetch/$s_!MVzo!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F261faea5-008b-4574-a70d-b3c19557f8b8_1020x376.png 848w, https://substackcdn.com/image/fetch/$s_!MVzo!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F261faea5-008b-4574-a70d-b3c19557f8b8_1020x376.png 1272w, https://substackcdn.com/image/fetch/$s_!MVzo!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F261faea5-008b-4574-a70d-b3c19557f8b8_1020x376.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>And finally, we extract the most relevant chunks and submit our augmented prompt.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!whLm!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd646547c-023b-470d-9937-729cb84be72a_1020x288.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!whLm!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd646547c-023b-470d-9937-729cb84be72a_1020x288.png 424w, https://substackcdn.com/image/fetch/$s_!whLm!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd646547c-023b-470d-9937-729cb84be72a_1020x288.png 848w, https://substackcdn.com/image/fetch/$s_!whLm!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd646547c-023b-470d-9937-729cb84be72a_1020x288.png 1272w, https://substackcdn.com/image/fetch/$s_!whLm!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd646547c-023b-470d-9937-729cb84be72a_1020x288.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!whLm!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd646547c-023b-470d-9937-729cb84be72a_1020x288.png" width="1020" height="288" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d646547c-023b-470d-9937-729cb84be72a_1020x288.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:288,&quot;width&quot;:1020,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:34116,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!whLm!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd646547c-023b-470d-9937-729cb84be72a_1020x288.png 424w, https://substackcdn.com/image/fetch/$s_!whLm!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd646547c-023b-470d-9937-729cb84be72a_1020x288.png 848w, https://substackcdn.com/image/fetch/$s_!whLm!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd646547c-023b-470d-9937-729cb84be72a_1020x288.png 1272w, https://substackcdn.com/image/fetch/$s_!whLm!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd646547c-023b-470d-9937-729cb84be72a_1020x288.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>And&#8230; we&#8217;re done! Our first retrieval-augmented application, in less than 100 lines of code total. Who said chatbots were hard?</p><h2>Conclusions</h2><p>In this post, we built our first non-vanilla chatbot application. It may seem like a small step for you, but it is a giant leap for&#8230; well, you get the point.</p><p>The crucial lesson in this chapter is how, with proper context, we can turn a standard chatbot into a powerful question-answering machine with deep expertise in any specific domain. You can use this formula to build smart assistants for your company or organization. You turn this into a research helper by feeding it papers or books (although we will build a proper research assistant). Or you can feed it with any book you&#8217;re currently reading and use it to extract insights and summaries.</p><p>One key limitation of this simplistic approach to RAG is that, since you&#8217;re embedding the user query to find relevant chunks, you might miss the answer to some question altogether if the relevant chunk doesn&#8217;t happen to have a similar enough embedding with the input. Many strategies can be used to mitigate this, and you&#8217;ll find a few suggestions summarized in the book.</p><p>The basic architecture we designed in this chapter will be the basis of much of what&#8217;s to come. The complexity will be in finding the right context and reformulating the user query in a convenient way.</p><p>If you have enjoyed this tutorial, please consider getting an early access copy of <strong>How to Train your Chatbot</strong>. It helps keep all this content free for everyone.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://apiad.gumroad.com/l/chatbots/gttjcez&quot;,&quot;text&quot;:&quot;How to Train your Chatbot - 50% off&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://apiad.gumroad.com/l/chatbots/gttjcez"><span>How to Train your Chatbot - 50% off</span></a></p><p></p>]]></content:encoded></item><item><title><![CDATA[Let's build our own ChatGPT]]></title><description><![CDATA[Build your own chat with an LLM application in just 56 lines of code and 15 minutes.]]></description><link>https://blog.apiad.net/p/chatgpt-clone</link><guid isPermaLink="false">https://blog.apiad.net/p/chatgpt-clone</guid><dc:creator><![CDATA[Alejandro Piad Morffis]]></dc:creator><pubDate>Sun, 21 Apr 2024 14:08:01 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!_vkz!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F04a6469d-4e29-475f-aa62-ae3be4c2a99c_1152x640.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote><p>This article is part of my upcoming book <strong>How to Train your Chatbot</strong>. You can get early access to the book and unlock all these perks:</p><ul><li><p>Lifetime access to all <strong>future updates</strong> of the book.</p></li><li><p>Full access to the <strong>source</strong> <strong>code</strong> with a commercially usable license.</p></li><li><p>Online <strong>demos</strong> of all applications (soon).</p></li><li><p>A <strong>private Discord space</strong> where you can ask anything (soon).</p></li></ul><p>The early access pass is 50% off for the rest of April.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://apiad.gumroad.com/l/chatbots/r8n3264&quot;,&quot;text&quot;:&quot;Get early access to the book&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://apiad.gumroad.com/l/chatbots/r8n3264"><span>Get early access to the book</span></a></p></blockquote><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!_vkz!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F04a6469d-4e29-475f-aa62-ae3be4c2a99c_1152x640.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!_vkz!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F04a6469d-4e29-475f-aa62-ae3be4c2a99c_1152x640.jpeg 424w, https://substackcdn.com/image/fetch/$s_!_vkz!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F04a6469d-4e29-475f-aa62-ae3be4c2a99c_1152x640.jpeg 848w, https://substackcdn.com/image/fetch/$s_!_vkz!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F04a6469d-4e29-475f-aa62-ae3be4c2a99c_1152x640.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!_vkz!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F04a6469d-4e29-475f-aa62-ae3be4c2a99c_1152x640.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!_vkz!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F04a6469d-4e29-475f-aa62-ae3be4c2a99c_1152x640.jpeg" width="1152" height="640" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/04a6469d-4e29-475f-aa62-ae3be4c2a99c_1152x640.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:&quot;normal&quot;,&quot;height&quot;:640,&quot;width&quot;:1152,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!_vkz!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F04a6469d-4e29-475f-aa62-ae3be4c2a99c_1152x640.jpeg 424w, https://substackcdn.com/image/fetch/$s_!_vkz!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F04a6469d-4e29-475f-aa62-ae3be4c2a99c_1152x640.jpeg 848w, https://substackcdn.com/image/fetch/$s_!_vkz!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F04a6469d-4e29-475f-aa62-ae3be4c2a99c_1152x640.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!_vkz!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F04a6469d-4e29-475f-aa62-ae3be4c2a99c_1152x640.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">A girl teaching a small robot how to read.</figcaption></figure></div><p>In this article, we will build a simple ChatGPT clone. We will emulate the basic chat workflow, maintaining the conversation context over several interactions. </p><p>There are a ton of ChatGPT clones out there, and ours won't be anything extraordinary, but it will lay the groundwork for future LLM applications we will build in this series.</p><p>In this article, you will learn:</p><ul><li><p>What is the difference between system, user, and assistant roles.</p></li><li><p>How to set up a basic conversation loop with an LLM model.</p></li><li><p>How to stream the chatbot response to simulate a typing animation.</p></li><li><p>How to simulate the bot memory by storing the conversation history.</p></li></ul><p>Furthermore, in this article, we will build critical functionalities that we'll use in future applications, such as conversation management with automatic history tracking, so we never need to write that functionality again.</p><blockquote><p><strong>NOTE: </strong>This post will be cut in your email browser. <a href="https://blog.apiad.net/p/chatgpt-clone">Read it online instead</a>.</p></blockquote><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://blog.apiad.net/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Mostly Harmless Ideas is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div><hr></div><h2>Initial setup</h2><p>We will use the Python library <code>streamlit</code> to build the front end of our application. Streamlit is a straightforward framework for creating data science apps focusing on productivity over fanciness. This means our artistic freedom is greatly restricted, and all our apps will look the same boring way, but we will need zero HTML or CSS, and development will be a breeze.</p><p>As for the LLM, I will use <a href="https://mistral.ai/">Mistral.ai</a> as the provider. They&#8217;re one of the mainstream LLM makers, just behind OpenAI, Google, and Meta in scale. They are very reliable and distribute several of their models as open source, so even though we&#8217;ll be using their cloud-hosted models in this article, you won&#8217;t be tied to their platform. You can take the same models and host it on your own infrastructure whenever you choose to.</p><p>The setup of this project will thus be simple. Our only major dependencies are <code>streamlit</code> and <code>mistralai</code>. We&#8217;ll start by creating a virtual environment and installing these dependencies.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!30DQ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F88971394-8103-4fd6-baee-ec7646d087b2_917x253.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!30DQ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F88971394-8103-4fd6-baee-ec7646d087b2_917x253.png 424w, https://substackcdn.com/image/fetch/$s_!30DQ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F88971394-8103-4fd6-baee-ec7646d087b2_917x253.png 848w, https://substackcdn.com/image/fetch/$s_!30DQ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F88971394-8103-4fd6-baee-ec7646d087b2_917x253.png 1272w, https://substackcdn.com/image/fetch/$s_!30DQ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F88971394-8103-4fd6-baee-ec7646d087b2_917x253.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!30DQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F88971394-8103-4fd6-baee-ec7646d087b2_917x253.png" width="917" height="253" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/88971394-8103-4fd6-baee-ec7646d087b2_917x253.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:253,&quot;width&quot;:917,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:30669,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!30DQ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F88971394-8103-4fd6-baee-ec7646d087b2_917x253.png 424w, https://substackcdn.com/image/fetch/$s_!30DQ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F88971394-8103-4fd6-baee-ec7646d087b2_917x253.png 848w, https://substackcdn.com/image/fetch/$s_!30DQ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F88971394-8103-4fd6-baee-ec7646d087b2_917x253.png 1272w, https://substackcdn.com/image/fetch/$s_!30DQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F88971394-8103-4fd6-baee-ec7646d087b2_917x253.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>After the initial creation, it will be convenient to dump our environment setup to reconstruct the same environment later.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!rkPp!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37a9e7eb-8181-4552-a098-67f3609f504b_917x67.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!rkPp!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37a9e7eb-8181-4552-a098-67f3609f504b_917x67.png 424w, https://substackcdn.com/image/fetch/$s_!rkPp!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37a9e7eb-8181-4552-a098-67f3609f504b_917x67.png 848w, https://substackcdn.com/image/fetch/$s_!rkPp!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37a9e7eb-8181-4552-a098-67f3609f504b_917x67.png 1272w, https://substackcdn.com/image/fetch/$s_!rkPp!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37a9e7eb-8181-4552-a098-67f3609f504b_917x67.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!rkPp!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37a9e7eb-8181-4552-a098-67f3609f504b_917x67.png" width="917" height="67" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/37a9e7eb-8181-4552-a098-67f3609f504b_917x67.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:67,&quot;width&quot;:917,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:6556,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!rkPp!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37a9e7eb-8181-4552-a098-67f3609f504b_917x67.png 424w, https://substackcdn.com/image/fetch/$s_!rkPp!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37a9e7eb-8181-4552-a098-67f3609f504b_917x67.png 848w, https://substackcdn.com/image/fetch/$s_!rkPp!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37a9e7eb-8181-4552-a098-67f3609f504b_917x67.png 1272w, https://substackcdn.com/image/fetch/$s_!rkPp!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F37a9e7eb-8181-4552-a098-67f3609f504b_917x67.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><h2>Basic chat app</h2><p>A streamlit application can be as simple as a Python file. Here&#8217;s the basic layout of a chat app that just echoes what you type.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!-FsR!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba8e9b14-b8c1-4703-b33d-9c442083f2d3_917x446.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!-FsR!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba8e9b14-b8c1-4703-b33d-9c442083f2d3_917x446.png 424w, https://substackcdn.com/image/fetch/$s_!-FsR!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba8e9b14-b8c1-4703-b33d-9c442083f2d3_917x446.png 848w, https://substackcdn.com/image/fetch/$s_!-FsR!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba8e9b14-b8c1-4703-b33d-9c442083f2d3_917x446.png 1272w, https://substackcdn.com/image/fetch/$s_!-FsR!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba8e9b14-b8c1-4703-b33d-9c442083f2d3_917x446.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!-FsR!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba8e9b14-b8c1-4703-b33d-9c442083f2d3_917x446.png" width="917" height="446" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ba8e9b14-b8c1-4703-b33d-9c442083f2d3_917x446.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:446,&quot;width&quot;:917,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:60348,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!-FsR!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba8e9b14-b8c1-4703-b33d-9c442083f2d3_917x446.png 424w, https://substackcdn.com/image/fetch/$s_!-FsR!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba8e9b14-b8c1-4703-b33d-9c442083f2d3_917x446.png 848w, https://substackcdn.com/image/fetch/$s_!-FsR!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba8e9b14-b8c1-4703-b33d-9c442083f2d3_917x446.png 1272w, https://substackcdn.com/image/fetch/$s_!-FsR!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba8e9b14-b8c1-4703-b33d-9c442083f2d3_917x446.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Assuming this code is located at <code>code/chatbot/app.py</code>, we can run it with:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!vrV2!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F146e1775-669a-482e-842e-4bba37fd8fce_917x66.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!vrV2!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F146e1775-669a-482e-842e-4bba37fd8fce_917x66.png 424w, https://substackcdn.com/image/fetch/$s_!vrV2!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F146e1775-669a-482e-842e-4bba37fd8fce_917x66.png 848w, https://substackcdn.com/image/fetch/$s_!vrV2!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F146e1775-669a-482e-842e-4bba37fd8fce_917x66.png 1272w, https://substackcdn.com/image/fetch/$s_!vrV2!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F146e1775-669a-482e-842e-4bba37fd8fce_917x66.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!vrV2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F146e1775-669a-482e-842e-4bba37fd8fce_917x66.png" width="917" height="66" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/146e1775-669a-482e-842e-4bba37fd8fce_917x66.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:66,&quot;width&quot;:917,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:7626,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!vrV2!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F146e1775-669a-482e-842e-4bba37fd8fce_917x66.png 424w, https://substackcdn.com/image/fetch/$s_!vrV2!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F146e1775-669a-482e-842e-4bba37fd8fce_917x66.png 848w, https://substackcdn.com/image/fetch/$s_!vrV2!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F146e1775-669a-482e-842e-4bba37fd8fce_917x66.png 1272w, https://substackcdn.com/image/fetch/$s_!vrV2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F146e1775-669a-482e-842e-4bba37fd8fce_917x66.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>A browser will automatically open and show something like the following:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!0UXv!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F933e2ce2-ff72-48eb-a9b7-27c3972f42e7_917x436.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!0UXv!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F933e2ce2-ff72-48eb-a9b7-27c3972f42e7_917x436.png 424w, https://substackcdn.com/image/fetch/$s_!0UXv!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F933e2ce2-ff72-48eb-a9b7-27c3972f42e7_917x436.png 848w, https://substackcdn.com/image/fetch/$s_!0UXv!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F933e2ce2-ff72-48eb-a9b7-27c3972f42e7_917x436.png 1272w, https://substackcdn.com/image/fetch/$s_!0UXv!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F933e2ce2-ff72-48eb-a9b7-27c3972f42e7_917x436.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!0UXv!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F933e2ce2-ff72-48eb-a9b7-27c3972f42e7_917x436.png" width="917" height="436" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/933e2ce2-ff72-48eb-a9b7-27c3972f42e7_917x436.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:436,&quot;width&quot;:917,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:17159,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!0UXv!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F933e2ce2-ff72-48eb-a9b7-27c3972f42e7_917x436.png 424w, https://substackcdn.com/image/fetch/$s_!0UXv!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F933e2ce2-ff72-48eb-a9b7-27c3972f42e7_917x436.png 848w, https://substackcdn.com/image/fetch/$s_!0UXv!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F933e2ce2-ff72-48eb-a9b7-27c3972f42e7_917x436.png 1272w, https://substackcdn.com/image/fetch/$s_!0UXv!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F933e2ce2-ff72-48eb-a9b7-27c3972f42e7_917x436.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>This is just a barebones app with no chat functionality at all. It will simply write anything you type in the input box back into the chat screen. But it will help us understand the basic application lifecycle in Streamlit.</p><p>The code above is the blueprint for a typical Streamlit app. You start by importing <code>streamlit</code> and setting up the page title and icon and then proceed to include any number of Streamlit statements. The code is executed top-to-bottom synchronously by the Streamlit server, and all commands are sent via WebSockets to a web app. There are no callbacks or hidden states.</p><p>If this sounds alien, don&#8217;t worry. It just means a Streamlit app works like a regular Python script: you can have global variables, methods, and classes and use them as you would in a terminal script, and it will (mostly) magically work.</p><h2>The first interaction with an LLM</h2><p>Ok, it&#8217;s time for the real thing. Let&#8217;s send our first message to a language model! This is easy using the <code>mistralai</code> package. We just need to instantiate a <code>MistralClient</code> and call its <code>chat</code> method.</p><p>First, we will need an API key from <a href="https://mistral.ai/">Mistral.ai</a>. This token is a password that grants your code access to the model. Beware not to share it with anyone, or they can use the API on your behalf (and you&#8217;ll pay for it). Paste the API token into <code>.streamlit/secrets.toml</code> like this:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!j8C1!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad8ba825-4cf2-4207-97a2-02f6c0189037_917x94.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!j8C1!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad8ba825-4cf2-4207-97a2-02f6c0189037_917x94.png 424w, https://substackcdn.com/image/fetch/$s_!j8C1!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad8ba825-4cf2-4207-97a2-02f6c0189037_917x94.png 848w, https://substackcdn.com/image/fetch/$s_!j8C1!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad8ba825-4cf2-4207-97a2-02f6c0189037_917x94.png 1272w, https://substackcdn.com/image/fetch/$s_!j8C1!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad8ba825-4cf2-4207-97a2-02f6c0189037_917x94.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!j8C1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad8ba825-4cf2-4207-97a2-02f6c0189037_917x94.png" width="917" height="94" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ad8ba825-4cf2-4207-97a2-02f6c0189037_917x94.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:94,&quot;width&quot;:917,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:13664,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!j8C1!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad8ba825-4cf2-4207-97a2-02f6c0189037_917x94.png 424w, https://substackcdn.com/image/fetch/$s_!j8C1!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad8ba825-4cf2-4207-97a2-02f6c0189037_917x94.png 848w, https://substackcdn.com/image/fetch/$s_!j8C1!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad8ba825-4cf2-4207-97a2-02f6c0189037_917x94.png 1272w, https://substackcdn.com/image/fetch/$s_!j8C1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad8ba825-4cf2-4207-97a2-02f6c0189037_917x94.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>Now, we can use Streamlit&#8217;s native secret management to inject this token into our application without having to copy/paste it every single time.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!MxUU!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9a6337f-5d80-4ca1-bffd-b3ae68561444_917x283.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!MxUU!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9a6337f-5d80-4ca1-bffd-b3ae68561444_917x283.png 424w, https://substackcdn.com/image/fetch/$s_!MxUU!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9a6337f-5d80-4ca1-bffd-b3ae68561444_917x283.png 848w, https://substackcdn.com/image/fetch/$s_!MxUU!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9a6337f-5d80-4ca1-bffd-b3ae68561444_917x283.png 1272w, https://substackcdn.com/image/fetch/$s_!MxUU!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9a6337f-5d80-4ca1-bffd-b3ae68561444_917x283.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!MxUU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9a6337f-5d80-4ca1-bffd-b3ae68561444_917x283.png" width="917" height="283" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e9a6337f-5d80-4ca1-bffd-b3ae68561444_917x283.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:283,&quot;width&quot;:917,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:51755,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!MxUU!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9a6337f-5d80-4ca1-bffd-b3ae68561444_917x283.png 424w, https://substackcdn.com/image/fetch/$s_!MxUU!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9a6337f-5d80-4ca1-bffd-b3ae68561444_917x283.png 848w, https://substackcdn.com/image/fetch/$s_!MxUU!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9a6337f-5d80-4ca1-bffd-b3ae68561444_917x283.png 1272w, https://substackcdn.com/image/fetch/$s_!MxUU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe9a6337f-5d80-4ca1-bffd-b3ae68561444_917x283.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Once our client is set up, we must change our chat workflow to call the LLM API and output that response.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!IJVK!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9a11bacc-adad-411f-89ce-502e7b765cb4_917x769.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!IJVK!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9a11bacc-adad-411f-89ce-502e7b765cb4_917x769.png 424w, https://substackcdn.com/image/fetch/$s_!IJVK!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9a11bacc-adad-411f-89ce-502e7b765cb4_917x769.png 848w, https://substackcdn.com/image/fetch/$s_!IJVK!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9a11bacc-adad-411f-89ce-502e7b765cb4_917x769.png 1272w, https://substackcdn.com/image/fetch/$s_!IJVK!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9a11bacc-adad-411f-89ce-502e7b765cb4_917x769.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!IJVK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9a11bacc-adad-411f-89ce-502e7b765cb4_917x769.png" width="917" height="769" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9a11bacc-adad-411f-89ce-502e7b765cb4_917x769.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:769,&quot;width&quot;:917,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:106126,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!IJVK!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9a11bacc-adad-411f-89ce-502e7b765cb4_917x769.png 424w, https://substackcdn.com/image/fetch/$s_!IJVK!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9a11bacc-adad-411f-89ce-502e7b765cb4_917x769.png 848w, https://substackcdn.com/image/fetch/$s_!IJVK!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9a11bacc-adad-411f-89ce-502e7b765cb4_917x769.png 1272w, https://substackcdn.com/image/fetch/$s_!IJVK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9a11bacc-adad-411f-89ce-502e7b765cb4_917x769.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Hit <code>R</code> on Streamlit to hot-reload the code and type something fancy in the input box. Maybe something like &#8220;What is the meaning of life?&#8221;. In the blink of an eye (or maybe a couple of lazy eyes), your message will be sent to the LLM API, and the response will be streamed back to your application. Voil&#225;, we have a ChatGPT clone!</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!DuOx!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7bd607ea-0383-495b-827c-dfb85574f02a_917x619.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!DuOx!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7bd607ea-0383-495b-827c-dfb85574f02a_917x619.png 424w, https://substackcdn.com/image/fetch/$s_!DuOx!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7bd607ea-0383-495b-827c-dfb85574f02a_917x619.png 848w, https://substackcdn.com/image/fetch/$s_!DuOx!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7bd607ea-0383-495b-827c-dfb85574f02a_917x619.png 1272w, https://substackcdn.com/image/fetch/$s_!DuOx!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7bd607ea-0383-495b-827c-dfb85574f02a_917x619.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!DuOx!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7bd607ea-0383-495b-827c-dfb85574f02a_917x619.png" width="917" height="619" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7bd607ea-0383-495b-827c-dfb85574f02a_917x619.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:619,&quot;width&quot;:917,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:236825,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!DuOx!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7bd607ea-0383-495b-827c-dfb85574f02a_917x619.png 424w, https://substackcdn.com/image/fetch/$s_!DuOx!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7bd607ea-0383-495b-827c-dfb85574f02a_917x619.png 848w, https://substackcdn.com/image/fetch/$s_!DuOx!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7bd607ea-0383-495b-827c-dfb85574f02a_917x619.png 1272w, https://substackcdn.com/image/fetch/$s_!DuOx!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7bd607ea-0383-495b-827c-dfb85574f02a_917x619.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>Persisting the chat state</h2><p>Not so fast! You will quickly notice there is something really unsatisfying with our current implementation. Every time you send a new message, the whole chat history is cleared!</p><p>This is due to Streamlit&#8217;s simple lifecycle. Since the script is executed top-to-bottom for every interaction, every button or key pressed essentially works as if the app was just started. There is no magic here; we haven&#8217;t done anything to store the conversation, so there is no place Streamlit could get it from. Let&#8217;s fix that first.</p><p>Streamlit provides a built-in way to deal with the state that persists across executions in the object <code>st.session_state</code>. This is a dict-like structure that can hold whatever we want and survives re-runs (but does not refresh the page). Technically, it&#8217;s session-associated data, meaning every different user of our web application gets their own private storage. But enough technobabble, let&#8217;s see some code; it&#8217;s easier to just show how it works.</p><p>First, we initialize the <code>history</code> key with an empty list, the first time the app is loaded. This goes right after the client initialization.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Bh3b!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad15b7a9-968a-4d2f-bc4d-c2d232e4c5c4_917x95.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Bh3b!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad15b7a9-968a-4d2f-bc4d-c2d232e4c5c4_917x95.png 424w, https://substackcdn.com/image/fetch/$s_!Bh3b!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad15b7a9-968a-4d2f-bc4d-c2d232e4c5c4_917x95.png 848w, https://substackcdn.com/image/fetch/$s_!Bh3b!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad15b7a9-968a-4d2f-bc4d-c2d232e4c5c4_917x95.png 1272w, https://substackcdn.com/image/fetch/$s_!Bh3b!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad15b7a9-968a-4d2f-bc4d-c2d232e4c5c4_917x95.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Bh3b!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad15b7a9-968a-4d2f-bc4d-c2d232e4c5c4_917x95.png" width="917" height="95" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ad15b7a9-968a-4d2f-bc4d-c2d232e4c5c4_917x95.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:95,&quot;width&quot;:917,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:11516,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Bh3b!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad15b7a9-968a-4d2f-bc4d-c2d232e4c5c4_917x95.png 424w, https://substackcdn.com/image/fetch/$s_!Bh3b!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad15b7a9-968a-4d2f-bc4d-c2d232e4c5c4_917x95.png 848w, https://substackcdn.com/image/fetch/$s_!Bh3b!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad15b7a9-968a-4d2f-bc4d-c2d232e4c5c4_917x95.png 1272w, https://substackcdn.com/image/fetch/$s_!Bh3b!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fad15b7a9-968a-4d2f-bc4d-c2d232e4c5c4_917x95.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>Next, before getting into the user/assistant chat workflow, we must reconstruct the previous chat history and render all the messages.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!3s52!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffca59256-dd48-47e9-8649-b3468d357005_917x115.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!3s52!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffca59256-dd48-47e9-8649-b3468d357005_917x115.png 424w, https://substackcdn.com/image/fetch/$s_!3s52!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffca59256-dd48-47e9-8649-b3468d357005_917x115.png 848w, https://substackcdn.com/image/fetch/$s_!3s52!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffca59256-dd48-47e9-8649-b3468d357005_917x115.png 1272w, https://substackcdn.com/image/fetch/$s_!3s52!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffca59256-dd48-47e9-8649-b3468d357005_917x115.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!3s52!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffca59256-dd48-47e9-8649-b3468d357005_917x115.png" width="917" height="115" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/fca59256-dd48-47e9-8649-b3468d357005_917x115.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:115,&quot;width&quot;:917,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:20855,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!3s52!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffca59256-dd48-47e9-8649-b3468d357005_917x115.png 424w, https://substackcdn.com/image/fetch/$s_!3s52!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffca59256-dd48-47e9-8649-b3468d357005_917x115.png 848w, https://substackcdn.com/image/fetch/$s_!3s52!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffca59256-dd48-47e9-8649-b3468d357005_917x115.png 1272w, https://substackcdn.com/image/fetch/$s_!3s52!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffca59256-dd48-47e9-8649-b3468d357005_917x115.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>Then, when we retrieve the new user input, we have to remember to store it in the history.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!QcbH!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8de27087-c3b9-4b30-9b14-0622df8d9170_917x334.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!QcbH!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8de27087-c3b9-4b30-9b14-0622df8d9170_917x334.png 424w, https://substackcdn.com/image/fetch/$s_!QcbH!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8de27087-c3b9-4b30-9b14-0622df8d9170_917x334.png 848w, https://substackcdn.com/image/fetch/$s_!QcbH!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8de27087-c3b9-4b30-9b14-0622df8d9170_917x334.png 1272w, https://substackcdn.com/image/fetch/$s_!QcbH!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8de27087-c3b9-4b30-9b14-0622df8d9170_917x334.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!QcbH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8de27087-c3b9-4b30-9b14-0622df8d9170_917x334.png" width="917" height="334" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8de27087-c3b9-4b30-9b14-0622df8d9170_917x334.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:334,&quot;width&quot;:917,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:32645,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!QcbH!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8de27087-c3b9-4b30-9b14-0622df8d9170_917x334.png 424w, https://substackcdn.com/image/fetch/$s_!QcbH!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8de27087-c3b9-4b30-9b14-0622df8d9170_917x334.png 848w, https://substackcdn.com/image/fetch/$s_!QcbH!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8de27087-c3b9-4b30-9b14-0622df8d9170_917x334.png 1272w, https://substackcdn.com/image/fetch/$s_!QcbH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8de27087-c3b9-4b30-9b14-0622df8d9170_917x334.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>And do the same with the LLM response.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!1_Ii!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9ec33d5-34b9-4f03-a279-3aeb061d5a90_917x336.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!1_Ii!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9ec33d5-34b9-4f03-a279-3aeb061d5a90_917x336.png 424w, https://substackcdn.com/image/fetch/$s_!1_Ii!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9ec33d5-34b9-4f03-a279-3aeb061d5a90_917x336.png 848w, https://substackcdn.com/image/fetch/$s_!1_Ii!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9ec33d5-34b9-4f03-a279-3aeb061d5a90_917x336.png 1272w, https://substackcdn.com/image/fetch/$s_!1_Ii!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9ec33d5-34b9-4f03-a279-3aeb061d5a90_917x336.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!1_Ii!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9ec33d5-34b9-4f03-a279-3aeb061d5a90_917x336.png" width="917" height="336" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a9ec33d5-34b9-4f03-a279-3aeb061d5a90_917x336.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:336,&quot;width&quot;:917,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:51018,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!1_Ii!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9ec33d5-34b9-4f03-a279-3aeb061d5a90_917x336.png 424w, https://substackcdn.com/image/fetch/$s_!1_Ii!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9ec33d5-34b9-4f03-a279-3aeb061d5a90_917x336.png 848w, https://substackcdn.com/image/fetch/$s_!1_Ii!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9ec33d5-34b9-4f03-a279-3aeb061d5a90_917x336.png 1272w, https://substackcdn.com/image/fetch/$s_!1_Ii!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa9ec33d5-34b9-4f03-a279-3aeb061d5a90_917x336.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>If you run this update, you&#8217;ll notice the chat history persists throughout the conversation. However, one thing is still missing. Even though the chat interface shows all the previous messages, the LLM only receives the last message. This is evident in the next screenshot, where the chatbot fails to remember information given just before.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!SkLa!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa638325c-c5f2-43dc-9a22-33d32b3fe60f_917x808.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!SkLa!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa638325c-c5f2-43dc-9a22-33d32b3fe60f_917x808.png 424w, https://substackcdn.com/image/fetch/$s_!SkLa!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa638325c-c5f2-43dc-9a22-33d32b3fe60f_917x808.png 848w, https://substackcdn.com/image/fetch/$s_!SkLa!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa638325c-c5f2-43dc-9a22-33d32b3fe60f_917x808.png 1272w, https://substackcdn.com/image/fetch/$s_!SkLa!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa638325c-c5f2-43dc-9a22-33d32b3fe60f_917x808.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!SkLa!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa638325c-c5f2-43dc-9a22-33d32b3fe60f_917x808.png" width="917" height="808" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a638325c-c5f2-43dc-9a22-33d32b3fe60f_917x808.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:808,&quot;width&quot;:917,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:212288,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!SkLa!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa638325c-c5f2-43dc-9a22-33d32b3fe60f_917x808.png 424w, https://substackcdn.com/image/fetch/$s_!SkLa!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa638325c-c5f2-43dc-9a22-33d32b3fe60f_917x808.png 848w, https://substackcdn.com/image/fetch/$s_!SkLa!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa638325c-c5f2-43dc-9a22-33d32b3fe60f_917x808.png 1272w, https://substackcdn.com/image/fetch/$s_!SkLa!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa638325c-c5f2-43dc-9a22-33d32b3fe60f_917x808.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>Remembering the context</h2><p>Most LLM APIs have a straightforward way to submit a full conversation history. In the case of <code>mistralai</code>, the <code>messages</code> parameter in the <code>client.chat</code> method expects a list of <code>ChatMessage</code> instances. Each message specifies the content and a role, which can be:</p><ul><li><p><code>system</code>: Used to send behavior instructions to the LLM. We will see an example right away.</p></li><li><p><code>user</code>: Used to specify the user input.</p></li><li><p><code>assistant</code>: Used to specify the previous assistant input.</p></li></ul><p>When you send a request to the LLM API, you can submit the previous messages (both the user&#8217;s and the LLM&#8217;s), and simulate as if the chatbot remembered the conversation. The LLM service has no actual memory, as it&#8217;s a stateless API. So, it&#8217;s your responsibility to reconstruct the conversation history in a meaningful way.</p><p>In our app, since we are already storing the conversation history in the session storage, we just need to construct the proper <code>ChatMessage</code> instances:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!8btt!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F056b063b-08e4-4d31-a721-0c9ff0d278e6_915x415.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!8btt!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F056b063b-08e4-4d31-a721-0c9ff0d278e6_915x415.png 424w, https://substackcdn.com/image/fetch/$s_!8btt!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F056b063b-08e4-4d31-a721-0c9ff0d278e6_915x415.png 848w, https://substackcdn.com/image/fetch/$s_!8btt!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F056b063b-08e4-4d31-a721-0c9ff0d278e6_915x415.png 1272w, https://substackcdn.com/image/fetch/$s_!8btt!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F056b063b-08e4-4d31-a721-0c9ff0d278e6_915x415.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!8btt!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F056b063b-08e4-4d31-a721-0c9ff0d278e6_915x415.png" width="915" height="415" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/056b063b-08e4-4d31-a721-0c9ff0d278e6_915x415.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:415,&quot;width&quot;:915,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:44320,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!8btt!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F056b063b-08e4-4d31-a721-0c9ff0d278e6_915x415.png 424w, https://substackcdn.com/image/fetch/$s_!8btt!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F056b063b-08e4-4d31-a721-0c9ff0d278e6_915x415.png 848w, https://substackcdn.com/image/fetch/$s_!8btt!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F056b063b-08e4-4d31-a721-0c9ff0d278e6_915x415.png 1272w, https://substackcdn.com/image/fetch/$s_!8btt!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F056b063b-08e4-4d31-a721-0c9ff0d278e6_915x415.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>This update seems to magically add memory to our chatbot, although you need that, under the hood, there is no magic. It&#8217;s you who&#8217;s keeping track of the conversation.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!YURq!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6998b02a-bbbe-45ef-8e42-b7494f10e6c1_915x736.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!YURq!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6998b02a-bbbe-45ef-8e42-b7494f10e6c1_915x736.png 424w, https://substackcdn.com/image/fetch/$s_!YURq!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6998b02a-bbbe-45ef-8e42-b7494f10e6c1_915x736.png 848w, https://substackcdn.com/image/fetch/$s_!YURq!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6998b02a-bbbe-45ef-8e42-b7494f10e6c1_915x736.png 1272w, https://substackcdn.com/image/fetch/$s_!YURq!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6998b02a-bbbe-45ef-8e42-b7494f10e6c1_915x736.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!YURq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6998b02a-bbbe-45ef-8e42-b7494f10e6c1_915x736.png" width="915" height="736" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6998b02a-bbbe-45ef-8e42-b7494f10e6c1_915x736.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:736,&quot;width&quot;:915,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:300951,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!YURq!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6998b02a-bbbe-45ef-8e42-b7494f10e6c1_915x736.png 424w, https://substackcdn.com/image/fetch/$s_!YURq!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6998b02a-bbbe-45ef-8e42-b7494f10e6c1_915x736.png 848w, https://substackcdn.com/image/fetch/$s_!YURq!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6998b02a-bbbe-45ef-8e42-b7494f10e6c1_915x736.png 1272w, https://substackcdn.com/image/fetch/$s_!YURq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6998b02a-bbbe-45ef-8e42-b7494f10e6c1_915x736.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Before closing, let&#8217;s take a look at the <code>system</code> prompt. By incorporating messages with <code>role="system"</code> you can guide the chatbot's behavior. In principle, this is no different from what you can do with a well-crafted user input, but some models are trained to pay special attention to system messages and prioritize them over user messages.</p><p>This means that, for example, you can override a previous instruction you gave as a user, but it will be much harder if the instruction was given as a system message. Thus, these messages often instruct the LLM to behave politely, avoid answering biased questions, etc. You can also use it to set the tone and style of the conversation.</p><p>To try this, let&#8217;s add an input box for the system messages to experiment with different prompts. By default, we&#8217;ll instruct the LLM to be nice.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!8O2n!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6163e604-a59f-46ba-be49-599d038ee923_915x584.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!8O2n!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6163e604-a59f-46ba-be49-599d038ee923_915x584.png 424w, https://substackcdn.com/image/fetch/$s_!8O2n!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6163e604-a59f-46ba-be49-599d038ee923_915x584.png 848w, https://substackcdn.com/image/fetch/$s_!8O2n!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6163e604-a59f-46ba-be49-599d038ee923_915x584.png 1272w, https://substackcdn.com/image/fetch/$s_!8O2n!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6163e604-a59f-46ba-be49-599d038ee923_915x584.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!8O2n!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6163e604-a59f-46ba-be49-599d038ee923_915x584.png" width="915" height="584" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6163e604-a59f-46ba-be49-599d038ee923_915x584.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:584,&quot;width&quot;:915,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:101620,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!8O2n!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6163e604-a59f-46ba-be49-599d038ee923_915x584.png 424w, https://substackcdn.com/image/fetch/$s_!8O2n!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6163e604-a59f-46ba-be49-599d038ee923_915x584.png 848w, https://substackcdn.com/image/fetch/$s_!8O2n!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6163e604-a59f-46ba-be49-599d038ee923_915x584.png 1272w, https://substackcdn.com/image/fetch/$s_!8O2n!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6163e604-a59f-46ba-be49-599d038ee923_915x584.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>And we can try it out with not-so-nice instructions. Delightful.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!uC2b!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5d62c13f-de6d-4089-8dfd-1b29d097e9fa_915x436.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!uC2b!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5d62c13f-de6d-4089-8dfd-1b29d097e9fa_915x436.png 424w, https://substackcdn.com/image/fetch/$s_!uC2b!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5d62c13f-de6d-4089-8dfd-1b29d097e9fa_915x436.png 848w, https://substackcdn.com/image/fetch/$s_!uC2b!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5d62c13f-de6d-4089-8dfd-1b29d097e9fa_915x436.png 1272w, https://substackcdn.com/image/fetch/$s_!uC2b!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5d62c13f-de6d-4089-8dfd-1b29d097e9fa_915x436.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!uC2b!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5d62c13f-de6d-4089-8dfd-1b29d097e9fa_915x436.png" width="915" height="436" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5d62c13f-de6d-4089-8dfd-1b29d097e9fa_915x436.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:436,&quot;width&quot;:915,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:115338,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!uC2b!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5d62c13f-de6d-4089-8dfd-1b29d097e9fa_915x436.png 424w, https://substackcdn.com/image/fetch/$s_!uC2b!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5d62c13f-de6d-4089-8dfd-1b29d097e9fa_915x436.png 848w, https://substackcdn.com/image/fetch/$s_!uC2b!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5d62c13f-de6d-4089-8dfd-1b29d097e9fa_915x436.png 1272w, https://substackcdn.com/image/fetch/$s_!uC2b!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5d62c13f-de6d-4089-8dfd-1b29d097e9fa_915x436.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>Streaming messages</h2><p>Let&#8217;s turn the chatbot response into a nice typing animation as a final tweak. We need to do two things. First, we&#8217;ll call a different API method that returns the response as a generator instead of the whole output at once. Then, we&#8217;ll wrap that with our own generator to unpack the API response and extract the text chunks.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!r-qP!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdb7de374-d70c-4a91-865b-5e625a008f05_915x655.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!r-qP!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdb7de374-d70c-4a91-865b-5e625a008f05_915x655.png 424w, https://substackcdn.com/image/fetch/$s_!r-qP!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdb7de374-d70c-4a91-865b-5e625a008f05_915x655.png 848w, https://substackcdn.com/image/fetch/$s_!r-qP!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdb7de374-d70c-4a91-865b-5e625a008f05_915x655.png 1272w, https://substackcdn.com/image/fetch/$s_!r-qP!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdb7de374-d70c-4a91-865b-5e625a008f05_915x655.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!r-qP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdb7de374-d70c-4a91-865b-5e625a008f05_915x655.png" width="915" height="655" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/db7de374-d70c-4a91-865b-5e625a008f05_915x655.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:655,&quot;width&quot;:915,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:94446,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!r-qP!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdb7de374-d70c-4a91-865b-5e625a008f05_915x655.png 424w, https://substackcdn.com/image/fetch/$s_!r-qP!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdb7de374-d70c-4a91-865b-5e625a008f05_915x655.png 848w, https://substackcdn.com/image/fetch/$s_!r-qP!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdb7de374-d70c-4a91-865b-5e625a008f05_915x655.png 1272w, https://substackcdn.com/image/fetch/$s_!r-qP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdb7de374-d70c-4a91-865b-5e625a008f05_915x655.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Fortunately, Streamlit has a nice functionality for rendering a text stream.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!OIVG!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45bf71ab-407c-4eef-b8e5-38f15b11327f_915x96.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!OIVG!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45bf71ab-407c-4eef-b8e5-38f15b11327f_915x96.png 424w, https://substackcdn.com/image/fetch/$s_!OIVG!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45bf71ab-407c-4eef-b8e5-38f15b11327f_915x96.png 848w, https://substackcdn.com/image/fetch/$s_!OIVG!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45bf71ab-407c-4eef-b8e5-38f15b11327f_915x96.png 1272w, https://substackcdn.com/image/fetch/$s_!OIVG!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45bf71ab-407c-4eef-b8e5-38f15b11327f_915x96.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!OIVG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45bf71ab-407c-4eef-b8e5-38f15b11327f_915x96.png" width="915" height="96" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/45bf71ab-407c-4eef-b8e5-38f15b11327f_915x96.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:96,&quot;width&quot;:915,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:15083,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!OIVG!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45bf71ab-407c-4eef-b8e5-38f15b11327f_915x96.png 424w, https://substackcdn.com/image/fetch/$s_!OIVG!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45bf71ab-407c-4eef-b8e5-38f15b11327f_915x96.png 848w, https://substackcdn.com/image/fetch/$s_!OIVG!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45bf71ab-407c-4eef-b8e5-38f15b11327f_915x96.png 1272w, https://substackcdn.com/image/fetch/$s_!OIVG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45bf71ab-407c-4eef-b8e5-38f15b11327f_915x96.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>Go ahead and run this update, and you&#8217;ll see how the chatbot response is streamed almost in real-time (depending on how fast the API responds, of course). This has the upside that the user doesn&#8217;t have to wait until the generation is completed to see something, which greatly improves the experience.</p><h2>Conclusions</h2><p>And that&#8217;s it, our first ChatGPT clone! Nothing too fancy, but if you&#8217;ve never coded a chatbot before, this can look like magic. It certainly did for me!</p><p>The patterns we learned in this article will help us tremendously in the upcoming applications. Just to recap, this is what we&#8217;ve mastered:</p><ul><li><p>How to setup a basic chat workflow, complete with history.</p></li><li><p>How to configure the chatbot with a system prompt.</p></li><li><p>How to stream the LLM response to get a fancy typing animation.</p></li></ul><p>Now that we have the basic setup ready, we can start building some cool stuff.</p><div><hr></div><blockquote><p>And that&#8217;s it for today! If you&#8217;re intrigued about LLMs, please check out my in-progress book <strong>How to Train your Chatbot</strong>. Inside you&#8217;ll find 60+ pages of content, plus the full source code for this application. The early access pass is 50% off for the rest of April.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://apiad.gumroad.com/l/chatbots/r8n3264&quot;,&quot;text&quot;:&quot;Get early access - 50% off&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://apiad.gumroad.com/l/chatbots/r8n3264"><span>Get early access - 50% off</span></a></p></blockquote>]]></content:encoded></item><item><title><![CDATA[The Lurker's Labyrinth]]></title><description><![CDATA[Chapter 1 of The Hitchhiker's Guide to Graphs]]></description><link>https://blog.apiad.net/p/the-lurkers-labyrinth</link><guid isPermaLink="false">https://blog.apiad.net/p/the-lurkers-labyrinth</guid><dc:creator><![CDATA[Alejandro Piad Morffis]]></dc:creator><pubDate>Tue, 09 Apr 2024 14:46:59 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!S-eF!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1e26fdb8-b5b9-432c-af42-fe0e07e40b26_1024x1024.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote><p>This post is a preview of the first chapter of my WIP book on graphs, <strong>The Hitchhikers Guide to Graphs</strong>, written in collaboration with <span class="mention-wrap" data-attrs="{&quot;name&quot;:&quot;Tivadar Danka&quot;,&quot;id&quot;:10322584,&quot;type&quot;:&quot;user&quot;,&quot;url&quot;:null,&quot;photo_url&quot;:&quot;https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F3b26cd48-153a-4207-b1e3-e14e1ec8d5e8_400x400.jpeg&quot;,&quot;uuid&quot;:&quot;5582104c-07c3-479e-b520-a8514b016cef&quot;}" data-component-name="MentionToDOM"></span> from <span class="mention-wrap" data-attrs="{&quot;name&quot;:&quot;The Palindrome&quot;,&quot;id&quot;:1176501,&quot;type&quot;:&quot;pub&quot;,&quot;url&quot;:&quot;https://open.substack.com/pub/thepalindrome&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f8b68cf8-d3f4-42f6-b8dd-cccde036005f_720x720.png&quot;,&quot;uuid&quot;:&quot;71ee6ac1-2780-489e-8a39-e2158a09eeeb&quot;}" data-component-name="MentionToDOM"></span>. </p><p>The full content of the book will be published for free online, but if you want to support its development, please consider getting an early access pass &#8212;<em><strong>50% off for the remaining of April</strong></em>&#8212;, which gives you access to frequent updates, digital copies of all future versions forever, and a special link to get the print version at cost when it&#8217;s published.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://apiad.gumroad.com/l/graphs/r8n3264&quot;,&quot;text&quot;:&quot;Early Access Pass - 50% off&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://apiad.gumroad.com/l/graphs/r8n3264"><span>Early Access Pass - 50% off</span></a></p></blockquote><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!S-eF!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1e26fdb8-b5b9-432c-af42-fe0e07e40b26_1024x1024.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!S-eF!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1e26fdb8-b5b9-432c-af42-fe0e07e40b26_1024x1024.jpeg 424w, https://substackcdn.com/image/fetch/$s_!S-eF!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1e26fdb8-b5b9-432c-af42-fe0e07e40b26_1024x1024.jpeg 848w, https://substackcdn.com/image/fetch/$s_!S-eF!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1e26fdb8-b5b9-432c-af42-fe0e07e40b26_1024x1024.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!S-eF!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1e26fdb8-b5b9-432c-af42-fe0e07e40b26_1024x1024.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!S-eF!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1e26fdb8-b5b9-432c-af42-fe0e07e40b26_1024x1024.jpeg" width="1024" height="1024" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1e26fdb8-b5b9-432c-af42-fe0e07e40b26_1024x1024.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1024,&quot;width&quot;:1024,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:217525,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!S-eF!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1e26fdb8-b5b9-432c-af42-fe0e07e40b26_1024x1024.jpeg 424w, https://substackcdn.com/image/fetch/$s_!S-eF!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1e26fdb8-b5b9-432c-af42-fe0e07e40b26_1024x1024.jpeg 848w, https://substackcdn.com/image/fetch/$s_!S-eF!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1e26fdb8-b5b9-432c-af42-fe0e07e40b26_1024x1024.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!S-eF!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1e26fdb8-b5b9-432c-af42-fe0e07e40b26_1024x1024.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><em>Ariadne Wanderlust has been tasked by Major von Veertex with mapping out the legendary underground labyrinth of Graphtopia, where a fearsome beast called the Lurker lives. Many brave adventurers had entered the maze, hoping to slay the monster or find the hidden treasure, but none had ever returned.</em></p><p><em>Ariadne was not afraid, for she was well-versed in the arcane knowledge of graph traversal algorithms! Before being appointed Chief Explorer, she taught graph search at Graphtopia University and was considered a world expert on depth-first exploration.</em></p><p><em>She knew what she had to do: walk deep into the maze for as long as possible until she hit a wall, and then backtrack her steps to find an alternative route, carefully marking the walls of the already explored paths to avoid getting stuck in a loop&#8212;all adventurers travel with a bag full of chalk, you know? </em></p><p><em>She hoped to find the Lurker&#8217;s lair and then exit before her bag of chalk ran out. However, she soon realized that the labyrinth was more complex and dangerous than she had imagined. It was full of traps, dead ends, and loops. She also heard the Lurker&#8217;s roars getting closer as it followed her scent. </em></p><p><em>She wondered if she had made a mistake by choosing this exploration technique, but it was too late to switch. Now, all she could do was hope to find the exit before the Minotaur could find her.</em></p><div><hr></div><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://blog.apiad.net/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Mostly Harmless Ideas is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div><hr></div><h2>Modelling the labyrinth</h2><p>The first step in solving any problem with graphs is to turn that problem into a graph problem!</p><p>In this case, we must find a way out of the labyrinth, so we must decide how to model it as a graph such that nodes and edges map to concepts in the labyrinth that are useful for this task. The most natural mapping, and the one you&#8217;re probably thinking about, is using edges to model the corridors in the labyrinth and nodes to model the intersections.</p><p>Here is a possible depiction of a labyrinth as a graph:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!y7OT!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6776b709-9c40-44d7-ab25-ba54a2315b0b_919x445.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!y7OT!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6776b709-9c40-44d7-ab25-ba54a2315b0b_919x445.png 424w, https://substackcdn.com/image/fetch/$s_!y7OT!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6776b709-9c40-44d7-ab25-ba54a2315b0b_919x445.png 848w, https://substackcdn.com/image/fetch/$s_!y7OT!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6776b709-9c40-44d7-ab25-ba54a2315b0b_919x445.png 1272w, https://substackcdn.com/image/fetch/$s_!y7OT!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6776b709-9c40-44d7-ab25-ba54a2315b0b_919x445.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!y7OT!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6776b709-9c40-44d7-ab25-ba54a2315b0b_919x445.png" width="919" height="445" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6776b709-9c40-44d7-ab25-ba54a2315b0b_919x445.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:445,&quot;width&quot;:919,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:34426,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!y7OT!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6776b709-9c40-44d7-ab25-ba54a2315b0b_919x445.png 424w, https://substackcdn.com/image/fetch/$s_!y7OT!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6776b709-9c40-44d7-ab25-ba54a2315b0b_919x445.png 848w, https://substackcdn.com/image/fetch/$s_!y7OT!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6776b709-9c40-44d7-ab25-ba54a2315b0b_919x445.png 1272w, https://substackcdn.com/image/fetch/$s_!y7OT!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6776b709-9c40-44d7-ab25-ba54a2315b0b_919x445.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>In this model, the solution to our problem &#8211;finding a way out of the labyrinth&#8211; translates into finding a sequence of nodes, each adjacent to the next, that takes us from the start <code>S</code> to the end <code>E</code>.</p><p>Let&#8217;s begin by formalizing this notion of &#8220;walking&#8221; through the graph and meet the most important algorithms that will free Ariadne from the Lurker.</p><h2>Walking through a graph</h2><p>The most important structure in a graph is a sequence of connected vertices. In the general case, this is called a <em>walk</em>, where the only restriction is that between any pair of consecutive vertices there is an edge. Let&#8217;s look at an example graph to see what we're talking about.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!_ZCG!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4e67820-7e22-4761-af17-a73984c3ea2f_775x506.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!_ZCG!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4e67820-7e22-4761-af17-a73984c3ea2f_775x506.png 424w, https://substackcdn.com/image/fetch/$s_!_ZCG!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4e67820-7e22-4761-af17-a73984c3ea2f_775x506.png 848w, https://substackcdn.com/image/fetch/$s_!_ZCG!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4e67820-7e22-4761-af17-a73984c3ea2f_775x506.png 1272w, https://substackcdn.com/image/fetch/$s_!_ZCG!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4e67820-7e22-4761-af17-a73984c3ea2f_775x506.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!_ZCG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4e67820-7e22-4761-af17-a73984c3ea2f_775x506.png" width="775" height="506" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f4e67820-7e22-4761-af17-a73984c3ea2f_775x506.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:506,&quot;width&quot;:775,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:59476,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!_ZCG!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4e67820-7e22-4761-af17-a73984c3ea2f_775x506.png 424w, https://substackcdn.com/image/fetch/$s_!_ZCG!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4e67820-7e22-4761-af17-a73984c3ea2f_775x506.png 848w, https://substackcdn.com/image/fetch/$s_!_ZCG!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4e67820-7e22-4761-af17-a73984c3ea2f_775x506.png 1272w, https://substackcdn.com/image/fetch/$s_!_ZCG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff4e67820-7e22-4761-af17-a73984c3ea2f_775x506.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>For example, the sequence <code>a, b, e, d, e, c</code> is a valid walk in our example graph, one that happens to touch all vertices. (But a walk doesn&#8217;t have to touch all vertices.) Notice that we can move over the same edge back and forth as we want, so we can extend any walk infinitely.</p><p>If we never repeat an edge (via backtracking or making a loop), then we have a <em>trail</em>. The previous walk is not a trail because we backtrack through <code>(d,e)</code>. But <code>a, b, e, d, c, e</code> is a valid trail in our example graph because although <code>e</code> appears twice, we get to it via different edges each time.</p><p>If we also never repeat a vertex, then we have a <em>path</em>. (Some literature will use <em>path</em> to refer to what we call a <em>trail</em>, and <em>simple path</em> to refer to a path with no repeated vertices). In our example graph, <code>a, b, d, c, e</code> is a path that happens to involve all vertices.</p><p>If the path loops over from the final vertex back into the first one, like <code>a, b, d, c, e, a</code>, then we call it a <em>cycle</em>.</p><h2>Graph traversal</h2><p>The simplest graph procedure involving some notion of &#8220;walking&#8221; is graph traversal. This task involves enumerating all nodes in a predefined order by moving through the edges. That is, we don&#8217;t want to simply list all nodes; we want to order them in a way that uses the graph structure so that subsequent nodes are connected.</p><p>There are two basic graph traversal algorithms: <em>depth&#8722;first search</em> (DFS) and <em>breadth&#8722;first search</em> (BFS). Both algorithms are very similar and will produce a full enumeration of a graph. DFS and BFS differ in how they prioritize being eager versus being comprehensive.</p><h3>Abstract traversal</h3><p>We will begin by defining what our abstract notion of &#8220;search&#8221; looks like. To keep things simple, we assume that a search algorithm provides a single method <code>traverse</code> that simply enumerates all edges in the order in which they are discovered.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!IV6j!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7e97e69-f11c-4212-a94e-e1521a90cf6c_1602x612.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!IV6j!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7e97e69-f11c-4212-a94e-e1521a90cf6c_1602x612.png 424w, https://substackcdn.com/image/fetch/$s_!IV6j!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7e97e69-f11c-4212-a94e-e1521a90cf6c_1602x612.png 848w, https://substackcdn.com/image/fetch/$s_!IV6j!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7e97e69-f11c-4212-a94e-e1521a90cf6c_1602x612.png 1272w, https://substackcdn.com/image/fetch/$s_!IV6j!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7e97e69-f11c-4212-a94e-e1521a90cf6c_1602x612.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!IV6j!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7e97e69-f11c-4212-a94e-e1521a90cf6c_1602x612.png" width="1456" height="556" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a7e97e69-f11c-4212-a94e-e1521a90cf6c_1602x612.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:556,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:70108,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!IV6j!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7e97e69-f11c-4212-a94e-e1521a90cf6c_1602x612.png 424w, https://substackcdn.com/image/fetch/$s_!IV6j!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7e97e69-f11c-4212-a94e-e1521a90cf6c_1602x612.png 848w, https://substackcdn.com/image/fetch/$s_!IV6j!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7e97e69-f11c-4212-a94e-e1521a90cf6c_1602x612.png 1272w, https://substackcdn.com/image/fetch/$s_!IV6j!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa7e97e69-f11c-4212-a94e-e1521a90cf6c_1602x612.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption"><a href="https://github.com/apiad/graphs/blob/main/graphs/search.py#L32&#8722;L39">See on Github</a></figcaption></figure></div><p>The <code>nodes</code> method is just a thing wrapper around <code>traverse</code> that yields the nodes instead of the full edges.</p><p>Why make this a class? Isn&#8217;t this just a method? Well, it&#8217;s a bit of a mouthful at the moment, for sure. But later, as we explore many search algorithms, we&#8217;ll want to compare different strategies. That&#8217;s when the <code>search</code> interface will shine.</p><p>Plus, this abstract method allows us to implement a very common search pattern that we&#8217;ll&#8217; reuse over and over: searching for a specific set of nodes. (Including a single node.) We can have the general&#8722;purpose case that matches any node with a given property and the special case when we need to find one particular node &#8722;&#8722; e.g., like the exit of the labyrinth.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!9blh!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbbda7333-eb23-4b9b-9ad0-78ddbcf6f6c3_1602x746.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!9blh!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbbda7333-eb23-4b9b-9ad0-78ddbcf6f6c3_1602x746.png 424w, https://substackcdn.com/image/fetch/$s_!9blh!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbbda7333-eb23-4b9b-9ad0-78ddbcf6f6c3_1602x746.png 848w, https://substackcdn.com/image/fetch/$s_!9blh!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbbda7333-eb23-4b9b-9ad0-78ddbcf6f6c3_1602x746.png 1272w, https://substackcdn.com/image/fetch/$s_!9blh!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbbda7333-eb23-4b9b-9ad0-78ddbcf6f6c3_1602x746.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!9blh!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbbda7333-eb23-4b9b-9ad0-78ddbcf6f6c3_1602x746.png" width="1456" height="678" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/bbda7333-eb23-4b9b-9ad0-78ddbcf6f6c3_1602x746.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:678,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:95024,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!9blh!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbbda7333-eb23-4b9b-9ad0-78ddbcf6f6c3_1602x746.png 424w, https://substackcdn.com/image/fetch/$s_!9blh!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbbda7333-eb23-4b9b-9ad0-78ddbcf6f6c3_1602x746.png 848w, https://substackcdn.com/image/fetch/$s_!9blh!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbbda7333-eb23-4b9b-9ad0-78ddbcf6f6c3_1602x746.png 1272w, https://substackcdn.com/image/fetch/$s_!9blh!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbbda7333-eb23-4b9b-9ad0-78ddbcf6f6c3_1602x746.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption"><a href="https://github.com/apiad/graphs/blob/main/graphs/search.py#L42&#8722;L53">See on Github</a></figcaption></figure></div><p>With this code in place, we&#8217;re ready for some actual search algorithms.</p><h2>Depth&#8722;first search</h2><p>As the name implies, depth&#8722;first search is a graph traversal algorithm that prioritizes going as deep as possible as fast as possible. In our labyrinth analogy, this means turning right until a dead end and backtracking to the last unexplored intersection.</p><p>More precisely, DFS starts at an arbitrary root node in the graph and then jumps to one of its neighbors, continuing the traversal from there. You can select which neighbor to visit by a random choice, but most commonly, one simply defines an order&#8212;e.g., the order in which neighbors are listed in the adjacency list.</p><p>In a practical scenario, this could mean always trying to move south, east, north, and west. Of course, you must track which nodes (or intersections, in this case) you have already visited. Otherwise, you can easily get stuck in a loop.</p><p>This is what DFS looks like in our sample graph that models the labyrinth problem.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!1RSz!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc7f289c9-f2ed-4c7f-83b6-4f4d38a2cf9e_984x487.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!1RSz!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc7f289c9-f2ed-4c7f-83b6-4f4d38a2cf9e_984x487.png 424w, https://substackcdn.com/image/fetch/$s_!1RSz!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc7f289c9-f2ed-4c7f-83b6-4f4d38a2cf9e_984x487.png 848w, https://substackcdn.com/image/fetch/$s_!1RSz!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc7f289c9-f2ed-4c7f-83b6-4f4d38a2cf9e_984x487.png 1272w, https://substackcdn.com/image/fetch/$s_!1RSz!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc7f289c9-f2ed-4c7f-83b6-4f4d38a2cf9e_984x487.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!1RSz!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc7f289c9-f2ed-4c7f-83b6-4f4d38a2cf9e_984x487.png" width="984" height="487" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c7f289c9-f2ed-4c7f-83b6-4f4d38a2cf9e_984x487.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:487,&quot;width&quot;:984,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:66843,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!1RSz!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc7f289c9-f2ed-4c7f-83b6-4f4d38a2cf9e_984x487.png 424w, https://substackcdn.com/image/fetch/$s_!1RSz!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc7f289c9-f2ed-4c7f-83b6-4f4d38a2cf9e_984x487.png 848w, https://substackcdn.com/image/fetch/$s_!1RSz!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc7f289c9-f2ed-4c7f-83b6-4f4d38a2cf9e_984x487.png 1272w, https://substackcdn.com/image/fetch/$s_!1RSz!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc7f289c9-f2ed-4c7f-83b6-4f4d38a2cf9e_984x487.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">We label each edge by a number that indicates the order in which it is discovered by DFS. Thus, edges that have consecutive numbers indicate that DFS traveled along that path. When you see two contiguous edges with non&#8722;consecutive numbers, that means DFS backtracked to the corresponding node after getting stuck to explore a different path.</figcaption></figure></div><h3>Programming DFS</h3><p>Let&#8217;s implement DFS! The easiest way is via <em>recursion<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a></em>: we start at the root node and recursively visit all non&#8722;visited neighbors. To make sure we don&#8217;t get stuck in a loop, we keep track of the explored nodes throughout all the recursive calls.</p><p>Each iteration returns the edge <code>(x, y)</code>, where <code>y</code> is the current node under consideration, and <code>x</code> is the &#8220;parent&#8221; node &#8722;&#8722; that we must also keep track of during recursion.</p><p>Here is the full implementation:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ZEGl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4c40265c-2715-4f72-813e-7c5a196ab2ca_1602x792.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ZEGl!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4c40265c-2715-4f72-813e-7c5a196ab2ca_1602x792.png 424w, https://substackcdn.com/image/fetch/$s_!ZEGl!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4c40265c-2715-4f72-813e-7c5a196ab2ca_1602x792.png 848w, https://substackcdn.com/image/fetch/$s_!ZEGl!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4c40265c-2715-4f72-813e-7c5a196ab2ca_1602x792.png 1272w, https://substackcdn.com/image/fetch/$s_!ZEGl!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4c40265c-2715-4f72-813e-7c5a196ab2ca_1602x792.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ZEGl!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4c40265c-2715-4f72-813e-7c5a196ab2ca_1602x792.png" width="1456" height="720" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4c40265c-2715-4f72-813e-7c5a196ab2ca_1602x792.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:720,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:113943,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ZEGl!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4c40265c-2715-4f72-813e-7c5a196ab2ca_1602x792.png 424w, https://substackcdn.com/image/fetch/$s_!ZEGl!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4c40265c-2715-4f72-813e-7c5a196ab2ca_1602x792.png 848w, https://substackcdn.com/image/fetch/$s_!ZEGl!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4c40265c-2715-4f72-813e-7c5a196ab2ca_1602x792.png 1272w, https://substackcdn.com/image/fetch/$s_!ZEGl!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4c40265c-2715-4f72-813e-7c5a196ab2ca_1602x792.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption"><a href="https://github.com/apiad/graphs/blob/main/graphs/search.py#L72&#8722;L84">See on Github</a></figcaption></figure></div><p>As it&#8217;s common in recursive methods, we have a public &#8220;portal&#8221; method <code>traverse</code> that exposes the public arguments, which in turn defers to a private implementation <code>_dfs</code> that takes any additional arguments we need for bookkeeping.</p><h2>Finding your way out</h2><p>Armed with the arcane knowledge of depth&#8722;first search, it is now trivial to exit the labyrinth (provided there is indeed a way out). We just need to run DFS in the start node and, eventually, we will reach the end node. We might have to backtrack once or twice if we are unlucky to pick to wrong turn, but as long as we keep track of every node we visit, and make sure not repeat any of them, the way out is guaranteed to be found.</p><h3>Computing paths</h3><p>While knowing that a goal node exists is useful, we often want to find the actual path that takes us there. Fortunately, our abstract <code>Search</code> strategy can implement this operation easily using the <code>parent</code> from the tuple <code>(parent, node)</code> available in each iteration in the <code>traverse</code> method.</p><p>To quickly compute paths, we can define a simple structure (<code>Paths</code>) that stores a reference to the parent of each node found during search. With this information, the path between our origin vertex and an arbitrary destination is easy to compute by following the links backwards. That is, we start at the destination node, and iteratively add the parent node to a list, until we find the origin node. Then, we simply reverse the list.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!gJXG!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba8ad37d-19e2-4d4c-ad1c-03ab2df1e56d_1602x1152.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!gJXG!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba8ad37d-19e2-4d4c-ad1c-03ab2df1e56d_1602x1152.png 424w, https://substackcdn.com/image/fetch/$s_!gJXG!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba8ad37d-19e2-4d4c-ad1c-03ab2df1e56d_1602x1152.png 848w, https://substackcdn.com/image/fetch/$s_!gJXG!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba8ad37d-19e2-4d4c-ad1c-03ab2df1e56d_1602x1152.png 1272w, https://substackcdn.com/image/fetch/$s_!gJXG!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba8ad37d-19e2-4d4c-ad1c-03ab2df1e56d_1602x1152.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!gJXG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba8ad37d-19e2-4d4c-ad1c-03ab2df1e56d_1602x1152.png" width="1456" height="1047" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ba8ad37d-19e2-4d4c-ad1c-03ab2df1e56d_1602x1152.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1047,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:141217,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!gJXG!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba8ad37d-19e2-4d4c-ad1c-03ab2df1e56d_1602x1152.png 424w, https://substackcdn.com/image/fetch/$s_!gJXG!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba8ad37d-19e2-4d4c-ad1c-03ab2df1e56d_1602x1152.png 848w, https://substackcdn.com/image/fetch/$s_!gJXG!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba8ad37d-19e2-4d4c-ad1c-03ab2df1e56d_1602x1152.png 1272w, https://substackcdn.com/image/fetch/$s_!gJXG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fba8ad37d-19e2-4d4c-ad1c-03ab2df1e56d_1602x1152.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption"><a href="https://github.com/apiad/graphs/blob/main/graphs/search.py#L5&#8722;L25">See on Github</a></figcaption></figure></div><blockquote><p><strong>Paths are origin&#8722;dependent (!)</strong></p><p>You&#8217;ll notice we don&#8217;t require an <code>origin</code> parameter in the <code>Paths.path</code> method. That is because this structure holds paths precomputed from a fixed origin node, that is, the node from which the search algorithm started.</p><p>If you need to precompute paths for arbitrary pairs of nodes, there is little you can do other than using a <code>Path</code> instance for each origin node.</p></blockquote><p>With this structure in place, we can add a method to the <code>Search</code> class to compute all paths for a given graph and origin.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!D6pR!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f3fa236-a924-4383-a7cc-8459eec0bb7b_1602x656.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!D6pR!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f3fa236-a924-4383-a7cc-8459eec0bb7b_1602x656.png 424w, https://substackcdn.com/image/fetch/$s_!D6pR!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f3fa236-a924-4383-a7cc-8459eec0bb7b_1602x656.png 848w, https://substackcdn.com/image/fetch/$s_!D6pR!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f3fa236-a924-4383-a7cc-8459eec0bb7b_1602x656.png 1272w, https://substackcdn.com/image/fetch/$s_!D6pR!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f3fa236-a924-4383-a7cc-8459eec0bb7b_1602x656.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!D6pR!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f3fa236-a924-4383-a7cc-8459eec0bb7b_1602x656.png" width="1456" height="596" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4f3fa236-a924-4383-a7cc-8459eec0bb7b_1602x656.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:596,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:74716,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!D6pR!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f3fa236-a924-4383-a7cc-8459eec0bb7b_1602x656.png 424w, https://substackcdn.com/image/fetch/$s_!D6pR!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f3fa236-a924-4383-a7cc-8459eec0bb7b_1602x656.png 848w, https://substackcdn.com/image/fetch/$s_!D6pR!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f3fa236-a924-4383-a7cc-8459eec0bb7b_1602x656.png 1272w, https://substackcdn.com/image/fetch/$s_!D6pR!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4f3fa236-a924-4383-a7cc-8459eec0bb7b_1602x656.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption"><a href="https://github.com/apiad/graphs/blob/main/graphs/search.py#L56&#8722;L65">See on Github</a></figcaption></figure></div><h2>Analyzing DFS</h2><p>Before finishing, let&#8217;s turn our attention now to the analysis of this algorithm. In computer science, we are often interested in answering a few critical questions for every algorithm:</p><ol><li><p>Does the algorithm always work?</p></li><li><p>How fast does it work?</p></li><li><p>How much memory does it need?</p></li><li><p>Is there any better algorithm?</p></li></ol><p>The first question asks about the <em>correctness</em> of the algorithm. An algorithm is only useful if it always works &#8722;&#8722; or if, at least, we can determine beforehand whether it will work or not. In the case of DFS, we can claim with absolute certainty that, if there is a path from origin to destination, the algorithm will eventually find it.</p><p>&#191;Why? We can give an intuitive justification of correctness for DFS as follows. Since we never repeat any vertex, and we explore all neighbors of the vertices we visit, we must visit every vertex, that is reachable from the origin, eventually.</p><p>Now, there is one massive caveat with DFS. You have no guarantee the path you find is &#8348;he shortest path_ from the origin to the destination. Because DFS walks down a path as long as possible, and then never revisits those nodes, you can actually discover the destination first by going throught the long way, instead of taking a shortcut.</p><p>The second and third questions ask about the <em>performance</em> of the algorithm. If we want to compare two algorithms for the same problem, there are almost always at least two obvious dimensions: running time and memory requirement. We usually want the fastest algorithm, provided we have enough memory to run it, right?</p><p>Now, for reasons beyond the scope of this book, it is almost always useless to think in terms of actual time (like seconds) and memory (like megabytes), since the exact values of those properties for any given algorithm will depend on many circumstancial details like the speed of your microprocessor or the programming language you used.</p><p>Instead, to compare two algorithms in the most abstract and fair possible way, we use a relative run&#8722;time and memory, that just considers the &#8220;size&#8221; of the problem. In most cases in this book, the &#8220;size&#8221; of our problem is something we can approximate by the total number of edges and vertices in our graph.</p><p>Thus, we can formulate questions 2 and 3 in terms of, given a graph of <code>n</code> vertices and <code>m</code> edges, how many steps of DFS do we need? You can probably suspect the answer by looking at [@fig&#8722;labyrinth&#8722;solve] and counting the numbers. You will notice the answer is just the number of edges, since DFS explores the whole graph, and never repeats any path. In general, most search algorithms in this book will take a time that is proportional to the total number of vertices and edges in the graph.</p><p>Regarding memory, we could assume we also need something proportional to the total size of the graph, since we need to mark every visited node, right? Well, that is true only if there are loops in the graph. But if you know beforehand there are no loops, then once you hit a dead end and backtrack, you can forget that whole branch up until the bifurcation. This means that, on average, you will only need enough memory to remember as many nodes as there are in the longest possible path in the graph.</p><p>Finally, we can ask if there is any way to discover the exit faster. The general answer is &#8220;no&#8221;, unless we know something special about the graph. That is, in a general graph, without any extra knowledge, you have absolutely no idea where the exit (or the destination nodes in general) will be, so at worst you&#8217;ll have to explore the whole graph.</p><p>However, as we will see a few chapters down the road, in many ocassions you do know something extra about the graph&#8212;e.g., when driving around a city, you have some sense of which direction your destination is. In these cases, we can often speed&#8722;up the search massively with some clever strategies.</p><h2>Final remarks</h2><p>Depth&#8722;First Search is a cornerstone of graph search. Almost all algorithms in this book will either include it as an explicit step, or use it as a building block.</p><p>Besides being extremely simple and elegant to implement, DFS is also the easiest to adapt to a real&#8722;life situation. Unlike most other graph search algorithms, DFS only moves to adjacent nodes. So, if you are an agent exploring a graph&#8722;like structure in real&#8722;life&#8212;just like Ariadne&#8212;DFS is what you would most likely use.</p><p>The main caveat of DFS, as we already saw, is that you cannot guarantee that the first time you discover a node, you did so via the shortest path to the origin. In fact, it is easy to come up with examples where you actually reach your destination through the longest possible path; it all depends on how lucky you are selecting which neighboor to visit next.</p><p>Next time, we will look at an alternative way to explore a graph that guarantees shortests paths, but it requires that you can teleport to any given node. If you&#8217;re want to read the chapters as soon as possible, consider getting an early access pass.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://apiad.gumroad.com/l/graphs/r8n3264&quot;,&quot;text&quot;:&quot;Early Access Pass - 50% off&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://apiad.gumroad.com/l/graphs/r8n3264"><span>Early Access Pass - 50% off</span></a></p><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p>As the saying goes, to understand recursion, you first need to understand recursion.</p></div></div>]]></content:encoded></item><item><title><![CDATA[Make a Customer Service Bot]]></title><description><![CDATA[Build a simple bot that replies to queries with personalized information from a user profile and general knowledge from a FAQ]]></description><link>https://blog.apiad.net/p/customer-service-bot</link><guid isPermaLink="false">https://blog.apiad.net/p/customer-service-bot</guid><dc:creator><![CDATA[Alejandro Piad Morffis]]></dc:creator><pubDate>Tue, 13 Feb 2024 17:46:51 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc2b621db-62a6-452b-b433-ccc0f5c4f0b3_1024x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Welcome to another <a href="https://blog.apiad.net/s/lessons">Coding Lesson</a>! </p><p>Building on the success of the <a href="https://blog.apiad.net/p/chat-with-your-pdf">last lesson on making a bot to chat with a PDF file</a>, today we&#8217;re reusing some of those ideas to build a customer service bot that can reply to user queries using a combination of profile information and a general knowledge base.</p><p>This is a toy example to illustrate a widespread use case. Imagine you have a SaaS product with a FAQ guide containing information such as pricing and features per plan, discount offers, a general description of your service, regional availability, etc. Usually, your service users need to read the guide and find answers about their specific situation: which plan they&#8217;re using, what country they&#8217;re based on, etc. </p><p>Instead of making your users parse the entire FAQ, ignore everything irrelevant to them &#8212;such as enterprise offers&#8212; and find the relevant information that answers their specific question, you can use an AI-powered chatbot.</p><p>This chatbot will answer a user query with information from the FAQ but &#8212;crucially&#8212; inject into its context details from the current user&#8217;s profile, so the answers will be tailored to this user. </p><p>If you want to check what that looks like in a toy situation, here&#8217;s an <a href="https://service-bot-demo.streamlit.app">online demo you can play with</a>, and here&#8217;s a short video of a possible interaction.</p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;62fe1149-663e-4c54-ad86-114fbb244dd7&quot;,&quot;duration&quot;:null}"></div><p>The whole source code for this lesson is <a href="https://github.com/apiad/service-bot">available on Github</a>.</p><h3>Prerequisites</h3><p>This lesson is somewhat advanced, so I expect you to be comfortable with basic Python and building simple apps in streamlit, although most of the streamlit code will be self-explanatory.</p><p>You must also know how to set up a basic Python project using a virtual environment and install dependencies from requirement files. If you need help with any of these topics, check out <a href="https://open.substack.com/pub/thepythoncodingstack">The Python Coding Stack &#8226; by Stephen Gruppetta</a> and <a href="https://open.substack.com/pub/mostlypython">Mostly Python</a> for plenty of beginner-friendly articles.</p><p>You <em>don&#8217;t need</em> prior knowledge of using LLMs or vector databases or building chat-based apps.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://blog.apiad.net/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">These coding lessons are free for everyone, but they take a lot of effort. You can support this work with a free or paid subscription.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><blockquote><p>This post will be cut on your email, so read <a href="https://blog.apiad.net/p/customer-service-bot">it here online</a>.</p></blockquote><div><hr></div><h2>Making a Customer Service Bot</h2><p>This lesson is somewhat advanced, so I won&#8217;t explain the code step by step. Instead, I will give you a high-level description of the app's architecture and then zoom into those crucial implementation details.</p><p>My purpose, as usual, is that you understand how each functionality is implemented and how they fit together, so when you read the actual code, everything clicks into place. Thus, I won&#8217;t go line by line but rather look at the main blocks in the order that makes understanding the app workflow easier.</p><p>At a high level, this is a typical streamlit application with a simple layout. It has a sidebar where the user can upload a PDF file, and the main content is a container for <a href="https://docs.streamlit.io/knowledge-base/tutorials/build-conversational-apps">chat UI elements</a>.</p><p>The workflow is as follows:</p><ol><li><p>Initially, the FAQ guide is indexed question by question and stored in a flat in-memory vector index using <a href="https://github.com/facebookresearch/faiss">faiss</a>.</p></li><li><p>Then, the user asks a question.</p></li><li><p>The user query is embedded, and the top-3 most relevant FAQ entries are extracted.</p></li><li><p>A custom prompt is built with the FAQ entries and the current user metadata.</p></li><li><p>Using <a href="https://pypi.org/project/mistralai/">mistralai</a>, a response is obtained from an LLM.</p></li><li><p>Go back to step 2.</p></li></ol><p>But the devil is in the details, so let&#8217;s dive in.</p><h3>Indexing the FAQ guide</h3><p>Let&#8217;s assume the FAQ guide is a collection of questions and answers covering most users&#8217; needs. We want to select a subset of 3 relevant questions for a given prompt. We can use the magic of vector embeddings for that.</p><p><em>Our example FAQ guide is a <a href="https://github.com/apiad/service-bot/blob/main/faq.md">simple markdown document</a> where each question starts with `#`. Go ahead and read it. It&#8217;s hilarious.</em></p><p>First, we will split the FAQ into chunks of a manageable size &#8212;in this case, one chunk per question. </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Qo0r!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F071262c4-1609-4d5a-b712-6fbb276cafff_1586x556.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Qo0r!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F071262c4-1609-4d5a-b712-6fbb276cafff_1586x556.png 424w, https://substackcdn.com/image/fetch/$s_!Qo0r!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F071262c4-1609-4d5a-b712-6fbb276cafff_1586x556.png 848w, https://substackcdn.com/image/fetch/$s_!Qo0r!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F071262c4-1609-4d5a-b712-6fbb276cafff_1586x556.png 1272w, https://substackcdn.com/image/fetch/$s_!Qo0r!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F071262c4-1609-4d5a-b712-6fbb276cafff_1586x556.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Qo0r!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F071262c4-1609-4d5a-b712-6fbb276cafff_1586x556.png" width="1456" height="510" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/071262c4-1609-4d5a-b712-6fbb276cafff_1586x556.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:510,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:67043,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Qo0r!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F071262c4-1609-4d5a-b712-6fbb276cafff_1586x556.png 424w, https://substackcdn.com/image/fetch/$s_!Qo0r!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F071262c4-1609-4d5a-b712-6fbb276cafff_1586x556.png 848w, https://substackcdn.com/image/fetch/$s_!Qo0r!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F071262c4-1609-4d5a-b712-6fbb276cafff_1586x556.png 1272w, https://substackcdn.com/image/fetch/$s_!Qo0r!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F071262c4-1609-4d5a-b712-6fbb276cafff_1586x556.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Once loaded and split, we use the <code>faiss</code> vector library to create a simple flat in-memory index. We can add a bit of streamlit magic to render a progress bar as this indexing takes place.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!5GXM!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdda1ea6e-4820-4c17-a261-1cf68adfd692_1586x822.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!5GXM!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdda1ea6e-4820-4c17-a261-1cf68adfd692_1586x822.png 424w, https://substackcdn.com/image/fetch/$s_!5GXM!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdda1ea6e-4820-4c17-a261-1cf68adfd692_1586x822.png 848w, https://substackcdn.com/image/fetch/$s_!5GXM!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdda1ea6e-4820-4c17-a261-1cf68adfd692_1586x822.png 1272w, https://substackcdn.com/image/fetch/$s_!5GXM!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdda1ea6e-4820-4c17-a261-1cf68adfd692_1586x822.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!5GXM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdda1ea6e-4820-4c17-a261-1cf68adfd692_1586x822.png" width="1456" height="755" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/dda1ea6e-4820-4c17-a261-1cf68adfd692_1586x822.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:755,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:139283,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!5GXM!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdda1ea6e-4820-4c17-a261-1cf68adfd692_1586x822.png 424w, https://substackcdn.com/image/fetch/$s_!5GXM!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdda1ea6e-4820-4c17-a261-1cf68adfd692_1586x822.png 848w, https://substackcdn.com/image/fetch/$s_!5GXM!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdda1ea6e-4820-4c17-a261-1cf68adfd692_1586x822.png 1272w, https://substackcdn.com/image/fetch/$s_!5GXM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdda1ea6e-4820-4c17-a261-1cf68adfd692_1586x822.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><em>The </em><code>@st.cache_resource</code><em> decorator ensures that we only create one index per execution of the application.</em></p><p>The embeddings are computed with a handy function that we will see later.</p><h3>Answering questions</h3><p>Once the FAQ guide is indexed, we can take user questions. We will use <a href="https://mistral.ai">Mistral AI</a> as our supplier of LLMs because they provide really powerful open-source language models that challenge even ChatGPT.</p><p>Here is the monster function to reply to a specific user query.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Za0f!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ef9238c-5321-470e-8ee0-e4a76284544e_1586x1088.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Za0f!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ef9238c-5321-470e-8ee0-e4a76284544e_1586x1088.png 424w, https://substackcdn.com/image/fetch/$s_!Za0f!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ef9238c-5321-470e-8ee0-e4a76284544e_1586x1088.png 848w, https://substackcdn.com/image/fetch/$s_!Za0f!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ef9238c-5321-470e-8ee0-e4a76284544e_1586x1088.png 1272w, https://substackcdn.com/image/fetch/$s_!Za0f!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ef9238c-5321-470e-8ee0-e4a76284544e_1586x1088.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Za0f!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ef9238c-5321-470e-8ee0-e4a76284544e_1586x1088.png" width="1456" height="999" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4ef9238c-5321-470e-8ee0-e4a76284544e_1586x1088.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:999,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:178714,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Za0f!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ef9238c-5321-470e-8ee0-e4a76284544e_1586x1088.png 424w, https://substackcdn.com/image/fetch/$s_!Za0f!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ef9238c-5321-470e-8ee0-e4a76284544e_1586x1088.png 848w, https://substackcdn.com/image/fetch/$s_!Za0f!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ef9238c-5321-470e-8ee0-e4a76284544e_1586x1088.png 1272w, https://substackcdn.com/image/fetch/$s_!Za0f!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ef9238c-5321-470e-8ee0-e4a76284544e_1586x1088.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Let&#8217;s break it down.</p><p>First, we embed the question and query the indexed FAQ to retrieve the top 3 most similar documents. These correspond to 3 questions (and their answers) that are linguistically close to our query, so there is a good chance one of them has the right answer. Here&#8217;s the embedding function:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!DhUP!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b68a18e-58cf-4c1b-be0b-cb0517b94641_1586x442.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!DhUP!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b68a18e-58cf-4c1b-be0b-cb0517b94641_1586x442.png 424w, https://substackcdn.com/image/fetch/$s_!DhUP!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b68a18e-58cf-4c1b-be0b-cb0517b94641_1586x442.png 848w, https://substackcdn.com/image/fetch/$s_!DhUP!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b68a18e-58cf-4c1b-be0b-cb0517b94641_1586x442.png 1272w, https://substackcdn.com/image/fetch/$s_!DhUP!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b68a18e-58cf-4c1b-be0b-cb0517b94641_1586x442.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!DhUP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b68a18e-58cf-4c1b-be0b-cb0517b94641_1586x442.png" width="1456" height="406" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9b68a18e-58cf-4c1b-be0b-cb0517b94641_1586x442.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:406,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:53183,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!DhUP!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b68a18e-58cf-4c1b-be0b-cb0517b94641_1586x442.png 424w, https://substackcdn.com/image/fetch/$s_!DhUP!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b68a18e-58cf-4c1b-be0b-cb0517b94641_1586x442.png 848w, https://substackcdn.com/image/fetch/$s_!DhUP!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b68a18e-58cf-4c1b-be0b-cb0517b94641_1586x442.png 1272w, https://substackcdn.com/image/fetch/$s_!DhUP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b68a18e-58cf-4c1b-be0b-cb0517b94641_1586x442.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Then, we verbalize the user data (e.g., username, role, credits, plan, etc.) to feed it to the LLM. In this case, we simply produce a line-by-line text in the format <code>key: value</code>, for example:</p><pre><code>Username: Neo
Plan: free
Credits: 100
Profession: Engineer</code></pre><p>Finally, we combine these two pieces of information with the actual query in a custom prompt and send it to the LLM. Here&#8217;s the prompt:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!YN8C!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F175729e1-431b-40cc-bea8-7566181c7e67_1586x1088.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!YN8C!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F175729e1-431b-40cc-bea8-7566181c7e67_1586x1088.png 424w, https://substackcdn.com/image/fetch/$s_!YN8C!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F175729e1-431b-40cc-bea8-7566181c7e67_1586x1088.png 848w, https://substackcdn.com/image/fetch/$s_!YN8C!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F175729e1-431b-40cc-bea8-7566181c7e67_1586x1088.png 1272w, https://substackcdn.com/image/fetch/$s_!YN8C!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F175729e1-431b-40cc-bea8-7566181c7e67_1586x1088.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!YN8C!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F175729e1-431b-40cc-bea8-7566181c7e67_1586x1088.png" width="1456" height="999" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/175729e1-431b-40cc-bea8-7566181c7e67_1586x1088.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:999,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:139323,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!YN8C!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F175729e1-431b-40cc-bea8-7566181c7e67_1586x1088.png 424w, https://substackcdn.com/image/fetch/$s_!YN8C!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F175729e1-431b-40cc-bea8-7566181c7e67_1586x1088.png 848w, https://substackcdn.com/image/fetch/$s_!YN8C!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F175729e1-431b-40cc-bea8-7566181c7e67_1586x1088.png 1272w, https://substackcdn.com/image/fetch/$s_!YN8C!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F175729e1-431b-40cc-bea8-7566181c7e67_1586x1088.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>We need a Mistral client correctly configured (assuming you have your API key in the secrets file).</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Ig1E!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1bce2597-1d22-4746-b847-587901b6a96c_1586x442.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Ig1E!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1bce2597-1d22-4746-b847-587901b6a96c_1586x442.png 424w, https://substackcdn.com/image/fetch/$s_!Ig1E!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1bce2597-1d22-4746-b847-587901b6a96c_1586x442.png 848w, https://substackcdn.com/image/fetch/$s_!Ig1E!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1bce2597-1d22-4746-b847-587901b6a96c_1586x442.png 1272w, https://substackcdn.com/image/fetch/$s_!Ig1E!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1bce2597-1d22-4746-b847-587901b6a96c_1586x442.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Ig1E!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1bce2597-1d22-4746-b847-587901b6a96c_1586x442.png" width="1456" height="406" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1bce2597-1d22-4746-b847-587901b6a96c_1586x442.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:406,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:60448,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Ig1E!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1bce2597-1d22-4746-b847-587901b6a96c_1586x442.png 424w, https://substackcdn.com/image/fetch/$s_!Ig1E!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1bce2597-1d22-4746-b847-587901b6a96c_1586x442.png 848w, https://substackcdn.com/image/fetch/$s_!Ig1E!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1bce2597-1d22-4746-b847-587901b6a96c_1586x442.png 1272w, https://substackcdn.com/image/fetch/$s_!Ig1E!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1bce2597-1d22-4746-b847-587901b6a96c_1586x442.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h3>Chat management</h3><p>The final piece of the puzzle is the actual chat workflow. We will use Streamlit&#8217;s chat widgets with some tricks to simulate a conversation.</p><p>The gist of the chat management problem is that since Streamlit is stateless, we need to rebuild the entire conversation on each execution. However, we want to show the typing animation only for the last message and simply output the previous history. </p><p>We will store all messages in a session state list to achieve this. The add_message function displays the current message (with a nice typing animation) and saves it for later.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Wt0-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd519821f-d74e-4426-864c-bc6388e2d188_1586x556.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Wt0-!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd519821f-d74e-4426-864c-bc6388e2d188_1586x556.png 424w, https://substackcdn.com/image/fetch/$s_!Wt0-!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd519821f-d74e-4426-864c-bc6388e2d188_1586x556.png 848w, https://substackcdn.com/image/fetch/$s_!Wt0-!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd519821f-d74e-4426-864c-bc6388e2d188_1586x556.png 1272w, https://substackcdn.com/image/fetch/$s_!Wt0-!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd519821f-d74e-4426-864c-bc6388e2d188_1586x556.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Wt0-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd519821f-d74e-4426-864c-bc6388e2d188_1586x556.png" width="1456" height="510" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d519821f-d74e-4426-864c-bc6388e2d188_1586x556.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:510,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:79082,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Wt0-!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd519821f-d74e-4426-864c-bc6388e2d188_1586x556.png 424w, https://substackcdn.com/image/fetch/$s_!Wt0-!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd519821f-d74e-4426-864c-bc6388e2d188_1586x556.png 848w, https://substackcdn.com/image/fetch/$s_!Wt0-!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd519821f-d74e-4426-864c-bc6388e2d188_1586x556.png 1272w, https://substackcdn.com/image/fetch/$s_!Wt0-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd519821f-d74e-4426-864c-bc6388e2d188_1586x556.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The msg argument can be one of two things: a regular string or an actual asynchronous response for Mistral. In the first case, we wrap it in a function that will turn it into a stream of words with some delay in between to simulate it is being typed.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!g7Q1!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6d7b150-5eaa-4d36-978c-c125e9d8061a_1586x708.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!g7Q1!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6d7b150-5eaa-4d36-978c-c125e9d8061a_1586x708.png 424w, https://substackcdn.com/image/fetch/$s_!g7Q1!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6d7b150-5eaa-4d36-978c-c125e9d8061a_1586x708.png 848w, https://substackcdn.com/image/fetch/$s_!g7Q1!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6d7b150-5eaa-4d36-978c-c125e9d8061a_1586x708.png 1272w, https://substackcdn.com/image/fetch/$s_!g7Q1!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6d7b150-5eaa-4d36-978c-c125e9d8061a_1586x708.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!g7Q1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6d7b150-5eaa-4d36-978c-c125e9d8061a_1586x708.png" width="1456" height="650" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a6d7b150-5eaa-4d36-978c-c125e9d8061a_1586x708.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:650,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:80291,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!g7Q1!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6d7b150-5eaa-4d36-978c-c125e9d8061a_1586x708.png 424w, https://substackcdn.com/image/fetch/$s_!g7Q1!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6d7b150-5eaa-4d36-978c-c125e9d8061a_1586x708.png 848w, https://substackcdn.com/image/fetch/$s_!g7Q1!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6d7b150-5eaa-4d36-978c-c125e9d8061a_1586x708.png 1272w, https://substackcdn.com/image/fetch/$s_!g7Q1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6d7b150-5eaa-4d36-978c-c125e9d8061a_1586x708.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>In the second case, we just need to unwrap the generator and yield the actual content:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Rqfy!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe17add82-2a29-4cf7-a904-6c7891d0f057_1586x290.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Rqfy!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe17add82-2a29-4cf7-a904-6c7891d0f057_1586x290.png 424w, https://substackcdn.com/image/fetch/$s_!Rqfy!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe17add82-2a29-4cf7-a904-6c7891d0f057_1586x290.png 848w, https://substackcdn.com/image/fetch/$s_!Rqfy!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe17add82-2a29-4cf7-a904-6c7891d0f057_1586x290.png 1272w, https://substackcdn.com/image/fetch/$s_!Rqfy!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe17add82-2a29-4cf7-a904-6c7891d0f057_1586x290.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Rqfy!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe17add82-2a29-4cf7-a904-6c7891d0f057_1586x290.png" width="1456" height="266" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e17add82-2a29-4cf7-a904-6c7891d0f057_1586x290.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:266,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:37040,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Rqfy!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe17add82-2a29-4cf7-a904-6c7891d0f057_1586x290.png 424w, https://substackcdn.com/image/fetch/$s_!Rqfy!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe17add82-2a29-4cf7-a904-6c7891d0f057_1586x290.png 848w, https://substackcdn.com/image/fetch/$s_!Rqfy!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe17add82-2a29-4cf7-a904-6c7891d0f057_1586x290.png 1272w, https://substackcdn.com/image/fetch/$s_!Rqfy!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe17add82-2a29-4cf7-a904-6c7891d0f057_1586x290.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><h3>Putting it all together</h3><p>Now that all the pieces are in place let&#8217;s see how the application workflow works.</p><p>First, we initialize our session state and re-write the chat history:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!w6bI!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8642583-719e-41e4-b907-8bd72318505f_1586x594.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!w6bI!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8642583-719e-41e4-b907-8bd72318505f_1586x594.png 424w, https://substackcdn.com/image/fetch/$s_!w6bI!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8642583-719e-41e4-b907-8bd72318505f_1586x594.png 848w, https://substackcdn.com/image/fetch/$s_!w6bI!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8642583-719e-41e4-b907-8bd72318505f_1586x594.png 1272w, https://substackcdn.com/image/fetch/$s_!w6bI!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8642583-719e-41e4-b907-8bd72318505f_1586x594.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!w6bI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8642583-719e-41e4-b907-8bd72318505f_1586x594.png" width="1456" height="545" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c8642583-719e-41e4-b907-8bd72318505f_1586x594.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:545,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:94049,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!w6bI!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8642583-719e-41e4-b907-8bd72318505f_1586x594.png 424w, https://substackcdn.com/image/fetch/$s_!w6bI!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8642583-719e-41e4-b907-8bd72318505f_1586x594.png 848w, https://substackcdn.com/image/fetch/$s_!w6bI!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8642583-719e-41e4-b907-8bd72318505f_1586x594.png 1272w, https://substackcdn.com/image/fetch/$s_!w6bI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8642583-719e-41e4-b907-8bd72318505f_1586x594.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Then, we compute (or simply restore from the cache) the FAQ data and index and get the user data and query.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ULm6!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7accf060-e419-489c-b170-025c78215bba_1586x746.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ULm6!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7accf060-e419-489c-b170-025c78215bba_1586x746.png 424w, https://substackcdn.com/image/fetch/$s_!ULm6!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7accf060-e419-489c-b170-025c78215bba_1586x746.png 848w, https://substackcdn.com/image/fetch/$s_!ULm6!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7accf060-e419-489c-b170-025c78215bba_1586x746.png 1272w, https://substackcdn.com/image/fetch/$s_!ULm6!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7accf060-e419-489c-b170-025c78215bba_1586x746.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ULm6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7accf060-e419-489c-b170-025c78215bba_1586x746.png" width="1456" height="685" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7accf060-e419-489c-b170-025c78215bba_1586x746.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:685,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:121876,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ULm6!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7accf060-e419-489c-b170-025c78215bba_1586x746.png 424w, https://substackcdn.com/image/fetch/$s_!ULm6!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7accf060-e419-489c-b170-025c78215bba_1586x746.png 848w, https://substackcdn.com/image/fetch/$s_!ULm6!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7accf060-e419-489c-b170-025c78215bba_1586x746.png 1272w, https://substackcdn.com/image/fetch/$s_!ULm6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7accf060-e419-489c-b170-025c78215bba_1586x746.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>If this is the start of the conversation, we want to output some information.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!kTeQ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F08ea564f-56f7-4b6d-bf42-5be6621dc93c_1586x974.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!kTeQ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F08ea564f-56f7-4b6d-bf42-5be6621dc93c_1586x974.png 424w, https://substackcdn.com/image/fetch/$s_!kTeQ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F08ea564f-56f7-4b6d-bf42-5be6621dc93c_1586x974.png 848w, https://substackcdn.com/image/fetch/$s_!kTeQ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F08ea564f-56f7-4b6d-bf42-5be6621dc93c_1586x974.png 1272w, https://substackcdn.com/image/fetch/$s_!kTeQ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F08ea564f-56f7-4b6d-bf42-5be6621dc93c_1586x974.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!kTeQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F08ea564f-56f7-4b6d-bf42-5be6621dc93c_1586x974.png" width="1456" height="894" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/08ea564f-56f7-4b6d-bf42-5be6621dc93c_1586x974.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:894,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:180233,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!kTeQ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F08ea564f-56f7-4b6d-bf42-5be6621dc93c_1586x974.png 424w, https://substackcdn.com/image/fetch/$s_!kTeQ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F08ea564f-56f7-4b6d-bf42-5be6621dc93c_1586x974.png 848w, https://substackcdn.com/image/fetch/$s_!kTeQ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F08ea564f-56f7-4b6d-bf42-5be6621dc93c_1586x974.png 1272w, https://substackcdn.com/image/fetch/$s_!kTeQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F08ea564f-56f7-4b6d-bf42-5be6621dc93c_1586x974.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>And since we want the first message from the LLM to be something informative, we query it with this initial request:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!A9sd!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1b859e3a-78d6-497c-9b82-31dfb010bcf1_1586x936.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!A9sd!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1b859e3a-78d6-497c-9b82-31dfb010bcf1_1586x936.png 424w, https://substackcdn.com/image/fetch/$s_!A9sd!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1b859e3a-78d6-497c-9b82-31dfb010bcf1_1586x936.png 848w, https://substackcdn.com/image/fetch/$s_!A9sd!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1b859e3a-78d6-497c-9b82-31dfb010bcf1_1586x936.png 1272w, https://substackcdn.com/image/fetch/$s_!A9sd!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1b859e3a-78d6-497c-9b82-31dfb010bcf1_1586x936.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!A9sd!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1b859e3a-78d6-497c-9b82-31dfb010bcf1_1586x936.png" width="1456" height="859" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1b859e3a-78d6-497c-9b82-31dfb010bcf1_1586x936.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:859,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:119785,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!A9sd!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1b859e3a-78d6-497c-9b82-31dfb010bcf1_1586x936.png 424w, https://substackcdn.com/image/fetch/$s_!A9sd!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1b859e3a-78d6-497c-9b82-31dfb010bcf1_1586x936.png 848w, https://substackcdn.com/image/fetch/$s_!A9sd!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1b859e3a-78d6-497c-9b82-31dfb010bcf1_1586x936.png 1272w, https://substackcdn.com/image/fetch/$s_!A9sd!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1b859e3a-78d6-497c-9b82-31dfb010bcf1_1586x936.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>And finally, we take the user query and compute a reply:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Yoop!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6c06995-602e-4cf9-971f-e47ec06d7b71_1586x594.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Yoop!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6c06995-602e-4cf9-971f-e47ec06d7b71_1586x594.png 424w, https://substackcdn.com/image/fetch/$s_!Yoop!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6c06995-602e-4cf9-971f-e47ec06d7b71_1586x594.png 848w, https://substackcdn.com/image/fetch/$s_!Yoop!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6c06995-602e-4cf9-971f-e47ec06d7b71_1586x594.png 1272w, https://substackcdn.com/image/fetch/$s_!Yoop!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6c06995-602e-4cf9-971f-e47ec06d7b71_1586x594.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Yoop!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6c06995-602e-4cf9-971f-e47ec06d7b71_1586x594.png" width="1456" height="545" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e6c06995-602e-4cf9-971f-e47ec06d7b71_1586x594.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:545,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:66429,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Yoop!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6c06995-602e-4cf9-971f-e47ec06d7b71_1586x594.png 424w, https://substackcdn.com/image/fetch/$s_!Yoop!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6c06995-602e-4cf9-971f-e47ec06d7b71_1586x594.png 848w, https://substackcdn.com/image/fetch/$s_!Yoop!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6c06995-602e-4cf9-971f-e47ec06d7b71_1586x594.png 1272w, https://substackcdn.com/image/fetch/$s_!Yoop!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6c06995-602e-4cf9-971f-e47ec06d7b71_1586x594.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>And that&#8217;s it! In little less than 250 lines of code, you have a chatbot that can answer custom queries combining user data with a general knowledge base.</p><h2>Moving forward</h2><p>This simple example can be extended to any length for the FAQ guide (or any other content guide) and any complexity of the user data. </p><p>However, if the user data becomes too large, it won&#8217;t all fit in a single prompt. You&#8217;ll need to apply clever filtering to select the subset of data relevant to the query.</p><p>In this case, you can treat the user data as another document, split it into chunks, index it, and let the embeddings do their magic.</p><p>Other limitations that might give you ideas for improvement are:</p><ul><li><p>Only the last message is sent to the LLM, so even though the whole conversation is displayed constantly, every query is independent. That is, the chatbot has no access to the previous context. This is easy to fix by passing the last few messages on the call to <code>client.chat_stream(...)</code>.</p></li><li><p>There is no caching of queries, only embeddings. The same query will consume API calls every time it is used. This is relatively easy to fix by caching the query before submission, but you cannot simply use <code>st.cache_*</code> because the response is a stream of data objects consumed asynchronously, and the same query can have a different response per user/document.</p></li><li><p>Only three chunks are retrieved for each query, so the model will give an incorrect answer if the question requires a longer context.</p></li><li><p>There is no attempt to sanitize the input or output, so the model can behave erratically and exhibit biased and impolite replies if prompted with such intention.</p></li></ul><p></p><div><hr></div><p>And that&#8217;s all for today. As usual, let me know your opinions about these coding lessons. Are they too specific, too fast, too technical, too dull?</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://blog.apiad.net/p/customer-service-bot/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://blog.apiad.net/p/customer-service-bot/comments"><span>Leave a comment</span></a></p><p> And if you think this is worth it, consider leaving a like and sharing this post with a friend.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://blog.apiad.net/p/customer-service-bot?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://blog.apiad.net/p/customer-service-bot?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p>]]></content:encoded></item><item><title><![CDATA[Chat with your PDF]]></title><description><![CDATA[Build a simple streamlit app using LLMs and vector databases to answer arbitrary questions from a PDF document]]></description><link>https://blog.apiad.net/p/chat-with-your-pdf</link><guid isPermaLink="false">https://blog.apiad.net/p/chat-with-your-pdf</guid><dc:creator><![CDATA[Alejandro Piad Morffis]]></dc:creator><pubDate>Sat, 10 Feb 2024 15:44:47 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc2b621db-62a6-452b-b433-ccc0f5c4f0b3_1024x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Welcome to the first coding lesson in Mostly Harmless Ideas. This is an experimental section where I want to share very concrete, project-based coding lessons for all types of learners and all experience levels. I&#8217;m still figuring out the best format for these, so please let me know what works and what doesn&#8217;t for you.</p><p>Today&#8217;s lesson is about building a bare-bones app to answer arbitrary questions from a PDF file. We will use <a href="https://mistral.ai">mistral.ai</a> as the provider of language models, which has some incredibly powerful open-source LLMs<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a>.</p><p>Here&#8217;s a quick look at the final application, answering questions on the latest draft of my upcoming book <a href="https://blog.apiad.net/p/book-teaser-the-science-of-computation">The Science of Computation</a>.</p><div class="native-video-embed" data-component-name="VideoPlaceholder" data-attrs="{&quot;mediaUploadId&quot;:&quot;98b77e22-6c29-415d-b74e-8ac266fb8851&quot;,&quot;duration&quot;:null}"></div><p>The full source code (minus Mistral API keys) is <a href="https://github.com/apiad/pdf-chat">located here</a>.</p><blockquote><p><strong>Note: </strong>This post will be truncated in your email. View it <a href="https://blog.apiad.net/p/chat-with-your-pdf">online here</a>.</p></blockquote><h3>Prerequisites</h3><p>This lesson is somewhat advanced, so I expect you to be comfortable with basic Python and building simple apps in streamlit, although most of the streamlit code will be self-explanatory. </p><p>You must also know how to set up a basic Python project using a virtual environment and install dependencies from requirement files. If you need help with any of these topics, check out <span class="mention-wrap" data-attrs="{&quot;name&quot;:&quot;The Python Coding Stack &#8226; by Stephen Gruppetta&quot;,&quot;id&quot;:1563052,&quot;type&quot;:&quot;pub&quot;,&quot;url&quot;:&quot;https://open.substack.com/pub/thepythoncodingstack&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/374a6e1c-d5f8-4d72-878d-83ede874f0ee_600x600.png&quot;,&quot;uuid&quot;:&quot;e9116c6f-bda2-475d-9241-363ccac34dc1&quot;}" data-component-name="MentionToDOM"></span> and <span class="mention-wrap" data-attrs="{&quot;name&quot;:&quot;Mostly Python&quot;,&quot;id&quot;:1261763,&quot;type&quot;:&quot;pub&quot;,&quot;url&quot;:&quot;https://open.substack.com/pub/mostlypython&quot;,&quot;photo_url&quot;:&quot;https://bucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com/public/images/4ac4a977-0380-4396-8bbc-5215a0dbbb9b_500x500.png&quot;,&quot;uuid&quot;:&quot;4fea88e0-b51d-4fd7-9c8e-85619381018d&quot;}" data-component-name="MentionToDOM"></span> for plenty of beginner-friendly articles.</p><p>You <em>don&#8217;t need</em> prior knowledge of using LLMs or vector databases or building chat-based apps.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://blog.apiad.net/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Mostly Harmless Ideas is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h2>Making a <em>Chat with your PDF</em> app</h2><p>Since this lesson is for advanced readers, I won&#8217;t explain the code step by step. Instead, I will give you a very high-level description of the architecture of the app (which you will see is extremely simple), and then I will zoom into those implementation details that are crucial, leaving aside the boilerplate code.</p><p>My purpose is that you understand how each functionality is implemented and how they fit together, so when you read the actual code, everything clicks into place. For this reason, I won&#8217;t go line by line either but rather look at fundamental blocks in the order that makes understanding the app workflow easier.</p><p>At a high level, this is a typical streamlit application with a simple layout. It has a sidebar where the user can upload a PDF file, and the main content is a container for <a href="https://docs.streamlit.io/knowledge-base/tutorials/build-conversational-apps">chat UI elements</a>.</p><p>The workflow is as follows:</p><ul><li><p>The user uploads a PDF file.</p></li><li><p>Using the <a href="https://pypi.org/project/pypdf/">pypdf</a> module, we extract the plain text and split it into overlapping chunks.</p></li><li><p>Each chunk is transformed into a vector, using Mistral&#8217;s embeddings via the <a href="https://pypi.org/project/mistralai/">mistralai</a> module.</p></li><li><p>The embeddings are stored in a flat in-memory vector index using <a href="https://github.com/facebookresearch/faiss">faiss</a>.</p></li><li><p>Then, a query/response cycle is run, using one of Mistral&#8217;s LLMs, with a custom prompt that injects the most relevant chunk of the document for a given user query.</p></li></ul><p>But the devil is in the details, so let&#8217;s dive in.</p><h3>PDF processing</h3><p>Let&#8217;s start by analyzing the PDF processing step. Although this is almost the last piece of code written, it is the starting point that triggers the whole workflow.</p><p>As is typical in Streamlit, we use a widget to let the user upload a PDF file and store the resulting object in a global variable.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!9qQw!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25d27155-c67c-41f7-b3e2-e2198da924f7_1494x404.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!9qQw!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25d27155-c67c-41f7-b3e2-e2198da924f7_1494x404.png 424w, https://substackcdn.com/image/fetch/$s_!9qQw!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25d27155-c67c-41f7-b3e2-e2198da924f7_1494x404.png 848w, https://substackcdn.com/image/fetch/$s_!9qQw!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25d27155-c67c-41f7-b3e2-e2198da924f7_1494x404.png 1272w, https://substackcdn.com/image/fetch/$s_!9qQw!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25d27155-c67c-41f7-b3e2-e2198da924f7_1494x404.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!9qQw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25d27155-c67c-41f7-b3e2-e2198da924f7_1494x404.png" width="1456" height="394" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/25d27155-c67c-41f7-b3e2-e2198da924f7_1494x404.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:394,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:49186,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!9qQw!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25d27155-c67c-41f7-b3e2-e2198da924f7_1494x404.png 424w, https://substackcdn.com/image/fetch/$s_!9qQw!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25d27155-c67c-41f7-b3e2-e2198da924f7_1494x404.png 848w, https://substackcdn.com/image/fetch/$s_!9qQw!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25d27155-c67c-41f7-b3e2-e2198da924f7_1494x404.png 1272w, https://substackcdn.com/image/fetch/$s_!9qQw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25d27155-c67c-41f7-b3e2-e2198da924f7_1494x404.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>However, since we want to do batch processing with it, and Streamlit runs this script <em>every</em> <em>time something changes</em>, we will use a callback that is only invoked when a new file is uploaded via the <code>on_change</code> parameter. (Notice we name the widget with a custom <code>key</code>, this will be important soon.)</p><p>The <code>build_index</code> function gets called whenever the PDF widget changes, that is, when the user selects a new file.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!m4wY!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94afcedc-5323-445d-95fa-709b77db4c4c_1494x974.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!m4wY!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94afcedc-5323-445d-95fa-709b77db4c4c_1494x974.png 424w, https://substackcdn.com/image/fetch/$s_!m4wY!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94afcedc-5323-445d-95fa-709b77db4c4c_1494x974.png 848w, https://substackcdn.com/image/fetch/$s_!m4wY!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94afcedc-5323-445d-95fa-709b77db4c4c_1494x974.png 1272w, https://substackcdn.com/image/fetch/$s_!m4wY!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94afcedc-5323-445d-95fa-709b77db4c4c_1494x974.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!m4wY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94afcedc-5323-445d-95fa-709b77db4c4c_1494x974.png" width="1456" height="949" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/94afcedc-5323-445d-95fa-709b77db4c4c_1494x974.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:949,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:154305,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!m4wY!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94afcedc-5323-445d-95fa-709b77db4c4c_1494x974.png 424w, https://substackcdn.com/image/fetch/$s_!m4wY!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94afcedc-5323-445d-95fa-709b77db4c4c_1494x974.png 848w, https://substackcdn.com/image/fetch/$s_!m4wY!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94afcedc-5323-445d-95fa-709b77db4c4c_1494x974.png 1272w, https://substackcdn.com/image/fetch/$s_!m4wY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F94afcedc-5323-445d-95fa-709b77db4c4c_1494x974.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The first part of this function is straightforward. We take care of the case when there is no PDF uploaded (the use of <code>session_state.messages</code> will be clear later on), and then proceed to extract the text from the PDF.</p><p>Notice we obtain the PDF file from the <code>session_state.pdf_file</code> key, which is exactly the key we used when naming our PDF upload widget. This is one of the ways you can connect different widgets in Streamlit without using global variables.</p><p>The last two lines of this first part of the function store the extracted text in the session state dictionary for later use and output a helpful message indicating the size of the uploaded document. This is mostly so the user knows something is happening, as the whole indexing can be slow.</p><p>Next comes the actual indexing part.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!I-uN!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4806c13e-05a7-4ff1-b78c-6f54b8dc225a_1494x1202.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!I-uN!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4806c13e-05a7-4ff1-b78c-6f54b8dc225a_1494x1202.png 424w, https://substackcdn.com/image/fetch/$s_!I-uN!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4806c13e-05a7-4ff1-b78c-6f54b8dc225a_1494x1202.png 848w, https://substackcdn.com/image/fetch/$s_!I-uN!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4806c13e-05a7-4ff1-b78c-6f54b8dc225a_1494x1202.png 1272w, https://substackcdn.com/image/fetch/$s_!I-uN!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4806c13e-05a7-4ff1-b78c-6f54b8dc225a_1494x1202.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!I-uN!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4806c13e-05a7-4ff1-b78c-6f54b8dc225a_1494x1202.png" width="1456" height="1171" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4806c13e-05a7-4ff1-b78c-6f54b8dc225a_1494x1202.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1171,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:224559,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!I-uN!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4806c13e-05a7-4ff1-b78c-6f54b8dc225a_1494x1202.png 424w, https://substackcdn.com/image/fetch/$s_!I-uN!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4806c13e-05a7-4ff1-b78c-6f54b8dc225a_1494x1202.png 848w, https://substackcdn.com/image/fetch/$s_!I-uN!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4806c13e-05a7-4ff1-b78c-6f54b8dc225a_1494x1202.png 1272w, https://substackcdn.com/image/fetch/$s_!I-uN!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4806c13e-05a7-4ff1-b78c-6f54b8dc225a_1494x1202.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>First, we split the text into chunks of 2048 characters, with an overlap of 1024. This is the crudest way to split a text document into meaningful parts, but it works as a proof of concept. The overlap ensures that if we (almost certainly) split an important sentence in half in one chunk, we will get a second chance to store it in full in the next chunk.</p><p>After the chunks are created, we will compute an embedding for each. The <code>embed</code> function (which we will see in a minute) calls the Mistral API and returns a vector (in the form of a list of float numbers). We stack them all together using <code>numpy</code> and create a huge matrix that has one row per chunk.</p><p>Finally, we use <code>faiss</code>&#8217; <code>IndexFlatL2</code> class to store all the embeddings in an efficient data structure we can query later. We then store both the index object and the chunk list in the session state because we will need to recover the actual chunks using this list.</p><h3>Answering questions</h3><p>The question-answering part is the core of the application. We implement this functionality in a function <code>reply</code> that answers a single query. Then, we&#8217;ll see how this all fits together with the app workflow.</p><p>Here it is:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ejlr!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e87458e-48c7-400e-af29-7505df3bac46_1494x936.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ejlr!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e87458e-48c7-400e-af29-7505df3bac46_1494x936.png 424w, https://substackcdn.com/image/fetch/$s_!ejlr!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e87458e-48c7-400e-af29-7505df3bac46_1494x936.png 848w, https://substackcdn.com/image/fetch/$s_!ejlr!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e87458e-48c7-400e-af29-7505df3bac46_1494x936.png 1272w, https://substackcdn.com/image/fetch/$s_!ejlr!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e87458e-48c7-400e-af29-7505df3bac46_1494x936.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ejlr!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e87458e-48c7-400e-af29-7505df3bac46_1494x936.png" width="1456" height="912" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3e87458e-48c7-400e-af29-7505df3bac46_1494x936.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:912,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:160477,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ejlr!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e87458e-48c7-400e-af29-7505df3bac46_1494x936.png 424w, https://substackcdn.com/image/fetch/$s_!ejlr!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e87458e-48c7-400e-af29-7505df3bac46_1494x936.png 848w, https://substackcdn.com/image/fetch/$s_!ejlr!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e87458e-48c7-400e-af29-7505df3bac46_1494x936.png 1272w, https://substackcdn.com/image/fetch/$s_!ejlr!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3e87458e-48c7-400e-af29-7505df3bac46_1494x936.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The first two lines compute the query embedding, which we use to find the closest chunk in the index in the next two lines.</p><p>Then, we use the Mistral API to query the LLM with a single message (the user query), but &#8212;here is where the magic happens&#8212; that query is wrapped in a custom prompt that injects the most relevant chunk of text (according to the embeddings at least) along with some instructions for the LLM. </p><p>Here is that prompt:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!inRZ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87a87c20-991f-4eda-9ef2-9eb4e8eb2853_1494x746.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!inRZ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87a87c20-991f-4eda-9ef2-9eb4e8eb2853_1494x746.png 424w, https://substackcdn.com/image/fetch/$s_!inRZ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87a87c20-991f-4eda-9ef2-9eb4e8eb2853_1494x746.png 848w, https://substackcdn.com/image/fetch/$s_!inRZ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87a87c20-991f-4eda-9ef2-9eb4e8eb2853_1494x746.png 1272w, https://substackcdn.com/image/fetch/$s_!inRZ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87a87c20-991f-4eda-9ef2-9eb4e8eb2853_1494x746.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!inRZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87a87c20-991f-4eda-9ef2-9eb4e8eb2853_1494x746.png" width="1456" height="727" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/87a87c20-991f-4eda-9ef2-9eb4e8eb2853_1494x746.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:727,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:103447,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!inRZ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87a87c20-991f-4eda-9ef2-9eb4e8eb2853_1494x746.png 424w, https://substackcdn.com/image/fetch/$s_!inRZ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87a87c20-991f-4eda-9ef2-9eb4e8eb2853_1494x746.png 848w, https://substackcdn.com/image/fetch/$s_!inRZ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87a87c20-991f-4eda-9ef2-9eb4e8eb2853_1494x746.png 1272w, https://substackcdn.com/image/fetch/$s_!inRZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87a87c20-991f-4eda-9ef2-9eb4e8eb2853_1494x746.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Because LLMs are pretty insane, this actually works most of the time. The LLM will see a block of 2048 characters closely related to the query and will almost certainly manage to extract the relevant part from that blob of text to answer the query. </p><p>Two details are still missing from this part. One is the instantiation of the Mistral API client, which is pretty straightforward, provided you have the right API key in the Streamlit secrets file:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!I-UE!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09428da4-0539-4ec3-8ed0-515650fb8645_1494x442.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!I-UE!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09428da4-0539-4ec3-8ed0-515650fb8645_1494x442.png 424w, https://substackcdn.com/image/fetch/$s_!I-UE!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09428da4-0539-4ec3-8ed0-515650fb8645_1494x442.png 848w, https://substackcdn.com/image/fetch/$s_!I-UE!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09428da4-0539-4ec3-8ed0-515650fb8645_1494x442.png 1272w, https://substackcdn.com/image/fetch/$s_!I-UE!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09428da4-0539-4ec3-8ed0-515650fb8645_1494x442.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!I-UE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09428da4-0539-4ec3-8ed0-515650fb8645_1494x442.png" width="1456" height="431" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/09428da4-0539-4ec3-8ed0-515650fb8645_1494x442.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:431,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:64924,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!I-UE!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09428da4-0539-4ec3-8ed0-515650fb8645_1494x442.png 424w, https://substackcdn.com/image/fetch/$s_!I-UE!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09428da4-0539-4ec3-8ed0-515650fb8645_1494x442.png 848w, https://substackcdn.com/image/fetch/$s_!I-UE!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09428da4-0539-4ec3-8ed0-515650fb8645_1494x442.png 1272w, https://substackcdn.com/image/fetch/$s_!I-UE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09428da4-0539-4ec3-8ed0-515650fb8645_1494x442.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The <code>@st.cache_resource</code> decorator avoids creating a new API client on every run.</p><p>The second implementation detail missing is the embed method, which basically just calls the Mistral API. We add the <code>@st.cache_data</code> decorator to save us from recomputing embeddings for the same blocks of text over and over.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!UUqD!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff577bec8-2262-4883-a1f6-15f034ad3543_1494x442.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!UUqD!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff577bec8-2262-4883-a1f6-15f034ad3543_1494x442.png 424w, https://substackcdn.com/image/fetch/$s_!UUqD!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff577bec8-2262-4883-a1f6-15f034ad3543_1494x442.png 848w, https://substackcdn.com/image/fetch/$s_!UUqD!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff577bec8-2262-4883-a1f6-15f034ad3543_1494x442.png 1272w, https://substackcdn.com/image/fetch/$s_!UUqD!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff577bec8-2262-4883-a1f6-15f034ad3543_1494x442.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!UUqD!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff577bec8-2262-4883-a1f6-15f034ad3543_1494x442.png" width="1456" height="431" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f577bec8-2262-4883-a1f6-15f034ad3543_1494x442.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:431,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:56025,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!UUqD!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff577bec8-2262-4883-a1f6-15f034ad3543_1494x442.png 424w, https://substackcdn.com/image/fetch/$s_!UUqD!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff577bec8-2262-4883-a1f6-15f034ad3543_1494x442.png 848w, https://substackcdn.com/image/fetch/$s_!UUqD!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff577bec8-2262-4883-a1f6-15f034ad3543_1494x442.png 1272w, https://substackcdn.com/image/fetch/$s_!UUqD!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff577bec8-2262-4883-a1f6-15f034ad3543_1494x442.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h3>Chat management</h3><p>Now that we know how to reply to one user query, let&#8217;s see how the actual conversation workflow works. We need to solve two things: persist the conversation history despite Streamlit&#8217;s stateless execution model and simulate the typing animation. The following function takes care of that:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Um4r!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde032422-2f56-4823-87bb-41b58cd9af04_1494x746.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Um4r!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde032422-2f56-4823-87bb-41b58cd9af04_1494x746.png 424w, https://substackcdn.com/image/fetch/$s_!Um4r!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde032422-2f56-4823-87bb-41b58cd9af04_1494x746.png 848w, https://substackcdn.com/image/fetch/$s_!Um4r!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde032422-2f56-4823-87bb-41b58cd9af04_1494x746.png 1272w, https://substackcdn.com/image/fetch/$s_!Um4r!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde032422-2f56-4823-87bb-41b58cd9af04_1494x746.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Um4r!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde032422-2f56-4823-87bb-41b58cd9af04_1494x746.png" width="1456" height="727" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/de032422-2f56-4823-87bb-41b58cd9af04_1494x746.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:727,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:124394,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Um4r!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde032422-2f56-4823-87bb-41b58cd9af04_1494x746.png 424w, https://substackcdn.com/image/fetch/$s_!Um4r!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde032422-2f56-4823-87bb-41b58cd9af04_1494x746.png 848w, https://substackcdn.com/image/fetch/$s_!Um4r!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde032422-2f56-4823-87bb-41b58cd9af04_1494x746.png 1272w, https://substackcdn.com/image/fetch/$s_!Um4r!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde032422-2f56-4823-87bb-41b58cd9af04_1494x746.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The key pieces of this functionality are the following. We use <code>st.write_stream</code> instead of the typical <code>st.write</code> to simulate the typing animation. This method receives an iterator of printable stuff (e.g., characters). In the case of the actual LLM response, we will receive an iterable of responses. The following function (used in <code>reply</code>) unwraps the response content, turning the LLM stream into a simple string iterable.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!weEI!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F79e4937b-db54-407d-965f-472c18e18b42_1494x290.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!weEI!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F79e4937b-db54-407d-965f-472c18e18b42_1494x290.png 424w, https://substackcdn.com/image/fetch/$s_!weEI!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F79e4937b-db54-407d-965f-472c18e18b42_1494x290.png 848w, https://substackcdn.com/image/fetch/$s_!weEI!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F79e4937b-db54-407d-965f-472c18e18b42_1494x290.png 1272w, https://substackcdn.com/image/fetch/$s_!weEI!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F79e4937b-db54-407d-965f-472c18e18b42_1494x290.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!weEI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F79e4937b-db54-407d-965f-472c18e18b42_1494x290.png" width="1456" height="283" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/79e4937b-db54-407d-965f-472c18e18b42_1494x290.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:283,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:37747,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!weEI!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F79e4937b-db54-407d-965f-472c18e18b42_1494x290.png 424w, https://substackcdn.com/image/fetch/$s_!weEI!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F79e4937b-db54-407d-965f-472c18e18b42_1494x290.png 848w, https://substackcdn.com/image/fetch/$s_!weEI!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F79e4937b-db54-407d-965f-472c18e18b42_1494x290.png 1272w, https://substackcdn.com/image/fetch/$s_!weEI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F79e4937b-db54-407d-965f-472c18e18b42_1494x290.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>For our system messages (like the welcome screen), we must wrap that string in a function that yields each character at a time.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!MsN6!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F141af436-7def-413c-bc1a-6d21f6f44d35_1494x328.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!MsN6!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F141af436-7def-413c-bc1a-6d21f6f44d35_1494x328.png 424w, https://substackcdn.com/image/fetch/$s_!MsN6!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F141af436-7def-413c-bc1a-6d21f6f44d35_1494x328.png 848w, https://substackcdn.com/image/fetch/$s_!MsN6!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F141af436-7def-413c-bc1a-6d21f6f44d35_1494x328.png 1272w, https://substackcdn.com/image/fetch/$s_!MsN6!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F141af436-7def-413c-bc1a-6d21f6f44d35_1494x328.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!MsN6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F141af436-7def-413c-bc1a-6d21f6f44d35_1494x328.png" width="1456" height="320" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/141af436-7def-413c-bc1a-6d21f6f44d35_1494x328.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:320,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:40125,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!MsN6!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F141af436-7def-413c-bc1a-6d21f6f44d35_1494x328.png 424w, https://substackcdn.com/image/fetch/$s_!MsN6!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F141af436-7def-413c-bc1a-6d21f6f44d35_1494x328.png 848w, https://substackcdn.com/image/fetch/$s_!MsN6!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F141af436-7def-413c-bc1a-6d21f6f44d35_1494x328.png 1272w, https://substackcdn.com/image/fetch/$s_!MsN6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F141af436-7def-413c-bc1a-6d21f6f44d35_1494x328.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>The last part of the <code>add_message</code> function simply stores the message just streamed in a session state list so that, in the next execution, we can rewrite all previous messages and simulate a persistent conversation.</p><h3>Putting it all together</h3><p>Now that we understand all the puzzle pieces let&#8217;s see how they fit into the main application workflow. The first step is to initialize the session state variable to hold our chat history (if this is the first run) and then write that history back into the chat container every other run.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!TaJb!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80e27451-17a4-4e08-8400-7526eb7644f8_1494x594.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!TaJb!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80e27451-17a4-4e08-8400-7526eb7644f8_1494x594.png 424w, https://substackcdn.com/image/fetch/$s_!TaJb!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80e27451-17a4-4e08-8400-7526eb7644f8_1494x594.png 848w, https://substackcdn.com/image/fetch/$s_!TaJb!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80e27451-17a4-4e08-8400-7526eb7644f8_1494x594.png 1272w, https://substackcdn.com/image/fetch/$s_!TaJb!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80e27451-17a4-4e08-8400-7526eb7644f8_1494x594.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!TaJb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80e27451-17a4-4e08-8400-7526eb7644f8_1494x594.png" width="1456" height="579" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/80e27451-17a4-4e08-8400-7526eb7644f8_1494x594.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:579,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:108206,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!TaJb!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80e27451-17a4-4e08-8400-7526eb7644f8_1494x594.png 424w, https://substackcdn.com/image/fetch/$s_!TaJb!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80e27451-17a4-4e08-8400-7526eb7644f8_1494x594.png 848w, https://substackcdn.com/image/fetch/$s_!TaJb!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80e27451-17a4-4e08-8400-7526eb7644f8_1494x594.png 1272w, https://substackcdn.com/image/fetch/$s_!TaJb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80e27451-17a4-4e08-8400-7526eb7644f8_1494x594.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Next, we show an introductory message only if we haven&#8217;t done the PDF processing yet (remember we store the PDF text in the session state when we do). These two messages will not be stored in the chat history so they will be replaced once we upload the PDF.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!WiSb!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdffba2f4-598f-4f62-a753-7e325bef47ef_1494x936.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!WiSb!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdffba2f4-598f-4f62-a753-7e325bef47ef_1494x936.png 424w, https://substackcdn.com/image/fetch/$s_!WiSb!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdffba2f4-598f-4f62-a753-7e325bef47ef_1494x936.png 848w, https://substackcdn.com/image/fetch/$s_!WiSb!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdffba2f4-598f-4f62-a753-7e325bef47ef_1494x936.png 1272w, https://substackcdn.com/image/fetch/$s_!WiSb!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdffba2f4-598f-4f62-a753-7e325bef47ef_1494x936.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!WiSb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdffba2f4-598f-4f62-a753-7e325bef47ef_1494x936.png" width="1456" height="912" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/dffba2f4-598f-4f62-a753-7e325bef47ef_1494x936.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:912,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:183524,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!WiSb!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdffba2f4-598f-4f62-a753-7e325bef47ef_1494x936.png 424w, https://substackcdn.com/image/fetch/$s_!WiSb!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdffba2f4-598f-4f62-a753-7e325bef47ef_1494x936.png 848w, https://substackcdn.com/image/fetch/$s_!WiSb!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdffba2f4-598f-4f62-a753-7e325bef47ef_1494x936.png 1272w, https://substackcdn.com/image/fetch/$s_!WiSb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdffba2f4-598f-4f62-a753-7e325bef47ef_1494x936.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Nex comes the PDF upload widget we&#8217;ve already seen. Once we have a PDF uploaded, we take the user query.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!efu9!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F891ecece-7c4d-442e-8cb4-d98decf85730_1494x670.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!efu9!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F891ecece-7c4d-442e-8cb4-d98decf85730_1494x670.png 424w, https://substackcdn.com/image/fetch/$s_!efu9!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F891ecece-7c4d-442e-8cb4-d98decf85730_1494x670.png 848w, https://substackcdn.com/image/fetch/$s_!efu9!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F891ecece-7c4d-442e-8cb4-d98decf85730_1494x670.png 1272w, https://substackcdn.com/image/fetch/$s_!efu9!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F891ecece-7c4d-442e-8cb4-d98decf85730_1494x670.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!efu9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F891ecece-7c4d-442e-8cb4-d98decf85730_1494x670.png" width="1456" height="653" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/891ecece-7c4d-442e-8cb4-d98decf85730_1494x670.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:653,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:97661,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!efu9!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F891ecece-7c4d-442e-8cb4-d98decf85730_1494x670.png 424w, https://substackcdn.com/image/fetch/$s_!efu9!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F891ecece-7c4d-442e-8cb4-d98decf85730_1494x670.png 848w, https://substackcdn.com/image/fetch/$s_!efu9!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F891ecece-7c4d-442e-8cb4-d98decf85730_1494x670.png 1272w, https://substackcdn.com/image/fetch/$s_!efu9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F891ecece-7c4d-442e-8cb4-d98decf85730_1494x670.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>We also retrieve the IndexFlatL2 from the session state for use in the next fragment.</p><p>And finally, we send the query to the LLM to compute the reply. However, the first time, we will add a custom query that simply asks for a document summary. </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!E0An!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0994c867-4d58-4395-8805-1c4ada21f146_1494x594.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!E0An!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0994c867-4d58-4395-8805-1c4ada21f146_1494x594.png 424w, https://substackcdn.com/image/fetch/$s_!E0An!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0994c867-4d58-4395-8805-1c4ada21f146_1494x594.png 848w, https://substackcdn.com/image/fetch/$s_!E0An!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0994c867-4d58-4395-8805-1c4ada21f146_1494x594.png 1272w, https://substackcdn.com/image/fetch/$s_!E0An!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0994c867-4d58-4395-8805-1c4ada21f146_1494x594.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!E0An!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0994c867-4d58-4395-8805-1c4ada21f146_1494x594.png" width="1456" height="579" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0994c867-4d58-4395-8805-1c4ada21f146_1494x594.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:579,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:99409,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!E0An!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0994c867-4d58-4395-8805-1c4ada21f146_1494x594.png 424w, https://substackcdn.com/image/fetch/$s_!E0An!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0994c867-4d58-4395-8805-1c4ada21f146_1494x594.png 848w, https://substackcdn.com/image/fetch/$s_!E0An!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0994c867-4d58-4395-8805-1c4ada21f146_1494x594.png 1272w, https://substackcdn.com/image/fetch/$s_!E0An!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0994c867-4d58-4395-8805-1c4ada21f146_1494x594.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>Final remarks</h2><p>And that&#8217;s it! In a little more than 200 lines of code, you got yourself a fully functional, although simple, app to chat with an arbitrary PDF file. All thanks to the magic of LLMs and the simplicity of the <a href="https://streamlit.io">streamlit</a> framework,</p><p>There are some obvious limitations in this implementation, though. Here are some of the most relevant, which can give you ideas to improve it.</p><ul><li><p>Only the last message is sent to the LLM, so even though the whole conversation is displayed constantly, every query is independent. That is, the chatbot has no access to the previous context. This is easy to fix by passing the last few messages on the call to <code>client.chat_stream(...)</code>.</p></li><li><p>To save resources, documents with more than 100 chunks will error. This number can be changed in the source code.</p></li><li><p>There is no caching of queries, only embeddings. The same query will consume API calls every time it&#8217;s used. This is relatively easy to fix by caching the query before submission, but you cannot simply use <code>st.cache_*</code> because the response is a stream of data objects consumed asynchronously, and the same query can have a different response per user/document.</p></li><li><p>Only one chunk is retrieved for each query, so the model will answer incorrectly if the question requires a longer context.</p></li><li><p>There is no attempt to sanitize the input or output, so the model can behave erratically and exhibit biased and impolite replies if prompted with such intention.</p></li><li><p>All LLMs are subject to hallucinations, so always double-check your responses.</p></li></ul><div><hr></div><p>And now we&#8217;re done! Feel free to <a href="https://github.com/apiad/pdf-chat">clone and modify this project</a> to your needs, and please let me know if you enjoyed this lesson, especially if this format is engaging and informative for you. All feedback is appreciated!</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://blog.apiad.net/p/chat-with-your-pdf/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://blog.apiad.net/p/chat-with-your-pdf/comments"><span>Leave a comment</span></a></p><p>The lessons are 100% free but really hard to make. I would appreciate a like and a share if you think they're worth it :)</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://blog.apiad.net/p/chat-with-your-pdf?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://blog.apiad.net/p/chat-with-your-pdf?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p>I&#8217;m using Mistral&#8217;s hosted LLM service because running such a beefy model locally is pretty intense, and their prices are very competitive, but the fact that it&#8217;s open-source means you can decide tomorrow to download and run the model yourself.</p></div></div>]]></content:encoded></item></channel></rss>