온디맨드 캐퍼빌리티

온디맨드 캐퍼빌리티 (On-Demand Capabilities)

캐퍼빌리티는 지침과/또는 도구의 묶음이며, 선택적으로 설정과 훅을 담아요. 여러 워크플로를 다루는 에이전트는 보통 매 턴마다 모든 워크플로의 지침과 도구 스키마를 보내고, 런 전체에 모든 워크플로의 설정과 훅을 적용하죠 — 대부분의 요청이 단 하나의 워크플로만 필요함에도요. 온디맨드 캐퍼빌리티는 이 비용을 줄이기 위해 캐퍼빌리티를 한 줄 카탈로그 항목으로 접어서, 모델이 필요할 때만 온디맨드로 가져오게 해 줘요.

출처: 문서

본문

캐퍼빌리티는 지침과/또는 도구의 묶음이에요, 선택적으로 설정과 훅을 담습니다. 멀티 워크플로 에이전트는 보통 매 턴마다 모든 워크플로의 지침과 도구 스키마를 보내고, 런 전체에 모든 워크플로의 설정과 훅을 적용합니다 — 대부분의 요청이 단 하나의 워크플로만 필요로 하는데도요. 이 비용은 워크플로를 추가할수록 커져요: 입력 토큰이 늘고, 보이는 도구셋이 모델이 잘못 고르기 시작하는 ~30-50개 도구 지점을 넘어가면 도구 선택이 나빠집니다 (그 압력이 tool search 뒤에도 있어요).

캐퍼빌리티defer_loading=True로 표시하고 안정적인 id를 주면, 그것은 한 줄 카탈로그 항목으로 수축합니다 — id와 선택적 description — 그리고 모델이 온디맨드로 가져옵니다. 최소 설정은:

from pydantic_ai import Agent
from pydantic_ai.capabilities import Capability

refunds = Capability(
    id='refunds',
    description='Use for refund eligibility, refund status, or processing a refund.',
    instructions='Always confirm the order ID before issuing a refund.',
    defer_loading=True,
)


@refunds.tool_plain
def refund_status(order_id: str) -> str:
    """Look up the refund status for an order."""
    return f'Order {order_id}: refund issued on 2026-05-01.'


agent = Agent(
    'openai-responses:gpt-5.4',
    instructions='You are a customer support assistant.',
    capabilities=[refunds],
)

첫 턴에서 환불 워크플로는 카탈로그 항목으로 수축됩니다. 모델은 기본 지침, 프레임워크가 관리하는 load_capability 도구, 그리고 지침에 추가된 카탈로그를 봅니다:

The following capabilities are deferred and can be loaded using the `load_capability` tool. A capability may have tools; they stay hidden until it is loaded:
- refunds: Use for refund eligibility, refund status, or processing a refund.

모델은 아직 환불 지침이나 refund_status 도구 정의를 받지 않으므로 그 도구를 호출할 이유가 없어요. 활성 모델에 따라 Pydantic AI는 숨은 상태를 보존하기 위해 공급자/도구 검색 plumbing을 보낼 수도 있는데, 그 plumbing은 캐퍼빌리티가 로드되기 전에는 환불 도구 정의를 노출하지 않습니다. 교환은 단일 agent.run_sync 호출 안에서 여러 모델 요청에 걸쳐 펼쳐집니다:

  1. 요청 1. 모델은 위 카탈로그와 사용자 프롬프트를 봅니다. id='refunds'load_capability 도구를 호출합니다.
  2. 로드. Pydantic AI가 캐퍼빌리티의 지침 — "Always confirm the order ID before issuing a refund." — 을 도구 결과로 반환하고 다음 요청에 refund_status 정의를 노출합니다.
  3. 요청 2. 모델은 이제 지침을 히스토리에서, refund_status를 도구 목록에서 봅니다. refund_status(order_id='ABC-123')을 호출하고 결과로 사용자에게 답합니다.

이미 로드된 캐퍼빌리티는 런의 나머지 동안 계속 로드된 상태를 유지합니다 — 모델이 다시 열 필요가 없어요.

검색은 캐퍼빌리티 소유 도구를 드러낼 수 없습니다. 그 캐퍼빌리티가 로드될 때까지 숨어 있기 때문이에요. 검색 가능한 지연 도구도 있는 런에서는 카탈로그가 모델에게 그 도구를 검색하라고 하기보다 캐퍼빌리티를 로드하라고 명시적으로 안내합니다. 캐퍼빌리티 전용 런 — 검색 표면이 없는 경우 — 에서는 카탈로그가 검색을 언급하지 않아요.

로드는 지침만이 아니라 전체 묶음을 활성화합니다: 캐퍼빌리티의 함수 도구, 모델 설정, 라이프사이클 훅이 함께 생겨나요 (참고: What you can defer). 이미 등록한 캐퍼빌리티의 한 줄 변경이며, 모든 공급자에서 동작하고, 히스토리 재생을 견딥니다.

참고

load_capability 도구 이름은 온디맨드 캐퍼빌리티가 있으면 언제나 예약됩니다. 캐퍼빌리티 id 값은 안정적이어야 해요 — 캐퍼빌리티가 MCP처럼 서버 URL에서 안정적인 id를 스스로 유도하지 않는다면 명시적으로 설정하세요. 참고: Resumable across runs.

지연 지침이 클라이언트 지향 메시지 히스토리에 도달합니다

지연 캐퍼빌리티의 지침은 load_capability 도구 결과 로 돌아오므로 런의 메시지 히스토리에 들어갑니다 — UI 어댑터가 클라이언트에 직렬화하는 복사본을 포함해서요. 항상 켜진 캐퍼빌리티의 지침은 대신 서버 측 시스템 프롬프트에 유지됩니다. 캐퍼빌리티의 지침을 클라이언트에 노출하면 안 된다면, 지연 대신 항상 켜짐으로 유지하세요.

그것들은 InstructionPart가 아니라 도구 결과 텍스트로 도착하므로 id로 주소 지정할 수도 없어요. ModelRequestParameters.instruction_parts를 다시 쓰는 before_model_request 훅은 그것들을 절대 보지 못합니다. 그래서 그 위에 세워진 것(원격 지침 오버라이드 포함)은 항상 켜진 캐퍼빌리티의 지침에는 닿지만 지연된 것에는 닿지 않아요. 지침이 주소 지정 가능해야 한다면 캐퍼빌리티를 항상 켜짐으로 유지하세요.

무엇을 지연할 수 있나 (What you can defer)

캐퍼빌리티 묶음의 모든 부분이 하나의 단위로 함께 활성화됩니다:

부분 로드 전 로드 후
지침 (정적 또는 동적) 보내지 않음 load_capability 도구 결과로 반환; 이후 요청에 포함
함수 도구 노출되지 않음 다음 요청에서 노출
모델 설정 (정적 또는 스텝별) 적용되지 않음 이후 요청의 설정에 병합
라이프사이클 발동하지 않음 캐퍼빌리티가 로드된 후 발동
네이티브 도구 노출되지 않음 다음 요청에서 노출 — 참고: Cache implications

언제 쓸까 (When to use it)

온디맨드 캐퍼빌리티를 써야 할 때:

  • 에이전트가 여러 개별 워크플로(환불, 반품, 사기 검토, 계정 보안...)를 서비스하고 대부분의 턴이 하나만 필요할 때
  • 워크플로가 지침보다 더 — 자체 도구, 높인 추론 노력, 승인 훅 — 필요하고, 그것들이 하나의 단위로 함께 이동해야 할 때
  • 스킬 스타일의 점진적 공개를 원하면서도 로드된 묶음이 런북만이 아니라 도구와 설정도 가져오길 원할 때

다음 경우엔 건너뛰세요:

  • 캐퍼빌리티가 대부분의 턴에서 쓰일 때 — 발견 왕복 비용이 절약하는 토큰보다 큽니다
  • 공유 지침 없이 개별 발견 가능한 도구의 평평한 카탈로그일 때 — 묶음을 로드하는 대신 이름으로 개별 도구를 발견하는 tool search를 쓰세요

Anthropic의 Agent Skills을 써 봤다면, 이것은 같은 아이디어를 일반화한 것입니다: 스킬은 모델이 온디맨드로 가져올 수 있는 마크다운 파일이죠. 온디맨드 캐퍼빌리티는 그것 에 더해 타입 있는 함수 도구, 스텝별 모델 설정, 라이프사이클 훅을 합니다.

기존 캐퍼빌리티 개조 (Retrofitting an existing capability)

defer_loading=TrueCapability 편의 클래스에만 있는 게 아니에요. 공유 필드는 AbstractCapability에 있고, 내장 캐퍼빌리티는 생성 시 id, description, defer_loading을 노출합니다. 커스텀 캐퍼빌리티는 인스턴스에 그 속성들을 설정하세요.

from pydantic_ai import Agent
from pydantic_ai.capabilities import MCP

agent = Agent(
    'openai-responses:gpt-5.4',
    capabilities=[
        MCP(
            url='https://mcp.example.com/analytics',
            native=True,
            id='analytics-mcp',
            description='Use for analytics queries, dashboards, and metric lookups.',
            defer_loading=True,
        ),
    ],
)

모델이 analytics-mcp를 로드하기 전까지는 MCP 서버의 어떤 도구 정의도 프롬프트에 들어가지 않아요. 같은 플래그는 WebSearch, WebFetch, Hooks, 그리고 어떤 커스텀 AbstractCapability 서브클래스에도 동작합니다 — 자신의 서브클래스에 defer_loading을 추가하는 방법은 Building custom capabilities을 보세요.

지연 MCP: 안정적인 id 설정하기

MCP는 생략하면 서버 URL에서 id를 유도하므로, defer_loading=True는 명시적 id 없이도 동작해요. 그래도 대화를 영속화하고 재개한다면 하나를 전달하세요. URL 유도 id는 URL이 바뀌면(다른 환경, 경로 버전...) 바뀌는데, 재개된 캐퍼빌리티의 로드 상태를 조용히 깨뜨립니다.

런 간 재개 가능 (Resumable across runs)

로드된 캐퍼빌리티와 도구 가용성 상태는 에이전트가 아니라 메시지 히스토리에 살아요. 대화가 데이터베이스에 영속화되고 나중에 — 어쩌면 다른 프로세스, 머신, 모델에서 — 재개될 때, Pydantic AI는 load_capability 호출/반환 쌍에서 로드된 캐퍼빌리티 ID를, ToolAvailabilityDeltaPart에서 드러난 도구 이름을 재구성합니다. 모델이 이전에 로드한 캐퍼빌리티는 계속 로드된 상태로, 로드하지 않은 것은 카탈로그에서 접힌 채로 유지됩니다. 재개 시 재발견 왕복이 없어요.

이것이 지연 캐퍼빌리티가 안정적인 명시적 id를 요구하는 이유예요. 히스토리 재생이 호출을 캐퍼빌리티와 id로 매칭하므로, 클래스 유도 id는 클래스 이름이 바뀌는 순간 조용히 깨집니다. 같은 속성이 크로스 공급자 재생을 동작하게 해요 — Anthropic에서 refunds를 로드하고 OpenAI Responses에서 계속하는 런은 스위치 후에도 refunds를 로드된 상태로 유지합니다.

히스토리는 어떤 캐퍼빌리티 id가 로드되었는지를 담지, 캐퍼빌리티 자체를 담지 않아요. 재개하는 에이전트는 같은 도구들로 구성되어야 하듯, 같은 캐퍼빌리티(일치하는 id)로 구성되어야 합니다. 상태는 히스토리에, 정의는 코드에 있죠.

RunContext의 런타임 상태

여러 RunContext 필드가 점진적 공개 상태를 도구, 훅, 캐퍼빌리티 소유 콜백에 노출합니다:

  • ctx.loaded_capability_idsload_capability 도구로 명시적으로 로드된 지연 캐퍼빌리티 ID. 매 모델 요청 전에 메시지 히스토리에서 재구성됩니다. 스텝 중에 로드된 캐퍼빌리티는 다음 스텝부터 나타나는데, 그것이 그 지침과 도구가 모델에 도달하는 첫 스텝이기도 해요.
  • ctx.active_capability_ids — 현재 활성 캐퍼빌리티 ID: 항상 켜진 캐퍼빌리티 + ctx.loaded_capability_ids.
  • ctx.capability_active — Pydantic AI가 캐퍼빌리티 소유 훅 또는 콜백을 실행하는 동안에만 의미가 있어요. 그 캐퍼빌리티에 한정되며, 지연 훅·콜백은 이 값이 참이 될 때까지 건너뜁니다. 활성(active)이지 로드됨이 아니에요: 항상 켜진 캐퍼빌리티의 훅은 아무것도 로드하지 않았어도 True를 읽습니다.
  • ctx.discovered_tool_names — 지속 히스토리에 의해 드러난 지연 함수 도구. 도구 검색, ToolReturn.tools, 또는 캐퍼빌리티 로드를 통해서요.
  • ctx.available_tool_names — 현재 사용 가능하다고 알려진 함수 도구 이름: 현재 스텝의 조립된 도구 관리자의 항상 보이는 도구 + 히스토리에서 드러난 이름. before_run 같은 초기 훅은 도구 정의가 준비되기 전에 히스토리 유도 이름만, 또는 아직 없으면 빈 집합을 볼 수 있어요. 무엇이 채워지는지에 훅 타이밍이 어떻게 영향을 미치는지는 Hook ordering을 보세요.
  • ctx.is_tool_available(tool) — 함수 도구가 현재 보이는지 여부. 툴셋을 감싸는 경우 자신이 보유한 ToolDefinition을 전달하고, 모델 요청 훅·도구 실행은 현재 ctx.tools 스냅샷의 이름을 전달할 수 있어요.
  • ctx.usage_limits — 런이 강제하는 UsageLimits (없으면 UsageLimits()로 기본 설정되어, 런 밖에서만 None). ctx.usage와 함께 지금까지의 사용량을 제공합니다. 캐퍼빌리티는 중복 복사본으로 구성되지 않고도 런의 한도를 읽어 남은 예산을 공개·적응할 수 있어요 (예: 예산 공개). 읽기 전용으로 취급하세요. 런이 강제하는 실제 객체이므로 필드를 변경하면 이후 요청에 런이 강제하는 것이 바뀝니다.

캐퍼빌리티를 로드하면 캐퍼빌리티 상태가 즉시 갱신되지만, 로드된 묶음의 함수 도구, 네이티브 도구, 모델 설정은 다음 모델 요청에서 효력이 생깁니다.

크로스 공급자 동작 (Cross-provider behavior)

온디맨드 캐퍼빌리티는 모든 모델에서 동작하며, 공급자가 가용성 변경을 네이티브로 표현할 수 있으면 로드해도 프롬프트 접두사가 그대로 유지됩니다.

캐퍼빌리티 소유 도구는 그 캐퍼빌리티가 로드될 때까지 숨어 있고, 결코 검색할 수 없어요 — 모델은 요청이 아니라 캐퍼빌리티를 로드해서 도달합니다. 통일된 규칙은 드러나지 않은 지연 도구가 모델의 사용 가능한 컨텍스트 밖에 유지된다는 것이고, 각 공급자의 공개 메커니즘이 그 통신 표시를 결정합니다.

  • Anthropic tool_addition_mode='by_reference'tool_addition 블록에서 드러난 이름을 참조합니다. 캐퍼빌리티 전용 런은 defer_loading=True로 그 정의를 미리 광고하고, 검색 표면이 있는 혼합 런은 보류하다가 공개와 같은 요청에서 지연 정의를 추가합니다.
  • OpenAI Responses tool_addition_mode='with_definitions' 는 추가된 additional_tools 입력 항목에 완전한 공개 정의를 실어 tools에는 두지 않습니다.
  • 공급자 네이티브 공개 항목 미지원 (tool_addition_mode=None) 은 스키마가 보일 때 The following tool(s) are now available: {names}를 알립니다. 결과가 여전히 보류된 스키마를 드러내야 할 때만 search_tools 교환을 합성합니다.

같은 런에 독립적인 defer_loading=True 도구를 추가하면 그 도구에 대해 도구 검색이 돌아옵니다 — 진짜 검색 가능하니까요. 캐퍼빌리티 소유 도구는 검색 표면이 있는 동안 통신에서 완전히 빠져 있어서, 검색은 완전히 네이티브로 유지되고(모델이 지원하면 서버 실행) 어떤 쿼리도 캐퍼빌리티가 로드되지 않은 도구를 표면화할 수 없습니다.

캐시 영향 (Cache implications)

load_capability 도구를 호출하면 요청 사이에 캐퍼빌리티 동작이 드러납니다. 그것이 공급자의 프롬프트 캐시 접두사를 깨는지는 무엇이 드러나는지에 달려 있어요:

load_capability는 로드된 캐퍼빌리티의 함수 도구 이름을 ToolReturn.tools로 반환하고, 실행기는 도구 결과 옆에 가용성 델타를 기록합니다. 어떤 사용자 도구든 같은 소스를 쓸 수 있어요. 델타 없이 완전한 캐퍼빌리티 로드 교환을 담은 히스토리는 다음 모델 요청 전에 번역됩니다.

무엇이 로드되나 캐시 접두사
지침만 안정적 — 지침이 요청 접두사가 아니라 메시지 히스토리에 들어갑니다.
공급자 네이티브 공개 항목 지원이 있는 함수 도구 (tool_addition_mode='by_reference' 또는 'with_definitions') Anthropic과 OpenAI Responses에서 안정적 — 지연 Anthropic 항목은 캐시 키 밖이고, OpenAI Responses는 tools[]를 바꾸지 않고 additional_tools를 추가합니다.
공급자 네이티브 공개 항목 미지원의 함수 도구 (tool_addition_mode=None) 턴 사이 깨질 수 있음 — 캐퍼빌리티가 로드되면 함수 도구 가시성이 바뀔 수 있습니다.
네이티브 도구 로드 시 항상 접두사 깨짐 — 네이티브 도구 정의는 모든 공급자에서 요청 접두사의 일부예요.

캐시 접두사 보존이 중요할 때는 가용성 변경을 네이티브로 표현할 수 있는 모델에서 지침 전용 또는 함수 도구 전용 온디맨드 캐퍼빌리티를 선호하세요. 접두사를 안정적으로 유지하는 공급자별 메커니즘은 Tool search and prompt caching에 있습니다.

Capability 편의 클래스

Capability는 서브클래싱 없이 지침, 함수 도구, 툴셋을 묶습니다. @agent.tool을 반영하는 데코레이터로 도구를 등록하세요:

from pydantic_ai import RunContext
from pydantic_ai.capabilities import Capability

refunds = Capability(
    id='refunds',
    description='Use for refund eligibility and refund status.',
    instructions='Always confirm the order ID before issuing a refund.',
    defer_loading=True,
)


@refunds.tool
def refund_status(ctx: RunContext[None], order_id: str) -> str:
    """Look up the refund status for an order."""
    return f'Order {order_id}: refund issued on 2026-05-01.'

@capability.tool@capability.tool_plain에 더해, tools=로 기존 함수나 Tool 인스턴스를, toolsets=툴셋 하나 이상을 넘길 수 있어요. 동적 지침에는 @capability.instructions 데코레이터를, 동적 카탈로그 항목에는 description=에 callable을 넘기세요.

@capability.tool@capability.tool_plaindefer_loading 인수를 포함해 @agent.tool을 정확히 반영합니다. 지연 캐퍼빌리티에서는 그 툴별 플래그가 무동작이에요 — 캐퍼빌리티가 모든 도구를 하나의 단위로 게이트하니까요 — 그래서 비-지연 Capability에서만 효과가 있는데, 거기서 개별 도구를 도구 검색 발견에 옵트인합니다.

지침, 함수 도구, 툴셋, 설명을 넘어서는 것 — 모델 설정, 훅, 네이티브 도구, 래퍼 툴셋, 커스텀 런별 로직 — 은 직접 AbstractCapability를 서브클래스하세요. 서브클래스할 때 카탈로그 항목이 런별로 달라져야 한다면 get_description을 오버라이드하세요.

지속 실행을 위한 id 설정

캐퍼빌리티가 기여한 툴셋 — Capability(tools=[...]) 또는 로컬로 실행되는 MCP 서버 — 은 캐퍼빌리티의 id에서 id를 상속합니다. 지속 실행은 각 리프 툴셋을 id로 식별하므로, 캐퍼빌리티와 Temporal·DBOS·Prefect를 결합할 때 Capability(id='...', tools=[...]) 또는 MCP(id='...', url='...')을 전달하세요. Temporal은 모든 리프 툴셋에, DBOS는 모든 MCP 서버에 id를 요구합니다 — 둘 다 없으면 생성 시 오류를 냅니다. (MCPid가 없으면 서버 URL에서 유도하기도 해요.) URL 유도 id는 두 서버가 호스트와 마지막 경로 세그먼트를 공유할 때 충돌할 수 있는데 (https://a.com/apihttps://a.com/v2/api 모두 a.com-api 유도), DBOS는 생성 시, Temporal은 워커 시작 시 오류를 내므로 명시적 id를 전달해 구분하세요.

지침을 넘어서: 도구, 설정, 훅, 네이티브 도구

Capability 예제는 지침과 함수 도구를 지연했지만, 같은 플래그가 전체 묶음 — 모델이 아는 것, 할 수 있는 것, 하는 방식 — 을 게이트합니다 (참고: What you can defer). 아래 스니펫들은 나머지 조각들, 모델 설정, 훅, 네이티브 도구를 차례로 보여줍니다.

지연 모델 설정

get_model_settings는 캐퍼빌리티 조립 중에 수집되지만, 그 설정은 지연 캐퍼빌리티가 로드된 후에만 적용됩니다. 즉 높인 추론 노력 같은 스텝별 설정은 모델이 옵트인한 워크플로에만 적용됩니다:

from dataclasses import dataclass
from typing import Any

from pydantic_ai import Agent, ModelSettings
from pydantic_ai.capabilities import AbstractCapability


@dataclass
class DeepReasoning(AbstractCapability[Any]):
    def get_model_settings(self) -> ModelSettings:
        return ModelSettings(extra_body={'reasoning_effort': 'high'})


agent = Agent(
    'openai-responses:gpt-5.4',
    capabilities=[
        DeepReasoning(
            id='deep-reasoning',
            description='Use for multi-step planning or hard analytical problems.',
            defer_loading=True,
        ),
    ],
)

지연 워크플로가 있는 라이프사이클 훅

훅도 지연 캐퍼빌리티에 있을 수 있어요. 그것을 소유한 캐퍼빌리티를 모델이 로드할 때까지 실행되지 않습니다:

from dataclasses import dataclass

from pydantic_ai import Agent
from pydantic_ai.capabilities import AbstractCapability


@dataclass
class AccountSecurityWorkflow(AbstractCapability[None]):
    id: str = 'account-security'
    description: str = 'Use when the next action may be destructive.'
    defer_loading: bool = True

    def get_instructions(self) -> str:
        return 'Confirm the customer identity before taking destructive action.'

    async def before_tool_execute(self, ctx, *, call, tool_def, args):
        # Inspect the call, prompt the operator, raise to block.
        return args


agent = Agent('openai-responses:gpt-5.4', capabilities=[AccountSecurityWorkflow()])

다른 캐퍼빌리티 확인하기

ctx.capability_active는 현재 훅이 실행 중인 그 캐퍼빌리티에 한정됩니다. 항상 켜진 훅 캐퍼빌리티에서는 항상 참이에요. 다른 지연 캐퍼빌리티가 로드되었는지 확인하려면 ctx.loaded_capability_ids에서 그 ID를 찾으세요, 예: if 'account-security' in ctx.loaded_capability_ids:. 훅이 워크플로 로드 전에 규칙을 강제해야 한다면, 그 훅을 항상 켜진 캐퍼빌리티에 두고 ctx.loaded_capability_ids를 검사하세요.

지연 네이티브 도구

어떤 provider-adaptive 캐퍼빌리티 (WebSearch, WebFetch, MCP, ...)든 같은 방식으로 지연할 수 있어요. 네이티브 도구 정의는 load_capability 도구가 그 캐퍼빌리티를 로드한 후에만 요청에 들어갑니다 — 트레이드오프는 참고: Cache implications.

from pydantic_ai import Agent
from pydantic_ai.capabilities import WebSearch

agent = Agent(
    'anthropic:claude-sonnet-4-6',
    capabilities=[
        WebSearch(
            local='duckduckgo',
            id='web-research',
            description='Use when the question requires up-to-date information.',
            defer_loading=True,
        ),
    ],
)

종합: 멀티 워크플로 지원 에이전트

현실적인 온디맨드 캐퍼빌리티는 거의 한 조각으로만 이루어지지 않아요. 아래 예제는 번들의 서로 다른 부분을 사용하는 두 개의 지연 워크플로를 가진 고객 지원 에이전트를 정의합니다:

  • orders — 지침과 함수 도구. Capability로 인라인 정의.
  • account-security — 지침, 함수 도구, 높인 추론 노력, 그리고 승인 훅. 모두 하나의 AbstractCapability 서브클래스로 묶음.

그 워크플로들에 대해 턴 1은 두 줄 카탈로그만 노출합니다. 기본 지침, 항상 켜진 도구, 프레임워크 관리 load_capability 도구, 공급자/도구 검색 plumbing은 평소처럼 나타나요. account-security를 로드하면 런북, 파괴적 도구, 더 높은 추론 노력, 그리고 승인 게이트가 함께 활성화됩니다 — 그것이 번들 수준 공개가 의미하는 바예요.

from dataclasses import dataclass

from pydantic_ai import Agent, ModelSettings, RunContext
from pydantic_ai.capabilities import AbstractCapability, Capability
from pydantic_ai.toolsets import AgentToolset, FunctionToolset


@dataclass
class Store:
    orders: dict[str, str]


# Workflow 1: instructions + function tool, defined inline.
orders = Capability[Store](
    id='orders',
    description='Use for order tracking, delivery status, or questions involving an order ID.',
    instructions='Quote the order ID and item name when discussing an order.',
    defer_loading=True,
)


@orders.tool
def order_status(ctx: RunContext[Store], order_id: str) -> str:
    """Look up shipping or delivery status for an order."""
    return ctx.deps.orders.get(order_id, f'No order found with id {order_id}.')


# Workflow 2: instructions + tool + per-step model settings + approval hook,
# all hidden until the model loads `account-security`.
security_tools = FunctionToolset[Store]()


@security_tools.tool
def revoke_sessions(ctx: RunContext[Store], account_id: str) -> str:
    """Revoke all active sessions for an account."""
    return f'Revoked sessions for {account_id}.'


@dataclass
class AccountSecurity(AbstractCapability[Store]):
    id: str = 'account-security'
    description: str = 'Use for suspicious logins, account takeover, or session revocation.'
    defer_loading: bool = True

    def get_instructions(self) -> str:
        return 'Confirm the customer identity before revoking sessions.'

    def get_toolset(self) -> AgentToolset[Store]:
        return security_tools

    def get_model_settings(self) -> ModelSettings:
        # Raise reasoning effort just for sensitive workflows.
        return ModelSettings(extra_body={'reasoning_effort': 'high'})

    async def before_tool_execute(self, ctx, *, call, tool_def, args):
        # Approval gate: inspect the call and raise to block, active once the model has loaded `account-security`.
        return args


support_agent = Agent(
    'openai-responses:gpt-5.4',
    deps_type=Store,
    instructions='You are a customer-support agent for an e-commerce store.',
    capabilities=[orders, AccountSecurity()],
)

"내 주문 어디 있어요?" 요청은 orders만 로드합니다. "누가 내 계정에 로그인하려고 해요" 요청은 account-security만 로드하고 — 그 시점부터 런의 모든 도구 호출은 승인 훅을 통과하고 그리고 높인 추론 노력의 혜택을 받지만, 그 워크플로를 건드리지 않은 요청에서는 둘 다 모델에 보이지 않아요.

읽기-전-행동 강제 (Enforcing read-before-act)

모델이 파괴적 행동 전에 런북을 실제로 읽게 하고 싶나요? 런북을 지연 캐퍼빌리티로 만들고, 한 메서드 훅에서 ctx.loaded_capability_ids를 확인하세요:

from dataclasses import dataclass, field

from pydantic_ai import Agent, ModelRetry
from pydantic_ai.capabilities import AbstractCapability, Capability


@dataclass
class RunbookRequired(AbstractCapability[None]):
    """Bounces a tool call back until the matching runbook has been loaded."""

    requirements: dict[str, str] = field(default_factory=dict)

    async def before_tool_execute(self, ctx, *, call, tool_def, args):
        required = self.requirements.get(tool_def.name)
        if required and required not in ctx.loaded_capability_ids:
            raise ModelRetry(
                f'Call the `load_capability` tool with `id={required!r}` and follow its '
                f'guidance before calling `{tool_def.name}`.'
            )
        return args


refund_policy = Capability(
    id='refund-policy',
    description='Read before issuing refunds. Eligibility rules and approval limits.',
    instructions=(
        'Refunds over $500 require manager approval. '
        'Refunds outside the 30-day window require a documented exception.'
    ),
    defer_loading=True,
)


agent = Agent(
    'openai-responses:gpt-5.4',
    capabilities=[
        refund_policy,
        RunbookRequired(requirements={'issue_refund': 'refund-policy'}),
    ],
)


@agent.tool_plain
def issue_refund(order_id: str, amount: float) -> str:
    """Issue a refund for an order."""
    return f'Refund of ${amount} issued for {order_id}.'

모델은 턴 1부터 issue_refund를 봅니다. refund-policy를 열기 전에 호출하려 하면, 훅이 정확히 어떤 load_capability 도구 호출을 해야 하는지 가리키는 메시지로 호출을 되돌려 보냅니다. 모델이 정책을 로드하고, 정책 텍스트가 최근 컨텍스트에 들어가며, 환불이 규칙 안에서 실행됩니다 — 그리고 그때만요. 같은 패턴이 어떤 도구-런북 쌍에도 동작합니다.

로드된 집합은 RunContext의 런타임 데이터일 뿐이므로 패턴은 일반화됩니다: 동적 지침이 위험한 워크플로 쌍이 열려 있을 때 경고하고, 감사 훅이 로드된 집합으로 트레이스를 태그하고, 에스컬레이션 훅이 paymentsaccount-security가 모두 활성일 때 추가 확인을 요구할 수 있어요.

Markdown 파일에서 스킬 로드 (Loading skills from Markdown files)

스킬을 이미 YAML frontmatter가 있는 Markdown 파일로 — Anthropic Agent Skills이 쓰는 형식 — 유지하고 있다면, 각각을 몇 줄의 접착 코드로 Capability에 감쌀 수 있어요.

스킬 파일 skills/refunds.md가 주어졌다면:

---
id: refunds
description: Use for refund eligibility, refund status, or processing a refund.
---
Always confirm the order ID before issuing a refund.
Never issue refunds over $500 without manager approval.

온디맨드 캐퍼빌리티로 에이전트에 로드합니다:

from pathlib import Path

import yaml

from pydantic_ai import Agent
from pydantic_ai.capabilities import Capability


def load_skill(path: Path) -> Capability:
    _, frontmatter, body = path.read_text().split('---', 2)
    meta = yaml.safe_load(frontmatter)
    return Capability(
        id=meta['id'],
        description=meta['description'],
        instructions=body.strip(),
        defer_loading=True,
    )


agent = Agent(
    'openai-responses:gpt-5.4',
    instructions='You are a customer support assistant.',
    capabilities=[load_skill(p) for p in Path('skills').glob('*.md')],
)

각 파일은 모델의 카탈로그에 iddescription으로 나타나고, 본문은 모델이 load_capability 도구를 호출할 때만 전송됩니다. 지침을 넘어서 — 특정 스킬에 함수 도구, 모델 설정, 훅을 추가 — 하려면 위 예제들처럼 AbstractCapability를 서브클래스하세요.

함께 구성됨 (Composes with)

온디맨드 캐퍼빌리티는 프레임워크의 나머지와 직교합니다 — 이미 쓰고 있을 수 있는 기능 위에 겹쳐집니다:

  • 도구 검색 — 캐퍼빌리티 수준 defer_loading=True가 전체 묶음을 하나의 단위로 게이트합니다. 도구별 발견에는 비-지연 캐퍼빌리티나 @agent.tool에서 도구 수준 defer_loading=True를 설정하세요.
  • MCP 서버MCP 캐퍼빌리티가 defer_loading=True를 받아, 모델이 옵트인할 때까지 서버의 전체 도구 목록을 숨깁니다.
  • 네이티브 도구WebSearch, WebFetch, ImageGeneration, MCP 모두 함수 도구처럼 똑같이 지연합니다 (참고: Cache implications).
  • — 지연 캐퍼빌리티(또는 지연 Hooks 캐퍼빌리티)에 선언된 라이프사이클 훅은 모델이 옵트인할 때까지 휴면 상태로 남습니다.
  • 메시지 히스토리 — 로드된 상태가 히스토리를 통해 왕복하므로, 영속화된 대화가 같은 상태로 재개됩니다 (참고: Resumable across runs).

어떤 출처든 함수 도구 공개는 ToolAvailabilityDeltaPart로 영속화되므로, 재개되거나 지속된 런이 애플리케이션 주도 제어가 모델이 수행한 도구 검색으로 기록되지 않고도 사용 가능한 도구 집합을 재구성할 수 있어요.

더 알아보기 (Learn more)