Hướng dẫn sử dụng Letta (MemGPT) với Python: Xây dựng AI Agent có bộ nhớ dài hạn tự quản lý

Artificial Intelligence tutorial - IT technology blog
Artificial Intelligence tutorial - IT technology blog

2 giờ sáng và sự cố sập bot VIP vì chạm trần Context Window

Đúng 2h15 sáng, kênh Slack trực chiến nổ chuỗi alert đỏ rực: HTTP 400 - ContextWindowExceededError. Nguyên nhân? Một khách hàng VIP nhắn liên tục suốt 3 tuần với hơn 180 tin nhắn tùy biến yêu cầu kỹ thuật. Con bot gục ngay lập tức vì prompt nhồi vượt quá giới hạn 128k token của gpt-4o.

Đây là bài toán kinh điển trên production. LLM vốn là stateless: mỗi request độc lập hoàn toàn với quá khứ. Ngược lại, người dùng luôn mong bot nhớ từng chi tiết nhỏ sau nhiều ngày trò chuyện.

Đặt lên bàn cân 3 kiến trúc duy trì bộ nhớ cho AI Agent

Ngay trong đêm đó, team mình phải mổ xẻ lại 3 hướng tiếp cận phổ biến để tìm lối thoát:

  • 1. Sliding Window (Cửa sổ trượt): Chỉ giữ lại k lượt chat gần nhất (thường là 10-20 message) rồi nhồi thẳng vào prompt. Mọi tin nhắn cũ hơn đều bị cắt bỏ.
  • 2. Vector DB + RAG (Semantic Search History): Lưu từng đoạn hội thoại vào vector database (như Qdrant hay pgvector). Khi có câu hỏi mới, hệ thống embedding rồi kéo top-k đoạn tương đồng đưa vào context.
  • 3. Bộ nhớ phân tầng kiểu OS (Letta / MemGPT): Mô phỏng phân tầng phần cứng máy tính. Core Memory đóng vai trò như RAM, còn Archival và Recall Memory đóng vai trò như ổ cứng SSD. Agent tự quyết định đọc, ghi, xóa hoặc cập nhật bộ nhớ thông qua các tool call nội tại.

So sánh thực chiến: Lỗ hổng của RAG và sức mạnh của Letta

1. Sliding Window: Rẻ, dễ làm nhưng nhanh “mất trí”

Giải pháp này triển khai siêu nhanh, chỉ cần một List hoặc Queue trong Redis. Điểm trừ là sau 15-20 lượt trao đổi, mọi thông tin quan trọng ban đầu như budget dự án, tech stack hay địa chỉ đều bay màu khỏi context window. Bot sẽ hỏi lại những câu rất ngô nghê, khiến khách hàng ức chế.

2. Vector DB RAG: Tìm đúng từ khóa nhưng mù mờ về dòng thời gian

RAG lưu trữ được lượng dữ liệu khổng lồ. Tuy nhiên, nó xử lý việc ghi đè trạng thái (state mutation) cực kỳ vụng về. Lấy ví dụ thực tế:

  • Tuần trước user nhắn: “Tôi đang dùng PostgreSQL 14 trên AWS RDS”.
  • Hôm nay user báo: “Hệ thống vừa migrate sang CockroachDB rồi nhé”.

RAG vector search thông thường sẽ query ra cả hai mẩu tin với điểm similarity xấp xỉ nhau. LLM nhận prompt chứa cả hai thông tin trái ngược và bắt đầu hallucinate, không biết đâu mới là thực tế hiện tại.

3. Letta: Kiến trúc bộ nhớ tự quản lý 3 tầng

Letta (tiền thân là dự án nghiên cứu MemGPT của UC Berkeley) giải quyết tận gốc bài toán này bằng cách chia bộ nhớ thành 3 phân vùng:

  • Core Memory (RAM): Gồm human block (thông tin user) và persona block (tính cách bot). Khối này luôn nằm trực tiếp trong context window của mọi request, bị giới hạn kích thước (thường vài nghìn token) để tối ưu chi phí.
  • Recall Memory: Nhật ký toàn bộ message theo thứ tự thời gian, hỗ trợ lọc và phân trang.
  • Archival Memory (Ổ cứng): Kho lưu trữ dài hạn không giới hạn dung lượng, hỗ trợ tìm kiếm ngữ nghĩa qua embedding.

Điểm khác biệt cốt lõi: Agent trong Letta sở hữu quyền tự sửa đổi bộ nhớ (Memory Editing Tools). Khi user thông báo đổi stack, agent lập tức gọi hàm core_memory_replace để ghi đè dữ liệu mới vào Core Memory. Không cần dev phải viết thêm cron job hay câu lệnh SQL nào.

Triển khai thực tế: Dựng Letta Agent với Python

Các bước dưới đây giúp bạn dựng một agent có bộ nhớ tự cập nhật từ local lên server.

Bước 1: Cài đặt Letta SDK và môi trường

Tạo một virtual environment sạch rồi cài đặt package letta:

python3 -m venv letta-env
source letta-env/bin/activate

pip install letta

Bước 2: Cấu hình API Key và khởi chạy Letta Server

Letta hoạt động theo mô hình Client – Server. Letta Server chịu trách nhiệm quản lý state, kết nối database (mặc định dùng SQLite cục bộ hoặc PostgreSQL trên production) và điều phối background tasks:

export OPENAI_API_KEY="sk-proj-your-api-key-here"

# Khởi chạy server nền local (port mặc định 8283)
letta server

Bước 3: Khởi tạo Agent và nạp Core Memory ban đầu

Tạo file agent_demo.py. Chúng ta sẽ kết nối tới server và định nghĩa thông tin ban đầu cho agent:

from letta import create_client

# Kết nối tới Letta server đang chạy trên localhost:8283
client = create_client()

# Tạo agent kèm hai khối Core Memory: persona và human
agent_state = client.create_agent(
    name="devops_support_agent",
    memory={
        "persona": "Tôi là kỹ sư SRE hỗ trợ hạ tầng. Phong cách trả lời ngắn gọn, ưu tiên command thực thi và phân tích root-cause từ log.",
        "human": "Khách hàng tên Nam, vị trí DevOps Lead, đang vận hành cụm Kubernetes production 40 nodes trên GCP."
    }
)

print(f"Agent khởi tạo thành công! ID: {agent_state.id}")

Bước 4: Kiểm chứng tính năng tự ghi đè bộ nhớ

Bây giờ, gửi hai tin nhắn liên tiếp để kiểm tra xem agent có tự phát hiện thay đổi và cập nhật RAM của nó hay không:

from letta import create_client

client = create_client()
# Điền ID lấy được từ Bước 3
agent_id = "agent-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"

# Message 1: Đưa ra thông tin cập nhật hạ tầng mới
response = client.send_message(
    agent_id=agent_id,
    role="user",
    message="Team tôi vừa dọn sạch Kubernetes rồi, toàn bộ dịch vụ đã chuyển sang chạy HashiCorp Nomad nhé!"
)

for msg in response.messages:
    # In suy nghĩ nội tâm của LLM khi nó quyết định gọi memory tool
    if hasattr(msg, 'message_type') and msg.message_type == 'reasoning_message':
        print(f"[LLM Reasoning]: {msg.reasoning}")
    elif hasattr(msg, 'text') and msg.text:
        print(f"[Agent]: {msg.text}")

# Message 2: Thử thách lại trí nhớ của bot
response2 = client.send_message(
    agent_id=agent_id,
    role="user",
    message="Hạ tầng chính hiện tại của tôi gồm những gì?"
)

for msg in response2.messages:
    if hasattr(msg, 'text') and msg.text:
        print(f"[Agent]: {msg.text}")

Bước 5: Trích xuất trực tiếp Core Memory để xác nhận

Không chỉ kiểm tra qua câu trả lời văn bản, bạn hãy gọi trực tiếp API để xem giá trị lưu trong database:

current_memory = client.get_core_memory(agent_id=agent_id)
print("\n=== DỮ LIỆU CORE MEMORY TRONG DB ===")
for block in current_memory.blocks:
    print(f"[{block.label}]: {block.value}")

Nhìn vào log console, bạn sẽ thấy block human đã được cập nhật thành HashiCorp Nomad. Toàn bộ quá trình diễn ra qua tool call core_memory_replace ngầm. Context window của mỗi request gửi lên OpenAI luôn được giữ cố định quanh mức 2.000 – 3.000 token, giúp loại bỏ hoàn toàn lỗi tràn context đồng thời tiết kiệm 70-80% chi phí API token so với việc dồn nén toàn bộ lịch sử chat.

Share: