대화 메모리를 설계할 때는 무엇을 기억할지와 언제 버릴지를 함께 결정해야 한다. 전체 기록, 최근 대화, 요약 기록을 비교하면서 문맥 보존과 입력 크기의 관계를 살펴본다. 세션 구분은 대화 기능을 확장하기 전에 먼저 명확히 해야 할 경계다.
목차
- 왜 메모리가 필요한가?
- 수동으로 대화 기록 관리하기
- 토큰 카운팅 (tiktoken)
- ConversationBufferMemory (langchain_classic)
- InMemoryChatMessageHistory (langchain_core)
- RunnableWithMessageHistory — 세션 기반 자동 관리
- Window Memory — 최근 N개 턴만 유지
- Summary Memory — 오래된 대화 요약으로 압축
- SummaryChatbot — 요약 메모리 통합 챗봇
- 자주 나오는 실수 / 주의사항
- [보충] 메모리 전략 비교
- 기억해야 할 정보부터 정의하기
1. 왜 메모리가 필요한가?
LLM(대규모 언어 모델)은 기본적으로 무상태(stateless) 입니다. 각 invoke() 호출은 완전히 독립된 요청이므로, 이전 대화 내용을 전혀 기억하지 못합니다.
1
2
3
4
5
| llm.invoke('내 이름은 abc입니다.').content
# → '안녕하세요, abc님! 어떻게 도와드릴까요?'
llm.invoke('내 이름은 뭔가요?').content
# → '죄송하지만, 당신의 이름은 알 수 없습니다...'
|
▶ 문제: 두 번째 호출 때는 첫 번째 호출의 정보가 없기 때문에 이름을 모릅니다.
해결책: 대화 기록을 직접 전달
messages 리스트에 대화 히스토리를 누적해서 LLM에 함께 전달해야 합니다.
1
2
3
4
5
6
7
| [SystemMessage] 당신은 친절한 AI 어시스턴트입니다
↓
[HumanMessage] 내 이름은 abc입니다
↓
[AIMessage] 반가워요, abc님!
↓
[HumanMessage] 내 이름은 뭔가요? ← 이 시점에 위의 히스토리가 함께 전달됨
|
메모리 전략 3가지
1
2
3
4
5
6
7
| ┌─────────────────────────────────────────────────────┐
│ 메모리 전략 │
│ │
│ 1. Buffer : 모든 대화 저장 (무제한) │
│ 2. Window : 최근 K개 턴만 유지 (슬라이딩 윈도우) │
│ 3. Summarize : 오래된 대화를 요약으로 압축 │
└─────────────────────────────────────────────────────┘
|
2. 수동으로 대화 기록 관리하기
messages 리스트에 SystemMessage / HumanMessage / AIMessage를 순서대로 추가합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| from langchain_core.messages import HumanMessage, SystemMessage, AIMessage
messages = [
SystemMessage(content='당신은 친절한 AI 어시스턴트입니다'),
HumanMessage(content='내 이름은 abc입니다. 반가워요')
]
response1 = llm.invoke(messages)
print(response1.content)
# → 반가워요, abc님! 어떻게 도와드릴까요?
# AI 응답을 기록에 추가하고, 다음 질문도 추가
messages.append(AIMessage(content=response1.content))
messages.append(HumanMessage(content='내 이름은 뭔가요?'))
response2 = llm.invoke(messages)
print(response2.content)
# → 당신의 이름은 abc입니다! 다른 질문이 있으신가요?
|
▶ 포인트: AIMessage를 직접 messages에 추가해야 다음 턴에서 이전 맥락을 기억합니다.
3. 토큰 카운팅 (tiktoken)
대화가 길어질수록 토큰 수가 증가해 비용과 속도에 영향을 줍니다. tiktoken으로 현재 대화의 토큰 수를 측정할 수 있습니다.
1
2
3
4
5
6
7
8
9
10
| import tiktoken
enc = tiktoken.encoding_for_model('gpt-4o-mini')
def count_tokens(messages):
total = 0
for msg in messages:
total += len(enc.encode(msg.content))
total += 4 # 메시지 구분자 토큰 (OpenAI 규격)
return total
|
실제 측정 예시:
| 턴 수 | 누적 토큰 |
|---|
| 1턴 (교환 1회) | 50 |
| 2턴 (교환 2회) | 82 |
▶ 주의: 대화가 쌓일수록 토큰이 선형 증가합니다. 100턴이면 수천 토큰 → 메모리 전략 필수.
4. ConversationBufferMemory (langchain_classic)
Deprecated 경고 발생 (현재는 langchain_core의 InMemoryChatMessageHistory 권장)
기본 사용법
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| from langchain_classic.memory import ConversationBufferMemory
memory = ConversationBufferMemory(return_messages=True) # True: 메시지 객체 반환
memory.save_context(
{'input': '안녕하세요, 저는 abc입니다'},
{'output': '안녕하세요, abc님! 만나서 반갑습니다'}
)
memory.save_context(
{'input': '오늘 날씨가 좋네요!'},
{'output': '네, 정말 화창한 날씨입니다'}
)
history = memory.load_memory_variables({})
|
return_messages 옵션 차이
return_messages | 반환 형식 |
|---|
True | [HumanMessage(...), AIMessage(...), ...] — 메시지 객체 리스트 |
False | 'Human: ...\nAI: ...' — 하나의 문자열 |
1
2
3
4
5
6
7
8
9
10
| # return_messages=True 결과
{'history': [
HumanMessage(content='안녕하세요, 저는 abc입니다'),
AIMessage(content='안녕하세요, abc님! 만나서 반갑습니다'),
HumanMessage(content='오늘 날씨가 좋네요!'),
AIMessage(content='네, 정말 화창한 날씨입니다')
]}
# return_messages=False 결과
{'history': 'Human: 안녕하세요, 저는 abc입니다\nAI: 안녕하세요, abc님! 만나서 반갑습니다\n...'}
|
▶ 실무 팁: 체인(Chain)에 주입할 때는 return_messages=True(메시지 리스트)가 더 범용적입니다.
5. InMemoryChatMessageHistory (langchain_core)
langchain_core에서 제공하는 현대적인 방식. Deprecated 경고 없음.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| from langchain_core.chat_history import InMemoryChatMessageHistory
chat_history = InMemoryChatMessageHistory()
# 메시지 추가
chat_history.add_user_message('안녕하세요, 저는 abc입니다')
chat_history.add_ai_message('안녕하세요, abc님! 만나서 반갑습니다')
chat_history.add_user_message('오늘 날씨가 좋네요')
chat_history.add_ai_message('네, 정말 화창한 날씨입니다')
# 메시지 조회
for msg in chat_history.messages:
print(msg.type, msg.content)
# human 안녕하세요, 저는 abc입니다
# ai 안녕하세요, abc님! 만나서 반갑습니다
# ...
|
.messages 직접 조작
1
2
3
4
5
6
7
8
9
10
| # 리스트처럼 직접 append 가능
chat_history.messages.append(HumanMessage(content='내 이름이 뭔가요?'))
# 또는 스프레드(*)로 기존 히스토리에 새 메시지 추가
messages = [
*chat_history.messages,
HumanMessage(content='내 이름이 뭔가요?')
]
llm.invoke(messages)
# → '당신의 이름은 "abc"입니다.'
|
▶ 포인트: *chat_history.messages로 언패킹하면 히스토리를 건드리지 않고 임시 메시지 추가가 가능합니다.
6. RunnableWithMessageHistory — 세션 기반 자동 관리
세션 식별자는 어떤 기록을 읽고 갱신할지 결정한다. 같은 질문을 두 세션에서 번갈아 보내 기록이 섞이지 않는지 확인하고, 새 대화 시작과 기존 대화 재개가 같은 동작으로 처리되지 않도록 호출 규칙을 정한다.
수동으로 messages에 추가하는 번거로움 없이, 세션 ID 기반으로 히스토리를 자동 관리합니다.
구조 흐름
1
2
3
4
5
6
7
8
9
10
| 사용자 입력 (input)
↓
ChatPromptTemplate
[SystemMessage: 역할 설정]
[MessagesPlaceholder: 히스토리 자동 삽입]
[HumanMessage: 사용자 입력]
↓
LLM
↓
AI 응답 → 자동으로 store[session_id]에 저장
|
구현 코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_core.chat_history import InMemoryChatMessageHistory
# 1. 프롬프트 템플릿 (MessagesPlaceholder가 히스토리 자리)
prompt = ChatPromptTemplate.from_messages([
('system', '당신은 친절한 AI 어시스턴트입니다'),
MessagesPlaceholder(variable_name='history'), # ← 히스토리가 여기에 자동 삽입
('human', '{input}')
])
chain = prompt | llm
# 2. 세션 저장소 (세션 ID → InMemoryChatMessageHistory)
store = {}
def get_session_history(session_id):
if session_id not in store:
store[session_id] = InMemoryChatMessageHistory()
return store[session_id]
# 3. 히스토리 자동 관리 래퍼 생성
chain_with_history = RunnableWithMessageHistory(
chain,
get_session_history,
input_messages_key='input', # chain의 입력 키
history_messages_key='history' # MessagesPlaceholder의 variable_name
)
|
실행 방법
1
2
3
4
5
6
7
8
9
10
11
| # session_id로 어떤 사용자의 히스토리를 쓸지 지정
config = {'configurable': {'session_id': 'user_001'}}
r1 = chain_with_history.invoke({'input': '안녕하세요, 제 이름은 abc입니다'}, config=config)
# → '안녕하세요, abc님! 어떻게 도와드릴까요?'
r2 = chain_with_history.invoke({'input': '내 이름이 뭐라고 했죠?'}, config=config)
# → '당신의 이름은 abc입니다. 맞나요?'
r3 = chain_with_history.invoke({'input': '오늘 뭐하면 좋을까요?'}, config=config)
# → '오늘 할 수 있는 다양한 아이디어가 있습니다! ...'
|
실전 예제 — 전자제품 판매원 챗봇
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| prompt = ChatPromptTemplate.from_messages([
('system', '당신은 친절한 전자제품 판매원입니다'),
MessagesPlaceholder(variable_name='history'),
('human', '{input}')
])
chain = prompt | llm
store = {}
def get_session_history(session_id):
if session_id not in store:
store[session_id] = InMemoryChatMessageHistory()
return store[session_id]
chain_with_history = RunnableWithMessageHistory(
chain, get_session_history,
input_messages_key='input',
history_messages_key='history'
)
config = {'configurable': {'session_id': 'user_001'}}
r1 = chain_with_history.invoke({'input': '안녕하세요, 노트북을 사려는데 어떤게 있나요?'}, config=config)
r2 = chain_with_history.invoke({'input': '가장 인기 있는 제품의 가격이 어떻게 되나요?'}, config=config)
r3 = chain_with_history.invoke({'input': '좋아요, 2번째 제품으로 구입하겠습니다.'}, config=config)
# → AI가 "2번째 제품"이 무엇인지 히스토리를 통해 파악하여 답변
|
대화 기록 확인:
1
2
3
4
5
6
7
8
9
10
| for msg in store['user_001'].messages:
prefix = 'customer' if msg.type == 'human' else 'sales person'
print(f"[{prefix}] - {msg.content[:30]}")
# [customer] - 안녕하세요, 노트북을 사려는데 어떤게 있나요?
# [sales person] - 안녕하세요! 노트북을 구매하시려는군요...
# [customer] - 가장 인기 있는 제품의 가격이 어떻게 되나요?
# [sales person] - 인기 있는 노트북 제품들의 가격은...
# [customer] - 좋아요, 2번째 제품으로 구입하겠습니다.
# [sales person] - 삼성 노트북 9을 선택해 주셨군요!...
|
7. Window Memory — 최근 N개 턴만 유지
대화가 길어지면 오래된 내용은 버리고 최근 K턴만 메모리에 유지합니다.
방법 1: ConversationBufferWindowMemory
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| from langchain_classic.memory import ConversationBufferWindowMemory
window_memory = ConversationBufferWindowMemory(k=2, return_messages=True)
# k=2 → 최근 2턴(Human+AI 쌍 2개)만 유지
window_memory.save_context({'input': '첫번째, 내 이름은 abc입니다'}, {'output': '안녕하세요, abc님'})
window_memory.save_context({'input': '두번째, 나는 학생입니다'}, {'output': '학생이시군요! 멋지십니다'})
window_memory.save_context({'input': '세번째, 나는 파이썬을 좋아합니다'}, {'output': '파이썬은 정말 재미있죠!'})
window_memory.load_memory_variables({})
# → 첫번째 대화는 사라지고, 두번째 + 세번째만 남음
# {'history': [
# HumanMessage('두번째, 나는 학생입니다'),
# AIMessage('학생이시군요! 멋지십니다'),
# HumanMessage('세번째, 나는 파이썬을 좋아합니다'),
# AIMessage('파이썬은 정말 재미있죠!')
# ]}
|
방법 2: trim_messages (langchain_core)
1
2
3
4
5
6
7
8
9
10
11
| from langchain_core.messages import trim_messages
trimmer = trim_messages(
max_tokens=4, # 최대 4개 메시지까지만 유지
strategy='last', # 최신 것부터 선택
token_counter=len, # 토큰 카운터 (여기서는 메시지 개수 기준)
start_on='human' # Human 메시지로 시작 (쌍 맞추기)
)
trimmed = trimmer.invoke(messages)
# → 최근 4개 메시지만 반환 (두번째, 세번째 대화만 남음)
|
방법 3: 수동 슬라이싱
1
2
3
4
5
6
7
8
| messages = []
K = 2 # 유지할 턴 수
def add_turn(user_input, ai_output):
global messages
messages.append(HumanMessage(content=user_input))
messages.append(AIMessage(content=ai_output))
messages = messages[-(K * 2):] # 최근 K턴(메시지 K*2개)만 유지
|
실행 결과:
1
2
3
| 턴 1 추가 후: [Human(첫번째), AI(첫번째)] → 2개
턴 2 추가 후: [Human(첫번째), AI(첫번째), Human(두번째), AI(두번째)] → 4개 (K*2=4)
턴 3 추가 후: [Human(두번째), AI(두번째), Human(세번째), AI(세번째)] → 4개 (첫번째 밀려남)
|
▶ 포인트: messages[-(K*2):]로 슬라이싱하면 K턴 윈도우를 간단하게 구현 가능.
8. Summary Memory — 오래된 대화 요약으로 압축
요약은 기록의 표현을 바꾸는 과정이므로 중요한 수치나 사용자 제약이 빠질 수 있다. 압축한 토큰 수와 함께 보존해야 할 사실 목록을 대조하고, 요약 이후의 질문에서도 그 사실을 사용할 수 있는지 확인한다.
오래된 대화를 LLM이 요약하여 압축 보관. 디테일 일부 유지 + 토큰 절약.
방법 1: ConversationSummaryMemory (langchain_classic)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| from langchain_classic.memory import ConversationSummaryMemory
summary_memory = ConversationSummaryMemory(
llm=ChatOpenAI(model='gpt-4o-mini'),
return_messages=False
)
conversations = [
('첫번째, 내 이름은 abc입니다', '안녕하세요, abc님'),
('두번째, 나는 학생입니다', '학생이시군요! 멋지십니다'),
('세번째, 나는 파이썬을 좋아합니다', '파이썬은 정말 재미있죠!')
]
for user_msg, ai_msg in conversations:
summary_memory.save_context({'input': user_msg}, {'output': ai_msg})
result = summary_memory.load_memory_variables({})
print(result['history'])
# → 'The human introduces themselves as abc. The AI greets abc.
# The human then states that they are a student, and the AI praises them...
# The human adds that they like Python, to which the AI responds that Python is indeed very fun.'
|
▶ 주의: ConversationSummaryMemory는 영어로 요약하는 경향이 있습니다. 한국어 강제 지정이 필요하면 커스텀 구현을 권장합니다.
방법 2: 커스텀 SummaryMemory 클래스
summary_interval 턴마다 자동으로 요약을 수행하는 커스텀 메모리 클래스입니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
| class SummaryMemory:
def __init__(self, summary_interval=3):
self.summary = '' # 누적 요약 텍스트
self.recent_messages = [] # 아직 요약되지 않은 최근 메시지들
self.summary_interval = summary_interval # 몇 턴마다 요약할지
self.turn_count = 0
def _summarize(self, text):
"""기존 요약 + 새 대화를 합쳐서 LLM으로 재요약"""
response = llm.invoke([
SystemMessage(content='주어진 대화 내용을 핵심만 간결하게 요약하세요. 한국어로 작성하세요'),
HumanMessage(content=f'기존 요약:\n{self.summary}\n\n새 대화:\n{text}')
])
return response.content
def add_exchange(self, user_msg, ai_msg):
"""새 대화 턴 추가. summary_interval 도달 시 자동 요약"""
self.recent_messages.append(f'사용자 : {user_msg}')
self.recent_messages.append(f'AI : {ai_msg}')
self.turn_count += 1
if self.turn_count % self.summary_interval == 0:
conversation_text = '\n'.join(self.recent_messages)
self.summary = self._summarize(conversation_text) # 요약 업데이트
self.recent_messages = [] # 최근 메시지 초기화
print(f' 요약 완료 턴 {self.turn_count} 에서 요약')
def get_context(self):
"""LLM에 전달할 컨텍스트 문자열 반환"""
parts = []
if self.summary:
parts.append(f'[이전 대화 요약] {self.summary}')
if self.recent_messages:
parts.append(f'[최근 대화]\n' + '\n'.join(self.recent_messages))
return '\n\n'.join(parts)
|
동작 원리
1
2
3
4
5
6
7
8
9
10
11
12
| 대화 1턴: recent_messages에 추가
대화 2턴: recent_messages에 추가 ← summary_interval=2 도달!
→ recent_messages를 LLM으로 요약 → self.summary 업데이트
→ recent_messages = [] 초기화
대화 3턴: recent_messages에 추가
get_context() 호출 시:
[이전 대화 요약] 사용자가 이름과 학생임을 소개하였고, AI는 인사를 하고...
[최근 대화]
사용자 : 세번째, 나는 파이썬을 좋아합니다
AI : 파이썬은 정말 재미있죠!
|
실사용 예시:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| smem = SummaryMemory(summary_interval=2)
conversations = [
('첫번째, 내 이름은 abc입니다', '안녕하세요, abc님'),
('두번째, 나는 학생입니다', '학생이시군요! 멋지십니다'), # ← 2턴: 요약 실행
('세번째, 나는 파이썬을 좋아합니다', '파이썬은 정말 재미있죠!')
]
for user_msg, ai_msg in conversations:
smem.add_exchange(user_msg, ai_msg)
# → '요약 완료 턴 2 에서 요약' 출력
smem.get_context()
# → '[이전 대화 요약] 사용자가 이름과 학생임을 소개하였고...\n\n[최근 대화]\n사용자: 세번째...'
|
9. SummaryChatbot — 요약 메모리 통합 챗봇
SummaryMemory를 활용한 실전 챗봇 클래스입니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| class SummaryChatbot:
def __init__(self, system_prompt='당신은 도움이 되는 AI 어시스턴트입니다.', summary_interval=3):
self.system_prompt = system_prompt
self.memory = SummaryMemory(summary_interval=summary_interval)
def chat(self, user_input):
# 1. 현재 메모리 컨텍스트 가져오기
context = self.memory.get_context()
# 2. 메시지 구성
messages = [SystemMessage(content=self.system_prompt)]
if context:
messages.append(SystemMessage(content=f'대화 맥락 : \n{context}'))
messages.append(HumanMessage(content=user_input))
# 3. LLM 호출
response = llm.invoke(messages)
ai_response = response.content
# 4. 메모리에 저장
self.memory.add_exchange(user_input, ai_response)
return ai_response
|
실전 예제 — IT 커리어 상담사
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| bot = SummaryChatbot(
system_prompt='당신은 IT 커리어 상담사입니다',
summary_interval=2
)
questions = [
"안녕하세요, 백엔드 개발자 3년차인데 고민이 있습니다",
"AI/ML 분야로 전환을 고려 중인데 어떤 준비가 필요할까요?",
"현재 Python은 잘하는데 수학과 통계 기초가 부족합니다",
"온라인 강의와 학교 중 어떤 것이 효과적일까요?"
]
for q in questions:
print(f'[user] {q}')
answer = bot.chat(q)
print(f'[상담사] {answer}')
|
실행 출력:
1
2
3
4
5
6
7
8
9
| [user] 안녕하세요, 백엔드 개발자 3년차인데 고민이 있습니다
[상담사] 안녕하세요! 백엔드 개발자로 3년 차라니 멋지네요...
[user] AI/ML 분야로 전환을 고려 중인데 어떤 준비가 필요할까요?
요약 완료 턴 2 에서 요약 ← summary_interval=2 도달, 자동 요약
[상담사] AI/ML 분야로의 전환을 고려하고 계시군요. 다음은 준비에 도움이 될 단계입니다:
1. 기초 수학 및 통계학 이해 (선형 대수, 미적분학, 확률/통계)
2. Python 심화 학습
3. ML 기본 개념 학습 ...
|
챗봇 클래스 데이터 흐름
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| user_input
↓
memory.get_context()
├─ [이전 대화 요약] ... (summary가 있을 경우)
└─ [최근 대화] ... (recent_messages가 있을 경우)
↓
messages 구성:
[SystemMessage: 역할]
[SystemMessage: 맥락] ← 있을 경우만
[HumanMessage: 질문]
↓
llm.invoke(messages)
↓
AI 응답
↓
memory.add_exchange(user_input, ai_response)
→ turn_count 증가
→ summary_interval 도달 시 자동 요약
|
10. 자주 나오는 실수 / 주의사항
❌ AIMessage를 빠뜨리는 실수
1
2
3
4
5
6
7
8
9
10
| # 잘못된 예
messages.append(HumanMessage('내 이름은 abc입니다'))
response = llm.invoke(messages)
messages.append(HumanMessage('내 이름은 뭔가요?')) # ← AIMessage 누락!
# 올바른 예
messages.append(HumanMessage('내 이름은 abc입니다'))
response = llm.invoke(messages)
messages.append(AIMessage(response.content)) # ← AI 응답도 반드시 추가
messages.append(HumanMessage('내 이름은 뭔가요?'))
|
❌ session_id 없이 RunnableWithMessageHistory 호출
1
2
3
4
5
6
| # 잘못된 예 (config 없음)
chain_with_history.invoke({'input': '안녕'})
# 올바른 예
config = {'configurable': {'session_id': 'user_001'}}
chain_with_history.invoke({'input': '안녕'}, config=config)
|
❌ MessagesPlaceholder의 variable_name과 history_messages_key 불일치
1
2
3
4
5
6
7
8
9
10
11
12
| # 잘못된 예
MessagesPlaceholder(variable_name='chat_history') # 'chat_history'로 설정
RunnableWithMessageHistory(
chain,
get_session_history,
history_messages_key='history' # 'history'로 다름 → 오류!
)
# 올바른 예: 이름 일치
MessagesPlaceholder(variable_name='history')
RunnableWithMessageHistory(..., history_messages_key='history')
|
⚠️ ConversationBufferMemory는 Deprecated
langchain_classic.memory 모듈의 클래스들은 LangChain v0.2+ 에서 deprecated.- 신규 프로젝트에서는
langchain_core.chat_history.InMemoryChatMessageHistory 사용 권장.
⚠️ SummaryMemory의 summary_interval 선택
summary_interval이 너무 작으면 LLM 요약 호출이 잦아져 비용 증가.summary_interval이 너무 크면 메모리 절감 효과가 없음.- 일반적으로 5~10 턴 권장.
11. [보충] 메모리 전략 비교
| 전략 | 특징 | 토큰 비용 | 맥락 유실 | 추가 LLM 호출 |
|---|
| Buffer | 전체 대화 저장 | 매우 높음 | 없음 | 없음 |
| Window (k턴) | 최근 k턴만 유지 | 낮음 | 오래된 것 유실 | 없음 |
| Summary | 오래된 것 요약 압축 | 중간 | 일부 유실 | 있음 (요약용) |
LangGraph와의 관계
1
2
3
| LangChain Chain : 단방향 파이프라인. memory를 내부에 자동 관리.
LangGraph : 상태(State) 기반 그래프. 개발자가 state를 직접 설계.
더 복잡한 multi-turn, multi-agent 시나리오에 적합.
|
요약 메모리 활용 시 컨텍스트 구조
1
2
3
4
5
6
7
8
9
10
11
12
13
| ┌──────────────────────────────────────────────┐
│ SystemMessage: 역할 설정 │
├──────────────────────────────────────────────┤
│ SystemMessage: 대화 맥락 │
│ [이전 대화 요약] 요약된 내용... │
│ [최근 대화] │
│ 사용자: 최근 질문1 │
│ AI: 최근 답변1 │
│ 사용자: 최근 질문2 │
│ AI: 최근 답변2 │
├──────────────────────────────────────────────┤
│ HumanMessage: 현재 질문 │
└──────────────────────────────────────────────┘
|
12. 기억해야 할 정보부터 정의하기
메모리 전략은 모든 대화를 오래 저장하는 방향으로만 정할 필요가 없다. 최근 작업의 지시, 장기적으로 유지할 사용자 설정, 일회성 검색 결과를 나누면 각각의 보존 기간과 전달 범위를 설명하기 쉽다.
Window와 Summary를 비교할 때는 긴 대화 뒤에 초반 조건을 다시 묻는 입력을 준비한다. 필요한 사실을 잃는 시점과 입력 크기를 함께 확인하고, 요약 오류가 이어지는 대화에 누적되지 않는지도 살펴본다.