From ee28fb14c981932de943ef26554477dd169f112f Mon Sep 17 00:00:00 2001 From: Mike Ryan Date: Fri, 21 Nov 2025 07:30:38 -0800 Subject: [PATCH] fix: Relax UserError into a warning when state deps is not provided --- docs/ui/ag-ui.md | 2 ++ pydantic_ai_slim/pydantic_ai/ui/_adapter.py | 8 +++++--- tests/test_ag_ui.py | 14 ++++++++------ 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/docs/ui/ag-ui.md b/docs/ui/ag-ui.md index 1e85b64760..67eff5f67f 100644 --- a/docs/ui/ag-ui.md +++ b/docs/ui/ag-ui.md @@ -178,6 +178,8 @@ validate state contained in [`RunAgentInput.state`](https://docs.ag-ui.com/sdk/j If the `state` field's type is a Pydantic `BaseModel` subclass, the raw state dictionary on the request is automatically validated. If not, you can validate the raw value yourself in your dependencies dataclass's `__post_init__` method. + If AG-UI state is provided but your dependencies do not implement [`StateHandler`][pydantic_ai.ag_ui.StateHandler], Pydantic AI will emit a warning and ignore the state. Use [`StateDeps`][pydantic_ai.ag_ui.StateDeps] or a custom [`StateHandler`][pydantic_ai.ag_ui.StateHandler] implementation to receive and validate the incoming state. + ```python {title="ag_ui_state.py"} from pydantic import BaseModel diff --git a/pydantic_ai_slim/pydantic_ai/ui/_adapter.py b/pydantic_ai_slim/pydantic_ai/ui/_adapter.py index 970f06e6ef..a1ca12cd6e 100644 --- a/pydantic_ai_slim/pydantic_ai/ui/_adapter.py +++ b/pydantic_ai_slim/pydantic_ai/ui/_adapter.py @@ -1,5 +1,6 @@ from __future__ import annotations +import warnings from abc import ABC, abstractmethod from collections.abc import AsyncIterator, Sequence from dataclasses import KW_ONLY, Field, dataclass @@ -21,7 +22,6 @@ from pydantic_ai.agent import AbstractAgent from pydantic_ai.agent.abstract import Instructions from pydantic_ai.builtin_tools import AbstractBuiltinTool -from pydantic_ai.exceptions import UserError from pydantic_ai.messages import ModelMessage from pydantic_ai.models import KnownModelName, Model from pydantic_ai.output import OutputDataT, OutputSpec @@ -243,8 +243,10 @@ def run_stream_native( deps.state = state elif self.state: - raise UserError( - f'State is provided but `deps` of type `{type(deps).__name__}` does not implement the `StateHandler` protocol: it needs to be a dataclass with a non-optional `state` field.' + warnings.warn( + f'State was provided but `deps` of type `{type(deps).__name__}` does not implement the `StateHandler` protocol, so the state was ignored. Use `StateDeps[...]` or implement `StateHandler` to receive AG-UI state.', + UserWarning, + stacklevel=2, ) return self.agent.run_stream_events( diff --git a/tests/test_ag_ui.py b/tests/test_ag_ui.py index 33fbff65df..5cbf85fc69 100644 --- a/tests/test_ag_ui.py +++ b/tests/test_ag_ui.py @@ -39,7 +39,6 @@ from pydantic_ai._run_context import RunContext from pydantic_ai.agent import Agent, AgentRunResult from pydantic_ai.builtin_tools import WebSearchTool -from pydantic_ai.exceptions import UserError from pydantic_ai.models.function import ( AgentInfo, BuiltinToolCallsReturns, @@ -1206,12 +1205,15 @@ async def test_request_with_state_without_handler() -> None: state=StateInt(value=41), ) - with pytest.raises( - UserError, - match='State is provided but `deps` of type `NoneType` does not implement the `StateHandler` protocol: it needs to be a dataclass with a non-optional `state` field.', + with pytest.warns( + UserWarning, + match='State was provided but `deps` of type `NoneType` does not implement the `StateHandler` protocol, so the state was ignored. Use `StateDeps\\[\\.\\.\\.\\]` or implement `StateHandler` to receive AG-UI state.', ): - async for _ in run_ag_ui(agent, run_input): - pass + events = list[dict[str, Any]]() + async for event in run_ag_ui(agent, run_input): + events.append(json.loads(event.removeprefix('data: '))) + + assert events == simple_result() async def test_request_with_empty_state_without_handler() -> None: