English translation is not available yet. Showing Russian content.
Как вы управляете контекстным окном (context window) для длинных диалогов?
Краткий тезис
Управление контекстным окном — это набор стратегий, позволяющих удерживать полезную информацию из длинной истории диалога, не превышая лимиты токенов LLM. Основные подходы: sliding window (храним последние N токенов), summarization (сжимаем историю в саммари), selective pruning (удаляем менее важные части) и semantic compression (сокращаем сообщения через специальные промпты). На практике для production-систем лучше всего работает комбинация: суммаризация каждые 10 сообщений с сохранением последних нескольких реплик в исходном виде.
1. Термин: Контекстное окно (context window)
Что это Максимальное количество токенов, которое LLM может обработать за один вызов (например, 8192 для GPT-4, 128k для Claude 3, 1M для Gemini 1.5 Pro). В диалогах каждое новое сообщение пользователя и ответ ассистента добавляют токены в историю, что может привести к превышению лимита.
Почему это важно для production
- Если контекстное окно переполняется, LLM либо обрезает историю (обычно с середины — проблема lost in the middle), либо выпадает в ошибку.
- Без управления окном длинные диалоги теряют связность: модель забывает, что было сказано 20 реплик назад.
- Оптимизация контекста напрямую влияет на латентность (больше токенов → дольше генерация) и стоимость (токены оплачиваются).
2. Sliding window (скользящее окно)
Идея хранить только последние N токенов (или последние K сообщений) диалога. Всё, что раньше, отбрасывается.
Реализация
MAX_TOKENS = 4096
messages = []
def add_message(user_msg: str, assistant_msg: str):
messages.append({"role": "user", "content": user_msg})
messages.append({"role": "assistant", "content": assistant_msg})
# подсчёт токенов (упрощённо — по длине строки)
total_tokens = sum(len(m["content"]) for m in messages)
while total_tokens > MAX_TOKENS:
removed = messages.pop(0) # удаляем самое старое сообщение
total_tokens -= len(removed["content"])
Плюсы
- Простота реализации.
- Никаких дополнительных вызовов LLM.
- Низкая задержка.
Минусы
- Полная потеря раннего контекста.
- Если важная информация была в начале диалога, она исчезает навсегда.
3. Summarization (суммаризация)
Идея каждые M сообщений (или когда общее количество токенов превышает порог) запускаем LLM, чтобы сжать историю в краткое саммари. Саммари помещается в начало контекста, а последние сообщения остаются в исходном виде.
Пример промпта для суммаризации
Ты — ассистент, который сжимает историю диалога в краткое саммари.
Сохрани все ключевые факты, предпочтения пользователя, принятые решения.
Формат: одно-два предложения.
История: {old_messages}
Саммари:
Схема работы
- Держим два буфера: long-term (саммари) и short-term (последние L сообщений).
- При добавлении каждого сообщения проверяем количество токенов.
- Если превышен порог — запускаем суммаризацию буфера short-term (или объединяем со старым саммари), записываем новое саммари в long-term, а short-term очищаем.
- В итоговый контекст LLM отправляем: [long-term summary] + [последние N сообщений из short-term].
Плюсы
- Сохраняется суть всего диалога.
- Гибкость — можно настраивать частоту суммаризации.
Минусы
- Дополнительные вызовы LLM (стоимость, задержка).
- Возможна потеря деталей при сжатии.
4. Selective pruning (выборочная обрезка)
Идея не отбрасывать всё подряд, а удалять только те части диалога, которые LLM считает неважными. LLM оценивает каждое сообщение по важности (relevance) и удаляет наименее важные, когда приближаемся к лимиту.
Реализация через промпт
Оцени важность следующих сообщений по шкале 1-5 (5 — критически важно).
Ответ в формате JSON: [{"index": 0, "importance": 4}, ...]
Сообщения: {list_of_messages}
Затем сортируем по важности, оставляем только верхние K сообщений до заполнения контекста.
Плюсы
- Более умная стратегия, чем sliding window.
- Может сохранять неожиданные, но важные детали.
Минусы
- Дополнительный вызов LLM (задержка, стоимость).
- Сложнее в отладке — решение LLM может быть нестабильным.
5. Semantic compression (семантическое сжатие)
Идея каждое сообщение перед добавлением в контекст сжимается через специальный промпт — удаляются стоп-слова, сокращаются фразы, но сохраняется смысл. В отличие от суммаризации, сжимается каждое сообщение индивидуально, а не история целиком.
Пример промпта
Сократи следующий текст до 30% исходной длины, сохранив все ключевые факты.
Текст: {message}
Сжатый вариант:
Плюсы
- Позволяет вместить больше реплик в то же окно.
- Сохраняет структуру диалога (каждое сообщение остаётся отдельным).
Минусы
- Каждое сообщение требует вызова LLM (дорого).
- Может исказить тон или нюансы.
6. Сравнение методов
| Метод | Сохранение раннего контекста | Затраты (LLM вызовы) | Сложность | Типичный use-case |
|---|---|---|---|---|
| Sliding window | Нет | Нет | Низкая | Короткие диалоги (< 20 реплик) |
| Summarization | Частичное | Один раз каждые N сообщений | Средняя | Длинные диалоги с повторяющимися темами |
| Selective pruning | Выборочное | При каждой обрезке | Высокая | Сложные диалоги с разной важностью реплик |
| Semantic compression | Да (сжато) | На каждое сообщение | Высокая | Когда нужно уместить максимум реплик в окно |
7. Инструменты и библиотеки
LangChain предоставляет готовые реализации:
ConversationBufferWindowMemory— sliding window.ConversationSummaryMemory— суммаризация (каждые N сообщений).ConversationSummaryBufferMemory— гибрид: сохраняет последние сообщения и саммари всей истории.ConversationTokenBufferMemory— обрезка по токенам, не по количеству сообщений.
Пример использования ConversationSummaryBufferMemory:
from langchain.memory import ConversationSummaryBufferMemory
from langchain.chat_models import ChatOpenAI
llm = ChatOpenAI(model="gpt-4", temperature=0)
memory = ConversationSummaryBufferMemory(
llm=llm,
max_token_limit=2000, # триггер для суммаризации
return_messages=True,
memory_key="chat_history"
)
# В цикле диалога:
memory.save_context({"input": user_msg}, {"output": assistant_msg})
history = memory.load_memory_variables({})["chat_history"]
Другие инструменты
- MemGPT (специализированная система управления памятью для LLM).
- Microsoft Guidance (позволяет писать кастомные pipeline сжатия).
- LlamaIndex (memory module для чат-ботов).
8. Практические рекомендации для production
На основе моего опыта, суммаризация каждые 10 сообщений работает лучше всего для большинства кейсов. Почему:
- Не создаёт излишней нагрузки (один вызов LLM на 10 реплик).
- Сохраняет достаточно контекста для связности.
- В комбинации с сохранением последних 3–5 сообщений в исходном виде даёт хороший баланс между полнотой и стоимостью.
Пошаговый алгоритм для production
- Использовать
ConversationSummaryBufferMemoryиз LangChain. - Установить
max_token_limitравным 50% от контекстного окна модели (например, 4096 для 8k). - Дополнительно хранить последние 5 сообщений без сжатия.
- Для чувствительных к стоимости сценариев — переключиться на
ConversationBufferWindowMemoryс большим окном, если диалоги редко превышают 20–30 реплик. - Мониторить: logs с метриками (количество токенов, латентность суммаризации, оценка релевантности саммари).
Метрики оценки
- Coherence score — оценка связности ответов (через LLM-as-judge).
- Recall важных фактов — доля фактов из ранних сообщений, которые модель использует в ответах.
- Cost per conversation — средняя стоимость вызовов LLM на суммаризацию.
9. Пример кода: гибридный подход (sliding + summarization)
import json
from langchain.memory import ConversationSummaryBufferMemory
from langchain.chat_models import ChatOpenAI
from langchain.schema import SystemMessage, HumanMessage, AIMessage
class HybridContextManager:
def __init__(self, llm, max_token_limit=3000, keep_last_raw=5):
self.memory = ConversationSummaryBufferMemory(
llm=llm,
max_token_limit=max_token_limit,
return_messages=True,
memory_key="chat_history"
)
self.keep_last_raw = keep_last_raw
self.raw_buffer = []
def add_message(self, user_msg: str, assistant_msg: str):
self.raw_buffer.append(HumanMessage(content=user_msg))
self.raw_buffer.append(AIMessage(content=assistant_msg))
# Сохраняем в memory (но там будет суммаризация)
self.memory.save_context({"input": user_msg}, {"output": assistant_msg})
# Обрезаем raw_buffer до keep_last_raw
self.raw_buffer = self.raw_buffer[-self.keep_last_raw:]
def build_prompt(self, system_prompt: str) -> list:
history = self.memory.load_memory_variables({})["chat_history"]
# В LangChain history уже содержит саммари + последние сообщения
# Но мы можем добавить raw_buffer поверх для свежести
return [SystemMessage(content=system_prompt)] + list(history) + self.raw_buffer
llm = ChatOpenAI(model="gpt-4", temperature=0)
manager = HybridContextManager(llm)
10. Продвинутые техники
- Hierarchical summarization (иерархическая суммаризация) — сжимаем диалог на нескольких уровнях: сначала микро-саммари для групп сообщений, затем мега-саммари для всего дня.
- Token-aware caching — кэшируем K, V матрицы для старых частей контекста (подход, используемый в KV-cache при инференсе, но для диалогов — можно хранить вычисленные ключи/значения для суммаризированного контекста).
- Adaptive threshold — динамический порог для суммаризации в зависимости от темы диалога (например, если пользователь меняет тему — сразу сжимать старую).
Пет-проект для закрепления
Задача Создать чат-бота для поддержки клиентов, который может вести диалоги длиной до 200 сообщений, не превышая лимит в 4096 токенов и не теряя важные детали.
Инструменты Python, LangChain, OpenAI API (или любая LLM), база данных SQLite для логирования.
Шаги:
- Реализовать класс
ContextManagerс двумя режимами: sliding window (окно 50 сообщений) и summarization (каждые 10 сообщений). - Создать простой веб-интерфейс (Gradio или Streamlit) для симуляции диалога.
- Добавить кнопку "Evaluate" — LLM-as-judge оценивает, насколько хорошо бот помнит факты из начала диалога (например, имя пользователя, его проблему).
- Сравнить метрики для двух режимов (coherence, recall, cost).
- Написать отчет: какой режим лучше для вашего сценария.
Ожидаемый результат Работающий прототип с выводом статистики (токены, вызовы LLM, оценка памяти). Вы сможете на собеседовании показать код и объяснить выбор стратегии.
Связь с другими вопросами
| Вопрос | Тема |
|---|---|
| 65 | Как вы обрабатываете запросы, требующие многошаговых рассуждений (multi-step reasoning)? |
| 67 | Как вы работаете с очень большими контекстами (например, 100k токенов)? |
| 62 | Как вы управляете памятью в чат-ботах? |
| 64 | Как вы предотвращаете перезапись важной информации в контексте? |
| 68 | Как вы используете кэширование для ускорения диалогов? |
| 70 | Как вы оцениваете качество диалогового AI? |
Навигация
- Предыдущий: 65
- Следующий: 67
- Индекс: 00. Индекс разборов