Расширенные контекстные окна не решают проблему RAG — поэтому я создал систему, которая её решает.
Я увеличивал размер контекстного окна пять раз. Каждый раз происходило что-то неожиданное.
Делиться
Вкратце:
- Я создал систему вопросов и ответов на основе набора данных и доверился ответу RAG, который оказался неполным (более чем наполовину верным).
- Я провел измерения по 7 типам запросов и 5 размерам контекста на 100 000 строках.
- Решение: полностью перенаправить вычислительные запросы из RAG.
Я доверился не тому номеру.
В прошлом месяце я с головой погрузился в разработку новой функции для EmiTechLogic. Теперь учащиеся могут загружать свои собственные неструктурированные CSV-файлы и задавать вопросы на простом английском языке о своих данных. Это показалось идеальным вариантом для RAG, поэтому я вложил в это все силы — встраивание данных, поиск информации, привлекательные ответы.
Первые несколько демонстраций выглядели потрясающе. Аккуратные таблицы, убедительные цифры, профессиональное форматирование. В ходе внутреннего тестирования я даже начал доверять системе.
Затем я выбрал одно число для перепроверки.
Реальные расходы на продукты питания в наборе данных: 1 140 033,24 долл. США .
Модель выдала мне прекрасную разбивку по категориям. Выглядело правдоподобно. Я сложил полученные ею числа.
Это было меньше половины.
Я сидел, уставившись в экран, и думал: «Этого не может быть». Поэтому я сделал то, что сделал бы любой инженер. Я увеличил контекстное окно. 4k… 16k… 32k… 128k токенов. Каждый раз ответ становился длиннее, подробнее и увереннее в своей неверности.
Тогда-то меня наконец осенило. Дело было не в самом поиске. Я просил систему поиска выполнить сложные вычисления над данными, которые она видела лишь частично. И вместо того, чтобы сообщать о неуверенности или отсутствии информации, модель выдавала отточенные, структурированные ответы, которые выглядели правильными.
Почему RAG не может объединять
Конвейер обработки данных RAG на самом деле не понимает структурированные данные. Всё, что он делает, это берёт каждую строку CSV-файла и преобразует её в обычный текст. Вот и всё. Для модели строка выглядит примерно так:
"2019-01-01 grocery_pos 107.23 F NC Jennifer Banks ..."
Для запроса типа «Каков общий объем расходов по категориям?» конвейер RAG выполняет следующее:
1. Tokenise: ["total", "spend", "category"] 2. Score all 100,000 rows by keyword overlap 3. Return the top-N rows as serialised plain text 4. Ask the LLM to sum and group from that text
На 4-м шаге система дает сбой. LLM не выполняет суммирование. Она сопоставляет числа с шаблонами из текстового массива и генерирует ответ, имитирующий агрегирование.
Модели испытывают трудности с точностью численных данных в больших масштабах [1], но настоящая проблема заключается в представлении. Модель предоставляет подробную разбивку по всем категориям. Это классическая ловушка. Результат выглядит профессионально. Он настолько хорошо имитирует структуру реального отчета, что ваш мозг считает содержание достоверным. У вас нет возможности проверить, что 92% ваших данных отсутствуют.
RAG — это инструмент поиска. Это не вычислительный механизм. Поиск позволяет найти релевантные фрагменты. Для вычислений требуется полное сканирование набора данных. При использовании RAG для математических вычислений вы получаете неверный ответ, который выглядит авторитетным. Это различие имеет решающее значение. Частичный ответ указывает на отсутствие данных. Неверный ответ, выглядящий полным, лишь создает ложное ощущение уверенности.
Полный код: https://github.com/Emmimal/context-window-engine/
Эталон: два конвейера обработки, один и тот же запрос.
Для точного измерения этого параметра я создал бенчмарк, который запускает два конвейера обработки запросов параллельно для каждого запроса.
Первый конвейер — это симуляция RAG. Он моделирует то, что наивный векторный конвейер передает в LLM при пяти размерах контекста. Я протестировал пять размеров контекста, от 5 строк до 8000. Это масштабируется от 325 токенов до 500 000. Для каждого размера я отслеживал три метрики: сколько данных видит LLM, какую сумму он вычисляет из этого конкретного фрагмента и может ли читатель действительно обнаружить ошибку.
Второй конвейер обработки данных представляет собой семантический механизм, который выполняет тот же запрос, что и детерминированное полное сканирование всех 100 000 строк, и возвращает точно правильный ответ.

Моделирование не воспроизводит в точности выходные данные LLM. Оно сохраняет ключевое структурное свойство: частичный фрагмент данных, поступающий в систему, которая возвращает полный ответ. Именно это свойство и вызывает проблему, и именно его измеряет эталонный тест.
Я выбрал семь типов запросов, чтобы охватить все шаблоны агрегирования, с которыми может столкнуться система структурированных данных:
| Запрос | Операция | Почему это нарушает RAG |
|---|---|---|
| Общие расходы по категориям | СУММА + ГРУППИРОВКА НА | Требуется суммировать все строки по 14 группам. |
| Наибольший средний объем транзакций по категориям. | СРЕДНЕЕ + ГРУППИРОВКА ПО | Среднее значение меняется с каждой пропущенной строкой. |
| Общая сумма, потраченная на покупки в продуктовом магазине. | Сумма + категориальный фильтр | Для фильтрации необходимо увидеть все совпадающие строки. |
| Сколько покупательниц совершили транзакцию? | COUNT + фильтр | Количество не имеет смысла при частичном сканировании. |
| Общая сумма расходов, если она превышает 500 долларов. | Сумма + числовое сравнение | Для работы пороговой логики требуются полные данные. |
| Штат с наименьшими общими расходами | MIN + GROUP BY по 50 группам | Минимальное значение может быть найдено только при наличии всех групп. |
| Процент мошеннических транзакций | КОЛИЧЕСТВО + соотношение | Отношение не определено в частном знаменателе. |
Эти запросы не уникальны и не сложны. Это стандартные вопросы, которые задает любой аналитик при изучении нового набора данных. Именно поэтому эта ошибка так критична.
Срыв наблюдаемости ошибок
Вот полный результат теста производительности для запроса, с которого все началось. Я показываю его целиком, потому что цифры не оставляют сомнений в проблеме.
GROUND TRUTH (Semantic Engine) SUM(amt) GROUP BY category → 14 groups #1 grocery_pos 1,140,033.24 #2 shopping_net 773,527.93 #3 shopping_pos 725,766.14 #4 gas_transport 648,804.24 #5 home 556,526.53 Latency: 100.47ms | Rows scanned: 100,000 RAG SIMULATION — what the LLM receives at each context size Context Rows Coverage Partial sum Error detectable? tiny (~325 tokens) 5 0.0050% 197.73 EASY small (~3K tokens) 50 0.0500% 2,003.56 MODERATE medium (~32K tokens) 500 0.5000% 31,023.21 HARD large (~130K tokens) 2,000 2.0000% 140,093.16 VERY HARD xlarge (~520K tokens) 8,000 8.0000% 569,368.22 NEAR IMPOSSIBLE
Я некоторое время рассматривал эти результаты. Самым тревожным было не только то, что ответы были неверными, но и то, насколько сложнее становилось обнаруживать ошибки по мере расширения контекстного окна.
При 8000 строках ошибка всё ещё превышала 50%, но результат выглядел как профессиональный отчёт. Чтобы заметить несоответствие, нужно было вручную проверить цифры. Это я и назвал «коллапсом наблюдаемости ошибок». Чем больше контекста я предоставлял модели, тем убедительнее — но не точнее — становился результат.
В столбце «Частичная сумма» отображается итоговая сумма, если бы LLM сложил все значения сумм в фактически полученных строках. В столбце «Обнаруживается ли ошибка?» оценивается вероятность того, что человек-читатель заметит ошибку.
При наличии 5 строк частичная сумма составляет 197,73. Правильная сумма равна 1 140 033,24. Это очевидно. Вывод короткий, числа неверны, и недостающие данные очевидны. Ошибка возникает мгновенно.
При обработке 8000 строк частичная сумма достигает 569 368,22. LLM теперь учёл все 14 категорий. Он генерирует отчёт объёмом 1500 слов с конкретными цифрами и уверенным языком. Ошибка составляет 50%, но она скрыта в авторитетном, хорошо структурированном тексте. Без внешнего источника читатель не сможет её обнаружить.
Эта закономерность сохранялась во всех семи запросах:
| Контекстное окно | Ряды | Покрытие набора данных | Длина ответа | Обнаруживается ошибка? |
|---|---|---|---|---|
| ~325 токенов | 5 | 0,005% | ~50 слов | ДА — очевидно, это предположение. |
| ~3000 токенов | 50 | 0,050% | ~150 слов | МОЖЕТ БЫТЬ |
| ~32 тыс. токенов | 500 | 0,500% | ~400 слов | ЖЕСТКИЙ |
| ~130 тыс. токенов | 2000 | 2.000% | ~800 слов | ОЧЕНЬ СЛОЖНО |
| ~520 тыс. токенов | 8000 | 8.000% | ~1500 слов | ПОЧТИ НЕВОЗМОЖНО |
| Семантический движок | 100,000 | 100% | <200 мс | Н/Д — точно |
Я назвал это «коллапсом наблюдаемости ошибок». По мере расширения контекста, уверенность возрастает вместе с ним. Правильность же — нет.

Режимы отказов асимметричны, что делает их опасными:
Неправильный ответ RAG выглядит правильным. Он отформатирован, конкретен и в нём есть уверенность. Неудачное вычисление приводит к явной ошибке. Она видна.
Одна ошибка незаметна. Другая – громкая. По мере того, как контекстные окна достигают миллионов токенов, незаметную ошибку становится сложнее обнаружить [4]. Система не становится безопаснее по мере масштабирования. Она просто становится более убедительной.
Семантический движок: доказательство того, что правильный ответ получается быстро.
Прежде чем я полностью понял проблему, я уже на скорую руку собрал простой семантический движок. Мне просто хотелось хотя бы раз получить правильный ответ.
Подход оказался простым: разбить запрос на соответствующие операции и выполнить один проход по всему набору данных. Никаких эмбеддингов, никакого поиска, никаких догадок.
Вот как это выглядит на практике:
Логика проста. Возьмем запрос типа «Какова общая сумма расходов по категориям?». Система преобразует его в прямую операцию: SUM(amt) GROUP BY category. Она обрабатывает весь набор из 100 000 строк за один проход. Она суммирует сгруппированные итоги. Нет никакого извлечения данных. Нет никакого вывода. Нет частичного сканирования. Она посещает каждую строку один раз и возвращает точный результат.
Это доказывает, что правильный ответ не является дорогостоящим. Тестовые запросы завершились менее чем за 200 мс. Размер выборки: 100 000 строк. Агрегация тривиальна. Сбой происходит, когда эти запросы направляются в систему, которая изначально неправильно их понимает.
from context_window_engine import compute_ground_truth, load_csv rows = load_csv("data/credit_card_transactions.csv", max_rows=100_000) gt = compute_ground_truth( query_label = "total by category", rows = rows, agg_func = "sum", agg_col = "amt", group_col = "category", ) # gt.answer → [(grocery_pos, 1140033.24), (shopping_net, 773527.93), ...] # gt.latency_ms → 100.47
Движок поддерживает функции SUM, AVG, COUNT, MIN, MAX. Обрабатывает категориальные и числовые фильтры. Включает GROUP BY и соотношения. Отсутствие внешних зависимостей. Каждая операция выполняется как детерминированная функция по всему списку.
Сама по себе система не является продуктом. Продуктом является доказательство: правильный ответ достижим менее чем за секунду. Никаких умозаключений не требуется. Настоящая проблема заключается в надежной маршрутизации запросов к этому ответу.
Решение не является лучшим способом извлечения информации.
Прекратите пытаться улучшить извлечение данных. Если запросу требуется 100% данных, выборка в 8% не сработает. Решение заключается в удалении извлечения данных из цикла.
Нам нужен слой классификации. Он располагается перед конвейером обработки данных и выполняет один бинарный запрос: вычисление или поиск?
Разница очевидна. «Общие расходы по категориям» требуют полного сканирования. «Найти транзакции от Дженнифер Бэнкс» — это простой поиск. Стандартный алгоритм RAG направляет оба запроса по одному и тому же пути. В этом и заключается недостаток проектирования.
QueryRouter решает эту проблему. Он проверяет каждый входящий запрос и направляет его по правильному пути до начала обработки первого запроса.

Классификатор использует три уровня сигналов с приоритетами. Уровень 1: глаголы агрегации — total , how many , average , lowest , percentage . Они требуют обработки всего набора данных. Уровень 2: числовое сравнение — greater than 500 , above $1,000 , at least . Они подразумевают фильтрацию с последующей агрегацией, что невозможно для RAG. Уровень 3: сигналы поиска find , show me , list , fetch . Они указывают на поиск, где работает семантическое сходство.
| Уровень | Сигнал | Примеры | Маршрут |
|---|---|---|---|
| 1 | глагол агрегирования | total , how many , average , lowest , percentage |
ВЫЧИСЛЕНИЕ |
| 2 | Числовое сравнение | greater than 500 , above $1,000 , at least |
ВЫЧИСЛЕНИЕ |
| 3 | Сигнал восстановления | find , show me , list , fetch |
ИЗВЛЕЧЕНИЕ |
| 0 | Нет совпадений | двусмысленный | ВЫЧИСЛЕНИЕ — более безопасный вариант по умолчанию |
Если ни один уровень не совпадает, по умолчанию используется COMPUTATION. Это сделано намеренно. Режимы ошибок асимметричны: неправильный ответ RAG на агрегацию означает молчаливую ошибку. Вычислительный механизм, который не может разобрать запрос, выдает ошибку. В случае сомнений, сообщайте об ошибке громко.
from query_router import QueryRouter router = QueryRouter(rows) result = router.route("What is the total spend by category?") # result.routed_to → "COMPUTATION" # result.answer.answer → [(grocery_pos, 1140033.24), ...] # result.total_latency → ~250ms — classify + execute combined result = router.route("Find transactions from Jennifer Banks") # result.routed_to → "RETRIEVAL" # result.answer.safe → True — RAG is appropriate
Маршрутизация полного бенчмарка
Я выполнил девять запросов через маршрутизатор, чтобы проверить производительность обоих типов запросов: семь агрегационных запросов, предназначенных для семантического механизма, и два запроса на поиск для RAG.
Все маршруты были правильными. Семь запросов агрегации обращались к механизму полного сканирования и возвращали точные результаты. Два запроса поиска корректно запускали путь RAG. Посмотрите на результат: высокие показатели достоверности, правильное сопоставление шаблонов и задержка менее 130 мс — даже при сканировании 100 000 строк.
[1] ✓ COMPUTATION "What is the total spend by category?" Tier 1 | matched='total' | confidence=0.97 #1 grocery_pos 1,140,033.24 (102.57ms | 100,000 rows | exact) [2] ✓ COMPUTATION "Which category has the highest average transaction amount?" Tier 1 | matched='highest' | confidence=0.97 71.91 (119.47ms | 100,000 rows | exact) [3] ✓ COMPUTATION "What is the total amount spent on grocery_pos?" Tier 1 | matched='total' | confidence=0.97 1,140,033.24 (49.96ms | 100,000 rows | exact) [4] ✓ COMPUTATION "How many transactions were made by female customers?" Tier 1 | matched='How many' | confidence=0.97 54,641.00 (90.45ms | 100,000 rows | exact) [5] ✓ COMPUTATION "What is the total spend where amount is greater than 500?" Tier 1 | matched='total' | confidence=0.97 1,274,269.60 (91.65ms | 100,000 rows | exact) [6] ✓ COMPUTATION "Which state has the lowest total spending?" Tier 1 | matched='lowest' | confidence=0.97 lowest RI 2,125.60 (109.05ms | 100,000 rows | exact) [7] ✓ COMPUTATION "What percentage of transactions are fraudulent?" Tier 1 | matched='percentage' | confidence=0.97 0.9900% (87.35ms | 100,000 rows | exact) [8] ✓ RETRIEVAL "Find transactions from Jennifer Banks" Tier 3 | matched='Find' | confidence=0.85 RAG is appropriate — no aggregation required [9] ✓ RETRIEVAL "Show me a sample transaction from Texas" Tier 3 | matched='Show me' | confidence=0.85 RAG is appropriate — no aggregation required Routing accuracy: 9/9
9 из 9 правильных ответов. Свертывание наблюдаемости ошибок невозможно, если запросы агрегации никогда не достигают RAG.
Набор тестов
Тестовый набор проверяет девять конкретных запросов. Он обеспечивает надежность в более широком диапазоне: граничные случаи, некорректные входные данные, пропущенные данные и распространенные точки отказа в производственной среде.
Набор тестов включает 87 заданий по 10 классам. Он охватывает разбор чисел с плавающей запятой с использованием знаков доллара, запятых и научной нотации; все пять функций агрегирования в обычных условиях и с пустыми входными данными; все пять операторов числовой фильтрации; полную агрегацию GROUP BY с комбинированными категориальными и числовыми фильтрами; метрики покрытия моделирования RAG для каждого размера контекста; а также граничные случаи, включая пустые наборы данных, строки с отсутствующими значениями столбцов и входные данные в виде одной строки.
Набор тестов для маршрутизатора включает 72 теста по 5 классам. Он охватывает все три уровня сложности, включая такие крайние случаи, как запросы, написанные заглавными буквами, и очень длинные запросы; преобразование естественного языка в типизированные операции для каждой поддерживаемой формы запроса; корректность маршрутизации и выполнения для всех семи эталонных запросов; а также набор тестов для сравнения, который проверяет соответствие ответов маршрутизатора независимым эталонным вычислениям — гарантируя, что маршрутизатор не вносит никаких отклонений от собственного результата работы движка.
Запустите тесты движка, набрав команду `python space -m space unittest space test_engine space -v`. Это выполнит 87 тестов из набора.
Запустите тесты маршрутизатора, набрав команду `python space -m space unittest space test_router space -v`. Это выполнит 72 теста из набора.
Все 159 тестов успешно пройдены на Python 3.9+ без каких-либо внешних зависимостей.
Честные ограничения
Это решение не идеально. На данный момент оно работает только с отдельными CSV-файлами. Реальные производственные наборы данных обычно представляют собой сложную систему с множеством таблиц, которые необходимо объединять — я намеренно ограничил область применения, потому что сначала хотел получить решение, которое действительно работало бы от начала до конца.
Маршрутизатор по-прежнему довольно простой (основан на регулярных выражениях). На раннем этапе я пробовал небольшой классификатор на основе LLM, но он оказался нестабильным и увеличивал задержку, поэтому я вернулся к простому подходу. Иногда побеждает скучное решение.
Я также имитировал ответы RAG вместо использования реальных API для бенчмарка. Полученные результаты подтверждаются, но ваши результаты с GPT-4o или Claude 3.5 могут немного отличаться.
Требуется формат CSV. Система загружает структурированные данные непосредственно из CSV-файлов. В настоящее время не поддерживаются подключения к базам данных, файлы Parquet и другие табличные форматы.
Что это меняет?
Добавление уровня маршрутизации практически ничего не стоит. Классификация запроса по 65 шаблонам регулярных выражений занимает всего несколько микросекунд. Семантический движок добавляет менее 200 мс к времени сканирования набора данных из 100 000 строк. Общие накладные расходы меньше, чем при одном вызове функции встраивания.
В ответ вы получаете детерминированный ответ на каждый агрегационный запрос. Каждая итоговая сумма, каждое количество и каждый процент теперь получаются в результате полного сканирования, а не на основе достоверного приближения, рассчитанного на 8% данных. RAG продолжает выполнять свои основные задачи: извлечение конкретных записей, поиск релевантных фрагментов и ответы на поисковые запросы, где семантическое сходство является подходящим инструментом.
RAG не сломан. Ему просто нужно выполнить вычисления, а он не может этого сделать.
Опасность заключается не в том, что это терпит неудачу. Опасность в том, что это терпит неудачу убедительно. И никакой контекст этого не изменит.
Вы можете попробовать набрать это так:
Для начала клонируйте репозиторий с помощью команды `git clone`, а затем перейдите по URL-адресу https://github.com/Emmimal/context-window-engine/ . После завершения клонирования перейдите в нужную директорию, набрав `cd context-window-engine`. Наконец, запустите проект, выполнив команду `python demo.py` в терминале.
Ссылки
[1] Леви, М., Джейкоби, А., и Голдберг, Й. (2024). Та же задача, больше токенов: влияние длины входных данных на эффективность рассуждений больших языковых моделей. В сборнике трудов 62-го ежегодного собрания Ассоциации вычислительной лингвистики (том 1: Длинные доклады), страницы 15339–15353, Бангкок, Таиланд. Ассоциация вычислительной лингвистики.
https://doi.org/10.18653/v1/2024.acl-long.818
[2] Льюис, П., Перес, Э., Пиктус, А., Петрони, Ф., Карпухин, В., Гоял, Н., Кюттлер, Х., Льюис, М., Их, В.-т., Рокташель, Т., Ридель, С., и Киела, Д. (2020). Генерация с расширенным поиском для задач обработки естественного языка, требующих больших объемов знаний. Достижения в области нейронных информационных систем, 33, 9459–9474. https://doi.org/10.48550/arXiv.2005.11401
[3] Гао Ю., Сюн Ю., Гао Х., Цзя К., Пан Дж., Би Ю., Дай Ю., Сунь Дж.,
Го, Ц., Ван, М., и Ван, Х. (2023). Генерация с расширенным поиском для больших языковых моделей: обзор. Препринт arXiv:2312.10997.
https://doi.org/10.48550/arXiv.2312.10997
[4] Лю, Н.Ф., Лин, К., Хьюитт, Дж., Паранджапе, А., Бевилаква, М., Петрони, Ф., и Лян, П. (2023). Затерянные посередине: как языковые модели используют длинные контексты. Труды Ассоциации вычислительной лингвистики, 12, 157–173. https://doi.org/10.1162/tacl_a_00638
[5] Кошорек, О., Гранот, Н., Аллони, А., Адмати, С., Хендель, Р., Вайс, И., Арази, А., Коэн, С.-Н., и Белинков, Ю. (2025). Структурированный RAG для ответа на агрегативные вопросы. Препринт arXiv:2511.08505.
https://doi.org/10.48550/arXiv.2511.08505
Раскрытие информации
Все результаты бенчмарка получены в ходе реальных запусков на Python 3.12.6, Windows 11, только на ЦП, без ГП. В бенчмарке используется набор данных для обнаружения мошенничества с транзакциями по кредитным картам (Kartik Gajjar, Kaggle, 2020), синтетический набор данных, сгенерированный с помощью симулятора транзакций Sparkov, созданного Брэндоном Харрисом, по лицензии CC0 (общественное достояние), доступный по адресу kaggle.com/datasets/kartik2112/fraud-detection. Базовый уровень RAG имитирует процесс извлечения данных и моделирует сигналы достоверности — реальные вызовы API LLM не выполняются. Для воспроизведения результатов, описанных в этой статье, не требуются внешние ключи API. Весь описанный здесь код был написан и протестирован мной.
Эммимал П. Александр. Посмотреть все работы Эммимал П. Александра.
Источник: towardsdatascience.com
Похожие записи
Оцените материал:
Похожие записи
Ён Ван превращает информацию в ценные выводы.
18.05.2026
