บทนำ: ทำไม 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

แหล่งข้อมูลที่เกี่ยวข้อง

บทความที่เกี่ยวข้อง