Токены рассуждений
У моделей с 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
{
"model": "claude-sonnet-4.6",
"messages": [],
"reasoning": {
"effort": "high",
"exclude": false,
"enabled": true
}
}| Поле | Тип | Смысл |
|---|---|---|
effort | enum | max, xhigh, high, medium, low, minimal, none |
max_tokens | int | Жёсткий бюджет мыслей (Claude, Gemini 2.5, часть Qwen) |
exclude | bool | Думать, но не класть текст мыслей в ответ |
enabled | bool | Включить со средним усилием, если не задали effort / max_tokens |
context | enum | GPT-5.6+: auto, all_turns, current_turn |
mode | enum | GPT-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 уходит уровнем, а не числом.
Если модель не умеет запрошенный уровень, он мапится в ближайший поддерживаемый.
Выключить рассуждения
{
"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).
{
"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.
Скрыть цепочку
{
"reasoning": { "effort": "high", "exclude": true }
}Модель думает как обычно и тратит те же токены (вы за них платите), но текста мыслей в ответе не будет — ни в reasoning, ни в reasoning_details.
Алиасы
Кроме объекта reasoning в /chat/completions принимаются формы, которые шлют популярные клиенты. Все они нормализуются в reasoning до отправки модели.
{
"reasoning_effort": "high",
"thinking": { "type": "enabled" },
"include_reasoning": true
}| Алиас | Во что превращается | Кто шлёт |
|---|---|---|
reasoning_effort: "high" | reasoning.effort: "high" | OpenAI SDK, Codex CLI |
thinking: { "type": "enabled" } | reasoning.enabled: true | OpenCode, Z.AI SDK |
thinking: { "type": "disabled" } | reasoning.enabled: false | OpenCode, Z.AI SDK |
thinking: { "budget_tokens": 2000 } | reasoning.max_tokens: 2000 | Anthropic SDK |
include_reasoning: true | reasoning: {} | Устаревший OpenRouter-стиль |
include_reasoning: false | reasoning.exclude: true | Устаревший OpenRouter-стиль |
reasoning_content в ответе | То же, что reasoning | DeepSeek-совместимые клиенты |
Явный reasoning в запросе главнее алиаса. В новом коде берите reasoning.
Как выглядит ответ
Non-stream — в choices[].message:
{
"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 по чанкам:
{
"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 для проверки целостности.
{
"type": "reasoning.text",
"text": "Разберу по шагам:\n1. Сначала пойму, что именно спрашивают…",
"signature": "sha256:abc123def456…",
"id": "reasoning-text-1",
"format": "anthropic-claude-v1",
"index": 2
}reasoning.summary
Краткое резюме цепочки вместо полного текста.
{
"type": "reasoning.summary",
"summary": "Модель разобрала задачу: сначала выделила ограничения, затем оценила варианты решения…",
"id": "reasoning-summary-1",
"format": "anthropic-claude-v1",
"index": 0
}reasoning.encrypted
Зашифрованные мысли. Читать нечего, но блок нужно вернуть модели без изменений, иначе цепочка порвётся.
{
"type": "reasoning.encrypted",
"data": "eyJlbmNyeXB0ZWQiOiJ0cnVlIiwiY29udGVudCI6IltSRURBQ1RFRF0ifQ==",
"id": "reasoning-encrypted-1",
"format": "anthropic-claude-v1",
"index": 1
}В стриме зашифрованное может прийти как [REDACTED]. Блоки приходят по мере готовности, в одном чанке их может быть несколько — склеивайте по порядку.
Сохранить между ходами
Нужно, когда модель вызвала инструмент и ждёт результат. Модель не «ответила и забыла» — она поставила ответ на паузу. Без исходных мыслей на следующем ходу она начинает цепочку заново: теряет ход рассуждений и может зациклиться на повторных вызовах инструментов.
Два способа отдать мысли обратно в assistant-сообщении:
reasoning— обычная строка. Хватает моделям, которые отдают только сырой текст.reasoning_details— полный массив. Обязателен там, где есть шифрование или резюме (Claude, GPT).
{
"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 и обратно.
См. также вызов инструментов.
Мысли одной модели для другой
Мысли — обычный текст, их можно подложить в промпт другой модели. Дешёвая модель с чужим разбором часто отвечает лучше, чем сама по себе.
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-блоки игнорируются. Когда нужен свежий разбор без влияния прошлых ходов.
{
"model": "gpt-5.6-sol",
"reasoning": { "effort": "high", "context": "all_turns" }
}Только GPT-5.6 / GPT-6 и новее. Дефолт зависит от модели — если поведение критично, задайте явно.
reasoning.mode
standard— обычное мышление. Не слать поле — то же самое.pro— более глубокое многопроходное мышление для сложных задач.
{
"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 |
{
"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.effort | thinkingLevel у Google |
|---|---|
minimal | minimal |
low | low |
medium | medium |
high | high |
xhigh | high |
{
"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 thinking | effort или max_tokens | max_tokens уходит как thinking_budget, поддержка зависит от модели |
glm-5.3, glm-5.3-flash | Всегда включено | none / disabled вернёт ошибку — отключить нельзя |
glm-5.2 и старше | effort | Рассуждения можно выключить через none |
grok-4.6, grok-4.5 | effort | Уровни, не бюджет |
| Серия OpenAI o | effort | Думает, но текст мыслей не отдаёт |
Сколько это стоит
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.