Skip to content

Commit fc2d208

Browse files
authored
feat: switch the default model to a newer mini model (affecting only when a model is unset) (#3147)
1 parent b9cbab1 commit fc2d208

6 files changed

Lines changed: 139 additions & 30 deletions

File tree

src/agents/agent.py

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,6 @@
3434
from .model_settings import ModelSettings
3535
from .models.default_models import (
3636
get_default_model_settings,
37-
gpt_5_reasoning_settings_required,
38-
is_gpt_5_default,
3937
)
4038
from .models.interface import Model
4139
from .prompts import DynamicPromptFunction, Prompt, PromptUtil
@@ -153,6 +151,20 @@ class MCPConfig(TypedDict):
153151
"""
154152

155153

154+
def _initial_model_settings_for_model(model: str | Model | None) -> ModelSettings:
155+
if model is None:
156+
return get_default_model_settings()
157+
if isinstance(model, str):
158+
return get_default_model_settings(model)
159+
return ModelSettings()
160+
161+
162+
def _model_settings_match_implicit_model_defaults(
163+
model: str | Model | None, model_settings: ModelSettings
164+
) -> bool:
165+
return model_settings == _initial_model_settings_for_model(model)
166+
167+
156168
@dataclass
157169
class AgentBase(Generic[TContext]):
158170
"""Base class for `Agent` and `RealtimeAgent`."""
@@ -265,7 +277,7 @@ class Agent(AgentBase, Generic[TContext]):
265277
"""The model implementation to use when invoking the LLM.
266278
267279
By default, if not set, the agent will use the default model configured in
268-
`agents.models.get_default_model()` (currently "gpt-4.1").
280+
`agents.models.get_default_model()` (currently "gpt-5.4-mini").
269281
"""
270282

271283
model_settings: ModelSettings = field(default_factory=get_default_model_settings)
@@ -383,25 +395,8 @@ def __post_init__(self):
383395
f"got {type(self.model_settings).__name__}"
384396
)
385397

386-
if (
387-
# The user sets a non-default model
388-
self.model is not None
389-
and (
390-
# The default model is gpt-5
391-
is_gpt_5_default() is True
392-
# However, the specified model is not a gpt-5 model
393-
and (
394-
isinstance(self.model, str) is False
395-
or gpt_5_reasoning_settings_required(self.model) is False # type: ignore
396-
)
397-
# The model settings are not customized for the specified model
398-
and self.model_settings == get_default_model_settings()
399-
)
400-
):
401-
# In this scenario, we should use a generic model settings
402-
# because non-gpt-5 models are not compatible with the default gpt-5 model settings.
403-
# This is a best-effort attempt to make the agent work with non-gpt-5 models.
404-
self.model_settings = ModelSettings()
398+
if self.model is not None and self.model_settings == get_default_model_settings():
399+
self.model_settings = _initial_model_settings_for_model(self.model)
405400

406401
if not isinstance(self.input_guardrails, list):
407402
raise TypeError(
@@ -467,6 +462,12 @@ def clone(self, **kwargs: Any) -> Agent[TContext]:
467462
new_agent = agent.clone(instructions="New instructions")
468463
```
469464
"""
465+
if (
466+
"model" in kwargs
467+
and "model_settings" not in kwargs
468+
and _model_settings_match_implicit_model_defaults(self.model, self.model_settings)
469+
):
470+
kwargs["model_settings"] = _initial_model_settings_for_model(kwargs["model"])
470471
return dataclasses.replace(self, **kwargs)
471472

472473
def as_tool(

src/agents/models/default_models.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ def get_default_model() -> str:
9696
"""
9797
Returns the default model name.
9898
"""
99-
return os.getenv(OPENAI_DEFAULT_MODEL_ENV_VARIABLE_NAME, "gpt-4.1").lower()
99+
return os.getenv(OPENAI_DEFAULT_MODEL_ENV_VARIABLE_NAME, "gpt-5.4-mini").lower()
100100

101101

102102
def get_default_model_settings(model: str | None = None) -> ModelSettings:

src/agents/run_internal/run_loop.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@
179179
get_all_tools,
180180
get_handoffs,
181181
get_model,
182+
get_model_settings,
182183
get_output_schema,
183184
maybe_filter_model_input,
184185
validate_run_hooks,
@@ -1341,7 +1342,7 @@ def _tool_search_fingerprint(raw_item: Any) -> str:
13411342

13421343
handoffs = await get_handoffs(execution_agent, context_wrapper)
13431344
model = get_model(execution_agent, run_config)
1344-
model_settings = execution_agent.model_settings.resolve(run_config.model_settings)
1345+
model_settings = get_model_settings(execution_agent, run_config)
13451346
model_settings = maybe_reset_tool_choice(public_agent, tool_use_tracker, model_settings)
13461347

13471348
final_response: ModelResponse | None = None
@@ -1825,7 +1826,7 @@ async def get_new_response(
18251826
filtered.input = deduplicate_input_items_preferring_latest(filtered.input)
18261827

18271828
model = get_model(execution_agent, run_config)
1828-
model_settings = execution_agent.model_settings.resolve(run_config.model_settings)
1829+
model_settings = get_model_settings(execution_agent, run_config)
18291830
model_settings = maybe_reset_tool_choice(public_agent, tool_use_tracker, model_settings)
18301831

18311832
if server_conversation_tracker is not None:

src/agents/run_internal/turn_preparation.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
from ..handoffs import Handoff, handoff
1111
from ..items import TResponseInputItem
1212
from ..lifecycle import AgentHooksBase, RunHooks, RunHooksBase
13+
from ..model_settings import ModelSettings
14+
from ..models.default_models import get_default_model_settings
1315
from ..models.interface import Model
1416
from ..run_config import CallModelData, ModelInputData, RunConfig
1517
from ..run_context import RunContextWrapper, TContext
@@ -24,6 +26,7 @@
2426
"get_handoffs",
2527
"get_all_tools",
2628
"get_model",
29+
"get_model_settings",
2730
]
2831

2932

@@ -130,3 +133,27 @@ def get_model(agent: Agent[Any], run_config: RunConfig) -> Model:
130133
return agent.model
131134

132135
return run_config.model_provider.get_model(agent.model)
136+
137+
138+
def _implicit_model_settings_for_agent(agent: Agent[Any]) -> ModelSettings:
139+
if agent.model is None:
140+
return get_default_model_settings()
141+
if isinstance(agent.model, str):
142+
return get_default_model_settings(agent.model)
143+
return ModelSettings()
144+
145+
146+
def _model_settings_for_resolved_name(agent: Agent[Any], run_config: RunConfig) -> ModelSettings:
147+
if isinstance(run_config.model, str):
148+
return get_default_model_settings(run_config.model)
149+
if isinstance(run_config.model, Model):
150+
return ModelSettings()
151+
return _implicit_model_settings_for_agent(agent)
152+
153+
154+
def get_model_settings(agent: Agent[Any], run_config: RunConfig) -> ModelSettings:
155+
"""Resolve model settings, keeping implicit defaults aligned with the resolved model name."""
156+
model_settings = agent.model_settings
157+
if model_settings == _implicit_model_settings_for_agent(agent):
158+
model_settings = _model_settings_for_resolved_name(agent, run_config)
159+
return model_settings.resolve(run_config.model_settings)

tests/models/test_default_models.py

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,11 @@ def _gpt_5_default_settings(
2222
return ModelSettings(reasoning=Reasoning(effort=reasoning_effort), verbosity="low")
2323

2424

25-
def test_default_model_is_gpt_4_1():
26-
assert get_default_model() == "gpt-4.1"
27-
assert is_gpt_5_default() is False
28-
assert gpt_5_reasoning_settings_required(get_default_model()) is False
29-
assert get_default_model_settings().reasoning is None
25+
def test_default_model_is_gpt_5_4_mini():
26+
assert get_default_model() == "gpt-5.4-mini"
27+
assert is_gpt_5_default() is True
28+
assert gpt_5_reasoning_settings_required(get_default_model()) is True
29+
assert get_default_model_settings() == _gpt_5_default_settings("none")
3030

3131

3232
@patch.dict(os.environ, {"OPENAI_DEFAULT_MODEL": "gpt-5.4"})
@@ -139,6 +139,39 @@ def test_agent_uses_gpt_5_default_model_settings():
139139
assert agent.model_settings.verbosity == "low"
140140

141141

142+
def test_agent_uses_model_specific_settings_for_explicit_gpt_5_models():
143+
"""Agent should not apply the fallback model's GPT-5 settings to explicit GPT-5 models."""
144+
agent = Agent(name="test", model="gpt-5")
145+
assert agent.model == "gpt-5"
146+
assert agent.model_settings == get_default_model_settings("gpt-5")
147+
assert agent.model_settings.reasoning.effort == "low" # type: ignore[union-attr]
148+
149+
150+
def test_agent_uses_empty_settings_for_explicit_non_gpt_5_models():
151+
"""Agent should not apply GPT-5 defaults to explicit non-GPT-5 models."""
152+
agent = Agent(name="test", model="gpt-4.1")
153+
assert agent.model == "gpt-4.1"
154+
assert agent.model_settings == ModelSettings()
155+
156+
157+
def test_agent_clone_recomputes_implicit_settings_when_model_changes():
158+
"""Agent.clone should keep implicit model settings aligned with the cloned model."""
159+
agent = Agent(name="test", model="gpt-5")
160+
cloned = agent.clone(model="gpt-5.4-mini")
161+
assert cloned.model == "gpt-5.4-mini"
162+
assert cloned.model_settings == get_default_model_settings("gpt-5.4-mini")
163+
assert cloned.model_settings.reasoning.effort == "none" # type: ignore[union-attr]
164+
165+
166+
def test_agent_clone_preserves_explicit_settings_when_model_changes():
167+
"""Agent.clone should not recompute model settings that were explicitly customized."""
168+
model_settings = ModelSettings(temperature=0.3)
169+
agent = Agent(name="test", model="gpt-5", model_settings=model_settings)
170+
cloned = agent.clone(model="gpt-5.4-mini")
171+
assert cloned.model == "gpt-5.4-mini"
172+
assert cloned.model_settings == model_settings
173+
174+
142175
@patch.dict(os.environ, {"OPENAI_DEFAULT_MODEL": "gpt-5"})
143176
def test_agent_resets_model_settings_for_non_gpt_5_models():
144177
"""Agent should reset default GPT-5 settings when using a non-GPT-5 model."""

tests/test_run_config.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import pytest
44

55
from agents import Agent, RunConfig, Runner
6+
from agents.model_settings import ModelSettings
67
from agents.models.interface import Model, ModelProvider
78

89
from .fake_model import FakeModel
@@ -56,6 +57,52 @@ async def test_run_config_model_name_override_takes_precedence() -> None:
5657
assert result.final_output == "override-name"
5758

5859

60+
@pytest.mark.asyncio
61+
async def test_run_config_model_name_override_uses_model_specific_default_settings(
62+
monkeypatch,
63+
) -> None:
64+
"""
65+
When RunConfig sets a model name, implicit settings should match that model name rather
66+
than the default fallback model.
67+
"""
68+
monkeypatch.setenv("OPENAI_DEFAULT_MODEL", "gpt-5.4-mini")
69+
fake_model = FakeModel(initial_output=[get_text_message("override-name")])
70+
provider = DummyProvider(model_to_return=fake_model)
71+
agent = Agent(name="test")
72+
run_config = RunConfig(model="gpt-5", model_provider=provider)
73+
result = await Runner.run(agent, input="any", run_config=run_config)
74+
assert result.final_output == "override-name"
75+
assert fake_model.first_turn_args is not None
76+
model_settings = fake_model.first_turn_args["model_settings"]
77+
assert model_settings.reasoning.effort == "low"
78+
assert model_settings.verbosity == "low"
79+
80+
81+
@pytest.mark.asyncio
82+
async def test_run_config_model_settings_override_implicit_model_specific_defaults(
83+
monkeypatch,
84+
) -> None:
85+
"""
86+
RunConfig model settings should overlay the implicit defaults for the resolved model name.
87+
"""
88+
monkeypatch.setenv("OPENAI_DEFAULT_MODEL", "gpt-5.4-mini")
89+
fake_model = FakeModel(initial_output=[get_text_message("override-name")])
90+
provider = DummyProvider(model_to_return=fake_model)
91+
agent = Agent(name="test")
92+
run_config = RunConfig(
93+
model="gpt-5",
94+
model_provider=provider,
95+
model_settings=ModelSettings(temperature=0.3),
96+
)
97+
result = await Runner.run(agent, input="any", run_config=run_config)
98+
assert result.final_output == "override-name"
99+
assert fake_model.first_turn_args is not None
100+
model_settings = fake_model.first_turn_args["model_settings"]
101+
assert model_settings.reasoning.effort == "low"
102+
assert model_settings.verbosity == "low"
103+
assert model_settings.temperature == 0.3
104+
105+
59106
@pytest.mark.asyncio
60107
async def test_run_config_model_override_object_takes_precedence() -> None:
61108
"""

0 commit comments

Comments
 (0)