Reasoning

Control, read, hide, and preserve model reasoning.

Reasoning models think before they answer. Aivene returns that thinking alongside the answer in a single, consistent shape for every model, lets you control how much the model thinks, hide it from your users, and carry it across turns so multi-step tool use keeps its train of thought.

This page covers the Chat Completions API (/v1/chat/completions). The Messages and Responses APIs keep their own native reasoning shapes, summarized at the end.

Quick example

import OpenAI from 'openai';

const client = new OpenAI({
  baseURL: 'https://api.aivene.com/v1',
  apiKey: process.env.AIVENE_API_KEY
});

const response = await client.chat.completions.create({
  model: 'claude-sonnet-5',
  messages: [{ role: 'user', content: 'Which is larger, 9.11 or 9.9?' }],
  // @ts-expect-error - `reasoning` is an Aivene extension
  reasoning: { effort: 'high' }
});

const message = response.choices[0].message;

console.log('THINKING:', 'reasoning' in message ? message.reasoning : null);
console.log('ANSWER:', message.content);

Controlling reasoning

Send a reasoning object with the request:

{
  "model": "claude-sonnet-5",
  "messages": [{ "role": "user", "content": "..." }],
  "reasoning": {
    "effort": "high",
    "max_tokens": 4000,
    "enabled": true,
    "exclude": false
  }
}
FieldTypeWhat it does
effortmax | xhigh | high | medium | low | minimal | noneHow much the model should think. none turns reasoning off on models that allow it.
max_tokensintegerA token budget for thinking. See Effort vs. token budget.
enabledbooleantrue turns reasoning on at medium effort when you don't pass effort or max_tokens. false turns it off.
excludebooleantrue keeps reasoning out of the response. The model still thinks. See Hiding reasoning.

All fields are optional. Use either effort or max_tokens, not both. When a model reasons by default, sending nothing keeps its default behavior.

reasoning_effort is accepted as a top-level shorthand for reasoning.effort:

{ "model": "gpt-5.5", "messages": [], "reasoning_effort": "low" }

Not every model can stop thinking

Some models always reason, so effort: "none" or enabled: false may still produce a short amount of thinking. Use exclude if your goal is to keep reasoning out of your app's output.

Effort vs. token budget

Models take reasoning settings in one of two ways, and Aivene translates between them for you:

Model familyeffortmax_tokens
ClaudeNative adaptive thinking at that effortUsed directly as the thinking budget (min 1,024, max 128,000)
GeminiMapped to the model's thinking levelUsed directly as the thinking budget
GPT, Grok, DeepSeek and othersNativeConverted to an effort level (below)

For effort-only models, reasoning.max_tokens becomes an effort level based on its share of the request's max_tokens:

reasoning.max_tokens / max_tokensEffort
80% or morehigh
50% to 79%medium
below 50%low
request has no max_tokensmedium

Claude needs room to answer

With a token budget, Claude requires the request's max_tokens to be strictly greater than reasoning.max_tokens, otherwise there is no room left for the answer.

Reading reasoning

Every reasoning model returns two fields on the assistant message:

FieldTypeUse it for
reasoningstringThe readable thinking text. Show it in your UI or log it.
reasoning_detailsarrayThe structured reasoning blocks. Send them back unchanged to keep reasoning across turns.

Non-streaming response

{
  "choices": [
    {
      "message": {
        "role": "assistant",
        "content": "9.9 is larger. Compare the tenths digit: 9 is greater than 1.",
        "reasoning": "Both numbers start with 9. Compare the decimal parts: 0.11 vs 0.90...",
        "reasoning_details": [
          {
            "type": "reasoning.text",
            "text": "Both numbers start with 9. Compare the decimal parts: 0.11 vs 0.90...",
            "signature": "EqQBCkgIBxABGAIqQH...",
            "format": "anthropic-claude-v1",
            "index": 0
          }
        ]
      },
      "finish_reason": "stop"
    }
  ]
}

Streaming response

Reasoning arrives in delta.reasoning and delta.reasoning_details, usually before the answer text:

{
  "choices": [
    {
      "delta": {
        "reasoning": "Let me check",
        "reasoning_details": [
          {
            "type": "reasoning.text",
            "text": "Let me check",
            "signature": null,
            "format": "anthropic-claude-v1",
            "index": 0
          }
        ]
      }
    }
  ]
}
  • Concatenate delta.reasoning across chunks to get the full thinking text.
  • Chunks that belong to the same reasoning block share the same index (and id, when present). Concatenate their text or summary in order.
  • A block's signature arrives in its own chunk, usually with an empty text.
  • reasoning.encrypted blocks arrive whole, in a single chunk.

You can also keep every reasoning_details entry exactly as streamed and send the list back as-is. Aivene merges the fragments for you.

Reasoning details

Each entry in reasoning_details has a type and these common fields:

FieldTypeDescription
typestringreasoning.text, reasoning.summary, or reasoning.encrypted
formatstring | nullWhich model family produced the block (see below)
indexintegerPosition of the block. Streamed fragments of one block share it.
idstring | nullBlock identifier, when the model provides one

reasoning.text

Raw thinking text, optionally with a signature that lets the model verify the block when you send it back.

{
  "type": "reasoning.text",
  "text": "First, list the constraints...",
  "signature": "EqQBCkgIBxABGAIqQH...",
  "format": "anthropic-claude-v1",
  "index": 0
}

reasoning.summary

A condensed summary of the model's thinking. Models that don't expose their raw thinking return summaries instead.

{
  "type": "reasoning.summary",
  "summary": "Compared both options on cost and latency.",
  "id": "rs_0a1b2c3d",
  "format": "azure-openai-responses-v1",
  "index": 0
}

reasoning.encrypted

Thinking the model keeps private. data is opaque: you can't read it, but sending it back lets the model continue from where it left off.

{
  "type": "reasoning.encrypted",
  "data": "gAAAAABo9x...",
  "id": "rs_0a1b2c3d",
  "format": "azure-openai-responses-v1",
  "index": 1
}

Formats

formatProduced by
anthropic-claude-v1Claude
openai-responses-v1GPT models
azure-openai-responses-v1GPT models
bedrock-openai-responses-v1GPT models
bedrock-xai-responses-v1Grok models
xai-responses-v1Grok
meta-responses-v1Meta models
google-gemini-v1Gemini
unknownAny other reasoning model (DeepSeek, Qwen, GLM, MiMo, ...)
nullFormat not specified

What each model family returns

Model familyreasoning textreasoning_details
ClaudeFull thinkingreasoning.text with signature, and reasoning.encrypted for redacted thinking
GPTSummariesreasoning.summary plus one reasoning.encrypted per reasoning step
GeminiThought summariesreasoning.text, plus a reasoning.encrypted per tool call (its id is the tool call id)
Grok, MetaUsually nonereasoning.encrypted
DeepSeek, Qwen, GLM, MiMo and othersFull thinkingreasoning.text

Preserving reasoning across turns

When you send the conversation back, include each assistant message's reasoning_details exactly as you received them. This matters most for tool calling: the model pauses mid-thought to call a tool, and sending its reasoning back lets it continue that thought once the tool result arrives instead of starting over.

import OpenAI from 'openai';

const client = new OpenAI({
  baseURL: 'https://api.aivene.com/v1',
  apiKey: process.env.AIVENE_API_KEY
});

const tools: OpenAI.Chat.Completions.ChatCompletionTool[] = [
  {
    type: 'function',
    function: {
      name: 'get_weather',
      description: 'Get the current weather for a city',
      parameters: {
        type: 'object',
        properties: { city: { type: 'string' } },
        required: ['city']
      }
    }
  }
];

const messages: OpenAI.Chat.Completions.ChatCompletionMessageParam[] = [
  { role: 'user', content: 'What should I wear in Jakarta today?' }
];

// Turn 1: the model thinks, then calls the tool.
const first = await client.chat.completions.create({
  model: 'claude-sonnet-5',
  messages,
  tools,
  // @ts-expect-error - `reasoning` is an Aivene extension
  reasoning: { max_tokens: 2000 },
  max_tokens: 4000
});

const assistant = first.choices[0].message;
const toolCall = assistant.tool_calls![0];

// Send the assistant message back unchanged, including `reasoning_details`.
messages.push(assistant);
messages.push({
  role: 'tool',
  tool_call_id: toolCall.id,
  content: JSON.stringify({ temperature: 31, condition: 'rain' })
});

// Turn 2: the model picks up its earlier reasoning and answers.
const second = await client.chat.completions.create({
  model: 'claude-sonnet-5',
  messages,
  tools,
  // @ts-expect-error - `reasoning` is an Aivene extension
  reasoning: { max_tokens: 2000 },
  max_tokens: 4000
});

console.log(second.choices[0].message.content);

Pushing the SDK's message object back as-is keeps every field, including reasoning and reasoning_details. If you rebuild messages yourself, copy both fields over.

Accepted on assistant messages in the history:

FieldNotes
reasoning_detailsPreferred. Carries signatures and encrypted blocks, which some models need to continue reasoning.
reasoningPlain text. Enough for models that only return readable thinking.

Rules for opaque values

  • Never edit signature or data. They are verified by the model and any change invalidates them. Keep the whole entry, including id, format, and index.
  • Keep tool call ids unchanged. Gemini binds its reasoning to the tool call id, so a rewritten id loses that context.
  • Don't move reasoning between models. Blocks from one model family are ignored by another.

Hiding reasoning

If your app shows everything the model returns (chat front-ends, workflow tools, roleplay clients), hide reasoning with any one of these:

OptionWhere
"reasoning": { "exclude": true }Request body
"include_reasoning": falseRequest body
x-strip-reasoning: trueRequest header

All three remove reasoning and reasoning_details from the response, streaming and non-streaming. The model still reasons and those tokens are still billed. To stop the reasoning itself, use effort: "none" or enabled: false on models that allow it.

{
  "model": "deepseek-v4-flash",
  "messages": [{ "role": "user", "content": "Write a haiku about rain." }],
  "reasoning": { "exclude": true }
}

Third-party apps

Most chat front-ends and workflow tools let you add extra body parameters to OpenAI-compatible requests (often called "Additional parameters", "Extra body", or "Custom JSON"). Add {"reasoning": {"exclude": true}} there. If the app only lets you add headers, use x-strip-reasoning: true.

Tokens and billing

Reasoning tokens are output tokens and are billed at the model's output price, whether or not you hide them.

{
  "usage": {
    "prompt_tokens": 42,
    "completion_tokens": 1180,
    "completion_tokens_details": {
      "reasoning_tokens": 1024
    }
  }
}
  • completion_tokens includes reasoning. Subtract reasoning_tokens to get the visible answer length.
  • The request's max_tokens covers reasoning and the answer. If thinking uses the whole budget, the response ends with finish_reason: "length" and little or no content. Raise max_tokens, or lower effort / reasoning.max_tokens.

Messages and Responses APIs

The other text APIs keep their native shapes:

  • Messages API (/v1/messages) - Use thinking ({ "type": "enabled", "budget_tokens": 2000 } or { "type": "adaptive" }) and output_config.effort. Thinking comes back as thinking / redacted_thinking content blocks. Send them back unchanged in the next assistant message.
  • Responses API (/v1/responses) - Use reasoning: { "effort": "high", "summary": "auto" } and add "include": ["reasoning.encrypted_content"] to receive encrypted reasoning. Reasoning comes back as reasoning output items. Pass them back unchanged in the next request's input.

The same rule applies: don't edit opaque values.

FAQ