Как построить RAG-систему за один день
Пошаговое руководство по созданию RAG-системы с нуля: от векторной базы до готового чат-бота на ваших документах за один рабочий день.
Ваша модель отвечает уверенно — но врёт. Она не знает о вашей внутренней документации, о приказах за прошлый квартал, о базе клиентов. Это не баг GPT-4o или Claude — это архитектурная проблема. Решение называется RAG: Retrieval-Augmented Generation.
За один рабочий день вы можете собрать систему, которая ищет нужные фрагменты в ваших документах и передаёт их модели как контекст. Без дообучения, без дорогих GPU, без магии. Только Python, несколько библиотек и здравый смысл.
Что такое RAG и почему это не просто “поиск”
RAG — это архитектурный паттерн, а не конкретный продукт. Идея проста: вместо того чтобы надеяться, что языковая модель запомнила нужную вам информацию при обучении, вы сами подаёте ей релевантный контекст прямо в промпт.
graph LR
A[Вопрос пользователя] --> B[Embedding-модель]
B --> C[Векторный поиск]
D[База документов] --> E[Chunking + Indexing]
E --> C
C --> F[Топ-K фрагментов]
F --> G[LLM с контекстом]
A --> G
G --> H[Ответ]
В отличие от простого keyword-поиска, векторный поиск понимает семантику. Запрос «как оформить отпуск» найдёт документ «порядок предоставления ежегодного оплачиваемого отдыха» — потому что их смысловые векторы близки в многомерном пространстве.
Языковая модель — это рассуждатель, а не хранилище. RAG даёт ей факты, а она умеет с ними работать.
Классический RAG состоит из двух фаз:
- Indexing (офлайн) — загружаем документы, нарезаем на чанки, превращаем в векторы, сохраняем в векторную БД.
- Retrieval + Generation (онлайн) — принимаем вопрос, ищем похожие чанки, формируем промпт, получаем ответ от LLM.
Шаг 1: Окружение и выбор инструментов
Для старта вам нужны три компонента: оркестратор, embedding-модель и векторная база.
Оркестратор
Два главных варианта в 2026 году:
| Фреймворк | Плюсы | Когда выбирать |
|---|---|---|
| LangChain | Гибкость, огромная экосистема, лучший для агентов | Сложные пайплайны, много интеграций |
| LlamaIndex | Проще для документов, нативный RAG | Document-heavy задачи, быстрый старт |
| Без фреймворка | Полный контроль, меньше магии | Понимание основ, кастомные решения |
Для этого туториала берём LlamaIndex — он оптимизирован именно под RAG и позволяет собрать рабочий прототип за 20 строк кода.
Векторная база данных
| База | Self-hosted | Managed | Лучший сценарий |
|---|---|---|---|
| Chroma | Бесплатно | Бесплатно до 1M векторов | Прототипы, разработка |
| Qdrant | ~$30-50/мес (VPS) | от $100/мес | Продакшн, высокая нагрузка |
| Weaviate | ~$50-100/мес | от $150/мес | Гибридный поиск, мультимодальность |
| Pinecone | — | от $70/мес | Enterprise, managed без забот |
| pgvector | В рамках PostgreSQL | — | Уже есть Postgres |
Для первого дня — Chroma. Работает локально, не требует инфраструктуры, хранит данные на диске.
Установка
pip install llama-index llama-index-vector-stores-chroma \
llama-index-embeddings-openai chromadb openai python-dotenv
Создайте .env:
OPENAI_API_KEY=sk-...
nomic-embed-text для эмбеддингов и llama3.2 для генерации. LlamaIndex поддерживает оба варианта через OllamaEmbedding и Ollama LLM.Шаг 2: Indexing — загружаем и нарезаем документы
Качество RAG-системы на 80% определяется качеством индексирования. Плохие чанки — плохие ответы, независимо от того, насколько умна ваша LLM.
Загрузка документов
from llama_index.core import SimpleDirectoryReader, Settings
from llama_index.embeddings.openai import OpenAIEmbedding
from llama_index.llms.openai import OpenAI
from dotenv import load_dotenv
load_dotenv()
# Настраиваем модели глобально
Settings.embed_model = OpenAIEmbedding(model="text-embedding-3-small")
Settings.llm = OpenAI(model="gpt-4o-mini", temperature=0.1)
# Загружаем документы из папки
documents = SimpleDirectoryReader("./docs").load_data()
print(f"Загружено документов: {len(documents)}")
LlamaIndex умеет читать PDF, DOCX, TXT, HTML, Markdown и ещё дюжину форматов — всё через SimpleDirectoryReader.
Нарезка на чанки (chunking)
Это критический шаг. Чанк — это фрагмент текста, который попадёт в промпт как один контекстный блок.
from llama_index.core.node_parser import SentenceSplitter
# Оптимальные параметры для большинства задач
splitter = SentenceSplitter(
chunk_size=512, # токены на чанк
chunk_overlap=50 # перекрытие для сохранения контекста
)
nodes = splitter.get_nodes_from_documents(documents)
print(f"Создано чанков: {len(nodes)}")
Создание индекса в Chroma
import chromadb
from llama_index.vector_stores.chroma import ChromaVectorStore
from llama_index.core import VectorStoreIndex, StorageContext
# Инициализируем Chroma с сохранением на диск
chroma_client = chromadb.PersistentClient(path="./chroma_db")
chroma_collection = chroma_client.get_or_create_collection("my_docs")
# Создаём vector store и индекс
vector_store = ChromaVectorStore(chroma_collection=chroma_collection)
storage_context = StorageContext.from_defaults(vector_store=vector_store)
index = VectorStoreIndex(
nodes,
storage_context=storage_context,
)
print("Индексирование завершено. База сохранена в ./chroma_db")
Первый запуск отправит все чанки в OpenAI Embeddings API и сохранит векторы локально. При следующем запуске достаточно загрузить уже существующий индекс.
Шаг 3: Retrieval + Generation — задаём вопросы
Индекс готов. Теперь самая приятная часть — спрашиваем.
from llama_index.core import VectorStoreIndex
from llama_index.vector_stores.chroma import ChromaVectorStore
from llama_index.core import StorageContext
import chromadb
# Загружаем существующий индекс (не нужно индексировать заново)
chroma_client = chromadb.PersistentClient(path="./chroma_db")
chroma_collection = chroma_client.get_or_create_collection("my_docs")
vector_store = ChromaVectorStore(chroma_collection=chroma_collection)
storage_context = StorageContext.from_defaults(vector_store=vector_store)
index = VectorStoreIndex.from_vector_store(vector_store, storage_context=storage_context)
# Создаём query engine
query_engine = index.as_query_engine(
similarity_top_k=5, # сколько чанков извлекать
response_mode="compact", # compact | refine | tree_summarize
)
# Задаём вопрос
response = query_engine.query(
"Какой порядок согласования командировок?"
)
print(response)
# Смотрим источники
for node in response.source_nodes:
print(f"\n--- Источник (score: {node.score:.3f}) ---")
print(node.text[:200])
compact — объединяет чанки в один промпт, быстро и дёшево. refine — итеративно уточняет ответ по каждому чанку, точнее но дороже. tree_summarize — строит иерархическое резюме, лучший вариант для длинных документов.Шаг 4: Оцениваем качество и улучшаем
Рабочий прототип готов. Но прежде чем отдавать его пользователям — нужно понять, насколько он хорош.
Метрики RAG
Есть три ключевых вопроса:
- Retrieval Relevance — нашли ли мы правильные чанки?
- Faithfulness — ответ основан на найденных чанках или LLM придумала?
- Answer Relevance — ответ действительно отвечает на вопрос?
Самый простой способ оценить — добавить логирование source_nodes и вручную проверить 20–30 запросов. Для автоматической оценки используйте RAGAS (pip install ragas) — он считает все три метрики через ещё один вызов LLM.
Типичные проблемы и решения
| Проблема | Симптом | Решение |
|---|---|---|
| Плохое извлечение | Ответ не по теме | Уменьшить chunk_size, попробовать hybrid search |
| Галлюцинации | Факты не из документов | Снизить temperature, добавить system prompt «отвечай только по контексту» |
| Медленный ответ | >5 сек на запрос | Уменьшить similarity_top_k, кэшировать эмбеддинги |
| Потеря контекста | Обрезанный ответ | Увеличить chunk_overlap, использовать refine mode |
Простейший улучшенный промпт
from llama_index.core import PromptTemplate
qa_prompt = PromptTemplate(
"""Ты — помощник, который отвечает на вопросы строго на основе предоставленного контекста.
Контекст:
{context_str}
Вопрос: {query_str}
Правила:
- Отвечай только на основе контекста выше
- Если информации нет в контексте — честно скажи об этом
- Давай конкретные ответы без лишней воды
- Если можешь — укажи, из какого раздела взята информация
Ответ:"""
)
query_engine = index.as_query_engine(
similarity_top_k=5,
text_qa_template=qa_prompt,
)
Шаг 5: Путь к продакшну
За один день вы получили работающий прототип. Что дальше, если система должна работать в проде?
Переход от Chroma к production-ready базе
Для продакшна рекомендуется Qdrant или Weaviate. Миграция занимает 15 минут — меняете только vector store, остальной код остаётся.
# Было (Chroma)
from llama_index.vector_stores.chroma import ChromaVectorStore
# Стало (Qdrant)
from llama_index.vector_stores.qdrant import QdrantVectorStore
from qdrant_client import QdrantClient
client = QdrantClient(url="http://localhost:6333")
vector_store = QdrantVectorStore(client=client, collection_name="my_docs")
Что улучшить в следующие итерации
Hybrid search — комбинирует векторный поиск с BM25 (keyword). Работает лучше для терминологии, аббревиатур, имён собственных. Поддерживается в Weaviate и Qdrant из коробки.
Reranking — после retrieval добавляете второй проход: cross-encoder модель (например, cross-encoder/ms-marco-MiniLM-L-6-v2) перепроверяет релевантность каждого чанка. Точность растёт на 10–20%.
Metadata filtering — к каждому чанку добавляете метаданные (дата, отдел, тип документа) и фильтруете при поиске. «Найди только в документах HR-отдела за 2025 год».
Кэширование — один и тот же вопрос не должен каждый раз идти в OpenAI. Кэшируйте эмбеддинги запросов через Redis или локальный dict.
Лучшая RAG-система — это не та, что использует самые сложные алгоритмы, а та, у которой хорошо структурированы документы и правильно подобраны размеры чанков.
Заключение
RAG — это не rocket science. За один день реально собрать систему, которая:
- Индексирует PDF, DOCX, TXT из вашей папки
- Отвечает на вопросы строго по вашим документам
- Показывает источники для каждого ответа
- Работает на локальной машине без серверной инфраструктуры
Стек для старта: LlamaIndex + Chroma + text-embedding-3-small + gpt-4o-mini. Стоимость индексирования 1000 страниц A4 — меньше $1. Стоимость ответа на вопрос — меньше цента.
Ключевые решения, которые определяют качество:
- Chunk size (512 токенов — хороший старт)
- similarity_top_k (5 — баланс точности и скорости)
- Системный промпт (явный запрет галлюцинаций)
- Качество исходных документов (мусор на входе — мусор на выходе)
Начните с простого прототипа, измерьте качество вручную, потом улучшайте. Это быстрее и дешевле, чем сразу проектировать сложную систему с гибридным поиском и reranking.
Источники
- https://developers.llamaindex.ai/python/examples/low_level/oss_ingestion_retrieval/
- https://dev.to/dharshan_a_23835c7dc05682/build-a-production-ready-rag-system-over-your-own-documents-in-2026-a-practical-tutorial-4hd0
- https://www.edenai.co/post/the-2025-guide-to-retrieval-augmented-generation-rag
- https://arxiv.org/abs/2501.07391
- https://4xxi.com/articles/vector-database-comparison/