Nén phiên bằng Prompt API

Ngày xuất bản: 23 tháng 6 năm 2026

Mỗi phiên LanguageModel đều có một cửa sổ ngữ cảnh hữu hạn. Khi cuộc trò chuyện phát triển, mô hình sẽ tích luỹ toàn bộ nhật ký tin nhắn trong ngữ cảnh của mình: mọi lời nhắc của người dùng và mọi câu trả lời của trợ lý. Khi cửa sổ này đầy, tính năng tự động xử lý tràn của trình duyệt sẽ hoạt động. Tính năng này sẽ loại bỏ các cặp tin nhắn cũ nhất (mỗi lần một cặp lời nhắc và câu trả lời) để giải phóng không gian cho lời nhắc mới. Nếu lời nhắc đến quá lớn đến mức việc xoá toàn bộ nhật ký cuộc trò chuyện cũng không đủ, thì lệnh gọi sẽ hoàn toàn không thành công với QuotaExceededError.

Tính năng nén phiên là một giải pháp chủ động: tóm tắt nhật ký trò chuyện bằng Summarizer API, sau đó khởi động lại một phiên mới bằng các bản tóm tắt đó làm initialPrompts. Trình duyệt không bao giờ loại bỏ initialPrompts trong quá trình xử lý tràn thời gian chạy, vì vậy, bản tóm tắt đã nén sẽ được neo vĩnh viễn trong ngữ cảnh của mô hình, miễn là bản tóm tắt đó vừa với cửa sổ ngữ cảnh khi create() được gọi. Phiên mới sẽ mang theo cùng một chuỗi trò chuyện với chi phí mã thông báo bằng một phần nhỏ so với chi phí ban đầu.

Tính năng nén phiên giúp các cuộc trò chuyện LanguageModel tồn tại lâu dài có thể nằm trong cửa sổ ngữ cảnh mà không bị mất tính liên tục. Các bước chính là:

  1. Theo dõi contextUsage so với contextWindow và hiển thị cho người dùng.
  2. Nghe sự kiện contextoverflow như một cảnh báo sớm.
  3. Phát hiện ngôn ngữ của từng tin nhắn bằng Language Detector API, sau đó tóm tắt bằng một thực thể Summarizer API có nhận biết ngôn ngữ.
  4. Huỷ phiên cũ và tạo một phiên mới bằng initialPrompts.
  5. Giữ một bản sao fullHistory để khôi phục lỗi.

Theo dõi mức sử dụng ngữ cảnh

Prompt API hiển thị 2 thuộc tính để theo dõi mức độ đầy của ngữ cảnh phiên:

  • session.contextUsage: số lượng mã thông báo hiện đang được sử dụng.
  • session.contextWindow: tổng dung lượng mã thông báo của phiên.

Phản ánh điều này trong phần tử <progress> để người dùng có thể biết nhanh phiên này gần đến giới hạn đến mức nào. Đặt valuemax trực tiếp thành số lượng mã thông báo; trình duyệt sẽ tự động điều chỉnh tỷ lệ thanh:

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

Gọi updateTokenDisplay() sau mỗi câu trả lời cho lời nhắc để thanh luôn ở trạng thái hiện tại.

Nghe sự kiện tràn ngữ cảnh

Khi một lời nhắc mới vượt quá ngữ cảnh còn lại, quá trình khôi phục tự động của trình duyệt sẽ bắt đầu: trình duyệt sẽ xoá các cặp lời nhắc và câu trả lời cũ nhất (mỗi lần một cặp) cho đến khi giải phóng đủ không gian. Sự kiện contextoverflow sẽ kích hoạt vào thời điểm quá trình loại bỏ này bắt đầu. Đăng ký trình xử lý ngay sau khi tạo phiên:

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

Có 2 thuộc tính quan trọng của hành vi loại bỏ này:

  • initialPrompts không bị loại bỏ trong thời gian chạy. Trình duyệt không xoá các lời nhắc này để tạo không gian cho lời nhắc đến. Tuy nhiên, nếu kích thước kết hợp của initialPrompts được truyền đến LanguageModel.create() quá lớn để vừa với cửa sổ ngữ cảnh, thì create() sẽ từ chối với QuotaExceededError. Vì vậy, hãy đảm bảo rằng quá trình nén đủ nhỏ để tiếp tục cuộc trò chuyện.
  • Quá trình loại bỏ có giới hạn. Nếu lời nhắc đến quá lớn đến mức việc xoá toàn bộ cuộc trò chuyện trước đó cũng không đủ, thì lệnh gọi prompt() hoặc promptStreaming() sẽ không thành công với QuotaExceededError và không có gì bị xoá.

Đọc thêm về cách xử lý tràn ngữ cảnh trong tài liệu Prompt API .

Sử dụng sự kiện contextoverflow để cảnh báo người dùng, tắt nút gửi hoặc tự động kích hoạt quá trình nén trước khi trình duyệt bắt đầu âm thầm loại bỏ nhật ký trò chuyện.

Nén phiên

Quá trình nén có 3 bước:

  1. Tóm tắt từng tin nhắn trong nhật ký trò chuyện bằng Summarizer API.
  2. Huỷ phiên cũ.
  3. Tạo một phiên mới bằng các bản tóm tắt làm initialPrompts.

Tóm tắt nhật ký

Summarizer API là một giải pháp phù hợp để nén các tin nhắn trò chuyện riêng lẻ. Đối với mỗi tin nhắn, trước tiên hãy phát hiện ngôn ngữ của tin nhắn đó bằng Language Detector API để có thể định cấu hình trình tóm tắt một cách chính xác:

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
}

Ngưỡng tin cậy 0.7 giúp tránh hành động dựa trên các phát hiện không chắc chắn. Khi độ tin cậy thấp hơn ngưỡng, hãy quay lại navigator.language.

Tiếp theo, hãy tạo một trình tóm tắt được định cấu hình cho ngôn ngữ được phát hiện. Ưu tiên preference: 'speed' để chọn biến thể mô hình nhỏ hơn, có độ trễ thấp hơn và quay lại preference: 'auto' nếu mô hình nhanh hơn không hỗ trợ ngôn ngữ được phát hiện:

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

Việc lưu vào bộ nhớ đệm các trình tóm tắt cho mỗi cặp format+lang giúp tránh các lệnh gọi create() dư thừa khi các tin nhắn liên tiếp có cùng ngôn ngữ.

Đối số format được lấy từ chính nội dung tin nhắn. Việc chỉ định 'markdown' cho văn xuôi thuần tuý có thể tạo ra định dạng không mong muốn và việc chỉ định 'plain-text' cho Markdown sẽ loại bỏ các rào cản mã và điểm nhấn. Một biểu thức chính quy nhỏ sẽ phân biệt 2 loại này:

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

Sau khi giải quyết ngôn ngữ và định dạng, hãy tóm tắt từng tin nhắn và truyền chuỗi context để mô hình hiểu rằng mô hình đang nén một lượt trò chuyện chứ không phải một tài liệu độc lập:

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

Huỷ phiên cũ

Giải phóng tài nguyên của phiên cũ trước khi tạo phiên thay thế:

session.destroy();
session = null;

Tạo một phiên mới có nhật ký đã nén

Truyền các tin nhắn đã nén làm initialPrompts để tạo phiên mới bằng ngữ cảnh trò chuyện:

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

Phiên mới bắt đầu ở mức contextUsage thấp hơn. Cuộc trò chuyện tiếp tục từ nơi đã dừng lại: mô hình có các bản tóm tắt làm ngữ cảnh trước đó, vì vậy, mô hình có thể trả lời các câu hỏi tiếp theo về các chủ đề trước đó.

Xử lý lỗi

Nếu quá trình tóm tắt hoặc tạo phiên không thành công sau khi phiên cũ đã bị huỷ, thì người dùng sẽ mất khả năng trò chuyện. Duy trì một mảng fullHistory riêng biệt không bao giờ bị ghi đè bởi quá trình nén và sử dụng mảng này làm phương án dự phòng khôi phục:

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

Việc khôi phục từ fullHistory có thể khiến ngữ cảnh gần đạt đến dung lượng tối đa, nhưng ít nhất người dùng đã quay lại trạng thái hoạt động và có thể thử nén lại ngay lập tức.

Tuỳ ý ngăn nén một số nội dung

Nếu có các phần quan trọng của tin nhắn luôn phải nằm trong ngữ cảnh (ví dụ: mẫu mã), hãy xử lý riêng các phần đó. Ví dụ sau đây chia một tin nhắn thành các phân đoạn văn xuôi và rào cản mã xen kẽ, sau đó chỉ tóm tắt các phần văn xuôi trong khi vẫn giữ nguyên các phân đoạn mã:

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

Xem bản minh hoạ

Bản minh hoạ về tính năng nén phiên cho phép bạn trò chuyện với Prompt API và nén phiên bất cứ lúc nào. Thanh mã thông báo cho biết mức sử dụng ngữ cảnh theo thời gian thực và thay đổi màu khi ngữ cảnh đầy. Sau mỗi lần nén, một mục nhập nhật ký sẽ ghi lại số lượng mã thông báo trước và sau khi nén để bạn có thể trực tiếp quan sát mức giảm.

Bạn có thể kiểm tra JSON trò chuyện đầy đủ và đã nén trong phần Gỡ lỗi: JSON trò chuyện có thể thu gọn ở cuối trang.

Mã nguồn có trên GitHub.