Пошаговое руководство по настройке Retrieval-Augmented Generation для поиска по корпоративным документам и личным архивам. Практические рекомендации по чанкованию, выбору embeddings, хранению векторов и интеграции с LLM (с конкретными цифрами и кодом).
0
Статья была полезной?
Комментарии (0)
Войдите или зарегистрируйтесь, чтобы оставить комментарий
Загрузка комментариев…
RAG (Retrieval-Augmented Generation) — это комбинация поиска по документам и генеративной модели, позволяющая LLM выдавать точные ответы на вопросы, опираясь на релевантные фрагменты корпуса. Ниже покажу рабочий процесс с цифрами, командами и кодом, чтобы за 1–3 дня получить рабочий прототип поиска по своим документам.
Что такое RAG?
RAG — подход, где на запрос пользователя сначала выполняется поиск релевантных фрагментов векторами (retrieval), а затем LLM использует найденные фрагменты в подсказке (augmentation) для генерации ответа. Это снижает выдумки (hallucination) у модели и ограничивает её знания базой документов. Практический пример: у вас 50 000 служебных записок, 15 ГБ PDF и 1000 презентаций — RAG позволяет быстро найти и сформулировать ответ, опираясь на конкретные абзацы.
Шаг 1: Собираем корпус документов
Конкретика: сформируйте корус в формате текстов — PDF, DOCX, HTML, TXT. Для начала целевой объём 0.5–10 ГБ: 10 000–100 000 документов. На этапе сбора учтите метаданные: дата, автор, источник. Для прототипа 1 000 документов (≈0.5–1 ГБ) хватает, на продакшн рекомендуют 50 000+ документов.
Инструменты парсинга: pdfminer.six или pypdf для PDF, python-docx для DOCX, встроенный парсер для HTML.
Критерии качества: удалите сканы без OCR — такие документы не пригодны. Для OCR используйте Tesseract 5 или платные сервисы (ABBYY) — OCR добавит 0.5–2 USD за страницу при стороннем сервисе в 2025 году.
Ожидаемые сроки: обработка 1 000 PDF (в среднем 10 страниц) с OCR на CPU — 6–12 часов; на GPU с параллелизмом — 1–2 часа.
# Пример: парсинг PDF -> текст (python, pypdf)
from pypdf import PdfReader
with open('document.pdf','rb') as f:
reader = PdfReader(f)
text = '
'.join(p.extract_text() or '' for p in reader.pages)
Шаг 2: Чанкование и нормализация
Разбейте документы на фрагменты (chunks) понятного размера. Рекомендуемые параметры: 200–500 токенов на чанк с перекрытием 50–100 токенов. В символах это примерно 800–2500 символов на чанк в зависимости от языка и стиля.
Почему 200–500 токенов: баланс между контекстом и точностью поиска; LLM корректно использует 2–5 таких фрагментов в подсказке.
Перекрытие 50–100 токенов: обеспечивает, что границы не обрывают смысл фразы.
Порядок и метаданные: сохраняйте doc_id, chunk_id, offset, source_url, date. Это позволит показывать пользователю источник ответа.
# Чанкование на 400 токенов с overlap 100 (пример на простом разбиении по словам)
def chunk_text(text, tokens_per_chunk=400, overlap=100):
words = text.split()
chunks = []
i = 0
while i < len(words):
chunk = ' '.join(words[i:i+tokens_per_chunk])
chunks.append(chunk)
i += tokens_per_chunk - overlap
return chunks
Шаг 3: Генерация embeddings
Embeddings — векторы фиксированной размерности, представляющие смысл фрагмента. В 2025–2026 рекомендую ориентироваться на размерность 768–1536 для типичных задач поиска: 768 — быстрее и дешевле, 1536 — точнее при большом объёме данных.
Онлайн-сервисы: OpenAI text-embedding-3-small/large (1536 dim у large), Anthropic, Cohere — latency ~50–200 мс/вектор, цена за 1k векторов на 2025 ~0.10–0.50 USD в зависимости от модели.
Локальные модели: sentence-transformers/all-MiniLM-L6-v2 (384 dim) — 10–20 мс на вектор на GPU A100; all-mpnet-base-v2 (768 dim) — 20–50 мс/вектор на GPU T4. Стоимость GPU в облаке (p3a/p4) ~0.50–3.00 USD/час в 2025 году.
Batch: отправляйте пачки по 64–512 векторов при использовании API или 128–1024 при локальном GPU для throughput; при batch=256 и 1536-d на GPU можно обработать 50–200k токенов в минуту.
# Пример генерации embeddings через OpenAI (python, 2025)
from openai import OpenAI
client = OpenAI()
texts = ['Первый фрагмент', 'Второй фрагмент']
emb = client.embeddings.create(model='text-embedding-3-large', input=texts)
vectors = [e['embedding'] for e in emb['data']]
Шаг 4: Хранение в векторной БД
Выбор хранилища зависит от объёма и SLA. Для прототипа подойдёт FAISS или Chroma на одной машине. Для продакшн 50k+ векторов используйте Qdrant, Milvus или Pinecone — они поддерживают репликацию и масштабирование.
Размеры: вектор 1536-d (float32) занимает 6 KB. 1M таких векторов ≈ 6 GB raw. С индексом (IVF, HNSW) и метаданными рассчитывайте 1.5–3× — значит ~9–18 GB на 1M векторов.
RAM и SSD: для low-latency рекомендуют размещать индекс в памяти: 16–64 GB RAM для 1M векторов. Если стоит экономить — используйте on-disk индекс с SSD NVMe, throughput падает, латентность возрастает в 2–5×.
Примеры стоимости (ориентировочно, 2025): управление Pinecone small pod ~0.20–0.50 USD/час; self-host Qdrant/Milvus на AWS r5.large (~16GB RAM) ~0.20–0.40 USD/час плюс диск.
# Сохранение в FAISS и метаданные в numpy
import faiss
import numpy as np
vectors = np.array(vectors).astype('float32') # shape (N, D)
index = faiss.IndexFlatIP(vectors.shape[1]) # cosine через нормализацию
faiss.normalize_L2(vectors)
index.add(vectors)
faiss.write_index(index, 'index.faiss')
np.save('metadata.npy', np.array(metadata, dtype=object))
Шаг 5: Интеграция с LLM (RAG)
Схема простая: на запрос пользователя — генерация embedding запроса, ближайший поиск в векторной БД (к примеру топ-5), сбор фрагментов и передача их LLM в подсказке. Важно: ограничьте токены контекста (часто 2–5 фрагментов по 400 токенов) и используйте шаблон подсказки с явными инструкциями о цитировании источников.
# Псевдокод процесса RAG (python)
query = 'Как настроить бэкап базы данных в PostgreSQL?'
q_emb = client.embeddings.create(model='text-embedding-3-large', input=query)['data'][0]['embedding']
# поиск top_k
D, I = index.search(np.array([q_emb], dtype='float32'), k=5)
context = '
'.join(metadata[i]['text'] for i in I[0])
prompt = f"Ответь кратко, опираясь на источники:
{context}
Вопрос: {query}"
resp = client.chat.completions.create(model='gpt-4o-mini', messages=[{'role':'user','content':prompt}])
print(resp.choices[0].message.content)
Рекомендации по качеству ответов:
Пропорция токенов: не больше 3–5 фрагментов с общей длиной контекста ~1200–2000 токенов.
Порог релевантности: при cosine similarity < 0.15 (для нормализованных 1536-d) не подставлять фрагмент и уведомлять пользователя о слабом совпадении.
Реранк: после первичного retrieval используйте дешёвую символьную модель (например, small cross-encoder) для реранжирования топ-20 — увеличивает точность на 5–15% за счёт дополнительной задержки 50–200 мс на запрос.
Какие embeddings выбрать?
Выбор зависит от бюджета, объёма и требования к латентности. Привожу проверенные рекомендации на 2025–2026 год с конкретикой.
Малый бюджет, прототип: sentence-transformers/all-MiniLM-L6-v2 — 384 dim, быстрое локальное исполнение на CPU, точность хорошая для коротких текстов. На CPU 8‑ядрах обработка 10k фрагментов займёт 1–3 часа.
Сбалансированно (точность/цена): all-mpnet-base-v2 — 768 dim; локально на GPU T4 ~50–100 ms/вектор, облачная обработка 100k фрагментов займёт 1–3 часа при batch=512.
Максимальная точность: облачные embeddings (OpenAI text-embedding-3-large или аналогичные) — 1536 dim; задержка 50–150 ms/вектор; стоимость 2025 порядка 0.10–0.40 USD за 1k векторов, но это сокращает ошибочные совпадения и уменьшает потребность в реранке.
Специальные доменные модели: для медицины/юриспруденции используйте fine-tuned models или instructor-family (например, hkunlp/instructor-xl) — лучше сохраняют юридические/медицинские нюансы.
Практический подбор: если у вас 10k документов, начните с local MPNet (768) и сравните точность на контрольном наборе из 100 запросов. Если точность <75% — переходите на 1536-d коммерческий embedding.
Как хранить векторы?
Опции: файловая система (FAISS index + npy), self-hosted DB (Qdrant, Milvus), managed (Pinecone). Выбор зависит от SLA, бюджета и команды поддержки. Ниже — конкретные схемы хранения и резервирования.
FAISS (локально): подходит для экспериментов. Храните индекс (.faiss) и метаданные (.npy/.parquet). Резервное копирование: ежедневный бекап индекса и метаданных на S3 или сетевой диск. Восстановление обычно занимает 10–60 минут для индекса 10–50 GB.
Qdrant/Milvus (self-host): лучше для продакшн без облачного lock-in. Конфигурация для 1M векторов (1536d): 4 CPU, 32 GB RAM, NVMe 200 GB. Стартовая стоимость сервера в облаке 2025 ~120–300 USD/месяц. Настройте репликацию 2 узла для отказоустойчивости и резерв копий каждые 6–24 часов.
Pinecone (managed): быстрый запуск и масштабирование. Для требований SLA 99.9% и latency <50 ms выбирайте production pods; стоимость примера: 0.20–0.50 USD/час для small pod, рост с учётом хранения и запросов. Учтите egress и API-лимиты.
Шаг 6: Тестирование и метрики качества
Измеряйте точность retrieval и RAG-ответа. Набор метрик и конкретика:
Recall@K: доля случаев, когда релевантный фрагмент попал в топ-K. Цель для внутренних документов — Recall@5 ≥ 0.85 на контрольной выборке.
NRR (Normalized Reciprocal Rank): среднее обратное рангу первого релевантного ответа; стремитесь к ≥0.6 для качественного поиска.
Latency: end-to-end (embedding + retrieval + LLM) для интерактивных интерфейсов — ≤1.5–2.5 секунды; для аналитических задач допускается 3–10 секунд.
Качество генерации: ручная оценка 100–500 ответов, метрики полезности/корректности. Автоматически можно проверять наличие цитирования источников и фактологическую точность через внешние проверки.
Шаг 7: Развёртывание и обслуживание
Планируйте CI/CD для индексации: инкрементальная генерация эмбеддингов при появлении новых документов, ежедневные/почасовые задания. Рекомендуемая архитектура при 24/7: разделение ingestion (ETL), embeddings сервис, векторная БД, сервис RAG (API) и frontend.
ETL: периодичность инкрементации 15–60 минут для часто меняющихся данных, раз в день для архива. Latency на обновление одного документа ~10–30 секунд при использовании очередей (RabbitMQ/Kafka) и batch-embeddings.
Мониторинг: отслеживайте рост индекса, латентность запросов, ошибочные OCR/парсинг. Алгоритм автоскейлинга для Qdrant/Milvus — правило: CPU > 70% и p99 latency > 200 ms — инстанты +1.
Показывайте источник и контекст. UX влияет на доверие пользователей сильнее, чем качество модели: показывайте snippet с подсветкой найденного фрагмента, дату и автора. Если RAG не уверен (низкие скоры), выводите фразу «Не найдено релевантных подтверждений» и предложите уточнить запрос.
Примеры правил вывода: если top-1 similarity < 0.18 — не давать категоричный ответ, а предлагать карточку с источниками.
Обратная связь: кнопка «Это помогло»/«Это неверно» для сбора данных по корректировке ранжирования.
Частые вопросы
Как работает RAG?
RAG работает в две стадии: retrieval — поиск семантически похожих фрагментов векторного пространства с помощью embedding моделей и векторной БД; generation — LLM получает найденные фрагменты в подсказке и формирует ответ, опираясь на них. Retrieval сокращает пространство знаний для LLM и снижает риск выдумывания фактов. На практике pipeline: запрос → embedding запроса → top-K search → формирование prompt с контекстом → вызов LLM → постобработка и выдача пользователю.
Что лучше для embeddings: локальный или облачный сервис?
Локальный вариант дешевле при больших объёмах (низкие переменные расходы) и даёт контроль над данными. Облачный сервис проще в эксплуатации, лучше масштабируется и зачастую даёт более качественные embeddings out-of-the-box. Если у вас 10k–100k фрагментов и строгие требования к конфиденциальности — локальная модель (MPNet/miniLM) на GPU будет разумным выбором. Если важнее точность и вы готовы платить — коммерческие embedding API часто дают лучший результат при тех же усилиях интеграции.
Почему возникают галлюцинации у RAG и как их снизить?
Галлюцинации возникают, когда LLM генерирует информацию, не подтверждённую контекстом. Причины: релевантные фрагменты не найдены, контекст слабо связан с вопросом или prompt не содержит строгих инструкций. Снижение: 1) увеличить Recall@K, 2) использовать порог сходства и не подставлять нерелевантные фрагменты, 3) требовать от LLM ссылаться на источник в ответе, 4) добавлять реранк или verification шаг (например, secondary call к model, которая проверяет факты).
Зачем нужен реранк и когда его включать?
Реранк повышает точность финальной выдачи: первичный ANN search даёт «кандидатов», реранк с помощью cross-encoder или более точной модели пересчитывает релевантность. Включайте реранк если точность retrieval ниже желаемой (Recall@5 < 0.85) или когда важно ранжирование по релевантности, а не просто наличие фрагмента. Стоимость: реранк добавляет вычисления — 20–200 ms на запрос при легкой модели, 0.5–2 USD/1k запросов для облачных cross-encoders в 2025.
Где хранить метаданные и как бэкапить индексы?
Метаданные удобно хранить в документной БД (Postgres + jsonb, MongoDB) или как parquet/numpy-файлы в S3. Индексы FAISS — бэкап как файлы (.faiss) на S3; для Qdrant/Milvus используйте встроенные механизмы snapshot и репликацию: снимок каждые 6–24 часов и реплика на отдельный регион. Тест восстановления: восстанавливайте из бэкапа раз в неделю, чтобы убедиться, что процесс работает и реконструкция индекса занимает приемлемое время (обычно 10–60 минут для 10–50 GB).
Архитектура RAG: поиск и генерация
Интерфейс Qdrant с примером поиска
Начните с 1 000 документов и local FAISS + MPNet, измерьте Recall@5 за 1–2 дня.
Переходите на managed vector DB (Pinecone/Qdrant) при росте корпуса до 50k+.
Оцените стоимость хранения: 1M vectors (1536d) ≈ 9–18 GB с индексом; считайте 1.5–3× к raw size.
RAG с нуля: строим поиск по своим документам | KtoHto
Комментарии (0)
Войдите или зарегистрируйтесь, чтобы оставить комментарий
Загрузка комментариев…