<?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: focxle</title>
    <description>The latest articles on DEV Community by focxle (@focxle).</description>
    <link>https://dev.to/focxle</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%2F4071243%2F58a54a2c-e2e4-43df-85a4-20bf79557b1a.png</url>
      <title>DEV Community: focxle</title>
      <link>https://dev.to/focxle</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/focxle"/>
    <language>en</language>
    <item>
      <title>You cannot predict what an LLM call will cost before you make it</title>
      <dc:creator>focxle</dc:creator>
      <pubDate>Mon, 10 Aug 2026 12:19:49 +0000</pubDate>
      <link>https://dev.to/focxle/you-cannot-predict-what-an-llm-call-will-cost-before-you-make-it-8e9</link>
      <guid>https://dev.to/focxle/you-cannot-predict-what-an-llm-call-will-cost-before-you-make-it-8e9</guid>
      <description>&lt;p&gt;I spent a week building spending caps for AI agents on an assumption that turned out to be wrong, and the way it was wrong is more interesting than the feature.&lt;/p&gt;

&lt;p&gt;The assumption is the obvious one. Before making a model call, estimate what it will cost. If that estimate breaks the budget, refuse the call. Every budget system works this way: check, then spend.&lt;/p&gt;

&lt;p&gt;Then I measured the estimate against real calls, and it does not work. Not "needs tuning". Does not work, and cannot be made to.&lt;/p&gt;

&lt;h2&gt;
  
  
  The measurement
&lt;/h2&gt;

&lt;p&gt;12 calls recorded through OpenRouter against &lt;code&gt;openai/gpt-oss-20b:free&lt;/code&gt;, 9 of which had a comparable pre-call estimate. For each, I compared what the estimator predicted against what the vendor's own usage block reported afterwards.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;estimate ÷ actual&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;median&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3.87x too high&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;worst overestimate&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;61x too high&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;worst underestimate&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.045x, or 22x too low&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Then I replayed those 9 calls against a $0.005 per-call cap and asked how many the estimator would have refused, where the real cost turned out to be under the cap:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4 of 9 legitimate calls wrongly refused.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;At a $0.05 cap, nothing was refused at all. Which sounds like the fix, until you look at which calls were underestimated.&lt;/p&gt;

&lt;h2&gt;
  
  
  Both directions is what kills it
&lt;/h2&gt;

&lt;p&gt;If the estimate were consistently high, you would divide by a constant and move on. If it were consistently low, you would multiply.&lt;/p&gt;

&lt;p&gt;It is neither. The two calls in my sample that did not declare &lt;code&gt;max_tokens&lt;/code&gt; came in &lt;strong&gt;18x and 22x too low&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That is the whole problem in one sentence. Tighten the cap and it refuses ordinary work. Loosen it until it stops doing that, and it waves through precisely the unbounded calls a cap exists to stop, because those are the ones it underestimates.&lt;/p&gt;

&lt;p&gt;The estimator is most wrong exactly where being wrong is most expensive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it is unfixable, not undertuned
&lt;/h2&gt;

&lt;p&gt;Input tokens are knowable. You have the prompt, you can count it.&lt;/p&gt;

&lt;p&gt;Output tokens are not knowable. That is not a gap in the estimator, it is a property of generation: the model decides how much to write while it is writing. "Summarise this" can return one line or four paragraphs, and the difference is 40x in cost.&lt;/p&gt;

&lt;p&gt;No overhead constant fixes an unknown that varies 60x in one direction and 22x in the other. No rolling percentile fixes it either, because the p95 of a distribution with that spread is either useless as a limit or useless as a prediction.&lt;/p&gt;

&lt;p&gt;People do reach for &lt;code&gt;max_tokens&lt;/code&gt; here, and it does bound the worst case. But it bounds it at a ceiling nobody sets tightly, because setting it tightly truncates real answers. A ceiling of 4096 on a call that returns 200 tokens prices at 20x reality, which is the 61x row above.&lt;/p&gt;

&lt;h2&gt;
  
  
  What works instead
&lt;/h2&gt;

&lt;p&gt;Enforce against spend that has already been reconciled.&lt;/p&gt;

&lt;p&gt;After each call, the vendor tells you exactly how many tokens it used. Price that against a rate table and add it to a running total. When the total crosses the cap, stop.&lt;/p&gt;

&lt;p&gt;Reconciliation error in the same measurement: &lt;strong&gt;0&lt;/strong&gt;. Not 0.1%. The vendor tells you the number, so there is nothing to estimate.&lt;/p&gt;

&lt;p&gt;The tradeoff is real and worth stating plainly: you can overshoot by one call. The cap is checked before a call using spend up to that point, so the call that crosses the line still completes. You are trading exactness for the ability to work at all.&lt;/p&gt;

&lt;p&gt;That is a good trade, because "one call of overshoot" is bounded and knowable, and "refuses 44% of legitimate work while missing the runaways" is neither.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one place the estimate still earns its keep
&lt;/h2&gt;

&lt;p&gt;A per-call ceiling.&lt;/p&gt;

&lt;p&gt;If a single call could cost more than some absolute number, refuse it before making it. Here the estimate is fine, because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;max_tokens&lt;/code&gt; &lt;strong&gt;is&lt;/strong&gt; declared in this case, so the ceiling is real rather than guessed&lt;/li&gt;
&lt;li&gt;the threshold sits orders of magnitude away from typical, so a 3.87x error does not change the answer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A $50 per-call ceiling is not troubled by an estimator that is 4x off on a call that costs $0.02. The failure mode above only appears when the threshold is close enough to typical spend that a 4x error crosses it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Caveats, before someone else supplies them
&lt;/h2&gt;

&lt;p&gt;Nine comparable calls is a small sample. Two unbounded calls is a tiny one. It was all one model on one provider.&lt;/p&gt;

&lt;p&gt;I would take this as directional rather than conclusive, and I would be genuinely interested if anyone has measured it at larger scale — I could not find anyone who had, which is partly why I am writing it down. The failure was consistent and bidirectional across every call I looked at, and the mechanism (output length being unknowable in advance) is not specific to a model or a provider.&lt;/p&gt;

&lt;h2&gt;
  
  
  The thing I built
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://pypi.org/project/focxle/" rel="noopener noreferrer"&gt;focxle&lt;/a&gt; is what came out of it: a Python package that tells you what each of your AI agents cost across every vendor it touched, model calls and HTTP APIs together.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;focxle
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;focxle&lt;/span&gt;
&lt;span class="n"&gt;focxle&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It prints per agent, per vendor, when the process exits. It also shows what a cap &lt;em&gt;would&lt;/em&gt; have stopped, using the reconciled approach above, so you can see whether you want one before turning one on.&lt;/p&gt;

&lt;p&gt;Zero dependencies, no account, no proxy in front of your traffic, and no network calls at all on the free path. MIT, source at &lt;a href="https://github.com/focxle/focxle-python" rel="noopener noreferrer"&gt;github.com/focxle/focxle-python&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Mostly though I wanted the negative result written down somewhere. The estimate-then-refuse design is the obvious one, it is what I would have built again, and it fails in a way you only see if you go and measure it.&lt;/p&gt;

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