🧠 Memory và quản lý trạng thái trong agent

By Ngọc Vương Trương

Bài trước ta đã chốt chuyện tool schema: ít tool, mô tả rõ, đừng mở quá nhiều tool trong một lượt vì model dễ chọn nhầm và tốn context. Memory là chỗ thứ hai cũng ăn context y hệt, và là chỗ nhiều người làm agent xong mới nhận ra mình đã nhét quá nhiều thứ vào prompt.

Trong 25 phút này bạn sẽ:

  • Phân biệt scratchpad, short-term memory và long-term memory, và biết mỗi tầng sống ở đâu
  • Chọn được chiến lược cắt gọn context phù hợp: sliding window, compaction hay retrieval
  • Biết khi nào chỉ cần state object nhỏ, khi nào mới cần long-term store
  • Nhận ra dấu hiệu memory đang làm agent tệ đi thay vì tốt lên

Ba tầng bộ nhớ: agent nhớ bằng gì?

Tầng Sống ở đâu Vòng đời Ví dụ
Scratchpad trong prompt của lượt hiện tại một task các cặp Thought / Action / Observation của ReAct
Short-term memory context window hoặc state object một session lịch sử hội thoại, kết quả tool đã gọi
Long-term memory ngoài model (DB, vector store, file) nhiều session preference người dùng, tài liệu đã index

Quy tắc dễ nhớ: scratchpad là bộ nhớ làm việc, xoá lúc nào cũng được. Short-term là thứ bạn phải chủ động quản lý. Long-term là thứ bạn phải truy xuất, không phải nhồi vào prompt.

Scratchpad: chỗ agent nghĩ

Trong vòng lặp ReAct, scratchpad là nơi agent ghi lại mình đã nghĩ gì, gọi tool nào, nhận về gì. Nó chỉ cần đủ để agent biết mình đang ở đâu trong task, không cần lưu vĩnh viễn.

Vấn đề xuất hiện khi observation quá to. Ví dụ: agent tra giá 20 sản phẩm, mỗi kết quả tool trả về 2KB JSON, riêng phần observation đã 40KB, chưa tính Thought. Sau bước thứ 10 thì context nổ và chi phí tăng theo.

Cách xử lý quen thuộc: để tool trả về bản tóm tắt kèm id, còn payload đầy đủ thì đẩy ra ngoài.

def compact_observation(raw: dict) -> str:
    # giữ phần agent cần để ra quyết định, phần còn lại tra cứu sau bằng id
    return f"{raw['name']}: {raw['price']} VND (id={raw['id']})"

Short-term memory: cắt gọn context

Đây là phần tốn công nhất. Ba chiến lược thường dùng:

  • Sliding window: giữ N lượt gần nhất, bỏ phần cũ. Rẻ và dễ, nhưng agent rất dễ quên yêu cầu ban đầu.
  • Compaction (summarization): khi context vượt ngưỡng, gọi LLM nén phần hội thoại cũ thành vài bullet, giữ nguyên phần gần.
  • Retrieval: không giữ lịch sử dài, chỉ lấy ra những đoạn liên quan từ long-term store tại thời điểm cần.

Important: Khi cắt gọn context, luôn giữ nguyên system prompt và mục tiêu gốc của task. Cắt mất mục tiêu là lỗi phổ biến nhất khiến agent chạy vòng vo rồi dừng non.

Long-term memory: nhớ ngoài context

Chia làm ba loại cho dễ hình dung:

  • Episodic: chuyện đã xảy ra, ví dụ "lần trước khách này hỏi về hoá đơn tháng 3".
  • Semantic: sự thật và tài liệu, ví dụ knowledge base sản phẩm đã index.
  • Procedural: cách làm, ví dụ "với loại yêu cầu này thì gọi tool A trước tool B".

Một lưu ý thực tế: long-term memory chỉ có giá trị khi retrieval đủ tốt. Ghi vào mà lúc cần không lấy ra được thì nó chỉ là log, không phải memory.

Quản lý trạng thái: tách state ra khỏi messages

Agent làm được việc thường không để toàn bộ trạng thái nằm trong lịch sử chat. Tách ra một object rõ ràng:

state = {
    "goal": "đặt lại lịch họp cho khách hàng A",
    "steps_done": ["xác nhận danh tính", "lấy các khung giờ trống"],
    "facts": {"customer_id": "A-102", "timezone": "Asia/Ho_Chi_Minh"},
    "open_questions": ["khung giờ ưu tiên?"],
    "tool_calls": 7,
}

Lợi ích: dễ debug (nhìn là biết agent đang kẹt ở đâu), dễ đặt giới hạn (ví dụ dừng ở 15 tool calls), dễ resume, và giảm context vì không phải nhét lại toàn bộ lịch sử. Kèm theo đó là checkpoint: lưu state sau mỗi bước để chạy tiếp khi lỗi giữa đường.

Chọn memory theo kiến trúc

Kiến trúc Memory tối thiểu Ghi chú
ReAct cho task ngắn (dưới 10 bước) scratchpad + state object nhỏ chưa cần vector store, đừng dựng sớm
Agent hội thoại nhiều phiên short-term cho session + long-term semantic preference và facts nên tách khỏi lịch sử chat
Plan-and-Execute state chứa plan và tiến độ từng bước memory chính là plan, không phải lịch sử chat
Nhiều agent phối hợp memory chia sẻ có schema rõ tránh hai agent ghi đè lên nhau

Cạm bẫy thường gặp khi thiết kế memory

  • Lưu mọi thứ: memory phình thì retrieval tệ đi. Chỉ lưu thứ bạn hình dung được sẽ dùng lại.
  • Nhồi lịch sử "cho chắc": context dài làm model loãng chú ý và tăng chi phí, không làm nó thông minh hơn.
  • Để ràng buộc cứng trong bản tóm tắt: những câu như "không gọi khách sau 21h" phải nằm trong system prompt, đừng phụ thuộc vào compaction.
  • Đặt ngưỡng mơ hồ: hãy chốt một con số, ví dụ context vượt 60% cửa sổ thì compact.
  • Kiểm tra bằng cách hỏi ngược: sau khi cắt context, agent còn nhớ mục tiêu không? Đó là test memory rẻ nhất.

Ba điều mang theo sang bài đánh giá agent

  1. Scratchpad là tạm, short-term là thứ bạn quản lý, long-term là thứ bạn truy xuất.
  2. Cắt gọn context luôn đi kèm việc giữ nguyên system prompt và mục tiêu gốc.
  3. State object tách khỏi messages giúp agent dễ debug, dễ resume và dễ đặt giới hạn.

Bài tới ta sẽ dùng chính những lựa chọn này làm đầu vào: đo agent bằng metric nào, và dựa vào tiêu chí gì để chốt kiến trúc thay vì chọn theo cảm tính.

Scratchpad sống trong _____ của lượt hiện tại và bị xoá khi task kết thúc. Long-term memory nằm _____ (vector store, database, file) và tồn tại qua nhiều session. Khi cắt gọn context, phải giữ nguyên _____ và mục tiêu gốc của task. Chiến lược _____ (summarization) nén phần hội thoại cũ thành vài bullet khi context vượt ngưỡng. State object giúp agent _____ sau khi lỗi nhờ checkpoint được lưu sau mỗi bước.

Front: Scratchpad trong agent là gì? Back: Vùng làm việc tạm trong prompt của lượt hiện tại, chứa Thought/Action/Observation và bị xoá khi task xong.

Front: Short-term memory thường sống ở đâu? Back: Trong context window hoặc state object của một session.

Front: Long-term memory nằm ở đâu? Back: Ngoài model: vector store, database hoặc file, tồn tại qua nhiều session.

Front: Ba loại long-term memory? Back: Episodic (chuyện đã xảy ra), semantic (sự thật, tài liệu), procedural (cách làm).

Front: Sliding window là gì? Back: Giữ N lượt gần nhất và bỏ phần cũ; rẻ nhưng dễ quên yêu cầu ban đầu.

Front: Compaction (summarization) làm gì? Back: Nén phần hội thoại cũ thành tóm tắt ngắn khi context vượt ngưỡng.

Front: Khi cắt gọn context bắt buộc phải giữ gì? Back: System prompt và mục tiêu gốc của task.

Front: Vì sao tách state object thay vì để hết trong messages? Back: Dễ debug, dễ resume, dễ đặt giới hạn số bước và giảm context phải nhét lại.

Front: Checkpoint state để làm gì? Back: Lưu trạng thái sau mỗi bước để chạy tiếp khi lỗi, quan trọng với task dài.

Front: Dấu hiệu memory đang phản tác dụng? Back: Context phình, chi phí tăng, agent quên mục tiêu hoặc retrieve sai thông tin.

Front: Agent ReAct cho task ngắn cần memory gì? Back: Scratchpad cộng một state object nhỏ; chưa cần vector store.

Front: Long-term memory chỉ có giá trị khi nào? Back: Khi retrieval đủ tốt để lấy đúng thông tin ra dùng lại.

Front: Ràng buộc cứng như giờ không được gọi khách nên nằm ở đâu? Back: Trong system prompt, không phụ thuộc vào bản tóm tắt.