cd /news/large-language-models/this-llm-cost-function-looks-right-c… · home › topics › large-language-models › article
[ARTICLE · art-166524] src=dev.to ↗ pub= topic=large-language-models verified=true sentiment=· neutral

This LLM cost function looks right. Can you spot the bug?

A developer demonstrated a common cost-tracking bug in OpenAI's Responses API: because output_tokens already includes reasoning_tokens, adding the reasoning field to the output count double-bills reasoning, inflating estimates by 58% in a sample calculation ($38.00 versus $24.00 per 10,000 requests). The writeup notes that Google's Gemini API splits usage differently, with thoughtsTokenCount outside candidatesTokenCount, so no single formula works across providers and each API's token definitions must be read before writing cost code.

by read3 min views2 publishedNov 9, 2026

OpenAI's reasoning models bill you for thinking you never see. So it seems careful to add that thinking to your cost tracking, like this:

from decimal import Decimal

INPUT_RATE = Decimal("0.50")
OUTPUT_RATE = Decimal("2.00")

def estimate_cost(usage):
    """Estimated USD cost of one OpenAI Responses API call."""
    reasoning = usage["output_tokens_details"]["reasoning_tokens"]
    billed_output = usage["output_tokens"] + reasoning
    return (usage["input_tokens"] * INPUT_RATE
            + billed_output * OUTPUT_RATE) / 1_000_000

It runs, and it returns a believable number. It's also wrong for every response that used reasoning. Have a guess at why before you read on.

OpenAI's Responses API reports everything the model generates as output_tokens. Its token counting guide says: "Reported output token usage includes all tokens generated by the model, not only the text visible in a response." The reasoning_tokens value under output_tokens_details breaks that number down: it tells you how many of the output tokens were reasoning. So estimate_cost counts the reasoning twice.

You can see it in the example response in OpenAI's reasoning guide:

{
  "input_tokens": 75,
  "input_tokens_details": { "cached_tokens": 0 },
  "output_tokens": 1186,
  "output_tokens_details": { "reasoning_tokens": 1024 },
  "total_tokens": 1261
}

The total is 75 + 1,186 = 1,261, so the 1,024 reasoning tokens are already inside the 1,186. Add them again and your log says 2,285 tokens for a response that reported 1,261. The same guide confirms the billing: "While reasoning tokens are not visible via the API, they still occupy space in the model's context window and are billed as output tokens."

Use output_tokens as it is. Here's the fixed function, with a made-up response to run both versions on:

def estimate_cost_fixed(usage):
    """Same estimate; output_tokens already includes the reasoning tokens."""
    billed_output = usage["output_tokens"]
    return (usage["input_tokens"] * INPUT_RATE
            + billed_output * OUTPUT_RATE) / 1_000_000

usage = {
    "input_tokens": 1200,
    "output_tokens": 900,
    "output_tokens_details": {"reasoning_tokens": 700},
}

for label, cost in [("Counted once: ", estimate_cost_fixed(usage)),
                    ("Counted twice:", estimate_cost(usage))]:
    print(f"{label} ${cost:.4f} per request, ${cost * 10_000:.2f} per 10,000")

Put both blocks in one file and run it:

Counted once:  $0.0024 per request, $24.00 per 10,000
Counted twice: $0.0038 per request, $38.00 per 10,000

On this made-up response, the buggy version is 58% too high. Both numbers look perfectly plausible, which is why the mistake is easy to miss.

Google's Gemini API splits it differently. In the generateContent reference, thoughtsTokenCount is the "Number of tokens of thoughts for thinking models", and totalTokenCount is "prompt + tool-use prompt + thoughts + response candidates". The thinking tokens sit outside candidatesTokenCount, so if you treat candidatesTokenCount as all of the output, they go missing.

No single formula covers both APIs. Adding the reasoning field counts tokens twice on OpenAI, and leaving it out drops tokens on Gemini. Read each provider's definitions before you write the formula.

Some providers report cached tokens inside the input count. Gemini's promptTokenCount, for example, "includes the number of tokens in the cached content", so adding the cached count on top counts those tokens twice.

Did you spot the bug before the answer? And if you call more than one provider, how do you keep their usage fields straight? Tell me in the comments.

── more in #large-language-models 4 stories · sorted by recency
── more on @openai 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/this-llm-cost-functi…] indexed:0 read:3min 2026-11-09 · —