Dev.to AI 🤖 Ai 👁 0 📖 6 min read

Four ways reasoning models hide their thinking (and what that does to your bill)

Same question, four endpoints: "show your reasoning." Four different answers. Not four opinions — four wire formats, four billing behaviors, and four ways your audit trail goes blind. This piece is the map I wish I had b

Same question, four endpoints: "show your reasoning." Four different answers. Not four opinions — four wire formats, four billing behaviors, and four ways your audit trail goes blind. This piece is the map I wish I had before I assumed any of them. Disclosure: I work on daoxe, an OpenAI-compatible gateway — one key, many models, all four shapes behind one endpoint. Bias declared up front; the captures below include results that make my own gateway look wrong, and every probe here runs the same way against anyone's endpoint.

Shape 1 — shared budget (GLM)

GLM's reasoning-capable IDs run the chain of thought inside the completion. max_tokens is the budget for thinking and the answer together. Undersize it and you don't get an error — you get empty content with finish_reason: "length". The model "said" nothing because it spent everything thinking.

The trap is this: it looks exactly like a dead endpoint. The tell is in the finish reason — length means budget, not outage. Size the cap for the thinking first; the answer gets what's left.

Behind the gateway, same shape, same warning label — and the accounting gets weirder. I sent max_tokens: 8 at glm-4.7-flash through my own endpoint (free-group capture, 2026-09-16; signals not proof):

"message": { "content": "", "reasoning_content": "1.  **Analyze the User" },
"finish_reason": "length"

The budget-trap textbook sample, alive at my own party: eight tokens of pure thinking, the answer starved to an empty string. One detail to note for Shape 4, though: that same response reported completion_tokens_details.reasoning_tokens: 0 — even with the whole budget visibly spent on thinking. A counter that is present is not a counter that works; verify before you build a dashboard on it.

Shape 2 — separate field (DeepSeek)

DeepSeek's R-series puts reasoning in a dedicated reasoning_content field on the message, separate from content. Clean — until the second turn of a tool loop. DeepSeek's API documents that the previous assistant message must be echoed back including its reasoning_content, or the request fails with a 400.

Build your history from the raw message objects, never from a trimmed summary of the visible text. The moment a "helpful" history compactor strips the reasoning field, every subsequent call fails in a way that looks like a gateway problem.

Except the split itself is a property of the endpoint you're actually talking to, not of the model's name. I called deepseek-r1-32b through my gateway twice (free-group captures, 2026-09-16; signals not proof). Both times reasoning_content was absent entirely — the thinking came back fused into content, inside think-style delimiters:

content: "<think>\nFirst, the farmer initially has 17 sheep.\n…\n</think>\n\n**Solution:** …"

The 400-echo rule is DeepSeek's documented API contract — against the official endpoint. On a translated channel that never produced the field in the first place, there is nothing to echo and nothing to enforce, and my captures confirmed it: no error, just a fused blob. The lesson generalizes past my gateway: before your multi-turn logic depends on reasoning_content existing, check that your endpoint actually returns it. The model ID is not the contract; the wire is.

Shape 3 — explicit flag (MiniMax)

MiniMax reasoning IDs keep thinking out of the answer text only when asked: pass extra_body={"reasoning_split": True} and the chain of thought arrives in reasoning_details. Without the flag, the split is not guaranteed — reasoning may stay fused into content or be dropped.

If your pipeline needs machine-separable reasoning, set the flag on every call. Hope is not a configuration — and on some endpoints, neither is the flag.

No MiniMax model exists in my free-group catalog, so I probed the flag mechanism on two reasoning IDs that do, mimo-v2.5 and qwen3-30b (free-group captures, 2026-09-16; signals not proof). On my gateway, mimo-v2.5 with reasoning_split: true and without it produced the same response shape — thinking fused into content, no separate field, no error, no acknowledgement. The flag was silently swallowed. Then qwen3-30b with chat_template_kwargs: {"enable_thinking": false}: thinking came back anyway in a reasoning_content field, while content was empty and finish_reason was "stop". A wrong answer that looks like a finished one.

Two explicit instructions in the request, both silently overridden. That's the honest qualifier for Shape 3 as a family: the flag says what the vendor's docs promise; it does not guarantee what your endpoint honors. Verify the response shape every time — a configuration you can't observe being applied is a wish, not a setting.

Shape 4 — usage accounting only (Grok)

xAI reports thinking as an accounting detail, not a message field: usage.completion_tokens_details.reasoning_tokens is meant to count the completion tokens consumed by reasoning. The chain of thought itself is never returned.

That should make Grok the cheapest of the four to log — the numbers arrive in your usage stream already — and the least inspectable, since there is nothing to read, only to count. If your billing dashboards read only top-level usage numbers, thinking spend is invisible to them.

"Should." Here is one of three grok-4.3 calls through my gateway (free-group capture, 2026-09-16; signals not proof). The message: visible step-by-step reasoning inlined in content, no reasoning field anywhere. The usage:

"completion_tokens_details": { "reasoning_tokens": 0 }

The field is present and the count is zero on all three captures. So the Shape 4 caveat, earned the hard way: field present ≠ counting happening. An audit trail built on "the field exists, therefore the tokens are attributed" inherits whatever the implementation quietly doesn't do. Check that the counter actually moves before you trust it to allocate cost.

What each shape costs you

Visible reasoning Separable for parsing Correct billing surface
GLM sometimes (reasoning_content) per-request inside max_tokens
DeepSeek per docs: always (field) yes, where honored separate field
MiniMax only with the flag yes, flag honored standard
Grok never only via counter reasoning_tokens, if it counts

Cost attribution, audit trails, client parsing and history management all break differently against each row. The GLM budget trap and the DeepSeek echo requirement are the two that turn into 2am pages; the Grok accounting gap is the one that turns into a quietly wrong bill. And the qualifier column is the point: "per docs", "where honored", "if it counts" — every row assumes a contract, and the captures above show a contract on each row moving under translation.

A four-line probe

You do not have to memorize any of this. Send one fixed prompt at temperature 0 and dump the three surfaces that differ:

import os, openai
c = openai.OpenAI(base_url=os.environ["BASE_URL"], api_key=os.environ["KEY"])
r = c.chat.completions.create(model=os.environ["MODEL"], temperature=0,
    max_tokens=2048,  # room for GLM-style shared budgets
    messages=[{"role": "user", "content": "Reason step by step: what is 17*23?"}])
m = r.choices[0].message
print("finish_reason:", r.choices[0].finish_reason)
print("message keys:  ", sorted(m.model_dump().keys()))       # reasoning_content? reasoning_details?
print("usage details: ", r.usage.completion_tokens_details)   # reasoning_tokens?

Reading the output: empty content with finish_reason: "length" is Shape 1 (raise the cap). A reasoning_content key on the message is Shape 2 — and from then on, echo it in multi-turn. A reasoning_details field means Shape 3 — set the flag and verify it stuck. reasoning_tokens in the usage details with nothing on the message is Shape 4 — and a zero there does not mean nothing was thought; I watched Shape 4 channels report 0 while reasoning in plain sight. Run this when you pin the model, and again whenever the provider ships a new version; the shape is part of the contract, and contracts move.

The general rule

Never assume reasoning is visible, separable, or billable the same way twice. The four shapes above are current as of this writing, and providers move — which is exactly why the probe comes before the assumption, every time.

📰 Read the original article on Dev.to AI

Originally published by Dev.to AI. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.