Как проверить, не теряет ли RAG актуальные документы после обновления индекса

Практический тест для RAG-системы: как убедиться, что новые и изменённые документы попали в индекс, а устаревшие версии не влияют на ответы.

Схема проверки актуальности документов в RAG-индексе
Схема проверки актуальности документов в RAG-индексе
File:Envisioning emerging technology for 2012 and beyond.png | by Michell Zappa | openverse | by-sa

RAG-поиск может продолжать отвечать по вчерашней версии документа, даже если источник уже обновлён. Причина не обязательно в модели: запись могла не попасть в индекс, остаться в очереди обработки или сохраниться под старым идентификатором. Проверить актуальность документов в RAG-индексе можно отдельным тестом на уровне поиска — до того, как подключать к нему генерацию ответа.

Ниже — метод для небольшого тестового корпуса. Он проверяет три ситуации: добавление нового документа, изменение существующего и удаление. Результат помогает отделить сбой индексации от ошибки поиска или генерации.

Что именно считать актуальностью RAG-индекса

Индекс актуален, если для заданного состояния источников поиск возвращает нужные версии документов и не подмешивает устаревшие. Это не то же самое, что «векторная база доступна» или «запрос возвращает несколько результатов»: система может успешно находить записи, но не те, которые соответствуют текущим данным.

Для проверки разделите путь документа на этапы:

Источник содержит ожидаемую версию.

Загрузчик обнаруживает добавление, изменение или удаление.
3. Обработчик разбивает содержимое на фрагменты и назначает им идентификаторы и метаданные.
4. Индекс принимает новые записи и удаляет или заменяет старые.
5. Поисковый запрос возвращает актуальные фрагменты с ожидаемыми идентификаторами.

Так проще локализовать проблему. Если записи нет в хранилище, проверяйте загрузку и обработку. Если запись есть, но запрос её не находит, исследуйте извлечение, фильтры и параметры поиска. Если правильный фрагмент найден, но ответ неверен, проверяйте уже сборку контекста и генерацию.

Подготовьте небольшой набор документов с версиями

Для воспроизводимого теста не начинайте с тысяч файлов. Возьмите 10–30 документов, которые можно однозначно распознать по содержимому. Добавьте в каждый тестовый документ уникальную метку версии, например `REVISION=17`. Она нужна не пользователю, а проверке: по ней скрипт сможет отличить новую редакцию от старой.

Пример тестового файла:

text
Документ: Политика возврата
Версия: REVISION=17
Срок возврата: 30 дней с даты покупки.

Подготовьте три группы:

  • Новые: отсутствовали в первоначальном корпусе, затем добавляются.
  • Изменённые: имеют новую метку версии и хотя бы одно изменённое фактическое поле.
  • Удалённые: убираются из исходного корпуса; проверяется, что их содержимое больше не находится.

Для каждого документа заранее запишите ожидаемые фрагменты и ответы поиска. Не используйте только общие запросы вроде «расскажи о возврате»: они могут совпасть с несколькими файлами и затруднят диагностику. Лучше сформулировать запрос, который должен находить конкретное изменённое поле, например: «Какой срок возврата указан в политике версии 17?»

Проверьте, что новые документы доходят до поиска

Сначала выполните базовый запрос до добавления нового файла и сохраните результаты. Затем добавьте документ в источник, запустите обычный процесс индексации и повторите запрос через ту же поисковую функцию, которую использует приложение.

Зафиксируйте как минимум:

  • идентификатор документа и фрагмента;
  • версию или дату обновления в метаданных;
  • текст найденного фрагмента;
  • позицию результата в выдаче;
  • время между изменением источника и появлением результата.

Наличие документа в базе ещё не гарантирует, что он попадает в выдачу. Поэтому проверяйте и запись, и retrieval — непосредственный поиск по индексированному корпусу. Если движок использует асинхронное обновление, учитывайте его механизм видимости: например, Elasticsearch документирует отдельный refresh, после которого изменения становятся видны поисковым запросам. Не делайте вывод о пропуске индексации до завершения штатного цикла обновления.

Установите для теста разумный тайм-аут, соответствующий вашей архитектуре. Универсального значения нет: задержка зависит от очереди, расписания загрузки, размера батча и поискового движка. Главное — измерять её одинаково при каждом прогоне.

Обновите документ и найдите устаревшую версию

Теперь измените один факт — например, срок возврата с 30 на 14 дней — и присвойте документу новую метку `REVISION=18`. После переиндексации выполните два запроса:

Запрос о новом значении.

Запрос, который должен обнаружить старое значение.

Ожидаемый результат: новая версия находится, старая не выдаётся как действующая. Если старый фрагмент всё ещё появляется, проверьте, как система обрабатывает идентификаторы чанков. При повторной загрузке те же исходные абзацы могут получить новые ID, а прежние записи останутся в индексе, если их отдельно не удалить.

В Qdrant, например, точки имеют идентификаторы и могут обновляться или удаляться через операции с коллекцией; конкретная стратегия синхронизации зависит от приложения. Важно проверить не только upsert новой записи, но и судьбу предыдущих фрагментов. Один из надёжных вариантов — хранить стабильный ID документа и версию в метаданных, а после успешной индексации новой версии удалять записи предыдущей. Для атомарной замены может потребоваться временная коллекция или иной механизм переключения, если кратковременное смешение версий недопустимо.

Не сравнивайте только полный текст совпадений: разбиение на чанки могло измениться. Сравнивайте принадлежность фрагмента документу и его версию, а также проверяйте фактическое значение в содержимом.

Проверьте удаление, а не только добавление

Удаление часто остаётся вне тестов: система умеет добавлять и обновлять записи, но не получает сигнал об удалении исходного файла. Удалите тестовый документ из источника, запустите штатную синхронизацию и запросите его уникальную метку.

Ожидаемый результат — удалённый документ не возвращается. Проверьте несколько путей:

  • поиск по уникальной фразе из удалённого документа;
  • выборку по его ID или метаданным;
  • фильтр по версии или состоянию, если приложение использует soft delete.

Отсутствие результата по одному семантическому запросу недостаточно: поиск мог просто поставить запись ниже лимита выдачи. Для надёжной проверки выполните прямой запрос к индексу по ID документа или метке, если это допускает используемый движок. Различайте физическое удаление и логическое исключение из поиска: оба подхода могут быть приемлемы, но тест должен отражать именно ожидаемое поведение системы.

Измерьте качество поиска отдельно от ответа LLM

Чтобы понять, где именно сбой, сначала тестируйте retriever без генеративной модели. Для каждого запроса сохраните список найденных фрагментов и отметьте, присутствует ли среди них нужный актуальный документ.

Полезны две простые метрики:

  • Recall@k: доля запросов, для которых правильный фрагмент попал в первые *k* результатов.
  • Precision@k: доля релевантных фрагментов среди первых *k* результатов.

В Ragas описаны отдельные метрики context recall и context precision для оценки извлечённого контекста. Они помогают анализировать качество набора результатов, но не заменяют проверку версий: для этого в эталонных данных явно укажите, какая редакция считается правильной.

Минимальный набор тестов можно представить так:

python
cases = [
{
«query»: «Какой срок возврата указан в политике версии 18?»,
«expected_doc_id»: «returns-policy»,
«expected_revision»: 18,
«forbidden_revision»: 17,
}
]

Для каждого случая проверьте метаданные и содержимое первых результатов. Затем отдельно отправьте найденный контекст модели и оцените ответ. Если retriever передал версию 18, но модель ответила «30 дней», проблема уже не в актуальности индекса: ищите ошибку в сборке контекста, инструкциях или генерации.

Сделайте тест повторяемым при каждом обновлении

Сохраните корпус, запросы и ожидаемые версии в репозитории рядом с кодом индексации. Запускайте тест после изменений загрузчика, разбиения на фрагменты, схемы метаданных, модели эмбеддингов и конфигурации поиска. Также полезно выполнять его после плановой полной переиндексации: миграция может изменить идентификаторы или фильтры, даже если код приложения не менялся.

Записывайте результаты прогона в машинно-читаемом виде: дата, версия индекса, ID документа, ожидаемая и фактическая редакция, задержка обновления, позиция в выдаче. Тогда регрессия будет видна как конкретное изменение — например, новый документ не появился за установленный интервал или старая редакция снова попала в топ-k.

Не ограничивайтесь единичным успешным запросом. Повторите тест после перезапуска обработчика и после повторной доставки того же задания: повторная обработка должна быть идемпотентной и не создавать дубли. Если используются несколько шардов, реплик или независимых индексов, проверьте каждый путь, по которому реально выполняется поиск.

Что проверить перед выводом о сбое

Если актуальная версия не находится, сначала убедитесь, что тестовый запрос соответствует её содержимому, обработка завершилась, а поиск использует правильную коллекцию и фильтры. Если одновременно находятся старая и новая версии, проверьте очистку предыдущих чанков и стабильность ID. Если верная версия есть в выдаче, но ответ неверен, сохраните переданный модели контекст и разбирайте генерацию отдельно.

Для рабочего контроля достаточно трёх сценариев — добавление, изменение и удаление — плюс проверки метаданных и поиска без LLM. Такой тест не доказывает качество всего RAG-приложения, но быстро показывает, соблюдает ли индекс базовое требование: ответы должны опираться на текущие документы, а не на случайно сохранившуюся редакцию.

Источники