<?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: kimbeomgyu</title>
    <description>The latest articles on DEV Community by kimbeomgyu (@kimbeomgyu).</description>
    <link>https://dev.to/kimbeomgyu</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%2F4006378%2Fba933921-f5db-4aa8-8a74-d7f35ae25160.jpg</url>
      <title>DEV Community: kimbeomgyu</title>
      <link>https://dev.to/kimbeomgyu</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kimbeomgyu"/>
    <language>en</language>
    <item>
      <title>7 things I learned trying to stop LLM API bills from silently exploding</title>
      <dc:creator>kimbeomgyu</dc:creator>
      <pubDate>Sun, 12 Jul 2026 15:35:45 +0000</pubDate>
      <link>https://dev.to/kimbeomgyu/7-things-i-learned-trying-to-stop-llm-api-bills-from-silently-exploding-3h0i</link>
      <guid>https://dev.to/kimbeomgyu/7-things-i-learned-trying-to-stop-llm-api-bills-from-silently-exploding-3h0i</guid>
      <description>&lt;p&gt;My first real LLM bill surprise wasn't dramatic. No infinite loop, no viral spike. A retry policy I'd written months earlier met a flaky endpoint, and a background job quietly re-sent the same long prompt all night. The bill was just... 40x normal. Nothing "failed", so nothing alerted.&lt;/p&gt;

&lt;p&gt;I've spent the last few weeks building a hard spending cap for LLM calls, and most of what I learned wasn't about caps at all. It was about how many small boring ways money leaks when the meter only exists on the provider's side. Here's the list I wish someone had handed me.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Billing dashboards are rear-view mirrors
&lt;/h3&gt;

&lt;p&gt;Every provider dashboard answers "how much did you spend?" hours later, after aggregation. Nobody answers "how much are you spending right now, from which feature?" By the time the dashboard shows the leak, the leak already happened. If you take one thing from this post: put the meter inside your process, at the call site, where it can act before the request instead of reporting after it.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Total spend is a useless number
&lt;/h3&gt;

&lt;p&gt;"$120 today" tells you nothing actionable. The question that matters is which feature is leaking. The fix is one tag per call (&lt;code&gt;feature: "chat"&lt;/code&gt;, &lt;code&gt;feature: "summarizer"&lt;/code&gt;) and a per-feature breakdown. The first time I ran a breakdown on a real app, a background enrichment job I'd forgotten about turned out to be 60% of spend. For me, the per-feature breakdown was the entire diagnosis.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Every provider reports usage differently, and they all lie a little
&lt;/h3&gt;

&lt;p&gt;OpenAI gives you &lt;code&gt;prompt_tokens&lt;/code&gt; and &lt;code&gt;completion_tokens&lt;/code&gt;. Anthropic gives &lt;code&gt;input_tokens&lt;/code&gt;/&lt;code&gt;output_tokens&lt;/code&gt;. Gemini nests everything in &lt;code&gt;usageMetadata&lt;/code&gt;. Bedrock and Cohere have their own shapes. Then it gets worse: cached input tokens bill at a different rate, and reasoning tokens are billed as output even though you never see them. Two providers even disagree on whether reasoning tokens are counted inside the output number at all. xAI and Gemini report them outside it, so if you naively bill the output count you're undercounting exactly when reasoning models get expensive. Budget a week of reading API docs that contradict the actual responses.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Streaming hides the receipt until the very end
&lt;/h3&gt;

&lt;p&gt;With &lt;code&gt;stream: true&lt;/code&gt;, usage arrives (if at all) in the final chunk. OpenAI won't even send it unless you pass &lt;code&gt;stream_options: { include_usage: true }&lt;/code&gt;. Anthropic splits it across &lt;code&gt;message_start&lt;/code&gt; and &lt;code&gt;message_delta&lt;/code&gt; events you have to assemble, and the delta is cumulative, so adding instead of replacing double-counts. Gemini sends a running &lt;code&gt;usageMetadata&lt;/code&gt; where only the last value is real. Any cost tracking that ignores streaming is blind to the calls most modern apps actually make.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. A cap that checks after the call is a receipt, not a guard
&lt;/h3&gt;

&lt;p&gt;The naive cap goes: call, add to a counter, compare. It always overshoots by one call. Concurrency makes it much worse. Ten parallel requests all read the same counter, all pass the check, all land. I only really fixed this with an atomic reserve-then-settle: estimate the cost, reserve it against the cap before the call (one atomic operation, Lua script if the counter lives in Redis), settle the difference after. If you run multiple workers the counter has to live in shared storage anyway, otherwise each worker politely enforces its own private budget while the fleet blows through ten of them.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Retry storms are the leak nobody instruments
&lt;/h3&gt;

&lt;p&gt;The single most expensive bug class I found wasn't prompts. It was retries: exponential backoff on a 429 while the request is actually succeeding on the provider's side, queue re-drives, at-least-once delivery re-running completed jobs. Track consecutive failures per feature and alert when the streak gets weird. A hard cap turns this failure mode from "unbounded bill" into "capped error you notice in the morning", and that trade is almost always correct for background workloads.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Caps and traces are two sides of the same thing
&lt;/h3&gt;

&lt;p&gt;A reader of my first post put it better than I had: cost caps and execution traces are both answers to "what is my agent doing when I'm not looking?" The cap is the brake. A per-call spend event (feature, model, usd, running total) piped into the logs you already have is the gauge. You want both, because a brake without a gauge just means you get stopped without knowing why.&lt;/p&gt;




&lt;p&gt;I ended up packaging all of this into a small MIT-licensed library called &lt;a href="https://github.com/kimbeomgyu/budget-guard" rel="noopener noreferrer"&gt;budget-guard&lt;/a&gt;: zero-dependency hard caps that hold under concurrent workers, per-feature attribution, streaming usage, and adapters for Vercel AI SDK, LangChain.js, LlamaIndex.TS and Mastra. But honestly, even if you never install anything: tag your calls, meter before the request, and go look at what your retries did last night.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>llm</category>
      <category>node</category>
    </item>
    <item>
      <title>I built a free circuit breaker for your LLM API bill</title>
      <dc:creator>kimbeomgyu</dc:creator>
      <pubDate>Sun, 28 Jun 2026 10:39:29 +0000</pubDate>
      <link>https://dev.to/kimbeomgyu/i-built-a-free-circuit-breaker-for-your-llm-api-bill-3ifk</link>
      <guid>https://dev.to/kimbeomgyu/i-built-a-free-circuit-breaker-for-your-llm-api-bill-3ifk</guid>
      <description>&lt;p&gt;Update: a few things landed since this was published. v0.2 added a Redis shared store so multiple workers can share one cap. After that came an onSpend hook (per-call cost events), streaming usage for OpenAI/Anthropic/Gemini, monthly caps with timezones, and adapters for Vercel AI SDK, LangChain.js, LlamaIndex.TS and Mastra. As of v0.6 the caps are also concurrency-safe (atomic reservation) and there's a built-in pre-call estimator, so over-budget calls get blocked before they're sent.&lt;/p&gt;

&lt;p&gt;We've all seen the screenshot: someone leaves an agent running overnight, a retry loop goes sideways, and they wake up to a bill that's 10x what they expected — "$40 from a $5 task." The scary part isn't the money, it's that &lt;strong&gt;nothing stopped it&lt;/strong&gt;. You find out when the invoice lands.&lt;/p&gt;

&lt;p&gt;I wanted the dumbest possible fix: a hard cap that just &lt;em&gt;blocks&lt;/em&gt; the next call when you're over budget. Not a dashboard I have to remember to check, not a gateway I have to deploy. So I built &lt;strong&gt;budget-guard&lt;/strong&gt; — a tiny drop-in wrapper for the OpenAI/Anthropic clients.&lt;/p&gt;

&lt;h2&gt;
  
  
  The whole idea in 3 lines
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;guard&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;budget-guard&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ai&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;guard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;openai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;project&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;dailyCapUSD&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gpt-4o&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;summarize&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once today's spend for &lt;code&gt;my-app&lt;/code&gt; crosses $50, the next &lt;code&gt;create()&lt;/code&gt; throws &lt;code&gt;BudgetExceededError&lt;/code&gt; &lt;strong&gt;before&lt;/strong&gt; it bills you. A runaway loop dies at the breaker instead of draining your account. It stays out of your critical path — your calls still go straight to the provider; budget-guard just meters the usage and trips when you're over.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus: where did the money go?
&lt;/h2&gt;

&lt;p&gt;It tags spend per feature, so you can finally answer "what cost that?":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;spendReport&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;budget-guard&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nf"&gt;spendReport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// → { summarize: 2.41, chat: 0.88 }  (today, USD)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It auto-detects OpenAI (&lt;code&gt;prompt_tokens&lt;/code&gt;/&lt;code&gt;completion_tokens&lt;/code&gt;) and Anthropic (&lt;code&gt;input_tokens&lt;/code&gt;/&lt;code&gt;output_tokens&lt;/code&gt;) usage shapes; for anything else you pass a one-line extractor.&lt;/p&gt;

&lt;h2&gt;
  
  
  Being honest about v0.1
&lt;/h2&gt;

&lt;p&gt;I'd rather tell you the limits than have you find them on an invoice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;In-memory, per-process.&lt;/strong&gt; Great for a single script, agent, or worker. Run multiple instances and the cap is per-instance and resets on restart. A shared (Redis) store is the obvious next step.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enforced on the next call.&lt;/strong&gt; No pre-call token estimation yet, so one call can overshoot before the breaker trips.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prices are a hand-maintained table.&lt;/strong&gt; PRs welcome to keep them current.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So today it's best for solo devs, side projects, and single-process agents — exactly the people most likely to get surprised by a bill.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it / break it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i budget-guard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;MIT, no runtime deps, TypeScript. Repo: &lt;a href="https://github.com/kimbeomgyu/budget-guard" rel="noopener noreferrer"&gt;https://github.com/kimbeomgyu/budget-guard&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'd love feedback on two things: should it block by default, or warn + callback? And if you run a fleet, would a Redis-backed shared cap actually get used, or do you already handle this at a gateway layer? Issues and PRs very welcome.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>ai</category>
      <category>opensource</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
