본문으로 건너뛰기
Kreath Archive
TechProjectsBooksAbout
TechProjectsBooksAbout

내비게이션

  • Tech
  • Projects
  • Books
  • About
  • Tags

카테고리

  • AI / ML
  • 웹 개발
  • 프로그래밍
  • 개발 도구

연결

  • GitHub
  • Email
  • RSS
© 2026 Kreath Archive. All rights reserved.Built with Next.js + MDX
홈TechProjectsBooksAbout
//
  1. 홈
  2. 테크
  3. 8장: 메모리 관리와 상태 유지
2026년 2월 16일·AI / ML·

8장: 메모리 관리와 상태 유지

대화 메모리, 장기 메모리, 벡터 메모리, 구조화된 상태를 각 프레임워크별로 비교하고 프로덕션 메모리 전략을 정리합니다.

16분860자10개 섹션
orchestrationai-frameworkaillm
공유
ai-orchestration8 / 11
1234567891011
이전7장: 체이닝과 라우팅 패턴다음9장: 스트리밍과 실시간 처리 패턴

이 장에서 배우는 것

  • LLM의 무상태 특성과 메모리의 필요성
  • 대화 메모리(Conversation Memory) 유형: 버퍼, 요약, 윈도우
  • 장기 메모리(Long-term Memory)와 벡터 기반 메모리
  • 구조화된 상태 관리 패턴
  • 각 프레임워크의 메모리 구현 비교
  • 프로덕션 메모리 아키텍처 설계

LLM은 기억하지 않는다

LLM은 본질적으로 무상태(Stateless)입니다. 각 API 호출은 독립적이며, 이전 대화의 맥락을 자동으로 기억하지 않습니다. "어제 물어본 것을 기억하나요?"라는 질문에 LLM이 답할 수 있으려면, 우리가 그 맥락을 명시적으로 전달해야 합니다.

메모리 관리의 핵심 도전은 두 가지입니다.

  1. 컨텍스트 윈도우 제한: 모든 대화 이력을 매번 전송하면 토큰 한도를 초과합니다.
  2. 비용과 지연: 대화가 길어질수록 입력 토큰이 증가하고, 비용과 지연 시간이 비례하여 늘어납니다.

오케스트레이션 프레임워크는 이 두 문제를 해결하기 위한 다양한 메모리 전략을 제공합니다.


대화 메모리 유형

버퍼 메모리 (Buffer Memory)

가장 단순한 형태로, 모든 메시지를 순서대로 저장합니다.

buffer_memory_langchain.py
python
from langchain_core.chat_history import InMemoryChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory
 
# 세션별 메모리 저장소
store = {}
 
def get_session_history(session_id: str):
    if session_id not in store:
        store[session_id] = InMemoryChatMessageHistory()
    return store[session_id]
 
# 메모리가 적용된 체인
chain_with_memory = RunnableWithMessageHistory(
    runnable=prompt | model,
    get_session_history=get_session_history,
    input_messages_key="question",
    history_messages_key="history",
)
 
# 대화
config = {"configurable": {"session_id": "user-abc"}}
 
response1 = chain_with_memory.invoke(
    {"question": "Python의 GIL에 대해 설명해줘"},
    config=config,
)
 
response2 = chain_with_memory.invoke(
    {"question": "그럼 멀티스레딩은 어떻게 해야 하나?"},
    config=config,
)
# 이전 대화 맥락이 자동으로 포함됨
Warning

버퍼 메모리는 대화가 길어질수록 토큰 사용량이 선형적으로 증가합니다. 20턴 이상의 대화에서는 컨텍스트 윈도우 한도에 도달할 수 있으므로, 윈도우 메모리나 요약 메모리로 전환하는 것이 좋습니다.

윈도우 메모리 (Window Memory)

최근 N개의 메시지만 유지하는 전략입니다.

window_memory.py
python
from langchain_core.messages import trim_messages
 
# 최근 10개 메시지만 유지
trimmer = trim_messages(
    max_tokens=4000,
    strategy="last",
    token_counter=model,
    include_system=True,  # 시스템 메시지는 항상 유지
    allow_partial=False,
)
 
chain_with_window = (
    RunnablePassthrough.assign(
        history=lambda x: trimmer.invoke(x["history"])
    )
    | prompt
    | model
)

요약 메모리 (Summary Memory)

오래된 대화를 요약하여 압축하는 전략입니다.

summary_memory.py
python
from langchain_core.messages import SystemMessage, HumanMessage, AIMessage
 
class SummaryMemory:
    def __init__(self, model, max_messages: int = 10):
        self.model = model
        self.max_messages = max_messages
        self.summary = ""
        self.messages: list = []
 
    async def add_message(self, message):
        self.messages.append(message)
 
        if len(self.messages) > self.max_messages:
            # 오래된 메시지를 요약
            old_messages = self.messages[:self.max_messages // 2]
            summary_prompt = f"""
이전 대화 요약: {self.summary}
 
추가 대화:
{self._format_messages(old_messages)}
 
위 내용을 간결하게 요약해주세요.
"""
            self.summary = str(
                await self.model.ainvoke(summary_prompt)
            )
            self.messages = self.messages[self.max_messages // 2:]
 
    def get_context(self) -> list:
        context = []
        if self.summary:
            context.append(
                SystemMessage(content=f"이전 대화 요약: {self.summary}")
            )
        context.extend(self.messages)
        return context

장기 메모리

대화 세션을 넘어서 사용자에 대한 정보를 기억하는 패턴입니다.

벡터 기반 장기 메모리

long_term_memory.py
python
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
 
class LongTermMemory:
    def __init__(self):
        self.embeddings = OpenAIEmbeddings(
            model="text-embedding-3-small"
        )
        self.vectorstore = Chroma(
            collection_name="user_memories",
            embedding_function=self.embeddings,
        )
 
    def store_memory(self, user_id: str, content: str, metadata: dict = None):
        """대화에서 중요한 정보를 장기 메모리에 저장"""
        meta = {"user_id": user_id, **(metadata or {})}
        self.vectorstore.add_texts(
            texts=[content],
            metadatas=[meta],
        )
 
    def recall(self, user_id: str, query: str, k: int = 5) -> list[str]:
        """관련 장기 메모리 검색"""
        results = self.vectorstore.similarity_search(
            query=query,
            k=k,
            filter={"user_id": user_id},
        )
        return [doc.page_content for doc in results]
 
# 사용 예시
memory = LongTermMemory()
 
# 대화 중 중요 정보 저장
memory.store_memory(
    user_id="user-123",
    content="사용자는 Python과 TypeScript를 주로 사용하며, AI 엔지니어링에 관심이 있다.",
    metadata={"type": "preference"},
)
 
# 새 세션에서 관련 기억 검색
relevant_memories = memory.recall(
    user_id="user-123",
    query="추천 프로그래밍 언어",
)

LangGraph의 듀러블 메모리

3장에서 다룬 LangGraph의 체크포인팅은 자연스러운 장기 메모리 역할을 합니다.

langgraph_memory.py
python
from langgraph.checkpoint.postgres import PostgresSaver
from langgraph.store.memory import InMemoryStore
 
# 크로스 스레드 메모리 저장소
memory_store = InMemoryStore()
 
app = graph.compile(
    checkpointer=PostgresSaver(conn_string),
    store=memory_store,
)
 
# 노드에서 메모리 스토어 접근
def agent_node(state, config, store):
    """에이전트 노드에서 장기 메모리 활용"""
    user_id = config["configurable"]["user_id"]
 
    # 기존 메모리 검색
    memories = store.search(
        namespace=("memories", user_id),
        query=state["messages"][-1].content,
    )
 
    # 메모리를 컨텍스트에 포함
    context = "\n".join(m.value["content"] for m in memories)
 
    response = model.invoke(
        f"사용자 컨텍스트: {context}\n\n{state['messages']}"
    )
 
    # 중요 정보 메모리에 저장
    store.put(
        namespace=("memories", user_id),
        key=f"memory-{len(memories)}",
        value={"content": "새로 학습한 정보..."},
    )
 
    return {"messages": [response]}

프레임워크별 메모리 비교

LlamaIndex 메모리

llamaindex_memory.py
python
from llama_index.core.memory import ChatMemoryBuffer
 
# 토큰 기반 윈도우 메모리
memory = ChatMemoryBuffer.from_defaults(
    token_limit=3000,
)
 
# 대화 메시지 추가
memory.put(ChatMessage(role="user", content="안녕하세요"))
memory.put(ChatMessage(role="assistant", content="안녕하세요! 무엇을 도와드릴까요?"))
 
# 현재 메모리의 메시지 조회 (토큰 한도 내)
messages = memory.get()

Semantic Kernel 메모리

semantic_kernel_memory.py
python
from semantic_kernel.memory import SemanticTextMemory
from semantic_kernel.connectors.memory.azure_cognitive_search import (
    AzureCognitiveSearchMemoryStore,
)
 
# Azure Cognitive Search 기반 장기 메모리
memory_store = AzureCognitiveSearchMemoryStore(
    search_endpoint="https://myorg-search.search.windows.net",
    admin_key="your-admin-key",
)
 
memory = SemanticTextMemory(
    storage=memory_store,
    embeddings_generator=embedding_service,
)
 
# 메모리 저장
await memory.save_information(
    collection="user_preferences",
    id="pref-001",
    text="사용자는 Python과 AI 엔지니어링을 선호합니다",
)
 
# 메모리 검색
results = await memory.search(
    collection="user_preferences",
    query="추천 기술 스택",
    limit=5,
)

Haystack 메모리

haystack_memory.py
python
from haystack.components.memory import ChatMessageMemory
from haystack import Pipeline
 
# 대화 메모리 컴포넌트
memory = ChatMessageMemory()
 
pipeline = Pipeline()
pipeline.add_component("memory", memory)
pipeline.add_component("prompt_builder", prompt_builder)
pipeline.add_component("generator", generator)
 
pipeline.connect("memory.messages", "prompt_builder.history")
pipeline.connect("prompt_builder", "generator")

구조화된 상태 관리

대화 메모리 외에도, 애플리케이션 수준의 구조화된 상태를 관리해야 하는 경우가 있습니다.

structured_state.py
python
from pydantic import BaseModel
from typing import Optional
 
class UserContext(BaseModel):
    """사용자 컨텍스트 상태"""
    user_id: str
    name: str
    language: str = "ko"
    preferences: dict = {}
    current_task: Optional[str] = None
    task_progress: dict = {}
 
class ConversationState(BaseModel):
    """대화 상태"""
    user_context: UserContext
    messages: list = []
    summary: str = ""
    turn_count: int = 0
    total_tokens_used: int = 0
 
# LangGraph에서 구조화된 상태 활용
from langgraph.graph import StateGraph
 
class AppState(TypedDict):
    user_context: UserContext
    messages: Annotated[list, add_messages]
    current_tool_results: dict
    error_count: int
 
graph = StateGraph(AppState)
Info

구조화된 상태는 Pydantic 모델이나 TypedDict로 정의하는 것이 좋습니다. 타입 안전성을 보장하고, 상태의 구조를 명시적으로 문서화하는 효과가 있습니다.


프로덕션 메모리 아키텍처

실제 프로덕션에서는 여러 메모리 계층을 조합하여 사용합니다.

계층화된 메모리 구현

layered_memory.py
python
class LayeredMemory:
    """계층화된 메모리 시스템"""
 
    def __init__(self, model, vectorstore, db):
        self.short_term = []  # 현재 대화 버퍼
        self.model = model
        self.vectorstore = vectorstore  # 장기 메모리
        self.db = db  # 영구 저장소
 
    async def get_context(self, user_id: str, query: str) -> dict:
        """모든 메모리 계층에서 컨텍스트 수집"""
        # 1. 단기: 최근 대화
        recent = self.short_term[-10:]
 
        # 2. 장기: 관련 기억 검색
        memories = self.vectorstore.similarity_search(
            query=query,
            k=3,
            filter={"user_id": user_id},
        )
 
        # 3. 영구: 사용자 프로필
        profile = await self.db.get_user_profile(user_id)
 
        return {
            "recent_messages": recent,
            "relevant_memories": [m.page_content for m in memories],
            "user_profile": profile,
        }
 
    async def process_and_store(self, user_id: str, message: str, response: str):
        """대화 후 메모리 업데이트"""
        # 단기 메모리에 추가
        self.short_term.append({"role": "user", "content": message})
        self.short_term.append({"role": "assistant", "content": response})
 
        # 중요 정보 추출 및 장기 메모리 저장
        importance = await self._assess_importance(message, response)
        if importance > 0.7:
            self.vectorstore.add_texts(
                texts=[f"User: {message}\nAssistant: {response}"],
                metadatas=[{"user_id": user_id, "importance": importance}],
            )
 
        # 단기 메모리 한도 관리
        if len(self.short_term) > 20:
            await self._summarize_old_messages()
Tip

메모리 계층화의 핵심은 각 계층의 역할을 명확히 분리하는 것입니다. 단기 메모리는 빠른 접근, 장기 메모리는 의미 기반 검색, 영구 메모리는 구조화된 정보 저장에 특화합니다. 모든 정보를 하나의 저장소에 넣으면 검색 품질이 떨어집니다.


메모리 전략 의사결정 가이드

상황권장 전략
짧은 대화 (5턴 이하)버퍼 메모리만으로 충분
중간 대화 (5-20턴)윈도우 메모리 (토큰 기반)
긴 대화 (20턴 이상)요약 + 윈도우 조합
멀티 세션장기 벡터 메모리 + 세션별 단기 메모리
개인화 서비스계층화된 메모리 (단기 + 장기 + 영구)
비용 최적화요약 메모리 (토큰 절약)
정확성 중시버퍼 메모리 (정보 손실 없음)

핵심 요약

  • LLM은 무상태이므로, 대화 맥락 유지를 위해 명시적인 메모리 관리가 필수입니다.
  • 버퍼, 윈도우, 요약 메모리는 각각 다른 트레이드오프를 가지며, 대화 길이와 요구사항에 따라 선택합니다.
  • 장기 메모리는 벡터 스토어 기반의 의미 검색으로 구현하며, 세션을 넘어선 사용자 경험을 제공합니다.
  • LangGraph의 체크포인팅은 가장 자연스러운 듀러블 상태 관리를 제공합니다.
  • 프로덕션에서는 단기/중기/장기/영구 메모리를 계층화하여 사용합니다.
  • Pydantic이나 TypedDict로 상태를 구조화하면 타입 안전성과 문서화 효과를 얻습니다.

다음 장 예고

9장에서는 LLM 애플리케이션의 사용자 경험에 직결되는 스트리밍과 실시간 처리 패턴을 다룹니다. SSE/WebSocket, 토큰 스트리밍, 이벤트 스트리밍, 구조화된 출력 스트리밍을 각 프레임워크별로 비교하고, 프론트엔드 통합 방법까지 살펴보겠습니다.

이 글이 도움이 되셨나요?

관련 주제 더 보기

#orchestration#ai-framework#ai#llm

관련 글

AI / ML

9장: 스트리밍과 실시간 처리 패턴

SSE/WebSocket, 토큰/이벤트 스트리밍, 구조화된 출력 스트리밍을 각 프레임워크별로 비교하고 프론트엔드 통합을 다룹니다.

2026년 2월 18일·13분
AI / ML

7장: 체이닝과 라우팅 패턴

순차/병렬 체이닝, 조건부/시맨틱 라우팅, 폴백 체인을 각 프레임워크별로 비교 구현하며 실전 패턴을 정리합니다.

2026년 2월 14일·15분
AI / ML

10장: 에러 처리, 폴백, 관측 가능성

재시도 전략, 서킷 브레이커, OpenTelemetry 통합, 비용 추적, 프로덕션 모니터링까지 프로덕션 안정성 패턴을 다룹니다.

2026년 2월 20일·15분
이전 글7장: 체이닝과 라우팅 패턴
다음 글9장: 스트리밍과 실시간 처리 패턴

댓글

목차

약 16분 남음
  • 이 장에서 배우는 것
  • LLM은 기억하지 않는다
  • 대화 메모리 유형
    • 버퍼 메모리 (Buffer Memory)
    • 윈도우 메모리 (Window Memory)
    • 요약 메모리 (Summary Memory)
  • 장기 메모리
    • 벡터 기반 장기 메모리
    • LangGraph의 듀러블 메모리
  • 프레임워크별 메모리 비교
    • LlamaIndex 메모리
    • Semantic Kernel 메모리
    • Haystack 메모리
  • 구조화된 상태 관리
  • 프로덕션 메모리 아키텍처
    • 계층화된 메모리 구현
  • 메모리 전략 의사결정 가이드
  • 핵심 요약
  • 다음 장 예고