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
| Level | Description | Use when |
|---|---|---|
xhigh | Maximum reasoning depth | Complex math, logic puzzles |
high | Deep reasoning (default when sansa-auto omits reasoning) | Multi-step analysis, code review |
medium | Balanced | General-purpose reasoning |
low | Light reasoning | Simple analysis, quick decisions |
minimal | Very light | Basic tasks that benefit slightly |
none | No reasoning | Disable 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: truehides reasoning from the response; its tokens are still billed.