Skip to content

pydantic_ai.profiles

ModelProfile

Bases: TypedDict

Describes how requests to and responses from specific models or families of models need to be constructed and processed to get the best results, independent of the model and provider classes used.

All fields are optional; absent keys mean “use the documented default” (defaults are documented per field below and applied at access sites).

Subclasses (OpenAIModelProfile, AnthropicModelProfile, …) add provider-specific keys; cross-class merging via dict-spread is supported.

Attributes

supports_tools

Whether the model supports tools. Default: True.

Type: bool

supports_text_output

Whether the model supports text output. Default: True.

Type: bool

supports_tool_return_schema

Whether the model natively supports tool return schemas. Default: False.

When True, the model’s API accepts a structured return schema alongside each tool definition. When False, return schemas are injected as JSON text into tool descriptions as a fallback.

Type: bool

supports_json_schema_output

Whether the model supports JSON schema output. Default: False.

This is also referred to as ‘native’ support for structured output. Relates to the NativeOutput output type.

Type: bool

supports_json_object_output

Whether the model supports a dedicated mode to enforce JSON output, without necessarily sending a schema. Default: False.

E.g. OpenAI’s JSON mode Relates to the PromptedOutput output type.

Type: bool

supports_image_output

Whether the model supports image output. Default: False.

Type: bool

supports_audio_input

Whether the model supports audio in user messages. Default: False.

Used when converting SpeechParts from realtime session history in Model.prepare_messages: if True, retained audio is sent to the model as BinaryContent; otherwise the transcript text is used.

No shipping profile sets this to True yet, so retained realtime audio is currently always forwarded as transcript text on handoff; enabling it needs per-model-family verification that the provider accepts audio in user messages.

Type: bool

supports_image_input

Whether the model accepts images. Default: True.

The file-input flags (this one, supports_document_input, supported_document_media_types, supports_audio_file_input and supports_video_input) say which files can be sent to the model. A model adapter turns off what its message mapping cannot send, whatever the profile says.

A file a tool returns that the model cannot take is replaced, in the request only, by a note naming it: see Where a returned file is sent. A file in a user prompt is sent as it is, and the request fails if the model cannot take it. FileUnderstanding reads the same flags to describe such files in text instead.

Shipped profiles turn these off where an adapter or a model family is known not to take the file; True means nothing says otherwise, not that the model was checked. A text-only model served through an adapter that sends images (a text-only Llama on vLLM, say) needs profile=ModelProfile(supports_image_input=False).

Type: bool

supports_document_input

Whether the model accepts documents, such as PDFs. Default: True.

Which document media types it accepts is narrowed by supported_document_media_types. See supports_image_input for how the file-input flags are used.

Type: bool

supported_document_media_types

The document media types the model accepts, when it accepts documents at all. Default: every media type in DocumentMediaType.

Only narrows those known types: a document with any other media type is judged by supports_document_input alone. Anthropic, for example, reads PDFs and plain text but not Word or Excel files. See supports_image_input for how the file-input flags are used.

Type: frozenset[DocumentMediaType]

supports_audio_file_input

Whether the model accepts audio files, such as an AudioUrl or audio BinaryContent. Default: True.

Separate from supports_audio_input, which is about realtime speech history. See supports_image_input for how the file-input flags are used.

Type: bool

supports_video_input

Whether the model accepts video. Default: True.

See supports_image_input for how the file-input flags are used.

Type: bool

supports_inline_system_prompts

Whether the provider’s API accepts SystemPromptParts inline at any position. Default: False.

When False, non-leading SystemPromptParts are wrapped as UserPromptParts with <system>...</system> content in Model.prepare_messages. Leading ones still hoist to the provider’s top-level system parameter.

APIs that only accept an inline system prompt in certain positions (e.g. Anthropic requires it to follow a user turn) still set this to True; it’s on their model adapters to make the positions the API rejects legal. Preserving the part’s authority is worth more than preserving the exact position it was authored at — an instruction only governs the generation that follows it, and that’s the same generation either way — so prefer adjusting placement over falling back to the <system>...</system> rendering, which the model reads as user-authored. Anthropic slides the entry past intervening user turns and gives it a minimal user turn to follow when nothing legal precedes it.

Provider.model_profile is resolved from the model name alone, so when support also turns on something it can’t see — which SDK client the provider was built with, say — the adapter narrows this in its own Model.profile override, as Anthropic does for Microsoft Foundry. Narrowing the flag rather than special-casing the adapter’s own rendering keeps Model.prepare_messages the only place that knows the <system>...</system> fallback.

Type: bool

supports_request_scoped_system_prompts

Whether the provider’s API keeps a request-scoped SystemPromptPart in the conversation and stops rendering it itself. Default: False.

A request-scoped part applies to one model request. When this is True, the model adapter sends every such part in the history, and the API stops rendering each one once a later request exists, as Anthropic’s clear_at: 'next_user_message' does. Sending it unchanged keeps the cached prefix and any reasoning bound to it valid. When False, Model.prepare_messages leaves a request-scoped part out of every request after its own, and the part renders like any other mid-conversation SystemPromptPart while it’s current.

Only meaningful together with supports_inline_system_prompts, and only set it on a model whose adapter renders a self-clearing entry, currently AnthropicModel. On any other adapter, True makes every request-scoped part render as a lasting system prompt on every later request.

Type: bool

default_structured_output_mode

The default structured output mode to use for the model. Default: 'tool'.

Type: StructuredOutputMode

prompted_output_template

The instructions template to use for prompted structured output. The {schema} placeholder will be replaced with the JSON schema for the output. Default: DEFAULT_PROMPTED_OUTPUT_TEMPLATE.

Type: str

native_output_requires_schema_in_instructions

Whether to add prompted output template in native structured output mode. Default: False.

Type: bool

json_schema_transformer

The transformer to use to make JSON schemas for tools and structured output compatible with the model. Default: None.

Type: type[JsonSchemaTransformer] | None

supports_cache

Whether prompt caching on this model needs request-side configuration that Pydantic AI can turn on. Default: False.

When True, the unified cache setting translates into the provider’s caching configuration. When False, it is silently ignored: the model either doesn’t support prompt caching, or caches implicitly without any request-side opt-in (as OpenAI’s models before GPT-5.6 and Gemini do), so there is nothing for the setting to turn on. Set by providers rather than shared profile functions, since cache behavior is a fact about the provider’s API, not the model family.

Type: bool

supports_auto_cache

Whether the provider has a server-managed automatic prompt-caching mode the request can switch on. Default: False.

The unified cache setting consults it only when supports_cache is True: then, if this is True, the conversation is cached with that mode, which places and moves the cache breakpoint itself; otherwise the library places an explicit breakpoint at the end of the conversation too, as it does at the end of the tool definitions and the static instructions.

On Anthropic, the explicit anthropic_cache setting consults it whatever supports_cache says: if this is True, the request carries the top-level cache_control parameter; otherwise anthropic_cache falls back to a breakpoint on the last block.

Type: bool

supported_cache_retentions

The prompt-cache retention tiers the request can ask for and the provider honors, shortest first. Default: ().

A retention requested via the unified cache setting snaps down to the nearest supported tier. Empty when the provider has no retention tier to request, in which case a requested retention caches at the provider’s default retention (default_cache_retention). prompt_cache_outlook likewise only lets a CachePoint.ttl in the history extend the expected retention when it’s one of these tiers, since a provider that doesn’t support a tier doesn’t honor it on a cache point either.

Type: tuple[CacheRetention, …]

default_cache_retention

How long the provider keeps a cached prompt prefix when the request doesn’t ask for a specific retention. Default: None (unknown).

Measured from the last request that used the prefix. Only documented values are populated. When a provider documents a range, the higher end is used: consumers of a 'cold' outlook are about to pay for a full prefix re-write, so a false 'cold' sacrifices a live cache hit while a false 'warm' merely defers maintenance. Because retention is provider infrastructure, providers populate this field; model-family profile functions must never set it. Providers without an honest documented expectation boundary leave it None.

Retention requested through model settings, such as anthropic_cache='1h' or openai_prompt_cache_retention='24h', is resolved by Model.resolve_cache_retention; this field is what applies when the settings request nothing.

Consumed by prompt_cache_outlook to classify, from a message history alone, whether the next request is likely to hit a warm cache. A 'cold' outlook is a free moment to run history-mutating maintenance (compaction, pruning, repair): the next request pays a full prefix re-write either way, so the marginal cache cost of the mutation is ~zero.

Type: timedelta | None

supports_thinking

Whether the model supports thinking/reasoning configuration. Default: False.

When False, the unified thinking setting in ModelSettings is silently ignored.

Type: bool

thinking_always_enabled

Whether the model always uses thinking/reasoning (e.g., OpenAI o-series, DeepSeek R1). Default: False.

When True, the model can’t disable thinking, so thinking=False uses the lowest effort the model accepts instead. Implies supports_thinking=True.

Type: bool

thinking_enabled_by_default

Whether the model thinks when the request doesn’t configure thinking. Default: False.

True for models that think unless told not to, such as Claude Opus 5, DeepSeek V4 and the OpenAI o-series. Pydantic AI uses it to tell whether a request without a thinking setting will think, for example to decide whether a tool call can be forced. Unlike thinking_always_enabled, it doesn’t mean thinking can’t be turned off.

Type: bool

supports_forced_tool_choice

Whether the model accepts a forced tool choice: tool_choice='required' or a specific tool. Default: True.

Some models reject forcing on every request, such as Claude Opus 5.5, Claude Fable 5.1 and Claude Mythos 5.1, as do some OpenAI-compatible providers, such as Moonshot AI. When False, a forced tool choice that Pydantic AI resolved itself (such as an output tool’s) falls back to 'auto', with the tools filtered to the requested ones where the API can’t restrict the choice. An explicit forcing tool_choice raises a UserError.

Type: bool

supports_forced_tool_choice_with_thinking

Whether the model accepts a forced tool choice while it thinks. Default: True.

DeepSeek’s V4 models, for example, only accept forcing while thinking is off. When False and the request thinks, a forced tool choice is handled as if supports_forced_tool_choice were False. Whether the request thinks accounts for thinking_enabled_by_default.

Type: bool

forced_tool_choice_disables_thinking

Whether the model answers a forced tool choice without thinking. Default: False.

Claude models accept a forced tool choice alongside adaptive thinking, but return no thinking for that request. When True and the request thinks, Pydantic AI doesn’t force a tool choice it resolved itself (such as an output tool’s): it falls back to 'auto', and a structured output_type defaults to Native Output where the model supports it. An explicit forcing tool_choice is still sent.

Type: bool

thinking_tags

The tags used to indicate thinking parts in the model’s output. Default: DEFAULT_THINKING_TAGS.

Type: tuple[str, str]

mimics_assistant_message_formatting

Whether the model reproduces markup it finds in prior assistant messages in its own visible output. Default: False.

A model with this quirk reads the assistant turns of the history as examples of how it is supposed to write, so replaying another model’s reasoning as assistant text teaches it to emit those tags in the answers the user reads — and once that answer is persisted, the next turn reinforces it.

When True, a ThinkingPart that can’t be sent through the model’s native reasoning channel is rendered into a user message ahead of the assistant turn instead of into the turn itself, which stops the imitation. Attributed to the user, that reasoning would read as something the user thought, so it is wrapped in an assistant_thinking tag rather than in the model’s own thinking tags. The cost is that the model reports the reasoning’s authorship inaccurately when asked about it, which naming the source in the tag would not change.

This is currently only used by AnthropicModel and BedrockConverseModel. Claude served through an OpenAI-compatible provider goes through OpenAIChatModel, which does not read this key — tracked in https://github.com/pydantic/pydantic-ai/issues/7419.

Where the carried reasoning goes is each adapter’s own decision rather than a shared rewrite ahead of them: it has to land in front of whatever that provider renders next out of the same request — an Anthropic system entry, for one — which has no message-level existence to insert before.

Type: bool

ignore_streamed_leading_whitespace

Whether to ignore leading whitespace when streaming a response. Default: False.

This is a workaround for models that emit `<think> </think>

or an empty text part ahead of tool calls (e.g. Ollama + Qwen3), which we don't want to end up treating as a final result when usingrun_streamwithstra validoutput_type`.

This is currently only used by OpenAIChatModel, HuggingFaceModel, GroqModel, and BedrockConverseModel.

Type: bool

supported_native_tools

The set of native tool types that this model/profile supports. Default: SUPPORTED_NATIVE_TOOLS (all).

Type: frozenset[type[AbstractNativeTool]]

context_window

The maximum number of tokens the model can handle in a single request, input and output combined. Default: None (unknown).

When no profile layer sets this, Model.profile fills it in from genai-prices data if the model is known there. Set it explicitly for custom or local models, e.g. profile={'context_window': 128_000}.

Type: int | None

tool_call_id_length

The exact length of the ASCII-alphanumeric tool call IDs the model accepts. Default: None (any ID).

Some models only accept tool call IDs of exactly this many a-z, A-Z and 0-9 characters, and reject a history whose IDs came from another provider (OpenAI’s call_..., Anthropic’s toolu_...) or were generated by Pydantic AI (pyd_ai_...). When set, Model.prepare_messages replaces every function tool call ID that doesn’t fit with a hash of it that does, and leaves IDs that already fit unchanged. The calls, their results and the <tool_result> tags that reference them keep pairing up, and since the replacement depends only on the history, the same history is sent as the same bytes on every request. Only the outgoing copy changes: stored message history keeps the original IDs.

Type: int | None

tool_deferral_mode

When the provider permits a tools entry whose schema is withheld. Default: None.

'standalone' permits the deferral flag on its own. 'with_tool_search' permits it only when a tool-search tool is present in the same request. None means hidden tools can only be withheld from the wire. Unsupported deferral is handled on a best-effort basis by withholding the tool.

Type: ToolDeferralMode | None

tool_addition_mode

How the model natively expresses tools added mid-conversation. Default: None.

'by_reference' reveals a tool already declared in the request’s tool definitions (Anthropic tool_addition blocks referencing a defer_loading entry); 'with_definitions' carries the full newly available definitions in the reveal (OpenAI Responses additional_tools items). None means no native channel: Model.prepare_messages projects the change into messages. Additions only — tool removal (#6985) is not modeled yet and will get its own field.

Type: ToolAdditionMode | None

tool_additions

Deprecated: use tool_addition_mode instead.

Translated (with a deprecation warning) whenever profiles are merged; an explicit tool_addition_mode in the same profile wins.

Type: ToolAdditionMode | None

Deprecated: use tool_deferral_mode instead.

True translates to tool_deferral_mode='with_tool_search' (with a deprecation warning) whenever profiles are merged. False carried no signal on its own — deferral capability came from native tool-search support — so it is dropped; an explicit tool_deferral_mode in the same profile wins.

Type: bool

JsonSchemaTransformer

Bases: ABC

Walks a JSON schema, applying transformations to it at each level.

The transformer is called during a model’s prepare_request() step to build the JSON schema before it is sent to the model provider.

Note: We may eventually want to rework tools to build the JSON schema from the type directly, using a subclass of pydantic.json_schema.GenerateJsonSchema, rather than making use of this machinery.

Attributes

strict

The strict parameter forces the conversion of the original JSON schema (self.schema) of a ToolDefinition or OutputObjectDefinition to a format supported by the model provider.

The “strict mode” offered by model providers ensures that the model’s output adheres closely to the defined schema. However, not all model providers offer it, and their support for various schema features may differ. For example, a model provider’s required schema may not support certain validation constraints like minLength or pattern.

Default: strict

is_strict_compatible

Whether the schema is compatible with strict mode.

This value is used to set ToolDefinition.strict or OutputObjectDefinition.strict when their values are None.

Default: True

Methods

transform

@abstractmethod

def transform(schema: JsonSchema) -> JsonSchema

Make changes to the schema.

Returns

JsonSchema

InlineDefsJsonSchemaTransformer

Bases: JsonSchemaTransformer

Transforms the JSON Schema to inline $defs, and $refs that point elsewhere in the schema like #/properties/from.

Object keywords (properties, additionalProperties, patternProperties, propertyNames) are only walked when type is 'object', and array keywords (items, prefixItems, contains) only when it is 'array'. On a schema with no type, or with a type list such as ['object', 'null'], they are left as written, so a $ref inside them is not inlined and can point at a definition the output no longer contains. If the schema is only meant to accept objects (or arrays), set type to 'object' (or 'array') to have it inlined; for a nullable one, put it in an anyOf with {'type': 'null'} instead of using a type list.

merge_profile

def merge_profile(
    base: ModelProfile | None,
    *overrides: ModelProfile | None,
) -> ModelProfile

Merge profiles via dict-spread. Later arguments override earlier ones; None is treated as empty.

This is the canonical way to layer profiles in providers and tests; replaces the old ModelProfile.update() method. Deprecated key spellings are translated per input before spreading, so a legacy key in an override still overrides the base.

Returns

ModelProfile

prompt_cache_outlook

def prompt_cache_outlook(
    messages: Sequence[ModelMessage],
    *,
    profile: ModelProfile | None = None,
    retention: timedelta | None = None,
    now: datetime | None = None,
) -> PromptCacheOutlook

Predict whether the provider’s prompt cache is still warm for the next request on this history.

This is a pure function of the message history and an expectation boundary — it holds no state and makes no requests, so a history processor, capability, or plain application code can call it with just a message history to decide whether the next turn is a cheap moment for history-mutating maintenance (compaction, pruning, repair). When the outlook is 'cold' the next request pays a full prefix re-write anyway, so the marginal cache cost of mutating history right now is ~zero.

The prediction compares the most recent ModelResponse.timestamp in messages against now: an idle gap within the retention window is 'warm', a larger gap is 'cold'. Responses are the anchor because they mark the provider’s last confirmed use of the cache — a request that has no response after it (like the just-appended request a history processor sees, which hasn’t been sent yet) never touched the cache, so its timestamp must not reset the idle clock.

Cache points in the history extend whichever boundary applies to their largest TTL that the provider honors: on a profile with supports_cache, one of its supported_cache_retentions. Without a profile, or with one that doesn’t describe its cache configuration, every cache point TTL is assumed to be honored.

Returns

PromptCacheOutlook — 'warm', 'cold', or 'unknown' (see PromptCacheOutlook).

Parameters

messages : Sequence[ModelMessage]

The message history the next request would be built on, oldest first.

profile : ModelProfile | None Default: None

The model profile whose default_cache_retention is used as the expectation boundary when retention is None.

retention : timedelta | None Default: None

The retention requested for these requests, replacing the profile’s default. With a model and its settings in hand, pass model.resolve_cache_retention(model_settings): it returns None when the settings request nothing, so the profile’s default still applies.

now : datetime | None Default: None

The reference time to measure idleness against. Defaults to the current UTC time; inject a fixed value for deterministic tests.

ModelProfileSpec

Acceptable shapes for the profile= argument on a Model.

  • A ModelProfile dict — a partial profile, merged on top of the provider’s resolved default.
  • A Callable[[ModelProfile], ModelProfile] — receives the provider’s resolved default (with DEFAULT_PROFILE already merged in) and returns the final profile (full control: replace, derive, ignore the default).

Provider classes still expose Provider.model_profile(model_name) (Callable[[str], ModelProfile | None]) — that’s a separate concept used internally by Model.profile to resolve the provider’s default for a given model name.

Type: TypeAlias Default: ModelProfile | Callable[['ModelProfile'], 'ModelProfile']

PromptCacheOutlook

Predicted state of the provider’s prompt cache for the next request built on a message history.

  • 'warm': the last request happened within the provider’s documented expectation boundary, so the cached prefix is likely still available and the next request should hit it.
  • 'cold': the last request happened longer ago than the retention window, so the prefix has likely been evicted and the next request will pay full input price regardless — a free moment to mutate history.
  • 'unknown': there’s no retention figure for the model, or the history has no usable timestamp, so no prediction can be made. Treat like 'warm' for scheduling (never mutate on a guess).

Type: TypeAlias Default: Literal['warm', 'cold', 'unknown']

DEFAULT_PROFILE

Fully populated default ModelProfile. Used as the base layer when resolving a model’s effective profile.

Type: ModelProfile Default: {'supports_tools': True, 'supports_text_output': True, 'supports_tool_return_schema': False, 'supports_json_schema_output': False, 'supports_json_object_output': False, 'supports_image_output': False, 'supports_audio_input': False, 'supports_image_input': True, 'supports_document_input': True, 'supported_document_media_types': KNOWN_DOCUMENT_MEDIA_TYPES, 'supports_audio_file_input': True, 'supports_video_input': True, 'default_structured_output_mode': 'tool', 'prompted_output_template': DEFAULT_PROMPTED_OUTPUT_TEMPLATE, 'native_output_requires_schema_in_instructions': False, 'json_schema_transformer': None, 'supports_cache': False, 'supports_auto_cache': False, 'supported_cache_retentions': (), 'default_cache_retention': None, 'supports_thinking': False, 'thinking_always_enabled': False, 'thinking_enabled_by_default': False, 'supports_forced_tool_choice': True, 'supports_forced_tool_choice_with_thinking': True, 'forced_tool_choice_disables_thinking': False, 'thinking_tags': DEFAULT_THINKING_TAGS, 'mimics_assistant_message_formatting': False, 'ignore_streamed_leading_whitespace': False, 'supported_native_tools': SUPPORTED_NATIVE_TOOLS, 'context_window': None, 'tool_call_id_length': None, 'tool_deferral_mode': None, 'tool_addition_mode': None}

DEFAULT_PROMPTED_OUTPUT_TEMPLATE

Default instructions template for prompted structured output. The {schema} placeholder is replaced with the JSON schema for the output.

Default: dedent("\n Always respond with a JSON object that's compatible with this schema:\n\n {schema}\n\n Don't include any text or Markdown fencing before or after.\n ")

DEFAULT_THINKING_TAGS

Default (start_tag, end_tag) pair for parsing thinking content out of text responses.

Type: tuple[str, str] Default: ('<think>', '</think>')

OpenAIModelProfile

Bases: ModelProfile

Profile for models used with OpenAIChatModel.

ALL FIELDS MUST BE openai_ PREFIXED SO YOU CAN MERGE THEM WITH OTHER MODELS.

Attributes

openai_chat_thinking_field

Non-standard field name used by some providers for model thinking content in Chat Completions API responses. Default: None.

Plenty of providers use custom field names for thinking content. Ollama and newer versions of vLLM use reasoning, while DeepSeek, older vLLM and some others use reasoning_content.

Notice that the thinking field configured here is currently limited to str type content.

If openai_chat_send_back_thinking_parts is set to 'field', this field must be set to a non-None value.

Type: str | None

openai_chat_send_back_thinking_parts

Whether the model includes thinking content in requests. Default: 'auto'.

This can be:

  • 'auto' (default): Automatically detects how to send thinking content. If thinking was received in a custom field (tracked via ThinkingPart.id and ThinkingPart.provider_name), it’s sent back in that same field. Otherwise, it’s sent using tags. Only the reasoning and reasoning_content fields are checked by default when receiving responses. If your provider uses a different field name, you must explicitly set openai_chat_thinking_field to that field name. When the request sends exactly one of these fields back, it’s also sent empty on tool-calling assistant turns after the last user message that don’t already carry it, such as a step served by another model or provider, since DeepSeek-style APIs reject those turns without it.
  • 'tags': The thinking content is included in the main content field, enclosed within thinking tags as specified in thinking_tags profile option.
  • 'field': The thinking content is included in a separate field specified by openai_chat_thinking_field.
  • False: No thinking content is sent in the request.

Defaults to 'auto' to ensure thinking is sent back in the format expected by the model/provider.

Type: Literal[‘auto’, ‘tags’, ‘field’, False]

openai_chat_thinking_requires_field_on_tool_calls

Whether the API rejects a thinking request whose tool-calling assistant turn lacks openai_chat_thinking_field. Default: False.

Only applies when openai_chat_send_back_thinking_parts is 'field'. In that mode, a tool-calling assistant turn that lacks the field gets it empty once the history holds a ThinkingPart. Set this for an API that also rejects such a turn before any response has thought, such as a step served without thinking: the field is then sent empty on every such turn whenever thinking is on for the request, through a setting or thinking_enabled_by_default.

Type: bool

openai_supports_strict_tool_definition

This can be set by a provider or user if the OpenAI-”compatible” API doesn’t support strict tool definitions. Default: True.

Type: bool

openai_unsupported_model_settings

A list of model settings that are not supported by this model. Default: ().

Type: Sequence[str]

openai_supports_tool_choice_required

Deprecated: use supports_forced_tool_choice instead.

Translated (with a deprecation warning) whenever profiles are merged.

Type: bool

openai_supports_forced_tool_choice_with_thinking

Deprecated: use supports_forced_tool_choice_with_thinking instead.

Translated (with a deprecation warning) whenever profiles are merged.

Type: bool

openai_system_prompt_role

The role to use for the system prompt message. If not provided, defaults to 'system'.

Type: OpenAISystemPromptRole | None

openai_chat_supports_multiple_system_messages

Whether the Chat Completions API accepts more than one system-role message at the start of the conversation. Default: True.

OpenAI itself and most compatible providers accept multiple system messages, so this defaults to True. Set to False for strict OpenAI-compatible backends (e.g. some LiteLLM/vLLM deployments) that require exactly one initial system message; consecutive system messages at the start will be merged into one (joined with two newlines) before being sent.

Type: bool

openai_chat_streaming_requires_finish_reason

Whether a streamed Chat Completions response must include a non-null finish_reason. Default: False.

When enabled, reaching clean EOF before any chunk supplies a finish_reason raises ModelAPIError, instead of treating the response as a 'stop'. This defaults to False because OpenAI-compatible APIs do not consistently guarantee the field.

Type: bool

Whether the model supports web search in Chat Completions API. Default: False.

Type: bool

openai_chat_audio_input_encoding

The encoding to use for audio input in Chat Completions requests. Default: 'base64'.

  • 'base64': Raw base64 encoded string. (Default, used by OpenAI)
  • 'uri': Data URI (e.g. data:audio/wav;base64,...).

Type: Literal[‘base64’, ‘uri’]

openai_chat_supports_file_urls

Whether the Chat API supports file URLs directly in the file_data field. Default: False.

OpenAI’s native Chat API only supports base64-encoded data, but some providers like OpenRouter support passing URLs directly.

Type: bool

openai_supports_encrypted_reasoning_content

Whether the model supports including encrypted reasoning content in the response. Default: False.

Type: bool

openai_supports_reasoning

Whether the model supports reasoning (o-series, GPT-5+). Default: False.

When True, sampling parameters may need to be dropped depending on reasoning_effort setting.

Type: bool

openai_reasoning_enabled_by_default

Deprecated: use thinking_enabled_by_default instead.

Translated (with a deprecation warning) whenever profiles are merged.

Type: bool

openai_supports_reasoning_effort_none

Whether the model accepts reasoning_effort='none' and allows sampling parameters (temperature, top_p, etc.) while reasoning is off. Default: False.

The GPT-5.1+ mainline models support turning reasoning off via effort='none', and sampling params are accepted in that mode. When reasoning is enabled (low/medium/high/xhigh), sampling params are not supported. Whether the model reasons by default is tracked separately by thinking_enabled_by_default.

Ignored when openai_reasoning_efforts is set: 'none' in that set decides it instead.

Type: bool

openai_supports_minimal_reasoning_effort

Whether the model accepts reasoning_effort='minimal'. Default: True.

Disabled for GPT-5.6 models, whose documented reasoning efforts exclude minimal. When disabled, unified thinking='minimal' falls back to reasoning_effort='low'. See https://developers.openai.com/api/docs/guides/latest-model. Explicit openai_reasoning_effort='minimal' settings are still passed through unchanged.

Ignored when openai_reasoning_efforts is set.

Type: bool

openai_reasoning_efforts

The reasoning_effort values the model accepts. Default: unset.

When set, the unified thinking setting resolves to the closest accepted effort: the lowest one at or above the requested level, else the highest. thinking=False sends 'none' where it’s accepted, and otherwise the lowest accepted effort, so a model that always reasons (thinking_always_enabled) thinks as little as it can. An empty set means the model has no effort knob, so the unified setting sends nothing.

When set, it also decides whether the model takes 'none' and 'minimal', in place of openai_supports_reasoning_effort_none and openai_supports_minimal_reasoning_effort. When unset, unified levels map one-to-one (see openai_supports_minimal_reasoning_effort), and thinking=False sends nothing on a model that always reasons. Explicit openai_reasoning_effort settings are passed through unchanged.

Type: frozenset[OpenAIReasoningEffort]

openai_chat_supports_max_reasoning_effort

Whether the Chat Completions API accepts reasoning_effort='max' for this model. Default: False.

When enabled, unified thinking='max' is sent as reasoning_effort='max'; otherwise it falls back to 'xhigh'. OpenAI’s own Chat Completions API rejects 'max' on every model, so only providers that accept it set this (DeepSeek V4, and Kimi K3, K2.6 and K2.7). Explicit openai_reasoning_effort='max' settings are still passed through unchanged.

Type: bool

openai_chat_thinking_false_body

Request body fields that unified thinking=False sends on the Chat Completions API, in place of reasoning_effort. Default: unset.

For providers whose off switch isn’t reasoning_effort='none', or whose lowest effort on a model that always reasons isn’t known from openai_reasoning_efforts. MoonshotAIProvider sets it to {'thinking': {'type': 'disabled'}} on Kimi K2.5 and K2.6, and to {'reasoning_effort': 'low'} on Kimi K3, which always reasons. The fields go out through extra_body, where keys the caller set in extra_body win, and nothing is added when openai_reasoning_effort is set explicitly.

Type: dict[str, Any]

openai_responses_supports_max_reasoning_effort

Whether the Responses API accepts reasoning.effort='max' for this model. Default: False.

OpenAIProvider enables it for the GPT-5.6 and GPT-6 families. openai_model_profile leaves it off, because max is unverified on the OpenAI-compatible endpoints that share that profile, and so do the Codex and ChatGPT plan usage profiles. When enabled, unified thinking='max' is sent as reasoning.effort='max'; otherwise it falls back to 'xhigh'. Explicit openai_reasoning_effort='max' settings are still passed through unchanged.

Type: bool

openai_responses_supports_reasoning_mode

Whether the Responses API supports reasoning.mode ('standard' | 'pro') for this model. Default: False.

Currently only supported by the GPT-5.6 family.

Type: bool

openai_responses_supports_reasoning_context

Whether the Responses API accepts reasoning.context='all_turns' for this model. Default: False.

auto and current_turn are accepted by every reasoning model, so they are gated on openai_supports_reasoning instead; only all_turns requires this flag.

Currently supported by the GPT-5.4, GPT-5.5, and GPT-5.6 families.

Type: bool

openai_responses_requires_function_call_status_none

Whether the Responses API requires the status field on function tool calls to be None. Default: False.

This is required by vLLM Responses API versions before https://github.com/vllm-project/vllm/pull/26706. See https://github.com/pydantic/pydantic-ai/issues/3245 for more details.

Type: bool

openai_responses_supports_json_schema_output

Whether the Responses API accepts text.format of type json_schema for this model. Default: False.

Only needed when the Responses API is more capable than Chat Completions for the same model, as with DeepSeek, whose Chat Completions endpoint rejects response_format of type json_schema with This response_format type is unavailable now while its Responses endpoint honors the schema. When set, OpenAIResponsesModel enables supports_json_schema_output on its resolved profile, so NativeOutput becomes available on the Responses API alone.

Type: bool

openai_responses_tool_call_ids_are_response_scoped

Whether Responses API tool call IDs are only unique within one response. Default: False.

When enabled, response IDs are incorporated into tool call IDs as responses are ingested so normalized message history keeps the history-wide uniqueness required by Pydantic AI. The qualified response_id:tool_call_id form is restored to the original provider tool call ID when history is replayed.

Type: bool

openai_responses_supports_interleaved_function_calls

Whether the Responses API accepts function calls interleaved with other assistant items in one assistant turn. Default: True.

DeepSeek’s Responses endpoint merges each function call into the assistant message next to it, so an assistant item between two calls splits them into separate messages that each carry an unanswered call (#7430). When this is False, such a turn has its calls moved to the end before the request goes out; the message history you hold is unchanged.

Reordering is best-effort: a turn carrying a native or compaction item, or a call still waiting on its result, is sent in its original order.

Type: bool

openai_supports_phase

Whether the Responses API supports the phase field on assistant messages. Default: False.

phase labels an assistant message as intermediate commentary or the final_answer. When the model supports it, OpenAI recommends preserving and sending it back unchanged on every assistant message in follow-up requests; dropping it can cause preambles to be interpreted as final answers and degrade behavior in long-running or tool-heavy flows.

Supported by gpt-5.3-codex, gpt-5.4 and later mainline models. The official OpenAI Responses API silently ignores the field on older models, but defaults to False so we don’t risk sending an unrecognized field to OpenAI-compatible APIs (vLLM, Bifrost, …) that haven’t been verified to accept it.

Type: bool

openai_chat_supports_document_input

Whether the Chat Completions API supports document content parts (type='file'). Default: True.

Some OpenAI-compatible providers (e.g. Azure) do not support document input via the Chat Completions API.

Type: bool

openai_chat_supports_max_completion_tokens

Whether the Chat Completions API accepts the max_completion_tokens field for the max_tokens setting. Default: True.

OpenAI itself (including the o-series reasoning models) uses max_completion_tokens, the field that caps visible output plus reasoning tokens, so this defaults to True. Many OpenAI-compatible providers (e.g. OpenRouter) only accept the older max_tokens field; set this to False for those so the max_tokens setting is sent as max_tokens instead.

Type: bool

openai_supports_prompt_cache_breakpoints

Whether the model supports OpenAI explicit prompt cache breakpoints. Default: False.

When enabled, CachePoint markers are translated into prompt_cache_breakpoint fields on the preceding content block, on both the Chat Completions and Responses APIs. When disabled, CachePoint markers are filtered out.

Type: bool

openai_responses_tool_choice_accepts_additional_tools

Whether the Responses API accepts a tool_choice naming a tool that only an additional_tools input item declares. Default: False.

Set by OpenAIProvider for GPT-5.6 and later models, which accept it by name and in allowed_tools. Earlier models reject it with Tool choice '<name>' not found in 'tools' parameter., so when a request’s tool_choice names a tool revealed that way, OpenAIResponsesModel declares it in tools for that request instead, which changes that request’s cached prompt prefix. A request with openai_previous_response_id or openai_conversation_id set keeps the tool in the item alone, so earlier models still reject it: declaring it in tools would leave the item out of the stored response and hide the tool from every request that continues it.

Type: bool

openai_responses_supports_prompt_cache_diagnostics

Whether the Responses endpoint serves prompt cache diagnostics for this model. Default: False.

When True, requests pass the most recent response from the same provider as prompt_cache_options.comparison_response_id, unless the openai_prompt_cache_diagnostics setting is False. Set by OpenAIProvider for GPT-5.6 and later models; OpenAI-compatible Responses endpoints may reject the field (OpenRouter does), so it stays off for them.

Type: bool

openai_responses_requires_streaming

Whether the Responses endpoint serves streaming responses only. Default: False.

When True, nominally non-streaming requests are sent with stream=True and aggregated from the terminal response.completed event, so callers see an ordinary non-streaming ModelResponse. Set for subscription-auth endpoints (e.g. OpenAI Codex) that reject stream=False.

Type: bool

openai_responses_requires_store_false

Whether the Responses endpoint requires store=false on every request. Default: False.

When True, store=false is sent even when no openai_store setting is given (the field cannot be omitted), and an explicit openai_store=True setting is silently overridden, consistent with how other backend-rejected settings are dropped.

Type: bool

openai_supports_input_token_counting

Whether the provider exposes server-side input-token counting (responses/input_tokens). Default: True.

When False, count_tokens() raises a UserError instead of calling a missing endpoint.

Type: bool

OpenAIJsonSchemaTransformer

Bases: JsonSchemaTransformer

Recursively handle the schema to make it compatible with OpenAI strict mode.

See https://platform.openai.com/docs/guides/function-calling?api-mode=responses#strict-mode for more details, but this basically just requires:

  • additionalProperties must be set to false for each object in the parameters
  • all fields in properties must be marked as required

validate_openai_profile

def validate_openai_profile(profile: ModelProfile) -> None

Validate an OpenAI-compatible profile after resolution. Called from OpenAIChatModel.__init__.

Returns

None

openai_model_profile

def openai_model_profile(model_name: str) -> ModelProfile

Get the model profile for an OpenAI model.

Returns

ModelProfile

is_openai_live_model

def is_openai_live_model(model_name: str) -> bool

Whether a model name belongs to OpenAI’s GPT-Live API rather than its Realtime API.

The two are different protocols on the same provider, so the model name is what picks between them — see OpenAILiveModel.

Returns

bool

openai_live_model_profile

def openai_live_model_profile(model_name: str) -> RealtimeModelProfile

Get the realtime model profile for an OpenAI GPT-Live model.

Live is far more constrained than the Realtime API: it owns turn-taking entirely (no manual turns, no server-side interruption or truncation), speaks rather than writes, takes an image only for its delegated backend to respond to, and seeds from text alone. It also has no end-of-turn frame, so Pydantic AI infers the boundary.

Returns

RealtimeModelProfile

openai_realtime_model_profile

def openai_realtime_model_profile(model_name: str) -> RealtimeModelProfile

Get the realtime model profile for an OpenAI realtime model.

Returns

RealtimeModelProfile

OpenAIReasoningEffort

A reasoning_effort value accepted by the OpenAI API, from least to most reasoning.

Default: Literal['none', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max']

OPENAI_REASONING_EFFORT_MAP

Maps unified thinking values to OpenAI reasoning_effort strings.

'max' maps to 'xhigh' here; OpenAIChatModel and OpenAIResponsesModel send 'max' instead on models whose profile sets openai_chat_supports_max_reasoning_effort or openai_responses_supports_max_reasoning_effort.

Type: dict[ThinkingLevel, OpenAIReasoningEffort] Default: {True: 'medium', False: 'none', 'minimal': 'minimal', 'low': 'low', 'medium': 'medium', 'high': 'high', 'xhigh': 'xhigh', 'max': 'xhigh'}

SAMPLING_PARAMS

Sampling parameter names that are incompatible with reasoning.

These parameters are not supported when reasoning is enabled (reasoning_effort != ‘none’). See https://platform.openai.com/docs/guides/reasoning for details.

Default: ('temperature', 'top_p', 'presence_penalty', 'frequency_penalty', 'logit_bias', 'openai_logprobs', 'openai_top_logprobs')

openai_codex_model_profile

def openai_codex_model_profile(model_name: str) -> ModelProfile

Get the model profile for OpenAI Codex subscription-auth models.

The Codex backend speaks the Responses API with a narrower dialect than the standard OpenAI endpoint: it serves streaming responses only, requires store=false, rejects sampling/tuning request fields (verified live on PR #6433), rejects the explicit prompt caching fields, and does not expose server-side input-token counting.

Returns

ModelProfile

openai_chatgpt_model_profile

def openai_chatgpt_model_profile(model_name: str) -> ModelProfile

Get the model profile for models used through ChatGPT plan usage (Sign in with ChatGPT).

ChatGPT plan usage serves the public Responses API with the narrower dialect OpenAI documents in its preview limitations: requests must stream and set store=false, explicit system messages are rejected in favor of developer ones, sampling, output-limit, and service-tier fields are rejected, only POST /v1/responses is served (so there is no server-side input-token counting), and of the hosted tools only web search is accepted. tool_search is not accepted, so deferred tools fall back to local search.

Returns

ModelProfile

AnthropicModelProfile

Bases: ModelProfile

Profile for models used with AnthropicModel.

ALL FIELDS MUST BE anthropic_ PREFIXED SO YOU CAN MERGE THEM WITH OTHER MODELS.

Attributes

anthropic_supports_fast_speed

Whether the model supports fast inference speed (anthropic_speed='fast'). Default: False.

Currently Claude Opus 4.6, 4.7, 4.8, 5, and 5.5 support fast mode. See the Anthropic docs for the latest list.

Type: bool

anthropic_supports_adaptive_thinking

Whether the model supports adaptive thinking (Sonnet 4.6+, Opus 4.6+, Haiku 5.5). Default: False.

When True, unified thinking translates to {'type': 'adaptive'}. When False, it translates to {'type': 'enabled', 'budget_tokens': N}.

Adaptive thinking, unlike extended thinking, accepts a forced tool_choice, so this also decides whether an explicit forcing tool_choice raises alongside unified thinking.

Type: bool

anthropic_supports_effort

Whether the model supports the effort parameter in output_config (Opus 4.5+, Sonnet 4.6+, Haiku 5.5). Default: False.

When True and the unified thinking level is a string (e.g. ‘high’), it is also mapped to output_config.effort.

Type: bool

anthropic_supports_dynamic_filtering

Whether the model supports Anthropic-managed dynamic filtering for web search/fetch. Default: False.

When enabled, Pydantic AI selects the web_search_20260209 / web_fetch_20260209 tool versions, which let Claude filter web results via code execution before they enter context.

Type: bool

anthropic_supports_xhigh_effort

Whether the model supports the xhigh effort value in output_config. Default: False.

Claude Opus 4.7, 4.8, and 5, Sonnet 5, Fable 5, Mythos 5, and Haiku 5.5 accept xhigh; on other models, unified thinking='xhigh' uses max instead, or high where max isn’t supported either.

Type: bool

anthropic_supports_max_effort

Whether the model supports the max effort value in output_config. Default: True.

Disabled for Claude Opus 4.5, which accepts only low, medium, and high. When disabled, unified thinking='max' falls back to the highest effort the model accepts (high on Claude Opus 4.5), and so does thinking='xhigh' where it would otherwise map to max. Explicit anthropic_effort='max' settings are still passed through unchanged.

Type: bool

anthropic_disallows_budget_thinking

Whether the model rejects budget-based thinking settings. Default: False.

Claude Opus 4.7, 4.8, 5, and Haiku 5.5 require adaptive thinking and return a 400 for {'type': 'enabled', 'budget_tokens': ...}.

Type: bool

anthropic_disallows_sampling_settings

Whether the model rejects sampling settings like temperature and top_p. Default: False.

Claude Opus 4.7, 4.8, 5, and 5.5, plus Haiku 5.5, require these settings to be omitted from request payloads.

Type: bool

anthropic_disallows_top_effort_when_thinking_disabled

Whether the model rejects xhigh/max effort while thinking is explicitly disabled. Default: False.

Claude Opus 5 and Haiku 5.5 cap effort at high when anthropic_thinking={'type': 'disabled'} and return a 400 for xhigh or max, as does Claude Sonnet 5.5 with {'type': 'between_tools'}; Claude Opus 4.8 accepts the same combination. Claude Opus 5.5 can’t disable thinking at any effort level, so the flag doesn’t apply to it.

Type: bool

anthropic_disables_thinking_with_between_tools

Whether thinking=False sends {'type': 'between_tools'} rather than {'type': 'disabled'}. Default: False.

Claude Sonnet 5.5 rejects {'type': 'disabled'} and turns thinking off with {'type': 'between_tools'} instead: the model doesn’t think before responding, and the short updates it writes between tool calls come back as thinking blocks.

Type: bool

anthropic_default_code_execution_tool_version

The Anthropic code execution tool version used when anthropic_code_execution_tool_version='auto'. Default: '20250825'.

Type: AnthropicCodeExecutionToolVersion

anthropic_supported_code_execution_tool_versions

The Anthropic code execution tool versions supported by the model. Default: ('20250825',).

Type: tuple[AnthropicCodeExecutionToolVersion, …]

anthropic_supports_task_budgets

Whether the model supports output_config.task_budget. Default: False.

Anthropic currently documents task budgets as a Claude Opus 4.7 / 4.8 / 5 / 5.5 and Haiku 5.5 beta feature.

Type: bool

anthropic_supports_forced_tool_choice

Deprecated: use supports_forced_tool_choice instead.

Translated (with a deprecation warning) whenever profiles are merged.

Type: bool

anthropic_max_output_tokens

The most output tokens the model can generate in one response, thinking included. Default: None (unknown).

AnthropicModel sends it as max_tokens when the request doesn’t set one, so responses are only cut off at the model’s limit, like on APIs where the output limit is optional.

Type: int | None

anthropic_rejects_max_tokens_beyond_context_window

Whether the model rejects a request whose input plus max_tokens exceeds its context window. Default: False.

Claude models older than Claude Sonnet 4.5 answer such a request with a 400, where later models accept it and stop at the context window. When True, a request that doesn’t set max_tokens gets the lower default of 4096, so a conversation close to the context window still fits. It’s also set for Claude 3 and 3.5, whose maximum output (4,096 or 8,192 tokens) is below the higher default.

Type: bool

anthropic_binds_thinking_blocks

Whether the model binds each thinking block to the conversation prefix that produced it. Default: False.

Claude Fable 5.1, Claude Opus 5.5, Claude Sonnet 5.5, and Claude Haiku 5.5 bind replayed thinking blocks to the conversation prefix. Anthropic documents a 400 after system, tools, or earlier messages change; for accounts created before 31 August 2026, the error requires thinking.block_binding.prefix_mismatch_behavior. When True, Pydantic AI preserves the account’s default behavior on the first request; if Anthropic rejects a stale block, it retries once with thinking.block_binding.prefix_mismatch_behavior='drop_block' and warns after the retry succeeds. See Anthropic’s thinking troubleshooting guide.

Type: bool

resolve_anthropic_effort

def resolve_anthropic_effort(
    level: ThinkingEffort,
    *,
    supports_xhigh: bool,
    supports_max: bool = True,
) -> AnthropicEffort

Resolve a unified thinking effort level to the Anthropic output_config.effort value.

Shared between the direct Anthropic path and any provider that translates to the Anthropic output_config wire shape (e.g. Bedrock Converse for Anthropic models). Keeps ANTHROPIC_THINKING_EFFORT_MAP as the single source of truth for the base mapping, while letting the xhigh and max support decisions live in one place.

Returns

AnthropicEffort

anthropic_model_profile

def anthropic_model_profile(model_name: str) -> ModelProfile | None

Get the model profile for an Anthropic model.

Returns

ModelProfile | None

ANTHROPIC_SAMPLING_PARAMS

The unified sampling settings gated by anthropic_disallows_sampling_settings.

Models whose profile sets that flag reject these settings outright with a 400, so every provider serving them has to drop and warn rather than forward. It lives here rather than beside either model because models/bedrock.py cannot import from models/anthropic.py without pulling the anthropic SDK into the bedrock extra.

Default: ('temperature', 'top_p', 'top_k')

AnthropicCodeExecutionToolVersion

Concrete Anthropic code execution tool version to send for CodeExecutionTool.

Type: TypeAlias Default: Literal['20250825', '20260120']

ANTHROPIC_THINKING_BUDGET_MAP

Maps unified thinking values to Anthropic budget_tokens for non-adaptive models.

Type: dict[ThinkingLevel, int] Default: {True: 10000, 'minimal': 1024, 'low': 2048, 'medium': 10000, 'high': 16384, 'xhigh': 32768, 'max': 32768}

AnthropicEffort

Effort values Anthropic accepts at output_config.effort.

Type: TypeAlias Default: Literal['low', 'medium', 'high', 'xhigh', 'max']

ANTHROPIC_THINKING_EFFORT_MAP

Maps unified thinking effort levels to Anthropic output_config.effort.

xhigh maps to 'max' by default; callers that target a model with anthropic_supports_xhigh_effort should pass supports_xhigh=True to resolve_anthropic_effort to preserve xhigh instead of upshifting, and supports_max=False for a model without anthropic_supports_max_effort, where 'max' falls back to the highest level it accepts.

Type: dict[ThinkingEffort, AnthropicEffort] Default: {'minimal': 'low', 'low': 'low', 'medium': 'medium', 'high': 'high', 'xhigh': 'max', 'max': 'max'}

GoogleModelProfile

Bases: ModelProfile

Profile for models used with GoogleModel.

ALL FIELDS MUST BE google_ PREFIXED SO YOU CAN MERGE THEM WITH OTHER MODELS.

Attributes

google_supports_tool_combination

Whether the model supports combining function declarations with native tools and response_schema. Default: False.

Gemini 3+ supports all tool combinations:

Type: bool

google_supports_server_side_tool_invocations

Whether the model accepts the include_server_side_tool_invocations tool-config field. Default: False.

When enabled, Gemini emits explicit tool_call/tool_response parts for server-side native tools (Google Search, URL Context, File Search) that we round-trip through NativeToolCallPart / NativeToolReturnPart. Pre-Gemini-3 models reject the field with 'Tool call context circulation is not enabled'.

This is a Gemini Developer API (ML Dev) only parameter: the google-genai SDK’s Vertex converter raises ValueError when the field is set, so GoogleModel skips it for Google Cloud (Vertex) even on Gemini 3+ models.

Distinct from google_supports_tool_combination even though both currently flip on for Gemini 3+ — the former gates the SDK request field, the latter gates which combinations of native / function / output tools are allowed in the same request.

Type: bool

google_supported_mime_types_in_tool_returns

MIME types supported in native FunctionResponseDict.parts. Default: (). See https://ai.google.dev/gemini-api/docs/function-calling#multimodal-function-responses

Type: tuple[str, …]

google_supports_thinking_level

Whether the model uses thinking_level (enum: LOW/MEDIUM/HIGH) instead of thinking_budget (int). Default: True.

Gemini 3+ models use thinking_level; older models (e.g. Gemini 2.5) use thinking_budget.

Type: bool

google_supports_minimal_thinking_level

Whether the model accepts thinking_level='MINIMAL'. Default: True.

Derived from google_thinking_levels when that is set. Sparse profiles without the level set fall back to this flag: when disabled, unified thinking='minimal' and thinking=False resolve to thinking_level='LOW'. See https://ai.google.dev/gemini-api/docs/thinking.

Type: bool

google_disables_thinking_with_zero_budget

Whether unified thinking=False turns thinking off with thinking_budget=0 on a model that otherwise takes thinking_level. Default: False.

When disabled, thinking=False on such a model sends its lowest thinking level instead. GoogleModel enables it for gemini-3.1-pro-preview on Vertex AI, which accepts thinking_budget=0 and then doesn’t think, while the Gemini API rejects it for that model (“Budget 0 is invalid. This model only works in thinking mode.”).

Type: bool

google_thinking_levels

Thinking levels the model supports, from Google’s per-model thinking table. Default: unset.

Unset means the full GOOGLE_THINKING_LEVELS scale is assumed. Unified thinking efforts snap to the nearest supported level. See https://ai.google.dev/gemini-api/docs/thinking.

Type: frozenset[GoogleThinkingLevel]

google_supports_strict_tool_definition

Whether the model supports Gemini’s VALIDATED function-calling mode. Default: False.

VALIDATED is Gemini’s equivalent of the cross-provider strict tool flag (like OpenAI/Anthropic strict tool calling): it behaves like AUTO but the API enforces that the model adheres to the declared function schema. Issue reports also observe that it mitigates the function-name hallucination some Gemini models exhibit (an observed effect, not a documented guarantee). When the flag is set, GoogleModel upgrades AUTO to VALIDATED by default (every schema is VALIDATED-compatible — no rewrites), and a caller opts out per tool with ToolDefinition.strict=False. Because Gemini’s mode is request-wide, any function or output tool with strict=False keeps the whole request on AUTO.

See https://ai.google.dev/gemini-api/docs/function-calling#function_calling_config.

Type: bool

google_web_search_billed_per_prompt

Whether Google Search grounding is billed once per grounded prompt rather than per search query. Default: False.

Gemini 2.5 and older bill a request once, however many queries it ran, and only when it returned a web source; Gemini 3+ bills each unique search query. This decides the web_searches count on RequestUsage. See https://ai.google.dev/gemini-api/docs/google-search#pricing.

Type: bool

GoogleJsonSchemaTransformer

Bases: JsonSchemaTransformer

Transforms the JSON Schema from Pydantic to be suitable for Gemini.

Gemini supports a subset of OpenAPI v3.0.3.

GoogleOpenAPISchemaTransformer

Bases: GoogleJsonSchemaTransformer

Transforms the JSON Schema from Pydantic into the OpenAPI v3.0.3 subset Gemini’s Schema accepts.

A function declaration carries its parameters as either parametersJsonSchema (full JSON Schema, which GoogleModel sends) or parameters (an OpenAPI v3.0.3 subset) — the two are mutually exclusive. The Live API only implements parameters, so GoogleRealtimeModel needs this narrower form, where a union is anyOf, an enum is a list of strings, and there are no $refs to resolve.

google_model_profile

def google_model_profile(model_name: str) -> ModelProfile | None

Get the model profile for a Google model.

Returns

ModelProfile | None

google_realtime_model_profile

def google_realtime_model_profile(model_name: str) -> RealtimeModelProfile

Get the realtime model profile for a Gemini Live model.

Returns

RealtimeModelProfile

GoogleThinkingLevel

Native Gemini thinking_level values.

Type: TypeAlias Default: Literal['MINIMAL', 'LOW', 'MEDIUM', 'HIGH']

GOOGLE_THINKING_LEVEL_SCALE

The full thinking-level scale, cheapest first. The resolver’s order map derives from this.

Type: tuple[GoogleThinkingLevel, …] Default: ('MINIMAL', 'LOW', 'MEDIUM', 'HIGH')

GOOGLE_THINKING_LEVELS

The full thinking-level scale as a set.

Type: frozenset[GoogleThinkingLevel] Default: frozenset(GOOGLE_THINKING_LEVEL_SCALE)

meta_model_profile

def meta_model_profile(model_name: str) -> ModelProfile | None

Get the model profile for a Meta model.

Returns

ModelProfile | None

muse_model_profile

def muse_model_profile(model_name: str) -> OpenAIModelProfile | None

Get the model profile for a Meta Muse model, or None if model_name isn’t one.

Returns

OpenAIModelProfile | None

amazon_model_profile

def amazon_model_profile(model_name: str) -> ModelProfile | None

Get the model profile for an Amazon model.

Returns

ModelProfile | None

deepseek_model_profile

def deepseek_model_profile(model_name: str) -> ModelProfile | None

Get the model profile for a DeepSeek model.

Returns

ModelProfile | None

GrokModelProfile

Bases: ModelProfile

Profile for Grok models (used with XaiProvider and various OpenAI-compatible providers).

ALL FIELDS MUST BE grok_ PREFIXED SO YOU CAN MERGE THEM WITH OTHER MODELS.

Attributes

grok_supports_builtin_tools

Whether the model supports builtin tools (web_search, x_search, code_execution, mcp). Default: False.

Type: bool

grok_supports_tool_choice_required

Deprecated: use supports_forced_tool_choice instead.

Translated (with a deprecation warning) whenever profiles are merged.

Type: bool

grok_reasoning_efforts

Native reasoning_effort values supported by the Grok model. Default: empty (frozenset()).

Type: frozenset[GrokReasoningEffort]

grok_default_effort_omitted

Whether a default thinking request omits reasoning_effort so xAI applies its model default. Default: False.

Type: bool

grok_model_profile

def grok_model_profile(model_name: str) -> ModelProfile | None

Get the model profile for a Grok model.

Returns

ModelProfile | None

grok_realtime_model_profile

def grok_realtime_model_profile(model_name: str) -> RealtimeModelProfile

Get the realtime model profile for an xAI Grok Voice model.

Returns

RealtimeModelProfile

GrokReasoningEffort

Native xAI reasoning_effort values.

Type: TypeAlias Default: Literal['none', 'low', 'medium', 'high']

mistral_model_profile

def mistral_model_profile(model_name: str) -> ModelProfile

Get the model profile for a Mistral model.

Returns

ModelProfile

qwen_model_profile

def qwen_model_profile(model_name: str) -> ModelProfile | None

Get the model profile for a Qwen model.

Returns

ModelProfile | None

GroqModelProfile

Bases: ModelProfile

Profile for models used with GroqModel.

ALL FIELDS MUST BE groq_ PREFIXED SO YOU CAN MERGE THEM WITH OTHER MODELS.

Attributes

groq_always_has_web_search_builtin_tool

Whether the model always has the web search built-in tool available. Default: False.

Type: bool

groq_supports_reasoning_disable

Whether thinking=False truly disables reasoning via reasoning_effort='none'. Default: False.

Only the qwen3 family supports this; other Groq reasoning models can at most suppress reasoning output via reasoning_format='hidden' while still reasoning internally.

Type: bool

groq_supports_graded_reasoning_effort

Whether the model accepts graded reasoning_effort values (low/medium/high). Default: False.

Only the gpt-oss family supports this; unified thinking levels map to those values via GROQ_GPT_OSS_REASONING_EFFORT_MAP. The qwen3 family instead only accepts none/default (see groq_supports_reasoning_disable).

Type: bool

groq_model_profile

def groq_model_profile(model_name: str) -> ModelProfile

Get the model profile for a Groq model.

Returns

ModelProfile

GROQ_GPT_OSS_REASONING_EFFORT_MAP

Maps unified thinking effort levels to the graded reasoning_effort values the gpt-oss family accepts.

gpt-oss only accepts low/medium/high (not none/default), so minimal folds into low and xhigh and max into high. thinking=True (bare enable) maps to medium in GroqModel, mirroring the neutral default other providers use. See the Groq docs.

Type: dict[ThinkingEffort, Literal[‘low’, ‘medium’, ‘high’]] Default: {'minimal': 'low', 'low': 'low', 'medium': 'medium', 'high': 'high', 'xhigh': 'high', 'max': 'high'}

ZaiModelProfile

Bases: ModelProfile

Profile for Z.AI (Zhipu AI) GLM models.

Attributes

zai_supports_reasoning_effort

Whether the model accepts a per-request reasoning_effort level (GLM-5.2 and GLM-5.3).

Type: bool

zai_reasoning_effort_mapping

Substitutions applied to the unified thinking effort level before it is sent as reasoning_effort.

Levels not in the mapping are forwarded unchanged. GLM-5.2 accepts all unified levels, so it needs no mapping; GLM-5.3 only accepts low/high/max (per the Z.AI docs and the error message returned when disabling thinking on it), so its other unified levels are mapped.

Type: Mapping[str, str]

zai_model_profile

def zai_model_profile(model_name: str) -> ModelProfile | None

The model profile for ZAI (Zhipu AI) GLM models, matched by Z.AI’s native glm-* ids.

Marks thinking-capable models (glm-5, glm-4.7, glm-4.6, glm-4.5) via supports_thinking=True. This includes the glm-4.6v and glm-4.5v vision models, which also support thinking mode per the Z.AI docs. GLM-5.2 and GLM-5.3 additionally accept a per-request reasoning effort level, flagged via zai_supports_reasoning_effort=True. GLM-5.3 always reasons and cannot disable thinking, flagged via thinking_always_enabled=True.

GLM-5.3 stringifies nested tool arguments whose schema is a $ref into $defs, so its schemas are inlined via InlineDefsJsonSchemaTransformer. See https://github.com/pydantic/pydantic-ai/issues/10243.

The provider-specific request/response shape (e.g. the reasoning_content field used by Z.AI’s API) is configured in ZaiProvider.model_profile() rather than here. Providers that serve GLM models under a different id scheme (e.g. Cerebras’s zai-glm-*, which doesn’t match the glm-* prefixes above) configure thinking support in their own model_profile().

Returns

ModelProfile | None

DecisionModelProfile

Bases: ModelProfile

Profile for a decision model: what the model can be asked.

These are facts about the model behind the URL, not the class that talks to it, so they are set by the provider for the model name, or by profile=. A key left out falls back to the class’s max_choice_options, max_score_levels and requires_instructions.

ALL FIELDS MUST BE decision_ PREFIXED SO YOU CAN MERGE THEM WITH OTHER MODELS.

Attributes

decision_max_choice_options

The most options the model accepts in one pick-one question, or None for no limit.

A pick-one field with more options, or more routes than this on the route question, is a UserError before a request is sent.

Type: int | None

decision_max_score_levels

The most levels the model accepts in one rubric, or None for no limit.

Whole numbers from 0 with more levels than this are not a rubric, so a field of them is asked as a pick-one instead, and counts against decision_max_choice_options.

Type: int | None

decision_requires_instructions

Whether the model refuses a question without instructions.

A pick-one, a rubric, or a yes/no with described answers with nothing else to ask is then sent with a generic question, 'Which of these applies?', instead of no instructions at all.

Type: bool

decision_model_profile

def decision_model_profile(model_name: str) -> ModelProfile

Get the model profile for a decision model.

A decision model answers typed questions about a state; it does not generate text or tool calls. Tool-mode structured output is how a decision model fills an output_type, and it rides on supports_tools, so that stays on. A system prompt anywhere in the history is part of what the model judges, so it needs no wrapping. Image input is declared by the adapter’s supports_image_input, rather than by this profile. The model refuses unsupported input itself.

Returns

ModelProfile