Summary
LangChain에서 이전 대화 내용을 기억하는 멀티턴 챗봇을 구현하는 4가지 방법을 단계적으로 설명. Message Passing → ChatMessageHistory → RunnableWithMessageHistory → Summarization 단계로 진화하면서, 각 방법의 장단점을 코드와 실습으로 비교. 끝말잇기 게임과 음악 추천 챗봇 예제로 실제 응용을 시연한다.
Key Claims
- 4가지 메모리 관리 패턴: 기본(수동 리스트) → 클래스 기반 → 자동 관리 → 요약 기반 최적화
- ChatMessageHistory의 가치: 메시지 저장/로드의 구조화로 코드 가독성 ↑, 여전히 수동 호출 필요 ↓
- RunnableWithMessageHistory의 자동화: invoke 한 번으로 메모리 저장/로드 자동 처리 ⟹ 개발 효율 증대
- 토큰 효율성: 대화가 길어질 때 요약 기능으로 프롬프트 길이 압축, 토큰 초과 방지
- 세션 관리의 중요성: session_id로 다중 사용자 독립 대화 세션 구현
- 도메인별 커스터마이제이션: 게임(끝말잇기), 추천(음악) 등 도메인에 맞춰 요약/정보 필터링
Key Concepts Introduced
- Message Passing Pattern — 대화 리스트를 프롬프트에 직접 삽입하는 기본 형태
- ChatMessageHistory — 사용자/AI 메시지를 구조화하여 저장하는 클래스
- RunnableWithMessageHistory — 자동 메모리 관리 래퍼 (invoke 시 자동 저장/로드)
- Session Management — session_id로 사용자별 독립 대화 컨텍스트 유지
- Message Summarization — 대화 히스토리 압축으로 토큰 관리
- 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)