Skip to content

Commit c28a253

Browse files
sviluppjan-simlDouweM
authored
Let model settings be passed to model classes (#2136)
Co-authored-by: J S <[email protected]> Co-authored-by: svilupp <[email protected]> Co-authored-by: Douwe Maan <[email protected]>
1 parent 83082cd commit c28a253

File tree

20 files changed

+337
-33
lines changed

20 files changed

+337
-33
lines changed

docs/agents.md

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -466,26 +466,43 @@ PydanticAI offers a [`settings.ModelSettings`][pydantic_ai.settings.ModelSetting
466466
This structure allows you to configure common parameters that influence the model's behavior, such as `temperature`, `max_tokens`,
467467
`timeout`, and more.
468468

469-
There are two ways to apply these settings:
469+
There are three ways to apply these settings, with a clear precedence order:
470470

471-
1. Passing to `run{_sync,_stream}` functions via the `model_settings` argument. This allows for fine-tuning on a per-request basis.
472-
2. Setting during [`Agent`][pydantic_ai.agent.Agent] initialization via the `model_settings` argument. These settings will be applied by default to all subsequent run calls using said agent. However, `model_settings` provided during a specific run call will override the agent's default settings.
471+
1. **Model-level defaults** - Set when creating a model instance via the `settings` parameter. These serve as the base defaults for that model.
472+
2. **Agent-level defaults** - Set during [`Agent`][pydantic_ai.agent.Agent] initialization via the `model_settings` argument. These are merged with model defaults, with agent settings taking precedence.
473+
3. **Run-time overrides** - Passed to `run{_sync,_stream}` functions via the `model_settings` argument. These have the highest priority and are merged with the combined agent and model defaults.
473474

474475
For example, if you'd like to set the `temperature` setting to `0.0` to ensure less random behavior,
475476
you can do the following:
476477

477478
```py
478479
from pydantic_ai import Agent
480+
from pydantic_ai.models.openai import OpenAIModel
481+
from pydantic_ai.settings import ModelSettings
479482

480-
agent = Agent('openai:gpt-4o')
483+
# 1. Model-level defaults
484+
model = OpenAIModel(
485+
'gpt-4o',
486+
settings=ModelSettings(temperature=0.8, max_tokens=500) # Base defaults
487+
)
488+
489+
# 2. Agent-level defaults (overrides model defaults by merging)
490+
agent = Agent(model, model_settings=ModelSettings(temperature=0.5))
481491

492+
# 3. Run-time overrides (highest priority)
482493
result_sync = agent.run_sync(
483-
'What is the capital of Italy?', model_settings={'temperature': 0.0}
494+
'What is the capital of Italy?',
495+
model_settings=ModelSettings(temperature=0.0) # Final temperature: 0.0
484496
)
485497
print(result_sync.output)
486498
#> Rome
487499
```
488500

501+
The final request uses `temperature=0.0` (run-time), `max_tokens=500` (from model), demonstrating how settings merge with run-time taking precedence.
502+
503+
!!! note "Model Settings Support"
504+
Model-level settings are supported by all concrete model implementations (OpenAI, Anthropic, Google, etc.). Wrapper models like `FallbackModel`, `WrapperModel`, and `InstrumentedModel` don't have their own settings - they use the settings of their underlying models.
505+
489506
### Model specific settings
490507

491508
If you wish to further customize model behavior, you can use a subclass of [`ModelSettings`][pydantic_ai.settings.ModelSettings], like [`GeminiModelSettings`][pydantic_ai.models.gemini.GeminiModelSettings], associated with your model of choice.

docs/models/index.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,39 @@ The `ModelResponse` message above indicates in the `model_name` field that the o
124124
!!! note
125125
Each model's options should be configured individually. For example, `base_url`, `api_key`, and custom clients should be set on each model itself, not on the `FallbackModel`.
126126

127+
### Per-Model Settings
128+
129+
You can configure different [`ModelSettings`][pydantic_ai.settings.ModelSettings] for each model in a fallback chain by passing the `settings` parameter when creating each model. This is particularly useful when different providers have different optimal configurations:
130+
131+
```python {title="fallback_model_per_settings.py"}
132+
from pydantic_ai import Agent
133+
from pydantic_ai.models.anthropic import AnthropicModel
134+
from pydantic_ai.models.fallback import FallbackModel
135+
from pydantic_ai.models.openai import OpenAIModel
136+
from pydantic_ai.settings import ModelSettings
137+
138+
# Configure each model with provider-specific optimal settings
139+
openai_model = OpenAIModel(
140+
'gpt-4o',
141+
settings=ModelSettings(temperature=0.7, max_tokens=1000) # Higher creativity for OpenAI
142+
)
143+
anthropic_model = AnthropicModel(
144+
'claude-3-5-sonnet-latest',
145+
settings=ModelSettings(temperature=0.2, max_tokens=1000) # Lower temperature for consistency
146+
)
147+
148+
fallback_model = FallbackModel(openai_model, anthropic_model)
149+
agent = Agent(fallback_model)
150+
151+
result = agent.run_sync('Write a creative story about space exploration')
152+
print(result.output)
153+
"""
154+
In the year 2157, Captain Maya Chen piloted her spacecraft through the vast expanse of the Andromeda Galaxy. As she discovered a planet with crystalline mountains that sang in harmony with the cosmic winds, she realized that space exploration was not just about finding new worlds, but about finding new ways to understand the universe and our place within it.
155+
"""
156+
```
157+
158+
In this example, if the OpenAI model fails, the agent will automatically fall back to the Anthropic model with its own configured settings. The `FallbackModel` itself doesn't have settings - it uses the individual settings of whichever model successfully handles the request.
159+
127160
In this next example, we demonstrate the exception-handling capabilities of `FallbackModel`.
128161
If all models fail, a [`FallbackExceptionGroup`][pydantic_ai.exceptions.FallbackExceptionGroup] is raised, which
129162
contains all the exceptions encountered during the `run` execution.

pydantic_ai_slim/pydantic_ai/agent.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -674,12 +674,14 @@ async def main():
674674
# typecast reasonable, even though it is possible to violate it with otherwise-type-checked code.
675675
output_validators = cast(list[_output.OutputValidator[AgentDepsT, RunOutputDataT]], self._output_validators)
676676

677-
model_settings = merge_model_settings(self.model_settings, model_settings)
677+
# Merge model settings in order of precedence: run > agent > model
678+
merged_settings = merge_model_settings(model_used.settings, self.model_settings)
679+
model_settings = merge_model_settings(merged_settings, model_settings)
678680
usage_limits = usage_limits or _usage.UsageLimits()
679681

680682
if isinstance(model_used, InstrumentedModel):
681-
instrumentation_settings = model_used.settings
682-
tracer = model_used.settings.tracer
683+
instrumentation_settings = model_used.instrumentation_settings
684+
tracer = model_used.instrumentation_settings.tracer
683685
else:
684686
instrumentation_settings = None
685687
tracer = NoOpTracer()

pydantic_ai_slim/pydantic_ai/models/__init__.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,27 @@ class Model(ABC):
321321
"""Abstract class for a model."""
322322

323323
_profile: ModelProfileSpec | None = None
324+
_settings: ModelSettings | None = None
325+
326+
def __init__(
327+
self,
328+
*,
329+
settings: ModelSettings | None = None,
330+
profile: ModelProfileSpec | None = None,
331+
) -> None:
332+
"""Initialize the model with optional settings and profile.
333+
334+
Args:
335+
settings: Model-specific settings that will be used as defaults for this model.
336+
profile: The model profile to use.
337+
"""
338+
self._settings = settings
339+
self._profile = profile
340+
341+
@property
342+
def settings(self) -> ModelSettings | None:
343+
"""Get the model settings."""
344+
return self._settings
324345

325346
@abstractmethod
326347
async def request(

pydantic_ai_slim/pydantic_ai/models/anthropic.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ def __init__(
127127
*,
128128
provider: Literal['anthropic'] | Provider[AsyncAnthropic] = 'anthropic',
129129
profile: ModelProfileSpec | None = None,
130+
settings: ModelSettings | None = None,
130131
):
131132
"""Initialize an Anthropic model.
132133
@@ -136,13 +137,15 @@ def __init__(
136137
provider: The provider to use for the Anthropic API. Can be either the string 'anthropic' or an
137138
instance of `Provider[AsyncAnthropic]`. If not provided, the other parameters will be used.
138139
profile: The model profile to use. Defaults to a profile picked by the provider based on the model name.
140+
settings: Default model settings for this model instance.
139141
"""
140142
self._model_name = model_name
141143

142144
if isinstance(provider, str):
143145
provider = infer_provider(provider)
144146
self.client = provider.client
145-
self._profile = profile or provider.model_profile
147+
148+
super().__init__(settings=settings, profile=profile or provider.model_profile)
146149

147150
@property
148151
def base_url(self) -> str:

pydantic_ai_slim/pydantic_ai/models/bedrock.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@ def __init__(
202202
*,
203203
provider: Literal['bedrock'] | Provider[BaseClient] = 'bedrock',
204204
profile: ModelProfileSpec | None = None,
205+
settings: ModelSettings | None = None,
205206
):
206207
"""Initialize a Bedrock model.
207208
@@ -213,13 +214,15 @@ def __init__(
213214
'bedrock' or an instance of `Provider[BaseClient]`. If not provided, a new provider will be
214215
created using the other parameters.
215216
profile: The model profile to use. Defaults to a profile picked by the provider based on the model name.
217+
settings: Model-specific settings that will be used as defaults for this model.
216218
"""
217219
self._model_name = model_name
218220

219221
if isinstance(provider, str):
220222
provider = infer_provider(provider)
221223
self.client = cast('BedrockRuntimeClient', provider.client)
222-
self._profile = profile or provider.model_profile
224+
225+
super().__init__(settings=settings, profile=profile or provider.model_profile)
223226

224227
def _get_tools(self, model_request_parameters: ModelRequestParameters) -> list[ToolTypeDef]:
225228
tools = [self._map_tool_definition(r) for r in model_request_parameters.function_tools]

pydantic_ai_slim/pydantic_ai/models/cohere.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ def __init__(
111111
*,
112112
provider: Literal['cohere'] | Provider[AsyncClientV2] = 'cohere',
113113
profile: ModelProfileSpec | None = None,
114+
settings: ModelSettings | None = None,
114115
):
115116
"""Initialize an Cohere model.
116117
@@ -121,13 +122,15 @@ def __init__(
121122
'cohere' or an instance of `Provider[AsyncClientV2]`. If not provided, a new provider will be
122123
created using the other parameters.
123124
profile: The model profile to use. Defaults to a profile picked by the provider based on the model name.
125+
settings: Model-specific settings that will be used as defaults for this model.
124126
"""
125127
self._model_name = model_name
126128

127129
if isinstance(provider, str):
128130
provider = infer_provider(provider)
129131
self.client = provider.client
130-
self._profile = profile or provider.model_profile
132+
133+
super().__init__(settings=settings, profile=profile or provider.model_profile)
131134

132135
@property
133136
def base_url(self) -> str:

pydantic_ai_slim/pydantic_ai/models/fallback.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ def __init__(
4242
fallback_models: The names or instances of the fallback models to use upon failure.
4343
fallback_on: A callable or tuple of exceptions that should trigger a fallback.
4444
"""
45+
super().__init__()
4546
self.models = [infer_model(default_model), *[infer_model(m) for m in fallback_models]]
4647

4748
if isinstance(fallback_on, tuple):

pydantic_ai_slim/pydantic_ai/models/function.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,12 @@ class FunctionModel(Model):
5252

5353
@overload
5454
def __init__(
55-
self, function: FunctionDef, *, model_name: str | None = None, profile: ModelProfileSpec | None = None
55+
self,
56+
function: FunctionDef,
57+
*,
58+
model_name: str | None = None,
59+
profile: ModelProfileSpec | None = None,
60+
settings: ModelSettings | None = None,
5661
) -> None: ...
5762

5863
@overload
@@ -62,6 +67,7 @@ def __init__(
6267
stream_function: StreamFunctionDef,
6368
model_name: str | None = None,
6469
profile: ModelProfileSpec | None = None,
70+
settings: ModelSettings | None = None,
6571
) -> None: ...
6672

6773
@overload
@@ -72,6 +78,7 @@ def __init__(
7278
stream_function: StreamFunctionDef,
7379
model_name: str | None = None,
7480
profile: ModelProfileSpec | None = None,
81+
settings: ModelSettings | None = None,
7582
) -> None: ...
7683

7784
def __init__(
@@ -81,6 +88,7 @@ def __init__(
8188
stream_function: StreamFunctionDef | None = None,
8289
model_name: str | None = None,
8390
profile: ModelProfileSpec | None = None,
91+
settings: ModelSettings | None = None,
8492
):
8593
"""Initialize a `FunctionModel`.
8694
@@ -91,16 +99,19 @@ def __init__(
9199
stream_function: The function to call for streamed requests.
92100
model_name: The name of the model. If not provided, a name is generated from the function names.
93101
profile: The model profile to use.
102+
settings: Model-specific settings that will be used as defaults for this model.
94103
"""
95104
if function is None and stream_function is None:
96105
raise TypeError('Either `function` or `stream_function` must be provided')
106+
97107
self.function = function
98108
self.stream_function = stream_function
99109

100110
function_name = self.function.__name__ if self.function is not None else ''
101111
stream_function_name = self.stream_function.__name__ if self.stream_function is not None else ''
102112
self._model_name = model_name or f'function:{function_name}:{stream_function_name}'
103-
self._profile = profile
113+
114+
super().__init__(settings=settings, profile=profile)
104115

105116
async def request(
106117
self,

pydantic_ai_slim/pydantic_ai/models/gemini.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ def __init__(
133133
*,
134134
provider: Literal['google-gla', 'google-vertex'] | Provider[httpx.AsyncClient] = 'google-gla',
135135
profile: ModelProfileSpec | None = None,
136+
settings: ModelSettings | None = None,
136137
):
137138
"""Initialize a Gemini model.
138139
@@ -142,6 +143,7 @@ def __init__(
142143
'google-gla' or 'google-vertex' or an instance of `Provider[httpx.AsyncClient]`.
143144
If not provided, a new provider will be created using the other parameters.
144145
profile: The model profile to use. Defaults to a profile picked by the provider based on the model name.
146+
settings: Default model settings for this model instance.
145147
"""
146148
self._model_name = model_name
147149
self._provider = provider
@@ -151,7 +153,8 @@ def __init__(
151153
self._system = provider.name
152154
self.client = provider.client
153155
self._url = str(self.client.base_url)
154-
self._profile = profile or provider.model_profile
156+
157+
super().__init__(settings=settings, profile=profile or provider.model_profile)
155158

156159
@property
157160
def base_url(self) -> str:

0 commit comments

Comments
 (0)