sy/dev
Guide
16 min read

OpenAI Agents Python: handoff·guardrail·trace로 에이전트 워크플로 설계하기

OpenAI Agents Python SDK의 핵심 추상화인 Agent, handoff, guardrail, tracing을 실무 워크플로 설계 관점에서 정리한다.

한 줄 요약

OpenAI Agents Python SDK는 “LLM 호출 래퍼”라기보다, 에이전트 실행 루프에서 반복해서 필요한 위임(handoff), 검증(guardrail), 관찰(trace) 을 기본 구성요소로 묶은 경량 런타임에 가깝다.

내 의견부터 말하면, 이 SDK의 가치는 모델 호출 코드 몇 줄을 줄이는 데 있지 않다. 진짜 포인트는 에이전트가 실패했을 때 “어느 agent가, 어떤 tool을, 어떤 guardrail 이후에, 왜 호출했는지”를 추적 가능한 구조로 만드는 데 있다.

무엇이 나왔나

OpenAI Agents Python은 Python에서 multi-agent workflow를 만들기 위한 SDK다. README 기준 핵심 개념은 대략 다음이다.

  • Agent: instructions, tools, guardrails, handoffs를 가진 실행 단위
  • Runner: agent 실행 루프를 돌리는 진입점
  • tools: function tool, MCP, hosted tool 등 외부 행동 표면
  • handoffs / agents as tools: 특정 작업을 다른 agent에게 넘기는 위임 메커니즘
  • guardrails: 입력·출력·tool 호출 전후 검증
  • sessions: 대화 히스토리 관리
  • tracing: agent run, LLM generation, tool call, handoff, guardrail 이벤트 추적
  • sandbox agents: 파일시스템과 명령 실행이 필요한 장기 작업용 격리 실행 환경

공식 README는 “lightweight yet powerful framework”라고 설명한다. 이 표현은 어느 정도 맞지만, 개발자 입장에서 더 중요한 질문은 이거다.

기존에 직접 짜던 agent loop를 어디까지 SDK에 맡겨도 되는가?

기존 방식과 차이

간단한 챗봇은 직접 루프를 짜도 된다.

messages = []
while True:
    response = client.responses.create(...)
    if response.tool_calls:
        run_tools(response.tool_calls)
    else:
        break

문제는 여기서 조금만 실무 쪽으로 가면 루프가 금방 지저분해진다는 점이다.

  • 사용자의 첫 입력을 막아야 하는가?
  • tool 호출 전에 권한·인자를 검증해야 하는가?
  • specialist agent에게 넘긴 뒤 context를 얼마나 보여줘야 하는가?
  • 최종 답변이 정책을 위반하면 어떻게 중단할 것인가?
  • 실패한 실행을 나중에 재현하거나 디버깅할 수 있는가?

OpenAI Agents SDK는 이 문제를 Agent, handoff, guardrail, trace라는 명시적 경계로 쪼갠다. 즉 “프롬프트 잘 쓰기”보다 “실행 경계를 코드로 표현하기”에 가깝다.

핵심 구조: Agent는 프롬프트가 아니라 실행 계약이다

공식 문서에서 Agent는 LLM에 instructions, tools, optional runtime behavior를 붙인 구성요소다. 예시는 단순하지만, 의미는 꽤 크다.

from agents import Agent, Runner, function_tool
 
@function_tool
def search_docs(query: str) -> str:
    """문서 검색 결과를 반환한다."""
    return "..."
 
agent = Agent(
    name="Docs assistant",
    instructions="문서 근거가 있을 때만 답변한다.",
    tools=[search_docs],
)
 
result = Runner.run_sync(agent, "배포 캐시는 어떻게 비워?")
print(result.final_output)

여기서 중요한 것은 Agent가 단순히 system prompt 저장소가 아니라는 점이다. 이 객체 안에 다음 실행 정책이 같이 들어간다.

  • 어떤 tool을 쓸 수 있는가
  • 어떤 agent에게 넘길 수 있는가
  • 어떤 input/output guardrail을 통과해야 하는가
  • structured output을 요구할 것인가
  • model settings와 tool behavior를 어떻게 제한할 것인가

실무에서는 agent를 “역할 이름”으로만 나누면 망하기 쉽다. Refund Agent, Billing Agent 같은 이름보다 중요한 것은 각 agent가 가진 권한과 종료 조건이다.

Handoff: multi-agent는 roleplay가 아니라 routing contract다

SDK의 handoff는 한 agent가 다른 agent에게 작업을 넘기는 구조다. 문서에 따르면 handoff는 LLM에게 tool처럼 노출된다. 예를 들어 Refund Agent로 넘기는 handoff는 transfer_to_refund_agent 같은 tool 호출로 표현될 수 있다.

from agents import Agent, handoff
 
billing_agent = Agent(
    name="Billing agent",
    instructions="결제 내역과 청구 관련 질문만 처리한다.",
)
 
refund_agent = Agent(
    name="Refund agent",
    instructions="환불 정책과 환불 요청만 처리한다.",
)
 
triage_agent = Agent(
    name="Triage agent",
    instructions="사용자 요청을 분류하고 적절한 specialist에게 넘긴다.",
    handoffs=[billing_agent, handoff(refund_agent)],
)

내가 보기엔 handoff를 쓸 때 가장 조심해야 할 부분은 “agent를 많이 만드는 것”이 아니다. 오히려 다음 세 가지를 명시하지 않은 handoff가 위험하다.

  1. 언제 넘길지: handoff description이나 tool description에 routing 조건을 구체적으로 써야 한다.
  2. 무엇을 넘길지: 다음 agent에게 전체 대화 로그를 줄지, 필요한 summary만 줄지 정해야 한다.
  3. 넘긴 뒤 누가 책임질지: final answer를 specialist가 끝낼지, manager가 다시 검토할지 정해야 한다.

multi-agent 시스템은 조직도처럼 그리면 그럴듯해 보인다. 하지만 실제 운영에서는 handoff가 많을수록 실패 지점도 늘어난다. 그래서 처음부터 agent 5개를 만들기보다, 단일 agent에서 tool boundary를 잡고 병목이 명확해질 때 specialist를 분리하는 편이 낫다.

Guardrail: 입력·출력만 막으면 충분하지 않다

공식 guardrails 문서에서 특히 중요한 부분은 workflow boundary다. 문서 기준으로 agent-level input guardrail은 첫 agent의 최초 사용자 입력에만 적용되고, output guardrail은 최종 출력을 만든 agent에 적용된다. custom function tool 호출마다 검증이 필요하면 tool guardrail을 써야 한다.

이건 실무적으로 꽤 큰 차이다.

from agents import Agent, input_guardrail, GuardrailFunctionOutput
 
@input_guardrail
def reject_secret_request(ctx, agent, input_data):
    text = str(input_data).lower()
    blocked = "secret" in text or "api key" in text
    return GuardrailFunctionOutput(
        output_info={"reason": "secret-like request" if blocked else "ok"},
        tripwire_triggered=blocked,
    )
 
agent = Agent(
    name="Internal assistant",
    instructions="사내 문서를 도와주는 assistant다.",
    input_guardrails=[reject_secret_request],
)

위 코드는 방향만 보여주는 예시다. 실제 서비스에서는 단순 문자열 검사보다 classifier, policy engine, permission check, audit log를 같이 붙여야 한다.

더 중요한 점: guardrail 실행 모드도 봐야 한다. 문서에 따르면 input guardrail은 기본적으로 agent 실행과 병렬로 돌 수 있고, blocking mode로 설정하면 agent 실행 전에 끝낼 수 있다. side effect가 있는 tool을 다루는 agent라면 latency보다 blocking 검증이 더 중요할 때가 많다.

내 기준은 이렇다.

  • 단순 답변 품질 검사는 parallel도 괜찮다.
  • 비용 절감 목적의 사전 필터도 parallel이 유리할 수 있다.
  • 파일 수정, 메시지 전송, 결제, 배포 같은 side effect 앞에서는 blocking 검증을 우선한다.
  • agent-level guardrail만 믿지 말고 tool-level guardrail을 별도로 둔다.

Tracing: 에이전트 운영에서 로그는 기능이다

tracing 문서에 따르면 SDK는 기본적으로 Runner.run, agent execution, LLM generation, function tool call, guardrail, handoff 등을 span으로 기록한다. OpenAI Traces dashboard로 볼 수 있고, 필요하면 비활성화하거나 custom trace processor를 붙일 수 있다.

이 부분은 agent framework 선택에서 생각보다 중요하다. demo에서는 답이 나오면 끝이지만, 운영에서는 다음 질문이 더 자주 나온다.

  • 왜 이 tool을 호출했나?
  • handoff는 적절했나?
  • guardrail은 언제 통과했고 언제 tripwire가 터졌나?
  • latency는 model generation 때문인가, tool 때문인가?
  • 같은 입력에서 실패가 재현되는가?

agent가 tool을 호출하기 시작하면 로그는 부가 기능이 아니라 안전장치다. 특히 여러 agent가 handoff를 반복하는 구조에서는 최종 답변만 저장해도 아무것도 디버깅할 수 없다.

from agents import Runner, trace, flush_traces
 
try:
    with trace("support_triage"):
        result = Runner.run_sync(triage_agent, "환불 상태 확인해줘")
        print(result.final_output)
finally:
    flush_traces()

공식 문서도 long-running worker에서는 작업 단위가 끝난 뒤 flush_traces()를 호출해 즉시 export를 보장하는 패턴을 소개한다. Celery, RQ, FastAPI background task처럼 프로세스 수명과 job 수명이 다를 때 특히 필요하다.

실무 설계 체크리스트

OpenAI Agents Python을 검토한다면 “SDK가 멋져 보이는가”보다 아래를 먼저 확인하는 게 낫다.

1. Agent를 권한 단위로 나눴는가

역할 이름보다 권한 경계가 먼저다.

  • 읽기 전용 agent
  • 파일 수정 가능 agent
  • 외부 메시지 전송 가능 agent
  • 배포·인프라 변경 가능 agent

이렇게 side effect 기준으로 나누면 guardrail과 tracing도 자연스럽게 붙는다.

2. Handoff 조건이 테스트 가능한가

“필요하면 전문가에게 넘긴다”는 조건은 너무 흐리다. 최소한 다음을 문서화해야 한다.

  • 어떤 intent에서 handoff하는가
  • handoff payload schema는 무엇인가
  • handoff 전후로 context를 필터링하는가
  • 잘못 handoff했을 때 fallback은 무엇인가

3. Tool 호출마다 정책 검증이 있는가

agent-level input/output guardrail은 필요하지만 충분하지 않다. 위험한 것은 대부분 중간 tool call에서 발생한다.

예를 들어 coding agent라면 다음 tool 앞에 별도 검증이 있어야 한다.

  • shell command 실행
  • file write / patch
  • network request
  • git commit / push
  • secret 접근

4. Trace를 운영 지표로 쓸 수 있는가

trace는 예쁜 디버그 UI로 끝나면 아깝다. 최소한 이런 지표를 뽑을 수 있어야 한다.

  • agent별 handoff 비율
  • tool별 실패율과 latency
  • guardrail tripwire 비율
  • final answer 전 평균 tool call 수
  • 같은 task class에서 retry 횟수

이 지표가 쌓이면 prompt 수정이 아니라 workflow 수정이 가능해진다.

어디에 쓰면 좋은가

지금 기준으로 OpenAI Agents Python은 이런 작업에 잘 맞아 보인다.

  • 고객지원 triage처럼 specialist handoff가 명확한 workflow
  • 내부 문서 QA + ticket 생성처럼 retrieval과 side effect가 섞인 workflow
  • coding assistant처럼 tool call, file edit, command execution이 필요한 workflow
  • agent 실행을 trace 기반으로 디버깅해야 하는 팀 실험
  • guardrail, handoff, tool boundary를 코드 레벨에서 표준화하려는 프로토타입

반대로 단순 Q&A, 한두 개 tool만 쓰는 작은 봇, 이미 LangGraph 같은 graph runtime에 깊게 묶인 시스템이라면 굳이 바꿀 이유는 약하다. SDK를 쓰는 순간 OpenAI 쪽 abstraction에 맞춰 사고하게 되므로, 기존 orchestration 계층과 중복되는지도 봐야 한다.

한계와 주의점

아직 초안 단계에서 단정하고 싶지 않은 부분도 있다.

첫째, SDK가 제공하는 guardrail 추상화가 실제 조직의 policy engine을 대체한다고 보면 안 된다. guardrail은 policy를 실행 루프에 연결하는 hook에 가깝다. 정책 자체는 별도의 권한 모델, 감사 로그, approval workflow로 관리해야 한다.

둘째, handoff가 multi-agent 품질을 자동으로 보장하지 않는다. routing instruction이 애매하면 specialist agent가 많아질수록 오히려 latency와 실패 모드가 늘어난다.

셋째, tracing은 저장되는 데이터의 민감도까지 같이 봐야 한다. 공식 문서에는 ZDR 정책 조직에서는 tracing을 사용할 수 없다고 적혀 있다. 사내 데이터나 사용자 개인정보가 span에 남을 수 있다면 trace redaction과 비활성화 정책을 먼저 설계해야 한다.

넷째, sandbox agent는 매력적이지만 권한 모델을 더 엄격히 봐야 한다. 파일시스템과 command execution이 들어가는 순간 “편리한 agent”가 아니라 “자동화된 작업자”가 된다. 격리, 네트워크 제한, secret boundary 없이 운영에 붙이는 건 별로 좋은 생각이 아니다.

내 결론

OpenAI Agents Python의 핵심은 “agent를 쉽게 만든다”가 아니다. 더 정확히는 agent workflow에서 반복되는 운영 경계를 기본 추상화로 올렸다는 점이다.

  • handoff는 역할 분담이 아니라 routing contract다.
  • guardrail은 안전 문구가 아니라 실행 전후의 차단 지점이다.
  • tracing은 디버그 옵션이 아니라 운영 필수 로그다.

그래서 이 SDK를 쓴다면 처음부터 화려한 multi-agent demo를 만들기보다, 단일 agent + 위험한 tool 1개 + blocking guardrail + trace export부터 검증하는 편이 낫다. 그 작은 루프가 안정적으로 보이면 handoff를 추가하고, 그 다음 sandbox나 장기 실행 workflow로 넓히는 순서가 안전하다.

참고 자료

Comments