LLM WikiAccess-protected knowledge portal
← 스터디 홈
20편 · 약 15분

PydanticAI 2.12: deferred tool과 durable execution으로 장기 실행 에이전트를 운영하는 방법

왜 이 업데이트를 지금 봐야 하나

2026년 7월 15일부터 17일까지 나온 PydanticAI v2.10.0v2.12.0 연속 릴리스는 새 모델 하나를 더 붙인 수준의 변화가 아니다. 핵심은 장기 실행 에이전트가 멈추고, 승인 대기하고, 외부 작업을 기다리고, 다시 이어서 실행되는 경계를 런타임 계약으로 드러냈다는 점이다.

이 묶음 릴리스에서 운영자 관점으로 중요한 변화는 네 가지다.

  • deferred tool 흐름이 명시적 이벤트와 데이터 구조로 정리됐다. 승인 대기나 외부 작업 대기를 에이전트 바깥의 ad-hoc 상태 머신으로 빼지 않아도 된다.
  • durable execution 공식 문서가 더 실용적인 기준점을 제공한다. Temporal, DBOS, Prefect, Restate뿐 아니라 Airflow 작업 단위까지 어떻게 이어받는지 설명한다.
  • 메시지 전달 시점이 이벤트로 보이기 시작했다. enqueue()로 밀어 넣은 메시지가 실제 run history에 들어간 순간을 EnqueuedMessagesEvent로 관찰할 수 있다.
  • 재시도·재개 시 history 유효성 보정이 강화됐다. message_history를 provider-valid 형태로 보정하고, background mode / pause_turn 같은 provider별 일시 정지 경로도 더 자연스럽게 다룬다.

즉, 이번 변화는 "PydanticAI가 더 많은 모델을 지원한다"보다 에이전트를 오래 돌릴 때 어디에서 멈추고 무엇을 기록하며 어떤 단위로 재개할지를 더 명확하게 만든 업데이트로 읽는 편이 맞다.


한눈에 보는 구조 변화

릴리스날짜핵심 변화운영 의미
v2.10.02026-07-15provider-valid message_history 보정, OpenAI background mode, Anthropic pause_turn, deferred 결과 resume 관련 수정재시도/재개 시 history 불일치와 provider별 pause/resume 차이를 줄임
v2.11.02026-07-16usage-limit / tool-retry 오류 힌트 강화실패 원인 파악과 운영자 디버깅이 쉬워짐
v2.12.02026-07-17EnqueuedMessagesEvent, deferred tool request/result 이벤트, durable execution용 toolset id 보정장기 실행 run의 상태 전이를 스트림과 capability 체인에서 더 정확히 관찰 가능
PydanticAI 2.12 — Deferred Tool / Queue / Durable Runtime 제어면 / 호출자 호출자 / UI / API agent.run() message_history deferred_tool_results 재개 시 원래 history + 결과를 다시 투입 PydanticAI 런 루프 1) 모델 응답 스트림 tool call / output / reasoning 생성 `message_history` 보정 후 provider-valid 형태 유지 2) deferred tool 판정 승인 필요 또는 외부 실행 필요 → 요청 배치 생성 inline handler가 해결하거나 run을 일시 종료 3) enqueue 전달 이벤트 대기 메시지가 history에 들어오면 `EnqueuedMessagesEvent` 발행 DeferredToolRequests approvals[] / calls[] output_type에 포함 가능 해결 안 되면 run 종료 HandleDeferredToolCalls inline handler 일부 또는 전부 해결 해결된 결과는 일반 tool pipeline으로 복귀 외부 시스템 / durable runtime Temporal / DBOS / Prefect Restate Airflow task durable cache 모델 요청 / tool call / 진행 상태 저장 크래시 후 마지막 완료 지점부터 재개 runtime마다 durability 경계는 다름 사람 / 외부 워커 / 프런트엔드 승인 여부 결정 비동기 작업 실행 결과 수집 후 재개 요청 `DeferredToolResults` 생성 다음 run에 주입 I/O와 상태 저장 승인/외부 결과 대기 inline 해결 replay / resume 주의 deferred tool은 장기 실행 구조를 단순하게 만들지만, 외부 side effect의 idempotency까지 자동으로 보장하지는 않는다. runtime이 무엇을 replay하고 무엇을 activity/tool로 다시 실행하는지 먼저 정해야 한다.
PydanticAI 2.12 — deferred tool과 durable execution 흐름

1. deferred tool은 이제 예외 처리 꼼수가 아니라 런타임 계약이다

PydanticAI 문서가 정의하는 deferred tool은 두 종류다.

  • 승인이 필요한 도구: 사용자가 승인해야만 실행되는 도구
  • 외부에서 실행되는 도구: 같은 Python 프로세스 안에서 즉시 끝낼 수 없고, 워커·프런트엔드·업스트림 시스템이 결과를 나중에 돌려줘야 하는 도구

중요한 변화는 이 둘을 단순한 예외가 아니라 표준화된 요청/결과 구조로 다루기 시작했다는 점이다.

해결 경로는 두 가지다

  1. inline handler 경로

- HandleDeferredToolCalls capability가 요청 배치를 받아 일부 또는 전부를 바로 해결한다. - 해결된 결과는 다시 일반 tool 실행 파이프라인으로 돌아간다. - 같은 run 안에서 계속 이어 가고 싶을 때 적합하다.

  1. stop-the-world 경로

- run이 DeferredToolRequests를 출력으로 내보내고 종료된다. - 호출자나 외부 시스템이 승인/실행 결과를 모은 뒤 DeferredToolResults와 함께 새 run을 시작한다. - 사람이 승인해야 하거나, 몇 분~몇 시간 걸리는 외부 작업을 기다릴 때 더 자연스럽다.

이 구조가 중요한 이유는 명확하다. 예전에는 에이전트가 승인을 기다리거나 외부 작업을 비동기로 맡기는 순간, 애플리케이션이 직접 별도 상태 테이블과 재개 로직을 만들어야 했다. 지금은 도구 호출이 멈추는 시점과 다시 이어지는 시점이 라이브러리 계약 안으로 들어왔다.

운영자가 바로 챙겨야 할 점

문서가 분명히 경고하는 대로, approval은 인증/인가 경계 자체가 아니다. 신뢰할 수 없는 클라이언트가 엔드포인트에 직접 요청할 수 있는 구조라면, "승인했다"는 사실도 결국 클라이언트가 제출한 값일 수 있다. 즉, deferred tool이 있어도 민감한 도구는 서버 측 정책과 감사 경로가 별도로 필요하다.


2. durable execution은 "재시도"가 아니라 "어디까지를 기억할지"에 대한 설계다

PydanticAI는 durable execution을 별도 플랫폼과의 통합으로 푼다. 공식 문서 기준으로 지원하는 축은 다음과 같다.

  • Temporal
  • DBOS
  • Prefect
  • Restate
  • 추가 외부 SDK 통합: Airflow, Kitaru

여기서 중요한 것은 "durable"이라는 말이 모든 runtime에서 같은 뜻이 아니라는 점이다.

Temporal류: workflow와 activity를 분리한다

Temporal 문서는 deterministic workflow와 non-deterministic activity를 구분한다. PydanticAI를 붙이면 모델 요청, MCP 통신, I/O가 있는 tool call은 activity 쪽으로 빠지고, workflow는 재실행 가능하게 유지된다. 장점은 프로세스가 죽어도 workflow 상태를 다시 읽고 같은 지점에서 이어갈 수 있다는 점이다.

Airflow류: durable 단위가 task다

Airflow 문서는 wrapper object가 아니라 Airflow task 자체가 durable unit이라고 설명한다. durable task에서는 완료된 model request와 tool call을 step-level cache에 기록해 두고, retry 시 완료된 단계는 다시 수행하지 않는다.

이건 데이터 플랫폼 운영자에게 익숙한 패턴이다. "태스크는 처음부터 다시 시작되지만, 이미 끝난 외부 I/O는 재실행하지 않도록 캐시와 fingerprint로 건너뛴다"는 뜻이기 때문이다. 결국 핵심은 런타임이 아니라 replay 경계다.

그래서 무엇이 좋아지나

  • 워커 크래시 뒤 같은 모델 요청을 다시 보내 비용을 두 번 내는 일을 줄인다.
  • 승인 대기나 외부 API 대기처럼 오래 걸리는 run을 프로세스 수명과 분리할 수 있다.
  • tool call이 side effect를 만들더라도, 적어도 어느 단계까지 끝났는지는 runtime이 기억하게 할 수 있다.

다만 이건 어디까지나 진행 상황을 기억하는 구조이지, 외부 부작용을 마술처럼 안전하게 만들어 주는 구조는 아니다. 예를 들어 발송 API를 호출하는 tool이 idempotency key 없이 작성돼 있다면, 재시도 시 중복 발송 위험은 여전히 남는다.


3. 2.12에서 특히 눈여겨볼 부분은 "무엇이 언제 history에 들어왔는지"가 보이기 시작했다는 점이다

v2.12.0에서 가장 실무적인 변화는 새 모델 추가보다 이벤트 가시성이다.

EnqueuedMessagesEvent

RunContext.enqueue()로 미리 넣어 둔 메시지가 실제 run의 message history에 들어가는 순간, 스트림에 EnqueuedMessagesEvent가 나온다. 이 이벤트는 단순히 "queue가 있다"를 알리는 수준이 아니라, 실제로 history에 찍힌 메시지 객체를 그대로 담는다. 문서와 코드 주석 기준으로 이 시점에는 timestamp, run_id, conversation_id 같은 메타데이터가 이미 stamp된 상태다.

운영적으로 이건 꽤 크다.

  • 프런트엔드가 "메시지를 보냈다"가 아니라 "런타임이 실제로 그 메시지를 받았다"를 기준으로 UI를 업데이트할 수 있다.
  • 감사 로그를 남길 때 enqueue 호출 시점과 history 반영 시점을 구분할 수 있다.
  • durable runtime 위에서 재개할 때, 어떤 외부 메시지가 이미 반영됐는지 더 명확하게 추적할 수 있다.

deferred tool 이벤트

v2.12.0 릴리스 노트는 deferred tool 관련 이벤트가 AgentStreamEvent에 추가됐다고 설명한다. 실제 v2.12.0 태그 코드를 보면 노출 단위는 DeferredToolRequestsEventDeferredToolResultsEvent다.

이 차이는 사소하지 않다.

  • 릴리스 노트 표현만 보면 각 개별 call/result가 바로 이벤트 타입으로 보이는 것처럼 읽힌다.
  • 실제 태그 코드는 배치 단위 요청/결과를 담는 event를 스트림에 태운다.

스트림 소비자나 UI 어댑터를 직접 구현하는 팀이라면, 릴리스 노트보다 태그 코드 기준으로 타입 이름과 페이로드 모양을 맞추는 편이 안전하다.


4. 재개 가능한 에이전트에서 message_history가 더 중요해졌다

v2.10.0message_history 보정은 겉으로는 작은 변경처럼 보인다. 하지만 장기 실행 에이전트에서는 오히려 이게 바닥 공사에 가깝다.

에이전트가 도구 호출을 포함한 긴 대화를 이어 가다 보면, provider마다 history 형식 제약이 다르다. tool call과 tool result의 pairing이 조금만 어긋나도 재개 시점에 provider가 history를 거부하거나, reasoning 흐름이 틀어질 수 있다. v2.10.0은 이 history를 provider-valid 형태로 보정해 재시도와 재개가 provider 차이에 덜 민감하도록 만든다.

같은 릴리스의 다른 변화도 같은 방향이다.

  • OpenAI background mode 지원: 즉시 응답이 끝나지 않는 모델 상호작용을 더 자연스럽게 다룬다.
  • Anthropic pause_turn 처리: provider가 일시 정지된 턴을 반환할 때 run을 이어 붙이기 쉬워진다.
  • deferred 결과 resume 수정: deferred call과 output tool call이 같은 배치에 있을 때 재개 경계가 더 안정적이다.

한마디로 정리하면, 2.10은 "멈춘 뒤 다시 이어 붙이는 기본기"를 다듬은 릴리스다. 2.12는 그 위에 queue와 deferred state를 관찰하는 표면을 더한 셈이다.


5. 이벤트 스트림을 붙일 때도 durable runtime의 경계를 이해해야 한다

ProcessEventStream capability 문서를 보면 하나의 중요한 제한이 적혀 있다. 현재 durable execution 통합(Temporal, DBOS, Prefect)에서는 모델 스트리밍이 outer agent loop가 아니라 activity/step 안에서 일어난다. 그래서 capability의 wrap_run_event_stream 훅은 tool-call 이벤트와 post-streaming batch는 보지만, 개별 model-response 이벤트를 실시간으로 다 보지는 못한다.

이건 운영자에게 두 가지 의미가 있다.

  1. 관측 파이프라인 위치를 잘못 잡으면 기대한 세밀한 토큰/델타 이벤트가 안 보일 수 있다.
  2. 실시간 UI와 durable replay 로그를 같은 스트림 처리기로 해결하려 하면 경계가 어긋날 수 있다.

문서는 다행히 예외도 설명한다. top-level event_stream_handler는 여전히 live event를 본다. 따라서 다음처럼 분리하는 편이 현실적이다.

  • 실시간 UI / tracing: top-level event handler
  • durable wrapper / workflow 주변 후처리: capability 기반 event stream processor

장기 실행 에이전트를 붙이는 팀이 흔히 저지르는 실수는, "스트림이 하나니까 관측도 하나로 끝나겠지"라고 가정하는 것이다. PydanticAI의 현재 구조는 그렇게 단순하지 않다.


6. 이 업데이트를 어디에 쓰면 좋은가

이번 변화는 특히 다음과 같은 워크로드에서 효과가 크다.

사람 승인 경로가 있는 내부 업무 에이전트

예를 들어 파일 삭제, 결재 생성, 고객 메일 발송 같은 도구는 승인 없이는 실행하면 안 된다. deferred tool 구조를 쓰면 모델은 도구 호출 자체를 제안하고, 실제 실행 권한은 바깥 시스템이 쥔다.

오래 걸리는 외부 작업을 기다리는 연구/배치 에이전트

크롤링, 배치 리포트 생성, 대용량 분석 작업처럼 몇 분 이상 걸리는 작업은 같은 Python run 안에서 붙들고 있을 이유가 없다. DeferredToolRequests로 끊고, 외부 워커가 결과를 만든 뒤 DeferredToolResults로 다시 이어 붙이는 편이 낫다.

Airflow·Temporal 같은 이미 있는 오케스트레이션 위에 에이전트를 얹는 팀

이 경우 PydanticAI는 별도 런타임을 새로 도입하기보다, 기존 orchestrator의 durable semantics를 agent loop에 연결하는 얇은 레이어로 쓰는 편이 맞다. 특히 Airflow처럼 이미 retry·task logging·storage policy가 자리 잡은 환경이라면 더 그렇다.


운영자 관점 체크리스트

  • [ ] 승인 대기나 외부 실행이 필요한 도구를 일반 tool call로 억지 구현하지 말고 deferred flow로 분리한다.
  • [ ] stop-the-world 경로를 쓸 때는 DeferredToolRequestsoutput_type에 포함돼 있는지 확인한다.
  • [ ] 외부 side effect가 있는 tool에는 idempotency key 또는 완료 여부 확인 절차를 넣는다.
  • [ ] message_history를 외부 저장소에 보관할 경우, provider-valid pairing이 깨지는지 회귀 테스트를 둔다.
  • [ ] 실시간 UI와 durable wrapper 로그를 같은 스트림 소비자로 합치지 말고, top-level handler와 capability 처리기를 분리한다.
  • [ ] Airflow를 쓴다면 durable cache 저장 위치와 보존 정책을 먼저 정한다.
  • [ ] 승인 기능을 보안 경계로 오해하지 말고, 서버 측 정책과 감사 로그를 별도로 둔다.
  • [ ] 릴리스 노트의 이벤트 이름만 믿지 말고, 실제 사용하는 태그 코드와 문서를 같이 확인한다.

어디까지 기대하고, 어디부터는 아직 직접 검증해야 하나

PydanticAI 2.12는 장기 실행 에이전트를 위한 표면을 꽤 잘 정리했다. 하지만 아직도 직접 검증이 필요한 경계가 분명하다.

  • deferred tool이 있다고 해서 외부 부작용의 정확한 한 번 실행이 자동으로 보장되지는 않는다.
  • durable runtime마다 replay 단위가 다르다. workflow 재실행과 task retry는 같은 현상이 아니다.
  • provider별 pause/resume 경로가 정리되고는 있지만, 실제 모델별 reasoning 품질과 tool-call 안정성은 여전히 별도 검증 대상이다.
  • 스트림 이벤트 명세는 릴리스 노트와 태그 코드가 다르게 읽히는 부분이 있어, SDK wrapper를 직접 짜는 팀은 타입 계약을 직접 확인해야 한다.

내 결론은 단순하다. 이번 업데이트는 에이전트를 더 똑똑하게 만든 릴리스가 아니라, 더 오래 버티고 더 안전하게 멈추게 만든 릴리스다. 프로덕션에서 필요한 건 대개 이쪽이다.

Open question

  • v2.12.0 릴리스 노트의 deferred-tool 이벤트 명칭과 태그 코드의 실제 클래스 명칭 차이가 문서 정정 대상인지 여부는 추가 확인이 필요하다.
  • Restate 통합의 운영 예제는 overview 문서에는 보이지만, 다른 통합만큼 상세한 현장 예시는 더 찾아볼 필요가 있다.

References

  • https://github.com/pydantic/pydantic-ai/releases/tag/v2.10.0
  • https://github.com/pydantic/pydantic-ai/releases/tag/v2.11.0
  • https://github.com/pydantic/pydantic-ai/releases/tag/v2.12.0
  • https://pydantic.dev/docs/ai/tools-toolsets/deferred-tools/
  • https://pydantic.dev/docs/ai/integrations/durable_execution/overview/
  • https://pydantic.dev/docs/ai/integrations/durable_execution/airflow/
  • https://github.com/pydantic/pydantic-ai/blob/v2.12.0/pydantic_ai_slim/pydantic_ai/messages.py
  • https://github.com/pydantic/pydantic-ai/blob/v2.12.0/pydantic_ai_slim/pydantic_ai/capabilities/process_event_stream.py