Skip to content
← All writing

Reasoning effort is a cost dial, not a quality setting

4 min read
.md
Cover illustration for Reasoning effort is a cost dial, not a quality setting

Reasoning modes are billed as output tokens, so a model left on its default thinking budget can cost several times more than needed on trivial calls. Each provider exposes the control differently, so normalise it behind one interface and set the level per agent role rather than globally.

Every message that arrives at my platform hits a classifier first, which picks the specialist agent to handle it. Small model, short prompt, four possible answers.

It was taking 4.6 seconds.

I checked the model. Fast. I checked the prompt. Short. Nothing about the job explained it, and since this call sits in front of every single message, 4.6 seconds was the floor for the whole product.

Then I read the response payload properly. The model was thinking. Hundreds of tokens of internal deliberation, on a multiple-choice question, because extended reasoning was on by default and I had never turned it off.

You are paying for the thinking

The thinking happens before the answer you see, and you are charged for it. Anthropic bills thinking tokens as output tokens. So does OpenAI with reasoning tokens. Output tokens are the expensive ones.

Which means leaving the default alone is a spending decision, taken once and then applied to every call you ever make, whether or not the work deserves it.

On my classifier it was about ten times the tokens needed to say one word.

Every provider spells it differently

This is the part that makes it easy to ignore. There is no shared parameter, so wiring it up means touching each provider adapter separately.

ProviderParameterValues
Anthropicthinking.budget_tokensAn integer budget
OpenAIreasoning_effortlow, medium, high
BytePlusextra_body.reasoning_effortminimal, low, medium, hi
DeepSeekNot configurableAlways on for reasoner models
OpenRouterPassed throughDepends on the underlying model

An integer budget and a three-value enum do not map onto each other cleanly, and pretending they do is how you end up with medium meaning something different on every path. I normalise to four internal levels and let each adapter translate, accepting that the translation is approximate.

type Effort = 'minimal' | 'low' | 'medium' | 'high'

const ANTHROPIC_BUDGET: Record<Effort, number | null> = {
  minimal: null,     // omit the thinking block entirely
  low: 5_000,
  medium: 10_000,
  high: 20_000,
}

export function applyEffort(provider: Provider, body: RequestBody, effort: Effort) {
  switch (provider) {
    case 'anthropic': {
      const budget = ANTHROPIC_BUDGET[effort]
      if (budget) body.thinking = { type: 'enabled', budget_tokens: budget }
      return body
    }
    case 'openai':
      body.reasoning_effort = effort === 'minimal' ? 'low' : effort
      return body
    case 'byteplus':
      body.extra_body = { ...body.extra_body, reasoning_effort: effort === 'high' ? 'hi' : effort }
      return body
    default:
      return body // provider ignores it; not an error
  }
}

The default branch returning the body untouched is deliberate. A provider that does not support the control should be a no-op, not a thrown error, or adding a provider breaks every call site.

Which calls actually need it?

The ones where the model has to work something out. Not the ones where it has to pick from a list.

I set effort per agent role rather than per request, because roles are stable and requests are not:

  • Routing and classification: minimal. The output space is a handful of tokens. Deliberation adds latency and changes the answer roughly never.
  • Summarising and formatting: low. Structure matters, invention does not.
  • Planning and multi-step tool use: medium. Getting the order wrong is expensive downstream.
  • Debugging and analysis: high. This is what the budget is for.

Tie the level to the role in the same place you choose the model, and it stays consistent as prompts get edited by people who are not thinking about token budgets.

Measure it, because intuition is wrong here

I would not have found the classification problem by reading code. Aggregate token counts looked normal. Per-role counts, once I tagged usage by role, showed a step that produced four words consuming a large share of the bill.

Route everything through a gateway that records tokens per call with the role attached. Cloudflare AI Gateway does this in front of multiple providers, which is convenient if you are already routing across several.

The number to watch after you turn the dial down is not cost. It is your quality signal for that role: retry rate, escalation rate, validation failures. Dropping a role to minimal and watching escalations climb tells you the reasoning was doing something. Dropping it and seeing nothing move tells you it was not.

My classification path lost nothing measurable at minimal effort. The debugging path would be worse at anything below high, and I have not tried to save money there.

Sources

Written by Elson Tan, Head of Technology and co-founder at Nedex Group, working on AI harness and agent infrastructure.

AboutRSS
  • 4 min read

    Metering tokens when the bill is the product

    Billing per message is easy and wrong: one user sends a sentence, another uploads a report. Metering the tokens you actually spend is harder, and the hard parts are idempotency, allowance checks and what to do mid-conversation.

  • 4 min read

    Route cheap models to orchestration, not to the work

    Most agent platforms pick one model and use it everywhere. Splitting model selection by role rather than by task is where the cost curve actually bends.

  • 4 min read

    Persist once, reference by id, hydrate on demand

    An agent that reads your uploaded file perfectly and then forgets it on the next turn is not a memory bug. It is a context design where file content was only ever turn-transient. The fix also cuts token cost.

Get in touch

Tell me who you are and what you are working on.

Your details are used only to reply to this message.