v0.24.0
Added
-
Every run is given typed dependencies. The endpoint now builds an
AgentDeps(user=request.user)per run and passes it to the agent, so tools,
toolsets and capabilities read request-scoped values offRunContext.deps—
pydantic-ai's own seam — instead of the agent closing over the request:@tool(registry) def whoami(ctx: RunContext[AgentDeps]) -> str: """Report the acting user.""" return str(ctx.deps.user)
- Spec tools bind the user natively.
SpecToolset's default extractor
readsctx.deps.user; this package used to override it with a closure,
purely because deps wereNone. - AG-UI
statenow lands somewhere.AgentDepssatisfies pydantic-ai's
StateHandlerprotocol, so a run'sRunAgentInput.stateis validated into
deps.staterather than dropped with aUserWarning. Emitting state back
is a tool's job — see the new Shared state
guide. - It is the precondition for reusing a built agent. A capability that
closes over a request can only serve that request, which is why the agent
(and every tool's JSON schema) is rebuilt on each call today. Nothing about
that caching changes here; this removes the blocker. - A request served without Django's auth middleware has no
userattribute
at all — that is an anonymous run (deps.user is None), matching what
materialize_request_useralready does, not anAttributeError.
- Spec tools bind the user natively.
-
deps_factory— supply the run'sAgentDepsyourself. A
request -> AgentDepscallable onAGUIServer/DjangoAGUIViewreplacing
the default, which binds only the acting user.- This closes a gap the previous release opened. Typed deps shipped, but
the endpoint constructedAgentDeps(user=request.user)itself with no hook,
so a project could not carry its own per-run context — and, specifically,
could not have AG-UI's inbound shared state validated, since pydantic-ai
validates it againsttype(deps.state)and that requires the deps to arrive
pre-seeded with a model instance. Now:
deps_factory=lambda request: AgentDeps(user=request.user, state=DocumentState()). - Subclass
AgentDepsto carry anything else per run (a tenant, a
feature-flag snapshot); whatever the factory returns reaches every tool,
toolset and capability asctx.deps.
- This closes a gap the previous release opened. Typed deps shipped, but
Changed
django-pydantic-agentfloor raised to>=0.3,<0.4—AgentDepsand the
request-freebuild_spec_capabilitycome from there.AgentSession(...)now requires a keyword-onlydeps. Deliberately
required rather than defaulted: a forgottendepswould mean spec tools
silently acting as nobody. Only affects code constructingAgentSession
directly; going throughDjangoAGUIView/AGUIServerneeds no change.
Documentation
-
New Shared state guide.
AG-UI's state channel works end to end and needed no package code — a tool
readsctx.deps.stateand writes back by returning aStateSnapshotEventas
ToolReturnmetadata, which pydantic-ai's adapter streams verbatim. The guide
is the recipe joining the two ends, plus when to prefer a tool instead: a
tool call is visible in the transcript and can be gated by a confirmation
card, which state events cannot.- Writing it surfaced the missing
deps_factoryseam (above) — without which
inbound state could not be validated into a model at all. The guide shows
the validated shape rather than a workaround.
- Writing it surfaced the missing
-
CI now checks doc snippets against the installed packages —
make docs-check/scripts/check_docs_snippets.py, wired into the docs job.
Every Python fence indocs/andREADME.mdmust parse, every
from X import Ymust resolve, and every keyword argument at a resolvable
call must exist in the real signature.- It closes the one gap the other gates share: ruff,
tyand
mkdocs --strictall stop at this package's boundary, so nothing checked a
claim about a dependency's API — where drift is likeliest, since the
dependency moves on its own schedule and no test imports the snippet. - A module the reader is meant to own is told apart from one that moved by
whether its root package is installed, not by a name list and not by
"the import failed".myproject.agentis a placeholder; a dependency
submodule that has been relocated is a failure — which is the shape of a
real, boot-breaking defect found in these docs before. - Verified against known-bad snippets, not just a clean run: a moved
dependency module, a bad keyword on one of our types, and a bad keyword on a
dependency's type are each caught. - Limits, stated so nobody over-trusts it: it does not execute snippets or
check semantics, and it cannot see non-Python fences — a JavaScript
example remains a matter for review.
- It closes the one gap the other gates share: ruff,