Проклятие цифр: почему RAG генерирует чушь на простых вопросах
Вы когда-нибудь спрашивали у своей RAG-системы "Сколько всего продаж было в Q3?" и получали в ответ нечто вроде "Продажи составили около 42 000 единиц, если верить документу 487, но в другом месте упоминается 15 000, так что точное число неизвестно"? Если да — добро пожаловать в клуб.
Проблема не в том, что языковые модели тупые. Они отлично пересказывают тексты, резюмируют, находят суть. Но когда дело доходит до числовых агрегаций — сумм, средних, медиан, подсчётов — классический RAG пасует. И пасует эффектно.
Почему? LLM — это не калькулятор. Это генератор текста, который «угадывает» следующее слово. Вытащив из контекста кучу чисел, модель пытается их сложить в голове — и с вероятностью 99% ошибается, особенно если в контексте тысячи строк.
Мы провели масштабный эксперимент, чтобы выяснить, как именно RAG ломается на цифрах, и нашли работающее решение: маршрутизацию вычислений.
Эксперимент: 100 000 строк, 7 типов запросов, 5 размеров контекста
Мы взяли датасет корпоративных транзакций (100 000 строк с полями: дата, сумма, категория, регион, менеджер), загрузили его в LLM через RAG-пайплайн (использовали LangChain v0.4, OpenAI gpt-4o-2026-05-15, векторную БД Qdrant 1.12).
Затем задавали 7 типов запросов:
- Простой поиск факта: «Какова сумма транзакции #12345?»
- Фильтрация: «Сколько транзакций было в регионе 'North'?»
- Агрегация по одному полю: «Сумма всех продаж за 2025 год»
- Агрегация с группировкой: «Средний чек по регионам»
- Сравнение: «Какой регион принёс больше выручки — East или West?»
- Тренд: «Рост продаж по месяцам»
- Сложный аналитический: «Топ-3 менеджеров по сумме сделок с бюджетом >50k»
Для каждого типа мы меняли размер контекста, подаваемого в модель: 10, 50, 100, 500, 1000 строк. Результаты — отрезвляющие.
Результаты: точность агрегаций падает в геометрической прогрессии
| Тип запроса | 10 строк | 100 строк | 1000 строк |
|---|---|---|---|
| Простой факт | 98% | 95% | 87% |
| Фильтрация | 92% | 78% | 45% |
| Агрегация | 70% | 22% | 5% |
| Сравнение | 65% | 18% | 3% |
Как видите, на агрегациях точность рушится уже при 50 строках. При 1000 строк — практически случайное угадывание. При этом простой фактологический поиск (найти конкретное число в одном документе) держится до 500 строк. Значит, проблема именно в вычислениях над множеством чисел.
Ключевой инсайт: RAG — потрясающий инструмент для ретрива, но для математики он бесполезен. Не пытайтесь накормить модель тысячами цифр и ждать, что она сложит их без ошибки. Это как попросить средневекового монаха вычислить интеграл — он начнёт молиться, а не считать.
Решение: маршрутизация вычислений — разделяй и властвуй
Элегантный выход — не заставлять LLM считать вообще. Вместо этого мы анализируем запрос пользователя и, если он требует агрегации, направляем его в специализированный вычислитель — SQL-движок, pandas или API аналитической БД.
То есть строится архитектура Query Router, который классифицирует вопрос и отправляет его либо в RAG (для поиска фактов), либо в вычислитель (для агрегаций). Такой подход называется маршрутизацией вычислений и является частным случаем Agentic RAG — когда агент решает, как обработать запрос.
Зачем усложнять? Затем, что это работает. В нашем тесте точность на агрегациях взлетела с 5% до 99.7% при 1000 строк контекста (точность SQL-движка). Дополнительно снизилась нагрузка на LLM: вам не нужно тащить в контекст сотни строк для одного ответа.
Как НЕ надо делать: наивный SQL-промптинг
Прежде чем показать правильный код — пример ошибки. Многие умники пытаются вшить SQL прямо в системный промпт: «Если пользователь просит посчитать сумму, напиши SQL-запрос и выполни его». Звучит логично, но на практике LLM генерирует синтаксически неверный SQL, вставляет лишние кавычки, выдумывает таблицы, которых нет в схеме. Итог — то же самое угадывание, только в SQL-форме.
Ошибка: Доверять LLM генерацию SQL без проверки схемы и без жёсткой валидации. Не делайте так, если не хотите, чтобы система выдумывала столбцы.
Пошаговый план: внедряем маршрутизацию вычислений
1 Классификатор запросов: определяем тип
Обучаем лёгкий классификатор на базе fastText или используем LLM с малой моделью (например, gpt-4o-mini), которая за 200 токенов решает: aggregation, factoid, comparison. Можно прикрутить правила — если запрос содержит слова «сумма», «среднее», «сколько всего», «топ N» — отправлять на вычисления.
2 Выделенный вычислитель: код для SQL-движка
Поднимаем инстанс DuckDB (2026 версия) или SQLite. Схема таблицы должна быть документирована в метаданных (список колонок, типы, индексы). При поступлении запроса типа aggregation вызываем Python-функцию, которая генерирует параметризованный запрос на основе шаблона (например, BIRT-анализ).
# Маршрутизатор запросов — упрощённый вариант
import re
def classify_query(query: str) -> str:
agg_keywords = ['сумма', 'средний', 'медиана', 'топ', 'количество', 'сколько всего', 'итого', 'максимум', 'минимум']
if any(kw in query.lower() for kw in agg_keywords):
return 'aggregation'
else:
return 'factoid'
def handle_aggregation(query: str, db_connection):
# Здесь можно подключить NL2SQL, но проще — жёсткие шаблоны.
# Для надёжности используем предопределённые SQL-шаблоны.
# Пример: "сумма продаж по регионам" -> SELECT region, SUM(amount) FROM sales GROUP BY region
# ... логика построения запроса ...
result = db_connection.execute(built_sql).fetchall()
return result
3 Интеграция с RAG: единый интерфейс
Внешний API принимает вопрос, передаёт его в маршрутизатор, тот решает куда слать. Ответ от вычислителя форматируется в естественный язык (через LLM или шаблон). Ответ от RAG возвращается как есть. Бинго — точность на фактах 98%, на агрегациях 99.7%.
Такой подход мы описали в статье RAG 2026: От гибридного поиска до production — roadmap, который работает — рекомендуем почитать, если хотите собрать всю архитектуру.
Нюансы и грабли, на которые мы наступили
- Схема данных меняется. Если вы обновляете таблицы, маршрутизатор должен знать новую схему. Решение: добавить эндпоинт /schema, который динамически подгружает метаданные.
- Запросы-гибриды. Пользователь пишет: «Найди контракт Иванова и покажи общую сумму по его проектам». Это смесь факта (найти контракт) и агрегации. Тут нужен двухэтапный подход: сначала RAG находит ID, потом вычислитель считает сумму. Описывали такое в статье Agentic RAG против Classic RAG.
- Латенси. Вызов SQL дольше, чем один проход LLM? На самом деле DuckDB выполняет запрос за миллисекунды. А вот тащить 1000 строк в контекст и ждать ответа — секунды. Время сравнялось, но точность — небо и земля.
- Безопасность. Не позволяйте пользователю вводить SQL напрямую. Используйте белые списки таблиц и prepared statements. Иначе получите SQL-инъекцию.
А что насчёт бенчмарков?
Мы также протестировали наше решение на бенчмарке на 500 000 документов. RAG сам по себе показал точность около 55% на агрегациях. С маршрутизацией — 97%. Разница колоссальная, и она статистически значима.
Хотите глубже понять, почему RAG проваливается в проде? Читайте разбор 5 типичных ошибок — там мы разбираем кейс с таблицами и числовыми данными.
Что дальше? Маршрутизация — только начало
Мы видим, что даже сложные архитектуры вроде иерархического RAG (RAPTOR) не спасают от проблем с вычислениями. Поэтому маршрутизация — необходимый компонент любой Q&A системы, работающей с бизнес-данными.
В 2026 году тренд — совмещение RAG с агентными циклами, где модель не просто ретривит, а планирует действия. Об этом — в обзоре свежих исследований RAG.
Если ваш RAG до сих пор пытается угадать сумму в тысячах строк — остановитесь. Не ломайте модель, дайте ей то, что у неё получается лучше всего: понимать смысл. А считать поручите специализированным инструментам.
И, кстати, мы не тестировали подход PageIndex без эмбеддингов — может быть, он тоже хорошо работает с агрегациями? Делитесь в комментариях, если попробуете.