RAG2026-5일22차시 · RAG를 Graph로 바꾸기10 / 20← 이전목차다음 →MetaContents

22차시. RAG를 Graph로 바꾸기

7. 실습 ⑤ — 흐름 확인

출처: 원본 PDF 22차시_RAG를Graph로바꾸기.pdf 12쪽 · 쪽 그림 12쪽

stream으로 실제 경로를 확인해 그래프가 의도대로 도는지 봅니다.

출력
from graph import app
from state import init_state

for q in ["환불은 며칠 이내인가요?", "대표이사가 누구인가요?"]:
    print(f"\n{'='*55}\nQ: {q}")
    for step in app.stream(init_state(q)):
        for node, update in step.items():
            keys = list(update.keys())
            print(f"  [{node}] {keys}")

═════════════════════════════════════════════
══════════
Q: 환불은 며칠 이내인가요?
  [retrieve] ['documents', 'scores', 'retrieval_ok', 'log']
  [generate] ['answer', 'insufficient', 'has_citation', 'log']

═════════════════════════════════════════════
══════════
Q: 대표이사가 누구인가요?
  [retrieve] ['documents', 'scores', 'retrieval_ok', 'log']
  [fallback] ['answer', 'grade', 'log']

🎯 두 경로가 다르게 흘렀습니다
정상 질문 → retrieve → generate
없는 질문 → retrieve → fallback
조건부 Edge가 제대로 판단하고 있다는 뜻입니다.
15차시 버전에서는 이 흐름이 함수 안에 숨어 있어
눈으로 확인할 수 없었습니다.

실습 코드 · 22/test_migration_stream.py

실습 코드: 실습/src/22/test_migration_stream.py · 위의 원본 PDF 22차시 12쪽 「7️⃣ 실습 ⑤ — 흐름 확인」과 이어집니다.

Python
# ============================================================
# 22차시 | RAG를 Graph로 바꾸기
# 교안: 22차시_RAG를Graph로바꾸기.pptx
# 역할: stream 으로 노드가 끝날 때마다 갱신된 State 키를 출력한다.
# 입력: QUESTIONS 두 개. make_initial_state 로 첫 노트를 만든다.
# 출력: 노드 이름, 그 노드가 돌려준 키 목록. retrieve 이면 점수와 성공 여부.
#
#   State  = graph.py 가 compile 해 둔 앱의 RAGState.
#   Node   = retrieve, generate, fallback.
#   stream = invoke 처럼 끝까지 모으지 않는다.
#            노드 하나가 끝나면 {노드이름: 그 노드가 return 한 칸} 을 하나씩 준다.
#   조건부 엣지 = retrieval_ok 이면 generate, 아니면 fallback.
#
# 흐름
#   START → retrieve
#              ├─ generate → END
#              └─ fallback → END
# ============================================================

# 이 파일은 LangGraph가 실제로 어떤 경로로 실행되는지 확인합니다.
# 질문을 입력하면 retrieve, generate, fallback 노드의 실행 순서를 화면에 출력합니다.

from graph import app
from graph_state2 import make_initial_state
import warnings
warnings.filterwarnings("ignore")

# ==================================================
# 실행 경로를 확인할 질문 목록
# ==================================================
QUESTIONS = [
    # 검색 결과가 있을 것으로 예상되는 질문입니다.
    "환불은 며칠 이내인가요?",

    # 검색 결과가 없을 것으로 예상되는 질문입니다.
    "대표이사가 누구인가요?",
]

# ==================================================
# 하나의 질문에 대해 그래프 실행 과정을 출력하는 함수
# ==================================================
def show_graph_process(question):
    """
    질문 하나를 그래프에 전달하고 실행 과정을 출력합니다.
    """

    # 질문 제목을 출력합니다.
    print()
    print("=" * 55)
    print("Q:", question)
    print("=" * 55)

    # 질문을 그래프가 사용할 초기 상태로 만듭니다.
    initial_state = make_initial_state(question)

    # 그래프를 실행하면서 각 노드의 결과를 하나씩 받습니다.
    # stream: 노드가 끝날 때마다 {노드이름: return 한 칸} 을 하나씩 준다.
    for step in app.stream(initial_state):

        # 현재 실행된 노드와 상태 변경 내용을 확인합니다.
        for node, update in step.items():

            # 어떤 노드가 실행되었는지 출력합니다.
            print(f"  [{node}] {list(update.keys())}")

            # 검색 노드가 실행된 경우 검색 정보를 출력합니다.
            if node == "retrieve":
                print("    검색 점수:", update.get("scores"))
                print(
                    "    검색 성공 여부:",
                    update.get("retrieval_ok"),
                )

# ==================================================
# 모든 질문에 대해 그래프 실행 과정을 확인합니다.
# ==================================================
def main():
    """
    질문 목록을 차례대로 실행합니다.
    """

    # 질문을 하나씩 꺼냅니다.
    for question in QUESTIONS:

        # 현재 질문의 그래프 실행 과정을 출력합니다.
        show_graph_process(question)

# ==================================================
# 이 파일을 직접 실행했을 때 시작합니다.
# ==================================================
if __name__ == "__main__":
    main()

실행 결과 샘플 (교안 기록. API 키가 필요한 코드는 키 없이 맞춘 예시이고, 모델의 답은 실행할 때마다 달라집니다)

출력
===== 실행 결과 샘플 =====
실행: cd 실습/src/22
      python test_migration_stream.py
예시 출력 (API 키 필요, 실행하지 않음)
=======================================================
Q: 환불은 며칠 이내인가요?
=======================================================
  [retrieve] ['documents', 'scores', 'retrieval_ok', 'log']
    검색 점수: (유사도 점수 미기록)
    검색 성공 여부: (미기록)
  [generate] ['answer', 'insufficient', 'has_citation', 'log']

=======================================================
Q: 대표이사가 누구인가요?
=======================================================
  [retrieve] ['documents', 'scores', 'retrieval_ok', 'log']
    검색 점수: (유사도 점수 미기록)
    검색 성공 여부: (미기록)
  (검색이 비면 다음은 fallback, 아니면 generate. 어느 쪽인지는 점수 기록이 없어 단정하지 않는다)
기록된 사실만: 환불은 grade pass, rewrites 0, elapsed 6.69.
              대표이사는 grade giveup, rewrites 2, elapsed 3.41.
이 파일은 그 세 값을 출력하지 않는다.
경로: retrieve → generate → END

코드 해설

stream 으로 노드가 끝날 때마다 갱신된 State 키를 출력한다. 입력은 QUESTIONS 두 개. make_initial_state 로 첫 노트를 만든다.이고, 출력은 노드 이름, 그 노드가 돌려준 키 목록. retrieve 이면 점수와 성공 여부.

주석에 적힌 흐름입니다.

출력
흐름
START → retrieve
├─ generate → END
└─ fallback → END

코드 앞에서 고정해 두는 말입니다.

  • 22차시 | RAG를 Graph로 바꾸기
  • 교안: 22차시_RAG를Graph로바꾸기.pptx
  • State = graph.py 가 compile 해 둔 앱의 RAGState.
  • Node = retrieve, generate, fallback.
  • stream = invoke 처럼 끝까지 모으지 않는다.
  • 노드 하나가 끝나면 {노드이름: 그 노드가 return 한 칸} 을 하나씩 준다.
  • 조건부 엣지 = retrieval_ok 이면 generate, 아니면 fallback.

함수와 클래스로 나누어 읽습니다.

  • show_graph_process: 하나의 질문에 대해 그래프 실행 과정을 출력하는 함수
  • main: 모든 질문에 대해 그래프 실행 과정을 확인합니다.

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

7-1) app.py 연결

출력
■ app.py에서 import 한 줄만 바꾸면 끝

# from rag import ask          # 15차시 버전
from graph import ask          # 22차시 버전

→ 나머지 코드는 하나도 안 고칩니다
→ python app.py 실행하면 똑같이 동작합니다

실습 코드 · 22/app.py

실습 코드: 실습/src/22/app.py · 위의 원본 PDF 22차시 12쪽 「7-1) app.py 연결」과 이어집니다.

Python
# ============================================================
# 22차시 | RAG를 Graph로 바꾸기
# 교안: 22차시_RAG를Graph로바꾸기.pptx
# 역할: 질문 한 줄을 입력받아 ask() 결과에 있는 답변과 출처를 출력한다.
# 입력: input() 한 번. 반복 루프는 없다.
# 출력: 답변 본문, 출처 딕셔너리를 한 줄씩
#
#   이 파일에는 State, Node, Edge 정의가 없다.
#   ../rag_app/graph.py 의 ask() 가 그래프를 invoke 한다.
#   그 그래프의 흐름은 아래와 같다.
#
#   START → retrieve
#              ├─ generate → END
#              └─ fallback → END
# ============================================================

# 이 파일은 사용자의 질문을 받아 LangGraph 기반 RAG를 실행합니다.
# 실행 결과로 답변과 답변에 사용된 문서 출처를 화면에 출력합니다.

import os
import sys

# ==================================================
# graph.py 파일을 찾기 위한 경로 설정
# ==================================================

# 현재 app.py 파일이 있는 폴더입니다.
CURRENT_DIR = os.path.dirname(__file__)

# graph.py가 있는 rag_app 폴더의 경로입니다.
RAG_APP_DIR = os.path.join(
    CURRENT_DIR,
    "..",
    "rag_app",
)

# Python이 rag_app 폴더의 모듈을 찾도록 경로를 추가합니다.
sys.path.insert(0, RAG_APP_DIR)

# LangGraph 기반 RAG의 ask() 함수를 가져옵니다.
from graph import ask

# ==================================================
# 질문을 받고 답변을 출력하는 함수
# ==================================================
def main():
    """
    사용자에게 질문을 받고 RAG 답변과 출처를 출력합니다.
    """

    # 사용자에게 질문을 입력받습니다.
    question = input("질문을 입력하세요: ")

    # 입력받은 질문을 RAG 시스템에 전달합니다.
    result = ask(question)

    # 최종 답변을 출력합니다.
    print()
    print("답변:")
    print(result["answer"])

    # 답변에 사용된 출처를 출력합니다.
    print()
    print("출처:")

    # 출처를 하나씩 꺼내 출력합니다.
    for source in result["sources"]:
        print(source)

# ==================================================
# 이 파일을 직접 실행했을 때 시작합니다.
# ==================================================
if __name__ == "__main__":
    main()

실행 결과 샘플 (교안 기록. API 키가 필요한 코드는 키 없이 맞춘 예시이고, 모델의 답은 실행할 때마다 달라집니다)

출력
===== 실행 결과 샘플 =====
실행: cd 실습/src/22
      python app.py
예시 출력 (API 키 필요, 실행하지 않음)
질문을 입력하세요: 환불은 며칠 이내인가요?

답변:
(답변 본문은 기록되어 있지 않다)

출처:
(파일명과 페이지는 기록되어 있지 않다)
기록된 사실만: 이 질문은 grade pass, rewrites 0, elapsed 6.69.
app.py 는 그 세 값을 출력하지 않는다.
경로: retrieve → generate → END

코드 해설

질문 한 줄을 입력받아 ask() 결과에 있는 답변과 출처를 출력한다. 입력은 input() 한 번. 반복 루프는 없다.이고, 출력은 답변 본문, 출처 딕셔너리를 한 줄씩입니다.

코드 앞에서 고정해 두는 말입니다.

  • 22차시 | RAG를 Graph로 바꾸기
  • 교안: 22차시_RAG를Graph로바꾸기.pptx
  • 이 파일에는 State, Node, Edge 정의가 없다.
  • ../rag_app/graph.py 의 ask() 가 그래프를 invoke 한다.
  • 그 그래프의 흐름은 아래와 같다.
  • START → retrieve
  • ├─ generate → END
  • └─ fallback → END

함수와 클래스로 나누어 읽습니다.

  • main: 질문을 받고 답변을 출력하는 함수

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

← 이전 절22차시 · 6. 실습 ④ — 회귀 테스트다음 절 →22차시 · 8. 3부에서 붙일 자리 미리 보기