21차시. 첫 번째 LangGraph 실습
3. 실습 ① — State와 더미 노드
출처: 원본 PDF 21차시_첫번째LangGraph실습.pdf 6~7쪽 · 쪽 그림 6쪽 7쪽
API 없이 돌아가는 완전한 그래프를 만들어봅니다.
📝 src/mini_graph.py — 1단계 State
from typing import TypedDict, List, Annotated
from langgraph.graph import StateGraph, START, END
import operator
class MiniState(TypedDict):
question: str
documents: List[str]
answer: str
grade: str
retries: int
log: Annotated[List[str], operator.add]
def init(q: str) -> MiniState:
return {"question": q, "documents": [], "answer": "",
"grade": "", "retries": 0, "log": []}📝 2단계 더미 노드
def retrieve(state: MiniState):
# 질문에 '없는'이 들어가면 검색 실패로 흉내
q = state["question"]
docs = [] if "없는" in q else ["조각A", "조각B", "조각C"]
print(f" → retrieve: {len(docs)}건")
return {"documents": docs, "log": [f"검색 {len(docs)}건"]}
def generate(state: MiniState):
n = len(state["documents"])
# 재시도할수록 답변이 좋아지는 상황을 흉내
quality = "좋은" if state["retries"] >= 1 else "부실한"
print(f" → generate: {quality} 답변")
return {"answer": f"{n}건 근거로 만든 {quality} 답변",
"log": [f"생성({quality})"]}
def verify(state: MiniState):
ok = "좋은" in state["answer"]
print(f" → verify: {'통과' if ok else '재시도'}")
return {"grade": "pass" if ok else "retry",
"log": ["검증 " + ("통과" if ok else "실패")]}
def bump(state: MiniState):
n = state["retries"] + 1
print(f" → bump: {n}회차")
return {"retries": n, "log": [f"재시도 {n}"]}
def fallback(state: MiniState):
print(" → fallback")
return {"answer": "자료를 찾지 못했습니다.",
"grade": "giveup", "log": ["fallback"]}💡 더미 노드 설계 요령
원하는 상황을 마음대로 만들 수 있게 설계합니다.
· "없는"이 포함되면 → 검색 실패 재현
· retries가 0이면 → 검증 실패 재현
· retries가 1 이상이면 → 통과 재현
이렇게 하면 정상 경로 · 실패 경로 · 루프를
질문 하나만 바꿔서 전부 테스트할 수 있습니다.
실습 코드 · 21/mini_graph.py
실습 코드: 실습/src/21/mini_graph.py · 위의 원본 PDF 21차시 6~7쪽 「3️⃣ 실습 ① — State와 더미 노드」과 이어집니다.
# ============================================================
# 21차시 | 첫 번째 LangGraph 실습
# 교안: 21차시_첫번째LangGraph실습.pptx
# 역할: 작은 RAG 그래프가 나눠 쓸 State 칸과 빈 노트를 만드는 함수만 둔다.
# 입력: init(question)
# 출력: 없음 (이 파일을 실행해도 print 하지 않는다)
#
# State = MiniState. 질문, 문서, 답변, 검증, 재시도, 로그.
# Node = 이 파일에는 없다. init 은 노드가 아니라 첫 노트 생성 함수.
# reducer = log. Annotated[List[str], operator.add] 이면
# 노드가 ["한 줄"] 을 돌려줄 때 기존 리스트 뒤에 붙는다.
# 통째로 덮어쓰지 않는다.
#
# 흐름
# (그래프 없음)
# question → init → question 만 채우고 나머지 칸은 비운 딕셔너리
# ============================================================
# 이 코드는 LangGraph에서 사용할 상태(State)의 구조를 정의합니다.
# 질문, 검색 문서, 답변, 검증 결과, 재시도 횟수, 실행 로그를 관리합니다.
from typing import TypedDict, List, Annotated
import operator
# ------------------------------------------------------------
# 그래프가 공유할 상태 구조
# ------------------------------------------------------------
class MiniState(TypedDict):
# 사용자가 입력한 질문
question: str
# 검색된 문서 목록
documents: List[str]
# 생성된 답변
answer: str
# 답변 검증 결과
# 예: "pass", "retry", "giveup"
grade: str
# 현재까지 재시도한 횟수
retries: int
# 그래프 실행 과정을 기록하는 로그
# reducer: 여러 노드의 log 를 한 리스트 뒤에 이어 붙인다.
log: Annotated[List[str], operator.add]
# ------------------------------------------------------------
# 그래프 실행에 사용할 초기 상태 만들기
# ------------------------------------------------------------
# 노드가 아님. invoke / stream 에 넣을 첫 State를 만든다.
# 읽기: 인자 question
# 쓰기: question, documents, answer, grade, retries, log (새 딕셔너리)
def init(question: str) -> MiniState:
"""
사용자의 질문을 바탕으로 초기 상태를 만듭니다.
그래프를 처음 실행할 때는 아직 검색 결과와 답변이 없습니다.
따라서 documents, answer, grade, log는 빈 값으로 시작합니다.
retries도 처음에는 0으로 설정합니다.
"""
# 그래프 실행에 필요한 초기 상태를 반환합니다.
return {
"question": question,
"documents": [],
"answer": "",
"grade": "",
"retries": 0,
"log": [],
}실행 결과 샘플 (교안 기록. API 키가 필요한 코드는 키 없이 맞춘 예시이고, 모델의 답은 실행할 때마다 달라집니다)
===== 실행 결과 샘플 =====
실행: cd 실습/src/21
python mini_graph.py
(실제 실행 출력)
(표준 출력 없음. State 와 init 만 정의한다.)코드 해설
작은 RAG 그래프가 나눠 쓸 State 칸과 빈 노트를 만드는 함수만 둔다. 입력은 init(question)이고, 출력은 없음 (이 파일을 실행해도 print 하지 않는다)입니다.
주석에 적힌 흐름입니다.
흐름
(그래프 없음)
question → init → question 만 채우고 나머지 칸은 비운 딕셔너리코드 앞에서 고정해 두는 말입니다.
- 21차시 | 첫 번째 LangGraph 실습
- 교안: 21차시_첫번째LangGraph실습.pptx
- State = MiniState. 질문, 문서, 답변, 검증, 재시도, 로그.
- Node = 이 파일에는 없다. init 은 노드가 아니라 첫 노트 생성 함수.
- reducer = log. Annotated[List[str], operator.add] 이면
- 노드가 ["한 줄"] 을 돌려줄 때 기존 리스트 뒤에 붙는다.
- 통째로 덮어쓰지 않는다.
함수와 클래스로 나누어 읽습니다.
MiniState: 이름 그대로 한 가지 일만 합니다. 본문 주석을 같이 읽습니다.init: 노드가 아님. invoke / stream 에 넣을 첫 State를 만든다. 읽기: 인자 question 쓰기: question, documents, answer, grade, retries, log (새 딕셔너리)
파일 맨 아래 실행 결과 샘플 주석은 실행되지 않습니다. 화면 출력은 다음 절에 다시 적어 두었습니다.
실습 코드 · 21/mini_graph2.py
실습 코드: 실습/src/21/mini_graph2.py · 위의 원본 PDF 21차시 6~7쪽 「3️⃣ 실습 ① — State와 더미 노드」과 이어집니다.
# ============================================================
# 21차시 | 첫 번째 LangGraph 실습
# 교안: 21차시_첫번째LangGraph실습.pptx
# 역할: 검색·생성·검증·재시도·안내를 가짜 데이터로 흉내 내는 노드 함수 모음.
# 이 파일은 노드를 엣지로 연결하지 않는다. 실행해도 출력은 없다.
# 입력: MiniState
# 출력: 각 함수가 고친 칸만 담은 딕셔너리
#
# State = MiniState. 노드들은 이 노트에서 자기 칸만 읽고 쓴다.
# Node = retrieve, generate, verify, bump, fallback.
# reducer = log. return 하는 log 는 한 칸짜리 리스트라 뒤에 이어 붙는다.
# Edge / compile / invoke = 다음 파일에서 연결한다.
#
# 함수만 있을 때의 데이터 흐름 (아직 자동 실행 아님)
# question → retrieve → documents
# documents, retries → generate → answer
# answer → verify → grade
# retries → bump → retries+1
# (검색 실패 또는 포기) → fallback → answer, grade="giveup"
# ============================================================
# 이 코드는 LangGraph에서 사용할 상태와 더미 노드를 정의합니다.
# 실제 검색이나 LLM 호출 없이 검색, 생성, 검증, 재시도 과정을 흉내 냅니다.
from typing import TypedDict, List, Annotated
import operator
# ============================================================
# 그래프 상태 정의
# ============================================================
class MiniState(TypedDict):
# 사용자가 입력한 질문
question: str
# 검색된 문서 목록
documents: List[str]
# 생성된 답변
answer: str
# 검증 결과
# pass: 통과, retry: 재시도, giveup: 포기
grade: str
# 재시도 횟수
retries: int
# 노드 실행 기록
# reducer: 각 노드가 돌려준 log 를 한 리스트에 이어 붙인다.
log: Annotated[List[str], operator.add]
# ============================================================
# 초기 상태 생성 함수
# ============================================================
# 노드가 아님. 첫 공유 노트를 만든다.
# 읽기: 인자 question
# 채우는 칸: question, documents, answer, grade, retries, log
def init(question: str) -> MiniState:
"""
그래프 실행에 사용할 초기 상태를 만듭니다.
처음에는 검색 결과와 답변이 없으므로
관련 항목을 빈 값으로 설정합니다.
"""
# 그래프 실행에 사용할 초기 상태를 반환합니다.
return {
"question": question,
"documents": [],
"answer": "",
"grade": "",
"retries": 0,
"log": [],
}
# ============================================================
# Retriever Node
# ============================================================
# 노드 retrieve
# 읽기: question
# 쓰기: documents, log
def retrieve(state: MiniState) -> dict:
"""
질문을 바탕으로 검색 결과를 만듭니다.
질문에 '없는'이라는 단어가 포함되면
검색 실패 상황을 흉내 냅니다.
"""
# 현재 질문을 가져옵니다.
question = state["question"]
# 검색 결과를 저장할 리스트를 만듭니다.
documents = []
# 질문에 '없는'이 포함되어 있는지 확인합니다.
if "없는" in question:
# 검색 실패 상황을 만듭니다.
documents = []
else:
# 검색 성공 상황을 만듭니다.
documents = ["조각A", "조각B", "조각C"]
# 검색된 문서 개수를 출력합니다.
print(f" → retrieve: {len(documents)}건")
# 변경된 값만 반환합니다.
return {
"documents": documents,
"log": [f"검색 {len(documents)}건"],
}
# ============================================================
# Generator Node
# ============================================================
# 노드 generate
# 읽기: documents, retries
# 쓰기: answer, log
def generate(state: MiniState) -> dict:
"""
검색된 문서를 바탕으로 가짜 답변을 생성합니다.
첫 번째 답변은 부실하게 만들고,
재시도 후에는 좋은 답변을 만듭니다.
"""
# 검색된 문서의 개수를 확인합니다.
document_count = len(state["documents"])
# 현재 재시도 횟수를 확인합니다.
retry_count = state["retries"]
# 답변 품질을 저장할 변수입니다.
quality = ""
# 재시도 횟수에 따라 답변 품질을 결정합니다.
if retry_count >= 1:
# 재시도 후에는 좋은 답변을 만듭니다.
quality = "좋은"
else:
# 첫 번째 답변은 부실한 답변으로 처리합니다.
quality = "부실한"
# 생성된 답변의 품질을 출력합니다.
print(f" → generate: {quality} 답변")
# 가짜 답변을 생성합니다.
answer = f"{document_count}건 근거로 만든 {quality} 답변"
# 변경된 값만 반환합니다.
return {
"answer": answer,
"log": [f"생성({quality})"],
}
# ============================================================
# Verifier Node
# ============================================================
# 노드 verify
# 읽기: answer
# 쓰기: grade, log
def verify(state: MiniState) -> dict:
"""
생성된 답변을 검증합니다.
답변에 '좋은'이라는 단어가 있으면 통과시키고,
없으면 재시도 대상으로 처리합니다.
"""
# 현재 답변을 가져옵니다.
answer = state["answer"]
# 검증 결과를 저장할 변수를 만듭니다.
grade = ""
message = ""
# 답변에 '좋은'이라는 단어가 포함되어 있는지 확인합니다.
if "좋은" in answer:
# 좋은 답변이면 통과시킵니다.
grade = "pass"
message = "통과"
else:
# 부실한 답변이면 재시도 대상으로 처리합니다.
grade = "retry"
message = "실패"
# 검증 결과를 출력합니다.
if grade == "pass":
print(" → verify: 통과")
else:
print(" → verify: 재시도")
# 검증 결과와 로그를 반환합니다.
return {
"grade": grade,
"log": [f"검증 {message}"],
}
# ============================================================
# Retry Counter Node
# ============================================================
# 노드 bump
# 읽기: retries
# 쓰기: retries, log
def bump(state: MiniState) -> dict:
"""
재시도 횟수를 1 증가시킵니다.
"""
# 현재 재시도 횟수에 1을 더합니다.
retry_count = state["retries"] + 1
# 증가한 재시도 횟수를 출력합니다.
print(f" → bump: {retry_count}회차")
# 변경된 값만 반환합니다.
return {
"retries": retry_count,
"log": [f"재시도 {retry_count}"],
}
# ============================================================
# Fallback Node
# ============================================================
# 노드 fallback
# 읽기: 없음 (state 는 받지만 칸을 읽지 않음)
# 쓰기: answer, grade, log
def fallback(state: MiniState) -> dict:
"""
검색에 실패했거나 더 이상 처리할 수 없을 때
사용자에게 안내 답변을 반환합니다.
"""
# Fallback 노드가 실행되었음을 출력합니다.
print(" → fallback")
# 안내 답변과 포기 상태를 반환합니다.
return {
"answer": "자료를 찾지 못했습니다.",
"grade": "giveup",
"log": ["fallback"],
}실행 결과 샘플 (교안 기록. API 키가 필요한 코드는 키 없이 맞춘 예시이고, 모델의 답은 실행할 때마다 달라집니다)
===== 실행 결과 샘플 =====
실행: cd 실습/src/21
python mini_graph2.py
(실제 실행 출력)
(표준 출력 없음. 노드 함수만 정의하고 호출하지 않는다.)코드 해설
검색·생성·검증·재시도·안내를 가짜 데이터로 흉내 내는 노드 함수 모음. 입력은 MiniState이고, 출력은 각 함수가 고친 칸만 담은 딕셔너리입니다.
코드 앞에서 고정해 두는 말입니다.
- 21차시 | 첫 번째 LangGraph 실습
- 교안: 21차시_첫번째LangGraph실습.pptx
- 이 파일은 노드를 엣지로 연결하지 않는다. 실행해도 출력은 없다.
- State = MiniState. 노드들은 이 노트에서 자기 칸만 읽고 쓴다.
- Node = retrieve, generate, verify, bump, fallback.
- reducer = log. return 하는 log 는 한 칸짜리 리스트라 뒤에 이어 붙는다.
- Edge / compile / invoke = 다음 파일에서 연결한다.
- 함수만 있을 때의 데이터 흐름 (아직 자동 실행 아님)
함수와 클래스로 나누어 읽습니다.
MiniState: 이름 그대로 한 가지 일만 합니다. 본문 주석을 같이 읽습니다.init: 노드가 아님. 첫 공유 노트를 만든다. 읽기: 인자 question 채우는 칸: question, documents, answer, grade, retries, logretrieve: 노드 retrieve 읽기: question 쓰기: documents, loggenerate: 노드 generate 읽기: documents, retries 쓰기: answer, logverify: 노드 verify 읽기: answer 쓰기: grade, logbump: 노드 bump 읽기: retries 쓰기: retries, logfallback: 노드 fallback 읽기: 없음 (state 는 받지만 칸을 읽지 않음) 쓰기: answer, grade, log
파일 맨 아래 실행 결과 샘플 주석은 실행되지 않습니다. 화면 출력은 다음 절에 다시 적어 두었습니다.
실습 코드 · 21/mini_graph_nodetest.py
실습 코드: 실습/src/21/mini_graph_nodetest.py · 위의 원본 PDF 21차시 6~7쪽 「3️⃣ 실습 ① — State와 더미 노드」과 이어집니다.
# ============================================================
# 21차시 | 첫 번째 LangGraph 실습
# 교안: 21차시_첫번째LangGraph실습.pptx
# 역할: 그래프에 연결하기 전에 노드 함수를 하나씩 직접 호출해 입출력을 본다.
# 입력: init("환불 규정은?") 로 만든 State. 중간에는 documents, answer 를 손으로 넣는다.
# 출력: 각 함수의 return 딕셔너리
#
# State = MiniState. 테스트에서는 그래프가 아니라 지역 변수 state 를 고친다.
# Node = retrieve, generate, verify, bump. fallback 은 정의만 하고 이 테스트에서 호출하지 않는다.
# reducer = log 규칙만 정의해 둔다. 함수를 직접 부르면 이어 붙이지 않고 return 값 그대로 보인다.
# Edge / compile / invoke = 없다.
#
# 테스트가 손으로 밟는 순서
# init → retrieve → (documents 를 2건으로 바꿈) → generate → (answer 를 부실로 바꿈) → verify → bump
# ============================================================
# 이 코드는 LangGraph에서 사용할 상태와 더미 노드를 정의합니다.
# 실제 RAG나 LLM 없이 검색, 생성, 검증, 재시도 과정을 흉내 냅니다.
from typing import TypedDict, List, Annotated
import operator
# ============================================================
# 그래프 상태 정의
# ============================================================
class MiniState(TypedDict):
# 사용자의 질문
question: str
# 검색된 문서 목록
documents: List[str]
# 생성된 답변
answer: str
# 검증 결과
# pass: 통과, retry: 재시도, giveup: 포기
grade: str
# 재시도 횟수
retries: int
# 노드 실행 로그
# reducer: 그래프에 넣으면 log 를 뒤에 이어 붙인다. 이 파일은 직접 호출만 한다.
log: Annotated[List[str], operator.add]
# ============================================================
# 초기 상태 생성
# ============================================================
# 노드가 아님. 첫 State를 만든다.
# 읽기: 인자 question
# 채우는 칸: question, documents, answer, grade, retries, log
def init(question: str) -> MiniState:
"""
그래프 실행에 사용할 초기 상태를 만듭니다.
"""
# 초기 상태를 반환합니다.
return {
"question": question,
"documents": [],
"answer": "",
"grade": "",
"retries": 0,
"log": [],
}
# ============================================================
# Retriever Node
# ============================================================
# 노드 retrieve
# 읽기: question
# 쓰기: documents, log
def retrieve(state: MiniState) -> dict:
"""
질문을 바탕으로 검색 결과를 만듭니다.
질문에 '없는'이 포함되면 검색 실패로 처리합니다.
"""
# 질문을 가져옵니다.
question = state["question"]
# 검색 결과를 저장할 리스트입니다.
documents = []
# 검색 실패 상황인지 확인합니다.
if "없는" in question:
# 검색 결과가 없는 상태를 만듭니다.
documents = []
else:
# 검색 성공 상황을 만듭니다.
documents = ["조각A", "조각B", "조각C"]
# 검색 결과 개수를 출력합니다.
print(f" → retrieve: {len(documents)}건")
# 변경된 값만 반환합니다.
return {
"documents": documents,
"log": [f"검색 {len(documents)}건"],
}
# ============================================================
# Generator Node
# ============================================================
# 노드 generate
# 읽기: documents, retries
# 쓰기: answer, log
def generate(state: MiniState) -> dict:
"""
검색된 문서를 바탕으로 가짜 답변을 생성합니다.
첫 번째 답변은 부실하게 만들고,
재시도 후에는 좋은 답변을 만듭니다.
"""
# 검색된 문서의 개수를 확인합니다.
document_count = len(state["documents"])
# 재시도 횟수를 확인합니다.
retry_count = state["retries"]
# 답변 품질을 결정합니다.
if retry_count >= 1:
quality = "좋은"
else:
quality = "부실한"
# 가짜 답변을 만듭니다.
answer = f"{document_count}건 근거로 만든 {quality} 답변"
# 생성 결과를 출력합니다.
print(f" → generate: {quality} 답변")
# 변경된 값만 반환합니다.
return {
"answer": answer,
"log": [f"생성({quality})"],
}
# ============================================================
# Verifier Node
# ============================================================
# 노드 verify
# 읽기: answer
# 쓰기: grade, log
def verify(state: MiniState) -> dict:
"""
생성된 답변을 검증합니다.
답변에 '좋은'이 포함되면 통과시키고,
그렇지 않으면 재시도 대상으로 처리합니다.
"""
# 현재 답변을 가져옵니다.
answer = state["answer"]
# 답변 품질을 확인합니다.
if "좋은" in answer:
print(" → verify: 통과")
return {
"grade": "pass",
"log": ["검증 통과"],
}
# 좋은 답변이 아니면 재시도합니다.
print(" → verify: 재시도")
return {
"grade": "retry",
"log": ["검증 실패"],
}
# ============================================================
# Retry Counter Node
# ============================================================
# 노드 bump
# 읽기: retries
# 쓰기: retries, log
def bump(state: MiniState) -> dict:
"""
재시도 횟수를 1 증가시킵니다.
"""
# 재시도 횟수를 1 증가시킵니다.
retry_count = state["retries"] + 1
# 재시도 횟수를 출력합니다.
print(f" → bump: {retry_count}회차")
# 변경된 값만 반환합니다.
return {
"retries": retry_count,
"log": [f"재시도 {retry_count}"],
}
# ============================================================
# Fallback Node
# ============================================================
# 노드 fallback
# 읽기: 없음
# 쓰기: answer, grade, log
def fallback(state: MiniState) -> dict:
"""
검색에 실패했을 때 안내 답변을 반환합니다.
"""
# Fallback 실행을 출력합니다.
print(" → fallback")
# 안내 답변과 포기 상태를 반환합니다.
return {
"answer": "자료를 찾지 못했습니다.",
"grade": "giveup",
"log": ["fallback"],
}
# ============================================================
# 노드 단독 테스트
# ============================================================
if __name__ == "__main__":
print("■ 노드 단독 테스트")
# 초기 상태를 만듭니다.
state = init("환불 규정은?")
# Retriever 노드를 테스트합니다.
retrieve_result = retrieve(state)
print("1) retrieve:", retrieve_result)
# 검색 결과를 임의로 설정합니다.
state["documents"] = ["조각A", "조각B"]
# Generator 노드를 테스트합니다.
generate_result = generate(state)
print("2) generate:", generate_result)
# 부실한 답변을 임의로 설정합니다.
state["answer"] = "부실한 답변"
# Verifier 노드를 테스트합니다.
verify_result = verify(state)
print("3) verify: ", verify_result)
# Bump 노드를 테스트합니다.
bump_result = bump(state)
print("4) bump: ", bump_result)실행 결과 샘플 (교안 기록. API 키가 필요한 코드는 키 없이 맞춘 예시이고, 모델의 답은 실행할 때마다 달라집니다)
===== 실행 결과 샘플 =====
실행: cd 실습/src/21
python mini_graph_nodetest.py
(실제 실행 출력)
■ 노드 단독 테스트
→ retrieve: 3건
1) retrieve: {'documents': ['조각A', '조각B', '조각C'], 'log': ['검색 3건']}
→ generate: 부실한 답변
2) generate: {'answer': '2건 근거로 만든 부실한 답변', 'log': ['생성(부실한)']}
→ verify: 재시도
3) verify: {'grade': 'retry', 'log': ['검증 실패']}
→ bump: 1회차
4) bump: {'retries': 1, 'log': ['재시도 1']}코드 해설
그래프에 연결하기 전에 노드 함수를 하나씩 직접 호출해 입출력을 본다. 입력은 init("환불 규정은?") 로 만든 State. 중간에는 documents, answer 를 손으로 넣는다.이고, 출력은 각 함수의 return 딕셔너리입니다.
코드 앞에서 고정해 두는 말입니다.
- 21차시 | 첫 번째 LangGraph 실습
- 교안: 21차시_첫번째LangGraph실습.pptx
- State = MiniState. 테스트에서는 그래프가 아니라 지역 변수 state 를 고친다.
- Node = retrieve, generate, verify, bump. fallback 은 정의만 하고 이 테스트에서 호출하지 않는다.
- reducer = log 규칙만 정의해 둔다. 함수를 직접 부르면 이어 붙이지 않고 return 값 그대로 보인다.
- Edge / compile / invoke = 없다.
- 테스트가 손으로 밟는 순서
- init → retrieve → (documents 를 2건으로 바꿈) → generate → (answer 를 부실로 바꿈) → verify → bump
함수와 클래스로 나누어 읽습니다.
MiniState: 이름 그대로 한 가지 일만 합니다. 본문 주석을 같이 읽습니다.init: 노드가 아님. 첫 State를 만든다. 읽기: 인자 question 채우는 칸: question, documents, answer, grade, retries, logretrieve: 노드 retrieve 읽기: question 쓰기: documents, loggenerate: 노드 generate 읽기: documents, retries 쓰기: answer, logverify: 노드 verify 읽기: answer 쓰기: grade, logbump: 노드 bump 읽기: retries 쓰기: retries, logfallback: 노드 fallback 읽기: 없음 쓰기: answer, grade, log
파일 맨 아래 실행 결과 샘플 주석은 실행되지 않습니다. 화면 출력은 다음 절에 다시 적어 두었습니다.