Reasoning Effort Parameter¶
This document describes how Continuum Router handles the reasoning_effort parameter across different LLM backends, including the token budget conversions and supported effort levels.
Overview¶
The reasoning_effort parameter controls the amount of computational effort a model spends on reasoning before generating a response. Different backends implement this feature differently:
- OpenAI: Native
reasoning_effortparameter for O-series and GPT-5.x thinking models - Anthropic: Converted to
thinking.budget_tokensfor Claude models with extended thinking, or to{"type": "adaptive"}forautoeffort - Gemini: Native
reasoning_effortfor thinking-capable models via OpenAI-compatible endpoint - Other backends: Pass-through (no transformation)
Parameter Formats¶
Continuum Router supports two input formats, both normalized internally:
Flat Format (Chat Completions API)¶
Nested Format (Responses API)¶
Both formats are automatically normalized to the flat reasoning_effort format before processing. If both are present, the flat format takes precedence.
GPT-5.6 Native Responses Controls¶
For a backend that exposes the native Responses API, Continuum Router preserves the newer GPT-5.6 request surface verbatim:
reasoning.mode(standardorpro) andreasoning.context(auto,current_turn, orall_turns)text.verbosity,safety_identifier, andincludeprompt_cache_key,prompt_cache_options, and explicitprompt_cache_breakpointmarkers oninput_text,input_image, andinput_file- Programmatic Tool Calling tools and replay items (
program,program_output, and caller-linked function outputs) - Beta Multi-agent configuration and output items
{
"model": "gpt-5.6-sol",
"input": "Continue the investigation.",
"previous_response_id": "resp_123",
"reasoning": {
"mode": "pro",
"effort": "max",
"context": "all_turns"
},
"text": {
"verbosity": "high"
},
"safety_identifier": "usr_privacy_preserving_hash"
}
Native previous_response_id values are forwarded unchanged, allowing OpenAI to reuse persisted reasoning. Conversion backends still use the router's local session expansion. Requests containing native-only fields fail with unsupported_request_parameter when routing selects a conversion strategy instead of silently dropping fields. Stateful and native-only requests bypass the router's local response cache.
Multi-agent raw HTTP clients must send OpenAI-Beta: responses_multi_agent=v1; the router forwards this header while continuing to filter client credentials and transport headers.
Direct Token Budget Specification¶
For advanced use cases, you can specify the token budget directly instead of using reasoning_effort levels:
Anthropic: Direct thinking Parameter¶
{
"model": "claude-sonnet-4-20250514",
"thinking": {
"type": "enabled",
"budget_tokens": 16000
},
"messages": [...]
}
For adaptive thinking (model decides when and how much to think):
Gemini: Direct thinking_budget via extra_body¶
{
"model": "gemini-2.5-pro",
"extra_body": {
"google": {
"thinking_config": {
"thinking_budget": 10000,
"include_thoughts": true
}
}
},
"messages": [...]
}
Priority
If both reasoning_effort and direct token specification are present, the direct specification takes precedence for Anthropic (thinking parameter). For Gemini, both can coexist but thinking_budget in extra_body is used for fine-grained control.
Backend-Specific Behavior¶
OpenAI Backend¶
OpenAI models support the reasoning_effort parameter natively. The router passes the value directly to the OpenAI API.
Supported Effort Levels¶
| Effort Level | Supported Models | Description |
|---|---|---|
none | GPT-5.x models that expose it | Disable reasoning |
minimal | Original GPT-5 family | Minimum reasoning effort |
low | O-series, GPT-5.x thinking | Minimal reasoning, faster responses |
medium | O-series, GPT-5.x thinking | Balanced reasoning effort |
high | O-series, GPT-5.x thinking | Deep reasoning, slower responses |
xhigh | GPT-5.2 through GPT-5.6 reasoning tiers, GPT-6 Astra | Extra-high reasoning effort |
max | GPT-5.6 family and GPT-6 Astra | Maximum reasoning effort |
Models Supporting reasoning_effort¶
O-series models (support low, medium, high):
o1,o1-mini,o1-previewo3,o3-mini,o3-proo4-mini
GPT-6 models:
gpt-6-astra(low,medium,high,xhigh, andmax).nonereturns a400from OpenAI, so this is the first OpenAI reasoning model with no way to turn reasoning off.
GPT-5.x thinking models (exact levels vary by generation):
gpt-5.6,gpt-5.6-sol,gpt-5.6-terra,gpt-5.6-luna(nonethroughmax)gpt-5.2throughgpt-5.5reasoning tiers (none,low,medium,high,xhigh)gpt-5.1reasoning tiers (none,low,medium,high)gpt-5,gpt-5-mini, andgpt-5-nano(minimal,low,medium,high)gpt-5-pro(highonly; Responses API only)gpt-5.2-pro,gpt-5.4-pro, andgpt-5.5-pro(medium,high,xhigh; Responses API only)
Exact model validation
Streaming and non-streaming Chat Completions requests use the same model-specific validation. Unsupported pairs such as GPT-5 + xhigh, GPT-5.5 + max, or any OpenAI model + auto return 400 invalid_request_error. The router never silently changes the requested OpenAI effort.
Function tools with reasoning effort¶
OpenAI refuses /v1/chat/completions on some models when the request carries function tools and the effective reasoning effort is anything other than none:
400 Function tools with reasoning_effort are not supported for gpt-5.6-sol in
/v1/chat/completions. To use function tools, use /v1/responses or set
reasoning_effort to 'none'.
The restriction is per model, not per family, and it is not the same rule in both affected generations:
| Model | Tools + effort omitted | Tools + low/medium/high | Tools + none | No tools |
|---|---|---|---|---|
gpt-6-astra | Refused | Refused | Refused by the model | Accepted |
gpt-5.6-sol (alias gpt-5.6), gpt-5.6-terra, gpt-5.6-luna | Refused | Refused | Accepted | Accepted |
gpt-5.5, gpt-5.4, gpt-5.4-mini, gpt-5.4-nano | Accepted | Refused | Accepted | Accepted |
gpt-5.2 and earlier, O-series | Accepted | Accepted | Accepted | Accepted |
The gpt-5.6 row differs because the upstream default effort for that family is medium: omitting the field does not mean "no reasoning" there, so the refusal fires on an omitted effort too.
GPT-6 Astra is stricter again: it rejects reasoning_effort: "none" on every request, with or without tools, so the second remedy OpenAI's own error text names does not exist for it. Every tool-carrying Chat Completions request for that model is bridged, and the chat_completions_default_reasoning_effort: "medium" on its entry is there only to be a value other than none, since OpenAI does not publish the effort it applies when the field is omitted.
Every cell above except the gpt-6-astra row was measured against the live API on 2026-09-03; that row is read from OpenAI's model documentation and its refusal text, and has not been measured here. The split does not follow a name prefix in either direction: the whole GPT-5.4 generation is affected including its mini and nano tiers, while GPT-5.2 and earlier are not, which is why the rule is data on each model entry rather than a family check.
The router bridges exactly the refused requests. When the resolved model carries chat_completions_tools_require_none_reasoning in its metadata, the request has a non-empty tools array, and the effective effort would be refused, the request is dispatched to /v1/responses (which accepts the combination) and the reply is translated back into the Chat Completions shape, streaming included. Everything else is preserved: the requested effort rides through as reasoning.effort, an omitted effort stays omitted, and the client sees an ordinary chat.completion or chat.completion.chunk stream with its tool calls.
Every other request keeps going straight to /v1/chat/completions, so nothing that works today changes cost or latency: reasoning_effort: "none" with tools, any request without tools, and every unaffected model. Validation still runs first, so an effort outside the model's supported set is the router's own 400 and never reaches the bridge. The router does not rewrite the requested effort to none, which would silently change model behavior.
Two metadata keys drive it, on the model entry next to responses_only:
| Key | Type | Meaning |
|---|---|---|
chat_completions_tools_require_none_reasoning | bool (default false) | Upstream refuses function tools with a non-none effort on /v1/chat/completions for this model. |
chat_completions_default_reasoning_effort | string (optional) | The effort upstream applies when the request omits reasoning_effort. Set to medium on the gpt-5.6 and gpt-6-astra entries so an omitted effort is bridged too; unset on gpt-5.4 and gpt-5.5. The router never sends this value. |
Both ship set on the affected entries (gpt-6-astra, gpt-5.6-sol and its gpt-5.6 alias, gpt-5.6-terra, gpt-5.6-luna, gpt-5.5, gpt-5.4, gpt-5.4-mini, gpt-5.4-nano), in the built-in OpenAI registry and in model-metadata.yaml. Because they are ordinary metadata, a model-metadata.d/ drop-in can clear them the day OpenAI lifts the restriction, without waiting for a router release:
# model-metadata.d/90-lift-tools-reasoning-bridge.yaml
models:
- id: gpt-5.6-sol
metadata:
chat_completions_tools_require_none_reasoning: false
Operators can watch the split on the responses_bridge_total metric, whose reason label separates tools_with_reasoning from an unconditionally bridged responses_only_model.
Mitigation on released versions before this bridge
On a router without the conditional bridge, the workaround is a drop-in that flags the model responses_only: true, which routes it through /v1/responses and makes the same requests succeed. It works, at the cost of sending every request for that model through the Responses API, including plain chat with no tools. continuum-router config validate warns when that override is set on one of these models in backends[].model_configs[], pointing at the narrower flag.
Models NOT Supporting reasoning_effort¶
The following models do not support reasoning parameters (parameter is stripped):
- GPT-4o, GPT-4o-mini, GPT-4-turbo, GPT-4
- GPT-5.2-chat-latest, GPT-5.2-instant (non-thinking variants)
- GPT-3.5-turbo
- Embedding models, Image models
Anthropic Backend (Claude)¶
Anthropic Claude models use different mechanisms depending on the model generation:
- Claude Fable 5 and Mythos 5 (Mythos-class; Mythos 5 is the safeguards-lifted, limited-release variant of Fable 5): Use adaptive thinking (
{"type": "adaptive"}) withoutput_config.effort, the same surface as Opus 4.8 (legacybudget_tokensrejected with HTTP 400;temperature/top_p/top_kdropped automatically;maxeffort supported). One additional constraint: an explicitthinking.type == "disabled"is rejected (HTTP 400), so the router omits thethinkingparameter entirely instead of forwarding it. - Claude 4.8+ models (Opus 4.8): Use adaptive thinking (
{"type": "adaptive"}) withoutput_config.effortto control thinking depth. The legacythinking.type == "enabled"+budget_tokensshape is rejected (HTTP 400). Sampling parameterstemperature,top_p, andtop_kare not accepted and the router drops them automatically. Effort default ishigh. - Claude Sonnet 5 (
claude-sonnet-5-*, aliasclaude-sonnet-5-latest): Uses adaptive thinking ({"type": "adaptive"}) withoutput_config.effort. The legacythinking.type == "enabled"+budget_tokensshape is rejected (HTTP 400). Sampling parameterstemperature,top_p, andtop_kare dropped automatically.xhighdowngrades to"high"(maxis not supported; Sonnet 5 is not Opus/Mythos-class). Effort default ishigh. - Claude 4.6+ adaptive models (Opus 4.6, Sonnet 4.6, Opus 4.7): Use adaptive thinking (
{"type": "adaptive"}) withoutput_config.effortto control thinking depth. Claude Opus 4.7 requires this API; sending the legacythinking.type == "enabled"+budget_tokensshape to Opus 4.7 produces HTTP 400. - Pre-4.6 models (Opus 4.5, Sonnet 4, etc.): Use
thinking.budget_tokensfor extended thinking
Conversion Table: Claude 4.6+ Models (Adaptive Thinking)¶
Claude 4.6+ adaptive models (claude-opus-4-6-*, claude-sonnet-4-6-*, claude-opus-4-7-*, claude-opus-4-8-*, claude-sonnet-5-*, claude-fable-5-*, claude-mythos-5-*) use output_config.effort instead of budget_tokens:
reasoning_effort | thinking | output_config.effort | Description |
|---|---|---|---|
none | disabled | (omitted) | Thinking disabled |
minimal | {"type": "adaptive"} | "low" | Mapped to low (no minimal level in Anthropic API) |
auto | {"type": "adaptive"} | (omitted) | Model uses default effort (high) |
low | {"type": "adaptive"} | "low" | Low thinking depth |
medium | {"type": "adaptive"} | "medium" | Moderate thinking depth |
high | {"type": "adaptive"} | "high" | High thinking depth |
xhigh (Opus 4.6, Opus 4.7, Opus 4.8, Fable 5, Mythos 5) | {"type": "adaptive"} | "max" | Maximum thinking depth |
xhigh (Sonnet 4.6, Sonnet 5) | {"type": "adaptive"} | "high" | Downgraded (max is Opus/Mythos-class-only) |
Conversion Table: Pre-4.6 Claude Models (Budget Tokens)¶
Pre-4.6 models use thinking.budget_tokens:
reasoning_effort | Anthropic thinking | Description |
|---|---|---|
none | disabled | Thinking feature disabled |
minimal | {"type": "enabled", "budget_tokens": 1024} | Minimum allowed budget |
auto | {"type": "adaptive"} | Model decides when and how much to think |
low | {"type": "enabled", "budget_tokens": 4096} | Light reasoning |
medium | {"type": "enabled", "budget_tokens": 10240} | Moderate reasoning |
high | {"type": "enabled", "budget_tokens": 32768} | Deep reasoning |
xhigh | {"type": "enabled", "budget_tokens": 32768} | Falls back to high budget |
Transformation Example: Claude 4.6+ (Adaptive Thinking + Effort)¶
Input (OpenAI format):
Transformed (Anthropic format):
{
"model": "claude-opus-4-6-20260205",
"thinking": {
"type": "adaptive"
},
"output_config": {
"effort": "high"
},
"messages": [...]
}
For xhigh on Opus 4.6 or Opus 4.7, output_config.effort is set to "max":
{
"model": "claude-opus-4-6-20260205",
"thinking": {"type": "adaptive"},
"output_config": {"effort": "max"},
"messages": [...]
}
Transformation Example: Pre-4.6 Claude (Budget Tokens)¶
Input (OpenAI format):
Transformed (Anthropic format):
{
"model": "claude-sonnet-4-20250514",
"thinking": {
"type": "enabled",
"budget_tokens": 32768
},
"messages": [...]
}
Adaptive Thinking (Auto)¶
When reasoning_effort is set to "auto", the router emits {"type": "adaptive"} without specifying output_config.effort. This lets Anthropic use its default effort level (currently high) and lets the model dynamically decide when and how much reasoning to apply.
Input (OpenAI format):
Transformed (Anthropic format):
Direct output_config Pass-Through¶
When a request already contains explicit thinking and output_config parameters, they are passed through directly without conversion:
{
"model": "claude-opus-4-6-20260205",
"thinking": {"type": "adaptive"},
"output_config": {"effort": "medium"},
"messages": [...]
}
Adaptive Thinking Availability
Adaptive thinking with output_config.effort is available on Claude 4.6+ adaptive models (claude-opus-4-6-*, claude-sonnet-4-6-*, claude-opus-4-7-*, claude-opus-4-8-*, claude-sonnet-5-*) and the Mythos-class family (claude-fable-5-*, claude-mythos-5-*). Claude Opus 4.7/4.8, Claude Sonnet 5, and the Mythos-class models require this API; the router normalizes explicit legacy thinking.type == "enabled" requests for these models to avoid upstream HTTP 400 errors. Pre-4.6 models use budget_tokens instead.
max Effort Level
The "max" effort level in output_config.effort is available on Opus models (Opus 4.6, Opus 4.7, Opus 4.8) and the Mythos-class family (Fable 5, Mythos 5). When xhigh is requested for Sonnet 4.6, the router automatically downgrades it to "high" with an info-level log message.
Claude Opus 4.7/4.8, Sonnet 5, and Mythos-class Sampling Parameter Deprecation
Anthropic deprecated temperature, top_p, and top_k for Claude Opus 4.7, Opus 4.8, Sonnet 5, Fable 5, and Mythos 5. The router automatically drops all three parameters before forwarding to any claude-opus-4-7-*, claude-opus-4-8-*, claude-sonnet-5-*, claude-fable-5-*, or claude-mythos-5-* model to avoid HTTP 400 errors.
Mythos-class Disabled-Thinking Rejection
Claude Fable 5 and Mythos 5 reject an explicit thinking: {"type": "disabled"} with HTTP 400 at every effort level. When a client sends a disabled-thinking config to a claude-fable-5-* or claude-mythos-5-* model, the router omits the thinking parameter entirely (the documented workaround) instead of forwarding it. Opus 4.8 and earlier, and every Sonnet family, accept an explicit disabled-thinking config unchanged. Opus 5 accepts it only up to effort high (see below).
Claude Opus 5 Disabled Thinking Above high Effort
Claude Opus 5 accepts an explicit thinking: {"type": "disabled"} config, but only while output_config.effort stays at high or below. Pairing it with xhigh/max is rejected by Anthropic with HTTP 400.
The router models this constraint and rejects the combination itself, with an HTTP 400 of its own, before any provider round-trip. The check runs on /v1/chat/completions (streaming and non-streaming) and on /anthropic/v1/messages, which are the two ingresses that can express the pair. The Responses-to-Anthropic converter carries the same guard on the payload it assembles, but no /v1/responses client can currently reach it: a Responses request expresses "no thinking" only as reasoning.effort: "none", which omits the thinking parameter rather than disabling it explicitly. Because validation is per request, it also catches a later turn that raises the effort while thinking stays disabled, not just the first call in a conversation.
Two Anthropic-shape surfaces are intentionally left ungated. /anthropic/v1/messages/count_tokens neither generates nor bills tokens, and gating it would risk rejecting a request the provider is willing to count. Bedrock Converse (endpoint_type: converse) copies extra_body/additionalModelRequestFields through verbatim by design, so a thinking config smuggled that way is the caller's own escape hatch.
The error body names both remedies so a client can self-correct: lower output_config.effort to high or below, or remove the disabled-thinking config so the request runs adaptive thinking at the requested effort. The router deliberately does not pick one on the caller's behalf. Clamping the effort and dropping the thinking config are both silent rewrites of an explicit client instruction, and neither is safe by default: Opus 5 with thinking disabled has two documented failure modes, emitting tool calls as plain text in the visible response instead of structured tool_use blocks, and leaking <thinking> tags into the output.
Mythos-class 5.1 Forced Tool Choice Removal
Claude Fable 5.1 and Claude Mythos 5.1 removed forced tool use. Both tool_choice: {"type": "any"} and {"type": "tool", "name": ...} are rejected by Anthropic with HTTP 400 (tool_choice: type "tool" and "any" are not supported for this model.). auto and none are unaffected, and disable_parallel_tool_use still works alongside auto. Claude Fable 5 and Claude Mythos 5 accept forced tool use, so the gate is parsed from the family version rather than applied to the whole Mythos-class family.
The router rejects the combination itself, with an HTTP 400 of its own, before any provider round-trip. The check runs on all three ingresses that can express a forced call: /v1/chat/completions (streaming and non-streaming, where OpenAI's "required" and {"type": "function", ...} become Anthropic's any and tool), /anthropic/v1/messages, and the Responses-to-Anthropic converter. Unlike the disabled-thinking guard above, the Responses path is reachable by an ordinary request rather than a guard against future drift, because a /v1/responses client can ask for a forced call today.
The same two Anthropic-shape surfaces stay ungated, for the same reasons: /anthropic/v1/messages/count_tokens neither generates nor bills tokens, and Bedrock Converse copies additionalModelRequestFields through verbatim by design.
The error body names the model, echoes the spelling the client actually sent, and lists the remedies Anthropic documents: use tool_choice auto with an instruction naming the tool, set strict: true on the tool so its arguments stay schema-valid, or ask for structured output. The router deliberately applies none of them. Downgrading a forced choice to auto is not an equivalent request: the model is then free to answer with plain text, which breaks an agent loop that was written to expect a guaranteed tool call, and the failure surfaces far from the request that caused it.
Models Supporting Extended Thinking¶
Extended thinking is supported by:
- Claude Fable 5 / Mythos 5 (adaptive thinking + effort;
maxsupported; sampling params dropped; explicit disabled-thinking omitted):claude-fable-5-*(aliasclaude-fable-5-latest),claude-mythos-5-*(aliasclaude-mythos-5-latest, limited release) - Claude Opus 4.8 (adaptive thinking + effort; sampling params deprecated; effort default
high):claude-opus-4-8-*, aliasclaude-opus-4-8-latest - Claude Sonnet 5 (adaptive thinking + effort; sampling params dropped;
xhighdowngraded tohigh; effort defaulthigh):claude-sonnet-5-*, aliasclaude-sonnet-5-latest - Claude Opus 4.7 (adaptive thinking + effort; sampling params deprecated):
claude-opus-4-7-* - Claude 4.6 family (adaptive thinking + effort):
claude-opus-4-6-*,claude-sonnet-4-6-* - Claude Opus models (budget tokens):
claude-opus-4-*,claude-opus-4-5-* - Claude Sonnet 4 models (budget tokens):
claude-sonnet-4-*,claude-sonnet-4-5-*
Temperature Restriction
When extended thinking is enabled, Claude does not support custom temperature settings. The router automatically removes the temperature parameter when thinking is active. For Claude Opus 4.7, Opus 4.8, Fable 5, and Mythos 5, temperature, top_p, and top_k are always dropped regardless of whether thinking is enabled.
xhigh for Pre-4.6 Claude
When xhigh is requested for pre-4.6 Claude models, the router automatically downgrades it to high (32,768 budget_tokens).
auto outside Anthropic
The auto effort level maps to Anthropic's adaptive thinking. OpenAI requests using auto are rejected because it is not an OpenAI effort value; Gemini retains its provider-specific auto to medium conversion.
Reverse Direction on Fallback Hops¶
Everything above describes the forward direction: an OpenAI reasoning_effort becoming a Claude thinking config. A cross-provider fallback hop can need the reverse. When a request carrying thinking or output_config falls back onto an OpenAI-wire backend, dispatch converts that config into a reasoning_effort value the selected target accepts and only then removes the Anthropic-native fields, so the reasoning intent crosses the hop instead of being dropped. output_config.effort maps level for level with max becoming xhigh where the target's vocabulary has it, budget_tokens maps back through the same band table, adaptive thinking without an effort and disabled thinking both emit nothing, and a client-supplied reasoning_effort always wins. The full table, the per-target vocabulary rule, and the local-engine decision are in Model fallback.
The Anthropic Messages API ingress (/anthropic/v1/messages) applies the same bands through the same function, on its Chat Completions transform and on the Responses API transform it uses for responses_only models. A budget_tokens value therefore resolves to one reasoning level across the whole router, whether the request arrived as an Anthropic message or was rescued onto an OpenAI-wire backend by a fallback hop. The band table itself is in Model fallback.
Gemini Backend¶
Gemini models support reasoning_effort natively through their OpenAI-compatible endpoint. The router validates and passes the value directly.
Supported Effort Levels¶
| Effort Level | Supported Models | Description |
|---|---|---|
none | Every model except those flagged rejects_reasoning_effort_none | Disable thinking |
minimal | All thinking models | Minimal reasoning |
low | All thinking models | Light reasoning |
medium | All thinking models | Moderate reasoning |
high | All thinking models | Deep reasoning |
Which models accept none¶
none is carried through to Gemini unless the model's metadata says the provider refuses it. The refusal is a deny list, rejects_reasoning_effort_none in model-metadata.yaml, and it ships set on the models measured to refuse:
gemini-3.6-flashgemini-3.5-flash-lite- every Gemini Pro tier
Everything else, including a model released after the router build, is forwarded and answered by Gemini. That default is deliberate. The predecessor was an allowlist of Flash families compiled into the router, so each new Gemini minor lost none support until someone measured it and cut a release, and Google shipped four Flash minors between May and September 2026.
Support does not follow a family prefix. Measured against the OpenAI-compatible endpoint on 2026-09-10, one reasoning_effort: "none" request per catalog id: 2.5 Flash, 2.5 Flash-Lite, 3 Flash, 3.1 Flash-Lite, 3.5 Flash, 3.7 Flash, and 3.8 Flash answered 200; gemini-3.5-flash-lite and gemini-3.6-flash answered 400 INVALID_ARGUMENT; the Pro tiers answered 400 Budget 0 is invalid. This model only works in thinking mode.; the 2.0 family answered 404, having been retired upstream.
The router's own 400 exists for the two Flash tiers, whose upstream error names neither the field nor the model. A Pro model's upstream message is already specific, so the flag there mainly saves the round trip. The check runs on the /v1/chat/completions ingress, before backend selection, so it covers streaming and non-streaming alike. An operator who disagrees with a row can set or clear it in a model-metadata.d/ drop-in without waiting for a release.
A fallback hop is fitted rather than gated. The ingress check judges the model the client asked for, while a hop lands on a different model chosen by the operator's chain, whose metadata can carry the flag when the original did not. On such a hop the router drops reasoning_effort: "none" and logs it, because a hop has already substituted the model: a client that accepted a different model over a failure is better served by that model reasoning than by an error. Before this, the hop forwarded none, the provider answered 400, the chain counted a failed hop, and the request came back as 500 fallback exhausted naming neither the model nor the field. One consequence is worth knowing: the substitute reasons, so a small max_tokens that fit a non-reasoning answer can now truncate one.
The native thinkingConfig.thinkingLevel: "MINIMAL" field is a different question with a different answer: Gemini 3.8 Flash refuses it while Gemini 3.6 Flash accepts it, the reverse of the two rows here. Google documents the minimal level rather than this mapping, so a documentation statement about minimal must not be used to edit the deny list. Measure the id.
xhigh Automatic Fallback
When xhigh is requested for Gemini models, the router automatically downgrades it to high since Gemini doesn't support xhigh.
Additional Features¶
For Gemini thinking models, the router automatically:
- Sets
include_thoughts: trueto expose reasoning content - Sets default
max_completion_tokens: 16384if not specified - Validates effort level against model capabilities
All three apply on both routes. Until issue #1591 the transform ran only when stream: true, so a non-streaming client received no reasoning_content from a thinking model, got Gemini's low implicit output cap instead of the 16384 default, and had reasoning_effort: "xhigh" forwarded rather than downgraded, while the same request with stream: true got all three. The non-streaming route now runs the same two steps in the same order the streaming route does, so a rejected effort is the same 400 either way.
Which models get these injections is a table of families (THINKING_FAMILIES in src/infrastructure/backends/gemini/transform.rs), not a name substring. The substring list it replaced matched gemini-3.1-pro-preview but not gemini-3.1-pro, so the same model behaved differently depending on which of its two names the caller sent. A version, date, or preview suffix reduces away and a -lite segment does not, because Flash and Flash-Lite are different models.
The thought summary itself needs the response side. Google returns it inline in message.content, wrapped in <thought>...</thought> and marked extra_content.google.thought: true, rather than in a field of its own. The router moves that text into message.reasoning_content and leaves content holding only the answer, so the markup never reaches the client and content is always present, empty rather than absent when the model produced nothing but thought.
Malformed function calls¶
Gemini intermittently ends a tool turn with finish_reason: "function_call_filter: MALFORMED_FUNCTION_CALL" (the native API spells it MALFORMED_FUNCTION_CALL): HTTP 200, an assistant message carrying neither content nor tool_calls, and completion_tokens: 0. The model emitted something meant to be a function call and the provider could not parse it. Measured on 2026-09-10, one call in two and one call in five on gemini-3.1-pro-preview with a single tool; every other call returned a proper tool_calls response.
The router refuses to forward that as a success. It becomes a retryable upstream failure (502, type: upstream_error, whose message names malformed_function_call), so the retry loop and any configured fallback.fallback_chains hop engage. A retry succeeded in every observed case, and answering stop instead would hide a turn that produced nothing and still cost prompt tokens. On a streaming request the failure is detected after the response is committed, so it arrives as an SSE error event rather than an HTTP status.
Two shape guarantees hold on every Gemini route as a result. finish_reason is always one of the five OpenAI values (stop, length, tool_calls, content_filter, function_call), because one shared table maps the provider's spellings and reduces anything unrecognized to stop. An assistant message always carries a content key, null only alongside tool_calls and empty otherwise.
Generic/HTTP Backend¶
Generic HTTP backends (used for Ollama, vLLM, LocalAI, LM Studio, etc.) pass requests through without transformation.
| Behavior | Description |
|---|---|
| Pass-through | reasoning_effort is forwarded as-is to the backend |
| No validation | The router does not validate effort levels |
| No conversion | No token budget conversion is performed |
Backend Responsibility
For generic backends, the target LLM server is responsible for handling (or ignoring) the reasoning_effort parameter. If the server doesn't support it, it may return an error or ignore the parameter.
llama.cpp Backend¶
The llama.cpp backend uses pass-through behavior:
| Behavior | Description |
|---|---|
| Pass-through | Parameters forwarded unchanged |
| Server-dependent | Support depends on llama-server configuration |
Thinking Model Support in llama.cpp
Recent versions of llama-server support thinking models (e.g., DeepSeek-R1) with <think> tag handling. However, there is no standardized API parameter for reasoning_effort or budget_tokens. The thinking behavior is typically controlled by:
- Model's built-in chat template with thinking tags
- Server-side configuration (e.g.,
--thinking-budgetif available) - Sampling parameters like temperature and top-p
vLLM Backend¶
vLLM provides an OpenAI-compatible API but reasoning effort support depends on the model:
| Behavior | Description |
|---|---|
| Pass-through | Parameters forwarded via generic backend |
| Model-dependent | Support varies by model type |
Thinking Model Support in vLLM
vLLM can run thinking-capable models (e.g., DeepSeek-R1, QwQ) but the reasoning_effort parameter handling depends on:
- Whether the model supports structured thinking
- vLLM server version and configuration
- Model's chat template configuration
For DeepSeek-R1 and similar models, thinking is often implicit in the model's behavior rather than controlled by an explicit budget parameter.
Response Field Normalization
Newer vLLM versions emit reasoning text as choices[].delta.reasoning (streaming) or choices[].message.reasoning (non-streaming) rather than the canonical reasoning_content. The router normalizes this field on all Chat Completions relay paths, so downstream clients always see reasoning_content regardless of vLLM version. Backends that already use reasoning_content are not modified.
Summary Table¶
| Backend | Effort Levels | Conversion | Notes |
|---|---|---|---|
| OpenAI | Model-specific subset of none, minimal, low, medium, high, xhigh, max | None (native) | Exact validation; no silent effort fallback |
| Anthropic (Fable 5 / Mythos 5) | none, minimal, auto, low, medium, high, xhigh | → adaptive + output_config.effort | xhigh → max; drops temperature/top_p/top_k always; explicit disabled-thinking omitted |
| Anthropic (Opus 4.8) | none, minimal, auto, low, medium, high, xhigh | → adaptive + output_config.effort | xhigh → max; drops temperature/top_p/top_k always; effort default high |
| Anthropic (Opus 4.7) | none, minimal, auto, low, medium, high, xhigh | → adaptive + output_config.effort | xhigh → max; drops temperature/top_p/top_k always |
| Anthropic (4.6) | none, minimal, auto, low, medium, high, xhigh* | → adaptive + output_config.effort | *xhigh → max for Opus 4.6, high for Sonnet 4.6; removes temperature |
| Anthropic (pre-4.6) | none, minimal, auto, low, medium, high | → budget_tokens or adaptive | auto maps to adaptive thinking; removes temperature |
| Gemini | none*, auto**, minimal, low, medium, high | None (native) | *none only for Flash models; **auto downgraded to medium |
| vLLM | Model-dependent | Pass-through | DeepSeek-R1, QwQ use implicit thinking |
| llama.cpp | Model-dependent | Pass-through | Uses <think> tag in chat template |
| Generic | Any | Pass-through | Backend handles validation |
Response Format¶
When reasoning/thinking is enabled, the response includes the model's reasoning process:
OpenAI Format (with reasoning_content)¶
{
"choices": [{
"message": {
"role": "assistant",
"content": "The answer is 42.",
"reasoning_content": "Let me think through this step by step..."
},
"finish_reason": "stop"
}]
}
Claude Extended Thinking (transformed to OpenAI format)¶
The router transforms Claude's thinking blocks to the reasoning_content field:
{
"choices": [{
"message": {
"role": "assistant",
"content": "The answer is 42.",
"reasoning_content": "I need to consider multiple factors..."
},
"finish_reason": "stop"
}]
}
Best Practices¶
- Use appropriate effort levels: Higher effort = better reasoning but slower and more expensive
- Check model support: Not all models support reasoning parameters
- Handle upper effort levels carefully: GPT-5.2–5.6 support
xhigh, while only GPT-5.6 supportsmax; the router applies documented fallbacks for older models - Consider cost: Extended thinking consumes additional tokens (especially with Anthropic's
budget_tokens) - Test with your backend: Generic backends may have varying support levels