k
korAI
고급 전체
🔥 고급2026-05-256~8분

멀티 에이전트 DAG 오케스트레이션: 병렬 실행과 실패 격리 전략

단순 체인 구조를 벗어나 DAG(방향 비순환 그래프) 기반으로 에이전트를 병렬 실행할 때 발생하는 부분 실패 처리, 비용 폭발, 데드락 문제를 실제 패턴으로 해결한다.

multi-agentorchestrationasync

왜 선형 체인이 아닌 DAG인가

단일 오케스트레이터가 서브에이전트를 순차 호출하면 전체 지연이 각 단계의 합이 된다. 3개 독립 에이전트를 직렬로 실행하면 평균 12~18초가 걸리지만, DAG로 병렬화하면 가장 긴 단계(보통 6~8초)로 수렴한다. 그러나 병렬화는 부분 실패 격리비용 상한 제어라는 새로운 복잡도를 가져온다.

핵심 패턴: Fan-out → Barrier → Fan-in

import asyncio
import anthropic
from dataclasses import dataclass
from typing import Any

@dataclass
class AgentResult:
    agent_id: str
    success: bool
    output: Any
    input_tokens: int
    output_tokens: int

async def run_agent(client: anthropic.AsyncAnthropic, agent_id: str, prompt: str) -> AgentResult:
    try:
        response = await client.messages.create(
            model="claude-opus-4-5",
            max_tokens=1024,
            messages=[{"role": "user", "content": prompt}],
        )
        return AgentResult(
            agent_id=agent_id,
            success=True,
            output=response.content[0].text,
            input_tokens=response.usage.input_tokens,
            output_tokens=response.usage.output_tokens,
        )
    except anthropic.APIError as e:
        return AgentResult(agent_id=agent_id, success=False, output=str(e), input_tokens=0, output_tokens=0)

async def dag_orchestrate(tasks: dict[str, str], fail_fast: bool = False) -> list[AgentResult]:
    client = anthropic.AsyncAnthropic()
    # Fan-out: 모든 독립 노드를 동시에 실행
    coros = [run_agent(client, aid, prompt) for aid, prompt in tasks.items()]
    # Barrier: 전체 또는 first-failure 대기
    if fail_fast:
        results = await asyncio.gather(*coros, return_exceptions=False)
    else:
        results = await asyncio.gather(*coros, return_exceptions=True)
    # Fan-in: 실패 분리 후 성공 결과만 집계
    return [r if isinstance(r, AgentResult) else AgentResult(
        agent_id="unknown", success=False, output=str(r), input_tokens=0, output_tokens=0
    ) for r in results]

fail_fast=False로 설정하면 하나의 에이전트 실패가 전체를 중단시키지 않는다. Fan-in 단계에서 실패 노드만 재시도 큐에 넣어 비용을 최소화할 수 있다.

트레이드오프와 실패 모드

비용 폭발 위험: 병렬 실행은 동시에 여러 API 호출을 발생시켜 단기 토큰 소비가 급등한다. 10개 에이전트를 동시에 실행하면 분당 토큰 한도(TPM)를 초과할 수 있다. asyncio.Semaphore(max_concurrent=5)로 동시 실행 수를 제한하고, 각 AgentResult의 토큰 합계를 누적하여 실행 전 예산 상한을 체크해야 한다. 일반적으로 병렬 에이전트 수는 4~6개가 비용 대비 속도 최적점이다.

데드락: 에이전트 A가 에이전트 B의 출력을 기다리고 B도 A를 기다리는 순환 의존이 생기면 asyncio는 영원히 대기한다. DAG를 구성할 때 위상 정렬(topological sort)로 순환 없음을 사전 검증하고, 개별 에이전트에 asyncio.wait_for(coro, timeout=30.0)으로 타임아웃을 강제해야 한다.

운영 체크리스트

  • [ ] 각 에이전트 호출에 고유 trace_id 부여 후 로그에 기록
  • [ ] 전체 DAG 실행당 총 토큰 예산 상한 설정 (예: 50,000 토큰)
  • [ ] 실패 노드 재시도 횟수 최대 2회, 지수 백오프 1s→4s
  • [ ] Fan-in 결과에서 성공률 < 70%이면 오케스트레이터 레벨 알림 발송
  • [ ] 위상 정렬 검증 로직을 DAG 빌드 타임에 실행 (런타임 아님)