from __future__ import annotations import pytest from agents import Agent, RunConfig, Runner, ToolExecutionConfig, ToolNotFoundBehavior from agents.model_settings import ModelSettings from agents.models.interface import Model, ModelProvider from .fake_model import FakeModel from .test_responses import get_text_message class DummyProvider(ModelProvider): """A simple model provider that always returns the same model, and records the model name it was asked to provide.""" def __init__(self, model_to_return: Model | None = None) -> None: self.last_requested: str | None = None self.model_to_return: Model = model_to_return or FakeModel() def get_model(self, model_name: str | None) -> Model: # record the requested model name and return our test model self.last_requested = model_name return self.model_to_return @pytest.mark.asyncio async def test_model_provider_on_run_config_is_used_for_agent_model_name() -> None: """ When the agent's ``model`` attribute is a string and no explicit model override is provided in the ``RunConfig``, the ``Runner`` should resolve the model using the ``model_provider`` on the ``RunConfig``. """ fake_model = FakeModel(initial_output=[get_text_message("from-provider")]) provider = DummyProvider(model_to_return=fake_model) agent = Agent(name="test", model="test-model") run_config = RunConfig(model_provider=provider) result = await Runner.run(agent, input="any", run_config=run_config) # We picked up the model from our dummy provider assert provider.last_requested == "test-model" assert result.final_output == "from-provider" @pytest.mark.asyncio async def test_run_config_model_name_override_takes_precedence() -> None: """ When a model name string is set on the RunConfig, then that name should be looked up using the RunConfig's model_provider, and should override any model on the agent. """ fake_model = FakeModel(initial_output=[get_text_message("override-name")]) provider = DummyProvider(model_to_return=fake_model) agent = Agent(name="test", model="agent-model") run_config = RunConfig(model="override-name", model_provider=provider) result = await Runner.run(agent, input="any", run_config=run_config) # We should have requested the override name, not the agent.model assert provider.last_requested == "override-name" assert result.final_output == "override-name" @pytest.mark.asyncio @pytest.mark.parametrize( ("model_name", "reasoning_effort"), [("gpt-5", "low"), ("gpt-5.6", "none")], ) async def test_run_config_model_name_override_uses_model_specific_default_settings( monkeypatch, model_name, reasoning_effort, ) -> None: """ When RunConfig sets a model name, implicit settings should match that model name rather than the default fallback model. """ monkeypatch.setenv("OPENAI_DEFAULT_MODEL", "gpt-5.4-mini") fake_model = FakeModel(initial_output=[get_text_message("override-name")]) provider = DummyProvider(model_to_return=fake_model) agent = Agent(name="test") run_config = RunConfig(model=model_name, model_provider=provider) result = await Runner.run(agent, input="any", run_config=run_config) assert result.final_output == "override-name" assert fake_model.first_turn_args is not None model_settings = fake_model.first_turn_args["model_settings"] assert model_settings.reasoning.effort == reasoning_effort assert model_settings.verbosity == "low" @pytest.mark.asyncio async def test_run_config_model_settings_override_implicit_model_specific_defaults( monkeypatch, ) -> None: """ RunConfig model settings should overlay the implicit defaults for the resolved model name. """ monkeypatch.setenv("OPENAI_DEFAULT_MODEL", "gpt-5.4-mini") fake_model = FakeModel(initial_output=[get_text_message("override-name")]) provider = DummyProvider(model_to_return=fake_model) agent = Agent(name="test") run_config = RunConfig( model="gpt-5", model_provider=provider, model_settings=ModelSettings(temperature=0.3), ) result = await Runner.run(agent, input="any", run_config=run_config) assert result.final_output == "override-name" assert fake_model.first_turn_args is not None model_settings = fake_model.first_turn_args["model_settings"] assert model_settings.reasoning.effort == "low" assert model_settings.verbosity == "low" assert model_settings.temperature == 0.3 @pytest.mark.asyncio async def test_run_config_model_override_object_takes_precedence() -> None: """ When a concrete Model instance is set on the RunConfig, then that instance should be returned by AgentRunner._get_model regardless of the agent's model. """ fake_model = FakeModel(initial_output=[get_text_message("override-object")]) agent = Agent(name="test", model="agent-model") run_config = RunConfig(model=fake_model) result = await Runner.run(agent, input="any", run_config=run_config) # Our FakeModel on the RunConfig should have been used. assert result.final_output == "override-object" @pytest.mark.asyncio async def test_agent_model_object_is_used_when_present() -> None: """ If the agent has a concrete Model object set as its model, and the RunConfig does not specify a model override, then that object should be used directly without consulting the RunConfig's model_provider. """ fake_model = FakeModel(initial_output=[get_text_message("from-agent-object")]) provider = DummyProvider() agent = Agent(name="test", model=fake_model) run_config = RunConfig(model_provider=provider) result = await Runner.run(agent, input="any", run_config=run_config) # The dummy provider should never have been called, and the output should come from # the FakeModel on the agent. assert provider.last_requested is None assert result.final_output == "from-agent-object" def test_trace_include_sensitive_data_defaults_to_true_when_env_not_set(monkeypatch): """By default, trace_include_sensitive_data should be True when the env is not set.""" monkeypatch.delenv("OPENAI_AGENTS_TRACE_INCLUDE_SENSITIVE_DATA", raising=False) config = RunConfig() assert config.trace_include_sensitive_data is True @pytest.mark.parametrize( "env_value,expected", [ ("true", True), ("True", True), ("1", True), ("yes", True), ("on", True), ("false", False), ("False", False), ("0", False), ("no", False), ("off", False), ], ids=[ "lowercase-true", "capital-True", "numeric-1", "text-yes", "text-on", "lowercase-false", "capital-False", "numeric-0", "text-no", "text-off", ], ) def test_trace_include_sensitive_data_follows_env_value(env_value, expected, monkeypatch): """trace_include_sensitive_data should follow the environment variable if not explicitly set.""" monkeypatch.setenv("OPENAI_AGENTS_TRACE_INCLUDE_SENSITIVE_DATA", env_value) config = RunConfig() assert config.trace_include_sensitive_data is expected def test_trace_include_sensitive_data_explicit_override_takes_precedence(monkeypatch): """Explicit value passed to RunConfig should take precedence over the environment variable.""" monkeypatch.setenv("OPENAI_AGENTS_TRACE_INCLUDE_SENSITIVE_DATA", "false") config = RunConfig(trace_include_sensitive_data=True) assert config.trace_include_sensitive_data is True monkeypatch.setenv("OPENAI_AGENTS_TRACE_INCLUDE_SENSITIVE_DATA", "true") config = RunConfig(trace_include_sensitive_data=False) assert config.trace_include_sensitive_data is False def test_tool_execution_config_rejects_invalid_function_tool_concurrency() -> None: with pytest.raises( ValueError, match="tool_execution.max_function_tool_concurrency must be at least 1", ): ToolExecutionConfig(max_function_tool_concurrency=0) def test_tool_execution_config_is_public_from_agents_package() -> None: config = RunConfig(tool_execution=ToolExecutionConfig(max_function_tool_concurrency=2)) assert config.tool_execution is not None assert config.tool_execution.max_function_tool_concurrency == 2 def test_tool_not_found_behavior_defaults_to_raise_error() -> None: config = RunConfig() assert config.tool_not_found_behavior == "raise_error" def test_tool_not_found_behavior_is_public_from_agents_package() -> None: behavior: ToolNotFoundBehavior = "return_error_to_model" config = RunConfig(tool_not_found_behavior=behavior) assert config.tool_not_found_behavior == "return_error_to_model"