paybondpaybond
Sign in

Pydantic AI adapter

Pydantic AI agent spend controls — guard Tool and callable execution with Harbor verify and automatic evidence via paybond_kit.pydantic_ai.

Paybond integrates with Pydantic AI at the tool execution boundary — Harbor verify runs before side-effecting Tool / callable handlers execute, then auto-evidence fires after success. Model inference stays on your LLM provider; Paybond guards paid and side-effecting tool calls only.

Python only — Pydantic AI is a Python framework. TypeScript Kit does not ship a Pydantic AI adapter; use agent-agnostic or MCP for other hosts.

Install

Install

npm install @paybond/kit

Import from @paybond/kit/agent

// Pydantic AI is Python-only — use paybond-kit[pydantic-ai] in Python projects.
  • Python only. TypeScript Kit does not ship a Pydantic AI adapter — use agent-agnostic middleware or MCP for other hosts.
  • Python: `paybond agent demo pydantic-ai smoke` requires the optional `pydantic-ai` extra. Use `pip install "paybond-kit[pydantic-ai]"`, `pipx install 'paybond-kit[pydantic-ai]'`, or `pipx inject paybond-kit pydantic-ai` (when base paybond-kit is already installed).
  • Smoke: `paybond agent demo pydantic-ai smoke --operation paid-tool --requested-spend-cents 100 --evidence-preset cost_and_completion --format json`.

One-liner (sandbox): paybond.instrument({ policy, framework: "pydantic-ai", tools }) or paybond.agent({ policy, framework: "pydantic-ai", tools }) returns guarded Pydantic AI tools. Production: omit sandbox and call instrumented.bind() per session.

from pydantic_ai import Agent, Tool
from paybond_kit import Paybond

paybond = await Paybond.open(api_key=os.environ["PAYBOND_API_KEY"])

result = await paybond.instrument(
    policy="./paybond.policy.yaml",
    framework="pydantic-ai",
    tools=[book_flight, search_catalog],  # callables or Tool instances
)

agent = Agent("openai:gpt-4o", tools=result.tools)

Already bound a run? Use create_paybond_pydantic_ai_config(run, tools) — returns { tools, wrap_tool } for incremental wiring. Prefer Agent(tools=config.tools) or FunctionToolset with pre-wrapped tools.

See Agent middleware for run binding, registry rules, and tenant isolation.

Advanced / manual wiring

When you need step-by-step control over registry and bind:

  1. Bind a PaybondAgentRun with a tool registry (sandbox bootstrap or production attach).
  2. Register side-effecting tools on the registry so spend resolvers read tool args (for example amount_cents from tool kwargs).
  3. Wrap tools with create_paybond_pydantic_ai_config before registering them on Agent / FunctionToolset.
from pydantic_ai import Tool
from paybond_kit.pydantic_ai import create_paybond_pydantic_ai_config

def submit_po(vendor_id: str, amount_cents: int) -> dict:
    """Submit a purchase order."""
    ...

config = create_paybond_pydantic_ai_config(
    run,
    [Tool(submit_po, name="procurement.submit_po")],
)
guarded_tools = config.tools

Non-side-effecting tools (per registry) pass through unchanged.

Scaffold and smoke

paybond init agent-middleware --framework pydantic-ai --out paybond_pydantic_ai.py
paybond policy init --preset saas --out paybond.policy.yaml

Validate authorization and evidence without a live Pydantic AI agent or LLM:

paybond login
paybond agent demo pydantic-ai smoke \
  --operation paid-tool \
  --requested-spend-cents 100 \
  --evidence-preset cost_and_completion \
  --format json

Requires the optional extra: pip install "paybond-kit[pydantic-ai]". With pipx: pipx install 'paybond-kit[pydantic-ai]' or pipx inject paybond-kit pydantic-ai.

Approval holds

When Harbor returns an approval hold, surface it to operators, approve in the tenant console, then retry with the same operation, amount, metadata, and approvalToken. Hard denials must not execute the tool. Guarded Pydantic AI tools raise pydantic_ai.ModelRetry with a descriptive Paybond message so the model loop can recover without performing the side effect.

run.store_approval_token(tool_call_id, approval_token_from_console)
# Retry the same tool call; the wrap path reads run.get_approval_token(tool_call_id).

Known limitations

Paybond only governs tools that go through the wrap helper:

  • Provider native tools (provider-executed function calling) are not intercepted.
  • Unwrapped MCP / remote toolsets bypass Harbor unless you re-wrap local callables.
  • Tools registered with @agent.tool / @agent.tool_plain after wrap (or without passing config.tools) are not guarded — wrap first, then pass tools into Agent(...).
  • prepare / prepare_tools only mutate tool definitions shown to the model; they do not intercept execution.