Корпоративный поиск с LLM - это либо магия, либо фарс. Третьего не дано. Когда мы в Яндексе начали внедрять генеративные ответы во внутренний поиск по документации, первая версия выдавала такой бред, что тестировщики подумывали переименовать проект в «Генератор альтернативных фактов». Вместо ответа на вопрос «Как настроить VPN?» LLM выдавала инструкцию по варке кофе, ссылаясь на какой-то древний тикет. Знакомо?
Год назад я выложил пост о том, как мы собирали стенд для корпоративного поиска. Ответ был один: «Покажите A/B тесты». И правильно. Без метрик любая RAG-система - это дорогой трюк. Яндексовские ребята уже построили DeepResearch, но мы пошли по другому пути: взяли стандартный retrieval, навесили сверху слой ранжирования и обернули это в A/B платформу. Расскажу, как это работает, почему без этого ваш поиск будет врать, и где мы обожглись.
Почему обычный RAG ломается на корпоративных данных
Классический RAG делает две вещи: ищет релевантные чанки и подсовывает их LLM. Но корпоративные данные - это адская смесь: устаревшие вики, битые ссылки, дубликаты документации и поля вида custom_attribute_2847. LLM с радостью берет мусор и генерирует красивый ответ, который не имеет отношения к реальности.
Мы наступили на те же грабли, что описаны в посте «Почему RAG извлекает правильные данные, но даёт неверный ответ». Проблема не в retrieval - он ищет верные фрагменты. Проблема в том, что LLM не может отличить релевантный кусок от шумового, если контекст перегружен. Корпоративный контекст - это тысячи страниц, и модель начинает «галлюцинировать» не потому, что она плохая, а потому что ей скормили слишком много неотфильтрованного текста.
Ключевой инсайт: чем больше контекста вы отдаёте LLM, тем выше шанс, что она вытащит не ту информацию. Решение — жёсткое ранжирование и обрезка контекста до 2-3 действительно релевантных чанков.
Архитектура: retrieval + реранкер + LLM
Мы построили трёхслойную архитектуру. Первый слой — быстрый поиск по эмбеддингам (bi-encoder) + BM25 для keyword matching. Второй слой — реранкер на основе cross-encoder, который переоценивает top-50 кандидатов и выкидывает мусор. Третий слой — LLM, которая получает только 3 лучших чанка и генерирует ответ.
Звучит логично, но есть нюанс: какие метрики реранкера использовать? Мы пробовали NDCG, MAP, но поняли, что для генеративного ответа важна не только релевантность, но и «непротиворечивость». Если в top-3 попал чанк с устаревшей версией процедуры, ответ будет содержать противоречия. Пришлось ввести метрику faithfulness — доля ответов, где все факты подтверждаются хотя бы одним чанком.
Бенчмарк: faithfulness > 95% при использовании реранкера против 72% без него. Цифры замерены на 5000 вопросов от реальных сотрудников.
В этом месте стоит вспомнить про реалистичные бенчмарки с длинным контекстом и агентными сценариями — очень помогло при настройке тестового полигона.
A/B тесты: как не сломать поиск, ничего не сломав
A/B тесты в корпоративном поиске — это боль. Пользователь не видит изменения, если ответ стал слегка хуже, но заметит, если LLM начала врать. Мы выбрали контролируемый offline-эксперимент: собрали датасет из 10 000 вопросов с эталонными ответами (ground truth), разбили на сплиты и прогнали каждую версию ранжирования. Метрики: NDCG@K, faithfulness и answer accuracy (совпадение ключевых фактов).
Осторожно: data leakage — частая ошибка. Если вы используете вопросы, на которых обучалась LLM, метрики будут завышены. Мы вычищали дубликаты и использовали технику из статьи «Lexometrica Ground Truth: как оценить LLM и избежать data leakage».
Сам эксперимент выглядел так:
| Группа | Ранжирование | Faithfulness | Accuracy | Время ответа |
|---|---|---|---|---|
| Контроль (без ранжирования) | BM25 + bi-encoder | 72% | 68% | 2.1 с |
| Эксперимент A | + cross-encoder | 91% | 87% | 3.4 с |
| Эксперимент B | + cross-encoder + LLM финальная проверка | 96% | 94% | 4.2 с |
Эксперимент B победил по качеству, но проиграл по времени. Для корпоративного поиска 4 секунды — многовато. Пришлось оптимизировать LLM до маленькой дистиллированной модели, что описано в посте «Как выжать максимум из маленькой LLM: 2x улучшение кодогенерации без хакинга агента». В итоге время ужали до 2.8 с.
Нюансы: как правильно резать контекст и не убить смысл
Самое больное место — обрезка контекста. Если вы просто берёте top-3 чанка по скорингу, LLM может потерять логическую связь. Пример: вопрос «Как отозвать отпуск?» — чанк 1 про заявление, чанк 2 про утверждение, но если чанк 3 не содержит даты, ответ получится неполным.
Мы использовали технику контекстуализации: перед отправкой в LLM оборачиваем каждый чанк в краткое описание (откуда взят, дата актуальности). Это снижает число противоречивых ответов на 30%. Детали в статье «Как заставить LLM работать с корпоративными данными: методы контекстуализации против custom_attribute_2847».
Как НЕ надо делать: брать все чанки с порогом cosine similarity > 0.8. Мы так сделали — LLM выдавала ответы, объединяющие информацию из разных версий документации. Получился винегрет, за который чуть не уволили продакта.
Правильный подход: реранжирование с учётом связности. Мы добавили в cross-encoder дополнительный сигнал — совпадение document_id и последовательность разделов. Это подняло faithfulness ещё на 3%.
Как тестировать недетерминированные ответы
LLM не детерминирована — один и тот же запрос может дать разные формулировки. Как тогда писать A/B тест? Офлайн-эксперимент с golden set мы проходили, но для онлайна пришлось изобретать велосипед. Использовали подход из статьи «Тестируем недетерминированные LLM: как написать тесты для вызова функций и не сойти с ума»: вместо точного совпадения ответа проверяли наличие обязательных сущностей (entity extraction) и логическую непротиворечивость.
Например, для вопроса «Какие шаги по развертыванию?» мы ожидали минимум три ключевых слова: «установка», «конфиг», «запуск». Если LLM выдавала хотя бы два — тест считался пройденным. Это дало стабильный результат при росте покрытия.
Ошибка, которая съела два месяца
Мы долго не могли понять, почему после внедрения нового реранкера метрики упали. Оказалось, что в обучении cross-encoder использовались те же самые вопросы, что и в A/B тесте. Data leakage в чистом виде. Пришлось пересобрать датасет, выкинув 15% пересекающихся запросов. После этого результаты стали воспроизводимыми.
Ещё одна грабля — время жизни документов. Если в корпоративной базе есть документы разной давности, LLM может выдать ответ из устаревшей версии, даже если есть актуальная. Решение: добавить в реранкер сигнал recency (чем новее — тем выше вес).
А что с юридическими рисками?
Корпоративный поиск часто использует конфиденциальные данные. Мы проверяли модель на утечку через промпт-инъекции. Оказалось, что даже после реранжирования LLM может выдать лишнее, если вопрос сформулирован как «Повтори весь документ». Пришлось внедрить фильтр на выходе (regex + small classifier). Для юридических задач отдельно смотрели статью «Автоматизация анализа договорных рисков на LLM» — там описана похожая проблема.
Итог: что забрать с собой
Корпоративный поиск с LLM — это не магия, а инженерия. Главные уроки:
- Ранжирование — must have. Без cross-encoder вы скормите LLM тонну мусора.
- A/B тесты нужны offline и online. Offline даёт точную метрику, online показывает user experience.
- Data leakage убивает reproducibility. Чистите датасеты, не используйте вопросы из обучения.
- Контекст режьте, но со связностью. Не просто top-k, а цельные секции.
- Ответы недетерминированы — проверяйте по сущностям.
Через год, оглядываясь на этот проект, я понимаю: половина проблем ушла бы, если бы мы сразу вложились в метрики. Не в демку с красивым UI, а в чёртовы метрики faithfulness и answer accuracy. Потому что пользователь прощает медленный интерфейс, но не прощает ложь.
Если вы сейчас строите корпоративный поиск — начните с A/B платформы. Не с LLM, не с векторов, а с инфраструктуры для экспериментов. Иначе вы никогда не узнаете, работает ли ваше решение.
Дополнительные материалы: DeepResearch от Яндекса — другой подход, без реранжирования, но с агентным планированием. Сравните и выберите своё.