🤖

본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.

⚠️

본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.

이미지 로딩 중...

Multi-Agent 멀티에이전트 완벽 가이드 - 슬라이드 1/7
A

AI Generated

2025. 12. 1. · 13 Views

Multi-Agent 멀티에이전트 완벽 가이드

LLM 기반 멀티에이전트 시스템의 핵심 패턴을 다룹니다. Supervisor, Handoffs, Context Injection 등 실무에서 바로 활용할 수 있는 에이전트 협업 전략을 초급 개발자도 이해할 수 있도록 설명합니다.


목차

  1. 멀티에이전트_사용_시점
  2. Tool_Calling_Supervisor_패턴
  3. Handoffs_패턴_제어_이양
  4. Context_Injection_전략
  5. 서브에이전트_출력_포맷팅
  6. 패턴_선택_기준

1. 멀티에이전트 사용 시점

김개발 씨는 요즘 LLM을 활용한 챗봇을 개발하고 있습니다. 처음에는 하나의 에이전트로 모든 것을 처리했는데, 기능이 점점 복잡해지면서 코드가 스파게티처럼 엉켜버렸습니다.

선배 박시니어 씨가 다가와 물었습니다. "혹시 멀티에이전트는 고려해 봤어요?"

멀티에이전트는 한마디로 여러 개의 전문화된 AI 에이전트가 협력하여 복잡한 작업을 처리하는 시스템입니다. 마치 회사에서 기획팀, 개발팀, 마케팅팀이 각자의 전문 분야를 담당하며 협업하는 것과 같습니다.

이것을 제대로 이해하면 복잡한 AI 시스템을 깔끔하게 설계할 수 있습니다.

다음 코드를 살펴봅시다.

# 단일 에이전트의 한계를 보여주는 예제
from langchain.agents import AgentExecutor

# 하나의 에이전트가 모든 것을 처리할 때의 문제점
class MonolithicAgent:
    def __init__(self):
        # 도구가 너무 많아지면 성능이 저하됩니다
        self.tools = [search_tool, code_tool, math_tool,
                      writing_tool, analysis_tool, ...]  # 계속 늘어남

    def process(self, query):
        # 모든 판단을 하나의 LLM이 담당
        # 복잡도가 높아질수록 정확도가 떨어집니다
        return self.agent.run(query)

# 멀티에이전트가 필요한 시점의 판단 기준
MULTI_AGENT_CRITERIA = {
    "tools_count": 10,      # 도구가 10개 이상일 때
    "domain_variety": 3,    # 서로 다른 도메인이 3개 이상일 때
    "workflow_complexity": "high"  # 워크플로우가 복잡할 때
}

김개발 씨는 입사 6개월 차 주니어 개발자입니다. 최근 회사에서 고객 서비스 챗봇 프로젝트를 맡게 되었습니다.

처음에는 간단했습니다. 고객 질문에 답변하는 것이 전부였으니까요.

그런데 요구사항이 점점 늘어났습니다. 주문 조회도 해야 하고, 환불 처리도 해야 하고, 상품 추천도 해야 합니다.

심지어 재고 확인과 배송 추적까지 추가되었습니다. 김개발 씨의 코드는 점점 복잡해져 갔습니다.

어느 날 챗봇이 이상하게 동작하기 시작했습니다. 고객이 환불을 요청했는데 상품 추천을 해버린 것입니다.

김개발 씨는 머리를 싸매고 디버깅을 시작했지만, 도대체 어디서 문제가 발생했는지 찾기가 어려웠습니다. 선배 박시니어 씨가 다가와 코드를 살펴보았습니다.

"아, 이건 전형적인 단일 에이전트의 한계예요. 멀티에이전트를 도입할 때가 된 것 같네요." 그렇다면 멀티에이전트란 정확히 무엇일까요?

쉽게 비유하자면, 멀티에이전트는 마치 종합병원과 같습니다. 환자가 병원에 오면 먼저 접수를 하고, 증상에 따라 내과, 외과, 피부과 등 전문의에게 배정됩니다.

각 전문의는 자신의 분야에서 최고의 진료를 제공합니다. 이처럼 멀티에이전트 시스템도 각각의 에이전트가 특정 분야를 전문적으로 담당합니다.

그렇다면 언제 멀티에이전트를 도입해야 할까요? 첫 번째 기준은 도구의 개수입니다.

하나의 에이전트에 도구가 10개 이상 붙으면 LLM이 어떤 도구를 선택해야 할지 헷갈리기 시작합니다. 마치 식당 메뉴가 100개가 넘으면 손님이 주문하기 어려운 것처럼요.

두 번째 기준은 도메인의 다양성입니다. 주문 처리, 고객 상담, 기술 지원처럼 서로 다른 전문 영역이 3개 이상이면 각각 전문 에이전트로 분리하는 것이 효과적입니다.

세 번째 기준은 워크플로우의 복잡도입니다. 작업이 순차적으로 또는 병렬로 처리되어야 하고, 중간에 판단이 필요한 분기점이 많다면 멀티에이전트가 답입니다.

위의 코드를 살펴보면, 단일 에이전트에 도구가 계속 추가되는 모습을 볼 수 있습니다. 이렇게 되면 에이전트가 올바른 도구를 선택할 확률이 점점 낮아집니다.

실제 현업에서는 어떻게 활용할까요? 대형 이커머스 회사들은 주문 에이전트, 배송 에이전트, 환불 에이전트, 추천 에이전트를 각각 분리하여 운영합니다.

각 에이전트는 자신의 영역에서만 판단하므로 정확도가 높아집니다. 하지만 주의할 점도 있습니다.

무조건 에이전트를 나누면 좋은 것은 아닙니다. 에이전트가 너무 많아지면 관리가 복잡해지고, 에이전트 간 통신 비용도 증가합니다.

따라서 적절한 단위로 나누는 것이 중요합니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.

박시니어 씨의 조언을 듣고 김개발 씨는 고객 서비스 챗봇을 주문 처리 에이전트, 환불 처리 에이전트, 상품 추천 에이전트로 분리했습니다. 그러자 마법처럼 버그가 사라졌습니다.

실전 팁

💡 - 도구가 10개를 넘어가면 멀티에이전트 도입을 적극 검토하세요

  • 처음부터 과도하게 나누지 말고, 필요에 따라 점진적으로 분리하세요

2. Tool Calling Supervisor 패턴

김개발 씨가 멀티에이전트 도입을 결정했습니다. 그런데 새로운 고민이 생겼습니다.

여러 에이전트가 있으면 누가 일을 분배하죠? 박시니어 씨가 웃으며 대답했습니다.

"그래서 Supervisor 패턴이 필요한 거예요."

Supervisor 패턴은 한마디로 중앙에서 모든 에이전트를 관리하고 조율하는 구조입니다. 마치 오케스트라의 지휘자가 각 파트에게 언제 연주할지 지시하는 것과 같습니다.

Supervisor가 사용자 요청을 분석하고, 적절한 서브에이전트를 호출하여 결과를 취합합니다.

다음 코드를 살펴봅시다.

from langchain.agents import AgentExecutor
from langchain_core.tools import tool

# 각 전문 에이전트를 도구로 정의합니다
@tool
def order_agent(query: str) -> str:
    """주문 관련 질문을 처리합니다. 주문 조회, 주문 취소 등."""
    return order_executor.invoke({"input": query})

@tool
def refund_agent(query: str) -> str:
    """환불 관련 질문을 처리합니다. 환불 요청, 환불 상태 확인 등."""
    return refund_executor.invoke({"input": query})

@tool
def recommend_agent(query: str) -> str:
    """상품 추천을 담당합니다. 개인화 추천, 인기 상품 등."""
    return recommend_executor.invoke({"input": query})

# Supervisor 에이전트 생성
supervisor = create_react_agent(
    llm=ChatOpenAI(model="gpt-4"),
    tools=[order_agent, refund_agent, recommend_agent],
    prompt="당신은 고객 서비스 관리자입니다. 고객 요청을 분석하여 적절한 담당자에게 전달하세요."
)

김개발 씨는 멀티에이전트의 필요성은 이해했습니다. 하지만 막상 구현하려니 막막했습니다.

주문 에이전트, 환불 에이전트, 추천 에이전트를 만들었는데, 이것들을 어떻게 연결해야 할까요? 박시니어 씨가 화이트보드에 그림을 그리기 시작했습니다.

가운데 큰 원을 그리고 Supervisor라고 적었습니다. 그리고 그 주변에 작은 원 세 개를 그려 각각 Order, Refund, Recommend라고 적었습니다.

"멀티에이전트 시스템에서 가장 기본적인 패턴이 바로 Supervisor 패턴이에요." Supervisor 패턴은 마치 회사의 팀장과 같습니다. 팀장은 외부에서 업무 요청이 들어오면 그 내용을 파악합니다.

그리고 팀원 중 누가 이 일을 가장 잘 처리할 수 있을지 판단하여 업무를 배정합니다. 팀원이 작업을 완료하면 결과를 받아서 최종 보고서를 작성합니다.

이 패턴에서 핵심은 서브에이전트를 **도구(Tool)**로 등록한다는 것입니다. LangChain에서는 @tool 데코레이터를 사용하여 함수를 도구로 만들 수 있습니다.

각 서브에이전트를 도구로 감싸면 Supervisor가 Tool Calling 방식으로 서브에이전트를 호출할 수 있습니다. 위의 코드를 살펴보겠습니다.

order_agent, refund_agent, recommend_agent 세 개의 함수가 각각 @tool 데코레이터로 감싸져 있습니다. 각 함수의 docstring은 매우 중요합니다.

Supervisor LLM이 이 설명을 읽고 어떤 도구를 호출할지 결정하기 때문입니다. 그 다음 supervisor를 생성할 때 이 세 도구를 리스트로 전달합니다.

이제 Supervisor는 사용자 요청이 들어오면 요청을 분석하고, 적절한 도구를 선택하여 호출합니다. 예를 들어 사용자가 "제 주문 상태가 어떻게 되나요?"라고 물으면 어떻게 될까요?

Supervisor는 이 질문이 주문과 관련되었다고 판단합니다. 그래서 order_agent 도구를 호출합니다.

order_agent가 결과를 반환하면 Supervisor가 그 결과를 사용자에게 전달합니다. 이 패턴의 장점은 무엇일까요?

첫째, 중앙 집중식 제어가 가능합니다. 모든 의사결정이 Supervisor를 통해 이루어지므로 시스템의 동작을 예측하기 쉽습니다.

둘째, 확장이 용이합니다. 새로운 에이전트를 추가하려면 도구를 하나 더 만들어서 등록하면 됩니다.

하지만 단점도 있습니다. Supervisor가 모든 판단을 담당하므로 병목이 될 수 있습니다.

또한 Supervisor의 프롬프트가 복잡해지면 잘못된 에이전트를 호출할 수도 있습니다. 실무에서는 Supervisor의 프롬프트를 신중하게 설계해야 합니다.

각 서브에이전트의 역할을 명확하게 정의하고, 어떤 경우에 어떤 에이전트를 호출해야 하는지 구체적으로 명시하는 것이 좋습니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.

김개발 씨는 Supervisor 패턴으로 챗봇을 재구성했습니다. 이제 고객의 질문이 들어오면 Supervisor가 정확하게 담당 에이전트를 선택합니다.

환불 요청에 상품 추천을 하는 실수는 더 이상 발생하지 않았습니다.

실전 팁

💡 - 서브에이전트의 docstring을 명확하게 작성하세요. Supervisor가 이를 보고 판단합니다

  • Supervisor 프롬프트에 각 에이전트의 역할 경계를 명확히 정의하세요

3. Handoffs 패턴 제어 이양

Supervisor 패턴으로 시스템을 구축한 김개발 씨. 그런데 어떤 경우에는 에이전트들이 직접 대화하면서 일을 처리하는 게 더 자연스러울 것 같았습니다.

박시니어 씨가 말했습니다. "그럴 땐 Handoffs 패턴을 써보세요."

Handoffs 패턴은 에이전트가 다른 에이전트에게 직접 제어권을 넘기는 방식입니다. 마치 전화 상담에서 "이 부분은 다른 부서에서 도와드릴게요.

연결해 드리겠습니다"라고 하는 것과 같습니다. Supervisor를 거치지 않고 에이전트 간 직접 이양이 이루어집니다.

다음 코드를 살펴봅시다.

from langgraph.prebuilt import create_react_agent
from langgraph.types import Command

# Handoff 함수 생성 유틸리티
def create_handoff(target_agent: str):
    """다른 에이전트로 제어권을 이양하는 도구를 생성합니다"""
    @tool
    def handoff_to_agent(summary: str) -> Command:
        """현재까지의 대화 요약과 함께 다른 에이전트로 이양합니다"""
        return Command(
            goto=target_agent,  # 이동할 에이전트 지정
            update={"summary": summary}  # 컨텍스트 전달
        )
    return handoff_to_agent

# 각 에이전트에 handoff 도구 추가
order_agent = create_react_agent(
    llm=llm,
    tools=[
        get_order_status,
        cancel_order,
        create_handoff("refund_agent"),  # 환불 에이전트로 이양 가능
        create_handoff("support_agent")   # 지원 에이전트로 이양 가능
    ]
)

김개발 씨의 고객 서비스 챗봇이 점점 성장하고 있었습니다. 그런데 어느 날 이상한 상황이 발생했습니다.

고객이 주문 조회를 요청했다가, 대화 중에 환불로 마음이 바뀌었습니다. 그리고 환불 처리 중에 다시 새로운 상품을 추천받고 싶어졌습니다.

Supervisor 패턴에서는 매번 Supervisor를 거쳐야 했습니다. 고객 입장에서는 마치 매번 처음부터 전화를 다시 거는 것 같은 느낌이었습니다.

불편하고 맥락도 끊기는 것 같았습니다. 박시니어 씨가 새로운 방법을 제안했습니다.

"이런 경우에는 Handoffs 패턴이 더 자연스러워요." Handoffs 패턴은 마치 대형 백화점의 고객 서비스와 같습니다. 1층 안내 데스크에서 "식품관 문의요?

지하 1층으로 안내해 드릴게요"라고 하면, 직원이 직접 지하 1층으로 고객을 안내합니다. 그리고 지하 1층 담당자에게 "이 고객분은 OO 상품을 찾고 계세요"라고 인수인계를 합니다.

이 패턴의 핵심은 Command 객체입니다. Command는 LangGraph에서 제공하는 특별한 반환 타입입니다.

goto 필드에 다음 에이전트를 지정하고, update 필드에 전달할 컨텍스트를 담습니다. 위 코드를 살펴보겠습니다.

create_handoff 함수는 특정 에이전트로 이양하는 도구를 생성합니다. 이 도구가 호출되면 Command 객체를 반환하고, 그래프 실행 엔진이 이를 감지하여 해당 에이전트로 제어권을 넘깁니다.

order_agent를 보면 자신의 고유 도구들(get_order_status, cancel_order) 외에 handoff 도구들도 가지고 있습니다. 주문 에이전트가 대화 중에 "이건 환불팀에서 처리해야겠다"고 판단하면 스스로 환불 에이전트를 호출할 수 있습니다.

이 패턴의 가장 큰 장점은 자연스러운 대화 흐름입니다. 고객은 계속 같은 대화 안에서 여러 에이전트와 소통하는 느낌을 받습니다.

맥락이 유지되므로 매번 상황을 다시 설명할 필요가 없습니다. 또 다른 장점은 분산된 의사결정입니다.

각 에이전트가 자신의 판단으로 다른 에이전트를 호출하므로, Supervisor에 부하가 집중되지 않습니다. 하지만 주의할 점도 있습니다.

Handoffs가 남발되면 대화가 핑퐁처럼 여러 에이전트를 오가며 혼란스러워질 수 있습니다. 따라서 handoff 도구의 docstring에 언제 이양해야 하는지 명확한 기준을 제시해야 합니다.

실무에서는 Supervisor 패턴과 Handoffs 패턴을 혼합하여 사용하는 경우가 많습니다. 최초 라우팅은 Supervisor가 담당하고, 이후 에이전트 간 이동은 Handoffs로 처리하는 방식입니다.

다시 김개발 씨의 이야기로 돌아가 봅시다. Handoffs 패턴을 도입한 후, 고객 만족도가 크게 올랐습니다.

고객들은 "상담이 끊기지 않고 자연스럽게 이어져서 좋아요"라는 피드백을 남겼습니다.

실전 팁

💡 - Handoff 시 반드시 현재까지의 대화 요약을 함께 전달하세요

  • 무한 루프를 방지하기 위해 handoff 횟수에 제한을 두는 것이 좋습니다

4. Context Injection 전략

김개발 씨가 멀티에이전트 시스템을 운영하면서 새로운 문제에 봉착했습니다. 서브에이전트가 필요한 정보를 제대로 전달받지 못해 엉뚱한 답변을 하는 것이었습니다.

박시니어 씨가 말했습니다. "Context Injection이 제대로 안 되어 있네요."

Context Injection은 에이전트에게 필요한 맥락 정보를 주입하는 전략입니다. 마치 신입 직원에게 업무를 넘기면서 관련 문서와 배경 설명을 함께 전달하는 것과 같습니다.

올바른 컨텍스트 없이는 아무리 똑똑한 에이전트도 제대로 된 결과를 낼 수 없습니다.

다음 코드를 살펴봅시다.

from langchain_core.messages import SystemMessage, HumanMessage

def inject_context(agent, context: dict):
    """에이전트 호출 시 필요한 컨텍스트를 주입합니다"""

    # 1. 시스템 메시지로 역할과 규칙 주입
    system_context = f"""
    당신은 {context['role']}입니다.
    현재 고객 정보: {context['customer_info']}
    대화 요약: {context['conversation_summary']}
    주의사항: {context['guidelines']}
    """

    # 2. 구조화된 컨텍스트 전달
    messages = [
        SystemMessage(content=system_context),
        HumanMessage(content=context['current_query'])
    ]

    return agent.invoke({"messages": messages})

# 실제 사용 예시
context = {
    "role": "환불 처리 전문가",
    "customer_info": {"name": "홍길동", "tier": "VIP", "history": "3년 고객"},
    "conversation_summary": "주문 #12345 배송 지연으로 환불 요청",
    "guidelines": "VIP 고객은 즉시 환불 승인 가능",
    "current_query": "환불 처리해 주세요"
}

김개발 씨의 환불 에이전트가 이상하게 동작했습니다. VIP 고객이 환불을 요청했는데, 일반 고객과 똑같이 "7일 내 처리됩니다"라고 답변한 것입니다.

회사 규정상 VIP 고객은 즉시 환불이 가능한데 말이죠. 원인을 분석해보니 환불 에이전트가 고객 등급 정보를 전혀 모르고 있었습니다.

Supervisor에서 환불 에이전트를 호출할 때 단순히 "환불 요청입니다"라고만 전달했기 때문입니다. 박시니어 씨가 설명했습니다.

"멀티에이전트에서 가장 중요한 것 중 하나가 Context Injection이에요. 아무리 좋은 에이전트도 정보가 없으면 제대로 판단할 수 없어요." Context Injection은 마치 의사가 환자를 다른 전문의에게 의뢰할 때와 같습니다.

단순히 "이 환자 좀 봐주세요"라고 하지 않습니다. 환자의 병력, 현재 증상, 복용 중인 약, 알레르기 정보 등을 담은 의뢰서를 함께 보냅니다.

그래야 전문의가 정확한 진료를 할 수 있기 때문입니다. Context Injection에는 여러 가지 요소가 있습니다.

첫째, 역할 컨텍스트입니다. 에이전트가 어떤 역할을 수행해야 하는지 명확히 알려줍니다.

"당신은 환불 처리 전문가입니다"처럼 역할을 정의하면 에이전트가 그에 맞게 행동합니다. 둘째, 고객 컨텍스트입니다.

현재 상대하고 있는 고객이 누구인지, 어떤 등급인지, 과거 이력은 어떤지 전달합니다. 이 정보가 있어야 에이전트가 고객 맞춤형 응대를 할 수 있습니다.

셋째, 대화 컨텍스트입니다. 지금까지 어떤 대화가 오갔는지 요약하여 전달합니다.

특히 Handoffs로 에이전트가 바뀔 때 이전 맥락이 유실되지 않도록 하는 것이 중요합니다. 넷째, 규칙 컨텍스트입니다.

업무 처리 시 적용해야 할 규칙이나 주의사항을 전달합니다. VIP 고객 정책, 환불 한도, 예외 처리 기준 등이 여기에 해당합니다.

위 코드에서 inject_context 함수는 이러한 컨텍스트들을 시스템 메시지로 구성하여 에이전트에게 전달합니다. 구조화된 형태로 정보를 전달하면 에이전트가 필요한 정보를 쉽게 찾아 활용할 수 있습니다.

실무에서 자주 하는 실수는 너무 많은 컨텍스트를 주입하는 것입니다. 관련 없는 정보까지 잔뜩 넣으면 오히려 에이전트가 혼란스러워합니다.

해당 작업에 꼭 필요한 정보만 선별하여 전달하는 것이 좋습니다. 또 다른 팁은 컨텍스트를 계층적으로 구성하는 것입니다.

시스템 레벨 컨텍스트, 세션 레벨 컨텍스트, 요청 레벨 컨텍스트로 나누어 관리하면 효율적입니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.

Context Injection을 제대로 구현한 후, VIP 고객에게도 적절한 응대가 이루어졌습니다. 환불 에이전트가 고객 등급을 알게 되니 "VIP 고객님이시네요.

바로 환불 처리해 드리겠습니다"라고 응답했습니다.

실전 팁

💡 - 컨텍스트는 필요한 것만 선별하여 주입하세요. 과도한 정보는 오히려 방해됩니다

  • 대화 요약은 너무 길지 않게 핵심만 추려서 전달하세요

5. 서브에이전트 출력 포맷팅

김개발 씨가 멀티에이전트 시스템을 완성했습니다. 그런데 새로운 문제가 생겼습니다.

서브에이전트마다 응답 형식이 달라서 Supervisor가 결과를 처리하기 어려웠습니다. 박시니어 씨가 말했습니다.

"출력 포맷팅을 통일해야 해요."

서브에이전트 출력 포맷팅은 모든 에이전트가 일관된 형식으로 결과를 반환하도록 하는 것입니다. 마치 회사에서 모든 부서가 같은 양식의 보고서를 사용하는 것과 같습니다.

표준화된 출력 형식이 있어야 결과를 조합하고 처리하기 쉬워집니다.

다음 코드를 살펴봅시다.

from pydantic import BaseModel
from typing import Optional, List
from langchain_core.output_parsers import PydanticOutputParser

# 표준 응답 스키마 정의
class AgentResponse(BaseModel):
    """모든 서브에이전트가 따라야 하는 응답 형식"""
    status: str  # "success", "error", "needs_handoff"
    result: str  # 처리 결과 또는 응답 메시지
    confidence: float  # 0.0 ~ 1.0 확신도
    next_action: Optional[str] = None  # 권장 후속 조치
    metadata: dict = {}  # 추가 정보

# 출력 파서 생성
parser = PydanticOutputParser(pydantic_object=AgentResponse)

# 에이전트 프롬프트에 포맷 지시 추가
agent_prompt = f"""
당신은 주문 처리 에이전트입니다.
작업을 완료한 후 반드시 다음 형식으로 응답하세요:

{parser.get_format_instructions()}

예시:
{{"status": "success", "result": "주문이 취소되었습니다", "confidence": 0.95}}
"""

# 응답 파싱
response = order_agent.invoke(query)
parsed = parser.parse(response)

김개발 씨의 멀티에이전트 시스템이 점점 커지고 있었습니다. 주문 에이전트, 환불 에이전트, 추천 에이전트, 배송 에이전트까지 총 4개의 서브에이전트가 운영 중이었습니다.

그런데 이상한 일이 발생했습니다. Supervisor가 서브에이전트의 응답을 처리하다가 자주 오류가 났습니다.

원인을 찾아보니 각 에이전트가 제각각 다른 형식으로 응답하고 있었습니다. 주문 에이전트는 "주문이 완료되었습니다"라고 짧게 답했습니다.

환불 에이전트는 JSON 형식으로 상세한 정보를 보냈습니다. 추천 에이전트는 리스트 형태로 상품을 나열했습니다.

Supervisor 입장에서는 이 다양한 형식을 일일이 파싱해야 했습니다. 박시니어 씨가 조언했습니다.

"모든 서브에이전트의 출력 형식을 표준화해야 해요. 마치 회사에서 보고서 양식을 통일하는 것처럼요." 출력 포맷팅은 마치 국제 우편 규격과 같습니다.

전 세계 어느 나라에서 편지를 보내도 봉투의 주소 표기 위치, 우표 부착 위치가 정해져 있습니다. 그래야 어느 나라 우체국에서도 편지를 처리할 수 있기 때문입니다.

표준 응답 스키마의 핵심 요소를 살펴보겠습니다. status는 처리 결과를 나타냅니다.

success, error, needs_handoff 등 몇 가지 상태 중 하나를 반환합니다. Supervisor는 이 값을 보고 다음 행동을 결정합니다.

result는 실제 응답 내용입니다. 사용자에게 전달할 메시지나 처리 결과가 담깁니다.

confidence는 에이전트의 확신도입니다. 0.0에서 1.0 사이의 값으로, 이 응답에 대한 에이전트의 자신감을 표현합니다.

확신도가 낮으면 Supervisor가 다른 에이전트에게 다시 물어볼 수 있습니다. next_action은 권장 후속 조치입니다.

예를 들어 환불 에이전트가 "환불 완료 후 설문조사 요청 권장"이라고 남기면, Supervisor가 이를 활용할 수 있습니다. 위 코드에서 Pydantic의 BaseModel을 사용하여 스키마를 정의했습니다.

PydanticOutputParser를 사용하면 에이전트 프롬프트에 포맷 지시를 자동으로 추가할 수 있습니다. 에이전트의 응답은 parser.parse()로 파싱하여 구조화된 객체로 변환합니다.

실무에서 주의할 점은 에이전트가 가끔 형식을 지키지 않는다는 것입니다. LLM은 완벽하지 않기 때문에 파싱에 실패할 수 있습니다.

따라서 try-except로 파싱 오류를 처리하고, 실패 시 재시도하거나 기본값을 사용하는 로직이 필요합니다. 또한 스키마는 처음부터 잘 설계해야 합니다.

나중에 필드를 추가하거나 변경하면 모든 에이전트와 Supervisor를 수정해야 하므로 비용이 큽니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.

출력 형식을 표준화한 후, Supervisor의 오류가 사라졌습니다. 모든 서브에이전트가 같은 형식으로 응답하니 결과 처리 로직도 깔끔해졌습니다.

코드 라인 수가 절반으로 줄었습니다.

실전 팁

💡 - 스키마는 초기에 신중하게 설계하세요. 나중에 바꾸기 어렵습니다

  • 파싱 실패에 대비한 에러 처리 로직을 반드시 추가하세요

6. 패턴 선택 기준

김개발 씨가 드디어 멀티에이전트의 핵심 패턴들을 모두 익혔습니다. 그런데 막상 실무에서 어떤 패턴을 선택해야 할지 고민이 됩니다.

박시니어 씨가 마지막 조언을 해주었습니다. "상황에 맞는 패턴 선택 기준을 알려줄게요."

멀티에이전트 패턴 선택은 프로젝트의 특성에 따라 달라집니다. Supervisor는 중앙 집중 제어가 필요할 때, Handoffs는 자연스러운 대화 흐름이 중요할 때 적합합니다.

실제로는 두 패턴을 혼합하여 사용하는 경우가 많습니다.

다음 코드를 살펴봅시다.

# 패턴 선택 의사결정 가이드

PATTERN_DECISION_TREE = {
    "supervisor": {
        "use_when": [
            "중앙에서 모든 결정을 통제해야 할 때",
            "에이전트 간 의존성이 적을 때",
            "감사/로깅이 중요한 규제 환경",
            "단순한 라우팅이 주 목적일 때"
        ],
        "avoid_when": [
            "대화 흐름이 자주 바뀌는 경우",
            "에이전트 간 깊은 협업이 필요한 경우"
        ]
    },
    "handoffs": {
        "use_when": [
            "자연스러운 대화 전환이 필요할 때",
            "에이전트가 자율적으로 판단해야 할 때",
            "복잡한 워크플로우가 있을 때"
        ],
        "avoid_when": [
            "엄격한 제어가 필요한 경우",
            "에이전트 순환 호출 위험이 있는 경우"
        ]
    },
    "hybrid": {
        "pattern": "초기 라우팅: Supervisor + 이후 흐름: Handoffs",
        "best_for": "대부분의 실무 프로젝트"
    }
}

김개발 씨가 6개월 동안 멀티에이전트 시스템을 개발하고 운영하면서 많은 것을 배웠습니다. 이제 후배 개발자가 찾아와 물었습니다.

"선배, 저도 멀티에이전트를 도입하려는데요. 어떤 패턴을 써야 할까요?" 김개발 씨는 잠시 생각하다가 박시니어 씨에게 들었던 조언을 떠올렸습니다.

패턴 선택에는 정답이 없습니다. 프로젝트의 특성에 따라 적절한 패턴이 달라집니다.

Supervisor 패턴은 언제 선택해야 할까요? 첫째, 중앙 집중 제어가 중요한 경우입니다.

금융이나 의료처럼 모든 판단을 기록하고 추적해야 하는 규제 환경에서는 Supervisor가 적합합니다. 모든 결정이 한 곳을 통과하므로 감사(audit)가 쉽습니다.

둘째, 단순 라우팅이 주 목적인 경우입니다. 사용자 요청을 분류해서 적절한 담당자에게 연결하는 것이 주 역할이라면 Supervisor로 충분합니다.

셋째, 에이전트 간 의존성이 적은 경우입니다. 각 에이전트가 독립적으로 작업을 완료하고 결과만 반환한다면 Supervisor가 효율적입니다.

Handoffs 패턴은 언제 선택해야 할까요? 첫째, 자연스러운 대화 전환이 필요한 경우입니다.

고객 상담처럼 대화 중에 주제가 자주 바뀌고, 맥락이 유지되어야 하는 상황에서 Handoffs가 빛을 발합니다. 둘째, 에이전트의 자율성이 중요한 경우입니다.

각 에이전트가 상황을 판단하여 스스로 다음 행동을 결정해야 한다면 Handoffs가 적합합니다. 셋째, 복잡한 워크플로우가 있는 경우입니다.

작업이 여러 단계를 거치며 진행되고, 중간에 분기가 많다면 Handoffs로 유연하게 처리할 수 있습니다. 그렇다면 실무에서는 어떻게 할까요?

박시니어 씨가 알려준 비밀이 있습니다. 대부분의 실무 프로젝트에서는 하이브리드 방식을 사용합니다.

초기 라우팅은 Supervisor가 담당하고, 이후 에이전트 간 이동은 Handoffs로 처리하는 것입니다. 예를 들어 고객이 처음 연락하면 Supervisor가 요청을 분석하여 주문팀, 환불팀, 기술지원팀 중 하나로 연결합니다.

이후 해당 팀 내에서 대화가 진행되다가 다른 팀의 도움이 필요하면 Handoffs로 직접 연결합니다. 패턴 선택 시 고려해야 할 다른 요소들도 있습니다.

확장성을 생각해보세요. 에이전트가 계속 늘어날 예정이라면 어떤 패턴이 관리하기 쉬울까요?

Supervisor는 새 에이전트를 도구로 추가하면 됩니다. Handoffs는 각 에이전트에 새로운 handoff 도구를 추가해야 할 수 있습니다.

디버깅 용이성도 중요합니다. 문제가 발생했을 때 어디서 잘못되었는지 찾기 쉬워야 합니다.

Supervisor는 모든 호출이 기록되므로 추적이 쉽습니다. Handoffs는 에이전트 간 이동 경로를 별도로 로깅해야 합니다.

성능도 고려해야 합니다. Supervisor는 매번 중앙을 거치므로 지연이 발생할 수 있습니다.

Handoffs는 직접 연결되어 빠르지만, 잘못된 이양이 발생하면 더 큰 비용이 들 수 있습니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.

후배에게 이 모든 것을 설명한 김개발 씨는 마지막으로 덧붙였습니다. "결국 정답은 없어요.

프로젝트 상황에 맞게 선택하고, 운영하면서 계속 개선하는 거예요." 6개월 전 스파게티 코드로 고생하던 김개발 씨가 이제는 멀티에이전트 전문가가 되어 있었습니다. 여러분도 오늘 배운 패턴들을 실제 프로젝트에 적용해 보세요.

처음에는 어렵겠지만, 하나씩 익히다 보면 어느새 복잡한 AI 시스템도 자신 있게 설계할 수 있게 될 것입니다.

실전 팁

💡 - 처음에는 Supervisor로 시작하고, 필요에 따라 Handoffs를 점진적으로 도입하세요

  • 패턴 선택보다 중요한 것은 Context Injection과 출력 포맷팅의 일관성입니다

이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!

#Python#MultiAgent#LangChain#Supervisor#Handoffs#LLM#AI,LLM,Python,LangChain

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.