Reasoning control¶
Most models on the catalog are hybrid reasoners, they can think before answering. Every vendor invented a different switch for it. On Keln there is one field in, one field out, identical on every model.
One switch: reasoning_effort¶
{
"model": "deepseek-ai/deepseek-v4-flash",
"messages": [{"role": "user", "content": "23 × 47?"}],
"reasoning_effort": "none"
}
"none", answer directly, no thinking. Lower latency, fewer billed tokens."low"/"medium"/"high"/"max", thinking on, at that depth where the serving route grades it ("xhigh"is accepted as an alias of"max").- Omitted, thinking on, at the model's deepest setting. See below.
reasoning_effort is a depth control, not a token budget: bound the output with max_tokens,
and where you want to hint a thinking budget use the OpenRouter-style
reasoning: {"max_tokens": N}, honored where the serving route has a budget knob.
The default is full depth¶
Send no reasoning field and a thinking-capable model thinks at its deepest setting. This
default protects against a request quietly getting less reasoning than assumed; levels exist to
ask for less, and "none" turns thinking off.
What that means in practice:
- Requests that send no reasoning field bill more output tokens (thinking bills as output) and
answer more slowly than a
"none"request, on a hard prompt, several times more of both. - It is the policy across the catalog, including models whose upstream default is off
(
google/gemma-4-*), on Keln they think unless you say otherwise. - Only an explicit
"none"(or a vendor alias meaning off) disables thinking. Nothing else does.
{ "model": "google/gemma-4-31b-it",
"messages": [{"role": "user", "content": "Summarize this changelog."}],
"reasoning_effort": "none" }
Cost-sensitive, high-volume, or latency-critical traffic should send "none" explicitly rather
than relying on a default.
Already using a vendor spelling? enable_thinking, thinking: {"type": "disabled"},
chat_template_kwargs.enable_thinking, and the OpenRouter/Vercel object, reasoning:
{"enabled": true | false}, reasoning: {"effort": …}, reasoning: {"max_tokens": …} (a
thinking budget implies on, honored where the serving route has a budget knob), all
accepted as aliases and folded into the same canonical behavior. Your existing code works
unchanged. include_reasoning (OpenRouter legacy) is accepted and inert, the trace comes back
either way, as is reasoning: {"exclude": true} (migration ledger).
One trace field: reasoning_content¶
The thinking trace always comes back the same way, whatever the model, in the canonical
reasoning_content, mirrored in reasoning so OpenRouter/Vercel-style readers work unchanged:
"message": {
"role": "assistant",
"reasoning_content": "The user wants 23 × 47…",
"reasoning": "The user wants 23 × 47…",
"content": "23 × 47 = 1,081."
}
data: {"choices":[{"delta":{"reasoning_content":"The user wants","reasoning":"The user wants"}}]}
data: {"choices":[{"delta":{"content":"23 × 47 = "}}]}
No per-vendor tag formats, no <think> blocks to parse out of content. Both keys always carry
the identical trace; reasoning_content is the canonical name.
reasoning_tokens in usage¶
Thinking bills as output, so the usage object itemizes it, the standard OpenAI shape, on every
route (streaming delivers it in the final usage frame with stream_options.include_usage):
"usage": {
"prompt_tokens": 44,
"completion_tokens": 1090,
"completion_tokens_details": { "reasoning_tokens": 950 }
}
completion_tokens stays the billable total; reasoning_tokens itemizes how much of it was
thinking. Where the serving route reports the split it is passed through as reported; where it
doesn't, Keln derives it. 0 means no reasoning happened; a missing field means the split
could not be established. The tokens are part of completion_tokens either way, not billed
separately.
Models that cannot disable reasoning¶
A few models reason always-on by design, stepfun-ai/step-3.7-flash,
openai/gpt-oss-120b, and openai/gpt-oss-20b cannot be told to stop thinking. Ask one to
disable it and you get an explicit error:
{"error": {"code": "reasoning_not_disableable",
"message": "This model cannot disable reasoning."}}
Every model on the catalog today is reasoning-capable, so the switch either takes effect or returns this error.
Where each model stands¶
The rate card marks the always-on models; everything else is switchable per request.