4장. 대화형 챗봇과 메모리 전략
앞부분을 요약하는 Summary 메모리
상담 기록철이 열 장이 넘으면, 꼼꼼한 상담원은 앞의 여덟 장을 읽고 "제주도, 2박 3일, 어른 둘 아이 둘, 오전 출발 원함" 같은 요약 메모 한 장을 써서 철 맨 앞에 붙입니다. 다음 상담원은 요약 메모와 최근 두 장만 읽어도 손님 사정을 압니다. Summary 메모리가 이 방법입니다. 오래된 기록은 요약 체인으로 짧게 줄이고, 요약과 최근 기록을 함께 보냅니다.
네 상자 가운데 둘째 상자에서 모델을 한 번 더 부른다는 점이 Window 메모리와 다릅니다. 요약하는 모델이 따로 한 번 일하고, 그 요약을 "앞의 대화 요약: …" 이라는 시스템 메시지로 만들어 최근 기록 앞에 붙입니다. 여기서 요약 메모가 요약 체인의 결과, 철 맨 앞에 붙이는 일이 시스템 메시지로 넣는 일입니다. 비유와 다른 점은, 요약도 모델이 쓰는 글이라 중요한 숫자를 빠뜨리거나 바꿔 쓸 수 있다는 것입니다. 그래서 요약 프롬프트에 "숫자와 인원은 빠뜨리지 말라"고 적습니다.
요약에 꼭 남길 것
| 남길 것 | 예 | 빠지면 생기는 일 |
|---|---|---|
| 숫자 | 2박 3일, 예산 150만 원 | 일정이나 예산이 틀린 답 |
| 사람 | 어른 둘, 아이 둘 | 아이에게 맞지 않는 일정 추천 |
| 정한 것 | 오전 출발, 동쪽 해안 숙소 | 이미 정한 것을 다시 묻는다 |
| 바뀐 것 | 둘째 날은 비 → 실내 일정 | 옛 계획대로 안내한다 |
짧은 예시
요약하는 모델 자리에 가짜 모델을 두고, 요약 + 최근 2개 + 새 질문으로 보낼 메시지를 꾸며 봅니다.
from langchain_core.language_models.fake_chat_models import FakeListChatModel
from langchain_core.messages import AIMessage, HumanMessage, SystemMessage
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
old = [HumanMessage("제주도 가요."), AIMessage("며칠인가요?"), HumanMessage("2박 3일, 아이 둘이요."), AIMessage("동쪽 해안을 추천합니다.")]
recent = [HumanMessage("렌터카는 언제 빌려요?"), AIMessage("공항에서 바로 빌리세요.")]
# 요약 체인: 앞부분 대화를 받아 짧은 요약 글을 쓴다. 숫자와 인원을 지키라고 적는다
summary_prompt = ChatPromptTemplate.from_messages([
("system", "다음 대화를 두 문장 이내로 요약하라. 날짜, 박수, 인원 같은 숫자는 빠뜨리지 않는다."),
("human", "{dialog}"),
])
summarizer = FakeListChatModel(responses=["손님은 제주도 2박 3일, 아이 둘과 함께 간다. 동쪽 해안 일정을 추천받았다."])
summary_chain = summary_prompt | summarizer | StrOutputParser()
dialog = "\n".join(f"{m.type}: {m.content}" for m in old) # 메시지를 "역할: 내용" 줄글로 바꾼다
summary = summary_chain.invoke({"dialog": dialog})
# 보낼 메시지: 시스템 지시 + 요약 + 최근 기록 + 새 질문
to_send = [SystemMessage("너는 여행 비서다."), SystemMessage("앞의 대화 요약: " + summary)] + recent + [HumanMessage("일정 정리해 주세요.")]
for m in to_send:
print(m.type, "|", m.content)system | 너는 여행 비서다.
system | 앞의 대화 요약: 손님은 제주도 2박 3일, 아이 둘과 함께 간다. 동쪽 해안 일정을 추천받았다.
human | 렌터카는 언제 빌려요?
ai | 공항에서 바로 빌리세요.
human | 일정 정리해 주세요.앞의 메시지 4개가 요약 한 줄로 바뀌었고, 그 요약이 둘째 시스템 메시지로 들어갔습니다. 최근 2개와 새 질문은 그대로입니다. 요약 안에 "2박 3일"과 "아이 둘"이 남아 있으므로 모델은 일정 정리 질문에 이 둘을 쓸 수 있습니다. 진짜 요약 모델로는 실습 6에서 해 봅니다.
코드가 의미하는 것
이 코드는 앞의 장들을 요약 메모 한 장으로 줄여 철 맨 앞에 붙이는 일을 흉내 냅니다. 요약을 쓰는 상담원 자리에는 가짜 모델을 앉혔습니다.
| 코드 | 하는 일 | 챗봇에서 뜻하는 것 |
|---|---|---|
summary_prompt | summarizer | StrOutputParser() | 요약 체인을 만든다 | 3장의 체인 모양 그대로, 요약만 하는 상담원을 하나 더 둡니다. 진짜 모델이면 이 호출에도 토큰이 듭니다 |
| 요약 프롬프트의 "숫자는 빠뜨리지 않는다" | 요약이 지킬 것을 적는다 | 요약에서 박수와 인원이 빠지면 뒤의 답도 빠뜨리므로 미리 못 박는 줄입니다 |
"\n".join(f"{m.type}: {m.content}" for m in old) | 메시지를 "역할: 내용" 줄글로 바꾼다 | 요약 체인에는 앞부분 대화를 글 하나로 건넵니다 |
SystemMessage("앞의 대화 요약: " + summary) | 요약을 시스템 메시지로 만든다 | 손님의 말이나 비서의 답이 아니라 참고 메모로 넣는다는 뜻입니다 |
[…] + recent + [HumanMessage(…)] | 시스템, 요약, 최근 기록, 새 질문을 잇는다 | 모델이 받을 목록입니다. 앞의 대화는 원문 대신 요약으로만 갑니다 |
결과에서 읽을 것은 두 가지입니다.
- 메시지 4개가 한 줄이 됐다. 둘째 줄
system | 앞의 대화 요약: …하나가 앞의 메시지 네 개를 대신합니다. - 요약 안에 조건이 남았다. "2박 3일"과 "아이 둘"이 요약에 있어 일정 정리에 쓸 수 있습니다. 이 요약은 가짜 모델에 적어 둔 글이므로, 진짜 모델의 요약이 이 둘을 지키는지는 실습 6에서 봅니다.
주의할 점
- 요약할 때마다 모델을 한 번 더 부르므로 요약 호출의 토큰과 시간도 듭니다. 대화가 짧을 때는 요약하지 않고, 기록이 상한을 넘을 때만 요약합니다.
- 요약을 매번 처음부터 새로 쓰지 않고 "지난 요약 + 새로 밀려난 메시지"로 고쳐 쓰면 요약 호출의 입력이 짧아집니다. 프로젝트에서 이 방식을 씁니다.