發布日期:2026 年 6 月 23 日
每個 LanguageModel 工作階段都有有限的背景資訊視窗。隨著對話進行,模型會在情境中累積完整訊息記錄,包括每則使用者提示和每則 Google 助理回覆。當視窗填滿時,瀏覽器的自動溢位處理機制就會啟動。系統會逐一清除最舊的訊息組合 (提示和回覆),以便為新提示騰出空間。如果傳入的提示過大,即使移除整個對話記錄也無法容納,呼叫就會直接失敗並傳回 QuotaExceededError。
工作階段壓縮是主動式替代方案:使用 Summarizer API 摘要對話記錄,然後使用這些摘要做為 initialPrompts,重新啟動新的工作階段。瀏覽器絕不會在執行階段溢位處理期間逐出 initialPrompts,因此只要摘要本身在呼叫 create() 時符合脈絡視窗大小,壓縮摘要就會永久錨定在模型的脈絡中。新工作階段會沿用相同的對話討論串,但權杖費用僅為原先的一小部分。
工作階段壓縮功能可讓長期對話LanguageModel維持在內容視窗中,不會中斷。主要步驟如下:
- 監控
contextUsage相對於contextWindow的位置,並顯示給使用者。 - 監聽
contextoverflow事件,做為預警。 - 使用 Language Detector API 偵測每則訊息的語言,然後使用可辨識語言的 Summarizer API 執行個體摘要訊息。
- 使用
initialPrompts毀損舊工作階段,並植入新的工作階段。 - 保留
fullHistory副本,以利錯誤復原。
追蹤脈絡使用情形
Prompt API 會公開兩個屬性,用於監控工作階段情境的完整程度:
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不會在執行階段遭到逐出。瀏覽器不會移除這些提示,以便接收新的提示。不過,如果傳遞至LanguageModel.create()的initialPrompts合併大小本身太大,無法放入脈絡視窗,create()就會以QuotaExceededError拒絕,因此請確保壓縮大小足夠小,可繼續對話。- 移除次數有限制。如果傳入的提示過大,即使移除先前的所有對話,仍無法容納該提示,
prompt()或promptStreaming()呼叫就會失敗並顯示QuotaExceededError,且不會移除任何內容。
如要進一步瞭解如何處理內容溢位,請參閱 Prompt API 說明文件。
在瀏覽器開始自動捨棄對話記錄前,使用 contextoverflow 事件警告使用者、停用傳送按鈕,或自動觸發壓縮。
壓縮工作階段
壓縮作業分為三個步驟:
- 使用 Summarizer API 摘要說明對話記錄中的每則訊息。
- 銷毀舊的工作階段。
- 建立以摘要做為種子的新工作階段,並將其命名為
initialPrompts。
重點摘要記錄
摘要 API 非常適合壓縮個別即時通訊訊息。針對每則訊息,請先使用 Language Detector 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: 'auto':preference: 'speed'
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' 可能會產生不必要的格式,為 Markdown 指定 'plain-text' 則會移除程式碼圍欄和強調效果。使用小型規則運算式區分兩者:
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;
}
立即試用
您可以透過工作階段壓縮示範與 Prompt API 對話,並隨時壓縮工作階段。權杖長條會顯示即時脈絡用量,並在脈絡填滿時變更顏色。每次壓縮後,記錄項目都會記錄壓縮前後的符記數量,方便您直接觀察減少的符記數量。
您可以在頁面底部的「Debug: conversation JSON」(偵錯:對話 JSON) 可收合部分,檢查完整和壓縮的對話 JSON。