Почти любая демонстрация «ИИ по вашим документам» показывает один и тот же фокус: загрузили десять PDF, спросили своими словами, получили ответ. Фокус работает, потому что на десяти документах промахнуться сложно. На двадцати тысячах служебных записок, актов и регламентов начинается инженерия, и первым ломается ровно то, что в демо выглядело магией, — векторный поиск.
Что вообще делает вектор
Эмбеддинг — это способ превратить кусок текста в набор чисел так, чтобы близкие по смыслу куски оказались рядом в пространстве. Мы используем BAAI/bge-m3, 1024 измерения, и храним получившиеся векторы в Qdrant. Когда приходит вопрос, он превращается в такой же вектор, и система ищет ближайшие к нему фрагменты документов.
Сильная сторона очевидна: можно спросить «что делать, если поставщик сорвал сроки», и найдётся раздел, где написано «в случае нарушения контрагентом договорных обязательств». Ни одного общего слова, а документ тот самый.
Слабая сторона ровно там же. Вектор работает со смыслом, а не с написанием. Для него «приказ №117-од» и «приказ №171-од» — почти одна и та же точка: смысл идентичен, отличаются две цифры. Артикулы, номера ГОСТов, фамилии, серийники, аббревиатуры — всё это для эмбеддинга шум, который он честно усредняет.
Снабженец спрашивает про конкретный артикул. Система отвечает по соседней позиции того же типа — уверенно, со ссылкой на документ, и почти правильно. «Почти» здесь хуже, чем «не знаю»: неправильный ответ, поданный уверенно, проверяют реже.
Зачем нужен BM25, которому сорок лет
BM25 — классический полнотекстовый поиск: он считает, насколько редкое слово из запроса встретилось в документе и сколько раз. Никакого понимания смысла, зато точное совпадение по строке. Ровно то, чего не умеет вектор.
Поэтому в контуре работают оба механизма сразу. Запрос уходит в два индекса: векторный и лексический. Результаты сводятся в один список с весом — в базовой конфигурации 0.5, то есть вклад обоих способов равный. Вес — не догма, а настройка: на юридической базе имеет смысл поднять лексическую часть, на переписке и заявках — векторную.
Это и называется гибридным поиском. Не потому, что модно, а потому, что каждый из двух методов по отдельности систематически ошибается в предсказуемых местах, и ошибки у них разные.
Переранжирование: второй, дорогой взгляд
После слияния списков у нас есть кандидаты — обычно несколько десятков фрагментов. Все они формально «похожи» на запрос, но релевантность у них разная, а в контекст модели влезет не всё. Тут включается переранжировщик BAAI/bge-reranker-v2-m3.
Разница с эмбеддингом принципиальная. Эмбеддинг кодирует запрос и документ независимо, и потом сравнивает числа — быстро, но грубо. Переранжировщик читает пару «запрос + фрагмент» целиком и выдаёт оценку соответствия. Это в разы дороже по вычислениям, поэтому его нельзя пустить на всю базу — но на сорока кандидатах он отрабатывает за доли секунды и заметно чистит верхушку списка.
В ответ модели уходит небольшая группа лучших фрагментов. Меньше мусора в контексте — меньше поводов у модели что-нибудь придумать.
Чем режем документы
Качество поиска определяется не только моделью, но и тем, на какие куски разрезан документ. Разрежете по 500 символов подряд — получите фрагменты, начинающиеся с середины предложения и заканчивающиеся в середине таблицы. Формально всё работает, практически — ответы будут рваными.
Мы режем структурно: по разделам и подразделам, с сохранением заголовков в теле фрагмента. За это отвечает связка Docling и собственный слой okb docling-shim — отдельный разбор про то, как это устроено, лежит здесь. Побочный, но важный эффект: раз фрагмент знает свой раздел, ответ может сослаться не просто на файл, а на «раздел 4.2 регламента».
Ссылка на источник — не украшение
Каждый ответ приходит со ссылками на файл и раздел, откуда взяты сведения. Это не про красоту интерфейса, а про единственный доступный способ проверки: человек открывает исходник и смотрит сам. Локальная модель ошибается не чаще и не реже облачной, разница в том, что здесь у вас есть быстрый способ поймать ошибку.
Из этого же следует неприятное, но честное: если нужного документа в базе нет, хорошего ответа не будет. Система не «догадается» — она в лучшем случае скажет, что не нашла. Наполнение базы знаний остаётся человеческой работой, и её нельзя купить вместе с сервером.
Где эта схема всё ещё промахивается
- Вопрос без единой зацепки. «Что там по тому проекту, о котором говорили в мае» — не находит ни вектор, ни BM25, потому что искать нечего. Помогает переформулировка, а не архитектура.
- Таблицы, где смысл в структуре. Сложные сводные таблицы разрезаются хуже текста: часть смысла живёт в объединённых ячейках и заголовках столбцов.
- Опечатки в редких словах. BM25 не найдёт «Иванв», вектор пожмёт плечами. Частично спасает нечёткое сопоставление, полностью — нет.
- Противоречащие друг другу документы. Если в базе лежат две редакции регламента, система покажет обе и сошлётся на обе. Решать, какая действует, будет человек.
Веса гибрида, количество кандидатов на переранжирование и размер фрагмента — это параметры, которые доводятся на ваших документах в первые недели работы. Универсальных значений не существует; всякий, кто называет их не глядя на базу, называет их наугад.
Коротко
Векторный поиск — не поиск, а половина поиска. Вторая половина — старый добрый BM25, третья — переранжировщик, который стоит дорого, но применяется точечно. Всё это крутится локально, на одной видеокарте, вместе с языковой моделью и голосовым контуром. Ни один фрагмент ваших документов при этом не покидает периметр — что, собственно, и было целью упражнения.