
LangGraph로 견고한 AI 에이전트를 구축하는 5가지 디자인 패턴
요약
LangGraph를 활용하여 운영 환경에서도 안정적으로 작동하는 견고한 AI 에이전트를 구축하기 위한 5가지 디자인 패턴을 소개합니다. 상태 유지형(Stateful) 워크플로우와 체크포인터를 통한 회복성 확보 방법을 다룹니다.
핵심 포인트
- LangGraph를 이용한 복잡한 루프 및 조건 분기 워크플로우 구현
- StateGraph, Nodes, Edges를 활용한 그래프 기반 에이전트 모델링
- Checkpointer를 통한 상태 저장 및 장애 발생 시 복구 메커니즘
- Human-in-the-Loop 및 스트리밍 지원을 통한 사용자 경험 향상
LLM 앱 개발에 있어, LangChain이나 LangGraph를 사용한 AI 에이전트의 복잡한 워크플로우 구축 과정에서 "에이전트가 의도하지 않은 루프에 빠진다", "운영 환경에서 예기치 않은 에러로 정지한다", "상태 관리(State Management)가 너무 복잡해서 디버깅할 수 없다"와 같은 과제에 직면한 적이 없으신가요?
이 기사에서는 LangGraph를 활용하여, 운영 환경을 고려한 견고한 AI 에이전트를 구축하기 위한 구체적인 **디자인 패턴 (Design Pattern)**과 구현 사례를 최신 LangGraph v1.2.0의 지식과 함께 해설합니다. 글을 다 읽을 때쯤이면, 여러분의 AI 에이전트가 더욱 안정적이고 유지보수성이 높은 시스템으로 진화해 있을 것입니다.
LangGraph는 LangChain 생태계의 일부로 개발된, 상태 유지형 (Stateful) AI 에이전트를 구축하기 위한 저수준 오케스트레이션 (Orchestration) 프레임워크입니다. 기존 LangChain의 선형적인 체인 (Chain)으로는 구현하기 어려웠던 루프, 조건 분기, 동적인 실행 경로와 같은 복잡한 AI 워크플로우를 그래프 구조로 모델링함으로써 가능하게 합니다.
LangChain이 제공하는 고수준 추상화는 신속한 프로토타입 개발에 적합하지만, 더 복잡한 로직이나 인터랙티브한 동작, 영구적인 상태 관리가 필요한 경우 LangGraph는 그 진가를 발휘합니다.
LangGraph의 주요 장점은 다음과 같습니다.
복잡한 워크플로우 표현: 노드 (Node, 태스크나 액션)와 엣지 (Edge, 실행 플로우)를 정의함으로써, 인간의 개입이나 동적인 의사결정을 포함하는 표현력이 풍부한 에이전트 워크플로우를 구축할 수 있습니다. -
상태 유지형 (Stateful) 실행: 공유 상태 (State)를 통해 에이전트는 과거의 컨텍스트를 유지하며, 대화 이력이나 태스크 진행 상황에 기반하여 의사결정을 내릴 수 있습니다. -
영속성 및 회복성: 체크포인터 (Checkpointer)를 통해 각 노드 실행 후에 그래프의 상태가 저장되므로, 장애 발생 시의 재개나 Human-in-the-Loop (HITL) 워크플로우 구현이 용이합니다. -
스트리밍 대응: 퍼스트 클래스 스트리밍 지원을 통해 사용자 경험을 향상시키는 실시간 응답이 가능합니다.
LangGraph v1.2.0 (2024년 5월 기준)을 기반으로 그 핵심 개념을 이해해 봅시다.
StateGraph: 에이전트의 로직을 정의하는 주요 클래스로, 공유 상태의 스키마를 정의하고 노드와 엣지를 추가합니다. -
Nodes: 그래프 내의 개별 컴포넌트나 에이전트를 나타내는 Python 함수입니다. LLM 호출, 툴 호출, 커스텀 로직 등이 노드로 기능합니다. -
Edges: 노드 간의 연결을 정의하고 워크플로우의 진행 방식을 제어합니다. add_edge를 통한 직접 엣지와 add_conditional_edges를 통한 조건부 엣지가 있습니다. -
State: 애플리케이션의 현재 스냅샷을 나타내는 공유 데이터 구조입니다. TypedDict 등으로 정의되며, 모든 노드가 읽고 쓸 수 있습니다. Annotated와 operator.add를 조합함으로써 리스트 등의 상태를 누적적으로 업데이트할 수 있습니다. -
Checkpointer: 각 노드 실행 후에 그래프의 상태를 저장하여 영속성과 회복성을 실현합니다. -
RetryPolicy / TimeoutPolicy: 노드 실행 시의 에러 회복이나 타임아웃을 설정하여 운영 환경에서의 견고성을 높입니다.
먼저 LangGraph의 기본적인 사용법을 확인해 봅시다. 설치부터 심플한 LLM 호출, 그리고 툴 이용과 조건 분기를 포함하는 에이전트 예제를 통해 LangGraph의 핵심 기능을 파악합니다.
uv 또는 pip로 langgraph를 설치합니다.
uv add langgraph
# 또는
pip install langgraph
OpenAI 모델을 사용하기 위해 langchain-openai도 설치하고, OPENAI_API_KEY 환경 변수를 설정해 주세요.
이 예제에서는 사용자로부터 메시지를 받아 LLM에 전달하고 응답을 반환하는 가장 기본적인 AI 에이전트를 구축합니다. 상태의 정의, 노드의 추가, 엣지의 설정, 그리고 그래프의 컴파일과 실행 흐름을 이해합니다.
from langgraph.graph import StateGraph, START, END
from langchain_openai import ChatOpenAI
from typing import TypedDict, Annotated, List
...
더 실용적인 AI 에이전트로서, LLM이 외부 도구 (Tool)를 호출해야 하는지 여부를 판단하고, 필요에 따라 도구를 실행하는 예시입니다. 여기서 LangGraph의 중요한 기능인 **조건부 엣지 (Conditional Edges)**가 등장합니다.
from langgraph.graph import StateGraph, END, START
from typing import TypedDict, Annotated, List
from langchain_core.messages import BaseMessage, HumanMessage, AIMessage, ToolMessage
...
이 예시를 통해 LangGraph가 어떻게 동적인 워크플로우 (Workflow)를 구축하고, 도구 사용과 같은 복잡한 동작을 구현하는지 이해했을 것입니다.
실제 운영을 고려한 AI 에이전트에서는 단순히 기능하는 것을 넘어, 에러 내성 (Error tolerance), 유지보수성, 확장성이 요구됩니다. 여기에서는 LangGraph로 견고한 AI 에이전트를 구축하기 위한 5가지 주요 **디자인 패턴 (Design Patterns)**을 소개합니다.
사용자의 의도나 입력에 따라 적절한 도구, 서브 루프 (Sub-loop), 또는 특정 에이전트로 처리를 분배하는 패턴입니다. 복잡한 태스크를 모듈화하여 효율적으로 처리하기 위해 필수적입니다.
필요한 이유: 단일 LLM이나 에이전트로는 대응하기 어려운 다양한 사용자 요구에 대해, 적절한 전문 에이전트를 동적으로 선택함으로써 응답의 정확도와 효율성을 향상시킵니다.
구현 포인트:
- 조건부 엣지 (
add_conditional_edges)를 활용합니다. - 라우팅 (Routing)을 결정하는 노드 (라우터 노드)는 사용자의 쿼리와 사용 가능한 도구/에이전트 목록을 LLM에 전달하여 최적의 다음 단계를 결정하게 합니다.
- 라우터 노드의 출력에 따라 서로 다른 경로로 분기시킵니다.
코드 예시: 위의 도구 호출 에이전트의 should_continue 함수가 라우팅 패턴의 가장 단순한 형태입니다. 여기에서는 더 나아가 여러 도구 또는 서브 에이전트로의 라우팅을 고려합니다.
from langgraph.graph import StateGraph, END, START
from typing import TypedDict, Annotated, List, Literal
from langchain_core.messages import BaseMessage, HumanMessage, AIMessage, ToolMessage
...
에이전트가 자신의 출력이나 행동을 평가하고, 필요에 따라 수정 및 개선하기 위해 루프를 도는 패턴입니다. 특히 복잡한 태스크나 창의적인 태스크에서 더 고품질의 결과를 생성하는 데 유효합니다.
필요한 이유: LLM의 출력은 때때로 부정확하거나 지시사항에서 벗어날 수 있습니다. 자기 평가 (Self-evaluation) 프로세스를 포함함으로써 에이전트는 자신의 실수를 탐지하고 수정하는 능력을 갖추게 됩니다.
구현 포인트:
- '계획 (Plan)' 노드, '실행 (Execute)' 노드, '평가 (Reflection)' 노드의 세 가지 주요 노드로 구성됩니다.
- 평가 노드는 LLM이나 규칙 기반 (Rule-based) 로직을 사용하여 실행 결과의 품질이나 지시사항 준수 여부를 판단합니다.
- 평가 결과에 따라 다시 계획 노드로 돌아갈지, 아니면 종료할지를 조건부 엣지로 제어합니다.
코드 예시:
여기서는 "사용자의 질문에 대해 답변을 생성하고, 그 답변이 질문에 적절한지 여부를 다른 LLM (Reflection)이 평가한다"는 단순한 리플렉션 (Reflection) 패턴을 구현합니다.
from langgraph.graph import StateGraph, END, START
from typing import TypedDict, Annotated, List, Literal
from langchain_core.messages import BaseMessage, HumanMessage, AIMessage
...
실제 운영 환경에서는 API 속도 제한(Rate Limit), 네트워크 장애, LLM의 예기치 않은 출력 등 다양한 에러가 발생합니다. 이러한 에러에 견고하게 대응하기 위한 패턴입니다.
필요한 이유: 적절한 에러 핸들링 (Error Handling)이 없으면 에이전트가 중단되거나 사용자 경험이 저해됩니다. 시스템 전체의 신뢰성을 확보하기 위해 필수적입니다.
구현 포인트:
- RetryPolicy: LangGraph의 노드에 설정할 수 있는
RetryPolicy를 이용하여, 일시적인 에러에 대한 자동 재시도 (Retry)를 설정합니다. - TimeoutPolicy: 노드의 실행 시간이나 유휴 시간(Idle time)의 상한을 설정하여 행업(Hang-up)을 방지합니다.
- 전용 에러 핸들러 노드: 재시도가 모두 실패한 후나, 특정 치명적인 에러가 발생했을 때 실행되는 클린업(Cleanup), 알림(Alert), 폴백 (Fallback) 로직을 가진 노드를 정의합니다.
- LangSmith: 에러 발생 시의 트레이스 (Trace) 및 디버깅을 위해 LangSmith를 통합합니다.
코드 예시:
여기서는 RetryPolicy와 에러 발생 시 전용 에러 핸들러 노드로 폴백하는 메커니즘을 구현합니다.
from langgraph.graph import StateGraph, START, END
from typing import TypedDict, Annotated, List, Literal
from langchain_core.messages import BaseMessage, HumanMessage, AIMessage
...
여러 전문 에이전트가 각자의 전문 분야를 활용하여 협력하고, 복잡한 태스크를 해결하는 패턴입니다. 인간 사회의 분업과 유사합니다.
필요한 이유: 단일 에이전트로는 대응하기 어려운, 다양한 지식과 기술을 요하는 복잡한 태스크에 대해 각 에이전트가 전문성을 발휘함으로써 더 고품질의 효율적인 해결책을 만들어냅니다.
구현 포인트:
- 각 에이전트를 독립된 LangGraph의 서브루프 (Sub-loop) 또는 노드로 정의합니다.
- 에이전트 간의 커뮤니케이션은 공유 상태 (State)를 통해 이루어집니다.
- 코디네이터 에이전트 (Coordinator Agent, Router)가 태스크의 진행 상황에 따라 적절한 에이전트에게 처리를 위임합니다.
- LangGraph의
add_subgraph기능을 이용하여 그래프 안에 또 다른 그래프를 포함시키는 것도 가능합니다.
코드 예시:
여기서는 사용자의 질문에 대해 "웹 검색 담당 에이전트"와 "답변 생성 담당 에이전트"가 협력하는 심플한 멀티 에이전트 (Multi-agent) 시스템을 구현합니다.
from langgraph.graph import StateGraph, END, START
from typing import TypedDict, Annotated, List, Literal
from langchain_core.messages import BaseMessage, HumanMessage, AIMessage, ToolMessage
...
AI 에이전트가 확신이 없는 출력을 하거나 중요한 결정을 내릴 때, 인간의 개입을 포함하는 패턴입니다.
필요한 이유: 완전 자동화된 AI 시스템에서는 잘못된 정보 생성, 윤리적인 문제, 혹은 단순히 너무 복잡하여 AI만으로는 판단할 수 없는 케이스가 존재합니다. 인간의 전문 지식이나 승인을 프로세스에 포함함으로써 시스템의 신뢰성과 안전성을 향상시킵니다.
구현 포인트:
- 특정 조건(예: LLM의 confidence score가 낮음, 특정 키워드가 포함됨)에서 인간 리뷰 (Human Review) 노드로 라우팅합니다.
- 인간 리뷰어가 결과를 확인하고, 승인 또는 수정 지시를 시스템에 피드백하는 인터페이스가 필요합니다.
- LangGraph의 체크포인트 (Checkpoint) 기능 덕분에 인간이 개입하는 동안에도 상태를 영속화(Persist)할 수 있으며, 개입 후에 재개할 수 있습니다.
코드 예시:
여기서는 생성된 답변의 신뢰도가 낮다고 판단될 경우, 인간의 리뷰를 거치는 심플한 HITL (Human-in-the-loop) 패턴을 구현합니다.
from langgraph.graph import StateGraph, END, START
from typing import TypedDict, Annotated, List, Literal
from langchain_core.messages import BaseMessage, HumanMessage, AIMessage
지금까지 소개한 디자인 패턴을 효과적으로 활용하고, LangGraph를 프로덕션 환경에서 운영하기 위해 몇 가지 베스트 프랙티스(Best Practices)와 흔히 발생하는 실수(Pitfalls) 및 그 회피 방안을 정리합니다.
실수(Pitfall): TypedDict로 정의된 상태(State) 객체가 비대해지거나, 여러 노드에서 상태 업데이트가 충돌하면 디버깅이 어려워집니다.
회피 방안:
-
상태는 최소한으로 유지하고 명시적으로 타입 지정(Typing)합니다. 임시 값은 상태에 담지 말고 함수 스코프(Scope) 내에서 전달합니다.
-
상태를 데이터베이스 스키마처럼 취급하여 문서화하고 버전 관리합니다. 어떤 키가 어떤 에이전트에 속하는지에 대해 명확한 규칙을 수립합니다.
-
원시 데이터(Raw data)를 상태에 저장하고, 프롬프트 포맷팅은 노드 내에서 온디맨드(On-demand)로 수행합니다. 이를 통해 프롬프트 템플릿의 변경이 상태 스키마에 영향을 주지 않게 합니다.
-
누적이 필요한 경우에만
operator.add와 같은 리듀서(Reducer) 헬퍼를 사용합니다.
실수(Pitfall): 특히 리플렉션(Reflection) 패턴이나 멀티 에이전트 협업(Multi-agent collaboration) 패턴과 같이, 에이전트가 스스로를 수정하거나 다른 에이전트와 상호작용할 때 의도치 않은 루프에 빠질 수 있습니다.
회피 방안:
-
최대 반복 횟수를 설정하여 무한 루프를 방지합니다. 위에서 언급한 리플렉션 패턴의 예시처럼,
iterations와 같은 상태 변수로 카운트를 관리하고 상한선에 도달하면 강제로 종료합니다. -
조건부 엣지(Conditional edge)의 로직을 신중하게 설계하고 명확한 종료 조건을 마련합니다. 각 루프의 출구를 명확히 하여, 에이전트가 최종적으로 태스크를 완료하고 종료할 수 있는 경로를 확보합니다.
실수(Pitfall): API 속도 제한(Rate limit), 네트워크 장애, LLM의 예기치 않은 출력 등 프로덕션 환경에서는 다양한 에러가 발생합니다. 적절한 에러 핸들링(Error handling)이 없으면 에이전트가 중단되거나 사용자 경험이 저해됩니다.
회피 방안:
-
LangGraph의
RetryPolicy와TimeoutPolicy를 적극적으로 활용하여 일시적인 에러로부터의 복구와 행업(Hang-up) 방지를 도모합니다. -
전용
error_handler노드를 정의하여, 재시도(Retry)가 끝난 후 실행될 로직(정리, 알림, 폴백(Fallback) 등)을 구현합니다. -
예기치 않은 에러(
TypeError,KeyError, 로직 버그 등)는 차라리 크래시(Crash)를 발생시켜, LangSmith와 같은 트레이싱(Tracing) 도구에서 상세한 정보를 얻고 디버깅에 활용합니다.
실수(Pitfall): 복잡한 그래프 구조나 멀티 에이전트 시스템에서는 문제 발생 시 어떤 에이전트가 어떻게 동작했는지 추적하기 어려울 수 있습니다.
회피 방안:
-
LangSmith를 통합합니다.
LANGSMITH_TRACING=true와 API 키를 설정하는 것만으로 에이전트의 실행 트레이스, 디버깅, 평가를 상세하게 수행할 수 있습니다. 이는 프로덕션 운영에 있어 필수적인 도구입니다. -
각 노드의 결정이나 주요 이벤트를 로그(Log)로 출력하여 워크플로우 디버깅에 활용합니다.
-
각 함수뿐만 아니라 그래프 전체를 테스트합니다. 유닛 테스트(Unit test)와 통합 테스트(Integration test)를 모두 실시하여 엣지 케이스(Edge case)도 커버합니다.
노드의 입도(Granularity): 워크플로우를 명확한 단계로 분할하여, 각 단계가 하나의 특정 작업만을 수행하는 노드로 기능하게 합니다. 노드가 너무 세밀하면 복잡해지고, 너무 뭉뚱그려지면 회복성이 떨어집니다. 적절한 균형을 찾으세요.
메모리와 영속성(Persistence): 프로덕션 환경에서는 Postgres와 같은 프로덕션 체크포인터(Checkpointer)를 사용합니다. 관련 없는 이력으로 프롬프트가 과도하게 채워지지 않도록 요약이나 관련성 필터링을 구현하는 것도 고려하십시오.
스트리밍과 성능: UI 요구 사항에 맞춰 적절한 스트림 모드(messages...
/updates
/values
/custom
)를 선택하십시오. LangGraph는 스트리밍 워크플로우를 염두에 두고 설계되었으므로 오버헤드가 적습니다.
타입 힌트 (Type Hints): TypedDict나 Pydantic 모델을 사용하여 상태(State) 및 노드(Node)의 입출력에 타입 힌트를 적극적으로 도입함으로써, 코드의 가독성, 유지보수성, 견고성을 크게 향상시킵니다.
이 글에서는 LangGraph를 사용하여 견고한 AI 에이전트를 구축하기 위한 기본적인 사고방식부터 5가지 구체적인 디자인 패턴(라우팅 (Routing), 리플렉션 (Reflection), 에러 핸들링 (Error Handling), 멀티 에이전트 협업 (Multi-agent Collaboration), Human-in-the-Loop)과 각각의 구현 사례를 설명했습니다.
LangGraph는 복잡한 AI 워크플로우를 그래프로 모델링함으로써, 기존의 선형적인 체인(Chain)으로는 어려웠던 동적인 의사결정, 루프(Loop), 상태 관리(State Management)를 가능하게 합니다. 실제 운영 환경에서 무한 루프, 에러 내성, 디버깅 문제에 직면했을 때, 이번에 소개한 디자인 패턴과 베스트 프랙티스가 강력한 도움이 될 것입니다.
LangGraph의 가능성은 매우 넓으며, 이러한 패턴들을 조합함으로써 더욱 고도화되고 적응성이 높은 AI 에이전트를 구축할 수 있습니다. 여러분의 프로젝트에 이러한 지식을 적용하여 더욱 견고한 AI 시스템 개발에 도전해 보시기 바랍니다.
다음 단계: 공식 문서의 「LangGraph Concepts」나 「LangGraph Cookbook」에서 더욱 상세한 정보와 심화 주제를 탐구해 보시는 것을 추천합니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기