출처: https://docs.langchain.com/oss/python/langchain/multi-agent
Multi-agent 시스템은 복잡한 애플리케이션을 여러 특화된 에이전트로 분해하여 함께 문제를 해결하도록 합니다. 모든 단계를 처리하기 위해 단일 에이전트에 의존하는 대신, multi-agent 아키텍처를 사용하면 더 작고 집중된 에이전트들을 조정된 워크플로우로 구성할 수 있습니다.
Multi-agent 시스템은 다음과 같은 경우에 유용합니다:
- 단일 에이전트가 너무 많은 도구를 가지고 있어 어떤 도구를 사용할지에 대한 판단이 좋지 않은 경우
- 컨텍스트나 메모리가 하나의 에이전트가 효과적으로 추적하기에는 너무 커지는 경우
- 작업에 전문화가 필요한 경우 (예: 계획자, 연구자, 수학 전문가)
Multi-agent 패턴
| 패턴 | 작동 방식 | 제어 흐름 | 사용 사례 예시 |
|---|---|---|---|
| Tool Calling | supervisor 에이전트가 다른 에이전트들을 도구로 호출합니다. "도구" 에이전트는 사용자와 직접 대화하지 않으며, 단지 작업을 실행하고 결과를 반환합니다. | 중앙 집중식: 모든 라우팅이 호출 에이전트를 통과합니다. | 작업 오케스트레이션, 구조화된 워크플로우 |
| Handoffs | 현재 에이전트가 다른 에이전트로 제어권을 이전하기로 결정합니다. 활성 에이전트가 변경되며, 사용자는 새 에이전트와 직접 계속 상호작용할 수 있습니다. | 분산형: 에이전트가 누가 활성 상태인지 변경할 수 있습니다. | 다중 도메인 대화, 전문가 인계 |
튜토리얼: supervisor 에이전트 구축
중앙 supervisor 에이전트가 특화된 worker 에이전트를 조정하는 supervisor 패턴을 사용하여 개인 비서를 구축하는 방법을 배웁니다.
이 튜토리얼은 다음을 보여줍니다:
- 다양한 도메인(캘린더 및 이메일)을 위한 특화된 서브 에이전트 생성
- 중앙 집중식 오케스트레이션을 위해 서브 에이전트를 도구로 래핑
- 민감한 작업에 대한 human-in-the-loop 검토 추가
패턴 선택하기
| 질문 | Tool Calling | Handoffs |
|---|---|---|
| 워크플로우에 대한 중앙 집중식 제어가 필요한가요? | ✅ 예 | ❌ 아니오 |
| 에이전트가 사용자와 직접 상호작용하기를 원하나요? | ❌ 아니오 | ✅ 예 |
| 전문가 간의 복잡하고 인간적인 대화가 필요한가요? | ❌ 제한적 | ✅ 강력 |
두 패턴을 혼합할 수 있습니다 - 에이전트 전환을 위해 handoffs를 사용하고, 각 에이전트가 특화된 작업을 위해 서브 에이전트를 도구로 호출하도록 할 수 있습니다.
에이전트 컨텍스트 커스터마이징
multi-agent 설계의 핵심은 컨텍스트 엔지니어링 - 각 에이전트가 어떤 정보를 볼 수 있는지 결정하는 것입니다. LangChain은 다음에 대한 세밀한 제어를 제공합니다:
- 대화나 상태의 어떤 부분이 각 에이전트에게 전달되는지
- 서브 에이전트에 맞춤화된 특화 프롬프트
- 중간 추론의 포함/제외
- 에이전트별 입출력 형식 커스터마이징
시스템의 품질은 컨텍스트 엔지니어링에 크게 의존합니다. 목표는 각 에이전트가 도구로 작동하든 활성 에이전트로 작동하든 작업을 수행하는 데 필요한 올바른 데이터에 접근할 수 있도록 하는 것입니다.
Tool calling
tool calling에서는 하나의 에이전트("controller")가 다른 에이전트들을 필요할 때 호출할 도구로 취급합니다. 컨트롤러는 오케스트레이션을 관리하고, 도구 에이전트는 특정 작업을 수행하고 결과를 반환합니다.
흐름:
- controller가 입력을 받고 어떤 도구(서브 에이전트)를 호출할지 결정합니다.
- tool agent가 컨트롤러의 지시에 따라 작업을 실행합니다.
- tool agent가 컨트롤러에게 결과를 반환합니다.
- controller가 다음 단계를 결정하거나 작업을 완료합니다.

도구로 사용되는 에이전트는 일반적으로 사용자와 대화를 계속할 것으로 기대되지 않습니다. 그들의 역할은 작업을 수행하고 컨트롤러 에이전트에게 결과를 반환하는 것입니다. 서브 에이전트가 사용자와 대화할 수 있어야 한다면, 대신 handoffs를 사용하세요.
Implementation
다음은 메인 에이전트가 도구 정의를 통해 단일 서브 에이전트에 접근할 수 있는 최소한의 예제입니다:
from langchain.tools import tool
from langchain.agents import create_agent
subagent1 = create_agent(
model="...",
tools=[...]
)
@tool("subagent1_name", description="subagent1_description")
def call_subagent1(query: str):
result = subagent1.invoke({
"messages": [{"role": "user", "content": query}]
})
return result["messages"][-1].content
agent = create_agent(
model="...",
tools=[call_subagent1]
)
이 패턴에서:
- 메인 에이전트가 작업이 서브 에이전트의 설명과 일치한다고 판단할 때
call_subagent1을 호출합니다. - 서브 에이전트가 독립적으로 실행되고 결과를 반환합니다.
- 메인 에이전트가 결과를 받고 오케스트레이션을 계속합니다.
Where to customize
메인 에이전트와 서브 에이전트 간에 컨텍스트가 전달되는 방식을 제어할 수 있는 여러 지점이 있습니다:
- Subagent name (
"subagent1_name"): 메인 에이전트가 서브 에이전트를 참조하는 방식입니다. 프롬프팅에 영향을 미치므로 신중하게 선택하세요. - Subagent description (
"subagent1_description"): 메인 에이전트가 서브 에이전트에 대해 "알고 있는" 내용입니다. 메인 에이전트가 언제 호출할지 결정하는 방식을 직접적으로 형성합니다. - Input to the subagent: 서브 에이전트가 작업을 해석하는 방식을 더 잘 형성하기 위해 이 입력을 커스터마이징할 수 있습니다. 위 예제에서는 에이전트가 생성한
query를 직접 전달합니다. - Output from the subagent: 메인 에이전트에게 다시 전달되는 응답입니다. 메인 에이전트가 결과를 해석하는 방식을 제어하기 위해 반환되는 내용을 조정할 수 있습니다. 위 예제에서는 최종 메시지 텍스트를 반환하지만, 추가 상태나 메타데이터를 반환할 수도 있습니다.
Control the input to the subagent
메인 에이전트가 서브 에이전트에게 전달하는 입력을 제어하는 두 가지 주요 레버가 있습니다:
- 프롬프트 수정 - 메인 에이전트의 프롬프트나 도구 메타데이터(즉, 서브 에이전트의 이름과 설명)를 조정하여 언제 어떻게 서브 에이전트를 호출할지 더 잘 안내합니다.
- 컨텍스트 주입 - 정적 프롬프트에 캡처하기 어려운 입력(예: 전체 메시지 히스토리, 이전 결과, 작업 메타데이터)을 에이전트의 상태에서 가져오도록 도구 호출을 조정하여 추가합니다.
from langchain.agents import AgentState
from langchain.tools import tool, ToolRuntime
class CustomState(AgentState):
example_state_key: str
@tool("subagent1_name", description="subagent1_description")
def call_subagent1(query: str, runtime: ToolRuntime[None, CustomState]):
# 메시지를 적절한 입력으로 변환하는 데 필요한 로직을 적용합니다
subagent_input = some_logic(query, runtime.state["messages"])
result = subagent1.invoke({
"messages": subagent_input,
# 여기에 필요에 따라 다른 상태 키를 전달할 수도 있습니다.
# 메인과 서브 에이전트의 상태 스키마 모두에서 이를 정의해야 합니다.
"example_state_key": runtime.state["example_state_key"]
})
return result["messages"][-1].content
Control the output from the subagent
메인 에이전트가 서브 에이전트로부터 받는 내용을 형성하는 두 가지 일반적인 전략:
프롬프트 수정 - 서브 에이전트의 프롬프트를 수정하여 정확히 무엇을 반환해야 하는지 지정합니다.
- 출력이 불완전하거나, 너무 장황하거나, 주요 세부 정보가 누락되었을 때 유용합니다.
- 일반적인 실패 모드는 서브 에이전트가 도구 호출이나 추론을 수행하지만 최종 메시지에 결과를 포함하지 않는 것입니다. 컨트롤러(및 사용자)는 최종 출력만 보므로 모든 관련 정보가 거기에 포함되어야 한다는 것을 상기시키세요.
커스텀 출력 포맷팅 - 메인 에이전트에게 다시 전달하기 전에 코드에서 서브 에이전트의 응답을 조정하거나 보강합니다.
- 예: 최종 텍스트 외에 특정 상태 키를 메인 에이전트에게 다시 전달합니다.
- 이를 위해서는 결과를
Command(또는 동등한 구조)로 래핑하여 서브 에이전트의 응답과 커스텀 상태를 병합할 수 있어야 합니다.
from typing import Annotated
from langchain.agents import AgentState
from langchain.tools import InjectedToolCallId
from langgraph.types import Command
@tool("subagent1_name", description="subagent1_description")
# 서브 에이전트가 도구 호출 결과로 응답할 수 있도록 `tool_call_id`를 전달해야 합니다
def call_subagent1(
query: str,
tool_call_id: Annotated[str, InjectedToolCallId],
# 최종 도구 호출 이상을 포함하려면 `Command` 객체를 반환해야 합니다
) -> Command:
result = subagent1.invoke({
"messages": [{"role": "user", "content": query}]
})
return Command(
update={
# 이것은 우리가 다시 전달하는 예시 상태 키입니다
"example_state_key": result["example_state_key"],
"messages": [
ToolMessage(
content=result["messages"][-1].content,
# 올바른 도구 호출과 일치하도록 tool call id를 포함해야 합니다
tool_call_id=tool_call_id
)
]
}
)
Handoffs
handoffs에서는 에이전트가 서로 직접 제어권을 전달할 수 있습니다. "활성" 에이전트가 변경되며, 사용자는 현재 제어권을 가진 에이전트와 상호작용합니다.
흐름:
- 현재 에이전트가 다른 에이전트의 도움이 필요하다고 결정합니다.
- 제어권(과 상태)을 다음 에이전트에게 전달합니다.
- 새 에이전트가 다시 인계하거나 완료하기로 결정할 때까지 사용자와 직접 상호작용합니다.

Langchain v1.0
- LangChain 개요
- LangChain v1
- LangChain v1 마이그레이션 가이드
- LangChain 설치
- QuickStart
- Philosophy
- Agents
- Models
- Messages
- Tools
- Short-term memory
- Streaming
- Middleware
- Structured output
- Guardrails
- Runtime
- Context Engineering
- Model Context Protocol (MCP)
- Human-in-the-loop
Multi-agent- Retrieval
- Long-term memory
- Studio
- Test
- Deploy
- Agent Chat UI
- Observability
