16차시. 1부 종합정리
실습 1 — 1부 종합정리 실습 따라 하기
관련 수업 자료: 16차시 「7. 1부에서 남긴 자산 확인」(15쪽) · 실습 코드 rag_app/config.py, rag_app/indexer.py, rag_app/rag.py, rag_app/app.py
이 실습은 교안 해설(16_1단계종합정리.md)의 실습 순서를 옮긴 것입니다. OpenAI 임베딩이나 채팅 모델을 부르는 파일은 .env 에 API 키가 있어야 돌아갑니다. 키가 없으면 실행 결과 샘플로 출력 모양을 확인합니다.
수업 자료와 이어지는 곳
| 실습 코드 | 원본 PDF | 이어지는 내용 |
|---|---|---|
rag_app/config.py | 15쪽 | 16차시 「7. 1부에서 남긴 자산 확인」 아래에 실었습니다 |
rag_app/indexer.py | - | 원본 PDF 에 짝이 되는 쪽이 없어 이 페이지 아래에 실었습니다 |
rag_app/rag.py | - | 원본 PDF 에 짝이 되는 쪽이 없어 이 페이지 아래에 실었습니다 |
rag_app/app.py | - | 원본 PDF 에 짝이 되는 쪽이 없어 이 페이지 아래에 실었습니다 |
실행 흐름
1에서8 일단 만든다 → 9에서14 측정한다
9에서14 측정한다 → 15에서16 나눈다
15에서16 나눈다 → 17 그래프실습 순서
자료를 덮고 RAG 5단계를 그림으로 그린 뒤, rag_app의 네 파일이 어느 단계인지 화살표로 연결합니다. 막히면 해당 차시 교안으로 돌아갑니다.
스스로 확인
- 5단계의 입출력을 자료 없이 말하는가?
- 검색 문제와 생성 문제를 가르는 질문을 기억하는가?
- 2부에서 if가 Edge로 바뀐다는 점을 예상하는가?
이어지는 실습 코드
실습 코드 · rag_app/indexer.py
실습 코드: 실습/src/rag_app/indexer.py · 원본 PDF 에 같은 이름의 쪽이 없어 여기에 실었습니다.
이 파일은 15차시 「4. 실습 ② — indexer.py」에 주석과 함께 전체가 실려 있습니다. 여기서는 이 차시에서 보는 부분의 해설과 실행 결과만 옮깁니다.
코드 해설
PDF를 잘라 FAISS 저장소를 만들거나, 이미 만든 인덱스를 읽어 Retriever 역할을 준비합니다.
코드 앞에서 고정해 두는 말입니다.
- 읽는 키: 없음 (config 상수)
- 쓰는 키: 반환: FAISS 저장소 객체. 새로 만들 때는 인덱스 파일을 디스크에 저장
- 그래프:
- get_store
- |-- index.faiss 와 index.pkl 이 있고 rebuild가 아님 --> 로드
- +-- 없음 --> PDF 로드 --> 청크 --> FAISS 생성 --> 저장
- Retriever에 해당하는 준비 단계입니다. 질문 함수는 없고, 저장소 객체를 돌려줍니다.
- 청크마다 metadata filename은 PDF 이름, page_no는 로더의 page에 1을 더한 값입니다.
함수와 클래스로 나누어 읽습니다.
get_store: 이름 그대로 한 가지 일만 합니다. 본문 주석을 같이 읽습니다.
파일 맨 아래 실행 결과 샘플 주석은 실행되지 않습니다. 화면 출력은 다음 절에 다시 적어 두었습니다.
실습 코드 · rag_app/rag.py
실습 코드: 실습/src/rag_app/rag.py · 원본 PDF 에 같은 이름의 쪽이 없어 여기에 실었습니다.
이 파일은 15차시 「5. 실습 ③ — rag.py (핵심)」에 주석과 함께 전체가 실려 있습니다. 여기서는 이 차시에서 보는 부분의 해설과 실행 결과만 옮깁니다.
코드 해설
한 함수 ask() 안에 검색(Retriever)과 답 생성(Generator), 인용 검사가 순서대로 들어 있습니다.
코드 앞에서 고정해 두는 말입니다.
- 읽는 키: 인자 question, k. LangGraph State 키는 없습니다.
- 쓰는 키: 반환 딕셔너리 answer, sources, cited, ok. 실패 시 sources는 빈 리스트
- 그래프:
- question
- |
- v
- _search (FAISS, MIN_SCORE 이상만)
- |-- 문서 없음 --> MSG_NO_DOC
함수와 클래스로 나누어 읽습니다.
_search: 이름 그대로 한 가지 일만 합니다. 본문 주석을 같이 읽습니다._build_context: 이름 그대로 한 가지 일만 합니다. 본문 주석을 같이 읽습니다.ask: 이름 그대로 한 가지 일만 합니다. 본문 주석을 같이 읽습니다.
파일 맨 아래 실행 결과 샘플 주석은 실행되지 않습니다. 화면 출력은 다음 절에 다시 적어 두었습니다.
실습 코드 · rag_app/app.py
실습 코드: 실습/src/rag_app/app.py · 원본 PDF 에 같은 이름의 쪽이 없어 여기에 실었습니다.
이 파일은 15차시 「7. 실습 ④ — app.py (터미널 버전)」에 주석과 함께 전체가 실려 있습니다. 여기서는 이 차시에서 보는 부분의 해설과 실행 결과만 옮깁니다.
코드 해설
콘솔에서 질문을 반복 입력받아 rag.ask 결과에 답과 출처를 출력합니다.
코드 앞에서 고정해 두는 말입니다.
- 읽는 키: ask() 반환의 answer, sources, cited
- 쓰는 키: 화면 출력
- 그래프:
- 배너
- |
- v
- 질문 입력 -- q/exit/quit/종료 --> 종료
- |
함수와 클래스로 나누어 읽습니다.
main: 이름 그대로 한 가지 일만 합니다. 본문 주석을 같이 읽습니다.
파일 맨 아래 실행 결과 샘플 주석은 실행되지 않습니다. 화면 출력은 다음 절에 다시 적어 두었습니다.
코드 설명
TOP_K = 5 # 3→5: +10%p (k=8은 노이즈로 하락)처럼 config.py 의 값 옆에는 그 값을 고른 근거를 적어 둡니다. 14차시 비교 실험의 결과가 이 한 줄에 남습니다.EMBED_MODEL옆의⚠ 변경 시 인덱스 재생성은 임베딩 모델을 바꾸면 저장해 둔 인덱스를 다시 만들어야 한다는 뜻입니다. 모델이 다르면 좌표계가 달라져 검색이 엉뚱해집니다.get_store는 index.faiss 와 index.pkl 이 있으면 불러오고, 없을 때만 PDF를 잘라 새로 만듭니다. 미니과제 요건 1번 "두 번째 실행부터는 불러올 것"이 바로 이 부분입니다.ask()는 answer, sources, ok 키에, 답을 만들었을 때는 cited 까지 담은 딕셔너리를 돌려줍니다. 2부에서 State가 "지금 반환하는 딕셔너리"라고 하는 것이 이 모양입니다._search가 MIN_SCORE 이상인 조각만 남기고, 하나도 없으면ask()가MSG_NO_DOC을 돌려줍니다. 앱이 멈추지 않고 안내 문장으로 끝납니다. 이 if 한 줄이 2부에서 조건부 Edge로 옮겨 갑니다.