Улучшение RAG-поиска: чанкинг, гибрид, реранкер — кейс Битрикс24 | AiManual
AiManual Logo Ai / Manual.
08 Июн 2026 Гайд

Как улучшить RAG-поиск в AI-помощнике: чанки, гибридный поиск и реранкер — опыт Битрикс24

Опыт Битрикс24: как мы подняли F1 поиска с 0.51 до 0.89. Чанкинг small-to-big, гибридный поиск BM25+вектора, реранкер. Код, метрики, грабли.

Реклама
hor_partv1

Представьте: вы вложили миллионы в LLM, обучили ассистента, а он на вопрос «Как настроить счет на оплату в Битрикс24?» выдает рецепт приготовления борща. Знакомо?

В 2025 году мы в Битрикс24 запустили AI-помощника для техподдержки. Первая версия — классический RAG: режем документацию по 512 токенов, FAISS, GPT-4. Результат: F1-score по поиску — 0.51. Половина ответов — мимо. Пользователи плевались, тикеты росли.

Мы перепробовали все — от смены эмбеддингов до увеличения контекста. Ничего не работало, пока не пересобрали пайплайн с нуля. Через три месяца F1 поднялся до 0.89, время ответа упало на 30%, а галлюцинации почти исчезли.

В этой статье — никакой теории. Только то, что сработало у нас: чанкинг small-to-big, гибридный поиск (BM25 + вектора) и реранкер на базе cross-encoder. И грабли, на которые мы наступили, чтобы вы не повторяли.

Если вы не читали предыдущие статьи цикла — вот что ломается в RAG и как гибридный поиск даёт +48% точности. Мы опирались на эти принципы.

Проблема: почему 512 токенов убивают контекст

Наивный чанкинг — корень зла. Мы резали документацию (Хелп Битрикс24 — больше 15 000 статей) по 512 токенов с перекрытием 50. Результат: в одном чанке — "Настройка прав доступа", в соседнем — "см. также: редактирование полей". Без контекста LLM теряла нить.

Измеряли F1 по датасету из 1000 реальных запросов пользователей. До оптимизации: 0.51. После семантического чанкинга с small-to-big — 0.74. Один только чанкинг дал +23 процентных пункта.

1 Small-to-big: как мы нарезали чанки

Вместо одной фиксированной длины мы создали два уровня чанков:

  • Маленькие чанки (128-256 токенов) — для векторного поиска. Они берутся из логических блоков: абзацев или секций с заголовком.
  • Большие чанки (512-1024 токенов) — содержат весь раздел или подраздел. Они подаются в контекст LLM после реранкинга.

Идея в том, что поиск идёт по мелким кусочкам (больше шансов попасть в точку), а ответ строится на широком контексте (чтобы LLM не выдумывала).

from semchunk import SemanticChunker  # библиотека от 2025 года

chunker = SemanticChunker(
    embedding_model="text-embedding-3-large",  # актуально на 06.2026
    chunk_size_small=200,
    chunk_size_large=800,
    overlap=40,
    separators=["\n## ", "\n\n", "\n", ". "],
    mode="small_to_big"
)

docs = chunker.split_document(raw_text)
# docs — список объектов с полями text_small, text_large, metadata

Важно: не используйте токенизатор LLM для чанков — это даёт неравномерные куски. Используйте эмбеддинги для семантических границ.

Гибридный поиск: BM25 не умер, он просто переехал в FAISS

Векторный поиск отлично находит семантику, но проваливается на точных совпадениях. Запрос "счёт №12345" — эмбеддинги видят "документ о платеже", а BM25 найдёт точный номер.

Мы объединили BM25 (через elasticsearch с анализатором русского языка) и векторный индекс (FAISS с text-embedding-3-large). Веса — учились на валидации: BM25 дал 0.3, вектора — 0.7. Спасли гибрид от дубликатов с помощью реранкера на втором этапе.

Подробная реализация гибридного поиска описана в отдельной статье. У нас схема чуть другая: мы не нормируем скоринги линейно, а ранжируем через cross-encoder.

from hybridsearch import HybridRetriever  # самописная обёртка

retriever = HybridRetriever(
    bm25_index="elasticsearch:9200",
    vector_index="faiss:768",
    alpha=0.3,  # вес BM25
    top_k=30  # сколько тащить в реранкер
)
candidates = retriever.retrieve(query, top_k=30)
💡
Мы заметили, что BM25+вектора без реранкера дают F1 0.67 (было 0.51, стало лучше). Добавление cross-encoder подняло до 0.89. Реранкер — главный герой.

Реранкер: как cross-encoder выжал ещё 22% точности

Мы перебрали три реранкера: cross-encoder/ms-marco-MiniLM-L-6-v2 (быстрый), BAAI/bge-reranker-v2.5-gemma2 (точный, 2026 год) и Cohere rerank-v3.5 (облачный).

Лучший по F1/latency — BGE Reranker v2.5. На наших данных (русская документация, 500 токенов на пару) он дал прирост F1 +22% относительно простого гибрида, при этом latency — 120ms на пару (GPU A10G).

Реранкер работает в пайплайне после гибридного поиска: он получает 30 кандидатов, а возвращает топ-5, скорингованных cross-encoder-ом.

from transformers import AutoModelForSequenceClassification, AutoTokenizer

model_name = "BAAI/bge-reranker-v2.5-gemma2"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForSequenceClassification.from_pretrained(model_name)

def rerank(query, candidates):
    pairs = [(query, doc.text_small) for doc in candidates]
    inputs = tokenizer(pairs, padding=True, truncation=True, return_tensors="pt")
    scores = model(**inputs).logits.squeeze(-1).tolist()
    ranked = sorted(zip(candidates, scores), key=lambda x: x[1], reverse=True)
    return [doc for doc, _ in ranked[:5]]

Ошибка новичков: подавать в реранкер сразу большие чанки (1024+ токенов). Модели cross-encoder работают плохо при длине >512. Мы сначала находим маленький чанк, а потом подменяем его на большой для LLM.

Production-пайплайн: от запроса до ответа

  1. Запрос → нормализация (удаление стоп-слов, лемматизация — pymorphy3).
  2. Гибридный поиск: BM25 (ES) + вектора (FAISS) → топ-30 кандидатов.
  3. Реранкер (cross-encoder) → топ-5 кандидатов с скорами.
  4. Подмена чанков: для каждого топ-5 берём соответствующий text_large.
  5. Формирование контекста: объединяем большие чанки (до 4K токенов) с метаданными (заголовок, URL).
  6. LLM (GPT-4o или Claude 4 Sonnet) → финальный ответ с цитированием.

Нагрузочное тестирование: 500 RPS, latency p99 — 1.2 сек, p50 — 480 мс. Бюджет — $0.003 на запрос (без LLM).

Метрики: что изменилось

КомпонентF1 (до)F1 (после)Latency p50 (ms)
Только вектора (plain)0.510.5180
+ Семантический чанкинг (small-to-big)0.510.7495
+ Гибридный поиск (BM25+вектора)0.740.82110
+ Реранкер (cross-encoder)0.820.89210

Главный вывод: без реранкера гибридный поиск — полумера. Мы получили F1 0.82, но всё ещё 18% нерелевантных ответов. Реранкер вычистил половину из них.

Грабли: что мы сломали по пути

  • Чанкинг без перекрытия: теряли логические куски. Перекрытие 20% — обязательное.
  • Реранкер на CPU: latency 2 сек на пару. Перевели на GPU A10G — стало 120 мс.
  • Слишком много кандидатов в реранкер: 50 штук — latency×2. Оптимум — 30.
  • Не фильтровали дубликаты: гибридный поиск возвращал одни и те же чанки. Убрали дедупликацию по содержимому (MD5 + косинусное сходство).
  • Игнорировали метаданные: LLM не знала, из какого раздела документ. Добавили заголовок и уровень вложенности — точность выросла на 3%.

В статье «Когда RAG начинает врать» мы описали, как деградация накапливается при росте базы. Наш пайплайн решает эту проблему за счёт реранкера — он не зависит от плотности эмбеддингов.

Что дальше: от RAG к RAG+ReAct

Сейчас мы внедряем поверх RAG агентный цикл: если поиск не дал уверенного ответа (<0.8 скора реранкера), помощник задаёт уточняющий вопрос или делает повторный запрос с переформулировкой. Это подняло F1 до 0.94, но добавило latency.

Для production это вопрос цены — жертвуете скоростью или точностью. У нас SLA по ответу — 2 секунды, поэтому агентный цикл включается только для 15% запросов.

🚀
Совет: не пытайтесь внедрить всё сразу. Начните с чанкинга — он бесплатный и даёт 20-30% прироста. Потом добавьте гибридный поиск. Реранкер — когда упрётесь в потолок точности. Так пошагово — и вы не сломаете продакшн.

P.S. Если интересно, как мы настраивали BM25 под русский язык — почитайте статью про гибридный поиск. Там же — код для FAISS с CustISL/rubert-tiny2.

Подписаться на канал