RAG 챗봇 만들기 9 - Streamlit으로 웹 UI 만들기
Streamlit을 처음 사용했을 때 “이게 진짜 Python인가?” 싶었다. Flask로 웹을 만들 때는 템플릿, 라우팅, CSS를 모두 신경 써야 했는데, Streamlit은 몇 줄로 UI가 나왔다. 프로토타입을 빨리 만들 때 정말 유용하다.
오늘은 Streamlit으로 RAG 챗봇의 웹 UI를 만든다.
Streamlit이 뭔가?
Python 코드만으로 웹 앱을 만들 수 있는 라이브러리다. HTML, CSS, JavaScript를 몰라도 된다.
설치
pip install streamlit
첫 번째 앱
app.py 파일 만들기:
import streamlit as st
st.title("안녕하세요!")
st.write("Streamlit으로 만든 첫 번째 앱이에요.")
실행:
streamlit run app.py
실행 결과:
You can now view your Streamlit app in your browser.
Local URL: http://localhost:8501
브라우저에서 http://localhost:8501을 열면 앱을 확인할 수 있다.
기본 챗봇 UI 만들기
채팅 인터페이스
import streamlit as st
st.title("RAG 챗봇")
# 대화 기록 저장
if "messages" not in st.session_state:
st.session_state.messages = []
# 기존 대화 표시
for message in st.session_state.messages:
with st.chat_message(message["role"]):
st.markdown(message["content"])
# 사용자 입력
if prompt := st.chat_input("질문을 입력하세요"):
# 사용자 메시지 추가
st.session_state.messages.append({"role": "user", "content": prompt})
with st.chat_message("user"):
st.markdown(prompt)
# AI 응답 (일단 에코)
response = f"'{prompt}'에 대한 답변입니다."
st.session_state.messages.append({"role": "assistant", "content": response})
with st.chat_message("assistant"):
st.markdown(response)
RAG 연결하기
RAG 체인 통합
import streamlit as st
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
from langchain.chains import ConversationalRetrievalChain
from langchain.memory import ConversationBufferMemory
# 페이지 설정
st.set_page_config(page_title="RAG 챗봇", page_icon="🤖")
st.title("🤖 회사 규정 챗봇")
# RAG 체인 초기화 (캐싱)
@st.cache_resource
def get_chain():
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
embeddings = OpenAIEmbeddings()
vectorstore = Chroma(
persist_directory="./chroma_db",
embedding_function=embeddings
)
memory = ConversationBufferMemory(
memory_key="chat_history",
return_messages=True,
output_key="answer"
)
chain = ConversationalRetrievalChain.from_llm(
llm=llm,
retriever=vectorstore.as_retriever(search_kwargs={"k": 3}),
memory=memory,
return_source_documents=True
)
return chain
chain = get_chain()
# 세션 상태 초기화
if "messages" not in st.session_state:
st.session_state.messages = []
# 대화 기록 표시
for message in st.session_state.messages:
with st.chat_message(message["role"]):
st.markdown(message["content"])
# 사용자 입력
if prompt := st.chat_input("무엇이 궁금하세요?"):
# 사용자 메시지
st.session_state.messages.append({"role": "user", "content": prompt})
with st.chat_message("user"):
st.markdown(prompt)
# RAG 응답 생성
with st.chat_message("assistant"):
with st.spinner("답변 생성 중..."):
result = chain.invoke({"question": prompt})
response = result["answer"]
st.markdown(response)
# 출처 표시
with st.expander("📚 참고 문서"):
for i, doc in enumerate(result["source_documents"]):
st.markdown(f"**{i+1}.** {doc.page_content[:200]}...")
st.session_state.messages.append({"role": "assistant", "content": response})
사이드바 추가하기
파일 업로드나 설정 옵션을 사이드바에 넣을 수 있다.
import streamlit as st
# 사이드바
with st.sidebar:
st.header("설정")
# 모델 선택
model = st.selectbox(
"모델 선택",
["gpt-3.5-turbo", "gpt-4"]
)
# 검색 개수
k = st.slider("검색할 문서 수", 1, 10, 3)
# 대화 초기화 버튼
if st.button("대화 초기화"):
st.session_state.messages = []
st.rerun()
st.divider()
# 파일 업로드
uploaded_file = st.file_uploader("PDF 업로드", type="pdf")
if uploaded_file:
st.success(f"'{uploaded_file.name}' 업로드됨!")
전체 앱 코드
모든 기능을 합친 전체 코드다:
import streamlit as st
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
from langchain.chains import ConversationalRetrievalChain
from langchain.memory import ConversationBufferMemory
import os
# 페이지 설정
st.set_page_config(
page_title="RAG 챗봇",
page_icon="🤖",
layout="wide"
)
# 사이드바
with st.sidebar:
st.title("⚙️ 설정")
model = st.selectbox(
"모델",
["gpt-3.5-turbo", "gpt-4"],
index=0
)
k = st.slider("검색 문서 수", 1, 10, 3)
st.divider()
if st.button("🗑️ 대화 초기화", use_container_width=True):
st.session_state.messages = []
st.session_state.memory.clear()
st.rerun()
st.divider()
st.caption("RAG 챗봇 v1.0")
# 메인 영역
st.title("🤖 회사 규정 챗봇")
st.caption("회사 규정에 대해 무엇이든 물어보세요!")
# 초기화
@st.cache_resource
def init_vectorstore():
embeddings = OpenAIEmbeddings()
return Chroma(
persist_directory="./chroma_db",
embedding_function=embeddings
)
if "memory" not in st.session_state:
st.session_state.memory = ConversationBufferMemory(
memory_key="chat_history",
return_messages=True,
output_key="answer"
)
if "messages" not in st.session_state:
st.session_state.messages = []
vectorstore = init_vectorstore()
# RAG 체인
def get_response(question: str, model: str, k: int):
llm = ChatOpenAI(model=model, temperature=0)
chain = ConversationalRetrievalChain.from_llm(
llm=llm,
retriever=vectorstore.as_retriever(search_kwargs={"k": k}),
memory=st.session_state.memory,
return_source_documents=True
)
return chain.invoke({"question": question})
# 대화 표시
for message in st.session_state.messages:
with st.chat_message(message["role"]):
st.markdown(message["content"])
# 입력 처리
if prompt := st.chat_input("질문을 입력하세요..."):
st.session_state.messages.append({"role": "user", "content": prompt})
with st.chat_message("user"):
st.markdown(prompt)
with st.chat_message("assistant"):
with st.spinner("생각 중..."):
result = get_response(prompt, model, k)
st.markdown(result["answer"])
with st.expander("📚 참고 문서 보기"):
for i, doc in enumerate(result["source_documents"]):
st.markdown(f"**문서 {i+1}**")
st.text(doc.page_content[:300] + "...")
if doc.metadata:
st.caption(f"출처: {doc.metadata.get('source', '알 수 없음')}")
st.divider()
st.session_state.messages.append({
"role": "assistant",
"content": result["answer"]
})
실행하기
streamlit run app.py
배포하기
Streamlit Cloud (무료)
- GitHub에 코드 업로드
- share.streamlit.io 접속
- GitHub 레포 연결
- Secrets에 API 키 추가
# .streamlit/secrets.toml
OPENAI_API_KEY = "sk-..."
코드에서 불러오기:
import streamlit as st
import os
os.environ["OPENAI_API_KEY"] = st.secrets["OPENAI_API_KEY"]
운영자 실전 노트
실제 프로젝트 진행하며 겪은 문제
- 세션 상태 초기화 안 됨: 페이지 새로고침 시
st.session_state사라짐 → 영구 저장 필요한 데이터는 DB나 파일로 관리 - 캐싱 오류:
@st.cache_resource없이 RAG 체인 초기화하니 입력마다 재생성 → 캐싱으로 속도 10배 개선 - 배포 시 메모리 부족: Streamlit Cloud 무료 플랜은 1GB 제한 → 벡터 DB를 외부 서비스로 분리
- 파일 업로드 후 처리 느림: PDF 업로드 후 벡터화에 30초 소요 → 백그라운드 처리나 진행 표시 필수
이 경험을 통해 알게 된 점
- 세션 상태 관리가 핵심: 대화 기록과 RAG 체인을
st.session_state에 저장해야 매끄러운 UX - 캐싱 전략 필수:
@st.cache_resource로 무거운 객체 캐싱하면 성능 대폭 향상 - 배포 환경 고려: 로컬에서는 괜찮아도 클라우드 제약 사항 확인 필요
마무리
Streamlit은 빠르게 프로토타입을 만들기에 최적이다. 아이디어가 있으면 Streamlit으로 먼저 구현해보고, 검증이 되면 그때 React로 본격 개발하는 패턴이 효율적이다. 시간을 아끼려면 처음부터 완벽하게 만들려 하지 말고, 빨리 만들어서 피드백을 받는 것이 낫다.
다음 마지막 편에서는 지금까지 만든 것을 정리하고, 나만의 문서로 RAG 챗봇을 완성한다.
RAG 챗봇 만들기 시리즈:
← 블로그 목록으로