프로덕션 환경에서의 멀티 에이전트 시스템 (Multi-Agent Systems): 단일 에이전트로는 부족할 때와 이를 조율하는 방법
요약
단일 에이전트의 한계를 극복하기 위한 멀티 에이전트 시스템(MAS)의 설계 원칙과 구현 패턴을 다룹니다. 컨텍스트 유실과 지연 시간 문제를 고려하여 관리자 패턴, 순차적 파이프라인, 이벤트 기반 구조 중 적절한 방식을 선택하는 가이드를 제공합니다.
핵심 포인트
- 단일 에이전트가 컨텍스트 윈도우 초과나 도구 망각 문제를 겪을 때 멀티 에이전트 전환 고려
- 에이전트 분리 시 컨텍스트 유실, 지연 시간 증가, 오류 누적 등의 오버헤드 주의
- 관리자(Supervisor) 패턴을 통해 라우팅과 작업 수행을 분리하여 효율성 증대
- 작업의 성격에 따라 순차적 파이프라인 또는 이벤트 기반 구조 선택 필요
우리는 첫 번째 "멀티 에이전트 시스템 (multi-agent system)"을 우연히 만들게 되었습니다. 주제를 조사하고, 보고서 초안을 작성하며, 소스 데이터와 대조하여 확인한 뒤 요약 이메일을 보낼 수 있는 단일 에이전트로 시작했던 것이, 2,000-토큰(token) 규모의 시스템 프롬프트(system prompt)와 모델이 도구의 존재를 계속 잊어버릴 정도로 긴 함수(function) 목록으로 커져 버렸습니다. 그것은 시스템이 아니었습니다. 지능적인 척하는 모놀리스 (monolith)였습니다.
이를 조율된 에이전트들로 분리하자 대부분의 문제가 해결되었습니다. 하지만 우리가 생각하지 못했던 새로운 범주의 문제들도 등장했습니다. 우리가 실제로 배운 점은 다음과 같습니다.
단일 에이전트로 충분할 때 (그리고 그렇지 않을 때)
더 많은 에이전트를 추가하고 싶은 유혹은 실재하지만, 그에 따른 오버헤드 (overhead)가 공짜는 아닙니다. 에이전트 경계를 추가할 때마다 컨텍스트 (context)가 유실될 수 있고, 지연 시간 (latency)이 증가하며, 오류가 누적될 수 있는 지점이 생깁니다.
단일 에이전트가 적절한 경우:
- 작업이 혼잡함 없이 단일 LLM 컨텍스트 윈도우 (context window)에 들어맞을 때
- 단계가 순차적이며 각 단계가 이전 출력에 크게 의존할 때
- 모든 정보에 대해 긴밀한 추론 (reasoning)이 필요할 때 (예: 문서 요약)
여러 에이전트가 필요한 경우:
- 도구 정의 (tool definitions), 히스토리 (history) 또는 데이터로 인해 단일 에이전트의 컨텍스트 윈도우가 가득 찰 때
- 각 단계가 진정으로 다른 "페르소나 (personas)" 또는 지침 세트(instruction sets)를 필요로 할 때 (조사 vs 작성 vs 사실 확인)
- 단계들을 병렬로 실행할 수 있고 지연 시간 절감이 중요할 때
- 실패를 격리하고 싶을 때 — 데이터 추출 에이전트가 실패하더라도 보고서 작성 에이전트에는 영향을 주지 않아야 함
우리가 던지는 핵심 질문은 다음과 같습니다: 이것은 하나의 작업인가, 아니면 작업의 파이프라인 (pipeline)인가? 만약 사람에게 이 작업을
이것은 가장 흔한 패턴입니다. 관리자(supervisor)의 시스템 프롬프트 (system prompt)는 추론 (reasoning)이 아닌 라우팅 (routing)을 수행하기 때문에 작게 유지됩니다. 반면 작업자(worker)들의 프롬프트는 각자의 특정 작업에 맞춰 고도로 최적화될 수 있습니다.
2. 순차적 파이프라인 (Sequential Pipeline)
각 에이전트의 출력이 다음 에이전트의 입력이 됩니다. 오케스트레이터 (orchestrator) 없이 그저 체인 (chain) 형태로 연결됩니다. 저희는 문서 처리 과정인 추출 (extract) → 청킹 (chunk) → 요약 (summarise) → 분류 (classify) 단계에 이 방식을 사용합니다. 각 단계는 충분히 독립적이어서 다른 단계에 영향을 주지 않고도 특정 단계만 교체하거나 재학습시킬 수 있습니다.
3. 이벤트 기반 에이전트 (Event-Driven Agents)
Django + Celery에서의 오케스트레이터
다음은 저희가 관리자 패턴 (supervisor pattern)을 구현하는 방식을 단순화한 버전입니다. 오케스트레이터 역할을 하는 Celery 태스크 (task)가 워크플로 (workflow)를 관리하며, 개별 에이전트 태스크들이 실제 LLM 호출을 수행합니다.
# tasks/orchestrator.py
from celery import chain, chord
from .agents import extract_data_task, analyse_data_task, draft_report_task
...
각 에이전트 태스크는 자체적인 LLM 호출과 자체적인 에러 핸들링 (error handling)을 책임집니다. 오케스트레이터는 각 에이전트가 어떤 모델을 사용하는지, 혹은 두 번째 에이전트가 도구 (tool)를 호출하는지 여부를 알 필요가 없습니다. 오케스트레이터는 오직 단계 사이를 통과하는 데이터의 형태 (shape)에만 관심을 가집니다.
정신을 잃지 않고 에이전트 간 상태 전달하기 (Passing State Between Agents Without Losing Your Mind)
가장 단순한 접근 방식은 각 에이전트의 전체 출력을 다음 에이전트로 직접 전달하는 것입니다. 하지만 이 방식은 금방 한계에 부딪힙니다. LLM 출력은 장황하며, 단 5개의 핵심 사실만 필요한 초안 작성자 (drafter)에게 3,000 토큰 분량의 분석 내용을 입력하는 것은 토큰을 낭비하고 품질을 저하시킵니다.
저희는 에이전트 간의 계약 (contract)으로서 구조화된 중간 형식(structured intermediate format) — 일반적인 Python 데이터 클래스 (dataclass) 또는 Pydantic 모델 (Pydantic model) — 을 사용합니다. 각 에이전트의 출력은 다음 단계로 전달되기 전에 이 스키마 (schema)에 따라 검증됩니다.
from pydantic import BaseModel
from typing import Optional
...
경계 지점에서 스키마를 강제한다는 것은, 분석 에이전트가 추출 에이전트로부터 무엇을 받았는지 추측할 필요가 없음을 의미합니다. 무언가 잘못되었을 때, 에러는 세 단계 뒤에 묻혀 있는 것이 아니라 마땅히 있어야 할 경계 지점에서 발생합니다.
멀티 에이전트 체인에서의 실패 처리 (Handling Failures in a Multi-Agent Chain)
멀티 에이전트 시스템 (Multi-Agent Systems)에서 가장 어려운 부분은 실패 처리 (failure handling)입니다. 모놀리식 에이전트 (monolithic agent)에서는 하나의 실패가 하나의 태스크를 종료시킵니다. 파이프라인 (pipeline)에서는 두 번째 단계에서의 실패가 첫 번째 단계를 낭비했음을 의미하며, 처음부터 다시 시도할지 아니면 두 번째 단계부터 다시 시도할지를 결정해야 합니다.
우리의 접근 방식:
- 각 단계가 끝날 때마다 결과를 데이터베이스에 체크포인트 (Checkpoint) 저장합니다. 단순히 마지막에만 저장하는 것이 아닙니다. 세 번째 단계가 실패하면, 두 번째 단계의 저장된 출력값으로부터 다시 실행 (replay)할 수 있습니다.
- 각 에이전트는 실패를 전파하기 전에 백오프 (backoff)와 함께 독립적으로 재시도합니다. 대부분의 LLM 실패는 일시적 (transient)입니다.
- 오케스트레이터 (orchestrator)가 파이프라인 상태를 추적합니다. 우리는 각 단계에 대한 상태 필드를 가진
PipelineRun모델을 보유하고 있습니다. 이를 통해 부분적인 파이프라인을 재개할 수 있으며, 어디에서 문제가 발생하는지에 대한 가시성 (visibility)을 확보할 수 있습니다.
# models.py
class PipelineRun(models.Model):
document = models.ForeignKey(Document, on_delete=models.CASCADE)
...
이러한 방식은 실패한 파이프라인의 디버깅 (debugging)을 실제로 가능하게 만듭니다. 관리자 페이지를 열어 PipelineRun을 찾고, 어떤 단계가 실패했는지 확인한 뒤 에러를 읽으면 됩니다. 이것이 없다면, 무엇이 일어났는지 알려주기를 바라며 Celery 로그를 파싱해야 할 것입니다.
솔직한 요약 (The Honest Summary)
멀티 에이전트 아키텍처 (Multi-agent architectures)는 컨텍스트 오버플로 (context overflow), 전문화 (specialisation), 병렬성 (parallelism), 그리고 실패 격리 (failure isolation)와 같은 실제 문제들을 해결합니다. 하지만 단일 에이전트에는 없는 조정 오버헤드 (coordination overhead)를 발생시킵니다. 즉, 단순함을 희생하여 확장성 (scalability)과 회복 탄력성 (resilience)을 얻는 것입니다.
이 방식이 해결하지 못하는 것들: 개별 에이전트의 잘못 설계된 시스템 프롬프트 (system prompt)를 고쳐주지는 않으며, 태스크 분해 (task decomposition)가 잘못되었을 때 당신을 구원해주지도 못하고, 지연 시간 (latency)을 추가합니다. 모든 에이전트 경계는 LLM으로의 왕복 (round-trip)을 의미합니다.
에이전트 하나로 시작하세요. 두 번째 에이전트는 더 인상적으로 보이기 때문이 아니라, 명확한 이유가 있을 때 추가하세요. 에이전트 2가 모호한 추출 결과를 제공했기 때문에 에이전트 3이 환각 (hallucination)을 일으킨 이유를 디버깅해야 하는 순간이 오면, 단순함의 가치를 깨닫게 될 것입니다.
우리는 문서 처리 (document processing), 자동화된 연구 워크플로우 (automated research workflows), 그리고 고객 분류 (customer triage)를 위해 프로덕션 환경에서 멀티 에이전트 파이프라인 (multi-agent pipelines)을 운영하고 있습니다. 이들은 잘 작동하지만, 그들 중 하나하나 모두 구체적인 이유가 생기기 전까지는 단일 에이전트 (single agent)로 시작되었습니다.
_Lycore는 기업을 위한 프로덕션 AI 시스템을 구축합니다 — 우리는 프로덕션 환경에서 견고하게 작동하는 멀티 에이전트 파이프라인 (multi-agent pipelines), RAG 시스템, 그리고 LLM 통합 (LLM integrations)을 설계하고 구현합니다. 귀하의 유스케이스 (use case)에 대해 논의하고 싶다면 문의해 주세요.]
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기