Опубликовано: 23 июня 2026 г.
Каждая сессия LanguageModel имеет конечное контекстное окно. По мере развития диалога модель накапливает полную историю сообщений в своем контексте: каждый запрос пользователя и каждый ответ помощника. Когда окно заполняется, срабатывает автоматическая обработка переполнения браузера. Она удаляет самые старые пары сообщений, по одной паре запрос-ответ за раз, чтобы освободить место для нового запроса. Если входящий запрос настолько велик, что удаление всей истории диалога не помещается в него, вызов завершается ошибкой QuotaExceededError .
Сжатие сессии — это проактивная альтернатива: обобщите историю разговора с помощью API Summarizer , а затем перезапустите новую сессию, используя эти сводки в качестве initialPrompts . Браузер никогда не удаляет initialPrompts во время обработки переполнения во время выполнения, поэтому сжатая сводка остается постоянно привязанной к контексту модели, пока сами сводки помещаются в контекстное окно при вызове create() . Новая сессия содержит ту же цепочку разговора, но с гораздо меньшими затратами на исходные токены.
Сжатие сессий позволяет длительным диалогам LanguageModel оставаться в рамках контекстного окна без потери непрерывности. Ключевые шаги следующие:
- Отслеживайте
contextUsageотносительноcontextWindowи отображайте его пользователю. - Для раннего предупреждения отслеживайте событие
contextoverflow. - Определите язык каждого сообщения с помощью API определения языка, а затем обобщите его с помощью экземпляра API сумматора, учитывающего язык.
- Уничтожьте старую сессию и создайте новую с
initialPrompts. - Для восстановления после ошибок сохраните
fullHistoryкопию истории.
Отслеживание использования контекста
API Prompt предоставляет два атрибута для мониторинга степени заполненности контекста сессии:
-
session.contextUsage: количество токенов, потребляемых в данный момент. -
session.contextWindow: общий объем токенов в сессии.
Отразите это в элементе <progress> , чтобы пользователи с первого взгляда понимали, насколько близка сессия к своему пределу. Установите value и max непосредственно равными количеству токенов; браузер автоматически масштабирует полосу прогресса:
<progress id="token-bar" value="0" max="1"></progress>
<label for="token-bar" id="token-label">Context: — / — tokens</label>
function updateTokenDisplay(session) {
const usage = session.contextUsage;
const total = session.contextWindow;
tokenBar.value = usage;
tokenBar.max = total;
tokenLabel.textContent =
`${Math.round(usage)} / ${Math.round(total)} tokens ` +
`(${Math.round((usage / total) * 100)}%)`;
}
Вызывайте updateTokenDisplay() после каждого ответа на запрос, чтобы панель оставалась актуальной.
Обратите внимание на переполнение контекста.
Когда количество новых запросов превышает оставшийся контекст, начинается автоматическое восстановление браузера: он удаляет самые старые пары запросов и ответов по очереди, пока не освободит достаточно места. Событие contextoverflow срабатывает в момент начала этого процесса удаления. Зарегистрируйте обработчик сразу после создания сессии:
session.addEventListener('contextoverflow', () => {
showWarning('⚠ Context window nearly full. Consider compacting the session.');
});
Такое поведение при выселении обладает двумя важными свойствами:
-
initialPromptsне удаляются во время выполнения. Браузер не удаляет их, чтобы освободить место для входящего запроса. Однако, если общий размерinitialPromptsпереданных вLanguageModel.create(), слишком велик, чтобы поместиться в контекстное окно,create()выдаст ошибкуQuotaExceededError, поэтому убедитесь, что размер объекта initialPrompts достаточно мал, чтобы продолжить диалог. - У удаления есть ограничение. Если входящий запрос настолько велик, что удаление всего предыдущего диалога всё ещё не помещается в него, вызов
prompt()илиpromptStreaming()завершаетсяQuotaExceededError, и ничего не удаляется.
Подробнее об обработке переполнения контекста можно прочитать в документации по API Prompt.
Используйте событие contextoverflow , чтобы предупредить пользователя, отключить кнопку отправки или автоматически запустить процесс сжатия данных до того, как браузер начнет незаметно удалять историю переписки.
Сжать сессию
Уплотнение состоит из трех этапов:
- С помощью API Summarizer можно получить краткое изложение каждого сообщения в истории переписки.
- Удалить старую сессию.
- Создайте новую сессию, предварительно задав в качестве
initialPromptsподсказки (initialPrompts) краткие описания событий.
Кратко изложите историю.
API сумматора идеально подходит для сжатия отдельных сообщений чата. Для каждого сообщения сначала определите его язык с помощью API определения языка, чтобы сумматор можно было правильно настроить:
async function detectLanguage(text, threshold = 0.7) {
const detector = await LanguageDetector.create();
const results = await detector.detect(text);
if (results.length > 0 && results[0].confidence >= threshold) {
return results[0].detectedLanguage;
}
return null; // confidence too low — caller falls back to navigator.language
}
Порог достоверности 0.7 позволяет избежать действий в отношении неопределенных обнаружений. Если достоверность ниже порога, используется метод navigator.language .
Далее создайте сумматор, настроенный для обнаруженного языка. Выберите preference: 'speed' , чтобы выбрать более компактный вариант модели с меньшей задержкой, и используйте preference: 'auto' если более быстрая модель не поддерживает обнаруженный язык.
const summarizers = {}; // cache, keyed by `${format}:${lang}`
async function getSummarizer(format, lang) {
const key = `${format}:${lang}`;
if (summarizers[key]) return summarizers[key];
const baseOptions = {
type: 'tldr',
format, // 'markdown' or 'plain-text'
length: 'short',
expectedInputLanguages: [lang],
expectedContextLanguages: [lang],
outputLanguage: lang,
};
let options = { ...baseOptions, preference: 'speed' };
let avail = await Summarizer.availability(options);
if (avail === 'unavailable') {
options = { ...baseOptions, preference: 'auto' };
avail = await Summarizer.availability(options);
}
if (avail === 'unavailable') {
throw new Error('Summarizer API unavailable on this device.');
}
summarizers[key] = await Summarizer.create(options);
return summarizers[key];
}
Кэширование сумматоров для каждой пары format + lang позволяет избежать избыточных вызовов функции create() когда последовательные сообщения имеют один и тот же язык.
Аргумент format определяется самим содержимым сообщения. Указание 'markdown' для обычного текста может привести к нежелательному форматированию, а указание 'plain-text' для Markdown удаляет блоки кода и выделение текста. Небольшое регулярное выражение позволяет различить эти два варианта:
function looksLikeMarkdown(text) {
return /(?:^#{1,6} |^[-*+] |\d+\. |\*\*|__|\[.+?\]\(|^> |^```)/m.test(text);
}
После определения языка и формата, кратко опишите каждое сообщение и передайте context строку, чтобы модель понимала, что сжимает реплику в чате, а не отдельный документ:
const compacted = [];
for (const msg of history) {
const lang = (await detectLanguage(msg.content)) ?? navigator.language;
const format = looksLikeMarkdown(msg.content) ? 'markdown' : 'plain-text';
const summarizer = await getSummarizer(format, lang);
const summary = await summarizer.summarize(msg.content.trim(), {
context:
`This is a ${msg.role} turn from a chat conversation. ` +
`Preserve its key meaning as concisely as possible.`,
});
// Only use the summary if it's actually shorter.
compacted.push({
role: msg.role,
content:
summary.trim().length < msg.content.length ? summary.trim() : msg.content,
});
}
Удалить старую сессию
Освободите ресурсы старой сессии перед созданием новой:
session.destroy();
session = null;
Создать новую сессию с сокращенной историей
Передайте сжатые сообщения в качестве initialPrompts , чтобы инициализировать новую сессию контекстом разговора:
// Collect every language the detector was confident about.
const sessionLangs =
confidentLangs.size > 0 ? [...confidentLangs] : [navigator.language];
session = await LanguageModel.create({
expectedInputs: [{ type: 'text', languages: sessionLangs }],
expectedOutputs: [{ type: 'text', languages: sessionLangs }],
initialPrompts: compacted,
});
// Re-register the overflow handler on the new session.
session.addEventListener('contextoverflow', () => {
/* ... */
});
Новая сессия начинается с более низкого contextUsage . Разговор продолжается с того места, где он был прерван: модель использует резюме в качестве своего предыдущего контекста, поэтому она может отвечать на последующие вопросы по ранее обсуждавшимся темам.
Обработка ошибок
Если суммирование или создание сессии завершается неудачей после того, как старая сессия уже была уничтожена, пользователь теряет возможность общаться в чате. Поддерживайте отдельный массив fullHistory , который никогда не перезаписывается при сжатии, и используйте его в качестве резервного варианта восстановления:
const history = []; // current session's view, replaced on each compaction
const fullHistory = []; // every original message, never overwritten
// In the catch block:
if (!session) {
session = await LanguageModel.create({
initialPrompts: fullHistory.map(({ role, content }) => ({ role, content })),
});
session.addEventListener('contextoverflow', () => {
/* ... */
});
}
Восстановление после выполнения fullHistory может снова привести к почти полному заполнению контекста, но пользователь, по крайней мере, вернется в рабочее состояние и сможет немедленно попробовать выполнить еще одну компакцию.
При желании можно запретить сжатие некоторого контента.
Если в сообщении есть критически важные части, которые должны всегда оставаться в контексте, например, фрагменты кода, обрабатывайте их отдельно. В следующем примере сообщение разбивается на чередующиеся текстовые и кодовые сегменты, а затем суммируются только текстовые части, при этом кодовые сегменты остаются нетронутыми:
// Splits text into alternating prose and code-fence segments.
// Returns [{ type: 'prose'|'code', content: string }, …]
function splitByCodeFences(text) {
const parts = [];
const re = /^```[^\n]*\n[\s\S]*?^```[ \t]*$/gm;
let lastIndex = 0;
let match;
while ((match = re.exec(text)) !== null) {
if (match.index > lastIndex) {
parts.push({
type: 'prose',
content: text.slice(lastIndex, match.index),
});
}
parts.push({ type: 'code', content: match[0] });
lastIndex = match.index + match[0].length;
}
if (lastIndex < text.length) {
parts.push({ type: 'prose', content: text.slice(lastIndex) });
}
return parts;
}
Попробуйте демоверсию
Демонстрация сжатия сессии позволяет взаимодействовать с API Prompt и сжимать сессию в любое время. Индикатор использования токенов отображает использование контекста в реальном времени и меняет цвет по мере заполнения контекста. После каждого сжатия в журнал записывается количество токенов до и после, так что вы можете напрямую наблюдать за уменьшением количества токенов.
Полный и сжатый JSON-файлы диалога можно просмотреть в сворачиваемом разделе «Отладка: JSON-файлы диалога» внизу страницы.