Logo
Overview

Контекст-инжиниринг для LLM: как готовить контекст и чанкинг

September 9, 2026
9 min read

Вы настроили RAG, проиндексировали все ADR и требования. Открываете чат, задаёте вопрос: «Какие решения по аутентификации приняты за последний год?» Модель задумывается и выдаёт два абзаца про платёжный шлюз. Вы перечитываете — к аутентификации это не имеет никакого отношения. Переформулируете запрос. Модель выдаёт то же самое, но другими словами.

Проблема не в retrieval и не в embedding-модели. Проблема в том, что именно попало в контекст модели и в каком порядке. Вы настроили поиск, но не настроили контекст. А именно он решает, будет ли LLM опираться на факты или гадать на кофейной гуще.

Что такое контекст-инжиниринг

Контекст-инжиниринг — это дисциплина проектирования того, что модель «видит» перед генерацией ответа. Это не промпт-инжиниринг (как сформулировать задачу) и не retrieval (как найти документы). Это про то, что делать с найденными документами перед тем, как скормить их LLM.

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

Компонентов у этой дисциплины несколько:

  • Чанкинг — как нарезать документы при индексации
  • Бюджетирование — сколько чанков влезает в окно модели
  • Ранжирование и фильтрация — какие из найденных чанков действительно важны
  • Сборка — в каком порядке и с какими инструкциями подавать контекст модели

Разберём каждый.

Окно контекста: что это и почему вы его не используете

Каждая LLM имеет лимит на объём входных данных — контекстное окно. GPT-4o — 128K токенов, Claude 3.5 Sonnet — 200K, Gemini 1.5 Pro — до 1M. На бумаге — залей весь Confluence и спрашивай. На практике модель теряет внимание к середине контекста, а стоимость запроса растёт экспоненциально.

128K токенов — это примерно 300 страниц текста. Вы не хотите отправлять 300 страниц в каждом запросе. Во-первых, это доллар за вызов. Во-вторых, модель просто не удержит всё в фокусе. Контекст-инжиниринг начинается с простого вопроса: сколько токенов вы реально готовы потратить на контекст и что туда положить.

Для большинства аналитических задач разумный бюджет — 4–8K токенов на контекст. Это примерно 10–20 страниц А4. Остальное окно уходит на системный промпт, историю диалога и сам ответ.

Чанкинг: как нарезать, чтобы не потерять смысл

Чанк — это атомарная единица контекста. Кусок документа, который будет найден retrieval-движком и передан модели. Качество чанкинга определяет качество всего RAG-пайплайна сильнее, чем выбор embedding-модели. Я проверял: с плохим чанкингом даже text-embedding-3-large даёт мусор, а с хорошим — text-embedding-3-small работает достойно.

Вот как выглядит разница на практике: документ из 1200 токенов разрезан тремя разными способами, и запрос попадает в границу между чанками.

100%
graph LR
  Doc["Документ из 1200 токенов"]
  Doc --> C512["Чанк A: 512 токенов"]
  Doc --> C512b["Чанк B: 512 токенов"]
  Doc --> C176["Чанк C: 176 токенов"]

  C512 --> Overlap["Overlap 128 токенов"]
  C512b --> Overlap

  Query["Запрос: Как устроена аутентификация в платёжном шлюзе"]

  C512 --> Lose["Потеря смысла: чанк C обрезан и не содержит ответ"]
  C176 --> Lose

  Overlap --> Keep["Сохранение связности: overlap не даёт разорвать предложение на границе"]

  style Doc fill:#4a90d9,stroke:#2c5f8a,color:#fff
  style C512 fill:#f0a500,stroke:#c88400,color:#fff
  style C512b fill:#f0a500,stroke:#c88400,color:#fff
  style C176 fill:#e0e0e0,stroke:#999,color:#333
  style Overlap fill:#50c878,stroke:#3a9a5c,color:#fff
  style Query fill:#7b68ee,stroke:#5a4db2,color:#fff
  style Keep fill:#50c878,stroke:#3a9a5c,color:#fff
  style Lose fill:#e74c3c,stroke:#c0392b,color:#fff

На диаграмме видно: без overlap-а граница между чанками может пройти прямо посередине нужного предложения. Чанк A содержит первую половину ответа, чанк B — вторую, а retrieval возвращает только один из них. Overlap в 128 токенов решает эту проблему: соседние чанки перекрываются, и нужный фрагмент гарантированно попадёт в контекст целиком.

Какие стратегии чанкинга работают

СтратегияРазмерOverlapКогда братьПодводный камень
Fixed-size256–51264–128Прототип, первая итерацияРвёт по границе токенов вообще без учёта смысла
Semantic (абзацы/секции)200–8000–128Структурированные документы: ADR, ТЗТребует, чтобы документы были хорошо структурированы изначально
Recursive (по разделителям)400–800100–200Универсальный вариант для productionПриоритет разделителей: абзац > предложение > слово
AgenticДинамическийНетСложные документы с таблицами и кодомLLM решает, где резать — медленно и дорого
Small-to-big256 (индекс) + 1024 (контекст)НетДокументы с контекстно-зависимой семантикойВ индекс идёт маленький чанк, в контекст — родительский блок

Для базы знаний аналитика recursive — золотая середина. LangChain и LlamaIndex делают это из коробки: вы задаёте размер чанка (допустим, 600 токенов) и список разделителей (\n\n, \n, . , ). Библиотека пытается разрезать по двойному переносу строки. Если не влезает — по одинарному. Если всё равно не влезает — по точке с пробелом. Так смысловые границы сохраняются максимально долго.

Overlap: почему без него нельзя

Overlap — это перекрытие соседних чанков. Стандарт: 10–20% от размера чанка. Для 512-токенного чанка — overlap в 64–128 токенов.

Зачем он нужен. Представьте ADR, где решение сформулировано в трёх предложениях. Первое попало в чанк №5, второе и третье — в чанк №6. Retrieval по запросу возвращает чанк №5 (там ключевые слова), но самого решения там нет — только заход. Если overlap отсутствует, модель получает оборванный контекст и достраивает ответ из воздуха. С overlap-ом в 128 токенов второе и третье предложения дублируются в обоих чанках, и retrieval вернёт осмысленный фрагмент.

Цена overlap-а — рост объёма индекса на те же 10–20%. Плюс при retrieval-е могут вернуться два почти идентичных чанка. Это решается на этапе сборки контекста дедупликацией — об этом ниже.

Бюджет контекста: что класть в промпт, а что выкинуть

Допустим, retrieval вернул 10 чанков по 512 токенов. 5120 токенов. Плюс системный промпт на 1500 токенов. Плюс история диалога на 800 токенов. Плюс сам запрос на 200 токенов. Итого 7620 токенов — в бюджет влезает, но половина чанков нерелевантна.

Проблема retrieval-движка в том, что он ищет похожие векторы, а не полезные. Косинусное расстояние между эмбеддингами запроса и чанка не равно релевантности. Чанк может быть семантически близок, но не содержать ответа. А чанк с ответом может отстоять дальше по вектору, потому что сформулирован другими словами.

Что с этим делать:

  • Не берите K=10 «на всякий случай». Берите K=20, но прогоняйте через re-ranker (cross-encoder вроде Cohere Rerank или BGE-Reranker), который оценивает релевантность каждого чанка именно этому запросу. Из 20 оставляйте 3–5 лучших. Эти 3–5 чанков дадут лучшее качество, чем 10 случайно отобранных по косинусу.
  • Фильтруйте по метаданным до retrieval-а. Если в базе лежат ADR за пять лет, а вам нужны только принятые решения за последний год — фильтруйте на уровне векторной БД. PGVector и Qdrant умеют WHERE status = 'accepted' AND date >= '2025-01-01' до поиска ближайших векторов. Это сокращает пространство поиска и убирает заведомо нерелевантные документы.
  • Удаляйте дубликаты перед подачей. Два чанка с перекрытием в 80% не несут новой информации. Сравнивайте чанки попарно (Jaccard-схожесть по токенам) и оставляйте один.
  • Суммаризуйте длинные чанки. Если чанк занимает 800 токенов, а его суть — в одном абзаце на 100 токенов, имеет смысл прогнать его через легковесную LLM с инструкцией «выдели ключевые факты» перед тем, как положить в контекст основной модели.

Потеря середины: почему LLM забывает то, что в центре

Это известный баг всех GPT-подобных архитектур: модель хорошо помнит начало и конец контекста, но проваливает середину. Исследование Liu et al. (2023) «Lost in the Middle» показало: accuracy на вопросах по документу падает с ~80% (начало) до ~50% (середина) и поднимается обратно к ~80% (конец). График напоминает U-образную кривую.

Для контекст-инжиниринга из этого следует два правила:

  1. Самый важный чанк кладите в начало или в конец. Не в середину. Если retrieval вернул три чанка и один из них — ключевой ADR, ставьте его первым.

  2. Не пытайтесь запихнуть «всё и сразу». Три осмысленных чанка в начале контекста дают лучший результат, чем пятнадцать размазанных по всему окну. Модель удержит три, потеряет пятнадцать.

Это не значит, что большие окна бесполезны. Они полезны для суммаризации длинных документов, где модель читает последовательно. Но для retrieval-ных сценариев, где ответ распределён по разным кускам контекста, большой объём вредит.

Сборка контекста: порядок имеет значение

Последний этап перед отправкой в LLM — сборка. Вы берёте отфильтрованные и отранжированные чанки, выстраиваете их в правильном порядке и добавляете инструкцию. Не «вот пять кусков текста, ответь», а «вот три документа, в каждом — часть ответа на твой вопрос».

Полный пайплайн контекст-инжиниринга выглядит так:

100%
flowchart TD
  Sources["Источники данных: ТЗ, ADR, переписка"]
  UserQuery["Запрос аналитика"]

  Sources --> Extraction["Извлечение и предобработка"]
  Extraction --> Chunking["Чанкинг: разбиение на фрагменты"]
  Chunking --> Embed["Векторизация чанков: embedding-модель"]
  Embed --> Index["Индекс: векторная БД PGVector"]

  UserQuery --> QueryEmbed["Векторизация запроса"]
  QueryEmbed --> Index
  Index --> Retrieval["Retrieval: поиск top-K релевантных чанков"]
  Retrieval --> Ranking["Ранжирование: cross-encoder реранкер"]
  Ranking --> Compression["Сжатие: удаление дублей и суммаризация"]
  Compression --> Assembly["Сборка контекста: чанки плюс инструкция"]
  Assembly --> Budget["Проверка бюджета: помещается ли в контекстное окно"]
  Budget --> LLM["LLM: генерация ответа с опорой на контекст"]
  LLM --> Response["Результат со ссылками на источники"]

  style Sources fill:#4a90d9,stroke:#2c5f8a,color:#fff
  style Extraction fill:#4a90d9,stroke:#2c5f8a,color:#fff
  style Chunking fill:#f0a500,stroke:#c88400,color:#fff
  style Embed fill:#7b68ee,stroke:#5a4db2,color:#fff
  style Index fill:#7b68ee,stroke:#5a4db2,color:#fff
  style UserQuery fill:#50c878,stroke:#3a9a5c,color:#fff
  style QueryEmbed fill:#50c878,stroke:#3a9a5c,color:#fff
  style Retrieval fill:#f0a500,stroke:#c88400,color:#fff
  style Ranking fill:#f0a500,stroke:#c88400,color:#fff
  style Compression fill:#e0e0e0,stroke:#999,color:#333
  style Assembly fill:#e0e0e0,stroke:#999,color:#333
  style Budget fill:#e0e0e0,stroke:#999,color:#333
  style LLM fill:#4a90d9,stroke:#2c5f8a,color:#fff
  style Response fill:#50c878,stroke:#3a9a5c,color:#fff

Диаграмма показывает две ветки. Верхняя (синяя) — офлайн-индексация: документы извлекаются, нарезаются на чанки, векторизуются и попадают в векторную БД. Нижняя (зелёная) — онлайн-обработка запроса: запрос векторизуется, retrieval находит K ближайших чанков, re-ranker отбирает действительно релевантные, компрессор убирает дубли, сборщик формирует контекст с инструкцией, а проверка бюджета следит, чтобы всё влезло в окно. Только после этого контекст попадает в LLM.

Практический пример сборки для запроса «Какие требования к отказоустойчивости платёжного шлюза?»:

[ИНСТРУКЦИЯ]
Ниже три документа, извлечённых из базы знаний проекта.
Ответь на вопрос аналитика, опираясь только на эти документы.
Если в документах нет ответа — напиши «не найдено»,
не придумывай. Для каждого утверждения указывай источник
(ADR-XXX, требование ФТ-YYY).
[ДОКУМЕНТ 1 — ADR-003: Архитектура платёжного шлюза]
... чанк из ADR-003 про отказоустойчивость ...
[ДОКУМЕНТ 2 — НФТ-014: Требования к платёжному шлюзу]
... чанк из спецификации НФТ ...
[ДОКУМЕНТ 3 — Протокол встречи 2025-11-15]
... чанк из протокола, где обсуждалась деградация сервиса ...
[ВОПРОС]
Какие требования к отказоустойчивости платёжного шлюза?

Обратите внимание: инструкция запрещает галлюцинации явно («не придумывай») и требует ссылок на источники. Это не просто вежливость — это меняет поведение модели на уровне логитов. Чанки упорядочены по релевантности: самый важный первым, наименее — последним.

Где контекст-инжиниринг не поможет

Несколько сценариев, где даже идеально собранный контекст не вытянет:

  • Документы написаны плохо. Если ADR состоит из «решили сделать хорошо» — модель не извлечёт из этого требований к отказоустойчивости. Качество контекста упирается в качество исходников.

  • Ответа действительно нет в документах. Контекст-инжиниринг не создаёт новое знание — он помогает модели извлечь существующее. Если в базе нет требований к безопасности — модель не придумает их (если не галлюцинирует, но это не фича).

  • Вопрос требует вычислений, а не поиска. LLM не калькулятор. Задача «посчитай среднее время ответа по 500 логам» — это задача для кода, а не для контекст-инжиниринга.

  • Контекст радикально противоречив. Если один ADR говорит «используем синхронные запросы», а другой — «все запросы асинхронные», модель может запутаться. Тут нужен не контекст-инжиниринг, а разрешение противоречий на уровне документации.

Резюме в двух словах

Контекст-инжиниринг — это работа с тем, что модель «видит», а не с тем, как она «думает». Если промпт-инжиниринг управляет рассуждением, то контекст-инжиниринг управляет фактами, на которых это рассуждение строится. Без него даже идеальный CoT-промпт даст ответ, основанный на трёх случайных чанках из середины контекстного окна. С ним — ответ, привязанный к реальной документации проекта.

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