15차시. 기본 RAG 앱 만들기
실습 1 — 기본 RAG 앱 만들기 실습 따라 하기
관련 수업 자료: 15차시 「3. 실습 ① — config.py」(6쪽), 15차시 「4. 실습 ② — indexer.py」(8~9쪽), 15차시 「2. 목표 구조 — 4개 모듈」(5쪽), 15차시 「5. 실습 ③ — rag.py (핵심)」(10쪽), 15차시 「7. 실습 ④ — app.py (터미널 버전)」(14쪽), 15차시 「8. 실습 ⑤ — Streamlit 웹 화면 (선택)」(16쪽) · 실습 코드 rag_app/config.py, rag_app/indexer.py, rag_app/prompts.py, rag_app/rag.py, rag_app/app.py, rag_app/web.py
이 실습은 교안 해설(15_기본RAG앱만들기.md)의 실습 순서를 옮긴 것입니다. OpenAI 임베딩이나 채팅 모델을 부르는 파일은 .env 에 API 키가 있어야 돌아갑니다. 키가 없으면 실행 결과 샘플로 출력 모양을 확인합니다.
수업 자료와 이어지는 곳
| 실습 코드 | 원본 PDF | 이어지는 내용 |
|---|---|---|
rag_app/config.py | 6쪽 | 15차시 「3. 실습 ① — config.py」 아래에 실었습니다 |
rag_app/indexer.py | 8~9쪽 | 15차시 「4. 실습 ② — indexer.py」 아래에 실었습니다 |
rag_app/prompts.py | 5쪽 | 15차시 「2. 목표 구조 — 4개 모듈」 아래에 실었습니다 |
rag_app/rag.py | 10쪽 | 15차시 「5. 실습 ③ — rag.py (핵심)」 아래에 실었습니다 |
rag_app/app.py | 14쪽 | 15차시 「7. 실습 ④ — app.py (터미널 버전)」 아래에 실었습니다 |
rag_app/web.py | 16쪽 | 15차시 「8. 실습 ⑤ — Streamlit 웹 화면 (선택)」 아래에 실었습니다 |
실행 흐름
config.py → indexer.py
config.py → rag.py
indexer.py → rag.py
rag.py → app.py
rag.py → web.py실습 순서
실습/src/rag_app에서 키가 있을 때 python app.py로 질문하고 q로 종료합니다. 웹은 streamlit run web.py입니다. 빈 문자열과 문서에 없는 질문을 넣어 프로세스가 죽지 않는지 봅니다.
스스로 확인
- 설정을 바꿀 때 여는 파일이 config 하나인가?
- ask가 답만 돌려주지 않고 출처도 돌려주는가?
- 화면을 콘솔에서 웹으로 바꿔도 rag.py를 그대로 쓰는가?
코드 설명
- 실습 코드
rag_app/config.py는 CHUNK_SIZE가 500, MIN_SCORE가 -0.01입니다. 수업 자료의 300, 0.47과 다르고, 줄 끝 주석("500→300")과도 어긋납니다. 실제로 쓰이는 것은 주석이 아니라 값입니다. 튜닝 결과를 옮길 때 값과 주석을 함께 고치십시오. indexer.py의(index_path / "index.faiss").is_file() and (index_path / "index.pkl").is_file()는 인덱스 파일 두 개가 모두 있을 때만 읽기로 갑니다. 하나라도 지워졌으면 PDF부터 다시 만듭니다. 문서를 바꿨다면get_store(rebuild=True)로 새로 만들어야 합니다.rag.py는 import되는 순간load_dotenv(),get_store(),ChatOpenAI(...)가 실행됩니다. 그래서 앱을 켤 때 한 번 느리고, 그 뒤 질문은 빠릅니다. get_store를 ask 안에 넣으면 질문마다 이 비용을 다시 냅니다.- ask의
ok는 "앱이 정상 동작했는가"입니다. 검색 결과가 0건이면 안내 문장을 돌려주지만 ok는 True이고, 빈 질문과 API 오류일 때만 False입니다. app.py의result.get("cited") is False는 cited 키가 없을 때(빈 질문, 검색 0건, 오류) None이 되어 경고를 찍지 않습니다.if not result.get("cited")로 쓰면 이런 경우에도 경고가 나와 버립니다.web.py는 입력이 있을 때마다 파일 전체가 처음부터 다시 실행됩니다. 그래서 대화 기록은st.session_state.history에 쌓아 두고, 매번 처음부터 다시 그립니다. 인덱스를 매번 불러오지 않게 하려면 9-1의@st.cache_resource를 씁니다.