Chain of Thought: как заставить LLM думать перед ответом

opensourceaillmprompt-engineeringreasoningit
← Back to Blog

Введение: почему LLM ошибается в простых задачах?

Prompt: У меня есть 3 яблока. Я съел 1, потом купил ещё 2 и съел ещё 1.
Сколько у меня яблок?

LLM без CoT: 3  ← неправильно!
LLM с CoT: 
  1. Было 3 яблока
  2. Съел 1 → осталось 2
  3. Купил 2 → стало 4
  4. Съел 1 → осталось 3
  Ответ: 3  ← правильно!

Chain of Thought (CoT) — это техника промптинга, при которой мы заставляем модель разбивать рассуждение на шаги.


Zero-Shot Chain of Thought

Основная идея

Просто добавляем инструкцию "давайте подумаем шаг за шагом".

Prompt (zero-shot CoT):
  "У меня есть 3 яблока. Я съел 1, потом купил ещё 2 и съел ещё 1.
   Сколько у меня яблок? Давайте подумаем шаг за шагом."

Ответ:
  "Давайте подумаем шаг за шагом:
   1. Сначала у меня было 3 яблока.
   2. Я съел 1 яблоко, осталось 3 - 1 = 2 яблока.
   3. Я купил ещё 2 яблока, стало 2 + 2 = 4 яблока.
   4. Я съел ещё 1 яблоко, осталось 4 - 1 = 3 яблока.
   
   Ответ: 3 яблока."

Почему это работает?

LLM обучена на огромном корпусе текста, включая:
  - Решения задач из учебников
  - Обсуждения на StackOverflow
  - Разбор проблем на форумах
  
Когда мы говорим "давайте подумаем шаг за шагом",
мы активируем соответствующие паттерны в модели.

Few-Shot Chain of Thought

Основная идея

Даём примеры решений с промежуточными шагами.

Prompt (few-shot CoT):

  Вопрос: Что такое 15% от 200?
  Ответ: 
    1. 10% от 200 = 20
    2. 5% от 200 = 10
    3. 15% = 20 + 10 = 30
    Ответ: 30
  
  Вопрос: В классе 24 ученика. 1/3 девочек.
  Сколько девочек?
  Ответ:
    1. Всего учеников: 24
    2. Девочек: 1/3 от 24
    3. 24 / 3 = 8
    Ответ: 8
  
  Вопрос: У меня 3 яблока. Съел 1, купил 2, съел 1.
  Сколько яблок?
  Ответ:

Few-shot vs Zero-shot

                    | Zero-shot CoT       | Few-shot CoT
  ------------------|---------------------|---------------------
  Качество          | Хорошее             | Лучше
  Длина промпта     | Короткий            | Длиннее
  Стоимость (tokens)| Меньше              | Больше
  Контроль формата  | Низкий              | Высокий
  Нужны примеры     | Нет                 | Да

Tree of Thoughts (ToT)

Основная идея

Chain of Thought — это ОДНА линия рассуждений.
Tree of Thoughts — МНОЖЕСТВО вариантов развития.

ToT позволяет модели:
  1. Генерировать несколько возможных следующих шагов
  2. Оценивать каждый шаг
  3. Выбирать наиболее перспективный
  4. Возвращаться (backtrack) если путь тупиковый

Визуализация

Tree of Thoughts:

                    [Начало]
                   /    |    \
              Thought1 Thought2 Thought3
               /   \      |       /   \
            T1.1  T1.2   T2.1   T3.1  T3.2
            /  \    |      |     /
          T1.1.1 T1.2.1 T2.1.1 T3.1.1
          
          Оценка:
          T1.1.1: 0.8 ← лучший путь!
          T1.2.1: 0.3
          T2.1.1: 0.5
          T3.1.1: 0.2

Реализация ToT

import itertools

class TreeOfThoughts:
    def __init__(self, model, evaluator, beam_size=3, depth=5):
        self.model = model
        self.evaluator = evaluator
        self.beam_size = beam_size
        self.depth = depth
    
    def generate_thoughts(self, state, n_thoughts=3):
        """Генерируем n возможных следующих мыслей."""
        prompt = f"Состояние: {state}\n\n"
        prompt += "Сгенерируйте {n_thoughts} возможных следующих шагов:"
        
        response = self.model.generate(prompt, max_tokens=500)
        thoughts = self._parse_thoughts(response)
        return thoughts[:n_thoughts]
    
    def evaluate_state(self, state):
        """Оцениваем состояние от 0 до 1."""
        return self.evaluator.predict(state)
    
    def solve(self, problem):
        """Решаем задачу с помощью Tree of Thoughts."""
        # Начальные состояния
        current_states = self.generate_thoughts(problem, n_thoughts=self.beam_size)
        
        for step in range(self.depth):
            all_candidates = []
            
            for state in current_states:
                next_thoughts = self.generate_thoughts(state)
                for thought in next_thoughts:
                    new_state = f"{state} → {thought}"
                    score = self.evaluate_state(new_state)
                    all_candidates.append((new_state, score))
            
            # Выбираем лучшие состояния (beam search)
            all_candidates.sort(key=lambda x: x[1], reverse=True)
            current_states = [s for s, _ in all_candidates[:self.beam_size]]
            
            # Проверяем, решена ли задача
            for state in current_states:
                if self._is_solution(state):
                    return state
        
        return current_states[0]

Self-Consistency CoT

Основная идея

Генерируем МНОЖЕСТВО цепочек рассуждений
и выбираем наиболее популярный ответ.

Почему это работает:
  - LLM может генерировать разные пути к одному ответу
  - Правильный ответ будет повторяться чаще
  - Неправильные ответы "размываются"

Пример

from collections import Counter

def self_consistent_cot(model, prompt, n_samples=10):
    """
    Self-Consistency CoT:
    Генерируем n цепочек рассуждений,
    выбираем ответ по большинству.
    """
    responses = []
    
    for _ in range(n_samples):
        # Генерируем CoT ответ
        cot_prompt = f"{prompt} Давайте подумаем шаг за шагом."
        response = model.generate(
            cot_prompt,
            temperature=0.7,  # разнообразие!
            max_tokens=500
        )
        responses.append(response)
    
    # Извлекаем финальные ответы
    final_answers = [self._extract_answer(r) for r in responses]
    
    # Голосование по большинству
    vote = Counter(final_answers)
    most_common = vote.most_common(1)[0][0]
    
    return most_common, vote

# Пример:
# responses = ["3", "5", "3", "3", "4", "3", "2", "3", "3", "5"]
# vote = {"3": 6, "5": 2, "4": 1, "2": 1}
# most_common = "3" ← правильный ответ!

Self-Consistency vs обычный CoT

                    | Обычный CoT     | Self-Consistency
  ------------------|-----------------|---------------------
  Точность          | ~70%            | ~90%
  Стоимость         | 1x              | 10x (n_samples)
  Латентность       | Быстрее         | Медленнее
  Качество          | Хорошо          | Отлично

ReAct: Reasoning + Acting

Основная идея

ReAct = Reasoning + Acting

Модель чередует:
  1. Thought — рассуждение
  2. Action — выполнение действия (поиск, вычисление)
  3. Observation — результат действия
  4. Thought — дальнейшее рассуждение
  ...
  Answer — финальный ответ

Пример ReAct

Prompt: "Какая высота Эйфелевой башни?"

Thought 1: Мне нужно найти высоту Эйфелевой башни.
Action: search("Eiffel Tower height")
Observation: 330 метров (1 063 футов)
Thought 2: Высота Эйфелевой башни — 330 метров.
Answer: 330 метров.

---

Prompt: "Сколько будет 17 * 23 + 45?"

Thought 1: Мне нужно умножить 17 на 23, затем прибавить 45.
Action: calculate("17 * 23 + 45")
Observation: 436
Thought 2: Ответ: 436.
Answer: 436.

Реализация ReAct

class ReActAgent:
    def __init__(self, model, tools):
        self.model = model
        self.tools = tools  # {name: function}
        self.max_steps = 10
    
    def run(self, query):
        """Запускаем ReAct цикл."""
        history = []
        
        for step in range(self.max_steps):
            # Генерируем Thought + возможное Action
            prompt = self._build_prompt(query, history)
            response = self.model.generate(prompt)
            
            # Парсим response
            thought, action, action_input = self._parse_response(response)
            
            if action is None:
                # Это финальный Answer
                return thought
            
            # Выполняем действие
            observation = self._execute_action(action, action_input)
            
            # Добавляем в историю
            history.append({
                "step": step,
                "thought": thought,
                "action": action,
                "observation": observation
            })
        
        return "Превышено максимальное количество шагов"
    
    def _execute_action(self, action, action_input):
        """Выполняем инструмент."""
        if action in self.tools:
            return self.tools[action](action_input)
        return f"Неизвестное действие: {action}"

Prompt Engineering для CoT

Ключевые фразы

# Zero-shot CoT фразы:
"Давайте подумаем шаг за шагом."
"Объясните ваше рассуждение."
"Разбейте задачу на части."
"Подумайте об этом внимательно."
"Покажите ваши вычисления."

# Few-shot CoT паттерны:
"Вопрос: ... Ответ: 1. ... 2. ... 3. ... Ответ: ..."

# Self-Consistency:
"Решите задачу тремя разными способами."

# ReAct:
"Сначала подумайте, затем выполните действие."

Форматирование ответов

# Структурированный CoT:
Шаг 1: [описание]
Шаг 2: [описание]
...
Итоговый ответ: [ответ]

# JSON CoT:
{
  "steps": [
    {"step": 1, "description": "...", "result": "..."},
    {"step": 2, "description": "...", "result": "..."}
  ],
  "answer": "..."
}

Практическое использование

CoT для математических задач

def solve_math_problem(model, problem):
    """Решаем математические задачи с CoT."""
    prompt = f"""
    Решите задачу с подробным объяснением.

    Задача: {problem}

    Решение:
    Шаг 1: Понимаем, что дано
    Шаг 2: Выбираем формулу
    Шаг 3: Подставляем значения
    Шаг 4: Вычисляем
    """
    
    response = model.generate(prompt, max_tokens=1000)
    return response

# Пример:
problem = "Найдите площадь круга радиусом 5 см"
solution = solve_math_problem(model, problem)
# Ответ: 
# Шаг 1: Дано r = 5 см
# Шаг 2: S = π * r²
# Шаг 3: S = π * 25
# Шаг 4: S ≈ 78.54 см²

CoT для программирования

def solve_coding_problem(model, problem):
    """Решаем задачи по программированию с CoT."""
    prompt = f"""
    Решите задачу по программированию.

    Задача: {problem}

    Подход:
    1. Понимаем входные и выходные данные
    2. Выбираем алгоритм
    3. Пишем псевдокод
    4. Реализуем на Python
    5. Тестируем на примерах
    """
    
    response = model.generate(prompt, max_tokens=2000)
    return response

Продвинутые техники

Auto-CoT: автоматический генерация подсказок

def auto_cot_generator(model, tasks):
    """
    Auto-CoT: автоматически генерирует 
    chain-of-thought подсказки для задач.
    """
    # Шаг 1: Кластеризуем задачи по типу
    clusters = cluster_tasks(tasks)
    
    # Шаг 2: Для каждого кластера генерируем пример
    examples = {}
    for cluster in clusters:
        representative = cluster[0]
        cot_example = generate_cot_example(model, representative)
        examples[cluster.name] = cot_example
    
    # Шаг 3: Используем примеры для few-shot CoT
    return examples

# Auto-CoT автоматически находит лучшие 
# chain-of-thought примеры для каждого типа задач.

Least-to-Most Prompting

Метод: разбиваем сложную задачу на подзадачи.

Сложная задача: 
  "Напишите веб-скрапер, который собирает данные 
   с сайта, обрабатывает их и сохраняет в CSV."

Разбиение:
  Подзадача 1: "Напишите код для загрузки страницы"
  Подзадача 2: "Напишите парсер HTML"
  Подзадача 3: "Напишите код для обработки данных"
  Подзадача 4: "Напишите код для сохранения в CSV"

Решаем по порядку, используя предыдущие решения.

Ограничения CoT

Проблемы Chain of Thought:

1. LLM может генерировать ошибочные шаги
   → Правильный формат, неправильный ответ

2. CoT не всегда улучшает результаты
   → Для простых задач может ухудшить

3. Увеличивает длину ответа в 5-10x
   → Больше токенов = дороже

4. Не работает для всех типов задач
   → Хорошо для логики/математики
   → Плохо для креативных задач

5. Модель может "потеряться" в длинных рассуждениях
   → Tree of Thoughts помогает, но дороже

Заключение

Chain of Thought — одна из самых эффективных техник для улучшения рассуждений LLM.

Ключевые выводы:

  • Zero-shot CoT: "давайте подумаем шаг за шагом"
  • Few-shot CoT: примеры с промежуточными шагами
  • Self-Consistency: несколько цепочек + голосование
  • Tree of Thoughts: несколько ветвей рассуждений
  • ReAct: чередование рассуждений и действий

Рекомендуемый стек:

Простые задачи: Zero-shot CoT
Средние задачи: Few-shot CoT
Сложные задачи: Self-Consistency + CoT
Задачи с инструментами: ReAct
Максимальная точность: Tree of Thoughts