Skip to the content.

LLM Reasoning Benchmark Research 🧠

GitHub Pages License: MIT Python 3.10+

Comprehensive study of 5 reasoning approaches on SQuAD dataset

Набор тестов для оценки производительности языковых моделей на датасете SQuAD v1.1 с фокусом на влияние reasoning (рассуждений) на точность и скорость.

📖 Read Full Research on GitHub Pages →


🚀 Quick Start

# Clone repository
git clone git@github.com:vakovalskii/reasoning-benchmark-research.git
cd reasoning-benchmark-research

# Install dependencies
pip install -r requirements.txt

# Configure API credentials
cp config.py.example config.py
# Edit config.py with your API key and endpoint

# Download SQuAD dataset
python download_squad.py

# Run benchmarks
cd without_reasoning && python test_squad_without_reasoning.py
cd ../with_structured_output && python test_squad_with_so.py
cd ../with_two_step_so && python test_squad_two_step_so.py
cd ../with_react && python test_squad_with_reasoning.py
cd ../with_react_two_tools && python test_squad_react_two_tools.py

🎯 Цель исследования

Сравнить различные подходы к использованию reasoning (рассуждений) в языковых моделях для задачи извлечения ответов из контекста. Мы тестируем две ключевые гипотезы:

  1. Явное рассуждение перед ответом улучшает точность
  2. Способ получения финального ответа влияет на результат

Тестируемые подходы:

  1. Without Reasoning (Baseline)
    • Модель сразу отвечает без промежуточных рассуждений
    • Самый быстрый подход
    • Базовая линия для сравнения
  2. Single-Step Structured Output (SO)
    • Модель генерирует reasoning и answer одновременно через JSON schema
    • Рассуждения и ответ в одном запросе
    • Проверяем: помогает ли SO структурировать мышление?
    • Проверяем: мешает ли одновременная генерация reasoning + answer?
  3. Two-Step Structured Output
    • Шаг 1: Генерация reasoning через SO
    • Шаг 2: Reasoning вставляется в контекст (assistant role), модель отвечает свободным текстом
    • Проверяем: помогает ли разделение на два шага?
    • Проверяем: лучше ли free-form answer, чем SO answer?
  4. ReAct (Function Calling - Single Tool)
    • Шаг 1: Модель вызывает функцию generate_reasoning
    • Шаг 2: На основе результата функции модель извлекает ответ свободным текстом
    • Проверяем: дает ли tool-based reasoning лучшие результаты?
    • Проверяем: влияет ли агентный подход на качество?
  5. ReAct Two Tools (Function Calling - Two Tools)
    • Шаг 1: Модель вызывает generate_reasoning(reasoning: str)
    • Шаг 2: Модель вызывает submit_answer(answer: str)
    • Проверяем: лучше ли извлекать answer через tool call, чем free-form?
    • Проверяем: влияет ли явная структура на точность?

Ключевые вопросы исследования:

О reasoning:

О способе получения ответа:

О trade-offs:


📋 Описание задачи

SQuAD (Stanford Question Answering Dataset) - это задача извлечения ответа из контекста (Reading Comprehension).

Что должна делать модель:

Дано:

Требуется:

Пример:

Контекст:

The Panthers used the San Jose State practice facility and stayed at the 
San Jose Marriott. The Broncos practiced at Stanford University and stayed 
at the Santa Clara Marriott.

Вопрос:

Which hotel did the Broncos use for Super Bowl 50?

Правильный ответ:

Santa Clara Marriott

Что требуется от модели:

  1. Понять вопрос - что именно спрашивают
  2. Найти релевантную часть контекста - где упоминаются Broncos и отель
  3. Извлечь точный ответ - “Santa Clara Marriott” (дословно из текста)
  4. НЕ генерировать - ответ должен быть точной цитатой

💬 Промпты (намеренно простые)

Важно: Все промпты максимально простые и не содержат специальных инструкций для задачи SQuAD. Мы тестируем способность модели понимать задачу из контекста, а не следовать детальным инструкциям.

1. Without Reasoning

System:

You are a helpful assistant. Answer the question based on the given context.

User:

Context: {context}

Question: {question}

Answer:

2. With Structured Output (Single-Step)

System:

You are a helpful assistant. Answer questions based on the given context.

User:

Context: {context}

Question: {question}

Think step-by-step, then provide the exact answer from the context.

JSON Schema:

{
  "reasoning": "Step-by-step analysis of where the answer is in the context",
  "answer": "The exact answer extracted from the context"
}

3. With Two-Step SO

Step 1 - System:

You are a helpful assistant. Analyze the context to find where the answer is located.

Step 1 - User:

Context: {context}

Question: {question}

Think step-by-step about where the answer is in the context.

Step 2 - Assistant: {reasoning from step 1}

Step 2 - User:

Based on your analysis, extract the exact answer from the context.

4. With ReAct (Function Calling)

System:

You are a helpful assistant. Use the generate_reasoning tool to think step-by-step, 
then provide the final answer.

User:

Context: {context}

Question: {question}

First, use the generate_reasoning tool to analyze where the answer is in the context. 
Then extract the exact answer.

Tool Definition:

{
  "name": "generate_reasoning",
  "description": "Generate step-by-step reasoning to find the answer in the context",
  "parameters": {
    "reasoning": "Step-by-step analysis of where the answer is in the context"
  }
}

Ключевое наблюдение: Промпты не содержат:


Структура проекта

reasoning_benchmark/
├── config.py                          # Единый конфиг для всех тестов
├── squad_data/                        # Данные SQuAD
│   ├── train.json
│   └── validation.json
├── without_reasoning/                 # Тесты без reasoning
│   ├── test_squad_without_reasoning.py
│   └── results/                       # Результаты тестов
├── with_structured_output/            # Тесты с SO (1 шаг: reasoning + answer)
│   ├── test_squad_with_so.py
│   └── results/                       # Результаты тестов
├── with_two_step_so/                  # Тесты с Two-Step SO
│   ├── test_squad_two_step_so.py
│   └── results/                       # Результаты тестов
└── with_react/                        # Тесты с ReAct (Function Calling)
    ├── test_squad_with_reasoning.py
    └── results/                       # Результаты тестов

Конфигурация

Все параметры настраиваются в файле config.py:

Подходы к тестированию

1. Without Reasoning

Папка: without_reasoning/

Простой подход: модель получает контекст и вопрос, сразу возвращает ответ.

cd without_reasoning
python test_squad_without_reasoning.py

2. With Structured Output (Single-Step)

Папка: with_structured_output/

Одношаговый подход с JSON schema:

cd with_structured_output
python test_squad_with_so.py

3. With Two-Step Structured Output

Папка: with_two_step_so/

Двухшаговый подход с JSON schema:

  1. Шаг 1: Модель генерирует reasoning через Structured Output
  2. Шаг 2: reasoning вставляется в assistant role, модель извлекает финальный ответ как free-form content
cd with_two_step_so
python test_squad_two_step_so.py

4. With ReAct (Function Calling)

Папка: with_react/

Агентный подход с использованием Function Calling:

  1. Шаг 1: Модель вызывает tool generate_reasoning для анализа
  2. Шаг 2: После получения результата tool, модель извлекает финальный ответ
cd with_react
python test_squad_with_reasoning.py

Результаты

Каждый тест сохраняет результаты в своей папке results/:

Метрики

📊 Результаты бенчмарков

Датасет: SQuAD v1.1 Validation (10,570 вопросов)
Модель: qwen3-30b-a3b-instruct-2507
Метрика: EMIN (Exact Match In) - проверка содержания правильного ответа в ответе модели
Дата: 28 декабря 2024

Сводная таблица

# Подход Accuracy Correct Incorrect Speed (q/s) Avg Response Total Time Статус
1 Without Reasoning 89.37% 9,446 1,124 15.00 2.00s 11.7 min
2 Single-Step SO 90.98% 9,617 953 4.62 6.42s 38.1 min
3 Two-Step SO 🏆 93.47% 9,880 690 2.64 11.35s 66.7 min
4 ReAct (1 Tool) 91.98% 9,722 848 3.45 8.68s 51.0 min
5 ReAct (2 Tools) 🥈 93.27% 9,859 711 3.31 9.04s 53.2 min

Детальное сравнение завершенных тестов

1. Without Reasoning (Baseline)

2. Single-Step SO

3. Two-Step SO 🏆

4. ReAct (1 Tool - Function Calling)

5. ReAct (2 Tools) 🥈

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

🎯 Главные открытия

1. Разделение reasoning и answer критично!

Топ-2 результата используют разделение на два шага:

Оба обогнали подходы с одновременной генерацией или одним tool call.

2. Способ получения answer имеет значение!

Сравнение при одинаковом reasoning подходе:

Структурированный answer через tool дает прирост!

Точность vs Скорость

Прирост точности от reasoning:

Цена за точность:

Латентность:

💡 Ключевые инсайты

1. Reasoning работает!

2. Способ интеграции reasoning имеет значение

Single-Step SO (90.98%) < ReAct (91.98%) < Two-Step SO (93.47%)

Почему Two-Step SO лучше:

Почему Single-Step SO хуже:

Почему ReAct (1 Tool) на 4-м месте:

Почему ReAct (2 Tools) на 2-м месте:

3. Trade-off: точность vs скорость

4. Архитектурные выводы

5. Неожиданные результаты


📊 Ответы на исследовательские вопросы

О reasoning:

❓ Улучшает ли reasoning точность? На сколько процентов?ДА! От +1.61% до +4.10%

❓ Какой способ reasoning лучше? SO vs Function Calling?Оба работают хорошо, но по-разному:

Вывод: Способ получения reasoning не критичен. Важно разделение на два шага.

О способе получения ответа:

❓ Free-form vs Structured Output для answer? Что точнее?Free-form НЕМНОГО лучше:

Почему free-form лучше?

❓ Одновременная генерация vs разделение? Мешает ли SO при генерации answer?ДА, мешает!

Почему одновременная генерация хуже?

❓ Tool call для answer vs free-form? Дает ли явная структура преимущество?Да, но НЕБОЛЬШОЕ:

НО: Free-form после SO reasoning еще лучше (93.47%)

О trade-offs:

❓ Какой ценой? Как меняется скорость и латентность?Чем выше точность, тем медленнее:

Подход Точность Замедление Латентность
Without Reasoning 89.37% 1x 2.00s
Single-Step SO 90.98% 3.2x 6.42s
ReAct 1 Tool 91.98% 4.3x 8.68s
ReAct 2 Tools 93.27% 4.5x 9.04s
Two-Step SO 93.47% 5.7x 11.35s

Формула: За каждый +1% точности платим ~1.4x замедлением

❓ Стоит ли оно того? Оправдывает ли прирост точности потерю скорости?ЗАВИСИТ ОТ ЗАДАЧИ:

Стоит использовать reasoning когда:

НЕ стоит использовать reasoning когда:


⚠️ Критичный инсайт для агентных фреймворков

Проблема: Tool call для финального ответа ограничивает гибкость

Наблюдение из эксперимента:

ReAct 2 Tools (answer через tool): 93.27%
Two-Step SO (answer free-form):    93.47%
Разница: -0.20% при использовании tool

Почему это важно для агентов?

1. Агентные фреймворки часто требуют структурированный output

# Типичный паттерн в LangChain, AutoGPT, etc.
class Agent:
    tools = [
        {"name": "search", "output": SearchResult},
        {"name": "calculate", "output": CalculationResult},
        {"name": "final_answer", "output": FinalAnswer}  # ← Структурированный!
    ]

Проблема: Модель должна “впихнуть” ответ в структуру, даже если это неестественно.

2. Примеры из нашего эксперимента:

Вопрос: “What is the position Derek Wolfe plays currently?”

Вопрос: “Which hotel did the Broncos use?”

Рекомендации для агентных систем:

❌ НЕ ДЕЛАЙТЕ ТАК:

# Плохо: финальный ответ через tool с жесткой структурой
tools = [
    {"name": "think", "parameters": {"reasoning": "string"}},
    {"name": "answer", "parameters": {"answer": "string"}}  # ← Ограничивает!
]

✅ ДЕЛАЙТЕ ТАК:

# Хорошо: reasoning через tool, answer free-form
# Шаг 1: Структурированное reasoning
reasoning = agent.call_tool("analyze_context", {"reasoning": "..."})

# Шаг 2: Свободный ответ с контекстом
messages.append({"role": "assistant", "content": reasoning})
messages.append({"role": "user", "content": "Now extract the exact answer."})
final_answer = agent.generate(messages)  # ← Free-form!

🎯 ИЛИ ГИБРИДНЫЙ ПОДХОД:

# Используйте tool для reasoning, но не для финального ответа
class SmartAgent:
    def process(self, task):
        # 1. Tool для анализа (структурировано)
        analysis = self.call_tool("analyze", task)
        
        # 2. Tool для промежуточных действий (структурировано)
        actions = self.call_tool("plan_actions", analysis)
        
        # 3. Финальный ответ БЕЗ tool (свободно)
        return self.generate_final_answer(task, analysis, actions)

Выводы для разработчиков агентов:

  1. Не заставляйте модель структурировать финальный output
    • Reasoning → структурируйте (SO или tool)
    • Final answer → оставьте свободным
  2. Tool calls хороши для промежуточных шагов
    • Поиск информации
    • Вычисления
    • Анализ
    • НО НЕ для финального ответа пользователю
  3. Разница 0.20% кажется малой, но:
    • На 10,000 запросов = 20 дополнительных ошибок
    • В критичных системах это важно
    • Пользовательский опыт страдает
  4. Баланс структуры и гибкости:
    Высокая структура (tool) → Хорошо для pipeline
    Низкая структура (free-form) → Хорошо для качества ответа
    

Практический пример:

# ❌ Плохая архитектура агента
class RigidAgent:
    def answer_question(self, question):
        # Все через tools - слишком жестко
        reasoning = self.call_tool("think", {"question": question})
        answer = self.call_tool("answer", {"answer": "..."})  # ← Ограничено!
        return answer

# ✅ Хорошая архитектура агента  
class FlexibleAgent:
    def answer_question(self, question):
        # Reasoning через tool (структурировано)
        reasoning = self.call_tool("think", {"question": question})
        
        # Answer свободно (гибко)
        context = self.build_context(question, reasoning)
        answer = self.llm.generate(context)  # ← Свободно!
        
        return {
            "answer": answer,
            "reasoning": reasoning,  # Для прослеживаемости
            "confidence": self.estimate_confidence(answer)
        }

Рекомендации по выбору подхода

Используйте Without Reasoning когда:

Используйте Single-Step SO когда:

Используйте ReAct когда:

Используйте Two-Step SO когда:

Файлы с результатами

Without Reasoning:

Single-Step SO:

Two-Step SO:

ReAct (Function Calling):


🚀 Применение результатов в бизнес-кейсах

1. Когда использовать каждый подход

💼 Customer Support / FAQ Bot

Рекомендация: Without Reasoning или Single-Step SO

Почему:

Пример:

# Простой подход для FAQ
response = client.chat.completions.create(
    model="qwen3-30b",
    messages=[
        {"role": "system", "content": "Answer based on FAQ context."},
        {"role": "user", "content": f"Context: {faq}\n\nQuestion: {question}"}
    ]
)

🏥 Medical/Legal AI Assistant

Рекомендация: Two-Step SO или ReAct 2 Tools

Почему:

Пример:

# Шаг 1: Генерация reasoning
reasoning_response = client.chat.completions.create(
    model="qwen3-30b",
    messages=[...],
    response_format={"type": "json_schema", "schema": reasoning_schema}
)

# Шаг 2: Извлечение ответа с контекстом reasoning
messages.append({"role": "assistant", "content": reasoning})
final_response = client.chat.completions.create(
    model="qwen3-30b",
    messages=messages
)

🤖 Autonomous Agents / Complex Workflows

Рекомендация: ReAct 2 Tools

Почему:

Пример:

tools = [
    {"name": "analyze_context", "parameters": {"reasoning": "..."}},
    {"name": "extract_answer", "parameters": {"answer": "..."}},
    {"name": "search_database", "parameters": {"query": "..."}},
]

# Агент сам решает последовательность
response = client.chat.completions.create(
    model="qwen3-30b",
    messages=messages,
    tools=tools
)

📊 Data Extraction / Document Processing

Рекомендация: ReAct 2 Tools или Two-Step SO

Почему:

2. Паттерны проектирования агентов

Паттерн 1: “Think Then Act”

# Основан на Two-Step SO (93.47% accuracy)
class ThinkThenActAgent:
    async def process(self, task):
        # 1. Генерируем reasoning через SO
        thinking = await self.generate_reasoning(task)
        
        # 2. Действуем на основе reasoning (free-form)
        action = await self.decide_action(task, thinking)
        
        return action, thinking  # Возвращаем оба для прослеживаемости

Применение:

Паттерн 2: “Tool-Based Reasoning”

# Основан на ReAct 2 Tools (93.27% accuracy)
class ToolBasedAgent:
    tools = [
        {"name": "analyze", "description": "Analyze the situation"},
        {"name": "decide", "description": "Make a decision"},
        {"name": "execute", "description": "Execute action"}
    ]
    
    async def process(self, task):
        # Агент сам выбирает последовательность tools
        while not done:
            tool_call = await self.llm.call_tool(task, self.tools)
            result = await self.execute_tool(tool_call)
            task.add_context(result)
        
        return task.final_result

Применение:

Паттерн 3: “Fast Path with Reasoning Fallback”

# Комбинация подходов для оптимизации
class HybridAgent:
    async def process(self, task):
        # Пробуем быстрый путь (Without Reasoning)
        quick_answer = await self.quick_answer(task)
        confidence = self.estimate_confidence(quick_answer)
        
        if confidence > 0.95:
            return quick_answer  # 15 q/s
        
        # Fallback на reasoning для сложных случаев
        return await self.reasoning_answer(task)  # 3-4 q/s

Применение:

3. Борьба с типичными ошибками

Проблема: “Too Long (Explanation)” - 44.8% ошибок без reasoning

Решение 1: Используйте reasoning подходы

# ❌ Плохо: модель дает объяснения
messages = [{"role": "user", "content": "Question: ..."}]

# ✅ Хорошо: разделяем thinking и answer
# Шаг 1: думаем
# Шаг 2: отвечаем кратко

Решение 2: Добавьте constraint в prompt

system_prompt = """Extract ONLY the exact answer from context. 
No explanations. Maximum 5 words."""

Проблема: “Partial Match” - 45-51% ошибок с reasoning

Решение: Используйте post-processing

def normalize_answer(answer: str, gold_answers: List[str]) -> str:
    """Normalize format to match expected answer."""
    # Убираем артикли
    answer = re.sub(r'\b(the|a|an)\b', '', answer, flags=re.IGNORECASE)
    
    # Нормализуем числа: "5" → "five" если ожидается слово
    if any(word.isalpha() for word in gold_answers[0].split()):
        answer = number_to_word(answer)
    
    # Убираем лишнюю пунктуацию
    answer = answer.strip('.,!?')
    
    return answer

Проблема: JSON артефакты в Single-Step SO (“} 4”, “} 7 {“)

Решение: Используйте Two-Step или ReAct 2 Tools

# ❌ Плохо: reasoning + answer в одном JSON
response_format = {
    "reasoning": "...",
    "answer": "..."  # ← может содержать артефакты
}

# ✅ Хорошо: разделяем на два шага
# Шаг 1: JSON для reasoning
# Шаг 2: Free-form или отдельный tool для answer

4. Метрики для мониторинга

class AgentMetrics:
    """Метрики для production мониторинга."""
    
    def track_performance(self, result):
        # 1. Accuracy метрики
        self.accuracy = self.calculate_emin(result)
        
        # 2. Latency метрики
        self.p50_latency = percentile(latencies, 50)
        self.p95_latency = percentile(latencies, 95)
        self.p99_latency = percentile(latencies, 99)
        
        # 3. Error категории (из нашего анализа)
        self.error_categories = {
            "partial_match": count,
            "wrong_answer": count,
            "too_long": count,
            "format_error": count
        }
        
        # 4. Cost метрики
        self.cost_per_request = (
            prompt_tokens * INPUT_PRICE + 
            completion_tokens * OUTPUT_PRICE
        )
        
        # 5. Reasoning quality (если используется)
        if result.reasoning:
            self.reasoning_length = len(result.reasoning)
            self.reasoning_relevance = self.score_relevance(result.reasoning)

5. A/B тестирование подходов

class ABTestFramework:
    """Framework для A/B тестирования разных подходов."""
    
    approaches = {
        "baseline": WithoutReasoningAgent(),
        "single_step": SingleStepSOAgent(),
        "two_step": TwoStepSOAgent(),
        "react_1": ReActOneToolAgent(),
        "react_2": ReActTwoToolsAgent(),
    }
    
    async def route_request(self, user_id: str, task):
        # Распределяем пользователей по группам
        approach = self.get_approach_for_user(user_id)
        
        # Логируем для анализа
        start = time.time()
        result = await approach.process(task)
        latency = time.time() - start
        
        self.log_metrics(
            approach=approach.name,
            user_id=user_id,
            accuracy=self.check_accuracy(result),
            latency=latency,
            cost=self.calculate_cost(result)
        )
        
        return result

6. Рекомендации по стоимости

Подход Requests/min Cost/1K req Use Case
Without Reasoning 900 (15 q/s) $X High-volume FAQ
Single-Step SO 277 (4.6 q/s) $1.5X Moderate accuracy
Two-Step SO 158 (2.6 q/s) $2.5X High accuracy critical
ReAct 1 Tool 207 (3.4 q/s) $2X Balanced approach
ReAct 2 Tools 199 (3.3 q/s) $2.2X Agent workflows

Оптимизация costs:

# Используйте кэширование для reasoning
@cache(ttl=3600)
def get_reasoning(context: str, question: str):
    return generate_reasoning(context, question)

# Batch processing для снижения overhead
async def process_batch(questions: List[str]):
    return await asyncio.gather(*[
        process_question(q) for q in questions
    ])

Установка зависимостей

pip install aiohttp rich datasets

Скачивание данных

python download_squad.py