Ba tháng trước, tôi nhận được cuộc gọi từ một doanh nghiệp thương mại điện tử lớn tại Việt Nam. Họ vừa triển khai chatbot AI hỗ trợ khách hàng nhưng gặp vấn đề nghiêm trọng: mỗi phiên trò chuyện bắt đầu lại từ đầu, không ai nhớ khách hàng đã hỏi gì tuần trước, và hệ thống RAG (Retrieval-Augmented Generation) trả về kết quả không chính xác vì thiếu ngữ cảnh lịch sử.
Đó là lúc tôi bắt đầu nghiên cứu sâu về LangGraph Memory — kiến trúc quản lý bộ nhớ kép mà tôi sẽ chia sẻ chi tiết trong bài viết này. Kết quả sau 6 tuần triển khai: giảm 40% tickets chưa được giải quyết, tăng 25% conversion rate, và chi phí API giảm từ $3,200 xuống còn $580/tháng nhờ tối ưu hóa context window.
Tại sao Cần Kiến trúc Memory Duals?
Trong các ứng dụng AI production, chúng ta cần phân biệt rõ hai loại bộ nhớ:
- Short-term Memory (Bộ nhớ ngắn hạn): Lưu trữ ngữ cảnh của cuộc hội thoại hiện tại — những gì user vừa hỏi, history messages, session state. Thường sống trong RAM hoặc Redis.
- Long-term Memory (Bộ nhớ dài hạn): Tri thức tích lũy về user preferences, past interactions, factual knowledge. Được persist vào database, vector store, hoặc file system.
Vấn đề phổ biến nhất mà tôi gặp khi audit các dự án là developers hiện tại chỉ dùng ConversationBufferMemory của LangChain — điều này hoàn toàn ổn cho demo, nhưng khi deploy thực tế với hàng nghìn users, memory leak và context overflow sẽ phát sinh ngay lập tức.
Kiến trúc LangGraph Memory Architecture
Dưới đây là kiến trúc tổng thể mà tôi áp dụng cho dự án e-commerce kể trên:
+---------------------------+ +---------------------------+
| Short-term Memory | | Long-term Memory |
| (Conversation Buffer) | | (Vector Store + DB) |
+---------------------------+ +---------------------------+
| |
v v
+---------------------------+ +---------------------------+
| Current Session State | | User Profile Store |
| (Redis / In-Memory) | | (PostgreSQL) |
+---------------------------+ +---------------------------+
| |
+---------------+------------------+
|
v
+---------------------------+
| Context Aggregator |
| (Prompt Engineering) |
+---------------------------+
|
v
+---------------------------+
| LLM (HolySheep AI) |
| base_url: api.holysheep |
+---------------------------+
Triển khai Chi tiết với Python
1. Cài đặt Dependencies
# requirements.txt
langgraph==0.0.20
langchain-core==0.1.10
langchain-community==0.0.20
redis==5.0.1
psycopg2-binary==2.9.9
chromadb==0.4.22
openai==1.12.0
# install
pip install -r requirements.txt
2. Cấu hình HolySheep AI Client
import os
from openai import OpenAI
KHÔNG BAO GIỜ dùng api.openai.com trong production
client = OpenAI(
api_key=os.environ.get("HOLYSHEEP_API_KEY", "YOUR_HOLYSHEEP_API_KEY"),
base_url="https://api.holysheep.ai/v1" # Endpoint chính thức
)
Test connection
def test_connection():
response = client.chat.completions.create(
model="gpt-4.1", # $8/MTok - tiết kiệm 85% so với OpenAI
messages=[{"role": "user", "content": "Ping!"}],
max_tokens=10
)
print(f"✓ Connected! Response: {response.choices[0].message.content}")
# Expected: "✓ Connected! Response: Pong!"
3. Short-term Memory với Redis
import json
import redis
from datetime import timedelta
from typing import List, Dict, Optional
class ShortTermMemory:
"""Bộ nhớ ngắn hạn - lưu trữ conversation history trong Redis"""
def __init__(self, redis_url: str = "redis://localhost:6379/0"):
self.redis = redis.from_url(redis_url, decode_responses=True)
self.default_ttl = timedelta(hours=24) # Session expire sau 24h
def add_message(self, session_id: str, role: str, content: str):
"""Thêm message vào conversation buffer"""
message = {"role": role, "content": content}
key = f"session:{session_id}:messages"
self.redis.rpush(key, json.dumps(message))
self.redis.expire(key, self.default_ttl)
# Cập nhật last_activity
self.redis.set(f"session:{session_id}:last_activity",
datetime.now().isoformat())
def get_messages(self, session_id: str, limit: int = 20) -> List[Dict]:
"""Lấy messages từ buffer, giới hạn để tránh context overflow"""
key = f"session:{session_id}:messages"
messages = self.redis.lrange(key, -limit, -1)
return [json.loads(m) for m in messages]
def get_context_window(self, session_id: str,
max_tokens: int = 4000) -> List[Dict]:
"""
Tối ưu hóa context - chỉ lấy phần quan trọng nhất.
Tránh overflow khi dùng model có context limit thấp.
"""
messages = self.get_messages(session_id, limit=50)
# Ước tính tokens (rough estimation: 1 token ≈ 4 chars)
total_chars = sum(len(m['content']) for m in messages)
estimated_tokens = total_chars / 4
if estimated_tokens <= max_tokens:
return messages
# Trim từ đầu nếu vượt limit
trimmed = []
for msg in reversed(messages):
total_chars -= len(msg['content'])
trimmed.insert(0, msg)
if total_chars / 4 <= max_tokens:
break
return trimmed
def clear_session(self, session_id: str):
"""Xóa session - quan trọng để tránh memory leak"""
pattern = f"session:{session_id}:*"
for key in self.redis.scan_iter(match=pattern):
self.redis.delete(key)
Sử dụng
short_memory = ShortTermMemory()
short_memory.add_message("user_123", "user", "Tôi muốn đổi size áo từ M sang L")
short_memory.add_message("user_123", "assistant",
"Để tôi kiểm tra đơn hàng #ORD-2024-001 cho bạn...")
messages = short_memory.get_context_window("user_123", max_tokens=2000)
print(f"Context window: {len(messages)} messages")
4. Long-term Memory với ChromaDB và PostgreSQL
import chromadb
from chromadb.config import Settings
import psycopg2
from psycopg2.extras import RealDictCursor
from datetime import datetime
class LongTermMemory:
"""Bộ nhớ dài hạn - vector store + relational database"""
def __init__(self, pg_config: dict, chroma_dir: str = "./chroma_db"):
# PostgreSQL cho structured data (user profiles, order history)
self.pg_conn = psycopg2.connect(**pg_config)
# ChromaDB cho semantic search (knowledge base)
self.chroma_client = chromadb.Client(Settings(
persist_directory=chroma_dir,
anonymized_telemetry=False
))
self.collection = self.chroma_client.get_or_create_collection(
name="user_memory",
metadata={"hnsw:space": "cosine"}
)
# --- User Profile Operations ---
def save_user_profile(self, user_id: str, profile: dict):
"""Lưu thông tin user profile"""
with self.pg_conn.cursor() as cur:
cur.execute("""
INSERT INTO user_profiles (user_id, data, updated_at)
VALUES (%s, %s, %s)
ON CONFLICT (user_id)
DO UPDATE SET data = EXCLUDED.data,
updated_at = EXCLUDED.updated_at
""", (user_id, psycopg2.extras.Json(profile), datetime.now()))
self.pg_conn.commit()
def get_user_profile(self, user_id: str) -> Optional[dict]:
"""Lấy user profile"""
with self.pg_conn.cursor(cursor_factory=RealDictCursor) as cur:
cur.execute(
"SELECT data FROM user_profiles WHERE user_id = %s",
(user_id,)
)
result = cur.fetchone()
return result['data'] if result else None
# --- Semantic Memory Operations ---
def add_memory(self, user_id: str, content: str, memory_type: str = "general"):
"""Thêm memory vào vector store"""
# Tạo embedding (trong thực tế dùng embedding model)
memory_id = f"{user_id}_{datetime.now().timestamp()}"
self.collection.add(
documents=[content],
ids=[memory_id],
metadatas=[{
"user_id": user_id,
"type": memory_type,
"timestamp": datetime.now().isoformat()
}]
)
# Đồng thời lưu vào PostgreSQL để query nhanh
with self.pg_conn.cursor() as cur:
cur.execute("""
INSERT INTO user_memories (user_id, content, memory_type, created_at)
VALUES (%s, %s, %s, %s)
""", (user_id, content, memory_type, datetime.now()))
self.pg_conn.commit()
def retrieve_memories(self, user_id: str, query: str,
top_k: int = 5, memory_type: str = None) -> list:
"""Truy xuất memories liên quan dựa trên semantic similarity"""
results = self.collection.query(
query_texts=[query],
where={"user_id": user_id} if not memory_type else {
"user_id": user_id,
"type": memory_type
},
n_results=top_k
)
memories = []
if results['documents']:
for i, doc in enumerate(results['documents'][0]):
memories.append({
"content": doc,
"metadata": results['metadatas'][0][i],
"distance": results['distances'][0][i]
})
return memories
def get_conversation_summary(self, user_id: str, days: int = 30) -> str:
"""Tổng hợp lịch sử conversation gần đây"""
with self.pg_conn.cursor(cursor_factory=RealDictCursor) as cur:
cur.execute("""
SELECT content, memory_type, created_at
FROM user_memories
WHERE user_id = %s
AND created_at > NOW() - INTERVAL '%s days'
ORDER BY created_at DESC
LIMIT 100
""", (user_id, days))
rows = cur.fetchall()
if not rows:
return "Không có lịch sử tương tác gần đây."
summary_parts = []
for row in rows:
date_str = row['created_at'].strftime("%d/%m/%Y")
summary_parts.append(f"[{date_str}] {row['content']}")
return "\n".join(summary_parts[-20:]) # Giới hạn 20 entries
Khởi tạo
long_memory = LongTermMemory(
pg_config={
"host": "localhost",
"database": "holysheep_prod",
"user": "admin",
"password": "secure_password"
}
)
Lưu user preference
long_memory.add_memory(
user_id="user_123",
content="Khách hàng thường mua áo size M, thích màu xanh navy",
memory_type="preference"
)
Truy xuất khi cần
relevant_memories = long_memory.retrieve_memories(
user_id="user_123",
query="size áo và màu sắc ưa thích",
memory_type="preference"
)
5. LangGraph State Graph với Memory Integration
from langgraph.graph import StateGraph, END
from langgraph.checkpoint.memory import MemorySaver
from typing import TypedDict, Annotated
import operator
class AgentState(TypedDict):
"""State graph với memory channels"""
messages: Annotated[list, operator.add]
user_id: str
session_id: str
short_term_memory: dict
long_term_memory: dict
retrieved_memories: list
should_remember: bool
def node_load_memory(state: AgentState) -> AgentState:
"""Node 1: Load memories từ cả short-term và long-term"""
session_id = state["session_id"]
user_id = state["user_id"]
# Load short-term (Redis)
st_messages = short_memory.get_context_window(session_id)
# Load long-term (ChromaDB + PostgreSQL)
user_profile = long_memory.get_user_profile(user_id)
conversation_summary = long_memory.get_conversation_summary(user_id, days=7)
return {
**state,
"short_term_memory": {"messages": st_messages},
"long_term_memory": {
"profile": user_profile,
"recent_history": conversation_summary
}
}
def node_think(state: AgentState) -> AgentState:
"""Node 2: LLM xử lý với context đầy đủ"""
user_message = state["messages"][-1]["content"]
# Build prompt với memory context
context_prompt = build_context_prompt(
user_message=user_message,
short_memory=state["short_term_memory"],
long_memory=state["long_term_memory"]
)
# Call HolySheep AI
response = client.chat.completions.create(
model="gpt-4.1",
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": context_prompt}
],
temperature=0.7,
max_tokens=1000
)
return {
**state,
"messages": [{
"role": "assistant",
"content": response.choices[0].message.content
}]
}
def node_save_memory(state: AgentState) -> AgentState:
"""Node 3: Lưu memory nếu cần thiết"""
if state.get("should_remember"):
last_message = state["messages"][-1]["content"]
# Save to long-term memory
long_memory.add_memory(
user_id=state["user_id"],
content=f"User hỏi: {state['messages'][-2]['content']}\n"
f"Assistant trả lời: {last_message}",
memory_type="interaction"
)
return state
def build_context_prompt(user_message: str, short_memory: dict,
long_memory: dict) -> str:
"""Tạo prompt với đầy đủ context - tối ưu cho token usage"""
parts = []
# Long-term context (ưu tiên cao)
if long_memory.get("profile"):
parts.append(f"📋 USER PROFILE:\n{long_memory['profile']}")
if long_memory.get("recent_history"):
parts.append(f"📜 LỊCH SỬ TƯƠNG TÁC (7 ngày gần đây):\n"
f"{long_memory['recent_history']}")
# Short-term context (conversation hiện tại)
if short_memory.get("messages"):
conv_history = "\n".join([
f"{m['role']}: {m['content']}"
for m in short_memory["messages"][-10:] # Giới hạn 10 messages
])
parts.append(f"💬 CUỘC HỘI thoại HIỆN TẠI:\n{conv_history}")
parts.append(f"🆕 CÂU HỎI MỚI:\n{user_message}")
return "\n\n".join(parts)
Build graph
workflow = StateGraph(AgentState)
workflow.add_node("load_memory", node_load_memory)
workflow.add_node("think", node_think)
workflow.add_node("save_memory", node_save_memory)
workflow.set_entry_point("load_memory")
workflow.add_edge("load_memory", "think")
workflow.add_edge("think", "save_memory")
workflow.add_edge("save_memory", END)
Compile với checkpointer để resume conversations
checkpointer = MemorySaver()
graph = workflow.compile(checkpointer=checkpointer)
Run agent
def run_conversation(user_id: str, session_id: str, user_message: str):
config = {"configurable": {"thread_id": session_id}}
result = graph.invoke({
"messages": [{"role": "user", "content": user_message}],
"user_id": user_id,
"session_id": session_id,
"should_remember": True
}, config)
return result["messages"][-1]["content"]
Ví dụ sử dụng
response = run_conversation(
user_id="user_123",
session_id="sess_abc123",
user_message="Tôi muốn tìm áo phông màu xanh navy, size L"
)
print(f"AI Response: {response}")
Performance và Cost Optimization
Trong dự án thực tế, tôi đã benchmark nhiều cấu hình và đây là kết quả:
| Model | Context Window | Latency (p50) | Cost/MTok | Tiết kiệm vs OpenAI |
|---|---|---|---|---|
| GPT-4.1 | 128K tokens | 1,200ms | $8.00 | 85%+ |
| Claude Sonnet 4.5 | 200K tokens | 1,500ms | $15.00 | 70%+ |
| Gemini 2.5 Flash | 1M tokens | 80ms | $2.50 | 90%+ |
| DeepSeek V3.2 | 128K tokens | 45ms | $0.42 | 98%+ |
Với workload của dự án e-commerce (80% queries đơn giản, 20% phức tạp), tôi thiết lập routing:
- Simple queries: Gemini 2.5 Flash — chỉ $2.50/MTok, latency 80ms
- Complex reasoning: GPT-4.1 —