Skip to content

pydantic_ai.profiles

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.

Source code in pydantic_ai_slim/pydantic_ai/profiles/__init__.py
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
@dataclass(kw_only=True)
class ModelProfile:
    """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."""

    # Maps deprecated `__init__` kwarg names to their renamed targets. Subclasses can extend
    # this by declaring their own `_deprecated_kwarg_aliases = {...}`; `__new__` walks the
    # MRO at first instantiation to collect every level's entries.
    _deprecated_kwarg_aliases: ClassVar[dict[str, str]] = {
        'supported_builtin_tools': 'supported_native_tools',
    }

    def __new__(cls, *args: Any, **kwargs: Any) -> Self:
        # Lazy install of deprecated-kwarg aliases on first instantiation of each subclass.
        # `@dataclass` regenerates `__init__` on each subclass, so a base-level wrap is lost.
        # `__new__` runs before `__init__` (via `type.__call__`), so we can wrap the
        # subclass's `__init__` exactly once before the constructor receives the legacy kwarg.
        if cls not in _DEPRECATED_KWARG_ALIASES_INSTALLED:
            _DEPRECATED_KWARG_ALIASES_INSTALLED.add(cls)
            collected: dict[str, str] = {}
            for klass in reversed(cls.__mro__):
                collected.update(getattr(klass, '_deprecated_kwarg_aliases', None) or {})
            for old, new in collected.items():
                install_deprecated_kwarg_alias(cls, old=old, new=new)
        return super().__new__(cls)

    supports_tools: bool = True
    """Whether the model supports tools."""
    supports_tool_return_schema: bool = False
    """Whether the model natively supports tool return schemas.

    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.
    """
    supports_json_schema_output: bool = False
    """Whether the model supports JSON schema output.

    This is also referred to as 'native' support for structured output.
    Relates to the `NativeOutput` output type.
    """
    supports_json_object_output: bool = False
    """Whether the model supports a dedicated mode to enforce JSON output, without necessarily sending a schema.

    E.g. [OpenAI's JSON mode](https://platform.openai.com/docs/guides/structured-outputs#json-mode)
    Relates to the `PromptedOutput` output type.
    """
    supports_image_output: bool = False
    """Whether the model supports image output."""
    supports_inline_system_prompts: bool = False
    """Whether the provider's API accepts `SystemPromptPart`s inline at any position.

    When `False` (default), non-leading `SystemPromptPart`s are wrapped as `UserPromptPart`s with
    `<system>...</system>` content in `Model.prepare_messages`. Leading ones still hoist to the
    provider's top-level system parameter.
    """
    default_structured_output_mode: StructuredOutputMode = 'tool'
    """The default structured output mode to use for the model."""
    prompted_output_template: str = dedent(
        """
        Always respond with a JSON object that's compatible with this schema:

        {schema}

        Don't include any text or Markdown fencing before or after.
        """
    )
    """The instructions template to use for prompted structured output. The '{schema}' placeholder will be replaced with the JSON schema for the output."""
    native_output_requires_schema_in_instructions: bool = False
    """Whether to add prompted output template in native structured output mode"""
    json_schema_transformer: type[JsonSchemaTransformer] | None = None
    """The transformer to use to make JSON schemas for tools and structured output compatible with the model."""

    supports_thinking: bool = False
    """Whether the model supports thinking/reasoning configuration.

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

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

    When True, `thinking=False` is silently ignored since the model cannot disable thinking.
    Implies `supports_thinking=True`.
    """

    thinking_tags: tuple[str, str] = ('<think>', '</think>')
    """The tags used to indicate thinking parts in the model's output. Defaults to ('<think>', '</think>')."""

    ignore_streamed_leading_whitespace: bool = False
    """Whether to ignore leading whitespace when streaming a response.

    This is a workaround for models that emit `<think>\n</think>\n\n` 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 using `run_stream` with `str` a valid `output_type`.

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

    supported_native_tools: frozenset[type[AbstractNativeTool]] = field(default_factory=lambda: SUPPORTED_NATIVE_TOOLS)
    """The set of native tool types that this model/profile supports.

    Defaults to ALL native tools. Profile functions should explicitly
    restrict this based on model capabilities.
    """

    @classmethod
    def from_profile(cls, profile: ModelProfile | None) -> Self:
        """Build a ModelProfile subclass instance from a ModelProfile instance."""
        if isinstance(profile, cls):
            return profile
        return cls().update(profile)

    def update(self, profile: ModelProfile | None) -> Self:
        """Update this ModelProfile (subclass) instance with the non-default values from another ModelProfile instance."""
        if not profile:
            return self
        field_names = set(f.name for f in fields(self))
        non_default_attrs = {
            f.name: getattr(profile, f.name)
            for f in fields(profile)
            if f.name in field_names and getattr(profile, f.name) != f.default
        }
        return replace(self, **non_default_attrs)

    def __getattr__(self, name: str) -> Any:
        # Deprecated alias for read access to a renamed field. Only warns when the renamed
        # target field actually exists on this class — otherwise raise `AttributeError` so
        # genuine typos still surface clearly.
        new_name = _MODEL_PROFILE_DEPRECATED_FIELD_ALIASES.get(name)
        if new_name is not None and new_name in {f.name for f in fields(type(self))}:
            warnings.warn(
                f'`{type(self).__name__}.{name}` is deprecated, use `.{new_name}` instead.',
                PydanticAIDeprecationWarning,
                stacklevel=2,
            )
            return getattr(self, new_name)
        raise AttributeError(name)

supports_tools class-attribute instance-attribute

supports_tools: bool = True

Whether the model supports tools.

supports_tool_return_schema class-attribute instance-attribute

supports_tool_return_schema: bool = False

Whether the model natively supports tool return schemas.

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.

supports_json_schema_output class-attribute instance-attribute

supports_json_schema_output: bool = False

Whether the model supports JSON schema output.

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

supports_json_object_output class-attribute instance-attribute

supports_json_object_output: bool = False

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

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

supports_image_output class-attribute instance-attribute

supports_image_output: bool = False

Whether the model supports image output.

supports_inline_system_prompts class-attribute instance-attribute

supports_inline_system_prompts: bool = False

Whether the provider's API accepts SystemPromptParts inline at any position.

When False (default), 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.

default_structured_output_mode class-attribute instance-attribute

default_structured_output_mode: StructuredOutputMode = (
    "tool"
)

The default structured output mode to use for the model.

prompted_output_template class-attribute instance-attribute

prompted_output_template: str = 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        "
)

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

native_output_requires_schema_in_instructions class-attribute instance-attribute

native_output_requires_schema_in_instructions: bool = False

Whether to add prompted output template in native structured output mode

json_schema_transformer class-attribute instance-attribute

json_schema_transformer: (
    type[JsonSchemaTransformer] | None
) = None

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

supports_thinking class-attribute instance-attribute

supports_thinking: bool = False

Whether the model supports thinking/reasoning configuration.

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

thinking_always_enabled class-attribute instance-attribute

thinking_always_enabled: bool = False

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

When True, thinking=False is silently ignored since the model cannot disable thinking. Implies supports_thinking=True.

thinking_tags class-attribute instance-attribute

thinking_tags: tuple[str, str] = ('<think>', '</think>')

The tags used to indicate thinking parts in the model's output. Defaults to ('', '').

ignore_streamed_leading_whitespace class-attribute instance-attribute

ignore_streamed_leading_whitespace: bool = False

Whether to ignore leading whitespace when streaming a response.

This is a workaround for models that emit `<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`, and `GroqModel`.

supported_native_tools class-attribute instance-attribute

supported_native_tools: frozenset[
    type[AbstractNativeTool]
] = field(default_factory=lambda: SUPPORTED_NATIVE_TOOLS)

The set of native tool types that this model/profile supports.

Defaults to ALL native tools. Profile functions should explicitly restrict this based on model capabilities.

from_profile classmethod

from_profile(profile: ModelProfile | None) -> Self

Build a ModelProfile subclass instance from a ModelProfile instance.

Source code in pydantic_ai_slim/pydantic_ai/profiles/__init__.py
144
145
146
147
148
149
@classmethod
def from_profile(cls, profile: ModelProfile | None) -> Self:
    """Build a ModelProfile subclass instance from a ModelProfile instance."""
    if isinstance(profile, cls):
        return profile
    return cls().update(profile)

update

update(profile: ModelProfile | None) -> Self

Update this ModelProfile (subclass) instance with the non-default values from another ModelProfile instance.

Source code in pydantic_ai_slim/pydantic_ai/profiles/__init__.py
151
152
153
154
155
156
157
158
159
160
161
def update(self, profile: ModelProfile | None) -> Self:
    """Update this ModelProfile (subclass) instance with the non-default values from another ModelProfile instance."""
    if not profile:
        return self
    field_names = set(f.name for f in fields(self))
    non_default_attrs = {
        f.name: getattr(profile, f.name)
        for f in fields(profile)
        if f.name in field_names and getattr(profile, f.name) != f.default
    }
    return replace(self, **non_default_attrs)

OPENAI_REASONING_EFFORT_MAP module-attribute

OPENAI_REASONING_EFFORT_MAP: dict[ThinkingLevel, str] = {
    True: "medium",
    False: "none",
    "minimal": "minimal",
    "low": "low",
    "medium": "medium",
    "high": "high",
    "xhigh": "xhigh",
}

Maps unified thinking values to OpenAI reasoning_effort strings.

SAMPLING_PARAMS module-attribute

SAMPLING_PARAMS = (
    "temperature",
    "top_p",
    "presence_penalty",
    "frequency_penalty",
    "logit_bias",
    "openai_logprobs",
    "openai_top_logprobs",
)

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.

OpenAIModelProfile dataclass

Bases: ModelProfile

Profile for models used with OpenAIChatModel.

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

Source code in pydantic_ai_slim/pydantic_ai/profiles/openai.py
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
@dataclass(kw_only=True)
class OpenAIModelProfile(ModelProfile):
    """Profile for models used with `OpenAIChatModel`.

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

    openai_chat_thinking_field: str | None = None
    """Non-standard field name used by some providers for model thinking content in Chat Completions API responses.

    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."""

    openai_chat_send_back_thinking_parts: Literal['auto', 'tags', 'field', False] = 'auto'
    """Whether the model includes thinking content in requests.

    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.
    * `'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."""

    openai_supports_strict_tool_definition: bool = True
    """This can be set by a provider or user if the OpenAI-"compatible" API doesn't support strict tool definitions."""

    openai_supports_sampling_settings: bool = True
    """Turn off to don't send sampling settings like `temperature` and `top_p` to models that don't support them, like OpenAI's o-series reasoning models."""

    openai_unsupported_model_settings: Sequence[str] = ()
    """A list of model settings that are not supported by this model."""

    # Some OpenAI-compatible providers (e.g. MoonshotAI) currently do **not** accept
    # `tool_choice="required"`.  This flag lets the calling model know whether it's
    # safe to pass that value along.  Default is `True` to preserve existing
    # behaviour for OpenAI itself and most providers.
    openai_supports_tool_choice_required: bool = True
    """Whether the provider accepts the value `tool_choice='required'` in the request payload."""

    openai_system_prompt_role: OpenAISystemPromptRole | None = None
    """The role to use for the system prompt message. If not provided, defaults to `'system'`."""

    supports_inline_system_prompts: bool = True
    """OpenAI's APIs accept `system`/`developer`/`user`-role messages at any position."""

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

    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."""

    openai_chat_supports_web_search: bool = False
    """Whether the model supports web search in Chat Completions API."""

    openai_chat_audio_input_encoding: Literal['base64', 'uri'] = 'base64'
    """The encoding to use for audio input in Chat Completions requests.

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

    openai_chat_supports_file_urls: bool = False
    """Whether the Chat API supports file URLs directly in the `file_data` field.

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

    openai_supports_encrypted_reasoning_content: bool = False
    """Whether the model supports including encrypted reasoning content in the response."""

    openai_supports_reasoning: bool = False
    """Whether the model supports reasoning (o-series, GPT-5+).

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

    openai_supports_reasoning_effort_none: bool = False
    """Whether the model supports sampling parameters (temperature, top_p, etc.) when reasoning_effort='none'.

    Models like GPT-5.1 and GPT-5.2 default to reasoning_effort='none' and support sampling params in that mode.
    When reasoning is enabled (low/medium/high/xhigh), sampling params are not supported."""

    openai_responses_requires_function_call_status_none: bool = False
    """Whether the Responses API requires the `status` field on function tool calls to be `None`.

    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.
    """

    openai_supports_phase: bool = False
    """Whether the Responses API supports the `phase` field on assistant messages.

    `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.
    """

    openai_chat_supports_document_input: bool = True
    """Whether the Chat Completions API supports document content parts (`type='file'`).

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

    def __post_init__(self):  # pragma: no cover
        if not self.openai_supports_sampling_settings:
            warnings.warn(
                'The `openai_supports_sampling_settings` has no effect, and it will be removed in future versions. '
                'Use `openai_unsupported_model_settings` instead.',
                DeprecationWarning,
            )
        if self.openai_chat_send_back_thinking_parts == 'field' and not self.openai_chat_thinking_field:
            raise UserError(
                'If `openai_chat_send_back_thinking_parts` is "field", '
                '`openai_chat_thinking_field` must be set to a non-None value.'
            )

openai_chat_thinking_field class-attribute instance-attribute

openai_chat_thinking_field: str | None = None

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

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.

openai_chat_send_back_thinking_parts class-attribute instance-attribute

openai_chat_send_back_thinking_parts: Literal[
    "auto", "tags", "field", False
] = "auto"

Whether the model includes thinking content in requests.

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. * '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.

openai_supports_strict_tool_definition class-attribute instance-attribute

openai_supports_strict_tool_definition: bool = True

This can be set by a provider or user if the OpenAI-"compatible" API doesn't support strict tool definitions.

openai_supports_sampling_settings class-attribute instance-attribute

openai_supports_sampling_settings: bool = True

Turn off to don't send sampling settings like temperature and top_p to models that don't support them, like OpenAI's o-series reasoning models.

openai_unsupported_model_settings class-attribute instance-attribute

openai_unsupported_model_settings: Sequence[str] = ()

A list of model settings that are not supported by this model.

openai_supports_tool_choice_required class-attribute instance-attribute

openai_supports_tool_choice_required: bool = True

Whether the provider accepts the value tool_choice='required' in the request payload.

openai_system_prompt_role class-attribute instance-attribute

openai_system_prompt_role: OpenAISystemPromptRole | None = (
    None
)

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

supports_inline_system_prompts class-attribute instance-attribute

supports_inline_system_prompts: bool = True

OpenAI's APIs accept system/developer/user-role messages at any position.

openai_chat_supports_multiple_system_messages class-attribute instance-attribute

openai_chat_supports_multiple_system_messages: bool = True

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

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.

openai_chat_supports_web_search: bool = False

Whether the model supports web search in Chat Completions API.

openai_chat_audio_input_encoding class-attribute instance-attribute

openai_chat_audio_input_encoding: Literal[
    "base64", "uri"
] = "base64"

The encoding to use for audio input in Chat Completions requests.

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

openai_chat_supports_file_urls class-attribute instance-attribute

openai_chat_supports_file_urls: bool = False

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

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

openai_supports_encrypted_reasoning_content class-attribute instance-attribute

openai_supports_encrypted_reasoning_content: bool = False

Whether the model supports including encrypted reasoning content in the response.

openai_supports_reasoning class-attribute instance-attribute

openai_supports_reasoning: bool = False

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

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

openai_supports_reasoning_effort_none class-attribute instance-attribute

openai_supports_reasoning_effort_none: bool = False

Whether the model supports sampling parameters (temperature, top_p, etc.) when reasoning_effort='none'.

Models like GPT-5.1 and GPT-5.2 default to reasoning_effort='none' and support sampling params in that mode. When reasoning is enabled (low/medium/high/xhigh), sampling params are not supported.

openai_responses_requires_function_call_status_none class-attribute instance-attribute

openai_responses_requires_function_call_status_none: (
    bool
) = False

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

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.

openai_supports_phase class-attribute instance-attribute

openai_supports_phase: bool = False

Whether the Responses API supports the phase field on assistant messages.

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.

openai_chat_supports_document_input class-attribute instance-attribute

openai_chat_supports_document_input: bool = True

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

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

openai_model_profile

openai_model_profile(model_name: str) -> ModelProfile

Get the model profile for an OpenAI model.

Source code in pydantic_ai_slim/pydantic_ai/profiles/openai.py
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
def openai_model_profile(model_name: str) -> ModelProfile:
    """Get the model profile for an OpenAI model."""
    # GPT-5.1+ models use `reasoning={"effort": "none"}` by default, which allows sampling params.
    is_gpt_5_1_plus = model_name.startswith(('gpt-5.1', 'gpt-5.2', 'gpt-5.3', 'gpt-5.4', 'gpt-5.5'))

    # doesn't support `reasoning={"effort": "none"}` -  default is set at 'medium'
    # see https://platform.openai.com/docs/guides/reasoning
    is_gpt_5 = model_name.startswith('gpt-5') and not is_gpt_5_1_plus

    # `phase` is supported by gpt-5.3-codex, gpt-5.4 and later mainline models.
    # See https://developers.openai.com/api/docs/guides/prompt-guidance.
    supports_phase = model_name.startswith(('gpt-5.3-codex', 'gpt-5.4', 'gpt-5.5'))

    # always reasoning
    is_o_series = model_name.startswith('o')

    # gpt-5.3-chat-latest is non-reasoning unlike other 5.1+ chat variants
    is_gpt_5_3_chat = model_name.startswith('gpt-5.3-chat')

    thinking_always_enabled = is_o_series or (is_gpt_5 and '-chat' not in model_name)

    supports_reasoning = (thinking_always_enabled or is_gpt_5_1_plus) and not is_gpt_5_3_chat

    # The o1-mini model doesn't support the `system` role, so we default to `user`.
    # See https://github.com/pydantic/pydantic-ai/issues/974 for more details.
    openai_system_prompt_role = 'user' if model_name.startswith('o1-mini') else None

    # Check if the model supports web search (only specific search-preview models)
    supports_web_search = '-search-preview' in model_name
    supports_image_output = (
        is_gpt_5 or is_gpt_5_1_plus or 'o3' in model_name or '4.1' in model_name or '4o' in model_name
    )

    # OpenAI's native `tool_search` tool with `defer_loading` is available on
    # GPT-5.4 and later mainline models.
    supports_tool_search = model_name.startswith(('gpt-5.4', 'gpt-5.5'))
    supported_native_tools = _OPENAI_BASE_BUILTINS | {ToolSearchTool} if supports_tool_search else _OPENAI_BASE_BUILTINS

    # Structured Outputs (output mode 'native') is only supported with the gpt-4o-mini, gpt-4o-mini-2024-07-18,
    # and gpt-4o-2024-08-06 model snapshots and later. We leave it in here for all models because the
    # `default_structured_output_mode` is `'tool'`, so `native` is only used when the user specifically uses
    # the `NativeOutput` marker, so an error from the API is acceptable.
    return OpenAIModelProfile(
        json_schema_transformer=OpenAIJsonSchemaTransformer,
        supports_json_schema_output=True,
        supports_json_object_output=True,
        supports_image_output=supports_image_output,
        supports_thinking=supports_reasoning,
        thinking_always_enabled=thinking_always_enabled,
        openai_system_prompt_role=openai_system_prompt_role,
        openai_chat_supports_web_search=supports_web_search,
        openai_supports_encrypted_reasoning_content=supports_reasoning,
        openai_supports_reasoning=supports_reasoning,
        openai_supports_reasoning_effort_none=is_gpt_5_1_plus and not is_gpt_5_3_chat,
        openai_supports_phase=supports_phase,
        supported_native_tools=supported_native_tools,
    )

OpenAIJsonSchemaTransformer dataclass

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

Source code in pydantic_ai_slim/pydantic_ai/profiles/openai.py
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
@dataclass(init=False)
class OpenAIJsonSchemaTransformer(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
    """

    def __init__(self, schema: JsonSchema, *, strict: bool | None = None):
        super().__init__(schema, strict=strict)
        self.root_ref = schema.get('$ref')

    def walk(self) -> JsonSchema:
        # Note: OpenAI does not support anyOf at the root in strict mode
        # However, we don't need to check for it here because we ensure in pydantic_ai._utils.check_object_json_schema
        # that the root schema either has type 'object' or is recursive.
        result = super().walk()

        # For recursive models, we need to tweak the schema to make it compatible with strict mode.
        # Because the following should never change the semantics of the schema we apply it unconditionally.
        if self.root_ref is not None:
            result.pop('$ref', None)  # We replace references to the self.root_ref with just '#' in the transform method
            root_key = re.sub(r'^#/\$defs/', '', self.root_ref)
            result.update(self.defs.get(root_key) or {})

        return result

    def transform(self, schema: JsonSchema) -> JsonSchema:  # noqa: C901
        # Remove unnecessary keys
        schema.pop('title', None)
        schema.pop('$schema', None)
        schema.pop('discriminator', None)

        default = schema.get('default', _sentinel)
        if default is not _sentinel:
            # the "default" keyword is not allowed in strict mode, but including it makes some Ollama models behave
            # better, so we keep it around when not strict
            if self.strict is True:
                schema.pop('default', None)
            elif self.strict is None:  # pragma: no branch
                self.is_strict_compatible = False

        if schema_ref := schema.get('$ref'):
            if schema_ref == self.root_ref:
                schema['$ref'] = '#'
            if len(schema) > 1:
                # OpenAI Strict mode doesn't support siblings to "$ref", but _does_ allow siblings to "anyOf".
                # So if there is a "description" field or any other extra info, we move the "$ref" into an "anyOf":
                schema['anyOf'] = [{'$ref': schema.pop('$ref')}]

        # Track strict-incompatible keys
        incompatible_values: dict[str, Any] = {}
        for key in _STRICT_INCOMPATIBLE_KEYS:
            value = schema.get(key, _sentinel)
            if value is not _sentinel:
                incompatible_values[key] = value
        if format := schema.get('format'):
            if format not in _STRICT_COMPATIBLE_STRING_FORMATS:
                incompatible_values['format'] = format
        pattern = schema.get('pattern')
        if isinstance(pattern, str) and _regex_contains_lookaround(pattern):
            incompatible_values['pattern'] = pattern
        description = schema.get('description')
        if incompatible_values:
            if self.strict is True:
                notes: list[str] = []
                for key, value in incompatible_values.items():
                    schema.pop(key)
                    notes.append(f'{key}={value}')
                notes_string = ', '.join(notes)
                schema['description'] = notes_string if not description else f'{description} ({notes_string})'
            elif self.strict is None:  # pragma: no branch
                self.is_strict_compatible = False

        schema_type = schema.get('type')
        if 'oneOf' in schema:
            # OpenAI does not support oneOf in strict mode
            if self.strict is True:
                schema['anyOf'] = schema.pop('oneOf')
            else:
                self.is_strict_compatible = False

        if schema_type == 'object':
            # Always ensure 'properties' key exists - OpenAI drops objects without it
            if 'properties' not in schema:
                schema['properties'] = dict[str, Any]()

            if self.strict is True:
                # additional properties are disallowed
                schema['additionalProperties'] = False

                # all properties are required
                schema['required'] = list(schema['properties'].keys())

            elif self.strict is None:
                if schema.get('additionalProperties', None) not in (None, False):
                    self.is_strict_compatible = False
                else:
                    # additional properties are disallowed by default
                    schema['additionalProperties'] = False

                if 'properties' not in schema or 'required' not in schema:
                    self.is_strict_compatible = False
                else:
                    required = schema['required']
                    for k in schema['properties'].keys():
                        if k not in required:
                            self.is_strict_compatible = False
        return schema

AnthropicCodeExecutionToolVersion module-attribute

AnthropicCodeExecutionToolVersion: TypeAlias = Literal[
    "20250825", "20260120"
]

Concrete Anthropic code execution tool version to send for CodeExecutionTool.

AnthropicModelProfile dataclass

Bases: ModelProfile

Profile for models used with AnthropicModel.

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

Source code in pydantic_ai_slim/pydantic_ai/profiles/anthropic.py
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
@dataclass(kw_only=True)
class AnthropicModelProfile(ModelProfile):
    """Profile for models used with `AnthropicModel`.

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

    anthropic_supports_fast_speed: bool = False
    """Whether the model supports fast inference speed (`anthropic_speed='fast'`).

    Currently only Claude Opus 4.6 supports fast mode. See the Anthropic docs for the latest list.
    """

    anthropic_supports_adaptive_thinking: bool = False
    """Whether the model supports adaptive thinking (Sonnet 4.6+, Opus 4.6+).

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

    anthropic_supports_effort: bool = False
    """Whether the model supports the `effort` parameter in `output_config` (Opus 4.5+, Sonnet 4.6+).

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

    anthropic_supports_xhigh_effort: bool = False
    """Whether the model supports the `xhigh` effort value in `output_config`.

    Claude Opus 4.7 adds `xhigh`; older Anthropic models should use `max` instead.
    """

    anthropic_disallows_budget_thinking: bool = False
    """Whether the model rejects budget-based thinking settings.

    Claude Opus 4.7+ requires adaptive thinking and returns a 400 for
    `{'type': 'enabled', 'budget_tokens': ...}`.
    """

    anthropic_disallows_sampling_settings: bool = False
    """Whether the model rejects sampling settings like `temperature` and `top_p`.

    Claude Opus 4.7+ requires these settings to be omitted from request payloads.
    """

    anthropic_default_code_execution_tool_version: AnthropicCodeExecutionToolVersion = '20250825'
    """The Anthropic code execution tool version used when `anthropic_code_execution_tool_version='auto'`."""

    anthropic_supported_code_execution_tool_versions: tuple[AnthropicCodeExecutionToolVersion, ...] = ('20250825',)
    """The Anthropic code execution tool versions supported by the model."""

    anthropic_supports_task_budgets: bool = False
    """Whether the model supports `output_config.task_budget`.

    Anthropic currently documents task budgets as a Claude Opus 4.7 beta feature.
    """

anthropic_supports_fast_speed class-attribute instance-attribute

anthropic_supports_fast_speed: bool = False

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

Currently only Claude Opus 4.6 supports fast mode. See the Anthropic docs for the latest list.

anthropic_supports_adaptive_thinking class-attribute instance-attribute

anthropic_supports_adaptive_thinking: bool = False

Whether the model supports adaptive thinking (Sonnet 4.6+, Opus 4.6+).

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

anthropic_supports_effort class-attribute instance-attribute

anthropic_supports_effort: bool = False

Whether the model supports the effort parameter in output_config (Opus 4.5+, Sonnet 4.6+).

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

anthropic_supports_xhigh_effort class-attribute instance-attribute

anthropic_supports_xhigh_effort: bool = False

Whether the model supports the xhigh effort value in output_config.

Claude Opus 4.7 adds xhigh; older Anthropic models should use max instead.

anthropic_disallows_budget_thinking class-attribute instance-attribute

anthropic_disallows_budget_thinking: bool = False

Whether the model rejects budget-based thinking settings.

Claude Opus 4.7+ requires adaptive thinking and returns a 400 for {'type': 'enabled', 'budget_tokens': ...}.

anthropic_disallows_sampling_settings class-attribute instance-attribute

anthropic_disallows_sampling_settings: bool = False

Whether the model rejects sampling settings like temperature and top_p.

Claude Opus 4.7+ requires these settings to be omitted from request payloads.

anthropic_default_code_execution_tool_version class-attribute instance-attribute

anthropic_default_code_execution_tool_version: (
    AnthropicCodeExecutionToolVersion
) = "20250825"

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

anthropic_supported_code_execution_tool_versions class-attribute instance-attribute

anthropic_supported_code_execution_tool_versions: tuple[
    AnthropicCodeExecutionToolVersion, ...
] = ("20250825",)

The Anthropic code execution tool versions supported by the model.

anthropic_supports_task_budgets class-attribute instance-attribute

anthropic_supports_task_budgets: bool = False

Whether the model supports output_config.task_budget.

Anthropic currently documents task budgets as a Claude Opus 4.7 beta feature.

ANTHROPIC_THINKING_BUDGET_MAP module-attribute

ANTHROPIC_THINKING_BUDGET_MAP: dict[ThinkingLevel, int] = {
    True: 10000,
    "minimal": 1024,
    "low": 2048,
    "medium": 10000,
    "high": 16384,
    "xhigh": 32768,
}

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

AnthropicEffort module-attribute

AnthropicEffort: TypeAlias = Literal[
    "low", "medium", "high", "xhigh", "max"
]

Effort values Anthropic accepts at output_config.effort.

ANTHROPIC_THINKING_EFFORT_MAP module-attribute

ANTHROPIC_THINKING_EFFORT_MAP: dict[
    ThinkingEffort, AnthropicEffort
] = {
    "minimal": "low",
    "low": "low",
    "medium": "medium",
    "high": "high",
    "xhigh": "max",
}

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 downshifting.

resolve_anthropic_effort

resolve_anthropic_effort(
    level: ThinkingEffort, *, supports_xhigh: bool
) -> 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 passthrough decision live in one place.

Source code in pydantic_ai_slim/pydantic_ai/profiles/anthropic.py
124
125
126
127
128
129
130
131
132
133
134
def resolve_anthropic_effort(level: ThinkingEffort, *, supports_xhigh: bool) -> 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` passthrough decision live in one place.
    """
    if level == 'xhigh' and supports_xhigh:
        return 'xhigh'
    return ANTHROPIC_THINKING_EFFORT_MAP[level]

anthropic_model_profile

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

Get the model profile for an Anthropic model.

Source code in pydantic_ai_slim/pydantic_ai/profiles/anthropic.py
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
def anthropic_model_profile(model_name: str) -> ModelProfile | None:
    """Get the model profile for an Anthropic model."""
    models_that_support_json_schema_output = (
        'claude-haiku-4-5',
        'claude-sonnet-4-5',
        'claude-sonnet-4-6',
        'claude-opus-4-1',
        'claude-opus-4-5',
        'claude-opus-4-6',
        'claude-opus-4-7',
    )
    """These models support both structured outputs and strict tool calling."""
    # TODO update when new models are released that support structured outputs
    # https://docs.claude.com/en/docs/build-with-claude/structured-outputs#example-usage

    supports_json_schema_output = model_name.startswith(models_that_support_json_schema_output)
    anthropic_supports_fast_speed = model_name.startswith('claude-opus-4-6')

    # Sonnet 4.6+ and Opus 4.6+ support adaptive thinking; older models use budget-based
    supports_adaptive = model_name.startswith(('claude-sonnet-4-6', 'claude-opus-4-6', 'claude-opus-4-7'))

    # Opus 4.5+ and Sonnet 4.6+ support the effort parameter in output_config
    supports_effort = model_name.startswith(
        ('claude-opus-4-5', 'claude-opus-4-6', 'claude-opus-4-7', 'claude-sonnet-4-6')
    )
    supports_xhigh_effort = model_name.startswith('claude-opus-4-7')
    disallows_budget_thinking = model_name.startswith('claude-opus-4-7')
    disallows_sampling_settings = model_name.startswith('claude-opus-4-7')
    default_code_execution_tool_version, supported_code_execution_tool_versions = _code_execution_tool_versions(
        model_name
    )
    supports_task_budgets = model_name.startswith('claude-opus-4-7')

    # Native tool search requires the `tool_search_tool_bm25_20251119` /
    # `tool_search_tool_regex_20251119` API types, which post-date Claude 4.0. In
    # practice, Anthropic enables it for Sonnet 4.5+, Opus 4.5+, and Haiku 4.5+.
    supports_tool_search = model_name.startswith(
        (
            'claude-sonnet-4-5',
            'claude-sonnet-4-6',
            'claude-opus-4-5',
            'claude-opus-4-6',
            'claude-opus-4-7',
            'claude-haiku-4-5',
        )
    )
    supported_native_tools = (
        _ANTHROPIC_BASE_BUILTINS | {ToolSearchTool} if supports_tool_search else _ANTHROPIC_BASE_BUILTINS
    )

    return AnthropicModelProfile(
        thinking_tags=('<thinking>', '</thinking>'),
        supports_json_schema_output=supports_json_schema_output,
        anthropic_supports_fast_speed=anthropic_supports_fast_speed,
        supports_thinking=True,
        anthropic_supports_adaptive_thinking=supports_adaptive,
        anthropic_supports_effort=supports_effort,
        anthropic_supports_xhigh_effort=supports_xhigh_effort,
        anthropic_disallows_budget_thinking=disallows_budget_thinking,
        anthropic_disallows_sampling_settings=disallows_sampling_settings,
        anthropic_default_code_execution_tool_version=default_code_execution_tool_version,
        anthropic_supported_code_execution_tool_versions=supported_code_execution_tool_versions,
        anthropic_supports_task_budgets=supports_task_budgets,
        supported_native_tools=supported_native_tools,
    )

GoogleModelProfile dataclass

Bases: ModelProfile

Profile for models used with GoogleModel.

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

Source code in pydantic_ai_slim/pydantic_ai/profiles/google.py
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
@dataclass(kw_only=True)
class GoogleModelProfile(ModelProfile):
    """Profile for models used with `GoogleModel`.

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

    google_supports_tool_combination: bool = False
    """Whether the model supports combining function declarations with native tools and response_schema.

    Gemini 3+ supports all tool combinations:
    - function_declarations + native_tools
    - output_tools (function declarations) + native_tools
    - response_schema (NativeOutput) + function_declarations
    See https://ai.google.dev/gemini-api/docs/tool-combination
    """

    google_supports_server_side_tool_invocations: bool = False
    """Whether the model accepts the `include_server_side_tool_invocations` tool-config field.

    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`][pydantic_ai.messages.NativeToolCallPart] /
    [`NativeToolReturnPart`][pydantic_ai.messages.NativeToolReturnPart]. Pre-Gemini-3 models
    reject the field with `'Tool call context circulation is not enabled'`.

    Distinct from [`google_supports_tool_combination`][pydantic_ai.profiles.google.GoogleModelProfile.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.
    """

    # TODO(v2): remove google_supports_native_output_with_builtin_tools
    google_supports_native_output_with_builtin_tools: bool | None = None
    """Deprecated: use `google_supports_tool_combination` instead."""

    google_supported_mime_types_in_tool_returns: tuple[str, ...] = ()
    """MIME types supported in native FunctionResponseDict.parts.
    See https://ai.google.dev/gemini-api/docs/function-calling#multimodal-function-responses"""

    google_supports_thinking_level: bool = False
    """Whether the model uses `thinking_level` (enum: LOW/MEDIUM/HIGH) instead of `thinking_budget` (int).

    Gemini 3+ models use `thinking_level`; Gemini 2.5 uses `thinking_budget`.
    """

    def __post_init__(self):
        if self.google_supports_native_output_with_builtin_tools is not None:
            warnings.warn(
                '`google_supports_native_output_with_builtin_tools` is deprecated, '
                'use `google_supports_tool_combination` instead.',
                PydanticAIDeprecationWarning,
                stacklevel=2,
            )
            # New flag wins on conflict — silently overwriting an explicitly-set new value with
            # the deprecated alias would surprise users mid-migration.
            if not self.google_supports_tool_combination:
                self.google_supports_tool_combination = self.google_supports_native_output_with_builtin_tools

google_supports_tool_combination class-attribute instance-attribute

google_supports_tool_combination: bool = False

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

Gemini 3+ supports all tool combinations: - function_declarations + native_tools - output_tools (function declarations) + native_tools - response_schema (NativeOutput) + function_declarations See https://ai.google.dev/gemini-api/docs/tool-combination

google_supports_server_side_tool_invocations class-attribute instance-attribute

google_supports_server_side_tool_invocations: bool = False

Whether the model accepts the include_server_side_tool_invocations tool-config field.

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'.

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.

google_supports_native_output_with_builtin_tools class-attribute instance-attribute

google_supports_native_output_with_builtin_tools: (
    bool | None
) = None

Deprecated: use google_supports_tool_combination instead.

google_supported_mime_types_in_tool_returns class-attribute instance-attribute

google_supported_mime_types_in_tool_returns: tuple[
    str, ...
] = ()

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

google_supports_thinking_level class-attribute instance-attribute

google_supports_thinking_level: bool = False

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

Gemini 3+ models use thinking_level; Gemini 2.5 uses thinking_budget.

google_model_profile

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

Get the model profile for a Google model.

Source code in pydantic_ai_slim/pydantic_ai/profiles/google.py
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
def google_model_profile(model_name: str) -> ModelProfile | None:
    """Get the model profile for a Google model."""
    is_image_model = 'image' in model_name
    is_3_or_newer = 'gemini-3' in model_name
    is_thinking_model = 'gemini-2.5' in model_name or is_3_or_newer
    # Pro models have always-on thinking: Gemini 2.5 Pro rejects budget=0, Gemini 3+ Pro rejects MINIMAL
    is_pro = 'pro' in model_name and 'flash' not in model_name
    thinking_always_enabled = is_thinking_model and is_pro
    return GoogleModelProfile(
        json_schema_transformer=GoogleJsonSchemaTransformer,
        supports_image_output=is_image_model,
        supports_json_schema_output=is_3_or_newer or not is_image_model,
        supports_json_object_output=is_3_or_newer or not is_image_model,
        supports_tools=not is_image_model,
        supports_tool_return_schema=not is_image_model,
        supports_thinking=is_thinking_model,
        thinking_always_enabled=thinking_always_enabled,
        google_supports_tool_combination=is_3_or_newer,
        google_supports_server_side_tool_invocations=is_3_or_newer,
        google_supported_mime_types_in_tool_returns=_GOOGLE_NATIVE_TOOL_RETURN_MIME_TYPES if is_3_or_newer else (),
        google_supports_thinking_level=is_3_or_newer,
    )

GoogleJsonSchemaTransformer dataclass

Bases: JsonSchemaTransformer

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

Gemini supports a subset of OpenAPI v3.0.3.

Source code in pydantic_ai_slim/pydantic_ai/profiles/google.py
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
class GoogleJsonSchemaTransformer(JsonSchemaTransformer):
    """Transforms the JSON Schema from Pydantic to be suitable for Gemini.

    Gemini supports [a subset of OpenAPI v3.0.3](https://ai.google.dev/gemini-api/docs/function-calling#function_declarations).
    """

    def transform(self, schema: JsonSchema) -> JsonSchema:
        # Remove properties not supported by Gemini
        schema.pop('$schema', None)
        if (const := schema.pop('const', None)) is not None:
            # Gemini doesn't support const, but it does support enum with a single value
            schema['enum'] = [const]
            # If type is not present, infer it from the const value for Gemini API compatibility
            if 'type' not in schema:
                if isinstance(const, str):
                    schema['type'] = 'string'
                elif isinstance(const, bool):
                    # bool must be checked before int since bool is a subclass of int in Python
                    schema['type'] = 'boolean'
                elif isinstance(const, int):
                    schema['type'] = 'integer'
                elif isinstance(const, float):
                    schema['type'] = 'number'
        schema.pop('discriminator', None)
        schema.pop('examples', None)

        # Remove 'title' due to https://github.com/googleapis/python-genai/issues/1732
        schema.pop('title', None)

        type_ = schema.get('type')
        if type_ == 'string' and (fmt := schema.pop('format', None)):
            description = schema.get('description')
            if description:
                schema['description'] = f'{description} (format: {fmt})'
            else:
                schema['description'] = f'Format: {fmt}'

        # Note: exclusiveMinimum/exclusiveMaximum are NOT yet supported
        schema.pop('exclusiveMinimum', None)
        schema.pop('exclusiveMaximum', None)

        return schema

meta_model_profile

meta_model_profile(model_name: str) -> ModelProfile | None

Get the model profile for a Meta model.

Source code in pydantic_ai_slim/pydantic_ai/profiles/meta.py
6
7
8
def meta_model_profile(model_name: str) -> ModelProfile | None:
    """Get the model profile for a Meta model."""
    return ModelProfile(json_schema_transformer=InlineDefsJsonSchemaTransformer)

amazon_model_profile

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

Get the model profile for an Amazon model.

Source code in pydantic_ai_slim/pydantic_ai/profiles/amazon.py
6
7
8
def amazon_model_profile(model_name: str) -> ModelProfile | None:
    """Get the model profile for an Amazon model."""
    return ModelProfile(json_schema_transformer=InlineDefsJsonSchemaTransformer)

deepseek_model_profile

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

Get the model profile for a DeepSeek model.

Source code in pydantic_ai_slim/pydantic_ai/profiles/deepseek.py
 6
 7
 8
 9
10
11
12
13
14
15
16
def deepseek_model_profile(model_name: str) -> ModelProfile | None:
    """Get the model profile for a DeepSeek model."""
    is_r1 = model_name.startswith('deepseek-r1') or model_name == 'deepseek-reasoner'
    # V4 models (deepseek-v4-flash, deepseek-v4-pro, …) support thinking via reasoning_effort
    # but do not always enable it — thinking_always_enabled stays False.
    is_v4 = model_name.startswith('deepseek-v4-')
    return ModelProfile(
        ignore_streamed_leading_whitespace=is_r1,
        supports_thinking=is_r1 or is_v4,
        thinking_always_enabled=is_r1,
    )

GrokModelProfile dataclass

Bases: ModelProfile

Profile for Grok models (used with both GrokProvider and XaiProvider).

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

Source code in pydantic_ai_slim/pydantic_ai/profiles/grok.py
 9
10
11
12
13
14
15
16
17
18
19
20
@dataclass(kw_only=True)
class GrokModelProfile(ModelProfile):
    """Profile for Grok models (used with both GrokProvider and XaiProvider).

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

    grok_supports_builtin_tools: bool = False
    """Whether the model supports builtin tools (web_search, x_search, code_execution, mcp)."""

    grok_supports_tool_choice_required: bool = True
    """Whether the provider accepts the value `tool_choice='required'` in the request payload."""

grok_supports_builtin_tools class-attribute instance-attribute

grok_supports_builtin_tools: bool = False

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

grok_supports_tool_choice_required class-attribute instance-attribute

grok_supports_tool_choice_required: bool = True

Whether the provider accepts the value tool_choice='required' in the request payload.

grok_model_profile

grok_model_profile(model_name: str) -> ModelProfile | None

Get the model profile for a Grok model.

Source code in pydantic_ai_slim/pydantic_ai/profiles/grok.py
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
def grok_model_profile(model_name: str) -> ModelProfile | None:
    """Get the model profile for a Grok model."""
    grok_supports_builtin_tools = model_name.startswith('grok-4') or 'code' in model_name
    # Only grok-3-mini accepts the `reasoning_effort` parameter. grok-4 reasoning models
    # always reason but reject the parameter, so we treat thinking as unsupported for them
    # to avoid forwarding an argument the API will error on.
    # See https://docs.x.ai/docs/guides/reasoning
    supports_thinking_effort = model_name.startswith('grok-3-mini')

    supported_native_tools: frozenset[type[AbstractNativeTool]] = (
        SUPPORTED_NATIVE_TOOLS if grok_supports_builtin_tools else frozenset()
    )

    return GrokModelProfile(
        supports_tools=True,
        supports_json_schema_output=True,
        supports_json_object_output=True,
        supports_thinking=supports_thinking_effort,
        grok_supports_builtin_tools=grok_supports_builtin_tools,
        supported_native_tools=supported_native_tools,
    )

mistral_model_profile

mistral_model_profile(
    model_name: str,
) -> ModelProfile | None

Get the model profile for a Mistral model.

Source code in pydantic_ai_slim/pydantic_ai/profiles/mistral.py
 6
 7
 8
 9
10
11
def mistral_model_profile(model_name: str) -> ModelProfile | None:
    """Get the model profile for a Mistral model."""
    is_magistral = model_name.startswith('magistral')
    if is_magistral:
        return ModelProfile(supports_thinking=True, thinking_always_enabled=True)
    return None

qwen_model_profile

qwen_model_profile(model_name: str) -> ModelProfile | None

Get the model profile for a Qwen model.

Source code in pydantic_ai_slim/pydantic_ai/profiles/qwen.py
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
def qwen_model_profile(model_name: str) -> ModelProfile | None:
    """Get the model profile for a Qwen model."""
    if model_name.startswith('qwen-3-coder'):
        return OpenAIModelProfile(
            json_schema_transformer=InlineDefsJsonSchemaTransformer,
            openai_supports_tool_choice_required=False,
            openai_supports_strict_tool_definition=False,
            ignore_streamed_leading_whitespace=True,
        )
    if _QWEN_3_5_RE.search(model_name):
        return ModelProfile(
            json_schema_transformer=InlineDefsJsonSchemaTransformer,
            ignore_streamed_leading_whitespace=True,
            supports_json_schema_output=True,
            supports_json_object_output=True,
        )
    return ModelProfile(
        json_schema_transformer=InlineDefsJsonSchemaTransformer,
        ignore_streamed_leading_whitespace=True,
    )