기존 Streamlit
코드에서는 페이지가 로드될 때마다 위치 정보(latitude, longitude)를 자동으로 가져오게 되며, 이 과정에서 불필요한 데이터 입력이 발생할 가능성이 있었고, 다른 장소의 대피소 정보나 다른 질문을 해도 불러온 위치 정보를 바탕으로 대피소 정보를 제공하는 문제가 있었다.
예를 들어, 특정 지역 근처의 대피소를 질문했을 때, 코드가 항상 현재 위치 정보를 참조하여 다른 응답이 출력되는 문제같은...
# 기존 코드 예제
location = streamlit_geolocation()
if location:
latitude = location.get("latitude")
longitude = location.get("longitude")
if latitude and longitude:
st.write("현재 위치를 확인했습니다.")
else:
st.write("위치 정보를 가져오는 데 실패했습니다.")
else:
st.write("위치 정보를 가져올 수 없습니다.")
latitude, longitude = None, None
이렇게 (지금은 또 수정되었지만...) 끝에 st.rerun()으로 세션 초기화 할 때마다 location 정보를 불러와서 llm.py의 파일과 상호작용을 했기에 불필요한 대답의 빈도가 잦았다.
# 수정된 코드
if st.button("가까운 대피소 검색"):
if location:
latitude = location.get("latitude")
longitude = location.get("longitude")
if latitude and longitude:
st.write("현재 위치를 확인했습니다.")
else:
st.write("위치 정보를 가져오는 데 실패했습니다.")
else:
st.write("위치 정보를 가져올 수 없습니다.")
이렇게 버튼식으로 묶어서 버튼을 누를때만, 위치정보를 제공받도록 수정했다. 이렇게 해도 처음 위치정보가 AI 모델에 입력되고 그 입력된 정보를 바탕으로 계속 대답을 작성하기에 답변의 질에는 이상이 없었다.
app.py
의 전체코드를 첨부한다.
import streamlit as st
from llm import initialize, get_response
from streamlit_geolocation import streamlit_geolocation
st.title("🚨 비상사태 대처 매뉴얼 챗봇 🚨")
st.write("비상사태에서 안전한 대처를 도와드리는 챗봇입니다. 질문이 있다면 자유롭게 입력하세요.")
# 위치 정보 버튼 출력
location = streamlit_geolocation()
# 위치 정보 출력
if location and location.get("latitude") and location.get("longitude"):
st.success(f"현재 위치를 확인했습니다.")
else:
st.warning("위치 정보를 가져오지 못했습니다. 버튼을 눌러주세요.")
# 메시지 상태 초기화
if "messages" not in st.session_state:
st.session_state.messages = []
# 초기 메시지 설정
initial_message = initialize()
st.session_state.messages.append({"role": "ai", "content": initial_message})
# 이전 메시지 출력
for message in st.session_state.messages:
if message["role"] == "ai":
st.chat_message("assistant").write(message["content"])
elif message["role"] == "function":
st.chat_message("function").write(message["content"])
else:
st.chat_message("user").write(message["content"])
# 위치 기반 대피소 검색 버튼
if st.button("가까운 대피소 검색"):
# 위치 정보 확인
if location:
latitude = location.get("latitude")
longitude = location.get("longitude")
# latitude와 longitude 값이 존재하는지 확인
if latitude and longitude:
# 명시적인 요청 메시지 추가
user_message = "현재 위치에서 가까운 대피소 정보를 알려줘."
st.session_state.messages.append({"role": "user", "content": user_message})
# 사용자 메시지 즉시 표시
st.chat_message("user").write(user_message)
# 응답 자리 확보 및 로딩 메시지 표시
with st.chat_message("assistant"):
message_placeholder = st.empty()
message_placeholder.write("💬답변을 생성 중입니다...")
# 모델에 위치 정보 전달하여 응답 생성
response = get_response(user_input=user_message, user_location={"latitude": latitude, "longitude": longitude})
# 챗봇 응답 업데이트
message_placeholder.markdown(response["response"])
st.session_state.messages.append({"role": "ai", "content": response["response"]})
else:
st.write("위치 정보를 가져오는 데 실패했습니다.")
else:
st.write("위치 정보를 가져올 수 없습니다. 브라우저에서 위치 정보 접근을 허용해주세요.")
# 사용자 입력 처리
if user_input := st.chat_input("질문을 입력하세요"):
# 사용자 메시지 추가 및 즉시 표시
st.session_state.messages.append({"role": "user", "content": user_input})
st.chat_message("user").write(user_input)
# 응답 자리 확보 및 로딩 메시지 표시
with st.chat_message("assistant"):
message_placeholder = st.empty()
message_placeholder.write("💬답변을 생성 중입니다...")
# 응답 생성
response = get_response(user_input, user_location=location)
# 챗봇 응답 업데이트
message_placeholder.markdown(response["response"])
st.session_state.messages.append({"role": "ai", "content": response["response"]})
streamlit_geolocation
이 라이브러리는 버튼식으로 제공되는 위치정보를 불러오는 함수다 이 아이콘의 모양은
이런식인데 바꾸려고 시도를 여러번 했지만, 이 아이콘을 직접 변경하는 것은 지금은 어렵다는 것을 찾았다.
파일 내에서 아이콘 모양을 변경 못하는거야?
app.py 내에서 streamlit-geolocation 컴포넌트가 생성하는 버튼의 동작을 직접 변경하는 것은 어렵습니다. 이유는 이 컴포넌트가 Streamlit의 기본 위젯이 아니라, 별도의 React 기반 JavaScript 코드로 구현된 사용자 정의 컴포넌트이기 때문입니다. 따라서 app.py에서는 컴포넌트를 호출하는 방법만 제어할 수 있고, 내부 동작이나 스타일링은 해당 컴포넌트의 소스 코드를 수정해야 합니다
기존 방식에서는 상태 변화가 있을 때마다 st.rerun()
을 호출하여 Streamlit 애플리케이션을 강제로 새로고침하는 방법을 사용했다. streamlit에 대한 이해가 낮았다.
전체가 다시 로드되니까 추가적은 상태 저장 관리를 해야했고, 불필요한 코드들이 자리했던거 같다.
st.rerun()은 애플리케이션의 모든 컴포넌트를 재렌더링합니다. 이는 특히 데이터 처리나 API 호출이 많은 애플리케이션에서 불필요한 리소스 소모로 이어질 수 있습니다.
수정된 방식
Streamlit
의 st.session_state를 사용해서 효율적인 동작을 구현했다. 아래는 기존 방식과 수정한 방식에 대한 차이다.
항목 | 기존 방식 (st.rerun() ) | 수정된 방식 (이벤트 기반) |
---|---|---|
상태 관리 | 매번 초기화. 추가 복원 로직 필요. | st.session_state 로 상태 유지 가능. |
UI 업데이트 | 전체 애플리케이션 재렌더링. 깜박임 발생. | 변경된 부분만 업데이트. 부드러운 사용자 경험 제공. |
사용자 경험 | 응답 속도가 느리고, 화면이 새로고침됨. | 즉각적인 응답 표시 및 자연스러운 인터페이스. |
리소스 효율성 | 불필요한 컴포넌트 재렌더링. | 필요한 부분만 렌더링. 리소스 사용 최적화. |
코드 복잡도 | 상태 복원 코드 필요. | Streamlit 기본 상태 관리 기능으로 간결한 코드. |
rerun()
을 남용했던걸 반성하고 앞으로 rerun()작동에 주의를 기울이자;