Loading
Loading
Neville James Achieng logo
All articlesLLM Engineering

Stop guessing your LLM bill: per-call token-and-cost accounting

If you can't see what each call costs, you can't make it cheaper. A small wrapper fixes that.

3 min read

Most LLM bills are a mystery until the invoice shows up. You know the total went up. You don't know which feature did it, which call is fat, or whether that "quick" retry loop you added last week is quietly doubling spend.

The fix is unglamorous and worth it: account for every call, in tokens and in money, as it happens.

Wrap the client

Don't call the provider SDK directly all over your codebase. Put a thin wrapper around it. Every response already comes back with a usage block — input tokens, output tokens, and (if you use it) cache read/write tokens. Capture it on the way through.

async function tracked(call) {
  const res = await client.messages.create(call);
  const u = res.usage;                 // input_tokens, output_tokens, cache_*
  record({
    model: call.model,
    feature: call.feature,             // tag who's calling
    input: u.input_tokens,
    output: u.output_tokens,
    cacheRead: u.cache_read_input_tokens ?? 0,
    usd: price(call.model, u),         // tokens -> dollars
    at: Date.now(),
  });
  return res;
}

The key move is the feature tag. "We spent $40 today" is useless. "Scoring spent $31, chat spent $6, the rest is noise" tells you exactly where to look.

Tokens to dollars

Keep a small price table per model — input and output are priced differently, and cached input is cheaper. Convert on write, so your logs store real USD instead of token counts you have to mentally price later.

const inputCost  = input  / 1e6 * price.inputPerM;
const outputCost = output / 1e6 * price.outputPerM;

Now every call has a dollar figure attached, and you can sum it any way you want.

A dashboard and a tripwire

Two things make the data actually useful:

  • Aggregate by feature and model over 24h / 7d / 30d. A tiny admin page that groups spend is enough. You'll spot the expensive call within a day instead of at month-end.
  • A spend alert. Pick a daily number that should never be crossed in normal use — even something low like $2/day on a small system — and fire an alert when it is. Runaway loops and accidental model upgrades announce themselves immediately, instead of on the invoice.

Then stop paying for the same answer twice

Once you can see spend, the cheapest optimization is not calling the model at all. A lot of LLM calls are repeats — the same input showing up again. Normalize the input, hash it, cache the result:

const key = sha256(normalize(input)).slice(0, 16);
if (cache.has(key)) return cache.get(key);   // no model call, no spend

For anything where the same input should give the same output — classification, scoring, assessment — this is free money. The first time costs tokens; every repeat after that costs nothing and returns instantly. No quality loss, because it's the exact same input.

(If your inputs are similar but not identical, that's a fuzzier problem — vector similarity — and worth doing only after the exact-match cache, which is simpler and catches more than you'd expect.)

Why bother

Three reasons, in order:

  1. You can answer "what does this cost?" per feature, per call, on demand. That changes every conversation about whether to ship something.
  2. Regressions surface fast. A prompt change that triples output length shows up as a spend bump the next day, not a surprise later.
  3. Optimization gets a target. "Make the LLM cheaper" is vague. "The scoring call is 70% of spend and half of it is cache-able" is a task.

You don't need a platform for any of this. A wrapper, a price table, one database table, and a page that sums it. A day of work that pays for itself the first time it catches a runaway loop.


Neville James Achieng builds LLM and voice systems in Nairobi. github.com/Neville777.