Архитектура RAG: индексирование и поиск
Разберите два этапа RAG: автономное индексирование, во время которого документы разбиваются на фрагменты, преобразуются в эмбеддинги и сохраняются, и онлайн-поиск релевантного контекста для каждого запроса.
«Архитектура RAG: индексирование и поиск» — бесплатный урок AI Engineering Academy на CoddyKit. Это урок 2 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения AI Engineering Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс AI Engineering Academy содержит 4 уроков всего.
В RAG есть два отдельных этапа
Система RAG работает в два принципиально разных этапа, выполняемых в разное время. На этапе автономной индексации Ваши документы обрабатываются один раз (или при их изменении), после чего подготавливается индекс для поиска. Этап онлайн-извлечения выполняется в реальном времени для каждого запроса пользователя. Понимание этого разделения необходимо для проектирования систем, которые быстро обрабатывают запросы и остаются удобными в сопровождении.
Этап индексации: шаг первый — загрузка
Индексация начинается с загрузки документов: чтения исходных файлов из Ваших исходных систем. Документы могут быть PDF-файлами, файлами Word, HTML-страницами, файлами Markdown, строками базы данных или любым другим текстовым источником. Каждый документ загружается в память как обычный текст, по возможности сохраняя структуру. Библиотеки вроде pypdf, python-docx и unstructured берут на себя основную работу по разбору конкретных форматов.
from pypdf import PdfReader
def load_pdf(path):
reader = PdfReader(path)
pages = []
for i, page in enumerate(reader.pages):
text = page.extract_text()
pages.append({'text': text, 'page': i + 1, 'source': path})
return pages
docs = load_pdf('company_policy.pdf')
print(f'Loaded {len(docs)} pages')Этап индексации: шаг второй — разбиение на фрагменты
Контекстные окна LLM имеют ограниченный размер, а извлечение документов целиком неэффективно. Загруженный текст разделяется на небольшие фрагменты размером примерно от 200 до 1000 токенов. Хорошее разбиение сохраняет смысловую целостность: фрагмент должен выражать законченную мысль. Распространённая стратегия использует перекрывающиеся окна, чтобы предложения рядом с границами фрагментов попадали в два фрагмента и информация не терялась в местах разбиения.
from langchain_text_splitters import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(
chunk_size=500, # characters per chunk
chunk_overlap=50, # overlap between chunks
separators=['\n\n', '\n', '. ', ' ']
)
for page in docs:
chunks = splitter.split_text(page['text'])
for chunk in chunks:
# Each chunk carries metadata from its source page
print(f'Chunk ({len(chunk)} chars): {chunk[:80]}...')Этап индексации: шаг третий — получение эмбеддингов
Каждый текстовый фрагмент преобразуется в плотный векторный эмбеддинг, который численно отражает его смысл. Для каждого фрагмента Вы вызываете модель эмбеддингов, например OpenAI text-embedding-3-small, и получаете массив чисел с плавающей точкой большой размерности. Фрагменты с похожим смыслом порождают близкие векторы в этом многомерном пространстве, что и делает возможным поиск по семантическому сходству.
from openai import OpenAI
client = OpenAI()
def embed_chunks(chunks):
texts = [c['text'] for c in chunks]
response = client.embeddings.create(
model='text-embedding-3-small',
input=texts
)
for i, chunk in enumerate(chunks):
chunk['embedding'] = response.data[i].embedding
return chunks
# Batch up to 2048 texts per API call
embedded = embed_chunks(all_chunks)Этап индексации: шаг четвёртый — сохранение
Фрагменты с эмбеддингами сохраняются в векторной базе данных вместе с метаданными (исходный файл, номер страницы, название раздела). Векторное хранилище создаёт структуру индекса (обычно HNSW), обеспечивающую быстрый приближённый поиск ближайших соседей. Этот индекс сохраняется на диске, поэтому переживает перезапуски. Индексация обычно выполняется один раз при настройке и постепенно обновляется при добавлении новых документов.
import pinecone
pc = pinecone.Pinecone(api_key='YOUR_KEY')
index = pc.Index('rag-documents')
# Upsert vectors with metadata
vectors_to_upsert = [
(
chunk['id'],
chunk['embedding'],
{'text': chunk['text'], 'source': chunk['source'], 'page': chunk['page']}
)
for chunk in embedded_chunks
]
# Upsert in batches of 100
for i in range(0, len(vectors_to_upsert), 100):
index.upsert(vectors=vectors_to_upsert[i:i+100])
print('Indexing complete')Этап извлечения: получение эмбеддинга запроса
Когда пользователь отправляет запрос, начинается этап онлайн-извлечения. Первый шаг — получить эмбеддинг вопроса пользователя с помощью той же модели эмбеддингов, которая использовалась при индексации. Это критически важно: если для индексации использовалась text-embedding-3-small, то и для запроса необходимо использовать text-embedding-3-small. Эмбеддинг запроса — это вектор, кодирующий смысл того, о чём спрашивает пользователь.
def embed_query(question):
response = client.embeddings.create(
model='text-embedding-3-small', # MUST match indexing model
input=[question]
)
return response.data[0].embedding
user_question = 'What is our parental leave policy?'
query_vector = embed_query(user_question)
print(f'Query embedded: {len(query_vector)}-dim vector')Этап извлечения: поиск ANN
Эмбеддинг запроса отправляется в векторное хранилище, которое выполняет приближённый поиск ближайших соседей (ANN), чтобы найти K фрагментов с эмбеддингами, наиболее похожими на вектор запроса. Этот поиск выполняется чрезвычайно быстро (обычно менее чем за 10 мс), поскольку индексы HNSW жертвуют небольшой частью полноты поиска ради огромного прироста скорости по сравнению с полным перебором. Вы извлекаете K лучших фрагментов — обычно от K=5 до K=20.
results = index.query(
vector=query_vector,
top_k=5,
include_metadata=True
)
print(f'Retrieved {len(results.matches)} chunks:')
for match in results.matches:
print(f' Score: {match.score:.3f} | Source: {match.metadata["source"]}')
print(f' Text: {match.metadata["text"][:100]}...')
print()Связь между двумя этапами
Главная идея состоит в том, что индексация и извлечение проектируются для совместной работы. Модель эмбеддингов должна быть одинаковой на обоих этапах, поскольку математическое пространство, в котором находятся векторы, зависит от модели. Если Вы смените модель эмбеддингов, необходимо переиндексировать все документы. Векторное хранилище служит связующим звеном: во время индексации оно принимает векторы, а во время извлечения возвращает их, разделяя два этапа во времени, но сохраняя их согласованность в векторном пространстве.
Фильтрация метаданных при извлечении
Векторный поиск находит семантически похожие фрагменты, но иногда также требуется фильтровать по метаданным. Например, извлекать фрагменты только из документов, загруженных в 2025 году, или только из папки отдела HR. Векторные хранилища поддерживают предварительную или последующую фильтрацию по полям метаданных. Предварительная фильтрация (поддерживаемая Pinecone и Qdrant) применяет фильтр до поиска ANN, поэтому она быстрее и точнее последующей фильтрации результатов K лучших совпадений.
# Retrieve only from HR department documents
results = index.query(
vector=query_vector,
top_k=5,
filter={'department': {'$eq': 'HR'}},
include_metadata=True
)
# Or filter by date range
results = index.query(
vector=query_vector,
top_k=5,
filter={
'upload_year': {'$gte': 2024},
'doc_type': {'$eq': 'policy'}
},
include_metadata=True
)Инкрементальная индексация обновлений
В рабочей системе корпус документов со временем меняется. Эффективная архитектура индексации поддерживает инкрементальные обновления: когда документ редактируется, удалите его существующие векторы по ID и добавьте новые с помощью upsert. При удалении документов удаляйте и их векторы. Присвойте каждому фрагменту детерминированный ID на основе пути к исходному документу и позиции фрагмента, чтобы всегда находить и обновлять нужные векторы без полной переиндексации.
import hashlib
def make_chunk_id(source_path, chunk_index):
# Deterministic, stable ID for each chunk
key = f'{source_path}::chunk_{chunk_index}'
return hashlib.md5(key.encode()).hexdigest()
def update_document(source_path, index):
# Delete old vectors for this document
index.delete(filter={'source': source_path})
# Re-index the updated document
new_chunks = load_and_chunk(source_path)
new_embedded = embed_chunks(new_chunks)
index.upsert(vectors=new_embedded)
print(f'Updated {source_path}: {len(new_chunks)} chunks')Полный конвейер RAG вкратце
Полная архитектура RAG выглядит так: автономный режим: документы → загрузчик → средство разбиения на фрагменты → модель эмбеддингов → векторное хранилище. Онлайн-режим: запрос пользователя → модель эмбеддингов → векторное хранилище (поиск ANN) → K лучших фрагментов → сборка запроса → LLM → ответ. Автономный конвейер запускается один раз при каждом обновлении документа. Онлайн-конвейер выполняется за миллисекунды для каждого запроса пользователя, при этом LLM видит только релевантный контекст, а не весь корпус документов.
Быстрая проверка
Проверьте, насколько хорошо Вы усвоили концепции разработки ИИ из этого урока.
Итоги урока
В этом уроке Вы узнали: об этапе автономной индексации, состоящем из шагов загрузки, разбиения на фрагменты, получения эмбеддингов и сохранения, которые выполняются один раз для каждого документа; об этапе онлайн-извлечения, на котором для запроса получают эмбеддинг, выполняют поиск ANN и за миллисекунды возвращают K лучших фрагментов; а также о стратегиях инкрементальной индексации, позволяющих поддерживать векторное хранилище в актуальном состоянии при изменении документов. Далее мы рассмотрим, как составлять эффективные расширенные запросы, грамотно использующие извлечённый контекст.
Изучай Python с ИИ-репетитором — бесплатно
Пиши и запускай код прямо в браузере, получай мгновенную помощь от ИИ-репетитора 24/7 и продолжи учиться на сайте или в приложении.
- Курсы
- 30
- Уроки
- 120
Часто задаваемые вопросы
Урок «Архитектура RAG: индексирование и поиск» бесплатный?
Да — полный текст урока «Архитектура RAG: индексирование и поиск» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс AI Engineering Academy, подпишись на CoddyKit PRO. Курс AI Engineering Academy содержит 4 уроков всего.
Чему я научусь в уроке «Архитектура RAG: индексирование и поиск»?
Разберите два этапа RAG: автономное индексирование, во время которого документы разбиваются на фрагменты, преобразуются в эмбеддинги и сохраняются, и онлайн-поиск релевантного контекста для каждого з… Ты практикуешь AI Engineering Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать AI Engineering Academy?
Предыдущий опыт не требуется. AI Engineering Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 2 из 4.
Сколько времени занимает урок «Архитектура RAG: индексирование и поиск»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке AI Engineering Academy?
Да. Каждый урок AI Engineering Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Какую проблему решает RAG
- Архитектура RAG: индексирование и поиск
- Составление дополненного запроса
- RAG или дообучение: что выбрать