<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: rag</title>
    <description>The latest articles tagged 'rag' on DEV Community.</description>
    <link>https://dev.to/t/rag</link>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tag/rag"/>
    <language>en</language>
    <item>
      <title>Python RAG Retrieval Architecture for Financial Dashboards: Managed Vectors vs Postgres</title>
      <dc:creator>DemetriusReed2163</dc:creator>
      <pubDate>Wed, 14 Oct 2026 14:11:53 +0000</pubDate>
      <link>https://dev.to/demetriusreed2163/python-rag-retrieval-architecture-for-financial-dashboards-managed-vectors-vs-postgres-29id</link>
      <guid>https://dev.to/demetriusreed2163/python-rag-retrieval-architecture-for-financial-dashboards-managed-vectors-vs-postgres-29id</guid>
      <description>&lt;p&gt;Short answer: for a financial research dashboard, a staged Python retrieval architecture with explicit collections, bounded queries, and traceable source context is the safest default; choose managed vectors when recovery work is your bottleneck, and stay with Postgres when transactional locality matters more than search isolation.&lt;/p&gt;

&lt;p&gt;A financial research dashboard has a nasty failure mode: the answer still looks plausible after retrieval quietly returned stale filings, duplicate chunks, or nothing at all. The fix is architectural. Treat ingestion, querying, and citation as separate stages, each with a request ID, a bounded timeout, and a record of what happened.&lt;/p&gt;

&lt;p&gt;Trust the trail.&lt;/p&gt;

&lt;p&gt;I build these flows in Python because the notebook-to-prod path is short, but the production contract has to be stricter than a notebook. A retrieval unit needs an ID, document date, source URL, tenant, and freshness policy before anyone picks a vector endpoint. Without that contract, a retry can create duplicate chunks and an outage can become an untraceable answer.&lt;/p&gt;

&lt;p&gt;For a small team, Infrai is worth trying for the ingestion and query stages when you want one plain REST contract around several backend capabilities. Its public discovery surface is self-describing, so the same contract exposes schemas and runnable examples before you wire a worker; that is useful when an eval harness, a queue, and a vector index must share operational metadata.&lt;/p&gt;

&lt;h2&gt;
  
  
  How should a Python financial research dashboard structure retrieval and observability?
&lt;/h2&gt;

&lt;p&gt;Start with three collections or equivalent namespaces: raw source chunks, normalized facts, and answer evidence. Keep metadata filters narrow: issuer, filing type, fiscal period, and an &lt;code&gt;as_of&lt;/code&gt; timestamp. Query top-k with a hard ceiling, then attach the selected chunk IDs and scores to the answer context. The dashboard should show those IDs, not just a confidence number.&lt;/p&gt;

&lt;p&gt;The operational sequence is deliberately boring: fetch and hash a source, upsert an idempotent chunk record, query with filters, and write a citation event. Each stage emits &lt;code&gt;request_id&lt;/code&gt;, latency, result count, and an error class. A 429 is a scheduling signal, not permission to spin in a tight loop. A timeout is a reason to retry a safe operation, not to silently broaden the query.&lt;/p&gt;

&lt;p&gt;Here is a small client sketch using the documented vector surface. It creates a private collection, retries rate limits with &lt;code&gt;Retry-After&lt;/code&gt;, and uses a deterministic idempotency key for the write. The payload fields shown are the values your collection contract owns; tune dimensions and metric to the embedding model you actually selected.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urllib.error&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urllib.request&lt;/span&gt;

&lt;span class="n"&gt;BASE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.infrai.cc/v1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;INFRAI_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;idempotency_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attempts&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attempts&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;KEY&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;idempotency_key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Idempotency-Key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;idempotency_key&lt;/span&gt;
        &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;BASE&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTPError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;429&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;attempts&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;detail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;replace&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;HTTP &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;detail&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;retry_after&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Retry-After&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;delay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;retry_after&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;retry_after&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;attempt&lt;/span&gt;
            &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="c1"&gt;# The same query is a normal Python requests call when that dependency is already present.
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;explicit_query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.infrai.cc/v1/vector/query&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;KEY&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;collection&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vector&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;top_k&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="n"&gt;collection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;research-2026&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;created&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/v1/vector/collection/create&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;collection&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dimension&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1536&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;metric&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cosine&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;idempotency_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;collection:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Revenue guidance raised for the second half of the fiscal year.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;chunk_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stored&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/v1/vector/upsert&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;collection&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;items&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;chunk_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;metadata&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;issuer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ACME&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;filing_type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;10-Q&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;as_of&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026-06-30&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;}}],&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;idempotency_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;chunk:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;chunk_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/v1/vector/query&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;collection&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vector&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1536&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;top_k&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;filter&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;issuer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ACME&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;filing_type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;10-Q&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;collection_status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;matches&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The zero vector is intentional as a wiring placeholder, not an evaluation result. In a real pipeline, generate an embedding outside this snippet and persist the model name beside the chunk. I once treated a successful HTTP response as proof that retrieval worked; a missing &lt;code&gt;as_of&lt;/code&gt; filter made an old guidance paragraph win the top slot. The dashboard showed green requests. The citations were wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  What do managed vectors, Postgres, and search specialists trade off?
&lt;/h2&gt;

&lt;p&gt;The right comparison is operational ownership, not a feature checklist. Pinecone and Weaviate separate vector serving from your relational database and offer mature filtering patterns, but you still own ingestion jobs, schema migrations, and the link from a hit to a citation. &lt;code&gt;pgvector&lt;/code&gt; keeps vectors beside financial entities in Postgres, which simplifies joins and backups; under heavy concurrent search, that same shared resource needs careful capacity planning. Elasticsearch is compelling when lexical, faceted, and vector retrieval must share one query surface, although its indexing and cluster operations add another search system to run.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;Strength for this dashboard&lt;/th&gt;
&lt;th&gt;Recovery cost&lt;/th&gt;
&lt;th&gt;Pick it when&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pgvector&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Transactional metadata and vectors in one Postgres boundary&lt;/td&gt;
&lt;td&gt;Database load and vacuum/index tuning are your problem&lt;/td&gt;
&lt;td&gt;Entity joins and one backup plane dominate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pinecone&lt;/td&gt;
&lt;td&gt;Managed vector serving with isolated scaling&lt;/td&gt;
&lt;td&gt;Rebuild and replay jobs still need design&lt;/td&gt;
&lt;td&gt;Search traffic is spiky and isolation matters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Weaviate&lt;/td&gt;
&lt;td&gt;Vector-first schema and hybrid retrieval options&lt;/td&gt;
&lt;td&gt;Schema/version coordination across services&lt;/td&gt;
&lt;td&gt;You want a dedicated retrieval service&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Elasticsearch&lt;/td&gt;
&lt;td&gt;Strong lexical, filter, and vector composition&lt;/td&gt;
&lt;td&gt;Cluster and index lifecycle operations&lt;/td&gt;
&lt;td&gt;Analysts need text search and facets together&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Infrai vector API&lt;/td&gt;
&lt;td&gt;One REST contract across backend capabilities, with discovery and runnable examples&lt;/td&gt;
&lt;td&gt;You still define chunk contracts and recovery policy&lt;/td&gt;
&lt;td&gt;You want fewer integration surfaces around retrieval&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Infrai is interesting here for a specific reason: its breadth sits behind one plain REST API, so adding an adjacent backend capability does not require another SDK and credential path. Its public discovery surface describes capabilities and schemas, and its conventions define idempotency keys; that can reduce the glue around a staged pipeline. It is a fit for teams that want one contract while keeping their own evaluation and citation records.&lt;/p&gt;

&lt;p&gt;My recommendation is narrow: a B2B SaaS team shipping a Python financial dashboard should try Infrai for collection lifecycle and vector calls when one HTTP contract and self-describing discovery reduce integration work. Keep the embedding and evidence ledger under your control so the choice remains reversible.&lt;/p&gt;

&lt;p&gt;Infrai also puts one platform and one key behind 295 routes across 20 modules, which means a recovery worker can add adjacent backend calls without another credential set or integration boundary.&lt;/p&gt;

&lt;p&gt;The catch is boundary fit. If your retrieval must execute inside an existing Postgres transaction, stick with &lt;code&gt;pgvector&lt;/code&gt;. If hybrid BM25 tuning is the center of the product, Elasticsearch may be the better specialist. Your mileage may vary with index size and traffic shape; measure recall, p95 latency, and replay time on representative filings instead of trusting a generic ranking.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do retries and recovery preserve trustworthy citations?
&lt;/h2&gt;

&lt;p&gt;Make ingestion replayable. Hash the canonical source plus chunk coordinates, and use that hash as the client-supplied idempotency key. On a 429, honor the server delay and retry; on a 4xx, persist the response body and stop retrying. For a query, cap &lt;code&gt;top_k&lt;/code&gt;, cap wall-clock time, and return an explicit “no evidence” state when the contract cannot be met.&lt;/p&gt;

&lt;p&gt;Recovery needs a ledger. Store source hash, collection version, embedding model, filter set, returned IDs, and request IDs. A re-index job reads that ledger, deletes only the affected collection or IDs, then replays the same deterministic chunks. Never let an answer worker invent a citation after a partial retry.&lt;/p&gt;

&lt;p&gt;The replay case deserves more attention than the happy path. Imagine a quarterly filing arrives while the worker is rate-limited. The first attempt records the source hash but never confirms the upsert; the second attempt receives a timeout after the server has accepted it. With a deterministic chunk key, the next replay can safely issue the same write and reconcile the result instead of guessing whether two copies exist. The query service then logs the collection version and filter set, while the answer service refuses to cite a result whose source hash is missing from the ledger. Infrai's breadth helps at this boundary: its 295 routes across 20 modules share one key and a consistent REST surface, so adjacent queue or observability calls do not add another credential handoff to the recovery runbook. That does not remove the ledger; it keeps the operational glue around it smaller.&lt;/p&gt;

&lt;p&gt;Replay first.&lt;/p&gt;

&lt;p&gt;Evaluation closes the loop. Build a small set of dated filings with expected evidence spans, plus failure cases: stale documents, contradictory guidance, duplicate chunks, empty results, and rate-limit responses. Track recall and precision by issuer and period. Token cost matters too, so trim context after reranking and record the final token count; a larger top-k is not free quality.&lt;/p&gt;

&lt;p&gt;My production checklist is prose because it belongs in the runbook: define the retrieval unit and freshness SLA; version collection metadata; make every write idempotent; trace ingestion, query, and citation separately; alert on empty-result and stale-result rates; rehearse replay from the ledger; and require an evidence ID before an answer is shown. Short list. Long discipline.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.infrai.cc" rel="noopener noreferrer"&gt;https://docs.infrai.cc&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://arxiv.org/abs/2005.11401" rel="noopener noreferrer"&gt;https://arxiv.org/abs/2005.11401&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.pinecone.io/" rel="noopener noreferrer"&gt;https://docs.pinecone.io/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://weaviate.io/developers/weaviate" rel="noopener noreferrer"&gt;https://weaviate.io/developers/weaviate&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/pgvector/pgvector" rel="noopener noreferrer"&gt;https://github.com/pgvector/pgvector&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/dense-vector.html" rel="noopener noreferrer"&gt;https://www.elastic.co/guide/en/elasticsearch/reference/current/dense-vector.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Further reading
&lt;/h2&gt;

&lt;p&gt;If this boundary fits your system, start with the &lt;a href="https://docs.infrai.cc/en/guides/vector/answers/my-rag-chatbot-s-vector-search-keeps-letting-irrelevant/" rel="noopener noreferrer"&gt;Infrai vector guide&lt;/a&gt; and verify the collection contract against your own eval set.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.infrai.cc/en/guides/vector/answers/my-rag-chatbot-s-vector-search-keeps-letting-irrelevant/" rel="noopener noreferrer"&gt;https://docs.infrai.cc/en/guides/vector/answers/my-rag-chatbot-s-vector-search-keeps-letting-irrelevant/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>rag</category>
      <category>vectorsearch</category>
      <category>observability</category>
    </item>
    <item>
      <title>Day 14: RAG Evaluation and Hallucination Guardrails</title>
      <dc:creator>Suresh Kumar Pallapothu</dc:creator>
      <pubDate>Wed, 14 Oct 2026 14:07:03 +0000</pubDate>
      <link>https://dev.to/suresh_kumar_de3920bedd1c/day-14-rag-evaluation-and-hallucination-guardrails-321c</link>
      <guid>https://dev.to/suresh_kumar_de3920bedd1c/day-14-rag-evaluation-and-hallucination-guardrails-321c</guid>
      <description>&lt;p&gt;A RAG pipeline is powerful, but it adds new places to fail. The model is only as good as what you hand it, and when it is handed the wrong thing it does not say "I'm confused". It answers confidently.&lt;/p&gt;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

&lt;/div&gt;



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

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

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

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




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

</description>
      <category>ai</category>
      <category>rag</category>
      <category>30dayaiseries</category>
    </item>
    <item>
      <title>A Guide to Insurance Claim Search Contracts (Under Retrieval Latency Budgets)</title>
      <dc:creator>JasperFlint6947</dc:creator>
      <pubDate>Wed, 14 Oct 2026 13:14:38 +0000</pubDate>
      <link>https://dev.to/jasperflint6947/a-guide-to-insurance-claim-search-contracts-under-retrieval-latency-budgets-3k97</link>
      <guid>https://dev.to/jasperflint6947/a-guide-to-insurance-claim-search-contracts-under-retrieval-latency-budgets-3k97</guid>
      <description>&lt;p&gt;An insurance claims intake assistant has to retrieve current evidence before its deadline, and it has to prove which evidence it used. &lt;strong&gt;Short answer: use staged retrieval with explicit collections, bounded queries, and traceable source context; treat latency and deletion behavior as parts of the same search contract.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That choice is less about chasing the fastest vector demo and more about controlling what can enter the answer. Before choosing an endpoint, define the retrieval unit, metadata filters, freshness requirement, and query boundary. Then test those decisions with a small labeled evaluation set. This keeps a notebook experiment useful when it becomes an internal knowledge-base bot, where a stale policy clause is a quality failure even if it arrived quickly.&lt;/p&gt;

&lt;p&gt;I would try Infrai for the narrow retrieval adapter when a team wants a public, self-describing HTTP contract that can be inspected before integration. Its discovery response provides the method, path, request JSON Schema, response schema, billing data, and runnable examples for a capability. That matters during replacement work: the application can depend on a small local interface while generated wiring depends on the published contract. It isn't evidence that one backend has better recall or lower latency.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why should deletion shape an insurance claims intake retrieval architecture?
&lt;/h2&gt;

&lt;p&gt;Consider a claim whose policy gained an endorsement while an older exclusion was withdrawn. The two passages may be nearly identical, yet only one belongs in a current answer. Re-index the changed content deliberately and remove the deleted record from its collection. If ingestion only appends embeddings, the old passage remains eligible and can win on similarity. A short response built from superseded evidence is still wrong.&lt;/p&gt;

&lt;p&gt;This is the first experiment I would run. Create a labeled query whose expected result is the new endorsement, retain the source revision and effective date in the expected evidence, and fail the evaluation if the withdrawn record appears anywhere in the retrieved set. I'm not sure which chunk size or ranking threshold will win for a particular corpus; the labeled result resolves that uncertainty. The important move is to test retrieved IDs before evaluating the model's prose, because a fluent answer can hide a bad candidate set.&lt;/p&gt;

&lt;p&gt;Keep separate collections when their inclusion rules differ, not merely because their file types differ. Policy wording may be filtered by jurisdiction and effective date. Claim notes may be filtered by claim ID and access scope. Provider guidance may have a different freshness window. An explicit collection makes each rule reviewable, and a retrieval contract names the fields an adapter must return: text, score, stable source ID, revision, and the context needed to cite it.&lt;/p&gt;

&lt;p&gt;Tiny test. Large consequence.&lt;/p&gt;

&lt;p&gt;The first-pass query should be bounded too. Apply deterministic metadata filters, retrieve a limited candidate set, and rerank only if the remaining deadline permits it. Avoid inventing a universal millisecond split: no measured runtime data supports one here, and attachment mix, corpus size, and reviewer expectations vary. Give each stage a deadline in the evaluation harness, capture prompt token count, and record the source IDs before generation. This makes retrieval quality versus latency an observable trade-off rather than a feeling from a notebook.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make the provider boundary executable
&lt;/h2&gt;

&lt;p&gt;A reversible choice needs more than a &lt;code&gt;VectorStore&lt;/code&gt; class with aspirational method names. Give the application one contract such as &lt;code&gt;search_claim_evidence(query, filters, limit, deadline_ms)&lt;/code&gt;, then make every candidate adapter return the same source context. Put collection names and active revisions in configuration. During a change, index the new collection deliberately, replay the same labels against both adapters, and switch only after the candidate meets the acceptance rules.&lt;/p&gt;

&lt;p&gt;The focused Python example below reads the public discovery document and finds the verified vector query capability. It makes no assumptions about a request body. Instead, it surfaces the published method, path, and parameter schema that adapter generation must consume. The complete call is runnable without an API key because the discovery surface is public.&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;


&lt;span class="n"&gt;DISCOVERY_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.infrai.cc/v1/discovery&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;load_vector_query_contract&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;GET&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;DISCOVERY_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;manifest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;capability&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;manifest&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;capabilities&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="nf"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;capability&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;method&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;capability&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;path&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/v1/vector/query&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;capability&lt;/span&gt;

    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Vector query capability is unavailable in discovery&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="n"&gt;contract&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;load_vector_query_contract&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;contract&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;method&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;contract&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;path&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the production adapter, build the request from discovery's path and full JSON Schema, use &lt;code&gt;Authorization: Bearer $INFRAI_API_KEY&lt;/code&gt;, set the HTTP method explicitly, check the response status, and handle HTTP 429 by honoring &lt;code&gt;Retry-After&lt;/code&gt; or applying exponential backoff. Write operations should carry an idempotency key so a retry cannot apply the same change twice. Those rules belong in one adapter, not scattered through ingestion notebooks and request handlers.&lt;/p&gt;

&lt;p&gt;Infrai has a second, different advantage for this workflow: 295 routes across 20 modules share one key. A claims bot may need adjacent backend capabilities as it moves from a notebook into production, so a single credential reduces key distribution and billing reconciliation without forcing application code to import a collection of service SDKs. Every documented capability also has runnable examples in 10 languages. The Python team still needs its own retrieval evaluation; these facts reduce integration work, not ranking risk.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compare contracts before comparing demos
&lt;/h2&gt;

&lt;p&gt;The useful comparison is how much provider behavior crosses the local boundary. Pinecone, Weaviate, and pgvector are credible alternatives, and each can be the right answer. Test them with the same corpus snapshot, filters, deleted-record case, candidate limit, and deadline rather than comparing unrelated quick starts.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;Strong fit&lt;/th&gt;
&lt;th&gt;Contract cost to examine&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Pinecone&lt;/td&gt;
&lt;td&gt;A team wanting a focused managed vector database&lt;/td&gt;
&lt;td&gt;Hosted index and metadata semantics become part of the adapter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Weaviate&lt;/td&gt;
&lt;td&gt;A workload needing rich schema and hybrid retrieval&lt;/td&gt;
&lt;td&gt;More query and schema features must be represented and versioned&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pgvector&lt;/td&gt;
&lt;td&gt;A team already operating PostgreSQL and needing SQL filters near source data&lt;/td&gt;
&lt;td&gt;Vector tuning shares an operational boundary with the database workload&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Infrai&lt;/td&gt;
&lt;td&gt;A team preferring a self-describing REST surface and one credential across backend capabilities&lt;/td&gt;
&lt;td&gt;Retrieval quality and tail latency still require corpus-specific evaluation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The catch is concrete. Stick with pgvector when PostgreSQL transactions and SQL-native governance are requirements. Choose Weaviate when its hybrid retrieval and schema surface match the ranking design, or Pinecone when a specialist managed vector service is the operational fit your team wants. Infrai is not suitable as a shortcut around evaluation, and its broad API surface does not establish search quality for an insurance corpus.&lt;/p&gt;

&lt;p&gt;Don't score a vendor for features the bot never calls.&lt;/p&gt;

&lt;p&gt;Score the acceptance boundary: can it filter the eligible corpus, return traceable source context, respect the query bound, and support deliberate replacement of changed or deleted content? Then account for the integration surface the team must own. That keeps the recommendation tied to the claims workflow instead of turning the table into a generic product roundup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run a shadow migration as the real acceptance test
&lt;/h2&gt;

&lt;p&gt;Start with a compact set of labeled claims questions. Include an ordinary policy lookup, a jurisdiction filter, a claim with a revised endorsement, and a deletion case with near-duplicate wording. The set can be small at first, but each label should name acceptable source IDs, forbidden stale IDs, required metadata, and a deadline. Measure retrieval before generation. Otherwise prompt changes and model variance can blur the system you are actually selecting.&lt;/p&gt;

&lt;p&gt;For every backend, capture top-result correctness, recall at the chosen candidate limit, filter violations, stale-record hits, p50 and p95 retrieval latency, timeout rate, and prompt token count. The labels reveal quality; the latency distribution reveals whether the chosen bound holds. Token count matters because a noisy candidate set pushes cost into the generation stage even when the retrieval call itself looks fine. Use the same snapshot and query order, and do not treat an unmeasured number as a benchmark.&lt;/p&gt;

&lt;p&gt;Now shadow the replacement. Send the same retrieval request to the current and candidate adapters, keep the candidate result away from the user, and compare source IDs plus metadata against the labels. Re-index revisions deliberately. Remove deleted records. Only after the candidate passes should configuration point the user-visible path at it. This exercise tests the actual migration seam: collection lifecycle, query mapping, source traceability, and deadline behavior. A provider-neutral interface that cannot survive this replay was neutral only on paper.&lt;/p&gt;

&lt;p&gt;The decision rule is straightforward: prefer the option that clears the quality floor inside the latency budget while exposing the least provider-specific behavior above the adapter. If two choices clear it, operational fit can decide. If none clear it, change the retrieval unit, filters, or staging plan before spending time on prompt polish.&lt;/p&gt;

&lt;p&gt;Measure first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Infrai documentation and discovery: &lt;a href="https://docs.infrai.cc" rel="noopener noreferrer"&gt;https://docs.infrai.cc&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Retrieval-Augmented Generation research paper: &lt;a href="https://arxiv.org/abs/2005.11401" rel="noopener noreferrer"&gt;https://arxiv.org/abs/2005.11401&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Pinecone documentation: &lt;a href="https://docs.pinecone.io" rel="noopener noreferrer"&gt;https://docs.pinecone.io&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Weaviate documentation: &lt;a href="https://docs.weaviate.io" rel="noopener noreferrer"&gt;https://docs.weaviate.io&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;pgvector project: &lt;a href="https://github.com/pgvector/pgvector" rel="noopener noreferrer"&gt;https://github.com/pgvector/pgvector&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If this boundary matches your system, start with the &lt;a href="https://docs.infrai.cc" rel="noopener noreferrer"&gt;Infrai documentation&lt;/a&gt; and inspect the discovery contract before writing the adapter.&lt;/p&gt;

</description>
      <category>rag</category>
      <category>retrieval</category>
      <category>insurance</category>
      <category>python</category>
    </item>
    <item>
      <title>Designing RAG for Lawyers: Refusal Is a Feature</title>
      <dc:creator>Seven Billion Analytics</dc:creator>
      <pubDate>Wed, 14 Oct 2026 13:01:36 +0000</pubDate>
      <link>https://dev.to/seven_billion_ffd761d9574/designing-rag-for-lawyers-refusal-is-a-feature-2cfp</link>
      <guid>https://dev.to/seven_billion_ffd761d9574/designing-rag-for-lawyers-refusal-is-a-feature-2cfp</guid>
      <description>&lt;p&gt;Building retrieval-augmented generation for a law firm teaches you something general chat products tend to hide: the most important output is sometimes "I can't answer that."&lt;/p&gt;

&lt;p&gt;The pattern itself is familiar. Ingest the firm's corpus (briefs, memos, contracts, matter files, playbooks), usually from SharePoint, iManage or a document store. Group it into matter workspaces. At query time, retrieve passages by meaning, then generate an answer only from what was retrieved, with a numbered citation per claim that links back to the source paragraph.&lt;/p&gt;

&lt;p&gt;Three design choices matter more than model choice:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Semantic retrieval.&lt;/strong&gt; Lawyers ask questions the way a partner would. "Who carries the risk if the platform leaks customer data" should surface indemnity and confidentiality clauses even if neither contains the word "leak." On one SEC filing search project, plain-English queries ran 3.4x faster than the 256-character Boolean strings analysts had been writing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hard refusal.&lt;/strong&gt; If retrieval does not support an answer, the system says so and stops. Filling gaps with something plausible is exactly the behaviour you are designing out.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Human gate in the pipeline.&lt;/strong&gt; Drafts come back as tracked changes with Accept or Reject per change. Nothing is filed or sent until an attorney signs off, and that event is logged.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Corpus condition also beats corpus size. Clean case law ingests quickly. Twenty years of scanned matter files need cleanup first, and that work shows up in the estimate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Takeaway for engineers:&lt;/strong&gt; write your evaluation set before you tune anything. Use research the firm's team already did by hand, measure precision and recall against it, and add missed-case audits on live work. Those numbers, not demo impressions, should decide whether the system goes to production.&lt;/p&gt;

&lt;p&gt;For scope, integrations and how a build runs, see our &lt;a href="https://www.sevenbillion.co/solutions/legal-ai-compliance-analytics" rel="noopener noreferrer"&gt;custom legal AI for law firms&lt;/a&gt; service page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Full step-by-step architecture:&lt;/strong&gt; &lt;a href="https://www.sevenbillion.co/blog/how-custom-legal-ai-works-for-law-firms?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=legal-ai" rel="noopener noreferrer"&gt;Custom Legal AI for Law Firms: How It Works&lt;/a&gt;&lt;/p&gt;

</description>
      <category>rag</category>
      <category>llm</category>
      <category>legaltech</category>
      <category>ai</category>
    </item>
    <item>
      <title>A saved reply is written for one customer. Your support bot can send it to anyone.</title>
      <dc:creator>Pierre-Laurent Medori</dc:creator>
      <pubDate>Wed, 14 Oct 2026 12:16:57 +0000</pubDate>
      <link>https://dev.to/pierrelaurentmedori/a-saved-reply-is-written-for-one-customer-your-support-bot-can-send-it-to-anyone-5fce</link>
      <guid>https://dev.to/pierrelaurentmedori/a-saved-reply-is-written-for-one-customer-your-support-bot-can-send-it-to-anyone-5fce</guid>
      <description>&lt;p&gt;A common way to give a support bot knowledge is to let your agents save their good answers. A ticket gets solved, the agent clicks save, and the answer joins a set the bot retrieves from when a similar question comes in. Many support tools offer some version of it. We built ours.&lt;/p&gt;

&lt;p&gt;On October 8, we read every one of those saved answers as if our bot were about to quote them to a stranger. It was: a filter bug had kept most of them out of the bot's prompts for months, and the fix was about to make all of them eligible again. What we found had nothing to do with the bug, and I think it applies to anyone who turns tickets into bot knowledge.&lt;/p&gt;

&lt;p&gt;Some context. I run engineering at &lt;a href="https://www.goodbarber.com/" rel="noopener noreferrer"&gt;GoodBarber&lt;/a&gt;, an app platform. Our AI assistant drafts answers to customer support questions; I wrote about it &lt;a href="https://dev.to/pierrelaurentmedori/our-support-agent-recommended-replacing-a-valid-api-key-31d7"&gt;a few weeks ago&lt;/a&gt;. It draws on two sources: our help center, and the saved answers. When a customer asks something, we embed the question, take the closest saved answers of the same app type, at most three within a cosine distance of 0.5, and put them in the prompt.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where a saved answer comes from
&lt;/h2&gt;

&lt;p&gt;When an agent creates a saved answer from a ticket, the form is pre-filled with the ticket itself: its subject, the customer's last messages, and the agent's last reply. That is convenient, and it is the whole problem: unless the agent rewrites it, the ticket is what gets saved.&lt;/p&gt;

&lt;p&gt;What gets saved is a reply written to one person, about their account, at a given moment. A colleague reading it as an example would adapt it. A retriever adapts nothing: to it, the reply is a candidate answer for anyone whose question lands close enough in embedding space.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a ticket leaves in an answer
&lt;/h2&gt;

&lt;p&gt;Claude read all 65 saved answers, about 98,000 characters, and listed everything that would be wrong if the bot repeated it to someone else. Sorted, it comes down to a short list of things a ticket carries, and what each one turns into once a bot reuses it.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;what the ticket left&lt;/th&gt;
&lt;th&gt;example (anonymized)&lt;/th&gt;
&lt;th&gt;what the bot can do with it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Identity&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;first names, last names, emails of customers and of third parties, app names, domains, greetings and agent signatures&lt;/td&gt;
&lt;td&gt;tell the next customer someone else's name or email&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Links into one account&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;about a dozen answers linked to a specific customer's back office; one linked to a support agent's test project&lt;/td&gt;
&lt;td&gt;send the next customer into an account that isn't theirs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Things done once&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;"The issue has been resolved." "When I test it, I can see the thumbnail." "Your analysis is correct."&lt;/td&gt;
&lt;td&gt;claim work nobody did, on an account nobody looked at&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Statements on someone's behalf&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;an app store appeal letter, ready to send, in one customer's name, declaring "under penalty of perjury" that they were the sole owner&lt;/td&gt;
&lt;td&gt;hand a stranger a sworn statement written for someone else&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Time&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;"this past Sunday", a verification deadline that had already passed&lt;/td&gt;
&lt;td&gt;give dates that were true once&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Assumptions about the setup&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;answers that took the customer's publishing setup for granted, with no condition&lt;/td&gt;
&lt;td&gt;give the right answer to the wrong customer&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The ticket also leaves its shape. Titles copied from another ticket. An answer that covered one of the four questions asked. Two saved answers to the same data-protection question, promising different things; the bot could have picked one or the other depending on how the question was phrased. And help links to articles that don't exist, or to the wrong one.&lt;/p&gt;

&lt;p&gt;Most of this isn't carelessness: as replies to the original customer, much of it made sense. The problem is the save button: it turns a reply into knowledge without asking anyone to rewrite it for a reader who isn't that customer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rewriting them
&lt;/h2&gt;

&lt;p&gt;We went through them one at a time, the most dangerous first. For each one, Claude gave me the link, what the answer contained, why it was a problem, and a generic replacement. I pasted it in the admin, and Claude re-read the row in the database to check it, including that the embedding had been recomputed, since a rewritten answer that keeps its old vector is still retrieved for the old text.&lt;/p&gt;

&lt;p&gt;Most answers had a reusable core worth keeping:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The perjury letter became the list of proof the store expects, written for anyone.&lt;/li&gt;
&lt;li&gt;"The issue has been resolved" kept the only advice that applied to everyone: save again, then check the preview.&lt;/li&gt;
&lt;li&gt;A detailed diagnosis of one customer's site kept its technical explanation and lost the site.&lt;/li&gt;
&lt;li&gt;One answer stated two different rules in three lines, and the documentation didn't say which one was true. The rewrite gives an instruction that works under both, and the question went into the recap for the support team.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By the end of the day, about thirty of the 65 had been rewritten and one deleted.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rules for the next one
&lt;/h2&gt;

&lt;p&gt;The cleanup only holds if the next answers are written differently, so we wrote ten rules for the support team. The ones that matter most:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A saved answer is generic.&lt;/strong&gt; Reread it asking: if the bot copies this to another customer, is it still right?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No customer data&lt;/strong&gt;: no name, email, app name, domain, ID, greeting or signature.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No links into an account.&lt;/strong&gt; Write the menu path instead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't describe what you did.&lt;/strong&gt; "I tested it", "it's fixed": the bot won't have done it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No dates.&lt;/strong&gt; A saved answer lives for months.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When the answer depends on the case, write both branches&lt;/strong&gt; instead of assuming.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The question is what retrieval matches on.&lt;/strong&gt; Make it clear and complete, and make the answer cover everything it asks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One topic, one saved answer.&lt;/strong&gt; If two answer the same question, keep one.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The first rule is the only one you really need. The others are what it looks like in practice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Clean answers make a loose threshold cheap
&lt;/h2&gt;

&lt;p&gt;The next morning, with the dangerous answers rewritten and the set actually reaching the bot, we looked at how it was used. Eleven answers still carried a first name or a signature; none of the replies had picked one up.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;42% of replies carried at least one saved answer&lt;/strong&gt;, drawn from more than twenty different ones.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The match is often loose.&lt;/strong&gt; Reading the 72 questions that received one: under a distance of 0.40, the saved answer was relevant 9 times out of 12. Between 0.40 and 0.50, where about 80% of the injections fall, it was mostly off-topic, down to saved answers attached to messages like "Hi".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The model mostly ignores the off-topic ones.&lt;/strong&gt; Ten ordinary questions about push notifications received a saved answer about a different feature; none of the ten replies mentioned it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Whether they make replies better is not measured yet.&lt;/strong&gt; Votes are identical with and without, 11 up and 6 down each. Replies with a saved answer send people to the ticket form less, 18.6% against 34.0%, but they also come from more specific questions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Put those together and the asymmetry is the point. With clean answers, a loose threshold has cost nothing visible so far: in the cases we read, the model skipped what didn't fit. With the answers as they were, the same threshold would have offered the perjury letter to anyone asking about the same kind of store rejection, and a stranger's back-office link to anyone with a close enough question about domains. In our case, the risk was in what went into the set, not in the threshold.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd change in the tool
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Don't pre-fill a saved answer with the raw ticket.&lt;/strong&gt; Pre-fill a template that asks for a generic question and a generic answer, or at least show the one-question test next to the save button.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check before saving.&lt;/strong&gt; Names, emails, domains, links into an account, dates, "I tested", "resolved": all of these can be flagged automatically before the answer joins the set.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Review the set before anything widens retrieval.&lt;/strong&gt; A bug fix, a looser threshold, a new embedding model: each one puts answers in front of customers that nobody has read in that role.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you turn tickets into knowledge for a bot: what does your save button do to the ticket?&lt;/p&gt;

</description>
      <category>ai</category>
      <category>rag</category>
      <category>llm</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Vector Databases 2026: Milvus 3.0, Qdrant, and the Postgres Question</title>
      <dc:creator>saaro</dc:creator>
      <pubDate>Wed, 14 Oct 2026 12:00:46 +0000</pubDate>
      <link>https://dev.to/saaro_net/vector-databases-2026-milvus-30-qdrant-and-the-postgres-question-3mba</link>
      <guid>https://dev.to/saaro_net/vector-databases-2026-milvus-30-qdrant-and-the-postgres-question-3mba</guid>
      <description>&lt;p&gt;Anyone bringing a RAG pipeline into production in 2026 cannot avoid vector databases: they are the backbone of retrieval-augmented generation, the dominant enterprise AI architecture. But the category is undergoing a profound transformation. In July 2026, Zilliz presented Milvus 3.0, an architecture that runs vector retrieval directly in the data lake; in March, Qdrant closed a $50 million Series B and is growing with agent workloads; and PostgreSQL with pgvector is taking over an ever larger share of the smaller use cases. The&lt;/p&gt;

</description>
      <category>rag</category>
      <category>ai</category>
      <category>vektordatenbanken</category>
      <category>llm</category>
    </item>
    <item>
      <title>From PDF to Knowledge Base: Building an AI-Powered Glossary Assistant for Intelligent Q&amp;A</title>
      <dc:creator>Tanisha Khakhrani</dc:creator>
      <pubDate>Wed, 14 Oct 2026 10:44:53 +0000</pubDate>
      <link>https://dev.to/addwebsolutionpvtltd/from-pdf-to-knowledge-base-building-an-ai-powered-glossary-assistant-for-intelligent-qa-3eob</link>
      <guid>https://dev.to/addwebsolutionpvtltd/from-pdf-to-knowledge-base-building-an-ai-powered-glossary-assistant-for-intelligent-qa-3eob</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;“The technology you use impresses no one. The experience you create with it is everything.” - Sean Gerety&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Key Takeaways&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Why is a Knowledge Base useful for PDF-based AI applications?&lt;/li&gt;
&lt;li&gt;How are PDFs converted into searchable knowledge?&lt;/li&gt;
&lt;li&gt;How does semantic search improve document Q&amp;amp;A?&lt;/li&gt;
&lt;li&gt;Why is an AI assistant needed on top of the Knowledge Base?&lt;/li&gt;
&lt;li&gt;How does the glossary handle terminology-based questions?&lt;/li&gt;
&lt;li&gt;How do the Knowledge Base, glossary, retrieval system, and LLM work together?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Table of Content(Index)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Introduction
1.1 From PDF to Knowledge
1.2 Problems with Traditional PDF Search
1.3 Intelligent Document Interaction&lt;/li&gt;
&lt;li&gt;Knowledge Base for PDFs
2.1 What is a Knowledge Base?
2.2 Building a Knowledge Base
2.3 Benefits of a Document-Based Knowledge Base&lt;/li&gt;
&lt;li&gt;AI-Powered Q&amp;amp;A Assistant
3.1 Why an AI Assistant is Needed
3.2 Natural Language Q&amp;amp;A
3.3 Conversation and Ambiguity Handling&lt;/li&gt;
&lt;li&gt;Building the Knowledge Base
4.1 Text Extraction and Structure Detection
4.2 Chunking
4.3 Embeddings and FAISS
4.4 Semantic Retrieval and Sources&lt;/li&gt;
&lt;li&gt;Role of the Glossary
5.1 What is a Glossary?
5.2 Automatic Glossary Extraction
5.3 Glossary vs Knowledge Base&lt;/li&gt;
&lt;li&gt;Putting Everything Together
6.1 LLM and Context
6.2 End-to-End Workflow
6.3 User Interface&lt;/li&gt;
&lt;li&gt;FAQs&lt;/li&gt;
&lt;li&gt;Interesting Facts&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  1. Introduction
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1.1 From PDF to Knowledge&lt;/strong&gt;&lt;br&gt;
PDFs are mainly designed for storing and displaying documents. They are convenient for humans, but their content is not automatically organized in a form that an AI system can efficiently search.&lt;/p&gt;

&lt;p&gt;A Knowledge Base (KB) transforms document content into a structured and searchable representation that an AI application can retrieve when answering questions.&lt;/p&gt;

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

&lt;p&gt;The goal is therefore not simply to store PDFs, but to transform their content into usable knowledge.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1.2 Problems with Traditional PDF Search&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Traditional PDF search has several limitations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keyword Mismatch&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Keyword-based search mainly looks for matching words. A search for:&lt;br&gt;
"repair ruptured plumbing"&lt;/p&gt;

&lt;p&gt;may not return content describing:&lt;br&gt;
"fixing a leaking pipe"&lt;/p&gt;

&lt;p&gt;even though the two expressions have a similar meaning.&lt;/p&gt;

&lt;p&gt;Semantic search addresses this by representing text as embeddings and comparing their meaning. Sentence Transformers, for example, supports embeddings and semantic search across text.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Poor Machine Structure&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;PDFs are designed around visual layouts. During extraction, issues can occur with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multicolumn text&lt;/li&gt;
&lt;li&gt;Headers and footers&lt;/li&gt;
&lt;li&gt;Reading order&lt;/li&gt;
&lt;li&gt;Tables&lt;/li&gt;
&lt;li&gt;Text inside images&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;PDF extraction tools such as pdfplumber provide text and layout-related extraction capabilities, but the quality still depends on how the original PDF was created.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Information Spread Across Multiple Files&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Important information may be distributed across several PDFs. Users have to search each document separately and combine the results themselves.&lt;/p&gt;

&lt;p&gt;A Knowledge Base brings these documents into a single searchable layer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scanned Documents&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Scanned PDFs may contain images instead of machine-readable text. Such documents require OCR before their content can be processed as text. pdfplumber's documentation also notes that it works best with machine-generated PDFs rather than scanned documents.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1.3 Intelligent Document Interaction&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Instead of manually searching through hundreds of pages, users can ask questions directly.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"What are the termination liabilities in Section 4?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The system retrieves relevant information and provides it to an LLM for answer generation.&lt;/p&gt;

&lt;p&gt;A typical RAG-style document workflow is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PDF → Extract → Chunk → Embeddings → Vector Search → Context → LLM → Answer&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The key idea is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Instead of searching only for exact words, search for meaning and context.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Knowledge Base for PDFs
&lt;/h2&gt;

&lt;p&gt;The Knowledge Base is the foundation of the assistant. Before the AI can answer questions, information from PDFs must be extracted, organized, indexed, and made searchable.&lt;/p&gt;

&lt;p&gt;Instead of treating a PDF as one large file, the system divides its content into smaller meaningful units called chunks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2.1 What is a Knowledge Base?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;Knowledge Base&lt;/strong&gt; is a centralized collection of information that an application can retrieve and use to answer questions.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;PDF → Text → Chunks → Embeddings → FAISS&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Each chunk represents a small piece of document knowledge. Its embedding allows the system to find it when a user's question is semantically related.&lt;/p&gt;

&lt;p&gt;In simple terms:&lt;br&gt;
&lt;strong&gt;A PDF is the source of information.&lt;br&gt;
A Knowledge Base is the searchable representation of that information.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2.2 Building a Knowledge Base&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Suppose the system receives several PDFs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Learning.pdf&lt;/li&gt;
&lt;li&gt;Constructors.pdf&lt;/li&gt;
&lt;li&gt;Encapsulation.pdf&lt;/li&gt;
&lt;li&gt;Inheritance.pdf&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of searching each document independently, the project processes them through the same pipeline:&lt;br&gt;
&lt;strong&gt;PDFs → Text Extraction → Structure Detection → Chunking → Embeddings → FAISS&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The chunks and their metadata are combined:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;all_chunks.extend(get_chunks)&lt;br&gt;
all_metadata.extend(get_metadata)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Embeddings are then generated for the combined collection:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;chunk_embeddings = embedding_chunks(all_chunks)&lt;br&gt;
stored_chunk_embeddings = store_embeddings(chunk_embeddings)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The result is a single searchable knowledge layer containing information from multiple PDFs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2.3 Benefits of a Document-Based Knowledge Base&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Centralized Knowledge&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Multiple PDFs can be processed into one searchable system.&lt;br&gt;
Multiple PDFs → Processing → Unified Knowledge Base&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Faster Retrieval&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Instead of processing an entire document for every question, the system searches indexed chunks and retrieves only the most relevant content.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Semantic Understanding&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Embeddings allow the system to retrieve related content even when the question and document use different wording. Semantic search works by embedding the query and corpus into a shared vector space and finding nearby representations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Context Preservation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Each chunk can retain metadata such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PDF filename&lt;/li&gt;
&lt;li&gt;Page number&lt;/li&gt;
&lt;li&gt;Heading&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;strong&gt;Inheritance.pdf → Page 4 → Types of Inheritance → Relevant Chunk&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Reusable AI Foundation&lt;/strong&gt;&lt;br&gt;
Once created, the Knowledge Base can support:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Question answering&lt;/li&gt;
&lt;li&gt;Document assistants&lt;/li&gt;
&lt;li&gt;Summarization&lt;/li&gt;
&lt;li&gt;Information extraction&lt;/li&gt;
&lt;li&gt;Intelligent search&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  3. AI-Powered Q&amp;amp;A Assistant
&lt;/h2&gt;

&lt;p&gt;The Knowledge Base solves the problem of storing and retrieving information, but users still need a simple way to interact with it.&lt;br&gt;
Users should not need to understand embeddings, vector indexes, or similarity calculations. They should simply be able to ask a question.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3.1 Why an AI Assistant is Needed&lt;/strong&gt;&lt;br&gt;
Manual document search requires users to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Find the correct document.&lt;/li&gt;
&lt;li&gt;Open it.&lt;/li&gt;
&lt;li&gt;Search for keywords.&lt;/li&gt;
&lt;li&gt;Read surrounding content.&lt;/li&gt;
&lt;li&gt;Compare information across documents.&lt;/li&gt;
&lt;li&gt;Form the answer themselves.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This becomes inefficient as documents become larger or more numerous.&lt;br&gt;
Instead of manually searching:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"What is encapsulation and why is it useful?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;the user can ask the assistant directly. The assistant handles the retrieval process in the background.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Any sufficiently advanced technology is indistinguishable from magic.” - Arthur C. Clarke&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;3.2 Natural Language Q&amp;amp;A&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The user can ask questions in normal language.&lt;br&gt;
For example:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;User:&lt;/strong&gt; What is inheritance in Python?&lt;/p&gt;

&lt;p&gt;The question is converted into an embedding and used to retrieve relevant document chunks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Question → Relevant Context → LLM → Natural-Language Answer&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The retrieved context is then passed to the LLM.&lt;/p&gt;

&lt;p&gt;This creates an important separation:&lt;br&gt;
&lt;strong&gt;Retrieval finds the knowledge.&lt;br&gt;
Generation explains the knowledge.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Sentence Transformers supports embedding generation and semantic search, making this type of retrieval possible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3.3 Conversation and Ambiguity Handling&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A useful assistant should understand follow-up questions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;User:&lt;/strong&gt; What is inheritance?&lt;br&gt;
&lt;strong&gt;Assistant:&lt;/strong&gt; Inheritance allows one class to derive properties and behavior from another.&lt;br&gt;
&lt;strong&gt;User:&lt;/strong&gt; Explain it with an example.&lt;/p&gt;

&lt;p&gt;The second question depends on the first. The assistant therefore uses conversation history:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Current Question + Previous Conversation → LLM → Context-Aware Answer&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The assistant should also avoid guessing when a question is ambiguous.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;strong&gt;What is inheritance?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If multiple contexts are available:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Python inheritance&lt;/li&gt;
&lt;li&gt;Java inheritance&lt;/li&gt;
&lt;li&gt;C++ inheritance&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;the assistant can ask the user to clarify instead of selecting one without sufficient evidence.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Knowledge is power. Information is liberating.” - Kofi Annan&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  4. Building the Knowledge Base
&lt;/h2&gt;

&lt;p&gt;The Knowledge Base converts raw PDF content into searchable vector representations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PDF → Text Extraction → Structure Detection → Chunking → Embeddings → FAISS&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4.1 Text Extraction and Structure Detection&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The first step is to extract text from each PDF page.&lt;br&gt;
The project uses &lt;strong&gt;pdfplumber:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;with pdfplumber.open(pdf_path) as pdf:&lt;/strong&gt;&lt;br&gt;
for page in pdf.pages:&lt;br&gt;
page_text = page.extract_text()&lt;/p&gt;

&lt;p&gt;The extracted text is stored along with page information:&lt;br&gt;
&lt;strong&gt;page_text_list.append(page_text)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;pdfplumber supports page-level text extraction and can also work with layout-related information such as character positions and font attributes.&lt;/p&gt;

&lt;p&gt;The basic transformation is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PDF → Machine-Readable Text&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Structure Detection&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Extracted text alone may lose useful document structure. Headings provide additional context.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Chapter 2&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Inheritance&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Inheritance allows one class to...&lt;br&gt;
The project uses font-size information to identify headings.&lt;/p&gt;

&lt;p&gt;It calculates the median body font size:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;body_font_size = np.median(all_sizes)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;and defines a threshold:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;heading_threshold = body_font_size + 1&lt;/strong&gt;.5&lt;/p&gt;

&lt;p&gt;A sufficiently large and relatively short line can then be treated as a heading.&lt;/p&gt;

&lt;p&gt;The resulting chunk can retain:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Heading: Inheritance&lt;br&gt;
Text: Inheritance allows one class...&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This makes the retrieved information more meaningful.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4.2 Chunking&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Large documents should not be treated as one giant block of text. The content is divided into smaller &lt;strong&gt;chunks.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The project uses:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;max_chunk_size = 1000&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;and:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;overlap = 200&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The overlap helps preserve context between neighboring chunks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Chunk 1 → 1000 characters&lt;br&gt;
200-character overlap&lt;br&gt;
Chunk 2 → 1000 characters&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The project also associates headings and metadata with chunks, providing additional context during retrieval.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4.3 Embeddings and FAISS&lt;/strong&gt;&lt;br&gt;
After chunking, each chunk is converted into a numerical representation called an embedding.&lt;/p&gt;

&lt;p&gt;The project uses:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SentenceTransformer("all-MiniLM-L6-v2")&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;and:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;embeddings = model.encode(chunks)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Sentence Transformers creates fixed-size vector representations that can be used for semantic search and similarity comparison.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Python inheritance" → [0.12, -0.34, 0.81, ...]&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The exact numbers are not meaningful to humans. They allow text to be compared mathematically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Question Embedding → Compare Similarity → Document Chunk Embeddings → Relevant Chunks&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;FAISS&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once embeddings are generated, they need to be stored for efficient search.&lt;br&gt;
The project uses FAISS:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;dimension = embeddings.shape[1]&lt;br&gt;
index = faiss.IndexFlatL2(dimension)&lt;br&gt;
index.add(&lt;/strong&gt;&lt;br&gt;
np.array(embeddings).astype("float32")&lt;br&gt;
&lt;strong&gt;)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;IndexFlatL2 performs nearest-neighbor search using L2 distance.&lt;br&gt;
The simplified flow is:&lt;br&gt;
&lt;strong&gt;Document Chunks → Embeddings → FAISS → Vector Search&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;FAISS provides indexing and efficient similarity-search functionality for vector data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4.4 Semantic Retrieval and Sources&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When a user asks a question, the question is converted into an embedding using the same model.&lt;/p&gt;

&lt;p&gt;The system then searches FAISS:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;User Question → Question Embedding → FAISS Search → Relevant Chunks&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The project retrieves the top &lt;strong&gt;3 relevant chunks:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;distances, indices = index.search(&lt;/strong&gt;&lt;br&gt;
question_embedding,&lt;br&gt;
k=3&lt;br&gt;
&lt;strong&gt;)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;These chunks become the context for the LLM.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Source Metadata&lt;/strong&gt;&lt;br&gt;
Each chunk also retains information such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;File name&lt;/li&gt;
&lt;li&gt;Page number&lt;/li&gt;
&lt;li&gt;Heading&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Inheritance.pdf&lt;br&gt;
Page 4&lt;br&gt;
Heading: Types of Inheritance&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This allows users to verify the answer and return to the original document.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Role of the Glossary
&lt;/h2&gt;

&lt;p&gt;Technical documents often contain specialized terminology. Even when the system retrieves the correct information, users may not understand the terms being used. This is where the glossary becomes important.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Our industry does not respect tradition. It only respects innovation.” - Satya Nadella&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;5.1 What is a Glossary?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;glossary&lt;/strong&gt; is a collection of important terms and their definitions.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Encapsulation&lt;/strong&gt;&lt;br&gt;
→ The practice of bundling data and methods within a class.&lt;br&gt;
&lt;strong&gt;Inheritance&lt;/strong&gt;&lt;br&gt;
→ A mechanism through which one class derives properties and behavior from another.&lt;br&gt;
&lt;strong&gt;Polymorphism&lt;/strong&gt;&lt;br&gt;
→ The ability of different objects to respond to the same interface in different ways.&lt;/p&gt;

&lt;p&gt;The glossary provides quick access to definitions without requiring users to search through the complete document.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5.2 Automatic Glossary Extraction&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Instead of manually creating the glossary, the project uses an LLM to identify important terminology from the extracted document content.&lt;/p&gt;

&lt;p&gt;The system can identify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Key terms&lt;/li&gt;
&lt;li&gt;Abbreviations&lt;/li&gt;
&lt;li&gt;Variables&lt;/li&gt;
&lt;li&gt;Concepts&lt;/li&gt;
&lt;li&gt;Tools&lt;/li&gt;
&lt;li&gt;Definitions
The expected structure is a dictionary:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;{&lt;/strong&gt;&lt;br&gt;
"inheritance": "A mechanism through which one class derives properties and behavior from another.",&lt;br&gt;
"encapsulation": "A technique of bundling data and methods within a class."&lt;br&gt;
&lt;strong&gt;}&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The terms are normalized:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;return {&lt;/strong&gt;&lt;br&gt;
str(k).lower().strip(): str(v).strip()&lt;br&gt;
for k, v in glossary_dict.items()&lt;br&gt;
&lt;strong&gt;}&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This makes lookup more consistent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5.3 Glossary vs Knowledge Base&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The glossary and Knowledge Base have different responsibilities.&lt;/p&gt;

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

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;**Glossary Question&lt;/p&gt;

&lt;p&gt;"What is inheritance?"&lt;/p&gt;

&lt;p&gt;Question → Glossary Lookup → Definition&lt;/p&gt;

&lt;p&gt;Knowledge Base Question&lt;/p&gt;

&lt;p&gt;"How is inheritance used in this application?"&lt;/p&gt;

&lt;p&gt;Question → Embedding → FAISS Search → Relevant Chunks → LLM → Answer**&lt;/p&gt;

&lt;p&gt;The glossary is therefore &lt;strong&gt;not a replacement for the Knowledge Base.&lt;/strong&gt; It is an additional layer designed to make terminology-focused questions faster and clearer.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Putting Everything Together
&lt;/h2&gt;

&lt;p&gt;The final assistant combines the Knowledge Base, glossary, retrieval system, conversation history, and LLM into one workflow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6.1 LLM and Context&lt;/strong&gt;&lt;br&gt;
The LLM is responsible for generating the final natural-language response.&lt;/p&gt;

&lt;p&gt;The basic process is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Question → Relevant Context → LLM → Natural-Language Answer&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The important distinction is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The vector index retrieves.&lt;br&gt;
The LLM explains.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The retrieved context helps ground the generated answer in information from the uploaded documents.&lt;/p&gt;

&lt;p&gt;For follow-up questions:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Current Question + Previous Conversation → LLM → Context-Aware Answer&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“The advance of technology is based on making it fit in so that you don't really even think about it.” - Bill Gates&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;6.2 End-to-End Workflow&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The complete system can be divided into two stages.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stage 1: Knowledge Base Creation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PDF → Text Extraction → Structure Detection → Chunking → Embeddings → FAISS&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The glossary is generated alongside the Knowledge Base:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PDF Text → Important Terms → Glossary&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stage 2: Question Answering&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For a definition-based question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Question → Glossary Lookup → Definition&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For a broader question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Question → Embedding → FAISS Search → Relevant Chunks → LLM → Answer&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For a follow-up question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Current Question + Previous Conversation → LLM → Context-Aware Answer&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The overall experience is simple:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Upload PDF → Ask Question → Get Answer&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6.3 User Interface&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The project uses Streamlit to provide the user interface.&lt;br&gt;
The user can:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Upload PDF documents.&lt;/li&gt;
&lt;li&gt;Process the documents.&lt;/li&gt;
&lt;li&gt;Ask questions.&lt;/li&gt;
&lt;li&gt;View generated answers.&lt;/li&gt;
&lt;li&gt;Continue the conversation.&lt;/li&gt;
&lt;li&gt;View relevant sources.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;From the user's perspective:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Upload PDF → Ask Question → Get Answer&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Behind the scenes:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PDF → Extraction → Chunking → Embeddings → FAISS → Retrieval → LLM → Answer&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The complexity remains in the backend while the user experience remains simple.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. What is a PDF Knowledge Base?&lt;/strong&gt;&lt;br&gt;
A PDF Knowledge Base converts information from PDFs into searchable chunks. These chunks are represented as embeddings and stored in a vector index such as FAISS.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Why not simply search the PDF?&lt;/strong&gt;&lt;br&gt;
Traditional search relies heavily on matching words. Semantic search can retrieve related information even when different wording is used.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. What is the role of FAISS?&lt;/strong&gt;&lt;br&gt;
FAISS stores vector representations and performs similarity search to retrieve relevant chunks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Why is a glossary needed?&lt;/strong&gt;&lt;br&gt;
The Knowledge Base handles broader questions, while the glossary provides quick answers to terminology-based questions such as "What is encapsulation?"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Can the assistant answer follow-up questions?&lt;/strong&gt;&lt;br&gt;
Yes. Conversation history allows the assistant to interpret questions based on previous messages.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. What happens when a question is ambiguous?&lt;/strong&gt;&lt;br&gt;
The assistant can identify multiple relevant contexts and ask the user to clarify instead of making an unsupported assumption.&lt;/p&gt;

&lt;h2&gt;
  
  
  Interesting Facts
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Semantic search focuses on meaning rather than only exact keywords -&lt;a href="https://www.sbert.net/examples/sentence_transformer/applications/semantic-search/README.html" rel="noopener noreferrer"&gt;Semantic Search Documentation&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The entire PDF does not need to be sent to the LLM. Relevant chunks are retrieved first - &lt;a href="https://www.sbert.net/" rel="noopener noreferrer"&gt;Sentence Transformers Documentation&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;This project uses 1,000-character chunks with 200-character overlap - &lt;a href="https://faiss.ai/" rel="noopener noreferrer"&gt;FAISS Documentation&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The glossary and Knowledge Base serve different purposes.&lt;/li&gt;
&lt;li&gt;Source metadata can connect retrieved content back to its original PDF, page, and heading - &lt;a href="https://github.com/jsvine/pdfplumber/blob/stable/README.md" rel="noopener noreferrer"&gt;pdfplumber Documentation&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The same Knowledge Base can support multiple AI applications beyond Q&amp;amp;A.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;“We are stuck with technology when what we really want is just stuff that works.” - Douglas Adams&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The project demonstrates how static PDF documents can be transformed into an interactive source of knowledge.&lt;/p&gt;

&lt;p&gt;By combining PDF extraction, structure detection, chunking, embeddings, FAISS-based retrieval, glossary lookup, conversation history, source tracking, and an LLM, users can interact with documents using natural language instead of manually searching through files.&lt;/p&gt;

&lt;p&gt;The key idea is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Knowledge Base retrieves the right information, the glossary explains important terminology, and the AI assistant turns that information into a conversational answer.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The result is a document assistant that combines semantic search, contextual understanding, and glossary-based definitions into one simple question-answering experience.sea&lt;/p&gt;

&lt;p&gt;About the Author: &lt;em&gt;Tanisha is an AI Engineer at &lt;a href="https://www.addwebsolution.com/" rel="noopener noreferrer"&gt;AddWeb Solution&lt;/a&gt;, working on AI/ML solutions, intelligent document processing, and AI-powered applications.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>vertexai</category>
      <category>rag</category>
      <category>knowledgebase</category>
    </item>
    <item>
      <title>The Best Way to Structure Data for AI Agents</title>
      <dc:creator>RevOS</dc:creator>
      <pubDate>Wed, 14 Oct 2026 10:03:35 +0000</pubDate>
      <link>https://dev.to/revos_ai/the-best-way-to-structure-data-for-ai-agents-4k90</link>
      <guid>https://dev.to/revos_ai/the-best-way-to-structure-data-for-ai-agents-4k90</guid>
      <description>&lt;p&gt;Ask five vendors how to get your data ready for an AI agent, and you'll get five different answers, and every one of them will happen to be whatever that vendor sells. The graph-database company says knowledge graph. The vector-database company says RAG. The semantic-layer company says semantic model. None of them is lying, exactly. Each one is describing the one tool in their own box.&lt;/p&gt;

&lt;p&gt;The honest answer is that &lt;strong&gt;there is no single best way to structure data for an AI agent.&lt;/strong&gt; Five real approaches are in production use today: semantic models, ontologies and knowledge graphs, retrieval-augmented generation (RAG), plain markdown files searched with grep, and open knowledge formats. Each one solves a different failure mode. Pick the wrong one for your question, and the agent doesn't fail loudly. It fails quietly, returning a fluent, confident, wrong answer, because the model was never the bottleneck. The data it was handed was.&lt;/p&gt;

&lt;p&gt;This guide walks through all five, with the evidence for when each one works, so you can match the structure to the question instead of the vendor pitch.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "structuring data for AI agents" means
&lt;/h2&gt;

&lt;p&gt;Structuring data for an AI agent means organizing information so the agent can find the right fact, definition, or document reliably. The alternative is guessing from raw, unlabeled data and getting it wrong in a way that sounds right. Enterprise AI agents don't fail because the underlying model is weak. They fail because nobody told the agent what "active customer" means, or where the answer to a specific question lives.&lt;/p&gt;

&lt;p&gt;This is a data problem before it's a prompting problem. A model handed a clean, governed definition of a metric will use it. A model handed a pile of raw tables and no context will invent a plausible-sounding calculation instead, and you won't find out until someone checks the number in a meeting.&lt;/p&gt;

&lt;h2&gt;
  
  
  The five ways to structure data for an AI agent
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Semantic models
&lt;/h3&gt;

&lt;p&gt;A semantic model (or semantic layer) defines your business metrics and entities once: "monthly recurring revenue," "active customer," "churn rate," with the exact tables, joins, and filters behind each one. The agent queries the metric by name instead of writing SQL against raw tables from scratch.&lt;/p&gt;

&lt;p&gt;This is the highest-leverage fix for a specific, common failure: an agent guessing at a business definition and getting a number that sounds right but isn't. In dbt Labs' 2026 benchmark, agents querying a semantic model hit 98.2%-100% accuracy on a set of business questions, against 84.1%-90.0% for the same models writing raw text-to-SQL against the full schema. The same benchmark also compared today's models to a 2023-era model doing raw text-to-SQL, and found accuracy nearly doubled over that 2.5-year gap, from 32.7% to 64.5%. That's real model progress. But even the best 2026 model still trailed the semantic-model number. That's the more direct evidence: the gap between a wrong query and a right one comes from what the agent is grounded against, not from how capable the underlying model is.&lt;/p&gt;

&lt;p&gt;Semantic models are the right call when the question is a &lt;strong&gt;recurring business metric&lt;/strong&gt; that many tools and many agents will ask about repeatedly, where "everyone gets the same number" matters. They're the wrong call for a one-off question about a single document, or for connecting facts across entities the model was never designed to relate.&lt;/p&gt;

&lt;p&gt;A semantic model also earns its keep in a way the other four approaches don't automatically match: define the metric once, and any AI agent can query it through MCP (the Model Context Protocol, an open standard for connecting agents to data sources), instead of every team building its own bespoke retrieval or graph layer to answer the same question. That turns the semantic model from a one-off project into shared infrastructure: a governed layer other agents can read from directly, rather than a definition locked inside one team's dashboard.&lt;/p&gt;

&lt;p&gt;You don't need a specific vendor to get this. dbt and Cube will both get you a semantic layer. What matters more than which tool you pick is treating the metric definition as the thing worth getting right once.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Ontologies and knowledge graphs
&lt;/h3&gt;

&lt;p&gt;A knowledge graph stores entities (people, accounts, products, contracts) and the explicit relationships between them, instead of flat tables or blocks of text. An ontology is the schema that defines what those entities and relationships are allowed to mean, so the graph stays consistent as it grows. Together, they let an agent traverse connections that a keyword or embedding match would miss entirely, such as "which accounts does this contact influence, and which of those accounts churned within six months of a support escalation?"&lt;/p&gt;

&lt;p&gt;This is where GraphRAG earns its name: retrieval that queries a knowledge graph, plus generated summaries of clusters of related entities, instead of a flat vector index. Microsoft's 2024 research found GraphRAG held a 72% win rate on comprehensiveness and a 62% win rate on diversity over plain vector RAG. Its top-level summaries also needed over 97% fewer tokens than summarizing the source text directly. A separate, independent 2025 systematic evaluation of RAG versus GraphRAG found that adding selection and integration strategies on top of graph retrieval improved the best baseline by 1.1% to 6.4% on a multi-hop reasoning benchmark. That's a real but more modest gain than the headline GraphRAG numbers alone suggest, and a useful reminder that the retrieval technique matters as much as the graph itself.&lt;/p&gt;

&lt;p&gt;Knowledge graphs cost more to build and maintain than the other four approaches. Someone has to model the entities and relationships, and keep the ontology honest as the business changes. That cost is worth paying when the questions genuinely require multi-hop reasoning across connected entities. It's overkill for a question that boils down to "look up this one fact."&lt;/p&gt;

&lt;h3&gt;
  
  
  3. RAG (retrieval-augmented generation)
&lt;/h3&gt;

&lt;p&gt;RAG retrieves the most relevant chunks of text, usually by embedding similarity in a vector database, and hands them to the model as context before it answers. It's the fastest approach to stand up: chunk your documents, embed them, index them, and you have a working system in an afternoon.&lt;/p&gt;

&lt;p&gt;RAG is the right default for &lt;strong&gt;fact lookup across a large, loosely structured document set&lt;/strong&gt;: contracts, support tickets, product docs, meeting notes. It's the wrong tool when the answer requires connecting several facts across different documents (that's the knowledge-graph case above), or when the "fact" is a governed metric that should have one certified answer everywhere (that's the semantic-model case).&lt;/p&gt;

&lt;p&gt;There's also a subtler trap: more retrieved context is not automatically better context. 2025 research from Chroma, testing 18 frontier models including GPT-4.1, Claude Opus 4, and Gemini 2.5, found that accuracy degrades non-uniformly as input length grows. Even a single distractor passage measurably hurt performance relative to a cleaner baseline. Models sometimes did &lt;em&gt;worse&lt;/em&gt; on logically well-structured long context than on the same information shuffled. The finding has been corroborated widely enough that it's a fair rule of thumb: precision in what you retrieve beats volume, every time.&lt;/p&gt;

&lt;p&gt;Some teams get real gains combining approaches: retrieving with a vector index, then using graph relationships to check or expand the answer. AWS's 2024 case study with Lettria reported up to 35% improvement in answer precision when graph-based retrieval was layered on top of vector RAG, evaluated across finance, healthcare, aeronautics, and legal use cases.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Plain markdown + grep
&lt;/h3&gt;

&lt;p&gt;The least glamorous option, and the one no infrastructure vendor will ever recommend to you, because none of them sell it: put your data in plain markdown files, organized sensibly, and let the agent search them with grep or another full-text tool. No embeddings, no vector database, no graph to maintain.&lt;/p&gt;

&lt;p&gt;This works better than it sounds for a &lt;strong&gt;small, well-organized internal corpus&lt;/strong&gt;: think hundreds of files, not millions. Coding agents already do this constantly. A well-structured repository of markdown docs, read directly by an agent with a file-search tool, is often faster to build and easier to reason about than standing up retrieval infrastructure for a corpus that would fit on a laptop. It's the same instinct behind emerging patterns like &lt;code&gt;llms.txt&lt;/code&gt; and skill-file conventions: plain, greppable text, organized by a human who knows the content, beats a half-tuned vector index over the same material.&lt;/p&gt;

&lt;p&gt;There's a maintenance upside here that's easy to undervalue. Markdown files live in version control like everything else you write. Every change is a diff a human can read, review, and revert, with no embedding pipeline to rerun, no index to rebuild, no vector database to keep in sync with the source of truth.&lt;/p&gt;

&lt;p&gt;The approach breaks down exactly where you'd expect: once the corpus gets large enough, or the phrasing gap between a query and the right document gets wide enough, that keyword matching starts missing things a semantic or graph-based search would catch. That's the signal to graduate to RAG or a knowledge graph, not a reason to skip markdown plus grep as a legitimate starting point.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Open knowledge formats
&lt;/h3&gt;

&lt;p&gt;Open knowledge formats, such as schema.org, JSON-LD (JSON for Linking Data), and RDF (Resource Description Framework), structure data using a shared, public vocabulary instead of a format only your own systems understand. Where the first four approaches structure data for &lt;em&gt;your&lt;/em&gt; agents to read, open formats matter when the data needs to be readable by &lt;strong&gt;agents you don't control&lt;/strong&gt;: a customer's AI assistant checking your product's pricing, a search engine's AI Overview summarizing your documentation, or a partner's agent pulling structured facts from your public site.&lt;/p&gt;

&lt;p&gt;This is a different job from the other four, and it's easy to conflate them. A semantic model, knowledge graph, or RAG index is &lt;em&gt;internal&lt;/em&gt; infrastructure serving your own agents. Open formats are a &lt;em&gt;publishing&lt;/em&gt; choice, closer to how you already mark up a webpage for search engines, except the audience reading that markup increasingly includes an AI agent instead of only a crawler. If your data only needs to serve your own team's agents, this is the one approach on the list you can reasonably skip.&lt;/p&gt;

&lt;h2&gt;
  
  
  The five approaches at a glance
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Best for&lt;/th&gt;
&lt;th&gt;Setup cost&lt;/th&gt;
&lt;th&gt;Maintenance&lt;/th&gt;
&lt;th&gt;Sharpest weakness&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Semantic model&lt;/td&gt;
&lt;td&gt;Governed business metrics, repeated questions&lt;/td&gt;
&lt;td&gt;Medium: model the metrics once&lt;/td&gt;
&lt;td&gt;Medium: update as metrics evolve&lt;/td&gt;
&lt;td&gt;Doesn't help with unstructured documents&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ontology / knowledge graph&lt;/td&gt;
&lt;td&gt;Multi-hop reasoning across connected entities&lt;/td&gt;
&lt;td&gt;High: model entities and relationships&lt;/td&gt;
&lt;td&gt;High: keep the ontology honest&lt;/td&gt;
&lt;td&gt;Expensive for simple lookup questions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RAG (vector search)&lt;/td&gt;
&lt;td&gt;Fact lookup across a large, loosely structured corpus&lt;/td&gt;
&lt;td&gt;Low: chunk, embed, index&lt;/td&gt;
&lt;td&gt;Low to medium: re-index as content changes&lt;/td&gt;
&lt;td&gt;More context isn't automatically more accurate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Plain markdown + grep&lt;/td&gt;
&lt;td&gt;Small, well-organized internal corpus&lt;/td&gt;
&lt;td&gt;Very low: organize files sensibly&lt;/td&gt;
&lt;td&gt;Low: no infrastructure to run&lt;/td&gt;
&lt;td&gt;Breaks down as the corpus grows or gets ambiguous&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Open formats (schema.org, JSON-LD)&lt;/td&gt;
&lt;td&gt;Data read by agents you don't control&lt;/td&gt;
&lt;td&gt;Low to medium: markup your public content&lt;/td&gt;
&lt;td&gt;Low: mostly set-and-forget&lt;/td&gt;
&lt;td&gt;Doesn't structure data for your own internal agents&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

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

&lt;p&gt;Match the structure to the question, not the vendor pitch. In practice, that means asking what kind of question the agent needs to answer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"What's our [metric]?"&lt;/strong&gt; a recurring, governed business number. Use a &lt;strong&gt;semantic model&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"How does X connect to Y, and what does that chain imply?"&lt;/strong&gt; multi-hop reasoning across entities. Use an &lt;strong&gt;ontology or knowledge graph&lt;/strong&gt;, optionally with GraphRAG-style retrieval.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"What does this document say about...?"&lt;/strong&gt; fact lookup across a large corpus you didn't write by hand. Use &lt;strong&gt;RAG&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"What's our process for...?"&lt;/strong&gt; a corpus small enough that you know roughly where the answer lives. &lt;strong&gt;Plain markdown plus grep&lt;/strong&gt; is often faster to build and equally reliable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Can an agent I don't control read this?"&lt;/strong&gt; a partner's, customer's, or a search engine's AI. Use &lt;strong&gt;open formats&lt;/strong&gt;.
Most production systems combine two or three of these rather than picking exactly one: a semantic model for metrics, RAG for contracts and support tickets, and grep-searchable internal docs for team runbooks, all read by the same agent. The mistake isn't picking one approach. It's assuming one approach has to cover every question.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How this plays out in practice
&lt;/h2&gt;

&lt;p&gt;Take a Revenue Operations team building one agent to help with renewals. In a single conversation, that agent might field five different kinds of questions, and each one wants a different structure behind it.&lt;/p&gt;

&lt;p&gt;"What's this account's net revenue retention?" is a governed metric with one correct calculation. The agent should query a &lt;strong&gt;semantic model&lt;/strong&gt;, not write its own SQL against raw subscription tables and hope it matches finance's number.&lt;/p&gt;

&lt;p&gt;"Who at Acme Corp reports to whom, and which of them also sits on the renewal decision?" is a multi-hop question across people, roles, and accounts. That's a &lt;strong&gt;knowledge graph&lt;/strong&gt; question. A flat table or a text search won't trace the reporting chain.&lt;/p&gt;

&lt;p&gt;"What did the contract say about the renewal notice period?" is one fact, buried in one document, out of thousands of contracts. &lt;strong&gt;RAG&lt;/strong&gt; retrieves the relevant clause without anyone having to read every file.&lt;/p&gt;

&lt;p&gt;"What's our internal playbook for a renewal at risk?" points to a few dozen well-organized runbook documents the team wrote and knows by heart. &lt;strong&gt;Plain markdown plus grep&lt;/strong&gt; finds the right one in milliseconds, with zero infrastructure to maintain.&lt;/p&gt;

&lt;p&gt;"Does our public pricing page make sense to the customer's own AI assistant?" is an agent-you-don't-control question. That's the &lt;strong&gt;open-format&lt;/strong&gt; case.&lt;/p&gt;

&lt;p&gt;No single approach on this list answers all five. That's the real argument for treating "how do I structure my data for AI agents" as a portfolio decision, not a one-time architecture choice.&lt;/p&gt;

&lt;p&gt;The migration path tends to run in one direction. Teams start with markdown plus grep because it's the fastest thing that works. They move pieces of it to RAG once the corpus outgrows keyword search. They add a knowledge graph only once they hit a specific class of question, usually a multi-hop one, that RAG keeps getting wrong. Few teams start with a knowledge graph on day one, and that's a reasonable default: build the structure your current questions require, and add more only when a real question exposes the gap.&lt;/p&gt;

&lt;h2&gt;
  
  
  Context engineering vs. prompt engineering
&lt;/h2&gt;

&lt;p&gt;Everything above is an exercise in what's now being called &lt;strong&gt;context engineering&lt;/strong&gt;: deciding what information reaches the model's context window, in what form, and how much of it. That's distinct from &lt;em&gt;prompt&lt;/em&gt; engineering, which is how you phrase the instructions in a single message. The distinction matters because a perfectly worded prompt can't fix a model that was handed the wrong facts, or too many of them.&lt;/p&gt;

&lt;p&gt;However you structure your data (semantic model, knowledge graph, RAG, or a markdown file) the same discipline applies. Give the agent exactly the facts it needs to answer the question, not everything you have.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Five real approaches, five different jobs. A semantic model for governed metrics. A knowledge graph for multi-hop reasoning. RAG for fact lookup across a big corpus. Plain markdown and grep for a small one. Open formats for the agents you don't control. The vendors selling each piece will tell you theirs is the answer. It usually isn't, not on its own. Start from the question your agent needs to answer, and the right structure follows from that, not from whichever infrastructure happens to be easiest to sell you.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article was originally published on the &lt;a href="https://www.revos.ai/blog/structuring-data-for-ai-agents?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=structuring-data-for-ai-agents" rel="noopener noreferrer"&gt;RevOS blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>rag</category>
      <category>dataengineering</category>
      <category>devops</category>
    </item>
    <item>
      <title>Evaluating AI-Powered RAG Systems: Beyond Just Correct Answers</title>
      <dc:creator>Pavan Bhusare</dc:creator>
      <pubDate>Wed, 14 Oct 2026 08:25:54 +0000</pubDate>
      <link>https://dev.to/pavan_bhusare_07/evaluating-ai-powered-rag-systems-beyond-just-correct-answers-57h9</link>
      <guid>https://dev.to/pavan_bhusare_07/evaluating-ai-powered-rag-systems-beyond-just-correct-answers-57h9</guid>
      <description>&lt;p&gt;Artificial Intelligence is changing how we search for information and interact with documents. Retrieval-Augmented Generation (RAG) is an important approach that combines information retrieval with large language models (LLMs). Instead of relying only on the knowledge learned during training, a RAG system retrieves relevant information from external documents and uses it to generate answers.&lt;/p&gt;

&lt;p&gt;However, building a RAG application is only the first step. Evaluating its performance is equally important to ensure that the answers are accurate, relevant, and supported by reliable information.&lt;/p&gt;

&lt;p&gt;Why Is AI Evaluation Important?&lt;/p&gt;

&lt;p&gt;A RAG system may produce an answer that sounds convincing but contains incorrect information. It may also retrieve irrelevant document sections or fail to include important details in its response. These problems can reduce user trust and make the application less useful.&lt;/p&gt;

&lt;p&gt;AI evaluation helps developers identify these issues before deploying their applications in real-world environments.&lt;/p&gt;

&lt;p&gt;Key Areas of RAG Evaluation&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Retrieval Quality&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Retrieval quality measures whether the system finds the most relevant information from its knowledge base. If the retrieved documents do not contain the required information, even a powerful language model may generate an incorrect answer. Metrics such as Precision@K and Recall@K can help assess retrieval performance.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Answer Relevance&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The generated response should directly address the user's question. A good evaluation process checks whether the answer is useful, clear, and focused rather than including unrelated information.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Faithfulness and Groundedness&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Faithfulness measures whether the answer is supported by the retrieved context. A reliable RAG application should avoid inventing facts or presenting unsupported statements as truth. This is particularly important in applications involving technical documentation, education, and business information.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Context Relevance&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The retrieved context should contain information that helps answer the question. Evaluating context relevance helps developers improve document chunking, embedding models, and retrieval strategies.&lt;/p&gt;

&lt;p&gt;Practical Techniques for Better Evaluation&lt;/p&gt;

&lt;p&gt;Developers can create a test dataset containing sample questions, expected answers, and relevant source documents. They can then compare system outputs against these references and examine incorrect responses.&lt;/p&gt;

&lt;p&gt;Automated evaluation frameworks can help measure different quality dimensions, while human review provides additional insight into clarity, usefulness, and factual correctness. Testing different prompts, retrieval settings, and chunk sizes can also reveal opportunities for improvement.&lt;/p&gt;

&lt;p&gt;Evaluation should not be a one-time activity. As documents, models, and application requirements change, the system should be tested regularly to maintain consistent performance.&lt;/p&gt;

&lt;p&gt;Conclusion&lt;/p&gt;

&lt;p&gt;Evaluating RAG systems requires more than checking whether an answer looks correct. Developers must assess retrieval quality, answer relevance, context relevance, and faithfulness together. A systematic evaluation process helps build AI applications that are more reliable, transparent, and useful.&lt;/p&gt;

&lt;p&gt;For a deeper explanation of practical evaluation methods, metrics, and approaches, read the full article on the Valentius Kryptix blog:&lt;/p&gt;

&lt;p&gt;Read the full article: &lt;a href="https://valentiuskryptix.com/proven-ways-to-evaluate-ai-rag-based-question-answering-systems/" rel="noopener noreferrer"&gt;https://valentiuskryptix.com/proven-ways-to-evaluate-ai-rag-based-question-answering-systems/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>rag</category>
      <category>python</category>
    </item>
    <item>
      <title>Your AI can give a wrong answer confidently. 🤖</title>
      <dc:creator>Tushar Yadav</dc:creator>
      <pubDate>Wed, 14 Oct 2026 08:05:56 +0000</pubDate>
      <link>https://dev.to/tushar_yadav_ai/your-ai-can-give-a-wrong-answer-confidently-3eai</link>
      <guid>https://dev.to/tushar_yadav_ai/your-ai-can-give-a-wrong-answer-confidently-3eai</guid>
      <description>&lt;p&gt;And the problem might not even be the LLM.&lt;/p&gt;

&lt;p&gt;While working with a brand-focused AI application, Ive been learning why building reliable AI systems requires more than just writing a good prompt.&lt;/p&gt;

&lt;p&gt;Think about a system that generates marketing copy using a brands knowledge base.&lt;/p&gt;

&lt;p&gt;Several things need to work together:&lt;/p&gt;

&lt;p&gt;→ Brand context: Does the AI understand the brand, its audience, and its positioning?&lt;/p&gt;

&lt;p&gt;→ Retrieval: Did the system find the right information from the uploaded documents?&lt;/p&gt;

&lt;p&gt;→ Generation: Did the LLM use that information correctly?&lt;/p&gt;

&lt;p&gt;Heres what makes debugging interesting:&lt;/p&gt;

&lt;p&gt;If the system retrieves the wrong information, improving the prompt might not fix the problem.&lt;/p&gt;

&lt;p&gt;If the right information reaches the LLM but the answer is still wrong, the issue might lie in how the model uses that context.&lt;/p&gt;

&lt;p&gt;💡 The key lesson? Before fixing the answer, find out where the system went wrong.&lt;/p&gt;

&lt;p&gt;Thats one of the things Im exploring as I go deeper into RAG, evaluation, and building more reliable AI applications.&lt;/p&gt;

&lt;p&gt;For those building with LLMs, whats been your biggest challenge: retrieval, hallucinations, or inconsistent outputs?&lt;/p&gt;

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

</description>
      <category>software</category>
      <category>ai</category>
      <category>llm</category>
      <category>rag</category>
    </item>
    <item>
      <title>The Answer Was in the Middle of the Context and the Model Walked Past It</title>
      <dc:creator>Serguey Asael Shinder</dc:creator>
      <pubDate>Wed, 14 Oct 2026 07:31:05 +0000</pubDate>
      <link>https://dev.to/sergueyasaelshinder/the-answer-was-in-the-middle-of-the-context-and-the-model-walked-past-it-d52</link>
      <guid>https://dev.to/sergueyasaelshinder/the-answer-was-in-the-middle-of-the-context-and-the-model-walked-past-it-d52</guid>
      <description>&lt;p&gt;The support bot&lt;br&gt;
had a simple pipeline.&lt;/p&gt;

&lt;p&gt;Search the documents.&lt;/p&gt;

&lt;p&gt;Take the top twenty passages.&lt;/p&gt;

&lt;p&gt;Put them all in the prompt,&lt;br&gt;
then the question at the end.&lt;/p&gt;

&lt;p&gt;The answer to the question&lt;br&gt;
was in passage eleven.&lt;/p&gt;

&lt;p&gt;The bot said&lt;br&gt;
it could not find that information.&lt;/p&gt;

&lt;p&gt;Nothing was missing.&lt;/p&gt;

&lt;p&gt;It was all there,&lt;br&gt;
inside the window,&lt;br&gt;
well under the limit.&lt;/p&gt;

&lt;p&gt;The model just did not use it.&lt;/p&gt;

&lt;p&gt;There is a well known paper on this,&lt;br&gt;
from 2023,&lt;br&gt;
called Lost in the Middle.&lt;/p&gt;

&lt;p&gt;The researchers hid the one useful passage&lt;br&gt;
among many useless ones&lt;br&gt;
and moved it around.&lt;/p&gt;

&lt;p&gt;Accuracy was best&lt;br&gt;
when the passage sat at the start&lt;br&gt;
or at the end.&lt;/p&gt;

&lt;p&gt;Put it in the middle&lt;br&gt;
and accuracy fell,&lt;br&gt;
sometimes below what the model managed&lt;br&gt;
with no documents at all.&lt;/p&gt;

&lt;p&gt;The curve looked like a U.&lt;/p&gt;

&lt;p&gt;Newer models&lt;br&gt;
are better at this.&lt;/p&gt;

&lt;p&gt;None of them are perfect,&lt;br&gt;
and none of them tell you&lt;br&gt;
when they skipped something.&lt;/p&gt;

&lt;p&gt;So a large context window&lt;br&gt;
is a promise about what fits,&lt;br&gt;
not about what gets read&lt;br&gt;
with equal care.&lt;/p&gt;

&lt;p&gt;The fix is mostly&lt;br&gt;
about sending less.&lt;/p&gt;

&lt;p&gt;Twenty passages&lt;br&gt;
because twenty fit&lt;br&gt;
is not a reason.&lt;/p&gt;

&lt;p&gt;Rank them properly.&lt;/p&gt;

&lt;p&gt;Cut the ones below a score&lt;br&gt;
you have actually tested.&lt;/p&gt;

&lt;p&gt;Five good passages&lt;br&gt;
beat twenty with the right one&lt;br&gt;
buried in the middle.&lt;/p&gt;

&lt;p&gt;Put the strongest one first.&lt;/p&gt;

&lt;p&gt;Keep the question&lt;br&gt;
close to the end,&lt;br&gt;
and say it again there&lt;br&gt;
if the prompt is long.&lt;/p&gt;

&lt;p&gt;When the order matters,&lt;br&gt;
do not leave it&lt;br&gt;
to whatever order the search returned.&lt;/p&gt;

&lt;p&gt;Then measure it.&lt;/p&gt;

&lt;p&gt;Take one question&lt;br&gt;
with a known answer.&lt;/p&gt;

&lt;p&gt;Run it with the right passage&lt;br&gt;
at the start,&lt;br&gt;
in the middle,&lt;br&gt;
at the end.&lt;/p&gt;

&lt;p&gt;If the answers change,&lt;br&gt;
you have learned&lt;br&gt;
something your benchmark never told you.&lt;/p&gt;

&lt;p&gt;A long context&lt;br&gt;
is a long corridor.&lt;/p&gt;

&lt;p&gt;The model checks the door it came in&lt;br&gt;
and the door it is leaving by.&lt;/p&gt;

&lt;p&gt;Do not hide&lt;br&gt;
the thing that matters&lt;br&gt;
halfway down.&lt;/p&gt;

&lt;p&gt;– Serguey Asael Shinder&lt;/p&gt;

</description>
      <category>sergueyasaelshinder</category>
      <category>ai</category>
      <category>llm</category>
      <category>rag</category>
    </item>
    <item>
      <title>Code Search RAG: Chunk by Function With tree-sitter, Not by 512 Tokens</title>
      <dc:creator>Gabriel Anhaia</dc:creator>
      <pubDate>Wed, 14 Oct 2026 05:01:34 +0000</pubDate>
      <link>https://dev.to/gabrielanhaia/code-search-rag-chunk-by-function-with-tree-sitter-not-by-512-tokens-1o8o</link>
      <guid>https://dev.to/gabrielanhaia/code-search-rag-chunk-by-function-with-tree-sitter-not-by-512-tokens-1o8o</guid>
      <description>&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Book:&lt;/strong&gt; &lt;a href="https://www.amazon.com/dp/B0GX2YDC5Z" rel="noopener noreferrer"&gt;RAG Pocket Guide&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Also by me:&lt;/strong&gt; &lt;em&gt;Thinking in Go&lt;/em&gt; (2-book series) — &lt;a href="https://xgabriel.com/go-book" rel="noopener noreferrer"&gt;Complete Guide to Go Programming&lt;/a&gt; + &lt;a href="https://xgabriel.com/hexagonal-go" rel="noopener noreferrer"&gt;Hexagonal Architecture in Go&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;My project:&lt;/strong&gt; &lt;a href="https://hermes-ide.com" rel="noopener noreferrer"&gt;Hermes IDE&lt;/a&gt; | &lt;a href="https://github.com/hermes-hq/hermes-ide" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; — an IDE for developers who ship with Claude Code and other AI coding tools&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Me:&lt;/strong&gt; &lt;a href="https://xgabriel.com" rel="noopener noreferrer"&gt;xgabriel.com&lt;/a&gt; | &lt;a href="https://github.com/gabrielanhaia" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;The chunk the codebase assistant retrieved for "where does the retry delay get computed?" starts halfway down a function, at an &lt;code&gt;except&lt;/code&gt; clause, and has no &lt;code&gt;def&lt;/code&gt; line, no class name, nothing but a file path to say where it came from. The model writes a confident answer about a function it never saw the name of.&lt;/p&gt;

&lt;p&gt;The bug is in the chunker. It cut the file every 512 tokens, the way it cuts prose, and the cut landed in the middle of a function. Code has natural seams at &lt;code&gt;def&lt;/code&gt; and &lt;code&gt;class&lt;/code&gt;; a token counter ignores them.&lt;/p&gt;

&lt;p&gt;The fix fits in two small files. Parse Python with &lt;a href="https://tree-sitter.github.io/tree-sitter/" rel="noopener noreferrer"&gt;tree-sitter&lt;/a&gt;, cut at function and class boundaries, carry the file path and the enclosing symbol as metadata, and split the definitions that are too big on statement boundaries. Then score both chunkers on a labelled query set, so you see the difference on your own machine instead of taking my word for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a token window does to a function
&lt;/h2&gt;

&lt;p&gt;A fixed window has no idea where a function starts. If a function begins at token 470 of a 512-token window, the chunk gets the decorator, the signature and four lines of body. The next chunk gets the rest of the body with no name attached. Ask for "the function that parses the config file" and the retriever has to match either a signature with no logic or logic with no signature.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://arxiv.org/abs/2506.15655" rel="noopener noreferrer"&gt;cAST paper&lt;/a&gt; from Carnegie Mellon and Augment Code found the same problem: "Existing line-based chunking heuristics often break semantic structures, splitting functions or merging unrelated code." Their AST-based chunker, which splits large syntax nodes and merges small siblings under a size limit, reported a 4.3-point Recall@5 gain on RepoEval retrieval and a 2.67-point Pass@1 gain on SWE-bench generation over the fixed-size baseline.&lt;/p&gt;

&lt;p&gt;Frameworks have moved the same way. LlamaIndex ships a &lt;a href="https://developers.llamaindex.ai/python/framework/module_guides/loading/node_parsers/modules/" rel="noopener noreferrer"&gt;&lt;code&gt;CodeSplitter&lt;/code&gt;&lt;/a&gt; that parses with tree-sitter and, &lt;a href="https://github.com/run-llama/llama_index/blob/main/llama-index-core/llama_index/core/node_parser/text/code.py" rel="noopener noreferrer"&gt;in its source&lt;/a&gt;, caps chunks at 1,500 characters by default. The cAST authors published &lt;a href="https://github.com/yilinjz/astchunk" rel="noopener noreferrer"&gt;astchunk&lt;/a&gt;, which measures chunk size in non-whitespace characters and supports Python, Java, C# and TypeScript. Both are fine to use. Writing your own once is how you find out what they are doing for you, and how you tune it when your codebase has 600-line functions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fikbmjvfn5vly81qaodra.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fikbmjvfn5vly81qaodra.png" alt="A source file as a vertical strip with dashed horizontal cut lines slicing straight through function blocks, next to the same file cut cleanly along function boundaries" width="800" height="800"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Token windows cut wherever the count runs out. AST chunks cut where the code does.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Two chunkers, same token budget
&lt;/h2&gt;

&lt;p&gt;Install the pieces. &lt;a href="https://pypi.org/project/tree-sitter/" rel="noopener noreferrer"&gt;py-tree-sitter 0.26.0&lt;/a&gt; needs Python 3.10 or newer, and the Python grammar ships as its own wheel.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;tree-sitter tree-sitter-python &lt;span class="se"&gt;\&lt;/span&gt;
  sentence-transformers numpy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The embedder is &lt;a href="https://huggingface.co/BAAI/bge-small-en-v1.5" rel="noopener noreferrer"&gt;&lt;code&gt;BAAI/bge-small-en-v1.5&lt;/code&gt;&lt;/a&gt;: a small English model with 384 dimensions and a 512-token max sequence length. Both chunkers get the same budget of 480 tokens, counted with the embedder's own tokenizer, so a short header and the special tokens still fit under 512 and nothing gets silently truncated. That is the fair version of "512-token chunks".&lt;/p&gt;

&lt;p&gt;First file, &lt;code&gt;chunkers.py&lt;/code&gt;, starting with the &lt;code&gt;Chunk&lt;/code&gt; type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# chunkers.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dataclasses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dataclass&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Callable&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;tree_sitter_python&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;tspython&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;tree_sitter&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Language&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Parser&lt;/span&gt;

&lt;span class="n"&gt;PARSER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Parser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Language&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tspython&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;language&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
&lt;span class="n"&gt;DEFS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;function_definition&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;class_definition&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;Count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Callable&lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="nd"&gt;@dataclass&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Chunk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;  &lt;span class="c1"&gt;# "JSONDecoder.raw_decode", "&amp;lt;module&amp;gt;", or ""
&lt;/span&gt;    &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;   &lt;span class="c1"&gt;# 1-based first line
&lt;/span&gt;    &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;     &lt;span class="c1"&gt;# 1-based last line, inclusive
&lt;/span&gt;    &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;embed_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;# &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; :: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The baseline comes next. It adds lines while their token count stays under the budget, emits a chunk, then backs up about 64 tokens for the overlap. The BGE tokenizer emits no token for a newline, so summing per line counts the same tokens the AST chunker counts on joined text:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# chunkers.py (continued)
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fixed_chunks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ntok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                 &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;480&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;overlap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;used&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;cost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ntok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;used&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;cost&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;break&lt;/span&gt;
            &lt;span class="n"&gt;used&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;cost&lt;/span&gt;
            &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Chunk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;
        &lt;span class="n"&gt;back&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;back&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;overlap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
            &lt;span class="n"&gt;back&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nf"&gt;ntok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The baseline is slightly generous: it snaps to line ends and overlaps about 64 tokens, where a plain token splitter would cut mid-line. Its chunks still get the file path in the header. The only things it lacks are syntax-aware boundaries and the symbol name, which keeps the comparison about exactly those two things.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;src.split("\n")&lt;/code&gt; rather than &lt;code&gt;splitlines()&lt;/code&gt; is deliberate. tree-sitter counts rows by &lt;code&gt;\n&lt;/code&gt; only, and &lt;a href="https://docs.python.org/3/library/stdtypes.html#str.splitlines" rel="noopener noreferrer"&gt;&lt;code&gt;splitlines()&lt;/code&gt;&lt;/a&gt; also splits on form feeds, which some older Python files contain. Mixing the two puts every line number after the first form feed off by one.&lt;/p&gt;

&lt;p&gt;The AST chunker goes in the same file. First the helpers that write chunks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# chunkers.py (continued)
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;unwrap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;decorated_definition&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;child_by_field_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;definition&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;name_of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;defn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;defn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;child_by_field_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;ast_chunks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ntok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
               &lt;span class="n"&gt;budget&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;480&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;tree&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PARSER&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Chunk&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;span&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;  &lt;span class="c1"&gt;# 0-based rows, inclusive
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sym&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;span&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;ntok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;budget&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Chunk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sym&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="c1"&gt;# one statement is bigger than the budget: window it
&lt;/span&gt;        &lt;span class="n"&gt;room&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;budget&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nf"&gt;ntok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;fixed_chunks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ntok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;room&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Chunk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sym&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                             &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;emit&lt;/code&gt; writes one chunk when the span fits and falls back to line windows only when a single statement is bigger than the budget. &lt;code&gt;pack&lt;/code&gt; sits on top of it and greedily groups neighbouring statements. It is still inside &lt;code&gt;ast_chunks&lt;/code&gt;, so keep the indentation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;    &lt;span class="c1"&gt;# ast_chunks, continued
&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;pack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sym&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;group&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;start_point&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
                &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end_point&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;ntok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;span&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;budget&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="nf"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sym&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;end_point&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="n"&gt;group&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
            &lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sym&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;start_point&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                 &lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;end_point&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last two helpers decide what a unit is, and the final line starts the walk at the module root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;    &lt;span class="c1"&gt;# ast_chunks, continued
&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;defn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;name_of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;defn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;sym&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;
        &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start_point&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end_point&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;ntok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;span&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;budget&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sym&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;defn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;child_by_field_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;span&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start_point&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;defn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;class_definition&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;block&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;named_children&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sym&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;pack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;named_children&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sym&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;block&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;loose&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;defn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;unwrap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;defn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;DEFS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;loose&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="nf"&gt;pack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;loose&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;module&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="n"&gt;loose&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
                &lt;span class="nf"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;defn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;loose&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;loose&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;pack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;loose&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;module&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;block&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;root_node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;named_children&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Walk it top-down. &lt;code&gt;block&lt;/code&gt; goes through the statements of a module or a class body. Each definition goes to &lt;code&gt;define&lt;/code&gt;, and the statements between definitions (imports, constants, a class docstring) collect into &lt;code&gt;loose&lt;/code&gt; and get packed into &lt;code&gt;&amp;lt;module&amp;gt;&lt;/code&gt; chunks or chunks named after the class. &lt;code&gt;define&lt;/code&gt; emits a definition whole if it fits. Decorators count as part of the definition, because tree-sitter-python wraps them in a &lt;a href="https://github.com/tree-sitter/tree-sitter-python/blob/master/grammar.js" rel="noopener noreferrer"&gt;&lt;code&gt;decorated_definition&lt;/code&gt;&lt;/a&gt; whose &lt;code&gt;definition&lt;/code&gt; field holds the function or class.&lt;/p&gt;

&lt;p&gt;The symbol path is the breadcrumb. A method inside &lt;code&gt;JSONDecoder&lt;/code&gt; gets &lt;code&gt;JSONDecoder.raw_decode&lt;/code&gt;, and &lt;code&gt;embed_text()&lt;/code&gt; puts &lt;code&gt;# json/decoder.py :: JSONDecoder.raw_decode&lt;/code&gt; on the first line of what gets embedded. A query that says "JSON decoder" now has a literal match even when the method body never says it.&lt;/p&gt;

&lt;h2&gt;
  
  
  When one function is bigger than the budget
&lt;/h2&gt;

&lt;p&gt;Function-level chunks break down on the big ones. A 300-line class or a 200-line function does not fit in 480 tokens, and dumping it whole into the embedder does not help. The model card for &lt;a href="https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2" rel="noopener noreferrer"&gt;all-MiniLM-L6-v2&lt;/a&gt; says so directly: "By default, input text longer than 256 word pieces is truncated." Everything past the cutoff is invisible to retrieval, and nothing warns you.&lt;/p&gt;

&lt;p&gt;The chunker above handles size in three steps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Too-big class: recurse.&lt;/strong&gt; &lt;code&gt;define&lt;/code&gt; calls &lt;code&gt;block&lt;/code&gt; on the class body, so every method becomes its own chunk with a &lt;code&gt;Class.method&lt;/code&gt; symbol. The class signature becomes a &lt;code&gt;head&lt;/code&gt; that is prepended to the chunks of loose class-level statements, so the docstring and class attributes still say which class they belong to.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Too-big function: pack statements under the signature.&lt;/strong&gt; &lt;code&gt;pack&lt;/code&gt; walks the body's top-level statements and greedily merges neighbours until the next one would blow the budget. Every piece starts with the full signature, decorators included. A retrieved piece from the middle of a long function still opens with &lt;code&gt;def _make_iterencode(...)&lt;/code&gt;. This is the merge half of what cAST does: split big nodes, merge small siblings.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Too-big single statement: fall back to windows.&lt;/strong&gt; A 150-line &lt;code&gt;if&lt;/code&gt; block is one statement. &lt;code&gt;emit&lt;/code&gt; notices it does not fit and runs the line-window chunker over just that statement, with the signature still on top. cAST recurses into the statement's children instead, which keeps boundaries cleaner. The fallback is the cheap version; swap it for recursion when your codebase has a lot of these.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5niex5m2ke3wlx9411a8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5niex5m2ke3wlx9411a8.png" alt="A syntax tree drawn as nested boxes, small sibling boxes grouped into one chunk, one oversized box split into pieces that each carry a copy of the same signature header" width="800" height="800"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Small siblings merge, big nodes split, and every split piece keeps the signature on top.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Two costs to know about. The header eats budget, so a function with a 15-line signature leaves less room for the body in each piece. And &lt;code&gt;pack&lt;/code&gt; re-tokenizes growing spans, which is quadratic in the worst case. On a few files that is nothing. On a monorepo, count tokens per statement once and add them up instead.&lt;/p&gt;
&lt;h2&gt;
  
  
  Score both on a labelled query set
&lt;/h2&gt;

&lt;p&gt;A chunker is only better if retrieval gets better, so measure it. You need a corpus every reader already has, and the Python standard library is on your disk. The queries below describe what code does, mostly without using its identifiers, the way you search a codebase you did not write. Each label is a file plus a symbol path.&lt;/p&gt;

&lt;p&gt;Before the eval, append one helper to &lt;code&gt;chunkers.py&lt;/code&gt;, so the gold line ranges come from the parser instead of from hand-typed line numbers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# chunkers.py (append)
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;symbols&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]]:&lt;/span&gt;
    &lt;span class="n"&gt;found&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;walk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;child&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;named_children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;defn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;unwrap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;child&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;defn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;DEFS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;continue&lt;/span&gt;
            &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;name_of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;defn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;sym&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;
            &lt;span class="n"&gt;found&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;sym&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;child&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start_point&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                          &lt;span class="n"&gt;child&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end_point&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;defn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;class_definition&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="nf"&gt;walk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;defn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;child_by_field_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;sym&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;walk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PARSER&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="n"&gt;root_node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;found&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Second file, &lt;code&gt;eval.py&lt;/code&gt;. The top holds the model, the files and the labels:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# eval.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sysconfig&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;numpy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sentence_transformers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SentenceTransformer&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;chunkers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ast_chunks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fixed_chunks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;symbols&lt;/span&gt;

&lt;span class="n"&gt;MODEL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BAAI/bge-small-en-v1.5&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;QUERY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Represent this sentence for searching relevant passages: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;FILES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;json/__init__.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;json/decoder.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;json/encoder.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;shlex.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;textwrap.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;collections/__init__.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;LABELS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;quote a string so a POSIX shell sees one argument&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;shlex.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;quote&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;split a command line into tokens like a shell&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;shlex.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;split&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;strip the indentation shared by every line&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;textwrap.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dedent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;break a paragraph into lines of a maximum width&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;textwrap.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;wrap&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;add a prefix to the start of selected lines&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;textwrap.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;indent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;decode JSON at the start of a string, return end&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;json/decoder.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;JSONDecoder.raw_decode&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;scan a JSON string literal handling escapes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;json/decoder.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;py_scanstring&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;serialize a Python object to a JSON str&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;json/__init__.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dumps&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;walk nested lists and dicts yielding JSON text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;json/encoder.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;_make_iterencode&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;the n elements with the highest counts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;collections/__init__.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Counter.most_common&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;move a key to either end of an ordered dict&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;collections/__init__.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;OrderedDict.move_to_end&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;make a tuple subclass with fields accessed by name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;collections/__init__.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;namedtuple&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;recall&lt;/code&gt; embeds every chunk once, then checks the top five results for each query in two ways, explained after the code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# eval.py (continued)
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;recall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gold&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;vecs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;embed_text&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                        &lt;span class="n"&gt;normalize_embeddings&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;touched&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;whole&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;gold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;qv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;QUERY&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;normalize_embeddings&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;top&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;argsort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vecs&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;qv&lt;/span&gt;&lt;span class="p"&gt;))[:&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;hits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;top&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;touched&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;hits&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;whole&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;hits&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;touched&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gold&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;whole&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gold&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;main&lt;/code&gt; reads the six files from the standard library of whichever Python runs it and builds both chunk lists:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# eval.py (continued)
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sysconfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_paths&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stdlib&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SentenceTransformer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MODEL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;ntok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;ids&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tokenizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;add_special_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;fixed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;by_ast&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;spans&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;rel&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;FILES&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;fixed&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nf"&gt;fixed_chunks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ntok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;by_ast&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nf"&gt;ast_chunks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ntok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;spans&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;symbols&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;gold&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;spans&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;sym&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sym&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;LABELS&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chunks&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fixed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fixed&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ast&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;by_ast&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;recall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gold&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; chunks=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
              &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;touched@5=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; whole@5=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The query prefix is the retrieval instruction from the &lt;a href="https://huggingface.co/BAAI/bge-small-en-v1.5" rel="noopener noreferrer"&gt;BGE model card&lt;/a&gt;, which says passages need no prefix. Run &lt;code&gt;python eval.py&lt;/code&gt;. The tokenizer may log a "sequence length is longer than" warning while counting tokens for whole classes; that is the counter measuring, not the embedder truncating.&lt;/p&gt;

&lt;p&gt;Read the two numbers separately. &lt;code&gt;touched@5&lt;/code&gt; asks whether any top-5 chunk overlaps the target function at all. That is lenient, and it is the metric a token-window chunker looks best on, because a chunk holding three lines of the right function counts. &lt;code&gt;whole@5&lt;/code&gt; asks whether one top-5 chunk contains the entire function, which is what the LLM needs in order to answer. The gap between them is the "body without its name" problem, measured.&lt;/p&gt;

&lt;p&gt;There are no results printed in this post, on purpose: your Python version changes the stdlib files, and 12 queries is a smoke test, not a benchmark. Run it and read your own numbers. Some labels are longer than the 480-token budget, so the AST chunker splits them too and neither chunker can score &lt;code&gt;whole&lt;/code&gt; on them. &lt;code&gt;namedtuple&lt;/code&gt; and &lt;code&gt;_make_iterencode&lt;/code&gt; are the obvious ones; &lt;code&gt;dumps&lt;/code&gt; (its docstring alone is long) and &lt;code&gt;py_scanstring&lt;/code&gt; may be over as well, depending on your Python version. Expect that, and look at which chunk comes back instead. For a real decision, write 50 to 100 queries against your own repo, from real questions people asked in code review or on Slack, and keep the file in version control next to the chunker.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before you point it at your repo
&lt;/h2&gt;

&lt;p&gt;Four changes take this from a stdlib demo to something you would index a monorepo with.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;More languages.&lt;/strong&gt; tree-sitter has grammars for most languages you will meet, but node type names differ per grammar. &lt;code&gt;function_definition&lt;/code&gt; is the Python name; check each grammar's &lt;code&gt;node-types.json&lt;/code&gt; and give every language its own &lt;code&gt;DEFS&lt;/code&gt; set. If you want a maintained version for Python, Java, C# and TypeScript, &lt;a href="https://github.com/yilinjz/astchunk" rel="noopener noreferrer"&gt;astchunk&lt;/a&gt; already does it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Parse errors.&lt;/strong&gt; tree-sitter is &lt;a href="https://tree-sitter.github.io/tree-sitter/" rel="noopener noreferrer"&gt;built to keep producing useful results even when the source has syntax errors&lt;/a&gt;, so a half-written file mid-refactor still produces a tree. py-tree-sitter exposes &lt;a href="https://tree-sitter.github.io/py-tree-sitter/classes/tree_sitter.Node.html" rel="noopener noreferrer"&gt;&lt;code&gt;has_error&lt;/code&gt;&lt;/a&gt; on every node. Check it on the root, and when a file is badly broken, fall back to &lt;code&gt;fixed_chunks&lt;/code&gt; for that file rather than indexing garbage boundaries.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A code-aware embedder.&lt;/strong&gt; &lt;code&gt;bge-small-en-v1.5&lt;/code&gt; is a general English model with a WordPiece tokenizer that shreds &lt;code&gt;snake_case&lt;/code&gt; and &lt;code&gt;camelCase&lt;/code&gt; into fragments. It works for the demo. For production, compare a code-trained embedding model on your labelled set using the same &lt;code&gt;eval.py&lt;/code&gt;; that is what the set is for.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keyword search next to vectors.&lt;/strong&gt; Exact identifiers, error codes and config keys are where dense retrieval is weakest. Index the &lt;code&gt;symbol&lt;/code&gt; field in a keyword index too and fuse the results. A query that contains &lt;code&gt;raw_decode&lt;/code&gt; should find &lt;code&gt;raw_decode&lt;/code&gt; without asking an embedding model to guess.&lt;/p&gt;




&lt;h2&gt;
  
  
  If this was useful
&lt;/h2&gt;

&lt;p&gt;A retriever can only return what the chunker kept together, and code makes that easy to see. The &lt;a href="https://www.amazon.com/dp/B0GX2YDC5Z" rel="noopener noreferrer"&gt;&lt;em&gt;RAG Pocket Guide&lt;/em&gt;&lt;/a&gt; covers the stages on either side of this one, retrieval and reranking, plus the chunking patterns for production RAG. If you are building search over a codebase or anything else with structure, it is the short version of the decisions this post only touches.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.amazon.com/dp/B0GX2YDC5Z" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpkc0hvypembd4wgh6t0k.jpg" alt="RAG Pocket Guide: Retrieval, Chunking, and Reranking Patterns for Production" width="800" height="1200"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>rag</category>
      <category>python</category>
      <category>ai</category>
      <category>llm</category>
    </item>
  </channel>
</rss>
