14장. Streamlit 화면과 Docker 컨테이너
채팅 화면
메신저는 위로 지난 대화가 쌓이고 아래에 입력 칸이 있습니다. 새 말을 보내면 내 말풍선과 상대의 답 말풍선이 차례로 붙습니다. Streamlit 의 채팅 화면도 대화 기록(세션 상태)을 위에서부터 다시 그리고, 새 입력이 오면 모델에게 기록과 함께 보내 답을 붙입니다.
1번에서 세션 상태의 대화 기록을 말풍선으로 다시 그립니다. 2번에서 st.chat_input 으로 새 질문을 받습니다. 3번에서 시스템 지시와 최근 대화를 함께 모델에 보냅니다. 4번에서 답을 말풍선으로 그리고 기록에 더합니다. 여기서 메신저의 대화방이 세션 상태의 기록입니다. 다만 Streamlit 의 기록은 탭을 닫으면 사라지므로, 남겨야 하면 따로 저장합니다.
대화 기록의 꼴
대화 기록은 6장부터 쓴 채팅 API 의 메시지 목록과 같은 꼴로 둡니다. 그래야 그대로 모델에 보낼 수 있습니다.
| 칸 | 값 | 뜻 |
|---|---|---|
| role | system | 모델의 역할과 규칙. 화면에는 그리지 않는다 |
| role | user | 사용자의 말 |
| role | assistant | 모델의 답 |
| content | 글 | 말 |
최근 대화만 보내기
history = []
for i in range(1, 8): # 일곱 번 주고받은 대화
history += [{"role": "user", "content": f"질문 {i}"}, {"role": "assistant", "content": f"답 {i}"}]
def to_send(system, history, keep_turns=3):
"""시스템 지시 + 최근 몇 번의 주고받음만 보낸다. 10차시의 컨텍스트 줄이기"""
return [{"role": "system", "content": system}] + history[-keep_turns * 2:]
sent = to_send("당신은 사내 도우미입니다.", history)
print(len(history), "개 가운데", len(sent) - 1, "개를 보냄:", [m["content"] for m in sent])코드에 나오는 함수
| 코드 | 쉬운 뜻 |
|---|---|
history += [{...}, {...}] | 대화 기록 목록 끝에 사용자 말과 모델 답 두 개를 이어 붙인다 |
{"role": "user", "content": ...} | 메시지 하나의 꼴이다. role(역할)은 누가 말했는지, content(내용)는 말이다 |
keep_turns=3 | 인자를 주지 않으면 3 을 쓴다. turn(차례)은 한 번 묻고 한 번 답한 것이다 |
history[-keep_turns * 2:] | 목록의 뒤에서 6개(3번 × 2개)만 잘라 낸다. 음수 번호는 뒤에서부터 센다 |
[m["content"] for m in sent] | 보낼 메시지마다 말만 뽑아 목록으로 만든다 |
이 코드가 뜻하는 것
회의에 늦게 온 동료에게 앞의 이야기를 다 들려주지 않고 "방금 나온 세 가지만" 알려 주는 것과 같습니다. 회의의 목적은 맨 앞에 꼭 말해 줍니다. 여기서 회의의 목적이 시스템 지시, 방금 나온 세 가지가 최근 세 번의 주고받음입니다.
| 줄 | 하는 일 | 쉽게 말하면 |
|---|---|---|
for i in range(1, 8): | 일곱 번 주고받은 대화를 만든다 | 회의에서 이야기가 일곱 번 오갔다 |
def to_send(...) | 시스템 지시 하나에 최근 6개를 붙인다 | 목적 한마디 + 최근 이야기 셋 |
sent = to_send(...) | 보낼 목록을 만든다 | 늦은 동료에게 전할 말 |
print(len(history), ..., len(sent) - 1, ...) | 기록 수와 보낸 대화 수를 찍는다. 1 을 빼는 것은 시스템 지시를 세지 않으려는 것이다 | 몇 개 가운데 몇 개를 전했나 |
결과는 14개 가운데 6개를 보냈고, 보낸 것은 시스템 지시와 질문 5부터 답 7까지입니다. 질문 1부터 답 4까지는 모델이 모르게 되므로, 아주 앞의 내용을 물으면 모델이 답하지 못할 수 있습니다.
주의할 점
- 대화가 길어지면 모델에 보내는 글도 길어집니다. 최근 몇 번만 보내거나 요약합니다(10장).
- 모델의 답이 늦으면 사용자는 화면이 멈췄다고 생각합니다.
st.spinner로 기다리는 중임을 보이거나, 답을 한 글자씩 흘려 보여 줍니다(st.write_stream).