LangChain 챗봇7장 · 웹 UI 연동과 배포6 / 22← 이전목차다음 →MetaContents

7장. 웹 UI 연동과 배포

채팅 화면: chat_message 와 chat_input

메신저 앱을 열면 위에는 주고받은 말풍선이 차례로 쌓여 있고, 아래에는 글을 적는 칸이 붙어 있습니다. 내가 보낸 말은 한쪽에, 상대의 말은 다른 쪽에 아이콘과 함께 보입니다. Streamlit 은 이 꼴을 함수 두 개로 만듭니다. 말풍선은 st.chat_message, 아래의 입력칸은 st.chat_input 입니다.

구름다리여행 여행 비서 채팅 화면을 본뜬 그림으로 제목, 사용자와 비서의 말풍선, 아래쪽 입력창, 왼쪽 사이드바에 번호를 붙여 설명한 그림

화면을 본뜬 그림입니다. 실제 캡처가 아니고, 실제 화면의 색과 아이콘은 판마다 조금 다릅니다. 번호는 화면 바깥에 붙였고 오른쪽 목록과 짝을 이룹니다. 1번이 제목, 2번이 손님의 말풍선(st.chat_message("user")), 3번이 비서의 말풍선(st.chat_message("assistant")), 4번이 화면 맨 아래에 붙는 입력창(st.chat_input), 5번이 설정을 두는 사이드바입니다. 여기서 메신저의 말풍선 목록이 st.session_state 에 쌓인 대화 기록, 글 적는 칸이 st.chat_input 입니다. 비유와 다른 점은, 메신저는 지난 말풍선이 화면에 그대로 남아 있지만 Streamlit 은 다시 돌 때마다 화면을 새로 그리므로 지난 말풍선을 기록에서 꺼내 우리가 다시 그려야 한다는 것입니다.

채팅 화면의 두 함수

쓰는 모양하는 일알아 둘 것
with st.chat_message("user"):손님의 말풍선을 연다. 이 안에서 st.markdown(…) 으로 글을 쓴다이름이 "user", "human" 이면 사람 아이콘
with st.chat_message("assistant"):비서의 말풍선을 연다"assistant", "ai" 면 로봇 아이콘
question = st.chat_input("질문을 적어 주세요")화면 아래 입력창. 괄호 안의 글은 빈칸에 흐리게 보이는 안내 글손님이 Enter 를 친 실행에서만 글을, 나머지 실행에서는 None 을 돌려준다
if question := st.chat_input(…):질문이 들어온 실행에서만 안쪽을 돈다:= 는 값을 변수에 넣으면서 바로 견주는 연산자다

짧은 예시

받은 말을 따라 말하는 작은 채팅 화면입니다. 질문 두 개를 넣어 봅니다.

Python
from streamlit.testing.v1 import AppTest

APP = """
import streamlit as st

if "messages" not in st.session_state:
    st.session_state.messages = []

# 1) 지금까지의 대화를 말풍선으로 다시 그린다
for m in st.session_state.messages:
    with st.chat_message(m["role"]):
        st.markdown(m["content"])

# 2) 새 질문이 들어온 실행에서만 아래가 돈다
if question := st.chat_input("질문을 적어 주세요"):
    st.session_state.messages.append({"role": "user", "content": question})
    with st.chat_message("user"):
        st.markdown(question)
    reply = f"'{question}' 라고 하셨군요."     # 아직은 따라 말하기 봇
    st.session_state.messages.append({"role": "assistant", "content": reply})
    with st.chat_message("assistant"):
        st.markdown(reply)
"""

at = AppTest.from_string(APP).run()
for q in ["제주도 가요", "2박 3일이요"]:
    # set_value: 입력창에 글을 넣고 Enter 를 누른 것과 같다
    at.chat_input[0].set_value(q).run()

for m in at.chat_message:
    print(m.name, "|", m.markdown[0].value)
print("메모장의 기록:", len(at.session_state["messages"]), "개")
출력
user | 제주도 가요
assistant | '제주도 가요' 라고 하셨군요.
user | 2박 3일이요
assistant | '2박 3일이요' 라고 하셨군요.
메모장의 기록: 4 개

둘째 질문을 넣은 실행에서 화면에는 말풍선 네 개가 보입니다. 앞의 두 개는 1)의 반복문이 기록에서 꺼내 다시 그린 것이고, 뒤의 두 개는 2)에서 새로 그린 것입니다. 기록을 {"role": …, "content": …} 사전으로 담은 것은 1장에서 본 메시지의 꼴 그대로입니다.

코드가 의미하는 것

이 코드는 메신저 화면의 두 동작, 지난 말풍선을 기록에서 꺼내 다시 그리기와 새 질문이 오면 말풍선 한 쌍을 더하기를 흉내 낸 것입니다. 답은 아직 받은 말을 따라 말할 뿐입니다.

코드하는 일챗봇에서 뜻하는 것
for m in st.session_state.messages: 와 with st.chat_message(m["role"]):기록을 하나씩 말풍선으로 그린다다시 실행마다 화면이 비워지므로, 지난 대화를 보이게 하는 것은 이 반복문입니다
if question := st.chat_input(…):질문이 들어온 실행에서만 안쪽을 돈다손님이 Enter 를 친 실행에서만 답을 만듭니다. 나중에 모델을 부를 자리도 이 안입니다
messages.append({"role": "user", "content": question})질문을 사전 하나로 기록에 넣는다1장의 메시지 꼴 그대로 담아 두어, 다음 절에서 체인에 바로 넘깁니다
reply = f"'{question}' 라고 하셨군요."답을 만든다챗봇 로직(주방)이 들어갈 자리입니다. 지금은 따라 말하기로 채워 두었습니다
at.chat_input[0].set_value(q).run()입력창에 글을 넣고 Enter 를 친 것처럼 다시 실행한다손님 한 사람이 질문 두 개를 차례로 보낸 시험입니다

결과에서 읽을 것은 두 가지입니다.

  • 말풍선은 user 와 assistant 가 번갈아 네 개입니다. 앞의 두 개는 기록에서 다시 그린 것, 뒤의 두 개는 둘째 질문에 새로 그린 것입니다.
  • 메모장의 기록도 4 개입니다. 화면의 말풍선 수와 기록 수가 같습니다. 화면은 기록을 그대로 옮겨 그린 것이라는 뜻입니다.

주의할 점

  • 1)의 다시 그리기를 빼면 새 질문을 보낼 때마다 앞의 말풍선이 사라집니다. 화면은 다시 돌 때마다 비워지기 때문입니다.
  • 기록에 넣는 역할 이름은 "user", "assistant" 로 맞춰 둡니다. 이 꼴은 LangChain 의 MessagesPlaceholder 에도 그대로 넣을 수 있어서 다음 절에서 체인에 바로 넘깁니다.
  • st.chat_input 은 한 화면에 하나만 둡니다. 사이드바 안이나 다른 부품 안에 넣지 않고 맨 바깥에 두면 화면 맨 아래에 붙습니다.
← 이전 절7장 · st.session_state 로 기억하기다음 절 →7장 · 챗봇 로직과 웹 화면 잇기