เผยแพร่เมื่อ: 23 มิถุนายน 2026
เซสชัน LanguageModel ทุกเซสชันมีหน้าต่างบริบทที่จำกัด
เมื่อการสนทนามีขนาดใหญ่ขึ้น โมเดลจะสะสมประวัติข้อความทั้งหมดไว้ในบริบท ซึ่งรวมถึงพรอมต์ของผู้ใช้ทุกรายการและการตอบกลับของผู้ช่วยทุกรายการ เมื่อหน้าต่างเต็ม ระบบจัดการการล้นอัตโนมัติของเบราว์เซอร์จะเริ่มทำงาน ระบบจะนำคู่ข้อความที่เก่าที่สุดออกทีละคู่ (พรอมต์ 1 รายการและการตอบกลับ 1 รายการ) เพื่อเพิ่มพื้นที่ว่างสำหรับพรอมต์ใหม่ หากพรอมต์ขาเข้ามีขนาดใหญ่มากจนการนำประวัติการสนทนาทั้งหมดออกก็ยังไม่พอ ระบบจะยกเลิกการเรียกโดยสมบูรณ์พร้อมแสดง QuotaExceededError
การบีบอัดเซสชัน เป็นทางเลือกเชิงรุก โดยคุณสามารถสรุปประวัติการสนทนา
ด้วย Summarizer
API แล้วเริ่มเซสชันใหม่
โดยใช้ข้อมูลสรุปเหล่านั้นเป็น initialPrompts เบราว์เซอร์จะไม่นำ initialPrompts ออกระหว่างการจัดการการล้นในรันไทม์ ดังนั้นข้อมูลสรุปที่บีบอัดแล้วจะยังคงอยู่ในบริบทของโมเดลอย่างถาวร ตราบใดที่ข้อมูลสรุปเองมีขนาดพอดีกับหน้าต่างบริบทเมื่อมีการเรียก create() เซสชันใหม่จะดำเนินการสนทนาในหัวข้อเดิมด้วยค่าใช้จ่ายโทเค็นเพียงเล็กน้อยเมื่อเทียบกับค่าใช้จ่ายเดิม
การบีบอัดเซสชันช่วยให้การสนทนา LanguageModel ที่มีระยะเวลานานสามารถอยู่ในหน้าต่างบริบทได้โดยไม่สูญเสียความต่อเนื่อง ขั้นตอนสำคัญมีดังนี้
- ตรวจสอบ
contextUsageเทียบกับcontextWindowแล้วแสดงให้ผู้ใช้เห็น - ฟังเหตุการณ์
contextoverflowเพื่อรับคำเตือนล่วงหน้า - ตรวจหาภาษาของข้อความแต่ละรายการด้วย Language Detector API แล้วสรุปข้อความนั้นด้วยอินสแตนซ์ Summarizer API ที่รองรับภาษา
- ทำลายเซสชันเก่าและสร้างเซสชันใหม่ด้วย
initialPrompts - เก็บสำเนา
fullHistoryไว้สำหรับการกู้คืนข้อผิดพลาด
ติดตามการใช้บริบท
Prompt API แสดงแอตทริบิวต์ 2 รายการสำหรับการตรวจสอบว่าบริบทของเซสชันเต็มแล้วหรือยัง ดังนี้
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.');
});
ลักษณะการทำงานของการนำออกนี้มีพร็อพเพอร์ตี้สำคัญ 2 รายการ ดังนี้
- ระบบจะไม่นำ
initialPromptsออกในรันไทม์ เบราว์เซอร์จะไม่นำออกเพื่อเพิ่มพื้นที่ว่างสำหรับพรอมต์ขาเข้า อย่างไรก็ตาม หากขนาดรวมของ theinitialPromptsที่ส่งไปยังLanguageModel.create()มีขนาดใหญ่เกินกว่า ที่จะใส่ในหน้าต่างบริบทได้create()จะปฏิเสธด้วยQuotaExceededErrorดังนั้นโปรดตรวจสอบว่าการบีบอัดมีขนาดเล็กพอที่จะ ดำเนินการสนทนาต่อได้ - การนำออกมีขีดจำกัด หากพรอมต์ขาเข้ามีขนาดใหญ่มากจนการนำการสนทนาก่อนหน้าทั้งหมดออกก็ยังไม่พอ การเรียก
prompt()หรือpromptStreaming()จะล้มเหลวด้วยQuotaExceededErrorและระบบจะไม่นำสิ่งใดออก
อ่านเพิ่มเติมเกี่ยวกับการจัดการการล้นบริบท ในเอกสารประกอบ Prompt API
ใช้เหตุการณ์ contextoverflow เพื่อเตือนผู้ใช้ ปิดใช้ปุ่มส่ง หรือทริกเกอร์การบีบอัดโดยอัตโนมัติก่อนที่เบราว์เซอร์จะเริ่มทิ้งประวัติการสนทนาโดยไม่แจ้งให้ทราบ
บีบอัดเซสชัน
การบีบอัดมี 3 ขั้นตอน ดังนี้
- สรุปข้อความแต่ละรายการในประวัติการสนทนาด้วย Summarizer API
- ทำลายเซสชันเก่า
- สร้างเซสชันใหม่โดยใช้ข้อมูลสรุปเป็น
initialPrompts
สรุปประวัติ
Summarizer API เหมาะอย่างยิ่งสำหรับการบีบอัดข้อความแชทแต่ละรายการ สำหรับข้อความแต่ละรายการ ให้ตรวจหาภาษาก่อนด้วย Language Detector API เพื่อให้กำหนดค่า Summarizer ได้อย่างถูกต้อง
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
จากนั้นสร้าง Summarizer ที่กำหนดค่าไว้สำหรับภาษาที่ตรวจพบ เลือก
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];
}
การแคช Summarizer ต่อคู่ format+lang ช่วยหลีกเลี่ยงการเรียก create() ที่ซ้ำซ้อนเมื่อข้อความที่ต่อเนื่องกันใช้ภาษาเดียวกัน
อาร์กิวเมนต์ format ได้มาจากเนื้อหาของข้อความเอง การระบุ
'markdown' สำหรับข้อความธรรมดาอาจทำให้เกิดการจัดรูปแบบที่ไม่ต้องการ และการระบุ
'plain-text' สำหรับ Markdown จะลบโค้ดและตัวเน้น นิพจน์ทั่วไปขนาดเล็กจะแยกความแตกต่างระหว่าง 2 อย่างนี้
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 และบีบอัดเซสชันได้ทุกเมื่อ แถบโทเค็นจะแสดงการใช้บริบทแบบเรียลไทม์และเปลี่ยนสีเมื่อบริบทเต็ม หลังจากการบีบอัดแต่ละครั้ง รายการบันทึกจะบันทึกจำนวนโทเค็นก่อนและหลังการบีบอัดเพื่อให้คุณเห็นการลดลงโดยตรง
คุณสามารถตรวจสอบ JSON การสนทนาแบบเต็มและแบบบีบอัดแล้วได้ในส่วน Debug: conversation JSON ที่ยุบได้ที่ด้านล่างของหน้า