본문 바로가기
AI Agent/Frameworks

LangGraph (1) - 왜 그래프인가

by AtoN 2024. 5. 10.

LangGraph 전체 구조와 실행 모델

LangGraph는 단순히 Node와 Edge를 연결하는 그래프 라이브러리가 아닙니다. 상태를 가진 장기 실행 workflow와 agent를 제어하기 위한 low-level orchestration framework이자 runtime입니다. 실행 상태를 저장했다가 이어 가는 persistence와 durable execution, streaming, human-in-the-loop 같은 기능이 이 실행 모델 위에서 제공되고, LangChain의 create_agent로 만든 표준 agent도 내부적으로 LangGraph runtime을 사용합니다.

이런 runtime이 필요한 이유는 모든 LLM 애플리케이션이 한 방향의 pipeline으로 끝나지는 않기 때문입니다. 입력을 prompt에 넣고 모델을 호출한 뒤 결과를 변환하는 작업이라면 단순한 Runnable 조합으로 충분합니다. 하지만 여러 단계가 하나의 상태를 공유하고, 결과에 따라 경로가 갈리거나 이전 단계로 돌아가며, 실행 중간에 멈췄다가 같은 상태에서 다시 이어야 한다면 데이터가 어디로 흐르는지만 아니라 실행 상태가 어떻게 변하는지까지 관리해야 합니다.

LangGraph는 이 실행 모델을 두 가지 API로 표현합니다. Graph API에서는 State, Node, Edge를 명시해 workflow의 구조와 상태 전이를 그래프로 정의합니다. 반면 Functional API는 @entrypoint와 @task를 사용하면서 if, for, 함수 호출 같은 일반적인 Python 제어문을 유지합니다. 표현 방식은 다르지만 둘은 같은 LangGraph runtime을 사용하며 persistence, streaming, human-in-the-loop 같은 핵심 기능을 공유합니다.

먼저 LangGraph가 어떤 문제를 해결하기 위해 필요한지와 하나의 실행이 상태를 갱신하며 진행되는 방식을 살펴봅니다. 이어서 Graph API의 State·Node·Edge와 Functional API가 같은 runtime을 어떻게 다르게 표현하는지 보고, checkpoint와 thread를 이용한 persistence, interrupt와 resume, short-term·long-term memory가 이 실행 모델에 어떻게 연결되는지까지 봅니다.


1장. LangGraph가 담당하는 범위

LangChain과 LangGraph를 같은 수준의 두 프레임워크로 놓으면 역할이 잘 보이지 않습니다. 현재 LangChain은 표준적인 agent loop를 빠르게 만드는 상위 API를 제공하고, LangGraph는 그보다 아래에서 실행 상태와 경로를 더 세밀하게 제어하는 런타임을 제공합니다.

LangChain create_agent
    표준 model ↔ tools 루프를 빠르게 구성
            │
            ▼
LangGraph runtime
    state / persistence / streaming / interrupt
    custom control flow / subgraph / durable execution

직접 LangGraph를 쓰는 경우도 LangChain 모델이나 도구를 반드시 써야 하는 것은 아닙니다. LangGraph는 LangChain 팀이 만들었지만 독립적으로 사용할 수 있는 실행 프레임워크입니다.

LangGraph 안에서도 작성 방식은 둘입니다.

Graph API
    State + Node + Edge를 명시
    흐름과 공유 상태가 설계의 중심일 때

Functional API
    @entrypoint + @task
    기존 Python if/for/function 구조를 유지하면서
    persistence와 interrupt 같은 런타임 기능을 붙일 때

둘은 서로 다른 런타임이 아니라 같은 기반을 공유합니다. 이 시리즈는 상태 병합과 분기 구조를 눈에 보이게 이해하기 위해 Graph API를 중심으로 설명하고, Functional API는 선택 기준을 함께 잡습니다.


2장. 파이프라인과 그래프의 차이

파이프라인은 데이터가 앞에서 뒤로 흐르는 구조입니다.

질문 → 검색 → 프롬프트 → 모델 → 답변

이 구조는 순서가 명확하고 각 단계의 입출력이 잘 정의되어 있을 때 좋습니다. 병렬 처리나 간단한 조건 분기도 Runnable 조합으로 표현할 수 있습니다.

그래프가 필요한 순간은 다음 실행 위치가 고정되지 않을 때입니다.

생성 → 평가
       ├─ 충분함 → 종료
       └─ 부족함 → 다시 생성

여기서는 평가의 결과가 다음 단계를 정합니다. 그리고 다시 생성이라는 뒤로 가는 화살표가 생깁니다.

또 실행 중 공유해야 하는 데이터도 늘어납니다.

질문
검색 결과
현재 초안
평가 점수
반복 횟수
승인 여부
도구 결과

이 정보를 각 함수 인자로 계속 이어 붙이는 대신 하나의 State로 두고 여러 단계가 읽고 갱신하게 하는 것이 LangGraph의 기본 방식입니다.


3장. 그래프가 필요한 실행 흐름

조건에 따라 경로가 달라진다

분류 결과에 따라 서로 다른 작업으로 보내는 흐름입니다.

요청
  ↓
분류
  ├─ 질문 → 답변
  ├─ 환불 → 환불 절차
  └─ 장애 → 기술 지원

조건 하나만 있다면 일반 코드나 RunnableBranch도 충분합니다. 경로가 많아지고 각 경로가 다시 합쳐지거나 반복되기 시작하면 그래프로 드러내는 편이 읽기 쉽습니다.

앞 단계로 되돌아간다

생성-평가-수정처럼 반복하는 흐름입니다.

generate → evaluate
   ▲          │
   └── retry ─┘

에이전트가 도구를 사용하는 루프도 같은 구조입니다. 모델이 tool call을 만들면 도구를 실행하고 결과를 다시 모델에 전달합니다.

여러 단계가 같은 상태를 공유한다

각 단계가 이전 단계의 출력 하나만 받는 것이 아니라 실행 전체의 상태를 함께 봐야 하는 경우입니다.

State
  ├─ messages
  ├─ draft
  ├─ score
  └─ iteration

Node는 필요한 값을 읽고 자신이 바꾼 부분만 반환합니다.

실행을 저장하고 이어야 한다

사람 승인을 기다리거나 긴 작업을 나중에 재개해야 한다면 현재 상태를 저장해야 합니다. LangGraph의 checkpointer는 그래프 상태를 단계별로 저장해 durable execution, memory between interactions, interrupt/resume 같은 기능의 기반이 됩니다.

이 네 상황은 서로 독립적이라기보다 자주 함께 나타납니다. 긴 에이전트 작업일수록 조건, 반복, 공유 상태, 지속성이 동시에 필요해집니다.


4장. State, Node, Edge

Graph API에서 가장 먼저 잡을 요소는 세 가지입니다.

요소 의미
State 현재 실행 상태를 표현하는 공유 데이터
Node State를 읽고 실제 작업을 수행하는 함수
Edge 다음에 어떤 Node를 실행할지 정하는 연결

공식 Graph API는 Node를 State -> Partial<State> 형태로 설명합니다. 즉 Node는 State를 입력으로 받고 전체 State를 새로 만드는 대신 갱신할 값만 반환합니다.

def evaluate(state: State):
    score = judge.invoke(state["draft"])
    return {"score": score}

Edge는 실행 순서를 정합니다.

builder.add_edge("generate", "evaluate")

조건에 따라 다음 위치가 달라지면 조건부 Edge를 사용합니다.

builder.add_conditional_edges("evaluate", route)

이 관계를 먼저 이해하면 LangGraph의 API가 훨씬 단순하게 보입니다. Node는 일을 하고, Edge는 다음 위치를 정하고, State는 그 사이에서 계속 이어지는 데이터입니다.


5장. 가장 작은 그래프

StateGraph는 현재 Graph API의 중심 클래스입니다.

from typing_extensions import TypedDict
from langgraph.graph import START, END, StateGraph

class State(TypedDict):
    text: str
    length: int


def measure(state: State):
    return {"length": len(state["text"])}

builder = StateGraph(State)
builder.add_node("measure", measure)
builder.add_edge(START, "measure")
builder.add_edge("measure", END)

graph = builder.compile()

실행합니다.

graph.invoke({"text": "hello", "length": 0})

START와 END는 실행의 시작과 끝을 나타내는 특수 노드입니다. StateGraph는 설계 단계의 빌더이고 compile() 결과가 실제 실행체입니다.

컴파일된 그래프는 invoke, stream, ainvoke, astream 같은 실행 방식을 제공합니다. LangChain의 Runnable을 배웠다면 이 부분은 낯설지 않습니다.


6장. 에이전트가 그래프인 이유

표준 도구 호출 에이전트를 흐름으로 펼치면 그래프 구조가 바로 보입니다.

사용자
  ↓
모델
  ├─ tool call 없음 → 종료
  │
  └─ tool call 있음
          ↓
        도구
          ↓
        모델

모델과 도구 사이에 반복이 있습니다. 모델 출력에 tool call이 있는지가 조건이고, 도구 결과가 다시 State의 메시지에 들어갑니다.

이 반복은 매우 일반적이어서 현재 LangChain의 create_agent()가 표준 형태를 만들어 줍니다.

from langchain.agents import create_agent

agent = create_agent(
    model="openai:gpt-5.5",
    tools=[search],
)

에이전트를 쓴다고 항상 StateGraph를 직접 작성해야 하는 것은 아닙니다. 표준 루프는 상위 API를 사용하고, 루프 규칙 자체를 바꿔야 할 때 그래프를 직접 그리는 것이 현재의 자연스러운 경계입니다.

초기 LangGraph의 create_react_agent()는 이 표준 루프를 제공한 대표적인 prebuilt API였습니다. 현재는 deprecated되어 같은 역할의 기본 진입점이 LangChain의 create_agent()로 이동했습니다.


7장. Graph API와 Functional API

Graph API와 Functional API는 같은 LangGraph runtime을 사용하는 두 작성 방식입니다.

Graph API는 State, Node, Edge를 명시적으로 드러냅니다. 여러 Node가 같은 상태를 공유하고, 조건 분기·병렬 합류·시각화가 중요할 때 잘 맞습니다.

Functional API는 @entrypoint와 @task를 사용해 기존 Python의 if, for, 함수 호출 구조를 최대한 유지합니다. 명시적인 State와 reducer를 설계하지 않고도 persistence, human-in-the-loop, streaming 같은 LangGraph 기능을 기존 절차형 코드에 붙이기 좋습니다.

두 API는 섞어 쓸 수도 있습니다. 이 시리즈는 상태 병합과 제어 흐름을 학습하기 위해 Graph API를 중심으로 진행합니다.


8장. 언제 LangGraph로 넘어갈까

그래프를 쓰는 것 자체가 목표가 되어서는 안 됩니다. 한 방향 체인으로 충분한 문제를 그래프로 만들면 코드만 늘어날 수 있습니다.

다음 질문으로 판단하면 좋습니다.

  • 이전 단계로 되돌아가는 반복이 핵심인가.
  • 여러 단계가 같은 State를 계속 수정하는가.
  • 실행 도중 사람이나 외부 이벤트를 기다려야 하는가.
  • 실행을 저장하고 나중에 같은 흐름을 이어야 하는가.
  • 분기와 합류 자체가 업무 규칙으로 중요하게 보이는가.

여러 항목이 맞는다면 그래프가 실행 구조를 더 잘 드러낼 가능성이 큽니다.

반대로 단순한 RAG 파이프라인처럼 입력 → 검색 → 프롬프트 → 모델로 끝나는 경우에는 LCEL이 더 간결할 수 있습니다.


9장. 정리

LangGraph의 핵심은 그래프 모양 자체가 아닙니다. 실행 상태와 다음 경로를 애플리케이션 코드의 명시적인 구조로 만든다는 점에 있습니다.

파이프라인에서는 순서가 중심이고, 그래프에서는 State가 변하면서 다음 Node가 달라질 수 있습니다. 이 차이가 반복, 도구 호출, 사람 승인, 재개 같은 에이전트 워크플로를 표현하기 쉽게 만듭니다.

다음 편에서는 State, Node, Edge를 실제 코드 수준에서 더 자세히 봅니다. 특히 Node가 State를 어떻게 갱신하고, 여러 Node가 같은 값을 수정할 때 reducer가 왜 필요한지가 핵심입니다.


참고자료

'AI Agent > Frameworks' 카테고리의 다른 글

LangGraph (2) - State, Node, Edge  (0) 2024.08.05
LangChain (4) - 조합하기  (0) 2024.05.30
LangChain (3) - 출력 구조  (0) 2024.03.31
LangChain (2) - 프롬프트 넣기  (0) 2024.02.22
LangChain (1) - 패키지 구조와 첫 호출  (0) 2024.01.18

댓글