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.
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.
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.
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.
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.
| Cannot see | Why |
|---|---|
| Real traffic and prompt length | Only your provider's dashboard records them; the estimate here is your own guess times list prices. |
| Models set through a variable or config | Reported as unpriced rather than guessed. |
| Calls hidden behind a wrapper library | v0 matches the vendors' own SDK call shapes, line by line. |
| Caching, batching and discount programs | List prices only; override with --prices. |
- 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.
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.
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.
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.