Когда одного шага недостаточно

Представь: ты просишь агента спланировать трёхдневную командировку с учётом бюджета, расписания встреч и предпочтений по отелям. Если агент просто «выстреливает» ответом — он либо игнорирует часть ограничений, либо делает внутренне противоречивый план.

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

Решение простое по идее и мощное на практике: заставить модель генерировать промежуточные шаги прежде чем дать финальный ответ. Эти шаги — скратчпад (scratch pad, черновик). Их содержимое попадает в контекст и буквально «видно» модели при генерации следующего токена.

🖊️ Почему «scratchpad»

Scratch pad — блокнот для черновиков, «блокнот математика»: место, где пишут промежуточные вычисления, которые не входят в итоговый ответ. В контексте LLM — это токены, которые модель генерирует «для себя», а не для пользователя.

Вот конкретный пример. Задача: «Сколько секунд в 2 неделях и 3 днях?»

💭 Скратчпад агента (chain-of-thought)
Шаг 1 Нужно найти общее количество секунд. Разобью на части: сначала переведу недели в дни, потом дни в секунды.
Шаг 2 2 недели = 2 × 7 = 14 дней. Добавляем 3 дня: 14 + 3 = 17 дней итого.
Шаг 3 17 дней × 24 часа = 408 часов.
Шаг 4 408 часов × 60 минут = 24 480 минут.
Шаг 5 24 480 минут × 60 секунд = 1 468 800 секунд.
Ответ В 2 неделях и 3 днях содержится 1 468 800 секунд.

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

Как chain-of-thought работает внутри трансформера

Чтобы понять, почему скратчпад работает — нужно понять один ключевой факт об архитектуре трансформеров: модель не «думает» больше при сложных задачах, она просто генерирует больше токенов. Количество вычислений на один токен — фиксировано.

Это создаёт фундаментальную асимметрию: вопрос «каков корень из 144?» занимает 5 токенов, но при генерации ответа модель расходует ровно столько же вычислений, сколько на вопрос «как пройти в библиотеку?». Для простых задач этого хватает. Для сложных — нет.

100%
колёсико — масштаб  ·  зажать и тянуть — перемещение
БЕЗ SCRATCHPAD ЗАПРОС Сколько секунд в 2 нед. и 3 днях? LLM Ответ «1 382 400?» Высокий шанс ошибки при многошаговых задачах Один проход. Нет промежуточных шагов. С SCRATCHPAD (CHAIN-OF-THOUGHT) ЗАПРОС тот же вопрос LLM SCRATCHPAD Шаг 1 разбить на части Шаг 2 нед→дни 17 дней Шаг 3 ×24×60 24 480 мин Шаг 4 ×60 сек 1 468 800 с Ответ 1 468 800 с ✓ каждый шаг «видит» все предыдущие через attention

Ключевой механизм: когда модель генерирует токены шага 2, эти токены уже находятся в контексте при генерации шага 3. Attention-механизм позволяет каждому новому токену «смотреть» на все предыдущие — включая промежуточные шаги скратчпада. Именно поэтому chain-of-thought работает: рассуждение само становится контекстом для следующего рассуждения.

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

Насколько это улучшает результаты

Исследование Wei et al. (2022) «Chain-of-Thought Prompting Elicits Reasoning in Large Language Models» показало: для задач, требующих многошаговых рассуждений, CoT кардинально улучшает точность. Причём разрыв тем больше, чем сложнее задача.

Без CoT
С CoT
Арифметика (GSM8K)
17%
58%
Логика (SVAMP)
23%
63%
Комбинаторика
8%
51%
Простые вопросы
78%
80%

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

⚠️ CoT работает не у всех моделей одинаково

Эффект chain-of-thought значительно усиливается с размером модели. У небольших моделей (до ~7B параметров) CoT иногда даже снижает качество — модель начинает «рассуждать» бессвязно и запутывается. Для агентов на базе Claude это не проблема, но держи в виду при работе с open-source моделями.

Три вида памяти агента: где скратчпад

Прежде чем переходить к реализации — зафиксируем, как скратчпад соотносится с другими типами памяти. Это разные инструменты для разных задач.

Тип Хранится где Живёт как долго Объём Задача
Краткосрочная Context window (messages) Текущая сессия До 200 к токенов История диалога, контекст разговора
Рабочая (scratchpad) Context window (одно сообщение) Один ответ Сотни–тысячи токенов Промежуточные рассуждения, черновик
Долгосрочная Внешнее хранилище (Vector DB) Между сессиями Миллионы документов Факты, документы, прошлые разговоры

Скратчпад — самый эфемерный тип: он живёт только во время генерации одного ответа. После того как ответ сформирован, скратчпад либо показывается пользователю (явный CoT), либо скрывается (extended thinking), либо сохраняется в историю как часть диалога.

Три вида скратчпада: как выбрать

Существует три принципиально разных способа реализовать рабочую память агента. Выбор зависит от того, нужен ли тебе доступ к процессу рассуждения, и как агент интегрирован в систему.

Неявный CoT
implicit

Триггер через промпт: «Думай пошагово» или «Let's think step by step». Модель сама решает, как рассуждать. Рассуждение видно в ответе.

  • Простая интеграция — одна фраза в промпте
  • Пользователь видит ход мыслей
  • Нет контроля над структурой
Явный скратчпад
explicit

В промпте задаётся структура через XML-теги: <thinking>...</thinking>. Рассуждение парсится и при необходимости скрывается от пользователя.

  • Полный контроль — ты парсишь результат
  • Можно логировать, отлаживать, отображать
  • Требует более сложного системного промпта
Extended Thinking
API feature

Встроенный механизм Claude: модель получает скрытый буфер токенов для рассуждений перед генерацией ответа. Контролируется параметром API.

  • Максимальная глубина рассуждений
  • Thinking блоки в ответе (опционально скрыты)
  • Доступно только в Claude API

Extended Thinking в Claude: встроенный скратчпад

Extended Thinking — это API-фича, которая даёт Claude специальный буфер токенов исключительно для рассуждений. Эти токены генерируются до финального ответа, но не отображаются пользователю по умолчанию. Ты как разработчик получаешь к ним доступ через ответ API.

Когда включён Extended Thinking, ответ содержит два типа контент-блоков:

thinking block (скрыт от пользователя)
Задача: спланировать командировку. Есть ограничения: бюджет 50 000 руб., встречи во вторник и четверг, предпочтение бизнес-отелей.

Шаг 1: Определю даты. Вторник — прилёт, пятница — вылет. Это 4 ночи.
Шаг 2: Бюджет. Перелёт туда-обратно ~12 000 руб. Остаток: 38 000 руб.
Шаг 3: Отель. 4 ночи × 8 000 = 32 000 руб. Вписывается в бюджет...
text block (показывается пользователю)
Предлагаю следующий план: прилёт во вторник утром, вылет в пятницу вечером (4 ночи). Отель «Метрополь» — 8 000 руб./ночь, итого 32 000 руб. Перелёт — 12 000 руб. Суммарные расходы: 44 000 руб., укладываемся в бюджет.

Вот как это выглядит в коде с Anthropic SDK:

import anthropic

client = anthropic.Anthropic()


def think_and_answer(question: str, budget_tokens: int = 8000) -> dict:
    """
    Задаёт вопрос с включённым Extended Thinking.
    Возвращает thinking и финальный ответ отдельно.

    budget_tokens: количество токенов для рассуждений (минимум 1024).
    Чем сложнее задача — тем больше бюджет.
    """
    response = client.messages.create(
        model="claude-opus-4-6",
        max_tokens=budget_tokens + 4096,   # max_tokens > budget_tokens — обязательно
        thinking={
            "type": "enabled",
            "budget_tokens": budget_tokens
        },
        messages=[{"role": "user", "content": question}]
    )

    result = {"thinking": None, "answer": None}

    for block in response.content:
        if block.type == "thinking":
            result["thinking"] = block.thinking
        elif block.type == "text":
            result["answer"] = block.text

    return result


# Пример: сложная логическая задача
q = """
Есть 5 домов разного цвета. В каждом живёт человек разной национальности.
Каждый пьёт свой напиток, курит свои сигареты, держит своё животное.
Норвежец живёт в первом доме. Красный дом стоит слева от белого...
[знаменитая загадка Эйнштейна]
Кто держит рыбок?
"""

result = think_and_answer(q, budget_tokens=10000)

# Логируем thinking для отладки
print("=== РАССУЖДЕНИЯ МОДЕЛИ ===")
print(result["thinking"][:500] + "...")   # первые 500 символов

print("\n=== ФИНАЛЬНЫЙ ОТВЕТ ===")
print(result["answer"])

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

budget_tokens — не жёсткий лимит, а верхняя граница. Если задача простая, модель потратит меньше. Если сложная — до лимита. Рекомендации:

  • 1 024–4 000 — анализ данных, сравнение вариантов, умеренно сложная логика
  • 4 000–10 000 — многошаговое планирование, код-ревью, задачи с несколькими ограничениями
  • 10 000–32 000 — сложные математические задачи, стратегический анализ, задачи олимпиадного уровня
⚠️ Extended Thinking несовместим с некоторыми параметрами

При включённом thinking нельзя использовать: temperature и top_p (не поддерживаются), stream=True работает, но thinking-блоки приходят целиком по завершении. Также thinking несовместим с prompt caching на уровне messages.

Extended Thinking в агентском цикле

При использовании tool calling с Extended Thinking — мышление происходит перед каждым вызовом инструмента. Это позволяет агенту обдумать, какой инструмент вызвать и с какими аргументами:

tools = [
    {
        "name": "search_web",
        "description": "Поиск актуальной информации в интернете",
        "input_schema": {
            "type": "object",
            "properties": {
                "query": {"type": "string", "description": "Поисковый запрос"}
            },
            "required": ["query"]
        }
    },
    {
        "name": "calculate",
        "description": "Точные арифметические вычисления",
        "input_schema": {
            "type": "object",
            "properties": {
                "expression": {"type": "string", "description": "Математическое выражение"}
            },
            "required": ["expression"]
        }
    }
]


def agent_with_thinking(user_message: str) -> str:
    """Агент с Extended Thinking и tool calling."""
    messages = [{"role": "user", "content": user_message}]

    while True:
        response = client.messages.create(
            model="claude-opus-4-6",
            max_tokens=16000,
            thinking={"type": "enabled", "budget_tokens": 8000},
            tools=tools,
            messages=messages
        )

        # Добавляем полный ответ (с thinking-блоками) в историю
        # Это важно: thinking-блоки нужно передавать обратно для корректной кэшируемости
        messages.append({"role": "assistant", "content": response.content})

        if response.stop_reason == "end_turn":
            # Извлекаем только текстовый ответ для пользователя
            return next(
                b.text for b in response.content if b.type == "text"
            )

        if response.stop_reason == "tool_use":
            tool_results = []
            for block in response.content:
                if block.type == "tool_use":
                    # Выполняем инструмент
                    result = execute_tool(block.name, block.input)
                    tool_results.append({
                        "type": "tool_result",
                        "tool_use_id": block.id,
                        "content": result
                    })

            messages.append({"role": "user", "content": tool_results})
💡 Thinking-блоки в истории диалога

При многоходовом диалоге с Extended Thinking необходимо передавать thinking-блоки обратно в messages вместе с ответом ассистента. API требует этого для внутренней консистентности. Но содержимое thinking не попадает к пользователю — только в твои логи.

Явный скратчпад через промпт-инжиниринг

Extended Thinking — не единственный способ добавить рабочую память. Иногда нужно большее: сохранить рассуждения в историю, отобразить их пользователю, использовать в другой модели. Тогда используют явный скратчпад через XML-теги в промпте.

Принцип: в системный промпт добавляем инструкцию писать рассуждения внутри тегов <thinking>...</thinking>, а финальный ответ — в <answer>...</answer>. Потом парсим теги.

import re
import anthropic

client = anthropic.Anthropic()

SYSTEM_PROMPT = """Ты — аналитический ассистент.

При ответе на любой сложный вопрос ОБЯЗАТЕЛЬНО:
1. Сначала запиши рассуждения внутри тегов ...
2. Потом дай финальный ответ внутри тегов ...

Формат ответа:

[Здесь — пошаговые рассуждения, промежуточные вычисления, анализ вариантов]


[Здесь — чёткий и конкретный финальный ответ для пользователя]

"""


def parse_scratchpad(response_text: str) -> tuple[str, str]:
    """
    Парсит явный скратчпад из ответа модели.
    Возвращает (thinking, answer).
    """
    thinking_match = re.search(
        r'(.*?)',
        response_text,
        re.DOTALL
    )
    answer_match = re.search(
        r'(.*?)',
        response_text,
        re.DOTALL
    )

    thinking = thinking_match.group(1).strip() if thinking_match else ""
    answer = answer_match.group(1).strip() if answer_match else response_text.strip()

    return thinking, answer


def ask_with_scratchpad(question: str, show_thinking: bool = False) -> str:
    """
    Задаёт вопрос с явным скратчпадом.
    show_thinking=True — логирует рассуждения для отладки.
    """
    response = client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=4096,
        system=SYSTEM_PROMPT,
        messages=[{"role": "user", "content": question}]
    )

    thinking, answer = parse_scratchpad(response.content[0].text)

    if show_thinking:
        print("── РАССУЖДЕНИЯ ──")
        print(thinking)
        print("── ОТВЕТ ──")

    return answer


# Использование
answer = ask_with_scratchpad(
    "Если я инвестирую 100 000 руб. под 12% годовых, "
    "сколько у меня будет через 5 лет с учётом реинвестирования?",
    show_thinking=True
)

Структурированный скратчпад для агентов

Для агентов, которые используют инструменты, можно встроить скратчпад прямо в схему вызова инструмента. Это позволяет агенту «думать» перед каждым вызовом — прямо в JSON-объекте:

tools_with_thinking = [
    {
        "name": "search_database",
        "description": """Поиск по базе данных клиентов.
        ВАЖНО: перед вызовом заполни поле 'reasoning' — объясни,
        почему именно такой запрос поможет решить задачу.""",
        "input_schema": {
            "type": "object",
            "properties": {
                "reasoning": {
                    "type": "string",
                    "description": "Твоё рассуждение: зачем нужен этот запрос, какую информацию ожидаешь найти"
                },
                "query": {
                    "type": "string",
                    "description": "SQL-запрос или поисковое выражение"
                },
                "filters": {
                    "type": "object",
                    "description": "Опциональные фильтры"
                }
            },
            "required": ["reasoning", "query"]
        }
    }
]

# Теперь каждый вызов инструмента будет содержать reasoning:
# {
#   "reasoning": "Нужно найти всех клиентов с задолженностью > 30 дней,
#                 чтобы определить объём проблемной дебиторки",
#   "query": "SELECT * FROM clients WHERE overdue_days > 30",
#   "filters": {"status": "active"}
# }

# Это можно логировать, отображать в UI отладки, или использовать
# для объяснения действий агента пользователю.
Когда использовать явный скратчпад вместо Extended Thinking

Явный скратчпад лучше подходит когда: (1) нужно сохранять рассуждения в историю диалога для последующего анализа; (2) рассуждения должны быть отображены пользователю в интерфейсе (например, «агент думает...»); (3) агент работает с нестандартными моделями. Extended Thinking — когда важна максимальная глубина рассуждений и не нужен полный контроль над форматом.

Скратчпад в ReAct-цикле

ReAct (Reason + Act) — паттерн, в котором агент чередует рассуждение и действие. «Reason» — это и есть скратчпад: перед каждым действием агент явно фиксирует свои мысли. Между итерациями рассуждения накапливаются в истории, создавая контекст для следующего шага.

Разберём полную реализацию ReAct-агента с явным скратчпадом и Extended Thinking:

from dataclasses import dataclass, field
from typing import Any
import anthropic

client = anthropic.Anthropic()


@dataclass
class AgentStep:
    """Один шаг ReAct-цикла с сохранённым скратчпадом."""
    step_num: int
    thought: str           # рассуждение агента
    action: str            # название инструмента
    action_input: dict     # аргументы инструмента
    observation: str       # результат выполнения
    thinking: str = ""     # Extended Thinking (если включён)


@dataclass
class AgentTrace:
    """Полная трасса выполнения агента для отладки."""
    task: str
    steps: list[AgentStep] = field(default_factory=list)
    final_answer: str = ""

    def show(self) -> None:
        """Выводит читаемую трассу выполнения."""
        print(f"\n{'='*60}")
        print(f"ЗАДАЧА: {self.task}")
        print('='*60)
        for step in self.steps:
            print(f"\n[Шаг {step.step_num}]")
            if step.thinking:
                print(f"  Extended Thinking: {step.thinking[:200]}...")
            print(f"  Мысль: {step.thought}")
            print(f"  Действие: {step.action}({step.action_input})")
            print(f"  Результат: {step.observation[:200]}")
        print(f"\nФИНАЛЬНЫЙ ОТВЕТ: {self.final_answer}")


REACT_SYSTEM = """Ты — агент, который решает задачи методом Reason-Act-Observe.

На каждом шаге ты ДОЛЖЕН:
1. Написать свои рассуждения в поле 'thought' (scratchpad)
2. Выбрать инструмент и заполнить его аргументы
3. Проанализировать результат инструмента

В поле 'thought' пиши честно: что ты знаешь, чего не знаешь, почему
выбираешь именно этот инструмент. Это поможет тебе принять лучшее решение.

Когда у тебя достаточно информации — вызови инструмент 'finish' с ответом."""

TOOLS = [
    {
        "name": "search",
        "description": "Поиск информации по запросу",
        "input_schema": {
            "type": "object",
            "properties": {
                "thought": {"type": "string", "description": "Твоё рассуждение перед поиском"},
                "query": {"type": "string", "description": "Поисковый запрос"}
            },
            "required": ["thought", "query"]
        }
    },
    {
        "name": "calculate",
        "description": "Вычислить математическое выражение",
        "input_schema": {
            "type": "object",
            "properties": {
                "thought": {"type": "string", "description": "Зачем нужно вычисление"},
                "expression": {"type": "string", "description": "Выражение Python: eval(expression)"}
            },
            "required": ["thought", "expression"]
        }
    },
    {
        "name": "finish",
        "description": "Завершить выполнение с финальным ответом",
        "input_schema": {
            "type": "object",
            "properties": {
                "thought": {"type": "string", "description": "Итоговое рассуждение"},
                "answer": {"type": "string", "description": "Финальный ответ для пользователя"}
            },
            "required": ["thought", "answer"]
        }
    }
]


def execute_tool(name: str, args: dict) -> str:
    """Выполняет инструмент и возвращает строку-результат."""
    if name == "search":
        # Здесь — реальный поиск (SerpAPI, Brave Search и т.д.)
        return f"[Результаты поиска по '{args['query']}': ...]"
    elif name == "calculate":
        try:
            result = eval(args["expression"], {"__builtins__": {}})
            return str(result)
        except Exception as e:
            return f"Ошибка вычисления: {e}"
    return ""


def run_react_agent(task: str, max_steps: int = 10, use_thinking: bool = True) -> AgentTrace:
    """
    Запускает ReAct-агента с явным скратчпадом в каждом шаге.
    use_thinking=True включает Extended Thinking дополнительно к явному CoT.
    """
    trace = AgentTrace(task=task)
    messages = [{"role": "user", "content": task}]

    for step_num in range(1, max_steps + 1):
        kwargs = {
            "model": "claude-sonnet-4-6",
            "max_tokens": 8192,
            "system": REACT_SYSTEM,
            "tools": TOOLS,
            "messages": messages
        }

        if use_thinking:
            kwargs["model"] = "claude-opus-4-6"
            kwargs["thinking"] = {"type": "enabled", "budget_tokens": 4000}
            kwargs["max_tokens"] = 12000

        response = client.messages.create(**kwargs)
        messages.append({"role": "assistant", "content": response.content})

        # Извлекаем Extended Thinking если есть
        extended_thinking = ""
        if use_thinking:
            for block in response.content:
                if block.type == "thinking":
                    extended_thinking = block.thinking

        if response.stop_reason == "tool_use":
            tool_results = []
            for block in response.content:
                if block.type != "tool_use":
                    continue

                if block.name == "finish":
                    trace.final_answer = block.input["answer"]
                    trace.steps.append(AgentStep(
                        step_num=step_num,
                        thought=block.input.get("thought", ""),
                        action="finish",
                        action_input=block.input,
                        observation="[завершение]",
                        thinking=extended_thinking
                    ))
                    return trace

                # Выполняем инструмент
                observation = execute_tool(block.name, block.input)

                trace.steps.append(AgentStep(
                    step_num=step_num,
                    thought=block.input.get("thought", ""),
                    action=block.name,
                    action_input=block.input,
                    observation=observation,
                    thinking=extended_thinking
                ))

                tool_results.append({
                    "type": "tool_result",
                    "tool_use_id": block.id,
                    "content": observation
                })

            messages.append({"role": "user", "content": tool_results})

        else:
            # stop_reason == "end_turn" — модель решила отвечать текстом
            text = next((b.text for b in response.content if b.type == "text"), "")
            trace.final_answer = text
            break

    return trace


# Запуск
trace = run_react_agent(
    "Сколько шагов нужно сделать за 30-минутную прогулку, если средний шаг — 75 см?",
    use_thinking=True
)
trace.show()

Обрати внимание: в каждом вызове инструмента есть поле thought. Это явный скратчпад — агент обязан написать рассуждение перед действием. Плюс, при use_thinking=True, дополнительно работает Extended Thinking. Это двойной скратчпад: один для логики выбора инструмента, другой — для глубокого анализа ситуации.

💡 Зачем хранить AgentTrace

AgentTrace — не просто дебаг-инструмент. Сохранённые скратчпады позволяют: (1) обнаружить где агент «сбился» при ошибках; (2) улучшить промпт на основе реальных рассуждений; (3) показать пользователю прозрачный процесс решения («агент думал вот так»). Логируй трассы в продакшне.

Типичные ошибки

Скратчпад для каждого запроса, даже простого
«Let's think step by step» или Extended Thinking на вопросах вроде «Как тебя зовут?» — лишние токены и задержка без какой-либо пользы. CoT помогает при многошаговых задачах. При тривиальных — замедляет.
Включай скратчпад только для задач с вычислениями, планированием, логическим выводом или несколькими ограничениями одновременно.
Не передавать thinking-блоки обратно в messages
При Extended Thinking в многоходовом диалоге многие разработчики сохраняют в историю только текстовые блоки ответа, выбрасывая thinking. Claude API требует передавать thinking-блоки обратно — без этого возникает ошибка или нарушается консистентность кэша.
Добавляй в messages полный response.content (список всех блоков), а не только текст.
Парсинг тегов через split() вместо regex
text.split("<thinking>")[1].split("</thinking>")[0] ломается, если модель напишет thinking-блок два раза, вставит пробел в тег, или вовсе не напишет его. В продакшне это происходит чаще, чем кажется.
Используй re.search(r'<thinking>(.*?)</thinking>', text, re.DOTALL) и обрабатывай случай отсутствия тега.
Слишком короткий budget_tokens для сложных задач
Выставили budget_tokens=1024 для задачи, требующей длинного рассуждения. Модель «обрезает» мышление на середине и выдаёт неполный ответ — но ошибки в API нет, всё выглядит нормально.
Для задач с несколькими шагами начинай с 4 000–8 000 токенов. Смотри на длину thinking-блока в ответе — если он обрывается, увеличь бюджет.
Показывать thinking пользователю без фильтрации
Thinking-блоки — внутренний монолог модели. Там могут быть неуверенные формулировки, пересмотренные гипотезы, «возможно, я ошибаюсь...». Показывать это пользователю без контекста — ухудшает восприятие продукта.
Используй thinking для логов и отладки. Для пользователя — только финальный text-блок. Если хочешь показать «процесс мышления» — используй явный скратчпад с контролем формата.

Шпаргалка

  • Scratchpad — рабочая память для одного ответа: промежуточные токены, которые агент генерирует «для себя»
  • Механика CoT: каждый шаг рассуждения попадает в контекст и «виден» через attention при следующем шаге
  • CoT помогает на сложных задачах; на тривиальных — лишние токены без пользы
  • Extended Thinking: thinking={"type": "enabled", "budget_tokens": N}, N ≥ 1024, max_tokens > N
  • Ответ с Extended Thinking содержит блоки type="thinking" и type="text"
  • Thinking-блоки нужно передавать обратно в messages при многоходовом диалоге
  • Явный скратчпад: XML-теги в промпте (<thinking>) + re.search() для парсинга
  • Поле reasoning/thought в схеме инструмента — встроенный скратчпад для каждого вызова
  • В ReAct-цикле поле thought в каждом шаге — это скратчпад агента
  • Логируй AgentTrace в продакшне: скратчпад — главный инструмент отладки
  • Extended Thinking + явный thought в инструментах — двойной скратчпад для максимальной надёжности

Практические задания

  1. Сравни качество с CoT и без. Составь список из 5 задач: 2 простых (фактический вопрос, перефразирование) и 3 сложных (многошаговая математика, планирование с ограничениями, логическая головоломка). Запусти каждую с CoT и без. Замерь точность и длину ответа. Убедись, что CoT помогает только там, где ожидалось.
  2. Явный скратчпад с визуализацией. Реализуй агента с явным скратчпадом (через XML-теги). Добавь веб-интерфейс (Flask или FastAPI), где рядом с ответом отображается «ход рассуждений» в раскрывающемся блоке. Пользователь должен видеть, что агент думал, прежде чем ответить.
  3. Extended Thinking для отладки ReAct. Возьми любого ReAct-агента, добавь Extended Thinking. Сохраняй thinking-блоки в AgentTrace. Найди задачу, на которой агент делает ошибку (например, вызывает не тот инструмент). Изучи thinking-блок этого шага — что именно пошло не так в рассуждениях? Исправь это через уточнение системного промпта.