нейропоток
ГайдыПрактик

RAG с нуля: строим систему вопрос-ответ по своим документам

Практический гайд по RAG (Retrieval-Augmented Generation) — подключаем AI к своим данным. Python, LangChain, ChromaDB. От установки до рабочего чат-бота по PDF за вечер.

Павел·5 мин чтения
RAG с нуля: строим систему вопрос-ответ по своим документам
Поделиться:TelegramVK

ChatGPT не знает ваши внутренние документы. Это очевидно — он обучен на данных до определённой даты и понятия не имеет о вашей документации, базе знаний или корпоративных процессах.

RAG (Retrieval-Augmented Generation) — это способ это исправить. Подключаете AI к своим данным, и он отвечает на вопросы, опираясь именно на них. Не галлюцинирует, не придумывает — цитирует ваши источники.

Сегодня строим это с нуля на Python за один вечер.

Что такое RAG и как он работает

RAG — это не отдельная модель. Это архитектурный паттерн: «поиск + генерация».

Упрощённо: когда пользователь задаёт вопрос, система сначала ищет релевантные куски в вашей базе документов, потом передаёт их в LLM вместе с вопросом. Модель видит контекст и отвечает на его основе.

Три шага:

  1. Индексация — разбиваем документы на chunks, превращаем их в векторы (числовые представления смысла), сохраняем в векторную БД
  2. Поиск — входящий вопрос тоже превращается в вектор, ищем ближайшие по смыслу chunks в базе
  3. Генерация — передаём найденные chunks + вопрос в LLM, получаем ответ

Векторная БД — это база данных, оптимизированная для поиска по смысловой близости, а не по точному совпадению текста. «Как отменить заказ» и «возврат товара» будут рядом, хотя слова разные.

Что будем строить

Чат-бот, который отвечает на вопросы по PDF-файлам. Пример: документация продукта, инструкции, корпоративная база знаний.

Стек:

  • Python 3.11+
  • LangChain — фреймворк для LLM-приложений
  • ChromaDB — локальная векторная БД (работает без сервера, данные в папке)
  • Claude API (Anthropic) — языковая модель
  • PyPDF2 — парсинг PDF

Установка окружения

bash
python -m venv rag-env source rag-env/bin/activate # macOS/Linux # rag-env\Scripts\activate # Windows pip install langchain langchain-anthropic chromadb pypdf2 sentence-transformers

Нужен API-ключ от Anthropic. Берём на console.anthropic.com → API Keys. Кладём в переменную окружения:

bash
export ANTHROPIC_API_KEY="sk-ant-..."

Шаг 1: Загружаем и разбиваем документ

python
from langchain.document_loaders import PyPDFLoader from langchain.text_splitter import RecursiveCharacterTextSplitter # Загружаем PDF loader = PyPDFLoader("документация.pdf") pages = loader.load() # Разбиваем на чанки по 1000 символов с перекрытием 200 splitter = RecursiveCharacterTextSplitter( chunk_size=1000, chunk_overlap=200, separators=["\n\n", "\n", ".", " "] ) chunks = splitter.split_documents(pages) print(f"Разбили на {len(chunks)} чанков")

Параметр chunk_overlap важен — он обеспечивает контекст на границах чанков. Без него можно потерять смысл на стыке кусков.

Шаг 2: Создаём векторную базу

python
from langchain_community.vectorstores import Chroma from langchain_community.embeddings import HuggingFaceEmbeddings # Embeddings — модель для превращения текста в вектор # all-MiniLM-L6-v2 — маленькая и быстрая, работает локально embeddings = HuggingFaceEmbeddings( model_name="sentence-transformers/all-MiniLM-L6-v2" ) # Создаём базу и добавляем документы vectorstore = Chroma.from_documents( documents=chunks, embedding=embeddings, persist_directory="./chroma_db" # сохраняем локально ) vectorstore.persist() print("База создана и сохранена")

Первый запуск скачает модель embeddings (~80 МБ) и займёт пару минут. Дальше работает мгновенно — модель кешируется локально.

Инфо

Embeddings — это числовое представление текста в многомерном пространстве. Тексты с похожим смыслом находятся «рядом» в этом пространстве. Именно это позволяет искать по смыслу, а не по ключевым словам.

Шаг 3: Строим цепочку вопрос-ответ

python
from langchain_anthropic import ChatAnthropic from langchain.chains import RetrievalQA from langchain.prompts import PromptTemplate # Загружаем уже созданную базу vectorstore = Chroma( persist_directory="./chroma_db", embedding_function=embeddings ) # Модель llm = ChatAnthropic( model="claude-3-5-haiku-20241022", # быстрая и дешёвая для тестов temperature=0 ) # Промпт — явно говорим использовать только контекст prompt_template = """Ответь на вопрос, используя только информацию из контекста ниже. Если ответа в контексте нет — скажи об этом прямо. Контекст: {context} Вопрос: {question} Ответ:""" PROMPT = PromptTemplate( template=prompt_template, input_variables=["context", "question"] ) # Собираем цепочку qa_chain = RetrievalQA.from_chain_type( llm=llm, chain_type="stuff", retriever=vectorstore.as_retriever( search_kwargs={"k": 4} # берём 4 самых релевантных чанка ), chain_type_kwargs={"prompt": PROMPT}, return_source_documents=True )

temperature=0 — детерминированные ответы, модель не фантазирует. Для RAG это правильно.

Шаг 4: Задаём вопросы

python
def ask(question: str) -> None: result = qa_chain({"query": question}) print(f"Вопрос: {question}") print(f"\nОтвет: {result['result']}") print("\nИсточники:") for doc in result['source_documents']: page = doc.metadata.get('page', '?') print(f" — стр. {page}: {doc.page_content[:100]}...") # Тестируем ask("Как оформить возврат товара?") ask("Какой срок гарантии на продукт?") ask("Что делать если устройство не включается?")

Пример вывода:

Вопрос: Как оформить возврат товара? Ответ: Для оформления возврата нужно обратиться в службу поддержки в течение 14 дней с момента покупки. Потребуется чек и оригинальная упаковка. Источники: — стр. 23: Политика возврата. Покупатель вправе вернуть товар надлежащего качества в течение 14 дней...

Добавляем поддержку нескольких файлов

Реальный случай — не один документ, а папка с PDF:

python
import os from langchain.document_loaders import DirectoryLoader loader = DirectoryLoader( path="./docs", glob="**/*.pdf", loader_cls=PyPDFLoader ) documents = loader.load() chunks = splitter.split_documents(documents) vectorstore = Chroma.from_documents( documents=chunks, embedding=embeddings, persist_directory="./chroma_db" )

Всё то же самое — только DirectoryLoader рекурсивно обходит папку и загружает все PDF.

Типичные проблемы и как их решать

Ответы не точные или галлюцинирует

Проверьте промпт — явно ли написано «используй только контекст». Увеличьте k в search_kwargs с 4 до 6-8, чтобы брать больше чанков. Иногда помогает уменьшить chunk_size — более мелкие куски точнее попадают в поиск.

Не находит очевидную информацию

Попробуйте другую модель embeddings. all-mpnet-base-v2 точнее, но медленнее. Для русскоязычных документов — intfloat/multilingual-e5-large.

Медленно работает

ChromaDB локально медленнее облачных баз. Для больших объёмов (10k+ документов) смотрите в сторону Pinecone, Weaviate или Qdrant.

Внимание

При работе с конфиденциальными документами — используйте локальные модели embeddings (что мы и делаем) и локальный LLM через Ollama. Тогда данные вообще не уходят во внешние API.

Что дальше

Базовая система работает. Вот куда можно двигаться дальше:

История диалога — добавить ConversationBufferMemory из LangChain, чтобы чат помнил контекст предыдущих вопросов.

Гибридный поиск — сочетание векторного поиска с классическим keyword-поиском. Лучше работает на специфических терминах и именах.

Оценка качества — метрики RAGAS (faithfulness, answer relevancy, context recall) — чтобы измерить, насколько хорошо система отвечает.

Стриминг ответов — для интерфейса лучше стримить токены по мере генерации, а не ждать полного ответа.


Это фундамент. Для анализа данных с Python дальше — смотрите гайд по AI-анализу таблиц с Claude. А для более сложных цепочек агентов с памятью — LangGraph.

RAG — не rocket science. Вечер, Python и ваши PDF — этого хватит.