Skip to content

Reasoning control

Most models on the catalog are hybrid reasoners, they can think before answering. Every model vendor invented a different switch for it. On Keln there is one field in and one field out, identical on every model.

One switch: reasoning_effort

{
  "model": "deepseek-ai/deepseek-v4.1-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 supports a thinking budget.

The default is full depth

Send no reasoning field and a thinking-capable model thinks at its deepest setting. This default prevents a request from silently getting less reasoning than you assumed; levels exist to ask for less, and "none" turns thinking off.

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.
{ "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.

Keln accepts vendor spellings as aliases and maps them to the same canonical behavior, so your existing code works unchanged: 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 supports a thinking budget). 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: moonshotai/kimi-k2.7-code, openai/gpt-oss-120b, qwen/qwen3.8-2.4t-a95b, zai-org/glm-5.3 and zai-org/glm-5.3-flash cannot be told to stop thinking. Ask one to disable it and you get an explicit error:

{"error": {"code": "reasoning_not_disableable",
           "message": "Reasoning cannot be disabled for this model."}}

Every model on the catalog today is reasoning-capable, so the switch either takes effect or returns this error.

Where each model stands

Every model accepts all of the values above. On each model the levels that change depth are listed once; a level that is not listed runs the same depth as a listed one (on zai-org/glm-5.2, low and medium run like high). The same facts are machine-readable in the reasoning block of GET /v1/models.

Model none (thinking off) Levels that change depth Omitted level runs
deepseek-ai/deepseek-v4-pro-0813 yes low, high, max max
deepseek-ai/deepseek-v4.1-flash yes low, high, max max
google/gemma-4-31b-it yes on/off only thinking on
minimaxai/minimax-m3 yes on/off only thinking on
moonshotai/kimi-k2.7-code no, always on no levels thinking on
moonshotai/kimi-k3 yes low, high, max max
nvidia/nemotron-3-ultra-550b-a55b yes medium, high high
openai/gpt-oss-120b no, always on low, medium, high high
qwen/qwen3.6-35b-a3b yes on/off only thinking on
qwen/qwen3.8-2.4t-a95b no, always on low, medium, xhigh xhigh
qwen/qwen3.8-27b yes low, medium, xhigh xhigh
zai-org/glm-5.2 yes high, max max
zai-org/glm-5.3 no, always on low, high, max max
zai-org/glm-5.3-flash no, always on low, high, max max