DBOS와 함께하는 지속 실행

DBOS와 함께하는 지속 실행 (Durable Execution with DBOS)

DBOS는 Pydantic AI에 네이티브로 통합된 가벼운 지속 실행 라이브러리예요. DBOS가 어떻게 프로그램을 지속으로 만들고, DBOSDurability 캐퍼빌리티로 에이전트에 붙이는지 정리할게요.

출처: 문서

본문

지속 실행 (Durable Execution)

DBOS 워크플로는 프로그램의 상태를 데이터베이스에 체크포인트해서 프로그램을 지속으로 만들어요. 프로그램이 실패하면, 재시작할 때 모든 워크플로가 마지막 완료 스텝부터 자동으로 재개됩니다.

  • 워크플로 (Workflows) 는 결정적이어야 하며 일반적으로 I/O를 포함할 수 없어요.
  • 스텝 (Steps) 은 I/O(네트워크, 디스크, API 호출)를 수행할 수 있어요. 실패하면 처음부터 다시 시작합니다.

모든 워크플로 입력과 스텝 출력은 시스템 데이터베이스에 지속 저장됩니다. 워크플로 실행이 크래시, 네트워크 문제, 서버 재시작으로 실패하면, DBOS는 이 체크포인트들을 이용해 마지막 완료 스텝부터 워크플로를 복구합니다.

DBOS 큐(queues) 는 Celery나 BullMQ 같은 시스템에 대한 지속적이고 데이터베이스 기반 대안을 제공하며, 동시성 제한, 비율 제한, 타임아웃, 우선순위 같은 기능을 지원해요. 자세한 내용은 DBOS 문서를 보세요.

아래 다이어그램은 DBOS에서 에이전트 애플리케이션의 전체 아키텍처를 보여줍니다. DBOS는 라이브러리로 완전히 인프로세스로 실행됩니다. 함수는 일반 파이썬 함수로 남지만 데이터베이스(Postgres 또는 SQLite)에 체크포인트됩니다.

                    Clients
            (HTTP, RPC, Kafka, etc.)
                        |
                        v
+------------------------------------------------------+
|               Application Servers                    |
|                                                      |
|   +----------------------------------------------+   |
|   |        Pydantic AI + DBOS Libraries          |   |
|   |                                              |   |
|   |  [ Workflows (Agent Run Loop) ]              |   |
|   |  [ Steps (Tool, MCP, Model) ]                |   |
|   |  [ Queues ]   [ Cron Jobs ]   [ Messaging ]  |   |
|   +----------------------------------------------+   |
|                                                      |
+------------------------------------------------------+
                        |
                        v
+------------------------------------------------------+
|                      Database                        |
|   (Stores workflow and step state, schedules tasks)  |
+------------------------------------------------------+

자세한 내용은 DBOS 문서를 보세요.

지속 에이전트 (Durable Agent)

DBOSDurability 캐퍼빌리티를 붙여 어떤 Agent든 지속 실행을 추가할 수 있어요. 에이전트가 DBOS 워크플로 안에서 실행되면, 이 캐퍼빌리티는 모델 요청MCP 통신을 DBOS 스텝으로 라우팅합니다. 런을 지속으로 만들려면 @DBOS.workflow 안에서 agent.run()을 호출하세요.

에이전트는 어디서나 일반 Agent로 유지됩니다 — DBOS 워크플로 밖에서는 캐퍼빌리티가 투명하고, 원래 에이전트·모델·MCP 서버를 평소처럼 쓸 수 있어요.

에이전트에 직접 또는 다른 캐퍼빌리티를 통해 등록된 커스텀 도구 함수와 이벤트 스트림 핸들러는 DBOS가 자동으로 감싸지 않아요. DBOSDurability에 전달된 event_stream_handler=는 DBOS 스텝 안에서 실행되며 실시간 스트리밍 이벤트를 받습니다. 비결정적 동작을 포함하거나 I/O를 수행한다면 @DBOS.step으로 명시적으로 장식해야 해요.

에이전트에 지속 실행을 붙이는 간단하지만 완전한 예제입니다. DBOS 오픈소스 라이브러리와 함께 Pydantic AI를 설치하기만 하면 됩니다:

pip install pydantic-ai[dbos]
uv add pydantic-ai[dbos]

slim 패키지를 쓰고 있다면 dbos 옵션 그룹으로 설치할 수 있어요:

pip install pydantic-ai-slim[dbos]
uv add pydantic-ai-slim[dbos]

그 후 다음 예제 코드를 실행하세요:

from dbos import DBOS, DBOSConfig

from pydantic_ai import Agent
from pydantic_ai.durable_exec.dbos import DBOSDurability

dbos_config: DBOSConfig = {
    'name': 'pydantic_dbos_agent',
    'system_database_url': 'sqlite:///dbostest.sqlite',  # (1)
}
DBOS(config=dbos_config)

agent = Agent(
    'openai:gpt-5.6-sol',
    instructions="You're an expert in geography.",
    name='geography',  # (2)
    capabilities=[DBOSDurability()],  # (3)
)


@DBOS.workflow()  # (4)
async def answer(question: str) -> str:
    result = await agent.run(question)
    return result.output


async def main():
    DBOS.launch()
    answer_text = await answer('What is the capital of Mexico?')
    print(answer_text)
    #> Mexico City (Ciudad de México, CDMX)
  • (1) 이 예제는 SQLite를 사용합니다. 프로덕션에는 Postgres가 권장됩니다.
  • (2) 에이전트의 name은 그 워크플로를 고유하게 식별하는 데 쓰입니다.
  • (3) capabilities=[...]로 지속성을 붙입니다. 에이전트가 워크플로 안에서 실행될 때 이 캐퍼빌리티가 모델 요청과 MCP 통신을 DBOS 스텝으로 라우팅합니다. DBOS 워크플로는 DBOS.launch() 전에 등록되어야 하므로, 에이전트도 DBOS.launch()를 호출하기 전에 생성해야 해요.
  • (4) agent.run()을 자신의 @DBOS.workflow로 감싸 런을 지속으로 만듭니다.

(이 예제를 실행하려면 asyncio를 임포트하고 asyncio.run(main())을 추가하세요; 다른 변경은 필요 없어요.)

같은 에이전트가 DBOS 워크플로 안팎에서 동작하므로, DBOSDurability는 각각 DBOS 특정 래퍼 변형 없이도 다른 모든 캐퍼빌리티와 조립됩니다.

파이썬 애플리케이션에서 DBOS를 쓰는 법은 Python SDK guide를 보세요.

래퍼 에이전트 경로 (Wrapper-agent path, 더 이상 사용되지 않음)

더 이상 사용되지 않음 (Deprecated)

DBOSAgent는 DBOS 통합의 원래 래퍼 에이전트 경로이고 v3에서 제거됩니다. 새 코드는 위의 DBOSDurability 캐퍼빌리티를 사용하세요.

마이그레이션할 때는 런을 스스로 워크플로로 감싸야 합니다. DBOSAgentrun/run_sync를 DBOS 워크플로로 자동으로 감쌌지만, DBOSDurability는 의도적으로 그러지 않아요 — 런은 agent.run()을 자신의 @DBOS.workflow 안에서 호출할 때만 지속됩니다. 생성자 인수를 옮겨 놓고 agent.run()을 직접 호출하면 동작하지만 지속되지 않는 런이 만들어져요.

마이그레이션 중 진행 중인 래퍼 시대 워크플로를 복구하려면 DBOSDurability에서 register_legacy_workflows=True를 켜고 배포 전반에 DBOS application_version을 고정하세요 (버전 게이팅은 어떤 DBOS 코드 변경에도 적용됩니다). 그 워크플로들이 빠진 후 플래그를 내리세요.

어떤 에이전트든 DBOSAgent로 감싸서 모델 요청과 MCP 통신을 DBOS 스텝으로 라우팅하는 지속 에이전트 변형을 얻을 수 있습니다:

from pydantic_ai import Agent
from pydantic_ai.durable_exec.dbos import DBOSAgent

agent = Agent('openai:gpt-5.6-sol', name='geography')
dbos_agent = DBOSAgent(agent)  # Use `dbos_agent` in place of `agent`.

캐퍼빌리티로 마이그레이션한다는 것은 DBOSDurability를 붙이고 DBOSAgent가 예전에 대신 적용해 주던 워크플로 데코레이터를 추가하는 것입니다:

-dbos_agent = DBOSAgent(agent)
-result = await dbos_agent.run(prompt)
+agent = Agent(..., capabilities=[DBOSDurability()])
+
[email protected]()
+async def answer(prompt: str) -> str:
+    result = await agent.run(prompt)
+    return result.output

DBOS 통합 고려사항 (DBOS Integration Considerations)

DBOS를 Pydantic AI 에이전트와 함께 쓸 때 워크플로와 툴셋이 올바르게 동작하도록 몇 가지 중요 고려사항이 있어요.

에이전트와 툴셋 요구사항

각 에이전트 인스턴스는 실패나 재시작 후 DBOS가 워크플로를 올바르게 재개할 수 있도록 고유한 name을 가져야 해요.

MCPToolset은 고유한 id를 가져야 해요. DBOS가 그것에서 스텝 이름과 런별 도구 정의 캐시 키를 유도하기 때문입니다. 이 필드는 보통 선택이지만 DBOS를 쓸 때는 필수예요. 지속 에이전트가 프로덕션에 배포된 후에는 바꾸면 안 됩니다. 활성 워크플로를 깨뜨릴 수 있으니까요.

MCP 도구는 I/O를 수행하고 항상 그 DBOS 스텝에서 실행됩니다. 도구의 metadata={'dbos': False}를 설정하면 워크플로 코드에서 MCP 호출을 인라인 실행하는 대신 거부됩니다.

MCP 서버는 스텝당이 아니라 워크플로당 한 번 연결됩니다: 그것이 필요한 첫 스텝이 연결합니다 — 스텝 안에서, 그래서 DBOS는 실패한 연결을 다른 스텝 실패처럼 재시도합니다 — 그리고 워크플로는 런이 끝날 때까지 세션을 보유합니다. 도구 발견은 여전히 get_tools 스텝을 실행하며, 그것이 실행될지는 따뜻한 프로세스가 우연히 가진 것이 아니라 런 자체의 기록된 히스토리가 결정하므로, 회복 시 스텝 순서가 같게 유지됩니다. 서버 자신의 cache_tools가 그 스텝들에 런이 이미 보유한 세션으로 답하고, 추가 왕복이 없어요. 한 프로세스의 동시 런은 워크플로 밖처럼 서버 세션을 공유하며, 마지막 런이 끝나면 닫힙니다.

DynamicToolsetDynamicCapability이 기여한 것 포함 — 도 감싸지며 안정적인 id가 필요합니다. 도구 발견, 인수 검증, 호출은 {name}__dynamic_toolset__{id}.get_tools, {name}__dynamic_toolset__{id}.validate_args, {name}__dynamic_toolset__{id}.call_tool 스텝으로 실행됩니다. 도구의 args_validatorcall_tool처럼 툴셋을 재해석하는 validate_args 스텝에서 실행되고, 없는 도구는 검증 스텝을 예약하지 않아요. 검증은 승인과 지연 전에 실행되므로 거부된 인수는 승인자에게 닿지 않습니다. 동적 툴셋의 모든 I/O — MCP 통신 포함 — 가 그 스텝들 안에서 일어나므로 체크포인트됩니다. 팩토리가 per_run_step=False로 만들어지면, 런은 툴셋을 한 번 해석하고 각 스텝이 재사용하며, 첫 스텝이 필요할 때 입력하고 런의 나머지 동안 입력된 채로 둡니다. 툴셋을 만드는 것은 연결하는 것과 같지 않아요 — MCPToolset은 입력될 때까지 아무것도 열지 않습니다 — 그래서 연결은 스텝 안에서 일어나고 다른 스텝 실패처럼 재시도됩니다. 팩토리 자체는 워크플로 코드에서 실행되고 그것이 재생될 때마다 다시 실행되므로, 아래 캐퍼빌리티 팩토리처럼 런의 deps에 대해 결정적이어야 합니다: 팩토리에서 툴셋을 만들고 I/O는 스텝에 맡기세요. 이것은 비-지속 런이 툴셋에 주는 라이프사이클이므로, 팩토리가 반환하는 MCPToolsetcache_tools 같은 툴셋 자신의 캐싱은 스텝 사이에 버려지는 대신 워크플로 밖에서처럼 동작합니다. per_run_step=True 팩토리는 요청한 대로 각 스텝 안에서 재해석됩니다.

캐퍼빌리티가 기여한 툴셋 — tools=가 있는 Capability 또는 로컬 실행 MCP 서버 — 은 캐퍼빌리티 자신의 id에서 id를 유도하므로 Capability(id='...', tools=[...]) 또는 MCP(id='...', url='...')을 설정하세요. MCP는 우선순위 순서로 id를 해석합니다: 명시적 id=, 그다음 native=MCPServerTool(...) id, 그다음 서버 URL의 호스트와 경로에서 유도된 슬러그. 이 중 어느 것도 없는 베어 비-URL 로컬 클라이언트(예: MCP(local=Path(...)))는 id 없이 남아 여기서 쓰려면 명시적 id를 받아야 해요.

에이전트에 직접 또는 다른 캐퍼빌리티를 통해 등록된 함수 도구와 이벤트 스트림 핸들러는 DBOS가 자동으로 감싸지 않아요. DBOSDurability에 전달된 event_stream_handler=는 DBOS 스텝 안에서 실행되며 실시간 스트리밍 이벤트를 받습니다. 직접 등록된 도구와 핸들러는 통합 방식을 결정할 수 있어요:

  • 함수에 비결정성이나 I/O가 있으면 @DBOS.step으로 장식.
  • 지속성이 필요 없으면 데코레이터를 건너뛰어 추가 DB 체크포인트 쓰기를 피함.
  • 함수가 작업을 큐에 넣거나 다른 DBOS 워크플로를 호출해야 하면, (스텝이 아닌) 에이전트의 메인 워크플로 안에서 실행.

다른 모든 에이전트와 툴셋은 지원됩니다.

에이전트 런 컨텍스트와 의존성 (Agent Run Context and Dependencies)

기본적으로 DBOS는 워크플로 입출력과 스텝 출력을 pickle로 데이터베이스에 체크포인트합니다. 하지만 DBOS 설정을 통해 커스텀 직렬화기를 선택적으로 공급할 수 있어요. 즉 Agent.run() / Agent.run_sync()에 제공하는 의존성 객체와 도구 출력이 직렬화 가능하도록 해야 합니다. 입력과 출력을 작게(~2MB 미만) 유지하고 싶을 수도 있어요. PostgreSQL과 SQLite는 필드당 최대 1GB를 지원하지만, 큰 객체는 성능에 영향을 줄 수 있어요.

런타임 모델 선택 (Model Selection at Runtime)

Agent.run(model=...)은 모델 문자열('openai:gpt-5.6-sol' 같은)과 모델 인스턴스를 모두 지원해요. 모델 인스턴스는 스텝 경계를 넘어 직렬화할 수 없고, model_id 문자열에서 재구성하면 다른 모델을 만들 것입니다 — 워커 환경이 암시하는 어떤 공급자의 같은 모델 이름이므로, 요청이 다른 자격 증명으로 다른 엔드포인트에 갈 거예요. 그래서 미리 등록되지 않은 인스턴스는 UserError로 거부됩니다. 특정 인스턴스를 쓰는 방법은 두 가지예요: DBOSDurabilitymodels dict를 전달해 사전 등록하고 키로 참조(또는 등록된 인스턴스 전달)하거나, 모델 이름 문자열을 전달하고 ResolveModelId 캐퍼빌리티로 스텝 안에서 인스턴스를 만드세요 — 모델이 런의 deps에 의존할 때(예: 사용자별 자격 증명) 올바른 선택입니다. 모델 이름 문자열은 등록이 절대 필요 없어요. 생성 시 설정된 에이전트 자신의 모델은 항상 기본값으로 사용 가능합니다.

모델 문자열이 만들어지는 방식을 커스터마이즈 — 커스텀 공급자, 또는 런의 deps에 실린 사용자별 자격 증명 — 하려면 DBOSDurability 앞에 ResolveModelId 캐퍼빌리티를 추가하세요. 그것이 모든 문자열에 첫 기회를 얻고, 해석기는 런의 실제 deps로 스텝 안에서 다시 실행되므로 주어진 (model_id, deps)에 대해 결정적이어야 하며 외부 I/O를 수행하면 안 됩니다.

런타임 캐퍼빌리티 (Capabilities at Runtime)

캐퍼빌리티는 에이전트가 생성될 때 붙여서, DBOSDurability.for_agent()가 애플리케이션 시작 전에 그 스텝을 등록할 수 있게 하세요. 워크플로 안에서 agent.run(capabilities=[...])을 전달하면 UserError가 납니다. 그렇게 늦게 추가된 캐퍼빌리티는 기여하는 툴셋이나 자기 @durable_operation 메서드에 등록된 스텝이 없기 때문입니다.

런만 관찰하는 캐퍼빌리티는 런별로 붙여도 안전해요. 그 훅은 런 상태를 읽지만 도구·툴셋·지속 연산을 기여하지 않으니까요. Instrumentation이 내장 예제이며 제한에서 면제됩니다. 현재 제한은 더 보수적인데, 서드파티 캐퍼빌리티가 아직 런만 관찰한다고 선언할 수 없기 때문이에요. 캐퍼빌리티가 오버라이드하는 훅에서 이를 유도하는 것은 #5477에서 추적 중입니다. 워크플로 안에서 런별 캐퍼빌리티가 필요하면 그곳에 사용 사례를 공유하세요. 워크플로 밖에서는 지속 캐퍼빌리티가 투명하므로 런별 캐퍼빌리티는 거기서 문제없어요.

스트리밍 (Streaming)

Agent.run_stream()Agent.run_stream_events()는 DBOS 워크플로 안에서 동작하지만, 그 이벤트는 실시간으로 전달되지 않고 버퍼링됩니다. 모델 스트림은 지속 스텝 안에서 실행되고, 그 이벤트는 스텝이 완료된 후 워크플로에 재생됩니다.

I/O 사이드 이펙트가 있는 핸들러는 event_stream_handler=DBOSDurability에 전달하세요. 모델 이벤트는 각 모델 요청 스텝 안에서 실시간 전달되고, 각 도구 이벤트는 자체 이벤트 핸들러 스텝에서 전달됩니다. 다른 DBOS 스텝처럼, 워크플로가 그 스텝이 체크포인트되기 전에 회복되면 핸들러가 두 번 이상 실행될 수 있으니 사이드 이펙트를 멱등으로 유지하세요.

대안으로 ProcessEventStream을 등록하세요. 그 핸들러는 워크플로 코드에서 실행되고, 워크플로 재생 때 다시 실행되므로 결정적이어야 해요. 도구와 최종 출력 이벤트는 실시간으로 도착하고, 실제 캡처된 모델 이벤트는 각 모델 요청이 완료된 후 재생됩니다. 예제는 streaming docs를 보세요.

지속성 event_stream_handler=와 별도로 등록된 ProcessEventStream은 두 개의 별개 핸들러이며 각각 한 번 발동합니다. 지속 핸들러는 지속 스텝 안에서 실시간 이벤트를 받고, ProcessEventStream은 워크플로 코드의 버퍼링된 재생을 봅니다.

Agent.run(event_stream_handler=...)에 전달된 런별 핸들러도 재생된 모델 이벤트에 대해 워크플로 측에서 실행됩니다.

DBOS 스텝 안에서 — MCP·동적 툴셋 도구, event_stream_handler, 또는 @DBOS.step으로 장식한 도구 — ctx.emit()으로 발행된 이벤트는 스텝이 실제로 실행될 때 전달되며, 회복 시 기록된 결과가 재생될 때 재발행되지 않아요. 스텝 안에 쓴 로그 줄처럼, 발행된 이벤트는 기록된 결과의 일부가 아니라 스텝을 실행하는 사이드 이펙트입니다. 일반 함수 도구와 캐퍼빌리티 훅은 워크플로 코드에서 실행되어, emit이 워크플로의 나머지와 함께 다시 실행됩니다. @on_event로 등록된 캐퍼빌리티 리스너도 워크플로 코드에서 실행되므로 회복 시 다시 실행되고 결정적이어야 해요. I/O는 자신의 스텝에서 실행되는 지속성 event_stream_handler=에 두세요. ctx.enqueue()는 스텝 안에서 버리는 것이 모델이 보는 것을 바꾸므로 거부되지만, 놓친 이벤트는 관찰자가 통지받지 않았다는 뜻일 뿐이에요. 지속 단위의 발행된 이벤트를 기록된 출력에 실어 재생이 재현하게 하는 것은 pydantic-ai#7971에서 추적합니다.

모델 스트림이 스텝 안에서 소비되므로, 워크플로 측에서 취소하는 것(AgentStream.cancel() 같은)은 지속 경계를 넘어 사용할 수 없어요.

CancellationToken은 DBOS 지속 런에 전달할 수 없고, RunContext.cancel()은 기록된 결과가 회복 시 재실행 없이 재생될 스텝-감싸진 단위(동적·MCP 도구, 또는 event_stream_handler) 안에서 명확한 UserError를 냅니다. 일반 함수 도구는 DBOS 아래 워크플로 수준에서 실행되어 cancel()이 동작하고 재생 일관적이에요. 밖에서 런을 멈추려면 DBOS 워크플로를 취소하세요.

Agent.run_stream_sync()는 워크플로 코드용이 아니에요. 실행 중인 이벤트 루프가 필요 없고 run_stream()을 감쌉니다. DBOSDurability 아래에서는 위 버퍼링 async 스트리밍 API나 이벤트 스트림 핸들러가 있는 Agent.run()을 쓰세요. 워크플로 밖에서는 DBOSDurability가 있는 에이전트가 일반 에이전트처럼 동작하므로 run_stream_sync()이 평소처럼 동작합니다. (래퍼 DBOSAgent는 워크플로 안에서 run_stream을 금지합니다 — 거기서는 run + 이벤트 스트림 핸들러를 쓰세요.)

일시 정지 턴과 백그라운드 모드 (Suspended Turns and Background Mode)

공급자가 모델 턴을 도중에 일시 정지하거나(Anthropic pause_turn) 준비될 때까지 폴링되는 서버 측 작업으로 실행하면(OpenAI background mode), 각 세그먼트는 별도의 모델 요청 스텝에서 실행됩니다. 일시 정지된 ModelResponse와 백그라운드 작업 ID는 세그먼트 사이에 체크포인트되고, 최종 응답은 병합되며 사용량은 한 번 기록됩니다. 일시 정지된 응답으로 끝나는 message_history가 첫 스텝에 전달됩니다. 스텝 타임아웃은 공급자 왕복 한 번으로 잡으세요. 오류가 일시 정지된 작업을 버리면, 그 공급자 정리는 전용 취소 스텝에서 실행됩니다.

병렬 도구 실행 (Parallel Tool Execution)

DBOS 아래에서 도구는 지연을 최소화하기 위해 기본적으로 병렬로 실행됩니다. 결정적 재생과 안정적 복구를 보장하기 위해, DBOS는 모든 병렬 도구 호출이 완료될 때까지 기다렸다가 이벤트를 순서대로 발행합니다. with agent.parallel_tool_call_execution_mode('parallel_ordered_events')의 동작과 동등합니다.

엄격한 순서를 선호한다면, DBOSDurability에서 parallel_execution_mode='sequential'을 설정해 에이전트가 도구를 순차 실행하게 할 수 있어요.

런타임 툴셋 (Toolsets at Runtime)

지속 감싸기가 필요한 실행 중인 모든 툴셋을 에이전트 생성자에 전달해서 워크플로 실행 전에 스텝이 등록되게 하세요. DynamicToolset 포함: 명시적 id를 주고 Agent(toolsets=[...])로 전달합니다. @agent.toolset 데코레이터는 엔진의 지속 단위가 만들어진 후 등록하므로, DBOSDurability 아래 워크플로 안에서 쓰면 UserError가 납니다. 더 이상 사용되지 않는 DBOSAgent는 이 검사를 실행하지 않아요: 워크플로 안에서 감싸는 시점에 동결된 스텝-감싸진 툴셋 목록을 실행하므로, 그렇게 늦게 등록된 툴셋은 조용히 런에서 빠집니다.

추가 툴셋은 agent.run(toolsets=...)으로 런별 전달할 수 있어요. ExternalToolset 같은 비-실행 툴셋과, 도구를 DBOS가 인라인 실행하는 FunctionToolset은 지원됩니다. 런타임에 전달된 MCPToolset과 동적 툴셋은 UserError를 냅니다.

워크플로 안에서 agent.override(toolsets=...)로 바꿔 넣은 툴셋도 같은 규칙에 묶입니다. 그것들도 에이전트의 스텝이 등록된 후 도착하기 때문이에요. 런타임에 추가된 툴셋은 에이전트를 생성할 때 만든 것의 id를 재사용할 수도 없는데, id가 도구 호출이 어떤 등록된 툴셋의 스텝으로 디스패치되는지 식별하기 때문입니다.

스텝 설정 (Step Configuration)

StepConfig 객체를 DBOSDurability 생성자에 전달해 재시도 같은 DBOS 스텝 동작을 커스터마이즈할 수 있어요:

  • mcp_step_config: MCP 서버 통신에 쓸 DBOS 스텝 설정. 생략하면 재시도 없음.
  • model_step_config: 모델 요청 스텝에 쓸 DBOS 스텝 설정. 생략하면 재시도 없음. 모델 요청 스텝은 지속 실행 재시도 계층을 싣습니다 — StepConfig 재시도가 SDK 클라이언트와 트랜스포트의 재시도와 어떻게 쌓이는지는 Retry multiplication을 보세요.
  • event_stream_handler_step_config: 이벤트 스트림 핸들러 스텝에 쓸 DBOS 스텝 설정 (DBOSDurability만). 생략하면 재시도 없음.

TemporalPrefect 통합과 달리, DBOS는 툴별 설정을 받지 않아요. 도구 메타데이터('dbos' 키든 아니든)는 무시되고, 개별 도구를 스텝 감싸기에서 제외할 방법이 없습니다.

커스텀 도구는 필요에 따라 @DBOS.step 또는 @DBOS.workflow 데코레이터로 직접 주석할 수 있어요. 이 데코레이터들은 DBOS 워크플로 밖에서 효과가 없으므로 도구는 비-DBOS 에이전트에서 계속 쓸 수 있습니다.

스텝 재시도 (Step Retries)

DBOS가 수행하는 요청 실패 자동 재시도 위에, Pydantic AI와 다양한 공급자 API 클라이언트도 자체 요청 재시도 로직이 있어요. 이들을 동시에 켜면 요청이 예상보다 자주 재시도될 수 있고, Retry-After 처리가 부적절해질 수 있어요.

DBOS를 쓸 때는 트랜스포트 재시도를 쓰지 말고 공급자 API 클라이언트의 자체 재시도 로직을 끄는 것을 권장합니다. 예를 들어 커스텀 OpenAIProvider API 클라이언트max_retries=0을 설정하세요.

DBOS의 재시도 정책은 스텝 설정으로 커스터마이즈할 수 있어요.

DBOS는 선택적 비-재시도 가능 예외 지원이 없으므로, 스텝 재시도(retries_allowed)를 켜면 UserError 같은 프레임워크 설정 오류도 다른 것들과 함께 재시도됩니다. Temporal과 Prefect 통합은 그것들을 비-재시도로 표시하지만, DBOS에서는 설정이 잘못된 에이전트가 실패하기 전에 전체 재시도 예산을 태울 것으로 예상하세요.

Logfire로 관측성 (Observability with Logfire)

DBOS는 각 워크플로·스텝 실행에 OpenTelemetry 스팬을 생성하도록 설정할 수 있고, Pydantic AI는 각 에이전트 런·모델 요청·도구 호출에 스팬을 발행합니다. 이 스팬들을 Pydantic Logfire로 보내 애플리케이션에서 일어나는 일의 전체 종단간 보기를 얻을 수 있어요.

로깅·트레이싱 세부 사항은 DBOS 문서를 보세요.

더 알아보기 (Learn more)