uttapen

Reasoning models: effort, tokens, cost

Drive o3, DeepSeek R1 and friends with reasoning_effort or the reasoning block, read reasoning tokens in response and stream, and budget the hold.

Updated: September 7, 2026

Reasoning models "think" before they answer: they generate tokens you usually never see, which still cost money, and which lift answer quality on multi-step problems such as maths, code, and document analysis. openai/o3, openai/gpt-5, deepseek/deepseek-r1, anthropic/claude-sonnet-4.5, and google/gemini-2.5-pro are the common ones. You can spot them by the reasoning badge on the models page, or by "reasoning" in supported_parameters.

Controlling how much it thinks

Two forms are accepted, and both pass through:

OpenAI form:

resp = client.chat.completions.create(
    model="openai/o3-mini",
    messages=[{"role": "user", "content": "Why does this recursive function loop forever when n=0?\n\n" + code}],
    reasoning_effort="low",   # low | medium | high
)

Generic form (works across providers):

resp = client.chat.completions.create(
    model="deepseek/deepseek-r1",
    messages=[...],
    extra_body={"reasoning": {"effort": "high"}},
)

The reasoning block takes more options: max_tokens to cap the reasoning tokens directly (Anthropic, Gemini), exclude: true if you do not want the reasoning text in the response, and enabled: false to turn thinking off on models that allow it. Providers that only understand effort will approximate max_tokens.

For everyday work low or medium is enough. high multiplies the cost several times over and only pays off on problems where a wrong answer is more expensive than the tokens.

Reading the reasoning in a response

The usage block reports reasoning tokens separately:

"usage": {
  "prompt_tokens": 8,
  "completion_tokens": 24,
  "completion_tokens_details": {"reasoning_tokens": 50},
  "total_tokens": 32
}

Depending on the provider, reasoning_tokens may be counted inside completion_tokens or reported alongside it — so for cost, rely on the X-Uttapen-Cost-Toman header or the uttapen.meta event, never on adding the token counts up yourself. Where the provider returns the reasoning text (DeepSeek, Gemini, some Anthropic models) you will find it in message.reasoning. OpenAI models return a summary, or nothing at all.

msg = resp.choices[0].message
thinking = getattr(msg, "reasoning", None) or msg.model_extra.get("reasoning")
if thinking:
    print("[reasoning]", thinking[:300])
print(msg.content)

Streaming

While streaming, reasoning text arrives before content, in the delta.reasoning field. It is what you want for a "thinking..." indicator in your UI:

stream = client.chat.completions.create(
    model="deepseek/deepseek-r1",
    messages=[{"role": "user", "content": "Work out 17 x 23 step by step."}],
    stream=True,
    extra_body={"reasoning": {"effort": "medium"}},
)
phase = None
for chunk in stream:
    if not chunk.choices:
        continue
    d = chunk.choices[0].delta
    r = getattr(d, "reasoning", None) or (d.model_extra or {}).get("reasoning")
    if r:
        if phase != "reasoning":
            print("\n--- reasoning ---"); phase = "reasoning"
        print(r, end="", flush=True)
    if d.content:
        if phase != "answer":
            print("\n--- answer ---"); phase = "answer"
        print(d.content, end="", flush=True)

Streaming is strongly recommended with reasoning models: a long think can take more than a minute, and in non-streaming mode the "120 seconds without a byte" watchdog fires. When you stream, the reasoning chunks keep the connection alive.

Holds and cost

Reasoning tokens are billed at the model's output token price, or at a separate reasoning price where the model page lists one. The final charge comes from the usage the provider reports for that request, so it makes no difference where in the counters the tokens landed.

For the hold placed before the request is sent, a request carrying reasoning or reasoning_effort gets its estimated output tokens multiplied by three. For example: max_tokens: 2000 on a model priced at 600,000 toman per 1M output tokens holds roughly 1,400 toman without reasoning and roughly 4,100 toman with it (safety factor included). Once the answer comes back, the real amount is settled and the rest released. If you hit a 402 while your balance "looked" fine, this multiplier is why — set a more realistic max_tokens.

On reasoning models, max_tokens covers both the thinking and the answer. Set it too low and the model gets cut off mid-thought, returning an empty content after you have already paid for the reasoning. For hard problems use at least 4000, or leave it unset.

Choosing a model

  • Fast and cheap: openai/o3-mini or openai/gpt-5-mini with reasoning_effort: "low".
  • Heavy code and maths: openai/o3, deepseek/deepseek-r1.
  • Reasoning over long inputs and documents: anthropic/claude-sonnet-4.5, google/gemini-2.5-pro.

For plenty of tasks, a non-reasoning model with a good prompt (a few examples plus an explicit "think step by step") is cheaper and entirely sufficient. Start with an ordinary model and only move up to reasoning if quality falls short.