0Pricing
AI Engineering Academy · 강의

버퍼 및 윈도우 메모리

ConversationBufferMemory와 ConversationBufferWindowMemory를 구현해 최근 N개의 대화 차례를 컨텍스트에 유지하고, 윈도우 크기가 일관성과 비용에 미치는 영향을 측정합니다.

버퍼 및 윈도우 메모리은(는) CoddyKit의 무료 AI Engineering Academy 강의입니다. 이것은 4개 중 2번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 AI Engineering Academy 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. AI Engineering Academy 강의에는 총 4개의 강의가 포함되어 있습니다.

이 강의의 일부는 아직 번역되지 않았으며 영어로 표시됩니다.

Buffer Memory: Keep Everything

Buffer memory is the simplest strategy: store every message from every turn in a list and include the complete history in every API call. It preserves perfect context — the model can reference anything said at any point. The downside is linear context growth with no bound. For short sessions like a single task completion, buffer memory is perfectly appropriate and the easiest to implement.

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({}))

Integrating Buffer Memory with a Chain

To use buffer memory with LCEL, wire it through RunnableWithMessageHistory or use ConversationChain for the legacy approach. The memory object holds the history and the chain uses a MessagesPlaceholder in the prompt template to inject it. After each invocation, the memory automatically appends the new turn.

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'
)

The Problem with Unlimited Buffer

Buffer memory works until the conversation grows too long and exceeds the model's context window. With GPT-4o-mini's 128K context, a typical chat might last 200–400 turns before overflowing. More practically, even at 50 turns, you are sending 50K+ tokens per request — a significant cost. You need a strategy to bound the history size.

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')

Window Memory: Keep Last N Turns

Window memory keeps only the last K conversation turns, discarding older messages. This bounds the context at K * avg_tokens_per_turn regardless of how long the conversation lasts. The trade-off is that very early context is lost — the model may forget things the user said many turns ago. For most general-purpose chatbots, a window of 5–10 turns provides good coherence at acceptable cost.

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

Implementing Window Memory with InMemoryChatMessageHistory

LangChain's InMemoryChatMessageHistory keeps all messages, but you can trim the history before injecting it into the prompt. A common pattern is to store the full history for logging but pass only the last N messages to the LLM. Use Python slice notation on history.messages to get the most recent window.

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
)

Token-Based vs Turn-Based Window

Window size can be specified in turns (last K human/AI pairs) or in tokens (last T tokens of history). Token-based windowing is more reliable because turn length varies — a turn with code is 10x longer than a turn with 'yes'. LangChain's trim_messages() utility supports both strategies and can preserve the system prompt while trimming history.

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')

Measuring Coherence vs Window Size

Choosing the right window size requires measuring how conversation coherence degrades as window size shrinks. Run a set of test conversations where turn N references information from turn N-5, N-10, and N-20. Test whether the model can still answer correctly with windows of 5, 10, and 20 turns. The answer gives you the minimum window size needed for your specific use case.

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

Combining System Prompt with Window Memory

When using window memory, always preserve the system prompt — it defines the AI persona and rules. Without it, the model may lose its persona after the window slides past the first turn. Use the include_system=True option in trim functions, or always insert the system message before the windowed history in your prompt template.

# 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

Buffer vs Window Memory Decision Guide

Use buffer memory when: conversations are short and bounded (a one-time task, a form wizard), full context is critical (legal analysis, code review), and token budget is not a concern. Use window memory when: conversations can be arbitrarily long (customer support, general assistants), recent context is more important than distant context, and you need predictable, bounded token costs.

# 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'

Persisting the Window to Redis

For production use, store the full conversation history in Redis (for fast retrieval) and trim it to the window size at read time. Redis with a TTL ensures old sessions expire automatically. Use a sorted set or a list with LRANGE to efficiently fetch only the last N messages without loading the entire history.

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

Monitoring Memory Health in Production

Track these metrics in production to catch memory-related issues: average history tokens per request to detect sessions growing too large, context window utilization (warn if > 80%), session count in store to detect memory leaks in the session store, and cache hit rate for sessions reloaded from Redis. Alert when average history tokens crosses a configurable threshold.

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

Quick Check

Test your understanding of buffer and window memory strategies.

Lesson Recap

In this lesson you learned: buffer memory preserves complete history but grows without bound, making it suitable only for short bounded conversations, window memory keeps only the last K turns for predictable bounded cost at the expense of distant context, and token-based trimming with trim_messages() is more reliable than turn-based windows because message length varies. Next up we explore summary memory for open-ended long conversations.

자주 묻는 질문

“버퍼 및 윈도우 메모리” 강의는 무료인가요?

네 — “버퍼 및 윈도우 메모리” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 AI Engineering Academy 강의 전체를 잠금 해제할 수 있습니다. AI Engineering Academy 강의에는 총 4개의 강의가 포함되어 있습니다.

“버퍼 및 윈도우 메모리”에서 뭘 배우나요?

ConversationBufferMemory와 ConversationBufferWindowMemory를 구현해 최근 N개의 대화 차례를 컨텍스트에 유지하고, 윈도우 크기가 일관성과 비용에 미치는 영향을 측정합니다. 브라우저에서 직접 실행하는 실습 코드로 AI Engineering Academy을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.

AI Engineering Academy을(를) 시작하는 데 경험이 필요한가요?

사전 경험은 필요하지 않습니다. CoddyKit의 AI Engineering Academy은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 2번째 강의입니다.

“버퍼 및 윈도우 메모리” 강의는 얼마나 걸리나요?

대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.

이 AI Engineering Academy 강의에서 코드를 작성하고 실행할 수 있나요?

네. 모든 AI Engineering Academy 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.

이 강의의 모든 강의

  1. 상태 비저장 LLMs에 외부 메모리가 필요한 이유
  2. 버퍼 및 윈도우 메모리
  3. 요약 메모리와 토큰 인식형 잘라내기
  4. Redis와 PostgreSQL에 채팅 기록 저장하기
← AI Engineering Academy(으)로 돌아가기