📚 Agentic AI - 기업용 자율 에이전트 개발 2장 · LLM API 기초 Agentic AI란

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가 기억하는 것처럼 보이는 이유는 매번 대화 전체를 다시 보내기 때문입니다.
  • 그 대가로 대화가 길어질수록 비용이 늘고, 언젠가는 컨텍스트 윈도 상한에 닿습니다.
  • 다음 절에서 이 "다시 보내기"를 손으로 구현합니다.
← 이전 절04. 따라하기 chat 함수 만들기다음 절 →06. 따라하기 대화를 손으로 쌓아 멀티턴 만들기