RAG2026-5일19차시 · Node 이해13 / 19← 이전목차다음 →MetaContents

19차시. Node 이해

10. 노드를 이어서 돌려보기

출처: 원본 PDF 19차시_Node이해.pdf 20~21쪽 · 쪽 그림 20쪽 21쪽

아직 그래프는 안 만듭니다. 손으로 이어서 전체 흐름을 확인합니다.

📝 src/manual_flow.py

출력
from nodes.retrieve import retrieve_node
from nodes.generate import generate_node
from nodes.fallback import fallback_node

def manual_run(question: str):
    # 그래프 없이 손으로 노드를 이어본다
    state = {"question": question, "query": question,
             "retries": 0, "log": [], "tried_queries": []}

    # ① 검색
    upd = retrieve_node(state)
    state = {**state, **upd, "log": state["log"] + upd["log"]}

    # ② 분기 (나중에 Edge가 될 부분)
    if state["retrieval_ok"]:
        upd = generate_node(state)
    else:
        upd = fallback_node(state)
    state = {**state, **upd, "log": state["log"] + upd["log"]}

    return state

for q in ["환불은 며칠 이내인가요?", "대표이사 이름은?"]:
    s = manual_run(q)
    print("Q:", q)
    print("A:", s["answer"][:80])
    for line in s["log"]:
        print("   ", line)
    print("-" * 55)

실행 결과
─────────────────────────────────────────────
─────────
Q: 환불은 며칠 이내인가요?
A: 환불은 상품 수령 후 7일 이내에 신청하실 수 있습니다[1]...
    [retrieve] '환불은 며칠 이내인가요?' → 3/5건 통과
    [generate] 87자 생성 (인용 정상)
─────────────────────────────────────────────
──────────
Q: 대표이사 이름은?
A: 관련 자료를 찾지 못했습니다.
   질문을 조금 더 구체적으로 바꿔 다시 물어봐 주세요.
    [retrieve] '대표이사 이름은?' → 0/5건 통과
    [fallback] 안내 메시지 반환
─────────────────────────────────────────────
──────────

🎯 지금 한 일이 곧 그래프입니다
위 코드에서
· 노드 호출 → add_node
· state 합치기 → LangGraph가 자동으로
· if문 분기 → add_conditional_edges
21차시에서 이걸 그래프로 바꿉니다.
내용은 오늘 만든 것 그대로입니다.

실습 코드 · 19/manual_flow.py

실습 코드: 실습/src/19/manual_flow.py · 위의 원본 PDF 19차시 20~21쪽 「노드를 이어서 돌려보기」과 이어집니다.

Python
# ============================================================
# 19차시 | Node 이해
# 교안: 19차시_Node이해.pptx
# 역할: LangGraph 없이 노드 함수를 순서대로 호출해 같은 흐름을 손으로 잇는다.
# 입력: 질문 문자열
# 출력: 질문, 답변 앞 80자, 각 노드 log
#
#   State  = manual_run 안의 딕셔너리. 그래프가 갱신해 주지 않으므로
#            노드 return 을 state.update 로 직접 합친다.
#   Node   = retrieve_node, generate_node, fallback_node (다른 파일).
#   Edge   = 여기에는 없다. if state["retrieval_ok"] 가 갈림길 역할을 한다.
#   reducer = 그래프 밖이라 log 가 자동으로 이어 붙지 않는다.
#            new_log = 기존 log + 노드 log 로 직접 붙인 뒤 다시 넣는다.
#            (reducer 가 있을 때의 operator.add 와 같은 일)
#
# 흐름
#   START 자리(함수 시작)
#     |
#     v
#   retrieve_node
#     |
#     +-- retrieval_ok 참 → generate_node → 반환
#     |
#     +-- 거짓 → fallback_node → 반환
#
# 테스트 질문
#   "환불은 며칠 이내인가요?"  → 생성 쪽으로 기대
#   "대표이사 이름은?"        → 안내 쪽으로 기대
# ============================================================

import warnings

from retrieve import retrieve_node
from generate import generate_node
from fallback import fallback_node

# 불필요한 경고 메시지를 숨깁니다.
warnings.filterwarnings("ignore")

# ============================================================
# Node들을 직접 연결하여 실행
# ============================================================
# 노드가 아님. 세 노드를 순서와 if 로 직접 호출하는 함수.
#   읽기: 인자 question, 그리고 각 노드가 돌려준 State 칸
#   쓰기: 함수 지역 state 전체 (그래프 return 이 아님)
def manual_run(question: str):
    """그래프 없이 Node들을 직접 연결하여 실행합니다."""

    # 처음 사용할 State를 만듭니다.
    state = {
        "question": question,
        "query": question,
        "retries": 0,
        "log": [],
        "tried_queries": [],
    }

    # --------------------------------------------------------
    # 1. Retriever Node 실행
    # retrieve_node 읽기: query, question / 쓰기: documents, scores, retrieval_ok, log
    # --------------------------------------------------------

    # 질문과 관련된 문서를 검색합니다.
    result = retrieve_node(state)

    # 검색 과정의 로그를 따로 보관합니다.
    # 그래프 reducer 가 없으므로 log 를 직접 이어 붙인다.
    new_log = state["log"] + result["log"]

    # 검색 결과를 State에 추가합니다.
    state.update(result)

    # 기존 로그와 검색 로그를 합칩니다.
    state["log"] = new_log

    # --------------------------------------------------------
    # 2. 검색 성공 여부에 따라 분기
    # 조건부 엣지 대신 if. 참이면 generate, 거짓이면 fallback.
    # --------------------------------------------------------

    # 검색에 성공하면 답변을 생성합니다.
    # generate_node 읽기: documents, question / 쓰기: answer, insufficient, has_citation, log
    if state["retrieval_ok"]:
        result = generate_node(state)

    # 검색에 실패하면 안내 메시지를 만듭니다.
    # fallback_node 읽기: tried_queries / 쓰기: answer, grade, log
    else:
        result = fallback_node(state)

    # --------------------------------------------------------
    # 3. 두 번째 Node의 결과를 State에 추가
    # --------------------------------------------------------

    # 지금까지의 로그와 새로운 로그를 합칩니다.
    new_log = state["log"] + result["log"]

    # Node 실행 결과를 State에 추가합니다.
    state.update(result)

    # 누적된 로그를 다시 State에 저장합니다.
    state["log"] = new_log

    # 최종 State를 반환합니다.
    return state

# ============================================================
# 전체 흐름 테스트
# ============================================================

# 테스트할 질문을 준비합니다.
questions = [
    "환불은 며칠 이내인가요?",
    "대표이사 이름은?",
]

# 질문을 하나씩 실행합니다.
for question in questions:

    # 전체 흐름을 실행합니다.
    result = manual_run(question)

    # 질문과 답변을 출력합니다.
    print("Q:", question)
    print("A:", result["answer"][:80])

    # 어떤 Node가 실행되었는지 로그를 출력합니다.
    for log in result["log"]:
        print("   ", log)

    # 질문별 결과를 구분합니다.
    print("-" * 55)

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

출력
===== 실행 결과 샘플 =====
실행: cd 실습/src/19
      python manual_flow.py
예시 출력 (API 키 필요, 실행하지 않음)
Q: 환불은 며칠 이내인가요?
A: (답변 앞 80자. 본문 미기록)
    [retrieve] '환불은 며칠 이내인가요?' → (통과 건수 미기록)
    [generate] (글자 수·인용 여부 미기록)
-------------------------------------------------------
Q: 대표이사 이름은?
A: (검색이 비면 fallback 문장. 답변 본문이 기록된 로그는 아님)
    [retrieve] '대표이사 이름은?' → (통과 건수 미기록)
    [fallback] 안내 메시지 반환
-------------------------------------------------------
기록된 사실만: 환불은 grade pass, rewrites 0, elapsed 6.69.
              대표이사는 grade giveup, rewrites 2, elapsed 3.41.
이 파일은 grade, rewrites, elapsed 를 출력하지 않는다. 점수는 적지 않는다.
경로: retrieve → generate
      (둘째 질문은 검색이 비면 retrieve → fallback)

코드 해설

LangGraph 없이 노드 함수를 순서대로 호출해 같은 흐름을 손으로 잇는다. 입력은 질문 문자열이고, 출력은 질문, 답변 앞 80자, 각 노드 log입니다.

주석에 적힌 흐름입니다.

출력
흐름
START 자리(함수 시작)
|
v
retrieve_node
|
+-- retrieval_ok 참 → generate_node → 반환
|
+-- 거짓 → fallback_node → 반환
테스트 질문
"환불은 며칠 이내인가요?"  → 생성 쪽으로 기대
"대표이사 이름은?"        → 안내 쪽으로 기대

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

  • 19차시 | Node 이해
  • 교안: 19차시_Node이해.pptx
  • State = manual_run 안의 딕셔너리. 그래프가 갱신해 주지 않으므로
  • 노드 return 을 state.update 로 직접 합친다.
  • Node = retrieve_node, generate_node, fallback_node (다른 파일).
  • Edge = 여기에는 없다. if state["retrieval_ok"] 가 갈림길 역할을 한다.
  • reducer = 그래프 밖이라 log 가 자동으로 이어 붙지 않는다.
  • new_log = 기존 log + 노드 log 로 직접 붙인 뒤 다시 넣는다.

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

  • manual_run: Node들을 직접 연결하여 실행 노드가 아님. 세 노드를 순서와 if 로 직접 호출하는 함수. 읽기: 인자 question, 그리고 각 노드가 돌려준 State 칸 쓰기: 함수 지역 state 전체 (그래프 return 이 아님)

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

← 이전 절19차시 · 9. 자주 하는 실수다음 절 →19차시 · 체크리스트와 과제