บทนำ: ทำไม Memory ถึงสำคัญสำหรับ AI Agent
ในโลกของ AI Agent ที่ทำงานซ้ำๆ กัน การจดจำบทสนทนาก่อนหน้าเป็นสิ่งจำเป็นอย่างยิ่ง LangGraph นำเสนอสถาปัตยกรรม State Graph ที่ช่วยให้นักพัฒาสามารถสร้าง Agent ที่มีความจำระยะยาวได้อย่างมีประสิทธิภาพ บทความนี้จะพาคุณเข้าใจกลไก Memory ของ LangGraph ตั้งแต่พื้นฐานจนถึงการนำไปใช้จริงใน Production
กรณีศึกษา: ผู้ให้บริการอีคอมเมิร์ซในเชียงใหม่
บริบทธุรกิจ
ทีมพัฒนาอีคอมเมิร์ซรายใหญ่ในเชียงใหม่ต้องการสร้าง AI Customer Support Agent ที่สามารถจดจำประวัติการสั่งซื้อและความชอบของลูกค้าได้ตลอดการสนทนา ทีมมีแชทบอทเก่าที่ต้องตอบคำถามเดิมซ้ำๆ เพราะไม่มี Memory และต้องการยกระดับประสบการณ์ลูกค้า
จุดเจ็บปวดของผู้ให้บริการเดิม
ระบบเดิมใช้ OpenAI API โดยตรง ซึ่งมีค่าใช้จ่ายสูงเมื่อเทียบกับปริมาณงาน บิลรายเดือนอยู่ที่ $4,200 และ Response Time เฉลี่ย 420ms ทำให้ลูกค้าบางส่วนรู้สึกว่าการตอบสนองช้า นอกจากนี้ยังต้องจัดการ Memory ด้วยตัวเองโดยใช้ Redis ซึ่งเพิ่มความซับซ้อนในการดูแลระบบ
การย้ายมาใช้ HolySheep AI
ทีมตัดสินใจย้ายมาใช้ HolySheep AI เพราะอัตราแลกเปลี่ยนที่คุ้มค่า ¥1=$1 ช่วยประหยัดได้ถึง 85% และ Latency เฉลี่ยต่ำกว่า 50ms พร้อมรองรับการชำระเงินผ่าน WeChat และ Alipay สำหรับทีมในเอเชีย ขั้นตอนการย้ายเริ่มจากการเปลี่ยน base_url เป็น https://api.holysheep.ai/v1 การหมุนคีย์ API ใหม่ และการทำ Canary Deploy เพื่อทดสอบระบบทีละ 10%
ตัวชี้วัดหลังการย้าย 30 วัน
ผลลัพธ์ที่ได้น่าประทับใจ: Response Time ลดลงจาก 420ms เหลือ 180ms และค่าใช้จ่ายรายเดือนลดลงจาก $4,200 เหลือ $680 ลูกค้าพึงพอใจมากขึ้นเพราะ Agent ตอบสนองเร็วและจดจำบริบทได้ตลอดการสนทนา
สถาปัตยกรรม Memory ใน LangGraph
การทำงานของ Checkpointers
LangGraph ใช้แนวคิด Checkpointing เพื่อบันทึก State ของ Graph ณ แต่ละ Node ทำให้สามารถกลับมาต่อยอดได้ในภายหลัง กลไกนี้ประกอบด้วยสามส่วนหลัก: Saver Interface สำหรับการบันทึกและโหลด State, Checkpoint Metadata สำหรับเก็บข้อมูลเพิ่มเติม และ Thread Management สำหรับจัดการการสนทนาหลายเธรดพร้อมกัน
Memory Buffers ใน LangGraph
LangGraph มี Memory Buffer หลายประเภทสำหรับกรณีการใช้งานต่างๆ ConversationBufferMemory เก็บข้อความทั้งหมดไว้ในรายการเดียว ConversationBufferWindowMemory เก็บเฉพาะข้อความล่าสุดตามจำนวนที่กำหนด ConversationSummaryMemory สร้างสรุปของการสนทนาที่ผ่านมาเพื่อประหยัด Token และ VectorStoreRetrieverMemory ใช้ Semantic Search เพื่อดึงข้อมูลที่เกี่ยวข้อง
การตั้งค่า Checkpointer กับ HolySheep AI
ส่วนนี้จะแสดงวิธีตั้งค่า LangGraph Memory โดยใช้ HolySheep AI เป็น LLM Provider เราจะใช้ MemorySaver Checkpointer และ SqliteSaver สำหรับ Persistence ใน Production
การติดตั้ง Dependencies
pip install langgraph langgraph-checkpoint sqlalchemy langchain-core langchain-holysheep
พื้นฐาน Checkpointing กับ LangGraph
import os
from langchain_holysheep import HolySheepChat
from langgraph.checkpoint.memory import MemorySaver
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from typing import TypedDict, Annotated
กำหนด State Schema สำหรับ Graph
class ConversationState(TypedDict):
messages: Annotated[list, add_messages]
summary: str
เชื่อมต่อกับ HolySheep AI
os.environ["HOLYSHEEP_API_KEY"] = "YOUR_HOLYSHEEP_API_KEY"
llm = HolySheepChat(
model="gpt-4.1",
base_url="https://api.holysheep.ai/v1",
temperature=0.7,
api_key=os.environ["HOLYSHEEP_API_KEY"]
)
สร้าง Memory Checkpointer
checkpointer = MemorySaver()
def conversation_node(state: ConversationState):
"""Node สำหรับสนทนากับ LLM"""
messages = state["messages"]
response = llm.invoke(messages)
return {"messages": [response]}
def should_continue(state: ConversationState) -> str:
"""กำหนดเส้นทางการทำงาน"""
messages = state["messages"]
if len(messages) > 10:
return "summarize"
return END
def summarize_node(state: ConversationState):
"""Node สำหรับสร้างสรุปการสนทนา"""
summary_prompt = f"สรุปการสนทนาต่อไปนี้: {state['messages']}"
summary = llm.invoke(summary_prompt)
return {"summary": summary.content, "messages": []}
สร้าง Graph
graph = StateGraph(ConversationState)
graph.add_node("conversation", conversation_node)
graph.add_node("summarize", summarize_node)
graph.add_edge(START, "conversation")
graph.add_conditional_edges("conversation", should_continue)
graph.add_edge("summarize", END)
Compile พร้อม Checkpointer
app = graph.compile(checkpointer=checkpointer)
ทดสอบการสนทนา
config = {"configurable": {"thread_id": "user-123"}}
รอบที่ 1
result = app.invoke(
{"messages": [{"role": "user", "content": "สวัสดี ผมสนใจซื้อ laptop"}]},
config
)
print(result["messages"][-1].content)
การใช้ SQLite Checkpointer สำหรับ Production
from langgraph.checkpoint.sqlite import SqliteSaver
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage
สร้าง SQLite Checkpointer สำหรับ Production
checkpointer = SqliteSaver.from_conn_string(":memory:")
ระบบ Prompt พร้อม Memory Integration
SYSTEM_PROMPT = """คุณเป็น AI Sales Agent สำหรับร้านค้าออนไลน์
คุณต้องจำข้อมูลลูกค้าและแนะนำสินค้าที่เหมาะสม
ใช้ข้อมูลจาก Memory ประกอบการตัดสินใจ
"""
def get_memory_context(thread_id: str) -> str:
"""ดึงข้อมูล Memory สำหรับ Thread นี้"""
checkpoint = checkpointer.get({"configurable": {"thread_id": thread_id}})
if checkpoint and checkpoint.get("channel_values"):
messages = checkpoint["channel_values"].get("messages", [])
return f"ประวัติการสนทนาก่อนหน้า: {messages[-5:]}"
return "ไม่มีประวัติการสนทนาก่อนหน้า"
def create_agent_graph():
"""สร้าง Agent Graph พร้อม Memory"""
def agent_node(state: ConversationState):
thread_id = state.get("config", {}).get("configurable", {}).get("thread_id", "default")
memory_context = get_memory_context(thread_id)
system_message = SystemMessage(content=f"{SYSTEM_PROMPT}\n\n{memory_context}")
all_messages = [system_message] + state["messages"]
response = llm.invoke(all_messages)
return {"messages": [response]}
def route_after_conversation(state: ConversationState) -> str:
"""ตรวจสอบว่าควรจบหรือทำงานต่อ"""
messages = state["messages"]
last_message = messages[-1].content.lower() if messages else ""
if any(word in last_message for word in ["ขอบคุณ", "ลาก่อน", "จบการสนทนา"]):
return END
return "conversation"
graph = StateGraph(ConversationState)
graph.add_node("conversation", agent_node)
graph.add_edge(START, "conversation")
graph.add_conditional_edges("conversation", route_after_conversation)
return graph.compile(checkpointer=checkpointer)
รัน Agent
agent = create_agent_graph()
config = {"configurable": {"thread_id": "customer-session-456"}}
สนทนาครั้งที่ 1
response1 = agent.invoke(
{"messages": [HumanMessage(content="สวัสดีครับ กำลังมองหา laptop สำหรับเขียนโปรแกรม")]},
config
)
สนทนาต่อใน Thread เดิม (Memory จะถูกโหลดอัตโนมัติ)
response2 = agent.invoke(
{"messages": [HumanMessage(content="งบประมาณอยู่ที่ 30000-40000 บาท")]},
config
)
Advanced Memory Patterns
การใช้ VectorStore สำหรับ Semantic Memory
from langgraph.checkpoint.postgres import PostgresSaver
from langchain_holysheep import HolySheepEmbeddings
from langchain_core.documents import Document
import json
class SemanticMemory:
"""ระบบ Semantic Memory ที่ใช้ Vector Search"""
def __init__(self, thread_id: str):
self.thread_id = thread_id
self.embeddings = HolySheepEmbeddings(
model="text-embedding-3-small",
base_url="https://api.holysheep.ai/v1",
api_key="YOUR_HOLYSHEEP_API_KEY"
)
self.vector_store = [] # ใช้ In-Memory Store เพื่อความเรียบง่าย
def add_memory(self, content: str, metadata: dict = None):
"""เพิ่ม Memory ใหม่พร้อม Embedding"""
embedding = self.embeddings.embed_query(content)
self.vector_store.append({
"content": content,
"embedding": embedding,
"metadata": metadata or {},
"thread_id": self.thread_id
})
def search_similar(self, query: str, top_k: int = 3) -> list:
"""ค้นหา Memory ที่เกี่ยวข้อง"""
query_embedding = self.embeddings.embed_query(query)
similarities = []
for item in self.vector_store:
if item["thread_id"] == self.thread_id:
sim = self._cosine_similarity(query_embedding, item["embedding"])
similarities.append((sim, item))
similarities.sort(key=lambda x: x[0], reverse=True)
return similarities[:top_k]
@staticmethod
def _cosine_similarity(a: list, b: list) -> float:
"""คำนวณ Cosine Similarity"""
dot_product = sum(x * y for x, y in zip(a, b))
norm_a = sum(x * x for x in a) ** 0.5
norm_b = sum(x * x for x in b) ** 0.5
return dot_product / (norm_a * norm_b + 1e-8)
ตัวอย่างการใช้งาน
memory = SemanticMemory(thread_id="user-789")
บันทึกข้อมูลลูกค้า
memory.add_memory(
"ลูกค้าชื่อ สมชาย สนใจสินค้าประเภท electronics",
metadata={"type": "customer_info", "timestamp": "2026-01-15"}
)
ค้นหาข้อมูลที่เกี่ยวข้อง
results = memory.search_similar("ข้อมูลลูกค้าและความสนใจ")
for score, item in results:
print(f"ความเกี่ยวข้อง: {score:.3f} - {item['content']}")
การสร้าง Agent ที่จดจำได้หลาย Session
from datetime import datetime
from typing import Optional
class PersistentAgent:
"""Agent ที่รักษา Memory ข้าม Session"""
def __init__(self, user_id: str):
self.user_id = user_id
self.checkpointer = MemorySaver()
self.conversation_history = []
self.user_preferences = {}
self.load_user_memory()
def load_user_memory(self):
"""โหลด Memory จาก Checkpointer"""
checkpoint = self.checkpointer.get({
"configurable": {"thread_id": f"user-{self.user_id}"}
})
if checkpoint:
self.conversation_history = checkpoint.get("messages", [])
self.user_preferences = checkpoint.get("preferences", {})
def save_interaction(self, user_message: str, agent_response: str):
"""บันทึก Interaction ลง Memory"""
self.conversation_history.append({
"timestamp": datetime.now().isoformat(),
"user": user_message,
"agent": agent_response
})
# อัปเดต User Preferences หากพบข้อมูลใหม่
if "ชอบ" in user_message or "สนใจ" in user_message:
self._extract_preference(user_message)
def _extract_preference(self, text: str):
"""ดึง Preference จากข้อความ"""
# Logic สำหรับดึง Keywords ที่เกี่ยวข้อง
keywords = ["electronics", "fashion", "food", "sports"]
for keyword in keywords:
if keyword in text.lower():
self.user_preferences[keyword] = datetime.now().isoformat()
def get_context_summary(self) -> str:
"""สร้าง Context Summary สำหรับ LLM"""
if not self.conversation_history:
return "ไม่มีประวัติการสนทนาก่อนหน้านี้"
recent = self.conversation_history[-5:]
summary = "ประวัติการสนทนาล่าสุด:\n"
for idx, msg in enumerate(recent, 1):
summary += f"{idx}. ถาม: {msg['user'][:50]}...\n"
summary += f" ตอบ: {msg['agent'][:50]}...\n"
if self.user_preferences:
summary += f"\nความชอบของผู้ใช้: {list(self.user_preferences.keys())}"
return summary
การใช้งาน
agent = PersistentAgent(user_id="user_001")
print(agent.get_context_summary())
ข้อผิดพลาดที่พบบ่อยและวิธีแก้ไข
กรณีที่ 1: Memory ไม่ถูกโหลดหลังจาก Restart
อาการ: เมื่อรีสตาร์ทรวจเซิร์ฟเวอร์ Memory หายไปทั้งหมด แม้ว่าจะใช้ thread_id เดิม
สาเหตุ: ใช้ MemorySaver ซึ่งเป็น In-Memory Storage ข้อมูลจะหายเมื่อ Process สิ้นสุด
วิธีแก้ไข:
# แก้ไข: เปลี่ยนจาก MemorySaver เป็น Persistent Checkpointer
ก่อนหน้า (ข้อมูลจะหายเมื่อรีสตาร์ท)
from langgraph.checkpoint.memory import MemorySaver
checkpointer = MemorySaver() # ❌ In-Memory - ข้อมูลหายเมื่อรีสตาร์ท
หลังแก้ไข: ใช้ PostgreSQL หรือ SQLite
from langgraph.checkpoint.postgres import PostgresSaver
สำหรับ Development ใช้ SQLite
from langgraph.checkpoint.sqlite import SqliteSaver
checkpointer = SqliteSaver.from_conn_string("/tmp/langgraph.db")
สำหรับ Production ใช้ PostgreSQL
checkpointer = PostgresSaver.from_conn_string(
"postgresql://user:password@localhost:5432/langgraph"
)
checkpointer.setup() # สร้าง Tables ที่จำเป็น
Compile Graph ใหม่
app = graph.compile(checkpointer=checkpointer)
กรณีที่ 2: Thread ID Conflict ทำให้ Memory ปนกัน
อาการ: ผู้ใช้คนละคนเห็น Memory ของกันและกัน
สาเหตุ: ใช้ Thread ID แบบ Hardcoded หรือ Session ID ที่ซ้ำกัน
วิธีแก้ไข:
# แก้ไข: ใช้ Unique Thread ID ที่รับจาก Request
from uuid import uuid4
from fastapi import Request, HTTPException
async def chat_endpoint(request: Request):
# สร้าง Thread ID ที่ไม่ซ้ำกัน
user_id = request.headers.get("X-User-ID")
session_id = request.cookies.get("session_id")
if not user_id:
raise HTTPException(status_code=401, detail="User ID จำเป็น")
# รวม User ID กับ Session เพื่อสร้าง Unique Thread
thread_id = f"{user_id}:{session_id or uuid4()}"
config = {
"configurable": {
"thread_id": thread_id,
"checkpoint_ns": "production" # แยก Namespace สำหรับ Production
}
}
response = await app.ainvoke(
{"messages": [request.json()["message"]]},
config
)
return {"response": response["messages"][-1]}
หลักการ: Thread ID = User + Session + Environment
ทำให้แต่ละ User Session มี Memory แยกกัน
กรณีที่ 3: Token Limit เกินเมื่อ Memory ใหญ่เกินไป
อาการ: LLM ตอบสนองช้ามากหรือเกิด Error เรื่อง Token Limit
สาเหตุ: ส่งข้อความทั้งหมดใน History ไปให้ LLM โดยไม่จำกัดจำนวน
วิธีแก้ไข:
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage
from langchain_core.messages import trim_messages
class SmartMemoryManager:
"""จัดการ Memory อย่างชาญฉลาดเพื่อไม่ให้เกิน Token Limit"""
def __init__(self, max_tokens: int = 6000):
self.max_tokens = max_tokens
def get_optimized_messages(self, thread_id: str, checkpointer) -> list:
"""ดึงข้อความที่ถูก Trim แล้ว"""
# ดึง History จาก Checkpointer
checkpoint = checkpointer.get({"configurable": {"thread_id": thread_id}})
if not checkpoint:
return []
messages = checkpoint.get("messages", [])
# Trim Messages ให้เหลือเฉพาะที่จำเป็น
trimmed = trim_messages(
messages,
max_tokens=self.max_tokens,
strategy="last", # เก็บเฉพาะข้อความล่าสุด
token_counter=len, # ใช้จำนวนเป็นตัวนับ
include_system=True
)
return trimmed
def add_summary_to_context(self, messages: list, summary: str) -> str:
"""เพิ่ม Summary ของ History ก่อนหน้า"""
if not summary:
return ""
return f"\n\nสรุปการสนทนาก่อนหน้า: {summary}"
การใช้งาน
memory_manager = SmartMemoryManager(max_tokens=6000)
messages = memory_manager.get_optimized_messages("user-123", checkpointer)
เพิ่ม Summary Context
context = memory_manager.add_summary_to_context(messages, "ลูกค้าสนใจ laptop gaming")
system_prompt = f"คุณเป็น Agent... ข้อมูลเพิ่มเติม: {context}"
ส่งเฉพาะ Messages ที่ถูก Trim แล้ว
optimized_input = [SystemMessage(content=system_prompt)] + messages