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쪽 「노드를 이어서 돌려보기」과 이어집니다.
# ============================================================
# 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 이 아님)
파일 맨 아래 실행 결과 샘플 주석은 실행되지 않습니다. 화면 출력은 다음 절에 다시 적어 두었습니다.