Python SDK: PydanticAI adapter - #131
Merged
Merged
Conversation
Bind a PydanticAI agent to a governed run by wrapping its model: `Agent(govern(model, run))` — no other change to the agent. One model request counts as one governed step, so the deterministic loop/time budget halts a runaway agent, and with `gate_tools=True` each tool call the model proposes routes through the human-approval gate before the agent executes it. I built this on PydanticAI's `WrapperModel`, which forwards every model method to the wrapped model, so I only override `request`/`request_stream`. That keeps it to one stable surface (the `Model.request` contract, which hasn't changed across the post-1.0 line) and covers both loop/time enforcement and tool gating: the tool calls a step will make are present in the model's response before the agent runs them, so I gate them right after the response comes back. The halt propagates: PydanticAI only retries on its own `ModelRetry` signal and re-raises every other model-request error by default, so I raise plain `BudgetExceeded`/`ApprovalDenied` (never `ModelRetry`) and they bubble out of `agent.run()`/`run_sync()` and stop the agent rather than being retried into another paid request. I verified this against a real pydantic-ai install with a FunctionModel that loops forever. `pydantic-ai` is lazily imported and stays an optional extra, so the SDK still installs and imports with no third-party deps. Supported against pydantic-ai (pydantic-ai-slim) >= 1, < 2.
prashar32
force-pushed
the
feat/pydantic-ai-adapter
branch
from
June 14, 2026 16:10
09bfeff to
f467136
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds a PydanticAI adapter to the Python SDK so a PydanticAI agent can be bound to a governed run with no change to the agent beyond wiring the adapter:
How it works
I wrap the model with PydanticAI's
WrapperModel(which forwards every model method to the wrapped model) and override onlyrequest/request_stream. One model request counts as one governed step, so the deterministic loop/time budget is enforced over the agent's outer loop and a halt surfaces asBudgetExceeded. Withgate_tools=True, each tool call the model proposes is routed through the human-approval gate before the agent executes it (a denial raisesApprovalDenied).This keeps the adapter on a single stable surface — the
Model.requestcontract, which hasn't changed across the post-1.0 line — for both loop/time enforcement and tool gating, rather than depending on a newer hooks API. The proposed tool calls are present in the model's response before the agent runs them, so I gate them right after the response comes back and before they execute.Propagation
PydanticAI only retries a model request on its own
ModelRetrysignal and re-raises every other model-request error by default, so I deliberately raise plainBudgetExceeded/ApprovalDenied(neverModelRetry): they propagate out ofagent.run()/run_sync()and stop the agent rather than being retried into another paid request. This matches the propagation discipline of the LangChain (raise_error) and CrewAI (step_callbackvs event bus) adapters.pydantic-aiis lazily imported and added only as an optional extra (pip install riskkernel[pydantic-ai]), so the core SDK still installs and imports with no third-party dependencies. Supported againstpydantic-ai(pydantic-ai-slim) >= 1, < 2.Tests
tests/test_pydantic_ai.py— stdlib-only unit tests (step ticking, loop-budget halt surfaced-not-swallowed with the underlying request skipped, tool gating off/on/multi-call/final-answer/denied) plus twoskipUnless(pydantic-ai installed)integration tests that drive a realAgentwith aFunctionModeland prove a runaway agent halts at its loop budget and a denied tool blocks. Ran the full SDK suite (passes; pydantic-ai-gated tests skip without it) and verified the integration tests against a real pydantic-ai 1.107.0 install in a throwaway venv — all green.Closes #85