В сентябре 2024 года Anthropic опубликовала технику Contextual Retrieval с обещанием снизить процент неудачных извлечений в RAG‑системах на 49% при использовании только embeddings и на 67% в сочетании с reranking. К середине 2026 года эта техника закрепилась как один из стандартов production‑RAG и применяется в подавляющем большинстве серьёзных корпоративных систем.
Разберём, в чём её суть, почему она вообще работает, как её реализовать и где она ломается.
Корневая проблема classical RAG, которую долго игнорировали
Стандартная схема RAG режет документ на чанки по 256–512 токенов, прогоняет каждый чанк через embedding‑модель, складывает векторы в базу. На запросе считается embedding вопроса, ищется топ‑K ближайших чанков по косинусной близости, найденные чанки склеиваются и подаются в промпт LLM как контекст.
В этой схеме есть глубинная проблема, которая не решается ни выбором лучшей embedding‑модели, ни тонкой настройкой размера чанка, ни добавлением overlap. Проблема в том, что чанк теряет контекст документа, к которому он относится.
Возьмём пример из квартального финансового отчёта компании. Где‑то на 47 странице есть параграф:
«Выручка компании в этом квартале выросла на 3% по сравнению с предыдущим кварталом. Северо‑американский регион показал рост в 12%, тогда как европейский снизился на 5%.»
При chunking этот параграф попадает в отдельный чанк, теряя всё, что было раньше в документе. Embedding модели для этого фрагмента будет похожа на embedding других параграфов про выручку из других кварталов, других компаний, других периодов. Когда пользователь спросит «какая выручка ACME в Q2 2024?», embedding‑поиск может вытащить любой из десятков чанков про выручку, и шансы попасть в правильный неочевидны.
Параметрические знания самой LLM здесь не помогают: модель не знает, какая компания, какой квартал, какой год. Метаданные документа теоретически могут помочь, но они хранятся отдельно от embedding и обычно используются только для фильтрации, не для ранжирования.
Эта проблема видна на любом корпусе, где документы тематически близки между собой. Юридические договоры все говорят про «стороны» и «обязательства», технические спецификации все говорят про «параметры» и «требования», финансовые отчёты все говорят про «выручку» и «расходы». Чанки из разных документов в таких корпусах легко перепутываются, и точность извлечения падает.
Идея Contextual Retrieval в одну фразу
Перед индексацией каждого чанка LLM генерирует короткий контекстный префикс (50–100 токенов), который объясняет, к какому документу и какой его части относится чанк. Префикс добавляется в начало чанка перед вычислением embedding и индексированием в BM25.
Тот же параграф из примера выше после обогащения выглядит так:
«Этот фрагмент взят из квартального финансового отчёта ACME Corporation за Q2 2024 года, раздел про региональные показатели выручки. Выручка компании в этом квартале выросла на 3% по сравнению с предыдущим кварталом. Североамериканский регион показал рост в 12%, тогда как европейский снизился на 5%».
Теперь embedding учитывает компанию, квартал, год, тип документа, раздел. Запрос «выручка ACME в Q2 2024» с большой вероятностью попадёт именно в этот чанк. BM25 тоже выигрывает: keyword‑поиск по «ACME» и «Q2 2024» теперь матчит с этим чанком, тогда как до обогащения никаких таких ключевых слов в чанке не было.
Бенчмарки Anthropic
В оригинальной публикации Anthropic приводит цифры на нескольких бенчмарках (Codebase, Common Resources, FinancialDocs, Knowledge Base):
-
Naive RAG только с embeddings: процент неудачных извлечений 5.7%.
-
С Contextual Embeddings (только embedding обогащённый): 3.7%, улучшение на 35%.
-
С Contextual Embeddings + Contextual BM25: 2.9%, улучшение на 49%.
-
С Contextual Embeddings + Contextual BM25 + Reranking: 1.9%, улучшение на 67%.
Воспроизведение на других корпусах в течение 2025–2026 годов показало похожие цифры: техника работает, эффект существенный, прирост качества стабильный. На некоторых специализированных корпусах (юридические документы, технические спецификации) эффект ещё больше — до 70–80% снижения failure rate.
Метрика, которую измеряет Anthropic, это retrieval failure rate: процент случаев, когда правильный документ не попал в топ-20 результатов поиска. Если правильный документ в топе, дальше его извлекает reranking или просто ставит на нужное место в финальном контексте. Если его в топе нет, никакие дальнейшие шаги не помогут.
Промпт для генерации контекста
В оригинальной публикации Anthropic использует следующий формат промпта:
<document>
{полный текст документа}
</document>
Вот чанк из этого документа:
<chunk>
{текст чанка}
</chunk>
Дай короткое описание (50-100 токенов) того, к чему относится этот чанк
в контексте всего документа. Описание должно помочь поиску правильно
классифицировать этот чанк. Отвечай только описанием, без преамбулы.
LLM получает весь документ целиком в качестве контекста и конкретный чанк, для которого нужно сгенерировать описание.
Описание должно содержать: тип документа, главную тему, конкретный раздел или подтему, к которому относится чанк, при наличии — конкретные сущности (компания, продукт, версия, период). Не должно содержать: пересказ чанка, выводы, оценки, ответы на гипотетические вопросы.
def generate_chunk_context(document: str, chunk: str, llm_client) -> str:
prompt = f"""<document>
{document}
</document>
Here is the chunk we want to situate within the whole document:
<chunk>
{chunk}
</chunk>
Please give a short succinct context (50-100 tokens) to situate this chunk
within the overall document for the purposes of improving search retrieval
of the chunk. Answer only with the succinct context and nothing else."""
response = llm_client.messages.create(
model="claude-haiku-4-5",
max_tokens=200,
messages=[{"role": "user", "content": prompt}]
)
return response.content[0].text.strip()
По моделькам хорошо работает Claude Haiku, потому что задача относительно простая, не требует креативности, а Haiku в 10–15 раз дешевле Sonnet или Opus. На бенчмарках разница в качестве между моделями для этой задачи минимальная.
Стоимость и оптимизация через prompt caching
Главная стоимость Contextual Retrieval — это вызов LLM для каждого чанка. Документ в 100 страниц при размере чанка 500 токенов даёт 50–100 чанков. На 10 тысяч документов это 500 тысяч — 1 миллион вызовов LLM. Без оптимизации стоимость становится заметной.
Anthropic в публикации указывает цифру: 1.02 доллара за миллион токенов с использованием Claude Haiku 3 и prompt caching. К 2026 году с Claude Haiku 4.5 цифра ещё ниже, около 0.50 доллара на тысячу чанков для типичных документов.
Prompt caching — это ключевая оптимизация. Без него каждый вызов LLM передаёт весь документ целиком, что для большого документа (200–1000 чанков) повторяется по 200–1000 раз. С prompt caching документ кэшируется на стороне модели, и при повторных вызовах для других чанков того же документа платится только за кэш‑чтение, что в 10 раз дешевле обычного input.
def generate_contexts_for_document(document: str, chunks: list[str], llm_client) -> list[str]:
contexts = []
for chunk in chunks:
response = llm_client.messages.create(
model="claude-haiku-4-5",
max_tokens=200,
messages=[
{
"role": "user",
"content": [
{
"type": "text",
"text": f"<document>n{document}n</document>",
"cache_control": {"type": "ephemeral"}
},
{
"type": "text",
"text": f"""Here is the chunk we want to situate:
<chunk>
{chunk}
</chunk>
Please give a short succinct context (50-100 tokens) to situate this chunk
within the document for retrieval purposes. Answer with the context only."""
}
]
}
]
)
contexts.append(response.content[0].text.strip())
return contexts
cache_control: {"type": "ephemeral"} указывает API кэшировать предыдущий контент на пять минут. При вызове для следующего чанка того же документа, LLM не пересчитывает токены документа, а читает их из кэша. Экономия драматическая.
Дополнительная оптимизация — batch API. Все вызовы для индексации можно отправить batch‑запросом, который выполняется асинхронно за 24 часа и стоит на 50% дешевле обычного. Для разовой индексации корпоративной базы знаний это идеальный вариант.
В итоге индексация корпуса в 10 тысяч документов с 50 чанками каждый (500 тысяч чанков) стоит порядка 200–300 долларов через Haiku 4.5 + batch + prompt caching. Это разовая затрата, дальше контекст переиспользуется на каждом запросе бесконечно.
Реализация end‑to‑end pipeline
Полная схема индексации с Contextual Retrieval выглядит примерно так.
import anthropic
from typing import Iterator
class ContextualRAGIndexer:
def __init__(self, anthropic_client, embedding_client, vector_store, bm25_store):
self.llm = anthropic_client
self.embedder = embedding_client
self.vectors = vector_store
self.bm25 = bm25_store
def chunk_document(self, document: str) -> list[str]:
# Любая стратегия chunking: sliding window, semantic, structural
# Здесь для примера простой sliding window
chunks = []
chunk_size = 500
overlap = 100
words = document.split()
for i in range(0, len(words), chunk_size - overlap):
chunks.append(" ".join(words[i:i + chunk_size]))
return chunks
def generate_context(self, document: str, chunk: str) -> str:
prompt = f"""<chunk>
{chunk}
</chunk>
Please give a short succinct context (50-100 tokens) to situate this chunk
within the overall document for the purposes of improving search retrieval.
Answer only with the succinct context and nothing else."""
response = self.llm.messages.create(
model="claude-haiku-4-5",
max_tokens=200,
messages=[{
"role": "user",
"content": [
{
"type": "text",
"text": f"<document>n{document}n</document>",
"cache_control": {"type": "ephemeral"}
},
{"type": "text", "text": prompt}
]
}]
)
return response.content[0].text.strip()
def index_document(self, doc_id: str, document: str, metadata: dict):
chunks = self.chunk_document(document)
for chunk_idx, chunk in enumerate(chunks):
context = self.generate_context(document, chunk)
enriched_chunk = f"{context}nn{chunk}"
embedding = self.embedder.embed(enriched_chunk)
chunk_id = f"{doc_id}_chunk_{chunk_idx}"
chunk_metadata = {
**metadata,
"doc_id": doc_id,
"chunk_idx": chunk_idx,
"original_chunk": chunk,
"context": context
}
self.vectors.upsert(chunk_id, embedding, chunk_metadata)
self.bm25.add(chunk_id, enriched_chunk, chunk_metadata)
При запросе извлекаются результаты из обеих систем (vectors + BM25), сливаются через Reciprocal Rank Fusion, опционально пропускаются через reranking, и финальный топ передаётся в LLM как контекст для генерации ответа.
При формировании финального промпта для генерации обычно используется поле original_chunk (без контекстного префикса), потому что префикс был нужен только для поиска, а в контексте генерации он избыточен и съедает токены. Метаданные с полем context сохраняются на случай, если они понадобятся для отображения пользователю.
Совместимость с reranking
Contextual Retrieval хорошо сочетается с cross‑encoder reranking. Логика двухэтапная: contextual hybrid retrieval достаёт топ-20-40 кандидатов с обогащёнными embeddings и BM25, потом reranker оценивает релевантность каждого кандидата к запросу и отдаёт топ-5-10 для финального контекста LLM.
Цифры из оригинальной публикации Anthropic показывают, что комбинация даёт мультипликативный эффект: каждая техника отдельно улучшает результаты, в сочетании они складываются почти линейно. Contextual embeddings + Contextual BM25 дают улучшение на 49%, добавление reranker поверх — улучшение до 67%.
В практической реализации reranking работает на чанках вместе с контекстом, потому что reranker тоже выигрывает от увиденного контекста. Чанк с фразой «выручка выросла на 3%» сам по себе мало значит для reranker, потому что не привязан к компании и периоду; с контекстом «отчёт ACME за Q2 2024» reranker точнее оценивает релевантность к конкретному запросу.
Когда Contextual Retrieval не работает или избыточен
Не каждая RAG‑система выигрывает от Contextual Retrieval, и для некоторых сценариев накладные расходы не окупаются.
-
Малые корпуса (меньше 1000 чанков). Если документов мало и они тематически различны, обычный embedding‑поиск с reranking работает достаточно хорошо. Затраты на индексацию контекстом не окупаются маргинальным улучшением.
-
Сильно структурированные документы. Если каждый чанк уже содержит явные сущности и контекст (например, JSON‑объекты с явными полями entity_type, entity_id, period), то контекст из него уже извлекается на этапе chunking без LLM. Contextual Retrieval тут дублирует то, что уже есть в данных.
-
Корпусы с очень высокой динамикой обновления. Если документы переписываются ежечасно, переиндексация с Contextual Retrieval становится постоянным расходом. Для таких систем обычно выгоднее построить специализированную retrieval‑стратегию.
-
Однотипные документы с очень похожими структурой. Например, тысячи product reviews одного формата. Контекст для каждого ревью получится одинаковым («это ревью продукта X пользователя Y от даты Z»), и его проще генерировать структурно из метаданных, чем через LLM.
-
Запросы, не требующие конкретного контекста документа. Если пользователи задают общие вопросы вроде «что такое OAuth» без привязки к конкретному документу, Contextual Retrieval может даже ухудшить результаты, потому что embedding обогащённого чанка дрифтует в сторону контекста (компания, продукт), а не в сторону самого предмета вопроса.
В этих случаях есть альтернативы: метаданные‑driven фильтрация перед поиском, hierarchical retrieval с двумя уровнями (сначала документ, потом чанк внутри), specialized retrievers для разных типов вопросов.
Что делать с обновлениями документов
Один из неочевидных аспектов Contextual Retrieval — что делать, когда документ обновляется. Контекст чанка генерировался на основе старой версии документа, и после редактирования может стать некорректным.
Несколько стратегий:
-
Полная переиндексация документа при любом изменении. Дорого, но простая логика и всегда актуальный контекст. Подходит для документов, которые меняются редко.
-
Инкрементальное обновление по изменённым чанкам. Если diff‑инструмент определяет, какие чанки изменились, контекст пересчитывается только для них. Сложнее в реализации, дешевле в эксплуатации.
-
Переиндексация только при значительных изменениях. Версионирование документа и переиндексация при major‑update, игнорирование minor‑edits. Компромисс: контекст немного устаревает, но индексация не идёт постоянно.
В большинстве случаев используется простая стратегия: полная переиндексация документа при любом изменении, плюс ночная задача, которая прогоняет переиндексацию всего корпуса раз в месяц для защиты от drift.
Базовая гигиена и чек‑лист внедрения
Минимальный план применения Contextual Retrieval в существующей RAG‑системе:
-
Проверить evaluation baseline. Без replicable metric нет смысла внедрять что‑либо: непонятно, помогло или нет. Если evaluation pipeline отсутствует, сначала собрать датасет из 100–200 вопрос‑ответ‑документ пар.
-
Реализовать генерацию контекстов через Claude Haiku 4.5 с prompt caching. На прототипе достаточно синхронной генерации, для production обязательно batch API.
-
Переиндексировать корпус с обогащёнными чанками. Сохранить оригинальные чанки и контексты отдельно в метаданных.
-
Прогнать evaluation на новой индексации. Сравнить Recall@K, MRR, NDCG со старой версией. Должно быть улучшение хотя бы на 20–30% по любой метрике; если меньше — что‑то не так с реализацией или корпусом.
-
Если результаты хорошие, добавить hybrid retrieval (BM25 + dense) на обогащённых чанках. Это даёт дополнительный буст.
-
Финальный шаг — добавить reranking через cross‑encoder. К этому моменту RAG‑система должна работать на 50–70% точнее, чем naive baseline.
-
Постоянный мониторинг качества на production‑трафике: процент запросов, на которых пользователь не нашёл правильный ответ, метрики через faithfulness и context precision, регулярное human evaluation на выборке.
После такой работы корпоративная RAG‑система с десятками тысяч документов становится действительно полезным инструментом для пользователей, а не источником постоянных жалоб на «ответил не про то». Стоимость внедрения окупается на первой же неделе использования через снижение нагрузки на службу поддержки и улучшение пользовательского опыта.
Если хотите разобрать RAG не только на уровне схемы, но и в прикладных сценариях, приходите на бесплатные уроки OTUS. Обсудим, как LLM-приложения работают с базами знаний, документами и разными типами данных — и где в таких системах начинаются реальные инженерные сложности.
-
6 июля в 20:00 — «Как сделать LLM приложение, которое отвечает клиентам по базе знаний компании». Записаться
Разберём, что такое LLM-app, как устроена база знаний для ИИ и как модель находит нужный ответ в документах. -
16 июля в 20:00 — «Мультимодальный RAG: как LLM работают с текстами, картинками и документами». Записаться
Поговорим о Retrieval-Augmented Generation и его мультимодальной версии: как LLM работают не только с текстом, но и с изображениями и документами.
Больше открытых уроков июля по ИИ, разработке и не только смотрите в дайджесте.
Что ещё почитать по теме RAG:
Автор: badcasedaily1
- Запись добавлена: 06.07.2026 в 11:15
- Оставлено в


