Реализация Multi-Query RAG для повышения качества извлечения
Представьте: ваша RAG-система на 20% запросов выдает нерелевантные ответы только из-за неудачной формулировки. Мы, как инженеры с глубоким опытом в AI/ML, сталкивались с этой проблемой десятки раз. Например, запрос «как уволить сотрудника» и «процедура расторжения трудового договора» — одно и то же, но система видит разные векторы и теряет половину релевантных документов. RAG (Retrieval-Augmented Generation) — техника улучшения retrieval, при которой исходный запрос автоматически перефразируется несколькими способами, каждый вариант запускается в поиске, а результаты объединяются. Это снижает зависимость качества ответа от конкретной формулировки запроса и повышает полноту извлечения. В нашей практике это дало прирост recall на 38% при умеренном росте latency.
Как Multi-Query RAG повышает полноту извлечения?
Одна и та же информация может быть описана разными терминами. Например, в корпоративной базе знаний запрос «как оформить отпуск» найдет заявления, «процедура получения ежегодного отпуска» — регламент, а «правила предоставления отпускных дней» — политику HR. Multi-Query объединяет все три и получает более полный контекст, что критически важно для бизнес-процессов. Согласно исследованию RAG на практике, прирост полноты извлечения достигает 38%.
Реализация с LangChain
from langchain.retrievers.multi_query import MultiQueryRetriever from langchain_openai import ChatOpenAI, OpenAIEmbeddings from langchain_community.vectorstores import Qdrant llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.3) embeddings = OpenAIEmbeddings(model="text-embedding-3-small") vectorstore = Qdrant.from_existing_collection( embeddings=embeddings, collection_name="knowledge_base", url="http://localhost:6333", ) retriever = MultiQueryRetriever.from_llm( retriever=vectorstore.as_retriever(search_kwargs={"k": 5}), llm=llm, include_original=True, ) # Использование docs = retriever.invoke("каков порядок согласования крупной сделки") # Внутри LangChain генерирует 3 перефразирования + оригинал, # ищет по каждому и дедуплицирует результаты Это базовый вариант — мы используем его для быстрых прототипов. В продакшене часто требуется кастомный промпт, адаптированный под специфику клиента.
Кастомный Multi-Query с контролем промпта
Стандартный промпт LangChain можно заменить специализированным:
from langchain.prompts import PromptTemplate from langchain_core.output_parsers import BaseOutputParser class LineListOutputParser(BaseOutputParser): """Парсит список вопросов из ответа LLM""" def parse(self, text: str) -> list[str]: lines = text.strip().split("\n") return [line.strip().lstrip("123456789.-) ") for line in lines if line.strip()] MULTI_QUERY_PROMPT = PromptTemplate( input_variables=["question"], template="""Ты — AI-ассистент по поиску документов. Твоя задача — сгенерировать 5 различных вариантов следующего вопроса для улучшения поиска в векторной базе. Правила: - Используй синонимы и альтернативные формулировки - Один вариант — более конкретный, один — более общий - Сохраняй смысл оригинального вопроса - Каждый вопрос с новой строки, без нумерации Оригинальный вопрос: {question} Варианты:""" ) custom_retriever = MultiQueryRetriever( retriever=vectorstore.as_retriever(search_kwargs={"k": 4}), llm_chain=MULTI_QUERY_PROMPT | llm | LineListOutputParser(), include_original=True, ) Наш опыт показывает, что кастомный промпт дает на 5–10% лучший recall, так как адаптирован под предметную область клиента.
Parallel Multi-Query с дедупликацией
Для уменьшения latency запускаем поиск по всем вариантам параллельно:
import asyncio from openai import AsyncOpenAI async def multi_query_search( original_query: str, vectorstore, n_variants: int = 4, top_k_per_query: int = 5, ) -> list[str]: """Параллельный multi-query retrieval""" async_client = AsyncOpenAI() # Генерируем варианты запроса response = await async_client.chat.completions.create( model="gpt-4o-mini", messages=[{ "role": "user", "content": f"Сгенерируй {n_variants} перефразирования вопроса:\n{original_query}\nОдин вопрос на строку." }], temperature=0.5, ) variants = response.choices[0].message.content.strip().split("\n") all_queries = [original_query] + variants[:n_variants] # Параллельный поиск search_tasks = [ asyncio.to_thread(vectorstore.similarity_search, q, k=top_k_per_query) for q in all_queries ] results_per_query = await asyncio.gather(*search_tasks) # Дедупликация по content seen_texts = set() unique_docs = [] for docs in results_per_query: for doc in docs: text_hash = hash(doc.page_content[:100]) if text_hash not in seen_texts: seen_texts.add(text_hash) unique_docs.append(doc) return unique_docs Этот подход позволяет уложиться в 700–800 мс даже при 5 вариантах запроса.
Из нашей практики: кейс юридической компании
Недавно мы внедрили Multi-Query RAG для клиента из юридической сферы. Датасет: корпоративная база знаний (3200 документов). Тестовый набор — 200 запросов с размеченными релевантными документами.
| Конфигурация | Recall@10 | Precision@5 | Latency (avg) |
|---|---|---|---|
| Single query, k=5 | 0.61 | 0.71 | 280мс |
| Single query, k=15 | 0.72 | 0.58 | 310мс |
| Multi-query (4 варианта), k=5 | 0.84 | 0.69 | 680мс |
| Multi-query + Reranker | 0.84 | 0.81 | 920мс |
Multi-query поднимает recall с 0.61 до 0.84 (+38%) при умеренном росте latency (×2.4). После reranker precision также восстанавливается до 0.81.
Сравнение с альтернативами: HyDE (Hypothetical Document Embeddings) в нашем тесте показал recall@10 = 0.71, но требовал дополнительного шага генерации гипотетического документа. Multi-Query оказался проще в реализации и дал на 18% лучший recall.
| Метод | Recall@10 | Latency (avg) | Сложность реализации |
|---|---|---|---|
| Single query | 0.61 | 280 мс | Низкая |
| HyDE | 0.71 | 450 мс | Средняя |
| Multi-Query | 0.84 | 680 мс | Средняя |
Из таблицы видно, что Multi-Query обеспечивает наилучший recall при приемлемом росте latency.
Почему Multi-Query эффективнее HyDE?
HyDE генерирует один гипотетический документ и ищет по нему, что даёт выигрыш, но меньше, чем Multi-Query. Причина: несколько вариантов запроса покрывают больше семантических вариаций, чем один документ. К тому же, Multi-Query проще в реализации — не нужен дополнительный шаг генерации документа.
Как мы внедряем Multi-Query RAG: пошагово
- Аудит текущей RAG-системы и датасета. Анализируем структуру запросов и документов.
- Выбор модели для генерации вариантов (GPT-4o-mini, Claude Haiku, LLaMA 3). Определяем число вариантов (обычно 3-5).
- Кастомизация промпта под предметную область. Тестируем на репрезентативной выборке.
- Интеграция параллельного поиска и дедупликации. Оптимизируем latency.
- A/B-тестирование на ваших запросах. Сравниваем с текущей системой.
- Документация и обучение команды. Передаём код и инструкции.
Весь цикл занимает 1 неделю. Стоимость рассчитывается индивидуально и окупается за 2-3 месяца за счёт экономии времени пользователей. По оценкам клиентов, экономия времени на поиск информации достигает существенных сумм для крупной компании.
Когда стоит внедрять Multi-Query, а когда нет?
Multi-Query оптимален, если:
- запросы пользователей вариативны и содержат синонимы;
- база знаний насчитывает более 5000 документов;
- latency до 1 секунды допустима.
Он не нужен, когда:
- требования по задержке менее 200 мс;
- пользователи жестко следуют единой терминологии;
- датасет мал (single query уже дает высокий recall).
Что входит в нашу работу по внедрению
Мы реализуем Multi-Query RAG под ключ:
- Аудит текущей RAG-системы и датасета;
- Подбор модели для генерации вариантов (GPT-4o-mini, Claude Haiku, LLaMA 3);
- Кастомизация промпта под предметную область;
- Интеграция параллельного поиска и дедупликации;
- A/B-тестирование на ваших запросах;
- Документация и обучение команды.
Пример промпта для генерации вариантов
Ты — AI-ассистент по поиску документов. Сгенерируй 5 различных вариантов следующего вопроса для улучшения поиска в векторной базе. Правила: - Используй синонимы и альтернативные формулировки - Один вариант — более конкретный, один — более общий - Сохраняй смысл оригинального вопроса - Каждый вопрос с новой строки, без нумерации Оригинальный вопрос: {question} Варианты: Сроки и стоимость
Ориентировочные сроки:
- Реализация Multi-Query Retriever: 2–3 дня;
- Подбор промпта и числа вариантов: 2–3 дня;
- Тестирование на датасете: 2–3 дня;
- Итого: 1 неделя.
Стоимость рассчитывается индивидуально, но благодаря ускорению поиска информации система окупается за 2–3 месяца. Для предварительной оценки вашего сценария свяжитесь с нами — мы бесплатно проанализируем ваш датасет и порекомендуем оптимальную конфигурацию. Закажите внедрение Multi-Query RAG и получите прирост recall до 38% уже через неделю. Получите консультацию инженера по внедрению.







