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

7장. 웹 UI 연동과 배포

위에서 아래로 다시 도는 실행 모델

키오스크에서 메뉴를 하나 누를 때마다 직원이 주문서를 버리고 맨 윗줄부터 새로 쓴다고 해 봅시다. 대신 직원은 손님이 지금까지 누른 단추를 모두 기억하고 있어서, 새로 쓴 주문서도 내용은 같습니다. 이상해 보이지만 이 방법에는 좋은 점이 있습니다. "무엇이 바뀌었는지" 따로 챙기지 않아도 되고, 주문서는 언제나 지금의 선택과 꼭 맞습니다. Streamlit 의 실행 모델이 이렇습니다. 손님이 위젯을 하나 만질 때마다 앱 스크립트 전체가 맨 위부터 다시 돕니다.

손님이 위젯을 만지면 스크립트가 맨 위부터 다시 돌고, 위젯은 손님이 고른 값을 돌려주고, 화면을 새로 그리는 네 걸음을 이은 흐름도

네 상자가 단추 한 번에 일어나는 일입니다. 손님이 위젯을 만지면(1), Streamlit 이 스크립트를 맨 윗줄부터 다시 실행하고(2), 위젯 함수는 손님이 고른 값을 돌려주며(3), 그 값으로 화면을 새로 그립니다(4). 다음에 손님이 또 만지면 다시 1번 상자부터입니다. 여기서 주문서를 새로 쓰는 일이 다시 실행(rerun), 직원이 기억하는 손님의 선택이 위젯의 값입니다. 비유와 다른 점은, 직원은 손님의 선택만 기억하고 우리가 스크립트에서 만든 보통 변수는 기억하지 않는다는 것입니다. 보통 변수는 다시 돌 때마다 처음 값으로 돌아갑니다.

다시 돌 때 남는 것과 사라지는 것

것다시 돌면예
스크립트의 보통 변수처음 값으로 돌아간다count = 0
위젯의 값Streamlit 이 기억했다가 다시 돌려준다입력칸에 적은 이름, 고른 여행지
st.button 의 값누른 그 실행에서만 True, 다음 실행은 False"인사하기" 단추
st.session_state남는다(손님마다 따로)대화 기록
캐시한 부품남는다(모든 손님이 함께)모델, 문서 색인

짧은 예시

보통 변수 count 를 1 올리는 앱입니다. 단추를 두 번 눌러 봅니다.

Python
from streamlit.testing.v1 import AppTest

APP = """
import streamlit as st

count = 0          # 보통 변수
count += 1
st.write(f"보통 변수 count: {count}")
if st.button("눌러 보기"):
    st.write("단추를 눌렀습니다.")
"""

at = AppTest.from_string(APP).run()
print("처음:", [m.value for m in at.markdown])
for k in range(2):
    # click() 으로 단추를 누르고 run() 으로 화면을 다시 그린다. 스크립트가 처음부터 다시 돈다
    at.button[0].click().run()
    print(f"{k + 1}번 누른 뒤:", [m.value for m in at.markdown])
출력
처음: ['보통 변수 count: 1']
1번 누른 뒤: ['보통 변수 count: 1', '단추를 눌렀습니다.']
2번 누른 뒤: ['보통 변수 count: 1', '단추를 눌렀습니다.']

두 번 눌렀는데도 count 는 계속 1 입니다. 누를 때마다 스크립트가 처음부터 돌아 count = 0 이 다시 실행되었기 때문입니다. "단추를 눌렀습니다"는 누른 실행에서만 나옵니다. 다음 절의 st.session_state 가 이 문제를 풉니다.

코드가 의미하는 것

이 코드는 단추를 누를 때마다 직원이 주문서를 버리고 맨 윗줄부터 새로 쓰는 모습을 세어 본 것입니다. 보통 변수를 하나 두고, 단추를 두 번 누르며, 그때마다 화면의 글을 꺼냅니다.

코드하는 일챗봇에서 뜻하는 것
count = 0, count += 1(앱 안)보통 변수를 0 으로 두고 1 올린다다시 실행마다 두 줄이 처음부터 돌아 값이 쌓이지 않습니다. 대화 기록을 보통 변수에 두면 이렇게 매번 사라집니다
if st.button("눌러 보기"):(앱 안)누른 실행에서만 안쪽을 돈다단추는 "지금 막 눌렸나"만 알려 줍니다. 한 번만 할 일을 이 안에 둡니다
at.button[0].click().run()단추를 누르고 스크립트를 다시 돌린다브라우저에서 손님이 단추를 누른 것과 같습니다. 누를 때마다 다시 실행이 한 번 일어납니다
[m.value for m in at.markdown]지금 화면의 글을 모두 꺼낸다다시 그린 뒤의 화면이 어떤지 코드로 읽습니다

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

  • 두 번 눌러도 `count` 는 1 입니다. 세 줄 모두 "보통 변수 count: 1" 입니다. 다시 돌 때마다 count = 0 이 먼저 실행되었습니다.
  • "단추를 눌렀습니다."는 누른 실행에만 나옵니다. 처음 실행의 목록에는 없고, 누른 뒤의 두 실행에만 들어 있습니다.

주의할 점

  • 다시 돌 때마다 맨 위의 코드가 모두 다시 실행됩니다. PDF 를 읽고 색인을 만드는 무거운 일을 그냥 적어 두면 단추를 누를 때마다 처음부터 다시 만듭니다. 이런 일은 "캐시" 절의 st.cache_resource 로 한 번만 하게 합니다.
  • 모델을 부르는 코드도 다시 돌 때마다 실행되면 그때마다 요금이 듭니다. 모델은 손님이 질문을 보낸 실행에서만 부르도록 if question := st.chat_input(...) 안에 둡니다.
← 이전 절7장 · Streamlit 기초와 화면 부품다음 절 →7장 · st.session_state 로 기억하기