Get API key
Text generationReasoning
Text generation

Reasoning

Reasoning models think before they answer. You set how much in the field your API uses, and llm.buzz sends it to each provider in that provider's terms, by OpenRouter's rules.

On this page

Which models reason

GET https://api.llm.buzz/api/v1/models lists a model's efforts as reasoning_efforts, in order from none to max, when the model's lead provider's listing names them. The key is left out otherwise, including where llm.buzz knows a model's efforts only from its own rules for the model's family. A model without the key can still reason. Do not filter on supported_parameters for reasoning: no model in the public list names reasoning there today.

The efforts differ from model to model. openai/gpt-5.6-luna takes none, low, medium, high, xhigh and max, with no minimal. anthropic/claude-sonnet-5.5 takes low to max and has no none, because it cannot turn thinking off. A request may name none, minimal, low, medium, high, xhigh, ultra or max; Chat Completions and Responses answer 400 invalid_parameter, naming the field, for any other value.

The controls in each API

Chat CompletionsResponsesMessages
Effortreasoning_effort, or reasoning.effortreasoning.effortoutput_config.effort, or reasoning.effort
Token budgetreasoning.max_tokens, thinking.budget_tokens or thinking_budgetNonethinking.budget_tokens or reasoning.max_tokens
On or offreasoning.enabled, thinking, enable_thinking or chat_template_kwargs.enable_thinkingAn effort of none is offthinking, or reasoning.enabled
Hide the reasoning textreasoning.exclude: true or include_reasoning: falseNonereasoning.exclude: true
  • Chat Completions. reasoning is OpenRouter's object: effort or max_tokens, not both (a request with both is 400 invalid_parameter), plus enabled and exclude. Its fields must agree: an effort tier or a budget beside enabled: false, or effort: "none" beside enabled: true, is 400 invalid_parameter naming reasoning.enabled. include_reasoning: true is reasoning: {} and false is reasoning: {"exclude": true}. thinking takes enabled, adaptive or disabled: a bare enabled or adaptive switches reasoning on at llm.buzz's default effort for the model (below), an enabled with budget_tokens is a budget (Token budgets), and disabled is effort none. The body's x-sgr-ignored-parameters names the move to an effort as thinking->translated(reasoning_effort). It says what llm.buzz did with the field, not that it dropped it.
  • Responses. reasoning takes effort, summary, generate_summary and context; summary and generate_summary sent together must match. reasoning.mode is 400 invalid_parameter, and so is any other key. Without a summary, llm.buzz asks OpenAI for an automatic one unless the effort is none.
  • Messages. thinking takes Anthropic's forms, described under Thinking. On the Claude models llm.buzz lists today, a bare {"type": "enabled"} runs adaptive thinking, named thinking.type->adaptive, and budget_tokens is refused (Token budgets). reasoning is OpenRouter's object, as on Chat Completions, and its fields must agree: an effort tier or a budget beside enabled: false, or effort: "none" beside enabled: true, is a 400 invalid_request_error whose message ends (param: reasoning.enabled). reasoning.effort with reasoning.max_tokens, or a reasoning.effort, reasoning.max_tokens or output_config.effort beside a thinking.budget_tokens, is a 400 invalid_request_error.
  • A control that belongs to another API is dropped and named in x-sgr-ignored-parameters, as Chat's reasoning_effort is on Messages. Send one depth control per request: an effort and a budget in one Chat Completions request, or two budgets, are 400 invalid_parameter, and metadata.param names one of the controls. Two efforts in one Chat Completions request, reasoning_effort with reasoning.effort, are not refused: reasoning_effort is used, in either order and in a stream, and x-sgr-ignored-parameters names the other as reasoning->ignored(explicit_reasoning_effort). If one of them is none and the other is not, the request is 400 invalid_parameter naming reasoning_effort.

Set the effort

This request asks openai/gpt-5.6-luna for a low effort and reads the model's reasoning beside its answer:

from openai import OpenAI
import os

client = OpenAI(
    base_url="https://api.llm.buzz/api/v1",
    api_key=os.environ["LLM_BUZZ_KEY"],
)
reply = client.chat.completions.create(
    model="openai/gpt-5.6-luna",
    messages=[{"role": "user", "content": "A train leaves at 9:40 and the trip takes 2 h 35 min. When does it arrive?"}],
    extra_body={"reasoning": {"effort": "low"}},
)
message = reply.choices[0].message
print((message.model_extra or {}).get("reasoning"))
print(message.content)

reasoning is not a field of the SDKs' Chat Completions method, so the Python call sends it through extra_body and the TypeScript call casts its parameters; reasoning_effort is one, and takes the effort alone. On Responses reasoning is an SDK field, and it can ask for a summary of the reasoning:

response = client.responses.create(
    model="openai/gpt-5.6-luna",
    input="A train leaves at 9:40 and the trip takes 2 h 35 min. When does it arrive?",
    reasoning={"effort": "low", "summary": "auto"},
)
for item in response.output:
    if item.type == "reasoning":
        for part in item.summary:
            print(part.text)
print(response.output_text)

On Messages, anthropic/claude-sonnet-5.5 thinks adaptively and takes its depth from output_config.effort. The Anthropic SDK builds client as in Messages:

message = client.messages.create(
    model="anthropic/claude-sonnet-5.5",
    max_tokens=4096,
    thinking={"type": "adaptive"},
    output_config={"effort": "low"},
    messages=[{"role": "user", "content": "A train leaves at 9:40 and the trip takes 2 h 35 min. When does it arrive?"}],
)
for block in message.content:
    if block.type == "thinking":
        print(block.thinking)
    elif block.type == "text":
        print(block.text)

How an effort is mapped

A model takes some efforts and not others, and llm.buzz moves your effort to one the model takes instead of failing the request.

  • Where llm.buzz knows a model's efforts, an effort the model does not take becomes the nearest one it does, and a tie takes the lower. x-sgr-ignored-parameters names the move as reasoning_effort-><level> on every API. minimal on openai/gpt-5.6-luna is served as low, named reasoning_effort->low, and xhigh on openai/gpt-5-mini, whose highest effort is high, is served as high.
  • none never becomes an active effort, and an active effort never becomes none. A model with no off level refuses none with a 400 before any provider is called, naming reasoning_effort on Chat Completions, whichever spelling you sent, and reasoning.effort on Responses. Today that includes Claude Opus 5.5, Sonnet 5.5, Fable 5 and Fable 5.1, and OpenAI's gpt-5-mini, gpt-5-nano, gpt-6-astra and gpt-6.1-sol. On Messages, thinking: {"type": "disabled"} is refused for the Claude models among them; Sonnet 5.5 offers {"type": "between_tools"} in its place. The refusal holds on every route of these models, llm.buzz's own provider included: a Claude model takes the efforts its family takes, so an effort the family refuses is refused before any provider is called, whichever API the provider speaks.
  • A switch with no level, such as reasoning.enabled: true or enable_thinking: true, takes llm.buzz's default effort for the model, which is medium unless the model has another. On Messages, a bare adaptive sent to Claude carries no effort, so Claude's own default applies.
  • Where llm.buzz does not know a model's efforts, your value goes to the provider as you sent it. A provider that refuses it answers 400, with its own sentence after provider rejected the request: and metadata.param naming the field.
  • If you send no setting, llm.buzz sends no effort, and the model runs at its own default. One exception: on Messages, a max_tokens under 1,024 with no setting of your own, on a model whose providers all reason by default and offer none, is sent at effort none, named reasoning_effort->none(max_tokens_headroom), so the reply is text (Thinking).
  • OpenAI's reasoning models and the Claude models that think adaptively take sampling settings only where the provider does: Sampling settings a model does not take.

Token budgets

Instead of an effort you can give a number of thinking tokens. Not every provider has a field for one, and llm.buzz never turns a budget into an effort: where none of a model's providers can take it, the request is refused before any provider is called, naming the field.

  • OpenAI's models take no budget. Neither do the Claude models llm.buzz lists today, all of which are in the families that think adaptively (Opus 4.7 onward, Sonnet 5, Haiku 5 and Fable 5). A budget sent to one answers 400 unsupported_parameter on Chat Completions and 400 invalid_request_error on Messages, naming the budget field you sent. Choose an effort for these models.
  • On Chat Completions, thinking.budget_tokens is an integer of at least 1,024 and below the output limit; any other value is 400 invalid_parameter naming it. On Messages, thinking.budget_tokens and reasoning.max_tokens must be below max_tokens, so the answer has room.
  • On a Claude model that takes a budget, which none on llm.buzz does today, a reasoning.max_tokens under 1,024 is raised to 1,024 and named reasoning.max_tokens->1024(anthropic_minimum).

Read the reasoning

Chat CompletionsResponsesMessages
Wheremessage.reasoning, and delta.reasoning in a streamA reasoning item in output, with summary_text partsthinking blocks in content

Readable reasoning is what the provider returns: reasoning text, thinking blocks or a summary. Where a provider withholds it by default, llm.buzz asks for it: Claude's thinking as a summary, and OpenAI's reasoning as an automatic summary unless you chose a summary or the effort is none. On Messages, Anthropic's own thinking blocks are signed. Reasoning from a provider other than Anthropic arrives as one unsigned thinking block.

reasoning: {"exclude": true} and include_reasoning: false keep the text out of the answer, in a stream too. The model still reasons, and you are billed for it. On Messages, Anthropic's own signed thinking blocks stay in the answer, because the next request needs them to continue. Responses has no field to hide a summary.

What reasoning costs

  • Reasoning tokens are output tokens. They count toward max_tokens, max_completion_tokens or max_output_tokens, so a limit that is too low can end the answer inside the reasoning, with finish_reason: length on Chat Completions, status: incomplete on Responses or stop_reason: max_tokens on Messages.
  • They are billed at the reasoning price of the provider that answered, and at its output price when the provider sets none. A model's public document lists reasoning among its prices (Public models). Pricing explains how a request is priced. Hiding the reasoning text saves nothing.
  • Chat Completions reports them in usage.completion_tokens_details.reasoning_tokens, a part of completion_tokens, where the provider reports a count. A stream's usage chunk always has it, 0 when the provider reported none, with usage.unreported_token_details listing reasoning_tokens (Usage and cost). Claude reports no separate count: its thinking is inside completion_tokens (output_tokens on Messages).

Keep reasoning in the conversation

Send the conversation back unchanged, reasoning included.

  • Chat Completions. An assistant message may carry reasoning, reasoning_content and OpenRouter's reasoning_details (its reasoning.text blocks). A provider that takes earlier reasoning back gets it. For another, llm.buzz drops it and names it, as messages.reasoning_content->dropped(unsupported_by_provider); the request still serves.
  • Responses. Send reasoning items back as they came, and ask for include: ["reasoning.encrypted_content"] when you send store: false: Input items.
  • Messages. Send every thinking and redacted_thinking block back with its assistant turn. Claude's signed blocks go back to Claude; another provider receives the turn without them, and x-sgr-ignored-parameters names it messages.thinking->dropped(unsupported_by_provider).

A tool turn whose reasoning a provider keeps for the next request stays with that provider. While it can serve, provider.order, only, ignore and the routing preset do not move the request off it, and allow_fallbacks: false keeps it there alone. If it is down, the model's other providers serve the turn without that reasoning, and the visible conversation is unchanged (Provider preferences).

Docs