Kompaktowanie sesji za pomocą interfejsu Prompt API

Opublikowano: 23 czerwca 2026 r.

Każda LanguageModel sesja ma skończone okno kontekstu. W miarę rozwoju rozmowy model gromadzi w kontekście pełną historię wiadomości: każdy prompt użytkownika i każdą odpowiedź asystenta. Gdy okno się zapełni, włączy się automatyczna obsługa przepełnienia w przeglądarce. Usuwa ona najstarsze pary wiadomości (prompt i odpowiedź) po jednej, aby zwolnić miejsce na nowy prompt. Jeśli przychodzący prompt jest tak duży, że nawet usunięcie całej historii rozmowy nie wystarczy, wywołanie zakończy się niepowodzeniem z powodu błędu QuotaExceededError.

Kompaktowanie sesji to proaktywna alternatywa: podsumuj historię rozmowy za pomocą interfejsu Summarizer API, a następnie uruchom nową sesję, używając tych podsumowań jako initialPrompts. Przeglądarka nigdy nie usuwa initialPrompts podczas obsługi przepełnienia w czasie działania, więc skompresowane podsumowanie pozostaje na stałe zakotwiczone w kontekście modelu, o ile same podsumowania mieszczą się w oknie kontekstu, gdy wywoływana jest funkcja create(). Nowa sesja zawiera ten sam wątek rozmowy przy ułamku pierwotnego kosztu tokena.

Kompaktowanie sesji umożliwia długotrwałym rozmowom LanguageModel pozostanie w oknie kontekstu bez utraty ciągłości. Najważniejsze kroki:

  1. Monitoruj contextUsage w stosunku do contextWindow i wyświetlaj te informacje użytkownikowi.
  2. Nasłuchuj zdarzenia contextoverflow jako wczesnego ostrzeżenia.
  3. Wykryj język każdej wiadomości za pomocą interfejsu Language Detector API, a następnie podsumuj ją za pomocą instancji interfejsu Summarizer API, która uwzględnia język.
  4. Zniszcz starą sesję i zainicjuj nową za pomocą initialPrompts.
  5. Zachowaj kopię fullHistory na potrzeby odzyskiwania po błędach.

Śledzenie wykorzystania kontekstu

Interfejs Prompt API udostępnia 2 atrybuty do monitorowania, jak pełny jest kontekst sesji:

  • session.contextUsage: liczba aktualnie wykorzystanych tokenów.
  • session.contextWindow: łączna pojemność tokenów sesji.

Odzwierciedlaj to w elemencie <progress>, aby użytkownicy mogli na pierwszy rzut oka zobaczyć, jak blisko limitu jest sesja. Ustaw value i max bezpośrednio na liczby tokenów. Przeglądarka automatycznie skaluje pasek:

<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)}%)`;
}

Po każdej odpowiedzi na prompt wywołaj funkcję updateTokenDisplay(), aby pasek był aktualny.

Nasłuchiwanie przepełnienia kontekstu

Gdy nowy prompt przekroczy pozostały kontekst, rozpocznie się automatyczne odzyskiwanie w przeglądarce: będzie ona usuwać najstarsze pary promptów i odpowiedzi po jednej, aż zwolni wystarczająco dużo miejsca. Zdarzenie contextoverflow jest wywoływane w momencie rozpoczęcia tego usuwania. Zarejestruj obsługę zdarzeń natychmiast po utworzeniu sesji:

session.addEventListener('contextoverflow', () => {
  showWarning('⚠ Context window nearly full. Consider compacting the session.');
});

Istnieją 2 ważne właściwości tego zachowania usuwania:

  • initialPrompts nie są usuwane w czasie działania. Przeglądarka nie usuwa ich, aby zrobić miejsce na przychodzący prompt. Jeśli jednak łączny rozmiar initialPrompts przekazanych do LanguageModel.create() jest zbyt duży aby zmieścić się w oknie kontekstu, create() odrzuci żądanie z powodu błędu QuotaExceededError. Upewnij się więc, że kompresja jest wystarczająco mała, aby można było kontynuować rozmowę.
  • Usuwanie ma limit. Jeśli przychodzący prompt jest tak duży, że nawet usunięcie całej poprzedniej rozmowy nie wystarczy, wywołanie prompt() lub promptStreaming() zakończy się niepowodzeniem z powodu błędu QuotaExceededError i nic nie zostanie usunięte.

Więcej informacji o obsłudze przepełnienia kontekstu w dokumentacji interfejsu Prompt API .

Użyj zdarzenia contextoverflow, aby ostrzec użytkownika, wyłączyć przycisk wysyłania lub automatycznie uruchomić kompresję, zanim przeglądarka zacznie po cichu odrzucać historię rozmowy.

Kompaktowanie sesji

Kompaktowanie obejmuje 3 etapy:

  1. Podsumuj każdą wiadomość w historii rozmowy za pomocą interfejsu Summarizer API.
  2. Zniszcz starą sesję.
  3. Utwórz nową sesję zainicjowaną podsumowaniami jako initialPrompts.

Podsumuj historię

Interfejs Summarizer API doskonale nadaje się do kompresowania poszczególnych wiadomości na czacie. W przypadku każdej wiadomości najpierw wykryj jej język za pomocą interfejsu Language Detector API, aby można było prawidłowo skonfigurować podsumowywanie:

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
}

Próg ufności 0.7 pozwala uniknąć działania na podstawie niepewnych wykryć. Gdy ufność jest niższa niż próg, wróć do navigator.language.

Następnie utwórz podsumowywanie skonfigurowane pod kątem wykrytego języka. Wybierz preference: 'speed' aby wybrać mniejszy wariant modelu o niższym opóźnieniu, i wróć do preference: 'auto' jeśli szybszy model nie obsługuje wykrytego języka:

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];
}

Buforowanie podsumowywania według pary format+lang pozwala uniknąć zbędnych wywołań create() w przypadku kolejnych wiadomości w tym samym języku.

Argument format jest wyprowadzany z samej treści wiadomości. Określenie 'markdown' w przypadku zwykłego tekstu może spowodować niechciane formatowanie, a określenie 'plain-text' w przypadku Markdowna spowoduje usunięcie obramowań kodu i wyróżnień. Dwa te przypadki można odróżnić za pomocą małego wyrażenia regularnego:

function looksLikeMarkdown(text) {
  return /(?:^#{1,6} |^[-*+] |\d+\. |\*\*|__|\[.+?\]\(|^> |^```)/m.test(text);
}

Po ustaleniu języka i formatu podsumuj każdą wiadomość i przekaż ciąg znaków context, aby model wiedział, że kompresuje turę czatu, a nie samodzielny dokument:

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,
  });
}

Niszczenie starej sesji

Przed utworzeniem zamiennika zwolnij zasoby starej sesji:

session.destroy();
session = null;

Tworzenie nowej sesji ze skompresowaną historią

Przekaż skompresowane wiadomości jako initialPrompts, aby zainicjować nową sesję z kontekstem rozmowy:

// 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', () => {
  /* ... */
});

Nowa sesja zaczyna się od niższego contextUsage. Rozmowa jest kontynuowana od miejsca, w którym została przerwana: model ma podsumowania jako poprzedni kontekst, dzięki czemu może odpowiadać na pytania dotyczące wcześniejszych tematów.

Obsługa błędów

Jeśli podsumowywanie lub tworzenie sesji nie powiedzie się po zniszczeniu starej sesji, użytkownik straci możliwość czatowania. Utrzymuj oddzielną tablicę fullHistory, która nigdy nie jest zastępowana przez kompresję, i używaj jej jako rezerwy na wypadek awarii:

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', () => {
    /* ... */
  });
}

Odzyskiwanie z fullHistory może ponownie umieścić kontekst blisko pojemności, ale użytkownik przynajmniej wróci do stanu roboczego i będzie mógł od razu spróbować kolejnej kompresji.

Opcjonalne uniemożliwianie kompresowania niektórych treści

Jeśli w wiadomości znajdują się krytyczne części, które muszą zawsze pozostać w kontekście, np. przykłady kodu, przetwórz je oddzielnie. Poniższy przykład dzieli wiadomość na naprzemienne segmenty prozy i obramowania kodu, a następnie podsumowuje tylko części prozy, pozostawiając segmenty kodu nienaruszone:

// 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;
}

Wypróbuj wersję demonstracyjną

Wersja demonstracyjna kompaktowania sesji umożliwia czatowanie z interfejsem Prompt API i kompaktowanie sesji w dowolnym momencie. Pasek tokenów pokazuje wykorzystanie kontekstu w czasie rzeczywistym i zmienia kolor w miarę zapełniania się kontekstu. Po każdej kompresji wpis logu rejestruje liczbę tokenów przed i po, dzięki czemu można bezpośrednio obserwować zmniejszenie.

Pełny i skompresowany JSON rozmowy możesz sprawdzić w rozwijanej sekcji Debug: JSON rozmowy u dołu strony.

Kod źródłowy jest dostępny na GitHubie.