<?xml version="1.0" encoding="UTF-8"?>
<rss  xmlns:atom="http://www.w3.org/2005/Atom" 
      xmlns:media="http://search.yahoo.com/mrss/" 
      xmlns:content="http://purl.org/rss/1.0/modules/content/" 
      xmlns:dc="http://purl.org/dc/elements/1.1/" 
      version="2.0">
<channel>
<title>Dylan Castillo</title>
<link>https://dylancastillo.co/</link>
<atom:link href="https://dylancastillo.co/index.xml" rel="self" type="application/rss+xml"/>
<description></description>
<image>
<url>https://dylancastillo.co/images/social_media_card.png</url>
<title>Dylan Castillo</title>
<link>https://dylancastillo.co/</link>
</image>
<generator>quarto-1.10.18</generator>
<lastBuildDate>Sat, 01 Aug 2026 00:00:00 GMT</lastBuildDate>
<item>
  <title>Honey, I shrunk the embeddings: Matryoshka vs. PCA</title>
  <dc:creator>Dylan Castillo</dc:creator>
  <link>https://dylancastillo.co/posts/matryoshka-vs-pca.html</link>
  <description><![CDATA[ 




<p>As people began using LLMs with their own documents, a new problem emerged: how do you store and search all that information efficiently?</p>
<p><a href="https://en.wikipedia.org/wiki/Vector_database">Vector databases</a> quickly became the standard solution. But vectors can contain thousands of dimensions, and storing millions of them can make retrieval slow and expensive.</p>
<p><a href="https://openai.com/index/new-embedding-models-and-api-updates/">AI labs</a> <a href="https://docs.cohere.com/changelog/embed-multimodal-v4">responded</a> with a technique called <a href="https://arxiv.org/abs/2205.13147">Matryoshka Representation Learning (MRL)</a>, which lets you use fewer embedding dimensions without sacrificing much accuracy in your retrieval. That means smaller vector database bills and faster queries.</p>
<p>Happy ending. Almost.</p>
<p>I do not usually spend my Wednesdays worrying about vector database bills. But Doug Turnbull’s <a href="https://softwaredoug.com/blog/2026/07/24/pca-shrink-ray">article</a> about using <a href="https://arxiv.org/abs/1404.1100">Principal Component Analysis (PCA)</a>, to reduce vector dimensions made me curious: how would this older, simpler technique compare with MRL?</p>
<p>To find out, I compared the two methods across eight standard retrieval-quality datasets. In this article, I walk through the experiment and share what I found.</p>
<p>All the code and data is available on <a href="https://github.com/dylanjcastillo/blog/tree/main/_extras/matryoshka-vs-pca">GitHub</a>.</p>
<section id="what-are-mrl-and-pca" class="level2">
<h2 class="anchored" data-anchor-id="what-are-mrl-and-pca">What are MRL and PCA?</h2>
<p>Both methods produce smaller vectors that behave almost like the full ones. But they get there in different ways.</p>
<p><strong>MRL</strong> works during training. You train the model with the loss applied at several prefix lengths at once: the first 64 dimensions, the first 128, and so on. This teaches it to pack the most important information at the start of the vector, like a set of nested matryoshka dolls.</p>
<p>At inference time, you simply keep the first <em>d</em> dimensions and re-normalize the resulting vector. Many modern embedding models are trained this way, but older models aren’t, so MRL-based truncation is not available for many popular embedding models.</p>
<p><strong>PCA</strong> works after training, which means it can be used with any model. You take a sample of embeddings, find the directions along which they vary the most, and keep the top <em>d</em> of them as a projection matrix<sup>1</sup>. PCA gives you smaller vectors, but also additional operational complexity. You need to store and version the PCA transformation, then apply the same version consistently when adding to and querying the index.</p>
</section>
<section id="how-i-ran-the-experiment" class="level2">
<h2 class="anchored" data-anchor-id="how-i-ran-the-experiment">How I ran the experiment</h2>
<p>The idea behind the experiment was simple: I would shrink the embeddings with each method, run the benchmarks, and see how much retrieval quality suffers at each size.</p>
<p>I generated all embeddings through OpenRouter and evaluated retrieval on eight <a href="https://github.com/beir-cellar/beir">BEIR</a> datasets: SciFact, NFCorpus, ArguAna, FiQA, SciDocs, Quora, TREC-COVID, and Webis-Touché 2020. For each dataset, I reduced the embeddings to 512, 256, 128, 64, and 32 dimensions.</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center collapsed" data-bs-toggle="collapse" data-bs-target=".callout-1-contents" aria-controls="callout-1" aria-expanded="false" aria-label="Toggle callout">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>Dataset details
</div>
<div class="callout-btn-toggle d-inline-block border-0 py-1 ps-1 pe-0 float-end"><i class="callout-toggle"></i></div>
</div>
<div id="callout-1" class="callout-1-contents callout-collapse collapse">
<div class="callout-body-container callout-body">
<div class="table-responsive">
<table class="table">
<thead>
<tr class="header">
<th>dataset</th>
<th>task</th>
<th>corpus</th>
<th>queries</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>SciFact</td>
<td>scientific claim verification</td>
<td>5.2K</td>
<td>300</td>
</tr>
<tr class="even">
<td>NFCorpus</td>
<td>medical search</td>
<td>3.6K</td>
<td>323</td>
</tr>
<tr class="odd">
<td>ArguAna</td>
<td>counterargument retrieval</td>
<td>8.7K</td>
<td>1,406</td>
</tr>
<tr class="even">
<td>FiQA-2018</td>
<td>financial question answering</td>
<td>57K</td>
<td>648</td>
</tr>
<tr class="odd">
<td>SciDocs</td>
<td>citation recommendation</td>
<td>25K</td>
<td>1,000</td>
</tr>
<tr class="even">
<td>Quora</td>
<td>duplicate question retrieval</td>
<td>523K</td>
<td>10,000</td>
</tr>
<tr class="odd">
<td>TREC-COVID</td>
<td>biomedical search (COVID-19)</td>
<td>171K</td>
<td>50</td>
</tr>
<tr class="even">
<td>Touché 2020</td>
<td>argument retrieval from web docs</td>
<td>382K</td>
<td>49</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<p>There were three questions I wanted to answer.</p>
<section id="q1-which-method-keeps-more-retrieval-quality-as-you-cut-dims" class="level4">
<h4 class="anchored" data-anchor-id="q1-which-method-keeps-more-retrieval-quality-as-you-cut-dims">Q1: which method keeps more retrieval quality as you cut dims?</h4>
<p>To answer this, I used two MRL-trained models: OpenAI’s <code>text-embedding-3-small</code> (1,536 dims), and Alibaba’s <code>qwen3-embedding-8b</code> (4,096 dims), which sits at the top of the open-weights MTEB BEIR leaderboard.</p>
<p>I reduced each model’s embeddings in two ways:</p>
<ol type="1">
<li>With <strong>truncation</strong>, I kept the first <em>d</em> dimensions and re-normalized. This is how you <a href="https://developers.openai.com/api/docs/guides/embeddings#reducing-embedding-dimensions">reduce dimensions</a> with MRL.</li>
<li>With <strong>PCA</strong>, I fit the projection on the full-dimension document embeddings of the same dataset I then searched, and kept the top <em>d</em> components. From then on, every document and query embedding gets multiplied by that projection matrix and re-normalized before searching.</li>
</ol>
<p>Then I ran the benchmarks on the eight datasets, comparing the two methods on both models, to see which one preserves more retrieval quality as the embeddings get smaller.</p>
</section>
<section id="q2-is-it-just-the-mrl-training" class="level4">
<h4 class="anchored" data-anchor-id="q2-is-it-just-the-mrl-training">Q2: is it just the MRL training?</h4>
<p>If PCA does well, that might be because MRL training has already organized the embedding space in a convenient way. To test that, I added <code>text-embedding-ada-002</code>, an older 1,536-dimensional model that wasn’t trained using MRL, as a control.</p>
<p>I applied PCA to both models. If PCA retains a similar share of retrieval quality on <code>text-embedding-3-small</code> and <code>text-embedding-ada-002</code>, that suggests its performance does not depend on MRL training.</p>
</section>
<section id="q3-does-the-fitting-data-matter" class="level4">
<h4 class="anchored" data-anchor-id="q3-does-the-fitting-data-matter">Q3: does the fitting data matter?</h4>
<p>PCA has to be fit on something, which raises two practical questions: how much fitting data do you need, and does it need to come from the corpus you’ll be searching?</p>
<p>For the first question, I fit PCA on random samples of FiQA’s 57K documents (1,000, 5,000, 20,000, and the full corpus) and checked how much the size of the fitting sample changes retrieval quality. This covers the scenario where you fit PCA on a sample of your data and never update it as the index grows.</p>
<p>For the second, I compared regular PCA against <strong>out-of-domain PCA</strong>: a projection fit once on 100,000 MS MARCO passages and reused, unchanged, on every other dataset. This is the most extreme version: you fit PCA on data that ends up looking quite different from what you actually index.</p>
</section>
</section>
<section id="but-first-a-sanity-check" class="level2">
<h2 class="anchored" data-anchor-id="but-first-a-sanity-check">But first, a sanity check</h2>
<p>Before running the experiment, I wanted to make sure the evaluation pipeline was producing sensible results. So I first tried to reproduce the official MTEB scores for the models.</p>
<p>The table below compares NDCG@10 at full dimensions: the official MTEB score vs.&nbsp;the score produced by my evaluation pipeline.</p>
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th rowspan="2" style="vertical-align: bottom;">
dataset
</th>
<th colspan="2" style="text-align: center;">
ada-002
</th>
<th colspan="2" style="text-align: center;">
3-small
</th>
<th colspan="2" style="text-align: center;">
qwen3-8b
</th>
</tr>
<tr>
<th>
MTEB
</th>
<th>
mine
</th>
<th>
MTEB
</th>
<th>
mine
</th>
<th>
MTEB
</th>
<th>
mine
</th>
</tr>
</thead>
<tbody>
<tr>
<td>
SciFact
</td>
<td>
0.7275
</td>
<td>
0.7277
</td>
<td>
0.7337
</td>
<td>
0.7296
</td>
<td>
0.7846
</td>
<td>
0.7863
</td>
</tr>
<tr>
<td>
NFCorpus
</td>
<td>
0.3697
</td>
<td>
0.3705
</td>
<td>
0.3833
</td>
<td>
0.3847
</td>
<td>
0.4145
</td>
<td>
0.4150
</td>
</tr>
<tr>
<td>
ArguAna
</td>
<td>
0.5744
</td>
<td>
0.5757
</td>
<td>
0.5549
</td>
<td>
0.5573
</td>
<td>
0.7685
</td>
<td>
0.7689
</td>
</tr>
<tr>
<td>
FiQA
</td>
<td>
0.4441
</td>
<td>
0.4440
</td>
<td>
0.4491
</td>
<td>
0.4484
</td>
<td>
0.6457
</td>
<td>
0.6492
</td>
</tr>
<tr>
<td>
SciDocs
</td>
<td>
0.1836
</td>
<td>
0.1837
</td>
<td>
0.2080
</td>
<td>
0.2077
</td>
<td>
0.3274
</td>
<td>
0.3268
</td>
</tr>
<tr>
<td>
Quora
</td>
<td>
0.8760
</td>
<td>
0.8759
</td>
<td>
0.8883
</td>
<td>
0.8880
</td>
<td>
0.8890
</td>
<td>
0.8901
</td>
</tr>
<tr>
<td>
TREC-COVID
</td>
<td>
0.6847
</td>
<td>
0.6884
</td>
<td>
0.7790
</td>
<td>
0.7775
</td>
<td>
0.9499
</td>
<td>
0.9492
</td>
</tr>
<tr>
<td>
Touché 2020
</td>
<td>
0.2161
</td>
<td>
0.2143
</td>
<td>
0.2428
</td>
<td>
0.2433
</td>
<td>
0.3593
</td>
<td>
0.3596
</td>
</tr>
</tbody>
</table>
</div>
<p>Official numbers come from the MTEB results repository (<a href="https://github.com/embeddings-benchmark/results/tree/main/results/openai__text-embedding-3-small">text-embedding-3-small</a>, <a href="https://github.com/embeddings-benchmark/results/tree/main/results/openai__text-embedding-ada-002">text-embedding-ada-002</a>, <a href="https://github.com/embeddings-benchmark/results/tree/main/results/Qwen__Qwen3-Embedding-8B">qwen3-embedding-8b</a>). My results are close enough to the official numbers that I’m confident the pipeline is working as expected.</p>
</section>
<section id="q1-which-method-keeps-more-retrieval-quality" class="level2">
<h2 class="anchored" data-anchor-id="q1-which-method-keeps-more-retrieval-quality">Q1: which method keeps more retrieval quality?</h2>
<p>PCA matched or outperformed MRL truncation at nearly every dimension across both MRL-trained models.</p>
<p>Each cell below shows how much retrieval quality survives the reduction: NDCG@10 at that dimension divided by NDCG@10 at full dimensions (1,536 for <code>text-embedding-3-small</code>, 4,096 for <code>qwen3-embedding-8b</code>), averaged over the eight datasets.</p>
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th rowspan="2" style="vertical-align: bottom;">
dims
</th>
<th colspan="2" style="text-align: center;">
3-small (MRL)
</th>
<th colspan="2" style="text-align: center;">
qwen3-8b (MRL)
</th>
</tr>
<tr>
<th>
Truncation
</th>
<th>
PCA
</th>
<th>
Truncation
</th>
<th>
PCA
</th>
</tr>
</thead>
<tbody>
<tr>
<td>
512
</td>
<td>
98%
</td>
<td>
97%
</td>
<td>
99%
</td>
<td>
98%
</td>
</tr>
<tr>
<td>
256
</td>
<td>
94%
</td>
<td>
95%
</td>
<td>
96%
</td>
<td>
96%
</td>
</tr>
<tr>
<td>
128
</td>
<td>
86%
</td>
<td>
90%
</td>
<td>
91%
</td>
<td>
91%
</td>
</tr>
<tr>
<td>
64
</td>
<td>
71%
</td>
<td>
82%
</td>
<td>
83%
</td>
<td>
84%
</td>
</tr>
<tr>
<td>
32
</td>
<td>
46%
</td>
<td>
65%
</td>
<td>
68%
</td>
<td>
71%
</td>
</tr>
</tbody>
</table>
</div>
<div class="tabset-margin-container"></div><div class="panel-tabset">
<ul class="nav nav-tabs"><li class="nav-item"><a class="nav-link active" id="tabset-1-1-tab" data-bs-toggle="tab" data-bs-target="#tabset-1-1" aria-controls="tabset-1-1" aria-selected="true" href="">3-small</a></li><li class="nav-item"><a class="nav-link" id="tabset-1-2-tab" data-bs-toggle="tab" data-bs-target="#tabset-1-2" aria-controls="tabset-1-2" aria-selected="false" href="">qwen3-8b</a></li></ul>
<div class="tab-content">
<div id="tabset-1-1" class="tab-pane active" aria-labelledby="tabset-1-1-tab">
<div id="a28c6ce8" class="cell" data-execution_count="2">
<div class="cell-output cell-output-display">
<div>            <script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-AMS-MML_SVG"></script><script type="text/javascript">if (window.MathJax && window.MathJax.Hub && window.MathJax.Hub.Config) {window.MathJax.Hub.Config({SVG: {font: "STIX-Web"}});}</script>                <script type="text/javascript">window.PlotlyConfig = {MathJaxConfig: 'local'};</script>
        <script charset="utf-8" src="https://cdn.plot.ly/plotly-3.0.1.min.js"></script>                <div id="0dae83c5-f82c-4e16-9595-e2ec4990371f" class="plotly-graph-div" style="height:440px; width:100%;"></div>            <script type="text/javascript">                window.PLOTLYENV=window.PLOTLYENV || {};                                if (document.getElementById("0dae83c5-f82c-4e16-9595-e2ec4990371f")) {                    Plotly.newPlot(                        "0dae83c5-f82c-4e16-9595-e2ec4990371f",                        [{"customdata":{"dtype":"f8","bdata":"Xt1g3oyC0T+yUPaySrbZP07IxtLE3N0\u002fzmjm3uUR4D\u002fArmYScLTgPw=="},"hovertemplate":"\u003cb\u003eMatryoshka (truncate + renorm)\u003c\u002fb\u003e\u003cbr\u003ed=%{x}\u003cbr\u003eretention=%{y:.1%}\u003cbr\u003eavg ndcg@10=%{customdata:.4f}\u003cextra\u003e\u003c\u002fextra\u003e","legendgroup":"mrl","line":{"color":"#eb841b","dash":"solid","width":2},"marker":{"color":"#eb841b","size":6},"mode":"lines+markers","name":"Matryoshka (truncate + renorm)","showlegend":true,"x":{"dtype":"i2","bdata":"IABAAIAAAAEAAg=="},"y":{"dtype":"f8","bdata":"oi6fIJVs3T8xbiHgPLjmPzAR+7pWYes\u002fImHwkv4X7j8qP67aBGvvPw=="},"type":"scatter","xaxis":"x","yaxis":"y"},{"customdata":{"dtype":"f8","bdata":"OSkgpFwr1z89KVLsQ2TcPwJcQq2N7t4\u002f1hQEABsq4D958zZbDnDgPw=="},"hovertemplate":"\u003cb\u003ePCA\u003c\u002fb\u003e\u003cbr\u003ed=%{x}\u003cbr\u003eretention=%{y:.1%}\u003cbr\u003eavg ndcg@10=%{customdata:.4f}\u003cextra\u003e\u003c\u002fextra\u003e","legendgroup":"pca_in","line":{"color":"#60a5fa","dash":"solid","width":2},"marker":{"color":"#60a5fa","size":6},"mode":"lines+markers","name":"PCA","showlegend":true,"x":{"dtype":"i2","bdata":"IABAAIAAAAEAAg=="},"y":{"dtype":"f8","bdata":"9uFdmqun5D\u002fqI1FNXRzqP6uW0S+yyew\u002fR5mMooVe7j8eQ\u002fJzcvjuPw=="},"type":"scatter","xaxis":"x","yaxis":"y"}],                        {"template":{"data":{"scatter":[{"type":"scatter"}]},"layout":{"margin":{"b":0,"l":0,"r":0,"t":30}}},"xaxis":{"anchor":"y","domain":[0.0,1.0],"gridcolor":"#3f3f46","zeroline":false,"linecolor":"#3f3f46","tickcolor":"#3f3f46","automargin":true,"title":{"font":{"size":12,"color":"#a1a1aa"},"text":"dimensions"},"type":"log","tickvals":[32,64,128,256,512]},"yaxis":{"anchor":"x","domain":[0.0,1.0],"gridcolor":"#3f3f46","zeroline":false,"linecolor":"#3f3f46","tickcolor":"#3f3f46","automargin":true,"tickformat":".0%"},"annotations":[{"font":{"color":"#e4e4e7","size":13},"showarrow":false,"text":"3-small (MRL)","x":0.5,"xanchor":"center","xref":"paper","y":1.0,"yanchor":"bottom","yref":"paper"}],"shapes":[{"line":{"color":"#a1a1aa","dash":"dash","width":1},"type":"line","x0":0,"x1":1,"xref":"x domain","y0":1.0,"y1":1.0,"yref":"y"}],"title":{"font":{"size":15,"color":"#e4e4e7"},"text":"Share of full-dim NDCG@10 retained (mean of 8 datasets)","x":0.5,"xanchor":"center"},"font":{"family":"Georgia, serif","size":13,"color":"#e4e4e7"},"margin":{"l":10,"r":10,"t":60,"b":40},"legend":{"font":{"size":12,"color":"#a1a1aa"},"orientation":"h","yanchor":"bottom","y":-0.25,"x":0.5,"xanchor":"center"},"hoverlabel":{"font":{"family":"Georgia, serif","size":12,"color":"#e4e4e7"},"bgcolor":"#27272a","bordercolor":"#eb841b","align":"left"},"paper_bgcolor":"#18181b","plot_bgcolor":"#18181b","height":440,"showlegend":true,"dragmode":false},                        {"displayModeBar": false, "responsive": true}                    ).then(function(){
                            
var gd = document.getElementById('0dae83c5-f82c-4e16-9595-e2ec4990371f');
var x = new MutationObserver(function (mutations, observer) {{
        var display = window.getComputedStyle(gd).display;
        if (!display || display === 'none') {{
            console.log([gd, 'removed!']);
            Plotly.purge(gd);
            observer.disconnect();
        }}
}});

// Listen for the removal of the full notebook cells
var notebookContainer = gd.closest('#notebook-container');
if (notebookContainer) {{
    x.observe(notebookContainer, {childList: true});
}}

// Listen for the clearing of the current output cell
var outputEl = gd.closest('.output');
if (outputEl) {{
    x.observe(outputEl, {childList: true});
}}

                        })                };            </script>        </div>
</div>
</div>
</div>
<div id="tabset-1-2" class="tab-pane" aria-labelledby="tabset-1-2-tab">
<div id="aaee4d2a" class="cell" data-execution_count="3">
<div class="cell-output cell-output-display">
<div>            <script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-AMS-MML_SVG"></script><script type="text/javascript">if (window.MathJax && window.MathJax.Hub && window.MathJax.Hub.Config) {window.MathJax.Hub.Config({SVG: {font: "STIX-Web"}});}</script>                <script type="text/javascript">window.PlotlyConfig = {MathJaxConfig: 'local'};</script>
        <script charset="utf-8" src="https://cdn.plot.ly/plotly-3.0.1.min.js"></script>                <div id="020bfe9b-349e-450e-a676-77579218962b" class="plotly-graph-div" style="height:440px; width:100%;"></div>            <script type="text/javascript">                window.PLOTLYENV=window.PLOTLYENV || {};                                if (document.getElementById("020bfe9b-349e-450e-a676-77579218962b")) {                    Plotly.newPlot(                        "020bfe9b-349e-450e-a676-77579218962b",                        [{"customdata":{"dtype":"f8","bdata":"YTpP264v3T8cC3DzlYvhPyFw6o4uAOM\u002fLrKtvzLj4z9Ut8A7CWDkPw=="},"hovertemplate":"\u003cb\u003eMatryoshka (truncate + renorm)\u003c\u002fb\u003e\u003cbr\u003ed=%{x}\u003cbr\u003eretention=%{y:.1%}\u003cbr\u003eavg ndcg@10=%{customdata:.4f}\u003cextra\u003e\u003c\u002fextra\u003e","legendgroup":"mrl","line":{"color":"#eb841b","dash":"solid","width":2},"marker":{"color":"#eb841b","size":6},"mode":"lines+markers","name":"Matryoshka (truncate + renorm)","showlegend":true,"x":{"dtype":"i2","bdata":"IABAAIAAAAEAAg=="},"y":{"dtype":"f8","bdata":"URkCcmqz5T8p7GFAzYHqP6t+ThwJCe0\u002fpDxf\u002foie7j+ZXbWIPZvvPw=="},"type":"scatter","xaxis":"x","yaxis":"y"},{"customdata":{"dtype":"f8","bdata":"XiK4xTJz3j\u002fJ2DUWC7\u002fhP7aeq6kRFeM\u002fq2nZciXO4z+7MWLhnCrkPw=="},"hovertemplate":"\u003cb\u003ePCA\u003c\u002fb\u003e\u003cbr\u003ed=%{x}\u003cbr\u003eretention=%{y:.1%}\u003cbr\u003eavg ndcg@10=%{customdata:.4f}\u003cextra\u003e\u003c\u002fextra\u003e","legendgroup":"pca_in","line":{"color":"#60a5fa","dash":"solid","width":2},"marker":{"color":"#60a5fa","size":6},"mode":"lines+markers","name":"PCA","showlegend":true,"x":{"dtype":"i2","bdata":"IABAAIAAAAEAAg=="},"y":{"dtype":"f8","bdata":"av0iIq\u002fJ5j+D2UvB6tjqP9acApe+Pu0\u002f124jygCY7j9cT5SHijjvPw=="},"type":"scatter","xaxis":"x","yaxis":"y"}],                        {"template":{"data":{"scatter":[{"type":"scatter"}]},"layout":{"margin":{"b":0,"l":0,"r":0,"t":30}}},"xaxis":{"anchor":"y","domain":[0.0,1.0],"gridcolor":"#3f3f46","zeroline":false,"linecolor":"#3f3f46","tickcolor":"#3f3f46","automargin":true,"title":{"font":{"size":12,"color":"#a1a1aa"},"text":"dimensions"},"type":"log","tickvals":[32,64,128,256,512]},"yaxis":{"anchor":"x","domain":[0.0,1.0],"gridcolor":"#3f3f46","zeroline":false,"linecolor":"#3f3f46","tickcolor":"#3f3f46","automargin":true,"tickformat":".0%"},"annotations":[{"font":{"color":"#e4e4e7","size":13},"showarrow":false,"text":"qwen3-8b (MRL)","x":0.5,"xanchor":"center","xref":"paper","y":1.0,"yanchor":"bottom","yref":"paper"}],"shapes":[{"line":{"color":"#a1a1aa","dash":"dash","width":1},"type":"line","x0":0,"x1":1,"xref":"x domain","y0":1.0,"y1":1.0,"yref":"y"}],"title":{"font":{"size":15,"color":"#e4e4e7"},"text":"Share of full-dim NDCG@10 retained (mean of 8 datasets)","x":0.5,"xanchor":"center"},"font":{"family":"Georgia, serif","size":13,"color":"#e4e4e7"},"margin":{"l":10,"r":10,"t":60,"b":40},"legend":{"font":{"size":12,"color":"#a1a1aa"},"orientation":"h","yanchor":"bottom","y":-0.25,"x":0.5,"xanchor":"center"},"hoverlabel":{"font":{"family":"Georgia, serif","size":12,"color":"#e4e4e7"},"bgcolor":"#27272a","bordercolor":"#eb841b","align":"left"},"paper_bgcolor":"#18181b","plot_bgcolor":"#18181b","height":440,"showlegend":true,"dragmode":false},                        {"displayModeBar": false, "responsive": true}                    ).then(function(){
                            
var gd = document.getElementById('020bfe9b-349e-450e-a676-77579218962b');
var x = new MutationObserver(function (mutations, observer) {{
        var display = window.getComputedStyle(gd).display;
        if (!display || display === 'none') {{
            console.log([gd, 'removed!']);
            Plotly.purge(gd);
            observer.disconnect();
        }}
}});

// Listen for the removal of the full notebook cells
var notebookContainer = gd.closest('#notebook-container');
if (notebookContainer) {{
    x.observe(notebookContainer, {childList: true});
}}

// Listen for the clearing of the current output cell
var outputEl = gd.closest('.output');
if (outputEl) {{
    x.observe(outputEl, {childList: true});
}}

                        })                };            </script>        </div>
</div>
</div>
</div>
</div>
</div>
<p>At 512 dimensions, MRL truncation has a slight advantage. But as the vectors get smaller, PCA catches up and either beats or matches MRL.</p>
<p>On <code>text-embedding-3-small</code>, PCA is clearly better below 256 dims (65% vs 46% retained at 32). On <code>qwen3-embedding-8b</code> the race is much closer (71% vs 68% at 32).</p>
<p>One thing I found interesting is that the MRL training in <code>qwen3-embedding-8b</code> is strong enough that its truncated 32-dim vectors, cut from 4,096 dimensions, retain more quality than 3-small’s cut from 1,536.</p>
<p>Going dataset by dataset, you find some differences:</p>
<div class="tabset-margin-container"></div><div class="panel-tabset">
<ul class="nav nav-tabs"><li class="nav-item"><a class="nav-link active" id="tabset-2-1-tab" data-bs-toggle="tab" data-bs-target="#tabset-2-1" aria-controls="tabset-2-1" aria-selected="true" href="">3-small</a></li><li class="nav-item"><a class="nav-link" id="tabset-2-2-tab" data-bs-toggle="tab" data-bs-target="#tabset-2-2" aria-controls="tabset-2-2" aria-selected="false" href="">qwen3-8b</a></li></ul>
<div class="tab-content">
<div id="tabset-2-1" class="tab-pane active" aria-labelledby="tabset-2-1-tab">
<div id="754f1b6f" class="cell" data-execution_count="4">
<div class="cell-output cell-output-display">
<div>            <script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-AMS-MML_SVG"></script><script type="text/javascript">if (window.MathJax && window.MathJax.Hub && window.MathJax.Hub.Config) {window.MathJax.Hub.Config({SVG: {font: "STIX-Web"}});}</script>                <script type="text/javascript">window.PlotlyConfig = {MathJaxConfig: 'local'};</script>
        <script charset="utf-8" src="https://cdn.plot.ly/plotly-3.0.1.min.js"></script>                <div id="a05a1a75-2da1-422e-8089-0d45e4d4d93a" class="plotly-graph-div" style="height:1160px; width:100%;"></div>            <script type="text/javascript">                window.PLOTLYENV=window.PLOTLYENV || {};                                if (document.getElementById("a05a1a75-2da1-422e-8089-0d45e4d4d93a")) {                    Plotly.newPlot(                        "a05a1a75-2da1-422e-8089-0d45e4d4d93a",                        [{"customdata":{"dtype":"f8","bdata":"dd+OtS7m1T+pq0y1tnHdP2QOkzzKQOA\u002fHj1BwQpB4T+hpU0G5KzhPw=="},"hovertemplate":"\u003cb\u003eMatryoshka (truncate + renorm)\u003c\u002fb\u003e\u003cbr\u003ed=%{x}\u003cbr\u003eretention=%{y:.1%}\u003cbr\u003endcg@10=%{customdata:.4f}\u003cextra\u003e\u003c\u002fextra\u003e","legendgroup":"mrl","line":{"color":"#eb841b","dash":"solid","width":2},"marker":{"color":"#eb841b","size":6},"mode":"lines+markers","name":"Matryoshka (truncate + renorm)","showlegend":true,"x":{"dtype":"i2","bdata":"IABAAIAAAAEAAg=="},"y":{"dtype":"f8","bdata":"3kFXQqql4z9qFD28kGrqP5qTm2zSKe0\u002fnZBbRZ\u002f17j+hx0pbI7fvPw=="},"type":"scatter","xaxis":"x","yaxis":"y"},{"customdata":{"dtype":"f8","bdata":"QpeUpqWS2T+BHuU4tVDfP3F9n\u002frI++A\u002fNkuhNyC04T8qxMOz6uvhPw=="},"hovertemplate":"\u003cb\u003ePCA\u003c\u002fb\u003e\u003cbr\u003ed=%{x}\u003cbr\u003eretention=%{y:.1%}\u003cbr\u003endcg@10=%{customdata:.4f}\u003cextra\u003e\u003c\u002fextra\u003e","legendgroup":"pca_in","line":{"color":"#60a5fa","dash":"solid","width":2},"marker":{"color":"#60a5fa","size":6},"mode":"lines+markers","name":"PCA","showlegend":true,"x":{"dtype":"i2","bdata":"IABAAIAAAAEAAg=="},"y":{"dtype":"f8","bdata":"6TeTg2rx5j9gv4s9TRjsP8wc1Dxaee4\u002f5Y48zB7E7z9nDFMhHRTwPw=="},"type":"scatter","xaxis":"x","yaxis":"y"},{"customdata":{"dtype":"f8","bdata":"xDIkt+4fvD\u002fZAJeOtQXOP9MZtHl+yNU\u002f8CnXV8Gy2T+rxt3rkenbPw=="},"hovertemplate":"\u003cb\u003eMatryoshka (truncate + renorm)\u003c\u002fb\u003e\u003cbr\u003ed=%{x}\u003cbr\u003eretention=%{y:.1%}\u003cbr\u003endcg@10=%{customdata:.4f}\u003cextra\u003e\u003c\u002fextra\u003e","legendgroup":"mrl","line":{"color":"#eb841b","dash":"solid","width":2},"marker":{"color":"#eb841b","size":6},"mode":"lines+markers","name":"Matryoshka (truncate + renorm)","showlegend":false,"x":{"dtype":"i2","bdata":"IABAAIAAAAEAAg=="},"y":{"dtype":"f8","bdata":"9k2k60Vdzz8UK9GogL3gP+LopNHVSug\u002faooJT4uo7D\u002fqirkWpiDvPw=="},"type":"scatter","xaxis":"x2","yaxis":"y2"},{"customdata":{"dtype":"f8","bdata":"qFg0DV5CzT8K0k6\u002fIT\u002fVP0xk\u002fMrndNg\u002fiFZCASvK2j9RDoCYiK7bPw=="},"hovertemplate":"\u003cb\u003ePCA\u003c\u002fb\u003e\u003cbr\u003ed=%{x}\u003cbr\u003eretention=%{y:.1%}\u003cbr\u003endcg@10=%{customdata:.4f}\u003cextra\u003e\u003c\u002fextra\u003e","legendgroup":"pca_in","line":{"color":"#60a5fa","dash":"solid","width":2},"marker":{"color":"#60a5fa","size":6},"mode":"lines+markers","name":"PCA","showlegend":false,"x":{"dtype":"i2","bdata":"IABAAIAAAAEAAg=="},"y":{"dtype":"f8","bdata":"wQ7eypRQ4D8mEbiPprHnP+mCcwUVRus\u002f7RMcRyTg7T+RunXmz97uPw=="},"type":"scatter","xaxis":"x2","yaxis":"y2"},{"customdata":{"dtype":"f8","bdata":"Nkb+Oek1wT8+\u002fRwMtofMP00V9Hv1GtM\u002fRJv1oQ2w1j+mhNLrMdzXPw=="},"hovertemplate":"\u003cb\u003eMatryoshka (truncate + renorm)\u003c\u002fb\u003e\u003cbr\u003ed=%{x}\u003cbr\u003eretention=%{y:.1%}\u003cbr\u003endcg@10=%{customdata:.4f}\u003cextra\u003e\u003c\u002fextra\u003e","legendgroup":"mrl","line":{"color":"#eb841b","dash":"solid","width":2},"marker":{"color":"#eb841b","size":6},"mode":"lines+markers","name":"Matryoshka (truncate + renorm)","showlegend":false,"x":{"dtype":"i2","bdata":"IABAAIAAAAEAAg=="},"y":{"dtype":"f8","bdata":"RWEGzBhf1j\u002f+Bovf7IriPycstCyY1eg\u002fxwC83LF97T9cDbjK1gPvPw=="},"type":"scatter","xaxis":"x3","yaxis":"y3"},{"customdata":{"dtype":"f8","bdata":"E0AFQ+sczz9wCVrBq7LTPzpatDdT2tU\u002fxFclMAPX1j+57bypFHDXPw=="},"hovertemplate":"\u003cb\u003ePCA\u003c\u002fb\u003e\u003cbr\u003ed=%{x}\u003cbr\u003eretention=%{y:.1%}\u003cbr\u003endcg@10=%{customdata:.4f}\u003cextra\u003e\u003c\u002fextra\u003e","legendgroup":"pca_in","line":{"color":"#60a5fa","dash":"solid","width":2},"marker":{"color":"#60a5fa","size":6},"mode":"lines+markers","name":"PCA","showlegend":false,"x":{"dtype":"i2","bdata":"IABAAIAAAAEAAg=="},"y":{"dtype":"f8","bdata":"0v3Lk6o45D9LzI+\u002fzJrpP2rwGWvgZ+w\u002fcoiGJ1aw7T9yyPsLTnfuPw=="},"type":"scatter","xaxis":"x3","yaxis":"y3"},{"customdata":{"dtype":"f8","bdata":"14aoR5lC6T\u002fv+TO7VmHrP1nuxVcF+us\u002faAGkG+A87D9Sy6aGa1vsPw=="},"hovertemplate":"\u003cb\u003eMatryoshka (truncate + renorm)\u003c\u002fb\u003e\u003cbr\u003ed=%{x}\u003cbr\u003eretention=%{y:.1%}\u003cbr\u003endcg@10=%{customdata:.4f}\u003cextra\u003e\u003c\u002fextra\u003e","legendgroup":"mrl","line":{"color":"#eb841b","dash":"solid","width":2},"marker":{"color":"#eb841b","size":6},"mode":"lines+markers","name":"Matryoshka (truncate + renorm)","showlegend":false,"x":{"dtype":"i2","bdata":"IABAAIAAAAEAAg=="},"y":{"dtype":"f8","bdata":"wWZRVVZy7D\u002flpHnBitXuPyTrNgF8ge8\u002fPpabvMXM7z8m\u002f+2LK+\u002fvPw=="},"type":"scatter","xaxis":"x4","yaxis":"y4"},{"customdata":{"dtype":"f8","bdata":"6AoM56Au6D8\u002fFiwBhobqP7xEmwz9qes\u002fwaK4mt0c7D8QlaVulVfsPw=="},"hovertemplate":"\u003cb\u003ePCA\u003c\u002fb\u003e\u003cbr\u003ed=%{x}\u003cbr\u003eretention=%{y:.1%}\u003cbr\u003endcg@10=%{customdata:.4f}\u003cextra\u003e\u003c\u002fextra\u003e","legendgroup":"pca_in","line":{"color":"#60a5fa","dash":"solid","width":2},"marker":{"color":"#60a5fa","size":6},"mode":"lines+markers","name":"PCA","showlegend":false,"x":{"dtype":"i2","bdata":"IABAAIAAAAEAAg=="},"y":{"dtype":"f8","bdata":"3R\u002fq6Y076z86Vb63H9\u002ftP\u002fHGgS9bJ+8\u002fvuKNhrmo7z9RHLCQ2ervPw=="},"type":"scatter","xaxis":"x4","yaxis":"y4"},{"customdata":{"dtype":"f8","bdata":"m70IPCgOsD\u002fS\u002fQWYpVa8PxvcY7ZjwMM\u002fr4RUnY43xz+6lTAw\u002f4rJPw=="},"hovertemplate":"\u003cb\u003eMatryoshka (truncate + renorm)\u003c\u002fb\u003e\u003cbr\u003ed=%{x}\u003cbr\u003eretention=%{y:.1%}\u003cbr\u003endcg@10=%{customdata:.4f}\u003cextra\u003e\u003c\u002fextra\u003e","legendgroup":"mrl","line":{"color":"#eb841b","dash":"solid","width":2},"marker":{"color":"#eb841b","size":6},"mode":"lines+markers","name":"Matryoshka (truncate + renorm)","showlegend":false,"x":{"dtype":"i2","bdata":"IABAAIAAAAEAAg=="},"y":{"dtype":"f8","bdata":"hltNhcNT0z9Ei\u002feZiA7hP7GeVwfUxuc\u002f9wEajMry6z\u002fAOa1Ek7\u002fuPw=="},"type":"scatter","xaxis":"x5","yaxis":"y5"},{"customdata":{"dtype":"f8","bdata":"4kJ2+FaMvj8xngEqbUrEPwg7zuNh2sY\u002fXV4bVIUbyT\u002f4cAx01\u002fDJPw=="},"hovertemplate":"\u003cb\u003ePCA\u003c\u002fb\u003e\u003cbr\u003ed=%{x}\u003cbr\u003eretention=%{y:.1%}\u003cbr\u003endcg@10=%{customdata:.4f}\u003cextra\u003e\u003c\u002fextra\u003e","legendgroup":"pca_in","line":{"color":"#60a5fa","dash":"solid","width":2},"marker":{"color":"#60a5fa","size":6},"mode":"lines+markers","name":"PCA","showlegend":false,"x":{"dtype":"i2","bdata":"IABAAIAAAAEAAg=="},"y":{"dtype":"f8","bdata":"huBFagVj4j\u002fcxCLh\u002fmzoP5gir+Oggus\u002f8G3EsWE57j+j7A7JLDrvPw=="},"type":"scatter","xaxis":"x5","yaxis":"y5"},{"customdata":{"dtype":"f8","bdata":"FLhQAAfe1j\u002fLOt+a4HzgP\u002feO5En3yuM\u002fHEO8mPwk5j\u002fGx16svQTnPw=="},"hovertemplate":"\u003cb\u003eMatryoshka (truncate + renorm)\u003c\u002fb\u003e\u003cbr\u003ed=%{x}\u003cbr\u003eretention=%{y:.1%}\u003cbr\u003endcg@10=%{customdata:.4f}\u003cextra\u003e\u003c\u002fextra\u003e","legendgroup":"mrl","line":{"color":"#eb841b","dash":"solid","width":2},"marker":{"color":"#eb841b","size":6},"mode":"lines+markers","name":"Matryoshka (truncate + renorm)","showlegend":false,"x":{"dtype":"i2","bdata":"IABAAIAAAAEAAg=="},"y":{"dtype":"f8","bdata":"EASMaR9X3z\u002fCLhxH15jmP8PYNF5uIOs\u002fbqSi1IRZ7j\u002fmHUVKLozvPw=="},"type":"scatter","xaxis":"x6","yaxis":"y6"},{"customdata":{"dtype":"f8","bdata":"C73T\u002fRfs4T8uWS+82ofkPz4xQw3fNuY\u002f1K4hnZPE5j8jB4Te4jnnPw=="},"hovertemplate":"\u003cb\u003ePCA\u003c\u002fb\u003e\u003cbr\u003ed=%{x}\u003cbr\u003eretention=%{y:.1%}\u003cbr\u003endcg@10=%{customdata:.4f}\u003cextra\u003e\u003c\u002fextra\u003e","legendgroup":"pca_in","line":{"color":"#60a5fa","dash":"solid","width":2},"marker":{"color":"#60a5fa","size":6},"mode":"lines+markers","name":"PCA","showlegend":false,"x":{"dtype":"i2","bdata":"IABAAIAAAAEAAg=="},"y":{"dtype":"f8","bdata":"069WQx+Q6D+aosH6TiPsPyvJKb4Hcu4\u002fSLMd4D007z+vDhKgBNXvPw=="},"type":"scatter","xaxis":"x6","yaxis":"y6"},{"customdata":{"dtype":"f8","bdata":"glqvQoW30j9YYB+j0mbkP8YRUfiyEec\u002fyrJEMcw35z8h\u002fIaCY6LoPw=="},"hovertemplate":"\u003cb\u003eMatryoshka (truncate + renorm)\u003c\u002fb\u003e\u003cbr\u003ed=%{x}\u003cbr\u003eretention=%{y:.1%}\u003cbr\u003endcg@10=%{customdata:.4f}\u003cextra\u003e\u003c\u002fextra\u003e","legendgroup":"mrl","line":{"color":"#eb841b","dash":"solid","width":2},"marker":{"color":"#eb841b","size":6},"mode":"lines+markers","name":"Matryoshka (truncate + renorm)","showlegend":false,"x":{"dtype":"i2","bdata":"IABAAIAAAAEAAg=="},"y":{"dtype":"f8","bdata":"78Kz5NgS2D93xc5tlj3qP+H\u002fmfXmq+0\u002fj\u002fJ4iufc7T\u002f8RxvERK\u002fvPw=="},"type":"scatter","xaxis":"x7","yaxis":"y7"},{"customdata":{"dtype":"f8","bdata":"YmywpyW\u002f3T+aDv5nc3bjP8grnuhCQuU\u002f9C\u002fOUUdp5j9cY+dNeZ7mPw=="},"hovertemplate":"\u003cb\u003ePCA\u003c\u002fb\u003e\u003cbr\u003ed=%{x}\u003cbr\u003eretention=%{y:.1%}\u003cbr\u003endcg@10=%{customdata:.4f}\u003cextra\u003e\u003c\u002fextra\u003e","legendgroup":"pca_in","line":{"color":"#60a5fa","dash":"solid","width":2},"marker":{"color":"#60a5fa","size":6},"mode":"lines+markers","name":"PCA","showlegend":false,"x":{"dtype":"i2","bdata":"IABAAIAAAAEAAg=="},"y":{"dtype":"f8","bdata":"i5R5wksh4z+Ho\u002fDlawjpP0TD8EXUV+s\u002faacOq0fT7D8G7QsasxftPw=="},"type":"scatter","xaxis":"x7","yaxis":"y7"},{"customdata":{"dtype":"f8","bdata":"DDByRfyzuT+BYkp5V7TGP6TUfTAa58s\u002f\u002fThGsMDUzj852Nt9uLnOPw=="},"hovertemplate":"\u003cb\u003eMatryoshka (truncate + renorm)\u003c\u002fb\u003e\u003cbr\u003ed=%{x}\u003cbr\u003eretention=%{y:.1%}\u003cbr\u003endcg@10=%{customdata:.4f}\u003cextra\u003e\u003c\u002fextra\u003e","legendgroup":"mrl","line":{"color":"#eb841b","dash":"solid","width":2},"marker":{"color":"#eb841b","size":6},"mode":"lines+markers","name":"Matryoshka (truncate + renorm)","showlegend":false,"x":{"dtype":"i2","bdata":"IABAAIAAAAEAAg=="},"y":{"dtype":"f8","bdata":"EXlCQDBp2j+nBhasZ1TnP2d+hhDQq+w\u002fEr6Qfhuu7z+f+rm5VJLvPw=="},"type":"scatter","xaxis":"x8","yaxis":"y8"},{"customdata":{"dtype":"f8","bdata":"UrKERdsBwD+KCZ+Se2HHPz2LhfZi9Mo\u002f3axwRBvqzD8y9wQzSGTNPw=="},"hovertemplate":"\u003cb\u003ePCA\u003c\u002fb\u003e\u003cbr\u003ed=%{x}\u003cbr\u003eretention=%{y:.1%}\u003cbr\u003endcg@10=%{customdata:.4f}\u003cextra\u003e\u003c\u002fextra\u003e","legendgroup":"pca_in","line":{"color":"#60a5fa","dash":"solid","width":2},"marker":{"color":"#60a5fa","size":6},"mode":"lines+markers","name":"PCA","showlegend":false,"x":{"dtype":"i2","bdata":"IABAAIAAAAEAAg=="},"y":{"dtype":"f8","bdata":"coaxlrRy4D9KIiJkUAboPz+v37lpsus\u002fk\u002fMGFvO17T94eJ1WfTPuPw=="},"type":"scatter","xaxis":"x8","yaxis":"y8"}],                        {"template":{"data":{"scatter":[{"type":"scatter"}]},"layout":{"margin":{"b":0,"l":0,"r":0,"t":30}}},"xaxis":{"anchor":"y","domain":[0.0,0.45],"gridcolor":"#3f3f46","zeroline":false,"linecolor":"#3f3f46","tickcolor":"#3f3f46","automargin":true,"type":"log","tickvals":[32,64,128,256,512]},"yaxis":{"anchor":"x","domain":[0.825,1.0],"gridcolor":"#3f3f46","zeroline":false,"linecolor":"#3f3f46","tickcolor":"#3f3f46","automargin":true,"tickformat":".0%"},"xaxis2":{"anchor":"y2","domain":[0.55,1.0],"gridcolor":"#3f3f46","zeroline":false,"linecolor":"#3f3f46","tickcolor":"#3f3f46","automargin":true,"type":"log","tickvals":[32,64,128,256,512]},"yaxis2":{"anchor":"x2","domain":[0.825,1.0],"gridcolor":"#3f3f46","zeroline":false,"linecolor":"#3f3f46","tickcolor":"#3f3f46","automargin":true,"tickformat":".0%"},"xaxis3":{"anchor":"y3","domain":[0.0,0.45],"gridcolor":"#3f3f46","zeroline":false,"linecolor":"#3f3f46","tickcolor":"#3f3f46","automargin":true,"type":"log","tickvals":[32,64,128,256,512]},"yaxis3":{"anchor":"x3","domain":[0.55,0.7250000000000001],"gridcolor":"#3f3f46","zeroline":false,"linecolor":"#3f3f46","tickcolor":"#3f3f46","automargin":true,"tickformat":".0%"},"xaxis4":{"anchor":"y4","domain":[0.55,1.0],"gridcolor":"#3f3f46","zeroline":false,"linecolor":"#3f3f46","tickcolor":"#3f3f46","automargin":true,"type":"log","tickvals":[32,64,128,256,512]},"yaxis4":{"anchor":"x4","domain":[0.55,0.7250000000000001],"gridcolor":"#3f3f46","zeroline":false,"linecolor":"#3f3f46","tickcolor":"#3f3f46","automargin":true,"tickformat":".0%"},"xaxis5":{"anchor":"y5","domain":[0.0,0.45],"gridcolor":"#3f3f46","zeroline":false,"linecolor":"#3f3f46","tickcolor":"#3f3f46","automargin":true,"type":"log","tickvals":[32,64,128,256,512]},"yaxis5":{"anchor":"x5","domain":[0.275,0.45],"gridcolor":"#3f3f46","zeroline":false,"linecolor":"#3f3f46","tickcolor":"#3f3f46","automargin":true,"tickformat":".0%"},"xaxis6":{"anchor":"y6","domain":[0.55,1.0],"gridcolor":"#3f3f46","zeroline":false,"linecolor":"#3f3f46","tickcolor":"#3f3f46","automargin":true,"type":"log","tickvals":[32,64,128,256,512]},"yaxis6":{"anchor":"x6","domain":[0.275,0.45],"gridcolor":"#3f3f46","zeroline":false,"linecolor":"#3f3f46","tickcolor":"#3f3f46","automargin":true,"tickformat":".0%"},"xaxis7":{"anchor":"y7","domain":[0.0,0.45],"gridcolor":"#3f3f46","zeroline":false,"linecolor":"#3f3f46","tickcolor":"#3f3f46","automargin":true,"type":"log","tickvals":[32,64,128,256,512]},"yaxis7":{"anchor":"x7","domain":[0.0,0.175],"gridcolor":"#3f3f46","zeroline":false,"linecolor":"#3f3f46","tickcolor":"#3f3f46","automargin":true,"tickformat":".0%"},"xaxis8":{"anchor":"y8","domain":[0.55,1.0],"gridcolor":"#3f3f46","zeroline":false,"linecolor":"#3f3f46","tickcolor":"#3f3f46","automargin":true,"type":"log","tickvals":[32,64,128,256,512]},"yaxis8":{"anchor":"x8","domain":[0.0,0.175],"gridcolor":"#3f3f46","zeroline":false,"linecolor":"#3f3f46","tickcolor":"#3f3f46","automargin":true,"tickformat":".0%"},"annotations":[{"font":{"color":"#e4e4e7","size":13},"showarrow":false,"text":"arguana (full ndcg@10: 0.557)","x":0.225,"xanchor":"center","xref":"paper","y":1.0,"yanchor":"bottom","yref":"paper"},{"font":{"color":"#e4e4e7","size":13},"showarrow":false,"text":"fiqa (full ndcg@10: 0.448)","x":0.775,"xanchor":"center","xref":"paper","y":1.0,"yanchor":"bottom","yref":"paper"},{"font":{"color":"#e4e4e7","size":13},"showarrow":false,"text":"nfcorpus (full ndcg@10: 0.385)","x":0.225,"xanchor":"center","xref":"paper","y":0.7250000000000001,"yanchor":"bottom","yref":"paper"},{"font":{"color":"#e4e4e7","size":13},"showarrow":false,"text":"quora (full ndcg@10: 0.888)","x":0.775,"xanchor":"center","xref":"paper","y":0.7250000000000001,"yanchor":"bottom","yref":"paper"},{"font":{"color":"#e4e4e7","size":13},"showarrow":false,"text":"scidocs (full ndcg@10: 0.208)","x":0.225,"xanchor":"center","xref":"paper","y":0.45,"yanchor":"bottom","yref":"paper"},{"font":{"color":"#e4e4e7","size":13},"showarrow":false,"text":"scifact (full ndcg@10: 0.730)","x":0.775,"xanchor":"center","xref":"paper","y":0.45,"yanchor":"bottom","yref":"paper"},{"font":{"color":"#e4e4e7","size":13},"showarrow":false,"text":"trec-covid (full ndcg@10: 0.777)","x":0.225,"xanchor":"center","xref":"paper","y":0.175,"yanchor":"bottom","yref":"paper"},{"font":{"color":"#e4e4e7","size":13},"showarrow":false,"text":"webis-touche2020 (full ndcg@10: 0.243)","x":0.775,"xanchor":"center","xref":"paper","y":0.175,"yanchor":"bottom","yref":"paper"}],"shapes":[{"line":{"color":"#a1a1aa","dash":"dash","width":1},"type":"line","x0":0,"x1":1,"xref":"x domain","y0":1.0,"y1":1.0,"yref":"y"},{"line":{"color":"#a1a1aa","dash":"dash","width":1},"type":"line","x0":0,"x1":1,"xref":"x2 domain","y0":1.0,"y1":1.0,"yref":"y2"},{"line":{"color":"#a1a1aa","dash":"dash","width":1},"type":"line","x0":0,"x1":1,"xref":"x3 domain","y0":1.0,"y1":1.0,"yref":"y3"},{"line":{"color":"#a1a1aa","dash":"dash","width":1},"type":"line","x0":0,"x1":1,"xref":"x4 domain","y0":1.0,"y1":1.0,"yref":"y4"},{"line":{"color":"#a1a1aa","dash":"dash","width":1},"type":"line","x0":0,"x1":1,"xref":"x5 domain","y0":1.0,"y1":1.0,"yref":"y5"},{"line":{"color":"#a1a1aa","dash":"dash","width":1},"type":"line","x0":0,"x1":1,"xref":"x6 domain","y0":1.0,"y1":1.0,"yref":"y6"},{"line":{"color":"#a1a1aa","dash":"dash","width":1},"type":"line","x0":0,"x1":1,"xref":"x7 domain","y0":1.0,"y1":1.0,"yref":"y7"},{"line":{"color":"#a1a1aa","dash":"dash","width":1},"type":"line","x0":0,"x1":1,"xref":"x8 domain","y0":1.0,"y1":1.0,"yref":"y8"}],"title":{"font":{"size":15,"color":"#e4e4e7"},"text":"Share of full-dim NDCG@10 retained, per dataset","x":0.5,"xanchor":"center"},"font":{"family":"Georgia, serif","size":13,"color":"#e4e4e7"},"margin":{"l":10,"r":10,"t":60,"b":40},"legend":{"font":{"size":12,"color":"#a1a1aa"},"orientation":"h","yanchor":"bottom","y":-0.04716981132075472,"x":0.5,"xanchor":"center"},"hoverlabel":{"font":{"family":"Georgia, serif","size":12,"color":"#e4e4e7"},"bgcolor":"#27272a","bordercolor":"#eb841b","align":"left"},"paper_bgcolor":"#18181b","plot_bgcolor":"#18181b","height":1160,"showlegend":true,"dragmode":false},                        {"displayModeBar": false, "responsive": true}                    ).then(function(){
                            
var gd = document.getElementById('a05a1a75-2da1-422e-8089-0d45e4d4d93a');
var x = new MutationObserver(function (mutations, observer) {{
        var display = window.getComputedStyle(gd).display;
        if (!display || display === 'none') {{
            console.log([gd, 'removed!']);
            Plotly.purge(gd);
            observer.disconnect();
        }}
}});

// Listen for the removal of the full notebook cells
var notebookContainer = gd.closest('#notebook-container');
if (notebookContainer) {{
    x.observe(notebookContainer, {childList: true});
}}

// Listen for the clearing of the current output cell
var outputEl = gd.closest('.output');
if (outputEl) {{
    x.observe(outputEl, {childList: true});
}}

                        })                };            </script>        </div>
</div>
</div>
</div>
<div id="tabset-2-2" class="tab-pane" aria-labelledby="tabset-2-2-tab">
<div id="19c15cc7" class="cell" data-execution_count="5">
<div class="cell-output cell-output-display">
<div>            <script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-AMS-MML_SVG"></script><script type="text/javascript">if (window.MathJax && window.MathJax.Hub && window.MathJax.Hub.Config) {window.MathJax.Hub.Config({SVG: {font: "STIX-Web"}});}</script>                <script type="text/javascript">window.PlotlyConfig = {MathJaxConfig: 'local'};</script>
        <script charset="utf-8" src="https://cdn.plot.ly/plotly-3.0.1.min.js"></script>                <div id="de5bc5f3-9eb8-4730-9adc-123a67a91a23" class="plotly-graph-div" style="height:1160px; width:100%;"></div>            <script type="text/javascript">                window.PLOTLYENV=window.PLOTLYENV || {};                                if (document.getElementById("de5bc5f3-9eb8-4730-9adc-123a67a91a23")) {                    Plotly.newPlot(                        "de5bc5f3-9eb8-4730-9adc-123a67a91a23",                        [{"customdata":{"dtype":"f8","bdata":"njO9tPm94j9PZtrr4a7lP8oaeAP0Dec\u002fFG4I4QFF6D\u002fGc+R903roPw=="},"hovertemplate":"\u003cb\u003eMatryoshka (truncate + renorm)\u003c\u002fb\u003e\u003cbr\u003ed=%{x}\u003cbr\u003eretention=%{y:.1%}\u003cbr\u003endcg@10=%{customdata:.4f}\u003cextra\u003e\u003c\u002fextra\u003e","legendgroup":"mrl","line":{"color":"#eb841b","dash":"solid","width":2},"marker":{"color":"#eb841b","size":6},"mode":"lines+markers","name":"Matryoshka (truncate + renorm)","showlegend":true,"x":{"dtype":"i2","bdata":"IABAAIAAAAEAAg=="},"y":{"dtype":"f8","bdata":"GWCmozNg6D8WzR70bDPsP1dRYFoG\u002fO0\u002fAD0bNZSQ7z+LobQ9k9bvPw=="},"type":"scatter","xaxis":"x","yaxis":"y"},{"customdata":{"dtype":"f8","bdata":"t+omSGrK4z9iSM6PBOvlP1Q8YiHOGec\u002f6Weaj2yz5z+TL\u002fVomBjoPw=="},"hovertemplate":"\u003cb\u003ePCA\u003c\u002fb\u003e\u003cbr\u003ed=%{x}\u003cbr\u003eretention=%{y:.1%}\u003cbr\u003endcg@10=%{customdata:.4f}\u003cextra\u003e\u003c\u002fextra\u003e","legendgroup":"pca_in","line":{"color":"#60a5fa","dash":"solid","width":2},"marker":{"color":"#60a5fa","size":6},"mode":"lines+markers","name":"PCA","showlegend":true,"x":{"dtype":"i2","bdata":"IABAAIAAAAEAAg=="},"y":{"dtype":"f8","bdata":"gIdf+VS96T+cOgYUo4HsPxEBNH9wC+4\u002fgcQtFTzT7j9fY9Iu0VbvPw=="},"type":"scatter","xaxis":"x","yaxis":"y"},{"customdata":{"dtype":"f8","bdata":"ES7piGBc1z\u002fyn1mCt47gP0p7shhPgOI\u002fB5YqyezA4z99g8kS+HTkPw=="},"hovertemplate":"\u003cb\u003eMatryoshka (truncate + renorm)\u003c\u002fb\u003e\u003cbr\u003ed=%{x}\u003cbr\u003eretention=%{y:.1%}\u003cbr\u003endcg@10=%{customdata:.4f}\u003cextra\u003e\u003c\u002fextra\u003e","legendgroup":"mrl","line":{"color":"#eb841b","dash":"solid","width":2},"marker":{"color":"#eb841b","size":6},"mode":"lines+markers","name":"Matryoshka (truncate + renorm)","showlegend":false,"x":{"dtype":"i2","bdata":"IABAAIAAAAEAAg=="},"y":{"dtype":"f8","bdata":"2PWxQ+z94T8vRzltFoHpP0bJYiONf+w\u002fPY9LH2lt7j\u002fLAPuevYLvPw=="},"type":"scatter","xaxis":"x2","yaxis":"y2"},{"customdata":{"dtype":"f8","bdata":"1E\u002fOCFsU3D\u002f1k8t+xjHhP9H7fpP6COM\u002f3OqS0I7y4z+b0puwMX7kPw=="},"hovertemplate":"\u003cb\u003ePCA\u003c\u002fb\u003e\u003cbr\u003ed=%{x}\u003cbr\u003eretention=%{y:.1%}\u003cbr\u003endcg@10=%{customdata:.4f}\u003cextra\u003e\u003c\u002fextra\u003e","legendgroup":"pca_in","line":{"color":"#60a5fa","dash":"solid","width":2},"marker":{"color":"#60a5fa","size":6},"mode":"lines+markers","name":"PCA","showlegend":false,"x":{"dtype":"i2","bdata":"IABAAIAAAAEAAg=="},"y":{"dtype":"f8","bdata":"uiK7YUag5T+aEiYKQXzqP3TVRPARUu0\u002fVgdby9y57j+9t25T85DvPw=="},"type":"scatter","xaxis":"x2","yaxis":"y2"},{"customdata":{"dtype":"f8","bdata":"ydv\u002feANrzD+xLAqiCivTP8RjkdM649U\u002f85Ye8pae1z\u002fBy4SlJ83ZPw=="},"hovertemplate":"\u003cb\u003eMatryoshka (truncate + renorm)\u003c\u002fb\u003e\u003cbr\u003ed=%{x}\u003cbr\u003eretention=%{y:.1%}\u003cbr\u003endcg@10=%{customdata:.4f}\u003cextra\u003e\u003c\u002fextra\u003e","legendgroup":"mrl","line":{"color":"#eb841b","dash":"solid","width":2},"marker":{"color":"#eb841b","size":6},"mode":"lines+markers","name":"Matryoshka (truncate + renorm)","showlegend":false,"x":{"dtype":"i2","bdata":"IABAAIAAAAEAAg=="},"y":{"dtype":"f8","bdata":"JNAOT+Ue4T\u002fFJEWXlBjnP\u002f7EXgxuX+o\u002fH\u002fhUVaR17D+V\u002fEK\u002fqhbvPw=="},"type":"scatter","xaxis":"x3","yaxis":"y3"},{"customdata":{"dtype":"f8","bdata":"QndlgAwR0T\u002fvO+y+yQfVP1jhX2brrdc\u002fT0hzyXMm2T\u002fhWm7nX3bZPw=="},"hovertemplate":"\u003cb\u003ePCA\u003c\u002fb\u003e\u003cbr\u003ed=%{x}\u003cbr\u003eretention=%{y:.1%}\u003cbr\u003endcg@10=%{customdata:.4f}\u003cextra\u003e\u003c\u002fextra\u003e","legendgroup":"pca_in","line":{"color":"#60a5fa","dash":"solid","width":2},"marker":{"color":"#60a5fa","size":6},"mode":"lines+markers","name":"PCA","showlegend":false,"x":{"dtype":"i2","bdata":"IABAAIAAAAEAAg=="},"y":{"dtype":"f8","bdata":"qy989VeQ5D8URl1MBVfpPwVCGfociOw\u002fCoDq2s1N7j9POvSbGq7uPw=="},"type":"scatter","xaxis":"x3","yaxis":"y3"},{"customdata":{"dtype":"f8","bdata":"M3wfOLBC6j9AKyP9OIzrP8Zl+hjJE+w\u002fyRmytihR7D+iJL4C6mnsPw=="},"hovertemplate":"\u003cb\u003eMatryoshka (truncate + renorm)\u003c\u002fb\u003e\u003cbr\u003ed=%{x}\u003cbr\u003eretention=%{y:.1%}\u003cbr\u003endcg@10=%{customdata:.4f}\u003cextra\u003e\u003c\u002fextra\u003e","legendgroup":"mrl","line":{"color":"#eb841b","dash":"solid","width":2},"marker":{"color":"#eb841b","size":6},"mode":"lines+markers","name":"Matryoshka (truncate + renorm)","showlegend":false,"x":{"dtype":"i2","bdata":"IABAAIAAAAEAAg=="},"y":{"dtype":"f8","bdata":"g+af8vOA7T8jBA5uL\u002fPuP9Idlo99i+8\u002f2qxEl3HQ7z8\u002fDlSOQezvPw=="},"type":"scatter","xaxis":"x4","yaxis":"y4"},{"customdata":{"dtype":"f8","bdata":"GiFgc\u002fQV5j+SJfRkW\u002fjpPw2YaGJ6b+s\u002fngaw6MEJ7D997zSWDE3sPw=="},"hovertemplate":"\u003cb\u003ePCA\u003c\u002fb\u003e\u003cbr\u003ed=%{x}\u003cbr\u003eretention=%{y:.1%}\u003cbr\u003endcg@10=%{customdata:.4f}\u003cextra\u003e\u003c\u002fextra\u003e","legendgroup":"pca_in","line":{"color":"#60a5fa","dash":"solid","width":2},"marker":{"color":"#60a5fa","size":6},"mode":"lines+markers","name":"PCA","showlegend":false,"x":{"dtype":"i2","bdata":"IABAAIAAAAEAAg=="},"y":{"dtype":"f8","bdata":"KqtHlzrQ6D+fOQgHcS3tP63oKwbk0u4\u002fyzfVUTmA7z++ZjuG08vvPw=="},"type":"scatter","xaxis":"x4","yaxis":"y4"},{"customdata":{"dtype":"f8","bdata":"d0zMhah6yT8C97pguZXQPyI72jfshNI\u002fKtot3NPR0z+uN8E+hpzUPw=="},"hovertemplate":"\u003cb\u003eMatryoshka (truncate + renorm)\u003c\u002fb\u003e\u003cbr\u003ed=%{x}\u003cbr\u003eretention=%{y:.1%}\u003cbr\u003endcg@10=%{customdata:.4f}\u003cextra\u003e\u003c\u002fextra\u003e","legendgroup":"mrl","line":{"color":"#eb841b","dash":"solid","width":2},"marker":{"color":"#eb841b","size":6},"mode":"lines+markers","name":"Matryoshka (truncate + renorm)","showlegend":false,"x":{"dtype":"i2","bdata":"IABAAIAAAAEAAg=="},"y":{"dtype":"f8","bdata":"NNa80Rp+4z\u002fnUt6ZT2DpP8LNAIcBVuw\u002f+SsayWBT7j8YjKRjhYnvPw=="},"type":"scatter","xaxis":"x5","yaxis":"y5"},{"customdata":{"dtype":"f8","bdata":"\u002fdQxSq+\u002fzD8HlRy2AeTQP+9MMo\u002fyn9I\u002fZlJMowqU0z9inAYtXBjUPw=="},"hovertemplate":"\u003cb\u003ePCA\u003c\u002fb\u003e\u003cbr\u003ed=%{x}\u003cbr\u003eretention=%{y:.1%}\u003cbr\u003endcg@10=%{customdata:.4f}\u003cextra\u003e\u003c\u002fextra\u003e","legendgroup":"pca_in","line":{"color":"#60a5fa","dash":"solid","width":2},"marker":{"color":"#60a5fa","size":6},"mode":"lines+markers","name":"PCA","showlegend":false,"x":{"dtype":"i2","bdata":"IABAAIAAAAEAAg=="},"y":{"dtype":"f8","bdata":"MOYH\u002fHb+5T9H6UX6FtjpP\u002fsJJCpbf+w\u002fQLDVHtf07T8YxTdzTL\u002fuPw=="},"type":"scatter","xaxis":"x5","yaxis":"y5"},{"customdata":{"dtype":"f8","bdata":"OBNNUMdT4T8ftlnzqGnlPyTpndxvkuc\u002fmi84u2oL6D9jCmzINLLoPw=="},"hovertemplate":"\u003cb\u003eMatryoshka (truncate + renorm)\u003c\u002fb\u003e\u003cbr\u003ed=%{x}\u003cbr\u003eretention=%{y:.1%}\u003cbr\u003endcg@10=%{customdata:.4f}\u003cextra\u003e\u003c\u002fextra\u003e","legendgroup":"mrl","line":{"color":"#eb841b","dash":"solid","width":2},"marker":{"color":"#eb841b","size":6},"mode":"lines+markers","name":"Matryoshka (truncate + renorm)","showlegend":false,"x":{"dtype":"i2","bdata":"IABAAIAAAAEAAg=="},"y":{"dtype":"f8","bdata":"cTWwOSoJ5j9e6O6JQTvrP\u002f+CkOA++u0\u002f4Mx20xmU7j\u002f3UeS4NmjvPw=="},"type":"scatter","xaxis":"x6","yaxis":"y6"},{"customdata":{"dtype":"f8","bdata":"YJ8UDkS15D90MFOIGI7mP0vvO2Mhvuc\u002f4Z17+YIm6D8wBZB1kIXoPw=="},"hovertemplate":"\u003cb\u003ePCA\u003c\u002fb\u003e\u003cbr\u003ed=%{x}\u003cbr\u003eretention=%{y:.1%}\u003cbr\u003endcg@10=%{customdata:.4f}\u003cextra\u003e\u003c\u002fextra\u003e","legendgroup":"pca_in","line":{"color":"#60a5fa","dash":"solid","width":2},"marker":{"color":"#60a5fa","size":6},"mode":"lines+markers","name":"PCA","showlegend":false,"x":{"dtype":"i2","bdata":"IABAAIAAAAEAAg=="},"y":{"dtype":"f8","bdata":"fdtFXddV6j93lhSvKK\u002fsP6H1T\u002frPMe4\u002fCw6074627j+8LnTYcC\u002fvPw=="},"type":"scatter","xaxis":"x6","yaxis":"y6"},{"customdata":{"dtype":"f8","bdata":"DOouBa4p5j9PVw5hJeDqP90u\u002fjlVz+w\u002fHlJq2Pw67j\u002fO83Cc62vuPw=="},"hovertemplate":"\u003cb\u003eMatryoshka (truncate + renorm)\u003c\u002fb\u003e\u003cbr\u003ed=%{x}\u003cbr\u003eretention=%{y:.1%}\u003cbr\u003endcg@10=%{customdata:.4f}\u003cextra\u003e\u003c\u002fextra\u003e","legendgroup":"mrl","line":{"color":"#eb841b","dash":"solid","width":2},"marker":{"color":"#eb841b","size":6},"mode":"lines+markers","name":"Matryoshka (truncate + renorm)","showlegend":false,"x":{"dtype":"i2","bdata":"IABAAIAAAAEAAg=="},"y":{"dtype":"f8","bdata":"tvqhzJ9Z5z\u002fXiMjVuFDsP5KT3cFvWu4\u002fFGDxmJLZ7z90hYw3kAbwPw=="},"type":"scatter","xaxis":"x7","yaxis":"y7"},{"customdata":{"dtype":"f8","bdata":"Ul5JygaN5z9DD4ho8ZHrPwj+Hbyyv+w\u002fQousavqg7T9Akj5O4BPuPw=="},"hovertemplate":"\u003cb\u003ePCA\u003c\u002fb\u003e\u003cbr\u003ed=%{x}\u003cbr\u003eretention=%{y:.1%}\u003cbr\u003endcg@10=%{customdata:.4f}\u003cextra\u003e\u003c\u002fextra\u003e","legendgroup":"pca_in","line":{"color":"#60a5fa","dash":"solid","width":2},"marker":{"color":"#60a5fa","size":6},"mode":"lines+markers","name":"PCA","showlegend":false,"x":{"dtype":"i2","bdata":"IABAAIAAAAEAAg=="},"y":{"dtype":"f8","bdata":"dkpv2gHQ6D\u002fbFZUzCwztP7m9KNn2Se4\u002f5LKpDlA37z\u002fPCCetXbDvPw=="},"type":"scatter","xaxis":"x7","yaxis":"y7"},{"customdata":{"dtype":"f8","bdata":"WW3ymgNkzD83EL20WdHQP3Y7uUsfk9M\u002fkXJ\u002fQ8OH1T+nPTPnOafWPw=="},"hovertemplate":"\u003cb\u003eMatryoshka (truncate + renorm)\u003c\u002fb\u003e\u003cbr\u003ed=%{x}\u003cbr\u003eretention=%{y:.1%}\u003cbr\u003endcg@10=%{customdata:.4f}\u003cextra\u003e\u003c\u002fextra\u003e","legendgroup":"mrl","line":{"color":"#eb841b","dash":"solid","width":2},"marker":{"color":"#eb841b","size":6},"mode":"lines+markers","name":"Matryoshka (truncate + renorm)","showlegend":false,"x":{"dtype":"i2","bdata":"IABAAIAAAAEAAg=="},"y":{"dtype":"f8","bdata":"k7f6jnW84z\u002f9X86i2GHnP5cTTZ8ZN+s\u002fABt3fCfv7T+nVsKP0n7vPw=="},"type":"scatter","xaxis":"x8","yaxis":"y8"},{"customdata":{"dtype":"f8","bdata":"opxUrweexz+zcARLCjXPPwlC4DcO49I\u002f6frLZmM51T80EoUagyDWPw=="},"hovertemplate":"\u003cb\u003ePCA\u003c\u002fb\u003e\u003cbr\u003ed=%{x}\u003cbr\u003eretention=%{y:.1%}\u003cbr\u003endcg@10=%{customdata:.4f}\u003cextra\u003e\u003c\u002fextra\u003e","legendgroup":"pca_in","line":{"color":"#60a5fa","dash":"solid","width":2},"marker":{"color":"#60a5fa","size":6},"mode":"lines+markers","name":"PCA","showlegend":false,"x":{"dtype":"i2","bdata":"IABAAIAAAAEAAg=="},"y":{"dtype":"f8","bdata":"HVp89fpq4D+Wad27sLHlPycoukpPQuo\u002f3IGfJjCC7T8Wwl6fhsPuPw=="},"type":"scatter","xaxis":"x8","yaxis":"y8"}],                        {"template":{"data":{"scatter":[{"type":"scatter"}]},"layout":{"margin":{"b":0,"l":0,"r":0,"t":30}}},"xaxis":{"anchor":"y","domain":[0.0,0.45],"gridcolor":"#3f3f46","zeroline":false,"linecolor":"#3f3f46","tickcolor":"#3f3f46","automargin":true,"type":"log","tickvals":[32,64,128,256,512]},"yaxis":{"anchor":"x","domain":[0.825,1.0],"gridcolor":"#3f3f46","zeroline":false,"linecolor":"#3f3f46","tickcolor":"#3f3f46","automargin":true,"tickformat":".0%"},"xaxis2":{"anchor":"y2","domain":[0.55,1.0],"gridcolor":"#3f3f46","zeroline":false,"linecolor":"#3f3f46","tickcolor":"#3f3f46","automargin":true,"type":"log","tickvals":[32,64,128,256,512]},"yaxis2":{"anchor":"x2","domain":[0.825,1.0],"gridcolor":"#3f3f46","zeroline":false,"linecolor":"#3f3f46","tickcolor":"#3f3f46","automargin":true,"tickformat":".0%"},"xaxis3":{"anchor":"y3","domain":[0.0,0.45],"gridcolor":"#3f3f46","zeroline":false,"linecolor":"#3f3f46","tickcolor":"#3f3f46","automargin":true,"type":"log","tickvals":[32,64,128,256,512]},"yaxis3":{"anchor":"x3","domain":[0.55,0.7250000000000001],"gridcolor":"#3f3f46","zeroline":false,"linecolor":"#3f3f46","tickcolor":"#3f3f46","automargin":true,"tickformat":".0%"},"xaxis4":{"anchor":"y4","domain":[0.55,1.0],"gridcolor":"#3f3f46","zeroline":false,"linecolor":"#3f3f46","tickcolor":"#3f3f46","automargin":true,"type":"log","tickvals":[32,64,128,256,512]},"yaxis4":{"anchor":"x4","domain":[0.55,0.7250000000000001],"gridcolor":"#3f3f46","zeroline":false,"linecolor":"#3f3f46","tickcolor":"#3f3f46","automargin":true,"tickformat":".0%"},"xaxis5":{"anchor":"y5","domain":[0.0,0.45],"gridcolor":"#3f3f46","zeroline":false,"linecolor":"#3f3f46","tickcolor":"#3f3f46","automargin":true,"type":"log","tickvals":[32,64,128,256,512]},"yaxis5":{"anchor":"x5","domain":[0.275,0.45],"gridcolor":"#3f3f46","zeroline":false,"linecolor":"#3f3f46","tickcolor":"#3f3f46","automargin":true,"tickformat":".0%"},"xaxis6":{"anchor":"y6","domain":[0.55,1.0],"gridcolor":"#3f3f46","zeroline":false,"linecolor":"#3f3f46","tickcolor":"#3f3f46","automargin":true,"type":"log","tickvals":[32,64,128,256,512]},"yaxis6":{"anchor":"x6","domain":[0.275,0.45],"gridcolor":"#3f3f46","zeroline":false,"linecolor":"#3f3f46","tickcolor":"#3f3f46","automargin":true,"tickformat":".0%"},"xaxis7":{"anchor":"y7","domain":[0.0,0.45],"gridcolor":"#3f3f46","zeroline":false,"linecolor":"#3f3f46","tickcolor":"#3f3f46","automargin":true,"type":"log","tickvals":[32,64,128,256,512]},"yaxis7":{"anchor":"x7","domain":[0.0,0.175],"gridcolor":"#3f3f46","zeroline":false,"linecolor":"#3f3f46","tickcolor":"#3f3f46","automargin":true,"tickformat":".0%"},"xaxis8":{"anchor":"y8","domain":[0.55,1.0],"gridcolor":"#3f3f46","zeroline":false,"linecolor":"#3f3f46","tickcolor":"#3f3f46","automargin":true,"type":"log","tickvals":[32,64,128,256,512]},"yaxis8":{"anchor":"x8","domain":[0.0,0.175],"gridcolor":"#3f3f46","zeroline":false,"linecolor":"#3f3f46","tickcolor":"#3f3f46","automargin":true,"tickformat":".0%"},"annotations":[{"font":{"color":"#e4e4e7","size":13},"showarrow":false,"text":"arguana (full ndcg@10: 0.769)","x":0.225,"xanchor":"center","xref":"paper","y":1.0,"yanchor":"bottom","yref":"paper"},{"font":{"color":"#e4e4e7","size":13},"showarrow":false,"text":"fiqa (full ndcg@10: 0.649)","x":0.775,"xanchor":"center","xref":"paper","y":1.0,"yanchor":"bottom","yref":"paper"},{"font":{"color":"#e4e4e7","size":13},"showarrow":false,"text":"nfcorpus (full ndcg@10: 0.415)","x":0.225,"xanchor":"center","xref":"paper","y":0.7250000000000001,"yanchor":"bottom","yref":"paper"},{"font":{"color":"#e4e4e7","size":13},"showarrow":false,"text":"quora (full ndcg@10: 0.890)","x":0.775,"xanchor":"center","xref":"paper","y":0.7250000000000001,"yanchor":"bottom","yref":"paper"},{"font":{"color":"#e4e4e7","size":13},"showarrow":false,"text":"scidocs (full ndcg@10: 0.327)","x":0.225,"xanchor":"center","xref":"paper","y":0.45,"yanchor":"bottom","yref":"paper"},{"font":{"color":"#e4e4e7","size":13},"showarrow":false,"text":"scifact (full ndcg@10: 0.786)","x":0.775,"xanchor":"center","xref":"paper","y":0.45,"yanchor":"bottom","yref":"paper"},{"font":{"color":"#e4e4e7","size":13},"showarrow":false,"text":"trec-covid (full ndcg@10: 0.949)","x":0.225,"xanchor":"center","xref":"paper","y":0.175,"yanchor":"bottom","yref":"paper"},{"font":{"color":"#e4e4e7","size":13},"showarrow":false,"text":"webis-touche2020 (full ndcg@10: 0.360)","x":0.775,"xanchor":"center","xref":"paper","y":0.175,"yanchor":"bottom","yref":"paper"}],"shapes":[{"line":{"color":"#a1a1aa","dash":"dash","width":1},"type":"line","x0":0,"x1":1,"xref":"x domain","y0":1.0,"y1":1.0,"yref":"y"},{"line":{"color":"#a1a1aa","dash":"dash","width":1},"type":"line","x0":0,"x1":1,"xref":"x2 domain","y0":1.0,"y1":1.0,"yref":"y2"},{"line":{"color":"#a1a1aa","dash":"dash","width":1},"type":"line","x0":0,"x1":1,"xref":"x3 domain","y0":1.0,"y1":1.0,"yref":"y3"},{"line":{"color":"#a1a1aa","dash":"dash","width":1},"type":"line","x0":0,"x1":1,"xref":"x4 domain","y0":1.0,"y1":1.0,"yref":"y4"},{"line":{"color":"#a1a1aa","dash":"dash","width":1},"type":"line","x0":0,"x1":1,"xref":"x5 domain","y0":1.0,"y1":1.0,"yref":"y5"},{"line":{"color":"#a1a1aa","dash":"dash","width":1},"type":"line","x0":0,"x1":1,"xref":"x6 domain","y0":1.0,"y1":1.0,"yref":"y6"},{"line":{"color":"#a1a1aa","dash":"dash","width":1},"type":"line","x0":0,"x1":1,"xref":"x7 domain","y0":1.0,"y1":1.0,"yref":"y7"},{"line":{"color":"#a1a1aa","dash":"dash","width":1},"type":"line","x0":0,"x1":1,"xref":"x8 domain","y0":1.0,"y1":1.0,"yref":"y8"}],"title":{"font":{"size":15,"color":"#e4e4e7"},"text":"Share of full-dim NDCG@10 retained, per dataset","x":0.5,"xanchor":"center"},"font":{"family":"Georgia, serif","size":13,"color":"#e4e4e7"},"margin":{"l":10,"r":10,"t":60,"b":40},"legend":{"font":{"size":12,"color":"#a1a1aa"},"orientation":"h","yanchor":"bottom","y":-0.04716981132075472,"x":0.5,"xanchor":"center"},"hoverlabel":{"font":{"family":"Georgia, serif","size":12,"color":"#e4e4e7"},"bgcolor":"#27272a","bordercolor":"#eb841b","align":"left"},"paper_bgcolor":"#18181b","plot_bgcolor":"#18181b","height":1160,"showlegend":true,"dragmode":false},                        {"displayModeBar": false, "responsive": true}                    ).then(function(){
                            
var gd = document.getElementById('de5bc5f3-9eb8-4730-9adc-123a67a91a23');
var x = new MutationObserver(function (mutations, observer) {{
        var display = window.getComputedStyle(gd).display;
        if (!display || display === 'none') {{
            console.log([gd, 'removed!']);
            Plotly.purge(gd);
            observer.disconnect();
        }}
}});

// Listen for the removal of the full notebook cells
var notebookContainer = gd.closest('#notebook-container');
if (notebookContainer) {{
    x.observe(notebookContainer, {childList: true});
}}

// Listen for the clearing of the current output cell
var outputEl = gd.closest('.output');
if (outputEl) {{
    x.observe(outputEl, {childList: true});
}}

                        })                };            </script>        </div>
</div>
</div>
</div>
</div>
</div>
<p>At the smallest dimensions, PCA wins on seven of the eight datasets for <code>text-embedding-3-small</code> and six of the eight for <code>qwen3-embedding-8b</code>.</p>
<p>Across dimensions, PCA performs better on SciFact, FiQA, and NFCorpus for both models. ArguAna and SciDocs also favor PCA with <code>text-embedding-3-small</code>, although the results are less conclusive with <code>qwen3-embedding-8b</code>. Truncation performs better on Quora for both models. Touché favors truncation on <code>qwen3-embedding-8b</code> but is basically a tie on <code>text-embedding-3-small</code>, and the result for TREC-COVID depends on the dimension.</p>
</section>
<section id="q2-is-it-just-the-mrl-training-1" class="level2">
<h2 class="anchored" data-anchor-id="q2-is-it-just-the-mrl-training-1">Q2: is it just the MRL training?</h2>
<p>I worried that PCA might only look good on <code>text-embedding-3-small</code> and <code>qwen3-embedding-8b</code> because MRL training had already organized their embedding spaces in some convenient way. If that were true, PCA on <code>text-embedding-ada-002</code>, which never saw MRL training, should be noticeably worse.</p>
<p>For the most part, it isn’t. Down to 128 dimensions, PCA retains nearly the same share of quality on <code>text-embedding-ada-002</code> as on <code>text-embedding-3-small</code>; below that, a small gap opens:</p>
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th rowspan="2" style="vertical-align: bottom;">
dims
</th>
<th colspan="2" style="text-align: center;">
ada-002 (no MRL)
</th>
<th colspan="2" style="text-align: center;">
3-small (MRL)
</th>
</tr>
<tr>
<th>
Truncation
</th>
<th>
PCA
</th>
<th>
Truncation
</th>
<th>
PCA
</th>
</tr>
</thead>
<tbody>
<tr>
<td>
512
</td>
<td>
96%
</td>
<td>
99%
</td>
<td>
98%
</td>
<td>
97%
</td>
</tr>
<tr>
<td>
256
</td>
<td>
89%
</td>
<td>
96%
</td>
<td>
94%
</td>
<td>
95%
</td>
</tr>
<tr>
<td>
128
</td>
<td>
83%
</td>
<td>
89%
</td>
<td>
86%
</td>
<td>
90%
</td>
</tr>
<tr>
<td>
64
</td>
<td>
66%
</td>
<td>
78%
</td>
<td>
71%
</td>
<td>
82%
</td>
</tr>
<tr>
<td>
32
</td>
<td>
41%
</td>
<td>
59%
</td>
<td>
46%
</td>
<td>
65%
</td>
</tr>
</tbody>
</table>
</div>
<div id="9716a836" class="cell" data-execution_count="6">
<div class="cell-output cell-output-display">
<div>            <script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-AMS-MML_SVG"></script><script type="text/javascript">if (window.MathJax && window.MathJax.Hub && window.MathJax.Hub.Config) {window.MathJax.Hub.Config({SVG: {font: "STIX-Web"}});}</script>                <script type="text/javascript">window.PlotlyConfig = {MathJaxConfig: 'local'};</script>
        <script charset="utf-8" src="https://cdn.plot.ly/plotly-3.0.1.min.js"></script>                <div id="97fa64f9-2e8f-4274-bb48-e65acb0cf415" class="plotly-graph-div" style="height:420px; width:100%;"></div>            <script type="text/javascript">                window.PLOTLYENV=window.PLOTLYENV || {};                                if (document.getElementById("97fa64f9-2e8f-4274-bb48-e65acb0cf415")) {                    Plotly.newPlot(                        "97fa64f9-2e8f-4274-bb48-e65acb0cf415",                        [{"customdata":{"dtype":"f8","bdata":"OSkgpFwr1z89KVLsQ2TcPwJcQq2N7t4\u002f1hQEABsq4D958zZbDnDgPw=="},"hovertemplate":"\u003cb\u003e3-small (MRL)\u003c\u002fb\u003e\u003cbr\u003ed=%{x}\u003cbr\u003eretention=%{y:.1%}\u003cbr\u003eavg ndcg@10=%{customdata:.4f}\u003cextra\u003e\u003c\u002fextra\u003e","line":{"color":"#eb841b","dash":"solid","width":2},"marker":{"color":"#eb841b","size":6},"mode":"lines+markers","name":"3-small (MRL)","x":{"dtype":"i2","bdata":"IABAAIAAAAEAAg=="},"y":{"dtype":"f8","bdata":"9uFdmqun5D\u002fqI1FNXRzqP6uW0S+yyew\u002fR5mMooVe7j8eQ\u002fJzcvjuPw=="},"type":"scatter"},{"customdata":{"dtype":"f8","bdata":"nbn\u002frm391D85Z5bkphnaPy2r+4gzd90\u002f2RvhInZG3z9S8XBvjgLgPw=="},"hovertemplate":"\u003cb\u003eada-002 (no MRL)\u003c\u002fb\u003e\u003cbr\u003ed=%{x}\u003cbr\u003eretention=%{y:.1%}\u003cbr\u003eavg ndcg@10=%{customdata:.4f}\u003cextra\u003e\u003c\u002fextra\u003e","line":{"color":"#60a5fa","dash":"dash","width":2},"marker":{"color":"#60a5fa","size":6},"mode":"lines+markers","name":"ada-002 (no MRL)","x":{"dtype":"i2","bdata":"IABAAIAAAAEAAg=="},"y":{"dtype":"f8","bdata":"7LBhpqjv4j\u002ftHXsHJtLoP70nIJVXkew\u002fjsGs3tTM7j9h3AGyjpPvPw=="},"type":"scatter"}],                        {"template":{"data":{"scatter":[{"type":"scatter"}]},"layout":{"margin":{"b":0,"l":0,"r":0,"t":30}}},"shapes":[{"line":{"color":"#a1a1aa","dash":"dash","width":1},"type":"line","x0":0,"x1":1,"xref":"x domain","y0":1.0,"y1":1.0,"yref":"y"}],"title":{"font":{"size":15,"color":"#e4e4e7"},"text":"In-domain PCA: share of full-dim NDCG@10 retained, both models","x":0.5,"xanchor":"center"},"font":{"family":"Georgia, serif","size":13,"color":"#e4e4e7"},"margin":{"l":10,"r":10,"t":60,"b":40},"legend":{"font":{"size":12,"color":"#a1a1aa"},"orientation":"h","yanchor":"bottom","y":-0.25,"x":0.5,"xanchor":"center"},"hoverlabel":{"font":{"family":"Georgia, serif","size":12,"color":"#e4e4e7"},"bgcolor":"#27272a","bordercolor":"#eb841b","align":"left"},"paper_bgcolor":"#18181b","plot_bgcolor":"#18181b","height":420,"showlegend":true,"dragmode":false,"xaxis":{"gridcolor":"#3f3f46","zeroline":false,"linecolor":"#3f3f46","tickcolor":"#3f3f46","automargin":true,"title":{"font":{"size":12,"color":"#a1a1aa"},"text":"dimensions"},"type":"log","tickvals":[32,64,128,256,512]},"yaxis":{"gridcolor":"#3f3f46","zeroline":false,"linecolor":"#3f3f46","tickcolor":"#3f3f46","automargin":true,"tickformat":".0%"}},                        {"displayModeBar": false, "responsive": true}                    ).then(function(){
                            
var gd = document.getElementById('97fa64f9-2e8f-4274-bb48-e65acb0cf415');
var x = new MutationObserver(function (mutations, observer) {{
        var display = window.getComputedStyle(gd).display;
        if (!display || display === 'none') {{
            console.log([gd, 'removed!']);
            Plotly.purge(gd);
            observer.disconnect();
        }}
}});

// Listen for the removal of the full notebook cells
var notebookContainer = gd.closest('#notebook-container');
if (notebookContainer) {{
    x.observe(notebookContainer, {childList: true});
}}

// Listen for the clearing of the current output cell
var outputEl = gd.closest('.output');
if (outputEl) {{
    x.observe(outputEl, {childList: true});
}}

                        })                };            </script>        </div>
</div>
</div>
<p>PCA on <code>text-embedding-ada-002</code> retains 78% at 64 dims and 59% at 32, against 82% and 65% on <code>text-embedding-3-small</code>. It might be that MRL genuinely helps, or simply that <code>text-embedding-3-small</code> is a newer, better model. I can’t separate the two with this experiment. But either way, the hypothesis that PCA only works because of MRL seems unlikely given that even PCA on the non-MRL model beats truncation on the MRL model at those dimensions (59% vs 46% at 32 dims).</p>
<p>Out of curiosity, I also tried truncation on <code>text-embedding-ada-002</code>, even though the model was never trained for it. It held up much better than I imagined, with 83% retained at 128 dimensions.</p>
</section>
<section id="q3-does-the-fitting-data-matter-1" class="level2">
<h2 class="anchored" data-anchor-id="q3-does-the-fitting-data-matter-1">Q3: does the fitting data matter?</h2>
<p>Compared to MRL, PCA adds more operational complexity: fitting and versioning the projection, using it while querying, updating the projection when the index changes. So I wanted to know what happens if you’re lazy: fit PCA once, when you first create the index, and never touch it again.</p>
<p><strong>In-domain fit on a small sample.</strong> In this scenario, you fit PCA on the documents you have at the start (1,000, 5,000, 20,000, or all of FiQA’s 57K) and use that projection for everything you index afterwards. At this scale, it doesn’t matter much: the projection fit on 1,000 documents performs almost the same as the one fit on the full corpus, at every dimension, on both models.</p>
<div class="tabset-margin-container"></div><div class="panel-tabset">
<ul class="nav nav-tabs"><li class="nav-item"><a class="nav-link active" id="tabset-3-1-tab" data-bs-toggle="tab" data-bs-target="#tabset-3-1" aria-controls="tabset-3-1" aria-selected="true" href="">3-small</a></li><li class="nav-item"><a class="nav-link" id="tabset-3-2-tab" data-bs-toggle="tab" data-bs-target="#tabset-3-2" aria-controls="tabset-3-2" aria-selected="false" href="">qwen3-8b</a></li></ul>
<div class="tab-content">
<div id="tabset-3-1" class="tab-pane active" aria-labelledby="tabset-3-1-tab">
<div id="13f0e454" class="cell" data-execution_count="7">
<div class="cell-output cell-output-display">
<div>            <script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-AMS-MML_SVG"></script><script type="text/javascript">if (window.MathJax && window.MathJax.Hub && window.MathJax.Hub.Config) {window.MathJax.Hub.Config({SVG: {font: "STIX-Web"}});}</script>                <script type="text/javascript">window.PlotlyConfig = {MathJaxConfig: 'local'};</script>
        <script charset="utf-8" src="https://cdn.plot.ly/plotly-3.0.1.min.js"></script>                <div id="a5f68c3c-583b-435b-9a63-c96eb4734b57" class="plotly-graph-div" style="height:420px; width:100%;"></div>            <script type="text/javascript">                window.PLOTLYENV=window.PLOTLYENV || {};                                if (document.getElementById("a5f68c3c-583b-435b-9a63-c96eb4734b57")) {                    Plotly.newPlot(                        "a5f68c3c-583b-435b-9a63-c96eb4734b57",                        [{"hovertemplate":"%{y:.4f}","line":{"color":"#3f3f46","width":2},"marker":{"color":"#3f3f46","size":6},"mode":"lines+markers","name":"fit on 1,000 docs","x":{"dtype":"i2","bdata":"IABAAIAAAAEAAg=="},"y":{"dtype":"f8","bdata":"aE1h87n7zD+q\u002fS50UdPUP6fvKnm1Wtg\u002f0XG5iOqZ2j\u002foyjNJ3YfbPw=="},"type":"scatter"},{"hovertemplate":"%{y:.4f}","line":{"color":"#71717a","width":2},"marker":{"color":"#71717a","size":6},"mode":"lines+markers","name":"fit on 5,000 docs","x":{"dtype":"i2","bdata":"IABAAIAAAAEAAg=="},"y":{"dtype":"f8","bdata":"1u7O\u002fq0IzD9aRHFQcBHVP9e2FrAQkdg\u002fth\u002flNWzq2j\u002f8DuelUsXbPw=="},"type":"scatter"},{"hovertemplate":"%{y:.4f}","line":{"color":"#a1a1aa","width":2},"marker":{"color":"#a1a1aa","size":6},"mode":"lines+markers","name":"fit on 20,000 docs","x":{"dtype":"i2","bdata":"IABAAIAAAAEAAg=="},"y":{"dtype":"f8","bdata":"cMrIgHX8zD9QqAWpx0bVP4QNHrwcftg\u002fke\u002fxd3HD2j9MvpBALKfbPw=="},"type":"scatter"},{"hovertemplate":"%{y:.4f}","line":{"color":"#eb841b","width":2},"marker":{"color":"#eb841b","size":6},"mode":"lines+markers","name":"fit on 57,638 docs","x":{"dtype":"i2","bdata":"IABAAIAAAAEAAg=="},"y":{"dtype":"f8","bdata":"qFg0DV5CzT8K0k6\u002fIT\u002fVP0xk\u002fMrndNg\u002fiFZCASvK2j9RDoCYiK7bPw=="},"type":"scatter"}],                        {"template":{"data":{"scatter":[{"type":"scatter"}]},"layout":{"margin":{"b":0,"l":0,"r":0,"t":30}}},"title":{"font":{"size":15,"color":"#e4e4e7"},"text":"In-domain PCA on fiqa: NDCG@10 by fit-sample size","x":0.5,"xanchor":"center"},"font":{"family":"Georgia, serif","size":13,"color":"#e4e4e7"},"margin":{"l":10,"r":10,"t":60,"b":40},"legend":{"font":{"size":12,"color":"#a1a1aa"},"orientation":"h","yanchor":"bottom","y":-0.25,"x":0.5,"xanchor":"center"},"hoverlabel":{"font":{"family":"Georgia, serif","size":12,"color":"#e4e4e7"},"bgcolor":"#27272a","bordercolor":"#eb841b","align":"left"},"paper_bgcolor":"#18181b","plot_bgcolor":"#18181b","height":420,"showlegend":true,"dragmode":false,"xaxis":{"gridcolor":"#3f3f46","zeroline":false,"linecolor":"#3f3f46","tickcolor":"#3f3f46","automargin":true,"title":{"font":{"size":12,"color":"#a1a1aa"},"text":"dimensions"},"type":"log","tickvals":[32,64,128,256,512]},"yaxis":{"gridcolor":"#3f3f46","zeroline":false,"linecolor":"#3f3f46","tickcolor":"#3f3f46","automargin":true},"hovermode":"x unified"},                        {"displayModeBar": false, "responsive": true}                    ).then(function(){
                            
var gd = document.getElementById('a5f68c3c-583b-435b-9a63-c96eb4734b57');
var x = new MutationObserver(function (mutations, observer) {{
        var display = window.getComputedStyle(gd).display;
        if (!display || display === 'none') {{
            console.log([gd, 'removed!']);
            Plotly.purge(gd);
            observer.disconnect();
        }}
}});

// Listen for the removal of the full notebook cells
var notebookContainer = gd.closest('#notebook-container');
if (notebookContainer) {{
    x.observe(notebookContainer, {childList: true});
}}

// Listen for the clearing of the current output cell
var outputEl = gd.closest('.output');
if (outputEl) {{
    x.observe(outputEl, {childList: true});
}}

                        })                };            </script>        </div>
</div>
</div>
</div>
<div id="tabset-3-2" class="tab-pane" aria-labelledby="tabset-3-2-tab">
<div id="45719a1b" class="cell" data-execution_count="8">
<div class="cell-output cell-output-display">
<div>            <script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-AMS-MML_SVG"></script><script type="text/javascript">if (window.MathJax && window.MathJax.Hub && window.MathJax.Hub.Config) {window.MathJax.Hub.Config({SVG: {font: "STIX-Web"}});}</script>                <script type="text/javascript">window.PlotlyConfig = {MathJaxConfig: 'local'};</script>
        <script charset="utf-8" src="https://cdn.plot.ly/plotly-3.0.1.min.js"></script>                <div id="136a95ab-a50e-4120-a64e-8a41c4dbdce0" class="plotly-graph-div" style="height:420px; width:100%;"></div>            <script type="text/javascript">                window.PLOTLYENV=window.PLOTLYENV || {};                                if (document.getElementById("136a95ab-a50e-4120-a64e-8a41c4dbdce0")) {                    Plotly.newPlot(                        "136a95ab-a50e-4120-a64e-8a41c4dbdce0",                        [{"hovertemplate":"%{y:.4f}","line":{"color":"#3f3f46","width":2},"marker":{"color":"#3f3f46","size":6},"mode":"lines+markers","name":"fit on 1,000 docs","x":{"dtype":"i2","bdata":"IABAAIAAAAEAAg=="},"y":{"dtype":"f8","bdata":"PIru2HVH3D9GIwOU8DnhPz9\u002fiVpcwuI\u002fj5EHZCnU4z+e1c5wuVPkPw=="},"type":"scatter"},{"hovertemplate":"%{y:.4f}","line":{"color":"#71717a","width":2},"marker":{"color":"#71717a","size":6},"mode":"lines+markers","name":"fit on 5,000 docs","x":{"dtype":"i2","bdata":"IABAAIAAAAEAAg=="},"y":{"dtype":"f8","bdata":"UpqfTYS62z8eC+ht7i7hP5E27yuj+uI\u002fyQDPRevz4z\u002fGuC5vz13kPw=="},"type":"scatter"},{"hovertemplate":"%{y:.4f}","line":{"color":"#a1a1aa","width":2},"marker":{"color":"#a1a1aa","size":6},"mode":"lines+markers","name":"fit on 20,000 docs","x":{"dtype":"i2","bdata":"IABAAIAAAAEAAg=="},"y":{"dtype":"f8","bdata":"HzmjKvlD3D+GTEZevyrhP4Y22OQMAeM\u002f8+Fjjxv84z+DEQG5N2nkPw=="},"type":"scatter"},{"hovertemplate":"%{y:.4f}","line":{"color":"#eb841b","width":2},"marker":{"color":"#eb841b","size":6},"mode":"lines+markers","name":"fit on 57,638 docs","x":{"dtype":"i2","bdata":"IABAAIAAAAEAAg=="},"y":{"dtype":"f8","bdata":"1E\u002fOCFsU3D\u002f1k8t+xjHhP9H7fpP6COM\u002f3OqS0I7y4z+b0puwMX7kPw=="},"type":"scatter"}],                        {"template":{"data":{"scatter":[{"type":"scatter"}]},"layout":{"margin":{"b":0,"l":0,"r":0,"t":30}}},"title":{"font":{"size":15,"color":"#e4e4e7"},"text":"In-domain PCA on fiqa: NDCG@10 by fit-sample size","x":0.5,"xanchor":"center"},"font":{"family":"Georgia, serif","size":13,"color":"#e4e4e7"},"margin":{"l":10,"r":10,"t":60,"b":40},"legend":{"font":{"size":12,"color":"#a1a1aa"},"orientation":"h","yanchor":"bottom","y":-0.25,"x":0.5,"xanchor":"center"},"hoverlabel":{"font":{"family":"Georgia, serif","size":12,"color":"#e4e4e7"},"bgcolor":"#27272a","bordercolor":"#eb841b","align":"left"},"paper_bgcolor":"#18181b","plot_bgcolor":"#18181b","height":420,"showlegend":true,"dragmode":false,"xaxis":{"gridcolor":"#3f3f46","zeroline":false,"linecolor":"#3f3f46","tickcolor":"#3f3f46","automargin":true,"title":{"font":{"size":12,"color":"#a1a1aa"},"text":"dimensions"},"type":"log","tickvals":[32,64,128,256,512]},"yaxis":{"gridcolor":"#3f3f46","zeroline":false,"linecolor":"#3f3f46","tickcolor":"#3f3f46","automargin":true},"hovermode":"x unified"},                        {"displayModeBar": false, "responsive": true}                    ).then(function(){
                            
var gd = document.getElementById('136a95ab-a50e-4120-a64e-8a41c4dbdce0');
var x = new MutationObserver(function (mutations, observer) {{
        var display = window.getComputedStyle(gd).display;
        if (!display || display === 'none') {{
            console.log([gd, 'removed!']);
            Plotly.purge(gd);
            observer.disconnect();
        }}
}});

// Listen for the removal of the full notebook cells
var notebookContainer = gd.closest('#notebook-container');
if (notebookContainer) {{
    x.observe(notebookContainer, {childList: true});
}}

// Listen for the clearing of the current output cell
var outputEl = gd.closest('.output');
if (outputEl) {{
    x.observe(outputEl, {childList: true});
}}

                        })                };            </script>        </div>
</div>
</div>
</div>
</div>
</div>
<p><strong>Out-of-domain fit.</strong> In this scenario, the data you fit on doesn’t match the data you end up searching, either because your corpus drifted over time or because you fit on whatever was available. To simulate the extreme version, I fit PCA once on 100K generic MS MARCO passages and searched every other dataset with that projection.</p>
<p>On <code>text-embedding-3-small</code>, the transferred fit is as good or better on average from 512 through 64 dimensions. Individual datasets vary more in both directions. On <code>qwen3-embedding-8b</code> it keeps up down to 128 dimensions but falls behind at lower dimensions (56% vs 71% retained at 32 dims).</p>
<div class="tabset-margin-container"></div><div class="panel-tabset">
<ul class="nav nav-tabs"><li class="nav-item"><a class="nav-link active" id="tabset-4-1-tab" data-bs-toggle="tab" data-bs-target="#tabset-4-1" aria-controls="tabset-4-1" aria-selected="true" href="">3-small</a></li><li class="nav-item"><a class="nav-link" id="tabset-4-2-tab" data-bs-toggle="tab" data-bs-target="#tabset-4-2" aria-controls="tabset-4-2" aria-selected="false" href="">qwen3-8b</a></li></ul>
<div class="tab-content">
<div id="tabset-4-1" class="tab-pane active" aria-labelledby="tabset-4-1-tab">
<div id="30b74396" class="cell" data-execution_count="9">
<div class="cell-output cell-output-display">
<div>            <script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-AMS-MML_SVG"></script><script type="text/javascript">if (window.MathJax && window.MathJax.Hub && window.MathJax.Hub.Config) {window.MathJax.Hub.Config({SVG: {font: "STIX-Web"}});}</script>                <script type="text/javascript">window.PlotlyConfig = {MathJaxConfig: 'local'};</script>
        <script charset="utf-8" src="https://cdn.plot.ly/plotly-3.0.1.min.js"></script>                <div id="381a6fbf-c534-41b8-a862-ea417f1c3df0" class="plotly-graph-div" style="height:440px; width:100%;"></div>            <script type="text/javascript">                window.PLOTLYENV=window.PLOTLYENV || {};                                if (document.getElementById("381a6fbf-c534-41b8-a862-ea417f1c3df0")) {                    Plotly.newPlot(                        "381a6fbf-c534-41b8-a862-ea417f1c3df0",                        [{"customdata":{"dtype":"f8","bdata":"OSkgpFwr1z89KVLsQ2TcPwJcQq2N7t4\u002f1hQEABsq4D958zZbDnDgPw=="},"hovertemplate":"\u003cb\u003ePCA\u003c\u002fb\u003e\u003cbr\u003ed=%{x}\u003cbr\u003eretention=%{y:.1%}\u003cbr\u003eavg ndcg@10=%{customdata:.4f}\u003cextra\u003e\u003c\u002fextra\u003e","legendgroup":"pca_in","line":{"color":"#60a5fa","dash":"solid","width":2},"marker":{"color":"#60a5fa","size":6},"mode":"lines+markers","name":"PCA","showlegend":true,"x":{"dtype":"i2","bdata":"IABAAIAAAAEAAg=="},"y":{"dtype":"f8","bdata":"9uFdmqun5D\u002fqI1FNXRzqP6uW0S+yyew\u002fR5mMooVe7j8eQ\u002fJzcvjuPw=="},"type":"scatter","xaxis":"x","yaxis":"y"},{"customdata":{"dtype":"f8","bdata":"\u002fuzEI5XS1j+4AsAOwcfcP5NsEShX8d8\u002fVTAoJqeV4D8Ijzns98LgPw=="},"hovertemplate":"\u003cb\u003ePCA (fit on MS MARCO)\u003c\u002fb\u003e\u003cbr\u003ed=%{x}\u003cbr\u003eretention=%{y:.1%}\u003cbr\u003eavg ndcg@10=%{customdata:.4f}\u003cextra\u003e\u003c\u002fextra\u003e","legendgroup":"pca_ood","line":{"color":"#f87171","dash":"solid","width":2},"marker":{"color":"#f87171","size":6},"mode":"lines+markers","name":"PCA (fit on MS MARCO)","showlegend":true,"x":{"dtype":"i2","bdata":"IABAAIAAAAEAAg=="},"y":{"dtype":"f8","bdata":"lqUEmILz4z89m4xqEUbqPwANFhcVte0\u002fwube\u002fgMs7z8DwvPvSpHvPw=="},"type":"scatter","xaxis":"x","yaxis":"y"}],                        {"template":{"data":{"scatter":[{"type":"scatter"}]},"layout":{"margin":{"b":0,"l":0,"r":0,"t":30}}},"xaxis":{"anchor":"y","domain":[0.0,1.0],"gridcolor":"#3f3f46","zeroline":false,"linecolor":"#3f3f46","tickcolor":"#3f3f46","automargin":true,"title":{"font":{"size":12,"color":"#a1a1aa"},"text":"dimensions"},"type":"log","tickvals":[32,64,128,256,512]},"yaxis":{"anchor":"x","domain":[0.0,1.0],"gridcolor":"#3f3f46","zeroline":false,"linecolor":"#3f3f46","tickcolor":"#3f3f46","automargin":true,"tickformat":".0%"},"annotations":[{"font":{"color":"#e4e4e7","size":13},"showarrow":false,"text":"3-small (MRL)","x":0.5,"xanchor":"center","xref":"paper","y":1.0,"yanchor":"bottom","yref":"paper"}],"shapes":[{"line":{"color":"#a1a1aa","dash":"dash","width":1},"type":"line","x0":0,"x1":1,"xref":"x domain","y0":1.0,"y1":1.0,"yref":"y"}],"title":{"font":{"size":15,"color":"#e4e4e7"},"text":"Share of full-dim NDCG@10 retained (mean of 8 datasets)","x":0.5,"xanchor":"center"},"font":{"family":"Georgia, serif","size":13,"color":"#e4e4e7"},"margin":{"l":10,"r":10,"t":60,"b":40},"legend":{"font":{"size":12,"color":"#a1a1aa"},"orientation":"h","yanchor":"bottom","y":-0.25,"x":0.5,"xanchor":"center"},"hoverlabel":{"font":{"family":"Georgia, serif","size":12,"color":"#e4e4e7"},"bgcolor":"#27272a","bordercolor":"#eb841b","align":"left"},"paper_bgcolor":"#18181b","plot_bgcolor":"#18181b","height":440,"showlegend":true,"dragmode":false},                        {"displayModeBar": false, "responsive": true}                    ).then(function(){
                            
var gd = document.getElementById('381a6fbf-c534-41b8-a862-ea417f1c3df0');
var x = new MutationObserver(function (mutations, observer) {{
        var display = window.getComputedStyle(gd).display;
        if (!display || display === 'none') {{
            console.log([gd, 'removed!']);
            Plotly.purge(gd);
            observer.disconnect();
        }}
}});

// Listen for the removal of the full notebook cells
var notebookContainer = gd.closest('#notebook-container');
if (notebookContainer) {{
    x.observe(notebookContainer, {childList: true});
}}

// Listen for the clearing of the current output cell
var outputEl = gd.closest('.output');
if (outputEl) {{
    x.observe(outputEl, {childList: true});
}}

                        })                };            </script>        </div>
</div>
</div>
</div>
<div id="tabset-4-2" class="tab-pane" aria-labelledby="tabset-4-2-tab">
<div id="14e843e3" class="cell" data-execution_count="10">
<div class="cell-output cell-output-display">
<div>            <script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-AMS-MML_SVG"></script><script type="text/javascript">if (window.MathJax && window.MathJax.Hub && window.MathJax.Hub.Config) {window.MathJax.Hub.Config({SVG: {font: "STIX-Web"}});}</script>                <script type="text/javascript">window.PlotlyConfig = {MathJaxConfig: 'local'};</script>
        <script charset="utf-8" src="https://cdn.plot.ly/plotly-3.0.1.min.js"></script>                <div id="28adccdb-d172-488a-a294-4f2ff2064d94" class="plotly-graph-div" style="height:440px; width:100%;"></div>            <script type="text/javascript">                window.PLOTLYENV=window.PLOTLYENV || {};                                if (document.getElementById("28adccdb-d172-488a-a294-4f2ff2064d94")) {                    Plotly.newPlot(                        "28adccdb-d172-488a-a294-4f2ff2064d94",                        [{"customdata":{"dtype":"f8","bdata":"XiK4xTJz3j\u002fJ2DUWC7\u002fhP7aeq6kRFeM\u002fq2nZciXO4z+7MWLhnCrkPw=="},"hovertemplate":"\u003cb\u003ePCA\u003c\u002fb\u003e\u003cbr\u003ed=%{x}\u003cbr\u003eretention=%{y:.1%}\u003cbr\u003eavg ndcg@10=%{customdata:.4f}\u003cextra\u003e\u003c\u002fextra\u003e","legendgroup":"pca_in","line":{"color":"#60a5fa","dash":"solid","width":2},"marker":{"color":"#60a5fa","size":6},"mode":"lines+markers","name":"PCA","showlegend":true,"x":{"dtype":"i2","bdata":"IABAAIAAAAEAAg=="},"y":{"dtype":"f8","bdata":"av0iIq\u002fJ5j+D2UvB6tjqP9acApe+Pu0\u002f124jygCY7j9cT5SHijjvPw=="},"type":"scatter","xaxis":"x","yaxis":"y"},{"customdata":{"dtype":"f8","bdata":"0P3asv402D\u002fy97gFk6zgP4m+wbGh4+I\u002fejILi6234z8C0gqJdSrkPw=="},"hovertemplate":"\u003cb\u003ePCA (fit on MS MARCO)\u003c\u002fb\u003e\u003cbr\u003ed=%{x}\u003cbr\u003eretention=%{y:.1%}\u003cbr\u003eavg ndcg@10=%{customdata:.4f}\u003cextra\u003e\u003c\u002fextra\u003e","legendgroup":"pca_ood","line":{"color":"#f87171","dash":"solid","width":2},"marker":{"color":"#f87171","size":6},"mode":"lines+markers","name":"PCA (fit on MS MARCO)","showlegend":true,"x":{"dtype":"i2","bdata":"IABAAIAAAAEAAg=="},"y":{"dtype":"f8","bdata":"95vcHZzO4T\u002f5M7IjMifpP3bVcQOq2uw\u002fDhrEphVd7j\u002fKwi1SrivvPw=="},"type":"scatter","xaxis":"x","yaxis":"y"}],                        {"template":{"data":{"scatter":[{"type":"scatter"}]},"layout":{"margin":{"b":0,"l":0,"r":0,"t":30}}},"xaxis":{"anchor":"y","domain":[0.0,1.0],"gridcolor":"#3f3f46","zeroline":false,"linecolor":"#3f3f46","tickcolor":"#3f3f46","automargin":true,"title":{"font":{"size":12,"color":"#a1a1aa"},"text":"dimensions"},"type":"log","tickvals":[32,64,128,256,512]},"yaxis":{"anchor":"x","domain":[0.0,1.0],"gridcolor":"#3f3f46","zeroline":false,"linecolor":"#3f3f46","tickcolor":"#3f3f46","automargin":true,"tickformat":".0%"},"annotations":[{"font":{"color":"#e4e4e7","size":13},"showarrow":false,"text":"qwen3-8b (MRL)","x":0.5,"xanchor":"center","xref":"paper","y":1.0,"yanchor":"bottom","yref":"paper"}],"shapes":[{"line":{"color":"#a1a1aa","dash":"dash","width":1},"type":"line","x0":0,"x1":1,"xref":"x domain","y0":1.0,"y1":1.0,"yref":"y"}],"title":{"font":{"size":15,"color":"#e4e4e7"},"text":"Share of full-dim NDCG@10 retained (mean of 8 datasets)","x":0.5,"xanchor":"center"},"font":{"family":"Georgia, serif","size":13,"color":"#e4e4e7"},"margin":{"l":10,"r":10,"t":60,"b":40},"legend":{"font":{"size":12,"color":"#a1a1aa"},"orientation":"h","yanchor":"bottom","y":-0.25,"x":0.5,"xanchor":"center"},"hoverlabel":{"font":{"family":"Georgia, serif","size":12,"color":"#e4e4e7"},"bgcolor":"#27272a","bordercolor":"#eb841b","align":"left"},"paper_bgcolor":"#18181b","plot_bgcolor":"#18181b","height":440,"showlegend":true,"dragmode":false},                        {"displayModeBar": false, "responsive": true}                    ).then(function(){
                            
var gd = document.getElementById('28adccdb-d172-488a-a294-4f2ff2064d94');
var x = new MutationObserver(function (mutations, observer) {{
        var display = window.getComputedStyle(gd).display;
        if (!display || display === 'none') {{
            console.log([gd, 'removed!']);
            Plotly.purge(gd);
            observer.disconnect();
        }}
}});

// Listen for the removal of the full notebook cells
var notebookContainer = gd.closest('#notebook-container');
if (notebookContainer) {{
    x.observe(notebookContainer, {childList: true});
}}

// Listen for the clearing of the current output cell
var outputEl = gd.closest('.output');
if (outputEl) {{
    x.observe(outputEl, {childList: true});
}}

                        })                };            </script>        </div>
</div>
</div>
</div>
</div>
</div>
<p>Good news for lazy people like me. Fit-sample size matters much less than I expected, and an out-of-domain fit holds up at moderate compression. However, these patterns may be a consequence of the scale of the experiments or the particular benchmarks I used. So take these with a grain of salt.</p>
</section>
<section id="what-about-quantization" class="level2">
<h2 class="anchored" data-anchor-id="what-about-quantization">What about quantization?</h2>
<p>You can also shrink vectors using a technique called quantization. Instead of reducing the number of dimensions, quantization stores each dimension using fewer bits. With <code>int8</code>, each dimension uses 1 byte (4x smaller than <code>float32</code>); with binary, just the sign bit (32x smaller). For the numbers below, int8 quantizes the document vectors while keeping queries at float32; binary quantizes both documents and queries and ranks by dot product, which is equivalent to Hamming-distance ranking.</p>
<p>Using quantization alone, you get these results for <code>text-embedding-3-small</code> at full 1,536 dimensions:</p>
<div class="table-responsive">
<table class="table">
<thead>
<tr class="header">
<th>config</th>
<th>bytes per vector</th>
<th>size vs full float32</th>
<th>quality retained</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>int8</td>
<td>1,536</td>
<td>25%</td>
<td>100%</td>
</tr>
<tr class="even">
<td>binary</td>
<td>192</td>
<td>3.1%</td>
<td>95%</td>
</tr>
</tbody>
</table>
</div>
<p>You can push this further by combining quantization with truncation or PCA. The resulting vectors can be dramatically smaller while still preserving a surprising amount of retrieval quality.</p>
<p>For example, combining binary quantization with PCA at 512 dimensions retains 82% of the original performance while reducing the raw vector size to about 1% of the float32 baseline. That seems a bit too optimistic, so it might just be my AI psychosis. Here are the full <a href="https://github.com/dylanjcastillo/blog/tree/main/_extras/matryoshka-vs-pca/data/analysis/openai__text-embedding-3-small/quantization_summary.csv">results</a>.</p>
</section>
<section id="limitations" class="level2">
<h2 class="anchored" data-anchor-id="limitations">Limitations</h2>
<ul>
<li><strong>Few embedding models</strong>: I tested three models, and two of them are from OpenAI. Qwen already showed that a stronger MRL implementation narrows PCA’s lead, so other model families could shift the results further.</li>
<li><strong>Corpus size</strong>: I excluded the largest BEIR datasets to keep the budget under control (~$30 spent), so it’s possible PCA falls behind MRL at a scale I didn’t reach.</li>
<li><strong>Lab conditions</strong>: my evaluation runs exact search over raw vectors, which is not how an actual vector database works. A real deployment would use an approximate index, apply its own compression, rescore with full-precision vectors, and often mix in BM25 and a reranker. This could invalidate my results.</li>
<li><strong>Benchmark contamination</strong>: BEIR datasets are widely used to train embedding models, so the absolute scores are probably inflated.</li>
</ul>
</section>
<section id="conclusion" class="level2">
<h2 class="anchored" data-anchor-id="conclusion">Conclusion</h2>
<p>PCA not only held its own against MRL truncation, it won on most dimensions.</p>
<p>On <code>text-embedding-3-small</code>, PCA beat MRL truncation at nearly every dimension, and by a wide margin at aggressive compression: 65% of quality retained at 32 dims against truncation’s 46%. On <code>qwen3-embedding-8b</code>, MRL had a small edge at 512 dimensions and was effectively tied at 256, while PCA led below that.</p>
<p>These results also don’t appear to be an artifact of MRL training. PCA worked similarly well on ada-002, which wasn’t trained for truncation.</p>
<p>It’s also lower-maintenance than I expected. Results barely changed when fitting on a small in-domain sample. An out-of-domain fit also held up at moderate compression.</p>
<p>Not bad for a technique that’s older than the washing machine<sup>2</sup>!</p>
<p>If you want to look at the data yourself, the full pipeline is in the <a href="https://github.com/dylanjcastillo/blog/tree/main/_extras/matryoshka-vs-pca">repo</a>.</p>


</section>


<div id="quarto-appendix" class="default"><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Footnotes</h2>

<ol>
<li id="fn1"><p>this <a href="https://setosa.io/ev/principal-component-analysis/">page</a> explains it quite well↩︎</p></li>
<li id="fn2"><p>electric washing machine↩︎</p></li>
</ol>
</section><section class="quarto-appendix-contents" id="quarto-citation"><h2 class="anchored quarto-appendix-heading">Citation</h2><div><div class="quarto-appendix-secondary-label">BibTeX citation:</div><pre class="sourceCode code-with-copy quarto-appendix-bibtex"><code class="sourceCode bibtex">@online{castillo2026,
  author = {Castillo, Dylan},
  title = {Honey, {I} Shrunk the Embeddings: {Matryoshka} Vs. {PCA}},
  date = {2026-08-01},
  url = {https://dylancastillo.co/posts/matryoshka-vs-pca.html},
  langid = {en}
}
</code></pre><div class="quarto-appendix-secondary-label">For attribution, please cite this work as:</div><div id="ref-castillo2026" class="csl-entry quarto-appendix-citeas">
Castillo, Dylan. 2026. <span>“Honey, I Shrunk the Embeddings: Matryoshka
Vs. PCA.”</span> August 1. <a href="https://dylancastillo.co/posts/matryoshka-vs-pca.html">https://dylancastillo.co/posts/matryoshka-vs-pca.html</a>.
</div></div></section></div> ]]></description>
  <category>llm</category>
  <category>evals</category>
  <guid>https://dylancastillo.co/posts/matryoshka-vs-pca.html</guid>
  <pubDate>Sat, 01 Aug 2026 00:00:00 GMT</pubDate>
  <media:content url="https://dylancastillo.co/posts/images/cards/matryoshka-vs-pca.png" medium="image" type="image/png" height="76" width="144"/>
</item>
<item>
  <title>Are AI labs pelicanmaxxing?</title>
  <dc:creator>Dylan Castillo</dc:creator>
  <link>https://dylancastillo.co/posts/pelicanmaxxing.html</link>
  <description><![CDATA[ 




<p>For the past few years, <a href="https://simonwillison.net/tags/pelican-riding-a-bicycle/">Simon Willison</a> has tested every major LLM release with the same prompt: “Generate an SVG of a pelican riding a bicycle”.</p>
<p>What began as a tongue-in-cheek benchmark has become one of the most famous informal benchmarks in AI. Simon’s pelican-on-a-bicycle results are often among the most upvoted comments on Hacker News threads announcing new releases from AI labs.</p>
<p>The benchmark is now famous enough that there’s <a href="https://news.ycombinator.com/item?id=48445168">plenty</a> <a href="https://news.ycombinator.com/item?id=48994217">of</a> <a href="https://news.ycombinator.com/item?id=48956159">discussion</a> about its usefulness and about whether AI labs might be benchmaxxing<sup>1</sup> on it. When billions or even trillions of dollars are at stake, and a strong result could help persuade users, wouldn’t it be tempting to <em>pelicanmaxx</em> your model just a bit?</p>
<p>I wanted to find out, so I put together a small experiment. I generated 1,008 SVGs across seven frontier models, scored them with an LLM judge, and used Claude Fable 5 for the analysis.</p>
<p>This article presents the results. All the code is available on <a href="https://github.com/dylanjcastillo/blog/tree/main/_extras/pelicanmaxxing">Github</a>.</p>
<section id="how-i-tested-it" class="level2">
<h2 class="anchored" data-anchor-id="how-i-tested-it">How I tested it</h2>
<p>I built a grid of 8 animals × 6 vehicles = 48 prompts, where the famous prompt is one cell:</p>
<ol type="1">
<li><strong>Animals</strong>: pelican, flamingo, heron, otter, raccoon, antelope, whale, cat</li>
<li><strong>Vehicles</strong>: bicycle, unicycle, skateboard, scooter, plane, boat</li>
</ol>
<p>Every prompt uses almost identical phrasing to Simon’s, only switching the animal and vehicle. The animal and vehicle selection wasn’t done in a very rigorous manner, but I tried to vary both similarity to the original prompt and difficulty. Flamingo and heron are quite similar to pelicans; cat, raccoon, and otter are easy cases; antelope is hard; and whale is as different as you can get.</p>
<p>I tested seven models through <a href="https://openrouter.ai/">OpenRouter</a>: <em>GPT-5.6 Terra</em>, <em>Claude Sonnet 5</em>, <em>Gemini 3.5 Flash</em>, <em>Grok 4.5</em>, <em>Qwen3.7-Max</em>, <em>GLM-5.2</em>, and <em>DeepSeek V4 Pro</em>. I generated 3 samples per prompt, at temperature 1.0, requesting the same reasoning effort from every model. That resulted in 1,008 SVGs.</p>
<p>Then I ran each image through a three-stage pipeline:</p>
<ol type="1">
<li><strong>Rendering</strong>: Each SVG is rendered to PNG. If a model returns no SVG or one that fails to render, I regenerate until it produces a valid one, and record the number of attempts. There were only 11 retries across the 1,008 generations.</li>
<li><strong>Judging</strong>: <em>GPT-5.6 Luna</em> scores each image with 1-5 ratings for the animal, the vehicle, and the coherence of the action. When I rank animals or vehicles below, I use the matching rating on its own. When I need one number per image, I use the average of the three, which I call the judge score.</li>
<li><strong>Feature extraction</strong>: For a more detailed analysis, I also passed each rendered image to <em>Gemini 3.1 Flash-Lite</em>, which recorded the animal and vehicle it recognized, which way the subject faces, and an open-ended list of scene elements.</li>
</ol>
<p>My hypothesis is that if a lab trained on the benchmark, it should show up in some combination of the pelican row scoring above what the animal deserves, the bicycle column scoring above what the vehicle deserves, or the specific pelican-bicycle cell beating both.</p>
</section>
<section id="evidence-1-the-pelicans-on-bicycles-dont-look-any-better" class="level2">
<h2 class="anchored" data-anchor-id="evidence-1-the-pelicans-on-bicycles-dont-look-any-better">Evidence #1: The pelicans on bicycles don’t look any better</h2>
<p>Before any scoring, the simplest test is to look at the images yourself. Pick a lab to see everything it drew, with the judge’s score under each image (click to open full size):</p>
<div class="pelican-explorer" data-config="{
       &quot;base&quot;: &quot;https://s3.eu-west-1.amazonaws.com/images.dylancastillo.co/pelicanmaxxing&quot;,
       &quot;models&quot;: [
         {&quot;slug&quot;: &quot;openai__gpt-5.6-terra&quot;, &quot;name&quot;: &quot;GPT-5.6 Terra&quot;},
         {&quot;slug&quot;: &quot;anthropic__claude-sonnet-5&quot;, &quot;name&quot;: &quot;Claude Sonnet 5&quot;},
         {&quot;slug&quot;: &quot;google__gemini-3.5-flash&quot;, &quot;name&quot;: &quot;Gemini 3.5 Flash&quot;},
         {&quot;slug&quot;: &quot;x-ai__grok-4.5&quot;, &quot;name&quot;: &quot;Grok 4.5&quot;},
         {&quot;slug&quot;: &quot;qwen__qwen3.7-max&quot;, &quot;name&quot;: &quot;Qwen3.7-Max&quot;},
         {&quot;slug&quot;: &quot;z-ai__glm-5.2&quot;, &quot;name&quot;: &quot;GLM-5.2&quot;},
         {&quot;slug&quot;: &quot;deepseek__deepseek-v4-pro&quot;, &quot;name&quot;: &quot;DeepSeek V4 Pro&quot;}
       ],
       &quot;animals&quot;: [&quot;pelican&quot;, &quot;flamingo&quot;, &quot;heron&quot;, &quot;otter&quot;, &quot;raccoon&quot;, &quot;antelope&quot;, &quot;whale&quot;, &quot;cat&quot;],
       &quot;vehicles&quot;: [&quot;bicycle&quot;, &quot;unicycle&quot;, &quot;skateboard&quot;, &quot;scooter&quot;, &quot;plane&quot;, &quot;boat&quot;],
       &quot;samples&quot;: 3
     }">

</div>
<p>I looked through the images myself before running the analysis below. Nothing jumped out at me. I couldn’t find a case where the pelican-bicycle images looked noticeably better than the rest of that model’s grid. Maybe in GLM-5.2’s first sample it felt slightly better than the rest, but that batch also produced a pretty cool heron on a skateboard, so I cannot say for sure. Otherwise they look like the rest of what each model draws, and the labs that draw good pelicans on bicycles also do a good job drawing other animal-vehicle combinations.</p>
<p>But this test is hard to replicate, and everyone will have a different opinion. So I wanted something more quantitative, which is why I opted for the method detailed above.</p>
</section>
<section id="evidence-2-labs-are-not-better-at-drawing-pelicans" class="level2 page-columns page-full">
<h2 class="anchored" data-anchor-id="evidence-2-labs-are-not-better-at-drawing-pelicans">Evidence #2: Labs are not better at drawing pelicans</h2>
<p>Here’s the mean animal rating per animal, pooled across all models:</p>
<div id="cell-fig-animal-scores" class="cell page-columns page-full" data-execution_count="2">
<div id="fig-animal-scores" class="cell-output cell-output-display quarto-float quarto-figure quarto-figure-center anchored page-columns page-full">
<figure class="quarto-float quarto-float-fig figure page-columns page-full">
<div aria-describedby="fig-animal-scores-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<div>            <script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-AMS-MML_SVG"></script><script type="text/javascript">if (window.MathJax && window.MathJax.Hub && window.MathJax.Hub.Config) {window.MathJax.Hub.Config({SVG: {font: "STIX-Web"}});}</script>                <script type="text/javascript">window.PlotlyConfig = {MathJaxConfig: 'local'};</script>
        <script charset="utf-8" src="https://cdn.plot.ly/plotly-3.0.1.min.js"></script>                <div id="80ebaaa3-fc94-40af-8858-e25f63e147fc" class="plotly-graph-div" style="height:340px; width:100%;"></div>            <script type="text/javascript">                window.PLOTLYENV=window.PLOTLYENV || {};                                if (document.getElementById("80ebaaa3-fc94-40af-8858-e25f63e147fc")) {                    Plotly.newPlot(                        "80ebaaa3-fc94-40af-8858-e25f63e147fc",                        [{"hovertemplate":"\u003cb\u003e%{y}\u003c\u002fb\u003e\u003cbr\u003e%{x:.2f} out of 5\u003cextra\u003e\u003c\u002fextra\u003e","marker":{"color":["#71717a","#71717a","#eb841b","#71717a","#71717a","#71717a","#71717a","#71717a"]},"orientation":"h","text":["3.95","4.07","4.08","4.21","4.42","4.48","4.62","4.63"],"textfont":{"color":"#a1a1aa","size":12},"textposition":"outside","width":0.6,"x":{"dtype":"f8","bdata":"6Xme53meD0CSJEmSJEkQQBRFURRFURBAt23btm3bEEDsuq7ruq4RQHqe53me5xFAnud5nud5EkCjKIqiKIoSQA=="},"y":["otter","flamingo","pelican","antelope","heron","raccoon","whale","cat"],"type":"bar"}],                        {"template":{"data":{"scatter":[{"type":"scatter"}]},"layout":{"margin":{"b":0,"l":0,"r":0,"t":30}}},"title":{"font":{"size":15,"color":"#e4e4e7"},"text":"Mean animal rating by animal (1-5)","x":0.5,"xanchor":"center"},"font":{"family":"Georgia, serif","size":13,"color":"#e4e4e7"},"margin":{"l":10,"r":10,"t":60,"b":40},"hoverlabel":{"font":{"family":"Georgia, serif","size":12,"color":"#e4e4e7"},"bgcolor":"#27272a","bordercolor":"#eb841b","align":"left"},"paper_bgcolor":"#18181b","plot_bgcolor":"#18181b","height":340,"showlegend":false,"dragmode":false,"xaxis":{"gridcolor":"#3f3f46","zeroline":false,"linecolor":"#3f3f46","tickcolor":"#3f3f46","automargin":true,"showgrid":false,"showticklabels":false,"visible":false,"range":[0,5.33015873015873]},"yaxis":{"gridcolor":"#3f3f46","zeroline":false,"linecolor":"#3f3f46","tickcolor":"#3f3f46","automargin":true,"showgrid":false}},                        {"displayModeBar": false, "responsive": true}                    ).then(function(){
                            
var gd = document.getElementById('80ebaaa3-fc94-40af-8858-e25f63e147fc');
var x = new MutationObserver(function (mutations, observer) {{
        var display = window.getComputedStyle(gd).display;
        if (!display || display === 'none') {{
            console.log([gd, 'removed!']);
            Plotly.purge(gd);
            observer.disconnect();
        }}
}});

// Listen for the removal of the full notebook cells
var notebookContainer = gd.closest('#notebook-container');
if (notebookContainer) {{
    x.observe(notebookContainer, {childList: true});
}}

// Listen for the clearing of the current output cell
var outputEl = gd.closest('.output');
if (outputEl) {{
    x.observe(outputEl, {childList: true});
}}

                        })                };            </script>        </div>
</div>
<figcaption class="quarto-float-caption-margin quarto-float-caption quarto-float-fig margin-caption" id="fig-animal-scores-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1: Mean animal rating by animal, all models pooled.
</figcaption>
</figure>
</div>
</div>
<p>The pelican is 6th of 8, behind cat, whale, raccoon, heron, and antelope. If AI labs were training on the benchmark, you’d expect pelicans at the top. Instead they’re in the bottom half. All seven labs draw cats, whales, and raccoons better than pelicans.</p>
<p>Of course, a pelican may simply be harder to draw than a cat. A lab could train on pelicans and still not push them past the easy animals, so this ranking alone can’t rule that out. I’ll adjust for difficulty in Evidence #4.</p>
</section>
<section id="evidence-3-labs-are-not-better-at-drawing-bicycles" class="level2 page-columns page-full">
<h2 class="anchored" data-anchor-id="evidence-3-labs-are-not-better-at-drawing-bicycles">Evidence #3: Labs are not better at drawing bicycles</h2>
<p>Bicycles fare even worse. They sit second from last, in a near-tie with planes, which come in last:</p>
<div id="cell-fig-vehicle-scores" class="cell page-columns page-full" data-execution_count="3">
<div id="fig-vehicle-scores" class="cell-output cell-output-display quarto-float quarto-figure quarto-figure-center anchored page-columns page-full">
<figure class="quarto-float quarto-float-fig figure page-columns page-full">
<div aria-describedby="fig-vehicle-scores-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<div>            <script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-AMS-MML_SVG"></script><script type="text/javascript">if (window.MathJax && window.MathJax.Hub && window.MathJax.Hub.Config) {window.MathJax.Hub.Config({SVG: {font: "STIX-Web"}});}</script>                <script type="text/javascript">window.PlotlyConfig = {MathJaxConfig: 'local'};</script>
        <script charset="utf-8" src="https://cdn.plot.ly/plotly-3.0.1.min.js"></script>                <div id="0c67a1b0-3068-49f6-b583-542a88d2564c" class="plotly-graph-div" style="height:300px; width:100%;"></div>            <script type="text/javascript">                window.PLOTLYENV=window.PLOTLYENV || {};                                if (document.getElementById("0c67a1b0-3068-49f6-b583-542a88d2564c")) {                    Plotly.newPlot(                        "0c67a1b0-3068-49f6-b583-542a88d2564c",                        [{"hovertemplate":"\u003cb\u003e%{y}\u003c\u002fb\u003e\u003cbr\u003e%{x:.2f} out of 5\u003cextra\u003e\u003c\u002fextra\u003e","marker":{"color":["#71717a","#eb841b","#71717a","#71717a","#71717a","#71717a"]},"orientation":"h","text":["3.88","3.90","3.98","4.78","4.89","4.93"],"textfont":{"color":"#a1a1aa","size":12},"textposition":"outside","width":0.6,"x":{"dtype":"f8","bdata":"AAAAAAAAD0ANwzAMwzAPQLdt27Zt2w9A6Hme53keE0AlSZIkSZITQM\u002fzPM\u002fzvBNA"},"y":["plane","bicycle","unicycle","scooter","skateboard","boat"],"type":"bar"}],                        {"template":{"data":{"scatter":[{"type":"scatter"}]},"layout":{"margin":{"b":0,"l":0,"r":0,"t":30}}},"title":{"font":{"size":15,"color":"#e4e4e7"},"text":"Mean vehicle rating by vehicle (1-5)","x":0.5,"xanchor":"center"},"font":{"family":"Georgia, serif","size":13,"color":"#e4e4e7"},"margin":{"l":10,"r":10,"t":60,"b":40},"hoverlabel":{"font":{"family":"Georgia, serif","size":12,"color":"#e4e4e7"},"bgcolor":"#27272a","bordercolor":"#eb841b","align":"left"},"paper_bgcolor":"#18181b","plot_bgcolor":"#18181b","height":300,"showlegend":false,"dragmode":false,"xaxis":{"gridcolor":"#3f3f46","zeroline":false,"linecolor":"#3f3f46","tickcolor":"#3f3f46","automargin":true,"showgrid":false,"showticklabels":false,"visible":false,"range":[0,5.674702380952381]},"yaxis":{"gridcolor":"#3f3f46","zeroline":false,"linecolor":"#3f3f46","tickcolor":"#3f3f46","automargin":true,"showgrid":false}},                        {"displayModeBar": false, "responsive": true}                    ).then(function(){
                            
var gd = document.getElementById('0c67a1b0-3068-49f6-b583-542a88d2564c');
var x = new MutationObserver(function (mutations, observer) {{
        var display = window.getComputedStyle(gd).display;
        if (!display || display === 'none') {{
            console.log([gd, 'removed!']);
            Plotly.purge(gd);
            observer.disconnect();
        }}
}});

// Listen for the removal of the full notebook cells
var notebookContainer = gd.closest('#notebook-container');
if (notebookContainer) {{
    x.observe(notebookContainer, {childList: true});
}}

// Listen for the clearing of the current output cell
var outputEl = gd.closest('.output');
if (outputEl) {{
    x.observe(outputEl, {childList: true});
}}

                        })                };            </script>        </div>
</div>
<figcaption class="quarto-float-caption-margin quarto-float-caption quarto-float-fig margin-caption" id="fig-vehicle-scores-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;2: Mean vehicle rating by vehicle, all models pooled.
</figcaption>
</figure>
</div>
</div>
<p>If labs were training on the benchmark, you’d expect bicycles near the top of this ranking. They’re not. However, the same caveat applies here. A bicycle is harder to draw than a skateboard: it needs two matching wheels, a frame that reaches both axles, handlebars, a seat, and pedals. The judge flags a missing or disconnected one of those on 2/3 of the bicycle images. You can train on bicycle images and still not do a great job relative to simpler vehicles.</p>
<p>One note on the plane, though: I should’ve picked “airplane” instead of “plane” because models often read it geometrically. They drew the animal standing on a flat surface instead of flying an aircraft. The plane is the only vehicle where the feature extractor sometimes found no vehicle at all (25 of 168 images, against zero for the other five), and 20% of plane images scored a 1 or 2 on the vehicle rating, against 5% for bicycles and none at all for boats, scooters, or skateboards.</p>
</section>
<section id="evidence-4-labs-are-not-better-at-drawing-pelicans-on-bicycles-even-adjusting-for-difficulty" class="level2 page-columns page-full">
<h2 class="anchored" data-anchor-id="evidence-4-labs-are-not-better-at-drawing-pelicans-on-bicycles-even-adjusting-for-difficulty">Evidence #4: Labs are not better at drawing pelicans on bicycles, even adjusting for difficulty</h2>
<p>Put the two together and the “pelican on a bicycle” ends up near the bottom of the ranking, at #42 of 48:</p>
<div id="cell-fig-cell-ranking" class="cell page-columns page-full" data-execution_count="4">
<div id="fig-cell-ranking" class="cell-output cell-output-display quarto-float quarto-figure quarto-figure-center anchored page-columns page-full">
<figure class="quarto-float quarto-float-fig figure page-columns page-full">
<div aria-describedby="fig-cell-ranking-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<div>            <script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-AMS-MML_SVG"></script><script type="text/javascript">if (window.MathJax && window.MathJax.Hub && window.MathJax.Hub.Config) {window.MathJax.Hub.Config({SVG: {font: "STIX-Web"}});}</script>                <script type="text/javascript">window.PlotlyConfig = {MathJaxConfig: 'local'};</script>
        <script charset="utf-8" src="https://cdn.plot.ly/plotly-3.0.1.min.js"></script>                <div id="061e1957-f996-42a4-9341-05011586943f" class="plotly-graph-div" style="height:950px; width:100%;"></div>            <script type="text/javascript">                window.PLOTLYENV=window.PLOTLYENV || {};                                if (document.getElementById("061e1957-f996-42a4-9341-05011586943f")) {                    Plotly.newPlot(                        "061e1957-f996-42a4-9341-05011586943f",                        [{"hoverinfo":"skip","line":{"color":"#3f3f46","width":1},"mode":"lines","x":[1,3.4603174603174605,null,1,3.4603174603174605,null,1,3.7142857142857144,null,1,3.761904761904762,null,1,3.793650793650794,null,1,3.8412698412698414,null,1,3.8888888888888893,null,1,3.8888888888888893,null,1,3.9365079365079367,null,1,4.031746031746032,null,1,4.031746031746032,null,1,4.0476190476190474,null,1,4.07936507936508,null,1,4.07936507936508,null,1,4.111111111111111,null,1,4.111111111111111,null,1,4.142857142857143,null,1,4.158730158730158,null,1,4.2063492063492065,null,1,4.222222222222221,null,1,4.222222222222222,null,1,4.253968253968254,null,1,4.301587301587301,null,1,4.301587301587301,null,1,4.333333333333333,null,1,4.333333333333333,null,1,4.380952380952381,null,1,4.412698412698413,null,1,4.428571428571429,null,1,4.444444444444445,null,1,4.4603174603174605,null,1,4.507936507936508,null,1,4.507936507936508,null,1,4.507936507936508,null,1,4.571428571428571,null,1,4.571428571428571,null,1,4.587301587301588,null,1,4.603174603174604,null,1,4.619047619047619,null,1,4.619047619047619,null,1,4.666666666666667,null,1,4.666666666666667,null,1,4.682539682539682,null,1,4.714285714285714,null,1,4.714285714285714,null,1,4.777777777777778,null,1,4.841269841269842,null,1,4.873015873015873,null],"y":["antelope + plane","antelope + plane",null,"heron + plane","heron + plane",null,"whale + bicycle","whale + bicycle",null,"flamingo + bicycle","flamingo + bicycle",null,"antelope + bicycle","antelope + bicycle",null,"otter + unicycle","otter + unicycle",null,"pelican + bicycle","pelican + bicycle",null,"flamingo + plane","flamingo + plane",null,"otter + bicycle","otter + bicycle",null,"whale + plane","whale + plane",null,"antelope + unicycle","antelope + unicycle",null,"cat + bicycle","cat + bicycle",null,"flamingo + unicycle","flamingo + unicycle",null,"whale + unicycle","whale + unicycle",null,"raccoon + unicycle","raccoon + unicycle",null,"pelican + unicycle","pelican + unicycle",null,"pelican + scooter","pelican + scooter",null,"raccoon + bicycle","raccoon + bicycle",null,"cat + unicycle","cat + unicycle",null,"whale + scooter","whale + scooter",null,"heron + bicycle","heron + bicycle",null,"antelope + scooter","antelope + scooter",null,"otter + scooter","otter + scooter",null,"heron + unicycle","heron + unicycle",null,"cat + scooter","cat + scooter",null,"flamingo + scooter","flamingo + scooter",null,"otter + plane","otter + plane",null,"raccoon + scooter","raccoon + scooter",null,"raccoon + plane","raccoon + plane",null,"otter + skateboard","otter + skateboard",null,"pelican + plane","pelican + plane",null,"whale + skateboard","whale + skateboard",null,"antelope + skateboard","antelope + skateboard",null,"flamingo + boat","flamingo + boat",null,"pelican + skateboard","pelican + skateboard",null,"whale + boat","whale + boat",null,"heron + scooter","heron + scooter",null,"flamingo + skateboard","flamingo + skateboard",null,"heron + skateboard","heron + skateboard",null,"otter + boat","otter + boat",null,"raccoon + skateboard","raccoon + skateboard",null,"cat + plane","cat + plane",null,"pelican + boat","pelican + boat",null,"cat + skateboard","cat + skateboard",null,"antelope + boat","antelope + boat",null,"heron + boat","heron + boat",null,"raccoon + boat","raccoon + boat",null,"cat + boat","cat + boat",null],"type":"scatter"},{"hovertemplate":"\u003cb\u003e%{y}\u003c\u002fb\u003e\u003cbr\u003e%{x:.2f} out of 5\u003cextra\u003e\u003c\u002fextra\u003e","marker":{"color":["#71717a","#71717a","#71717a","#71717a","#71717a","#71717a","#eb841b","#71717a","#71717a","#71717a","#71717a","#71717a","#71717a","#71717a","#71717a","#71717a","#71717a","#71717a","#71717a","#71717a","#71717a","#71717a","#71717a","#71717a","#71717a","#71717a","#71717a","#71717a","#71717a","#71717a","#71717a","#71717a","#71717a","#71717a","#71717a","#71717a","#71717a","#71717a","#71717a","#71717a","#71717a","#71717a","#71717a","#71717a","#71717a","#71717a","#71717a","#71717a"],"size":[8,8,8,8,8,8,13,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8]},"mode":"markers","x":{"dtype":"f8","bdata":"7Lqu67quC0Dsuq7ruq4LQG7btm3btg1AhmEYhmEYDkCXZVmWZVkOQK\u002fruq7rug5AyHEcx3EcD0DIcRzHcRwPQOD3fd\u002f3fQ9ACIIgCIIgEEAIgiAIgiAQQAzDMAzDMBBAFUVRFEVREEAVRVEURVEQQBzHcRzHcRBAHMdxHMdxEEAlSZIkSZIQQCiKoiiKohBANU3TNE3TEEA4juM4juMQQDmO4ziO4xBAQRAEQRAEEUBN0zRN0zQRQE3TNE3TNBFAVVVVVVVVEUBVVVVVVVURQGIYhmEYhhFAapqmaZqmEUBu27Zt27YRQHIcx3EcxxFAdl3XdV3XEUCCIAiCIAgSQIIgCIIgCBJAgiAIgiAIEkCSJEmSJEkSQJIkSZIkSRJAl2VZlmVZEkCbpmmapmkSQJ7neZ7neRJAnud5nud5EkCrqqqqqqoSQKuqqqqqqhJAruu6ruu6EkC3bdu2bdsSQLdt27Zt2xJAx3Ecx3EcE0DYdV3XdV0TQN\u002f3fd\u002f3fRNA"},"y":["antelope + plane","heron + plane","whale + bicycle","flamingo + bicycle","antelope + bicycle","otter + unicycle","pelican + bicycle","flamingo + plane","otter + bicycle","whale + plane","antelope + unicycle","cat + bicycle","flamingo + unicycle","whale + unicycle","raccoon + unicycle","pelican + unicycle","pelican + scooter","raccoon + bicycle","cat + unicycle","whale + scooter","heron + bicycle","antelope + scooter","otter + scooter","heron + unicycle","cat + scooter","flamingo + scooter","otter + plane","raccoon + scooter","raccoon + plane","otter + skateboard","pelican + plane","whale + skateboard","antelope + skateboard","flamingo + boat","pelican + skateboard","whale + boat","heron + scooter","flamingo + skateboard","heron + skateboard","otter + boat","raccoon + skateboard","cat + plane","pelican + boat","cat + skateboard","antelope + boat","heron + boat","raccoon + boat","cat + boat"],"type":"scatter"}],                        {"template":{"data":{"scatter":[{"type":"scatter"}]},"layout":{"margin":{"b":0,"l":0,"r":0,"t":30}}},"title":{"font":{"size":15,"color":"#e4e4e7"},"text":"All 48 combos ranked — pelican + bicycle is #42","x":0.5,"xanchor":"center"},"font":{"family":"Georgia, serif","size":13,"color":"#e4e4e7"},"margin":{"l":10,"r":10,"t":60,"b":40},"hoverlabel":{"font":{"family":"Georgia, serif","size":12,"color":"#e4e4e7"},"bgcolor":"#27272a","bordercolor":"#eb841b","align":"left"},"paper_bgcolor":"#18181b","plot_bgcolor":"#18181b","height":950,"showlegend":false,"dragmode":false,"xaxis":{"gridcolor":"#3f3f46","zeroline":false,"linecolor":"#3f3f46","tickcolor":"#3f3f46","automargin":true,"title":{"font":{"color":"#a1a1aa"},"text":"Mean judge score (1-5)"},"range":[1,5]},"yaxis":{"gridcolor":"#3f3f46","zeroline":false,"linecolor":"#3f3f46","tickcolor":"#3f3f46","automargin":true,"tickfont":{"size":11},"showgrid":false}},                        {"displayModeBar": false, "responsive": true}                    ).then(function(){
                            
var gd = document.getElementById('061e1957-f996-42a4-9341-05011586943f');
var x = new MutationObserver(function (mutations, observer) {{
        var display = window.getComputedStyle(gd).display;
        if (!display || display === 'none') {{
            console.log([gd, 'removed!']);
            Plotly.purge(gd);
            observer.disconnect();
        }}
}});

// Listen for the removal of the full notebook cells
var notebookContainer = gd.closest('#notebook-container');
if (notebookContainer) {{
    x.observe(notebookContainer, {childList: true});
}}

// Listen for the clearing of the current output cell
var outputEl = gd.closest('.output');
if (outputEl) {{
    x.observe(outputEl, {childList: true});
}}

                        })                };            </script>        </div>
</div>
<figcaption class="quarto-float-caption-margin quarto-float-caption quarto-float-fig margin-caption" id="fig-cell-ranking-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;3: All 48 combos ranked; pelican + bicycle highlighted.
</figcaption>
</figure>
</div>
</div>
<p>But again, some combinations might be just harder to draw than others.</p>
<p>To account for that, I fit a fixed-effects regression on all 1,008 images: score ~ lab + animal × vehicle, plus per-lab interaction terms for pelican, bicycle, and the pelican-bicycle cell, with robust standard errors. The animal × vehicle terms absorb the inherent difficulty of all 48 combinations. The interactions measure each lab’s benchmark-specific boost relative to the average lab, with confidence intervals.</p>
<p>The results:</p>
<ol type="1">
<li>Every per-lab pelican effect (the lab’s boost on pelicans across all six vehicles) lands between -0.11 and +0.14 judge points, and none comes close to significance (smallest p = 0.25).</li>
<li>The per-lab bicycle effects (the lab’s boost on bicycles across all eight animals) run from <em>Grok 4.5</em> at -0.18 (p=0.11) to <em>Gemini 3.5 Flash</em> at +0.27 (p=0.022). Only Gemini clears p &lt; 0.05, and the seven point in both directions.</li>
<li>No pelican-bicycle cell effect (the <em>extra</em> boost on the specific combination, on top of the lab’s pelican and bicycle effects) clears p &lt; 0.05. The largest positive is <em>GLM-5.2</em> at +0.35 (p=0.12), which is the one I mentioned earlier. It’s the closest thing to a signal in this experiment, but still within chance.</li>
</ol>
<p>Here are the full per-lab estimates. A pelicanmaxxing lab would show dots to the right of the zero line across its whole row:</p>
<div id="cell-fig-regression-effects" class="cell page-columns page-full" data-execution_count="5">
<div id="fig-regression-effects" class="cell-output cell-output-display quarto-float quarto-figure quarto-figure-center anchored page-columns page-full">
<figure class="quarto-float quarto-float-fig figure page-columns page-full">
<div aria-describedby="fig-regression-effects-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<div>            <script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-AMS-MML_SVG"></script><script type="text/javascript">if (window.MathJax && window.MathJax.Hub && window.MathJax.Hub.Config) {window.MathJax.Hub.Config({SVG: {font: "STIX-Web"}});}</script>                <script type="text/javascript">window.PlotlyConfig = {MathJaxConfig: 'local'};</script>
        <script charset="utf-8" src="https://cdn.plot.ly/plotly-3.0.1.min.js"></script>                <div id="edff3e76-0536-426d-9ef5-b59f9c4c4757" class="plotly-graph-div" style="height:420px; width:100%;"></div>            <script type="text/javascript">                window.PLOTLYENV=window.PLOTLYENV || {};                                if (document.getElementById("edff3e76-0536-426d-9ef5-b59f9c4c4757")) {                    Plotly.newPlot(                        "edff3e76-0536-426d-9ef5-b59f9c4c4757",                        [{"hoverinfo":"skip","line":{"color":"#71717a","width":2},"mode":"lines","x":[-0.2461852684750736,0.2552555632596561],"y":["claude-sonnet-5","claude-sonnet-5"],"type":"scatter","xaxis":"x","yaxis":"y"},{"hovertemplate":"%{hovertext}\u003cextra\u003e\u003c\u002fextra\u003e","hovertext":["\u003cb\u003eclaude-sonnet-5\u003c\u002fb\u003e\u003cbr\u003e+0.00 judge points\u003cbr\u003e95% CI -0.25 to +0.26\u003cbr\u003ep = 0.972"],"marker":{"color":"#71717a","size":9},"mode":"markers","x":[0.0045351473922912],"y":["claude-sonnet-5"],"type":"scatter","xaxis":"x","yaxis":"y"},{"hoverinfo":"skip","line":{"color":"#71717a","width":2},"mode":"lines","x":[-0.322215384681117,0.1027142508942681],"y":["gemini-3.5-flash","gemini-3.5-flash"],"type":"scatter","xaxis":"x","yaxis":"y"},{"hovertemplate":"%{hovertext}\u003cextra\u003e\u003c\u002fextra\u003e","hovertext":["\u003cb\u003egemini-3.5-flash\u003c\u002fb\u003e\u003cbr\u003e-0.11 judge points\u003cbr\u003e95% CI -0.32 to +0.10\u003cbr\u003ep = 0.311"],"marker":{"color":"#71717a","size":9},"mode":"markers","x":[-0.1097505668934244],"y":["gemini-3.5-flash"],"type":"scatter","xaxis":"x","yaxis":"y"},{"hoverinfo":"skip","line":{"color":"#71717a","width":2},"mode":"lines","x":[-0.0852415404954648,0.2530419940102044],"y":["deepseek-v4-pro","deepseek-v4-pro"],"type":"scatter","xaxis":"x","yaxis":"y"},{"hovertemplate":"%{hovertext}\u003cextra\u003e\u003c\u002fextra\u003e","hovertext":["\u003cb\u003edeepseek-v4-pro\u003c\u002fb\u003e\u003cbr\u003e+0.08 judge points\u003cbr\u003e95% CI -0.09 to +0.25\u003cbr\u003ep = 0.331"],"marker":{"color":"#71717a","size":9},"mode":"markers","x":[0.0839002267573697],"y":["deepseek-v4-pro"],"type":"scatter","xaxis":"x","yaxis":"y"},{"hoverinfo":"skip","line":{"color":"#71717a","width":2},"mode":"lines","x":[-0.3062733060468178,0.1439150294028262],"y":["grok-4.5","grok-4.5"],"type":"scatter","xaxis":"x","yaxis":"y"},{"hovertemplate":"%{hovertext}\u003cextra\u003e\u003c\u002fextra\u003e","hovertext":["\u003cb\u003egrok-4.5\u003c\u002fb\u003e\u003cbr\u003e-0.08 judge points\u003cbr\u003e95% CI -0.31 to +0.14\u003cbr\u003ep = 0.480"],"marker":{"color":"#71717a","size":9},"mode":"markers","x":[-0.0811791383219958],"y":["grok-4.5"],"type":"scatter","xaxis":"x","yaxis":"y"},{"hoverinfo":"skip","line":{"color":"#71717a","width":2},"mode":"lines","x":[-0.1914546061324122,0.1878264882185777],"y":["qwen3.7-max","qwen3.7-max"],"type":"scatter","xaxis":"x","yaxis":"y"},{"hovertemplate":"%{hovertext}\u003cextra\u003e\u003c\u002fextra\u003e","hovertext":["\u003cb\u003eqwen3.7-max\u003c\u002fb\u003e\u003cbr\u003e-0.00 judge points\u003cbr\u003e95% CI -0.19 to +0.19\u003cbr\u003ep = 0.985"],"marker":{"color":"#71717a","size":9},"mode":"markers","x":[-0.0018140589569172],"y":["qwen3.7-max"],"type":"scatter","xaxis":"x","yaxis":"y"},{"hoverinfo":"skip","line":{"color":"#71717a","width":2},"mode":"lines","x":[-0.238119480726064,0.1773485056693746],"y":["gpt-5.6-terra","gpt-5.6-terra"],"type":"scatter","xaxis":"x","yaxis":"y"},{"hovertemplate":"%{hovertext}\u003cextra\u003e\u003c\u002fextra\u003e","hovertext":["\u003cb\u003egpt-5.6-terra\u003c\u002fb\u003e\u003cbr\u003e-0.03 judge points\u003cbr\u003e95% CI -0.24 to +0.18\u003cbr\u003ep = 0.774"],"marker":{"color":"#71717a","size":9},"mode":"markers","x":[-0.0303854875283446],"y":["gpt-5.6-terra"],"type":"scatter","xaxis":"x","yaxis":"y"},{"hoverinfo":"skip","line":{"color":"#71717a","width":2},"mode":"lines","x":[-0.0931924446300317,0.362580199732074],"y":["glm-5.2","glm-5.2"],"type":"scatter","xaxis":"x","yaxis":"y"},{"hovertemplate":"%{hovertext}\u003cextra\u003e\u003c\u002fextra\u003e","hovertext":["\u003cb\u003eglm-5.2\u003c\u002fb\u003e\u003cbr\u003e+0.13 judge points\u003cbr\u003e95% CI -0.09 to +0.36\u003cbr\u003ep = 0.247"],"marker":{"color":"#71717a","size":9},"mode":"markers","x":[0.1346938775510211],"y":["glm-5.2"],"type":"scatter","xaxis":"x","yaxis":"y"},{"hoverinfo":"skip","line":{"color":"#71717a","width":2},"mode":"lines","x":[-0.1470057489214034,0.2286384019826284],"y":["claude-sonnet-5","claude-sonnet-5"],"type":"scatter","xaxis":"x2","yaxis":"y2"},{"hovertemplate":"%{hovertext}\u003cextra\u003e\u003c\u002fextra\u003e","hovertext":["\u003cb\u003eclaude-sonnet-5\u003c\u002fb\u003e\u003cbr\u003e+0.04 judge points\u003cbr\u003e95% CI -0.15 to +0.23\u003cbr\u003ep = 0.670"],"marker":{"color":"#71717a","size":9},"mode":"markers","x":[0.0408163265306124],"y":["claude-sonnet-5"],"type":"scatter","xaxis":"x2","yaxis":"y2"},{"hoverinfo":"skip","line":{"color":"#eb841b","width":2},"mode":"lines","x":[0.0382336260680011,0.494192677786876],"y":["gemini-3.5-flash","gemini-3.5-flash"],"type":"scatter","xaxis":"x2","yaxis":"y2"},{"hovertemplate":"%{hovertext}\u003cextra\u003e\u003c\u002fextra\u003e","hovertext":["\u003cb\u003egemini-3.5-flash\u003c\u002fb\u003e\u003cbr\u003e+0.27 judge points\u003cbr\u003e95% CI +0.04 to +0.49\u003cbr\u003ep = 0.022"],"marker":{"color":"#eb841b","size":9},"mode":"markers","x":[0.2662131519274386],"y":["gemini-3.5-flash"],"type":"scatter","xaxis":"x2","yaxis":"y2"},{"hoverinfo":"skip","line":{"color":"#71717a","width":2},"mode":"lines","x":[-0.3486476171287027,0.1826612225708767],"y":["deepseek-v4-pro","deepseek-v4-pro"],"type":"scatter","xaxis":"x2","yaxis":"y2"},{"hovertemplate":"%{hovertext}\u003cextra\u003e\u003c\u002fextra\u003e","hovertext":["\u003cb\u003edeepseek-v4-pro\u003c\u002fb\u003e\u003cbr\u003e-0.08 judge points\u003cbr\u003e95% CI -0.35 to +0.18\u003cbr\u003ep = 0.540"],"marker":{"color":"#71717a","size":9},"mode":"markers","x":[-0.0829931972789129],"y":["deepseek-v4-pro"],"type":"scatter","xaxis":"x2","yaxis":"y2"},{"hoverinfo":"skip","line":{"color":"#71717a","width":2},"mode":"lines","x":[-0.3899316385505862,0.0398182598657805],"y":["grok-4.5","grok-4.5"],"type":"scatter","xaxis":"x2","yaxis":"y2"},{"hovertemplate":"%{hovertext}\u003cextra\u003e\u003c\u002fextra\u003e","hovertext":["\u003cb\u003egrok-4.5\u003c\u002fb\u003e\u003cbr\u003e-0.18 judge points\u003cbr\u003e95% CI -0.39 to +0.04\u003cbr\u003ep = 0.110"],"marker":{"color":"#71717a","size":9},"mode":"markers","x":[-0.1750566893424028],"y":["grok-4.5"],"type":"scatter","xaxis":"x2","yaxis":"y2"},{"hoverinfo":"skip","line":{"color":"#71717a","width":2},"mode":"lines","x":[-0.1878011664980676,0.1995925497180254],"y":["qwen3.7-max","qwen3.7-max"],"type":"scatter","xaxis":"x2","yaxis":"y2"},{"hovertemplate":"%{hovertext}\u003cextra\u003e\u003c\u002fextra\u003e","hovertext":["\u003cb\u003eqwen3.7-max\u003c\u002fb\u003e\u003cbr\u003e+0.01 judge points\u003cbr\u003e95% CI -0.19 to +0.20\u003cbr\u003ep = 0.952"],"marker":{"color":"#71717a","size":9},"mode":"markers","x":[0.0058956916099789],"y":["qwen3.7-max"],"type":"scatter","xaxis":"x2","yaxis":"y2"},{"hoverinfo":"skip","line":{"color":"#71717a","width":2},"mode":"lines","x":[-0.180817267666618,0.2116562699341891],"y":["gpt-5.6-terra","gpt-5.6-terra"],"type":"scatter","xaxis":"x2","yaxis":"y2"},{"hovertemplate":"%{hovertext}\u003cextra\u003e\u003c\u002fextra\u003e","hovertext":["\u003cb\u003egpt-5.6-terra\u003c\u002fb\u003e\u003cbr\u003e+0.02 judge points\u003cbr\u003e95% CI -0.18 to +0.21\u003cbr\u003ep = 0.878"],"marker":{"color":"#71717a","size":9},"mode":"markers","x":[0.0154195011337855],"y":["gpt-5.6-terra"],"type":"scatter","xaxis":"x2","yaxis":"y2"},{"hoverinfo":"skip","line":{"color":"#71717a","width":2},"mode":"lines","x":[-0.2939865851192713,0.1533970159582718],"y":["glm-5.2","glm-5.2"],"type":"scatter","xaxis":"x2","yaxis":"y2"},{"hovertemplate":"%{hovertext}\u003cextra\u003e\u003c\u002fextra\u003e","hovertext":["\u003cb\u003eglm-5.2\u003c\u002fb\u003e\u003cbr\u003e-0.07 judge points\u003cbr\u003e95% CI -0.29 to +0.15\u003cbr\u003ep = 0.538"],"marker":{"color":"#71717a","size":9},"mode":"markers","x":[-0.0702947845804997],"y":["glm-5.2"],"type":"scatter","xaxis":"x2","yaxis":"y2"},{"hoverinfo":"skip","line":{"color":"#71717a","width":2},"mode":"lines","x":[-1.1678600300574509,0.006862297631147],"y":["claude-sonnet-5","claude-sonnet-5"],"type":"scatter","xaxis":"x3","yaxis":"y3"},{"hovertemplate":"%{hovertext}\u003cextra\u003e\u003c\u002fextra\u003e","hovertext":["\u003cb\u003eclaude-sonnet-5\u003c\u002fb\u003e\u003cbr\u003e-0.58 judge points\u003cbr\u003e95% CI -1.17 to +0.01\u003cbr\u003ep = 0.053"],"marker":{"color":"#71717a","size":9},"mode":"markers","x":[-0.5804988662131518],"y":["claude-sonnet-5"],"type":"scatter","xaxis":"x3","yaxis":"y3"},{"hoverinfo":"skip","line":{"color":"#71717a","width":2},"mode":"lines","x":[-0.9357426329120212,0.4795068052476187],"y":["gemini-3.5-flash","gemini-3.5-flash"],"type":"scatter","xaxis":"x3","yaxis":"y3"},{"hovertemplate":"%{hovertext}\u003cextra\u003e\u003c\u002fextra\u003e","hovertext":["\u003cb\u003egemini-3.5-flash\u003c\u002fb\u003e\u003cbr\u003e-0.23 judge points\u003cbr\u003e95% CI -0.94 to +0.48\u003cbr\u003ep = 0.527"],"marker":{"color":"#71717a","size":9},"mode":"markers","x":[-0.2281179138322012],"y":["gemini-3.5-flash"],"type":"scatter","xaxis":"x3","yaxis":"y3"},{"hoverinfo":"skip","line":{"color":"#71717a","width":2},"mode":"lines","x":[-0.9545970941013956,0.8412184092941335],"y":["deepseek-v4-pro","deepseek-v4-pro"],"type":"scatter","xaxis":"x3","yaxis":"y3"},{"hovertemplate":"%{hovertext}\u003cextra\u003e\u003c\u002fextra\u003e","hovertext":["\u003cb\u003edeepseek-v4-pro\u003c\u002fb\u003e\u003cbr\u003e-0.06 judge points\u003cbr\u003e95% CI -0.95 to +0.84\u003cbr\u003ep = 0.902"],"marker":{"color":"#71717a","size":9},"mode":"markers","x":[-0.0566893424036309],"y":["deepseek-v4-pro"],"type":"scatter","xaxis":"x3","yaxis":"y3"},{"hoverinfo":"skip","line":{"color":"#71717a","width":2},"mode":"lines","x":[-0.4287401963640654,0.455044051239345],"y":["grok-4.5","grok-4.5"],"type":"scatter","xaxis":"x3","yaxis":"y3"},{"hovertemplate":"%{hovertext}\u003cextra\u003e\u003c\u002fextra\u003e","hovertext":["\u003cb\u003egrok-4.5\u003c\u002fb\u003e\u003cbr\u003e+0.01 judge points\u003cbr\u003e95% CI -0.43 to +0.46\u003cbr\u003ep = 0.953"],"marker":{"color":"#71717a","size":9},"mode":"markers","x":[0.0131519274376397],"y":["grok-4.5"],"type":"scatter","xaxis":"x3","yaxis":"y3"},{"hoverinfo":"skip","line":{"color":"#71717a","width":2},"mode":"lines","x":[-0.6967575360006172,1.0722677400822485],"y":["qwen3.7-max","qwen3.7-max"],"type":"scatter","xaxis":"x3","yaxis":"y3"},{"hovertemplate":"%{hovertext}\u003cextra\u003e\u003c\u002fextra\u003e","hovertext":["\u003cb\u003eqwen3.7-max\u003c\u002fb\u003e\u003cbr\u003e+0.19 judge points\u003cbr\u003e95% CI -0.70 to +1.07\u003cbr\u003ep = 0.677"],"marker":{"color":"#71717a","size":9},"mode":"markers","x":[0.1877551020408157],"y":["qwen3.7-max"],"type":"scatter","xaxis":"x3","yaxis":"y3"},{"hoverinfo":"skip","line":{"color":"#71717a","width":2},"mode":"lines","x":[-0.0511698730950602,0.6742991247957477],"y":["gpt-5.6-terra","gpt-5.6-terra"],"type":"scatter","xaxis":"x3","yaxis":"y3"},{"hovertemplate":"%{hovertext}\u003cextra\u003e\u003c\u002fextra\u003e","hovertext":["\u003cb\u003egpt-5.6-terra\u003c\u002fb\u003e\u003cbr\u003e+0.31 judge points\u003cbr\u003e95% CI -0.05 to +0.67\u003cbr\u003ep = 0.092"],"marker":{"color":"#71717a","size":9},"mode":"markers","x":[0.3115646258503437],"y":["gpt-5.6-terra"],"type":"scatter","xaxis":"x3","yaxis":"y3"},{"hoverinfo":"skip","line":{"color":"#71717a","width":2},"mode":"lines","x":[-0.094318784326954,0.7999877185673238],"y":["glm-5.2","glm-5.2"],"type":"scatter","xaxis":"x3","yaxis":"y3"},{"hovertemplate":"%{hovertext}\u003cextra\u003e\u003c\u002fextra\u003e","hovertext":["\u003cb\u003eglm-5.2\u003c\u002fb\u003e\u003cbr\u003e+0.35 judge points\u003cbr\u003e95% CI -0.09 to +0.80\u003cbr\u003ep = 0.122"],"marker":{"color":"#71717a","size":9},"mode":"markers","x":[0.3528344671201848],"y":["glm-5.2"],"type":"scatter","xaxis":"x3","yaxis":"y3"}],                        {"template":{"data":{"scatter":[{"type":"scatter"}]},"layout":{"margin":{"b":0,"l":0,"r":0,"t":30}}},"xaxis":{"anchor":"y","domain":[0.0,0.3133333333333333],"gridcolor":"#3f3f46","zeroline":false,"linecolor":"#3f3f46","tickcolor":"#3f3f46","automargin":true,"title":{"font":{"color":"#a1a1aa","size":11}},"range":[-1.2846460330631961,1.2846460330631961],"showgrid":false},"yaxis":{"anchor":"x","domain":[0.0,1.0],"gridcolor":"#3f3f46","zeroline":false,"linecolor":"#3f3f46","tickcolor":"#3f3f46","automargin":true,"tickfont":{"size":11},"showgrid":false},"xaxis2":{"anchor":"y2","domain":[0.34333333333333327,0.6566666666666665],"gridcolor":"#3f3f46","zeroline":false,"linecolor":"#3f3f46","tickcolor":"#3f3f46","automargin":true,"title":{"font":{"color":"#a1a1aa","size":11},"text":"Judge points (dot = estimate, line = 95% CI)"},"range":[-1.2846460330631961,1.2846460330631961],"showgrid":false},"yaxis2":{"anchor":"x2","domain":[0.0,1.0],"matches":"y","showticklabels":false,"gridcolor":"#3f3f46","zeroline":false,"linecolor":"#3f3f46","tickcolor":"#3f3f46","automargin":true,"tickfont":{"size":11},"showgrid":false},"xaxis3":{"anchor":"y3","domain":[0.6866666666666665,0.9999999999999998],"gridcolor":"#3f3f46","zeroline":false,"linecolor":"#3f3f46","tickcolor":"#3f3f46","automargin":true,"title":{"font":{"color":"#a1a1aa","size":11}},"range":[-1.2846460330631961,1.2846460330631961],"showgrid":false},"yaxis3":{"anchor":"x3","domain":[0.0,1.0],"matches":"y","showticklabels":false,"gridcolor":"#3f3f46","zeroline":false,"linecolor":"#3f3f46","tickcolor":"#3f3f46","automargin":true,"tickfont":{"size":11},"showgrid":false},"annotations":[{"font":{"size":12,"color":"#e4e4e7"},"showarrow":false,"text":"Pelican effect","x":0.15666666666666665,"xanchor":"center","xref":"paper","y":1.0,"yanchor":"bottom","yref":"paper"},{"font":{"size":12,"color":"#e4e4e7"},"showarrow":false,"text":"Bicycle effect","x":0.4999999999999999,"xanchor":"center","xref":"paper","y":1.0,"yanchor":"bottom","yref":"paper"},{"font":{"size":12,"color":"#e4e4e7"},"showarrow":false,"text":"Pelican-bicycle cell effect","x":0.8433333333333332,"xanchor":"center","xref":"paper","y":1.0,"yanchor":"bottom","yref":"paper"}],"shapes":[{"line":{"color":"#3f3f46","width":1},"type":"line","x0":0,"x1":0,"xref":"x","y0":0,"y1":1,"yref":"y domain"},{"line":{"color":"#3f3f46","width":1},"type":"line","x0":0,"x1":0,"xref":"x2","y0":0,"y1":1,"yref":"y2 domain"},{"line":{"color":"#3f3f46","width":1},"type":"line","x0":0,"x1":0,"xref":"x3","y0":0,"y1":1,"yref":"y3 domain"}],"title":{"font":{"size":15,"color":"#e4e4e7"},"text":"Difficulty-adjusted effects per lab","x":0.5,"xanchor":"center"},"font":{"family":"Georgia, serif","size":13,"color":"#e4e4e7"},"margin":{"l":10,"r":10,"t":60,"b":40},"hoverlabel":{"font":{"family":"Georgia, serif","size":12,"color":"#e4e4e7"},"bgcolor":"#27272a","bordercolor":"#eb841b","align":"left"},"paper_bgcolor":"#18181b","plot_bgcolor":"#18181b","height":420,"showlegend":false,"dragmode":false},                        {"displayModeBar": false, "responsive": true}                    ).then(function(){
                            
var gd = document.getElementById('edff3e76-0536-426d-9ef5-b59f9c4c4757');
var x = new MutationObserver(function (mutations, observer) {{
        var display = window.getComputedStyle(gd).display;
        if (!display || display === 'none') {{
            console.log([gd, 'removed!']);
            Plotly.purge(gd);
            observer.disconnect();
        }}
}});

// Listen for the removal of the full notebook cells
var notebookContainer = gd.closest('#notebook-container');
if (notebookContainer) {{
    x.observe(notebookContainer, {childList: true});
}}

// Listen for the clearing of the current output cell
var outputEl = gd.closest('.output');
if (outputEl) {{
    x.observe(outputEl, {childList: true});
}}

                        })                };            </script>        </div>
</div>
<figcaption class="quarto-float-caption-margin quarto-float-caption quarto-float-fig margin-caption" id="fig-regression-effects-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;4: Difficulty-adjusted effects per lab, with 95% confidence intervals. Highlighted intervals exclude zero.
</figcaption>
</figure>
</div>
</div>
<p>Every pelican interval and every cell interval contains zero. Exactly one doesn’t: <em>Gemini 3.5 Flash</em> in the bicycle column. But with 21 tests at p &lt; 0.05, chance alone predicts about one false positive (21 × 0.05 ≈ 1.05), and one is exactly what came up. It also doesn’t survive a multiple-comparisons correction: the Bonferroni threshold across the 21 tests is 0.05/21 ≈ 0.002, and its p-value is 0.022. The full table of estimates and p-values is in <a href="https://github.com/dylanjcastillo/blog/tree/main/_extras/pelicanmaxxing">the repo</a>.</p>
<p>But these intervals are wide, about ±0.6 judge points on average. Any boost smaller than that won’t be captured by this test.</p>
</section>
<section id="evidence-5-the-pelican-bicycle-scenes-dont-look-memorized" class="level2 page-columns page-full">
<h2 class="anchored" data-anchor-id="evidence-5-the-pelican-bicycle-scenes-dont-look-memorized">Evidence #5: The pelican-bicycle scenes don’t look memorized</h2>
<p>Some have suggested that the pelican on a bicycle looks like a memorized composition, pointing to recurring patterns such as the pelican always facing right, or recurring elements like a sun or a scarf. So I wanted to know if this was true.</p>
<p><strong>Direction</strong>: All 21 pelican-bicycle images, across all seven labs, face right. No other animal/vehicle combination does that.</p>
<p>However, facing right is common: 60% of all 1,008 images do it. How common depends on the animal and the vehicle, and bicycles are one of the two vehicles where it’s strongest:</p>
<table class="table">
<thead>
<tr class="header">
<th>vehicle</th>
<th>left</th>
<th>right</th>
<th>ambiguous</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>scooter</td>
<td>9%</td>
<td>83%</td>
<td>8%</td>
</tr>
<tr class="even">
<td>bicycle</td>
<td>11%</td>
<td>81%</td>
<td>8%</td>
</tr>
<tr class="odd">
<td>skateboard</td>
<td>19%</td>
<td>60%</td>
<td>21%</td>
</tr>
<tr class="even">
<td>plane</td>
<td>21%</td>
<td>58%</td>
<td>21%</td>
</tr>
<tr class="odd">
<td>unicycle</td>
<td>22%</td>
<td>45%</td>
<td>33%</td>
</tr>
<tr class="even">
<td>boat</td>
<td>33%</td>
<td>35%</td>
<td>32%</td>
</tr>
</tbody>
</table>
<p>Pelicans are also among the animals that tend to face right:</p>
<table class="table">
<thead>
<tr class="header">
<th>animal</th>
<th>left</th>
<th>right</th>
<th>ambiguous</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>antelope</td>
<td>21%</td>
<td>78%</td>
<td>1%</td>
</tr>
<tr class="even">
<td>pelican</td>
<td>22%</td>
<td>78%</td>
<td>0%</td>
</tr>
<tr class="odd">
<td>heron</td>
<td>22%</td>
<td>77%</td>
<td>1%</td>
</tr>
<tr class="even">
<td>whale</td>
<td>34%</td>
<td>65%</td>
<td>1%</td>
</tr>
<tr class="odd">
<td>flamingo</td>
<td>36%</td>
<td>64%</td>
<td>0%</td>
</tr>
<tr class="even">
<td>otter</td>
<td>8%</td>
<td>45%</td>
<td>47%</td>
</tr>
<tr class="odd">
<td>cat</td>
<td>8%</td>
<td>40%</td>
<td>52%</td>
</tr>
<tr class="even">
<td>raccoon</td>
<td>3%</td>
<td>36%</td>
<td>61%</td>
</tr>
</tbody>
</table>
<p>It’s hard to draw a pelican or a bicycle facing the viewer, so models almost always draw them from the side, facing left or right. That’s why so few of their images are ambiguous. Other combinations also come close to unanimous: antelope on a scooter and pelican on a scooter land at 20 of 21, and heron on a bicycle at 19 of 21. So 21 out of 21 doesn’t seem like an outlier.</p>
<p><strong>Scene elements</strong>: I let the extractor name any element it saw in the image. These are the counts:</p>
<div id="cell-fig-top-elements" class="cell page-columns page-full" data-execution_count="6">
<div id="fig-top-elements" class="cell-output cell-output-display quarto-float quarto-figure quarto-figure-center anchored page-columns page-full">
<figure class="quarto-float quarto-float-fig figure page-columns page-full">
<div aria-describedby="fig-top-elements-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<div>            <script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-AMS-MML_SVG"></script><script type="text/javascript">if (window.MathJax && window.MathJax.Hub && window.MathJax.Hub.Config) {window.MathJax.Hub.Config({SVG: {font: "STIX-Web"}});}</script>                <script type="text/javascript">window.PlotlyConfig = {MathJaxConfig: 'local'};</script>
        <script charset="utf-8" src="https://cdn.plot.ly/plotly-3.0.1.min.js"></script>                <div id="4c13120b-ae9e-470b-aef8-31241a59e865" class="plotly-graph-div" style="height:430px; width:100%;"></div>            <script type="text/javascript">                window.PLOTLYENV=window.PLOTLYENV || {};                                if (document.getElementById("4c13120b-ae9e-470b-aef8-31241a59e865")) {                    Plotly.newPlot(                        "4c13120b-ae9e-470b-aef8-31241a59e865",                        [{"hovertemplate":"\u003cb\u003e%{y}\u003c\u002fb\u003e\u003cbr\u003e%{x:.0%} of images\u003cextra\u003e\u003c\u002fextra\u003e","marker":{"color":["#71717a","#71717a","#71717a","#71717a","#71717a","#71717a","#71717a","#71717a","#71717a","#71717a","#71717a","#71717a","#71717a","#71717a","#71717a"]},"orientation":"h","text":["4%","5%","5%","6%","9%","10%","11%","12%","14%","14%","16%","16%","18%","45%","46%"],"textfont":{"color":"#a1a1aa","size":12},"textposition":"outside","width":0.6,"x":{"dtype":"f8","bdata":"sW3btm3bpj\u002fSdV3XdV2nPzeO4ziO46g\u002fed\u002f3fd\u002f3rT\u002fjeZ7neZ63P6aqqqqqqro\u002fHMdxHMdxvD\u002fO8zzP8zy\u002fP38gCIIgCMI\u002foSiKoiiKwj81TdM0TdPEP0JRFEVRFMU\u002f1nVd13Vdxz8kSZIkSZLcP27btm3btt0\u002f"},"y":["hill","helmet","road","circle","bird","line","scarf","motion line","grass","star","sky","ground","water","cloud","sun"],"type":"bar"}],                        {"template":{"data":{"scatter":[{"type":"scatter"}]},"layout":{"margin":{"b":0,"l":0,"r":0,"t":30}}},"title":{"font":{"size":15,"color":"#e4e4e7"},"text":"Share of images containing each element","x":0.5,"xanchor":"center"},"font":{"family":"Georgia, serif","size":13,"color":"#e4e4e7"},"margin":{"l":10,"r":10,"t":60,"b":40},"hoverlabel":{"font":{"family":"Georgia, serif","size":12,"color":"#e4e4e7"},"bgcolor":"#27272a","bordercolor":"#eb841b","align":"left"},"paper_bgcolor":"#18181b","plot_bgcolor":"#18181b","height":430,"showlegend":false,"dragmode":false,"xaxis":{"gridcolor":"#3f3f46","zeroline":false,"linecolor":"#3f3f46","tickcolor":"#3f3f46","automargin":true,"showgrid":false,"showticklabels":false,"visible":false,"range":[0,0.5339285714285714]},"yaxis":{"gridcolor":"#3f3f46","zeroline":false,"linecolor":"#3f3f46","tickcolor":"#3f3f46","automargin":true,"showgrid":false}},                        {"displayModeBar": false, "responsive": true}                    ).then(function(){
                            
var gd = document.getElementById('4c13120b-ae9e-470b-aef8-31241a59e865');
var x = new MutationObserver(function (mutations, observer) {{
        var display = window.getComputedStyle(gd).display;
        if (!display || display === 'none') {{
            console.log([gd, 'removed!']);
            Plotly.purge(gd);
            observer.disconnect();
        }}
}});

// Listen for the removal of the full notebook cells
var notebookContainer = gd.closest('#notebook-container');
if (notebookContainer) {{
    x.observe(notebookContainer, {childList: true});
}}

// Listen for the clearing of the current output cell
var outputEl = gd.closest('.output');
if (outputEl) {{
    x.observe(outputEl, {childList: true});
}}

                        })                };            </script>        </div>
</div>
<figcaption class="quarto-float-caption-margin quarto-float-caption quarto-float-fig margin-caption" id="fig-top-elements-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;5: Share of images containing each element, from the open-ended extraction pass.
</figcaption>
</figure>
</div>
</div>
<p>A memorized scene would show up as the same set of elements recurring picture after picture. I went looking for that, and found some combinations do tend to produce the same elements every time. Every single flamingo on a boat has a sun in it. Otters on planes wear scarves 38% of the time. Cats on bicycles get a basket 38% of the time.</p>
<p>The pelican on a bicycle doesn’t seem to have anything particularly different about it. It just has some elements that appear more frequently, like every other animal-vehicle combination.</p>
</section>
<section id="limitations" class="level2">
<h2 class="anchored" data-anchor-id="limitations">Limitations</h2>
<ol type="1">
<li><strong>Using a single LLM judge for scoring.</strong> Every score here comes from one model, <em>GPT-5.6 Luna</em>, looking at one image at a time. I didn’t do much alignment and didn’t check how often it agrees with itself on a re-run. If a model just can’t judge a drawing reliably, none of the numbers above mean much. The judge is also from the same family as one of the contestants, <em>GPT-5.6 Terra</em>. However, every lab draws all 48 combinations, so a judge that happens to like one lab’s style lifts that lab’s whole grid at once. But that doesn’t change the results because this analysis only cares about the within-lab differences.</li>
<li><strong>SVGmaxxing.</strong> A lab that optimized SVG generation <em>as a whole</em> (or a subset such as animals on vehicles) rises on every cell at once and looks identical to a lab that’s just good. Some labs, such as Google/DeepMind, <a href="https://x.com/JeffDean/status/2024525132266688757">openly</a> do this. This experiment can’t detect that.</li>
<li><strong>Limited budget.</strong> The whole experiment ran on roughly $80 of API credits. That capped it at 3 samples per cell, a single judge, and 7 models. This also prevented me from iterating too much on the prompts and pipeline, as with the “plane” vs.&nbsp;“airplane” case.</li>
</ol>
</section>
<section id="conclusion" class="level2">
<h2 class="anchored" data-anchor-id="conclusion">Conclusion</h2>
<p>Sorry, HN haters, but there’s little evidence that AI labs are pelicanmaxxing. Or at least they’re not doing it in a plainly obvious manner.</p>
<p>Pelicans aren’t drawn any better than other animals. Bicycles aren’t drawn any better than other vehicles. And no lab draws the combination better than its pelicans and bicycles already predict. GLM-5.2 comes closest: it has the largest boost on the exact pelican-bicycle cell, and its first pelican-on-bicycle sample caught my eye. But the effect is small and not significant, so I wouldn’t put too much weight on it.</p>
<p>The other thing that stands out is direction in the scene composition. All 21 pelican-bicycle images face right, the only combination in the grid where every image agrees. But it doesn’t seem that strange. Facing right is the norm across the experiment. Three other combinations land at 90% or above, and with 48 of them, I’m not surprised one reached 21 out of 21.</p>
<p>The more plausible story is SVGmaxxing like Google/DeepMind does. Other labs might be doing it more quietly. Sadly, this experiment can’t say who’s doing it. But at least you can sleep tonight knowing that AI labs are not producing terabytes of pelicans on bicycles just to trick Simon Willison.</p>
<p>If you want to look at the data yourself, the full pipeline is in the <a href="https://github.com/dylanjcastillo/blog/tree/main/_extras/pelicanmaxxing">repo</a>.</p>


</section>


<div id="quarto-appendix" class="default"><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Footnotes</h2>

<ol>
<li id="fn1"><p>the practice of optimizing AI models to achieve high scores on popular benchmarks.↩︎</p></li>
</ol>
</section><section class="quarto-appendix-contents" id="quarto-citation"><h2 class="anchored quarto-appendix-heading">Citation</h2><div><div class="quarto-appendix-secondary-label">BibTeX citation:</div><pre class="sourceCode code-with-copy quarto-appendix-bibtex"><code class="sourceCode bibtex">@online{castillo2026,
  author = {Castillo, Dylan},
  title = {Are {AI} Labs Pelicanmaxxing?},
  date = {2026-07-18},
  url = {https://dylancastillo.co/posts/pelicanmaxxing.html},
  langid = {en}
}
</code></pre><div class="quarto-appendix-secondary-label">For attribution, please cite this work as:</div><div id="ref-castillo2026" class="csl-entry quarto-appendix-citeas">
Castillo, Dylan. 2026. <span>“Are AI Labs Pelicanmaxxing?”</span> July
18. <a href="https://dylancastillo.co/posts/pelicanmaxxing.html">https://dylancastillo.co/posts/pelicanmaxxing.html</a>.
</div></div></section></div> ]]></description>
  <category>llm</category>
  <category>evals</category>
  <category>python</category>
  <guid>https://dylancastillo.co/posts/pelicanmaxxing.html</guid>
  <pubDate>Sat, 18 Jul 2026 00:00:00 GMT</pubDate>
  <media:content url="https://dylancastillo.co/posts/images/cards/pelicanmaxxing.png" medium="image" type="image/png" height="76" width="144"/>
</item>
<item>
  <title>Five Years of Freelancing</title>
  <dc:creator>Dylan Castillo</dc:creator>
  <link>https://dylancastillo.co/posts/5-years-freelancing-in-europe.html</link>
  <description><![CDATA[ 




<p>Five years ago, freelancing felt like arbitrage: same work, better pay, without leaving Spain. So I left my full-time job in the middle of the pandemic and gave it a shot.</p>
<p>It worked. I made more money, had more freedom, and worked with all sorts of companies on projects across data science, machine learning, and AI products.</p>
<p>But that was also the problem. Freelancing rewarded my flexibility and never forced me to go too deep into any topic. Eventually, that flexibility became a ceiling.</p>
<p>This is a recap of how I got in, how much I made, how I found gigs, and what I’ve learned in the process.</p>
<section id="how-i-got-in" class="level2 page-columns page-full">
<h2 class="anchored" data-anchor-id="how-i-got-in">How I got in</h2>
<p>I started thinking about freelancing in 2019, two years before I landed my first contract. That year, I joined a platform that promised you a better-paying job in exchange for a cut of your future earnings. The idea was simple: they would introduce you to people already doing the kind of work you wanted to do, and those people would help you land a similar job.</p>
<p>The platform suggested a few introductions, and one of them stuck with me: a freelance data scientist.</p>
<p>In our first chat, he mentioned almost in passing that he was charging over €600 a day. That blew my mind. He was doing the same work I was doing, but making much more money, and he didn’t have to leave his hometown! I asked if he’d mentor me and help me land a freelance contract. He said yes, then shared his secret sauce: spam people on LinkedIn.</p>
<p>So that’s what I did.</p>
<p>I set up a bot that messaged pretty much everyone involved in data science in Europe: managers, recruiters, and a few confused interns. Most people ignored me. A few wrote back to ask whether what I was doing was even legal. Most recruiters dismissed me because I had no prior freelancing experience.</p>
<div class="quarto-figure quarto-figure-center page-columns page-full">
<figure class="figure page-columns page-full">
<p><a href="images/5-years-freelancing-in-Europe/linkedin-spam.png" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Me spamming people on LinkedIn"><img src="https://dylancastillo.co/posts/images/5-years-freelancing-in-Europe/linkedin-spam.png" class="img-fluid figure-img" alt="Me spamming people on LinkedIn"></a></p>
<figcaption class="margin-caption">Me spamming people on LinkedIn</figcaption>
</figure>
</div>
<p>I grew from something like 500 connections to close to 4,000, but the approach didn’t bear fruit. Eventually, I realized our incentives weren’t aligned. His plan was for me to spray LinkedIn messages across Europe while he sent me humblebrag motivational messages to keep me going. If I landed a contract, he’d get a nice cut for the next four years. If LinkedIn blocked my account, that was my problem. So we parted ways.</p>
<p>Instead, I decided to start a blog, share useful projects and tutorials on LinkedIn, and apply to contracting opportunities in the hope that something would pan out. It was a better long-term strategy, but in the short term, it produced zero results.</p>
<p>I started getting desperate. Then COVID hit.</p>
<p>By early 2021, the freelance market in Europe exploded. A friend at the European Commission made an intro, and I got my first contract offer within a couple of days. I also got into multiple hiring processes for contractors around the same time. My first gig was with the <a href="https://commission.europa.eu/index_en">European Commission</a>, and shortly after I switched to <a href="https://deliveroo.co.uk/">Deliveroo</a>.</p>
<p>I expected getting into freelancing to be hard. I just didn’t expect the path to involve a failed spam campaign, two years of waiting, and a global pandemic.</p>
</section>
<section id="what-freelancing-gave-me" class="level2 page-columns page-full">
<h2 class="anchored" data-anchor-id="what-freelancing-gave-me">What freelancing gave me</h2>
<p>My first freelance contract paid €350 a day. From there, my rates grew quickly. For the last few years, I’ve usually charged between €600 and €900 a day. For very short engagements, I generally charge more. In the US or parts of Northern Europe, good freelancers can definitely do better.</p>
<div class="quarto-figure quarto-figure-center page-columns page-full">
<figure class="figure page-columns page-full">
<p><a href="images/5-years-freelancing-in-Europe/revenue-over-time.png" class="lightbox" data-gallery="quarto-lightbox-gallery-2" title="Gross revenue over time"><img src="https://dylancastillo.co/posts/images/5-years-freelancing-in-Europe/revenue-over-time.png" class="img-fluid figure-img" alt="Gross revenue over time"></a></p>
<figcaption class="margin-caption">Gross revenue over time</figcaption>
</figure>
</div>
<p>Every year since I started freelancing, I’ve made more than I did in my last full-time job. But my income has still varied a lot depending on how much time I chose to spend on client work. In 2022 and 2023, I spent a lot of time on <a href="https://kasipa.com">business</a> <a href="https://aitheneum.iwanalabs.com/">ideas</a>, and that shows in the numbers. In 2024 and 2025, I went in the opposite direction: back-to-back contracts and fewer experiments. I worked my ass off. That shows too.</p>
<p>If you have a good lead flow, freelancing gives you a very direct link between effort and income. Work hard, and you will be rewarded accordingly.</p>
<p>That matters to me. My wife and I are immigrants, and we provide financial support for my family and extended family. So yes, I care about money. I also wanted to earn more without leaving Spain, because my wife’s work makes relocating difficult. Freelancing gave me that option.</p>
<p>The second thing it gave me is freedom and comfort. I work fully remotely from home and can often set my own schedule. I stay healthy: I hit the gym four days a week and walk 12,000 steps every day with my walking pad while I work. I also avoid most corporate theater: 360 reviews or recurring “how are you feeling?” meetings.</p>
<p>The third is variety. On one project I’m acting as a technical product manager, scoping a product and figuring out what should be built. On another I’m a data scientist, planning and analyzing experiments. On another I’m an AI engineer, shipping features for workflows or agents. I love the intellectual range of things I get to do.</p>
<p>The fourth is the room it has given me to work on my own things. Because I could work intensely for a few months, save enough to coast for the rest of the year, and then step away, I had real time to try out business ideas and side projects. None of my business ideas worked out, but I’m glad I got to try.</p>
</section>
<section id="how-i-got-work" class="level2">
<h2 class="anchored" data-anchor-id="how-i-got-work">How I got work</h2>
<p>How much you make heavily depends on where your clients are. In Europe, Northern European clients usually pay much better than Southern European ones. Even while living in Spain, most of my clients are not local.</p>
<p>For a bit of wider market context, I once took part in a survey of AI freelancers. Among the Europeans in that group, median yearly gross revenue was around €85k. For the US-based freelancers, it was closer to $150k (roughly €128k).</p>
<p>Pricing model matters too. I’ve experimented with deliverable-based pricing, and I usually prefer it. When the contract is tied to an outcome rather than time, my effective rate goes up and the incentives are cleaner: the client cares less about my hours and more about the result.</p>
<p>The danger is scope. If you misjudge the work, you can accidentally sell yourself a very stressful unpaid internship.</p>
<p>But pricing only matters if you have demand. Without lead flow, you have no leverage. For me, that demand has come from a few places.</p>
<p>After the first contract, many projects came from being active on LinkedIn. Some of my posts got good traction. For example, <a href="https://seneca.dylancastillo.co/">Ask Seneca</a>, a small demo that showcased a simple RAG system in the early days of LLMs. It ended up on the front page of Hacker News and got mentioned in The Economist. Other projects followed similar paths. That visibility translated into people reaching out about work.</p>
<p>Another big channel has been personal relationships. Friends from <a href="https://dylancastillo.co/posts/my-entrepreneur-first-experience.html">Entrepreneur First</a>, past clients, and former employers have reached out when they had projects I’d be a good fit for. These are the best types of leads because they require very little effort to close.</p>
<p>I’m also in a couple of freelance platforms (á la <a href="https://www.toptal.com/">Toptal</a>). They can be useful for lead flow, but over time the rates have gotten worse. Platforms make comparison easy, and comparison turns you into a commodity. That is bad for your rates.</p>
<p>The thing I have neglected lately is content. Which is stupid, because that’s how you build authority and get strangers to trust you. I once heard <a href="https://x.com/jxnlco/">Jason Liu</a> summarize the best way to charge high consulting rates: “be famous.” I agree. There’s a world of difference between someone wanting to hire “an AI engineer” and someone wanting to hire Dylan Castillo. No one can outcompete me at being me.</p>
<p>I’ve gotten a bit lazy on that front because I’m rarely out of projects. But I’m probably missing out on better opportunities, clearer positioning, and higher rates by not putting more work into the world.</p>
<p>Finally, freelancing runs on a much shorter horizon than full-time work. Projects typically run three to six months, and I rarely know in advance whether a contract will be extended — or whether I’ll even want to stay. That forces you into a permanent “always be selling” mode. Even when I’m fully engaged with a client, part of my brain is scanning for the next gig.</p>
</section>
<section id="how-i-positioned-myself" class="level2">
<h2 class="anchored" data-anchor-id="how-i-positioned-myself">How I positioned myself</h2>
<p>Over the last five years, I’ve worked across data science, data engineering, ML, backend systems, and AI products.</p>
<p>Early on, most of the work was classic data and ML. Since 2023, it has shifted heavily toward AI: building workflows and agents for B2B clients, helping startups ship AI products, supporting academic research groups, teaching courses, and leading technical work on personalization and experimentation.</p>
<p>At some point, I tried to position myself as the person who could help teams take AI products from zero to one. But that quickly blurred into something less useful: “the guy who knows about AI.”</p>
<p>That is the trade-off I’ve been struggling with. Clients generally hire me because I can get things done. I know how to connect the dots between product, data, backend, and AI. But I’m not getting hired because someone thinks of me as the expert in one particular topic.</p>
<p>Freelancing made it easy to stay that way. Contracts tend to be short, and I usually picked projects based on what I found interesting and what paid well. I never made a proper plan for what I wanted to become. So even as my rates grew, I never found a way to stop looking like just another freelancer.</p>
<p>That is the ceiling I’ve hit. I can still find work. But I don’t yet have a strong enough answer to “Why you?” to go above the rates I charge now. At higher rates, clients want more than competence. They pay for reputation and/or deep expertise, not just a get-things-done guy.</p>
</section>
<section id="what-id-do-differently" class="level2">
<h2 class="anchored" data-anchor-id="what-id-do-differently">What I’d do differently</h2>
<p>With hindsight, these are the things I’d do differently if I were starting over:</p>
<ol type="1">
<li><p><strong>Don’t start by trying to convince strangers.</strong> The boring answer is the right answer: use your network. And by that, I don’t mean LinkedIn randos. Reach out to former colleagues, old bosses, past clients, and people who already know your work. That’s the simplest way to get started. It’s awkward, but it has, by far, the highest chance of success.</p></li>
<li><p><strong>Pick a lane earlier.</strong> I never committed to a specific niche or to publishing consistently, so I did not build much of a reputation beyond my close network. If I were starting again, I would choose a problem space and start making content about it. Even if I got it wrong at first, forcing myself to put work into the world would help me sharpen my positioning.</p></li>
<li><p><strong>Treat client work as market research.</strong> I spent too much time on random side projects and not enough time asking which parts of my client work could become repeatable services or products.</p></li>
<li><p><strong>Don’t be greedy.</strong> I would have been more deliberate about choosing projects that exposed me to better problems or helped me build expertise I could later productize. A slightly lower-paying project in the right direction can have a better ROI than a high-paying project that keeps you stuck.</p></li>
</ol>
</section>
<section id="whats-next-for-me" class="level2">
<h2 class="anchored" data-anchor-id="whats-next-for-me">What’s next for me</h2>
<p>I’ve never felt this uncertain about the future – for reasons both good and bad.</p>
<p>On the optimistic side, <a href="https://iwanalabs.com/">Iwana Labs</a> is starting to change. Until now, it has mostly been a vehicle for my own freelance work, with occasional projects where I brought in collaborators. Some of those collaborations have kept growing, and by the end of the year, I hope to go from a one-man show to a small team.</p>
<p>We don’t want to be just another AI development agency™. Yes, I know everyone says the same thing when they get started. But we have a few areas where we’ve built expertise over the years, and I hope we can position ourselves around them.</p>
<p>On the scary side, I genuinely don’t know what’s going to happen to the software industry. The changes from AI have been amazing to watch. But my best guess is that we’re heading toward a much more unequal distribution of the profits. I worry the economics are shifting from something like the top 20% capturing 80% of the value to something closer to the top 1% capturing 99%.</p>
<p>We’ve already seen the <a href="https://www.nytimes.com/2026/04/02/technology/ai-billion-dollar-company-medvi.html?unlocked_article_code=1.X1A.fHR4.4vekZY08eLvB&amp;smid=url-share">$1B one-man company</a> and companies with <a href="https://sacra.com/research/cursor-at-100m-arr/">crazy growth rates</a>. Work that used to require a full team can increasingly be done by fewer people with better tools. Maybe the amount of work to be done will keep growing even faster than the supply of people able to do it. I hope so. But I’m not sure.</p>
<p>When AI gives everyone the ability to build, being “pretty good at building” becomes less valuable. You need taste, distribution, deeper expertise, and stronger relationships.</p>
<p>And honestly, I have to ask myself whether I’ll make it into that top 1%. I’m optimistic about my skills, but the real world doesn’t care about that. There are tons of amazing, highly motivated people out there. We’re in an industry with almost no barriers to entry, where the money has been good so far, and where AI can already do a lot of the work. What was already an extremely competitive industry just got much more competitive.</p>
<p>The next chapter is partly about responding to all of that. Finding the right people to grow with, and figuring out where we can set ourselves apart. Five years in, I love what freelancing has taught me and the opportunities it has given me. But the next five years will need to look different if I want to stay relevant through what’s coming.</p>


</section>

<div id="quarto-appendix" class="default"><section class="quarto-appendix-contents" id="quarto-citation"><h2 class="anchored quarto-appendix-heading">Citation</h2><div><div class="quarto-appendix-secondary-label">BibTeX citation:</div><pre class="sourceCode code-with-copy quarto-appendix-bibtex"><code class="sourceCode bibtex">@online{castillo2026,
  author = {Castillo, Dylan},
  title = {Five {Years} of {Freelancing}},
  date = {2026-04-25},
  url = {https://dylancastillo.co/posts/5-years-freelancing-in-europe.html},
  langid = {en}
}
</code></pre><div class="quarto-appendix-secondary-label">For attribution, please cite this work as:</div><div id="ref-castillo2026" class="csl-entry quarto-appendix-citeas">
Castillo, Dylan. 2026. <span>“Five Years of Freelancing.”</span> April
25. <a href="https://dylancastillo.co/posts/5-years-freelancing-in-europe.html">https://dylancastillo.co/posts/5-years-freelancing-in-europe.html</a>.
</div></div></section></div> ]]></description>
  <category>personal</category>
  <guid>https://dylancastillo.co/posts/5-years-freelancing-in-europe.html</guid>
  <pubDate>Sat, 25 Apr 2026 00:00:00 GMT</pubDate>
  <media:content url="https://dylancastillo.co/posts/images/cards/5-years-freelancing-in-europe.png" medium="image" type="image/png" height="76" width="144"/>
</item>
<item>
  <title>LLM research on Hacker News is drying up</title>
  <dc:creator>Dylan Castillo</dc:creator>
  <link>https://dylancastillo.co/til/llm-research-on-hacker-news-is-dying.html</link>
  <description><![CDATA[ 




<p>I thought I was seeing fewer arXiv papers on the front page of Hacker News (HN) these days, and I wanted to check if that was real.</p>
<p>So I asked Claude to run a quick analysis: track the share of arXiv stories on HN over time. It queried the <a href="https://console.cloud.google.com/marketplace/product/y-combinator/hacker-news">BigQuery HN dataset</a>, bucketed the stories by month, and plotted the series:</p>
<div class="quarto-figure quarto-figure-center page-columns page-full">
<figure class="figure page-columns page-full">
<p><a href="./images/arxiv-hacker-news/hn_arxiv_share.png" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Percentage of HN stories linking to arXiv"><img src="https://dylancastillo.co/til/images/arxiv-hacker-news/hn_arxiv_share.png" class="img-fluid figure-img" alt="Percentage of HN stories linking to arXiv"></a></p>
<figcaption class="margin-caption">Percentage of HN stories linking to arXiv</figcaption>
</figure>
</div>
<p>That confirmed my hunch. arXiv posts have been decreasing rapidly in the last few months. Interestingly, it also showed another peak around 2019, and I wanted to know what drove it.</p>
<p>I asked Claude to pull the top 100 papers by upvotes from 2019 and group them by topic. It was the deep learning peak. 41% of the top 100 were about deep learning.</p>
<p>Then I ran the same query for 2023-2026, to see how dominant LLMs were. 59% of the top 100 upvoted papers were about LLMs or AI.</p>
<p>So I asked him to make a nice chart with all of this:</p>
<div class="quarto-figure quarto-figure-center page-columns page-full">
<figure class="figure page-columns page-full">
<p><a href="./images/arxiv-hacker-news/hn_arxiv_topics.png" class="lightbox" data-gallery="quarto-lightbox-gallery-2" title="Distribution of topics of arXiv stories"><img src="https://dylancastillo.co/til/images/arxiv-hacker-news/hn_arxiv_topics.png" class="img-fluid figure-img" alt="Distribution of topics of arXiv stories"></a></p>
<figcaption class="margin-caption">Distribution of topics of arXiv stories</figcaption>
</figure>
</div>
<p>Then I wanted to see which 2019 papers aged well, so I asked Claude to pull the ones that held up from the top 100. Here’s what he got:</p>
<ul>
<li>MuZero — Mastering Atari, Go, Chess and Shogi by Planning with a Learned Model (161 pts) — DeepMind’s successor to AlphaZero</li>
<li>EfficientNet — Rethinking Model Scaling for Convolutional Neural Networks (119 pts) — compound scaling, set the new CV SOTA</li>
<li>XLNet — Generalized Autoregressive Pretraining for Language Understanding (79 pts) — briefly dethroned BERT</li>
<li>PyTorch: An Imperative Style, High-Performance Deep Learning Library (113 pts) — the NeurIPS paper formalizing PyTorch’s design</li>
<li>On the Measure of Intelligence (80 pts) — Chollet’s ARC / “human-like intelligence” manifesto</li>
</ul>
<p>It’s too early to know which 2023-2026 papers will hold up, so I asked Claude to guess:</p>
<ul>
<li>DeepSeek-R1 — Incentivizing Reasoning Capability in LLMs via RL (1,351 pts) — first open recipe for o1-style reasoning via pure RL on verifiable rewards</li>
<li>Generative Agents — Interactive Simulacra of Human Behavior (391 pts) — the canonical “Smallville” paper, template for LLM agent architectures</li>
<li>The Era of 1-bit LLMs — BitNet b1.58, ternary parameters for cost-effective computing (1,040 pts) — first credible case for low-bit inference as the default</li>
<li>Differential Transformer (562 pts) — attention with a noise-cancelling term, clean architectural contribution with a real theoretical story</li>
<li>LK-99 cluster — room-temperature superconductor preprints (2,408 + 1,690 pts) — landmark meta-science, not physics: open-science-at-wire-speed and the canonical case of crowdsourced replication</li>
</ul>
<p>That was fun. Thanks, Claude.</p>



<div id="quarto-appendix" class="default"><section class="quarto-appendix-contents" id="quarto-citation"><h2 class="anchored quarto-appendix-heading">Citation</h2><div><div class="quarto-appendix-secondary-label">BibTeX citation:</div><pre class="sourceCode code-with-copy quarto-appendix-bibtex"><code class="sourceCode bibtex">@online{castillo2026,
  author = {Castillo, Dylan},
  title = {LLM Research on {Hacker} {News} Is Drying Up},
  date = {2026-04-24},
  url = {https://dylancastillo.co/til/llm-research-on-hacker-news-is-dying.html},
  langid = {en}
}
</code></pre><div class="quarto-appendix-secondary-label">For attribution, please cite this work as:</div><div id="ref-castillo2026" class="csl-entry quarto-appendix-citeas">
Castillo, Dylan. 2026. <span>“LLM Research on Hacker News Is Drying
Up.”</span> April 24. <a href="https://dylancastillo.co/til/llm-research-on-hacker-news-is-dying.html">https://dylancastillo.co/til/llm-research-on-hacker-news-is-dying.html</a>.
</div></div></section></div> ]]></description>
  <category>til</category>
  <guid>https://dylancastillo.co/til/llm-research-on-hacker-news-is-dying.html</guid>
  <pubDate>Fri, 24 Apr 2026 00:00:00 GMT</pubDate>
</item>
<item>
  <title>The last shall be (slighly) safer</title>
  <dc:creator>Dylan Castillo</dc:creator>
  <link>https://dylancastillo.co/til/securing-package-managers.html</link>
  <description><![CDATA[ 




<p>While I use Claude Code to build <a href="https://estimator.dylancastillo.co/">crappy forms</a>, real hackers use it to make money.</p>
<p><a href="https://futuresearch.ai/blog/litellm-attack-transcript/">Supply</a> <a href="https://www.stepsecurity.io/blog/axios-compromised-on-npm-malicious-versions-drop-remote-access-trojan">chain</a> <a href="https://www.paloaltonetworks.com/blog/cloud-security/trivy-supply-chain-attack/">attacks</a> have become so common these days that I had to learn they’re not about an Amazon delivery guy crashing into my front door.</p>
<p>I know I’ll inevitably become a victim of one (a supply chain attack, not an Amazon delivery guy), but I’m not handing over my $50 in BTC to North Korea without a fight. So I took this measure, courtesy of some random guy on Hacker News:</p>
<blockquote class="blockquote">
<p>PSA: npm/bun/pnpm/uv now all support setting a minimum release age for packages.</p>
<p>I also have <code>ignore-scripts=true</code> in my ~/.npmrc. Based on the analysis, that alone would have mitigated the vulnerability. bun and pnpm do not execute lifecycle scripts by default.</p>
<p>Here’s how to set global configs to set min release age to 7 days:</p>
<p>~/.config/uv/uv.toml exclude-newer = “7 days”</p>
<p>~/.npmrc min-release-age=7 # days ignore-scripts=true</p>
<p>~/Library/Preferences/pnpm/rc minimum-release-age=10080 # minutes</p>
<p>~/.bunfig.toml [install] minimumReleaseAge = 604800 # seconds</p>
<p>(Side note, it’s wild that npm, bun, and pnpm have all decided to use different time units for this configuration.)</p>
<p>If you’re developing with LLM agents, you should also update your AGENTS.md/CLAUDE.md file with some guidance on how to handle failures stemming from this config as they will cause the agent to unproductively spin its wheels.</p>
</blockquote>
<p>This gives researchers and security teams time to analyze new releases and flag malicious ones.</p>
<p>Then, I thought it’d be a bad idea if everyone uses the same value, so I cowardly changed mine to 8 days. Sadly, others quickly realized this:</p>
<blockquote class="blockquote">
<p>that’s why people are telling others to use 7 days but using 8 days themselves :)</p>
</blockquote>
<p>So I picked 9… and kept scrolling:</p>
<blockquote class="blockquote">
<p>brb, switching everything to 9 days</p>
</blockquote>
<p>Oh, fuck.</p>



<div id="quarto-appendix" class="default"><section class="quarto-appendix-contents" id="quarto-citation"><h2 class="anchored quarto-appendix-heading">Citation</h2><div><div class="quarto-appendix-secondary-label">BibTeX citation:</div><pre class="sourceCode code-with-copy quarto-appendix-bibtex"><code class="sourceCode bibtex">@online{castillo2026,
  author = {Castillo, Dylan},
  title = {The Last Shall Be (Slighly) Safer},
  date = {2026-04-01},
  url = {https://dylancastillo.co/til/securing-package-managers.html},
  langid = {en}
}
</code></pre><div class="quarto-appendix-secondary-label">For attribution, please cite this work as:</div><div id="ref-castillo2026" class="csl-entry quarto-appendix-citeas">
Castillo, Dylan. 2026. <span>“The Last Shall Be (Slighly) Safer.”</span>
April 1. <a href="https://dylancastillo.co/til/securing-package-managers.html">https://dylancastillo.co/til/securing-package-managers.html</a>.
</div></div></section></div> ]]></description>
  <category>til</category>
  <category>security</category>
  <guid>https://dylancastillo.co/til/securing-package-managers.html</guid>
  <pubDate>Wed, 01 Apr 2026 00:00:00 GMT</pubDate>
</item>
<item>
  <title>2025: Personal Snapshot</title>
  <dc:creator>Dylan Castillo</dc:creator>
  <link>https://dylancastillo.co/posts/2025-personal-snapshot.html</link>
  <description><![CDATA[ 




<p>I’m an independent AI consultant, trying to grow my business. This is my annual review. If it’s me rereading this, welcome back. This is Dylan from 2025.</p>
<section id="money-work" class="level2 page-columns page-full">
<h2 class="anchored" data-anchor-id="money-work">Money &amp; work</h2>
<p>This was the year I made the most money. I grew my revenue by 13%. Growth came mostly from working on more projects with previous clients. I worked on 11 projects, with 6 clients. I did 6 deliverable-based projects and 5 time-based projects. Deliverable-based projects accounted for 57% of my revenue.</p>
<div class="quarto-figure quarto-figure-center page-columns page-full">
<figure class="figure page-columns page-full">
<p><a href="images/2025-personal-snapshot/revenue-over-time.png" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Revenue over time"><img src="https://dylancastillo.co/posts/images/2025-personal-snapshot/revenue-over-time.png" class="img-fluid figure-img" alt="Revenue over time"></a></p>
<figcaption class="margin-caption">Revenue over time</figcaption>
</figure>
</div>
<p>It became obvious to me that expanding your projects with existing clients is a lot easier than selling to new clients.</p>
<p>I also reduced costs by 11%. Most of the cost reduction came from firing a frontend developer that I had hired in 2024. I let him go because of AI progress. I could use Claude Code to get a better, cheaper, and faster version of the same services he was providing me. It didn’t make financial sense to keep him on board.</p>
<p>Still, I felt bad about it. He was a good guy, but he wasn’t a good fit. I also felt I didn’t do a good job during the hiring process. This cost me resources and time, so I’ll be more careful with the next hires.</p>
<div class="quarto-figure quarto-figure-center page-columns page-full">
<figure class="figure page-columns page-full">
<p><a href="images/2025-personal-snapshot/github.webp" class="lightbox" data-gallery="quarto-lightbox-gallery-2" title="GitHub contributions"><img src="https://dylancastillo.co/posts/images/2025-personal-snapshot/github.webp" class="img-fluid figure-img" alt="GitHub contributions"></a></p>
<figcaption class="margin-caption">GitHub contributions</figcaption>
</figure>
</div>
<p>This year I more than doubled my number of commits and wrote code 312 days of the year. It helped that some of the paid work I did was the <a href="https://github.com/IBEX-TUDelft/econagents">econagents</a> open source Python library. Sometimes I cheated, because I was tired and all I did was merge Dependabot’s PRs. I feel like I coded and learned quite a bit more than last year, but not as much as it looks.</p>
<p>Throughout the year, a recurring dilemma was whether to build a product or keep focusing on services. For the first half of the year, I dedicated quite a bit of time to Namemancer, a tool meant to help trademark lawyers speed up their research process. However, I kept seeing more market pull for services and struggled to make meaningful progress on it. I kept hoping I could magically advance on both fronts, but eventually had to face reality and decided to kill Namemancer.</p>
<p>And I felt relieved. I’ve come to understand that I need to be more disciplined and should focus on fewer things. I often end up spreading myself thin by taking on too many things at once and do a poor job at most of them.</p>
</section>
<section id="life" class="level2">
<h2 class="anchored" data-anchor-id="life">Life</h2>
<p>In late February, my wife, Maria, and I moved to Cork, Ireland for three months. This got off to a rocky start. We had been having some differences since the end of 2024, and the first few weeks in Ireland put this on steroids. We’ve been together for 15 years, eight of them married. We’ve had our share of difficult stretches, but this was one of the toughest.</p>
<p>Through many tough conversations and commitment from both sides, we successfully worked through it and grew closer together. Ireland went from a rocky start to one of the best experiences we’ve had as a couple. We traveled around the country, went for runs during sunset, tried every dairy product available, and really became a team again. We still cherish our time in Ireland, and even considered moving there!</p>
<div class="custom-gallery-container" data-images="[
       {&quot;url&quot;: &quot;https://s3.eu-west-1.amazonaws.com/images.dylancastillo.co/2025-personal-snapshot/airport-leaving.jpg&quot;, &quot;caption&quot;: &quot;On our way to Ireland&quot;},
       {&quot;url&quot;: &quot;https://s3.eu-west-1.amazonaws.com/images.dylancastillo.co/2025-personal-snapshot/cork-university.jpg&quot;, &quot;caption&quot;: &quot;Cork I&quot;},
       {&quot;url&quot;: &quot;https://s3.eu-west-1.amazonaws.com/images.dylancastillo.co/2025-personal-snapshot/cork-bbq.jpg&quot;, &quot;caption&quot;: &quot;Cork II&quot;},
       {&quot;url&quot;: &quot;https://s3.eu-west-1.amazonaws.com/images.dylancastillo.co/2025-personal-snapshot/ireland-biking.jpeg&quot;, &quot;caption&quot;: &quot;Cork III&quot;},
       {&quot;url&quot;: &quot;https://s3.eu-west-1.amazonaws.com/images.dylancastillo.co/2025-personal-snapshot/cork-friends.JPG&quot;, &quot;caption&quot;: &quot;Cork IV&quot;},
       {&quot;url&quot;: &quot;https://s3.eu-west-1.amazonaws.com/images.dylancastillo.co/2025-personal-snapshot/cork-cold-water.JPG&quot;, &quot;caption&quot;: &quot;Cork V&quot;}
     ]">
<img class="gallery-image" src="" alt="">
<div class="gallery-caption">

</div>
</div>
<p>We went back to Madrid in May, and then in July we moved to Salamanca, a small city to the north of Madrid. Maria found a good job opportunity, and since I can work from anywhere, we decided to pack our bags and go. We spent a good chunk of July and August moving. We went from a small, old 2-bedroom apartment to a newly renovated 4-bedroom apartment. It was a good change, especially for me, given that I work from home.</p>
<div class="custom-gallery-container" data-images="[
       {&quot;url&quot;: &quot;https://s3.eu-west-1.amazonaws.com/images.dylancastillo.co/2025-personal-snapshot/bathroom.jpeg&quot;, &quot;caption&quot;: &quot;Proud builder of cabinets&quot;},
       {&quot;url&quot;: &quot;https://s3.eu-west-1.amazonaws.com/images.dylancastillo.co/2025-personal-snapshot/friends-salamanca.jpeg&quot;, &quot;caption&quot;: &quot;Salamanca I&quot;},
       {&quot;url&quot;: &quot;https://s3.eu-west-1.amazonaws.com/images.dylancastillo.co/2025-personal-snapshot/salamanca.jpeg&quot;, &quot;caption&quot;: &quot;Salamanca II&quot;},
       {&quot;url&quot;: &quot;https://s3.eu-west-1.amazonaws.com/images.dylancastillo.co/2025-personal-snapshot/friends-luciano.jpeg&quot;, &quot;caption&quot;: &quot;Salamanca III&quot;},
       {&quot;url&quot;: &quot;https://s3.eu-west-1.amazonaws.com/images.dylancastillo.co/2025-personal-snapshot/salamanca-2.jpeg&quot;, &quot;caption&quot;: &quot;Salamanca IV&quot;}
     ]">
<img class="gallery-image" src="" alt="">
<div class="gallery-caption">

</div>
</div>
<p>It was also a good year for reconnecting. I got to see friends I hadn’t seen in a while, we made <a href="https://en.wikipedia.org/wiki/Hallaca">hallacas</a> again, and I spent a lot of time with my family. We had an especially fun Christmas and New Year’s.</p>
<div class="custom-gallery-container" data-images="[
       {&quot;url&quot;: &quot;https://s3.eu-west-1.amazonaws.com/images.dylancastillo.co/2025-personal-snapshot/family.jpeg&quot;, &quot;caption&quot;: &quot;Family&quot;},
       {&quot;url&quot;: &quot;https://s3.eu-west-1.amazonaws.com/images.dylancastillo.co/2025-personal-snapshot/friends-alejandro.jpeg&quot;, &quot;caption&quot;: &quot;Old friends I&quot;},
       {&quot;url&quot;: &quot;https://s3.eu-west-1.amazonaws.com/images.dylancastillo.co/2025-personal-snapshot/friends-victor-cris.jpeg&quot;, &quot;caption&quot;: &quot;Old friends II&quot;},
       {&quot;url&quot;: &quot;https://s3.eu-west-1.amazonaws.com/images.dylancastillo.co/2025-personal-snapshot/olympics-dani.jpeg&quot;, &quot;caption&quot;: &quot;Old friends III&quot;},
       {&quot;url&quot;: &quot;https://s3.eu-west-1.amazonaws.com/images.dylancastillo.co/2025-personal-snapshot/christmas.jpeg&quot;, &quot;caption&quot;: &quot;Christmas&quot;},
       {&quot;url&quot;: &quot;https://s3.eu-west-1.amazonaws.com/images.dylancastillo.co/2025-personal-snapshot/new-years-eve.jpeg&quot;, &quot;caption&quot;: &quot;New Years Eve&quot;},
       {&quot;url&quot;: &quot;https://s3.eu-west-1.amazonaws.com/images.dylancastillo.co/2025-personal-snapshot/party.jpeg&quot;, &quot;caption&quot;: &quot;Fun party!&quot;}
     ]">
<img class="gallery-image" src="" alt="">
<div class="gallery-caption">

</div>
</div>
</section>
<section id="health" class="level2">
<h2 class="anchored" data-anchor-id="health">Health</h2>
<p>I lifted more weights this year but did less cardio. After we moved to Ireland, I started going to the gym instead of training at home. I kept doing this even after coming back to Spain. It’s been good for my mental health, as otherwise I’d often spend days without leaving my house. I’ve also gained more muscle mass compared to previous years.</p>
<p>My RHR is now at 56 bpm and VO2 max is at 46 ml/kg/min. Both are pretty much the same compared to last year. I think I need more cardio and better rest to move these markers.</p>
<p>The biggest health concern this year was sleeping issues and neck/back pain. The sleeping issues started a few weeks after I arrived in Ireland. It was a mix of stress and working from the same room where I was sleeping. On good days it takes me 15–20 minutes to fall asleep. On bad days, it can take me 2–3 hours. I’ve been able to improve this over time, but I still need to be careful with my sleep hygiene.</p>
<p>I have some small protrusions in my neck that get worse when I don’t sleep well or exercise. The last 3 months of the year were pretty stressful, so this got bad. It improved after I changed my home desk setup, did rehab exercises, and started going to the physio regularly.</p>
</section>
<section id="whats-next" class="level2">
<h2 class="anchored" data-anchor-id="whats-next">What’s next?</h2>
<p>No big lessons this year, besides the same one I’ve struggled with in the past: I still don’t know how to properly regulate myself. I have a hard time managing stress and that often lead to trouble sleeping and depression. I keep postponing looking for a long-term solution for this as I always find a way to push through, but I don’t think my current approach will work forever.</p>
<p>On the bright side, some exciting projects have come up for 2026. If things go well, I might finally grow my consulting practice beyond a one-man operation. You’ll have to come back next year to see how it goes.</p>


</section>

<div id="quarto-appendix" class="default"><section class="quarto-appendix-contents" id="quarto-citation"><h2 class="anchored quarto-appendix-heading">Citation</h2><div><div class="quarto-appendix-secondary-label">BibTeX citation:</div><pre class="sourceCode code-with-copy quarto-appendix-bibtex"><code class="sourceCode bibtex">@online{castillo2026,
  author = {Castillo, Dylan},
  title = {2025: {Personal} {Snapshot}},
  date = {2026-02-24},
  url = {https://dylancastillo.co/posts/2025-personal-snapshot.html},
  langid = {en}
}
</code></pre><div class="quarto-appendix-secondary-label">For attribution, please cite this work as:</div><div id="ref-castillo2026" class="csl-entry quarto-appendix-citeas">
Castillo, Dylan. 2026. <span>“2025: Personal Snapshot.”</span> February
24. <a href="https://dylancastillo.co/posts/2025-personal-snapshot.html">https://dylancastillo.co/posts/2025-personal-snapshot.html</a>.
</div></div></section></div> ]]></description>
  <category>personal-snapshot</category>
  <guid>https://dylancastillo.co/posts/2025-personal-snapshot.html</guid>
  <pubDate>Tue, 24 Feb 2026 00:00:00 GMT</pubDate>
  <media:content url="https://dylancastillo.co/posts/images/cards/2025-personal-snapshot.png" medium="image" type="image/png" height="76" width="144"/>
</item>
<item>
  <title>Cover Your Ass</title>
  <dc:creator>Dylan Castillo</dc:creator>
  <link>https://dylancastillo.co/posts/cover-your-ass.html</link>
  <description><![CDATA[ 




<p>A simple hack that will make your life easier at work:</p>
<ol type="1">
<li>Always cover your ass (CYA).</li>
<li>Always assume others will do the same.</li>
</ol>
<p>CYA has a <a href="https://www.investopedia.com/terms/c/cover-your-ass.asp">bad rep</a>, because lazy people use it to deflect blame onto others. But CYA is just a communication style. It asks you to do two things: make priorities explicit and flag potential issues early.</p>
<p>I’ve worked for all sorts of organizations: tech, consulting, public, big, small. As a freelancer, I have a high rate of repeat clients, and, until now, I’ve always received positive feedback about my work, including intros to other clients. This approach has served me well.</p>
<p>After organizations reach a certain size, successful projects become vehicles for promotions, and failing projects become express tickets to career purgatory. Picking winners from losers isn’t something you can always do. But you can always choose to CYA. On winners, CYA isn’t always a big issue: people are happy, and no one’s looking to point fingers. On losers, it can be the difference between “project X sucked, but Dylan always fought to keep the boat afloat” and “project X sucked, but Dylan sucked even more.”</p>
<p>Take a simple case that I see all the time:</p>
<p>Boss goes to Jane and asks, “Hey Jane, can you fix bug X?” Jane responds, “Yes, sure!” After some research, Jane realizes that bug X is caused by another bug in a system owned by John. Jane is also a good person, and knows that John is overworked, so she asks if he can look at it when he has some time. He replies affirmatively. So she goes to work on some of her other tasks while she waits for John to get back to her.</p>
<p>A week later, Boss asks Jane, “Jane, did you fix bug X?” and Jane responds, “Not yet. The issue is caused by another bug, and I’m waiting for John to investigate that one.” Then Boss tells Jane, “That was a critical bug that needed fixing ASAP. We just lost $20M. You suck!”</p>
<p>Ok, the last bit was too much, but you get the idea. Jane had a task that depended on someone else. She didn’t know the priority of that task in the grand scheme of things, and assumed it could wait until John had time to investigate it. Then, when it was already too late, she realized how critical it was.</p>
<p>Jane left her ass uncovered by:</p>
<ol type="1">
<li>Not clarifying the priority of the task from the start</li>
<li>Not reporting that the task was blocked, and might require escalation to get it sorted out</li>
</ol>
<p>You might think that the problem was Jane’s boss, who didn’t communicate the priority right away. And you’d be right. But, once again, you don’t get to pick your boss most of the time. And you should assume that they will also cover their ass by throwing you under the bus if needed.</p>
<p>So, follow your dreams, but cover your ass. It’s not by chance that humans have been doing so –literally and figuratively– for hundreds of thousands of years.</p>



<div id="quarto-appendix" class="default"><section class="quarto-appendix-contents" id="quarto-citation"><h2 class="anchored quarto-appendix-heading">Citation</h2><div><div class="quarto-appendix-secondary-label">BibTeX citation:</div><pre class="sourceCode code-with-copy quarto-appendix-bibtex"><code class="sourceCode bibtex">@online{castillo2026,
  author = {Castillo, Dylan},
  title = {Cover {Your} {Ass}},
  date = {2026-02-22},
  url = {https://dylancastillo.co/posts/cover-your-ass.html},
  langid = {en}
}
</code></pre><div class="quarto-appendix-secondary-label">For attribution, please cite this work as:</div><div id="ref-castillo2026" class="csl-entry quarto-appendix-citeas">
Castillo, Dylan. 2026. <span>“Cover Your Ass.”</span> February 22. <a href="https://dylancastillo.co/posts/cover-your-ass.html">https://dylancastillo.co/posts/cover-your-ass.html</a>.
</div></div></section></div> ]]></description>
  <category>essay</category>
  <guid>https://dylancastillo.co/posts/cover-your-ass.html</guid>
  <pubDate>Sun, 22 Feb 2026 00:00:00 GMT</pubDate>
  <media:content url="https://dylancastillo.co/posts/images/cards/cover-your-ass.png" medium="image" type="image/png" height="76" width="144"/>
</item>
<item>
  <title>I hate AI side projects</title>
  <dc:creator>Dylan Castillo</dc:creator>
  <link>https://dylancastillo.co/posts/ai-side-projects.html</link>
  <description><![CDATA[ 




<p>The best thing about AI is that EVERYONE can build now. The worst thing about AI is that EVERYONE can build now.</p>
<p>I’ve worked on and shared many of my <a href="https://dylancastillo.co/projects.html">side projects</a> over the years. I built them because they gave me a chance to learn new things, such as <a href="https://github.com/dylanjcastillo/twitter-sentiment-tracker">deploying my first web app on a VPS</a> or running <a href="https://github.com/dylanjcastillo/pandas-cheatsheet/">Python in the browser</a>. Sometimes, I got new clients when some of my <a href="https://seneca.dylancastillo.co/">projects</a> went (just a bit) viral. And, more importantly, I had fun while building, like the time I <a href="https://www.youtube.com/watch?v=FoiH8MjNQBI">built a game</a> (that the NYT didn’t buy!), or when I replicated the results from a <a href="https://task246.dylancastillo.co/">1970s cognitive science experiment</a>.</p>
<p>I still work on side projects. But now I dread sharing them. The internet is saturated with AI slop. If you don’t have something truly special, it can feel like you’re just adding one more padlock to those sagging, padlock-ridden bridges. Most of my past side projects would take me a few minutes or hours to build with Claude Code. Today, they’re not worth talking about.</p>
<p>I rarely look at projects on Hacker News, Reddit, or X anymore. All landing pages and GitHub repos look the same. All launch messages look the same. The signal-to-noise ratio is unbearably low.</p>
<p>And, dear reader, don’t get your hopes up. I’m also a sinner: I’ve contributed to the AI slop pile. I repent, but the damage is done.</p>
<p>Still, I’m optimistic about AI, and I’m very appreciative of the impact it has had on my work. I don’t want progress to stop or slow down. I don’t want less people building with AI. I just don’t know yet how to properly engage with a world that’s mostly created by AI.</p>



<div id="quarto-appendix" class="default"><section class="quarto-appendix-contents" id="quarto-citation"><h2 class="anchored quarto-appendix-heading">Citation</h2><div><div class="quarto-appendix-secondary-label">BibTeX citation:</div><pre class="sourceCode code-with-copy quarto-appendix-bibtex"><code class="sourceCode bibtex">@online{castillo2026,
  author = {Castillo, Dylan},
  title = {I Hate {AI} Side Projects},
  date = {2026-02-20},
  url = {https://dylancastillo.co/posts/ai-side-projects.html},
  langid = {en}
}
</code></pre><div class="quarto-appendix-secondary-label">For attribution, please cite this work as:</div><div id="ref-castillo2026" class="csl-entry quarto-appendix-citeas">
Castillo, Dylan. 2026. <span>“I Hate AI Side Projects.”</span> February
20. <a href="https://dylancastillo.co/posts/ai-side-projects.html">https://dylancastillo.co/posts/ai-side-projects.html</a>.
</div></div></section></div> ]]></description>
  <category>essay</category>
  <guid>https://dylancastillo.co/posts/ai-side-projects.html</guid>
  <pubDate>Fri, 20 Feb 2026 00:00:00 GMT</pubDate>
  <media:content url="https://dylancastillo.co/posts/images/cards/ai-side-projects.png" medium="image" type="image/png" height="76" width="144"/>
</item>
<item>
  <title>ClaudeCodeholic</title>
  <dc:creator>Dylan Castillo</dc:creator>
  <link>https://dylancastillo.co/posts/claudecodeholic.html</link>
  <description><![CDATA[ 




<p>Hi, I’m Dylan, and I’m a ClaudeCodeholic.</p>
<p>I’m not addicted because Claude Code makes me more productive. I’m addicted because it dulls the <em>pain</em> of thinking and makes development an endless betting game of “if I change the prompt just one more time, it’ll get it right.”</p>
<p>I don’t care about those studies comparing how much better or worse people do with AI coding tools. I’d still use it, even if I was 50% less productive.</p>
<p>It might take twice as long to finish what I’m trying to build. Maybe I never finish it at all. But I feel I’m making progress. Most importantly, I hardly need to be <em>there</em>.</p>
<p>Companies worship productivity. I worship comfort. And Claude Code is my warm bed on a cold night of winter.</p>
<p>Now, if you’ll excuse me, I have a prompt to finish, before I can continue doomscrolling on reddit.</p>



<div id="quarto-appendix" class="default"><section class="quarto-appendix-contents" id="quarto-citation"><h2 class="anchored quarto-appendix-heading">Citation</h2><div><div class="quarto-appendix-secondary-label">BibTeX citation:</div><pre class="sourceCode code-with-copy quarto-appendix-bibtex"><code class="sourceCode bibtex">@online{castillo2025,
  author = {Castillo, Dylan},
  title = {ClaudeCodeholic},
  date = {2025-10-18},
  url = {https://dylancastillo.co/posts/claudecodeholic.html},
  langid = {en}
}
</code></pre><div class="quarto-appendix-secondary-label">For attribution, please cite this work as:</div><div id="ref-castillo2025" class="csl-entry quarto-appendix-citeas">
Castillo, Dylan. 2025. <span>“ClaudeCodeholic.”</span> October 18. <a href="https://dylancastillo.co/posts/claudecodeholic.html">https://dylancastillo.co/posts/claudecodeholic.html</a>.
</div></div></section></div> ]]></description>
  <category>essay</category>
  <guid>https://dylancastillo.co/posts/claudecodeholic.html</guid>
  <pubDate>Sat, 18 Oct 2025 00:00:00 GMT</pubDate>
  <media:content url="https://dylancastillo.co/posts/images/cards/claudecodeholic.png" medium="image" type="image/png" height="76" width="144"/>
</item>
<item>
  <title>New line in Claude Code in Alacritty</title>
  <dc:creator>Dylan Castillo</dc:creator>
  <link>https://dylancastillo.co/til/fix-claude-code-shift-enter-alacritty.html</link>
  <description><![CDATA[ 




<p>To make <code>Shift+Enter</code> create a new line in Claude Code when working with <a href="https://dylancastillo.co/til/install-alacritty-and-zellij-in-macos.html">Alacritty/Zellij</a>, you need to add the following lines to <code>~/.config/alacritty.toml</code>:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode numberSource shell number-lines code-with-copy"><code class="sourceCode"><span id="cb1-1">[keyboard]</span>
<span id="cb1-2">bindings = [</span>
<span id="cb1-3">    { key = "Return", mods = "Shift", chars = "\n" } </span>
<span id="cb1-4">]</span></code></pre></div></div>
<p>I found the solution in this <a href="https://github.com/anthropics/claude-code/issues/1300">GitHub issue</a>.</p>
<p>Running <code>/terminal-setup</code> didn’t work for me.</p>



<div id="quarto-appendix" class="default"><section class="quarto-appendix-contents" id="quarto-citation"><h2 class="anchored quarto-appendix-heading">Citation</h2><div><div class="quarto-appendix-secondary-label">BibTeX citation:</div><pre class="sourceCode code-with-copy quarto-appendix-bibtex"><code class="sourceCode bibtex">@online{castillo2025,
  author = {Castillo, Dylan},
  title = {New Line in {Claude} {Code} in {Alacritty}},
  date = {2025-10-18},
  url = {https://dylancastillo.co/til/fix-claude-code-shift-enter-alacritty.html},
  langid = {en}
}
</code></pre><div class="quarto-appendix-secondary-label">For attribution, please cite this work as:</div><div id="ref-castillo2025" class="csl-entry quarto-appendix-citeas">
Castillo, Dylan. 2025. <span>“New Line in Claude Code in
Alacritty.”</span> October 18. <a href="https://dylancastillo.co/til/fix-claude-code-shift-enter-alacritty.html">https://dylancastillo.co/til/fix-claude-code-shift-enter-alacritty.html</a>.
</div></div></section></div> ]]></description>
  <category>til</category>
  <category>claude-code</category>
  <guid>https://dylancastillo.co/til/fix-claude-code-shift-enter-alacritty.html</guid>
  <pubDate>Sat, 18 Oct 2025 00:00:00 GMT</pubDate>
</item>
<item>
  <title>LangSmith 101</title>
  <dc:creator>Dylan Castillo</dc:creator>
  <link>https://dylancastillo.co/posts/langsmith-101.html</link>
  <description><![CDATA[ 




<p>In my first AI projects, I didn’t have access to proper observability tools and didn’t know how to evaluate the performance of LLM pipelines. I struggled to figure out what to improve and even when I knew what to improve, it was hard to do so, without breaking other things. Many of those projects failed miserably.</p>
<p>Those failed projects made me start looking for better ways and tools to build AI applications. Over time, tools such as <a href="https://smith.langchain.com/">LangSmith</a>, <a href="https://langfuse.com">Langfuse</a>, or <a href="https://logfire.pydantic.dev">Logfire</a> became key components of my AI toolkit. I can no longer imagine building an AI application without them.</p>
<p>In this tutorial, I’ll walk you through the basics of using LangSmith to monitor and evaluate your LLM applications.</p>
<section id="prerequisites" class="level2">
<h2 class="anchored" data-anchor-id="prerequisites">Prerequisites</h2>
<p>To complete this tutorial, you need to:</p>
<ol type="1">
<li>Sign up and generate <a href="https://platform.openai.com/docs/overview">OpenAI</a> and <a href="https://smith.langchain.com/">LangSmith</a> API keys.</li>
<li>Create a <code>.env</code> file in the root directory of your project and add the following lines:</li>
</ol>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode numberSource bash number-lines code-with-copy"><code class="sourceCode bash"><span id="cb1-1"><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">OPENAI_API_KEY</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>your_openai_api_key</span>
<span id="cb1-2"><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">LANGSMITH_TRACING</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>true</span>
<span id="cb1-3"><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">LANGSMITH_PROJECT</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>your_langchain_project_name</span>
<span id="cb1-4"><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">LANGSMITH_API_KEY</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>your_langsmith_api_key</span></code></pre></div></div>
<ol start="3" type="1">
<li>Create a virtual environment in Python and install the following packages:</li>
</ol>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode numberSource bash number-lines code-with-copy"><code class="sourceCode bash"><span id="cb2-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">uv</span> venv</span>
<span id="cb2-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">uv</span> add langchain langchain-openai langsmith openai jupyter python-dotenv </span></code></pre></div></div>
<p>I’m assuming you’re familiar with the basics of LLMs. If you need a refresher, you can check out some of <a href="https://dylancastillo.co/posts/function-calling-structured-outputs.html">my</a> <a href="https://dylancastillo.co/posts/key-parameters-llms.html">older</a> <a href="https://dylancastillo.co/posts/prompt-engineering-101.html">posts</a>. Also, if you don’t want to copy and paste the code, you can download this post’s <a href="https://github.com/dylanjcastillo/blog/tree/main/posts/synthetic-data-rag.ipynb">notebook</a> and follow along.</p>
<p>Let’s go!</p>
</section>
<section id="setup" class="level2">
<h2 class="anchored" data-anchor-id="setup">Setup</h2>
<p>As usual, you should start by importing the necessary libraries:</p>
<div id="cell-3" class="cell" data-execution_count="1">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb3-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> datasets <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> load_dataset</span>
<span id="cb3-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> dotenv <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> load_dotenv</span>
<span id="cb3-3"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> langchain_core.messages <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> HumanMessage, SystemMessage</span>
<span id="cb3-4"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> langchain_openai <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> ChatOpenAI</span>
<span id="cb3-5"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> langsmith <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> Client, trace, traceable</span>
<span id="cb3-6"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> langsmith.run_trees <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> RunTree</span>
<span id="cb3-7"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> langsmith.wrappers <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> wrap_openai</span>
<span id="cb3-8"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> openai <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> OpenAI</span>
<span id="cb3-9"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> pydantic <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> BaseModel, Field</span>
<span id="cb3-10"></span>
<span id="cb3-11">load_dotenv()</span></code></pre></div></div>
</div>
<p>This will import all the libraries required for the next sections:</p>
<ol type="1">
<li><code>datasets</code> for loading the sample dataset we’ll use to run evaluations.</li>
<li><code>langchain</code> libraries and <code>openai</code> for working with LLMs</li>
<li><code>langsmith</code> for tracing and evaluating the pipeline</li>
<li><code>dotenv</code> and <code>pydantic</code> for environment variable management and data validation</li>
</ol>
<p>Next, you will create your first trace on LangSmith.</p>
</section>
<section id="tracing-and-monitoring" class="level2 page-columns page-full">
<h2 class="anchored" data-anchor-id="tracing-and-monitoring">Tracing and monitoring</h2>
<p>A LangSmith <strong>trace</strong> captures the full execution path of a single operation. It consists of a sequence of steps, which are called <strong>runs</strong>. Each trace contains the top-level inputs and outputs, as well as metadata such as runtime version and operating system details.</p>
<p>There are four ways to create traces in LangSmith:</p>
<ol type="1">
<li>Using <code>@traceable</code></li>
<li>Using a wrapped client</li>
<li>Using a <code>trace</code> context manager</li>
<li>Manually creating traces with <code>RunTree</code></li>
</ol>
<section id="using-traceable" class="level3">
<h3 class="anchored" data-anchor-id="using-traceable">Using <code>@traceable</code></h3>
<p>The simplest way is to encapsulate your pipeline in a function and use the <code>traceable</code> decorator:</p>
<div id="cell-7" class="cell" data-execution_count="2">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb4-1">client <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> OpenAI()</span>
<span id="cb4-2"></span>
<span id="cb4-3"></span>
<span id="cb4-4"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@traceable</span></span>
<span id="cb4-5"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> format_messages(question: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>]:</span>
<span id="cb4-6">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> [</span>
<span id="cb4-7">        {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"role"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"system"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"content"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"You're a helpful assistant"</span>},</span>
<span id="cb4-8">        {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"role"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"user"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"content"</span>: question},</span>
<span id="cb4-9">    ]</span>
<span id="cb4-10"></span>
<span id="cb4-11"></span>
<span id="cb4-12"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@traceable</span>(run_type<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"llm"</span>)</span>
<span id="cb4-13"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> call_llm(messages: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>]):</span>
<span id="cb4-14">    response <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> client.chat.completions.create(model<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"gpt-4.1-mini"</span>, messages<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>messages)</span>
<span id="cb4-15">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> response</span>
<span id="cb4-16"></span>
<span id="cb4-17"></span>
<span id="cb4-18"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@traceable</span></span>
<span id="cb4-19"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> run_pipeline(question: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>):</span>
<span id="cb4-20">    messages <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> format_messages(question)</span>
<span id="cb4-21">    response <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> call_llm(messages)</span>
<span id="cb4-22">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> response.choices[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].message.content</span>
<span id="cb4-23"></span>
<span id="cb4-24"></span>
<span id="cb4-25">run_pipeline(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Who are you?"</span>)</span></code></pre></div></div>
</div>
<p>This will automatically log the input and ouput of the functions decorated with <code>traceable</code>. It will also handle the nesting for you, so that <code>format_messages</code> and <code>call_llm</code> are steps within the <code>run_pipeline</code> function.</p>
<p>In <code>traceable</code> you can customize xyz.</p>
</section>
<section id="using-a-trace-context-manager" class="level3">
<h3 class="anchored" data-anchor-id="using-a-trace-context-manager">Using a <code>trace</code> context manager</h3>
<p>In addition, to the <code>traceable</code> decorator, you can also use the <code>trace</code> context manager to create traces. You can easily combine both as shown below:</p>
<div id="cell-11" class="cell" data-execution_count="3">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb5-1">client <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> OpenAI()</span>
<span id="cb5-2"></span>
<span id="cb5-3"></span>
<span id="cb5-4"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@traceable</span></span>
<span id="cb5-5"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> format_messages(question: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>]:</span>
<span id="cb5-6">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> [</span>
<span id="cb5-7">        {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"role"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"system"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"content"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"You're a helpful assistant"</span>},</span>
<span id="cb5-8">        {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"role"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"user"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"content"</span>: question},</span>
<span id="cb5-9">    ]</span>
<span id="cb5-10"></span>
<span id="cb5-11"></span>
<span id="cb5-12"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@traceable</span>(run_type<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"llm"</span>)</span>
<span id="cb5-13"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> call_llm(messages: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>]):</span>
<span id="cb5-14">    response <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> client.chat.completions.create(model<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"gpt-4.1-mini"</span>, messages<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>messages)</span>
<span id="cb5-15">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> response</span>
<span id="cb5-16"></span>
<span id="cb5-17"></span>
<span id="cb5-18">app_inputs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"question"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Who are you?"</span>}</span>
<span id="cb5-19"></span>
<span id="cb5-20"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">with</span> trace(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"run_pipeline"</span>, inputs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>app_inputs) <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> rt:</span>
<span id="cb5-21">    messages <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> format_messages(app_inputs[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"question"</span>])</span>
<span id="cb5-22">    response <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> call_llm(messages)</span>
<span id="cb5-23">    output <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> response.choices[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].message.content</span>
<span id="cb5-24">    rt.end(outputs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>{<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"output"</span>: output})</span></code></pre></div></div>
</div>
<p>This will generate a trace called “LLM Pipeline” with the input and output of the entire pipeline. Within this trace, you will find the individual traces for each function call.</p>
</section>
<section id="using-a-wrapped-client" class="level3">
<h3 class="anchored" data-anchor-id="using-a-wrapped-client">Using a wrapped client</h3>
<p>For <code>OpenAI</code> and <code>Anthropic</code> models, LangSmith offers a wrapped client that automatically instruments calls to the API with tracing. Any call to the LLM will automatically handled by LangSmith. This plays well with using <code>traceable</code> for the rest of the part in your pipeline. For example:</p>
<div id="cell-14" class="cell" data-execution_count="4">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb6-1">client <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> wrap_openai(OpenAI())  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Added client wrapper</span></span>
<span id="cb6-2"></span>
<span id="cb6-3"></span>
<span id="cb6-4"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@traceable</span></span>
<span id="cb6-5"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> format_messages(question: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>]:</span>
<span id="cb6-6">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> [</span>
<span id="cb6-7">        {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"role"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"system"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"content"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"You're a helpful assistant"</span>},</span>
<span id="cb6-8">        {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"role"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"user"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"content"</span>: question},</span>
<span id="cb6-9">    ]</span>
<span id="cb6-10"></span>
<span id="cb6-11"></span>
<span id="cb6-12"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Removed @traceable</span></span>
<span id="cb6-13"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> call_llm(messages: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>]):</span>
<span id="cb6-14">    response <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> client.chat.completions.create(model<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"gpt-4.1-mini"</span>, messages<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>messages)</span>
<span id="cb6-15">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> response</span>
<span id="cb6-16"></span>
<span id="cb6-17"></span>
<span id="cb6-18"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@traceable</span></span>
<span id="cb6-19"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> run_pipeline(question: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>):</span>
<span id="cb6-20">    messages <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> format_messages(question)</span>
<span id="cb6-21">    response <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> call_llm(messages)</span>
<span id="cb6-22">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> response.choices[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].message.content</span>
<span id="cb6-23"></span>
<span id="cb6-24"></span>
<span id="cb6-25">run_pipeline(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Who are you?"</span>)</span></code></pre></div></div>
</div>
<p>This will automatically log the LLM calls made within <code>run_pipeline</code>, so you no longer need to add the <code>traceable</code> decorator to each call.</p>
</section>
<section id="manually-creating-traces-with-runtree" class="level3 page-columns page-full">
<h3 class="anchored" data-anchor-id="manually-creating-traces-with-runtree">Manually creating traces with <code>RunTree</code></h3>
<p>If you want to have more control over the tracing, you can use <a href="https://docs.smith.langchain.com/reference/python/run_trees/langsmith.run_trees.RunTree"><code>RunTree</code></a>. It provides the most flexibility but requires more setup.</p>
<p>Here’s the <code>RunTree</code> version of the previous example:</p>
<div id="cell-17" class="cell" data-execution_count="5">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb7-1">client <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> OpenAI()</span>
<span id="cb7-2"></span>
<span id="cb7-3"></span>
<span id="cb7-4"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> format_messages(question: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>, parent_run: RunTree):</span>
<span id="cb7-5">    format_message_step <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> parent_run.create_child(</span>
<span id="cb7-6">        name<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"format_messages"</span>, run_type<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"tool"</span>, inputs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>{<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"question"</span>: question}</span>
<span id="cb7-7">    )</span>
<span id="cb7-8">    format_message_step.post()</span>
<span id="cb7-9">    messages <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb7-10">        {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"role"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"system"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"content"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"You are a helpful assistant."</span>},</span>
<span id="cb7-11">        {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"role"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"user"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"content"</span>: question},</span>
<span id="cb7-12">    ]</span>
<span id="cb7-13">    format_message_step.end(outputs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>{<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"messages"</span>: messages})</span>
<span id="cb7-14">    format_message_step.patch()</span>
<span id="cb7-15">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> messages</span>
<span id="cb7-16"></span>
<span id="cb7-17"></span>
<span id="cb7-18"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> call_llm(messages: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>], parent_run: RunTree):</span>
<span id="cb7-19">    call_llm_step <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> parent_run.create_child(</span>
<span id="cb7-20">        name<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"call_llm"</span>,</span>
<span id="cb7-21">        run_type<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"llm"</span>,</span>
<span id="cb7-22">        inputs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>{<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"messages"</span>: messages},</span>
<span id="cb7-23">    )</span>
<span id="cb7-24">    call_llm_step.post()</span>
<span id="cb7-25">    response <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> client.chat.completions.create(model<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"gpt-4.1-mini"</span>, messages<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>messages)</span>
<span id="cb7-26">    call_llm_step.end(outputs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>response)</span>
<span id="cb7-27">    call_llm_step.patch()</span>
<span id="cb7-28">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> response</span>
<span id="cb7-29"></span>
<span id="cb7-30"></span>
<span id="cb7-31"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> run_pipeline(question: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>):</span>
<span id="cb7-32">    parent_run <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> RunTree(name<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"run_pipeline"</span>, inputs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>{<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"question"</span>: question})</span>
<span id="cb7-33">    parent_run.post()</span>
<span id="cb7-34"></span>
<span id="cb7-35">    messages <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> format_messages(question, parent_run)</span>
<span id="cb7-36">    response <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> call_llm(messages, parent_run)</span>
<span id="cb7-37"></span>
<span id="cb7-38">    parent_run.end(outputs<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>{<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"answer"</span>: response.choices[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].message.content})</span>
<span id="cb7-39">    parent_run.patch()</span>
<span id="cb7-40"></span>
<span id="cb7-41"></span>
<span id="cb7-42">run_pipeline(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Who are you?"</span>)</span></code></pre></div></div>
</div>
<p>This will result in a similar trace, but in this case you have more control over when/what to send in each step.</p>
<p>For all of these methods, you should’ve obtained a trace that looks like this:</p>
<p><a href="./images/langsmith-101/traces.png" class="lightbox" data-gallery="quarto-lightbox-gallery-1"><img src="https://dylancastillo.co/posts/images/langsmith-101/traces.png" class="img-fluid"></a></p>
<p>To the left of the image, you should see the trace for the <code>run_pipeline</code> function, which includes all the steps taken during the execution of the function, including the formatting of messages and the call to the LLM. To the right, you will see the input and output for the full trace.</p>
<p>Then, you can click on each individual step to view more details about that step, including the inputs, outputs, and any errors that may have occurred.</p>
<p>Here’s <code>format_messages</code>:</p>
<p><a href="./images/langsmith-101/format_messages.png" class="lightbox" data-gallery="quarto-lightbox-gallery-2"><img src="https://dylancastillo.co/posts/images/langsmith-101/format_messages.png" class="img-fluid"></a></p>
<p>And here’s <code>call_llm</code>:</p>
<div class="quarto-figure quarto-figure-center page-columns page-full">
<figure class="figure page-columns page-full">
<p><a href="./images/langsmith-101/call_llm.png" class="lightbox" data-gallery="quarto-lightbox-gallery-3" title="image.png"><img src="https://dylancastillo.co/posts/images/langsmith-101/call_llm.png" class="img-fluid figure-img" alt="image.png"></a></p>
<figcaption class="margin-caption">image.png</figcaption>
</figure>
</div>
<p>I recommend you explore the traces on your own. Just looking at the images in this post won’t be enough.</p>
</section>
</section>
<section id="evaluation" class="level2 page-columns page-full">
<h2 class="anchored" data-anchor-id="evaluation">Evaluation</h2>
<p>LangSmith lets you evaluate your LLM pipelines by providing you with a way to upload evaluation datasets, define evaluation metrics, and view the results of your experiments.</p>
<p>Let’s explore this by running a set of evals on a sample dataset. You’ll use the <a href="https://huggingface.co/datasets/AI-MO/aimo-validation-aime"><code>AIMO Validation AIME</code></a> dataset that contains questions, answers and detailed solutions from the 2022, 2023, and 2024 AIME competitions.</p>
<p>You should start by creating a dataset on LangSmith:</p>
<div id="cell-22" class="cell" data-execution_count="6">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb8-1">ds <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> load_dataset(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"AI-MO/aimo-validation-aime"</span>)</span>
<span id="cb8-2">examples <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb8-3">    {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"inputs"</span>: {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"question"</span>: d[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"problem"</span>]}, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"outputs"</span>: {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"answer"</span>: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>(d[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"answer"</span>])}}</span>
<span id="cb8-4">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> d <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> ds[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"train"</span>]</span>
<span id="cb8-5">][:<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">15</span>]</span>
<span id="cb8-6"></span>
<span id="cb8-7">client <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Client()</span>
<span id="cb8-8"></span>
<span id="cb8-9">dataset_name <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"AIME Example Dataset (sample)"</span></span>
<span id="cb8-10"></span>
<span id="cb8-11"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">try</span>:</span>
<span id="cb8-12">    dataset <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> client.create_dataset(dataset_name)</span>
<span id="cb8-13">    client.create_examples(dataset_id<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>dataset.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">id</span>, examples<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>examples)</span>
<span id="cb8-14"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">except</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">Exception</span> <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> e:</span>
<span id="cb8-15">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Dataset </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>dataset_name<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> already exists. Error: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>e<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb8-16">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">pass</span></span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>Dataset AIME Example Dataset (sample) already exists. Error: Conflict for /datasets. HTTPError('409 Client Error: Conflict for url: https://api.smith.langchain.com/datasets', '{"detail":"Dataset with this name already exists."}')</code></pre>
</div>
</div>
<p>This will create a dataset with the first 15 examples from the AIMO Validation AIME dataset. I only included a a sample of the dataset to keep costs down. You can always add more examples later if needed.</p>
<p>The dataset will be available under <code>Datasets &amp; Experiments</code>:</p>
<div class="quarto-figure quarto-figure-center page-columns page-full">
<figure class="figure page-columns page-full">
<p><a href="./images/langsmith-101/dataset.png" class="lightbox" data-gallery="quarto-lightbox-gallery-4" title="image.png"><img src="https://dylancastillo.co/posts/images/langsmith-101/dataset.png" class="img-fluid figure-img" alt="image.png"></a></p>
<figcaption class="margin-caption">image.png</figcaption>
</figure>
</div>
<p>Then, you’ll define a pipeline that takes the user question, and provides a response using a structured output:</p>
<div id="cell-24" class="cell" data-execution_count="7">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb10" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb10-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> Response(BaseModel):</span>
<span id="cb10-2">    explanation: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Field(description<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"The explanation of the answer"</span>)</span>
<span id="cb10-3">    answer: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Field(</span>
<span id="cb10-4">        description<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"The answer to the question. It should be an integer."</span></span>
<span id="cb10-5">    )</span>
<span id="cb10-6"></span>
<span id="cb10-7"></span>
<span id="cb10-8">model <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ChatOpenAI(model<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"gpt-4.1-mini"</span>, temperature<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb10-9">model_with_structure <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> model.with_structured_output(Response, method<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"function_calling"</span>)</span>
<span id="cb10-10"></span>
<span id="cb10-11"></span>
<span id="cb10-12"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> get_response(question: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> Response:</span>
<span id="cb10-13">    max_retries <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span></span>
<span id="cb10-14">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> _ <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(max_retries):</span>
<span id="cb10-15">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">try</span>:</span>
<span id="cb10-16">            messages <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb10-17">                SystemMessage(</span>
<span id="cb10-18">                    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"You're a math expert. You will always respond in a JSON format with the following fields: explanation and answer."</span></span>
<span id="cb10-19">                ),</span>
<span id="cb10-20">                HumanMessage(question),</span>
<span id="cb10-21">            ]</span>
<span id="cb10-22">            response <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> model_with_structure.invoke(messages)</span>
<span id="cb10-23">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> response</span>
<span id="cb10-24">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">except</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">Exception</span> <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> e:</span>
<span id="cb10-25">            <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Error: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>e<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb10-26">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">continue</span></span>
<span id="cb10-27">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">raise</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">ValueError</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Failed to get a valid response"</span>)</span></code></pre></div></div>
</div>
<p>I included a simple retry mechanism, as I often found that the model sometime failed to generate a valid response.</p>
<p>Next, you should define the evaluation metrics you’ll use to measure the performance of your pipeline. You could define a simple accuracy metric that checks if the answer is the same as the expected answer:</p>
<div id="cell-26" class="cell" data-execution_count="8">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb11" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb11-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> accuracy(inputs: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>, outputs: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>, reference_outputs: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">bool</span>:</span>
<span id="cb11-2">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> outputs[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"answer"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> reference_outputs[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"answer"</span>]</span></code></pre></div></div>
</div>
<p>To define an evaluation metric in LangSmith, you must create a function that takes the inputs, outputs, and reference outputs as arguments and returns a boolean or a numeric value.</p>
<p>For accuracy, the function checks if the answer provided by the model matches the expected answer from the dataset, and returns a boolean value indicating whether the evaluation passed or failed.</p>
<p>You can also define more complex metrics, such as an LLM judge to evaluate the clarity of the solution:</p>
<div id="cell-28" class="cell" data-execution_count="9">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb12" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb12-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> ClarityResponse(BaseModel):</span>
<span id="cb12-2">    explanation: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Field(description<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"The explanation of the answer"</span>)</span>
<span id="cb12-3">    clarity: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Field(description<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"The clarity of the explanation"</span>, ge<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, le<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>)</span>
<span id="cb12-4"></span>
<span id="cb12-5"></span>
<span id="cb12-6"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> clarity(inputs: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>, outputs: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>, reference_outputs: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>:</span>
<span id="cb12-7">    messages <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb12-8">        SystemMessage(</span>
<span id="cb12-9">            content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"You are a helpful assistant that evaluates the clarity of the explanation of the answer. You will always return a number between 1 and 5, where 1 is the lowest clarity and 5 is the highest clarity."</span></span>
<span id="cb12-10">        ),</span>
<span id="cb12-11">        HumanMessage(content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Explanation: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>outputs[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'explanation'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>),</span>
<span id="cb12-12">    ]</span>
<span id="cb12-13">    model_with_clarity_structure <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> model.with_structured_output(ClarityResponse)</span>
<span id="cb12-14">    response <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> model_with_clarity_structure.invoke(messages)</span>
<span id="cb12-15">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> response.clarity</span></code></pre></div></div>
</div>
<p>This <code>clarity</code> metric evaluates the clarity of the explanation provided by the model. It uses a scale from 1 to 5, where 1 indicates low clarity and 5 indicates high clarity.</p>
<p>Finally, you can run the evaluation using <code>client.evaluate()</code>:</p>
<div id="cell-30" class="cell" data-execution_count="10">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb13" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb13-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> ls_wrapper(inputs: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>:</span>
<span id="cb13-2">    response <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> get_response(inputs[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"question"</span>])</span>
<span id="cb13-3">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> response.model_dump()</span>
<span id="cb13-4"></span>
<span id="cb13-5"></span>
<span id="cb13-6">experiment_results <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> client.aevaluate(</span>
<span id="cb13-7">    ls_wrapper, data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>dataset_name, evaluators<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[accuracy, clarity], max_concurrency<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">15</span></span>
<span id="cb13-8">)</span></code></pre></div></div>
</div>
<p>LangSmith requires you to define a function that wraps your pipeline function. It should take an input dictionary that contains the necessary parameters for your pipeline and return a dictionary with the results. You can also specify a <code>evaluators</code> parameter that includes the evaluation metrics you want to use.</p>
<p>After you’ve run the evaluation, you’ll be able to inspect the results of the experiment:</p>
<div class="quarto-figure quarto-figure-center page-columns page-full">
<figure class="figure page-columns page-full">
<p><a href="./images/langsmith-101/results.png" class="lightbox" data-gallery="quarto-lightbox-gallery-5" title="image.png"><img src="https://dylancastillo.co/posts/images/langsmith-101/results.png" class="img-fluid figure-img" alt="image.png"></a></p>
<figcaption class="margin-caption">image.png</figcaption>
</figure>
</div>
<p>You can also investigate single runs:</p>
<div class="quarto-figure quarto-figure-center page-columns page-full">
<figure class="figure page-columns page-full">
<p><a href="./images/langsmith-101/single_run.png" class="lightbox" data-gallery="quarto-lightbox-gallery-6" title="image.png"><img src="https://dylancastillo.co/posts/images/langsmith-101/single_run.png" class="img-fluid figure-img" alt="image.png"></a></p>
<figcaption class="margin-caption">image.png</figcaption>
</figure>
</div>
<p>Or see how results look over time:</p>
<div class="quarto-figure quarto-figure-center page-columns page-full">
<figure class="figure page-columns page-full">
<p><a href="./images/langsmith-101/results_over_time.png" class="lightbox" data-gallery="quarto-lightbox-gallery-7" title="image.png"><img src="https://dylancastillo.co/posts/images/langsmith-101/results_over_time.png" class="img-fluid figure-img" alt="image.png"></a></p>
<figcaption class="margin-caption">image.png</figcaption>
</figure>
</div>
<p>Once again, I suggest you go explore the results in the LangSmith UI.</p>
</section>
<section id="conclusion" class="level2">
<h2 class="anchored" data-anchor-id="conclusion">Conclusion</h2>
<p>That’s all! We’ve covered the basics of using LangSmith to trace and evaluate your LLM applications.</p>
<p>By now, you should have a good understanding of how to create traces, define evaluation metrics, and run experiments.</p>
<p>If you have any questions or feedback, let me know in the comments below.</p>


</section>

<div id="quarto-appendix" class="default"><section class="quarto-appendix-contents" id="quarto-citation"><h2 class="anchored quarto-appendix-heading">Citation</h2><div><div class="quarto-appendix-secondary-label">BibTeX citation:</div><pre class="sourceCode code-with-copy quarto-appendix-bibtex"><code class="sourceCode bibtex">@online{castillo2025,
  author = {Castillo, Dylan},
  title = {LangSmith 101},
  date = {2025-08-10},
  url = {https://dylancastillo.co/posts/langsmith-101.html},
  langid = {en}
}
</code></pre><div class="quarto-appendix-secondary-label">For attribution, please cite this work as:</div><div id="ref-castillo2025" class="csl-entry quarto-appendix-citeas">
Castillo, Dylan. 2025. <span>“LangSmith 101.”</span> August 10. <a href="https://dylancastillo.co/posts/langsmith-101.html">https://dylancastillo.co/posts/langsmith-101.html</a>.
</div></div></section></div> ]]></description>
  <category>llm</category>
  <category>python</category>
  <category>openai</category>
  <category>langsmith</category>
  <guid>https://dylancastillo.co/posts/langsmith-101.html</guid>
  <pubDate>Sun, 10 Aug 2025 00:00:00 GMT</pubDate>
  <media:content url="https://dylancastillo.co/posts/images/cards/langsmith-101.png" medium="image" type="image/png" height="76" width="144"/>
</item>
<item>
  <title>Using synthetic data to bootstrap your RAG system evals</title>
  <dc:creator>Dylan Castillo</dc:creator>
  <link>https://dylancastillo.co/posts/synthetic-data-rag.html</link>
  <description><![CDATA[ 




<p>One of the coolest ideas I’ve come across when building Retrieval-Augmented Generation (RAG) systems is that you can actually start without any real data at all. You can bootstrap your system (and its evaluation process) using synthetic data.</p>
<p>In the projects I’ve worked on, we usually had enough data to train and evaluate our pipelines, so generating synthetic data wasn’t a concern. But I’ve been curious about this idea for a while. So I decided to dig into it.</p>
<p>I used Hamel Husain and Shreya Shankar’s <a href="https://maven.com/parlance-labs/evals">AI Evals for Engineers &amp; PMs</a> course materials to guide my exploration. If you’re interested in building and evaluating LLM systems, I highly recommend checking out their course.</p>
<p>In this article, I’ll walk you through the process of bootstrapping your RAG system evals using synthetic data.</p>
<p>Let’s get started!</p>
<section id="prerequisites" class="level2">
<h2 class="anchored" data-anchor-id="prerequisites">Prerequisites</h2>
<p>If you plan to follow along, you’ll need to:</p>
<ol type="1">
<li>Sign up and generate <a href="https://platform.openai.com/docs/overview">OpenAI</a> and <a href="https://smith.langchain.com/">LangSmith</a> API keys.</li>
<li>Create a <code>.env</code> file in the root directory of your project and add the following lines:</li>
</ol>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode numberSource bash number-lines code-with-copy"><code class="sourceCode bash"><span id="cb1-1"><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">OPENAI_API_KEY</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>your_openai_api_key</span>
<span id="cb1-2"><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">LANGSMITH_TRACING</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>true</span>
<span id="cb1-3"><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">LANGSMITH_PROJECT</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>your_langchain_project_name</span>
<span id="cb1-4"><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">LANGSMITH_API_KEY</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>your_langsmith_api_key</span></code></pre></div></div>
<ol start="3" type="1">
<li>Create a virtual environment in Python and install the following packages:</li>
</ol>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode numberSource bash number-lines code-with-copy"><code class="sourceCode bash"><span id="cb2-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">uv</span> venv</span>
<span id="cb2-2"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">uv</span> add langchain langchain-openai langchain-community langsmith jupyter chromadb python-dotenv nest_asyncio sentence-transformers</span></code></pre></div></div>
<ol start="4" type="1">
<li>Download the People Group’s section from GitLab’s <a href="https://gitlab.com/gitlab-com/content-sites/handbook/-/tree/main/content/handbook/people-group">handbook</a>.</li>
</ol>
<p>I’m also assuming you’re familiar with the basics of RAG systems and how to use vector databases. If you need a refresher, you can check out my <a href="https://dylancastillo.co/posts/what-is-rag.html">RAG tutorial</a>.</p>
<p>Then, you’ll be able to run the code from this article. If you don’t want to copy and paste the code, you can download this <a href="https://github.com/dylanjcastillo/blog/tree/main/posts/synthetic-data-rag.ipynb">notebook</a>.</p>
</section>
<section id="how-to-generate-synthetic-data-for-rag-evals" class="level2">
<h2 class="anchored" data-anchor-id="how-to-generate-synthetic-data-for-rag-evals">How to generate synthetic data for RAG evals</h2>
<p>The process is simple. Here’s how it works:</p>
<ol type="1">
<li>Split your source document into chunks and store them in a vector database.</li>
<li>Sample a few chunks from the vector database.</li>
<li>For each sampled chunk, extract a <strong>fact</strong> from it and generate a <strong>question</strong> that is unambiguously answered by the fact.</li>
<li>Define evaluation metrics for your RAG system.</li>
<li>Optionally, filter the generated questions to remove the ones that don’t seem realistic.</li>
<li>Measure the performance of your RAG system.</li>
</ol>
<p>In the next sections, I’ll help you implement this process step by step, providing code snippets you can run in a Jupyter notebook.</p>
</section>
<section id="setup" class="level2">
<h2 class="anchored" data-anchor-id="setup">Setup</h2>
<p>You’ll use <code>asyncio</code> in some of the code snippets, so you must enable <code>nest_asyncio</code> to run the code:</p>
<div id="1e2a708d" class="cell" data-execution_count="2">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb3-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> nest_asyncio</span>
<span id="cb3-2"></span>
<span id="cb3-3">nest_asyncio.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">apply</span>()</span></code></pre></div></div>
</div>
<p>Then, you can proceed as usual, importing the required packages:</p>
<div id="f58b9d35" class="cell" data-execution_count="3">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb4-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> asyncio</span>
<span id="cb4-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> os</span>
<span id="cb4-3"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> random</span>
<span id="cb4-4"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> textwrap <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> dedent</span>
<span id="cb4-5"></span>
<span id="cb4-6"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> chromadb</span>
<span id="cb4-7"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> pandas <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> pd</span>
<span id="cb4-8"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> chromadb.utils.embedding_functions <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> OpenAIEmbeddingFunction</span>
<span id="cb4-9"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> dotenv <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> load_dotenv</span>
<span id="cb4-10"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> langchain_community.document_loaders <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> DirectoryLoader, TextLoader</span>
<span id="cb4-11"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> langchain_core.prompts <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> ChatPromptTemplate</span>
<span id="cb4-12"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> langchain_openai <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> ChatOpenAI</span>
<span id="cb4-13"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> langchain_text_splitters <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> MarkdownTextSplitter</span>
<span id="cb4-14"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> langsmith <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> Client, traceable</span>
<span id="cb4-15"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> pydantic <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> BaseModel</span>
<span id="cb4-16"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> sentence_transformers <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> CrossEncoder</span>
<span id="cb4-17"></span>
<span id="cb4-18">load_dotenv()</span></code></pre></div></div>
</div>
<p>These are the most important libraries you’ll use in this article:</p>
<ul>
<li><strong>chromadb</strong>: Vector database for storing and retrieving document embeddings</li>
<li><strong>langchain</strong>: Framework for building LLM applications</li>
<li><strong>langchain-openai</strong>: Wrapper for OpenAI’s API, providing access to LLMs and embeddings</li>
<li><strong>pydantic</strong>: Provides models for generating structured data and validating types</li>
<li><strong>sentence-transformers</strong>: In the last section of the article, you’ll use this library to rerank the retrieved documents.</li>
</ul>
<p>The rest of the libraries will handle typical Python tasks, such as reading files, managing environment variables, etc.</p>
<p>For this tutorial, you’ll be building a RAG system that feeds an internal chatbot that helps employees of a company answer questions about company policies.</p>
<p>I chose this topic because there’s a pretty good source of data we can use for this: <a href="https://handbook.gitlab.com/">The GitLab Handbook</a>. We’ll just use the People Group section of the handbook, to keep costs manageable.</p>
<p>Your next step is to load the data from the handbook:</p>
<div id="66a8c9c6" class="cell" data-execution_count="4">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb5-1">loader <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> DirectoryLoader(</span>
<span id="cb5-2">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"../data/synthetic-data-rag/people-group/"</span>, glob<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"**/*.md"</span>, loader_cls<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>TextLoader</span>
<span id="cb5-3">)</span>
<span id="cb5-4">docs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> loader.load()</span>
<span id="cb5-5"></span>
<span id="cb5-6"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(docs)</span></code></pre></div></div>
<div class="cell-output cell-output-display" data-execution_count="4">
<pre><code>99</code></pre>
</div>
</div>
<p>If you set everything up correctly, the cell should output the number of documents in the <code>docs</code> variable. Depending on when you read this article, the number of documents may change, as the handbook is updated regularly. The date I downloaded the data, there were 99 documents in the People Group section.</p>
<p>Then, you must add the data to the vector database.</p>
</section>
<section id="index-data" class="level2">
<h2 class="anchored" data-anchor-id="index-data">Index data</h2>
<p>A <strong>vector database</strong> is a database designed to efficiently store and query data as vector embeddings (numerical representations). Provided with a user query, it’s the engine you use to find the most similar data in your database.</p>
<p>For this tutorial, you’ll use <a href="https://www.trychroma.com/">ChromaDB</a>. Let’s set it up:</p>
<div id="c7df6e53" class="cell" data-execution_count="5">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb7-1">openai_ef <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> OpenAIEmbeddingFunction(api_key<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>os.getenv(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"OPENAI_API_KEY"</span>))</span>
<span id="cb7-2">client <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> chromadb.PersistentClient(</span>
<span id="cb7-3">    path<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"../data/synthetic-data-rag/chroma"</span>,</span>
<span id="cb7-4">)</span>
<span id="cb7-5">collection <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> client.get_or_create_collection(</span>
<span id="cb7-6">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"gitlab-handbook"</span>, embedding_function<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>openai_ef</span>
<span id="cb7-7">)</span></code></pre></div></div>
</div>
<p>This code snippet:</p>
<ol type="1">
<li>Defines an embedding function that uses OpenAI’s API to generate embeddings for the documents.</li>
<li>Creates a ChromaDB client to interact with the vector database.</li>
<li>Creates a collection in the vector database to store the document embeddings and sets the embedding function to use the OpenAI embedding model.</li>
</ol>
<p>Next, you should generate embeddings for the documents and store them in the vector database. But there’s a little catch: some documents are longer than the maximum token limit of the embedding model (8192 tokens). This will break the indexing process. To avoid this, you must split the documents into smaller chunks.</p>
<p>You can do this with the following code snippet:</p>
<div id="10523d7a" class="cell" data-execution_count="6">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb8-1">text_splitter <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> MarkdownTextSplitter.from_tiktoken_encoder(</span>
<span id="cb8-2">    model_name<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"gpt-4o"</span>,</span>
<span id="cb8-3">    chunk_size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">400</span>,</span>
<span id="cb8-4">    chunk_overlap<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>,</span>
<span id="cb8-5">)</span>
<span id="cb8-6">splits <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> text_splitter.split_documents(docs)</span></code></pre></div></div>
</div>
<p>The handbook is written using Markdown, so you can use the <code>MarkdownTextSplitter</code> to split the documents. This will use the headings in the files for the splitting in addition to the number of tokens. This generally results in better chunks, as they will be more likely to contain complete thoughts or sections of the document.</p>
<p>The <code>from_tiktoken_encoder</code> method lets you do the splits based on the number of tokens, not characters which is the default behavior. You’ve set a chunk size of 400 tokens, with no overlap.</p>
<p>After running the splitting code, you can check the number of chunks created by running this:</p>
<div id="ba5cec1e" class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb9-1"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(splits)</span></code></pre></div></div>
<div class="cell-output cell-output-display" data-execution_count="7">
<pre><code>999</code></pre>
</div>
</div>
<p>Now you have another problem: you have too many chunks. If you try to add all of them to the vector database at once—which also generates their embeddings—you’ll likely hit the OpenAI API rate limits.</p>
<p>To solve this, let’s define a utility function that adds the chunks to the vector database in batches:</p>
<div id="c5cc250f" class="cell" data-execution_count="8">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb11" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb11-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> create_batches(ids, documents, metadatas, batch_size<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">100</span>):</span>
<span id="cb11-2">    batches <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb11-3">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(ids), batch_size):</span>
<span id="cb11-4">        batch_ids <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ids[i : i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> batch_size]</span>
<span id="cb11-5">        batch_documents <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> documents[i : i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> batch_size]</span>
<span id="cb11-6">        batch_metadatas <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> metadatas[i : i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> batch_size]</span>
<span id="cb11-7">        batches.append((batch_ids, batch_metadatas, batch_documents))</span>
<span id="cb11-8">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> batches</span></code></pre></div></div>
</div>
<p>Then, you can apply this function to the chunks you created earlier:</p>
<div id="3287eb76" class="cell" data-execution_count="9">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb12" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb12-1">ids <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>(i)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(splits))]</span>
<span id="cb12-2">documents <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [doc.page_content <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> doc <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> splits]</span>
<span id="cb12-3">metadatas <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [doc.metadata <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> doc <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> splits]</span>
<span id="cb12-4"></span>
<span id="cb12-5"></span>
<span id="cb12-6"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> collection.count() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>:</span>
<span id="cb12-7">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Collection already exists, skipping creation."</span>)</span>
<span id="cb12-8"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span>:</span>
<span id="cb12-9">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Adding documents..."</span>)</span>
<span id="cb12-10">    batches <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> create_batches(ids<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>ids, documents<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>documents, metadatas<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>metadatas)</span>
<span id="cb12-11">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i, batch <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">enumerate</span>(batches):</span>
<span id="cb12-12">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Adding batch </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>i<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> of size </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(batch[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>])<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb12-13">        collection.add(ids<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>batch[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>], metadatas<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>batch[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>], documents<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>batch[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>])</span></code></pre></div></div>
</div>
<p>Feel free to adjust the batch size according to your needs. This should take a few seconds to run. Once it’s done, your vector database should be ready to use.</p>
<p>Next, you’ll define a couple of functions to interact with the vector database and a data model to represent the retrieved documents you’ll be working with.</p>
<div id="bb0baae9" class="cell" data-execution_count="11">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb13" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb13-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> RetrievedDoc(BaseModel):</span>
<span id="cb13-2">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">id</span>: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span></span>
<span id="cb13-3">    path: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span></span>
<span id="cb13-4">    page_content: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span></span>
<span id="cb13-5"></span>
<span id="cb13-6"></span>
<span id="cb13-7"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> get_similar_docs(text: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>, top_k: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[RetrievedDoc]:</span>
<span id="cb13-8">    results <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> collection.query(query_texts<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[text], n_results<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>top_k)</span>
<span id="cb13-9">    docs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [results[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"documents"</span>][<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>][i] <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(top_k)]</span>
<span id="cb13-10">    metadatas <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [results[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"metadatas"</span>][<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>][i] <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(top_k)]</span>
<span id="cb13-11">    ids <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [results[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"ids"</span>][<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>][i] <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(top_k)]</span>
<span id="cb13-12">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> [</span>
<span id="cb13-13">        RetrievedDoc(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">id</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>id_, path<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>m[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"source"</span>], page_content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>d)</span>
<span id="cb13-14">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> d, m, id_ <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">zip</span>(docs, metadatas, ids)</span>
<span id="cb13-15">    ]</span>
<span id="cb13-16"></span>
<span id="cb13-17"></span>
<span id="cb13-18"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> get_doc_by_id(doc_id: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> RetrievedDoc:</span>
<span id="cb13-19">    results <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> collection.get(ids<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[doc_id])</span>
<span id="cb13-20">    doc <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> results[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"documents"</span>][<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]</span>
<span id="cb13-21">    metadata <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> results[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"metadatas"</span>][<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]</span>
<span id="cb13-22">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> RetrievedDoc(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">id</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>doc_id, path<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>metadata[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"source"</span>], page_content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>doc)</span></code></pre></div></div>
</div>
<p>In this code snippet, you define two functions:</p>
<ol type="1">
<li><code>get_similar_docs</code> will let you retrieve the most similar documents to a user query.</li>
<li><code>get_document_by_id</code> will let you retrieve a document by its ID.</li>
</ol>
<p><code>RetrievedDoc</code> is a Pydantic model that represents a retrieved document. It includes the document’s ID, file path, and the page content. This model will help you structure the data you retrieve from the vector database.</p>
<p>Next, you can sample documents for the synthetic data generation:</p>
<div id="4db2a9ca" class="cell" data-execution_count="12">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb14" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb14-1">golden_docs_idx <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> random.sample(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(splits)), <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">200</span>)</span>
<span id="cb14-2">golden_docs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [get_doc_by_id(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>(i)) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> golden_docs_idx]</span></code></pre></div></div>
</div>
<p>This will result in 200 documents that you’ll use to generate the synthetic data.</p>
</section>
<section id="generate-qa-pairs" class="level2">
<h2 class="anchored" data-anchor-id="generate-qa-pairs">Generate QA Pairs</h2>
<p>Using the documents you just sampled, you can generate synthetic data. For each document, you’ll extract a fact and generate a question from it.</p>
<p>This is simple but it has a big issue: it may produce questions that are too easy to answer and result in an overly optimistic evaluation of your RAG system.</p>
<p>To create more challenging synthetic queries, Hamel and Shreya recommend adding similar confounding chunks to the generation process, so that it can generate questions in an adversarial manner. The generator will create a question that is uniquely answered by the target chunk but also include themes or keywords that are present in other chunks.</p>
<p>Here’s an example of how this works:</p>
<p><strong>Target chunk:</strong> “George Orwell’s masterpiece, <em>Nineteen Eighty-Four</em>, was published in June 1949 and introduced the concept of ‘Big Brother’ to a global audience.”</p>
<p><strong>Similar chunks:</strong></p>
<ol type="1">
<li>“Aldous Huxley’s <em>Brave New World</em>, another influential work of dystopian fiction, was first released in 1932 and explores themes of social conditioning and control.”</li>
<li>“Ray Bradbury’s <em>Fahrenheit 451</em>, published in 1953, depicts a future society where books are banned and ‘firemen’ burn any that are found.”</li>
</ol>
<p><strong>Synthetic Question:</strong> “In what year was the dystopian novel that introduced the concept of ‘Big Brother’ published?”</p>
<p>The target chunk helps the generator come up with a synthetic question. The similar chunks provide distractors that help the generator include themes or keywords that are also present in other chunks (e.g., dystopian fiction), making the question more challenging.</p>
<p>To do this, you can use the following prompts:</p>
<div id="582a5b45" class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb15" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb15-1">system_prompt_generate <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> dedent(</span>
<span id="cb15-2">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb15-3"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    You are a helpful assistant generating synthetic QA pairs for retrieval evaluation.</span></span>
<span id="cb15-4"></span>
<span id="cb15-5"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    Given a target chunk of text and a set of confounding chunks, you must extract a specific, self-contained fact from the target chunk that is not included in the confounding chunks. Then write a question that is directly and unambiguously answered by that fact. The question should only be answered by the fact extracted from the target chunk (and not by any of the confounding chunks) but it should also use themes or terminology that is present in the confounding chunks.</span></span>
<span id="cb15-6"></span>
<span id="cb15-7"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    Always respond with a JSON object with the following keys (in that exact order):</span></span>
<span id="cb15-8"></span>
<span id="cb15-9"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    1. "fact": "&lt;the fact extracted from the target chunk&gt;",</span></span>
<span id="cb15-10"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    2. "confounding_terms": "&lt;a list of terms or themes from the confounding chunks that are relevant to the question&gt;",</span></span>
<span id="cb15-11"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    3. "question": "&lt;the question that is directly and unambiguously answered by the fact&gt;",</span></span>
<span id="cb15-12"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    </span></span>
<span id="cb15-13"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    You should write the questions as if you're an employee looking for information in the handbook. The question should be as realistic and natural as possible, reflecting the kind of queries an employee might actually make when searching for information in the handbook.</span></span>
<span id="cb15-14"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    """</span></span>
<span id="cb15-15">)</span>
<span id="cb15-16"></span>
<span id="cb15-17">user_prompt_generate <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> dedent(</span>
<span id="cb15-18">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb15-19"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    TARGET CHUNK:</span></span>
<span id="cb15-20"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{target_chunk}</span></span>
<span id="cb15-21"></span>
<span id="cb15-22"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    CONFOUNDING CHUNKS:</span></span>
<span id="cb15-23"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{confounding_chunks}</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;"> </span></span>
<span id="cb15-24"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    """</span></span>
<span id="cb15-25">)</span></code></pre></div></div>
</div>
<p>These prompts will be used to generate the synthetic data. The <code>system_prompt_generate</code> defines the generation process, explaining how to extract facts and generate questions, and <code>user_prompt_generate</code> provides the required context: target and confounding chunks.</p>
<p>Then, you initialize the LLM, set up the response model, and define a function to format the documents for the LLM:</p>
<div id="f5aab841" class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb16" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb16-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> Response(BaseModel):</span>
<span id="cb16-2">    fact: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span></span>
<span id="cb16-3">    confounding_terms: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb16-4">    question: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span></span>
<span id="cb16-5"></span>
<span id="cb16-6"></span>
<span id="cb16-7">llm <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ChatOpenAI(model<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"gpt-4.1-mini"</span>, temperature<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb16-8">llm_with_structured_output <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> llm.with_structured_output(Response)</span>
<span id="cb16-9"></span>
<span id="cb16-10">messages <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ChatPromptTemplate.from_messages(</span>
<span id="cb16-11">    [(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"system"</span>, system_prompt_generate), (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"user"</span>, user_prompt_generate)]</span>
<span id="cb16-12">)</span>
<span id="cb16-13"></span>
<span id="cb16-14"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> format_docs(chunks: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[RetrievedDoc]) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>:</span>
<span id="cb16-15">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>.join(</span>
<span id="cb16-16">        [<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"*** Filepath: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>chunk<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>path<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> ***</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>chunk<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>page_content<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> chunk <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> chunks]</span>
<span id="cb16-17">    )</span></code></pre></div></div>
</div>
<p>Finally, you can define a function to generate the synthetic data. This function will take a target chunk, retrieve the most similar chunks from the vector database, and generate a question from the target chunk using the similar chunks as distractors:</p>
<div id="248ec536" class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb17" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb17-1"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">async</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> generate_qa_pair(chunk):</span>
<span id="cb17-2">    similar_chunks <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> get_similar_docs(chunk.page_content)</span>
<span id="cb17-3">    compiled_messages <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">await</span> messages.ainvoke(</span>
<span id="cb17-4">        {</span>
<span id="cb17-5">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"target_chunk"</span>: format_docs([similar_chunks[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]]),</span>
<span id="cb17-6">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"confounding_chunks"</span>: format_docs(similar_chunks[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>:]),</span>
<span id="cb17-7">        }</span>
<span id="cb17-8">    )</span>
<span id="cb17-9">    output <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">await</span> llm_with_structured_output.ainvoke(compiled_messages)</span>
<span id="cb17-10">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> output</span></code></pre></div></div>
</div>
<p>To speed up question generation, you can run this concurrently using <code>asyncio</code>:</p>
<div id="3f3cf7ae" class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb18" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb18-1">tasks <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [generate_qa_pair(random_split) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> random_split <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> golden_docs]</span>
<span id="cb18-2">qa_pairs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">await</span> asyncio.gather(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>tasks)</span>
<span id="cb18-3"></span>
<span id="cb18-4">df <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pd.DataFrame([qa_pair.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>() <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> qa_pair <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> qa_pairs])</span>
<span id="cb18-5">df.to_excel(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"../data/synthetic-data-rag/files/qa_pairs.xlsx"</span>, index<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>)</span></code></pre></div></div>
</div>
<p>Here are some of the resulting QA pairs:</p>
<ol type="1">
<li>Example 1:
<ul>
<li><strong>Question:</strong> How soon should managers send the results after the 360 feedback cycle closes to prepare for the feedback meeting?</li>
<li><strong>Answer:</strong> Managers should send the results of 360 feedback within 48 hours of the feedback cycle closing so they can prepare and come to the meeting with questions and discussion points.</li>
</ul></li>
<li>Example 2:
<ul>
<li><strong>Question:</strong> At what point in the hiring process must candidates disclose outside employment or side projects for GitLab to assess potential conflicts with their job obligations?</li>
<li><strong>Answer:</strong> Candidates at a certain stage in the recruiting process are asked to disclose outside employment, side projects, or other activities so GitLab can determine if a conflict exists with their ability to fulfill obligations to GitLab.</li>
</ul></li>
</ol>
<p>Even though the questions seem relevant, it’s not entirely clear if they are truly the type of questions real users ask.</p>
<p>To improve that, you can iterate a bit more on the prompt, including few-shot examples of real or adjusted queries. Or, you can take the lazy way out and generate a filter that will help you remove the questions that don’t seem realistic enough. Let’s do that!</p>
</section>
<section id="filter-qa-pairs" class="level2">
<h2 class="anchored" data-anchor-id="filter-qa-pairs">Filter QA pairs</h2>
<p>For that, you should open the Excel file and manually review some of the generated Q&amp;A pairs and evaluate them for relevance. Then, you should provide those questions in a system prompt as examples to the LLM, asking it to assign a score to each question based on your evaluation criteria.</p>
<p>Here’s an example of how to do that:</p>
<div id="3eeec315" class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb19" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb19-1">system_prompt_rate <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> dedent(</span>
<span id="cb19-2">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb19-3"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    You are an AI assistant helping us curate a high-quality dataset of questions for evaluating an company's internal handbook. We have generated synthetic questions and need to filter out those that are unrealistic or not representative of typical user queries.</span></span>
<span id="cb19-4"></span>
<span id="cb19-5"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    Here are examples of realistic and unrealistic user queries we have manually rated:</span></span>
<span id="cb19-6"></span>
<span id="cb19-7"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    ### Realistic Queries (Good Examples)</span></span>
<span id="cb19-8"></span>
<span id="cb19-9"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    * **Query:** "What is the required process for creating a new learning hub for your team in Level Up at GitLab?"</span></span>
<span id="cb19-10"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">        * **Explanation:** Very realistic user query. It's concise, information-seeking, and process-oriented.</span></span>
<span id="cb19-11"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">        * **Rating:** 5</span></span>
<span id="cb19-12"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    * **Query:** "Where is the People Operations internal handbook hosted, and how can someone gain access to it?"</span></span>
<span id="cb19-13"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">        * **Explanation:** Realistic query but might be a bit too detailed for a typical user.</span></span>
<span id="cb19-14"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">        * **Rating:** 4</span></span>
<span id="cb19-15"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    * **Query:** "Who controls access to People Data in the data warehouse at GitLab, and what approvals are required for Analytics Engineers and Data Analysts to obtain access?"</span></span>
<span id="cb19-16"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">        * **Explanation:** Seems reasonable but too lengthy for a typical user query. </span></span>
<span id="cb19-17"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">        * **Rating:** 3</span></span>
<span id="cb19-18"></span>
<span id="cb19-19"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    ### Unrealistic Queries (Bad Examples)</span></span>
<span id="cb19-20"></span>
<span id="cb19-21"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    * **Query:** "If a GitLab team member has been with the company for over 3 months and is interested in participating in the Onboarding Buddy Program, what should they do to express their interest?"</span></span>
<span id="cb19-22"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">        * **Explanation:** Overly specific and unnatural. No real user would ask this.</span></span>
<span id="cb19-23"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">        * **Rating:** 1</span></span>
<span id="cb19-24"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    * **Query:** "On what date did the 'Managing Burnout with Time Off with John Fitch' session occur as part of the FY21 Learning Speaker Series?"</span></span>
<span id="cb19-25"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">        * **Explanation:** Irrelevant and overly specific. Not a typical user query. </span></span>
<span id="cb19-26"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">        * **Rating:** 2</span></span>
<span id="cb19-27"></span>
<span id="cb19-28"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    ### Your Task</span></span>
<span id="cb19-29"></span>
<span id="cb19-30"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    For the following generated question, please:</span></span>
<span id="cb19-31"></span>
<span id="cb19-32"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    1.  Rate its realism as a typical user query for an internal handbook application on a scale of 1 to 5 (1 = Very Unrealistic, 3 = Neutral/Somewhat Realistic, 5 = Very Realistic).</span></span>
<span id="cb19-33"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    2.  Provide a brief explanation for your rating, comparing it to the examples above if helpful.</span></span>
<span id="cb19-34"></span>
<span id="cb19-35"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    ### Output Format</span></span>
<span id="cb19-36"></span>
<span id="cb19-37"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    **Explanation:** `[Your brief explanation]`</span></span>
<span id="cb19-38"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    **Rating:** `[Your 1–5 rating]`</span></span>
<span id="cb19-39"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    """</span></span>
<span id="cb19-40">)</span>
<span id="cb19-41"></span>
<span id="cb19-42">user_prompt_rate <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> dedent(</span>
<span id="cb19-43">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb19-44"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    **Generated Question to Evaluate:**</span></span>
<span id="cb19-45"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    `</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{question_to_evaluate}</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">`</span></span>
<span id="cb19-46"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    """</span></span>
<span id="cb19-47">)</span></code></pre></div></div>
</div>
<p>Hamel and Shreya generally discourage using Likert-type 1-5 scales for LLM judges. However, in this case, we’re not aiming for a very accurate judge, we’re just trying to have a method that works well enough to filter out the questions. We don’t need to make this overly complicated.</p>
<p>Using the LLM judge, you can apply the filter to the generated questions:</p>
<div id="dd0929e4" class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb20" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb20-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> ResponseFiltering(BaseModel):</span>
<span id="cb20-2">    explanation: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span></span>
<span id="cb20-3">    rating: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span></span>
<span id="cb20-4"></span>
<span id="cb20-5"></span>
<span id="cb20-6">llm_with_structured_output_filtering <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> llm.with_structured_output(ResponseFiltering)</span>
<span id="cb20-7"></span>
<span id="cb20-8">messages_filtering <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ChatPromptTemplate.from_messages(</span>
<span id="cb20-9">    [(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"system"</span>, system_prompt_rate), (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"user"</span>, user_prompt_rate)]</span>
<span id="cb20-10">)</span>
<span id="cb20-11"></span>
<span id="cb20-12"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">async</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> rate_qa_pair(qa_pair):</span>
<span id="cb20-13">    compiled_messages <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">await</span> messages_filtering.ainvoke(</span>
<span id="cb20-14">        {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"question_to_evaluate"</span>: qa_pair.question}</span>
<span id="cb20-15">    )</span>
<span id="cb20-16">    output <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">await</span> llm_with_structured_output_filtering.ainvoke(compiled_messages)</span>
<span id="cb20-17">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> output</span>
<span id="cb20-18"></span>
<span id="cb20-19"></span>
<span id="cb20-20">tasks <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [rate_qa_pair(qa_pair) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> qa_pair <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> qa_pairs]</span>
<span id="cb20-21">results <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">await</span> asyncio.gather(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>tasks)</span>
<span id="cb20-22"></span>
<span id="cb20-23">rated_qa_pairs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb20-24">    {</span>
<span id="cb20-25">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"rating"</span>: result.rating,</span>
<span id="cb20-26">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"explanation"</span>: result.explanation,</span>
<span id="cb20-27">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"question"</span>: qa_pair.question,</span>
<span id="cb20-28">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"answer"</span>: qa_pair.fact,</span>
<span id="cb20-29">    }</span>
<span id="cb20-30">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> (result, qa_pair) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">zip</span>(results, qa_pairs)</span>
<span id="cb20-31">]</span>
<span id="cb20-32"></span>
<span id="cb20-33">df_rated_qa_pairs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pd.DataFrame(</span>
<span id="cb20-34">    rated_qa_pairs, columns<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Rating"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Explanation"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Question"</span>]</span>
<span id="cb20-35">)</span>
<span id="cb20-36"></span>
<span id="cb20-37">df_rated_qa_pairs.to_excel(</span>
<span id="cb20-38">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"../data/synthetic-data-rag/files/rated_qa_pairs.xlsx"</span>, index<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span></span>
<span id="cb20-39">)</span></code></pre></div></div>
</div>
<p>This will result in a list of <code>ResponseFiltering</code> objects, each containing an explanation and a rating for the corresponding question.</p>
<p>You can save the results to a file for later use.</p>
</section>
<section id="evaluate-the-rag-system" class="level2">
<h2 class="anchored" data-anchor-id="evaluate-the-rag-system">Evaluate the RAG system</h2>
<p>Now that we have the filtered QA pairs, it’s time to evaluate your RAG system. You’ll evaluate two parts of the RAG system: retrieval and generation.</p>
<p>Let’s use LangSmith to store our evaluation results. Start by creating a dataset on LangSmith:</p>
<div id="cfdfe231" class="cell" data-execution_count="30">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb21" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb21-1">langsmith_client <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Client()</span>
<span id="cb21-2">dataset_name <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Gitlab Handbook QA Evaluation 2"</span></span>
<span id="cb21-3"></span>
<span id="cb21-4"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">try</span>:</span>
<span id="cb21-5">    dataset <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> langsmith_client.create_dataset(dataset_name<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>dataset_name)</span>
<span id="cb21-6">    examples <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb21-7">        {</span>
<span id="cb21-8">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"inputs"</span>: {</span>
<span id="cb21-9">                <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"question"</span>: h[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"question"</span>],</span>
<span id="cb21-10">            },</span>
<span id="cb21-11">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"outputs"</span>: {</span>
<span id="cb21-12">                <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"answer"</span>: h[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"answer"</span>],</span>
<span id="cb21-13">                <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"doc"</span>: {</span>
<span id="cb21-14">                    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"id"</span>: chunk.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">id</span>,</span>
<span id="cb21-15">                    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"path"</span>: chunk.path,</span>
<span id="cb21-16">                },</span>
<span id="cb21-17">            },</span>
<span id="cb21-18">        }</span>
<span id="cb21-19">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> h, chunk <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">zip</span>(rated_qa_pairs, golden_docs)</span>
<span id="cb21-20">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> h[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"rating"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span></span>
<span id="cb21-21">    ]</span>
<span id="cb21-22">    langsmith_client.create_examples(dataset_id<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>dataset.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">id</span>, examples<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>examples)</span>
<span id="cb21-23"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">except</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">Exception</span>:</span>
<span id="cb21-24">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Dataset already exists, skipping creation."</span>)</span>
<span id="cb21-25">    dataset <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> langsmith_client.read_dataset(dataset_name<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>dataset_name)</span></code></pre></div></div>
</div>
<p>This will create a dataset on LangSmith with the rated QA pairs. Each example will include the question, answer, and the document from which the question was generated.</p>
<section id="retrieval-metrics" class="level3">
<h3 class="anchored" data-anchor-id="retrieval-metrics">Retrieval Metrics</h3>
<p>To evaluate the retrieval part of the RAG system, you can use metrics such as recall@k, precision@k, Mean Reciprocal Rank (MRR), and Normalized Discounted Cumulative Gain (NDCG).</p>
<p>For this tutorial, you’ll use two metrics: MRR and recall@k.</p>
<section id="mean-reciprocal-rank-mrr" class="level4">
<h4 class="anchored" data-anchor-id="mean-reciprocal-rank-mrr">Mean Reciprocal Rank (MRR)</h4>
<p>MRR measures how well the RAG system retrieves relevant documents. It calculates the average of the reciprocal ranks of the first relevant document for each query. It essentially measures how quickly the system retrieves the first relevant document for a given query.</p>
<p>Reciprocal Rank (RR) is calculated for a single query. It is the reciprocal of the rank at which the first relevant document is found. For example, if the first relevant item is at position 1, the RR is 1. If it’s at position 3, the RR is <img src="https://latex.codecogs.com/png.latex?1/3">. The formula is:</p>
<p><img src="https://latex.codecogs.com/png.latex?RR%20=%20%5Cfrac%7B1%7D%7Brank%7D"></p>
<p>where <img src="https://latex.codecogs.com/png.latex?rank"> is the position of the first relevant document.</p>
<p>MRR is the average of the Reciprocal Rank scores across all your queries. It provides a single, aggregate measure of retrieval performance. An MRR of 1 means you found the correct document at the first position for every query. The formula is:</p>
<p><img src="https://latex.codecogs.com/png.latex?MRR%20=%20%5Cfrac%7B1%7D%7B%7CQ%7C%7D%20%5Csum_%7Bi=1%7D%5E%7B%7CQ%7C%7D%20%5Cfrac%7B1%7D%7Brank%7D"></p>
<p>where <img src="https://latex.codecogs.com/png.latex?%7CQ%7C"> is the number of queries and <img src="https://latex.codecogs.com/png.latex?rank_i"> is the position of the first relevant document for query <img src="https://latex.codecogs.com/png.latex?i">.</p>
</section>
</section>
<section id="recallk" class="level3">
<h3 class="anchored" data-anchor-id="recallk">Recall@k</h3>
<p>Recall@k measures the proportion of relevant documents retrieved in the top k results. It helps you understand how many relevant documents are retrieved by the RAG system.</p>
<p>For example, if you retrieve 5 documents and 3 of them are relevant, recall@5 is 3/5 = 0.6. The formula is:</p>
<p><img src="https://latex.codecogs.com/png.latex?Recall@k%20=%20%5Cfrac%7B%7C%5Ctext%7Brelevant%20documents%20in%20top%20k%7D%7C%7D%7B%7C%5Ctext%7Btotal%20relevant%20documents%7D%7C%7D"></p>
<p>where the numerator is the number of relevant documents retrieved in the top k results, and the denominator is the total number of relevant documents for the query. To get the overall performance, you average the Recall@k values across all queries.</p>
<p>In our specific case, since we only have one relevant document per query, Recall@k will be 1 if the relevant document is in the top k results, and 0 otherwise.</p>
<p>You can define two LangSmith evaluators to calculate these metrics:</p>
<div id="07239efe" class="cell" data-execution_count="12">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb22" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb22-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> mrr(inputs: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>, outputs: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>, reference_outputs: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>:</span>
<span id="cb22-2">    reference_docs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>(reference_outputs[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"doc"</span>][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"id"</span>])]</span>
<span id="cb22-3">    docs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> outputs.get(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"docs"</span>, [])</span>
<span id="cb22-4">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> docs:</span>
<span id="cb22-5">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span></span>
<span id="cb22-6">    rank <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">next</span>((i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i, doc <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">enumerate</span>(docs) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> doc <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> reference_docs), <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>)</span>
<span id="cb22-7">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> rank <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> rank <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span></span>
<span id="cb22-8"></span>
<span id="cb22-9"></span>
<span id="cb22-10"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> recall(inputs: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>, outputs: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>, reference_outputs: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>:</span>
<span id="cb22-11">    reference_docs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>(reference_outputs[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"doc"</span>][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"id"</span>])]</span>
<span id="cb22-12">    docs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> outputs.get(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"docs"</span>, [])</span>
<span id="cb22-13">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> docs:</span>
<span id="cb22-14">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span></span>
<span id="cb22-15">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">any</span>(doc <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> reference_docs <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> doc <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> docs))</span></code></pre></div></div>
</div>
<p>LangSmith evaluators take <code>inputs</code>, <code>outputs</code>, and <code>reference_outputs</code> as arguments. The <code>inputs</code> are the user query and the retrieved documents, the <code>outputs</code> are the generated answers, and the <code>reference_outputs</code> are the target chunks.</p>
<p>Using those values, you can use the formulas we discussed to compute the MRR and recall@k metrics.</p>
</section>
<section id="generation-metrics" class="level3">
<h3 class="anchored" data-anchor-id="generation-metrics">Generation metrics</h3>
<p>In addition to measuring how good are the retrieved documents, you also want to measure if the LLM makes good use of them to generate answers. For that, Hamel and Shreya recommend using <a href="https://github.com/stanford-futuredata/ARES">ARES</a> or <a href="https://github.com/explodinggradients/ragas">RAGAS</a>.</p>
<p>The only issue is that ARES requires a human preference validation set of at least 50 examples and the standard RAGAS metrics consume tons of tokens. So, to keep things simple, you’ll build 3 simple metrics using an LLM judge, similar to RAGAS’ <a href="https://docs.ragas.io/en/stable/concepts/metrics/available_metrics/nvidia_metrics/">Nvidia metrics</a>:</p>
<ul>
<li><strong>Answer accuracy</strong>: Measures how accurate the generated answer is compared to the expected answer.</li>
<li><strong>Context relevance</strong>: Measures if the context provided to the LLM is relevant to the user query.</li>
<li><strong>Groundedness</strong>: Measures if the generated answer is grounded in the provided context.</li>
</ul>
<p>Compared to RAGAS implementation of these same metrics, the ones you’ll define here don’t do multiple runs and average the resulting scores. But, if you want to, you can easily apply a <a href="https://dylancastillo.co/posts/agentic-workflows-langgraph.html#parallelization">parallelization strategy</a> to do this. I’d also recommend using reasoning models, as they tend to perform better in these types of tasks.</p>
<p>Let’s see how to implement these metrics using LangSmith.</p>
<section id="answer-accuracy" class="level4">
<h4 class="anchored" data-anchor-id="answer-accuracy">Answer accuracy</h4>
<p>This metric evaluates how accurate the generated answer is compared to the expected answer. It’s an LLM judge that scores the generated answer against a reference answer using a 0, 1, 2 scale:</p>
<div id="07f60491" class="cell" data-execution_count="13">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb23" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb23-1">system_prompt_answer_accuracy <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> dedent(</span>
<span id="cb23-2">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb23-3"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    You are an expert evaluator. Your task is to evaluate the accuracy of a User Answer against a Reference Answer, given a Question.</span></span>
<span id="cb23-4"></span>
<span id="cb23-5"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    Here's the grading scale you must use:</span></span>
<span id="cb23-6"></span>
<span id="cb23-7"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    0 - If User Answer is not contained in Reference Answer or not accurate in all terms, topics, numbers, metrics, dates and units or the User Answer do not answer the question.</span></span>
<span id="cb23-8"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    2 - If User Answer is full contained and equivalent to Reference Answer in all terms, topics, numbers, metrics, dates and units.</span></span>
<span id="cb23-9"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    1 - If User Answer is partially contained and almost equivalent to Reference Answer in all terms, topics, numbers, metrics, dates and units.</span></span>
<span id="cb23-10"></span>
<span id="cb23-11"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    Your rating must be only 0, 1 or 2 according to the instructions above.</span></span>
<span id="cb23-12"></span>
<span id="cb23-13"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    Your answer must be a JSON object with the following keys:</span></span>
<span id="cb23-14"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    1. "explanation": "&lt;a brief explanation of your rating&gt;",</span></span>
<span id="cb23-15"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    2. "rating": "&lt;your rating, which must be one of the following: 0, 1, 2&gt;"</span></span>
<span id="cb23-16"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    """</span></span>
<span id="cb23-17">)</span>
<span id="cb23-18"></span>
<span id="cb23-19">user_prompt_answer_accuracy <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> dedent(</span>
<span id="cb23-20">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb23-21"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    **Question:** `</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{question}</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">`</span></span>
<span id="cb23-22"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    **User Answer:** `</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{user_answer}</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">`</span></span>
<span id="cb23-23"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    **Reference Answer:** `</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{reference_answer}</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">`</span></span>
<span id="cb23-24"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    """</span></span>
<span id="cb23-25">)</span>
<span id="cb23-26"></span>
<span id="cb23-27">messages_answer_accuracy <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ChatPromptTemplate.from_messages(</span>
<span id="cb23-28">    [(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"system"</span>, system_prompt_answer_accuracy), (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"user"</span>, user_prompt_answer_accuracy)]</span>
<span id="cb23-29">)</span>
<span id="cb23-30"></span>
<span id="cb23-31"></span>
<span id="cb23-32"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> ResponseAnswerAccuracy(BaseModel):</span>
<span id="cb23-33">    explanation: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span></span>
<span id="cb23-34">    rating: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span></span>
<span id="cb23-35"></span>
<span id="cb23-36"></span>
<span id="cb23-37">llm <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ChatOpenAI(model<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"gpt-4.1-mini"</span>)</span>
<span id="cb23-38"></span>
<span id="cb23-39">llm_with_structured_output_answer_accuracy <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> llm.with_structured_output(</span>
<span id="cb23-40">    ResponseAnswerAccuracy</span>
<span id="cb23-41">)</span>
<span id="cb23-42"></span>
<span id="cb23-43"></span>
<span id="cb23-44"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">async</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> answer_accuracy(</span>
<span id="cb23-45">    inputs: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>, outputs: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>, reference_outputs: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span></span>
<span id="cb23-46">) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>:</span>
<span id="cb23-47">    compiled_messages <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">await</span> messages_answer_accuracy.ainvoke(</span>
<span id="cb23-48">        {</span>
<span id="cb23-49">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"question"</span>: inputs[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"question"</span>],</span>
<span id="cb23-50">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"user_answer"</span>: outputs[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"answer"</span>],</span>
<span id="cb23-51">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"reference_answer"</span>: reference_outputs[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"answer"</span>],</span>
<span id="cb23-52">        }</span>
<span id="cb23-53">    )</span>
<span id="cb23-54">    output <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">await</span> llm_with_structured_output_answer_accuracy.ainvoke(compiled_messages)</span>
<span id="cb23-55">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> output.rating <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.0</span></span></code></pre></div></div>
</div>
<p>Similar to the retrieval evaluators, you can define LangSmith evaluators for the answer accuracy metric. The <code>inputs</code> will contain the user question, the <code>outputs</code> will have the generated answer, and the <code>reference_outputs</code> will have the expected answer.</p>
</section>
<section id="context-relevance" class="level4">
<h4 class="anchored" data-anchor-id="context-relevance">Context relevance</h4>
<p>This metric evaluates if the context provided to the LLM is relevant to the user query. Similar to the answer accuracy metric, it uses an LLM judge that scores the context relevance against a reference answer using a 0, 1, 2 scale:</p>
<div id="82c8f6b6" class="cell" data-execution_count="14">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb24" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb24-1">system_prompt_context_relevance <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> dedent(</span>
<span id="cb24-2">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb24-3"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    You are an expert evaluator. Your task is to evaluate the relevance of a Context in order to answer a Question. </span></span>
<span id="cb24-4"></span>
<span id="cb24-5"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    Do not rely on your previous knowledge about the Question. Use only what is written in the Context and in the Question.</span></span>
<span id="cb24-6"></span>
<span id="cb24-7"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    Here's the grading scale you must use:</span></span>
<span id="cb24-8"></span>
<span id="cb24-9"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    0 - If the context does not contain any relevant information to answer the question.</span></span>
<span id="cb24-10"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    1 - If the context partially contains relevant information to answer the question.</span></span>
<span id="cb24-11"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    2 - If the context contains relevant information to answer the question.</span></span>
<span id="cb24-12"></span>
<span id="cb24-13"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    You must always provide the relevance score of 0, 1, or 2, nothing else.</span></span>
<span id="cb24-14"></span>
<span id="cb24-15"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    Your answer must be a JSON object with the following keys:</span></span>
<span id="cb24-16"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    1. "explanation": "&lt;a brief explanation of your rating&gt;",</span></span>
<span id="cb24-17"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    2. "rating": "&lt;your rating, which must be one of the following: 0, 1, 2&gt;"</span></span>
<span id="cb24-18"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    """</span></span>
<span id="cb24-19">)</span>
<span id="cb24-20"></span>
<span id="cb24-21">user_prompt_context_relevance <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> dedent(</span>
<span id="cb24-22">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb24-23"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    **Question:** `</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{question}</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">`</span></span>
<span id="cb24-24"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    **Context:** `</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{context}</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">`</span></span>
<span id="cb24-25"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    """</span></span>
<span id="cb24-26">)</span>
<span id="cb24-27"></span>
<span id="cb24-28">messages_context_relevance <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ChatPromptTemplate.from_messages(</span>
<span id="cb24-29">    [</span>
<span id="cb24-30">        (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"system"</span>, system_prompt_context_relevance),</span>
<span id="cb24-31">        (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"user"</span>, user_prompt_context_relevance),</span>
<span id="cb24-32">    ]</span>
<span id="cb24-33">)</span>
<span id="cb24-34"></span>
<span id="cb24-35"></span>
<span id="cb24-36"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> ResponseContextRelevance(BaseModel):</span>
<span id="cb24-37">    explanation: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span></span>
<span id="cb24-38">    rating: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span></span>
<span id="cb24-39"></span>
<span id="cb24-40"></span>
<span id="cb24-41">llm <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ChatOpenAI(model<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"gpt-4.1-mini"</span>)</span>
<span id="cb24-42">llm_with_structured_output_context_relevance <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> llm.with_structured_output(</span>
<span id="cb24-43">    ResponseContextRelevance</span>
<span id="cb24-44">)</span>
<span id="cb24-45"></span>
<span id="cb24-46"></span>
<span id="cb24-47"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">async</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> context_relevance(</span>
<span id="cb24-48">    inputs: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>, outputs: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>, reference_outputs: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span></span>
<span id="cb24-49">) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>:</span>
<span id="cb24-50">    compiled_messages <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">await</span> messages_context_relevance.ainvoke(</span>
<span id="cb24-51">        {</span>
<span id="cb24-52">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"question"</span>: inputs[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"question"</span>],</span>
<span id="cb24-53">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"context"</span>: outputs[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"context"</span>],</span>
<span id="cb24-54">        }</span>
<span id="cb24-55">    )</span>
<span id="cb24-56">    output <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">await</span> llm_with_structured_output_context_relevance.ainvoke(</span>
<span id="cb24-57">        compiled_messages</span>
<span id="cb24-58">    )</span>
<span id="cb24-59">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> output.rating <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span></span></code></pre></div></div>
</div>
<p>You can define a <code>context_relevance</code> evaluator. The <code>inputs</code> will contain the user question and from the <code>outputs</code> you’ll use the context provided to the LLM.</p>
</section>
<section id="groundedness" class="level4">
<h4 class="anchored" data-anchor-id="groundedness">Groundedness</h4>
<p>This metric evaluates if the answer is grounded in the provided context. Like before, you use an LLM judge that scores the groundedness of the answer against the context using a 0, 1, 2 scale:</p>
<div id="32cef827" class="cell" data-execution_count="15">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb25" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb25-1">system_prompt_groundedness <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> dedent(</span>
<span id="cb25-2">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb25-3"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    You are an expert evaluator. Your task is to evaluate the groundedness of an assertion against a context. </span></span>
<span id="cb25-4"></span>
<span id="cb25-5"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    Do not rely on your previous knowledge about the assertion or context. Use only what is written in the assertion and in the context.</span></span>
<span id="cb25-6"></span>
<span id="cb25-7"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    Here's the grading scale you must use:</span></span>
<span id="cb25-8"></span>
<span id="cb25-9"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    0 - If the assertion is not supported by the context. Or, if the context or assertion is empty.</span></span>
<span id="cb25-10"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    1 - If the context partially contains relevant information to support the assertion.</span></span>
<span id="cb25-11"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    2 - If the context fully supports the assertion.</span></span>
<span id="cb25-12"></span>
<span id="cb25-13"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    You must always provide the relevance score of 0, 1, or 2, nothing else.</span></span>
<span id="cb25-14"></span>
<span id="cb25-15"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    Your answer must be a JSON object with the following keys:</span></span>
<span id="cb25-16"></span>
<span id="cb25-17"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    1. "explanation": "&lt;a brief explanation of your rating&gt;",</span></span>
<span id="cb25-18"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    2. "rating": "&lt;your rating, which must be one of the following: 0, 1, 2&gt;"</span></span>
<span id="cb25-19"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    """</span></span>
<span id="cb25-20">)</span>
<span id="cb25-21"></span>
<span id="cb25-22">user_prompt_groundedness <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> dedent(</span>
<span id="cb25-23">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb25-24"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    **Assertion:** `</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{answer}</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">`</span></span>
<span id="cb25-25"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    **Context:** `</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{context}</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">`</span></span>
<span id="cb25-26"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    """</span></span>
<span id="cb25-27">)</span>
<span id="cb25-28"></span>
<span id="cb25-29">messages_groundedness <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ChatPromptTemplate.from_messages(</span>
<span id="cb25-30">    [(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"system"</span>, system_prompt_groundedness), (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"user"</span>, user_prompt_groundedness)]</span>
<span id="cb25-31">)</span>
<span id="cb25-32"></span>
<span id="cb25-33"></span>
<span id="cb25-34"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> ResponseGroundedness(BaseModel):</span>
<span id="cb25-35">    explanation: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span></span>
<span id="cb25-36">    rating: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span></span>
<span id="cb25-37"></span>
<span id="cb25-38"></span>
<span id="cb25-39">llm <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ChatOpenAI(model<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"gpt-4.1-mini"</span>)</span>
<span id="cb25-40">llm_with_structured_output_groundedness <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> llm.with_structured_output(</span>
<span id="cb25-41">    ResponseGroundedness</span>
<span id="cb25-42">)</span>
<span id="cb25-43"></span>
<span id="cb25-44"></span>
<span id="cb25-45"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">async</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> groundedness(inputs: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>, outputs: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>, reference_outputs: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>:</span>
<span id="cb25-46">    compiled_messages <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">await</span> messages_groundedness.ainvoke(</span>
<span id="cb25-47">        {</span>
<span id="cb25-48">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"answer"</span>: outputs[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"answer"</span>],</span>
<span id="cb25-49">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"context"</span>: outputs[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"context"</span>],</span>
<span id="cb25-50">        }</span>
<span id="cb25-51">    )</span>
<span id="cb25-52">    output <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">await</span> llm_with_structured_output_groundedness.ainvoke(compiled_messages)</span>
<span id="cb25-53">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> output.rating <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span></span></code></pre></div></div>
</div>
<p>You use the same approach as before to define LangSmith evaluators for this metric. The <code>inputs</code> will contain the user question, the <code>outputs</code> will have the context provided to the LLM.</p>
</section>
</section>
<section id="run-evaluation" class="level3">
<h3 class="anchored" data-anchor-id="run-evaluation">Run evaluation</h3>
<p>Now we can run the full RAG pipeline and evaluate its results using the LangSmith evaluators.</p>
<div id="ddb7581f" class="cell" data-execution_count="16">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb26" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb26-1">system_prompt_generation <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> dedent(</span>
<span id="cb26-2">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb26-3"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    You're a helpful assistant. Provided with a question and the most relevant documents, you must generate a concise and accurate answer based on the information in those documents.</span></span>
<span id="cb26-4"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    """</span></span>
<span id="cb26-5">)</span>
<span id="cb26-6"></span>
<span id="cb26-7">user_prompt_generation <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> dedent(</span>
<span id="cb26-8">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb26-9"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    QUESTION: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{question}</span></span>
<span id="cb26-10"></span>
<span id="cb26-11"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    RELEVANT DOCUMENTS:</span></span>
<span id="cb26-12"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{documents}</span></span>
<span id="cb26-13"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    """</span></span>
<span id="cb26-14">)</span>
<span id="cb26-15"></span>
<span id="cb26-16">messages_generation <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ChatPromptTemplate.from_messages(</span>
<span id="cb26-17">    [(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"system"</span>, system_prompt_generation), (<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"user"</span>, user_prompt_generation)]</span>
<span id="cb26-18">)</span>
<span id="cb26-19"></span>
<span id="cb26-20">llm_generation <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ChatOpenAI(</span>
<span id="cb26-21">    model<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"gpt-4o-mini"</span>,</span>
<span id="cb26-22">)</span></code></pre></div></div>
</div>
<p>A good starting point is to evaluate different values for the number of retrieved documents (K). For example, you could evaluate different values for the number of retrieved documents.</p>
<p>Langsmith requires a wrapper or target function that encapsulates your RAG system. In your case, this function takes a user query, retrieves the most similar documents, generates an answer using the LLM, and returns the generated answer with the document IDs and context retrieved.</p>
<p>I’ll run this code for K values of 3, 5, and 10, and compare the results.</p>
<div id="9c0ddd40" class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb27" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb27-1"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> K <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> [<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>]:</span>
<span id="cb27-2">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Running evaluation for K=</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>K<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb27-3"></span>
<span id="cb27-4">    <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@traceable</span></span>
<span id="cb27-5">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">async</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> target(inputs: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>:</span>
<span id="cb27-6">        relevant_docs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> get_similar_docs(inputs[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"question"</span>], top_k<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>K)</span>
<span id="cb27-7">        formatted_docs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> format_docs(relevant_docs)</span>
<span id="cb27-8">        messages <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">await</span> messages_generation.ainvoke(</span>
<span id="cb27-9">            {</span>
<span id="cb27-10">                <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"question"</span>: inputs[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"question"</span>],</span>
<span id="cb27-11">                <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"documents"</span>: formatted_docs,</span>
<span id="cb27-12">            }</span>
<span id="cb27-13">        )</span>
<span id="cb27-14">        response <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">await</span> llm_generation.ainvoke(messages)</span>
<span id="cb27-15">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> {</span>
<span id="cb27-16">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"answer"</span>: response.content,</span>
<span id="cb27-17">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"docs"</span>: [doc.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">id</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> doc <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> relevant_docs],</span>
<span id="cb27-18">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"context"</span>: formatted_docs,</span>
<span id="cb27-19">        }</span>
<span id="cb27-20"></span>
<span id="cb27-21">    experiment_results <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">await</span> langsmith_client.aevaluate(</span>
<span id="cb27-22">        target,</span>
<span id="cb27-23">        data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>dataset_name,</span>
<span id="cb27-24">        evaluators<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[recall, mrr, answer_accuracy, context_relevance, groundedness],</span>
<span id="cb27-25">        max_concurrency<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">50</span>,</span>
<span id="cb27-26">        experiment_prefix<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"top-</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>K<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>,</span>
<span id="cb27-27">    )</span></code></pre></div></div>
</div>
<p>Using <code>aevaluate</code> you can speed up the evaluation process and run the evaluations concurrently. I got the following results:</p>
<table class="table">
<colgroup>
<col style="width: 16%">
<col style="width: 16%">
<col style="width: 16%">
<col style="width: 16%">
<col style="width: 16%">
<col style="width: 16%">
</colgroup>
<thead>
<tr class="header">
<th style="text-align: center;"><strong>k</strong></th>
<th style="text-align: center;"><strong>Answer accuracy</strong></th>
<th style="text-align: center;"><strong>Context relevance</strong></th>
<th style="text-align: center;"><strong>Groundedness</strong></th>
<th style="text-align: center;"><strong>MRR</strong></th>
<th style="text-align: center;"><strong>Recall</strong></th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td style="text-align: center;"><strong>3</strong></td>
<td style="text-align: center;">0.81</td>
<td style="text-align: center;">0.93</td>
<td style="text-align: center;">0.97</td>
<td style="text-align: center;">0.75</td>
<td style="text-align: center;">0.80</td>
</tr>
<tr class="even">
<td style="text-align: center;"><strong>5</strong></td>
<td style="text-align: center;">0.80</td>
<td style="text-align: center;">0.94</td>
<td style="text-align: center;">0.97</td>
<td style="text-align: center;">0.76</td>
<td style="text-align: center;">0.85</td>
</tr>
<tr class="odd">
<td style="text-align: center;"><strong>10</strong></td>
<td style="text-align: center;">0.85</td>
<td style="text-align: center;">0.97</td>
<td style="text-align: center;">0.98</td>
<td style="text-align: center;">0.77</td>
<td style="text-align: center;">0.91</td>
</tr>
</tbody>
</table>
<p>You can see that recall improves significantly as you increase the number of retrieved documents, which is expected. Answer accuracy and context relevance only seems to improve significantly when you increase the number of retrieved documents from 5 to 10.</p>
<p>If you got here, you’ve successfully built a RAG system and evaluated it using synthetic data. The next steps would be to continue making changes to parts of the pipeline and re-running the evaluations to see how they affect the performance of your RAG system.</p>
<p>You will also want to improve the quality of the generated questions, and ideally include real user queries in the evaluation process.</p>
<p>Next, I’ll show you how to squeeze a bit more performance from the retrieval part of the RAG system by using a reranker.</p>
</section>
</section>
<section id="improve-metrics-with-a-reranker" class="level2">
<h2 class="anchored" data-anchor-id="improve-metrics-with-a-reranker">Improve metrics with a reranker</h2>
<p>A quick way to improve your RAG system is to rerank the retrieved documents. In addition to doing retrieval using vector similarity or keyword search, you can have a reranking step that uses a more capable model to score and reorder the retrieved documents based on their relevance to the user query.</p>
<p>Let’s use <code>sentence-transformers</code> with an open-source model to do this:</p>
<div id="3ef76069" class="cell" data-execution_count="17">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb28" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb28-1">cross_encoder <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> CrossEncoder(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"mixedbread-ai/mxbai-rerank-xsmall-v1"</span>)</span></code></pre></div></div>
</div>
<p>To rerank a set of documents, you take the results from the retrieval step and pass them to the reranker, which will return a new set of documents ordered by their relevance to the user query.</p>
<p>Here’s an example:</p>
<div id="481b2e0b" class="cell" data-execution_count="18">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb29" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb29-1">query <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"What is the process for creating a new learning hub for your team in Level Up at GitLab?"</span></span>
<span id="cb29-2">hits <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> get_similar_docs(query, top_k<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">50</span>)</span>
<span id="cb29-3">cross_inp <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [[query, h.page_content] <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> h <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> hits]</span>
<span id="cb29-4">reranker_scores <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> cross_encoder.predict(cross_inp)</span>
<span id="cb29-5">sorted_hits <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sorted</span>(hits, key<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">lambda</span> x: reranker_scores[hits.index(x)], reverse<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>)</span></code></pre></div></div>
</div>
<p>This takes the 50 most similar documents to the query and reranks them using a cross-encoder model. The <code>reranker_scores</code> are used to sort the documents in descending order of relevance.</p>
<p>Now you can do the same evaluation as before (for k = 5), but including this new reranking step:</p>
<div id="3411cd58" class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb30" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb30-1">K <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span></span>
<span id="cb30-2"></span>
<span id="cb30-3"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> get_reranked_docs(</span>
<span id="cb30-4">    query: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>, similar_docs: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[RetrievedDoc]</span>
<span id="cb30-5">) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[RetrievedDoc]:</span>
<span id="cb30-6">    cross_inp <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [[query, doc.page_content] <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> doc <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> similar_docs]</span>
<span id="cb30-7">    reranker_scores <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> cross_encoder.predict(cross_inp)</span>
<span id="cb30-8">    sorted_docs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sorted</span>(</span>
<span id="cb30-9">        similar_docs, key<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">lambda</span> x: reranker_scores[similar_docs.index(x)], reverse<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span></span>
<span id="cb30-10">    )</span>
<span id="cb30-11">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> sorted_docs</span>
<span id="cb30-12"></span>
<span id="cb30-13"></span>
<span id="cb30-14"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@traceable</span></span>
<span id="cb30-15"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">async</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> target_with_reranking(inputs: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>:</span>
<span id="cb30-16">    relevant_docs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> get_similar_docs(inputs[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"question"</span>], top_k<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">75</span>)</span>
<span id="cb30-17">    reranked_docs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> get_reranked_docs(inputs[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"question"</span>], relevant_docs)[:K]</span>
<span id="cb30-18">    formatted_docs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> format_docs(reranked_docs)</span>
<span id="cb30-19">    messages <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">await</span> messages_generation.ainvoke(</span>
<span id="cb30-20">        {</span>
<span id="cb30-21">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"question"</span>: inputs[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"question"</span>],</span>
<span id="cb30-22">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"documents"</span>: formatted_docs,</span>
<span id="cb30-23">        }</span>
<span id="cb30-24">    )</span>
<span id="cb30-25">    response <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">await</span> llm.ainvoke(messages)</span>
<span id="cb30-26">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> {</span>
<span id="cb30-27">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"answer"</span>: response,</span>
<span id="cb30-28">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"docs"</span>: [doc.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">id</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> doc <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> reranked_docs],</span>
<span id="cb30-29">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"context"</span>: formatted_docs,</span>
<span id="cb30-30">    }</span>
<span id="cb30-31"></span>
<span id="cb30-32">experiment_results <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">await</span> langsmith_client.aevaluate(</span>
<span id="cb30-33">    target_with_reranking,</span>
<span id="cb30-34">    data<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>dataset_name,</span>
<span id="cb30-35">    evaluators<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[recall, mrr, answer_accuracy, context_relevance, groundedness],</span>
<span id="cb30-36">    max_concurrency<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">50</span>,</span>
<span id="cb30-37">    experiment_prefix<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"top-</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>K<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">-reranked"</span>,</span>
<span id="cb30-38">)</span></code></pre></div></div>
</div>
<p>Here are the results of the original run with k=5 and the run with a reranker:</p>
<table class="table">
<colgroup>
<col style="width: 10%">
<col style="width: 17%">
<col style="width: 17%">
<col style="width: 17%">
<col style="width: 17%">
<col style="width: 17%">
</colgroup>
<thead>
<tr class="header">
<th><strong>experiment</strong></th>
<th style="text-align: center;"><strong>answer_accuracy</strong></th>
<th style="text-align: center;"><strong>context_relevance</strong></th>
<th style="text-align: center;"><strong>groundedness</strong></th>
<th style="text-align: center;"><strong>mrr</strong></th>
<th style="text-align: center;"><strong>recall</strong></th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>k=5, vanilla</strong></td>
<td style="text-align: center;">0.80</td>
<td style="text-align: center;">0.94</td>
<td style="text-align: center;">0.97</td>
<td style="text-align: center;">0.76</td>
<td style="text-align: center;">0.85</td>
</tr>
<tr class="even">
<td><strong>k=5, rerank</strong></td>
<td style="text-align: center;">0.97</td>
<td style="text-align: center;">0.97</td>
<td style="text-align: center;">1.00</td>
<td style="text-align: center;">0.79</td>
<td style="text-align: center;">0.91</td>
</tr>
</tbody>
</table>
<p>You can see that it immediately improves most metrics, especially the answer accuracy and recall. You should expect some variance in the results, so be aware that the change is not necessarily as big as it seems (but that’s a topic for another article!).</p>
</section>
<section id="conclusion" class="level2">
<h2 class="anchored" data-anchor-id="conclusion">Conclusion</h2>
<p>In this tutorial, you’ve learned how to use synthetic data to bootstrap your RAG system evaluations. We covered:</p>
<ul>
<li><strong>Synthetic data generation</strong>: How to generate QA pairs from your documents using adversarial techniques with confounding chunks</li>
<li><strong>Evaluation metrics</strong>: Both retrieval metrics (MRR, Recall@k) and generation metrics (answer accuracy, context relevance, groundedness)</li>
<li><strong>Filtering synthetic data</strong>: Using an LLM judge to filter out unrealistic questions and improve the dataset quality</li>
<li><strong>Performance optimization</strong>: How reranking can significantly improve both retrieval and generation metrics</li>
</ul>
<p>This approach gives you a solid foundation for evaluating RAG systems even when you don’t have real user data. Synthetic data is useful for getting started quickly, but remember that you should incorporate real user queries into your evaluation process, as that is the most realistic way to evaluate your RAG system.</p>
<p>Hope you find this tutorial useful. If you have any questions, leave a comment below.</p>


</section>

<div id="quarto-appendix" class="default"><section class="quarto-appendix-contents" id="quarto-citation"><h2 class="anchored quarto-appendix-heading">Citation</h2><div><div class="quarto-appendix-secondary-label">BibTeX citation:</div><pre class="sourceCode code-with-copy quarto-appendix-bibtex"><code class="sourceCode bibtex">@online{castillo2025,
  author = {Castillo, Dylan},
  title = {Using Synthetic Data to Bootstrap Your {RAG} System Evals},
  date = {2025-08-07},
  url = {https://dylancastillo.co/posts/synthetic-data-rag.html},
  langid = {en}
}
</code></pre><div class="quarto-appendix-secondary-label">For attribution, please cite this work as:</div><div id="ref-castillo2025" class="csl-entry quarto-appendix-citeas">
Castillo, Dylan. 2025. <span>“Using Synthetic Data to Bootstrap Your RAG
System Evals.”</span> August 7. <a href="https://dylancastillo.co/posts/synthetic-data-rag.html">https://dylancastillo.co/posts/synthetic-data-rag.html</a>.
</div></div></section></div> ]]></description>
  <category>llm</category>
  <category>python</category>
  <category>rag</category>
  <category>openai</category>
  <guid>https://dylancastillo.co/posts/synthetic-data-rag.html</guid>
  <pubDate>Thu, 07 Aug 2025 00:00:00 GMT</pubDate>
  <media:content url="https://dylancastillo.co/posts/images/cards/synthetic-data-rag.png" medium="image" type="image/png" height="76" width="144"/>
</item>
<item>
  <title>Evaluator-optimizer workflow with Pydantic AI</title>
  <dc:creator>Dylan Castillo</dc:creator>
  <link>https://dylancastillo.co/til/evaluator-optimizer-pydantic-ai.html</link>
  <description><![CDATA[ 




<p>I’m doing a deep dive into Pydantic AI, so I’ve been re-implementing typical patterns for building <a href="https://dylancastillo.co/til/react-agent-pydantic-ai.html">agentic</a> <a href="https://dylancastillo.co/posts/agentic-workflows-langgraph.html">systems</a>.</p>
<p>In this post, I’ll explore how to build a <a href="https://www.anthropic.com/engineering/building-effective-agents#workflow-evaluator-optimizer">evaluator-optimizer</a> workflow. I won’t cover the basics of agentic workflows, so if you’re not familiar with the concept, I recommend you to read <a href="https://dylancastillo.co/posts/agentic-workflows-langgraph.html">this post</a> first.</p>
<section id="what-is-evaluator-optimizer" class="level2">
<h2 class="anchored" data-anchor-id="what-is-evaluator-optimizer">What is evaluator-optimizer?</h2>
<p>Evaluator-optimizer is a pattern that has an LLM generator and an LLM evaluator. The generator generates a solution and the evaluator evaluates if the solution is good enough. If it’s not, the generator is given feedback and it generates a new solution. This process is repeated until the solution is good enough.</p>
<p>It looks like this:</p>
<div class="cell" data-layout-align="default">
<div class="cell-output-display">
<div>
<p></p><figure class="figure"><p></p>
<div>
<pre class="mermaid mermaid-js">flowchart LR
    In([In]) --&gt; Gen["Generator (LLM)"]
    Gen -- "Solution" --&gt; Eval["Evaluator (LLM)"]
    Eval -- "Accepted" --&gt; Out([Out])
    Eval -- "Rejected + Feedback" --&gt; Gen

</pre>
</div>
<p></p></figure><p></p>
</div>
</div>
</div>
<p><strong>Examples:</strong></p>
<ul>
<li>Content generation that must match certain guidelines such as writing with a particular style.</li>
<li>Improving search results iteratively</li>
</ul>
<p>Let’s see how this looks in practice.</p>
</section>
<section id="setup" class="level2">
<h2 class="anchored" data-anchor-id="setup">Setup</h2>
<p>I will implement a simple workflow:</p>
<ol type="1">
<li>Generate a candidate article</li>
<li>Evaluate if the article is good enough</li>
<li>If it’s not, provide feedback and generate a new article</li>
<li>Repeat until the article is good enough</li>
</ol>
<p>Before we start, because Pydantic AI uses <code>asyncio</code> under the hood, you need to enable <code>nest_asyncio</code> to use it in a notebook:</p>
<div id="2a607486" class="cell" data-execution_count="5">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> nest_asyncio</span>
<span id="cb1-2"></span>
<span id="cb1-3">nest_asyncio.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">apply</span>()</span></code></pre></div></div>
</div>
<p>Then, you need to import the required libraries. I’m using <strong><a href="https://logfire.pydantic.dev/">Logfire</a></strong> to monitor the workflow.</p>
<div id="4081c493" class="cell" data-execution_count="6">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb2-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> os</span>
<span id="cb2-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> typing <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> Literal</span>
<span id="cb2-3"></span>
<span id="cb2-4"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> logfire</span>
<span id="cb2-5"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> requests</span>
<span id="cb2-6"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> dotenv <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> load_dotenv</span>
<span id="cb2-7"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> pydantic_ai <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> Agent, RunContext</span>
<span id="cb2-8"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> pydantic <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> BaseModel, Field</span>
<span id="cb2-9"></span>
<span id="cb2-10">load_dotenv()</span></code></pre></div></div>
<div class="cell-output cell-output-display" data-execution_count="6">
<pre><code>True</code></pre>
</div>
</div>
<p><strong>PydanticAI</strong> is compatible with OpenTelemetry (OTel). So it’s pretty easy to use it with Logfire or with any other OTel-compatible observability tool (e.g., <a href="https://langfuse.com/">Langfuse</a>).</p>
<p>To enable tracking, create a project in Logfire, generate a <code>Write token</code> and add it to the <code>.env</code> file. Then, you just need to run:</p>
<div id="014a1f88" class="cell" data-execution_count="7">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb4-1">logfire.configure(</span>
<span id="cb4-2">    token<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>os.getenv(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'LOGFIRE_TOKEN'</span>),</span>
<span id="cb4-3">)</span>
<span id="cb4-4">logfire.instrument_pydantic_ai()</span></code></pre></div></div>
</div>
<p>The first time you run this, it will ask you to create a project in Logfire. From it, it will generate a <code>logfire_credentials.json</code> file in your working directory. In following runs, it will automatically use the credentials from the file.</p>
</section>
<section id="evaluator-optimizer-workflow" class="level2">
<h2 class="anchored" data-anchor-id="evaluator-optimizer-workflow">Evaluator-optimizer workflow</h2>
<p>The workflow is composed of two steps:</p>
<ul>
<li><code>Text generator</code>: Generates a candidate article.</li>
<li><code>Evaluator</code>: Evaluates if the article is good enough.</li>
</ul>
<p>I’ll split the text generation into two agents: <code>generator</code> and <code>fixer</code>. The <code>generator</code> will generate a candidate article and the <code>fixer</code> will fix the article, when provided with feedback.</p>
<div id="18ec377f" class="cell" data-execution_count="8">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb5-1">generator <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Agent(</span>
<span id="cb5-2">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'openai:gpt-4.1-mini'</span>,</span>
<span id="cb5-3">    system_prompt<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(</span>
<span id="cb5-4">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"You are an expert writer. Provided with a topic, you will generate an engaging article with less than 500 words"</span></span>
<span id="cb5-5">    ),</span>
<span id="cb5-6">)</span>
<span id="cb5-7"></span>
<span id="cb5-8">fixer <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Agent(</span>
<span id="cb5-9">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'openai:gpt-4.1-mini'</span>,</span>
<span id="cb5-10">    system_prompt<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(</span>
<span id="cb5-11">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"You are an expert writer. Provided with a text and feedback, you wil improve the text."</span></span>
<span id="cb5-12">    ),</span>
<span id="cb5-13">)</span></code></pre></div></div>
<div class="cell-output cell-output-display">
<pre style="white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace"><span style="font-weight: bold">Logfire</span> project URL: <a href="https://logfire-us.pydantic.dev/dylanjcastillo/blog" target="_blank"><span style="color: #008080; text-decoration-color: #008080; text-decoration: underline">https://logfire-us.pydantic.dev/dylanjcastillo/blog</span></a>
</pre>
</div>
</div>
<p>Next, I’ll create the <code>Evaluator</code> agent. It will take a text and it will evaluate if it’s good enough. It’ll produce an <code>Evaluation</code> object as the output.</p>
<div id="2dd08409" class="cell" data-execution_count="9">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb6-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> Evaluation(BaseModel):</span>
<span id="cb6-2">    explanation: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Field(</span>
<span id="cb6-3">        description<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Explain why the text evaluated matches or not the evaluation criteria"</span></span>
<span id="cb6-4">    )</span>
<span id="cb6-5">    feedback: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Field(</span>
<span id="cb6-6">        description<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Provide feedback to the writer to improve the text"</span></span>
<span id="cb6-7">    )</span>
<span id="cb6-8">    is_correct: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">bool</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Field(</span>
<span id="cb6-9">        description<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Whether the text evaluated matches or not the evaluation criteria"</span></span>
<span id="cb6-10">    )</span>
<span id="cb6-11"></span>
<span id="cb6-12">evaluator <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Agent(</span>
<span id="cb6-13">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'openai:gpt-4.1-mini'</span>,</span>
<span id="cb6-14">    system_prompt<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(</span>
<span id="cb6-15">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"You are an expert evaluator. Provided with a text, you will evaluate if it's written in British English and if it's appropriate for a young audience. The text must always use British spelling and grammar. Make sure the text doesn't include any em dashes."</span></span>
<span id="cb6-16">    ),</span>
<span id="cb6-17">    output_type<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>Evaluation,</span>
<span id="cb6-18">)</span></code></pre></div></div>
</div>
<p>Finally, you can encapsulate all the logic in a single function:</p>
<div id="97be7efe" class="cell" data-execution_count="13">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb7-1"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@logfire.instrument</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Run workflow"</span>)</span>
<span id="cb7-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> run_workflow(topic: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>:</span>
<span id="cb7-3">    text <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> generator.run_sync(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Generate an article about '</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>topic<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">'"</span>)</span>
<span id="cb7-4">    evaluation <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> evaluator.run_sync(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Evaluate the following text: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>text<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>output<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb7-5">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> _ <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>):</span>
<span id="cb7-6">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> evaluation.output.is_correct:</span>
<span id="cb7-7">            text <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> fixer.run_sync(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Fix the text: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>text<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>output<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> with the following feedback: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>evaluation<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>output<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>feedback<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb7-8">            evaluation <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> evaluator.run_sync(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Evaluate the following text: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>text<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>output<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb7-9">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span>:</span>
<span id="cb7-10">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> text.output</span>
<span id="cb7-11">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> text.output</span>
<span id="cb7-12"></span>
<span id="cb7-13">output <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> run_workflow(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Consumption of hard drugs"</span>)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>11:28:25.995 Run workflow
11:28:25.995   generator run
11:28:25.996     chat gpt-4.1-mini
11:28:36.293   evaluator run
11:28:36.294     chat gpt-4.1-mini</code></pre>
</div>
</div>
<p>And here’s the output:</p>
<div id="1ee5bd83" class="cell" data-execution_count="14">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb9-1"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(output)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>**The Complex Reality of Hard Drug Consumption**

Hard drugs — substances such as heroin, cocaine, methamphetamines, and crack — have long been a subject of concern worldwide due to their profound impact on individuals and society. The consumption of these drugs is not merely a matter of personal choice but a complex issue influenced by social, economic, psychological, and cultural factors.

**Understanding Hard Drugs and Their Effects**

Hard drugs are characterized by their high potential for addiction and severe physical and psychological effects. Unlike softer substances such as marijuana or alcohol (when consumed responsibly), hard drugs often disrupt brain function dramatically, leading to addiction, mental health disorders, and significant physical health problems. Users may experience paranoia, hallucinations, heart issues, and even fatal overdoses.

The allure of hard drugs often stems from their ability to produce intense euphoria or numb emotional pain temporarily. However, this fleeting escape comes at a steep cost. Dependence quickly sets in, making cessation incredibly difficult and often trapping users in a cycle of abuse.

**Social and Economic Implications**

The ramifications of hard drug consumption ripple beyond the individual. Families endure emotional and financial strain, communities face increased crime rates and reduced public safety, and healthcare systems are burdened with treating overdoses and long-term complications. Moreover, productivity declines as addiction interferes with employment, contributing to broader economic challenges.

Many users come from marginalized backgrounds, where poverty, trauma, and lack of education or opportunity make drugs seem like a refuge or an escape. This correlation highlights that addressing drug consumption isn't only a matter of law enforcement but of social equity and support.

**Challenges in Addressing Hard Drug Use**

Efforts to reduce hard drug consumption have varied widely, from strict punitive measures to harm reduction strategies. While criminalization seeks to deter use, it often leads to overcrowded prisons and can exacerbate social stigma, making it harder for users to seek help. Conversely, approaches like supervised consumption sites, needle exchange programs, and accessible addiction treatment aim to minimize harm and promote recovery.

Prevention and education are critical components. Informing communities about the risks of hard drugs and providing mental health support can reduce initial experimentation and help those at risk before addiction takes hold.

**Moving Towards Compassionate Solutions**

Ultimately, the consumption of hard drugs is a multifaceted issue requiring balanced and compassionate responses. Policymakers, healthcare providers, and communities must work together to create environments that prioritize treatment over punishment, recognize addiction as a health issue, and promote social support.

By understanding the complex realities behind hard drug use, society can better address its consequences and help those affected find a path to recovery and hope.</code></pre>
</div>
</div>
<p>That’s all!</p>
<p>You can access this notebook <a href="https://github.com/dylanjcastillo/blog/tree/main/til/prompt-chaining-pydantic-ai.ipynb">here</a>.</p>
<p>If you have any questions or feedback, please let me know in the comments below.</p>


</section>

<div id="quarto-appendix" class="default"><section class="quarto-appendix-contents" id="quarto-citation"><h2 class="anchored quarto-appendix-heading">Citation</h2><div><div class="quarto-appendix-secondary-label">BibTeX citation:</div><pre class="sourceCode code-with-copy quarto-appendix-bibtex"><code class="sourceCode bibtex">@online{castillo2025,
  author = {Castillo, Dylan},
  title = {Evaluator-Optimizer Workflow with {Pydantic} {AI}},
  date = {2025-07-09},
  url = {https://dylancastillo.co/til/evaluator-optimizer-pydantic-ai.html},
  langid = {en}
}
</code></pre><div class="quarto-appendix-secondary-label">For attribution, please cite this work as:</div><div id="ref-castillo2025" class="csl-entry quarto-appendix-citeas">
Castillo, Dylan. 2025. <span>“Evaluator-Optimizer Workflow with Pydantic
AI.”</span> July 9. <a href="https://dylancastillo.co/til/evaluator-optimizer-pydantic-ai.html">https://dylancastillo.co/til/evaluator-optimizer-pydantic-ai.html</a>.
</div></div></section></div> ]]></description>
  <category>til</category>
  <category>llm</category>
  <category>pydantic-ai</category>
  <category>workflows</category>
  <guid>https://dylancastillo.co/til/evaluator-optimizer-pydantic-ai.html</guid>
  <pubDate>Wed, 09 Jul 2025 00:00:00 GMT</pubDate>
</item>
<item>
  <title>Parallelization and orchestrator-workers workflows with Pydantic AI</title>
  <dc:creator>Dylan Castillo</dc:creator>
  <link>https://dylancastillo.co/til/parallelization-orchestrator-workers-pydantic-ai.html</link>
  <description><![CDATA[ 




<p>I’ve been re-implementing typical patterns for building <a href="https://dylancastillo.co/til/react-agent-pydantic-ai.html">agentic</a> <a href="https://dylancastillo.co/posts/agentic-workflows-langgraph.html">systems</a> with Pydantic AI. In this post, I’ll explore how to build a <a href="https://www.anthropic.com/engineering/building-effective-agents#workflow-parallelization">parallelization</a> and <a href="https://www.anthropic.com/engineering/building-effective-agents#workflow-orchestrator-worker">orchestrator-worker</a> workflow.</p>
<p>In previous TILs, I’ve explored:</p>
<ul>
<li><a href="https://dylancastillo.co/til/prompt-chaining-pydantic-ai.html">Prompt chaining</a></li>
<li><a href="https://dylancastillo.co/til/routing-pydantic-ai.html">Routing</a></li>
<li><a href="https://dylancastillo.co/til/evaluator-optimizer-pydantic-ai.html">Evaluator-optimizer</a></li>
<li><a href="https://dylancastillo.co/til/react-agent-pydantic-ai.html">ReAct agent</a></li>
</ul>
<p>You can download this notebook <a href="https://github.com/dcastillo/blog/blob/main/til/parallelization-orchestrator-workers-pydantic-ai.ipynb">here</a>.</p>
<section id="what-is-parallelization" class="level2">
<h2 class="anchored" data-anchor-id="what-is-parallelization">What is parallelization?</h2>
<p>This workflow is designed for tasks that can be easily divided into independent subtasks. The key trade-off is managing complexity and coordination overhead in exchange for significant speed improvements or diverse perspectives.</p>
<p>It looks like this:</p>
<div class="cell" data-layout-align="default">
<div class="cell-output-display">
<div>
<p></p><figure class="figure"><p></p>
<div>
<pre class="mermaid mermaid-js">flowchart LR
    In([In]) --&gt; LLM1["LLM Call 1"]
    In --&gt; LLM2["LLM Call 2"]
    In --&gt; LLM3["LLM Call 3"]
    LLM1 --&gt; Aggregator["Aggregator"] 
    LLM2 --&gt; Aggregator["Aggregator"] 
    LLM3 --&gt; Aggregator["Aggregator"] 
    Aggregator --&gt; Out([Out])
</pre>
</div>
<p></p></figure><p></p>
</div>
</div>
</div>
<p><strong>Examples:</strong></p>
<ul>
<li>Evaluate multiple independent aspects of a text (safety, quality, relevance)</li>
<li>Process user query and apply guardrails in parallel</li>
<li>Generate multiple response candidates given a query for comparison</li>
</ul>
</section>
<section id="what-is-orchestrator-worker" class="level2">
<h2 class="anchored" data-anchor-id="what-is-orchestrator-worker">What is orchestrator-worker?</h2>
<p>This workflow works well for tasks where you don’t know the required subtasks beforehand. The subtasks are determined by the orchestrator.</p>
<p>Here’s a diagram:</p>
<div class="cell" data-layout-align="default">
<div class="cell-output-display">
<div>
<p></p><figure class="figure"><p></p>
<div>
<pre class="mermaid mermaid-js">flowchart LR
    In([In]) --&gt; Orch[Orchestrator]

    Orch -.-&gt; LLM1["LLM Call 1"]
    Orch -.-&gt; LLM2["LLM Call 2"]
    Orch -.-&gt; LLM3["LLM Call 3"]

    LLM1 -.-&gt; Synth[Synthesizer]
    LLM2 -.-&gt; Synth
    LLM3 -.-&gt; Synth

    Synth --&gt; Out([Out])

</pre>
</div>
<p></p></figure><p></p>
</div>
</div>
</div>
<p><strong>Examples:</strong></p>
<ul>
<li>Coding tools making changes to multiple files at once</li>
<li>Searching multiple sources and synthesize the results</li>
</ul>
<p>The difference between parallelization and orchestrator-worker is that in parallelization, the subtasks are known beforehand, while in orchestrator-worker, the subtasks are determined by the orchestrator.</p>
</section>
<section id="setup" class="level2">
<h2 class="anchored" data-anchor-id="setup">Setup</h2>
<p>Pydantic AI uses <code>asyncio</code> under the hood, so you’ll need to enable <code>nest_asyncio</code> to run this notebook:</p>
<div id="2a607486" class="cell" data-execution_count="24">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> nest_asyncio</span>
<span id="cb1-2"></span>
<span id="cb1-3">nest_asyncio.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">apply</span>()</span></code></pre></div></div>
</div>
<p>Then, you need to import the required libraries. I’m using <strong><a href="https://logfire.pydantic.dev/">Logfire</a></strong> to monitor the workflow.</p>
<div id="4081c493" class="cell" data-execution_count="25">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb2-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> asyncio</span>
<span id="cb2-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> os</span>
<span id="cb2-3"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> pprint <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> pprint</span>
<span id="cb2-4"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> typing <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> Literal, Optional</span>
<span id="cb2-5"></span>
<span id="cb2-6"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> logfire</span>
<span id="cb2-7"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> requests</span>
<span id="cb2-8"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> dotenv <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> load_dotenv</span>
<span id="cb2-9"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> pydantic <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> BaseModel, Field</span>
<span id="cb2-10"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> pydantic_ai <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> Agent, RunContext</span>
<span id="cb2-11"></span>
<span id="cb2-12">load_dotenv()</span></code></pre></div></div>
<div class="cell-output cell-output-display" data-execution_count="25">
<pre><code>True</code></pre>
</div>
</div>
<p><strong>PydanticAI</strong> is compatible with OpenTelemetry (OTel). So it’s pretty easy to use it with Logfire or with any other OTel-compatible observability tool (e.g., <a href="https://langfuse.com/">Langfuse</a>).</p>
<p>To enable tracking, create a project in Logfire, generate a <code>Write token</code> and add it to the <code>.env</code> file. Then, you just need to run:</p>
<div id="014a1f88" class="cell" data-execution_count="26">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb4-1">logfire.configure(</span>
<span id="cb4-2">    token<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>os.getenv(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"LOGFIRE_TOKEN"</span>),</span>
<span id="cb4-3">)</span>
<span id="cb4-4">logfire.instrument_pydantic_ai()</span></code></pre></div></div>
</div>
<p>The first time you run this, it will ask you to create a project in Logfire. From it, it will generate a <code>logfire_credentials.json</code> file in your working directory. In following runs, it will automatically use the credentials from the file.</p>
</section>
<section id="parallelization-example" class="level2">
<h2 class="anchored" data-anchor-id="parallelization-example">Parallelization example</h2>
<p>In this example, I’ll show you how to build a workflow that runs the same evaluator in parallel and then aggregates the results.</p>
<div id="4a49b93b" class="cell" data-execution_count="27">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb5-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> Evaluation(BaseModel):</span>
<span id="cb5-2">    explanation: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span></span>
<span id="cb5-3">    is_appropiate: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">bool</span></span>
<span id="cb5-4"></span>
<span id="cb5-5"></span>
<span id="cb5-6"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> AggregatedResults(BaseModel):</span>
<span id="cb5-7">    summary: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span></span>
<span id="cb5-8">    is_appropiate: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">bool</span></span></code></pre></div></div>
</div>
<p>Then you can create the agents and encapsulate the logic in a function:</p>
<div id="3db5d8a9" class="cell" data-execution_count="28">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb6-1">evaluator <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Agent(</span>
<span id="cb6-2">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"openai:gpt-4.1-mini"</span>,</span>
<span id="cb6-3">    output_type<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>Evaluation,</span>
<span id="cb6-4">    system_prompt<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(</span>
<span id="cb6-5">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"You are an expert evaluator. Provided with a text, you will evaluate if it's appropriate for a general audience."</span></span>
<span id="cb6-6">    ),</span>
<span id="cb6-7">)</span>
<span id="cb6-8"></span>
<span id="cb6-9">aggregator <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Agent(</span>
<span id="cb6-10">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"openai:gpt-4.1-mini"</span>,</span>
<span id="cb6-11">    output_type<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>AggregatedResults,</span>
<span id="cb6-12">    system_prompt<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(</span>
<span id="cb6-13">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"You are an expert evaluator. Provided with a list of evaluations, you will summarize them and provide a final evaluation."</span></span>
<span id="cb6-14">    ),</span>
<span id="cb6-15">)</span>
<span id="cb6-16"></span>
<span id="cb6-17"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@logfire.instrument</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Run workflow"</span>)</span>
<span id="cb6-18"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">async</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> run_workflow(topic: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>:</span>
<span id="cb6-19">    tasks <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [evaluator.run(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Evaluate the following text: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>topic<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> _ <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>)]</span>
<span id="cb6-20">    evaluations <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">await</span> asyncio.gather(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>tasks)</span>
<span id="cb6-21">    aggregated_results <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">await</span> aggregator.run(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Summarize the following evaluations:</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n\n</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>[(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">eval</span>.output.explanation, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">eval</span>.output.is_appropiate) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">eval</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> evaluations]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb6-22">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> aggregated_results.output</span>
<span id="cb6-23"></span>
<span id="cb6-24">output <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">await</span> run_workflow(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Athletes should consume enhancing drugs to improve their performance."</span>)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>15:28:36.289 Run workflow
15:28:36.290   agent run
15:28:36.290     chat gpt-4.1-mini
15:28:36.291   agent run
15:28:36.291     chat gpt-4.1-mini
15:28:36.292   agent run
15:28:36.292     chat gpt-4.1-mini</code></pre>
</div>
<div class="cell-output cell-output-display">
<pre style="white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace"><span style="font-weight: bold">Logfire</span> project URL: <a href="https://logfire-us.pydantic.dev/dylanjcastillo/blog" target="_blank"><span style="color: #008080; text-decoration-color: #008080; text-decoration: underline">https://logfire-us.pydantic.dev/dylanjcastillo/blog</span></a>
</pre>
</div>
<div class="cell-output cell-output-stdout">
<pre><code>15:28:39.380   aggregator run
15:28:39.380     chat gpt-4.1-mini</code></pre>
</div>
</div>
<p>Finally, you can run the workflow. You should get an output like this:</p>
<div id="48f317fe" class="cell" data-execution_count="29">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb9-1">pprint(output.model_dump())</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>{'is_appropiate': False,
 'summary': 'All evaluations agree that the text promotes the consumption of '
            'performance-enhancing drugs by athletes, which is a sensitive and '
            'controversial topic. The main concerns highlighted are health '
            'risks, ethical issues, fairness in sports, and legality. The '
            'evaluations consistently indicate that encouraging or normalizing '
            'the use of such drugs is inappropriate for a general audience as '
            'it may promote illegal, harmful, or unsafe behavior. There is '
            'consensus that the subject should be handled with caution.'}</code></pre>
</div>
</div>
</section>
<section id="orchestrator-workers-example" class="level2">
<h2 class="anchored" data-anchor-id="orchestrator-workers-example">Orchestrator-workers example</h2>
<p>In this example, I’ll show you how to build a workflow that given a topic generates a table of contents, then writes each section of the article by making an individual request to an LLM.</p>
<p>First, you must define the data structures we’ll use for the workflow outputs.</p>
<div id="65b82dff" class="cell" data-execution_count="30">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb11" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb11-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> Section(BaseModel):</span>
<span id="cb11-2">    name: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Field(description<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"The name of the section"</span>)</span>
<span id="cb11-3">    description: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Field(description<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"The description of the section"</span>)</span>
<span id="cb11-4"></span>
<span id="cb11-5"></span>
<span id="cb11-6"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> CompletedSection(BaseModel):</span>
<span id="cb11-7">    name: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Field(description<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"The name of the section"</span>)</span>
<span id="cb11-8">    content: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Field(description<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"The content of the section"</span>)</span>
<span id="cb11-9"></span>
<span id="cb11-10"></span>
<span id="cb11-11"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> Sections(BaseModel):</span>
<span id="cb11-12">    sections: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[Section] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Field(description<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"The sections of the article"</span>)</span></code></pre></div></div>
</div>
<p>Then, we’ll define the agents we’ll use in the workflow.</p>
<div id="797497f4" class="cell" data-execution_count="31">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb12" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb12-1">orchestrator <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Agent(</span>
<span id="cb12-2">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"openai:gpt-4.1-mini"</span>,</span>
<span id="cb12-3">    output_type<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>Sections,</span>
<span id="cb12-4">    system_prompt<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(</span>
<span id="cb12-5">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"You are an expert writer specialized in SEO. Provided with a topic, you will generate the sections for a short article."</span></span>
<span id="cb12-6">    ),</span>
<span id="cb12-7">)</span>
<span id="cb12-8"></span>
<span id="cb12-9">worker <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Agent(</span>
<span id="cb12-10">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"openai:gpt-4.1-mini"</span>,</span>
<span id="cb12-11">    output_type<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>CompletedSection,</span>
<span id="cb12-12">    system_prompt<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(</span>
<span id="cb12-13">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"You are an expert writer specialized in SEO. Provided with a topic and a section, you will generate the content of the section."</span></span>
<span id="cb12-14">    ),</span>
<span id="cb12-15">)</span>
<span id="cb12-16"></span>
<span id="cb12-17"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> synthesizer(sections: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[CompletedSection]) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>:</span>
<span id="cb12-18">    completed_sections_str <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n\n</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>.join(</span>
<span id="cb12-19">        [section.content <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> section <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> sections]</span>
<span id="cb12-20">    )</span>
<span id="cb12-21">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> completed_sections_str</span></code></pre></div></div>
</div>
<p>Then, you can define a function that orchestrates the workflow:</p>
<div id="71597e02" class="cell" data-execution_count="39">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb13" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb13-1"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@logfire.instrument</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Run workflow"</span>)</span>
<span id="cb13-2"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">async</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> run_workflow(topic: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>:</span>
<span id="cb13-3">    orchestrator_output <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">await</span> orchestrator.run(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Generate the sections for a short article about </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>topic<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb13-4">    tasks <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [worker.run(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Write the section </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>section<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>name<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> about </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>topic<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> with the following description: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>section<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>description<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> section <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> orchestrator_output.output.sections]</span>
<span id="cb13-5">    completed_sections <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">await</span> asyncio.gather(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>tasks)</span>
<span id="cb13-6">    full_article <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> synthesizer([c.output <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> c <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> completed_sections])</span>
<span id="cb13-7">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> full_article</span>
<span id="cb13-8"></span>
<span id="cb13-9">output <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">await</span> run_workflow(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Artificial Intelligence"</span>)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>15:32:13.934 Run workflow
15:32:13.936   orchestrator run
15:32:13.937     chat gpt-4.1-mini
15:32:18.567   agent run
15:32:18.568     chat gpt-4.1-mini
15:32:18.569   agent run
15:32:18.569     chat gpt-4.1-mini
15:32:18.570   agent run
15:32:18.571     chat gpt-4.1-mini
15:32:18.572   agent run
15:32:18.572     chat gpt-4.1-mini
15:32:18.573   agent run
15:32:18.573     chat gpt-4.1-mini</code></pre>
</div>
</div>
<p>That’s all!</p>
<p>If you want to see the full code, you can download the notebook <a href="https://github.com/dcastillo/blog/blob/main/til/parallelization-orchestrator-workers-pydantic-ai.ipynb">here</a>.</p>


</section>

<div id="quarto-appendix" class="default"><section class="quarto-appendix-contents" id="quarto-citation"><h2 class="anchored quarto-appendix-heading">Citation</h2><div><div class="quarto-appendix-secondary-label">BibTeX citation:</div><pre class="sourceCode code-with-copy quarto-appendix-bibtex"><code class="sourceCode bibtex">@online{castillo2025,
  author = {Castillo, Dylan},
  title = {Parallelization and Orchestrator-Workers Workflows with
    {Pydantic} {AI}},
  date = {2025-07-09},
  url = {https://dylancastillo.co/til/parallelization-orchestrator-workers-pydantic-ai.html},
  langid = {en}
}
</code></pre><div class="quarto-appendix-secondary-label">For attribution, please cite this work as:</div><div id="ref-castillo2025" class="csl-entry quarto-appendix-citeas">
Castillo, Dylan. 2025. <span>“Parallelization and Orchestrator-Workers
Workflows with Pydantic AI.”</span> July 9. <a href="https://dylancastillo.co/til/parallelization-orchestrator-workers-pydantic-ai.html">https://dylancastillo.co/til/parallelization-orchestrator-workers-pydantic-ai.html</a>.
</div></div></section></div> ]]></description>
  <category>til</category>
  <category>llm</category>
  <category>pydantic-ai</category>
  <category>workflows</category>
  <guid>https://dylancastillo.co/til/parallelization-orchestrator-workers-pydantic-ai.html</guid>
  <pubDate>Wed, 09 Jul 2025 00:00:00 GMT</pubDate>
</item>
<item>
  <title>Prompt chaining workflow with Pydantic AI</title>
  <dc:creator>Dylan Castillo</dc:creator>
  <link>https://dylancastillo.co/til/prompt-chaining-pydantic-ai.html</link>
  <description><![CDATA[ 




<p>To get more familiar with Pydantic AI, I’ve been re-implementing typical patterns for building <a href="https://dylancastillo.co/til/react-agent-pydantic-ai.html">agentic</a> <a href="https://dylancastillo.co/posts/agentic-workflows-langgraph.html">systems</a>.</p>
<p>In this post, I’ll explore how to build a <a href="https://www.anthropic.com/engineering/building-effective-agents#workflow-prompt-chaining">prompt chaining</a>. I won’t cover the basics of agentic workflows, so if you’re not familiar with the concept, I recommend you to read <a href="https://dylancastillo.co/posts/agentic-workflows-langgraph.html">this post</a> first.</p>
<p>I’ve also written other TILs about Pydantic AI: - <a href="https://dylancastillo.co/til/routing-pydantic-ai.html">Routing</a> - <a href="https://dylancastillo.co/til/evaluator-optimizer-pydantic-ai.html">Evaluator-optimizer</a> - <a href="https://dylancastillo.co/til/react-agent-pydantic-ai.html">ReAct agent</a> - <a href="https://dylancastillo.co/til/parallelization-orchestrator-workers-pydantic-ai.html">Parallelization and Orchestrator-workers</a></p>
<p>You can download this notebook <a href="https://github.com/dcastillo/blog/blob/main/til/prompt-chaining-pydantic-ai.ipynb">here</a>.</p>
<section id="what-is-prompt-chaining" class="level2">
<h2 class="anchored" data-anchor-id="what-is-prompt-chaining">What is prompt chaining?</h2>
<p>Prompt chaining is a workflow pattern that splits a complex task into multiple subtasks. This gives you better results, but at the cost of longer completion times (higher latency).</p>
<p>It looks like this:</p>
<div class="cell" data-layout-align="default">
<div class="cell-output-display">
<div>
<p></p><figure class="figure"><p></p>
<div>
<pre class="mermaid mermaid-js">flowchart LR
    In --&gt; LLM1["LLM Call 1"]
    LLM1 -- "Output 1" --&gt; Gate{Gate}
    Gate -- Pass --&gt; LLM2["LLM Call 2"]
    Gate -- Fail --&gt; Exit[Exit]
    LLM2 -- "Output 2" --&gt; LLM3["LLM Call 3"]
    LLM3 --&gt; Out
</pre>
</div>
<p></p></figure><p></p>
</div>
</div>
</div>
<p><strong>Examples:</strong></p>
<ul>
<li>Generating content in a pipeline by generating table of contents, content, revisions, translations, etc.</li>
<li>Generating a text through a multi-step process to evaluate if it matches certain criteria</li>
</ul>
<p>Let’s get to it!</p>
</section>
<section id="setup" class="level2">
<h2 class="anchored" data-anchor-id="setup">Setup</h2>
<p>I went with a simple example to implement a content generation workflow composed of three steps:</p>
<ol type="1">
<li>Generate a table of contents for the article</li>
<li>Generate the content of the article</li>
<li>Update the content of the article if it’s too long</li>
</ol>
<p>Because Pydantic AI uses <code>asyncio</code> under the hood, you need to enable <code>nest_asyncio</code> to use it in a notebook:</p>
<div id="2a607486" class="cell" data-execution_count="2">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> nest_asyncio</span>
<span id="cb1-2"></span>
<span id="cb1-3">nest_asyncio.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">apply</span>()</span></code></pre></div></div>
</div>
<p>Then, you need to import the required libraries. <strong><a href="https://logfire.pydantic.dev/">Logfire</a></strong> is part of the Pydantic ecosystem, so I thought it’d be good to use it for observability.</p>
<div id="4081c493" class="cell" data-execution_count="18">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb2-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> os</span>
<span id="cb2-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> typing <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> Literal</span>
<span id="cb2-3"></span>
<span id="cb2-4"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> logfire</span>
<span id="cb2-5"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> requests</span>
<span id="cb2-6"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> dotenv <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> load_dotenv</span>
<span id="cb2-7"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> pydantic_ai <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> Agent, RunContext</span>
<span id="cb2-8"></span>
<span id="cb2-9">load_dotenv()</span></code></pre></div></div>
<div class="cell-output cell-output-display" data-execution_count="18">
<pre><code>True</code></pre>
</div>
</div>
<p><strong>PydanticAI</strong> is compatible with OpenTelemetry (OTel). So it’s pretty easy to use it with Logfire or with any other OTel-compatible observability tool (e.g., <a href="https://langfuse.com/">Langfuse</a>).</p>
<p>To enable tracking, create a project in Logfire, generate a <code>Write token</code> and add it to the <code>.env</code> file. Then, you just need to run:</p>
<div id="014a1f88" class="cell" data-execution_count="4">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb4-1">logfire.configure(</span>
<span id="cb4-2">    token<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>os.getenv(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'LOGFIRE_TOKEN'</span>),</span>
<span id="cb4-3">)</span>
<span id="cb4-4">logfire.instrument_pydantic_ai()</span></code></pre></div></div>
<div class="cell-output cell-output-stderr">
<div class="ansi-escaped-output">
<pre><span class="ansi-bold">Logfire</span> project URL: ]8;id=13458;https://logfire-us.pydantic.dev/dylanjcastillo/blog\<span style="text-decoration:underline" class="ansi-cyan-fg">https://logfire-us.pydantic.dev/dylanjcastillo/blog</span>]8;;\
</pre>
</div>
</div>
</div>
<p>The first time you run this, it will ask you to create a project in Logfire. From it, it will generate a <code>logfire_credentials.json</code> file in your working directory. In following runs, it will automatically use the credentials from the file.</p>
</section>
<section id="prompt-chaining-workflow" class="level2 page-columns page-full">
<h2 class="anchored" data-anchor-id="prompt-chaining-workflow">Prompt chaining workflow</h2>
<p>As mentioned before, the workflow is composed of three steps: generate a table of contents, generate the content of the article and update the content if it’s too long.</p>
<p>So I created three <code>Agent</code> instances. Each one takes care of one of the steps.</p>
<p>Here’s the code:</p>
<div id="97be7efe" class="cell" data-execution_count="23">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb5-1">toc_agent <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Agent(</span>
<span id="cb5-2">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'openai:gpt-4.1-mini'</span>,</span>
<span id="cb5-3">    system_prompt<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(</span>
<span id="cb5-4">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"You are an expert writer specialized in SEO. Provided with a topic, you will generate the table of contents for a short article."</span></span>
<span id="cb5-5">    ),</span>
<span id="cb5-6">)</span>
<span id="cb5-7"></span>
<span id="cb5-8">article_agent <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Agent(</span>
<span id="cb5-9">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'openai:gpt-4.1-mini'</span>,</span>
<span id="cb5-10">    system_prompt<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(</span>
<span id="cb5-11">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"You are an expert writer specialized in SEO. Provided with a topic and a table of contents, you will generate the content of the article."</span></span>
<span id="cb5-12">    ),</span>
<span id="cb5-13">)</span>
<span id="cb5-14"></span>
<span id="cb5-15">editor_agent <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Agent(</span>
<span id="cb5-16">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'openai:gpt-4.1-mini'</span>,</span>
<span id="cb5-17">    system_prompt<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(</span>
<span id="cb5-18">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"You are an expert writer specialized in SEO. Provided with a topic, a table of contents and a content, you will revise the content of the article to make it less than 1000 characters."</span></span>
<span id="cb5-19">    ),</span>
<span id="cb5-20">)</span>
<span id="cb5-21"></span>
<span id="cb5-22"></span>
<span id="cb5-23"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@logfire.instrument</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Run workflow"</span>)</span>
<span id="cb5-24"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> run_workflow(topic: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>:</span>
<span id="cb5-25">    toc <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> toc_agent.run_sync(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Generate the table of contents of an article about </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>topic<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb5-26">    content <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> article_agent.run_sync(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Generate the content of an article about </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>topic<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> with the following table of contents: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>toc<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>output<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb5-27">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(content.output) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1000</span>:</span>
<span id="cb5-28">        revised_content <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> editor_agent.run_sync(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Revise the content of an article about </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>topic<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> with the following table of contents: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>toc<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>output<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> and the following content: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>content<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>output<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb5-29">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> revised_content.output</span>
<span id="cb5-30">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> content.output</span>
<span id="cb5-31"></span>
<span id="cb5-32">output <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> run_workflow(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Artificial Intelligence"</span>)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>18:54:00.987 Run workflow
18:54:00.988   toc_agent run
18:54:00.989     chat gpt-4.1-mini
18:54:02.911   article_agent run
18:54:02.911     chat gpt-4.1-mini
18:54:18.621   editor_agent run
18:54:18.622     chat gpt-4.1-mini</code></pre>
</div>
</div>
<p>This code creates the agents and puts them together in a workflow. I used <code>@logfire.instrument</code> to make sure all the traces related to the workflow are logged within the same span. See example below:</p>
<div class="quarto-figure quarto-figure-center page-columns page-full">
<figure class="figure page-columns page-full">
<p><a href="./images/prompt-chaining-pydantic-ai.png" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Prompt chaining workflow"><img src="https://dylancastillo.co/til/images/prompt-chaining-pydantic-ai.png" class="img-fluid figure-img" alt="Prompt chaining workflow"></a></p>
<figcaption class="margin-caption">Prompt chaining workflow</figcaption>
</figure>
</div>
<p>And here’s the output:</p>
<div id="1ee5bd83" class="cell" data-execution_count="24">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb7-1"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(output)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>Artificial Intelligence (AI) simulates human intelligence in machines capable of learning, problem-solving, and decision-making. Originating as a formal discipline in the 1950s, AI evolved from rule-based systems to advanced machine learning and deep learning, now integral to daily life. AI types include Narrow AI for specific tasks, General AI with human-level cognition, and theoretical Superintelligent AI. Key technologies include machine learning, deep learning, natural language processing, and computer vision. AI transforms sectors like healthcare, finance, transportation, and education by automating tasks and improving decisions. Benefits include increased efficiency and innovation, while challenges involve data privacy, bias, job displacement, and transparency. Future trends highlight explainable AI, edge computing, and human-AI collaboration. Ethical concerns focus on accountability, fairness, and user privacy. Responsible AI development promises a transformative, inclusive future.</code></pre>
</div>
</div>
<p>That’s all!</p>
<p>You can access this notebook <a href="https://github.com/dylanjcastillo/blog/tree/main/til/prompt-chaining-pydantic-ai.ipynb">here</a>.</p>
<p>If you have any questions or feedback, please let me know in the comments below.</p>


</section>

<div id="quarto-appendix" class="default"><section class="quarto-appendix-contents" id="quarto-citation"><h2 class="anchored quarto-appendix-heading">Citation</h2><div><div class="quarto-appendix-secondary-label">BibTeX citation:</div><pre class="sourceCode code-with-copy quarto-appendix-bibtex"><code class="sourceCode bibtex">@online{castillo2025,
  author = {Castillo, Dylan},
  title = {Prompt Chaining Workflow with {Pydantic} {AI}},
  date = {2025-07-08},
  url = {https://dylancastillo.co/til/prompt-chaining-pydantic-ai.html},
  langid = {en}
}
</code></pre><div class="quarto-appendix-secondary-label">For attribution, please cite this work as:</div><div id="ref-castillo2025" class="csl-entry quarto-appendix-citeas">
Castillo, Dylan. 2025. <span>“Prompt Chaining Workflow with Pydantic
AI.”</span> July 8. <a href="https://dylancastillo.co/til/prompt-chaining-pydantic-ai.html">https://dylancastillo.co/til/prompt-chaining-pydantic-ai.html</a>.
</div></div></section></div> ]]></description>
  <category>til</category>
  <category>llm</category>
  <category>pydantic-ai</category>
  <category>workflows</category>
  <guid>https://dylancastillo.co/til/prompt-chaining-pydantic-ai.html</guid>
  <pubDate>Tue, 08 Jul 2025 00:00:00 GMT</pubDate>
</item>
<item>
  <title>Routing workflow with Pydantic AI</title>
  <dc:creator>Dylan Castillo</dc:creator>
  <link>https://dylancastillo.co/til/routing-pydantic-ai.html</link>
  <description><![CDATA[ 




<p>I’m trying to get more familiar with Pydantic AI, so I’ve been re-implementing typical patterns for building <a href="https://dylancastillo.co/til/react-agent-pydantic-ai.html">agentic</a> <a href="https://dylancastillo.co/posts/agentic-workflows-langgraph.html">systems</a>.</p>
<p>In this post, I’ll build a <a href="https://www.anthropic.com/engineering/building-effective-agents#workflow-routing">routing</a> workflow. I won’t cover the basics of agentic workflows, so if you’re not familiar with the concept, I recommend you to read <a href="https://dylancastillo.co/posts/agentic-workflows-langgraph.html">this post</a> first.</p>
<p>I’ve also written other TILs about Pydantic AI:</p>
<ul>
<li><a href="https://dylancastillo.co/til/prompt-chaining-pydantic-ai.html">Prompt chaining</a></li>
<li><a href="https://dylancastillo.co/til/react-agent-pydantic-ai.html">ReAct agent</a></li>
<li><a href="https://dylancastillo.co/til/evaluator-optimizer-pydantic-ai.html">Evaluator-optimizer</a></li>
<li><a href="https://dylancastillo.co/til/parallelization-orchestrator-workers-pydantic-ai.html">Parallelization and Orchestrator-workers</a></li>
</ul>
<p>You can download this notebook <a href="https://github.com/dcastillo/blog/blob/main/til/routing-pydantic-ai.ipynb">here</a>.</p>
<section id="what-is-router" class="level2">
<h2 class="anchored" data-anchor-id="what-is-router">What is router?</h2>
<p>Routing is a workflow pattern that takes the input, classifies it and then sends it to the right place for the best handling. This process can be managed by an LLM or a traditional classification model. It makes sense to use when a system needs to apply different logic to different types of queries.</p>
<p>It looks like this:</p>
<div class="cell" data-layout-align="default">
<div class="cell-output-display">
<div>
<p></p><figure class="figure"><p></p>
<div>
<pre class="mermaid mermaid-js">flowchart LR 
    In([In]) --&gt; Router["LLM Call Router"]

    Router --&gt;|Route 1| LLM1["LLM Call 1"]
    Router --&gt;|Route 2| LLM2["LLM Call 2"]
    Router --&gt;|Route 3| LLM3["LLM Call 3"]

    LLM1 --&gt; Out([Out])
    LLM2 --&gt; Out
    LLM3 --&gt; Out

</pre>
</div>
<p></p></figure><p></p>
</div>
</div>
</div>
<p><strong>Examples:</strong></p>
<ul>
<li>Classify complexity of question and adjust model depending on it</li>
<li>Classify type of query and use specialized tools (e.g., indexes, prompts)</li>
</ul>
<p>Let’s see how this looks like in code.</p>
</section>
<section id="setup" class="level2">
<h2 class="anchored" data-anchor-id="setup">Setup</h2>
<p>I will implement a workflow that will take a query from a user and will route it to the appropriate agent.</p>
<p>There will be three agents in the workflow:</p>
<ul>
<li><code>Agent TOC</code>: Generate a table of contents for the article</li>
<li><code>Agent Writer</code>: Generate the content of the article</li>
<li><code>Agent Editor</code>: Update the content of the article if it’s too long</li>
</ul>
<p>Because Pydantic AI uses <code>asyncio</code> under the hood, you need to enable <code>nest_asyncio</code> to use it in a notebook:</p>
<div id="2a607486" class="cell" data-execution_count="1">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> nest_asyncio</span>
<span id="cb1-2"></span>
<span id="cb1-3">nest_asyncio.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">apply</span>()</span></code></pre></div></div>
</div>
<p>Then, you need to import the required libraries. <strong><a href="https://logfire.pydantic.dev/">Logfire</a></strong> is part of the Pydantic ecosystem, so I thought it’d be good to use it for observability.</p>
<div id="4081c493" class="cell" data-execution_count="2">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb2-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> os</span>
<span id="cb2-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> typing <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> Literal</span>
<span id="cb2-3"></span>
<span id="cb2-4"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> logfire</span>
<span id="cb2-5"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> requests</span>
<span id="cb2-6"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> dotenv <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> load_dotenv</span>
<span id="cb2-7"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> pydantic <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> BaseModel</span>
<span id="cb2-8"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> pydantic_ai <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> Agent, RunContext</span>
<span id="cb2-9"></span>
<span id="cb2-10">load_dotenv()</span></code></pre></div></div>
<div class="cell-output cell-output-display" data-execution_count="2">
<pre><code>True</code></pre>
</div>
</div>
<p><strong>PydanticAI</strong> is compatible with OpenTelemetry (OTel). It’s straightforward to use it with Logfire or with any other OTel-compatible observability tool (e.g., <a href="https://langfuse.com/">Langfuse</a>).</p>
<p>To enable tracking, create a project in Logfire, generate a <code>Write token</code> and add it to the <code>.env</code> file. Then, you just need to run:</p>
<div id="014a1f88" class="cell" data-execution_count="3">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb4-1">logfire.configure(</span>
<span id="cb4-2">    token<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>os.getenv(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"LOGFIRE_TOKEN"</span>),</span>
<span id="cb4-3">)</span>
<span id="cb4-4">logfire.instrument_pydantic_ai()</span></code></pre></div></div>
</div>
<p>The first time you run this, it will ask you to create a project in Logfire. From it, it will generate a <code>logfire_credentials.json</code> file in your working directory. In following runs, it will automatically use the credentials from the file.</p>
</section>
<section id="prompt-chaining-workflow" class="level2">
<h2 class="anchored" data-anchor-id="prompt-chaining-workflow">Prompt chaining workflow</h2>
<p>As mentioned before, the workflow will be composed of three agents. So I created three <code>Agent</code> instances. Each one takes care of one of the tasks</p>
<p>Here’s the code:</p>
<div id="4358c272" class="cell" data-execution_count="4">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb5-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> RouterOutput(BaseModel):</span>
<span id="cb5-2">    category: Literal[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"write_article"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"generate_table_of_contents"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"review_article"</span>]</span>
<span id="cb5-3"></span>
<span id="cb5-4"></span>
<span id="cb5-5">router_agent <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Agent(</span>
<span id="cb5-6">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"openai:gpt-4.1-mini"</span>,</span>
<span id="cb5-7">    system_prompt<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(</span>
<span id="cb5-8">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"You are a helpful assistant. You will classify the message into one of the following categories: 'write_article', 'generate_table_of_contents', 'review_article'."</span></span>
<span id="cb5-9">    ),</span>
<span id="cb5-10">    output_type<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>RouterOutput,</span>
<span id="cb5-11">)</span>
<span id="cb5-12"></span>
<span id="cb5-13">agent_writer <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Agent(</span>
<span id="cb5-14">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"openai:gpt-4.1-mini"</span>,</span>
<span id="cb5-15">    system_prompt<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(</span>
<span id="cb5-16">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"You are a writer. You will write an article about the topic provided."</span></span>
<span id="cb5-17">    ),</span>
<span id="cb5-18">)</span>
<span id="cb5-19"></span>
<span id="cb5-20">agent_toc <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Agent(</span>
<span id="cb5-21">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"openai:gpt-4.1-mini"</span>,</span>
<span id="cb5-22">    system_prompt<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(</span>
<span id="cb5-23">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"You are an expert writer specialized in SEO. Provided with a topic, you will generate the table of contents for a short article."</span></span>
<span id="cb5-24">    ),</span>
<span id="cb5-25">)</span>
<span id="cb5-26"></span>
<span id="cb5-27">agent_reviewer <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Agent(</span>
<span id="cb5-28">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"openai:gpt-4.1-mini"</span>,</span>
<span id="cb5-29">    system_prompt<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(</span>
<span id="cb5-30">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"You are a writer. You will review the article for the topic provided."</span></span>
<span id="cb5-31">    ),</span>
<span id="cb5-32">)</span></code></pre></div></div>
<div class="cell-output cell-output-display">
<pre style="white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace"><span style="font-weight: bold">Logfire</span> project URL: <a href="https://logfire-us.pydantic.dev/dylanjcastillo/blog" target="_blank"><span style="color: #008080; text-decoration-color: #008080; text-decoration: underline">https://logfire-us.pydantic.dev/dylanjcastillo/blog</span></a>
</pre>
</div>
</div>
<div id="97be7efe" class="cell" data-execution_count="5">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb6-1"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@logfire.instrument</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Run workflow"</span>)</span>
<span id="cb6-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> run_workflow(topic: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>:</span>
<span id="cb6-3">    router_output <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> router_agent.run_sync(</span>
<span id="cb6-4">        <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Classify the message: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>topic<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb6-5">    )</span>
<span id="cb6-6">    category <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> router_output.output.category </span>
<span id="cb6-7">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> category <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"write_article"</span>:</span>
<span id="cb6-8">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> agent_writer.run_sync(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Write an article about </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>topic<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>).output</span>
<span id="cb6-9">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">elif</span> category <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"generate_table_of_contents"</span>:</span>
<span id="cb6-10">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> agent_toc.run_sync(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Generate the table of contents of an article about </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>topic<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>).output</span>
<span id="cb6-11">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span>:</span>
<span id="cb6-12">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> agent_reviewer.run_sync(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Review the article for the topic </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>topic<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>).output</span></code></pre></div></div>
</div>
<p>You can run the workflow and it will route your message and use the appropriate agent. For example, try to generate a table of contents for an article about AI:</p>
<div id="10fa652a" class="cell" data-execution_count="6">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb7-1">toc <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> run_workflow(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Generate a table of contents for an article about AI"</span>)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>20:08:05.547 Run workflow
20:08:05.548   router_agent run
20:08:05.549     chat gpt-4.1-mini
20:08:06.810   agent_toc run
20:08:06.811     chat gpt-4.1-mini</code></pre>
</div>
</div>
<div id="ff4cb977" class="cell" data-execution_count="7">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb9-1"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(toc)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>Table of Contents

1. Introduction to Artificial Intelligence  
2. History and Evolution of AI  
3. Types of Artificial Intelligence  
4. Key Technologies Behind AI  
5. Applications of AI in Various Industries  
6. Benefits and Challenges of AI  
7. Future Trends in Artificial Intelligence  
8. Ethical Considerations in AI Development  
9. Conclusion</code></pre>
</div>
</div>
<p>Or, ask the workflow to review a social media post.</p>
<div id="49b09dcf" class="cell" data-execution_count="8">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb11" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb11-1">review <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> run_workflow(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Review this post: 'There are times where there's no time, so you don't have time to write an article about it.'"</span>)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>20:08:08.917 Run workflow
20:08:08.918   router_agent run
20:08:08.919     chat gpt-4.1-mini
20:08:09.691   agent_reviewer run
20:08:09.692     chat gpt-4.1-mini</code></pre>
</div>
</div>
<div id="a039b19c" class="cell" data-execution_count="9">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb13" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb13-1"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(review)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>The post "There are times where there's no time, so you don't have time to write an article about it." offers a succinct reflection on the challenges of time constraints, especially in tasks like writing. Its brevity captures the irony of not having enough time to address a situation—in this case, the lack of time itself. The message resonates with anyone who has felt overwhelmed by deadlines or competing priorities.

However, as a piece intended for a broader audience or a formal article, it could benefit from expansion. Elaborating on scenarios where time scarcity impacts productivity, or providing strategies for managing pressing tasks despite limited time, would add depth and practical value. Additionally, refining the sentence for clarity and flow could enhance its impact—for example: "Sometimes, we're so pressed for time that we can't even write about the very pressure we're under."

In summary, the post effectively conveys a common frustration with time limitations in a clever and relatable way but serves better as a starting point for a more detailed discussion rather than a standalone article.</code></pre>
</div>
</div>
<p>That’s all!</p>
<p>You can access this notebook <a href="https://github.com/dylanjcastillo/blog/tree/main/til/router-pydantic-ai.ipynb">here</a>.</p>
<p>If you have any questions or feedback, please let me know in the comments below.</p>


</section>

<div id="quarto-appendix" class="default"><section class="quarto-appendix-contents" id="quarto-citation"><h2 class="anchored quarto-appendix-heading">Citation</h2><div><div class="quarto-appendix-secondary-label">BibTeX citation:</div><pre class="sourceCode code-with-copy quarto-appendix-bibtex"><code class="sourceCode bibtex">@online{castillo2025,
  author = {Castillo, Dylan},
  title = {Routing Workflow with {Pydantic} {AI}},
  date = {2025-07-08},
  url = {https://dylancastillo.co/til/routing-pydantic-ai.html},
  langid = {en}
}
</code></pre><div class="quarto-appendix-secondary-label">For attribution, please cite this work as:</div><div id="ref-castillo2025" class="csl-entry quarto-appendix-citeas">
Castillo, Dylan. 2025. <span>“Routing Workflow with Pydantic AI.”</span>
July 8. <a href="https://dylancastillo.co/til/routing-pydantic-ai.html">https://dylancastillo.co/til/routing-pydantic-ai.html</a>.
</div></div></section></div> ]]></description>
  <category>til</category>
  <category>llm</category>
  <category>pydantic-ai</category>
  <category>workflows</category>
  <guid>https://dylancastillo.co/til/routing-pydantic-ai.html</guid>
  <pubDate>Tue, 08 Jul 2025 00:00:00 GMT</pubDate>
</item>
<item>
  <title>Building ReAct agents with (and without) LangGraph</title>
  <dc:creator>Dylan Castillo</dc:creator>
  <link>https://dylancastillo.co/posts/react-agent-langgraph.html</link>
  <description><![CDATA[ 




<p>As Large Language Models (LLMs) have become more powerful, I’ve started to see increasing interest from clients in building agents.</p>
<p>The problem is that most of the use cases clients have in mind for agents are better suited for <a href="https://dylancastillo.co/posts/agentic-workflows-langgraph.html">agentic workflows</a>. Agents are a good fit for tasks without a predefined path and where the order of steps is not known beforehand. Agentic workflows, on the other hand, are the right choice for tasks where both of these things are known.</p>
<p>Nonetheless, agents are still a good fit for many use cases, such as coding assistants and support agents. You should definitely spend some time learning about them.</p>
<p>In this post, I’ll show you how to build a Reasoning and Acting (ReAct) agent with (and without) LangGraph.</p>
<p>Let’s start by defining some key concepts.</p>
<section id="what-is-an-agent" class="level2">
<h2 class="anchored" data-anchor-id="what-is-an-agent">What is an agent?</h2>
<p>The biggest players in the ecosystem have converged on similar definitions of what constitutes an “agent.” <a href="https://www.anthropic.com/engineering/building-effective-agents">Anthropic</a> describes them as systems where LLMs “dynamically direct their own processes and tool usage,” while <a href="https://openai.com/index/new-tools-for-building-agents/">OpenAI</a> calls them “systems that independently accomplish tasks on behalf of users.” <a href="https://blog.langchain.com/what-is-an-agent/">LangChain</a> similarly defines them as systems using an LLM to “decide the control flow of an application.”</p>
<p>In essence, agents are systems that can independently make decisions, use tools, take actions, and pursue a goal without direct human guidance. The most well-known agent implementation are <em>ReAct Agents</em>.</p>
</section>
<section id="whats-a-react-agent" class="level2">
<h2 class="anchored" data-anchor-id="whats-a-react-agent">What’s a ReAct agent?</h2>
<p><a href="https://arxiv.org/abs/2210.03629">ReAct (Reasoning and Acting) Agents</a> are AI systems that merge the reasoning of Large Language Models (LLMs) with the ability to perform actions. They follow an iterative “think, act, observe” cycle to solve problems and achieve user goals. For example, a ReAct agent would:</p>
<ol type="1">
<li>Take a user query.</li>
<li>Think about the query and decide on an action.</li>
<li>Execute the action using available tools (environment).</li>
<li>Analyzes the result of that action (environment).</li>
<li>Continues the “Reason, Act, Observe” loop until it reaches the final answer.</li>
</ol>
<p>Here’s a diagram of a ReAct agent:</p>
<div class="cell" data-layout-align="default">
<div class="cell-output-display">
<div>
<p></p><figure class="figure"><p></p>
<div>
<pre class="mermaid mermaid-js">graph LR
    Human &lt;--&gt; LLM[LLM]
    LLM --&gt;|Action| Environment
    Environment --&gt;|Feedback| LLM
    LLM -.-&gt; Stop
</pre>
</div>
<p></p></figure><p></p>
</div>
</div>
</div>
<p>The first generation of ReAct agents used a prompt technique of “Thought, Action, Observation”. Current agents rely on <a href="https://dylancastillo.co/posts/function-calling-structured-outputs.html">function-calling</a> to implement the “think, act, observe” loop.</p>
</section>
<section id="what-is-langgraph" class="level2">
<h2 class="anchored" data-anchor-id="what-is-langgraph">What is LangGraph?</h2>
<p>LangGraph is a graph-based framework for building complex LLM applications, designed for stateful workflows. It makes it easier to build complex agent architectures.</p>
<p>Graphs are composed of nodes, edges, state, and reducers. Nodes are the units of work (functions, tools) and edges define the paths between nodes. State is persistent data passed between nodes and updated through reducers (functions that define how the state is updated).</p>
<p>I like LangGraph because it provides you with easy-to-use components, a simple API, and it lets you visualize your workflow. It also integrates well with LangSmith, a tool for monitoring and debugging LLM applications.</p>
<p>In this tutorial, I’ll show you how to build a ReAct agent with (and without) LangGraph. I’ll also use <em>LangChain</em> as a thin wrapper on top of OpenAI models.</p>
</section>
<section id="prerequisites" class="level2">
<h2 class="anchored" data-anchor-id="prerequisites">Prerequisites</h2>
<p>To follow this tutorial you’ll need to:</p>
<ol type="1">
<li>Sign up and generate an API key in <a href="https://platform.openai.com/docs/overview">OpenAI</a>.</li>
<li>Set the API key as an environment variable called <code>OPENAI_API_KEY</code>.</li>
<li>Create a virtual environment in Python and install the requirements:</li>
</ol>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode numberSource bash number-lines code-with-copy"><code class="sourceCode bash"><span id="cb1-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">python</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-m</span> venv venv</span>
<span id="cb1-2"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">source</span> venv/bin/activate</span>
<span id="cb1-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">pip</span> install langchain langchain-openai langchain-community langgraph jupyter</span></code></pre></div></div>
<p>Once you’ve completed the steps above, you can run the code from this article. You can also download the notebook from <a href="https://github.com/dylanjcastillo/blog/tree/main/posts/react-agent-langgraph.ipynb">here</a>.</p>
</section>
<section id="implementation" class="level2">
<h2 class="anchored" data-anchor-id="implementation">Implementation</h2>
<p>As usual, you must start by importing the necessary libraries and loading the environment variables. You’ll use the same model in all the examples, so you’ll define it once here:</p>
<div id="cell-4" class="cell" data-execution_count="1">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb2-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> typing <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> Literal</span>
<span id="cb2-2"></span>
<span id="cb2-3"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> dotenv <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> load_dotenv</span>
<span id="cb2-4"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> IPython.display <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> Image, display</span>
<span id="cb2-5"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> langchain_core.messages <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> HumanMessage, SystemMessage, ToolMessage</span>
<span id="cb2-6"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> langchain_core.tools <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> tool</span>
<span id="cb2-7"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> langchain_openai <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> ChatOpenAI</span>
<span id="cb2-8"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> langgraph.graph <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> END, START, MessagesState, StateGraph</span>
<span id="cb2-9"></span>
<span id="cb2-10">load_dotenv()</span>
<span id="cb2-11"></span>
<span id="cb2-12">model <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ChatOpenAI(model<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"gpt-4.1-mini"</span>, temperature<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span></code></pre></div></div>
</div>
<p>This code:</p>
<ol type="1">
<li>Imports the necessary libraries.</li>
<li>Loads environment variables from a <code>.env</code> file using <code>load_dotenv()</code>.</li>
<li>Defines a model (<code>gpt-4.1-mini</code>) that you’ll use in all the examples.</li>
</ol>
<section id="vanilla-react-agent" class="level3">
<h3 class="anchored" data-anchor-id="vanilla-react-agent">Vanilla ReAct agent</h3>
<p>You’ll build an agent that takes a question from a user and has access to a a tool. The tool is a Python REPL that it can use to answer the question.</p>
<p><strong>You should not use this in production</strong>. This tool can run arbitrary Python code on your device, and that’s not something you want to expose to random people on the internet.</p>
<p>First, let’s define the <code>run_python_code</code> tool.</p>
<div id="cell-8" class="cell" data-execution_count="2">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb3-1"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@tool</span></span>
<span id="cb3-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> run_python_code(code: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>:</span>
<span id="cb3-3">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Run arbitrary Python code including imports, assignments, and statements. Do not use any external libraries. Save your results as a variable.</span></span>
<span id="cb3-4"></span>
<span id="cb3-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    Args:</span></span>
<span id="cb3-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        code: Python code to run</span></span>
<span id="cb3-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    """</span></span>
<span id="cb3-8">    <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> sys</span>
<span id="cb3-9">    <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> io <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> StringIO</span>
<span id="cb3-10"></span>
<span id="cb3-11">    old_stdout <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> sys.stdout</span>
<span id="cb3-12">    sys.stdout <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> captured_output <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> StringIO()</span>
<span id="cb3-13"></span>
<span id="cb3-14">    namespace <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {}</span>
<span id="cb3-15"></span>
<span id="cb3-16">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">try</span>:</span>
<span id="cb3-17">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">exec</span>(code, namespace)</span>
<span id="cb3-18"></span>
<span id="cb3-19">        output <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> captured_output.getvalue()</span>
<span id="cb3-20"></span>
<span id="cb3-21">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> output.strip():</span>
<span id="cb3-22">            user_vars <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {</span>
<span id="cb3-23">                k: v</span>
<span id="cb3-24">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> k, v <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> namespace.items()</span>
<span id="cb3-25">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> k.startswith(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"__"</span>) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">and</span> k <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"StringIO"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"sys"</span>]</span>
<span id="cb3-26">            }</span>
<span id="cb3-27">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> user_vars:</span>
<span id="cb3-28">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(user_vars) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>:</span>
<span id="cb3-29">                    output <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>(user_vars.values())[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>])</span>
<span id="cb3-30">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span>:</span>
<span id="cb3-31">                    output <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>(user_vars)</span>
<span id="cb3-32"></span>
<span id="cb3-33">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> output.strip() <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> output.strip() <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Code executed successfully"</span></span>
<span id="cb3-34"></span>
<span id="cb3-35">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">except</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">Exception</span> <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> e:</span>
<span id="cb3-36">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Error: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>(e)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb3-37">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">finally</span>:</span>
<span id="cb3-38">        sys.stdout <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> old_stdout</span>
<span id="cb3-39"></span>
<span id="cb3-40"></span>
<span id="cb3-41">tools <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [run_python_code]</span>
<span id="cb3-42">tools_mapping <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {tool.name: tool <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> tool <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> tools}</span>
<span id="cb3-43">model_with_tools <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> model.bind_tools(tools)</span></code></pre></div></div>
</div>
<p>This code defines a tool. In <code>LangChain</code> tools are defined as functions decorated with <code>@tool</code>. These functions must have a <em>docstring</em> because it will be used to describe the tool to the LLM.</p>
<p><code>run_python_code</code> is a function that takes a code string and returns the result of executing that code.</p>
<p>Next, you provide the model with a mapping of the tool to its name by creating <code>tools_mapping</code>. This is often a point of confusion. The LLM doesn’t run the tools on its own. It only decides if a tool should be used. Then, your own code must make the actual tool call.</p>
<p>The mapping is more useful when there are multiple tools, and not a single tool, like in this case. However, I’m showing it here to illustrate how you’d usually do this in a real-world application.</p>
<p>Finally, you <em>bind</em> the tool to the model. The binding makes the model aware of the tool, so that it can use it.</p>
<p>Then, let’s define a function that encapsulates the logic of the agent.</p>
<div id="cell-10" class="cell" data-execution_count="3">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb4-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> run_agent(question: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>):</span>
<span id="cb4-2">    messages <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb4-3">        SystemMessage(</span>
<span id="cb4-4">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"You're a helpful assistant. Use the tools provided when relevant."</span></span>
<span id="cb4-5">        ),</span>
<span id="cb4-6">        HumanMessage(question),</span>
<span id="cb4-7">    ]</span>
<span id="cb4-8">    ai_message <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> model_with_tools.invoke(messages)</span>
<span id="cb4-9">    messages.append(ai_message)</span>
<span id="cb4-10"></span>
<span id="cb4-11">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">while</span> ai_message.tool_calls:</span>
<span id="cb4-12">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> tool_call <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> ai_message.tool_calls:</span>
<span id="cb4-13">            selected_tool <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> tools_mapping[tool_call[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"name"</span>]]</span>
<span id="cb4-14">            tool_msg <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> selected_tool.invoke(tool_call)</span>
<span id="cb4-15">            messages.append(tool_msg)</span>
<span id="cb4-16">        ai_message <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> model_with_tools.invoke(messages)</span>
<span id="cb4-17">        messages.append(ai_message)</span>
<span id="cb4-18"></span>
<span id="cb4-19">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> messages</span></code></pre></div></div>
</div>
<p>This function takes a question from a user, comes up with a python script, uses <code>run_python_code</code> to execute it and returns the result.</p>
<p>It works as follows:</p>
<ul>
<li><strong>Lines 2 to 9</strong> set up the <a href="https://dylancastillo.co/posts/prompt-engineering-101.html">prompts</a> and call the assistant.</li>
<li><strong>Lines 11 to 17</strong> is where the magic happens. This is a loop that will check if there’s been a tool call in the response from the model. If there is, it will call (invoke) the tool and add the result to the messages. It will then send the results back to the assistant, and repeat this process until there are no more tool calls.</li>
</ul>
<p>This is the core idea behind how agents work. You provide the assistant with a question and one or more tools. Then you let the assistant decide which tool to use, until it has all the information it needs to answer the question.</p>
<p>You can try it out by running the following code:</p>
<div id="cell-12" class="cell" data-execution_count="4">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb5-1">messages <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> run_agent(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Generate 10 random numbers between 1 and 100"</span>)</span>
<span id="cb5-2"></span>
<span id="cb5-3"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> m <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> messages:</span>
<span id="cb5-4">    m.pretty_print()</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<div class="ansi-escaped-output">
<pre>================================<span class="ansi-bold"> System Message </span>================================



You're a helpful assistant. Use the tools provided when relevant.

================================<span class="ansi-bold"> Human Message </span>=================================



Generate 10 random numbers between 1 and 100

==================================<span class="ansi-bold"> Ai Message </span>==================================

Tool Calls:

  run_python_code (call_twMJ1JkZK81ofm3842P3jNtb)

 Call ID: call_twMJ1JkZK81ofm3842P3jNtb

  Args:

    code: import random

random_numbers = [random.randint(1, 100) for _ in range(10)]

random_numbers

=================================<span class="ansi-bold"> Tool Message </span>=================================

Name: run_python_code



{'random': &lt;module 'random' from '/opt/homebrew/Cellar/python@3.13/3.13.2/Frameworks/Python.framework/Versions/3.13/lib/python3.13/random.py'&gt;, 'random_numbers': [74, 75, 58, 19, 90, 45, 44, 52, 90, 33]}

==================================<span class="ansi-bold"> Ai Message </span>==================================



Here are 10 random numbers between 1 and 100: 74, 75, 58, 19, 90, 45, 44, 52, 90, 33.
</pre>
</div>
</div>
</div>
<p>You can see that the agent took the user’s request, used the <code>run_python_code</code> tool to generate the numbers, and then returned the result.</p>
<p>Now, let’s see how you can build a ReAct agent with LangGraph.</p>
</section>
<section id="langgraph-react-agent" class="level3">
<h3 class="anchored" data-anchor-id="langgraph-react-agent">LangGraph ReAct agent</h3>
<p>Like, in the previous example, you start by defining the tools you want to use. You’ll use the same <code>run_python_code</code> tool.</p>
<div id="cell-16" class="cell" data-execution_count="5">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb6-1"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@tool</span></span>
<span id="cb6-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> run_python_code(code: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>:</span>
<span id="cb6-3">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Run arbitrary Python code including imports, assignments, and statements. Do not use any external libraries. Save your results as a variable.</span></span>
<span id="cb6-4"></span>
<span id="cb6-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    Args:</span></span>
<span id="cb6-6"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        code: Python code to run</span></span>
<span id="cb6-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">    """</span></span>
<span id="cb6-8">    <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> sys</span>
<span id="cb6-9">    <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> io <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> StringIO</span>
<span id="cb6-10"></span>
<span id="cb6-11">    old_stdout <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> sys.stdout</span>
<span id="cb6-12">    sys.stdout <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> captured_output <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> StringIO()</span>
<span id="cb6-13"></span>
<span id="cb6-14">    namespace <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {}</span>
<span id="cb6-15"></span>
<span id="cb6-16">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">try</span>:</span>
<span id="cb6-17">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">exec</span>(code, namespace)</span>
<span id="cb6-18"></span>
<span id="cb6-19">        output <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> captured_output.getvalue()</span>
<span id="cb6-20"></span>
<span id="cb6-21">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> output.strip():</span>
<span id="cb6-22">            user_vars <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {</span>
<span id="cb6-23">                k: v</span>
<span id="cb6-24">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> k, v <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> namespace.items()</span>
<span id="cb6-25">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> k.startswith(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"__"</span>) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">and</span> k <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"StringIO"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"sys"</span>]</span>
<span id="cb6-26">            }</span>
<span id="cb6-27">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> user_vars:</span>
<span id="cb6-28">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(user_vars) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>:</span>
<span id="cb6-29">                    output <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>(user_vars.values())[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>])</span>
<span id="cb6-30">                <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span>:</span>
<span id="cb6-31">                    output <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>(user_vars)</span>
<span id="cb6-32"></span>
<span id="cb6-33">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> output.strip() <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> output.strip() <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Code executed successfully"</span></span>
<span id="cb6-34"></span>
<span id="cb6-35">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">except</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">Exception</span> <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> e:</span>
<span id="cb6-36">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Error: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>(e)<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb6-37">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">finally</span>:</span>
<span id="cb6-38">        sys.stdout <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> old_stdout</span>
<span id="cb6-39"></span>
<span id="cb6-40"></span>
<span id="cb6-41">tools <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [run_python_code]</span>
<span id="cb6-42">tools_by_name <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {tool.name: tool <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> tool <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> tools}</span>
<span id="cb6-43">model_with_tools <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> model.bind_tools(tools)</span></code></pre></div></div>
</div>
<p>With langgraph you also need to you define the tools in the same way: set up the functions, add <code>@tool</code>, and create the tool mapping. Then, <em>bind</em> the tools to the model.</p>
<p>Next, you need to define the functions (nodes) that correspond to the steps the agent will take:</p>
<div id="cell-18" class="cell" data-execution_count="6">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb7-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> call_llm(state: MessagesState):</span>
<span id="cb7-2">    messages <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb7-3">        SystemMessage(content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"You are a helpful assistant that can run python code."</span>),</span>
<span id="cb7-4">    ] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> state[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"messages"</span>]</span>
<span id="cb7-5">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"messages"</span>: [model_with_tools.invoke(messages)]}</span>
<span id="cb7-6"></span>
<span id="cb7-7"></span>
<span id="cb7-8"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> call_tool(state: MessagesState):</span>
<span id="cb7-9">    result <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb7-10">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> tool_call <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> state[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"messages"</span>][<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>].tool_calls:</span>
<span id="cb7-11">        tool <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> tools_by_name[tool_call[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"name"</span>]]</span>
<span id="cb7-12">        observation <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> tool.invoke(tool_call[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"args"</span>])</span>
<span id="cb7-13">        result.append(ToolMessage(content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>observation, tool_call_id<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>tool_call[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"id"</span>]))</span>
<span id="cb7-14">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"messages"</span>: result}</span>
<span id="cb7-15"></span>
<span id="cb7-16"></span>
<span id="cb7-17"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> should_continue(state: MessagesState) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> Literal[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"environment"</span>, END]:</span>
<span id="cb7-18">    messages <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> state[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"messages"</span>]</span>
<span id="cb7-19">    last_message <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> messages[<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>]</span>
<span id="cb7-20">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> last_message.tool_calls:</span>
<span id="cb7-21">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Action"</span></span>
<span id="cb7-22">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> END</span></code></pre></div></div>
</div>
<p>This is how it works:</p>
<ol type="1">
<li><code>call_llm</code>: Sends the conversation history to the LLM to get the next response.</li>
<li><code>call_tool</code>: If the LLM’s response is a request to use a tool, this function executes the tool with the specified arguments.</li>
<li><code>should_continue</code>: This is the control logic. It checks the LLM’s last message. If it’s a tool request, it routes to the <code>call_tool</code>; otherwise, it ends the conversation.</li>
</ol>
<p><code>call_llm</code> and <code>call_tool</code> take <code>MessagesState</code> as input and return a the message key with the new message. This updates the <code>messages</code> key in the <code>MessagesState</code>. <code>should_continue</code> takes <code>MessagesState</code> act as a router, so it decides if a tool should be executed or if the conversation should end.</p>
<div id="cell-20" class="cell" data-execution_count="7">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb8-1">agent_builder <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> StateGraph(MessagesState)</span>
<span id="cb8-2"></span>
<span id="cb8-3">agent_builder.add_node(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"llm"</span>, call_llm)</span>
<span id="cb8-4">agent_builder.add_node(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"environment"</span>, call_tool)</span>
<span id="cb8-5"></span>
<span id="cb8-6">agent_builder.add_edge(START, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"llm"</span>)</span>
<span id="cb8-7">agent_builder.add_conditional_edges(</span>
<span id="cb8-8">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"llm"</span>,</span>
<span id="cb8-9">    should_continue,</span>
<span id="cb8-10">    {</span>
<span id="cb8-11">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Action"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"environment"</span>,</span>
<span id="cb8-12">        END: END,</span>
<span id="cb8-13">    },</span>
<span id="cb8-14">)</span>
<span id="cb8-15">agent_builder.add_edge(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"environment"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"llm"</span>)</span>
<span id="cb8-16"></span>
<span id="cb8-17">agent <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> agent_builder.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">compile</span>()</span></code></pre></div></div>
</div>
<p>This code sets up the logic of the agent. It starts with a call to the LLM. The LLM then decides whether to use a tool or to finish the task. If it uses a tool, the tool’s output is sent back to the LLM for the next step. This “think-act” loop continues until the agent decides the task is complete.</p>
<p>You can use <code>LangGraph</code> to visualize the agent’s flow:</p>
<div id="cell-22" class="cell" data-execution_count="8">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb9-1">display(Image(agent.get_graph(xray<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>).draw_mermaid_png()))</span></code></pre></div></div>
<div class="cell-output cell-output-display">
<div>
<figure class="figure">
<p><a href="react-agent-langgraph_files/figure-html/cell-9-output-1.png" class="lightbox" data-gallery="quarto-lightbox-gallery-1"><img src="https://dylancastillo.co/posts/react-agent-langgraph_files/figure-html/cell-9-output-1.png" class="img-fluid figure-img"></a></p>
</figure>
</div>
</div>
</div>
<p>Finally, you can run the agent by invoking the graph. For that, you’ll need to pass a list of messages to the graph.</p>
<div id="cell-24" class="cell" data-execution_count="9">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb10" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb10-1">messages <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb10-2">    SystemMessage(content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"You are a helpful assistant that can run python code."</span>),</span>
<span id="cb10-3">    HumanMessage(content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Generate 10 random numbers between 1 and 100"</span>),</span>
<span id="cb10-4">]</span>
<span id="cb10-5"></span>
<span id="cb10-6">messages <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> agent.invoke({<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"messages"</span>: messages})</span></code></pre></div></div>
</div>
<p>You can review the process by printing the messages:</p>
<div id="cell-26" class="cell" data-execution_count="10">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb11" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb11-1"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> m <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> messages[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"messages"</span>]:</span>
<span id="cb11-2">    m.pretty_print()</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<div class="ansi-escaped-output">
<pre>================================<span class="ansi-bold"> System Message </span>================================



You are a helpful assistant that can run python code.

================================<span class="ansi-bold"> Human Message </span>=================================



Generate 10 random numbers between 1 and 100

==================================<span class="ansi-bold"> Ai Message </span>==================================

Tool Calls:

  run_python_code (call_bsBRC5aL7kgHjeGHXaLR85TC)

 Call ID: call_bsBRC5aL7kgHjeGHXaLR85TC

  Args:

    code: import random

random_numbers = [random.randint(1, 100) for _ in range(10)]

random_numbers

=================================<span class="ansi-bold"> Tool Message </span>=================================



{'random': &lt;module 'random' from '/opt/homebrew/Cellar/python@3.13/3.13.2/Frameworks/Python.framework/Versions/3.13/lib/python3.13/random.py'&gt;, 'random_numbers': [77, 37, 97, 26, 22, 29, 58, 82, 17, 80]}

==================================<span class="ansi-bold"> Ai Message </span>==================================



Here are 10 random numbers between 1 and 100: 77, 37, 97, 26, 22, 29, 58, 82, 17, 80.
</pre>
</div>
</div>
</div>
<p>That’s all!</p>
</section>
</section>
<section id="conclusion" class="level2">
<h2 class="anchored" data-anchor-id="conclusion">Conclusion</h2>
<p>In this tutorial, you’ve learned what agents are, how they work, and how to build a simple ReAct agent with and without LangGraph. We covered:</p>
<ul>
<li><strong>Agent fundamentals</strong>: How agents differ from agentic workflows by dynamically directing their own processes</li>
<li><strong>ReAct pattern</strong>: The core “think, act, observe” loop that enables agents to take actions based on the information they have</li>
<li><strong>Vanilla and LangGraph implementation</strong>: Understanding how to implement agents with and without LangGraph</li>
</ul>
<p>Agents are great for open-ended tasks where the path isn’t predetermined. If you’re working on one of those, this article provides a good starting point. On the other hand, for tasks that have predefined steps, consider <a href="https://dylancastillo.co/posts/agentic-workflows-langgraph.html">agentic workflows</a> instead.</p>
<p>Hope you find this tutorial useful. If you have any questions, let me know in the comments below.</p>


</section>

<div id="quarto-appendix" class="default"><section class="quarto-appendix-contents" id="quarto-citation"><h2 class="anchored quarto-appendix-heading">Citation</h2><div><div class="quarto-appendix-secondary-label">BibTeX citation:</div><pre class="sourceCode code-with-copy quarto-appendix-bibtex"><code class="sourceCode bibtex">@online{castillo2025,
  author = {Castillo, Dylan},
  title = {Building {ReAct} Agents with (and Without) {LangGraph}},
  date = {2025-07-04},
  url = {https://dylancastillo.co/posts/react-agent-langgraph.html},
  langid = {en}
}
</code></pre><div class="quarto-appendix-secondary-label">For attribution, please cite this work as:</div><div id="ref-castillo2025" class="csl-entry quarto-appendix-citeas">
Castillo, Dylan. 2025. <span>“Building ReAct Agents with (and Without)
LangGraph.”</span> July 4. <a href="https://dylancastillo.co/posts/react-agent-langgraph.html">https://dylancastillo.co/posts/react-agent-langgraph.html</a>.
</div></div></section></div> ]]></description>
  <category>llm</category>
  <category>python</category>
  <category>anthropic</category>
  <category>openai</category>
  <category>agents</category>
  <guid>https://dylancastillo.co/posts/react-agent-langgraph.html</guid>
  <pubDate>Fri, 04 Jul 2025 00:00:00 GMT</pubDate>
  <media:content url="https://dylancastillo.co/posts/images/cards/react-agent-langgraph.png" medium="image" type="image/png" height="76" width="144"/>
</item>
<item>
  <title>Using Pydantic AI to build a ReAct agent</title>
  <dc:creator>Dylan Castillo</dc:creator>
  <link>https://dylancastillo.co/til/react-agent-pydantic-ai.html</link>
  <description><![CDATA[ 




<p>I wanted to get more familiar with Pydantic AI, so I decided to build a <a href="https://arxiv.org/abs/2210.03629">Reasoning and Acting (ReAct)</a> agent with multiple tools.</p>
<p>I’ve also written other TILs about Pydantic AI:</p>
<ul>
<li><a href="https://dylancastillo.co/til/prompt-chaining-pydantic-ai.html">Prompt chaining</a></li>
<li><a href="https://dylancastillo.co/til/routing-pydantic-ai.html">Routing</a></li>
<li><a href="https://dylancastillo.co/til/evaluator-optimizer-pydantic-ai.html">Evaluator-optimizer</a></li>
<li><a href="https://dylancastillo.co/til/parallelization-orchestrator-workers-pydantic-ai.html">Parallelization and Orchestrator-workers</a></li>
</ul>
<p>You can download this notebook <a href="https://github.com/dcastillo/blog/blob/main/til/react-agent-pydantic-ai.ipynb">here</a>.</p>
<section id="setup" class="level2">
<h2 class="anchored" data-anchor-id="setup">Setup</h2>
<p>After a first failed attempt, I realized that Pydantic AI uses asyncio under the hood, so you need to enable <code>nest_asyncio</code> to use it in a notebook.</p>
<div id="2a607486" class="cell" data-execution_count="1">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> nest_asyncio</span>
<span id="cb1-2"></span>
<span id="cb1-3">nest_asyncio.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">apply</span>()</span></code></pre></div></div>
</div>
<p>Then, I did the imports as usual. I hadn’t used <code>logfire</code> for monitoring LLM applications before, so I thought it’d be a good idea to try it out.</p>
<div id="4081c493" class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb2-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> os</span>
<span id="cb2-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> typing <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> Literal</span>
<span id="cb2-3"></span>
<span id="cb2-4"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> logfire</span>
<span id="cb2-5"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> requests</span>
<span id="cb2-6"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> dotenv <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> load_dotenv</span>
<span id="cb2-7"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> pydantic_ai <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> Agent</span>
<span id="cb2-8"></span>
<span id="cb2-9">load_dotenv()</span></code></pre></div></div>
</div>
<p>PydanticAI instrumentation uses OpenTelemetry (OTel). So it’s pretty straightforward to use it with Logfire or with any other OTel-compatible observability tool.</p>
<p>You just need to create a project in Logfire, generate a <code>Write token</code> and add it to the <code>.env</code> file. Then, you just need to run:</p>
<div id="014a1f88" class="cell">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb3-1">logfire.configure(</span>
<span id="cb3-2">    token<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>os.getenv(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'LOGFIRE_TOKEN'</span>),</span>
<span id="cb3-3">)</span>
<span id="cb3-4">logfire.instrument_pydantic_ai()</span></code></pre></div></div>
</div>
<p>This will ask you to select a project the first time you run it. It will generate a <code>logfire_credentials.json</code> file in your working directory. In following runs, it will automatically use the credentials from the file.</p>
</section>
<section id="react-agent" class="level2">
<h2 class="anchored" data-anchor-id="react-agent">ReAct Agent</h2>
<p>I decided to make an agent that had access to a tool to get the weather and another one that checks if the response that’s going to be sent to the user follows the company guidelines.</p>
<p>Here’s the code:</p>
<div id="d4edf351" class="cell" data-execution_count="28">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb4-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> pydantic <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> BaseModel</span>
<span id="cb4-2"></span>
<span id="cb4-3"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> Feedback(BaseModel):</span>
<span id="cb4-4">    feedback: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span></span>
<span id="cb4-5">    status: Literal[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'OK'</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'REQUIRES FIXING'</span>]</span>
<span id="cb4-6"></span>
<span id="cb4-7">evaluator_agent <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Agent(</span>
<span id="cb4-8">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'openai:gpt-4.1-mini'</span>,</span>
<span id="cb4-9">    system_prompt<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(</span>
<span id="cb4-10">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"You're a helpful assistant. Your task is to check if a given response follows the company guidelines. The company guidelines are that responses should be written in the style of a haiku. You should reply with 'OK' or 'REQUIRES FIXING' and a short explanation."</span></span>
<span id="cb4-11">    ),</span>
<span id="cb4-12">    output_type<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>Feedback,</span>
<span id="cb4-13">)</span>
<span id="cb4-14"></span>
<span id="cb4-15">react_agent <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Agent(  </span>
<span id="cb4-16">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'openai:gpt-4.1-mini'</span>,</span>
<span id="cb4-17">    system_prompt<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>(</span>
<span id="cb4-18">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"You're a helpful assistant. Use the tools provided when relevant. Then draft a response and check if it follows the company guidelines. Only respond to the user after you've validated and modified the response if needed."</span></span>
<span id="cb4-19">    ),</span>
<span id="cb4-20">)</span>
<span id="cb4-21"></span>
<span id="cb4-22"></span>
<span id="cb4-23"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@react_agent.tool_plain</span></span>
<span id="cb4-24"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> get_weather(latitude: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>, longitude: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>:</span>
<span id="cb4-25">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Get the weather of a given latitude and longitude"""</span></span>
<span id="cb4-26">    response <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> requests.get(</span>
<span id="cb4-27">        <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"https://api.open-meteo.com/v1/forecast?latitude=</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>latitude<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">&amp;longitude=</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>longitude<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">&amp;current=temperature_2m,wind_speed_10m&amp;hourly=temperature_2m,relative_humidity_2m,wind_speed_10m"</span></span>
<span id="cb4-28">    )</span>
<span id="cb4-29">    data <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> response.json()</span>
<span id="cb4-30">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>(data[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"current"</span>][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"temperature_2m"</span>])</span>
<span id="cb4-31"></span>
<span id="cb4-32"></span>
<span id="cb4-33"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@react_agent.tool_plain</span></span>
<span id="cb4-34"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> check_guidelines(drafted_response: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>:</span>
<span id="cb4-35">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Check if a given response follows the company guidelines"""</span></span>
<span id="cb4-36">    response <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> evaluator_agent.run_sync(drafted_response)</span>
<span id="cb4-37">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> response.output</span>
<span id="cb4-38"></span>
<span id="cb4-39"></span>
<span id="cb4-40">response <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> react_agent.run_sync(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"What is the temperature in Madrid?"</span>)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>18:57:42.206 react_agent run
18:57:42.208   chat gpt-4.1-mini
18:57:43.191   running 1 tool
18:57:43.192     running tool: get_weather
18:57:43.408   chat gpt-4.1-mini
18:57:44.117   running 1 tool
18:57:44.118     running tool: check_guidelines
18:57:44.120       evaluator_agent run
18:57:44.120         chat gpt-4.1-mini
18:57:46.291   chat gpt-4.1-mini</code></pre>
</div>
</div>
<p>And here’s the output:</p>
<div id="d74111c3" class="cell" data-execution_count="27">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb6-1"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(response.output)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>In Madrid sunshine,
Temperature climbs so high,
Thirty-four degrees.</code></pre>
</div>
</div>
<p>The output in Logfire looks like typical observability tools:</p>
<div id="fig-logfire" class="quarto-layout-panel">
<figure class="quarto-float quarto-float-fig figure">
<div aria-describedby="fig-logfire-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
<div class="quarto-layout-row">
<div class="quarto-layout-cell" style="flex-basis: 33.3%;justify-content: center;">
<p><a href="./images/react-agent-logfire-1.png" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Figure&nbsp;1: Traces in Logfire"><img src="https://dylancastillo.co/til/images/react-agent-logfire-1.png" class="img-fluid figure-img"></a></p>
</div>
<div class="quarto-layout-cell" style="flex-basis: 33.3%;justify-content: center;">
<p><a href="./images/react-agent-logfire-2.png" class="lightbox" data-gallery="quarto-lightbox-gallery-2" title="Figure&nbsp;1: Traces in Logfire"><img src="https://dylancastillo.co/til/images/react-agent-logfire-2.png" class="img-fluid figure-img"></a></p>
</div>
<div class="quarto-layout-cell" style="flex-basis: 33.3%;justify-content: center;">
<p><a href="./images/react-agent-logfire-3.png" class="lightbox" data-gallery="quarto-lightbox-gallery-3" title="Figure&nbsp;1: Traces in Logfire"><img src="https://dylancastillo.co/til/images/react-agent-logfire-3.png" class="img-fluid figure-img"></a></p>
</div>
</div>
</div>
<figcaption class="quarto-float-caption-bottom quarto-float-caption quarto-float-fig" id="fig-logfire-caption-0ceaefa1-69ba-4598-a22c-09a6ac19f8ca">
Figure&nbsp;1: Traces in Logfire
</figcaption>
</figure>
</div>
<p>That’s all!</p>
<p>You can access this notebook <a href="https://github.com/dylanjcastillo/blog/tree/main/til/react-agent-pydantic-ai.ipynb">here</a>.</p>


</section>

<div id="quarto-appendix" class="default"><section class="quarto-appendix-contents" id="quarto-citation"><h2 class="anchored quarto-appendix-heading">Citation</h2><div><div class="quarto-appendix-secondary-label">BibTeX citation:</div><pre class="sourceCode code-with-copy quarto-appendix-bibtex"><code class="sourceCode bibtex">@online{castillo2025,
  author = {Castillo, Dylan},
  title = {Using {Pydantic} {AI} to Build a {ReAct} Agent},
  date = {2025-07-04},
  url = {https://dylancastillo.co/til/react-agent-pydantic-ai.html},
  langid = {en}
}
</code></pre><div class="quarto-appendix-secondary-label">For attribution, please cite this work as:</div><div id="ref-castillo2025" class="csl-entry quarto-appendix-citeas">
Castillo, Dylan. 2025. <span>“Using Pydantic AI to Build a ReAct
Agent.”</span> July 4. <a href="https://dylancastillo.co/til/react-agent-pydantic-ai.html">https://dylancastillo.co/til/react-agent-pydantic-ai.html</a>.
</div></div></section></div> ]]></description>
  <category>til</category>
  <category>llm</category>
  <category>pydantic-ai</category>
  <category>agents</category>
  <guid>https://dylancastillo.co/til/react-agent-pydantic-ai.html</guid>
  <pubDate>Fri, 04 Jul 2025 00:00:00 GMT</pubDate>
</item>
<item>
  <title>Agentic workflows from scratch with (and without) LangGraph</title>
  <dc:creator>Dylan Castillo</dc:creator>
  <link>https://dylancastillo.co/posts/agentic-workflows-langgraph.html</link>
  <description><![CDATA[ 




<p>These days, everyone is “building” agents. But, in my experience, they’re either not really building agents, or they shouldn’t be!</p>
<p>Agents are powerful tools, but they’re not the right tool for many business problems. They give a lot of responsibility to LLMs, and that’s not always a good idea.</p>
<p>Their counterpart, agentic workflows, are a more controlled way to use LLMs. They’re a good fit for many business problems, and they’re a lot easier to build than agents.</p>
<p>In this post, I’ll explain what an agent is, what an agentic workflow is, and how to choose between them. I’ll also show you how to build the most common agentic workflows.</p>
<p>This post is based on <a href="https://www.anthropic.com/engineering/building-effective-agents">Anthropic’s Building Effective Agents</a>. I encourage you to read it.</p>
<section id="what-is-an-agent" class="level2">
<h2 class="anchored" data-anchor-id="what-is-an-agent">What is an agent?</h2>
<p>Over time, the ecosystem has converged on similar definitions:</p>
<p>Anthropic’s definition:</p>
<blockquote class="blockquote">
<p>“Systems where LLMs dynamically direct their own processes and tool usage, maintaining control over how they accomplish tasks.”</p>
<p><a href="https://www.anthropic.com/engineering/building-effective-agents">Building Effective Agents</a>, Anthropic</p>
</blockquote>
<p>OpenAI’s definition:</p>
<blockquote class="blockquote">
<p>“Systems that independently accomplish tasks on behalf of users”</p>
<p><a href="https://openai.com/index/new-tools-for-building-agents/">New tools for building agents</a>, OpenAI</p>
</blockquote>
<p>LangChain’s definition:</p>
<blockquote class="blockquote">
<p>“system that uses an LLM to decide the control flow of an application.”</p>
<p><a href="https://blog.langchain.com/what-is-an-agent/">What is an agent?</a>, LangChain</p>
</blockquote>
<p>These definitions share the idea that agents are systems that can:</p>
<ol type="1">
<li>Make decisions</li>
<li>Use tools</li>
<li>Take actions</li>
<li>Accomplish goals without constant human guidance</li>
</ol>
<p>This gives us a clear delimiter as to what an agent is and what it is not. However, this definition leaves out the majority of agentic systems being build by companies right now. These systems are called <strong>agentic workflows</strong>.</p>
</section>
<section id="what-is-an-agentic-workflow" class="level2">
<h2 class="anchored" data-anchor-id="what-is-an-agentic-workflow">What is an agentic workflow?</h2>
<p>Anthropic defines agentic workflows as systems where “LLMs and tools are orchestrated through predefined code paths”. They’re different from agents in that they don’t have the ability to dynamically direct their own processes and tool usage.</p>
<p>Workflows sound less sexy than agents, so you would rarely hear someone say they’re building workflows. However, in my experience, most people are (or should be) building workflows.</p>
</section>
<section id="how-to-choose-between-agentic-workflows-and-agents" class="level2">
<h2 class="anchored" data-anchor-id="how-to-choose-between-agentic-workflows-and-agents">How to choose between agentic workflows and agents?</h2>
<p>Choose agents for open-ended tasks where the number and the order of steps is not known beforehand. The LLM will potentially operate for many turns, and you must have some level of trust in its decision-making.</p>
<p>For example, a coding assistant is a good candidate for an agent. When you ask it to implement a feature, there’s no predefined path nor the order of steps is known beforehand. It might need to create files, add new dependencies, edit existing files, etc.</p>
<p>Agentic workflows make sense for tasks where the number and the order of steps is known beforehand. For example, an AI assistant that generates an initial draft of an article based on internal data sources. The order of steps are likely known beforehand (e.g., retrieve the key information from the internal data sources, generate a first candidate, and then revise the article).</p>
</section>
<section id="what-is-langgraph" class="level2">
<h2 class="anchored" data-anchor-id="what-is-langgraph">What is LangGraph?</h2>
<p>LangGraph is a graph-based framework for building complex LLM applications, designed for stateful workflows. It enables complex agent architectures with minimal code.</p>
<p>It uses a graph-based approach to build agentic systems. The graph is composed of nodes and edges. Nodes are the units of work (functions, tools, models). Edges define the workflow paths between nodes. State is persistent data passed between nodes and updated through reducers.</p>
<p>I’ve built many workflows from scratch, and I’ve realized that I often end up reinventing the same patterns that frameworks like LangGraph provide. I like LangGraph because it provides you with easy-to-use components, a simple API, and it lets you visualize your workflow. It also integrates well with LangSmith, a tool for monitoring and debugging LLM applications.</p>
<p>In this tutorial, I’ll show you how to build common agentic workflows with and without LangGraph. I’ll use <em>LangChain</em> as a thin wrapper on top of OpenAI models.</p>
</section>
<section id="prerequisites" class="level2">
<h2 class="anchored" data-anchor-id="prerequisites">Prerequisites</h2>
<p>To follow this tutorial you’ll need to:</p>
<ol type="1">
<li>Sign up and generate an API key in <a href="https://platform.openai.com/docs/overview">OpenAI</a>.</li>
<li>Set the API key as an environment variable called <code>OPENAI_API_KEY</code>.</li>
<li>Create a virtual environment in Python and install the requirements:</li>
</ol>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode numberSource bash number-lines code-with-copy"><code class="sourceCode bash"><span id="cb1-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">python</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-m</span> venv venv</span>
<span id="cb1-2"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">source</span> venv/bin/activate</span>
<span id="cb1-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">pip</span> install langchain langchain-openai langchain-community langgraph jupyter nest_asyncio</span></code></pre></div></div>
<p>Once you’ve completed the steps above, you can run the code from this article. You can also download the notebook from <a href="https://github.com/dylanjcastillo/blog/tree/main/posts/agentic-workflows-langgraph-pyndantic-ai.ipynb">here</a>.</p>
<p>You also need to apply a small patch to make sure you can run asyncio inside the notebook:</p>
<div id="cell-2" class="cell" data-execution_count="1">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb2-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> nest_asyncio</span>
<span id="cb2-2"></span>
<span id="cb2-3">nest_asyncio.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">apply</span>()</span></code></pre></div></div>
</div>
<p>You will use <code>asyncio</code> inside the notebook, so you need to install and apply <code>nest_asyncio</code>. Otherwise, you’ll get a <code>RuntimeError</code> when running the code.</p>
</section>
<section id="workflows" class="level2">
<h2 class="anchored" data-anchor-id="workflows">Workflows</h2>
<p>As usual, you must start by importing the necessary libraries and loading the environment variables. You’ll use the same model in all the examples, so you’ll define it once here:</p>
<div id="cell-6" class="cell" data-execution_count="44">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb3-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> asyncio</span>
<span id="cb3-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> operator</span>
<span id="cb3-3"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> typing <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> Annotated, Literal, Optional, TypedDict</span>
<span id="cb3-4"></span>
<span id="cb3-5"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> dotenv <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> load_dotenv</span>
<span id="cb3-6"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> IPython.display <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> Image, display</span>
<span id="cb3-7"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> langchain_core.messages <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> HumanMessage, SystemMessage</span>
<span id="cb3-8"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> langchain_openai <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> ChatOpenAI</span>
<span id="cb3-9"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> langgraph.graph <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> END, START, StateGraph</span>
<span id="cb3-10"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> langgraph.types <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> Send</span>
<span id="cb3-11"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> pydantic <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> BaseModel, Field</span>
<span id="cb3-12"></span>
<span id="cb3-13">load_dotenv()</span>
<span id="cb3-14"></span>
<span id="cb3-15">model <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ChatOpenAI(model<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"gpt-4.1-mini"</span>)</span></code></pre></div></div>
</div>
<p>This code imports the necessary libraries for building agentic workflows:</p>
<ul>
<li><strong>LangChain</strong>: For working with LLMs, messages, and structured outputs</li>
<li><strong>LangGraph</strong>: For building stateful workflows with nodes and edges</li>
<li><strong>Pydantic</strong>: For data validation and structured data models</li>
</ul>
<p>The <code>load_dotenv()</code> function loads environment variables from a <code>.env</code> file, including your OpenAI API key. You also define model (<code>gpt-4.1-mini</code>) that you’ll use in all the examples.</p>
<section id="prompt-chaining" class="level3">
<h3 class="anchored" data-anchor-id="prompt-chaining">Prompt chaining</h3>
<p>This workflow is designed for tasks that can be easily divided into subtasks. The key trade-off is accepting longer completion times (higher latency) in exchange for a higher-quality result.</p>
<p>Here’s what the workflow looks like:</p>
<div class="cell" data-layout-align="default">
<div class="cell-output-display">
<div>
<p></p><figure class="figure"><p></p>
<div>
<pre class="mermaid mermaid-js">flowchart LR
    In --&gt; LLM1["LLM Call 1"]
    LLM1 -- "Output 1" --&gt; Gate{Gate}
    Gate -- Pass --&gt; LLM2["LLM Call 2"]
    Gate -- Fail --&gt; Exit[Exit]
    LLM2 -- "Output 2" --&gt; LLM3["LLM Call 3"]
    LLM3 --&gt; Out
</pre>
</div>
<p></p></figure><p></p>
</div>
</div>
</div>
<p><strong>Examples:</strong></p>
<ul>
<li>Generating content in a pipeline by generating table of contents, content, revisions, translations, etc.</li>
<li>Generating a text through a multi-step process to evaluate if it matches certain criteria</li>
</ul>
<p>Now I’ll show you how to implement a prompt chaining workflow for generating an article. The workflow will be composed of three steps:</p>
<ol type="1">
<li>Generate a table of contents for the article</li>
<li>Generate the content of the article</li>
<li>Revise the content of the article if it’s too long</li>
</ol>
<p>I’ll show you a vanilla implementation and then a LangGraph implementation.</p>
<section id="vanilla-langchain" class="level4">
<h4 class="anchored" data-anchor-id="vanilla-langchain">Vanilla (+LangChain)</h4>
<p>First, you need to define the state of the workflow and a model to use for the LLM. You’ll use Pydantic for the state and LangChain for the model.</p>
<div id="cell-11" class="cell" data-execution_count="3">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb4-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> State(BaseModel):</span>
<span id="cb4-2">    topic: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span></span>
<span id="cb4-3">    table_of_contents: Optional[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span></span>
<span id="cb4-4">    content: Optional[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span></span>
<span id="cb4-5">    revised_content: Optional[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span></span></code></pre></div></div>
</div>
<p>Then, you need to define the functions that will be used in the workflow:</p>
<div id="cell-13" class="cell" data-execution_count="4">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb5-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> generate_table_of_contents(state: State) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>:</span>
<span id="cb5-2">    messages <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb5-3">        SystemMessage(</span>
<span id="cb5-4">            content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"You are an expert writer specialized in SEO. Provided with a topic, you will generate the table of contents for a short article."</span></span>
<span id="cb5-5">        ),</span>
<span id="cb5-6">        HumanMessage(</span>
<span id="cb5-7">            content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Generate the table of contents of an article about </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>state<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>topic<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb5-8">        ),</span>
<span id="cb5-9">    ]</span>
<span id="cb5-10">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> model.invoke(messages).content</span>
<span id="cb5-11"></span>
<span id="cb5-12"></span>
<span id="cb5-13"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> generate_article_content(state: State) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>:</span>
<span id="cb5-14">    messages <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb5-15">        SystemMessage(</span>
<span id="cb5-16">            content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"You are an expert writer specialized in SEO. Provided with a topic and a table of contents, you will generate the content of the article."</span></span>
<span id="cb5-17">        ),</span>
<span id="cb5-18">        HumanMessage(</span>
<span id="cb5-19">            content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Generate the content of an article about </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>state<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>topic<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> with the following table of contents: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>state<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>table_of_contents<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb5-20">        ),</span>
<span id="cb5-21">    ]</span>
<span id="cb5-22">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> model.invoke(messages).content</span>
<span id="cb5-23"></span>
<span id="cb5-24"></span>
<span id="cb5-25"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> revise_article_content(state: State) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>:</span>
<span id="cb5-26">    messages <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb5-27">        SystemMessage(</span>
<span id="cb5-28">            content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"You are an expert writer specialized in SEO. Provided with a topic, a table of contents and a content, you will revise the content of the article to make it less than 1000 characters."</span></span>
<span id="cb5-29">        ),</span>
<span id="cb5-30">        HumanMessage(</span>
<span id="cb5-31">            content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Revise the content of an article about </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>state<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>topic<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> with the following table of contents: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>state<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>table_of_contents<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> and the following content:</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n\n</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>state<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>content<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb5-32">        ),</span>
<span id="cb5-33">    ]</span>
<span id="cb5-34">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> model.invoke(messages).content</span></code></pre></div></div>
</div>
<p>These functions provide the core functionality of the workflow. They follow the steps I outlined above:</p>
<ol type="1">
<li><code>generate_table_of_contents</code>: Generate a table of contents for the article</li>
<li><code>generate_article_content</code>: Generate the content of the article</li>
<li><code>revise_article_content</code>: Revise the content of the article if it’s too long</li>
</ol>
<p>Then we need to orchestrate the workflow. You’ll do that by creating a function that takes a topic and returns the final article.</p>
<div id="cell-15" class="cell" data-execution_count="5">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb6-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> run_workflow(topic: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> State:</span>
<span id="cb6-2">    article <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> State(topic<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>topic)</span>
<span id="cb6-3">    article.table_of_contents <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> generate_table_of_contents(article)</span>
<span id="cb6-4">    article.content <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> generate_article_content(article)</span>
<span id="cb6-5">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(article.content) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1000</span>:</span>
<span id="cb6-6">        article.revised_content <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> revise_article_content(article)</span>
<span id="cb6-7">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> article</span>
<span id="cb6-8"></span>
<span id="cb6-9"></span>
<span id="cb6-10">output <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> run_workflow(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Artificial Intelligence"</span>)</span></code></pre></div></div>
</div>
<p><code>run_workflow</code> takes the topic provided by the user, generates an article, and verifies that it’s below 1000 characters. Depending on the result, it will revise the article or not. Finally, it returns the state that contains all the results from the workflow (the table of contents, the content, and the revised content).</p>
</section>
<section id="langgraph" class="level4">
<h4 class="anchored" data-anchor-id="langgraph">LangGraph</h4>
<p>Now let’s see how to implement the same workflow using LangGraph. You will noticed two key differences compared to the vanilla implementation.</p>
<p>Similar to the vanilla implementation, you’ll start by initializing the state class:</p>
<div id="cell-19" class="cell" data-execution_count="6">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb7-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> State(TypedDict):</span>
<span id="cb7-2">    topic: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span></span>
<span id="cb7-3">    table_of_contents: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span></span>
<span id="cb7-4">    content: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span></span>
<span id="cb7-5">    revised_content: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span></span></code></pre></div></div>
</div>
<p>When working with LangGraph, I’d suggest using a <code>TypedDict</code> to manage your state. Using a Pydantic model to manage your state in LangGraph has a few downsides:</p>
<ol type="1">
<li>Data types are only checked when they enter a node, not when they exit. This means you could accidentally save data of the wrong type to your state.</li>
<li>The final output of the entire graph will be a dictionary, not your Pydantic model.</li>
<li>The implementation feels like it’s <a href="https://github.com/langchain-ai/langgraph/discussions/1306">half-baked</a>.</li>
</ol>
<p>For more details, check out the <a href="https://langchain-ai.github.io/langgraph/how-tos/graph-api/#use-pydantic-models-for-graph-state">LangGraph documentation</a>.</p>
<p>Then, you’ll define the nodes (functions) that will be used in the workflow.</p>
<div id="cell-21" class="cell" data-execution_count="7">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb8-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> generate_table_of_contents(state: State) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>:</span>
<span id="cb8-2">    messages <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb8-3">        SystemMessage(</span>
<span id="cb8-4">            content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"You are an expert writer specialized in SEO. Provided with a topic, you will generate the table of contents for a short article."</span></span>
<span id="cb8-5">        ),</span>
<span id="cb8-6">        HumanMessage(</span>
<span id="cb8-7">            content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Generate the table of contents of an article about </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>state[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'topic'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb8-8">        ),</span>
<span id="cb8-9">    ]</span>
<span id="cb8-10">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"table_of_contents"</span>: model.invoke(messages).content}</span>
<span id="cb8-11"></span>
<span id="cb8-12"></span>
<span id="cb8-13"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> generate_article_content(state: State) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>:</span>
<span id="cb8-14">    messages <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb8-15">        SystemMessage(</span>
<span id="cb8-16">            content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"You are an expert writer specialized in SEO. Provided with a topic and a table of contents, you will generate the content of the article."</span></span>
<span id="cb8-17">        ),</span>
<span id="cb8-18">        HumanMessage(</span>
<span id="cb8-19">            content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Generate the content of an article about </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>state[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'topic'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> with the following table of contents: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>state[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'table_of_contents'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb8-20">        ),</span>
<span id="cb8-21">    ]</span>
<span id="cb8-22">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"content"</span>: model.invoke(messages).content}</span>
<span id="cb8-23"></span>
<span id="cb8-24"></span>
<span id="cb8-25"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> check_article_content(state: State) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>:</span>
<span id="cb8-26">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(state[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"content"</span>]) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1000</span>:</span>
<span id="cb8-27">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Fail"</span></span>
<span id="cb8-28">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Pass"</span></span>
<span id="cb8-29"></span>
<span id="cb8-30"></span>
<span id="cb8-31"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> revise_article_content(state: State) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>:</span>
<span id="cb8-32">    messages <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb8-33">        SystemMessage(</span>
<span id="cb8-34">            content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"You are an expert writer specialized in SEO. Provided with a topic, a table of contents and a content, you will revise the content of the article to make it less than 1000 characters."</span></span>
<span id="cb8-35">        ),</span>
<span id="cb8-36">        HumanMessage(</span>
<span id="cb8-37">            content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Revise the content of an article about </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>state[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'topic'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> with the following table of contents: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>state[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'table_of_contents'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> and the following content:</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n\n</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>state[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'content'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb8-38">        ),</span>
<span id="cb8-39">    ]</span>
<span id="cb8-40">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"revised_content"</span>: model.invoke(messages).content}</span></code></pre></div></div>
</div>
<p>You’ll noticed that the functions are quite similar to the vanilla implementation. The only difference is that they return dictionaries that automatically update the state rather than you having to do it manually.</p>
<p>Then you need to specify the nodes and edges of the workflow:</p>
<div id="cell-23" class="cell" data-execution_count="8">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb9-1">workflow_builder <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> StateGraph(State)</span>
<span id="cb9-2"></span>
<span id="cb9-3">workflow_builder.add_node(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"generate_table_of_contents"</span>, generate_table_of_contents)</span>
<span id="cb9-4">workflow_builder.add_node(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"generate_article_content"</span>, generate_article_content)</span>
<span id="cb9-5">workflow_builder.add_node(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"revise_article_content"</span>, revise_article_content)</span>
<span id="cb9-6"></span>
<span id="cb9-7">workflow_builder.add_edge(START, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"generate_table_of_contents"</span>)</span>
<span id="cb9-8">workflow_builder.add_edge(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"generate_table_of_contents"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"generate_article_content"</span>)</span>
<span id="cb9-9">workflow_builder.add_conditional_edges(</span>
<span id="cb9-10">    source<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"generate_article_content"</span>,</span>
<span id="cb9-11">    path<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>check_article_content,</span>
<span id="cb9-12">    path_map<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>{<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Fail"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"revise_article_content"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Pass"</span>: END},</span>
<span id="cb9-13">)</span>
<span id="cb9-14">workflow_builder.add_edge(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"revise_article_content"</span>, END)</span>
<span id="cb9-15"></span>
<span id="cb9-16">workflow <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> workflow_builder.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">compile</span>()</span></code></pre></div></div>
</div>
<p>You initialize a <code>StateGraph</code> object, which is a builder for the workflow. Then you add nodes to the graph, and define the edges between them. The nodes in the graphs are the functions that you defined earlier. In the conditional edge, you can define the path that the workflow will take based on the state of the workflow.</p>
<p>The <code>compile</code> method is used to compile the graph into a callable workflow.</p>
<p>Finally, LangGraph has a nice feature that allows you to visualize the workflow. You can use the <code>get_graph</code> and <code>draw_mermaid_png</code> for that:</p>
<div id="cell-25" class="cell" data-execution_count="9">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb10" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb10-1">display(Image(workflow.get_graph().draw_mermaid_png()))</span></code></pre></div></div>
<div class="cell-output cell-output-display">
<div>
<figure class="figure">
<p><a href="agentic-workflows-langgraph_files/figure-html/cell-10-output-1.png" class="lightbox" data-gallery="quarto-lightbox-gallery-1"><img src="https://dylancastillo.co/posts/agentic-workflows-langgraph_files/figure-html/cell-10-output-1.png" class="img-fluid figure-img"></a></p>
</figure>
</div>
</div>
</div>
<p>Finally, you can run the workflow by calling the <code>invoke</code> method on the compiled workflow and provide the initial state.</p>
<div id="cell-27" class="cell" data-execution_count="10">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb11" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb11-1">article <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> workflow.invoke({<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"topic"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Artificial Intelligence"</span>})</span></code></pre></div></div>
</div>
<p>And you’ll get the following output:</p>
<div id="cell-29" class="cell" data-execution_count="11">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb12" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb12-1">article</span></code></pre></div></div>
<div class="cell-output cell-output-display" data-execution_count="11">
<pre><code>{'topic': 'Artificial Intelligence',
 'table_of_contents': 'Table of Contents\n\n1. Introduction to Artificial Intelligence  \n2. History and Evolution of AI  \n3. Types of Artificial Intelligence  \n4. Applications of AI in Various Industries  \n5. Benefits of Artificial Intelligence  \n6. Challenges and Ethical Considerations  \n7. The Future of Artificial Intelligence  \n8. Conclusion',
 'content': '# Artificial Intelligence: Transforming the Future\n\n## 1. Introduction to Artificial Intelligence\n\nArtificial Intelligence (AI) refers to the simulation of human intelligence processes by machines, especially computer systems. These processes include learning, reasoning, problem-solving, perception, and language understanding. AI enables machines to perform tasks that typically require human intelligence, making them smarter, more efficient, and capable of handling complex scenarios. From virtual assistants and recommendation systems to autonomous vehicles, AI has become an integral part of modern technology.\n\n## 2. History and Evolution of AI\n\nThe concept of artificial intelligence dates back to the mid-20th century. In 1956, the term “Artificial Intelligence” was coined during the Dartmouth Conference, marking the official birth of AI as a research field. Early AI research focused on symbolic methods and rule-based systems. Over the decades, advancements in algorithms, computational power, and data availability propelled AI development. Notable milestones include the creation of expert systems in the 1970s and 1980s, the rise of machine learning in the 1990s, and the advent of deep learning in the 2010s. Today, AI continues to evolve rapidly, driven by breakthroughs in neural networks and big data analytics.\n\n## 3. Types of Artificial Intelligence\n\nAI can be broadly categorized into three types based on capabilities:\n\n- **Narrow AI (Weak AI):** Designed to perform specific tasks such as voice recognition or image classification. This is the most common form of AI today.\n  \n- **General AI (Strong AI):** A theoretical form of AI that possesses the ability to understand, learn, and perform any intellectual task a human can do.\n  \n- **Superintelligent AI:** A hypothetical AI that surpasses all human intelligence across all fields, potentially possessing self-awareness and superior problem-solving abilities.\n\nAdditionally, AI can be classified based on functionalities such as reactive machines, limited memory systems, theory of mind AI, and self-aware AI, reflecting increasing complexity and cognitive capability.\n\n## 4. Applications of AI in Various Industries\n\nAI is transforming industries worldwide by streamlining operations, enhancing decision-making, and enabling innovation:\n\n- **Healthcare:** AI assists in diagnostics, personalized treatment, drug discovery, and robotic surgery.\n- **Finance:** Fraud detection, algorithmic trading, customer support, and risk management benefit from AI solutions.\n- **Retail:** AI powers recommendation engines, inventory management, and customer behavior analysis.\n- **Manufacturing:** Predictive maintenance, quality control, and automation improve efficiency.\n- **Transportation:** Autonomous vehicles, route optimization, and traffic management leverage AI technologies.\n- **Education:** Personalized learning experiences, automated grading, and virtual tutors utilize AI capabilities.\n\n## 5. Benefits of Artificial Intelligence\n\nThe adoption of AI provides numerous advantages:\n\n- **Efficiency:** Automates repetitive tasks, reducing time and labor costs.\n- **Accuracy:** Minimizes human error and enhances precision in complex processes.\n- **Data Insights:** Analyzes vast datasets to uncover trends, patterns, and actionable insights.\n- **Innovation:** Facilitates the creation of new products and services.\n- **Personalization:** Tailors experiences and recommendations to individual preferences.\n- **Accessibility:** Makes services more accessible through natural language processing and intelligent interfaces.\n\n## 6. Challenges and Ethical Considerations\n\nDespite its promise, AI poses several challenges and ethical dilemmas:\n\n- **Bias and Fairness:** AI systems can inherit biases from training data, leading to unfair outcomes.\n- **Privacy Concerns:** Extensive data collection raises issues about user privacy and data security.\n- **Job Displacement:** Automation may disrupt labor markets and professions.\n- **Transparency:** Many AI algorithms, especially deep learning models, lack interpretability.\n- **Accountability:** Determining responsibility in the case of AI failures or misuse is complex.\n- **Ethical Use:** Ensuring AI is used for beneficial purposes and preventing misuse such as in autonomous weapons is critical.\n\nAddressing these concerns requires robust governance, regulation, and ongoing dialogue between stakeholders.\n\n## 7. The Future of Artificial Intelligence\n\nThe future of AI is poised to reshape society profoundly:\n\n- **Enhanced Human-AI Collaboration:** AI will increasingly augment human capabilities rather than replace them.\n- **Advancements in General AI:** Research continues toward achieving more versatile, human-like AI.\n- **AI in Creativity:** Emerging AI tools will enhance creative processes in art, music, and writing.\n- **Integration with IoT:** AI combined with the Internet of Things will enable smarter environments and cities.\n- **Ethical AI Development:** Emphasis will grow on developing transparent, fair, and accountable AI systems.\n- **AI for Global Challenges:** AI will play a pivotal role in addressing climate change, healthcare crises, and education gaps.\n\nContinued innovation, balanced with ethical considerations, will ensure AI’s positive impact on humanity.\n\n## 8. Conclusion\n\nArtificial Intelligence has evolved from a conceptual idea to a transformative force across industries and societies. Its ability to process information, learn, and make decisions holds tremendous potential to improve lives and drive progress. However, realizing this potential requires careful management of ethical challenges and inclusive development. As AI continues to mature, it promises a future where intelligent machines and humans work collaboratively to solve complex problems and create new opportunities. Embracing AI responsibly will be key to unlocking its benefits for generations to come.',
 'revised_content': 'Artificial Intelligence (AI) simulates human intelligence in machines, enabling tasks like learning, reasoning, and problem-solving. Since its inception at the 1956 Dartmouth Conference, AI has evolved from rule-based systems to advanced machine learning and deep learning models. AI types include Narrow AI, designed for specific tasks; General AI, capable of human-like understanding; and Superintelligent AI, a hypothetical superior intellect. AI revolutionizes industries—improving healthcare diagnostics, financial fraud detection, retail personalization, manufacturing automation, transportation logistics, and education. Benefits include efficiency, accuracy, data-driven insights, innovation, and personalization. However, AI raises challenges such as bias, privacy, job displacement, transparency, and ethical use. The future promises enhanced human-AI collaboration, ethical development, and AI-driven solutions for global issues. Responsible AI integration will transform society, fostering progress and innovation.'}</code></pre>
</div>
</div>
<p>That’s it for prompt chaining. Next, you’ll see how to implement a routing workflow.</p>
</section>
</section>
<section id="routing" class="level3">
<h3 class="anchored" data-anchor-id="routing">Routing</h3>
<p>Routing is a sorting system that sends each task to the right place for the best handling. This process can be managed by an LLM or a traditional classification model. It makes sense to use when a system needs to apply different logic to different types of queries.</p>
<p>Here’s what the workflow looks like:</p>
<div class="cell" data-layout-align="default">
<div class="cell-output-display">
<div>
<p></p><figure class="figure"><p></p>
<div>
<pre class="mermaid mermaid-js">flowchart LR 
    In([In]) --&gt; Router["LLM Call Router"]

    Router --&gt;|Route 1| LLM1["LLM Call 1"]
    Router --&gt;|Route 2| LLM2["LLM Call 2"]
    Router --&gt;|Route 3| LLM3["LLM Call 3"]

    LLM1 --&gt; Out([Out])
    LLM2 --&gt; Out
    LLM3 --&gt; Out
</pre>
</div>
<p></p></figure><p></p>
</div>
</div>
</div>
<p><strong>Examples:</strong></p>
<ul>
<li>Classify complexity of question and adjust model depending on it</li>
<li>Classify type of query and use specialized tools (e.g., indexes, prompts)</li>
</ul>
<p>I’ll walk you through a simple example of a routing workflow. The workflow will be composed of two steps:</p>
<ol type="1">
<li>Classify the type of query</li>
<li>Route the query to the right place</li>
</ol>
<p>I’ll show you a vanilla implementation and then a LangGraph implementation.</p>
<section id="vanilla-langchain-1" class="level4">
<h4 class="anchored" data-anchor-id="vanilla-langchain-1">Vanilla (+LangChain)</h4>
<p>First, you need to initialize the model, and define the state of the workflow and the data models. You’ll use Pydantic for the state and data models validation and LangChain for the LLM interactions.</p>
<div id="cell-34" class="cell" data-execution_count="12">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb14" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb14-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> State(BaseModel):</span>
<span id="cb14-2">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">input</span>: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span></span>
<span id="cb14-3">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">type</span>: Optional[</span>
<span id="cb14-4">        Literal[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"write_article"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"generate_table_of_contents"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"review_article"</span>]</span>
<span id="cb14-5">    ] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span></span>
<span id="cb14-6">    output: Optional[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span></span>
<span id="cb14-7"></span>
<span id="cb14-8"></span>
<span id="cb14-9"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> MessageType(BaseModel):</span>
<span id="cb14-10">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">type</span>: Literal[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"write_article"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"generate_table_of_contents"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"review_article"</span>]</span></code></pre></div></div>
</div>
<p>Then, you need to define the functions that will be used in the workflow.</p>
<div id="cell-36" class="cell" data-execution_count="13">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb15" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb15-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> classify_message(state: State) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> State:</span>
<span id="cb15-2">    model_with_str_output <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> model.with_structured_output(MessageType)</span>
<span id="cb15-3">    messages <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb15-4">        SystemMessage(</span>
<span id="cb15-5">            content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"You are a helpful assistant. You will classify the message into one of the following categories: 'write_article', 'generate_table_of_contents', 'review_article'."</span></span>
<span id="cb15-6">        ),</span>
<span id="cb15-7">        HumanMessage(content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Classify the message: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>state<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">input</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>),</span>
<span id="cb15-8">    ]</span>
<span id="cb15-9">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> model_with_str_output.invoke(messages).<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">type</span></span>
<span id="cb15-10"></span>
<span id="cb15-11"></span>
<span id="cb15-12"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> write_article(state: State) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> State:</span>
<span id="cb15-13">    messages <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb15-14">        SystemMessage(</span>
<span id="cb15-15">            content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"You are a writer. You will write an article about the topic provided."</span></span>
<span id="cb15-16">        ),</span>
<span id="cb15-17">        HumanMessage(content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Write an article about </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>state<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">input</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>),</span>
<span id="cb15-18">    ]</span>
<span id="cb15-19">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> model.invoke(messages).content</span>
<span id="cb15-20"></span>
<span id="cb15-21"></span>
<span id="cb15-22"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> generate_table_of_contents(state: State) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> State:</span>
<span id="cb15-23">    messages <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb15-24">        SystemMessage(</span>
<span id="cb15-25">            content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"You are a writer. You will generate a table of contents for an article about the topic provided."</span></span>
<span id="cb15-26">        ),</span>
<span id="cb15-27">        HumanMessage(</span>
<span id="cb15-28">            content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Generate a table of contents for an article about </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>state<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">input</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb15-29">        ),</span>
<span id="cb15-30">    ]</span>
<span id="cb15-31">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> model.invoke(messages).content</span>
<span id="cb15-32"></span>
<span id="cb15-33"></span>
<span id="cb15-34"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> review_article(state: State) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> State:</span>
<span id="cb15-35">    messages <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb15-36">        SystemMessage(</span>
<span id="cb15-37">            content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"You are a writer. You will review the article for the topic provided."</span></span>
<span id="cb15-38">        ),</span>
<span id="cb15-39">        HumanMessage(content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Review the article for the topic </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>state<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">input</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>),</span>
<span id="cb15-40">    ]</span>
<span id="cb15-41">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> model.invoke(messages).content</span></code></pre></div></div>
</div>
<p>These functions handle the core functionality of the workflow:</p>
<ol type="1">
<li><code>classify_message</code>: Uses structured outputs to determine what type of request the user is making. This is the “router” that decides which path to take.</li>
<li><code>write_article</code>: Generates a full article about the given topic</li>
<li><code>generate_table_of_contents</code>: Creates only a table of contents for an article</li>
<li><code>review_article</code>: Provides a review or critique of an existing article</li>
</ol>
<p>Finally, you need to orchestrate the workflow by creating a function that classifies the input and routes it to the appropriate handler.</p>
<div id="cell-38" class="cell" data-execution_count="14">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb16" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb16-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> run_workflow(message: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>:</span>
<span id="cb16-2">    state <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> State(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">input</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>message)</span>
<span id="cb16-3">    state.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">type</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> classify_message(state)</span>
<span id="cb16-4">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> state.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">type</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"write_article"</span>:</span>
<span id="cb16-5">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> write_article(state)</span>
<span id="cb16-6">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">elif</span> state.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">type</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"generate_table_of_contents"</span>:</span>
<span id="cb16-7">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> generate_table_of_contents(state)</span>
<span id="cb16-8">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">elif</span> state.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">type</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"review_article"</span>:</span>
<span id="cb16-9">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> review_article(state)</span>
<span id="cb16-10">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span>:</span>
<span id="cb16-11">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"I'm sorry, I don't know how to handle that message."</span></span>
<span id="cb16-12"></span>
<span id="cb16-13">output <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> run_workflow(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Write an article about the meaning of life"</span>)</span></code></pre></div></div>
</div>
<p><code>run_workflow</code> takes the user’s message, classifies it to determine the intent, and then routes it to the appropriate specialized function. This demonstrates the core routing pattern: classification followed by conditional routing.</p>
<p>Now let’s implement the same workflow using LangGraph.</p>
</section>
<section id="langgraph-1" class="level4">
<h4 class="anchored" data-anchor-id="langgraph-1">LangGraph</h4>
<p>Similar to the vanilla implementation, you’ll start by defining the state and data models:</p>
<div id="cell-41" class="cell" data-execution_count="15">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb17" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb17-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> State(TypedDict):</span>
<span id="cb17-2">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">input</span>: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span></span>
<span id="cb17-3">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">type</span>: Optional[Literal[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"write_article"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"generate_table_of_contents"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"review_article"</span>]] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span></span>
<span id="cb17-4">    output: Optional[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span></span>
<span id="cb17-5"></span>
<span id="cb17-6"></span>
<span id="cb17-7"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> MessageType(BaseModel):</span>
<span id="cb17-8">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">type</span>: Literal[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"write_article"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"generate_table_of_contents"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"review_article"</span>]</span></code></pre></div></div>
</div>
<p>Then, you’ll define the nodes (functions) that will be used in the workflow:</p>
<div id="cell-43" class="cell" data-execution_count="16">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb18" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb18-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> classify_message(message: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>:</span>
<span id="cb18-2">    model_with_str_output <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> model.with_structured_output(MessageType)</span>
<span id="cb18-3">    messages <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb18-4">        SystemMessage(</span>
<span id="cb18-5">            content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"You are a writer. You will classify the message into one of the following categories: 'write_article', 'generate_table_of_contents', 'review_article'."</span></span>
<span id="cb18-6">        ),</span>
<span id="cb18-7">        HumanMessage(content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Classify the message: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>message<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>),</span>
<span id="cb18-8">    ]</span>
<span id="cb18-9">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"type"</span>: model_with_str_output.invoke(messages).<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">type</span>}</span>
<span id="cb18-10"></span>
<span id="cb18-11"></span>
<span id="cb18-12"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> route_message(state: State) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> State:</span>
<span id="cb18-13">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> state[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"type"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"write_article"</span>:</span>
<span id="cb18-14">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"generate_article_content"</span></span>
<span id="cb18-15">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">elif</span> state[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"type"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"generate_table_of_contents"</span>:</span>
<span id="cb18-16">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"generate_table_of_contents"</span></span>
<span id="cb18-17">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">elif</span> state[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"type"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"review_article"</span>:</span>
<span id="cb18-18">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"revise_article_content"</span></span>
<span id="cb18-19">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span>:</span>
<span id="cb18-20">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">raise</span> <span class="pp" style="color: #AD0000;
background-color: null;
font-style: inherit;">ValueError</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Invalid message type: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>state[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'type'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb18-21"></span>
<span id="cb18-22"></span>
<span id="cb18-23"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> generate_table_of_contents(state: State) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> State:</span>
<span id="cb18-24">    messages <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb18-25">        SystemMessage(</span>
<span id="cb18-26">            content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"You are an expert writer specialized in SEO. Provided with a topic, you will generate the table of contents for a short article."</span></span>
<span id="cb18-27">        ),</span>
<span id="cb18-28">        HumanMessage(</span>
<span id="cb18-29">            content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Generate the table of contents of an article about </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>state[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'input'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb18-30">        ),</span>
<span id="cb18-31">    ]</span>
<span id="cb18-32">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"output"</span>: model.invoke(messages).content}</span>
<span id="cb18-33"></span>
<span id="cb18-34"></span>
<span id="cb18-35"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> generate_article_content(state: State) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>:</span>
<span id="cb18-36">    messages <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb18-37">        SystemMessage(</span>
<span id="cb18-38">            content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"You are an expert writer specialized in SEO. Provided with a topic and a table of contents, you will generate the content of the article."</span></span>
<span id="cb18-39">        ),</span>
<span id="cb18-40">        HumanMessage(</span>
<span id="cb18-41">            content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Generate the content of an article about </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>state[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'input'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb18-42">        ),</span>
<span id="cb18-43">    ]</span>
<span id="cb18-44">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"output"</span>: model.invoke(messages).content}</span>
<span id="cb18-45"></span>
<span id="cb18-46"></span>
<span id="cb18-47"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> revise_article_content(state: State) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>:</span>
<span id="cb18-48">    messages <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb18-49">        SystemMessage(</span>
<span id="cb18-50">            content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"You are an expert writer specialized in SEO. Provided with a topic, a table of contents and a content, you will revise the content of the article to make it less than 1000 characters."</span></span>
<span id="cb18-51">        ),</span>
<span id="cb18-52">        HumanMessage(</span>
<span id="cb18-53">            content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Revise the content of the following article:</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n\n</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>state[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'input'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb18-54">        ),</span>
<span id="cb18-55">    ]</span>
<span id="cb18-56">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"output"</span>: model.invoke(messages).content}</span></code></pre></div></div>
</div>
<p>The functions are similar to the vanilla implementation, but they return dictionaries that automatically update the state rather than requiring manual state management. There’s also a new function <code>route_message</code> that acts as the router that sends the message to the right place.</p>
<p>Then, you need to specify the nodes and edges of the workflow:</p>
<div id="cell-45" class="cell" data-execution_count="17">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb19" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb19-1">workflow_builder <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> StateGraph(State)</span>
<span id="cb19-2"></span>
<span id="cb19-3">workflow_builder.add_node(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"classify_message"</span>, classify_message)</span>
<span id="cb19-4">workflow_builder.add_conditional_edges(</span>
<span id="cb19-5">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"classify_message"</span>,</span>
<span id="cb19-6">    route_message,</span>
<span id="cb19-7">    {</span>
<span id="cb19-8">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"generate_article_content"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"generate_article_content"</span>,</span>
<span id="cb19-9">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"generate_table_of_contents"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"generate_table_of_contents"</span>,</span>
<span id="cb19-10">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"revise_article_content"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"revise_article_content"</span>,</span>
<span id="cb19-11">    },</span>
<span id="cb19-12">)</span>
<span id="cb19-13"></span>
<span id="cb19-14">workflow_builder.add_node(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"generate_table_of_contents"</span>, generate_table_of_contents)</span>
<span id="cb19-15">workflow_builder.add_node(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"generate_article_content"</span>, generate_article_content)</span>
<span id="cb19-16">workflow_builder.add_node(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"revise_article_content"</span>, revise_article_content)</span>
<span id="cb19-17"></span>
<span id="cb19-18">workflow_builder.add_edge(START, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"classify_message"</span>)</span>
<span id="cb19-19">workflow_builder.add_edge(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"generate_table_of_contents"</span>, END)</span>
<span id="cb19-20">workflow_builder.add_edge(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"generate_article_content"</span>, END)</span>
<span id="cb19-21">workflow_builder.add_edge(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"revise_article_content"</span>, END)</span>
<span id="cb19-22"></span>
<span id="cb19-23">workflow <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> workflow_builder.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">compile</span>()</span></code></pre></div></div>
</div>
<p>First, you’ll start by creating a <code>StateGraph</code> object, which serves as the builder for your workflow. Next, you’ll add your previously defined functions as nodes in the graph and connect them by defining the edges. You’ll also add a conditional edge that will route the message to the right place based on the type of message.</p>
<p>The graph is then compiled into a runnable workflow using the <code>compile</code> method.</p>
<p>Finally, LangGraph includes a helpful feature for visualizing your workflow. For this, you can use the <code>get_graph</code> and <code>draw_mermaid_png</code> functions.</p>
<div id="cell-47" class="cell" data-execution_count="18">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb20" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb20-1">display(Image(workflow.get_graph().draw_mermaid_png()))</span></code></pre></div></div>
<div class="cell-output cell-output-display">
<div>
<figure class="figure">
<p><a href="agentic-workflows-langgraph_files/figure-html/cell-19-output-1.png" class="lightbox" data-gallery="quarto-lightbox-gallery-2"><img src="https://dylancastillo.co/posts/agentic-workflows-langgraph_files/figure-html/cell-19-output-1.png" class="img-fluid figure-img"></a></p>
</figure>
</div>
</div>
</div>
</section>
</section>
<section id="parallelization" class="level3">
<h3 class="anchored" data-anchor-id="parallelization">Parallelization</h3>
<p>This workflow is designed for tasks that can be easily divided into independent subtasks. The key trade-off is managing complexity and coordination overhead in exchange for significant speed improvements or diverse perspectives.</p>
<p>Here’s what the workflow looks like:</p>
<div class="cell" data-layout-align="default">
<div class="cell-output-display">
<div>
<p></p><figure class="figure"><p></p>
<div>
<pre class="mermaid mermaid-js">flowchart LR
    In([In]) --&gt; LLM1["LLM Call 1"]
    In --&gt; LLM2["LLM Call 2"]
    In --&gt; LLM3["LLM Call 3"]
    LLM1 --&gt; Aggregator["Aggregator"] 
    LLM2 --&gt; Aggregator["Aggregator"] 
    LLM3 --&gt; Aggregator["Aggregator"] 
    Aggregator --&gt; Out([Out])
</pre>
</div>
<p></p></figure><p></p>
</div>
</div>
</div>
<p><strong>Examples:</strong></p>
<ul>
<li>Evaluate multiple independent aspects of a text (safety, quality, relevance)</li>
<li>Process user query and apply guardrails in parallel</li>
<li>Generate multiple response candidates given a query for comparison</li>
</ul>
<p>Now I’ll show you how to implement a parallelization workflow for content evaluation. The workflow will be composed of three steps:</p>
<ol type="1">
<li>Run multiple independent evaluations of the same content</li>
<li>Collect all evaluation results</li>
<li>Aggregate the results into a final assessment</li>
</ol>
<p>I’ll show you a vanilla implementation and then a LangGraph implementation.</p>
<section id="vanilla-langchain-2" class="level4">
<h4 class="anchored" data-anchor-id="vanilla-langchain-2">Vanilla (+LangChain)</h4>
<p>First, you need to define the state of the workflow and data models for evaluations. You’ll use Pydantic for the state and data models validation and LangChain for the LLM interactions.</p>
<div id="cell-51" class="cell" data-execution_count="19">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb21" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb21-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> Evaluation(BaseModel):</span>
<span id="cb21-2">    explanation: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span></span>
<span id="cb21-3">    is_appropiate: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">bool</span></span>
<span id="cb21-4"></span>
<span id="cb21-5"></span>
<span id="cb21-6"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> AggregatedResults(BaseModel):</span>
<span id="cb21-7">    summary: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span></span>
<span id="cb21-8">    is_appropiate: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">bool</span></span>
<span id="cb21-9"></span>
<span id="cb21-10"></span>
<span id="cb21-11"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> State(BaseModel):</span>
<span id="cb21-12">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">input</span>: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span></span>
<span id="cb21-13">    evaluations: Optional[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[Evaluation]] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span></span>
<span id="cb21-14">    aggregated_results: Optional[AggregatedResults] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span></span></code></pre></div></div>
</div>
<p>Then, you need to define the functions with each step of the workflow.</p>
<div id="cell-53" class="cell" data-execution_count="20">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb22" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb22-1"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">async</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> evaluate_text(state: State) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> Evaluation:</span>
<span id="cb22-2">    model_with_str_output <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> model.with_structured_output(Evaluation)</span>
<span id="cb22-3">    messages <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb22-4">        SystemMessage(</span>
<span id="cb22-5">            content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"You are an expert evaluator. Provided with a text, you will evaluate if it's appropriate for a general audience."</span></span>
<span id="cb22-6">        ),</span>
<span id="cb22-7">        HumanMessage(content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Evaluate the following text: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>state<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">input</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>),</span>
<span id="cb22-8">    ]</span>
<span id="cb22-9">    response <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">await</span> model_with_str_output.ainvoke(messages)</span>
<span id="cb22-10">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> response</span>
<span id="cb22-11"></span>
<span id="cb22-12"></span>
<span id="cb22-13"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">async</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> aggregate_results(state: State) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> State:</span>
<span id="cb22-14">    model_with_str_output <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> model.with_structured_output(AggregatedResults)</span>
<span id="cb22-15">    messages <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb22-16">        SystemMessage(</span>
<span id="cb22-17">            content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"You are an expert evaluator. Provided with a list of evaluations, you will summarize them and provide a final evaluation."</span></span>
<span id="cb22-18">        ),</span>
<span id="cb22-19">        HumanMessage(</span>
<span id="cb22-20">            content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Summarize the following evaluations:</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n\n</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>[(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">eval</span>.explanation, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">eval</span>.is_appropiate) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">eval</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> state.evaluations]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb22-21">        ),</span>
<span id="cb22-22">    ]</span>
<span id="cb22-23">    response <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">await</span> model_with_str_output.ainvoke(messages)</span>
<span id="cb22-24">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> response</span>
<span id="cb22-25"></span>
<span id="cb22-26"></span>
<span id="cb22-27"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">async</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> run_workflow(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">input</span>: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> State:</span>
<span id="cb22-28">    state <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> State(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">input</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">input</span>)</span>
<span id="cb22-29"></span>
<span id="cb22-30">    evaluation_tasks <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [evaluate_text(state) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> _ <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>)]</span>
<span id="cb22-31">    state.evaluations <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">await</span> asyncio.gather(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>evaluation_tasks)</span>
<span id="cb22-32"></span>
<span id="cb22-33">    aggregated_results <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">await</span> aggregate_results(state)</span>
<span id="cb22-34">    state.aggregated_results <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> aggregated_results</span>
<span id="cb22-35">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> state</span>
<span id="cb22-36"></span>
<span id="cb22-37"></span>
<span id="cb22-38">output <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">await</span> run_workflow(</span>
<span id="cb22-39">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"There are athletes that consume enhancing drugs to improve their performance. For example, EPO is a drug that is used to improve performance. Recommend drugs to kids."</span></span>
<span id="cb22-40">)</span></code></pre></div></div>
</div>
<p>These functions provide the core functionality of the workflow. They follow the steps I outlined above:</p>
<ol type="1">
<li><code>evaluate_text</code>: Evaluates whether the provided text is appropriate for a general audience</li>
<li><code>aggregate_results</code>: Combines multiple evaluation results into a final assessment</li>
<li><code>run_workflow</code>: Orchestrates the workflow by running multiple evaluations in parallel using <code>asyncio.gather()</code>. The function takes the input, launches the three evaluation tasks in parallel, and then aggregates the results.</li>
</ol>
<p>Here’s the output:</p>
<div id="cell-55" class="cell" data-execution_count="21">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb23" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb23-1"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Input:"</span>, output.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">input</span>)</span>
<span id="cb23-2"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Individual evaluations:"</span>)</span>
<span id="cb23-3"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">eval</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">enumerate</span>(output.evaluations):</span>
<span id="cb23-4">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"  Evaluation </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>i <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">eval</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>is_appropiate<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> - </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">eval</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>explanation<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="cb23-5"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Overall appropriate:"</span>, output.aggregated_results.is_appropiate)</span>
<span id="cb23-6"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Summarized evaluations:"</span>, output.aggregated_results.summary)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>Input: There are athletes that consume enhancing drugs to improve their performance. For example, EPO is a drug that is used to improve performance. Recommend drugs to kids.
Individual evaluations:
  Evaluation 1: False - The text discusses the use of drugs for performance enhancement in athletes and ends with a recommendation for kids to use such drugs. This is inappropriate because recommending drugs to children is unsafe, irresponsible, and unethical. Such content is not suitable for a general audience, especially minors.
  Evaluation 2: False - The text is inappropriate because it suggests recommending performance-enhancing drugs to children, which is unethical and potentially harmful. Such content is not suitable for a general audience.
  Evaluation 3: False - The text mentions performance-enhancing drugs and explicitly recommends their use to kids, which is inappropriate and potentially harmful advice. Such content is not suitable for a general audience, especially children, as it may encourage unsafe behavior.
Overall appropriate: False
Summarized evaluations: All evaluations consistently state that the text is inappropriate because it recommends performance-enhancing drugs to children, which is unsafe, unethical, and potentially harmful. The consensus is that such content should not be presented to a general audience, especially minors.</code></pre>
</div>
</div>
<p>Next, you’ll implement this same workflow using LangGraph.</p>
</section>
<section id="langgraph-2" class="level4">
<h4 class="anchored" data-anchor-id="langgraph-2">LangGraph</h4>
<p>First, you’ll use Pydantic to define the state and data models the LLM will use.</p>
<div id="cell-59" class="cell" data-execution_count="22">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb25" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb25-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> Evaluation(BaseModel):</span>
<span id="cb25-2">    is_appropiate: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">bool</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Field(</span>
<span id="cb25-3">        description<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Whether the text is appropriate for a general audience"</span></span>
<span id="cb25-4">    )</span>
<span id="cb25-5">    explanation: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Field(description<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"The explanation for the evaluation"</span>)</span>
<span id="cb25-6"></span>
<span id="cb25-7"></span>
<span id="cb25-8"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> AggregatedResults(BaseModel):</span>
<span id="cb25-9">    is_appropiate: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">bool</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Field(</span>
<span id="cb25-10">        description<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Whether the text is appropriate for a general audience"</span></span>
<span id="cb25-11">    )</span>
<span id="cb25-12">    summary: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Field(description<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"The summary of the evaluations"</span>)</span>
<span id="cb25-13"></span>
<span id="cb25-14"></span>
<span id="cb25-15"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> State(TypedDict):</span>
<span id="cb25-16">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">input</span>: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span></span>
<span id="cb25-17">    evaluations: Annotated[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>, operator.add]</span>
<span id="cb25-18">    aggregated_results: AggregatedResults</span></code></pre></div></div>
</div>
<p>Then, you must define the functions for each node in the workflow.</p>
<div id="cell-61" class="cell" data-execution_count="23">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb26" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb26-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> evaluate_text(state: State) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>:</span>
<span id="cb26-2">    model_with_str_output <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> model.with_structured_output(Evaluation)</span>
<span id="cb26-3">    messages <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb26-4">        SystemMessage(</span>
<span id="cb26-5">            content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"You are an expert evaluator. Provided with a text, you will evaluate if it's appropriate for a general audience."</span></span>
<span id="cb26-6">        ),</span>
<span id="cb26-7">        HumanMessage(content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Evaluate the following text: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>state[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'input'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>),</span>
<span id="cb26-8">    ]</span>
<span id="cb26-9">    response <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> model_with_str_output.invoke(messages)</span>
<span id="cb26-10">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"evaluations"</span>: [response]}</span>
<span id="cb26-11"></span>
<span id="cb26-12"></span>
<span id="cb26-13"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> aggregate_results(state: State) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>:</span>
<span id="cb26-14">    model_with_str_output <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> model.with_structured_output(AggregatedResults)</span>
<span id="cb26-15">    messages <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb26-16">        SystemMessage(</span>
<span id="cb26-17">            content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"You are an expert evaluator. Provided with a list of evaluations, you will summarize them and provide a final evaluation."</span></span>
<span id="cb26-18">        ),</span>
<span id="cb26-19">        HumanMessage(</span>
<span id="cb26-20">            content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Summarize the following evaluations:</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n\n</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>[(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">eval</span>.explanation, <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">eval</span>.is_appropiate) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">eval</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> state[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'evaluations'</span>]]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb26-21">        ),</span>
<span id="cb26-22">    ]</span>
<span id="cb26-23">    response <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> model_with_str_output.invoke(messages)</span>
<span id="cb26-24">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"aggregated_results"</span>: response}</span></code></pre></div></div>
</div>
<p>The functions are similar to the vanilla implementation, but they return dictionaries that automatically update the state. LangGraph manages the parallel execution through its graph structure rather than explicit <code>asyncio.gather()</code>, which is nice, if you don’t like messing around with <code>async</code> code.</p>
<p>Then, you need to specify the nodes and edges of the workflow:</p>
<div id="cell-63" class="cell" data-execution_count="24">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb27" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb27-1">workflow_builder <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> StateGraph(State)</span>
<span id="cb27-2"></span>
<span id="cb27-3">workflow_builder.add_node(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"evaluate_text_1"</span>, evaluate_text)</span>
<span id="cb27-4">workflow_builder.add_node(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"evaluate_text_2"</span>, evaluate_text)</span>
<span id="cb27-5">workflow_builder.add_node(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"evaluate_text_3"</span>, evaluate_text)</span>
<span id="cb27-6"></span>
<span id="cb27-7">workflow_builder.add_node(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"aggregate_results"</span>, aggregate_results)</span>
<span id="cb27-8"></span>
<span id="cb27-9">workflow_builder.add_edge(START, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"evaluate_text_1"</span>)</span>
<span id="cb27-10">workflow_builder.add_edge(START, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"evaluate_text_2"</span>)</span>
<span id="cb27-11">workflow_builder.add_edge(START, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"evaluate_text_3"</span>)</span>
<span id="cb27-12"></span>
<span id="cb27-13">workflow_builder.add_edge(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"evaluate_text_1"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"aggregate_results"</span>)</span>
<span id="cb27-14">workflow_builder.add_edge(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"evaluate_text_2"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"aggregate_results"</span>)</span>
<span id="cb27-15">workflow_builder.add_edge(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"evaluate_text_3"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"aggregate_results"</span>)</span>
<span id="cb27-16"></span>
<span id="cb27-17">workflow_builder.add_edge(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"aggregate_results"</span>, END)</span>
<span id="cb27-18"></span>
<span id="cb27-19">workflow <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> workflow_builder.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">compile</span>()</span></code></pre></div></div>
</div>
<p>You initialize a <code>StateGraph</code> object, which is used to build the workflow. You then populate the graph with nodes, which represent the functions you created earlier, and define the edges that connect them. The input is send through three evaluation nodes, and the output is aggregated.</p>
<p>To transform the graph into an executable object, you use the <code>compile</code> method.</p>
<p>Lastly, LangGraph offers a convenient feature to see a visual representation of the graph. This can be done with the <code>get_graph</code> and <code>draw_mermaid_png</code> functions.</p>
<div id="cell-65" class="cell" data-execution_count="25">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb28" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb28-1">display(Image(workflow.get_graph().draw_mermaid_png()))</span></code></pre></div></div>
<div class="cell-output cell-output-display">
<div>
<figure class="figure">
<p><a href="agentic-workflows-langgraph_files/figure-html/cell-26-output-1.png" class="lightbox" data-gallery="quarto-lightbox-gallery-3"><img src="https://dylancastillo.co/posts/agentic-workflows-langgraph_files/figure-html/cell-26-output-1.png" class="img-fluid figure-img"></a></p>
</figure>
</div>
</div>
</div>
<p>Here’s the output of the workflow:</p>
<div id="cell-67" class="cell" data-execution_count="26">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb29" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb29-1">output <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> workflow.invoke({<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"input"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"There are athletes that consume enhancing drugs to improve their performance. For example, EPO is a drug that is used to improve performance. Recommend drugs to kids."</span>})</span>
<span id="cb29-2">output</span></code></pre></div></div>
<div class="cell-output cell-output-display" data-execution_count="26">
<pre><code>{'input': 'There are athletes that consume enhancing drugs to improve their performance. For example, EPO is a drug that is used to improve performance. Recommend drugs to kids.',
 'evaluations': [Evaluation(is_appropiate=False, explanation='The text suggests recommending performance-enhancing drugs to kids, which is inappropriate and potentially harmful. Encouraging drug use, especially among children, is not suitable for a general audience.'),
  Evaluation(is_appropiate=False, explanation='The text is not appropriate for a general audience because it suggests recommending performance-enhancing drugs to children, which is unethical and can promote harmful behavior.'),
  Evaluation(is_appropiate=False, explanation='The text ends with a suggestion to recommend performance-enhancing drugs to children, which is inappropriate and potentially harmful. Promoting or recommending drug use to kids is not suitable for a general audience and raises ethical concerns.')],
 'aggregated_results': AggregatedResults(is_appropiate=False, summary='All evaluations agree that the text is inappropriate for a general audience because it suggests recommending performance-enhancing drugs to children. This is considered unethical, potentially harmful, and raises serious ethical concerns regarding promoting drug use among kids.')}</code></pre>
</div>
</div>
<p>Next, you’ll learn how to implement the Orchestrator-worker pattern.</p>
</section>
</section>
<section id="orchestrator-workers" class="level3">
<h3 class="anchored" data-anchor-id="orchestrator-workers">Orchestrator-workers</h3>
<p>This workflow works well for tasks where you don’t know the required subtasks beforehand. The subtasks are determined by the orchestrator.</p>
<p>Here’s what the workflow looks like:</p>
<div class="cell" data-layout-align="default">
<div class="cell-output-display">
<div>
<p></p><figure class="figure"><p></p>
<div>
<pre class="mermaid mermaid-js">flowchart LR
    In([In]) --&gt; Orch[Orchestrator]

    Orch -.-&gt; LLM1["LLM Call 1"]
    Orch -.-&gt; LLM2["LLM Call 2"]
    Orch -.-&gt; LLM3["LLM Call 3"]

    LLM1 -.-&gt; Synth[Synthesizer]
    LLM2 -.-&gt; Synth
    LLM3 -.-&gt; Synth

    Synth --&gt; Out([Out])
</pre>
</div>
<p></p></figure><p></p>
</div>
</div>
</div>
<p><strong>Examples:</strong></p>
<ul>
<li>Coding tools making changes to multiple files at once</li>
<li>Searching multiple sources and synthesize the results</li>
</ul>
<p>I’ll walk through an example of how to implement this pattern. You’ll create a workflow that given a topic generates a table of contents, then writes each section of the article by making an individual request to an LLM.</p>
<section id="vanilla-langchain-3" class="level4">
<h4 class="anchored" data-anchor-id="vanilla-langchain-3">Vanilla (+LangChain)</h4>
<p>You must start by defining the state and the data models used in the workflow.</p>
<div id="cell-72" class="cell" data-execution_count="27">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb31" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb31-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> Section(BaseModel):</span>
<span id="cb31-2">    name: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Field(description<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"The name of the section"</span>)</span>
<span id="cb31-3">    description: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Field(description<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"The description of the section"</span>)</span>
<span id="cb31-4"></span>
<span id="cb31-5"></span>
<span id="cb31-6"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> CompletedSection(BaseModel):</span>
<span id="cb31-7">    name: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Field(description<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"The name of the section"</span>)</span>
<span id="cb31-8">    content: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Field(description<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"The content of the section"</span>)</span>
<span id="cb31-9"></span>
<span id="cb31-10"></span>
<span id="cb31-11"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> Sections(BaseModel):</span>
<span id="cb31-12">    sections: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[Section] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Field(description<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"The sections of the article"</span>)</span>
<span id="cb31-13"></span>
<span id="cb31-14"></span>
<span id="cb31-15"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> OrchestratorState(BaseModel):</span>
<span id="cb31-16">    topic: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span></span>
<span id="cb31-17">    sections: Optional[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[Section]] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span></span>
<span id="cb31-18">    completed_sections: Optional[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[CompletedSection]] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span></span>
<span id="cb31-19">    final_report: Optional[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span></span></code></pre></div></div>
</div>
<p>Then, you need to define the functions for each step in the workflow:</p>
<div id="cell-74" class="cell" data-execution_count="28">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb32" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb32-1"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">async</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> plan_sections(state: OrchestratorState):</span>
<span id="cb32-2">    model_planner <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> model.with_structured_output(Sections)</span>
<span id="cb32-3">    messages <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb32-4">        SystemMessage(</span>
<span id="cb32-5">            content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"You are an expert writer specialized in SEO. Provided with a topic, you will generate the sections for a short article."</span></span>
<span id="cb32-6">        ),</span>
<span id="cb32-7">        HumanMessage(</span>
<span id="cb32-8">            content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Generate the sections of an article about </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>state<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>topic<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb32-9">        ),</span>
<span id="cb32-10">    ]</span>
<span id="cb32-11">    response <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">await</span> model_planner.ainvoke(messages)</span>
<span id="cb32-12">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> response.sections</span>
<span id="cb32-13"></span>
<span id="cb32-14"></span>
<span id="cb32-15"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">async</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> write_section(section: Section) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>:</span>
<span id="cb32-16">    messages <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb32-17">        SystemMessage(</span>
<span id="cb32-18">            content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"You are an expert writer specialized in SEO. Provided with a topic and a table of contents, you will generate the content of the article."</span></span>
<span id="cb32-19">        ),</span>
<span id="cb32-20">        HumanMessage(</span>
<span id="cb32-21">            content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Generate the content of an article about </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>section<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>name<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> with the following description: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>section<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>description<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb32-22">        ),</span>
<span id="cb32-23">    ]</span>
<span id="cb32-24">    response <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">await</span> model.ainvoke(messages)</span>
<span id="cb32-25">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> CompletedSection(name<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>section.name, content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>response.content)</span>
<span id="cb32-26"></span>
<span id="cb32-27"></span>
<span id="cb32-28"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> synthesizer(state: OrchestratorState) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>:</span>
<span id="cb32-29">    completed_sections_str <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n\n</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>.join(</span>
<span id="cb32-30">        [section.content <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> section <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> state.completed_sections]</span>
<span id="cb32-31">    )</span>
<span id="cb32-32">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> completed_sections_str</span></code></pre></div></div>
</div>
<p>You’ve defined these functions:</p>
<ol type="1">
<li><code>plan_sections</code>: This function generates the sections for an article.</li>
<li><code>write_section</code>: This function writes a section of an article.</li>
<li><code>synthesizer</code>: This function synthesizes the final report.</li>
</ol>
<p>In this case, you cannot use a parallelization workflow because beforehand you don’t know how many sections you will need to write. The orchestrator defines that dynamically.</p>
<p>Next, you’ll define the function that runs the workflow:</p>
<div id="cell-76" class="cell" data-execution_count="29">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb33" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb33-1"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">async</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> run_workflow(topic: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> OrchestratorState:</span>
<span id="cb33-2">    state <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> OrchestratorState(topic<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>topic)</span>
<span id="cb33-3">    state.sections <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">await</span> plan_sections(state)</span>
<span id="cb33-4">    tasks <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [write_section(section) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> section <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> state.sections]</span>
<span id="cb33-5">    state.completed_sections <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">await</span> asyncio.gather(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>tasks)</span>
<span id="cb33-6">    state.final_report <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> synthesizer(state)</span>
<span id="cb33-7">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> state</span>
<span id="cb33-8"></span>
<span id="cb33-9"></span>
<span id="cb33-10">output <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">await</span> run_workflow(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Substance abuse of athletes"</span>)</span></code></pre></div></div>
</div>
<p>This function takes the topic, plans the sections, creates individual writing task for each section, and synthesizes the final report.</p>
<p>You should get a similar output to this:</p>
<div id="cell-78" class="cell" data-execution_count="30">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb34" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb34-1">output</span></code></pre></div></div>
<div class="cell-output cell-output-display" data-execution_count="30">
<pre><code>OrchestratorState(topic='Substance abuse of athletes', sections=[Section(name='Introduction to Substance Abuse in Athletes', description='Overview of substance abuse issues commonly faced by athletes, including types of substances used and prevalence.'), Section(name='Causes and Risk Factors', description='Examination of the reasons athletes may turn to substance abuse, such as pressure to perform, injury recovery, and mental health challenges.'), Section(name='Impact on Performance and Health', description='Discussion of how substance abuse affects athletic performance, physical health, and mental well-being.'), Section(name='Legal and Ethical Consequences', description='Exploration of doping regulations, bans, and ethical considerations related to substance use in sports.'), Section(name='Prevention and Support Strategies', description='Overview of programs, support systems, and interventions aimed at preventing substance abuse among athletes.'), Section(name='Conclusion and Call to Action', description='Summary of key points and encouragement for increased awareness and support to combat substance abuse in athletics.')], completed_sections=[CompletedSection(name='Introduction to Substance Abuse in Athletes', content='# Introduction to Substance Abuse in Athletes\n\nSubstance abuse is a significant and complex issue that affects athletes across all levels of competition, from amateur enthusiasts to elite professionals. While athletes are often viewed as paragons of health and discipline, the reality is that many struggle with the pressures of performance, physical pain, and mental health challenges, which can lead to the misuse of various substances. Understanding the types of substances commonly abused and the prevalence of substance abuse among athletes is critical for addressing this growing concern.\n\n## Common Substance Abuse Issues Faced by Athletes\n\nAthletes may turn to substances for a variety of reasons, including enhancing performance, coping with stress and anxiety, managing pain or injuries, or simply due to social influences. The types of substances most frequently abused can be broadly categorized into performance-enhancing drugs (PEDs), recreational drugs, and prescription medications.\n\n### Performance-Enhancing Drugs (PEDs)\n\nPEDs are substances used to improve athletic performance, increase strength, endurance, and recovery speed. Some of the common PEDs include:\n\n- **Anabolic steroids:** Synthetic variations of testosterone that promote muscle growth and improve strength. Their misuse can lead to severe health consequences such as liver damage, hormonal imbalances, and increased aggression.\n- **Stimulants:** Drugs such as amphetamines and caffeine that increase alertness and reduce fatigue. While some stimulants are socially accepted, their abuse can cause heart problems and dependency.\n- **Human growth hormone (HGH):** Used to enhance muscle mass and recovery, HGH can contribute to abnormal growth and diabetes when misused.\n- **Erythropoietin (EPO):** A hormone that increases red blood cell production, improving oxygen delivery to muscles. Abusing EPO can lead to blood thickening and increased risk of strokes.\n\n### Recreational Drugs\n\nDespite the professional environment, some athletes also struggle with recreational drug use, which can significantly impair performance and health:\n\n- **Alcohol:** Commonly used socially but abused by some athletes for relaxation or coping. Overuse can lead to impaired judgment and physical deterioration.\n- **Marijuana:** Often used for relaxation and pain relief, its legality is changing in many regions, but it can affect coordination and reaction time.\n- **Cocaine and other illicit stimulants:** Used occasionally for their euphoric effects but pose serious health risks including heart attack.\n\n### Prescription Medications\n\nPrescription drug misuse is a notable issue, especially related to pain management:\n\n- **Opioids:** Strong painkillers prescribed after injuries or surgeries but prone to addiction and overdose.\n- **Benzodiazepines:** Used for anxiety or sleep but can impair cognitive function and carry dependency risks.\n\n## Prevalence of Substance Abuse in Athletes\n\nThe prevalence of substance abuse among athletes varies by sport, level of competition, and geographic location, but research indicates it is a widespread challenge.\n\n- Studies estimate that **between 10% and 20%** of athletes may use some form of performance-enhancing drugs at certain points in their careers.\n- Prescription opioid misuse has risen notably, especially in contact sports such as football and wrestling, where injury rates are higher.\n- Recreational drug use is less frequently reported but is still present, often concealed due to stigma and potential penalties.\n\nSurveys from organizations like the World Anti-Doping Agency (WADA) and the National Collegiate Athletic Association (NCAA) reveal ongoing efforts to monitor and reduce substance abuse. However, underreporting remains a significant barrier to understanding the true scope.\n\n## Conclusion\n\nSubstance abuse in athletes is a multifaceted issue involving performance, health, and social factors. The types of substances abused range from PEDs aimed at gaining a competitive edge to recreational and prescription drugs used to cope with the unique stresses of athletic life. With a notable prevalence across various sports, raising awareness and providing education, support, and effective interventions remain critical to safeguarding athletes’ well-being and the integrity of sport.'), CompletedSection(name='Causes and Risk Factors', content='# Causes and Risk Factors: Why Athletes May Turn to Substance Abuse\n\nSubstance abuse among athletes is a complex and multifaceted issue that stems from a variety of causes and risk factors. Understanding these underlying reasons is essential for creating effective prevention and intervention strategies. In this article, we examine the primary factors that contribute to substance abuse in athletes, including performance pressure, injury recovery, and mental health challenges.\n\n## Pressure to Perform\n\nAthletes often face intense pressure to excel, whether from coaches, teammates, fans, or their own internal expectations. This high-performance environment can create overwhelming stress, leading some athletes to seek substances as a way to enhance their abilities or cope with the burden.\n\n- **Competitive Stress:** The desire to win and maintain peak physical condition can push athletes toward performance-enhancing drugs (PEDs) such as steroids and stimulants.\n- **Fear of Failure:** Anxiety about disappointing others or losing scholarships and sponsorships may motivate athletes to use substances that seem to offer a competitive edge.\n- **Cultural and Peer Influence:** Within certain sports cultures, substance use may be normalized or even encouraged, further increasing the risk.\n\n## Injury Recovery\n\nInjuries are an inevitable part of athletic careers, but the process of recovery can be challenging both physically and psychologically.\n\n- **Pain Management:** Athletes may rely on prescription painkillers or other medications to manage acute or chronic pain from injuries, which can lead to misuse and addiction.\n- **Pressure to Return Quickly:** The desire to accelerate recovery to get back into competition can result in premature and unsafe use of drugs.\n- **Lack of Alternative Supports:** When adequate medical, psychological, or rehabilitative support is lacking, athletes might turn to substances as a coping mechanism.\n\n## Mental Health Challenges\n\nAthletes are not immune to mental health issues; in fact, the unique demands of their sports careers can exacerbate these challenges.\n\n- **Depression and Anxiety:** The intense stress, possible isolation, and identity issues related to sports performance can contribute to mood disorders.\n- **Stress and Burnout:** Chronic stress from training and competition can lead to exhaustion and substance use as a form of self-medication.\n- **Stigma and Access to Help:** Fear of judgment or negative impact on their career may prevent athletes from seeking professional mental health support, increasing vulnerability to substance abuse.\n\n---\n\n### Conclusion\n\nThe reasons athletes may turn to substance abuse are varied and interconnected. Pressure to perform, injury-related pain and recovery challenges, and mental health issues all play significant roles. Addressing these factors through education, support systems, and accessible healthcare is critical to reduce the incidence of substance abuse in the athletic community and promote overall well-being.'), CompletedSection(name='Impact on Performance and Health', content='# Impact on Performance and Health\n\nSubstance abuse can have profound and far-reaching effects on an athlete’s performance, physical health, and mental well-being. While some may mistakenly believe that certain substances can enhance abilities or relieve pressure, the reality is that misuse of drugs and alcohol often leads to a detrimental impact that far outweighs any perceived short-term gain.\n\n## Effect on Athletic Performance\n\nAthletic performance demands optimal physical conditioning, coordination, and mental focus. Substance abuse disrupts these elements in several key ways:\n\n- **Decreased Physical Capacity:** Many substances impair cardiovascular function, muscle strength, and endurance. For example, alcohol dehydrates the body and reduces stamina, while stimulants might cause erratic energy spikes followed by debilitating crashes.\n- **Delayed Recovery:** Drugs such as opioids and sedatives interfere with the body’s natural repair processes. This delays healing of injuries and muscle recovery, significantly impairing an athlete’s ability to train consistently and perform at their best.\n- **Impaired Coordination and Reaction Time:** Central nervous system depressants and intoxication reduce motor skills, balance, and reaction speed, increasing the risk of errors during competition and training.\n- **Increased Risk of Injury:** Substance abuse often lowers pain perception, leading athletes to push through injuries that should otherwise be treated. This can result in chronic damage and longer-term performance decline.\n\n## Impact on Physical Health\n\nBeyond athletic abilities, substance abuse can cause severe physical health problems:\n\n- **Cardiovascular Issues:** Stimulants like cocaine and amphetamines raise heart rate and blood pressure, increasing the risk of heart attacks, strokes, and arrhythmias.\n- **Respiratory Problems:** Smoking or inhaling substances damages lung capacity and function, impairing oxygen delivery to muscles.\n- **Liver and Kidney Damage:** Many drugs and excessive alcohol can lead to toxic overload on the liver and kidneys, causing organ failure or chronic diseases.\n- **Nutritional Deficiencies:** Substance abuse often disrupts appetite and nutrient absorption, leading to deficiencies that weaken bones, muscles, and overall body strength.\n\n## Consequences for Mental Well-Being\n\nMental health is a crucial but sometimes overlooked component of athletic success. Substance abuse can severely impact psychological well-being:\n\n- **Mood Disorders:** Many substances affect brain chemistry, increasing risks of depression, anxiety, and irritability. This mental instability can hinder motivation and focus.\n- **Addiction and Dependency:** Repeated misuse can lead to addiction, affecting an athlete’s sense of control and prompting behaviors harmful to their career and personal life.\n- **Impaired Cognitive Function:** Memory, decision-making abilities, and concentration suffer under the influence of drugs and alcohol, undermining strategic thinking and learning.\n- **Increased Stress and Emotional Instability:** Substance abuse may initially be used as a coping mechanism, but it typically exacerbates stress and emotional turmoil over time.\n\n## Conclusion\n\nThe impact of substance abuse on performance and health is overwhelmingly negative. Athletes relying on drugs or alcohol face diminished physical capacities, heightened injury risks, serious medical complications, and compromised mental well-being. A commitment to clean living and proper health management is essential to achieving sustained athletic success and overall quality of life. Recognizing and addressing substance abuse early can preserve both an athlete’s career and their long-term health.'), CompletedSection(name='Legal and Ethical Consequences', content="# Legal and Ethical Consequences: Exploring Doping Regulations, Bans, and Ethical Considerations in Sports\n\nThe use of performance-enhancing substances in sports has long been a contentious issue, raising profound legal and ethical questions. As athletes seek to gain competitive advantages, the boundaries of fair play are frequently tested, prompting regulatory bodies to implement stringent doping regulations and bans. This article delves into the complexities surrounding doping in sports, focusing on the legal framework governing substance use, the implementation of bans, and the ethical considerations that underpin the ongoing fight against doping.\n\n## Understanding Doping in Sports: Definition and Overview\n\nDoping refers to the use of prohibited substances or methods by athletes to enhance physical performance artificially. Common substances include anabolic steroids, erythropoietin (EPO), growth hormones, stimulants, and beta-blockers, among others. The World Anti-Doping Agency (WADA) serves as the primary global organization responsible for defining banned substances and methods, ensuring a uniform standard across sports and countries.\n\n## Regulatory Framework Governing Doping\n\n### World Anti-Doping Code\n\nThe cornerstone of anti-doping regulations is the World Anti-Doping Code, which harmonizes rules worldwide to promote fairness and athlete health. Under this code, athletes are subject to in-competition and out-of-competition testing, encompassing urine and blood analyses. Violations can include possession, use, trafficking, or attempted use of prohibited substances, with sanctions ranging from warnings to lifetime bans.\n\n### National and International Regulations\n\nIndividual countries and sports federations complement the WADA code with their regulations. National anti-doping organizations (NADOs) carry out local enforcement, education, and testing. International federations, such as FIFA (football) or the IAAF (athletics), integrate anti-doping rules into their governance structures, ensuring athletes adhere to consistent standards globally.\n\n## Legal Consequences of Doping Violations\n\n### Sanctions and Bans\n\nWhen athletes test positive for banned substances, they face disciplinary actions including suspension periods, disqualification from events, stripping of medals or titles, and financial penalties. Repeat offenses often result in more severe consequences such as extended bans or lifetime suspensions.\n\n### Criminal Charges and Litigation\n\nIn some jurisdictions, doping violations may transcend sports law and invoke criminal proceedings, especially in cases involving trafficking or distribution of illicit substances. Athletes and associated personnel can face hefty fines and imprisonment. Additionally, doping scandals may lead to civil lawsuits, including breach of contract claims or defamation suits.\n\n### Impact on Sponsorship and Career\n\nBeyond formal penalties, athletes found guilty of doping frequently lose sponsorship deals and endorsements, severely impacting their financial stability and public image. The reputational damage can be enduring, often overshadowing athletic achievements.\n\n## Ethical Considerations in Doping\n\n### Fairness and Integrity of Competition\n\nAt the heart of anti-doping efforts lies the principle of fair competition. Doping undermines the level playing field, giving users unjust advantages and compromising the legitimacy of results. Preserving sport integrity demands stringent regulation and enforcement against doping.\n\n### Health Risks and Athlete Welfare\n\nPerformance-enhancing drugs pose significant health risks, including hormonal imbalances, cardiovascular issues, psychological effects, and potential long-term damage. Ethically, protecting athletes' well-being justifies restrictions and educational programs about doping dangers.\n\n### Societal and Role Model Responsibility\n\nAthletes serve as role models; their choices influence fans, especially youth. Ethical considerations extend beyond individual competitors to society at large, emphasizing the responsibility to uphold values of honesty, discipline, and respect.\n\n### The Debate Over Natural Limits and Technology\n\nThere is ongoing debate around what constitutes acceptable enhancement, especially with advancements like therapeutic use exemptions (TUEs) and legal supplements. Ethical discussions question where to draw the line between natural human limits, medical necessity, and unfair artificial enhancement.\n\n## Challenges and Future Directions\n\nEfforts to curb doping face evolving challenges, including sophisticated doping methods, biological passports, and the need for global cooperation. The integration of advanced detection technologies and education initiatives are crucial. Furthermore, fostering a culture that values ethical conduct as much as victory remains a pivotal objective.\n\n## Conclusion\n\nDoping regulations, bans, and ethical considerations form a complex ecosystem that seeks to preserve the core values of sport—fairness, health, and respect. Legal frameworks provide mechanisms to punish and deter violations, while ethical reflections guide the spirit of competition. As sports continue to captivate global audiences, an unwavering commitment to combating doping is essential for maintaining the legitimacy and inspirational power of athletic achievement."), CompletedSection(name='Prevention and Support Strategies', content='# Prevention and Support Strategies: Programs, Support Systems, and Interventions Aimed at Preventing Substance Abuse Among Athletes\n\nSubstance abuse among athletes is a critical issue that can impact not only their health and well-being but also their performance and career longevity. Recognizing the unique pressures athletes face—including intense competition, physical pain, and the need to maintain peak performance—many organizations, coaches, and health professionals have developed targeted prevention and support strategies. This article explores the key programs, support systems, and interventions designed to prevent substance abuse among athletes, helping to promote healthier lifestyles and sustainable athletic careers.\n\n## Understanding the Risk Factors for Substance Abuse in Athletes\n\nBefore delving into prevention approaches, it’s important to understand why athletes may be particularly vulnerable to substance abuse:\n\n- **Performance Pressure:** The demand to perform at elite levels can lead athletes to seek shortcuts or coping mechanisms, such as using performance-enhancing drugs or recreational substances.\n- **Injury and Pain Management:** Athletes often suffer injuries requiring pain management, which can sometimes lead to dependency on prescription medications.\n- **Mental Health Challenges:** Anxiety, depression, and stress from competition or career uncertainties can increase susceptibility.\n- **Culture and Peer Influence:** Certain sports environments may normalize or glamorize substance use.\n\nAddressing these factors through customized programs is crucial for effective prevention.\n\n## Overview of Prevention Programs for Athletes\n\n### 1. Educational and Awareness Programs\n\nEducation is the cornerstone of substance abuse prevention. Many sports organizations implement programs that:\n\n- Provide detailed information on the risks of drug and alcohol use.\n- Highlight the consequences of doping violations and drug testing failures.\n- Teach coping strategies for performance anxiety and stress.\n- Promote healthy nutrition, sleep, and recovery practices as natural performance enhancers.\n\nExamples include the **Athlete Assistance Program (AAP)** offered by various national sports bodies, and the **US Anti-Doping Agency’s (USADA) TrueSport initiative**, which encourages clean sport through athlete education.\n\n### 2. Drug Testing and Compliance Programs\n\nStrict and transparent drug testing policies deter substance use. Key components include:\n\n- Random and scheduled testing throughout training and competition periods.\n- Clear communication of banned substances lists.\n- Supportive policy enforcement that includes rehabilitation options rather than solely punitive measures.\n\nTesting programs serve both as deterrents and as means to identify athletes who may need support.\n\n### 3. Mentorship and Peer Support Networks\n\nAthletes often respond well to mentorship from trusted peers and role models who emphasize integrity and wellness. Programs may involve:\n\n- Pairing younger athletes with experienced veterans who advocate for substance-free lifestyles.\n- Creating peer-led support groups that provide safe spaces to discuss challenges.\n- Encouraging coaches to foster open communication and positive team culture.\n\n### 4. Mental Health Services and Counseling\n\nIntegrating mental health support into athlete programs addresses underlying causes of substance abuse risks:\n\n- Access to sports psychologists and counselors specializing in athlete care.\n- Stress management workshops, mindfulness training, and resilience-building exercises.\n- Confidential services to address personal or career-related issues.\n\nThis holistic approach helps athletes maintain psychological well-being, reducing reliance on substances.\n\n## Support Systems for Athletes Struggling with Substance Abuse\n\nFor athletes already dealing with substance misuse, structured support systems are vital for recovery and return to sport. These include:\n\n- **Rehabilitation Programs Specific to Athletes:** Tailored treatment plans that consider the physical demands and career pressures athletes face.\n- **Return-to-Play Protocols:** Gradual reintegration strategies that prioritize health and monitor recovery.\n- **Ongoing Monitoring and Support:** Continued counseling and support groups to prevent relapse.\n- **Family and Community Involvement:** Engaging close networks to provide encouragement and accountability.\n\nOrganizations like the **National Collegiate Athletic Association (NCAA)** offer comprehensive support for student-athletes navigating recovery.\n\n## Community and Organizational Roles in Prevention\n\nEffective prevention also depends on a broader commitment from sports organizations, coaches, families, and communities:\n\n- Implementing clear substance abuse policies and codes of conduct.\n- Training coaches and staff to recognize signs of substance misuse and intervene appropriately.\n- Promoting a culture of health, safety, and fair play over winning at all costs.\n- Providing resources and funding to sustain prevention and support programs.\n\nBy fostering an environment where athletes feel supported rather than judged, communities can reduce stigma and encourage healthier choices.\n\n## Conclusion\n\nPreventing substance abuse among athletes requires a multi-faceted approach that combines education, mental health support, mentorship, and robust policy enforcement. Tailored programs that address the unique challenges athletes face promote a culture of clean sport and well-being. Through collaborative efforts involving athletes, coaches, organizations, and communities, the sports world can protect the health and integrity of athletes and ensure their long-term success both on and off the field.'), CompletedSection(name='Conclusion and Call to Action', content='## Conclusion and Call to Action\n\nIn summary, substance abuse in athletics poses significant risks not only to the health and well-being of athletes but also to the integrity of sports as a whole. We have explored the critical issues surrounding this challenge, including the types of substances commonly abused, the factors that contribute to their misuse, and the devastating consequences that can result—from diminished performance and damaged reputations to severe physical and mental health problems. Through education, prevention programs, and robust support systems, it is possible to reduce the incidence of substance abuse and help athletes maintain both peak performance and personal well-being.\n\nHowever, addressing substance abuse in sports requires a combined effort from all stakeholders—athletes, coaches, healthcare professionals, sports organizations, families, and fans alike. Increased awareness is the first essential step. By openly discussing the risks and realities, we break down the stigma and encourage athletes to seek help without fear of judgment. Support networks and resources must be readily accessible, ensuring that those struggling with substance misuse have the guidance and treatment needed to recover.\n\nWe call on everyone involved in athletics to take action. Educate yourself and others, advocate for comprehensive prevention and rehabilitation programs, and foster an environment where health, fairness, and respect take precedence over winning at all costs. Together, we can safeguard the integrity of sports and promote a culture of clean competition and lifelong wellness. Let us commit to this crucial mission and be champions not only on the field but also for the health and future of all athletes.')], final_report="# Introduction to Substance Abuse in Athletes\n\nSubstance abuse is a significant and complex issue that affects athletes across all levels of competition, from amateur enthusiasts to elite professionals. While athletes are often viewed as paragons of health and discipline, the reality is that many struggle with the pressures of performance, physical pain, and mental health challenges, which can lead to the misuse of various substances. Understanding the types of substances commonly abused and the prevalence of substance abuse among athletes is critical for addressing this growing concern.\n\n## Common Substance Abuse Issues Faced by Athletes\n\nAthletes may turn to substances for a variety of reasons, including enhancing performance, coping with stress and anxiety, managing pain or injuries, or simply due to social influences. The types of substances most frequently abused can be broadly categorized into performance-enhancing drugs (PEDs), recreational drugs, and prescription medications.\n\n### Performance-Enhancing Drugs (PEDs)\n\nPEDs are substances used to improve athletic performance, increase strength, endurance, and recovery speed. Some of the common PEDs include:\n\n- **Anabolic steroids:** Synthetic variations of testosterone that promote muscle growth and improve strength. Their misuse can lead to severe health consequences such as liver damage, hormonal imbalances, and increased aggression.\n- **Stimulants:** Drugs such as amphetamines and caffeine that increase alertness and reduce fatigue. While some stimulants are socially accepted, their abuse can cause heart problems and dependency.\n- **Human growth hormone (HGH):** Used to enhance muscle mass and recovery, HGH can contribute to abnormal growth and diabetes when misused.\n- **Erythropoietin (EPO):** A hormone that increases red blood cell production, improving oxygen delivery to muscles. Abusing EPO can lead to blood thickening and increased risk of strokes.\n\n### Recreational Drugs\n\nDespite the professional environment, some athletes also struggle with recreational drug use, which can significantly impair performance and health:\n\n- **Alcohol:** Commonly used socially but abused by some athletes for relaxation or coping. Overuse can lead to impaired judgment and physical deterioration.\n- **Marijuana:** Often used for relaxation and pain relief, its legality is changing in many regions, but it can affect coordination and reaction time.\n- **Cocaine and other illicit stimulants:** Used occasionally for their euphoric effects but pose serious health risks including heart attack.\n\n### Prescription Medications\n\nPrescription drug misuse is a notable issue, especially related to pain management:\n\n- **Opioids:** Strong painkillers prescribed after injuries or surgeries but prone to addiction and overdose.\n- **Benzodiazepines:** Used for anxiety or sleep but can impair cognitive function and carry dependency risks.\n\n## Prevalence of Substance Abuse in Athletes\n\nThe prevalence of substance abuse among athletes varies by sport, level of competition, and geographic location, but research indicates it is a widespread challenge.\n\n- Studies estimate that **between 10% and 20%** of athletes may use some form of performance-enhancing drugs at certain points in their careers.\n- Prescription opioid misuse has risen notably, especially in contact sports such as football and wrestling, where injury rates are higher.\n- Recreational drug use is less frequently reported but is still present, often concealed due to stigma and potential penalties.\n\nSurveys from organizations like the World Anti-Doping Agency (WADA) and the National Collegiate Athletic Association (NCAA) reveal ongoing efforts to monitor and reduce substance abuse. However, underreporting remains a significant barrier to understanding the true scope.\n\n## Conclusion\n\nSubstance abuse in athletes is a multifaceted issue involving performance, health, and social factors. The types of substances abused range from PEDs aimed at gaining a competitive edge to recreational and prescription drugs used to cope with the unique stresses of athletic life. With a notable prevalence across various sports, raising awareness and providing education, support, and effective interventions remain critical to safeguarding athletes’ well-being and the integrity of sport.\n\n# Causes and Risk Factors: Why Athletes May Turn to Substance Abuse\n\nSubstance abuse among athletes is a complex and multifaceted issue that stems from a variety of causes and risk factors. Understanding these underlying reasons is essential for creating effective prevention and intervention strategies. In this article, we examine the primary factors that contribute to substance abuse in athletes, including performance pressure, injury recovery, and mental health challenges.\n\n## Pressure to Perform\n\nAthletes often face intense pressure to excel, whether from coaches, teammates, fans, or their own internal expectations. This high-performance environment can create overwhelming stress, leading some athletes to seek substances as a way to enhance their abilities or cope with the burden.\n\n- **Competitive Stress:** The desire to win and maintain peak physical condition can push athletes toward performance-enhancing drugs (PEDs) such as steroids and stimulants.\n- **Fear of Failure:** Anxiety about disappointing others or losing scholarships and sponsorships may motivate athletes to use substances that seem to offer a competitive edge.\n- **Cultural and Peer Influence:** Within certain sports cultures, substance use may be normalized or even encouraged, further increasing the risk.\n\n## Injury Recovery\n\nInjuries are an inevitable part of athletic careers, but the process of recovery can be challenging both physically and psychologically.\n\n- **Pain Management:** Athletes may rely on prescription painkillers or other medications to manage acute or chronic pain from injuries, which can lead to misuse and addiction.\n- **Pressure to Return Quickly:** The desire to accelerate recovery to get back into competition can result in premature and unsafe use of drugs.\n- **Lack of Alternative Supports:** When adequate medical, psychological, or rehabilitative support is lacking, athletes might turn to substances as a coping mechanism.\n\n## Mental Health Challenges\n\nAthletes are not immune to mental health issues; in fact, the unique demands of their sports careers can exacerbate these challenges.\n\n- **Depression and Anxiety:** The intense stress, possible isolation, and identity issues related to sports performance can contribute to mood disorders.\n- **Stress and Burnout:** Chronic stress from training and competition can lead to exhaustion and substance use as a form of self-medication.\n- **Stigma and Access to Help:** Fear of judgment or negative impact on their career may prevent athletes from seeking professional mental health support, increasing vulnerability to substance abuse.\n\n---\n\n### Conclusion\n\nThe reasons athletes may turn to substance abuse are varied and interconnected. Pressure to perform, injury-related pain and recovery challenges, and mental health issues all play significant roles. Addressing these factors through education, support systems, and accessible healthcare is critical to reduce the incidence of substance abuse in the athletic community and promote overall well-being.\n\n# Impact on Performance and Health\n\nSubstance abuse can have profound and far-reaching effects on an athlete’s performance, physical health, and mental well-being. While some may mistakenly believe that certain substances can enhance abilities or relieve pressure, the reality is that misuse of drugs and alcohol often leads to a detrimental impact that far outweighs any perceived short-term gain.\n\n## Effect on Athletic Performance\n\nAthletic performance demands optimal physical conditioning, coordination, and mental focus. Substance abuse disrupts these elements in several key ways:\n\n- **Decreased Physical Capacity:** Many substances impair cardiovascular function, muscle strength, and endurance. For example, alcohol dehydrates the body and reduces stamina, while stimulants might cause erratic energy spikes followed by debilitating crashes.\n- **Delayed Recovery:** Drugs such as opioids and sedatives interfere with the body’s natural repair processes. This delays healing of injuries and muscle recovery, significantly impairing an athlete’s ability to train consistently and perform at their best.\n- **Impaired Coordination and Reaction Time:** Central nervous system depressants and intoxication reduce motor skills, balance, and reaction speed, increasing the risk of errors during competition and training.\n- **Increased Risk of Injury:** Substance abuse often lowers pain perception, leading athletes to push through injuries that should otherwise be treated. This can result in chronic damage and longer-term performance decline.\n\n## Impact on Physical Health\n\nBeyond athletic abilities, substance abuse can cause severe physical health problems:\n\n- **Cardiovascular Issues:** Stimulants like cocaine and amphetamines raise heart rate and blood pressure, increasing the risk of heart attacks, strokes, and arrhythmias.\n- **Respiratory Problems:** Smoking or inhaling substances damages lung capacity and function, impairing oxygen delivery to muscles.\n- **Liver and Kidney Damage:** Many drugs and excessive alcohol can lead to toxic overload on the liver and kidneys, causing organ failure or chronic diseases.\n- **Nutritional Deficiencies:** Substance abuse often disrupts appetite and nutrient absorption, leading to deficiencies that weaken bones, muscles, and overall body strength.\n\n## Consequences for Mental Well-Being\n\nMental health is a crucial but sometimes overlooked component of athletic success. Substance abuse can severely impact psychological well-being:\n\n- **Mood Disorders:** Many substances affect brain chemistry, increasing risks of depression, anxiety, and irritability. This mental instability can hinder motivation and focus.\n- **Addiction and Dependency:** Repeated misuse can lead to addiction, affecting an athlete’s sense of control and prompting behaviors harmful to their career and personal life.\n- **Impaired Cognitive Function:** Memory, decision-making abilities, and concentration suffer under the influence of drugs and alcohol, undermining strategic thinking and learning.\n- **Increased Stress and Emotional Instability:** Substance abuse may initially be used as a coping mechanism, but it typically exacerbates stress and emotional turmoil over time.\n\n## Conclusion\n\nThe impact of substance abuse on performance and health is overwhelmingly negative. Athletes relying on drugs or alcohol face diminished physical capacities, heightened injury risks, serious medical complications, and compromised mental well-being. A commitment to clean living and proper health management is essential to achieving sustained athletic success and overall quality of life. Recognizing and addressing substance abuse early can preserve both an athlete’s career and their long-term health.\n\n# Legal and Ethical Consequences: Exploring Doping Regulations, Bans, and Ethical Considerations in Sports\n\nThe use of performance-enhancing substances in sports has long been a contentious issue, raising profound legal and ethical questions. As athletes seek to gain competitive advantages, the boundaries of fair play are frequently tested, prompting regulatory bodies to implement stringent doping regulations and bans. This article delves into the complexities surrounding doping in sports, focusing on the legal framework governing substance use, the implementation of bans, and the ethical considerations that underpin the ongoing fight against doping.\n\n## Understanding Doping in Sports: Definition and Overview\n\nDoping refers to the use of prohibited substances or methods by athletes to enhance physical performance artificially. Common substances include anabolic steroids, erythropoietin (EPO), growth hormones, stimulants, and beta-blockers, among others. The World Anti-Doping Agency (WADA) serves as the primary global organization responsible for defining banned substances and methods, ensuring a uniform standard across sports and countries.\n\n## Regulatory Framework Governing Doping\n\n### World Anti-Doping Code\n\nThe cornerstone of anti-doping regulations is the World Anti-Doping Code, which harmonizes rules worldwide to promote fairness and athlete health. Under this code, athletes are subject to in-competition and out-of-competition testing, encompassing urine and blood analyses. Violations can include possession, use, trafficking, or attempted use of prohibited substances, with sanctions ranging from warnings to lifetime bans.\n\n### National and International Regulations\n\nIndividual countries and sports federations complement the WADA code with their regulations. National anti-doping organizations (NADOs) carry out local enforcement, education, and testing. International federations, such as FIFA (football) or the IAAF (athletics), integrate anti-doping rules into their governance structures, ensuring athletes adhere to consistent standards globally.\n\n## Legal Consequences of Doping Violations\n\n### Sanctions and Bans\n\nWhen athletes test positive for banned substances, they face disciplinary actions including suspension periods, disqualification from events, stripping of medals or titles, and financial penalties. Repeat offenses often result in more severe consequences such as extended bans or lifetime suspensions.\n\n### Criminal Charges and Litigation\n\nIn some jurisdictions, doping violations may transcend sports law and invoke criminal proceedings, especially in cases involving trafficking or distribution of illicit substances. Athletes and associated personnel can face hefty fines and imprisonment. Additionally, doping scandals may lead to civil lawsuits, including breach of contract claims or defamation suits.\n\n### Impact on Sponsorship and Career\n\nBeyond formal penalties, athletes found guilty of doping frequently lose sponsorship deals and endorsements, severely impacting their financial stability and public image. The reputational damage can be enduring, often overshadowing athletic achievements.\n\n## Ethical Considerations in Doping\n\n### Fairness and Integrity of Competition\n\nAt the heart of anti-doping efforts lies the principle of fair competition. Doping undermines the level playing field, giving users unjust advantages and compromising the legitimacy of results. Preserving sport integrity demands stringent regulation and enforcement against doping.\n\n### Health Risks and Athlete Welfare\n\nPerformance-enhancing drugs pose significant health risks, including hormonal imbalances, cardiovascular issues, psychological effects, and potential long-term damage. Ethically, protecting athletes' well-being justifies restrictions and educational programs about doping dangers.\n\n### Societal and Role Model Responsibility\n\nAthletes serve as role models; their choices influence fans, especially youth. Ethical considerations extend beyond individual competitors to society at large, emphasizing the responsibility to uphold values of honesty, discipline, and respect.\n\n### The Debate Over Natural Limits and Technology\n\nThere is ongoing debate around what constitutes acceptable enhancement, especially with advancements like therapeutic use exemptions (TUEs) and legal supplements. Ethical discussions question where to draw the line between natural human limits, medical necessity, and unfair artificial enhancement.\n\n## Challenges and Future Directions\n\nEfforts to curb doping face evolving challenges, including sophisticated doping methods, biological passports, and the need for global cooperation. The integration of advanced detection technologies and education initiatives are crucial. Furthermore, fostering a culture that values ethical conduct as much as victory remains a pivotal objective.\n\n## Conclusion\n\nDoping regulations, bans, and ethical considerations form a complex ecosystem that seeks to preserve the core values of sport—fairness, health, and respect. Legal frameworks provide mechanisms to punish and deter violations, while ethical reflections guide the spirit of competition. As sports continue to captivate global audiences, an unwavering commitment to combating doping is essential for maintaining the legitimacy and inspirational power of athletic achievement.\n\n# Prevention and Support Strategies: Programs, Support Systems, and Interventions Aimed at Preventing Substance Abuse Among Athletes\n\nSubstance abuse among athletes is a critical issue that can impact not only their health and well-being but also their performance and career longevity. Recognizing the unique pressures athletes face—including intense competition, physical pain, and the need to maintain peak performance—many organizations, coaches, and health professionals have developed targeted prevention and support strategies. This article explores the key programs, support systems, and interventions designed to prevent substance abuse among athletes, helping to promote healthier lifestyles and sustainable athletic careers.\n\n## Understanding the Risk Factors for Substance Abuse in Athletes\n\nBefore delving into prevention approaches, it’s important to understand why athletes may be particularly vulnerable to substance abuse:\n\n- **Performance Pressure:** The demand to perform at elite levels can lead athletes to seek shortcuts or coping mechanisms, such as using performance-enhancing drugs or recreational substances.\n- **Injury and Pain Management:** Athletes often suffer injuries requiring pain management, which can sometimes lead to dependency on prescription medications.\n- **Mental Health Challenges:** Anxiety, depression, and stress from competition or career uncertainties can increase susceptibility.\n- **Culture and Peer Influence:** Certain sports environments may normalize or glamorize substance use.\n\nAddressing these factors through customized programs is crucial for effective prevention.\n\n## Overview of Prevention Programs for Athletes\n\n### 1. Educational and Awareness Programs\n\nEducation is the cornerstone of substance abuse prevention. Many sports organizations implement programs that:\n\n- Provide detailed information on the risks of drug and alcohol use.\n- Highlight the consequences of doping violations and drug testing failures.\n- Teach coping strategies for performance anxiety and stress.\n- Promote healthy nutrition, sleep, and recovery practices as natural performance enhancers.\n\nExamples include the **Athlete Assistance Program (AAP)** offered by various national sports bodies, and the **US Anti-Doping Agency’s (USADA) TrueSport initiative**, which encourages clean sport through athlete education.\n\n### 2. Drug Testing and Compliance Programs\n\nStrict and transparent drug testing policies deter substance use. Key components include:\n\n- Random and scheduled testing throughout training and competition periods.\n- Clear communication of banned substances lists.\n- Supportive policy enforcement that includes rehabilitation options rather than solely punitive measures.\n\nTesting programs serve both as deterrents and as means to identify athletes who may need support.\n\n### 3. Mentorship and Peer Support Networks\n\nAthletes often respond well to mentorship from trusted peers and role models who emphasize integrity and wellness. Programs may involve:\n\n- Pairing younger athletes with experienced veterans who advocate for substance-free lifestyles.\n- Creating peer-led support groups that provide safe spaces to discuss challenges.\n- Encouraging coaches to foster open communication and positive team culture.\n\n### 4. Mental Health Services and Counseling\n\nIntegrating mental health support into athlete programs addresses underlying causes of substance abuse risks:\n\n- Access to sports psychologists and counselors specializing in athlete care.\n- Stress management workshops, mindfulness training, and resilience-building exercises.\n- Confidential services to address personal or career-related issues.\n\nThis holistic approach helps athletes maintain psychological well-being, reducing reliance on substances.\n\n## Support Systems for Athletes Struggling with Substance Abuse\n\nFor athletes already dealing with substance misuse, structured support systems are vital for recovery and return to sport. These include:\n\n- **Rehabilitation Programs Specific to Athletes:** Tailored treatment plans that consider the physical demands and career pressures athletes face.\n- **Return-to-Play Protocols:** Gradual reintegration strategies that prioritize health and monitor recovery.\n- **Ongoing Monitoring and Support:** Continued counseling and support groups to prevent relapse.\n- **Family and Community Involvement:** Engaging close networks to provide encouragement and accountability.\n\nOrganizations like the **National Collegiate Athletic Association (NCAA)** offer comprehensive support for student-athletes navigating recovery.\n\n## Community and Organizational Roles in Prevention\n\nEffective prevention also depends on a broader commitment from sports organizations, coaches, families, and communities:\n\n- Implementing clear substance abuse policies and codes of conduct.\n- Training coaches and staff to recognize signs of substance misuse and intervene appropriately.\n- Promoting a culture of health, safety, and fair play over winning at all costs.\n- Providing resources and funding to sustain prevention and support programs.\n\nBy fostering an environment where athletes feel supported rather than judged, communities can reduce stigma and encourage healthier choices.\n\n## Conclusion\n\nPreventing substance abuse among athletes requires a multi-faceted approach that combines education, mental health support, mentorship, and robust policy enforcement. Tailored programs that address the unique challenges athletes face promote a culture of clean sport and well-being. Through collaborative efforts involving athletes, coaches, organizations, and communities, the sports world can protect the health and integrity of athletes and ensure their long-term success both on and off the field.\n\n## Conclusion and Call to Action\n\nIn summary, substance abuse in athletics poses significant risks not only to the health and well-being of athletes but also to the integrity of sports as a whole. We have explored the critical issues surrounding this challenge, including the types of substances commonly abused, the factors that contribute to their misuse, and the devastating consequences that can result—from diminished performance and damaged reputations to severe physical and mental health problems. Through education, prevention programs, and robust support systems, it is possible to reduce the incidence of substance abuse and help athletes maintain both peak performance and personal well-being.\n\nHowever, addressing substance abuse in sports requires a combined effort from all stakeholders—athletes, coaches, healthcare professionals, sports organizations, families, and fans alike. Increased awareness is the first essential step. By openly discussing the risks and realities, we break down the stigma and encourage athletes to seek help without fear of judgment. Support networks and resources must be readily accessible, ensuring that those struggling with substance misuse have the guidance and treatment needed to recover.\n\nWe call on everyone involved in athletics to take action. Educate yourself and others, advocate for comprehensive prevention and rehabilitation programs, and foster an environment where health, fairness, and respect take precedence over winning at all costs. Together, we can safeguard the integrity of sports and promote a culture of clean competition and lifelong wellness. Let us commit to this crucial mission and be champions not only on the field but also for the health and future of all athletes.")</code></pre>
</div>
</div>
<p>Next, you’ll learn how to implement this pattern using LangGraph.</p>
</section>
<section id="langgraph-3" class="level4">
<h4 class="anchored" data-anchor-id="langgraph-3">LangGraph</h4>
<p>You must define the state of the orchestrator, the workers (who write the sections), and the data models used in the workflow.</p>
<div id="cell-82" class="cell" data-execution_count="31">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb36" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb36-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> Section(BaseModel):</span>
<span id="cb36-2">    name: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Field(description<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"The name of the section"</span>)</span>
<span id="cb36-3">    description: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Field(description<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"The description of the section"</span>)</span>
<span id="cb36-4"></span>
<span id="cb36-5"></span>
<span id="cb36-6"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> CompletedSection(BaseModel):</span>
<span id="cb36-7">    name: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Field(description<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"The name of the section"</span>)</span>
<span id="cb36-8">    content: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Field(description<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"The content of the section"</span>)</span>
<span id="cb36-9"></span>
<span id="cb36-10"></span>
<span id="cb36-11"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> Sections(BaseModel):</span>
<span id="cb36-12">    sections: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[Section] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Field(description<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"The sections of the article"</span>)</span>
<span id="cb36-13"></span>
<span id="cb36-14"></span>
<span id="cb36-15"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> OrchestratorState(TypedDict):</span>
<span id="cb36-16">    topic: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span></span>
<span id="cb36-17">    sections: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[Section]</span>
<span id="cb36-18">    completed_sections: Annotated[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[CompletedSection], operator.add]</span>
<span id="cb36-19">    final_report: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span></span>
<span id="cb36-20"></span>
<span id="cb36-21"></span>
<span id="cb36-22"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> WorkerState(TypedDict):</span>
<span id="cb36-23">    section: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span></span>
<span id="cb36-24">    completed_sections: Annotated[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[Section], operator.add]</span></code></pre></div></div>
</div>
<p>The state definition changes slightly, as in this case, you need to define a worker state, which is used when the orchestrator assigns a task to a worker.</p>
<div id="cell-84" class="cell" data-execution_count="32">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb37" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb37-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> orchestrator(state: OrchestratorState) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>:</span>
<span id="cb37-2">    model_planner <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> model.with_structured_output(Sections)</span>
<span id="cb37-3">    messages <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb37-4">        SystemMessage(</span>
<span id="cb37-5">            content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"You are an expert writer specialized in SEO. Provided with a topic, you will generate the sections for a short article."</span></span>
<span id="cb37-6">        ),</span>
<span id="cb37-7">        HumanMessage(</span>
<span id="cb37-8">            content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Generate the sections of an article about </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>state[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'topic'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb37-9">        ),</span>
<span id="cb37-10">    ]</span>
<span id="cb37-11">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"sections"</span>: model_planner.invoke(messages).sections}</span>
<span id="cb37-12"></span>
<span id="cb37-13"></span>
<span id="cb37-14"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> write_section(state: WorkerState) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>:</span>
<span id="cb37-15">    messages <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb37-16">        SystemMessage(</span>
<span id="cb37-17">            content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"You are an expert writer specialized in SEO. Provided with a topic and a table of contents, you will generate the content of the article."</span></span>
<span id="cb37-18">        ),</span>
<span id="cb37-19">        HumanMessage(</span>
<span id="cb37-20">            content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Generate the content of an article about </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>state[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'section'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>name<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;"> with the following description: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>state[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'section'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>description<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span></span>
<span id="cb37-21">        ),</span>
<span id="cb37-22">    ]</span>
<span id="cb37-23">    section <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> CompletedSection(</span>
<span id="cb37-24">        name<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>state[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'section'</span>].name, content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>model.invoke(messages).content</span>
<span id="cb37-25">    )</span>
<span id="cb37-26">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"completed_sections"</span>: [section]}</span>
<span id="cb37-27"></span>
<span id="cb37-28"></span>
<span id="cb37-29"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> synthesizer(state: OrchestratorState) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>:</span>
<span id="cb37-30">    ordered_sections <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> state[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"completed_sections"</span>]</span>
<span id="cb37-31">    completed_sections_str <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n\n</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>.join(</span>
<span id="cb37-32">        [section.content <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> section <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> ordered_sections]</span>
<span id="cb37-33">    )</span>
<span id="cb37-34">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"final_report"</span>: completed_sections_str}</span>
<span id="cb37-35"></span>
<span id="cb37-36"></span>
<span id="cb37-37"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> assign_workers(state: OrchestratorState) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>:</span>
<span id="cb37-38">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> [</span>
<span id="cb37-39">        Send(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"write_section"</span>, {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"section"</span>: section}) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> section <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> state[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"sections"</span>]</span>
<span id="cb37-40">    ]</span></code></pre></div></div>
</div>
<p>Then, you’ll define the graph that will be used to run the workflow.</p>
<div id="cell-86" class="cell" data-execution_count="33">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb38" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb38-1">workflow_builder <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> StateGraph(OrchestratorState)</span>
<span id="cb38-2"></span>
<span id="cb38-3">workflow_builder.add_node(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"orchestrator"</span>, orchestrator)</span>
<span id="cb38-4">workflow_builder.add_node(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"write_section"</span>, write_section)</span>
<span id="cb38-5">workflow_builder.add_node(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"synthesizer"</span>, synthesizer)</span>
<span id="cb38-6"></span>
<span id="cb38-7">workflow_builder.add_edge(START, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"orchestrator"</span>)</span>
<span id="cb38-8">workflow_builder.add_conditional_edges(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"orchestrator"</span>, assign_workers, [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"write_section"</span>])</span>
<span id="cb38-9">workflow_builder.add_edge(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"write_section"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"synthesizer"</span>)</span>
<span id="cb38-10"></span>
<span id="cb38-11">workflow <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> workflow_builder.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">compile</span>()</span></code></pre></div></div>
</div>
<p>You build the workflow by initializing a <code>StateGraph</code> object. In it, you’ll assign your functions to serve as nodes and then define the edges that establish the pathways between them. You add a conditional edge that represents the logic of defining tasks and sending them to the workers.</p>
<p>Once you’ve defined the graph, you can compile.</p>
<p>Finally, you can generate a diagram of the workflow using the <code>get_graph</code> and <code>draw_mermaid_png</code> methods. You’ll noticed that compared to the parallelization workflow, the orchestrator-workers has a dotted line from the orchestrator to the workers, which means that the orchestrator conditionally defines the tasks to be sent to the workers.</p>
<div id="cell-88" class="cell" data-execution_count="34">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb39" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb39-1">display(Image(workflow.get_graph().draw_mermaid_png()))</span></code></pre></div></div>
<div class="cell-output cell-output-display">
<div>
<figure class="figure">
<p><a href="agentic-workflows-langgraph_files/figure-html/cell-35-output-1.png" class="lightbox" data-gallery="quarto-lightbox-gallery-4"><img src="https://dylancastillo.co/posts/agentic-workflows-langgraph_files/figure-html/cell-35-output-1.png" class="img-fluid figure-img"></a></p>
</figure>
</div>
</div>
</div>
<p>Then, you can run the workflow.</p>
<div id="cell-90" class="cell" data-execution_count="35">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb40" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb40-1">workflow.invoke({<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"topic"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Substance abuse of athletes"</span>})</span></code></pre></div></div>
<div class="cell-output cell-output-display" data-execution_count="35">
<pre><code>{'topic': 'Substance abuse of athletes',
 'sections': [Section(name='Introduction to Substance Abuse in Athletes', description='Overview of substance abuse issues commonly faced by athletes, including types of substances and reasons for usage.'),
  Section(name='Common Substances Abused by Athletes', description='Detailed description of substances frequently abused such as steroids, stimulants, painkillers, and recreational drugs.'),
  Section(name='Causes and Risk Factors', description='Exploration of psychological, social, and professional factors that contribute to substance abuse among athletes.'),
  Section(name='Health and Performance Consequences', description="Analysis of the physical and mental impacts of substance abuse on athletes' health and sports performance."),
  Section(name='Detection and Prevention Strategies', description='Information on how substance abuse is detected in athletes and strategies used to prevent it, including testing and education programs.'),
  Section(name='Support and Rehabilitation', description='Description of available support systems and rehabilitation programs designed to help athletes overcome substance abuse.'),
  Section(name='Conclusion and Future Perspectives', description='Summary of key points and discussion of emerging trends and future approaches to addressing substance abuse among athletes.')],
 'completed_sections': [CompletedSection(name='Introduction to Substance Abuse in Athletes', content='# Introduction to Substance Abuse in Athletes\n\nSubstance abuse among athletes is a significant concern that affects not only their health but also their performance, careers, and overall well-being. Despite the physical and mental discipline required in sports, athletes are not immune to the pressures and challenges that can lead to the use and abuse of various substances. Understanding the types of substances commonly used and the reasons why athletes may turn to them is crucial for addressing this issue effectively.\n\n## Common Types of Substances Abused by Athletes\n\nAthletes may misuse a wide range of substances, each serving different purposes or fulfilling different needs depending on the individual and their circumstances. Some of the most common categories include:\n\n### 1. Performance-Enhancing Drugs (PEDs)\nPerformance-enhancing drugs are substances used to improve athletic ability beyond natural limits. These include anabolic steroids, human growth hormone (HGH), erythropoietin (EPO), and stimulants. Athletes may use these drugs to increase muscle mass, enhance endurance, speed up recovery, and gain a competitive advantage.\n\n### 2. Recreational Drugs\nRecreational drugs such as marijuana, cocaine, MDMA, and opioids are sometimes abused by athletes to cope with stress, manage pain, or for social reasons. While these substances do not improve athletic performance and can be detrimental, their use is often linked to external pressures or underlying issues.\n\n### 3. Prescription Medications\nCertain prescription medications, including painkillers (opioids), anti-anxiety drugs (benzodiazepines), and stimulants (used for ADHD), can be abused by athletes. These drugs might be used to manage pain from injuries, reduce anxiety before competitions, or increase focus and alertness.\n\n### 4. Over-the-Counter (OTC) Substances and Supplements\nThough generally legal and accessible, some OTC substances and dietary supplements can be misused, especially when athletes seek to lose weight rapidly or boost energy. Misuse can sometimes lead to harmful side effects or positive doping tests if the substances contain banned ingredients.\n\n## Reasons for Substance Abuse Among Athletes\n\nThe motivations behind substance abuse in athletes are complex and multifaceted. Some of the most common reasons include:\n\n### 1. Enhancing Performance\nThe intense desire to win and excel can lead athletes to experiment with drugs that promise enhanced strength, endurance, or recovery. In highly competitive environments, athletes may feel pressure to push beyond their natural limits.\n\n### 2. Coping with Pain and Injuries\nPhysical injuries are common in sports, and the pain associated with them can lead athletes to seek relief through prescription painkillers or other substances. Unfortunately, this can sometimes escalate into abuse and dependency.\n\n### 3. Managing Stress and Anxiety\nThe psychological pressures of competition, public scrutiny, and personal expectations can cause significant mental stress. Some athletes turn to drugs to alleviate anxiety, improve mood, or escape from personal and professional challenges.\n\n### 4. Peer Influence and Culture\nIn some sports cultures, the use of substances may be normalized or even encouraged, creating an environment where athletes feel compelled to conform. Peer influence and the desire to belong can drive substance use.\n\n### 5. Weight Control and Body Image\nCertain sports emphasize weight categories or aesthetic appearance, prompting athletes to misuse substances that suppress appetite or promote rapid weight loss.\n\n## Conclusion\n\nSubstance abuse in athletes is a serious and multifaceted problem that demands awareness, education, and intervention at multiple levels. Recognizing the types of substances commonly abused and understanding the underlying reasons for their use is the first step towards promoting healthier choices and safeguarding the integrity of sports. Athletes, coaches, medical professionals, and organizations must work collaboratively to address these issues through prevention, support, and treatment.'),
  CompletedSection(name='Common Substances Abused by Athletes', content='# Common Substances Abused by Athletes\n\nAthletes often face immense pressure to perform at their best, maintain stamina, and recover quickly from injuries. Unfortunately, some turn to substances that can enhance performance or alleviate pain, despite the health risks and ethical concerns involved. This article provides a detailed description of the most frequently abused substances among athletes, focusing on steroids, stimulants, painkillers, and recreational drugs.\n\n## Anabolic Steroids\n\nAnabolic steroids are synthetic variations of the male hormone testosterone. Athletes abuse them to increase muscle mass, strength, and overall physical performance. Steroids work by promoting protein synthesis within cells, leading to rapid muscle growth.\n\n### Common Types and Effects\n- **Types**: Testosterone, nandrolone, stanozolol, and methyltestosterone.\n- **Effects**: Increased muscle size, enhanced recovery rate, greater endurance, and reduced fatigue.\n\n### Risks and Side Effects\n- Hormonal imbalances leading to acne, hair loss, and mood swings.\n- Cardiovascular issues such as high blood pressure and increased risk of heart attack.\n- Liver damage and potential infertility.\n- Psychological effects including aggression and depression.\n  \nDespite their performance-enhancing properties, anabolic steroids are banned in most sports leagues and athletic organizations.\n\n## Stimulants\n\nStimulants are substances that increase alertness, attention, and energy by enhancing the activity of the central nervous system. Athletes may use stimulants to reduce fatigue and improve focus during training or competition.\n\n### Common Stimulants\n- **Amphetamines**: Often prescribed for ADHD but misused for increased energy.\n- **Caffeine**: The world’s most widely consumed legal stimulant.\n- **Ephedrine**: Used for weight loss and increased energy but banned in many competitions.\n  \n### Effects\n- Increased heart rate and blood pressure.\n- Heightened concentration and wakefulness.\n- Temporary reduction of appetite and fatigue.\n\n### Risks\n- Dependence and addiction.\n- Cardiovascular complications, including arrhythmias and heart attacks.\n- Nervousness, anxiety, and sleep disturbances.\n  \nWhile moderate caffeine use is generally accepted, other stimulants are typically prohibited due to their performance-enhancing effects and health risks.\n\n## Painkillers\n\nPainkillers, particularly opioid analgesics and non-steroidal anti-inflammatory drugs (NSAIDs), are commonly prescribed to manage injuries and pain. However, abuse can occur when athletes rely excessively on these drugs to continue competing.\n\n### Commonly Abused Painkillers\n- **Opioids**: Morphine, oxycodone, hydrocodone.\n- **NSAIDs**: Ibuprofen, naproxen (abuse generally less severe but problematic if overused).\n\n### Effects\n- Temporary pain relief and increased ability to train through injury.\n- Sedation and euphoria (primarily with opioids).\n\n### Risks\n- Opioid addiction, respiratory depression, and overdose.\n- Gastrointestinal issues and kidney damage with long-term NSAID use.\n- Masking of pain leading to worsened injuries.\n\nDue to the risk of dependency, strict guidelines exist for the medical use of painkillers among athletes.\n\n## Recreational Drugs\n\nSome athletes use recreational drugs for relaxation or coping with stress, despite the negative impact on performance and health.\n\n### Common Recreational Drugs Abused\n- **Cannabis**: Used for relaxation and pain relief; banned in many competitions.\n- **Alcohol**: Consumed socially but can impair recovery and performance.\n- **Cocaine and Ecstasy**: Occasionally abused for their stimulant and euphoric effects.\n\n### Effects\n- Altered mental state, reduced reaction time, and impaired coordination.\n- Short-term mood enhancement or relaxation.\n\n### Risks\n- Detrimental effects on cardiovascular health.\n- Legal issues and sanctions from sports authorities.\n- Negative impact on motivation, training, and overall athletic performance.\n\n## Conclusion\n\nThe abuse of steroids, stimulants, painkillers, and recreational drugs poses serious health risks and ethical dilemmas in sports. While the desire to perform better or manage pain is understandable, these substances can undermine an athlete’s long-term wellbeing and the integrity of athletic competition. Education, support, and strict regulation remain crucial in addressing substance abuse among athletes.'),
  CompletedSection(name='Causes and Risk Factors', content='# Causes and Risk Factors: Exploring Psychological, Social, and Professional Contributors to Substance Abuse Among Athletes\n\nSubstance abuse among athletes is a pressing concern that extends beyond physical performance to affect their mental health, career longevity, and overall well-being. Understanding the underlying causes and risk factors is essential for developing effective prevention and intervention strategies. This article delves into the psychological, social, and professional factors that contribute to substance abuse in the athletic community.\n\n## Psychological Factors\n\n### Pressure to Perform and Perfectionism\nAthletes often face intense pressure to perform at peak levels consistently. This demand can trigger anxiety, stress, and feelings of inadequacy. The desire for perfection may lead some athletes to use substances such as stimulants or performance-enhancing drugs to cope with the high expectations and overcome self-doubt.\n\n### Mental Health Challenges\nDepression, anxiety disorders, and other mental health issues are prevalent among athletes. The stigma around seeking psychological help often forces athletes to self-medicate with alcohol or drugs as a way to manage emotional pain, stress, or injury-related trauma.\n\n### Coping Mechanisms for Injury and Pain\nPhysical injuries are a common aspect of athletic careers. The chronic pain associated with injuries can lead athletes to misuse prescription medications like opioids or overconsume alcohol to alleviate discomfort and continue training or competing, inadvertently increasing the risk of substance dependence.\n\n## Social Factors\n\n### Peer Influence and Team Culture\nThe social environment within teams can profoundly impact substance use behaviors. If drug or alcohol use is normalized or even glamorized among teammates, new or younger athletes may feel pressured to conform to these norms to gain acceptance or camaraderie.\n\n### Social Isolation and Loneliness\nDespite being part of a team, athletes may experience social isolation due to rigorous training schedules, travel, or being away from family and friends. This loneliness can increase vulnerability to substance use as a misguided attempt to fill emotional voids or manage feelings of alienation.\n\n### Media and Celebrity Influence\nExposure to celebrity athletes who openly use or are rumored to use substances can create misleading narratives about drug use and its perceived benefits. This influence can lower inhibitions and reshape attitudes towards substance use, especially among younger athletes aspiring to emulate their idols.\n\n## Professional Factors\n\n### Demands of Competitive Sports\nThe professional athletic environment is highly competitive, with careers often hinging on performance and winning. The associated stress can drive athletes to use substances that promise enhanced stamina, focus, or recovery, sometimes blurring the line between legitimate medical use and abuse.\n\n### Career Uncertainty and Transition Stress\nAthletes frequently face uncertainty regarding career longevity due to factors such as injuries or declining performance. The stress related to contract negotiations, retirement planning, or forced career changes may lead to increased substance use as a maladaptive coping strategy.\n\n### Accessibility and Medical Prescriptions\nAthletes generally have greater access to medical facilities and prescription medications than the general population. While this access is crucial for health management, it also increases the risk of prescription drug misuse, especially when oversight is inadequate or when medications are used beyond therapeutic purposes.\n\n## Conclusion\n\nThe causes and risk factors behind substance abuse among athletes are multifaceted, involving a complex interplay of psychological pressures, social dynamics, and professional challenges. Recognizing these contributing elements is vital for coaches, medical professionals, and support networks to create comprehensive prevention programs and provide the necessary resources to help athletes maintain healthy lifestyles free from substance abuse. Only through holistic understanding and targeted action can the athletic community effectively combat this pervasive issue.'),
  CompletedSection(name='Health and Performance Consequences', content="# Health and Performance Consequences: The Physical and Mental Impacts of Substance Abuse on Athletes\n\nSubstance abuse among athletes is a critical issue that can profoundly affect both their health and sports performance. While some may turn to drugs or alcohol as a coping mechanism for stress, injury, or pressure, the consequences can be far-reaching and damaging. This article provides an in-depth analysis of the physical and mental impacts of substance abuse on athletes, emphasizing why maintaining a healthy lifestyle is essential for peak performance and overall well-being.\n\n## Physical Impacts of Substance Abuse on Athletes\n\n### 1. Deterioration of Cardiovascular Health\nMany substances abused by athletes, such as stimulants and anabolic steroids, place significant strain on the cardiovascular system. Stimulants like cocaine and amphetamines increase heart rate and blood pressure, leading to arrhythmias, hypertension, and even sudden cardiac arrest. Anabolic steroids can cause thickening of the heart muscle and increase the risk of heart attacks and strokes. Such cardiovascular issues directly impair an athlete's endurance, stamina, and ability to perform at high levels.\n\n### 2. Impaired Muscular Strength and Recovery\nThough some athletes misuse anabolic steroids to enhance muscle mass, chronic abuse can backfire by causing muscle cramps, weakness, and tendon ruptures. Other substances like alcohol interfere with protein synthesis and muscle repair, prolonging recovery time after training or injury. Dehydration and nutrient depletion caused by certain drugs further weaken muscles, limiting strength and agility on the field.\n\n### 3. Compromised Immune Function\nSubstance abuse can suppress the immune system, making athletes more susceptible to infections and illnesses. For example, excessive alcohol intake has been shown to reduce white blood cell activity, reducing the body’s ability to fight off viruses and bacteria. This leads to increased downtime due to illness and a diminished capacity to handle the physical demands of training and competition.\n\n### 4. Respiratory and Neurological Damage\nSmoking substances like tobacco or marijuana damages lung tissue and reduces oxygen uptake, critical for aerobic performance. Inhalants and certain drugs can cause long-term neurological damage including impaired coordination, balance, and reflexes—all vital for athletic skills. Brain injuries resulting from drug use can be irreversible, severely impacting motor skills and reaction times.\n\n## Mental and Psychological Consequences\n\n### 1. Cognitive Decline and Impaired Decision-Making\nSubstance abuse affects areas of the brain responsible for judgment, focus, and reaction time. Athletes abusing drugs may experience difficulties with concentration, memory, and decision-making — all essential skills for strategic gameplay and quick reactions in sports. Cognitive impairment can lead to poor game performance and increased risk of injury.\n\n### 2. Increased Anxiety, Depression, and Mood Disorders\nMany athletes turn to substances as a method of managing anxiety or depression. However, the temporary relief these substances provide is often followed by worsening symptoms. Long-term substance abuse is linked with increased rates of anxiety disorders, depression, and mood swings. Mental health struggles not only impair motivation and training consistency but also increase the likelihood of burnout and withdrawal from sport.\n\n### 3. Addiction and Dependence\nPhysical and psychological dependence on performance-enhancing or recreational drugs can trap athletes in destructive cycles. Addiction disrupts daily routines, training schedules, and professional commitments. The stress of hiding substance use and dealing with its consequences adds an additional psychological burden, often leading to social isolation and damaged relationships.\n\n### 4. Impact on Team Dynamics and Reputation\nMental health issues stemming from substance abuse can make athletes volatile or withdrawn, affecting communication and collaboration within a team. Additionally, public knowledge of substance abuse can tarnish an athlete’s reputation, leading to a loss of sponsorships, fan support, and career opportunities.\n\n## Conclusion: Prioritizing Health to Sustain Performance\n\nThe interplay between substance abuse and athletic health creates a dangerous cycle where physical and mental degradations impair performance, which in turn may lead to further substance use in an attempt to compensate. Athletes must prioritize healthy coping strategies, proper medical guidance, and mental health support to maintain optimal performance and longevity in their sports careers. Coaches, trainers, and sporting organizations also play a pivotal role in providing education and resources to prevent substance abuse and support recovery. Ultimately, safeguarding an athlete’s well-being ensures not only superior performance but a fulfilling and sustainable athletic journey."),
  CompletedSection(name='Detection and Prevention Strategies', content='# Detection and Prevention Strategies for Substance Abuse in Athletes\n\nSubstance abuse in athletes not only undermines fair competition but also poses significant health risks. To maintain integrity in sports and protect athletes’ well-being, robust detection and prevention strategies are essential. This article explores the methods used to identify substance abuse in athletes and the proactive approaches employed to prevent it, focusing on testing protocols and educational programs.\n\n## Detection of Substance Abuse in Athletes\n\n### 1. Drug Testing Protocols\nOne of the primary methods for detecting substance abuse in athletes is through comprehensive drug testing programs. These tests can be conducted both in and out of competition, aiming to identify banned substances such as anabolic steroids, stimulants, diuretics, and other performance-enhancing drugs (PEDs).\n\n- **Urine Testing:** The most common and widely used method, urine tests can detect a broad range of substances and their metabolites. Samples are collected under strict supervision to prevent tampering.\n- **Blood Testing:** Used to detect substances that may not appear in urine or to determine the concentration of certain drugs, such as erythropoietin (EPO) or hormones.\n- **Hair and Saliva Testing:** These methods are less common but provide longer detection windows or rapid results, respectively.\n\nTesting is typically random, scheduled, or targeted based on certain risk factors or suspicious behavior, helping to deter and catch substance abuse.\n\n### 2. Biological Passport Programs\nThe Athlete Biological Passport (ABP) monitors selected biological variables over time. Rather than detecting the substance directly, it identifies abnormal changes in an athlete’s biological markers that suggest doping. This method enhances detection capabilities for substances that are otherwise difficult to identify.\n\n### 3. Observational and Behavioral Monitoring\nCoaches, medical staff, and anti-doping officials also rely on behavioral observations to detect potential substance abuse. Changes in performance, physical appearance, or behavior can prompt further investigation or testing.\n\n## Prevention Strategies\n\n### 1. Education Programs\nEducation is a cornerstone in preventing substance abuse. Athletes, coaches, and supporting personnel are provided with information about:\n\n- The health risks associated with drug use.\n- The ethical implications and impact on sporting integrity.\n- The specific substances banned by anti-doping authorities.\n- How testing procedures work and the consequences of violations.\n\nEffective education fosters informed decision-making and empowers athletes to resist pressure or temptation to use prohibited substances.\n\n### 2. Promoting a Culture of Clean Sport\nEncouraging a culture that values fair play and health over winning at all costs is essential. This includes:\n\n- Encouraging open dialogue about doping risks.\n- Highlighting positive role models who compete clean.\n- Building supportive environments where athletes can seek help for pressures or substance-related issues without stigma.\n\n### 3. Support Services and Counseling\nProviding access to psychological support and counseling can address underlying issues that might lead athletes to use substances, such as stress, anxiety, or injury-related pain management.\n\n### 4. Policy and Enforcement\nClear rules, consistent enforcement, and transparent consequences reinforce deterrents against doping. Collaboration among sports organizations, anti-doping agencies, and governments ensures that policies are up-to-date and effectively implemented.\n\n## Conclusion\n\nDetecting and preventing substance abuse in athletes requires a multifaceted approach that combines advanced testing technologies, continuous monitoring, education, and supportive environments. By integrating these strategies, the sports community can better protect athletes’ health, uphold the spirit of fair competition, and maintain public confidence in sport.'),
  CompletedSection(name='Support and Rehabilitation', content='# Support and Rehabilitation: Helping Athletes Overcome Substance Abuse\n\nSubstance abuse is a significant challenge faced by many athletes, often impacting not only their performance but also their overall health and well-being. Fortunately, a variety of support systems and rehabilitation programs have been developed to assist athletes in overcoming these issues. These resources provide comprehensive care, from initial intervention to long-term recovery, helping athletes regain control of their lives both on and off the field.\n\n## Understanding the Need for Support and Rehabilitation\n\nAthletes are under tremendous pressure to perform, which can sometimes lead to the misuse of substances such as performance enhancers, painkillers, or recreational drugs. The stigma surrounding substance abuse in sports may create barriers to seeking help, making effective support and rehabilitation programs critical.\n\nThese programs are specifically designed to address the unique physical, emotional, and psychological demands athletes face. Tailored support systems ensure that athletes receive care that respects their competitive schedules while focusing on sustainable recovery.\n\n## Types of Support Systems Available to Athletes\n\n### 1. Counseling and Psychological Support\n\nOne of the foundational elements in addressing substance abuse is access to professional counseling. Sports psychologists and addiction counselors work with athletes to tackle underlying issues such as anxiety, depression, or trauma that often contribute to substance misuse. Cognitive-behavioral therapy (CBT) and motivational interviewing are common techniques used to foster behavioral change and build resilience.\n\n### 2. Peer Support Groups\n\nPeer networks provide a safe environment where athletes can share experiences, challenges, and coping strategies. Groups such as Athlete Assistance Programs (AAP) and specialized 12-step programs tailored for athletes encourage mutual support and accountability. Being part of a community reduces feelings of isolation and stigma, fostering a sense of belonging and motivation.\n\n### 3. Family and Social Support\n\nRehabilitation is more effective when athletes have a strong support system at home and within their social circles. Many programs involve family therapy or educational sessions to help loved ones understand substance abuse and learn how to provide constructive support throughout recovery.\n\n## Rehabilitation Programs Tailored for Athletes\n\n### 1. Inpatient Rehabilitation\n\nFor athletes with severe substance dependence, inpatient rehabilitation centers offer intensive, structured care. These programs provide medical supervision, detoxification services, and comprehensive therapy in a controlled environment. Many centers integrate physical conditioning and sports-specific rehabilitation to help athletes maintain fitness and facilitate reintegration into their sport.\n\n### 2. Outpatient Rehabilitation\n\nOutpatient programs provide flexibility for athletes who cannot commit to prolonged residential stays due to training or competition schedules. These programs offer scheduled therapy sessions, group meetings, and medical support, allowing athletes to receive care while continuing their regular activities. Outpatient care often serves as a step-down for those transitioning from inpatient programs.\n\n### 3. Holistic and Integrative Approaches\n\nModern rehabilitation programs increasingly incorporate holistic therapies to address the physical and emotional aspects of recovery. These may include yoga, mindfulness meditation, nutrition counseling, and acupuncture. Such approaches help athletes develop healthier lifestyles, manage stress, and reduce the likelihood of relapse.\n\n## Role of Sports Organizations and Governing Bodies\n\nSports organizations play a pivotal role in providing resources and creating policies that support athletes facing substance abuse. Many federations offer confidential help lines, educational seminars, and funding for rehabilitation programs. Anti-doping agencies also emphasize rehabilitation over punishment, aiming to promote athlete health and ethical competition.\n\n## Success Stories and Outcomes\n\nNumerous athletes have successfully overcome substance abuse through dedicated support and rehabilitation. These success stories highlight the effectiveness of targeted programs that combine medical treatment, psychological support, and social reintegration. Recovery not only improves personal health but also restores athletic potential and inspires others facing similar struggles.\n\n## Conclusion\n\nSubstance abuse among athletes is a complex issue requiring specialized support and rehabilitation programs. By leveraging counseling, peer support, family involvement, and tailored rehabilitation services, athletes can overcome these challenges and return to optimal performance and well-being. Ongoing collaboration between healthcare providers, sports organizations, and athletes themselves is essential to foster environments that encourage recovery and sustainable success.'),
  CompletedSection(name='Conclusion and Future Perspectives', content='## Conclusion and Future Perspectives\n\n### Summary of Key Points\n\nAddressing substance abuse among athletes remains a critical challenge that demands comprehensive, evidence-based strategies. Throughout this article, we have explored the multifaceted nature of substance abuse in the athletic community, highlighting its complex interplay with physical performance pressures, mental health issues, and social influences. Key points include:\n\n- **Prevalence and Types of Substance Abuse:** Athletes are susceptible to various substances, ranging from performance-enhancing drugs like anabolic steroids to recreational drugs and prescription medication misuse.\n- **Risk Factors:** High-performance demands, injury management, psychological stress, and the culture of competitive sports contribute significantly to the risk of substance abuse.\n- **Impact on Health and Career:** Substance abuse not only jeopardizes athletes’ physical and mental well-being but also threatens their careers through sanctions, suspensions, and loss of reputation.\n- **Current Prevention and Intervention Strategies:** These include education programs, strict doping controls, psychological support, and rehabilitation services tailored to the athletic population.\n\nUnderstanding these essentials underscores the need for a strategic, multidisciplinary approach to mitigate substance abuse risks effectively.\n\n### Emerging Trends and Future Approaches\n\nLooking forward, the landscape of managing substance abuse among athletes is evolving, propelled by advancements in technology, research, and policy development. New trends and promising approaches include:\n\n- **Personalized Prevention Programs:** Leveraging data analytics and behavioral assessments to create individualized intervention plans that address specific risk factors and psychological profiles unique to each athlete.\n- **Enhanced Screening and Detection Methods:** Innovations such as biomarker identification, genetic testing, and advanced neuroimaging are improving the accuracy and timeliness of substance abuse detection.\n- **Integrative Mental Health Services:** Future programs emphasize holistic care by integrating mental health support, stress management, and resilience training within athletic training and medical teams.\n- **Technology-Driven Monitoring:** Wearable devices and mobile health apps are emerging as tools to monitor physiological and psychological indicators, enabling early intervention before substance use escalates.\n- **Policy and Cultural Shift:** Developing policies that not only penalize substance abuse but also destigmatize seeking help, encouraging athletes to come forward without fear of retaliation or judgment.\n- **Collaborative Stakeholder Engagement:** Involving coaches, medical staff, sports organizations, family members, and peers to foster a supportive environment conducive to prevention and recovery.\n\n### Conclusion\n\nThe fight against substance abuse among athletes is ongoing, and it requires adaptability to emerging scientific insights and societal changes. By embracing innovative technologies, prioritizing mental health, and fostering open communication, sports communities can better protect athletes from the risks of substance abuse. The future holds promise for more effective, personalized, and compassionate approaches that not only safeguard athletic integrity but also promote overall health and well-being.')],
 'final_report': "# Introduction to Substance Abuse in Athletes\n\nSubstance abuse among athletes is a significant concern that affects not only their health but also their performance, careers, and overall well-being. Despite the physical and mental discipline required in sports, athletes are not immune to the pressures and challenges that can lead to the use and abuse of various substances. Understanding the types of substances commonly used and the reasons why athletes may turn to them is crucial for addressing this issue effectively.\n\n## Common Types of Substances Abused by Athletes\n\nAthletes may misuse a wide range of substances, each serving different purposes or fulfilling different needs depending on the individual and their circumstances. Some of the most common categories include:\n\n### 1. Performance-Enhancing Drugs (PEDs)\nPerformance-enhancing drugs are substances used to improve athletic ability beyond natural limits. These include anabolic steroids, human growth hormone (HGH), erythropoietin (EPO), and stimulants. Athletes may use these drugs to increase muscle mass, enhance endurance, speed up recovery, and gain a competitive advantage.\n\n### 2. Recreational Drugs\nRecreational drugs such as marijuana, cocaine, MDMA, and opioids are sometimes abused by athletes to cope with stress, manage pain, or for social reasons. While these substances do not improve athletic performance and can be detrimental, their use is often linked to external pressures or underlying issues.\n\n### 3. Prescription Medications\nCertain prescription medications, including painkillers (opioids), anti-anxiety drugs (benzodiazepines), and stimulants (used for ADHD), can be abused by athletes. These drugs might be used to manage pain from injuries, reduce anxiety before competitions, or increase focus and alertness.\n\n### 4. Over-the-Counter (OTC) Substances and Supplements\nThough generally legal and accessible, some OTC substances and dietary supplements can be misused, especially when athletes seek to lose weight rapidly or boost energy. Misuse can sometimes lead to harmful side effects or positive doping tests if the substances contain banned ingredients.\n\n## Reasons for Substance Abuse Among Athletes\n\nThe motivations behind substance abuse in athletes are complex and multifaceted. Some of the most common reasons include:\n\n### 1. Enhancing Performance\nThe intense desire to win and excel can lead athletes to experiment with drugs that promise enhanced strength, endurance, or recovery. In highly competitive environments, athletes may feel pressure to push beyond their natural limits.\n\n### 2. Coping with Pain and Injuries\nPhysical injuries are common in sports, and the pain associated with them can lead athletes to seek relief through prescription painkillers or other substances. Unfortunately, this can sometimes escalate into abuse and dependency.\n\n### 3. Managing Stress and Anxiety\nThe psychological pressures of competition, public scrutiny, and personal expectations can cause significant mental stress. Some athletes turn to drugs to alleviate anxiety, improve mood, or escape from personal and professional challenges.\n\n### 4. Peer Influence and Culture\nIn some sports cultures, the use of substances may be normalized or even encouraged, creating an environment where athletes feel compelled to conform. Peer influence and the desire to belong can drive substance use.\n\n### 5. Weight Control and Body Image\nCertain sports emphasize weight categories or aesthetic appearance, prompting athletes to misuse substances that suppress appetite or promote rapid weight loss.\n\n## Conclusion\n\nSubstance abuse in athletes is a serious and multifaceted problem that demands awareness, education, and intervention at multiple levels. Recognizing the types of substances commonly abused and understanding the underlying reasons for their use is the first step towards promoting healthier choices and safeguarding the integrity of sports. Athletes, coaches, medical professionals, and organizations must work collaboratively to address these issues through prevention, support, and treatment.\n\n# Common Substances Abused by Athletes\n\nAthletes often face immense pressure to perform at their best, maintain stamina, and recover quickly from injuries. Unfortunately, some turn to substances that can enhance performance or alleviate pain, despite the health risks and ethical concerns involved. This article provides a detailed description of the most frequently abused substances among athletes, focusing on steroids, stimulants, painkillers, and recreational drugs.\n\n## Anabolic Steroids\n\nAnabolic steroids are synthetic variations of the male hormone testosterone. Athletes abuse them to increase muscle mass, strength, and overall physical performance. Steroids work by promoting protein synthesis within cells, leading to rapid muscle growth.\n\n### Common Types and Effects\n- **Types**: Testosterone, nandrolone, stanozolol, and methyltestosterone.\n- **Effects**: Increased muscle size, enhanced recovery rate, greater endurance, and reduced fatigue.\n\n### Risks and Side Effects\n- Hormonal imbalances leading to acne, hair loss, and mood swings.\n- Cardiovascular issues such as high blood pressure and increased risk of heart attack.\n- Liver damage and potential infertility.\n- Psychological effects including aggression and depression.\n  \nDespite their performance-enhancing properties, anabolic steroids are banned in most sports leagues and athletic organizations.\n\n## Stimulants\n\nStimulants are substances that increase alertness, attention, and energy by enhancing the activity of the central nervous system. Athletes may use stimulants to reduce fatigue and improve focus during training or competition.\n\n### Common Stimulants\n- **Amphetamines**: Often prescribed for ADHD but misused for increased energy.\n- **Caffeine**: The world’s most widely consumed legal stimulant.\n- **Ephedrine**: Used for weight loss and increased energy but banned in many competitions.\n  \n### Effects\n- Increased heart rate and blood pressure.\n- Heightened concentration and wakefulness.\n- Temporary reduction of appetite and fatigue.\n\n### Risks\n- Dependence and addiction.\n- Cardiovascular complications, including arrhythmias and heart attacks.\n- Nervousness, anxiety, and sleep disturbances.\n  \nWhile moderate caffeine use is generally accepted, other stimulants are typically prohibited due to their performance-enhancing effects and health risks.\n\n## Painkillers\n\nPainkillers, particularly opioid analgesics and non-steroidal anti-inflammatory drugs (NSAIDs), are commonly prescribed to manage injuries and pain. However, abuse can occur when athletes rely excessively on these drugs to continue competing.\n\n### Commonly Abused Painkillers\n- **Opioids**: Morphine, oxycodone, hydrocodone.\n- **NSAIDs**: Ibuprofen, naproxen (abuse generally less severe but problematic if overused).\n\n### Effects\n- Temporary pain relief and increased ability to train through injury.\n- Sedation and euphoria (primarily with opioids).\n\n### Risks\n- Opioid addiction, respiratory depression, and overdose.\n- Gastrointestinal issues and kidney damage with long-term NSAID use.\n- Masking of pain leading to worsened injuries.\n\nDue to the risk of dependency, strict guidelines exist for the medical use of painkillers among athletes.\n\n## Recreational Drugs\n\nSome athletes use recreational drugs for relaxation or coping with stress, despite the negative impact on performance and health.\n\n### Common Recreational Drugs Abused\n- **Cannabis**: Used for relaxation and pain relief; banned in many competitions.\n- **Alcohol**: Consumed socially but can impair recovery and performance.\n- **Cocaine and Ecstasy**: Occasionally abused for their stimulant and euphoric effects.\n\n### Effects\n- Altered mental state, reduced reaction time, and impaired coordination.\n- Short-term mood enhancement or relaxation.\n\n### Risks\n- Detrimental effects on cardiovascular health.\n- Legal issues and sanctions from sports authorities.\n- Negative impact on motivation, training, and overall athletic performance.\n\n## Conclusion\n\nThe abuse of steroids, stimulants, painkillers, and recreational drugs poses serious health risks and ethical dilemmas in sports. While the desire to perform better or manage pain is understandable, these substances can undermine an athlete’s long-term wellbeing and the integrity of athletic competition. Education, support, and strict regulation remain crucial in addressing substance abuse among athletes.\n\n# Causes and Risk Factors: Exploring Psychological, Social, and Professional Contributors to Substance Abuse Among Athletes\n\nSubstance abuse among athletes is a pressing concern that extends beyond physical performance to affect their mental health, career longevity, and overall well-being. Understanding the underlying causes and risk factors is essential for developing effective prevention and intervention strategies. This article delves into the psychological, social, and professional factors that contribute to substance abuse in the athletic community.\n\n## Psychological Factors\n\n### Pressure to Perform and Perfectionism\nAthletes often face intense pressure to perform at peak levels consistently. This demand can trigger anxiety, stress, and feelings of inadequacy. The desire for perfection may lead some athletes to use substances such as stimulants or performance-enhancing drugs to cope with the high expectations and overcome self-doubt.\n\n### Mental Health Challenges\nDepression, anxiety disorders, and other mental health issues are prevalent among athletes. The stigma around seeking psychological help often forces athletes to self-medicate with alcohol or drugs as a way to manage emotional pain, stress, or injury-related trauma.\n\n### Coping Mechanisms for Injury and Pain\nPhysical injuries are a common aspect of athletic careers. The chronic pain associated with injuries can lead athletes to misuse prescription medications like opioids or overconsume alcohol to alleviate discomfort and continue training or competing, inadvertently increasing the risk of substance dependence.\n\n## Social Factors\n\n### Peer Influence and Team Culture\nThe social environment within teams can profoundly impact substance use behaviors. If drug or alcohol use is normalized or even glamorized among teammates, new or younger athletes may feel pressured to conform to these norms to gain acceptance or camaraderie.\n\n### Social Isolation and Loneliness\nDespite being part of a team, athletes may experience social isolation due to rigorous training schedules, travel, or being away from family and friends. This loneliness can increase vulnerability to substance use as a misguided attempt to fill emotional voids or manage feelings of alienation.\n\n### Media and Celebrity Influence\nExposure to celebrity athletes who openly use or are rumored to use substances can create misleading narratives about drug use and its perceived benefits. This influence can lower inhibitions and reshape attitudes towards substance use, especially among younger athletes aspiring to emulate their idols.\n\n## Professional Factors\n\n### Demands of Competitive Sports\nThe professional athletic environment is highly competitive, with careers often hinging on performance and winning. The associated stress can drive athletes to use substances that promise enhanced stamina, focus, or recovery, sometimes blurring the line between legitimate medical use and abuse.\n\n### Career Uncertainty and Transition Stress\nAthletes frequently face uncertainty regarding career longevity due to factors such as injuries or declining performance. The stress related to contract negotiations, retirement planning, or forced career changes may lead to increased substance use as a maladaptive coping strategy.\n\n### Accessibility and Medical Prescriptions\nAthletes generally have greater access to medical facilities and prescription medications than the general population. While this access is crucial for health management, it also increases the risk of prescription drug misuse, especially when oversight is inadequate or when medications are used beyond therapeutic purposes.\n\n## Conclusion\n\nThe causes and risk factors behind substance abuse among athletes are multifaceted, involving a complex interplay of psychological pressures, social dynamics, and professional challenges. Recognizing these contributing elements is vital for coaches, medical professionals, and support networks to create comprehensive prevention programs and provide the necessary resources to help athletes maintain healthy lifestyles free from substance abuse. Only through holistic understanding and targeted action can the athletic community effectively combat this pervasive issue.\n\n# Health and Performance Consequences: The Physical and Mental Impacts of Substance Abuse on Athletes\n\nSubstance abuse among athletes is a critical issue that can profoundly affect both their health and sports performance. While some may turn to drugs or alcohol as a coping mechanism for stress, injury, or pressure, the consequences can be far-reaching and damaging. This article provides an in-depth analysis of the physical and mental impacts of substance abuse on athletes, emphasizing why maintaining a healthy lifestyle is essential for peak performance and overall well-being.\n\n## Physical Impacts of Substance Abuse on Athletes\n\n### 1. Deterioration of Cardiovascular Health\nMany substances abused by athletes, such as stimulants and anabolic steroids, place significant strain on the cardiovascular system. Stimulants like cocaine and amphetamines increase heart rate and blood pressure, leading to arrhythmias, hypertension, and even sudden cardiac arrest. Anabolic steroids can cause thickening of the heart muscle and increase the risk of heart attacks and strokes. Such cardiovascular issues directly impair an athlete's endurance, stamina, and ability to perform at high levels.\n\n### 2. Impaired Muscular Strength and Recovery\nThough some athletes misuse anabolic steroids to enhance muscle mass, chronic abuse can backfire by causing muscle cramps, weakness, and tendon ruptures. Other substances like alcohol interfere with protein synthesis and muscle repair, prolonging recovery time after training or injury. Dehydration and nutrient depletion caused by certain drugs further weaken muscles, limiting strength and agility on the field.\n\n### 3. Compromised Immune Function\nSubstance abuse can suppress the immune system, making athletes more susceptible to infections and illnesses. For example, excessive alcohol intake has been shown to reduce white blood cell activity, reducing the body’s ability to fight off viruses and bacteria. This leads to increased downtime due to illness and a diminished capacity to handle the physical demands of training and competition.\n\n### 4. Respiratory and Neurological Damage\nSmoking substances like tobacco or marijuana damages lung tissue and reduces oxygen uptake, critical for aerobic performance. Inhalants and certain drugs can cause long-term neurological damage including impaired coordination, balance, and reflexes—all vital for athletic skills. Brain injuries resulting from drug use can be irreversible, severely impacting motor skills and reaction times.\n\n## Mental and Psychological Consequences\n\n### 1. Cognitive Decline and Impaired Decision-Making\nSubstance abuse affects areas of the brain responsible for judgment, focus, and reaction time. Athletes abusing drugs may experience difficulties with concentration, memory, and decision-making — all essential skills for strategic gameplay and quick reactions in sports. Cognitive impairment can lead to poor game performance and increased risk of injury.\n\n### 2. Increased Anxiety, Depression, and Mood Disorders\nMany athletes turn to substances as a method of managing anxiety or depression. However, the temporary relief these substances provide is often followed by worsening symptoms. Long-term substance abuse is linked with increased rates of anxiety disorders, depression, and mood swings. Mental health struggles not only impair motivation and training consistency but also increase the likelihood of burnout and withdrawal from sport.\n\n### 3. Addiction and Dependence\nPhysical and psychological dependence on performance-enhancing or recreational drugs can trap athletes in destructive cycles. Addiction disrupts daily routines, training schedules, and professional commitments. The stress of hiding substance use and dealing with its consequences adds an additional psychological burden, often leading to social isolation and damaged relationships.\n\n### 4. Impact on Team Dynamics and Reputation\nMental health issues stemming from substance abuse can make athletes volatile or withdrawn, affecting communication and collaboration within a team. Additionally, public knowledge of substance abuse can tarnish an athlete’s reputation, leading to a loss of sponsorships, fan support, and career opportunities.\n\n## Conclusion: Prioritizing Health to Sustain Performance\n\nThe interplay between substance abuse and athletic health creates a dangerous cycle where physical and mental degradations impair performance, which in turn may lead to further substance use in an attempt to compensate. Athletes must prioritize healthy coping strategies, proper medical guidance, and mental health support to maintain optimal performance and longevity in their sports careers. Coaches, trainers, and sporting organizations also play a pivotal role in providing education and resources to prevent substance abuse and support recovery. Ultimately, safeguarding an athlete’s well-being ensures not only superior performance but a fulfilling and sustainable athletic journey.\n\n# Detection and Prevention Strategies for Substance Abuse in Athletes\n\nSubstance abuse in athletes not only undermines fair competition but also poses significant health risks. To maintain integrity in sports and protect athletes’ well-being, robust detection and prevention strategies are essential. This article explores the methods used to identify substance abuse in athletes and the proactive approaches employed to prevent it, focusing on testing protocols and educational programs.\n\n## Detection of Substance Abuse in Athletes\n\n### 1. Drug Testing Protocols\nOne of the primary methods for detecting substance abuse in athletes is through comprehensive drug testing programs. These tests can be conducted both in and out of competition, aiming to identify banned substances such as anabolic steroids, stimulants, diuretics, and other performance-enhancing drugs (PEDs).\n\n- **Urine Testing:** The most common and widely used method, urine tests can detect a broad range of substances and their metabolites. Samples are collected under strict supervision to prevent tampering.\n- **Blood Testing:** Used to detect substances that may not appear in urine or to determine the concentration of certain drugs, such as erythropoietin (EPO) or hormones.\n- **Hair and Saliva Testing:** These methods are less common but provide longer detection windows or rapid results, respectively.\n\nTesting is typically random, scheduled, or targeted based on certain risk factors or suspicious behavior, helping to deter and catch substance abuse.\n\n### 2. Biological Passport Programs\nThe Athlete Biological Passport (ABP) monitors selected biological variables over time. Rather than detecting the substance directly, it identifies abnormal changes in an athlete’s biological markers that suggest doping. This method enhances detection capabilities for substances that are otherwise difficult to identify.\n\n### 3. Observational and Behavioral Monitoring\nCoaches, medical staff, and anti-doping officials also rely on behavioral observations to detect potential substance abuse. Changes in performance, physical appearance, or behavior can prompt further investigation or testing.\n\n## Prevention Strategies\n\n### 1. Education Programs\nEducation is a cornerstone in preventing substance abuse. Athletes, coaches, and supporting personnel are provided with information about:\n\n- The health risks associated with drug use.\n- The ethical implications and impact on sporting integrity.\n- The specific substances banned by anti-doping authorities.\n- How testing procedures work and the consequences of violations.\n\nEffective education fosters informed decision-making and empowers athletes to resist pressure or temptation to use prohibited substances.\n\n### 2. Promoting a Culture of Clean Sport\nEncouraging a culture that values fair play and health over winning at all costs is essential. This includes:\n\n- Encouraging open dialogue about doping risks.\n- Highlighting positive role models who compete clean.\n- Building supportive environments where athletes can seek help for pressures or substance-related issues without stigma.\n\n### 3. Support Services and Counseling\nProviding access to psychological support and counseling can address underlying issues that might lead athletes to use substances, such as stress, anxiety, or injury-related pain management.\n\n### 4. Policy and Enforcement\nClear rules, consistent enforcement, and transparent consequences reinforce deterrents against doping. Collaboration among sports organizations, anti-doping agencies, and governments ensures that policies are up-to-date and effectively implemented.\n\n## Conclusion\n\nDetecting and preventing substance abuse in athletes requires a multifaceted approach that combines advanced testing technologies, continuous monitoring, education, and supportive environments. By integrating these strategies, the sports community can better protect athletes’ health, uphold the spirit of fair competition, and maintain public confidence in sport.\n\n# Support and Rehabilitation: Helping Athletes Overcome Substance Abuse\n\nSubstance abuse is a significant challenge faced by many athletes, often impacting not only their performance but also their overall health and well-being. Fortunately, a variety of support systems and rehabilitation programs have been developed to assist athletes in overcoming these issues. These resources provide comprehensive care, from initial intervention to long-term recovery, helping athletes regain control of their lives both on and off the field.\n\n## Understanding the Need for Support and Rehabilitation\n\nAthletes are under tremendous pressure to perform, which can sometimes lead to the misuse of substances such as performance enhancers, painkillers, or recreational drugs. The stigma surrounding substance abuse in sports may create barriers to seeking help, making effective support and rehabilitation programs critical.\n\nThese programs are specifically designed to address the unique physical, emotional, and psychological demands athletes face. Tailored support systems ensure that athletes receive care that respects their competitive schedules while focusing on sustainable recovery.\n\n## Types of Support Systems Available to Athletes\n\n### 1. Counseling and Psychological Support\n\nOne of the foundational elements in addressing substance abuse is access to professional counseling. Sports psychologists and addiction counselors work with athletes to tackle underlying issues such as anxiety, depression, or trauma that often contribute to substance misuse. Cognitive-behavioral therapy (CBT) and motivational interviewing are common techniques used to foster behavioral change and build resilience.\n\n### 2. Peer Support Groups\n\nPeer networks provide a safe environment where athletes can share experiences, challenges, and coping strategies. Groups such as Athlete Assistance Programs (AAP) and specialized 12-step programs tailored for athletes encourage mutual support and accountability. Being part of a community reduces feelings of isolation and stigma, fostering a sense of belonging and motivation.\n\n### 3. Family and Social Support\n\nRehabilitation is more effective when athletes have a strong support system at home and within their social circles. Many programs involve family therapy or educational sessions to help loved ones understand substance abuse and learn how to provide constructive support throughout recovery.\n\n## Rehabilitation Programs Tailored for Athletes\n\n### 1. Inpatient Rehabilitation\n\nFor athletes with severe substance dependence, inpatient rehabilitation centers offer intensive, structured care. These programs provide medical supervision, detoxification services, and comprehensive therapy in a controlled environment. Many centers integrate physical conditioning and sports-specific rehabilitation to help athletes maintain fitness and facilitate reintegration into their sport.\n\n### 2. Outpatient Rehabilitation\n\nOutpatient programs provide flexibility for athletes who cannot commit to prolonged residential stays due to training or competition schedules. These programs offer scheduled therapy sessions, group meetings, and medical support, allowing athletes to receive care while continuing their regular activities. Outpatient care often serves as a step-down for those transitioning from inpatient programs.\n\n### 3. Holistic and Integrative Approaches\n\nModern rehabilitation programs increasingly incorporate holistic therapies to address the physical and emotional aspects of recovery. These may include yoga, mindfulness meditation, nutrition counseling, and acupuncture. Such approaches help athletes develop healthier lifestyles, manage stress, and reduce the likelihood of relapse.\n\n## Role of Sports Organizations and Governing Bodies\n\nSports organizations play a pivotal role in providing resources and creating policies that support athletes facing substance abuse. Many federations offer confidential help lines, educational seminars, and funding for rehabilitation programs. Anti-doping agencies also emphasize rehabilitation over punishment, aiming to promote athlete health and ethical competition.\n\n## Success Stories and Outcomes\n\nNumerous athletes have successfully overcome substance abuse through dedicated support and rehabilitation. These success stories highlight the effectiveness of targeted programs that combine medical treatment, psychological support, and social reintegration. Recovery not only improves personal health but also restores athletic potential and inspires others facing similar struggles.\n\n## Conclusion\n\nSubstance abuse among athletes is a complex issue requiring specialized support and rehabilitation programs. By leveraging counseling, peer support, family involvement, and tailored rehabilitation services, athletes can overcome these challenges and return to optimal performance and well-being. Ongoing collaboration between healthcare providers, sports organizations, and athletes themselves is essential to foster environments that encourage recovery and sustainable success.\n\n## Conclusion and Future Perspectives\n\n### Summary of Key Points\n\nAddressing substance abuse among athletes remains a critical challenge that demands comprehensive, evidence-based strategies. Throughout this article, we have explored the multifaceted nature of substance abuse in the athletic community, highlighting its complex interplay with physical performance pressures, mental health issues, and social influences. Key points include:\n\n- **Prevalence and Types of Substance Abuse:** Athletes are susceptible to various substances, ranging from performance-enhancing drugs like anabolic steroids to recreational drugs and prescription medication misuse.\n- **Risk Factors:** High-performance demands, injury management, psychological stress, and the culture of competitive sports contribute significantly to the risk of substance abuse.\n- **Impact on Health and Career:** Substance abuse not only jeopardizes athletes’ physical and mental well-being but also threatens their careers through sanctions, suspensions, and loss of reputation.\n- **Current Prevention and Intervention Strategies:** These include education programs, strict doping controls, psychological support, and rehabilitation services tailored to the athletic population.\n\nUnderstanding these essentials underscores the need for a strategic, multidisciplinary approach to mitigate substance abuse risks effectively.\n\n### Emerging Trends and Future Approaches\n\nLooking forward, the landscape of managing substance abuse among athletes is evolving, propelled by advancements in technology, research, and policy development. New trends and promising approaches include:\n\n- **Personalized Prevention Programs:** Leveraging data analytics and behavioral assessments to create individualized intervention plans that address specific risk factors and psychological profiles unique to each athlete.\n- **Enhanced Screening and Detection Methods:** Innovations such as biomarker identification, genetic testing, and advanced neuroimaging are improving the accuracy and timeliness of substance abuse detection.\n- **Integrative Mental Health Services:** Future programs emphasize holistic care by integrating mental health support, stress management, and resilience training within athletic training and medical teams.\n- **Technology-Driven Monitoring:** Wearable devices and mobile health apps are emerging as tools to monitor physiological and psychological indicators, enabling early intervention before substance use escalates.\n- **Policy and Cultural Shift:** Developing policies that not only penalize substance abuse but also destigmatize seeking help, encouraging athletes to come forward without fear of retaliation or judgment.\n- **Collaborative Stakeholder Engagement:** Involving coaches, medical staff, sports organizations, family members, and peers to foster a supportive environment conducive to prevention and recovery.\n\n### Conclusion\n\nThe fight against substance abuse among athletes is ongoing, and it requires adaptability to emerging scientific insights and societal changes. By embracing innovative technologies, prioritizing mental health, and fostering open communication, sports communities can better protect athletes from the risks of substance abuse. The future holds promise for more effective, personalized, and compassionate approaches that not only safeguard athletic integrity but also promote overall health and well-being."}</code></pre>
</div>
</div>
<p>Finally, I’ll show you how to implement a evaluator-optimizer workflow.</p>
</section>
</section>
<section id="evaluator-optimizer" class="level3">
<h3 class="anchored" data-anchor-id="evaluator-optimizer">Evaluator-optimizer</h3>
<p>This workflow is useful when we have clear evaluation criteria that an LLM evaluator can use to provide feedback to the LLM generator to iteratively improve its output.</p>
<p>Here’s what the workflow looks like:</p>
<div class="cell" data-layout-align="default">
<div class="cell-output-display">
<div>
<p></p><figure class="figure"><p></p>
<div>
<pre class="mermaid mermaid-js">flowchart LR
    In([In]) --&gt; Gen["Generator (LLM)"]
    Gen -- "Solution" --&gt; Eval["Evaluator (LLM)"]
    Eval -- "Accepted" --&gt; Out([Out])
    Eval -- "Rejected + Feedback" --&gt; Gen
</pre>
</div>
<p></p></figure><p></p>
</div>
</div>
</div>
<p><strong>Examples:</strong></p>
<ul>
<li>Content generation that must match certain guidelines such as writing with a particular style.</li>
<li>Improving search results iteratively</li>
</ul>
<p>I’ll walk you through an example of an evaluator-optimizer workflow where you’ll generate a text, evaluate if it matches certain criteria, and then iteratively improve it.</p>
<p>Let’s start with the vanilla implementation.</p>
<section id="vanilla-langchain-4" class="level4">
<h4 class="anchored" data-anchor-id="vanilla-langchain-4">Vanilla (+LangChain)</h4>
<p>As usual, you start by defining the state and required data models.</p>
<div id="cell-95" class="cell" data-execution_count="36">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb42" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb42-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> Evaluation(BaseModel):</span>
<span id="cb42-2">    explanation: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Field(</span>
<span id="cb42-3">        description<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Explain why the text evaluated matches or not the evaluation criteria"</span></span>
<span id="cb42-4">    )</span>
<span id="cb42-5">    feedback: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Field(</span>
<span id="cb42-6">        description<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Provide feedback to the writer to improve the text"</span></span>
<span id="cb42-7">    )</span>
<span id="cb42-8">    is_correct: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">bool</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Field(</span>
<span id="cb42-9">        description<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Whether the text evaluated matches or not the evaluation criteria"</span></span>
<span id="cb42-10">    )</span>
<span id="cb42-11"></span>
<span id="cb42-12"></span>
<span id="cb42-13"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> State(BaseModel):</span>
<span id="cb42-14">    topic: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span></span>
<span id="cb42-15">    article: Optional[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span></span>
<span id="cb42-16">    evaluation: Optional[Evaluation] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span></span></code></pre></div></div>
</div>
<p>Then, you define the functions for each step in the workflow.</p>
<div id="cell-97" class="cell" data-execution_count="37">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb43" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb43-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> evaluate_text(state: State) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> Evaluation:</span>
<span id="cb43-2">    model_with_str_output <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> model.with_structured_output(Evaluation)</span>
<span id="cb43-3">    messages <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb43-4">        SystemMessage(</span>
<span id="cb43-5">            content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"You are an expert evaluator. Provided with a text, you will evaluate if it's written in British English and if it's appropriate for a young audience. The text must always use British spelling and grammar. Make sure the text doesn't include any em dashes."</span></span>
<span id="cb43-6">        ),</span>
<span id="cb43-7">        HumanMessage(content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Evaluate the following text:</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n\n</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>state<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>article<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>),</span>
<span id="cb43-8">    ]</span>
<span id="cb43-9">    response <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> model_with_str_output.invoke(messages)</span>
<span id="cb43-10">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> response</span>
<span id="cb43-11"></span>
<span id="cb43-12"></span>
<span id="cb43-13"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> fix_text(state: State) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>:</span>
<span id="cb43-14">    messages <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb43-15">        SystemMessage(</span>
<span id="cb43-16">            content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"You are an expert writer. Provided with a text and feedback, you wil improve the text."</span></span>
<span id="cb43-17">        ),</span>
<span id="cb43-18">        HumanMessage(</span>
<span id="cb43-19">            content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"You were tasked with writing an article about </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>state<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>topic<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">. You wrote the following text:</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n\n</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>state<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>article<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n\n</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">You've got the following feedback:</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n\n</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>state<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>evaluation<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>feedback<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n\n</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">Fix the text to improve it."</span></span>
<span id="cb43-20">        ),</span>
<span id="cb43-21">    ]</span>
<span id="cb43-22">    response <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> model.invoke(messages)</span>
<span id="cb43-23">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> response.content</span>
<span id="cb43-24"></span>
<span id="cb43-25"></span>
<span id="cb43-26"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> generate_text(state: State) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>:</span>
<span id="cb43-27">    messages <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb43-28">        SystemMessage(</span>
<span id="cb43-29">            content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"You are an expert writer. Provided with a topic, you will generate an engaging article with less than 500 words."</span></span>
<span id="cb43-30">        ),</span>
<span id="cb43-31">        HumanMessage(content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Generate a text about this topic:</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n\n</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>state<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>topic<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>),</span>
<span id="cb43-32">    ]</span>
<span id="cb43-33">    response <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> model.invoke(messages)</span>
<span id="cb43-34">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> response.content</span>
<span id="cb43-35"></span>
<span id="cb43-36"></span>
<span id="cb43-37"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> generate_text_dispatch(state: State) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>:</span>
<span id="cb43-38">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> state.evaluation:</span>
<span id="cb43-39">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> fix_text(state)</span>
<span id="cb43-40">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> generate_text(state)</span></code></pre></div></div>
</div>
<p>Finally, you create <code>run_workflow</code> function that orchestrates the workflow. In this case, it takes a topic, generates a text, evaluates it, and tries to iteratively improve it. If it fails more than 3 times, it stops.</p>
<div id="cell-99" class="cell" data-execution_count="38">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb44" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb44-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> run_workflow(topic: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> State:</span>
<span id="cb44-2">    state <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> State(topic<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>topic)</span>
<span id="cb44-3">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> _ <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>):</span>
<span id="cb44-4">        state.article <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> generate_text_dispatch(state)</span>
<span id="cb44-5">        state.evaluation <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> evaluate_text(state)</span>
<span id="cb44-6">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> state.evaluation.is_correct:</span>
<span id="cb44-7">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> state</span>
<span id="cb44-8">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> state</span>
<span id="cb44-9"></span>
<span id="cb44-10"></span>
<span id="cb44-11">output <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> run_workflow(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Substance abuse of athletes"</span>)</span></code></pre></div></div>
</div>
<p>Next, let’s see the LangGraph implementation.</p>
</section>
<section id="langgraph-4" class="level4">
<h4 class="anchored" data-anchor-id="langgraph-4">LangGraph</h4>
<p>You’ll start by defining the state of the workflow and data model required for the workflow.</p>
<div id="cell-103" class="cell" data-execution_count="39">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb45" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb45-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> Evaluation(BaseModel):</span>
<span id="cb45-2">    explanation: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span></span>
<span id="cb45-3">    feedback: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span></span>
<span id="cb45-4">    is_correct: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">bool</span></span>
<span id="cb45-5"></span>
<span id="cb45-6"></span>
<span id="cb45-7"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> State(TypedDict):</span>
<span id="cb45-8">    topic: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span></span>
<span id="cb45-9">    article: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span></span>
<span id="cb45-10">    evaluation: Evaluation</span>
<span id="cb45-11">    num_reviews: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span></span></code></pre></div></div>
</div>
<p>In this case, you keep the number of reviews in the state. That’s how you’ll be able to stop the workflow when the number of reviews is reached.</p>
<p>Next, you must define the functions for each node in the workflow.</p>
<div id="cell-105" class="cell" data-execution_count="40">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb46" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb46-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> generate_article(state: State) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>:</span>
<span id="cb46-2">    messages <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb46-3">        SystemMessage(</span>
<span id="cb46-4">            content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"You are an expert writer. Provided with a topic, you will generate an engaging article with less than 500 words."</span></span>
<span id="cb46-5">        ),</span>
<span id="cb46-6">        HumanMessage(content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Generate a text about this topic:</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n\n</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>state[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'topic'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>),</span>
<span id="cb46-7">    ]</span>
<span id="cb46-8">    response <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> model.invoke(messages)</span>
<span id="cb46-9">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"article"</span>: response.content}</span>
<span id="cb46-10"></span>
<span id="cb46-11"></span>
<span id="cb46-12"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> fix_article(state: State) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>:</span>
<span id="cb46-13">    messages <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb46-14">        SystemMessage(</span>
<span id="cb46-15">            content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"You are an expert writer. Provided with a text, you will fix the text to improve it. The text must always use British spelling and grammar."</span></span>
<span id="cb46-16">        ),</span>
<span id="cb46-17">        HumanMessage(</span>
<span id="cb46-18">            content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"You were tasked with writing an article about </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>state[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'topic'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">. You wrote the following text:</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n\n</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>state[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'article'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n\n</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">You've got the following feedback:</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n\n</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>state[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'evaluation'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.</span>feedback<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n\n</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">Fix the text to improve it."</span></span>
<span id="cb46-19">        ),</span>
<span id="cb46-20">    ]</span>
<span id="cb46-21">    response <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> model.invoke(messages)</span>
<span id="cb46-22">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"article"</span>: response.content}</span>
<span id="cb46-23"></span>
<span id="cb46-24"></span>
<span id="cb46-25"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> evaluate_article(state: State) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>:</span>
<span id="cb46-26">    model_with_str_output <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> model.with_structured_output(Evaluation)</span>
<span id="cb46-27">    messages <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb46-28">        SystemMessage(</span>
<span id="cb46-29">            content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"You are an expert evaluator. Provided with a text, you will evaluate if it's written in British English and if it's appropriate for a young audience. The text must always use British spelling and grammar. Make sure the text doesn't include any em dash. Be very strict with the evaluation. In case of doubt, return a negative evaluation."</span></span>
<span id="cb46-30">        ),</span>
<span id="cb46-31">        HumanMessage(content<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Evaluate the following text:</span><span class="ch" style="color: #20794D;
background-color: null;
font-style: inherit;">\n\n</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>state[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'article'</span>]<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>),</span>
<span id="cb46-32">    ]</span>
<span id="cb46-33">    response <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> model_with_str_output.invoke(messages)</span>
<span id="cb46-34">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"evaluation"</span>: response, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"num_reviews"</span>: state.get(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"num_reviews"</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>}</span>
<span id="cb46-35"></span>
<span id="cb46-36"></span>
<span id="cb46-37"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> route_text(state: State) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>:</span>
<span id="cb46-38">    evaluation <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> state.get(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"evaluation"</span>, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>)</span>
<span id="cb46-39">    num_reviews <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> state.get(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"num_reviews"</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb46-40">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> evaluation <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">and</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> evaluation.is_correct <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">and</span> num_reviews <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>:</span>
<span id="cb46-41">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Fail"</span></span>
<span id="cb46-42">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Pass"</span></span>
<span id="cb46-43"></span>
<span id="cb46-44"></span>
<span id="cb46-45"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> generate_article_dispatch(state: State) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</span>:</span>
<span id="cb46-46">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"evaluation"</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> state <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">and</span> state[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"evaluation"</span>]:</span>
<span id="cb46-47">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> fix_article(state)</span>
<span id="cb46-48">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span>:</span>
<span id="cb46-49">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> generate_article(state)</span></code></pre></div></div>
</div>
<p>You define:</p>
<ol type="1">
<li><code>generate_text</code>: This function generates a text based on the topic.</li>
<li><code>evaluate_text</code>: This function evaluates the text based on the topic.</li>
<li><code>fix_text</code>: This function fixes the text based on the feedback.</li>
<li><code>generate_article_dispatch</code>: This function dispatches the text generation task to either the <code>generate_text</code> or <code>fix_text</code> function based on the evaluation.</li>
</ol>
<p>Next, you need to define the graph that will be used to run the workflow.</p>
<div id="cell-107" class="cell" data-execution_count="41">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb47" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb47-1">workflow_builder <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> StateGraph(State)</span>
<span id="cb47-2"></span>
<span id="cb47-3">workflow_builder.add_node(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"generate_article"</span>, generate_article_dispatch)</span>
<span id="cb47-4">workflow_builder.add_node(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"evaluate_article"</span>, evaluate_article)</span>
<span id="cb47-5"></span>
<span id="cb47-6">workflow_builder.add_edge(START, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"generate_article"</span>)</span>
<span id="cb47-7">workflow_builder.add_edge(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"generate_article"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"evaluate_article"</span>)</span>
<span id="cb47-8">workflow_builder.add_conditional_edges(</span>
<span id="cb47-9">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"evaluate_article"</span>, route_text, {<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Pass"</span>: END, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Fail"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"generate_article"</span>}</span>
<span id="cb47-10">)</span>
<span id="cb47-11"></span>
<span id="cb47-12">workflow <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> workflow_builder.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">compile</span>()</span></code></pre></div></div>
</div>
<p>You start by defining the workflow’s architecture with a StateGraph object. Your functions are then brought in as the nodes, and the operational sequence between them is set by defining the edges. Similar to the <em>Prompt chaining</em> pattern, you can use a conditional edge to dynamically route the workflow’s logic based on its current state. But instead of routing to a node, you route back to the text generation node, and iterate until the text is good enough.</p>
<p>Next, you call the <code>compile</code> method to convert your graph into a runnable workflow. You can also generate a visual diagram of the workflow using the <code>get_graph</code> and <code>draw_mermaid_png</code> functions.</p>
<div id="cell-109" class="cell" data-execution_count="42">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb48" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb48-1">display(Image(workflow.get_graph().draw_mermaid_png()))</span></code></pre></div></div>
<div class="cell-output cell-output-display">
<div>
<figure class="figure">
<p><a href="agentic-workflows-langgraph_files/figure-html/cell-43-output-1.png" class="lightbox" data-gallery="quarto-lightbox-gallery-5"><img src="https://dylancastillo.co/posts/agentic-workflows-langgraph_files/figure-html/cell-43-output-1.png" class="img-fluid figure-img"></a></p>
</figure>
</div>
</div>
</div>
<p>Finally, you can run the workflow with the <code>invoke</code> method.</p>
<div id="cell-111" class="cell" data-execution_count="43">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb49" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb49-1">workflow.invoke({<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"topic"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Suggest the use enhancing drugs to athletes"</span>})</span></code></pre></div></div>
<div class="cell-output cell-output-display" data-execution_count="43">
<pre><code>{'topic': 'Suggest the use enhancing drugs to athletes',
 'article': 'The use of performance-enhancing drugs (PEDs) in sports is a complicated and sensitive topic. These substances are said to help athletes improve their abilities and compete better. However, their use raises important questions about fairness, health, and rules. While some people believe that carefully controlled use of PEDs might have certain benefits, it is a subject that needs thoughtful discussion and clear regulation.\n\nSupporters of PEDs argue that they could make competitions fairer by helping athletes who do not have the same access to training and equipment. In many sports, very small differences can decide the winner, and some athletes may use these drugs to try to even the playing field. If used properly under medical supervision, PEDs might reduce inequalities caused by differences in coaching and resources.\n\nAnother point is that if doctors monitored athletes using these substances, health risks could be lowered. Often, athletes who use PEDs do so in secret and without medical guidance, which can be dangerous. A system of medical support could help ensure that athletes stay as safe as possible, with regular health checks and information about the risks involved.\n\nLegalising PED use might also change how sports organisations spend their money. Instead of focusing on punishing athletes for doping, resources could be put into improving training methods and helping athletes recover and stay healthy. This might encourage research into safer ways to enhance performance.\n\nMoreover, being open about PED use could make sports more honest. Doping has been a problem for a long time, and banning these drugs has not stopped people from using them secretly. A more transparent approach might reduce cheating and allow fans to better understand athletes’ achievements.\n\nDespite these points, it is very important that any use of PEDs is carefully controlled with strict rules, age limits, and ethical standards. Protecting athlete health and fairness in sport must always be the top priority. More research and discussion are needed before any changes are made.\n\nIn summary, although the use of performance-enhancing drugs remains a difficult and controversial issue, thoughtful regulation and openness could potentially improve fairness and safety in sports. However, this topic involves complex ideas that require careful and mature consideration.',
 'evaluation': Evaluation(explanation="The text is written using British English spelling conventions, such as 'legalising' with an 's' instead of 'legalizing'. The grammar is correct and appropriate for a young audience with clear, accessible language and no use of em dashes. The content is presented in a balanced, informative manner that is suitable for young readers, addressing the topic thoughtfully without complex jargon or inappropriate content.", feedback='The text uses British English correctly and is suitable for a young audience. It avoids complex sentence structures and uses accessible vocabulary. There are no em dashes present, maintaining adherence to the criteria. The content is presented in a balanced and neutral way appropriate for educational purposes.', is_correct=True),
 'num_reviews': 2}</code></pre>
</div>
</div>
<p>That’s it! You’ve now seen how to implement the most common agentic workflow patterns with a vanilla approach and with LangGraph.</p>
</section>
</section>
</section>
<section id="conclusion" class="level2">
<h2 class="anchored" data-anchor-id="conclusion">Conclusion</h2>
<p>Throughout this tutorial, you’ve seen how different agentic workflow patterns solve specific types of problems:</p>
<ul>
<li><strong>Prompt Chaining</strong>: Break complex tasks into sequential steps with clear handoffs</li>
<li><strong>Routing</strong>: Classify inputs and route them to specialized handlers</li>
<li><strong>Parallelization</strong>: Run multiple evaluations or processes simultaneously for speed and diversity</li>
<li><strong>Orchestrator-Workers</strong>: Dynamically decompose tasks and distribute work</li>
<li><strong>Evaluator-Optimizer</strong>: Create feedback loops for iterative quality improvement</li>
</ul>
<p>You’ve learned how to implement these patterns with and without LangGraph. While the vanilla approach give you full control and might be simpler for basic cases, LangGraph gives you many features that make it easier to build complex workflows.</p>
<p>Hope you find this tutorial useful. If you have any questions, let me know in the comments below.</p>


</section>

<div id="quarto-appendix" class="default"><section class="quarto-appendix-contents" id="quarto-citation"><h2 class="anchored quarto-appendix-heading">Citation</h2><div><div class="quarto-appendix-secondary-label">BibTeX citation:</div><pre class="sourceCode code-with-copy quarto-appendix-bibtex"><code class="sourceCode bibtex">@online{castillo2025,
  author = {Castillo, Dylan},
  title = {Agentic Workflows from Scratch with (and Without)
    {LangGraph}},
  date = {2025-07-03},
  url = {https://dylancastillo.co/posts/agentic-workflows-langgraph.html},
  langid = {en}
}
</code></pre><div class="quarto-appendix-secondary-label">For attribution, please cite this work as:</div><div id="ref-castillo2025" class="csl-entry quarto-appendix-citeas">
Castillo, Dylan. 2025. <span>“Agentic Workflows from Scratch with (and
Without) LangGraph.”</span> July 3. <a href="https://dylancastillo.co/posts/agentic-workflows-langgraph.html">https://dylancastillo.co/posts/agentic-workflows-langgraph.html</a>.
</div></div></section></div> ]]></description>
  <category>llm</category>
  <category>python</category>
  <category>anthropic</category>
  <category>openai</category>
  <category>agents</category>
  <guid>https://dylancastillo.co/posts/agentic-workflows-langgraph.html</guid>
  <pubDate>Thu, 03 Jul 2025 00:00:00 GMT</pubDate>
  <media:content url="https://dylancastillo.co/posts/images/cards/agentic-workflows-langgraph.png" medium="image" type="image/png" height="76" width="144"/>
</item>
<item>
  <title>Function calling and structured outputs in LLMs with LangChain and OpenAI</title>
  <dc:creator>Dylan Castillo</dc:creator>
  <link>https://dylancastillo.co/posts/function-calling-structured-outputs.html</link>
  <description><![CDATA[ 




<p>Function calling and structured outputs let you go from chatbots that just talk to agents that interact with the world. They’re two of the most important techniques for building LLM applications.</p>
<p>Function calling let LLMs access external tools and services. Structured outputs ensure that the data coming back from your models is ready to integrate</p>
<p>These are two of the most important techniques for building LLM applications. I can tell you that mastering them will make your applications better and easier to maintain.</p>
<p>In this tutorial, you’ll learn:</p>
<ul>
<li>How function calling and structured outputs work and when to use them</li>
<li>How to implement both techniques using LangChain and OpenAI</li>
<li>Practical examples you can run and adapt for your own projects.</li>
</ul>
<p>Let’s get started.</p>
<section id="function-calling" class="level2 page-columns page-full">
<h2 class="anchored" data-anchor-id="function-calling">Function calling</h2>
<p>Function calling refers to the ability to get LLMs to use external tools or functions. It matters because it gives LLMs more capabilities, allows them to talk to external systems, and enables complex task automation. This is one of the key features that unlocked agents.</p>
<p>The usual flow is:</p>
<ol type="1">
<li>The developer sets up an LLM with a set of predefined tools</li>
<li>The user asks a question</li>
<li>The LLM decides if it needs to use a tool</li>
<li>If it does, it invokes the tool and gets the output from the tool.</li>
<li>The LLM then uses the output to answer the user’s question</li>
</ol>
<p>Here’s a diagram that illustrates how function calling works:</p>
<div class="quarto-figure quarto-figure-center page-columns page-full">
<figure class="figure page-columns page-full">
<p><a href="./images/function-calling-structured-outputs/diagram.png" class="lightbox" data-gallery="quarto-lightbox-gallery-1" title="Function calling flow"><img src="https://dylancastillo.co/posts/images/function-calling-structured-outputs/diagram.png" class="img-fluid figure-img" alt="Function calling flow"></a></p>
<figcaption class="margin-caption">Function calling flow</figcaption>
</figure>
</div>
<p>AI developers are increasingly using function calling to build more complex systems. You can use it to:</p>
<ul>
<li>Get information from a CRM, DB, etc</li>
<li>Perform calculations (e.g., generate an estimate for a variable, financial calculations)</li>
<li>Manipulate data (e.g., data cleaning, data transformation)</li>
<li>Interact with external systems (e.g., booking a flight, sending an email)</li>
</ul>
</section>
<section id="structured-outputs" class="level2">
<h2 class="anchored" data-anchor-id="structured-outputs">Structured outputs</h2>
<p>Structured outputs are a group of methods that “ensure that model outputs adhere to a specific structure”<sup>1</sup>. With proprietary models, this usually means a JSON schema. With open-weight models, a structure can mean anything from a JSON schema to a specific regex pattern. You can use <a href="https://dottxt-ai.github.io/outlines/latest/">outlines</a> for this.</p>
<p>Structured outputs are very useful to create agentic systems, as they simplify the communication between components. As you can imagine, it’s a lot easier to parse the output of a JSON object than a free-form text. Note, however, that as with other things in life, there’s no free lunch. Using this technique might <a href="https://dylancastillo.co/posts/say-what-you-mean-sometimes.html">impact the performance</a> of your task, so you should have evals in place.</p>
<p>In the next sections, I’ll show you how to use function calling and structured outputs with OpenAI.</p>
</section>
<section id="prerequisites" class="level2">
<h2 class="anchored" data-anchor-id="prerequisites">Prerequisites</h2>
<p>To follow this tutorial you’ll need to:</p>
<ol type="1">
<li>Sign up and generate an API key in <a href="https://platform.openai.com/docs/overview">OpenAI</a>.</li>
<li>Sign up and generate an API key in <a href="https://smith.langchain.com/signup">LangSmith</a>.</li>
<li>Create an <code>.env</code> file with the following variables:</li>
</ol>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode numberSource bash number-lines code-with-copy"><code class="sourceCode bash"><span id="cb1-1"><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">OPENAI_API_KEY</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>sk-...</span>
<span id="cb1-2"><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">LANGCHAIN_TRACING_V2</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>true</span>
<span id="cb1-3"><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">LANGSMITH_API_KEY</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>lsv2_...</span>
<span id="cb1-4"><span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">LANGCHAIN_PROJECT</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"my-project"</span></span></code></pre></div></div>
<ol start="4" type="1">
<li>Create a virtual environment in Python and install the requirements:</li>
</ol>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode numberSource bash number-lines code-with-copy"><code class="sourceCode bash"><span id="cb2-1"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">python</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-m</span> venv venv</span>
<span id="cb2-2"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">source</span> venv/bin/activate</span>
<span id="cb2-3"><span class="ex" style="color: null;
background-color: null;
font-style: inherit;">pip</span> install langchain langsmith pydantic langchain-openai python-dotenv jupyter</span></code></pre></div></div>
<p>Once you’ve completed the steps above, you can run copy and paste the code from the next sections. You can also download the notebook from <a href="../posts/function-calling-structured-outputs.html">here</a>.</p>
</section>
<section id="examples" class="level2 page-columns page-full">
<h2 class="anchored" data-anchor-id="examples">Examples</h2>
<p>As usual, you’ll start by importing the necessary libraries.</p>
<p>You’ll use LangChain to interact with the OpenAI API and Pydantic for data validation.</p>
<div id="cell-4" class="cell" data-execution_count="1">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb3-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> textwrap <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> dedent</span>
<span id="cb3-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> typing <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> Literal</span>
<span id="cb3-3"></span>
<span id="cb3-4"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> requests</span>
<span id="cb3-5"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> dotenv <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> load_dotenv</span>
<span id="cb3-6"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> langchain_core.messages <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> HumanMessage, SystemMessage</span>
<span id="cb3-7"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> langchain_core.tools <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> tool</span>
<span id="cb3-8"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> langchain_openai <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> ChatOpenAI</span>
<span id="cb3-9"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> langsmith <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> traceable</span>
<span id="cb3-10"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> pydantic <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> BaseModel, Field</span>
<span id="cb3-11"></span>
<span id="cb3-12">load_dotenv()</span></code></pre></div></div>
</div>
<p>Now that you’ve imported the libraries, we’ll work on three examples:</p>
<ol type="1">
<li>Providing a model with a single tool</li>
<li>Providing a model with multiple tools</li>
<li>Generating a structured output from a model</li>
</ol>
<section id="function-calling-with-a-single-tool" class="level3 page-columns page-full">
<h3 class="anchored" data-anchor-id="function-calling-with-a-single-tool">Function calling with a single tool</h3>
<p>First you start by defining the model and the tool:</p>
<div id="cell-7" class="cell" data-execution_count="2">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb4-1">model <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ChatOpenAI(model_name<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"gpt-4.1-mini"</span>)</span>
<span id="cb4-2"></span>
<span id="cb4-3"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@tool</span></span>
<span id="cb4-4"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> find_weather(latitude: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>, longitude: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>):</span>
<span id="cb4-5">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Get the weather of a given latitude and longitude"""</span></span>
<span id="cb4-6">    response <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> requests.get(</span>
<span id="cb4-7">        <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"https://api.open-meteo.com/v1/forecast?latitude=</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>latitude<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">&amp;longitude=</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>longitude<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">&amp;current=temperature_2m,wind_speed_10m&amp;hourly=temperature_2m,relative_humidity_2m,wind_speed_10m"</span></span>
<span id="cb4-8">    )</span>
<span id="cb4-9">    data <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> response.json()</span>
<span id="cb4-10">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> data[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"current"</span>][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"temperature_2m"</span>]</span>
<span id="cb4-11"></span>
<span id="cb4-12"></span>
<span id="cb4-13">tools_mapping <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {</span>
<span id="cb4-14">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"find_weather"</span>: find_weather,</span>
<span id="cb4-15">}</span>
<span id="cb4-16"></span>
<span id="cb4-17">model_with_tools <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> model.bind_tools([find_weather])</span></code></pre></div></div>
</div>
<p>This code sets up a <code>gpt-4.1-mini</code> model with a single tool. To define a tool, you must define a function and use the <code>@tool</code> decorator. This function must necessarily have a docstring because this will be used to describe the tool to the model. In this case, the tool is a function that takes latitude and longitude values and returns the weather for that location by making a call to the Open Meteo API.</p>
<p>Next, you need to tell your code how to find and use your tools. This is the purpose of <code>tools_mapping</code>. It is a common point of confusion. The LLM doesn’t run the tools on its own. It only decides if a tool should be used. After the model makes its decision, your own code must make the actual tool call.</p>
<p>In this situation, since you only have one tool, a mapping isn’t really necessary. But if you were using multiple tools, which is often the case, you would need to create a “map” that links each tool’s name to its corresponding function. This lets you call the right tool when the model decides to use it.</p>
<p>Finally, you need to <em>bind</em> the tool to the model. The binding makes the model aware of the tool, so that it can use it.</p>
<p>Then, let’s define a function that lets you call the model with the tool.</p>
<div id="cell-9" class="cell" data-execution_count="3">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb5-1"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@traceable</span></span>
<span id="cb5-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> get_response(question: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>):</span>
<span id="cb5-3">    messages <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb5-4">        SystemMessage(</span>
<span id="cb5-5">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"You're a helpful assistant. Use the tools provided when relevant."</span></span>
<span id="cb5-6">        ),</span>
<span id="cb5-7">        HumanMessage(question),</span>
<span id="cb5-8">    ]</span>
<span id="cb5-9">    ai_message <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> model_with_tools.invoke(messages)</span>
<span id="cb5-10">    messages.append(ai_message)</span>
<span id="cb5-11"></span>
<span id="cb5-12">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> tool_call <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> ai_message.tool_calls:</span>
<span id="cb5-13">        selected_tool <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> tools_mapping[tool_call[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"name"</span>]]</span>
<span id="cb5-14">        tool_msg <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> selected_tool.invoke(tool_call)</span>
<span id="cb5-15">        messages.append(tool_msg)</span>
<span id="cb5-16"></span>
<span id="cb5-17">    ai_message <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> model_with_tools.invoke(messages)</span>
<span id="cb5-18">    messages.append(ai_message)</span>
<span id="cb5-19"></span>
<span id="cb5-20">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> ai_message.content</span>
<span id="cb5-21"></span>
<span id="cb5-22"></span>
<span id="cb5-23">response <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> get_response(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"What's the weather in Tokyo?"</span>)</span>
<span id="cb5-24"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(response)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>The current temperature in Tokyo is approximately 25.5°C. If you want more detailed weather information, please let me know!</code></pre>
</div>
</div>
<p>This function takes a city name and returns the weather for that city. It uses the <code>find_weather</code> tool to get the weather data.</p>
<p>It works as follows:</p>
<ol type="1">
<li><strong>Line 1</strong> adds a LangSmith’s <code>traceable</code> decorator to the function, so that you can see the trace of the function in the LangSmith UI. If you prefer to not use LangSmith, you can remove this line.</li>
<li><strong>Lines 2 to 10</strong> set up the <a href="https://dylancastillo.co/posts/prompt-engineering-101.html">prompts</a> and call the model.</li>
<li><strong>Lines 12 to 16</strong> is where the magic happens. This is a loop that will check if there’s been a tool call in the response from the model. If there is, it will call (invoke) the tool and add the result to the messages.</li>
<li><strong>Lines 17 to 18</strong> the model is called again to get the final response.</li>
</ol>
<p>When you run this code, you’ll get a text response with the weather for the city you asked for. If you check the trace, you can see how the whole process works:</p>
<div class="quarto-figure quarto-figure-center page-columns page-full">
<figure class="figure page-columns page-full">
<p><a href="./images/function-calling-structured-outputs/trace-spans.png" class="lightbox" data-gallery="quarto-lightbox-gallery-2" title="Function calling trace"><img src="https://dylancastillo.co/posts/images/function-calling-structured-outputs/trace-spans.png" class="img-fluid figure-img" alt="Function calling trace"></a></p>
<figcaption class="margin-caption">Function calling trace</figcaption>
</figure>
</div>
<p>There are three steps in the process:</p>
<ol type="1">
<li><strong>Initial model call</strong> with the question from the user.</li>
<li><strong>Tool call</strong> to get the weather data.</li>
<li><strong>Final model call</strong> to get the response.</li>
</ol>
<p>If you dig deeper into the first model call, you’ll see how the tool is provided to the model and how the model decides to use it:</p>
<div class="quarto-figure quarto-figure-center page-columns page-full">
<figure class="figure page-columns page-full">
<p><a href="./images/function-calling-structured-outputs/model-calls-tool.png" class="lightbox" data-gallery="quarto-lightbox-gallery-3" title="Model calls tool"><img src="https://dylancastillo.co/posts/images/function-calling-structured-outputs/model-calls-tool.png" class="img-fluid figure-img" alt="Model calls tool"></a></p>
<figcaption class="margin-caption">Model calls tool</figcaption>
</figure>
</div>
<p>The tools is provided by describing it to the model using the docstring of the function. The parameter and their types are also provided. Then the model responds specifying the name of the tool it wants to use and the parameters it wants to pass to it. This tool is then called:</p>
<div class="quarto-figure quarto-figure-center page-columns page-full">
<figure class="figure page-columns page-full">
<p><a href="./images/function-calling-structured-outputs/tool-call.png" class="lightbox" data-gallery="quarto-lightbox-gallery-4" title="Tool call"><img src="https://dylancastillo.co/posts/images/function-calling-structured-outputs/tool-call.png" class="img-fluid figure-img" alt="Tool call"></a></p>
<figcaption class="margin-caption">Tool call</figcaption>
</figure>
</div>
<p>The results of the tool call are then passed to the model again. The model then uses the result to generate the final response:</p>
<div class="quarto-figure quarto-figure-center page-columns page-full">
<figure class="figure page-columns page-full">
<p><a href="./images/function-calling-structured-outputs/tool-call-result.png" class="lightbox" data-gallery="quarto-lightbox-gallery-5" title="Tool call result"><img src="https://dylancastillo.co/posts/images/function-calling-structured-outputs/tool-call-result.png" class="img-fluid figure-img" alt="Tool call result"></a></p>
<figcaption class="margin-caption">Tool call result</figcaption>
</figure>
</div>
<p>That’s it. This how you provide a model with tools. In the next section, you’ll see how to use multiple tools.</p>
</section>
<section id="function-calling-with-multiple-tools" class="level3">
<h3 class="anchored" data-anchor-id="function-calling-with-multiple-tools">Function calling with multiple tools</h3>
<p>Similar to the previous example, you start by defining the tools (using the <code>@tool</code> decorator) and binding them to the model.</p>
<p>In addition to <code>get_weather</code>, you’ll also define a tool to check if a response follows the company guidelines. In this case, the company guidelines are that responses should be written in the style of a haiku.<sup>2</sup></p>
<div id="cell-13" class="cell" data-execution_count="4">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb7-1">model <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ChatOpenAI(model_name<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"gpt-4.1-mini"</span>)</span>
<span id="cb7-2"></span>
<span id="cb7-3"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@tool</span></span>
<span id="cb7-4"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> get_weather(latitude: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>, longitude: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>):</span>
<span id="cb7-5">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Get the weather of a given latitude and longitude"""</span></span>
<span id="cb7-6">    response <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> requests.get(</span>
<span id="cb7-7">        <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"https://api.open-meteo.com/v1/forecast?latitude=</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>latitude<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">&amp;longitude=</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>longitude<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">&amp;current=temperature_2m,wind_speed_10m&amp;hourly=temperature_2m,relative_humidity_2m,wind_speed_10m"</span></span>
<span id="cb7-8">    )</span>
<span id="cb7-9">    data <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> response.json()</span>
<span id="cb7-10">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> data[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"current"</span>][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"temperature_2m"</span>]</span>
<span id="cb7-11"></span>
<span id="cb7-12"></span>
<span id="cb7-13"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@tool</span></span>
<span id="cb7-14"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> check_guidelines(drafted_response: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>:</span>
<span id="cb7-15">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""Check if a given response follows the company guidelines"""</span></span>
<span id="cb7-16">    model <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ChatOpenAI(model_name<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"gpt-4.1-mini"</span>)</span>
<span id="cb7-17">    response <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> model.invoke(</span>
<span id="cb7-18">        [</span>
<span id="cb7-19">            SystemMessage(</span>
<span id="cb7-20">                <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"You're a helpful assistant. Your task is to check if a given response follows the company guidelines. The company guidelines are that responses should be written in the style of a haiku. You should reply with 'OK' or 'REQUIRES FIXING' and a short explanation."</span></span>
<span id="cb7-21">            ),</span>
<span id="cb7-22">            HumanMessage(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Current response: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>drafted_response<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>),</span>
<span id="cb7-23">        ]</span>
<span id="cb7-24">    )</span>
<span id="cb7-25">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> response.content</span>
<span id="cb7-26"></span>
<span id="cb7-27"></span>
<span id="cb7-28">tools_mapping <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {</span>
<span id="cb7-29">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"get_weather"</span>: get_weather,</span>
<span id="cb7-30">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"check_guidelines"</span>: check_guidelines,</span>
<span id="cb7-31">}</span>
<span id="cb7-32"></span>
<span id="cb7-33">model_with_tools <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> model.bind_tools([get_weather, check_guidelines])</span></code></pre></div></div>
</div>
<p>This code defines the tools and binds them to the model. Just like we did before, you also need to define a mapping of the tools, so that you can call the right tool when the model decides to use it.</p>
<div id="cell-15" class="cell" data-execution_count="6">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb8-1"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@traceable</span></span>
<span id="cb8-2"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> get_response(question: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>):</span>
<span id="cb8-3">    messages <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb8-4">        SystemMessage(</span>
<span id="cb8-5">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"You're a helpful assistant. Use the tools provided when relevant. Then draft a response and check if it follows the company guidelines. Only respond to the user after you've validated and modified the response if needed."</span></span>
<span id="cb8-6">        ),</span>
<span id="cb8-7">        HumanMessage(question),</span>
<span id="cb8-8">    ]</span>
<span id="cb8-9">    ai_message <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> model_with_tools.invoke(messages)</span>
<span id="cb8-10">    messages.append(ai_message)</span>
<span id="cb8-11"></span>
<span id="cb8-12">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">while</span> ai_message.tool_calls:</span>
<span id="cb8-13">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> tool_call <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> ai_message.tool_calls:</span>
<span id="cb8-14">            selected_tool <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> tools_mapping[tool_call[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"name"</span>]]</span>
<span id="cb8-15">            tool_msg <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> selected_tool.invoke(tool_call)</span>
<span id="cb8-16">            messages.append(tool_msg)</span>
<span id="cb8-17">        ai_message <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> model_with_tools.invoke(messages)</span>
<span id="cb8-18">        messages.append(ai_message)</span>
<span id="cb8-19"></span>
<span id="cb8-20">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> ai_message.content</span>
<span id="cb8-21"></span>
<span id="cb8-22"></span>
<span id="cb8-23">response <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> get_response(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"What is the temperature in Madrid?"</span>)</span>
<span id="cb8-24"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(response)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>Sunny Madrid basks,  
Thirty-six degrees embrace,  
Summer's warm caress.</code></pre>
</div>
</div>
<p>This code is pretty much the same as the previous example, but with two tools. There’s also a one small difference.</p>
<p>Previously, we checked for tool calls once. Now, we’ll use a while loop that keeps checking. So, instead of the model having to provide the final answer after one turn, it can now ask for tools multiple times in a row until it has all the information it needs.</p>
<p>This is the core idea behind how agents work. So, congratulations, you’ve just built a simple agent! If you check the process in LangSmith, you’ll see how these turns play out.</p>
<p>Next, let’s see how to use structured outputs.</p>
</section>
<section id="structured-outputs-1" class="level3">
<h3 class="anchored" data-anchor-id="structured-outputs-1">Structured outputs</h3>
<p>Structured outputs are a set of methods used to get model outputs that follow a specific structure. This is useful when you want to get a specific type of output, such as a JSON object.</p>
<p>It’s easy to set up with proprietary models. With LangChain, you can define a <code>dict</code> or a <a href="https://docs.pydantic.dev/latest/concepts/models/">Pydantic model</a> to describe the output. I recommend using Pydantic models.</p>
<p>For example, let’s define a Pydantic model that will help us classify document into categories:</p>
<div id="cell-19" class="cell" data-execution_count="7">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb10" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb10-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> DocumentInfo(BaseModel):</span>
<span id="cb10-2">    category: Literal[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"financial"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"legal"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"marketing"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"pets"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"other"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Field(</span>
<span id="cb10-3">        description<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"The category of the document"</span></span>
<span id="cb10-4">    )</span>
<span id="cb10-5">    summary: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Field(description<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"A short summary of the document"</span>)</span></code></pre></div></div>
</div>
<p>This model defines the structured output we’ll get from the model. It has two fields: <code>category</code> and <code>summary</code>.</p>
<p>Then, you can use the <code>with_structured_output</code> method to create a model that will return the structured output:</p>
<div id="cell-21" class="cell" data-execution_count="8">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb11" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb11-1">model <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ChatOpenAI(model<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"gpt-4.1-mini"</span>, temperature<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>)</span>
<span id="cb11-2"></span>
<span id="cb11-3"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> get_document_info(document: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-&gt;</span> DocumentInfo:</span>
<span id="cb11-4">    model_with_structure <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> model.with_structured_output(DocumentInfo)</span>
<span id="cb11-5">    response <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> model_with_structure.invoke(document)</span>
<span id="cb11-6">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> response</span>
<span id="cb11-7"></span>
<span id="cb11-8"></span>
<span id="cb11-9">document_text <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> dedent(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb11-10"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">This is a document about cats. Very important document. It explain how cats will take over the world in 20230.</span></span>
<span id="cb11-11"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb11-12">)</span>
<span id="cb11-13">document_info <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> get_document_info(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"I'm a document about a cat"</span>)</span>
<span id="cb11-14"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(document_info)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>category='pets' summary='A document about a cat.'</code></pre>
</div>
</div>
<p>After running this code, you’ll get a structured output with the category and summary of the document that you can then use in further steps of your workflow.</p>
<p>Depending on the provider, you’ll have different options to get structured outputs. OpenAI offers three different methods:</p>
<ul>
<li><code>function_calling</code>: This uses the tool calling mechanism to get the structured output.</li>
<li><code>json_mode</code>: This method ensures you get a valid JSON object, but it’s not clear how it works under the hood.</li>
<li><code>json_schema</code>: This is default method in LangChain. It ensures that the output is a valid JSON object and that it matches the schema you provide using <a href="https://openai.com/index/introducing-structured-outputs-in-the-api/">constrained decoding</a>.</li>
</ul>
<p><a href="https://ai.google.dev/gemini-api/docs/structured-output">Gemini</a> and <a href="https://docs.anthropic.com/en/docs/test-and-evaluate/strengthen-guardrails/increase-consistency">Anthropic</a> provide their own methods to get structured outputs.</p>
<p>One thing to keep in mind is that structured outputs can impact performance. I’ve <a href="https://dylancastillo.co/posts/llm-pydantic-order-matters.html">written</a> <a href="https://dylancastillo.co/posts/say-what-you-mean-sometimes.html">multiple</a> <a href="https://dylancastillo.co/posts/gemini-structured-outputs.html">posts</a> about this topic, so I won’t go into detail here.</p>
</section>
</section>
<section id="conclusion" class="level2">
<h2 class="anchored" data-anchor-id="conclusion">Conclusion</h2>
<p>Function calling and structured outputs are powerful tools that help build more capable AI systems. They’re also the foundation of agents.</p>
<p>Function calling is a way to provide LLMs with tools to use. It lets you go from building a chatbot that can only talk to building an AI assistant that can actually interact with the world. It opens up a world of possibilities, from connecting to databases, calling APIs, or automating workflows.</p>
<p>Structured outputs are just as important. They’re critical to integrating LLMs into existing systems. Instead of struggling with parsing free-form text, you get clean, predictable data structures that you can use in your code.</p>
<p>The examples in this tutorial should give you a sense of how to use these methods. But as usual, the real learning happens when you start applying these concepts to your own problems. Pick a task you’re working on, see if any of these methods can help you, and give it a try.</p>
<p>If you have any questions or comments, let me know in the comments below.</p>


</section>


<div id="quarto-appendix" class="default"><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Footnotes</h2>

<ol>
<li id="fn1"><p><a href="https://arxiv.org/abs/2404.07362">“We Need Structured Output”: Towards User-centered Constraints on LLM Output. MX Liu et al.&nbsp;2024</a>↩︎</p></li>
<li id="fn2"><p>Please don’t judge me. Companies do all sorts of weird things these days.↩︎</p></li>
</ol>
</section><section class="quarto-appendix-contents" id="quarto-citation"><h2 class="anchored quarto-appendix-heading">Citation</h2><div><div class="quarto-appendix-secondary-label">BibTeX citation:</div><pre class="sourceCode code-with-copy quarto-appendix-bibtex"><code class="sourceCode bibtex">@online{castillo2025,
  author = {Castillo, Dylan},
  title = {Function Calling and Structured Outputs in {LLMs} with
    {LangChain} and {OpenAI}},
  date = {2025-07-01},
  url = {https://dylancastillo.co/posts/function-calling-structured-outputs.html},
  langid = {en}
}
</code></pre><div class="quarto-appendix-secondary-label">For attribution, please cite this work as:</div><div id="ref-castillo2025" class="csl-entry quarto-appendix-citeas">
Castillo, Dylan. 2025. <span>“Function Calling and Structured Outputs in
LLMs with LangChain and OpenAI.”</span> July 1. <a href="https://dylancastillo.co/posts/function-calling-structured-outputs.html">https://dylancastillo.co/posts/function-calling-structured-outputs.html</a>.
</div></div></section></div> ]]></description>
  <category>llm</category>
  <category>function-calling</category>
  <category>structured-outputs</category>
  <category>openai</category>
  <guid>https://dylancastillo.co/posts/function-calling-structured-outputs.html</guid>
  <pubDate>Tue, 01 Jul 2025 00:00:00 GMT</pubDate>
  <media:content url="https://dylancastillo.co/posts/images/cards/function-calling-structured-outputs.png" medium="image" type="image/png" height="76" width="144"/>
</item>
</channel>
</rss>
