Quản lý cửa sổ ngữ cảnh LLM cho các cuộc hội thoại dài
Cửa sổ ngữ cảnh có hai chi phí: lỗi tràn và tính phí theo từng lượt. Cách đếm token, cắt tỉa và tóm tắt lịch sử, lưu đệm tiền tố ổn định và dùng cơ chế nén phía máy chủ.
Một cuộc hội thoại đang lớn dần thất bại theo hai cách khác nhau, và nhầm lẫn chúng dẫn đến cách khắc phục sai. Thất bại ồn ào là tràn (overflow): prompt cộng với đầu ra được yêu cầu vượt quá cửa sổ ngữ cảnh của mô hình và yêu cầu bị từ chối thẳng thừng. Thất bại lặng lẽ là chi phí (cost): mọi thứ bạn giữ trong ngữ cảnh đều bị tính phí lại trên mỗi lượt duy nhất, nên một cuộc chat không bao giờ tràn vẫn có thể trở nên đắt đỏ một cách tuyến tính khi lịch sử chồng chất. Quản lý một cửa sổ ngữ cảnh nghĩa là phòng thủ chống lại cả hai — giữ số đếm token dưới giới hạn cứng và giữ nó đủ nhỏ để bạn không phải trả tiền để gửi lại cùng một lịch sử mãi mãi. Bài viết này trình bày cách đo lường ngân sách, các chiến lược cắt tỉa và tóm tắt giữ nó trong tầm kiểm soát, và caching cùng compaction phía máy chủ giúp giảm chi phí của một cuộc hội thoại dài.
Một ngân sách, được chia sẻ bởi đầu vào, đầu ra, và suy luận
Cửa sổ ngữ cảnh là một ngân sách token duy nhất mà toàn bộ yêu cầu rút từ đó. Nó không phải là "token đầu vào tới giới hạn, rồi đầu ra thêm lên trên" — các token đầu vào, các token bạn yêu cầu mô hình sinh ra, và (trên các mô hình suy luận) các token suy luận ẩn đều cạnh tranh cho cùng một trần. Hai hệ quả theo ngay lập tức. Thứ nhất, một prompt dài thu hẹp chỗ còn lại cho câu trả lời, nên một ngữ cảnh bị nhồi quá mức có thể buộc một phản hồi bị cắt cụt hoặc rỗng ngay cả khi không có lỗi tràn cứng. Thứ hai, trên các mô hình suy luận, các token suy luận vô hình cũng ăn vào cái trần đó, đó là lý do tham số API của mô hình suy luận nhấn mạnh việc dành khoảng dư đầu ra. Hãy lập ngân sách cho cửa sổ như một tổng thể: quyết định trước bao nhiêu token bạn sẽ cho phép lịch sử chiếm, bao nhiêu bạn dành cho suy luận, và bao nhiêu bạn đảm bảo cho câu trả lời hiển thị.
Theo mặc định, vượt quá cửa sổ là lỗi cứng: khi không bật cắt bớt, nhà cung cấp trả về lỗi độ dài ngữ cảnh thay vì âm thầm bỏ token, nên tràn thường là vấn đề bạn phải ngăn ngừa chứ không phải để mô hình tự xử lý. Có một ngoại lệ có chủ ý: Responses API nhận truncation: auto, cho phép nhà cung cấp bỏ các mục ở giữa đầu vào để yêu cầu quá lớn vẫn vừa thay vì bị từ chối. Cách này đổi một lỗi rõ ràng lấy sự mất nội dung âm thầm mà bạn không tự chọn, vì vậy hãy dùng có chủ đích và ghi lại khi nó kích hoạt; hành vi cắt tự động và phần bị bỏ khác nhau theo nhà cung cấp. Dù theo cách nào, biện pháp đáng tin cậy vẫn là biết số token trước khi gửi.
Đo lường trước khi bạn gửi
Bạn không thể quản lý một ngân sách mà bạn không đo lường. Mọi quyết định quản-lý-lịch-sử phụ thuộc vào một số đếm token chính xác của yêu cầu bạn sắp thực hiện, và "khoảng bốn ký tự mỗi token" là một quy tắc ngón tay cái trôi dạt tệ hại trên code, các chữ viết phi Latinh, và dữ liệu có cấu trúc. Hãy đếm bằng chính bộ tokenizer của nhà cung cấp ở nơi bạn có thể, và đếm toàn bộ yêu cầu — chỉ dẫn hệ thống, công cụ, mọi message, và bất kỳ tài liệu đính kèm nào — không chỉ lượt người dùng mới nhất.
Việc đếm cũng phải trung thực về những gì mô hình thấy. Các định nghĩa công cụ, các JSON schema cho structured output, và hình ảnh đều tiêu thụ token, và chúng dễ bị quên vì trông không giống "hội thoại." Một danh mục công cụ lớn có thể lặng lẽ chiếm một lát cắt đáng kể của cửa sổ trên mỗi lệnh gọi. Một khi bạn có một số đếm đáng tin cậy, các chiến lược bên dưới chỉ là những chính sách để giữ con số đó ở nơi bạn muốn.
Các chiến lược cắt tỉa, từ thô đến tinh
Có bốn cách phổ biến để giữ lịch sử dưới ngân sách, và các hệ thống thực tế kết hợp chúng.
Cắt cụt các lượt cũ nhất. Chính sách đơn giản nhất: giữ system prompt cộng với N lượt gần nhất và bỏ phần còn lại. Nó rẻ và có thể dự đoán được, và là mặc định đúng cho các cuộc chat kiểu hỗ trợ nơi ngữ cảnh cũ hiếm khi quan trọng. Điểm yếu của nó là chứng hay quên — mô hình quên các sự kiện được thiết lập từ sớm trong một phiên dài.
Tóm tắt và thay thế. Thay vì bỏ các lượt cũ, hãy nén chúng. Định kỳ yêu cầu một mô hình tóm tắt phần sớm nhất của cuộc hội thoại thành một ghi chú gọn, rồi thay các lượt đó bằng bản tóm tắt. Điều này bảo tồn cái cốt lõi với một phần nhỏ số token, đổi lấy một lệnh gọi thêm và một chút mất mát độ trung thực. Sự đánh đổi thường đáng giá cho các trợ lý dài phải nhớ những quyết định đã đưa ra một giờ trước.
Truy xuất thay vì giữ lại. Đối với kiến thức nằm trong tài liệu chứ không phải trong cuộc đối thoại, đừng mang nó trong ngữ cảnh chút nào. Lưu nó bên ngoài, và trên mỗi lượt chỉ truy xuất các đoạn liên quan đến câu hỏi hiện tại và chèn những đoạn đó. Truy xuất giữ cho cửa sổ nhỏ và ổn định bất kể có bao nhiêu tài liệu nguồn — sự khác biệt giữa việc trả tiền cho cả một cẩm nang mỗi lượt và trả tiền cho hai đoạn văn quan trọng.
Giữ một tiền tố ổn định và biến đổi phần đuôi. Cấu trúc cuộc hội thoại sao cho phần không thay đổi (system prompt, công cụ, tài liệu tham chiếu) nằm ở phía trước và chỉ các lượt gần đây thay đổi ở phía cuối. Đây là vệ sinh tốt tự thân, và nó là điều kiện tiên quyết cho caching, được bàn tiếp theo.
Hầu hết các hệ thống chat bền vững đều xếp lớp những cái này: một tiền tố hệ thống cố định, một bản tóm tắt đang chạy của phần giữa, vài lượt cuối nguyên văn, và truy xuất cho bất cứ điều gì mang tính sự kiện. Hỗn hợp chính xác phụ thuộc vào việc ưu tiên của bạn là khả năng nhớ lại, độ trễ, hay chi phí.
Cache cái tiền tố mà bạn không thể tránh gửi lại
Một số ngữ cảnh thực sự phải có mặt mỗi lượt — một system prompt chi tiết, một bộ công cụ cố định, một tài liệu đang được thảo luận. Bạn không thể cắt tỉa nó mà không phá vỡ tác vụ, nhưng bạn có thể ngừng trả giá đầy đủ để xử lý lại nó, và đó chính xác là điều prompt caching làm. Khi tiền tố ổn định được cache, nhà cung cấp tái sử dụng nó và tính phí các token đó với chiết khấu sâu thay vì mức đầu vào đầy đủ. Nước đi quản lý là sắp xếp cuộc hội thoại sao cho vùng có thể cache là tối đa và ổn định theo byte — không có dấu thời gian hay công cụ bị sắp xếp lại bên trong nó — và để cái đuôi hay thay đổi rơi xuống sau ranh giới cache. Cơ chế theo từng nhà cung cấp nằm trong prompt caching trên OpenAI, Claude, và Gemini; điểm mấu chốt ở đây là caching và quản lý ngữ cảnh là cùng một kỷ luật nhìn từ hai góc độ. Cắt tỉa giảm số token bạn gửi; caching giảm chi phí của những token không thể tránh.
Trạng thái phía máy chủ không làm cho lịch sử miễn phí
Responses API cho phép bạn tiếp tục một luồng với previous_response_id thay vì gửi lại toàn bộ lịch sử, đó là một thắng lợi về công thái học thực sự — bạn chỉ gửi lượt mới và máy chủ khâu nó vào ngữ cảnh đã lưu. Thật hấp dẫn khi đọc điều này như "ngữ cảnh trước đó giờ đây miễn phí." Nó không phải vậy. Tất cả các token đầu vào trước đó trong chuỗi vẫn được tính phí như token đầu vào trên mỗi lần tiếp tục; nối chuỗi giúp bạn khỏi gửi các byte qua đường truyền, chứ không giúp bạn khỏi trả tiền cho ngữ cảnh mà mô hình vẫn xử lý. Vậy nên trạng thái hội thoại phía máy chủ thay đổi ai giữ lịch sử, chứ không phải liệu bạn có trả tiền cho nó. Bạn vẫn cần việc cắt tỉa và caching ở trên để kiểm soát chi phí; previous_response_id trực giao với điều đó.
Compaction: thu nhỏ một cuộc hội thoại trên máy chủ
Khi một cuộc hội thoại lớn đến mức ngay cả việc tóm tắt ở phía bạn cũng trở nên vụng về, một số quy trình kiểu Responses phơi bày một bước compaction phía máy chủ để cô đọng ngữ cảnh đang chạy thành một biểu diễn nhỏ hơn mà bạn có thể tiếp tục từ đó. Gateway này hiển thị điều đó dưới dạng một endpoint chuyên dụng, POST /v1/responses/compact, chuyển tiếp một yêu cầu compaction tới một thượng nguồn hỗ trợ nó. Yêu cầu mang cuộc hội thoại cần cô đọng — model, input, instructions, và tùy chọn previous_response_id, tools, và một đối tượng reasoning — và phản hồi trả về một output đã được compact cộng với một đối tượng usage với số token đầu vào và đầu ra mà bản thân việc compaction đã tiêu thụ.
curl https://your-gateway.example.com/v1/responses/compact \
-H "Authorization: Bearer AVE_GATEWAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-5.6",
"previous_response_id": "resp_abc123",
"instructions": "Condense the conversation so far, preserving decisions and open tasks."
}'
Hai điều cần ghi nhớ. Compaction bản thân nó là một lệnh gọi mô hình: nó đọc cuộc hội thoại và viết ra phiên bản cô đọng, nên tiêu thụ token xuất hiện trong nhật ký sử dụng như mọi yêu cầu khác; nó đổi một chi phí compaction một lần lấy các lượt tiếp theo rẻ hơn. Nó cũng chỉ có ý nghĩa với thượng nguồn triển khai endpoint compaction, nên đây là khả năng truyền thẳng chứ không phải thứ gateway có thể tự tạo cho nhà cung cấp thiếu nó. Ở nơi có hỗ trợ, đây là bản tương ứng phía máy chủ của chiến lược "tóm tắt và thay thế", chuyển công việc nén lên thượng nguồn thay vì chạy trong client của bạn.
Dành chỗ cho câu trả lời
Bất kể cách kết hợp nào bạn chọn, hãy luôn để lại đủ chỗ cho câu trả lời hiển thị. Đặt trần đầu ra rõ ràng và đảm bảo context_used + reserved_output nằm thoải mái dưới cửa sổ, nhớ rằng trên mô hình suy luận, phần dự trữ cũng phải bao gồm token suy luận ẩn. Bỏ qua điều này và bạn sẽ gặp tình huống bực mình: yêu cầu thành công, tính phí một đầu vào lớn nhưng trả về câu trả lời bị cắt vì không còn chỗ để hoàn tất. Hãy áp dụng cùng nguyên tắc cho thời gian chờ: chủ động để lại khoảng dư.
Gộp tất cả lại
Một chính sách ngữ cảnh cho một trợ lý chạy dài thường trông như thế này: ghim một tiền tố hệ thống có thể cache, giữ nguyên văn nắm lượt cuối cùng, duy trì một bản tóm tắt cuộn (hoặc một compaction phía máy chủ) của phần giữa cũ hơn, truy xuất các sự kiện từ một kho lưu trữ bên ngoài thay vì mang chúng theo, đếm token trước mỗi lần gửi, và dành một ngân sách đầu ra cố định. Mỗi mảnh nhắm vào một phần cụ thể của bài toán hai-chi-phí — tràn hoặc tính phí theo từng lượt — và cùng nhau chúng giữ một cuộc hội thoại vừa dưới giới hạn cứng vừa nằm ngoài một đường cong chi phí không ngừng tăng. Vai trò của gateway là làm cho hiệu ứng trở nên hữu hình: một nơi duy nhất để theo dõi số đếm token theo từng yêu cầu và xác nhận rằng một thay đổi trong chính sách lịch sử của bạn thực sự đã dịch chuyển con số, điều này gắn với công việc rộng hơn của giảm chi phí LLM API.
FAQ
Điều gì xảy ra khi một yêu cầu vượt quá cửa sổ ngữ cảnh?
Theo mặc định, nhà cung cấp từ chối yêu cầu bằng lỗi độ dài ngữ cảnh và không âm thầm cắt prompt cho vừa, vì vậy bạn phải ngăn tràn bằng cách đếm token trước khi gửi. Ngoại lệ là truncation: auto được bật có chủ ý trong Responses; chế độ này bỏ các mục ở giữa đầu vào để nhét yêu cầu quá lớn vào cửa sổ. Nó tiện lợi nhưng gây mất nội dung âm thầm, nên hãy bật có cân nhắc thay vì dùng như lưới an toàn. Vì token đầu vào, đầu ra và suy luận dùng chung một ngân sách, prompt quá dài cũng có thể làm câu trả lời bị cắt ngay cả dưới giới hạn cứng nếu còn quá ít chỗ để sinh.
Việc dùng previous_response_id có giảm hóa đơn token của tôi không?
Không. Nó giúp bạn khỏi gửi lại lịch sử qua đường truyền, nhưng tất cả các token đầu vào trước đó trong chuỗi vẫn được tính phí như đầu vào trên mỗi lần tiếp tục. Trạng thái phía máy chủ là một tính năng công thái học và độ trễ, không phải một sự giảm chi phí. Để thực sự cắt giảm chi phí ngữ cảnh bạn vẫn cần cắt tỉa, tóm tắt, và prompt caching cho tiền tố ổn định.
Tóm tắt lịch sử hay cắt cụt nó — tôi nên dùng cái nào?
Cắt cụt (giữ N lượt cuối) là đơn giản nhất và ổn khi ngữ cảnh cũ hiếm khi quan trọng, nhưng nó quên các sự kiện sớm. Tóm tắt nén các lượt cũ thành một ghi chú gọn, bảo tồn cái cốt lõi với chi phí phụ trội cho mỗi lần nén. Các trợ lý dài phải nhớ những quyết định trước đó thường chạy cả hai: các lượt gần đây nguyên văn cộng với một bản tóm tắt cuộn của phần giữa cũ hơn, với truy xuất cho bất cứ điều gì mang tính sự kiện.
Endpoint /v1/responses/compact làm gì?
Nó chuyển tiếp một yêu cầu compaction tới một thượng nguồn hỗ trợ việc cô đọng một cuộc hội thoại Responses thành một biểu diễn nhỏ hơn mà bạn có thể tiếp tục từ đó. Bạn truyền cuộc hội thoại (qua input/instructions và tùy chọn previous_response_id), và nó trả về một output đã được compact và một số đếm usage cho các token mà việc compaction đã tiêu thụ. Nó là một khả năng truyền thẳng — chỉ có sẵn khi thượng nguồn triển khai nó — và nó là phiên bản phía máy chủ của tóm-tắt-và-thay-thế.
Vẽ đường cong rồi làm phẳng nó
Ghi tổng số token của một cuộc hội thoại chạy dài ở mỗi lượt rồi vẽ đường cong. Nếu nó tăng đều, hãy thêm bản tóm tắt cuộn hoặc bước compaction tại một ngưỡng và theo dõi đường cong phẳng lại trong nhật ký sử dụng. Sau đó đánh dấu tiền tố hệ thống là có thể cache và xác nhận chi phí đầu vào mỗi lượt giảm xuống. Hai đường cong trước và sau — số token và chi phí đầu vào — cho biết chính sách ngữ cảnh có thực sự hoạt động trên lưu lượng của bạn hay chỉ đúng trên lý thuyết.