Thực hành nén context: giữ agent chạy phiên dài mà không quên việc đang làm
Cắt bớt lịch sử là cách dễ nhất để agent không bị tràn context, nhưng cắt sai chỗ thì lượt gọi model kế tiếp có thể lỗi, nên bạn cần một vòng nén làm có chủ đích và đo được kết quả.
- 1Đo tokenĐếm token mỗi lượt, kích hoạt khi vượt ngưỡng thấp hơn giới hạn thật
- 2Xoá kết quả tool cũThay output thô đã dùng xong bằng placeholder, giữ nguyên cấu trúc lịch sử
- 3Cắt an toànKhông tách tool call khỏi tool result để lịch sử vẫn hợp lệ
- 4Tóm tắt tăng dầnCập nhật bản tóm tắt cũ bằng phần mới; ưu tiên recall trước, precision sau
- 5Ghi note ra ngoàiLưu quyết định và ID quan trọng vào bộ nhớ nằm ngoài context
- 6Kiểm tra tóm tắtĐo bằng nhiều thước đo: recall, tỉ lệ nén, khẳng định không có nguồn
Dùng cách rẻ và an toàn trước, chỉ tóm tắt khi cần, và luôn kiểm tra bản tóm tắt.
Đồ hoạ: FDE Times
Tóm tắt nhanh
- Context dài vừa tốn compute (tăng theo bình phương độ dài chuỗi), vừa khiến model dùng kém thông tin nằm ở giữa. Vì vậy nén có chủ đích tốt hơn nhồi hết lịch sử vào.
- Nên đi từ nhẹ đến nặng: xoá kết quả tool cũ, cắt nhưng giữ nguyên cặp tool call/result, tóm tắt tăng dần, rồi ghi note ra ngoài context.
- Bản tóm tắt nào cũng có thể làm mất chi tiết hoặc bịa thêm, nên cần kiểm tra bằng nhiều thước đo chứ không chỉ một.
Thử hình dung một agent xử lý ticket cho khách hàng logistics. Đến bước thứ 60, nó gọi lại một API đã gọi từ bước 5, vì kết quả cũ đã bị cắt mất hoặc bị chôn giữa hàng chục đoạn JSON. Model không kém đi. Vấn đề là context của nó bị quản lý tệ.
Hàng chục đoạn JSON đó gây hại theo hai cách. Lượt gọi model nào cũng phải mang theo chúng, trong khi IBM giải thích rằng nhu cầu compute tăng theo bình phương độ dài chuỗi.
Còn kết quả của bước 5 lại nằm đúng chỗ model đọc kém nhất: IBM gọi đó là hiện tượng “lost in the middle”, tức model làm kém hơn khi phải xử lý cẩn thận thông tin nằm giữa một context dài.
Những đoạn JSON đã dùng xong cũng chẳng vô hại. Redis cảnh báo rằng context window chỉ chứa được một lượng có hạn, và thông tin không liên quan làm bẩn context, kéo chất lượng suy luận của agent xuống. Vì thế, muốn agent chạy lâu thì đừng tìm cách nhồi thêm lịch sử. Việc cần làm là chọn xem giữ lại cái gì.
FDE dễ gặp đúng tình huống này: agent chạy mười lượt trên môi trường demo rất ổn, sang môi trường khách hàng chạy ba tiếng thì bắt đầu quên. Hướng dẫn dưới đây dựng một vòng compaction sáu bước, đi từ cách nhẹ nhất đến cách nặng nhất.
Bạn sẽ dựng gì, và cần chuẩn bị gì?
Sản phẩm cuối là một hàm manage_context(messages) chạy trước mỗi lượt gọi model. Bạn cần Python, một agent có gọi tool và lưu lịch sử dạng list các message {"role", "content"}, cùng một hàm gọi LLM. Code bên dưới là bản rút gọn để học: count_tokens và call_llm chỉ là placeholder, bạn thay bằng tokenizer và SDK của model mình đang dùng.
Bước 1: đo trước, rồi mới nén
Tài liệu LangChain mô tả chiến lược cơ bản là đếm token trong lịch sử message và cắt khi gần chạm giới hạn. Nên đặt ngưỡng kích hoạt thấp hơn giới hạn thật để còn chỗ cho chính lượt tóm tắt.
LIMIT = 100_000 # ngưỡng bạn tự chọn, nhỏ hơn context window thật
TRIGGER = int(0.8 * LIMIT)
def need_compaction(messages):
return count_tokens(messages) > TRIGGER
Kiểm tra: in số token sau mỗi lượt. Con số thường tăng vọt ngay sau những lần gọi tool trả về nhiều dữ liệu, và đó là chỗ cần xử lý trước.
Bước 2: xoá kết quả tool cũ, cách rẻ và an toàn nhất
Anthropic xếp tool result clearing vào nhóm compaction an toàn và nhẹ tay nhất: output thô của tool đã dùng xong thì bỏ đi. Một khi agent đã đọc hết 5.000 dòng JSON và rút ra kết luận, nó gần như không cần đọc lại bản gốc nữa.
def clear_old_tool_results(messages, keep_last=3):
tool_idx = [i for i, m in enumerate(messages) if m["role"] == "tool"]
for i in tool_idx[:-keep_last]:
messages[i]["content"] = "[kết quả tool cũ đã xoá; xem notes nếu cần]"
return messages
Chi tiết đáng chú ý là hàm này thay nội dung chứ không xoá message. Nhờ vậy cấu trúc lịch sử vẫn còn nguyên, đúng với cảnh báo ở bước tiếp theo. Kiểm tra: chạy lại cùng một kịch bản và so số token trước và sau.
Bước 3: cắt mà không làm hỏng lịch sử
Tài liệu LangChain nhắc rằng khi xoá message khỏi state, phần lịch sử còn lại vẫn phải hợp lệ, chẳng hạn cặp tool call và tool result phải đi cùng nhau. Nếu điểm cắt rơi vào giữa một cặp, lịch sử sẽ mở đầu bằng một tool result mồ côi và lượt gọi model kế tiếp có thể lỗi.
def safe_cut_index(messages, cut):
# đẩy điểm cắt về sau cho tới khi không mở đầu bằng tool result mồ côi
while len(messages) > cut and messages[cut]["role"] == "tool":
cut += 1
return cut
Đây là bản giản lược. Nếu một tool call của bạn trả về nhiều result, hãy ghép cặp theo ID của call chứ đừng chỉ dựa vào role.
Bước 4: tóm tắt, vì cắt suông thì mất thông tin
LangChain cũng thừa nhận việc bỏ bớt message khỏi hàng đợi có thể làm mất thông tin. Vì thế thư viện này có sẵn SummarizationMiddleware, dùng một chat model để tóm tắt lịch sử. Về bản chất, đây chính là compaction theo định nghĩa của Anthropic: tóm tắt cuộc hội thoại sắp đầy rồi chạy tiếp từ bản tóm tắt.
COMPACT_PROMPT = """Tóm tắt phần hội thoại cũ để agent làm tiếp.
GIỮ NGUYÊN: mục tiêu người dùng, quyết định đã chốt, mọi ID/tên file/số liệu,
lỗi chưa xử lý, việc đang làm dở và bước kế tiếp.
BỎ: lời chào, output tool đã dùng xong, các lần thử đã bị loại."""
def compact(messages, old_summary, keep_recent=10):
cut = safe_cut_index(messages, len(messages) - keep_recent)
old, recent = messages[:cut], messages[cut:]
summary = call_llm(COMPACT_PROMPT, previous=old_summary, new=old)
head = {"role": "user", "content": "Tóm tắt phiên trước:\n" + summary}
return [head] + recent, summary
Đoạn code này có hai lựa chọn thiết kế. Lựa chọn đầu tiên: hàm nhận old_summary và chỉ đưa phần mới vào. Redis mô tả cách làm này là cập nhật bản tóm tắt tăng dần mỗi khi có dữ liệu mới, thay vì viết lại từ đầu.
Lựa chọn còn lại nằm ở prompt, được viết theo lời khuyên của Anthropic: trước hết tối đa hoá recall để prompt không bỏ sót thông tin liên quan, sau đó mới tăng precision bằng cách gọt phần thừa.
Thứ tự đó có lý do. Redis chỉ ra cái giá của việc tóm tắt là có thể mất những chi tiết lúc này trông vô nghĩa nhưng về sau lại quan trọng. Thiếu một ID khách hàng gây hại nặng hơn nhiều so với thừa vài câu.
Kết quả trông thế nào? Quay lại agent logistics, với số liệu giả định để minh hoạ. Lịch sử đang ở 85.000 token, vượt ngưỡng TRIGGER 80.000 ở bước 1. Bước 2 không đủ kéo xuống, nên compact chạy và phần đầu context được thay bằng một khối như sau:
Tóm tắt phiên trước:
- Mục tiêu: xử lý ticket giao trễ của khách hàng, trả lời trước cuối ngày
- Đã chốt: không hoàn tiền, đề xuất giao lại miễn phí
- ID: đơn ORD-1042, vận đơn VD-88317
- Lỗi chưa xử lý: API tra cứu vận đơn đã timeout 2 lần
- Bước kế tiếp: thử lại API tra cứu, rồi soạn email cho khách
Năm dòng này thay cho hàng chục lượt hội thoại và tool output. Điều cần soi là các ID còn nguyên: nếu bản tóm tắt ghi “một đơn hàng bị trễ” thay vì ORD-1042, agent sẽ lại gọi API như ở bước 60 trong ví dụ đầu bài. Kiểm tra: in số token ngay sau compact và đọc khối tóm tắt bằng mắt ít nhất vài lần đầu.
Bước 5: đưa trạng thái quan trọng ra ngoài context
Có những thứ không nên phụ thuộc vào chất lượng của một bản tóm tắt. Anthropic gọi kỹ thuật này là structured note-taking, hay agentic memory: agent định kỳ ghi note vào một bộ nhớ nằm ngoài context window.
def write_note(key, value, path="agent_notes.md"):
with open(path, "a", encoding="utf-8") as f:
f.write(f"- {key}: {value}\n")
Hãy đăng ký hàm này làm một tool, rồi dặn trong system prompt rằng mọi quyết định đã chốt đều phải được ghi lại. Placeholder ở bước 2 trỏ về file này nên agent biết chỗ tìm lại.
Với những nhánh việc nặng như đọc cả một kho log, bạn có thể giao cho sub-agent: theo mô tả của Anthropic, sub-agent làm việc trong một context sạch và chỉ gửi về agent chính một bản tóm tắt cô đọng.
Bước 6: bản tóm tắt cũng cần được test
Dừng lại ở bước 5 và mặc định rằng bản tóm tắt ổn là một rủi ro, vì chính bước tóm tắt có thể làm rơi chi tiết hoặc bịa thêm. Nhóm SEI tại Carnegie Mellon cho rằng đánh giá bằng một thước đo duy nhất chưa được chứng minh là hiệu quả, nên cách làm hiện nay là dùng cả một bộ thước đo.
SEI cũng lưu ý nhiều thước đo độ chính xác cần một bản tóm tắt tham chiếu, thứ thường không có sẵn, còn các thước đo độ trung thực thì bắt được một phần lỗi bịa đặt.
Dưới đây là một bộ test tối thiểu (chỉ là gợi ý, không phải chuẩn):
def eval_summary(summary, must_keep, source_text):
recall = sum(k in summary for k in must_keep) / len(must_keep)
ratio = count_tokens(summary) / count_tokens(source_text)
unsupported = call_llm("Liệt kê các khẳng định trong TÓM TẮT không có trong NGUỒN",
summary=summary, source=source_text)
return {"recall": recall, "compression": ratio, "unsupported": unsupported}
must_keep là danh sách bạn tự đánh dấu từ log thật, dùng thay cho bản tóm tắt tham chiếu mà bạn không có. Với ví dụ ở bước 4, danh sách đó chí ít phải có ORD-1042 và VD-88317.
Ba con số này phải đọc cùng nhau: recall cao nhưng tỉ lệ nén kém nghĩa là bạn chưa nén được gì, còn nén tốt mà danh sách unsupported dài thì bản tóm tắt đang bịa.
Những lỗi khiến phiên dài vẫn sập
Lỗi đầu tiên là nhảy thẳng vào tóm tắt trong khi chỉ cần xoá kết quả tool cũ là đủ, vừa tốn thêm một lượt gọi model vừa chịu thêm rủi ro mất chi tiết. Lỗi tiếp theo là cắt theo một số message cố định mà không kiểm tra cặp tool call/result.
Một lỗi khác là lần nào cũng tóm tắt lại từ đầu, khiến chi tiết bị bào mòn dần qua nhiều vòng. Lỗi khó thấy hơn cả là đặt bản tóm tắt vào giữa context. Vì model dùng kém thông tin ở giữa context dài, hãy để bản tóm tắt ở đầu, còn các lượt gần nhất ở cuối.
Kỹ năng này xuất hiện thế nào ở chỗ khách hàng?
Nó thường đến dưới dạng lời phàn nàn: “agent chạy lâu thì ngớ ngẩn”, “chi phí mỗi phiên cao bất thường”. Việc nên làm đầu tiên là xin một log của phiên bị lỗi, vẽ đường token theo từng lượt và tìm xem tool nào làm context phình to.
Khi đọc JD của các vị trí FDE hoặc agent engineer, hãy để ý những cụm như “long-running agents”, “context management”, “memory”. Trong CV, đừng chỉ viết “đã dùng LangChain”. Hãy ghi rõ bạn giảm được bao nhiêu phần trăm token mỗi phiên, giữ recall các chi tiết bắt buộc ở mức nào và đo bằng bộ test gì.
Một agent chạy ba tiếng mà vẫn nhớ ID khách hàng từ phút đầu tiên là kết quả khách hàng cảm nhận được ngay, dù họ sẽ không bao giờ thấy hàm compact nằm phía sau.
5 nguồn
- Effective context engineering for AI agents (Anthropic Engineering) · 2025-09-29
- Short-term memory (LangChain docs)
- Build AI agents with short-term & long-term memory in Redis · 2026-07-01
- What is a context window? (IBM Think)
- Evaluating LLMs for Text Summarization: An Introduction (SEI, CMU) · 2025-04-07