키비타스 아겐티아 3권 — 구축

22 · Vol 3

제21장. 9-node GoT 아키텍처 설계

*이 장을 다 읽고 나면 알게 될 것: Graph of Thought가 무엇인지, 왜 9개 노드가 실무의 최적점인지, 각 노드가 어떤 역할을 하는지, LangGraph로 어떻게 구현하는지, 그리고 모든 과제에 9개 노드가 다 필요하지는 않다는 것*

도입: 화이트보드 앞의 세 시간

2024년 여름이었다. 나의 시스템의 회의실 화이트보드 앞에서 나는 세 시간째 서 있었다. 마커 세 개가 바닥났다. 보드에는 박스와 화살표가 어지럽게 그려져 있었다. KVIC — 한국 가치사슬 인텔리전스 시스템 — 의 아키텍처를 설계하고 있었다.

문제는 이랬다. 사용자가 "현대자동차의 2차 전지 공급망 리스크를 분석해줘"라고 요청하면, 시스템은 무엇을 해야 하는가.

단순하지 않다. 먼저 질문을 이해해야 한다. 현대자동차인지, 현대차인지, Hyundai Motor인지 구분해야 한다. 그 다음 관련 문서를 찾아야 한다. 찾은 문서에서 패턴을 발견해야 한다. 발견한 패턴에서 의미를 추론해야 한다. 추론한 결과를 보고서로 만들어야 한다. 보고서가 맞는지 검증해야 한다. 틀렸으면 고쳐야 한다. 그리고 이 모든 과정의 흐름을 누군가 제어해야 한다.

화이트보드의 박스를 세어 보니 아홉 개였다.

처음에는 우연이라고 생각했다. 그러나 이후 여러 프로젝트를 거치면서 같은 패턴이 반복되었다. 단순한 과제는 세 개나 네 개로 충분했다. 복잡한 과제도 아홉 개면 충분했다. 열 개를 넘기면 오히려 복잡성이 관리 불가능해졌다. 아홉 개가 실무에서의 최적점이었다.

이 장에서는 그 아홉 개 노드를 하나씩 풀어본다.

21.1 Graph of Thought — 사고의 그래프

먼저 용어를 정리한다.

Chain of Thought라는 말은 이미 익숙할 것이다. LLM에게 "단계적으로 생각해봐"라고 시키면 답의 품질이 올라가는 현상이다. 2022년 구글 연구팀이 발표한 이래로, 거의 모든 프롬프트 엔지니어링의 기본 기법이 되었다. 한 줄로 생각하게 하는 것. 이것이 Chain of Thought다.

그런데 현실의 사고는 한 줄로 흐르지 않는다.

우리가 복잡한 문제를 풀 때, 머릿속에서 일어나는 일을 떠올려보자. 여러 정보를 동시에 탐색한다. 어떤 경로는 막다른 길이라 되돌아온다. 어떤 결과는 다른 경로의 입력이 된다. 검증하다가 앞의 단계로 돌아간다. 이것은 직선이 아니라 그래프다.

2023년 ETH 취리히 연구팀이 Graph of Thought(GoT)라는 개념을 제안했다. 사고를 직선이 아니라 그래프로 모델링하는 것이다. 노드는 사고의 단계이고, 엣지는 단계 사이의 흐름이다. 직선도 가능하고, 순환도 가능하고, 분기도 가능하다.

이것이 왜 중요한가.

Agent 시스템을 만들 때, 우리는 LLM 하나에 모든 것을 시키는 유혹에 빠진다. "이 문서를 읽고, 분석하고, 보고서를 써줘." 간단해 보인다. 그러나 결과는 대개 실망스럽다. LLM은 만능이 아니다. 한 번에 너무 많은 일을 시키면 각각의 품질이 떨어진다. 마치 한 사람에게 기획, 조사, 분석, 작성, 검토를 동시에 시키는 것과 같다.

좋은 팀은 역할을 나눈다. 좋은 Agent 시스템도 마찬가지다. 각 노드가 한 가지 역할에 집중하고, 그 결과를 다음 노드에 넘긴다. 이것이 GoT 아키텍처의 핵심이다.

잠시 멈추고 생각해보자
당신이 복잡한 보고서를 쓸 때, 머릿속에서 몇 단계를 거치는가? 그 단계들은 항상 같은 순서로 진행되는가, 아니면 왔다 갔다 하는가?

21.2 왜 아홉 개인가

솔직히 말하면, 처음부터 아홉 개를 목표로 한 것은 아니다.

나의 시스템의 첫 번째 시스템인 Article Lingua는 네 개 노드로 시작했다. 입력을 받고, 관련 자료를 검색하고, 결과를 생성하고, 흐름을 제어한다. 이 네 개면 영어 학습 콘텐츠를 만드는 데 충분했다.

두 번째 시스템인 SAF(Strategic Analysis Framework)는 일곱 개가 필요했다. 전략 분석은 더 복잡하다. 검색만으로는 안 되고 분석이 필요했다. 생성만으로는 안 되고 검증이 필요했다. 그래서 분석 노드와 검증 노드가 추가되었다.

세 번째 시스템인 KVIC에 이르러 아홉 개가 완성되었다. 가치사슬 인텔리전스는 가장 복잡한 과제였다. 추론이 필요했다. 수정 루프가 필요했다. 그래서 추론 노드와 수정 노드가 추가되었다.

정리하면 이렇다.

| 시스템 | 과제 복잡도 | 노드 수 | 추가된 노드 |

|---|---|---|---|

| Article Lingua | 낮음 | 4 | Intake, Retriever, Generator, Router |

| SAF | 중간 | 7 | + Planner, Analyzer, Critic |

| KVIC | 높음 | 9 | + Reasoner, Refiner |

패턴이 보인다. 과제의 복잡도가 올라갈수록 노드가 추가된다. 그러나 아홉 개를 넘기지는 않았다. 왜 그런가.

두 가지 이유가 있다.

첫째, 인간의 인지 한계와 비슷하다. 조지 밀러의 유명한 연구가 있다. 인간이 작업 기억에서 동시에 다룰 수 있는 정보 단위는 7±2개라는 것이다. 아홉 개는 그 상한이다. 설계자가 아홉 개 이상의 노드를 동시에 머릿속에 두고 관리하기 어렵다. 디버깅도 어렵다. 모니터링도 어렵다.

둘째, 실무적으로 아홉 개면 대부분의 과제를 커버한다. 입력, 계획, 검색, 분석, 추론, 생성, 검증, 수정, 흐름 제어. 이 아홉 가지 역할 바깥에 있는 역할을 나는 아직 만나지 못했다. 물론 미래에 만날 수 있다. 그러나 지금까지의 경험으로는 아홉 개면 충분하다.

물론 이것은 내 경험에서 나온 것이다. 다른 사람의 경험은 다를 수 있다. 어떤 팀은 여섯 개면 충분할 것이고, 어떤 팀은 열두 개가 필요할 수도 있다. 그러나 출발점으로 아홉 개를 제안한다. 줄이는 것은 쉽다. 늘리는 것도 어렵지 않다.

21.3 아홉 개 노드의 역할

이제 각 노드를 하나씩 본다.

1. Intake — 입력 수집과 정제

모든 것은 입력에서 시작한다. 사용자의 질문, 업로드된 문서, API에서 들어온 데이터. 이것들을 받아서 정제하는 것이 Intake 노드의 역할이다.

정제란 무엇인가. 사용자가 "현대차 배터리 리스크"라고 입력하면, 이것을 시스템이 이해할 수 있는 형태로 바꾸는 것이다. "현대차"를 "현대자동차"로 정규화한다. "배터리"를 "2차 전지"와 연결한다. "리스크"를 구체적 분석 유형으로 분류한다.

좋은 Intake는 나머지 여덟 개 노드의 품질을 결정한다. 쓰레기가 들어가면 쓰레기가 나온다. 이 오래된 원칙은 Agent 시스템에서도 변하지 않는다.

2. Planner — 작업 계획 수립

Intake가 정제한 입력을 받아서, 이 과제를 어떻게 풀 것인지 계획을 세운다. 어떤 정보를 먼저 찾을지, 어떤 분석을 할지, 어떤 순서로 진행할지를 정한다.

단순한 질문이면 계획도 단순하다. "삼성전자 시가총액 알려줘"라면 검색 한 번이면 된다. 그러나 "반도체 후공정 패키징 시장에서 한국 기업의 경쟁력 변화를 분석해줘"라면 계획이 복잡해진다. 어떤 기업을 다룰지, 어떤 기간을 볼지, 어떤 지표를 쓸지를 먼저 정해야 한다.

Planner가 없으면 Agent는 닥치는 대로 일한다. 그러면 결과가 산만해진다. 좋은 보고서와 나쁜 보고서의 차이는 대개 분석의 깊이가 아니라 계획의 명확함에 있다.

3. Retriever — 관련 지식 검색

RAG의 핵심이다. Planner가 세운 계획에 따라 관련 문서, 데이터, 지식을 검색한다. 벡터 DB에서 의미 검색을 하고, 그래프 DB에서 관계를 탐색하고, 외부 API에서 실시간 데이터를 가져온다.

나의 시스템에서는 pgvector로 의미 검색을 하고, ArangoDB로 기업 간 공급망 관계를 탐색한다. 이 두 가지를 결합하는 것을 GraphRAG라고 부른다. 단순한 키워드 매칭이 아니라, 의미와 관계를 동시에 고려하는 검색이다.

4. Analyzer — 데이터 분석과 패턴 발견

Retriever가 가져온 데이터에서 패턴을 발견한다. 숫자를 계산하고, 추세를 파악하고, 비교를 수행한다. "지난 3년간 HBM 매출이 연평균 45% 성장했다"는 것은 분석의 결과다.

Analyzer는 종종 코드를 실행한다. Python 스크립트를 돌려 통계를 내거나, 차트를 만들거나, 회귀 분석을 수행한다. LLM만으로는 정확한 계산이 어렵다. 그래서 Analyzer 노드에는 코드 실행 도구가 붙는다.

5. Reasoner — 추론과 논리 전개

Analyzer가 발견한 패턴에서 의미를 도출한다. "HBM 매출이 성장한다"는 사실이고, "그러므로 SK하이닉스의 패키징 투자가 늘어날 것이다"는 추론이다. 사실에서 해석으로, 해석에서 전망으로 넘어가는 것이 Reasoner의 역할이다.

이 노드는 가장 LLM에 의존적이다. 추론은 결국 언어 모델이 가장 잘하는 일이기 때문이다. 그러나 동시에 가장 위험한 노드이기도 하다. 환각이 가장 많이 발생하는 곳이다. 그래서 Reasoner의 출력은 반드시 Critic이 검증해야 한다.

6. Generator — 출력 생성

최종 결과물을 만든다. 보고서, 요약, 대시보드, 이메일, 슬라이드. 사용자가 원하는 형식으로 출력을 생성한다.

Generator는 단순히 텍스트를 뱉는 것이 아니다. 구조화된 출력을 만든다. 제목, 소제목, 표, 차트, 핵심 요약, 부록. 이 모든 것을 사용자의 요구에 맞게 배치한다.

7. Critic — 품질 검증과 자기 비판

생성된 결과물을 검증한다. 사실이 맞는지, 논리가 일관되는지, 빠진 것은 없는지, 편향은 없는지를 점검한다.

Critic이 없는 Agent는 위험하다. 자기가 만든 결과를 검증하지 않기 때문이다. 인간도 마찬가지다. 보고서를 쓰고 나서 스스로 검토하지 않는 사람의 보고서는 대개 구멍이 있다.

Critic 노드를 구현하는 방법은 여러 가지다. 같은 LLM에게 "이 답을 검증해줘"라고 시킬 수도 있고, 다른 LLM에게 시킬 수도 있고, 규칙 기반으로 검증할 수도 있다. 나의 시스템에서는 세 가지를 결합한다. 규칙 기반으로 형식을 검증하고, LLM으로 논리를 검증하고, 외부 데이터로 사실을 검증한다.

8. Refiner — 수정과 개선

Critic이 발견한 문제를 수정한다. 사실 오류를 고치고, 논리 비약을 메우고, 누락된 정보를 추가한다.

Refiner와 Critic은 순환 관계다. Critic이 문제를 발견하면 Refiner가 고치고, Refiner가 고친 것을 Critic이 다시 검증한다. 이 루프가 Agent 시스템의 품질을 끌어올린다. 몇 바퀴를 돌릴지는 설계자가 정한다. 보통 2~3회면 충분하다. 너무 많이 돌리면 비용만 올라가고 품질은 수렴한다.

9. Router — 흐름 제어와 조건 분기

마지막으로 Router다. 이 노드는 다른 노드들과 성격이 다르다. 데이터를 처리하는 것이 아니라, 데이터의 흐름을 결정한다. 어떤 노드로 보낼지, 언제 순환할지, 언제 종료할지를 판단한다.

Router는 교통경찰이다. 교차로에 서서 차량을 보내는 것처럼, 상태를 보고 다음 행선지를 정한다. "이 입력은 단순하니 Planner를 건너뛰고 바로 Retriever로", "Critic 점수가 70점 미만이니 Refiner로 돌려보내", "세 번째 수정이니 이제 Generator로 최종 출력" 같은 판단을 한다.

잠시 멈추고 생각해보자
당신의 일상 업무에서, 이 아홉 가지 역할 중 가장 자주 건너뛰는 것은 무엇인가? Critic인가, Planner인가? 건너뛰었을 때 결과가 어떻게 달라졌는가?

21.4 세 가지 흐름 패턴

아홉 개 노드를 어떻게 연결하느냐에 따라 세 가지 패턴이 나온다.

선형 패턴. 가장 단순하다. 노드가 일렬로 이어진다. Intake → Planner → Retriever → Generator. Article Lingua의 기본 흐름이 이것이다. 단순한 과제에 적합하다. 장점은 예측 가능성이다. 어디서 무엇이 일어나는지 명확하다. 단점은 유연성이 없다는 것이다. 중간에 문제가 발생해도 되돌아갈 수 없다.

순환 패턴. Generator → Critic → Refiner → Generator로 다시 돌아온다. 자기 검증 루프다. SAF의 핵심 패턴이었다. 전략 보고서는 한 번에 잘 나오지 않는다. 두세 번 수정해야 쓸 만해진다. 순환 패턴이 이것을 자동화한다. 장점은 품질 향상이다. 단점은 비용과 시간이다. 루프를 돌 때마다 LLM 호출이 발생한다.

조건 분기 패턴. Router가 상태를 보고 다른 경로로 보낸다. "이 질문은 분석이 필요하니 Analyzer로", "이 질문은 단순 검색이니 Retriever로 바로" 같은 판단이다. KVIC의 핵심 패턴이다. 다양한 유형의 질문을 하나의 시스템이 처리해야 할 때 필요하다. 장점은 효율성이다. 단순한 질문에 복잡한 파이프라인을 태우지 않는다. 단점은 설계 복잡도다. 분기 조건을 잘못 정하면 엉뚱한 경로로 빠진다.

실전에서는 세 패턴을 섞어 쓴다. 전체 흐름은 조건 분기로 설계하되, 특정 구간에서는 순환 패턴을 넣고, 단순한 경로는 선형으로 처리한다.

[Intake] → [Router] ─── 단순 질문 ──→ [Retriever] → [Generator] → 출력

└── 복잡한 질문 ──→ [Planner] → [Retriever] → [Analyzer]
→ [Reasoner] → [Generator] → [Critic]
↑ │
│ 점수 미달 │
[Refiner] ←────────┘
점수 통과 → 출력

이 그림이 KVIC의 실제 아키텍처를 단순화한 것이다.

21.5 LangGraph로 구현하기

개념은 충분하다. 이제 코드를 본다.

LangGraph는 이 아홉 개 노드를 그래프로 연결하는 데 가장 적합한 프레임워크다. 19장에서 다룬 RAPIDS + MECE + 4D Ready로 과제를 발굴했다면, 이 장에서는 그 과제를 실제 그래프로 옮긴다.

LangGraph의 핵심 개념은 세 가지다. StateGraph(그래프 전체), Node(각 처리 단계), Edge(노드 사이의 연결). 이것만 알면 아홉 개 노드 아키텍처를 구현할 수 있다.

아래는 9-node GoT의 골격 코드다. 실제 프로덕션 코드가 아니라 구조를 이해하기 위한 개념적 코드다.

from langgraph.graph import StateGraph, END
from typing import TypedDict, Literal

# 상태 정의 — 모든 노드가 공유하는 정보
class AgentState(TypedDict):
query: str # 사용자 입력
plan: list[str] # Planner가 만든 계획
documents: list[str] # Retriever가 찾은 문서
analysis: str # Analyzer의 분석 결과
reasoning: str # Reasoner의 추론 결과
output: str # Generator의 출력
critic_score: float # Critic의 평가 점수
critic_feedback: str # Critic의 피드백
revision_count: int # 수정 횟수
complexity: str # Router가 판단한 복잡도

# 각 노드는 함수로 정의한다
def intake_node(state: AgentState) -> AgentState:
"""입력을 정제한다"""
# 정규화, 엔터티 추출, 의도 파악
return {"query": normalize(state["query"])}

def router_node(state: AgentState) -> AgentState:
"""복잡도를 판단한다"""
complexity = assess_complexity(state["query"])
return {"complexity": complexity}

def planner_node(state: AgentState) -> AgentState:
"""작업 계획을 세운다"""
plan = llm.invoke(f"이 질문에 답하기 위한 단계를 설계하라: {state['query']}")
return {"plan": plan}

def retriever_node(state: AgentState) -> AgentState:
"""관련 지식을 검색한다"""
docs = vector_db.search(state["query"])
graph_docs = graph_db.traverse(state["query"])
return {"documents": docs + graph_docs}

def analyzer_node(state: AgentState) -> AgentState:
"""데이터를 분석한다"""
analysis = llm.invoke(f"다음 문서를 분석하라: {state['documents']}")
return {"analysis": analysis}

def reasoner_node(state: AgentState) -> AgentState:
"""분석 결과에서 추론한다"""
reasoning = llm.invoke(
f"분석: {state['analysis']}\n이로부터 도출할 수 있는 결론은?"
)
return {"reasoning": reasoning}

def generator_node(state: AgentState) -> AgentState:
"""최종 출력을 생성한다"""
output = llm.invoke(
f"다음을 바탕으로 보고서를 작성하라:\n{state['reasoning']}"
)
return {"output": output}

def critic_node(state: AgentState) -> AgentState:
"""출력을 검증한다"""
evaluation = llm.invoke(f"다음 보고서를 검증하라: {state['output']}")
return {"critic_score": evaluation.score,
"critic_feedback": evaluation.feedback}

def refiner_node(state: AgentState) -> AgentState:
"""피드백을 반영해 수정한다"""
refined = llm.invoke(
f"원본: {state['output']}\n피드백: {state['critic_feedback']}\n수정하라."
)
return {"output": refined,
"revision_count": state["revision_count"] + 1}

이것이 아홉 개 노드의 뼈대다. 각 함수는 상태를 받고, 자기 역할을 수행하고, 상태를 갱신한다. 단순하다.

이제 이 노드들을 그래프로 연결한다.

# 그래프를 만든다
graph = StateGraph(AgentState)

# 노드를 등록한다
graph.add_node("intake", intake_node)
graph.add_node("router", router_node)
graph.add_node("planner", planner_node)
graph.add_node("retriever", retriever_node)
graph.add_node("analyzer", analyzer_node)
graph.add_node("reasoner", reasoner_node)
graph.add_node("generator", generator_node)
graph.add_node("critic", critic_node)
graph.add_node("refiner", refiner_node)

# 시작점
graph.set_entry_point("intake")

# 엣지를 연결한다
graph.add_edge("intake", "router")

# 조건 분기 — Router가 복잡도에 따라 경로를 나눈다
def route_by_complexity(state: AgentState) -> Literal["planner", "retriever"]:
if state["complexity"] == "complex":
return "planner"
return "retriever"

graph.add_conditional_edges("router", route_by_complexity)

# 선형 연결
graph.add_edge("planner", "retriever")
graph.add_edge("retriever", "analyzer")
graph.add_edge("analyzer", "reasoner")
graph.add_edge("reasoner", "generator")
graph.add_edge("generator", "critic")

# 순환 — Critic 점수에 따라 Refiner로 보내거나 종료한다
def route_after_critic(state: AgentState) -> Literal["refiner", "__end__"]:
if state["critic_score"] < 0.7 and state["revision_count"] < 3:
return "refiner"
return "__end__"

graph.add_conditional_edges("critic", route_after_critic)
graph.add_edge("refiner", "generator")

# 컴파일
app = graph.compile()

이 코드에서 세 가지 패턴이 다 보인다. `route_by_complexity`가 조건 분기다. `planner → retriever → ... → generator`가 선형이다. `generator → critic → refiner → generator`가 순환이다.

핵심은 이것이다. 각 노드의 내부 구현은 나중에 얼마든지 바꿀 수 있다. Retriever를 pgvector에서 Pinecone으로 바꿔도, 그래프 구조는 그대로다. Reasoner의 LLM을 GPT-4o에서 Qwen3으로 바꿔도, 그래프 구조는 그대로다. 아키텍처와 구현을 분리하는 것. 이것이 GoT 설계의 가장 큰 장점이다.

잠시 멈추고 생각해보자
위 코드에서 단순한 질문은 Planner를 건너뛴다. 당신의 업무에서, 어떤 요청이 들어왔을 때 계획 없이 바로 실행해도 되는 것과, 반드시 계획을 먼저 세워야 하는 것을 어떻게 구분하는가?

21.6 설계의 원칙 — 최소한에서 시작하라

아홉 개 노드를 다 보았다. 그러나 한 가지를 분명히 해야 한다.

모든 과제가 아홉 개 노드를 다 쓸 필요는 없다.

이것은 매우 중요한 원칙이다. 초보 설계자가 가장 자주 빠지는 함정이 과잉 설계다. 멋진 아키텍처를 먼저 그리고, 거기에 과제를 끼워 맞추려 한다. 그러면 단순한 과제에 복잡한 시스템이 붙어서, 느리고, 비싸고, 디버깅하기 어려운 괴물이 탄생한다.

내가 나의 시스템에서 따르는 원칙은 세 가지다.

첫째, 최소한의 노드로 시작한다. 새 과제가 오면 먼저 세 개 노드로 시도한다. Intake, Retriever, Generator. 이것만으로 충분한지 본다. 부족하면 한 개씩 추가한다. 한 번에 아홉 개를 다 올리지 않는다.

둘째, 노드를 추가할 때마다 이유를 쓴다. "Critic을 추가한다. 이유: 현재 시스템이 환각을 자체 검출하지 못하기 때문." 이런 식이다. 이유 없이 노드를 추가하면 나중에 왜 이 노드가 있는지 아무도 모르게 된다.

셋째, 순환은 최후의 수단이다. 순환 패턴은 강력하지만 비용이 크다. LLM 호출이 배로 늘어난다. 지연 시간도 늘어난다. 그래서 선형과 조건 분기로 해결할 수 있으면 그렇게 한다. 순환은 정말 품질이 중요하고, 비용을 감당할 수 있을 때만 쓴다.

이 원칙을 표로 정리하면 이렇다.

| 과제 유형 | 권장 노드 수 | 필수 노드 | 선택 노드 |

|---|---|---|---|

| 단순 Q&A | 3~4 | Intake, Retriever, Generator | Router |

| 보고서 생성 | 5~6 | + Planner, Analyzer | Critic |

| 전략 분석 | 7~8 | + Reasoner, Critic | Refiner |

| 고신뢰 의사결정 | 9 | 전체 | — |

이 표가 절대적인 기준은 아니다. 참고점이다. 자기 과제에 맞게 조정해야 한다.

한 가지 더. 데이터 분석 기반 컨설팅을 하면서 배운 것이 있다. 좋은 프레임워크는 빈칸이 있어야 한다. 모든 칸을 다 채운 프레임워크는 경직되어 현실에 맞지 않는다. 아홉 개 노드도 마찬가지다. 이것은 빈칸이 있는 프레임워크다. 아홉 개 중 몇 개를 쓸지, 어떻게 연결할지, 순환을 몇 번 돌릴지는 과제가 결정한다. 프레임워크가 결정하는 것이 아니다.

21.7 그래프의 상태 관리

마지막으로 한 가지 기술적 주제를 짚고 넘어간다. 상태 관리다.

아홉 개 노드가 정보를 주고받으려면, 공유하는 상태가 필요하다. 위 코드의 `AgentState`가 그것이다. 모든 노드가 이 상태를 읽고 쓴다.

상태 설계에서 중요한 것은 세 가지다.

무엇을 상태에 넣을지 정한다. 모든 중간 결과를 다 넣을 수도 있고, 핵심만 넣을 수도 있다. 너무 많이 넣으면 상태가 비대해진다. 너무 적게 넣으면 노드 사이에 정보가 끊긴다. 적절한 균형이 필요하다.

상태의 버전을 관리한다. LangGraph는 체크포인터(Checkpointer) 기능을 제공한다. 각 노드가 실행될 때마다 상태의 스냅샷을 저장한다. 그래서 문제가 생기면 이전 상태로 돌아갈 수 있다. 디버깅에 필수적이다.

상태를 영속화한다. 메모리에만 두면 시스템이 재시작될 때 사라진다. PostgreSQL이나 Redis에 저장하면 대화가 이어진다. 24장에서 다룰 AgentOps와 연결되는 부분이다.

from langgraph.checkpoint.postgres import PostgresSaver

# 체크포인터 설정
checkpointer = PostgresSaver(conn_string="postgresql://...")

# 컴파일 시 체크포인터 연결
app = graph.compile(checkpointer=checkpointer)

# 실행 — thread_id로 대화를 구분한다
result = app.invoke(
{"query": "현대자동차 배터리 공급망 리스크 분석", "revision_count": 0},
config={"configurable": {"thread_id": "analysis-001"}}
)

이것으로 아홉 개 노드가 상태를 공유하고, 그 상태가 DB에 영속되며, 대화별로 분리된다. Agent 시스템의 기반이 완성된 것이다.

핵심 정리

Graph of Thought는 사고를 직선이 아니라 그래프로 모델링하는 방식이다. Chain of Thought가 한 줄로 생각하게 하는 것이라면, GoT는 분기하고, 순환하고, 합류하는 사고를 가능하게 한다.

9개 노드는 실무에서 발견된 최적점이다. Intake, Planner, Retriever, Analyzer, Reasoner, Generator, Critic, Refiner, Router. 이 아홉 가지 역할이 대부분의 복잡한 과제를 커버한다. 단순한 과제라면 3~4개로 충분하고, 복잡한 과제라도 9개면 충분하다.

세 가지 흐름 패턴이 있다. 선형, 순환, 조건 분기. 실전에서는 이 세 가지를 섞어 쓴다. LangGraph의 StateGraph, Node, Edge가 이 패턴을 코드로 옮기는 도구다.

설계의 핵심 원칙은 최소한에서 시작하는 것이다. 아홉 개를 다 올리지 않는다. 세 개에서 시작해서, 필요할 때 한 개씩 추가한다. 노드를 추가할 때마다 이유를 기록한다.

상태 관리는 노드 사이의 정보 공유를 가능하게 한다. 체크포인터로 버전을 관리하고, DB로 영속화한다. 이것이 운영 가능한 Agent 시스템의 기반이다.

반드시 답해봐야 할 질문 5가지

질문 1. 당신이 자동화하려는 과제를 이 아홉 개 노드에 매핑해보라. 어떤 노드가 필수이고, 어떤 노드는 필요 없는가? 그 판단의 근거는 무엇인가?

질문 2. Critic과 Refiner의 순환 루프에서, 몇 번까지 수정을 반복해야 하는가? 무한 반복을 막으려면 어떤 종료 조건이 필요한가?

질문 3. Router의 분기 조건은 누가 정하는가? LLM이 스스로 판단하게 할 것인가, 규칙으로 정할 것인가? 각각의 장단점은 무엇인가?

질문 4. 선형 패턴만으로 해결할 수 있는 과제와, 반드시 순환이 필요한 과제의 차이는 어디에 있는가? 당신의 업무에서 예를 들어보라.

질문 5. 아홉 개 노드 프레임워크에 빠진 역할이 있다고 생각하는가? 있다면 그것은 무엇이고, 기존 노드 중 어디에 가장 가까운가?

더 깊이 탐구하기

Besta et al., "Graph of Thoughts: Solving Elaborate Problems with Large Language Models" (2023). GoT 개념을 처음 제안한 ETH 취리히의 논문.

LangGraph 공식 문서 (https://langchain-ai.github.io/langgraph/). StateGraph, Node, Edge, Checkpointer의 상세 API와 튜토리얼.

조지 밀러, "The Magical Number Seven, Plus or Minus Two" (1956). 인간의 작업 기억 용량에 관한 고전적 논문. 노드 수의 상한을 이해하는 데 도움이 된다.

Harrison Chase, "Building Effective Agents with LangGraph" (2025). LangGraph 창시자의 실전 가이드. 멀티 노드 아키텍처 설계의 모범 사례를 다룬다.

나의 시스템 Composite AI Framework 백서 (2025). 9-node GoT 아키텍처의 실제 적용 사례와 설계 원칙을 다룬 내부 문서.

다음 장에서는 이 아홉 개 노드가 먹고 사는 먹이, 즉 데이터와 지식 구조를 다룬다. Graflo, OntoCast, GraphRAG. Agent가 똑똑해지려면 좋은 지식 구조가 먼저다.