Токены рассуждений

У моделей с thinking API может вернуть токены рассуждений — цепочку мыслей до ответа. Они считаются выходными токенами и входят в usage.cost_rub.

По умолчанию, если модель их выдала, они лежат в message.reasoning (и часто в reasoning_details). Часть моделей (серия OpenAI o) думает, но текст мыслей не отдаёт — токены при этом всё равно тратятся и оплачиваются.

Работает в /chat/completions, /responses и /messages. То же можно сохранить в пресете. Поле reasoning не входит в стандарт OpenAI: в Python — extra_body, в TypeScript — @ts-expect-error.

Параметр reasoning

JSON
{
  "model": "claude-sonnet-4.6",
  "messages": [],
  "reasoning": {
    "effort": "high",
    "exclude": false,
    "enabled": true
  }
}
ПолеТипСмысл
effortenummax, xhigh, high, medium, low, minimal, none
max_tokensintЖёсткий бюджет мыслей (Claude, Gemini 2.5, часть Qwen)
excludeboolДумать, но не класть текст мыслей в ответ
enabledboolВключить со средним усилием, если не задали effort / max_tokens
contextenumGPT-5.6+: auto, all_turns, current_turn
modeenumGPT-5.6+: standard, pro
curl https://api.aitunnel.ru/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer sk-aitunnel-xxx" \
  -d '{
    "model": "claude-sonnet-4.6",
    "max_tokens": 8000,
    "reasoning": { "effort": "high" },
    "messages": [
      { "role": "user", "content": "Что больше: 9.11 или 9.9?" }
    ]
  }'

Уровень усилий

effort — переносимый способ сказать «думай больше» или «думай меньше», не привязываясь к числам конкретного провайдера.

effortДоля от max_tokens ответаКогда брать
max≈ 95%Самые сложные задачи, цена не важна
xhigh≈ 95%То же, что max
high≈ 80%Сложный код, многошаговый анализ
medium≈ 50%Дефолт при enabled: true
low≈ 20%Простые задачи, важна задержка
minimal≈ 10%Минимум мыслей
noneВыключить рассуждения

Доли применяются там, где провайдер хочет бюджет в токенах (Claude, Gemini 2.5). Где провайдер принимает уровни (Gemini 3, GPT-5, Grok), effort уходит уровнем, а не числом.

Если модель не умеет запрошенный уровень, он мапится в ближайший поддерживаемый.

Выключить рассуждения

JSON
{
  "model": "glm-5.2",
  "reasoning": { "effort": "none" }
}

У моделей, где thinking обязателен (glm-5.3, glm-5.3-flash, часть Gemini 3), none вернёт ошибку — рассуждения там отключить нельзя.

Включить с дефолтами

reasoning: { "enabled": true } — рассуждения со средним усилием, цепочка в ответе. Удобно, когда не хочется выбирать уровень.

Бюджет токенов

Точное число токенов на мысли. Работает у Claude, Gemini 2.5 и части Qwen (уходит как thinking_budget).

JSON
{
  "model": "claude-sonnet-4.6",
  "max_tokens": 8000,
  "reasoning": { "max_tokens": 2000 }
}
response = client.chat.completions.create(
    model="claude-sonnet-4.6",
    messages=[{"role": "user", "content": "Какой алгоритм сортировки выбрать для 10 млрд записей?"}],
    max_tokens=8000,
    extra_body={"reasoning": {"max_tokens": 2000}},
)

msg = response.choices[0].message
print(getattr(msg, "reasoning", None))
print(msg.content)
print(response.usage.completion_tokens_details.reasoning_tokens)

max_tokens ответа должен быть строго больше бюджета мыслей, иначе на текст не останется места и ответ придёт пустым с finish_reason: "length".

У Claude бюджет зажимается в диапазон 1024 … 128 000.

Скрыть цепочку

JSON
{
  "reasoning": { "effort": "high", "exclude": true }
}

Модель думает как обычно и тратит те же токены (вы за них платите), но текста мыслей в ответе не будет — ни в reasoning, ни в reasoning_details.

Алиасы

Кроме объекта reasoning в /chat/completions принимаются формы, которые шлют популярные клиенты. Все они нормализуются в reasoning до отправки модели.

JSON
{
  "reasoning_effort": "high",

  "thinking": { "type": "enabled" },

  "include_reasoning": true
}
АлиасВо что превращаетсяКто шлёт
reasoning_effort: "high"reasoning.effort: "high"OpenAI SDK, Codex CLI
thinking: { "type": "enabled" }reasoning.enabled: trueOpenCode, Z.AI SDK
thinking: { "type": "disabled" }reasoning.enabled: falseOpenCode, Z.AI SDK
thinking: { "budget_tokens": 2000 }reasoning.max_tokens: 2000Anthropic SDK
include_reasoning: truereasoning: {}Устаревший OpenRouter-стиль
include_reasoning: falsereasoning.exclude: trueУстаревший OpenRouter-стиль
reasoning_content в ответеТо же, что reasoningDeepSeek-совместимые клиенты

Явный reasoning в запросе главнее алиаса. В новом коде берите reasoning.

Как выглядит ответ

Non-stream — в choices[].message:

JSON
{
  "choices": [
    {
      "finish_reason": "stop",
      "message": {
        "role": "assistant",
        "content": "9.9 больше.",
        "reasoning": "Сравниваю 9.11 и 9.9 как десятичные…",
        "reasoning_details": [
          {
            "type": "reasoning.text",
            "text": "9.11 — это 9 + 11/100. 9.9 — это 9 + 9/10.",
            "signature": null,
            "id": "reasoning-text-1",
            "format": "anthropic-claude-v1",
            "index": 0
          }
        ]
      }
    }
  ],
  "usage": {
    "prompt_tokens": 19,
    "completion_tokens": 214,
    "total_tokens": 233,
    "completion_tokens_details": { "reasoning_tokens": 187 },
    "cost_rub": 0.42,
    "balance": 517.31
  }
}

Stream — то же в choices[].delta по чанкам:

JSON
{
  "choices": [
    {
      "delta": {
        "reasoning": "Сравниваю 9.11 и 9.9…",
        "reasoning_details": [
          {
            "type": "reasoning.text",
            "text": "Сравниваю 9.11 и 9.9…",
            "signature": null,
            "id": "reasoning-text-1",
            "format": "anthropic-claude-v1",
            "index": 0
          }
        ]
      }
    }
  ]
}
stream = client.chat.completions.create(
    model="claude-sonnet-4.6",
    messages=[{"role": "user", "content": "Что больше: 9.9 или 9.11?"}],
    max_tokens=10000,
    extra_body={"reasoning": {"max_tokens": 8000}},
    stream=True,
)

for chunk in stream:
    delta = chunk.choices[0].delta
    if getattr(delta, "reasoning", None):
        print("МЫСЛИ:", delta.reasoning, end="")
    elif getattr(delta, "content", None):
        print("ОТВЕТ:", delta.content, end="")

В usage смотрите completion_tokens_details.reasoning_tokens (в Responses — output_tokens_details.reasoning_tokens). Это часть completion_tokens, а не добавка сверху.

reasoning_details

reasoning — просто текст. reasoning_details — структура, которую нужно отдавать обратно, если вы продолжаете диалог с моделью.

Общие поля у каждого элемента:

ПолеСмысл
idИдентификатор блока, может быть null
formatЧей это формат (см. ниже)
indexПорядковый номер блока

Значения format: anthropic-claude-v1 (дефолт), google-gemini-v1, openai-responses-v1, azure-openai-responses-v1, bedrock-openai-responses-v1, xai-responses-v1, bedrock-xai-responses-v1, meta-responses-v1, unknown.

reasoning.text

Сырой текст мыслей, иногда с signature для проверки целостности.

JSON
{
  "type": "reasoning.text",
  "text": "Разберу по шагам:\n1. Сначала пойму, что именно спрашивают…",
  "signature": "sha256:abc123def456…",
  "id": "reasoning-text-1",
  "format": "anthropic-claude-v1",
  "index": 2
}

reasoning.summary

Краткое резюме цепочки вместо полного текста.

JSON
{
  "type": "reasoning.summary",
  "summary": "Модель разобрала задачу: сначала выделила ограничения, затем оценила варианты решения…",
  "id": "reasoning-summary-1",
  "format": "anthropic-claude-v1",
  "index": 0
}

reasoning.encrypted

Зашифрованные мысли. Читать нечего, но блок нужно вернуть модели без изменений, иначе цепочка порвётся.

JSON
{
  "type": "reasoning.encrypted",
  "data": "eyJlbmNyeXB0ZWQiOiJ0cnVlIiwiY29udGVudCI6IltSRURBQ1RFRF0ifQ==",
  "id": "reasoning-encrypted-1",
  "format": "anthropic-claude-v1",
  "index": 1
}

В стриме зашифрованное может прийти как [REDACTED]. Блоки приходят по мере готовности, в одном чанке их может быть несколько — склеивайте по порядку.

Сохранить между ходами

Нужно, когда модель вызвала инструмент и ждёт результат. Модель не «ответила и забыла» — она поставила ответ на паузу. Без исходных мыслей на следующем ходу она начинает цепочку заново: теряет ход рассуждений и может зациклиться на повторных вызовах инструментов.

Два способа отдать мысли обратно в assistant-сообщении:

  1. reasoning — обычная строка. Хватает моделям, которые отдают только сырой текст.
  2. reasoning_details — полный массив. Обязателен там, где есть шифрование или резюме (Claude, GPT).
JSON
{
  "role": "assistant",
  "content": null,
  "tool_calls": [{ "id": "call_abc", "type": "function", "function": { "name": "get_weather", "arguments": "{\"city\": \"Москва\"}" } }],
  "reasoning_details": [
    {
      "type": "reasoning.text",
      "text": "Нужна погода, затем посоветую одежду.",
      "format": "anthropic-claude-v1",
      "index": 0
    }
  ]
}
tools = [{
    "type": "function",
    "function": {
        "name": "get_weather",
        "description": "Текущая погода в городе",
        "parameters": {
            "type": "object",
            "properties": {"city": {"type": "string"}},
            "required": ["city"],
        },
    },
}]

messages = [{"role": "user", "content": "Какая погода в Москве? И что надеть?"}]

# Ход 1 — модель думает и просит инструмент
first = client.chat.completions.create(
    model="claude-sonnet-4.6",
    messages=messages,
    tools=tools,
    max_tokens=8000,
    extra_body={"reasoning": {"max_tokens": 2000}},
)
msg = first.choices[0].message

messages.append({
    "role": "assistant",
    "content": msg.content,
    "tool_calls": msg.tool_calls,
    # Отдаём мысли обратно как есть — не режем и не переставляем
    "reasoning_details": msg.reasoning_details,
})
messages.append({
    "role": "tool",
    "tool_call_id": msg.tool_calls[0].id,
    "content": '{"temperature": 7, "condition": "дождь"}',
})

# Ход 2 — модель продолжает ту же цепочку
second = client.chat.completions.create(
    model="claude-sonnet-4.6",
    messages=messages,
    tools=tools,
    max_tokens=8000,
)
print(second.choices[0].message.content)
  • reasoning_content — алиас reasoning, работает так же.
  • Формат одинаковый у всех моделей: код не меняется при переезде с Claude на GPT и обратно.

См. также вызов инструментов.

Мысли одной модели для другой

Мысли — обычный текст, их можно подложить в промпт другой модели. Дешёвая модель с чужим разбором часто отвечает лучше, чем сама по себе.

Python
question = "Что больше: 9.11 или 9.9?"

# Шаг 1 — берём мысли у сильной reasoning-модели
thinker = client.chat.completions.create(
    model="deepseek-r1-0528",
    messages=[{"role": "user", "content": f"{question} Подумай, но не давай ответ"}],
)
reasoning = getattr(thinker.choices[0].message, "reasoning", "")

# Шаг 2 — подкладываем их дешёвой модели как контекст
answer = client.chat.completions.create(
    model="gemini-3.7-flash",
    messages=[{"role": "user", "content": f"{question}\n\nВот разбор: {reasoning}"}],
)
print(answer.choices[0].message.content)

GPT-5.6 / GPT-6

reasoning.context

Какие прошлые мысли модель видит, когда вы возвращаете их в истории:

  • auto — дефолт модели. Не слать поле — то же самое.
  • all_turns — мысли со всех ходов во входе. Для многоходовых диалогов, где нужно продолжать прошлую цепочку.
  • current_turn — только текущий ход, прошлые reasoning-блоки игнорируются. Когда нужен свежий разбор без влияния прошлых ходов.
JSON
{
  "model": "gpt-5.6-sol",
  "reasoning": { "effort": "high", "context": "all_turns" }
}

Только GPT-5.6 / GPT-6 и новее. Дефолт зависит от модели — если поведение критично, задайте явно.

reasoning.mode

  • standard — обычное мышление. Не слать поле — то же самое.
  • pro — более глубокое многопроходное мышление для сложных задач.
JSON
{
  "model": "gpt-5.6-sol",
  "reasoning": { "mode": "pro" }
}

mode не зависит от effort: pro сочетается с любым уровнем. Цена за токен та же, что у обычного режима, но токенов обычно заметно больше.

Тот же результат даёт вызов модели с суффиксом -pro из каталога: gpt-6-astra-pro, gpt-5.6-sol-pro, gpt-5.6-terra-pro, gpt-5.6-luna-pro.

Claude

Включается только объектом reasoning — суффикса :thinking в имени модели у нас нет.

Бюджет:

  • reasoning.max_tokens — используется напрямую, минимум 1024.
  • reasoning.effort — бюджет считается от max_tokens ответа: max(min(max_tokens × доля, 128000), 1024). Доли: max/xhigh — 0.95, high — 0.8, medium — 0.5, low — 0.2, minimal — 0.1.

max_tokens ответа должен быть строго больше бюджета мыслей.

Резюме вместо полной цепочки

На новых Claude в ответ по умолчанию кладётся краткое резюме мыслей, а не полный текст. Поэтому символов в reasoning меньше, чем reasoning_tokens в usage: модель думала полным объёмом, показала сжатую версию. Списывается по факту сгенерированного, не по длине резюме.

В нативном /messages этим управляет thinking.display:

ЗначениеЧто в ответе
summarizedСжатое резюме мыслей (по умолчанию)
omittedМыслей нет — то же, что reasoning.exclude: true
JSON
{
  "model": "claude-sonnet-4.6",
  "max_tokens": 8000,
  "thinking": { "type": "enabled", "budget_tokens": 2000, "display": "omitted" }
}

Gemini 3

Gemini 3 (gemini-3.1-pro-preview, gemini-3.7-flash и другие) работает уровнями thinkingLevel, а не точным бюджетом. effort мапится напрямую:

reasoning.effortthinkingLevel у Google
minimalminimal
lowlow
mediummedium
highhigh
xhighhigh
JSON
{
  "model": "gemini-3.1-pro-preview",
  "reasoning": { "effort": "low" }
}

Сколько токенов съест уровень, решает Google — публичных границ нет. low на сложной задаче спокойно даёт несколько сотен reasoning-токенов, это нормально.

reasoning.max_tokens уходит как thinkingBudget, но Gemini 3 всё равно свернёт его в уровень: точного контроля над числом токенов не будет. У Gemini 2.5 бюджет работает прямее.

Другие семейства

МоделиКак включаетсяОсобенности
deepseek-r1-0528, deepseek-r1Думает всегдаОтдаёт полный текст мыслей в reasoning
qwen3-max-thinking, Qwen thinkingeffort или max_tokensmax_tokens уходит как thinking_budget, поддержка зависит от модели
glm-5.3, glm-5.3-flashВсегда включеноnone / disabled вернёт ошибку — отключить нельзя
glm-5.2 и старшеeffortРассуждения можно выключить через none
grok-4.6, grok-4.5effortУровни, не бюджет
Серия OpenAI oeffortДумает, но текст мыслей не отдаёт

Сколько это стоит

Reasoning-токены — это выходные токены. Тарифицируются по цене вывода модели из каталога и уже включены в usage.cost_rub.

Отсюда практическое следствие: exclude: true не экономит деньги, он только убирает текст из ответа. Экономит снижение effort или бюджета.

Частые проблемы

  • Ответ пустой, в reasoning текст есть. Лимит кончился на мыслях. Поднимите max_tokens ответа или снизьте effort / бюджет.
  • finish_reason: "length" на каждом запросе. Бюджет мыслей близок к max_tokens ответа. Разведите их: бюджет должен быть заметно меньше.
  • Модель зацикливается на вызовах инструментов. Не возвращаются reasoning_details между ходами — см. Сохранить между ходами.
  • reasoning пустой, токены списаны. Либо модель не отдаёт мысли (серия o), либо в запросе exclude: true.
  • Ошибка при effort: "none". У модели thinking обязателен, отключить нельзя.

Устаревшее

include_reasoning: true = reasoning: {}. include_reasoning: false = reasoning: { "exclude": true }. Работает для совместимости, в новом коде используйте объект reasoning.