<?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: Shakti Wadekar</title>
    <description>The latest articles on DEV Community by Shakti Wadekar (@shaktiwadekar).</description>
    <link>https://dev.to/shaktiwadekar</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3984168%2Fc103dcba-7c5f-4bb5-8748-4e42d549b48d.png</url>
      <title>DEV Community: Shakti Wadekar</title>
      <link>https://dev.to/shaktiwadekar</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/shaktiwadekar"/>
    <language>en</language>
    <item>
      <title>[Boost]</title>
      <dc:creator>Shakti Wadekar</dc:creator>
      <pubDate>Mon, 29 Jun 2026 14:00:03 +0000</pubDate>
      <link>https://dev.to/shaktiwadekar/-d03</link>
      <guid>https://dev.to/shaktiwadekar/-d03</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/shaktiwadekar/1m-context-tokens-is-not-memory-the-beginners-guide-to-long-context-4h18" class="crayons-story__hidden-navigation-link"&gt;1M Context Tokens Is Not Memory: The Beginner’s Guide to Long Context&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/shaktiwadekar" class="crayons-avatar  crayons-avatar--l  "&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%2Fuser%2Fprofile_image%2F3984168%2Fc103dcba-7c5f-4bb5-8748-4e42d549b48d.png" alt="shaktiwadekar profile" class="crayons-avatar__image" width="96" height="96"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/shaktiwadekar" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Shakti Wadekar
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Shakti Wadekar
                
              
              &lt;div id="story-author-preview-content-4005736" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/shaktiwadekar" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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%2Fuser%2Fprofile_image%2F3984168%2Fc103dcba-7c5f-4bb5-8748-4e42d549b48d.png" class="crayons-avatar__image" alt="" width="96" height="96"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Shakti Wadekar&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/shaktiwadekar/1m-context-tokens-is-not-memory-the-beginners-guide-to-long-context-4h18" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jun 27&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/shaktiwadekar/1m-context-tokens-is-not-memory-the-beginners-guide-to-long-context-4h18" id="article-link-4005736"&gt;
          1M Context Tokens Is Not Memory: The Beginner’s Guide to Long Context
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/beginners"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;beginners&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/learning"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;learning&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/llm"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;llm&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
            &lt;a href="https://dev.to/shaktiwadekar/1m-context-tokens-is-not-memory-the-beginners-guide-to-long-context-4h18#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            6 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>1M Context Tokens Is Not Memory: The Beginner’s Guide to Long Context</title>
      <dc:creator>Shakti Wadekar</dc:creator>
      <pubDate>Sat, 27 Jun 2026 12:23:04 +0000</pubDate>
      <link>https://dev.to/shaktiwadekar/1m-context-tokens-is-not-memory-the-beginners-guide-to-long-context-4h18</link>
      <guid>https://dev.to/shaktiwadekar/1m-context-tokens-is-not-memory-the-beginners-guide-to-long-context-4h18</guid>
      <description>&lt;p&gt;So your favorite LLM now supports a 1 million token context window. Marketing slides everywhere: “Fits the entire Harry Potter series! Twice! With footnotes!”&lt;/p&gt;

&lt;p&gt;A model with a 1 million token context window sounds powerful. And it is powerful.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;But here are the key points:&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;code&gt;A model having 1M context means it can receive a lot of input.&lt;br&gt;
Whether it remembers, finds, connects, or uses all of it correctly&lt;br&gt;
is a completely separate problem.&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Long Context Is Capacity, Not Capability
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Context length = how much the model can receive. &lt;br&gt;
Capability = how well the model can use it.&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Access is not the same as intelligence.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;“fits in context” does not mean “understood perfectly”&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“Okay but it can read a lot, so it understands a lot, right?”&lt;/strong&gt;&lt;br&gt;
Not necessarily.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Reading ≠ remembering accurately. &lt;br&gt;
Reading ≠ using everything you read correctly when it matters.&lt;/code&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  So, Is Long Context Bad?
&lt;/h2&gt;

&lt;p&gt;No.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Long context is extremely useful.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ol&gt;
&lt;li&gt;It reduces the need for aggressive chunking (RAG).&lt;/li&gt;
&lt;li&gt;It helps with large documents and big codebases.&lt;/li&gt;
&lt;li&gt;It makes many workflows easier.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;The problem is NOT long context.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;code&gt;The problem is expecting long context to behave like perfect memory, perfect search, perfect reasoning, and perfect summarization all at once.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;That is NOT how production AI works.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A good AI system usually combines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Long context
+ retrieval
+ memory
+ summarization
+ structured context
+ evaluation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Three known problems with long context models:
&lt;/h2&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%2Fo4g6ce3q6z8a0h1a3q64.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%2Fo4g6ce3q6z8a0h1a3q64.png" alt="Long context model problems" width="800" height="424"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Forgetting (a.k.a. “Lost in the Middle”)
&lt;/h3&gt;

&lt;p&gt;Studies on long-context models found something very interesting:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Models are great at remembering stuff at the start and end of a long input, and surprisingly bad at the middle.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;If you bury the one important text of your 80-page document in the middle, the model might just… not notice it. Even though it “read” it.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Missing (needle-in-a-haystack failures)
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;Hide one specific sentence (“The secret code is 4471”) inside a huge pile of text, then ask the model to find it. Sometimes it nails it. Sometimes gives you a confident wrong answer.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;More tokens means more haystack, and more haystack means more places for the needle to hide.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Failing multi-hop reasoning
&lt;/h3&gt;

&lt;p&gt;Multi-hop reasoning means the model must connect multiple facts from different places.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multi-hop reasoning&lt;/strong&gt; = needing to connect Fact A (page 3) with Fact B (page 250) with Fact C (page 800) to answer one question.&lt;/p&gt;

&lt;p&gt;The longer and more scattered the chain of facts or critical information, the more likely the model is to drop a link.&lt;/p&gt;

&lt;p&gt;Rather than say “I don’t know,” it’ll often just invent a plausible-sounding connection (Hallucination).&lt;/p&gt;




&lt;h2&gt;
  
  
  So… is there anything you can actually do about this?
&lt;/h2&gt;

&lt;p&gt;Yes. And that’s the more useful half of this article, so let’s get into it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution: Stop Trusting, Start Evaluating
&lt;/h2&gt;

&lt;p&gt;Okay, enough doom-scrolling through failure modes.&lt;/p&gt;

&lt;p&gt;Here’s the actual fix, and it’s less glamorous than “buy a bigger context window”:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Evaluate the model thoroughly on your long-context use case before you let it anywhere near your application or business workflow.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;A long-context model should not be judged only by &lt;em&gt;&lt;strong&gt;how much&lt;/strong&gt;&lt;/em&gt; text it can receive. &lt;strong&gt;It should be judged by &lt;em&gt;how well&lt;/em&gt; it can&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Find the right information
2. Remember the important constraints during the task
3. Connect facts across distant sections
4. Ignore irrelevant noise
5. Avoid hallucination
6. Produce a faithful final answer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  There are two ways to do this, and you need both:
&lt;/h2&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%2F7rtg36ycy135rbv5weoj.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%2F7rtg36ycy135rbv5weoj.png" alt="Two Evaluation methods" width="800" height="353"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Academic benchmarks:&lt;/strong&gt; LongBench and LongGenBench. Good for understanding a model’s general long-context behavior before you even pick which model to use.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your own domain-specific evaluation pipeline:&lt;/strong&gt;
&lt;code&gt;Because no academic benchmark knows what “correct” means for your 200-page insurance policy or your codebase’s internal logic.&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let’s take both in turn.&lt;/p&gt;

&lt;h3&gt;
  
  
  LongBench and LongGenBench
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;LongBench and LongGenBench exist precisely to measure the gap between “received the text” and “remembers, finds, connects, or uses it correctly”&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://arxiv.org/abs/2412.15204" rel="noopener noreferrer"&gt;LongBench&lt;/a&gt;&lt;/strong&gt;: A benchmark suite that tests models on &lt;em&gt;real long-document tasks&lt;/em&gt;: long Q&amp;amp;A, summarization, code understanding, few-shot learning, all stretched across long inputs in multiple languages.&lt;/p&gt;

&lt;p&gt;The point: see how performance holds up as documents get longer and more complex, not just whether the model can technically accept the tokens.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://arxiv.org/abs/2410.04199" rel="noopener noreferrer"&gt;LongGenBench&lt;/a&gt;&lt;/strong&gt;: Focuses on something sneakier: &lt;strong&gt;&lt;em&gt;long-form generation&lt;/em&gt;&lt;/strong&gt;, not just long-form reading.&lt;/p&gt;

&lt;p&gt;It checks whether a model can produce a long, coherent piece of output (think: a long structured document with consistent constraints throughout) without contradicting itself, drifting off-topic, or quietly forgetting an instruction it agreed to 3,000 words ago.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Use these two benchmarks the way you’d use a car’s official mileage rating: useful for comparing models before you buy, but not a guarantee of what will happen on your specific roads, in your specific traffic. For that, you need your own test.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;There are &lt;strong&gt;&lt;em&gt;various other&lt;/em&gt;&lt;/strong&gt; benchmarks, but mentioning here 2 which covers long context &lt;strong&gt;&lt;em&gt;understanding&lt;/em&gt;&lt;/strong&gt; and long context &lt;strong&gt;&lt;em&gt;generation&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Design your own domain-specific evaluation pipeline
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;This is the part most people skip, and it’s the part that actually saves you when production breaks at 2 AM. A solid pipeline looks like this:&lt;/code&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Build a test set out of real examples from your domain&lt;/strong&gt;, not generic Wikipedia paragraphs. If you’re building a legal-contract assistant, your test documents should be actual long contracts, with real clauses buried in real places.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plant “needles” deliberately, at different positions&lt;/strong&gt;. Put your critical facts at the start, middle, and end of test documents on purpose. Remember the “lost in the middle” problem? This is how you measure whether your model, on your documents, suffers from it, and how badly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Include multi-hop questions, not just single-fact lookups&lt;/strong&gt;. A question that requires connecting a clause on page 4 with an exception on page 60 will expose reasoning failures that simple needle-in-haystack tests won’t.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Score for correctness, not just “an answer was produced.”&lt;/strong&gt; A confident, fluent, completely wrong answer should fail your eval just as hard as a refusal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automate the grading where you can.&lt;/strong&gt; Exact-match for factual lookups, a separate LLM-as-judge step or rubric for open-ended answers, human spot-checks for anything high-stakes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Set a minimum acceptable threshold before shipping&lt;/strong&gt; (Example: “95%+ accuracy on critical-fact retrieval across all document positions”) and treat dropping below it as a blocking bug.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Re-run the eval whenever you change anything:&lt;/strong&gt; model version, prompt, chunking strategy, retrieval logic. Long-context behavior is surprisingly sensitive to small changes, and “it worked last month” is not a test result.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;code&gt;The academic benchmarks tell you whether a model is generally trustworthy with long context.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Your own pipeline tells you whether it’s trustworthy with your documents, your questions, and your definition of “correct.” Skip the second one, and you’re deploying on hope.&lt;/code&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What Metrics Should You Track?
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Answer accuracy
2. Faithfulness to the provided context
3. Evidence citation quality
4. Multi-hop reasoning correctness
5. Instruction following
6. Long-output consistency
7. Hallucination rate
8. Latency
9. Cost
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Accuracy&lt;/strong&gt; tells you whether the answer is correct. &lt;strong&gt;Faithfulness&lt;/strong&gt; tells you whether the answer is grounded in the provided context.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Citation&lt;/strong&gt; quality tells you whether the model can point to the right evidence.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Latency&lt;/strong&gt; and &lt;strong&gt;cost&lt;/strong&gt; tell you whether the solution is actually usable, or whether every user question requires a small financial ceremony :)&lt;/p&gt;




&lt;h2&gt;
  
  
  Compare Different Context Strategies
&lt;/h2&gt;

&lt;p&gt;Do not evaluate only one setup.&lt;/p&gt;

&lt;p&gt;Compare multiple approaches:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Full long context
2. RAG-based retrieval
3. Summarized context
4. Hybrid approach: retrieval + summaries + long context
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sometimes full long context works well. Sometimes retrieval works better.&lt;/p&gt;

&lt;p&gt;Sometimes a structured summary beats dumping raw text. Sometimes the best solution is a hybrid system.&lt;/p&gt;




&lt;h2&gt;
  
  
  Concluding remarks:
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;The solution to long-context risk is not avoiding long-context models. They are powerful and useful. The solution is to evaluate them properly.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;So when you see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Supports 1M tokens
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the better question is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;What can it reliably do with 1M tokens?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because context length is a specification. Performance is an evaluation result.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Marketing loves the first one. Engineers should care about the second one.&lt;/code&gt;&lt;/p&gt;




&lt;p&gt;Editing credit goes to an AI (ChatGPT and Claude). It suggested better phrasing, cleaner diagrams, and only hallucinated few facts, which I caught using the multi-hop reasoning skills it taught me two sections ago. Synergy :)&lt;/p&gt;




</description>
      <category>ai</category>
      <category>beginners</category>
      <category>learning</category>
      <category>llm</category>
    </item>
    <item>
      <title>Stop Letting LLMs Hallucinate Your Codebase: A Graph-First Way to Summarize Repos</title>
      <dc:creator>Shakti Wadekar</dc:creator>
      <pubDate>Fri, 26 Jun 2026 04:34:36 +0000</pubDate>
      <link>https://dev.to/shaktiwadekar/stop-letting-llms-hallucinate-your-codebase-a-graph-first-way-to-summarize-repos-2ncn</link>
      <guid>https://dev.to/shaktiwadekar/stop-letting-llms-hallucinate-your-codebase-a-graph-first-way-to-summarize-repos-2ncn</guid>
      <description>&lt;h2&gt;
  
  
  1. The problem we’re actually trying to solve
&lt;/h2&gt;

&lt;p&gt;Ask any LLM to “summarize this repository” and it will happily oblige, and it will also happily make things up. It will mention a test suite that doesn’t exist. It will describe an API endpoint it inferred from the folder name api/. It will confidently tell you about a “data flow” it never actually traced :(&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Reason:&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;LLMs are pattern completers, not code analyzers. When you dump a pile of files into a context window and ask for a summary, the model is guessing based on naming conventions and statistical priors from millions of other repos it has seen, not from understanding this code.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Solution:&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://github.com/shaktiwadekar9/code-graph-ai-summarizer" rel="noopener noreferrer"&gt;code-graph-ai-summarizer&lt;/a&gt; is a small, Python project that takes a different approach: don't let the LLM look at raw code at all.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Instead, build a precise, structured, graph-derived set of facts about the repo first, using real static analysis, and only then hand the LLM a curated fact-sheet and ask it to write summary.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The LLM's job shrinks from “understand this codebase” to “narrate these facts I already verified,” which is a job LLMs are very good at.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;That one design decision is the whole story of this repo&lt;/strong&gt;, and it’s a pattern worth learning regardless of whether you ever run this exact tool.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. The core idea, in one picture
&lt;/h2&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%2F629kqfhjwb2xhnxp5lxf.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%2F629kqfhjwb2xhnxp5lxf.png" alt="Algorithm to generate code summaries" width="800" height="727"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Five stages. Each stage only passes forward what the next stage needs and never the raw source code itself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Let’s walk through each one, building up from the basics.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Stage 1 : Turning code into a graph (Joern + CPG)
&lt;/h2&gt;

&lt;p&gt;Before we can reason about a codebase, we need a representation of it that a program can query.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Reading characters in a .py file tells you nothing about which function calls which other function. You need structure. This is where Joern comes in.&lt;/code&gt; &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/joernio/joern" rel="noopener noreferrer"&gt;Joern&lt;/a&gt;&lt;/strong&gt; is an open-source static analysis platform that parses source code into a &lt;strong&gt;Code Property Graph (CPG)&lt;/strong&gt;: a single graph data structure that fuses together several representations:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The Abstract Syntax Tree (what the code literally says)&lt;/li&gt;
&lt;li&gt;The Control Flow Graph (what order things execute in)&lt;/li&gt;
&lt;li&gt;The Data Dependence Graph (which values flow into which)&lt;/li&gt;
&lt;li&gt;Call graph edges (what calls what)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Once your repo is imported into Joern, all of that becomes queryable through CPGQL : a Scala-based query language that treats the whole codebase as one big graph you can filter, map, and traverse.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://github.com/shaktiwadekar9/code-graph-ai-summarizer" rel="noopener noreferrer"&gt;code-graph-ai-summarizer&lt;/a&gt; repo, that connection lives in &lt;code&gt;joern/client.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class JoernRunner:
    def __init__(self, server: str) -&amp;gt; None:
        self.client = CPGQLSClient(server)

    def import_repo(self, repo_path: Path, project_name: str) -&amp;gt; None:
            result = self.client.execute(import_code_query(str(repo_path), project_name))
            ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;JoernRunner&lt;/code&gt; is just a thin wrapper around &lt;code&gt;cpgqls_client&lt;/code&gt;, which talks to a Joern server running locally (&lt;code&gt;joern --server&lt;/code&gt;, listening on &lt;code&gt;localhost:8080&lt;/code&gt; by default).&lt;/p&gt;

&lt;p&gt;You point it at a local folder, it imports the repo, and from then on you can fire CPGQL queries at it. (This is done by &lt;a href="https://github.com/shaktiwadekar9/code-graph-ai-summarizer" rel="noopener noreferrer"&gt;code-graph-ai-summarizer&lt;/a&gt; so you don’t have to)&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Why a graph and not just an AST per file?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Because the interesting questions about a codebase are inherently cross-file: “what calls this function,” “where does this value end up,” “which file is the most central.”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Those are graph-traversal questions, not single-file parsing questions.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The CPG gives you one graph spanning the entire repo, so those questions become tractable.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Stage 2 : Asking the graph the right questions
&lt;/h2&gt;

&lt;p&gt;A CPG by itself is just a big graph sitting in memory.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The value comes from the specific queries you run against it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;joern/queries.py&lt;/code&gt; defines six of them, and reading through them is basically a mini-lesson in “what does a useful static analysis tool actually need to know about a codebase.”&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Query and What it extracts:&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;code&gt;files&lt;/code&gt; : Every source file in the repo&lt;/p&gt;

&lt;p&gt;&lt;code&gt;methods&lt;/code&gt;: Every function/method, with file, full name, signature, line&lt;/p&gt;

&lt;p&gt;&lt;code&gt;types&lt;/code&gt;: Every class/type declared&lt;/p&gt;

&lt;p&gt;&lt;code&gt;call_edges&lt;/code&gt;: For each method, which other methods it calls (internal + external)&lt;/p&gt;

&lt;p&gt;&lt;code&gt;calls&lt;/code&gt;: Every individual call site, with its code text&lt;/p&gt;

&lt;p&gt;&lt;code&gt;entry_candidates&lt;/code&gt;: Methods that look like entry points&lt;/p&gt;

&lt;p&gt;&lt;code&gt;source_sink_calls&lt;/code&gt;: Calls that look like data sources or data sinks&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;entry_candidates&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The &lt;code&gt;entry_candidates&lt;/code&gt; query is critical. There’s no universal CPGQL way to say “find the main function” across Python, JS, Go, etc.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;So the repo uses a name/filename heuristic instead:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;val entryRe = "(?i).*(main|run|start|serve|handler|handle|route|controller|command|execute|process|consume|worker|app).*"

cpg.method
  .filterNot(_.isExternal)
  .filter(m =&amp;gt; m.name.matches(entryRe) || m.filename.matches(entryRe))
  .take(maxItems)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;One more detail worth noticing:&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;code&gt;joern/client.py&lt;/code&gt; wraps every single query in a &lt;code&gt;try/except&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;for name, query in joern_queries(max_items).items():
    try:
        facts[name] = self.run_json_query(name, query)
    except Exception as exc:
        print(f"[warn] Joern query failed: {name}: {exc}")
        facts[name] = []
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If one query fails (say, the data-flow query isn’t supported for a given language overlay), the pipeline doesn’t crash, it just records an empty result and moves on.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Stage 3 : From raw facts to ranked signal (the Python analysis layer)
&lt;/h2&gt;

&lt;p&gt;Joern hands back raw lists: &lt;strong&gt;every file, every method, every call&lt;/strong&gt;. That’s hundreds or thousands of items, too much, too unstructured, and too noisy to throw straight at an LLM.&lt;/p&gt;

&lt;p&gt;This is where the repo’s &lt;code&gt;analysis/&lt;/code&gt; package comes in.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Its whole job is compression with judgment: turning a flood of graph facts into a small set of ranked, labeled signals.&lt;/code&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  5.1 Classifying what code “is”, without a single import statement
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;analysis/patterns.py&lt;/code&gt; defines keyword buckets for common categories: &lt;code&gt;api_web&lt;/code&gt;, &lt;code&gt;cli&lt;/code&gt;, &lt;code&gt;storage_db&lt;/code&gt;, &lt;code&gt;filesystem&lt;/code&gt;, &lt;code&gt;llm&lt;/code&gt;, &lt;code&gt;network&lt;/code&gt;, &lt;code&gt;auth&lt;/code&gt;, &lt;code&gt;queue_worker&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A snippet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CATEGORY_PATTERNS = {
    "storage_db": [
        "sqlite", "postgres", "mysql", "mongodb", "redis", "sqlalchemy",
        "save", "insert", "update", "delete", "select", "execute", "commit", "query",
    ],
    "llm": [
        "openai", "ollama", "anthropic", "gemini", "groq", "cerebras",
        "completion", "chat.completions", "llm", "model", "generate",
    ],
    ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;analysis/classify.py&lt;/code&gt; then just checks whether any of these substrings show up in a call's name/code/target text.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This is deliberately simple:&lt;/strong&gt; no embeddings, no ML model, just substring matching. Reason: it's fast, debuggable, language-agnostic, and “good enough” because its output isn't the final answer, it's a signal that downstream ranking and the LLM will further interpret.&lt;/p&gt;

&lt;p&gt;Don't reach for a heavyweight model when a keyword list solves 90% of the problem at near-zero cost.&lt;/p&gt;




&lt;h3&gt;
  
  
  5.2 Finding the repo’s “important” files
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;analysis/architecture.py&lt;/code&gt; turns the call-edge facts into a per-file importance score.&lt;/p&gt;

&lt;p&gt;The logic, simplified:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A file gets points for every internal call it makes (it’s doing things)&lt;/li&gt;
&lt;li&gt;A file gets more points when other files call into it (it’s depended on)&lt;/li&gt;
&lt;li&gt;A file gets points whenever its calls match one of the category patterns above (it touches storage, network, LLMs, etc.)
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;file_scores[caller_file] += len(internal_callees) + len(external_callees)
...
file_edge_counts[(caller_file, callee_file)] += 1
file_scores[callee_file] += 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Sort by score&lt;/strong&gt;, and you get a ranked list of “central files”, a cheap but effective proxy for architectural importance.&lt;/p&gt;




&lt;h3&gt;
  
  
  5.3 Finding runtime flows: graph traversal, not guesswork
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;This is the most conceptually interesting part of the repo.&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;analysis/flows.py&lt;/code&gt; runs a breadth-first search (BFS) starting from each entry-point candidate, walking forward through the call graph, and scoring every path it finds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;entry_point
   -&amp;gt; calls method A
        -&amp;gt; calls method B  (touches "storage_db")
             -&amp;gt; calls method C  (touches "llm")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;queue = deque([[entry]])
while queue and seen_paths &amp;lt; 80:
    path = queue.popleft()
    if len(path) &amp;gt;= 3:
        score, signals = path_score(path, method_to_file)
        if signals:
            candidates.append(runtime_candidate(...))
    if len(path) &amp;gt;= 5:
        continue
    for next_method in graph.get(path[-1], [])[:12]:
        if next_method not in path:
            queue.append(path + [next_method])
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each path’s score (&lt;code&gt;analysis/graph.py&lt;/code&gt;) rewards length and, much more heavily, rewards touching “important” categories like &lt;code&gt;api_web&lt;/code&gt;, &lt;code&gt;storage_db&lt;/code&gt;, or &lt;code&gt;llm&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;score = len(path) + 4 * len(set(categories))
important = {"api_web", "storage_db", "filesystem", "llm", "network", "auth", "queue_worker"}
score += 5 * len(important.intersection(categories))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;In plain English:&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;code&gt;A path that goes from an entry point all the way to a database call or an LLM call is more “interesting” than a path that just bounces between two utility functions.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;That’s a simple but effective heuristic.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;find_data_flows&lt;/code&gt; is the mirror image: instead of starting from entry points, it starts from calls that look like &lt;strong&gt;data sources&lt;/strong&gt; (&lt;code&gt;request&lt;/code&gt;, &lt;code&gt;input&lt;/code&gt;, &lt;code&gt;argv&lt;/code&gt;, &lt;code&gt;env&lt;/code&gt;, ...) and BFS-searches forward until it reaches calls that look like data sinks (&lt;code&gt;write&lt;/code&gt;, &lt;code&gt;save&lt;/code&gt;, &lt;code&gt;insert&lt;/code&gt;, &lt;code&gt;chat&lt;/code&gt;, &lt;code&gt;post&lt;/code&gt;, ...).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;source: read user input
    |
    v
  [ some processing methods ]
    |
    v
sink: save to DB / send to LLM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Important nuance the README states explicitly and the code backs up: &lt;strong&gt;these are graph-derived candidates, not proven runtime traces.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Joern is doing static analysis, it never executes the code.&lt;/p&gt;

&lt;p&gt;A BFS path through the call graph is a plausible flow, not a guaranteed one.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Stage 4 : Compacting everything into one fact sheet
&lt;/h2&gt;

&lt;p&gt;All of the analysis above gets assembled in &lt;code&gt;summarization/facts_builder.py&lt;/code&gt; into a single &lt;code&gt;summary_facts&lt;/code&gt; dictionary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def build_summary_facts(repo_path: Path, facts: dict) -&amp;gt; dict:
    repo_map = build_repo_map(facts.get("files", []))
    architecture = derive_architecture(facts)

    return {
            "repo_name": repo_path.name,
            "repo_map": repo_map,
            "architecture_signals": architecture,
            "entry_points": facts.get("entry_candidates", [])[:40],
            "critical_runtime_flow_candidates": find_runtime_flows(facts),
            "critical_data_flow_candidates": find_data_flows(facts),
            "important_symbols": important_symbols(facts, architecture),
            "limits": {"note": "This is static analysis. Runtime/data flows are graph-derived candidates, not guaranteed actual production traces."},
        }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Notice what’s not in here:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;raw source code,&lt;/li&gt;
&lt;li&gt;full call lists,&lt;/li&gt;
&lt;li&gt;every method in the repo.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;code&gt;important_symbols&lt;/code&gt; is deliberately filtered down to only the methods/types that live in the already-identified “central files”, another compression step that keeps the eventual LLM prompt small and focused.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;This dictionary, not the repo itself, is what the LLM will actually see.&lt;/code&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Stage 5 : Writing the prompt like a contract, not a suggestion
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;summarization/prompts.py&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It builds the final prompt, and it’s worth reading closely because it shows how to constrain an LLM rather than just hope it behaves:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;return f"""
You are generating a repository summary using Joern Code Property Graph facts.

Use only the supplied graph facts.
Do not invent files, folders, APIs, tests, classes, functions, runtime flows, or data flows.
Separate detected facts from inferred conclusions.
For runtime flows and data flows, include only the critical ones, not every path.
If something is weakly supported, say "likely".
If something is not supported, say "not detected".

Return Markdown with exactly these sections:
# Repository Summary
## 1. Repository Purpose
## 2. Repository Map
## 3. Architecture
## 4. Critical Runtime Flows
## 5. Critical Data Flows
## 6. Important Files
## 7. Important Symbols
## 8. Not Detected / Unknown
...
"""
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;A few important details:&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Closed-world instruction&lt;/strong&gt;: “use only the supplied facts” + “do not invent X, Y, Z” is the single highest-leverage anti-hallucination instruction you can give a model. It can’t promise zero hallucination, but combined with a small, accurate fact-sheet, it dramatically narrows the model’s room to wander.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Calibrated language is mandated, not optional&lt;/strong&gt;: forcing the model to say “likely” or “not detected” instead of asserting things flatly turns confidence into a visible, checkable signal for the reader.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A fixed output schema&lt;/strong&gt;: by naming the exact eight sections, the output is predictable and easy to parse, render, or diff across repos. Useful if you ever want to compare summaries over time or build a UI on top of this.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An explicit “Not Detected / Unknown” section&lt;/strong&gt;: most prompts ask a model what it knows; this one also asks it to state what it doesn’t know. That’s a small change with an outsized effect on trustworthiness.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;llm/client.py&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;code&gt;llm/client.py&lt;/code&gt; then does the boring-but-important part: it’s a thin OpenAI-SDK wrapper that works with any OpenAI-compatible endpoint: Groq, OpenRouter, Gemini’s OpenAI-compatible endpoint, or Cerebras, controlled purely through &lt;code&gt;.env&lt;/code&gt; config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LLM_PROVIDER=groq
LLM_API_KEY=your_api_key_here
LLM_MODEL=llama-3.3-70b-versatile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def generate_repo_summary(summary_facts: dict, config: LLMConfig) -&amp;gt; str:
    client = make_client(config)
    response = client.chat.completions.create(
        model=config.model,
        temperature=config.temperature,
        max_tokens=config.max_tokens,
        messages=[
            {"role": "system", "content": "You are a precise static-analysis repo summarizer. You must not hallucinate unsupported repo facts."},
            {"role": "user", "content": build_summary_prompt(summary_facts)},
        ],
    )
    return response.choices[0].message.content or ""
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  8. Putting it all together: what actually happens when you run it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;uv run code-graph-ai-summarizer /path/to/local/repo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Walking through &lt;code&gt;run()&lt;/code&gt; in &lt;code&gt;cli/main.py&lt;/code&gt; function end to end:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Make the output directory &lt;code&gt;outputs/&amp;lt;repo-name&amp;gt;/&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;JoernRunner.import_repo(...)&lt;/code&gt;: Joern parses your repo into a CPG.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;joern.collect_facts(max_items)&lt;/code&gt; : the six CPGQL queries (+ the optional data-flow query) run, each wrapped in its own try/except.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;build_summary_facts(...)&lt;/code&gt; : repo map, architecture signals, runtime/data flow candidates, important symbols all get derived and compacted.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;generate_repo_summary(...)&lt;/code&gt; : the compact fact JSON goes into the locked-down prompt, and the LLM writes the final Markdown.&lt;/li&gt;
&lt;li&gt;Three files land in &lt;code&gt;outputs/&amp;lt;repo-name&amp;gt;/&lt;/code&gt; . First, &lt;code&gt;joern_facts.json&lt;/code&gt;: the raw graph extraction (for debugging / inspection). Second, &lt;code&gt;summary_facts.json&lt;/code&gt; : the compacted, ranked fact-sheet (what the LLM actually saw).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Third, repo_summary.md: the final human-readable summary.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;outputs/&amp;lt;repo-name&amp;gt;/
├── joern_facts.json     &amp;lt;- raw, large, exact
├── summary_facts.json   &amp;lt;- compact, ranked, curated
└── repo_summary.md      &amp;lt;- narrated, by the LLM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  9. Why this design generalizes beyond “summarizing a repo”
&lt;/h2&gt;

&lt;p&gt;The specific use case here is repo summarization, but the underlying pattern is broadly applicable to anyone building tools on top of LLMs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Don’t hand an LLM a haystack and ask it to find the needle&lt;/strong&gt;. Use a purpose-built tool (a parser, a graph engine, a database, a search index) to find candidate needles first.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rank and compress before you prompt&lt;/strong&gt;. The smaller and more relevant the context, the less room there is for hallucination, and the cheaper/faster the call.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep every intermediate artifact&lt;/strong&gt;. &lt;code&gt;joern_facts.json&lt;/code&gt; and &lt;code&gt;summary_facts.json&lt;/code&gt; aren’t just debug exhaust, &lt;strong&gt;&lt;em&gt;they’re what let you trust, or distrust, the final output&lt;/em&gt;&lt;/strong&gt; with evidence rather than vibes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fail softly at each stage&lt;/strong&gt;. One bad query or one weird file shouldn’t take down the whole pipeline.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  10. Trying it yourself
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git clone &amp;lt;your-repo-url&amp;gt;
cd code-graph-ai-summarizer
uv sync
cp .env.example .env
# edit .env: set LLM_PROVIDER, LLM_API_KEY, LLM_MODEL

# in a separate terminal
joern --server

# in a separate terminal (if using ollama)
ollama serve

# back in your main terminal
uv run code-graph-ai-summarizer /path/to/any/local/repo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Point it at a small repo first, and get &lt;code&gt;repo_summary.md&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  11. A closing note:
&lt;/h2&gt;

&lt;p&gt;Full credit where it’s due: I didn’t write this by hand, line by line, heroically, at 2 AM, fueled by coffee.&lt;/p&gt;

&lt;p&gt;I played as an orchestrator, pointing ChatGPT and Claude at the problem, arguing with them when they hallucinated a function that didn’t exist, and stitching their outputs into &lt;strong&gt;something that actually runs and is a useful application&lt;/strong&gt;. They wrote the code, I supplied the opinions, the rejections, and the “no, that’s not what I meant” loop until it converged.&lt;/p&gt;

&lt;p&gt;So consider &lt;a href="https://github.com/shaktiwadekar9/code-graph-ai-summarizer" rel="noopener noreferrer"&gt;this repo&lt;/a&gt; a small case study in human + AI pair programming, minus the part where the AI gets annoyed at my code review comments.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>agents</category>
      <category>llm</category>
    </item>
    <item>
      <title>Don’t Burn Claude Tokens: A Free, Local, Secure Way to Explore Your Code First</title>
      <dc:creator>Shakti Wadekar</dc:creator>
      <pubDate>Mon, 22 Jun 2026 07:25:50 +0000</pubDate>
      <link>https://dev.to/shaktiwadekar/dont-burn-claude-tokens-a-free-local-secure-way-to-explore-your-code-first-22f3</link>
      <guid>https://dev.to/shaktiwadekar/dont-burn-claude-tokens-a-free-local-secure-way-to-explore-your-code-first-22f3</guid>
      <description>&lt;p&gt;Every time you point Claude or ChatGPT at an unfamiliar codebase and ask “where is the auth logic?”, you’re spending real money and context just to get oriented.&lt;/p&gt;

&lt;p&gt;The model has to read files it’s never seen, guess at structure, and you’re paying API tokens (or burning your message limit) for what is essentially a Google-Maps-level question, not a hard reasoning question.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://github.com/shaktiwadekar9/talk-to-your-code" rel="noopener noreferrer"&gt;&lt;code&gt;talk-to-your-code&lt;/code&gt;&lt;/a&gt; is a neat fix for this. It's a small local app that lets you index a codebase on your own machine and ask it plain-English questions, using a small local model through Ollama instead of a cloud API.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;The idea isn't to replace Claude, but to do the cheap, repetitive "let me look around first" work locally and for free, so that by the time you do talk to Claude, you already know which files matter and can ask a sharp, narrow question.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The big picture: what actually happens
&lt;/h2&gt;

&lt;p&gt;At a high level, the app does four things in sequence:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It walks your repo and breaks files into chunks,&lt;/li&gt;
&lt;li&gt;It stores those chunks in a local database,&lt;/li&gt;
&lt;li&gt;It lets an LLM figure out which chunks are relevant to your question. (Context building)&lt;/li&gt;
&lt;li&gt;It lets the LLM answer using only those chunks.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;&amp;gt; Ingests any repository&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0h13bbnhefokiinpkp7z.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%2F0h13bbnhefokiinpkp7z.png" alt="Local repo parsing, chunking and saving to local database" width="799" height="235"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This happens once, when you click “Ingest.” After that, everything else like the actual conversation, runs against this local database, not against the raw files.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;That matters: it means each question is fast and cheap, because the app isn’t re-reading your whole repo every time you ask something.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&amp;gt; What happens when you actually ask a question&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa30cbz5dhv31rtwgma68.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%2Fa30cbz5dhv31rtwgma68.png" alt="How user query is handled" width="800" height="424"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here’s what each box is doing in practice:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Build a query plan&lt;/strong&gt;. Your raw question gets turned into something a retrieval system can actually use: keywords to search for, files it suspects are relevant, and what kind of question this is (explain vs debug vs locate).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Hybrid retrieval&lt;/strong&gt;. “Hybrid” here means combining different ways of finding relevant chunks: keyword, symbol matching (good for “find the function named authenticate") and embedding similarity search (good for "find code that does something like this," even without exact wording). Using both catches more relevant code than either alone.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Context builder with a budget&lt;/strong&gt;. This is the unglamorous but critical step. You can’t just hand the LLM every chunk that matched. The context windows are finite, and stuffing in irrelevant code wastes tokens and confuses the model. So the builder ranks chunks by relevance and packs in as many as fit under a character/token limit you control (that’s the “context length slider” in the UI).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Generate a structured answer&lt;/strong&gt;. The final LLM call gets the user’s question plus the packed context, and is asked to generate answer with a certain schema. [Example]&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class StructuredAnswer(BaseModel):
    summary: str
    relevant_files: list[str]
    confidence: str
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s it, two structured LLM calls, with a plain retrieval step sandwiched in between. &lt;strong&gt;No agents looping indefinitely, no unbounded tool calls. It’s a deliberately small, predictable pipeline.&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Important detail:&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When you type a question like “where is authentication handled?”, the app doesn’t just dump your code into a prompt and hope for the best.&lt;/p&gt;

&lt;p&gt;It runs the LLM twice, each time forcing a specific output shape using &lt;strong&gt;structured generation&lt;/strong&gt; (also called constrained decoding), so you get a guaranteed JSON object back instead of free text you’d have to parse and hope is valid.&lt;/p&gt;

&lt;p&gt;In Python, that schema is typically just a Pydantic model, something like this: [Example]&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from pydantic import BaseModel
class QueryPlan(BaseModel):
    keywords: list[str]
    target_files: list[str]
    intent: str  # e.g. "explain", "debug", "locate"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You hand this structured-data to the LLM call and the API guarantees the response matches it. No more “please respond in JSON” prompts that occasionally come back with extra commentary attached.&lt;/p&gt;




&lt;h2&gt;
  
  
  Summary:
&lt;/h2&gt;

&lt;p&gt;A few things &lt;a href="https://github.com/shaktiwadekar9/talk-to-your-code" rel="noopener noreferrer"&gt;this repo&lt;/a&gt; demonstrates cleanly that show up everywhere once you start building with LLMs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Structured generation beats prompting for JSON.&lt;/strong&gt; Defining a Pydantic schema and binding it to the call is more reliable than asking nicely in the prompt and parsing the response with regex.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Separate “finding the right context” from “answering the question.”&lt;/strong&gt; Bundling retrieval and generation into a single giant prompt is how you get hallucinated answers about code that isn’t relevant.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Splitting it into a plan → retrieve → answer pipeline keeps each step’s job small and checkable — you can literally inspect the query plan in the UI before the final answer is generated.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Context budgets aren’t optional&lt;/strong&gt;. Whether you’re calling a 7B local model or Claude through the API, “how much do I actually send” is a real engineering decision, not an afterthought.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Local-first has a real use case&lt;/strong&gt;. It’s not about avoiding Claude, it’s about not exposing a private codebase to an external API for the boring first-pass questions, and not paying API costs for what a free local model can already answer.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once this &lt;a href="https://github.com/shaktiwadekar9/talk-to-your-code" rel="noopener noreferrer"&gt;tool/application&lt;/a&gt; helps you find the right files and understand them, you can ask Claude about the actual fix instead of spending tokens on repo exploration.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>opensource</category>
      <category>python</category>
    </item>
    <item>
      <title>AI Agents in Production: Structured Generation for AI Workflows</title>
      <dc:creator>Shakti Wadekar</dc:creator>
      <pubDate>Sun, 14 Jun 2026 17:56:55 +0000</pubDate>
      <link>https://dev.to/shaktiwadekar/ai-agents-in-production-structured-generation-for-ai-workflows-188h</link>
      <guid>https://dev.to/shaktiwadekar/ai-agents-in-production-structured-generation-for-ai-workflows-188h</guid>
      <description>&lt;p&gt;Structured generation is one of the most important steps in moving AI agents from demos to production systems. In real applications, an agent is not just writing text for a user, it is passing decisions, tool arguments, routing outputs, validation results, and workflow states to other parts of a software pipeline. In this article, we will look at how vLLM helps enforce this structure during generation.&lt;/p&gt;




&lt;h3&gt;
  
  
  📚 Content
&lt;/h3&gt;

&lt;h3&gt;
  
  
  🚀 1. Motivation
&lt;/h3&gt;

&lt;h3&gt;
  
  
  🏭 2. Production Reality
&lt;/h3&gt;

&lt;h3&gt;
  
  
  ⚙️ 3. Structured Generation in vLLM
&lt;/h3&gt;

&lt;h5&gt;
  
  
  🧩 3.1 JSON Schema-Constrained Generation
&lt;/h5&gt;

&lt;h5&gt;
  
  
  🏗️ 3.2 Pydantic Model → JSON Schema Conversion
&lt;/h5&gt;




&lt;h3&gt;
  
  
  🚀 1. Motivation
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Why Structured Generation?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Imagine you built an AI customer support agent. A user sends: “I want to return my order #4821.” Your agent needs to call an internal API to look up the order. That API expects a clean JSON payload:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"order_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"4821"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"return"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"reason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But your LLM, without any constraints, might output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;Sure!&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;I&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;can&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;help&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;with&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;that.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Here&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;request:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"order_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4821&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"return"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"reason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"not specified"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;Let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;me&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;know&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;you&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;need&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;anything&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;else!&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three problems in that one response:&lt;/p&gt;

&lt;p&gt;Extra text wrapped around the JSON,&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;order_id is a number instead of a string,&lt;/li&gt;
&lt;li&gt;reason is "not specified" instead of null.&lt;/li&gt;
&lt;li&gt;Your json.loads() will either crash or your API will reject the payload.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In a demo, you’d just fix this with a try/except and with more prompting.&lt;/p&gt;

&lt;p&gt;In production, the same issue can happen thousands of times a day across multiple agents, tools, and workflows. At that scale, even a 2% formatting failure rate is no longer a small bug, it becomes broken automations, failed handoffs, and real customer impact.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The core problem:&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;LLMs are probabilistic text generators. They predict the most likely next token, they do not inherently “know” that your downstream system needs a strictly-typed JSON object. Even after prompting it with JSON requirements, it might still fail to produce exact required format.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The solution: Structured generation&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Structured generation guides the model to produce outputs that follow a predefined format, such as JSON, a schema, or a set of allowed choices, so the response is easier for your code to validate and use reliably.&lt;/p&gt;




&lt;h3&gt;
  
  
  🏭 2. Production Reality
&lt;/h3&gt;

&lt;p&gt;Production AI agents &lt;strong&gt;operate in pipelines&lt;/strong&gt;. The &lt;strong&gt;LLM output is almost never the final product&lt;/strong&gt;. The LLM output is fed into databases, APIs, other models, or UI components. Each handoff requires the output to conform to a format. Structured generation is how you enforce that format at the generation level.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Here is what structured generation unlocks:&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Cleaner backend integration&lt;/strong&gt; because the LLM output can map directly to typed application models, validation logic, APIs, and databases.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cleaner agent pipelines and more reliable agent handoffs&lt;/strong&gt; because each step can pass structured data to the next step without relying on messy text interpretation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fewer production failures&lt;/strong&gt; because the model is constrained to return valid, expected outputs instead of unpredictable free text.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lower retry and repair cost&lt;/strong&gt; because the system spends less time fixing bad outputs and more time executing the actual workflow.&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  ⚙️ 3. Structured Generation in vLLM
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Introduction&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;vLLM is mainly known as a high-throughput inference and serving engine for LLMs, but it also provides built-in support for constraining model outputs into specific formats.&lt;/p&gt;

&lt;p&gt;In vLLM, structured generation can be used in two common ways: through &lt;code&gt;structured_outputs&lt;/code&gt; and &lt;code&gt;StructuredOutputsParams&lt;/code&gt; for offline inference, or through &lt;code&gt;response_format&lt;/code&gt; / &lt;code&gt;extra_body={"structured_outputs": ...}&lt;/code&gt; when using the OpenAI-compatible API.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Setup&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&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; &lt;span class="nt"&gt;-U&lt;/span&gt; openai vllm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you again get the NumPy Inf error, run:&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; &lt;span class="s2"&gt;"numpy&amp;lt;2"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the following command in the terminal to locally host the model with vLLM.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vllm serve Qwen/Qwen2.5-1.5B-Instruct &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--host&lt;/span&gt; 0.0.0.0 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--port&lt;/span&gt; 8000 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--dtype&lt;/span&gt; auto &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--gpu-memory-utilization&lt;/span&gt; 0.85 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--max-model-len&lt;/span&gt; 4096 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--enable-auto-tool-choice&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--tool-call-parser&lt;/span&gt; hermes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;vllm serve&lt;/code&gt; starts vLLM as a model server. Instead of loading the model inside every notebook run, the model is loaded once in a terminal and kept running. Your notebook code then sends requests to this local server, just like it would send requests to the OpenAI API.&lt;/p&gt;

&lt;p&gt;vLLM exposes an OpenAI-compatible API server, so the normal openai Python client can call it by changing only the &lt;code&gt;base_url&lt;/code&gt; to &lt;code&gt;http://localhost:8000/v1&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Qwen/Qwen2.5-1.5B-Instruct&lt;/code&gt;&lt;br&gt;
This is the Hugging Face model that vLLM will download/load and serve.&lt;/p&gt;

&lt;p&gt;Run the following code. If this prints the model name, vLLM is running correctly.&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;openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAI&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;base_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;http://localhost:8000/v1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;api_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;unused&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h4&gt;
  
  
  🧩 3.1 JSON Schema-Constrained Generation
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The most common use case:&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You define a JSON Schema, and vLLM constrains decoding so the generated text follows that schema.&lt;/p&gt;

&lt;p&gt;Let’s understand this with an example:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Production Use Case: Support Ticket Triage System&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&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;openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAI&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;base_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;http://127.0.0.1:8000/v1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;api_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;EMPTY&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Qwen/Qwen2.5-1.5B-Instruct&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;triage_schema&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;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;object&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;properties&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;urgency&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;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;string&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;enum&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;low&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;medium&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;high&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;critical&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;category&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;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;string&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;enum&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;billing&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;technical&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;returns&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;general&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;customer_id&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;type&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;string&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;null&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;summary&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;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;string&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;maxLength&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&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;required&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;urgency&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;category&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;summary&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;additionalProperties&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;email_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;
Customer #C-4821 says: My payment was charged twice yesterday
and I still haven&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t received any confirmation. This is urgent!
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Analyze this support email and return only JSON.

Email:
&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;email_text&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="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&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;messages&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;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
    &lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;max_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;response_format&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;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;json_schema&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_schema&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;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;support_triage&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;schema&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;triage_schema&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="n"&gt;text&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="n"&gt;choices&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;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text: &lt;/span&gt;&lt;span class="si"&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;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;triage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;triage: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;triage&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="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Urgency: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;triage&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;urgency&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Category: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;triage&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;category&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Summary: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;triage&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;summary&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Expected Output:

text: {"urgency":"high","category":"billing","customer_id":"C-4821","summary":"Customer was charged twice yesterday and has not received confirmation."}

triage: {'urgency': 'high', 'category': 'billing', 'customer_id': 'C-4821', 'summary': 'Customer was charged twice yesterday and has not received confirmation.'}

Urgency: high
Category: billing
Summary: Customer was charged twice yesterday and has not received confirmation.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The important part and what it does?&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;response_format&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;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;json_schema&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_schema&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;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;support_triage&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;schema&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;triage_schema&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;vLLM uses a structured-output backend such as &lt;code&gt;xgrammar&lt;/code&gt; or &lt;code&gt;guidance&lt;/code&gt; to constrain decoding.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;At each generation step invalid next tokens are masked from the model’s logits before sampling.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This makes the model generate text that follows the required structure, such as a JSON schema, regex, choice list, or grammar.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Important nuance:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Structured generation only guarantees structural validity, not that the extracted values are semantically correct.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h4&gt;
  
  
  🏗️ 3.2 Pydantic Model → JSON Schema Conversion
&lt;/h4&gt;

&lt;p&gt;Writing raw JSON Schema objects is &lt;strong&gt;tedious and error-prone&lt;/strong&gt;. Hence use Pydantic.&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;openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAI&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;from&lt;/span&gt; &lt;span class="n"&gt;pydantic&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ConfigDict&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;Literal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;base_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;http://127.0.0.1:8000/v1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;api_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;EMPTY&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Qwen/Qwen2.5-1.5B-Instruct&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;# 1. Define output structure using Pydantic
&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TriageOutput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;model_config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ConfigDict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;extra&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;forbid&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;urgency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Literal&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;low&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;medium&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;high&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;critical&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Literal&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;billing&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;technical&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;returns&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;general&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&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;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# 2. Convert Pydantic model to JSON Schema
&lt;/span&gt;&lt;span class="n"&gt;triage_schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TriageOutput&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;model_json_schema&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;email_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;
Customer #C-4821 says: My payment was charged twice yesterday
and I still haven&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t received any confirmation. This is urgent!
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Analyze this support email and return only JSON.

Email:
&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;email_text&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="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&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;messages&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;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
    &lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;max_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;response_format&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;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;json_schema&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_schema&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;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;support_triage&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;schema&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;triage_schema&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="n"&gt;text&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="n"&gt;choices&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;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text: &lt;/span&gt;&lt;span class="si"&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;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;triage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;triage: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;triage&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="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Urgency: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;triage&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;urgency&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Category: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;triage&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;category&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Summary: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;triage&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;summary&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;vLLM also provides more structured generation options like Regex-Constrained Generation, Grammar-Constrained Generation and Custom Logits Processors. &lt;/p&gt;

&lt;p&gt;An more extended version of this article covers these topics along with examples of Tool-Calling and Routing agent, in my following medium article published in Towards AI. &lt;/p&gt;

&lt;p&gt;Link the article: &lt;a href="https://medium.com/towards-artificial-intelligence/ai-agents-in-production-why-structured-generation-matters-more-than-prompt-engineering-3332666ce0d9" rel="noopener noreferrer"&gt;AI Agents in Production: Why Structured Generation Matters More Than Prompt Engineering&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I hope this article was useful in showing why structured generation is not just a formatting trick, but a practical requirement for production AI agents. When agents are part of real software pipelines, their outputs must be predictable, valid, and easy for downstream systems to use.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>architecture</category>
      <category>agents</category>
    </item>
  </channel>
</rss>
