4장. 대화형 챗봇과 메모리 전략
MessagesPlaceholder 로 기록 자리 만들기
상담 카드 양식 맨 위에 "지난 상담 기록" 칸이 있다고 해 봅시다. 칸은 비어 있고, 손님이 오면 그 손님의 기록 장을 몇 장이든 끼워 넣습니다. 처음 온 손님이면 한 장도 없고, 단골이면 열 장도 넘습니다. 3장의 ChatPromptTemplate 에는 {question} 처럼 글자 하나가 들어갈 자리만 있었습니다. 메시지 여러 개가 통째로 들어갈 자리가 MessagesPlaceholder 입니다.
세 상자가 프롬프트 틀의 세 칸입니다. 가운데 보라 테두리 상자인 기록 자리에는 메시지가 0개부터 몇 개든 들어가고, 양옆의 시스템 지시와 새 질문은 늘 메시지 하나씩입니다. 여기서 "지난 상담 기록" 칸이 MessagesPlaceholder("history"), 끼워 넣는 기록 장이 대화 기록의 메시지들입니다. 비유와 다른 점은, 끼워 넣은 장이 많을수록 모델이 읽을 토큰이 늘어 요금과 시간이 늘어난다는 것입니다.
프롬프트 틀의 칸
| 칸 | 쓰는 모양 | 들어가는 것 | 메시지 수 |
|---|---|---|---|
| 시스템 지시 | ("system", "너는 여행 비서다.") | 고정된 지시 글 | 1 |
| 기록 자리 | MessagesPlaceholder("history") | 메시지 목록 | 0개 이상 |
| 새 질문 | ("human", "{question}") | 글자 하나 | 1 |
짧은 예시
기록 자리에 메시지 두 개를 넣었을 때와 비웠을 때, 프롬프트가 만드는 메시지를 봅니다.
from langchain_core.messages import AIMessage, HumanMessage
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
prompt = ChatPromptTemplate.from_messages([
("system", "너는 구름다리여행의 여행 비서다."),
# optional=True: 기록을 넘기지 않아도 오류 없이 빈 자리로 둔다
MessagesPlaceholder("history", optional=True),
("human", "{question}"),
])
history = [HumanMessage("제주도에 가요."), AIMessage("며칠 일정인가요?")]
# invoke 로 틀을 채우고 to_messages() 로 메시지 목록을 꺼낸다
for m in prompt.invoke({"history": history, "question": "2박 3일이요."}).to_messages():
print(m.type, "|", m.content)
print("기록 없이:", len(prompt.invoke({"question": "안녕하세요"}).to_messages()), "개")system | 너는 구름다리여행의 여행 비서다.
human | 제주도에 가요.
ai | 며칠 일정인가요?
human | 2박 3일이요.
기록 없이: 2 개기록 두 개가 시스템 지시와 새 질문 사이에 그대로 끼워졌습니다. 기록을 넘기지 않은 둘째 호출은 시스템과 질문, 메시지 2개만 만듭니다. 이름 "history" 는 invoke 에 넘기는 사전의 열쇠와 같아야 합니다.
코드가 의미하는 것
이 코드는 "지난 상담 기록" 칸이 있는 카드 양식을 만들고, 기록 두 장을 끼울 때와 한 장도 없을 때를 견줍니다.
| 코드 | 하는 일 | 챗봇에서 뜻하는 것 |
|---|---|---|
ChatPromptTemplate.from_messages([...]) | 세 칸짜리 프롬프트 틀을 만든다 | 3장의 틀에 기록 자리 하나가 더해졌습니다 |
MessagesPlaceholder("history", optional=True) | 메시지 목록이 들어갈 자리를 비워 둔다 | 손님마다 길이가 다른 기록을 같은 틀에 끼우게 해 줍니다. optional=True 라서 처음 온 손님에게도 씁니다 |
prompt.invoke({"history": history, "question": …}) | 자리 이름에 맞춰 기록과 질문을 넣는다 | 사전의 열쇠 "history" 가 자리 이름과 같아서 기록이 그 자리에 들어갑니다 |
.to_messages() | 채운 틀을 메시지 목록으로 꺼낸다 | 모델이 실제로 받을 목록을 미리 찍어 보는 방법입니다 |
결과에서 읽을 것은 두 가지입니다.
- 기록이 가운데 끼워졌다. system, human, ai, human 차례로, 지난 기록 두 개가 시스템 지시와 새 질문 사이에 들어갔습니다.
- 빈 자리는 흔적 없이 빠진다. 기록 없이 부르면 2개만 나왔습니다. 빈 자리가 빈 메시지로 남지 않습니다.
주의할 점
optional=True를 빼면 기록을 넘기지 않았을 때 "history 가 없다"는 오류가 납니다. 처음 온 손님에게도 쓰는 틀이면 켜 두거나, 빈 목록[]을 넘깁니다.- 기록 자리는 시스템 지시 뒤, 새 질문 앞에 둡니다. 순서가 바뀌면 모델이 대화의 앞뒤를 거꾸로 읽습니다.