콘텐츠로 이동

핸드오프

핸드오프를 사용하면 에이전트가 다른 에이전트에 작업을 위임할 수 있습니다. 이는 서로 다른 에이전트가 각기 다른 영역에 특화된 시나리오에서 특히 유용합니다. 예를 들어 고객 지원 앱에는 주문 상태, 환불, FAQ 등의 작업을 각각 전담하는 에이전트가 있을 수 있습니다.

핸드오프는 LLM에 도구로 표현됩니다. 따라서 Refund Agent라는 에이전트로 핸드오프가 있다면, 도구는 transfer_to_refund_agent로 호출됩니다.

핸드오프 생성

모든 에이전트에는 handoffs 매개변수가 있으며, 이 매개변수는 Agent를 직접 받거나 핸드오프를 사용자 지정하는 Handoff 객체를 받을 수 있습니다.

일반 Agent 인스턴스를 전달하면, 해당 handoff_description이 설정된 경우 기본 도구 설명에 덧붙여집니다. 전체 handoff() 객체를 작성하지 않고도 모델이 해당 핸드오프를 선택해야 하는 경우를 암시하는 데 사용하세요.

Agents SDK에서 제공하는 handoff() 함수를 사용하여 핸드오프를 만들 수 있습니다. 이 함수를 사용하면 핸드오프할 에이전트를 지정하고, 선택적으로 오버라이드와 입력 필터를 지정할 수 있습니다.

기본 사용법

간단한 핸드오프를 만드는 방법은 다음과 같습니다:

from agents import Agent, handoff

billing_agent = Agent(name="Billing agent")
refund_agent = Agent(name="Refund agent")

# (1)!
triage_agent = Agent(name="Triage agent", handoffs=[billing_agent, handoff(refund_agent)])
  1. 에이전트를 직접 사용할 수도 있고(billing_agent에서처럼), handoff() 함수를 사용할 수도 있습니다.

handoff() 함수를 통한 핸드오프 사용자 지정

handoff() 함수를 사용하면 여러 항목을 사용자 지정할 수 있습니다.

  • agent: 핸드오프 대상 에이전트입니다.
  • tool_name_override: 기본적으로 Handoff.default_tool_name() 함수가 사용되며, 이는 transfer_to_<agent_name>으로 해석됩니다. 이를 오버라이드할 수 있습니다.
  • tool_description_override: Handoff.default_tool_description()의 기본 도구 설명을 오버라이드합니다.
  • on_handoff: 핸드오프가 호출될 때 실행되는 콜백 함수입니다. 핸드오프가 호출되는 것을 알게 되는 즉시 일부 데이터 가져오기를 시작하는 등의 작업에 유용합니다. 이 함수는 에이전트 컨텍스트를 받으며, 선택적으로 LLM이 생성한 입력도 받을 수 있습니다. 입력 데이터는 input_type 매개변수로 제어됩니다.
  • input_type: 핸드오프 도구 호출 인자의 스키마입니다. 설정하면 파싱된 페이로드가 on_handoff에 전달됩니다.
  • input_filter: 다음 에이전트가 받는 입력을 필터링할 수 있게 해줍니다. 자세한 내용은 아래를 참조하세요.
  • is_enabled: 핸드오프가 활성화되어 있는지 여부입니다. 불리언이거나 불리언을 반환하는 함수일 수 있으므로, 런타임에 동적으로 핸드오프를 활성화하거나 비활성화할 수 있습니다.
  • nest_handoff_history: RunConfig 수준의 nest_handoff_history 설정에 대한 선택적 호출별 오버라이드입니다. None이면 활성 실행 구성에 정의된 값이 대신 사용됩니다.

handoff() 헬퍼는 항상 전달한 특정 agent로 제어권을 이전합니다. 가능한 목적지가 여러 개라면 목적지마다 하나의 핸드오프를 등록하고 모델이 그중에서 선택하게 하세요. 호출 시점에 어떤 에이전트를 반환할지 자체 핸드오프 코드가 결정해야 하는 경우에만 사용자 지정 Handoff를 사용하세요.

from agents import Agent, handoff, RunContextWrapper

def on_handoff(ctx: RunContextWrapper[None]):
    print("Handoff called")

agent = Agent(name="My agent")

handoff_obj = handoff(
    agent=agent,
    on_handoff=on_handoff,
    tool_name_override="custom_handoff_tool",
    tool_description_override="Custom description",
)

핸드오프 입력

특정 상황에서는 LLM이 핸드오프를 호출할 때 일부 데이터를 제공하기를 원할 수 있습니다. 예를 들어 "에스컬레이션 에이전트"로 핸드오프하는 경우를 생각해 보세요. 이를 기록할 수 있도록 사유를 제공받고 싶을 수 있습니다.

from pydantic import BaseModel

from agents import Agent, handoff, RunContextWrapper

class EscalationData(BaseModel):
    reason: str

async def on_handoff(ctx: RunContextWrapper[None], input_data: EscalationData):
    print(f"Escalation agent called with reason: {input_data.reason}")

agent = Agent(name="Escalation agent")

handoff_obj = handoff(
    agent=agent,
    on_handoff=on_handoff,
    input_type=EscalationData,
)

input_type은 핸드오프 도구 호출 자체의 인자를 설명합니다. SDK는 해당 스키마를 핸드오프 도구의 parameters로 모델에 노출하고, 반환된 JSON을 로컬에서 검증하며, 파싱된 값을 on_handoff에 전달합니다.

이는 다음 에이전트의 주 입력을 대체하지 않으며, 다른 목적지를 선택하지도 않습니다. handoff() 헬퍼는 여전히 래핑한 특정 에이전트로 이전하며, 수신 에이전트는 input_filter 또는 중첩 핸드오프 기록 설정으로 변경하지 않는 한 여전히 대화 기록을 봅니다.

input_typeRunContextWrapper.context와도 별개입니다. 로컬에 이미 있는 애플리케이션 상태나 종속성이 아니라, 모델이 핸드오프 시점에 결정하는 메타데이터에는 input_type을 사용하세요.

input_type 사용 시점

핸드오프에 reason, language, priority, summary와 같이 모델이 생성한 작은 메타데이터가 필요할 때 input_type을 사용하세요. 예를 들어 분류 에이전트는 { "reason": "duplicate_charge", "priority": "high" }와 함께 환불 에이전트로 핸드오프할 수 있으며, 환불 에이전트가 이어받기 전에 on_handoff에서 해당 메타데이터를 기록하거나 저장할 수 있습니다.

목표가 다른 경우에는 다른 메커니즘을 선택하세요:

입력 필터

핸드오프가 발생하면 새 에이전트가 대화를 이어받아 이전 대화 기록 전체를 볼 수 있는 것처럼 동작합니다. 이를 변경하려면 input_filter를 설정할 수 있습니다. 입력 필터는 HandoffInputData를 통해 기존 입력을 받는 함수이며, 새로운 HandoffInputData를 반환해야 합니다.

HandoffInputData에는 다음이 포함됩니다:

  • input_history: Runner.run(...)이 시작되기 전의 입력 기록입니다.
  • pre_handoff_items: 핸드오프가 호출된 에이전트 턴 이전에 생성된 항목입니다.
  • new_items: 핸드오프 호출과 핸드오프 출력 항목을 포함하여 현재 턴 동안 생성된 항목입니다.
  • input_items: new_items 대신 다음 에이전트로 전달할 선택적 항목으로, 세션 기록을 위해 new_items는 그대로 유지하면서 모델 입력을 필터링할 수 있습니다.
  • run_context: 핸드오프가 호출된 시점의 활성 RunContextWrapper입니다.

중첩 핸드오프는 옵트인 베타로 제공되며, 안정화하는 동안 기본적으로 비활성화되어 있습니다. RunConfig.nest_handoff_history를 활성화하면 러너는 이전 대화 기록을 하나의 assistant 요약 메시지로 축약하고, 동일한 실행 중 여러 핸드오프가 발생할 때 새 턴을 계속 덧붙이는 <CONVERSATION HISTORY> 블록으로 감쌉니다. 전체 input_filter를 작성하지 않고 생성된 메시지를 대체하려면 RunConfig.handoff_history_mapper를 통해 자체 매핑 함수를 제공할 수 있습니다. 이 옵트인은 핸드오프와 실행 모두 명시적 input_filter를 제공하지 않을 때만 적용되므로, 이미 페이로드를 사용자 지정하는 기존 코드(이 저장소의 코드 예제 포함)는 변경 없이 현재 동작을 유지합니다. 단일 핸드오프에 대한 중첩 동작은 handoff(...)nest_handoff_history=True 또는 False를 전달하여 오버라이드할 수 있으며, 이는 Handoff.nest_handoff_history를 설정합니다. 생성된 요약의 래퍼 텍스트만 변경해야 하는 경우, 에이전트를 실행하기 전에 set_conversation_history_wrappers를 호출하세요(선택적으로 reset_conversation_history_wrappers도 호출할 수 있습니다).

핸드오프와 활성 RunConfig.handoff_input_filter가 모두 필터를 정의하는 경우, 해당 특정 핸드오프에는 핸드오프별 input_filter가 우선합니다.

Note

핸드오프는 단일 실행 안에서만 이루어집니다. 입력 가드레일은 여전히 체인의 첫 번째 에이전트에만 적용되고, 출력 가드레일은 최종 출력을 생성하는 에이전트에만 적용됩니다. 워크플로 안의 각 사용자 지정 함수 도구 호출 주변에 검사가 필요할 때는 도구 가드레일을 사용하세요.

몇 가지 일반적인 패턴(예: 기록에서 모든 도구 호출 제거)은 agents.extensions.handoff_filters에 구현되어 있습니다.

from agents import Agent, handoff
from agents.extensions import handoff_filters

agent = Agent(name="FAQ agent")

handoff_obj = handoff(
    agent=agent,
    input_filter=handoff_filters.remove_all_tools, # (1)!
)
  1. 이렇게 하면 FAQ agent가 호출될 때 기록에서 모든 도구가 자동으로 제거됩니다.

권장 프롬프트

LLM이 핸드오프를 올바르게 이해하도록 하려면 에이전트에 핸드오프 관련 정보를 포함할 것을 권장합니다. agents.extensions.handoff_prompt.RECOMMENDED_PROMPT_PREFIX에 권장 접두사가 있으며, agents.extensions.handoff_prompt.prompt_with_handoff_instructions를 호출하여 프롬프트에 권장 데이터를 자동으로 추가할 수도 있습니다.

from agents import Agent
from agents.extensions.handoff_prompt import RECOMMENDED_PROMPT_PREFIX

billing_agent = Agent(
    name="Billing agent",
    instructions=f"""{RECOMMENDED_PROMPT_PREFIX}
    <Fill in the rest of your prompt here>.""",
)