Claude знает много — но не знает содержимое твоих файлов, внутренних документов компании или вчерашнего отчёта. LLM обучена на публичных данных до определённой даты. Всё, что произошло после обучения, или всё, что никогда не было в интернете — для модели не существует. RAG решает эту проблему: он даёт модели доступ к твоим данным в момент запроса.
LLM знает огромный объём информации — но у неё три ограничения:
1. Дата обучения (knowledge cutoff)
Модель не знает, что произошло после обучения.
"Какие продажи были в прошлом месяце?" → не знает
2. Приватные данные
Модель не видела внутреннюю документацию, переписку, базу знаний.
"Что написано в нашей политике возврата?" → не знает
3. Галлюцинации
Если модель не знает ответ — она может придумать правдоподобный,
но неверный ответ, вместо того чтобы сказать "не знаю".
Аналогия. Представь очень умного консультанта, который прочитал все книги в мире — но не видел ни одного документа из твоего офиса. Он может блестяще рассуждать, но на вопрос «Какой у нас график работы в праздники?» ответит неправильно или придумает.
RAG (Retrieval-Augmented Generation) — Генерация с дополненным поиском. Термин придумали исследователи из Meta (Facebook) в 2020 году.
Идея простая: перед тем как спросить модель — найди релевантную информацию в своих данных и вложи её в запрос.
Без RAG:
Вопрос → LLM → Ответ (на основе обучения)
"Когда платить аренду?" → "Обычно 1-го числа" (угадывает)
С RAG:
Вопрос → Поиск по документам → Найденный фрагмент + Вопрос → LLM → Ответ
"Когда платить аренду?" → [находит: "Аренда до 15-го числа"] →
Claude получает: "Вот документ: {аренда до 15-го}. Вопрос: когда платить?" →
"Согласно документу, аренда оплачивается до 15-го числа."
Аналогия. RAG — это как подсказка на экзамене, только легальная. Студент (модель) умный, но не может помнить всё. Вместо того чтобы зубрить каждый факт, он получает доступ к конспекту (твои документы) и находит нужную страницу перед ответом.
Есть два способа дать модели новые знания. Они решают разные задачи:
┌──────────────────────────────────────────────────────────────────┐
│ RAG vs FINE-TUNING │
│ │
│ RAG (поиск) Fine-tuning (дообучение) │
│ ───────────── ────────────────────── │
│ Данные: в отдельной базе Данные: "вшиты" в модель │
│ Обновление: мгновенное Обновление: нужно переобучать │
│ Стоимость: дешевле Стоимость: дорого │
│ Точность: высокая (есть Точность: модель может │
│ источник) "запомнить" неточно │
│ Когда: факты, документы, Когда: стиль, формат, │
│ база знаний специфическая терминология │
│ │
│ Пример: бот отвечает Пример: модель пишет │
│ по документации компании в стиле юридических договоров │
└──────────────────────────────────────────────────────────────────┘
Для большинства задач RAG — правильный выбор. Fine-tuning нужен редко и стоит дорого.
Источник: Lewis et al. — "Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks" (2020)
https://arxiv.org/abs/2005.11401
ПОДГОТОВКА (один раз) ЗАПРОС (каждый раз)
┌────────────────────────────────┐ ┌────────────────────────────────┐
│ │ │ │
│ 1. Загрузить документы │ │ 4. Поиск: найти похожие │
│ ↓ │ │ фрагменты по вопросу │
│ 2. Разбить на фрагменты │ │ ↓ │
│ (chunking) │ │ 5. Генерация: отправить │
│ ↓ │ │ фрагменты + вопрос │
│ 3. Превратить в векторы │ │ в Claude │
│ (embeddings) │ │ │
│ → сохранить в базу │ │ │
│ │ │ │
└────────────────────────────────┘ └────────────────────────────────┘
Разберём каждый шаг.
Данные для RAG — это любой текст: файлы .txt, .md, .pdf, .docx, страницы сайтов, записи из базы данных. Главное — превратить их в строки Python.
# Простейший вариант — текстовые файлы
def load_documents(folder_path):
"""Загружает все .txt и .md файлы из папки."""
import os
documents = []
for filename in os.listdir(folder_path):
if filename.endswith(('.txt', '.md')):
filepath = os.path.join(folder_path, filename)
with open(filepath, 'r', encoding='utf-8') as f:
documents.append({
'text': f.read(),
'source': filename # запоминаем откуда взяли
})
return documents
Документ может быть огромным — 100 страниц. Нельзя отправить всё целиком в Claude: не влезет в контекстное окно, будет дорого, а модель будет искать иголку в стоге сена.
Решение — разбить документ на маленькие кусочки (chunks). Каждый кусочек — один факт, один параграф, одна мысль.
Аналогия. Представь учебник на 500 страниц. Если кто-то спросит «Что такое фотосинтез?» — не нужно отправлять все 500 страниц. Достаточно найти один параграф на стр. 47, где это объяснено. Chunking — это индексация учебника по параграфам.
1. По фиксированному размеру (самый простой)
Каждый чанк — 500 символов. Если мысль попала на границу — разрежет.
Плюс: просто реализовать
Минус: может разрезать предложение пополам
2. По предложениям / параграфам
Чанк = один или несколько параграфов.
Плюс: не рвёт мысль
Минус: параграфы бывают разной длины
3. С перекрытием (overlap)
Каждый чанк на 50-100 символов заходит на территорию соседнего.
Плюс: если факт попал на границу — он будет в обоих чанках
Минус: чуть больше данных
Рекомендуемый размер чанка: 200–1000 символов. Слишком маленькие — теряется контекст. Слишком большие — поиск становится неточным.
def split_into_chunks(text, chunk_size=500, overlap=50):
"""
Разбивает текст на чанки с перекрытием.
chunk_size: максимальный размер одного чанка (символы)
overlap: сколько символов перекрывается между соседними чанками
"""
chunks = []
start = 0
while start < len(text):
end = start + chunk_size
# Попробуем разрезать по точке или переносу строки
if end < len(text):
# Ищем ближайший конец предложения
last_period = text.rfind('.', start, end)
last_newline = text.rfind('\n', start, end)
best_break = max(last_period, last_newline)
if best_break > start: # нашли хорошее место для разреза
end = best_break + 1
chunks.append(text[start:end].strip())
start = end - overlap # перекрытие
return [c for c in chunks if c] # убрать пустые
Это ключевой шаг. Чтобы искать «по смыслу», нужно превратить текст в числа — векторы. Этот процесс называется embedding (встраивание).
Embedding — это представление текста в виде списка чисел (вектора). Каждое число отражает какой-то аспект смысла текста.
Текст: "Кошка спит на диване"
Embedding: [0.12, -0.34, 0.56, 0.78, -0.23, ... ] ← 384-1536 чисел
Эти числа — не случайные. Они расположены так, что похожие по смыслу тексты получают похожие векторы.
"Кот лежит на софе" → [0.11, -0.33, 0.55, 0.77, -0.22, ...]
"Кошка спит на диване" → [0.12, -0.34, 0.56, 0.78, -0.23, ...]
"Квантовая физика атома" → [0.89, 0.45, -0.67, 0.12, 0.91, ...]
Первые два вектора БЛИЗКИ друг к другу (смысл похож).
Третий вектор ДАЛЁК от первых двух (смысл другой).
Представь, что каждому тексту присваиваются координаты на карте. Тексты про кошек оказываются в одном районе. Тексты про физику — в другом. Тексты про программирование — в третьем.
Когда приходит вопрос «Где купить корм для кота?» — он тоже получает координаты. И эти координаты оказываются рядом с районом «кошки». Поиск сводится к вопросу: какие точки на карте ближе всего к моему вопросу?
"Карта смыслов" (упрощённо)
Физика Кулинария
• •
• • • •
• •
Программирование Домашние животные
• • ← "кот лежит на софе"
• • • ← "кошка спит на диване"
• •
★ ← "где купить корм для кота?"
(попадает в тот же район!)
Embedding создаёт специальная модель (не Claude — отдельная, маленькая модель). Самые популярные:
| Модель | Кто сделал | Размер вектора | Цена |
|---|---|---|---|
text-embedding-3-small |
OpenAI | 1536 | $0.02 / 1M токенов |
text-embedding-3-large |
OpenAI | 3072 | $0.13 / 1M токенов |
| Voyage AI | Anthropic рекомендует | 1024 | есть бесплатный tier |
| Встроенная в ChromaDB | ChromaDB (локальная) | 384 | Бесплатно |
Для обучения и простых проектов встроенная модель ChromaDB — идеальный выбор: ничего не стоит, не нужен API-ключ, работает локально.
Источник: Anthropic — рекомендует Voyage AI для embeddings:
https://docs.anthropic.com/en/docs/build-with-claude/embeddings
Когда все чанки превращены в векторы и сохранены — можно искать.
Поиск работает так: 1. Получаем вопрос пользователя 2. Превращаем вопрос в вектор (тем же способом) 3. Ищем чанки, чьи векторы ближе всего к вектору вопроса 4. Возвращаем top-3 или top-5 самых близких
Как измерить «близость» векторов? Математически это косинусное сходство (cosine similarity) — угол между двумя векторами. Если угол маленький — тексты похожи. Если большой — разные.
Не нужно знать формулу — библиотеки делают это автоматически. Главное — понимать принцип: «ближе» в пространстве векторов = «похожее» по смыслу.
Найденные фрагменты вставляются в промпт для Claude:
# Формируем промпт с контекстом
prompt = f"""Ответь на вопрос пользователя, используя ТОЛЬКО информацию
из предоставленных документов. Если в документах нет ответа — скажи об этом.
<documents>
{found_chunks}
</documents>
<question>
{user_question}
</question>"""
Ключевое: инструкция «используй ТОЛЬКО информацию из документов» — это то, что предотвращает галлюцинации. Модель не придумывает — она отвечает на основе конкретных фрагментов.
pip install chromadb anthropic python-dotenv
| Библиотека | Зачем |
|---|---|
chromadb |
Векторная база данных (хранит чанки и их embeddings) |
anthropic |
Claude API для генерации ответов |
python-dotenv |
Загрузка API-ключа из .env |
ChromaDB — векторная база, которая работает прямо на компьютере, без серверов. Установил — пользуешься. Она сама превращает текст в векторы (встроенная модель) и умеет искать похожие. Идеально для обучения и небольших проектов.
Источник: ChromaDB — Getting Started
https://docs.trychroma.com/docs/overview/getting-started
Создай папку my-docs/ с несколькими текстовыми файлами — это будет база знаний:
my-rag-project/
my-docs/
faq.txt # Часто задаваемые вопросы
rules.txt # Правила компании
products.txt # Описание продуктов
rag.py # Основной код
.env # API-ключ Claude
Пример файла my-docs/faq.txt:
Часто задаваемые вопросы
Как оформить возврат?
Возврат товара возможен в течение 14 дней с момента покупки.
Для оформления возврата обратитесь в службу поддержки по email: support@example.com
или позвоните по телефону 8-800-123-45-67. Необходимо предоставить чек и товар
в оригинальной упаковке.
Какие способы оплаты доступны?
Мы принимаем банковские карты (Visa, Mastercard, МИР), электронные кошельки
(ЮMoney, QIWI) и наличные при доставке курьером.
Сколько стоит доставка?
Доставка бесплатная при заказе от 3000 рублей. При заказе менее 3000 рублей
стоимость доставки — 300 рублей. Доставка по Москве — 1-2 дня,
по России — 3-7 дней.
# rag.py — RAG-система: поиск по своим документам + ответ от Claude
import os
from dotenv import load_dotenv
import chromadb
from anthropic import Anthropic
load_dotenv()
# === ШАГ 1: ЗАГРУЗКА ДОКУМЕНТОВ ===
def load_documents(folder_path):
"""Загружает все .txt и .md файлы из папки."""
documents = []
for filename in os.listdir(folder_path):
if filename.endswith(('.txt', '.md')):
filepath = os.path.join(folder_path, filename)
with open(filepath, 'r', encoding='utf-8') as f:
text = f.read()
documents.append({
'text': text,
'source': filename
})
print(f" Загружен: {filename} ({len(text)} символов)")
return documents
# === ШАГ 2: РАЗБИВКА НА ЧАНКИ ===
def split_into_chunks(text, source, chunk_size=500, overlap=50):
"""
Разбивает текст на фрагменты.
Каждый фрагмент запоминает, из какого файла он взят.
"""
chunks = []
start = 0
chunk_number = 0
while start < len(text):
end = start + chunk_size
# Ищем хорошее место для разреза (точка или перенос строки)
if end < len(text):
last_period = text.rfind('.', start, end)
last_newline = text.rfind('\n\n', start, end)
best_break = max(last_period, last_newline)
if best_break > start:
end = best_break + 1
chunk_text = text[start:end].strip()
if chunk_text:
chunks.append({
'text': chunk_text,
'source': source,
'chunk_id': f"{source}_chunk_{chunk_number}"
})
chunk_number += 1
start = end - overlap
return chunks
# === ШАГ 3: СОЗДАНИЕ ВЕКТОРНОЙ БАЗЫ ===
def create_vector_store(chunks):
"""
Сохраняет чанки в ChromaDB.
ChromaDB сама превращает текст в векторы (embeddings).
"""
# Создаём клиент ChromaDB (локальный, в памяти)
client = chromadb.Client()
# Создаём коллекцию (аналог таблицы в обычной БД)
collection = client.create_collection(
name="my_documents",
metadata={"hnsw:space": "cosine"} # искать по косинусному сходству
)
# Добавляем все чанки в коллекцию
collection.add(
ids=[c['chunk_id'] for c in chunks], # уникальный ID каждого чанка
documents=[c['text'] for c in chunks], # текст (ChromaDB сама сделает embedding)
metadatas=[{'source': c['source']} for c in chunks] # откуда взят
)
print(f" Сохранено {len(chunks)} чанков в базу")
return collection
# === ШАГ 4: ПОИСК ===
def search(collection, query, n_results=3):
"""
Ищет чанки, похожие на запрос.
n_results: сколько фрагментов вернуть.
"""
results = collection.query(
query_texts=[query], # ChromaDB сама превратит запрос в вектор
n_results=n_results
)
found = []
for i in range(len(results['documents'][0])):
found.append({
'text': results['documents'][0][i],
'source': results['metadatas'][0][i]['source'],
'distance': results['distances'][0][i] # чем меньше — тем ближе
})
return found
# === ШАГ 5: ГЕНЕРАЦИЯ ОТВЕТА ===
def ask(collection, question):
"""
Полный цикл RAG:
1. Найти релевантные фрагменты
2. Отправить их + вопрос в Claude
3. Вернуть ответ
"""
# Поиск
results = search(collection, question)
# Собираем контекст из найденных фрагментов
context_parts = []
for r in results:
context_parts.append(
f"[Источник: {r['source']}]\n{r['text']}"
)
context = "\n\n---\n\n".join(context_parts)
# Генерация
claude = Anthropic()
response = claude.messages.create(
model="claude-haiku-4-5-20251001",
max_tokens=1024,
system=(
"Ты — ассистент, который отвечает на вопросы СТРОГО на основе "
"предоставленных документов. Если в документах нет ответа — "
"честно скажи: 'В предоставленных документах нет информации об этом.' "
"Указывай источник (имя файла), откуда взята информация."
),
messages=[{
"role": "user",
"content": f"""Вот фрагменты из документов:
<documents>
{context}
</documents>
Вопрос: {question}"""
}]
)
return response.content[0].text
# === ЗАПУСК ===
if __name__ == "__main__":
print("=== RAG-система ===\n")
# 1. Загружаем документы
print("Загрузка документов...")
docs = load_documents("my-docs")
# 2. Разбиваем на чанки
print("\nРазбивка на фрагменты...")
all_chunks = []
for doc in docs:
chunks = split_into_chunks(doc['text'], doc['source'])
all_chunks.extend(chunks)
print(f" Всего фрагментов: {len(all_chunks)}")
# 3. Создаём векторную базу
print("\nСоздание векторной базы...")
collection = create_vector_store(all_chunks)
# 4. Задаём вопросы
print("\n" + "="*50)
print("Готово! Задавай вопросы (введи 'выход' для завершения)")
print("="*50 + "\n")
while True:
question = input("Вопрос: ").strip()
if question.lower() in ('выход', 'exit', 'quit'):
break
if not question:
continue
print("\nПоиск и генерация ответа...\n")
answer = ask(collection, question)
print(f"Ответ: {answer}\n")
# 1. Убедись, что .env содержит ANTHROPIC_API_KEY
# 2. Создай папку my-docs/ с текстовыми файлами
# 3. Запусти:
python rag.py
Пример работы:
=== RAG-система ===
Загрузка документов...
Загружен: faq.txt (612 символов)
Загружен: rules.txt (445 символов)
Разбивка на фрагменты...
Всего фрагментов: 5
Создание векторной базы...
Сохранено 5 чанков в базу
==================================================
Готово! Задавай вопросы (введи 'выход' для завершения)
==================================================
Вопрос: Как вернуть товар?
Поиск и генерация ответа...
Ответ: Согласно документу faq.txt, возврат товара возможен в течение
14 дней с момента покупки. Для оформления возврата нужно обратиться
в поддержку по email support@example.com или по телефону 8-800-123-45-67.
Необходимо предоставить чек и товар в оригинальной упаковке.
client = chromadb.Client() # создаёт локальную базу (в оперативной памяти)
Client() без параметров — база живёт в памяти и исчезает при завершении. Для постоянного хранения:
# Постоянная база (сохраняется на диск)
client = chromadb.PersistentClient(path="./chroma_db")
collection = client.create_collection(name="my_documents")
Коллекция — как таблица в обычной базе данных (урок 4.2). Но вместо SQL-запросов — поиск по похожести.
collection.add(
ids=["doc1", "doc2"], # уникальные ID
documents=["текст 1", "текст 2"], # тексты (ChromaDB сама сделает embedding)
metadatas=[{"source": "file.txt"}, {"source": "file2.txt"}] # метаданные
)
add() — добавить документы. ChromaDB автоматически:
1. Превращает каждый текст в вектор (embedding)
2. Сохраняет вектор + текст + метаданные
results = collection.query(
query_texts=["мой вопрос"], # что ищем
n_results=3 # сколько результатов
)
query() — поиск. ChromaDB:
1. Превращает вопрос в вектор
2. Находит 3 ближайших вектора в базе
3. Возвращает их тексты, метаданные и расстояния
system = ("Ты — ассистент, который отвечает СТРОГО на основе "
"предоставленных документов...")
Ключевое слово — «СТРОГО на основе документов». Без этого Claude может добавить информацию из своих знаний, что нежелательно в RAG.
content = f"""<documents>{context}</documents>
Вопрос: {question}"""
XML-теги <documents> отделяют контекст от вопроса — рекомендация Anthropic из урока 4.5.
ChromaDB — не единственная векторная база. Вот обзор популярных:
Что тебе нужно?
│
├── Учёба, эксперименты, простые проекты
│ → ChromaDB (pip install, работает локально, бесплатно)
│
├── Продакшн, много данных (миллионы документов)
│ → Pinecone (облачный сервис, масштабируемый)
│ → Weaviate (self-hosted или облако)
│
├── Максимальная скорость, исследования
│ → FAISS (от Meta, только поиск, без хранения)
│
├── Уже используешь PostgreSQL
│ → pgvector (расширение для PostgreSQL)
│
└── Всё в одном (база + API + интерфейс)
→ Qdrant (Rust, быстрый, open-source)
Для этого курса ChromaDB — правильный выбор. Она покрывает задачи до десятков тысяч документов.
Симптом: Модель отвечает "В документах нет информации", хотя она там есть.
Причина: Чанки слишком большие или слишком маленькие.
Решение: Попробовать размер 300-500 символов с overlap 50.
Симптом: Модель находит фрагмент, но отвечает некорректно.
Причина: Фрагмент обрезан на середине мысли (chunking разрезал).
Решение: Увеличить overlap или использовать разбивку по параграфам.
Симптом: Каждый вопрос стоит дорого.
Причина: Отправляешь слишком много фрагментов в промпт.
Решение: Использовать n_results=3 (не 10), модель haiku, max_tokens=1024.
Симптом: Ответы основаны на старых данных.
Причина: Документы обновились, но в базе — старые чанки.
Решение: Пересоздавать базу при обновлении документов
(удалить коллекцию → загрузить заново).
┌──────────────────────────────────────────────────────────┐
│ ГДЕ ИСПОЛЬЗУЕТСЯ RAG │
│ │
│ Поддержка клиентов │
│ Бот отвечает по документации продукта. │
│ Не нужно обучать людей — бот знает всё. │
│ │
│ Внутренняя база знаний компании │
│ Сотрудник спрашивает: "Как оформить отпуск?" │
│ Бот находит ответ в HR-документах. │
│ │
│ Юридический поиск │
│ Юрист загружает 100 договоров → спрашивает: │
│ "В каких договорах есть штраф за просрочку?" │
│ │
│ Медицина │
│ Врач загружает справочники → спрашивает │
│ о взаимодействии препаратов. │
│ │
│ Образование │
│ Студент загружает учебник → задаёт вопросы │
│ по конкретным главам. │
└──────────────────────────────────────────────────────────┘
Создай папку my-docs/ с 2-3 текстовыми файлами на любую тему (правила компании, рецепты, описание продуктов). Запусти rag.py и задай несколько вопросов. Убедись, что ответы основаны на документах, а не на знаниях Claude.
Задай вопрос, ответа на который НЕТ в документах. Например, если в документах — правила магазина, спроси про погоду. Модель должна ответить «В документах нет информации». Если она придумывает ответ — усиль системный промпт.
Замени chromadb.Client() на chromadb.PersistentClient(path="./chroma_db"). Запусти программу дважды — убедись, что при втором запуске данные уже в базе (не нужно загружать заново). Подсказка: при втором запуске используй get_or_create_collection() вместо create_collection().
Задача 1: Что делает RAG?
Задача 2: Зачем разбивать документ на чанки?
Задача 3: Что такое embedding?
Задача 4: В чём разница между RAG и fine-tuning?
| Термин | Что значит |
|---|---|
| RAG | Retrieval-Augmented Generation — генерация ответа с предварительным поиском по своим данным |
| Embedding | Представление текста в виде вектора (списка чисел), где похожие тексты → близкие векторы |
| Вектор | Список чисел, представляющий смысл текста в «пространстве значений» |
| Chunk (чанк) | Фрагмент документа, на которые разбивается текст для индексации |
| Chunking | Процесс разбивки документа на чанки |
| Overlap (перекрытие) | Часть текста, общая между соседними чанками — защита от разрезания мысли |
| Векторная база данных | База, которая хранит векторы и умеет искать по похожести (ChromaDB, Pinecone, FAISS) |
| ChromaDB | Простая векторная база на Python — работает локально, бесплатная |
| Cosine similarity | Метрика похожести двух векторов — чем ближе к 1, тем более похожи тексты |
| Collection | Коллекция в ChromaDB — аналог таблицы в обычной базе данных |
| Fine-tuning | Дообучение модели на своих данных — данные «вшиваются» в модель |
| Knowledge cutoff | Дата, после которой модель не имеет данных (всё, что позже — не знает) |
| Галлюцинации | Когда модель уверенно генерирует неверную информацию |
| Retrieval | Этап поиска релевантных документов перед генерацией ответа |
Проблема:
LLM не знает твои данные (документы, базы, записи)
LLM может придумывать ответы (галлюцинации)
Решение — RAG:
1. Загрузить документы
2. Разбить на чанки (фрагменты по 300-500 символов)
3. Превратить в векторы (embeddings) → сохранить в ChromaDB
4. При вопросе: найти похожие чанки → отправить их + вопрос в Claude
5. Claude отвечает на основе найденных фрагментов
Ключевые принципы:
• Embedding = GPS-координаты для текста (похожие тексты → рядом)
• ChromaDB = простейшая векторная база (pip install, бесплатно)
• Системный промпт: "отвечай СТРОГО на основе документов"
• Чанки: 300-500 символов с overlap 50
RAG vs Fine-tuning:
RAG = подсказка на экзамене (данные в базе, обновляются мгновенно)
Fine-tuning = зубрёжка (данные в модели, обновляются дорого)
RAG работает с одним агентом — он ищет и отвечает. Но что если задача требует нескольких агентов? Один ищет, другой анализирует, третий пишет отчёт. В следующем уроке — мульти-агентные системы: как несколько AI-агентов работают вместе над сложными задачами.