- BrainTools - https://www.braintools.ru -

Contextual Retrieval: техника, которая чинит главную проблему RAG за 50 центов на тысячу чанков

В сентябре 2024 года Anthropic опубликовала технику Contextual Retrieval с обещанием снизить процент неудачных извлечений в RAG‑системах на 49% при использовании только embeddings и на 67% в сочетании с reranking. К середине 2026 года эта техника закрепилась как один из стандартов production‑RAG и применяется в подавляющем большинстве серьёзных корпоративных систем.

Разберём, в чём её суть, почему она вообще работает, как её реализовать и где она ломается.

Корневая проблема classical RAG, которую долго игнорировали

Стандартная схема RAG режет документ на чанки по 256–512 токенов, прогоняет каждый чанк через embedding‑модель, складывает векторы в базу. На запросе считается embedding вопроса, ищется топ‑K ближайших чанков по косинусной близости, найденные чанки склеиваются и подаются в промпт LLM [1] как контекст.

В этой схеме есть глубинная проблема, которая не решается ни выбором лучшей 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. Логика [2] двухэтапная: 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, и для некоторых сценариев накладные расходы не окупаются.

  1. Малые корпуса (меньше 1000 чанков). Если документов мало и они тематически различны, обычный embedding‑поиск с reranking работает достаточно хорошо. Затраты на индексацию контекстом не окупаются маргинальным улучшением.

  2. Сильно структурированные документы. Если каждый чанк уже содержит явные сущности и контекст (например, JSON‑объекты с явными полями entity_type, entity_id, period), то контекст из него уже извлекается на этапе chunking без LLM. Contextual Retrieval тут дублирует то, что уже есть в данных.

  3. Корпусы с очень высокой динамикой обновления. Если документы переписываются ежечасно, переиндексация с Contextual Retrieval становится постоянным расходом. Для таких систем обычно выгоднее построить специализированную retrieval‑стратегию.

  4. Однотипные документы с очень похожими структурой. Например, тысячи product reviews одного формата. Контекст для каждого ревью получится одинаковым («это ревью продукта X пользователя Y от даты Z»), и его проще генерировать структурно из метаданных, чем через LLM.

  5. Запросы, не требующие конкретного контекста документа. Если пользователи задают общие вопросы вроде «что такое OAuth» без привязки к конкретному документу, Contextual Retrieval может даже ухудшить результаты, потому что embedding обогащённого чанка дрифтует в сторону контекста (компания, продукт), а не в сторону самого предмета вопроса.

В этих случаях есть альтернативы: метаданные‑driven фильтрация перед поиском, hierarchical retrieval с двумя уровнями (сначала документ, потом чанк внутри), specialized retrievers для разных типов вопросов.

Что делать с обновлениями документов

Один из неочевидных аспектов Contextual Retrieval — что делать, когда документ обновляется. Контекст чанка генерировался на основе старой версии документа, и после редактирования может стать некорректным.

Несколько стратегий:

  1. Полная переиндексация документа при любом изменении. Дорого, но простая логика и всегда актуальный контекст. Подходит для документов, которые меняются редко.

  2. Инкрементальное обновление по изменённым чанкам. Если diff‑инструмент определяет, какие чанки изменились, контекст пересчитывается только для них. Сложнее в реализации, дешевле в эксплуатации.

  3. Переиндексация только при значительных изменениях. Версионирование документа и переиндексация при major‑update, игнорирование minor‑edits. Компромисс: контекст немного устаревает, но индексация не идёт постоянно.

В большинстве случаев используется простая стратегия: полная переиндексация документа при любом изменении, плюс ночная задача, которая прогоняет переиндексацию всего корпуса раз в месяц для защиты от drift.

Базовая гигиена и чек‑лист внедрения

Минимальный план применения Contextual Retrieval в существующей RAG‑системе:

  1. Проверить evaluation baseline. Без replicable metric нет смысла внедрять что‑либо: непонятно, помогло или нет. Если evaluation pipeline отсутствует, сначала собрать датасет из 100–200 вопрос‑ответ‑документ пар.

  2. Реализовать генерацию контекстов через Claude Haiku 4.5 с prompt caching. На прототипе достаточно синхронной генерации, для production обязательно batch API.

  3. Переиндексировать корпус с обогащёнными чанками. Сохранить оригинальные чанки и контексты отдельно в метаданных.

  4. Прогнать evaluation на новой индексации. Сравнить Recall@K, MRR, NDCG со старой версией. Должно быть улучшение хотя бы на 20–30% по любой метрике; если меньше — что‑то не так с реализацией или корпусом.

  5. Если результаты хорошие, добавить hybrid retrieval (BM25 + dense) на обогащённых чанках. Это даёт дополнительный буст.

  6. Финальный шаг — добавить reranking через cross‑encoder. К этому моменту RAG‑система должна работать на 50–70% точнее, чем naive baseline.

  7. Постоянный мониторинг качества на production‑трафике: процент запросов, на которых пользователь не нашёл правильный ответ, метрики через faithfulness и context precision, регулярное human evaluation на выборке.

После такой работы корпоративная RAG‑система с десятками тысяч документов становится действительно полезным инструментом для пользователей, а не источником постоянных жалоб на «ответил не про то». Стоимость внедрения окупается на первой же неделе использования через снижение нагрузки на службу поддержки и улучшение пользовательского опыта [3].


Если хотите разобрать RAG не только на уровне схемы, но и в прикладных сценариях, приходите на бесплатные уроки OTUS. Обсудим, как LLM-приложения работают с базами знаний, документами и разными типами данных — и где в таких системах начинаются реальные инженерные сложности.

  • 6 июля в 20:00 — «Как сделать LLM приложение, которое отвечает клиентам по базе знаний компании». Записаться [4]
    Разберём, что такое LLM-app, как устроена база знаний для ИИ и как модель находит нужный ответ в документах.

  • 16 июля в 20:00 — «Мультимодальный RAG: как LLM работают с текстами, картинками и документами». Записаться [5]
    Поговорим о Retrieval-Augmented Generation и его мультимодальной версии: как LLM работают не только с текстом, но и с изображениями и документами.

Больше открытых уроков июля по ИИ, разработке и не только смотрите в дайджесте. [6]

Что ещё почитать по теме RAG:

Автор: badcasedaily1

Источник [9]


Сайт-источник BrainTools: https://www.braintools.ru

Путь до страницы источника: https://www.braintools.ru/article/32699

URLs in this post:

[1] LLM: https://otus.pw/qdLz/

[2] Логика: http://www.braintools.ru/article/7640

[3] опыта: http://www.braintools.ru/article/6952

[4] Записаться: https://otus.pw/BtNI/

[5] Записаться: https://otus.pw/kF8s/

[6] в дайджесте.: https://otus.pw/89kk/

[7] Возвращение RAG в 2026 году.: https://otus.pw/xSSS/

[8] RAG для тех, кто разочаровался: почему retrieval ломается и как это починить.: https://otus.pw/hvme/

[9] Источник: https://habr.com/ru/companies/otus/articles/1054594/?utm_source=habrahabr&utm_medium=rss&utm_campaign=1054594

www.BrainTools.ru

Rambler's Top100