Что такое агентный RAG и зачем нужен гибридный поиск
Retrieval‑Augmented Generation (RAG) — комбинация поиска релевантного контента и генеративных языковых моделей (LLM). Классический RAG просто подбирает документы, а затем передаёт их в модель для формирования ответа. Агентный RAG добавляет слой «агента»: небольшую управляющую логику, которая принимает решения о том, какой тип поиска использовать, какие запросы формировать и как обрабатывать полученные результаты. Это позволяет системе адаптироваться к разным запросам, динамически менять стратегии и повышать точность.
Гибридный поиск объединяет два подхода:
- Векторный поиск – основан на эмбеддингах, хорошо справляется с семантическим сходством, но может упустить точные совпадения.
- Традиционный (символьный) поиск – использует обратные индексы, BM25 и другие алгоритмы, обеспечивает точные совпадения по ключевым словам, но не улавливает смысловые нюансы.
Сочетание этих методов в рамках агентного RAG позволяет покрыть как «что‑то похожее», так и «что‑то точное», а агент управляет их соотношением в зависимости от контекста запроса.
Архитектура системы
┌─────────────────────┐
│ Пользовательский │
│ запрос │
└─────────┬───────────┘
│
▼
┌─────────────────────┐
│ Агент‑контроллер │ ← решает, какой тип поиска и какие параметры использовать
└───────┬─────┬───────┘
│ │
┌────▼─┐ ┌─▼─────┐
│Вектор│ │Текстовый│
│поиск │ │поиск │
└──────┘ └───────┘
│ │
└───►└───────►
│
▼
┌─────────────────┐
│ Объединитель │ (ранжирование, дедупликация, пороговые фильтры)
└───────┬─────────┘
│
▼
┌─────────────────┐
│ LLM‑генератор │ (prompt‑инжиниринг, цепочки мыслей)
└───────┬─────────┘
│
▼
┌─────────────────┐
│ Ответ клиенту │
└─────────────────┘
Компоненты
| Компонент | Функция | Технологии |
|---|---|---|
| Векторный индекс | Хранит эмбеддинги документов, быстрый семантический поиск | FAISS, Milvus, Elastic Vector, Pinecone |
| Текстовый (символьный) индекс | Поиск по ключевым словам, фильтрация | Elasticsearch, Apache Solr, OpenSearch |
| Агент‑контроллер | Логика выбора стратегии, динамическое построение запросов | Python + LangChain, LLM‑prompt, правила‑based system |
| Объединитель | Слияние результатов, вычисление окончательного ранжирования | RRF (Reciprocal Rank Fusion), BM25‑вес, кастомные метрики |
| LLM‑генератор | Формирует ответ, учитывая найденные фрагменты | GPT‑4, Claude, LLaMA, Mistral (через OpenAI API или локальный inference) |
Процесс построения
1. Подготовка корпуса и индексация
- Сбор данных – документы могут быть в виде PDF, HTML, markdown, базы знаний и т.п.
- Токенизация и очистка – удаление шума, нормализация Unicode, разбор структуры (заголовки, списки).
- Создание эмбеддингов – используем предобученную модель (например,
sentence‑transformers/all‑mpnet-base-v2), получаем 768‑мерные вектора. - Индексация – векторные эмбеддинги помещаем в FAISS (IVF‑PQ) для масштабируемости; параллельно создаём BM25‑индекс в Elasticsearch.
- Метаданные – сохраняем ID, путь к оригиналу, тип документа, дату обновления – они понадобятся при пост‑обработке.
2. Реализация агентного контроллера
Контроллер может работать по принципу if‑else или использовать LLM для принятия решения. Пример простого правила:
def choose_strategy(query: str) -> str:
if len(query.split()) <= 3:
return "keyword" # короткие запросы чаще требуют точного совпадения
if any(word in query.lower() for word in ["как", "что", "почему"]):
return "hybrid" # вопросы, требующие объяснения → комбинировать
return "semantic"
Более продвинутая версия использует LLM‑prompt:
You are an optimizer for a retrieval system. Given the user query, decide which search mode to use:
- "semantic" – only vector search,
- "keyword" – only BM25,
- "hybrid" – both combined.
Return only the chosen mode.
Ответ LLM парсим и передаём в соответствующий модуль.
3. Выполнение поиска
- Семантический поиск – запрос преобразуется в эмбеддинг тем же encoder, затем
faiss_index.search(embedding, k). - Ключевой поиск – формируем BM25‑запрос, отправляем в Elasticsearch (
search(query, size=k)). - Гибридный – получаем два списка, объединяем с помощью RRF:
def rrf(ranked_lists, k=20, weight=60):
scores = defaultdict(float)
for lst in ranked_lists:
for rank, doc_id in enumerate(lst, start=1):
scores[doc_id] += 1.0 / (weight + rank)
return sorted(scores, key=scores.get, reverse=True)[:k]
4. Пост‑обработка и формирование подсказки
Из выбранных документов извлекаем релевантные фрагменты (например, первые 2–3 предложения, содержащие ключевые термины). Затем собираем prompt:
You are a knowledgeable assistant. Use only the information provided in the following excerpts to answer the question.
--- Excerpts ---
{excerpt_1}
{excerpt_2}
...
--- Question ---
{user_query}
Такой подход ограничивает LLM «залипание» на внешних данных и повышает точность.
5. Генерация ответа
Отправляем готовый prompt в выбранную LLM‑службу. При необходимости задаём temperature=0 и max_tokens в зависимости от длины ответа. Полученный текст возвращаем пользователю.
Лучшие практики и типичные подводные камни
| Практика | Почему важна |
|---|---|
| Регулярное обновление эмбеддингов | Данные меняются – устаревшие векторы приводят к деградации качества поиска. |
| Кеширование запросов | Часто повторяющиеся запросы (особенно в корпоративных чат‑ботах) экономят вычисления LLM и векторных поисков. |
| Контроль за длиной контекста | LLM имеет ограничение токенов (например, 8 К для GPT‑4). Применяйте ранжирование и отбор, чтобы не превысить лимит. |
| Метрики оценки | Используйте R‑Precision, NDCG@k и пользовательские оценки (CTR, удовлетворённость) для мониторинга. |
| Обратная связь | Позвольте пользователю помечать неправильные ответы – это источник данных для переобучения ранжировщика. |
Распространённые ошибки
- Переусложнение гибридного ранжирования – слишком много весовых коэффициентов делает модель нечувствительной к изменениям. Начните с простого RRF, затем экспериментируйте.
- Отсутствие фильтрации дубликатов – одинаковые фрагменты из разных источников могут «перекрывать» друг друга, ухудшая читаемость ответа.
- Неправильный размер k – слишком маленькое
kприводит к потере полезной информации, слишком большое – к перегрузке LLM. Оптимальное значение часто лежит в диапазоне 10‑30.
Пример минимального прототипа на Python
import faiss, torch
from sentence_transformers import SentenceTransformer
from elasticsearch import Elasticsearch
from openai import OpenAI
# 1. Инициализация
embedder = SentenceTransformer('all-mpnet-base-v2')
es = Elasticsearch('http://localhost:9200')
client = OpenAI(api_key='YOUR_KEY')
# 2. Поиск
def search(query):
mode = choose_strategy(query) # агент
results = []
if mode in ('semantic', 'hybrid'):
q_vec = embedder.encode([query])[0]
D, I = faiss_index.search(q_vec.reshape(1, -1), 20)
results.append(I[0].tolist())
if mode in ('keyword', 'hybrid'):
resp = es.search(index='docs', body={'query': {'match': {'content': query}}}, size=20)
kw_ids = [hit['_id'] for hit in resp['hits']['hits']]
results.append(kw_ids)
# 3. Объединяем
merged = rrf(results, k=15)
# 4. Формируем подсказку
excerpts = [fetch_excerpt(doc_id) for doc_id in merged[:3]]
prompt = f"""You are a helpful assistant. Use only the following excerpts to answer the question.\n---\n{'\n---\n'.join(excerpts)}\n---\nQuestion: {query}"""
# 5. Генерация
answer = client.chat.completions.create(
model='gpt-4o',
messages=[{'role': 'user', 'content': prompt}],
temperature=0,
max_tokens=512
)
return answer.choices[0].message.content
# Пример вызова
print(search("Как настроить репликацию в PostgreSQL?"))
Код демонстрирует базовый цикл: агент выбирает стратегию, происходит параллельный поиск, объединение результатов, построение prompt и генерация ответа. В реальном проекте добавляются слои кэширования, логирование, мониторинг и автоматическое пере‑индексирование.
Масштабирование и эксплуатация
- Горизонтальное масштабирование FAISS – использовать
faiss.IndexIVFFlatс несколькими шард‑процессами или перейти к облачному векторному сервису (Pinecone, Weaviate). - Кластер Elasticsearch – обеспечить репликацию и шардирование, настроить
refresh_intervalпод нагрузку. - Контейнеризация – упаковать каждый компонент в Docker, оркестровать через Kubernetes: отдельные поды для индексов, отдельный сервис‑агент, LLM‑gateway.
- CI/CD – автоматизировать пере‑индексацию при появлении новых документов, тестировать метрики поиска в пайплайне.
Сочетание гибридного поиска и агентного управления позволяет построить RAG‑систему, которая адаптируется к разным типам запросов, сохраняет высокую точность и остаётся масштабируемой в продакшн‑окружении.