🦙

LlamaIndex: RAG-агент с нуля за час

Создание RAG-агента на LlamaIndex: загрузка PDF-документов, индексация в ChromaDB, агент с инструментами поиска и ответы на вопросы по вашей базе знаний.

intermediate ⏱ 20 мин
Архитектура LlamaIndex RAG-агента 📄 PDF / TXT Исходные документы SimpleDirectoryReader + SentenceSplitter Embedding Model text-embedding-3-small → векторы ChromaDB Векторное хранилище 👤 Вопрос пользователя "Какой срок договора?" VectorIndexRetriever top-k=3 чанка LLM Agent gpt-4o → ответ top-k Документы → чанки → эмбеддинги → ChromaDB → Retriever → LLM → ответ QueryEngineTool оборачивает всю цепочку в инструмент для агента

# 1. Установка зависимостей

Устанавливаем LlamaIndex, ChromaDB, PyPDF для парсинга PDF и OpenAI SDK. ChromaDB будет нашим векторным хранилищем — она легковесная, работает в памяти и отлично подходит для прототипов. Для продакшена можно заменить на Pinecone или Weaviate.

# Создаём окружение
mkdir rag-agent && cd rag-agent
python3 -m venv venv
source venv/bin/activate

# Устанавливаем LlamaIndex и зависимости
pip install llama-index llama-index-vector-stores-chroma chromadb
pip install llama-index-embeddings-openai openai
pip install pypdf  # для чтения PDF
pip install llama-index-llms-openai

# Проверяем версии
pip show llama-index | grep Version
    

# 2. Загрузка документов и разбивка на чанки

Загружаем PDF-файлы из папки data/. SimpleDirectoryReader автоматически обрабатывает все форматы: PDF, TXT, DOCX. SentenceSplitter разбивает документы на чанки по 1024 токена с перекрытием 20 токенов — это обеспечивает сохранение контекста между фрагментами.

from llama_index.core import SimpleDirectoryReader
from llama_index.core.node_parser import SentenceSplitter

# Загружаем все документы из папки data/
documents = SimpleDirectoryReader(
    input_dir="./data",
    recursive=True,
    required_exts=[".pdf", ".txt", ".md"]
).load_data()

print(f"Загружено документов: {len(documents)}")

# Разбиваем на чанки (1024 токена, перекрытие 20)
splitter = SentenceSplitter(
    chunk_size=1024,
    chunk_overlap=20,
    separator=" "
)
nodes = splitter.get_nodes_from_documents(documents)
print(f"Создано чанков: {len(nodes)}")
    

# 3. Индексация в ChromaDB

Создаём векторный индекс. Каждый чанк превращается в эмбеддинг с помощью OpenAI text-embedding-3-small (размерность 1536) и сохраняется в ChromaDB. При повторном запуске индекс загружается из коллекции — не нужно переиндексировать.

import chromadb
from llama_index.vector_stores.chroma import ChromaVectorStore
from llama_index.core import (
    VectorStoreIndex,
    StorageContext,
    Settings
)
from llama_index.embeddings.openai import OpenAIEmbedding

# Настройка модели эмбеддингов
Settings.embed_model = OpenAIEmbedding(
    model="text-embedding-3-small",
    dimensions=1536
)

# Инициализируем ChromaDB клиент
chroma_client = chromadb.PersistentClient(path="./chroma_db")
chroma_collection = chroma_client.get_or_create_collection(
    name="rag_docs"
)

# Оборачиваем ChromaDB в StorageContext
vector_store = ChromaVectorStore(chroma_collection=chroma_collection)
storage_context = StorageContext.from_defaults(
    vector_store=vector_store
)

# Строим индекс (или загружаем существующий)
index = VectorStoreIndex(
    nodes,
    storage_context=storage_context,
    show_progress=True
)

print(f"Индекс создан: {chroma_collection.count()} векторов")
    

# 4. Создание QueryEngine и тестирование поиска

Создаём движок запросов (QueryEngine) поверх индекса. Он выполняет семантический поиск и передаёт найденные чанки в LLM для генерации ответа. Проверяем работу на тестовых вопросах, используя gpt-4o.

from llama_index.llms.openai import OpenAI

# Настраиваем LLM
Settings.llm = OpenAI(
    model="gpt-4o",
    temperature=0.3,  # низкая температура — фактологические ответы
)

# Создаём query engine с топ-3 релевантными чанками
query_engine = index.as_query_engine(
    similarity_top_k=3,
    response_mode="compact",  # компактный промпт с чанками
    streaming=False
)

# Тестируем
questions = [
    "Какие условия расторжения договора?",
    "Перечисли основные продукты компании",
    "Какая ответственность сторон при задержке?"
]

for q in questions:
    response = query_engine.query(q)
    print(f"\n❓ {q}")
    print(f"📝 {response.response[:300]}...")
    print(f"📎 Источники: {len(response.source_nodes)} чанков")
    

# 5. Превращаем в RAG-агента с инструментами

Теперь создаём полноценного агента. Оборачиваем QueryEngine в QueryEngineTool и добавляем ReActAgent, который может использовать несколько инструментов одновременно. Агент сам решает, какой инструмент применить для ответа.

from llama_index.core.tools import QueryEngineTool, ToolMetadata
from llama_index.core.agent import ReActAgent

# Оборачиваем query engine в инструмент
rag_tool = QueryEngineTool(
    query_engine=query_engine,
    metadata=ToolMetadata(
        name="document_search",
        description=(
            "Поиск по корпоративным документам, "
            "договорам и внутренним регламентам. "
            "Используй для вопросов о политиках компании."
        )
    )
)

# Создаём ReAct-агента
agent = ReActAgent.from_tools(
    tools=[rag_tool],
    llm=Settings.llm,
    verbose=True,  # видим ход мыслей агента
    max_iterations=5
)

# Задаём сложный вопрос
response = agent.chat(
    "Расскажи про штрафные санкции за просрочку поставки. "
    "Есть ли максимальный размер неустойки?"
)
print(f"🤖 Агент: {response.response}")
    

# 6. Мульти-индексный агент: несколько коллекций документов

Для реальных проектов одного индекса недостаточно. Создаём несколько индексов: для договоров, технической документации и HR-политик. Агент автоматически выбирает нужный индекс в зависимости от вопроса.

# Создаём несколько индексов
collections = {
    "contracts": "./data/contracts",
    "technical": "./data/docs",
    "hr_policies": "./data/hr"
}

tools = []
for name, path in collections.items():
    docs = SimpleDirectoryReader(input_dir=path).load_data()
    nodes = splitter.get_nodes_from_documents(docs)

    # Отдельная коллекция ChromaDB для каждого индекса
    coll = chroma_client.get_or_create_collection(name)
    vs = ChromaVectorStore(chroma_collection=coll)
    sc = StorageContext.from_defaults(vector_store=vs)
    idx = VectorStoreIndex(nodes, storage_context=sc)

    # Каждый индекс → свой инструмент
    tool = QueryEngineTool(
        query_engine=idx.as_query_engine(similarity_top_k=3),
        metadata=ToolMetadata(
            name=f"search_{name}",
            description=f"Поиск в разделе: {name}"
        )
    )
    tools.append(tool)

# Мульти-индексный агент
multi_agent = ReActAgent.from_tools(
    tools=tools,
    llm=Settings.llm,
    verbose=True
)

multi_agent.chat("Какие условия отпуска для сотрудников?")
    
✅ Итог

За час вы создали полноценного RAG-агента, способного отвечать на вопросы по вашим документам. LlamaIndex берёт на себя всю сложность: чанкинг, эмбеддинги, хранение векторов и интеграцию с LLM. Мульти-индексная архитектура позволяет масштабировать решение на десятки коллекций документов без потери точности. Для продакшена замените ChromaDB на Pinecone и добавьте кэширование ответов.

🔗 Полезные ссылки

📖 Документация 💻 GitHub 🗄️ ChromaDB