Summary

LangChain에서 이전 대화 내용을 기억하는 멀티턴 챗봇을 구현하는 4가지 방법을 단계적으로 설명. Message Passing → ChatMessageHistory → RunnableWithMessageHistory → Summarization 단계로 진화하면서, 각 방법의 장단점을 코드와 실습으로 비교. 끝말잇기 게임과 음악 추천 챗봇 예제로 실제 응용을 시연한다.

Key Claims

  • 4가지 메모리 관리 패턴: 기본(수동 리스트) → 클래스 기반 → 자동 관리 → 요약 기반 최적화
  • ChatMessageHistory의 가치: 메시지 저장/로드의 구조화로 코드 가독성 ↑, 여전히 수동 호출 필요 ↓
  • RunnableWithMessageHistory의 자동화: invoke 한 번으로 메모리 저장/로드 자동 처리 ⟹ 개발 효율 증대
  • 토큰 효율성: 대화가 길어질 때 요약 기능으로 프롬프트 길이 압축, 토큰 초과 방지
  • 세션 관리의 중요성: session_id로 다중 사용자 독립 대화 세션 구현
  • 도메인별 커스터마이제이션: 게임(끝말잇기), 추천(음악) 등 도메인에 맞춰 요약/정보 필터링

Key Concepts Introduced

  1. Message Passing Pattern — 대화 리스트를 프롬프트에 직접 삽입하는 기본 형태
  2. ChatMessageHistory — 사용자/AI 메시지를 구조화하여 저장하는 클래스
  3. RunnableWithMessageHistory — 자동 메모리 관리 래퍼 (invoke 시 자동 저장/로드)
  4. Session Management — session_id로 사용자별 독립 대화 컨텍스트 유지
  5. Message Summarization — 대화 히스토리 압축으로 토큰 관리
  6. Custom Summarization — 도메인별로 핵심 정보만 추출하여 저장 (게임은 단어만, 음악은 선호도 등)

LangChain Memory Management Patterns

Method 1: Message Passing (기본)

from langchain.schema import HumanMessage, AIMessage
 
chat_history = []
chat_history.append(HumanMessage(content="사용자 입력"))
chain.invoke({"messages": chat_history, "input": "새 질문"})
  • 장점: 간단, 직관적
  • 단점: 수동 관리, 토큰 낭비, 코드 길이 증가

Method 2: ChatMessageHistory (클래스 기반)

from langchain.memory import ChatMessageHistory
 
history = ChatMessageHistory()
history.add_user_message("사용자 입력")
history.add_ai_message("AI 응답")
chain.invoke({"chat_history": history.messages, "input": "새 질문"})
  • 장점: 구조화, add_user_message/add_ai_message로 명확한 역할 분리
  • 단점: 여전히 수동 호출 필요, invoke 시점에 저장 코드 필요

Method 3: RunnableWithMessageHistory (자동 관리)

from langchain.runnables.history import RunnableWithMessageHistory
 
session_history = {}
def get_session_history(session_id: str):
    if session_id not in session_history:
        session_history[session_id] = ChatMessageHistory()
    return session_history[session_id]
 
runnable_with_history = RunnableWithMessageHistory(
    runnable=chain,
    get_session_history=get_session_history,
    input_messages_key="input",
    history_messages_key="chat_history"
)
 
response = runnable_with_history.invoke(
    {"input": "사용자 입력"},
    config={"configurable": {"session_id": "user123"}}
)
  • 장점: 완전 자동 메모리 관리, 세션 ID로 다중 사용자 지원, invoke만 호출하면 됨
  • 단점: 설정 복잡도 증가, 세션 저장소 관리 필요

Method 4: Summarization + RunnableWithMessageHistory (고급)

def summarize_messages(chat_history: ChatMessageHistory) -> str:
    if len(chat_history.messages) == 0:
        return ""
    
    summary = (summarization_prompt | model).invoke({
        "chat_history": chat_history.messages
    })
    
    # 원본 히스토리 삭제 후 요약으로 교체
    chat_history.clear()
    chat_history.add_ai_message(summary.content)
    return ""
  • 장점: 토큰 효율성, 장시간 대화 지원, 핵심 정보만 보존
  • 단점: 요약 품질에 따라 정보 손실 위험

Real-World Examples

Example 1: 일반 대화 (음악 추천)

사용자: "안녕"
AI: "안녕하세요!"

사용자: "나는 말차를 좋아해"
AI: "좋아요, 말차는..."

사용자: "디저트 추천해줘"
AI: "당신이 말차를 좋아하신다니, 말차 라떼를 추천합니다."

특징: 전체 대화 히스토리 참고 → “말차”라는 컨텍스트 유지

Example 2: 끝말잇기 게임

사용자: "사과"
AI: "과자"

사용자: "자동차"
AI: "차멀미"

[요약: "사과 → 과자 → 자동차 → 차멀미"]

특징: 게임 규칙 적용 → 단어만 저장 → 불필요한 대화 제거

Example 3: 다중 사용자 세션

# User A
 
> [!caution] 검토 필요
> 
> 원본 자료 재방문 권장 (최초 수집: 2024-10-14)
runnable.invoke({"input": "안녕"}, config={"configurable": {"session_id": "userA"}})
 
# User B (독립 세션)
runnable.invoke({"input": "안녕"}, config={"configurable": {"session_id": "userB"}})

특징: 같은 session_id = 같은 대화 컨텍스트, 다른 session_id = 독립 대화

LangChain 핵심 컴포넌트 상세

ChatPromptTemplate with Placeholders

from langchain.prompts import ChatPromptTemplate
 
prompt = ChatPromptTemplate.from_messages([
    ("system", "당신은 도움을 주는 어시스턴트입니다."),
    ("placeholder", "{chat_history}"),
    ("human", "{input}")
])
  • placeholder: 동적으로 메시지 삽입할 위치 정의
  • 대화 흐름(system → chat_history → human input) 명확화

Chain Composition

chain = prompt | model | output_parser
  • | 연산자로 단계별 파이프라인 구성
  • 각 단계의 output이 다음 단계의 input으로 전달

멀티턴 메모리 아키텍처

┌─────────────────────────────────────────┐
│ 사용자 입력 (Session ID와 함께)          │
└─────────────────────────────────────────┘
              ↓
┌─────────────────────────────────────────┐
│ RunnableWithMessageHistory              │
│ ├─ 기존 세션 히스토리 로드               │
│ └─ 요약 단계 (optional)                 │
└─────────────────────────────────────────┘
              ↓
┌─────────────────────────────────────────┐
│ Prompt 구성                             │
│ ├─ System: 역할 정의                    │
│ ├─ History: 과거 대화                   │
│ └─ Input: 현재 입력                     │
└─────────────────────────────────────────┘
              ↓
┌─────────────────────────────────────────┐
│ LLM (GPT-4o 등)                         │
└─────────────────────────────────────────┘
              ↓
┌─────────────────────────────────────────┐
│ AI 응답 생성 + 히스토리에 저장          │
└─────────────────────────────────────────┘

온톨로지 설계 적용점

LLM 앱 아키텍처:

  • 메모리 구성요소 (히스토리) = 상태저장 에이전트의 핵심
  • Session Management = Multi-user 시스템에서의 컨텍스트 격리
  • Message Schema (Human/AI) = 온톨로지의 구조화된 표현

정보 구조화:

  • ChatMessageHistory 클래스 = 도메인 객체 (Message 엔티티)
  • add_user_message/add_ai_message = 관계 정의 (발화자 역할)
  • Session ID = 엔티티 간 식별자

컨텍스트 엔지니어링:

  • Method 1~3: 전체 히스토리 전달 = 광범위 컨텍스트
  • Method 4: 요약된 히스토리 = 압축된 컨텍스트 (비용 절감)

참고: 공원나연 | 게임 & 추천 시스템에 LangChain 메모리 패턴 실제 적용

인정: YouTube 공원나연 채널 (2024-10-14)