<?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: Maxim </title>
    <description>The latest articles on DEV Community by Maxim  (@maxim_maxim_16849913c85c4).</description>
    <link>https://dev.to/maxim_maxim_16849913c85c4</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%2F4188383%2F1111e0fb-655c-47e3-ada1-d865606d85f8.png</url>
      <title>DEV Community: Maxim </title>
      <link>https://dev.to/maxim_maxim_16849913c85c4</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/maxim_maxim_16849913c85c4"/>
    <language>en</language>
    <item>
      <title>I treated my LLM like a machine on a factory floor — and it stopped lying</title>
      <dc:creator>Maxim </dc:creator>
      <pubDate>Fri, 16 Oct 2026 10:02:39 +0000</pubDate>
      <link>https://dev.to/maxim_maxim_16849913c85c4/i-treated-my-llm-like-a-machine-on-a-factory-floor-and-it-stopped-lying-10io</link>
      <guid>https://dev.to/maxim_maxim_16849913c85c4/i-treated-my-llm-like-a-machine-on-a-factory-floor-and-it-stopped-lying-10io</guid>
      <description>&lt;p&gt;I didn't start in software. I spent years around real production lines.&lt;/p&gt;

&lt;p&gt;A part goes out against a drawing. Someone checks it. Scrap doesn't move to the next station.&lt;br&gt;
Nobody says "hopefully it's fine".&lt;/p&gt;

&lt;p&gt;Then I started building systems with LLMs — and watched teams do exactly that.&lt;br&gt;
Prompt. Hope. Print the output. Ship.&lt;/p&gt;




&lt;h2&gt;
  
  
  The shift that changed everything
&lt;/h2&gt;

&lt;p&gt;Stop treating the model like a colleague who tries their best.&lt;br&gt;
Treat it like a noisy machine:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Draw a contract&lt;/strong&gt; (what the output must look like)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inspect every unit&lt;/strong&gt; (schema validation)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;If scrap: rework with the defect note&lt;/strong&gt; (repair loop)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;If the machine is broken: turn it off&lt;/strong&gt; (circuit breaker)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's it. Same as QC. Just for tokens instead of steel.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why LLM reliability is its own engineering
&lt;/h2&gt;

&lt;p&gt;Code fails loudly. An LLM fails &lt;strong&gt;politely&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Wrong type? Sometimes valid JSON.&lt;/li&gt;
&lt;li&gt;Missing field? Model says "Sure!" and you're happy until QA.&lt;/li&gt;
&lt;li&gt;Truncated? Might look complete.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So you need more than try/except. You need &lt;strong&gt;gates&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I actually shipped
&lt;/h2&gt;

&lt;p&gt;Small open-source layers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/fabrikalunnogosveta-del/structured-llm-output-guard" rel="noopener noreferrer"&gt;structured-llm-output-guard&lt;/a&gt; — text → typed JSON&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/fabrikalunnogosveta-del/guards-and-laws" rel="noopener noreferrer"&gt;guards-and-laws&lt;/a&gt; — quality gates + domain laws with HARD VETO&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nothing fancy. Boring layers that make models usable in production.&lt;/p&gt;




&lt;h2&gt;
  
  
  Practical tips (if you want the factory mindset)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Write the drawing first.&lt;/strong&gt; If you can't say what "good" looks like, the model can't either.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inspect every unit.&lt;/strong&gt; One shared validator. Not "we'll see in prod".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rework beats recreate.&lt;/strong&gt; Feed the defect note back. Cheaper than a new prompt.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scrap is allowed.&lt;/strong&gt; Better to reject output than ship junk and apologize later.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Measure scrap rate.&lt;/strong&gt; If 20% of outputs fail schema, fix the prompt or the model tier.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;If you can feel where a factory line breaks,&lt;br&gt;
you can feel where an LLM pipeline breaks.&lt;/p&gt;

&lt;p&gt;Same ear. Different material.&lt;/p&gt;

</description>
      <category>python</category>
      <category>ai</category>
      <category>testing</category>
      <category>productivity</category>
    </item>
    <item>
      <title>How I stopped losing 3 hours of GPU work every time a job crashed</title>
      <dc:creator>Maxim </dc:creator>
      <pubDate>Fri, 16 Oct 2026 09:57:19 +0000</pubDate>
      <link>https://dev.to/maxim_maxim_16849913c85c4/how-i-stopped-losing-3-hours-of-gpu-work-every-time-a-job-crashed-3o4i</link>
      <guid>https://dev.to/maxim_maxim_16849913c85c4/how-i-stopped-losing-3-hours-of-gpu-work-every-time-a-job-crashed-3o4i</guid>
      <description>&lt;p&gt;We had a pipeline that could run for hours. GPU time is expensive. A crash at 95%&lt;br&gt;
was the same as a crash at 0% — except we were angrier.&lt;/p&gt;

&lt;p&gt;I got tired of it. Not in the "we need a framework" way. In the "this is stupid" way.&lt;/p&gt;


&lt;h2&gt;
  
  
  What we had before
&lt;/h2&gt;

&lt;p&gt;A long script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;research → script → render (forever) → assemble → done
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the process died in &lt;code&gt;render&lt;/code&gt;, we started over. Every time.&lt;br&gt;
People say "use a job queue". We had workers. We still lost time, because&lt;br&gt;
&lt;strong&gt;progress lived only in RAM&lt;/strong&gt;.&lt;/p&gt;


&lt;h2&gt;
  
  
  The fix (boring on purpose)
&lt;/h2&gt;

&lt;p&gt;After every stage, &lt;strong&gt;write down that you finished it&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;research   [done]
render     [done]   ← crash here? next run skips research+render
assemble   [pending]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;run()&lt;/code&gt; executes pending stages&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;resume()&lt;/code&gt; skips &lt;code&gt;done&lt;/code&gt;, retries &lt;code&gt;failed&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;state is written &lt;strong&gt;atomically&lt;/strong&gt; (tmp file → rename), so a kill mid-write
doesn't corrupt everything&lt;/li&gt;
&lt;li&gt;workers send a &lt;strong&gt;heartbeat&lt;/strong&gt;; watchdog notices when they go silent&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's it. No magic scheduler. Just refusing to forget finished work.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why not "just checkpoint tensors"?
&lt;/h2&gt;

&lt;p&gt;We don't need mid-layer optimizer state for every job. Most of our cost is&lt;br&gt;
&lt;strong&gt;stage-level&lt;/strong&gt; work: research, render, package. Stage checkpoints cover 95% of the pain&lt;br&gt;
with 5% of the complexity.&lt;/p&gt;

&lt;p&gt;Trade-off on purpose: a stage still re-runs from its start. That's acceptable.&lt;br&gt;
Losing the whole pipeline is not.&lt;/p&gt;




&lt;h2&gt;
  
  
  What broke along the way
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Non-atomic state writes.&lt;/strong&gt; Kill during save → corrupted JSON → resume lied to us.
Fixed with write-to-temp + &lt;code&gt;os.replace&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retries that duplicated side effects.&lt;/strong&gt; Stage contract now says: re-entry must be safe
(idempotent), or the stage owns its own local temp dir.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Busy" hung workers.&lt;/strong&gt; Looked alive in the process list. Heartbeat file + staleness check.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Numbers
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Long jobs: tens of minutes to hours&lt;/li&gt;
&lt;li&gt;Before: crash = full re-run&lt;/li&gt;
&lt;li&gt;After: crash = re-run from last finished stage&lt;/li&gt;
&lt;li&gt;Tests: 12 behavioral contracts (resume, fail-fast, atomic state, stale heartbeat)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Practical checklist (copy/paste)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Checkpoint &lt;strong&gt;after each stage&lt;/strong&gt;, not "sometime later"&lt;/li&gt;
&lt;li&gt;[ ] Atomic write (tmp + rename). Kill mid-write must not corrupt state&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;resume()&lt;/code&gt; skips &lt;code&gt;done&lt;/code&gt;, retries &lt;code&gt;failed&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;[ ] Stages should be &lt;strong&gt;idempotent&lt;/strong&gt; on re-entry&lt;/li&gt;
&lt;li&gt;[ ] Heartbeat file + staleness check for hung workers&lt;/li&gt;
&lt;li&gt;[ ] Fail-fast on poison stages&lt;/li&gt;
&lt;li&gt;[ ] Test: crash after stage 1 → resume does stage 2 only&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Rule of thumb
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;If re-running the job costs more than 5 minutes — checkpoint it.&lt;br&gt;
If it costs GPU-hours — checkpoint it &lt;em&gt;today&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Checkpoint I/O is cheap. Re-running 3 hours of GPU is not.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Atomic writes&lt;/strong&gt; matter more than fancy progress bars.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resume is a feature&lt;/strong&gt;, not an ops afterthought.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Heartbeat ≠ "it works".&lt;/strong&gt; It's only "it's alive".&lt;/li&gt;
&lt;li&gt;Fail-fast on poison stages beats "keep going and hope".&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Open-sourced here: &lt;a href="https://github.com/fabrikalunnogosveta-del/checkpointed-task-pipeline" rel="noopener noreferrer"&gt;checkpointed-task-pipeline&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Related reliability pieces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/fabrikalunnogosveta-del/structured-llm-output-guard" rel="noopener noreferrer"&gt;structured-llm-output-guard&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/fabrikalunnogosveta-del/guards-and-laws" rel="noopener noreferrer"&gt;guards-and-laws&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>opensource</category>
      <category>devops</category>
      <category>testing</category>
    </item>
    <item>
      <title>How I fixed silent JSON failures in my LLM pipeline (spoiler: json.loads was the trap)</title>
      <dc:creator>Maxim </dc:creator>
      <pubDate>Fri, 16 Oct 2026 09:27:00 +0000</pubDate>
      <link>https://dev.to/maxim_maxim_16849913c85c4/how-i-fixed-silent-json-failures-in-my-llm-pipeline-spoiler-jsonloads-was-the-trap-1h82</link>
      <guid>https://dev.to/maxim_maxim_16849913c85c4/how-i-fixed-silent-json-failures-in-my-llm-pipeline-spoiler-jsonloads-was-the-trap-1h82</guid>
      <description>&lt;p&gt;I'm an engineer. I build pipelines where an LLM is not a chat toy — it's a part in a machine.&lt;/p&gt;

&lt;p&gt;One evening I was staring at logs. Half the videos went out with broken titles. JSON looked fine. &lt;code&gt;json.loads&lt;/code&gt; didn't crash. The data was garbage anyway.&lt;/p&gt;

&lt;p&gt;That was the first time we fell into this hole. Not the last.&lt;/p&gt;

&lt;h2&gt;
  
  
  The trap
&lt;/h2&gt;

&lt;p&gt;The code looks innocent:&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="n"&gt;data&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;llm_response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nice and short. &lt;strong&gt;And fragile.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;An LLM does not return JSON. It returns &lt;em&gt;text that looks like JSON&lt;/em&gt;. Like a person you asked to fill a form, and they answered: "Well, you see…" — maybe useful, maybe not.&lt;/p&gt;

&lt;p&gt;What we actually got in production:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Markdown fences&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The model wraps JSON in triple-backtick code fences. &lt;code&gt;json.loads&lt;/code&gt; dies immediately. At least that failure is loud.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Politeness&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;Sure! Here is the JSON you asked for:
{"title": "Hello"}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dies again. The model was helpful. The pipeline wasn't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Trailing commas, smart quotes&lt;/strong&gt;&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="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hello"&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;&lt;strong&gt;4. Truncation&lt;/strong&gt;&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="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hello"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"score"&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;&lt;strong&gt;5. The worst one — silent junk&lt;/strong&gt;&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="nl"&gt;"ok"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&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;instead of &lt;code&gt;{"title": "...", "score": 42}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;json.loads&lt;/code&gt; is &lt;strong&gt;happy&lt;/strong&gt;. Valid JSON. Wrong shape. Garbage flows downstream. You notice an hour later. Or never.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two failure modes, not one
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;What happens&lt;/th&gt;
&lt;th&gt;When you see it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Loud failure&lt;/td&gt;
&lt;td&gt;Exception, job dies&lt;/td&gt;
&lt;td&gt;immediately&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Silent failure&lt;/td&gt;
&lt;td&gt;wrong fields / wrong types&lt;/td&gt;
&lt;td&gt;too late&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;try/except handles the first. The second is the one that burns you.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we tried (and what didn't work)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"Just tell the model to always return clean JSON."&lt;/strong&gt; Sometimes helps. A model is not a compiler.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Regex between &lt;code&gt;{&lt;/code&gt; and &lt;code&gt;}&lt;/code&gt;.&lt;/strong&gt; Breaks on nested objects and braces inside strings.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Another pile of if-statements.&lt;/strong&gt; Every new response shape = more ifs. Snowball.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We stopped begging the model and started &lt;strong&gt;inspecting it&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LLM text
   |
   +-- strip markdown fences and "Sure! Here's..."
   +-- normalize quotes / trailing commas
   +-- parse
   +-- validate SCHEMA (types, fields, bounds)
   |
   +-- if fail -&amp;gt; repair loop
   |      re-prompt with the EXACT error
   |      ("score: expected int, got str")
   |
   +-- if API is dying -&amp;gt; circuit breaker
          stop hammering a corpse
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same idea as QC on a factory floor: &lt;strong&gt;make it -&amp;gt; check it -&amp;gt; fix or scrap. No hoping.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I didn't start in software. I spent years around real production. A part goes out against a drawing, gets inspected, and scrap doesn't move down the line. An LLM is a machine too. It just lies with more confidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code (no magic)
&lt;/h2&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;llm_output_guard&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Schema&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;RepairLoop&lt;/span&gt;

&lt;span class="n"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;fields&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;title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Field&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;score&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;min_value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# err = exact validation error from last attempt
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;...,&lt;/span&gt; &lt;span class="n"&gt;hint&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RepairLoop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;schema&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_attempts&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;call_model&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Repair is not "roll the dice again". It is &lt;strong&gt;feed the model its own error&lt;/strong&gt;. In our runs, attempt 2 fixes most type mistakes.&lt;/p&gt;

&lt;p&gt;Separately: &lt;strong&gt;circuit breaker&lt;/strong&gt; — 5 failures -&amp;gt; cooldown -&amp;gt; reset. Otherwise a clever repair loop becomes a hammer that finishes off a dying API.&lt;/p&gt;

&lt;h2&gt;
  
  
  Numbers from our systems (not from a blog)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;49 Instructor injection points in one production pipeline — &lt;strong&gt;all&lt;/strong&gt; through one JSON path&lt;/li&gt;
&lt;li&gt;60+ schema contracts&lt;/li&gt;
&lt;li&gt;12 behavioral tests just for "dirty" LLM outputs&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;finish_reason=length&lt;/code&gt; counts as &lt;strong&gt;failure&lt;/strong&gt;, not success&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When you have dozens of calls, "it'll parse somehow" is not a strategy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Steal this checklist (practical, Monday-morning)
&lt;/h2&gt;

&lt;p&gt;If you only take one thing from this post — take this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] &lt;strong&gt;Never&lt;/strong&gt; &lt;code&gt;json.loads(llm_text)&lt;/code&gt; raw. Strip fences first.&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Always&lt;/strong&gt; validate shape after parse (types + required fields).&lt;/li&gt;
&lt;li&gt;[ ] Treat &lt;code&gt;finish_reason=length&lt;/code&gt; as &lt;strong&gt;failure&lt;/strong&gt;, not success.&lt;/li&gt;
&lt;li&gt;[ ] On schema error: &lt;strong&gt;re-prompt with the exact error&lt;/strong&gt;. Don't roll dice.&lt;/li&gt;
&lt;li&gt;[ ] Put a &lt;strong&gt;circuit breaker&lt;/strong&gt; around the API. 5 fails -&amp;gt; cooldown.&lt;/li&gt;
&lt;li&gt;[ ] Log &lt;em&gt;why&lt;/em&gt; validation failed. Future-you will need it.&lt;/li&gt;
&lt;li&gt;[ ] Test with &lt;strong&gt;ugly outputs&lt;/strong&gt;: fences, prose, trailing commas, wrong types.&lt;/li&gt;
&lt;li&gt;[ ] One shared contract for all call sites. Not 49 special cases.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Quick "before you ship" check
&lt;/h3&gt;

&lt;p&gt;Ask three questions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What happens if the model returns markdown?&lt;/li&gt;
&lt;li&gt;What happens if a field has the wrong type?&lt;/li&gt;
&lt;li&gt;What happens if the API dies mid-run?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the answer is "we crash" or "we ship garbage" — you're not done.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we do not do
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;No sending secrets to random "JSON fixer" services&lt;/li&gt;
&lt;li&gt;No treating &lt;code&gt;json.loads(strip(text))&lt;/code&gt; as enough&lt;/li&gt;
&lt;li&gt;No hiding "almost passed" results&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;LLM is not a JSON API.&lt;/strong&gt; It's a text API. JSON is your job.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Silent failures hurt more than crashes.&lt;/strong&gt; Schema catches what try/except misses.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Repair with the exact error&lt;/strong&gt; beats regenerate-from-scratch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Circuit breaker&lt;/strong&gt; protects you from your own stubbornness.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One contract for every call&lt;/strong&gt; beats 49 special snowflakes.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We published this as small open-source libraries so nobody rebuilds it again:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/fabrikalunnogosveta-del/structured-llm-output-guard" rel="noopener noreferrer"&gt;structured-llm-output-guard&lt;/a&gt; — the safe JSON path&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/fabrikalunnogosveta-del/guards-and-laws" rel="noopener noreferrer"&gt;guards-and-laws&lt;/a&gt; — quality gates + domain laws with hard veto&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/fabrikalunnogosveta-del/checkpointed-task-pipeline" rel="noopener noreferrer"&gt;checkpointed-task-pipeline&lt;/a&gt; — long jobs survive crashes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you run LLMs in production, check your &lt;code&gt;json.loads&lt;/code&gt;. If it has no schema and no repair — you may already be paying for it. Just haven't seen the invoice.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What's the ugliest LLM JSON you've seen in production? I'm collecting war stories.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>llm</category>
      <category>testing</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
