출처: LangGraph Docs - Agent Supervisor
Agent Supervisor는 LangGraph에서 제공하는 멀티 에이전트 아키텍처 패턴입니다. 이 패턴은 마치 Spring Framework의 Controller-Service 구조처럼, 중앙의 감독자 에이전트(Supervisor Agent)가 여러 전문 작업자 에이전트(Worker Agents)들을 조정하고 관리하는 방식으로 동작합니다.
Spring Boot에서 Controller가 여러 Service를 호출하여 비즈니스 로직을 처리하는 것처럼, Supervisor Agent는 각각 다른 전문 분야를 담당하는 Worker Agent들에게 작업을 위임하고 결과를 취합합니다.
전통적인 Spring 아키텍처와의 비교:
이 튜토리얼에서는 연구(Research) 전문가와 수학(Math) 전문가, 두 개의 Worker Agent를 가진 Supervisor 시스템을 구축해보겠습니다. 튜토리얼을 완료하면 다음을 할 수 있게 됩니다:
langgraph-supervisor를 사용한 감독자 구축프로젝트를 시작하기 전에 필요한 패키지들을 설치하고 API 키를 설정해야 합니다. 이는 Spring Boot 프로젝트에서 pom.xml이나 build.gradle에 의존성을 추가하는 것과 유사합니다.
%%capture --no-stderr
%pip install -U langgraph langgraph-supervisor langchain-tavily "langchain[openai]"
패키지 설명:
import getpass
import os
def _set_if_undefined(var: str):
if not os.environ.get(var):
os.environ[var] = getpass.getpass(f"Please provide your {var}")
_set_if_undefined("OPENAI_API_KEY")
_set_if_undefined("TAVILY_API_KEY")
API 키 설명:
💡 팁: Spring Boot의
application.properties에서 설정값을 관리하는 것처럼, 이러한 API 키들은 환경변수로 관리하여 보안을 유지합니다.
LangSmith는 Spring Boot Actuator나 Micrometer와 유사한 역할을 하는 모니터링 도구입니다. 에이전트의 동작을 추적하고 성능을 분석할 수 있습니다.
작업자 에이전트는 Spring Framework의 Service 클래스와 유사한 개념입니다. 각각이 특정 도메인의 전문 기능을 담당하며, 독립적으로 동작할 수 있습니다.
우리는 두 개의 전문 작업자 에이전트를 생성할 것입니다:
이는 마치 Spring 프로젝트에서 UserService, OrderService처럼 각각 다른 책임을 가진 서비스 클래스를 만드는 것과 같습니다.
연구 에이전트는 웹 검색 기능을 담당합니다. Spring에서 외부 API를 호출하는 Service와 유사한 역할을 합니다.
먼저 Tavily Search 도구를 설정합니다. 이는 Spring에서 RestTemplate이나 WebClient를 설정하는 것과 유사합니다.
from langchain_tavily import TavilySearch
# 웹 검색 도구 초기화 (최대 3개 결과 반환)
web_search = TavilySearch(max_results=3)
# 테스트 검색 실행
web_search_results = web_search.invoke("who is the mayor of NYC?")
print(web_search_results["results"][0]["content"])
출력 예시:
Eric L. Adams는 뉴욕시의 110번째 시장입니다. 그는 NYPD 경찰관, 주 상원의원, 브루클린 자치구 구청장을 거쳐 현재 뉴욕시 시장으로 재직하고 있습니다...
이제 실제 연구 에이전트를 생성합니다. 이는 Spring의 @Service 어노테이션이 붙은 클래스를 만드는 것과 유사합니다.
from langgraph.prebuilt import create_react_agent
research_agent = create_react_agent(
model="openai:gpt-4.1", # 사용할 AI 모델
tools=[web_search], # 사용 가능한 도구들
prompt=(
"당신은 연구 전문 에이전트입니다.\n\n"
"지침:\n"
"- 연구 관련 작업만 수행하고, 수학 계산은 하지 마세요\n"
"- 작업 완료 후 감독자에게 직접 응답하세요\n"
"- 작업 결과만 응답하고, 다른 텍스트는 포함하지 마세요."
),
name="research_agent" # 에이전트 식별자
)
코드 설명:
생성된 에이전트가 올바르게 동작하는지 테스트해봅시다. 이는 Spring에서 단위 테스트를 작성하는 것과 유사합니다.
# 메시지 출력을 위한 헬퍼 함수들
from langchain_core.messages import convert_to_messages
def pretty_print_message(message, indent=False):
pretty_message = message.pretty_repr(html=True)
if not indent:
print(pretty_message)
return
indented = "\n".join("\t" + c for c in pretty_message.split("\n"))
print(indented)
def pretty_print_messages(update, last_message=False):
is_subgraph = False
if isinstance(update, tuple):
ns, update = update
if len(ns) == 0:
return
graph_id = ns[-1].split(":")[0]
print(f"서브그래프 {graph_id}에서 업데이트:")
print("\n")
is_subgraph = True
for node_name, node_update in update.items():
update_label = f"노드 {node_name}에서 업데이트:"
if is_subgraph:
update_label = "\t" + update_label
print(update_label)
print("\n")
messages = convert_to_messages(node_update["messages"])
if last_message:
messages = messages[-1:]
for m in messages:
pretty_print_message(m, indent=is_subgraph)
print("\n")
# 연구 에이전트 실행 테스트
for chunk in research_agent.stream(
{"messages": [{"role": "user", "content": "뉴욕시 시장이 누구인가요?"}]}
):
pretty_print_messages(chunk)
이 테스트는 Spring Boot의 @Test 메서드와 유사하게, 에이전트가 예상대로 동작하는지 확인합니다.
수학 에이전트는 수학적 계산을 담당하는 전문 에이전트입니다. Spring에서 계산 로직을 담당하는 Service 클래스와 유사합니다.
먼저 수학 에이전트가 사용할 수 있는 기본적인 수학 도구들을 정의합니다. 이는 Spring에서 Utility 클래스의 메서드들을 정의하는 것과 유사합니다.
from typing import Annotated
from langchain_core.tools import tool
@tool
def add(a: Annotated[float, "첫 번째 숫자"], b: Annotated[float, "두 번째 숫자"]) -> float:
"""두 숫자를 더합니다."""
return a + b
@tool
def multiply(a: Annotated[float, "첫 번째 숫자"], b: Annotated[float, "두 번째 숫자"]) -> float:
"""두 숫자를 곱합니다."""
return a * b
@tool
def divide(a: Annotated[float, "나누어질 숫자"], b: Annotated[float, "나누는 숫자"]) -> float:
"""첫 번째 숫자를 두 번째 숫자로 나눕니다."""
if b == 0:
raise ValueError("0으로 나눌 수 없습니다")
return a / b
코드 설명:
@Component나 @Service와 유사하게, 함수를 에이전트가 사용할 수 있는 도구로 등록@Valid나 @RequestParam과 유사하게, 매개변수에 대한 메타데이터 제공math_agent = create_react_agent(
model="openai:gpt-4.1",
tools=[add, multiply, divide], # 수학 도구들을 주입
prompt=(
"당신은 수학 계산 전문 에이전트입니다.\n\n"
"지침:\n"
"- 수학 계산 작업만 수행하고, 연구나 검색은 하지 마세요\n"
"- 계산 결과를 명확하고 정확하게 제공하세요\n"
"- 작업 완료 후 감독자에게 직접 응답하세요\n"
"- 계산 결과만 응답하고, 다른 텍스트는 포함하지 마세요."
),
name="math_agent"
)
이제 두 개의 전문 작업자 에이전트가 준비되었습니다. 이는 Spring 프로젝트에서 ResearchService와 MathService를 만든 것과 같습니다.
감독자 에이전트는 Spring MVC의 Controller와 유사한 역할을 합니다. 사용자의 요청을 받아 적절한 작업자 에이전트에게 작업을 위임하고, 결과를 취합하여 응답합니다.
LangGraph는 감독자 패턴을 쉽게 구현할 수 있는 사전 구축된 도구를 제공합니다. 이는 Spring Boot Starter와 유사하게, 복잡한 설정 없이 바로 사용할 수 있는 기능입니다.
from langgraph_supervisor import create_supervisor
# 간단한 감독자 생성
supervisor = create_supervisor(
agents=[research_agent, math_agent], # 관리할 작업자 에이전트들
model="openai:gpt-4.1" # 감독자가 사용할 AI 모델
)
더 세밀한 제어가 필요한 경우, 처음부터 감독자를 구축할 수 있습니다. 이는 Spring에서 커스텀 Controller를 만드는 것과 유사합니다.
감독자와 작업자 에이전트 간의 통신을 위해 핸드오프(Handoff) 메커니즘을 구현해야 합니다. 이는 Spring의 의존성 주입이나 메서드 호출과 유사한 개념입니다.
from typing import Annotated
from langchain_core.tools import tool
from langgraph.prebuilt import InjectedToolCallId, InjectedState
from langgraph.graph import StateGraph, START, MessagesState
from langgraph.types import Command
def create_handoff_tool(*, agent_name: str, description: str = None) -> None:
"""특정 에이전트로 작업을 이관하는 도구를 생성합니다."""
name = f"transfer_to_{agent_name}"
description = description or f"{agent_name} 에이전트에게 작업을 요청합니다."
@tool(name, description=description)
def handoff_tool(
state: Annotated[MessagesState, InjectedState],
tool_call_id: Annotated[str, InjectedToolCallId],
) -> Command:
tool_message = {
"role": "tool",
"content": f"{agent_name}에게 성공적으로 이관되었습니다",
"name": name,
"tool_call_id": tool_call_id,
}
return Command(
goto=agent_name,
update={"state": "messages": state["messages"] + [tool_message]},
graph=Command.PARENT,
)
return handoff_tool
# 각 작업자 에이전트로의 핸드오프 도구 생성
assign_to_research_agent = create_handoff_tool(
agent_name="research_agent",
description="연구 작업을 연구 에이전트에게 할당합니다."
)
assign_to_math_agent = create_handoff_tool(
agent_name="math_agent",
description="수학 계산 작업을 수학 에이전트에게 할당합니다."
)
핸드오프 메커니즘 설명:
ResponseEntity와 유사하게, 에이전트 간 통신의 결과를 담는 객체redirect:와 유사)@Autowired와 유사한 의존성 주입 메커니즘이제 핸드오프 도구들을 사용하는 감독자 에이전트를 생성합니다. 이는 Spring의 Controller 클래스를 만드는 것과 유사합니다.
supervisor_agent = create_react_agent(
model="openai:gpt-4.1",
tools=[assign_to_research_agent, assign_to_math_agent], # 핸드오프 도구들
prompt=(
"당신은 두 명의 에이전트를 관리하는 감독자입니다:\n"
"- 연구 에이전트: 연구 관련 작업을 이 에이전트에게 할당하세요\n"
"- 수학 에이전트: 수학 관련 작업을 이 에이전트에게 할당하세요\n"
"한 번에 한 에이전트에게만 작업을 할당하고, 병렬로 실행하지 마세요\n"
"직접 작업을 수행하지 마세요."
),
name="supervisor"
)
감독자의 역할:
이는 Spring MVC에서 Controller가 하는 역할과 매우 유사합니다:
@RestController
public class TaskController {
@Autowired
private ResearchService researchService;
@Autowired
private MathService mathService;
@PostMapping("/process")
public ResponseEntity<?> processRequest(@RequestBody TaskRequest request) {
if (request.getType().equals("RESEARCH")) {
return researchService.process(request);
} else if (request.getType().equals("MATH")) {
return mathService.process(request);
}
// ...
}
}
이제 모든 구성 요소를 하나의 그래프로 연결합니다. 이는 Spring의 Application Context에서 Bean들을 연결하는 것과 유사합니다.
from langgraph.graph import END
# 멀티 에이전트 감독자 그래프 정의
supervisor = (
StateGraph(MessagesState)
# 노드 추가 (각 에이전트를 그래프의 노드로 등록)
.add_node("supervisor", supervisor_agent)
.add_node("research_agent", research_agent)
.add_node("math_agent", math_agent)
# 엣지 추가 (에이전트 간 연결 관계 정의)
.add_edge(START, "supervisor") # 시작점에서 감독자로
.add_edge("research_agent", "supervisor") # 연구 에이전트에서 감독자로
.add_edge("math_agent", "supervisor") # 수학 에이전트에서 감독자로
# 그래프 컴파일
.compile()
)
그래프 구조 설명:
생성된 그래프의 실행 흐름은 다음과 같습니다:
사용자 요청 → START → supervisor → (research_agent 또는 math_agent) → supervisor → 응답
이는 Spring MVC의 요청 처리 흐름과 유사합니다:
HTTP 요청 → DispatcherServlet → Controller → Service → Controller → HTTP 응답
중요한 특징:
생성된 그래프를 시각적으로 확인할 수 있습니다:
from IPython.display import display, Image
# 그래프 구조를 이미지로 생성하여 표시
display(Image(supervisor.get_graph().draw_mermaid_png()))
이 명령어는 다음과 같은 다이어그램을 생성합니다:
__start__
↓
supervisor
↙ ↘
math_agent research_agent
↘ ↙
supervisor
↓
__end__
이제 완성된 멀티 에이전트 시스템을 테스트해봅시다. 이는 Spring Boot 애플리케이션을 실행하고 API를 테스트하는 것과 유사합니다.
# 복합적인 요청으로 시스템 테스트
for chunk in supervisor.stream(
{
"messages": [
{
"role": "user",
"content": "2024년 미국과 뉴욕주의 GDP를 찾아주세요. 그리고 뉴욕주 GDP가 미국 전체 GDP에서 차지하는 비율을 계산해주세요."
}
]
}
):
pretty_print_messages(chunk, last_message=True)
# 최종 메시지 히스토리 확인
final_message_history = chunk["supervisor"]["messages"]
예상 실행 흐름:
1. 감독자: 요청을 분석하여 연구가 필요함을 인식
2. 연구 에이전트: 웹 검색을 통해 2024년 미국과 뉴욕주 GDP 데이터 수집
3. 감독자: 연구 결과를 받고 계산이 필요함을 인식
4. 수학 에이전트: 비율 계산 수행
5. 감독자: 최종 결과를 사용자에게 제공
이러한 흐름은 Spring에서 여러 Service를 순차적으로 호출하는 것과 매우 유사합니다.
감독자는 더 복잡한 로직을 통해 작업을 위임할 수 있습니다. 이는 Spring의 @ConditionalOnProperty나 복잡한 비즈니스 로직과 유사합니다.
def route_request(state):
"""요청 내용을 분석하여 적절한 에이전트로 라우팅"""
last_message = state["messages"][-1]
content = last_message.content.lower()
# 키워드 기반 라우팅 로직
if any(keyword in content for keyword in ["검색", "찾아", "조사", "연구"]):
return "research_agent"
elif any(keyword in content for keyword in ["계산", "수학", "더하기", "곱하기"]):
return "math_agent"
else:
return "supervisor" # 애매한 경우 감독자가 다시 판단
복잡한 워크플로우에서는 에이전트 간 상태를 공유해야 할 수 있습니다. 이는 Spring의 Session이나 Redis와 유사한 개념입니다.
from typing import TypedDict
class WorkflowState(TypedDict):
messages: list
research_data: dict # 연구 결과 저장
calculations: dict # 계산 결과 저장
current_step: str # 현재 진행 단계
이제 학습한 Supervisor Architecture를 여행 계획 시스템에 적용해보겠습니다.
시스템 구조:
사용자 요청 → Travel Supervisor → [Budget Agent, Activity Agent, Accommodation Agent] → Travel Supervisor → 최적 계획 선택
각 에이전트의 역할:
# 여행 계획 전문 에이전트들
budget_travel_agent = create_react_agent(
model="openai:gpt-4.1",
tools=[search_flights, search_budget_hotels, calculate_costs],
prompt=(
"당신은 예산 효율성을 중시하는 여행 계획 전문가입니다.\n"
"주어진 예산 내에서 최대한 알찬 여행 계획을 세우세요.\n"
"비용 대비 효과를 항상 고려하세요."
),
name="budget_agent"
)
activity_travel_agent = create_react_agent(
model="openai:gpt-4.1",
tools=[search_attractions, search_activities, check_schedules],
prompt=(
"당신은 액티비티와 체험을 중시하는 여행 계획 전문가입니다.\n"
"흥미롭고 다양한 경험을 제공하는 여행 계획을 세우세요.\n"
"현지 문화와 특별한 체험을 우선시하세요."
),
name="activity_agent"
)
accommodation_travel_agent = create_react_agent(
model="openai:gpt-4.1",
tools=[search_luxury_hotels, search_unique_stays, check_amenities],
prompt=(
"당신은 편안함과 숙박을 중시하는 여행 계획 전문가입니다.\n"
"편안하고 특별한 숙박 경험을 중심으로 여행 계획을 세우세요.\n"
"숙박 시설의 품질과 서비스를 우선시하세요."
),
name="accommodation_agent"
)
# 여행 감독자 에이전트
travel_supervisor = create_react_agent(
model="openai:gpt-4.1",
tools=[assign_to_budget, assign_to_activity, assign_to_accommodation],
prompt=(
"당신은 여행 계획 감독자입니다.\n"
"사용자의 요구사항을 분석하여 세 명의 전문가에게 여행 계획을 요청하세요:\n"
"1. 예산 중심 계획 (budget_agent)\n"
"2. 액티비티 중심 계획 (activity_agent)\n"
"3. 숙박 중심 계획 (accommodation_agent)\n\n"
"모든 계획을 받은 후, 사용자의 우선순위와 요구사항에 가장 적합한 계획을 선택하여 추천하세요."
),
name="travel_supervisor"
)
# 여행 계획 요청
travel_request = {
"messages": [
{
"role": "user",
"content": """
3박 4일 제주도 여행을 계획하고 있습니다.
- 예산: 1인당 50만원
- 관심사: 자연 경관, 맛집 탐방
- 숙박: 깨끗하고 편안한 곳 선호
여러 가지 스타일의 여행 계획을 만들어서
가장 좋은 것을 추천해주세요.
"""
}
]
}
# 시스템 실행
for chunk in travel_supervisor.stream(travel_request):
pretty_print_messages(chunk)
예상 실행 흐름:
1. Travel Supervisor: 요청 분석 후 세 에이전트에게 각각 계획 요청
2. Budget Agent: 50만원 예산 내 최적화된 계획 수립
3. Activity Agent: 자연 경관과 맛집 중심의 체험 계획 수립
4. Accommodation Agent: 편안한 숙박 중심의 계획 수립
5. Travel Supervisor: 세 계획을 비교 분석하여 최적안 선택 및 추천
다음은 시스템이 실제로 생성하는 출력의 예시입니다:
감독자 노드에서 업데이트:
================================== Tool Message ==================================
Name: transfer_to_research_agent
Successfully transferred to research_agent
연구 에이전트 노드에서 업데이트:
================================== AI Message ==================================
Name: research_agent
2024년 GDP 데이터:
- 미국 GDP: 약 28.18조 달러 (추정치)
- 뉴욕주 GDP: 약 2.16조 달러 (추정치)
출처:
- https://www.statista.com/statistics/216985/forecast-of-us-gross-domestic-product/
- https://nyassembly.gov/Reports/WAM/20250economic_revenue/2025_report.pdf
감독자 노드에서 업데이트:
================================== Tool Message ==================================
Name: transfer_to_math_agent
Successfully transferred to math_agent
수학 에이전트 노드에서 업데이트:
================================== AI Message ==================================
Name: math_agent
계산 결과:
- 2024년 미국 GDP: 28.18조 달러
- 2024년 뉴욕주 GDP: 2.16조 달러
- 뉴욕주가 미국 GDP에서 차지하는 비율: 약 7.67%
감독자 노드에서 업데이트:
================================== AI Message ==================================
Name: supervisor
최종 결과:
**2024년 GDP 정보:**
- 미국 전체 GDP: 28.18조 달러 (추정)
- 뉴욕주 GDP: 2.16조 달러 (추정)
- 뉴욕주의 비중: 약 7.67%
뉴욕주는 미국 전체 GDP의 약 7.67%를 차지하여, 단일 주로서는 상당히 큰 경제 규모를 가지고 있습니다.
| LangGraph 개념 | Spring Framework 개념 | 설명 |
|---|---|---|
| Agent | Service/Component | 특정 기능을 담당하는 독립적인 모듈 |
| Supervisor | Controller | 요청을 받아 적절한 서비스로 라우팅 |
| Handoff Tools | Method Call/Autowired | 컴포넌트 간 통신 메커니즘 |
| StateGraph | ApplicationContext | 전체 시스템의 구조와 의존성 정의 |
| Messages | Request/Response | 컴포넌트 간 데이터 전달 객체 |
| Tools | External API/Library | 외부 기능을 사용하기 위한 인터페이스 |
장점:
활용 사례:
LangGraph의 Supervisor Architecture는 복잡한 AI 시스템을 구조화하고 관리하는 강력한 패턴입니다. Spring Framework에 익숙한 개발자라면 Controller-Service 패턴과 유사한 개념으로 쉽게 이해할 수 있으며, 이를 통해 확장 가능하고 유지보수가 용이한 멀티 에이전트 시스템을 구축할 수 있습니다.
여행 계획 시스템에 적용할 때는 각 에이전트가 서로 다른 관점(예산, 액티비티, 숙박)에서 계획을 수립하고, 감독자가 사용자의 요구사항에 가장 적합한 계획을 선택하는 방식으로 활용할 수 있습니다. 이를 통해 사용자에게 더 다양하고 최적화된 여행 옵션을 제공할 수 있을 것입니다.