RAG на Ollama + Mistral 7B + pgvector: подняли поиск по 50 тысячам страниц документации без выхода данных
Запустили RAG-систему для внутренней базы знаний клиента на полностью локальном стеке - Ollama, Mistral 7B, pgvector. Данные остались в контуре, поиск заработал.
RAG с локальными LLM становится рабочим вариантом для корпоративных knowledge base в изолированных контурах - без передачи данных наружу
Месяц назад мы писали про пилоты с GigaChat и YandexGPT на технической документации - и сразу оговаривались, что для данных с ограниченным доступом нужно смотреть в сторону локальных моделей. Вот, смотрим.
Клиент - промышленный сектор, изолированный сегмент сети, юридически оформленные ограничения на вывод данных за пределы контура. Задача: поиск по корпусу технической документации, около 50 тысяч страниц. Документы копились годами, они разнородные - PDF, экспорты из Word, отсканированные регламенты, HTML из внутренней вики. Инженеры тратили реальное время на поиск нужного куска, и это была не абстрактная проблема, а жалоба на каждой ретроспективе.
Стек и почему именно этот
Ограничение «данные не покидают контур» сразу закрыло облачные API. Оставались локальные модели.
Ollama выбрали как среду запуска: она умеет держать модель в памяти, имеет HTTP API, и её можно поднять без особых церемоний на Linux-сервере без GPU класса A100 - у клиента был сервер с потребительской видеокартой с 24 GB VRAM. Альтернативы рассматривали (llama.cpp напрямую, vLLM), но Ollama выиграла по простоте развёртывания и обновления моделей.
Mistral 7B - модель в квантизации Q4. Выбор в марте 2024 года: Llama 2 тоже рассматривали, но на технических русскоязычных текстах Mistral 7B давал более связные ответы при той же скорости. Llama 3 в планах посмотреть после выхода.
pgvector - расширение к PostgreSQL для хранения векторных эмбеддингов и ANN-поиска. PostgreSQL у клиента уже был в инфраструктуре, добавить расширение проще, чем тащить отдельную векторную базу. Рассматривали Qdrant и Milvus, но pgvector закрывал задачу, и операционная команда уже умела администрировать Postgres.
Для эмбеддингов - модель nomic-embed-text через тот же Ollama. Тоже локально, тоже без интернета.
Как устроена пайплайн
Документы -> парсинг -> чанки -> эмбеддинги -> pgvector
Запрос -> эмбеддинг запроса -> ANN-поиск в pgvector -> топ-K чанков -> промпт -> Mistral 7B -> ответ
Разобьём по этапам, где были реальные решения.
Парсинг - больная часть. PDF с таблицами плохо конвертируется в плоский текст. Отсканированные документы вообще требовали OCR. Не стали изобретать - pdfplumber для обычных PDF, pytesseract + easyocr для сканов. Качество OCR на старых документах местами печальное, но это уже не наша зона ответственности - это состояние исходника.
Чанкинг - взяли скользящее окно по 512 токенов с перекрытием 50 токенов. Экспериментировали с разными размерами, 512 на этом корпусе дало лучший баланс: чанк достаточно большой, чтобы содержать законченную мысль, и достаточно маленький, чтобы не разводнять контекст.
Эмбеддинги - генерация заняла несколько дней на имеющемся железе. Это узкое место, но разовая операция; при добавлении новых документов индексируются только они.
Retrieval - берём топ-5 чанков по косинусному расстоянию, передаём в промпт. Пробовали топ-3 и топ-10: при трёх слишком часто не попадали нужный кусок, при десяти контекст раздувался и модель начинала путаться.
Промпт - системная инструкция с явным указанием отвечать только по предоставленным фрагментам и маркировать, если ответа в них нет. Этот момент оказался важным - без него модель охотно додумывала.
Что получилось и где ограничения
На типовых запросах - «как выполняется операция X по регламенту N», «какие допуски на параметр Y» - система работает. Инженеры из пилотной группы оценивают попадание в нужный документ как высокое, хотя точных метрик мы намеренно не публикуем - замеры ещё идут.
Где сложно: запросы, требующие агрегации по нескольким документам («сравни подходы в регламентах A и B»), работают хуже. RAG возвращает фрагменты, но модель с ними в 8B параметрах при большом контексте начинает терять нить. Это не баг реализации, это ограничение модели. Для таких запросов, скорее всего, нужен отдельный подход - либо multi-step retrieval, либо постобработка на стороне приложения.
Ещё момент: скорость. Mistral 7B на GPU с 24 GB VRAM генерирует ответ за 10-30 секунд в зависимости от длины. Для асинхронного сценария - «задал вопрос, пошёл за кофе» - нормально. Для интерактивного чата - на грани. Модель побольше (70B) клиент не тянет без апгрейда железа.
Про инфраструктуру
Важная деталь, которую недооценили на старте: сервис развёртывания потребовал больше инженерного времени, чем сами модели. Ollama в Docker, pgvector через стандартное расширение Postgres, простой Python-сервис на FastAPI как оркестратор - казалось бы, несложно. Но: мониторинг Ollama нетривиален (есть метрики, но их нужно выставлять наружу), логирование запросов для отладки нужно делать самому, перезапуск после OOM-а GPU требует отдельной обработки.
В итоге интеграция локального LLM-стека - это не «поставил Ollama и готово», а полноценный проект с инфраструктурным слоем.
Где мы сейчас
Пилот идёт второй месяц. Клиент расширяет корпус документов и добавляет второй отдел в тест. Вопросы про доработку интерфейса (сейчас это простая веб-форма) - открытые, пока смотрим на реакцию пользователей.
Главное наблюдение: локальный RAG-стек на Ollama + pgvector - это сегодня рабочая история, не лабораторный эксперимент. Но «рабочая» не значит «дешёвая в поддержке». Кто рассматривает - закладывайте операционную нагрузку реалистично.