Цитата RAG указывает не на тот абзац после очистки текста

Помощник отвечает на вопрос о штрафе и даёт ссылку «стр. 7, абзац 3». Читатель открывает документ и видит там условия доставки. Правильный абзац о штрафе находится ниже. Поиск вроде бы нашёл нужные слова, модель процитировала их, но указатель на исходник сдвинулся. Такой сбой часто появляется при подготовке текста: перед индексацией удаляют колонтитулы, схлопывают пробелы и соединяют переносы строк, а сохранённые смещения продолжают относиться к старой строке.

Две разные координатные системы

Пусть парсер PDF вернул текст страницы, где перед нужным пунктом стоит колонтитул длиной 80 символов. После очистки он исчезает, и нужный пункт начинается раньше. Если векторный чанк хранит start=420 в очищенной строке, а интерфейс ищет символ 420 в исходном тексте, он покажет другой участок. Сложнее, если нормализация превращает несколько пробелов в один или удаляет перенос слова: смещение меняется по всей странице, а не только после одной удалённой строки.

Поэтому у чанка должны быть две связанные вещи: текст, который пошёл в эмбеддинг, и источник, к которому можно вернуться без угадывания позиции. Для цифрового PDF это может быть набор исходных блоков со страницей и bounding box. Для HTML — устойчивые ID элементов или диапазон внутри версии исходного документа. Храните source_version: после переиздания PDF та же страница может содержать другой текст. Ссылка на «страницу 7» без версии не защищает от такого изменения.

Как подготовить отображение

Разбивайте текст после извлечения на исходные блоки и присваивайте каждому block_id. Нормализуйте содержимое блока для поиска, но сохраняйте его page_number, координаты и исходный текст. Если один чанк соединяет три блока, запишите массив их ID и диапазоны нормализованного текста, получившиеся из каждого блока. Тогда при ответе можно открыть страницу и подсветить исходные блоки, а не пересчитывать смещение очищенной строки назад. Координаты блоков PDF доступны, например, через page.get_text("blocks") в PyMuPDF.

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

Простой тест обнаруживает эту ошибку до запуска модели. Для каждого чанка выберите короткий фрагмент, восстановите его место в исходном блоке и сравните подсвеченный текст с сохранённым. Если после удаления колонтитула чанк «штраф 1%» показывает «поставка 7 дней», ошибка находится в отображении координат. Если исходный блок содержит обе фразы, но неверно выделена одна из них, уточняйте локальный диапазон внутри блока. Так вы не смешиваете проблему индексации с проблемой UI.

Проверка на одном вопросе

Возьмите ответ с известным пунктом о штрафе. Откройте сохранённые chunk_id, block_id и source_version, затем сравните текст фрагмента из индекса с тем, что подсвечивает интерфейс. Успех означает, что цитата буквально относится к тому же пункту, на который опирается ответ. Отдельно проверьте случаи с удалённым колонтитулом, переносом слова и несколькими одинаковыми фразами. Такая проверка отличается от оценки того, подтверждает ли цитата вывод модели: сначала надо добиться, чтобы ссылка вообще вела к процитированному месту.

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

В платном курсе Vector DB & RAG Developer вы собираете RAG-бота с цитатами, создаёте индекс на своих документах и измеряете качество выдачи. Привязка очищенного текста к исходному блоку делает эти цитаты проверяемыми для читателя и помогает разбирать ошибки после переиндексации. Откройте программу курса, чтобы пройти путь от документов до ответа с источником.

Продолжить на практике

  • Vector DB & RAG Developer ↗

    Практический курс по векторным базам данных и семантическому поиску. Вы развернёте Qdrant, Weaviate и FAISS, научитесь индексировать миллионы эмбеддингов, строить k-NN-поиск с фильтрами, объединять его с BM-25 и создавать RAG-ботов, которые отвечают на вопросы на основе ваших данных.