15차시. 기본 RAG 앱 만들기
8. 실습 ⑤ — Streamlit 웹 화면 (선택)
출처: 원본 PDF 15차시_기본RAG앱만들기.pdf 16쪽 · 쪽 그림 16쪽
터미널이 아쉽다면 웹 화면도 20줄이면 만들 수 있습니다.
pip install streamlit📝 web.py
import streamlit as st
from rag import ask
st.set_page_config(page_title="문서 Q&A", page_icon="■")
st.title("■ 문서 기반 질의응답")
if "history" not in st.session_state:
st.session_state.history = []
q = st.chat_input("궁금한 것을 물어보세요")
if q:
with st.spinner("자료를 찾는 중..."):
result = ask(q)
st.session_state.history.append((q, result))
for question, result in st.session_state.history:
with st.chat_message("user"):
st.write(question)
with st.chat_message("assistant"):
st.write(result["answer"])
if result["sources"]:
# 중복 없이, 순서대로 페이지 번호만 모으기
pages = []
for s in result["sources"]:
if s["page"] not in pages:
pages.append(s["page"])
pages.sort()
page_texts = []
for p in pages:
page_texts.append(str(p))
page_text = ", ".join(page_texts)
st.caption(f"※ 참고: {result['sources'][0]['file']} "
f"{page_text}페이지")
실행 방법
──────────────────────────────
streamlit run web.py
→ 브라우저가 자동으로 열립니다
→ 채팅 형태의 화면이 나타납니다💡 app.py와 web.py가 따로인 이유
둘 다 rag.ask()를 부르기만 합니다.
검색·생성 로직은 하나도 안 들어 있습니다.
→ 화면을 몇 개를 만들든 rag.py는 그대로입니다
→ 나중에 API 서버로 만들어도 rag.py는 그대로입니다
이것이 관심사 분리의 실질적 이득입니다.
실습 코드 · rag_app/web.py
실습 코드: 실습/src/rag_app/web.py · 위의 원본 PDF 15차시 16쪽 「8️⃣ 실습 ⑤ — Streamlit 웹 화면 (선택)」과 이어집니다.
# ==============================================================
# 15차시. PPT 15차시_기본RAG앱만들기. 코드 폴더는 src/rag_app
# PPT: 15차시 문서 질의응답
# 역할: Streamlit 채팅 화면. 질문을 rag.ask에 넘기고 말풍선과 참고 페이지를 쌓습니다.
# 읽는 키: st.session_state.history, ask()의 answer, sources, cited
# 쓰는 키: 브라우저 화면. history에 (질문, 결과)를 추가
# 그래프:
# chat_input
# |
# v
# ask(question) --> history
# |
# v
# 사용자 말풍선 / 답변 말풍선 / 참고 페이지 / 인용 경고
# --------------------------------------------------------------
# session_state.history가 없으면 빈 리스트로 시작합니다. 새로고침 전까지 대화가 남습니다.
# 출처 페이지 번호는 중복을 빼고 정렬해 '참고: 파일명 N페이지'로 보여 줍니다.
# 파일 맨 아래 주석의 실행 명령은 streamlit run web.py 입니다.
# 서버를 띄우지 않았습니다.
# ==============================================================
# streamlit은 파이썬 코드만으로 웹 화면을 만드는 라이브러리입니다. streamlit run web.py 로 실행합니다.
import streamlit as st
# 검색과 생성은 rag.py의 ask 하나에 맡깁니다. 이 파일은 화면만 그립니다.
from rag import ask
# 브라우저 탭에 보일 제목과 아이콘을 정합니다.
st.set_page_config(
page_title="문서 Q&A",
page_icon="■"
)
# 화면 맨 위의 큰 제목입니다.
st.title("■ 문서 기반 질의응답")
# Streamlit은 입력이 있을 때마다 파일을 처음부터 다시 실행합니다. 대화 기록은 session_state에 두어야 사라지지 않습니다.
if "history" not in st.session_state:
st.session_state.history = []
# 화면 아래 채팅 입력칸입니다. 아직 입력이 없으면 None입니다.
question = st.chat_input(
"궁금한 것을 물어보세요"
)
# 새 질문이 들어온 실행에서만 ask를 부릅니다.
if question:
# 답변을 만드는 동안 로딩 메시지를 표시합니다.
with st.spinner("자료를 찾는 중..."):
# RAG 시스템에 질문을 전달합니다.
result = ask(question)
# 질문과 결과를 대화 기록에 저장합니다.
st.session_state.history.append(
(question, result)
)
# 지금까지 쌓인 대화를 처음부터 다시 그립니다.
for question, result in st.session_state.history:
# 사용자 말풍선을 먼저, 답변 말풍선을 다음에 그립니다.
with st.chat_message("user"):
st.write(question)
with st.chat_message("assistant"):
st.write(result["answer"])
# 출처가 있을 때만 참고 페이지를 붙입니다. 같은 페이지는 한 번만 넣습니다.
if result["sources"]:
pages = []
for source in result["sources"]:
page = source["page"]
if page not in pages:
pages.append(page)
# 페이지 번호를 작은 수부터 정렬합니다.
pages.sort()
# 숫자 페이지를 문자열로 변환합니다.
page_texts = []
for page in pages:
page_texts.append(str(page))
# 1, 2, 3 형태의 문자열로 만듭니다.
page_text = ", ".join(page_texts)
# 첫 번째 검색 결과에서 파일 이름을 가져옵니다.
filename = result["sources"][0]["file"]
# 참고 문서와 페이지 정보를 출력합니다.
st.caption(
f"※ 참고: {filename} "
f"{page_text}페이지"
)
# streamlit run web.py실행 결과 샘플 (교안 기록. API 키가 필요한 코드는 키 없이 맞춘 예시이고, 모델의 답은 실행할 때마다 달라집니다)
===== 실행 결과 샘플 =====
예시 Streamlit 화면 (API 키 필요, 실행하지 않음)
실행 명령: streamlit run web.py
화면 제목: 문서 기반 질의응답
입력 안내: 궁금한 것을 물어보세요
검색 중에는 '자료를 찾는 중...' 이 표시됩니다.
인용 번호가 없으면 '인용 표시가 확인되지 않았습니다.' 가 붙습니다.
API 키와 FAISS 인덱스가 있어야 답이 나옵니다. 실행하지 않았습니다.코드 해설
Streamlit 채팅 화면. 질문을 rag.ask에 넘기고 말풍선과 참고 페이지를 쌓습니다.
코드 앞에서 고정해 두는 말입니다.
- 읽는 키: st.session_state.history, ask()의 answer, sources, cited
- 쓰는 키: 브라우저 화면. history에 (질문, 결과)를 추가
- 그래프:
- chat_input
- |
- v
- ask(question) --> history
- |
함수로 나누지 않고 위에서 아래로 실행됩니다.
with st.spinner("자료를 찾는 중..."):— 답변을 만드는 동안 로딩 메시지를 표시합니다.result = ask(question)— RAG 시스템에 질문을 전달합니다.st.session_state.history.append(— 질문과 결과를 대화 기록에 저장합니다.page_texts = []— 숫자 페이지를 문자열로 변환합니다.page_text = ", ".join(page_texts)— 1, 2, 3 형태의 문자열로 만듭니다.filename = result["sources"][0]["file"]— 첫 번째 검색 결과에서 파일 이름을 가져옵니다.st.caption(— 참고 문서와 페이지 정보를 출력합니다.
파일 맨 아래 실행 결과 샘플 주석은 실행되지 않습니다. 화면 출력은 다음 절에 다시 적어 두었습니다.