Python으로 에이전틱 워크플로우 (Agentic Workflows) 구축하기
요약
Python을 사용하여 에이전틱 워크플로우를 구축하는 세 가지 방식과 구현 전략을 설명합니다. 단일 호출, 코드 오케스트레이션, 그리고 모델이 스스로 도구 사용을 결정하는 에이전트 방식의 차이점을 다룹니다.
핵심 포인트
- 에이전틱 워크플로우는 모델이 도구 호출 순서와 횟수를 스스로 결정하는 방식입니다.
- 수동 루프 방식은 인자 검증 및 로그 기록 등 완전한 제어가 가능합니다.
- SDK 도구 러너는 데코레이터를 통해 구현이 간편하지만 제어 범위가 제한적입니다.
- 운영 환경에서는 비용과 안전을 위해 루프 횟수 제한과 체크포인트 설정이 중요합니다.
서론
"에이전트 (Agent)\
- 단일 LLM 호출 (A single LLM call). 이 티켓을 분류하세요. 이 문서를 요약하세요. 하나의 프롬프트가 입력되어 하나의 답변이 출력되는 것으로 문제가 해결된다면, 그것이 시스템의 전부입니다.
- 코드에 의해 오케스트레이션되는 워크플로우 (A code-orchestrated workflow). LLM 호출과 결정론적 단계(deterministic steps)로 이루어진 고정된 시퀀스 — 모델을 호출하여 필드를 추출하고, 코드로 이를 검증하며, 다시 모델을 호출하여 답장 초안을 작성합니다. 단계의 *순서(order)*는 사전에 알려져 있으며, 모델의 머릿속이 아닌 Python 코드 내에 존재합니다.
- 에이전트 (An agent). 모델 스스로가 각 결과로부터 배운 내용을 바탕으로 어떤 도구를 호출할지, 몇 번 호출할지, 그리고 어떤 순서로 호출할지를 결정합니다. 이러한 개방성(open-endedness) 자체가 핵심인 작업에 에이전트를 할당하세요. 예를 들어, 검색이 몇 번 필요할지 미리 알 수 없는 연구 보조원이나, 마지막 명령어가 출력한 내용에 따라 반응해야 하는 디버깅 도우미 같은 경우입니다.
3단계를 구축하기 전에, 해당 작업을 네 가지 기준으로 검토하십시오. 만약 하나라도
에이전트가 필요하다고 판단되면, 관련된 도구(tools)가 무엇이든 그 형태는 동일합니다. 사용 가능한 도구 목록과 함께 모델을 호출합니다. 만약 모델이 도구 사용을 요청하며 응답한다면(stop_reason == "tool_use"), 여러분의 코드에서 해당 도구를 실행하고 그 결과를 tool_result로 다시 보냅니다. 모델이 end_turn으로 응답할 때까지 이 과정을 반복합니다. Python에서 이 루프(loop)를 실행하는 두 가지 방법이 있습니다. 완전한 제어를 위해 직접 작성하거나, SDK의 도구 러너(tool runner)가 대신 실행하도록 하는 것입니다.
수동 루프 (The Manual Loop) — 완전한 제어
루프를 직접 작성한다는 것은 모든 도구 호출이 실행되기 전에 여러분의 코드를 거친다는 것을 의미합니다. 이 단계에서 인자(arguments)를 검증하고, 결정을 로그로 남기며, 되돌릴 수 없는 작업에 대해 게이트(gate)를 설정할 수 있습니다.
import anthropic
client = anthropic.Anthropic() # 환경 변수에서 ANTHROPIC_API_KEY를 읽어옵니다 — 절대 코드에 직접 입력하지 마세요
...
편의를 위한 러너(runner)가 숨겨버리는 두 가지 요소가 여기서 제 역할을 합니다. 바로 MAX_ITERATIONS 제한과 도구 결과가 왕복하기 직전의 로그 포인트(log point)입니다. 이 두 가지는 추가하기는 쉽지만, 에이전트가 운영 환경(production)에서 한 시간 동안 루프를 돌고 난 뒤에 사후 적용하려면 비용이 많이 듭니다.
SDK 도구 러너 (The SDK Tool Runner) — 편의성
모든 호출을 가로챌 필요가 없는 경우 — 예를 들어 위험 부담이 적은 읽기 전용 에이전트나 프로토타입의 경우 — 베타 도구 러너(beta tool runner)가 동일한 루프를 대신 실행해 줍니다. 일반 함수에 @beta_tool 데코레이터를 붙이면, 해당 함수의 독스트링(docstring)이 모델이 보게 될 도구 설명이 됩니다.
from anthropic import beta_tool
@beta_tool
...
트레이드오프(trade-off)는 명확합니다. 러너를 사용하면 코드 줄 수는 줄어들지만, 검증 및 승인 로직이 모델과 실행 사이의 단일 통제 지점(choke point)이 아닌 도구 함수 내부에 존재해야 합니다. 읽기 전용 데모 이상의 단계라면, 수동 루프의 명시적인 체크포인트(checkpoint)가 추가 코드만큼의 가치를 합니다.
중요한 지점에서의 결정론 (Determinism Where It Matters)
루프의 형태(shape) — 즉, 몇 번의 반복(iteration)을 허용할지, 무엇을 완료로 간주할지, 실패한 도구 호출(tool call)을 어떻게 재시도할지 — 는 모델에게 "성공할 때까지 계속 시도하세요"라고 요청하는 시스템 프롬프트(system prompt)가 아니라 Python 코드에 포함되어야 합니다. Building Reliable LLM Applications in Python에서 다룬 바와 같이, 모델은 판단(어떤 도구를, 어떤 인자(argument)로 사용할지, 언제 멈출지)을 위해 사용하고, 코드(루프, 재시도 정책(retry policy), 제한(cap), 감사 로그(audit log))는 장부 기록(bookkeeping)을 위해 사용하십시오. 자연어로 재시도 로직을 스스로 추론하는 에이전트는, 일시적인 오류(transient failure)에 대해 무엇을 해야 할지 이미 알고 있는 except 블록보다 더 느리고, 비용이 많이 들며, 예측 불가능합니다.
단계 간의 구조화된 핸드오프 (Structured Hand-offs Between Steps)
에이전트 단계 사이의 자유 형식 텍스트(free-text) 핸드오프는 오류가 조용히 누적되는 지점입니다. 예를 들어, 2단계에서 생성된 약간 잘못된 필드가 3단계의 도구 호출에서 잘못된 인자가 될 수 있습니다. 한 단계의 출력이 (단순히 사람에게 보여지는 것이 아니라) 다음 단계에서 _사용_되어야 하는 경우, 다시 파싱(re-parse)해야 하는 산문(prose) 형태 대신 검증된 타입이 지정된 객체(validated, typed object)로 받아오십시오:
from pydantic import BaseModel
class PlanStep(BaseModel):
...
검증된 PlanStep은 파싱을 성공하거나 아니면 오류를 발생(raise)시킵니다. 모델이 "완료(done)"를 의미하는지 아니면 "거의 다 끝났습니다(we're basically done)"를 의미하는지 추측하려고 시도하는 정규 표현식(regex)은 필요 없습니다.
안전성과 비용 — 선택이 아닌 필수
에이전트는 읽은 텍스트를 바탕으로 런타임(runtime)에 당신의 함수 중 무엇을 어떤 인자로 호출할지 결정하는 프로그램입니다. 이에 따라 모든 도구를 공격 표면(attack surface)으로 취급하십시오:
- 도구 입력값 검증 및 화이트리스트(Whitelist) 적용.
tool.input(또는 도구 함수의 인자)은 모델이 제공하는 데이터이며, 네트워크로부터 온 요청 본문(request body)과 마찬가지로 신뢰할 수 없는 것으로 취급해야 합니다. 실행 코드에 도달하기 전에 허용된 값을 화이트리스트로 지정하고, 숫자 범위를 제한하며, 도구의 규약(contract)에 맞지 않는 모든 것을 거부하십시오. 모델이 제공한 인자를 쉘 명령어나 SQL 쿼리에 절대 문자열 보간(string-interpolate) 방식으로 삽입하지 마십시오. - 되돌릴 수 없거나 외부로 향하는 작업 전에는 반드시 사람의 승인을 요구하십시오. 파일을 읽거나 API를 조회하는 것은 하나의 위험 단계이지만, 이메일을 보내거나, 레코드를 삭제하거나, 돈을 이체하는 것은 또 다른 단계입니다. 후자의 작업은 명시적인 승인 단계(사람의 확인, 실행 전 미리보기(dry-run preview), 또는 최소한 안전한 작업에 대한 하드코딩된 허용 목록)를 통해 제어하십시오. 되돌릴 수 없는 결과가 발생하기 전의 마지막 확인 절차를 모델의 판단에만 맡기지 마십시오.
- 루프 반복 횟수를 제한하십시오. 모든 에이전틱 루프(agentic loop)에는 엄격한
MAX_ITERATIONS(또는 실제 시간 기준 타임아웃)가 필요합니다. 제한이 없다면, 혼란에 빠진 모델이 무한 루프를 돌며 토큰을 낭비하고, 실패하는 도구 호출을 영원히 재시도할 수 있습니다. - 토큰 비용과 지연 시간(latency)을 일급 메트릭(first-class metrics)으로 추적하십시오. 에이전트의 비용은 단일 호출이 아니라 모든 반복의 합계입니다. 턴(turn)당
response.usage를 측정하고, 통제 불능의 재시도 폭풍(retry storm)에 알람을 설정하는 것과 동일한 방식으로 폭주하는 루프에 대해 알림을 설정하십시오. - API 키를 절대 하드코딩하지 마십시오. 위의 모든 예제는
anthropic.Anthropic()을 통해ANTHROPIC_API_KEY를 읽어옵니다. 소스 코드, 버전 관리 시스템에 커밋된 설정 파일, 또는 로그에 키가 절대 나타나지 않도록 하십시오.
실무적 시사점 (Practical Takeaways)
실무적 시사점 (Practical Takeaways)
- 필요한 만큼만 계단을 오르세요: 단일 호출 → 코드 기반 워크플로우 → 에이전트. 대부분의 작업은 1단계 또는 2단계에서 멈춥니다.
- 루프를 구축하기 전에 네 가지 점검 사항 — 복잡성, 가치, 실현 가능성, 오류 비용 — 을 실행하세요. '아니오'라는 답변 하나하나가 더 단순하게 유지해야 할 이유입니다.
- 수동 루프는 검증, 로깅 및 모든 도구 호출을 게이트(제한)하기 위한 단일 병목 지점을 얻기 위해 장황함을 포기합니다. 반면, SDK 도구 러너는 이러한 제어권을 편리함과 맞바꿉니다 — 기본값으로가 아니라 의도적으로 선택하세요.
- 제어 흐름(루프, 재시도, 반복 횟수 제한)은 Python에 유지하고, 모델에게 판단을 맡기세요.
- 산문(prose)을 파싱하는 대신 단계 간 구조화되고 타입이 지정된 핸드오프를 사용하세요.
- 도구 입력은 신뢰할 수 없는 데이터로 검증하고, 되돌릴 수 없는 작업은 승인 절차 뒤에 게이트를 설정하며, 반복 횟수를 제한하고 비용을 측정하세요 — 이러한 장치 없이 에이전트는 기능(feature)이 아니라 책임(liability)입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기