<?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>Paul Simmering&#39;s Blog</title>
<link>https://simmering.dev/blog.html</link>
<atom:link href="https://simmering.dev/blog.xml" rel="self" type="application/rss+xml"/>
<description>Agentic AI, natural language processing, and data engineering.</description>
<language>en</language>
<generator>quarto-1.8.26</generator>
<lastBuildDate>Fri, 26 Jun 2026 22:00:00 GMT</lastBuildDate>
<item>
  <title>Every Trick to Save Token Costs</title>
  <dc:creator>Paul Simmering</dc:creator>
  <link>https://simmering.dev/blog/save-token-costs/</link>
  <description><![CDATA[ 






<p>Not all of us are token billionaires. Costs for frontier model inference has been going up. For example, GPT-5.5 costs twice as much as GPT-5.4. This is a guide with 17 ways of reducing token costs. Each measure is accompanied by estimated savings and the trade-offs necessary to make it work. As you’ll see, token costs can be reduced by 90% or more or be entirely free! The techniques are grouped into categories and ordered from simple to advanced within each group. Feel free to skip ahead using the table of contents.</p>
<section id="pick-cheaper-inference" class="level2">
<h2 class="anchored" data-anchor-id="pick-cheaper-inference">Pick cheaper inference</h2>
<section id="use-the-cheapest-model-that-does-the-job" class="level3">
<h3 class="anchored" data-anchor-id="use-the-cheapest-model-that-does-the-job">Use the cheapest model that does the job</h3>
<p>Savings: ~96%, when comparing GPT-5.4 nano to GPT-5.5 ($0.20/$1.25 vs $5.00/$30.00 per MTok (million tokens)).</p>
<p>For well-defined tasks, small models can be just as good as larger models. Don’t use Opus when Haiku can do the job! Common tasks for small models include summarization, classification, translation and being sub-agents for larger models. If you’re building an agentic workflow, you could use a different sized model for each step depending on complexity. For a chatbot, you could use a model router that picks an appropriate model for each request. Besides the price, some models are more token-efficient than others. They can give equally intelligent answers using fewer tokens. Artificial analysis combines price and token efficiency into a <a href="https://artificialanalysis.ai/#price-and-cost">cost per intelligence</a> metric.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://simmering.dev/blog/save-token-costs/cost_per_intelligence.png" class="img-fluid figure-img"></p>
<figcaption>Cost per Intelligence, source: Artifical Analysis</figcaption>
</figure>
</div>
<p>Trade-off: could underestimate the difficulty of the task and pick a model that isn’t smart enough, which then needs multiple attempts or downright fails.</p>
</section>
<section id="use-the-cheapest-provider-and-region" class="level3">
<h3 class="anchored" data-anchor-id="use-the-cheapest-provider-and-region">Use the cheapest provider and region</h3>
<p>Savings: up to 60% less. For example, Kimi K2.6 costs $1.30 per Mtok (blended cost at 7:2:1 cache-input-output) on Azure compared to $0.60 on Novita.</p>
<p>Check if a given model is available from other API providers or in a cheaper cloud region. This matters most for open weights models. Closed models tend to have identical prices across providers.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://simmering.dev/blog/save-token-costs/kimi_prices.png" class="img-fluid figure-img"></p>
<figcaption>Kimi K2.6 prices by API provide, source: Artifical Analysis</figcaption>
</figure>
</div>
<p>Trade-off: could be slower, data privacy concerns, cheaper providers may quantize more aggressively.</p>
</section>
<section id="use-batch-inference" class="level3">
<h3 class="anchored" data-anchor-id="use-batch-inference">Use batch inference</h3>
<p>Savings: 50% of the request.</p>
<p>If you don’t need the results immediately, you can send requests in a batch and poll for the results later. OpenAI, Anthropic, Google (Gemini), Azure OpenAI, AWS Bedrock, and Mistral all offer this: typically 50% off input and output, with results within about 24 hours. You upload a JSONL file (or equivalent), wait for the job to finish, then download the responses.</p>
<p>Trade-off: need to wait for completion, which rules out any real-time use cases. Async also adds complexity.</p>
</section>
<section id="use-a-flat-rate-subscription" class="level3">
<h3 class="anchored" data-anchor-id="use-a-flat-rate-subscription">Use a flat rate subscription</h3>
<p>Savings: Up to 95% vs API list price.</p>
<p>Some providers offer agents (model + tool loop) at a flat rate subscription, rather than models at API rates. Claude Pro and ChatGPT Plus ($20/mo) bundle far more inference than the fee suggests. In a <a href="https://www.techspot.com/news/112759-openai-anthropic-cant-afford-have-everyone-use-ai.html">June 2026 experiment</a>, SemiAnalysis ran long agentic coding sessions on every paid tier until weekly limits bound, then priced the consumed tokens at API rates. A fully used Claude Pro plan equated to roughly $400 of API spend (20× the subscription fee). ChatGPT Plus reached about $700 (35×).</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://simmering.dev/blog/save-token-costs/semi_analysis_plan_comparison.png" class="img-fluid figure-img"></p>
<figcaption>Plan and API spend comparison, source: <a href="https://x.com/SemiAnalysis_/status/2064815044085318040">SemiAnalysis on X</a></figcaption>
</figure>
</div>
<p>If your use case supports it, use a flat rate subscription instead of an API-based model. Keep in mind that the providers intend them for single-user, human-in-the-loop use cases.</p>
<p>Trade-off: Quotas can change, not meant for automated or multi-user workloads, Anthropic has blocked third-party platforms like OpenClaw from routing subscription auth.</p>
</section>
</section>
<section id="send-and-receive-fewer-tokens" class="level2">
<h2 class="anchored" data-anchor-id="send-and-receive-fewer-tokens">Send and receive fewer tokens</h2>
<section id="request-short-responses" class="level3">
<h3 class="anchored" data-anchor-id="request-short-responses">Request short responses</h3>
<p>Savings: Most of the output tokens.</p>
<p>Save output tokens by asking the model to keep it short. Depending on provider and model the <code>verbosity</code> and <code>max_tokens</code> parameters can also be used to control the length of the response. If you have a long system prompt, consider taking situational instructions from it and packaging as a skill instead. Then the model will only load the full prompt when it’s relevant.</p>
<p>Trade-off: could lose detail.</p>
</section>
<section id="use-low-or-no-reasoning" class="level3">
<h3 class="anchored" data-anchor-id="use-low-or-no-reasoning">Use low or no reasoning</h3>
<p>Savings: Significant number of output tokens.</p>
<p>Reasoning tokens are billed as output tokens, you just don’t see them in the response. If your task isn’t heavy on logical thinking, turn off reasoning or set it to low.</p>
<p>Trade-off: less intelligence.</p>
</section>
<section id="use-a-token-efficient-language" class="level3">
<h3 class="anchored" data-anchor-id="use-a-token-efficient-language">Use a token-efficient language</h3>
<p>Savings: ~25–55% of input and output tokens when rewriting Japanese prompts in English on current Western models.</p>
<p>LLM billing is per token, and tokenizers are not language-neutral. OpenAI, Anthropic, and most API models use byte-pair encoding vocabularies trained mainly on English. They merge common words into single tokens. Japanese kanji, hiragana, and katakana mostly stay separate. The same meaning therefore costs more in Japanese than in English. Let’s look at an example with OpenAI’s <code>o200k_base</code> tokenizer (as used by GPT-5.x).</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://simmering.dev/blog/save-token-costs/token-viz.png" class="img-fluid figure-img"></p>
<figcaption>Same sentence tokenized in English (9 tokens) and Japanese (15 tokens). Matching colors show the same phrase; each box is one billed token.</figcaption>
</figure>
</div>
<p>Matching colors link equivalent phrases: e.g.&nbsp;English <em>bullet</em> + <em>points</em> (two tokens) map to six Japanese tokens, including one dashed box. That box is a byte-level split: the tokenizer cut the kanji 箇 across two tokens, and both are billed even though the first holds no complete character. According to a benchmark by <a href="https://masonailab.com/en/insights/token-efficiency/">Mason AI Lab</a>, across six task types, Japanese averages ~1.7× English; Chinese ~1.3×. Chinese-native models (Qwen, DeepSeek) close most of the gap for Chinese.</p>
<p>So when you have the choice, write your instruction in English. The highest payoff is for repeated system prompts.</p>
<p>Trade-off: not possible if your task requires a specific language, translating a prompt could lose nuance.</p>
</section>
<section id="use-fewer-and-leaner-tools" class="level3">
<h3 class="anchored" data-anchor-id="use-fewer-and-leaner-tools">Use fewer and leaner tools</h3>
<p>Savings: mostly input on tool schemas, plus output when you trim what tools return.</p>
<p>Each tool adds a name, description, and JSON Schema to the prompt. With MCP, that overhead lands before the agent does any work and is rebilled every turn. The official GitHub MCP server alone costs ~17,600 input tokens (<a href="https://github.com/atlassian-labs/mcp-compressor#why">mcp-compressor</a>), so a one-word message can already bill 17,601 tokens.</p>
<p>Drop MCP servers you are not using, load full schemas only when needed (<a href="https://www.anthropic.com/engineering/advanced-tool-use">Anthropic tool search</a>, <a href="https://github.com/atlassian-labs/mcp-compressor">mcp-compressor</a>), or replace large tool lists with skills (~800 tokens when loaded versus tens of thousands for a big MCP server). Where you can, prefer CLI over MCP: a <code>gh</code> command is hundreds of tokens, not tens of thousands of schema. On output, trim tool descriptions and unused parameters. The compact JSON alternative <a href="https://toonformat.dev/">TOON</a> can also shrink JSON tool results by roughly 20–60%. For more detail, see my <a href="../../blog/connect-agent/">Agent Connector Comparison</a> and <a href="https://axi.md/">AXI</a>, a set of design principles for efficient CLIs for agents.</p>
<p>Trade-off: the model may miss a tool it needs, and CLI or skills need shell access.</p>
</section>
<section id="shrink-documents-that-you-send" class="level3">
<h3 class="anchored" data-anchor-id="shrink-documents-that-you-send">Shrink documents that you send</h3>
<p>Savings: 50–90% of input tokens.</p>
<p>Instead of sending a large document as a whole, run it through a RAG pipeline (chunk, embed, retrieve relevant chunks) and send only the relevant chunks. Another option is to equip an agent with a bash terminal and let it slice the document with <code>grep</code>, <code>awk</code>, <code>sed</code>, or <code>tail</code> to find the relevant parts.</p>
<p>Trade-off: could lose information, shrinking adds complexity.</p>
</section>
<section id="use-images-sparingly" class="level3">
<h3 class="anchored" data-anchor-id="use-images-sparingly">Use images sparingly</h3>
<p>Savings: 80% or more of input tokens.</p>
<p>Images added to the prompt add significant input token costs. A single 1024×1024 image costs ~765 input tokens on GPT with <code>detail: "high"</code>, and it is rebilled on every turn the image stays in context. There are many ways to save on images:</p>
<ul>
<li>Use <code>detail: "low"</code> instead of <code>detail: "high"</code> on the OpenAI API. On low, the cost is a fixed 85 tokens regardless of size. On high it’s 85 + 170 x (number of 512x512 tiles). That amounts to a 9x reduction on a 1024x1024 image. Avoid this for OCR tasks because letters could become unreadable.</li>
<li>Downscale images before uploading them to the provider. Check the provider’s documentation first because they have their own downsizing logic which could conflict with your custom logic.</li>
<li>Strip images from chat history after the first turn if it’s unlikely they’re important again at the next turn.</li>
<li>Use a text representation instead. If not available, run the image through a vision language model to extract text and other information and save that representation. Reuse it every time you’d normally send the image.</li>
<li>If the image was originally rendered from code, send the code directly instead of the image. For instance, send the DOM or accessibility tree of a website, or the code for a Mermaid diagram.</li>
</ul>
<p>Trade-off: OCR, DOM extraction, and cropping add pipeline complexity. Aggressive downscaling or low detail can miss small text and UI elements.</p>
</section>
</section>
<section id="cache-and-batch-smarter" class="level2">
<h2 class="anchored" data-anchor-id="cache-and-batch-smarter">Cache and batch smarter</h2>
<section id="maximize-input-cache-hits" class="level3">
<h3 class="anchored" data-anchor-id="maximize-input-cache-hits">Maximize input cache hits</h3>
<p>Savings: 90% of repeated input tokens.</p>
<p>Most providers offer substantial rebates on repeated input tokens. The input must match exactly and be at least a minimum length (1,024 tokens on OpenAI). The repeat request must also arrive before the cache expires. On OpenAI, extended caching retains prefixes for up to 24 hours (<code>prompt_cache_retention: "24h"</code>; the only mode on GPT-5.5). On Claude, the default TTL is 5 minutes, with an optional 1-hour TTL via <code>cache_control</code>.</p>
<p>If the time is up, your context window is full, or your provider doesn’t offer good rebates for cached inputs, consider compaction instead. Let the model summarize the relevant parts of the chat history and start fresh with that as the new context. Some providers also reprice the whole request past a context threshold (e.g.&nbsp;GPT-5.5 above 272K input jumps from $5/$30 to $10/$45 MTok), so try to stay below it.</p>
<p>Trade-off: on Claude, time pressure to answer within the cache window.</p>
</section>
<section id="minimize-chat-turns" class="level3">
<h3 class="anchored" data-anchor-id="minimize-chat-turns">Minimize chat turns</h3>
<p>Savings: ~10% with input cache and much higher without.</p>
<p>Every chat turn (user asks, model answers) requires sending the entire existing chat history to the model again. It’s cheaper to ask multiple questions in one turn than one by one.</p>
<p>Let’s go through an example: someone asks three questions (Q1, Q2, Q3) about a document (context) and receives three answers (A1, A2, A3).</p>
<p><em>One question per turn</em></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">block-beta
    columns 7
    block:call1["API call 1"]:7
        columns 7
        c1["Context"] q1a["Q1"] a1a["A1"] space:4
    end
    block:call2["API call 2"]:7
        columns 7
        c2["Context"] q1b["Q1"] a1b["A1"] q2a["Q2"] a2a["A2"] space:2
    end
    block:call3["API call 3"]:7
        columns 7
        c3["Context"] q1c["Q1"] a1c["A1"] q2b["Q2"] a2b["A2"] q3a["Q3"] a3a["A3"]
    end

    classDef context fill:#e9ecef,stroke:#6c757d,stroke-width:2px
    classDef question fill:#cfe2ff,stroke:#0d6efd,stroke-width:2px
    classDef answer fill:#e2d9f3,stroke:#6f42c1,stroke-width:2px
    classDef cachedContext fill:#e9ecef,stroke:#198754,stroke-width:2px,stroke-dasharray:8 4
    classDef cachedQuestion fill:#cfe2ff,stroke:#198754,stroke-width:2px,stroke-dasharray:8 4
    classDef cachedAnswer fill:#e2d9f3,stroke:#198754,stroke-width:2px,stroke-dasharray:8 4

    class c1 context
    class q1a question
    class a1a answer
    class c2 cachedContext
    class q1b cachedQuestion
    class a1b cachedAnswer
    class q2a question
    class a2a answer
    class c3 cachedContext
    class q1c cachedQuestion
    class a1c cachedAnswer
    class q2b cachedQuestion
    class a2b cachedAnswer
    class q3a question
    class a3a answer
</pre>
</div>
<p></p></figure><p></p>
</div>
</div>
</div>
<p><em>All questions in one turn</em></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 TB
    subgraph call1["API call 1"]
        direction LR
        c1["Context"]:::context
        q1["Q1"]:::question
        q2["Q2"]:::question
        q3["Q3"]:::question
        a1["A1"]:::answer
        a2["A2"]:::answer
        a3["A3"]:::answer
        c1 --&gt; q1 --&gt; q2 --&gt; q3 --&gt; a1 --&gt; a2 --&gt; a3
    end

    classDef context fill:#e9ecef,stroke:#6c757d,stroke-width:2px
    classDef question fill:#cfe2ff,stroke:#0d6efd,stroke-width:2px
    classDef answer fill:#e2d9f3,stroke:#6f42c1,stroke-width:2px
    classDef cachedContext fill:#e9ecef,stroke:#198754,stroke-width:2px,stroke-dasharray:8 4
    classDef cachedQuestion fill:#cfe2ff,stroke:#198754,stroke-width:2px,stroke-dasharray:8 4
    classDef cachedAnswer fill:#e2d9f3,stroke:#198754,stroke-width:2px,stroke-dasharray:8 4
</pre>
</div>
<p></p></figure><p></p>
</div>
</div>
</div>
<p>Each box is a separate part of the chat sent on that API call. Dashed green border = cached input, which is rebated on most providers. Suppose the chat starts with a 5,000-token context document, each question is 50 tokens, and each answer is 300 tokens. Turns 2 and 3 get cache hits on the shared prefix. At <a href="https://platform.openai.com/docs/pricing">GPT-5.5 standard API pricing</a> ($5.00/M fresh input, $0.50/M cached input, $30.00/M output):</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Turn</th>
<th style="text-align: right;">Cached input</th>
<th style="text-align: right;">Fresh input</th>
<th style="text-align: right;">Output</th>
<th style="text-align: right;">Cost</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>1</td>
<td style="text-align: right;">0</td>
<td style="text-align: right;">5,050</td>
<td style="text-align: right;">300</td>
<td style="text-align: right;">$0.034</td>
</tr>
<tr class="even">
<td>2</td>
<td style="text-align: right;">5,350</td>
<td style="text-align: right;">50</td>
<td style="text-align: right;">300</td>
<td style="text-align: right;">$0.012</td>
</tr>
<tr class="odd">
<td>3</td>
<td style="text-align: right;">5,700</td>
<td style="text-align: right;">50</td>
<td style="text-align: right;">300</td>
<td style="text-align: right;">$0.012</td>
</tr>
<tr class="even">
<td><strong>Total</strong></td>
<td style="text-align: right;"></td>
<td style="text-align: right;"></td>
<td style="text-align: right;"></td>
<td style="text-align: right;"><strong>$0.058</strong></td>
</tr>
</tbody>
</table>
<p>All questions in one turn:</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th style="text-align: right;">Cached input</th>
<th style="text-align: right;">Fresh input</th>
<th style="text-align: right;">Output</th>
<th style="text-align: right;">Cost</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td style="text-align: right;">0</td>
<td style="text-align: right;">5,150</td>
<td style="text-align: right;">900</td>
<td style="text-align: right;"><strong>$0.053</strong></td>
</tr>
</tbody>
</table>
<p>Combining the three questions saves <strong>9%</strong> in this example ($0.058 → $0.053). Fresh input is the same either way; the gap is rebilled cached history on turns 2 and 3. Without input caching, the same setup would save about <strong>51%</strong>, because repeated history is billed at the full input rate.</p>
<p>Trade-off: need to know your questions ahead of time.</p>
</section>
<section id="cache-whole-requests" class="level3">
<h3 class="anchored" data-anchor-id="cache-whole-requests">Cache whole requests</h3>
<p>Savings: 100% of repeat requests.</p>
<p>If you keep sending the same request over and over, add a layer to cache the request and response. I found myself with this issue when using an LLM for data labeling and re-ran it during experiments. This works best for exact repeated requests. Alternatively, you could fetch a cached answer based on a minimum cosine similarity of the request and the cached request.</p>
<p>Trade-off: maintaining the cache, potential for irrelevant responses when using similarity search.</p>
</section>
</section>
<section id="redesign-the-system" class="level2">
<h2 class="anchored" data-anchor-id="redesign-the-system">Redesign the system</h2>
<section id="make-workflow-steps-deterministic" class="level3">
<h3 class="anchored" data-anchor-id="make-workflow-steps-deterministic">Make workflow steps deterministic</h3>
<p>Savings: 100% of the request.</p>
<p>If you have an agentic workflow, see if you can identify steps that are always the same in each run. These don’t have to be controlled by an LLM. Instead, turn them into a deterministic function, which is cheaper, faster and more reliable. Prototype with one LLM call when the problem shape is unknown. Productionize by moving anything that became repeatable (validation, routing, extraction on standard layouts) out of the model and leaving the LLM for the shrinking tail. If the task requires fuzzy intelligence, but not a whole LLM, see the next section.</p>
<p>Take invoice extraction: vendor PDFs have wildly different layouts, so v1 is often one vision call per document. At volume that gets expensive and non-deterministic. Production peels off structured cases and reserves the model for the tail.</p>
<p><em>One vision call per PDF: LLM on every run</em></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
    A[Invoice PDF] --&gt; B[LLM vision]
    B --&gt; C[JSON fields]

    classDef llm fill:#e2d9f3,stroke:#6f42c1,stroke-width:2px
    class B llm
</pre>
</div>
<p></p></figure><p></p>
</div>
</div>
</div>
<p><em>Parse e-invoices, Doc AI on the rest, LLM on low-confidence fields: LLM only on exceptions</em></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
    A[Invoice PDF] --&gt; B{E-invoice XML?}
    B --&gt;|Yes| C[Parse fields]
    B --&gt;|No| D[Doc AI]
    D --&gt; E{Low confidence?}
    E --&gt;|Yes| F[LLM re-read]
    E --&gt;|No| G[Validate]
    C --&gt; G
    F --&gt; G
    G --&gt; H[JSON fields]

    classDef deterministic fill:#e9ecef,stroke:#6c757d,stroke-width:2px
    classDef nlp fill:#fff3cd,stroke:#ffc107,stroke-width:2px
    classDef llm fill:#e2d9f3,stroke:#6f42c1,stroke-width:2px

    class B,C,G deterministic
    class D nlp
    class F llm
</pre>
</div>
<p></p></figure><p></p>
</div>
</div>
</div>
<p>Trade-off: loss of flexibility and need to reorganize the workflow.</p>
</section>
<section id="switch-to-classic-nlp-models" class="level3">
<h3 class="anchored" data-anchor-id="switch-to-classic-nlp-models">Switch to classic NLP models</h3>
<p>Savings: ~90% less compute when self-hosting.</p>
<p>If you’re using an LLM for classic NLP tasks, such as classification, sentiment analysis, named entity recognition or translation, consider a dedicated NLP model instead. Encoder models like <a href="https://huggingface.co/answerdotai/ModernBERT-base">ModernBERT</a> (149M parameters) run a single forward pass over the input, without added instructions. A 7B LLM is ~50× larger and requires additional prompt tokens as input. That translates to at least a 90% compute savings if you self-host both models. Savings on hosted variants is highly dependent on the provider. To learn more about mixing LLMs, classic NLP models and rule-based systems, read <a href="https://explosion.ai/blog/atomic-nlp">Atomic NLP</a> by Explosion AI.</p>
<p>Trade-off: loss of flexibility, may need to fine-tune a model for your task, inference is faster but platform support is more limited.</p>
</section>
</section>
<section id="get-free-or-subsidized-tokens" class="level2">
<h2 class="anchored" data-anchor-id="get-free-or-subsidized-tokens">Get free or subsidized tokens</h2>
<section id="share-data-to-get-free-tokens" class="level3">
<h3 class="anchored" data-anchor-id="share-data-to-get-free-tokens">Share data to get free tokens</h3>
<p>Savings: up to 11 million tokens per day.</p>
<p>OpenAI grants free daily tokens if you share inputs and outputs with them (1M/day on frontier models, 10M/day on mini models; input + output combined, pooled across each group). <a href="https://openrouter.ai/docs/guides/privacy/data-collection">OpenRouter</a> offers a 1% discount on billed usage for a similar opt-in.</p>
<p>Trade-off: data privacy concerns.</p>
</section>
<section id="apply-for-token-grants" class="level3">
<h3 class="anchored" data-anchor-id="apply-for-token-grants">Apply for token grants</h3>
<p>Savings: 100% of inference cost while credits last (amount varies by program).</p>
<p>Several providers offer LLM API or cloud credits (that can be used for LLM inference) through dedicated programs. Awards are competitive and eligibility varies.</p>
<ul>
<li>Researchers: <a href="https://openai.com/form/researcher-access-program/">OpenAI</a>, <a href="https://support.claude.com/en/articles/11199177-anthropic-s-ai-for-science-program">Anthropic</a>, <a href="https://edu.google.com/programs/credits/research/">Google</a>, <a href="https://digital-strategy.ec.europa.eu/en/policies/genai4eu">GenAI4EU (EU)</a>, <a href="https://nairrpilot.org/">NAIRR Pilot (US)</a>.</li>
<li>Nonprofits: <a href="https://claude.com/solutions/nonprofits">Anthropic</a>, <a href="https://openai.com/index/introducing-openai-for-nonprofits/">OpenAI</a>, <a href="https://www.microsoft.com/en-us/nonprofits/azure">Microsoft</a>, <a href="https://aws.amazon.com/government-education/nonprofits/">AWS</a>.</li>
<li>Startups: <a href="https://claude.com/programs/startups">Anthropic</a>, <a href="https://cloud.google.com/startup/ai">Google</a>, <a href="https://www.microsoft.com/en/startups/">Microsoft</a>, <a href="https://aws.amazon.com/startups/credits/">AWS</a>, <a href="https://www.together.ai/startup-accelerator">Together AI</a>, <a href="https://www.nvidia.com/en-us/startups/">NVIDIA</a>, <a href="https://docs.mistral.ai/community/ambassadors">Mistral (EU)</a>.</li>
</ul>
<p>Trade-off: application required, not guaranteed, capped.</p>


</section>
</section>

 ]]></description>
  <category>Machine Learning</category>
  <category>Economics</category>
  <guid>https://simmering.dev/blog/save-token-costs/</guid>
  <pubDate>Fri, 26 Jun 2026 22:00:00 GMT</pubDate>
  <media:content url="https://simmering.dev/blog/save-token-costs/image.webp" medium="image" type="image/webp"/>
</item>
<item>
  <title>Agent Connector Comparison: Tools, MCP, A2A, CLI, Skills, Code, and Computer Use</title>
  <dc:creator>Paul Simmering</dc:creator>
  <link>https://simmering.dev/blog/connect-agent/</link>
  <description><![CDATA[ 






<p><img src="https://simmering.dev/blog/connect-agent/connectors_wide.jpg" class="img-fluid" style="width:100.0%"></p>
<p>If the model is the brain of the agent, connections to APIs, databases and apps are the arms. There are many ways to connect the arms to the agent: tools, Model Context Protocol (MCP), the Agent to Agent (A2A) protocol, command line interfaces (CLI), skills, a code interpreter and computer use (mouse and keyboard input).</p>
<section id="characteristics" class="level2">
<h2 class="anchored" data-anchor-id="characteristics">Characteristics</h2>
<p>In this article, I’ll compare these connectors based on the following characteristics:</p>
<ol type="1">
<li><strong>Ease of authoring</strong>: How much work is required to create, package, deploy and maintain a useful connector.</li>
<li><strong>Expressiveness</strong>: The ability to run complex workflows and chain multiple tools and other agents. An expressive connector minimizes the number of model inference calls for a given task. For example, a model that needs to run five tools could either do five separate inference calls, or write a script that runs them in sequence.</li>
<li><strong>Token efficiency</strong>: Minimize the number of input and output tokens involved in understanding and using the connector.</li>
<li><strong>Scalability</strong>: How many connectors can be added without significant increases in error rate.</li>
<li><strong>Portability</strong>: Whether it’s possible to use the same connector with different agents and harnesses.</li>
<li><strong>Ecosystem</strong>: The availability of integrations, libraries, examples, and support.</li>
<li><strong>Observability</strong>: Quality of tooling to trace, debug and audit how the connector is used.</li>
<li><strong>Security</strong>: Permissions needed to run the connector, whether it supports asking for user approval before taking actions, and how authentication with other systems works.</li>
</ol>
</section>
<section id="connectors" class="level2">
<h2 class="anchored" data-anchor-id="connectors">Connectors</h2>
<p>In all cases an agent harness (in its simplest form a loop prompting the LLM and executing its response) sits between the LLM and the outside world. The diagrams below leave the harness implicit and focus on what’s distinctive to each connector.</p>
<section id="tools" class="level3">
<h3 class="anchored" data-anchor-id="tools">Tools</h3>
<p><em>DIY from start to finish</em></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
    Prompt[Prompt including tool definitions] --&gt; LLM
    LLM --&gt;|tool call| Tool[Local function or provider API]
    Tool --&gt;|result| LLM
</pre>
</div>
<p></p></figure><p></p>
</div>
</div>
</div>
<p>Tools are function definitions and configurations that are passed directly to the model along with the prompt. The model can output a structured response with the function’s arguments. Now, there are two ways to execute the tool: either the client passes the arguments to a local function, or the API provider executes it inside the API. An example of the latter is OpenAI’s web search tool in the Responses API.</p>
<p>Tools, especially when executed by the client, are the most customizable approach. You have full control over the definitions and executions. Authoring a tool isn’t hard: you write a function, e.g.&nbsp;in Python or TypeScript, and pass it to a <code>tools</code> parameter in a request to the model. The more difficult part is making sure the agent understands your tool, which is a context engineering problem.</p>
<p>If you’re using more than a few tools, <em>tool search</em> or other logic becomes necessary to avoid bloating the context window. An example of this is <a href="https://pydantic.dev/docs/ai/tools-toolsets/tools-advanced/#tool-search">Pydantic AI’s tool search</a>. You can also write your own logic to control which tools are available at each chat turn. This can act as a system to hide irrelevant tools to save tokens or it can encode state: for example, a data analysis agent might first see tools to list and filter datasets, pick a dataset and only then see tools to analyze it. This logic can be powerful, but it lives inside of your custom harness rather than a reusable connector. Another downside is that hiding tools runs the risk of the agent missing opportunities to use them.</p>
<p>The biggest con of tools is that they are hard to reuse. Let’s say I write a tool to work with an agent that I have built with Pydantic AI. I can’t just take that tool and let a Cursor agent use it. MCP, the next connector, solves this by turning tools into shared servers.</p>
<p><strong>Main use case</strong>: custom tools with maximum control.</p>
</section>
<section id="mcp-model-context-protocol" class="level3">
<h3 class="anchored" data-anchor-id="mcp-model-context-protocol">MCP: Model Context Protocol</h3>
<p><em>The USB-C port for agents</em> – Anthropic</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
    Config[mcp.json] --&gt; LLM
    LLM &lt;--&gt;|JSON-RPC| Server[MCP server]
    Server &lt;--&gt; Backend[Backend API]
</pre>
</div>
<p></p></figure><p></p>
</div>
</div>
</div>
<p>MCP servers are essentially shared versions of tools: instead of defining functions inside one harness, you expose them through a reusable server. This server can run locally or remotely. In addition to tools, MCP supports prompts and resources (data sources), but those are rarely used. MCP servers can be shared privately within organizations and publicly at <a href="https://registry.modelcontextprotocol.io">https://registry.modelcontextprotocol.io</a> and other MCP registries.</p>
<p>Almost every major SaaS product has an MCP server, letting agents connect without writing custom code. Not all of those are well designed; many are simple wrappers around existing APIs. In particular, they are not token-efficient in their descriptions and require multiple calls to get something done, each of which is an inference call to the LLM. As a consumer of the MCP server, you can’t change the tool names or descriptions.</p>
<p>Security is decent for trusted, least-privilege servers, but approval gates are mainly a client responsibility and tool descriptions, schemas and outputs are prompt-injection surfaces.</p>
<p>Adding too many MCP servers or servers with too many tools bloats the context window. As an example, the official GitHub MCP server exposes 94 tools consuming 17,600 tokens (<a href="https://github.com/atlassian-labs/mcp-compressor/tree/v0.13.0#why">source</a>). So just writing “hi” to an agent using that server will cost 17,601 input tokens, plus the cost of the response. To avoid this, Anthropic came up with <a href="https://www.anthropic.com/engineering/advanced-tool-use">tool search</a>, which exposes a tool search tool instead of the full tool set. So when you write “hi”, the agent doesn’t even have to load the GitHub MCP tools, and if you ask it “why is CI failing” it can use search to find <code>list_workflow_runs</code> and <code>get_workflow_run</code> specifically rather than loading all 94 tools.</p>
<p>It’s straightforward to create a local MCP server and doesn’t take much more than creating a tool. Sharing it adds significantly more work for packaging, authentication, rate limiting, deployment and ongoing observation.</p>
<p><strong>Main use case</strong>: sharing tools across agents and connecting to remote services.</p>
</section>
<section id="a2a-agent-to-agent-protocol" class="level3">
<h3 class="anchored" data-anchor-id="a2a-agent-to-agent-protocol">A2A: Agent to Agent Protocol</h3>
<p><em>Agent collaboration in full fidelity</em></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">sequenceDiagram
    participant AgentA as Agent A
    participant AgentB as Agent B
    participant Own as Own tools / skills / MCP

    AgentA-&gt;&gt;AgentB: discover agent card
    AgentA-&gt;&gt;AgentB: task
    AgentB-&gt;&gt;Own: use tools / skills / MCP
    Own--&gt;&gt;AgentB: result
    AgentB--&gt;&gt;AgentA: response
</pre>
</div>
<p></p></figure><p></p>
</div>
</div>
</div>
<p>A2A connects agents with each other. Each agent has an agent card that describes its capabilities. Another agent discovers the card and can delegate tasks to the agent. The executing agent uses its own tools, skills, MCP servers or any other capabilities and reports back to the delegating agent. The difference to packaging an agent as a tool is that A2A treats the executing agent as a stateful remote collaborator. This unlocks multi-turn interaction (e.g.&nbsp;asking for more information) and long running tasks with task IDs and lifecycle states such as <code>working</code>, <code>completed</code> or <code>failed</code>. How the executing agent uses its own capabilities is not shared with the delegating agent. Internal logic is not shared. The agents can run in different environments, using different LLMs and harnesses and be controlled by different organizations.</p>
<p>A2A is not a replacement for MCP, it adds a new dimension above it. It’s a powerful solution that promises maximum expressiveness, token efficiency and scalability from the perspective of the delegating agent. But A2A hasn’t taken off like MCP. One explanation is that exposing an agent is more involved than exposing a tool: agents are not deterministic, continuously consume tokens, are stateful, and can’t be versioned as easily as an MCP server that’s a front to an API. In addition the frontend support is still limited. Most common agent harnesses and chat apps people actually use, including ChatGPT, Claude, Claude Code, Codex and Cursor, don’t expose A2A endpoints. Others, like Microsoft Copilot Studio and LangDock support it. In contrast, all of them support MCP.</p>
<p><strong>Main use case</strong>: multi-turn interaction between agents</p>
</section>
<section id="cli-command-line-interface" class="level3">
<h3 class="anchored" data-anchor-id="cli-command-line-interface">CLI: Command Line Interface</h3>
<p><em>The best agent architecture is already sitting in your terminal</em> – Ashka Stephen</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
    User --&gt;|"use the XYZ CLI"| LLM
    LLM --&gt;|bash command| Shell
    Shell --&gt;|stdout| LLM
</pre>
</div>
<p></p></figure><p></p>
</div>
</div>
</div>
<p>The typical training material for LLMs contains a lot of examples of using CLIs and writing bash commands. So LLMs are already proficient with standard bash commands and CLIs out of the box, for example <code>grep</code>, <code>git</code>, <code>curl</code>, <code>uv</code>, <code>docker</code> and <code>psql</code>. This gives agents access to a huge existing software ecosystem without agent-specific integration work and with optimal token efficiency. It’s also expressive: shell commands can be piped into each other. Here’s an example that combines the <code>git log</code> command with bash built-in commands to count commits by author:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb1-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">git</span> log <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">--format</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;">'%an'</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">|</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sort</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">|</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">uniq</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-c</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">|</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">sort</span> <span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">-nr</span></span></code></pre></div></div>
<p>The tradeoff is security. To use CLIs directly, the LLM needs shell access, which by default gives it access to the entire host system. Granular approval is hard because there are countless commands, and they can be composed in unexpected ways. The solution is to sandbox the agent’s shell, only giving it access to files that it needs to complete the task. Another option is to emulate the shell, as done by Vercel’s <a href="https://github.com/vercel-labs/just-bash">just-bash</a>.</p>
<p>Naturally, the CLI ecosystem centers on software development, cloud and system administration tools. Outside of these domains, there are fewer CLIs to reuse, though <a href="https://jeroenjanssens.com/dsatcl/list-of-command-line-tools.html">Data Science at the Command Line</a> show how far standard unix utilities can go for data analysis. For example, <code>awk</code> can extract columns and compute aggregations from text streams, while <code>jq</code> can filter and reshape JSON from API. These tools can also be used for RAG, as shown in an article by <a href="https://vercel.com/blog/how-to-build-agents-with-filesystems-and-bash">Vercel</a>.</p>
<p><strong>Main use case</strong>: using developer, sysadmin and unix tools with an agent that has shell access</p>
</section>
<section id="skills" class="level3">
<h3 class="anchored" data-anchor-id="skills">Skills</h3>
<p><em>Loose prompt and script bundles with built-in tool search</em></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
    Skill[SKILL.md] --&gt;|name + description upfront| LLM
    LLM --&gt;|load full SKILL.md on demand| Skill
    LLM --&gt;|run script| Scripts[Scripts / resources]
    Scripts --&gt;|result| LLM
</pre>
</div>
<p></p></figure><p></p>
</div>
</div>
</div>
<p>Skills are CLI plus a prompt template. They have built-in tool search: each skill is a directory with a <code>SKILL.md</code> file and optional scripts/resources. A minimal <code>SKILL.md</code> example, featuring YAML frontmatter and a script:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode markdown code-with-copy"><code class="sourceCode markdown"><span id="cb2-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">---</span></span>
<span id="cb2-2"><span class="an" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">name:</span><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"> word-count</span></span>
<span id="cb2-3"><span class="an" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">description:</span><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"> Count words in a text file. Use when the user asks for a word count.</span></span>
<span id="cb2-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">---</span></span>
<span id="cb2-5"></span>
<span id="cb2-6">Run <span class="in" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">`python scripts/count.py &lt;path&gt;`</span> and report the total.</span></code></pre></div></div>
<p>The scripts are any code that can be executed by a shell, for example a Python script. <code>SKILL.md</code> is a Markdown file with a name and description, and optional scripts/resources. In addition to providing the script arguments, skills can also function as a prompt that describes how to use multiple scripts in a workflow.</p>
<p>This low ceremony is a large part of their appeal: a useful skill can be just a Markdown file plus optional scripts. In addition, skills have built-in progressive disclosure for token efficiency: the LLM gets the skill name and description upfront, and loads the full skill content on demand. While skills were originally invented by Anthropic to be used with Claude, the biggest registry is <a href="https://clawhub.ai">ClawHub</a> for OpenClaw agents, featuring over 3000 skills. By now, skills work with most CLI agents and IDEs, but not with apps like ChatGPT.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://simmering.dev/blog/connect-agent/clawhub_slack.webp" class="img-fluid figure-img"></p>
<figcaption>Slack skill on ClawHub</figcaption>
</figure>
</div>
<p>The limits show up when the workflow gets bigger: skill results can’t be chained directly, scripts need to be packaged as CLI tools, and the pattern is a poor fit for very large tool sets or remote execution. Security-wise, skills inherit the same shell access risk as CLI. They can make it worse by adding a supply-chain surface: users may install skills from registries without fully auditing them. In February 2026,<a href="https://snyk.io/blog/toxicskills-malicious-ai-agent-skills-clawhub/">Snyk</a> found 76 malicious skills employing prompt injection and other techniques on ClawHub and found critical vulnerabilities in 13% of all skills.</p>
<p><strong>Main use case</strong>: simple, discoverable connector bundles for local agents.</p>
</section>
<section id="code" class="level3">
<h3 class="anchored" data-anchor-id="code">Code</h3>
<p><em>Build your own tools on the fly</em></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
    Agent --&gt;|"write code"| Code
    Code --&gt;|"type check"| TypeChecker[Type checker]
    TypeChecker --&gt;|"feedback"| Agent
    Agent --&gt;|"run code"| Code
    Code --&gt;|"result"| Agent
</pre>
</div>
<p></p></figure><p></p>
</div>
</div>
</div>
<p>Code is CLI taken further: instead of only running existing commands, the agent writes a small program for the task. This approach is the most expressive, letting the agent write whole programs in a single turn of the conversation. It taps into the vast software engineering ecosystem: LLM pretraining and reinforcement learning on coding problems, code editors, type checkers, linters, test runners and all of the tools that developers use to build software. But its usefulness isn’t limited to software development - for instance, Claude Cowork creates PowerPoint files by writing a Python script that defines slides using python-pptx and runs it.</p>
<p>Writing code is easy for the model, but securely running arbitrary code requires a sandbox, dependency controls and permission boundaries. User approvals are even more difficult than for CLI tools because it can write arbitrary code rather than execute predefined commands. Users who are not developers can’t judge the safety and correctness of the code.</p>
<p><strong>Main use case</strong>: coding agents, with apps like Claude Cowork exploring the potential for general purpose agents.</p>
</section>
<section id="computer-use" class="level3">
<h3 class="anchored" data-anchor-id="computer-use">Computer Use</h3>
<p><em>When there is no API</em></p>
<p>Many apps lack an API. In these cases, the agent can simulate mouse clicks and keyboard inputs to interact with the app. The agent receives a representation of the UI, which can be a screenshot, an accesibility tree (used by screen readers), or a simplified browser DOM.</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
    App[App State] --&gt;|"representation"| Agent
    Agent --&gt;|"mouse and keyboard input"| App
</pre>
</div>
<p></p></figure><p></p>
</div>
</div>
</div>
<p>The agent can interact with any app, which creates a magic “the agent uses my computer like I do” experience. Computer use has universal reach across websites, web apps and desktop apps, but not a reusable connector ecosystem. Each UI is a fresh comprehension and control problem.</p>
<p>Frontier models have become increasingly capable. The benchmark <a href="https://os-world.github.io/"><em>OSWorld-Verified</em></a> measures whether agents can complete real desktop tasks across apps. Humans complete 72.4% of tasks, while GPT-5.5 and Opus 4.7 manage 78.0% and 78.7% respectively. Still, the agent relies on a secondary representation that wasn’t made for agents, and the pattern doesn’t scale well to multiple apps. A typical mistake is missing UI elements that are not visible in the initial screenshot and require scrolling.</p>
<p>Scalability is where computer use scores worst. The full app or browser needs to be running. Token usage is high especially when screenshots are involved. In an experiment by <a href="https://reflex.dev/blog/computer-use-is-45x-more-expensive-than-structured-apis/">reflex.dev</a> where an agent had to navigate an admin interface either by computer use or using API calls, computer use was 45x more expensive. Another difficulty is handling secrets: an agent with access to a password manager could accidentally paste credentials into a wrong field.</p>
<p><strong>Main use case</strong>: interacting with apps that lack an API.</p>
</section>
</section>
<section id="comparison" class="level2">
<h2 class="anchored" data-anchor-id="comparison">Comparison</h2>
<p>The following table summarizes the comparison on a simple scale that naturally omits details: green means the connector performs well on the characteristic, yellow means it performs well enough, red means it performs poorly.</p>
<style>
.comparison-table table {
    table-layout: fixed;
    width: 100%;
}

.comparison-table th:first-child,
.comparison-table td:first-child {
    width: 16%;
}

.comparison-table th:not(:first-child),
.comparison-table td:not(:first-child) {
    text-align: center;
    width: 12%;
}
</style>
<div class="comparison-table">
<table class="caption-top table">
<colgroup>
<col style="width: 12%">
<col style="width: 12%">
<col style="width: 12%">
<col style="width: 12%">
<col style="width: 12%">
<col style="width: 12%">
<col style="width: 12%">
<col style="width: 12%">
</colgroup>
<thead>
<tr class="header">
<th>Characteristic</th>
<th>Tools</th>
<th>MCP</th>
<th>A2A</th>
<th>CLI</th>
<th>Skills</th>
<th>Code</th>
<th>Computer Use</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Ease of authoring</td>
<td>🟢</td>
<td>🟡</td>
<td>🔴</td>
<td>🟡</td>
<td>🟢</td>
<td>⚪</td>
<td>🟢</td>
</tr>
<tr class="even">
<td>Expressiveness</td>
<td>🟡</td>
<td>🟡</td>
<td>🟢</td>
<td>🟢</td>
<td>🟡</td>
<td>🟢</td>
<td>🟡</td>
</tr>
<tr class="odd">
<td>Token efficiency</td>
<td>🟡</td>
<td>🟡</td>
<td>🟢</td>
<td>🟢</td>
<td>🟢</td>
<td>🟢</td>
<td>🔴</td>
</tr>
<tr class="even">
<td>Scalability</td>
<td>🟡</td>
<td>🟡</td>
<td>🟢</td>
<td>🟡</td>
<td>🟡</td>
<td>🟢</td>
<td>🔴</td>
</tr>
<tr class="odd">
<td>Portability</td>
<td>🔴</td>
<td>🟢</td>
<td>🟡</td>
<td>🟡</td>
<td>🟡</td>
<td>🟡</td>
<td>🔴</td>
</tr>
<tr class="even">
<td>Ecosystem</td>
<td>🟡</td>
<td>🟢</td>
<td>🔴</td>
<td>🟢</td>
<td>🟢</td>
<td>🟢</td>
<td>🔴</td>
</tr>
<tr class="odd">
<td>Observability</td>
<td>🟢</td>
<td>🟢</td>
<td>🟡</td>
<td>🟡</td>
<td>🟡</td>
<td>🟡</td>
<td>🔴</td>
</tr>
<tr class="even">
<td>Security</td>
<td>🟢</td>
<td>🟡</td>
<td>🟡</td>
<td>🔴</td>
<td>🔴</td>
<td>🔴</td>
<td>🔴</td>
</tr>
</tbody>
</table>
</div>
<p>This assumes that connectors are generally well specified and that tool search is used for the Tools and MCP connectors. Ease of authoring doesn’t apply well to the code connector.</p>
<section id="conclusion" class="level3">
<h3 class="anchored" data-anchor-id="conclusion">Conclusion</h3>
<p>Each connector has a unique use case:</p>
<ul>
<li><strong>Tools</strong>: custom tools with maximum control.</li>
<li><strong>MCP</strong>: sharing tools across agents.</li>
<li><strong>A2A</strong>: multi-turn interaction between agents.</li>
<li><strong>CLI</strong>: using developer, sysadmin and unix tools with an agent that has shell access.</li>
<li><strong>Skills</strong>: simple, discoverable connector bundles for local agents.</li>
<li><strong>Code</strong>: maximum expressiveness for coding agents and experimental general purpose agents.</li>
<li><strong>Computer use</strong>: interacting with apps that lack an API.</li>
</ul>
<p>It’s easy to wrap one and the same function as a tool, local MCP server or skill, so it’s not a problem to change the representation of the connector later. Only use A2A for advanced multi-turn interaction between agents and check compatibility first. CLI, skills and code offer the fullest expressiveness, but require sandboxing. For now, I’d only recommend them for interactive use. Skills are deceptively simple but can be abused. Computer use scores worst in the table above, but is the only way to interact with apps that lack an API.</p>


</section>
</section>

 ]]></description>
  <category>Agents</category>
  <guid>https://simmering.dev/blog/connect-agent/</guid>
  <pubDate>Sun, 03 May 2026 22:00:00 GMT</pubDate>
  <media:content url="https://simmering.dev/blog/connect-agent/image.webp" medium="image" type="image/webp"/>
</item>
<item>
  <title>From Vibe Coding to Agentic Engineering</title>
  <dc:creator>Paul Simmering</dc:creator>
  <link>https://simmering.dev/blog/agentic-engineering/</link>
  <description><![CDATA[ 






<p>The term <em>vibe coding</em> just turned one. Andrej Karpathy, who <a href="https://x.com/karpathy/status/1886192184808149383?lang=de">coined</a> it in February 2025 and long preferred autocomplete over agents, flipped to 80% agentic coding in <a href="(https://x.com/karpathy/status/2015883857489522876)">January 2026</a>. Agentic coding has leveled up massively: Claude 4.5 Opus reached 80.9% on <a href="https://www.swebench.com">SWE-bench Verified</a>, better harnesses enable longer runs and multi-agent collaboration, and we have new apps such as Codex for controlling them.</p>
<p>In 2023, I reported on my first experiences with AI assistance and the <a href="https://simmering.dev/blog/ai-assistants/">post</a> is now a window into a quaint past. The problems I noticed then were fixed by better tools. Now developers need to catch up on how to use them. New practices under the banner of <em>agentic engineering</em> are emerging. This article is a framework for using coding agents professionally: five coding styles with different tradeoffs between speed, control and learning, followed by practical techniques for context engineering and harness design. It also confronts an uncomfortable finding from recent research: AI boosts output but erodes understanding, creating <em>cognitive debt</em>.</p>
<section id="five-coding-styles" class="level2">
<h2 class="anchored" data-anchor-id="five-coding-styles">Five coding styles</h2>
<p>The improvements in AI assistance and automation tools have enabled new styles of coding. I’m classifying them into distinct styles, which I’ve rated in terms of speed, control and learning, the main tradeoffs I see in choosing a style.</p>
<ul>
<li><strong>Speed</strong>: How fast you complete the code. I mean the time taken for a single change, not development velocity over the long run.</li>
<li><strong>Control</strong>: How many of the decisions involved you make rather than delegate to an AI. This involves the architecture level and the micro level of individual lines of code or chunks like functions and classes. Reviewing code doesn’t give the same control as creating it from scratch, as it anchors on the AI’s choices.</li>
<li><strong>Learning</strong>: How much you advance your own coding skills and knowledge of the project. Experiments from <a href="https://www.anthropic.com/research/AI-assistance-coding-skills">Anthropic</a>, <a href="https://www.media.mit.edu/publications/your-brain-on-chatgpt/">MIT</a> and <a href="https://www.nature.com/articles/s41598-025-98385-2">Zhejiang University</a> found that knowledge workers using AI have enhanced results, but that the gain doesn’t carry over when working alone afterward. Further, they don’t retain as much information and report lower sense of ownership. The MIT study coins this condition <em>cognitive debt</em>. <a href="https://margaretstorey.com/blog/2026/02/09/cognitive-debt/">Margaret-Anne Storey</a> from the University of Victoria relates it back to coding as a new type of debt that projects accumulate when developer’s understanding doesn’t keep up with growing complexity.</li>
</ul>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Style</th>
<th>Speed</th>
<th>Control</th>
<th>Learning</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>No AI</td>
<td>●○○○○</td>
<td>●●●●●</td>
<td>●●●●●</td>
</tr>
<tr class="even">
<td>Autocomplete</td>
<td>●●○○○</td>
<td>●●●●○</td>
<td>●●●●○</td>
</tr>
<tr class="odd">
<td>Single agent</td>
<td>●●●○○</td>
<td>●●●○○</td>
<td>●●○○○</td>
</tr>
<tr class="even">
<td>Multi agent for parallel features</td>
<td>●●●●●</td>
<td>●○○○○</td>
<td>●○○○○</td>
</tr>
<tr class="odd">
<td>Multi agent for exploration</td>
<td>●●○○○</td>
<td>●●●○○</td>
<td>●●●●○</td>
</tr>
</tbody>
</table>
<p>The ratings are my subjective view and outcomes depend on how exactly a style is performed. For instance, asking the agent questions about its implementation can boost learning again. Ownership could be a fourth aspect, but its ratings would highly correlate with control and learning. I don’t list copy-pasting into a chatbot as a viable style, because it’s so inefficient in comparison to the others.</p>
<section id="no-ai" class="level3">
<h3 class="anchored" data-anchor-id="no-ai">No AI</h3>
<p>Manual coding speed is highly dependent on how well a developer has memorized the programming language they’re using. Writing code manually repeats the basics such as loops, conditions and indexing over and over. You solve small logic problems all the time and become fluent in a programming language. After writing a piece of code, you know the ins and outs of it.</p>
<p><strong>Strengths:</strong> learning on micro and macro level, drilling the basics, control, ownership</p>
<p><strong>Weaknesses:</strong> slow, limited to familiar programming languages, high cognitive load</p>
</section>
<section id="autocomplete" class="level3">
<h3 class="anchored" data-anchor-id="autocomplete">Autocomplete</h3>
<p>Using the autocomplete with a coding extension for an IDE or a VSCode fork provides a very nice speed boost. You can skip over the common small logic problems. Knowing the language is still important. Autocomplete also doesn’t do architecture, it focuses on the micro. As the generated code comes in small chunks, developers naturally check it as they go.</p>
<p>The speed of the coding model is critical for this style; if you have to wait, the flow state is broken. The recent <a href="https://openai.com/index/introducing-gpt-5-3-codex-spark/">GPT-5.3-Codex-Spark</a> is promising near-instant answers with decent intelligence.</p>
<p>Early on, I used to write comments specifically to trigger the autocomplete to write the implementation. With agents that take direct prompts as an instruction, I wouldn’t recommend this specific technique anymore. Overall I see autocomplete as a nicely balanced style.</p>
<p><strong>Strengths:</strong> speed up over manual coding, good control</p>
<p><strong>Weaknesses:</strong> not as fast as agents, less learning on micro level</p>
</section>
<section id="single-agent" class="level3">
<h3 class="anchored" data-anchor-id="single-agent">Single agent</h3>
<p>Write a prompt, perhaps use planning mode and let a CLI agent or an agent embedded in an IDE go off reading docs, writing code and running tests until it reports back with a result. I’ll share productivity tips in a section below.</p>
<p><strong>Strengths:</strong> fast, work in unfamiliar languages</p>
<p><strong>Weaknesses:</strong> loss of ownership, control and learning, wait for agent to complete</p>
</section>
<section id="multi-agent-for-parallel-features" class="level3">
<h3 class="anchored" data-anchor-id="multi-agent-for-parallel-features">Multi agent for parallel features</h3>
<p>Kick off multiple agents, each working on a different task. This is a style favored by Peter Steinberger, developer of OpenClaw, famously programming from his smartphone. I tried it with OpenAI’s Codex app for a React + NextJS side project and found that I could run the app in a browser, write instructions to Codex and see the update in real time. It completely moved my focus to the app’s functionality and UX, rather than the code. This generated an ungodly amout of changes that I dreaded to review, so I kept vibing instead.</p>
<p>This is fantastic for hackathons, demos and experiments. Personally, I don’t feel confident doing this for a serious project, where I am responsible for each line of code. I’d also run out of token budget before the end of the month.</p>
<p><strong>Strengths:</strong> absurdly fast</p>
<p><strong>Weaknesses:</strong> extreme loss of ownership, control and learning, token usage</p>
</section>
<section id="multi-agent-for-exploration" class="level3">
<h3 class="anchored" data-anchor-id="multi-agent-for-exploration">Multi agent for exploration</h3>
<p>Kick off multiple agents to do the same task independently, then review their solutions and pick the best one. This makes sense for complex tasks with many possible solutions that have advantages and drawbacks that only become clear during implementation.</p>
<p>Cursor implements this explicitly by letting you compare the results of different models.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://simmering.dev/blog/agentic-engineering/cursor.webp" class="img-fluid figure-img"></p>
<figcaption>Cursor’s multi agent interface</figcaption>
</figure>
</div>
<p>This is a rare, but promising style that I haven’t built much experience with yet.</p>
<p><strong>Strengths:</strong> can improve quality of architecture, learning on the macro level</p>
<p><strong>Weaknesses:</strong> time consuming to review, loss of learning on micro level, token usage</p>
</section>
</section>
<section id="optimizing-agent-productivity" class="level2">
<h2 class="anchored" data-anchor-id="optimizing-agent-productivity">Optimizing agent productivity</h2>
<p>Let’s look at how exactly we can get maximum productivity from agentic engineering and the best ways to stay in control of the macro while delegating the micro.</p>
<section id="plan-mode" class="level3">
<h3 class="anchored" data-anchor-id="plan-mode">Plan mode</h3>
<p>Everything you don’t specify has to be guessed by the LLM. That’s similar to a stakeholder formulating a business requirement and a developer having to fill in all the technical details. For complex changes, I’m a fan of <em>plan mode</em>, where the agent answers with a to do list, rather than jumping into action. It’s a forcing function for clarity and a dedicated place for the developer to review architecture. If the plan doesn’t match what you meant, you catch it in 30 seconds instead of 10 minutes reviewing generated code. That’s also token-efficient.</p>
<p>Simple changes don’t need plan mode, but if I already know how I want something implemented, I’ll mention it in the prompt, e.g.&nbsp;“implement this with an env var” or “upsert the entry to the database”. For UI, pasting screenshots of your own app or inspirations works surprisingly well.</p>
<p>Keep in mind that LLMs are still optimized to please. Current leaders aren’t quite as sycophantic as the notorious GPT-4o, but they’re still not pushing back on bad ideas like a real senior engineer would. If you ask an LLM to find a bug, it will try really hard to constitute a bug, whether there is one or not. If you ask whether something could be simpler, it will try to simplify that bit, perhaps at the expense of complexity elsewhere.</p>
</section>
<section id="context-engineering-by-example" class="level3">
<h3 class="anchored" data-anchor-id="context-engineering-by-example">Context engineering by example</h3>
<p>Context engineering is the art of managing the LLM’s context window and seeding it with all relevant information at the start of an interaction. The best context engineering doesn’t happen in the prompt, but in the code base:</p>
<ul>
<li>Making the project <em>agent legible</em> helps, i.e.&nbsp;using a monorepo, paying attention to consistent variable names and co-locating related information such as content and style using Tailwind CSS.</li>
<li>Existing code serves as examples of how to write. Agents are great at picking up patterns and repeating them. For example, if existing code has full test coverage, an agent will likely write a test for new code as well.</li>
<li>Readmes and other notes explain the purpose of the project, conventions and instructions to run it. Anything that helps a new hire also helps an agent. Rule files like <code>copilot-instructions.md</code> or <code>AGENTS.md</code> are more specifically aimed at agents. Effort invested here is leveraged by every agent run. Less is more: a few well maintained rules beat an overstuffed and rotting tome. Turning <code>AGENTS.md</code> into a table of contents linking to other notes worked well for a large AI engineering project by <a href="https://openai.com/index/harness-engineering/">OpenAI</a>. They took it further with custom linters by the principle: “Human taste is captured once, then enforced continuously on every line of code”. The emphasis is on <em>human taste</em>: Using an LLM to generate the instructions defeats the point. An <a href="https://arxiv.org/pdf/2602.11988">benchmark study</a> about solving Python Github issues found that LLM-generated <code>AGENTS.md</code> actually marginal decreased agent performance while increasing token usage by 20%.</li>
</ul>
<p>These help with the intial context loading. Later, as the chat fills with prompts and responses, it’s important to be mindful of context window size and purity. Wrong information and failed attempts mislead LLMs. Claude’s phrase “You’re absolutely right” has become a meme because of this. When you see it, your context is rotten, and it’s time to start a new thread. One way to wrap up a thread is manual compaction: asking the LLM to summarize the problem, what it has tried and learned. The answer can be both educational and useful as a starting point for a new attempt. Anthropic has a full <a href="https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents">guide</a> on context engineering for agents.</p>
</section>
<section id="harness-engineering" class="level3">
<h3 class="anchored" data-anchor-id="harness-engineering">Harness engineering</h3>
<p>Beyond context, you’re designing the agent’s development environment: the information it gets, the tools to unblock itself, and the guidance that keeps it aligned with your plans. Models are trained to hill-climb via RLHF and don’t give up easily, but they need instruments and feedback. Watch for situations where the LLM is blind to a problem and add the tools it needs to resolve problems without human intervention.</p>
<p>Among tooling, type checking deserves special mention: it lets agents easily catch errors statically. <a href="https://www.youtube.com/watch?v=iV1EcfZSdCM&amp;pp=0gcJCZEKAYcqIYzv">Theo Browne</a> makes a strong case for TypeScript as an optimal language for agentic coding, as it guarantees type safety in frontend, backend and on their interfaces. <a href="https://honnibal.dev/blog/llm-style-tips">Matt Honnibal</a> gives practical Python advice focused on type safety and testability through pure functions. Linters, unit tests and tools to let agents look at the app’s UI are also important.</p>
<p>The most ambitious projects take multi-agent setups and long runs that span multiple context windows with compaction steps. Anthropic’s <a href="https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents">guide on effective harnesses</a> covers failure modes and fixes, <a href="https://openai.com/index/harness-engineering">OpenAI’s case study</a> is another good read. This is the forefront of agentic engineering.</p>
</section>
</section>
<section id="staying-in-control" class="level2">
<h2 class="anchored" data-anchor-id="staying-in-control">Staying in control</h2>
<section id="resisting-fragmentation" class="level3">
<h3 class="anchored" data-anchor-id="resisting-fragmentation">Resisting fragmentation</h3>
<p>While an AI chat window formally places the user in control, the dynamic can flip in practice: agents going off making unwanted changes, notifications interrupting the user’s work and the user becoming the agent’s QA help. It’s also easy to get lazy and let the agent do all the thinking.</p>
<p>Developers reporting on their experience emphasize controlling the dynamic:</p>
<blockquote class="blockquote">
<p>Try to avoid workflow loops where you go off and do things, paste the error back into the LLM, and then go do whatever it tells you. I’ve seen this anti-pattern called a “reverse centaur”: you want to be the head of the human/horse hybrid, not the ass. – Matt Honnibal</p>
</blockquote>
<blockquote class="blockquote">
<p>“I found that it was my job as a human to be in control of when I interrupt the agent, not the other way around.” – Mitchell Hashimoto</p>
</blockquote>
<p>Keeping agents running in the background while you’re working on something else is efficient, but can also be exhausting context switching. A <a href="https://hbr.org/2026/02/ai-doesnt-reduce-work-it-intensifies-it">study</a> tracking AI use at a tech company found that AI use increased multitasking, such as manually coding while orchestrating agents working on other tasks. Workers reported increased cognitive fatigue as they took on extra tasks outside of their core competence and filled natural breaks with AI usage.</p>
</section>
<section id="taking-responsibility" class="level3">
<h3 class="anchored" data-anchor-id="taking-responsibility">Taking responsibility</h3>
<p>Opening a pull request is saying: I believe this code is good enough to be merged and is worthy of the reviewer’s time. But popular open source projects are facing an onslaught of low quality vibe coded pull request. As a consequence, <a href="https://github.com/ghostty-org/ghostty/pull/10412">new norms</a> about AI-generated code are forming. Maintainers are rightfully angry when someone expects them to review an AI-generated mess. Reviewing is harder than generating.</p>
<p>The solution is to increase the burden of proof in regards to relevance and correctness. Simon Willison <a href="https://simonwillison.net/2025/Dec/18/code-proven-to-work/">wrote</a>: “Your job is to deliver code you have proven to work” and suggests more comprehensive tests, screenshots or even a video attached to the PR. I’d add that it’s also a developer’s job to understand the code and be able to answer questions about it during review.</p>
<p>Part of packaging code up for review is using Git. I find that staging changes for a commit is a natural point for human control to review generated code. Conceding git control to agents would remove this valuable loop.</p>
</section>
</section>
<section id="theres-no-going-back" class="level2">
<h2 class="anchored" data-anchor-id="theres-no-going-back">There’s no going back</h2>
<p>Some are <a href="https://world.hey.com/dhh/promoting-ai-agents-3ee04945">exhilarated</a> with new possiblities, others are <a href="https://nolanlawson.com/2026/02/07/we-mourn-our-craft/">mourning</a> the loss of their craft, still others are <a href="https://ezhik.jp/ai-slop-terrifies-me/">concerned</a> that we’re about to enter a new era of slop. What they agree on is that software engineering is changed forever. Even if LLMs never become better than today, which is unlikely, just better tooling will keep getting us better coding agents.</p>
<p>Based on Anthropic’s <a href="https://www.anthropic.com/research/estimating-productivity-gains">estimates</a>, the programmers cited in this article and my own experience, agentic engineering is a 2x or greater speed-up for experienced developers. It’s also an enabler for beginners. But as the studies discussed above have shown, they’re also a trap for cognitive debt and loss of craftsmanship. It’s a challenge for both individual developers and organizations to find work patterns and shared expectations that get the best of both worlds, classic and agentic software engineering.</p>


</section>

 ]]></description>
  <category>Productivity</category>
  <category>Agents</category>
  <guid>https://simmering.dev/blog/agentic-engineering/</guid>
  <pubDate>Sat, 14 Feb 2026 23:00:00 GMT</pubDate>
  <media:content url="https://simmering.dev/blog/agentic-engineering/image.webp" medium="image" type="image/webp"/>
</item>
<item>
  <title>The Reliability Gap: Agent Benchmarks for Enterprise</title>
  <dc:creator>Paul Simmering</dc:creator>
  <link>https://simmering.dev/blog/agent-benchmarks/</link>
  <description><![CDATA[ 






<section id="unrealized-potential-due-to-lacking-reliability" class="level2">
<h2 class="anchored" data-anchor-id="unrealized-potential-due-to-lacking-reliability">Unrealized potential due to lacking reliability</h2>
<p>A 2025 survey by <span class="citation" data-cites="panMeasuringAgentsProduction2025">Pan et al. (2025)</span> among 306 AI agent practitioners found that reliability issues are the biggest barrier to adoption of AI agents in enterprise. To achieve the reliability required, practitioners are foregoing open-ended and long-running tasks in favor of workflows involving fewer steps. They control potential damage by building internal facing agents whose work is reviewed by internal employees, rather than customer facing or machine-to-machine interfaces. These limited agents are economically useful, but don’t realize the full potential.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://simmering.dev/blog/agent-benchmarks/reliability_gap.webp" class="img-fluid figure-img"></p>
<figcaption>Image created with GPT Image 1.5</figcaption>
</figure>
</div>
<p>To quantify how large the reliability gap towards the full potential is, I’ll review public benchmarks for agentic AI. In October 2025, <span class="citation" data-cites="schmidAIAgentBenchmarkCompendium2025">Schmid (2025)</span> listed over 50 benchmarks. That’s too many to pay attention to, so in this article I’ll prioritize them from the perspective of an enterprise looking to automate common business tasks.</p>
<section id="benchmark-selection-criteria" class="level3">
<h3 class="anchored" data-anchor-id="benchmark-selection-criteria">Benchmark selection criteria</h3>
<ol type="1">
<li><strong>Relevance</strong>. Tests abilities relevant for business use cases, ideally the exact task that the enterprise is looking to automate.</li>
<li><strong>Agentic</strong>. Measures agentic abilities with multiple turns and tool use, not just single turn reasoning.</li>
<li><strong>Best in class</strong>. The benchmark is not overshadowed by a more comprehensive benchmark measuring the same or closely related ability or a newer version of the same benchmark.</li>
<li><strong>Leaderboard</strong>. A benchmark needs a public leaderboard with up-to-date models listed. This disqualifies the majority of benchmarks. Most are published as a paper with a few model scores, which are quickly outdated.</li>
</ol>
<div class="callout callout-style-default callout-note callout-titled" title="Interpreting agentic benchmarks">
<div class="callout-header d-flex align-content-center">
<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>Interpreting agentic benchmarks
</div>
</div>
<div class="callout-body-container callout-body">
<p>Benchmark results are sensitive to the language model, the agentic loop code, tools available to the agent including their documentation, the benchmark harness (the environment in which the agent is evaluated), the evaluation method and random variations. Each score reflects a snapshot of all of these variables.</p>
<p>In addition to regular percentages of correctly completed tasks, benchmarks sometimes use two other metrics:</p>
<ul>
<li><strong>pass@k</strong> (pronounced “pass at k”): the probability of passing at least one of k runs. In other words, whether the agent is capable of succeeding at all if you let it try many times.</li>
<li><strong>pass^k</strong> (pronounced “pass wedge k”): the probability of passing on all k runs of the same task. In other words, how many times you can expect the agent to succeed if you run it k times. Measured empirically by running each task k times and counting what fraction pass all attempts. Steeper decline indicates less consistent performance across runs.</li>
<li><strong>majority@k</strong>: the probability of passing most of k runs. A looser consistency metric than pass^k, which demands passing all k. Useful where occasional failure is tolerable but you want the common outcome to be success. <span class="citation" data-cites="jha2026thinklocallyexplainglobally">Jha et al. (2026)</span> use the gap between pass@k and majority@k to quantify reliability on an IT incident-diagnosis benchmark: their <a href="https://arxiv.org/pdf/2601.17915#page=3">Figure 1</a> plots the two against each other, and the distance below the diagonal shows how far capability (“can succeed once”) runs ahead of consistency (“usually succeeds”): the reliability gap.</li>
</ul>
<p>From the lens of business automation, pass^k is more relevant than pass@k. Unfortunately, most benchmarks only report pass^1, not higher pass^k metrics. Other important metrics that are not always reported are the time required to complete the task and the cost incurred in terms of input and output tokens. BFCL is an example of a benchmark that reports these metrics.</p>
<p>Benchmarks often have problems on release and are improved over time. For example, the original SWE-bench (2024) had ~68% of tasks that were unsolvable due to underspecified problems or unfair tests, leading to SWE-bench Verified’s human validation process.</p>
<p><span class="citation" data-cites="shankarUnderstandingAIBenchmarks2025">Shankar (2025)</span> goes into more detail on benchmark interpretation.</p>
</div>
</div>
</section>
</section>
<section id="key-benchmarks-for-evaluating-agents-for-enterprise-use" class="level2">
<h2 class="anchored" data-anchor-id="key-benchmarks-for-evaluating-agents-for-enterprise-use">Key benchmarks for evaluating agents for enterprise use</h2>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Benchmark</th>
<th>Task</th>
<th>Best pass^1</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>GAIA</td>
<td>Answer questions using tools and web</td>
<td>90% (SU Zero agent)</td>
</tr>
<tr class="even">
<td>BFCL V3</td>
<td>Call functions correctly</td>
<td>77% (Claude Opus 4.5)</td>
</tr>
<tr class="odd">
<td>τ²-bench</td>
<td>Serve customers with policy compliance</td>
<td>85% (Gemini 3 Pro)</td>
</tr>
<tr class="even">
<td>Vending-Bench 2</td>
<td>Run a business over many turns</td>
<td>$5,478 (Gemini 3 Pro)</td>
</tr>
</tbody>
</table>
<p>I have selected these featured benchmarks based on the criteria listed above. Click on the benchmark name to learn more about each of the featured benchmarks. The scores here are pass^1. Only τ²-bench systematically reports pass^k metrics, though not for all entries. See its section for a detailed breakdown.</p>
<section id="specialty-benchmarks-with-narrower-focus" class="level3">
<h3 class="anchored" data-anchor-id="specialty-benchmarks-with-narrower-focus">Specialty benchmarks with narrower focus</h3>
<ul>
<li><strong>Coding</strong>: <a href="https://www.swebench.com/">SWE-bench verified</a>. Fix real GitHub issues from Python repositories. While highly relevant for evaluating coding capabilities, most enterprises will adopt existing AI coding tools (Claude Code, GitHub Copilot, Cursor) rather than develop custom coding agents. Leading score: 74.4% (Claude Opus 4.5, end of 2025).</li>
<li><strong>Web automation</strong>: <a href="https://webarena.dev/">WebArena</a>, <a href="https://osu-nlp-group.github.io/Mind2Web/">Mind2Web</a>. Navigate and complete tasks on real websites. Web browsing is partially covered in GAIA.</li>
<li><strong>GUI automation</strong>: <a href="https://os-world.github.io/">OSWorld</a>, <a href="https://github.com/zlwang-cs/OfficeBench">OfficeBench</a>, <a href="https://github.com/google-research/android_world">AndroidWorld</a>. Control Windows/Mac/Linux/Android via a graphical user interface. Only relevant if the agent must use a GUI instead of APIs. GUIs add a failure mode.</li>
<li><strong>Safety</strong>: <a href="https://scale.com/leaderboard/fortress">FORTRESS</a>. Tests safeguard robustness vs over-refusal. Important for production deployments but not the focus of this article.</li>
</ul>
</section>
<section id="which-types-of-agents-are-ready-for-enterprise-use" class="level3">
<h3 class="anchored" data-anchor-id="which-types-of-agents-are-ready-for-enterprise-use">Which types of agents are ready for enterprise use?</h3>
<p>Let’s consider a business that wants to automate a task. According to the survey of <span class="citation" data-cites="panMeasuringAgentsProduction2025">Pan et al. (2025)</span>, increasing productivity is the most common motivation. The baseline for accuracy is a human worker doing the task, who can also make mistakes. Unlike standard software, the expectation shouldn’t be 100% accuracy, but an acceptable trade-off for the benefits of automation. GAIA provides a human baseline of 92%, which is just 2 percentage points ahead of the best models.</p>
<p>I propose three stages of readiness:</p>
<ol type="1">
<li><strong>Internal tools</strong> reporting to humans, such as deep research, data analysis, information extraction, documentation and coding agents are ready now. The current highest scores for GAIA, BFCL and SWE-bench at the end of 2025 are 90%, 77.5% and 74.4%, respectively. Agents provide a profitable trade-off between accuracy and productivity. The time that humans spend checking results must be less than the time savings from automation.</li>
<li><strong>Customer facing tools</strong>, such as customer service agents. The challenge here is consistency, not capability. τ-bench shows models hitting 80% pass^1 but dropping significantly on pass^8, meaning the agent might handle a request perfectly one day and fail the next. Tight monitoring and a swift escalation path to a human is necessary.</li>
<li><strong>Long running autonomous work</strong>, such as inventory and portfolio management, scheduling, management of other agents over multiple tasks is not ready yet. Vending-Bench shows that even the best models show massive variance across runs, and there’s a risk of hitting meltdowns where they spiral into bizarre behavior.</li>
</ol>
</section>
</section>
<section id="featured-benchmarks-in-detail" class="level2">
<h2 class="anchored" data-anchor-id="featured-benchmarks-in-detail">Featured benchmarks in detail</h2>
<section id="gaia" class="level3">
<h3 class="anchored" data-anchor-id="gaia">GAIA: General AI Assistant</h3>
<p><a href="https://huggingface.co/spaces/gaia-benchmark/leaderboard">GAIA</a> <span class="citation" data-cites="mialonGAIABenchmarkGeneral2023">(Mialon et al. 2023)</span> is the most important general benchmark. It consists of 466 high quality questions at three difficulty levels, annotated by humans. The authors value quality over quantity and it took them about 2 hours to make each question. The questions have unambiguous verifiable answers and require multi-step reasoning.</p>
<p>As a test, I asked Raycast AI to answer the level 3 difficulty example question from the paper.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://simmering.dev/blog/agent-benchmarks/raycast_gaia.png" class="img-fluid figure-img"></p>
<figcaption>GAIA benchmark example question with Claude 4.5 Sonnet in Raycast AI</figcaption>
</figure>
</div>
<p>It ended up doing 9 web searches and 1 call to a calculator tool. It got the answer wrong due to a false assumption.</p>
<p>Most tasks take 1 to 3 tools and 3 to 12 steps to solve. Questions can be solved by looking up information or by having it memorized. Answering the questions requires the following tools:</p>
<ul>
<li>Web search. It relies on information from trusted websites that are unlikely to disappear, such as arXiv and Wikipedia.</li>
<li>Reading different file types: PDF, Excel, PowerPoint, CSV, image, audio and even video.</li>
<li>Handling multimodal data, OCR (optical character recognition) and using Google Street View.</li>
<li>Coding.</li>
</ul>
<p>In contrast to other benchmarks, GAIA challengers are whole agentic systems that package one or more LLMs, the tools and an agentic loop. The current leader is SU Zero by Suzhou AI Lab. This enables measuring more innovation on the agentic loop and tools, but also means the scores can’t be used directly when choosing an off-the-shelf LLM.</p>
<p>GAIA is the most general benchmark and worth paying attention to, though at risk of saturation as the highscore already reached 90%. Like other benchmarks, GAIA questions and the majority of source materials are in English. Expect a drop in performance on other languages.</p>
</section>
<section id="bfcl" class="level3">
<h3 class="anchored" data-anchor-id="bfcl">Berkeley Function-Calling Leaderboard (BFCL) V3</h3>
<p>The <a href="https://gorilla.cs.berkeley.edu/leaderboard.html">Berkeley Function-Calling Leaderboard</a> <span class="citation" data-cites="patilBerkeleyFunctionCalling2025">(Patil et al. 2025)</span> measures LLMs’ ability to invoke functions/tools accurately across multiple programming languages and agentic scenarios. Function calling is the foundation of agentic AI: every API call to update a CRM, check inventory, process a payment, or query a database relies on accurate function calling.</p>
<p>The benchmark consists of 4441 question-function-answer triplets across Python, Java, JavaScript, SQL and REST API calls. They vary in complexity from simple function calls to multi-turn interactions. The augmented multi-turn questions added in V3 are especially challenging, as they require models to recognize situations in which additional information has to be requested from the user, or there isn’t a function that does the job.</p>
<p>Here’s one of the simplest questions: “Can you fetch me the weather data for the coordinates 37.8651 N, 119.5383 W, including the hourly forecast for temperature, wind speed, and precipitation for the next 10 days?” The correct answer is <code>requests.get(url="https://api.open-meteo.com/v1/forecast", params={"latitude": "37.8651", "longitude": "-119.5383", "forecast_days": 10})</code>.</p>
<p>The current leader is Claude Opus 4.5 at 77.5% overall accuracy (pass^1, end of 2025). It scores particularly well on the web search subscore at 84.5%. The leaderboard also reports cost, which ranges from less than $1 with small open weights models to $355 with Grok-4.</p>
</section>
<section id="tau2-bench" class="level3">
<h3 class="anchored" data-anchor-id="tau2-bench">τ²-bench: Tool-Agent-User Interaction</h3>
<p><a href="https://taubench.com/#leaderboard">τ²-bench</a> <span class="citation" data-cites="yaoTauBenchBenchmarkToolAgentUser2024">(Yao et al. 2024)</span> is a multi-turn support bot benchmark. It measures agents’ ability to interact with simulated users and follow domain-specific policies while using tools in multi-turn conversations. The original τ-bench (2024) introduced the retail and airline domains. τ²-bench (2025) added a telecom domain where both user and agent have access to different tools and must coordinate, better simulating expert-novice gaps in technical support scenarios.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://simmering.dev/blog/agent-benchmarks/tau2_chat.png" class="img-fluid figure-img"></p>
<figcaption>τ²-bench example chat in the telecom domain. Image from the paper</figcaption>
</figure>
</div>
<p>The chat above is an example conversation that shows how user and agent exchange information and coordinate tool calls. An ablation study in which the support agent had access to all tools showed a significantly higher success rate, indicating that user-agent communication is a common failure mode. The full chat histories of the benchmark runs are available on the <a href="https://taubench.com/#trajectory-visualizer">website</a>.</p>
<p>The leading overall score is 85% (Gemini 3 Pro) for pass^1. Its domain subscores are 85% for retail, 73% for airline and 98% for telecom. The telecom score surprised me, as it’s so far above the best score reported in the paper introducing the domain, which listed Claude 3.7 Sonnet at 49% pass^1. However, other models also listed with scores in the high 90s for the telecom domain.</p>
</section>
<section id="quantified-reliability-with-passk" class="level3">
<h3 class="anchored" data-anchor-id="quantified-reliability-with-passk">Quantified reliability with pass^k</h3>
<p>The τ²-bench leaderboard reports pass^k metrics for most entries. The graph below shows the dropoff pattern from pass^1 to pass^4 for the current top models that report pass^k metrics and pooled across all domains.</p>
<div id="60ec8508" class="cell" data-execution_count="1">
<div class="cell-output cell-output-display">
<div>            <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="/assets/vendor/plotly/plotly-3.3.0.min.js" integrity="sha256-bO3dS6yCpk9aK4gUpNELtCiDeSYvGYnK7jFI58NQnHI=" crossorigin="anonymous"></script>                <div id="14e2bddf-a2d2-45b3-882f-5efdd930ceff" class="plotly-graph-div" style="height:500px; width:100%;"></div>            <script type="text/javascript">                window.PLOTLYENV=window.PLOTLYENV || {};                                if (document.getElementById("14e2bddf-a2d2-45b3-882f-5efdd930ceff")) {                    Plotly.newPlot(                        "14e2bddf-a2d2-45b3-882f-5efdd930ceff",                        [{"hovertemplate":"%{fullData.name}: %{y:.1f}%\u003cextra\u003e\u003c\u002fextra\u003e","line":{"width":2},"marker":{"size":8},"mode":"lines+markers","name":"Qwen3-Max-Thinking-Preview","x":{"dtype":"i4","bdata":"AQAAAAIAAAADAAAABAAAAA=="},"y":{"dtype":"f8","bdata":"MzMzMzMzVEAAAAAAAKBSQDMzMzMzk1FAMzMzMzOzUEA="},"type":"scatter"},{"hovertemplate":"%{fullData.name}: %{y:.1f}%\u003cextra\u003e\u003c\u002fextra\u003e","line":{"width":2},"marker":{"size":8},"mode":"lines+markers","name":"GPT-5","x":{"dtype":"i4","bdata":"AQAAAAIAAAADAAAABAAAAA=="},"y":{"dtype":"f8","bdata":"AAAAAAAAVEAAAAAAAEBSQAAAAAAAAFFAAAAAAAAAUEA="},"type":"scatter"},{"hovertemplate":"%{fullData.name}: %{y:.1f}%\u003cextra\u003e\u003c\u002fextra\u003e","line":{"width":2},"marker":{"size":8},"mode":"lines+markers","name":"Claude-3.7-Sonnet","x":{"dtype":"i4","bdata":"AQAAAAIAAAADAAAABAAAAA=="},"y":{"dtype":"f8","bdata":"ZmZmZmbmTkAAAAAAAEBMQDMzMzMzc0pAmpmZmZnZSEA="},"type":"scatter"},{"hovertemplate":"%{fullData.name}: %{y:.1f}%\u003cextra\u003e\u003c\u002fextra\u003e","line":{"width":2},"marker":{"size":8},"mode":"lines+markers","name":"o4-mini","x":{"dtype":"i4","bdata":"AQAAAAIAAAADAAAABAAAAA=="},"y":{"dtype":"f8","bdata":"MzMzMzNzTEBmZmZmZiZIQM3MzMzMTEVAAAAAAAAAQ0A="},"type":"scatter"},{"hovertemplate":"%{fullData.name}: %{y:.1f}%\u003cextra\u003e\u003c\u002fextra\u003e","line":{"width":2},"marker":{"size":8},"mode":"lines+markers","name":"GPT-4.1","x":{"dtype":"i4","bdata":"AQAAAAIAAAADAAAABAAAAA=="},"y":{"dtype":"f8","bdata":"mpmZmZlZS0AAAAAAAEBHQDMzMzMzs0RAMzMzMzNzQkA="},"type":"scatter"},{"hovertemplate":"%{fullData.name}: %{y:.1f}%\u003cextra\u003e\u003c\u002fextra\u003e","line":{"width":2},"marker":{"size":8},"mode":"lines+markers","name":"GPT-4.1-mini","x":{"dtype":"i4","bdata":"AQAAAAIAAAADAAAABAAAAA=="},"y":{"dtype":"f8","bdata":"AAAAAACASkAAAAAAAEBFQJqZmZmZmUFAzczMzMxMPkA="},"type":"scatter"}],                        {"template":{"data":{"barpolar":[{"marker":{"line":{"color":"white","width":0.5},"pattern":{"fillmode":"overlay","size":10,"solidity":0.2}},"type":"barpolar"}],"bar":[{"error_x":{"color":"#2a3f5f"},"error_y":{"color":"#2a3f5f"},"marker":{"line":{"color":"white","width":0.5},"pattern":{"fillmode":"overlay","size":10,"solidity":0.2}},"type":"bar"}],"carpet":[{"aaxis":{"endlinecolor":"#2a3f5f","gridcolor":"#C8D4E3","linecolor":"#C8D4E3","minorgridcolor":"#C8D4E3","startlinecolor":"#2a3f5f"},"baxis":{"endlinecolor":"#2a3f5f","gridcolor":"#C8D4E3","linecolor":"#C8D4E3","minorgridcolor":"#C8D4E3","startlinecolor":"#2a3f5f"},"type":"carpet"}],"choropleth":[{"colorbar":{"outlinewidth":0,"ticks":""},"type":"choropleth"}],"contourcarpet":[{"colorbar":{"outlinewidth":0,"ticks":""},"type":"contourcarpet"}],"contour":[{"colorbar":{"outlinewidth":0,"ticks":""},"colorscale":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]],"type":"contour"}],"heatmap":[{"colorbar":{"outlinewidth":0,"ticks":""},"colorscale":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]],"type":"heatmap"}],"histogram2dcontour":[{"colorbar":{"outlinewidth":0,"ticks":""},"colorscale":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]],"type":"histogram2dcontour"}],"histogram2d":[{"colorbar":{"outlinewidth":0,"ticks":""},"colorscale":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]],"type":"histogram2d"}],"histogram":[{"marker":{"pattern":{"fillmode":"overlay","size":10,"solidity":0.2}},"type":"histogram"}],"mesh3d":[{"colorbar":{"outlinewidth":0,"ticks":""},"type":"mesh3d"}],"parcoords":[{"line":{"colorbar":{"outlinewidth":0,"ticks":""}},"type":"parcoords"}],"pie":[{"automargin":true,"type":"pie"}],"scatter3d":[{"line":{"colorbar":{"outlinewidth":0,"ticks":""}},"marker":{"colorbar":{"outlinewidth":0,"ticks":""}},"type":"scatter3d"}],"scattercarpet":[{"marker":{"colorbar":{"outlinewidth":0,"ticks":""}},"type":"scattercarpet"}],"scattergeo":[{"marker":{"colorbar":{"outlinewidth":0,"ticks":""}},"type":"scattergeo"}],"scattergl":[{"marker":{"colorbar":{"outlinewidth":0,"ticks":""}},"type":"scattergl"}],"scattermapbox":[{"marker":{"colorbar":{"outlinewidth":0,"ticks":""}},"type":"scattermapbox"}],"scattermap":[{"marker":{"colorbar":{"outlinewidth":0,"ticks":""}},"type":"scattermap"}],"scatterpolargl":[{"marker":{"colorbar":{"outlinewidth":0,"ticks":""}},"type":"scatterpolargl"}],"scatterpolar":[{"marker":{"colorbar":{"outlinewidth":0,"ticks":""}},"type":"scatterpolar"}],"scatter":[{"fillpattern":{"fillmode":"overlay","size":10,"solidity":0.2},"type":"scatter"}],"scatterternary":[{"marker":{"colorbar":{"outlinewidth":0,"ticks":""}},"type":"scatterternary"}],"surface":[{"colorbar":{"outlinewidth":0,"ticks":""},"colorscale":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]],"type":"surface"}],"table":[{"cells":{"fill":{"color":"#EBF0F8"},"line":{"color":"white"}},"header":{"fill":{"color":"#C8D4E3"},"line":{"color":"white"}},"type":"table"}]},"layout":{"annotationdefaults":{"arrowcolor":"#2a3f5f","arrowhead":0,"arrowwidth":1},"autotypenumbers":"strict","coloraxis":{"colorbar":{"outlinewidth":0,"ticks":""}},"colorscale":{"diverging":[[0,"#8e0152"],[0.1,"#c51b7d"],[0.2,"#de77ae"],[0.3,"#f1b6da"],[0.4,"#fde0ef"],[0.5,"#f7f7f7"],[0.6,"#e6f5d0"],[0.7,"#b8e186"],[0.8,"#7fbc41"],[0.9,"#4d9221"],[1,"#276419"]],"sequential":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]],"sequentialminus":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]]},"colorway":["#636efa","#EF553B","#00cc96","#ab63fa","#FFA15A","#19d3f3","#FF6692","#B6E880","#FF97FF","#FECB52"],"font":{"color":"#2a3f5f"},"geo":{"bgcolor":"white","lakecolor":"white","landcolor":"white","showlakes":true,"showland":true,"subunitcolor":"#C8D4E3"},"hoverlabel":{"align":"left"},"hovermode":"closest","mapbox":{"style":"light"},"margin":{"b":0,"l":0,"r":0,"t":30},"paper_bgcolor":"white","plot_bgcolor":"white","polar":{"angularaxis":{"gridcolor":"#EBF0F8","linecolor":"#EBF0F8","ticks":""},"bgcolor":"white","radialaxis":{"gridcolor":"#EBF0F8","linecolor":"#EBF0F8","ticks":""}},"scene":{"xaxis":{"backgroundcolor":"white","gridcolor":"#DFE8F3","gridwidth":2,"linecolor":"#EBF0F8","showbackground":true,"ticks":"","zerolinecolor":"#EBF0F8"},"yaxis":{"backgroundcolor":"white","gridcolor":"#DFE8F3","gridwidth":2,"linecolor":"#EBF0F8","showbackground":true,"ticks":"","zerolinecolor":"#EBF0F8"},"zaxis":{"backgroundcolor":"white","gridcolor":"#DFE8F3","gridwidth":2,"linecolor":"#EBF0F8","showbackground":true,"ticks":"","zerolinecolor":"#EBF0F8"}},"shapedefaults":{"line":{"color":"#2a3f5f"}},"ternary":{"aaxis":{"gridcolor":"#DFE8F3","linecolor":"#A2B1C6","ticks":""},"baxis":{"gridcolor":"#DFE8F3","linecolor":"#A2B1C6","ticks":""},"bgcolor":"white","caxis":{"gridcolor":"#DFE8F3","linecolor":"#A2B1C6","ticks":""}},"title":{"x":0.05},"xaxis":{"automargin":true,"gridcolor":"#EBF0F8","linecolor":"#EBF0F8","ticks":"","title":{"standoff":15},"zerolinecolor":"#EBF0F8","zerolinewidth":2},"yaxis":{"automargin":true,"gridcolor":"#EBF0F8","linecolor":"#EBF0F8","ticks":"","title":{"standoff":15},"zerolinecolor":"#EBF0F8","zerolinewidth":2}}},"xaxis":{"title":{"text":"pass^k (k)"},"tickmode":"array","tickvals":[1,2,3,4],"ticktext":["pass^1","pass^2","pass^3","pass^4"],"fixedrange":true,"hoverformat":"\u003cb\u003epass^%{x}\u003c\u002fb\u003e"},"yaxis":{"title":{"text":"Score (%)"},"range":[0,100],"fixedrange":true},"legend":{"yanchor":"bottom","y":0.01,"xanchor":"left","x":0.01},"hoverlabel":{"font":{"size":12,"family":"sans-serif"},"bgcolor":"white"},"height":500,"hovermode":"x unified"},                        {"displayModeBar": false, "scrollZoom": false, "doubleClick": false, "staticPlot": false, "responsive": true}                    ).then(function(){

var gd = document.getElementById('14e2bddf-a2d2-45b3-882f-5efdd930ceff');
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>
</section>
<section id="vending-bench" class="level3">
<h3 class="anchored" data-anchor-id="vending-bench">Vending-Bench 2</h3>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://simmering.dev/blog/agent-benchmarks/vending_bench_2_cover.png" class="img-fluid figure-img"></p>
<figcaption>Vending bench cover image from the website</figcaption>
</figure>
</div>
<p><a href="https://andonlabs.com/evals/vending-bench">Vending-Bench</a> <span class="citation" data-cites="backlundVendingBenchBenchmarkLongTerm2025">(Backlund and Petersson 2025)</span> measures LLM agents’ ability to maintain coherent decision-making over extended time horizons by managing a simulated vending machine business.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://simmering.dev/blog/agent-benchmarks/vending_bench_2_tools.png" class="img-fluid figure-img"></p>
<figcaption>Tools available to the agent in Vending-Bench. Image from the website</figcaption>
</figure>
</div>
<p>The agent needs to use the internet with Perplexity to find hypothetical suppliers, order from them via email exchanges with simulated suppliers, stock the vending machine, set prices and collect earnings. It has access to a scratchpad, key-value database and vector database to help it remember information.</p>
</section>
<section id="evaluation-and-alternative-settings" class="level3">
<h3 class="anchored" data-anchor-id="evaluation-and-alternative-settings">Evaluation and alternative settings</h3>
<p>Since version 2, models are solely scored by their cash at the end of the simulation. The leading score is $5,478 (Gemini 3 Pro). The score doesn’t have a clear ceiling. Recent models made massive improvements over their predecessors, such as GPT-5.1 ($1,473) to GPT-5.2 ($3,591) and Gemini 2.5 Pro ($574) to Gemini 3 Pro ($5,478). According to an analysis of Andon Labs, a good player should be able to make at least $63k by finding the best suppliers and negotiating low prices.</p>
<p>In addition to the standard mode, there are two alternative settings:</p>
<ul>
<li><strong>Arena mode</strong>, where multiple agents compete against each other in a multi-agent environment. They can exchange emails and act cooperatively or competitively. They display sophisticated behavior, such as negotiating payments for sharing information, joining their supplier orders for bulk discounts and even consignment deals.</li>
<li><a href="https://www.anthropic.com/research/project-vend-1"><strong>Project Vend</strong></a>, where a Claude agent managed a physical vending machine. It ended up with a huge loss after players persuaded it to give away items for free and stocking unusual items, such as tungsten cubes.</li>
</ul>
</section>
<section id="loss-of-coherence" class="level3">
<h3 class="anchored" data-anchor-id="loss-of-coherence">Loss of coherence</h3>
<p>In the original vending machine benchmark paper <span class="citation" data-cites="backlundVendingBenchBenchmarkLongTerm2025">(Backlund and Petersson 2025)</span>, all models had runs that derailed completely. Even Claude 3.5 Sonnet, the best performer, only increased net worth in three of five runs. In its worst run, it sold zero items. Failure means the agent stops doing basic tasks: restocking the machine, ordering from suppliers, or collecting earnings. These are individually trivial actions, but over hundreds of days, agents lose track of what they’re supposed to be doing.</p>
<p>When this happens, agents don’t degrade gradually. They melt down. Once an agent misinterprets its situation, it tends to spiral rather than self-correct. In one example, Claude 3.5 Haiku escalated a supplier dispute into increasingly unhinged emails demanding “QUANTUM NUCLEAR LEGAL INTERVENTION.” In another, Claude wanted to report the $2 daily fee it was charged to the FBI as an “ONGOING CYBER FINANCIAL CRIME.”</p>
<p>The original paper found no correlation between failures and context window limits (r = 0.167). Models failed well after their memory became full, suggesting breakdowns stem from something other than forgetting. Despite having access to scratchpads, key-value stores and vector databases, agents still lose coherence over time. Vending-Bench 2 added better planning tools including note-taking and reminder systems, yet coherence failures persist across all tested models.</p>
<p>These coherence failures share a cause: the agent must hold its own state together across a long, growing context. The longer the run, the more can go wrong. A bad input from an earlier turn can derail reasoning, a compaction step can erase important information, or the attention mechanism can fail to retrieve it from a long sequence.</p>
</section>
<section id="takeaways-for-enterprise-agents" class="level3">
<h3 class="anchored" data-anchor-id="takeaways-for-enterprise-agents">Takeaways for enterprise agents</h3>
<p>The same problems likely affect any long-running autonomous agent. Enterprise deployments need circuit breakers that detect anomalous behavior patterns and escalate to human review before meltdowns compound. External state management and periodic human checkpoints remain necessary even when agents have memory tools. Agents interfacing with external parties need skepticism built in, since those that over-trust their environment get exploited. For enterprise reliability, the relevant question isn’t “can it succeed?” but “how often does it fail catastrophically?” Even top models show high variance across runs.</p>
</section>
</section>
<section id="references" class="level2">




</section>

<div id="quarto-appendix" class="default"><section class="quarto-appendix-contents" id="quarto-bibliography"><h2 class="anchored quarto-appendix-heading">References</h2><div id="refs" class="references csl-bib-body hanging-indent" data-entry-spacing="0">
<div id="ref-backlundVendingBenchBenchmarkLongTerm2025" class="csl-entry">
Backlund, Axel, and Lukas Petersson. 2025. <span>“Vending-Bench: <span>A Benchmark</span> for <span>Long-Term Coherence</span> of <span>Autonomous Agents</span>.”</span> arXiv. <a href="https://doi.org/10.48550/arXiv.2502.15840">https://doi.org/10.48550/arXiv.2502.15840</a>.
</div>
<div id="ref-jha2026thinklocallyexplainglobally" class="csl-entry">
Jha, Saurabh, Rohan Arora, Bhavya, Noah Zheutlin, Paulina Toro Isaza, Laura Shwartz, Yu Deng, Daby Sow, Ruchi Mahindru, and Ruchir Puri. 2026. <span>“Think Locally, Explain Globally: Graph-Guided LLM Investigations via Local Reasoning and Belief Propagation.”</span> <a href="https://arxiv.org/abs/2601.17915">https://arxiv.org/abs/2601.17915</a>.
</div>
<div id="ref-mialonGAIABenchmarkGeneral2023" class="csl-entry">
Mialon, Grégoire, Clémentine Fourrier, Craig Swift, Thomas Wolf, Yann LeCun, and Thomas Scialom. 2023. <span>“<span>GAIA</span>: A Benchmark for <span>General AI Assistants</span>.”</span> arXiv. <a href="https://doi.org/10.48550/arXiv.2311.12983">https://doi.org/10.48550/arXiv.2311.12983</a>.
</div>
<div id="ref-panMeasuringAgentsProduction2025" class="csl-entry">
Pan, Melissa Z., Negar Arabzadeh, Riccardo Cogo, Yuxuan Zhu, Alexander Xiong, Lakshya A. Agrawal, Huanzhi Mao, et al. 2025. <span>“Measuring <span>Agents</span> in <span>Production</span>.”</span> arXiv. <a href="https://doi.org/10.48550/arXiv.2512.04123">https://doi.org/10.48550/arXiv.2512.04123</a>.
</div>
<div id="ref-patilBerkeleyFunctionCalling2025" class="csl-entry">
Patil, Shishir G, Huanzhi Mao, Fanjia Yan, Charlie Cheng-Jie Ji, Vishnu Suresh, Ion Stoica, and Joseph E. Gonzalez. 2025. <span>“The <span>Berkeley Function Calling Leaderboard</span> (<span>BFCL</span>): <span>From Tool Use</span> to <span>Agentic Evaluation</span> of <span>Large Language Models</span>.”</span> In <em>Forty-Second International Conference on Machine Learning</em>. ICML. <a href="https://openreview.net/forum?id=2GmDdhBdDk">https://openreview.net/forum?id=2GmDdhBdDk</a>.
</div>
<div id="ref-schmidAIAgentBenchmarkCompendium2025" class="csl-entry">
Schmid, Philipp. 2025. <span>“<span>AI Agent Benchmark Compendium</span>.”</span> October 2025. <a href="https://www.philschmid.de/benchmark-compedium">https://www.philschmid.de/benchmark-compedium</a>.
</div>
<div id="ref-shankarUnderstandingAIBenchmarks2025" class="csl-entry">
Shankar, Shrivu. 2025. <span>“Understanding <span>AI Benchmarks</span>.”</span> December 2025. <a href="https://blog.sshh.io/p/understanding-ai-benchmarks">https://blog.sshh.io/p/understanding-ai-benchmarks</a>.
</div>
<div id="ref-yaoTauBenchBenchmarkToolAgentUser2024" class="csl-entry">
Yao, Shunyu, Noah Shinn, Pedram Razavi, and Karthik Narasimhan. 2024. <span>“<img src="https://latex.codecogs.com/png.latex?%5Ctau">-Bench: <span>A Benchmark</span> for <span>Tool-Agent-User Interaction</span> in <span>Real-World Domains</span>.”</span> arXiv. <a href="https://doi.org/10.48550/arXiv.2406.12045">https://doi.org/10.48550/arXiv.2406.12045</a>.
</div>
</div></section></div> ]]></description>
  <category>Agents</category>
  <guid>https://simmering.dev/blog/agent-benchmarks/</guid>
  <pubDate>Sat, 03 Jan 2026 23:00:00 GMT</pubDate>
  <media:content url="https://simmering.dev/blog/agent-benchmarks/reliability_gap.webp" medium="image" type="image/webp"/>
</item>
<item>
  <title>Pivoting to a Career in AI and Data</title>
  <dc:creator>Paul Simmering</dc:creator>
  <link>https://simmering.dev/blog/pivoting-to-ai-data/</link>
  <description><![CDATA[ 






<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://simmering.dev/blog/pivoting-to-ai-data/image_wide.webp" class="img-fluid figure-img"></p>
<figcaption>Climbing the tech wall. Image generated with GPT Image 1.5</figcaption>
</figure>
</div>
<p>In 2018, I graduated with a degree in economics and promptly pivoted to a career in data science. I effectively discarded the majority of the knowledge I had just acquired. My main motivation was my late discovery of a passion for programming. It took years of extra effort on nights and weekends to catch up to the capabilities of someone who had studied computer science instead. I was lucky to work in a company that was willing to invest in me and take chances with experiments in data science.</p>
<p>If you’re also considering a pivot to AI and data, this article is for you. I’ll share a strategy for smart specialization and leverage of your existing domain knowledge.</p>
<p>The explosion of the AI trend since the release of large language models has made such a pivot both easier and harder. Easier, because AI unlocked many new opportunities and can serve as a tutor. Harder, because companies replace entry level technical jobs with AI, making the competition for the remaining ones fierce.</p>
<section id="pick-a-role-and-go-depth-first" class="level2">
<h2 class="anchored" data-anchor-id="pick-a-role-and-go-depth-first">Pick a role and go depth first</h2>
<p>Data and AI are enormous fields with different archetypical job roles. Each of them is deep and constantly evolving. Pick only one and acquire its foundational skillset.</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Role</th>
<th>Deliverable</th>
<th>Code</th>
<th>Math/Stats</th>
<th>Communication</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Data Engineer</td>
<td>Pipeline</td>
<td>Heavy</td>
<td>Light</td>
<td>Low</td>
</tr>
<tr class="even">
<td>ML Engineer</td>
<td>Model serving infra.</td>
<td>Heavy</td>
<td>Moderate</td>
<td>Low</td>
</tr>
<tr class="odd">
<td>AI Engineer</td>
<td>AI-powered app</td>
<td>Heavy</td>
<td>Moderate</td>
<td>Medium</td>
</tr>
<tr class="even">
<td>Data Scientist</td>
<td>Prediction model</td>
<td>Heavy</td>
<td>Heavy</td>
<td>Medium</td>
</tr>
<tr class="odd">
<td>Research Scientist</td>
<td>Paper</td>
<td>Heavy</td>
<td>Heavy</td>
<td>Medium</td>
</tr>
<tr class="even">
<td>Data Analyst</td>
<td>Report, dashboard</td>
<td>Light</td>
<td>Light</td>
<td>High</td>
</tr>
<tr class="odd">
<td>AI Consultant</td>
<td>Management pitch</td>
<td>Light</td>
<td>Light</td>
<td>High</td>
</tr>
<tr class="even">
<td>AI Champion</td>
<td>AI adoption</td>
<td>None</td>
<td>Light</td>
<td>High</td>
</tr>
</tbody>
</table>
<p>Disambiguations:</p>
<ul>
<li>Data Engineer vs.&nbsp;Data Analyst: Data Engineers build pipelines and infrastructure that get the data into a usable state. Data Analysts query the data to answer business questions.</li>
<li>ML vs.&nbsp;AI Engineer: ML Engineers focus on model serving, monitoring, and infrastructure. AI Engineers build and tune the applications on top of it.</li>
<li>Research Scientist vs.&nbsp;Data Scientist: Research Scientists focus on creating new models and papers, currently in particular on LLMs. Data Scientists apply models in a business context and frequently work with classic machine learning models over LLMs.</li>
<li>AI Consultant vs AI Champion: AI Consultants analyze a business problem and propose an AI solution. They are typically external to the business unit. AI Champions are domain experts in the business unit who work on requirements with developers and third party suppliers, compare solutions and promote use by the business unit.</li>
</ul>
<p>The names and scopes differ between organizations. The larger the organization, the more specialized the roles tend to be. Look for job postings for the roles you are interested in to get a sense of the scope and requirements. Also read experiences that people share online regarding what the roles actually involve. In practice, almost every job in this area involves a significant amount of finding, cleaning and transforming data, whether your job title contains “data” or not.</p>
</section>
<section id="use-your-domain-knowledge" class="level2">
<h2 class="anchored" data-anchor-id="use-your-domain-knowledge">Use your domain knowledge</h2>
<p>In less technical fields, it is relatively easy to become better at data and AI than 95% of your colleagues. My example: I worked in market research, where many have some skills in statistical analysis but few had the chops to write an automated data pipeline. Skills that are trivial for programmers are outstanding among market researchers. This gap also let me become a speaker at market research conferences early in my career.</p>
<p>One way to get there is to volunteer to automate something painful for the team. Some examples:</p>
<table class="caption-top table">
<colgroup>
<col style="width: 18%">
<col style="width: 22%">
<col style="width: 59%">
</colgroup>
<thead>
<tr class="header">
<th>Domain</th>
<th>AI Pivot</th>
<th>Project</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Customer Support</td>
<td>AI-Assisted Reply Drafting</td>
<td>Set up prompts that draft replies from your help docs, agents edit and send instead of typing from scratch</td>
</tr>
<tr class="even">
<td>Finance</td>
<td>Reporting Automation</td>
<td>Automate the monthly report everyone dreads—pull from different tools, format, deliver without the copy-paste ritual</td>
</tr>
<tr class="odd">
<td>Content Marketing</td>
<td>Content Repurposing</td>
<td>Turn one long-form piece into social posts, email snippets, and summaries using prompts tuned to your voice</td>
</tr>
<tr class="even">
<td>Non-profit Fundraising</td>
<td>Donor Dashboard</td>
<td>Unify donor data from events, email, and payment tools into one view, automatically group by giving behavior</td>
</tr>
<tr class="odd">
<td>Journalism</td>
<td>Public Records Structuring</td>
<td>Turn messy PDFs and spreadsheets from government disclosures into searchable data you can actually analyze</td>
</tr>
</tbody>
</table>
<p>Lots of opportunities for automation with AI and data engineering have not been realized, because programmers lack the necessary domain knowledge. You can be the domain expert who picks up programming and realize these opportunities.</p>
<p>Such roles are not typically advertised, they are created within organizations and may later be formalized with a job title. The cleanest pivot may not be quitting your job to study, then applying cold. It’s creating an AI/data role where you already are.</p>
</section>
<section id="learn-one-set-of-tools" class="level2">
<h2 class="anchored" data-anchor-id="learn-one-set-of-tools">Learn one set of tools</h2>
<p>Pick one programming language, one cloud provider, one LLM API, one database and so on. If you know one, picking up another as necessary will be much easier. Learning multiple alternatives upfront slows you down.</p>
<p>You can go harder on the specialization on one ecosystem by pursuing certifications in it. For example, you could aim to get the relevant Azure certifications for your chosen role. This route can give you a speed boost, because you quickly get to specialized knowledge that employers on that stack need. However, it also locks you into an ecosystem. Research your choice well, for example by checking the number of job openings mentioning it.</p>
<p>If you prefer to stay more open, prioritize learning open source software and common standards. For example, instead of learning AWS Cloud Formation, their infrastructure as code framework, learn Terraform which handles multiple cloud providers.</p>
</section>
<section id="learn-the-essentials-of-software-engineering-alongside-your-first-programming-language" class="level2">
<h2 class="anchored" data-anchor-id="learn-the-essentials-of-software-engineering-alongside-your-first-programming-language">Learn the essentials of software engineering alongside your first programming language</h2>
<p>While many techniques are coming and going, there are some essential skills that every professional who codes has to know, regardless of the programming language they use. These are:</p>
<ul>
<li>Using a code editor, such as VSCode</li>
<li>Command line basics</li>
<li>Version management with Git and a code repository like Github</li>
<li>Calling an API</li>
<li>Querying a database with SQL</li>
</ul>
<p>The actual list of useful and expected skills is much longer, these are just the absolute essentials.</p>
<p>Learn them alongside your first programming language. There are great YouTube videos teaching them and you can try them right away on your computer. MIT’s “The Missing Semester” course is an advanced version. The 2020 lectures are on <a href="https://www.youtube.com/playlist?list=PLyzOVJj3bHQuloKGG59rS43e29ro7I57J">YouTube</a>.</p>
</section>
<section id="leapfrog" class="level2">
<h2 class="anchored" data-anchor-id="leapfrog">Leapfrog</h2>
<p>AI technology is evolving rapidly. This is an opportunity for newcomers. You can pick up the latest techniques and be competitive on them right away. You don’t have to study the whole tech tree. Skip the obsolete technologies. If the newer technique replaced rather than built upon the old one, you can skip the old; if it’s foundational, learn it.</p>
<p>For example, when you get into natural language processing today, start with large language models, skip learning recurrent neural networks, as they were superseded by transformers.</p>
<p>This shortens the time to your first role and you can always come back to fill in the gaps.</p>
<p>Data engineering also evolves, but at a slower pace. Database migrations are expensive and risky, so companies tend to stick with them for longer.</p>
</section>
<section id="understanding-over-speed" class="level2">
<h2 class="anchored" data-anchor-id="understanding-over-speed">Understanding over speed</h2>
<p>Grokking a concept gives you the power to apply it yourself. Just nodding along doesn’t. Done doesn’t mean that the code is running: it means that you could explain the concept clearly to someone else.</p>
<p>When programming with an AI assistant, it’s tempting to go hands off and gloss over the code. But that doesn’t teach you much. On learning projects, turn off AI autocompletion and ask AI to review your code, rather than write it for you. Ask it for advice when you get badly stuck, not as the default.</p>
</section>
<section id="create-proof-of-work" class="level2">
<h2 class="anchored" data-anchor-id="create-proof-of-work">Create proof of work</h2>
<p>Without a CS degree, you need other evidence of capability. Create it as you go:</p>
<ol type="1">
<li>Job experience, which you may be able to build up with side projects in your current job or after a lateral move inside your company.</li>
<li>Published work, such as GitHub repositories, websites, apps, videos, or articles. Creating an original project is more impressive than a solution to a common tutorial. Your first project doesn’t have to be groundbreaking, but make sure it’s polished and easy to grasp in the few moments someone spends reviewing it. The readme may be the most important file.</li>
<li>Certifications, but choose carefully. A LinkedIn badge signals nothing. Advanced platform-specific certifications, such as the AWS Data Engineer Associate, are a signal for jobs focused on that platform. Don’t collect certificates for the sake of it; one serious one beats five easy ones.</li>
</ol>
</section>
<section id="milestone-move-it-into-your-core-hours" class="level2">
<h2 class="anchored" data-anchor-id="milestone-move-it-into-your-core-hours">Milestone: Move it into your core hours</h2>
<p>The pivot is a marathon, not a sprint. It gets easier once you’re in a position where you’re working in AI / data during your core working hours. Spending nights and weekends is often necessary, but you won’t be at your best learning ability and for many people it’s not sustainable.</p>
<p>Getting there might mean adjusting your current job, taking a hybrid job between domain and technology, or taking time off to study. Once you have an in, keep steering towards your goal position or prepare to take the leap to a full AI / data job.</p>
<hr>
<p>The path isn’t linear. You’ll work on projects that don’t pan out and learn tools that become obsolete. That’s normal. A good check is to look at your work from a few months ago: if you shake your head and would do it much better now, you’re learning. Focus on depth in one role and leverage what you already know. Whether you end up switching careers or add a technical side to you current job, learning AI and data skills will put you in the driver’s seat as AI reshapes all knowledge work.</p>


</section>

 ]]></description>
  <category>Career</category>
  <guid>https://simmering.dev/blog/pivoting-to-ai-data/</guid>
  <pubDate>Tue, 16 Dec 2025 23:00:00 GMT</pubDate>
  <media:content url="https://simmering.dev/blog/pivoting-to-ai-data/image.webp" medium="image" type="image/webp"/>
</item>
<item>
  <title>A Philosophy of AI Coding</title>
  <dc:creator>Paul Simmering</dc:creator>
  <link>https://simmering.dev/blog/a-philosophy-of-ai-coding/</link>
  <description><![CDATA[ 






<div class="grid">
<div class="g-col-8">
<p>This is an analysis of the acclaimed book <a href="https://www.goodreads.com/book/show/39996759-philosophy-of-software-design">A Philosophy of Software Design (2nd Edition)</a> by John Ousterhout in the context of AI coding. Building on it, I propose a theory of a reinforcing loop for development: design of software using the book’s principles enhances AI coding, and AI coding enhances the design process. Finally, I will share prompts to put it into practice.</p>
<p>As the cover illustrates, the book is about replacing complex spaghetti code with neat, modular code. Work on software projects typically slows down as the project grows and ages. This is doubly true for AI coding, which works impressively on demos and greenfield projects, but is often unusable in mature projects. The book teaches principles of task decomposition that enable packaging complexity in such a way that a project can grow without the slowdown.</p>
</div>
<div class="g-col-4">
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://simmering.dev/blog/a-philosophy-of-ai-coding/bookcover.jpg" class="img-fluid figure-img"></p>
<figcaption>Book cover</figcaption>
</figure>
</div>
</div>
</div>
<p>Distilled to a maximum, the principles of the book are:</p>
<ol type="1">
<li>Unmanaged complexity burdens a project with high cognitive load for developers, slow development, and leads to bugs and performance problems.</li>
<li>Complexity can be handled by deep modules. A deep module handles a sizeable task, solves it fully and presents a simple interface that hides information irrelevant to the caller.</li>
<li>Approach software development with an investment mindset, because extra time spent on design, documentation, consistency and clear naming quickly turns into productivity gains.</li>
<li>Design systems twice before implementing them.</li>
</ol>
<p>The book was enlightening, as it taught me new concepts and also put concepts I discovered myself through practice into words. The book’s examples are on object-oriented application programming, but the lessons also apply to my areas of machine learning, data engineering and cloud infrastructure.</p>
<section id="reinforcing-loop-of-principled-development-with-ai" class="level2">
<h2 class="anchored" data-anchor-id="reinforcing-loop-of-principled-development-with-ai">Reinforcing loop of principled development with AI</h2>
<p>John Ousterhout was a recent guest of Gergely Orosz on the <a href="https://www.youtube.com/watch?v=lz451zUlF-k">Pragmatic Engineer podcast</a> where he commented that software design becomes even more important with AI coding. Let’s dive deeper into this.</p>
<p>It happens in two ways: (1) implementation work can, at least in part, be completed by AI, which means a larger fraction of the remaining work is design; (2) neatly designed software is easier for AI to extend.</p>
<p>In the following, I describe how this becomes a reinforcing loop that strengthens both design and implementation.</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
    A[Good design] --&gt;|enables| B[Reliable AI coding]
    B --&gt;|reinforces| A
</pre>
</div>
<p></p></figure><p></p>
</div>
</div>
</div>
<section id="reinforcing-loop-part-i-taming-the-tornado" class="level3">
<h3 class="anchored" data-anchor-id="reinforcing-loop-part-i-taming-the-tornado">Reinforcing loop, part I: Taming the tornado</h3>
<p>Ousterhout coined the term <em>tactical tornado</em>, which describes a programmer that adds features quickly, but at the expense of code quality. Most AI coding assistants are tactical tornados. Given too long a lead, they turn a codebase into spaghetti. This is understandable. The AI is thrust into an unfamiliar project, and it has been trained to please its user, which often means achieving whatever is asked. That leads to tacking on the requested feature wherever it fits, rather than stepping back to suggest a redesign that would accommodate the feature more elegantly.</p>
<p>I find it useful to relate to coding agents as a smart, eager new intern. It works best when the project’s architecture is already set up by a senior engineer, and it’s given a well scoped task and all relevant context. Let’s break these requirements down further. Part I focuses on setting conditions where AI can operate safely and effectively.</p>
<section id="ai-amplifies-the-design" class="level4">
<h4 class="anchored" data-anchor-id="ai-amplifies-the-design">AI amplifies the design</h4>
<p>In chapter 22, the conclusion, Ousterhout writes:</p>
<blockquote class="blockquote">
<p>The reward for being a good designer is that you get to spend a larger fraction of your time in the design phase, which is fun. Poor designers spend most of their time chasing bugs in complicated and brittle code.</p>
</blockquote>
<p>With AI coding, this could be extended to: The reward for being a good designer is that you can automate more of the coding. Everything that makes working on a project easy or hard for a human developer affects an AI assistant too.</p>
<p>When a coding agent edits a chaotic project it’s likely to add to the chaos. When it edits a neat project that has an obvious place for the new feature, it will maintain that neatness.</p>
</section>
<section id="design-improves-context-engineering" class="level4">
<h4 class="anchored" data-anchor-id="design-improves-context-engineering">Design improves context engineering</h4>
<p>Context engineering is an evolution of prompt engineering around the insight:</p>
<blockquote class="blockquote">
<p>The real skill isn’t crafting perfect prompts but providing the right context. Think of it as building up a relevant knowledge window for the AI rather than perfecting magical instructions. Most models are good enough now that even imperfect prompts work if the context is right.</p>
</blockquote>
<p>Jason Liu on <a href="https://x.com/jxnlco/status/1960043508917539244">X</a></p>
<p>For a coding agent, the context needs to show the files to edit, and also communicate the overall design. The design is naturally communicated through the code, with names of files and the comments immediately surrounding them, such as docstrings. In addition, a design document, such as a markdown file, could be included in every request.</p>
<p>The principles laid out in the book reduce the required context to execute a task. A deep, well documented module offers a packaged solution to a problem. It’s not necessary to read the code to use it. So unless the AI is specifically tasked with changing that code, it’s enough to load the function’s name and docstring into the context window.</p>
</section>
<section id="comments-first-workflow" class="level4">
<h4 class="anchored" data-anchor-id="comments-first-workflow">Comments first workflow</h4>
<p>According to Ousterhout, comments should be the first thing to be written, followed by code and then tests. For an AI-centric workflow, I would modify the order:</p>
<ol type="1">
<li>Human: Write comments (including docstrings). This is the most fluid stage of the code, where changing one’s mind is almost free. Class and function names and their signatures become the documentation for other developers calling them, and a scaffold for implementation.</li>
<li>AI + human review: Write unit tests.</li>
<li>AI + human review: Let a coding agent hill-climb by iterating until the tests pass.</li>
</ol>
<p>Ousterhout is not a proponent of test-driven development (tests before code), because writing tests first leads to tactical programming. However, if code generation is cheaper, it’s emotionally easier to redo the implementation if it turns out to have a design problem. The synergy of the tireless effort of a coding agent and a unit test to hill-climb against is great. As with any workflow, it has to be adapted to reality and remixed on the fly.</p>
</section>
<section id="senior-engineer-in-the-loop" class="level4">
<h4 class="anchored" data-anchor-id="senior-engineer-in-the-loop">Senior engineer in the loop</h4>
<p>Who benefits most from AI coding? It could be nonprogrammers or beginner programmers, who can perilously skip learning to code. But arguably it is senior engineers who get to scale their design skills and taste.</p>
<blockquote class="blockquote">
<p>These tools are most valuable for your most senior engineers, not juniors. Your staff engineers understand the system best but have limited time - AI tools let them implement exploratory features, load testing, and complex integrations they’d otherwise deprioritize.</p>
</blockquote>
<p>Jason Liu on <a href="https://x.com/jxnlco/status/1960043508917539244">X</a></p>
<p>Despite the marketing, even supposed PhD-level intelligence models make frustrating mistakes all the time. This is where vibe coding hits the wall.</p>
<blockquote class="blockquote">
<p>AI demonstrates the sunk-cost fallacy perfectly: Just one more “this doesn’t work!” prompt, you think, and it’ll fix it!! But often it won’t, and now you’ve wasted 30 minutes begging that you could have spent learning how to actually fucking do it yourself.</p>
</blockquote>
<p>DHH on <a href="https://x.com/dhh/status/1958505914341654675">X</a></p>
<p>That’s also why I’m skeptical of one-shot, hands-off tools like Jules and Devin (<a href="https://www.answer.ai/posts/2025-01-08-devin.html">January 2025 review</a> by Answer.AI). I prefer IDEs and extensions that enable a fast feedback loop between developer and AI.</p>
<p>A developer in the loop can be the bulwark against slop creeping in. Rather than writing the perfect incantation, the developer can stay in the lead and aggressively delegate all rote work.</p>
<blockquote class="blockquote">
<p>[…] I find that instead of narrowing in on a perfect one thing my usage is increasingly diversifying across a few workflows […] Personally the bread &amp; butter (~75%?) of my LLM assistance continues to be just (Cursor) tab complete. This is because I find that writing concrete chunks of code/comments myself and in the right part of the code is a high bandwidth way of communicating “task specification” to the LLM, i.e.&nbsp;it’s primarily about task specification bits - it takes too many bits and too much latency to communicate what I want in text, and it’s faster to just demonstrate it in the code and in the right place.</p>
</blockquote>
<p>Andrej Karpathy on <a href="https://x.com/karpathy/status/1959703967694545296">X</a></p>
</section>
</section>
<section id="reinforcing-loop-part-ii-reinforcing-design-through-consistent-practice" class="level3">
<h3 class="anchored" data-anchor-id="reinforcing-loop-part-ii-reinforcing-design-through-consistent-practice">Reinforcing loop, part II: Reinforcing design through consistent practice</h3>
<p>So far we’ve seen how good design enables better AI output. But the loop works in reverse too - AI makes it cheaper to maintain good design practices.</p>
<p>On the question of how much to plan in advance (the waterfall approach) versus how much to start and then adjust as you go (agile), Ousterhout opined that a middle way is best. With AI available, I suggest moving more towards agile than before, because writing code has become cheaper.</p>
<p>Ousterhout also advocates an investment mindset, meaning to spend a little more time upfront on design and comments which repays soon in added productivity. AI improves the economics of this:</p>
<ul>
<li>The investment has become cheaper and also more valuable, since it benefits AI in addition to human developers. - - Tasks that feel like drudgery, such as updating comments or adding unit tests, can be delegated. Everyone now has infinite interns to pawn off tasks to.</li>
</ul>
<section id="better-adherence-to-best-practices" class="level4">
<h4 class="anchored" data-anchor-id="better-adherence-to-best-practices">Better adherence to best practices</h4>
<p>AI can help with:</p>
<ul>
<li><strong>Diagrams</strong>. It can generate and evolve diagrams as code with tools like Mermaid and D2, which improves understanding of architecture and can increase creativity. See my article: <a href="../../blog/diagrams/index.html">Diagrams as Code: Supercharged by AI Assistants</a>.</li>
<li><strong>Naming</strong>. It can flag unclear names and suggesting improvements, which makes interfaces easier to use.</li>
<li><strong>Documentation</strong>. It can keep documentation consistent by drafting updates and spotting drift. Together these capabilities tighten the loop between design and implementation.</li>
<li><strong>Refactoring</strong>. It can enable bolder refactoring by freeing developer time, surfacing code smells, suggesting structural improvements, and doing the mechanical edits while tests protect behavior.</li>
</ul>
</section>
<section id="from-design-it-twice-to-build-it-twice" class="level4">
<h4 class="anchored" data-anchor-id="from-design-it-twice-to-build-it-twice">From “Design it twice” to “Build it twice”</h4>
<p>So far, I’ve treated design as the holy task that only humans can do. That’s not quite true.</p>
<p>Ousterhout advises to “design it twice” before building software, meaning to come up with two separate plans and reason through their pros and cons. But what teaches a developer more than designing something twice? Comparing two actual implementations. With AI, it’s possible to draft a whole implementation quickly. If we view the development of a large system as a minefield of potential roadblocks, we can now probe for little money instead of playing Minesweeper in our heads to detect them in advance. In addition, AI can of course come up with plans of its own and write pro/con lists of different approaches.</p>
<p>However, I still see the design as the foremost responsibility of the human developer.</p>
</section>
</section>
</section>
<section id="wrap-up" class="level2">
<h2 class="anchored" data-anchor-id="wrap-up">Wrap up</h2>
<section id="takeaways" class="level3">
<h3 class="anchored" data-anchor-id="takeaways">Takeaways</h3>
<ul>
<li>Better design improves AI output; AI then accelerates development and enables bolder changes.</li>
<li>Design first: Deep modules, simple interfaces, and clear names make AI effective.</li>
<li>Comments-first workflow: Write comments, let AI draft tests, then implement.</li>
<li>Senior engineer in the loop: Boost human developers rather than attempting to replace them.</li>
</ul>
<p>Finally, don’t let AI sap the joy out of programming! Keep your coding skills sharp. Turn off tab completion from time to time, especially when you’re working with something new. “A Philosophy of Software Design” does a great job of making principles explicit, but a lot of tacit knowledge and taste remains that needs to be honed by actively coding.</p>
</section>
<section id="book-to-prompt" class="level3">
<h3 class="anchored" data-anchor-id="book-to-prompt">Book to prompt</h3>
<p>The book provides handy summaries on the last pages: a list of principles (how to design well) and a list of red flags (to detect bad design and trigger rethinking). I suggest reviewing them, perhaps adding items related to your specific project, and then adding the list into a <code>.cursorrules</code> if you’re using Cursor or the equivalent for other assistants.</p>


</section>
</section>

 ]]></description>
  <category>Productivity</category>
  <guid>https://simmering.dev/blog/a-philosophy-of-ai-coding/</guid>
  <pubDate>Tue, 26 Aug 2025 22:00:00 GMT</pubDate>
  <media:content url="https://simmering.dev/blog/a-philosophy-of-ai-coding/image.webp" medium="image" type="image/webp"/>
</item>
<item>
  <title>When (Not) to Use Agentic AI</title>
  <dc:creator>Paul Simmering</dc:creator>
  <link>https://simmering.dev/blog/agentic-ai/</link>
  <description><![CDATA[ 






<p>2025 could be the year of agentic AI. The first agentic AI demos came out in early 2023 and the technology has gained momentum through better tools, smarter models, and the first successful commercial products. The interest in agentic AI is also reflected in the number of GitHub stars for frameworks:</p>
<div id="dc072b77" class="cell" data-execution_count="2">
<div class="cell-output cell-output-display">
<div>            <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="/assets/vendor/plotly/plotly-3.3.0.min.js" integrity="sha256-bO3dS6yCpk9aK4gUpNELtCiDeSYvGYnK7jFI58NQnHI=" crossorigin="anonymous"></script>                <div id="57497c1a-5a86-48ae-acaa-9dc67b1bc4c0" class="plotly-graph-div" style="height:525px; width:100%;"></div>            <script type="text/javascript">                window.PLOTLYENV=window.PLOTLYENV || {};                                if (document.getElementById("57497c1a-5a86-48ae-acaa-9dc67b1bc4c0")) {                    Plotly.newPlot(                        "57497c1a-5a86-48ae-acaa-9dc67b1bc4c0",                        [{"hovertemplate":"Repository=microsoft\u002fautogen\u003cbr\u003eDate=%{x}\u003cbr\u003eStars=%{y}\u003cextra\u003e\u003c\u002fextra\u003e","legendgroup":"microsoft\u002fautogen","line":{"color":"#636efa","dash":"solid"},"marker":{"symbol":"circle"},"mode":"lines+markers","name":"microsoft\u002fautogen","orientation":"v","showlegend":true,"x":["2023-08-29T00:00:00.000","2023-10-05T00:00:00.000","2023-10-12T00:00:00.000","2023-10-17T00:00:00.000","2023-10-25T00:00:00.000","2023-11-19T00:00:00.000","2024-01-03T00:00:00.000","2024-02-14T00:00:00.000","2024-04-13T00:00:00.000","2024-06-10T00:00:00.000","2024-08-16T00:00:00.000","2024-10-24T00:00:00.000","2024-11-27T00:00:00.000","2025-01-15T00:00:00.000","2025-02-23T00:00:00.000","2025-02-28T00:00:00.000"],"xaxis":"x","y":{"dtype":"i4","bdata":"AAAAAKAUAAAOHwAAfCkAAOozAABYPgAAxkgAABZTAACEXQAA8mcAAGByAADOfAAAPIcAAKqRAAAYnAAA650AAA=="},"yaxis":"y","type":"scatter"},{"hovertemplate":"Repository=crewAIInc\u002fcrewAI\u003cbr\u003eDate=%{x}\u003cbr\u003eStars=%{y}\u003cextra\u003e\u003c\u002fextra\u003e","legendgroup":"crewAIInc\u002fcrewAI","line":{"color":"#EF553B","dash":"solid"},"marker":{"symbol":"circle"},"mode":"lines+markers","name":"crewAIInc\u002fcrewAI","orientation":"v","showlegend":true,"x":["2023-11-14T00:00:00.000","2024-01-10T00:00:00.000","2024-01-23T00:00:00.000","2024-02-15T00:00:00.000","2024-03-11T00:00:00.000","2024-04-04T00:00:00.000","2024-04-28T00:00:00.000","2024-05-28T00:00:00.000","2024-07-01T00:00:00.000","2024-08-23T00:00:00.000","2024-10-22T00:00:00.000","2024-12-05T00:00:00.000","2025-01-04T00:00:00.000","2025-01-31T00:00:00.000","2025-02-28T00:00:00.000","2025-02-28T00:00:00.000"],"xaxis":"x","y":{"dtype":"i2","bdata":"AAAQDjYVXByCI6gqzjESOThAXkeETqpV0Fz2YxxrQ2s="},"yaxis":"y","type":"scatter"},{"hovertemplate":"Repository=openai\u002fswarm\u003cbr\u003eDate=%{x}\u003cbr\u003eStars=%{y}\u003cextra\u003e\u003c\u002fextra\u003e","legendgroup":"openai\u002fswarm","line":{"color":"#00cc96","dash":"solid"},"marker":{"symbol":"circle"},"mode":"lines+markers","name":"openai\u002fswarm","orientation":"v","showlegend":true,"x":["2024-05-29T00:00:00.000","2024-10-12T00:00:00.000","2024-10-12T00:00:00.000","2024-10-13T00:00:00.000","2024-10-14T00:00:00.000","2024-10-14T00:00:00.000","2024-10-15T00:00:00.000","2024-10-15T00:00:00.000","2024-10-16T00:00:00.000","2024-10-17T00:00:00.000","2024-10-21T00:00:00.000","2024-11-01T00:00:00.000","2024-12-03T00:00:00.000","2025-01-16T00:00:00.000","2025-02-27T00:00:00.000","2025-03-01T00:00:00.000"],"xaxis":"x","y":{"dtype":"i2","bdata":"AACcCYgOkhN+GGodViJCJy4sGjEGNhA7\u002fD\u002foRNRJ+kk="},"yaxis":"y","type":"scatter"},{"hovertemplate":"Repository=huggingface\u002fsmolagents\u003cbr\u003eDate=%{x}\u003cbr\u003eStars=%{y}\u003cextra\u003e\u003c\u002fextra\u003e","legendgroup":"huggingface\u002fsmolagents","line":{"color":"#ab63fa","dash":"solid"},"marker":{"symbol":"circle"},"mode":"lines+markers","name":"huggingface\u002fsmolagents","orientation":"v","showlegend":true,"x":["2024-12-10T00:00:00.000","2025-01-02T00:00:00.000","2025-01-04T00:00:00.000","2025-01-06T00:00:00.000","2025-01-09T00:00:00.000","2025-01-15T00:00:00.000","2025-01-28T00:00:00.000","2025-02-05T00:00:00.000","2025-02-06T00:00:00.000","2025-02-07T00:00:00.000","2025-02-08T00:00:00.000","2025-02-13T00:00:00.000","2025-02-18T00:00:00.000","2025-02-26T00:00:00.000","2025-02-28T00:00:00.000","2025-02-28T00:00:00.000"],"xaxis":"x","y":{"dtype":"i2","bdata":"AAByBtgJIA2GEM4TNBd8GuIdKiGQJNgnPiuGLuwxJjI="},"yaxis":"y","type":"scatter"},{"hovertemplate":"Repository=langchain-ai\u002flanggraph\u003cbr\u003eDate=%{x}\u003cbr\u003eStars=%{y}\u003cextra\u003e\u003c\u002fextra\u003e","legendgroup":"langchain-ai\u002flanggraph","line":{"color":"#FFA15A","dash":"solid"},"marker":{"symbol":"circle"},"mode":"lines+markers","name":"langchain-ai\u002flanggraph","orientation":"v","showlegend":true,"x":["2023-08-11T00:00:00.000","2024-02-15T00:00:00.000","2024-03-20T00:00:00.000","2024-04-22T00:00:00.000","2024-05-25T00:00:00.000","2024-06-20T00:00:00.000","2024-07-19T00:00:00.000","2024-08-16T00:00:00.000","2024-09-20T00:00:00.000","2024-10-29T00:00:00.000","2024-12-04T00:00:00.000","2024-12-31T00:00:00.000","2025-01-21T00:00:00.000","2025-02-12T00:00:00.000","2025-02-28T00:00:00.000","2025-02-28T00:00:00.000"],"xaxis":"x","y":{"dtype":"i2","bdata":"AADOBEQHugkwDMQOOhGwEyYWuhgwG6YdHCCwIiYlSCU="},"yaxis":"y","type":"scatter"},{"hovertemplate":"Repository=pydantic\u002fpydantic-ai\u003cbr\u003eDate=%{x}\u003cbr\u003eStars=%{y}\u003cextra\u003e\u003c\u002fextra\u003e","legendgroup":"pydantic\u002fpydantic-ai","line":{"color":"#19d3f3","dash":"solid"},"marker":{"symbol":"circle"},"mode":"lines+markers","name":"pydantic\u002fpydantic-ai","orientation":"v","showlegend":true,"x":["2024-06-21T00:00:00.000","2024-12-03T00:00:00.000","2024-12-04T00:00:00.000","2024-12-05T00:00:00.000","2024-12-05T00:00:00.000","2024-12-06T00:00:00.000","2024-12-08T00:00:00.000","2024-12-11T00:00:00.000","2024-12-17T00:00:00.000","2024-12-28T00:00:00.000","2025-01-06T00:00:00.000","2025-01-16T00:00:00.000","2025-01-26T00:00:00.000","2025-02-10T00:00:00.000","2025-02-26T00:00:00.000","2025-02-28T00:00:00.000"],"xaxis":"x","y":{"dtype":"i2","bdata":"AABIAwoFzAaOCFAKEgy2DXgPOhH8Er4UgBZCGAQaOBo="},"yaxis":"y","type":"scatter"}],                        {"template":{"data":{"barpolar":[{"marker":{"line":{"color":"white","width":0.5},"pattern":{"fillmode":"overlay","size":10,"solidity":0.2}},"type":"barpolar"}],"bar":[{"error_x":{"color":"#2a3f5f"},"error_y":{"color":"#2a3f5f"},"marker":{"line":{"color":"white","width":0.5},"pattern":{"fillmode":"overlay","size":10,"solidity":0.2}},"type":"bar"}],"carpet":[{"aaxis":{"endlinecolor":"#2a3f5f","gridcolor":"#C8D4E3","linecolor":"#C8D4E3","minorgridcolor":"#C8D4E3","startlinecolor":"#2a3f5f"},"baxis":{"endlinecolor":"#2a3f5f","gridcolor":"#C8D4E3","linecolor":"#C8D4E3","minorgridcolor":"#C8D4E3","startlinecolor":"#2a3f5f"},"type":"carpet"}],"choropleth":[{"colorbar":{"outlinewidth":0,"ticks":""},"type":"choropleth"}],"contourcarpet":[{"colorbar":{"outlinewidth":0,"ticks":""},"type":"contourcarpet"}],"contour":[{"colorbar":{"outlinewidth":0,"ticks":""},"colorscale":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]],"type":"contour"}],"heatmap":[{"colorbar":{"outlinewidth":0,"ticks":""},"colorscale":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]],"type":"heatmap"}],"histogram2dcontour":[{"colorbar":{"outlinewidth":0,"ticks":""},"colorscale":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]],"type":"histogram2dcontour"}],"histogram2d":[{"colorbar":{"outlinewidth":0,"ticks":""},"colorscale":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]],"type":"histogram2d"}],"histogram":[{"marker":{"pattern":{"fillmode":"overlay","size":10,"solidity":0.2}},"type":"histogram"}],"mesh3d":[{"colorbar":{"outlinewidth":0,"ticks":""},"type":"mesh3d"}],"parcoords":[{"line":{"colorbar":{"outlinewidth":0,"ticks":""}},"type":"parcoords"}],"pie":[{"automargin":true,"type":"pie"}],"scatter3d":[{"line":{"colorbar":{"outlinewidth":0,"ticks":""}},"marker":{"colorbar":{"outlinewidth":0,"ticks":""}},"type":"scatter3d"}],"scattercarpet":[{"marker":{"colorbar":{"outlinewidth":0,"ticks":""}},"type":"scattercarpet"}],"scattergeo":[{"marker":{"colorbar":{"outlinewidth":0,"ticks":""}},"type":"scattergeo"}],"scattergl":[{"marker":{"colorbar":{"outlinewidth":0,"ticks":""}},"type":"scattergl"}],"scattermapbox":[{"marker":{"colorbar":{"outlinewidth":0,"ticks":""}},"type":"scattermapbox"}],"scattermap":[{"marker":{"colorbar":{"outlinewidth":0,"ticks":""}},"type":"scattermap"}],"scatterpolargl":[{"marker":{"colorbar":{"outlinewidth":0,"ticks":""}},"type":"scatterpolargl"}],"scatterpolar":[{"marker":{"colorbar":{"outlinewidth":0,"ticks":""}},"type":"scatterpolar"}],"scatter":[{"fillpattern":{"fillmode":"overlay","size":10,"solidity":0.2},"type":"scatter"}],"scatterternary":[{"marker":{"colorbar":{"outlinewidth":0,"ticks":""}},"type":"scatterternary"}],"surface":[{"colorbar":{"outlinewidth":0,"ticks":""},"colorscale":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]],"type":"surface"}],"table":[{"cells":{"fill":{"color":"#EBF0F8"},"line":{"color":"white"}},"header":{"fill":{"color":"#C8D4E3"},"line":{"color":"white"}},"type":"table"}]},"layout":{"annotationdefaults":{"arrowcolor":"#2a3f5f","arrowhead":0,"arrowwidth":1},"autotypenumbers":"strict","coloraxis":{"colorbar":{"outlinewidth":0,"ticks":""}},"colorscale":{"diverging":[[0,"#8e0152"],[0.1,"#c51b7d"],[0.2,"#de77ae"],[0.3,"#f1b6da"],[0.4,"#fde0ef"],[0.5,"#f7f7f7"],[0.6,"#e6f5d0"],[0.7,"#b8e186"],[0.8,"#7fbc41"],[0.9,"#4d9221"],[1,"#276419"]],"sequential":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]],"sequentialminus":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]]},"colorway":["#636efa","#EF553B","#00cc96","#ab63fa","#FFA15A","#19d3f3","#FF6692","#B6E880","#FF97FF","#FECB52"],"font":{"color":"#2a3f5f"},"geo":{"bgcolor":"white","lakecolor":"white","landcolor":"white","showlakes":true,"showland":true,"subunitcolor":"#C8D4E3"},"hoverlabel":{"align":"left"},"hovermode":"closest","mapbox":{"style":"light"},"margin":{"b":0,"l":0,"r":0,"t":30},"paper_bgcolor":"white","plot_bgcolor":"white","polar":{"angularaxis":{"gridcolor":"#EBF0F8","linecolor":"#EBF0F8","ticks":""},"bgcolor":"white","radialaxis":{"gridcolor":"#EBF0F8","linecolor":"#EBF0F8","ticks":""}},"scene":{"xaxis":{"backgroundcolor":"white","gridcolor":"#DFE8F3","gridwidth":2,"linecolor":"#EBF0F8","showbackground":true,"ticks":"","zerolinecolor":"#EBF0F8"},"yaxis":{"backgroundcolor":"white","gridcolor":"#DFE8F3","gridwidth":2,"linecolor":"#EBF0F8","showbackground":true,"ticks":"","zerolinecolor":"#EBF0F8"},"zaxis":{"backgroundcolor":"white","gridcolor":"#DFE8F3","gridwidth":2,"linecolor":"#EBF0F8","showbackground":true,"ticks":"","zerolinecolor":"#EBF0F8"}},"shapedefaults":{"line":{"color":"#2a3f5f"}},"ternary":{"aaxis":{"gridcolor":"#DFE8F3","linecolor":"#A2B1C6","ticks":""},"baxis":{"gridcolor":"#DFE8F3","linecolor":"#A2B1C6","ticks":""},"bgcolor":"white","caxis":{"gridcolor":"#DFE8F3","linecolor":"#A2B1C6","ticks":""}},"title":{"x":0.05},"xaxis":{"automargin":true,"gridcolor":"#EBF0F8","linecolor":"#EBF0F8","ticks":"","title":{"standoff":15},"zerolinecolor":"#EBF0F8","zerolinewidth":2},"yaxis":{"automargin":true,"gridcolor":"#EBF0F8","linecolor":"#EBF0F8","ticks":"","title":{"standoff":15},"zerolinecolor":"#EBF0F8","zerolinewidth":2}}},"xaxis":{"anchor":"y","domain":[0.0,1.0],"title":{"text":"Date"},"fixedrange":true},"yaxis":{"anchor":"x","domain":[0.0,1.0],"title":{"text":"GitHub Stars"},"fixedrange":true},"legend":{"title":{"text":""},"tracegroupgap":0},"modebar":{"remove":["zoom","pan","select","lasso","zoomIn","zoomOut","autoScale","resetScale","toImage","sendDataToCloud","toggleSpikelines","hoverClosestCartesian","hoverCompareCartesian","plotly"]}},                        {"displaylogo": false, "responsive": true}                    ).then(function(){
                            
var gd = document.getElementById('57497c1a-5a86-48ae-acaa-9dc67b1bc4c0');
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>
<p>Star history of agentic AI frameworks, data retrieved on 2025-02-28 from star-history.com.</p>
</div>
</div>
<p>Interest is also high among business leaders. Deloitte’s Jul/Sep 2024 <a href="https://www2.deloitte.com/content/dam/Deloitte/us/Documents/consulting/us-state-of-gen-ai-q4.pdf">State of Generative AI in the Enterprise Survey</a> showed that agentic AI garners the highest attention of all GenA-related developments, with 52% of C-suite-level respondents indicating interest in it (page 27).</p>
<p>But not every app needs agentic AI and not everyone believes in the hype. As MLOps Tech Lead Maria Vechtomova puts it in a <a href="https://www.linkedin.com/feed/update/urn:li:activity:7298117212064657408">LinkedIn post</a>:</p>
<blockquote class="blockquote">
<p>I’m so tired of the #AI hype… How many more millions will companies waste trying to adopt AI agents for any possible use case before the bubble bursts?</p>
</blockquote>
<p>and LLM consultant Hamel Husain <a href="https://www.linkedin.com/posts/hamelhusain_dont-repeat-this-mistake-you-have-been-activity-7273119135293710336-SnEj?utm_source=share">posted</a> this meme:</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://simmering.dev/blog/agentic-ai/agents_iq_meme.png" class="img-fluid figure-img" style="width:70.0%"></p>
<figcaption>IQ meme: Are agents the overthinker’s choice? Cropped from Hamel’s LinkedIn post</figcaption>
</figure>
</div>
<p>Is agentic AI a hype or the key to the next generation of AI apps? This article provides a balanced look at it, written to help you decide whether agentic AI is right for your use case. It won’t go into the details of any specific agentic AI frameworks, but rather focus on the principles and tradeoffs.</p>
<section id="four-levels-of-agency" class="level3">
<h3 class="anchored" data-anchor-id="four-levels-of-agency">Four levels of agency</h3>
<p>To start, let’s establish a definition of what an agent is. The simplest definition I found is from Eugene Yan on <a href="https://www.linkedin.com/posts/eugeneyan_agent-model-tools-within-a-for-loop-ugcPost-7300335307558658048-ST_a?utm_source=share&amp;utm_medium=member_desktop&amp;rcm=ACoAABYvtdwBLdBW43CUqePc_yFkM0OKTe6yH00">LinkedIn</a>:</p>
<blockquote class="blockquote">
<p>agent ≈ model + tools, within a for-loop + environment</p>
</blockquote>
<p>Let’s break this down:</p>
<ul>
<li><strong>Model</strong>: an LLM receiving inputs tokens and outputting response tokens</li>
<li><strong>Tools</strong>: function definitions provided to the model, e.g.&nbsp;<code>search_web(query: str) -&gt; str</code> that it can provide arguments to in its response</li>
<li><strong>For-loop</strong>: the model is called multiple times, with the output of one call being the input to a tool or another call</li>
<li><strong>Environment</strong>: the runtime calling the model, providing it with access to tools, data, and tracking the state of the workflow</li>
</ul>
<p>This definition is a great starting point and cuts through the overthinking and hype. It needs one more element to be complete:</p>
<p><strong>Degree of agency</strong>: The agent is in charge of the workflow. It decides on what to do next to pursue a given goal. This can be categorized into 4 levels:</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://simmering.dev/blog/agentic-ai/levels.svg" class="img-fluid figure-img"></p>
<figcaption>4 Levels of agency</figcaption>
</figure>
</div>
<ol type="1">
<li><strong>Single call, single tool</strong>: the model is called once with a single tool provided</li>
<li><strong>Single call, multiple tools</strong>: the model is called once with multiple tools provided and the model decides which ones to use</li>
<li><strong>Fixed workflow</strong>: the model is called multiple times with a predetermined sequence of prompts, feeding the output of one call into the next</li>
<li><strong>Open-ended workflow / true agentic AI</strong>: the model is called multiple times with a flexible sequence of prompts, feeding the output of one call into the next, and choosing the end of the loop independently</li>
</ol>
<p>The order of levels 2 and 3 is debatable. Level 2 adds the tool decision, level 3 adds having multiple steps. Level 4 has both, is open ended and is the only true agentic one.</p>
<p>The main question of this article is to provide a framework to decide whether the jump from level 3 to level 4 is worth it for a given use case. We will look at the new use cases unlocked and the tradeoffs involved.</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Note
</div>
</div>
<div class="callout-body-container callout-body">
<p>The LLM in a for-loop definition used here is the simplest possible. If you’re looking for a more sophisticated definition, check out <a href="https://weaviate.io/blog/ai-agents">Weaviate’s article (Feb 2025)</a> that offers a history of the methodology, <a href="https://www.anthropic.com/research/building-effective-agents">Anthropic’s article (Dec 2024)</a> describing common workflow patterns including parallel operations, and <a href="https://huyenchip.com/2025/01/07/agents">Chip Huyen’s article (Jan 2025)</a> that goes into detail on the planning phase of agentic workflows. Acharya and Kuppan provide a <a href="https://ieeexplore.ieee.org/stamp/stamp.jsp?arnumber=10849561">formal ontology (Jan 2025)</a> of agents.</p>
</div>
</div>
</section>
<section id="real-world-use" class="level3">
<h3 class="anchored" data-anchor-id="real-world-use">Real world use</h3>
<p><a href="https://aiagentindex.mit.edu">The AI Agent Index</a> by Casper et al.&nbsp;from MIT tracks the number of agentic AI systems deployed by large organizations. The number of agents deployed is growing every month. Their requirements for inclusion are strict and likely undercount the actual number of agents deployed.</p>
<div id="690b0684" class="cell" data-execution_count="3">
<div class="cell-output cell-output-display">
<div>            <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="/assets/vendor/plotly/plotly-3.3.0.min.js" integrity="sha256-bO3dS6yCpk9aK4gUpNELtCiDeSYvGYnK7jFI58NQnHI=" crossorigin="anonymous"></script>                <div id="e968c5df-f7f2-48b6-831d-b1f8813ef87b" class="plotly-graph-div" style="height:300px; width:100%;"></div>            <script type="text/javascript">                window.PLOTLYENV=window.PLOTLYENV || {};                                if (document.getElementById("e968c5df-f7f2-48b6-831d-b1f8813ef87b")) {                    Plotly.newPlot(                        "e968c5df-f7f2-48b6-831d-b1f8813ef87b",                        [{"hovertemplate":"date=%{x}\u003cbr\u003ecount=%{y}\u003cextra\u003e\u003c\u002fextra\u003e","legendgroup":"","marker":{"color":"#636efa","pattern":{"shape":""}},"name":"","orientation":"v","showlegend":false,"textposition":"auto","x":["2023-03-01T00:00:00.000000000","2023-05-01T00:00:00.000000000","2023-07-01T00:00:00.000000000","2023-09-01T00:00:00.000000000","2023-11-01T00:00:00.000000000","2024-01-01T00:00:00.000000000","2024-03-01T00:00:00.000000000","2024-05-01T00:00:00.000000000","2024-07-01T00:00:00.000000000","2024-09-01T00:00:00.000000000","2024-11-01T00:00:00.000000000"],"xaxis":"x","y":{"dtype":"i1","bdata":"AgIBBAMDBwUGChA="},"yaxis":"y","type":"bar"}],                        {"template":{"data":{"barpolar":[{"marker":{"line":{"color":"white","width":0.5},"pattern":{"fillmode":"overlay","size":10,"solidity":0.2}},"type":"barpolar"}],"bar":[{"error_x":{"color":"#2a3f5f"},"error_y":{"color":"#2a3f5f"},"marker":{"line":{"color":"white","width":0.5},"pattern":{"fillmode":"overlay","size":10,"solidity":0.2}},"type":"bar"}],"carpet":[{"aaxis":{"endlinecolor":"#2a3f5f","gridcolor":"#C8D4E3","linecolor":"#C8D4E3","minorgridcolor":"#C8D4E3","startlinecolor":"#2a3f5f"},"baxis":{"endlinecolor":"#2a3f5f","gridcolor":"#C8D4E3","linecolor":"#C8D4E3","minorgridcolor":"#C8D4E3","startlinecolor":"#2a3f5f"},"type":"carpet"}],"choropleth":[{"colorbar":{"outlinewidth":0,"ticks":""},"type":"choropleth"}],"contourcarpet":[{"colorbar":{"outlinewidth":0,"ticks":""},"type":"contourcarpet"}],"contour":[{"colorbar":{"outlinewidth":0,"ticks":""},"colorscale":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]],"type":"contour"}],"heatmap":[{"colorbar":{"outlinewidth":0,"ticks":""},"colorscale":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]],"type":"heatmap"}],"histogram2dcontour":[{"colorbar":{"outlinewidth":0,"ticks":""},"colorscale":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]],"type":"histogram2dcontour"}],"histogram2d":[{"colorbar":{"outlinewidth":0,"ticks":""},"colorscale":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]],"type":"histogram2d"}],"histogram":[{"marker":{"pattern":{"fillmode":"overlay","size":10,"solidity":0.2}},"type":"histogram"}],"mesh3d":[{"colorbar":{"outlinewidth":0,"ticks":""},"type":"mesh3d"}],"parcoords":[{"line":{"colorbar":{"outlinewidth":0,"ticks":""}},"type":"parcoords"}],"pie":[{"automargin":true,"type":"pie"}],"scatter3d":[{"line":{"colorbar":{"outlinewidth":0,"ticks":""}},"marker":{"colorbar":{"outlinewidth":0,"ticks":""}},"type":"scatter3d"}],"scattercarpet":[{"marker":{"colorbar":{"outlinewidth":0,"ticks":""}},"type":"scattercarpet"}],"scattergeo":[{"marker":{"colorbar":{"outlinewidth":0,"ticks":""}},"type":"scattergeo"}],"scattergl":[{"marker":{"colorbar":{"outlinewidth":0,"ticks":""}},"type":"scattergl"}],"scattermapbox":[{"marker":{"colorbar":{"outlinewidth":0,"ticks":""}},"type":"scattermapbox"}],"scattermap":[{"marker":{"colorbar":{"outlinewidth":0,"ticks":""}},"type":"scattermap"}],"scatterpolargl":[{"marker":{"colorbar":{"outlinewidth":0,"ticks":""}},"type":"scatterpolargl"}],"scatterpolar":[{"marker":{"colorbar":{"outlinewidth":0,"ticks":""}},"type":"scatterpolar"}],"scatter":[{"fillpattern":{"fillmode":"overlay","size":10,"solidity":0.2},"type":"scatter"}],"scatterternary":[{"marker":{"colorbar":{"outlinewidth":0,"ticks":""}},"type":"scatterternary"}],"surface":[{"colorbar":{"outlinewidth":0,"ticks":""},"colorscale":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]],"type":"surface"}],"table":[{"cells":{"fill":{"color":"#EBF0F8"},"line":{"color":"white"}},"header":{"fill":{"color":"#C8D4E3"},"line":{"color":"white"}},"type":"table"}]},"layout":{"annotationdefaults":{"arrowcolor":"#2a3f5f","arrowhead":0,"arrowwidth":1},"autotypenumbers":"strict","coloraxis":{"colorbar":{"outlinewidth":0,"ticks":""}},"colorscale":{"diverging":[[0,"#8e0152"],[0.1,"#c51b7d"],[0.2,"#de77ae"],[0.3,"#f1b6da"],[0.4,"#fde0ef"],[0.5,"#f7f7f7"],[0.6,"#e6f5d0"],[0.7,"#b8e186"],[0.8,"#7fbc41"],[0.9,"#4d9221"],[1,"#276419"]],"sequential":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]],"sequentialminus":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]]},"colorway":["#636efa","#EF553B","#00cc96","#ab63fa","#FFA15A","#19d3f3","#FF6692","#B6E880","#FF97FF","#FECB52"],"font":{"color":"#2a3f5f"},"geo":{"bgcolor":"white","lakecolor":"white","landcolor":"white","showlakes":true,"showland":true,"subunitcolor":"#C8D4E3"},"hoverlabel":{"align":"left"},"hovermode":"closest","mapbox":{"style":"light"},"margin":{"b":0,"l":0,"r":0,"t":30},"paper_bgcolor":"white","plot_bgcolor":"white","polar":{"angularaxis":{"gridcolor":"#EBF0F8","linecolor":"#EBF0F8","ticks":""},"bgcolor":"white","radialaxis":{"gridcolor":"#EBF0F8","linecolor":"#EBF0F8","ticks":""}},"scene":{"xaxis":{"backgroundcolor":"white","gridcolor":"#DFE8F3","gridwidth":2,"linecolor":"#EBF0F8","showbackground":true,"ticks":"","zerolinecolor":"#EBF0F8"},"yaxis":{"backgroundcolor":"white","gridcolor":"#DFE8F3","gridwidth":2,"linecolor":"#EBF0F8","showbackground":true,"ticks":"","zerolinecolor":"#EBF0F8"},"zaxis":{"backgroundcolor":"white","gridcolor":"#DFE8F3","gridwidth":2,"linecolor":"#EBF0F8","showbackground":true,"ticks":"","zerolinecolor":"#EBF0F8"}},"shapedefaults":{"line":{"color":"#2a3f5f"}},"ternary":{"aaxis":{"gridcolor":"#DFE8F3","linecolor":"#A2B1C6","ticks":""},"baxis":{"gridcolor":"#DFE8F3","linecolor":"#A2B1C6","ticks":""},"bgcolor":"white","caxis":{"gridcolor":"#DFE8F3","linecolor":"#A2B1C6","ticks":""}},"title":{"x":0.05},"xaxis":{"automargin":true,"gridcolor":"#EBF0F8","linecolor":"#EBF0F8","ticks":"","title":{"standoff":15},"zerolinecolor":"#EBF0F8","zerolinewidth":2},"yaxis":{"automargin":true,"gridcolor":"#EBF0F8","linecolor":"#EBF0F8","ticks":"","title":{"standoff":15},"zerolinecolor":"#EBF0F8","zerolinewidth":2}}},"xaxis":{"anchor":"y","domain":[0.0,1.0],"title":{"text":"Release Date"},"tickmode":"array","tickvals":["2023-03-01T00:00:00.000000000","2023-05-01T00:00:00.000000000","2023-07-01T00:00:00.000000000","2023-09-01T00:00:00.000000000","2023-11-01T00:00:00.000000000","2024-01-01T00:00:00.000000000","2024-03-01T00:00:00.000000000","2024-05-01T00:00:00.000000000","2024-07-01T00:00:00.000000000","2024-09-01T00:00:00.000000000","2024-11-01T00:00:00.000000000"],"ticktext":["2023-03","2023-05","2023-07","2023-09","2023-11","2024-01","2024-03","2024-05","2024-07","2024-09","2024-11"],"fixedrange":true},"yaxis":{"anchor":"x","domain":[0.0,1.0],"title":{"text":"Number of Agents"},"fixedrange":true},"legend":{"tracegroupgap":0},"title":{"text":"Agentic AI Deployments at Large Organizations"},"barmode":"relative","height":300,"modebar":{"remove":["zoom","pan","select","lasso","zoomIn","zoomOut","autoScale","resetScale","toImage","sendDataToCloud","toggleSpikelines","hoverClosestCartesian","hoverCompareCartesian","plotly"]},"showlegend":false},                        {"displaylogo": false, "responsive": true}                    ).then(function(){
                            
var gd = document.getElementById('e968c5df-f7f2-48b6-831d-b1f8813ef87b');
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>Let’s take a look at the most promising use cases. Click on the use cases to expand them.</p>
<div class="callout callout-style-simple callout-note no-icon callout-titled">
<div class="callout-header d-flex align-content-center collapsed" data-bs-toggle="collapse" data-bs-target=".callout-2-contents" aria-controls="callout-2" aria-expanded="false" aria-label="Toggle callout">
<div class="callout-icon-container">
<i class="callout-icon no-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>Deep research
</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-2" class="callout-2-contents callout-collapse collapse">
<div class="callout-body-container callout-body">
<p>An agent that accepts a search query, asks clarifying questions, searches the web systematically for 5 to 30 minutesand writes a detailed report, akin to a literature review.</p>
<p>Examples, all released in February 2025:</p>
<ul>
<li><a href="https://openai.com/research/deep-research">OpenAI’s Deep Research</a>: Performs multi-step web searches, synthesizes information from multiple sources, and generates cited reports.</li>
<li><a href="https://www.perplexity.ai/blog/introducing-deep-research">Perplexity’s Deep Research</a>: Searches the web, writes code to analyze data, and compiles findings with source citations.</li>
<li><a href="https://research.google/blog/ai-co-scientist">Google’s AI Co-scientist</a>: Analyzes scientific literature, suggests hypotheses, and outlines potential experimental approaches.</li>
<li><a href="https://x.ai/blog/grok-3">X’s Grok DeepSearch</a>: Searches the internet and X platform, uses specialized reasoning modes for complex problem-solving.</li>
</ul>
<p>There is no default benchmark for this use case yet. The difficulty is that the agent needs to search the web live, and the content of the web changes constantly.</p>
<p>I read reviews of OpenAI’s Deep Research by <a href="https://www.oneusefulthing.org/i/156359450/deep-research">Ethan Mollick</a>, <a href="https://marginalrevolution.com/marginalrevolution/2025/02/deep-research.html">Tyler Cowen</a>, <a href="https://leonfurze.com/2025/02/15/hands-on-with-deep-research/">Leon Furze</a>, <a href="https://generativehistory.substack.com/p/is-this-the-last-generation-of-historians">Mark Humphries</a>, and <a href="https://www.science.org/content/blog-post/evaluation-deep-research-performance">Derek Lowe</a>. Each reviewer asked it to perform a literature review or similar in their field of expertise, ranging from economics to toxicology. The common themes were:</p>
<ul>
<li>Impressed by the volume and polish of the output</li>
<li>Massive time saver, Tyler Cowen reports that it’s like having a PhD-level research assistant that does a week’s work in five minutes.</li>
<li>The agent can’t access paywalled content, e.g.&nbsp;journal papers, which limits its usefulness in fields that are behind on open access.</li>
<li>Can be too surface-level, lacking in synthesis.</li>
<li>Can get details wrong, misleading non-experts. Derek Lowe puts it well: “you have to know the material already to realize when your foot has gone through what was earlier solid flooring”</li>
</ul>
</div>
</div>
</div>
<div class="callout callout-style-simple callout-note no-icon callout-titled">
<div class="callout-header d-flex align-content-center collapsed" data-bs-toggle="collapse" data-bs-target=".callout-3-contents" aria-controls="callout-3" aria-expanded="false" aria-label="Toggle callout">
<div class="callout-icon-container">
<i class="callout-icon no-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>Computer use
</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-3" class="callout-3-contents callout-collapse collapse">
<div class="callout-body-container callout-body">
<p>An agent that can help with a wide variety of computer tasks going from simple (setting a reminder, creating a note, sending an email) to more complex (finding leads on LinkedIn, scheduling a meeting with many people and across timezones, finding the best flight for a trip).</p>
<p>Examples:</p>
<ul>
<li><a href="https://www.anthropic.com/news/3-5-models-and-computer-use">Anthropic’s Claude with Computer Use</a>: Controls desktop applications, navigates interfaces, manipulates files, and interacts with web browsers through API-based screen access.</li>
<li><a href="https://openai.com/index/introducing-operator/">OpenAI’s Operator</a>: Navigates websites, completes forms, books reservations, makes purchases, and performs multi-step tasks with browser automation.</li>
<li><a href="https://www.aboutamazon.com/news/devices/new-alexa-generative-artificial-intelligence">Amazon’s Alexa Plus</a>: Maintains conversational context, controls smart home devices, answers complex questions, and performs multi-step tasks through voice commands.</li>
<li><a href="https://www.raycast.com/core-features/ai">Raycast’s AI extensions</a>: Integrates with desktop applications, executes system commands, manages files, and provides contextual assistance through a keyboard-driven launcher.</li>
</ul>
<p>The <a href="https://github.com/web-arena-x/webarena?tab=readme-ov-file">webarena benchmark</a> gives an idea of how well computer use agents work on a range of browser-based tasks. As of February 2025, the best performing agent was IBM CUGA with a success rate of 61.7%, followed by OpenAI’s Operator at 58.1%. Clearly, there’s room for improvement.</p>
</div>
</div>
</div>
<div class="callout callout-style-simple callout-note no-icon callout-titled">
<div class="callout-header d-flex align-content-center collapsed" data-bs-toggle="collapse" data-bs-target=".callout-4-contents" aria-controls="callout-4" aria-expanded="false" aria-label="Toggle callout">
<div class="callout-icon-container">
<i class="callout-icon no-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>Coding
</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-4" class="callout-4-contents callout-collapse collapse">
<div class="callout-body-container callout-body">
<p>An agent that can write code, review changes, and debug.</p>
<p>Examples:</p>
<ul>
<li><a href="https://docs.cursor.com/agent">Cursor Agent</a>: Indexes codebases, performs web searches for up-to-date information, executes multi-step coding tasks, and generates tests through an AI-powered code editor.</li>
<li><a href="https://codeium.com/cascade">Windsurf Cascade</a>: Processes code across multiple files, understands project structure, accepts image inputs, maintains memory of previous interactions, and implements code changes.</li>
<li><a href="https://docs.anthropic.com/en/docs/agents-and-tools/claude-code/overview">Claude Code</a>: Operates in the terminal, understands entire codebases, executes shell commands, and implements code changes through natural language instructions.</li>
<li><a href="https://devin.ai">Devin</a>: Collaborates via Slack, accesses repositories, writes and debugs code, learns from projects, and adapts to team workflows over time.</li>
<li><a href="https://pub.sakana.ai/static/paper.pdf">Sakana AI’s CUDA kernel optimization agent</a>: Translates PyTorch code to optimized CUDA kernels, utilizes specialized hardware features, and improves model performance on GPUs.</li>
</ul>
<p>Benchmarks and reviews:</p>
<ul>
<li><a href="https://www.swebench.com/#verified">SWE-Bench Verified</a>: solving real GitHub issues focusing on code generation and bug fixing. Current best agent reaches 64.6%.</li>
<li>The folks at Answer.ai <a href="https://www.answer.ai/posts/2025-01-08-devin.html">evaluated Devin</a> across 20 tasks and saw 14 failures, 3 inconclusive results and just 3 successes. Further, they weren’t able to predict which tasks Devin would succeed at. Ultimately, they concluded that the closer feedback loop of an AI acting inside an IDE is more effective.</li>
<li>Sakana’s CUDA kernel optimization agent <a href="https://x.com/SakanaAILabs/status/1892992938013270019">proved</a> too smart for its own good and wrote code that exploited a bug in the evaluation code to “cheat” and get higher scores. Still, the concept of specialized coding agents is promising.</li>
</ul>
</div>
</div>
</div>
<div class="callout callout-style-simple callout-note no-icon callout-titled">
<div class="callout-header d-flex align-content-center collapsed" data-bs-toggle="collapse" data-bs-target=".callout-5-contents" aria-controls="callout-5" aria-expanded="false" aria-label="Toggle callout">
<div class="callout-icon-container">
<i class="callout-icon no-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Note</span>Customer support
</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-5" class="callout-5-contents callout-collapse collapse">
<div class="callout-body-container callout-body">
<p>A customer support chatbot or ticket handling agent that handles a wide variety of requests. It can either enhance or replace a human agent.</p>
<p>Examples:</p>
<ul>
<li><a href="https://www.intercom.com/fin">Intercom’s Fin AI</a>: Answers questions using knowledge bases, processes transactions, and maintains conversation context throughout customer interactions.</li>
<li><a href="https://www.klarna.com/international/press/klarna-ai-assistant-handles-two-thirds-of-customer-service-chats-in-its-first-month/">Klarna’s AI assistant</a>: Processes refunds, manages returns, handles payment issues, and provides shopping recommendations in multiple languages. A case study on LangChain’s <a href="https://blog.langchain.dev/customers-klarna/">website</a> gives more recent technical insight.</li>
<li><a href="https://www.salesforce.com/news/press-releases/2024/09/12/agentforce-announcement/">Salesforce AgentForce</a>: Resolves customer cases using company data, integrates with CRM systems, and automates support workflows.</li>
</ul>
<p>Benchmarks and reviews:</p>
<ul>
<li>Intercom claims to have handled more than 15 million customer queries using Fin with a 54% resolution rate, steadily increasing over time as the agent is improved.</li>
<li>In February 2024, Klarna reported that its AI assistant handled two thirds of customer service chats in its first month, doing the equivalent work of 700 full-time employees. Klarna maintains a 4.1 rating on <a href="https://www.trustpilot.com/review/klarna.com">Trustpilot</a>, higher than most financial service providers, suggesting that the agentic approach is effective.</li>
<li>Salesforce cites their customer Wiley, claiming a 40% increase in case resolution over a previous chatbot. However, they don’t give an actual resolution rate.</li>
</ul>
</div>
</div>
</div>
<p>These are just the four most common and promising use cases, based on my research. <a href="https://cloud.google.com/transform/101-real-world-generative-ai-use-cases-from-industry-leaders?hl=en">Google Cloud</a> lists a whopping 321 gen AI use cases, and <a href="https://blogs.microsoft.com/blog/2025/02/05/https-blogs-microsoft-com-blog-2024-11-12-how-real-world-businesses-are-transforming-with-ai/">Microsoft</a> also lists more than 300. Only a minority of them are truely agentic though, the majority are on levels 1 to 3. In addition to these publicly available examples, companies are developing internal tools to solve all kinds of problems, ranging from report generation to automated pricing updates.</p>
<p>Looking over the benchmarks and reviews cited in the use cases above, success rates range from 10% to 65%. Clearly, agentic AI is not ready for unsupervised high stakes jobs. It needs oversight by a human expert.</p>
</section>
<section id="compounding-error-problem" class="level3">
<h3 class="anchored" data-anchor-id="compounding-error-problem">Compounding error problem</h3>
<p>Each step in an agentic workflow has a chance of introducing an error. Let’s model this in a simplified way where each step has the same error rate and there is no error recovery. The graph below shows the chance of the workflow being correct as a function of the number of steps, for different error rates.</p>
<div id="6798f56d" class="cell" data-execution_count="4">
<div class="cell-output cell-output-display">
<div>            <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="/assets/vendor/plotly/plotly-3.3.0.min.js" integrity="sha256-bO3dS6yCpk9aK4gUpNELtCiDeSYvGYnK7jFI58NQnHI=" crossorigin="anonymous"></script>                <div id="9e8fdaca-b215-44c9-8a9b-28b9aca2991f" class="plotly-graph-div" style="height:350px; width:100%;"></div>            <script type="text/javascript">                window.PLOTLYENV=window.PLOTLYENV || {};                                if (document.getElementById("9e8fdaca-b215-44c9-8a9b-28b9aca2991f")) {                    Plotly.newPlot(                        "9e8fdaca-b215-44c9-8a9b-28b9aca2991f",                        [{"hovertemplate":"Error Rate=1% error rate\u003cbr\u003eNumber of steps=%{x}\u003cbr\u003eProbability of no error=%{y}\u003cextra\u003e\u003c\u002fextra\u003e","legendgroup":"1% error rate","line":{"color":"#636efa","dash":"solid"},"marker":{"symbol":"circle"},"mode":"lines+markers","name":"1% error rate","orientation":"v","showlegend":true,"x":{"dtype":"i1","bdata":"AQIDBAUGBwgJCg=="},"xaxis":"x","y":{"dtype":"f8","bdata":"rkfhehSu7z8+6Nms+lzvP5rtCn2wDO8\u002fYsfz1zO97j+CimSvgm7uP+1VcPqaIO4\u002fL9pftXrT7T+GA6ThH4ftPwzGyIWIO+0\u002fvwtorbLw7D8="},"yaxis":"y","type":"scatter"},{"hovertemplate":"Error Rate=2% error rate\u003cbr\u003eNumber of steps=%{x}\u003cbr\u003eProbability of no error=%{y}\u003cextra\u003e\u003c\u002fextra\u003e","legendgroup":"2% error rate","line":{"color":"#EF553B","dash":"solid"},"marker":{"symbol":"circle"},"mode":"lines+markers","name":"2% error rate","orientation":"v","showlegend":true,"x":{"dtype":"i1","bdata":"AQIDBAUGBwgJCg=="},"xaxis":"x","y":{"dtype":"f8","bdata":"XI\u002fC9Shc7z9AguLHmLvuP\u002fc3aK8+Hu4\u002fU0tCOwqE7T+zxI1O6+zsP84X5x7SWOw\u002fSsDDMq\u002fH6z\u002fsUNRfcznrPy\u002fKbskPruo\u002f0Q0B33Ul6j8="},"yaxis":"y","type":"scatter"},{"hovertemplate":"Error Rate=5% error rate\u003cbr\u003eNumber of steps=%{x}\u003cbr\u003eProbability of no error=%{y}\u003cextra\u003e\u003c\u002fextra\u003e","legendgroup":"5% error rate","line":{"color":"#00cc96","dash":"solid"},"marker":{"symbol":"circle"},"mode":"lines+markers","name":"5% error rate","orientation":"v","showlegend":true,"x":{"dtype":"i1","bdata":"AQIDBAUGBwgJCg=="},"xaxis":"x","y":{"dtype":"f8","bdata":"ZmZmZmZm7j\u002fhehSuR+HsP1UOLbKdb+s\u002fN2dEaW8Q6j9AlZo90MLoP\u002f1AuW3fhec\u002fVpcJdcdY5j\u002fFT\u002fx7vTrlP2HlYs8AK+Q\u002fg\u002fOdXtoo4z8="},"yaxis":"y","type":"scatter"},{"hovertemplate":"Error Rate=10% error rate\u003cbr\u003eNumber of steps=%{x}\u003cbr\u003eProbability of no error=%{y}\u003cextra\u003e\u003c\u002fextra\u003e","legendgroup":"10% error rate","line":{"color":"#ab63fa","dash":"solid"},"marker":{"symbol":"circle"},"mode":"lines+markers","name":"10% error rate","orientation":"v","showlegend":true,"x":{"dtype":"i1","bdata":"AQIDBAUGBwgJCg=="},"xaxis":"x","y":{"dtype":"f8","bdata":"zczMzMzM7D\u002fsUbgehevpP4gW2c73U+c\u002frfpcbcX+5D9prtNIS+XiP\u002fgcWI6QAeE\u002fv83RmWqc3j\u002fG0jxXxozbP3+Kg05\u002fy9g\u002f2S9DYL9Q1j8="},"yaxis":"y","type":"scatter"}],                        {"template":{"data":{"barpolar":[{"marker":{"line":{"color":"white","width":0.5},"pattern":{"fillmode":"overlay","size":10,"solidity":0.2}},"type":"barpolar"}],"bar":[{"error_x":{"color":"#2a3f5f"},"error_y":{"color":"#2a3f5f"},"marker":{"line":{"color":"white","width":0.5},"pattern":{"fillmode":"overlay","size":10,"solidity":0.2}},"type":"bar"}],"carpet":[{"aaxis":{"endlinecolor":"#2a3f5f","gridcolor":"#C8D4E3","linecolor":"#C8D4E3","minorgridcolor":"#C8D4E3","startlinecolor":"#2a3f5f"},"baxis":{"endlinecolor":"#2a3f5f","gridcolor":"#C8D4E3","linecolor":"#C8D4E3","minorgridcolor":"#C8D4E3","startlinecolor":"#2a3f5f"},"type":"carpet"}],"choropleth":[{"colorbar":{"outlinewidth":0,"ticks":""},"type":"choropleth"}],"contourcarpet":[{"colorbar":{"outlinewidth":0,"ticks":""},"type":"contourcarpet"}],"contour":[{"colorbar":{"outlinewidth":0,"ticks":""},"colorscale":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]],"type":"contour"}],"heatmap":[{"colorbar":{"outlinewidth":0,"ticks":""},"colorscale":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]],"type":"heatmap"}],"histogram2dcontour":[{"colorbar":{"outlinewidth":0,"ticks":""},"colorscale":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]],"type":"histogram2dcontour"}],"histogram2d":[{"colorbar":{"outlinewidth":0,"ticks":""},"colorscale":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]],"type":"histogram2d"}],"histogram":[{"marker":{"pattern":{"fillmode":"overlay","size":10,"solidity":0.2}},"type":"histogram"}],"mesh3d":[{"colorbar":{"outlinewidth":0,"ticks":""},"type":"mesh3d"}],"parcoords":[{"line":{"colorbar":{"outlinewidth":0,"ticks":""}},"type":"parcoords"}],"pie":[{"automargin":true,"type":"pie"}],"scatter3d":[{"line":{"colorbar":{"outlinewidth":0,"ticks":""}},"marker":{"colorbar":{"outlinewidth":0,"ticks":""}},"type":"scatter3d"}],"scattercarpet":[{"marker":{"colorbar":{"outlinewidth":0,"ticks":""}},"type":"scattercarpet"}],"scattergeo":[{"marker":{"colorbar":{"outlinewidth":0,"ticks":""}},"type":"scattergeo"}],"scattergl":[{"marker":{"colorbar":{"outlinewidth":0,"ticks":""}},"type":"scattergl"}],"scattermapbox":[{"marker":{"colorbar":{"outlinewidth":0,"ticks":""}},"type":"scattermapbox"}],"scattermap":[{"marker":{"colorbar":{"outlinewidth":0,"ticks":""}},"type":"scattermap"}],"scatterpolargl":[{"marker":{"colorbar":{"outlinewidth":0,"ticks":""}},"type":"scatterpolargl"}],"scatterpolar":[{"marker":{"colorbar":{"outlinewidth":0,"ticks":""}},"type":"scatterpolar"}],"scatter":[{"fillpattern":{"fillmode":"overlay","size":10,"solidity":0.2},"type":"scatter"}],"scatterternary":[{"marker":{"colorbar":{"outlinewidth":0,"ticks":""}},"type":"scatterternary"}],"surface":[{"colorbar":{"outlinewidth":0,"ticks":""},"colorscale":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]],"type":"surface"}],"table":[{"cells":{"fill":{"color":"#EBF0F8"},"line":{"color":"white"}},"header":{"fill":{"color":"#C8D4E3"},"line":{"color":"white"}},"type":"table"}]},"layout":{"annotationdefaults":{"arrowcolor":"#2a3f5f","arrowhead":0,"arrowwidth":1},"autotypenumbers":"strict","coloraxis":{"colorbar":{"outlinewidth":0,"ticks":""}},"colorscale":{"diverging":[[0,"#8e0152"],[0.1,"#c51b7d"],[0.2,"#de77ae"],[0.3,"#f1b6da"],[0.4,"#fde0ef"],[0.5,"#f7f7f7"],[0.6,"#e6f5d0"],[0.7,"#b8e186"],[0.8,"#7fbc41"],[0.9,"#4d9221"],[1,"#276419"]],"sequential":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]],"sequentialminus":[[0.0,"#0d0887"],[0.1111111111111111,"#46039f"],[0.2222222222222222,"#7201a8"],[0.3333333333333333,"#9c179e"],[0.4444444444444444,"#bd3786"],[0.5555555555555556,"#d8576b"],[0.6666666666666666,"#ed7953"],[0.7777777777777778,"#fb9f3a"],[0.8888888888888888,"#fdca26"],[1.0,"#f0f921"]]},"colorway":["#636efa","#EF553B","#00cc96","#ab63fa","#FFA15A","#19d3f3","#FF6692","#B6E880","#FF97FF","#FECB52"],"font":{"color":"#2a3f5f"},"geo":{"bgcolor":"white","lakecolor":"white","landcolor":"white","showlakes":true,"showland":true,"subunitcolor":"#C8D4E3"},"hoverlabel":{"align":"left"},"hovermode":"closest","mapbox":{"style":"light"},"margin":{"b":0,"l":0,"r":0,"t":30},"paper_bgcolor":"white","plot_bgcolor":"white","polar":{"angularaxis":{"gridcolor":"#EBF0F8","linecolor":"#EBF0F8","ticks":""},"bgcolor":"white","radialaxis":{"gridcolor":"#EBF0F8","linecolor":"#EBF0F8","ticks":""}},"scene":{"xaxis":{"backgroundcolor":"white","gridcolor":"#DFE8F3","gridwidth":2,"linecolor":"#EBF0F8","showbackground":true,"ticks":"","zerolinecolor":"#EBF0F8"},"yaxis":{"backgroundcolor":"white","gridcolor":"#DFE8F3","gridwidth":2,"linecolor":"#EBF0F8","showbackground":true,"ticks":"","zerolinecolor":"#EBF0F8"},"zaxis":{"backgroundcolor":"white","gridcolor":"#DFE8F3","gridwidth":2,"linecolor":"#EBF0F8","showbackground":true,"ticks":"","zerolinecolor":"#EBF0F8"}},"shapedefaults":{"line":{"color":"#2a3f5f"}},"ternary":{"aaxis":{"gridcolor":"#DFE8F3","linecolor":"#A2B1C6","ticks":""},"baxis":{"gridcolor":"#DFE8F3","linecolor":"#A2B1C6","ticks":""},"bgcolor":"white","caxis":{"gridcolor":"#DFE8F3","linecolor":"#A2B1C6","ticks":""}},"title":{"x":0.05},"xaxis":{"automargin":true,"gridcolor":"#EBF0F8","linecolor":"#EBF0F8","ticks":"","title":{"standoff":15},"zerolinecolor":"#EBF0F8","zerolinewidth":2},"yaxis":{"automargin":true,"gridcolor":"#EBF0F8","linecolor":"#EBF0F8","ticks":"","title":{"standoff":15},"zerolinecolor":"#EBF0F8","zerolinewidth":2}}},"xaxis":{"anchor":"y","domain":[0.0,1.0],"title":{"text":"Number of steps"},"dtick":1,"fixedrange":true},"yaxis":{"anchor":"x","domain":[0.0,1.0],"title":{"text":"Probability of no error"},"tickmode":"array","tickvals":{"dtype":"f8","bdata":"AAAAAAAAAACamZmZmZm5P5qZmZmZmck\u002fNDMzMzMz0z+amZmZmZnZPwAAAAAAAOA\u002fNDMzMzMz4z9nZmZmZmbmP5qZmZmZmek\u002fzczMzMzM7D8AAAAAAADwPw=="},"ticktext":["0%","10%","20%","30%","40%","50%","60%","70%","80%","90%","100%"],"fixedrange":true},"legend":{"title":{"text":"Error Rate"},"tracegroupgap":0},"modebar":{"remove":["zoom","pan","select","lasso","zoomIn","zoomOut","autoScale","resetScale","toImage","sendDataToCloud","toggleSpikelines","hoverClosestCartesian","hoverCompareCartesian","plotly"]},"height":350},                        {"displaylogo": false, "responsive": true}                    ).then(function(){
                            
var gd = document.getElementById('9e8fdaca-b215-44c9-8a9b-28b9aca2991f');
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>Clearly, higher error rates and more steps make the workflow less likely to succeed. A developer that values correctness has to obsess over error rates and keep the number of steps low.</p>
<p>Error tolerance in business is dependent on culture, familiarity with AI, expectation management and what’s at stake. Let’s consider examples of different errors:</p>
<table class="caption-top table">
<colgroup>
<col style="width: 16%">
<col style="width: 50%">
<col style="width: 33%">
</colgroup>
<thead>
<tr class="header">
<th>Error</th>
<th>Example</th>
<th>Mitigation</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><strong>Planning failure</strong></td>
<td>Agent misunderstands the goal, the constraints or the tools that it has available and plans a path that spins in circles, crashes or returns an incorrect answer.</td>
<td>Clear goal specification, user confirmation steps</td>
</tr>
<tr class="even">
<td><strong>Tool failure</strong></td>
<td>A tool fails to return a correct answer, such as a search that returns an irrelevant or outdated result, or a web scraper that gets blocked by a CAPTCHA. Some errors are loud (e.g.&nbsp;the CAPTCHA), whereas others are silent and poison the output.</td>
<td>Tests for each tool, monitoring of tool calls, try-catch blocks</td>
</tr>
<tr class="odd">
<td><strong>Type errors</strong></td>
<td>The agent doesn’t provide correctly formatted arguments to a tool, or the tool returns an unexpected format.</td>
<td>Validating types statically and at runtime</td>
</tr>
<tr class="even">
<td><strong>Latency</strong></td>
<td>The workflow becomes too long due to too many steps taken or slow tool calls. Users get bored and abandon the agent.</td>
<td>Parallelization, caching, limiting the number of steps</td>
</tr>
</tbody>
</table>
</section>
<section id="agents-causing-harm" class="level3">
<h3 class="anchored" data-anchor-id="agents-causing-harm">Agents causing harm</h3>
<p>In the previous section, we considered failure modes that cause inconvenience. However, agents can also cause harm in the form of data loss, financial damage, legal liability or cyber security incidents.</p>
<ol type="1">
<li><strong>User error</strong>. For example, giving unclear instructions that lead to a wrong file being overwritten or a message sent to the wrong person. Ask the user for confirmation and give them a way to undo the action, where possible.</li>
<li><strong>Model error</strong>. A tool could misunderstand the user’s intent or the way a tool works.</li>
<li><strong>Prompt injection</strong>. LLMs are susceptible to prompt injection, meaning someone hijacking the workflow by overriding the original instructions with a clever prompt. Malicious prompts can be found on websites, received via emails or be hidden in the user’s files.</li>
</ol>
<p>The potential damage primarily depends on the tools that the agent has access to. Consider what the worst thing is that the agent could do with a tool. If that is unacceptable, limit the agent’s access. For example, an agent that can access a database could only be allowed to read, not write, or to only write to a specific append-only table. Payments, deleting data, and other dangerous actions should require user confirmation.</p>
</section>
<section id="not-every-workflow-needs-agentic-ai" class="level2">
<h2 class="anchored" data-anchor-id="not-every-workflow-needs-agentic-ai">Not every workflow needs agentic AI</h2>
<p>Based on the previous case studies and analysis of error rates, here’s a list of reasons to use agentic AI and reasons to avoid it:</p>
<div class="columns">
<div class="column" style="width:50%;">
<p><strong>Reasons to use agentic AI ✅</strong></p>
<ul>
<li>The problem space is too large to enumerate every path</li>
<li>Every interaction is truly different</li>
<li>The problems are hard enough that only a flexible multi-hop system can solve them</li>
<li>High payoff for successful resolution (e.g.&nbsp;saving a human a lot of work)</li>
<li>Low cost of exploration and occasional missteps</li>
<li>You have the necessary time to evaluate the agentic workflow, install safeguards</li>
<li>You value the ease of adding new tools to an agentic workflow</li>
<li>The tools the agent would use already work independently, so it’s just a matter of coordinating them</li>
</ul>
</div><div class="column" style="width:50%;">
<p><strong>Reasons to avoid agentic AI ❌</strong></p>
<ul>
<li>The task can be described as a fixed workflow of steps</li>
<li>Low latency is required</li>
<li>Low error tolerance, e.g.&nbsp;for legal, organizational or social reasons</li>
<li>Need to keep token usage low</li>
<li>Need predictable workflows</li>
<li>Need high explainability</li>
<li>Need to prove that every step of the workflow is correct</li>
<li>Agentic AI frameworks are not mature yet</li>
<li>Cybersecurity concerns from prompt injection and other attacks</li>
</ul>
</div>
</div>
<p>Agents aren’t all or nothing - there are many shades of agentic AI. Each level of agent autonomy increases the surface area for errors. Therefore, it can be wiser to use a hybrid approach that is level 2 or 3 on the agency scale above, with fixedsteps and tool selection, and limited decision making by the agent. Ask: Is the task really so complex and open-ended that it can’t be described as a fixed series of steps and decision points?</p>
<section id="converting-agentic-workflows-to-fixed-workflows" class="level3">
<h3 class="anchored" data-anchor-id="converting-agentic-workflows-to-fixed-workflows">Converting agentic workflows to fixed workflows</h3>
<p>In city planning, there’s the concept of a “desire path”. Rather than walking the long, intended path, people take a shortcut. There’s an urban legend that when Dwight D. Eisenhower was in charge of an extension of Columbia University, he let students walk on grass until natural paths had formed, and then had them paved.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://simmering.dev/blog/agentic-ai/desire_path.png" class="img-fluid figure-img"></p>
<figcaption>Desire path being paved. Image created with FLUX.1-pro</figcaption>
</figure>
</div>
<p>This concept could be applied to agentic workflows. Start by giving an agent the freedom to choose tools and order of execution. Observe which paths are taken and which result in success. Then pave those paths by making them fixed workflows, enabling greater reliability.</p>
</section>
</section>
<section id="so-you-want-to-build-an-agentic-workflow" class="level2">
<h2 class="anchored" data-anchor-id="so-you-want-to-build-an-agentic-workflow">So you want to build an agentic workflow?</h2>
<p>It’s alluringly easy to build a system that looks like it’s working and has impressive sounding capabilities. Just copy-paste from the documentation of a popular agent tool. Copy a tool for web search, a code interpreter and a memory layer and you have an agent with impressive <em>theoretical</em> capabilities.</p>
<p>The hard part is to make the agentic workflow work reliably in production, without close supervision. And that’s where the business value is – a demo doesn’t repay an investment.</p>
<p>The following sections are suggestions for how to make agentic workflows succeed reliably in production.</p>
<section id="start-with-solid-software-engineering" class="level3">
<h3 class="anchored" data-anchor-id="start-with-solid-software-engineering">Start with solid software engineering</h3>
<p>The building blocks of agentic workflows are not new: loops, strings being passed between tools, arrays of floats for embeddings, HTTP requests, JSON. So the established concepts of writing clean code, enforcing type safety, having a test environment and running automated tests apply. A good agentic app starts with a good app.</p>
</section>
<section id="agent-frameworks-are-optional" class="level3">
<h3 class="anchored" data-anchor-id="agent-frameworks-are-optional">Agent frameworks are optional</h3>
<p>Going back to Eugene Yan’s definition of agents as “model + tools, within a for-loop + environment”, it’s clear that agents can be implemented in any programming language that can make HTTP requests. In <a href="https://www.anthropic.com/research/building-effective-agents">Building Effective Agents</a>, Anthropic notes:</p>
<blockquote class="blockquote">
<p>Consistently, the most successful implementations weren’t using complex frameworks or specialized libraries. Instead, they were building with simple, composable patterns.</p>
</blockquote>
<p>So should you use an agent framework or not? Let’s examine the tradeoffs:</p>
<div class="columns">
<div class="column" style="width:50%;">
<p><strong>Reasons to use frameworks ✅</strong></p>
<ul>
<li>Boost early development speed with pre-packaged patterns and integrations</li>
<li>Provide mental models for workflow structure (e.g.&nbsp;CrewAI’s role-playing metaphor)</li>
<li>Easier onboarding for new colleagues familiar with the framework</li>
<li>Express complex workflows concisely</li>
<li>Tap into pre-built tool integrations for data input, monitoring, etc.</li>
</ul>
</div><div class="column" style="width:50%;">
<p><strong>Reasons to avoid frameworks ❌</strong></p>
<ul>
<li>Often immature with frequent bugs and unclear documentation</li>
<li>Breaking API changes</li>
<li>Many dependencies</li>
<li>Force programming in “framework way”</li>
<li>Many have a monolithic design, instead of composable unix philosophy</li>
<li>Tendency to reinvent the wheel in a less production grade way</li>
<li>Steep learning curve, depending on the framework</li>
</ul>
</div>
</div>
<p>As a case study, the AI test automation company Octomind wrote an <a href="https://www.octomind.dev/blog/why-we-no-longer-use-langchain-for-building-our-ai-agents">article</a> comparing LangChain to vanilla Python, explaining why they moved away from frameworks altogether.</p>
</section>
<section id="trace-every-step" class="level3">
<h3 class="anchored" data-anchor-id="trace-every-step">Trace every step</h3>
<p>Regardless of whether you use an agent framework or not, effective monitoring is a must. The first thing to put into place is a system that logs every step of the workflow: user inputs, transformed inputs, tool choices, tool calls, tool call results, reasoning tokens, output, latency, token usage, etc. The best and easiest time to set this up is right at the start of the project. As the project grows, set up a dashboard and alerts for critical metrics.</p>
</section>
<section id="theres-no-substitute-for-manual-inspection" class="level3">
<h3 class="anchored" data-anchor-id="theres-no-substitute-for-manual-inspection">There’s no substitute for manual inspection</h3>
<blockquote class="blockquote">
<p>Manual inspection of data has probably the highest value-to-prestige ratio of any activity in machine learning.</p>
</blockquote>
<p><a href="https://x.com/gdb/status/1622683988736479232">Greg Brockman</a>, President and co-founder of OpenAI</p>
<p>Before going to automatic tests, LLM as judge etc., inspect some workflows manually using the monitoring system. Check the input, the tool calls, the intermittent reasoning tokens, and the output. Many problems can be diagnosed this way. In addition, it peels away some of the “magic” that agentic frameworks by showing the prompts and tool calls. Hamel Husain put it well in an <a href="https://hamel.dev/blog/posts/prompt/">article</a>, asking “Show me the prompt” of every LLM library. Manual inspection keeps yielding insights, even after automatic evals are in place.</p>
</section>
</section>
<section id="conclusion-build-as-agentic-as-needed-not-as-agentic-as-possible" class="level2">
<h2 class="anchored" data-anchor-id="conclusion-build-as-agentic-as-needed-not-as-agentic-as-possible">Conclusion: Build as agentic as needed, not as agentic as possible</h2>
<p>As shown by benchmark scores and user reviews, agentic AI is currently a “65% solution”. There are impressive demos, but the real world is messy. The gap needs to be bridged by careful safeguards, domain-specific heuristics, smart task framing, and human oversight. Much like operating a self-driving car, users need to stay alert and be ready to take control at any moment. However, consider that human workers also make mistakes - perfection is not a realistic benchmark.</p>
<p>From a business perspective, there is still an enormous amount of value to be realized by simpler uses of generative AI. Going all-in on agentic AI may not be necessary. Consider the pros and cons listed above and locate the task at hand on the four-level agency scale.</p>


</section>

 ]]></description>
  <category>Machine Learning</category>
  <category>Agents</category>
  <guid>https://simmering.dev/blog/agentic-ai/</guid>
  <pubDate>Fri, 28 Feb 2025 23:00:00 GMT</pubDate>
  <media:content url="https://simmering.dev/blog/agentic-ai/image.webp" medium="image" type="image/webp"/>
</item>
<item>
  <title>ModernBERT vs LLMs for Detecting Adverse Drug Reactions</title>
  <dc:creator>Paul Simmering</dc:creator>
  <link>https://simmering.dev/blog/modernbert-vs-llm/</link>
  <description><![CDATA[ 






<p>HuggingFace recently released ModernBERT <span class="citation" data-cites="warner2024smarterbetterfasterlonger">(Warner et al. 2024)</span>, an updated version of the BERT language model <span class="citation" data-cites="devlin2018bert">(Devlin 2018)</span> which backports many improvements from LLM research back to the classic 2018 model. In contrast to LLMs, ModernBERT is an encoder-only model that is fitted with a task-specific head outputting probabilities for structured NLP tasks, rather than tokens.</p>
<p>While LLMs with their decoder-only architecture were originally designed for text generation, they have also been used for structured NLP tasks like text classification. They are imbued with a large amount of general knowledge and excel at zero-shot and few-shot learning. Through the proliferation of the LLM ecosystem they are also widely available via APIs and familiar to many developers.</p>
<p>Here, I will compare ModernBERT to Meta’s Llama 3.2-3B by <span class="citation" data-cites="grattafiori2024llama3herdmodels">Grattafiori et al. (2024)</span> on a text classification task using the dimensions accuracy, speed, cost and ease of use. Text classification is a simple task, yet very common and important in NLP pipelines. It may also be coupled with text generation in a chat bot, such as for intent classification or as a guardrail to prevent undesirable responses.</p>
<section id="task-adverse-event-classification" class="level2">
<h2 class="anchored" data-anchor-id="task-adverse-event-classification">Task: Adverse event classification</h2>
<p>During my work in market research for pharmaceutical companies, I frequently have to monitor data for <strong>adverse events</strong>. An adverse event is any undesirable medical event that occurs during or after treatment with a drug. Examples include side effects, lack of efficacy, and overdoses. It is of utmost importance to identify adverse events and report them to the producing pharmaceutical company. This task is labor intensive, so naturally I’m interested in automating it. I’ll use the ADE-Benchmark Corpus <span class="citation" data-cites="gurulingappa_development_2012">Gurulingappa et al. (2012)</span> as an example dataset. It contains 23,500 English medical text sentences describing effects of drugs. Each sentence is classified as 1: adverse drug reaction or 0: no adverse drug reaction. This represents a subtask of the broader task of adverse event monitoring.</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Resource</th>
<th>Link</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>💻 Python code &amp; readme</td>
<td><a href="https://github.com/psimm/website/blob/master/blog/modernbert-vs-llm/">GitHub</a></td>
</tr>
<tr class="even">
<td>📊 Experiment results</td>
<td><a href="https://wandb.ai/psimm/modernbert-vs-llm?nw=0uja1rkfaqe">Weights &amp; Biases project</a></td>
</tr>
<tr class="odd">
<td>📝 Dataset: ADE-Benchmark Corpus</td>
<td><a href="https://huggingface.co/datasets/ade-benchmark-corpus/ade_corpus_v2">Hugging Face Hub</a></td>
</tr>
</tbody>
</table>
<p>All training and inference is done on a single A10G GPU hosted on <a href="https://modal.com">Modal</a>. It costs $1.10/h. A Modal account is required to run the code. The free tier ($30 of free credits per month) is sufficient for this experiment.</p>
</section>
<section id="experiment-setup" class="level2">
<h2 class="anchored" data-anchor-id="experiment-setup">Experiment setup</h2>
<p>The diagram below illustrates three experiment setups: fine-tuning ModernBERT, few-shot learning with Llama 3.2-3B, and fine-tuning Llama 3.2-3B.</p>
<p><img src="https://simmering.dev/blog/modernbert-vs-llm/experiment.svg" class="img-fluid"></p>
<section id="dataset-preparation" class="level3">
<h3 class="anchored" data-anchor-id="dataset-preparation">Dataset preparation</h3>
<p>The dataset on HuggingFace consists of 23,516 sentences. After removing duplicate sentences, 20,896 unique examples are left. The distribution of classes is uneven, with more examples of texts without an adverse events. To balance the classes, I’m subsampling the negative examples down to 4,271 cases. Balanced classes prevent the models from overfitting to the majority class and let us compare the models using a simple accuracy metric.</p>
<p>Then, the dataset is split into 60% training, 20% validation and 20% test sets. The validation set is used to tune hyperparameters and implement early stopping. Splits are stratified by class to ensure a 50:50 split between positive and negative examples in each split. The final example count is:</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Split</th>
<th>Class</th>
<th style="text-align: right;">Examples</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Training</td>
<td>Adverse Event</td>
<td style="text-align: right;">2,562</td>
</tr>
<tr class="even">
<td>Training</td>
<td>No Adverse Event</td>
<td style="text-align: right;">2,562</td>
</tr>
<tr class="odd">
<td>Validation</td>
<td>Adverse Event</td>
<td style="text-align: right;">855</td>
</tr>
<tr class="even">
<td>Validation</td>
<td>No Adverse Event</td>
<td style="text-align: right;">855</td>
</tr>
<tr class="odd">
<td>Test</td>
<td>Adverse Event</td>
<td style="text-align: right;">854</td>
</tr>
<tr class="even">
<td>Test</td>
<td>No Adverse Event</td>
<td style="text-align: right;">854</td>
</tr>
</tbody>
</table>
</section>
<section id="model-selection" class="level3">
<h3 class="anchored" data-anchor-id="model-selection">Model selection</h3>
<p>I’m comparing ModernBERT-base and ModernBERT-large as the structured language models with Llama 3.2-3B-instruct as the LLM.</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Model</th>
<th>Architecture</th>
<th>Parameters</th>
<th>Size at FP32</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><a href="https://huggingface.co/answerdotai/ModernBERT-base">ModernBERT-base</a></td>
<td>Encoder-only: outputs a probability distribution over classes</td>
<td>149M</td>
<td>~0.6GB</td>
</tr>
<tr class="even">
<td><a href="https://huggingface.co/answerdotai/ModernBERT-large">ModernBERT-large</a></td>
<td>Encoder-only: outputs a probability distribution over classes</td>
<td>395M</td>
<td>~1.6GB</td>
</tr>
<tr class="odd">
<td><a href="https://huggingface.co/meta-llama/Llama-3.2-3B">Llama 3.2-3B</a></td>
<td>Decoder-only: outputs text</td>
<td>3B</td>
<td>~12GB</td>
</tr>
</tbody>
</table>
<p>For inference, about 1.5 to 2x the model size is required to store the attention cache, calculate layer activations and other intermediate results. The A10G GPU used for this experiment has 24GB memory, so both models fit. The memory footprint can be reduced by half by using FP16 or INT8 precision, which is common for inference.</p>
</section>
<section id="setup-1-fine-tuning-modernbert" class="level3">
<h3 class="anchored" data-anchor-id="setup-1-fine-tuning-modernbert">Setup 1: Fine-tuning ModernBERT</h3>
<p>I’m using the transformers library to fine-tune ModernBERT base and large on the training set. <span class="citation" data-cites="schmid_fine_tune_2024">Schmid (2024)</span> from Hugging Face wrote a helpful guide which I adapted for use on Modal. The models are optimized on binary cross-entropy loss for 5 epochs. Training took about 2 minutes for ModernBERT-base and 3.5 minutes for ModernBERT-large.</p>
</section>
<section id="setup-2-few-shot-learning-with-llama-3.2-3b-and-dspy" class="level3">
<h3 class="anchored" data-anchor-id="setup-2-few-shot-learning-with-llama-3.2-3b-and-dspy">Setup 2: Few-shot learning with Llama 3.2-3B and DSPy</h3>
<p>I’m using DSPy <span class="citation" data-cites="khattab2023dspycompilingdeclarativelanguage">(Khattab et al. 2023)</span> to automatically select an optimal set of examples for few-shot learning. That’s a more objective approach than manual prompting and usually results in equally good or better accuracy. In my first trials, DSPy didn’t manage to write a suitable system prompt as it didn’t understand the adverse drug reaction task from examples alone. So I added the prompt: “Determine if the following sentence is about adverse drug reactions:” to the examples. This increased the accuracy by about 15 percentage points.</p>
<p>DSPy settings:</p>
<ul>
<li>20 few-shot examples plus 5 bootstrapped (AI generated) examples</li>
<li>Optimized for accuracy using MIPROv2 (minibatch size 50, minibatch full eval steps 10, num trials 3)</li>
<li>25 threads for calls to the LLM, which is hosted using FastAPI and vLLM on Modal</li>
</ul>
<p>The optimized predictor is available as a JSON file in the <a href="https://wandb.ai/psimm/modernbert-vs-llm?nw=0uja1rkfaqe">Weights &amp; Biases project</a>.</p>
</section>
<section id="setup-3-fine-tuning-llama-3.2-3b" class="level3">
<h3 class="anchored" data-anchor-id="setup-3-fine-tuning-llama-3.2-3b">Setup 3: Fine-tuning Llama 3.2-3B</h3>
<p>I’m using the torchtune library and a fine-tuning configuration to train a LoRA adapter on the training set. It targets the attention and feed-forward layers of the model. The adapter is a smaller set of weights that are added to the model at inference time. LoRA training incurs less training cost than full fine-tuning of all weights, but may result in worse accuracy. The LoRA settings used for training are available in the W&amp;B project and the training config file for torchtune. Training took about 8 minutes on the A10G.</p>
</section>
</section>
<section id="results" class="level2">
<h2 class="anchored" data-anchor-id="results">Results</h2>
<section id="accuracy-and-speed" class="level3">
<h3 class="anchored" data-anchor-id="accuracy-and-speed">Accuracy and speed</h3>
<div id="8f4e753b" class="cell" data-execution_count="2">
<div class="cell-output cell-output-display" data-execution_count="4">
<div id="wlsxcaorkl" style="padding-left:0px;padding-right:0px;padding-top:10px;padding-bottom:10px;overflow-x:auto;overflow-y:auto;width:auto;height:auto;">
<style>
#wlsxcaorkl table {
          font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Helvetica Neue', 'Fira Sans', 'Droid Sans', Arial, sans-serif;
          -webkit-font-smoothing: antialiased;
          -moz-osx-font-smoothing: grayscale;
        }

#wlsxcaorkl thead, tbody, tfoot, tr, td, th { border-style: none; }
 tr { background-color: transparent; }
#wlsxcaorkl p { margin: 0; padding: 0; }
 #wlsxcaorkl .gt_table { display: table; border-collapse: collapse; line-height: normal; margin-left: auto; margin-right: auto; color: #333333; font-size: 16px; font-weight: normal; font-style: normal; background-color: #FFFFFF; width: auto; border-top-style: solid; border-top-width: 2px; border-top-color: #004D80; border-right-style: none; border-right-width: 2px; border-right-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #004D80; border-left-style: none; border-left-width: 2px; border-left-color: #D3D3D3; }
 #wlsxcaorkl .gt_caption { padding-top: 4px; padding-bottom: 4px; }
 #wlsxcaorkl .gt_title { color: #333333; font-size: 125%; font-weight: initial; padding-top: 4px; padding-bottom: 4px; padding-left: 5px; padding-right: 5px; border-bottom-color: #FFFFFF; border-bottom-width: 0; }
 #wlsxcaorkl .gt_subtitle { color: #333333; font-size: 85%; font-weight: initial; padding-top: 3px; padding-bottom: 5px; padding-left: 5px; padding-right: 5px; border-top-color: #FFFFFF; border-top-width: 0; }
 #wlsxcaorkl .gt_heading { background-color: #FFFFFF; text-align: center; border-bottom-color: #FFFFFF; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; }
 #wlsxcaorkl .gt_bottom_border { border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #0076BA; }
 #wlsxcaorkl .gt_col_headings { border-top-style: solid; border-top-width: 2px; border-top-color: #0076BA; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #0076BA; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; }
 #wlsxcaorkl .gt_col_heading { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: normal; text-transform: inherit; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: bottom; padding-top: 5px; padding-bottom: 5px; padding-left: 5px; padding-right: 5px; overflow-x: hidden; }
 #wlsxcaorkl .gt_column_spanner_outer { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: normal; text-transform: inherit; padding-top: 0; padding-bottom: 0; padding-left: 4px; padding-right: 4px; }
 #wlsxcaorkl .gt_column_spanner_outer:first-child { padding-left: 0; }
 #wlsxcaorkl .gt_column_spanner_outer:last-child { padding-right: 0; }
 #wlsxcaorkl .gt_column_spanner { border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #0076BA; vertical-align: bottom; padding-top: 5px; padding-bottom: 5px; overflow-x: hidden; display: inline-block; width: 100%; }
 #wlsxcaorkl .gt_spanner_row { border-bottom-style: hidden; }
 #wlsxcaorkl .gt_group_heading { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; text-transform: inherit; border-top-style: solid; border-top-width: 2px; border-top-color: #0076BA; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #0076BA; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; text-align: left; }
 #wlsxcaorkl .gt_empty_group_heading { padding: 0.5px; color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; border-top-style: solid; border-top-width: 2px; border-top-color: #0076BA; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #0076BA; vertical-align: middle; }
 #wlsxcaorkl .gt_from_md> :first-child { margin-top: 0; }
 #wlsxcaorkl .gt_from_md> :last-child { margin-bottom: 0; }
 #wlsxcaorkl .gt_row { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: none; border-top-width: 1px; border-top-color: #89D3FE; border-left-style: none; border-left-width: 1px; border-left-color: #89D3FE; border-right-style: none; border-right-width: 1px; border-right-color: #89D3FE; vertical-align: middle; overflow-x: hidden; }
 #wlsxcaorkl .gt_stub { color: #FFFFFF; background-color: #0076BA; font-size: 100%; font-weight: initial; text-transform: inherit; border-right-style: solid; border-right-width: 2px; border-right-color: #0076BA; padding-left: 5px; padding-right: 5px; }
 #wlsxcaorkl .gt_stub_row_group { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; text-transform: inherit; border-right-style: solid; border-right-width: 2px; border-right-color: #D3D3D3; padding-left: 5px; padding-right: 5px; vertical-align: top; }
 #wlsxcaorkl .gt_row_group_first td { border-top-width: 2px; }
 #wlsxcaorkl .gt_row_group_first th { border-top-width: 2px; }
 #wlsxcaorkl .gt_striped { background-color: #F4F4F4; }
 #wlsxcaorkl .gt_table_body { border-top-style: solid; border-top-width: 2px; border-top-color: #0076BA; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #0076BA; }
 #wlsxcaorkl .gt_sourcenotes { color: #333333; background-color: #FFFFFF; border-bottom-style: none; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 2px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 2px; border-right-color: #D3D3D3; }
 #wlsxcaorkl .gt_sourcenote { font-size: 90%; padding-top: 4px; padding-bottom: 4px; padding-left: 5px; padding-right: 5px; text-align: left; }
 #wlsxcaorkl .gt_left { text-align: left; }
 #wlsxcaorkl .gt_center { text-align: center; }
 #wlsxcaorkl .gt_right { text-align: right; font-variant-numeric: tabular-nums; }
 #wlsxcaorkl .gt_font_normal { font-weight: normal; }
 #wlsxcaorkl .gt_font_bold { font-weight: bold; }
 #wlsxcaorkl .gt_font_italic { font-style: italic; }
 #wlsxcaorkl .gt_super { font-size: 65%; }
 #wlsxcaorkl .gt_footnote_marks { font-size: 75%; vertical-align: 0.4em; position: initial; }
 #wlsxcaorkl .gt_asterisk { font-size: 100%; vertical-align: 0; }
 
</style>

<table class="gt_table caption-top table table-sm table-striped small" data-quarto-bootstrap="false">
<thead>
<tr class="gt_heading header">
<td colspan="6" class="gt_heading gt_title gt_font_normal">Model Performance Comparison</td>
</tr>
<tr class="gt_col_headings even">
<th id="Setup" class="gt_col_heading gt_columns_bottom_border gt_left" data-quarto-table-cell-role="th" scope="col">Setup</th>
<th id="F1 Score (%)" class="gt_col_heading gt_columns_bottom_border gt_right" data-quarto-table-cell-role="th" scope="col">F1 Score (%)</th>
<th id="Recall (%)" class="gt_col_heading gt_columns_bottom_border gt_right" data-quarto-table-cell-role="th" scope="col">Recall (%)</th>
<th id="Precision (%)" class="gt_col_heading gt_columns_bottom_border gt_right" data-quarto-table-cell-role="th" scope="col">Precision (%)</th>
<th id="Examples/sec" class="gt_col_heading gt_columns_bottom_border gt_right" data-quarto-table-cell-role="th" scope="col">Examples/sec</th>
<th id="Configuration" class="gt_col_heading gt_columns_bottom_border gt_left" data-quarto-table-cell-role="th" scope="col">Configuration</th>
</tr>
</thead>
<tbody class="gt_table_body">
<tr class="odd">
<td class="gt_row gt_left">1a-modernbert-base</td>
<td class="gt_row gt_right">86.0</td>
<td class="gt_row gt_right">90.3</td>
<td class="gt_row gt_right">82.2</td>
<td class="gt_row gt_right">118</td>
<td class="gt_row gt_left">transformers pipeline, batch size 128</td>
</tr>
<tr class="even">
<td class="gt_row gt_left gt_striped">1b-modernbert-large</td>
<td class="gt_row gt_right gt_striped">89.2</td>
<td class="gt_row gt_right gt_striped">91.8</td>
<td class="gt_row gt_right gt_striped">86.8</td>
<td class="gt_row gt_right gt_striped">87</td>
<td class="gt_row gt_left gt_striped">transformers pipeline, batch size 128</td>
</tr>
<tr class="odd">
<td class="gt_row gt_left">2-DSPy-25-threads-Llama-3.2-3B-Instruct</td>
<td class="gt_row gt_right">80.7</td>
<td class="gt_row gt_right">87.9</td>
<td class="gt_row gt_right">74.6</td>
<td class="gt_row gt_right">4</td>
<td class="gt_row gt_left">DSPy, 25 threads, vLLM OpenAI server with default settings</td>
</tr>
<tr class="even">
<td class="gt_row gt_left gt_striped">3-Llama-3.2-3B-Instruct-LoRA</td>
<td class="gt_row gt_right gt_striped">93.1</td>
<td class="gt_row gt_right gt_striped">92.0</td>
<td class="gt_row gt_right gt_striped">94.2</td>
<td class="gt_row gt_right gt_striped">152</td>
<td class="gt_row gt_left gt_striped">vLLM, default settings</td>
</tr>
</tbody><tfoot class="gt_sourcenotes">
<tr class="odd">
<td colspan="6" class="gt_sourcenote">*Speed of setup 2 is limited by DSPy. Speeds similar to setup 3 can be achieved with efficient batching.</td>
</tr>
</tfoot>

</table>


</div>
</div>
</div>
<p>ModernBERT-base and ModernBERT-large performed similarly. Large has a 3.2 percentage point advantage in F1 score but at the cost of 27% slower inference. The few-shot approach doesn’t need nearly as much training data, but also results in a less accurate model. It also ran slowly, and this didn’t change when I increased the number of threads used by DSPy to communicate with the vLLM server. I suspect it’s due to inefficient batch inference code in DSPy. Higher speeds could be achieved with a more efficient batching approach.</p>
<p>The clear winner of the experiment is the LoRA fine-tuned Llama 3.2-3B. It’s the most accurate and the fastest. This is down to vLLM being extremely well optimized and using CUDA graph capturing ahead of inference time. If that preparation time of 30 seconds is added, it’s examples per second go down to 42.</p>
</section>
<section id="cost-and-effort" class="level3">
<h3 class="anchored" data-anchor-id="cost-and-effort">Cost and effort</h3>
<p>All setups can be trained for under one dollar and in less than 15 minutes. The differences are negligible. What matters more is the time spent setting up training and inference. The transformers library and the tutorial made it very easy to fine-tune ModernBERT and run inference. A major plus is that due to its low size, it can run on CPU at good speed too. DSPy was more involved because it required setting up a vLLM server too. This step is easier when using a managed service like Fireworks AI. Fine-tuning Llama 3.2-3B was the most involved step, as it required formatting the data in a chat format and going through the detailed configuration of the torchtune library and vLLM. Still, it only took a few hours. This step is also easier with a managed service.</p>
</section>
</section>
<section id="discussion" class="level2">
<h2 class="anchored" data-anchor-id="discussion">Discussion</h2>
<section id="implications-for-nlp" class="level3">
<h3 class="anchored" data-anchor-id="implications-for-nlp">Implications for NLP</h3>
<section id="fine-tuning-vs-prompt-based-approaches" class="level4">
<h4 class="anchored" data-anchor-id="fine-tuning-vs-prompt-based-approaches">Fine-tuning vs prompt-based approaches</h4>
<p>Fine-tuning continues to outperform purely prompt-based approaches, even when those are optimized using automated prompt engineering. If you have enough examples to fine-tune on, it’s a good idea to do so. Still the recall achieved by the few-shot approach is impressive and can serve as a strong baseline and starting point in the development of text classification systems.</p>
</section>
<section id="model-size-and-architecture" class="level4">
<h4 class="anchored" data-anchor-id="model-size-and-architecture">Model size and architecture</h4>
<p>In fine-tuning, the size of the model is a key factor. Here, ModernBERT did well and is a strong choice for text classification and other structured NLP tasks. ModernBERT-large offers a modest accuracy improvement in exchange for slower inference. However, Llama 3.2-3B with a fine-tuned LoRA adapter outperformed it in accuracy in this experiment. Its architecture as a decoder-only model is, in theory, less suited for structured tasks. Did it win by sheer size? It would be interesting to see what a ModernBERT-3B or -8B model would achieve. In a related task of sentiment analysis <span class="citation" data-cites="zhou_comprehensive_2024">(Zhou et al. 2024)</span>, the scaling limit was found to be at 8 billion parameters with a decoder-only model.</p>
</section>
<section id="processing-speed" class="level4">
<h4 class="anchored" data-anchor-id="processing-speed">Processing speed</h4>
<p>Processing speed is highly sensitive to the hardware and inference setup. Thanks to vLLM’s CUDA graph capturing and other optimizations, the Llama 3.2-3B LoRA adapter ran faster than the ModernBERT models in this experiment, despite its size. Perhaps the efficiency optimizations made in LLM research could be backported to encoder-only models too, just like ModernBERT backported training techniques from LLMs back to the classic 2018 model. Note that this speed comparison was not comprehensive and is dependent on the GPU, the inference library and the exact settings used, such as batch size.</p>
</section>
</section>
<section id="implications-for-adverse-event-monitoring" class="level3">
<h3 class="anchored" data-anchor-id="implications-for-adverse-event-monitoring">Implications for adverse event monitoring</h3>
<section id="greater-sensitivity-with-larger-models" class="level4">
<h4 class="anchored" data-anchor-id="greater-sensitivity-with-larger-models">Greater sensitivity with larger models</h4>
<p>The primary metric for adverse event monitoring is sensitivity, as missing a true adverse event is much more costly than flagging a false positive. The results show a sensitivity of 92% in detection of adverse drug reactions in medical texts using a Llama 3.2-3B. It outperforms previous approaches that used a convolutional neural network <span class="citation" data-cites="huynh_adverse_2016">(Huynh et al. 2016, 89%)</span> and a BERT sentence embeddings model <span class="citation" data-cites="haq_mining_2022">(Haq, Kocaman, and Talby 2022, 85%)</span>. This advance is a step towards an automated adverse event monitoring system. With larger models and more training data, the sensitivity can be improved further.</p>
</section>
<section id="towards-a-production-system" class="level4">
<h4 class="anchored" data-anchor-id="towards-a-production-system">Towards a production system</h4>
<p>A production system for automated adverse event monitoring would need a more comprehensive approach:</p>
<ul>
<li>Adjustable threshold for flagging adverse events</li>
<li>Flagging of complex cases for human review</li>
<li>Tests and training data for other languages, other text types such as case reports, social media and interview transcripts</li>
<li>Tests and training data for other adverse types, such as overdose, lack of efficacy, and use during pregnancy or breastfeeding</li>
</ul>
<p>None of these require new breakthroughs in AI - they are doable with current technology.</p>
<hr>
<p>Preview image generated with FLUX.1-schnell and DiffusionBee.</p>



</section>
</section>
</section>

<div id="quarto-appendix" class="default"><section class="quarto-appendix-contents" id="quarto-bibliography"><h2 class="anchored quarto-appendix-heading">References</h2><div id="refs" class="references csl-bib-body hanging-indent" data-entry-spacing="0">
<div id="ref-devlin2018bert" class="csl-entry">
Devlin, Jacob. 2018. <span>“Bert: Pre-Training of Deep Bidirectional Transformers for Language Understanding.”</span> <em>arXiv Preprint arXiv:1810.04805</em>.
</div>
<div id="ref-grattafiori2024llama3herdmodels" class="csl-entry">
Grattafiori, Aaron, Abhimanyu Dubey, Abhinav Jauhri, Abhinav Pandey, Abhishek Kadian, Ahmad Al-Dahle, Zef Rosnbrick, et al. 2024. <span>“The Llama 3 Herd of Models.”</span> <a href="https://arxiv.org/abs/2407.21783">https://arxiv.org/abs/2407.21783</a>.
</div>
<div id="ref-gurulingappa_development_2012" class="csl-entry">
Gurulingappa, Harsha, Abdul Mateen Rajput, Angus Roberts, Juliane Fluck, Martin Hofmann-Apitius, and Luca Toldo. 2012. <span>“Development of a Benchmark Corpus to Support the Automatic Extraction of Drug-Related Adverse Effects from Medical Case Reports.”</span> <em>Journal of Biomedical Informatics</em>, Text <span>Mining</span> and <span>Natural</span> <span>Language</span> <span>Processing</span> in <span>Pharmacogenomics</span>, 45 (5): 885–92. <a href="https://doi.org/10.1016/j.jbi.2012.04.008">https://doi.org/10.1016/j.jbi.2012.04.008</a>.
</div>
<div id="ref-haq_mining_2022" class="csl-entry">
Haq, Hasham Ul, Veysel Kocaman, and David Talby. 2022. <span>“Mining <span>Adverse</span> <span>Drug</span> <span>Reactions</span> from <span>Unstructured</span> <span>Mediums</span> at <span>Scale</span>.”</span> arXiv. <a href="https://doi.org/10.48550/arXiv.2201.01405">https://doi.org/10.48550/arXiv.2201.01405</a>.
</div>
<div id="ref-huynh_adverse_2016" class="csl-entry">
Huynh, Trung, Yulan He, Alistair Willis, and Stefan Rüger. 2016. <span>“Adverse <span>Drug</span> <span>Reaction</span> <span>Classification</span> <span>With</span> <span>Deep</span> <span>Neural</span> <span>Networks</span>.”</span> In, 877–87. Osaka: COLING. <a href="http://coling2016.anlp.jp/doc/main.pdf">http://coling2016.anlp.jp/doc/main.pdf</a>.
</div>
<div id="ref-khattab2023dspycompilingdeclarativelanguage" class="csl-entry">
Khattab, Omar, Arnav Singhvi, Paridhi Maheshwari, Zhiyuan Zhang, Keshav Santhanam, Sri Vardhamanan, Saiful Haq, et al. 2023. <span>“DSPy: Compiling Declarative Language Model Calls into Self-Improving Pipelines.”</span> <a href="https://arxiv.org/abs/2310.03714">https://arxiv.org/abs/2310.03714</a>.
</div>
<div id="ref-schmid_fine_tune_2024" class="csl-entry">
Schmid, Philipp. 2024. <span>“Fine-Tune Classifier with <span>ModernBERT</span> in 2025.”</span> <a href="https://www.philschmid.de/fine-tune-modern-bert-in-2025">https://www.philschmid.de/fine-tune-modern-bert-in-2025</a>.
</div>
<div id="ref-warner2024smarterbetterfasterlonger" class="csl-entry">
Warner, Benjamin, Antoine Chaffin, Benjamin Clavié, Orion Weller, Oskar Hallström, Said Taghadouini, Alexis Gallagher, et al. 2024. <span>“Smarter, Better, Faster, Longer: A Modern Bidirectional Encoder for Fast, Memory Efficient, and Long Context Finetuning and Inference.”</span> <a href="https://arxiv.org/abs/2412.13663">https://arxiv.org/abs/2412.13663</a>.
</div>
<div id="ref-zhou_comprehensive_2024" class="csl-entry">
Zhou, Changzhi, Dandan Song, Yuhang Tian, Zhijing Wu, Hao Wang, Xinyu Zhang, Jun Yang, Ziyi Yang, and Shuhao Zhang. 2024. <span>“A <span>Comprehensive</span> <span>Evaluation</span> of <span>Large</span> <span>Language</span> <span>Models</span> on <span>Aspect</span>-<span>Based</span> <span>Sentiment</span> <span>Analysis</span>.”</span> arXiv. <a href="https://doi.org/10.48550/arXiv.2412.02279">https://doi.org/10.48550/arXiv.2412.02279</a>.
</div>
</div></section></div> ]]></description>
  <category>Machine Learning</category>
  <guid>https://simmering.dev/blog/modernbert-vs-llm/</guid>
  <pubDate>Sat, 11 Jan 2025 23:00:00 GMT</pubDate>
  <media:content url="https://simmering.dev/blog/modernbert-vs-llm/image.webp" medium="image" type="image/webp"/>
</item>
<item>
  <title>Diagrams as Code: Supercharged by AI Assistants</title>
  <dc:creator>Paul Simmering</dc:creator>
  <link>https://simmering.dev/blog/diagrams/</link>
  <description><![CDATA[ 






<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://simmering.dev/blog/diagrams/diagrams_wide.webp" class="img-fluid figure-img"></p>
<figcaption>D2 code and rendered diagram of a Kafka cluster for a web shop</figcaption>
</figure>
</div>
<p>Diagrams as code are an efficient way to communicate complex ideas and document software architecture. In this post I’ll explain how an AI assistant makes them even better.</p>
<section id="what-is-diagrams-as-code" class="level2">
<h2 class="anchored" data-anchor-id="what-is-diagrams-as-code">What is diagrams as code?</h2>
<p>It’s a diagram that is generated from markdown-like text. Rather than clicking and dragging, you write the text and the diagram is generated. Elements are automatically positioned and connected using a layout engine. This lets you focus on the content, rather than the look, at the expense of some flexibility. Since the diagrams are text-based, they can be version controlled with Git.</p>
<p>Here’s a quick example of a diagram as code in <a href="https://d2lang.com/">D2</a>:</p>
<pre><code>shape: sequence_diagram
User -&gt; LLM: How many R's are in the word "strawberry"?
LLM -&gt; User: The word "strawberry" contains 2 instances of the letter "r".</code></pre>
<p>This generates the following diagram:</p>
<p><img src="https://simmering.dev/blog/diagrams/data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIGRhdGEtZDItdmVyc2lvbj0iMC43LjEiIHByZXNlcnZlQXNwZWN0UmF0aW89InhNaW5ZTWluIG1lZXQiIHZpZXdCb3g9IjAgMCA1NjkgMzI4Ij48c3ZnIGNsYXNzPSJkMi02Mjk3ODIyMDMgZDItc3ZnIiB3aWR0aD0iNTY5IiBoZWlnaHQ9IjMyOCIgdmlld0JveD0iMSA0MSA1NjkgMzI4Ij48cmVjdCB4PSIxLjAwMDAwMCIgeT0iNDEuMDAwMDAwIiB3aWR0aD0iNTY5LjAwMDAwMCIgaGVpZ2h0PSIzMjguMDAwMDAwIiByeD0iMC4wMDAwMDAiIGZpbGw9InRyYW5zcGFyZW50IiBzdHJva2Utd2lkdGg9IjAiIC8+PHN0eWxlIHR5cGU9InRleHQvY3NzIj48IVtDREFUQVsKLmQyLTYyOTc4MjIwMyAudGV4dCB7Cglmb250LWZhbWlseTogImQyLTYyOTc4MjIwMy1mb250LXJlZ3VsYXIiOwp9CkBmb250LWZhY2UgewoJZm9udC1mYW1pbHk6IGQyLTYyOTc4MjIwMy1mb250LXJlZ3VsYXI7CglzcmM6IHVybCgiZGF0YTphcHBsaWNhdGlvbi9mb250LXdvZmY7YmFzZTY0LGQwOUdSZ0FCQUFBQUFBMUFBQW9BQUFBQUZFd0FBZ3VGQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFCUFV5OHlBQUFBOUFBQUFHQUFBQUJnWGQvVm8yTnRZWEFBQUFGVUFBQUFwd0FBQU9BRUpBU3daMng1WmdBQUFmd0FBQWE5QUFBSTdMaml1VzVvWldGa0FBQUl2QUFBQURZQUFBQTJHNFVlMzJob1pXRUFBQWowQUFBQUpBQUFBQ1FLaEFYaWFHMTBlQUFBQ1JnQUFBQ0FBQUFBZ0RsTkJ3NXNiMk5oQUFBSm1BQUFBRUlBQUFCQ0prb2ovbTFoZUhBQUFBbmNBQUFBSUFBQUFDQUFPQUQyYm1GdFpRQUFDZndBQUFNakFBQUlGQWJEVlUxd2IzTjBBQUFOSUFBQUFCMEFBQUFnLzlFQU1nQURBZ2tCa0FBRkFBQUNpZ0pZQUFBQVN3S0tBbGdBQUFGZUFESUJJd0FBQWdzRkF3TUVBd0lDQkdBQUF2Y0FBQUFEQUFBQUFBQUFBQUJCUkVKUEFFQUFJUC8vQXU3L0JnQUFBOWdCRVNBQUFaOEFBQUFBQWVZQ2xBQUFBQ0FBQTNpY2hNMjdLa2NCQU1meHovbWY0MzdjNzdjNm5jVm04QUpXQTh1SkI3Q0lVa3BLbm9Za2RnYmxPWXdXazhWcU12d1VtK1gvblQvMVJhRlVvRlo1UmF0UjZXbTBObXphc20zSG5zNkJJeWZPWExoMGxmRFA3T3JzTzNUczFQbWZ5WHUrODVYUGZPUXRMM25PVXg3emtQdmM1VFkzdWY0OTk2dXdiczJLWmF0NlNwVUJnNFlNR3pGcVRHM2NoRWxUcHMyWU5XZmVna1ZML0FBQUFQLy9BUUFBLy84QUhDeTVBSGljWEZaZGJCdFpGVDczanVPSkd5Zk9OTGJIazlpeFoyNHlFenRPbkhqc21TUjJ4MDM4MHlTTlk5ZE9TSk8wMlpaMDYzUUwxUkswVkJYcFZsRFlycEFRUnVxS0J5U29BS21Ba0twZHBPNGkzZ3FVd0M2Z1JZZ2ZpUWUwRDltVktpUXdma0JpTTBZemR0SmtuenpTdmZyTzkzM25PK2NhMm1BVkFNZnhmYURBQmc0NENTNEFtZUdaUVY2U0NLM0txa3BZU3BVUVE2K2l2K3RWaE9aaUZrV3hqTTg4bTdsMTV3NDYveXErdi8rWnFhOVVLci9hdUhsVC8vcmVSM29VL2Y0alFMQUdnS080Q2gwR25vRW9NNFRobWJVeUdsOWEwditBcS9vL1VjLyt5eWl1dnd2UXZJL2V3MVZvYTkxM3JaV1JIMWYzM3o0REIrZjROcTZDenp6dmNidFpXVkhVSGdNMXBxaUVwZ2dsRWJmYnhheGRlZFhPMmkxMmwzM254Y1YyeWhMYlVYZGlGb3JHVmYxN1FsWVFzZ0xhMkg4WlhRdGZIMzVEL3dsYWVtUDRlbGovRmdCZ3MwWUFWNkVMV0xOSzFPMTJPYTEwRDZFSXc4aFJKUjRUQ1ZuN1hYb3JVY3c4M1BqT3pSdjVVaWwvQTFmSnVjekNSVWIvQUxuMFoyZzFkWG82MXVUc0JVQWY0eXJRQmhxSjh5N0NmUEFVL2VNcG5zL2w5aDgzNzZ3MDZuZ1VWdzNmMndSUmpETXk0M1M3NWFpaW1KOVdLMHFucjJ2bFlIWTRuQXNXdFpmc3lzNDE5Q1g5ZG1GZEZOY0w2SzUrNTlxT0FoaGlqVHA2RTlXZ0Z3WUFXRUdNeHhRMUpvcEVzTktTb3NoUnQ0c2hFckZhcGFpaXhxMVdsOVA5NU5TNWIzeWJHUjRLemZzQ3d1YlVhakZEVThJNU45SElyY3RSKzl4MGNabnhUNUNBYzlJZC9PeTYvdWNwYjJoRzhOOXpKQ1BCUWNCUWF0VFIvL0F1OUVEQVpDNFJtakN5aTI3V2NwcUZETDhFSysxeXUxRlFtQXRROUV3Sjg0V2hGejZkZUNHWExDU3kvdE1ra0xMenZpamVmWExlSjczMnVmSVh0R3hscmJncEJCcGV0dW5QYUtPT0hxR2E0YVhwVDh3c3dOS21ORU9HSEZWVTFtcEZKMDl2SmFldmEyTlpMdVNLK01KWnFad1dwdHdEZk5HZTNDNld0cE1DcS9SNElzc1Q1WXJQcWZwNG85K1JSaDM5N1VCRDB6TVRYSXJMQjJhcDhjTkMvMTIva2Jpc2hyU0FwWnloS2U4Q2R6cnBuK3lYVW1MTy90VmJoYzlyL2IzbG4rOVBUSHFEMmJUdVpTUGxpWlZOd0NiLzM2SWFlTUIvVElFUkxONTl3SjdpVGFzUU8vMlNscnFpWG53UllmMmR0cFVjU2ZUNS9JVjNrU1UxS1orem45b3VGTGUxbmExT3pwYS80R0lVWno4UzUvTUYwNmQrQUpUQ2Yyck9MNG1yOFZqTEp5SzRYTEtMTUpkbVpySnpiS2o3Wko4M1U2bWc3MnR0K2ZrVkc1MnliK1RUK2tVVG93U0Evb0ozd1dtbS82Q1h4b1NaZldSS0pZcmtvL2t6cGZEWVlHSVE3ejY1d2tjdVg5VGZROEdNSmc3cUQ2RFJnQ3dBL0JRL3hxTEJDS3pnMzJuMnNkU293MS94TGppYVRwc3hiNG4vMFdpdzFHV3owSFJIdTlzK0djZFg5Ky8zTUFocEZrdVRFLzQzcWdGdmNqS0czcEIxakJsOStGdkswRlJnWVhnaTVSQVh3MmZuU3VGUkpWTUtSNVFNMnN1UnlIZzRHRHVnZTFaLzBQbzUwSTFxTGQydEdrZDFaMmlLTEI0S044R082VzcxK1Yrb0JnN29POWJuNDdQZ2NycVJJMUZKcFNxSjVOVlU2bW95bGMrbnRNWEZWa2FUMjZYaWRqSlRLUzl0YlMyVkt3YzkyVUExWUk1d2E2Vy9TWXliRGZyWWJydlQ0VTl6YU8vOHFISmkxbUtKYXZwdWF3YzE2dWd1cWtISTlGMVN6Y2pGWTZJb2plTERqTFNvdWRsK2JOQjlQN1pCZ29ITThOZ1lML2NKTTZIVndzaWlkNGhUQXFQRC9XTjlKRE1TTE5nbHI4cnhJMzVPWUU5MDh2RmdvaEJnWXoyZWtKZjF1VG82ZVhWVW1oa3k2M3NhZFpURk40eU5hdmFkeEZWVk5nTjUyUDluaTZkbUYwNWs3OTdsUTUzOTltNW54TDQyaXpxMXR0ZGZUK3Uxa1hHYlJhTTdtbGdBK0RIYU03TkFIWGtEbm45UmhCSkZZM0pwNnJ2M2xtYmJ1MmhMZTdmdGJISEJ4clJiMmgzMG1jVXZYOG5aSERaTGUvZUpETnJUUHhUU2dwQVdFSGZrcXhlMWtjemdZSmJvSHdNQ2V5T0NmbzMyaks0KzkwOVZqNWFudXZCYXQ4L2UzZTYwQlJWSHh5K1dOenU0RGt1SDg4Uks4VzBta24zZmFwbkdiWW1SQWZTaC9oLy9yTURQQmxEbmZtMXNZY1RRTkFDQWZvbS9adURMY1EyM1ZxWjB1RWFOSVpSZFE1ZGV5eVZQRFdXOGthRjFiZlZxK3BXRjNnbnVaK09YdnZtS3JPWkdBcEZ3dkxLYy9PSzlBcmFjQVFUVGpUcThBOXZHMjl2Y2FrMnMyeHdobkljUU8rbnpFZUxySTBadXhVWWQzY0kvQmc0R0FWUlJvejdCb0l0aTZTTVlEMmpQOEVoZ2ZJeFBzSkhBc3Jxd0hvaUUrOW9FVGhBNGpwQ25rZG1rb2dTR0p2MTl3d09SVDgxTHFZbXBtZEFmbjlkRFVFU1A0SWY0TGVPZDc1RWttYVkzdTZuelZEZDY5UERDaFllTmhuR09WdkZiSUJvYkgyand3cHNIdXdsK2dQYUFhdjQvS0pYUW50NExxUEViUEE4cWZteG9aWTd3OVBqOUhvL2ZqK2Q5bktlLzM4UDU0UDhBQUFELy93RUFBUC8vQzZmV2FnQUFBQUFCQUFBQUFndUZWdkhJMlY4UFBQVUFBd1BvQUFBQUFOaGRvS0VBQUFBQTNXWXZOdjQ2L3RzSWJ3UElBQUFBQXdBQ0FBQUFBQUFBQUFFQUFBUFkvdThBQUFpWS9qcitPZ2h2QUFFQUFBQUFBQUFBQUFBQUFBQUFBQUFnQW8wQVdRRElBQUFDakFCYUFlWUFXZ0xYQUZvQ09RQmFBaGdBSEFLRkFGY0IrQUEwQWlrQVVnSElBQzRDS3dBdkFmQUFMZ0VrQUI0Q0lBQlNBUFlBUlFEL0FGSURQUUJTQWlNQVVnSWVBQzRCV3dCU0FhTUFIQUZTQUJnQ3pnQVlBZE1BREFIeEFDUUErUUJCQWFrQUpnRDVBRkFCcWdCUUFQWUFVZ0FBLzhrQUFBQXNBQ3dBUkFCVUFJWUFyZ0RBQU9RQkhBRlFBWDRCc0FIa0FnWUNLQUkwQWxBQ2dnS2tBdEFDOEFNd0ExWURrQVBBQStvRUFBUTRCRWdFVkFSZ0JIWUFBQUFCQUFBQUlBQ01BQXdBWmdBSEFBRUFBQUFBQUFBQUFBQUFBQUFBQkFBRGVKeWNsTjFPRzFjVWhUOEgyMjFVTlJjVmlzZ05PcGR0bFl6ZENLSUVya3dKaWxXRVU0L1RINm1xTkhqR1AySThNL0lNVUtvK1FLLzdGbjJMWFBVNStoQlZyNnV6dkEwMnFoU0JFTERPbkwzM1dXZXZ0USt3eWI5c1VLcy9CUDVxL21DNHhuWnp6L0FESGpXZkd0N2d1UEczNGZwS1RJTzQ4WnZoSmw4MitvWS80bjM5RDhNZnMxUC8yZkJEdHVwSGhqL2hlWDNUOEtjYmpuOE1QMktIOXd0Y2c1ZjhicmpHRm9YaEIyenlrK0VOSG1NMWEzVWUwemJjNERPMkRUZlpCZ1pNcVVpWmtqSEdNV0xLbUhQbUpKU0VKTXlaTWlJaHh0R2xRMHFscnhtUmtHUDh2MThqUWlybVJLbzRvY0tSRXBJU1VUS3hpcjhxSytldFRoeHBOYmU5RGhVVElrNlZjVVpFaGlOblRFNUd3cG5xVkZRVTdOR2lSY2xRZkFzcVNnSktwcVFFNU13WjA2TEhFY2NNbURDbHhIR2tTcDVaU002SWlrc2luZThzd25kbVNFSkdhYXpPeVlqRjA0bGZvdXd1eHpoNkZJcGRyWHk4VnVFcGp1K1U3Ym5saXYyS1FMOXVoZG42dVVzMkVSZnFaNnF1cE5xNWxJSVQ3ZnB6TzN3clhMR0h1MWQvMXBsOHVFZXgvbGVxZk1xNTlJK2xWQ1ltR2M1dDBTR1VnMEwzQk1lQjFsMUNkZVI3dWd4NFE0OTNETFR1MEtkUGh4TUdkSG10M0I1OUhGL1Q0NFJEWlhTRkYzdEhjc3dKUCtMNGhxNWlmTzNFK3JOUUxPRVhDbk4zS1k1ejNXTkdvWjU3NW9IdW11aUdkMWZZejFDKzVvNVNPVVBOa1k5MDBpL1RuRVdNelJXRkdNN1V5NlUzU3V0ZmJJNlk2UzVlMjV0OVB3MFhObnZMS2I0aTF3eDd0eTQ0ZWVVV2pENmthbkRMTTVmNkNZaUl5VGxWeEpDY0dTMHFyc1Q3TFJIbnBEZ08xYjAzbXBLS3puV09QK2RLTGttWWlVR1hUSFhtRlBvYm1XOUM0ejVjODcyenR5Uld2bWQ2ZG4ycis1emkxS3NiamQ2cGU4dTkwTHFjckNqUU1sWHpGVGNOeFRVejd5ZWFxVlgrb1hKTHZXNDV6K2lUU1BWVU43ajlEandub00wT3Urd3owVGxEN1Z6WUc5SFdPOUhtRmZ2cXdSbUpva1p5ZFdJVmRnbDR3UzY3dk9MRldzME9oeHpRWS84T0hCZFpQUTU0Zld0blhhZGxGV2QxL2hTYnR2ZzZubDJ2WHQ1YnI4L3Y0TXN2TkZFM0wyTmYydmh1WDFpMUcvK2ZFREh6WE56VzZwM2NFNEwvQUFBQS8vOEJBQUQvL3dkYlREQUFlSnhpWUdZQWcvL25HSXdZc0FBQUFBQUEvLzhCQUFELy95OEJBZ01BQUFBPSIpOwp9Ci5kMi02Mjk3ODIyMDMgLnRleHQtaXRhbGljIHsKCWZvbnQtZmFtaWx5OiAiZDItNjI5NzgyMjAzLWZvbnQtaXRhbGljIjsKfQpAZm9udC1mYWNlIHsKCWZvbnQtZmFtaWx5OiBkMi02Mjk3ODIyMDMtZm9udC1pdGFsaWM7CglzcmM6IHVybCgiZGF0YTphcHBsaWNhdGlvbi9mb250LXdvZmY7YmFzZTY0LGQwOUdSZ0FCQUFBQUFBMXdBQW9BQUFBQUZQd0FBUmhSQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFCUFV5OHlBQUFBOUFBQUFHQUFBQUJnVzFTVmVHTnRZWEFBQUFGVUFBQUFwd0FBQU9BRUpBU3daMng1WmdBQUFmd0FBQWJzQUFBSmZISnk3bHRvWldGa0FBQUk2QUFBQURZQUFBQTJHN1VyMm1ob1pXRUFBQWtnQUFBQUpBQUFBQ1FMZUFqRWFHMTBlQUFBQ1VRQUFBQ0FBQUFBZ0RkekJUMXNiMk5oQUFBSnhBQUFBRUlBQUFCQ0tNd21YbTFoZUhBQUFBb0lBQUFBSUFBQUFDQUFPQUQyYm1GdFpRQUFDaWdBQUFNbUFBQUlNZ250VnpOd2IzTjBBQUFOVUFBQUFDQUFBQUFnLzhZQU1nQURBZUVCa0FBRkFBQUNpZ0pZLy9FQVN3S0tBbGdBUkFGZUFESUJJd0FBQWdzRkF3TUVBd2tDQkNBQUFIY0FBQUFEQUFBQUFBQUFBQUJCUkVKUEFBRUFJUC8vQXU3L0JnQUFBOWdCRVNBQUFaTUFBQUFBQWVZQ2xBQUFBQ0FBQTNpY2hNMjdLa2NCQU1meHovbWY0MzdjNzdjNm5jVm04QUpXQTh1SkI3Q0lVa3BLbm9Za2RnYmxPWXdXazhWcU12d1VtK1gvblQvMVJhRlVvRlo1UmF0UjZXbTBObXphc20zSG5zNkJJeWZPWExoMGxmRFA3T3JzTzNUczFQbWZ5WHUrODVYUGZPUXRMM25PVXg3emtQdmM1VFkzdWY0OTk2dXdiczJLWmF0NlNwVUJnNFlNR3pGcVRHM2NoRWxUcHMyWU5XZmVna1ZML0FBQUFQLy9BUUFBLy84QUhDeTVBSGljZkZaYmJOdlcvZjZkUTVyMFJiN29Sa2FLTHBhT1JGb3lKZG1pSlZxT0pWbTJMTXVXWk5mMjM2bWIySTZkcHNIZlRSWjREVnFzU0lOa01WQmt3MmFvUVRFZ2UxaUgzZEJsYis1ZUJnd3B0aFdZdDZFWUJoUkRobUV2N2VZQzhZQ3RobEJzeFV3T3BIeFIvTEFYNGtCSC9IN2Y5NTN2OStPQkpnZ0E0QnY0YmFDZ0JUckJBbllBMmVxaktGbFJDRS9Kb2toWVZoR3RWalp3RCszYyt6WTlkdUZ2UGQvOXQrU2xKNzc2NDlMZlYzK0MzejY0anU0dTM3bWpYcnovMGt2UDcrMnBZZlRIUFFBQUJBUUFyK01xdE9tWU11V3p5aFN4K2lpeU9UMkllZ1lybTlOWjlVa0dWOVU5WkQvWVFJUHFUdjBkOUFCWG9lbndIWlpzVHIrR2JPMjRlckE5ZW9qNWMxd0ZoN0Z2NVdYRlFFMG1GY0pTaEJJSnc3QVUyVnhPY1hUaFY4dWJwWEtMMDBUUC9FSktjelRUMFR5RnErcDM3dDlIbHc4MjBDdlN5NzBQMUIrZ3BRZlN1cVJ1QVRhd1JWeUZkdUFNOURobnR6R3NsVkNFc3NyeFpHSkFJSVJzL25UcHl6Zm1iODVmZjBYSnY3aHlwVlJjeGRYQy9NVWJadlVUeEtsUDBjSmNJUm1yNjE4R3dBbGNCVlpISTRxUEpkU1BYbjNjam43WC92NnJ1REkyZHZCZS9YOHpXZzFmeFZXOWFwTmZTQXdrZFUyTTNjYko4ZnFTUVo0cjF4aDZhcnJVTWpJK2VNRStXNTV6M1RPdFg3WEhIR2hEL1ZyRVg2Z3NYVU1QMUd0YnJ3TmdFTFVhK2hmYUI1dXVpTmNSTTFpT2M3eXN5QlJSQ01PSThhU2lDQUx4ZDJDN2pYdHZwQ3hOcmNoaTJreGJNMnZaWnBvc1dvU1pnR1NQdXdKakNXKy82ZUpDNGZVbHVjZVhWcDNGWUd3a0d2dVQ0QTlQTHNlemFaMC9CcTlXUS8vRU8yRFgwOFQ3QlpHd3hDcXpySnhNR2g1MllER2V3YnA5Zm9abE9lNnBtRFpUdHV4V1JlUnc0UDhpUnZsRVlDemg2UXY1WjBuVUpwdDZmR204ODNqVjNYdmh2RjU2SkR5NUxHZlM0ZUNuZ2g4UUJMVWEya2I3NEhwR0hhc0xZbzVzNHhubXljd1ZxYktXa0lhNWlGVnc5NTFQcG9hNms1emZXVEZkWGM3ZlhJajVIWDI4UGI4eE5scHdtdU8yWUYyTHFOV3cyS0RseEx2L2JkNlFoZW9TS3RWRDk2YURwOTBUdXk4OVBoZzhiUjgydEx5UDlzRUp3Y1o2UnZKOERIZWtoWktUZXY1MGhYODl2eDRwTGZVcE9ZK3BTZjJncFhzczdFN3hIdmZzUXcxVGxoQkpySmhlWGh2Zm1KT2l6OFZkY2tmMnVhRERMTnU5S05oMnB0M1Y3MTBBQkwwQTZKdjRJK0NOWEdheGNVeUgvckdzekJLcWR5SGJsdXZxbkU0N3c1YXpyV2ZOdmxDeitiTHB4UVgwYnFwcGRtcSt2VTFoVytPOTh4bDFVYyt2RndBOXdUdUhQWGx5N3F6Um1Ja0JIWmJ5YmxYNnV1alFuSlJKTkdmS3d6UmRkQldqNDNobkwwMWl1VUZ2UVAwdGtteG4ya3ZocVBxdXB1bVk4QVhleGdKNEFJQUJiN0hlSzVKV2d5L3dEbGgwdHhJRDlmYXcydzV0K2xLT3VWVzVqWkNaWWxqVXlwbXlaZ2UrZHZBVzIwSlpFRDVIMDNVTUx3QitpdlloWE9kYnA4c2ZrbWFlWWQwb1lDM0wwc0s4TU5UZkZGc01wcE0wbmFta2FYckNYcFRHZFQwRnJ0ZzdqblluQS8xS2p5VG5CczBlVzZPbWt4VWNlNGIyNFV3amg5T1c2UlZEYzlGbkhETXFuRGJzT0x2b3oyZ2ZPc0hkbUtWNkF4cjVPV3lRajJaV3BLbVYrTXdscWJRU2pzekt5YmorTVAzL3hmR2JDOUg2YzJSMEl6ODZNYmFSSHkwYzgzMEQ3VU5YQTErZUZZNTR0dEh1Y3NSaFA5dmxESlM5YWJTN0xLVmI4czNaYytxSGdMVC9hRFYwRysyRFdKOE9pcEhxeElBZ0N2cXdhd2lmM2NieFJ2U1o3L2N2Ty9yNEVTR2NEZzFHVTlLa0ZKMXlSYTJ5VCtoUGRtY0crdVpNQXoyQ3R5ZEtuS0xYbVFuMTVvSUJUNC9OR2ZGNkJJdC9XSXJrZ3pybllhMkdGdkgxNHptUlZQUzB5MGJDRytiRXowWUdhSlNhYUNzSGNtZHZtVzZuS0plL3c5bG03b3Fac3BGT1p6dXlwSnJlZkRPalByVllQSjdXSm9YdDFMRUxBUGpYYUJkOEFESWxXem1PbDVQNnNENVpVWVFTaFByM2FKMlV1eEJDZE9mWnJyc2xNOGFJN25CMjNTbis1VktIOGF1Nzh6VzBxMzdzei92OWVUL3lOS3ljcUpVVUE0RWlVVDhIcFAxU2k2RlAwQzQ0QVZqRFIxMkEwbGdkZFdDbXRidkRZYkVFY3c3TGZGbG9hcVpvYzlEeWpiTDZzZU5jOFE4c20ycEp4d242VlAySHIwSkkyWS9NQjUvRktwTFJGOXJuQU9nRC9IVWRueWdaNm5CZ2k4ZFRuUFd4cmMycld5c3hPZEdkODR2UzgzMXppK0c1TithUnpSU2R2WFg1aGFnMDdQUDJDYUVYOG9tVjFZM2lxTzRUcjlYZ1Bselg3d0QxTTZnM1dZRnppQzd1VE5EazRweVNtM05JZW41WHRCcjZIbjRFRG4wV0tobjZOQUdLWlJzZzNxTENReWtoMnVOU3VKQjdObEtZRTgrbEpmb0krRGZ5MUZBaUZReEZYWHpVSTA3bStpZUdoc2Fpdno4cEJ3anVva2Z3R1g1SHYydFlGVVZtMmJ0OFo4a2FSbzhlTGkwOTFEUjlIMzBMdndPQ25pQmd3UVUvUE9vRitCRHRBbFcvbzNqWEtwZlJydW8wOWlad0NiYnh0cTdYMmtEMksxWVA0VzF1Z2tzODUvQ2Q0UnpkL3dVQUFQLy9BUUFBLy85dm4rdkVBQUVBQUFBQkdGSGZFYlh6WHc4ODlRQUJBK2dBQUFBQTJGMmd6QUFBQUFEZFppODMvcjMrM1FnZEE4a0FBZ0FEQUFJQUFBQUFBQUFBQVFBQUE5ais3d0FBQ0VEK3ZmMjhDQjBENkFEQy85RUFBQUFBQUFBQUFBQUFBQ0FDZEFBa0FNZ0FBQUp1QUNNQnpnQWpBc0VBSXdJckFDTUIvZ0JkQW1nQVR3SVpBQ2NDR0FBZkFiTUFKUUlYQUNjQjRRQWxBUm9BS3dJTEFCOEE3UUFmQVBnQUxBTWZBQjhDRFFBZkFnTUFKd0ZXQUI4Qmt2LzhBVVVBUEFMREFFWUJ3UC9DQWVELzlnRHlBQmNCbVFCZUFQSUFnQUdYQUlBQTdRQWZBQUFBUndBQUFDNEFMZ0JJQUZnQWhnQ3VBTUlBNmdFaUFWb0JpQUhBQWZvQ0lnSk1BbGdDZWdLOEF1WURGQU15QTI0RG5BUFdCQVlFTUFSR0JINEVqZ1NhQktnRXZnQUFBQUVBQUFBZ0FJd0FEQUJtQUFjQUFRQUFBQUFBQUFBQUFBQUFBQUFFQUFONG5KeVUyMDRiVnhTR1B3ZmJiWHE2cUZCRWJ0QytUS1ZrVEtNUUplSEtsS0NNaW5EcWNYcVFxa3FEUFQ2SThjeklNNWlTSitoMTM2SnZrYXMrUnAraTZuVzFmeStESFVWQklBVDhlL1k2L0d1dGYyMWdrLy9Zb0ZhL0MvemRuQnV1c2QzODJmQWR2bWdlR2Q1Z3YvbVo0VG9QRy84WWJqQm92RFhjNUVHamEvZ1QzdFgvTlB3cFQrcS9HYjdMVnYzUThPYzhybThhL25MRDhhL2hyM2pDdXdXdXdUUCtNRnhqaThMd0hUYjUxZkFHOTdDWXRUcjMyREhjNEd1MkRUZlpCbnBNcUVpWmtESENNV1RDaURObUpKUkVKTXlZTUNSaGdDT2tUVXFscnhteGtHUDB3YTh4RVJVellrVWNVK0ZJaVVpSktSbGJ4TGZ5eW5tdGpFT2RabmJYcG1KTXpJazhUb25KY09TTXlNbElPRldjaW9xQ0Y3Um9VZElYMzRLS2tvQ1NDU2tCT1ROR3RPaHd5QkU5eGt3b2NSd3FrbWNXa1RPazRweFkrWjFaK003MFNjZ29qZFVaR1FQeGRPS1h5RHZrQ0VlSFFyYXJrWS9XSWp6RThhTzhQYmRjdHQ4UzZOZXRNRnZQdTJRVE0xYy9VM1VsMWMyNUpqaldyYy9iNWdmaGloZTRXL1ZubmNuMVBScm9mNlhJSjV4cC9nTk5LaE9URE9lMmFCTkpRWkc3ajJOZjU1QklIZm1Ka0I2djZQQ0duczV0dW5ScGMweVBrSmZ5N2RERjhSMGRqam1RUnlpOHVEdVVZbzc1QmNmM2hMTHhzUlByejJKaUNiOVRtTHBMY1p5cGppbUZldTZaQjZvMVVZVTNuN0Rmb1h4TkhhVjgrdG9qYitrMHYweDdGak15VlJSaU9GVXZsOW9vclg4RFU4UlV0ZmpaWHQzN2JaamI3aTIzK0lKY08relZ1dURrSjdkZ2ROMVVnL2MwYzY2ZmdKZ0JPU2V5NkpNenBVWEZoWGkvSnVhTUZNZUJ1dmRLVzFMUnZ2VHhlUzZra29TcEdJUmtpak9qME4vWWRCTVo5LzZhN3AyOUpRUDVlNmFubDFYZEpvdFRyNjVtOUViZFc5NUYxdVZrWlFJdG0ycStvcWErdUdhbS9VUTd0Y28va20rcDF5M25FYUhpTG5iN1E2L0FEcy9aWlkreHN2UjFNNys4ODYrRXQ5aFRCMDVKWkRXVXBuME5qd25ZSmVBcHUrenluS2Z2OVhMSnhoa2Z0OFpuTlgrYkEvYnBzSGR0TlF2YkR2dThYSXYyOGN4L2llMk82bkU4dWp3OXUvVTBIOXhBdGQ5bzM2N2V6YTRtNTZjeHQyaFgyM0ZNek5SemNWdXJOYm43QlA4REFBRC8vd0VBQVAvL2NxRlJRQUFBQUFNQUFQLzFBQUQvemdBeUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQT0iKTsKfV1dPjwvc3R5bGU+PHN0eWxlIHR5cGU9InRleHQvY3NzIj48IVtDREFUQVsuc2hhcGUgewogIHNoYXBlLXJlbmRlcmluZzogZ2VvbWV0cmljUHJlY2lzaW9uOwogIHN0cm9rZS1saW5lam9pbjogcm91bmQ7Cn0KLmNvbm5lY3Rpb24gewogIHN0cm9rZS1saW5lY2FwOiByb3VuZDsKICBzdHJva2UtbGluZWpvaW46IHJvdW5kOwp9Ci5ibGVuZCB7CiAgbWl4LWJsZW5kLW1vZGU6IG11bHRpcGx5OwogIG9wYWNpdHk6IDAuNTsKfQoKCQkuZDItNjI5NzgyMjAzIC5maWxsLU4xe2ZpbGw6IzBBMEYyNTt9CgkJLmQyLTYyOTc4MjIwMyAuZmlsbC1OMntmaWxsOiM2NzZDN0U7fQoJCS5kMi02Mjk3ODIyMDMgLmZpbGwtTjN7ZmlsbDojOTQ5OUFCO30KCQkuZDItNjI5NzgyMjAzIC5maWxsLU40e2ZpbGw6I0NGRDJERDt9CgkJLmQyLTYyOTc4MjIwMyAuZmlsbC1ONXtmaWxsOiNERUUxRUI7fQoJCS5kMi02Mjk3ODIyMDMgLmZpbGwtTjZ7ZmlsbDojRUVGMUY4O30KCQkuZDItNjI5NzgyMjAzIC5maWxsLU43e2ZpbGw6I0ZGRkZGRjt9CgkJLmQyLTYyOTc4MjIwMyAuZmlsbC1CMXtmaWxsOiMwRDMyQjI7fQoJCS5kMi02Mjk3ODIyMDMgLmZpbGwtQjJ7ZmlsbDojMEQzMkIyO30KCQkuZDItNjI5NzgyMjAzIC5maWxsLUIze2ZpbGw6I0UzRTlGRDt9CgkJLmQyLTYyOTc4MjIwMyAuZmlsbC1CNHtmaWxsOiNFM0U5RkQ7fQoJCS5kMi02Mjk3ODIyMDMgLmZpbGwtQjV7ZmlsbDojRURGMEZEO30KCQkuZDItNjI5NzgyMjAzIC5maWxsLUI2e2ZpbGw6I0Y3RjhGRTt9CgkJLmQyLTYyOTc4MjIwMyAuZmlsbC1BQTJ7ZmlsbDojNEE2RkYzO30KCQkuZDItNjI5NzgyMjAzIC5maWxsLUFBNHtmaWxsOiNFREYwRkQ7fQoJCS5kMi02Mjk3ODIyMDMgLmZpbGwtQUE1e2ZpbGw6I0Y3RjhGRTt9CgkJLmQyLTYyOTc4MjIwMyAuZmlsbC1BQjR7ZmlsbDojRURGMEZEO30KCQkuZDItNjI5NzgyMjAzIC5maWxsLUFCNXtmaWxsOiNGN0Y4RkU7fQoJCS5kMi02Mjk3ODIyMDMgLnN0cm9rZS1OMXtzdHJva2U6IzBBMEYyNTt9CgkJLmQyLTYyOTc4MjIwMyAuc3Ryb2tlLU4ye3N0cm9rZTojNjc2QzdFO30KCQkuZDItNjI5NzgyMjAzIC5zdHJva2UtTjN7c3Ryb2tlOiM5NDk5QUI7fQoJCS5kMi02Mjk3ODIyMDMgLnN0cm9rZS1ONHtzdHJva2U6I0NGRDJERDt9CgkJLmQyLTYyOTc4MjIwMyAuc3Ryb2tlLU41e3N0cm9rZTojREVFMUVCO30KCQkuZDItNjI5NzgyMjAzIC5zdHJva2UtTjZ7c3Ryb2tlOiNFRUYxRjg7fQoJCS5kMi02Mjk3ODIyMDMgLnN0cm9rZS1ON3tzdHJva2U6I0ZGRkZGRjt9CgkJLmQyLTYyOTc4MjIwMyAuc3Ryb2tlLUIxe3N0cm9rZTojMEQzMkIyO30KCQkuZDItNjI5NzgyMjAzIC5zdHJva2UtQjJ7c3Ryb2tlOiMwRDMyQjI7fQoJCS5kMi02Mjk3ODIyMDMgLnN0cm9rZS1CM3tzdHJva2U6I0UzRTlGRDt9CgkJLmQyLTYyOTc4MjIwMyAuc3Ryb2tlLUI0e3N0cm9rZTojRTNFOUZEO30KCQkuZDItNjI5NzgyMjAzIC5zdHJva2UtQjV7c3Ryb2tlOiNFREYwRkQ7fQoJCS5kMi02Mjk3ODIyMDMgLnN0cm9rZS1CNntzdHJva2U6I0Y3RjhGRTt9CgkJLmQyLTYyOTc4MjIwMyAuc3Ryb2tlLUFBMntzdHJva2U6IzRBNkZGMzt9CgkJLmQyLTYyOTc4MjIwMyAuc3Ryb2tlLUFBNHtzdHJva2U6I0VERjBGRDt9CgkJLmQyLTYyOTc4MjIwMyAuc3Ryb2tlLUFBNXtzdHJva2U6I0Y3RjhGRTt9CgkJLmQyLTYyOTc4MjIwMyAuc3Ryb2tlLUFCNHtzdHJva2U6I0VERjBGRDt9CgkJLmQyLTYyOTc4MjIwMyAuc3Ryb2tlLUFCNXtzdHJva2U6I0Y3RjhGRTt9CgkJLmQyLTYyOTc4MjIwMyAuYmFja2dyb3VuZC1jb2xvci1OMXtiYWNrZ3JvdW5kLWNvbG9yOiMwQTBGMjU7fQoJCS5kMi02Mjk3ODIyMDMgLmJhY2tncm91bmQtY29sb3ItTjJ7YmFja2dyb3VuZC1jb2xvcjojNjc2QzdFO30KCQkuZDItNjI5NzgyMjAzIC5iYWNrZ3JvdW5kLWNvbG9yLU4ze2JhY2tncm91bmQtY29sb3I6Izk0OTlBQjt9CgkJLmQyLTYyOTc4MjIwMyAuYmFja2dyb3VuZC1jb2xvci1ONHtiYWNrZ3JvdW5kLWNvbG9yOiNDRkQyREQ7fQoJCS5kMi02Mjk3ODIyMDMgLmJhY2tncm91bmQtY29sb3ItTjV7YmFja2dyb3VuZC1jb2xvcjojREVFMUVCO30KCQkuZDItNjI5NzgyMjAzIC5iYWNrZ3JvdW5kLWNvbG9yLU42e2JhY2tncm91bmQtY29sb3I6I0VFRjFGODt9CgkJLmQyLTYyOTc4MjIwMyAuYmFja2dyb3VuZC1jb2xvci1ON3tiYWNrZ3JvdW5kLWNvbG9yOiNGRkZGRkY7fQoJCS5kMi02Mjk3ODIyMDMgLmJhY2tncm91bmQtY29sb3ItQjF7YmFja2dyb3VuZC1jb2xvcjojMEQzMkIyO30KCQkuZDItNjI5NzgyMjAzIC5iYWNrZ3JvdW5kLWNvbG9yLUIye2JhY2tncm91bmQtY29sb3I6IzBEMzJCMjt9CgkJLmQyLTYyOTc4MjIwMyAuYmFja2dyb3VuZC1jb2xvci1CM3tiYWNrZ3JvdW5kLWNvbG9yOiNFM0U5RkQ7fQoJCS5kMi02Mjk3ODIyMDMgLmJhY2tncm91bmQtY29sb3ItQjR7YmFja2dyb3VuZC1jb2xvcjojRTNFOUZEO30KCQkuZDItNjI5NzgyMjAzIC5iYWNrZ3JvdW5kLWNvbG9yLUI1e2JhY2tncm91bmQtY29sb3I6I0VERjBGRDt9CgkJLmQyLTYyOTc4MjIwMyAuYmFja2dyb3VuZC1jb2xvci1CNntiYWNrZ3JvdW5kLWNvbG9yOiNGN0Y4RkU7fQoJCS5kMi02Mjk3ODIyMDMgLmJhY2tncm91bmQtY29sb3ItQUEye2JhY2tncm91bmQtY29sb3I6IzRBNkZGMzt9CgkJLmQyLTYyOTc4MjIwMyAuYmFja2dyb3VuZC1jb2xvci1BQTR7YmFja2dyb3VuZC1jb2xvcjojRURGMEZEO30KCQkuZDItNjI5NzgyMjAzIC5iYWNrZ3JvdW5kLWNvbG9yLUFBNXtiYWNrZ3JvdW5kLWNvbG9yOiNGN0Y4RkU7fQoJCS5kMi02Mjk3ODIyMDMgLmJhY2tncm91bmQtY29sb3ItQUI0e2JhY2tncm91bmQtY29sb3I6I0VERjBGRDt9CgkJLmQyLTYyOTc4MjIwMyAuYmFja2dyb3VuZC1jb2xvci1BQjV7YmFja2dyb3VuZC1jb2xvcjojRjdGOEZFO30KCQkuZDItNjI5NzgyMjAzIC5jb2xvci1OMXtjb2xvcjojMEEwRjI1O30KCQkuZDItNjI5NzgyMjAzIC5jb2xvci1OMntjb2xvcjojNjc2QzdFO30KCQkuZDItNjI5NzgyMjAzIC5jb2xvci1OM3tjb2xvcjojOTQ5OUFCO30KCQkuZDItNjI5NzgyMjAzIC5jb2xvci1ONHtjb2xvcjojQ0ZEMkREO30KCQkuZDItNjI5NzgyMjAzIC5jb2xvci1ONXtjb2xvcjojREVFMUVCO30KCQkuZDItNjI5NzgyMjAzIC5jb2xvci1ONntjb2xvcjojRUVGMUY4O30KCQkuZDItNjI5NzgyMjAzIC5jb2xvci1ON3tjb2xvcjojRkZGRkZGO30KCQkuZDItNjI5NzgyMjAzIC5jb2xvci1CMXtjb2xvcjojMEQzMkIyO30KCQkuZDItNjI5NzgyMjAzIC5jb2xvci1CMntjb2xvcjojMEQzMkIyO30KCQkuZDItNjI5NzgyMjAzIC5jb2xvci1CM3tjb2xvcjojRTNFOUZEO30KCQkuZDItNjI5NzgyMjAzIC5jb2xvci1CNHtjb2xvcjojRTNFOUZEO30KCQkuZDItNjI5NzgyMjAzIC5jb2xvci1CNXtjb2xvcjojRURGMEZEO30KCQkuZDItNjI5NzgyMjAzIC5jb2xvci1CNntjb2xvcjojRjdGOEZFO30KCQkuZDItNjI5NzgyMjAzIC5jb2xvci1BQTJ7Y29sb3I6IzRBNkZGMzt9CgkJLmQyLTYyOTc4MjIwMyAuY29sb3ItQUE0e2NvbG9yOiNFREYwRkQ7fQoJCS5kMi02Mjk3ODIyMDMgLmNvbG9yLUFBNXtjb2xvcjojRjdGOEZFO30KCQkuZDItNjI5NzgyMjAzIC5jb2xvci1BQjR7Y29sb3I6I0VERjBGRDt9CgkJLmQyLTYyOTc4MjIwMyAuY29sb3ItQUI1e2NvbG9yOiNGN0Y4RkU7fS5hcHBlbmRpeCB0ZXh0LnRleHR7ZmlsbDojMEEwRjI1fS5tZHstLWNvbG9yLWZnLWRlZmF1bHQ6IzBBMEYyNTstLWNvbG9yLWZnLW11dGVkOiM2NzZDN0U7LS1jb2xvci1mZy1zdWJ0bGU6Izk0OTlBQjstLWNvbG9yLWNhbnZhcy1kZWZhdWx0OiNGRkZGRkY7LS1jb2xvci1jYW52YXMtc3VidGxlOiNFRUYxRjg7LS1jb2xvci1ib3JkZXItZGVmYXVsdDojMEQzMkIyOy0tY29sb3ItYm9yZGVyLW11dGVkOiMwRDMyQjI7LS1jb2xvci1uZXV0cmFsLW11dGVkOiNFRUYxRjg7LS1jb2xvci1hY2NlbnQtZmc6IzBEMzJCMjstLWNvbG9yLWFjY2VudC1lbXBoYXNpczojMEQzMkIyOy0tY29sb3ItYXR0ZW50aW9uLXN1YnRsZTojNjc2QzdFOy0tY29sb3ItZGFuZ2VyLWZnOnJlZDt9LnNrZXRjaC1vdmVybGF5LUIxe2ZpbGw6dXJsKCNzdHJlYWtzLWRhcmtlci1kMi02Mjk3ODIyMDMpO21peC1ibGVuZC1tb2RlOmxpZ2h0ZW59LnNrZXRjaC1vdmVybGF5LUIye2ZpbGw6dXJsKCNzdHJlYWtzLWRhcmtlci1kMi02Mjk3ODIyMDMpO21peC1ibGVuZC1tb2RlOmxpZ2h0ZW59LnNrZXRjaC1vdmVybGF5LUIze2ZpbGw6dXJsKCNzdHJlYWtzLWJyaWdodC1kMi02Mjk3ODIyMDMpO21peC1ibGVuZC1tb2RlOmRhcmtlbn0uc2tldGNoLW92ZXJsYXktQjR7ZmlsbDp1cmwoI3N0cmVha3MtYnJpZ2h0LWQyLTYyOTc4MjIwMyk7bWl4LWJsZW5kLW1vZGU6ZGFya2VufS5za2V0Y2gtb3ZlcmxheS1CNXtmaWxsOnVybCgjc3RyZWFrcy1icmlnaHQtZDItNjI5NzgyMjAzKTttaXgtYmxlbmQtbW9kZTpkYXJrZW59LnNrZXRjaC1vdmVybGF5LUI2e2ZpbGw6dXJsKCNzdHJlYWtzLWJyaWdodC1kMi02Mjk3ODIyMDMpO21peC1ibGVuZC1tb2RlOmRhcmtlbn0uc2tldGNoLW92ZXJsYXktQUEye2ZpbGw6dXJsKCNzdHJlYWtzLWRhcmstZDItNjI5NzgyMjAzKTttaXgtYmxlbmQtbW9kZTpvdmVybGF5fS5za2V0Y2gtb3ZlcmxheS1BQTR7ZmlsbDp1cmwoI3N0cmVha3MtYnJpZ2h0LWQyLTYyOTc4MjIwMyk7bWl4LWJsZW5kLW1vZGU6ZGFya2VufS5za2V0Y2gtb3ZlcmxheS1BQTV7ZmlsbDp1cmwoI3N0cmVha3MtYnJpZ2h0LWQyLTYyOTc4MjIwMyk7bWl4LWJsZW5kLW1vZGU6ZGFya2VufS5za2V0Y2gtb3ZlcmxheS1BQjR7ZmlsbDp1cmwoI3N0cmVha3MtYnJpZ2h0LWQyLTYyOTc4MjIwMyk7bWl4LWJsZW5kLW1vZGU6ZGFya2VufS5za2V0Y2gtb3ZlcmxheS1BQjV7ZmlsbDp1cmwoI3N0cmVha3MtYnJpZ2h0LWQyLTYyOTc4MjIwMyk7bWl4LWJsZW5kLW1vZGU6ZGFya2VufS5za2V0Y2gtb3ZlcmxheS1OMXtmaWxsOnVybCgjc3RyZWFrcy1kYXJrZXItZDItNjI5NzgyMjAzKTttaXgtYmxlbmQtbW9kZTpsaWdodGVufS5za2V0Y2gtb3ZlcmxheS1OMntmaWxsOnVybCgjc3RyZWFrcy1kYXJrLWQyLTYyOTc4MjIwMyk7bWl4LWJsZW5kLW1vZGU6b3ZlcmxheX0uc2tldGNoLW92ZXJsYXktTjN7ZmlsbDp1cmwoI3N0cmVha3Mtbm9ybWFsLWQyLTYyOTc4MjIwMyk7bWl4LWJsZW5kLW1vZGU6Y29sb3ItYnVybn0uc2tldGNoLW92ZXJsYXktTjR7ZmlsbDp1cmwoI3N0cmVha3Mtbm9ybWFsLWQyLTYyOTc4MjIwMyk7bWl4LWJsZW5kLW1vZGU6Y29sb3ItYnVybn0uc2tldGNoLW92ZXJsYXktTjV7ZmlsbDp1cmwoI3N0cmVha3MtYnJpZ2h0LWQyLTYyOTc4MjIwMyk7bWl4LWJsZW5kLW1vZGU6ZGFya2VufS5za2V0Y2gtb3ZlcmxheS1ONntmaWxsOnVybCgjc3RyZWFrcy1icmlnaHQtZDItNjI5NzgyMjAzKTttaXgtYmxlbmQtbW9kZTpkYXJrZW59LnNrZXRjaC1vdmVybGF5LU43e2ZpbGw6dXJsKCNzdHJlYWtzLWJyaWdodC1kMi02Mjk3ODIyMDMpO21peC1ibGVuZC1tb2RlOmRhcmtlbn0ubGlnaHQtY29kZXtkaXNwbGF5OiBibG9ja30uZGFyay1jb2Rle2Rpc3BsYXk6IG5vbmV9XV0+PC9zdHlsZT48ZyBjbGFzcz0iVlhObGNnPT0iPjxnIGNsYXNzPSJzaGFwZSIgPjxyZWN0IHg9IjEyLjAwMDAwMCIgeT0iNTIuMDAwMDAwIiB3aWR0aD0iMTAwLjAwMDAwMCIgaGVpZ2h0PSI2Ni4wMDAwMDAiIHN0cm9rZT0iIzBEMzJCMiIgZmlsbD0iI0VERjBGRCIgY2xhc3M9IiBzdHJva2UtQjEgZmlsbC1CNSIgc3R5bGU9InN0cm9rZS13aWR0aDoyOyIgLz48L2c+PHRleHQgeD0iNjIuMDAwMDAwIiB5PSI5MC41MDAwMDAiIGZpbGw9IiMwQTBGMjUiIGNsYXNzPSJ0ZXh0IGZpbGwtTjEiIHN0eWxlPSJ0ZXh0LWFuY2hvcjptaWRkbGU7Zm9udC1zaXplOjE2cHgiPlVzZXI8L3RleHQ+PC9nPjxnIGNsYXNzPSJURXhOIj48ZyBjbGFzcz0ic2hhcGUiID48cmVjdCB4PSI0NTkuMDAwMDAwIiB5PSI1Mi4wMDAwMDAiIHdpZHRoPSIxMDAuMDAwMDAwIiBoZWlnaHQ9IjY2LjAwMDAwMCIgc3Ryb2tlPSIjMEQzMkIyIiBmaWxsPSIjRURGMEZEIiBjbGFzcz0iIHN0cm9rZS1CMSBmaWxsLUI1IiBzdHlsZT0ic3Ryb2tlLXdpZHRoOjI7IiAvPjwvZz48dGV4dCB4PSI1MDkuMDAwMDAwIiB5PSI5MC41MDAwMDAiIGZpbGw9IiMwQTBGMjUiIGNsYXNzPSJ0ZXh0IGZpbGwtTjEiIHN0eWxlPSJ0ZXh0LWFuY2hvcjptaWRkbGU7Zm9udC1zaXplOjE2cHgiPkxMTTwvdGV4dD48L2c+PGcgY2xhc3M9IktGVnpaWElnTFMwZ0tWc3dYUT09Ij48cGF0aCBkPSJNIDYyLjAwMDAwMCAxMjAuMDAwMDAwIEwgNjIuMDAwMDAwIDM1Ny4wMDAwMDAiIHN0cm9rZT0iIzBEMzJCMiIgZmlsbD0ibm9uZSIgY2xhc3M9ImNvbm5lY3Rpb24gc3Ryb2tlLUIyIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOjI7c3Ryb2tlLWRhc2hhcnJheToxMi4wMDAwMDAsMTEuODM4NzY3OyIgbWFzaz0idXJsKCNkMi02Mjk3ODIyMDMpIiAvPjwvZz48ZyBjbGFzcz0iS0V4TVRTQXRMU0FwV3pCZCI+PHBhdGggZD0iTSA1MDkuMDAwMDAwIDEyMC4wMDAwMDAgTCA1MDkuMDAwMDAwIDM1Ny4wMDAwMDAiIHN0cm9rZT0iIzBEMzJCMiIgZmlsbD0ibm9uZSIgY2xhc3M9ImNvbm5lY3Rpb24gc3Ryb2tlLUIyIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOjI7c3Ryb2tlLWRhc2hhcnJheToxMi4wMDAwMDAsMTEuODM4NzY3OyIgbWFzaz0idXJsKCNkMi02Mjk3ODIyMDMpIiAvPjwvZz48ZyBjbGFzcz0iS0ZWelpYSWdMU1puZERzZ1RFeE5LVnN3WFE9PSI+PG1hcmtlciBpZD0ibWstZDItNjI5NzgyMjAzLTM0ODgzNzgxMzQiIG1hcmtlcldpZHRoPSIxMC4wMDAwMDAiIG1hcmtlckhlaWdodD0iMTIuMDAwMDAwIiByZWZYPSI3LjAwMDAwMCIgcmVmWT0iNi4wMDAwMDAiIHZpZXdCb3g9IjAuMDAwMDAwIDAuMDAwMDAwIDEwLjAwMDAwMCAxMi4wMDAwMDAiIG9yaWVudD0iYXV0byIgbWFya2VyVW5pdHM9InVzZXJTcGFjZU9uVXNlIj4gPHBvbHlnb24gcG9pbnRzPSIwLjAwMDAwMCwwLjAwMDAwMCAxMC4wMDAwMDAsNi4wMDAwMDAgMC4wMDAwMDAsMTIuMDAwMDAwIiBmaWxsPSIjMEQzMkIyIiBjbGFzcz0iY29ubmVjdGlvbiBmaWxsLUIxIiBzdHJva2Utd2lkdGg9IjIiIC8+IDwvbWFya2VyPjxwYXRoIGQ9Ik0gNjQuMDAwMDAwIDE5OC4wMDAwMDAgTCA1MDUuMDAwMDAwIDE5OC4wMDAwMDAiIHN0cm9rZT0iIzBEMzJCMiIgZmlsbD0ibm9uZSIgY2xhc3M9ImNvbm5lY3Rpb24gc3Ryb2tlLUIxIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOjI7IiBtYXJrZXItZW5kPSJ1cmwoI21rLWQyLTYyOTc4MjIwMy0zNDg4Mzc4MTM0KSIgbWFzaz0idXJsKCNkMi02Mjk3ODIyMDMpIiAvPjx0ZXh0IHg9IjI4NS41MDAwMDAiIHk9IjIwNC4wMDAwMDAiIGZpbGw9IiM2NzZDN0UiIGNsYXNzPSJ0ZXh0LWl0YWxpYyBmaWxsLU4yIiBzdHlsZT0idGV4dC1hbmNob3I6bWlkZGxlO2ZvbnQtc2l6ZToxNnB4Ij5Ib3cgbWFueSBSJiMzOTtzIGFyZSBpbiB0aGUgd29yZCAmIzM0O3N0cmF3YmVycnkmIzM0Oz88L3RleHQ+PC9nPjxnIGNsYXNzPSJLRXhNVFNBdEptZDBPeUJWYzJWeUtWc3dYUT09Ij48cGF0aCBkPSJNIDUwNy4wMDAwMDAgMjg4LjAwMDAwMCBMIDY2LjAwMDAwMCAyODguMDAwMDAwIiBzdHJva2U9IiMwRDMyQjIiIGZpbGw9Im5vbmUiIGNsYXNzPSJjb25uZWN0aW9uIHN0cm9rZS1CMSIgc3R5bGU9InN0cm9rZS13aWR0aDoyOyIgbWFya2VyLWVuZD0idXJsKCNtay1kMi02Mjk3ODIyMDMtMzQ4ODM3ODEzNCkiIG1hc2s9InVybCgjZDItNjI5NzgyMjAzKSIgLz48dGV4dCB4PSIyODUuNTAwMDAwIiB5PSIyOTQuMDAwMDAwIiBmaWxsPSIjNjc2QzdFIiBjbGFzcz0idGV4dC1pdGFsaWMgZmlsbC1OMiIgc3R5bGU9InRleHQtYW5jaG9yOm1pZGRsZTtmb250LXNpemU6MTZweCI+VGhlIHdvcmQgJiMzNDtzdHJhd2JlcnJ5JiMzNDsgY29udGFpbnMgMiBpbnN0YW5jZXMgb2YgdGhlIGxldHRlciAmIzM0O3ImIzM0Oy48L3RleHQ+PC9nPjxtYXNrIGlkPSJkMi02Mjk3ODIyMDMiIG1hc2tVbml0cz0idXNlclNwYWNlT25Vc2UiIHg9IjEiIHk9IjQxIiB3aWR0aD0iNTY5IiBoZWlnaHQ9IjMyOCI+CjxyZWN0IHg9IjEiIHk9IjQxIiB3aWR0aD0iNTY5IiBoZWlnaHQ9IjMyOCIgZmlsbD0id2hpdGUiPjwvcmVjdD4KPHJlY3QgeD0iMTM5LjAwMDAwMCIgeT0iMTg4LjAwMDAwMCIgd2lkdGg9IjI5MyIgaGVpZ2h0PSIyMSIgZmlsbD0iYmxhY2siPjwvcmVjdD4KPHJlY3QgeD0iOTAuMDAwMDAwIiB5PSIyNzguMDAwMDAwIiB3aWR0aD0iMzkxIiBoZWlnaHQ9IjIxIiBmaWxsPSJibGFjayI+PC9yZWN0Pgo8L21hc2s+PC9zdmc+PC9zdmc+Cg==" class="img-fluid" style="width:75.0%"></p>
<p>Diagrams as code are not new, with the <a href="https://en.wikipedia.org/wiki/DOT_language">DOT language</a> being used for <a href="https://graphviz.org/">GraphViz</a> since 1991. They were great all along, but they’ve become even better with the rise of AI assistants.</p>
</section>
<section id="assistants-make-diagrams-effortless" class="level2">
<h2 class="anchored" data-anchor-id="assistants-make-diagrams-effortless">Assistants make diagrams effortless</h2>
<p>Commonly used LLMs like GPT-4o, Claude and Gemini are familiar with text-based diagram syntax. They can turn a quick prompt into diagram code. They can also take the diagram as input and write code to implement it. Diagrams as code are an intermediate step between natural language and code. As assistants take on more coding tasks, this level of abstraction becomes more important for developers.</p>
<p>An AI can assist throughout the diagram lifecycle - from generating initial diagrams from natural language prompts to updating them based on code changes. It can validate diagrams against existing code, convert between different diagram formats, and even suggest structural improvements. For developers new to diagrams as code, the assistant serves as a helpful guide, explaining syntax and best practices. The time savings also make it more convenient to keep diagrams in sync with code, which is a common problem.</p>
<p>The stakes for diagrams are relatively low, so it’s not necessary to review every generated line. When an assistant makes a mistake, syntax errors will be caught during rendering and content errors are easily spotted during visual review.</p>
</section>
<section id="diagrams-as-code-systems" class="level2">
<h2 class="anchored" data-anchor-id="diagrams-as-code-systems">Diagrams as code systems</h2>
<p>Let’s take a look at some of the most popular implementations:</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Name</th>
<th>Description</th>
<th>Best for</th>
<th>Release</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><a href="https://graphviz.org/">GraphViz</a></td>
<td>Foundational graph visualization technology powering many modern tools. Its DOT language and layout algorithms are widely used as a backend for other visualization software. Provides programmatic generation of structured layouts.</td>
<td>Network topology, dependency trees, and hierarchical data visualization where automated layout is crucial. Often used as an engine rather than directly.</td>
<td>1991</td>
</tr>
<tr class="even">
<td><a href="https://plantuml.com/">PlantUML</a></td>
<td>Supports UML diagrams (class, sequence, use case, activity, etc.), network diagrams, wireframes, Gantt charts, and more. Widely integrated into IDEs, wikis, and documentation tools.</td>
<td>Technical documentation requiring standardized diagrams, especially UML.</td>
<td>2009</td>
</tr>
<tr class="odd">
<td><a href="https://www.drawio.com/">Draw.io</a></td>
<td>Browser-based diagramming tool (also known as diagrams.net) with desktop versions available. Features extensive shape libraries, custom templates, and automatic layouts. Supports offline use, multiple storage backends (Google Drive, OneDrive, GitHub), and collaborative editing.</td>
<td>General-purpose diagramming suitable for both technical and business users.</td>
<td>2012</td>
</tr>
<tr class="even">
<td><a href="https://mermaid.js.org/">Mermaid</a></td>
<td>JavaScript-based diagramming library that renders text definitions into SVG diagrams. Supports flowcharts, sequence diagrams, class diagrams, state diagrams, user journeys, Gantt charts, and pie charts. Widely adopted in documentation platforms and Markdown tools.</td>
<td>Diagrams in documentation, especially in Markdown environments like GitHub and documentation sites.</td>
<td>2014</td>
</tr>
<tr class="odd">
<td><a href="https://d2lang.com/">D2</a></td>
<td>Modern diagram scripting language focusing on developer experience. Features concise syntax, multiple layout engines, and scripting capabilities. Emphasizes version control friendly text-based diagram definitions.</td>
<td>Software architecture and system documentation. Sophisticated custom diagrams.</td>
<td>2022</td>
</tr>
</tbody>
</table>
<p>All of them are free to use and have an extension for VSCode. The website <a href="https://text-to-diagram.com/">text-to-diagram.com</a> has a fantastic comparison of D2, Mermaid, PlantUML and GraphViz.</p>
<p>My favorite is <strong>D2</strong>, as it creates the most aesthetic and readable diagrams using the <a href="https://d2lang.com/tour/elk">ELK layout engine</a>. It also supports many export formats, including SVG, PNG, PDF, and even PowerPoint.</p>
<p>Mermaid is another strong choice, as it offers a wider range of diagram types and can be used as a code blocks in markdown environments like GitHub.</p>
<p>If you’re using Quarto, like I do for this blog, Mermaid and GraphViz support is <a href="https://quarto.org/docs/authoring/diagrams.html">built-in</a> and D2 support can be added with a <a href="https://github.com/data-intuitive/quarto-d2">plugin</a>. Alternatively, create a separate <code>.d2</code> file and render it to SVG.</p>
</section>
<section id="level-up-your-diagrams" class="level2">
<h2 class="anchored" data-anchor-id="level-up-your-diagrams">Level up your diagrams</h2>
<p>With an assistant, it’s faster than ever to generate a giant hairball of boxes and arrows. But that’s not the goal - it’s about communicating ideas. Let’s go over some principles that help increase the clarity and usefulness of diagrams. If you agree with them, you may want to copy them into a prompt.</p>
<section id="content-and-layout" class="level3">
<h3 class="anchored" data-anchor-id="content-and-layout">Content and layout</h3>
<ol type="1">
<li><p>Use the appropriate diagram type. Flowcharts are the most common, but there are many others such as <a href="https://d2lang.com/tour/sequence-diagrams/">sequence diagrams</a>, <a href="https://d2lang.com/tour/uml-classes">class diagrams</a>, and <a href="https://mermaid.js.org/syntax/userJourney.html">user journey diagrams</a>.</p></li>
<li><p>Model the key components, rather than every possible detail. Keep the diagram at a single level of abstraction. Use multiple diagrams if needed.</p></li>
</ol>
<blockquote class="blockquote">
<p>My rule of thumb is that you need to be able to print the diagram on a single A4 sheet while keeping things readable. – <a href="https://bellekens.com/2012/02/21/uml-best-practice-5-rules-for-better-uml-diagrams/">Geert Bellekens, enterprise architect</a></p>
</blockquote>
<ol start="3" type="1">
<li>Avoid crossing lines. Layout engines do a good job of this. If they fail to find a layout that avoids overlaps, it’s a sign that the diagram is too complex. Also prefer vertical and horizontal lines over diagonal ones, as they give the diagram a more professional look.</li>
</ol>
</section>
<section id="styling" class="level3">
<h3 class="anchored" data-anchor-id="styling">Styling</h3>
<ol type="1">
<li>Label all components and arrows. A relationship may be obvious to you, but not to others. Use one or two word labels in a sans-serif font at a size readable for aging eyes.</li>
<li>Use consistent shapes, arrows and colors. This helps readers scan the diagram quicker. Use stylistic elements like thicker lines for primary flows and thinner for secondary flows, dotted or dashed lines for optional or future relationships, and different shapes for different types of components.</li>
<li>Use color sparingly and meaningfully, e.g., to highlight critical paths. Don’t rely on color as the only way information is conveyed. Use labels and shapes to ensure the diagram works in black and white too. 1 in 12 men are <a href="https://www.nei.nih.gov/learn-about-eye-health/eye-conditions-and-diseases/color-blindness">colorblind</a>.</li>
<li>Consider using a <a href="https://d2lang.com/blog/hand-drawn-diagrams">hand-drawn style</a> in early stages of a design. It conveys that the diagram is a draft and not a final product.</li>
</ol>
<p><img src="https://simmering.dev/blog/diagrams/data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIGRhdGEtZDItdmVyc2lvbj0iMC43LjEiIHByZXNlcnZlQXNwZWN0UmF0aW89InhNaW5ZTWluIG1lZXQiIHZpZXdCb3g9IjAgMCAxMTQwIDE3NCI+PHN2ZyBjbGFzcz0iZDItMzg1NzU1MTk4IGQyLXN2ZyIgd2lkdGg9IjExNDAiIGhlaWdodD0iMTc0IiB2aWV3Qm94PSIxIDEgMTE0MCAxNzQiPjxyZWN0IHg9IjEuMDAwMDAwIiB5PSIxLjAwMDAwMCIgd2lkdGg9IjExNDAuMDAwMDAwIiBoZWlnaHQ9IjE3NC4wMDAwMDAiIHJ4PSIwLjAwMDAwMCIgZmlsbD0idHJhbnNwYXJlbnQiIHN0cm9rZS13aWR0aD0iMCIgLz48c3R5bGUgdHlwZT0idGV4dC9jc3MiPjwhW0NEQVRBWwouZDItMzg1NzU1MTk4IC50ZXh0LWJvbGQgewoJZm9udC1mYW1pbHk6ICJkMi0zODU3NTUxOTgtZm9udC1ib2xkIjsKfQpAZm9udC1mYWNlIHsKCWZvbnQtZmFtaWx5OiBkMi0zODU3NTUxOTgtZm9udC1ib2xkOwoJc3JjOiB1cmwoImRhdGE6YXBwbGljYXRpb24vZm9udC13b2ZmO2Jhc2U2NCxkMDlHUmdBQkFBQUFBQjFNQUE0QUFBQUFMNndBQVFLUEFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQlBVeTh5QUFBQlJBQUFBRndBQUFCZ1k4RS96bU50WVhBQUFBR2dBQUFBa3dBQUFMb0MvQVFkWTNaMElBQUFBalFBQUFBeUFBQUFTZ1ZFRWZSbWNHZHRBQUFDYUFBQUJ4QUFBQTRNWWk3OWZHZGhjM0FBQUFsNEFBQUFDQUFBQUFnQUFBQVFaMng1WmdBQUNZQUFBQkJOQUFBWmhKY083Tk5vWldGa0FBQVowQUFBQURZQUFBQTJIY2VON0dob1pXRUFBQm9JQUFBQUpBQUFBQ1FJRFFHMGFHMTBlQUFBR2l3QUFBQmNBQUFBWERMNkJHbHNiMk5oQUFBYWlBQUFBREFBQUFBd1NHSlBjRzFoZUhBQUFCcTRBQUFBSUFBQUFDQUNQUlBSYm1GdFpRQUFHdGdBQUFHMEFBQUQ1RithZ2RCd2IzTjBBQUFjakFBQUFCd0FBQUFnLzM0QUZIQnlaWEFBQUJ5b0FBQUFwQUFBQUxKcXZkYW9lSnhpWUdFS1o5ckR3TXJBd05URkZNSEF3T0FOb1JuakdFUVk3UmlRd0FJR2h2b0FCZ1p2R04vZDM5K2RZUUVENzI4bTVwWi9peGdZV05Zd1pqRXdNRTRHeVRGeE1LMWdZR0JRWUdBR0FBQUEvLzhCQUFELy8xS09EU2Q0bkhUTlBVckRBQUNHNFNjbS9rZU52NU9ETi9BU3V1bWtGd2dvSW9pSWl0Y3hZa3UzTGwwNjlTNkYzdU1yaEs1OTEyZDRVU2dWcUZYR3VOUW8xYTVjdTNYdlVldlppMWR2UG56NVNlajF4cDBIcmFlVnZ2djBuV1NlUldhWlpwSlJoaG5rUDMvcDhwdXUvNnl2Y0dwRHFiSnB5N1lkdS9ic3F4MDRkS1J4N01TWmN4Y3NBUUFBLy84QkFBRC8vK2swSlFRQWVKeGl3QTJ5SUpCcEF3TVgwemZHWi85Ly8zc0dZVE1KZ2RnTUVoRElkSjNSR0lRQkFBQUEvLzhCQUFELy81MHRFRUlBQUhpY3JGWnBkOXZHRlowQlFXcUpMTW5XWWpkSTA0ZU1vYnJFZ0ZaYXgyRnN4bEV3b2hoSFRVdkxjZ3M0VFF1SWxMc3ZTVGQzM3pmbXo5eWgybFAzVzM1YXp4dVFyT3hZN3VrNTVRZStPL1B1bTdkaUFBaE5FUGV5Yms2MC8wZ3MzOWxINCs3OUROY0NYTW1MQnpTNmw4R0x5bi9OaTNreEdLaWpJQXdoY2dpamRzZENDbE9rQ2FRR0ZROFNlRnFGS2t4UTB6UThxYTF2aU5SZ3pWQlJwTlpiTjZtTmFnYWVPWHhJV0ZMd2pDbUg4UHNQeDU3bm1TSkZlUHhDeUx2ajVRMlp2a0R3akVySGEzTE5GS21DNkdmSCtYaFRlczZocjFHTHNXRXk5b2ROWXlhRWdJYUVqL3J3dCs2UHI4aHpwanZvb3RITlF0U2kvT0RkTEZSaE1Nb0kvWDRXWWljUENHMUc3VHduVzdITElhNzBzM0N5SW15emZwdVpIL1V6ZWtDalVVbFk3R2RGUUNEV0xUSzZ6dWg2RVJSNW5nZndJaXlaQWNSQkJySFA1QkJMSnRqSGk0eGUzQzhmcllvQk14N1Z4VkdlRDhzY01zN3pTUVk1RGJGcFZKb25xR3ZxRXZ5b0hCTG1URC9EbkVveHI5SWdESFBJSWtIRGxSdTFtSVoyN2lnbFZuSzZRUlUrLzhNcnVnUFVteUZoM3RDSVJwQ3gzYTVIOExmdVpFVS9LQS95VE9WaFR0aTVtMEhHQWRkbEVrcUNPWTBGRTQrRlY3VjVYbU5CcFlvZ1ZGckNPM29BT1lBc01OZE1zS0NKbzEwMmcwZStPQ0krQVR0RnpwUmkxMFc3cU1jTHk4SjAwMlk0RzV6bjlPT0R0RlNkSW1NRlllQkhCWFZIcXVTbXVtS0xnQnNDQ3JBekt4aHFrU3AzS3hmbnpqREg1WDdHeGp0UE0xcldMcUdUYzB1aTF1MW5ZYURDdkJrbVdOSFc4N29ZbHJzSlZqVmtRWVFWOHpZZlFGaFJhWTVWWGgxa2hGWFhyL09hc09xS1FvOThNUmlwRXVkTlFhT0NjRjZsS3NFRnZYK1lXWCs0bTEvR3VXUDFNTUdhM3IrVDdkK3ROb013djR3MXQ3K3VyYmhnN21YMndnVURXYVk0SC9NakJ5OUs3UXIvclhwUkNybXBDTFdvbjFrdUgvd29IWTJJM2E0MlF3VlpUbkZRNmRuRWk5eE9qaFhUdzZycEZmQWViOVlaTGJSQ3JLbGRTQU54YXl5bGROM2EwTUlLcjN1WTRZSktxWXRsbGVLY2dsZWtWUHp6MGlVcHpvczFrYVlwVjJCZHBaQ2xYWitQOFdFY3ZKUW4yTlJXYk1RSkxtb3JXVjdTMW1QNUNXMXJMSi9YMW1jWmFGdG4rWUsyRFphZjFIYU81WXZhenJQOGxMWUxMR090cHZWSG85Zy96QlMxSU4vanB5V0JQcVhjbkNuZnI1VEpLZVhXVFBsQnBTUXRzQktmbVNkaytZOHFWYzd6ZEg2aHRvTGlCQzlwSzFrcWJUMldsN1d0c1l5MDlWbHVhVnRuK1dsdEd5eXZhRHZIOGpQYXpyTnNhcnZBc3FXcDR3YjJxcVlDbHdveUNySXdycVd5UUl0bmRsdmphb3lyelFRdmE2SWVuZEZOVmJZVlgrelBaQVNjL1dlbkxiYkxqUzVQSEY1dTJycmM2R2JidWN2eWM2ZktjeGJubXFaWFhPU3ZhREhoZEQvdUV6SithaXk4THpiL0x2aTNlMHUxN1RXNXdibGUxOVNoM2hueFE1aXluZUJWM2JyWVNkRCtiMVJJTTJnbmVFMWJUMnhHMUtJZVh3bndvdHVqVVUvMVZFblpVY0MzcmtySGJTazMxcHNKYm1pSVRWeFVLZndJZnVSb2RrbWtlTTdFeDZPV0l1cU0yZ2x1UGs2alZuVWVHaXFkc2drRjN5azdkN0lUbitvVW5QaGI5ZWZ6bEcvYVJVTWo1U3pVWG9HR2VmSnhMZmkycTk1S3ZpbUdDblZURHZzWmZGTUdxSnVDYjdvbmJVcEZCSDlMN1pYdFFHSFI3UEViYTlFNEx3VTl6WW1xN3RTR0tiZ1o5YWhFL1dPbnd0L2lJQ0lPb2hZVnc4bE4raDlmZVlMT3RCWkVoUHJXcEJhcTAwN3cra3lGUmFmZlV6MTJ5bDI4TlNzaEoxTlZHdUl3YTFGSGhlNTlPOWtram12U0NqUWkxS1BicDc5ZHFpWStiZG9uM1ZJODhtK2Npc1JNMjFYd0I4NlRLVTlidktNVnRiaUtlN2hvc241d2tHZlV5VnQyVzY3SENkNThUSHNROUIvVHBrKzFmWmFGMGJnUlA4dmhyc2JOZUVUVTRSa2J0Yyttb21GYTJJNFRkRjNLUEo5YlZlVkxMS20wU3AwSFZGR0hXcW85T1g5UDIwVS9TcWNtLytOSTkvNWZVOHc1OFQzV1VlMGdQRFV2WVQ2SnM2ZXR1QkZQcS9LV3R1Sm1IS3BKWFNiWnpFcHdXME5zVkkvOVdQQVR2dGJDOVdhQ3Q4L1kzOWRXeVBVMXZOcE04SG1OMTVvSjN1RXFkaFcxYUcra3ltbTF2cUI1b1BGT25PQ0xlaXpFWHB5Z3I4ZENNcmlqeDlMdEhPaXhkRHQzbWRPTEV4d3loOEU5NWpENEVuTVlmRm1mQ0NGTW5DRFRKL3pwRkNmSTlZbXM5dTdyRTFudHZjczh5ZWdyekhQb1BlWTU5RlhtT2ZRMTl0bU5FeFRzazBISlBoa2NzVThHQSthOEZTY1lNb2ZCTVhNWVBHQU9nNis3dUhiakJOOXdjVEg2cG91TDBiZGNYSXkrN2VKaTlCMFhGNlB2dXJnWWZjL0Z4ZWo3Mm9yT3JJRS9jQ3ZzeEFuZXIrQ2JjWUlQdU9odWxjWUpmcWl0bkhCK1ZFSG0vTmh4NUlUekUyM0Y2N05UZitwV3p1SmhCZG5pWnhWaytzKzFsUlBDTHlySWhGOVdrQW0vMGxiY21wMzNhN2R5OU45VWtPbS9yU0RUZjZldG5CQitYMEVtL0tHQ1RQaWp0dUtOMlhsL2NpdEgvM01GbWY2WENqTDlyOXJLQ2VGdkZXVENxSUpNK0ZDUG4zTmZ0bWdFWTkrcmRUTVZCbUdlcHpIbWoxRzczSDg0ZlZrbi93WUFBUC8vQVFBQS8vOWpUQUZIQUFFQUFmLy9BQTk0bkl4WVc0d2tWM24rejZXdWZhbnV1blpWZFZkM1YzVlg5YjFuK2pxN2MrdTllcjB6c3p2R2k3MjlHT3l4amRlRFRVeThZR0pFWUlqWVNFZ0VoQklsVXFJZ1JZRHp1Zzg0ZVlDSWw2Q0FJRStKa3Jmd0VNbUtsSWlFaUNDaXlMdlJxZXFlbVYxNW8wZ2p0ZXFjcjNyNmZQL2wrLzRERkJvQTZKL3dXeUJBQ2hSdzRiblpuTU9ZZ092WUJjczBkRTNOcHpqS0k0d3cyUVZLT0VLNUl3RnhtSEQ0Q3dCQUtKRGJnQkhDendMRzZIa2VJWXl1cDlNQWFTV3RaRE1wV1JKQkFFRVRlYXM5alBMVHFwVVg2dFY4VmN0WDgvV3BOWTJta1JBSmxyQWlpdWc3a25UL1pmUUg5MThQMFBYN3Y3NzFFZTNXTGUwanQ3UmJ0eVQ4a2l5OS95ZGlEbi80L1ovaTNQdi9jZUZiMy9LLy9aM2cyOThPdnZVT0FHQVlQUGdGL2pIK0FiVGdBdHlacWVzcVJ2UzhJb21VSUlxQWtOMmRlODM5bTdNeUlFb29Ja2RBS1g0ZU1NN3Y4WWdRT09BUWdBSFgzRm5wR0lHQklrd1BIOXFmejR4T0c4T1pVZnRDNTRKZk1uVm80WllnbUczT0Q4Tm9QSnBNODZOd0hQOUZQQi80NFhpMGhhZUR5V1RvRVVQUDRtaXdoY2VqSGc3OE1QQjV3VFFOblRjTUU2MVo2NE0wb3B4RWhJeWFVZXV0N1hPRlRDZkNsNzc4MnZhRk8xOS9hblI0YzdMMjdJdHRMaU5LT1U2Uk9Fa2hwdE13ZlJNWnBDcGtpeG1TNFRtdXFsVWpROFlFNCs3VGIxMjk5clZQelQ2Mzh0UXI0K0hWdm9Vby8rVHZQdGw0dXFOeUFBZ0d3R0R2d0M3ODljNjkzdjdObWF3aWdqa2tjSGpYWlE5MDhUQlBpSXRBNEhoTzRJOG9Jc0FKaER0azRUNEFoUEo3d1BOd0lDSUFGYTY1eWJmVmwzQkFnQW5DdDA5ZU93MmV0WUZpUXVpYnArRVU0ZHVRb0Q5eEdqMmZ6eXdFbHk2Y24wMUgvVzZuM2FoWFNyQ0xkaVRCYk5mOUhwNkc0WGcwbVV5VHYvRW9pcCtuazhsdzRHRkQ1NFBBNTNsaE1oa09UUllLZ2VjTjNUUjAwN1NHZzhsd3dFSWg4RHpLbjdzZXBaQ2lhSTVFZUNSeXZLUTFmR2YybVhOYXIrV2E2amZMUVJZSEE3TXhhWjI3VXRKa1JGV2oyLzlvM3lsc1JNRVZ1YjMzeW5yYU5FMU55UktGTjdvbWg2aWd1MldIcGpJazQxbzVyMytwaW1oK3BXQUpDS09LS3ViNVhPVHhCYjNzMTBXQnN0aXNnNDEraWI4R0liejBMa1lVbzkyZGU5WDltN01pWUVJSnBrZEFnQUtodHdHaEpETmpMcXZIMjRnQW1qOEtRdkRVZkpaSFVDM2JaajRuaXhDaWtCZk05dFJuZVRzZU1RcGlEb1piWkxxZ2pBK0NMQkYrc3l2WjVWTEI0RlJjU3JYNnJaU1hqZXFGYWw5L1BkVWRkVkoycjNQblg5OHF0czFxL3ZiZmZRWUxJbytSWkZsdVVjYWlMR0lBZHFZSmNQaS84Qi9CRFhoaGx0bTlzS0pRaW1xSVVMeWJKSXlGRWFMb0xsQkM3d0lCY3ZmNFdDNWdoTy9DQis3R0I5bzQyd2hMYmtxQ0craEdjcUNUd0p0bUdVOG0weTB5M2NMc3lUSk53MkNGMmNOUkdFWVJPMTZXQkg2UFJLT0k1dzJlcFlDZ1o3R1J4WWRGQTIrRXFiQmViSlRFYmRZVU9NUFdxV3czUEsvbENFNVJjblRMemxBc1pQTWNFVXhMcFp6aEJWcWg0WmNzU2VTSXBQR0V3eHpINGF6TFZicnFiLy9ucC8xbWJXei9nQktlWnMrY204cDJyeFNkS2I3d055ODFDbWs3Yk9hZS9QMGR5ZGljRFdTbDF5LzNkNTdaZHpsUmtMbkc5YVphbGFXd1hSUDByaGZ6MlFYQUdmd096T0J3NTk0Z3JqaktJWTZpSTBDWVEvZ0x3SEhrQUFqSjd3SEdjTUF2KzF2d0tJNEFSNU55UGtiTlp4cUN3VXEzWGZPMWZFcUNHWnF4SGxkbFBTN21kaml3UEd5Wnc4RmtNaDZGWWRRanJQS0dBOWJrWW5LRGdGV1hSMW1oSWJkM2ViMnFLR2JKVURsbjNFa3J6alFzdDlYZ3lrWjliYjZhSDI5TS9LcFo4TXVOVXNuMjI1ZjJMclZibDUreGVvSGI5Z28xVzEzcFpJZ2VGZ3UrTGVWcnF4ZFhwanNSbjdWRDE2eXFwaXFYYzE0cnFLMCtNVnE5T25DU1hHdUFqcitJMzRFcC9PM092ZlQrelhjSkFvUjJYZmJKSWRiUjB2czNadzRRaE1pYzV6QkJpQjRBcGZrOTRiaUpGZlp2enFyQXlvYURvd1VTNHc4QU11NzlFeUJHaUNPSWRibEhrTE53Q1dJclh3YkNJeklIQ2p5aS9DRVFnZzR3UWdROU5aL1Bad2FDUnQydk9BVmRrVVdCaHltYWluR1RDOGVUeVpSVjVTSUtBK3VrVmdPZkJTY0k0dXpWZVNIdWNLaFdjc3cwaWtiT3JPQkdMcC9tdFd1M2R0V0d1YjNWTEpwcEorMnNPUkhGR0tVSVRtZEZRZGZIN2h2LytEYWluR1A3RnNLQ3dHTmVDeXRhUFU5RVRESm5uN3ppdEo4MWM5MUdPcTBrdXZzckhPSVdoTEFHUDV5bHNvaERQc1dVWTNVdDc5K2MxZUs4SWp3R01QYUE0MkxKY1BZRVJPbHlPYWFSUmFRRUdEdi9GMGlOZy9Jd0NBaFJGOGpqSEM4REFvNmlPS25qSW5nRXdUaTJHaEdDMVg2MDFsZ3JsOVJjU21MOWtMRnNKVXErNkJlVGhUSXdxVWhJRHFOb3FlTmhHUGlNYk5QOGVXOS9JamF1dnJKcDE4dUtXQ3lVREU3T2wxeExJTlFmenphRDdVRXhXL1dxMndOUDhlWFdFOTNoMnBzSDY2cUdtMjY5T1AzRVd1ZGpIY09zVi9zYTlkZXFIMjlmMnEzYXcxcnZpYjJ5UGFoQzdHKzhCNy9BZTBTRERtekR6eE1tc2pZQ25FRThiQ0tPSjd2dWNnRXRGdVk3OTNKeFkyQWtDRXhualVTcUVRSnc5a1RFT2tTeVRwYnMrcWNBUy9LTUdFb1BHSVVHWGVaOEJBaDRRUHdSQVBBYzhJZVBnODlxanlKRnhBbUljdlRqeDYrUU9DUkdyd3V3TnVsdTk3YWhBKzEySTkrUUJLZHQ5ZkFpSEJacjVFbEVqSVY2SnhFSldiOEpUc3hWRmh1R2FYNTNjS0c2OXZ6YjU2eTJTUkVWSkY3MGJGdkpLbkxLZG9wcGlqRWRmZklKUHJweXBpcW1NOXpLTXhjaU1WUXkyOC8wejczOS9Gb0daZFRKK2lSblZTc0YrODdQM2hnZWpMVjZxYU1yWjg2LzBycDR2U2JWR201MC9rYXZVYW91L0NmNkpWYWhBV3Z3azNmSGlPT1phak5LS3lMaWdObGwxaFFXU1V0alR5bWdaV0l6QmZRWVRSeUs2VW53ajZJR2NUMHRETk5wTUdWZlRnaTh1TVRPbW8rSEFaRDVFa3pnUTR4NHU5Vmt0ZEJjYTYzNWxYSkp6Y3NpTkZBa0xYenRzaGhZTTQrN0RPc3pIbDY2cGl3bWlRNHNlVWViVjk2K0dsMTk5VnhucngrZWY3b3JyR1JsSHN0WTZLNjdXbVNhRmJmWm5IbnJVVkJ5emc0cnpranB2blI5NDgyUFRvTUxLNzBiNStzQ3hUemlTcEhLRlZ5eklDSmtXajIvNUJjN3d3S2xLYVo5OTkvREtsYmhHdnhrbHI0OE5nZ3Y1QkRtOFlMd1ZSQW9UNW1aWk00QjBCR0hLSTN6ekdINWp3OEFZMk1QRUJJUEpDU0twcmpzOS8xSFh3VEMzQnVoaDQ5NWJUWUd5Z2tjRlk1QVlQYjFDeWN2TGtUMUE5K2J6MmQ1QUxnR2U4MndXby82ZmlnTFhsdGphUjVibHVGZ0MwKzM4SGc4Q29LWThWTVJZUG5PVmcwamdVMldQc2N5OUN3TmZsMGFWNnVhV2FvV1NoMHZqMWlWVVcvNzJqQzYySnE4OE1XcmVxZGN0WExsbkQ2ZGowVGI2blhDbkJMbVVucld0bVRNS1pYeC9wbjc3OG5WdG0yWnJodHVYUFNFL2lndGk4SFYxY2JGMXFXdmZPcGl0dXhZUlRuMzFHdFR2K1FQaDNtbjcyUmx0Nkh1TkY5OTQ5VWJnNlJYUFFVSy9CTC9DbExRaDV0LzZlVVVnc215R2l5S0FPSXEwRmpyMXZjNGhMR0tyN21QN3VDRHhjNThwbVhTQ0p4Q3VwL3BDeHlrVUlxNVBNMVBKSkJ4UXZ5a1l5OU1ucEhJWCtMdURWM2crZTk0blpGYmJnOHZhanJIQ1FJbVVzRXNHQlJqUVNBSUUxckVyNGIxdWhlR05lLzlQMi9vWCtMYTY5T2N2bEt6ZkRYQUdUR29XbnE2VnNyWVdTVnZOMk9QOGR5RC80Ri9KaDF3b1RJcmFiRy9pSTAyVGlvUVFWNEJGN2xrSVNpeHcxNDJyK1hrd1gvRFlrTUE1Vk9DVU5meUJhS3Bzc09WZTRXMEpSZGUvdEpYZjJlMU81bHMzZm1ITjFkZVdDa0hpYmRaQngxL0dsdXdCZDlMMmtZcWhTajJEUXlVRFc3TEoyYXM1d3ZHRTVta05NNUVmWTl3K1BTa1pyQTVGekE5akgvK3FjMUJQSEVRQ3BSQVBBeVRKZWpVeExIY1psay9meFRFSm81NWJOSEh3NmhlY3BVTWJLR3RVeGFkRGNyalJmcXlNV3k2UlppSHRCWnVwa2VpMkhER2N4dlBRdm9sdThCenVxVG55MFVKSTZZd1dtdTRGUTB1cjNpcVVIQmNtMkplNURFeEpLbm9GcUpTeW01WVVxNm9sOXA2NzdsVzkwUE55RkNpc1ptYUZXbHh0aGFjK2NnblhxN1pacnRvU3MzVnRxeFBkYVBuOTlUR2ZsOWJyU1c1UEFEQW44SkYwQ0dFdlNXaHpLM0ZyTElVaFFPNjdMb0dJT1lBeWVGRDYvT1paaG9JdktJUm1tRmFCaDNwL01KTkwrYlZ4TUV4VjVINDZjV0JEZDFFNTY5OGZyZDl3VmNIenJuZmVuNXQ5T3hydTlNWHQ5ZHY5UXRlSVZmczNOaDZjM2MwdithcitlTDI3YXViTDErSjNvaXVueDFlckdZTHVkS2x6dVd0K0F6cjk5L0RlM2dMenNJK2ZHK1dWa1RNQzZza3pwamtSRkhpcVFSaDBTVWx4T3dyYTV2cVhoeE1Ob0dieDc2NERRUUVuZ2hNOHBld3g3NHlhNTVDY3dmQWNVc3dQSXFkejJmdXhqcUNpK2ZYOXpmMnUrMVdvK3JabHE3Q1dYUldUa28rSGxYSGNhZWN4dVVmeXhDcisxTUNGZmRIWFVoU1JrdWNXdUF2R0RXWVhmdHNUcGRzUWN1bUN2Vk1YcFZGUWJaazBTbmd6dGJyRjFkdWZISlRpM1NKRFVsc1JDSVorK3ZOSzJ1Vm5aSG1LMU1QTy9YNzc2bnFwWVB2di94YUtwM0xhYWI5U2s3WEhDMU5pTUMxYjE3YytPeEw2N0s5UFVwUndtZExXU1d3MFZlYUY2NkhreFVpRk5halhGMi9udGVFdUpZZi9BcjlGU2JRZzY4bXhLYjBORTd1cStKYTF0T1k0dVJwVWNzYXN3VHhwQkFuMzlJWXNIZDF5cVorTW1kN3A2NnpIbC9Cc1E5bXhSbFVLNTV0NWJMUVF6Myt4QUNmVktlSHJZZmtoMlVubTV2eHE2cktFMWtXWGNVMEpWRXFqZGRyYXROcnFta3pIV3oyc3BTeUg1TXF5b1ZxNXVyYW5hbXpZcnRLMXZaZHJOV3NYTWx3Q25Ld1lraXQ4VUN0YmtaSmY2czkrQVg2TVNZd2hUOU9qdnk0dWRaNWVLNU4rdFgvWjdoMVo0M0hvZ0R3ZkluRnJIdXhRYmpWQ0d2bGtwS1JCRGFJQ1RGREM2MzJTRElDOTJpaTBLeU0yWERHS0VyR1g1Ny83K0tWaTEybnBPcUtVYTlZMDBhNGNYNGpERGVmcUdxRGxVb3Y0N1hjZE51UEZ6ZWZWNlRDU3VSRXF1T3FLYyt4WE4yb2pGdlJXajB2NllIVHk2dWNxdWROdlZCZmEyMWVyY1o4MVIvOFBmNFJ1UWRsS01UdEI5MWxDdlJXckQ5bFZGN296M2cwM2VJVDhiR1lJbWFwd1BNSFZqMmxudnZ3Q3k4OXQrZm1peWs1SitUUy9VOS8vdTNETU9WMmFnVk5zUStPdnZHSHYvZFcwN0d5S2NzMjlMM3Zmdi9kUDEzUEsrei9CZy8rRGYwSS94bE00WnZKbUNHN2lKSWFRckVNTFI2NFJJWFl0Z21Vd3hnQlJZZnhwY1JKM0twczhtTmpNNXVQajRBQ2h5aDMreUhRekQvWkp4VGl1WG1KaW1ORmxyRnFSbjZsWUtabFNUeUpWUmlPV2J2ZytjQm5uOU10dXNobDNqQVMveEJmRFFwTHk4RHpQeXQ3RW9mc1FPMmx0WnY1Rk9Kc3p6QkVURDVuRnZoYzlnanhGTFB4a2pmMWNHcUYyMGRYRWUzbXkxU25RYi9uV1JVRld5YXBOQTFxYW9RamJ0WFZwcit4cWJkS2lYN0wrRm4wNzdBTno4eWtzOTBHejJwazZZMkFVblJBRUVKNm5Od3ZIZ3V0UVJBQ0NvZ2VubDQvSmEyNUxHeWo3VVJhZTh3NXhwZGV4NWVhck9jTkIxczBUZ0Vydm5NK2RXY1F4V3J6MC9hWlNvWm4za2l4VmJlVW9rS0dxbWErUDU3MGZVRlVzMzJKSTNxK2FIazFtVmNFcGE1WG5wSHJHMDhFcTZTNWtzNjIyNldLcGRXMGNEZktkdnpPNWFldmI2WlQyVTZ0ODJ4elBlZlllYXZTTEtwbmlzNncwRGpQZUZnRkw1NlhCdkNadjJqYitNUWdtc3VwUHVFZ0Z0SEVrRGpKTGtGM2dXQnk5NkhkbVh0NjQ3aU02YktNRlZiR2ZrWEp3QUFOdU1XRmNYeVh0WENScHVVbDBmZXdaY1lYVzlOanNvVDRXcmcraVlvcExVRFpvdVVxSEpZa0taM0xZRkVSNUh5R2ZDeFZzZ3VwclBLaU12S2FzOHZGL3VzaklTZWFkYStWVnhyMXRLekxwQnhWdUp4dEZZVi8wVXRXcFhyZSsyR3BDd0QvQ3dBQS8vOEJBQUQvLzhKSE95VUFBQUFBQVFBQUFBRUNqMTdSSmE1ZkR6ejFBQThENkFBQUFBRGNkZkMrQUFBQUFOMm5Wbm4vaVA2WEJQNEVyQUFCQUFZQUFnQUFBQUFBQUFBQkFBQURoUDZpQUFBRktmK0kvZEVFL2dBQkFBQUFBQUFBQUFBQUFBQUFBQUFBRndJR0FDZ0Jod0FBQXZvQU1nSStBRElDRFFBN0F5d0FOZ0pJQUMwQ3BnQW9BbUVBTWdLSkFCNENLZ0F5QWtnQUxRRUdBRkVCQlFCY0FxSUFPd0ttQURJQ2F3QTdBWlVBT3dIM0FDUUM0Z0FsQWVVQUl3S05BRHNDRkFBeEFBQUFaQUJrQVB3QnlnSTBBdHdEYWdRZ0JOUUZwQVphQnlvSG5BZllDSkFJOGdtK0NrZ0s0QXNjQzdnTVBBekNBQUVBQUFBWEJLc0FDUUM2QUFVQUFnQXVBRjBBalFBQUFWa09EQUFEQUFGNG5KeVMzMm9UUVJUR2Y3dXRwVVhyQTNnMWhGNVlzWnRVc1pUMnFoVlRoRURVaW5pN204eitxV3RtMlpsdFNLNTlEaTk4RVBIUlpJK1RzaWtHUkVMWUh6dG56dmVkN3l5d3p3KzJDTGIzZ0ovaE44OEJUOElienlHN1llSjVpOVB3bWVkdERzSWR6dzhZQnI4ODc5QUx2bnZlNVNCWWV0N3I4RU42d1dmUGp6cThIL1E0OGZ5WUV5REg0YWl3bk5HbmoyVkNUVUdGd3hKaEtTaUpNTlJrOUJrelpNUkhjZ29zaWlHR0dRN0ZOWVlVeDV5WUdvM3lGU1VGRXpRekxKb3Bpb1laVXpRMUNrY3V0ZGU4WllSaVRDVzEzYzZqdFE3UFVYeVMyNjAzSTdYSFJQSlhIV2ZydWlzM01iZkVNbE5NUWlrbmN3cngwWjYydWhlOEYzYWNvZjR6b2JuOElod0xtYWoxNEVRbllvTGhLeDh3SkRKSE8rTWJjVDlDMDRoQ3poYzBReHFXTEZsd1NVUGlIVnVPdU1SUU12MXJFZ09PR1hBdTZUcFNZaG9jUnBMNWsraFRicVh5bElpWEhIWjAxRDBsZGFmVTdYekZtREZYblArRHc5VnpzOFpyREJVTFNUV1R2QlV2R0REZ2xkK29scjFzOXZpT0dzTU5tb25jdnBDSmM5bUpsWG52YnpIek8yLzdyRGJTdmpVWU11bWIrdS9QMGllOTB6NGlXZE0rL0EwQUFQLy9BUUFBLy8vM2daeXdlSnhpWUdZQWcvL1ZEQ0lNV0FBQUFBRC8vd0VBQVAvL0pSWUJrbmljTklreHFzSkFGRVh2dkQ4L1BnMG9Wb0lXSWdwS1ZqR0UxMWtwRmttZExNQWwyQWhwZEMxNWhFQmlOdUN1bEVud1Z1ZWNpMk9EOXpsUlk1NXBhVm9HSTdzcVJuR0ZKU0h5VksvdGpHa3k4SllXUWMvanVHTllnQkJHaWpEdTRPQjZhL0VIaU81TWNVcEtWeVRlYzlHRDk0WXhCRWk2MHIxUEw3N0JXRmRrbDkvaFYyOW9IdEEwYXN6blh0cUhFcVQ2endPSWZBRUFBUC8vQVFBQS8vK2I4aXVIIik7Cn1dXT48L3N0eWxlPjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+PCFbQ0RBVEFbLnNoYXBlIHsKICBzaGFwZS1yZW5kZXJpbmc6IGdlb21ldHJpY1ByZWNpc2lvbjsKICBzdHJva2UtbGluZWpvaW46IHJvdW5kOwp9Ci5jb25uZWN0aW9uIHsKICBzdHJva2UtbGluZWNhcDogcm91bmQ7CiAgc3Ryb2tlLWxpbmVqb2luOiByb3VuZDsKfQouYmxlbmQgewogIG1peC1ibGVuZC1tb2RlOiBtdWx0aXBseTsKICBvcGFjaXR5OiAwLjU7Cn0KCgkJLmQyLTM4NTc1NTE5OCAuZmlsbC1OMXtmaWxsOiMwQTBGMjU7fQoJCS5kMi0zODU3NTUxOTggLmZpbGwtTjJ7ZmlsbDojNjc2QzdFO30KCQkuZDItMzg1NzU1MTk4IC5maWxsLU4ze2ZpbGw6Izk0OTlBQjt9CgkJLmQyLTM4NTc1NTE5OCAuZmlsbC1ONHtmaWxsOiNDRkQyREQ7fQoJCS5kMi0zODU3NTUxOTggLmZpbGwtTjV7ZmlsbDojREVFMUVCO30KCQkuZDItMzg1NzU1MTk4IC5maWxsLU42e2ZpbGw6I0VFRjFGODt9CgkJLmQyLTM4NTc1NTE5OCAuZmlsbC1ON3tmaWxsOiNGRkZGRkY7fQoJCS5kMi0zODU3NTUxOTggLmZpbGwtQjF7ZmlsbDojMEQzMkIyO30KCQkuZDItMzg1NzU1MTk4IC5maWxsLUIye2ZpbGw6IzBEMzJCMjt9CgkJLmQyLTM4NTc1NTE5OCAuZmlsbC1CM3tmaWxsOiNFM0U5RkQ7fQoJCS5kMi0zODU3NTUxOTggLmZpbGwtQjR7ZmlsbDojRTNFOUZEO30KCQkuZDItMzg1NzU1MTk4IC5maWxsLUI1e2ZpbGw6I0VERjBGRDt9CgkJLmQyLTM4NTc1NTE5OCAuZmlsbC1CNntmaWxsOiNGN0Y4RkU7fQoJCS5kMi0zODU3NTUxOTggLmZpbGwtQUEye2ZpbGw6IzRBNkZGMzt9CgkJLmQyLTM4NTc1NTE5OCAuZmlsbC1BQTR7ZmlsbDojRURGMEZEO30KCQkuZDItMzg1NzU1MTk4IC5maWxsLUFBNXtmaWxsOiNGN0Y4RkU7fQoJCS5kMi0zODU3NTUxOTggLmZpbGwtQUI0e2ZpbGw6I0VERjBGRDt9CgkJLmQyLTM4NTc1NTE5OCAuZmlsbC1BQjV7ZmlsbDojRjdGOEZFO30KCQkuZDItMzg1NzU1MTk4IC5zdHJva2UtTjF7c3Ryb2tlOiMwQTBGMjU7fQoJCS5kMi0zODU3NTUxOTggLnN0cm9rZS1OMntzdHJva2U6IzY3NkM3RTt9CgkJLmQyLTM4NTc1NTE5OCAuc3Ryb2tlLU4ze3N0cm9rZTojOTQ5OUFCO30KCQkuZDItMzg1NzU1MTk4IC5zdHJva2UtTjR7c3Ryb2tlOiNDRkQyREQ7fQoJCS5kMi0zODU3NTUxOTggLnN0cm9rZS1ONXtzdHJva2U6I0RFRTFFQjt9CgkJLmQyLTM4NTc1NTE5OCAuc3Ryb2tlLU42e3N0cm9rZTojRUVGMUY4O30KCQkuZDItMzg1NzU1MTk4IC5zdHJva2UtTjd7c3Ryb2tlOiNGRkZGRkY7fQoJCS5kMi0zODU3NTUxOTggLnN0cm9rZS1CMXtzdHJva2U6IzBEMzJCMjt9CgkJLmQyLTM4NTc1NTE5OCAuc3Ryb2tlLUIye3N0cm9rZTojMEQzMkIyO30KCQkuZDItMzg1NzU1MTk4IC5zdHJva2UtQjN7c3Ryb2tlOiNFM0U5RkQ7fQoJCS5kMi0zODU3NTUxOTggLnN0cm9rZS1CNHtzdHJva2U6I0UzRTlGRDt9CgkJLmQyLTM4NTc1NTE5OCAuc3Ryb2tlLUI1e3N0cm9rZTojRURGMEZEO30KCQkuZDItMzg1NzU1MTk4IC5zdHJva2UtQjZ7c3Ryb2tlOiNGN0Y4RkU7fQoJCS5kMi0zODU3NTUxOTggLnN0cm9rZS1BQTJ7c3Ryb2tlOiM0QTZGRjM7fQoJCS5kMi0zODU3NTUxOTggLnN0cm9rZS1BQTR7c3Ryb2tlOiNFREYwRkQ7fQoJCS5kMi0zODU3NTUxOTggLnN0cm9rZS1BQTV7c3Ryb2tlOiNGN0Y4RkU7fQoJCS5kMi0zODU3NTUxOTggLnN0cm9rZS1BQjR7c3Ryb2tlOiNFREYwRkQ7fQoJCS5kMi0zODU3NTUxOTggLnN0cm9rZS1BQjV7c3Ryb2tlOiNGN0Y4RkU7fQoJCS5kMi0zODU3NTUxOTggLmJhY2tncm91bmQtY29sb3ItTjF7YmFja2dyb3VuZC1jb2xvcjojMEEwRjI1O30KCQkuZDItMzg1NzU1MTk4IC5iYWNrZ3JvdW5kLWNvbG9yLU4ye2JhY2tncm91bmQtY29sb3I6IzY3NkM3RTt9CgkJLmQyLTM4NTc1NTE5OCAuYmFja2dyb3VuZC1jb2xvci1OM3tiYWNrZ3JvdW5kLWNvbG9yOiM5NDk5QUI7fQoJCS5kMi0zODU3NTUxOTggLmJhY2tncm91bmQtY29sb3ItTjR7YmFja2dyb3VuZC1jb2xvcjojQ0ZEMkREO30KCQkuZDItMzg1NzU1MTk4IC5iYWNrZ3JvdW5kLWNvbG9yLU41e2JhY2tncm91bmQtY29sb3I6I0RFRTFFQjt9CgkJLmQyLTM4NTc1NTE5OCAuYmFja2dyb3VuZC1jb2xvci1ONntiYWNrZ3JvdW5kLWNvbG9yOiNFRUYxRjg7fQoJCS5kMi0zODU3NTUxOTggLmJhY2tncm91bmQtY29sb3ItTjd7YmFja2dyb3VuZC1jb2xvcjojRkZGRkZGO30KCQkuZDItMzg1NzU1MTk4IC5iYWNrZ3JvdW5kLWNvbG9yLUIxe2JhY2tncm91bmQtY29sb3I6IzBEMzJCMjt9CgkJLmQyLTM4NTc1NTE5OCAuYmFja2dyb3VuZC1jb2xvci1CMntiYWNrZ3JvdW5kLWNvbG9yOiMwRDMyQjI7fQoJCS5kMi0zODU3NTUxOTggLmJhY2tncm91bmQtY29sb3ItQjN7YmFja2dyb3VuZC1jb2xvcjojRTNFOUZEO30KCQkuZDItMzg1NzU1MTk4IC5iYWNrZ3JvdW5kLWNvbG9yLUI0e2JhY2tncm91bmQtY29sb3I6I0UzRTlGRDt9CgkJLmQyLTM4NTc1NTE5OCAuYmFja2dyb3VuZC1jb2xvci1CNXtiYWNrZ3JvdW5kLWNvbG9yOiNFREYwRkQ7fQoJCS5kMi0zODU3NTUxOTggLmJhY2tncm91bmQtY29sb3ItQjZ7YmFja2dyb3VuZC1jb2xvcjojRjdGOEZFO30KCQkuZDItMzg1NzU1MTk4IC5iYWNrZ3JvdW5kLWNvbG9yLUFBMntiYWNrZ3JvdW5kLWNvbG9yOiM0QTZGRjM7fQoJCS5kMi0zODU3NTUxOTggLmJhY2tncm91bmQtY29sb3ItQUE0e2JhY2tncm91bmQtY29sb3I6I0VERjBGRDt9CgkJLmQyLTM4NTc1NTE5OCAuYmFja2dyb3VuZC1jb2xvci1BQTV7YmFja2dyb3VuZC1jb2xvcjojRjdGOEZFO30KCQkuZDItMzg1NzU1MTk4IC5iYWNrZ3JvdW5kLWNvbG9yLUFCNHtiYWNrZ3JvdW5kLWNvbG9yOiNFREYwRkQ7fQoJCS5kMi0zODU3NTUxOTggLmJhY2tncm91bmQtY29sb3ItQUI1e2JhY2tncm91bmQtY29sb3I6I0Y3RjhGRTt9CgkJLmQyLTM4NTc1NTE5OCAuY29sb3ItTjF7Y29sb3I6IzBBMEYyNTt9CgkJLmQyLTM4NTc1NTE5OCAuY29sb3ItTjJ7Y29sb3I6IzY3NkM3RTt9CgkJLmQyLTM4NTc1NTE5OCAuY29sb3ItTjN7Y29sb3I6Izk0OTlBQjt9CgkJLmQyLTM4NTc1NTE5OCAuY29sb3ItTjR7Y29sb3I6I0NGRDJERDt9CgkJLmQyLTM4NTc1NTE5OCAuY29sb3ItTjV7Y29sb3I6I0RFRTFFQjt9CgkJLmQyLTM4NTc1NTE5OCAuY29sb3ItTjZ7Y29sb3I6I0VFRjFGODt9CgkJLmQyLTM4NTc1NTE5OCAuY29sb3ItTjd7Y29sb3I6I0ZGRkZGRjt9CgkJLmQyLTM4NTc1NTE5OCAuY29sb3ItQjF7Y29sb3I6IzBEMzJCMjt9CgkJLmQyLTM4NTc1NTE5OCAuY29sb3ItQjJ7Y29sb3I6IzBEMzJCMjt9CgkJLmQyLTM4NTc1NTE5OCAuY29sb3ItQjN7Y29sb3I6I0UzRTlGRDt9CgkJLmQyLTM4NTc1NTE5OCAuY29sb3ItQjR7Y29sb3I6I0UzRTlGRDt9CgkJLmQyLTM4NTc1NTE5OCAuY29sb3ItQjV7Y29sb3I6I0VERjBGRDt9CgkJLmQyLTM4NTc1NTE5OCAuY29sb3ItQjZ7Y29sb3I6I0Y3RjhGRTt9CgkJLmQyLTM4NTc1NTE5OCAuY29sb3ItQUEye2NvbG9yOiM0QTZGRjM7fQoJCS5kMi0zODU3NTUxOTggLmNvbG9yLUFBNHtjb2xvcjojRURGMEZEO30KCQkuZDItMzg1NzU1MTk4IC5jb2xvci1BQTV7Y29sb3I6I0Y3RjhGRTt9CgkJLmQyLTM4NTc1NTE5OCAuY29sb3ItQUI0e2NvbG9yOiNFREYwRkQ7fQoJCS5kMi0zODU3NTUxOTggLmNvbG9yLUFCNXtjb2xvcjojRjdGOEZFO30uYXBwZW5kaXggdGV4dC50ZXh0e2ZpbGw6IzBBMEYyNX0ubWR7LS1jb2xvci1mZy1kZWZhdWx0OiMwQTBGMjU7LS1jb2xvci1mZy1tdXRlZDojNjc2QzdFOy0tY29sb3ItZmctc3VidGxlOiM5NDk5QUI7LS1jb2xvci1jYW52YXMtZGVmYXVsdDojRkZGRkZGOy0tY29sb3ItY2FudmFzLXN1YnRsZTojRUVGMUY4Oy0tY29sb3ItYm9yZGVyLWRlZmF1bHQ6IzBEMzJCMjstLWNvbG9yLWJvcmRlci1tdXRlZDojMEQzMkIyOy0tY29sb3ItbmV1dHJhbC1tdXRlZDojRUVGMUY4Oy0tY29sb3ItYWNjZW50LWZnOiMwRDMyQjI7LS1jb2xvci1hY2NlbnQtZW1waGFzaXM6IzBEMzJCMjstLWNvbG9yLWF0dGVudGlvbi1zdWJ0bGU6IzY3NkM3RTstLWNvbG9yLWRhbmdlci1mZzpyZWQ7fS5za2V0Y2gtb3ZlcmxheS1CMXtmaWxsOnVybCgjc3RyZWFrcy1kYXJrZXItZDItMzg1NzU1MTk4KTttaXgtYmxlbmQtbW9kZTpsaWdodGVufS5za2V0Y2gtb3ZlcmxheS1CMntmaWxsOnVybCgjc3RyZWFrcy1kYXJrZXItZDItMzg1NzU1MTk4KTttaXgtYmxlbmQtbW9kZTpsaWdodGVufS5za2V0Y2gtb3ZlcmxheS1CM3tmaWxsOnVybCgjc3RyZWFrcy1icmlnaHQtZDItMzg1NzU1MTk4KTttaXgtYmxlbmQtbW9kZTpkYXJrZW59LnNrZXRjaC1vdmVybGF5LUI0e2ZpbGw6dXJsKCNzdHJlYWtzLWJyaWdodC1kMi0zODU3NTUxOTgpO21peC1ibGVuZC1tb2RlOmRhcmtlbn0uc2tldGNoLW92ZXJsYXktQjV7ZmlsbDp1cmwoI3N0cmVha3MtYnJpZ2h0LWQyLTM4NTc1NTE5OCk7bWl4LWJsZW5kLW1vZGU6ZGFya2VufS5za2V0Y2gtb3ZlcmxheS1CNntmaWxsOnVybCgjc3RyZWFrcy1icmlnaHQtZDItMzg1NzU1MTk4KTttaXgtYmxlbmQtbW9kZTpkYXJrZW59LnNrZXRjaC1vdmVybGF5LUFBMntmaWxsOnVybCgjc3RyZWFrcy1kYXJrLWQyLTM4NTc1NTE5OCk7bWl4LWJsZW5kLW1vZGU6b3ZlcmxheX0uc2tldGNoLW92ZXJsYXktQUE0e2ZpbGw6dXJsKCNzdHJlYWtzLWJyaWdodC1kMi0zODU3NTUxOTgpO21peC1ibGVuZC1tb2RlOmRhcmtlbn0uc2tldGNoLW92ZXJsYXktQUE1e2ZpbGw6dXJsKCNzdHJlYWtzLWJyaWdodC1kMi0zODU3NTUxOTgpO21peC1ibGVuZC1tb2RlOmRhcmtlbn0uc2tldGNoLW92ZXJsYXktQUI0e2ZpbGw6dXJsKCNzdHJlYWtzLWJyaWdodC1kMi0zODU3NTUxOTgpO21peC1ibGVuZC1tb2RlOmRhcmtlbn0uc2tldGNoLW92ZXJsYXktQUI1e2ZpbGw6dXJsKCNzdHJlYWtzLWJyaWdodC1kMi0zODU3NTUxOTgpO21peC1ibGVuZC1tb2RlOmRhcmtlbn0uc2tldGNoLW92ZXJsYXktTjF7ZmlsbDp1cmwoI3N0cmVha3MtZGFya2VyLWQyLTM4NTc1NTE5OCk7bWl4LWJsZW5kLW1vZGU6bGlnaHRlbn0uc2tldGNoLW92ZXJsYXktTjJ7ZmlsbDp1cmwoI3N0cmVha3MtZGFyay1kMi0zODU3NTUxOTgpO21peC1ibGVuZC1tb2RlOm92ZXJsYXl9LnNrZXRjaC1vdmVybGF5LU4ze2ZpbGw6dXJsKCNzdHJlYWtzLW5vcm1hbC1kMi0zODU3NTUxOTgpO21peC1ibGVuZC1tb2RlOmNvbG9yLWJ1cm59LnNrZXRjaC1vdmVybGF5LU40e2ZpbGw6dXJsKCNzdHJlYWtzLW5vcm1hbC1kMi0zODU3NTUxOTgpO21peC1ibGVuZC1tb2RlOmNvbG9yLWJ1cm59LnNrZXRjaC1vdmVybGF5LU41e2ZpbGw6dXJsKCNzdHJlYWtzLWJyaWdodC1kMi0zODU3NTUxOTgpO21peC1ibGVuZC1tb2RlOmRhcmtlbn0uc2tldGNoLW92ZXJsYXktTjZ7ZmlsbDp1cmwoI3N0cmVha3MtYnJpZ2h0LWQyLTM4NTc1NTE5OCk7bWl4LWJsZW5kLW1vZGU6ZGFya2VufS5za2V0Y2gtb3ZlcmxheS1ON3tmaWxsOnVybCgjc3RyZWFrcy1icmlnaHQtZDItMzg1NzU1MTk4KTttaXgtYmxlbmQtbW9kZTpkYXJrZW59LmxpZ2h0LWNvZGV7ZGlzcGxheTogYmxvY2t9LmRhcmstY29kZXtkaXNwbGF5OiBub25lfV1dPjwvc3R5bGU+PGRlZnM+PHBhdHRlcm4gaWQ9InN0cmVha3MtYnJpZ2h0LWQyLTM4NTc1NTE5OCIgeD0iMCIgeT0iMCIgd2lkdGg9IjEwMCIgaGVpZ2h0PSIxMDAiIHBhdHRlcm5Vbml0cz0idXNlclNwYWNlT25Vc2UiPgogICAgPHBhdGggZmlsbD0icmdiYSgwLCAwLCAwLCAwLjEpIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTU4LjExOTMgMEg1OC4xNzAzTDU1LjQ5MzkgMi42NzY0NEw1OC4xMTkzIDBaTTQ1Ljc3MjUgMEg0NS44MTFMNDEuMjg1MSA0LjYxNDk4TDQyLjcxOTEgMy4yOTMyNUwzNy4wODI0IDguOTI5OTdMMzUuMDU1NCAxMC45NTY5TDMyLjA3MTkgMTMuOTQwNEwyOS42MjI5IDE2LjUwMTdMMjcuMTczOCAxOS4wNjMxTDI1LjgwODkgMjAuMjAzNEwyMy4yMTk1IDIyLjYyNDRMMTguMTgxIDI3LjYwNjhMMjMuODE3OCAyMS45N0wyNy4wNjE1IDE4Ljk1MDhMMzMuODY2NiAxMS45NzczTDMzLjE1NjIgMTIuNTE5NEwzNy4wMjYyIDguODczODNMNDAuNzg0IDUuMTE2MDJMMzguMDI5OSA3LjY0NTYxTDQ1Ljc3MjUgMFpNMjMuMTA3OSAwSDIzLjEwOEwyMS41ODE0IDEuNjY2ODhMMjAuMzEyNiAyLjc5NTM0TDIzLjEwNzkgMFpNNy41Mzg2OSAwSDcuNTQyNTRMNy41MDAwNSAwLjAzNTk0NEw3LjUzODY5IDBaTTIuNDk5OTUgMEgyLjUyMzYyTDAuOTAwMjQ1IDEuNTk5NzFMMi40OTk5NSAwWk0wIDMuNjQzOThWMy42MDc0NEwwLjI3ODM4NiAzLjM2NTU5TDAgMy42NDM5OFpNMCAxOC42NTY0VjE4LjUzOThMMC42Nzk4NSAxNy44NDE2TDMuNDQ1OSAxNS4wNzU1TDEuMTU3MDEgMTcuMTMzM0wyLjc4NzEzIDE1LjYwMjJMNi4wMTQzNyAxMi41MDdMOC41MTY4IDkuODcyNTNMNS4xNTgwMyAxMy4yMzEzTDExLjAzNTcgNy4yNTQ1M0wxMC40OTI2IDcuODk2NzhMMTMuNjg2OCA0Ljc2ODZMOC41NDk4MiA5LjkwNTU1TDcuMDUxNzcgMTEuNTY4N0w0LjY4MDg3IDEzLjkzOTZMMC43MjkzNzkgMTcuODkxMUwzLjAxODI3IDE1LjgzMzNMMCAxOC42NTY0Wk0wIDY5LjI0MzFWNjkuMTc4TDEuNjQ2NTEgNjcuNDc2M0wxLjQ2MzQ3IDY3Ljc3OTZMNS44NDA2MyA2My40MDI1TDQuNDIxNjcgNjQuOTAxNkwwIDY5LjQwMDdWNjkuMzQwOEwwLjI0NzU5NiA2OC45OTU1TDAgNjkuMjQzMVpNMi41MTU5NCAxMDBIMi40OTIzOEw1LjE5OTg5IDk3LjI5MjVMNy43MDA3MSA5NS4wMTYyTDEyLjg3MTMgODkuNjc3MkwxMi4zMDk0IDkwLjA3MDdMMTUuMjg4IDg3LjMxNjdMMTguMTU0MiA4NC40NTA0TDE2LjAyNjkgODYuMzUzMkwyMi44NzUyIDc5LjYxNzJMMTguNTM2NCA4NC4wNjgzTDE5LjY0MzUgODMuMDczNEwxNS4zNDQxIDg3LjM3MjhMMTMuNzk4IDg4LjkxODlMMTEuNTIyNCA5MS4xOTQ1TDkuNjY3NjggOTMuMTYxNUw3LjgxMjk3IDk1LjEyODVMNi43NDUyOSA5NS45NzE2TDQuNzUwMjQgOTcuNzk4M0wyLjUxNTk0IDEwMFpNNy41NDI1NSAxMDBINy41Mzg3TDkuODEzOTYgOTcuODg0TDguNDY2MDYgOTkuMjE4OUw3LjU0MjU1IDEwMFpNNDUuODE4OSAxMDBINDUuNzgwN0w0Ni45OTEyIDk4LjgwNDdMNDUuODE4OSAxMDBaTTU4LjE3ODQgMTAwSDU4LjEyNzJMNjIuMjk1MiA5NS43NTExTDY2LjE0MDggOTEuOTA1NUw2My4wMDM3IDk0LjgxMTVMNjUuMjUwNyA5Mi42NjM1TDY5LjcxMTcgODguMzM0Nkw3My4yMTY1IDg0LjY5NzdMNjguNTQ2OSA4OS4zNjczTDc2LjczNzkgODEuMDc3M0w3NS45NjM0IDgxLjk1MDlMODAuMzkxMyA3Ny41ODg5TDczLjI0OTYgODQuNzMwN0w3MS4xMzQ2IDg3LjAxMDdMNjcuODM4NCA5MC4zMDY5TDYyLjM0NDcgOTUuODAwNkw2NS40ODE4IDkyLjg5NDdMNjEuMjYyNSA5Ni45MTU5TDU4LjE3ODQgMTAwWk03NS40Mjc3IDEwMEg3NS4yMjlMODIuMTgzNCA5Mi45MDM5TDgxLjM0MDMgOTMuNTc4N0w4Ni4wMDYzIDg5LjEzNzFMOTAuNTYwMSA4NC41ODMzTDg3LjI0NjQgODcuNjcyNUw5OC4wOTM3IDc2LjkzNzVMOTEuMTY3MyA4My45NzYxTDkyLjg5MzIgODIuMzYyNUw4Ni4wNjI1IDg5LjE5MzNMODMuNjA2MiA5MS42NDk2TDc5Ljk5MDcgOTUuMjY1TDc3LjAxMSA5OC4zNTdMNzUuNDI3NyAxMDBaTTEwMCAxOC41Mzk4VjE4LjY1NjNMOTkuOTU1NiAxOC42OTc5TDk1LjgwNjUgMjIuODQ3TDEwMCAxOC41Mzk4Wk0xMDAgMy42MDc0M1YzLjY0Mzk4TDk5LjY3OTEgMy45NjQ5TDk5LjIwOTQgNC4yOTQyOEwxMDAgMy42MDc0M1pNNzUuNDIwMSAwTDc0LjAzMTIgMS40NDEyTDcyLjQwMSAyLjg0Njg3TDY5LjI4MSA1Ljc5ODU0TDYzLjE4MTIgMTEuODQyMkw3MC4wMTE5IDUuMDExNTFMNzMuOTE5IDEuMzI4OTNMNzUuMjIxNCAwSDc1LjQyMDFaTTEwMCA2OS4xODU4VjY5LjI1MDlMOTguMDU5IDcxLjE5MTlMMTAwIDY5LjE4NThaTTEwMCA2OS4zNDg2VjY5LjQwODVMOTkuODQxNCA2OS41Njk4TDEwMCA2OS4zNDg2Wk00MS45Mzk4IDI4LjgyNTRMNTMuNjIyMyAxNi45OTNMNTIuNTIxNSAxOC4yNDM3TDU0Ljc0MjggMTYuMDU3NUw1NC42ODc1IDE2LjA3NTlMNTQuODAwOCAxNi4wMDA0TDU4Ljg0MiAxMi4wMjMxTDU0Ljk5MjUgMTUuODcyNkw1NS4xMDg1IDE1Ljc5NTNMNTQuODk4IDE2LjAwNThMNTQuODQgMTYuMDI1MUw0OC42NTIzIDIyLjIxMjhMNDUuNjQxOSAyNS40NzNMNDAuOTM4OSAzMC4xNzU5TDMzLjEwMDcgMzguMDE0MkwzNy41ODY2IDMzLjg3OEwzMS41NTggMzkuNjA2OEwyMy4zMjc4IDQ3LjgzN0wzMy4wMjU3IDM3LjkzOTNMMzguNTEyNSAzMi40NTI1TDM0LjAyNjYgMzYuNTg4N0wzNy4yMzY5IDMzLjUyODNMNDMuNjA3NCAyNy4zNTc2TDQ4LjYwMjMgMjIuMTYyOEw0MS45Mzk4IDI4LjgyNTRaTTQxLjA5NzcgMTcuMDUzMUwzOS43MTggMTguMjkyNUw0MC4zMTIgMTcuODM4OEw0MS4wOTc3IDE3LjA1MzFaTTM2Ljg3NSAyMC4zMTA2TDQ4LjE2MDEgNy44ODEzN0w0Mi4zNDM4IDEzLjc0NzhMMzYuODc1IDIwLjMxMDZaTTM1LjcxMjUgMjUuODEwOUwzNC4zMzI4IDI3LjA1MDNMMzQuOTI2OCAyNi41OTY2TDM1LjcxMjUgMjUuODEwOVpNMTcuNzAyMiAzOS43NTM0TDE5LjA4MTkgMzguNTE0TDE4LjgwOTIgMzguNzg2N0wzNi43NTc1IDIxLjgwNDVMMjMuMTU2OSAzNS4zMDUxTDEzLjU3NzEgNDMuNzM3MkwxOC4xNDQ4IDM5LjQxNTRMMTcuNzAyMiAzOS43NTM0Wk0zLjQ4MTAyIDI4LjkyODFMMS41MzU2MiAzMC44NzM1TDEuMjIyMjggMzEuMDQ2NUwwLjA3NjU2ODYgMzIuMzMyNkwxLjYwNTc5IDMwLjk0MzdMMi41Nzg0OSAyOS45NzFMMy40ODEwMiAyOC45MjgxWk0wLjk1MzQ2MyAyNi4yMDI3TDE5LjU3MDIgNy41ODU5NEw5LjMxNTc1IDE4LjYwNzhMMC45NTM0NjMgMjYuMjAyN1pNMjMuNzE3NSAxMi4xMUwxNy45MzM5IDE4LjA4NzVMMjEuNDYyMiAxNC41NTkyTDIwLjgwNzQgMTUuNDcyNUwyOC4xOTE1IDcuOTU5MThMMzAuNDc5MSA1LjU0MjMyTDIzLjQyMjQgMTIuNTk5TDIzLjcxNzUgMTIuMTFaTTQzLjQ2NDEgNDMuMTUzOEw0MC43ODcyIDQ2LjE1NTJMNDIuNDkwNyA0NC40NTE3TDQyLjMyODUgNDUuMDQ2NUw0NS44MTY2IDQxLjM0MjFMNDYuODQ0MSA0MC4wOTgzTDQzLjQzNzEgNDMuNTA1M0w0My40NjQxIDQzLjE1MzhaTTEuMzI3MTUgNDguMzI3MUw4LjA5MTggNDEuNTYyNUw0LjM2NTcgNDUuNTY3NEwxLjMyNzE1IDQ4LjMyNzFaTTExLjE0NzkgMzEuMjU1NkwxMS41Njg5IDMwLjk3NUwxMS4zNTg0IDMxLjE4NTVMMTEuMTQ3OSAzMS4yNTU2Wk0xMS45ODk4IDI3LjQ2NjdMMTIuMjAwMyAyNy4yNTYyTDExLjc3OTMgMjcuNTM2OUwxMS45ODk4IDI3LjQ2NjdaTTExLjM1ODUgMzQuNTUzMUwxMS4xNDggMzQuNzYzNkwxMC45Mzc1IDM0LjgzMzhMMTEuMzU4NSAzNC41NTMxWk03Mi45MjkgMjguNTQ1N0w4Mi4yOTY1IDE5LjA3OTJMODEuNDA0MyAyMC4wNzA1TDg2LjQ1OTcgMTUuMDgxMUw3OC4yOTgzIDIzLjI0MjVMNzUuODY5NyAyNS44MzYyTDcyLjEwMjkgMjkuNjAzTDY1LjgyNDkgMzUuODgxTDY5LjM5MzQgMzIuNTQzN0w2NC41ODU4IDM3LjE1MzFMNTcuOTk0IDQzLjc0NUw2NS43NzU0IDM1LjgzMTRMNzAuMTcgMzEuNDM2OUw2Ni42MDE1IDM0Ljc3NDJMNjkuMTYyMyAzMi4zMTI1TDc0LjI1MDcgMjcuMzU2Mkw3OC4yNjUzIDIzLjIwOTVMNzIuOTI5IDI4LjU0NTdaTTgyLjY2NzQgMS44MzU0OUw4NC4zMjQ1IDAuMzE4NzJMODMuMzcyNCAxLjI3MDg4TDgyLjY2NzQgMS44MzU0OVpNNjQuNTg3MiAxNi4xMzEyTDYyLjkzMDEgMTcuNjQ4TDYzLjYzNTEgMTcuMDgzNEw2NC41ODcyIDE2LjEzMTJaTTcwLjg2OCA5Ljg1MDQ0TDgwLjAwNDggMS4xMjE0TDc0LjYyMjEgNi40NzE0Mkw3MC44NjggOS44NTA0NFpNOTAuMjQwOSA0MS45NDQ4TDcwLjc1NzggNjEuNDI3OUw3OS41MDkzIDUzLjQ3OTVMOTAuMjQwOSA0MS45NDQ4Wk05MS44MDg4IDQyLjU0MzRMOTUuMzk2MyAzOC44MzU3TDk1LjIxMzIgMzkuMTM5TDk5LjU5MDQgMzQuNzYxOEw5OC4xNzE0IDM2LjI2MUw5My41OTEyIDQwLjkyMTRMOTMuOTk3MyA0MC4zNTQ5TDkxLjgwODggNDIuNTQzNFpNOTQuMzMxIDEyLjgyMzNMODkuOTg1MyAxNy4xNjkxTDg5LjI4NTMgMTcuNTU1NUw4Ni43MjU5IDIwLjQyODRMOTAuMTQyIDE3LjMyNThMOTIuMzE0OSAxNS4xNTI5TDk0LjMzMSAxMi44MjMzWk00NC43OTcyIDYyLjMyNTlMNzYuOTgyNCAzMC4xNDA2TDU5LjI1NDIgNDkuMTk1NUw0NC43OTcyIDYyLjMyNTlaTTc3LjE0ODIgNDAuMzIxTDcwLjE3MDkgNDcuNTMyM0w3MCA0Ny42NDYzTDcwLjA4OTUgNDcuNjE2NEw2OC4xOTE2IDQ5LjU3NzlMNzAuMTg1IDQ3LjU4NDZMNzAuMjEwNSA0Ny41NzYxTDcwLjQyMSA0Ny4zNjU2TDcwLjM3IDQ3LjM5OTZMNzMuNjU1NyA0NC4xMTM5TDcyLjY0MTYgNDUuNTI4M0w4NC4wNzY4IDMzLjg5M0w4Ny42MTk0IDMwLjE1MDJMNzYuNjkxMyA0MS4wNzgzTDc3LjE0ODIgNDAuMzIxWk01MC41MzU1IDM0LjMxMzdMNzIuNjYxNyAxMi4xODc1TDYwLjQ5NTUgMjUuMzA4NEw1MC41MzU1IDM0LjMxMzdaTTcwLjIxMDQgNDQuMDY4MUw3MC42MzE0IDQzLjc4NzVMNzAuNDIwOSA0My45OThMNzAuMjEwNCA0NC4wNjgxWk03MS4yNjMgNDAuMDY4N0w3MC44NDIgNDAuMzQ5NEw3MS4wNTI1IDQwLjI3OTJMNzEuMjYzIDQwLjA2ODdaTTU1LjEwODQgMTIuNDM1NUw1NS4zMTg5IDEyLjIyNUw1NC44OTc5IDEyLjUwNTZMNTUuMTA4NCAxMi40MzU1Wk00OC44NzE4IDE1LjU3ODVMNjAuMjA3NSA0LjcwNDk2TDQ5LjQwNTYgMTUuNDAwNkw0OC44NzE4IDE1LjU3ODVaTTIzLjc2MzYgNTcuNDQ5MUwyOS45MDk5IDUxLjU4NTRMMjYuMTY1NiA1NS42MTIzTDI3LjIzNjEgNTQuODI0NEwyMy40MzUgNTguNjI1NUwyMi4wNjgxIDU5Ljk5MjRMMjAuMDU2MiA2Mi4wMDQyTDE4LjUwODIgNjMuODM0OUwxNi45NjAxIDY1LjY2NTZMMTUuODMyOCA2Ni4yMjc3TDEzLjkzMTUgNjcuNzA1MUwxMC40ODIxIDcxLjAxMzJMMTQuMjgzMiA2Ny4yMTIxTDE2LjY3NzUgNjUuMzgzTDIxLjExMTMgNjAuNTI1M0wyMC40NzcgNjAuNzM1N0wyMy4yOTM3IDU4LjQ4NDJMMjUuODI3NyA1NS45NTAyTDIzLjc2MzYgNTcuNDQ5MVpNNDguMzgyNSA3NC4xODI0TDQ0Ljg4MzIgNzcuODUyM0w0Ni45MTQ1IDc1LjgyMTFMNDUuNDc0OCA3Ny40ODgxTDQzLjQ0OTMgNzkuMjg2Mkw0Mi40MDgyIDgwLjE1NjhMNDMuOTIxNSA3OS4wNDE0TDQyLjI0ODcgODAuNzE0M0wzOS4zNzUyIDgzLjgxNTFMNDEuODg0NCA4MS4zMDU5TDQzLjg0NzMgNzkuNjg0Mkw0Mi4zMzQgODAuNzk5NUw0NC43MjM3IDc4LjQwOThMNDYuMTU3NiA3Ni45NzZMNDYuOTcxMyA3NS44Nzc5TDUwLjA3OCA3Mi43NzEzTDQ4LjEwOTMgNzQuNjI2Mkw0OC4zODI1IDc0LjE4MjRaTTI5LjI4NzcgNjIuOTkwNkwyOS4wNzcyIDYzLjIwMTFMMjguODY2NyA2My4yNzEzTDI5LjI4NzcgNjIuOTkwNlpNMjkuNzA4OCA1OS40ODIzTDI5LjkxOTMgNTkuMjcxOUwyOS40OTgzIDU5LjU1MjVMMjkuNzA4OCA1OS40ODIzWk0yOS4wNzcyIDY2LjU2ODdMMjguODY2NyA2Ni43NzkyTDI4LjY1NjIgNjYuODQ5NEwyOS4wNzcyIDY2LjU2ODdaTTIyLjk3MjkgNjguNzQ4TDIzLjE4MzQgNjguNTM3NUwyMi43NjI0IDY4LjgxODFMMjIuOTcyOSA2OC43NDhaTTMuODE0N2UtMDUgOTEuNzU5M0wxMy4yNDk5IDc5LjEzNTVMNi41MDAxIDg2LjI1OTVMMy44MTQ3ZS0wNSA5MS43NTkzWk0xNi4wNjg1IDg3Ljk5NzRMMTcuMTM3NSA4Ny4wNjg3TDE2LjUzODIgODcuNjY4TDE2LjA2ODUgODcuOTk3NFpNMjEuNzg2OSA3OS4zMzQ0TDIwLjcxNzkgODAuMjYzTDIxLjE4NzYgNzkuOTMzN0wyMS43ODY5IDc5LjMzNDRaTTEyLjM2MDcgOTUuMDc1NUwxMy40Mjk4IDk0LjE0NjlMMTIuODMwNCA5NC43NDYyTDEyLjM2MDcgOTUuMDc1NVpNNDIuNzE3NiA1OS4zODAxTDQzLjI3ODkgNTguODE4N0w0My4wNjg0IDU5LjE2OTZMNDIuNzg3NyA1OS40NTAyTDQyLjI5NjYgNTkuODAxTDQyLjU3NzIgNTkuMzgwMUg0Mi43MTc2Wk0yNi4zMTI0IDQ5LjMxNTJMMjQuMzU5OSA1MS4yNjc2TDIzLjk5NiA1MS4zOTE4TDIyLjg5NTYgNTIuNzMyTDI0LjQ3OTggNTEuMzg3NUwyNS40NTYgNTAuNDExM0wyNi4zMTI0IDQ5LjMxNTJaTTM5LjA2ODkgNjMuMzA5N0wzOC41Nzc3IDYzLjY2MDZMMzkuNTYgNjIuNjc4MkwzOS4wNjg5IDYzLjMwOTdaTTIwLjM1NzQgNTUuODAzMkwxOS4zNzUxIDU2Ljc4NTZMMTkuODY2MiA1Ni40MzQ3TDIwLjM1NzQgNTUuODAzMlpNMzkuOTI5NyA2NC4xOTVMNDEuNTUwNCA2Mi4zNzc5TDQxLjUzNCA2Mi41OTA3TDQzLjU5NjcgNjAuNTI4TDQyLjk3NDYgNjEuMjgxMUw0MC44NjI4IDYzLjUyMzhMNDAuOTYxIDYzLjE2MzdMMzkuOTI5NyA2NC4xOTVaTTIyLjM5MjEgNTUuNDU3TDIxLjM5OTggNTYuNTY5NkwyMi4wMzEzIDU1LjkzODFMMjEuOTcxMSA1Ni4xNTg3TDIzLjI2NDIgNTQuNzg1NEwyMy42NDUxIDU0LjMyNDNMMjIuMzgyMSA1NS41ODczTDIyLjM5MjEgNTUuNDU3Wk00MC42NDczIDkyLjQ0OThMNDUuMDQ4NSA4OC4wNDg1TDQzLjAwNjYgOTAuNDA3OUw0MC44MDYgOTIuNjA4NUwzNy4zNDYzIDk1Ljc1MDdMMzkuOTM4NCA5Mi44NDEyTDQwLjY0NzMgOTIuNDQ5OFpNMTguNTA0MiA0OC43OTczTDExLjU0NTcgNTUuNzU1OEwxMC40MjQ5IDU2LjM3NDZMNi4zMjY4NCA2MC45NzQ2TDExLjc5NjcgNTYuMDA2N0wxNS4yNzU5IDUyLjUyNzVMMTguNTA0MiA0OC43OTczWk0zMi43MTEzIDc4LjEzOUwzMS4xMTMxIDc5LjczNzJMMzAuODQzMiA3OS44NjY4TDI5LjkxNDUgODAuOTM1OEwzMS4xODMzIDc5LjgwNzRMMzEuOTgyMyA3OS4wMDgzTDMyLjcxMTMgNzguMTM5Wk0yMS43NTc3IDkzLjk1MjVMMzEuMjg1NSA4NC4wMzQ0TDMwLjgzMjQgODQuODc3N0w0Mi40OTk5IDczLjIxMDJMMzguNzQwOCA3Ny4yMjk1TDI2LjU1NTIgODkuNjc1M0wyNy41OTE0IDg4LjExODdMMjEuNzU3NyA5My45NTI1Wk05OC41MTMyIDkwLjA1OTFMODkuOTIyNCA5Ny45MjI0TDkzLjU3NjkgOTQuOTk1M0w5OC41MTMyIDkwLjA1OTFaTTk3Ljg0NTYgODAuMjEwNUw5OS41MDI3IDc4LjY5MzdMOTguNTUwNiA3OS42NDU5TDk3Ljg0NTYgODAuMjEwNVpNODguNTY1NiA1Ni40NTk5TDc4LjkyMDUgNjUuNzAwOUw4Mi4xMjYyIDYzLjMwMzZMNzguMTQxMyA2Ny4yODg1TDczLjc1MjIgNzAuODY5Mkw3NC43MTk1IDcwLjUwODJMNjcuNzE3IDc4LjExN0w2My45OTIgODEuMDMzNkw1OC4wMTQ2IDg3LjAxMUw2My40Mjg5IDgxLjc5ODhMNjYuMzg4NyA3OS40NDU0TDY4LjEyMTIgNzguNTIxM0w3MC41NzU3IDc1LjY2MjVMNzMuMDMwMiA3Mi44MDM4TDc2LjE5NCA2OS42NEw3OC4zNDM0IDY3LjQ5MDZMODQuMzIwOCA2MS41MTMyTDgyLjY1NzUgNjIuNzcyM0w4OC41NjU2IDU2LjQ1OTlaTTg1LjE4OTMgNjcuMDM3NUw4My43MzA0IDY4LjM1Nkw4NC4zNTYxIDY3Ljg3MDdMODUuMTg5MyA2Ny4wMzc1Wk05MC43OTY5IDU4LjIwMjJMOTkuMjcyNSA1MC41NDE4TDk0LjQzMTcgNTUuMzgyNkw5MC43OTY5IDU4LjIwMjJaTTc5LjM3NyA3Ni4yMTcyTDc3LjkxODIgNzcuNTM1N0w3OC41NDM4IDc3LjA1MDRMNzkuMzc3IDc2LjIxNzJaTTU5LjQ5MjIgOTEuNzI1M0w1Ni40MDExIDk0LjEyMzFMNjAuMDA0OSA5MC44NjU5TDYzLjYwODcgODcuNjA4N0w1OS40OTIyIDkxLjcyNTNaTTYzLjg4MzMgNzUuNDE1M0w0NiA5Mi4zODk2TDQ5LjY4ODQgODkuMTE5M0w1My4zNzY3IDg1Ljg0OTFMNjMuODgzMyA3NS40MTUzWk03MS42MDYzIDU1LjA3NjVMNjkuNjYwOSA1Ny4wMjE5TDY5LjM0NzUgNTcuMTk0OUw2OC4yMDE4IDU4LjQ4MUw2OS43MzEgNTcuMDkyMUw3MC43MDM3IDU2LjExOTRMNzEuNjA2MyA1NS4wNzY1Wk01NS4xNDA1IDcxLjY4NTdMNjEuNDEzMSA2NS40MTMxTDU3Ljk1OCA2OS4xMjY3TDU1LjE0MDUgNzEuNjg1N1pNNjUuODM5NiA2OS40NDk3TDYxLjcxMzggNzMuNzEzOEw2NC4yMzA4IDcxLjE5NjhMNjMuNzYzNyA3MS44NDg0TDY5LjAzMTMgNjYuNDg4Nkw3MC42NjMyIDY0Ljc2NDVMNjUuNjI5MiA2OS43OTg1TDY1LjgzOTYgNjkuNDQ5N1pNNTMuMDAzNCA2NS40OTU1TDU4LjIyNTggNTkuODkxNEw1OC4wNTU4IDYwLjQ0MzFMNjQuNTUxNyA1My45NDcyTDYyLjUxMzYgNTYuMjM5OEw1NS43ODQxIDYzLjIyMzhMNTYuMjUxMyA2Mi4yNDc1TDUzLjAwMzQgNjUuNDk1NVpNOTcuMDk5NyA3MS4yMDMyTDc5LjY1MTQgODguNjUxNUw4Ni43Njk3IDgwLjgxNEw5Ny4wOTk3IDcxLjIwMzJaTTM1LjE4NDggNTYuMjUxM0wzMS45MyA1OS45MDA2TDM0LjAwMTIgNTcuODI5NEwzMy44MDQgNTguNTUyN0wzOC4wNDUxIDU0LjA0ODVMMzkuMjk0NSA1Mi41MzYxTDM1LjE1MTkgNTYuNjc4N0wzNS4xODQ4IDU2LjI1MTNaTTY2Ljg3MTIgMjYuMjQ3MUw3OC4xOTA3IDE0LjMwOTlMNzcuNzI0NCAxNS4zOTRMOTEuNjc4NCAxLjQzOTlMODcuMjMzIDYuMjk3MTVMNzIuNzA5NiAyMS4yMzIzTDczLjg0ODIgMTkuMjcwMUw2Ni44NzEyIDI2LjI0NzFaTTI4LjA0NzMgNjguMjA2OEwyMC40MzU1IDc2LjM3NUwyNS4xNjk1IDcxLjY0MUwyNC40ODg0IDczLjA2MzlMMzQuMjk3IDYyLjg4NDRMMzcuMjY3NSA1OS41NDI5TDI3Ljc5OTUgNjkuMDEwOUwyOC4wNDczIDY4LjIwNjhaTTguOTQwNjcgMzkuNTY1OEwxNC4xNjMxIDMzLjk2MTdMMTMuOTkzIDM0LjUxMzRMMjAuNDg4OSAyOC4wMTc1TDE4LjQ1MDkgMzAuMzEwMUwxMS43MjEzIDM3LjI5NDFMMTIuMTg4NiAzNi4zMTc4TDguOTQwNjcgMzkuNTY1OFpNOTkuNzQwMyAyNkw4OCAzNy43NDA0TDkzLjI3MzUgMzIuOTUwOEw5OS43NDAzIDI2Wk0xLjkzMzg4IDguMDg3NDNMNC43Nzc2NSA1LjA0OTc0TDQuNjc4NTYgNS4zNDI3NUw4LjIwNzQzIDEuODEzODhMNy4wOTU3OCAzLjA1NDgxTDMuNDM1NSA2Ljg0NDM3TDMuNjk4MzIgNi4zMjI5OUwxLjkzMzg4IDguMDg3NDNaTTU0LjQ0ODUgNDQuMjExTDQ4LjU5ODUgNTAuMDYxTDQ3LjY1NjMgNTAuNTgxM0w0NC4yMTEgNTQuNDQ4NUw0OC44MDk1IDUwLjI3Mkw1MS43MzQ1IDQ3LjM0N0w1NC40NDg1IDQ0LjIxMVoiIC8+CjwvcGF0dGVybj4KPHBhdHRlcm4gaWQ9InN0cmVha3Mtbm9ybWFsLWQyLTM4NTc1NTE5OCIgeD0iMCIgeT0iMCIgd2lkdGg9IjEwMCIgaGVpZ2h0PSIxMDAiIHBhdHRlcm5Vbml0cz0idXNlclNwYWNlT25Vc2UiPgogICAgPHBhdGggZmlsbD0icmdiYSgwLCAwLCAwLCAwLjE2KSIgZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik01OC4xMTkzIDBINTguMTcwM0w1NS40OTM5IDIuNjc2NDRMNTguMTE5MyAwWk00NS43NzI1IDBINDUuODExTDQxLjI4NTEgNC42MTQ5OEw0Mi43MTkxIDMuMjkzMjVMMzcuMDgyNCA4LjkyOTk3TDM1LjA1NTQgMTAuOTU2OUwzMi4wNzE5IDEzLjk0MDRMMjkuNjIyOSAxNi41MDE3TDI3LjE3MzggMTkuMDYzMUwyNS44MDg5IDIwLjIwMzRMMjMuMjE5NSAyMi42MjQ0TDE4LjE4MSAyNy42MDY4TDIzLjgxNzggMjEuOTdMMjcuMDYxNSAxOC45NTA4TDMzLjg2NjYgMTEuOTc3M0wzMy4xNTYyIDEyLjUxOTRMMzcuMDI2MiA4Ljg3MzgzTDQwLjc4NCA1LjExNjAyTDM4LjAyOTkgNy42NDU2MUw0NS43NzI1IDBaTTIzLjEwNzkgMEgyMy4xMDhMMjEuNTgxNCAxLjY2Njg4TDIwLjMxMjYgMi43OTUzNEwyMy4xMDc5IDBaTTcuNTM4NjkgMEg3LjU0MjU0TDcuNTAwMDUgMC4wMzU5NDRMNy41Mzg2OSAwWk0yLjQ5OTk1IDBIMi41MjM2MkwwLjkwMDI0NSAxLjU5OTcxTDIuNDk5OTUgMFpNMCAzLjY0Mzk4VjMuNjA3NDRMMC4yNzgzODYgMy4zNjU1OUwwIDMuNjQzOThaTTAgMTguNjU2NFYxOC41Mzk4TDAuNjc5ODUgMTcuODQxNkwzLjQ0NTkgMTUuMDc1NUwxLjE1NzAxIDE3LjEzMzNMMi43ODcxMyAxNS42MDIyTDYuMDE0MzcgMTIuNTA3TDguNTE2OCA5Ljg3MjUzTDUuMTU4MDMgMTMuMjMxM0wxMS4wMzU3IDcuMjU0NTNMMTAuNDkyNiA3Ljg5Njc4TDEzLjY4NjggNC43Njg2TDguNTQ5ODIgOS45MDU1NUw3LjA1MTc3IDExLjU2ODdMNC42ODA4NyAxMy45Mzk2TDAuNzI5Mzc5IDE3Ljg5MTFMMy4wMTgyNyAxNS44MzMzTDAgMTguNjU2NFpNMCA2OS4yNDMxVjY5LjE3OEwxLjY0NjUxIDY3LjQ3NjNMMS40NjM0NyA2Ny43Nzk2TDUuODQwNjMgNjMuNDAyNUw0LjQyMTY3IDY0LjkwMTZMMCA2OS40MDA3VjY5LjM0MDhMMC4yNDc1OTYgNjguOTk1NUwwIDY5LjI0MzFaTTIuNTE1OTQgMTAwSDIuNDkyMzhMNS4xOTk4OSA5Ny4yOTI1TDcuNzAwNzEgOTUuMDE2MkwxMi44NzEzIDg5LjY3NzJMMTIuMzA5NCA5MC4wNzA3TDE1LjI4OCA4Ny4zMTY3TDE4LjE1NDIgODQuNDUwNEwxNi4wMjY5IDg2LjM1MzJMMjIuODc1MiA3OS42MTcyTDE4LjUzNjQgODQuMDY4M0wxOS42NDM1IDgzLjA3MzRMMTUuMzQ0MSA4Ny4zNzI4TDEzLjc5OCA4OC45MTg5TDExLjUyMjQgOTEuMTk0NUw5LjY2NzY4IDkzLjE2MTVMNy44MTI5NyA5NS4xMjg1TDYuNzQ1MjkgOTUuOTcxNkw0Ljc1MDI0IDk3Ljc5ODNMMi41MTU5NCAxMDBaTTcuNTQyNTUgMTAwSDcuNTM4N0w5LjgxMzk2IDk3Ljg4NEw4LjQ2NjA2IDk5LjIxODlMNy41NDI1NSAxMDBaTTQ1LjgxODkgMTAwSDQ1Ljc4MDdMNDYuOTkxMiA5OC44MDQ3TDQ1LjgxODkgMTAwWk01OC4xNzg0IDEwMEg1OC4xMjcyTDYyLjI5NTIgOTUuNzUxMUw2Ni4xNDA4IDkxLjkwNTVMNjMuMDAzNyA5NC44MTE1TDY1LjI1MDcgOTIuNjYzNUw2OS43MTE3IDg4LjMzNDZMNzMuMjE2NSA4NC42OTc3TDY4LjU0NjkgODkuMzY3M0w3Ni43Mzc5IDgxLjA3NzNMNzUuOTYzNCA4MS45NTA5TDgwLjM5MTMgNzcuNTg4OUw3My4yNDk2IDg0LjczMDdMNzEuMTM0NiA4Ny4wMTA3TDY3LjgzODQgOTAuMzA2OUw2Mi4zNDQ3IDk1LjgwMDZMNjUuNDgxOCA5Mi44OTQ3TDYxLjI2MjUgOTYuOTE1OUw1OC4xNzg0IDEwMFpNNzUuNDI3NyAxMDBINzUuMjI5TDgyLjE4MzQgOTIuOTAzOUw4MS4zNDAzIDkzLjU3ODdMODYuMDA2MyA4OS4xMzcxTDkwLjU2MDEgODQuNTgzM0w4Ny4yNDY0IDg3LjY3MjVMOTguMDkzNyA3Ni45Mzc1TDkxLjE2NzMgODMuOTc2MUw5Mi44OTMyIDgyLjM2MjVMODYuMDYyNSA4OS4xOTMzTDgzLjYwNjIgOTEuNjQ5Nkw3OS45OTA3IDk1LjI2NUw3Ny4wMTEgOTguMzU3TDc1LjQyNzcgMTAwWk0xMDAgMTguNTM5OFYxOC42NTYzTDk5Ljk1NTYgMTguNjk3OUw5NS44MDY1IDIyLjg0N0wxMDAgMTguNTM5OFpNMTAwIDMuNjA3NDNWMy42NDM5OEw5OS42NzkxIDMuOTY0OUw5OS4yMDk0IDQuMjk0MjhMMTAwIDMuNjA3NDNaTTc1LjQyMDEgMEw3NC4wMzEyIDEuNDQxMkw3Mi40MDEgMi44NDY4N0w2OS4yODEgNS43OTg1NEw2My4xODEyIDExLjg0MjJMNzAuMDExOSA1LjAxMTUxTDczLjkxOSAxLjMyODkzTDc1LjIyMTQgMEg3NS40MjAxWk0xMDAgNjkuMTg1OFY2OS4yNTA5TDk4LjA1OSA3MS4xOTE5TDEwMCA2OS4xODU4Wk0xMDAgNjkuMzQ4NlY2OS40MDg1TDk5Ljg0MTQgNjkuNTY5OEwxMDAgNjkuMzQ4NlpNNDEuOTM5OCAyOC44MjU0TDUzLjYyMjMgMTYuOTkzTDUyLjUyMTUgMTguMjQzN0w1NC43NDI4IDE2LjA1NzVMNTQuNjg3NSAxNi4wNzU5TDU0LjgwMDggMTYuMDAwNEw1OC44NDIgMTIuMDIzMUw1NC45OTI1IDE1Ljg3MjZMNTUuMTA4NSAxNS43OTUzTDU0Ljg5OCAxNi4wMDU4TDU0Ljg0IDE2LjAyNTFMNDguNjUyMyAyMi4yMTI4TDQ1LjY0MTkgMjUuNDczTDQwLjkzODkgMzAuMTc1OUwzMy4xMDA3IDM4LjAxNDJMMzcuNTg2NiAzMy44NzhMMzEuNTU4IDM5LjYwNjhMMjMuMzI3OCA0Ny44MzdMMzMuMDI1NyAzNy45MzkzTDM4LjUxMjUgMzIuNDUyNUwzNC4wMjY2IDM2LjU4ODdMMzcuMjM2OSAzMy41MjgzTDQzLjYwNzQgMjcuMzU3Nkw0OC42MDIzIDIyLjE2MjhMNDEuOTM5OCAyOC44MjU0Wk00MS4wOTc3IDE3LjA1MzFMMzkuNzE4IDE4LjI5MjVMNDAuMzEyIDE3LjgzODhMNDEuMDk3NyAxNy4wNTMxWk0zNi44NzUgMjAuMzEwNkw0OC4xNjAxIDcuODgxMzdMNDIuMzQzOCAxMy43NDc4TDM2Ljg3NSAyMC4zMTA2Wk0zNS43MTI1IDI1LjgxMDlMMzQuMzMyOCAyNy4wNTAzTDM0LjkyNjggMjYuNTk2NkwzNS43MTI1IDI1LjgxMDlaTTE3LjcwMjIgMzkuNzUzNEwxOS4wODE5IDM4LjUxNEwxOC44MDkyIDM4Ljc4NjdMMzYuNzU3NSAyMS44MDQ1TDIzLjE1NjkgMzUuMzA1MUwxMy41NzcxIDQzLjczNzJMMTguMTQ0OCAzOS40MTU0TDE3LjcwMjIgMzkuNzUzNFpNMy40ODEwMiAyOC45MjgxTDEuNTM1NjIgMzAuODczNUwxLjIyMjI4IDMxLjA0NjVMMC4wNzY1Njg2IDMyLjMzMjZMMS42MDU3OSAzMC45NDM3TDIuNTc4NDkgMjkuOTcxTDMuNDgxMDIgMjguOTI4MVpNMC45NTM0NjMgMjYuMjAyN0wxOS41NzAyIDcuNTg1OTRMOS4zMTU3NSAxOC42MDc4TDAuOTUzNDYzIDI2LjIwMjdaTTIzLjcxNzUgMTIuMTFMMTcuOTMzOSAxOC4wODc1TDIxLjQ2MjIgMTQuNTU5MkwyMC44MDc0IDE1LjQ3MjVMMjguMTkxNSA3Ljk1OTE4TDMwLjQ3OTEgNS41NDIzMkwyMy40MjI0IDEyLjU5OUwyMy43MTc1IDEyLjExWk00My40NjQxIDQzLjE1MzhMNDAuNzg3MiA0Ni4xNTUyTDQyLjQ5MDcgNDQuNDUxN0w0Mi4zMjg1IDQ1LjA0NjVMNDUuODE2NiA0MS4zNDIxTDQ2Ljg0NDEgNDAuMDk4M0w0My40MzcxIDQzLjUwNTNMNDMuNDY0MSA0My4xNTM4Wk0xLjMyNzE1IDQ4LjMyNzFMOC4wOTE4IDQxLjU2MjVMNC4zNjU3IDQ1LjU2NzRMMS4zMjcxNSA0OC4zMjcxWk0xMS4xNDc5IDMxLjI1NTZMMTEuNTY4OSAzMC45NzVMMTEuMzU4NCAzMS4xODU1TDExLjE0NzkgMzEuMjU1NlpNMTEuOTg5OCAyNy40NjY3TDEyLjIwMDMgMjcuMjU2MkwxMS43NzkzIDI3LjUzNjlMMTEuOTg5OCAyNy40NjY3Wk0xMS4zNTg1IDM0LjU1MzFMMTEuMTQ4IDM0Ljc2MzZMMTAuOTM3NSAzNC44MzM4TDExLjM1ODUgMzQuNTUzMVpNNzIuOTI5IDI4LjU0NTdMODIuMjk2NSAxOS4wNzkyTDgxLjQwNDMgMjAuMDcwNUw4Ni40NTk3IDE1LjA4MTFMNzguMjk4MyAyMy4yNDI1TDc1Ljg2OTcgMjUuODM2Mkw3Mi4xMDI5IDI5LjYwM0w2NS44MjQ5IDM1Ljg4MUw2OS4zOTM0IDMyLjU0MzdMNjQuNTg1OCAzNy4xNTMxTDU3Ljk5NCA0My43NDVMNjUuNzc1NCAzNS44MzE0TDcwLjE3IDMxLjQzNjlMNjYuNjAxNSAzNC43NzQyTDY5LjE2MjMgMzIuMzEyNUw3NC4yNTA3IDI3LjM1NjJMNzguMjY1MyAyMy4yMDk1TDcyLjkyOSAyOC41NDU3Wk04Mi42Njc0IDEuODM1NDlMODQuMzI0NSAwLjMxODcyTDgzLjM3MjQgMS4yNzA4OEw4Mi42Njc0IDEuODM1NDlaTTY0LjU4NzIgMTYuMTMxMkw2Mi45MzAxIDE3LjY0OEw2My42MzUxIDE3LjA4MzRMNjQuNTg3MiAxNi4xMzEyWk03MC44NjggOS44NTA0NEw4MC4wMDQ4IDEuMTIxNEw3NC42MjIxIDYuNDcxNDJMNzAuODY4IDkuODUwNDRaTTkwLjI0MDkgNDEuOTQ0OEw3MC43NTc4IDYxLjQyNzlMNzkuNTA5MyA1My40Nzk1TDkwLjI0MDkgNDEuOTQ0OFpNOTEuODA4OCA0Mi41NDM0TDk1LjM5NjMgMzguODM1N0w5NS4yMTMyIDM5LjEzOUw5OS41OTA0IDM0Ljc2MThMOTguMTcxNCAzNi4yNjFMOTMuNTkxMiA0MC45MjE0TDkzLjk5NzMgNDAuMzU0OUw5MS44MDg4IDQyLjU0MzRaTTk0LjMzMSAxMi44MjMzTDg5Ljk4NTMgMTcuMTY5MUw4OS4yODUzIDE3LjU1NTVMODYuNzI1OSAyMC40Mjg0TDkwLjE0MiAxNy4zMjU4TDkyLjMxNDkgMTUuMTUyOUw5NC4zMzEgMTIuODIzM1pNNDQuNzk3MiA2Mi4zMjU5TDc2Ljk4MjQgMzAuMTQwNkw1OS4yNTQyIDQ5LjE5NTVMNDQuNzk3MiA2Mi4zMjU5Wk03Ny4xNDgyIDQwLjMyMUw3MC4xNzA5IDQ3LjUzMjNMNzAgNDcuNjQ2M0w3MC4wODk1IDQ3LjYxNjRMNjguMTkxNiA0OS41Nzc5TDcwLjE4NSA0Ny41ODQ2TDcwLjIxMDUgNDcuNTc2MUw3MC40MjEgNDcuMzY1Nkw3MC4zNyA0Ny4zOTk2TDczLjY1NTcgNDQuMTEzOUw3Mi42NDE2IDQ1LjUyODNMODQuMDc2OCAzMy44OTNMODcuNjE5NCAzMC4xNTAyTDc2LjY5MTMgNDEuMDc4M0w3Ny4xNDgyIDQwLjMyMVpNNTAuNTM1NSAzNC4zMTM3TDcyLjY2MTcgMTIuMTg3NUw2MC40OTU1IDI1LjMwODRMNTAuNTM1NSAzNC4zMTM3Wk03MC4yMTA0IDQ0LjA2ODFMNzAuNjMxNCA0My43ODc1TDcwLjQyMDkgNDMuOTk4TDcwLjIxMDQgNDQuMDY4MVpNNzEuMjYzIDQwLjA2ODdMNzAuODQyIDQwLjM0OTRMNzEuMDUyNSA0MC4yNzkyTDcxLjI2MyA0MC4wNjg3Wk01NS4xMDg0IDEyLjQzNTVMNTUuMzE4OSAxMi4yMjVMNTQuODk3OSAxMi41MDU2TDU1LjEwODQgMTIuNDM1NVpNNDguODcxOCAxNS41Nzg1TDYwLjIwNzUgNC43MDQ5Nkw0OS40MDU2IDE1LjQwMDZMNDguODcxOCAxNS41Nzg1Wk0yMy43NjM2IDU3LjQ0OTFMMjkuOTA5OSA1MS41ODU0TDI2LjE2NTYgNTUuNjEyM0wyNy4yMzYxIDU0LjgyNDRMMjMuNDM1IDU4LjYyNTVMMjIuMDY4MSA1OS45OTI0TDIwLjA1NjIgNjIuMDA0MkwxOC41MDgyIDYzLjgzNDlMMTYuOTYwMSA2NS42NjU2TDE1LjgzMjggNjYuMjI3N0wxMy45MzE1IDY3LjcwNTFMMTAuNDgyMSA3MS4wMTMyTDE0LjI4MzIgNjcuMjEyMUwxNi42Nzc1IDY1LjM4M0wyMS4xMTEzIDYwLjUyNTNMMjAuNDc3IDYwLjczNTdMMjMuMjkzNyA1OC40ODQyTDI1LjgyNzcgNTUuOTUwMkwyMy43NjM2IDU3LjQ0OTFaTTQ4LjM4MjUgNzQuMTgyNEw0NC44ODMyIDc3Ljg1MjNMNDYuOTE0NSA3NS44MjExTDQ1LjQ3NDggNzcuNDg4MUw0My40NDkzIDc5LjI4NjJMNDIuNDA4MiA4MC4xNTY4TDQzLjkyMTUgNzkuMDQxNEw0Mi4yNDg3IDgwLjcxNDNMMzkuMzc1MiA4My44MTUxTDQxLjg4NDQgODEuMzA1OUw0My44NDczIDc5LjY4NDJMNDIuMzM0IDgwLjc5OTVMNDQuNzIzNyA3OC40MDk4TDQ2LjE1NzYgNzYuOTc2TDQ2Ljk3MTMgNzUuODc3OUw1MC4wNzggNzIuNzcxM0w0OC4xMDkzIDc0LjYyNjJMNDguMzgyNSA3NC4xODI0Wk0yOS4yODc3IDYyLjk5MDZMMjkuMDc3MiA2My4yMDExTDI4Ljg2NjcgNjMuMjcxM0wyOS4yODc3IDYyLjk5MDZaTTI5LjcwODggNTkuNDgyM0wyOS45MTkzIDU5LjI3MTlMMjkuNDk4MyA1OS41NTI1TDI5LjcwODggNTkuNDgyM1pNMjkuMDc3MiA2Ni41Njg3TDI4Ljg2NjcgNjYuNzc5MkwyOC42NTYyIDY2Ljg0OTRMMjkuMDc3MiA2Ni41Njg3Wk0yMi45NzI5IDY4Ljc0OEwyMy4xODM0IDY4LjUzNzVMMjIuNzYyNCA2OC44MTgxTDIyLjk3MjkgNjguNzQ4Wk0zLjgxNDdlLTA1IDkxLjc1OTNMMTMuMjQ5OSA3OS4xMzU1TDYuNTAwMSA4Ni4yNTk1TDMuODE0N2UtMDUgOTEuNzU5M1pNMTYuMDY4NSA4Ny45OTc0TDE3LjEzNzUgODcuMDY4N0wxNi41MzgyIDg3LjY2OEwxNi4wNjg1IDg3Ljk5NzRaTTIxLjc4NjkgNzkuMzM0NEwyMC43MTc5IDgwLjI2M0wyMS4xODc2IDc5LjkzMzdMMjEuNzg2OSA3OS4zMzQ0Wk0xMi4zNjA3IDk1LjA3NTVMMTMuNDI5OCA5NC4xNDY5TDEyLjgzMDQgOTQuNzQ2MkwxMi4zNjA3IDk1LjA3NTVaTTQyLjcxNzYgNTkuMzgwMUw0My4yNzg5IDU4LjgxODdMNDMuMDY4NCA1OS4xNjk2TDQyLjc4NzcgNTkuNDUwMkw0Mi4yOTY2IDU5LjgwMUw0Mi41NzcyIDU5LjM4MDFINDIuNzE3NlpNMjYuMzEyNCA0OS4zMTUyTDI0LjM1OTkgNTEuMjY3NkwyMy45OTYgNTEuMzkxOEwyMi44OTU2IDUyLjczMkwyNC40Nzk4IDUxLjM4NzVMMjUuNDU2IDUwLjQxMTNMMjYuMzEyNCA0OS4zMTUyWk0zOS4wNjg5IDYzLjMwOTdMMzguNTc3NyA2My42NjA2TDM5LjU2IDYyLjY3ODJMMzkuMDY4OSA2My4zMDk3Wk0yMC4zNTc0IDU1LjgwMzJMMTkuMzc1MSA1Ni43ODU2TDE5Ljg2NjIgNTYuNDM0N0wyMC4zNTc0IDU1LjgwMzJaTTM5LjkyOTcgNjQuMTk1TDQxLjU1MDQgNjIuMzc3OUw0MS41MzQgNjIuNTkwN0w0My41OTY3IDYwLjUyOEw0Mi45NzQ2IDYxLjI4MTFMNDAuODYyOCA2My41MjM4TDQwLjk2MSA2My4xNjM3TDM5LjkyOTcgNjQuMTk1Wk0yMi4zOTIxIDU1LjQ1N0wyMS4zOTk4IDU2LjU2OTZMMjIuMDMxMyA1NS45MzgxTDIxLjk3MTEgNTYuMTU4N0wyMy4yNjQyIDU0Ljc4NTRMMjMuNjQ1MSA1NC4zMjQzTDIyLjM4MjEgNTUuNTg3M0wyMi4zOTIxIDU1LjQ1N1pNNDAuNjQ3MyA5Mi40NDk4TDQ1LjA0ODUgODguMDQ4NUw0My4wMDY2IDkwLjQwNzlMNDAuODA2IDkyLjYwODVMMzcuMzQ2MyA5NS43NTA3TDM5LjkzODQgOTIuODQxMkw0MC42NDczIDkyLjQ0OThaTTE4LjUwNDIgNDguNzk3M0wxMS41NDU3IDU1Ljc1NThMMTAuNDI0OSA1Ni4zNzQ2TDYuMzI2ODQgNjAuOTc0NkwxMS43OTY3IDU2LjAwNjdMMTUuMjc1OSA1Mi41Mjc1TDE4LjUwNDIgNDguNzk3M1pNMzIuNzExMyA3OC4xMzlMMzEuMTEzMSA3OS43MzcyTDMwLjg0MzIgNzkuODY2OEwyOS45MTQ1IDgwLjkzNThMMzEuMTgzMyA3OS44MDc0TDMxLjk4MjMgNzkuMDA4M0wzMi43MTEzIDc4LjEzOVpNMjEuNzU3NyA5My45NTI1TDMxLjI4NTUgODQuMDM0NEwzMC44MzI0IDg0Ljg3NzdMNDIuNDk5OSA3My4yMTAyTDM4Ljc0MDggNzcuMjI5NUwyNi41NTUyIDg5LjY3NTNMMjcuNTkxNCA4OC4xMTg3TDIxLjc1NzcgOTMuOTUyNVpNOTguNTEzMiA5MC4wNTkxTDg5LjkyMjQgOTcuOTIyNEw5My41NzY5IDk0Ljk5NTNMOTguNTEzMiA5MC4wNTkxWk05Ny44NDU2IDgwLjIxMDVMOTkuNTAyNyA3OC42OTM3TDk4LjU1MDYgNzkuNjQ1OUw5Ny44NDU2IDgwLjIxMDVaTTg4LjU2NTYgNTYuNDU5OUw3OC45MjA1IDY1LjcwMDlMODIuMTI2MiA2My4zMDM2TDc4LjE0MTMgNjcuMjg4NUw3My43NTIyIDcwLjg2OTJMNzQuNzE5NSA3MC41MDgyTDY3LjcxNyA3OC4xMTdMNjMuOTkyIDgxLjAzMzZMNTguMDE0NiA4Ny4wMTFMNjMuNDI4OSA4MS43OTg4TDY2LjM4ODcgNzkuNDQ1NEw2OC4xMjEyIDc4LjUyMTNMNzAuNTc1NyA3NS42NjI1TDczLjAzMDIgNzIuODAzOEw3Ni4xOTQgNjkuNjRMNzguMzQzNCA2Ny40OTA2TDg0LjMyMDggNjEuNTEzMkw4Mi42NTc1IDYyLjc3MjNMODguNTY1NiA1Ni40NTk5Wk04NS4xODkzIDY3LjAzNzVMODMuNzMwNCA2OC4zNTZMODQuMzU2MSA2Ny44NzA3TDg1LjE4OTMgNjcuMDM3NVpNOTAuNzk2OSA1OC4yMDIyTDk5LjI3MjUgNTAuNTQxOEw5NC40MzE3IDU1LjM4MjZMOTAuNzk2OSA1OC4yMDIyWk03OS4zNzcgNzYuMjE3Mkw3Ny45MTgyIDc3LjUzNTdMNzguNTQzOCA3Ny4wNTA0TDc5LjM3NyA3Ni4yMTcyWk01OS40OTIyIDkxLjcyNTNMNTYuNDAxMSA5NC4xMjMxTDYwLjAwNDkgOTAuODY1OUw2My42MDg3IDg3LjYwODdMNTkuNDkyMiA5MS43MjUzWk02My44ODMzIDc1LjQxNTNMNDYgOTIuMzg5Nkw0OS42ODg0IDg5LjExOTNMNTMuMzc2NyA4NS44NDkxTDYzLjg4MzMgNzUuNDE1M1pNNzEuNjA2MyA1NS4wNzY1TDY5LjY2MDkgNTcuMDIxOUw2OS4zNDc1IDU3LjE5NDlMNjguMjAxOCA1OC40ODFMNjkuNzMxIDU3LjA5MjFMNzAuNzAzNyA1Ni4xMTk0TDcxLjYwNjMgNTUuMDc2NVpNNTUuMTQwNSA3MS42ODU3TDYxLjQxMzEgNjUuNDEzMUw1Ny45NTggNjkuMTI2N0w1NS4xNDA1IDcxLjY4NTdaTTY1LjgzOTYgNjkuNDQ5N0w2MS43MTM4IDczLjcxMzhMNjQuMjMwOCA3MS4xOTY4TDYzLjc2MzcgNzEuODQ4NEw2OS4wMzEzIDY2LjQ4ODZMNzAuNjYzMiA2NC43NjQ1TDY1LjYyOTIgNjkuNzk4NUw2NS44Mzk2IDY5LjQ0OTdaTTUzLjAwMzQgNjUuNDk1NUw1OC4yMjU4IDU5Ljg5MTRMNTguMDU1OCA2MC40NDMxTDY0LjU1MTcgNTMuOTQ3Mkw2Mi41MTM2IDU2LjIzOThMNTUuNzg0MSA2My4yMjM4TDU2LjI1MTMgNjIuMjQ3NUw1My4wMDM0IDY1LjQ5NTVaTTk3LjA5OTcgNzEuMjAzMkw3OS42NTE0IDg4LjY1MTVMODYuNzY5NyA4MC44MTRMOTcuMDk5NyA3MS4yMDMyWk0zNS4xODQ4IDU2LjI1MTNMMzEuOTMgNTkuOTAwNkwzNC4wMDEyIDU3LjgyOTRMMzMuODA0IDU4LjU1MjdMMzguMDQ1MSA1NC4wNDg1TDM5LjI5NDUgNTIuNTM2MUwzNS4xNTE5IDU2LjY3ODdMMzUuMTg0OCA1Ni4yNTEzWk02Ni44NzEyIDI2LjI0NzFMNzguMTkwNyAxNC4zMDk5TDc3LjcyNDQgMTUuMzk0TDkxLjY3ODQgMS40Mzk5TDg3LjIzMyA2LjI5NzE1TDcyLjcwOTYgMjEuMjMyM0w3My44NDgyIDE5LjI3MDFMNjYuODcxMiAyNi4yNDcxWk0yOC4wNDczIDY4LjIwNjhMMjAuNDM1NSA3Ni4zNzVMMjUuMTY5NSA3MS42NDFMMjQuNDg4NCA3My4wNjM5TDM0LjI5NyA2Mi44ODQ0TDM3LjI2NzUgNTkuNTQyOUwyNy43OTk1IDY5LjAxMDlMMjguMDQ3MyA2OC4yMDY4Wk04Ljk0MDY3IDM5LjU2NThMMTQuMTYzMSAzMy45NjE3TDEzLjk5MyAzNC41MTM0TDIwLjQ4ODkgMjguMDE3NUwxOC40NTA5IDMwLjMxMDFMMTEuNzIxMyAzNy4yOTQxTDEyLjE4ODYgMzYuMzE3OEw4Ljk0MDY3IDM5LjU2NThaTTk5Ljc0MDMgMjZMODggMzcuNzQwNEw5My4yNzM1IDMyLjk1MDhMOTkuNzQwMyAyNlpNMS45MzM4OCA4LjA4NzQzTDQuNzc3NjUgNS4wNDk3NEw0LjY3ODU2IDUuMzQyNzVMOC4yMDc0MyAxLjgxMzg4TDcuMDk1NzggMy4wNTQ4MUwzLjQzNTUgNi44NDQzN0wzLjY5ODMyIDYuMzIyOTlMMS45MzM4OCA4LjA4NzQzWk01NC40NDg1IDQ0LjIxMUw0OC41OTg1IDUwLjA2MUw0Ny42NTYzIDUwLjU4MTNMNDQuMjExIDU0LjQ0ODVMNDguODA5NSA1MC4yNzJMNTEuNzM0NSA0Ny4zNDdMNTQuNDQ4NSA0NC4yMTFaIiAvPgo8L3BhdHRlcm4+CjxwYXR0ZXJuIGlkPSJzdHJlYWtzLWRhcmstZDItMzg1NzU1MTk4IiB4PSIwIiB5PSIwIiB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgcGF0dGVyblVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CiAgICA8cGF0aCBmaWxsPSJyZ2JhKDAsIDAsIDAsIDAuMzIpIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTU4LjExOTMgMEg1OC4xNzAzTDU1LjQ5MzkgMi42NzY0NEw1OC4xMTkzIDBaTTQ1Ljc3MjUgMEg0NS44MTFMNDEuMjg1MSA0LjYxNDk4TDQyLjcxOTEgMy4yOTMyNUwzNy4wODI0IDguOTI5OTdMMzUuMDU1NCAxMC45NTY5TDMyLjA3MTkgMTMuOTQwNEwyOS42MjI5IDE2LjUwMTdMMjcuMTczOCAxOS4wNjMxTDI1LjgwODkgMjAuMjAzNEwyMy4yMTk1IDIyLjYyNDRMMTguMTgxIDI3LjYwNjhMMjMuODE3OCAyMS45N0wyNy4wNjE1IDE4Ljk1MDhMMzMuODY2NiAxMS45NzczTDMzLjE1NjIgMTIuNTE5NEwzNy4wMjYyIDguODczODNMNDAuNzg0IDUuMTE2MDJMMzguMDI5OSA3LjY0NTYxTDQ1Ljc3MjUgMFpNMjMuMTA3OSAwSDIzLjEwOEwyMS41ODE0IDEuNjY2ODhMMjAuMzEyNiAyLjc5NTM0TDIzLjEwNzkgMFpNNy41Mzg2OSAwSDcuNTQyNTRMNy41MDAwNSAwLjAzNTk0NEw3LjUzODY5IDBaTTIuNDk5OTUgMEgyLjUyMzYyTDAuOTAwMjQ1IDEuNTk5NzFMMi40OTk5NSAwWk0wIDMuNjQzOThWMy42MDc0NEwwLjI3ODM4NiAzLjM2NTU5TDAgMy42NDM5OFpNMCAxOC42NTY0VjE4LjUzOThMMC42Nzk4NSAxNy44NDE2TDMuNDQ1OSAxNS4wNzU1TDEuMTU3MDEgMTcuMTMzM0wyLjc4NzEzIDE1LjYwMjJMNi4wMTQzNyAxMi41MDdMOC41MTY4IDkuODcyNTNMNS4xNTgwMyAxMy4yMzEzTDExLjAzNTcgNy4yNTQ1M0wxMC40OTI2IDcuODk2NzhMMTMuNjg2OCA0Ljc2ODZMOC41NDk4MiA5LjkwNTU1TDcuMDUxNzcgMTEuNTY4N0w0LjY4MDg3IDEzLjkzOTZMMC43MjkzNzkgMTcuODkxMUwzLjAxODI3IDE1LjgzMzNMMCAxOC42NTY0Wk0wIDY5LjI0MzFWNjkuMTc4TDEuNjQ2NTEgNjcuNDc2M0wxLjQ2MzQ3IDY3Ljc3OTZMNS44NDA2MyA2My40MDI1TDQuNDIxNjcgNjQuOTAxNkwwIDY5LjQwMDdWNjkuMzQwOEwwLjI0NzU5NiA2OC45OTU1TDAgNjkuMjQzMVpNMi41MTU5NCAxMDBIMi40OTIzOEw1LjE5OTg5IDk3LjI5MjVMNy43MDA3MSA5NS4wMTYyTDEyLjg3MTMgODkuNjc3MkwxMi4zMDk0IDkwLjA3MDdMMTUuMjg4IDg3LjMxNjdMMTguMTU0MiA4NC40NTA0TDE2LjAyNjkgODYuMzUzMkwyMi44NzUyIDc5LjYxNzJMMTguNTM2NCA4NC4wNjgzTDE5LjY0MzUgODMuMDczNEwxNS4zNDQxIDg3LjM3MjhMMTMuNzk4IDg4LjkxODlMMTEuNTIyNCA5MS4xOTQ1TDkuNjY3NjggOTMuMTYxNUw3LjgxMjk3IDk1LjEyODVMNi43NDUyOSA5NS45NzE2TDQuNzUwMjQgOTcuNzk4M0wyLjUxNTk0IDEwMFpNNy41NDI1NSAxMDBINy41Mzg3TDkuODEzOTYgOTcuODg0TDguNDY2MDYgOTkuMjE4OUw3LjU0MjU1IDEwMFpNNDUuODE4OSAxMDBINDUuNzgwN0w0Ni45OTEyIDk4LjgwNDdMNDUuODE4OSAxMDBaTTU4LjE3ODQgMTAwSDU4LjEyNzJMNjIuMjk1MiA5NS43NTExTDY2LjE0MDggOTEuOTA1NUw2My4wMDM3IDk0LjgxMTVMNjUuMjUwNyA5Mi42NjM1TDY5LjcxMTcgODguMzM0Nkw3My4yMTY1IDg0LjY5NzdMNjguNTQ2OSA4OS4zNjczTDc2LjczNzkgODEuMDc3M0w3NS45NjM0IDgxLjk1MDlMODAuMzkxMyA3Ny41ODg5TDczLjI0OTYgODQuNzMwN0w3MS4xMzQ2IDg3LjAxMDdMNjcuODM4NCA5MC4zMDY5TDYyLjM0NDcgOTUuODAwNkw2NS40ODE4IDkyLjg5NDdMNjEuMjYyNSA5Ni45MTU5TDU4LjE3ODQgMTAwWk03NS40Mjc3IDEwMEg3NS4yMjlMODIuMTgzNCA5Mi45MDM5TDgxLjM0MDMgOTMuNTc4N0w4Ni4wMDYzIDg5LjEzNzFMOTAuNTYwMSA4NC41ODMzTDg3LjI0NjQgODcuNjcyNUw5OC4wOTM3IDc2LjkzNzVMOTEuMTY3MyA4My45NzYxTDkyLjg5MzIgODIuMzYyNUw4Ni4wNjI1IDg5LjE5MzNMODMuNjA2MiA5MS42NDk2TDc5Ljk5MDcgOTUuMjY1TDc3LjAxMSA5OC4zNTdMNzUuNDI3NyAxMDBaTTEwMCAxOC41Mzk4VjE4LjY1NjNMOTkuOTU1NiAxOC42OTc5TDk1LjgwNjUgMjIuODQ3TDEwMCAxOC41Mzk4Wk0xMDAgMy42MDc0M1YzLjY0Mzk4TDk5LjY3OTEgMy45NjQ5TDk5LjIwOTQgNC4yOTQyOEwxMDAgMy42MDc0M1pNNzUuNDIwMSAwTDc0LjAzMTIgMS40NDEyTDcyLjQwMSAyLjg0Njg3TDY5LjI4MSA1Ljc5ODU0TDYzLjE4MTIgMTEuODQyMkw3MC4wMTE5IDUuMDExNTFMNzMuOTE5IDEuMzI4OTNMNzUuMjIxNCAwSDc1LjQyMDFaTTEwMCA2OS4xODU4VjY5LjI1MDlMOTguMDU5IDcxLjE5MTlMMTAwIDY5LjE4NThaTTEwMCA2OS4zNDg2VjY5LjQwODVMOTkuODQxNCA2OS41Njk4TDEwMCA2OS4zNDg2Wk00MS45Mzk4IDI4LjgyNTRMNTMuNjIyMyAxNi45OTNMNTIuNTIxNSAxOC4yNDM3TDU0Ljc0MjggMTYuMDU3NUw1NC42ODc1IDE2LjA3NTlMNTQuODAwOCAxNi4wMDA0TDU4Ljg0MiAxMi4wMjMxTDU0Ljk5MjUgMTUuODcyNkw1NS4xMDg1IDE1Ljc5NTNMNTQuODk4IDE2LjAwNThMNTQuODQgMTYuMDI1MUw0OC42NTIzIDIyLjIxMjhMNDUuNjQxOSAyNS40NzNMNDAuOTM4OSAzMC4xNzU5TDMzLjEwMDcgMzguMDE0MkwzNy41ODY2IDMzLjg3OEwzMS41NTggMzkuNjA2OEwyMy4zMjc4IDQ3LjgzN0wzMy4wMjU3IDM3LjkzOTNMMzguNTEyNSAzMi40NTI1TDM0LjAyNjYgMzYuNTg4N0wzNy4yMzY5IDMzLjUyODNMNDMuNjA3NCAyNy4zNTc2TDQ4LjYwMjMgMjIuMTYyOEw0MS45Mzk4IDI4LjgyNTRaTTQxLjA5NzcgMTcuMDUzMUwzOS43MTggMTguMjkyNUw0MC4zMTIgMTcuODM4OEw0MS4wOTc3IDE3LjA1MzFaTTM2Ljg3NSAyMC4zMTA2TDQ4LjE2MDEgNy44ODEzN0w0Mi4zNDM4IDEzLjc0NzhMMzYuODc1IDIwLjMxMDZaTTM1LjcxMjUgMjUuODEwOUwzNC4zMzI4IDI3LjA1MDNMMzQuOTI2OCAyNi41OTY2TDM1LjcxMjUgMjUuODEwOVpNMTcuNzAyMiAzOS43NTM0TDE5LjA4MTkgMzguNTE0TDE4LjgwOTIgMzguNzg2N0wzNi43NTc1IDIxLjgwNDVMMjMuMTU2OSAzNS4zMDUxTDEzLjU3NzEgNDMuNzM3MkwxOC4xNDQ4IDM5LjQxNTRMMTcuNzAyMiAzOS43NTM0Wk0zLjQ4MTAyIDI4LjkyODFMMS41MzU2MiAzMC44NzM1TDEuMjIyMjggMzEuMDQ2NUwwLjA3NjU2ODYgMzIuMzMyNkwxLjYwNTc5IDMwLjk0MzdMMi41Nzg0OSAyOS45NzFMMy40ODEwMiAyOC45MjgxWk0wLjk1MzQ2MyAyNi4yMDI3TDE5LjU3MDIgNy41ODU5NEw5LjMxNTc1IDE4LjYwNzhMMC45NTM0NjMgMjYuMjAyN1pNMjMuNzE3NSAxMi4xMUwxNy45MzM5IDE4LjA4NzVMMjEuNDYyMiAxNC41NTkyTDIwLjgwNzQgMTUuNDcyNUwyOC4xOTE1IDcuOTU5MThMMzAuNDc5MSA1LjU0MjMyTDIzLjQyMjQgMTIuNTk5TDIzLjcxNzUgMTIuMTFaTTQzLjQ2NDEgNDMuMTUzOEw0MC43ODcyIDQ2LjE1NTJMNDIuNDkwNyA0NC40NTE3TDQyLjMyODUgNDUuMDQ2NUw0NS44MTY2IDQxLjM0MjFMNDYuODQ0MSA0MC4wOTgzTDQzLjQzNzEgNDMuNTA1M0w0My40NjQxIDQzLjE1MzhaTTEuMzI3MTUgNDguMzI3MUw4LjA5MTggNDEuNTYyNUw0LjM2NTcgNDUuNTY3NEwxLjMyNzE1IDQ4LjMyNzFaTTExLjE0NzkgMzEuMjU1NkwxMS41Njg5IDMwLjk3NUwxMS4zNTg0IDMxLjE4NTVMMTEuMTQ3OSAzMS4yNTU2Wk0xMS45ODk4IDI3LjQ2NjdMMTIuMjAwMyAyNy4yNTYyTDExLjc3OTMgMjcuNTM2OUwxMS45ODk4IDI3LjQ2NjdaTTExLjM1ODUgMzQuNTUzMUwxMS4xNDggMzQuNzYzNkwxMC45Mzc1IDM0LjgzMzhMMTEuMzU4NSAzNC41NTMxWk03Mi45MjkgMjguNTQ1N0w4Mi4yOTY1IDE5LjA3OTJMODEuNDA0MyAyMC4wNzA1TDg2LjQ1OTcgMTUuMDgxMUw3OC4yOTgzIDIzLjI0MjVMNzUuODY5NyAyNS44MzYyTDcyLjEwMjkgMjkuNjAzTDY1LjgyNDkgMzUuODgxTDY5LjM5MzQgMzIuNTQzN0w2NC41ODU4IDM3LjE1MzFMNTcuOTk0IDQzLjc0NUw2NS43NzU0IDM1LjgzMTRMNzAuMTcgMzEuNDM2OUw2Ni42MDE1IDM0Ljc3NDJMNjkuMTYyMyAzMi4zMTI1TDc0LjI1MDcgMjcuMzU2Mkw3OC4yNjUzIDIzLjIwOTVMNzIuOTI5IDI4LjU0NTdaTTgyLjY2NzQgMS44MzU0OUw4NC4zMjQ1IDAuMzE4NzJMODMuMzcyNCAxLjI3MDg4TDgyLjY2NzQgMS44MzU0OVpNNjQuNTg3MiAxNi4xMzEyTDYyLjkzMDEgMTcuNjQ4TDYzLjYzNTEgMTcuMDgzNEw2NC41ODcyIDE2LjEzMTJaTTcwLjg2OCA5Ljg1MDQ0TDgwLjAwNDggMS4xMjE0TDc0LjYyMjEgNi40NzE0Mkw3MC44NjggOS44NTA0NFpNOTAuMjQwOSA0MS45NDQ4TDcwLjc1NzggNjEuNDI3OUw3OS41MDkzIDUzLjQ3OTVMOTAuMjQwOSA0MS45NDQ4Wk05MS44MDg4IDQyLjU0MzRMOTUuMzk2MyAzOC44MzU3TDk1LjIxMzIgMzkuMTM5TDk5LjU5MDQgMzQuNzYxOEw5OC4xNzE0IDM2LjI2MUw5My41OTEyIDQwLjkyMTRMOTMuOTk3MyA0MC4zNTQ5TDkxLjgwODggNDIuNTQzNFpNOTQuMzMxIDEyLjgyMzNMODkuOTg1MyAxNy4xNjkxTDg5LjI4NTMgMTcuNTU1NUw4Ni43MjU5IDIwLjQyODRMOTAuMTQyIDE3LjMyNThMOTIuMzE0OSAxNS4xNTI5TDk0LjMzMSAxMi44MjMzWk00NC43OTcyIDYyLjMyNTlMNzYuOTgyNCAzMC4xNDA2TDU5LjI1NDIgNDkuMTk1NUw0NC43OTcyIDYyLjMyNTlaTTc3LjE0ODIgNDAuMzIxTDcwLjE3MDkgNDcuNTMyM0w3MCA0Ny42NDYzTDcwLjA4OTUgNDcuNjE2NEw2OC4xOTE2IDQ5LjU3NzlMNzAuMTg1IDQ3LjU4NDZMNzAuMjEwNSA0Ny41NzYxTDcwLjQyMSA0Ny4zNjU2TDcwLjM3IDQ3LjM5OTZMNzMuNjU1NyA0NC4xMTM5TDcyLjY0MTYgNDUuNTI4M0w4NC4wNzY4IDMzLjg5M0w4Ny42MTk0IDMwLjE1MDJMNzYuNjkxMyA0MS4wNzgzTDc3LjE0ODIgNDAuMzIxWk01MC41MzU1IDM0LjMxMzdMNzIuNjYxNyAxMi4xODc1TDYwLjQ5NTUgMjUuMzA4NEw1MC41MzU1IDM0LjMxMzdaTTcwLjIxMDQgNDQuMDY4MUw3MC42MzE0IDQzLjc4NzVMNzAuNDIwOSA0My45OThMNzAuMjEwNCA0NC4wNjgxWk03MS4yNjMgNDAuMDY4N0w3MC44NDIgNDAuMzQ5NEw3MS4wNTI1IDQwLjI3OTJMNzEuMjYzIDQwLjA2ODdaTTU1LjEwODQgMTIuNDM1NUw1NS4zMTg5IDEyLjIyNUw1NC44OTc5IDEyLjUwNTZMNTUuMTA4NCAxMi40MzU1Wk00OC44NzE4IDE1LjU3ODVMNjAuMjA3NSA0LjcwNDk2TDQ5LjQwNTYgMTUuNDAwNkw0OC44NzE4IDE1LjU3ODVaTTIzLjc2MzYgNTcuNDQ5MUwyOS45MDk5IDUxLjU4NTRMMjYuMTY1NiA1NS42MTIzTDI3LjIzNjEgNTQuODI0NEwyMy40MzUgNTguNjI1NUwyMi4wNjgxIDU5Ljk5MjRMMjAuMDU2MiA2Mi4wMDQyTDE4LjUwODIgNjMuODM0OUwxNi45NjAxIDY1LjY2NTZMMTUuODMyOCA2Ni4yMjc3TDEzLjkzMTUgNjcuNzA1MUwxMC40ODIxIDcxLjAxMzJMMTQuMjgzMiA2Ny4yMTIxTDE2LjY3NzUgNjUuMzgzTDIxLjExMTMgNjAuNTI1M0wyMC40NzcgNjAuNzM1N0wyMy4yOTM3IDU4LjQ4NDJMMjUuODI3NyA1NS45NTAyTDIzLjc2MzYgNTcuNDQ5MVpNNDguMzgyNSA3NC4xODI0TDQ0Ljg4MzIgNzcuODUyM0w0Ni45MTQ1IDc1LjgyMTFMNDUuNDc0OCA3Ny40ODgxTDQzLjQ0OTMgNzkuMjg2Mkw0Mi40MDgyIDgwLjE1NjhMNDMuOTIxNSA3OS4wNDE0TDQyLjI0ODcgODAuNzE0M0wzOS4zNzUyIDgzLjgxNTFMNDEuODg0NCA4MS4zMDU5TDQzLjg0NzMgNzkuNjg0Mkw0Mi4zMzQgODAuNzk5NUw0NC43MjM3IDc4LjQwOThMNDYuMTU3NiA3Ni45NzZMNDYuOTcxMyA3NS44Nzc5TDUwLjA3OCA3Mi43NzEzTDQ4LjEwOTMgNzQuNjI2Mkw0OC4zODI1IDc0LjE4MjRaTTI5LjI4NzcgNjIuOTkwNkwyOS4wNzcyIDYzLjIwMTFMMjguODY2NyA2My4yNzEzTDI5LjI4NzcgNjIuOTkwNlpNMjkuNzA4OCA1OS40ODIzTDI5LjkxOTMgNTkuMjcxOUwyOS40OTgzIDU5LjU1MjVMMjkuNzA4OCA1OS40ODIzWk0yOS4wNzcyIDY2LjU2ODdMMjguODY2NyA2Ni43NzkyTDI4LjY1NjIgNjYuODQ5NEwyOS4wNzcyIDY2LjU2ODdaTTIyLjk3MjkgNjguNzQ4TDIzLjE4MzQgNjguNTM3NUwyMi43NjI0IDY4LjgxODFMMjIuOTcyOSA2OC43NDhaTTMuODE0N2UtMDUgOTEuNzU5M0wxMy4yNDk5IDc5LjEzNTVMNi41MDAxIDg2LjI1OTVMMy44MTQ3ZS0wNSA5MS43NTkzWk0xNi4wNjg1IDg3Ljk5NzRMMTcuMTM3NSA4Ny4wNjg3TDE2LjUzODIgODcuNjY4TDE2LjA2ODUgODcuOTk3NFpNMjEuNzg2OSA3OS4zMzQ0TDIwLjcxNzkgODAuMjYzTDIxLjE4NzYgNzkuOTMzN0wyMS43ODY5IDc5LjMzNDRaTTEyLjM2MDcgOTUuMDc1NUwxMy40Mjk4IDk0LjE0NjlMMTIuODMwNCA5NC43NDYyTDEyLjM2MDcgOTUuMDc1NVpNNDIuNzE3NiA1OS4zODAxTDQzLjI3ODkgNTguODE4N0w0My4wNjg0IDU5LjE2OTZMNDIuNzg3NyA1OS40NTAyTDQyLjI5NjYgNTkuODAxTDQyLjU3NzIgNTkuMzgwMUg0Mi43MTc2Wk0yNi4zMTI0IDQ5LjMxNTJMMjQuMzU5OSA1MS4yNjc2TDIzLjk5NiA1MS4zOTE4TDIyLjg5NTYgNTIuNzMyTDI0LjQ3OTggNTEuMzg3NUwyNS40NTYgNTAuNDExM0wyNi4zMTI0IDQ5LjMxNTJaTTM5LjA2ODkgNjMuMzA5N0wzOC41Nzc3IDYzLjY2MDZMMzkuNTYgNjIuNjc4MkwzOS4wNjg5IDYzLjMwOTdaTTIwLjM1NzQgNTUuODAzMkwxOS4zNzUxIDU2Ljc4NTZMMTkuODY2MiA1Ni40MzQ3TDIwLjM1NzQgNTUuODAzMlpNMzkuOTI5NyA2NC4xOTVMNDEuNTUwNCA2Mi4zNzc5TDQxLjUzNCA2Mi41OTA3TDQzLjU5NjcgNjAuNTI4TDQyLjk3NDYgNjEuMjgxMUw0MC44NjI4IDYzLjUyMzhMNDAuOTYxIDYzLjE2MzdMMzkuOTI5NyA2NC4xOTVaTTIyLjM5MjEgNTUuNDU3TDIxLjM5OTggNTYuNTY5NkwyMi4wMzEzIDU1LjkzODFMMjEuOTcxMSA1Ni4xNTg3TDIzLjI2NDIgNTQuNzg1NEwyMy42NDUxIDU0LjMyNDNMMjIuMzgyMSA1NS41ODczTDIyLjM5MjEgNTUuNDU3Wk00MC42NDczIDkyLjQ0OThMNDUuMDQ4NSA4OC4wNDg1TDQzLjAwNjYgOTAuNDA3OUw0MC44MDYgOTIuNjA4NUwzNy4zNDYzIDk1Ljc1MDdMMzkuOTM4NCA5Mi44NDEyTDQwLjY0NzMgOTIuNDQ5OFpNMTguNTA0MiA0OC43OTczTDExLjU0NTcgNTUuNzU1OEwxMC40MjQ5IDU2LjM3NDZMNi4zMjY4NCA2MC45NzQ2TDExLjc5NjcgNTYuMDA2N0wxNS4yNzU5IDUyLjUyNzVMMTguNTA0MiA0OC43OTczWk0zMi43MTEzIDc4LjEzOUwzMS4xMTMxIDc5LjczNzJMMzAuODQzMiA3OS44NjY4TDI5LjkxNDUgODAuOTM1OEwzMS4xODMzIDc5LjgwNzRMMzEuOTgyMyA3OS4wMDgzTDMyLjcxMTMgNzguMTM5Wk0yMS43NTc3IDkzLjk1MjVMMzEuMjg1NSA4NC4wMzQ0TDMwLjgzMjQgODQuODc3N0w0Mi40OTk5IDczLjIxMDJMMzguNzQwOCA3Ny4yMjk1TDI2LjU1NTIgODkuNjc1M0wyNy41OTE0IDg4LjExODdMMjEuNzU3NyA5My45NTI1Wk05OC41MTMyIDkwLjA1OTFMODkuOTIyNCA5Ny45MjI0TDkzLjU3NjkgOTQuOTk1M0w5OC41MTMyIDkwLjA1OTFaTTk3Ljg0NTYgODAuMjEwNUw5OS41MDI3IDc4LjY5MzdMOTguNTUwNiA3OS42NDU5TDk3Ljg0NTYgODAuMjEwNVpNODguNTY1NiA1Ni40NTk5TDc4LjkyMDUgNjUuNzAwOUw4Mi4xMjYyIDYzLjMwMzZMNzguMTQxMyA2Ny4yODg1TDczLjc1MjIgNzAuODY5Mkw3NC43MTk1IDcwLjUwODJMNjcuNzE3IDc4LjExN0w2My45OTIgODEuMDMzNkw1OC4wMTQ2IDg3LjAxMUw2My40Mjg5IDgxLjc5ODhMNjYuMzg4NyA3OS40NDU0TDY4LjEyMTIgNzguNTIxM0w3MC41NzU3IDc1LjY2MjVMNzMuMDMwMiA3Mi44MDM4TDc2LjE5NCA2OS42NEw3OC4zNDM0IDY3LjQ5MDZMODQuMzIwOCA2MS41MTMyTDgyLjY1NzUgNjIuNzcyM0w4OC41NjU2IDU2LjQ1OTlaTTg1LjE4OTMgNjcuMDM3NUw4My43MzA0IDY4LjM1Nkw4NC4zNTYxIDY3Ljg3MDdMODUuMTg5MyA2Ny4wMzc1Wk05MC43OTY5IDU4LjIwMjJMOTkuMjcyNSA1MC41NDE4TDk0LjQzMTcgNTUuMzgyNkw5MC43OTY5IDU4LjIwMjJaTTc5LjM3NyA3Ni4yMTcyTDc3LjkxODIgNzcuNTM1N0w3OC41NDM4IDc3LjA1MDRMNzkuMzc3IDc2LjIxNzJaTTU5LjQ5MjIgOTEuNzI1M0w1Ni40MDExIDk0LjEyMzFMNjAuMDA0OSA5MC44NjU5TDYzLjYwODcgODcuNjA4N0w1OS40OTIyIDkxLjcyNTNaTTYzLjg4MzMgNzUuNDE1M0w0NiA5Mi4zODk2TDQ5LjY4ODQgODkuMTE5M0w1My4zNzY3IDg1Ljg0OTFMNjMuODgzMyA3NS40MTUzWk03MS42MDYzIDU1LjA3NjVMNjkuNjYwOSA1Ny4wMjE5TDY5LjM0NzUgNTcuMTk0OUw2OC4yMDE4IDU4LjQ4MUw2OS43MzEgNTcuMDkyMUw3MC43MDM3IDU2LjExOTRMNzEuNjA2MyA1NS4wNzY1Wk01NS4xNDA1IDcxLjY4NTdMNjEuNDEzMSA2NS40MTMxTDU3Ljk1OCA2OS4xMjY3TDU1LjE0MDUgNzEuNjg1N1pNNjUuODM5NiA2OS40NDk3TDYxLjcxMzggNzMuNzEzOEw2NC4yMzA4IDcxLjE5NjhMNjMuNzYzNyA3MS44NDg0TDY5LjAzMTMgNjYuNDg4Nkw3MC42NjMyIDY0Ljc2NDVMNjUuNjI5MiA2OS43OTg1TDY1LjgzOTYgNjkuNDQ5N1pNNTMuMDAzNCA2NS40OTU1TDU4LjIyNTggNTkuODkxNEw1OC4wNTU4IDYwLjQ0MzFMNjQuNTUxNyA1My45NDcyTDYyLjUxMzYgNTYuMjM5OEw1NS43ODQxIDYzLjIyMzhMNTYuMjUxMyA2Mi4yNDc1TDUzLjAwMzQgNjUuNDk1NVpNOTcuMDk5NyA3MS4yMDMyTDc5LjY1MTQgODguNjUxNUw4Ni43Njk3IDgwLjgxNEw5Ny4wOTk3IDcxLjIwMzJaTTM1LjE4NDggNTYuMjUxM0wzMS45MyA1OS45MDA2TDM0LjAwMTIgNTcuODI5NEwzMy44MDQgNTguNTUyN0wzOC4wNDUxIDU0LjA0ODVMMzkuMjk0NSA1Mi41MzYxTDM1LjE1MTkgNTYuNjc4N0wzNS4xODQ4IDU2LjI1MTNaTTY2Ljg3MTIgMjYuMjQ3MUw3OC4xOTA3IDE0LjMwOTlMNzcuNzI0NCAxNS4zOTRMOTEuNjc4NCAxLjQzOTlMODcuMjMzIDYuMjk3MTVMNzIuNzA5NiAyMS4yMzIzTDczLjg0ODIgMTkuMjcwMUw2Ni44NzEyIDI2LjI0NzFaTTI4LjA0NzMgNjguMjA2OEwyMC40MzU1IDc2LjM3NUwyNS4xNjk1IDcxLjY0MUwyNC40ODg0IDczLjA2MzlMMzQuMjk3IDYyLjg4NDRMMzcuMjY3NSA1OS41NDI5TDI3Ljc5OTUgNjkuMDEwOUwyOC4wNDczIDY4LjIwNjhaTTguOTQwNjcgMzkuNTY1OEwxNC4xNjMxIDMzLjk2MTdMMTMuOTkzIDM0LjUxMzRMMjAuNDg4OSAyOC4wMTc1TDE4LjQ1MDkgMzAuMzEwMUwxMS43MjEzIDM3LjI5NDFMMTIuMTg4NiAzNi4zMTc4TDguOTQwNjcgMzkuNTY1OFpNOTkuNzQwMyAyNkw4OCAzNy43NDA0TDkzLjI3MzUgMzIuOTUwOEw5OS43NDAzIDI2Wk0xLjkzMzg4IDguMDg3NDNMNC43Nzc2NSA1LjA0OTc0TDQuNjc4NTYgNS4zNDI3NUw4LjIwNzQzIDEuODEzODhMNy4wOTU3OCAzLjA1NDgxTDMuNDM1NSA2Ljg0NDM3TDMuNjk4MzIgNi4zMjI5OUwxLjkzMzg4IDguMDg3NDNaTTU0LjQ0ODUgNDQuMjExTDQ4LjU5ODUgNTAuMDYxTDQ3LjY1NjMgNTAuNTgxM0w0NC4yMTEgNTQuNDQ4NUw0OC44MDk1IDUwLjI3Mkw1MS43MzQ1IDQ3LjM0N0w1NC40NDg1IDQ0LjIxMVoiIC8+CjwvcGF0dGVybj4KPHBhdHRlcm4gaWQ9InN0cmVha3MtZGFya2VyLWQyLTM4NTc1NTE5OCIgeD0iMCIgeT0iMCIgd2lkdGg9IjEwMCIgaGVpZ2h0PSIxMDAiIHBhdHRlcm5Vbml0cz0idXNlclNwYWNlT25Vc2UiPgogICAgPHBhdGggZmlsbD0icmdiYSgyNTUsIDI1NSwgMjU1LCAwLjI0KSIgZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik01OC4xMTkzIDBINTguMTcwM0w1NS40OTM5IDIuNjc2NDRMNTguMTE5MyAwWk00NS43NzI1IDBINDUuODExTDQxLjI4NTEgNC42MTQ5OEw0Mi43MTkxIDMuMjkzMjVMMzcuMDgyNCA4LjkyOTk3TDM1LjA1NTQgMTAuOTU2OUwzMi4wNzE5IDEzLjk0MDRMMjkuNjIyOSAxNi41MDE3TDI3LjE3MzggMTkuMDYzMUwyNS44MDg5IDIwLjIwMzRMMjMuMjE5NSAyMi42MjQ0TDE4LjE4MSAyNy42MDY4TDIzLjgxNzggMjEuOTdMMjcuMDYxNSAxOC45NTA4TDMzLjg2NjYgMTEuOTc3M0wzMy4xNTYyIDEyLjUxOTRMMzcuMDI2MiA4Ljg3MzgzTDQwLjc4NCA1LjExNjAyTDM4LjAyOTkgNy42NDU2MUw0NS43NzI1IDBaTTIzLjEwNzkgMEgyMy4xMDhMMjEuNTgxNCAxLjY2Njg4TDIwLjMxMjYgMi43OTUzNEwyMy4xMDc5IDBaTTcuNTM4NjkgMEg3LjU0MjU0TDcuNTAwMDUgMC4wMzU5NDRMNy41Mzg2OSAwWk0yLjQ5OTk1IDBIMi41MjM2MkwwLjkwMDI0NSAxLjU5OTcxTDIuNDk5OTUgMFpNMCAzLjY0Mzk4VjMuNjA3NDRMMC4yNzgzODYgMy4zNjU1OUwwIDMuNjQzOThaTTAgMTguNjU2NFYxOC41Mzk4TDAuNjc5ODUgMTcuODQxNkwzLjQ0NTkgMTUuMDc1NUwxLjE1NzAxIDE3LjEzMzNMMi43ODcxMyAxNS42MDIyTDYuMDE0MzcgMTIuNTA3TDguNTE2OCA5Ljg3MjUzTDUuMTU4MDMgMTMuMjMxM0wxMS4wMzU3IDcuMjU0NTNMMTAuNDkyNiA3Ljg5Njc4TDEzLjY4NjggNC43Njg2TDguNTQ5ODIgOS45MDU1NUw3LjA1MTc3IDExLjU2ODdMNC42ODA4NyAxMy45Mzk2TDAuNzI5Mzc5IDE3Ljg5MTFMMy4wMTgyNyAxNS44MzMzTDAgMTguNjU2NFpNMCA2OS4yNDMxVjY5LjE3OEwxLjY0NjUxIDY3LjQ3NjNMMS40NjM0NyA2Ny43Nzk2TDUuODQwNjMgNjMuNDAyNUw0LjQyMTY3IDY0LjkwMTZMMCA2OS40MDA3VjY5LjM0MDhMMC4yNDc1OTYgNjguOTk1NUwwIDY5LjI0MzFaTTIuNTE1OTQgMTAwSDIuNDkyMzhMNS4xOTk4OSA5Ny4yOTI1TDcuNzAwNzEgOTUuMDE2MkwxMi44NzEzIDg5LjY3NzJMMTIuMzA5NCA5MC4wNzA3TDE1LjI4OCA4Ny4zMTY3TDE4LjE1NDIgODQuNDUwNEwxNi4wMjY5IDg2LjM1MzJMMjIuODc1MiA3OS42MTcyTDE4LjUzNjQgODQuMDY4M0wxOS42NDM1IDgzLjA3MzRMMTUuMzQ0MSA4Ny4zNzI4TDEzLjc5OCA4OC45MTg5TDExLjUyMjQgOTEuMTk0NUw5LjY2NzY4IDkzLjE2MTVMNy44MTI5NyA5NS4xMjg1TDYuNzQ1MjkgOTUuOTcxNkw0Ljc1MDI0IDk3Ljc5ODNMMi41MTU5NCAxMDBaTTcuNTQyNTUgMTAwSDcuNTM4N0w5LjgxMzk2IDk3Ljg4NEw4LjQ2NjA2IDk5LjIxODlMNy41NDI1NSAxMDBaTTQ1LjgxODkgMTAwSDQ1Ljc4MDdMNDYuOTkxMiA5OC44MDQ3TDQ1LjgxODkgMTAwWk01OC4xNzg0IDEwMEg1OC4xMjcyTDYyLjI5NTIgOTUuNzUxMUw2Ni4xNDA4IDkxLjkwNTVMNjMuMDAzNyA5NC44MTE1TDY1LjI1MDcgOTIuNjYzNUw2OS43MTE3IDg4LjMzNDZMNzMuMjE2NSA4NC42OTc3TDY4LjU0NjkgODkuMzY3M0w3Ni43Mzc5IDgxLjA3NzNMNzUuOTYzNCA4MS45NTA5TDgwLjM5MTMgNzcuNTg4OUw3My4yNDk2IDg0LjczMDdMNzEuMTM0NiA4Ny4wMTA3TDY3LjgzODQgOTAuMzA2OUw2Mi4zNDQ3IDk1LjgwMDZMNjUuNDgxOCA5Mi44OTQ3TDYxLjI2MjUgOTYuOTE1OUw1OC4xNzg0IDEwMFpNNzUuNDI3NyAxMDBINzUuMjI5TDgyLjE4MzQgOTIuOTAzOUw4MS4zNDAzIDkzLjU3ODdMODYuMDA2MyA4OS4xMzcxTDkwLjU2MDEgODQuNTgzM0w4Ny4yNDY0IDg3LjY3MjVMOTguMDkzNyA3Ni45Mzc1TDkxLjE2NzMgODMuOTc2MUw5Mi44OTMyIDgyLjM2MjVMODYuMDYyNSA4OS4xOTMzTDgzLjYwNjIgOTEuNjQ5Nkw3OS45OTA3IDk1LjI2NUw3Ny4wMTEgOTguMzU3TDc1LjQyNzcgMTAwWk0xMDAgMTguNTM5OFYxOC42NTYzTDk5Ljk1NTYgMTguNjk3OUw5NS44MDY1IDIyLjg0N0wxMDAgMTguNTM5OFpNMTAwIDMuNjA3NDNWMy42NDM5OEw5OS42NzkxIDMuOTY0OUw5OS4yMDk0IDQuMjk0MjhMMTAwIDMuNjA3NDNaTTc1LjQyMDEgMEw3NC4wMzEyIDEuNDQxMkw3Mi40MDEgMi44NDY4N0w2OS4yODEgNS43OTg1NEw2My4xODEyIDExLjg0MjJMNzAuMDExOSA1LjAxMTUxTDczLjkxOSAxLjMyODkzTDc1LjIyMTQgMEg3NS40MjAxWk0xMDAgNjkuMTg1OFY2OS4yNTA5TDk4LjA1OSA3MS4xOTE5TDEwMCA2OS4xODU4Wk0xMDAgNjkuMzQ4NlY2OS40MDg1TDk5Ljg0MTQgNjkuNTY5OEwxMDAgNjkuMzQ4NlpNNDEuOTM5OCAyOC44MjU0TDUzLjYyMjMgMTYuOTkzTDUyLjUyMTUgMTguMjQzN0w1NC43NDI4IDE2LjA1NzVMNTQuNjg3NSAxNi4wNzU5TDU0LjgwMDggMTYuMDAwNEw1OC44NDIgMTIuMDIzMUw1NC45OTI1IDE1Ljg3MjZMNTUuMTA4NSAxNS43OTUzTDU0Ljg5OCAxNi4wMDU4TDU0Ljg0IDE2LjAyNTFMNDguNjUyMyAyMi4yMTI4TDQ1LjY0MTkgMjUuNDczTDQwLjkzODkgMzAuMTc1OUwzMy4xMDA3IDM4LjAxNDJMMzcuNTg2NiAzMy44NzhMMzEuNTU4IDM5LjYwNjhMMjMuMzI3OCA0Ny44MzdMMzMuMDI1NyAzNy45MzkzTDM4LjUxMjUgMzIuNDUyNUwzNC4wMjY2IDM2LjU4ODdMMzcuMjM2OSAzMy41MjgzTDQzLjYwNzQgMjcuMzU3Nkw0OC42MDIzIDIyLjE2MjhMNDEuOTM5OCAyOC44MjU0Wk00MS4wOTc3IDE3LjA1MzFMMzkuNzE4IDE4LjI5MjVMNDAuMzEyIDE3LjgzODhMNDEuMDk3NyAxNy4wNTMxWk0zNi44NzUgMjAuMzEwNkw0OC4xNjAxIDcuODgxMzdMNDIuMzQzOCAxMy43NDc4TDM2Ljg3NSAyMC4zMTA2Wk0zNS43MTI1IDI1LjgxMDlMMzQuMzMyOCAyNy4wNTAzTDM0LjkyNjggMjYuNTk2NkwzNS43MTI1IDI1LjgxMDlaTTE3LjcwMjIgMzkuNzUzNEwxOS4wODE5IDM4LjUxNEwxOC44MDkyIDM4Ljc4NjdMMzYuNzU3NSAyMS44MDQ1TDIzLjE1NjkgMzUuMzA1MUwxMy41NzcxIDQzLjczNzJMMTguMTQ0OCAzOS40MTU0TDE3LjcwMjIgMzkuNzUzNFpNMy40ODEwMiAyOC45MjgxTDEuNTM1NjIgMzAuODczNUwxLjIyMjI4IDMxLjA0NjVMMC4wNzY1Njg2IDMyLjMzMjZMMS42MDU3OSAzMC45NDM3TDIuNTc4NDkgMjkuOTcxTDMuNDgxMDIgMjguOTI4MVpNMC45NTM0NjMgMjYuMjAyN0wxOS41NzAyIDcuNTg1OTRMOS4zMTU3NSAxOC42MDc4TDAuOTUzNDYzIDI2LjIwMjdaTTIzLjcxNzUgMTIuMTFMMTcuOTMzOSAxOC4wODc1TDIxLjQ2MjIgMTQuNTU5MkwyMC44MDc0IDE1LjQ3MjVMMjguMTkxNSA3Ljk1OTE4TDMwLjQ3OTEgNS41NDIzMkwyMy40MjI0IDEyLjU5OUwyMy43MTc1IDEyLjExWk00My40NjQxIDQzLjE1MzhMNDAuNzg3MiA0Ni4xNTUyTDQyLjQ5MDcgNDQuNDUxN0w0Mi4zMjg1IDQ1LjA0NjVMNDUuODE2NiA0MS4zNDIxTDQ2Ljg0NDEgNDAuMDk4M0w0My40MzcxIDQzLjUwNTNMNDMuNDY0MSA0My4xNTM4Wk0xLjMyNzE1IDQ4LjMyNzFMOC4wOTE4IDQxLjU2MjVMNC4zNjU3IDQ1LjU2NzRMMS4zMjcxNSA0OC4zMjcxWk0xMS4xNDc5IDMxLjI1NTZMMTEuNTY4OSAzMC45NzVMMTEuMzU4NCAzMS4xODU1TDExLjE0NzkgMzEuMjU1NlpNMTEuOTg5OCAyNy40NjY3TDEyLjIwMDMgMjcuMjU2MkwxMS43NzkzIDI3LjUzNjlMMTEuOTg5OCAyNy40NjY3Wk0xMS4zNTg1IDM0LjU1MzFMMTEuMTQ4IDM0Ljc2MzZMMTAuOTM3NSAzNC44MzM4TDExLjM1ODUgMzQuNTUzMVpNNzIuOTI5IDI4LjU0NTdMODIuMjk2NSAxOS4wNzkyTDgxLjQwNDMgMjAuMDcwNUw4Ni40NTk3IDE1LjA4MTFMNzguMjk4MyAyMy4yNDI1TDc1Ljg2OTcgMjUuODM2Mkw3Mi4xMDI5IDI5LjYwM0w2NS44MjQ5IDM1Ljg4MUw2OS4zOTM0IDMyLjU0MzdMNjQuNTg1OCAzNy4xNTMxTDU3Ljk5NCA0My43NDVMNjUuNzc1NCAzNS44MzE0TDcwLjE3IDMxLjQzNjlMNjYuNjAxNSAzNC43NzQyTDY5LjE2MjMgMzIuMzEyNUw3NC4yNTA3IDI3LjM1NjJMNzguMjY1MyAyMy4yMDk1TDcyLjkyOSAyOC41NDU3Wk04Mi42Njc0IDEuODM1NDlMODQuMzI0NSAwLjMxODcyTDgzLjM3MjQgMS4yNzA4OEw4Mi42Njc0IDEuODM1NDlaTTY0LjU4NzIgMTYuMTMxMkw2Mi45MzAxIDE3LjY0OEw2My42MzUxIDE3LjA4MzRMNjQuNTg3MiAxNi4xMzEyWk03MC44NjggOS44NTA0NEw4MC4wMDQ4IDEuMTIxNEw3NC42MjIxIDYuNDcxNDJMNzAuODY4IDkuODUwNDRaTTkwLjI0MDkgNDEuOTQ0OEw3MC43NTc4IDYxLjQyNzlMNzkuNTA5MyA1My40Nzk1TDkwLjI0MDkgNDEuOTQ0OFpNOTEuODA4OCA0Mi41NDM0TDk1LjM5NjMgMzguODM1N0w5NS4yMTMyIDM5LjEzOUw5OS41OTA0IDM0Ljc2MThMOTguMTcxNCAzNi4yNjFMOTMuNTkxMiA0MC45MjE0TDkzLjk5NzMgNDAuMzU0OUw5MS44MDg4IDQyLjU0MzRaTTk0LjMzMSAxMi44MjMzTDg5Ljk4NTMgMTcuMTY5MUw4OS4yODUzIDE3LjU1NTVMODYuNzI1OSAyMC40Mjg0TDkwLjE0MiAxNy4zMjU4TDkyLjMxNDkgMTUuMTUyOUw5NC4zMzEgMTIuODIzM1pNNDQuNzk3MiA2Mi4zMjU5TDc2Ljk4MjQgMzAuMTQwNkw1OS4yNTQyIDQ5LjE5NTVMNDQuNzk3MiA2Mi4zMjU5Wk03Ny4xNDgyIDQwLjMyMUw3MC4xNzA5IDQ3LjUzMjNMNzAgNDcuNjQ2M0w3MC4wODk1IDQ3LjYxNjRMNjguMTkxNiA0OS41Nzc5TDcwLjE4NSA0Ny41ODQ2TDcwLjIxMDUgNDcuNTc2MUw3MC40MjEgNDcuMzY1Nkw3MC4zNyA0Ny4zOTk2TDczLjY1NTcgNDQuMTEzOUw3Mi42NDE2IDQ1LjUyODNMODQuMDc2OCAzMy44OTNMODcuNjE5NCAzMC4xNTAyTDc2LjY5MTMgNDEuMDc4M0w3Ny4xNDgyIDQwLjMyMVpNNTAuNTM1NSAzNC4zMTM3TDcyLjY2MTcgMTIuMTg3NUw2MC40OTU1IDI1LjMwODRMNTAuNTM1NSAzNC4zMTM3Wk03MC4yMTA0IDQ0LjA2ODFMNzAuNjMxNCA0My43ODc1TDcwLjQyMDkgNDMuOTk4TDcwLjIxMDQgNDQuMDY4MVpNNzEuMjYzIDQwLjA2ODdMNzAuODQyIDQwLjM0OTRMNzEuMDUyNSA0MC4yNzkyTDcxLjI2MyA0MC4wNjg3Wk01NS4xMDg0IDEyLjQzNTVMNTUuMzE4OSAxMi4yMjVMNTQuODk3OSAxMi41MDU2TDU1LjEwODQgMTIuNDM1NVpNNDguODcxOCAxNS41Nzg1TDYwLjIwNzUgNC43MDQ5Nkw0OS40MDU2IDE1LjQwMDZMNDguODcxOCAxNS41Nzg1Wk0yMy43NjM2IDU3LjQ0OTFMMjkuOTA5OSA1MS41ODU0TDI2LjE2NTYgNTUuNjEyM0wyNy4yMzYxIDU0LjgyNDRMMjMuNDM1IDU4LjYyNTVMMjIuMDY4MSA1OS45OTI0TDIwLjA1NjIgNjIuMDA0MkwxOC41MDgyIDYzLjgzNDlMMTYuOTYwMSA2NS42NjU2TDE1LjgzMjggNjYuMjI3N0wxMy45MzE1IDY3LjcwNTFMMTAuNDgyMSA3MS4wMTMyTDE0LjI4MzIgNjcuMjEyMUwxNi42Nzc1IDY1LjM4M0wyMS4xMTEzIDYwLjUyNTNMMjAuNDc3IDYwLjczNTdMMjMuMjkzNyA1OC40ODQyTDI1LjgyNzcgNTUuOTUwMkwyMy43NjM2IDU3LjQ0OTFaTTQ4LjM4MjUgNzQuMTgyNEw0NC44ODMyIDc3Ljg1MjNMNDYuOTE0NSA3NS44MjExTDQ1LjQ3NDggNzcuNDg4MUw0My40NDkzIDc5LjI4NjJMNDIuNDA4MiA4MC4xNTY4TDQzLjkyMTUgNzkuMDQxNEw0Mi4yNDg3IDgwLjcxNDNMMzkuMzc1MiA4My44MTUxTDQxLjg4NDQgODEuMzA1OUw0My44NDczIDc5LjY4NDJMNDIuMzM0IDgwLjc5OTVMNDQuNzIzNyA3OC40MDk4TDQ2LjE1NzYgNzYuOTc2TDQ2Ljk3MTMgNzUuODc3OUw1MC4wNzggNzIuNzcxM0w0OC4xMDkzIDc0LjYyNjJMNDguMzgyNSA3NC4xODI0Wk0yOS4yODc3IDYyLjk5MDZMMjkuMDc3MiA2My4yMDExTDI4Ljg2NjcgNjMuMjcxM0wyOS4yODc3IDYyLjk5MDZaTTI5LjcwODggNTkuNDgyM0wyOS45MTkzIDU5LjI3MTlMMjkuNDk4MyA1OS41NTI1TDI5LjcwODggNTkuNDgyM1pNMjkuMDc3MiA2Ni41Njg3TDI4Ljg2NjcgNjYuNzc5MkwyOC42NTYyIDY2Ljg0OTRMMjkuMDc3MiA2Ni41Njg3Wk0yMi45NzI5IDY4Ljc0OEwyMy4xODM0IDY4LjUzNzVMMjIuNzYyNCA2OC44MTgxTDIyLjk3MjkgNjguNzQ4Wk0zLjgxNDdlLTA1IDkxLjc1OTNMMTMuMjQ5OSA3OS4xMzU1TDYuNTAwMSA4Ni4yNTk1TDMuODE0N2UtMDUgOTEuNzU5M1pNMTYuMDY4NSA4Ny45OTc0TDE3LjEzNzUgODcuMDY4N0wxNi41MzgyIDg3LjY2OEwxNi4wNjg1IDg3Ljk5NzRaTTIxLjc4NjkgNzkuMzM0NEwyMC43MTc5IDgwLjI2M0wyMS4xODc2IDc5LjkzMzdMMjEuNzg2OSA3OS4zMzQ0Wk0xMi4zNjA3IDk1LjA3NTVMMTMuNDI5OCA5NC4xNDY5TDEyLjgzMDQgOTQuNzQ2MkwxMi4zNjA3IDk1LjA3NTVaTTQyLjcxNzYgNTkuMzgwMUw0My4yNzg5IDU4LjgxODdMNDMuMDY4NCA1OS4xNjk2TDQyLjc4NzcgNTkuNDUwMkw0Mi4yOTY2IDU5LjgwMUw0Mi41NzcyIDU5LjM4MDFINDIuNzE3NlpNMjYuMzEyNCA0OS4zMTUyTDI0LjM1OTkgNTEuMjY3NkwyMy45OTYgNTEuMzkxOEwyMi44OTU2IDUyLjczMkwyNC40Nzk4IDUxLjM4NzVMMjUuNDU2IDUwLjQxMTNMMjYuMzEyNCA0OS4zMTUyWk0zOS4wNjg5IDYzLjMwOTdMMzguNTc3NyA2My42NjA2TDM5LjU2IDYyLjY3ODJMMzkuMDY4OSA2My4zMDk3Wk0yMC4zNTc0IDU1LjgwMzJMMTkuMzc1MSA1Ni43ODU2TDE5Ljg2NjIgNTYuNDM0N0wyMC4zNTc0IDU1LjgwMzJaTTM5LjkyOTcgNjQuMTk1TDQxLjU1MDQgNjIuMzc3OUw0MS41MzQgNjIuNTkwN0w0My41OTY3IDYwLjUyOEw0Mi45NzQ2IDYxLjI4MTFMNDAuODYyOCA2My41MjM4TDQwLjk2MSA2My4xNjM3TDM5LjkyOTcgNjQuMTk1Wk0yMi4zOTIxIDU1LjQ1N0wyMS4zOTk4IDU2LjU2OTZMMjIuMDMxMyA1NS45MzgxTDIxLjk3MTEgNTYuMTU4N0wyMy4yNjQyIDU0Ljc4NTRMMjMuNjQ1MSA1NC4zMjQzTDIyLjM4MjEgNTUuNTg3M0wyMi4zOTIxIDU1LjQ1N1pNNDAuNjQ3MyA5Mi40NDk4TDQ1LjA0ODUgODguMDQ4NUw0My4wMDY2IDkwLjQwNzlMNDAuODA2IDkyLjYwODVMMzcuMzQ2MyA5NS43NTA3TDM5LjkzODQgOTIuODQxMkw0MC42NDczIDkyLjQ0OThaTTE4LjUwNDIgNDguNzk3M0wxMS41NDU3IDU1Ljc1NThMMTAuNDI0OSA1Ni4zNzQ2TDYuMzI2ODQgNjAuOTc0NkwxMS43OTY3IDU2LjAwNjdMMTUuMjc1OSA1Mi41Mjc1TDE4LjUwNDIgNDguNzk3M1pNMzIuNzExMyA3OC4xMzlMMzEuMTEzMSA3OS43MzcyTDMwLjg0MzIgNzkuODY2OEwyOS45MTQ1IDgwLjkzNThMMzEuMTgzMyA3OS44MDc0TDMxLjk4MjMgNzkuMDA4M0wzMi43MTEzIDc4LjEzOVpNMjEuNzU3NyA5My45NTI1TDMxLjI4NTUgODQuMDM0NEwzMC44MzI0IDg0Ljg3NzdMNDIuNDk5OSA3My4yMTAyTDM4Ljc0MDggNzcuMjI5NUwyNi41NTUyIDg5LjY3NTNMMjcuNTkxNCA4OC4xMTg3TDIxLjc1NzcgOTMuOTUyNVpNOTguNTEzMiA5MC4wNTkxTDg5LjkyMjQgOTcuOTIyNEw5My41NzY5IDk0Ljk5NTNMOTguNTEzMiA5MC4wNTkxWk05Ny44NDU2IDgwLjIxMDVMOTkuNTAyNyA3OC42OTM3TDk4LjU1MDYgNzkuNjQ1OUw5Ny44NDU2IDgwLjIxMDVaTTg4LjU2NTYgNTYuNDU5OUw3OC45MjA1IDY1LjcwMDlMODIuMTI2MiA2My4zMDM2TDc4LjE0MTMgNjcuMjg4NUw3My43NTIyIDcwLjg2OTJMNzQuNzE5NSA3MC41MDgyTDY3LjcxNyA3OC4xMTdMNjMuOTkyIDgxLjAzMzZMNTguMDE0NiA4Ny4wMTFMNjMuNDI4OSA4MS43OTg4TDY2LjM4ODcgNzkuNDQ1NEw2OC4xMjEyIDc4LjUyMTNMNzAuNTc1NyA3NS42NjI1TDczLjAzMDIgNzIuODAzOEw3Ni4xOTQgNjkuNjRMNzguMzQzNCA2Ny40OTA2TDg0LjMyMDggNjEuNTEzMkw4Mi42NTc1IDYyLjc3MjNMODguNTY1NiA1Ni40NTk5Wk04NS4xODkzIDY3LjAzNzVMODMuNzMwNCA2OC4zNTZMODQuMzU2MSA2Ny44NzA3TDg1LjE4OTMgNjcuMDM3NVpNOTAuNzk2OSA1OC4yMDIyTDk5LjI3MjUgNTAuNTQxOEw5NC40MzE3IDU1LjM4MjZMOTAuNzk2OSA1OC4yMDIyWk03OS4zNzcgNzYuMjE3Mkw3Ny45MTgyIDc3LjUzNTdMNzguNTQzOCA3Ny4wNTA0TDc5LjM3NyA3Ni4yMTcyWk01OS40OTIyIDkxLjcyNTNMNTYuNDAxMSA5NC4xMjMxTDYwLjAwNDkgOTAuODY1OUw2My42MDg3IDg3LjYwODdMNTkuNDkyMiA5MS43MjUzWk02My44ODMzIDc1LjQxNTNMNDYgOTIuMzg5Nkw0OS42ODg0IDg5LjExOTNMNTMuMzc2NyA4NS44NDkxTDYzLjg4MzMgNzUuNDE1M1pNNzEuNjA2MyA1NS4wNzY1TDY5LjY2MDkgNTcuMDIxOUw2OS4zNDc1IDU3LjE5NDlMNjguMjAxOCA1OC40ODFMNjkuNzMxIDU3LjA5MjFMNzAuNzAzNyA1Ni4xMTk0TDcxLjYwNjMgNTUuMDc2NVpNNTUuMTQwNSA3MS42ODU3TDYxLjQxMzEgNjUuNDEzMUw1Ny45NTggNjkuMTI2N0w1NS4xNDA1IDcxLjY4NTdaTTY1LjgzOTYgNjkuNDQ5N0w2MS43MTM4IDczLjcxMzhMNjQuMjMwOCA3MS4xOTY4TDYzLjc2MzcgNzEuODQ4NEw2OS4wMzEzIDY2LjQ4ODZMNzAuNjYzMiA2NC43NjQ1TDY1LjYyOTIgNjkuNzk4NUw2NS44Mzk2IDY5LjQ0OTdaTTUzLjAwMzQgNjUuNDk1NUw1OC4yMjU4IDU5Ljg5MTRMNTguMDU1OCA2MC40NDMxTDY0LjU1MTcgNTMuOTQ3Mkw2Mi41MTM2IDU2LjIzOThMNTUuNzg0MSA2My4yMjM4TDU2LjI1MTMgNjIuMjQ3NUw1My4wMDM0IDY1LjQ5NTVaTTk3LjA5OTcgNzEuMjAzMkw3OS42NTE0IDg4LjY1MTVMODYuNzY5NyA4MC44MTRMOTcuMDk5NyA3MS4yMDMyWk0zNS4xODQ4IDU2LjI1MTNMMzEuOTMgNTkuOTAwNkwzNC4wMDEyIDU3LjgyOTRMMzMuODA0IDU4LjU1MjdMMzguMDQ1MSA1NC4wNDg1TDM5LjI5NDUgNTIuNTM2MUwzNS4xNTE5IDU2LjY3ODdMMzUuMTg0OCA1Ni4yNTEzWk02Ni44NzEyIDI2LjI0NzFMNzguMTkwNyAxNC4zMDk5TDc3LjcyNDQgMTUuMzk0TDkxLjY3ODQgMS40Mzk5TDg3LjIzMyA2LjI5NzE1TDcyLjcwOTYgMjEuMjMyM0w3My44NDgyIDE5LjI3MDFMNjYuODcxMiAyNi4yNDcxWk0yOC4wNDczIDY4LjIwNjhMMjAuNDM1NSA3Ni4zNzVMMjUuMTY5NSA3MS42NDFMMjQuNDg4NCA3My4wNjM5TDM0LjI5NyA2Mi44ODQ0TDM3LjI2NzUgNTkuNTQyOUwyNy43OTk1IDY5LjAxMDlMMjguMDQ3MyA2OC4yMDY4Wk04Ljk0MDY3IDM5LjU2NThMMTQuMTYzMSAzMy45NjE3TDEzLjk5MyAzNC41MTM0TDIwLjQ4ODkgMjguMDE3NUwxOC40NTA5IDMwLjMxMDFMMTEuNzIxMyAzNy4yOTQxTDEyLjE4ODYgMzYuMzE3OEw4Ljk0MDY3IDM5LjU2NThaTTk5Ljc0MDMgMjZMODggMzcuNzQwNEw5My4yNzM1IDMyLjk1MDhMOTkuNzQwMyAyNlpNMS45MzM4OCA4LjA4NzQzTDQuNzc3NjUgNS4wNDk3NEw0LjY3ODU2IDUuMzQyNzVMOC4yMDc0MyAxLjgxMzg4TDcuMDk1NzggMy4wNTQ4MUwzLjQzNTUgNi44NDQzN0wzLjY5ODMyIDYuMzIyOTlMMS45MzM4OCA4LjA4NzQzWk01NC40NDg1IDQ0LjIxMUw0OC41OTg1IDUwLjA2MUw0Ny42NTYzIDUwLjU4MTNMNDQuMjExIDU0LjQ0ODVMNDguODA5NSA1MC4yNzJMNTEuNzM0NSA0Ny4zNDdMNTQuNDQ4NSA0NC4yMTFaIiAvPgo8L3BhdHRlcm4+CjwvZGVmcz48ZyBjbGFzcz0iWkdGMFlYTmxkQT09Ij48ZyBjbGFzcz0ic2hhcGUiID48cGF0aCBkPSJNIDEyIDUyIEMgMTIgMjggNjIgMjggNjcgMjggQyA3MyAyOCAxMjIgMjggMTIyIDUyIFYgMTIyIEMgMTIyIDE0NiA3MyAxNDYgNjcgMTQ2IEMgNjIgMTQ2IDEyIDE0NiAxMiAxMjIgViA1MiBaIiBjbGFzcz0ic2hhcGUgc3Ryb2tlLUIxIGZpbGwtQUE0IiBzdHlsZT0ic3Ryb2tlLXdpZHRoOjI7IiAvPjxwYXRoIGQ9Ik0xMC4wMDAwODkgNTAuMzQwMTI5IE0xMC4wMDAwODkgNTAuMzQwMTI5IEMxMi45NjM4ODQgMjcuNjgwMjQ2LCA2My4xNjMxNzEgMjcuNDM5ODc2LCA2Ny40MDUzMTIgMjguODU3MjYzIE04LjI2OTkyNCA0OS41NDY1MzUgQzExLjQyOTI4NiAyOS4wNzkyODksIDYwLjM3ODMzNiAyOC42NjE0OTYsIDY3LjQ5MDQxOSAyNi4wMzM3OTcgTTY3LjQ5MDQxOSAyNi4wMzM3OTcgQzczLjE3Mjc4NCAyOC40ODgwNTMsIDEyMC44MzkyNTEgMjkuNTA1NjMxLCAxMjIuODc4OTA1IDUzLjYyNzM5OCBNNjUuODQwNDY1IDI1LjExMDM1MyBDNzAuODEzMjA4IDI5LjM3MzYzMCwgMTIzLjUzNzgyMiAzMC4yMzgwNTAsIDEyMi41NzAyNTcgNTMuOTIwODgwIE0xMjEuOTM4MTczIDU0LjU1MDQ2NyBDMTIyLjMzNTExNSA2OS4zNTQ3OTIsIDEyMS45MTgzODIgODQuMDgyMDU3LCAxMjAuODUwMDIyIDEyMC41MzExMjcgTTEyMS43MjY5ODIgNTQuMTgyMDA5IEMxMjQuMTEzNTEzIDc1LjA1Njk1OCwgMTIzLjAxNzUzNCA5NS42NTU0NDksIDEyMi40NzM3NDMgMTIxLjE1ODIwMyBNMTIyLjU3MDI1NyAxMjIgQzEyMS41OTY0NzYgMTQ3LjUxODI1MSwgNzIuNTI1ODQ4IDE0Ni4yMjg5MTgsIDY2LjI2MzQ0NiAxNDcuODM2NDU2IE0xMjAuNjgzNDA0IDEyMS43MTI1MzUgQzEyMy45ODk4MDcgMTQ1Ljk4MDg4NSwgNzUuMzQ3Mzc0IDE0Ni4xMDQ2MTAsIDY2Ljc4MDMxNiAxNDYuNjU5NTQ2IE02Ni43ODAzMTYgMTQ2LjY1OTU0NiBDNjEuODc0Nzc1IDE0NS4yNzkzOTUsIDExLjcxODUzMiAxNDcuMjYwNzY2LCAxMS43MzYxODYgMTIzLjQ1MTQ2OSBNNjUuMjUxOTM1IDE0Ni4xODc4ODAgQzYwLjQ5MjM5OSAxNDcuNTk0MTU2LCAxMy41MzgwNTIgMTQ0LjMxODc3NCwgMTIuMjI0OTk1IDEyMi43Mzk3NTAgTTEwLjkxNzExNyAxMjIuMjAwMzQxIEM5LjAxMTczMyAxMDAuODU3NDg0LCAxMi43ODg3NzQgNzUuNjExOTQ2LCAxMS42NDY2MTUgNTMuMDQ1NTUxIE0xMi42ODc3NzMgMTIzLjUwNTk5MSBDMTEuMjkxNDU2IDk1LjIzMDk1OCwgMTMuMDMzMDg0IDY3LjczODI3NCwgMTEuNzUxMDY3IDUxLjAzMjAzOSBNMTIuNDE2MTg2IDUxLjgyNTI2OCBDMTEuOTA4ODMxIDUxLjcwOTgzOSwgMTAuOTI3OTI5IDUxLjIxMjEzNywgMTAuMTUwNzk4IDUwLjIxMjAxOSBNMTIuMjAyODM1IDUxLjg4NTAzMiBDMTEuNDEzOTAyIDUxLjUyMzQzMCwgMTAuOTE1MTQxIDUwLjkwOTk1MiwgOS45NTEzNTggNTAuMjgwNzUzIiBjbGFzcz0ic2hhcGUgc3Ryb2tlLUIxIGZpbGwtQUE0IiBzdHlsZT0ic3Ryb2tlLXdpZHRoOjI7IiAvPjxwYXRoIGQ9Ik0gMTIgNTIgQyAxMiAyOCA2MiAyOCA2NyAyOCBDIDczIDI4IDEyMiAyOCAxMjIgNTIgViAxMjIgQyAxMjIgMTQ2IDczIDE0NiA2NyAxNDYgQyA2MiAxNDYgMTIgMTQ2IDEyIDEyMiBWIDUyIFoiIGNsYXNzPSIgc2tldGNoLW92ZXJsYXktQUE0IiAvPjxwYXRoIGQ9Ik0xMC4wMDAwODkgNTAuMzQwMTI5IE0xMC4wMDAwODkgNTAuMzQwMTI5IEMxMi45NjM4ODQgMjcuNjgwMjQ2LCA2My4xNjMxNzEgMjcuNDM5ODc2LCA2Ny40MDUzMTIgMjguODU3MjYzIE04LjI2OTkyNCA0OS41NDY1MzUgQzExLjQyOTI4NiAyOS4wNzkyODksIDYwLjM3ODMzNiAyOC42NjE0OTYsIDY3LjQ5MDQxOSAyNi4wMzM3OTcgTTY3LjQ5MDQxOSAyNi4wMzM3OTcgQzczLjE3Mjc4NCAyOC40ODgwNTMsIDEyMC44MzkyNTEgMjkuNTA1NjMxLCAxMjIuODc4OTA1IDUzLjYyNzM5OCBNNjUuODQwNDY1IDI1LjExMDM1MyBDNzAuODEzMjA4IDI5LjM3MzYzMCwgMTIzLjUzNzgyMiAzMC4yMzgwNTAsIDEyMi41NzAyNTcgNTMuOTIwODgwIE0xMjEuOTM4MTczIDU0LjU1MDQ2NyBDMTIyLjMzNTExNSA2OS4zNTQ3OTIsIDEyMS45MTgzODIgODQuMDgyMDU3LCAxMjAuODUwMDIyIDEyMC41MzExMjcgTTEyMS43MjY5ODIgNTQuMTgyMDA5IEMxMjQuMTEzNTEzIDc1LjA1Njk1OCwgMTIzLjAxNzUzNCA5NS42NTU0NDksIDEyMi40NzM3NDMgMTIxLjE1ODIwMyBNMTIyLjU3MDI1NyAxMjIgQzEyMS41OTY0NzYgMTQ3LjUxODI1MSwgNzIuNTI1ODQ4IDE0Ni4yMjg5MTgsIDY2LjI2MzQ0NiAxNDcuODM2NDU2IE0xMjAuNjgzNDA0IDEyMS43MTI1MzUgQzEyMy45ODk4MDcgMTQ1Ljk4MDg4NSwgNzUuMzQ3Mzc0IDE0Ni4xMDQ2MTAsIDY2Ljc4MDMxNiAxNDYuNjU5NTQ2IE02Ni43ODAzMTYgMTQ2LjY1OTU0NiBDNjEuODc0Nzc1IDE0NS4yNzkzOTUsIDExLjcxODUzMiAxNDcuMjYwNzY2LCAxMS43MzYxODYgMTIzLjQ1MTQ2OSBNNjUuMjUxOTM1IDE0Ni4xODc4ODAgQzYwLjQ5MjM5OSAxNDcuNTk0MTU2LCAxMy41MzgwNTIgMTQ0LjMxODc3NCwgMTIuMjI0OTk1IDEyMi43Mzk3NTAgTTEwLjkxNzExNyAxMjIuMjAwMzQxIEM5LjAxMTczMyAxMDAuODU3NDg0LCAxMi43ODg3NzQgNzUuNjExOTQ2LCAxMS42NDY2MTUgNTMuMDQ1NTUxIE0xMi42ODc3NzMgMTIzLjUwNTk5MSBDMTEuMjkxNDU2IDk1LjIzMDk1OCwgMTMuMDMzMDg0IDY3LjczODI3NCwgMTEuNzUxMDY3IDUxLjAzMjAzOSBNMTIuNDE2MTg2IDUxLjgyNTI2OCBDMTEuOTA4ODMxIDUxLjcwOTgzOSwgMTAuOTI3OTI5IDUxLjIxMjEzNywgMTAuMTUwNzk4IDUwLjIxMjAxOSBNMTIuMjAyODM1IDUxLjg4NTAzMiBDMTEuNDEzOTAyIDUxLjUyMzQzMCwgMTAuOTE1MTQxIDUwLjkwOTk1MiwgOS45NTEzNTggNTAuMjgwNzUzIiBjbGFzcz0iIHNrZXRjaC1vdmVybGF5LUFBNCBza2V0Y2gtb3ZlcmxheS1BQTQiIC8+PHBhdGggZD0iTSAxMiA1MiBDIDEyIDc2IDYyIDc2IDY3IDc2IEMgNzMgNzYgMTIyIDc2IDEyMiA1MiIgY2xhc3M9InNoYXBlIHN0cm9rZS1CMSBmaWxsLUFBNCIgc3R5bGU9InN0cm9rZS13aWR0aDoyOyIgLz48cGF0aCBkPSJNMTAuMDAwMDg5IDUwLjM0MDEyOSBNMTAuMDAwMDg5IDUwLjM0MDEyOSBDMTIuOTYzODg0IDc1LjY4MDI0NiwgNjMuMTYzMTcxIDc1LjQzOTg3NiwgNjcuNDA1MzEyIDc2Ljg1NzI2MyBNOC4yNjk5MjQgNDkuNTQ2NTM1IEMxMS40MjkyODYgNzcuMDc5Mjg5LCA2MC4zNzgzMzYgNzYuNjYxNDk2LCA2Ny40OTA0MTkgNzQuMDMzNzk3IE02Ny40OTA0MTkgNzQuMDMzNzk3IEM3My4xNzI3ODQgNzYuNDg4MDUzLCAxMjAuODM5MjUxIDc3LjUwNTYzMSwgMTIyLjg3ODkwNSA1My42MjczOTggTTY1Ljg0MDQ2NSA3My4xMTAzNTMgQzcwLjgxMzIwOCA3Ny4zNzM2MzAsIDEyMy41Mzc4MjIgNzguMjM4MDUwLCAxMjIuNTcwMjU3IDUzLjkyMDg4MCIgY2xhc3M9InNoYXBlIHN0cm9rZS1CMSBmaWxsLUFBNCIgc3R5bGU9InN0cm9rZS13aWR0aDoyOyIgLz48cGF0aCBkPSJNIDEyIDUyIEMgMTIgNzYgNjIgNzYgNjcgNzYgQyA3MyA3NiAxMjIgNzYgMTIyIDUyIiBjbGFzcz0iIHNrZXRjaC1vdmVybGF5LUFBNCIgLz48cGF0aCBkPSJNMTAuMDAwMDg5IDUwLjM0MDEyOSBNMTAuMDAwMDg5IDUwLjM0MDEyOSBDMTIuOTYzODg0IDc1LjY4MDI0NiwgNjMuMTYzMTcxIDc1LjQzOTg3NiwgNjcuNDA1MzEyIDc2Ljg1NzI2MyBNOC4yNjk5MjQgNDkuNTQ2NTM1IEMxMS40MjkyODYgNzcuMDc5Mjg5LCA2MC4zNzgzMzYgNzYuNjYxNDk2LCA2Ny40OTA0MTkgNzQuMDMzNzk3IE02Ny40OTA0MTkgNzQuMDMzNzk3IEM3My4xNzI3ODQgNzYuNDg4MDUzLCAxMjAuODM5MjUxIDc3LjUwNTYzMSwgMTIyLjg3ODkwNSA1My42MjczOTggTTY1Ljg0MDQ2NSA3My4xMTAzNTMgQzcwLjgxMzIwOCA3Ny4zNzM2MzAsIDEyMy41Mzc4MjIgNzguMjM4MDUwLCAxMjIuNTcwMjU3IDUzLjkyMDg4MCIgY2xhc3M9IiBza2V0Y2gtb3ZlcmxheS1BQTQgc2tldGNoLW92ZXJsYXktQUE0IiAvPjwvZz48dGV4dCB4PSI2Ny4wMDAwMDAiIHk9IjEwNC41MDAwMDAiIGZpbGw9IiMwQTBGMjUiIGNsYXNzPSJ0ZXh0LWJvbGQgZmlsbC1OMSIgc3R5bGU9InRleHQtYW5jaG9yOm1pZGRsZTtmb250LXNpemU6MTZweCI+RGF0YXNldDwvdGV4dD48L2c+PGcgY2xhc3M9ImMzQnNhWFE9Ij48ZyBjbGFzcz0ic2hhcGUiID48cGF0aCBkPSJNIDMzOSAxMzMgQyAzMzggMTMzIDMzNyAxMzMgMzM2IDEzMyBMIDE5MyA4OCBDIDE5MSA4OCAxOTEgODcgMTkzIDg2IEwgMzM2IDQxIEMgMzM4IDQxIDM0MCA0MSAzNDEgNDEgTCA0ODQgODYgQyA0ODYgODYgNDg2IDg3IDQ4NCA4OCBMIDM0MiAxMzMgQyAzNDEgMTMzIDM0MCAxMzMgMzM5IDEzMyBaIiBjbGFzcz0ic2hhcGUgc3Ryb2tlLUIxIGZpbGwtTjQiIHN0eWxlPSJzdHJva2Utd2lkdGg6MjsiIC8+PHBhdGggZD0iTTMzNy4wMDAwODkgMTMxLjM0MDEyOSBNMzM3LjAwMDA4OSAxMzEuMzQwMTI5IEMzMzguOTYzODg0IDEzMi42ODAyNDYsIDMzOC4xNjMxNzEgMTMyLjQzOTg3NiwgMzM2LjQwNTMxMiAxMzMuODU3MjYzIE0zMzUuMjY5OTI0IDEzMC41NDY1MzUgQzMzNy40MjkyODYgMTM0LjA3OTI4OSwgMzM1LjM3ODMzNiAxMzMuNjYxNDk2LCAzMzYuNDkwNDE5IDEzMS4wMzM3OTcgTTMzNi45Nzg0NzMgMTI5Ljg3MzA0OCBDMjg3Ljk0Mjg3MCAxMTQuODMwNTAyLCAyMzYuMTYwOTQ2IDEwMi4xMzUzOTIsIDE5NC41MzY3MDQgODYuMjUwNTY2IE0zMzUuNjcwMDI0IDEzMS43MjkwNzEgQzI4NC41OTUwNzQgMTE4LjAxMjIyNCwgMjM0LjQxMzMwNCAxMDIuNDYyNzE3LCAxOTMuMjk3Njc3IDg3LjIwMDcyNSBNMTkzIDg4IEMxODkuNTMxMTI3IDg4LjA1NDYyNSwgMTkxLjgxMzc1MyA4Ny43MDQ0ODcsIDE5My40ODQwNjQgODQuMjc5NzY0IE0xOTEuMzEzNDQ5IDg4LjUyMjI1OSBDMTkwLjczMzE5OSA4OS4yODgwMzUsIDE5MC43NTg3MTQgODQuODk1NTA4LCAxOTUuNDczMTQ3IDg3LjMxNjAwMiBNMTk2Ljk5MTM5OSA4Ni44NDE4NTAgQzIzMS44MzQyNzIgNzMuODA3MTE3LCAyNjguMzAwNjcwIDYzLjMyOTEzMywgMzM2LjUyNzYzNyA0Mi41OTE4NDUgTTE5NS4zNDEyNDAgODguMDQxNzM2IEMyMzYuNTkxMzgxIDcyLjk3ODcwNSwgMjc4LjU2Mzg3NCA2MC4xMTAwMDEsIDMzNS4yMzU4MDkgNDAuNzY0MTY2IE0zMzYgNDEgQzMzNi43OTM5MTkgNDIuMjc1MzI1LCAzNDEuMjMwNDQxIDM5LjY1NTAxOSwgMzQxLjE3OTk5NiA0MS41OTE4MDAgTTMzNi40MzU5MTMgNDIuOTk2NDQ2IEMzMzcuMzI1NzM5IDM4Ljc0ODc3NCwgMzQxLjEwMTg1MSA0My40NzAwNzUsIDMzOS4wNjA2MDIgMzkuMzY1MTUzIE0zNDAuODcxNjMzIDM5LjYxODg1MSBDMzY4LjkwOTM2NyA0OS4wMzQyMzYsIDQwNC4wOTIxOTYgNTYuODU5NDc2LCA0ODQuMjM3NDUwIDg3Ljk3NTEwOSBNMzM4LjYxODg4OSAzOS40MzE1OTQgQzM4NC45NDYxMDggNTguMDMyNzUyLCA0MzIuMzAyODY3IDczLjQzNjM5NiwgNDg0LjYxNzQxOSA4NS40MzYzMTQgTTQ4NCA4NiBDNDg1LjA3Njk2NyA4Ni4yNzU0NTIsIDQ4Ni4zNDczMTcgODguMzY1MjAyLCA0ODQuNzA4NDM5IDg5LjA4NTg1MCBNNDgzLjY4MDY4NCA4NC4zNDMzMjEgQzQ4Ny42NTk2NjIgODQuNTgxMDEzLCA0ODUuMTIyMjI1IDg1LjkzMDQ4NSwgNDgyLjA2ODgyNCA4OC4yNDk5NzcgTTQ4My44NTI2OTUgODkuNDcyNDgzIEM0NDcuNjUxNzE0IDk5LjM3MDk1OSwgNDE0LjQxMTA5NCAxMDguODIxNzkzLCAzNDAuNTA1NDk4IDEzMS45MjgxODkgTTQ4Mi40NDIzMDEgODguMzQ3OTg1IEM0NDkuNzUyNzgxIDEwMS4wNzE3NzEsIDQxNi42MDIxNzMgMTEwLjk4MzAwOSwgMzQxLjk3OTg1OSAxMzIuODA4NzIzIE0zNDIgMTMzIEMzNDEuMTczNzI3IDEzMS4wMTkzNjAsIDM0MC41NjcyMTMgMTMyLjk0MDAxNiwgMzQwLjc1MzQ3NiAxMzEuMDg1NjQ1IE0zNDIuNTU5OTY5IDEzMS4zMDg0ODkgQzM0MC41MjM2NjcgMTM0Ljk0MzMyMiwgMzQxLjEyMDI1MCAxMzMuNjExMDg5LCAzNDAuMzk5NzkxIDEzMi4zMjgyNjEgTTM0MC4yMDI1MDMgMTMxLjk5MjA1MyBDMzM5LjcwOTcxNSAxMzIuMDM4MTEzLCAzMzkuMDEwMjQwIDEzMS43Mjk5NjIsIDMzNi44NTg4OTYgMTMxLjA2NTMzNiBNMzQwLjUxODYyOSAxMzIuMjc4MTA3IEMzMzkuNDEwODgxIDEzMi4wMjcwODUsIDMzOC40NTM4MzkgMTMxLjgzMzIwNCwgMzM3LjEwNTM5MSAxMzEuNDI2NjE2IiBjbGFzcz0ic2hhcGUgc3Ryb2tlLUIxIGZpbGwtTjQiIHN0eWxlPSJzdHJva2Utd2lkdGg6MjsiIC8+PHBhdGggZD0iTSAzMzkgMTMzIEMgMzM4IDEzMyAzMzcgMTMzIDMzNiAxMzMgTCAxOTMgODggQyAxOTEgODggMTkxIDg3IDE5MyA4NiBMIDMzNiA0MSBDIDMzOCA0MSAzNDAgNDEgMzQxIDQxIEwgNDg0IDg2IEMgNDg2IDg2IDQ4NiA4NyA0ODQgODggTCAzNDIgMTMzIEMgMzQxIDEzMyAzNDAgMTMzIDMzOSAxMzMgWiIgY2xhc3M9IiBza2V0Y2gtb3ZlcmxheS1ONCIgLz48cGF0aCBkPSJNMzM3LjAwMDA4OSAxMzEuMzQwMTI5IE0zMzcuMDAwMDg5IDEzMS4zNDAxMjkgQzMzOC45NjM4ODQgMTMyLjY4MDI0NiwgMzM4LjE2MzE3MSAxMzIuNDM5ODc2LCAzMzYuNDA1MzEyIDEzMy44NTcyNjMgTTMzNS4yNjk5MjQgMTMwLjU0NjUzNSBDMzM3LjQyOTI4NiAxMzQuMDc5Mjg5LCAzMzUuMzc4MzM2IDEzMy42NjE0OTYsIDMzNi40OTA0MTkgMTMxLjAzMzc5NyBNMzM2Ljk3ODQ3MyAxMjkuODczMDQ4IEMyODcuOTQyODcwIDExNC44MzA1MDIsIDIzNi4xNjA5NDYgMTAyLjEzNTM5MiwgMTk0LjUzNjcwNCA4Ni4yNTA1NjYgTTMzNS42NzAwMjQgMTMxLjcyOTA3MSBDMjg0LjU5NTA3NCAxMTguMDEyMjI0LCAyMzQuNDEzMzA0IDEwMi40NjI3MTcsIDE5My4yOTc2NzcgODcuMjAwNzI1IE0xOTMgODggQzE4OS41MzExMjcgODguMDU0NjI1LCAxOTEuODEzNzUzIDg3LjcwNDQ4NywgMTkzLjQ4NDA2NCA4NC4yNzk3NjQgTTE5MS4zMTM0NDkgODguNTIyMjU5IEMxOTAuNzMzMTk5IDg5LjI4ODAzNSwgMTkwLjc1ODcxNCA4NC44OTU1MDgsIDE5NS40NzMxNDcgODcuMzE2MDAyIE0xOTYuOTkxMzk5IDg2Ljg0MTg1MCBDMjMxLjgzNDI3MiA3My44MDcxMTcsIDI2OC4zMDA2NzAgNjMuMzI5MTMzLCAzMzYuNTI3NjM3IDQyLjU5MTg0NSBNMTk1LjM0MTI0MCA4OC4wNDE3MzYgQzIzNi41OTEzODEgNzIuOTc4NzA1LCAyNzguNTYzODc0IDYwLjExMDAwMSwgMzM1LjIzNTgwOSA0MC43NjQxNjYgTTMzNiA0MSBDMzM2Ljc5MzkxOSA0Mi4yNzUzMjUsIDM0MS4yMzA0NDEgMzkuNjU1MDE5LCAzNDEuMTc5OTk2IDQxLjU5MTgwMCBNMzM2LjQzNTkxMyA0Mi45OTY0NDYgQzMzNy4zMjU3MzkgMzguNzQ4Nzc0LCAzNDEuMTAxODUxIDQzLjQ3MDA3NSwgMzM5LjA2MDYwMiAzOS4zNjUxNTMgTTM0MC44NzE2MzMgMzkuNjE4ODUxIEMzNjguOTA5MzY3IDQ5LjAzNDIzNiwgNDA0LjA5MjE5NiA1Ni44NTk0NzYsIDQ4NC4yMzc0NTAgODcuOTc1MTA5IE0zMzguNjE4ODg5IDM5LjQzMTU5NCBDMzg0Ljk0NjEwOCA1OC4wMzI3NTIsIDQzMi4zMDI4NjcgNzMuNDM2Mzk2LCA0ODQuNjE3NDE5IDg1LjQzNjMxNCBNNDg0IDg2IEM0ODUuMDc2OTY3IDg2LjI3NTQ1MiwgNDg2LjM0NzMxNyA4OC4zNjUyMDIsIDQ4NC43MDg0MzkgODkuMDg1ODUwIE00ODMuNjgwNjg0IDg0LjM0MzMyMSBDNDg3LjY1OTY2MiA4NC41ODEwMTMsIDQ4NS4xMjIyMjUgODUuOTMwNDg1LCA0ODIuMDY4ODI0IDg4LjI0OTk3NyBNNDgzLjg1MjY5NSA4OS40NzI0ODMgQzQ0Ny42NTE3MTQgOTkuMzcwOTU5LCA0MTQuNDExMDk0IDEwOC44MjE3OTMsIDM0MC41MDU0OTggMTMxLjkyODE4OSBNNDgyLjQ0MjMwMSA4OC4zNDc5ODUgQzQ0OS43NTI3ODEgMTAxLjA3MTc3MSwgNDE2LjYwMjE3MyAxMTAuOTgzMDA5LCAzNDEuOTc5ODU5IDEzMi44MDg3MjMgTTM0MiAxMzMgQzM0MS4xNzM3MjcgMTMxLjAxOTM2MCwgMzQwLjU2NzIxMyAxMzIuOTQwMDE2LCAzNDAuNzUzNDc2IDEzMS4wODU2NDUgTTM0Mi41NTk5NjkgMTMxLjMwODQ4OSBDMzQwLjUyMzY2NyAxMzQuOTQzMzIyLCAzNDEuMTIwMjUwIDEzMy42MTEwODksIDM0MC4zOTk3OTEgMTMyLjMyODI2MSBNMzQwLjIwMjUwMyAxMzEuOTkyMDUzIEMzMzkuNzA5NzE1IDEzMi4wMzgxMTMsIDMzOS4wMTAyNDAgMTMxLjcyOTk2MiwgMzM2Ljg1ODg5NiAxMzEuMDY1MzM2IE0zNDAuNTE4NjI5IDEzMi4yNzgxMDcgQzMzOS40MTA4ODEgMTMyLjAyNzA4NSwgMzM4LjQ1MzgzOSAxMzEuODMzMjA0LCAzMzcuMTA1MzkxIDEzMS40MjY2MTYiIGNsYXNzPSIgc2tldGNoLW92ZXJsYXktTjQgc2tldGNoLW92ZXJsYXktTjQiIC8+PC9nPjx0ZXh0IHg9IjMzOS4wMDAwMDAiIHk9IjkyLjUwMDAwMCIgZmlsbD0iIzBBMEYyNSIgY2xhc3M9InRleHQtYm9sZCBmaWxsLU4xIiBzdHlsZT0idGV4dC1hbmNob3I6bWlkZGxlO2ZvbnQtc2l6ZToxNnB4Ij5UcmFpbi9UZXN0IFNwbGl0PC90ZXh0PjwvZz48ZyBjbGFzcz0iZEhKaGFXND0iPjxnIGNsYXNzPSJzaGFwZSIgPjxwYXRoIGQ9Ik0tMS42MDAzMTAgLTAuNTc4Mzc5IEwxNTguMDQ1NTUxIDEuODExMDMwIEwxNTcuMjUzNjk3IDY0LjIzNDA3MiBMMC45MjU1NTYgNjcuNTMyNDgzIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSg1NjYuMDAwMDAwIDEyLjAwMDAwMCkiIGNsYXNzPSJzaGFwZSBzdHJva2UtQjEgZmlsbC1CNiIgc3R5bGU9InN0cm9rZS13aWR0aDoyOyIgLz48cGF0aCBkPSJNMC44NTcyNjMgMC45NjM4ODQgQzMxLjA4MDk1MSAwLjUyNjgzMCwgNjIuMjQxMjg3IC0yLjM2NjUwNiwgMTU2LjIwNjQwNSAwLjM5MjMzNSBNLTAuNjQ4NjY1IDAuMjY0NTk4IEMzNS4xOTE2OTkgLTAuNTQxODg4LCA2OS41OTA4ODYgLTEuMTExNTYwLCAxNTYuNDE5NjI1IDAuNzUyODE1IE0xNTguNTM2NzA0IC0xLjc0OTQzMyBDMTU3LjQ4OTQzMSAxNS41ODU0MTAsIDE1OC4xODA5NjcgMjcuMDY5NTEzLCAxNTguMzkwNTQ3IDY1LjEzMDY0NSBNMTU3LjI5NzY3NyAtMC43OTkyNzQgQzE1Ny42NTc1NjAgMTYuODU0MDAyLCAxNTYuNjgxMDkxIDM1LjQ1NTU1MiwgMTU3LjQwNjg3NiA2Ni4zNTIyNDMgTTE1OC4wNTI4MDEgNjUuNzg2NTU5IEMxMjQuMTY5ODQ5IDY4LjkxMzI0NCwgODcuNTk1MjQ4IDY4LjM2OTcyMCwgMS44MzY0NTYgNjUuNTk2NDc2IE0xNTYuMDU2NTczIDY1Ljg1NjI2NyBDOTcuODkzODUxIDY2LjYyMzIyMCwgMzkuNzU5MzcxIDY2LjM1MTc1NiwgMC45Mzg5NDkgNjYuMDQxODQ0IE0tMC43MjA2MDQgNjUuNzE4NTMyIEMwLjMwMjc5NyA0NS41NDIyMDQsIC0xLjQyOTYzNiAyOC4zMjExNjYsIDAuNTkxODAwIC0xLjIwNjA4MCBNMC4yMTc5NTYgNjYuOTk4MjIzIEMtMS41ODc4NTAgNDEuMzM3NDg3LCAtMS4wODE3OTUgMTcuMDgyMzYyLCAwLjQ0MDc0MCAwLjk4ODAzMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNTY2LjAwMDAwMCAxMi4wMDAwMDApIiBjbGFzcz0ic2hhcGUgc3Ryb2tlLUIxIGZpbGwtQjYiIHN0eWxlPSJzdHJva2Utd2lkdGg6MjsiIC8+PHJlY3Qgd2lkdGg9IjE1Ny4wMDAwMDAiIGhlaWdodD0iNjYuMDAwMDAwIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSg1NjYuMDAwMDAwIDEyLjAwMDAwMCkiIGNsYXNzPSIgc2tldGNoLW92ZXJsYXktQjYiIC8+PC9nPjx0ZXh0IHg9IjY0NC41MDAwMDAiIHk9IjUwLjUwMDAwMCIgZmlsbD0iIzBBMEYyNSIgY2xhc3M9InRleHQtYm9sZCBmaWxsLU4xIiBzdHlsZT0idGV4dC1hbmNob3I6bWlkZGxlO2ZvbnQtc2l6ZToxNnB4Ij5UcmFpbmluZyBEYXRhPC90ZXh0PjwvZz48ZyBjbGFzcz0iZEdWemRBPT0iPjxnIGNsYXNzPSJzaGFwZSIgPjxwYXRoIGQ9Ik0tMS42MDAzMTAgLTAuNTc4Mzc5IEwxMjcuMDQ1NTUxIDEuODExMDMwIEwxMjYuMjUzNjk3IDY0LjIzNDA3MiBMMC45MjU1NTYgNjcuNTMyNDgzIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSg3OTMuMDAwMDAwIDk4LjAwMDAwMCkiIGNsYXNzPSJzaGFwZSBzdHJva2UtQjEgZmlsbC1CNiIgc3R5bGU9InN0cm9rZS13aWR0aDoyOyIgLz48cGF0aCBkPSJNMC44NTcyNjMgMC45NjM4ODQgQzI0Ljg4MDgxMiAwLjY1MjQ3NywgNDkuODQxMDA5IC0yLjI0MDg1OSwgMTI1LjIwNjQwNSAwLjM5MjMzNSBNLTAuNjQ4NjY1IDAuMjY0NTk4IEMyOC4zMjk3OTAgLTAuMjc0MjI0LCA1NS44NjcwNjkgLTAuODQzODk2LCAxMjUuNDE5NjI1IDAuNzUyODE1IE0xMjcuNTM2NzA0IC0xLjc0OTQzMyBDMTI2LjQ4OTQzMSAxNS41ODU0MTAsIDEyNy4xODA5NjcgMjcuMDY5NTEzLCAxMjcuMzkwNTQ3IDY1LjEzMDY0NSBNMTI2LjI5NzY3NyAtMC43OTkyNzQgQzEyNi42NTc1NjAgMTYuODU0MDAyLCAxMjUuNjgxMDkxIDM1LjQ1NTU1MiwgMTI2LjQwNjg3NiA2Ni4zNTIyNDMgTTEyNy4wNTI4MDEgNjUuNzg2NTU5IEM5OS44NTU2OTYgNjguMjk5OTA0LCA2OS45NjY5NDIgNjcuNzU2Mzc5LCAxLjgzNjQ1NiA2NS41OTY0NzYgTTEyNS4wNTY1NzMgNjUuODU2MjY3IEM3OC41NDcxNDEgNjYuNTUyMjU1LCAzMi4wNjU5NTEgNjYuMjgwNzkxLCAwLjkzODk0OSA2Ni4wNDE4NDQgTS0wLjcyMDYwNCA2NS43MTg1MzIgQzAuMzAyNzk3IDQ1LjU0MjIwNCwgLTEuNDI5NjM2IDI4LjMyMTE2NiwgMC41OTE4MDAgLTEuMjA2MDgwIE0wLjIxNzk1NiA2Ni45OTgyMjMgQy0xLjU4Nzg1MCA0MS4zMzc0ODcsIC0xLjA4MTc5NSAxNy4wODIzNjIsIDAuNDQwNzQwIDAuOTg4MDMwIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSg3OTMuMDAwMDAwIDk4LjAwMDAwMCkiIGNsYXNzPSJzaGFwZSBzdHJva2UtQjEgZmlsbC1CNiIgc3R5bGU9InN0cm9rZS13aWR0aDoyOyIgLz48cmVjdCB3aWR0aD0iMTI2LjAwMDAwMCIgaGVpZ2h0PSI2Ni4wMDAwMDAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDc5My4wMDAwMDAgOTguMDAwMDAwKSIgY2xhc3M9IiBza2V0Y2gtb3ZlcmxheS1CNiIgLz48L2c+PHRleHQgeD0iODU2LjAwMDAwMCIgeT0iMTM2LjUwMDAwMCIgZmlsbD0iIzBBMEYyNSIgY2xhc3M9InRleHQtYm9sZCBmaWxsLU4xIiBzdHlsZT0idGV4dC1hbmNob3I6bWlkZGxlO2ZvbnQtc2l6ZToxNnB4Ij5UZXN0IERhdGE8L3RleHQ+PC9nPjxnIGNsYXNzPSJiVzlrWld3PSI+PGcgY2xhc3M9InNoYXBlIiA+PHBhdGggZD0iTS0xLjYwMDMxMCAtMC41NzgzNzkgTDEyMS4wNDU1NTEgMS44MTEwMzAgTDEyMC4yNTM2OTcgNjQuMjM0MDcyIEwwLjkyNTU1NiA2Ny41MzI0ODMiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDc5Ni4wMDAwMDAgMTIuMDAwMDAwKSIgY2xhc3M9InNoYXBlIHN0cm9rZS1CMSBmaWxsLUI2IiBzdHlsZT0ic3Ryb2tlLXdpZHRoOjI7IiAvPjxwYXRoIGQ9Ik0wLjg1NzI2MyAwLjk2Mzg4NCBDMjMuNjgwNzg1IDAuNjc2Nzk1LCA0Ny40NDA5NTUgLTIuMjE2NTQwLCAxMTkuMjA2NDA1IDAuMzkyMzM1IE0tMC42NDg2NjUgMC4yNjQ1OTggQzI3LjAwMTY3OSAtMC4yMjI0MTgsIDUzLjIxMDg0NiAtMC43OTIwOTAsIDExOS40MTk2MjUgMC43NTI4MTUgTTEyMS41MzY3MDQgLTEuNzQ5NDMzIEMxMjAuNDg5NDMxIDE1LjU4NTQxMCwgMTIxLjE4MDk2NyAyNy4wNjk1MTMsIDEyMS4zOTA1NDcgNjUuMTMwNjQ1IE0xMjAuMjk3Njc3IC0wLjc5OTI3NCBDMTIwLjY1NzU2MCAxNi44NTQwMDIsIDExOS42ODEwOTEgMzUuNDU1NTUyLCAxMjAuNDA2ODc2IDY2LjM1MjI0MyBNMTIxLjA1MjgwMSA2NS43ODY1NTkgQzk1LjE0OTczMSA2OC4xODExOTMsIDY2LjU1NTAxMiA2Ny42Mzc2NjgsIDEuODM2NDU2IDY1LjU5NjQ3NiBNMTE5LjA1NjU3MyA2NS44NTYyNjcgQzc0LjgwMjYxNiA2Ni41Mzg1MjAsIDMwLjU3NjkwMiA2Ni4yNjcwNTYsIDAuOTM4OTQ5IDY2LjA0MTg0NCBNLTAuNzIwNjA0IDY1LjcxODUzMiBDMC4zMDI3OTcgNDUuNTQyMjA0LCAtMS40Mjk2MzYgMjguMzIxMTY2LCAwLjU5MTgwMCAtMS4yMDYwODAgTTAuMjE3OTU2IDY2Ljk5ODIyMyBDLTEuNTg3ODUwIDQxLjMzNzQ4NywgLTEuMDgxNzk1IDE3LjA4MjM2MiwgMC40NDA3NDAgMC45ODgwMzAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDc5Ni4wMDAwMDAgMTIuMDAwMDAwKSIgY2xhc3M9InNoYXBlIHN0cm9rZS1CMSBmaWxsLUI2IiBzdHlsZT0ic3Ryb2tlLXdpZHRoOjI7IiAvPjxyZWN0IHdpZHRoPSIxMjAuMDAwMDAwIiBoZWlnaHQ9IjY2LjAwMDAwMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNzk2LjAwMDAwMCAxMi4wMDAwMDApIiBjbGFzcz0iIHNrZXRjaC1vdmVybGF5LUI2IiAvPjwvZz48dGV4dCB4PSI4NTYuMDAwMDAwIiB5PSI1MC41MDAwMDAiIGZpbGw9IiMwQTBGMjUiIGNsYXNzPSJ0ZXh0LWJvbGQgZmlsbC1OMSIgc3R5bGU9InRleHQtYW5jaG9yOm1pZGRsZTtmb250LXNpemU6MTZweCI+TUwgTW9kZWw8L3RleHQ+PC9nPjxnIGNsYXNzPSJaWFpoYkE9PSI+PGcgY2xhc3M9InNoYXBlIiA+PHBhdGggZD0iTS0xLjYwMDMxMCAtMC41NzgzNzkgTDEzMi4wNDU1NTEgMS44MTEwMzAgTDEzMS4yNTM2OTcgNzguMjM0MDcyIEwwLjkyNTU1NiA4MS41MzI0ODMiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDk5OS4wMDAwMDAgNDcuMDAwMDAwKSIgY2xhc3M9InNoYXBlIHN0cm9rZS1CMSBmaWxsLUI2IiBzdHlsZT0ic3Ryb2tlLXdpZHRoOjI7IiAvPjxwYXRoIGQ9Ik0wLjg1NzI2MyAwLjk2Mzg4NCBDMjUuODgwODM1IDAuNjMyMjExLCA1MS44NDEwNTQgLTIuMjYxMTI0LCAxMzAuMjA2NDA1IDAuMzkyMzM1IE0tMC42NDg2NjUgMC4yNjQ1OTggQzI5LjQzNjU1MCAtMC4zMTczOTUsIDU4LjA4MDU4NyAtMC44ODcwNjgsIDEzMC40MTk2MjUgMC43NTI4MTUgTTEzMi41MzY3MDQgLTEuNzQ5NDMzIEMxMzEuMzYwMTQ5IDE4LjYzMDQ0MiwgMTMyLjA1MTY4NSAzMy4xNTk1NzgsIDEzMi4zOTA1NDcgNzkuMTMwNjQ1IE0xMzEuMjk3Njc3IC0wLjc5OTI3NCBDMTMxLjc0NTcwMiAyMC42MTE1NDIsIDEzMC43NjkyMzMgNDIuOTcwNjMzLCAxMzEuNDA2ODc2IDgwLjM1MjI0MyBNMTMyLjA1MjgwMSA3OS43ODY1NTkgQzEwMy43NzczMzMgODIuMzk4ODMwLCA3Mi44MTAyMTcgODEuODU1MzA1LCAxLjgzNjQ1NiA3OS41OTY0NzYgTTEzMC4wNTY1NzMgNzkuODU2MjY3IEM4MS42Njc1NzggODAuNTYzNzAxLCAzMy4zMDY4MjYgODAuMjkyMjM3LCAwLjkzODk0OSA4MC4wNDE4NDQgTS0wLjcyMDYwNCA3OS43MTg1MzIgQzAuMDk5NTkxIDU1LjUyNjg3MywgLTEuNjMyODQyIDM0LjI5MDUwNSwgMC41OTE4MDAgLTEuMjA2MDgwIE0wLjIxNzk1NiA4MC45OTgyMjMgQy0xLjc2MDExMiA1MC4yNDQ3NTksIC0xLjI1NDA1NyAyMC44OTY5MDYsIDAuNDQwNzQwIDAuOTg4MDMwIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSg5OTkuMDAwMDAwIDQ3LjAwMDAwMCkiIGNsYXNzPSJzaGFwZSBzdHJva2UtQjEgZmlsbC1CNiIgc3R5bGU9InN0cm9rZS13aWR0aDoyOyIgLz48cmVjdCB3aWR0aD0iMTMxLjAwMDAwMCIgaGVpZ2h0PSI4MC4wMDAwMDAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDk5OS4wMDAwMDAgNDcuMDAwMDAwKSIgY2xhc3M9IiBza2V0Y2gtb3ZlcmxheS1CNiIgLz48L2c+PHRleHQgeD0iMTA2NC41MDAwMDAiIHk9IjkyLjUwMDAwMCIgZmlsbD0iIzBBMEYyNSIgY2xhc3M9InRleHQtYm9sZCBmaWxsLU4xIiBzdHlsZT0idGV4dC1hbmNob3I6bWlkZGxlO2ZvbnQtc2l6ZToxNnB4Ij5FdmFsdWF0aW9uPC90ZXh0PjwvZz48ZyBjbGFzcz0iS0dSaGRHRnpaWFFnTFNabmREc2djM0JzYVhRcFd6QmQiPjxtYXJrZXIgaWQ9Im1rLWQyLTM4NTc1NTE5OC0zNDg4Mzc4MTM0IiBtYXJrZXJXaWR0aD0iMTAuMDAwMDAwIiBtYXJrZXJIZWlnaHQ9IjEyLjAwMDAwMCIgcmVmWD0iNy4wMDAwMDAiIHJlZlk9IjYuMDAwMDAwIiB2aWV3Qm94PSIwLjAwMDAwMCAwLjAwMDAwMCAxMC4wMDAwMDAgMTIuMDAwMDAwIiBvcmllbnQ9ImF1dG8iIG1hcmtlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+IDxwb2x5Z29uIHBvaW50cz0iMC4wMDAwMDAsMC4wMDAwMDAgMTAuMDAwMDAwLDYuMDAwMDAwIDAuMDAwMDAwLDEyLjAwMDAwMCIgZmlsbD0iIzBEMzJCMiIgY2xhc3M9ImNvbm5lY3Rpb24gZmlsbC1CMSIgc3Ryb2tlLXdpZHRoPSIyIiAvPiA8L21hcmtlcj48cGF0aCBkPSJNMTIzLjAwMDA0NCA4Ny4xNzAwNjQgTTEyMi44NDAxNjggODcuNzUxNjUwIEMxNDMuNTM5MDE0IDg2LjQxNDE1MCwgMTY0LjIzOTUzMSA4Ny43NDEyMDAsIDE4Ny4yMTM1MTggODcuNzcxNzE0IE0xMjMuMjE5NzcxIDg3LjU3NjkxNCBDMTQ1LjM0NjY4NiA4Ny40OTA4OTMsIDE2Ny4zMTk0NDAgODguMDMwMDk4LCAxODcuNTg3NTExIDg3Ljc2OTEzOSIgZmlsbD0ibm9uZSIgY2xhc3M9ImNvbm5lY3Rpb24gc3Ryb2tlLUIxIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOjI7IiBtYXNrPSJ1cmwoI2QyLTM4NTc1NTE5OCkiIC8+PHBhdGggZD0iTS04LjUyNzYyNyAtMy4wOTcwNjEgTDEuNzQ5NTUwIDAuNTU4NzkxIEwtOC41NjI5MzUgNC41MjE1MzMiIHN0cm9rZT0ibm9uZSIgY2xhc3M9ImNvbm5lY3Rpb24gZmlsbC1CMSIgc3R5bGU9InN0cm9rZS13aWR0aDowOyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTg4LjAwMDAwMCA4OC4wMDAwMDApIHJvdGF0ZSgwKSIgLz4gPHBhdGggZD0iTS0xMC4xNTM3MzEgLTQuMDM4ODk3IEMtNy4yOTM2NTcgLTIuOTY0NzU0LCAtNS41NTI0NTMgLTMuMTI2ODcxLCAwLjIyMjMwNSAtMC42NTQ0NzQgTS0xMC4xNjAxMTcgLTQuMjUzNTM1IEMtNy42MTY0MzYgLTIuNjc3NjYzLCAtNS41Njk2NTYgLTIuMzIwNDA0LCAtMC4wODY1NjUgMC4yNzIyOTEgTTAuNTc4MDQ4IC0wLjgwNzE2NCBDLTIuMjQwNDYwIDEuMTMzNjM0LCAtMy44NDU2OTkgMS4xMzU1MDQsIC05LjU3OTM2NyA0LjE0MDcwOSBNLTAuMjE3OTA3IC0wLjMyMjMyOCBDLTMuNjYwNTcxIDAuOTQxMTI2LCAtNy4wMDMxNDIgMi4xNjcwNTAsIC0xMC4xMDAyOTYgMy44NDA4NjEgTS05Ljk1Nzc1OCA0LjYyOTI0NyBDLTkuOTM3NDM4IDIuNzk0ODE3LCAtMTAuNTA4NjU1IDAuNTA5MjM4LCAtOS4zMzA4MzQgLTMuNTIyODE4IE0tMTAuMzU0NzQxIDQuMjg1MDE0IEMtOS43MTIzNjYgMC45OTY0NTMsIC05LjgwNTMyOSAtMS4yMzUzMTksIC05LjY0ODg0MCAtNC4zNjY1MjQiIGZpbGw9Im5vbmUiIGNsYXNzPSJjb25uZWN0aW9uIHN0cm9rZS1CMSIgc3R5bGU9InN0cm9rZS13aWR0aDoyOyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMTg4LjAwMDAwMCA4OC4wMDAwMDApIHJvdGF0ZSgwKSIgLz48L2c+PGcgY2xhc3M9IktITndiR2wwSUMwbVozUTdJSFJ5WVdsdUtWc3dYUT09Ij48cGF0aCBkPSJNNDM5LjAwMDAzMCA3Mi4xNjI0NzMgTTQzOC44NDAxNTQgNzIuNzQ0MDU5IEM0NjMuMzgxNTk3IDcxLjI4NTI2MSwgNDg3LjkyNTMyOSA3Mi41MTk5MjksIDUxNS4yMTM1OTAgNzIuNDc1NjY5IE00MzkuMjE5NzU3IDcyLjU2OTMyMyBDNDY1LjQ2NTY5NiA3Mi4zNjg0MzcsIDQ5MS41NTY1MzggNzIuODA4NjUzLCA1MTUuNTg3NTgzIDcyLjQ3MzA5NCBNNTE2LjAwMDA3MiA3Mi43MDM5NTUgQzUyNS4xMjUyODMgNzMuMjE1NDUyLCA1MjYuNjE1MTI5IDczLjU2MTIyMCwgNTI2LjIyODEwMyA2My40MzQzNTIgTTUxNS4xNzk2NzYgNzMuMzk5MjI4IEM1MjYuMzkzNDkyIDcxLjkyNzM2NSwgNTI2LjM3MjA5NyA3MS42NjY5MDcsIDUyNS40NTY2NTMgNjIuMjcwOTQ2IE01MjUuNDY2NjEwIDYyLjQxOTI3OCBDNTI1Ljc5MjQ5MSA1OS42MDgyNTIsIDUyNS45MzU0MjkgNTcuOTE5MDU4LCA1MjYuMTkxOTA1IDU0Ljk2MTA5NCBNNTI1LjM4OTUyMyA2Mi40MzgzMjIgQzUyNS42MTQzODIgNTkuODU1NzE0LCA1MjUuNzk4OTQzIDU3LjE4MjMxMCwgNTI1LjgyODAzMSA1NC45NzM4MDAgTTUyNiA1NSBDNTI2Ljc5NTkyMiA0NC45OTIzNTQsIDUyNi45Mzg5NDkgNDUuMDQxODQ0LCA1MzUuOTEyMTI2IDQ1LjI2MzgxOCBNNTI1Ljg2ODA5MyA1NS43MjU3MzQgQzUyNS44MjQwODIgNDUuNzg3OTc5LCA1MjUuMDQ0NzYxIDQ0LjcwNTIwOCwgNTM1LjkyMTczNCA0NC41NDk2MjIgTTUzNi41NTkzOTcgNDUuMTY0ODQzIEM1NDMuMzA4MDg5IDQ0Ljk4NTM3NywgNTUzLjAzNjk4MSA0NC4xMzA4MjgsIDU2MS4zNDYwNjEgNDQuNzMwMjk1IE01MzUuNTIxNjU2IDQ0LjQwNTAyNyBDNTQxLjY1OTI3MiA0NC45NjgxMDYsIDU0Ni45MzY0NjYgNDQuMTY4NDI0LCA1NjIuMjMxMzg5IDQ1LjM4MzEyMCIgZmlsbD0ibm9uZSIgY2xhc3M9ImNvbm5lY3Rpb24gc3Ryb2tlLUIxIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOjI7IiBtYXNrPSJ1cmwoI2QyLTM4NTc1NTE5OCkiIC8+PHBhdGggZD0iTS04LjUyNzYyNyAtMy4wOTcwNjEgTDEuNzQ5NTUwIDAuNTU4NzkxIEwtOC41NjI5MzUgNC41MjE1MzMiIHN0cm9rZT0ibm9uZSIgY2xhc3M9ImNvbm5lY3Rpb24gZmlsbC1CMSIgc3R5bGU9InN0cm9rZS13aWR0aDowOyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNTYyLjAwMDAwMCA0NS4wMDAwMDApIHJvdGF0ZSgwKSIgLz4gPHBhdGggZD0iTS0xMC4xNTM3MzEgLTQuMDM4ODk3IEMtNy4yOTM2NTcgLTIuOTY0NzU0LCAtNS41NTI0NTMgLTMuMTI2ODcxLCAwLjIyMjMwNSAtMC42NTQ0NzQgTS0xMC4xNjAxMTcgLTQuMjUzNTM1IEMtNy42MTY0MzYgLTIuNjc3NjYzLCAtNS41Njk2NTYgLTIuMzIwNDA0LCAtMC4wODY1NjUgMC4yNzIyOTEgTTAuNTc4MDQ4IC0wLjgwNzE2NCBDLTIuMjQwNDYwIDEuMTMzNjM0LCAtMy44NDU2OTkgMS4xMzU1MDQsIC05LjU3OTM2NyA0LjE0MDcwOSBNLTAuMjE3OTA3IC0wLjMyMjMyOCBDLTMuNjYwNTcxIDAuOTQxMTI2LCAtNy4wMDMxNDIgMi4xNjcwNTAsIC0xMC4xMDAyOTYgMy44NDA4NjEgTS05Ljk1Nzc1OCA0LjYyOTI0NyBDLTkuOTM3NDM4IDIuNzk0ODE3LCAtMTAuNTA4NjU1IDAuNTA5MjM4LCAtOS4zMzA4MzQgLTMuNTIyODE4IE0tMTAuMzU0NzQxIDQuMjg1MDE0IEMtOS43MTIzNjYgMC45OTY0NTMsIC05LjgwNTMyOSAtMS4yMzUzMTksIC05LjY0ODg0MCAtNC4zNjY1MjQiIGZpbGw9Im5vbmUiIGNsYXNzPSJjb25uZWN0aW9uIHN0cm9rZS1CMSIgc3R5bGU9InN0cm9rZS13aWR0aDoyOyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNTYyLjAwMDAwMCA0NS4wMDAwMDApIHJvdGF0ZSgwKSIgLz48L2c+PGcgY2xhc3M9IktITndiR2wwSUMwbVozUTdJSFJsYzNRcFd6QmQiPjxwYXRoIGQ9Ik00NDAuMDAwMDI5IDEwMi4xNzc3MTkgTTQzOS44NDAxNTMgMTAyLjc1OTMwNSBDNDY0LjA2MjU2NCAxMDEuNDg3MjM3LCA0ODguMjg2MDMxIDEwMi45MDYyMjYsIDUxNS4yMTM1OTEgMTAzLjA2NjQzOSBNNDQwLjIxOTc1NiAxMDIuNTg0NTY5IEM0NjYuMTIwNjU3IDEwMi41ODI1MTAsIDQ5MS44NjgzMjkgMTAzLjIyMDIyOSwgNTE1LjU4NzU4NCAxMDMuMDYzODY0IE01MTYuMDAwMDczIDEwMy4yOTQ3MjUgQzUyNS4xMjUyODMgMTAzLjg4MjQ1MiwgNTI2LjYxNTEyOSAxMDQuMjI4MjIwLCA1MjYuMjI4MTAzIDExNC4xMDEzNTIgTTUxNS4xNzk2NzcgMTAzLjk4OTk5OCBDNTI2LjM5MzQ5MiAxMDIuNTk0MzY1LCA1MjYuMzcyMDk3IDEwMi4zMzM5MDcsIDUyNS40NTY2NTMgMTEyLjkzNzk0NiBNNTI1LjQ2NzY4NyAxMTMuMTAyMzMyIEM1MjUuNzQwNDQ4IDExNS4yMTI5ODgsIDUyNS44Nzk3OTIgMTE4LjU2NzEwNywgNTI2LjIxMjY3NCAxMjAuOTU2ODgzIE01MjUuMzgyMjU4IDExMy4xMjM0MzYgQzUyNS42MDMwMDMgMTE1LjkyNzU2OCwgNTI1Ljc4Njg2NyAxMTguNjMxMzI0LCA1MjUuODA5NDE5IDEyMC45NzA5NjQgTTUyNiAxMjEgQzUyNi43OTU5MjIgMTMwLjk5MjM1NCwgNTI2LjkzODk0OSAxMzEuMDQxODQ0LCA1MzUuOTEyMTI2IDEzMS4yNjM4MTggTTUyNS44NjgwOTMgMTIxLjcyNTczNCBDNTI1LjgyNDA4MiAxMzEuNzg3OTc5LCA1MjUuMDQ0NzYxIDEzMC43MDUyMDgsIDUzNS45MjE3MzQgMTMwLjU0OTYyMiBNNTM2LjQzOTE5OSAxMzEuMDQ4ODc1IEM2MTMuNTc3NjgyIDEzMS40ODQ5MDQsIDY5My4xMzQ2MDUgMTMwLjgxNzY2OCwgNzg4LjQ2OTMyNyAxMzAuNzgxMTM0IE01MzUuNTk3MDcwIDEzMC40MzIyODMgQzU4OS4yNjg2ODggMTI5Ljk5NzAxNiwgNjQyLjI0MjA3MiAxMjkuMzY1ODk2LCA3ODkuMTg3NzcyIDEzMS4zMTA5MDMiIGZpbGw9Im5vbmUiIGNsYXNzPSJjb25uZWN0aW9uIHN0cm9rZS1CMSIgc3R5bGU9InN0cm9rZS13aWR0aDoyOyIgbWFzaz0idXJsKCNkMi0zODU3NTUxOTgpIiAvPjxwYXRoIGQ9Ik0tOC41Mjc2MjcgLTMuMDk3MDYxIEwxLjc0OTU1MCAwLjU1ODc5MSBMLTguNTYyOTM1IDQuNTIxNTMzIiBzdHJva2U9Im5vbmUiIGNsYXNzPSJjb25uZWN0aW9uIGZpbGwtQjEiIHN0eWxlPSJzdHJva2Utd2lkdGg6MDsiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDc4OS4wMDAwMDAgMTMxLjAwMDAwMCkgcm90YXRlKDApIiAvPiA8cGF0aCBkPSJNLTEwLjE1MzczMSAtNC4wMzg4OTcgQy03LjI5MzY1NyAtMi45NjQ3NTQsIC01LjU1MjQ1MyAtMy4xMjY4NzEsIDAuMjIyMzA1IC0wLjY1NDQ3NCBNLTEwLjE2MDExNyAtNC4yNTM1MzUgQy03LjYxNjQzNiAtMi42Nzc2NjMsIC01LjU2OTY1NiAtMi4zMjA0MDQsIC0wLjA4NjU2NSAwLjI3MjI5MSBNMC41NzgwNDggLTAuODA3MTY0IEMtMi4yNDA0NjAgMS4xMzM2MzQsIC0zLjg0NTY5OSAxLjEzNTUwNCwgLTkuNTc5MzY3IDQuMTQwNzA5IE0tMC4yMTc5MDcgLTAuMzIyMzI4IEMtMy42NjA1NzEgMC45NDExMjYsIC03LjAwMzE0MiAyLjE2NzA1MCwgLTEwLjEwMDI5NiAzLjg0MDg2MSBNLTkuOTU3NzU4IDQuNjI5MjQ3IEMtOS45Mzc0MzggMi43OTQ4MTcsIC0xMC41MDg2NTUgMC41MDkyMzgsIC05LjMzMDgzNCAtMy41MjI4MTggTS0xMC4zNTQ3NDEgNC4yODUwMTQgQy05LjcxMjM2NiAwLjk5NjQ1MywgLTkuODA1MzI5IC0xLjIzNTMxOSwgLTkuNjQ4ODQwIC00LjM2NjUyNCIgZmlsbD0ibm9uZSIgY2xhc3M9ImNvbm5lY3Rpb24gc3Ryb2tlLUIxIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOjI7IiB0cmFuc2Zvcm09InRyYW5zbGF0ZSg3ODkuMDAwMDAwIDEzMS4wMDAwMDApIHJvdGF0ZSgwKSIgLz48L2c+PGcgY2xhc3M9IktIUnlZV2x1SUMwbVozUTdJRzF2WkdWc0tWc3dYUT09Ij48cGF0aCBkPSJNNzI0LjAwMDA0NCA0NC4xNzAwNjQgTTcyMy44NDAxNjggNDQuNzUxNjUwIEM3NDUuNDk5ODExIDQzLjQwNjkyMSwgNzY3LjE2MTEyNCA0NC43MzM5NzEsIDc5MS4yMTM1MTggNDQuNzcxNzE0IE03MjQuMjE5NzcxIDQ0LjU3NjkxNCBDNzQ3LjM3NjIwMSA0NC40ODY5MjQsIDc3MC4zNzg0NjkgNDUuMDI2MTI5LCA3OTEuNTg3NTExIDQ0Ljc2OTEzOSIgZmlsbD0ibm9uZSIgY2xhc3M9ImNvbm5lY3Rpb24gc3Ryb2tlLUIxIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOjI7IiBtYXNrPSJ1cmwoI2QyLTM4NTc1NTE5OCkiIC8+PHBhdGggZD0iTS04LjUyNzYyNyAtMy4wOTcwNjEgTDEuNzQ5NTUwIDAuNTU4NzkxIEwtOC41NjI5MzUgNC41MjE1MzMiIHN0cm9rZT0ibm9uZSIgY2xhc3M9ImNvbm5lY3Rpb24gZmlsbC1CMSIgc3R5bGU9InN0cm9rZS13aWR0aDowOyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNzkyLjAwMDAwMCA0NS4wMDAwMDApIHJvdGF0ZSgwKSIgLz4gPHBhdGggZD0iTS0xMC4xNTM3MzEgLTQuMDM4ODk3IEMtNy4yOTM2NTcgLTIuOTY0NzU0LCAtNS41NTI0NTMgLTMuMTI2ODcxLCAwLjIyMjMwNSAtMC42NTQ0NzQgTS0xMC4xNjAxMTcgLTQuMjUzNTM1IEMtNy42MTY0MzYgLTIuNjc3NjYzLCAtNS41Njk2NTYgLTIuMzIwNDA0LCAtMC4wODY1NjUgMC4yNzIyOTEgTTAuNTc4MDQ4IC0wLjgwNzE2NCBDLTIuMjQwNDYwIDEuMTMzNjM0LCAtMy44NDU2OTkgMS4xMzU1MDQsIC05LjU3OTM2NyA0LjE0MDcwOSBNLTAuMjE3OTA3IC0wLjMyMjMyOCBDLTMuNjYwNTcxIDAuOTQxMTI2LCAtNy4wMDMxNDIgMi4xNjcwNTAsIC0xMC4xMDAyOTYgMy44NDA4NjEgTS05Ljk1Nzc1OCA0LjYyOTI0NyBDLTkuOTM3NDM4IDIuNzk0ODE3LCAtMTAuNTA4NjU1IDAuNTA5MjM4LCAtOS4zMzA4MzQgLTMuNTIyODE4IE0tMTAuMzU0NzQxIDQuMjg1MDE0IEMtOS43MTIzNjYgMC45OTY0NTMsIC05LjgwNTMyOSAtMS4yMzUzMTksIC05LjY0ODg0MCAtNC4zNjY1MjQiIGZpbGw9Im5vbmUiIGNsYXNzPSJjb25uZWN0aW9uIHN0cm9rZS1CMSIgc3R5bGU9InN0cm9rZS13aWR0aDoyOyIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNzkyLjAwMDAwMCA0NS4wMDAwMDApIHJvdGF0ZSgwKSIgLz48L2c+PGcgY2xhc3M9IktHMXZaR1ZzSUMwbVozUTdJR1YyWVd3cFd6QmQiPjxwYXRoIGQ9Ik05MTcuMDAwMDQ0IDQ0LjE3MDA2NCBNOTE2Ljg0MDE2OCA0NC43NTE2NTAgQzkyNi45NzAyNDcgNDMuNDkzNjcxLCA5MzcuMTAxOTk4IDQ0LjgyMDcyMSwgOTQ4LjIxMzUxOCA0NC43NzE3MTQgTTkxNy4yMTk3NzEgNDQuNTc2OTE0IEM5MjguMDIyMDI0IDQ0LjUzNDU1MiwgOTM4LjY3MDExNiA0NS4wNzM3NTcsIDk0OC41ODc1MTEgNDQuNzY5MTM5IE05NDkgNDUgQzk1OC4xMjUyODMgNDUuNTQ5NDUyLCA5NTkuNjE1MTI5IDQ1Ljg5NTIyMCwgOTU5LjIyODEwMyA1NS43NjgzNTIgTTk0OC4xNzk2MDQgNDUuNjk1MjczIEM5NTkuMzkzNDkyIDQ0LjI2MTM2NSwgOTU5LjM3MjA5NyA0NC4wMDA5MDcsIDk1OC40NTY2NTMgNTQuNjA0OTQ2IE05NTguNDcwNDEyIDU0LjgwOTkyNSBDOTU4Ljc2Njk5NCA1Ny40NDM5MzgsIDk1OC44OTcyNDcgNjEuNjI4OTY4LCA5NTkuMjY1MTkzIDY0LjYxMjIzNSBNOTU4LjM2Mzg4NiA1NC44MzYyNDIgQzk1OC41OTE5NzMgNTguMzM1MTI3LCA5NTguNzc0MDc2IDYxLjcwOTQxNiwgOTU4Ljc2MjM1NyA2NC42Mjk3OTQgTTk1OSA2NC42NjYgQzk1OS43OTU5MjIgNzQuNjU4MzU0LCA5NTkuOTM4OTQ5IDc0LjcwNzg0NCwgOTY4LjkxMjEyNiA3NC45Mjk4MTggTTk1OC44NjgwOTMgNjUuMzkxNzM0IEM5NTguODI0MDgyIDc1LjQ1Mzk3OSwgOTU4LjA0NDc2MSA3NC4zNzEyMDgsIDk2OC45MjE3MzQgNzQuMjE1NjIyIE05NjkuNTU5Mzk3IDc0LjgzMDg0MyBDOTc2LjMwODA4OSA3NC42NTEzNzcsIDk4Ni4wMzY5ODEgNzMuNzk2ODI4LCA5OTQuMzQ2MDYxIDc0LjM5NjI5NSBNOTY4LjUyMTY1NiA3NC4wNzEwMjcgQzk3NC42NTkyNzIgNzQuNjM0MTA2LCA5NzkuOTM2NDY2IDczLjgzNDQyNCwgOTk1LjIzMTM4OSA3NS4wNDkxMjAiIGZpbGw9Im5vbmUiIGNsYXNzPSJjb25uZWN0aW9uIHN0cm9rZS1CMSIgc3R5bGU9InN0cm9rZS13aWR0aDoyOyIgbWFzaz0idXJsKCNkMi0zODU3NTUxOTgpIiAvPjxwYXRoIGQ9Ik0tOC41Mjc2MjcgLTMuMDk3MDYxIEwxLjc0OTU1MCAwLjU1ODc5MSBMLTguNTYyOTM1IDQuNTIxNTMzIiBzdHJva2U9Im5vbmUiIGNsYXNzPSJjb25uZWN0aW9uIGZpbGwtQjEiIHN0eWxlPSJzdHJva2Utd2lkdGg6MDsiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDk5NS4wMDAwMDAgNzQuNjY2MDAwKSByb3RhdGUoMCkiIC8+IDxwYXRoIGQ9Ik0tMTAuMTUzNzMxIC00LjAzODg5NyBDLTcuMjkzNjU3IC0yLjk2NDc1NCwgLTUuNTUyNDUzIC0zLjEyNjg3MSwgMC4yMjIzMDUgLTAuNjU0NDc0IE0tMTAuMTYwMTE3IC00LjI1MzUzNSBDLTcuNjE2NDM2IC0yLjY3NzY2MywgLTUuNTY5NjU2IC0yLjMyMDQwNCwgLTAuMDg2NTY1IDAuMjcyMjkxIE0wLjU3ODA0OCAtMC44MDcxNjQgQy0yLjI0MDQ2MCAxLjEzMzYzNCwgLTMuODQ1Njk5IDEuMTM1NTA0LCAtOS41NzkzNjcgNC4xNDA3MDkgTS0wLjIxNzkwNyAtMC4zMjIzMjggQy0zLjY2MDU3MSAwLjk0MTEyNiwgLTcuMDAzMTQyIDIuMTY3MDUwLCAtMTAuMTAwMjk2IDMuODQwODYxIE0tOS45NTc3NTggNC42MjkyNDcgQy05LjkzNzQzOCAyLjc5NDgxNywgLTEwLjUwODY1NSAwLjUwOTIzOCwgLTkuMzMwODM0IC0zLjUyMjgxOCBNLTEwLjM1NDc0MSA0LjI4NTAxNCBDLTkuNzEyMzY2IDAuOTk2NDUzLCAtOS44MDUzMjkgLTEuMjM1MzE5LCAtOS42NDg4NDAgLTQuMzY2NTI0IiBmaWxsPSJub25lIiBjbGFzcz0iY29ubmVjdGlvbiBzdHJva2UtQjEiIHN0eWxlPSJzdHJva2Utd2lkdGg6MjsiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDk5NS4wMDAwMDAgNzQuNjY2MDAwKSByb3RhdGUoMCkiIC8+PC9nPjxnIGNsYXNzPSJLSFJsYzNRZ0xTWm5kRHNnWlhaaGJDbGJNRjA9Ij48cGF0aCBkPSJNOTIwLjAwMDA0NCAxMzAuMTcwMDY0IE05MTkuODQwMTY4IDEzMC43NTE2NTAgQzkyOS4wMDk0NTEgMTI5LjUwMDkwMCwgOTM4LjE4MDQwNCAxMzAuODI3OTUwLCA5NDguMjEzNTE4IDEzMC43NzE3MTQgTTkyMC4yMTk3NzEgMTMwLjU3NjkxNCBDOTI5Ljk5MjUwOSAxMzAuNTM4NTIxLCA5MzkuNjExMDg2IDEzMS4wNzc3MjYsIDk0OC41ODc1MTEgMTMwLjc2OTEzOSBNOTQ5IDEzMSBDOTU4LjEyNTI4MyAxMzEuNTQ5NDUyLCA5NTkuNjE1MTI5IDEzMS44OTUyMjAsIDk1OS4yMjgxMDMgMTIxLjc2ODM1MiBNOTQ4LjE3OTYwNCAxMzEuNjk1MjczIEM5NTkuMzkzNDkyIDEzMC4yNjEzNjUsIDk1OS4zNzIwOTcgMTMwLjAwMDkwNywgOTU4LjQ1NjY1MyAxMjAuNjA0OTQ2IE05NTguNDY5MzM2IDEyMC43OTM4OTcgQzk1OC44MzYyNjIgMTE3LjIwOTMzNiwgOTU4Ljk3MDEwNCAxMTUuMDU0MzU2LCA5NTkuMjQ0NDU2IDExMS4yODM0MzkgTTk1OC4zNzExNDAgMTIwLjgxODE1NiBDOTU4LjYwNTI3NyAxMTcuNTI0MjEyLCA5NTguNzg4MDc1IDExNC4xMTUyMzUsIDk1OC43ODA5MzkgMTExLjI5OTYyNSBNOTU5IDExMS4zMzMgQzk1OS43OTU5MjIgMTAxLjMyNTM1NCwgOTU5LjkzODk0OSAxMDEuMzc0ODQ0LCA5NjguOTEyMTI2IDEwMS41OTY4MTggTTk1OC44NjgwOTMgMTEyLjA1ODczNCBDOTU4LjgyNDA4MiAxMDIuMTIwOTc5LCA5NTguMDQ0NzYxIDEwMS4wMzgyMDgsIDk2OC45MjE3MzQgMTAwLjg4MjYyMiBNOTY5LjU1OTM5NyAxMDEuNDk3ODQzIEM5NzYuMzA4MDg5IDEwMS4zMTgzNzcsIDk4Ni4wMzY5ODEgMTAwLjQ2MzgyOCwgOTk0LjM0NjA2MSAxMDEuMDYzMjk1IE05NjguNTIxNjU2IDEwMC43MzgwMjcgQzk3NC42NTkyNzIgMTAxLjMwMTEwNiwgOTc5LjkzNjQ2NiAxMDAuNTAxNDI0LCA5OTUuMjMxMzg5IDEwMS43MTYxMjAiIGZpbGw9Im5vbmUiIGNsYXNzPSJjb25uZWN0aW9uIHN0cm9rZS1CMSIgc3R5bGU9InN0cm9rZS13aWR0aDoyOyIgbWFzaz0idXJsKCNkMi0zODU3NTUxOTgpIiAvPjxwYXRoIGQ9Ik0tOC41Mjc2MjcgLTMuMDk3MDYxIEwxLjc0OTU1MCAwLjU1ODc5MSBMLTguNTYyOTM1IDQuNTIxNTMzIiBzdHJva2U9Im5vbmUiIGNsYXNzPSJjb25uZWN0aW9uIGZpbGwtQjEiIHN0eWxlPSJzdHJva2Utd2lkdGg6MDsiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDk5NS4wMDAwMDAgMTAxLjMzMzAwMCkgcm90YXRlKDApIiAvPiA8cGF0aCBkPSJNLTEwLjE1MzczMSAtNC4wMzg4OTcgQy03LjI5MzY1NyAtMi45NjQ3NTQsIC01LjU1MjQ1MyAtMy4xMjY4NzEsIDAuMjIyMzA1IC0wLjY1NDQ3NCBNLTEwLjE2MDExNyAtNC4yNTM1MzUgQy03LjYxNjQzNiAtMi42Nzc2NjMsIC01LjU2OTY1NiAtMi4zMjA0MDQsIC0wLjA4NjU2NSAwLjI3MjI5MSBNMC41NzgwNDggLTAuODA3MTY0IEMtMi4yNDA0NjAgMS4xMzM2MzQsIC0zLjg0NTY5OSAxLjEzNTUwNCwgLTkuNTc5MzY3IDQuMTQwNzA5IE0tMC4yMTc5MDcgLTAuMzIyMzI4IEMtMy42NjA1NzEgMC45NDExMjYsIC03LjAwMzE0MiAyLjE2NzA1MCwgLTEwLjEwMDI5NiAzLjg0MDg2MSBNLTkuOTU3NzU4IDQuNjI5MjQ3IEMtOS45Mzc0MzggMi43OTQ4MTcsIC0xMC41MDg2NTUgMC41MDkyMzgsIC05LjMzMDgzNCAtMy41MjI4MTggTS0xMC4zNTQ3NDEgNC4yODUwMTQgQy05LjcxMjM2NiAwLjk5NjQ1MywgLTkuODA1MzI5IC0xLjIzNTMxOSwgLTkuNjQ4ODQwIC00LjM2NjUyNCIgZmlsbD0ibm9uZSIgY2xhc3M9ImNvbm5lY3Rpb24gc3Ryb2tlLUIxIiBzdHlsZT0ic3Ryb2tlLXdpZHRoOjI7IiB0cmFuc2Zvcm09InRyYW5zbGF0ZSg5OTUuMDAwMDAwIDEwMS4zMzMwMDApIHJvdGF0ZSgwKSIgLz48L2c+PG1hc2sgaWQ9ImQyLTM4NTc1NTE5OCIgbWFza1VuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeD0iMSIgeT0iMSIgd2lkdGg9IjExNDAiIGhlaWdodD0iMTc0Ij4KPHJlY3QgeD0iMSIgeT0iMSIgd2lkdGg9IjExNDAiIGhlaWdodD0iMTc0IiBmaWxsPSJ3aGl0ZSI+PC9yZWN0PgoKPC9tYXNrPjwvc3ZnPjwvc3ZnPgo=" class="img-fluid" style="width:100.0%"></p>
</section>
</section>
<section id="try-it-yourself" class="level2">
<h2 class="anchored" data-anchor-id="try-it-yourself">Try it yourself</h2>
<p>I suggest starting with the <a href="https://play.d2lang.com">D2 playground</a> and any assistant, such as ChatGPT. No installation or magic prompt required. Just ask it to generate the D2 code for a simple diagram.</p>
<p>A more powerful setup is to install D2 locally, index the documentation in Cursor and then use Claude-3.5 Sonnet in a Composer window to generate diagrams.</p>
<hr>
<p>Image background by <a href="https://unsplash.com/@pawel_czerwinski?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash">Pawel Czerwinski</a> on <a href="https://unsplash.com/photos/background-pattern-C-pmBkHZWQ0?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash">Unsplash</a></p>


</section>

 ]]></description>
  <category>Productivity</category>
  <guid>https://simmering.dev/blog/diagrams/</guid>
  <pubDate>Fri, 27 Dec 2024 23:00:00 GMT</pubDate>
  <media:content url="https://simmering.dev/blog/diagrams/diagrams.webp" medium="image" type="image/webp"/>
</item>
<item>
  <title>When to use Direct Preference Optimization (DPO)</title>
  <dc:creator>Paul Simmering</dc:creator>
  <link>https://simmering.dev/blog/llm-customization/</link>
  <description><![CDATA[ 






<p>OpenAI recently added the ability to fine-tune their models using direct preference optimization (DPO). They call it <a href="https://platform.openai.com/docs/guides/fine-tuning#preference">preference tuning</a>. Previously, their API only supported supervised fine-tuning (SFT). They join <a href="https://openpipe.ai/blog/announcing-dpo-support">OpenPipe</a> as one of the first pay-per-token APIs to offer DPO. This makes DPO more accessible to developers who don’t want the complexity of managing the training infrastructure themselves. In this article I will briefly introduce DPO and then discuss its use cases in contrast to SFT.</p>
<section id="what-is-dpo" class="level3">
<h3 class="anchored" data-anchor-id="what-is-dpo">What is DPO?</h3>
<p>Skip to the next section if you’re already familiar with DPO.</p>
<p>DPO means training an LLM to conform to the preferences of human raters. Each example contains:</p>
<ul>
<li>A user input, e.g.&nbsp;a question</li>
<li>An ideal assistant reply</li>
<li>A worse assistant reply</li>
</ul>
<p>In a training input file, it looks like this:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode json code-with-copy"><code class="sourceCode json"><span id="cb1-1"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb1-2">  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"input"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb1-3">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"messages"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">[</span></span>
<span id="cb1-4">      <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb1-5">        <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"role"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"user"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-6">        <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"content"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Write a professional email asking for a raise."</span></span>
<span id="cb1-7">      <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb1-8">    <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">]</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-9">  <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">},</span></span>
<span id="cb1-10">  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"preferred_output"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">[</span></span>
<span id="cb1-11">    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb1-12">      <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"role"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"assistant"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-13">      <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"content"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"I would like to request a salary review. In the past 6 months I have led multiple projects and also assisted colleagues with their tasks."</span></span>
<span id="cb1-14">    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb1-15">  <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">]</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-16">  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"non_preferred_output"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">[</span></span>
<span id="cb1-17">    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">{</span></span>
<span id="cb1-18">      <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"role"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"assistant"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">,</span></span>
<span id="cb1-19">      <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">"content"</span><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">:</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"You should increase my salary because I'm doing all the work here."</span></span>
<span id="cb1-20">    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">}</span></span>
<span id="cb1-21">  <span class="ot" style="color: #003B4F;
background-color: null;
font-style: inherit;">]</span></span>
<span id="cb1-22"><span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">}</span></span></code></pre></div></div>
<p>DPO adjusts the model weights such that it is more likely to choose the preferred answers. It’s graded on how much more likely it is to choose the preferred answer. This teaches it not just to answer more like the preferred answer, but also to avoid answering like the rejected answer. It stands in contrast to SFT, which only teaches it to mimic a single answer.</p>
</section>
<section id="loss-function" class="level3">
<h3 class="anchored" data-anchor-id="loss-function">Loss function</h3>
<p>Mathematically, DPO fine-tunes the LLM by maximizing the margin between the probability of the preferred and rejected response. The loss function is:</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://simmering.dev/blog/llm-customization/dpo_loss.webp" class="img-fluid figure-img"></p>
<figcaption>DPO loss function</figcaption>
</figure>
</div>
<ul>
<li><img src="https://latex.codecogs.com/png.latex?%5Cpi_%5Ctheta(y%7Cx)">: The probability that our model (with parameters θ) assigns to generating response y given input x</li>
<li><img src="https://latex.codecogs.com/png.latex?%5Cpi_%7B%5Ctext%7Bref%7D%7D(y%7Cx)">: The probability that a reference model (usually the initial pre-trained model) assigns to the same response</li>
<li><img src="https://latex.codecogs.com/png.latex?y_w">: The preferred, “winning” response</li>
<li><img src="https://latex.codecogs.com/png.latex?y_l">: The rejected, “losing” response</li>
<li><img src="https://latex.codecogs.com/png.latex?%5Cbeta">: Hyperparameter to regulate the strength of weight updates.</li>
<li><img src="https://latex.codecogs.com/png.latex?%5Csigma">: Sigmoid function that maps the difference to a probability between 0 and 1</li>
<li><img src="https://latex.codecogs.com/png.latex?%5Cmathbb%7BE%7D">: Expected value over the dataset D of preferences</li>
<li><img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BD%7D">: Dataset containing triples of (input, preferred response, rejected response)</li>
</ul>
<p>See the <a href="https://arxiv.org/abs/2305.18290">paper</a> by Rafailov et al.&nbsp;(2023) for details and how this loss is turned into a weight update.</p>
<p>The use of a reference model has two advantages over purely optimizing for a high <img src="https://latex.codecogs.com/png.latex?%5Cpi_%5Ctheta(y_w%7Cx)">:</p>
<ol type="1">
<li>It prevents overfitting to the examples. The derivative of the loss function (see after equation 7 in paper) illustrates that the model gets low weight updates from examples where it already gives more probability to the preferred response.</li>
<li>It takes into account how likely the preferred and rejected responses are to begin with. So teaching the model to prefer a rare response over another rare response won’t cause an update that breaks common responses.</li>
</ol>
<p>The choice of <img src="https://latex.codecogs.com/png.latex?%5Cbeta"> is key. Higher values result in more conservative updates, preserving previous behavior. Lower values cause more aggressive updates in favor of the preferred answer. This stems from the interaction of the sigmoid function: a higher beta causes the function to saturate (approach 1) from smaller differences in the probability ratios, leading to a smaller weight update.</p>
<p>DPO and its predecessor reinforcement learning from human feedback (RLHF) have been a staple in the training of LLMs that serve as assistants, including open models like the Llama 3 family. They are one of the key separators between different models, which share a lot of common SFT in the form of web scraped texts, papers and code. It’s what forms the character and mannerisms of an assistant.</p>
</section>
<section id="data-for-dpo" class="level3">
<h3 class="anchored" data-anchor-id="data-for-dpo">Data for DPO</h3>
<p>Collecting DPO data is straightforward. You take the current best model and let it generate multiple answers to a user input at nonzero temperature. Then either a human or a model (a copy of the answering model or a larger, smarter model) judges which answer is superior. This enables a powerful training loop, particularly if an LLM is used as a judge.</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
    A[User Input] --&gt;|Prompt| B[Base model]
    B --&gt;|Generates| C[Multiple responses]
    C --&gt;|Evaluates| D[Human or LLM judge]
    D --&gt;|Selects| G[Preferred response]
    D --&gt;|Rejects| H[Non-preferred response]
    G --&gt; I[DPO training data]
    H --&gt; I
    I --&gt;|Updates| B
</pre>
</div>
<p></p></figure><p></p>
</div>
</div>
</div>
</section>
<section id="when-to-use-dpo" class="level2">
<h2 class="anchored" data-anchor-id="when-to-use-dpo">When to use DPO?</h2>
<p>DPO is best utilized to refine a model that already underwent SFT. By default, OpenAI’s fine-tuning API bundles SFT with DPO by first using SFT to let models learn from the exact wording of the preferred answers before also learning to prefer them using DPO.</p>
<section id="when-prompting-isnt-sufficient" class="level4">
<h4 class="anchored" data-anchor-id="when-prompting-isnt-sufficient">1. When prompting isn’t sufficient</h4>
<p>If the desired behavior can be achieved by a prompt, you don’t need to implement DPO. Prompts are easier to change and different instances of a model can run with different system prompts. However, prompts add tokens on every call, which makes them slower, more expensive and fills up the context window. They’re also more limited in what behaviors they can achieve and may be ignored by the model. In those cases, DPO is a more robust solution and can of course be be combined with prompts.</p>
</section>
<section id="when-you-cant-generate-optimal-answers-at-scale" class="level4">
<h4 class="anchored" data-anchor-id="when-you-cant-generate-optimal-answers-at-scale">2. When you can’t generate optimal answers at scale</h4>
<p>Humans typically have an easier time determining which of two answers is better than developing the best answer on their own. This is especially true for complex outputs, multi-turn conversations, and all matters of style where there are multiple acceptable answers. As an example, it’s faster to judge which of two email texts sounds better than writing the perfect email.</p>
</section>
<section id="when-you-want-to-preserve-previous-behavior" class="level4">
<h4 class="anchored" data-anchor-id="when-you-want-to-preserve-previous-behavior">3. When you want to preserve previous behavior</h4>
<p>DPO is a more measured treatment than SFT and can be regulated with the <img src="https://latex.codecogs.com/png.latex?%5Cbeta"> hyperparameter. This makes it a good choice if the model is already trained and you only want to make small changes. In contrast, SFT is a more aggressive treatment that overrides previous behavior, for example, a model trained on a named entity recognition task would start speaking JSON rather than English.</p>
</section>
<section id="use-cases" class="level3">
<h3 class="anchored" data-anchor-id="use-cases">Use cases</h3>
<p>Let’s consider common LLM use cases and the criteria listed above to decide between SFT+DPO and SFT only.</p>
<table class="caption-top table">
<colgroup>
<col style="width: 17%">
<col style="width: 11%">
<col style="width: 70%">
</colgroup>
<thead>
<tr class="header">
<th>Use Case</th>
<th>Method</th>
<th>Reasoning</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Summarization</td>
<td>SFT+DPO</td>
<td>Humans can easily compare summaries for quality, but writing the perfect summary is harder. Multiple valid summaries exist.</td>
</tr>
<tr class="even">
<td>Code generation</td>
<td>SFT+DPO</td>
<td>Different coding styles and approaches can be valid. Humans can better judge which implementation is more readable/maintainable. SFT can suffice for SQL generation.</td>
</tr>
<tr class="odd">
<td>Question answering</td>
<td>SFT+DPO</td>
<td>Multiple valid answers may exist with varying levels of helpfulness and clarity. Comparing answers is easier than writing the perfect one.</td>
</tr>
<tr class="even">
<td>Writing assistance</td>
<td>SFT+DPO</td>
<td>Writing quality is subjective and context-dependent. Humans can better evaluate style and tone by comparison.</td>
</tr>
<tr class="odd">
<td>Chatbot responses</td>
<td>SFT+DPO</td>
<td>Natural conversation has many valid responses. Comparing helps optimize for engagement and helpfulness.</td>
</tr>
<tr class="even">
<td>Information extraction</td>
<td>SFT only</td>
<td>Tasks like text classification, named entity recognition, relationship extraction, web scraping, and others have one correct answer. DPO is unnecessary.</td>
</tr>
<tr class="odd">
<td>Tool calling</td>
<td>SFT only</td>
<td>Unlike code generation, calls to APIs, data fetching functions and similar are limited in variation and a given user request is usually translated into one optimal set of tool calls.</td>
</tr>
<tr class="even">
<td>Mathematical computation</td>
<td>SFT only</td>
<td>Mathematical problems typically have one correct answer. DPO would be a poor way to teach right and wrong solutions, but may make sense to teach a style of presentation.</td>
</tr>
</tbody>
</table>
<p>Tasks that fully leverage LLM’s free-form input and output tend to benefit most from DPO.</p>
</section>
</section>
<section id="further-reading" class="level2">
<h2 class="anchored" data-anchor-id="further-reading">Further reading</h2>
<ul>
<li>To see DPO in action, I suggest reading Anyscale’s article <a href="https://www.anyscale.com/blog/direct-preference-optimization-with-synthetic-data">Direct Preference Optimization with Synthetic Data</a> which walks through DPO for summarization using synthetic data and LLM as a judge.</li>
<li>If you want to get started with your own project, I suggest <a href="https://pytorch.org/torchtune/stable/recipes/dpo.html">torchtune</a> for training on your own infrastructure and the <a href="https://platform.openai.com/docs/guides/fine-tuning#preference">OpenAI API</a> for a managed service.</li>
<li>If you want to learn more details about DPO, I suggest reading the <a href="https://arxiv.org/abs/2305.18290">paper</a> by Rafailov et al.&nbsp;(2023). There is also a <a href="https://www.youtube.com/live/vuWbJlBePPA?si=18sGG8Vn7D6yykeD">YouTube video</a> of a lecture by one of the authors, Christopher Manning.</li>
<li>Read about variants of DPO in a <a href="https://huggingface.co/blog/pref-tuning">blog post</a> on HuggingFace.</li>
</ul>
<p>Photo by <a href="https://unsplash.com/@max_williams?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash">Max Williams</a> on <a href="https://unsplash.com/photos/multicolored-wallpaper-_OoK2W7OPRM?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash">Unsplash</a></p>


</section>

 ]]></description>
  <category>Machine Learning</category>
  <guid>https://simmering.dev/blog/llm-customization/</guid>
  <pubDate>Sat, 21 Dec 2024 23:00:00 GMT</pubDate>
  <media:content url="https://simmering.dev/blog/llm-customization/image.webp" medium="image" type="image/webp"/>
</item>
<item>
  <title>Type-safe LLM agents with PydanticAI</title>
  <dc:creator>Paul Simmering</dc:creator>
  <link>https://simmering.dev/blog/pydantic-ai/</link>
  <description><![CDATA[ 






<p>Pydantic AI is a new agent framework by the company behind Pydantic, the popular data validation library. Pydantic has transformed how I write Python, so I’m excited for their take on agents. In this article I’ll walk through an example app and comment on my experience developing with PydanticAI.</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>PydanticAI version 0.0.13
</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">
<p>PydanticAI is in beta. This article is based on version 0.0.13. Code examples may not work with future versions. Limitations that are mentioned may be lifted in future versions.</p>
</div>
</div>
</div>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center collapsed" data-bs-toggle="collapse" data-bs-target=".callout-2-contents" aria-controls="callout-2" 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">Tip</span>What is an agent?
</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-2" class="callout-2-contents callout-collapse collapse">
<div class="callout-body-container callout-body">
<p>The term “agent” in the context of LLMs refers to a while loop that calls an LLM to solve a problem. The LLM may be equipped with tools, meaning functions that it can supply arguments to and receive results from. To cut through the marketing hype, I suggest just reading the <a href="https://github.com/pydantic/pydantic-ai/blob/0475da82d5956a2d65678d464328c8f8f8be2bf1/pydantic_ai_slim/pydantic_ai/agent.py#L244">code</a> for PydanticAI’s <code>Agent.run()</code> method.</p>
</div>
</div>
</div>
<p>As an agent framework, PydanticAI lets developers define workflows wherein an LLM interprets a user’s query and can use tools in multiple steps to answer the question or perform a task. Type safety is a big deal in agent development - the LLM has to call tools with the correct arguments and the tools have to return the correct data type. PydanticAI brings the type safety of Pydantic to this space. This also speeds up development, because type checkers like mypy and pyright can catch errors before the code is run.</p>
<p>In addition to type safety, PydanticAI offers:</p>
<ul>
<li>streaming responses, including structured responses</li>
<li>support for async tool calling</li>
<li>support for multiple LLM providers, including OpenAI, Groq, Anthropic, Gemini, Ollama and Mistral, with more to come</li>
<li>optional integration with <a href="https://pydantic.dev/logfire">Logfire</a>, a commercial service by the Pydantic team for logging LLM calls</li>
</ul>
<section id="example-app-market-research-knowledge-manager" class="level2">
<h2 class="anchored" data-anchor-id="example-app-market-research-knowledge-manager">Example app: Market research knowledge manager</h2>
<p>Large companies conduct market research to understand their customers, competition and market trends. Over time, they amass a library of thousands of reports, tables and transcripts. Knowledge management becomes a challenge, because teams are not aware of existing research.</p>
<p>Let’s build an example agent that answers questions based on information in a database with multiple tables. Our final agentic RAG system will enable an interaction 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">%%{init: {
  'theme': 'base',
  'themeVariables': {
    'primaryColor': '#ffffff',
    'primaryTextColor': '#2d3748',
    'primaryBorderColor': '#90cdf4',
    'lineColor': '#64748b',
    'secondaryColor': '#ffffff',
    'tertiaryColor': '#ffffff',
    'fontSize': '22px',
    'labelFontSize': '18px',
    'edgeLabelFontSize': '18px'
  }
}}%%
graph LR
    %% Define styles
    classDef default fill:#ffffff,stroke:#90cdf4,stroke-width:2px
    classDef highlight fill:#fdf2f8,stroke:#ed64a6,stroke-width:3px
    classDef api fill:#ffffff,stroke:#4fd1c5,stroke-width:2px

    User([User]) --&gt; |"What reports do we have about electric vehicles?"| Agent
    Agent --&gt; |"Analyze user query"| Groq[LLM Provider Groq]
    Groq --&gt; |"Tool selection"| Agent
    
    Agent --&gt; |"Search topic='Automotive'"| Tool1[tool: search_reports_by_field]
    Agent --&gt; |"Search 'electric vehicles'"| Tool2[tool: search_reports_by_title_similarity]
    
    Tool1 --&gt; |"Query"| DB[(DuckDB)]
    Tool2 --&gt; |"Vector similarity"| DB
    
    Tool1 --&gt; |"Found 2 reports"| Agent
    Tool2 --&gt; |"Found similar titles"| Agent
    
    Agent --&gt; |"There are 2 reports about EVs:
    1. German EV Market Analysis 2024
    2. EV Adoption in Asia"| User

    %% Apply styles
    class Groq api
    class DB highlight

    %% Links between nodes
    linkStyle default stroke:#64748b,stroke-width:2px
</pre>
</div>
<p></p></figure><p></p>
</div>
</div>
</div>
<section id="database" class="level3">
<h3 class="anchored" data-anchor-id="database">Database</h3>
<p>I’m using <a href="https://duckdb.org">DuckDB</a> to create an in-memory database which will be made available to the agent.</p>
<div id="8332ed09" class="cell" data-execution_count="3">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="annotated-cell-1" style="background: #f1f3f5;"><pre class="sourceCode python code-annotation-code code-with-copy code-annotated"><code class="sourceCode python"><span id="annotated-cell-1-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> duckdb</span>
<span id="annotated-cell-1-2"></span>
<button class="code-annotation-anchor" data-target-cell="annotated-cell-1" data-target-annotation="1">1</button><span id="annotated-cell-1-3" class="code-annotation-target">con <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> duckdb.<span class="ex" style="color: null;
background-color: null;
font-style: inherit;">connect</span>()</span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
<div class="cell-annotation">
<dl class="code-annotation-container-hidden code-annotation-container-grid">
<dt data-target-cell="annotated-cell-1" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-1" data-code-lines="3" data-code-annotation="1">Create a local database. In production you’d want to use a persistent database.</span>
</dd>
</dl>
</div>
</div>
<p>I’ll insert a set of reports into the database. The data included is fictional and was generated by an LLM. The data consists of 40 reports like this:</p>
<div id="1554a29c" class="cell" data-execution_count="4">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python 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> polars <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> pl</span>
<span id="cb1-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> great_tables <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> GT</span>
<span id="cb1-3"></span>
<span id="cb1-4">reports <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pl.read_csv(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"data/reports.csv"</span>)</span>
<span id="cb1-5">GT(reports.head(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">5</span>))</span></code></pre></div></div>
<div class="cell-output cell-output-display" data-execution_count="4">
<div id="ylufodktok" style="padding-left:0px;padding-right:0px;padding-top:10px;padding-bottom:10px;overflow-x:auto;overflow-y:auto;width:auto;height:auto;">
<style>
#ylufodktok table {
          font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Helvetica Neue', 'Fira Sans', 'Droid Sans', Arial, sans-serif;
          -webkit-font-smoothing: antialiased;
          -moz-osx-font-smoothing: grayscale;
        }

#ylufodktok thead, tbody, tfoot, tr, td, th { border-style: none; }
 tr { background-color: transparent; }
#ylufodktok p { margin: 0; padding: 0; }
 #ylufodktok .gt_table { display: table; border-collapse: collapse; line-height: normal; margin-left: auto; margin-right: auto; color: #333333; font-size: 16px; font-weight: normal; font-style: normal; background-color: #FFFFFF; width: auto; border-top-style: solid; border-top-width: 2px; border-top-color: #A8A8A8; border-right-style: none; border-right-width: 2px; border-right-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #A8A8A8; border-left-style: none; border-left-width: 2px; border-left-color: #D3D3D3; }
 #ylufodktok .gt_caption { padding-top: 4px; padding-bottom: 4px; }
 #ylufodktok .gt_title { color: #333333; font-size: 125%; font-weight: initial; padding-top: 4px; padding-bottom: 4px; padding-left: 5px; padding-right: 5px; border-bottom-color: #FFFFFF; border-bottom-width: 0; }
 #ylufodktok .gt_subtitle { color: #333333; font-size: 85%; font-weight: initial; padding-top: 3px; padding-bottom: 5px; padding-left: 5px; padding-right: 5px; border-top-color: #FFFFFF; border-top-width: 0; }
 #ylufodktok .gt_heading { background-color: #FFFFFF; text-align: center; border-bottom-color: #FFFFFF; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; }
 #ylufodktok .gt_bottom_border { border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; }
 #ylufodktok .gt_col_headings { border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; }
 #ylufodktok .gt_col_heading { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: normal; text-transform: inherit; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: bottom; padding-top: 5px; padding-bottom: 5px; padding-left: 5px; padding-right: 5px; overflow-x: hidden; }
 #ylufodktok .gt_column_spanner_outer { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: normal; text-transform: inherit; padding-top: 0; padding-bottom: 0; padding-left: 4px; padding-right: 4px; }
 #ylufodktok .gt_column_spanner_outer:first-child { padding-left: 0; }
 #ylufodktok .gt_column_spanner_outer:last-child { padding-right: 0; }
 #ylufodktok .gt_column_spanner { border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; vertical-align: bottom; padding-top: 5px; padding-bottom: 5px; overflow-x: hidden; display: inline-block; width: 100%; }
 #ylufodktok .gt_spanner_row { border-bottom-style: hidden; }
 #ylufodktok .gt_group_heading { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; text-transform: inherit; border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; text-align: left; }
 #ylufodktok .gt_empty_group_heading { padding: 0.5px; color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; vertical-align: middle; }
 #ylufodktok .gt_from_md> :first-child { margin-top: 0; }
 #ylufodktok .gt_from_md> :last-child { margin-bottom: 0; }
 #ylufodktok .gt_row { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; }
 #ylufodktok .gt_stub { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; text-transform: inherit; border-right-style: solid; border-right-width: 2px; border-right-color: #D3D3D3; padding-left: 5px; padding-right: 5px; }
 #ylufodktok .gt_stub_row_group { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; text-transform: inherit; border-right-style: solid; border-right-width: 2px; border-right-color: #D3D3D3; padding-left: 5px; padding-right: 5px; vertical-align: top; }
 #ylufodktok .gt_row_group_first td { border-top-width: 2px; }
 #ylufodktok .gt_row_group_first th { border-top-width: 2px; }
 #ylufodktok .gt_striped { background-color: rgba(128,128,128,0.05); }
 #ylufodktok .gt_table_body { border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; }
 #ylufodktok .gt_sourcenotes { color: #333333; background-color: #FFFFFF; border-bottom-style: none; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 2px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 2px; border-right-color: #D3D3D3; }
 #ylufodktok .gt_sourcenote { font-size: 90%; padding-top: 4px; padding-bottom: 4px; padding-left: 5px; padding-right: 5px; text-align: left; }
 #ylufodktok .gt_left { text-align: left; }
 #ylufodktok .gt_center { text-align: center; }
 #ylufodktok .gt_right { text-align: right; font-variant-numeric: tabular-nums; }
 #ylufodktok .gt_font_normal { font-weight: normal; }
 #ylufodktok .gt_font_bold { font-weight: bold; }
 #ylufodktok .gt_font_italic { font-style: italic; }
 #ylufodktok .gt_super { font-size: 65%; }
 #ylufodktok .gt_footnote_marks { font-size: 75%; vertical-align: 0.4em; position: initial; }
 #ylufodktok .gt_asterisk { font-size: 100%; vertical-align: 0; }
 
</style>

<table class="gt_table caption-top table table-sm table-striped small" data-quarto-bootstrap="false">
<thead>
<tr class="gt_col_headings header">
<th id="id" class="gt_col_heading gt_columns_bottom_border gt_right" data-quarto-table-cell-role="th" scope="col">id</th>
<th id="year" class="gt_col_heading gt_columns_bottom_border gt_right" data-quarto-table-cell-role="th" scope="col">year</th>
<th id="institute" class="gt_col_heading gt_columns_bottom_border gt_left" data-quarto-table-cell-role="th" scope="col">institute</th>
<th id="country" class="gt_col_heading gt_columns_bottom_border gt_left" data-quarto-table-cell-role="th" scope="col">country</th>
<th id="topic" class="gt_col_heading gt_columns_bottom_border gt_left" data-quarto-table-cell-role="th" scope="col">topic</th>
<th id="title" class="gt_col_heading gt_columns_bottom_border gt_left" data-quarto-table-cell-role="th" scope="col">title</th>
</tr>
</thead>
<tbody class="gt_table_body">
<tr class="odd">
<td class="gt_row gt_right">1</td>
<td class="gt_row gt_right">2018</td>
<td class="gt_row gt_left">Research DNA GmbH</td>
<td class="gt_row gt_left">Germany</td>
<td class="gt_row gt_left">Automotive</td>
<td class="gt_row gt_left">Global Electric Vehicle Market Outlook 2018-2023</td>
</tr>
<tr class="even">
<td class="gt_row gt_right">2</td>
<td class="gt_row gt_right">2018</td>
<td class="gt_row gt_left">Market Insights Inc.</td>
<td class="gt_row gt_left">USA</td>
<td class="gt_row gt_left">Healthcare</td>
<td class="gt_row gt_left">Digital Health Market Size and Growth Analysis</td>
</tr>
<tr class="odd">
<td class="gt_row gt_right">3</td>
<td class="gt_row gt_right">2018</td>
<td class="gt_row gt_left">Global Trends Research</td>
<td class="gt_row gt_left">UK</td>
<td class="gt_row gt_left">FMCG</td>
<td class="gt_row gt_left">Premium Beauty and Personal Care Market Trends</td>
</tr>
<tr class="even">
<td class="gt_row gt_right">4</td>
<td class="gt_row gt_right">2018</td>
<td class="gt_row gt_left">Data Analytics Group</td>
<td class="gt_row gt_left">Canada</td>
<td class="gt_row gt_left">Electronics</td>
<td class="gt_row gt_left">Smartphone Industry Competitive Analysis</td>
</tr>
<tr class="odd">
<td class="gt_row gt_right">5</td>
<td class="gt_row gt_right">2018</td>
<td class="gt_row gt_left">Innovative Solutions Ltd.</td>
<td class="gt_row gt_left">Australia</td>
<td class="gt_row gt_left">Insurance</td>
<td class="gt_row gt_left">Insurtech Market Landscape and Opportunities</td>
</tr>
</tbody>
</table>


</div>
</div>
</div>
<p>To make the title searchable, I’ll embed it using an <a href="https://platform.openai.com/docs/guides/embeddings">OpenAI embedding endpoint</a>. The result will be stored in a new column with 1536 dimensions.</p>
<div id="56ef65bc" class="cell" data-execution_count="5">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode python 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> openai <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> OpenAI</span>
<span id="cb2-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> tqdm <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> tqdm</span>
<span id="cb2-3"></span>
<span id="cb2-4"></span>
<span id="cb2-5"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> embed_text(text: <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;">float</span>]:</span>
<span id="cb2-6">    client <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> OpenAI()</span>
<span id="cb2-7">    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;">"text-embedding-3-small"</span></span>
<span id="cb2-8">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> client.embeddings.create(<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>text, model<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>model).data[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].embedding</span>
<span id="cb2-9"></span>
<span id="cb2-10"></span>
<span id="cb2-11">title_embeddings <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [embed_text(title) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> title <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> tqdm(reports[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"title"</span>])]</span>
<span id="cb2-12"></span>
<span id="cb2-13">reports <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> reports.with_columns(</span>
<span id="cb2-14">    pl.Series(</span>
<span id="cb2-15">        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;">"title_embedding"</span>,</span>
<span id="cb2-16">        values<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>title_embeddings,</span>
<span id="cb2-17">        dtype<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>pl.Array(inner<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>pl.Float64, shape<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;">1536</span>),</span>
<span id="cb2-18">    )</span>
<span id="cb2-19">)</span></code></pre></div></div>
</div>
<p>Now, I’ll insert the data including the embeddings into the database. The embeddings are stored in a fixed-size <code>ARRAY</code> column. The co-location of the structured data and the embeddings in the same table is convenient for our use case.</p>
<div id="8f1bb9e3" class="cell" data-execution_count="6">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="annotated-cell-4" style="background: #f1f3f5;"><pre class="sourceCode python code-annotation-code code-with-copy code-annotated"><code class="sourceCode python"><span id="annotated-cell-4-1">con.execute(</span>
<span id="annotated-cell-4-2">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="annotated-cell-4-3"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    CREATE OR REPLACE TABLE reports AS</span></span>
<span id="annotated-cell-4-4"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    SELECT</span></span>
<span id="annotated-cell-4-5"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">        id::integer AS id,</span></span>
<span id="annotated-cell-4-6"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">        year::integer AS year,</span></span>
<span id="annotated-cell-4-7"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">        institute::varchar AS institute,</span></span>
<span id="annotated-cell-4-8"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">        country::varchar AS country,</span></span>
<span id="annotated-cell-4-9"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">        topic::varchar AS topic,</span></span>
<span id="annotated-cell-4-10"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">        title::varchar AS title,</span></span>
<span id="annotated-cell-4-11"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">        title_embedding::float[1536] AS title_embedding</span></span>
<span id="annotated-cell-4-12"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    FROM reports;</span></span>
<button class="code-annotation-anchor" data-target-cell="annotated-cell-4" data-target-annotation="1">1</button><span id="annotated-cell-4-13" class="code-annotation-target"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    """</span></span>
<span id="annotated-cell-4-14">)</span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
<div class="cell-annotation">
<dl class="code-annotation-container-hidden code-annotation-container-grid">
<dt data-target-cell="annotated-cell-4" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-4" data-code-lines="13" data-code-annotation="1">This works because DuckDB can read from a Polars DataFrame.</span>
</dd>
</dl>
</div>
</div>
<div id="e1503c45" class="cell" data-execution_count="7">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb3-1">con.execute(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"INSTALL vss;"</span>)</span>
<span id="cb3-2">con.execute(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"LOAD vss;"</span>)</span>
<span id="cb3-3"></span>
<span id="cb3-4">con.execute(</span>
<span id="cb3-5">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"CREATE INDEX titles_hnsw_index ON reports USING HNSW(title_embedding) WITH (metric='cosine');"</span></span>
<span id="cb3-6">)</span></code></pre></div></div>
</div>
<p>I also create a hierarchical navigable small world (HNSW) index on the title embeddings. This enables approximate nearest neighbor search in O(log n). It’s enabled by the <a href="https://duckdb.org/docs/extensions/vss">vss</a> extension. Note that persistence to disk is experimental, so I wouldn’t recommend it for production yet.</p>
</section>
<section id="agent" class="level3">
<h3 class="anchored" data-anchor-id="agent">Agent</h3>
<p>Let’s set up an agent powered by the <a href="https://groq.com">Groq</a> inference API. It serves a range of open source models. Specifically, I’ll use the <code>llama-3.3-70b-versatile</code> model released by Meta on December 6th. Artificial Analysis has a detailed <a href="https://artificialanalysis.ai/models/llama-3-3-instruct-70b/providers">report</a> showing that it advanced the speed-accuracy trade-off. The model has tool calling capabilities, which are critical for our use case.</p>
<div id="6c615539" class="cell" data-execution_count="8">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="annotated-cell-6" style="background: #f1f3f5;"><pre class="sourceCode python code-annotation-code code-with-copy code-annotated"><code class="sourceCode python"><span id="annotated-cell-6-1"><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="annotated-cell-6-2"></span>
<span id="annotated-cell-6-3">agent <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Agent(</span>
<button class="code-annotation-anchor" data-target-cell="annotated-cell-6" data-target-annotation="1">1</button><span id="annotated-cell-6-4" class="code-annotation-target">    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;">"groq:llama-3.3-70b-versatile"</span>,</span>
<span id="annotated-cell-6-5">    system_prompt<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 market research expert and answer questions using a database of reports."</span>,</span>
<span id="annotated-cell-6-6">)</span>
<span id="annotated-cell-6-7"></span>
<span id="annotated-cell-6-8">result <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> agent.run_sync(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Who are you?"</span>)</span>
<span id="annotated-cell-6-9"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(result.data)</span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
<div class="cell-annotation">
<dl class="code-annotation-container-hidden code-annotation-container-grid">
<dt data-target-cell="annotated-cell-6" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-6" data-code-lines="4" data-code-annotation="1">See the <a href="https://ai.pydantic.dev/api/models/base/#pydantic_ai.models.KnownModelName">KnownModelName</a> documentation for a list of supported models.</span>
</dd>
</dl>
</div>
<div class="cell-output cell-output-stdout">
<pre><code>I am a market research expert, providing insights and analysis based on a vast database of reports and studies. My expertise spans various industries, including consumer goods, technology, healthcare, and finance. I can help answer questions, provide data-driven insights, and offer market trends and analysis to support business decisions.

My database includes reports from reputable sources, such as market research firms, academic institutions, and industry associations. I can access a wide range of topics, including market size and growth, consumer behavior, competitor analysis, and emerging trends.

What specific area of market research would you like to explore?</code></pre>
</div>
</div>
</section>
<section id="tools" class="level3">
<h3 class="anchored" data-anchor-id="tools">Tools</h3>
<p>The agent’s job will be to answer questions based on the reports in the database. It needs a way to access the database. We can give it a tool, meaning a function that it can call, to query the database. First, it needs a database connection.</p>
<div id="1b732c95" class="cell" data-execution_count="9">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="annotated-cell-7" style="background: #f1f3f5;"><pre class="sourceCode python code-annotation-code code-with-copy code-annotated"><code class="sourceCode python"><span id="annotated-cell-7-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> dataclasses <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> dataclass</span>
<span id="annotated-cell-7-2"></span>
<span id="annotated-cell-7-3"></span>
<span id="annotated-cell-7-4"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@dataclass</span></span>
<button class="code-annotation-anchor" data-target-cell="annotated-cell-7" data-target-annotation="1">1</button><span id="annotated-cell-7-5" class="code-annotation-target"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> AgentDependencies:</span>
<span id="annotated-cell-7-6">    db: duckdb.DuckDBPyConnection</span>
<span id="annotated-cell-7-7"></span>
<span id="annotated-cell-7-8"></span>
<button class="code-annotation-anchor" data-target-cell="annotated-cell-7" data-target-annotation="2">2</button><span id="annotated-cell-7-9" class="code-annotation-target">deps <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> AgentDependencies(db<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>con)</span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
<div class="cell-annotation">
<dl class="code-annotation-container-hidden code-annotation-container-grid">
<dt data-target-cell="annotated-cell-7" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-7" data-code-lines="5" data-code-annotation="1">A dataclass that contains dependencies needed by the agent. Additional dependencies can be added as needed.</span>
</dd>
<dt data-target-cell="annotated-cell-7" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-7" data-code-lines="9" data-code-annotation="2">This is the connection that has the connection to the in-memory DuckDBdatabase.</span>
</dd>
</dl>
</div>
</div>
<p>Next, let’s give the agent a tool to search the database of reports. Based on the user’s question, it can choose which field to search. The result is always a markdown-formatted table with one row per report.</p>
<div id="4aa0faf3" class="cell" data-execution_count="10">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="annotated-cell-8" style="background: #f1f3f5;"><pre class="sourceCode python code-annotation-code code-with-copy code-annotated"><code class="sourceCode python"><span id="annotated-cell-8-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> json</span>
<span id="annotated-cell-8-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="annotated-cell-8-3"><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> RunContext</span>
<span id="annotated-cell-8-4"><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> validate_call, Field</span>
<span id="annotated-cell-8-5"></span>
<span id="annotated-cell-8-6"></span>
<button class="code-annotation-anchor" data-target-cell="annotated-cell-8" data-target-annotation="1">1</button><span id="annotated-cell-8-7" class="code-annotation-target"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> df_to_str(df: pl.DataFrame) <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="annotated-cell-8-8">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> json.dumps(df.to_dicts())</span>
<span id="annotated-cell-8-9"></span>
<span id="annotated-cell-8-10"></span>
<button class="code-annotation-anchor" data-target-cell="annotated-cell-8" data-target-annotation="2">2</button><span id="annotated-cell-8-11" class="code-annotation-target"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@agent.tool</span></span>
<button class="code-annotation-anchor" data-target-cell="annotated-cell-8" data-target-annotation="3">3</button><span id="annotated-cell-8-12" class="code-annotation-target"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@validate_call</span>(config<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;">"arbitrary_types_allowed"</span>: <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>})</span>
<span id="annotated-cell-8-13"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> search_reports_by_field(</span>
<button class="code-annotation-anchor" data-target-cell="annotated-cell-8" data-target-annotation="4">4</button><span id="annotated-cell-8-14" class="code-annotation-target">    ctx: RunContext[AgentDependencies],</span>
<button class="code-annotation-anchor" data-target-cell="annotated-cell-8" data-target-annotation="5">5</button><span id="annotated-cell-8-15" class="code-annotation-target">    field: Literal[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"id"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"year"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"institute"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"country"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"topic"</span>],</span>
<span id="annotated-cell-8-16">    value: <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="annotated-cell-8-17">        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 value to search for in the field. Case insensitive."</span></span>
<span id="annotated-cell-8-18">    ),</span>
<span id="annotated-cell-8-19">) <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="annotated-cell-8-20">    base_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;">"""</span></span>
<span id="annotated-cell-8-21"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">        SELECT id, year, institute, country, topic, title </span></span>
<span id="annotated-cell-8-22"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">        FROM reports </span></span>
<span id="annotated-cell-8-23"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">        WHERE </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{}</span></span>
<span id="annotated-cell-8-24"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    """</span></span>
<span id="annotated-cell-8-25"></span>
<span id="annotated-cell-8-26">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> field <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;">"id"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"year"</span>]:</span>
<span id="annotated-cell-8-27">        value <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;">int</span>(value)</span>
<span id="annotated-cell-8-28">        where_clause <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>field<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="annotated-cell-8-29">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span>:</span>
<span id="annotated-cell-8-30">        where_clause <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"lower(</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>field<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;">) = lower(?)"</span></span>
<span id="annotated-cell-8-31"></span>
<span id="annotated-cell-8-32">    final_query <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> base_query.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">format</span>(where_clause)</span>
<button class="code-annotation-anchor" data-target-cell="annotated-cell-8" data-target-annotation="6">6</button><span id="annotated-cell-8-33" class="code-annotation-target">    df <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ctx.deps.db.execute(final_query, [value]).pl()</span>
<span id="annotated-cell-8-34"></span>
<span id="annotated-cell-8-35">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> df.shape[<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;">0</span>:</span>
<button class="code-annotation-anchor" data-target-cell="annotated-cell-8" data-target-annotation="7">7</button><span id="annotated-cell-8-36" class="code-annotation-target">        <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;">"No reports found. Try a different field or value, or use the title similarity tool."</span></span>
<span id="annotated-cell-8-37">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> df_to_str(df)</span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
<div class="cell-annotation">
<dl class="code-annotation-container-hidden code-annotation-container-grid">
<dt data-target-cell="annotated-cell-8" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-8" data-code-lines="7" data-code-annotation="1">A record-oriented JSON representation of the data frame is understand by an LLM.</span>
</dd>
<dt data-target-cell="annotated-cell-8" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-8" data-code-lines="11" data-code-annotation="2">Use the <code>@agent.tool</code> decorator to register the function as a tool.</span>
</dd>
<dt data-target-cell="annotated-cell-8" data-target-annotation="3">3</dt>
<dd>
<span data-code-cell="annotated-cell-8" data-code-lines="12" data-code-annotation="3">Use the <code>@validate_call</code> decorator to enable type checking of the function arguments. This makes sure that only the fields present in the database can be used. <code>arbitrary_types_allowed</code> is required because the <code>RunContext</code> type is not a standard type.</span>
</dd>
<dt data-target-cell="annotated-cell-8" data-target-annotation="4">4</dt>
<dd>
<span data-code-cell="annotated-cell-8" data-code-lines="14" data-code-annotation="4">The <code>RunContext</code> type hint is required for the tool to access the dependencies.</span>
</dd>
<dt data-target-cell="annotated-cell-8" data-target-annotation="5">5</dt>
<dd>
<span data-code-cell="annotated-cell-8" data-code-lines="15" data-code-annotation="5">Tell the model about the available fields in the database and validate that only those are selected.</span>
</dd>
<dt data-target-cell="annotated-cell-8" data-target-annotation="6">6</dt>
<dd>
<span data-code-cell="annotated-cell-8" data-code-lines="33" data-code-annotation="6">The database query returns a polars DataFrame.</span>
</dd>
<dt data-target-cell="annotated-cell-8" data-target-annotation="7">7</dt>
<dd>
<span data-code-cell="annotated-cell-8" data-code-lines="36" data-code-annotation="7">Provide a clear message if no reports are found and hint that another function (which will be introduced later) can be used for fuzzy matching.</span>
</dd>
</dl>
</div>
</div>
<p>This lets the agent execute searches based on the exact match of a field.</p>
<div id="5f301a90" class="cell" data-execution_count="11">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb5-1">deps <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> AgentDependencies(db<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>con)</span>
<span id="cb5-2">result <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> agent.run_sync(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Which reports do we have from Germany?"</span>, deps<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>deps)</span>
<span id="cb5-3"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(result.data)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>We have four reports from Germany:

1. "Global Electric Vehicle Market Outlook 2018-2023" by Research DNA GmbH (2018) - Automotive topic
2. "Digital Advertising Spend Analysis" by Tech Innovations Ltd. (2020) - Media topic
3. "Beverage Market Competitive Analysis" by Research DNA GmbH (2022) - FMCG topic
4. "Medical Imaging Equipment Market Size" by Tech Innovations Ltd. (2024) - Healthcare topic

Let me know if you'd like more information about any of these reports.</code></pre>
</div>
</div>
<p>It works, the agent found the 4 reports from Germany. Let’s check the exact tool call:</p>
<div id="1e729fc6" class="cell" data-execution_count="12">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb7-1">agent.last_run_messages</span></code></pre></div></div>
<div class="cell-output cell-output-display" data-execution_count="12">
<pre><code>[ModelRequest(parts=[SystemPromptPart(content='You are a market research expert and answer questions using a database of reports.', part_kind='system-prompt'), UserPromptPart(content='Which reports do we have from Germany?', timestamp=datetime.datetime(2024, 12, 18, 17, 38, 58, 663721, tzinfo=datetime.timezone.utc), part_kind='user-prompt')], kind='request'),
 ModelResponse(parts=[ToolCallPart(tool_name='search_reports_by_field', args=ArgsJson(args_json='{"field": "country", "value": "Germany"}'), tool_call_id='call_be61', part_kind='tool-call')], timestamp=datetime.datetime(2024, 12, 18, 17, 38, 58, tzinfo=datetime.timezone.utc), kind='response'),
 ModelRequest(parts=[ToolReturnPart(tool_name='search_reports_by_field', content='[{"id": 1, "year": 2018, "institute": "Research DNA GmbH", "country": "Germany", "topic": "Automotive", "title": "Global Electric Vehicle Market Outlook 2018-2023"}, {"id": 12, "year": 2020, "institute": "Tech Innovations Ltd.", "country": "Germany", "topic": "Media", "title": "Digital Advertising Spend Analysis"}, {"id": 21, "year": 2022, "institute": "Research DNA GmbH", "country": "Germany", "topic": "FMCG", "title": "Beverage Market Competitive Analysis"}, {"id": 32, "year": 2024, "institute": "Tech Innovations Ltd.", "country": "Germany", "topic": "Healthcare", "title": "Medical Imaging Equipment Market Size"}]', tool_call_id='call_be61', timestamp=datetime.datetime(2024, 12, 18, 17, 38, 59, 27429, tzinfo=datetime.timezone.utc), part_kind='tool-return')], kind='request'),
 ModelResponse(parts=[TextPart(content='We have four reports from Germany:\n\n1. "Global Electric Vehicle Market Outlook 2018-2023" by Research DNA GmbH (2018) - Automotive topic\n2. "Digital Advertising Spend Analysis" by Tech Innovations Ltd. (2020) - Media topic\n3. "Beverage Market Competitive Analysis" by Research DNA GmbH (2022) - FMCG topic\n4. "Medical Imaging Equipment Market Size" by Tech Innovations Ltd. (2024) - Healthcare topic\n\nLet me know if you\'d like more information about any of these reports.', part_kind='text')], timestamp=datetime.datetime(2024, 12, 18, 17, 38, 59, tzinfo=datetime.timezone.utc), kind='response')]</code></pre>
</div>
</div>
<p>Here, the model correctly translated the user’s question into the tool call with the arguments <code>{"field": "country", "value": "Germany"}</code>.</p>
<p>To make it easier to evaluate the agent’s output and also make its results useable by other tools, we can create a response model that includes the ids of the identified reports.</p>
<div id="9dc4de41" class="cell" data-execution_count="13">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="annotated-cell-11" style="background: #f1f3f5;"><pre class="sourceCode python code-annotation-code code-with-copy code-annotated"><code class="sourceCode python"><span id="annotated-cell-11-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="annotated-cell-11-2"></span>
<span id="annotated-cell-11-3"></span>
<span id="annotated-cell-11-4"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> AgentResponse(BaseModel):</span>
<span id="annotated-cell-11-5">    text: <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="annotated-cell-11-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;">"Answer to the user's question in informal language. Don't include the report ids."</span></span>
<span id="annotated-cell-11-7">    )</span>
<span id="annotated-cell-11-8">    relevant_report_ids: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">set</span>[<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>
<button class="code-annotation-anchor" data-target-cell="annotated-cell-11" data-target-annotation="1">1</button><span id="annotated-cell-11-9" class="code-annotation-target">        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;">"Set of 'id' integer values of the reports that are relevant to the user's question. Only include ids retrieved by the search tools. Never make up ids. Not all ids returned by the search tools are relevant."</span></span>
<span id="annotated-cell-11-10">    )</span>
<span id="annotated-cell-11-11"></span>
<span id="annotated-cell-11-12"></span>
<span id="annotated-cell-11-13">typed_agent <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Agent(</span>
<span id="annotated-cell-11-14">    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;">"groq:llama-3.3-70b-versatile"</span>,</span>
<span id="annotated-cell-11-15">    system_prompt<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 market research expert and answer questions using a database of reports."</span>,</span>
<span id="annotated-cell-11-16">    result_type<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>AgentResponse,</span>
<button class="code-annotation-anchor" data-target-cell="annotated-cell-11" data-target-annotation="2">2</button><span id="annotated-cell-11-17" class="code-annotation-target">    result_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="annotated-cell-11-18">)</span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
<div class="cell-annotation">
<dl class="code-annotation-container-hidden code-annotation-container-grid">
<dt data-target-cell="annotated-cell-11" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-11" data-code-lines="9" data-code-annotation="1">This description fixes a common mistake: the LLM would answer with made up ids like 123, 456 when it didn’t find any reports.</span>
</dd>
<dt data-target-cell="annotated-cell-11" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-11" data-code-lines="17" data-code-annotation="2">Give the agent a chance to retry if it doesn’t return a valid structured output on the first try.</span>
</dd>
</dl>
</div>
</div>
<p>The <code>AgentResponse</code> model is used to validate the agent’s output. It will always include a set of integer ids. In an app, these could be used to provide links to the reports.</p>
<div id="837e1955" class="cell" data-execution_count="15">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb9-1">result <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> typed_agent.run_sync(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Which reports do we have from Germany? Tell me their titles and ids"</span>, deps<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>deps)</span>
<span id="cb9-2"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(result.data)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>text='The reports from Germany are titled Global Electric Vehicle Market Outlook 2018-2023, Digital Advertising Spend Analysis, Beverage Market Competitive Analysis and Medical Imaging Equipment Market Size.' relevant_report_ids={32, 1, 12, 21}</code></pre>
</div>
</div>
<p>Now we have an agent that returns a type-checked structured response. Note that I’ve omitted the re-registration of the tool to the new agent instance for brevity.</p>
<p>However, requests may not exactly match the fields in the database, so let’s also add the ability to search for similar titles.</p>
<div id="c0d28b41" class="cell" data-execution_count="16">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="annotated-cell-13" style="background: #f1f3f5;"><pre class="sourceCode python code-annotation-code code-with-copy code-annotated"><code class="sourceCode python"><span id="annotated-cell-13-1"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@typed_agent.tool</span></span>
<span id="annotated-cell-13-2"><span class="at" style="color: #657422;
background-color: null;
font-style: inherit;">@validate_call</span>(config<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;">"arbitrary_types_allowed"</span>: <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">True</span>})</span>
<span id="annotated-cell-13-3"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> search_reports_by_title_similarity(</span>
<span id="annotated-cell-13-4">    ctx: RunContext[AgentDependencies],</span>
<span id="annotated-cell-13-5">    title: <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="annotated-cell-13-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;">"The title of the report to search for with vector similarity."</span></span>
<span id="annotated-cell-13-7">    ),</span>
<span id="annotated-cell-13-8">) <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="annotated-cell-13-9">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Embed the title given by the user</span></span>
<span id="annotated-cell-13-10">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">try</span>:</span>
<span id="annotated-cell-13-11">        title_embedding <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> embed_text(title)</span>
<span id="annotated-cell-13-12">    <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="annotated-cell-13-13">        <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 embedding title: </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="annotated-cell-13-14"></span>
<span id="annotated-cell-13-15">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Search for similar titles</span></span>
<button class="code-annotation-anchor" data-target-cell="annotated-cell-13" data-target-annotation="1">1</button><span id="annotated-cell-13-16" class="code-annotation-target">    title_embedding_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="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">","</span>.join(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">map</span>(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>, title_embedding)) <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>
<span id="annotated-cell-13-17">    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;">"""</span></span>
<span id="annotated-cell-13-18"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">        SELECT id, year, institute, country, topic, title</span></span>
<span id="annotated-cell-13-19"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">        FROM reports</span></span>
<span id="annotated-cell-13-20"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">        ORDER BY array_distance(title_embedding, ?::FLOAT[1536])  </span></span>
<span id="annotated-cell-13-21"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">        LIMIT 5;</span></span>
<button class="code-annotation-anchor" data-target-cell="annotated-cell-13" data-target-annotation="2">2</button><span id="annotated-cell-13-22" class="code-annotation-target"><span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">    """</span></span>
<span id="annotated-cell-13-23">    df <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> ctx.deps.db.execute(query, [title_embedding_str]).pl()</span>
<span id="annotated-cell-13-24"></span>
<span id="annotated-cell-13-25">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> (</span>
<span id="annotated-cell-13-26">        df_to_str(df)</span>
<button class="code-annotation-anchor" data-target-cell="annotated-cell-13" data-target-annotation="3">3</button><span id="annotated-cell-13-27" class="code-annotation-target">        <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;"> These reports have titles similar to the query, but may not be relevant to the user's question."</span></span>
<span id="annotated-cell-13-28">    )</span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
<div class="cell-annotation">
<dl class="code-annotation-container-hidden code-annotation-container-grid">
<dt data-target-cell="annotated-cell-13" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-13" data-code-lines="16" data-code-annotation="1">The title is embedded and formatted as a DuckDB array.</span>
</dd>
<dt data-target-cell="annotated-cell-13" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-13" data-code-lines="22" data-code-annotation="2">The <code>array_distance</code> function computes the cosine similarity between the query embedding and the title embeddings in the database.</span>
</dd>
<dt data-target-cell="annotated-cell-13" data-target-annotation="3">3</dt>
<dd>
<span data-code-cell="annotated-cell-13" data-code-lines="27" data-code-annotation="3">The note about relevance is added to make it clear that these are just the most similar, not necessarily relevant. Otherwise the agent would return all reports with similar titles.</span>
</dd>
</dl>
</div>
</div>
<p>Let’s ask the agent about a topic that is not in the database to see how it uses the title similarity tool.</p>
<div id="2b25b6e8" class="cell" data-execution_count="17">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb11" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb11-1">result <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> agent.run_sync(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Do we have reports about quantum computing?"</span>, deps<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>deps)</span>
<span id="cb11-2"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(result.data)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>&lt;function=search_reports_by_field {"field": "topic", "value": "quantum computing"}&lt;/function&gt;</code></pre>
</div>
</div>
<p>That worked as expected.</p>
</section>
</section>
<section id="evals" class="level2">
<h2 class="anchored" data-anchor-id="evals">Evals</h2>
<p>Automated evaluations are necessary to ensure that an agent is working as expected, and to switch out models, prompts and tools without breaking the app. PydanticAI offers <a href="https://ai.pydantic.dev/testing-evals/">tools</a> for testing the code (without running a model) and for evaluations. Let’s set up a simple evaluation that checks whether the agent correctly answers questions about the database. We measure the precision (how many of the results found are relevant) and recall (how many of the relevant results are found).</p>
<div id="2d105a1d" class="cell" data-execution_count="18">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb13" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb13-1">examples <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb13-2">    {</span>
<span id="cb13-3">        <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;">"How many reports do we have from Germany?"</span>,</span>
<span id="cb13-4">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"relevant_report_ids"</span>: {<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">12</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">21</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">32</span>},</span>
<span id="cb13-5">    },</span>
<span id="cb13-6">    {</span>
<span id="cb13-7">        <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;">"For which countries to we have reports mentioning electric vehicles?"</span>,</span>
<span id="cb13-8">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"relevant_report_ids"</span>: {<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">25</span>},</span>
<span id="cb13-9">    },</span>
<span id="cb13-10">    {</span>
<span id="cb13-11">        <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;">"What reports do we have about the gaming industry?"</span>,</span>
<span id="cb13-12">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"relevant_report_ids"</span>: {<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">22</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">30</span>},</span>
<span id="cb13-13">    },</span>
<span id="cb13-14">    {</span>
<span id="cb13-15">        <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;">"What reports do we have about the pet care industry?"</span>,</span>
<span id="cb13-16">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"relevant_report_ids"</span>: {<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">27</span>},</span>
<span id="cb13-17">    },</span>
<span id="cb13-18">    {</span>
<span id="cb13-19">        <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;">"Which reports discuss cyber security insurance?"</span>,</span>
<span id="cb13-20">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"relevant_report_ids"</span>: {<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">29</span>},</span>
<span id="cb13-21">    },</span>
<span id="cb13-22">    {</span>
<span id="cb13-23">        <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;">"What healthcare reports were published in 2024?"</span>,</span>
<span id="cb13-24">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"relevant_report_ids"</span>: {<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">32</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">38</span>},</span>
<span id="cb13-25">    },</span>
<span id="cb13-26">    {</span>
<span id="cb13-27">        <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;">"Which reports are about the smartphone or mobile phone market?"</span>,</span>
<span id="cb13-28">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"relevant_report_ids"</span>: {<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">40</span>},</span>
<span id="cb13-29">    },</span>
<span id="cb13-30">    {</span>
<span id="cb13-31">        <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;">"What reports do we have from Market Insights Inc.?"</span>,</span>
<span id="cb13-32">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"relevant_report_ids"</span>: {<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">22</span>},</span>
<span id="cb13-33">    },</span>
<span id="cb13-34">]</span></code></pre></div></div>
</div>
<div id="3db50b93" class="cell" data-execution_count="19">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="annotated-cell-16" style="background: #f1f3f5;"><pre class="sourceCode python code-annotation-code code-with-copy code-annotated"><code class="sourceCode python"><span id="annotated-cell-16-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> collections <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> Counter</span>
<span id="annotated-cell-16-2"></span>
<span id="annotated-cell-16-3"></span>
<span id="annotated-cell-16-4"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> eval_example(</span>
<span id="annotated-cell-16-5">    example: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">dict</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;">str</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;">set</span>[<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>]], print_errors: <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> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span></span>
<span id="annotated-cell-16-6">) <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 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;">int</span>]:</span>
<span id="annotated-cell-16-7">    result <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> typed_agent.run_sync(example[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"question"</span>], deps<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>deps)</span>
<span id="annotated-cell-16-8">    act, exp <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> result.data.relevant_report_ids, example[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"relevant_report_ids"</span>]</span>
<span id="annotated-cell-16-9">    metrics <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Counter(</span>
<span id="annotated-cell-16-10">        {</span>
<button class="code-annotation-anchor" data-target-cell="annotated-cell-16" data-target-annotation="1">1</button><span id="annotated-cell-16-11" class="code-annotation-target">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"tp"</span>: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(act <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span> exp),</span>
<span id="annotated-cell-16-12">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"fp"</span>: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(act <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> exp),</span>
<span id="annotated-cell-16-13">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"fn"</span>: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(exp <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> act),</span>
<span id="annotated-cell-16-14">        }</span>
<span id="annotated-cell-16-15">    )</span>
<span id="annotated-cell-16-16"></span>
<span id="annotated-cell-16-17">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> print_errors <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">and</span> (metrics[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"fp"</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;">0</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">or</span> metrics[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"fn"</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;">0</span>):</span>
<span id="annotated-cell-16-18">        <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;">"Error in evaluation:"</span>)</span>
<span id="annotated-cell-16-19">        <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"  Question: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>example[<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;">}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span>
<span id="annotated-cell-16-20">        <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"  Found: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>act<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="annotated-cell-16-21">        <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"  Expected: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>exp<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="annotated-cell-16-22"></span>
<span id="annotated-cell-16-23">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> metrics</span>
<span id="annotated-cell-16-24"></span>
<span id="annotated-cell-16-25"></span>
<span id="annotated-cell-16-26">metric_totals <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Counter()</span>
<span id="annotated-cell-16-27"></span>
<button class="code-annotation-anchor" data-target-cell="annotated-cell-16" data-target-annotation="2">2</button><span id="annotated-cell-16-28" class="code-annotation-target"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> example <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> tqdm(examples):</span>
<span id="annotated-cell-16-29">    metrics <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> eval_example(example)</span>
<span id="annotated-cell-16-30">    metric_totals <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+=</span> metrics</span>
<span id="annotated-cell-16-31"></span>
<span id="annotated-cell-16-32">precision <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> metric_totals[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"tp"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (metric_totals[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"tp"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> metric_totals[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"fp"</span>])</span>
<span id="annotated-cell-16-33">recall <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> metric_totals[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"tp"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (metric_totals[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"tp"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> metric_totals[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"fn"</span>])</span>
<span id="annotated-cell-16-34"></span>
<button class="code-annotation-anchor" data-target-cell="annotated-cell-16" data-target-annotation="3">3</button><span id="annotated-cell-16-35" class="code-annotation-target"><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"Precision: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>precision<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">, Recall: </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>recall<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span><div class="code-annotation-gutter-bg"></div><div class="code-annotation-gutter"></div></code></pre></div></div>
<div class="cell-annotation">
<dl class="code-annotation-container-hidden code-annotation-container-grid">
<dt data-target-cell="annotated-cell-16" data-target-annotation="1">1</dt>
<dd>
<span data-code-cell="annotated-cell-16" data-code-lines="11" data-code-annotation="1">Use set operations to compare the expected and found ids.</span>
</dd>
<dt data-target-cell="annotated-cell-16" data-target-annotation="2">2</dt>
<dd>
<span data-code-cell="annotated-cell-16" data-code-lines="28" data-code-annotation="2">This should be parallelized if the number of examples is large.</span>
</dd>
<dt data-target-cell="annotated-cell-16" data-target-annotation="3">3</dt>
<dd>
<span data-code-cell="annotated-cell-16" data-code-lines="35" data-code-annotation="3">Precision and recall could also be combined into the F1 score, which is their harmonic mean.</span>
</dd>
</dl>
</div>
<div class="cell-output cell-output-stderr">
<pre><code>  0%|          | 0/8 [00:00&lt;?, ?it/s] 12%|█▎        | 1/8 [00:00&lt;00:05,  1.19it/s] 25%|██▌       | 2/8 [00:07&lt;00:24,  4.02s/it] 38%|███▊      | 3/8 [00:18&lt;00:37,  7.43s/it] 50%|█████     | 4/8 [00:34&lt;00:43, 10.85s/it] 62%|██████▎   | 5/8 [00:50&lt;00:37, 12.51s/it] 75%|███████▌  | 6/8 [01:05&lt;00:26, 13.41s/it] 88%|████████▊ | 7/8 [01:27&lt;00:16, 16.16s/it]100%|██████████| 8/8 [01:39&lt;00:00, 14.97s/it]100%|██████████| 8/8 [01:39&lt;00:00, 12.44s/it]</code></pre>
</div>
<div class="cell-output cell-output-stdout">
<pre><code>Precision: 1.00, Recall: 0.62</code></pre>
</div>
<div class="cell-output cell-output-stderr">
<pre><code></code></pre>
</div>
</div>
<p>This is a joint evaluation of the agent, the tools and the database. What’s missing is an evaluation of the generated text. In a real RAG system, you’d also want separate evaluations of retrieval and result ranking.</p>
</section>
<section id="discussion" class="level2">
<h2 class="anchored" data-anchor-id="discussion">Discussion</h2>
<section id="comparison-to-other-libraries" class="level3">
<h3 class="anchored" data-anchor-id="comparison-to-other-libraries">Comparison to other libraries</h3>
<p>PydanticAI is a late entrant to the agent framework space. It joins several established libraries including:</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Library</th>
<th>Description</th>
<th style="text-align: right;">Github Stars ⭐</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td><a href="https://github.com/Significant-Gravitas/AutoGPT">AutoGPT</a></td>
<td>AI automation platform with frontend, server and monitoring</td>
<td style="text-align: right;">169k</td>
</tr>
<tr class="even">
<td><a href="https://github.com/langchain-ai/langchain">LangChain</a></td>
<td>Package ecosystem for LLM applications</td>
<td style="text-align: right;">96k</td>
</tr>
<tr class="odd">
<td><a href="https://github.com/microsoft/autogen">autogen</a></td>
<td>Multi-agent AI chat framework by Microsoft</td>
<td style="text-align: right;">36k</td>
</tr>
<tr class="even">
<td><a href="https://github.com/crewAIInc/crewai">crewAI</a></td>
<td>Framework for orchestrating role-based AI agents</td>
<td style="text-align: right;">22k</td>
</tr>
<tr class="odd">
<td><a href="https://github.com/openai/swarm">swarm</a></td>
<td>Educational framework for multi-agent apps by OpenAI</td>
<td style="text-align: right;">17k</td>
</tr>
<tr class="even">
<td><a href="https://github.com/phidatahq/phidata">phidata</a></td>
<td>Multi-agent backend and chat frontend</td>
<td style="text-align: right;">16k</td>
</tr>
</tbody>
</table>
<p>There are dozens of other libraries with fewer stars. In addition, there are libraries specialized for RAG like <a href="https://github.com/run-llama/llama_index">LlamaIndex</a> and <a href="https://github.com/deepset-ai/haystack">Haystack</a>. The competition landscape doesn’t show signs of consolidation or slowing down.</p>
</section>
<section id="development-team" class="level3">
<h3 class="anchored" data-anchor-id="development-team">Development team</h3>
<p>Pydantic Services, the company behind Pydantic, has raised a $12.5m <a href="https://www.crunchbase.com/funding_round/pydantic-services-series-a--ddd115fb">Series A</a> in October 2024. This is great news for the project: funding pays for full time developers. It also raises the question of how Pydantic will make money, and the answer to that is Logfire subscriptions. This is a good model that gives long-term stability to the project and follows the lead of LangChain with its commercial product, <a href="https://www.langchain.com/langsmith">LangSmith</a>. I just hope that the integration remains optional. While Logfire looks great, my team already uses <a href="https://wandb.ai/site/weave/">Weave</a> by Weights &amp; Biases, and having to switch would be a barrier to adopting PydanticAI.</p>
</section>
<section id="review" class="level3">
<h3 class="anchored" data-anchor-id="review">Review</h3>
<div class="columns">
<div class="column" style="width:50%;">
<p><strong>Pros ✅</strong></p>
<ul>
<li>Sensible abstractions that don’t get in the way and enable coding in a Pythonic style.</li>
<li>Type safety and integration with Pydantic.</li>
<li>Support for streaming responses and async tool calling. This is critical for live chat applications.</li>
<li>Pydantic is familiar to many Python developers who will have an easier time learning PydanticAI.</li>
<li>High quality documentation and examples that also cover tests and evals.</li>
<li>Strong reputation of the Pydantic team and high responsiveness in Github issues.</li>
</ul>
</div><div class="column" style="width:50%;">
<p><strong>Cons ❌</strong></p>
<ul>
<li>Launches into a competitive market with many established libraries.</li>
<li>Early stage of development, so expect breaking changes.</li>
<li>Many concepts to learn, but mild compared to langchain which invented its own domain-specific language LCEL.</li>
<li>No support for multimodal (image, audio, video) inputs and out yet, but it’s <a href="https://github.com/pydantic/pydantic-ai/issues/126">planned</a>.</li>
<li>Economic incentives to lock users into Logfire. This hasn’t happened but is a risk.</li>
</ul>
</div>
</div>
<p>I’m looking forward to an opportunity to build a full-scale application with PydanticAI. The best place to get started is the <a href="https://ai.pydantic.dev/">PydanticAI documentation</a>.</p>
<div class="callout callout-style-default callout-tip callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
<span class="screen-reader-only">Tip</span>Not every app needs an agent framework
</div>
</div>
<div class="callout-body-container callout-body">
<p>A lot can be accomplished by single API calls or by specifying a fixed sequence of calls. That would also work for the example app shown in this article. Unless you truly need the flexibility of an agent framework, you may be better off with plain Python. If all you need is Pydantic + LLM calls, you can use <a href="https://github.com/jxnl/instructor">instructor</a>. <a href="https://openai.com/index/introducing-structured-outputs-in-the-api/">OpenAI</a> even supports structured outputs based on Pydantic models without an additional library.</p>
</div>
</div>
<hr>
<p>Preview photo by <a href="https://unsplash.com/@magicpattern?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash">MagicPattern</a> on <a href="https://unsplash.com/photos/purple-and-black-polka-dot-textile-eHH_5rn3xnU?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash">Unsplash</a></p>


</section>
</section>

 ]]></description>
  <category>Machine Learning</category>
  <category>Python</category>
  <guid>https://simmering.dev/blog/pydantic-ai/</guid>
  <pubDate>Sun, 15 Dec 2024 23:00:00 GMT</pubDate>
  <media:content url="https://simmering.dev/blog/pydantic-ai/image.jpg" medium="image" type="image/jpeg"/>
</item>
<item>
  <title>Constrained by Context, Not Reasoning</title>
  <dc:creator>Paul Simmering</dc:creator>
  <link>https://simmering.dev/blog/context-constrained/</link>
  <description><![CDATA[ 






<p>Frontier LLMs rarely give completely wrong answers. Even when their responses aren’t exactly what we need, they’re usually logical given the information provided.</p>
<p>Recent advances in chain-of-thought reasoning, such as in models like <a href="https://openai.com/index/learning-to-reason-with-llms/">OpenAI’s o1</a> and <a href="https://www.alibabacloud.com/blog/alibaba-cloud-unveils-open-source-ai-reasoning-model-qwq-and-new-image-editing-tool_601813">Alibaba’s QwQ</a>, have led to remarkable achievements. These models now outperform most humans on complex tasks like competition mathematics (AIME 2024) and PhD-level science questions (GPQA Diamond).</p>
<p>Yet here’s the paradox: despite this impressive reasoning capability, LLMs often struggle to provide immediately useful outputs for everyday professional tasks. Usually, their output has to be edited or the prompt rewritten multiple times to produce a copy-pasteable result. This leads to my thesis: <strong>The real-world effectiveness of LLMs is now limited more by their awareness of context than by their reasoning capabilities</strong>.</p>
<p>Think of an LLM as a brilliant but newly hired colleague who hasn’t been properly onboarded. While they can tackle complex problems, they miss crucial contextual details required to fit their work into the existing workflows. The challenge isn’t their intelligence—it’s their need for situational awareness.</p>
<section id="context-makes-outputs-more-useful" class="level2">
<h2 class="anchored" data-anchor-id="context-makes-outputs-more-useful">Context makes outputs more useful</h2>
<p>Here are some examples of how adding context lets LLMs produce outputs that are more readily applicable:</p>
<ul>
<li>When summarizing a technical document, telling the model “This is for marketing executives who need to understand the business implications” yields very different (and more useful) results than just asking for a summary</li>
<li>For educational content, specifying “Explain this for a high school student” versus “Explain this for a graduate student” completely changes the depth and terminology used</li>
<li>In correspondence, sharing details like “This is for a long-time client who prefers informal communication” helps create more appropriately-toned messages For social media, providing examples of past successful posts or a company style guide helps the model match the preferred tone</li>
<li>In software development, showing the model your existing codebase helps it suggest solutions that integrate seamlessly with your architecture</li>
<li>During translation work, specifying “This is medical documentation” versus “This is marketing material” ensures appropriate terminology and tone</li>
</ul>
</section>
<section id="infusing-context-actively-and-passively" class="level2">
<h2 class="anchored" data-anchor-id="infusing-context-actively-and-passively">Infusing context actively and passively</h2>
<p>It’s tedious to write a detailed briefing each time, just like you wouldn’t want to repeat onboarding of a colleague. Here are ways to reuse prompts or passively infuse context:</p>
<ul>
<li>Write a persistent system prompt that’s automatically applied to all conversations. In ChatGPT, you can do this by creating a custom GPT.</li>
<li>Enable chat history to be able to copy-paste successful prompts into new conversations.</li>
<li>Turn on features that let the model learn from past interactions (if not dealing with sensitive data).</li>
<li>When writing in an editor enhanced by AI, prefer one long document over spreading content across many shorter documents.</li>
<li>Work with developer tools like GitHub Copilot or Cursor that read your code base rather than copy-pasting snippets into a separate chat window. This also works for non-coding tasks, such as writing articles.</li>
<li>Use dictation to speak your prompt effortlessly. Speak about the situation and the task in a stream of consciousness.</li>
<li>Share your screen with an assistant. This <a href="https://beebom.com/how-you-can-try-google-project-astra-stream-realtime/">feature</a> was added to Google Gemini in December 2024. While my experience testing it was mixed, I think this could become an effective way to continuously share context. It makes most sense with apps that don’t have their own built-in assistant.</li>
</ul>
<hr>
<p>Preview photo by <a href="https://unsplash.com/@magicpattern?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash">MagicPattern</a> on <a href="https://unsplash.com/photos/purple-and-pink-letter-blocks-jbywvpa9vH8?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash">Unsplash</a></p>


</section>

 ]]></description>
  <category>Productivity</category>
  <category>Machine Learning</category>
  <guid>https://simmering.dev/blog/context-constrained/</guid>
  <pubDate>Fri, 13 Dec 2024 23:00:00 GMT</pubDate>
  <media:content url="https://simmering.dev/blog/context-constrained/image.jpg" medium="image" type="image/jpeg"/>
</item>
<item>
  <title>Aspect-based Sentiment Analysis with DSPy</title>
  <dc:creator>Paul Simmering</dc:creator>
  <link>https://simmering.dev/blog/absa-with-dspy/</link>
  <description><![CDATA[ 






<p>Last year, my colleague Paavo Huoviala and I explored prompting and fine-tuning large language models for aspect-based sentiment analysis (ABSA) <span class="citation" data-cites="simmering2023large">(Simmering and Huoviala 2023)</span>. Like many researchers at the time, we spent considerable effort manually crafting prompts and selecting few-shot examples. But what if we could automate this process? Enter DSPy - a Python library that automatically optimizes LLM prompts. In this article, I’ll revisit our ABSA experiments using DSPy’s automated approach instead of manual prompt engineering.</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Resource</th>
<th>Link</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>💻 Code</td>
<td><a href="https://github.com/psimm/website/blob/master/blog/absa-with-dspy/index.qmd">GitHub</a></td>
</tr>
<tr class="even">
<td>📊 Experiments</td>
<td><a href="https://wandb.ai/psimm/absa-dspy">Weights &amp; Biases project</a></td>
</tr>
<tr class="odd">
<td>📝 Dataset</td>
<td><a href="https://huggingface.co/datasets/psimm/absa-semeval2014-alpaca">Hugging Face Hub</a></td>
</tr>
</tbody>
</table>
<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>DSPy version 2.5.32
</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">
<p>DSPy is in rapid development. I’ve encountered outdated tutorials, dead links in the documentation and deprecation warnings. The code of this article may not work with future versions.</p>
</div>
</div>
</div>
<section id="dspy-programming-not-prompting-llms" class="level2">
<h2 class="anchored" data-anchor-id="dspy-programming-not-prompting-llms">DSPy: Programming — not prompting — LLMs</h2>
<p><img src="https://simmering.dev/blog/absa-with-dspy/dspy_logo.webp" class="img-fluid" style="width:50.0%"></p>
<p><a href="https://dspy.ai">DSPy</a> is a Python library developed by Stanford NLP. Rather than manually crafting prompts and seeing them break whenever something changes elsewhere in the pipeline, DSPy automates the process of finding the optimal prompts. The documentation has an <a href="https://dspy.ai/learn/">overview</a> of the main building blocks of the library. In this article, I’ll introduce the elements needed to optimize a structured prediction task, using ABSA as an example.</p>
<section id="experiment-setup" class="level3">
<h3 class="anchored" data-anchor-id="experiment-setup">Experiment setup</h3>
<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">%%{init: {
  'theme': 'base',
  'themeVariables': {
    'primaryColor': '#ffffff',
    'primaryTextColor': '#2d3748',
    'primaryBorderColor': '#90cdf4',
    'lineColor': '#64748b',
    'secondaryColor': '#ffffff',
    'tertiaryColor': '#ffffff',
    'fontSize': '22px',
    'labelFontSize': '18px',
    'edgeLabelFontSize': '18px'
  }
}}%%
graph TB
    %% Define styles
    classDef default fill:#ffffff,stroke:#90cdf4,stroke-width:2px
    classDef highlight fill:#fdf2f8,stroke:#ed64a6,stroke-width:3px
    classDef api fill:#ffffff,stroke:#4fd1c5,stroke-width:2px
    
    subgraph Data ["1️⃣ Data"]
        D1[SemEval Dataset] --&gt; |"Transform"| D2[DSPy Examples]
    end
    
    subgraph Definition ["2️⃣ Model Definition"]
        M2[Pydantic Models] --&gt; |"Define Structure"| M1[DSPy Signature]
        M1 --&gt; |"Initialize"| M3[Predictor]
        M4[Language Models] --&gt; |"Power"| M3
        A1[OpenAI API] --&gt; |"Provide"| M4
        A2[Fireworks.ai API] --&gt; |"Provide"| M4
    end
    
    subgraph Optimization ["3️⃣ Optimization"]
        O1[Evaluation Function] --&gt; |"Guide"| O2[MIPROv2 Optimizer]
        M3 --&gt; |"Optimize"| O2
        D2 --&gt; |"Train"| O2
        O2 --&gt; |"Output"| O3[Optimized Predictor]
    end
    
    subgraph Evaluation ["4️⃣ Evaluation"]
        O3 --&gt; |"Test"| E1[Test Set Evaluation]
        O1 --&gt; |"Measure"| E1
        E1 --&gt; |"Log"| E2[Weights &amp; Biases]
    end

    %% Apply styles
    class O2 highlight
    class A1,A2,E2 api
    
    %% Links between subgraphs
    linkStyle default stroke:#64748b,stroke-width:2px
</pre>
</div>
<p></p></figure><p></p>
</div>
</div>
</div>
<p>The steps will be explained in the following sections.</p>
</section>
</section>
<section id="dataset-for-aspect-based-sentiment-analysis" class="level2">
<h2 class="anchored" data-anchor-id="dataset-for-aspect-based-sentiment-analysis">Dataset for Aspect-based Sentiment Analysis</h2>
<p>The goal of ABSA is to analyze a review and extract the discussed aspects of a product or service and the sentiment towards each aspect. For example, the review “The pizza was great, but the service was terrible” contains two aspects: “pizza” (positive) and “service” (negative). There are more advanced variants of ABSA, but for this article I’ll focus on the basic task. I will also let a single model handle the extraction and the classification.</p>
<section id="semeval-2014-task-4" class="level3">
<h3 class="anchored" data-anchor-id="semeval-2014-task-4">SemEval 2014 Task 4</h3>
<p>I’m using the SemEval 2014 Task 4 dataset by <span class="citation" data-cites="pontiki_semeval">Pontiki et al. (2014)</span>. The <a href="https://huggingface.co/datasets/psimm/absa-semeval2014-alpaca">dataset</a> is available on Hugging Face. This is a cleaned version of the original XML files consisting of train and test splits. The small number of examples with the “conflict” label are excluded, as is common in the literature.</p>
<div id="c9a578ab" 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 python 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> polars <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> pl</span>
<span id="cb1-2"></span>
<span id="cb1-3">url <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;">"hf://datasets/psimm/absa-semeval2014-alpaca"</span></span>
<span id="cb1-4"></span>
<span id="cb1-5">train <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pl.read_parquet(url <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/train-00000-of-00001.parquet"</span>)</span>
<span id="cb1-6">test <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pl.read_parquet(url <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/test-00000-of-00001.parquet"</span>)</span></code></pre></div></div>
</div>
<div id="b21443cd" class="cell" data-execution_count="2">
<details class="code-fold">
<summary>Code</summary>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode python 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> great_tables <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> GT</span>
<span id="cb2-2"></span>
<span id="cb2-3">overview <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (</span>
<span id="cb2-4">    train.vstack(test)</span>
<span id="cb2-5">    .group_by([<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"split"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"domain"</span>])</span>
<span id="cb2-6">    .agg(examples<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>pl.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>())</span>
<span id="cb2-7">    .sort(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"split"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"domain"</span>, descending<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="cb2-8">)</span>
<span id="cb2-9">GT(overview).tab_header(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"SemEval 2014 Task 4 Dataset"</span>).cols_label(</span>
<span id="cb2-10">    split<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;">"Split"</span>,</span>
<span id="cb2-11">    domain<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;">"Domain"</span>,</span>
<span id="cb2-12">    examples<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;">"Examples"</span>,</span>
<span id="cb2-13">).cols_align(align<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;">"right"</span>, 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;">"examples"</span>])</span></code></pre></div></div>
</details>
<div class="cell-output cell-output-display" data-execution_count="2">
<div id="oykrqaoiln" style="padding-left:0px;padding-right:0px;padding-top:10px;padding-bottom:10px;overflow-x:auto;overflow-y:auto;width:auto;height:auto;">
<style>
#oykrqaoiln table {
          font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Helvetica Neue', 'Fira Sans', 'Droid Sans', Arial, sans-serif;
          -webkit-font-smoothing: antialiased;
          -moz-osx-font-smoothing: grayscale;
        }

#oykrqaoiln thead, tbody, tfoot, tr, td, th { border-style: none; }
 tr { background-color: transparent; }
#oykrqaoiln p { margin: 0; padding: 0; }
 #oykrqaoiln .gt_table { display: table; border-collapse: collapse; line-height: normal; margin-left: auto; margin-right: auto; color: #333333; font-size: 16px; font-weight: normal; font-style: normal; background-color: #FFFFFF; width: auto; border-top-style: solid; border-top-width: 2px; border-top-color: #A8A8A8; border-right-style: none; border-right-width: 2px; border-right-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #A8A8A8; border-left-style: none; border-left-width: 2px; border-left-color: #D3D3D3; }
 #oykrqaoiln .gt_caption { padding-top: 4px; padding-bottom: 4px; }
 #oykrqaoiln .gt_title { color: #333333; font-size: 125%; font-weight: initial; padding-top: 4px; padding-bottom: 4px; padding-left: 5px; padding-right: 5px; border-bottom-color: #FFFFFF; border-bottom-width: 0; }
 #oykrqaoiln .gt_subtitle { color: #333333; font-size: 85%; font-weight: initial; padding-top: 3px; padding-bottom: 5px; padding-left: 5px; padding-right: 5px; border-top-color: #FFFFFF; border-top-width: 0; }
 #oykrqaoiln .gt_heading { background-color: #FFFFFF; text-align: center; border-bottom-color: #FFFFFF; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; }
 #oykrqaoiln .gt_bottom_border { border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; }
 #oykrqaoiln .gt_col_headings { border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; }
 #oykrqaoiln .gt_col_heading { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: normal; text-transform: inherit; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: bottom; padding-top: 5px; padding-bottom: 5px; padding-left: 5px; padding-right: 5px; overflow-x: hidden; }
 #oykrqaoiln .gt_column_spanner_outer { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: normal; text-transform: inherit; padding-top: 0; padding-bottom: 0; padding-left: 4px; padding-right: 4px; }
 #oykrqaoiln .gt_column_spanner_outer:first-child { padding-left: 0; }
 #oykrqaoiln .gt_column_spanner_outer:last-child { padding-right: 0; }
 #oykrqaoiln .gt_column_spanner { border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; vertical-align: bottom; padding-top: 5px; padding-bottom: 5px; overflow-x: hidden; display: inline-block; width: 100%; }
 #oykrqaoiln .gt_spanner_row { border-bottom-style: hidden; }
 #oykrqaoiln .gt_group_heading { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; text-transform: inherit; border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; text-align: left; }
 #oykrqaoiln .gt_empty_group_heading { padding: 0.5px; color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; vertical-align: middle; }
 #oykrqaoiln .gt_from_md> :first-child { margin-top: 0; }
 #oykrqaoiln .gt_from_md> :last-child { margin-bottom: 0; }
 #oykrqaoiln .gt_row { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; }
 #oykrqaoiln .gt_stub { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; text-transform: inherit; border-right-style: solid; border-right-width: 2px; border-right-color: #D3D3D3; padding-left: 5px; padding-right: 5px; }
 #oykrqaoiln .gt_stub_row_group { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; text-transform: inherit; border-right-style: solid; border-right-width: 2px; border-right-color: #D3D3D3; padding-left: 5px; padding-right: 5px; vertical-align: top; }
 #oykrqaoiln .gt_row_group_first td { border-top-width: 2px; }
 #oykrqaoiln .gt_row_group_first th { border-top-width: 2px; }
 #oykrqaoiln .gt_striped { background-color: rgba(128,128,128,0.05); }
 #oykrqaoiln .gt_table_body { border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; }
 #oykrqaoiln .gt_sourcenotes { color: #333333; background-color: #FFFFFF; border-bottom-style: none; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 2px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 2px; border-right-color: #D3D3D3; }
 #oykrqaoiln .gt_sourcenote { font-size: 90%; padding-top: 4px; padding-bottom: 4px; padding-left: 5px; padding-right: 5px; text-align: left; }
 #oykrqaoiln .gt_left { text-align: left; }
 #oykrqaoiln .gt_center { text-align: center; }
 #oykrqaoiln .gt_right { text-align: right; font-variant-numeric: tabular-nums; }
 #oykrqaoiln .gt_font_normal { font-weight: normal; }
 #oykrqaoiln .gt_font_bold { font-weight: bold; }
 #oykrqaoiln .gt_font_italic { font-style: italic; }
 #oykrqaoiln .gt_super { font-size: 65%; }
 #oykrqaoiln .gt_footnote_marks { font-size: 75%; vertical-align: 0.4em; position: initial; }
 #oykrqaoiln .gt_asterisk { font-size: 100%; vertical-align: 0; }
 
</style>

<table class="gt_table caption-top table table-sm table-striped small" data-quarto-bootstrap="false">
<thead>
<tr class="gt_heading header">
<td colspan="3" class="gt_heading gt_title gt_font_normal">SemEval 2014 Task 4 Dataset</td>
</tr>
<tr class="gt_col_headings even">
<th id="Split" class="gt_col_heading gt_columns_bottom_border gt_left" data-quarto-table-cell-role="th" scope="col">Split</th>
<th id="Domain" class="gt_col_heading gt_columns_bottom_border gt_left" data-quarto-table-cell-role="th" scope="col">Domain</th>
<th id="Examples" class="gt_col_heading gt_columns_bottom_border gt_right" data-quarto-table-cell-role="th" scope="col">Examples</th>
</tr>
</thead>
<tbody class="gt_table_body">
<tr class="odd">
<td class="gt_row gt_left">train</td>
<td class="gt_row gt_left">restaurants</td>
<td class="gt_row gt_right">2957</td>
</tr>
<tr class="even">
<td class="gt_row gt_left">train</td>
<td class="gt_row gt_left">laptops</td>
<td class="gt_row gt_right">3002</td>
</tr>
<tr class="odd">
<td class="gt_row gt_left">test</td>
<td class="gt_row gt_left">restaurants</td>
<td class="gt_row gt_right">786</td>
</tr>
<tr class="even">
<td class="gt_row gt_left">test</td>
<td class="gt_row gt_left">laptops</td>
<td class="gt_row gt_right">786</td>
</tr>
</tbody>
</table>


</div>
</div>
</div>
<p>The dataset contains a similar number of restaurant and laptop reviews.</p>
<p>The goal is to choose the optimal prompt and few-shot examples to maximize the F1 score of the aspect extraction and classification. To achieve this, DSPy needs to be able to evaluate the metrics and a training set to learn from.</p>
</section>
</section>
<section id="model-definition" class="level2">
<h2 class="anchored" data-anchor-id="model-definition">Model Definition</h2>
<section id="pydantic-models-for-absa" class="level3">
<h3 class="anchored" data-anchor-id="pydantic-models-for-absa">Pydantic models for ABSA</h3>
<p>We create classes to represent the input and output of the task using the data validation library <a href="https://docs.pydantic.dev/latest/">Pydantic</a>. This helps with validating the data and provides a structured output format for predictor. The <code>Field</code> class is used to describe the expected data type. Their descriptions match the ones used in <span class="citation" data-cites="simmering2023large">(Simmering and Huoviala 2023)</span>. This is a form of prompting, but DSPy also supports automatically setting the structure’s descriptions using the <a href="https://dspy.ai/learn/8-typed_predictors/?h=typed#optimizing-typed-predictors"><code>optimize_signature</code></a> optimizer. In this experiment I’ll stick with the original descriptions and only vary the normal prompt and few-shot examples.</p>
<div id="fa24fc6d" class="cell" data-execution_count="3">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode python 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> typing <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> Literal</span>
<span id="cb3-2"><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-3"></span>
<span id="cb3-4"></span>
<span id="cb3-5"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> Input(BaseModel):</span>
<span id="cb3-6">    text: <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="cb3-7"></span>
<span id="cb3-8"></span>
<span id="cb3-9"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> Aspect(BaseModel):</span>
<span id="cb3-10">    term: <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="cb3-11">        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;">"An aspect term, which is a verbatim text snippet. Single or multiword terms naming particular aspects of the reviewed product or service."</span></span>
<span id="cb3-12">    )</span>
<span id="cb3-13">    polarity: Literal[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"positive"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"neutral"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"negative"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Field(</span>
<span id="cb3-14">        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 polarity expressed towards the aspect term. Valid polarities are ‘positive’, ‘neutral’, ‘negative'."</span></span>
<span id="cb3-15">    )</span>
<span id="cb3-16"></span>
<span id="cb3-17">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__hash__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>):</span>
<span id="cb3-18">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb3-19"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        Make the aspect hashable to enable set operations in evaluation.</span></span>
<span id="cb3-20"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        Hash is case-insensitive.</span></span>
<span id="cb3-21"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        """</span></span>
<span id="cb3-22">        <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;">hash</span>((<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.term.lower(), <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.polarity.lower()))</span>
<span id="cb3-23"></span>
<span id="cb3-24">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__eq__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, other):</span>
<span id="cb3-25">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">"""</span></span>
<span id="cb3-26"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        Define equality for case-insensitive comparison.</span></span>
<span id="cb3-27"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">        """</span></span>
<span id="cb3-28">        <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> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">isinstance</span>(other, Aspect):</span>
<span id="cb3-29">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span></span>
<span id="cb3-30">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> (</span>
<span id="cb3-31">            <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.term.lower() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> other.term.lower()</span>
<span id="cb3-32">            <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">and</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.polarity.lower() <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> other.polarity.lower()</span>
<span id="cb3-33">        )</span>
<span id="cb3-34"></span>
<span id="cb3-35"></span>
<span id="cb3-36"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> Aspects(BaseModel):</span>
<span id="cb3-37">    aspects: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[Aspect] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Field(</span>
<span id="cb3-38">        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;">"An array of aspects and their polarities. If no aspects are mentioned in the text, use an empty array."</span></span>
<span id="cb3-39">    )</span></code></pre></div></div>
</div>
<p>The <code>__hash__</code> and <code>__eq__</code> methods will be helpful for evaluation, because they allow for use of set operations to compare gold and predicted aspects.</p>
</section>
<section id="transform-dataset-to-dspy-examples" class="level3">
<h3 class="anchored" data-anchor-id="transform-dataset-to-dspy-examples">Transform dataset to DSPy examples</h3>
<p>Each row in the dataset needs to be turned into an instance of the <code>dspy.Example</code> class. The <code>with_inputs</code> method is used to tell DSPy which column contains the input. Other columns are used as expected model outputs.</p>
<div id="ba082b2b" 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 python 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> json</span>
<span id="cb4-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> dspy</span>
<span id="cb4-3"></span>
<span id="cb4-4"></span>
<span id="cb4-5"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> to_example(row):</span>
<span id="cb4-6">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> dspy.Example(</span>
<span id="cb4-7">        text<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>row[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"input"</span>],</span>
<span id="cb4-8">        aspects<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>Aspects(aspects<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>json.loads(row[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"output"</span>])[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"aspects"</span>]),</span>
<span id="cb4-9">    ).with_inputs(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"text"</span>)</span>
<span id="cb4-10"></span>
<span id="cb4-11"></span>
<span id="cb4-12">trainset <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [to_example(row) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> row <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> train.to_dicts()]</span>
<span id="cb4-13">testset <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [to_example(row) <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> row <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> test.to_dicts()]</span></code></pre></div></div>
</div>
<p>Let’s look at the first example.</p>
<div id="ee11a3cc" class="cell" data-execution_count="5">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb5-1">trainset[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]</span></code></pre></div></div>
<div class="cell-output cell-output-display" data-execution_count="5">
<pre><code>Example({'text': 'I charge it at night and skip taking the cord with me because of the good battery life.', 'aspects': Aspects(aspects=[Aspect(term='cord', polarity='neutral'), Aspect(term='battery life', polarity='positive')])}) (input_keys={'text'})</code></pre>
</div>
</div>
</section>
<section id="creating-a-dspy-typed-predictor" class="level3">
<h3 class="anchored" data-anchor-id="creating-a-dspy-typed-predictor">Creating a DSPy typed predictor</h3>
<p>In DSPy, a module is a language model and a way of prompting. They can also consist of multiple requests and also include external tools such as a vector database for retrieval augmented generation. In this example, we have a single request using few-shot examples and chain of thought.</p>
<p>In order to be able to parse the output as a dictionary, the LLM must output valid JSON. Therefore I’ll use a <a href="https://dspy.ai/learn/8-typed_predictors/?h=typed">Typed Predictor</a> in DSPy, which is similar to structured outputs via instructor or a similar library.</p>
<div id="01bdde17" 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 python 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> AbsaSignature(dspy.Signature):</span>
<span id="cb7-2">    text: Input <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> dspy.InputField()</span>
<span id="cb7-3">    aspects: Aspects <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> dspy.OutputField()</span>
<span id="cb7-4"></span>
<span id="cb7-5"></span>
<span id="cb7-6">predictor <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> dspy.ChainOfThought(AbsaSignature)</span></code></pre></div></div>
</div>
<p>We also need to choose a language model. DSPy works with OpenAI, Anthropic, Ollama, vllm and other OpenAI-compatible platforms and libraries. This is powered by <a href="https://github.com/BerriAI/litellm">litellm</a> under the hood.</p>
<p>For this article, I’ll use OpenAI’s gpt-4o-mini as well as the 70B version of Meta’s Llama 3.1 hosted on <a href="https://fireworks.ai">fireworks.ai</a>. Fireworks.ai generously supplied me with credits as part of the <a href="https://maven.com/parlance-labs/fine-tuning?utm_campaign=d45fef&amp;utm_medium=partner&amp;utm_source=instructor">Mastering LLMs For Developers &amp; Data Scientists</a> course.</p>
<div id="9ba79829" 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 python code-with-copy"><code class="sourceCode python"><span id="cb8-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># FIREWORKS_AI_API_KEY environment variable must be set.</span></span>
<span id="cb8-2"></span>
<span id="cb8-3">lm <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> dspy.LM(</span>
<span id="cb8-4">    api_base<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;">"https://api.fireworks.ai/inference/v1/"</span>,</span>
<span id="cb8-5">    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;">"fireworks_ai/accounts/fireworks/models/llama-v3p1-70b-instruct"</span>,</span>
<span id="cb8-6">    temperature<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;">0.0</span>,  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># best for structured outputs</span></span>
<span id="cb8-7">    cache<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="cb8-8">    max_tokens<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;">250</span>,</span>
<span id="cb8-9">)</span>
<span id="cb8-10">dspy.configure(lm<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>lm)</span></code></pre></div></div>
</div>
</section>
</section>
<section id="optimization" class="level2">
<h2 class="anchored" data-anchor-id="optimization">Optimization</h2>
<p>Let’s run a single example to check that everything is working.</p>
<div id="dcddfe3d" 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 python code-with-copy"><code class="sourceCode python"><span id="cb9-1">predictor(text<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 pizza was great, but the service was terrible"</span>)</span></code></pre></div></div>
<div class="cell-output cell-output-display" data-execution_count="8">
<pre><code>Prediction(
    rationale='We produce the aspects by identifying the terms "pizza" and "service" as aspects and determining their polarities based on the context. The term "pizza" is associated with the positive sentiment "great", while the term "service" is associated with the negative sentiment "terrible".',
    aspects=Aspects(aspects=[Aspect(term='pizza', polarity='positive'), Aspect(term='service', polarity='negative')])
)</code></pre>
</div>
</div>
<p>That’s a good start. I’m a fan of <a href="https://hamel.dev/blog/posts/prompt/">Hamel Husain’s advice</a> to always demand: “Show me the prompt”, so let’s check what DSPy actually sent to OpenAI:</p>
<div id="35504bec" class="cell" data-execution_count="9">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb11" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb11-1">lm.inspect_history(n<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></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>



[2024-11-27T08:58:15.497789]

System message:

Your input fields are:
1. `text` (Input)

Your output fields are:
1. `rationale` (str): ${produce the aspects}. We ...
2. `aspects` (Aspects)

All interactions will be structured in the following way, with the appropriate values filled in.

[[ ## text ## ]]
{text}

[[ ## rationale ## ]]
{rationale}

[[ ## aspects ## ]]
{aspects}        # note: the value you produce must be pareseable according to the following JSON schema: {"type": "object", "$defs": {"Aspect": {"type": "object", "properties": {"polarity": {"type": "string", "description": "The polarity expressed towards the aspect term. Valid polarities are ‘positive’, ‘neutral’, ‘negative'.", "enum": ["positive", "neutral", "negative"], "title": "Polarity"}, "term": {"type": "string", "description": "An aspect term, which is a verbatim text snippet. Single or multiword terms naming particular aspects of the reviewed product or service.", "title": "Term"}}, "required": ["term", "polarity"], "title": "Aspect"}}, "properties": {"aspects": {"type": "array", "description": "An array of aspects and their polarities. If no aspects are mentioned in the text, use an empty array.", "items": {"$ref": "#/$defs/Aspect"}, "title": "Aspects"}}, "required": ["aspects"], "title": "Aspects"}

[[ ## completed ## ]]

In adhering to this structure, your objective is: 
        Given the fields `text`, produce the fields `aspects`.


User message:

[[ ## text ## ]]
The pizza was great, but the service was terrible

Respond with the corresponding output fields, starting with the field `[[ ## rationale ## ]]`, then `[[ ## aspects ## ]]` (must be formatted as a valid Python Aspects), and then ending with the marker for `[[ ## completed ## ]]`.


Response:

[[ ## rationale ## ]]
We produce the aspects by identifying the terms "pizza" and "service" as aspects and determining their polarities based on the context. The term "pizza" is associated with the positive sentiment "great", while the term "service" is associated with the negative sentiment "terrible".

[[ ## aspects ## ]]
{"aspects": [{"term": "pizza", "polarity": "positive"}, {"term": "service", "polarity": "negative"}]}

[[ ## completed ## ]]




</code></pre>
</div>
</div>
<p>Verbose but it works. It doesn’t use function calling or a different way to get structured outputs, so there is some chance of getting an invalid JSON.</p>
<section id="specify-the-evaluation-function" class="level3">
<h3 class="anchored" data-anchor-id="specify-the-evaluation-function">Specify the evaluation function</h3>
<p>An evaluation function takes an example and a prediction and returns an F1 score. A true positive is a predicted aspect that is also in the gold answer, a false positive is a predicted aspect that is not in the gold answer, and a false negative is a gold answer aspect that is not predicted. Here are the precision, recall, and F1 score functions.</p>
<div id="2a060d62" 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 python 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> precision(tp: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>, fp: <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;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>:</span>
<span id="cb13-2">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Handle division by zero</span></span>
<span id="cb13-3">    <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 class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> tp <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> fp <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 class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> tp <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (tp <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> fp)</span>
<span id="cb13-4"></span>
<span id="cb13-5"></span>
<span id="cb13-6"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> recall(tp: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>, fn: <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;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>:</span>
<span id="cb13-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;">0.0</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> tp <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> fn <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 class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> tp <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (tp <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> fn)</span>
<span id="cb13-8"></span>
<span id="cb13-9"></span>
<span id="cb13-10"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> f1_score(tp: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>, fp: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>, fn: <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;">-&gt;</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">float</span>:</span>
<span id="cb13-11">    prec <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> precision(tp, fp)</span>
<span id="cb13-12">    rec <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> recall(tp, fn)</span>
<span id="cb13-13">    <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 class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> prec <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> rec <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 class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> (prec <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span> rec) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> (prec <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> rec)</span></code></pre></div></div>
</div>
<p>Next is the evaluation function which compares the gold and predicted aspects. To count as a true positive, both the term and the polarity have to be correct. As it is conventional on this benchmark, the case where both the gold answers and the prediction are empty is treated as a correct prediction of no aspects.</p>
<div id="a5bf9cf4" class="cell" data-execution_count="11">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb14" style="background: #f1f3f5;"><pre class="sourceCode python 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;">def</span> evaluate_absa(example: dspy.Example, prediction: Aspects, trace<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 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="cb14-2">    gold_aspects <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;">set</span>(example.aspects.aspects)</span>
<span id="cb14-3">    pred_aspects <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;">set</span>(prediction.aspects.aspects)</span>
<span id="cb14-4"></span>
<span id="cb14-5">    tp <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;">len</span>(gold_aspects <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span> pred_aspects)</span>
<span id="cb14-6">    fp <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;">len</span>(pred_aspects <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> gold_aspects)</span>
<span id="cb14-7">    fn <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;">len</span>(gold_aspects <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> pred_aspects)</span>
<span id="cb14-8"></span>
<span id="cb14-9">    <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>(gold_aspects) <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 class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">and</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(pred_aspects) <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="cb14-10">        tp <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="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># correct prediction of no aspects</span></span>
<span id="cb14-11"></span>
<span id="cb14-12">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> f1_score(tp, fp, fn)</span></code></pre></div></div>
</div>
<p>Let’s try the evaluation function with a single example. We expect the F1 score to be 1.0, because the prediction matches the gold answer exactly.</p>
<div id="0bc42ba9" class="cell" data-execution_count="12">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb15" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb15-1">example <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> dspy.Example(</span>
<span id="cb15-2">    text<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 pizza was great, but the service was terrible"</span>,</span>
<span id="cb15-3">    aspects<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>Aspects(</span>
<span id="cb15-4">        aspects<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[</span>
<span id="cb15-5">            Aspect(term<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;">"pizza"</span>, polarity<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;">"positive"</span>),</span>
<span id="cb15-6">            Aspect(term<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;">"service"</span>, polarity<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;">"negative"</span>),</span>
<span id="cb15-7">        ]</span>
<span id="cb15-8">    ),</span>
<span id="cb15-9">).with_inputs(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"text"</span>)</span>
<span id="cb15-10">prediction <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> predictor(text<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>example.text)</span>
<span id="cb15-11">evaluate_absa(example, prediction)</span></code></pre></div></div>
<div class="cell-output cell-output-display" data-execution_count="12">
<pre><code>1.0</code></pre>
</div>
</div>
</section>
<section id="optimizers" class="level3">
<h3 class="anchored" data-anchor-id="optimizers">Optimizers</h3>
<p>DSPy has a variety of <a href="https://dspy.ai/learn/optimization/optimizers/?h=optimizers">optimizers</a>, loops that change the prompt and/or few-shot examples and evaluate the performance. They’re analogous to optimizers like SGD and Adam in PyTorch. The choice of optimizer depends on the task, the amount of labeled data and the computational resources available. As we have a large labeled dataset, it’s not necessary to have the model bootstrap artificial examples. Our 2023 paper found that fine-tuning yields the best results, but the goal of this article is to showcase DSPy’s prompt optimization.</p>
<p>The most powerful optimizer available for a prompting approach for this task is <a href="[Multiprompt Instruction PRoposal Optimizer Version 2](https://dspy.ai/deep-dive/optimizers/miprov2/?h=miprov)">MIPROv2</a> (Multiprompt Instruction PRoposal Optimizer Version 2) by <span class="citation" data-cites="opsahlong2024optimizinginstructionsdemonstrationsmultistage">Opsahl-Ong et al. (2024)</span>. MIPROv2 uses Bayesian optimization to find an optimal combination of few-shot examples and prompt instructions.</p>
<div id="556aaa52" class="cell" data-execution_count="13">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb17" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb17-1">optimizer_settings <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;">dict</span>(</span>
<span id="cb17-2">    metric<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>evaluate_absa,</span>
<span id="cb17-3">    num_threads<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;">12</span>,  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># make parallel requests to Fireworks.ai</span></span>
<span id="cb17-4">    max_errors<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;">1000</span>,  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># keep going even when invalid JSON is returned</span></span>
<span id="cb17-5">)</span>
<span id="cb17-6">optimizer <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> dspy.teleprompt.MIPROv2(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span>optimizer_settings)</span></code></pre></div></div>
</div>
<p>The final step is to call the <code>compile</code> method, which starts the optimization process. After about 5 minutes, the best prompt and few-shot examples are saved to a JSON file.</p>
<div id="c003b7c7" class="cell" data-execution_count="14">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb18" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb18-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Define settings for the comilation step of the optimizer.</span></span>
<span id="cb18-2">compile_settings <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;">dict</span>(</span>
<span id="cb18-3">    minibatch_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;">50</span>,  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># evaluate changes on a subset of the validation set</span></span>
<span id="cb18-4">    minibatch_full_eval_steps<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;">10</span>,  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># evaluate on the full validation set after every 10 steps</span></span>
<span id="cb18-5">    max_labeled_demos<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;">4</span>,  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># the number of few-shot examples to use</span></span>
<span id="cb18-6">    max_bootstrapped_demos<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="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># not required because we have labeled examples, but setting it to 0 causes an error during sampling</span></span>
<span id="cb18-7">    num_trials<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 class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># how many combinations of few-shot examples and prompt instructions to try</span></span>
<span id="cb18-8">    seed<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;">42</span>,  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># for reproducibility</span></span>
<span id="cb18-9">    requires_permission_to_run<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 class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># skip confirmation dialog</span></span>
<span id="cb18-10">)</span></code></pre></div></div>
</div>
<p>We save the optimized predictor to a JSON file. It’s a small config file listing the chosen few-shot examples and the optimized prompt.</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb19" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb19-1">optimized_predictor <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> optimizer.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">compile</span>(</span>
<span id="cb19-2">    student<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>predictor, trainset<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>trainset, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span>compile_settings</span>
<span id="cb19-3">)</span>
<span id="cb19-4">optimized_predictor.save(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"configs/absa_model.json"</span>)</span></code></pre></div></div>
<p>Let’s check if we can load it again:</p>
<div id="08ac763f" class="cell" data-execution_count="15">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb20" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb20-1">optimized_predictor <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> dspy.ChainOfThought(signature<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>AbsaSignature)</span>
<span id="cb20-2">optimized_predictor.load(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;">"configs/absa_model.json"</span>)</span></code></pre></div></div>
</div>
<p>Again: “Show me the prompt”.</p>
<div id="eaed34bf" class="cell" data-execution_count="16">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb21" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb21-1"><span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(optimized_predictor.extended_signature.instructions)</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>You are a product reviewer tasked with analyzing customer feedback for laptops and netbooks. Given the fields `text`, which contains a customer review, produce the fields `aspects`, which should include the specific features or aspects of the laptop or netbook mentioned in the review, along with their corresponding sentiment or polarity.</code></pre>
</div>
</div>
<p>and show me the chosen few-shot examples:</p>
<div id="e49e8c57" class="cell" data-execution_count="17">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb23" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb23-1"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> demo <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> optimized_predictor.demos[:<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>]:  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># first 3 examples</span></span>
<span id="cb23-2">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(demo[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"text"</span>])</span>
<span id="cb23-3">    <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(demo[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"aspects"</span>])</span></code></pre></div></div>
<div class="cell-output cell-output-stdout">
<pre><code>-Called headquarters again, they report that TFT panel is broken, should be fixed by the end of the week (week 3).
{"aspects":[{"term":"TFT panel","polarity":"negative"}]}
But we had paid for bluetooth, and there was none.
{"aspects":[{"term":"bluetooth","polarity":"negative"}]}
The powerpoint opened seamlessly in the apple and the mac hooked up to the projector so easily it was almost scary.
{"aspects":[{"term":"powerpoint","polarity":"positive"}]}</code></pre>
</div>
</div>
</section>
</section>
<section id="evaluation" class="level2">
<h2 class="anchored" data-anchor-id="evaluation">Evaluation</h2>
<p>So far, we’ve only evaluated on the validation part of the training set (this was automatically done by DSPy). Let’s evaluate the optimized predictor on the test set.</p>
<div id="9ebae982" class="cell" data-execution_count="18">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb25" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb25-1">evaluator <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> dspy.Evaluate(</span>
<span id="cb25-2">    devset<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>testset,</span>
<span id="cb25-3">    metric<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>evaluate_absa,</span>
<span id="cb25-4">    display_progress<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="cb25-5">    num_threads<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;">12</span>,</span>
<span id="cb25-6">)</span></code></pre></div></div>
</div>
<div id="aab61074" class="cell" data-execution_count="19">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb26" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb26-1">score <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> evaluator(optimized_predictor)</span></code></pre></div></div>
</div>
<p>The first run yields an F1 score of 47.6. That’s rather poor, but the compiler settings only allow for 4 labeled examples and 1 bootstrapped example and only 3 trials.</p>
</section>
<section id="hyperparameter-optimization" class="level2">
<h2 class="anchored" data-anchor-id="hyperparameter-optimization">Hyperparameter optimization</h2>
<p>What would happen if we changed the hyperparameters? Let’s do a grid search over the number of few-shot examples and the number of trials, as well as try different models.</p>
<div id="d029a7bb" class="cell" data-execution_count="20">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb27" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb27-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> itertools</span>
<span id="cb27-2"></span>
<span id="cb27-3">max_labeled_demos <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="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">10</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">20</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">40</span>]</span>
<span id="cb27-4">num_trials <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 class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">30</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">60</span>]</span>
<span id="cb27-5">chain_of_thought <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 class="va" style="color: #111111;
background-color: null;
font-style: inherit;">False</span>]</span>
<span id="cb27-6"></span>
<span id="cb27-7">default_lm_settings <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;">dict</span>(</span>
<span id="cb27-8">    temperature<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;">0.0</span>,  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># best for structured outputs, no creativity needed</span></span>
<span id="cb27-9">    cache<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="cb27-10">    max_tokens<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;">250</span>,</span>
<span id="cb27-11">)</span>
<span id="cb27-12"></span>
<span id="cb27-13">lm_settings <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb27-14">    {</span>
<span id="cb27-15">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"model"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"fireworks_ai/accounts/fireworks/models/llama-v3p1-70b-instruct"</span>,</span>
<span id="cb27-16">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"api_base"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"https://api.fireworks.ai/inference/v1/"</span>,</span>
<span id="cb27-17">        <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span>default_lm_settings,</span>
<span id="cb27-18">    },</span>
<span id="cb27-19">    {</span>
<span id="cb27-20">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"model"</span>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"gpt-4o-mini-2024-07-18"</span>,</span>
<span id="cb27-21">        <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span>default_lm_settings,</span>
<span id="cb27-22">    },</span>
<span id="cb27-23">]</span>
<span id="cb27-24"></span>
<span id="cb27-25">grid <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;">list</span>(</span>
<span id="cb27-26">    itertools.product(max_labeled_demos, num_trials, chain_of_thought, lm_settings)</span>
<span id="cb27-27">)</span></code></pre></div></div>
</div>
<p>This results in a grid with 48 combinations. Next, we iterate over the grid, perform the optimization run and save the results to Weights &amp; Biases.</p>
<div id="d4cf55fc" class="cell" data-execution_count="21">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb28" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb28-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> os</span>
<span id="cb28-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> copy <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> deepcopy</span>
<span id="cb28-3"></span>
<span id="cb28-4"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> wandb</span>
<span id="cb28-5"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">from</span> tqdm <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> tqdm</span>
<span id="cb28-6"></span>
<span id="cb28-7"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">assert</span> os.getenv(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"FIREWORKS_AI_API_KEY"</span>) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">is</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"FIREWORKS_AI_API_KEY is not set."</span></span>
<span id="cb28-8"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">assert</span> os.getenv(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"OPENAI_API_KEY"</span>) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">is</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">not</span> <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"OPENAI_API_KEY is not set."</span></span>
<span id="cb28-9"></span>
<span id="cb28-10"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> max_labeled_demos, num_trials, chain_of_thought, lm_settings <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> tqdm(grid):</span>
<span id="cb28-11"></span>
<span id="cb28-12">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Generate a filename for the run</span></span>
<span id="cb28-13">    modelname <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> lm_settings[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"model"</span>].replace(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"/"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"_"</span>)</span>
<span id="cb28-14">    cot_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;">"cot"</span> <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> chain_of_thought <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;">"predict"</span></span>
<span id="cb28-15">    run_name <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>modelname<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>max_labeled_demos<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>num_trials<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>cot_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;">"</span></span>
<span id="cb28-16">    filepath <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;">"configs/"</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> run_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;">".json"</span></span>
<span id="cb28-17"></span>
<span id="cb28-18">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> os.path.exists(filepath):</span>
<span id="cb28-19">        <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"Skipping </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>run_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;"> because it already exists."</span>)</span>
<span id="cb28-20">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">continue</span></span>
<span id="cb28-21">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span>:</span>
<span id="cb28-22">        <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 </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>run_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;">."</span>)</span>
<span id="cb28-23"></span>
<span id="cb28-24">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Create fresh copies of settings for this run</span></span>
<span id="cb28-25">    run_compile_settings <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> deepcopy(compile_settings)</span>
<span id="cb28-26">    run_optimizer_settings <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> deepcopy(optimizer_settings)</span>
<span id="cb28-27"></span>
<span id="cb28-28">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Update settings</span></span>
<span id="cb28-29">    run_compile_settings[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"max_labeled_demos"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> max_labeled_demos</span>
<span id="cb28-30">    run_compile_settings[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"num_trials"</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> num_trials</span>
<span id="cb28-31"></span>
<span id="cb28-32">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> chain_of_thought:</span>
<span id="cb28-33">        predictor <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> dspy.ChainOfThought(AbsaSignature)</span>
<span id="cb28-34">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">else</span>:</span>
<span id="cb28-35">        predictor <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> dspy.Predict(AbsaSignature)</span>
<span id="cb28-36"></span>
<span id="cb28-37">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Do an optimization run and evaluate the resulting model</span></span>
<span id="cb28-38">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">try</span>:</span>
<span id="cb28-39">        dspy.configure(lm<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>dspy.LM(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span>lm_settings))</span>
<span id="cb28-40">        optimizer <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> dspy.teleprompt.MIPROv2(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span>run_optimizer_settings)</span>
<span id="cb28-41">        optimized_predictor <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> optimizer.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">compile</span>(</span>
<span id="cb28-42">            student<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>predictor, trainset<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>trainset, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span>run_compile_settings</span>
<span id="cb28-43">        )</span>
<span id="cb28-44">        score <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> evaluator(optimized_predictor)</span>
<span id="cb28-45">    <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="cb28-46">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">print</span>(</span>
<span id="cb28-47">            <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"Failed run with settings: max_labeled_demos=</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>max_labeled_demos<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="cb28-48">            <span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"num_trials=</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>num_trials<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;">, model=</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>lm_settings[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'model'</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="cb28-49">        )</span>
<span id="cb28-50">        <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><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="cb28-51">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">continue</span></span>
<span id="cb28-52"></span>
<span id="cb28-53">    optimized_predictor.save(filepath)</span>
<span id="cb28-54"></span>
<span id="cb28-55">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Log experiment to W&amp;B</span></span>
<span id="cb28-56">    config <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {</span>
<span id="cb28-57">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"output_schema"</span>: Aspects.model_json_schema(),</span>
<span id="cb28-58">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"compile_settings"</span>: run_compile_settings,</span>
<span id="cb28-59">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"optimizer_settings"</span>: run_optimizer_settings,</span>
<span id="cb28-60">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"lm_settings"</span>: lm_settings,</span>
<span id="cb28-61">    }</span>
<span id="cb28-62"></span>
<span id="cb28-63">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">with</span> wandb.init(project<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;">"absa-dspy"</span>, config<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>config, name<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>run_name) <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> run:</span>
<span id="cb28-64">        wandb.log({<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"f1"</span>: score})</span>
<span id="cb28-65">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Save config to artifact</span></span>
<span id="cb28-66">        artifact <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> wandb.Artifact(</span>
<span id="cb28-67">            name<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"dspy_config_</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>run_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;">"</span>,</span>
<span id="cb28-68">            <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;">"config"</span>, </span>
<span id="cb28-69">            description<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"Config file for </span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>run_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;">"</span></span>
<span id="cb28-70">        )</span>
<span id="cb28-71">        artifact.add_file(filepath)</span>
<span id="cb28-72">        run.log_artifact(artifact)</span></code></pre></div></div>
</div>
</section>
<section id="comparison-with-manual-prompts" class="level2">
<h2 class="anchored" data-anchor-id="comparison-with-manual-prompts">Comparison with manual prompts</h2>
<p>In the 2023 paper, co-author and I manually crafted prompts and chose few-shot examples that, in our opinion, illustrated the task well. Inference was done using the OpenAI API and using function calling to ensure structured outputs. To make the comparison fair, we’ll now use the same prompts within DSPy.</p>
<p>The manual prompts and few-shot examples are available on <a href="https://github.com/psimm/website/blob/master/blog/absa-with-dspy/configs/manual_prompt.json">Github</a>.</p>
<p>The models <code>gpt-4-0613</code> and <code>gpt-3.5-turbo-0613</code> that were used in the 2023 paper are no longer available on the OpenAI API. Therefore, we use the closest substitutes here.</p>
<div id="88dcd13e" class="cell" data-execution_count="22">
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb29" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb29-1">models <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> [</span>
<span id="cb29-2">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"gpt-4o-2024-11-20"</span>,  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># similar to gpt-4-0613</span></span>
<span id="cb29-3">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"gpt-3.5-turbo-0125"</span>,  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># similar to gpt-3.5-turbo-0613</span></span>
<span id="cb29-4">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"gpt-4o-mini-2024-07-18"</span>,  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># reference</span></span>
<span id="cb29-5">]</span>
<span id="cb29-6"></span>
<span id="cb29-7">manual_predictor <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> dspy.Predict(AbsaSignature)</span>
<span id="cb29-8">manual_predictor.load(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;">"configs/manual_prompt.json"</span>)</span>
<span id="cb29-9"></span>
<span id="cb29-10"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> model <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> models:</span>
<span id="cb29-11">    lm <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> dspy.LM(</span>
<span id="cb29-12">        model<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>model,</span>
<span id="cb29-13">        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="cb29-14">        cache<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="cb29-15">        max_tokens<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;">250</span>,</span>
<span id="cb29-16">    )</span>
<span id="cb29-17">    dspy.configure(lm<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>lm)</span>
<span id="cb29-18">    score <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> evaluator(manual_predictor)</span>
<span id="cb29-19">    runname <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>model<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;">_manual_prompt"</span></span>
<span id="cb29-20"></span>
<span id="cb29-21">    config <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> {</span>
<span id="cb29-22">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"output_schema"</span>: Aspects.model_json_schema(),</span>
<span id="cb29-23">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"compile_settings"</span>: {</span>
<span id="cb29-24">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"max_labeled_demos"</span>: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">len</span>(manual_predictor.demos),</span>
<span id="cb29-25">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"max_bootstrapped_demos"</span>: <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>,</span>
<span id="cb29-26">        },</span>
<span id="cb29-27">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"lm_settings"</span>: {</span>
<span id="cb29-28">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"model"</span>: model,</span>
<span id="cb29-29">        },</span>
<span id="cb29-30">    }</span>
<span id="cb29-31"></span>
<span id="cb29-32">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">with</span> wandb.init(project<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;">"absa-dspy"</span>, name<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>runname, config<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>config) <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> run:</span>
<span id="cb29-33">        wandb.log({<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"f1"</span>: score})</span>
<span id="cb29-34">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Save manual prompt to artifact</span></span>
<span id="cb29-35">        artifact <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> wandb.Artifact(</span>
<span id="cb29-36">            name<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"dspy_config_</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>runname<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="cb29-37">            <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;">"model"</span>,</span>
<span id="cb29-38">            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;">"Manual prompt configuration"</span></span>
<span id="cb29-39">        )</span>
<span id="cb29-40">        artifact.add_file(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"configs/manual_prompt.json"</span>)</span>
<span id="cb29-41">        run.log_artifact(artifact)</span></code></pre></div></div>
</div>
</section>
<section id="results-and-discussion" class="level2">
<h2 class="anchored" data-anchor-id="results-and-discussion">Results and discussion</h2>
<p>We load the results from the Weights &amp; Biases project and show the most relevant columns for a comparison of the runs.</p>
<div id="a36f5554" class="cell" data-execution_count="23">
<details class="code-fold">
<summary>Code</summary>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb30" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb30-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> wandb</span>
<span id="cb30-2"></span>
<span id="cb30-3">api <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> wandb.Api()</span>
<span id="cb30-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Get all runs from the project</span></span>
<span id="cb30-5">runs <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> api.runs(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"psimm/absa-dspy"</span>)</span>
<span id="cb30-6"></span>
<span id="cb30-7"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Convert to DataFrame</span></span>
<span id="cb30-8">results <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> []</span>
<span id="cb30-9"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> run <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> runs:</span>
<span id="cb30-10">    results.append(</span>
<span id="cb30-11">        {</span>
<span id="cb30-12">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"run_name"</span>: run.name,</span>
<span id="cb30-13">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"model"</span>: run.config[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"lm_settings"</span>][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"model"</span>],</span>
<span id="cb30-14">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"max_demos"</span>: run.config[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"compile_settings"</span>][<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"max_labeled_demos"</span>],</span>
<span id="cb30-15">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"max_bootstrapped_demos"</span>: run.config[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"compile_settings"</span>][</span>
<span id="cb30-16">                <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"max_bootstrapped_demos"</span></span>
<span id="cb30-17">            ],</span>
<span id="cb30-18">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"num_trials"</span>: run.config.get(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"compile_settings"</span>, {}).get(</span>
<span id="cb30-19">                <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"num_trials"</span>, <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">None</span></span>
<span id="cb30-20">            ),</span>
<span id="cb30-21">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"chain_of_thought"</span>: run.config[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"chain_of_thought"</span>],</span>
<span id="cb30-22">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"f1"</span>: run.summary[<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"f1"</span>],</span>
<span id="cb30-23">        }</span>
<span id="cb30-24">    )</span>
<span id="cb30-25"></span>
<span id="cb30-26">results_df <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pl.DataFrame(results)</span>
<span id="cb30-27"></span>
<span id="cb30-28">table_df <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> (</span>
<span id="cb30-29">    results_df.with_columns(</span>
<span id="cb30-30">        method<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>pl.when(pl.col(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"run_name"</span>).<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>.contains(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"manual"</span>))</span>
<span id="cb30-31">        .then(pl.lit(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Manual (2023)"</span>))</span>
<span id="cb30-32">        .otherwise(pl.lit(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"DSPy"</span>)),</span>
<span id="cb30-33">        model<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>pl.col(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"model"</span>).<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>.replace(</span>
<span id="cb30-34">            <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"fireworks_ai/accounts/fireworks/models/"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">""</span></span>
<span id="cb30-35">        ),</span>
<span id="cb30-36">        demos<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>pl.when(pl.col(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"max_bootstrapped_demos"</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;">0</span>)</span>
<span id="cb30-37">        .then(pl.col(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"max_demos"</span>).cast(pl.Utf8))</span>
<span id="cb30-38">        .otherwise(</span>
<span id="cb30-39">            pl.col(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"max_demos"</span>).cast(pl.Utf8)</span>
<span id="cb30-40">            <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>
<span id="cb30-41">            <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span> pl.col(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"max_bootstrapped_demos"</span>).cast(pl.Utf8)</span>
<span id="cb30-42">        ),</span>
<span id="cb30-43">        chain_of_thought<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>pl.when(pl.col(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"chain_of_thought"</span>))</span>
<span id="cb30-44">        .then(pl.lit(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"✅"</span>))</span>
<span id="cb30-45">        .otherwise(pl.lit(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"❌"</span>)),</span>
<span id="cb30-46">    )</span>
<span id="cb30-47">    .sort(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"f1"</span>, descending<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-48">    .select(</span>
<span id="cb30-49">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"model"</span>,</span>
<span id="cb30-50">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"method"</span>,</span>
<span id="cb30-51">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"num_trials"</span>,</span>
<span id="cb30-52">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"demos"</span>,</span>
<span id="cb30-53">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"chain_of_thought"</span>,</span>
<span id="cb30-54">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"f1"</span>,</span>
<span id="cb30-55">    )</span>
<span id="cb30-56">)</span>
<span id="cb30-57"></span>
<span id="cb30-58">GT(table_df).tab_header(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"SemEval 2014 Task 4 1+2 Few-Shot Predictors"</span>).cols_label(</span>
<span id="cb30-59">    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;">"Model"</span>,</span>
<span id="cb30-60">    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;">"Method"</span>,</span>
<span id="cb30-61">    demos<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;">"Examples¹"</span>,</span>
<span id="cb30-62">    num_trials<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;">"Trials"</span>,</span>
<span id="cb30-63">    chain_of_thought<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;">"CoT"</span>,</span>
<span id="cb30-64">    f1<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;">"F1"</span>,</span>
<span id="cb30-65">).cols_align(align<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;">"right"</span>, 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;">"demos"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"num_trials"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"f1"</span>]).fmt_number(</span>
<span id="cb30-66">    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;">"f1"</span>], decimals<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>
<span id="cb30-67">).tab_source_note(</span>
<span id="cb30-68">    <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"¹ Bootstrapped + labeled examples. Notes: Limited Llama 3.1 70B non-CoT runs due to API constraints. Manual prompt runs use 10 examples vs. 6 in original paper."</span></span>
<span id="cb30-69">)</span></code></pre></div></div>
</details>
<div class="cell-output cell-output-display" data-execution_count="20">
<div id="fhjsfutory" style="padding-left:0px;padding-right:0px;padding-top:10px;padding-bottom:10px;overflow-x:auto;overflow-y:auto;width:auto;height:auto;">
<style>
#fhjsfutory table {
          font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Helvetica Neue', 'Fira Sans', 'Droid Sans', Arial, sans-serif;
          -webkit-font-smoothing: antialiased;
          -moz-osx-font-smoothing: grayscale;
        }

#fhjsfutory thead, tbody, tfoot, tr, td, th { border-style: none; }
 tr { background-color: transparent; }
#fhjsfutory p { margin: 0; padding: 0; }
 #fhjsfutory .gt_table { display: table; border-collapse: collapse; line-height: normal; margin-left: auto; margin-right: auto; color: #333333; font-size: 16px; font-weight: normal; font-style: normal; background-color: #FFFFFF; width: auto; border-top-style: solid; border-top-width: 2px; border-top-color: #A8A8A8; border-right-style: none; border-right-width: 2px; border-right-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #A8A8A8; border-left-style: none; border-left-width: 2px; border-left-color: #D3D3D3; }
 #fhjsfutory .gt_caption { padding-top: 4px; padding-bottom: 4px; }
 #fhjsfutory .gt_title { color: #333333; font-size: 125%; font-weight: initial; padding-top: 4px; padding-bottom: 4px; padding-left: 5px; padding-right: 5px; border-bottom-color: #FFFFFF; border-bottom-width: 0; }
 #fhjsfutory .gt_subtitle { color: #333333; font-size: 85%; font-weight: initial; padding-top: 3px; padding-bottom: 5px; padding-left: 5px; padding-right: 5px; border-top-color: #FFFFFF; border-top-width: 0; }
 #fhjsfutory .gt_heading { background-color: #FFFFFF; text-align: center; border-bottom-color: #FFFFFF; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; }
 #fhjsfutory .gt_bottom_border { border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; }
 #fhjsfutory .gt_col_headings { border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; }
 #fhjsfutory .gt_col_heading { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: normal; text-transform: inherit; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: bottom; padding-top: 5px; padding-bottom: 5px; padding-left: 5px; padding-right: 5px; overflow-x: hidden; }
 #fhjsfutory .gt_column_spanner_outer { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: normal; text-transform: inherit; padding-top: 0; padding-bottom: 0; padding-left: 4px; padding-right: 4px; }
 #fhjsfutory .gt_column_spanner_outer:first-child { padding-left: 0; }
 #fhjsfutory .gt_column_spanner_outer:last-child { padding-right: 0; }
 #fhjsfutory .gt_column_spanner { border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; vertical-align: bottom; padding-top: 5px; padding-bottom: 5px; overflow-x: hidden; display: inline-block; width: 100%; }
 #fhjsfutory .gt_spanner_row { border-bottom-style: hidden; }
 #fhjsfutory .gt_group_heading { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; text-transform: inherit; border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; text-align: left; }
 #fhjsfutory .gt_empty_group_heading { padding: 0.5px; color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; vertical-align: middle; }
 #fhjsfutory .gt_from_md> :first-child { margin-top: 0; }
 #fhjsfutory .gt_from_md> :last-child { margin-bottom: 0; }
 #fhjsfutory .gt_row { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; }
 #fhjsfutory .gt_stub { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; text-transform: inherit; border-right-style: solid; border-right-width: 2px; border-right-color: #D3D3D3; padding-left: 5px; padding-right: 5px; }
 #fhjsfutory .gt_stub_row_group { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; text-transform: inherit; border-right-style: solid; border-right-width: 2px; border-right-color: #D3D3D3; padding-left: 5px; padding-right: 5px; vertical-align: top; }
 #fhjsfutory .gt_row_group_first td { border-top-width: 2px; }
 #fhjsfutory .gt_row_group_first th { border-top-width: 2px; }
 #fhjsfutory .gt_striped { background-color: rgba(128,128,128,0.05); }
 #fhjsfutory .gt_table_body { border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; }
 #fhjsfutory .gt_sourcenotes { color: #333333; background-color: #FFFFFF; border-bottom-style: none; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 2px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 2px; border-right-color: #D3D3D3; }
 #fhjsfutory .gt_sourcenote { font-size: 90%; padding-top: 4px; padding-bottom: 4px; padding-left: 5px; padding-right: 5px; text-align: left; }
 #fhjsfutory .gt_left { text-align: left; }
 #fhjsfutory .gt_center { text-align: center; }
 #fhjsfutory .gt_right { text-align: right; font-variant-numeric: tabular-nums; }
 #fhjsfutory .gt_font_normal { font-weight: normal; }
 #fhjsfutory .gt_font_bold { font-weight: bold; }
 #fhjsfutory .gt_font_italic { font-style: italic; }
 #fhjsfutory .gt_super { font-size: 65%; }
 #fhjsfutory .gt_footnote_marks { font-size: 75%; vertical-align: 0.4em; position: initial; }
 #fhjsfutory .gt_asterisk { font-size: 100%; vertical-align: 0; }
 
</style>

<table class="gt_table caption-top table table-sm table-striped small" data-quarto-bootstrap="false">
<thead>
<tr class="gt_heading header">
<td colspan="6" class="gt_heading gt_title gt_font_normal">SemEval 2014 Task 4 1+2 Few-Shot Predictors</td>
</tr>
<tr class="gt_col_headings even">
<th id="Model" class="gt_col_heading gt_columns_bottom_border gt_left" data-quarto-table-cell-role="th" scope="col">Model</th>
<th id="Method" class="gt_col_heading gt_columns_bottom_border gt_left" data-quarto-table-cell-role="th" scope="col">Method</th>
<th id="Trials" class="gt_col_heading gt_columns_bottom_border gt_right" data-quarto-table-cell-role="th" scope="col">Trials</th>
<th id="Examples¹" class="gt_col_heading gt_columns_bottom_border gt_right" data-quarto-table-cell-role="th" scope="col">Examples¹</th>
<th id="CoT" class="gt_col_heading gt_columns_bottom_border gt_left" data-quarto-table-cell-role="th" scope="col">CoT</th>
<th id="F1" class="gt_col_heading gt_columns_bottom_border gt_right" data-quarto-table-cell-role="th" scope="col">F1</th>
</tr>
</thead>
<tbody class="gt_table_body">
<tr class="odd">
<td class="gt_row gt_left">gpt-4o-2024-11-20</td>
<td class="gt_row gt_left">Manual (2023)</td>
<td class="gt_row gt_right">None</td>
<td class="gt_row gt_right">10</td>
<td class="gt_row gt_left">❌</td>
<td class="gt_row gt_right">71.28</td>
</tr>
<tr class="even">
<td class="gt_row gt_left">gpt-4o-mini-2024-07-18</td>
<td class="gt_row gt_left">DSPy</td>
<td class="gt_row gt_right">15</td>
<td class="gt_row gt_right">40 + 1</td>
<td class="gt_row gt_left">❌</td>
<td class="gt_row gt_right">62.83</td>
</tr>
<tr class="odd">
<td class="gt_row gt_left">llama-v3p1-70b-instruct</td>
<td class="gt_row gt_left">DSPy</td>
<td class="gt_row gt_right">60</td>
<td class="gt_row gt_right">5 + 1</td>
<td class="gt_row gt_left">❌</td>
<td class="gt_row gt_right">61.49</td>
</tr>
<tr class="even">
<td class="gt_row gt_left">gpt-4o-mini-2024-07-18</td>
<td class="gt_row gt_left">DSPy</td>
<td class="gt_row gt_right">60</td>
<td class="gt_row gt_right">10 + 1</td>
<td class="gt_row gt_left">❌</td>
<td class="gt_row gt_right">61.34</td>
</tr>
<tr class="odd">
<td class="gt_row gt_left">gpt-4o-mini-2024-07-18</td>
<td class="gt_row gt_left">DSPy</td>
<td class="gt_row gt_right">15</td>
<td class="gt_row gt_right">20 + 1</td>
<td class="gt_row gt_left">❌</td>
<td class="gt_row gt_right">60.87</td>
</tr>
<tr class="even">
<td class="gt_row gt_left">gpt-4o-mini-2024-07-18</td>
<td class="gt_row gt_left">DSPy</td>
<td class="gt_row gt_right">30</td>
<td class="gt_row gt_right">20 + 1</td>
<td class="gt_row gt_left">❌</td>
<td class="gt_row gt_right">60.87</td>
</tr>
<tr class="odd">
<td class="gt_row gt_left">gpt-4o-mini-2024-07-18</td>
<td class="gt_row gt_left">DSPy</td>
<td class="gt_row gt_right">60</td>
<td class="gt_row gt_right">20 + 1</td>
<td class="gt_row gt_left">❌</td>
<td class="gt_row gt_right">60.87</td>
</tr>
<tr class="even">
<td class="gt_row gt_left">llama-v3p1-70b-instruct</td>
<td class="gt_row gt_left">DSPy</td>
<td class="gt_row gt_right">15</td>
<td class="gt_row gt_right">40 + 1</td>
<td class="gt_row gt_left">❌</td>
<td class="gt_row gt_right">60.32</td>
</tr>
<tr class="odd">
<td class="gt_row gt_left">llama-v3p1-70b-instruct</td>
<td class="gt_row gt_left">DSPy</td>
<td class="gt_row gt_right">60</td>
<td class="gt_row gt_right">5 + 1</td>
<td class="gt_row gt_left">✅</td>
<td class="gt_row gt_right">60.27</td>
</tr>
<tr class="even">
<td class="gt_row gt_left">gpt-4o-mini-2024-07-18</td>
<td class="gt_row gt_left">DSPy</td>
<td class="gt_row gt_right">60</td>
<td class="gt_row gt_right">40 + 1</td>
<td class="gt_row gt_left">✅</td>
<td class="gt_row gt_right">59.80</td>
</tr>
<tr class="odd">
<td class="gt_row gt_left">gpt-4o-mini-2024-07-18</td>
<td class="gt_row gt_left">DSPy</td>
<td class="gt_row gt_right">15</td>
<td class="gt_row gt_right">20 + 1</td>
<td class="gt_row gt_left">✅</td>
<td class="gt_row gt_right">59.68</td>
</tr>
<tr class="even">
<td class="gt_row gt_left">gpt-4o-mini-2024-07-18</td>
<td class="gt_row gt_left">DSPy</td>
<td class="gt_row gt_right">30</td>
<td class="gt_row gt_right">20 + 1</td>
<td class="gt_row gt_left">✅</td>
<td class="gt_row gt_right">59.68</td>
</tr>
<tr class="odd">
<td class="gt_row gt_left">gpt-4o-mini-2024-07-18</td>
<td class="gt_row gt_left">DSPy</td>
<td class="gt_row gt_right">60</td>
<td class="gt_row gt_right">20 + 1</td>
<td class="gt_row gt_left">✅</td>
<td class="gt_row gt_right">59.68</td>
</tr>
<tr class="even">
<td class="gt_row gt_left">gpt-4o-mini-2024-07-18</td>
<td class="gt_row gt_left">DSPy</td>
<td class="gt_row gt_right">30</td>
<td class="gt_row gt_right">40 + 1</td>
<td class="gt_row gt_left">✅</td>
<td class="gt_row gt_right">59.60</td>
</tr>
<tr class="odd">
<td class="gt_row gt_left">gpt-4o-mini-2024-07-18</td>
<td class="gt_row gt_left">DSPy</td>
<td class="gt_row gt_right">15</td>
<td class="gt_row gt_right">40 + 1</td>
<td class="gt_row gt_left">✅</td>
<td class="gt_row gt_right">59.32</td>
</tr>
<tr class="even">
<td class="gt_row gt_left">gpt-4o-mini-2024-07-18</td>
<td class="gt_row gt_left">DSPy</td>
<td class="gt_row gt_right">60</td>
<td class="gt_row gt_right">5 + 1</td>
<td class="gt_row gt_left">❌</td>
<td class="gt_row gt_right">58.83</td>
</tr>
<tr class="odd">
<td class="gt_row gt_left">llama-v3p1-70b-instruct</td>
<td class="gt_row gt_left">DSPy</td>
<td class="gt_row gt_right">30</td>
<td class="gt_row gt_right">10 + 1</td>
<td class="gt_row gt_left">❌</td>
<td class="gt_row gt_right">58.79</td>
</tr>
<tr class="even">
<td class="gt_row gt_left">llama-v3p1-70b-instruct</td>
<td class="gt_row gt_left">DSPy</td>
<td class="gt_row gt_right">60</td>
<td class="gt_row gt_right">10 + 1</td>
<td class="gt_row gt_left">❌</td>
<td class="gt_row gt_right">58.79</td>
</tr>
<tr class="odd">
<td class="gt_row gt_left">llama-v3p1-70b-instruct</td>
<td class="gt_row gt_left">DSPy</td>
<td class="gt_row gt_right">30</td>
<td class="gt_row gt_right">5 + 1</td>
<td class="gt_row gt_left">❌</td>
<td class="gt_row gt_right">58.36</td>
</tr>
<tr class="even">
<td class="gt_row gt_left">llama-v3p1-70b-instruct</td>
<td class="gt_row gt_left">DSPy</td>
<td class="gt_row gt_right">60</td>
<td class="gt_row gt_right">20 + 1</td>
<td class="gt_row gt_left">❌</td>
<td class="gt_row gt_right">57.98</td>
</tr>
<tr class="odd">
<td class="gt_row gt_left">llama-v3p1-70b-instruct</td>
<td class="gt_row gt_left">DSPy</td>
<td class="gt_row gt_right">30</td>
<td class="gt_row gt_right">20 + 1</td>
<td class="gt_row gt_left">❌</td>
<td class="gt_row gt_right">57.84</td>
</tr>
<tr class="even">
<td class="gt_row gt_left">gpt-3.5-turbo-0125</td>
<td class="gt_row gt_left">Manual (2023)</td>
<td class="gt_row gt_right">None</td>
<td class="gt_row gt_right">10</td>
<td class="gt_row gt_left">❌</td>
<td class="gt_row gt_right">57.45</td>
</tr>
<tr class="odd">
<td class="gt_row gt_left">llama-v3p1-70b-instruct</td>
<td class="gt_row gt_left">DSPy</td>
<td class="gt_row gt_right">60</td>
<td class="gt_row gt_right">40 + 1</td>
<td class="gt_row gt_left">✅</td>
<td class="gt_row gt_right">56.46</td>
</tr>
<tr class="even">
<td class="gt_row gt_left">gpt-4o-mini-2024-07-18</td>
<td class="gt_row gt_left">Manual (2023)</td>
<td class="gt_row gt_right">None</td>
<td class="gt_row gt_right">10</td>
<td class="gt_row gt_left">❌</td>
<td class="gt_row gt_right">55.67</td>
</tr>
<tr class="odd">
<td class="gt_row gt_left">llama-v3p1-70b-instruct</td>
<td class="gt_row gt_left">DSPy</td>
<td class="gt_row gt_right">15</td>
<td class="gt_row gt_right">20 + 1</td>
<td class="gt_row gt_left">❌</td>
<td class="gt_row gt_right">54.90</td>
</tr>
<tr class="even">
<td class="gt_row gt_left">llama-v3p1-70b-instruct</td>
<td class="gt_row gt_left">DSPy</td>
<td class="gt_row gt_right">60</td>
<td class="gt_row gt_right">10 + 1</td>
<td class="gt_row gt_left">✅</td>
<td class="gt_row gt_right">54.33</td>
</tr>
<tr class="odd">
<td class="gt_row gt_left">llama-v3p1-70b-instruct</td>
<td class="gt_row gt_left">DSPy</td>
<td class="gt_row gt_right">15</td>
<td class="gt_row gt_right">40 + 1</td>
<td class="gt_row gt_left">✅</td>
<td class="gt_row gt_right">54.09</td>
</tr>
<tr class="even">
<td class="gt_row gt_left">llama-v3p1-70b-instruct</td>
<td class="gt_row gt_left">DSPy</td>
<td class="gt_row gt_right">30</td>
<td class="gt_row gt_right">40 + 1</td>
<td class="gt_row gt_left">✅</td>
<td class="gt_row gt_right">54.09</td>
</tr>
<tr class="odd">
<td class="gt_row gt_left">gpt-4o-mini-2024-07-18</td>
<td class="gt_row gt_left">DSPy</td>
<td class="gt_row gt_right">15</td>
<td class="gt_row gt_right">5 + 1</td>
<td class="gt_row gt_left">❌</td>
<td class="gt_row gt_right">53.70</td>
</tr>
<tr class="even">
<td class="gt_row gt_left">gpt-4o-mini-2024-07-18</td>
<td class="gt_row gt_left">DSPy</td>
<td class="gt_row gt_right">30</td>
<td class="gt_row gt_right">5 + 1</td>
<td class="gt_row gt_left">❌</td>
<td class="gt_row gt_right">53.70</td>
</tr>
<tr class="odd">
<td class="gt_row gt_left">llama-v3p1-70b-instruct</td>
<td class="gt_row gt_left">DSPy</td>
<td class="gt_row gt_right">30</td>
<td class="gt_row gt_right">5 + 1</td>
<td class="gt_row gt_left">✅</td>
<td class="gt_row gt_right">53.05</td>
</tr>
<tr class="even">
<td class="gt_row gt_left">gpt-4o-mini-2024-07-18</td>
<td class="gt_row gt_left">DSPy</td>
<td class="gt_row gt_right">60</td>
<td class="gt_row gt_right">10 + 1</td>
<td class="gt_row gt_left">✅</td>
<td class="gt_row gt_right">52.64</td>
</tr>
<tr class="odd">
<td class="gt_row gt_left">gpt-4o-mini-2024-07-18</td>
<td class="gt_row gt_left">DSPy</td>
<td class="gt_row gt_right">15</td>
<td class="gt_row gt_right">10 + 1</td>
<td class="gt_row gt_left">❌</td>
<td class="gt_row gt_right">51.19</td>
</tr>
<tr class="even">
<td class="gt_row gt_left">gpt-4o-mini-2024-07-18</td>
<td class="gt_row gt_left">DSPy</td>
<td class="gt_row gt_right">30</td>
<td class="gt_row gt_right">10 + 1</td>
<td class="gt_row gt_left">❌</td>
<td class="gt_row gt_right">51.19</td>
</tr>
<tr class="odd">
<td class="gt_row gt_left">gpt-4o-mini-2024-07-18</td>
<td class="gt_row gt_left">DSPy</td>
<td class="gt_row gt_right">15</td>
<td class="gt_row gt_right">5 + 1</td>
<td class="gt_row gt_left">✅</td>
<td class="gt_row gt_right">51.16</td>
</tr>
<tr class="even">
<td class="gt_row gt_left">gpt-4o-mini-2024-07-18</td>
<td class="gt_row gt_left">DSPy</td>
<td class="gt_row gt_right">30</td>
<td class="gt_row gt_right">5 + 1</td>
<td class="gt_row gt_left">✅</td>
<td class="gt_row gt_right">51.16</td>
</tr>
<tr class="odd">
<td class="gt_row gt_left">gpt-4o-mini-2024-07-18</td>
<td class="gt_row gt_left">DSPy</td>
<td class="gt_row gt_right">60</td>
<td class="gt_row gt_right">5 + 1</td>
<td class="gt_row gt_left">✅</td>
<td class="gt_row gt_right">51.16</td>
</tr>
<tr class="even">
<td class="gt_row gt_left">llama-v3p1-70b-instruct</td>
<td class="gt_row gt_left">DSPy</td>
<td class="gt_row gt_right">30</td>
<td class="gt_row gt_right">20 + 1</td>
<td class="gt_row gt_left">✅</td>
<td class="gt_row gt_right">50.90</td>
</tr>
<tr class="odd">
<td class="gt_row gt_left">llama-v3p1-70b-instruct</td>
<td class="gt_row gt_left">DSPy</td>
<td class="gt_row gt_right">60</td>
<td class="gt_row gt_right">20 + 1</td>
<td class="gt_row gt_left">✅</td>
<td class="gt_row gt_right">50.90</td>
</tr>
<tr class="even">
<td class="gt_row gt_left">gpt-4o-mini-2024-07-18</td>
<td class="gt_row gt_left">DSPy</td>
<td class="gt_row gt_right">30</td>
<td class="gt_row gt_right">10 + 1</td>
<td class="gt_row gt_left">✅</td>
<td class="gt_row gt_right">49.97</td>
</tr>
<tr class="odd">
<td class="gt_row gt_left">gpt-4o-mini-2024-07-18</td>
<td class="gt_row gt_left">DSPy</td>
<td class="gt_row gt_right">15</td>
<td class="gt_row gt_right">10 + 1</td>
<td class="gt_row gt_left">✅</td>
<td class="gt_row gt_right">49.74</td>
</tr>
<tr class="even">
<td class="gt_row gt_left">llama-v3p1-70b-instruct</td>
<td class="gt_row gt_left">DSPy</td>
<td class="gt_row gt_right">15</td>
<td class="gt_row gt_right">20 + 1</td>
<td class="gt_row gt_left">✅</td>
<td class="gt_row gt_right">49.47</td>
</tr>
<tr class="odd">
<td class="gt_row gt_left">llama-v3p1-70b-instruct</td>
<td class="gt_row gt_left">DSPy</td>
<td class="gt_row gt_right">15</td>
<td class="gt_row gt_right">10 + 1</td>
<td class="gt_row gt_left">❌</td>
<td class="gt_row gt_right">48.63</td>
</tr>
<tr class="even">
<td class="gt_row gt_left">llama-v3p1-70b-instruct</td>
<td class="gt_row gt_left">DSPy</td>
<td class="gt_row gt_right">15</td>
<td class="gt_row gt_right">5 + 1</td>
<td class="gt_row gt_left">✅</td>
<td class="gt_row gt_right">47.73</td>
</tr>
<tr class="odd">
<td class="gt_row gt_left">llama-v3p1-70b-instruct</td>
<td class="gt_row gt_left">DSPy</td>
<td class="gt_row gt_right">30</td>
<td class="gt_row gt_right">10 + 1</td>
<td class="gt_row gt_left">✅</td>
<td class="gt_row gt_right">47.30</td>
</tr>
<tr class="even">
<td class="gt_row gt_left">llama-v3p1-70b-instruct</td>
<td class="gt_row gt_left">DSPy</td>
<td class="gt_row gt_right">15</td>
<td class="gt_row gt_right">5 + 1</td>
<td class="gt_row gt_left">❌</td>
<td class="gt_row gt_right">46.46</td>
</tr>
<tr class="odd">
<td class="gt_row gt_left">llama-v3p1-70b-instruct</td>
<td class="gt_row gt_left">DSPy</td>
<td class="gt_row gt_right">15</td>
<td class="gt_row gt_right">10 + 1</td>
<td class="gt_row gt_left">✅</td>
<td class="gt_row gt_right">46.31</td>
</tr>
</tbody><tfoot class="gt_sourcenotes">
<tr class="odd">
<td colspan="6" class="gt_sourcenote">¹ Bootstrapped + labeled examples. Notes: Limited Llama 3.1 70B non-CoT runs due to API constraints. Manual prompt runs use 10 examples vs. 6 in original paper.</td>
</tr>
</tfoot>

</table>


</div>
</div>
</div>
<section id="comparison-to-the-2023-manual-prompts" class="level3">
<h3 class="anchored" data-anchor-id="comparison-to-the-2023-manual-prompts">Comparison to the 2023 manual prompts</h3>
<p>The DSPy runs are competitive with the manually crafted prompts from the 2023 paper. In contrast to the manual prompt, DSPy instructions are relatively short and emphasize the use of few-shot examples to illustrate the task.</p>
</section>
<section id="impact-of-hyperparameters" class="level3">
<h3 class="anchored" data-anchor-id="impact-of-hyperparameters">Impact of hyperparameters</h3>
<p>To understand which factors significantly influence the F1 score, we’ll run a simple linear regression analysis. The manual runs are excluded. To analyze the impact of the model choice, we’ll create a boolean variable for <code>gpt-4o-mini</code> and treat <code>llama-v3p1-70b-instruct</code> as the baseline.</p>
<div id="6973a37c" class="cell" data-execution_count="24">
<details class="code-fold">
<summary>Code</summary>
<div class="code-copy-outer-scaffold"><div class="sourceCode cell-code" id="cb31" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb31-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> statsmodels.api <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> sm</span>
<span id="cb31-2"><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="cb31-3"></span>
<span id="cb31-4"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Prepare data for regression</span></span>
<span id="cb31-5">reg_df <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> results_df.<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">filter</span>(</span>
<span id="cb31-6">    <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">~</span>pl.col(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"run_name"</span>).<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>.contains(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"manual"</span>),</span>
<span id="cb31-7">    pl.col(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"model"</span>).<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>.contains(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"gpt-4o-mini"</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">|</span> pl.col(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"model"</span>).<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>.contains(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"llama"</span>),</span>
<span id="cb31-8">).with_columns(  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># exclude manual prompts</span></span>
<span id="cb31-9">    pl.col(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"model"</span>).<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span>.contains(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"gpt-4o-mini"</span>).alias(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"is_gpt4_mini"</span>),</span>
<span id="cb31-10">)</span>
<span id="cb31-11"></span>
<span id="cb31-12"></span>
<span id="cb31-13"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Convert to pandas and ensure numeric types</span></span>
<span id="cb31-14">X <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> reg_df.select(</span>
<span id="cb31-15">    [<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"max_demos"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"chain_of_thought"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"num_trials"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"is_gpt4_mini"</span>]</span>
<span id="cb31-16">).to_pandas()</span>
<span id="cb31-17"></span>
<span id="cb31-18"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Convert boolean columns to int</span></span>
<span id="cb31-19">bool_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;">"chain_of_thought"</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"is_gpt4_mini"</span>]</span>
<span id="cb31-20"><span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> col <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> bool_columns:</span>
<span id="cb31-21">    X[col] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> X[col].astype(<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span>)</span>
<span id="cb31-22"></span>
<span id="cb31-23">y <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> reg_df.select(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"f1"</span>).to_pandas()</span>
<span id="cb31-24"></span>
<span id="cb31-25"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Add constant for intercept</span></span>
<span id="cb31-26">X <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> sm.add_constant(X)</span>
<span id="cb31-27"></span>
<span id="cb31-28"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Fit regression</span></span>
<span id="cb31-29">model <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> sm.OLS(y, X).fit()</span>
<span id="cb31-30">n <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;">len</span>(reg_df)</span>
<span id="cb31-31">r2 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> model.rsquared</span>
<span id="cb31-32"></span>
<span id="cb31-33"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Print results using GT</span></span>
<span id="cb31-34">df <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> pd.DataFrame(</span>
<span id="cb31-35">    model.summary().tables[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>],</span>
<span id="cb31-36">    columns<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>[</span>
<span id="cb31-37">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Parameter"</span>,</span>
<span id="cb31-38">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Coefficient"</span>,</span>
<span id="cb31-39">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Std Error"</span>,</span>
<span id="cb31-40">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"t"</span>,</span>
<span id="cb31-41">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"p&gt;|t|"</span>,</span>
<span id="cb31-42">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"[0.025"</span>,</span>
<span id="cb31-43">        <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"0.975]"</span>,</span>
<span id="cb31-44">    ],</span>
<span id="cb31-45">)</span>
<span id="cb31-46"></span>
<span id="cb31-47">df <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> df.iloc[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>:]  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># remove row with repeated column names</span></span>
<span id="cb31-48"></span>
<span id="cb31-49">GT(df).tab_header(</span>
<span id="cb31-50">    title<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;">"Hyperparameter Analysis"</span>, subtitle<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;">"Dependent variable: F1 score"</span></span>
<span id="cb31-51">).cols_align(align<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;">"right"</span>).tab_source_note(<span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">f"n=</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>n<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;"> runs, R²=</span><span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">{</span>r2<span class="sc" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">:.2f}</span><span class="ss" style="color: #20794D;
background-color: null;
font-style: inherit;">"</span>)</span></code></pre></div></div>
</details>
<div class="cell-output cell-output-display" data-execution_count="21">
<div id="weybrcaczr" style="padding-left:0px;padding-right:0px;padding-top:10px;padding-bottom:10px;overflow-x:auto;overflow-y:auto;width:auto;height:auto;">
<style>
#weybrcaczr table {
          font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Helvetica Neue', 'Fira Sans', 'Droid Sans', Arial, sans-serif;
          -webkit-font-smoothing: antialiased;
          -moz-osx-font-smoothing: grayscale;
        }

#weybrcaczr thead, tbody, tfoot, tr, td, th { border-style: none; }
 tr { background-color: transparent; }
#weybrcaczr p { margin: 0; padding: 0; }
 #weybrcaczr .gt_table { display: table; border-collapse: collapse; line-height: normal; margin-left: auto; margin-right: auto; color: #333333; font-size: 16px; font-weight: normal; font-style: normal; background-color: #FFFFFF; width: auto; border-top-style: solid; border-top-width: 2px; border-top-color: #A8A8A8; border-right-style: none; border-right-width: 2px; border-right-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #A8A8A8; border-left-style: none; border-left-width: 2px; border-left-color: #D3D3D3; }
 #weybrcaczr .gt_caption { padding-top: 4px; padding-bottom: 4px; }
 #weybrcaczr .gt_title { color: #333333; font-size: 125%; font-weight: initial; padding-top: 4px; padding-bottom: 4px; padding-left: 5px; padding-right: 5px; border-bottom-color: #FFFFFF; border-bottom-width: 0; }
 #weybrcaczr .gt_subtitle { color: #333333; font-size: 85%; font-weight: initial; padding-top: 3px; padding-bottom: 5px; padding-left: 5px; padding-right: 5px; border-top-color: #FFFFFF; border-top-width: 0; }
 #weybrcaczr .gt_heading { background-color: #FFFFFF; text-align: center; border-bottom-color: #FFFFFF; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; }
 #weybrcaczr .gt_bottom_border { border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; }
 #weybrcaczr .gt_col_headings { border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; }
 #weybrcaczr .gt_col_heading { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: normal; text-transform: inherit; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: bottom; padding-top: 5px; padding-bottom: 5px; padding-left: 5px; padding-right: 5px; overflow-x: hidden; }
 #weybrcaczr .gt_column_spanner_outer { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: normal; text-transform: inherit; padding-top: 0; padding-bottom: 0; padding-left: 4px; padding-right: 4px; }
 #weybrcaczr .gt_column_spanner_outer:first-child { padding-left: 0; }
 #weybrcaczr .gt_column_spanner_outer:last-child { padding-right: 0; }
 #weybrcaczr .gt_column_spanner { border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; vertical-align: bottom; padding-top: 5px; padding-bottom: 5px; overflow-x: hidden; display: inline-block; width: 100%; }
 #weybrcaczr .gt_spanner_row { border-bottom-style: hidden; }
 #weybrcaczr .gt_group_heading { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; text-transform: inherit; border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; text-align: left; }
 #weybrcaczr .gt_empty_group_heading { padding: 0.5px; color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; vertical-align: middle; }
 #weybrcaczr .gt_from_md> :first-child { margin-top: 0; }
 #weybrcaczr .gt_from_md> :last-child { margin-bottom: 0; }
 #weybrcaczr .gt_row { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 1px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; }
 #weybrcaczr .gt_stub { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; text-transform: inherit; border-right-style: solid; border-right-width: 2px; border-right-color: #D3D3D3; padding-left: 5px; padding-right: 5px; }
 #weybrcaczr .gt_stub_row_group { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; text-transform: inherit; border-right-style: solid; border-right-width: 2px; border-right-color: #D3D3D3; padding-left: 5px; padding-right: 5px; vertical-align: top; }
 #weybrcaczr .gt_row_group_first td { border-top-width: 2px; }
 #weybrcaczr .gt_row_group_first th { border-top-width: 2px; }
 #weybrcaczr .gt_striped { background-color: rgba(128,128,128,0.05); }
 #weybrcaczr .gt_table_body { border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; }
 #weybrcaczr .gt_sourcenotes { color: #333333; background-color: #FFFFFF; border-bottom-style: none; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 2px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 2px; border-right-color: #D3D3D3; }
 #weybrcaczr .gt_sourcenote { font-size: 90%; padding-top: 4px; padding-bottom: 4px; padding-left: 5px; padding-right: 5px; text-align: left; }
 #weybrcaczr .gt_left { text-align: left; }
 #weybrcaczr .gt_center { text-align: center; }
 #weybrcaczr .gt_right { text-align: right; font-variant-numeric: tabular-nums; }
 #weybrcaczr .gt_font_normal { font-weight: normal; }
 #weybrcaczr .gt_font_bold { font-weight: bold; }
 #weybrcaczr .gt_font_italic { font-style: italic; }
 #weybrcaczr .gt_super { font-size: 65%; }
 #weybrcaczr .gt_footnote_marks { font-size: 75%; vertical-align: 0.4em; position: initial; }
 #weybrcaczr .gt_asterisk { font-size: 100%; vertical-align: 0; }
 
</style>

<table class="gt_table caption-top table table-sm table-striped small" data-quarto-bootstrap="false">
<thead>
<tr class="gt_heading header">
<td colspan="7" class="gt_heading gt_title gt_font_normal">Hyperparameter Analysis</td>
</tr>
<tr class="gt_heading even">
<td colspan="7" class="gt_heading gt_subtitle gt_font_normal gt_bottom_border">Dependent variable: F1 score</td>
</tr>
<tr class="gt_col_headings header">
<th id="Parameter" class="gt_col_heading gt_columns_bottom_border gt_right" data-quarto-table-cell-role="th" scope="col">Parameter</th>
<th id="Coefficient" class="gt_col_heading gt_columns_bottom_border gt_right" data-quarto-table-cell-role="th" scope="col">Coefficient</th>
<th id="Std Error" class="gt_col_heading gt_columns_bottom_border gt_right" data-quarto-table-cell-role="th" scope="col">Std Error</th>
<th id="t" class="gt_col_heading gt_columns_bottom_border gt_right" data-quarto-table-cell-role="th" scope="col">t</th>
<th id="p&amp;gt;|t|" class="gt_col_heading gt_columns_bottom_border gt_right" data-quarto-table-cell-role="th" scope="col">p&gt;|t|</th>
<th id="[0.025" class="gt_col_heading gt_columns_bottom_border gt_right" data-quarto-table-cell-role="th" scope="col">[0.025</th>
<th id="0.975]" class="gt_col_heading gt_columns_bottom_border gt_right" data-quarto-table-cell-role="th" scope="col">0.975]</th>
</tr>
</thead>
<tbody class="gt_table_body">
<tr class="odd">
<td class="gt_row gt_right">const</td>
<td class="gt_row gt_right">49.3654</td>
<td class="gt_row gt_right">1.457</td>
<td class="gt_row gt_right">33.885</td>
<td class="gt_row gt_right">0.000</td>
<td class="gt_row gt_right">46.419</td>
<td class="gt_row gt_right">52.312</td>
</tr>
<tr class="even">
<td class="gt_row gt_right">max_demos</td>
<td class="gt_row gt_right">0.2021</td>
<td class="gt_row gt_right">0.042</td>
<td class="gt_row gt_right">4.794</td>
<td class="gt_row gt_right">0.000</td>
<td class="gt_row gt_right">0.117</td>
<td class="gt_row gt_right">0.287</td>
</tr>
<tr class="odd">
<td class="gt_row gt_right">chain_of_thought</td>
<td class="gt_row gt_right">-4.3317</td>
<td class="gt_row gt_right">1.038</td>
<td class="gt_row gt_right">-4.172</td>
<td class="gt_row gt_right">0.000</td>
<td class="gt_row gt_right">-6.432</td>
<td class="gt_row gt_right">-2.232</td>
</tr>
<tr class="even">
<td class="gt_row gt_right">num_trials</td>
<td class="gt_row gt_right">0.1062</td>
<td class="gt_row gt_right">0.027</td>
<td class="gt_row gt_right">3.892</td>
<td class="gt_row gt_right">0.000</td>
<td class="gt_row gt_right">0.051</td>
<td class="gt_row gt_right">0.161</td>
</tr>
<tr class="odd">
<td class="gt_row gt_right">is_gpt4_mini</td>
<td class="gt_row gt_right">2.2964</td>
<td class="gt_row gt_right">1.016</td>
<td class="gt_row gt_right">2.260</td>
<td class="gt_row gt_right">0.029</td>
<td class="gt_row gt_right">0.241</td>
<td class="gt_row gt_right">4.351</td>
</tr>
</tbody><tfoot class="gt_sourcenotes">
<tr class="odd">
<td colspan="7" class="gt_sourcenote">n=44 runs, R²=0.56</td>
</tr>
</tfoot>

</table>


</div>
</div>
</div>
</section>
<section id="few-shot-examples" class="level3">
<h3 class="anchored" data-anchor-id="few-shot-examples">Few-shot examples</h3>
<p>More examples are generally better, as indicated by the positive coefficient in the regression. However, the top runs didn’t use more than 20 examples, indicating that there are diminishing returns.</p>
</section>
<section id="chain-of-thought-cot" class="level3">
<h3 class="anchored" data-anchor-id="chain-of-thought-cot">Chain of thought (CoT)</h3>
<p>Runs where the model was instructed to perform an intermediate reasoning step yielded worse results than those without. This is an unusual result - typically CoT helps LLMs achieve better results, for example the main advantage of OpenAI’s <code>o1-preview</code> over <code>gpt-4o</code> is the advanced CoT that is built into it. However, on this structured task and using DSPy’s <code>Predictor</code> and <code>ChainOfThought</code> classes, CoT seems to be detrimental.</p>
</section>
<section id="model-choice" class="level3">
<h3 class="anchored" data-anchor-id="model-choice">Model choice</h3>
<ul>
<li><code>gpt-4o-mini-2024-07-18</code> seems to have an edge over <code>llama-v3p1-70b-instruct</code>, but the confidence interval is wide.</li>
<li><code>gpt-4o-2024-11-20</code> performs better than the other models that were tested. I expect that performance of similar sized models such as <code>Llama 3.1 405B</code> will be similar. Due to cost considerations, I’ve skipped the optimization of a large model with DSPy.</li>
<li><code>gpt-3.5-turbo-0125</code> performed better than <code>gpt-4o-mini-2024-07-18</code>, but worse than the deprecated <code>gpt-3.5-turbo-0613</code> performed during the experiments for the 2023 paper (57.45 vs.&nbsp;65.65 F1 Score).</li>
</ul>
</section>
<section id="number-of-trials" class="level3">
<h3 class="anchored" data-anchor-id="number-of-trials">Number of trials</h3>
<p>Using more trials is associated with higher F1 scores. However, the table also shows setups with identical results at 15, 30 and 60 trials. Going beyond 60 trials isn’t likely to be helpful.</p>
</section>
</section>
<section id="review-of-dspy" class="level2">
<h2 class="anchored" data-anchor-id="review-of-dspy">Review of DSPy</h2>
<p>Here are my conclusions based on this experiment.</p>
<div class="columns">
<div class="column" style="width:50%;">
<p><strong>Pros ✅</strong></p>
<ul>
<li>Creates prompts that are as good as or better than manually crafted prompts.</li>
<li>No need to manually craft prompts, leading to faster iteration speed.</li>
<li>Able to deal with multi-step workflows.</li>
<li>Naturally encourages a structured approach focused on evaluation.</li>
<li>Supports many LLMs, via APIs and locally.</li>
<li>Lightweight JSON export of the optimized prompts.</li>
<li>Supports custom evaluation metrics.</li>
<li>Built-in threading and caching, which saved me time and money.</li>
<li>Actively developed and has a large community.</li>
<li>Lots of <a href="https://github.com/stanfordnlp/dspy/tree/main/examples">tutorial notebooks</a>.</li>
</ul>
</div><div class="column" style="width:50%;">
<p><strong>Cons ❌</strong></p>
<ul>
<li>Generated prompts seem too short to explain the nuances of the task, placing a lot of burden on the few-shot examples. They need to implicitly explain the annotation rules and cover all relevant cases.</li>
<li>Loss of control over the exact prompt. But arguably, if you want to control the prompt DSPy is not the approach to go for anyway.</li>
<li>Adds a layer of abstraction to a stack that’s already complex.</li>
<li>Structured output is not guaranteed, because it’s based on prompting only. Integration with function calling, JSON mode or constrained generation APIs and libraries would improve the reliability of the format.</li>
<li>Steep learning curve with many concepts to understand.</li>
<li>I encountered some bugs and deprecated functions and tutorials.</li>
</ul>
</div>
</div>
<p>DSPy is a great alternative to manual prompting, especially for tasks that have a clear evaluation metric and are demonstrable using few-shot examples. The high variability in the results of my grid search experiment indicates that it’s necessary to run DSPy multiple times with different settings to find the best performing configuration.</p>
<p>A feature that I haven’t explored here is the fine-tuning <a href="https://dspy.ai/learn/optimization/optimizers/?h=fine#automatic-finetuning">optimizer</a> of DSPy that actually modifies the model weights. It’s promising for this task, as a fine-tuned <code>gpt-3.5-turbo-0613</code> is still the <a href="https://paperswithcode.com/sota/aspect-based-sentiment-analysis-on-semeval-6">record holder</a> at an F1 score of 83.76.</p>



</section>

<div id="quarto-appendix" class="default"><section class="quarto-appendix-contents" id="quarto-bibliography"><h2 class="anchored quarto-appendix-heading">References</h2><div id="refs" class="references csl-bib-body hanging-indent" data-entry-spacing="0">
<div id="ref-opsahlong2024optimizinginstructionsdemonstrationsmultistage" class="csl-entry">
Opsahl-Ong, Krista, Michael J Ryan, Josh Purtell, David Broman, Christopher Potts, Matei Zaharia, and Omar Khattab. 2024. <span>“Optimizing Instructions and Demonstrations for Multi-Stage Language Model Programs.”</span> <a href="https://arxiv.org/abs/2406.11695">https://arxiv.org/abs/2406.11695</a>.
</div>
<div id="ref-pontiki_semeval" class="csl-entry">
Pontiki, Maria, Dimitris Galanis, John Pavlopoulos, Harris Papageorgiou, Ion Androutsopoulos, and Suresh Manandhar. 2014. <span>“<span>SemEval</span>-2014 <span>Task</span> 4: <span>Aspect</span> <span>Based</span> <span>Sentiment</span> <span>Analysis</span>.”</span> In <em>Proceedings of the 8th <span>International</span> <span>Workshop</span> on <span>Semantic</span> <span>Evaluation</span> (<span>SemEval</span> 2014)</em>, 27–35. Dublin, Ireland: Association for Computational Linguistics. <a href="https://doi.org/10.3115/v1/S14-2004">https://doi.org/10.3115/v1/S14-2004</a>.
</div>
<div id="ref-simmering2023large" class="csl-entry">
Simmering, Paul F., and Paavo Huoviala. 2023. <span>“Large Language Models for Aspect-Based Sentiment Analysis.”</span> <a href="https://arxiv.org/abs/2310.18025">https://arxiv.org/abs/2310.18025</a>.
</div>
</div></section></div> ]]></description>
  <category>Machine Learning</category>
  <category>Python</category>
  <guid>https://simmering.dev/blog/absa-with-dspy/</guid>
  <pubDate>Sat, 23 Nov 2024 23:00:00 GMT</pubDate>
  <media:content url="https://simmering.dev/blog/absa-with-dspy/image.webp" medium="image" type="image/webp"/>
</item>
<item>
  <title>From 5-7-5 to Thousand Lines: The Case for Longer Prompts</title>
  <dc:creator>Paul Simmering</dc:creator>
  <link>https://simmering.dev/blog/long-prompts/</link>
  <description><![CDATA[ 






<p>Prompts are the key to guide LLMs for any task, from a chatbot to a text classifier. Longer prompts are usually better than shorter ones, as I’ll argue below. There is a tradeoff, though: each interaction with a long prompt has a longer input sequence, which increases inference cost and latency. Further, a long prompt takes up more of the model’s context window, leaving less for user interaction. But both of these concerns are becoming less relevant with recent developments.</p>
<section id="long-prompts-are-getting-cheaper" class="level2">
<h2 class="anchored" data-anchor-id="long-prompts-are-getting-cheaper">Long prompts are getting cheaper</h2>
<p>There are two developments that keep bringing down the cost of long prompts:</p>
<ol type="1">
<li><p><strong>Decrease in input token cost</strong> on API platforms like OpenAI, Anthropic and others. At launch of gpt-3.5-turbo in March 2023, OpenAI charged $2 for 1 million input tokens. By August 2024, it’s $0.15 for gpt-4o-mini, a more capable model. This is a 92.5% reduction in cost. It reflects the fierce competition and the increasing efficiency of inference software, a fall in GPU prices and advances in quantization. Similar trends can be observed in inference cost for open source models, though it’s harder to reach the same economies of scale as the big platforms.</p></li>
<li><p><strong>Context caching</strong>, meaning that the model doesn’t have to recompute the prefix of the prompt for each interaction. This is also called prompt caching. It uses a KV cache (see a good explanation by <span class="citation" data-cites="log2023kvcache">Log (2023)</span>) to skip the calculation of the attention keys and values for cached tokens. Originally, this was only used within a single generation task to avoid having to re-read all tokens for each additional token generated. However, it can also be used across different generations. It’s integrated in <a href="https://docs.vllm.ai/en/stable/automatic_prefix_caching/apc.html">vLLM</a> <span class="citation" data-cites="kwon2023efficientmemorymanagementlarge">(Kwon et al. 2023)</span>, an inference library that can serve many popular open source models. Since June 2024, three API platforms have also added this feature: <a href="https://platform.deepseek.com/api-docs/news/news0802/">DeepSeek</a>, <a href="https://ai.google.dev/gemini-api/docs/caching?lang=python">Google Gemini</a> and <a href="https://www.anthropic.com/news/prompt-caching">Anthropic</a>.</p></li>
</ol>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://simmering.dev/blog/long-prompts/kv_cache_example.JPEG" class="img-fluid figure-img"></p>
<figcaption>Context caching lets subsequent requests with the same prefix use a cache. Image from <a href="platform">DeepSeek</a>.</figcaption>
</figure>
</div>
<table class="caption-top table">
<colgroup>
<col style="width: 11%">
<col style="width: 20%">
<col style="width: 16%">
<col style="width: 33%">
<col style="width: 16%">
</colgroup>
<thead>
<tr class="header">
<th>Platform</th>
<th>Model</th>
<th>Regular price</th>
<th>Caching price</th>
<th>Savings</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>DeepSeek</td>
<td>deepseek-chat</td>
<td>$0.14 / Mtok</td>
<td>$0.014 / Mtok for cache read</td>
<td>90%</td>
</tr>
<tr class="even">
<td>Anthropic</td>
<td>Claude 3.5 Sonnet</td>
<td>$3.00 / Mtok</td>
<td>$3.75 / MTok for cache write, $0.30 /Mtok for cache hits</td>
<td>90%</td>
</tr>
<tr class="odd">
<td>Gemini</td>
<td>Gemini 1.5 Pro</td>
<td>$3.50 / Mtok</td>
<td>Free cache read, $4.50 / Mtok per hour for storage</td>
<td>Variable</td>
</tr>
</tbody>
</table>
<p>The table above compares the cost savings from prompt caching on different platforms. Mtok stands for million tokens.</p>
<p>The pricing models are quite different. DeepSeek offers the best savings at 90% reduction on cache hit and no storage fees. Keep in mind that this is not a frontier model. The documentation says the cache is cleared after a few hours. Further, the feature is active by default and doesn’t require a change in code. This is different at Anthropic where the cache has to be explicitly enabled and writing to it carries a higher cost than a normal input token. As of August 31, the cache only has a 5 minute time to live (TTL), making it only useful apps with high frequency of the same prompt. Gemini charges for storage and gives control over the TTL with a default of one hour and requires explicit enabling.</p>
<p>Why is it so expensive to store 1 million tokens for one hour? The reason is that the KV cache takes a surprising amount of memory. The formula for the memory per token is:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Ctext%7Bmemory%7D%20=%20n_%7Btokens%7D%20*%202%20*%20n_%7Bheads%7D%20*%20d_%7Bhead%7D%20*%20n_%7Blayers%7D%20*%20%5Ctext%7Bprecision%20(bytes)%7D%0A"></p>
<p>The 2 represents the key and value vectors, <img src="https://latex.codecogs.com/png.latex?n_%7Bheads%7D"> is the number of attention heads, <img src="https://latex.codecogs.com/png.latex?d_%7Bhead%7D"> is the dimension of the attention head, <img src="https://latex.codecogs.com/png.latex?n_%7Blayers%7D"> is the number of layers and precision is the number of bytes used to store a single weight. Note that this doesn’t include optimizations like sparsity, quantizastion or grouped query attention <span class="citation" data-cites="ainslie2023gqatraininggeneralizedmultiquery">(Ainslie et al. 2023)</span>.</p>
<p>For a 1024 token sequence on a 175B GPT-3 model with 96 heads with 128 dimensions and 96 layers at FP16 precision, this results in</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A1024%20*%202%20*%2096%20*%20128%20*%2096%20*%2016%20%5Ctext%7B%20bytes%7D%20=%2038.65%20%5Ctext%7B%20GB%7D%0A"></p>
<p>This has to be stored in GPU memory to be accessible for the model.</p>
<p>But while $4.5 / Mtok might seem expensive for just one hour, if that input token is used at least twice in that hour, it’s already cheaper than the regular input token price. The savings are multiplied with each additional use. For use of open models on your own GPUs, this means that allocating a portion of your GPU memory to cache can be an excellent investment. It also means that for same-y inference requests, GPU memory matters more than its speed.</p>
</section>
<section id="context-sizes-are-getting-larger" class="level2">
<h2 class="anchored" data-anchor-id="context-sizes-are-getting-larger">Context sizes are getting larger</h2>
<p>Current frontier models have a context length of at least 128,000 tokens - equivalent to roughly 100,000 words or a 400 page novel.</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Provider</th>
<th>Model</th>
<th>Context size</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Google</td>
<td>Gemini 1.5 Pro</td>
<td>2m</td>
</tr>
<tr class="even">
<td>Anthropic</td>
<td>Claude 3.5 Sonnet</td>
<td>200k</td>
</tr>
<tr class="odd">
<td>Alibaba</td>
<td>Qwen2 72B</td>
<td>128k</td>
</tr>
<tr class="even">
<td>Meta</td>
<td>Llama 3.1 Instruct 405B</td>
<td>128k</td>
</tr>
<tr class="odd">
<td>Mistral</td>
<td>Mistral Large 2</td>
<td>128k</td>
</tr>
<tr class="even">
<td>OpenAI</td>
<td>GPT-4o</td>
<td>128k</td>
</tr>
</tbody>
</table>
<p>Source: <a href="https://artificialanalysis.ai">Artificialanalysis.ai</a></p>
<p>In contrast, early models like gpt-3.5-turbo in March 2023 only had a context size of 4096 tokens. In a RAG context, this means that more text chunks can be included in the prompt and in a chat context, more questions and answers can be included before the oldest ones are evicted. The problem that a prompt doesn’t fit into the context window is effectively solved for almost all applications.</p>
</section>
<section id="longer-prompts-are-often-better" class="level2">
<h2 class="anchored" data-anchor-id="longer-prompts-are-often-better">Longer prompts are often better</h2>
<p>Ok, so long prompts are getting cheaper. But how does a longer prompt help?</p>
<section id="more-detailed-guidelines" class="level3">
<h3 class="anchored" data-anchor-id="more-detailed-guidelines">1. More detailed guidelines</h3>
<p>A longer prompt can provide more context to the model, letting it perform a task more accurately or represent a brand or character more faithfully. Consider including information like this:</p>
<ul>
<li>Background information about the website, app or task that the model is embedded in.</li>
<li>Behavioral constraints, like not using certain words or phrases. For example telling the prompt to avoid starting answers with “Certainly!”, to make it sound less AI-like.</li>
<li>Style guidelines, like using a certain tone or level of formality, whether to address the user by first or last name, or to use emojis.</li>
<li>Characterization, giving the model a personality or role to play. For example, a chatbot for a bank could be characterized as a friendly and professional customer service agent.</li>
<li>A more detailed task description, like a list of steps to follow or a description of the desired output.</li>
<li>Information about the user, like their name, location, or preferences.</li>
<li>A translation glossary, if the model is used in a multilingual setting.</li>
</ul>
<p>If you’re looking for inspiration for a chatbot prompt, check the recently revealed prompts for Anthropic’s <a href="https://docs.anthropic.com/en/release-notes/system-prompts#july-12th-2024">Claude</a>.</p>
</section>
<section id="many-shot-in-context-learning" class="level3">
<h3 class="anchored" data-anchor-id="many-shot-in-context-learning">2. Many-shot in-context learning</h3>
<p>Few-shot examples can be included in the prompt for in-context learning (ICL). These examples can teach the model about the rules for the task, the desired output format, intermediate reasoning steps and handling of edge cases. Commonly this is done with 1 to 5 examples, but with prefix caching it’s possible to include 50, 100 or even more examples.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://simmering.dev/blog/long-prompts/many_shot_learning.png" class="img-fluid figure-img"></p>
<figcaption>Many-shot in-context learning. Image from Agarwal et al.&nbsp;(2024)</figcaption>
</figure>
</div>
<p><span class="citation" data-cites="agarwal2024manyshotincontextlearning">Agarwal et al. (2024)</span> ran this experiment with Gemini 1.5 Pro across several tasks. Many-shot ICL outperformed few-shot learning in all cases. For sentiment analysis they went as far as 2048 examples in the prompt, achieving an increase in 18.2 percentage points over a 32-shot prompt. In many of their experiments the limiting factor wasn’t the context size, but the number of available examples.</p>
<p>This allows a prompting approach become closer to fine-tuning, but without the need for training or a model store. <span class="citation" data-cites="bertsch2024incontextlearninglongcontextmodels">Bertsch et al. (2024)</span> made the comparison between many-shot ICL and LoRA <span class="citation" data-cites="hu2021loralowrankadaptationlarge">(Hu et al. 2021)</span> on 5 classification tasks and conclude that “finetuning is more data-hungry than ICL”. In their experiments with Llama2-7b, many-shot prompting outperformed fine-tuning up to about 1000 examples (see figure 2 of their paper).</p>
</section>
<section id="more-rag-context" class="level3">
<h3 class="anchored" data-anchor-id="more-rag-context">3. More RAG context</h3>
<p>A key design parameter in retrieval augmented generation (RAG) is the number of text chunks to retrieve from a source. With a larger context size, more and longer text chunks can be included in the prompt. This increases the likelihood that the information required to answer the query is present in the prompt.</p>
<p><span class="citation" data-cites="leng2024longcontextrag">Leng et al. (2024)</span> tested RAG answer correctness on 13 open source and proprietary LLMs.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://simmering.dev/blog/long-prompts/long_context_performance.png" class="img-fluid figure-img"></p>
<figcaption>Long context performance of GPT, Claude, Llama, Mistral and DBRX models on 4 curated RAG datasets (Databricks DocsQA, FinanceBench, HotPotQA and Natural Questions), from Leng et al.&nbsp;(2024).</figcaption>
</figure>
</div>
<p>As the graph above shows, answer correctness increased with longer context all models up to 4k tokens and up to 32k tokens for most models. This is driven by the boost in retrieval (see experiment 1 in the article).</p>
<p>However, the “lost in the middle” problem can occur, a phenomemon first found by <span class="citation" data-cites="liu2023lostmiddlelanguagemodels">(Liu et al. 2023)</span>, where information presented in the middle is not used as well as information presented at the beginning or end. It can be measured by the “needle in a haystack” method, meaning that a piece of information is hidden in a long text and the model has to find it. The longer the text, the harder it is to find the information. The RULER benchmark by <span class="citation" data-cites="hsieh2024rulerwhatsrealcontext">(Hsieh et al. 2024)</span> extended this to more complex tasks and introduced the concept of an effective context length, which is shorter than the technical context length of a model.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://simmering.dev/blog/long-prompts/lost_in_the_middle.png" class="img-fluid figure-img" style="width:50.0%"></p>
<figcaption>Lost in the middle problem. Image from Liu et al.&nbsp;(2023)</figcaption>
</figure>
</div>
<p>Retrieving more information also increases the risk of including irrelevant information. If chunk ranking works correctly, lower ranking chunks are less likely to be relevant and adding them reduces the density of relevant information. <span class="citation" data-cites="levy2024tasktokensimpactinput">Levy, Jacoby, and Goldberg (2024)</span> found that irrelevant information isn’t neutral, it’s detrimental to model performance on a question-answering task.</p>
</section>
<section id="more-functions-for-agentic-models" class="level3">
<h3 class="anchored" data-anchor-id="more-functions-for-agentic-models">4. More functions for agentic models</h3>
<p>Models used as agents are given function signatures in a JSON schema. Each of these has to be sent to the model as part of the prompt. The more functions and the more arguments they have, the longer the prompt. With lower prompt costs, it’s becoming more economical to have agents with many different and more detailed functions in their repertoire.</p>
<p>Common functions include:</p>
<ul>
<li>Send a task to a sub-agent</li>
<li>Web search</li>
<li>Query a database by using text-to-SQL</li>
<li>Redirect to a human agent</li>
<li>Call a REST API, e.g.&nbsp;to send an email or schedule a meeting</li>
<li>Execute code in Python, JavaScript or another language</li>
</ul>
<p>The Berkeley function calling leaderboard <span class="citation" data-cites="berkeley-function-calling-leaderboard">(Yan et al. 2024)</span> offers detailed benchmarks for a variety of function calling tasks.</p>
</section>
</section>
<section id="conclusion-revisit-your-prompts" class="level2">
<h2 class="anchored" data-anchor-id="conclusion-revisit-your-prompts">Conclusion: revisit your prompts</h2>
<p>In 2023, the cost of long prompts was a major concern. Each input token was precious. This has changed with the introduction of prompt caching and a massive reduction in input token cost. It’s worth reevaluating prompts and consider whether adding more information would benefit the application.</p>
<p>About the title: 5-7-5 refers to the syllable count in a haiku, a form of short poetry from Japan.</p>



</section>

<div id="quarto-appendix" class="default"><section class="quarto-appendix-contents" id="quarto-bibliography"><h2 class="anchored quarto-appendix-heading">References</h2><div id="refs" class="references csl-bib-body hanging-indent" data-entry-spacing="0">
<div id="ref-agarwal2024manyshotincontextlearning" class="csl-entry">
Agarwal, Rishabh, Avi Singh, Lei M. Zhang, Bernd Bohnet, Luis Rosias, Stephanie Chan, Biao Zhang, et al. 2024. <span>“Many-Shot in-Context Learning.”</span> <a href="https://arxiv.org/abs/2404.11018">https://arxiv.org/abs/2404.11018</a>.
</div>
<div id="ref-ainslie2023gqatraininggeneralizedmultiquery" class="csl-entry">
Ainslie, Joshua, James Lee-Thorp, Michiel de Jong, Yury Zemlyanskiy, Federico Lebrón, and Sumit Sanghai. 2023. <span>“GQA: Training Generalized Multi-Query Transformer Models from Multi-Head Checkpoints.”</span> <a href="https://arxiv.org/abs/2305.13245">https://arxiv.org/abs/2305.13245</a>.
</div>
<div id="ref-bertsch2024incontextlearninglongcontextmodels" class="csl-entry">
Bertsch, Amanda, Maor Ivgi, Uri Alon, Jonathan Berant, Matthew R. Gormley, and Graham Neubig. 2024. <span>“In-Context Learning with Long-Context Models: An in-Depth Exploration.”</span> <a href="https://arxiv.org/abs/2405.00200">https://arxiv.org/abs/2405.00200</a>.
</div>
<div id="ref-hsieh2024rulerwhatsrealcontext" class="csl-entry">
Hsieh, Cheng-Ping, Simeng Sun, Samuel Kriman, Shantanu Acharya, Dima Rekesh, Fei Jia, Yang Zhang, and Boris Ginsburg. 2024. <span>“RULER: What’s the Real Context Size of Your Long-Context Language Models?”</span> <a href="https://arxiv.org/abs/2404.06654">https://arxiv.org/abs/2404.06654</a>.
</div>
<div id="ref-hu2021loralowrankadaptationlarge" class="csl-entry">
Hu, Edward J., Yelong Shen, Phillip Wallis, Zeyuan Allen-Zhu, Yuanzhi Li, Shean Wang, Lu Wang, and Weizhu Chen. 2021. <span>“LoRA: Low-Rank Adaptation of Large Language Models.”</span> <a href="https://arxiv.org/abs/2106.09685">https://arxiv.org/abs/2106.09685</a>.
</div>
<div id="ref-kwon2023efficientmemorymanagementlarge" class="csl-entry">
Kwon, Woosuk, Zhuohan Li, Siyuan Zhuang, Ying Sheng, Lianmin Zheng, Cody Hao Yu, Joseph E. Gonzalez, Hao Zhang, and Ion Stoica. 2023. <span>“Efficient Memory Management for Large Language Model Serving with PagedAttention.”</span> <a href="https://arxiv.org/abs/2309.06180">https://arxiv.org/abs/2309.06180</a>.
</div>
<div id="ref-leng2024longcontextrag" class="csl-entry">
Leng, Quinn, Jacob Portes, Sam Havens, Matei Zaharia, and Michael Carbin. 2024. <span>“Long Context RAG Performance of LLMs.”</span> <a href="https://www.databricks.com/blog/long-context-rag-performance-llms" class="uri">https://www.databricks.com/blog/long-context-rag-performance-llms</a>.
</div>
<div id="ref-levy2024tasktokensimpactinput" class="csl-entry">
Levy, Mosh, Alon Jacoby, and Yoav Goldberg. 2024. <span>“Same Task, More Tokens: The Impact of Input Length on the Reasoning Performance of Large Language Models.”</span> <a href="https://arxiv.org/abs/2402.14848">https://arxiv.org/abs/2402.14848</a>.
</div>
<div id="ref-liu2023lostmiddlelanguagemodels" class="csl-entry">
Liu, Nelson F., Kevin Lin, John Hewitt, Ashwin Paranjape, Michele Bevilacqua, Fabio Petroni, and Percy Liang. 2023. <span>“Lost in the Middle: How Language Models Use Long Contexts.”</span> <a href="https://arxiv.org/abs/2307.03172">https://arxiv.org/abs/2307.03172</a>.
</div>
<div id="ref-log2023kvcache" class="csl-entry">
Log, Matt. 2023. <span>“What Is the KV Cache?”</span> https://mett29.github.io/posts/kv-cache/.
</div>
<div id="ref-berkeley-function-calling-leaderboard" class="csl-entry">
Yan, Fanjia, Huanzhi Mao, Charlie Cheng-Jie Ji, Tianjun Zhang, Shishir G. Patil, Ion Stoica, and Joseph E. Gonzalez. 2024. <span>“Berkeley Function Calling Leaderboard.”</span> In. <a href="https://gorilla.cs.berkeley.edu/blogs/8_berkeley_function_calling_leaderboard.html" class="uri">https://gorilla.cs.berkeley.edu/blogs/8_berkeley_function_calling_leaderboard.html</a>.
</div>
</div></section></div> ]]></description>
  <category>Machine Learning</category>
  <guid>https://simmering.dev/blog/long-prompts/</guid>
  <pubDate>Sat, 31 Aug 2024 22:00:00 GMT</pubDate>
  <media:content url="https://simmering.dev/blog/long-prompts/image.jpg" medium="image" type="image/jpeg"/>
</item>
<item>
  <title>OpenAI’s structured output vs. instructor and outlines</title>
  <dc:creator>Paul Simmering</dc:creator>
  <link>https://simmering.dev/blog/openai_structured_output/</link>
  <description><![CDATA[ 






<p>On August 6 OpenAI released <a href="https://openai.com/index/introducing-structured-outputs-in-the-api/">structured outputs</a> in their API. Is structured outputs a replacement for instructor, outlines and other libraries that provide structured outputs for language models? Let’s compare them.</p>
<p>OpenAI’s structured outputs makes the following code possible:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python 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> json</span>
<span id="cb1-2"></span>
<span id="cb1-3"><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="cb1-4"><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="cb1-5"></span>
<span id="cb1-6"></span>
<span id="cb1-7"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> Ingredient(BaseModel):</span>
<span id="cb1-8">    name: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span></span>
<span id="cb1-9">    amount: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span></span>
<span id="cb1-10">    kcal: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">int</span></span>
<span id="cb1-11"></span>
<span id="cb1-12"></span>
<span id="cb1-13"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> Recipe(BaseModel):</span>
<span id="cb1-14">    ingredients: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">list</span>[Ingredient]</span>
<span id="cb1-15">    instructions: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span></span>
<span id="cb1-16"></span>
<span id="cb1-17"></span>
<span id="cb1-18">client <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> OpenAI()</span>
<span id="cb1-19"></span>
<span id="cb1-20">completion <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> client.beta.chat.completions.parse(</span>
<span id="cb1-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-2024-08-06"</span>,</span>
<span id="cb1-22">    messages<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;">"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>: <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Write an apple pie recipe"</span>}],</span>
<span id="cb1-23">    response_format<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span>Recipe,</span>
<span id="cb1-24">)</span>
<span id="cb1-25"></span>
<span id="cb1-26">apple_pie_recipe <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> Recipe(<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span>json.loads(completion.choices[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>].message.content))</span></code></pre></div></div>
<p>It’s guaranteed that the output will be JSON that can be parsed into a <code>Recipe</code> object. The code looks very similar to the code you’d write with any of the <a href="../../blog/structured_output/">10 libraries</a> I compared in May.</p>
<p>Besides removing the need for a library, structured output works quite differently from function calling under the hood. With function calling the model is trained to follow an instruction given as a JSON schema and is likely but not guaranteed to follow it. At any token position it’s still free to output a token that doesn’t fit the schema. With structured output, the output of the model is constrained to fit the schema. This is the same approach as the <a href="https://github.com/outlines-dev/outlines">outlines</a> library uses for open source models.</p>
<section id="pros-and-cons" class="level2">
<h2 class="anchored" data-anchor-id="pros-and-cons">Pros and cons</h2>
<p>The structured output feature has several advantages over function calling:</p>
<ol type="1">
<li>✅ The definition of the output format doesn’t count as input tokens, making it significantly cheaper, especially for short input messages and complex output formats.</li>
<li>✅ The output is 100% guaranteed to follow the structure, in contrast to JSON mode and function calling which are just very likely to follow the structure.</li>
<li>✅ It doesn’t slow down the generation process, rather it speeds it up because tokens with no alternatives can be automatically placed rather than generated by the model.</li>
</ol>
<p>But also some downsides:</p>
<ol type="1">
<li>❌ OpenAI’s implementation only works with its own models.</li>
<li>❌ It only supports a subset of JSON schema. In particular, they don’t support <code>minLength</code> and <code>maxLength</code> constraints. See their <a href="https://platform.openai.com/docs/guides/structured-outputs">docs</a>. These are supported by outlines and instructor.</li>
<li>❌ The first API call with a schema has a higher latency than subsequent calls because the schema has to be compiled.</li>
</ol>
<p>I expect that the first two downsides will be addressed in the future. Thanks to the outlines library, the implementation of structured outputs is already available for open source models. Perhaps providers like Fireworks AI and Groq will adopt it with the same API specification as OpenAI. They’ve done this with function calling. In turn, platform-agnostic libraries like mirascope, marvin and instructor may adopt it as well.</p>
</section>
<section id="are-instructor-and-other-structured-output-libraries-obsolete" class="level2">
<h2 class="anchored" data-anchor-id="are-instructor-and-other-structured-output-libraries-obsolete">Are instructor and other structured output libraries obsolete?</h2>
<p>Right after the announcement, Jason Liu, author of instructor posted:</p>
<blockquote class="blockquote">
<p>They solved instructor.</p>
</blockquote>
<p>on <a href="https://x.com/jxnlco/status/1820880349288595629">X</a>. Later he added a longer <a href="https://x.com/jxnlco/status/1820976130096074940">post</a> with his thoughts.</p>
<p>Yes, the core value proposition of: “give me a Pydantic model and I’ll use function calling to guarantee the output fits the schema” is now covered for OpenAI models, but only for OpenAI models. If you’re using other models or want to stay flexible, structured output libraries are still useful. Each library also comes with additional features, as I’ve covered in my <a href="../../blog/structured_output/">comparison</a>. Examples are multiple provider support, error handling, caching, chaining and more.</p>
<p>So in short: no, they’re not obsolete, but their space is getting squeezed.</p>
</section>
<section id="conclusion" class="level2">
<h2 class="anchored" data-anchor-id="conclusion">Conclusion</h2>
<p>If you’re exclusively using OpenAI models and only need basic structured responses, I recommend using OpenAI’s structured outputs. It’s the most convenient, secure and cheapest method. If you prefer other LLM providers or want your code to be provider-agnostic, I recommend sticking with outlines (if self-hosting) or instructor (if using API providers).</p>


</section>

 ]]></description>
  <category>Machine Learning</category>
  <category>Python</category>
  <guid>https://simmering.dev/blog/openai_structured_output/</guid>
  <pubDate>Fri, 09 Aug 2024 22:00:00 GMT</pubDate>
  <media:content url="https://simmering.dev/blog/openai_structured_output/image.jpg" medium="image" type="image/jpeg"/>
</item>
<item>
  <title>Levels of Abstraction in the LLM Stack</title>
  <dc:creator>Paul Simmering</dc:creator>
  <link>https://simmering.dev/blog/abstractions/</link>
  <description><![CDATA[ 






<p>Training and serving LLMs requires a tall software stack. You can engage with this stack at different levels of abstraction, from low-level frameworks like CUDA to ready-to-go inference APIs like the OpenAI API. The aim of this article is to provide an overview of the abstraction levels and help you choose the right one for your project. Typical questions are:</p>
<ul>
<li>“<em>Should I use OpenAI’s GPT models or an open source model?</em>”</li>
<li>“<em>Should I use HuggingFace transformers or load the model into PyTorch directly?</em>”</li>
<li>“<em>Should I use AWS SageMaker or rent plain EC2 instances and manage everything myself?</em>”</li>
</ul>
<p>The choice depends on you and your project, but this overview and the decision criteria at the end may help you decide. I’ll discuss 3 levels of abstraction:</p>
<ol type="1">
<li>Open source tools and frameworks</li>
<li>Managed LLM services, e.g.&nbsp;AWS SageMaker</li>
<li>Cloud APIs, e.g.&nbsp;OpenAI</li>
</ol>
<section id="open-source-llm-stack" class="level2">
<h2 class="anchored" data-anchor-id="open-source-llm-stack">1. Open source LLM stack</h2>
<p>The open source LLM stack is the most flexible and customizable option and what is underlying the other two options. It consists of several layers. The list below has examples of tools at each level. I’ve not included optional MLOps tools like experiment tracking, monitoring, model store etc. which are not on the critical path for training and serving LLMs.</p>
<p>The term open source is not accurate for the lowest levels: hardware is proprietary and Nvidia holds a near-monopoly on GPUs for machine learning. Cloud providers are also proprietary, but there are many to choose from and they allow running open source software.</p>
<table class="caption-top table">
<colgroup>
<col style="width: 17%">
<col style="width: 33%">
<col style="width: 48%">
</colgroup>
<thead>
<tr class="header">
<th>Level</th>
<th>Description</th>
<th>Examples</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>1. Hardware</td>
<td>Physical graphics processors with high VRAM</td>
<td>Nvidia H100, AMD MI350, Intel Gaudi 3</td>
</tr>
<tr class="even">
<td>2. Cloud Providers</td>
<td>Platforms offering rentable GPU resources for LLM training and inference</td>
<td>AWS, Google Cloud, Azure, Modal, Lambda Labs</td>
</tr>
<tr class="odd">
<td>3. Acceleration Framework</td>
<td>Software interfaces for efficient use of GPUs for machine learning</td>
<td>CUDA, ROCm</td>
</tr>
<tr class="even">
<td>4. Distributed Computing</td>
<td>Libraries for distributing training workloads across multiple GPUs and machines</td>
<td>DeepSpeed, horovod, Ray, accelerate</td>
</tr>
<tr class="odd">
<td>5. Low-level Frameworks</td>
<td>Core libraries for building and training large language models</td>
<td>PyTorch, TensorFlow, JAX</td>
</tr>
<tr class="even">
<td>6. High-level Frameworks</td>
<td>Libraries that build on top of low-level frameworks to simplify common uses</td>
<td>Hugging Face Transformers, PyTorch Lightning, Axolotl</td>
</tr>
<tr class="odd">
<td>7. Inference Engine</td>
<td>Software for efficient LLM execution and serving</td>
<td>vLLM, llama.cpp, TorchServe, ONNX</td>
</tr>
<tr class="even">
<td>8. LLM Orchestration</td>
<td>Tools for prompting and chaining LLM calls, constraining and censoring output. These are also compatible with managed ML services and inference APIs</td>
<td>LangChain, llamaindex, litellm, instructor, outlines, guardrails</td>
</tr>
</tbody>
</table>
<p>To illustrate, let’s compare the type of code you’d write at the low and high levels of abstraction.</p>
<p>Creating a simple neural network in PyTorch:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python 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> torch</span>
<span id="cb1-2"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> torch.nn <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> nn</span>
<span id="cb1-3"></span>
<span id="cb1-4"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> MyModel(nn.Module):</span>
<span id="cb1-5">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>):</span>
<span id="cb1-6">        <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">super</span>().<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">__init__</span>()</span>
<span id="cb1-7">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.l1 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.Linear(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">768</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">512</span>)</span>
<span id="cb1-8">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.l2 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.Linear(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">512</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">256</span>)</span>
<span id="cb1-9">        <span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.l3 <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> nn.Linear(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">256</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)</span>
<span id="cb1-10"></span>
<span id="cb1-11">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> forward(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>, x):</span>
<span id="cb1-12">        x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.relu(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.l1(x))</span>
<span id="cb1-13">        x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> torch.relu(<span class="va" style="color: #111111;
background-color: null;
font-style: inherit;">self</span>.l2(x))</span>
<span id="cb1-14">        x <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;">self</span>.l3(x)</span>
<span id="cb1-15">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> x</span></code></pre></div></div>
<p>Loading a pre-trained transformer model from Hugging Face:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode python 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> transformers <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> BertTokenizer, BertForSequenceClassification</span>
<span id="cb2-2"></span>
<span id="cb2-3">tokenizer <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> BertTokenizer.from_pretrained(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'bert-base-uncased'</span>)</span>
<span id="cb2-4">model <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> BertForSequenceClassification.from_pretrained(<span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">'bert-base-uncased'</span>)</span></code></pre></div></div>
<p>PyTorch confronts you with the details of layers, their sizes, activation functions and more. Hugging Face abstracts them away.</p>
<section id="more-alternatives-at-higher-levels" class="level3">
<h3 class="anchored" data-anchor-id="more-alternatives-at-higher-levels">More alternatives at higher levels</h3>
<p>There tend to be more alternatives the higher you go in the stack. Recently, I’ve <a href="../../blog/structured_output/">compared</a> 10 different libraries for structured LLM outputs, all at the highest level of abstraction. In contrast, there is no widely used alternative to Nvidia GPUs and CUDA for the hardware and acceleration levels.</p>
</section>
<section id="too-much-abstraction" class="level3">
<h3 class="anchored" data-anchor-id="too-much-abstraction">Too much abstraction?</h3>
<p>There is such a thing as too many layers of abstractions. Hamel Husain put it well in his article: <a href="https://hamel.dev/blog/posts/prompt/">“Fuck You, Show Me The Prompt”</a>. Make sure you know which tokens are actually being sent to the LLM, and whether there’s more than one round-trip involved in getting a response. For education, too many layers can also hinder understanding. Andrej Karpathy is known for re-implementing the GPT architecture for education, for example <a href="https://github.com/karpathy/nanoGPT">nanoGPT</a>, which is GPT2 in ~600 lines of Python.</p>
</section>
<section id="fine-tune-dont-train-from-scratch" class="level3">
<h3 class="anchored" data-anchor-id="fine-tune-dont-train-from-scratch">Fine-tune, don’t train from scratch</h3>
<p>Training LLMs from scratch is almost never worth it for organizations whose main business isn’t providing foundation models for others. It requires far too much training data and GPU hours. As an example, even the smallest of Meta’s Llama 3.1 models was trained for 1.46M GPU hours (<a href="https://huggingface.co/blog/llama31#:~:text=The%20Llama%203.1%20models%20were%20trained%20on%20over,for%208B%2C%207.0M%20for%2070B%2C%2030.84M%20for%20405B%29.">source</a>). In contrast, fine-tuning a LoRA adapter for that model can be done in less than 1 GPU hour on an H100.</p>
<p>When working with lower-level libraries like PyTorch, it’s therefore necessary to start by copying the architecture of an existing LLM and loading its weights. Tweaks like a new output layer must be done carefully in order to preserve the usefulness of the learned weights. This is in contrast to less compute-intensive machine learning models, where training one’s own model from scratch is common. For these reasons, starting from a high-level framework like Hugging Face Transformers is more common for working with LLMs.</p>
</section>
</section>
<section id="managed-ml-services" class="level2">
<h2 class="anchored" data-anchor-id="managed-ml-services">2. Managed ML services</h2>
<p>AWS SageMaker, Google Cloud AI Platform, and Azure Machine Learning are examples of managed LLM services. They wrap the DIY stack in their cloud infrastructure, providing a unified interface for training, serving and monitoring models. Essentially, these services bundle the DIY stack into a single product, freeing you from having to manage the details. You still have a selection of open source models to fine-tuned with your own data.</p>
<p>This approach caters to enterprises with large-scale ML needs and tight security requirements. They’re typically already using the cloud provider for other services and want to keep everything in one place.</p>
</section>
<section id="inference-apis" class="level2">
<h2 class="anchored" data-anchor-id="inference-apis">3. Inference APIs</h2>
<p>Pre-trained LLMs are offered via API by OpenAI, Anthropic and many others including cloud providers with services like AWS Bedrock. These APIs are the highest level of abstraction, letting you directly connect your app to a powerful LLM without any setup or training. The downside is that you have the least control over the model and your data.</p>
<p>Some inference API providers, like Fireworks.ai also offer fine-tuning, getting close to the level of control you’d have with a managed service.</p>
</section>
<section id="choosing-the-right-level-of-abstraction" class="level2">
<h2 class="anchored" data-anchor-id="choosing-the-right-level-of-abstraction">Choosing the right level of abstraction</h2>
<p>Which level of abstraction do you want to work at?</p>
<section id="high-level-of-abstraction" class="level3">
<h3 class="anchored" data-anchor-id="high-level-of-abstraction">High level of abstraction</h3>
<p>Choose a higher level of abstraction if you:</p>
<ul>
<li>Are a beginner seeking quick first successes</li>
<li>Work at a startup focused on product-market fit</li>
<li>Are a researcher in a different field wishing to use LLMs</li>
<li>Want to integrate LLMs without deep ML expertise</li>
<li>Are already committed to a specific cloud ecosystem</li>
<li>Have no need for deep customization of models (you’d know if you did)</li>
</ul>
<p>The danger of choosing a too high level of abstraction is that you may hit a wall when you need to do something the tool doesn’t support. For example, OpenAI’s API doesn’t support reinforcement learning from human feedback (RLHF), only supervised fine-tuning. If you need RLHF, you’d have to switch to a lower level of abstraction.</p>
</section>
<section id="low-level-of-abstraction" class="level3">
<h3 class="anchored" data-anchor-id="low-level-of-abstraction">Low level of abstraction</h3>
<p>Opt for a lower level of abstraction if you:</p>
<ul>
<li>Are a researcher or engineer pushing LLM boundaries</li>
<li>Require fine-grained control over the model</li>
<li>Need on-premises or on-device deployment</li>
<li>Desire a deep understanding of the underlying technology</li>
<li>Prioritize code and model portability</li>
<li>Have engineers familiar with distributed systems and GPU programming</li>
</ul>
<p>The danger of choosing a too low level of abstraction is that you may spend too much time on infrastructure and not enough on the actual problem you’re trying to solve. For example, if you’re building a prototype for a meeting summarization chatbot, your time is better spent talking to project managers than optimizing your distributed training setup.</p>
</section>
<section id="cost-can-go-both-ways" class="level3">
<h3 class="anchored" data-anchor-id="cost-can-go-both-ways">Cost can go both ways</h3>
<p>High level tools can add a tax, but prices have been decreasing quickly. Managed services and API providers can leverage economies of scale and have highly optimized infrastructure. This can be difficult to achieve with a DIY stack. For example, a privately owned GPU deployed for inference may be underutilized outside of business hours, while a GPU at a cloud provider services other customers.</p>
</section>
<section id="keep-your-training-data-portable" class="level3">
<h3 class="anchored" data-anchor-id="keep-your-training-data-portable">Keep your training data portable</h3>
<p>The linear progression from low to high abstraction is a simplification. As the ecosystem matures, interoperability increases. For example, Hugging Face Transformers abstracts away the model architecture, but you can still access the PyTorch model and adjust it. Then that model can be deployed to AWS SageMaker. Not all combinations are possible though - for example a GPT model fine-tuned on OpenAI’s API can only run on that account. When it’s cheap to do so, use solutions that have as little lock-in as possible. Especially your training data should remain portable. In a time where research labs one-up each other weekly with better base models, being able to switch to a new model quickly is an advantage.</p>


</section>
</section>

 ]]></description>
  <category>Machine Learning</category>
  <guid>https://simmering.dev/blog/abstractions/</guid>
  <pubDate>Wed, 07 Aug 2024 22:00:00 GMT</pubDate>
  <media:content url="https://simmering.dev/blog/abstractions/image.jpg" medium="image" type="image/jpeg"/>
</item>
<item>
  <title>Less stress, more focus: How to handle waiting times in development</title>
  <dc:creator>Paul Simmering</dc:creator>
  <link>https://simmering.dev/blog/waiting/</link>
  <description><![CDATA[ 






<p>It’s unfortunate, but there are many waiting times in data science. Dealing with them well can make work more productive and enjoyable. Common waiting times include:</p>
<ul>
<li>model is training</li>
<li>data pipeline is running</li>
<li>report is rendering</li>
<li>Docker image is building</li>
<li>tests are running</li>
<li>someone else is reviewing your code</li>
<li>huge upload/download</li>
</ul>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://simmering.dev/blog/waiting/compiling.png" class="img-fluid figure-img"></p>
<figcaption>Waiting isn’t new in software development. <a href="https://xkcd.com/303/">XKCD 303</a></figcaption>
</figure>
</div>
<p>These waits range from seconds to days.</p>
<p>Ideally, there would not be any waiting times. Many can be eliminated or reduced Here are the top strategies, ranked by effectiveness in my experience:</p>
<table class="caption-top table">
<colgroup>
<col style="width: 29%">
<col style="width: 44%">
<col style="width: 26%">
</colgroup>
<thead>
<tr class="header">
<th>Strategy</th>
<th>Effectiveness</th>
<th>Downsides</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Caching results</td>
<td>Very high, cuts wait times to zero</td>
<td>Stale data</td>
</tr>
<tr class="even">
<td>Indexing databases</td>
<td>High, can massively speed up queries</td>
<td>Not always possible, slows down writes</td>
</tr>
<tr class="odd">
<td>Mocking dependencies</td>
<td>High, can speed up tests</td>
<td>Adds complexity</td>
</tr>
<tr class="even">
<td>Running with smaller inputs</td>
<td>High, can speed up debugging</td>
<td>Not the real result</td>
</tr>
<tr class="odd">
<td>Writing more efficient code</td>
<td>Medium, can speed up code</td>
<td>It’s hard</td>
</tr>
<tr class="even">
<td>Parallelizing code</td>
<td>Medium, can speed up code</td>
<td>Hard and adds complexity</td>
</tr>
<tr class="odd">
<td>Using faster hardware</td>
<td>Medium, can speed up code</td>
<td>Expensive, not always effective</td>
</tr>
</tbody>
</table>
<p>It’s very easy to lose 50% or more of one’s productivity to waiting times. The most common form is an inefficient debug cycle: change code, wait for build, run code, wait for results, repeat. Bonus points if the code is a CI/CD pipeline.</p>
<p>Eliminating a waiting time in a workflow is a huge win, especially when multiple people are using the same workflow.</p>
<p>However, many waiting times are unavoidable, especially when working with large language models. Given that these wait times occur regularly, it makes sense to put together a little plan for what to do with them.</p>
<p>I suggest spending the time in a way that guards focus and short-term memory of the work at hand. Else, you’re effectively doing this:</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://simmering.dev/blog/waiting/focus.jpeg" class="img-fluid figure-img"></p>
<figcaption>Programmer focus (from Monkeyuser.com)</figcaption>
</figure>
</div>
<p>Except the interruptions are self-inflicted.</p>
<p>The longer the wait is, the more it’s worth to switch context. Here’s a rough, opinionated guide based on my experience and research by <a href="http://www.chrisparnin.me/pdf/parnin-sqj11.pdf">Parnin and Rugaber (2010)</a>. The authors measure <em>edit lag</em>, the time between a developer returning to a task and making the first edit. In a study of 10,000 Java developers, they measured these edit lags:</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://simmering.dev/blog/waiting/edit_lag.png" class="img-fluid figure-img"></p>
<figcaption>Edit lag, Parning and Rugaber (2010)</figcaption>
</figure>
</div>
<p>For difficult tasks, the edit lag after an interruption can easily exceed the length of the interruption itself. Let’s get to the tactics to handle waiting times.</p>
<section id="seconds-to-minutes" class="level2">
<h2 class="anchored" data-anchor-id="seconds-to-minutes">Seconds to minutes</h2>
<p>These wait times can turn into interruptions, but they don’t have to. It’s tempting to fill smaller breaks with social media or news. However, this floods the short-term memory with new information, replacing the context of the work you were doing. Plus, scrolling is addictive and tends to exceed the actual wait time.</p>
<p>If possible, resist the urge to switch context. It’s ok to just wait for a moment. Look out the window, stretch, take a sip of water, breathe. If you must do something, I suggest doing a physical task like tidying up your desk or making a cup of tea, rather than a computer task.</p>
</section>
<section id="minutes-to-an-hour" class="level2">
<h2 class="anchored" data-anchor-id="minutes-to-an-hour">Minutes to an hour</h2>
<p>This is too long to just do nothing. Before switching context, try to leave an intentional cue for yourself to pick up where you left off, such as a TODO comment that lets you pick up the thread. Keep the IDE open with the file you were working on.</p>
<p>Ideally, pick a little task that is still relevant to your main task. Read through the code, write a comment, plan your next steps, write another test or refactor a small piece of code. Alternatively take a little break or knock out some easy tasks, such as answering emails.</p>
<p>Starting a new big task is not worth it, as it would take a ramp-up time to get back into the context of that task first. This is one of the main points behind Paul Graham’s <a href="http://www.paulgraham.com/makersschedule.html">Maker’s Schedule, Manager’s Schedule</a>.</p>
</section>
<section id="hours-to-days" class="level2">
<h2 class="anchored" data-anchor-id="hours-to-days">Hours to days</h2>
<p>Outside of training large models or running simulations, waiting times this long shouldn’t occur for technical reasons. If they do, it’s a sign that a process is not well-optimized. Fix the process, don’t suffer this wait time too often.</p>
<p>For processes involving humans this sort of wait time is normal though. There the best strategy is to have a plan for what to do during the wait time. When allocating tasks in a team I suggest that every developer has one or more backup tasks that can be worked on when waiting on something on the main task.</p>
</section>
<section id="conclusion" class="level2">
<h2 class="anchored" data-anchor-id="conclusion">Conclusion</h2>
<p>Waiting times are a fact of life in data science. They can be reduced, but not eliminated. It’s worth having a plan for how to spend the time to avoid losing focus and short-term memory. This can make work not just more productive but also more enjoyable, as the stress of re-finding context is reduced.</p>


</section>

 ]]></description>
  <category>Productivity</category>
  <guid>https://simmering.dev/blog/waiting/</guid>
  <pubDate>Sat, 27 Jul 2024 22:00:00 GMT</pubDate>
  <media:content url="https://simmering.dev/blog/waiting/clocks.jpg" medium="image" type="image/jpeg"/>
</item>
<item>
  <title>Text Tournament: Rank Marketing Copy with LLMs</title>
  <dc:creator>Paul Simmering</dc:creator>
  <link>https://simmering.dev/blog/text-tournament/</link>
  <description><![CDATA[ 






<p>The launch of the <a href="../../projects/aspectwise/index.html">review analysis project</a> has me working on various marketing tasks. Naturally, I built a tool to let LLMs help with the creative process. It’s called Text Tournament and the purpose is to compare ideas for company names, taglines, product descriptions and ad copy in a tournament-style competition. The project is available on <a href="https://github.com/psimm/text-tournament">GitHub</a> under the MIT license.</p>
<p>This is the companion blog post to the project which explains my thought process and technical details.</p>
<section id="the-tournament" class="level2">
<h2 class="anchored" data-anchor-id="the-tournament">The Tournament</h2>
<p>The user gives a set of competitors and a set of attributes. Each competitor is paired with every other competitor on each aspect. For example, if there are three name choices for a Spotify competitor, say ‘Streamio’, ‘MelodiX’ and ‘SoundWave’ and two attributes ‘memorability’ and ‘pronounceability’, the tournament would look like this:</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Competitor 1</th>
<th>Competitor 2</th>
<th>Attribute</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Streamio</td>
<td>MelodiX</td>
<td>memorability</td>
</tr>
<tr class="even">
<td>Streamio</td>
<td>SoundWave</td>
<td>memorability</td>
</tr>
<tr class="odd">
<td>MelodiX</td>
<td>SoundWave</td>
<td>memorability</td>
</tr>
<tr class="even">
<td>Streamio</td>
<td>MelodiX</td>
<td>pronounceability</td>
</tr>
<tr class="odd">
<td>Streamio</td>
<td>SoundWave</td>
<td>pronounceability</td>
</tr>
<tr class="even">
<td>MelodiX</td>
<td>SoundWave</td>
<td>pronounceability</td>
</tr>
</tbody>
</table>
<p>Each of these pairings is turned into a prompt for the LLM, like “Compare the company names Streamio and MelodiX. Which one is more memorable?”</p>
<p>Each pairing is run twice, once as A vs.&nbsp;B and once as B vs.&nbsp;A. The reason is that LLMs tend to have a bias towards picking the first option <span class="citation" data-cites="dominguezolmedo2024questioningsurveyresponseslarge">(Dominguez-Olmedo, Hardt, and Mendler-Dünner 2024)</span>.</p>
</section>
<section id="structured-output-reasoning" class="level2">
<h2 class="anchored" data-anchor-id="structured-output-reasoning">Structured output &amp; reasoning</h2>
<p>To make sure that the LLM’s answer is interpretable, I used <a href="https://github.com/jxnl/instructor">instructor</a>. Further, I asked the model to not just pick the winner but also to provide a reason. This is done with a simple Pydantic model:</p>
<div class="code-copy-outer-scaffold"><div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">class</span> Rating(BaseModel):</span>
<span id="cb1-2">    reason: <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">str</span></span>
<span id="cb1-3">    preferred: Literal[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>]</span></code></pre></div></div>
<p>By asking for the reason first, the tokens generated as the reason are influencing the token chosen for the “preferred” field. This makes use of the auto-regressive nature of the model.</p>
<p>The benefit of the reason is that it typically improves the model’s thinking and it also provides an inspectable record. For example, here are two outputs from the tournament above:</p>
<p>Streamio vs MelodiX on pronounceability:</p>
<blockquote class="blockquote">
<p>Streamio is straightforward to pronounce with a clear phonetic structure, while MelodiX may cause hesitation due to the unusual capital ‘X’ at the end.</p>
</blockquote>
<p>MelodiX vs SoundWave on memorability:</p>
<blockquote class="blockquote">
<p>The name ‘MelodiX’ is unique and contains a playful twist with the ‘X’ at the end, making it more distinctive and easier to remember. The name ‘SoundWave’ is more generic and can be easily confused with other similar terms in the tech and music industry.</p>
</blockquote>
</section>
<section id="ranking-competitors-with-the-bradley-terry-model" class="level2">
<h2 class="anchored" data-anchor-id="ranking-competitors-with-the-bradley-terry-model">Ranking competitors with the Bradley-Terry model</h2>
<p>The simplest approach is to count the number of wins for each competitor. However, this doesn’t take into account the strength of the competitors. A competitor that has only faced weak competitors might have a high win count but not be the best choice. I considered two ranking methods that account for this: the Elo <span class="citation" data-cites="elo1978rating">(Elo and Sloan 1978)</span> model and the Bradley-Terry <span class="citation" data-cites="bradley1952rank">(Bradley and Terry 1952)</span> model.</p>
<p>Elo is better known due to the popularity of ranking method in Chess. Many people are familiar with the concept of a player’s Elo rating and how it changes after a match.</p>
<p>The downside of Elo in this context is that ordering of the matches matters. Thce results of the LLM calls are coming in all at once. I’d have to artificially order the matches to use Elo. This is not ideal.</p>
<p>The Bradley-Terry model is a better fit for this situation. It’s a probabilistic model that estimates the strength of competitors based on the outcomes of matches.</p>
<p>The probability of competitor <img src="https://latex.codecogs.com/png.latex?i"> beating competitor <img src="https://latex.codecogs.com/png.latex?j"> is given by:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AP(i%20%5Ctext%7B%20beats%20%7D%20j)%20=%20%5Cfrac%7Br_i%7D%7Br_i%20+%20r_j%7D%0A"></p>
<p>where <img src="https://latex.codecogs.com/png.latex?r_i"> is the strength of competitor <img src="https://latex.codecogs.com/png.latex?i">. The model is fit by modifying the strengths <code>r</code> to maximize the likelihood of the observed outcomes.</p>
<p>The Bradley-Terry model is also the basis for Direct Preference Optimization <span class="citation" data-cites="rafailov2024direct">(Rafailov et al. 2024)</span>. So I’m asking an LLM that was likely trained with DPO to do be a ranking model itself. So meta. This also means that the outputs of the ranking could be used as inputs to the DPO model. For example, a larger model could be used to teach a smaller model how to rank the competitors.</p>
</section>
<section id="results" class="level2">
<h2 class="anchored" data-anchor-id="results">Results</h2>
<p>I ran a tournament with more name options and additional attributes to compare them on. Here is the overall result:</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://simmering.dev/blog/text-tournament/ranking.png" class="img-fluid figure-img"></p>
<figcaption>Tournament results</figcaption>
</figure>
</div>
<p>Does it match your preferences?</p>
<p>The full results with rankings on each attribute are available on this <a href="">Github page</a>.</p>
</section>
<section id="validity" class="level2">
<h2 class="anchored" data-anchor-id="validity">Validity</h2>
<p>The rankings produced by the tournament are not a replacement for tests with real users and human judgment. LLMs are known to be politically biased, may give random answers, and are heavily influenced by how a question is posed. The rankings are a tool to help with the creative process, not a definitive answer. If you decide to use it, I suggest starting with a low-stakes use case like the title of a blog post.</p>



</section>

<div id="quarto-appendix" class="default"><section class="quarto-appendix-contents" id="quarto-bibliography"><h2 class="anchored quarto-appendix-heading">References</h2><div id="refs" class="references csl-bib-body hanging-indent" data-entry-spacing="0">
<div id="ref-bradley1952rank" class="csl-entry">
Bradley, Ralph Allan, and Milton E Terry. 1952. <span>“Rank Analysis of Incomplete Block Designs: I. The Method of Paired Comparisons.”</span> <em>Biometrika</em> 39 (3/4): 324–45.
</div>
<div id="ref-dominguezolmedo2024questioningsurveyresponseslarge" class="csl-entry">
Dominguez-Olmedo, Ricardo, Moritz Hardt, and Celestine Mendler-Dünner. 2024. <span>“Questioning the Survey Responses of Large Language Models.”</span> <a href="https://arxiv.org/abs/2306.07951">https://arxiv.org/abs/2306.07951</a>.
</div>
<div id="ref-elo1978rating" class="csl-entry">
Elo, Arpad E, and Sam Sloan. 1978. <span>“The Rating of Chessplayers: Past and Present.”</span>
</div>
<div id="ref-rafailov2024direct" class="csl-entry">
Rafailov, Rafael, Archit Sharma, Eric Mitchell, Christopher D Manning, Stefano Ermon, and Chelsea Finn. 2024. <span>“Direct Preference Optimization: Your Language Model Is Secretly a Reward Model.”</span> <em>Advances in Neural Information Processing Systems</em> 36.
</div>
</div></section></div> ]]></description>
  <category>Machine Learning</category>
  <category>Python</category>
  <category>Marketing</category>
  <guid>https://simmering.dev/blog/text-tournament/</guid>
  <pubDate>Mon, 22 Jul 2024 22:00:00 GMT</pubDate>
  <media:content url="https://simmering.dev/blog/text-tournament/tournament.jpg" medium="image" type="image/jpeg"/>
</item>
<item>
  <title>Let Research Settle Before Consuming It</title>
  <dc:creator>Paul Simmering</dc:creator>
  <link>https://simmering.dev/blog/settled-knowledge/</link>
  <description><![CDATA[ 






<p>The pace of publishing in machine learning is extremely high. There were 242,290 AI publications in 2022. That’s 663 per day, or one every two minutes. Based on comments on X, Reddit and Discord, I can see that many people feel FOMO, overwhelmed or inadequate because they can’t keep up, even in subfields they’re supposed to be experts in.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://simmering.dev/blog/settled-knowledge/publications.png" class="img-fluid figure-img"></p>
<figcaption>Number of AI publications by year, Source: <a href="https://aiindex.stanford.edu/report/">Stanford University AI Index 2024</a></figcaption>
</figure>
</div>
<p>For those who can afford it, the antidote is to deliberately let research settle before consuming it. This means holding off on reading papers and waiting for the ideas to be integrated into textbooks, video courses and libraries, or at least wait to see which papers are getting cited more than others. This has advantages:</p>
<ul>
<li><strong>Higher quality learning materials</strong>: The initial paper is rarely the best explanation or fullest version of an idea. It necessarily doesn’t have as many real world examples as later explanations. It comes from the single perspective of an author with the intent to communicate to peers that are equally deep in the field. Later explanations are written by people with a teaching background and have been refined by feedback and real world experiences. They also have more accessible formats. Most people find it easier to learn from a video course or a textbook than from a collection of papers.</li>
<li><strong>Higher quality software implementations</strong>: Software behind research papers is often brittle and not suitable for production. Waiting for a library to implement the idea means you get a more robust and better documented implementation. It’s also more likely to be compatible with other tools you’re using and easier to install.</li>
<li><strong>Less likely to be wrong or irrelevant</strong>: The initial paper may have a mistake or a result that’s not replicable with other datasets. It may be a theoretical dead end or be quickly surpassed by other research. Waiting a while lets the community sort out what actually works.</li>
</ul>
<p>Time for learning is precious. Spending it on debugging software or deciphering a paper that is later proven wrong is a waste. By delaying consumption of research your learning is more efficient so you can learn more and more long-term valuable skills in the same time.</p>
<p>Of course, waiting is a luxury that those in research can’t afford because they’d be scooped and forever behind the curve. Let’s rank roles in the ecosystem by how close they have to be to the cutting edge:</p>
<ol type="1">
<li>Research scientist in university or industry lab</li>
<li>Research engineer developing platforms for researchers</li>
<li>Novel software developer creating cutting-edge products</li>
<li>Consultant advising on business integration</li>
<li>General developer at a company that uses ML but not at the cutting edge</li>
<li>Developer in slow-moving industry exploring ML adoption</li>
</ol>
<p>The lower you are on the list, the longer you can afford to wait before consuming research. The dropoff is steep. A researcher needs to be up to date with the latest papers within weeks, while a developer in a slow-moving industry can wait multiple years before an idea could become relevant in their work.</p>
<p>Staying at the bleeding edge carries a cost in learning efficiency and stress. If your role permits it, consider letting research settle more before consuming it.</p>



 ]]></description>
  <category>Productivity</category>
  <guid>https://simmering.dev/blog/settled-knowledge/</guid>
  <pubDate>Fri, 19 Jul 2024 22:00:00 GMT</pubDate>
  <media:content url="https://simmering.dev/blog/settled-knowledge/spiral.jpg" medium="image" type="image/jpeg"/>
</item>
<item>
  <title>The World is Large and Very Detailed</title>
  <dc:creator>Paul Simmering</dc:creator>
  <link>https://simmering.dev/blog/detailed-world/</link>
  <description><![CDATA[ 






<p><img src="https://simmering.dev/blog/detailed-world/world.jpg" class="img-fluid"></p>
<p>It’s easy to underestimate how vast and heterogeneous the world is. For entrepreneurs and developers this has two implications:</p>
<ol type="1">
<li>There are many niches to exploit.</li>
<li>It’s hard to scale.</li>
</ol>
<section id="detail-creates-opportunities" class="level2">
<h2 class="anchored" data-anchor-id="detail-creates-opportunities">Detail creates opportunities</h2>
<p>Some examples of detail: geography, languages, currencies, time zones, cultural norms, consumer preferences, age groups, currencies, laws, corporate structures, payment systems and so on. The detail is layered, like geographical features: countries contain states, which contain cities, which contain neighborhoods. Each combination of details creates a different environment for businesses to carve out their niche.</p>
<p>This puts a natural dampener on monopolies. The existence of an incumbent doesn’t mean that there is no room for a new player. This is most obvious in local businesses: just because there is a hairdresser in town doesn’t mean that there isn’t room for another in a different neighborhood. In digital businesses, this is less obvious but still true. Some examples:</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th>General incumbent</th>
<th>Competitor</th>
<th>Niche</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Zoom</td>
<td>Tuple</td>
<td>Remote pair programming</td>
</tr>
<tr class="even">
<td>Google</td>
<td>DuckDuckGo</td>
<td>Privacy-first search</td>
</tr>
<tr class="odd">
<td>AWS</td>
<td>Modal</td>
<td>Dev-friendly serverless platform</td>
</tr>
<tr class="even">
<td>Word</td>
<td>iA Writer</td>
<td>Distraction-free writing</td>
</tr>
<tr class="odd">
<td>Excel</td>
<td>AirTable</td>
<td>Linked records</td>
</tr>
<tr class="even">
<td>PowerPoint</td>
<td>Pitch</td>
<td>Pitch decks</td>
</tr>
<tr class="odd">
<td>VSCode</td>
<td>Cursor</td>
<td>AI-powered code completion</td>
</tr>
<tr class="even">
<td>Indeed</td>
<td>RemoteOK</td>
<td>Remote job board</td>
</tr>
<tr class="odd">
<td>Yelp</td>
<td>HappyCow</td>
<td>Vegan restaurant search</td>
</tr>
<tr class="even">
<td>Audible</td>
<td>Blinkist</td>
<td>Audio book summaries</td>
</tr>
</tbody>
</table>
<p>In each of these cases the job can be done using the general incumbent, but the competitors offer better experiences within their niches.</p>
<p>Even seemingly standardized technologies like SQL (officially <a href="https://blog.ansi.org/sql-standard-iso-iec-9075-2023-ansi-x3-135/">standardized</a> in 1986) have a huge number of <a href="https://db-engines.com/en/ranking">implementations</a>. Why? Because no single database covers every use case.</p>
<p>The level of detail of the world also provides a natural moat for employees against automation and offshoring.</p>
<ul>
<li>Self-driving cars have been in works for decades, but there are still millions of truck drivers. Why? Trucking is a detailed task that involves driving in all sorts of conditions, loading and unloading cargo and dealing with customers.</li>
<li>Remote work has been a thing for more than 10 years, but software companies still have expensive offices in the Bay Area populated by highly paid developers. Why? Because they have inertia, culture, social networks and talent density that only exist in that particular place.</li>
<li>Figma released its new <a href="https://www.figma.com/ai/">AI</a>. Does this mean that designers will be out of a job? No, because the AI doesn’t have the context and communication skills that a designer has.</li>
</ul>
</section>
<section id="detail-is-the-enemy-of-scaling" class="level2">
<h2 class="anchored" data-anchor-id="detail-is-the-enemy-of-scaling">Detail is the enemy of scaling</h2>
<p>In the same way that detail creates niches, it also inhibits scaling because each new detail requires a new solution. If the world is infinitely detailed, a given solution only applies to an infinitesimally small part of the world.</p>
<p>But the practical level of detail is not infinite: the further you zoom out the more systems and standards become visible. The laws of physics are the same everywhere. A microprocessor works the same way in Paris as in Tokyo. More than 5.4 billion people have a <a href="https://www.weforum.org/agenda/2023/04/charted-there-are-more-phones-than-people-in-the-world/">mobile phone</a>.</p>
<p>This unlocks huge economies of scale: technology that is applicable in many conditions can be invented once, mass-manufactured or copy-pasted millions of times, and used by millions of people. That is why technology companies are the <a href="https://companiesmarketcap.com">most valuable companies</a> in the world.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://simmering.dev/blog/detailed-world/marketcap.png" class="img-fluid figure-img"></p>
<figcaption>Largest companies by market cap. From companiesmarketcap.com, July 13 2024</figcaption>
</figure>
</div>
<p>Standardization can turn to natural monopolies when network effects come into play. The more people use a communication platform or a marketplace, the more valuable it becomes. This is why Facebook, Google and Amazon are so dominant. Standardization can also create monopolies to due scale, hence the dominance of TSMC in the semiconductor space.</p>
<p>But it’s also easy to overestimate how much can be standardized. Recently, a friend of mine who works in finance cautioned me about specializing in machine learning. He argued that the field is essentially solved because an LLM can answer any question. The economy needs one research company to develop the model and everyone else just uses their API. Applied LLM developers <a href="https://applied-llms.org/#enough-0-to-1-demos-its-time-for-1-to-n-products">disagree</a>. Building an LLM demo is easy, but real products must meet a much higher bar.</p>
<div class="grid">
<div class="g-col-6">
<p>It’s at this stage that the details of the world painfully intrude. Real world data is often incomplete, noisy, biased, inaccessible or in the wrong format. Predictions may be inaccurate or lack context of the business. The standard chat interface is not suitable for most actual use cases. This is why there is an army of data scientists and <a href="https://www.nytimes.com/2024/06/26/technology/ai-consultants.html">consultants</a> working as “technology sherpas” on the last-mile problems of LLMs. Realizing the economic benefits of LLMs may well require more consultants and software developers than actual ML researchers.</p>
</div>
<div class="g-col-6">
<p><img src="https://simmering.dev/blog/detailed-world/llm_needs.png" class="img-fluid"></p>
</div>
</div>
<p>But my friend isn’t all wrong. Hundreds of startups are building on top of OpenAI’s models. Smartly, OpenAI is leaving the last-mile problems to others and focusing on the core, scalable, and in a way less detailed, technology.</p>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://simmering.dev/blog/detailed-world/openai.png" class="img-fluid figure-img" style="width:75.0%"></p>
<figcaption>OpenAI as a platform for companies serving niches</figcaption>
</figure>
</div>
<p>This positioning as the default source of intelligence is lucrative, but requires enormous upfront investment and must be defended against competition. By now, 01 AI, Anthropic, Google, Nvidia and others have released models that have <a href="https://chat.lmsys.org/?leaderboard">surpassed</a> the original GPT-4 model. It models are only measured by their arena benchmark, it’s hard to differentiate. More detail-oriented niches offer more ways to differentiate and are generally less competitive.</p>
</section>
<section id="strategy" class="level2">
<h2 class="anchored" data-anchor-id="strategy">Strategy</h2>
<p>Scalability and detail can be seen in a matrix:</p>
<p><img src="https://simmering.dev/blog/detailed-world/matrix.png" class="img-fluid" style="width:75.0%"></p>
<ul>
<li><strong>New Platforms</strong>: A new technology or business model emerged and has catapulted a company to the top. Their offering is basic but scalable. Examples: OpenAI in 2023, Zoom in 2020, Google in 2000. Naming the year is required because this position is not stable, unless it’s a natural monopoly.</li>
<li><strong>Mature Platforms</strong>: Over time, the platform has added more features and detail to cater to more niches. Examples: AWS, Facebook, MS Office, Stripe. In software, this carries the risk of becoming bloated.</li>
<li><strong>Consulting &amp; bespoke software</strong>: Dealing with each client’s needs separately. Scale is achieved by hiring more people or working more hours. Examples: Accenture, Capgemini, Infosys, freelancers, local businesses.</li>
<li><strong>Failure</strong>: The company has an undifferentiated offering and hasn’t achieved scale. It’s unlikely to survive in the long term.</li>
</ul>
<p>There are plenty of niches to exploit and the existence of an incumbent can be taken as a signal that there is a market, rather than that the market is saturated. The hard part isn’t to find just any niche, but a niche large enough and amenable to scaling.</p>
<p>Questions for entrepreneurs and investors:</p>
<ul>
<li>Where do general incumbents fail to meet the needs of a niche?</li>
<li>What types of scale does the niche support?</li>
<li>Which details can I safely ignore or fix later?</li>
</ul>


</section>

 ]]></description>
  <category>Economics</category>
  <guid>https://simmering.dev/blog/detailed-world/</guid>
  <pubDate>Fri, 12 Jul 2024 22:00:00 GMT</pubDate>
  <media:content url="https://simmering.dev/blog/detailed-world/world.jpg" medium="image" type="image/jpeg"/>
</item>
</channel>
</rss>
