LangGraph 에이전트 오케스트레이션: StateGraph 기반 멀티에이전트 시스템 설계
LangGraph StateGraph 기반 멀티에이전트 오케스트레이션과 Durable Execution 설계
왜 오케스트레이션이 필요한가
멀티에이전트 시스템에서 개별 에이전트의 추론 능력만큼 중요한 것이 에이전트 간 통신, 상태 관리, 실행 흐름 제어다. 9개 에이전트가 순차 파이프라인으로 연결된 단순한 for-loop 구조는 다음과 같은 근본적인 한계를 가진다.
| 한계 | 설명 | 결과 |
|---|---|---|
| 장애 복구 불가 | 7번째 단계에서 오류 시 처음부터 재실행 | 불필요한 API 호출, 비용 낭비 |
| 병렬 실행 불가 | 독립적인 분석 단계도 순차 대기 | 총 지연 시간 증가 |
| 조건 분기 불가 | 데이터 품질 불량 시에도 전체 실행 | 오염된 데이터로 의사결정 |
| 상태 추적 부재 | "어디까지 진행됐는가" 기록 없음 | 디버깅, 감사 불가 |
| 사람 개입 불가 | 고위험 결정에서 승인 절차 없음 | 자동화의 위험 노출 |
LangGraph는 이 모든 문제를 그래프 기반 상태기계(StateGraph) 로 해결한다.
LangGraph 핵심 개념
StateGraph: 그래프 기반 워크플로우
LangGraph는 에이전트 워크플로우를 상태 기반의 제어된 실행 그래프(Stateful, controlled execution graph) 로 모델링한다. 노드(Node)는 각 에이전트의 실행 단위이고, 엣지(Edge)는 상태 전이를 정의한다.
from langgraph.graph import StateGraph, END
from langgraph.checkpoint.postgres import PostgresSaver
class PipelineState(TypedDict):
project_id: str
market: str
config: dict
scan_results: list[ScanResult]
news_items: list[NewsItem]
position_alerts: list[PositionAlert]
signals: list[Signal]
debate_result: dict | None
final_decision: dict | None
errors: list[str]
graph = StateGraph(PipelineState)
PipelineState는 전체 워크플로우의 공유 상태다. 각 노드는 이 상태를 읽고 업데이트하며, 체크포인터가 매 단계마다 스냅샷을 저장한다.
핵심 구성 요소
| 구성 요소 | 역할 | 비유 |
|---|---|---|
| Node | 에이전트 실행 단위 (함수) | 공장의 작업 스테이션 |
| Edge | 노드 간 상태 전이 | 컨베이어 벨트 |
| Conditional Edge | 조건에 따른 분기 | 품질 검사 분기점 |
| State | 공유 상태 (TypedDict) | 작업 지시서 |
| Checkpointer | 매 노드 완료 시 상태 저장 | 세이브 포인트 |
| Thread | 실행 인스턴스 식별자 | 작업 주문번호 |
StateGraph DAG 설계: 트레이딩 파이프라인
Sense-Think-Act 3계층 매핑
9개 에이전트를 Sense-Think-Act 3계층으로 분리하고, LangGraph의 StateGraph로 DAG를 구성한다.
그래프 정의 코드
graph = StateGraph(PipelineState)
# --- Sense Layer ---
graph.add_node("data_quality_gate", data_quality_gate)
graph.add_node("volume_scanner", volume_scanner)
graph.add_node("news_collector", news_collector)
graph.add_node("position_sensor", position_sensor)
# --- Think Layer (Fan-out / Fan-in) ---
graph.add_node("quant_analyst", quant_analyst)
graph.add_node("llm_analyst", llm_analyst)
graph.add_node("sector_rotation", sector_rotation)
graph.add_node("analysis_merge", merge_analysis_results)
graph.add_node("bull_bear_debate", bull_bear_debate)
graph.add_node("ensemble_voter", ensemble_voter)
# --- Act Layer ---
graph.add_node("risk_sentinel", risk_sentinel)
graph.add_node("trade_executor", trade_executor)
# 엣지 정의
graph.set_entry_point("data_quality_gate")
# Data Quality Gate: 조건부 분기
graph.add_conditional_edges(
"data_quality_gate",
lambda s: "continue" if not s["errors"] else "end",
{"continue": "volume_scanner", "end": END},
)
# Sense Layer 순차 실행
graph.add_edge("volume_scanner", "news_collector")
graph.add_edge("news_collector", "position_sensor")
# Fan-out: 병렬 분석
graph.add_edge("position_sensor", "quant_analyst")
graph.add_edge("position_sensor", "llm_analyst")
graph.add_edge("position_sensor", "sector_rotation")
# Fan-in: 결과 합류
graph.add_edge("quant_analyst", "analysis_merge")
graph.add_edge("llm_analyst", "analysis_merge")
graph.add_edge("sector_rotation", "analysis_merge")
# Think Layer 후반
graph.add_edge("analysis_merge", "bull_bear_debate")
graph.add_edge("bull_bear_debate", "ensemble_voter")
graph.add_edge("ensemble_voter", "risk_sentinel")
# Risk Sentinel: 조건부 분기
graph.add_conditional_edges(
"risk_sentinel",
lambda s: "execute" if s["final_decision"].get("approved") else "end",
{"execute": "trade_executor", "end": END},
)
graph.add_edge("trade_executor", END)Fan-out / Fan-in 병렬 처리
왜 병렬이 중요한가
의사결정 루프에서 Quant Analyst(p95 < 300ms), Sector Rotation(p95 < 300ms), LLM Analyst(p95 < 8s)는 서로 독립적이다. 순차 실행하면 총 8.6초 이상 소요되지만, 병렬 실행하면 가장 느린 LLM Analyst 기준 8초로 수렴한다.
LangGraph의 Fan-out 동작 방식
LangGraph에서 하나의 노드가 여러 노드로 엣지를 가지면 자동으로 Fan-out(병렬 분기) 된다. Fan-in은 여러 노드가 동일한 다음 노드를 가리킬 때 발생하며, 모든 선행 노드가 완료될 때까지 대기한다.
# position_sensor -> [quant, llm, sector] : Fan-out
graph.add_edge("position_sensor", "quant_analyst")
graph.add_edge("position_sensor", "llm_analyst")
graph.add_edge("position_sensor", "sector_rotation")
# [quant, llm, sector] -> analysis_merge : Fan-in
graph.add_edge("quant_analyst", "analysis_merge")
graph.add_edge("llm_analyst", "analysis_merge")
graph.add_edge("sector_rotation", "analysis_merge")analysis_merge 노드는 세 에이전트의 출력을 하나의 통합 리포트로 합친다. 상태(State) 내에 각 에이전트의 결과가 축적되므로, 합류 노드에서 모든 결과를 읽을 수 있다.
체크포인팅과 내구 실행(Durable Execution)
체크포인팅이란
LangGraph의 체크포인터는 매 super-step(노드 실행 완료)마다 전체 상태의 스냅샷을 저장한다. 이를 통해 다음이 가능해진다.
| 기능 | 설명 |
|---|---|
| Fault Tolerance | 장애 발생 시 마지막 체크포인트부터 재개 |
| Human-in-the-Loop | 특정 단계에서 실행을 중단하고 사람의 승인을 기다림 |
| Time Travel | 이전 상태로 되돌아가 "만약 다르게 판단했다면?" 시뮬레이션 |
| Audit Trail | 각 단계별 상태가 기록되어 의사결정 추적 가능 |
PostgreSQL 기반 체크포인터
from langgraph.checkpoint.postgres import PostgresSaver
checkpointer = PostgresSaver.from_conn_string(DATABASE_URL)
app = graph.compile(checkpointer=checkpointer)
# 실행
result = await app.ainvoke(
initial_state,
config={"configurable": {"thread_id": "pipeline-run-uuid"}}
)thread_id가 체크포인트 조회의 기본 키다. 동일한 thread_id로 재실행하면 마지막 체크포인트 이후 단계부터 자동으로 재개된다.
재시작 시퀀스
Durable Execution 원칙
Durable Execution은 LangGraph가 제공하는 핵심 패턴으로, 다음 원칙을 따른다.
| 원칙 | 트레이딩 적용 |
|---|---|
| Deterministic | Risk Sentinel까지는 동일 입력에 동일 출력 보장 |
| Idempotent | 주문 제출은 idempotency_key로 중복 방지 |
| Side Effect 격리 | 주문 제출(외부 API 호출)은 task로 래핑 |
트레이딩에서 "정확히 한 번 실행(exactly-once)"은 브로커/거래소 경계 때문에 완벽하게 보장하기 어렵다. 따라서
idempotency_key를 주문 단위로 부여하고, 재시작 시 동일 키의 주문이 이미 제출되었는지 확인하는 멱등성 검사가 필수다.
Human-in-the-Loop
왜 사람 개입이 필요한가
자동매매에서 특정 상황은 기계적 판단보다 사람의 확인이 필요하다.
- 일 손실 한도 근접 시 추가 거래
- 시장 레짐 급변 (VIX 급등, 블랙 스완 이벤트)
- 뉴스 신뢰도가 매우 낮은 상태에서의 대규모 매수
- 새로운 전략/프롬프트 변경 직후 첫 실거래
LangGraph의 Human-in-the-Loop 구현
Risk Sentinel의 출력에 requires_human_approval 플래그를 포함시키고, LangGraph의 interrupt 기능으로 실행을 중단한다.
def risk_sentinel(state: PipelineState) -> PipelineState:
intent = state["final_decision"]
portfolio = get_portfolio_snapshot(state["project_id"])
decision = evaluate_risk(intent, portfolio)
if decision.requires_human_approval:
# LangGraph interrupt: 실행 중단, 사람 승인 대기
raise NodeInterrupt(
f"고위험 거래 감지: {decision.reasons}"
)
state["final_decision"]["approved"] = decision.approved
return state사용자가 승인하면 동일 thread_id로 재실행하여 중단된 지점부터 계속 진행한다. 체크포인트 덕분에 이전 단계를 다시 실행할 필요가 없다.
조건부 엣지(Conditional Edge)를 활용한 분기
Data Quality Gate
파이프라인 진입점에서 데이터 품질을 검증하고, 기준 미달 시 전체 실행을 중단한다.
graph.add_conditional_edges(
"data_quality_gate",
lambda s: "continue" if not s["errors"] else "end",
{"continue": "volume_scanner", "end": END},
)Data Quality Gate 체크리스트:
| 체크 항목 | 기준 | 실패 시 |
|---|---|---|
| API 응답 시간 | p95 < 3초 | 재시도 3회 후 중단 |
| 시세 데이터 최신성 | 현재 시각 - 데이터 시각 < 5분 | 경고, 10분 초과 시 중단 |
| 가격 이상치 | 전일 종가 대비 30% 이내 | 스플릿/권리락 확인 |
| 필수 종목 누락 | 유니버스 90% 이상 조회 성공 | 미달 시 중단 |
| 뉴스 API 가용성 | 최소 1개 소스 응답 | 전체 불응답 시 뉴스 없이 진행 |
Risk Sentinel 분기
리스크 판정 결과에 따라 실행(execute) 또는 종료(end)로 분기한다.
graph.add_conditional_edges(
"risk_sentinel",
lambda s: "execute" if s["final_decision"].get("approved") else "end",
{"execute": "trade_executor", "end": END},
)Risk Sentinel의 판정은 5가지 결과를 가진다.
| 판정 | 의미 | 후속 동작 |
|---|---|---|
| APPROVE | 원안 승인 | Trade Executor로 전달 |
| RESIZE | 사이즈 조정 후 승인 | 조정된 Intent로 실행 |
| HOLD | 조건부 보류 | 조건 충족 시 재평가 |
| REJECT | 거부 | 로그 기록 후 종료 |
| KILL | 긴급 중단 | 모든 미체결 취소 |
이벤트 소스 기반 상태 지속성
3중 저장 구조
트레이딩 시스템의 상태 지속성은 3중 구조로 설계한다.
| 저장소 | 성격 | 역할 |
|---|---|---|
| Kafka | 불변(append-only) | 모든 이벤트 기록, 재생(replay) 가능, 감사 추적 |
| Redis | 가변(TTL 기반) | 실시간 캐시, Pub/Sub, Rate Limit, 에이전트 가중치 |
| PostgreSQL | 영구 저장 | LangGraph 체크포인트, 거래 이력, Reflection 메모 |
이벤트 Envelope 스키마
모든 에이전트 간 메시지는 공통 Envelope로 표준화한다.
{
"event_id": "uuid-v7",
"ts": "2026-02-21T09:15:00.123+09:00",
"event_type": "QUANT_DONE",
"correlation_id": "pipeline-run-uuid",
"project_id": "project-uuid",
"agent": "quant_analyst",
"payload": {
"symbol": "005930",
"signal": 0.72,
"confidence": 0.68,
"feature_vector": { "rsi_14": 32.5, "macd_signal": 0.003 }
},
"schema_version": "v2",
"idempotency_key": "uuid-for-dedup"
}idempotency_key와 correlation_id가 핵심이다. 전자는 중복 처리 방지, 후자는 하나의 파이프라인 실행에 속한 모든 이벤트를 추적하는 데 사용된다.
LangGraph vs AutoGen 비교
프레임워크 특성 비교
| 비교 항목 | LangGraph | AutoGen |
|---|---|---|
| 모델링 방식 | 그래프/상태기계 | 이벤트 기반 대화 |
| 실행 흐름 | DAG로 명시적 정의 | 에이전트 간 메시징 |
| 상태 관리 | TypedDict 공유 상태 | 대화 히스토리 |
| 체크포인팅 | 내장 (PostgreSQL, SQLite) | 외부 구현 필요 |
| Durable Execution | 네이티브 지원 | 미지원 |
| Human-in-the-Loop | interrupt 내장 | UserProxyAgent |
| 병렬 실행 | Fan-out/Fan-in 내장 | Teams/GroupChat |
| 디버깅 | Time Travel (상태 되감기) | 대화 로그 |
TradingAgents 프레임워크
TradingAgents는 실제 트레이딩 조직을 모사한 멀티에이전트 구조를 제안한다. 7가지 역할 (Fundamental Analyst, Sentiment Analyst, News Analyst, Technical Analyst, Bull/Bear Researcher, Trader, Risk Manager)이 5단계 파이프라인으로 협업한다.
Analyst Team -> Researcher Team -> Trader -> Risk Team -> Fund Manager
(4명 병렬) (Bull/Bear 토론) (종합) (3관점 토론) (최종 승인)Q1 2024 백테스트에서 AAPL 누적수익률 26.62%(Sharpe 8.21), GOOGL 24.36%(Sharpe 6.39), AMZN 23.21%(Sharpe 5.60)을 기록했다. 다만 3종목/3개월이라는 제한된 범위에 주의가 필요하다.
어떤 프레임워크를 선택할 것인가
트레이딩 시스템에서는 LangGraph가 더 적합하다. 이유는 (1) 체크포인팅으로 장애 복구가 가능하고, (2) Conditional Edge로 리스크 게이트를 자연스럽게 모델링할 수 있으며, (3) Human-in-the-Loop가 내장되어 있고, (4) Time Travel로 "다른 판단을 했다면?"을 시뮬레이션할 수 있기 때문이다.
AutoGen의 강점은 에이전트 간 자유로운 대화와 동적 팀 구성에 있어, Bull/Bear Debate 같은 토론 구조에는 AutoGen의 GroupChat 패턴이 유리할 수 있다. 두 프레임워크를 혼용하는 것도 가능하다.
에이전트별 SLO와 실패 모드
지연/처리량 목표
| 에이전트 | 권장 지연 SLO | 동시성 | 주요 실패 모드 | 완화책 |
|---|---|---|---|---|
| Volume Scanner | p99 < 200ms | 수백 심볼/회 | 데이터 지연/누락 | 워터마크, 쿨다운 |
| News Collector | p95 < 3s | 수십 기사/회 | 중복, 루머 | 디듀프, 소스 신뢰도 |
| Position Sensor | p99 < 200ms | 프로젝트당 전종목 | 시세 지연 | Redis 캐시, DB 정합 |
| Quant Analyst | p95 < 300ms | 티켓당 1회 | 피처 오류 | 피처 버전 고정 |
| LLM Analyst | p95 < 8s | 동시 10건 | 환각, 과잉확신 | 근거 인용, Critic 루프 |
| Sector Rotation | p95 < 300ms | 주기적 | 레짐 오판 | 히스테리시스 |
| Bull/Bear Debate | p95 < 10s | 동시 수건 | 수렴 실패 | 라운드 제한(2) |
| Ensemble Voter | p95 < 200ms | 티켓당 1회 | 과적합 | 캘리브레이션 |
| Risk Sentinel | p99 < 50ms | 주문 전 필수 | 한도 미적용 | 하드룰 우선 |
| Trade Executor | p99 < 100ms | 주문/초 | 주문거부, 미체결 | 재호가, 타임아웃 |
에이전트별 I/O 흐름
운영: 테스트, 카나리 배포, 킬 스위치
3단 테스트 체계
| 단계 | 설명 | 규제 근거 |
|---|---|---|
| 회귀 테스트 | 코드 변경 시 기존 기능 검증 | MiFID II RTS 6: 적합성 테스트 |
| 시뮬레이션 | 백테스트 + 페이퍼 트레이딩 | SEC Rule 15c3-5 |
| 카나리 배포 | 자본 1-5%로 시작 후 확대 | Google SRE 카나리 릴리스 |
카나리 배포 원칙
트레이딩에서 "트래픽" 대신 "자본/노출(Exposure)"을 카나리 단위로 삼는다.
- 신규 전략/모델 변경: 총 자본의 1-5%로 시작 후 성과/리스크/슬리피지 모니터링
- LLM 프롬프트/토론 규약 변경: 제한된 섹터/소수 심볼로 먼저 적용 후 드리프트 확인
- 문제 발생 시 즉시 이전 버전으로 롤백 가능하도록 "버전 고정 + 이벤트 로그 기반 재현" 준비
Kill Switch
Kill Switch는 모든 미체결 주문을 즉시 취소하고 파이프라인을 중단하는 비상 장치다.
모든 LangGraph 노드는 실행 전에 Redis의 Kill Switch 플래그를 확인한다. 플래그가 설정되어 있으면 즉시 종료한다.
전체 인프라 스택 요약
| 기술 | 역할 | 핵심 사용처 |
|---|---|---|
| LangGraph | 오케스트레이션 | StateGraph DAG, 체크포인트, 조건부 분기, Human-in-the-Loop |
| Kafka | 이벤트 스트림 | 파이프라인/거래/포지션 이벤트 토픽, 감사 로그 |
| Redis | 실시간 캐시 | 시세 캐시, Pub/Sub, Rate Limiter, Kill Switch, 에이전트 가중치 |
| Celery | 스케줄링/비동기 | Beat(정기 트리거), PositionSensor 폴링, Reflection, 보고서 |
| PostgreSQL | 영구 저장 | 체크포인트, 거래 이력, Reflection Memo, 가중치 스냅샷 |
LangGraph는 "에이전트를 어떻게 연결하고 실행할 것인가"에 대한 답이다. 단순한 순차 파이프라인에서 벗어나, 그래프 기반 DAG + 체크포인팅 + 조건 분기 + 병렬 실행 + Human-in-the-Loop를 하나의 프레임워크로 제공함으로써, 운영 가능한(production-grade) 멀티에이전트 시스템을 구축할 수 있게 한다.
참고 자료
- LangGraph Persistence: https://docs.langchain.com/oss/python/langgraph/persistence
- LangGraph Durable Execution: https://docs.langchain.com/oss/python/langgraph/durable-execution
- TradingAgents: Multi-Agents LLM Financial Trading Framework (arxiv:2412.20138)
- AutoGen: Enabling Next-Gen LLM Applications via Multi-Agent Conversation (arxiv:2308.08155)
- QuantAgents: Towards Multi-agent Financial System via Simulated Trading (arxiv:2510.04643)
- Event Sourcing Pattern: https://learn.microsoft.com/en-us/azure/architecture/patterns/event-sourcing
- SEC Rule 15c3-5: Risk Management Controls for Brokers
- MiFID II RTS 6: Commission Delegated Regulation (EU) 2017/589