Timeouts
Bounding how long one step inside a run may take, and ending a run from inside a tool, are answered by separate mechanisms with separate failure modes. This page maps them. To stop a run that is already in flight, see Cancelling a Run.
Each knob below bounds a different unit of work. None of them bounds the wall-clock duration of a whole run.
| What you want to bound | How to set it | What happens on expiry |
|---|---|---|
| A single model request | timeout on ModelSettings | The provider client raises; the run fails unless a FallbackModel or a transport retry handles it |
| A function tool call | Agent(tool_timeout=...), or timeout= on an individual tool — see Tool Timeout | The model receives a retry prompt 'Timed out after N seconds.', consuming that tool’s retry budget. A def tool is not actually stopped: the deadline is enforced around the await, so the worker thread runs to completion |
| A hook function | timeout= on the @hooks.on.* decorator | HookTimeoutError, which is an AgentRunError and aborts the run |
| Connecting to an MCP server | MCPToolset(init_timeout=...), default 5 seconds | The connection and initialize handshake fail |
| A single MCP request | MCPToolset(read_timeout=...), default 300 seconds | The request fails; under the default tool_error_behavior='retry' the model sees it as a retryable tool error |
| Total work done by a run | UsageLimits — requests, tool calls, tokens, or cost — see Usage Limits | UsageLimitExceeded |
| Wall-clock duration of a whole run | Nothing built in — wrap agent.run() in asyncio.timeout (Python 3.11+) or anyio.fail_after(), or cancel a CancellationToken from a timer | The run is cancelled |
Two of these need qualifying:
-
ModelSettings['timeout']is applied per model class, not universally. The OpenAI, Anthropic, Google, Groq, and Mistral model classes forward it to their provider client, as do the model classes built on OpenAI’s —CerebrasModel,OllamaModel,OpenRouterModel,ZaiModel, and the Bedrock Mantle models — which inherit the forwarding fromOpenAIChatModel/OpenAIResponsesModel. Other model classes ignore the setting, and the timeout on the HTTP client they were built with applies instead. When Pydantic AI creates that client itself, it defaults to a 600-second total timeout with a 5-second connect timeout. Google and Mistral additionally reject anhttpx.Timeoutobject and accept only a number of seconds.To bound a request on a model class that ignores the setting, configure the timeout where that provider actually takes one. Most providers accept your own
http_client, but several don’t:XaiProvidertakes a client-leveltimeout(or a preconfiguredxai_client),BedrockProvidertakesaws_read_timeoutandaws_connect_timeout(or a preconfiguredbedrock_client), andHuggingFaceProviderrejectshttp_clientoutright in favor ofhf_client. -
Tool timeouts are enforced by
FunctionToolsetonly, and each toolset carries its own.Agent(tool_timeout=...)sets the default for tools you register on the agent — it does not reach into aFunctionToolsetyou constructed yourself and passed viatoolsets=[...]. Give that toolset its ownFunctionToolset(timeout=...), or settimeout=on the individual tools. Tools coming from an MCP server, an external toolset, or a customAbstractToolsetread neither; bound those with the server-side or transport-level timeout instead.
If you enforce a deadline inside a tool body yourself, catch the TimeoutError and re-raise it as ModelRetry or ToolFailed rather than letting it escape. What happens to a bare TimeoutError depends on whether that tool has a timeout of its own:
- No
timeouton the tool or its toolset. It is an ordinary exception and propagates out of the agent run — unless a capability implementson_tool_execute_error, which can turn it into a replacement tool result or aModelRetry. - A
timeoutis configured. The call runs insideanyio.fail_after(timeout), which signals expiry withTimeoutErrortoo, so aTimeoutErroryou raised yourself is indistinguishable from the deadline expiring and becomes the same'Timed out after N seconds.'retry prompt — reporting a deadline that may never have passed.
Re-raising in the tool is the more local choice; the hook is for applying one policy across every tool.
What a tool raises decides whether the run continues, and what the model gets to see:
| Raise | Run continues? | The model sees |
|---|---|---|
ModelRetry | Yes | A retry prompt asking it to correct the call — consumes that tool’s retry budget |
ToolFailed | Yes | A failed tool result to adapt to — does not consume the retry budget |
ApprovalRequired / CallDeferred | Ends the run with a DeferredToolRequests output, unless a HandleDeferredToolCalls handler resolves the call inline | Nothing yet — see Deferred Tools |
| Any other exception | No | By default nothing — it propagates out of agent.run(). A capability implementing on_tool_execute_error sees it first and can return a replacement tool result or raise ModelRetry, letting the run continue |
A tool can also end the run without raising, by calling RunContext.cancel() — the run ends with RunCancelled and the tool’s return value is discarded. See Cancelling the Run from a Tool.
There is no exception that ends a run early with a successful output. To let a tool finish the run with a value, make that value the run’s output: give the agent an output tool the model can call, or an output function that produces the result.