10. 핸드오프: 통제권을 넘기는 협업
- 핸드오프(handoff)의 개념과 "에이전트를 도구로"와의 차이를 명확히 안다
- 트리아지(triage) 에이전트로 요청을 적절한 전문가에게 라우팅한다
- 핸드오프가 내부적으로 도구로 표현된다는 점을 이해한다
- 입력 필터로 핸드오프 시 전달 맥락을 조절하는 법을 본다
비유로 시작하기: 콜센터의 부서 연결
콜센터에 전화하면 먼저 안내원이 받습니다. "환불 문의는 1번, 기술 지원은 2번..." 안내원은 용건을 파악한 뒤 해당 부서로 연결합니다. 연결되는 순간, 통화의 주도권은 그 부서로 완전히 넘어갑니다. 안내원은 더 이상 통화에 끼어들지 않습니다.
핸드오프가 정확히 이것입니다. 한 에이전트(트리아지)가 요청을 파악해 적절한 전문 에이전트에게 대화 자체를 넘기고, 통제권을 이양합니다. 8장의 "에이전트를 도구로"(매니저가 통제권 유지)와는 반대입니다.
핸드오프 vs 에이전트를 도구로 (다시)
flowchart TB
classDef agent fill:#80DEEA,stroke:#00838F,color:#000
classDef result fill:#A5D6A7,stroke:#2E7D32,color:#000
subgraph 핸드오프
T[트리아지]:::agent -->|통제권 이양| B[빌링 에이전트]:::agent --> F1[빌링이 최종 답]:::result
end
subgraph 에이전트를_도구로
M[매니저]:::agent -->|호출·결과 회수| W[전문가]:::agent
W --> M
M --> F2[매니저가 최종 답]:::result
end
핵심 차이는 "누가 최종 답을 내는가"입니다. 핸드오프는 넘겨받은 에이전트가 최종 답을 내고, 에이전트를 도구로 쓰면 매니저가 종합해 답합니다.
트리아지 에이전트 만들기
가장 흔한 핸드오프 패턴은 트리아지입니다. 들어온 요청을 분류해 전담 에이전트로 보냅니다. 강의의 스포츠 예제(크리켓/축구)를 따라가 봅시다.
from agents import Agent, Runner # 전문 에이전트들 billing_agent = Agent( name="Billing agent", handoff_description="결제·청구·환불 관련 문의를 처리한다.", instructions="결제 관련 질문에 정확히 답한다.", ) refund_agent = Agent( name="Refund agent", handoff_description="환불 요청을 처리한다.", instructions="환불 절차를 안내하고 처리한다.", ) # 트리아지 에이전트 — handoffs로 연결 triage_agent = Agent( name="Triage agent", instructions="사용자 요청을 파악해 적절한 전문 에이전트로 넘긴다.", handoffs=[billing_agent, refund_agent], ) result = Runner.run_sync(triage_agent, "지난달 환불이 아직 안 됐어요.") print(result.final_output) # 환불 에이전트가 답한 내용 print(result.last_agent.name) # "Refund agent" — 누가 최종 답을 냈는지
handoffs=[...]에 에이전트를 나열하면 끝입니다. 트리아지가 요청을 보고 적절한 에이전트로 넘기며, result.last_agent로 누가 최종 답을 냈는지 확인할 수 있습니다.
📌 핵심: handoff_description은 트리아지가 "이 에이전트에게 언제 넘길지" 판단하는 힌트입니다. 8장의 도구 description, 7장의 독스트링과 같은 역할입니다. 명확히 적을수록 라우팅이 정확해집니다.
핸드오프는 내부적으로 도구다
흥미로운 사실: SDK는 핸드오프를 도구로 표현합니다. Refund agent로의 핸드오프는 모델 입장에서 transfer_to_refund_agent라는 도구처럼 보입니다. 그래서 모델은 "이 도구(=핸드오프)를 호출할까?"를 다른 도구와 같은 방식으로 판단합니다.
sequenceDiagram
participant U as 사용자
participant T as 트리아지
participant R as 환불 에이전트
U->>T: "환불 문의"
Note over T: transfer_to_refund_agent<br/>도구 호출로 인식
T->>R: 핸드오프 (통제권 이양)
R-->>U: 환불 안내 (최종 답)
Note over T,R: 이후 트리아지는 개입하지 않음
핸드오프 커스터마이징: handoff() 함수
단순 위임을 넘어, 핸드오프 시점에 콜백을 실행하거나 전달 맥락을 조절하고 싶을 때 handoff() 함수를 씁니다.
from agents import Agent, handoff from agents.extensions import handoff_filters faq_agent = Agent(name="FAQ agent", instructions="자주 묻는 질문에 답한다.") # 핸드오프 시 이전 도구 호출 기록을 제거하고 넘김 handoff_obj = handoff( agent=faq_agent, input_filter=handoff_filters.remove_all_tools, ) triage = Agent(name="Triage", handoffs=[handoff_obj])
input_filter로 넘겨받는 에이전트가 볼 맥락을 다듬을 수 있습니다. 예시의 remove_all_tools는 이전 단계의 도구 호출 기록을 지워, FAQ 에이전트가 불필요한 "작업 메모"를 보지 않게 합니다.
handoff() 옵션 | 역할 |
|---|---|
tool_name_override | 핸드오프 도구 이름 변경(기본 transfer_to_*) |
tool_description_override | 핸드오프 설명 변경 |
on_handoff | 핸드오프 발생 시 실행할 콜백(로깅·데이터 사전 로드 등) |
input_filter | 넘길 맥락을 가공 |
⚠️ 흔한 실수: 핸드오프가 양방향이라 착각하는 것. 핸드오프는 단방향입니다. 넘긴 뒤 자동으로 돌아오지 않습니다. 다시 트리아지로 보내려면 넘겨받은 에이전트의 handoffs에 트리아지를 넣어 명시적으로 구성해야 합니다.
💡 팁: 가드레일과의 관계도 기억하세요. 입력 가드레일은 체인의 첫 에이전트(트리아지)에서만 돕니다. 핸드오프로 넘어간 에이전트에는 입력 가드레일이 다시 적용되지 않습니다.
💡 실습 아이디어(강의의 Handoffs 실습 대응): 트리아지 + 기초상담 + 전문분석 3개 에이전트를 만들어, 간단한 질문은 기초상담이, 깊은 분석이 필요한 질문은 전문분석 에이전트가 처리하도록 라우팅해 보세요. result.last_agent로 매번 누가 답했는지 확인하면 라우팅이 의도대로인지 검증됩니다.
이 장에서 배운 것
- 핸드오프는 한 에이전트가 다른 에이전트에게 통제권을 완전히 넘기는 협업이다.
- 에이전트를 도구로 쓰는 패턴(매니저 유지)과 달리, 넘겨받은 에이전트가 최종 답을 낸다.
- 핸드오프는 내부적으로
transfer_to_*도구로 표현되며,handoff_description이 라우팅 정확도를 좌우한다. handoff()함수와input_filter로 콜백·전달 맥락을 커스터마이즈할 수 있다. 핸드오프는 단방향이다.
✍️ 확인 문제
- 같은 3개 에이전트로 "매니저가 종합해 답하는 보고서"와 "담당 부서가 직접 답하는 콜센터"를 만든다면, 각각 어떤 패턴(도구 vs 핸드오프)이 맞는가?
result.last_agent는 무엇을 알려주며, 왜 멀티 에이전트 디버깅에 유용한가?- 트리아지가 자꾸 엉뚱한 에이전트로 요청을 넘긴다. 가장 먼저 손볼 부분은 무엇인가?
이전 장: 09. 가드레일
다음 부: 4부 · AutoGen — 대화로 협업하는 멀티 에이전트의 또 다른 철학을 배웁니다.