0Pricing
AI Engineering Academy · บทเรียน

หน่วยความจำแบบบัฟเฟอร์และหน้าต่าง

ใช้ ConversationBufferMemory และ ConversationBufferWindowMemory เพื่อเก็บการโต้ตอบ N รอบล่าสุดไว้ในบริบท และวัดว่าขนาดหน้าต่างส่งผลต่อความต่อเนื่องและค่าใช้จ่ายอย่างไร

หน่วยความจำแบบบัฟเฟอร์และหน้าต่าง เป็นบทเรียน AI Engineering Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน AI Engineering Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส AI Engineering Academy มีบทเรียนทั้งหมด 4 บทเรียน

หน่วยความจำแบบบัฟเฟอร์: เก็บทุกอย่าง

หน่วยความจำแบบบัฟเฟอร์ เป็นกลยุทธ์ที่ง่ายที่สุด โดยจัดเก็บทุกข้อความจากทุกการโต้ตอบไว้ในรายการ และใส่ประวัติทั้งหมดลงในการเรียก API ทุกครั้ง วิธีนี้รักษาบริบทได้อย่างครบถ้วน โมเดลจึงอ้างอิงสิ่งที่พูดไว้ในช่วงใดก็ได้ ข้อเสียคือบริบทจะเพิ่มขึ้นเป็นเส้นตรงโดยไม่มีขอบเขต สำหรับเซสชันสั้น ๆ เช่น การทำงานหนึ่งงานให้เสร็จ หน่วยความจำแบบบัฟเฟอร์เหมาะสมอย่างยิ่งและนำไปใช้ได้ง่ายที่สุด

from langchain.memory import ConversationBufferMemory

memory = ConversationBufferMemory(
    return_messages=True,  # return Message objects, not a string
    memory_key='history'   # key to inject into prompt template
)

# Add messages manually
memory.chat_memory.add_user_message('What is a neural network?')
memory.chat_memory.add_ai_message('A neural network is a system of layers...')

# Load what will be injected
print(memory.load_memory_variables({}))

การผสานหน่วยความจำแบบบัฟเฟอร์เข้ากับสายงาน

หากต้องการใช้หน่วยความจำแบบบัฟเฟอร์กับ LCEL ให้เชื่อมผ่าน RunnableWithMessageHistory หรือใช้ ConversationChain สำหรับแนวทางแบบเดิม วัตถุหน่วยความจำจะเก็บประวัติไว้ และสายงานจะใช้ MessagesPlaceholder ในแม่แบบพรอมต์เพื่อแทรกประวัตินั้น หลังการเรียกใช้แต่ละครั้ง หน่วยความจำจะเพิ่มการโต้ตอบรอบใหม่โดยอัตโนมัติ

from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.output_parsers import StrOutputParser
from langchain_core.chat_history import InMemoryChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory

store = {}

def get_history(session_id: str):
    if session_id not in store:
        store[session_id] = InMemoryChatMessageHistory()
    return store[session_id]

chain = (
    ChatPromptTemplate.from_messages([
        ('system', 'You are helpful.'),
        MessagesPlaceholder('history'),
        ('human', '{input}')
    ])
    | ChatOpenAI(model='gpt-4o-mini')
    | StrOutputParser()
)

with_memory = RunnableWithMessageHistory(
    chain, get_history,
    input_messages_key='input',
    history_messages_key='history'
)

ปัญหาของบัฟเฟอร์ที่ไม่จำกัด

หน่วยความจำแบบบัฟเฟอร์จะใช้งานได้จนกว่าการสนทนาจะยาวเกินไปและเกินหน้าต่างบริบทของโมเดล ด้วยบริบทขนาด 128K ของ GPT-4o-mini แชตทั่วไปอาจดำเนินไปได้ประมาณ 200–400 รอบก่อนจะล้น แต่ในทางปฏิบัติ แม้มีเพียง 50 รอบ คุณก็อาจส่งโทเค็นมากกว่า 50K ต่อคำขอ ซึ่งมีค่าใช้จ่ายสูง คุณจึงต้องมีกลยุทธ์เพื่อ จำกัดขนาดประวัติ

import tiktoken

enc = tiktoken.encoding_for_model('gpt-4o-mini')

def count_history_tokens(messages: list) -> int:
    total = 0
    for msg in messages:
        total += len(enc.encode(msg.content))
        total += 4  # per-message overhead
    return total

# Check how big the history has grown
history = store.get('session-1')
if history:
    token_count = count_history_tokens(history.messages)
    print(f'History size: {len(history.messages)} messages, {token_count} tokens')

หน่วยความจำแบบหน้าต่าง: เก็บ N รอบล่าสุด

หน่วยความจำแบบหน้าต่าง จะเก็บเฉพาะการโต้ตอบ K รอบล่าสุด และทิ้งข้อความที่เก่ากว่า วิธีนี้จำกัดบริบทไว้ที่ K * avg_tokens_per_turn ไม่ว่าการสนทนาจะยาวเพียงใด ข้อแลกเปลี่ยนคือบริบทช่วงแรก ๆ จะหายไป โมเดลอาจลืมสิ่งที่ผู้ใช้พูดไว้หลายรอบก่อน สำหรับแชตบอตทั่วไปส่วนใหญ่ หน้าต่างขนาด 5–10 รอบให้ความต่อเนื่องที่ดีด้วยค่าใช้จ่ายที่ยอมรับได้

from langchain.memory import ConversationBufferWindowMemory

# Keep last 5 turns (10 messages: 5 user + 5 assistant)
memory = ConversationBufferWindowMemory(
    k=5,              # number of TURNS to keep (each turn = user + AI)
    return_messages=True,
    memory_key='history'
)

# After 10 turns, only turns 6-10 will be in context
# Turns 1-5 are silently dropped

การนำหน่วยความจำแบบหน้าต่างไปใช้ด้วย InMemoryChatMessageHistory

InMemoryChatMessageHistory ของ LangChain จะเก็บข้อความทั้งหมดไว้ แต่คุณสามารถตัดประวัติก่อนแทรกลงในพรอมต์ได้ รูปแบบที่ใช้กันทั่วไปคือเก็บประวัติทั้งหมดไว้สำหรับการบันทึกข้อมูล แต่ส่งเฉพาะข้อความ N รายการล่าสุดไปยัง LLM ใช้สัญกรณ์การแบ่งส่วนของ Python กับ history.messages เพื่อดึงหน้าต่างล่าสุด

from langchain_core.chat_history import InMemoryChatMessageHistory
from langchain_core.runnables import RunnableLambda

WINDOW_SIZE = 10  # last 10 messages (5 turns)

def get_windowed_history(session_id: str):
    full_history = store.get(session_id, InMemoryChatMessageHistory())
    store[session_id] = full_history
    return full_history

# In the chain, trim before injecting
def trim_history(messages):
    return messages[-WINDOW_SIZE:] if len(messages) > WINDOW_SIZE else messages

# Use in prompt with trimming
from langchain_core.messages import trim_messages
trimmer = trim_messages(
    max_tokens=2000,
    strategy='last',
    token_counter=ChatOpenAI(model='gpt-4o-mini'),
    include_system=True
)

หน้าต่างตามโทเค็นเทียบกับหน้าต่างตามรอบ

คุณสามารถระบุขนาดหน้าต่างเป็น รอบ ได้แก่ คู่มนุษย์/AI K คู่ล่าสุด หรือเป็น โทเค็น ได้แก่โทเค็น T รายการล่าสุดของประวัติ การกำหนดหน้าต่างตามโทเค็นเชื่อถือได้มากกว่า เพราะความยาวของแต่ละรอบแตกต่างกัน รอบที่มีโค้ดอาจยาวกว่ารอบที่ตอบว่า 'ใช่' ถึง 10 เท่า ยูทิลิตี trim_messages() ของ LangChain รองรับทั้งสองกลยุทธ์ และสามารถเก็บพรอมต์ระบบไว้ขณะตัดประวัติได้

from langchain_core.messages import trim_messages, SystemMessage, HumanMessage, AIMessage

# Token-based trimming — keep last 1000 tokens of conversation
trimmer = trim_messages(
    max_tokens=1000,
    strategy='last',       # keep most recent messages
    token_counter=len,     # approximate: count characters / 4
    include_system=True,   # always include the system prompt
    allow_partial=False,   # don't split a message in half
    start_on='human'       # start window on a human message
)

trimmed = trimmer.invoke(all_messages)
print(f'Trimmed to {len(trimmed)} messages')

การวัดความต่อเนื่องเทียบกับขนาดหน้าต่าง

การเลือกขนาดหน้าต่างที่เหมาะสมจำเป็นต้องวัดว่าความต่อเนื่องของการสนทนาลดลงอย่างไรเมื่อหน้าต่างเล็กลง ให้เรียกใช้ชุดการสนทนาทดสอบที่รอบ N อ้างอิงข้อมูลจากรอบ N-5, N-10 และ N-20 ทดสอบว่าโมเดลยังตอบได้ถูกต้องหรือไม่เมื่อใช้หน้าต่าง 5, 10 และ 20 รอบ ผลลัพธ์จะบอกขนาดหน้าต่างขั้นต่ำที่จำเป็นสำหรับกรณีการใช้งานเฉพาะของคุณ

def test_reference_at_distance(chain_with_memory, distances=[5, 10, 20]):
    results = {}
    for distance in distances:
        session_id = f'test-dist-{distance}'
        # Fill with filler turns
        for i in range(distance):
            chain_with_memory.invoke(
                {'input': f'Turn {i}: filler message'},
                config={'configurable': {'session_id': session_id}}
            )
        # Ask about something said at the start
        first_msg = 'Recall that the user said the magic word is AZURE.'
        response = chain_with_memory.invoke(
            {'input': 'What was the magic word?'},
            config={'configurable': {'session_id': session_id}}
        )
        results[distance] = 'AZURE' in response.upper()
    return results

การรวมพรอมต์ระบบเข้ากับหน่วยความจำแบบหน้าต่าง

เมื่อใช้หน่วยความจำแบบหน้าต่าง ให้เก็บ พรอมต์ระบบ ไว้เสมอ เพราะพรอมต์นี้กำหนดบุคลิกและกฎของ AI หากไม่มีพรอมต์ดังกล่าว โมเดลอาจสูญเสียบุคลิกเมื่อหน้าต่างเลื่อนผ่านรอบแรก ใช้ตัวเลือก include_system=True ในฟังก์ชันตัดข้อความ หรือแทรกข้อความระบบไว้ก่อนประวัติแบบหน้าต่างในแม่แบบพรอมต์เสมอ

# Always put system prompt BEFORE the windowed history
prompt = ChatPromptTemplate.from_messages([
    ('system', 'You are a Python tutor. Always explain with code examples.'),
    MessagesPlaceholder('history'),  # windowed history injected here
    ('human', '{input}'),
])

# The system prompt is never trimmed — only the history window is managed
# This ensures the model's persona is always present regardless of window

คู่มือเลือกใช้หน่วยความจำแบบบัฟเฟอร์เทียบกับแบบหน้าต่าง

ใช้ หน่วยความจำแบบบัฟเฟอร์ เมื่อการสนทนาสั้นและมีขอบเขตแน่นอน เช่น งานครั้งเดียวหรือตัวช่วยกรอกแบบฟอร์ม บริบททั้งหมดมีความสำคัญ เช่น การวิเคราะห์ทางกฎหมายหรือการตรวจสอบโค้ด และไม่ต้องกังวลเรื่องงบประมาณโทเค็น ใช้ หน่วยความจำแบบหน้าต่าง เมื่อการสนทนาอาจยาวได้ไม่จำกัด เช่น การสนับสนุนลูกค้าหรือผู้ช่วยทั่วไป บริบทล่าสุดสำคัญกว่าบริบทที่ห่างออกไป และคุณต้องการค่าใช้จ่ายโทเค็นที่คาดการณ์ได้และมีขอบเขต

# Decision matrix in code
def choose_memory_strategy(
    expected_turns: int,
    max_context_tokens: int = 128000,
    avg_tokens_per_turn: int = 200
) -> str:
    buffer_tokens = expected_turns * avg_tokens_per_turn
    if buffer_tokens < max_context_tokens * 0.5:
        return 'buffer'  # Safe to keep everything
    elif expected_turns <= 20:
        return 'window_10'  # Keep last 10 turns
    else:
        return 'summary'  # Need summarization for long convos

print(choose_memory_strategy(5))    # 'buffer'
print(choose_memory_strategy(50))   # 'window_10'
print(choose_memory_strategy(200))  # 'summary'

การจัดเก็บหน้าต่างไว้ใน Redis

สำหรับการใช้งานจริง ให้จัดเก็บประวัติการสนทนาทั้งหมดใน Redis เพื่อให้เรียกคืนได้รวดเร็ว แล้วตัดให้เหลือขนาดหน้าต่างเมื่ออ่านข้อมูล Redis ที่ใช้ TTL จะทำให้เซสชันเก่าหมดอายุโดยอัตโนมัติ ใช้ชุดเรียงลำดับหรือรายการร่วมกับ LRANGE เพื่อดึงเฉพาะข้อความ N รายการล่าสุดได้อย่างมีประสิทธิภาพ โดยไม่ต้องโหลดประวัติทั้งหมด

from langchain_community.chat_message_histories import RedisChatMessageHistory

def get_windowed_redis_history(session_id: str, window: int = 10):
    # RedisChatMessageHistory stores all messages
    history = RedisChatMessageHistory(
        session_id=session_id,
        url='redis://localhost:6379',
        ttl=3600  # 1 hour TTL
    )
    # Trim to window size in-memory before use
    all_msgs = history.messages
    if len(all_msgs) > window * 2:  # window turns = window*2 messages
        history.messages = all_msgs[-(window * 2):]
    return history

การตรวจสอบสุขภาพของหน่วยความจำในการใช้งานจริง

ติดตามตัวชี้วัดเหล่านี้ในการใช้งานจริงเพื่อค้นหาปัญหาที่เกี่ยวข้องกับหน่วยความจำ: จำนวนโทเค็นประวัติเฉลี่ยต่อคำขอ เพื่อตรวจหาเซสชันที่ใหญ่เกินไป การใช้หน้าต่างบริบท โดยแจ้งเตือนหากเกิน 80% จำนวนเซสชันในที่จัดเก็บ เพื่อตรวจหาหน่วยความจำรั่วในที่จัดเก็บเซสชัน และ อัตราการพบในแคช สำหรับเซสชันที่โหลดซ้ำจาก Redis ให้แจ้งเตือนเมื่อจำนวนโทเค็นประวัติเฉลี่ยเกินค่าขีดจำกัดที่กำหนดค่าได้

import time
from langchain_core.callbacks import BaseCallbackHandler

class MemoryMonitorCallback(BaseCallbackHandler):
    def on_chain_start(self, serialized, inputs, **kwargs):
        history = inputs.get('history', [])
        token_estimate = sum(len(m.content.split()) * 1.3 for m in history)
        if token_estimate > 50000:
            print(f'WARNING: Large history {token_estimate:.0f} estimated tokens')

    def on_chain_end(self, outputs, **kwargs):
        # Log usage for dashboards
        pass

ตรวจสอบความเข้าใจอย่างรวดเร็ว

ทดสอบความเข้าใจของคุณเกี่ยวกับกลยุทธ์หน่วยความจำแบบบัฟเฟอร์และแบบหน้าต่าง

ทบทวนบทเรียน

ในบทเรียนนี้ คุณได้เรียนรู้ว่า หน่วยความจำแบบบัฟเฟอร์ รักษาประวัติทั้งหมดไว้แต่เพิ่มขึ้นโดยไม่มีขอบเขต จึงเหมาะเฉพาะกับการสนทนาสั้น ๆ ที่มีขอบเขตแน่นอน หน่วยความจำแบบหน้าต่าง เก็บเพียง K รอบล่าสุดเพื่อให้ค่าใช้จ่ายคาดการณ์ได้และมีขอบเขต โดยแลกกับการสูญเสียบริบทที่ห่างออกไป และ การตัดตามโทเค็นด้วย trim_messages() เชื่อถือได้กว่าหน้าต่างตามรอบ เพราะความยาวของข้อความแตกต่างกัน บทถัดไปเราจะสำรวจหน่วยความจำแบบสรุปสำหรับการสนทนายาวที่ไม่มีจุดสิ้นสุดแน่นอน

คำถามที่พบบ่อย

บทเรียน “หน่วยความจำแบบบัฟเฟอร์และหน้าต่าง” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “หน่วยความจำแบบบัฟเฟอร์และหน้าต่าง” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส AI Engineering Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส AI Engineering Academy มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “หน่วยความจำแบบบัฟเฟอร์และหน้าต่าง”

ใช้ ConversationBufferMemory และ ConversationBufferWindowMemory เพื่อเก็บการโต้ตอบ N รอบล่าสุดไว้ในบริบท และวัดว่าขนาดหน้าต่างส่งผลต่อความต่อเนื่องและค่าใช้จ่ายอย่างไร คุณปฏิบัติ AI Engineering Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน AI Engineering Academy หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน AI Engineering Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน

บทเรียน “หน่วยความจำแบบบัฟเฟอร์และหน้าต่าง” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน AI Engineering Academy นี้ได้ไหม

ได้ บทเรียน AI Engineering Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. เหตุใด LLM แบบไร้สถานะจึงต้องใช้หน่วยความจำภายนอก
  2. หน่วยความจำแบบบัฟเฟอร์และหน้าต่าง
  3. หน่วยความจำแบบสรุปและการตัดข้อความโดยคำนึงถึงโทเคน
  4. การเก็บประวัติการสนทนาใน Redis และ PostgreSQL
← กลับไปที่ AI Engineering Academy