27차시. 기본 Modular RAG 구현
실습 1 — 기본 Modular RAG 구현 실습 따라 하기
관련 수업 자료: 27차시 「4. 실습 ① — 그래프 완성」(9쪽), 27차시 「6. 실습 ③ — 루프가 도는 것 확인」(13~14쪽), 27차시 「7. 실습 ④ — 개선 효과 측정」(15~16쪽) · 실습 코드 27/graph27.py, 27/test_retry.py, 27/compare_v22_v27.py
이 실습은 교안 해설(27_기본ModularRAG실습.md)의 실습 순서를 옮긴 것입니다. OpenAI 임베딩이나 채팅 모델을 부르는 파일은 .env 에 API 키가 있어야 돌아갑니다. 키가 없으면 실행 결과 샘플로 출력 모양을 확인합니다.
수업 자료와 이어지는 곳
| 실습 코드 | 원본 PDF | 이어지는 내용 |
|---|---|---|
27/graph27.py | 9쪽 | 27차시 「4. 실습 ① — 그래프 완성」 아래에 실었습니다 |
27/test_retry.py | 13쪽 | 27차시 「6. 실습 ③ — 루프가 도는 것 확인」 아래에 실었습니다 |
27/compare_v22_v27.py | 15~16쪽 | 27차시 「7. 실습 ④ — 개선 효과 측정」 아래에 실었습니다 |
실행 흐름
retrieve → generate (ok)
retrieve → fallback (empty)
generate → verify
verify → END (pass)
verify → bump (retry)
bump → generate
verify → END (상한)실습 순서
라우팅 표를 코드보다 먼저 적습니다. 조건 순서에 상한이 맨 위인지 확인한 뒤, 키가 있으면 test_retry.py로 log에 bump가 남는지 봅니다.
스스로 확인
- bump가 따로 필요한 이유를 말하는가?
- 상한 검사가 아래에 있으면 어떤 버그가 나는가?
- 같은 근거로 다시 써도 안 되는 실패가 재검색 문제라는 점을 아는가?
코드 설명
app.stream(initial_state, {"recursion_limit": 25})는 노드 하나가 끝날 때마다{노드 이름: 그 노드가 돌려준 값}을 하나씩 내줍니다.app.invoke는 마지막 State 만 주므로, 중간에 bump 가 몇 번 끼었는지 보려면 stream 을 씁니다.graph27.py의route_after_verify는 pass 와 giveup 을 먼저 보고 그다음retries >= MAX_RETRY를 봅니다. 수업 자료 2-3) 처럼 상한을 맨 위에 두지는 않았지만, retry 로 가는 마지막 줄보다는 앞에 있어서 무한루프는 생기지 않습니다. 분기 이름도 PDF 의 regenerate 대신 retry 를 씁니다.- PDF 4절 코드는 상한에 닿으면 END 로 끝내는 A안이고,
graph27.py는"giveup": "fallback"으로 연결한 B안입니다. 같은 차시 안에서도 두 정책이 함께 보이니 경로맵의 값을 꼭 확인하십시오. graph.add_edge("bump", "generate")한 줄이 재생성 루프입니다. bump 에서 retrieve 로 돌아가지 않으므로 documents 는 그대로이고 답만 다시 씁니다. 검색 결과가 나빠서 생긴 실패는 이 루프로 고쳐지지 않습니다.compare_v22_v27.py의contains_answer_keyword는 먼저is_no_answer로 "모른다" 안내인지 봅니다. 안내 문장 안에 우연히 '환불' 이 들어 있어도 정답으로 세지 않기 위해서입니다. no_answer 질문에서 안내가 없으면 hallucination_count 가 1 늘어납니다.