Reasoning

Extended thinking tokens for complex tasks

Overview

Reasoning gives the model extra "thinking" time before answering. Useful for math, logic, multi-step analysis, and code review. Two ways to enable it: the reasoning object or the top-level reasoning_effort parameter. Reasoning tokens count as output tokens.

On sansa-auto, omitting reasoning defaults to effort: "high"; see Smart Routing.

Two Configuration Formats

Format A: reasoning object

reasoning isn't in the OpenAI SDK's types, so pass it in extra_body:

const response = await client.chat.completions.create(
  {
    model: 'sansa-auto',
    messages: [{ role: 'user', content: 'Solve this math problem...' }],
  },
  {
    // @ts-expect-error extra_body is not in the official type
    extra_body: { reasoning: { effort: 'high' } },
  }
);

In Python:

response = client.chat.completions.create(
    model="sansa-auto",
    messages=[{"role": "user", "content": "Solve this math problem..."}],
    extra_body={"reasoning": {"effort": "high"}},
)

Raw JSON (curl / any HTTP client):

{
  "messages": [{"role": "user", "content": "Solve this math problem..."}],
  "reasoning": {
    "effort": "high"
  }
}

Full reasoning object:

interface ReasoningConfig {
  // One of "xhigh", "high", "medium", "low", "minimal", "none".
  effort?: string;

  // Token budget (alternative to effort).
  max_tokens?: number;

  // If true, reasoning is used internally but not returned in the response.
  // Tokens are still generated and billed.
  exclude?: boolean;

  // Optional. false disables reasoning. On sansa-auto, omitting reasoning
  // entirely defaults to enabled=true, effort="high".
  enabled?: boolean;
}

Rules: Use effort OR max_tokens, not both.

Format B: reasoning_effort (OpenAI-style)

{
  "messages": [{"role": "user", "content": "Solve this math problem..."}],
  "reasoning_effort": "high"
}

Accepted values: "xhigh", "high", "medium", "low", "minimal", "none".

Equivalent to reasoning: { effort: "high" }.

Precedence: If both reasoning and reasoning_effort are provided, the reasoning object takes precedence.

Effort Levels

LevelDescriptionUse when
xhighMaximum reasoning depthComplex math, logic puzzles
highDeep reasoning (default when sansa-auto omits reasoning)Multi-step analysis, code review
mediumBalancedGeneral-purpose reasoning
lowLight reasoningSimple analysis, quick decisions
minimalVery lightBasic tasks that benefit slightly
noneNo reasoningDisable reasoning / non-reasoning routing on sansa-auto

On sansa-auto, see What sansa-auto accepts for the levels it takes.

Reasoning in Responses

Non-Streaming

Reasoning appears in choices[0].message.reasoning (string) and/or choices[0].message.reasoning_details (structured array).

TypeScript note: reasoning and reasoning_details aren't in the OpenAI SDK's types. Read them with a type assertion:

const msg = response.choices[0].message as any;
console.log('Reasoning:', msg.reasoning);
{
  "choices": [{
    "message": {
      "role": "assistant",
      "content": "The answer is 42.",
      "reasoning": "Let me think step by step...",
      "reasoning_details": [{
        "type": "reasoning.text",
        "text": "Let me think step by step...",
        "id": "reasoning-1",
        "format": "rf-c",
        "index": 0
      }]
    }
  }]
}

Streaming

Reasoning details appear in delta.reasoning_details before content starts:

Note: Line breaks added for readability.

data: {
  "choices": [{
    "delta": {
      "reasoning_details": [{
        "type": "reasoning.text",
        "text": "Step 1..."
      }]
    }
  }]
}

data: {
  "choices": [{
    "delta": { "content": "The answer is " }
  }]
}

data: {
  "choices": [{
    "delta": { "content": "42." }
  }]
}

reasoning_details Types

interface ReasoningDetail {
  // Common fields
  id: string;
  index: number;

  // Opaque format token assigned by Sansa (e.g., "rf-a", "rf-b", "rf-c", "rf-d").
  // Pass it back unchanged; don't parse or interpret these values.
  format: string;

  // Type-specific fields
  type: "reasoning.text" | "reasoning.summary" | "reasoning.encrypted";
  
  // For "reasoning.text"
  text?: string;
  signature?: string;
  
  // For "reasoning.summary"
  summary?: string;
  
  // For "reasoning.encrypted"
  data?: string;
}

Preserving Reasoning Across Turns

With the OpenAI SDK, append the full message object to your messages array; the SDK keeps reasoning_details intact on non-streaming responses:

const response = await client.chat.completions.create({
  model: "sansa-auto",
  messages: [...],
  extra_body: { reasoning: { effort: "high" } },
});

// Append the full message; reasoning_details comes with it
messages.push(response.choices[0].message);
messages.push({ role: "user", content: "Follow up question..." });

This maintains reasoning continuity across turns and tool calls.

Requests work without it: continuity improves quality but isn't required.

Streaming limitation

The OpenAI SDK doesn't collect reasoning_details from streaming deltas, so use non-streaming requests when continuity matters, such as multi-turn tool calling.

Example: tool calling with reasoning

{
  "messages": [
    {"role": "user", "content": "What's the weather?"},
    {
      "role": "assistant",
      "content": null,
      "tool_calls": [{"id": "callwx001", "function": {"name": "get_weather", "arguments": "{}"}}],
      "reasoning_details": [/* pass back unmodified from previous response */]
    },
    {
      "role": "tool",
      "tool_call_id": "callwx001",
      "content": "{\"temp\": 72}"
    }
  ]
}

Billing

  • Reasoning tokens are output tokens, included in usage.completion_tokens.
  • Higher effort means more output tokens.
  • exclude: true hides reasoning from the response; its tokens are still billed.