LLM은 방금 한 말도 잊는다
한 줄 요약
generate_content 를 두 번 부르면 두 번째는 첫 번째를 전혀 모릅니다. 이 성질을 무상태(stateless) 라고 합니다. 챗봇을 만들려면 반드시 넘어야 할 벽입니다.
1. 직접 확인하기
앞 절에서 만든 파일의 [2] 부분입니다.
아래는 code/ch02_chat.py 의 일부입니다 — 위에서 이미 붙여 넣었으니 다시 넣지 마세요.
print(" 고객: 제 이름은 김민준입니다.")
print(" 상담원:", chat(SYSTEM, "제 이름은 김민준입니다."))
print(" 고객: 제 이름이 뭐라고 했죠?")
print(" 상담원:", chat(SYSTEM, "제 이름이 뭐라고 했죠?"))
실행 결과:
==============================================================
[2] 기억하지 못하는 상담원
고객: 제 이름은 김민준입니다.
상담원: 김민준 고객님, 안녕하세요. 무엇을 도와드릴까요?
고객: 제 이름이 뭐라고 했죠?
상담원: 고객님의 성함은 확인 후 안내드리겠습니다.
→ 방금 들은 이름을 잊었습니다. 두 번의 호출이 서로 남남이기 때문입니다.
한 줄 전에 "김민준 고객님"이라고 불러 놓고, 바로 다음 줄에서 이름을 모릅니다.
2. 왜 이런가
두 번의 호출에서 실제로 무엇을 보냈는지 보면 당연합니다.
[호출 ①]
system_instruction: "너는 승승장구몰의 CS 상담원이다..."
contents: "제 이름은 김민준입니다."
→ "김민준 고객님, 안녕하세요."
[호출 ②]
system_instruction: "너는 승승장구몰의 CS 상담원이다..."
contents: "제 이름이 뭐라고 했죠?" ← ①의 내용이 어디에도 없다
→ "확인 후 안내드리겠습니다."
호출 ②의 입력 어디에도 "김민준"이 없습니다. 모델 입장에서는 난데없이 "제 이름이 뭐라고 했죠?"라는 질문 하나만 받은 것입니다. 모를 수밖에 없죠.
모델은 대화를 기억하는 장치가 아니라, 입력을 받아 출력을 내놓는 함수입니다. 함수
f(x)를 두 번 부르면 두 번째 호출이 첫 번째 인자를 기억하지 못하는 것과 똑같습니다.
그림으로
[기억할 거라는 착각] 나 ──"김민준입니다"──▶ [모델] (어딘가에 기억해 둔다) 나 ──"이름이 뭐죠?"──▶ [모델] ──▶ "김민준님이시죠" [실제로 일어나는 일] 나 ──"김민준입니다"──▶ [함수] ──▶ 답변 (끝. 아무것도 남지 않는다) 나 ──"이름이 뭐죠?"──▶ [함수] ──▶ "모릅니다" (앞 호출을 모른다)
3. 시스템 지시도 못 구합니다
"시스템 지시에 기억하라고 적으면 되지 않나요?"
아래는 개념 설명용 코드입니다 — 실습 파일에 넣지 않습니다.
SYSTEM = "너는 CS 상담원이다. 고객이 한 말을 꼭 기억한다." # 소용없음
안 됩니다. 기억할 내용 자체가 입력에 없기 때문입니다. 지시는 "주어진 입력을 어떻게 다룰지" 를 정하지, 없는 입력을 만들어 내지는 못합니다.
이건 시스템 지시로 풀 수 있는 문제가 아니라 구조의 문제입니다.
4. 그럼 ChatGPT는 어떻게 기억하나
ChatGPT도 기억하지 않습니다. 대신 내부에서 이런 일을 합니다.
사용자가 세 번째 메시지를 보낼 때, 실제로 서버에 가는 것:
[1] 사용자: 제 이름은 김민준입니다.
[2] AI : 김민준 고객님, 안녕하세요.
[3] 사용자: 제 이름이 뭐라고 했죠? ← 이번 질문
전부 한 덩어리로 보냄
지금까지의 대화를 통째로 매번 다시 보내는 것입니다. 그래서 기억하는 것처럼 보입니다.
이 방식을 멀티턴(multi-turn) 이라고 부릅니다. 다음 절에서 이걸 직접 만듭니다.
5. 이 성질이 낳는 결과 두 가지
① 대화가 길어질수록 비용이 늘어난다
매번 전체를 다시 보내므로, 10번째 메시지를 보낼 때는 1~9번째까지 전부 다시 보냅니다. 요금은 보낸 양에 매겨지므로 대화가 길어질수록 한 번 말할 때마다 비용이 커집니다.
1번째 메시지 → 보내는 양: 1
2번째 메시지 → 보내는 양: 3 (1,2번 + 답변)
3번째 메시지 → 보내는 양: 5
...
10번째 메시지 → 보내는 양: 19
② 무한정 보낼 수는 없다
모델이 한 번에 받을 수 있는 양에는 상한이 있습니다. 이를 컨텍스트 윈도(context window) 라고 합니다. gemini-2.5-flash 는 100만 토큰까지 받지만, 무한은 아닙니다.
대화가 아주 길어지면 오래된 부분을 잘라내거나 요약해서 넣는 전략이 필요해집니다. 7장에서 LangGraph를 쓰면 이런 관리를 라이브러리가 맡아 줍니다.
6. 왜 굳이 손으로 해 보나
다음 절에서 대화 쌓기를 손으로 구현합니다. 7장에서 라이브러리가 대신해 줄 일인데도 말이죠.
이유는 하나입니다.
7장에서
checkpointer=InMemorySaver()한 줄을 쓰게 됩니다. (체크포인터(checkpointer) 는 "대화를 어디에 저장할지 정하는 부품"이고,InMemorySaver는 그중 "메모리에 담아 둔다"를 고른 것입니다.) 그 한 줄이 무슨 일을 대신해 주는지 모르면, 대화가 이상해졌을 때 어디를 봐야 할지 알 수 없습니다. 여기서 손으로 한 번 쌓아 보면 그 한 줄의 정체가 "리스트에 append 하는 일" 이라는 걸 알게 됩니다.
AI가 대신해 주는 것과 사람이 판단해야 하는 것을 가르는 선이 여기 있습니다. 라이브러리는 append를 대신해 주지만, "어디까지 기억할 것인가", "누구의 기억인가" 는 여전히 사람이 정합니다.
핵심 정리
- LLM 호출은 무상태입니다. 두 번째 호출은 첫 번째를 전혀 모릅니다.
- 실행해 보면 한 줄 전에 부른 이름을 바로 다음 줄에서 모릅니다.
- 시스템 지시로는 해결되지 않습니다. 기억할 내용 자체가 입력에 없기 때문입니다.
- ChatGPT가 기억하는 것처럼 보이는 이유는 매번 대화 전체를 다시 보내기 때문입니다.
- 그 대가로 대화가 길어질수록 비용이 늘고, 언젠가는 컨텍스트 윈도 상한에 닿습니다.
- 다음 절에서 이 "다시 보내기"를 손으로 구현합니다.