Overview

supervisor 패턴은 중앙 supervisor agent가 전문화된 worker agent들을 조정하는 multi-agent 아키텍처입니다. 이 접근 방식은 작업에 다양한 유형의 전문 지식이 필요할 때 탁월합니다. 도메인 전반에 걸쳐 tool 선택을 관리하는 하나의 agent를 구축하는 대신, supervisor가 전체 워크플로우를 이해하고 조정하는 집중된 전문가들을 만듭니다. 이 튜토리얼에서는 현실적인 워크플로우를 통해 이러한 이점을 보여주는 개인 비서 시스템을 구축합니다. 시스템은 근본적으로 다른 책임을 가진 두 명의 전문가를 조정합니다:
  • 일정 관리, 가용성 확인 및 이벤트 관리를 처리하는 calendar agent.
  • 커뮤니케이션 관리, 메시지 초안 작성 및 알림 전송을 관리하는 email agent.
또한 사용자가 원하는 대로 작업(예: 발신 이메일)을 승인, 편집 및 거부할 수 있도록 human-in-the-loop review를 통합합니다.

supervisor를 사용하는 이유는?

Multi-agent 아키텍처를 사용하면 tools를 worker들에게 분할할 수 있으며, 각각 고유한 prompt나 지침을 가집니다. 모든 calendar 및 email API에 직접 액세스할 수 있는 agent를 고려해보세요: 많은 유사한 tool 중에서 선택하고, 각 API의 정확한 형식을 이해하며, 여러 도메인을 동시에 처리해야 합니다. 성능이 저하되면 관련 tool과 관련 prompt를 논리적 그룹으로 분리하는 것이 도움이 될 수 있습니다(부분적으로 반복적인 개선을 관리하기 위해).

개념

다음 개념을 다룹니다:

Setup

Installation

이 튜토리얼은 langchain 패키지가 필요합니다:
pip install langchain
자세한 내용은 Installation guide를 참조하세요.

LangSmith

agent 내부에서 무슨 일이 일어나는지 검사하려면 LangSmith를 설정하세요. 그런 다음 다음 환경 변수를 설정합니다:
export LANGSMITH_TRACING="true"
export LANGSMITH_API_KEY="..."

Components

LangChain의 통합 제품군에서 chat model을 선택해야 합니다:
  • OpenAI
  • Anthropic
  • Azure
  • Google Gemini
  • AWS Bedrock
👉 OpenAI chat model 통합 문서를 읽어보세요
pip install -U "langchain[openai]"
import os
from langchain.chat_models import init_chat_model

os.environ["OPENAI_API_KEY"] = "sk-..."

model = init_chat_model("openai:gpt-4.1")

1. Define tools

구조화된 입력이 필요한 tool을 정의하는 것부터 시작합니다. 실제 애플리케이션에서는 실제 API(Google Calendar, SendGrid 등)를 호출합니다. 이 튜토리얼에서는 패턴을 시연하기 위해 stub을 사용합니다.
from langchain_core.tools import tool

@tool
def create_calendar_event(
    title: str,
    start_time: str,       # ISO format: "2024-01-15T14:00:00"
    end_time: str,         # ISO format: "2024-01-15T15:00:00"
    attendees: list[str],  # email addresses
    location: str = ""
) -> str:
    """Create a calendar event. Requires exact ISO datetime format."""
    # Stub: In practice, this would call Google Calendar API, Outlook API, etc.
    return f"Event created: {title} from {start_time} to {end_time} with {len(attendees)} attendees"


@tool
def send_email(
    to: list[str],  # email addresses
    subject: str,
    body: str,
    cc: list[str] = []
) -> str:
    """Send an email via email API. Requires properly formatted addresses."""
    # Stub: In practice, this would call SendGrid, Gmail API, etc.
    return f"Email sent to {', '.join(to)} - Subject: {subject}"


@tool
def get_available_time_slots(
    attendees: list[str],
    date: str,  # ISO format: "2024-01-15"
    duration_minutes: int
) -> list[str]:
    """Check calendar availability for given attendees on a specific date."""
    # Stub: In practice, this would query calendar APIs
    return ["09:00", "14:00", "16:00"]

2. Create specialized sub-agents

다음으로 각 도메인을 처리하는 전문화된 sub-agent를 만듭니다.

Create a calendar agent

calendar agent는 자연어 일정 요청을 이해하고 정확한 API 호출로 변환합니다. 날짜 파싱, 가용성 확인 및 이벤트 생성을 처리합니다.
from langchain.agents import create_agent


CALENDAR_AGENT_PROMPT = (
    "You are a calendar scheduling assistant. "
    "Parse natural language scheduling requests (e.g., 'next Tuesday at 2pm') "
    "into proper ISO datetime formats. "
    "Use get_available_time_slots to check availability when needed. "
    "Use create_calendar_event to schedule events. "
    "Always confirm what was scheduled in your final response."
)

calendar_agent = create_agent(
    model,
    tools=[create_calendar_event, get_available_time_slots],
    system_prompt=CALENDAR_AGENT_PROMPT,
)
calendar agent를 테스트하여 자연어 일정을 어떻게 처리하는지 확인하세요:
query = "Schedule a team meeting next Tuesday at 2pm for 1 hour"

for step in calendar_agent.stream(
    {"messages": [{"role": "user", "content": query}]}
):
    for update in step.values():
        for message in update.get("messages", []):
            message.pretty_print()
================================== Ai Message ==================================
Tool Calls:
  get_available_time_slots (call_EIeoeIi1hE2VmwZSfHStGmXp)
 Call ID: call_EIeoeIi1hE2VmwZSfHStGmXp
  Args:
    attendees: []
    date: 2024-06-18
    duration_minutes: 60
================================= Tool Message =================================
Name: get_available_time_slots

["09:00", "14:00", "16:00"]
================================== Ai Message ==================================
Tool Calls:
  create_calendar_event (call_zgx3iJA66Ut0W8S3NpT93kEB)
 Call ID: call_zgx3iJA66Ut0W8S3NpT93kEB
  Args:
    title: Team Meeting
    start_time: 2024-06-18T14:00:00
    end_time: 2024-06-18T15:00:00
    attendees: []
================================= Tool Message =================================
Name: create_calendar_event

Event created: Team Meeting from 2024-06-18T14:00:00 to 2024-06-18T15:00:00 with 0 attendees
================================== Ai Message ==================================

The team meeting has been scheduled for next Tuesday, June 18th, at 2:00 PM and will last for 1 hour. If you need to add attendees or a location, please let me know!
agent는 “next Tuesday at 2pm”을 ISO 형식(“2024-01-16T14:00:00”)으로 파싱하고, 종료 시간을 계산하고, create_calendar_event를 호출하고, 자연어 확인을 반환합니다.

Create an email agent

email agent는 메시지 작성 및 전송을 처리합니다. 수신자 정보 추출, 적절한 제목 및 본문 텍스트 작성, 이메일 커뮤니케이션 관리에 중점을 둡니다.
EMAIL_AGENT_PROMPT = (
    "You are an email assistant. "
    "Compose professional emails based on natural language requests. "
    "Extract recipient information and craft appropriate subject lines and body text. "
    "Use send_email to send the message. "
    "Always confirm what was sent in your final response."
)

email_agent = create_agent(
    model,
    tools=[send_email],
    system_prompt=EMAIL_AGENT_PROMPT,
)
자연어 요청으로 email agent를 테스트하세요:
query = "Send the design team a reminder about reviewing the new mockups"

for step in email_agent.stream(
    {"messages": [{"role": "user", "content": query}]}
):
    for update in step.values():
        for message in update.get("messages", []):
            message.pretty_print()
================================== Ai Message ==================================
Tool Calls:
  send_email (call_OMl51FziTVY6CRZvzYfjYOZr)
 Call ID: call_OMl51FziTVY6CRZvzYfjYOZr
  Args:
    to: ['[email protected]']
    subject: Reminder: Please Review the New Mockups
    body: Hi Design Team,

This is a friendly reminder to review the new mockups at your earliest convenience. Your feedback is important to ensure that we stay on track with our project timeline.

Please let me know if you have any questions or need additional information.

Thank you!

Best regards,
================================= Tool Message =================================
Name: send_email

Email sent to [email protected] - Subject: Reminder: Please Review the New Mockups
================================== Ai Message ==================================

I've sent a reminder to the design team asking them to review the new mockups. If you need any further communication on this topic, just let me know!
agent는 비공식적인 요청에서 수신자를 추론하고, 전문적인 제목과 본문을 작성하고, send_email을 호출하고, 확인을 반환합니다. 각 sub-agent는 도메인별 tool과 prompt를 사용하여 좁은 초점을 가지므로 특정 작업에서 탁월할 수 있습니다.

3. Wrap sub-agents as tools

이제 각 sub-agent를 supervisor가 호출할 수 있는 tool로 래핑합니다. 이것은 계층화된 시스템을 만드는 핵심 아키텍처 단계입니다. supervisor는 “create_calendar_event”와 같은 저수준 tool이 아닌 “schedule_event”와 같은 고수준 tool을 보게 됩니다.
@tool
def schedule_event(request: str) -> str:
    """Schedule calendar events using natural language.

    Use this when the user wants to create, modify, or check calendar appointments.
    Handles date/time parsing, availability checking, and event creation.

    Input: Natural language scheduling request (e.g., 'meeting with design team
    next Tuesday at 2pm')
    """
    result = calendar_agent.invoke({
        "messages": [{"role": "user", "content": request}]
    })
    return result["messages"][-1].text


@tool
def manage_email(request: str) -> str:
    """Send emails using natural language.

    Use this when the user wants to send notifications, reminders, or any email
    communication. Handles recipient extraction, subject generation, and email
    composition.

    Input: Natural language email request (e.g., 'send them a reminder about
    the meeting')
    """
    result = email_agent.invoke({
        "messages": [{"role": "user", "content": request}]
    })
    return result["messages"][-1].text
tool 설명은 supervisor가 각 tool을 언제 사용할지 결정하는 데 도움이 되므로 명확하고 구체적으로 작성하세요. supervisor는 중간 추론이나 tool 호출을 볼 필요가 없으므로 sub-agent의 최종 응답만 반환합니다.

4. Create the supervisor agent

이제 sub-agent를 조정하는 supervisor를 만듭니다. supervisor는 고수준 tool만 보고 개별 API 수준이 아닌 도메인 수준에서 라우팅 결정을 내립니다.
SUPERVISOR_PROMPT = (
    "You are a helpful personal assistant. "
    "You can schedule calendar events and send emails. "
    "Break down user requests into appropriate tool calls and coordinate the results. "
    "When a request involves multiple actions, use multiple tools in sequence."
)

supervisor_agent = create_agent(
    model,
    tools=[schedule_event, manage_email],
    system_prompt=SUPERVISOR_PROMPT,
)

5. Use the supervisor

이제 여러 도메인에 걸친 조정이 필요한 복잡한 요청으로 전체 시스템을 테스트하세요:

Example 1: Simple single-domain request

query = "Schedule a team standup for tomorrow at 9am"

for step in supervisor_agent.stream(
    {"messages": [{"role": "user", "content": query}]}
):
    for update in step.values():
        for message in update.get("messages", []):
            message.pretty_print()
================================== Ai Message ==================================
Tool Calls:
  schedule_event (call_mXFJJDU8bKZadNUZPaag8Lct)
 Call ID: call_mXFJJDU8bKZadNUZPaag8Lct
  Args:
    request: Schedule a team standup for tomorrow at 9am with Alice and Bob.
================================= Tool Message =================================
Name: schedule_event

The team standup has been scheduled for tomorrow at 9:00 AM with Alice and Bob. If you need to make any changes or add more details, just let me know!
================================== Ai Message ==================================

The team standup with Alice and Bob is scheduled for tomorrow at 9:00 AM. If you need any further arrangements or adjustments, please let me know!
supervisor는 이것을 calendar 작업으로 식별하고, schedule_event를 호출하며, calendar agent가 날짜 파싱 및 이벤트 생성을 처리합니다.
각 chat model 호출에 대한 prompt 및 응답을 포함한 정보 흐름에 대한 완전한 투명성을 위해 위 실행에 대한 LangSmith trace를 확인하세요.

Example 2: Complex multi-domain request

query = (
    "Schedule a meeting with the design team next Tuesday at 2pm for 1 hour, "
    "and send them an email reminder about reviewing the new mockups."
)

for step in supervisor_agent.stream(
    {"messages": [{"role": "user", "content": query}]}
):
    for update in step.values():
        for message in update.get("messages", []):
            message.pretty_print()
================================== Ai Message ==================================
Tool Calls:
  schedule_event (call_YA68mqF0koZItCFPx0kGQfZi)
 Call ID: call_YA68mqF0koZItCFPx0kGQfZi
  Args:
    request: meeting with the design team next Tuesday at 2pm for 1 hour
  manage_email (call_XxqcJBvVIuKuRK794ZIzlLxx)
 Call ID: call_XxqcJBvVIuKuRK794ZIzlLxx
  Args:
    request: send the design team an email reminder about reviewing the new mockups
================================= Tool Message =================================
Name: schedule_event

Your meeting with the design team is scheduled for next Tuesday, June 18th, from 2:00pm to 3:00pm. Let me know if you need to add more details or make any changes!
================================= Tool Message =================================
Name: manage_email

I've sent an email reminder to the design team requesting them to review the new mockups. If you need to include more information or recipients, just let me know!
================================== Ai Message ==================================

Your meeting with the design team is scheduled for next Tuesday, June 18th, from 2:00pm to 3:00pm.

I've also sent an email reminder to the design team, asking them to review the new mockups.

Let me know if you'd like to add more details to the meeting or include additional information in the email!
supervisor는 이것이 calendar와 email 작업 모두를 필요로 한다는 것을 인식하고, 회의를 위해 schedule_event를 호출한 다음 알림을 위해 manage_email을 호출합니다. 각 sub-agent는 작업을 완료하고, supervisor는 두 결과를 일관된 응답으로 합성합니다.
개별 chat model prompt 및 응답을 포함한 위 실행에 대한 자세한 정보 흐름을 보려면 LangSmith trace를 참조하세요.

Complete working example

다음은 실행 가능한 스크립트로 모든 것을 함께 모은 것입니다:

Understanding the architecture

시스템에는 세 개의 계층이 있습니다. 하단 계층에는 정확한 형식이 필요한 엄격한 API tool이 포함되어 있습니다. 중간 계층에는 자연어를 받아들이고, 구조화된 API 호출로 변환하고, 자연어 확인을 반환하는 sub-agent가 포함되어 있습니다. 상단 계층에는 고수준 기능으로 라우팅하고 결과를 합성하는 supervisor가 포함되어 있습니다. 이러한 관심사의 분리는 여러 가지 이점을 제공합니다: 각 계층은 집중된 책임을 가지며, 기존 도메인에 영향을 주지 않고 새 도메인을 추가할 수 있으며, 각 계층을 독립적으로 테스트하고 반복할 수 있습니다.

6. Add human-in-the-loop review

민감한 작업에 대한 human-in-the-loop review를 통합하는 것이 신중할 수 있습니다. LangChain에는 tool 호출을 검토하기 위한 built-in middleware가 포함되어 있으며, 이 경우 sub-agent가 호출하는 tool입니다. 두 sub-agent 모두에 human-in-the-loop review를 추가해 보겠습니다:
  • create_calendar_eventsend_email tool을 중단하도록 구성하여 모든 response types(approve, edit, reject)를 허용합니다
  • 최상위 agent에만 checkpointer를 추가합니다. 이는 실행을 일시 중지하고 재개하는 데 필요합니다.
from langchain.agents import create_agent
from langchain.agents.middleware import HumanInTheLoopMiddleware 
from langgraph.checkpoint.memory import InMemorySaver 


calendar_agent = create_agent(
    model,
    tools=[create_calendar_event, get_available_time_slots],
    system_prompt=CALENDAR_AGENT_PROMPT,
    middleware=[ 
        HumanInTheLoopMiddleware( 
            interrupt_on={"create_calendar_event": True}, 
            description_prefix="Calendar event pending approval", 
        ), 
    ], 
)

email_agent = create_agent(
    model,
    tools=[send_email],
    system_prompt=EMAIL_AGENT_PROMPT,
    middleware=[ 
        HumanInTheLoopMiddleware( 
            interrupt_on={"send_email": True}, 
            description_prefix="Outbound email pending approval", 
        ), 
    ], 
)

supervisor_agent = create_agent(
    model,
    tools=[schedule_event, manage_email],
    system_prompt=SUPERVISOR_PROMPT,
    checkpointer=InMemorySaver(), 
)
쿼리를 반복해 보겠습니다. 다운스트림에서 액세스하기 위해 interrupt 이벤트를 목록으로 수집합니다:
query = (
    "Schedule a meeting with the design team next Tuesday at 2pm for 1 hour, "
    "and send them an email reminder about reviewing the new mockups."
)

config = {"configurable": {"thread_id": "6"}}

interrupts = []
for step in supervisor_agent.stream(
    {"messages": [{"role": "user", "content": query}]},
    config,
):
    for update in step.values():
        if isinstance(update, dict):
            for message in update.get("messages", []):
                message.pretty_print()
        else:
            interrupt_ = update[0]
            interrupts.append(interrupt_)
            print(f"\nINTERRUPTED: {interrupt_.id}")
================================== Ai Message ==================================
Tool Calls:
  schedule_event (call_t4Wyn32ohaShpEZKuzZbl83z)
 Call ID: call_t4Wyn32ohaShpEZKuzZbl83z
  Args:
    request: Schedule a meeting with the design team next Tuesday at 2pm for 1 hour.
  manage_email (call_JWj4vDJ5VMnvkySymhCBm4IR)
 Call ID: call_JWj4vDJ5VMnvkySymhCBm4IR
  Args:
    request: Send an email reminder to the design team about reviewing the new mockups before our meeting next Tuesday at 2pm.

INTERRUPTED: 4f994c9721682a292af303ec1a46abb7

INTERRUPTED: 2b56f299be313ad8bc689eff02973f16
이번에는 실행을 중단했습니다. interrupt 이벤트를 검사해 보겠습니다:
for interrupt_ in interrupts:
    for request in interrupt_.value["action_requests"]:
        print(f"INTERRUPTED: {interrupt_.id}")
        print(f"{request['description']}\n")
INTERRUPTED: 4f994c9721682a292af303ec1a46abb7
Calendar event pending approval

Tool: create_calendar_event
Args: {'title': 'Meeting with the Design Team', 'start_time': '2024-06-18T14:00:00', 'end_time': '2024-06-18T15:00:00', 'attendees': ['design team']}

INTERRUPTED: 2b56f299be313ad8bc689eff02973f16
Outbound email pending approval

Tool: send_email
Args: {'to': ['[email protected]'], 'subject': 'Reminder: Review New Mockups Before Meeting Next Tuesday at 2pm', 'body': "Hello Team,\n\nThis is a reminder to review the new mockups ahead of our meeting scheduled for next Tuesday at 2pm. Your feedback and insights will be valuable for our discussion and next steps.\n\nPlease ensure you've gone through the designs and are ready to share your thoughts during the meeting.\n\nThank you!\n\nBest regards,\n[Your Name]"}
Command를 사용하여 ID를 참조하여 각 interrupt에 대한 결정을 지정할 수 있습니다. 자세한 내용은 human-in-the-loop guide를 참조하세요. 시연 목적으로 여기서는 calendar 이벤트를 수락하지만 발신 이메일의 제목을 편집합니다:
from langgraph.types import Command 

resume = {}
for interrupt_ in interrupts:
    if interrupt_.id == "2b56f299be313ad8bc689eff02973f16":
        # Edit email
        edited_action = interrupt_.value["action_requests"][0].copy()
        edited_action["arguments"]["subject"] = "Mockups reminder"
        resume[interrupt_.id] = {
            "decisions": [{"type": "edit", "edited_action": edited_action}]
        }
    else:
        resume[interrupt_.id] = {"decisions": [{"type": "approve"}]}

interrupts = []
for step in supervisor_agent.stream(
    Command(resume=resume), 
    config,
):
    for update in step.values():
        if isinstance(update, dict):
            for message in update.get("messages", []):
                message.pretty_print()
        else:
            interrupt_ = update[0]
            interrupts.append(interrupt_)
            print(f"\nINTERRUPTED: {interrupt_.id}")
================================= Tool Message =================================
Name: schedule_event

Your meeting with the design team has been scheduled for next Tuesday, June 18th, from 2:00 pm to 3:00 pm.
================================= Tool Message =================================
Name: manage_email

Your email reminder to the design team has been sent. Here’s what was sent:

- Recipient: [email protected]
- Subject: Mockups reminder
- Body: A reminder to review the new mockups before the meeting next Tuesday at 2pm, with a request for feedback and readiness for discussion.

Let me know if you need any further assistance!
================================== Ai Message ==================================

- Your meeting with the design team has been scheduled for next Tuesday, June 18th, from 2:00 pm to 3:00 pm.
- An email reminder has been sent to the design team about reviewing the new mockups before the meeting.

Let me know if you need any further assistance!
실행이 우리의 입력으로 진행됩니다.

7. Advanced: Control information flow

기본적으로 sub-agent는 supervisor로부터 요청 문자열만 받습니다. 대화 기록이나 사용자 기본 설정과 같은 추가 컨텍스트를 전달하고 싶을 수 있습니다.

Pass additional conversational context to sub-agents

from langchain.tools import tool, ToolRuntime

@tool
def schedule_event(
    request: str,
    runtime: ToolRuntime
) -> str:
    """Schedule calendar events using natural language."""
    # Customize context received by sub-agent
    original_user_message = next(
        message for message in runtime.state["messages"]
        if message.type == "human"
    )
    prompt = (
        "You are assisting with the following user inquiry:\n\n"
        f"{original_user_message.text}\n\n"
        "You are tasked with the following sub-request:\n\n"
        f"{request}"
    )
    result = calendar_agent.invoke({
        "messages": [{"role": "user", "content": prompt}],
    })
    return result["messages"][-1].text
이를 통해 sub-agent가 전체 대화 컨텍스트를 볼 수 있으며, 이는 “내일 같은 시간에 일정을 잡아줘”(이전 대화 참조)와 같은 모호성을 해결하는 데 유용할 수 있습니다.
LangSmith trace의 chat model call에서 sub agent가 받은 전체 컨텍스트를 볼 수 있습니다.

Control what supervisor receives

supervisor로 다시 흐르는 정보를 사용자 정의할 수도 있습니다:
import json

@tool
def schedule_event(request: str) -> str:
    """Schedule calendar events using natural language."""
    result = calendar_agent.invoke({
        "messages": [{"role": "user", "content": request}]
    })

    # Option 1: Return just the confirmation message
    return result["messages"][-1].text

    # Option 2: Return structured data
    # return json.dumps({
    #     "status": "success",
    #     "event_id": "evt_123",
    #     "summary": result["messages"][-1].text
    # })
중요: sub-agent prompt가 최종 메시지에 모든 관련 정보가 포함되어야 함을 강조하는지 확인하세요. 일반적인 실패 모드는 tool 호출을 수행하지만 최종 응답에 결과를 포함하지 않는 sub-agent입니다.

8. Key takeaways

supervisor 패턴은 각 계층이 명확한 책임을 가진 추상화 계층을 만듭니다. supervisor 시스템을 설계할 때 명확한 도메인 경계로 시작하고 각 sub-agent에 집중된 tool과 prompt를 제공하세요. supervisor를 위한 명확한 tool 설명을 작성하고, 통합 전에 각 계층을 독립적으로 테스트하며, 특정 요구 사항에 따라 정보 흐름을 제어하세요.
supervisor 패턴을 사용해야 하는 경우여러 개의 뚜렷한 도메인(calendar, email, CRM, database)이 있고, 각 도메인에 여러 tool이나 복잡한 로직이 있으며, 중앙 집중식 워크플로우 제어를 원하고, sub-agent가 사용자와 직접 대화할 필요가 없을 때 supervisor 패턴을 사용하세요.몇 개의 tool만 있는 간단한 경우에는 단일 agent를 사용하세요. agent가 사용자와 대화해야 하는 경우 대신 handoffs를 사용하세요. agent 간의 peer-to-peer 협업의 경우 다른 multi-agent 패턴을 고려하세요.

Next steps

agent 간 대화를 위한 handoffs에 대해 알아보고, 정보 흐름을 미세 조정하기 위한 context engineering을 탐색하고, 다양한 패턴을 비교하기 위해 multi-agent overview를 읽고, multi-agent 시스템을 디버그하고 모니터링하기 위해 LangSmith를 사용하세요.
Connect these docs programmatically to Claude, VSCode, and more via MCP for real-time answers.
I