← Back to LLM Call Cost Scan

Why LLM API bills surprise teams, and what a code scan can catch

An LLM call is billed per token, in and out. Most surprises come from a handful of code shapes that look harmless in review, because the cost is not visible in the diff. Four of them can be found by reading the source.

1. No output cap

Without max_tokens (or max_output_tokens), a single call may generate the model's full output limit. Output tokens are usually priced several times higher than input tokens, so the one unbounded call dominates. A cap is one line and bounds the worst case.

2. A call inside a loop

A call per item is fine for ten items and a surprise for ten thousand. The scan flags calls nested under for, while, .map or forEach so a reviewer asks what bounds the loop, and whether items can share one prompt.

3. A premium model chosen once and never revisited

Prototypes tend to hard-code the largest model. Many production tasks (classification, extraction, short rewrites) are handled by a smaller tier from the same vendor at a fraction of the price. The scan notes the sibling; only an evaluation on your own prompts can say whether it is good enough.

4. A retired model name

Vendors retire model names. A call to a retired name fails at runtime, often in a rarely-exercised code path. The built-in list is short and unambiguous; always confirm on the vendor's deprecations page.

What a static scan cannot see

Cannot seeWhy
Real traffic and prompt lengthOnly your provider's dashboard records them; the estimate here is your own guess times list prices.
Models set through a variable or configReported as unpriced rather than guessed.
Calls hidden behind a wrapper libraryv0 matches the vendors' own SDK call shapes, line by line.
Caching, batching and discount programsList prices only; override with --prices.

Wiring it into CI as a gate

- name: Scan for costly LLM calls
  run: node ventures/llm-call-cost-scan/cli.mjs scan src --fail-on warn

By default only retired model names fail the run. --fail-on warn also fails on uncapped outputs and calls in loops, which is the stricter gate for teams that want every call bounded.

Common questions

Can a static scan tell me what I actually spend?

No. It sees what your code asks for, not how often it runs or how long real prompts are. It can bound the worst case (no output cap) and point at multipliers (a loop), but your provider's usage dashboard is the only record of actual spend.

Is a premium model always wasteful?

No. The scan reports it as a note because prototypes often hard-code the largest model and never revisit it. Whether a smaller tier is good enough is an empirical question about your prompts, so evaluate before switching.

What about SDK wrappers, LangChain or LiteLLM?

v0 matches the vendors' own SDK call shapes. A wrapper that hides the call behind its own function is outside what a line-by-line text scan can see; point the CLI at the wrapper's source or inspect where it sets the model.