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ớ:

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ả:

ModelContext WindowLatency (p50)Cost/MTokTiết kiệm vs OpenAI
GPT-4.1128K tokens1,200ms$8.0085%+
Claude Sonnet 4.5200K tokens1,500ms$15.0070%+
Gemini 2.5 Flash1M tokens80ms$2.5090%+
DeepSeek V3.2128K tokens45ms$0.4298%+

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: