<?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: Andrey Altrouter</title>
    <description>The latest articles on DEV Community by Andrey Altrouter (@altrouter).</description>
    <link>https://dev.to/altrouter</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%2F4074301%2F0600ae16-ff26-459e-8ddd-ace73be1a5a1.png</url>
      <title>DEV Community: Andrey Altrouter</title>
      <link>https://dev.to/altrouter</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/altrouter"/>
    <language>en</language>
    <item>
      <title>Your LLM call works 99% of the time. Your 100-call job fails 63% of the time.</title>
      <dc:creator>Andrey Altrouter</dc:creator>
      <pubDate>Tue, 03 Nov 2026 12:00:02 +0000</pubDate>
      <link>https://dev.to/altrouter/your-llm-call-works-99-of-the-time-your-100-call-job-fails-63-of-the-time-20b4</link>
      <guid>https://dev.to/altrouter/your-llm-call-works-99-of-the-time-your-100-call-job-fails-63-of-the-time-20b4</guid>
      <description>&lt;p&gt;You write a script that summarizes a 100-page PDF, one LLM call per page. It dies on page 71 with a timeout. You run it again, and it starts from page 1. No single part is broken: the API had one bad second, which happens. But the rerun pays for pages 1 to 70 a second time, and its odds of dying are the same as the first run's.&lt;/p&gt;

&lt;h3&gt;
  
  
  One call is a coin toss, a job is a streak
&lt;/h3&gt;

&lt;p&gt;Model APIs bill per &lt;strong&gt;token&lt;/strong&gt;, a chunk of text about three-quarters of an English word. &lt;strong&gt;Input&lt;/strong&gt; tokens are what you send, &lt;strong&gt;output&lt;/strong&gt; tokens are what the model writes back. A call can fail with nobody at fault: a timeout, an overloaded server, an answer your code can't parse. Call the share of calls that succeed &lt;strong&gt;p&lt;/strong&gt;. At p = 99%, a single call is safe enough.&lt;/p&gt;

&lt;p&gt;A job finishes only if every call in it succeeds, so the odds multiply: a job of N calls finishes with probability p to the power N. Two calls at 99%: 0.99 × 0.99 ≈ 98%. A hundred calls: 0.99¹⁰⁰ = 36.6%, so the job fails in 63% of runs. At 95% per call, a hundred-call job finishes 0.6% of the time. Both rates are my assumptions, not measurements: 99% is a call that fails only on network trouble, 95% is closer to one whose answer must also parse as JSON. Short jobs barely notice: five calls at 99% still finish 95% of the time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Starting over pays for finished pages again
&lt;/h3&gt;

&lt;p&gt;What a failure costs depends on what your code does next. Start again from call 1, and every call before the failure is bought again, as many times as it takes. Retry only the call that failed, and nothing finished is paid for twice. Here are both, for jobs of 20 and 100 calls:&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;PRICE_IN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PRICE_OUT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;2.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;12.00&lt;/span&gt;                   &lt;span class="c1"&gt;# GPT-5.6 list price, $ per 1M tokens
&lt;/span&gt;&lt;span class="n"&gt;CALL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1_000&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;PRICE_IN&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;250&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;PRICE_OUT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;1e6&lt;/span&gt;   &lt;span class="c1"&gt;# one page: $0.005
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;job&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;finish&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;                                 &lt;span class="c1"&gt;# every call has to succeed
&lt;/span&gt;    &lt;span class="n"&gt;start_over&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;finish&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;finish&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# calls paid, restarting from call 1
&lt;/span&gt;    &lt;span class="n"&gt;retry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;                                   &lt;span class="c1"&gt;# calls paid, retrying the failed call
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;finish&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start_over&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;retry&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.99&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.95&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.99&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.95&lt;/span&gt;&lt;span class="p"&gt;)]:&lt;/span&gt;
    &lt;span class="n"&gt;finish&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start_over&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;retry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;job&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; calls at &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: finishes &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;finish&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;5.1&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
          &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;start over $&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;start_over&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;CALL&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;5.2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, retry $&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;retry&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;CALL&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A page is 1,000 tokens in and 250 out. On GPT-5.6 at its &lt;strong&gt;list price&lt;/strong&gt;, the one the vendor publishes, that's $2 per million input tokens and $12 per million output: $0.005 a page, $0.50 for the PDF. The prices come from the catalog of the LLM gateway I work on, read on 29 October 2026. The &lt;code&gt;start_over&lt;/code&gt; line is the textbook formula for the expected wait until N successes in a row.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Job&lt;/th&gt;
&lt;th&gt;Finishes in one run&lt;/th&gt;
&lt;th&gt;Start over from call 1&lt;/th&gt;
&lt;th&gt;Retry only the failed call&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;20 calls, 99% each&lt;/td&gt;
&lt;td&gt;81.8%&lt;/td&gt;
&lt;td&gt;$0.11, ×1.11&lt;/td&gt;
&lt;td&gt;$0.10, ×1.01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20 calls, 95% each&lt;/td&gt;
&lt;td&gt;35.8%&lt;/td&gt;
&lt;td&gt;$0.18, ×1.79&lt;/td&gt;
&lt;td&gt;$0.11, ×1.05&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100 calls, 99% each&lt;/td&gt;
&lt;td&gt;36.6%&lt;/td&gt;
&lt;td&gt;$0.87, ×1.73&lt;/td&gt;
&lt;td&gt;$0.51, ×1.01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100 calls, 95% each&lt;/td&gt;
&lt;td&gt;0.6%&lt;/td&gt;
&lt;td&gt;$16.79, ×33.6&lt;/td&gt;
&lt;td&gt;$0.53, ×1.05&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each × compares with a run without errors: $0.10 for 20 pages, $0.50 for 100. Retrying costs the same small extra at any length, because you pay for N ÷ p calls. Starting over grows with the job. At 95%, the 100-page PDF takes 169 runs on average, and each failed run dies about 19 pages in: 3,358 paid calls, $16.79 for a $0.50 job. By hand you'd quit after a few runs, with nothing saved.&lt;/p&gt;

&lt;h3&gt;
  
  
  Retry the page, keep the pages
&lt;/h3&gt;

&lt;p&gt;The fix has two parts. Retry the call that failed, a couple of times with a growing pause, instead of failing the whole job. And write each answer to disk as soon as you have it. Saved progress like this is a &lt;strong&gt;checkpoint&lt;/strong&gt;: if the job dies anyway, the next run reads the file, skips finished pages and starts from the one that failed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run_job&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pages&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;span class="n"&gt;out&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;pathlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;results.jsonl&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;attempts&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;done&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;                      &lt;span class="c1"&gt;# an earlier run already paid for these
&lt;/span&gt;        &lt;span class="n"&gt;done&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;page&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;splitlines&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;done&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;continue&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attempts&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="n"&gt;answer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;call_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="k"&gt;break&lt;/span&gt;
                &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;         &lt;span class="c1"&gt;# narrow this to timeouts and server errors
&lt;/span&gt;                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;attempts&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                        &lt;span class="k"&gt;raise&lt;/span&gt;             &lt;span class="c1"&gt;# give up; the next run starts from this page
&lt;/span&gt;                    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;page&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;answer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flush&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;                     &lt;span class="c1"&gt;# on disk before the next paid call
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;call_model&lt;/code&gt; is your own function: it sends one page to the model and returns the answer as text. Each line of &lt;code&gt;results.jsonl&lt;/code&gt; is one finished page, so a crash costs at most the call in progress, and the next run carries on from there. The job now costs N ÷ p calls at any length: 105 for the PDF at 95%.&lt;/p&gt;

&lt;p&gt;Why not catch the error and skip the page? The job would always finish and never pay twice, but at 95% about 5 pages in 100 would be quietly missing, and the summary would have holes you can't see.&lt;/p&gt;

&lt;h3&gt;
  
  
  What this doesn't solve
&lt;/h3&gt;

&lt;p&gt;A page too long for the model fails on every attempt: let the job stop, fix the cause, and the checkpoint keeps the rest. The math treats failures as independent, but in an outage they come together; that's why the snippet waits longer each time and then gives up. Retry only &lt;strong&gt;idempotent&lt;/strong&gt; steps, ones that are safe to run twice: a call that also emails a customer isn't. And no gateway can do this for you, including the one I work on: it sees single calls, not your job, so it can't know which 70 pages you already have.&lt;/p&gt;

&lt;h3&gt;
  
  
  The takeaway
&lt;/h3&gt;

&lt;p&gt;A job of N calls finishes with probability p to the power N. Make one call, not the whole job, the thing you retry, and save each answer before you pay for the next.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>python</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Your LLM cost log says $0.00. The invoice says $1,320.</title>
      <dc:creator>Andrey Altrouter</dc:creator>
      <pubDate>Mon, 02 Nov 2026 12:00:02 +0000</pubDate>
      <link>https://dev.to/altrouter/your-llm-cost-log-says-000-the-invoice-says-1320-2lkk</link>
      <guid>https://dev.to/altrouter/your-llm-cost-log-says-000-the-invoice-says-1320-2lkk</guid>
      <description>&lt;p&gt;You log what each LLM call costs, right next to the request, in a &lt;code&gt;DECIMAL(10,2)&lt;/code&gt; column like every other amount of money in your app. A month later the dashboard says the new feature cost $0.00. The vendor's invoice says $1,320. Nothing leaked. One call cost less than half a cent, and your database rounded it away.&lt;/p&gt;

&lt;h3&gt;
  
  
  A cent is too big for one LLM call
&lt;/h3&gt;

&lt;p&gt;Model APIs bill per &lt;strong&gt;token&lt;/strong&gt;, a chunk of text about three-quarters of an English word. &lt;strong&gt;Input&lt;/strong&gt; tokens are what you send and &lt;strong&gt;output&lt;/strong&gt; tokens are what the model writes back; output costs more. Prices are quoted per million tokens, and the ones vendors publish themselves are called &lt;strong&gt;list prices&lt;/strong&gt;. Every response also carries a &lt;strong&gt;&lt;code&gt;usage&lt;/code&gt;&lt;/strong&gt; field: the exact number of input and output tokens you were billed for.&lt;/p&gt;

&lt;p&gt;Take a modest call: 1,000 tokens in, 200 out. On GPT-5.6, at $2 per million input tokens and $12 per million output, that's $0.002 + $0.0024 = $0.0044. A money column keeps two digits after the point, so anything under half a cent becomes $0.00. PostgreSQL does exactly that when you insert 0.0044 into a &lt;code&gt;DECIMAL(10,2)&lt;/code&gt; column, and raises no error. The habit comes from payments, where an amount is a whole number of cents. A single LLM call is usually a fraction of one.&lt;/p&gt;

&lt;h3&gt;
  
  
  Four of five models log as free
&lt;/h3&gt;

&lt;p&gt;I priced the same call on five models. List prices come from the catalog of the LLM gateway I work on, read on 27 October 2026. A month here is 10,000 calls a day for 30 days.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model, list price per 1M in / out&lt;/th&gt;
&lt;th&gt;One call costs&lt;/th&gt;
&lt;th&gt;Logged as&lt;/th&gt;
&lt;th&gt;Month, spent&lt;/th&gt;
&lt;th&gt;Month, logged&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;claude-haiku-5.5, $0.10 / $0.50&lt;/td&gt;
&lt;td&gt;$0.0002&lt;/td&gt;
&lt;td&gt;$0.00&lt;/td&gt;
&lt;td&gt;$60&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gpt-5.6-mini, $0.20 / $1.20&lt;/td&gt;
&lt;td&gt;$0.00044&lt;/td&gt;
&lt;td&gt;$0.00&lt;/td&gt;
&lt;td&gt;$132&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gemini-3.8-flash, $0.75 / $3.75&lt;/td&gt;
&lt;td&gt;$0.0015&lt;/td&gt;
&lt;td&gt;$0.00&lt;/td&gt;
&lt;td&gt;$450&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gpt-5.6, $2 / $12&lt;/td&gt;
&lt;td&gt;$0.0044&lt;/td&gt;
&lt;td&gt;$0.00&lt;/td&gt;
&lt;td&gt;$1,320&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;claude-opus-5.5, $4 / $20&lt;/td&gt;
&lt;td&gt;$0.008&lt;/td&gt;
&lt;td&gt;$0.01&lt;/td&gt;
&lt;td&gt;$2,400&lt;/td&gt;
&lt;td&gt;$3,000&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Four models log as free. The fifth, Claude Opus 5.5, logs 25% too much, because $0.008 rounds up to a whole cent. Don't count on the errors cancelling out: one feature sends calls of roughly the same size, so they all round the same way. Your vendor's dashboard still shows the right total, but it can't tell you which feature or which user spent it. Only your log can, and here it says nobody did.&lt;/p&gt;

&lt;p&gt;The log can also jump when almost nothing changed. On GPT-5.6 with 200 output tokens, a call with 1,200 input tokens costs $0.0048 and logs as $0.00; with 1,400 it costs $0.0052 and logs as $0.01. Make your instructions 200 tokens longer, and the monthly line goes from $0 to $3,000 while the real bill moves from $1,440 to $1,560.&lt;/p&gt;

&lt;h3&gt;
  
  
  Store millionths, round once
&lt;/h3&gt;

&lt;p&gt;The fix is to round where you read, not where you write. Two ways work. Store the raw facts, the model name and the two token counts from &lt;code&gt;usage&lt;/code&gt;, and turn them into money when you build the report; when a price changes, you can redo the math. Or store the cost as a whole number of &lt;strong&gt;micro-dollars&lt;/strong&gt;, millionths of a dollar. Tokens times the price per million already gives micro-dollars: 1,000 × $0.20 + 200 × $1.20 is 440 micro-dollars for one GPT-5.6 mini call.&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;PRICES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  &lt;span class="c1"&gt;# list price, USD per 1M tokens: (input, output), 27 Oct 2026
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claude-haiku-5.5&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.50&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-5.6-mini&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.20&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gemini-3.8-flash&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.75&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;3.75&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-5.6&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;2.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;12.00&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claude-opus-5.5&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;4.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;20.00&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;cost_micro_usd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input_tokens&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;output_tokens&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="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Cost of one call in millionths of a dollar. Store this, not cents.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;price_in&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;price_out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PRICES&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input_tokens&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;price_in&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;output_tokens&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;price_out&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;CALLS_A_DAY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10_000&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;PRICES&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;micro&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;cost_micro_usd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1_000&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="n"&gt;logged&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;micro&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1_000_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;CALLS_A_DAY&lt;/span&gt;  &lt;span class="c1"&gt;# round each call, then add
&lt;/span&gt;    &lt;span class="n"&gt;spent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;micro&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;CALLS_A_DAY&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1_000_000&lt;/span&gt;             &lt;span class="c1"&gt;# add, then convert once
&lt;/span&gt;    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; logged $&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;logged&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;7.2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;   spent $&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;spent&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;7.2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;   a day&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;It prints $0.00 logged against $2.00, $4.40, $15.00 and $44.00 actually spent a day on the first four models, and $100 against $80 on Opus. In your app, feed it the two counts from each response's &lt;code&gt;usage&lt;/code&gt;: &lt;code&gt;prompt_tokens&lt;/code&gt; and &lt;code&gt;completion_tokens&lt;/code&gt; in OpenAI-style APIs. In the database, that's a &lt;code&gt;BIGINT&lt;/code&gt; (64-bit integer) column, say &lt;code&gt;cost_micro_usd&lt;/code&gt;, which holds up to $9.2 trillion. A float column would keep the fractions too, but floats added in a different order can give slightly different totals; integers add up exactly.&lt;/p&gt;

&lt;h3&gt;
  
  
  What this doesn't solve
&lt;/h3&gt;

&lt;p&gt;An exact sum of your rows still isn't the invoice. It's only as right as your token counts and your price table. Discounted tokens, such as a repeated prompt the vendor serves from its cache, have their own price; give them their own column. A call that times out mid-answer may return no &lt;code&gt;usage&lt;/code&gt;, and then your log never learns what it cost. And no gateway can fix this for you, including the one I work on: the rounding happens in your database, after the response has arrived. Once a month, compare your sum with the vendor's invoice.&lt;/p&gt;

&lt;h3&gt;
  
  
  The takeaway
&lt;/h3&gt;

&lt;p&gt;A typical LLM call costs less than the smallest amount your money column can hold. Log tokens or micro-dollars for every call, and round once, when you add them up.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>database</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Your LLM judge can cost 9.5x the chatbot it grades. Grade 400 replies a day, not all of them.</title>
      <dc:creator>Andrey Altrouter</dc:creator>
      <pubDate>Sun, 01 Nov 2026 12:00:02 +0000</pubDate>
      <link>https://dev.to/altrouter/your-llm-judge-can-cost-95x-the-chatbot-it-grades-grade-400-replies-a-day-not-all-of-them-43fj</link>
      <guid>https://dev.to/altrouter/your-llm-judge-can-cost-95x-the-chatbot-it-grades-grade-400-replies-a-day-not-all-of-them-43fj</guid>
      <description>&lt;p&gt;You shipped a support chatbot on GPT-5.6 mini, and now you want to know how often it answers wrong. The usual advice is to let a stronger model grade every reply. I priced that setup: each grade costs 9.5 times the reply it grades. At 10,000 replies a day, the bot costs $132 a month to run and $1,260 a month to watch. Most of those grades tell you little that 400 wouldn't.&lt;/p&gt;

&lt;h3&gt;
  
  
  Grading a reply costs more than writing it
&lt;/h3&gt;

&lt;p&gt;Model APIs bill per &lt;strong&gt;token&lt;/strong&gt;, a chunk of text about three-quarters of an English word. &lt;strong&gt;Input&lt;/strong&gt; tokens are what you send and &lt;strong&gt;output&lt;/strong&gt; tokens are what the model writes back; output costs more. Prices are quoted per million tokens, and the ones vendors publish themselves are called &lt;strong&gt;list prices&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;An &lt;strong&gt;LLM judge&lt;/strong&gt; is a second model that reads a reply and grades it against your instructions: did the bot answer the question, did it make up a refund policy. Three things make it expensive. It reads everything the bot read, plus the reply, plus the grading instructions. It is usually a stronger model, because a judge no smarter than the bot tends to miss the bot's mistakes. And the usual setup calls it on every reply.&lt;/p&gt;

&lt;p&gt;My numbers are assumptions, not measurements. The bot reads 1,000 tokens and writes 200. The judge reads those 1,200 plus 300 tokens of instructions and writes a 100-token verdict. List prices come from the catalog of the LLM gateway I work on, read on 26 October 2026: $0.20 / $1.20 per million for GPT-5.6 mini, $2 / $12 for GPT-5.6. A reply costs $0.00044. Its grade costs $0.0042.&lt;/p&gt;

&lt;h3&gt;
  
  
  Precision depends on the count, not on the traffic
&lt;/h3&gt;

&lt;p&gt;The user already has the reply, so grading it fixes nothing. What you're after is one number: the share of bad replies. A random &lt;strong&gt;sample&lt;/strong&gt;, a few replies picked by chance, measures that number too. How close it lands is the &lt;strong&gt;margin of error&lt;/strong&gt;: 95% of the time, what you measure is within that many points of the true rate. For a 5% failure rate it equals 1.96 × √(0.05 × 0.95 / n), where n is the number of graded replies. Your traffic isn't in the formula.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Grades a day&lt;/th&gt;
&lt;th&gt;Margin on a 5% failure rate&lt;/th&gt;
&lt;th&gt;Judge on GPT-5.6, $ a month&lt;/th&gt;
&lt;th&gt;Judge vs the bot's $132&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;200&lt;/td&gt;
&lt;td&gt;±3.0 points&lt;/td&gt;
&lt;td&gt;$25.20&lt;/td&gt;
&lt;td&gt;0.19x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;400&lt;/td&gt;
&lt;td&gt;±2.1 points&lt;/td&gt;
&lt;td&gt;$50.40&lt;/td&gt;
&lt;td&gt;0.38x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1,600&lt;/td&gt;
&lt;td&gt;±1.1 points&lt;/td&gt;
&lt;td&gt;$201.60&lt;/td&gt;
&lt;td&gt;1.5x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10,000 (every reply)&lt;/td&gt;
&lt;td&gt;±0.4 points&lt;/td&gt;
&lt;td&gt;$1,260.00&lt;/td&gt;
&lt;td&gt;9.5x&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Four times the grades buy half the margin. Grading every reply instead of 400 costs 25 times more and narrows "somewhere between 2.9% and 7.1%" to "between 4.6% and 5.4%". At a million replies a day, the same 400 grades still give ±2.1 points, and the judge shrinks to 0.4% of the bot's bill.&lt;/p&gt;

&lt;h3&gt;
  
  
  Grade a fixed number a day
&lt;/h3&gt;

&lt;p&gt;So pick the precision first, turn it into a daily count, and let the chance of grading follow your traffic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;

&lt;span class="n"&gt;GRADES_PER_DAY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;should_grade&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;replies_yesterday&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="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Pick about GRADES_PER_DAY random replies a day, whatever the traffic.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;GRADES_PER_DAY&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;replies_yesterday&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;failure_rate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;graded&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="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;How far the measured rate can be from the true one, 95% of the time.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mf"&gt;1.96&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;failure_rate&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;failure_rate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;graded&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="ow"&gt;in&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="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10_000&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; grades a day: 5% ± &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.05&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&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;p&gt;It prints the margin column of the table. Call &lt;code&gt;should_grade&lt;/code&gt; after each reply with yesterday's reply count, and send the chosen replies to the judge in the background, so no user waits for it. Don't grade the first 400 replies of the day instead: people who write at night ask different questions. If traffic doubles before the count catches up, you grade 800 and pay $3.36 for the day instead of $1.68. Need tighter numbers? Pool a week: 2,800 grades give ±0.8 points at the same daily cost. On a small bot with 2,000 replies a day, 400 grades still cost 1.9 times the bot itself; settle for 200 and ±3 points if that's too much.&lt;/p&gt;

&lt;h3&gt;
  
  
  What this doesn't solve
&lt;/h3&gt;

&lt;p&gt;A sample measures the rate; it doesn't stop a bad reply. If one must never reach a user, you need a check before every reply goes out, paid on every reply. Rare failures slip through too: at a true rate of 0.1%, two days out of three a 400-reply sample contains none, so catch things like a leaked email address with a cheap code check over all traffic. The judge is a model as well; compare it with 50 replies you graded by hand before trusting its number. And no gateway does this for you: the one I work on logs each call's status code, and a 200 means the model answered, not that it answered right.&lt;/p&gt;

&lt;h3&gt;
  
  
  The takeaway
&lt;/h3&gt;

&lt;p&gt;Decide how precisely you need to know your failure rate, turn that into a daily number of grades, and grade that many random replies. Your traffic will grow; the judge bill doesn't have to.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>monitoring</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Your tests can make 66,000 paid LLM calls a month before your first user</title>
      <dc:creator>Andrey Altrouter</dc:creator>
      <pubDate>Sat, 31 Oct 2026 12:00:02 +0000</pubDate>
      <link>https://dev.to/altrouter/your-tests-can-make-66000-paid-llm-calls-a-month-before-your-first-user-1i2i</link>
      <guid>https://dev.to/altrouter/your-tests-can-make-66000-paid-llm-calls-a-month-before-your-first-user-1i2i</guid>
      <description>&lt;p&gt;You wrote tests for your LLM feature, and they call the real model, because that feels like the honest way to test it. Say there are 100 of them, one call each, and the suite runs 30 times a working day: locally while you work, and again on every push. That's 66,000 paid calls a month. On GPT-5.6 at list price it comes to $290, before a single user has signed up.&lt;/p&gt;

&lt;h3&gt;
  
  
  Every test run is a batch of paid calls
&lt;/h3&gt;

&lt;p&gt;Model APIs bill per &lt;strong&gt;token&lt;/strong&gt;, a chunk of text about three-quarters of an English word. &lt;strong&gt;Input&lt;/strong&gt; tokens are what you send and &lt;strong&gt;output&lt;/strong&gt; tokens are what the model writes back; output costs more. Prices are quoted per million tokens, and the ones vendors publish themselves are called &lt;strong&gt;list prices&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A test that calls the model is billed exactly like a user's request. The API can't tell them apart, and the 500th run of the same question is a new request. A vendor's prompt cache, a discount on input it has seen recently, can make the question cheaper, but the answer is written, and billed, again. So the monthly cost of your tests is calls per run × runs per day × working days × the price of one call. &lt;strong&gt;CI&lt;/strong&gt;, the server that runs your tests on every push, grows the middle number without asking. A test watcher that reruns the suite on every file save grows it again.&lt;/p&gt;

&lt;p&gt;My numbers are assumptions, not measurements: 100 model calls per run, 1,000 input and 200 output tokens each, 30 runs a working day, 22 working days a month. Put in your own.&lt;/p&gt;

&lt;h3&gt;
  
  
  Five models, one test suite
&lt;/h3&gt;

&lt;p&gt;List prices come from the catalog of the LLM gateway I work on, read on 25 October 2026. The last column is what's left if the model is called live only once a night, which is where the next section ends up.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;List price in / out, $ per 1M&lt;/th&gt;
&lt;th&gt;One test call&lt;/th&gt;
&lt;th&gt;Every run, 66,000 calls a month&lt;/th&gt;
&lt;th&gt;Live once a night, 3,000 calls&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;claude-haiku-5.5&lt;/td&gt;
&lt;td&gt;0.10 / 0.50&lt;/td&gt;
&lt;td&gt;$0.0002&lt;/td&gt;
&lt;td&gt;$13.20&lt;/td&gt;
&lt;td&gt;$0.60&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gpt-5.6-mini&lt;/td&gt;
&lt;td&gt;0.20 / 1.20&lt;/td&gt;
&lt;td&gt;$0.00044&lt;/td&gt;
&lt;td&gt;$29.04&lt;/td&gt;
&lt;td&gt;$1.32&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gemini-3.8-flash&lt;/td&gt;
&lt;td&gt;0.75 / 3.75&lt;/td&gt;
&lt;td&gt;$0.0015&lt;/td&gt;
&lt;td&gt;$99.00&lt;/td&gt;
&lt;td&gt;$4.50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gpt-5.6&lt;/td&gt;
&lt;td&gt;2 / 12&lt;/td&gt;
&lt;td&gt;$0.0044&lt;/td&gt;
&lt;td&gt;$290.40&lt;/td&gt;
&lt;td&gt;$13.20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;claude-opus-5.5&lt;/td&gt;
&lt;td&gt;4 / 20&lt;/td&gt;
&lt;td&gt;$0.008&lt;/td&gt;
&lt;td&gt;$528.00&lt;/td&gt;
&lt;td&gt;$24.00&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;On the two smallest models the test bill is noise. On the models people pick for quality it's a real line in the budget, and it grows with every push instead of every user. Running the tests on a cheaper model than production lowers the price, but then you're testing a model you don't ship. The fix that keeps your production model is to stop paying for answers you already have.&lt;/p&gt;

&lt;h3&gt;
  
  
  Record each answer once, replay it after that
&lt;/h3&gt;

&lt;p&gt;The idea is called &lt;strong&gt;record and replay&lt;/strong&gt;: the first time a test sends a request, save the model's answer to a file, and every later run with the same request reads the file instead of calling the API. Libraries like vcrpy do this for HTTP requests in general. For one function, eleven lines of standard-library Python are enough:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;recorded&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;folder&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;llm_cassettes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;wrapper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sort_keys&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;folder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;                      &lt;span class="c1"&gt;# replay: no API call, no bill
&lt;/span&gt;            &lt;span class="k"&gt;return&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;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="n"&gt;reply&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                &lt;span class="c1"&gt;# record: one paid call
&lt;/span&gt;        &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mkdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exist_ok&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;reply&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;wrapper&lt;/span&gt;

&lt;span class="n"&gt;paid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="n"&gt;ask&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;recorded&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;paid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;label&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;positive&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;                             &lt;span class="c1"&gt;# three test runs
&lt;/span&gt;    &lt;span class="nf"&gt;ask&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;gpt-5.6&lt;/span&gt;&lt;span class="sh"&gt;"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Great phone!&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;3 test runs, paid API calls: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;paid&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;p&gt;The lambda stands in for the real API call, so the snippet runs as is and prints &lt;code&gt;3 test runs, paid API calls: 1&lt;/code&gt;. Run it again and it prints &lt;code&gt;0&lt;/code&gt;: the answer is already on disk. With the OpenAI SDK, the function you wrap is &lt;code&gt;lambda **r: client.chat.completions.create(**r).model_dump()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Commit the folder, so CI replays the same answers. Change a prompt and its request no longer matches the saved one, so only that test pays again. Once a night, point &lt;code&gt;folder&lt;/code&gt; at an empty directory and let every call reach the API: that's the 3,000-call column, under 5% of the calls. As a side effect, replayed runs can't fail because the API is slow, down or refusing requests at a busy hour.&lt;/p&gt;

&lt;h3&gt;
  
  
  What this doesn't solve
&lt;/h3&gt;

&lt;p&gt;Replay tests your code, not the model. If the vendor changes how a model behaves, or you switch models, the saved answers won't notice until the nightly live run. A saved answer is also one sample: the model may word it differently next time, so live tests should check structure, like valid JSON with the right keys, rather than exact text. And the files hold whatever your test inputs held, so keep real customer data out of them.&lt;/p&gt;

&lt;p&gt;A lower price per token is no substitute. The gateway I mentioned sells gpt-5.6 at $1.70 / $10.20 against the list $2 / $12, which takes the every-run bill from $290.40 to $246.84. Replay takes it to $13.20, at list price.&lt;/p&gt;

&lt;h3&gt;
  
  
  The takeaway
&lt;/h3&gt;

&lt;p&gt;Your tests are your first heavy user. Multiply calls per run by runs per month before you wire a live model into CI, then record each answer once and pay for live calls only when you mean to.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>testing</category>
      <category>beginners</category>
    </item>
    <item>
      <title>A $5-a-month AI plan is a 17-cent daily budget. On GPT-5.6 that's 25 messages.</title>
      <dc:creator>Andrey Altrouter</dc:creator>
      <pubDate>Fri, 30 Oct 2026 12:00:02 +0000</pubDate>
      <link>https://dev.to/altrouter/a-5-a-month-ai-plan-is-a-17-cent-daily-budget-on-gpt-56-thats-25-messages-7</link>
      <guid>https://dev.to/altrouter/a-5-a-month-ai-plan-is-a-17-cent-daily-budget-on-gpt-56-thats-25-messages-7</guid>
      <description>&lt;p&gt;You've built a chatbot and plan to charge $5 a month for it. One message on GPT-5.6 costs well under a cent, so unlimited use sounds safe. Divide the $5 by 30 days, though, and each subscriber gets about 17 cents of model calls a day. A typical message costs 0.66 cents at OpenAI's published price, so from the 26th message of the day, that user costs you more than they pay.&lt;/p&gt;

&lt;h3&gt;
  
  
  A monthly price is a daily budget
&lt;/h3&gt;

&lt;p&gt;Model APIs bill per &lt;strong&gt;token&lt;/strong&gt;, a chunk of text about three-quarters of an English word. &lt;strong&gt;Input&lt;/strong&gt; tokens are what you send: your instructions, the recent chat history and the user's question. &lt;strong&gt;Output&lt;/strong&gt; tokens are what the model writes back, and they cost more. Prices are quoted per million tokens, and the ones vendors publish themselves are called &lt;strong&gt;list prices&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So one message costs input tokens × input price + output tokens × output price. For this article I fixed a message at 1,500 input tokens and 300 output: a few lines of instructions, a few earlier turns and an answer of two paragraphs. On GPT-5.6, at $2 per million input tokens and $12 per million output, that's $0.003 + $0.0036 = $0.0066.&lt;/p&gt;

&lt;p&gt;A flat monthly price is a daily budget per user: $5 / 30 = $0.167. Divide it by the cost of one message and you get how many messages a day the plan pays for. That number still leaves nothing for hosting, card fees or profit.&lt;/p&gt;

&lt;h3&gt;
  
  
  Five models, one $5 plan
&lt;/h3&gt;

&lt;p&gt;I ran that division for five models from three vendors. List prices come from the catalog of the LLM gateway I work on, read on 24 October 2026. The last column assumes you keep 70% of the $5 as &lt;strong&gt;margin&lt;/strong&gt;, the share of revenue left after paying for the model, so the model gets $1.50 a month, 5 cents a day.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;List price in / out, $ per 1M&lt;/th&gt;
&lt;th&gt;One message&lt;/th&gt;
&lt;th&gt;Messages a day for $5&lt;/th&gt;
&lt;th&gt;Keeping 70%&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;claude-haiku-5.5&lt;/td&gt;
&lt;td&gt;0.10 / 0.50&lt;/td&gt;
&lt;td&gt;$0.0003&lt;/td&gt;
&lt;td&gt;555&lt;/td&gt;
&lt;td&gt;166&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gpt-5.6-mini&lt;/td&gt;
&lt;td&gt;0.20 / 1.20&lt;/td&gt;
&lt;td&gt;$0.00066&lt;/td&gt;
&lt;td&gt;252&lt;/td&gt;
&lt;td&gt;75&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gemini-3.8-flash&lt;/td&gt;
&lt;td&gt;0.75 / 3.75&lt;/td&gt;
&lt;td&gt;$0.00225&lt;/td&gt;
&lt;td&gt;74&lt;/td&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gpt-5.6&lt;/td&gt;
&lt;td&gt;2 / 12&lt;/td&gt;
&lt;td&gt;$0.0066&lt;/td&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;claude-opus-5.5&lt;/td&gt;
&lt;td&gt;4 / 20&lt;/td&gt;
&lt;td&gt;$0.012&lt;/td&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A message costs 40 times more in the last row than in the first, and the same $5 buys 40 times fewer of them. With a margin, a GPT-5.6 user who sends eight messages a day is already over budget.&lt;/p&gt;

&lt;p&gt;Most of your users won't send 25 messages a day, and a flat price is set for the average user. The budget is for the one who isn't average. Someone who wires your bot into a script and sends 1,000 messages a day costs $6.60 a day on GPT-5.6, $198 a month, and pays you $5.&lt;/p&gt;

&lt;h3&gt;
  
  
  Count dollars per user, not messages
&lt;/h3&gt;

&lt;p&gt;A message limit is easy to write and wrong in both directions: "hi" and a pasted 20-page document count the same. Count dollars instead. The API reports what each call billed in a &lt;strong&gt;&lt;code&gt;usage&lt;/code&gt;&lt;/strong&gt; field: &lt;code&gt;prompt_tokens&lt;/code&gt; and &lt;code&gt;completion_tokens&lt;/code&gt;. Price them, add them to the user's total for the day, and refuse new messages once the total reaches the budget.&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;collections&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;defaultdict&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;

&lt;span class="n"&gt;PLAN_USD&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;KEEP&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;5.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.70&lt;/span&gt;         &lt;span class="c1"&gt;# monthly price, share you keep as margin
&lt;/span&gt;&lt;span class="n"&gt;PRICE_IN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PRICE_OUT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;2.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;12.00&lt;/span&gt;   &lt;span class="c1"&gt;# $ per 1M tokens, gpt-5.6 list price
&lt;/span&gt;&lt;span class="n"&gt;DAILY_USD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PLAN_USD&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;KEEP&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;
&lt;span class="n"&gt;spent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defaultdict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;          &lt;span class="c1"&gt;# (user, day) -&amp;gt; dollars spent on the model
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;allowed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;spent&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;today&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;DAILY_USD&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prompt_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;completion_tokens&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;cost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt_tokens&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;PRICE_IN&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;completion_tokens&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;PRICE_OUT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;1e6&lt;/span&gt;
    &lt;span class="n"&gt;spent&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;today&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;cost&lt;/span&gt;

&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="nf"&gt;allowed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;alice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;             &lt;span class="c1"&gt;# one simulated day of 1,500-in / 300-out messages
&lt;/span&gt;    &lt;span class="nf"&gt;record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;alice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&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;$&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;DAILY_USD&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; a day, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; messages, spent $&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;spent&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;alice&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;today&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It needs no API key: the loop at the bottom stands in for a day of GPT-5.6 messages and prints &lt;code&gt;$0.0500 a day, 8 messages, spent $0.0528&lt;/code&gt;. In your app, call &lt;code&gt;allowed(user)&lt;/code&gt; before each request and &lt;code&gt;record(user, r.usage.prompt_tokens, r.usage.completion_tokens)&lt;/code&gt; after it, where &lt;code&gt;r&lt;/code&gt; is the API response. The eighth message gets through because the check runs before the reply is priced, so a user can go over by one reply at most.&lt;/p&gt;

&lt;p&gt;Keep &lt;code&gt;spent&lt;/code&gt; in your database rather than in memory, or every restart gives everyone a fresh day. When a user hits the limit, say so and offer something: a reset at midnight, a cheaper model for the rest of the day, or a higher plan.&lt;/p&gt;

&lt;h3&gt;
  
  
  What this doesn't solve
&lt;/h3&gt;

&lt;p&gt;1,500 tokens in and 300 out is my assumption, not a measurement: in a long chat the input grows every turn, so take averages from &lt;code&gt;usage&lt;/code&gt; on real traffic. The check runs before a call and the charge after, so one 100,000-token paste costs $0.20 on GPT-5.6, four days of the 5-cent budget, in a single message. Cap the size of a single request too. And the table says nothing about quality: a model 40 times cheaper may not do your job.&lt;/p&gt;

&lt;p&gt;A lower price per token barely moves any of this. The gateway I mentioned sells GPT-5.6 15% under list, at $1.70 / $10.20, which lifts the $5 plan from 25 to 29 messages a day. Switching to gpt-5.6-mini lifts it to 252.&lt;/p&gt;

&lt;h3&gt;
  
  
  The takeaway
&lt;/h3&gt;

&lt;p&gt;A flat price doesn't make usage free. It gives every user a daily budget: divide the price by 30, divide again by what a message costs, and enforce the result in dollars, not messages.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>cost</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Python wrote 19.944444444444446 and the model billed 7 tokens for it</title>
      <dc:creator>Andrey Altrouter</dc:creator>
      <pubDate>Thu, 29 Oct 2026 12:00:02 +0000</pubDate>
      <link>https://dev.to/altrouter/python-wrote-19944444444444446-and-the-model-billed-7-tokens-for-it-23dn</link>
      <guid>https://dev.to/altrouter/python-wrote-19944444444444446-and-the-model-billed-7-tokens-for-it-23dn</guid>
      <description>&lt;p&gt;You want a model to look over a day of sensor readings and flag anything odd. One reading a minute is 1,440 rows, and you write them out with Python's &lt;code&gt;csv&lt;/code&gt; module, the obvious way. That file is 49,723 tokens. The same information fits in 17,287. The other 32,436 tokens are digits that either repeat on every row or mean nothing.&lt;/p&gt;

&lt;h3&gt;
  
  
  The model reads digits three at a time
&lt;/h3&gt;

&lt;p&gt;You pay per &lt;strong&gt;token&lt;/strong&gt;, a short chunk of text the model reads as one piece. &lt;strong&gt;Input&lt;/strong&gt; tokens are what you send, &lt;strong&gt;output&lt;/strong&gt; tokens are what it writes back, and each has its own price per million. A &lt;strong&gt;tokenizer&lt;/strong&gt; is the program that does the cutting. I used tiktoken, OpenAI's open-source one, with its &lt;code&gt;o200k_base&lt;/code&gt; encoding.&lt;/p&gt;

&lt;p&gt;For numbers the rule is mechanical: digits are cut into groups of up to three, left to right. &lt;code&gt;19.94&lt;/code&gt; is three tokens: &lt;code&gt;19&lt;/code&gt;, &lt;code&gt;.&lt;/code&gt;, &lt;code&gt;94&lt;/code&gt;. &lt;code&gt;19.944444444444446&lt;/code&gt; is seven: &lt;code&gt;19&lt;/code&gt;, &lt;code&gt;.&lt;/code&gt;, &lt;code&gt;944&lt;/code&gt;, &lt;code&gt;444&lt;/code&gt;, &lt;code&gt;444&lt;/code&gt;, &lt;code&gt;444&lt;/code&gt;, &lt;code&gt;446&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That second number is what Python gives you for &lt;code&gt;(67.9 - 32) * 5 / 9&lt;/code&gt;, a Fahrenheit reading converted to Celsius. A &lt;strong&gt;floating-point number&lt;/strong&gt; is stored in binary, most decimal fractions can't be stored exactly, and Python prints the shortest form that reads back to the same value, often 17 digits. &lt;code&gt;str()&lt;/code&gt;, f-strings, &lt;code&gt;csv.writer&lt;/code&gt; and &lt;code&gt;json.dumps&lt;/code&gt; all print that full form. The sensor measured to 0.1 °F, so four digits carry the measurement and thirteen are leftovers of arithmetic.&lt;/p&gt;

&lt;h3&gt;
  
  
  The timestamps cost more than the readings
&lt;/h3&gt;

&lt;p&gt;I built one synthetic day: a greenhouse sensor, one row per minute, three columns. Temperature is the converted reading above. Humidity is the average of three whole-number samples, which is where &lt;code&gt;50.666666666666664&lt;/code&gt; comes from. Timestamps are &lt;code&gt;isoformat()&lt;/code&gt; strings with microseconds, as &lt;code&gt;datetime.now()&lt;/code&gt; produces them. Then I counted the file four ways.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What you send, 1,440 rows&lt;/th&gt;
&lt;th&gt;Tokens&lt;/th&gt;
&lt;th&gt;Per row&lt;/th&gt;
&lt;th&gt;Saved&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;csv.writer&lt;/code&gt; default&lt;/td&gt;
&lt;td&gt;49,723&lt;/td&gt;
&lt;td&gt;34.5&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Values rounded to 2 decimals&lt;/td&gt;
&lt;td&gt;40,327&lt;/td&gt;
&lt;td&gt;28.0&lt;/td&gt;
&lt;td&gt;19%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Timestamps as &lt;code&gt;HH:MM&lt;/code&gt;, values as is&lt;/td&gt;
&lt;td&gt;26,683&lt;/td&gt;
&lt;td&gt;18.5&lt;/td&gt;
&lt;td&gt;46%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Both&lt;/td&gt;
&lt;td&gt;17,287&lt;/td&gt;
&lt;td&gt;12.0&lt;/td&gt;
&lt;td&gt;65%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I expected the long floats to be the problem. They're the smaller half. The timestamp column alone is 28,799 tokens, 58% of the file. &lt;code&gt;2026-10-22T00:00:00.670487+00:00&lt;/code&gt; is 19 tokens, and 16 of them are a date and timezone that repeat on every row plus seconds and microseconds that only record when the logger woke up. &lt;code&gt;00:00&lt;/code&gt; is 3 tokens. That long form is &lt;strong&gt;ISO 8601&lt;/strong&gt;, the standard date-time format, and it's the right choice for storage. In a prompt where every row shares one date, it's a tax.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rounding to one decimal buys nothing
&lt;/h3&gt;

&lt;p&gt;With short timestamps in place, I tried rounding harder. One, two and three decimals all came out at exactly 17,287 tokens. That's the three-digit rule again: &lt;code&gt;19.9&lt;/code&gt;, &lt;code&gt;19.94&lt;/code&gt; and &lt;code&gt;19.944&lt;/code&gt; are each &lt;code&gt;19&lt;/code&gt;, &lt;code&gt;.&lt;/code&gt; and one more token. I checked all 1,110 digit strings of length one to three, and every one is a single token. The fourth decimal is where the next token starts.&lt;/p&gt;

&lt;p&gt;So one decimal only costs you data. Two decimals kept all 96 distinct temperatures in the file. One decimal merged them into 53, because 0.1 °C is coarser than the sensor's 0.1 °F. The rule: round to the precision your source has, and don't expect tokens back below three decimals. To check, count distinct values before and after rounding. If the count drops, you rounded away a measurement.&lt;/p&gt;

&lt;h3&gt;
  
  
  Shorten the rows before the call
&lt;/h3&gt;

&lt;p&gt;Save your readings the way your code writes them and run &lt;code&gt;python compact_csv.py readings.csv&lt;/code&gt;. It expects an ISO timestamp in the first column and numbers in the rest:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tiktoken&lt;/span&gt;  &lt;span class="c1"&gt;# pip install tiktoken
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;

&lt;span class="n"&gt;DECIMALS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;  &lt;span class="c1"&gt;# the precision your data really has; 1-3 cost the same tokens
&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;splitlines&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="n"&gt;first&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromisoformat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rows&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="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;# &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;Y&lt;/span&gt;&lt;span class="o"&gt;-%&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;-%&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;Z&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, time is HH:MM&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;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;hhmm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromisoformat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;H&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;M&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&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;join&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;hhmm&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;DECIMALS&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
&lt;span class="n"&gt;compact&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;tok&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tiktoken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_encoding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;o200k_base&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;encode&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;tok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; tokens as written, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;tok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;compact&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; compact&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="n"&gt;lines&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;lines&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;sep&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It writes the date and timezone once in a header line, keeps hours and minutes, rounds the values and prints both counts. On my test file it prints &lt;code&gt;49,723 tokens as written, 17,302 compact&lt;/code&gt;; the header line is the 15 tokens over the table. The &lt;code&gt;compact&lt;/code&gt; variable is what you send.&lt;/p&gt;

&lt;p&gt;Now the bill. Say ten sensors, each checked every hour against its last 24 hours: 240 calls a day, each with a 41-token instruction and an answer of about 200 tokens. Prices are gpt-5.6's list prices, $2 per million input tokens and $12 per million output, from the catalog of the LLM gateway I work on, read on 23 October 2026. As written, a call costs $0.10 and the month $734. Compact, it's $0.037 a call and $267 a month. Input is 98% of the first bill, so the answer length barely matters here. On gpt-5.6-mini, ten times cheaper, the month is $73 against $27.&lt;/p&gt;

&lt;h3&gt;
  
  
  What this doesn't solve
&lt;/h3&gt;

&lt;p&gt;Rounding destroys information when the digits are real: money (keep the cents, and keep money out of floats altogether), GPS coordinates, where the fifth decimal is about a metre, or lab measurements. &lt;code&gt;HH:MM&lt;/code&gt; only works while the window sits inside one day and one timezone. A window across midnight needs the date where it changes, or minutes since the window start. The data is synthetic, one simulated sensor, and the counts come from tiktoken, not from an API's &lt;code&gt;usage&lt;/code&gt; field. Claude and Gemini use their own tokenizers, so count yours with your vendor's token counter. Sending min, max and mean computed in code is cheaper still, though then the model no longer sees each reading.&lt;/p&gt;

&lt;p&gt;The gateway sells gpt-5.6 at $1.70 / $10.20, 15% under list, which makes the two monthly bills $624 and $227. It bills the tokens you send, digits included, and rounds nothing for you.&lt;/p&gt;

&lt;h3&gt;
  
  
  The takeaway
&lt;/h3&gt;

&lt;p&gt;The tokenizer can't tell a measurement from arithmetic noise, and it bills both. Write the date once and round to the precision you have, and a day of readings costs a third of what Python's defaults send.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>python</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Two-thirds of a web page's tokens are markup, and most of it isn't the menu</title>
      <dc:creator>Andrey Altrouter</dc:creator>
      <pubDate>Wed, 28 Oct 2026 12:00:01 +0000</pubDate>
      <link>https://dev.to/altrouter/two-thirds-of-a-web-pages-tokens-are-markup-and-most-of-it-isnt-the-menu-10pa</link>
      <guid>https://dev.to/altrouter/two-thirds-of-a-web-pages-tokens-are-markup-and-most-of-it-isnt-the-menu-10pa</guid>
      <description>&lt;p&gt;You build a small "ask the docs" helper: download a page, paste it into the prompt, add the user's question. It works, and the bill comes out about three times what the text on the screen suggests. The Node.js reference page for UDP sockets is 7,127 tokens of text. Downloaded as is, it's 23,178.&lt;/p&gt;

&lt;h3&gt;
  
  
  The model reads the tags too
&lt;/h3&gt;

&lt;p&gt;You pay per &lt;strong&gt;token&lt;/strong&gt;, a chunk of text about three-quarters of an English word long. &lt;strong&gt;Input&lt;/strong&gt; tokens are what you send, &lt;strong&gt;output&lt;/strong&gt; tokens are what the model writes back. What your code downloads isn't the page you see, it's the HTML behind it: tags, attributes, inline scripts, SVG icons. The model has no "skip the markup" mode. Every character you send is cut into tokens and billed at the input price.&lt;/p&gt;

&lt;p&gt;Markup is made of short, rare fragments, so each token covers fewer characters than in prose: 3.4 against 4.1 on the pages below. Here is one heading from the Node.js &lt;code&gt;path&lt;/code&gt; page, exactly as the server sends it:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;section&amp;gt;&amp;lt;h3&amp;gt;Windows vs. POSIX&amp;lt;span&amp;gt;&amp;lt;a class="mark" href="#windows-vs-posix" id="windows-vs-posix"&amp;gt;#&amp;lt;/a&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;a aria-hidden="true" class="legacy" id="path_windows_vs_posix"&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/h3&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;That's 62 tokens. The words a reader sees, "Windows vs. POSIX", are 5. Code samples pay as well: every apostrophe in them is written as &lt;code&gt;&amp;amp;#39;&lt;/code&gt;, an &lt;strong&gt;HTML entity&lt;/strong&gt; (the escaped form of a character), and each one adds about 2.5 tokens over a plain &lt;code&gt;'&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  I counted 63 pages four ways
&lt;/h3&gt;

&lt;p&gt;I took all 63 API reference pages of Node.js 20.19.2, the HTML copies that ship with its documentation on Debian, and counted each one four ways with tiktoken, OpenAI's open-source &lt;strong&gt;tokenizer&lt;/strong&gt; (the program that cuts text into tokens), using &lt;code&gt;o200k_base&lt;/code&gt;, the encoding of current GPT models.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What you send, all 63 pages&lt;/th&gt;
&lt;th&gt;Tokens&lt;/th&gt;
&lt;th&gt;× text&lt;/th&gt;
&lt;th&gt;Saved&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Raw HTML, as downloaded&lt;/td&gt;
&lt;td&gt;2,284,043&lt;/td&gt;
&lt;td&gt;3.06×&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Only the article block, tags kept&lt;/td&gt;
&lt;td&gt;1,754,618&lt;/td&gt;
&lt;td&gt;2.35×&lt;/td&gt;
&lt;td&gt;23%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Whole page, tags stripped&lt;/td&gt;
&lt;td&gt;746,178&lt;/td&gt;
&lt;td&gt;1.00×&lt;/td&gt;
&lt;td&gt;67%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Article block, tags stripped&lt;/td&gt;
&lt;td&gt;705,226&lt;/td&gt;
&lt;td&gt;0.95×&lt;/td&gt;
&lt;td&gt;69%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The obvious fix is to cut away the &lt;strong&gt;page chrome&lt;/strong&gt;, the navigation, header and footer repeated on every page. Here it saves 23%. Stripping the tags saves 67%, because most of the waste sits inside the article. Against the bare article text, the raw pages carry 1.58 million extra tokens: 34% around the article and 66% inside it. The 63 pages hold 34,912 &lt;code&gt;href=&lt;/code&gt; attributes, 33,092 &lt;code&gt;class=&lt;/code&gt; attributes and 22,622 escaped apostrophes.&lt;/p&gt;

&lt;p&gt;Per page, raw HTML ran from 2.05× its text (C++ addons) to 9.2× (the short usage example), plus one stub page at 20.7×. 50 of the 63 landed between 2.5× and 4.5×.&lt;/p&gt;

&lt;h3&gt;
  
  
  What it does to the bill
&lt;/h3&gt;

&lt;p&gt;Take the UDP page, close to the median at 3.25×, and a helper that answers 1,000 questions a day: one page, a 30-token question and a 300-token answer per call. Prices are gpt-5.6's list prices, $2 per million input tokens and $12 per million output, from the catalog of the LLM gateway I work on, read on 22 October 2026.&lt;/p&gt;

&lt;p&gt;With raw HTML the month costs $1,500. With text it costs $537. The answer is the same $108 either way, 7% of the first bill and 20% of the second. Cutting away only the navigation and keeping the tags lands at $1,070.&lt;/p&gt;

&lt;p&gt;So the lever here is input. It's 93% of the raw bill, and more than two-thirds of that input is markup the model didn't need to answer a question about UDP sockets.&lt;/p&gt;

&lt;h3&gt;
  
  
  Strip the tags before the call
&lt;/h3&gt;

&lt;p&gt;Download the page the way your code will, &lt;code&gt;curl -o page.html &amp;lt;url&amp;gt;&lt;/code&gt;, and run &lt;code&gt;python page_text.py page.html&lt;/code&gt;. It needs the standard library plus &lt;code&gt;tiktoken&lt;/code&gt; for the count:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tiktoken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parser&lt;/span&gt;  &lt;span class="c1"&gt;# pip install tiktoken
&lt;/span&gt;&lt;span class="n"&gt;SKIP&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;head&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;script&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;style&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;svg&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;nav&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;header&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;footer&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;aside&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;BLOCK&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;p&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;div&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;li&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;pre&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;tr&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;td&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;th&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;br&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;h1&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;h2&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;h3&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;h4&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;section&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PageText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTMLParser&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;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;skip&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parts&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="p"&gt;[]&lt;/span&gt;  &lt;span class="c1"&gt;# skip &amp;gt; 0 means "inside a dropped tag"
&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_starttag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attrs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;skip&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;SKIP&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;BLOCK&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_endtag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;skip&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;SKIP&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;skip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;PageText&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;feed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;splitlines&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="n"&gt;tok&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tiktoken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_encoding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;o200k_base&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;encode&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;tok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; tokens as HTML, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;tok&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="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; as text&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;It drops what rarely holds the answer (scripts, styles, icons, navigation, header, footer), keeps the rest as text, and starts a new line at every paragraph, list item, heading and table cell, so code samples stay readable. On the UDP page it prints &lt;code&gt;23,178 tokens as HTML, 7,127 as text&lt;/code&gt;. The &lt;code&gt;text&lt;/code&gt; variable is what you send.&lt;/p&gt;

&lt;p&gt;The Node.js sidebar isn't a &lt;code&gt;&amp;lt;nav&amp;gt;&lt;/code&gt; tag, so its list of modules stays in. If a site wraps the article in &lt;code&gt;&amp;lt;main&amp;gt;&lt;/code&gt; or gives it an id, cut that block out first; here that removed the sidebar and the table of contents, about 5% of the text tokens.&lt;/p&gt;

&lt;h3&gt;
  
  
  What this doesn't solve
&lt;/h3&gt;

&lt;p&gt;Stripping throws away what tags carry. Links lose their URLs and tables lose their columns, so if the task is "list every link" or "read this price table", keep that part as HTML or convert it to Markdown. Pages built by JavaScript in the browser can arrive as a nearly empty shell, and no stripping helps there. Documentation pages are lean by web standards, I measured local copies rather than the live site, and I didn't measure any other kind of site, so run the script on yours. The counts come from OpenAI's tokenizer; Claude and Gemini split text differently.&lt;/p&gt;

&lt;p&gt;The gateway I mentioned sells gpt-5.6 at $1.70 / $10.20, 15% under list, which turns the two monthly bills into $1,275 and $457. That's the same 15% off both. It bills the tokens you send, tags included, and strips nothing for you.&lt;/p&gt;

&lt;h3&gt;
  
  
  The takeaway
&lt;/h3&gt;

&lt;p&gt;The model bills markup at the same price as meaning. Send it the text of the page, not the page: on these 63 pages that was two-thirds of the input, for twenty lines of code.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>cost</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Ten reviews per LLM call cut the bill by 57%. A thousand per call only reaches 65%.</title>
      <dc:creator>Andrey Altrouter</dc:creator>
      <pubDate>Tue, 27 Oct 2026 12:00:01 +0000</pubDate>
      <link>https://dev.to/altrouter/ten-reviews-per-llm-call-cut-the-bill-by-57-a-thousand-per-call-only-reaches-65-4d9j</link>
      <guid>https://dev.to/altrouter/ten-reviews-per-llm-call-cut-the-bill-by-57-a-thousand-per-call-only-reaches-65-4d9j</guid>
      <description>&lt;p&gt;You have 100,000 customer reviews and a prompt that tags one of them as positive or negative and says what it's about. Sending them one by one is the obvious first version. Sending ten per request cuts the bill by 57%. Sending a thousand per request cuts it by 65%, and those last eight points are where the trouble starts.&lt;/p&gt;

&lt;h3&gt;
  
  
  You pay for the rules once per request
&lt;/h3&gt;

&lt;p&gt;You pay per &lt;strong&gt;token&lt;/strong&gt;, a chunk of text about three-quarters of an English word. &lt;strong&gt;Input&lt;/strong&gt; tokens are what you send, &lt;strong&gt;output&lt;/strong&gt; tokens are what the model writes back, and output usually costs several times more.&lt;/p&gt;

&lt;p&gt;A labeling request has two parts: the instructions (the labels, the rules, a couple of examples) and the review itself. The API remembers nothing between calls, so the instructions travel with every request. In my test, a one-review request sends 313 tokens, and 295 of them are the same every time: the rules plus a few tokens of message formatting. Label 100,000 reviews one at a time and you pay for those rules 100,000 times.&lt;/p&gt;

&lt;p&gt;Pack ten reviews into one request and the rules are paid once per ten. Each review now costs a tenth of the instructions, its own text, a short &lt;code&gt;[7]&lt;/code&gt; label in front of it, and its line of the answer. The first part shrinks as you pack more. The rest doesn't shrink at all.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where the curve goes flat
&lt;/h3&gt;

&lt;p&gt;My test task is a made-up clothing store with two labels per review: sentiment (positive, negative, mixed) and topic (shipping, quality, sizing, price, support, other). I wrote 20 short reviews and their correct answers myself, and counted everything with tiktoken, OpenAI's open-source token counter, using &lt;code&gt;o200k_base&lt;/code&gt;, the encoding of current GPT models. I didn't call a model: the replies are the right answers in the requested format. Prices are gpt-5.6's list prices, the ones the vendor publishes, $2 per million input tokens and $12 per million output, from the catalog of the LLM gateway I work on, read on 21 October 2026.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Reviews per call&lt;/th&gt;
&lt;th&gt;Tokens per review, in / out&lt;/th&gt;
&lt;th&gt;Tokens in one reply&lt;/th&gt;
&lt;th&gt;100,000 reviews&lt;/th&gt;
&lt;th&gt;Saved&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;313 / 13&lt;/td&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;$78.38&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;90 / 19&lt;/td&gt;
&lt;td&gt;96&lt;/td&gt;
&lt;td&gt;$41.00&lt;/td&gt;
&lt;td&gt;48%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;54 / 19&lt;/td&gt;
&lt;td&gt;192&lt;/td&gt;
&lt;td&gt;$33.82&lt;/td&gt;
&lt;td&gt;57%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;24 / 19&lt;/td&gt;
&lt;td&gt;1,920&lt;/td&gt;
&lt;td&gt;$27.83&lt;/td&gt;
&lt;td&gt;64%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1,000&lt;/td&gt;
&lt;td&gt;21 / 19&lt;/td&gt;
&lt;td&gt;19,201&lt;/td&gt;
&lt;td&gt;$27.25&lt;/td&gt;
&lt;td&gt;65%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Even with free instructions the bill can't drop below $27.18, because the reviews and the answers stay. So 65% is the ceiling, and ten per call already covers 87% of the way there. Packing also adds a cost: each answer line needs an &lt;code&gt;"id"&lt;/code&gt;, so output per review grows from 13 tokens to 19, at six times the input price. At one per call output was 20% of the bill. At ten it's 68%, and packing can't touch that part.&lt;/p&gt;

&lt;h3&gt;
  
  
  A bigger request means a bigger failure
&lt;/h3&gt;

&lt;p&gt;With one review per call, a bad reply loses one review. With a hundred per call, a reply that stops halfway can lose all hundred, and you still pay for the half that arrived. Replies break for boring reasons: the connection drops, the model skips an item, or it hits &lt;strong&gt;max_tokens&lt;/strong&gt;, the cap you set on how long its answer may be.&lt;/p&gt;

&lt;p&gt;The answer format decides how much you keep. I cut a ten-review reply at 70% of its length. Written as one JSON array, it doesn't parse at all, so 0 of 10 labels survive. Written as &lt;strong&gt;JSON Lines&lt;/strong&gt;, one JSON object per line, 6 of 10 lines parse and only 4 reviews go back for a second try. Both formats cost the same: 194 and 192 tokens.&lt;/p&gt;

&lt;p&gt;So ask for one object per line that carries the id, set max_tokens to the number of items times one answer line with room to spare, and check &lt;strong&gt;finish_reason&lt;/strong&gt;, the field in the response that reads &lt;code&gt;length&lt;/code&gt; when the reply hit the cap.&lt;/p&gt;

&lt;h3&gt;
  
  
  Find your own flat spot
&lt;/h3&gt;

&lt;p&gt;Count your instructions, one item and one answer line on your real prompt, then plug them in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Token counts from your own prompt, prices in $ per 1M tokens
&lt;/span&gt;&lt;span class="n"&gt;INSTR_1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;INSTR_N&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;295&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;325&lt;/span&gt;  &lt;span class="c1"&gt;# instructions: one-item version, packed version
&lt;/span&gt;&lt;span class="n"&gt;ITEM&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TAG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;            &lt;span class="c1"&gt;# tokens per item, plus its "[7] " label
&lt;/span&gt;&lt;span class="n"&gt;OUT_1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OUT_N&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;19&lt;/span&gt;        &lt;span class="c1"&gt;# answer per item: alone, and with its "id" key
&lt;/span&gt;&lt;span class="n"&gt;PRICE_IN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PRICE_OUT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;2.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;12.00&lt;/span&gt;
&lt;span class="n"&gt;ITEMS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100_000&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;bill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;tokens_in&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tokens_out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;INSTR_1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;ITEM&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OUT_1&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;tokens_in&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tokens_out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;INSTR_N&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;ITEM&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;TAG&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OUT_N&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ITEMS&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tokens_in&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;PRICE_IN&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;tokens_out&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;PRICE_OUT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;1e6&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; per call: $&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;bill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;6.2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nf"&gt;bill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nf"&gt;bill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; saved)&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;It prints $78.20 at one per call, $33.50 at ten and $27.65 at a hundred, within 35 cents of the table because the inputs are rounded. Stop where doubling the batch adds only a couple of points; here that's ten to twenty. This isn't a vendor's batch API, which gives a discount for waiting hours for results but still sends one request per review, rules included. The two stack.&lt;/p&gt;

&lt;p&gt;Then read the reply line by line and resend only what's missing:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse_packed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sent_ids&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;labels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;splitlines&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;item&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;line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sentiment&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;topic&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="nf"&gt;except &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;KeyError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;TypeError&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;  &lt;span class="c1"&gt;# cut-off or malformed line: that item gets resent
&lt;/span&gt;    &lt;span class="n"&gt;missing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;sent_ids&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;missing&lt;/span&gt;

&lt;span class="n"&gt;reply&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'''&lt;/span&gt;&lt;span class="s"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: 1, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sentiment&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;negative&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;topic&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sizing&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;}
{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: 2, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sentiment&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;negative&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;topic&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;shipping&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;}
{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: 3, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sentiment&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;posi&lt;/span&gt;&lt;span class="sh"&gt;'''&lt;/span&gt;
&lt;span class="n"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;missing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parse_packed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&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;parsed &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, resend &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;missing&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;p&gt;It prints &lt;code&gt;parsed 2, resend [3, 4]&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  What this doesn't solve
&lt;/h3&gt;

&lt;p&gt;I didn't measure accuracy. Models can skip, merge or mislabel items in a long list, and my replies are perfect by construction, so compare a sample of packed results with one-at-a-time ones before you switch. The counts come from OpenAI's tokenizer, and Claude and Gemini split text differently. gpt-5.6 can also spend hidden thinking tokens, billed as output, that none of these numbers include. And short reviews favour packing: the longer each item, the smaller the instructions' share and the smaller the saving.&lt;/p&gt;

&lt;p&gt;The gateway I mentioned sells gpt-5.6 at $1.70 / $10.20, 15% under list. That takes ten per call from $33.82 to $28.75 and leaves the curve where it was. It also fills in a max_tokens of 4,096 when a request has none, which the official API doesn't, so at 19 tokens per review a call with more than about 213 reviews gets cut off there.&lt;/p&gt;

&lt;h3&gt;
  
  
  The takeaway
&lt;/h3&gt;

&lt;p&gt;Instructions are billed per request, items per item. Pack enough to turn the instructions into a rounding error, about ten here, and stop: past that point you're buying risk, not savings.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>cost</category>
      <category>beginners</category>
    </item>
    <item>
      <title>A cheap model that handles 70% of your requests can cut the bill by 65% — or raise it by 5%</title>
      <dc:creator>Andrey Altrouter</dc:creator>
      <pubDate>Mon, 26 Oct 2026 12:00:02 +0000</pubDate>
      <link>https://dev.to/altrouter/a-cheap-model-that-handles-70-of-your-requests-can-cut-the-bill-by-65-or-raise-it-by-5-2p2k</link>
      <guid>https://dev.to/altrouter/a-cheap-model-that-handles-70-of-your-requests-can-cut-the-bill-by-65-or-raise-it-by-5-2p2k</guid>
      <description>&lt;p&gt;A popular way to cut an LLM bill is to send every request to a small, cheap model first and hand it to the big one only when the small one gets it wrong. I priced that setup on five pairs of models from the same vendor, with the small model handling 7 requests out of 10. On one pair it cuts the bill by 65%. On another it makes the bill 5% bigger. Same success rate, opposite outcome.&lt;/p&gt;

&lt;h3&gt;
  
  
  The failed attempt is billed too
&lt;/h3&gt;

&lt;p&gt;You pay per &lt;strong&gt;token&lt;/strong&gt;, a chunk of text about three-quarters of an English word, at one price for &lt;strong&gt;input&lt;/strong&gt; (what you send) and a higher one for &lt;strong&gt;output&lt;/strong&gt; (what the model writes back). The per-token prices vendors publish are called &lt;strong&gt;list prices&lt;/strong&gt; and are quoted per million tokens.&lt;/p&gt;

&lt;p&gt;The setup above is called a &lt;strong&gt;cascade&lt;/strong&gt;: try the cheap model, check its answer, and escalate to the strong model only if the check fails. The detail that's easy to miss is that the cheap attempt is billed whether it passes or not. Every request pays for the small call, and the failed ones pay for the big call on top.&lt;/p&gt;

&lt;p&gt;Call the share of requests that get through the check the &lt;strong&gt;pass rate&lt;/strong&gt;, p. Call the small model's cost per request divided by the big model's the price ratio, r. Then the cascade costs r + (1 − p) of what the strong model alone would cost, and your saving is p − r. The cascade breaks even when the pass rate equals the price ratio, and loses money below it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Five pairs, one support ticket
&lt;/h3&gt;

&lt;p&gt;To get real ratios I took one task: turn a customer email into a JSON ticket with six fields. The instructions and the email come to 346 input tokens and the reply to 65, counted with tiktoken, OpenAI's open-source token counter, using &lt;code&gt;o200k_base&lt;/code&gt;, the encoding of current GPT models. List prices come from the catalog of the LLM gateway I work on, snapshot of 19 October 2026. I didn't measure pass rates: 70% in the last column is a round number for illustration.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Small → strong&lt;/th&gt;
&lt;th&gt;List price in / out, $ per 1M&lt;/th&gt;
&lt;th&gt;Break-even pass rate&lt;/th&gt;
&lt;th&gt;Bill change at 70% pass&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;claude-haiku-5.5 → claude-sonnet-5.5&lt;/td&gt;
&lt;td&gt;0.10 / 0.50 → 2 / 10&lt;/td&gt;
&lt;td&gt;5%&lt;/td&gt;
&lt;td&gt;−65%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gpt-5.6-mini → gpt-5.6&lt;/td&gt;
&lt;td&gt;0.20 / 1.20 → 2 / 12&lt;/td&gt;
&lt;td&gt;10%&lt;/td&gt;
&lt;td&gt;−60%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gemini-3.8-flash → gemini-3.1-pro&lt;/td&gt;
&lt;td&gt;0.75 / 3.75 → 2 / 12&lt;/td&gt;
&lt;td&gt;34%&lt;/td&gt;
&lt;td&gt;−36%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;claude-sonnet-5.5 → claude-opus-5.5&lt;/td&gt;
&lt;td&gt;2 / 10 → 4 / 20&lt;/td&gt;
&lt;td&gt;50%&lt;/td&gt;
&lt;td&gt;−20%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gemini-3.5-flash → gemini-3.1-pro&lt;/td&gt;
&lt;td&gt;1.50 / 9 → 2 / 12&lt;/td&gt;
&lt;td&gt;75%&lt;/td&gt;
&lt;td&gt;+5%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The model's name tells you nothing about the ratio. Two Gemini "flash" models in front of the same 3.1 Pro land at 34% and 75%. Haiku in front of Sonnet pays off above a 5% pass rate, while Sonnet in front of Opus has to handle half the work just to break even. The 3.8 Flash row is the only one where the task shape matters: its input costs 37.5% of Pro's and its output 31%, so the ratio slides between those two numbers.&lt;/p&gt;

&lt;p&gt;In dollars, at 100,000 tickets a month, gpt-5.6 alone costs $147 and the mini-first cascade $59. The Gemini 3.5 Flash cascade costs $155 against $147 for 3.1 Pro alone. You pay extra for an extra moving part.&lt;/p&gt;

&lt;h3&gt;
  
  
  Measure two numbers before you build it
&lt;/h3&gt;

&lt;p&gt;Use what your calls actually cost, not list prices. The ratio in the table assumes both models write the same number of tokens. If the small model writes five times more, gpt-5.6-mini's ratio climbs from 10% to 31%. If it's a "thinking" model that spends 1,000 &lt;strong&gt;reasoning tokens&lt;/strong&gt;, hidden notes billed as output, on this short ticket while the big one answers straight away, the ratio hits 92% and the cascade almost never pays. So run 50 real requests through the small model, check every reply, and plug in the average token counts the API returned:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Averages from your own calls, and the prices you pay ($ per 1M tokens)
&lt;/span&gt;&lt;span class="n"&gt;SMALL&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;in&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;346&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;out&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;65&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;price_in&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;price_out&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.20&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;   &lt;span class="c1"&gt;# gpt-5.6-mini
&lt;/span&gt;&lt;span class="n"&gt;BIG&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;in&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;346&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;out&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;65&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;price_in&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;2.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;price_out&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;12.00&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;    &lt;span class="c1"&gt;# gpt-5.6
&lt;/span&gt;&lt;span class="n"&gt;PASSED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TRIED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;35&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;   &lt;span class="c1"&gt;# small-model replies that passed your check
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;cost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;in&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;price_in&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;out&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;price_out&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;1e6&lt;/span&gt;

&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;cost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SMALL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nf"&gt;cost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BIG&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PASSED&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;TRIED&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;break-even pass rate: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, yours: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&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;cascade vs strong model only: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; of the bill&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;It prints &lt;code&gt;break-even pass rate: 10%, yours: 70%&lt;/code&gt;, then &lt;code&gt;cascade vs strong model only: 40% of the bill&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The check has to be automatic, or there's nothing to escalate on. For structured output it costs nothing: parse the JSON and compare the keys. Return which model answered and log it. In production, that log is your pass rate.&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="n"&gt;KEYS&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;order_id&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;product&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;issue&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;wants_refund&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;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;summary&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;passes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="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;reply&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;isinstance&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="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;KEYS&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;cascade&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;ask_small&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ask_big&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;reply&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ask_small&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="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;passes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;small&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;ask_big&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;big&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;small&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Sure! Here is the JSON: {&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A-48213&lt;/span&gt;&lt;span class="sh"&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;big&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromkeys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;KEYS&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="nf"&gt;cascade&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&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;small&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;big&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The two lambdas stand in for real API calls, so the snippet runs as is. The fake small model chats before its JSON, the parse fails, and it prints &lt;code&gt;big&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  What this doesn't solve
&lt;/h3&gt;

&lt;p&gt;Failed requests wait for two calls, so at a 70% pass rate three tickets in ten get slower, not faster. A wrong answer that passes the check is never escalated: a key check catches broken JSON, not a wrong order number. Free-text answers have no cheap check, and grading them with a third model adds its cost to every request. tiktoken is OpenAI's tokenizer, so Claude and Gemini counts will differ.&lt;/p&gt;

&lt;p&gt;Prices below list don't fix this and can make it worse. The gateway I mentioned sells all eight models under list, but not at the same discount: Gemini 3.1 Pro gets a deeper cut than 3.5 Flash, so at our prices that pair's break-even moves from 75% to 85%. Recompute the ratio at the prices you actually pay.&lt;/p&gt;

&lt;h3&gt;
  
  
  The takeaway
&lt;/h3&gt;

&lt;p&gt;A cheap first try is never free. Divide what the two calls cost, measure how often the small model passes, and build the cascade only if the pass rate sits well above that ratio.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>cost</category>
      <category>beginners</category>
    </item>
    <item>
      <title>The model fixed one character and billed you for retyping all 497 lines</title>
      <dc:creator>Andrey Altrouter</dc:creator>
      <pubDate>Sun, 25 Oct 2026 12:00:01 +0000</pubDate>
      <link>https://dev.to/altrouter/the-model-fixed-one-character-and-billed-you-for-retyping-all-497-lines-ia9</link>
      <guid>https://dev.to/altrouter/the-model-fixed-one-character-and-billed-you-for-retyping-all-497-lines-ia9</guid>
      <description>&lt;p&gt;A typical first AI coding script reads a file, sends it with "fix this bug", and writes the reply back over the original. It works. But for a one-character fix in a 497-line file, the model writes back 4,495 tokens, the whole file again, and you pay for every unchanged line it retypes just to deliver one changed character.&lt;/p&gt;

&lt;h3&gt;
  
  
  The fix is one character, the reply is the whole file
&lt;/h3&gt;

&lt;p&gt;You pay per &lt;strong&gt;token&lt;/strong&gt;, a chunk of text the model reads or writes, roughly four characters of English or code. Prices are quoted per million tokens and split in two: &lt;strong&gt;input tokens&lt;/strong&gt; are everything you send, &lt;strong&gt;output tokens&lt;/strong&gt; are everything the model writes back. Output is the expensive side. On most current models it costs 5–6 times as much as input.&lt;/p&gt;

&lt;p&gt;A whole-file request puts your file on both sides of the bill. The model reads it once as input, then writes it again as output, unchanged lines included. The fix you actually wanted is a rounding error inside that second number.&lt;/p&gt;

&lt;p&gt;That gives a rule of thumb you can work out in your head. When the model has to retype the file, the edit costs close to 1 + (output price ÷ input price) times what it costs the model to read the file. If output is 6 times the input price, that's nearly 7×, and the bigger the file, the closer you get to that ceiling.&lt;/p&gt;

&lt;h3&gt;
  
  
  One off-by-one bug, five models
&lt;/h3&gt;

&lt;p&gt;To put numbers on it, I took a real file: &lt;code&gt;textwrap.py&lt;/code&gt; from Python's standard library, 497 lines and 4,491 tokens. I counted them with tiktoken, OpenAI's open-source token counter, using &lt;code&gt;o200k_base&lt;/code&gt;, the encoding of current GPT models. I broke one line, turning &lt;code&gt;&amp;lt;=&lt;/code&gt; into &lt;code&gt;&amp;lt;&lt;/code&gt; in the check that decides whether a word still fits, and priced the fix two ways. If the model returns the whole corrected file, that's 4,495 output tokens. If it returns a &lt;strong&gt;SEARCH/REPLACE block&lt;/strong&gt;, meaning the lines to find and what they should become, that's 51.&lt;/p&gt;

&lt;p&gt;All prices are &lt;strong&gt;list prices&lt;/strong&gt;, the per-token prices vendors publish. They come from the catalog of the LLM gateway I work on, snapshot of 17.10.2026.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;List price in / out, $ per 1M&lt;/th&gt;
&lt;th&gt;Whole file back&lt;/th&gt;
&lt;th&gt;Change only&lt;/th&gt;
&lt;th&gt;Ratio&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;gpt-5.6-mini&lt;/td&gt;
&lt;td&gt;0.20 / 1.20&lt;/td&gt;
&lt;td&gt;$0.0063&lt;/td&gt;
&lt;td&gt;$0.0010&lt;/td&gt;
&lt;td&gt;6.5×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gpt-5.6&lt;/td&gt;
&lt;td&gt;2 / 12&lt;/td&gt;
&lt;td&gt;$0.0630&lt;/td&gt;
&lt;td&gt;$0.0098&lt;/td&gt;
&lt;td&gt;6.5×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;claude-sonnet-5.5&lt;/td&gt;
&lt;td&gt;2 / 10&lt;/td&gt;
&lt;td&gt;$0.0540&lt;/td&gt;
&lt;td&gt;$0.0097&lt;/td&gt;
&lt;td&gt;5.6×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;deepseek-v4.1-flash&lt;/td&gt;
&lt;td&gt;0.30 / 1.20&lt;/td&gt;
&lt;td&gt;$0.0068&lt;/td&gt;
&lt;td&gt;$0.0014&lt;/td&gt;
&lt;td&gt;4.7×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;grok-4.3&lt;/td&gt;
&lt;td&gt;1.25 / 2.50&lt;/td&gt;
&lt;td&gt;$0.0169&lt;/td&gt;
&lt;td&gt;$0.0058&lt;/td&gt;
&lt;td&gt;2.9×&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;On gpt-5.6, 86% of the whole-file bill is the file being typed back. Almost all of the change-only cost is the model reading the file. The ratio doesn't follow the price level: gpt-5.6-mini is ten times cheaper and lands at the same 6.5×. It follows the gap between output and input prices, which is why grok-4.3, where output costs only twice as much as input, gains least. File size matters less than you'd think. With the same block of about 50 tokens, &lt;code&gt;getopt.py&lt;/code&gt; (215 lines) comes out at 5.8× and &lt;code&gt;argparse.py&lt;/code&gt; (2,676 lines) at 6.9×.&lt;/p&gt;

&lt;p&gt;For a script that makes 200 edits a day on gpt-5.6, that's $378 a month against $59.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ask for the change, not the file
&lt;/h3&gt;

&lt;p&gt;Add this to your instructions. It costs 53 input tokens per call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Return only the change, never the whole file. Use one block per change:
&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt; SEARCH
(the exact lines to change, copied from the file, plus one unchanged line above them)
=======
(the same lines as they should read after the change)
&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt; REPLACE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The unchanged line goes in both halves. It's there to make the match unique, because a single line like &lt;code&gt;return None&lt;/code&gt; may appear a dozen times in a file. Then apply the reply yourself, and refuse anything you can't match exactly once:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;
&lt;span class="n"&gt;BLOCK&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt; SEARCH\n(.*?)\n=======\n(.*?)\n&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt; REPLACE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;apply_edits&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;blocks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BLOCK&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;blocks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;no SEARCH/REPLACE block in the reply&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;old&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;blocks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;old&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&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;search text found &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;old&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; times&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;old&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;

&lt;span class="n"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;def area(w, h):&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;    # rectangle&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;    return w + h&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;reply&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt; SEARCH&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;    # rectangle&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;    return w + h&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;=======&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;    # rectangle&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;    return w * h&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt; REPLACE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;apply_edits&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;apply_edits&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;w + h&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;w - h&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;ValueError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;refused:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It prints the fixed function, then &lt;code&gt;refused: search text found 0 times&lt;/code&gt;. Zero means the model misquoted your code, and more than one means the match is ambiguous. Guessing in either case could corrupt the file, so the function stops.&lt;/p&gt;

&lt;p&gt;When an edit is refused, ask again for the whole file. That fallback costs one extra call, but a failed change-only call costs little more than reading the file. So on gpt-5.6 the format stays cheaper until about 85% of your edits need the fallback.&lt;/p&gt;

&lt;h3&gt;
  
  
  What this doesn't solve
&lt;/h3&gt;

&lt;p&gt;The input side doesn't shrink: the model still reads all 4,491 tokens. Cutting that means sending only the relevant function, a different trade-off. &lt;strong&gt;Reasoning tokens&lt;/strong&gt;, the notes a "thinking" model writes to itself before it answers, are billed as output in both modes. With 2,000 of them, gpt-5.6's 6.5× shrinks to 2.6×. I didn't measure how often models misquote the search text. That depends on the model and the file, so count your refusals. Claude and Gemini also split text into tokens differently from tiktoken, so the counts will shift. The ratio follows the prices.&lt;/p&gt;

&lt;p&gt;A cheaper price per token doesn't do this job either. The gateway I mentioned sells these models 10–29% below list. But a discount takes the same share off both modes: at our price, gpt-5.6's whole-file edit is $0.054, still 5.5 times the change-only edit at full list price.&lt;/p&gt;

&lt;h3&gt;
  
  
  The takeaway
&lt;/h3&gt;

&lt;p&gt;The fix was one character. Ask for the change, apply it with an exact-match check, and you pay once to have the file read instead of paying output price to have it typed back.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>cost</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Your chatbot's average call costs $0.006 — one pasted file can make it $2.03</title>
      <dc:creator>Andrey Altrouter</dc:creator>
      <pubDate>Sat, 24 Oct 2026 11:00:02 +0000</pubDate>
      <link>https://dev.to/altrouter/your-chatbots-average-call-costs-0006-one-pasted-file-can-make-it-203-4bl6</link>
      <guid>https://dev.to/altrouter/your-chatbots-average-call-costs-0006-one-pasted-file-can-make-it-203-4bl6</guid>
      <description>&lt;p&gt;I priced a typical support-bot question on Claude Sonnet 5.5 at list price: $0.006. The same endpoint, with the same key and the same code, will accept a request that costs $2.03, as much as 339 typical questions at once. Nothing in the API asks whether you meant it. All it takes is one user pasting a big file into your text box.&lt;/p&gt;

&lt;h3&gt;
  
  
  Every request has a price ceiling
&lt;/h3&gt;

&lt;p&gt;You pay per &lt;strong&gt;token&lt;/strong&gt;, a chunk of text about three-quarters of an English word. Prices are quoted per million tokens, separately for &lt;strong&gt;input&lt;/strong&gt; (everything you send: instructions, chat history, the user's message) and &lt;strong&gt;output&lt;/strong&gt; (what the model writes back). The &lt;strong&gt;context window&lt;/strong&gt; is the most tokens one request may hold. &lt;strong&gt;&lt;code&gt;max_tokens&lt;/code&gt;&lt;/strong&gt; is the request field that caps the length of the reply.&lt;/p&gt;

&lt;p&gt;Put them together and you get the most a single request can cost: &lt;em&gt;input tokens you let in × input price + &lt;code&gt;max_tokens&lt;/code&gt; × output price&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;If you don't limit the input, the first term is the whole context window. Nobody types a million tokens by hand, but three ordinary things get you there. A user pastes a log file into the chat box. A bug appends the same document to the prompt on every loop. A search step hands the model whole files instead of the relevant paragraphs. The API treats none of these as an error. If it fits the window, it's a valid request, and it's billed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Five models, one full window
&lt;/h3&gt;

&lt;p&gt;I compared a typical call (1,500 tokens in, 300 out: a system prompt, a short history and a question) with a call that fills the window and gets a 4,096-token reply. Context windows and list prices, meaning the vendors' own published per-token prices, come from the catalog of the LLM gateway I work on (snapshot of 16.10.2026), where prices sit 10–29% below list, which shrinks every dollar cell and leaves the ratio column untouched.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Context window&lt;/th&gt;
&lt;th&gt;Input, $/1M&lt;/th&gt;
&lt;th&gt;Typical call&lt;/th&gt;
&lt;th&gt;Full-window call&lt;/th&gt;
&lt;th&gt;Ratio&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;gpt-5.6-mini&lt;/td&gt;
&lt;td&gt;400K&lt;/td&gt;
&lt;td&gt;$0.20&lt;/td&gt;
&lt;td&gt;$0.0007&lt;/td&gt;
&lt;td&gt;$0.08&lt;/td&gt;
&lt;td&gt;127×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;claude-sonnet-4-5&lt;/td&gt;
&lt;td&gt;200K&lt;/td&gt;
&lt;td&gt;$3&lt;/td&gt;
&lt;td&gt;$0.0090&lt;/td&gt;
&lt;td&gt;$0.65&lt;/td&gt;
&lt;td&gt;72×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gpt-5.6&lt;/td&gt;
&lt;td&gt;400K&lt;/td&gt;
&lt;td&gt;$2&lt;/td&gt;
&lt;td&gt;$0.0066&lt;/td&gt;
&lt;td&gt;$0.84&lt;/td&gt;
&lt;td&gt;127×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;claude-sonnet-5.5&lt;/td&gt;
&lt;td&gt;1M&lt;/td&gt;
&lt;td&gt;$2&lt;/td&gt;
&lt;td&gt;$0.0060&lt;/td&gt;
&lt;td&gt;$2.03&lt;/td&gt;
&lt;td&gt;339×&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gpt-6-astra&lt;/td&gt;
&lt;td&gt;1.05M&lt;/td&gt;
&lt;td&gt;$10&lt;/td&gt;
&lt;td&gt;$0.0300&lt;/td&gt;
&lt;td&gt;$10.66&lt;/td&gt;
&lt;td&gt;355×&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The ratio doesn't follow the price. It follows the window. Across the whole catalog, every 200K model lands between 72× and 79×, and every 1M model between 258× and 375×. claude-haiku-5.5 at $0.10 per million and claude-fable-5.1 at $10 both come out at exactly 339×.&lt;/p&gt;

&lt;p&gt;That's how an upgrade can cut your price per token and still raise your worst case. Moving from claude-sonnet-4-5 to claude-sonnet-5.5 makes input a third cheaper, and the typical call drops from $0.0090 to $0.0060. The full-window call goes from $0.65 to $2.03, because the window grew five times.&lt;/p&gt;

&lt;h3&gt;
  
  
  A megabyte of logs is half a million tokens
&lt;/h3&gt;

&lt;p&gt;How much text fills the window depends on what's pasted, and the usual rule of thumb, four characters per token, only holds for prose. I counted with tiktoken, OpenAI's open-source &lt;strong&gt;tokenizer&lt;/strong&gt; (the program that cuts text into tokens), using &lt;code&gt;o200k_base&lt;/code&gt;, the encoding behind current GPT models.&lt;/p&gt;

&lt;p&gt;95 KB of English articles from this blog came out at 24,434 tokens, 3% above the four-characters estimate. A synthetic one-megabyte server log came out at 529,646 tokens, 2.1 times the estimate. It's lines like &lt;code&gt;2026-10-14T09:23:18.178Z WARN req=c41f9dfd-… POST /health/66746 201 1647ms&lt;/code&gt;, and random hex IDs and long numbers split into many small tokens.&lt;/p&gt;

&lt;p&gt;So one megabyte of logs is $1.06 of input on claude-sonnet-5.5 and $2.12 on claude-opus-5.5. On gpt-5.6 it doesn't fit at all: the 400K window is full at 0.76 MB, and the API answers with an error instead. The paste that costs you money is the one that just fits.&lt;/p&gt;

&lt;p&gt;It also means a guard that counts characters lets through twice the tokens you planned for whenever someone pastes machine output.&lt;/p&gt;

&lt;h3&gt;
  
  
  Set the ceiling yourself, in dollars
&lt;/h3&gt;

&lt;p&gt;Decide what one request may cost, and check it before sending: count the input tokens, add the reply ceiling, multiply by your model's prices, refuse anything above the limit. It needs &lt;code&gt;pip install tiktoken&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="n"&gt;enc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tiktoken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_encoding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;o200k_base&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# GPT-5.x tokenizer; others differ
&lt;/span&gt;&lt;span class="n"&gt;PRICE_IN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PRICE_OUT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;2.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;12.00&lt;/span&gt;          &lt;span class="c1"&gt;# $ per 1M tokens, gpt-5.6 list price
&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;1_000&lt;/span&gt;                         &lt;span class="c1"&gt;# send the same value as max_tokens
&lt;/span&gt;&lt;span class="n"&gt;MAX_USD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.02&lt;/span&gt;                             &lt;span class="c1"&gt;# the most one request may cost
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;tokens_in&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;enc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&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="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;usd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tokens_in&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;PRICE_IN&lt;/span&gt; &lt;span class="o"&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="n"&gt;PRICE_OUT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;1e6&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;usd&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;MAX_USD&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&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;refused: up to $&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;usd&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, limit $&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;MAX_USD&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;usd&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;ok: up to $&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;check&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="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&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="si"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Where is my order?&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;paste&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026-10-14T09:23:18.178Z WARN req=c41f9dfd-b484 POST /health 201&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;5_000&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;check&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;paste&lt;/span&gt;&lt;span class="p"&gt;}])&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;ValueError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&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;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It prints &lt;code&gt;ok: up to $0.0120&lt;/code&gt;, then &lt;code&gt;refused: up to $0.32, limit $0.02&lt;/code&gt;. The 325 KB paste never reaches the API, so it costs nothing. The &lt;code&gt;+ 4&lt;/code&gt; is a rough allowance for the few tokens of wrapping each message carries.&lt;/p&gt;

&lt;p&gt;Look at where the short question's $0.012 comes from: almost all of it is &lt;code&gt;max_tokens&lt;/code&gt;, 1,000 reply tokens at $12 per million. If your bot usually answers in 300 tokens, a lower &lt;code&gt;max_tokens&lt;/code&gt; is the cheaper lever. At these settings the guard leaves room for 4,000 input tokens, about 2,700 words. If that's too tight, raise the limit. What matters is that it's a number you picked. Instead of refusing, you can also trim the paste to fit and tell the user.&lt;/p&gt;

&lt;h3&gt;
  
  
  What this doesn't solve
&lt;/h3&gt;

&lt;p&gt;tiktoken is OpenAI's tokenizer. Claude and Gemini split text differently, so on those models treat the count as an estimate and keep a margin. A per-request ceiling also says nothing about how many requests arrive: a runaway loop stays under the limit every single time. That needs a daily cap. Caps have a gap of their own: a request's price is known only after the reply ends, so spend limits are usually checked before a request and charged after it. Ours works that way too, and the request that crosses the limit is still billed in full.&lt;/p&gt;

&lt;h3&gt;
  
  
  The takeaway
&lt;/h3&gt;

&lt;p&gt;Until you set a lower one, the context window is the price ceiling of every request. Pick a dollar limit, count tokens rather than characters, and check before you send.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>cost</category>
      <category>beginners</category>
    </item>
    <item>
      <title>The "next version" of your LLM can cost 33% less — or 4x more</title>
      <dc:creator>Andrey Altrouter</dc:creator>
      <pubDate>Fri, 23 Oct 2026 11:00:01 +0000</pubDate>
      <link>https://dev.to/altrouter/the-next-version-of-your-llm-can-cost-33-less-or-4x-more-58hl</link>
      <guid>https://dev.to/altrouter/the-next-version-of-your-llm-can-cost-33-less-or-4x-more-58hl</guid>
      <description>&lt;p&gt;Upgrading an LLM looks like the cheapest change in your codebase. You replace &lt;code&gt;"gemini-2.5-flash"&lt;/code&gt; with &lt;code&gt;"gemini-3.5-flash"&lt;/code&gt; and redeploy. One string, no new library, no new API.&lt;/p&gt;

&lt;p&gt;On your invoice it is not one string. On the same traffic, that edit makes the bill 4.1 times bigger. The same kind of edit on Claude Sonnet makes it a third smaller. The version number tells you nothing about which way the price will move.&lt;/p&gt;

&lt;h3&gt;
  
  
  What changes when you change the model string
&lt;/h3&gt;

&lt;p&gt;Every request names a model by its &lt;strong&gt;model ID&lt;/strong&gt; — the string like &lt;code&gt;gpt-5.6&lt;/code&gt; that you pass in the &lt;code&gt;model&lt;/code&gt; field. You pay per &lt;strong&gt;token&lt;/strong&gt;: a chunk of text, roughly three-quarters of an English word. Prices are quoted per million tokens and split in two: &lt;strong&gt;input tokens&lt;/strong&gt; are everything you send (instructions, chat history, documents), &lt;strong&gt;output tokens&lt;/strong&gt; are what the model writes back. Output typically costs 4 to 8 times more than input.&lt;/p&gt;

&lt;p&gt;Each model ID has its own pair of prices. When a vendor ships a new version, it sets a new pair, and nothing obliges it to go down. Sometimes the new model is cheaper to run and the vendor passes that on. Sometimes the new model is positioned a tier higher and priced like it.&lt;/p&gt;

&lt;p&gt;Your code doesn't notice either way. The request format is identical, the response looks the same, and the price change arrives silently at the end of the month.&lt;/p&gt;

&lt;h3&gt;
  
  
  Six upgrades, one workload
&lt;/h3&gt;

&lt;p&gt;To compare like with like, I fixed one workload: a small app that sends 20 million input tokens and gets back 4 million output tokens a month, a 5:1 ratio. Then I priced it before and after a same-vendor upgrade, using the vendors' &lt;strong&gt;list prices&lt;/strong&gt; (their own published per-token prices) from our catalog snapshot of 16.10.2026.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Upgrade&lt;/th&gt;
&lt;th&gt;Old, in / out per 1M&lt;/th&gt;
&lt;th&gt;New, in / out per 1M&lt;/th&gt;
&lt;th&gt;Monthly bill&lt;/th&gt;
&lt;th&gt;Change&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;claude-sonnet-4-5 → claude-sonnet-5.5&lt;/td&gt;
&lt;td&gt;$3 / $15&lt;/td&gt;
&lt;td&gt;$2 / $10&lt;/td&gt;
&lt;td&gt;$120 → $80&lt;/td&gt;
&lt;td&gt;−33%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;claude-opus-4-5 → claude-opus-5.5&lt;/td&gt;
&lt;td&gt;$5 / $25&lt;/td&gt;
&lt;td&gt;$4 / $20&lt;/td&gt;
&lt;td&gt;$200 → $160&lt;/td&gt;
&lt;td&gt;−20%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gpt-4o → gpt-5.6&lt;/td&gt;
&lt;td&gt;$2.50 / $10&lt;/td&gt;
&lt;td&gt;$2 / $12&lt;/td&gt;
&lt;td&gt;$90 → $88&lt;/td&gt;
&lt;td&gt;−2%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gpt-4o-mini → gpt-5.6-mini&lt;/td&gt;
&lt;td&gt;$0.15 / $0.60&lt;/td&gt;
&lt;td&gt;$0.20 / $1.20&lt;/td&gt;
&lt;td&gt;$5.40 → $8.80&lt;/td&gt;
&lt;td&gt;+63%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gemini-2.5-flash → gemini-3.5-flash&lt;/td&gt;
&lt;td&gt;$0.30 / $2.50&lt;/td&gt;
&lt;td&gt;$1.50 / $9&lt;/td&gt;
&lt;td&gt;$16 → $66&lt;/td&gt;
&lt;td&gt;+313%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Same direction in the name, opposite directions on the bill. Both Anthropic upgrades got cheaper. OpenAI's flagship stayed flat, while its small model doubled its output price. Google's Flash line went up the most: five times on input, 3.6 times on output. A sixth pair I checked, gemini-2.5-pro → gemini-3.1-pro, comes out +35% on the same workload.&lt;/p&gt;

&lt;p&gt;The bottom two rows are the trap for beginners. Small, cheap models are the ones tutorials hardcode, and "cheap" in an older tutorial is a statement about an older price list.&lt;/p&gt;

&lt;h3&gt;
  
  
  When the answer depends on your traffic
&lt;/h3&gt;

&lt;p&gt;The gpt-4o → gpt-5.6 row deserves a second look. Input got cheaper by $0.50 per million, output got pricier by $2. Which one wins depends on your mix, and the break-even is exactly 4 input tokens per output token. At 4:1 the bill doesn't move. At 12:1, an app that stuffs documents into every prompt, the upgrade saves 10%. At 2:1, an app that writes long answers to short questions, it costs 6.7% more.&lt;/p&gt;

&lt;p&gt;There is a second multiplier the price list doesn't show. Newer models often write more for the same task: longer answers, or &lt;strong&gt;reasoning tokens&lt;/strong&gt;, hidden "thinking" text the model produces before answering and bills as output even though you never see it. The price per token can fall while the tokens per task grow.&lt;/p&gt;

&lt;p&gt;For the Sonnet upgrade I computed the margin: on this workload, claude-sonnet-5.5 stays cheaper than claude-sonnet-4-5 until its output doubles. That sounds like plenty of room. With reasoning switched on, it isn't always.&lt;/p&gt;

&lt;h3&gt;
  
  
  Check before you bump
&lt;/h3&gt;

&lt;p&gt;Before changing a model ID, put both price pairs and your real monthly token counts into a few lines of arithmetic. Your counts are in the &lt;code&gt;usage&lt;/code&gt; field of every API response, or on your provider's billing dashboard.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;monthly&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;in_m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;out_m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;price_in&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;price_out&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;in_m&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;price_in&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;out_m&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;price_out&lt;/span&gt;

&lt;span class="c1"&gt;# your traffic, millions of tokens per month
&lt;/span&gt;&lt;span class="n"&gt;IN_M&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OUT_M&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;
&lt;span class="c1"&gt;# expected output growth on the new model: 1.0 = same, 1.5 = 50% more
&lt;/span&gt;&lt;span class="n"&gt;OUT_GROWTH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.5&lt;/span&gt;

&lt;span class="n"&gt;old&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;monthly&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IN_M&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OUT_M&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;2.50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;               &lt;span class="c1"&gt;# gemini-2.5-flash
&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;monthly&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IN_M&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OUT_M&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;OUT_GROWTH&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;9.00&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# gemini-3.5-flash
&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;$&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;old&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; -&amp;gt; $&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;old&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;old&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;%&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="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This prints &lt;code&gt;$16.00 -&amp;gt; $84.00  (+425%)&lt;/code&gt;. The 1.5 is a guess, so replace it with a measurement: send the same few hundred real requests to both models and compare the output token totals. That measured ratio is the number worth trusting.&lt;/p&gt;

&lt;p&gt;Upgrades usually come with a good reason, like better answers or a deprecation notice for the old model. The point isn't to avoid them. It's to know what the upgrade costs before the invoice tells you.&lt;/p&gt;

&lt;h3&gt;
  
  
  What this doesn't solve
&lt;/h3&gt;

&lt;p&gt;This compares price, not quality. A pricier model that gets the task right on the first try can beat a cheap one you have to call three times, and I haven't measured that here. The table also uses list prices. Resellers price below list, and not always by the same percentage across versions: in our own catalog claude-opus-4-5 is 25% off and claude-opus-5.5 is 15% off, so at our prices that upgrade saves 9%, not 20%. Compare the prices you actually pay.&lt;/p&gt;

&lt;h3&gt;
  
  
  The takeaway
&lt;/h3&gt;

&lt;p&gt;A new model ID is a new price list. Before you change the string, multiply your own token counts by both price pairs, then measure how many output tokens the new model actually writes.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>cost</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
