데이터 과학자를 위한 웹 개발 도구 Streamlit - RAG를 활용한 웹 검색 챗봇 데모페이지 시연
오늘은 그동안 Streamlit 관련 포스팅 한 내용을 토대로 간단한 데모페이지를 만들어 보겠습니다.
데모페이지는 Streamlit를 통해 인터렉티브하게 질의응답을 받을 수 있는 웹 검색 챗봇을 구현해 보았습니다.
페이징 처리

먼저 파일 위치 구성은 Streamlit 애플리케이션을 실행할 메인 파일과 같은 경로에 config.py와 pages에 페이징 처리할 파일을 두었습니다.
home.py은 페이지에 접속하면 기본으로 설정될 메인 페이지이고 demo_page.py는 오늘 알아볼 웹 검색 챗봇 데모 페이지입니다.
main.py
import streamlit as st
st.set_page_config(
page_title="데모 페이지",
page_icon="🤖",
)
# 페이지 정의
p1 = st.Page("pages/home.py", title="홈 화면", icon="📄", default=True)
p2 = st.Page("pages/demo_page.py", title="데모 페이지", icon="📄")
menu = {
"🏠 메인" : [p1],
"🖥️ 데모페이지" : [p2],
}
pg = st.navigation(
menu,
position="sidebar",
expanded=True,
)
pg.run()
st.set_page_config로 페이지 타이틀과 아이콘을 지정합니다.
st.Page로 home.py와 demo_page.py를 각각 페이지 변수로 지정하고 st.navigation으로 페이지를 구성합니다.
home.py
import streamlit as st
st.image("/Users/seonghwanpark/Desktop/데스크탑/shpark/Development/Streamlit/src/image.png", width=300)
st.subheader("데모 페이지")
st.markdown(
"""
###
### 메뉴
- 메인 홈화면
- 데모 페이지
"""
)

main.py가 있는 경로에서 streamlit run main.py으로 메인 파일을 실행하면 main.py에 작성된 대로 페이징 처리가 된 화면이 띄워집니다.
페이징 처리에서 home.py에 설정한 화면을 기본 페이지로 지정했기 때문에 url에 접근하면 해당 페이지를 가장 먼저 확인할 수 있습니다.
streamlit을 실행할 때, nohup같은 프로그램으로 계속 플로팅할 수 있고, --server.port 옵션으로 원하는 포트를 지정할 수도 있습니다. 예) nohup streamlit run main.py --server.port=8925
demo_page.py
import os, sys
from PIL import Image
import streamlit as st
from langchain.chat_models import ChatOpenAI
from langchain.utilities import GoogleSearchAPIWrapper
from langchain.agents.initialize import initialize_agent
from langchain_core.tools import Tool
from langchain.agents.agent_types import AgentType
from langchain.agents import AgentExecutor
current_dir = os.path.dirname(os.path.abspath(__file__))
parent_dir = os.path.dirname(current_dir)
sys.path.append(parent_dir)
import config as cfg
@st.cache_resource()
def get_model():
# del chat state
for key in st.session_state.keys():
del st.session_state[key]
model = ChatOpenAI(**cfg.gpt)
return model
def main():
# cache_resource
model = get_model()
# image
image = Image.open("chat-bot.png")
col1, col2, col3 = st.columns([1, 2, 1]) # col2가 가운데
with col2:
st.image(
image,
caption=None,
width=200, # 원하는 크기 지정
use_column_width=None,
clamp=False,
channels="RGB",
output_format="auto",
)
# align
mystyle = """
<style>
p {
text-align: justify;
}
</style>
"""
st.markdown(mystyle, unsafe_allow_html=True)
con1 = st.container()
con1.caption(f"🤖 챗봇")
con2 = st.container()
con2.info(f"""감사합니다. 챗봇입니다. 무엇을 도와드릴까요?""")
def ask_flow(user_input):
search = GoogleSearchAPIWrapper(
google_api_key=cfg.google_api_key,
google_cse_id=cfg.google_cse_id,
k=3
)
tools = [
Tool(
name="Intermediate Answer",
func=search.run,
description="검색 중간 결과를 반환하는 도구"
)
]
# Option A: initialize_agent 사용
agent = initialize_agent(
tools=tools,
llm=model,
agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
verbose=True,
handle_parsing_errors=True,
)
response = agent.run({"input": user_input})
print("Result:", response)
print("-" * 100)
return response
st.header("")
st.header("")
# Initialize chat history
if "messages" not in st.session_state:
st.session_state.messages = []
# Display chat messages from history on app rerun
for message in st.session_state.messages:
with st.chat_message(message["role"]):
st.markdown(message["content"])
# React to user input
if prompt := st.chat_input("말씀해 주세요."):
# Display user message in chat message container
with st.chat_message("user"):
st.markdown(prompt)
# Add user message to chat history
st.session_state.messages.append({"role": "user", "content": prompt})
# Request chat completion
response = ask_flow(prompt)
# Display assistant response in chat message container
with st.chat_message("assistant"):
st.markdown(response)
# Add assistant response to chat history
st.session_state.messages.append({"role": "assistant", "content": response})
if __name__ == "__main__":
main()

위 데모페이지 코드의 역할을 요약하자면 Langchain을 활용하여 OpenAI gpt4.1 api와 Google Custom Search API를 통해 실시간 웹 검색하는 챗봇입니다. Streamlit과 LangChain을 사용할 때, LLM(ChatOpenAI) 모델을 초기화하고 세션 상태를 정리하는 역할을 합니다.
위에서부터 같이 한 번 살펴보겠습니다.
get_model()
@st.cache_resource() 캐시 데코레이터를 활용하여 함수 실행 결과인 model을 한 번만 실행해서 저장합니다.
이후 재실행 시에도 저장된 model을 재사용하므로, 성능과 응답 속도 개선에 도움 됩니다.
ask_flow()


- GoogleSearchAPIWrapper
- LangChain이 제공하는 구글 검색 래퍼를 통해 웹 검색 기능을 사용.
- cfg에 있는 키(API KEY, CSE ID)를 이용하여 실제 Google Custom Search API를 호출.
- k=3은 검색 결과 중 상위 3개만 사용한다는 의미입니다.
- Tool
- LangChain Tool은 LLM이 사용할 수 있는 도구 정의.
- "Intermediate Answer"라는 이름으로 검색 기능을 툴 등록.
- initialize_agent
- 도구와 LLM을 연결하는 추론 에이전트를 생성합니다.
- AgentType.ZERO_SHOT_REACT_DESCRIPTION은 질문을 분석해서 툴을 사용할지 말지 LLM이 직접 판단하는 방식.
- handle_parsing_errors=True는 LLM이 예상치 못한 출력을 내더라도 다시 시도하게 해 줍니다.
st.session_state
- Streamlit은 웹페이지가 새로 고쳐질 때마다 스크립트를 위에서부터 다시 실행합니다.
- st.session_state는 사용자 세션 상태(예: 대화 기록, 필터 값 등)를 저장하는 공간인데,
이 줄은 기존에 남아 있는 세션 상태 데이터를 모두 초기화합니다. - for message in st.session_state.messages: with st.chat_message(message["role"]): st.markdown(message["content"]) 이렇게 처리되면 아래 입력받은 값을 session_state의 messages 키에 담을 수 있고 그 값을 출력하여 이전 대화 목적을 출력하는 형태로 보여줄 수 있습니다.
- 질문과 답변은 각각 st.session_state messages에 "role":"user", "role":"assistant"로 append 합니다.
✅ 정리
이번 데모 페이지를 통해 다음과 같은 내용을 함께 살펴보았습니다.
- LangChain과 OpenAI 기반 LLM을 어떻게 연동하는지
- Google Search API를 활용해 외부 검색 기능을 접목하는 방법
- Streamlit으로 대화형 챗 UI를 구성하는 방식
Streamlit의 상호작용성과 세션 상태 관리 기능을 적절히 활용하면, 챗봇 프로토타입을 빠르게 구축하고 실험할 수 있습니다.
프론트엔드 개발이 여의치 않거나, POC 단계에서 빠르게 기능을 검증해야 할 때 특히 유용하게 활용될 수 있습니다.