16장. 멀티턴 대화와 세션 관리
세션과 스레드 — 누구의, 어느 대화인가
한 줄 요약
이력을 보관하기로 했으면 어떤 단위로 나눠 보관할지 정해야 합니다. 대화 하나를 스레드(thread) 라고 부르고, 스레드마다 이력을 따로 둡니다. 8장에서 만든 "누가 로그인했는가"(신원 세션)와는 다른 개념입니다.
1. 이력이 섞이면 안 된다
상담 창구에는 동시에 여러 고객이 들어옵니다. 이력을 리스트 하나에 전부 쌓으면 고객 A의 "그거"가 고객 B의 주문을 가리키게 됩니다. 같은 고객이라도 어제의 상담과 오늘의 상담은 다른 대화일 수 있습니다.
그래서 이력은 대화 하나 단위로 격리합니다. 이 단위가 스레드이고, 스레드를 가리키는 이름표가 thread_id입니다.
2. 신원 세션과 대화 스레드
8장에서 CustomerSession을 만들었습니다. 이름에 "세션"이 들어 있어 헷갈리기 쉬우니 나란히 놓고 구분합니다.
신원 세션 (8장 CustomerSession) |
대화 스레드 (이번 장 ChatSession) |
|
|---|---|---|
| 답하는 질문 | 지금 상대가 누구인가 | 지금 어느 대화의 연속인가 |
| 정하는 것 | 볼 수 있는 데이터의 범위 (도구의 권한) | 모델에게 보낼 이력 (문맥) |
| 수명 | 로그인해 있는 동안 | 대화 하나가 이어지는 동안 |
| 코드에서 | CustomerSession("C007") |
ChatSession(thread_id="web-C007-0001") |
한 고객(신원 세션 하나)이 스레드를 여러 개 가질 수 있습니다. 반대로 스레드 하나가 여러 고객에 걸치는 일은 없어야 합니다.
코드 위쪽의 이 두 줄이 신원 쪽입니다.
session = CustomerSession("C007")
tools = make_tools(session)
make_tools(session)은 8장에서 본 대로 이 고객의 주문만 볼 수 있는 도구 묶음을 만듭니다. 이력이 아무리 길어져도 이 경계는 바뀌지 않습니다. 권한은 이력이 아니라 신원 세션이 정합니다.
3. 스레드 하나 = 객체 하나
우리 구현에서는 스레드 하나를 ChatSession 객체 하나로 나타냅니다.
class ChatSession:
"""세션(스레드) 하나의 대화 이력을 관리한다."""
def __init__(self, thread_id: str, max_turns: int = 10):
self.thread_id = thread_id
self.history: list[types.Content] = [] # 대화 이력이 곧 '기억'
self.max_turns = max_turns # 보관할 최근 턴 수
history가 객체 안에 들어 있으므로, 객체를 두 개 만들면 이력도 두 개가 됩니다. 따로 격리하는 코드를 쓰지 않아도 객체가 다르면 대화가 섞이지 않습니다.
a = ChatSession(thread_id="ex-A")
b = ChatSession(thread_id="ex-B") # a 의 대화를 전혀 모른다
이번 장의 thread_id는 이름표 역할만 합니다. 여러 사람이 동시에 접속하는 서비스라면 이 ID로 "이 대화의 이력"을 찾아 오게 됩니다.
4. 스레드가 끝나면 기억도 끝난다
history는 프로그램이 돌아가는 동안 메모리에만 있습니다. 프로그램을 끄거나 객체가 사라지면 이력도 함께 사라집니다.
| 상황 | 기억 |
|---|---|
| 같은 스레드에서 다음 문의 | 남아 있다 |
| 다른 스레드 | 모른다 (격리) |
| 프로그램을 껐다 켠 뒤 | 사라졌다 |
지난주에 교환을 문의한 고객이 다시 찾아와 "저번에 문의한 건 어떻게 됐어요?"라고 하면, 지금의 구조로는 처음 보는 사람처럼 대할 수밖에 없습니다. 이 숙제는 17장에서 풉니다.
핵심 정리
- 이력은 스레드(대화 하나) 단위로 격리하고,
thread_id로 구분합니다. - 신원 세션은 "누구인가"(권한)를, 대화 스레드는 "어느 대화인가"(문맥)를 정합니다.
- 권한의 경계는 이력이 아니라 신원 세션과 도구 코드에 있습니다.
- 스레드 하나를
ChatSession객체 하나로 나타냅니다. 객체가 다르면 이력이 섞이지 않습니다. - 이력은 메모리에만 있어, 스레드가 끝나면 사라집니다.