24장. 서비스 통합과 실행
세션과 대화 이력 — 브라우저마다 대화가 따로 있다
한 줄 요약
서버 하나에 브라우저 여러 개가 붙습니다. 16장에서 배운 "이력이 곧 기억"을 그대로 쓰되, 누구의 이력인지 구분할 이름표가 필요합니다. 화면이 session_id 를 만들어 매 요청에 실어 보내고, 서버는 그 이름표별로 이력을 따로 보관합니다.
1. LLM은 기억하지 않고, 서버가 기억한다
16장의 원리를 다시 적습니다.
- LLM은 이전 호출을 기억하지 않는다.
- 그래서 앞선 대화 전체를 매번 다시 보낸다.
- 그 이력을 보관하는 것은 우리 프로그램이다.
22장의 run_supervisor도 이 방식이었습니다. 이력을 받아서, 새 질문을 붙여 보내고, 갱신된 이력을 돌려주었습니다. 터미널에서는 변수 하나에 이력을 담으면 됐습니다. 서버에서는 대화가 여러 개입니다.
2. 이름표는 화면이 만든다
제공된 index.html에 이런 줄이 있습니다.
const sessionId = "web-" + Math.random().toString(36).slice(2, 10);
화면이 열릴 때 web-k3x9a2bf 같은 무작위 이름표를 하나 만듭니다. 그리고 문의를 보낼 때마다 함께 보냅니다.
body: JSON.stringify({ session_id: sessionId, message }),
이 이름표가 16장에서 말한 세션(session), 곧 "하나의 이어지는 대화"를 가리킵니다.
| 상황 | session_id |
대화 |
|---|---|---|
| 같은 화면에서 계속 묻는다 | 그대로 | 이어진다 |
| 화면을 새로고침한다 | 새로 만들어진다 | 새 대화가 된다 |
| 탭을 하나 더 연다 | 탭마다 다르다 | 서로 섞이지 않는다 |
3. 서버는 이름표별로 보관한다
main.py에서 이력을 다루는 줄은 셋입니다.
histories: dict[str, list] = {} # session_id → messages
history = histories.get(req.session_id, []) # 1) 꺼낸다 (없으면 빈 목록)
messages = list(history) + [{"role": "user", "content": req.message}] # 2) 새 문의를 붙인다
...
histories[req.session_id] = new_messages # 3) 갱신해 넣는다
| 단계 | 하는 일 | 16장에서는 |
|---|---|---|
| 꺼낸다 | 이 이름표의 이력을 찾는다. 처음이면 빈 목록 | 대화 객체의 이력 변수 |
| 붙인다 | 이력 뒤에 이번 문의를 더해 Supervisor에게 보낸다 | 같다 |
| 넣는다 | 답까지 포함된 이력으로 바꿔 둔다 | 같다 |
16장에서 변수 하나였던 것이 딕셔너리로 바뀌었을 뿐입니다. 탭 A의 "그거"는 A의 이력에서, 탭 B의 "그거"는 B의 이력에서 풀립니다. 실습문제 2에서 직접 확인합니다.
4. 두 가지 "세션"을 구분한다
이 프로젝트에는 세션이라는 말이 두 군데 나옵니다. 서로 다른 것입니다.
| 이름 | 무엇을 가리키나 | 어디에 있나 | 무엇을 정하나 |
|---|---|---|---|
CustomerSession |
누가 로그인했는가 | 8장 haru_tools.py |
누구의 주문을 볼 수 있는가 (권한) |
session_id |
어느 대화인가 | 화면이 만들어 보낸다 | 어느 이력을 이어 갈 것인가 (기억) |
지금 서버에서는 CustomerSession이 C003 하나로 고정이고, session_id만 브라우저마다 다릅니다. 탭을 열 개 열면 같은 고객 C003의 서로 다른 대화 열 개가 됩니다.
5. 이력을 메모리에 둔다는 것
histories는 파이썬 딕셔너리, 곧 서버 프로그램의 메모리에 있습니다. 수업에서 내 컴퓨터로 돌리기에 알맞은 가장 단순한 보관 방법입니다. 이 선택에서 세 가지 성질이 따라옵니다.
| 성질 | 뜻 | 여러 사람이 쓰는 서비스로 갈 때 |
|---|---|---|
| 서버와 수명이 같다 | 서버를 다시 켜면 대화가 처음부터다. --reload로 코드를 고쳐 저장할 때도 같다 |
이력을 데이터베이스 같은 바깥 저장소에 둔다 |
| 대화만큼 길어진다 | 이력이 매 호출의 입력으로 나간다 | 17장의 요약 메모리를 붙인다 |
| 이름표는 화면이 만든다 | session_id는 화면이 보낸 값을 그대로 쓴다 |
이름표를 로그인 정보와 묶는다 |
23장에서 InMemorySaver를 두고 한 이야기와 같습니다. 메모리에 둔 것은 프로그램과 함께 사라지고, 오래 남겨야 하는 것은 바깥 저장소로 옮깁니다.
이름표가 화면에서 오는데도 주문 데이터가 안전한 까닭도 봐 둡니다. 어떤 이름표로 대화하든 주문 데이터는 CustomerSession이 정한 고객 것만 나옵니다. 권한의 경계를 대화 이름표가 아니라 로그인 세션에 둔 8장의 설계가 여기서도 받쳐 줍니다.
핵심 정리
- 대화의 기억은 서버가 이력을 보관해 만듭니다. 16장과 같은 원리입니다.
- 화면이 무작위
session_id를 만들어 매 요청에 실어 보냅니다. 새로고침하면 새 대화입니다. - 서버는
session_id별로 이력을 꺼내고, 붙이고, 다시 넣습니다. CustomerSession(누가 로그인했나)과session_id(어느 대화인가)는 다른 것입니다.- 이력은 메모리에 있어 서버를 끄면 사라집니다. 서비스로 가려면 바깥 저장소로 옮깁니다.