RAG часто показывают очень просто: берём PDF, загружаем его в Open WebUI или другую систему, строим эмбеддинги — и после этого локальная LLM якобы умеет отвечать на вопросы по документам.
На демонстрационных файлах это действительно работает. На реальном архиве — почти никогда.
Мы столкнулись с этим при создании полностью локальной базы знаний на массиве примерно из 4000 страниц русскоязычных документов и 16 часов аудиозаписей. Значительная часть PDF представляла собой сканы, некоторые документы уже содержали старый OCR-слой сомнительного качества, а аудио было записано далеко не в студийных условиях.
Стек при этом был полностью локальным:
- Open WebUI;
- Ollama;
- gpt-oss-120b;
- qwen2.5-vl-32b;
- bge-m3;
- DeepSeek-OCR через vLLM;
- whisper.cpp с CUDA;
- bge-reranker-v2-m3;
- SearXNG.
Железо — система на NVIDIA GB10 с 128 ГБ памяти.
Главный вывод проекта: качество RAG на реальных документах примерно на 90% определяется не самой LLM, а тем, что произошло с документами до векторного поиска.
Если в базу попал мусор, более крупная модель, больший контекст и даже хороший реранкер этот мусор уже не исправят.
Оглавление
- Почему схема «загрузил PDF в RAG» не работает
- Распознавание: VLM против классического OCR
- Текстовый слой есть — но пользоваться им нельзя
- Нарезка документов: почему метаданных недостаточно
- Эмбеддинги, гибридный поиск и реранкер
- Что RAG принципиально не умеет
- Почему нельзя поручать LLM сведение больших списков
- Аудио: VAD, разорванные слова и тайм-коды
- Точный поиск как отдельный инструмент модели
- Рассуждающие модели и скрытый расход токенов
- Как настроить системный промпт для работы с документами
- Инженерия процесса: проблемы, о которых нет в туториалах
- Зачем возвращать OCR-текст обратно в PDF
- Итоговый чек-лист
1. Почему схема «загрузил PDF в RAG» не работает
Большинство демонстраций RAG строится на идеально подготовленных документах.
Это цифровой PDF с нормальным текстовым слоем, правильной кодировкой, аккуратной структурой и предсказуемым содержимым.
Реальный архив выглядит иначе.
В нём встречаются:
- отсканированные бумажные документы;
- фотографии страниц;
- перекошенные сканы;
- плохо пропечатанный текст;
- печати поверх текста;
- рукописные пометки;
- факсы;
- документы с несколькими поколениями копирования;
- PDF со старым OCR-слоем;
- диктофонные записи;
- фоновый шум;
- длинные участки тишины.
Если такой PDF просто загрузить в стандартный RAG-конвейер, система обычно пытается извлечь существующий текст.
И здесь начинается первая серьёзная проблема.
Вместо нормального русского текста можно получить все что угодно: кириллические буквы заменяются визуально похожими латинскими символами, слова распадаются, таблицы превращаются в набор случайных строк.
Для человека очевидно, что это испорченный текст.
Для embedding-модели это уже просто входные данные.
Дальше этот текст индексируется, попадает в векторную базу и используется как источник для ответа.
LLM при этом не обязательно понимает, что источник повреждён. Она пытается восстановить смысл и начинает заполнять пробелы вероятными словами.
То, что снаружи выглядит как «галлюцинация модели», очень часто является следствием гораздо более ранней ошибки: модель получила плохой источник.
Поэтому нормальный RAG начинается не с выбора LLM. Он начинается с контроля качества исходных данных.
2. Распознавание: VLM против классического OCR
Традиционный OCR и современные Vision Language Models решают похожую задачу, но делают это принципиально по-разному.
Классический OCR
Tesseract, EasyOCR и подобные системы в первую очередь пытаются распознать изображения отдельных символов и сопоставить их с буквами.
На хорошем печатном документе это работает быстро и достаточно точно.
Проблемы начинаются, когда появляются:
- печати;
- сложная вёрстка;
- перекос;
- рукописные комментарии;
- фон;
- плохой контраст;
- смешение таблиц и обычного текста.
Vision Language Model
VLM рассматривает страницу как изображение целиком и пытается понять не только отдельные символы, но и её структуру.
Например, qwen2.5-vl-32b значительно лучше справляется со сложными страницами, чем обычный OCR.
Но появляется другая проблема — скорость.
В нашем случае обработка одной страницы через универсальную vision-модель занимала примерно 25 секунд.
Для тома в 300 страниц это уже около 2,5 часа.
Для нескольких тысяч страниц такой подход превращается в серьёзную вычислительную задачу.
Специализированная OCR-модель DeepSeek-OCR размером около 3B, запущенная через vLLM, обрабатывала страницу примерно за 1 секунду.
То есть тот же том на 300 страниц можно было обработать примерно за 5 минут вместо 2,5 часов.
При обычной машинописи разница в качестве при этом часто была небольшой.
Лучший вариант — каскад распознавания
Практическая схема выглядит так:
быстрая OCR-модель
↓
проверка качества результата
↓
тяжёлая VLM только для проблемных страниц
Быстрая модель обрабатывает большинство документов. Если результат выглядит подозрительно, конкретная страница отправляется на повторное распознавание более тяжёлой vision-моделью.
Это намного эффективнее, чем прогонять все 4000 страниц через 32B VLM.
Детектор плохого OCR обязателен
Недостаточно проверять только то, что результат распознавания не пустой.
Плохой OCR очень часто возвращает вполне большой текст. Только этот текст бесполезен.
В нашем массиве 137 из 4042 страниц без дополнительной проверки могли уйти в базу в повреждённом виде.
Детектор качества может учитывать:
- появление китайских и других неожиданных символов;
- слишком низкую долю кириллицы;
- многократное повторение одной строки;
- пустые Markdown-таблицы;
- слишком большое количество одиночных символов;
- аномальное количество знаков пунктуации;
- слишком короткий результат относительно содержимого страницы.
Такой детектор становится одним из важнейших компонентов всего RAG-pipeline.
3. Текстовый слой есть — но пользоваться им нельзя
Отдельная ловушка — старые сканированные PDF.
Некоторые из них уже содержат текстовый слой.
Формально PDF не является просто картинкой: из него можно извлечь текст.
Проблема в том, что OCR мог быть выполнен десять или пятнадцать лет назад, а результат может выглядеть примерно так:
OTPH 1031851041431
npoTOKon Necepa
MocKBa
Стандартный загрузчик видит, что текстовый слой существует, и решает, что новое OCR-распознавание не требуется.
В результате именно этот повреждённый слой индексируется в RAG.
Проверять нужно качество, а не наличие текста
Одна из простых эвристик для преимущественно русскоязычного массива:
Среди буквенных символов не менее 50% должны составлять кириллические символы.
То есть проверять следует не:
есть ли текст?
а:
похож ли этот текст на нормальный русский текст?
Если нет — страницу нужно принудительно отправлять на новое распознавание.
Разумеется, конкретный порог зависит от корпуса. Для документов с большим количеством английских терминов его потребуется скорректировать.
Но даже такая простая эвристика надёжнее стандартной логики «текстовый слой найден — значит всё хорошо».
4. Нарезка документов: почему метаданных недостаточно
После OCR появляется следующая задача — chunking.
Стандартный подход — разбивать текст каждые N символов или токенов.
Для юридических документов и архивов это далеко не всегда правильно.
Естественной единицей документа часто является страница.
Причина проста: пользователь хочет получить не только ответ, но и понять, откуда взялась информация.
В идеальном случае модель должна уметь отвечать:
Согласно документу X, лист 147…
Проблема внешних metadata
Можно попробовать передавать вместе с каждым chunk дополнительные метаданные:
{
"file": "tom_2.pdf",
"page": 147
}
Но конкретный RAG-движок может сохранить их не так, как ожидает разработчик.
Система может:
- переписать metadata;
- объединить фрагменты;
- удалить часть полей;
- создать собственные идентификаторы.
Поэтому критически важные данные нельзя хранить только снаружи текста.
Адрес источника лучше помещать внутрь chunk
Например:
[том_2.pdf · лист 147]
Текст страницы...
Если страница слишком большая и её нужно разделить на несколько частей, каждая часть получает тот же источник:
[том_2.pdf · лист 147]
Первый фрагмент...
[том_2.pdf · лист 147]
Второй фрагмент...
Таким образом каждый найденный фрагмент сам сообщает модели своё происхождение.
Это одновременно решает несколько задач:
- можно указывать источник ответа;
- сохраняется номер страницы;
- не приходится полностью полагаться на внутренние metadata движка;
- можно самостоятельно контролировать размер chunk;
- pipeline меньше зависит от конкретной RAG-системы.
Для архивных и юридических документов такой подход оказался значительно надёжнее.
5. Эмбеддинги, гибридный поиск и реранкер
После подготовки документов наступает этап поиска.
И здесь очень легко оставить настройки по умолчанию.
Например, во многих системах используется:
all-MiniLM-L6-v2
Модель компактная и быстрая, но в первую очередь ориентирована на английский язык.
Для русскоязычного корпуса логичнее использовать многоязычную embedding-модель.
В нашем случае:
bge-m3
Она поддерживает разные языки и создаёт векторы размерностью 1024.
Смена embedding-модели требует переиндексации
Размерность embeddings является частью структуры векторной коллекции.
Если старая модель создавала векторы одной размерности, а новая — другой, существующая коллекция становится несовместимой.
То есть потребуется полная переиндексация базы.
Если документов несколько десятков — ничего страшного.
Если это тысячи страниц после OCR, дополнительной обработки и генерации мета-документов, стоимость ошибки уже становится заметной.
Поэтому embedding-модель лучше выбирать до массовой загрузки данных.
Почему одного векторного поиска недостаточно
Векторный поиск хорошо работает с вопросами по смыслу.
Например:
Где обсуждались условия поставки?
Но намного хуже справляется с запросами, содержащими:
- ИНН;
- номер договора;
- номер банковского счёта;
- конкретную сумму;
- номер телефона;
- точную формулировку.
Для таких данных нужен лексический поиск.
Поэтому рабочая схема:
BM25 + Vector Search
Векторный поиск отвечает за смысл, BM25 — за точное совпадение терминов и последовательностей символов.
Реранкер
После первоначального поиска можно получить, например, 20 потенциально подходящих фрагментов.
Дальше отдельная модель повторно оценивает, какие из них действительно ближе к запросу.
Мы использовали:
bge-reranker-v2-m3
По отношению к стоимости всей инфраструктуры реранкер оказался одним из самых дешёвых способов заметно улучшить качество выдачи.
6. Что RAG принципиально не умеет
Есть категория вопросов, на которых пользователю кажется, что RAG «тупит».
Например:
- Какие документы вообще есть в базе?
- Сколько всего эпизодов описано в материалах?
- Построй полную хронологию событий.
- Перечисли всех людей, которые встречаются в документах.
Проблема здесь не обязательно в модели.
Проблема в самой архитектуре поиска.
RAG ищет фрагменты, похожие на вопрос.
Но в исходной базе может вообще не существовать фрагмента:
Всего в коллекции 143 документа.
или:
Всего обнаружено 87 событий.
Ни один chunk отдельного документа этого не знает.
Решение — мета-документы
После первичной обработки корпуса полезно автоматически создавать дополнительные документы.
Например:
Оглавление коллекции
Том 1
Документ 1...
Документ 2...
Том 2
Документ 1...
Хронология
12.03.2021 — ...
17.03.2021 — ...
02.04.2021 — ...
Реестр лиц
Иванов Иван Иванович
Упоминается в...
Реестр организаций
ООО «Компания»
ИНН...
Упоминания...
Эти материалы индексируются вместе с исходными документами и становятся для RAG обычными searchable documents.
Каждый chunk должен понимать, что он собой представляет
Предположим, мета-документ начинается так:
# Хронология событий
Затем идёт 50 000 символов текста, которые RAG делит на 20 частей.
Название «Хронология событий» останется только в первом chunk.
Остальные части будут выглядеть примерно так:
17 апреля...
19 апреля...
22 апреля...
Когда пользователь спросит «Покажи хронологию», поиск может не связать эти фрагменты с нужным мета-документом.
Поэтому название нужно повторять:
[Хронология событий · часть 7]
17 апреля...
19 апреля...
Каждый chunk должен сам себя идентифицировать.
7. Почему нельзя поручать LLM сведение больших списков
Естественная идея выглядит так:
- извлечь из документов события;
- передать все результаты большой LLM;
- попросить собрать единую хронологию.
Мы попробовали похожий подход.
Из исходных материалов было извлечено порядка 1600 событий.
После передачи их модели с инструкцией составить общую хронологию в результате осталось примерно 102 события.
Модель не сломалась.
Она сделала то, для чего хорошо подходит: обобщила информацию.
Проблема в том, что здесь обобщение было не нужно. Нужно было сохранить практически всё.
Правильное разделение ответственности
LLM должна делать то, в чём она сильна: извлекать структуру из неструктурированного текста.
Например:
{
"date": "2024-05-13",
"person": "Иванов И.И.",
"event": "Получил документ",
"source": "том_3.pdf",
"page": 117
}
Но объединять тысячи таких записей лучше обычным кодом.
Код может:
- распарсить даты;
- нормализовать имена;
- отсортировать записи;
- дедуплицировать их;
- сгруппировать;
- проверить обязательные поля;
- точно посчитать количество элементов.
И сделает это детерминированно.
LLM извлекает факты. Код сводит факты.
8. Аудио: VAD, разорванные слова и тайм-коды
Добавление аудио в RAG создаёт ещё один отдельный pipeline.
В нашем проекте было около 16 часов русскоязычных записей.
Для транскрибации использовался whisper.cpp с CUDA.
Проблема №1. Галлюцинации Whisper на тишине
На длинных участках тишины, гула или фонового шума Whisper иногда начинает повторять одну и ту же фразу.
Например:
Понятно.
Понятно.
Понятно.
Понятно.
...
Повтор может продолжаться сотни строк.
Для последующей RAG-индексации это особенно опасно: бессмысленный текст начинает занимать заметную часть базы и участвовать в поиске.
Решение: VAD
Перед Whisper аудио нужно пропускать через Voice Activity Detection.
Мы использовали Silero VAD.
Он определяет интервалы, где действительно присутствует человеческая речь, и Whisper получает только эти участки.
Результат:
- меньше ложной транскрибации;
- быстрее обработка;
- меньше мусора;
- меньше лишних токенов.
Проблема №2. VAD может разрывать слова
На границе двух фрагментов может получиться:
заключ
и:
ение эксперта
Если просто поставить пробел, получится:
заключ ение
Если всегда склеивать без пробела, появляются другие ошибки:
с нимзнакомы
Используем информацию самого Whisper
В сегментах Whisper начало нового слова обычно обозначается ведущим пробелом.
Поэтому можно использовать простое правило:
- если следующий token начинается с пробела — между частями должен быть пробел;
- если пробела нет — части можно склеить вплотную.
Это оказалось надёжнее эвристик только по длительности паузы.
Проблема №3. Нужны тайм-коды
Допустим, RAG нашёл фразу в двухчасовой записи.
Сам факт нахождения текста мало помогает, если пользователь не знает, где именно это было сказано.
Поэтому транскрипт нужно формировать примерно так:
[00:12:34]
После этого документ был передан...
А дальше:
[00:14:18]
Далее обсуждался...
Теперь найденный chunk позволяет перейти не просто к файлу, а к конкретному времени записи.
Предобработка аудио важнее, чем кажется
Для диктофонных записей хороший эффект дали обычные фильтры ffmpeg:
- удаление низкочастотного гула;
afftdn;- нормализация уровня.
Иногда такая обработка улучшает распознавание больше, чем переход на более тяжёлую speech-to-text модель.
9. Точный поиск как отдельный инструмент модели
Даже после hybrid search остаётся класс запросов, где векторная база — не лучший инструмент.
Например:
- Где упоминается конкретный ИНН?
- В каких документах встречается определённая фамилия?
- Сколько раз упоминается номер договора?
- Найди точную фразу.
Embedding-поиск для этого избыточен.
Гораздо проще дать LLM дополнительный tool.
Например:
search_exact("7707474216")
Инструмент выполняет обычный поиск по содержимому документов и возвращает:
- файл;
- номер страницы;
- совпавшую строку;
- окружающий контекст;
- количество совпадений.
Можно добавить отдельную функцию:
count_mentions("Иванов И.И.")
Тогда модель сама выбирает, какой способ использовать:
- semantic search;
- hybrid search;
- точный полнотекстовый поиск.
Реализация подобного инструмента может быть очень простой, а эффект для реальных документов — больше, чем от многочасового подбора коэффициентов ранжирования.
10. Рассуждающие модели и скрытый расход токенов
Отдельный класс проблем появляется с reasoning-моделями.
Например, с gpt-oss.
Пользователь видит большое окно контекста и предполагает, что почти весь доступный лимит можно использовать для входного текста и результата.
Но внутреннее рассуждение тоже расходует token budget.
Фактически получается:
длинный prompt
+
внутреннее рассуждение
+
финальный ответ
Если reasoning съел доступный output budget, пользователь может получить:
- пустой результат;
- оборванный JSON;
- незакрытую структуру;
- ошибку parser.
Снаружи это выглядит как ошибка приложения, хотя модель просто не успела сформировать финальный ответ в пределах доступного лимита.
Ограничивайте reasoning там, где он не нужен
Для задач массового структурированного извлечения нет смысла заставлять модель долго рассуждать.
Можно уменьшить reasoning effort, например:
think: "low"
и оставить достаточный запас под сам ответ.
Большой context window тоже стоит памяти
Если модель поддерживает 128k контекста, это не означает, что всегда нужно использовать максимальное значение.
Большое окно контекста требует значительного KV-кэша.
В нашем случае уменьшение рабочего контекста примерно до 32k высвободило около 40 ГБ памяти и одновременно убрало заметные подтормаживания.
Поэтому выбирать context window нужно не по принципу «сколько максимум поддерживает модель», а по тому, сколько реально требуется конкретной задаче.
11. Как настроить системный промпт для работы с документами
Когда техническая часть RAG уже работает, остаётся ещё одна проблема — поведение самой LLM.
Слишком мягкая инструкция
Если написать что-нибудь вроде:
Ответь на вопрос пользователя на основе документов.
модель иногда добавляет типичные или вероятные детали.
Для обычного чат-бота это может быть почти незаметно. Для юридических, финансовых и других конфиденциальных материалов такой подход неприемлем.
Слишком жёсткая инструкция
Можно уйти в другую крайность:
Отвечай только тем, что дословно содержится в переданных фрагментах. Если чего-либо нет — откажись отвечать.
В этом случае модель становится чрезмерно осторожной.
Информация фактически присутствует, но требует объединить несколько фрагментов — а модель отвечает, что данных недостаточно.
Более рабочая формула
Хороший системный prompt должен одновременно требовать три вещи.
- Подробно сообщать найденное. Не просто утверждать, что сведения присутствуют, а объяснять, какие именно сведения удалось найти.
- Не дополнять неизвестное. Если в источниках нет даты, суммы или имени — их нельзя придумывать.
- Не выбрасывать найденное из-за отсутствия части ответа. Сначала нужно изложить всё, что удалось установить, а затем явно указать, каких данных не хватает.
Вместо:
Недостаточно информации.
полезнее:
В документах удалось установить A, B и C. Сведения о D в найденных фрагментах отсутствуют.
12. Инженерия процесса: проблемы, о которых нет в туториалах
После нескольких тысяч страниц становится понятно, что production RAG — это уже не только задача машинного обучения.
Это обычная инженерная система.
И многие самые болезненные проблемы вообще не связаны с LLM.
Кэшируйте результат на уровне страницы
Если обработка одного тома занимает несколько часов, любой сбой не должен начинать процесс сначала.
Минимальная единица кэша:
hash файла + номер страницы
Например:
SHA256(document.pdf):147
После распознавания результат сохраняется.
При повторном запуске pipeline сначала проверяет, была ли эта страница уже успешно обработана.
Это позволяет:
- продолжать работу после сбоя;
- менять последующие этапы без повторного OCR;
- повторно обрабатывать только проблемные страницы.
Читайте файл до удаления старой записи
В некоторых системах файл и запись о нём связаны.
Кажется логичным выполнить обновление так:
удалить старый документ
↓
загрузить новый
Но удаление database record может физически удалить и исходный файл.
После этого загружать уже нечего.
Безопасный порядок:
прочитать исходник
↓
сохранить данные
↓
удалить старую запись
↓
создать новую
Такое поведение API лучше заранее проверить на копии тестового файла.
Не перезапускайте сервис во время batch processing
Если по API одновременно обрабатывается несколько страниц, restart Ollama, vLLM или Open WebUI оборвёт запросы, находящиеся в полёте.
Pipeline должен либо самостоятельно делать retry, либо сохранять состояние каждого задания.
Иначе обычное изменение конфигурации способно уничтожить несколько часов прогресса.
Мониторьте вехи, а не поток логов
Поток низкоуровневых сообщений вроде:
request started
token generated
CUDA buffer...
HTTP 200
...
почти бесполезен при обработке тысяч страниц.
Гораздо полезнее события высокого уровня:
Том 3 → готов
Том 4 → 272/300
Том 5 → ошибка на странице 17
Это информация, по которой действительно можно принимать решение.
Тестируйте производительность на худшем документе
Одна из самых показательных проблем проекта вообще не была связана с нейросетями.
При генерации PDF с новым текстовым слоем нужно было подобрать размер шрифта.
Первоначальный алгоритм перебирал варианты.
На обычном документе всё выглядело нормально.
Но на одном большом томе обработка заняла 19 часов.
После замены перебора математическим расчётом та же операция выполнялась примерно за 2 секунды.
Разница составила порядка 30 000 раз.
Проблема скрывалась в совершенно невинном участке кода.
Поэтому производительность pipeline нужно оценивать не на первых десяти красивых страницах. Нужно брать самый тяжёлый документ в архиве.
13. Зачем возвращать OCR-текст обратно в PDF
После качественного OCR у нас уже есть хороший текст каждой страницы.
Обычно его сразу отправляют в RAG.
Но на этом его ценность не заканчивается.
Текст можно встроить обратно в исходный PDF как невидимый слой.
Визуально документ остаётся тем же самым:
- тот же скан;
- те же подписи;
- те же печати;
- та же вёрстка.
Но внутри появляется полноценный текстовый слой.
После этого PDF можно:
- искать через Ctrl+F;
- индексировать;
- копировать цитаты;
- использовать в других программах;
- открывать в обычных PDF-просмотрщиках;
- автоматически анализировать без повторного OCR.
При нормальной реализации рост размера файла может составлять всего около 1%.
Это важный результат проекта.
RAG-интерфейс завтра можно заменить.
Open WebUI можно сменить на другую систему.
Embedding-модель можно переиндексировать.
LLM можно заменить.
А качественно распознанный searchable PDF останется полезным независимо от всего AI-стека.
Поэтому результатом OCR-проекта должна быть не только векторная база.
Результатом должен быть нормальный цифровой архив.
14. Итоговый чек-лист
1. Проверять качество существующего текстового слоя
Не просто:
текст есть?
А:
текст пригоден для использования?
Для русского корпуса можно начать с проверки доли кириллицы среди буквенных символов.
2. Использовать каскад OCR
быстрый OCR
↓
детектор качества
↓
тяжёлая VLM для проблемных страниц
3. Привязывать chunk к странице
Для архивных документов естественная единица — страница.
Адрес источника лучше хранить непосредственно внутри текста:
[файл.pdf · лист 147]
4. Использовать multilingual embeddings
Для русского корпуса можно использовать bge-m3.
При смене размерности embeddings потребуется переиндексация коллекции.
5. Включить hybrid search
Векторный поиск — для смысла.
BM25 — для:
- номеров;
- фамилий;
- сумм;
- ИНН;
- точных формулировок.
6. Добавить reranker
Например:
bge-reranker-v2-m3
7. Создавать мета-документы
Полезно автоматически формировать:
- оглавление;
- хронологию;
- реестр людей;
- реестр организаций;
- другие агрегированные представления корпуса.
При этом каждый chunk мета-документа должен сам себя идентифицировать.
8. Не поручать LLM сведение больших списков
Модель извлекает — код объединяет.
LLM возвращает структурированные данные. Код занимается сортировкой, группировкой, дедупликацией и подсчётом.
9. Обрабатывать аудио отдельным pipeline
ffmpeg preprocessing
↓
VAD
↓
Whisper
↓
корректная склейка сегментов
↓
тайм-коды
↓
chunking
↓
RAG
10. Дать модели exact-search tool
Векторная база не должна решать задачу точного поиска номера договора, ИНН или конкретной формулировки.
Обычный полнотекстовый поиск делает это лучше.
11. Настраивать context window под задачу
Не нужно резервировать 128k только потому, что модель это поддерживает.
Большое контекстное окно расходует память на KV cache.
12. Учитывать reasoning budget
У reasoning-моделей внутреннее рассуждение тоже расходует доступный output budget.
Для массового извлечения структурированных данных часто выгоднее использовать минимально необходимый reasoning effort.
13. Кэшировать каждую страницу
Удобный ключ:
hash документа + номер страницы
Pipeline должен уметь продолжить работу после любого сбоя.
14. Не уничтожать исходники при обновлении
Нужно заранее проверить, что делает API при удалении документа.
Database record и physical file могут быть связаны.
15. Возвращать OCR обратно в PDF
Searchable PDF — отдельный полезный продукт всей обработки.
RAG может измениться. Распознанный цифровой архив останется.
Вместо вывода
После работы с несколькими тысячами реальных страниц отношение к RAG заметно меняется.
Поначалу кажется, что основная задача — подобрать:
- хорошую LLM;
- embedding-модель;
- размер chunk;
- temperature;
- system prompt.
На практике это уже последний участок pipeline.
Перед ним находятся OCR, контроль качества, нормализация, разметка источников, индексация, exact search, мета-документы, аудиообработка, кэширование и контроль выполнения.
Можно запустить модель на 120 миллиардов параметров.
Но если retrieval передал ей OCR-мусор, она просто очень убедительно обработает этот мусор.
RAG не исправляет плохие данные. Он масштабирует качество тех данных, которые вы в него положили.
Если входные документы подготовлены правильно, даже относительно компактная локальная система начинает отвечать очень хорошо.
Если нет — увеличение модели лишь делает ошибки дороже.
Именно поэтому при создании локальной базы знаний стоит начинать не с вопроса:
Какую LLM поставить?
А с другого:
Как гарантировать, что каждый фрагмент, который увидит LLM, действительно содержит корректный текст и понятный источник?
С этого и начинается production-ready RAG.
Использованный стек: Open WebUI, Ollama (gpt-oss-120b, qwen2.5-vl-32b, bge-m3), DeepSeek-OCR на vLLM, whisper.cpp с CUDA, bge-reranker-v2-m3, SearXNG.
Все компоненты работают локально, без передачи документов внешним AI-сервисам. Для персональных, корпоративных, юридических и других конфиденциальных материалов это зачастую не дополнительное преимущество, а базовое требование.




