Chain of Thought: как заставить LLM думать перед ответом
opensourceaillmprompt-engineeringreasoningit
Введение: почему 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