diff --git a/homeassistant/components/openai_conversation/__init__.py b/homeassistant/components/openai_conversation/__init__.py index 28ada1a71d5..edceaf1d6df 100644 --- a/homeassistant/components/openai_conversation/__init__.py +++ b/homeassistant/components/openai_conversation/__init__.py @@ -46,6 +46,7 @@ from .const import ( CONF_MAX_TOKENS, CONF_PROMPT, CONF_REASONING_EFFORT, + CONF_REASONING_SUMMARY, CONF_STORE_RESPONSES, CONF_TEMPERATURE, CONF_TOP_P, @@ -59,6 +60,7 @@ from .const import ( RECOMMENDED_CHAT_MODEL, RECOMMENDED_MAX_TOKENS, RECOMMENDED_REASONING_EFFORT, + RECOMMENDED_REASONING_SUMMARY, RECOMMENDED_STORE_RESPONSES, RECOMMENDED_STT_OPTIONS, RECOMMENDED_TEMPERATURE, @@ -490,6 +492,25 @@ async def async_migrate_entry(hass: HomeAssistant, entry: OpenAIConfigEntry) -> _add_stt_subentry(hass, entry) hass.config_entries.async_update_entry(entry, minor_version=6) + if entry.version == 2 and entry.minor_version == 6: + for subentry in entry.subentries.values(): + if subentry.subentry_type in ("conversation", "ai_task_data"): + data = dict(subentry.data) + updated = False + if data.get(CONF_REASONING_SUMMARY) == "short": + data[CONF_REASONING_SUMMARY] = "concise" + updated = True + if data.get(CONF_REASONING_SUMMARY) == "concise" and not data.get( + CONF_CHAT_MODEL, "" + ).startswith("gpt-5"): + data[CONF_REASONING_SUMMARY] = RECOMMENDED_REASONING_SUMMARY + updated = True + if updated: + hass.config_entries.async_update_subentry( + entry, subentry, data=data + ) + hass.config_entries.async_update_entry(entry, minor_version=7) + LOGGER.debug( "Migration to version %s:%s successful", entry.version, entry.minor_version ) diff --git a/homeassistant/components/openai_conversation/config_flow.py b/homeassistant/components/openai_conversation/config_flow.py index 8294cd5b51f..d54c6a7624b 100644 --- a/homeassistant/components/openai_conversation/config_flow.py +++ b/homeassistant/components/openai_conversation/config_flow.py @@ -127,7 +127,7 @@ class OpenAIConfigFlow(ConfigFlow, domain=DOMAIN): """Handle a config flow for OpenAI Conversation.""" VERSION = 2 - MINOR_VERSION = 6 + MINOR_VERSION = 7 async def async_step_user( self, user_input: dict[str, Any] | None = None @@ -435,23 +435,37 @@ class OpenAISubentryFlowHandler(ConfigSubentryFlow): mode=SelectSelectorMode.DROPDOWN, ) ), + } + ) + elif CONF_VERBOSITY in options: + options.pop(CONF_VERBOSITY) + + if model.startswith(("o", "gpt-5")): + reasoning_summary_options = ["off", "auto", "concise", "detailed"] + if model.startswith("o"): + reasoning_summary_options.remove("concise") + stored_summary = options.get( + CONF_REASONING_SUMMARY, RECOMMENDED_REASONING_SUMMARY + ) + if stored_summary not in reasoning_summary_options: + stored_summary = RECOMMENDED_REASONING_SUMMARY + options[CONF_REASONING_SUMMARY] = stored_summary + step_schema.update( + { vol.Optional( CONF_REASONING_SUMMARY, - default=RECOMMENDED_REASONING_SUMMARY, + default=stored_summary, ): SelectSelector( SelectSelectorConfig( - options=["off", "auto", "short", "detailed"], + options=reasoning_summary_options, translation_key=CONF_REASONING_SUMMARY, mode=SelectSelectorMode.DROPDOWN, ) ), } ) - elif CONF_VERBOSITY in options: - options.pop(CONF_VERBOSITY) - if CONF_REASONING_SUMMARY in options: - if not model.startswith("gpt-5"): - options.pop(CONF_REASONING_SUMMARY) + elif CONF_REASONING_SUMMARY in options: + options.pop(CONF_REASONING_SUMMARY) service_tiers = self._get_service_tiers(model) if "flex" in service_tiers or "priority" in service_tiers: diff --git a/homeassistant/components/openai_conversation/entity.py b/homeassistant/components/openai_conversation/entity.py index b3aeb8f71b6..bf7d7633917 100644 --- a/homeassistant/components/openai_conversation/entity.py +++ b/homeassistant/components/openai_conversation/entity.py @@ -43,7 +43,10 @@ from openai.types.responses import ( ToolParam, WebSearchToolParam, ) -from openai.types.responses.response_create_params import ResponseCreateParamsStreaming +from openai.types.responses.response_create_params import ( + Reasoning, + ResponseCreateParamsStreaming, +) from openai.types.responses.response_input_param import ( FunctionCallOutput, ImageGenerationCall as ImageGenerationCallParam, @@ -520,16 +523,19 @@ class OpenAIBaseLLMEntity(Entity): ) if model_args["model"].startswith(("o", "gpt-5")): - model_args["reasoning"] = { + reasoning: Reasoning = { "effort": options.get( CONF_REASONING_EFFORT, RECOMMENDED_REASONING_EFFORT ) if not model_args["model"].startswith("gpt-5-pro") else "high", # GPT-5 pro only supports reasoning.effort: high - "summary": options.get( - CONF_REASONING_SUMMARY, RECOMMENDED_REASONING_SUMMARY - ), } + reasoning_summary = options.get( + CONF_REASONING_SUMMARY, RECOMMENDED_REASONING_SUMMARY + ) + if reasoning_summary != "off": + reasoning["summary"] = reasoning_summary + model_args["reasoning"] = reasoning model_args["include"] = ["reasoning.encrypted_content"] if ( diff --git a/homeassistant/components/openai_conversation/strings.json b/homeassistant/components/openai_conversation/strings.json index 58e0f35ba3c..3193581a3e5 100644 --- a/homeassistant/components/openai_conversation/strings.json +++ b/homeassistant/components/openai_conversation/strings.json @@ -242,9 +242,9 @@ "reasoning_summary": { "options": { "auto": "[%key:common::state::auto%]", + "concise": "Concise", "detailed": "Detailed", - "off": "[%key:common::state::off%]", - "short": "Short" + "off": "[%key:common::state::off%]" } }, "search_context_size": { diff --git a/tests/components/openai_conversation/conftest.py b/tests/components/openai_conversation/conftest.py index 37bf0938a0b..3e709975f75 100644 --- a/tests/components/openai_conversation/conftest.py +++ b/tests/components/openai_conversation/conftest.py @@ -58,7 +58,7 @@ def mock_config_entry( "api_key": "bla", }, version=2, - minor_version=6, + minor_version=7, subentries_data=[ ConfigSubentryData( data=mock_conversation_subentry_data, diff --git a/tests/components/openai_conversation/test_config_flow.py b/tests/components/openai_conversation/test_config_flow.py index 6bdee46d720..5ad9b75bd99 100644 --- a/tests/components/openai_conversation/test_config_flow.py +++ b/tests/components/openai_conversation/test_config_flow.py @@ -119,7 +119,7 @@ async def test_form(hass: HomeAssistant) -> None: }, ] assert result2["version"] == 2 - assert result2["minor_version"] == 6 + assert result2["minor_version"] == 7 assert len(mock_setup_entry.mock_calls) == 1 @@ -278,10 +278,10 @@ async def test_subentry_unsupported_model( ) async def test_subentry_reasoning_effort_list( hass: HomeAssistant, - mock_config_entry, - mock_init_component, - model, - reasoning_effort_options, + mock_config_entry: MockConfigEntry, + mock_init_component: None, + model: str, + reasoning_effort_options: list[str], ) -> None: """Test the list reasoning effort options.""" subentry = next(iter(mock_config_entry.subentries.values())) @@ -318,6 +318,152 @@ async def test_subentry_reasoning_effort_list( ) +@pytest.mark.parametrize( + ("model", "has_reasoning_summary"), + [ + ("o3", True), + ("o4-mini", True), + ("gpt-5", True), + ("gpt-5-mini", True), + ("gpt-5-pro", True), + ("gpt-4o", False), + ("gpt-4.1", False), + ], +) +async def test_subentry_reasoning_summary_visibility( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_init_component: None, + model: str, + has_reasoning_summary: bool, +) -> None: + """Test that reasoning_summary option is shown for all reasoning models.""" + subentry = next(iter(mock_config_entry.subentries.values())) + subentry_flow = await mock_config_entry.start_subentry_reconfigure_flow( + hass, subentry.subentry_id + ) + assert subentry_flow["type"] is FlowResultType.FORM + assert subentry_flow["step_id"] == "init" + + # Configure initial step + subentry_flow = await hass.config_entries.subentries.async_configure( + subentry_flow["flow_id"], + { + CONF_RECOMMENDED: False, + CONF_PROMPT: "Speak like a pirate", + CONF_LLM_HASS_API: ["assist"], + }, + ) + assert subentry_flow["type"] is FlowResultType.FORM + assert subentry_flow["step_id"] == "advanced" + + # Configure advanced step + subentry_flow = await hass.config_entries.subentries.async_configure( + subentry_flow["flow_id"], + { + CONF_CHAT_MODEL: model, + }, + ) + assert subentry_flow["type"] is FlowResultType.FORM + assert subentry_flow["step_id"] == "model" + assert (CONF_REASONING_SUMMARY in subentry_flow["data_schema"].schema) == ( + has_reasoning_summary + ) + + +@pytest.mark.parametrize( + ("model", "reasoning_summary_options"), + [ + ("o3", ["off", "auto", "detailed"]), + ("o4-mini", ["off", "auto", "detailed"]), + ("gpt-5", ["off", "auto", "concise", "detailed"]), + ("gpt-5-mini", ["off", "auto", "concise", "detailed"]), + ], +) +async def test_subentry_reasoning_summary_options( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_init_component: None, + model: str, + reasoning_summary_options: list[str], +) -> None: + """Test the list of reasoning summary options for reasoning models.""" + subentry = next(iter(mock_config_entry.subentries.values())) + subentry_flow = await mock_config_entry.start_subentry_reconfigure_flow( + hass, subentry.subentry_id + ) + assert subentry_flow["type"] is FlowResultType.FORM + assert subentry_flow["step_id"] == "init" + + subentry_flow = await hass.config_entries.subentries.async_configure( + subentry_flow["flow_id"], + { + CONF_RECOMMENDED: False, + CONF_PROMPT: "Speak like a pirate", + CONF_LLM_HASS_API: ["assist"], + }, + ) + assert subentry_flow["type"] is FlowResultType.FORM + assert subentry_flow["step_id"] == "advanced" + + subentry_flow = await hass.config_entries.subentries.async_configure( + subentry_flow["flow_id"], + { + CONF_CHAT_MODEL: model, + }, + ) + assert subentry_flow["type"] is FlowResultType.FORM + assert subentry_flow["step_id"] == "model" + assert ( + subentry_flow["data_schema"].schema[CONF_REASONING_SUMMARY].config["options"] + == reasoning_summary_options + ) + + +async def test_subentry_reasoning_summary_default_sanitized_on_model_switch( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_init_component: None, +) -> None: + """Test that a stored 'concise' default is sanitized to 'auto' for o* models.""" + subentry = next( + s + for s in mock_config_entry.subentries.values() + if s.subentry_type == "conversation" + ) + hass.config_entries.async_update_subentry( + mock_config_entry, + subentry, + data={**subentry.data, CONF_REASONING_SUMMARY: "concise"}, + ) + await hass.async_block_till_done() + + subentry_flow = await mock_config_entry.start_subentry_reconfigure_flow( + hass, subentry.subentry_id + ) + assert subentry_flow["step_id"] == "init" + + subentry_flow = await hass.config_entries.subentries.async_configure( + subentry_flow["flow_id"], + { + CONF_RECOMMENDED: False, + CONF_PROMPT: "Speak like a pirate", + CONF_LLM_HASS_API: ["assist"], + }, + ) + assert subentry_flow["step_id"] == "advanced" + + subentry_flow = await hass.config_entries.subentries.async_configure( + subentry_flow["flow_id"], + {CONF_CHAT_MODEL: "o3"}, + ) + assert subentry_flow["step_id"] == "model" + + schema = subentry_flow["data_schema"].schema + summary_key = next(k for k in schema if k == CONF_REASONING_SUMMARY) + assert summary_key.default() == RECOMMENDED_REASONING_SUMMARY + + @pytest.mark.parametrize( ("model", "service_tier_options"), [ @@ -544,6 +690,7 @@ async def test_form_invalid_auth(hass: HomeAssistant, side_effect, error) -> Non CONF_MAX_TOKENS: 10000, CONF_STORE_RESPONSES: False, CONF_REASONING_EFFORT: "high", + CONF_REASONING_SUMMARY: RECOMMENDED_REASONING_SUMMARY, CONF_CODE_INTERPRETER: True, }, ), @@ -810,6 +957,7 @@ async def test_form_invalid_auth(hass: HomeAssistant, side_effect, error) -> Non CONF_MAX_TOKENS: 1000, CONF_STORE_RESPONSES: False, CONF_REASONING_EFFORT: "low", + CONF_REASONING_SUMMARY: RECOMMENDED_REASONING_SUMMARY, CONF_CODE_INTERPRETER: True, }, ), @@ -823,6 +971,7 @@ async def test_form_invalid_auth(hass: HomeAssistant, side_effect, error) -> Non CONF_TOP_P: 0.9, CONF_MAX_TOKENS: 1000, CONF_REASONING_EFFORT: "low", + CONF_REASONING_SUMMARY: "auto", CONF_SERVICE_TIER: "flex", CONF_CODE_INTERPRETER: True, CONF_VERBOSITY: "medium", diff --git a/tests/components/openai_conversation/test_conversation.py b/tests/components/openai_conversation/test_conversation.py index eac9a2bac0f..c9a545c8065 100644 --- a/tests/components/openai_conversation/test_conversation.py +++ b/tests/components/openai_conversation/test_conversation.py @@ -19,7 +19,9 @@ from homeassistant.components import conversation from homeassistant.components.homeassistant.exposed_entities import async_expose_entity from homeassistant.components.intent import async_register_timer_handler from homeassistant.components.openai_conversation.const import ( + CONF_CHAT_MODEL, CONF_CODE_INTERPRETER, + CONF_REASONING_SUMMARY, CONF_SERVICE_TIER, CONF_STORE_RESPONSES, CONF_WEB_SEARCH, @@ -385,6 +387,46 @@ async def test_function_call_without_reasoning( assert mock_chat_log.content[1:] == snapshot +@freeze_time("2025-10-31 18:00:00") +async def test_reasoning_summary_off_omits_summary_key( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_init_component: None, + mock_create_stream: AsyncMock, + mock_chat_log: MockChatLog, # noqa: F811 +) -> None: + """Test that reasoning summary 'off' omits the summary key from the API call.""" + conversation_subentry = next( + subentry + for subentry in mock_config_entry.subentries.values() + if subentry.subentry_type == "conversation" + ) + hass.config_entries.async_update_subentry( + mock_config_entry, + conversation_subentry, + data={ + CONF_CHAT_MODEL: "o4-mini", + CONF_REASONING_SUMMARY: "off", + }, + ) + await hass.async_block_till_done() + + mock_create_stream.return_value = [ + create_message_item(id="msg_A", text="Hello", output_index=0), + ] + + await conversation.async_converse( + hass, + "Hello", + mock_chat_log.conversation_id, + Context(), + agent_id="conversation.openai_conversation", + ) + + reasoning = mock_create_stream.call_args.kwargs["reasoning"] + assert "summary" not in reasoning + + @pytest.mark.parametrize( ("description", "messages"), [ diff --git a/tests/components/openai_conversation/test_init.py b/tests/components/openai_conversation/test_init.py index acfb57abae0..fff61052000 100644 --- a/tests/components/openai_conversation/test_init.py +++ b/tests/components/openai_conversation/test_init.py @@ -19,6 +19,7 @@ from syrupy.filters import props from homeassistant.components.openai_conversation import CONF_CHAT_MODEL from homeassistant.components.openai_conversation.const import ( + CONF_REASONING_SUMMARY, CONF_STORE_RESPONSES, DEFAULT_AI_TASK_NAME, DEFAULT_CONVERSATION_NAME, @@ -680,7 +681,7 @@ async def test_migration_from_v1( await hass.async_block_till_done() assert mock_config_entry.version == 2 - assert mock_config_entry.minor_version == 6 + assert mock_config_entry.minor_version == 7 assert mock_config_entry.data == {"api_key": "1234"} assert mock_config_entry.options == {} @@ -825,7 +826,7 @@ async def test_migration_from_v1_with_multiple_keys( for idx, entry in enumerate(entries): assert entry.version == 2 - assert entry.minor_version == 6 + assert entry.minor_version == 7 assert not entry.options assert len(entry.subentries) == 4 @@ -930,7 +931,7 @@ async def test_migration_from_v1_with_same_keys( entry = entries[0] assert entry.version == 2 - assert entry.minor_version == 6 + assert entry.minor_version == 7 assert not entry.options assert ( len(entry.subentries) == 5 @@ -1142,7 +1143,7 @@ async def test_migration_from_v1_disabled( assert entry.disabled_by is merged_config_entry_disabled_by assert entry.version == 2 assert entry.minor_version == ( - 4 if merged_config_entry_disabled_by is not None else 6 + 4 if merged_config_entry_disabled_by is not None else 7 ) assert not entry.options assert entry.title == "OpenAI Conversation" @@ -1315,7 +1316,7 @@ async def test_migration_from_v2_1( assert len(entries) == 1 entry = entries[0] assert entry.version == 2 - assert entry.minor_version == 6 + assert entry.minor_version == 7 assert not entry.options assert entry.title == "ChatGPT" assert len(entry.subentries) == 5 # 2 conversation + 1 AI task + 1 STT + 1 TTS @@ -1408,15 +1409,18 @@ async def test_devices( ) assert len(devices) == 4 # One for conversation, AI task, STT, and TTS - # Find the conversation device for snapshot comparison - device = next(d for d in devices if d.name == "OpenAI Conversation") - assert device == snapshot(exclude=props("identifiers")) - # Verify the device has identifiers matching one of the subentries - expected_identifiers = [ - {(DOMAIN, subentry.subentry_id)} + # Find the conversation subentry device specifically, since device ordering + # from concurrent platform setup is non-deterministic. + conversation_subentry = next( + subentry for subentry in mock_config_entry.subentries.values() - ] - assert device.identifiers in expected_identifiers + if subentry.subentry_type == "conversation" + ) + device = device_registry.async_get_device( + identifiers={(DOMAIN, conversation_subentry.subentry_id)} + ) + assert device is not None + assert device == snapshot(exclude=props("identifiers")) async def test_migration_from_v2_2( @@ -1463,7 +1467,7 @@ async def test_migration_from_v2_2( assert len(entries) == 1 entry = entries[0] assert entry.version == 2 - assert entry.minor_version == 6 + assert entry.minor_version == 7 assert not entry.options assert entry.title == "ChatGPT" assert len(entry.subentries) == 4 @@ -1508,7 +1512,7 @@ async def test_migration_from_v2_2( DeviceEntryDisabler.CONFIG_ENTRY, RegistryEntryDisabler.CONFIG_ENTRY, True, - 6, + 7, None, DeviceEntryDisabler.USER, RegistryEntryDisabler.DEVICE, @@ -1518,7 +1522,7 @@ async def test_migration_from_v2_2( DeviceEntryDisabler.USER, RegistryEntryDisabler.DEVICE, True, - 6, + 7, None, DeviceEntryDisabler.USER, RegistryEntryDisabler.DEVICE, @@ -1528,7 +1532,7 @@ async def test_migration_from_v2_2( DeviceEntryDisabler.USER, RegistryEntryDisabler.USER, True, - 6, + 7, None, DeviceEntryDisabler.USER, RegistryEntryDisabler.USER, @@ -1538,7 +1542,7 @@ async def test_migration_from_v2_2( None, None, True, - 6, + 7, None, None, None, @@ -1730,7 +1734,7 @@ async def test_migration_from_v2_4( assert len(entries) == 1 entry = entries[0] assert entry.version == 2 - assert entry.minor_version == 6 + assert entry.minor_version == 7 assert not entry.options assert entry.title == "ChatGPT" assert len(entry.subentries) == 4 @@ -1833,7 +1837,7 @@ async def test_migration_from_v2_5( assert len(entries) == 1 entry = entries[0] assert entry.version == 2 - assert entry.minor_version == 6 + assert entry.minor_version == 7 assert not entry.options assert entry.title == "ChatGPT" assert len(entry.subentries) == 4 @@ -1878,3 +1882,124 @@ async def test_migration_from_v2_5( stt_subentry = stt_subentries[0] assert stt_subentry.data == {} assert stt_subentry.title == "OpenAI STT" + + +async def test_migration_from_v2_6( + hass: HomeAssistant, +) -> None: + """Test migration from version 2.6. + + Ensures reasoning_summary "short" is renamed to "concise" for gpt-5 models, + and that "concise" (whether from "short" or already stored) is reset to "auto" + for o* models where it is unsupported. Other values and unrelated subentries + are unchanged. + """ + conversation_options_short_o = { + "chat_model": "o4-mini", + CONF_REASONING_SUMMARY: "short", + } + conversation_options_auto = { + "chat_model": "gpt-5-mini", + CONF_REASONING_SUMMARY: "auto", + } + ai_task_options_short_o = { + "chat_model": "o3", + CONF_REASONING_SUMMARY: "short", + } + tts_options = { + "chat_model": "gpt-4o-mini-tts", + } + conversation_options_short_gpt5 = { + "chat_model": "gpt-5", + CONF_REASONING_SUMMARY: "short", + } + conversation_options_concise_o = { + "chat_model": "o3", + CONF_REASONING_SUMMARY: "concise", + } + mock_config_entry = MockConfigEntry( + domain=DOMAIN, + data={"api_key": "1234"}, + entry_id="mock_entry_id", + version=2, + minor_version=6, + subentries_data=[ + ConfigSubentryData( + data=conversation_options_short_o, + subentry_id="mock_id_1", + subentry_type="conversation", + title="ChatGPT short o", + unique_id=None, + ), + ConfigSubentryData( + data=conversation_options_auto, + subentry_id="mock_id_2", + subentry_type="conversation", + title="ChatGPT auto", + unique_id=None, + ), + ConfigSubentryData( + data=ai_task_options_short_o, + subentry_id="mock_id_3", + subentry_type="ai_task_data", + title="OpenAI AI Task", + unique_id=None, + ), + ConfigSubentryData( + data=tts_options, + subentry_id="mock_id_4", + subentry_type="tts", + title="OpenAI TTS", + unique_id=None, + ), + ConfigSubentryData( + data=conversation_options_short_gpt5, + subentry_id="mock_id_5", + subentry_type="conversation", + title="ChatGPT short gpt5", + unique_id=None, + ), + ConfigSubentryData( + data=conversation_options_concise_o, + subentry_id="mock_id_6", + subentry_type="conversation", + title="ChatGPT concise o", + unique_id=None, + ), + ], + title="ChatGPT", + ) + mock_config_entry.add_to_hass(hass) + + with patch( + "homeassistant.components.openai_conversation.async_setup_entry", + return_value=True, + ): + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + + entries = hass.config_entries.async_entries(DOMAIN) + assert len(entries) == 1 + entry = entries[0] + assert entry.version == 2 + assert entry.minor_version == 7 + + subentries_by_id = entry.subentries + + # "short" on an o* model: short→concise→auto (concise unsupported on o*) + assert subentries_by_id["mock_id_1"].data[CONF_REASONING_SUMMARY] == "auto" + + # "auto" on a gpt-5 model: unchanged + assert subentries_by_id["mock_id_2"].data[CONF_REASONING_SUMMARY] == "auto" + + # "short" on an o* ai_task_data subentry: short→concise→auto + assert subentries_by_id["mock_id_3"].data[CONF_REASONING_SUMMARY] == "auto" + + # TTS subentry is unaffected + assert subentries_by_id["mock_id_4"].data == tts_options + + # "short" on a gpt-5 model: short→concise (concise is valid for gpt-5) + assert subentries_by_id["mock_id_5"].data[CONF_REASONING_SUMMARY] == "concise" + + # "concise" already stored on an o* model: concise→auto + assert subentries_by_id["mock_id_6"].data[CONF_REASONING_SUMMARY] == "auto"