16장. 멀티턴 대화와 세션 관리
대화 이력 — 기억은 우리가 보관한다
한 줄 요약
모델은 기억하지 않으므로 기억은 우리 코드가 보관합니다. 오간 메시지를 리스트에 쌓아 두었다가, 새 문의가 올 때마다 "지금까지의 대화 전체 + 새 문의" 를 보냅니다. 모델은 매번 대화 전체를 다시 읽기 때문에 기억하는 것처럼 동작합니다.
1. 이력은 메시지의 리스트다
대화 이력(message history) 은 지금까지 오간 메시지를 순서대로 담은 리스트입니다. Gemini API에서 메시지 하나는 types.Content이고, 누가 한 말인지를 role로 적습니다.
self.history: list[types.Content] = [] # 대화 이력이 곧 '기억'
self.history.append(
types.Content(role="user", parts=[types.Part.from_text(text=text)]))
호출할 때는 문자열 하나 대신 이 리스트를 통째로 넘깁니다.
response = client.models.generate_content(
model=MODEL,
contents=self.history, # 매번 '전체 이력'이 입력으로 나간다
config=...,
)
2장부터 써 온 contents=에 문자열이 아니라 리스트가 들어간 것이 전부입니다.
2. 이력 안에는 말만 있는 것이 아니다
우리 상담원은 도구를 씁니다. 그래서 고객 문의 하나를 처리하는 동안 메시지가 네 개 생깁니다. 「따라하기」의 코드에서 첫 문의를 처리한 직후의 이력을 찍어 보면 이렇습니다.
| 순서 | role | 내용물 | 누가 만들었나 |
|---|---|---|---|
| 0 | user |
글: "제 최근 주문 상태 알려주세요." | 고객 |
| 1 | model |
도구 호출(function_call): get_my_orders |
모델 |
| 2 | user |
도구 결과(function_response): 주문 3건 | 우리 코드 |
| 3 | model |
글: 최종 답변 | 모델 |
여기서 두 가지를 봅니다.
도구 결과의 role이 user입니다. 2번 메시지는 고객이 한 말이 아니라 우리 코드가 도구를 실행해 얻은 결과인데, role은 user로 들어갑니다. "모델이 아닌 쪽에서 온 것"이라는 뜻으로 읽으면 됩니다. 이 사실은 뒤에서 이력을 자를 때 중요해집니다.
1번과 2번은 짝입니다. 도구 호출과 도구 결과는 한 묶음으로 움직입니다. 5장에서 손으로 왕복시켰던 바로 그 두 메시지입니다.
3. 도구 왕복도 이력에 남겨야 한다
이력을 가볍게 만들고 싶으면 0번(고객의 말)과 3번(최종 답변)만 남기고 1·2번을 버릴 수도 있습니다. 그러면 무엇이 사라질까요.
"아까 조회한 그 주문"의 조회 결과가 사라집니다. 주문번호, 옵션, 배송 완료일 같은 값은 최종 답변의 문장이 아니라 도구 결과 안에 온전히 들어 있습니다. 다음 턴에 고객이 "그거 취소하면…"이라고 물었을 때 모델이 주문번호를 꺼내 쓸 수 있는 것은 2번 메시지가 이력에 남아 있기 때문입니다.
우리 코드는 SDK가 도구를 자동으로 왕복시키게 하고(5장), 그 왕복 기록을 통째로 이력으로 삼습니다.
# 자동 호출 왕복 이력(도구 결정·결과)도 대화 이력에 보존해야
# 다음 턴에 "아까 조회한 그 주문"을 이해할 수 있다.
if response.automatic_function_calling_history:
self.history = list(response.automatic_function_calling_history)
if response.candidates:
self.history.append(response.candidates[0].content)
| 코드 | 하는 일 |
|---|---|
automatic_function_calling_history |
우리가 보낸 이력에 이번 턴의 도구 호출과 도구 결과가 덧붙은 리스트. 도구를 쓰지 않은 턴에는 비어 있습니다 |
response.candidates[0].content |
모델의 최종 답변 메시지. 이것까지 붙여야 한 턴이 끝납니다 |
기억할 가치가 있는 것은 말만이 아닙니다. 모델이 한 행동(도구 호출)과 그 결과도 대화의 일부입니다.
4. 누가 무엇을 하는가
| 일 | 누가 |
|---|---|
| 지시어("그거")가 무엇을 가리키는지 이력에서 찾아 해석한다 | 모델 |
| 어떤 도구를 부를지 고른다 | 모델 |
| 도구를 실제로 실행하고 결과를 돌려준다 | 우리 코드(SDK의 자동 호출) |
| 이력을 보관하고, 매번 함께 보낸다 | 우리 코드 |
| 이력을 얼마나 길게 둘지, 언제 버릴지 정한다 | 개발자 |
모델이 하는 일은 "읽고 해석하는 것"까지입니다. 읽을 것을 준비하는 일은 전부 우리 몫입니다.
시스템 프롬프트에도 한 줄을 보탰습니다.
대화 맥락을 기억하고, '그거/아까 그 주문' 같은 지시어를 앞선 대화에서 찾아 이해합니다.
이력을 넣어 주는 것에 더해, 지시어는 이력에서 찾아 해석하라고 분명히 적어 준 것입니다.
시스템 프롬프트에는 한 가지가 더 있습니다.
환불 기간·배송비 같은 정책 내용은 이 도구들로 조회할 수 없습니다. 지어내지 않고
"확인 후 안내드리겠습니다"라고 답합니다.
이 상담원이 가진 도구는 주문·상품 조회용입니다. 정책 문서를 찾는 도구는 없습니다. 가진 도구로 확인할 수 있는 것과 없는 것의 경계를 프롬프트에 적어 주면, 모델은 확인할 수 없는 것을 짐작으로 채우지 않습니다.
5. 참고 — 서버가 이력을 맡아 주는 방식
이 과정에서는 이력을 우리 쪽에 보관합니다. 무엇이 모델에게 들어가는지 눈으로 볼 수 있고, 자르거나 요약하는 일(17장)을 우리가 정할 수 있기 때문입니다.
Gemini API에는 이력을 서버가 맡아 주는 방식(Interactions API)도 있습니다. 앞 응답의 ID를 다음 요청에 넘기면 대화가 이어집니다. 다만 2026년 10월 기준으로 이 과정에서 설치한 SDK는 이 기능을 쓸 때 "실험적 기능이며 바뀔 수 있다"는 경고를 띄웁니다. 자세한 내용은 공식 문서(https://ai.google.dev/gemini-api/docs)를 참고하세요.
어느 방식이든 원리는 같습니다. 모델은 기억하지 않고, 누군가 대화를 보관했다가 다시 넣어 줍니다.
핵심 정리
- 대화 이력은 메시지(
types.Content)의 리스트이고,contents=에 통째로 넘깁니다. - 도구를 쓰는 턴 하나는 고객의 말 → 도구 호출 → 도구 결과 → 최종 답변 네 메시지입니다.
- 도구 결과 메시지의 role은
user입니다. 도구 호출과 도구 결과는 짝입니다. - 도구 왕복까지 이력에 남겨야 고객이 말한 적 없는 값(주문번호 등) 을 다음 턴에 꺼내 쓸 수 있습니다.
- 해석은 모델이, 보관과 재전송은 우리 코드가 합니다.