Agents Course documentation

멀티 에이전트 시스템

Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

Ask a Question Open In Colab

멀티 에이전트 시스템

멀티 에이전트 시스템은 특화된 에이전트들이 협력하여 복잡한 작업을 해결할 수 있게 해주며, 모듈성, 확장성, 견고성을 높여줍니다. 단일 에이전트에 의존하는 대신, 각기 다른 능력을 가진 에이전트들이 작업을 분담합니다.

smolagents에서는 다양한 에이전트를 결합해 파이썬 코드 생성, 외부 도구 호출, 웹 검색 등 다양한 작업을 수행할 수 있습니다. 이러한 에이전트들을 조율함으로써 강력한 워크플로우를 만들 수 있습니다.

일반적인 구성 예시:

  • 매니저 에이전트: 작업 분배 담당
  • 코드 인터프리터 에이전트: 코드 실행 담당
  • 웹 검색 에이전트: 정보 검색 담당

아래 다이어그램은 매니저 에이전트코드 인터프리터 도구웹 검색 에이전트를 조율하는 간단한 멀티 에이전트 아키텍처를 보여줍니다. 웹 검색 에이전트는 DuckDuckGoSearchToolVisitWebpageTool 같은 도구를 활용해 정보를 수집합니다.

멀티 에이전트 시스템 실전 예시

멀티 에이전트 시스템은 여러 특화 에이전트가 오케스트레이터 에이전트의 조율 아래 협력하는 구조입니다. 이 방식은 역할이 다른 에이전트들에게 작업을 분산시켜 복잡한 워크플로우를 구현할 수 있게 해줍니다.

예를 들어, 멀티 에이전트 RAG 시스템은 다음을 통합할 수 있습니다:

  • 웹 에이전트: 인터넷 브라우징 담당
  • 리트리버 에이전트: 지식 베이스 정보 검색 담당
  • 이미지 생성 에이전트: 시각 자료 생성 담당

이 모든 에이전트는 오케스트레이터가 작업 분배와 상호작용을 관리합니다.

멀티 에이전트 계층 구조로 복잡한 문제 해결하기

이 노트북을 따라가며 코드를 직접 실행해볼 수 있습니다.

리셉션이 다가오고 있습니다! 여러분의 도움으로 Alfred는 거의 모든 준비를 마쳤습니다.

하지만 문제가 생겼습니다: 배트모빌이 사라졌습니다. Alfred는 대체 차량을 빨리 찾아야 합니다.

다행히 브루스 웨인의 삶을 다룬 바이오픽 영화가 몇 편 제작되었으니, Alfred는 영화 세트장에 남겨진 차량을 찾아 현대식으로 개조할 수 있을지도 모릅니다. 물론 자율주행 기능도 필수겠죠.

하지만 이 차량들은 전 세계 촬영지 어디에나 있을 수 있습니다.

그래서 Alfred는 여러분의 도움이 필요합니다. 이 문제를 해결할 수 있는 에이전트를 만들어볼까요?

👉 전 세계 배트맨 촬영지를 모두 찾아, 고담(40.7128° N, 74.0060° W)까지 화물선(비행기)으로 이동하는 데 걸리는 시간을 계산하고, 이를 지도에 표시하세요. 이동 시간에 따라 색상을 다르게 하고, 슈퍼카 공장도 같은 방식으로 표시하세요.

함께 만들어봅시다!

이 예시에는 추가 패키지가 필요하니 먼저 설치해 주세요:

pip install 'smolagents[litellm]' plotly geopandas shapely kaleido -q

먼저 화물기 이동 시간 계산 도구를 만듭니다.

import math
from typing import Optional, Tuple

from smolagents import tool


@tool
def calculate_cargo_travel_time(
    origin_coords: Tuple[float, float],
    destination_coords: Tuple[float, float],
    cruising_speed_kmh: Optional[float] = 750.0,  # 화물기 평균 속도
) -> float:
    """
    두 지점(위도, 경도) 간 화물기 이동 시간을 대권거리로 계산합니다.

    Args:
        origin_coords: 출발지 (위도, 경도) 튜플
        destination_coords: 도착지 (위도, 경도) 튜플
        cruising_speed_kmh: 순항 속도(km/h, 기본값 750)

    Returns:
        float: 예상 이동 시간(시간 단위)

    Example:
        >>> # 시카고(41.8781° N, 87.6298° W) → 시드니(33.8688° S, 151.2093° E)
        >>> result = calculate_cargo_travel_time((41.8781, -87.6298), (-33.8688, 151.2093))
    """

    def to_radians(degrees: float) -> float:
        return degrees * (math.pi / 180)

    # 좌표 변환
    lat1, lon1 = map(to_radians, origin_coords)
    lat2, lon2 = map(to_radians, destination_coords)

    # 지구 반지름(km)
    EARTH_RADIUS_KM = 6371.0

    # haversine 공식으로 대권거리 계산
    dlon = lon2 - lon1
    dlat = lat2 - lat1

    a = (
        math.sin(dlat / 2) ** 2
        + math.cos(lat1) * math.cos(lat2) * math.sin(dlon / 2) ** 2
    )
    c = 2 * math.asin(math.sqrt(a))
    distance = EARTH_RADIUS_KM * c

    # 우회 경로, 관제 등 감안해 10% 추가
    actual_distance = distance * 1.1

    # 비행 시간 계산(이착륙 1시간 추가)
    flight_time = (actual_distance / cruising_speed_kmh) + 1.0

    return round(flight_time, 2)


print(calculate_cargo_travel_time((41.8781, -87.6298), (-33.8688, 151.2093)))

에이전트 설정하기

모델 제공자는 Together AI를 사용합니다. GoogleSearchTool은 Serper API를 사용하므로, 환경 변수 SERPAPI_API_KEY(provider=“serpapi”) 또는 SERPER_API_KEY(provider=serper)를 설정해야 합니다.

Serp API가 없다면 DuckDuckGoSearchTool을 사용할 수 있지만, 속도 제한에 유의하세요.

import os
from PIL import Image
from smolagents import CodeAgent, GoogleSearchTool, InferenceClientModel, VisitWebpageTool

model = InferenceClientModel(model_id="Qwen/Qwen2.5-Coder-32B-Instruct", provider="together")

간단한 에이전트를 만들어 기본 리포트를 받아봅시다.

task = """전 세계 배트맨 촬영지를 모두 찾아, 고담(40.7128° N, 74.0060° W)까지 화물기로 이동하는 시간을 계산해 pandas 데이터프레임으로 반환하세요. 슈퍼카 공장도 같은 방식으로 포함하세요."""
agent = CodeAgent(
    model=model,
    tools=[GoogleSearchTool("serper"), VisitWebpageTool(), calculate_cargo_travel_time],
    additional_authorized_imports=["pandas"],
    max_steps=20,
)
result = agent.run(task)
result

실행 결과 예시:

|  | Location                                             | Travel Time to Gotham (hours) |
|--|------------------------------------------------------|------------------------------|
| 0  | Necropolis Cemetery, Glasgow, Scotland, UK         | 8.60                         |
| 1  | St. George's Hall, Liverpool, England, UK         | 8.81                         |
| 2  | Two Temple Place, London, England, UK             | 9.17                         |
| 3  | Wollaton Hall, Nottingham, England, UK           | 9.00                         |
| 4  | Knebworth House, Knebworth, Hertfordshire, UK    | 9.15                         |
| 5  | Acton Lane Power Station, Acton Lane, Acton, UK  | 9.16                         |
| 6  | Queensboro Bridge, New York City, USA            | 1.01                         |
| 7  | Wall Street, New York City, USA                  | 1.00                         |
| 8  | Mehrangarh Fort, Jodhpur, Rajasthan, India       | 18.34                        |
| 9  | Turda Gorge, Turda, Romania                      | 11.89                        |
| 10 | Chicago, USA                                     | 2.68                         |
| 11 | Hong Kong, China                                 | 19.99                        |
| 12 | Cardington Studios, Northamptonshire, UK        | 9.10                         |
| 13 | Warner Bros. Leavesden Studios, Hertfordshire, UK | 9.13                         |
| 14 | Westwood, Los Angeles, CA, USA                  | 6.79                         |
| 15 | Woking, UK (McLaren)                             | 9.13                         |

계획 단계(planning step)를 추가하고 프롬프트를 보강하면 더 나은 결과를 얻을 수 있습니다.

계획 단계는 에이전트가 다음 단계를 미리 생각하고 계획할 수 있게 해줍니다.

agent.planning_interval = 4

detailed_report = agent.run(f"""
여러분은 전문 분석가입니다. 여러 웹사이트를 방문해 종합 리포트를 작성합니다.
for 루프를 활용해 여러 쿼리를 한 번에 검색하세요.
각 데이터 포인트마다 소스 URL을 방문해 수치를 확인하세요.

{task}
""")

print(detailed_report)
detailed_report

실행 결과 예시:

|  | Location                                         | Travel Time (hours) |
|--|--------------------------------------------------|---------------------|
| 0  | Bridge of Sighs, Glasgow Necropolis, Glasgow, UK | 8.6                 |
| 1  | Wishart Street, Glasgow, Scotland, UK         | 8.6                 |

이처럼 프롬프트와 계획 기능만으로도 훨씬 간결한 리포트를 얻을 수 있습니다!

모델의 컨텍스트 윈도우가 빠르게 채워집니다. 따라서 상세 검색 결과와 다른 결과를 결합하도록 요청하면 느려지고 토큰·비용이 급증할 수 있습니다.

➡️ 시스템 구조를 개선해야 합니다.

✌️ 두 에이전트로 작업 분할하기

멀티 에이전트 구조는 서로 다른 하위 작업의 메모리를 분리해 두 가지 큰 이점을 제공합니다:

  • 각 에이전트가 핵심 작업에 집중해 성능이 향상됩니다.
  • 메모리 분리로 각 단계의 입력 토큰 수가 줄어, 지연과 비용이 감소합니다.

웹 검색 전담 에이전트와 이를 관리하는 매니저 에이전트로 팀을 만들어봅시다.

매니저 에이전트는 최종 리포트 작성을 위해 추가 import(plotly, geopandas, shapely)가 필요합니다.

model = InferenceClientModel(
    "Qwen/Qwen2.5-Coder-32B-Instruct", provider="together", max_tokens=8096
)

web_agent = CodeAgent(
    model=model,
    tools=[
        GoogleSearchTool(provider="serper"),
        VisitWebpageTool(),
        calculate_cargo_travel_time,
    ],
    name="web_agent",
    description="웹에서 정보를 찾는 역할",
    verbosity_level=0,
    max_steps=10,
)

매니저 에이전트는 더 강력한 모델 DeepSeek-R1을 사용하고, planning_interval도 추가합니다.

from smolagents.utils import encode_image_base64, make_image_url
from smolagents import OpenAIServerModel


def check_reasoning_and_plot(final_answer, agent_memory):
    multimodal_model = OpenAIServerModel("gpt-4o", max_tokens=8096)
    filepath = "saved_map.png"
    assert os.path.exists(filepath), "saved_map.png 파일이 저장되어야 합니다!"
    image = Image.open(filepath)
    prompt = (
        f"다음은 사용자 요청과 에이전트 단계입니다: {agent_memory.get_succinct_steps()}. 아래는 생성된 지도입니다."
        "추론 과정과 지도가 요청을 올바르게 해결하는지 확인하세요."
        "먼저 이유를 나열한 뒤, 최종 결정(PASS/FAIL)을 작성하세요."
        "너무 엄격할 필요는 없습니다. 대부분 해결했다면 PASS입니다."
        "지도는 px.scatter_map으로 만들어야 PASS입니다."
    )
    messages = [
        {
            "role": "user",
            "content": [
                {
                    "type": "text",
                    "text": prompt,
                },
                {
                    "type": "image_url",
                    "image_url": {"url": make_image_url(encode_image_base64(image))},
                },
            ],
        }
    ]
    output = multimodal_model(messages).content
    print("피드백: ", output)
    if "FAIL" in output:
        raise Exception(output)
    return True


manager_agent = CodeAgent(
    model=InferenceClientModel("deepseek-ai/DeepSeek-R1", provider="together", max_tokens=8096),
    tools=[calculate_cargo_travel_time],
    managed_agents=[web_agent],
    additional_authorized_imports=[
        "geopandas",
        "plotly",
        "shapely",
        "json",
        "pandas",
        "numpy",
    ],
    planning_interval=5,
    verbosity_level=2,
    final_answer_checks=[check_reasoning_and_plot],
    max_steps=15,
)

이 팀의 구조를 시각화해봅시다:

manager_agent.visualize()

아래와 같이 구조와 도구 사용 관계를 한눈에 볼 수 있습니다:

CodeAgent | deepseek-ai/DeepSeek-R1
├── ✅ Authorized imports: ['geopandas', 'plotly', 'shapely', 'json', 'pandas', 'numpy']
├── 🛠️ Tools:
│   ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
│   ┃ Name                        ┃ Description                           ┃ Arguments                             ┃
│   ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│   │ calculate_cargo_travel_time │ 두 지점 간 화물기 이동 시간 계산         │ origin_coords (`array`): 출발지 좌표   │
│   │                             │                                       │ destination_coords (`array`): 도착지   │
│   │                             │                                       │ cruising_speed_kmh (`number`): 속도    │
│   │ final_answer                │ 문제에 대한 최종 답변 제공              │ answer (`any`): 최종 답변              │
│   └─────────────────────────────┴───────────────────────────────────────┴───────────────────────────────────────┘
└── 🤖 Managed agents:
    └── web_agent | CodeAgent | Qwen/Qwen2.5-Coder-32B-Instruct
        ├── ✅ Authorized imports: []
        ├── 📝 Description: 웹에서 정보를 찾는 역할
        └── 🛠️ Tools:
            ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
            ┃ Name                        ┃ Description                       ┃ Arguments                         ┃
            ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
            │ web_search                  │ 구글 웹 검색 수행                  │ query (`string`): 검색어           │
            │                             │                                   │ filter_year (`integer`): 연도 제한 │
            │ visit_webpage               │ 웹페이지 방문 및 내용 읽기         │ url (`string`): 방문할 URL         │
            │ calculate_cargo_travel_time │ 두 지점 간 화물기 이동 시간 계산   │ origin_coords (`array`): 출발지     │
            │                             │                                   │ destination_coords (`array`): 도착지│
            │                             │                                   │ cruising_speed_kmh (`number`): 속도 │
            │ final_answer                │ 문제에 대한 최종 답변 제공         │ answer (`any`): 최종 답변           │
            └─────────────────────────────┴───────────────────────────────────┴───────────────────────────────────┘
manager_agent.run("""
전 세계 배트맨 촬영지를 모두 찾아, 고담(40.7128° N, 74.0060° W)까지 화물기로 이동하는 시간을 계산하세요. 슈퍼카 공장도 같은 방식으로 포함해 총 6개 이상 지점을 지도에 산점도로 표시하고, 이동 시간에 따라 색상을 다르게 하세요. 결과 이미지는 saved_map.png로 저장하세요!

지도 예시:
import plotly.express as px
df = px.data.carshare()
fig = px.scatter_map(df, lat="centroid_lat", lon="centroid_lon", text="name", color="peak_hour", size=100,
     color_continuous_scale=px.colors.sequential.Magma, size_max=15, zoom=1)
fig.show()
fig.write_image("saved_image.png")
final_answer(fig)

문자열을 코드로 처리하지 마세요. 문자열이 있으면 print로 출력하세요.
""")

제 실행에서는 매니저 에이전트가 웹 에이전트에게 1. 배트맨 촬영지 검색, 2. 슈퍼카 공장 찾기로 작업을 분배한 뒤, 결과를 집계해 지도를 그렸습니다.

에이전트 상태에서 지도를 직접 확인해봅시다:

manager_agent.python_executor.state["fig"]

아래와 같이 지도가 출력됩니다:

멀티에이전트 시스템 예시 출력 지도

참고 자료

Update on GitHub