diff --git a/homeassistant/components/openai_conversation/__init__.py b/homeassistant/components/openai_conversation/__init__.py index ad2c898bcd1f..ac25e4ec05e5 100644 --- a/homeassistant/components/openai_conversation/__init__.py +++ b/homeassistant/components/openai_conversation/__init__.py @@ -55,6 +55,7 @@ from .const import ( LOGGER, RECOMMENDED_AI_TASK_OPTIONS, RECOMMENDED_CHAT_MODEL, + RECOMMENDED_CONVERSATION_REASONING_EFFORT, RECOMMENDED_MAX_TOKENS, RECOMMENDED_REASONING_EFFORT, RECOMMENDED_REASONING_SUMMARY, @@ -217,7 +218,10 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: if model.startswith(("o", "gpt-5")): model_args["reasoning"] = { "effort": conversation_subentry.data.get( - CONF_REASONING_EFFORT, RECOMMENDED_REASONING_EFFORT + CONF_REASONING_EFFORT, + RECOMMENDED_CONVERSATION_REASONING_EFFORT + if model == RECOMMENDED_CHAT_MODEL + else RECOMMENDED_REASONING_EFFORT, ) } diff --git a/homeassistant/components/openai_conversation/ai_task.py b/homeassistant/components/openai_conversation/ai_task.py index 681ea6aaaf7a..1f3d9e65315e 100644 --- a/homeassistant/components/openai_conversation/ai_task.py +++ b/homeassistant/components/openai_conversation/ai_task.py @@ -16,6 +16,7 @@ from homeassistant.util.json import json_loads from .const import ( CONF_CHAT_MODEL, CONF_IMAGE_MODEL, + RECOMMENDED_AI_TASK_REASONING_EFFORT, RECOMMENDED_CHAT_MODEL, RECOMMENDED_IMAGE_MODEL, UNSUPPORTED_IMAGE_MODELS, @@ -52,6 +53,8 @@ class OpenAITaskEntity( ): """OpenAI AI Task entity.""" + _recommended_reasoning_effort = RECOMMENDED_AI_TASK_REASONING_EFFORT + def __init__(self, entry: OpenAIConfigEntry, subentry: ConfigSubentry) -> None: """Initialize the entity.""" super().__init__(entry, subentry) diff --git a/homeassistant/components/openai_conversation/const.py b/homeassistant/components/openai_conversation/const.py index 8c9fde6e5785..16498fe3dbd5 100644 --- a/homeassistant/components/openai_conversation/const.py +++ b/homeassistant/components/openai_conversation/const.py @@ -44,6 +44,8 @@ RECOMMENDED_IMAGE_MODEL = "gpt-image-2" RECOMMENDED_MAX_TOKENS = 3000 RECOMMENDED_PRO_MODE = False RECOMMENDED_REASONING_EFFORT = "low" +RECOMMENDED_CONVERSATION_REASONING_EFFORT = "minimal" +RECOMMENDED_AI_TASK_REASONING_EFFORT = "medium" RECOMMENDED_STORE_RESPONSES = False RECOMMENDED_REASONING_SUMMARY = "auto" RECOMMENDED_SERVICE_TIER = "auto" diff --git a/homeassistant/components/openai_conversation/conversation.py b/homeassistant/components/openai_conversation/conversation.py index 78c531f87162..148ef389e45c 100644 --- a/homeassistant/components/openai_conversation/conversation.py +++ b/homeassistant/components/openai_conversation/conversation.py @@ -9,7 +9,7 @@ from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from . import OpenAIConfigEntry -from .const import DOMAIN +from .const import DOMAIN, RECOMMENDED_CONVERSATION_REASONING_EFFORT from .entity import OpenAIBaseLLMEntity # Max number of back and forth with the LLM to generate a response @@ -39,6 +39,7 @@ class OpenAIConversationEntity( """OpenAI conversation agent.""" _attr_supports_streaming = True + _recommended_reasoning_effort = RECOMMENDED_CONVERSATION_REASONING_EFFORT def __init__(self, entry: OpenAIConfigEntry, subentry: ConfigSubentry) -> None: """Initialize the agent.""" diff --git a/homeassistant/components/openai_conversation/entity.py b/homeassistant/components/openai_conversation/entity.py index 5fa447e9b925..543017b64ae0 100644 --- a/homeassistant/components/openai_conversation/entity.py +++ b/homeassistant/components/openai_conversation/entity.py @@ -480,6 +480,8 @@ class OpenAIBaseLLMEntity(Entity): _attr_has_entity_name = True _attr_name: str | None = None + # Effort used in recommended mode, must be supported by RECOMMENDED_CHAT_MODEL + _recommended_reasoning_effort = RECOMMENDED_REASONING_EFFORT def __init__(self, entry: OpenAIConfigEntry, subentry: ConfigSubentry) -> None: """Initialize the entity.""" @@ -523,10 +525,14 @@ class OpenAIBaseLLMEntity(Entity): ) if model_args["model"].startswith(("o", "gpt-5")): + # Other models support a different set of efforts + recommended_effort = ( + self._recommended_reasoning_effort + if model_args["model"] == RECOMMENDED_CHAT_MODEL + else RECOMMENDED_REASONING_EFFORT + ) reasoning: Reasoning = { - "effort": options.get( - CONF_REASONING_EFFORT, RECOMMENDED_REASONING_EFFORT - ) + "effort": options.get(CONF_REASONING_EFFORT, recommended_effort) if not model_args["model"].startswith("gpt-5-pro") else "high", # GPT-5 pro only supports reasoning.effort: high } diff --git a/tests/components/openai_conversation/snapshots/test_conversation.ambr b/tests/components/openai_conversation/snapshots/test_conversation.ambr index dac962c59c7f..d5e7edc35ae3 100644 --- a/tests/components/openai_conversation/snapshots/test_conversation.ambr +++ b/tests/components/openai_conversation/snapshots/test_conversation.ambr @@ -297,7 +297,7 @@ }), ]) # --- -# name: test_model_args[subentry_options0] +# name: test_model_args[pro_mode] dict({ 'include': list([ 'reasoning.encrypted_content', @@ -318,6 +318,25 @@ }), }) # --- +# name: test_model_args[recommended] + dict({ + 'include': list([ + 'reasoning.encrypted_content', + ]), + 'max_output_tokens': 3000, + 'model': 'gpt-5-nano', + 'reasoning': dict({ + 'effort': 'minimal', + 'summary': 'auto', + }), + 'service_tier': 'auto', + 'store': False, + 'stream': True, + 'text': dict({ + 'verbosity': 'medium', + }), + }) +# --- # name: test_web_search[False] list([ dict({ diff --git a/tests/components/openai_conversation/test_ai_task.py b/tests/components/openai_conversation/test_ai_task.py index fe9f414c68fe..7b3624f9ee97 100644 --- a/tests/components/openai_conversation/test_ai_task.py +++ b/tests/components/openai_conversation/test_ai_task.py @@ -68,6 +68,26 @@ async def test_generate_data( assert mock_create_stream.call_args.kwargs["store"] is expected_store +@pytest.mark.usefixtures("mock_init_component", "mock_config_entry") +async def test_recommended_reasoning_effort( + hass: HomeAssistant, + mock_create_stream: AsyncMock, +) -> None: + """Test AI Task reasons more than conversation with recommended settings.""" + mock_create_stream.return_value = [ + create_message_item(id="msg_A", text="The test data", output_index=0) + ] + + await ai_task.async_generate_data( + hass, + task_name="Test Task", + entity_id="ai_task.openai_ai_task", + instructions="Generate test data", + ) + + assert mock_create_stream.call_args.kwargs["reasoning"]["effort"] == "medium" + + @pytest.mark.usefixtures("mock_init_component") async def test_generate_structured_data( hass: HomeAssistant, diff --git a/tests/components/openai_conversation/test_conversation.py b/tests/components/openai_conversation/test_conversation.py index 933e57bc7d1b..ace78bb841be 100644 --- a/tests/components/openai_conversation/test_conversation.py +++ b/tests/components/openai_conversation/test_conversation.py @@ -821,7 +821,13 @@ async def test_flex_tier_retry( @pytest.mark.parametrize( - "subentry_options", [{CONF_CHAT_MODEL: "gpt-5.6-sol", CONF_PRO_MODE: True}] + "subentry_options", + [ + pytest.param( + {CONF_CHAT_MODEL: "gpt-5.6-sol", CONF_PRO_MODE: True}, id="pro_mode" + ), + pytest.param({}, id="recommended"), + ], ) async def test_model_args( hass: HomeAssistant, diff --git a/tests/components/openai_conversation/test_init.py b/tests/components/openai_conversation/test_init.py index 20466fa29b03..1b4f2a4550bd 100644 --- a/tests/components/openai_conversation/test_init.py +++ b/tests/components/openai_conversation/test_init.py @@ -429,7 +429,7 @@ async def test_generate_content_service( service_data["config_entry"] = mock_config_entry.entry_id expected_args["model"] = "gpt-5-nano" expected_args["max_output_tokens"] = 3000 - expected_args["reasoning"] = {"effort": "low"} + expected_args["reasoning"] = {"effort": "minimal"} expected_args["user"] = None expected_args["store"] = store_responses expected_args["input"][0]["type"] = "message"