7장 — 멀티 에이전트 협업

단일 에이전트 아키텍처는 잘 정의된 문제에는 효과적일 수 있지만, 여러 도메인에 걸친 복잡한 작업을 만나면 역량이 쉽게 한계에 부딪힙니다. 멀티 에이전트 협업 패턴은 서로 다른 전문 에이전트가 팀을 이뤄 함께 작업하는 협력 체계로 시스템을 구성하여 이러한 한계를 해결합니다.

패턴 개요

이 접근 방식은 작업 분해 원칙에 바탕을 둡니다. 먼저 상위 목표를 개별 하위 문제로 나눕니다. 그리고 각 하위 문제를 해당 작업에 가장 적합한 도구, 데이터 접근 권한, 또는 추론 역량을 갖춘 에이전트에게 배정합니다.

예를 들어 복잡한 연구 질의는 정보 검색을 담당하는 조사 에이전트, 통계 처리를 담당하는 데이터 분석 에이전트, 최종 보고서 생성을 담당하는 종합 에이전트에게 나누어 배정할 수 있습니다.

여기서 책이 강조하는 대목이 있습니다.

이러한 시스템의 효과는 단순한 분업에서만 나오지 않고, 에이전트 간 통신 메커니즘에 결정적으로 좌우됩니다. 이를 위해서는 표준화된 통신 프로토콜과 공유 온톨로지(shared ontology) 가 필요합니다. 그래야 에이전트가 데이터를 주고받고, 하위 작업을 위임하며, 서로 동작을 조율해 최종 출력의 일관성을 확보할 수 있습니다.

역할을 잘 나누는 것만으로는 부족하고, 나눈 것을 다시 합칠 수 있는 공통의 말이 있어야 한다는 뜻입니다.

이러한 분산 아키텍처의 장점은 세 가지입니다.

장점 설명
모듈성 · 확장성 에이전트를 독립적으로 추가·교체할 수 있다
견고성 에이전트 하나가 실패하더라도 반드시 전체 시스템의 실패로 이어지지는 않는다
시너지 멀티 에이전트 시스템의 집단 성능이 협력 체계 안의 특정 단일 에이전트가 낼 수 있는 최대 역량을 뛰어넘을 수 있다

협업의 여섯 가지 형태

멀티 에이전트 협업 패턴은 여러 독립적인(또는 일부만 독립적인) 에이전트가 공통 목표를 달성하기 위해 함께 작동하도록 시스템을 설계하는 방식입니다. 각 에이전트는 대체로 정해진 역할과 전체 목표에 부합하는 구체적 목표를 가지며, 경우에 따라 서로 다른 도구나 지식 베이스에 접근할 수도 있습니다. 이 패턴의 힘은 에이전트 간 상호작용과 시너지에서 나옵니다.

형태 동작 방식
순차적 핸드오프(handoff) 한 에이전트가 작업을 마치면 그 출력을 파이프라인의 다음 단계를 맡은 다른 에이전트에게 넘긴다. 계획 수립 패턴과 유사하지만, 서로 다른 에이전트가 명시적으로 개입한다는 점이 다르다
병렬 처리 여러 에이전트가 문제의 서로 다른 부분을 동시에 처리한 뒤, 나중에 결과를 결합한다
토론과 합의 서로 다른 관점과 정보 출처를 지닌 에이전트가 토론을 통해 선택지를 평가하고, 최종적으로 합의에 이르거나 더 나은 의사결정을 내린다
계층 구조 관리자 에이전트가 작업자 에이전트의 도구 접근 권한이나 플러그인의 역량에 따라 동적으로 작업을 위임하고, 결과를 종합한다. 하나의 에이전트가 모든 도구를 처리하는 대신, 각 에이전트가 관련 도구 그룹을 담당하도록 설계할 수도 있다
전문가 팀 서로 다른 도메인에 전문성을 지닌 에이전트(예: 조사 에이전트, 문서 작성 에이전트, 편집 에이전트)가 협력하여 복합적인 결과물을 만들어 낸다
비평-검토 에이전트가 계획·초안·답변 등 초기 결과물을 생성한다. 이어서 또 다른 에이전트 집단이 이 결과물을 정책/규정 준수 여부, 보안, 정확성, 품질, 조직 목표와 부합하는지 등을 기준으로 비판적으로 평가한다. 처음에 글을 생성한 에이전트 또는 최종 에이전트가 이 평가를 반영하여 결과물을 수정한다

비평-검토는 코드 생성, 연구 글 작성, 논리 검증, 윤리적 기준에 부합하도록 하는 데 특히 효과적입니다. 이 방식을 쓰면 견고성이 높아지고, 품질이 개선되며, 환각이나 오류 발생 가능성도 낮아집니다. 4장 리플렉션의 생성자-비평자 모델을 여러 에이전트로 확장한 형태로 볼 수 있습니다.

멀티 에이전트 시스템의 세 요소

멀티 에이전트 시스템은 기본적으로 세 요소로 이루어집니다.

  1. 에이전트의 역할과 책임을 정한다
  2. 에이전트 간 정보 교환을 위한 통신 채널을 구축한다
  3. 협업을 이끄는 작업 흐름 또는 상호작용 프로토콜을 수립한다
  사용자 ⇄ 관리자
              │
      ┌───────┼───────┐
      ▼       ▼       ▼
   전문     전문     전문
  에이전트  에이전트  에이전트
                      │
                  ┌───┴───┐
                  ▼       ▼
              에이전트  에이전트

그림 7.1 — 멀티 에이전트 시스템 예시

CrewAI와 구글 ADK 같은 프레임워크는 에이전트·작업·상호작용 절차를 정의(specification) 할 수 있는 구조를 제공하여 이러한 패러다임을 쉽게 구현하도록 설계되어 있습니다. 이 접근 방식은 다양한 전문 지식이 필요하거나, 여러 개별 단계로 이루어져 있거나, 동시 처리와 에이전트 간 정보 교차 검증의 이점을 활용해야 하는 과제에 특히 효과적입니다.

실제 적용 및 활용 사례

도메인 에이전트 구성
복합 연구 및 분석 한 에이전트는 학술 데이터베이스 검색을, 다른 에이전트는 결과 요약을, 세 번째는 트렌드 파악을, 네 번째는 정보를 종합하여 보고서 작성을 담당한다. 사람이 연구 팀을 꾸려 일하는 방식과 유사하다
소프트웨어 개발 요구사항 분석 / 코드 생성 / 테스트 / 문서 작성을 각각 담당하며, 서로 출력을 주고받으며 기능을 구축하고 검증한다
창작 콘텐츠 생성 마케팅 캠페인을 만들 때 시장 조사 에이전트, 카피라이터 에이전트, 그래픽 디자인 에이전트(이미지 생성 도구 활용), 소셜 미디어 일정 관리 에이전트가 함께 작업한다
재무 분석 주가 데이터 수집, 뉴스 감성(news sentiment) 분석, 기술적 분석, 투자 추천 생성을 각각 전문적으로 담당한다
고객 지원 이관 최전방 지원 에이전트가 초기 문의를 처리하다가, 복잡한 문제가 발생하면 전문 에이전트(기술 전문 또는 요금 결제·청구 전문)에게 이관(escalate) 한다. 문제 복잡도에 따른 순차적 핸드오프를 보여 주는 사례
공급망 최적화 에이전트가 공급망의 각 노드(공급업체, 제조업체, 유통업체)를 담당하며, 수요 변화나 차질에 대응하여 재고 수준·물류·스케줄링을 협력적으로 최적화한다
네트워크 분석 및 복구 자율 운영 환경에서 장애 지점 파악에 효과적이다. 여러 에이전트가 협업하여 이슈를 중요도에 따라 분류하고 복구하면서 최적의 조치를 제안한다. 기존 머신러닝 모델 및 도구와도 통합할 수 있어, 기존 시스템을 활용하면서 동시에 생성형 AI의 장점을 더할 수 있다

전문화된 에이전트를 정의하고 이들의 상호 관계를 정교하게 오케스트레이션할 수 있으면, 개발자는 모듈성과 확장성이 뛰어난 시스템을 구축할 수 있습니다.

상호 관계와 통신 구조

효과적인 멀티 에이전트 시스템을 설계하려면 에이전트 간의 복잡한 상호작용과 통신 방식을 이해해야 합니다. 가장 단순한 단일 에이전트 시나리오부터 복잡한 맞춤형 협업 프레임워크까지 다양한 모델이 존재하며, 각 모델은 고유한 장점과 과제를 지니고 시스템의 효율성·견고성·적응성에 영향을 미칩니다.

1. 단일 에이전트

기본적으로 다른 개체와 직접 상호작용하거나 통신하지 않고 자율적으로 작동합니다.

  • 장점 — 구현과 관리가 단순하다
  • 한계 — 개별 에이전트의 범위와 자원에 따라 역량이 태생적으로 제한된다
  • 적합 — 독립적인 하위 문제로 분해할 수 있고, 각 하위 문제를 자체적으로 완결된 단일 에이전트가 해결할 수 있는 작업

2. 네트워크

에이전트가 탈중앙화 방식으로 서로 직접 상호작용합니다. 통신은 대체로 피어 투 피어 방식으로 이루어지며, 정보·자원·심지어 작업까지 공유할 수 있습니다.

  • 장점 — 하나의 에이전트가 실패해도 전체 시스템이 반드시 마비되지는 않으므로 회복 탄력성이 높다
  • 한계 — 대규모 비구조적 네트워크에서 통신 오버헤드를 관리하고 일관된 의사결정을 보장하기 어렵다

3. 관리자

전담 에이전트인 “슈퍼바이저” 가 하위 에이전트 집단의 활동을 감독하고 조율합니다. 슈퍼바이저는 통신·작업 할당·충돌 해결의 중앙 허브 역할을 합니다.

  • 장점 — 명확한 권한 체계를 마련해 주어 관리와 통제를 단순화할 수 있다
  • 한계그 자체로 단일 장애 지점이 생기며, 하위 에이전트 수가 많거나 작업이 복잡해지면 병목이 될 수 있다

4. 도구로서의 관리자

슈퍼바이저 개념을 미묘하게 확장한 것으로, 관리자의 역할이 직접적인 명령과 통제보다는 다른 에이전트에게 자원·지침·분석 지원을 제공하는 데 가깝습니다.

관리자는 다른 에이전트가 작업을 더 효과적으로 수행할 수 있도록 도구·데이터·연산 서비스를 제공하되, 반드시 모든 동작을 지시하지는 않습니다. 이 접근 방식은 경직된 하향식 통제를 부과하지 않으면서도 관리자의 역량을 활용하는 것을 목표로 합니다.

5. 계층형

슈퍼바이저 개념을 확장하여 다층적 조직 구조를 만듭니다. 상위 슈퍼바이저가 하위 슈퍼바이저를 감독하고, 최하위 계층에는 실제 작업을 수행하는 에이전트가 배치되는 다단계 구조입니다.

각 계층이 특정 범위를 관리하므로, 하위 문제로 분해할 수 있고 정해진 범위 안에서 분산 의사결정이 가능한 복잡한 문제에 적합합니다. 확장성과 복잡성을 체계적으로 관리할 수 있습니다.

6. 맞춤형

멀티 에이전트 시스템 설계에서 설계 유연성이 가장 큽니다. 주어진 문제나 애플리케이션의 구체적 요구사항에 정확히 맞춰 고유한 상호 관계 및 통신 구조를 만들 수 있습니다. 앞서 소개한 모델의 요소를 결합한 하이브리드 방식이 될 수도 있고, 해당 환경의 고유한 제약과 기회에서 비롯된 완전히 새로운 설계가 될 수도 있습니다.

맞춤형 모델은 특정 성능 지표를 최적화하거나, 급변하는 환경을 다루거나, 도메인 특화 지식을 시스템 아키텍처에 반영해야 할 때 흔히 등장합니다. 설계하고 구현하려면 멀티 에이전트 시스템 원리를 깊이 이해하고, 통신 프로토콜·조율 메커니즘·창발적 행동을 신중히 고려해야 합니다.

요컨대 멀티 에이전트 시스템의 상호 관계와 통신 모델 선택은 핵심적인 설계 결정입니다. 최적의 선택은 작업의 복잡도, 에이전트 수, 원하는 자율성 수준, 요구되는 견고성의 수준, 허용 가능한 통신 오버헤드 등의 요소에 따라 달라집니다.

실습 코드 (CrewAI)

AI 트렌드에 관한 블로그 포스트를 생성하는 크루입니다. AI 트렌드를 찾아 요약하는 리서치 에이전트리서치 결과를 바탕으로 블로그 포스트를 작성하는 에이전트 두 개를 정의하고, 작성 작업이 조사 작업의 출력에 의존하도록 순차 프로세스로 묶습니다.

import os
from dotenv import load_dotenv
from crewai import Agent, Task, Crew, Process
from langchain_google_genai import ChatGoogleGenerativeAI

def setup_environment():
    """환경 변수를 로드하고 필수 API 키가 설정되어 있는지 확인한다."""
    load_dotenv()
    if not os.getenv("GOOGLE_API_KEY"):
        raise ValueError("GOOGLE_API_KEY not found. Please set it in your .env file.")

def main():
    """
    최신 제미나이 모델로 콘텐츠 생성용
    AI 크루를 초기화하고 실행한다.
    """
    setup_environment()

    # 사용할 언어 모델을 정의한다.
    # 제미나이 2.0 시리즈 모델로 업데이트하여 성능과 기능을 향상시켰다.
    # 최첨단(프리뷰) 기능이 필요하면 "gemini-2.5-flash"를 사용할 수 있다.
    llm = ChatGoogleGenerativeAI(model="gemini-2.0-flash")

    # 특정 역할과 목표를 지닌 에이전트를 정의한다.
    researcher = Agent(
        role='Senior Research Analyst',
        goal='Find and summarize the latest trends in AI.',
        backstory="You are an experienced research analyst with a knack for identifying key trends and synthesizing information.",
        verbose=True,
        allow_delegation=False,
    )

    writer = Agent(
        role='Technical Content Writer',
        goal='Write a clear and engaging blog post based on research findings.',
        backstory="You are a skilled writer who can translate complex technical topics into accessible content.",
        verbose=True,
        allow_delegation=False,
    )

    # 에이전트에 할당할 작업을 정의한다.
    research_task = Task(
        description="Research the top 3 emerging trends in Artificial Intelligence in 2024-2025. Focus on practical applications and potential impact.",
        expected_output="A detailed summary of the top 3 AI trends, including key points and sources.",
        agent=researcher,
    )

    writing_task = Task(
        description="Write a 500-word blog post based on the research findings. The post should be engaging and easy for a general audience to understand.",
        expected_output="A complete 500-word blog post about the latest AI trends.",
        agent=writer,
        context=[research_task],   # 조사 작업의 출력에 의존한다
    )

    # 크루를 생성한다.
    blog_creation_crew = Crew(
        agents=[researcher, writer],
        tasks=[research_task, writing_task],
        process=Process.sequential,
        llm=llm,
        verbose=2  # 자세한 크루 실행 로그를 보기 위한 설정
    )

    # 크루를 실행한다.
    print("## Running the blog creation crew with Gemini 2.0 Flash... ##")
    try:
        result = blog_creation_crew.kickoff()
        print("\n------------------\n")
        print("## Crew Final Output ##")
        print(result)
    except Exception as e:
        print(f"\nAn unexpected error occurred: {e}")

if __name__ == "__main__":
    main()

핵심은 writing_taskcontext=[research_task] 입니다. 이 한 줄이 순차적 핸드오프를 만듭니다. 작성 작업이 조사 작업의 출력을 입력으로 받으며, Process.sequential 이 실행 순서를 보장합니다.

실습 코드 (구글 ADK)

ADK는 계층형·순차·병렬·루프도구로서의 에이전트까지 조율 패러다임을 클래스로 제공합니다.

계층형 — 부모-자식 관계

LlmAgentBaseAgent 에서 파생된 TaskExecutor 두 종류의 에이전트를 정의합니다. TaskExecutor 는 LLM을 사용하지 않으며, 그저 이벤트를 발생시키는 역할만 합니다.

from google.adk.agents import LlmAgent, BaseAgent
from google.adk.agents.invocation_context import InvocationContext
from google.adk.events import Event
from typing import AsyncGenerator

# BaseAgent를 상속해 사용자 정의 에이전트를 올바르게 구현한다.
class TaskExecutor(BaseAgent):
    """LLM을 쓰지 않고 사용자 정의 동작을 수행하는 전문 에이전트."""
    name: str = "TaskExecutor"
    description: str = "Executes a predefined task."

    async def _run_async_impl(self, context: InvocationContext) -> AsyncGenerator[Event, None]:
        """작업별 사용자 정의 구현 로직."""
        # 여기에 사용자 정의 로직을 작성한다.
        # 이 예제에서는 단순한 이벤트만 생성한다.
        yield Event(author=self.name, content="Task finished successfully.")

# 개별 에이전트를 올바르게 초기화한다.
# LlmAgent는 모델을 반드시 지정해야 한다.
greeter = LlmAgent(
    name="Greeter",
    model="gemini-2.0-flash-exp",
    instruction="You are a friendly greeter."
)

task_doer = TaskExecutor()   # 구현한 사용자 정의 에이전트를 인스턴스화한다.

# 부모 에이전트를 만들고 하위 에이전트를 할당한다.
# 부모 에이전트는 설명과 지시문을 바탕으로 어떤 하위 에이전트에 작업을 위임할지 결정한다.
coordinator = LlmAgent(
    name="Coordinator",
    model="gemini-2.0-flash-exp",
    description="A coordinator that can greet users and execute tasks.",
    instruction="When asked to greet, delegate to the Greeter. When asked to perform a task, delegate to the TaskExecutor.",
    sub_agents=[
        greeter,
        task_doer
    ]
)

# ADK 프레임워크가 부모-자식 관계를 자동으로 설정한다.
# 초기화 뒤 확인하면 아래 단언문이 모두 통과한다.
assert greeter.parent_agent == coordinator
assert task_doer.parent_agent == coordinator
print("Agent hierarchy created successfully.")

모든 하위 에이전트가 LLM을 쓸 필요는 없다는 점이 이 예제의 요지 중 하나입니다. TaskExecutor 는 결정적 로직만 수행하고, 코디네이터는 그것을 다른 LLM 에이전트와 똑같이 다룹니다.

LoopAgent — 반복 워크플로

ConditionChecker 는 세션 상태의 "status" 값을 확인하는 직접 정의한 에이전트입니다. "completed" 이면 루프를 중지하기 위해 이벤트를 상위로 이관(escalate) 하고, 그렇지 않으면 루프를 계속하는 이벤트를 생성합니다.

import asyncio
from typing import AsyncGenerator
from google.adk.agents import LoopAgent, LlmAgent, BaseAgent
from google.adk.events import Event, EventActions
from google.adk.agents.invocation_context import InvocationContext

# 모범 사례: 사용자 정의 에이전트는 역할과 설정이 드러나도록 하나의 완결된 클래스로 정의한다.
class ConditionChecker(BaseAgent):
    """세션 상태에서 'completed' 상태를 확인하는 사용자 정의 에이전트."""
    name: str = "ConditionChecker"
    description: str = "Checks if a process is complete and signals the loop to stop."

    async def _run_async_impl(
        self, context: InvocationContext
    ) -> AsyncGenerator[Event, None]:
        """상태를 확인하고, 루프를 계속하거나 중지하는 이벤트를 생성한다."""
        status = context.session.state.get("status", "pending")
        is_done = (status == "completed")

        if is_done:
            # 조건이 충족되면 상위로 이관하여 루프를 종료한다.
            yield Event(author=self.name, actions=EventActions(escalate=True))
        else:
            # 루프를 계속하기 위해 단순 이벤트를 생성한다.
            yield Event(author=self.name, content="Condition not met, continuing loop.")

# 참고: LlmAgent에는 모델과 명확한 지시가 반드시 필요하다.
process_step = LlmAgent(
    name="ProcessingStep",
    model="gemini-2.0-flash-exp",
    instruction="You are a step in a longer process. Perform your task. If you are the final step, update session state by setting 'status' to 'completed'."
)

# LoopAgent가 워크플로를 오케스트레이션한다.
poller = LoopAgent(
    name="StatusPoller",
    max_iterations=10,
    sub_agents=[
        process_step,
        ConditionChecker()   # 잘 정의된 사용자 정의 에이전트를 인스턴스화한다.
    ]
)

# 이 poller는 'process_step'을 실행한 다음
# 'ConditionChecker'를 실행하는 과정을
# 상태가 'completed'가 되거나 10회 반복에 도달할 때까지 반복한다.

max_iterations=10escalate=True 두 가지가 종료 장치입니다. 4장 리플렉션의 반복 순환을 프레임워크 수준에서 제공하는 셈입니다.

SequentialAgent — 선형 워크플로

from google.adk.agents import SequentialAgent, Agent

# 이 에이전트의 출력은 session.state["data"]에 저장된다.
step1 = Agent(name="Step1_Fetch", output_key="data")

# 이 에이전트는 이전 단계의 데이터를 사용한다.
# 데이터를 찾고 활용하는 방법을 지시로 전달한다.
step2 = Agent(
    name="Step2_Process",
    instruction="Analyze the information found in state['data'] and provide a summary."
)

pipeline = SequentialAgent(
    name="MyPipeline",
    sub_agents=[step1, step2]
)

# 파이프라인을 초기 입력과 함께 실행하면 Step1이 먼저 실행되고,
# 그 응답이 session.state["data"]에 저장된다. 이어서
# Step2가 실행되며, 지시에 따라 상태에 저장된 정보를 활용한다.

한 에이전트의 출력이 다음 에이전트의 입력이 되는 구조로, 다단계 AI 또는 데이터 처리 파이프라인을 만들 때 흔히 사용되는 패턴입니다. 연결 고리는 output_key 로 지정한 세션 상태 키입니다.

ParallelAgent — 동시 실행

from google.adk.agents import Agent, ParallelAgent

# 가져오기(fetch) 로직은 에이전트의 도구로 구현하는 편이 더 낫다.
# 단순한 설명을 위해 이 예제에서는 에이전트의 지시에 로직을 넣었다.
# 실제 시나리오에서는 도구를 사용해야 한다.

# 병렬로 실행할 개별 에이전트를 정의한다.
weather_fetcher = Agent(
    name="weather_fetcher",
    model="gemini-2.0-flash-exp",
    instruction="Fetch the weather for the given location and return only the weather report.",
    output_key="weather_data"   # 결과가 session.state["weather_data"]에 저장된다.
)

news_fetcher = Agent(
    name="news_fetcher",
    model="gemini-2.0-flash-exp",
    instruction="Fetch the top news story for the given topic and return only that story.",
    output_key="news_data"      # 결과가 session.state["news_data"]에 저장된다.
)

# 하위 에이전트를 오케스트레이션할 ParallelAgent를 생성한다.
data_gatherer = ParallelAgent(
    name="data_gatherer",
    sub_agents=[
        weather_fetcher,
        news_fetcher
    ]
)

3장 병렬화에서 본 ParallelAgent 와 같은 구조입니다. 각 하위 에이전트가 서로 다른 output_key 로 세션 상태에 결과를 쌓고, 실행 완료 후 final_state 에서 수집된 데이터에 접근합니다.

도구로서의 에이전트 (AgentTool)

한 에이전트가 다른 에이전트의 역량을 함수 호출과 유사한 방식으로 활용할 수 있게 하는 구조입니다. 5장에서 말한 “도구 호출”의 확장 — 도구가 곧 다른 에이전트인 경우입니다.

from google.adk.agents import LlmAgent
from google.adk.tools import agent_tool
from google.genai import types

# 1. 핵심 기능을 담당하는 간단한 함수 도구.
# 행동과 추론을 분리하는 모범 사례를 따른다.
def generate_image(prompt: str) -> dict:
    """
    텍스트 프롬프트를 바탕으로 이미지를 생성한다.

    Args:
        prompt: 생성할 이미지를 자세히 설명하는 프롬프트.

    Returns:
        상태와 생성된 이미지 바이트를 담은 딕셔너리.
    """
    print(f"TOOL: Generating image for prompt: '{prompt}'")
    # 실제 구현에서는 이미지 생성 API를 호출한다.
    # 이 예제에서는 모의 이미지 데이터를 반환한다.
    mock_image_bytes = b"mock_image_data_for_a_cat_wearing_a_hat"
    return {
        "status": "success",
        # 도구는 원시 바이트를 반환하고, 에이전트가 Part 생성을 처리한다.
        "image_bytes": mock_image_bytes,
        "mime_type": "image/png"
    }

# 2. ImageGeneratorAgent를 LlmAgent로 리팩터링한다.
# 이제 전달받은 입력을 올바르게 사용한다.
image_generator_agent = LlmAgent(
    name="ImageGen",
    model="gemini-2.0-flash",
    description="Generates an image based on a detailed text prompt.",
    instruction=(
        "You are an image generation specialist. Your task is to take the user's request "
        "and use the `generate_image` tool to create the image. "
        "The user's entire request should be used as the 'prompt' argument for the tool. "
        "After the tool returns the image bytes, you MUST output the image."
    ),
    tools=[generate_image]
)

# 3. 수정한 에이전트를 AgentTool로 래핑한다.
# 여기의 description은 부모 에이전트가 보는 설명이다.
image_tool = agent_tool.AgentTool(
    agent=image_generator_agent,
    description="Use this tool to generate an image. The input should be a descriptive prompt of the desired image."
)

# 4. 부모 에이전트는 바뀌지 않는다. 기존 로직이 이미 올바르기 때문이다.
artist_agent = LlmAgent(
    name="Artist",
    model="gemini-2.0-flash",
    instruction=(
        "You are a creative artist. First, invent a creative and descriptive prompt for an image. "
        "Then, use the `ImageGen` tool to generate the image using your prompt."
    ),
    tools=[image_tool]
)

흐름을 정리하면 이렇습니다.

artist_agent (창의적 프롬프트를 만든다)
    ↓ image_tool 호출
AgentTool (다리 역할)
    ↓ 아티스트가 만든 프롬프트로 실행
image_generator_agent
    ↓ generate_image 함수 호출
generate_image (모의 이미지 바이트 반환)
    ↑ 에이전트 사슬을 거슬러 올라가 반환

AgentTool 은 한 에이전트가 다른 에이전트를 도구처럼 쓸 수 있게 해 주는 다리 역할을 합니다. 상위 에이전트가 하위의 전문 에이전트를 오케스트레이션하여 작업을 수행하는 계층형 시스템입니다.

정리

멀티 에이전트 협업이란 무엇인가?

복잡한 문제는 단일 LLM 기반 에이전트의 역량을 넘어서는 경우가 많습니다. 단독 에이전트는 다면적인 작업의 모든 부분을 처리하는 데 필요한 다양한 전문 역량이나 특정 도구에 접근할 권한을 갖추지 못할 수 있습니다. 이러한 한계는 병목을 만들어 시스템의 전반적 효율성과 확장성을 떨어뜨립니다. 그 결과, 여러 도메인에 걸친 정교한 목표를 다루는 과정이 비효율적이 되며, 불완전하거나 차선의 결과를 초래할 수 있습니다.

왜 사용하는가?

멀티 에이전트 협업 패턴은 여러 에이전트가 협력하는 시스템을 구축하여 이 문제를 해결하는 표준화된 해결책입니다. 복잡한 문제를 더 작고 관리하기 쉬운 하위 문제로 분해한 뒤, 각 하위 문제를 해결에 필요한 정확한 도구와 역량을 갖춘 전문 에이전트에게 배정합니다. 에이전트는 순차적 핸드오프, 병렬 처리, 계층적 위임 같은 정의된 통신 프로토콜과 상호작용 모델을 통해 협력합니다. 이러한 에이전틱 분산 접근 방식은 시너지 효과를 만들어 내어, 어떤 단일 에이전트로도 불가능한 결과를 집단으로 달성할 수 있게 합니다.

언제 사용해야 하는가?

단일 에이전트가 감당하기에는 작업이 너무 복잡하고, 전문 역량이나 도구가 필요한 개별 하위 작업으로 분해할 수 있을 때 이 패턴을 사용합니다. 다양한 전문성, 병렬 처리, 또는 여러 단계로 이루어진 구조화된 워크플로가 유리한 문제에 적합합니다. 복합 연구 및 분석, 소프트웨어 개발, 창작 콘텐츠 생성 등이 대표적인 사례입니다.

멀티 에이전트 협업 패턴 개요도

                        ┌───────────────────────────┐
                        │  에이전트(1)  에이전트(2)  │
  사용자 ──► 프롬프트 ──►│                           │
     ▲                  │  에이전트(3)  에이전트(n)  │
     │                  └─────────────┬─────────────┘
     └──────────  출력  ◄─────────────┘

그림 7.3 — 멀티 에이전트 디자인 패턴. 여러 에이전트 간의 연결이 있을 수 있다

핵심 정리

  • 멀티 에이전트 협업은 여러 에이전트가 공통 목표를 달성하기 위해 함께 작동하는 방식입니다.
  • 이 패턴은 전문화된 역할, 분산된 작업, 에이전트 간 통신을 활용합니다.
  • 협업은 순차적 핸드오프, 병렬 처리, 토론, 계층 구조 등 다양한 형태를 취할 수 있습니다.
  • 이 패턴은 다양한 전문성이나 여러 개별 단계가 필요한 복잡한 문제에 적합합니다.

결론

이 장에서는 멀티 에이전트 협업 패턴을 탐구하며, 시스템 내에서 여러 전문 에이전트를 오케스트레이션할 때 얻을 수 있는 이점을 살펴보았습니다. 다양한 협업 모델을 검토하면서, 여러 도메인에 걸친 복합적 문제를 해결하는 데 이 패턴이 얼마나 핵심적인 역할을 하는지 확인했습니다.

에이전트 협업을 이해하면, 자연스럽게 에이전트가 외부 환경과 어떻게 상호작용하는지 탐구하게 됩니다.

참고 문헌