LangGraph 설계 패턴과 프로덕션 전략: 노드 설계부터 멀티 에이전트까지

· 12분 읽기 Threads Disquiet
목차
2 / 2
  1. 1. LangGraph 핵심 컴포넌트 가이드: StateGraph부터 Checkpointer까지
  2. 2. LangGraph 설계 패턴과 프로덕션 전략: 노드 설계부터 멀티 에이전트까지 현재

LangGraph 노드 설계 5단계 방법론, 그래프 구조 패턴 4가지, 멀티 에이전트 오케스트레이션 3가지, 에러 핸들링 4단계 전략, 그리고 프로덕션 체크리스트까지.
LLM 오케스트레이션의 복잡도가 임계점을 넘는 순간, 그래프 기반 설계가 필요해진다.

LangGraph 실전 설계 패턴과 프로덕션 운영

이 글은 LangGraph 시리즈의 두 번째이자 마지막 글이다. 첫 번째 글에서 LangChain 생태계의 위치, StateGraph, Node, Edge, State/Reducer, Checkpointer, interrupt 등 기본 컴포넌트를 다뤘다. 이 글에서는 그 컴포넌트들을 조합하여 프로덕션 수준의 LangGraph 시스템을 설계하는 패턴과 운영 전략을 정리한다.

LangGraph design pattern, multi-agent orchestration, subgraph factory, durable execution, production deployment 같은 키워드로 검색했을 때 실질적으로 도움이 되는 내용을 목표로 했다. 코드 예시는 모두 TypeScript(@langchain/langgraph) 기준이다.

LangGraph를 선택한 다음의 진짜 문제

첫 번째 글에서 LangGraph를 선택하는 근거와 대안 프레임워크와의 비교를 다뤘다. 이 글은 LangGraph를 도입하기로 결정한 이후의 이야기다.

컴포넌트를 알고 있는 것과 잘 조합하는 것은 다른 문제다. StateGraph, Node, Edge, Checkpointer를 이해했더라도, 실제 시스템을 설계하면 새로운 복잡도가 나타난다. 노드 하나에 어디까지 책임을 부여할 것인가, 서브그래프 간 state가 충돌하면 어떻게 격리할 것인가, 에이전트가 늘어날 때 오케스트레이션 구조를 어떻게 잡을 것인가. 이런 설계 판단이 시스템의 테스트 용이성, 장애 복원력, 확장성을 결정한다.

참고로, 상태 머신 기반 흐름 제어가 필요하지만 LLM 특화 기능(Checkpointer, interrupt, reducer 등)이 불필요한 경우에는 XState 같은 범용 상태 머신 라이브러리가 더 가벼운 선택지다. 장시간 실행 워크플로우의 내구성(durable execution)만 필요하다면 Temporal이 프로덕션에서 더 성숙한 솔루션이다. LangGraph의 가치는 이런 범용 도구들이 제공하지 않는 LLM 오케스트레이션 특화 기능—state reducer, 도구 호출 자동 복구, HITL interrupt, LangChain 생태계 통합—에 있다.

노드 설계 원칙: 5단계 방법론

LangGraph에서 노드는 그래프의 실행 단위이며, state를 받아 partial update를 반환하는 함수다. 노드를 어떻게 설계하느냐가 시스템의 테스트 용이성, 재사용성, 장애 복원력을 결정한다.

워크플로우에서 노드로

노드 설계는 다음 5단계를 따른다.

  1. 워크플로우를 이산적 단계(각각 독립적으로 분리된 단위)로 분해한다.
  2. 각 단계의 유형을 식별한다 — LLM 호출인지, 데이터 변환인지, 외부 API 액션인지, 사용자 입력 대기인지.
  3. 노드 간 공유 데이터를 기반으로 State 스키마를 설계한다.
  4. 각 단계를 노드 함수로 구현한다.
  5. 노드를 엣지로 연결한다.

핵심은 2단계에서의 유형 식별이다. LLM 호출 노드와 데이터 변환 노드, 부수효과 노드를 분리하면 테스트와 재시도 전략이 완전히 달라진다.

단일 책임과 분리 기준

기본 원칙은 한 노드에 한 가지 역할을 부여하는 것이다. 다만 두 연산이 항상 함께 실행되고 중간 상태를 외부에 노출할 필요가 없다면 복합 노드도 허용된다. 분리가 반드시 필요한 지점은 interrupt가 필요한 곳, retry 정책이 다른 곳, 조건부 분기가 있는 곳이다.

입력만으로 결과가 정해지는 노드와 외부에 영향을 주는 노드

노드는 크게 두 종류로 나뉜다. 입력만으로 결과가 결정되는 노드(순수 함수 노드)DB 쓰기, API 호출 등 외부 시스템에 영향을 주는 노드(부수효과 노드)다. 이 구분은 함수형 프로그래밍에서 온 개념이지만, LangGraph에서는 실용적 이유로 더 중요해진다.

// 입력만으로 결과가 결정되는 노드: 테스트 쉬움, 재실행해도 안전
const transform = async (state: State) => {
  return { processed: state.raw.toUpperCase() };
};

// 외부에 영향을 주는 노드: 반드시 격리
const saveToDb = async (state: State) => {
  await db.upsert(state.record); // upsert로 중복 실행에도 안전
  return { saved: true };
};

외부 영향을 주는 노드를 격리하는 이유는 세 가지다. 첫째, interrupt() resume 시 이전 코드가 재실행되므로 DB 쓰기나 API 호출이 중복 실행될 수 있다. 둘째, RetryPolicy 적용 시 재시도해도 안전해야 한다. 셋째, 테스트에서 외부 의존성을 mock으로 교체하기 쉬워진다.

그래프 구조 패턴 4가지

LangGraph 그래프는 크게 네 가지 구조 패턴으로 분류된다.

선형 파이프라인

가장 단순한 패턴으로, 순차적 변환 체인이다. ETL이나 단순 프롬프트 체이닝에 적합하다.

graph LR
  START --> extract --> transform --> load --> END
const graph = new StateGraph(StateAnnotation)
  .addNode("extract", extract)
  .addNode("transform", transform)
  .addNode("load", load)
  .addEdge(START, "extract")
  .addEdge("extract", "transform")
  .addEdge("transform", "load")
  .addEdge("load", END)
  .compile();

분기-합류 (Branch-Merge)

분류 후 서로 다른 경로로 처리하고, 최종적으로 합류한다. 조건부 엣지로 분기점을 정의하고, 여러 경로가 하나의 노드로 모인다.

graph LR
    START --> classify
    classify -->|weather| weather_handler
    classify -->|general| general_handler
    weather_handler --> respond
    general_handler --> respond
    respond --> END

루프/검증 패턴

결과가 기준을 충족할 때까지 반복 실행한다. LLM 출력의 품질을 검증하고 미달 시 재생성하는 패턴에서 가장 많이 사용된다.

graph LR
  START --> generate --> check
  check -- "score < 0.8" --> revise --> generate
  check -- "score >= 0.8" --> accept --> END
const validate = async (state: State) => {
  if (state.qualityScore < 0.8) return "revise";
  return "accept";
};

builder.addConditionalEdges("check", validate, ["revise", "accept"]);
builder.addEdge("revise", "generate");
builder.addEdge("generate", "check");
builder.addEdge("accept", END);

무한 루프 방지를 위해 END로 가는 경로를 반드시 확보하고, recursionLimit을 이중 안전장치로 설정해야 한다.

서브그래프

독립적으로 테스트하고 재사용할 수 있는 워크플로우 단위로 분리한다. 컴파일된 서브그래프를 addNode로 메인 그래프에 편입시킨다.

graph LR
  START --> pre_process --> sub_workflow --> post_process --> END
  subgraph sub_workflow [sub_workflow]
    direction LR
    s_START[START] --> process --> s_END[END]
  end
const sub = new StateGraph(SubStateAnnotation)
  .addNode("process", processFn)
  .addEdge(START, "process")
  .addEdge("process", END)
  .compile();

mainBuilder.addNode("sub_workflow", sub);

서브그래프의 checkpointer scoping은 용도에 따라 달라진다. checkpointer: false로 설정하면 interrupt는 불가하지만 병렬 인스턴스가 충돌 없이 동작한다. 기본값(생략)은 interrupt를 지원하되 멀티턴 메모리는 유지하지 않는다. checkpointer: true는 멀티턴 메모리를 유지하지만 namespace 충돌에 주의해야 한다.

신뢰성 확보: 멱등성, 에러 핸들링, 테스트

Durable Execution: 멱등성과 결정적 재실행

장시간 실행되는 에이전트에서 신뢰성을 보장하는 핵심 메커니즘이 durable execution이다. LangGraph는 매 superstep(병렬 실행 가능한 노드들이 동시에 실행되는 단위, 1편 Pregel과 Superstep 참고) 완료 후 state를 checkpoint에 저장하며, 장애 발생 시 마지막 checkpoint에서 재개한다. 처음부터 다시 실행하지 않는다.

이 모델이 올바르게 동작하려면 노드 내부의 부수효과가 멱등(idempotent)이어야 한다.

상황멱등성 확보 방법
DB 쓰기INSERT 대신 UPSERT 사용
API 호출idempotency key 전달
파일 생성덮어쓰기 또는 존재 여부 체크
interrupt 이전 코드resume마다 재실행됨 — 부수효과 배치 금지

단일 pod에서 짧은 요청-응답 사이클만 처리하는 시스템이라면, durable execution은 오버엔지니어링일 수 있다. 멱등성 확보와 checkpointer 설정의 부담이 실제 장애 복구 이점보다 클 수 있기 때문이다. 에이전트 실행이 수 분 이상 걸리거나, 다중 pod 배포 환경이거나, interrupt로 사용자 승인을 기다리는 구간이 있을 때 도입을 검토한다.

RetryPolicy를 결합하면 transient 에러에 대한 자동 복구까지 가능하다.

builder.addNode("external_api", callApi, {
  retryPolicy: {
    maxAttempts: 3,
    retryOn: (e: any) => e.status === 429 || e.status >= 500,
  },
});

에러 핸들링 4단계 전략

에러를 단일 try-catch로 처리하는 것은 LLM 시스템에서 통하지 않는다. 에러 유형별로 해결 주체가 다르기 때문이다.

단계에러 유형해결 주체전략
1Transient (네트워크, rate limit)시스템retryPolicy: { maxAttempts: 3 }
2LLM-recoverable (도구 실패)LLMToolNode가 에러를 tool message로 반환
3User-fixable (정보 누락)사용자interrupt({ message: ... })
4Unexpected개발자throw로 버블업

1단계는 RetryPolicy로 자동 재시도한다. 2단계는 ToolNode가 도구 실행 실패를 에러가 아닌 tool message로 LLM에 전달하여, LLM이 다른 도구를 시도하거나 파라미터를 수정할 기회를 준다. 3단계는 interrupt로 사용자에게 추가 정보를 요청한다. 4단계는 예측하지 못한 에러로, 그래프 밖으로 전파하여 개발자가 대응한다.

이 4단계를 의식적으로 분리하면 “모든 에러에 retry를 걸고, 그래도 안 되면 사용자에게 떠넘기는” 패턴을 피할 수 있다.

테스트 전략

노드 설계를 단일 책임 원칙에 따르면 테스트가 자연스럽게 분리된다.

단위 테스트는 노드 함수를 직접 호출한다. 노드는 (state) => partial update 시그니처를 가진 순수 함수이므로, state 객체를 만들어 넘기면 프레임워크 의존성 없이 테스트할 수 있다.

// 노드 함수를 직접 테스트 — 프레임워크 불필요
const result = await transformNode({
  rawData: mockInput,
  transformedData: [],
});
expect(result.transformedData).toHaveLength(3);

통합 테스트compile()invoke() 또는 stream()으로 전체 플로우를 검증한다. 특정 분기가 올바르게 선택되는지, 루프가 기대한 횟수 안에 종료하는지를 확인한다.

// 전체 그래프 플로우 검증
const graph = builder.compile();
const result = await graph.invoke(
  { messages: [{ role: "user", content: "테스트 입력" }] },
  { recursionLimit: 10 }
);
expect(result.messages.at(-1)?.content).toContain("기대 출력");

디버깅 시에는 streamMode: "debug"를 사용하면 superstep 단위로 실행 과정을 추적할 수 있다. 어떤 노드가 어떤 순서로 실행되었고, state가 어떻게 변했는지를 관찰할 수 있다.

// superstep 단위로 실행 과정 추적
for await (const [mode, chunk] of await graph.stream(
  { messages: [{ role: "user", content: "디버그 입력" }] },
  { streamMode: ["debug"] }
)) {
  console.log(`[${chunk.step}] ${chunk.payload?.node}`, chunk.payload);
}

여기까지 단일 그래프 안에서의 설계였다. 노드를 어떻게 나누고, 어떤 구조 패턴으로 엮고, 에러를 어떻게 처리하는지를 다뤘다. 이제 그래프 자체를 여러 개 조합하는 멀티 에이전트 패턴을 다룬다. 단일 그래프의 복잡도가 관리 가능한 수준이라면 아래 섹션은 나중에 읽어도 된다.

멀티 에이전트: 오케스트레이션에서 MCP 통합까지

에이전트가 하나일 때는 단일 그래프로 충분하다. 에이전트가 여러 개 필요해지는 순간 오케스트레이션 패턴이 필요하다. LangGraph에서는 세 가지 패턴이 주로 사용된다. 에이전트 간 통신 프로토콜까지 고려한다면 A2A Protocol도 함께 살펴볼 만하다.

에이전트가 2개 이하이고 역할 간 상호작용이 단순하다면, 서브그래프를 직접 addNode로 연결하는 것으로 충분하다. 오케스트레이션 패턴은 에이전트가 3개 이상이거나, 동적 라우팅/피드백 루프가 필요할 때 도입한다.

Orchestrator-Worker (Send 기반)

중앙 오케스트레이터가 작업을 분배하고, 워커들이 병렬로 실행한 뒤 결과를 합산한다.

const assignWorkers = (state: State) => {
  return state.sections.map(
    (section) => new Send("worker", { section })
  );
};

const worker = async (state: { section: Section }) => {
  const result = await llm.invoke([
    { role: "system", content: "Write a section..." },
    { role: "user", content: state.section.description },
  ]);
  return { completedSections: [result.content] };
};

Send를 사용하면 런타임에 병렬 개수가 결정된다. 결과를 받는 state 필드에는 반드시 reducer를 설정해야 한다. 그렇지 않으면 마지막 워커의 결과만 남는다.

Supervisor 패턴

중앙 감독자(supervisor)가 각 전문 에이전트에게 작업을 라우팅하고, 결과를 받아 다음 행동을 결정한다.

graph TD
    supervisor -->|code task| developer
    supervisor -->|design task| designer
    supervisor -->|review task| reviewer
    developer --> supervisor
    designer --> supervisor
    reviewer --> supervisor
    supervisor -->|done| END

Orchestrator-Worker와의 차이는 감독자가 결과를 평가하고 추가 라우팅을 결정한다는 점이다. 반복적인 피드백 루프가 필요한 시스템에 적합하다.

Hierarchical Teams

서브그래프 자체가 하나의 팀이 된다. 팀 내부에 supervisor와 workers가 있고, 팀 간에는 또 다른 상위 오케스트레이터가 존재한다. 대규모 멀티 에이전트 시스템에서 관심사를 계층적으로 분리할 때 사용한다.

graph LR
    top_supervisor[Top Supervisor]
    top_supervisor --> team_a
    top_supervisor --> team_b

    subgraph team_a [Research Team]
        direction LR
        sup_a[Supervisor A] --> searcher
        sup_a --> analyst
        searcher --> sup_a
        analyst --> sup_a
    end

    subgraph team_b [Writing Team]
        direction LR
        sup_b[Supervisor B] --> writer
        sup_b --> editor
        writer --> sup_b
        editor --> sup_b
    end

    team_a --> top_supervisor
    team_b --> top_supervisor

각 팀은 독립된 서브그래프로 컴파일되므로, 팀 단위로 테스트하고 배포할 수 있다. 상위 오케스트레이터는 팀의 내부 구조를 알 필요 없이, 입력을 전달하고 결과만 받는다. Supervisor 패턴과의 차이는 라우팅 대상이 개별 에이전트가 아니라 팀(서브그래프)이라는 점이다. 팀 내부의 복잡도가 올라가도 상위 오케스트레이터의 로직은 변하지 않는다.

서브그래프 팩토리 패턴으로 에이전트 표준화하기

멀티 에이전트 시스템에서 역할별 에이전트(분석, 설계, 구현, 검증 등)를 서브그래프로 구현할 때, 각 서브그래프가 제각각의 구조를 가지면 유지보수가 급격히 어려워진다. 이 문제를 해결하는 것이 고정 노드 구성 서브그래프 팩토리 패턴이다.

모든 에이전트 서브그래프가 동일한 노드 시퀀스를 따르되, 각 노드의 내부 동작만 에이전트별로 달라진다.

노드역할구현 포인트
context상위 문서/이전 산출물 로드, 컨텍스트 윈도우 관리요약 필요 시 손실 검증 루프
executeLLM 호출, 도구 사용(function call)system prompt + tools를 에이전트별 설정
validationLLM 출력의 품질/정합성 검증미달 시 execute로 재생성 루프
interrupt사용자 리뷰/승인 대기interrupt() + Command resume
output산출물 생성파일 쓰기 또는 state 업데이트

팩토리 함수로 일괄 생성하면 새로운 에이전트 역할을 추가할 때 prompt와 tools만 정의하면 된다.

function createAgentSubgraph(config: {
  agentName: string;
  systemPrompt: string;
  tools: StructuredTool[];
}): CompiledStateGraph {
  const builder = new StateGraph(AgentStateAnnotation)
    .addNode("context", createContextNode(config.agentName))
    .addNode("execute", createExecuteNode(
      config.systemPrompt, config.tools
    ))
    .addNode("validate", createValidationNode())
    .addNode("interrupt", createInterruptNode())
    .addNode("output", createOutputNode(config.agentName))
    .addEdge(START, "context")
    .addEdge("context", "execute")
    .addConditionalEdges("execute", checkTerminal, [
      "validate", END,
    ])
    .addConditionalEdges("validate", checkQuality, [
      "execute", "interrupt",
    ])
    .addEdge("interrupt", "output")
    .addEdge("output", END);

  return builder.compile();
}

에이전트가 3개 이하이고 각각의 노드 구성이 다르다면, 팩토리 없이 개별 서브그래프를 직접 구성해도 충분하다. 팩토리 패턴은 에이전트가 4개 이상으로 늘어나거나, 동일한 검증-재생성 루프를 반복 구현하고 있다는 신호가 보일 때 도입한다.

이 패턴의 장점은 세 가지다. 첫째, 에이전트 간 구조가 동일하므로 디버깅과 모니터링이 일관된다. 둘째, 검증-재생성 루프, interrupt 포인트 같은 공통 관심사를 한 번만 구현한다. 셋째, 새 역할을 추가할 때 팩토리에 설정만 전달하면 된다.

const analyzerGraph = createAgentSubgraph({
  agentName: "analyzer",
  systemPrompt: ANALYZER_PROMPT,
  tools: analyzerTools,
});
const designerGraph = createAgentSubgraph({
  agentName: "designer",
  systemPrompt: DESIGNER_PROMPT,
  tools: designerTools,
});

GAIA 프로젝트의 SubAgentFactory와 MetaGPT의 역할 체인이 이 패턴의 대표적인 참조 구현이다.

Prebuilt Agent와 MCP 도구 통합

핵심 설계 패턴과 별개로, LangGraph는 StateGraph를 직접 구성하지 않고도 에이전트를 빠르게 만들 수 있는 도구를 제공한다. createReactAgent는 LLM + 도구 조합의 ReAct 에이전트를 한 줄로 생성한다.

import { createReactAgent } from "@langchain/langgraph/prebuilt";
import { ChatAnthropic } from "@langchain/anthropic";

const agent = createReactAgent({
  llm: new ChatAnthropic({ model: "claude-sonnet-4-6-20250514" }),
  tools: [searchTool, calculatorTool],
  checkpointSaver: new MemorySaver(),
});

도구 실행 전 사용자 승인이 필요하면 interruptBefore: ["tools"] 옵션을 추가한다. ToolNode는 도구 호출 실패를 에러로 던지는 대신 tool message로 반환하여 LLM이 스스로 복구를 시도할 수 있게 한다.

MCP Tool Integration

@langchain/mcp-adapters를 사용하면 MCP 서버의 도구를 LangGraph 에이전트에 직접 연결할 수 있다.

import { MultiServerMCPClient } from "@langchain/mcp-adapters";

const mcpClient = new MultiServerMCPClient({
  mcpServers: {
    atlassian: {
      transport: "stdio",
      command: "npx",
      args: ["-y", "@anthropic/mcp-server-atlassian"],
      env: { ATLASSIAN_API_KEY: process.env.ATLASSIAN_API_KEY },
    },
  },
});

const tools = await mcpClient.getTools();
const agent = createReactAgent({ llm: model, tools });

MCP 도구는 표준 LangChain 도구와 동일하게 동작하므로, 기존 도구와 혼합하여 사용할 수 있다. 에이전트 간 통신까지 필요한 경우에는 A2A Protocol이 MCP와 다른 레이어에서 그 역할을 담당한다.

컨테이너 환경에서의 상태 관리

Sticky Session과 Stateless Pod

구성Sticky Session조건
단일 Pod불필요기본 구성
다중 Pod + PostgresSaver불필요DB에서 state 복원
다중 Pod + InMemorySaver필수메모리에만 state 존재
스트리밍 중권장SSE 연결 유지 필요

PostgresSaver를 사용하면 매 superstep마다 state를 DB에 쓰고, 다음 invoke 시 DB에서 읽는다. thread_idcheckpoint_id로 정확한 상태를 복원하므로, 어떤 pod이 요청을 받아도 동일하게 동작한다. 완전 stateless pod 구성이 가능해진다.

스트리밍 시에는 주의가 필요하다. SSE 연결 중에 pod이 바뀌면 스트림이 끊기므로, 스트리밍 세션 동안만 sticky session을 유지하거나 클라이언트 측 재연결 로직을 구현해야 한다.

Store도 동일한 원칙이 적용된다. InMemoryStore는 pod 간 공유가 불가능하므로 sticky session이 필수이며, 프로덕션에서는 PostgreSQL이나 Redis 같은 외부 Store를 사용해야 한다.

LangGraph Platform 아키텍처

공식 프로덕션 배포 구조는 API Server(stateless) - Queue - Worker(stateless) - PostgreSQL(state)로 구성된다. API Server가 요청을 수신하고, Queue Worker가 그래프를 실행하며, 모든 state는 PostgreSQL에 저장된다. 사용자가 응답 전에 연속 메시지를 보내는 double texting 상황도 이 구조에서 처리된다.

프로덕션 체크리스트와 참조 사례

지금까지 다룬 설계 패턴을 실제로 적용한 오픈소스 프로젝트들과, 배포 전 빠뜨리기 쉬운 점검 항목을 정리한다.

참조 사례

아래 프로젝트들은 각각 다른 설계 과제를 해결한다. 서브그래프 표준화, 에이전트 간 통신, 프로덕션 배포, RAG 파이프라인 등 자신의 시스템에서 해결해야 할 문제와 가장 가까운 프로젝트를 참조하면 된다.

설계 과제참조 프로젝트핵심 포인트
서브그래프 표준화GAIASubAgentFactory로 모든 서브그래프 동일 노드 시퀀스
에이전트 간 통신MetaGPT역할 체인, 구조화된 문서로 에이전트 간 메시지 전달
아티팩트 생성LangChain Deep AgentsFilesystemMiddleware로 파일 기반 아티팩트 관리
클라우드 배포AWS Multi-Agent on LangGraphSupervisor 패턴, Bedrock 연동, 프로덕션 인프라 구성
토폴로지 최적화G-Designer (논문)GNN으로 멀티에이전트 통신 구조 자동 설계
RAG 파이프라인LangGraph RAG Tutorial쿼리 재작성, 다중 소스 선택, 검증 루프를 노드로 분리

프로덕션 체크리스트

배포 전 확인해야 할 항목을 정리했다.

상태 관리

  • MemorySaver 대신 PostgresSaver 사용
  • 모든 invoke에 thread_id 전달
  • 분산 배포 시 Store도 외부 저장소 사용
  • 서브그래프 병렬 실행 시 namespace 격리 확인

안전장치

  • 무한 루프 방지: END로 가는 조건부 경로 + recursionLimit 설정
  • START는 진입 전용 — 되돌아갈 수 없음을 설계에 반영
  • invoke 입력으로는 new Command({ resume }) 만 유효

멱등성

  • 부수효과 노드는 멱등(upsert) 연산 사용
  • interrupt 이전 코드에 부수효과 배치 금지
  • compile() 후에만 invoke() 호출 가능

관측성

  • LangSmith 트레이싱 연동으로 노드별 실행 시간, 토큰 사용량, 에러 추적
  • 스트리밍 모드 선택: 용도에 따라 적절한 모드를 조합한다
streamMode용도언제 사용
values매 superstep 후 전체 state상태 변화 추적, 디버깅
updates노드별 state 변경분만프로덕션 실시간 모니터링
messagesLLM 토큰 단위 스트리밍챗 UI, 타이핑 효과
debugsuperstep 단위 전체 실행 정보개발/디버깅 전용
custom노드 내부에서 직접 emit진행률 표시, 중간 결과 알림

복수 모드를 동시에 사용할 수 있다: streamMode: ["updates", "messages"]

이 체크리스트의 각 항목은 실제로 빠뜨렸을 때 디버깅이 어려운 문제를 일으킨다. 특히 reducer 누락(병렬 노드에서 마지막 결과만 남음), interrupt 이전 부수효과(resume 시 중복 실행), namespace 미격리(서브그래프 간 state 충돌)는 증상만 보면 원인을 찾기 힘든 대표적인 문제다.

마무리: SDK 차이와 적용 범위

Python vs TypeScript 주요 차이

LangGraph는 Python과 TypeScript 양쪽을 지원하지만, 동일한 개념이 다른 문법으로 표현된다. Python 문서를 보고 TypeScript로 옮길 때 주의할 점을 정리했다.

구분PythonTypeScript
State 정의TypedDict + AnnotatedAnnotation.Root({})
Reduceroperator.add(a, b) => a.concat(b)
Decorator@task, @entrypointtask("name", fn), entrypoint({}, fn)
Config 접근config: RunnableConfigconfig: LangGraphRunnableConfig
Command 노드목적지 자동 추론addNode("name", fn, { ends: [...] }) 명시 필요
RetryPolicyRetryPolicy 클래스{ retryPolicy: { maxAttempts } } 인라인

가장 자주 빠뜨리는 차이는 Command 노드의 ends 명시다. Python에서는 자동 추론되지만 TypeScript에서는 addNode의 세 번째 인자로 가능한 목적지를 반드시 명시해야 한다.

적용 범위와 한계

이 글에서 다룬 패턴들은 LangGraph @langchain/langgraph 1.x 기준이며, 프레임워크 업데이트에 따라 API가 변경될 수 있다.

LangGraph가 적합한 경우: 분기 2개 이상, 검증 루프, 상태 영속화, HITL이 필요한 복잡한 LLM 워크플로우. 멀티 에이전트 시스템에서 역할 분리와 오케스트레이션이 필요한 경우.

LangGraph가 과한 경우: 단일 프롬프트 체이닝, 도구 하나를 호출하는 수준의 에이전트, 상태 영속화가 필요 없는 일회성 작업. 이런 경우 LangChain 단독 사용이나 Vercel AI SDK가 더 적합하다.

이 글이 다루지 않는 것: LangGraph Platform(Cloud/Self-hosted)의 상세 배포 구성, LangSmith를 활용한 트레이싱/평가 파이프라인, Agentic RAG의 구체적 구현(쿼리 재작성, 다중 소스 선택, 검색 품질 검증 루프 등—참조 사례 표에 링크를 포함했다). 이들은 각각 별도의 주제로 다룰 필요가 있다.

2 / 2
  1. 1. LangGraph 핵심 컴포넌트 가이드: StateGraph부터 Checkpointer까지
  2. 2. LangGraph 설계 패턴과 프로덕션 전략: 노드 설계부터 멀티 에이전트까지 현재

이어서 읽기