RAG2026-5일15차시 · 기본 RAG 앱 만들기11 / 19← 이전목차다음 →MetaContents

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 웹 화면 (선택)」과 이어집니다.

Python
# ==============================================================
# 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( — 참고 문서와 페이지 정보를 출력합니다.

파일 맨 아래 실행 결과 샘플 주석은 실행되지 않습니다. 화면 출력은 다음 절에 다시 적어 두었습니다.

← 이전 절15차시 · 7. 실습 ④ — app.py (터미널 버전)다음 절 →15차시 · 9. 자주 겪는 문제