diff --git a/python/packages/autogen-ext/src/autogen_ext/models/openai/config/__init__.py b/python/packages/autogen-ext/src/autogen_ext/models/openai/config/__init__.py index 02b1e3a80ff6..d0a17875875e 100644 --- a/python/packages/autogen-ext/src/autogen_ext/models/openai/config/__init__.py +++ b/python/packages/autogen-ext/src/autogen_ext/models/openai/config/__init__.py @@ -50,6 +50,13 @@ class CreateArguments(TypedDict, total=False): user: str stream_options: Optional[StreamOptions] parallel_tool_calls: Optional[bool] + reasoning_effort: Optional[Literal["minimal", "low", "medium", "high"]] + """Controls the amount of effort the model uses for reasoning. + Only applicable to reasoning models like o1 and o3-mini. + - 'minimal': Fastest response with minimal reasoning + - 'low': Faster responses with less reasoning + - 'medium': Balanced reasoning and speed + - 'high': More thorough reasoning, may take longer""" AsyncAzureADTokenProvider = Callable[[], Union[str, Awaitable[str]]] @@ -99,6 +106,8 @@ class CreateArgumentsConfigModel(BaseModel): user: str | None = None stream_options: StreamOptions | None = None parallel_tool_calls: bool | None = None + # Controls the amount of effort the model uses for reasoning (reasoning models only) + reasoning_effort: Literal["minimal", "low", "medium", "high"] | None = None class BaseOpenAIClientConfigurationConfigModel(CreateArgumentsConfigModel): diff --git a/python/packages/autogen-ext/tests/models/test_openai_model_client.py b/python/packages/autogen-ext/tests/models/test_openai_model_client.py index 2c1e19521ac4..ba79795d1ed7 100644 --- a/python/packages/autogen-ext/tests/models/test_openai_model_client.py +++ b/python/packages/autogen-ext/tests/models/test_openai_model_client.py @@ -3269,3 +3269,112 @@ def _different_function(text: str) -> str: # TODO: add integration tests for Azure OpenAI using AAD token. + + +@pytest.mark.asyncio +async def test_reasoning_effort_parameter() -> None: + """Test that reasoning_effort parameter is properly handled in client configuration.""" + + # Test OpenAI client with reasoning_effort + openai_client = OpenAIChatCompletionClient( + model="gpt-5", + api_key="fake_key", + reasoning_effort="low", + ) + assert openai_client._create_args["reasoning_effort"] == "low" # pyright: ignore[reportPrivateUsage] + + # Test Azure OpenAI client with reasoning_effort + azure_client = AzureOpenAIChatCompletionClient( + model="gpt-5", + azure_endpoint="fake_endpoint", + azure_deployment="gpt-5-2025-08-07", + api_version="2025-02-01-preview", + api_key="fake_key", + reasoning_effort="medium", + ) + assert azure_client._create_args["reasoning_effort"] == "medium" # pyright: ignore[reportPrivateUsage] + + # Test load_component with reasoning_effort for OpenAI + from autogen_core.models import ChatCompletionClient + + openai_config = { + "provider": "OpenAIChatCompletionClient", + "config": { + "model": "gpt-5", + "api_key": "fake_key", + "reasoning_effort": "high", + }, + } + + loaded_openai_client = ChatCompletionClient.load_component(openai_config) + assert loaded_openai_client._create_args["reasoning_effort"] == "high" # type: ignore[attr-defined] # pyright: ignore[reportPrivateUsage, reportUnknownMemberType, reportAttributeAccessIssue] + assert loaded_openai_client._raw_config["reasoning_effort"] == "high" # type: ignore[attr-defined] # pyright: ignore[reportPrivateUsage, reportUnknownMemberType, reportAttributeAccessIssue] + + # Test load_component with reasoning_effort for Azure OpenAI + azure_config = { + "provider": "AzureOpenAIChatCompletionClient", + "config": { + "model": "gpt-5", + "azure_endpoint": "fake_endpoint", + "azure_deployment": "gpt-5-2025-08-07", + "api_version": "2025-02-01-preview", + "api_key": "fake_key", + "reasoning_effort": "low", + }, + } + + loaded_azure_client = ChatCompletionClient.load_component(azure_config) + assert loaded_azure_client._create_args["reasoning_effort"] == "low" # type: ignore[attr-defined] # pyright: ignore[reportPrivateUsage, reportUnknownMemberType, reportAttributeAccessIssue] + assert loaded_azure_client._raw_config["reasoning_effort"] == "low" # type: ignore[attr-defined] # pyright: ignore[reportPrivateUsage, reportUnknownMemberType, reportAttributeAccessIssue] + + # Test serialization and deserialization + config_dict = openai_client.dump_component() + reloaded_client = OpenAIChatCompletionClient.load_component(config_dict) + assert reloaded_client._create_args["reasoning_effort"] == "low" # pyright: ignore[reportPrivateUsage] + + +@pytest.mark.asyncio +async def test_reasoning_effort_validation() -> None: + """Test reasoning_effort parameter validation.""" + + # Test valid values + for valid_value in ["low", "medium", "high"]: + client = OpenAIChatCompletionClient( + model="gpt-5", + api_key="fake_key", + reasoning_effort=valid_value, # type: ignore[arg-type] # pyright: ignore[reportArgumentType] + ) + assert client._create_args["reasoning_effort"] == valid_value # pyright: ignore[reportPrivateUsage] + + # Test None value (should be included if explicitly set) + client_with_none = OpenAIChatCompletionClient( + model="gpt-5", + api_key="fake_key", + reasoning_effort=None, + ) + # When explicitly set to None, it will be included in create_args + assert client_with_none._create_args["reasoning_effort"] is None # pyright: ignore[reportPrivateUsage] + + # Test not providing reasoning_effort (should not be in create_args) + client_without_reasoning = OpenAIChatCompletionClient( + model="gpt-5", + api_key="fake_key", + ) + assert "reasoning_effort" not in client_without_reasoning._create_args # pyright: ignore[reportPrivateUsage] + + # Test invalid value via load_component (Pydantic validation) + from pydantic import ValidationError + + with pytest.raises(ValidationError): # Should raise ValidationError + from autogen_core.models import ChatCompletionClient + + config = { + "provider": "OpenAIChatCompletionClient", + "config": { + "model": "gpt-5", + "api_key": "fake_key", + "reasoning_effort": "invalid_value", + }, + } + + ChatCompletionClient.load_component(config)