LangGraph: 상태 그래프(Stateful Graphs)와 지속 실행(Durable Execution)
LangGraph는 2026년 저수준 상태 오케스트레이션(stateful orchestration)의 참조 구현입니다. 에이전트(Agent)는 상태 머신(state machine)이며, 노드(node)는 함수, 간선(edge)은 전이(transition), 상태는 불변(immutable)이고 매 단계가 끝날 때마다 체크포인트(checkpoint)됩니다. 어떤 실패가 발생하더라도 멈춘 지점 그대로 다시 이어서 실행할 수 있습니다.
유형: Learn + Build
언어: Python (stdlib)
선수 학습: Phase 14 · 01 (에이전트 루프), Phase 14 · 12 (워크플로 패턴; Workflow Patterns)
소요 시간: 약 75분
학습 목표
- LangGraph의 핵심 모델을 설명합니다. 불변 상태, 함수 노드, 조건부 간선(conditional edge), 단계 이후 체크포인트를 가진 상태 머신이 그 핵심입니다.
- 공식 문서가 강조하는 네 가지 기능을 이야기할 수 있습니다. 지속 실행(durable execution), 스트리밍(streaming), 휴먼 인 더 루프(human-in-the-loop), 포괄적 메모리(comprehensive memory)가 그것입니다.
- LangGraph가 지원하는 세 가지 오케스트레이션 토폴로지를 설명합니다. 수퍼바이저(supervisor), 동등 노드 방식(peer-to-peer; swarm), 계층형(hierarchical; nested subgraphs) 구조가 있습니다.
- 불변 상태, 조건부 간선, 체크포인트와 재개(checkpoint/resume) 주기를 갖춘 표준 라이브러리 기반 상태 그래프를 직접 구현합니다.
문제
에이전트와 워크플로는 같은 문제를 공유합니다. 40단계짜리 실행이 38단계에서 실패하면, 처음부터 다시 시작하는 것이 아니라 38단계에서 이어서 재개하고 싶습니다. 그런데 상태 모델이 이류 시민(second-class)으로 취급되는 라이브러리에서는, 운영자가 매번 새 실행을 가정한 라이브러리 주변에 재시도 로직을 억지로 덧붙여야 하는 상황에 놓입니다.
이에 대한 LangGraph의 설계 답변은 다음과 같습니다. 상태는 일급(first-class) 타입 객체로 다루고, 상태 변경은 명시적으로 일어나며, 모든 노드 실행 뒤에 체크포인트가 영구 저장됩니다. 재개는 load_state(session_id) 호출 한 번으로 끝납니다.
개념
그래프
그래프는 다음과 같은 요소로 정의됩니다.
- 상태 타입(State type). 모든 노드가 읽고 변경하는, 타입이 지정된 딕셔너리(typed dict) 또는 Pydantic 모델(model)입니다.
- 노드(Nodes).
(state) -> state_update 형태의 순수 함수입니다. 업데이트 결과는 노드가 반환한 뒤 상태에 병합됩니다.
- 간선(Edges). 노드 사이의 직접 전이 또는 조건부 전이입니다.
- 진입과 종료(Entry and exit).
START와 END라는 센티넬 노드(sentinel node)가 그래프의 경계를 표시합니다.
예를 들어 classify, refund, bug, sales, done 노드를 가진 에이전트는 라우팅(routing) 워크플로를 그래프로 표현한 것입니다.
지속 실행(Durable execution)
각 노드가 반환되면 런타임(runtime)은 상태를 직렬화(serialize)하고 체크포인터(checkpointer)에 기록합니다. 체크포인터의 저장소로는 SQLite, Postgres, Redis, 사용자 정의 백엔드(custom backend) 등 어느 것이든 사용할 수 있습니다. N단계에서 실패하면 런타임은 resume(session_id)를 호출해, 멈췄을 때와 정확히 동일한 상태에서 N+1단계부터 이어서 실행합니다.
LangGraph 공식 문서는 이런 특성이 실제로 중요했던 프로덕션 사용 사례로 Klarna, Uber, J.P. Morgan을 명시합니다. 여기서의 핵심 주장은 그래프 모양 그 자체가 아닙니다. 그래프 모양에 체크포인팅(checkpointing)이 결합되어, 장애 복구 비용이 매우 낮아진다는 점이 핵심입니다.
스트리밍(Streaming)
모든 노드는 부분 출력(partial output)을 양보(yield)할 수 있습니다. 그래프는 노드별 변경분(delta) 이벤트를 호출자(caller)에게 스트리밍해 주므로, 사용자 인터페이스(UI)는 그래프가 실행되는 도중에도 실시간으로 업데이트됩니다.
휴먼 인 더 루프(Human-in-the-loop)
노드와 노드 사이에서 상태를 검사하고 수정할 수 있습니다. 구현 방식은 다음과 같습니다. 중요한 노드 앞에서 일단 멈추고, 사람에게 현재 상태를 보여 주고, 사람이 가한 수정을 받아들인 뒤 재개합니다. 상태가 이미 직렬화되어 있기 때문에, 체크포인터가 이 흐름을 매우 손쉽게 만들어 줍니다.
메모리
단기 메모리(short-term memory)는 한 번의 실행 안에서 유지되는 대화 이력으로, 상태(state) 안에 저장됩니다. 장기 메모리(long-term memory)는 여러 실행에 걸쳐 지속되며, 체크포인터와 별도의 장기 저장소(long-term store)를 함께 사용해 보존합니다. LangGraph는 도구(tools)를 통해 Mem0 같은 외부 메모리 시스템이나 사용자 정의 메모리 시스템과 연동할 수 있습니다.
세 가지 토폴로지
- 수퍼바이저(Supervisor). 중앙 라우터 역할을 하는 LLM이 전문 하위 에이전트(specialist subagent)들에게 작업을 배분합니다.
langgraph-supervisor 패키지의 create_supervisor()로 구현할 수 있습니다. 다만 LangChain 팀은 2026년 시점에서, 컨텍스트(context)를 더 정밀하게 통제하기 위해 도구 호출(tool call)을 통해 직접 구현하는 방식을 권장합니다.
- 스웜/동등 노드 방식(Swarm / peer-to-peer). 에이전트들이 공유하는 도구 표면(shared tool surface)을 통해 서로에게 직접 작업을 인계(handoff)합니다. 중앙 라우터가 따로 없습니다.
- 계층형(Hierarchical). 수퍼바이저가 하위 수퍼바이저(sub-supervisor)들을 관리하는 구조입니다. 중첩된 하위 그래프(nested subgraph)로 구현됩니다.
이 패턴이 잘못되는 지점
- 너무 작은 체크포인트. 대화 턴(conversation turn)만 체크포인트로 남기면, 도구 상태(tool state)와 메모리 쓰기(memory write)는 복구할 수 없습니다. 전체 상태를 빠짐없이 직렬화해야 합니다.
- 비결정적 노드(Non-deterministic nodes). 재개는 같은 입력에 대해 노드가 같은 상태 업데이트를 만들어 낸다는 가정을 따릅니다. 난수 시드(random seed), 현재 시각(wall-clock), 외부 API 호출 결과 같은 비결정적 요소는 모두 상태에 함께 기록해 두어야 합니다.
- 조건부 간선의 과다 사용. 모든 간선이 조건부인 그래프는 사람이 추론할 수 없는 상태 머신이 되어 버립니다. 군데군데 분기를 두는 선형 체인(linear chain) 구조를 우선하세요.
만들어보기
code/main.py는 표준 라이브러리만으로 동작하는 상태 그래프를 구현합니다.
State — messages, step, route, output, human_approval 필드를 가진 타입 딕셔너리(typed dict)입니다.
Node — 상태를 받아 업데이트 딕셔너리(update dict)를 반환하는 호출 가능 객체(callable)입니다.
StateGraph — 노드, 간선, 조건부 간선, 실행(run), 재개(resume) 기능을 묶어 둔 그래프 클래스입니다.
SQLiteCheckpointer (이 예제에서는 인메모리 가짜 구현) — 모든 노드 실행 뒤에 상태를 직렬화하고, load(session_id)로 복원합니다.
- 데모 그래프 —
classify -> branch(refund / bug / sales) -> human gate -> send 흐름을 가집니다.
실행 방법은 다음과 같습니다.
python3 code/main.py
실행 추적을 보면, 첫 번째 실행이 human_gate에서 정지하고 상태가 보존된 뒤, 사람 승인을 받아 재개되면서 최종 출력을 만들어 내는 과정을 따라갈 수 있습니다.
사용해보기
- LangGraph — 참조 구현이자 프로덕션에 바로 쓸 수 있는 라이브러리입니다.
create_react_agent나 create_supervisor를 사용하거나, 직접 그래프를 정의해 사용할 수 있습니다.
- AutoGen v0.4 (14강) — 높은 동시성(concurrency) 시나리오에 적합한 액터 모델(actor model) 기반 대안입니다.
- Claude Agent SDK (17강) — 세션 저장소(session store)가 내장된 관리형 실행 환경(managed harness)입니다.
- 사용자 정의(Custom) — 상태의 형태(state shape)나 체크포인터(checkpointer) 백엔드를 직접 정밀하게 통제해야 할 때 선택합니다.
산출물 만들기
outputs/skill-state-graph.md는 어떤 대상 런타임에서도 체크포인팅과 재개가 연결된 LangGraph 형태의 상태 그래프를 만들어 주는 스킬(skill)입니다.
연습문제
- (쉬움) 분류 신뢰도(classification confidence)가 임계값 아래일 때
classify에서 end로 향하는 조건부 간선을 추가하세요. 그런 다음, 사람이 route를 수동으로 설정한 뒤 실행을 재개해 보세요.
- (중간) SQLite를 흉내 낸 가짜 구현을 실제 SQLite 체크포인터로 교체하세요. 단계별 직렬화 오버헤드(serialization overhead)를 측정해 보세요.
- (중간) 병렬 간선(parallel edge)을 구현하세요. 두 노드가 동시에 실행되고, 사용자 정의 리듀서(reducer)로 결과를 병합하도록 만드세요. 이때 불변 상태(immutable state)는 어떤 이점을 가져다 주나요?
- (어려움)
langgraph-supervisor 참조 문서를 읽어 보세요. 이 강의의 작은 예제를 create_supervisor 기반으로 옮기고, 두 구현의 실행 추적(trace) 형태를 비교해 보세요.
- (어려움) 스트리밍을 추가하세요. 각 노드가 실행되는 도중에 부분 상태를 양보(yield)하도록 만들고, 변경분(delta)이 도착하는 대로 출력하세요.
핵심 용어
| 용어 | 흔한 설명 | 실제 의미 |
|---|
| 상태 그래프(State graph) | "상태 머신으로서의 에이전트" | 타입이 지정된 상태 + 노드 + 간선 + 리듀서(reducer)의 결합. |
| 체크포인터(Checkpointer) | "지속성 백엔드" | 모든 노드 실행 뒤에 상태를 직렬화해, 재개를 가능하게 만드는 컴포넌트. |
| 리듀서(Reducer) | "상태 병합기" | 현재 상태와 노드가 만들어 낸 업데이트를 결합하는 함수. |
| 조건부 간선(Conditional edge) | "분기" | 상태에 따라 결정되는 함수로 선택되는 간선. |
| 하위 그래프(Subgraph) | "중첩 그래프" | 다른 그래프 안에서 하나의 노드처럼 사용되는 그래프. |
| 지속 실행(Durable execution) | "실패 지점에서 재개" | 마지막으로 성공한 노드 직후의 정확한 상태에서 다시 시작하는 실행 방식. |
| 수퍼바이저(Supervisor) | "라우터 LLM" | 전문 하위 에이전트들에게 작업을 분배하는 중앙 디스패처(dispatcher). |
| 스웜(Swarm) | "P2P 에이전트" | 중앙 라우터 없이, 공유 도구를 통해 서로 작업을 인계(handoff)하는 에이전트 무리. |
더 읽을거리