From 107da6e03fa9c9a0a23ec2c3ec6fb8766c497299 Mon Sep 17 00:00:00 2001 From: Denis Shulyaka Date: Wed, 9 Sep 2026 08:02:52 +0300 Subject: [PATCH] Add GPT-6 Astra support (#181693) --- .../openai_conversation/config_flow.py | 11 +- .../components/openai_conversation/entity.py | 11 +- .../snapshots/test_conversation.ambr | 125 +++++++++++++++++- .../openai_conversation/test_config_flow.py | 44 +++++- .../openai_conversation/test_conversation.py | 35 ++++- 5 files changed, 210 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/openai_conversation/config_flow.py b/homeassistant/components/openai_conversation/config_flow.py index de033b6f213f..953b983629d9 100644 --- a/homeassistant/components/openai_conversation/config_flow.py +++ b/homeassistant/components/openai_conversation/config_flow.py @@ -423,7 +423,7 @@ class OpenAISubentryFlowHandler(ConfigSubentryFlow): elif CONF_REASONING_EFFORT in options: options.pop(CONF_REASONING_EFFORT) - if model.startswith("gpt-5.6"): + if model.startswith(("gpt-5.6", "gpt-6")): step_schema.update( { vol.Optional( @@ -435,7 +435,7 @@ class OpenAISubentryFlowHandler(ConfigSubentryFlow): elif CONF_PRO_MODE in options: options.pop(CONF_PRO_MODE) - if model.startswith("gpt-5"): + if model.startswith(("gpt-5", "gpt-6")): step_schema.update( { vol.Optional( @@ -453,7 +453,7 @@ class OpenAISubentryFlowHandler(ConfigSubentryFlow): elif CONF_VERBOSITY in options: options.pop(CONF_VERBOSITY) - if model.startswith(("o", "gpt-5")): + if model.startswith(("o", "gpt-5", "gpt-6")): reasoning_summary_options = ["off", "auto", "concise", "detailed"] if model.startswith("o"): reasoning_summary_options.remove("concise") @@ -602,10 +602,13 @@ class OpenAISubentryFlowHandler(ConfigSubentryFlow): def _get_reasoning_options(self, model: str) -> list[str]: """Get reasoning effort options based on model.""" - if not model.startswith(("o", "gpt-5")) or model.startswith("gpt-5-pro"): + if not model.startswith(("o", "gpt-5", "gpt-6")) or model.startswith( + "gpt-5-pro" + ): return [] models_reasoning_map: dict[str | tuple[str, ...], list[str]] = { + "gpt-6": ["low", "medium", "high", "xhigh", "max"], "gpt-5.6": ["none", "low", "medium", "high", "xhigh", "max"], ("gpt-5.2-pro", "gpt-5.4-pro", "gpt-5.5-pro"): ["medium", "high", "xhigh"], ("gpt-5.2", "gpt-5.3", "gpt-5.4", "gpt-5.5"): [ diff --git a/homeassistant/components/openai_conversation/entity.py b/homeassistant/components/openai_conversation/entity.py index 8402b3d12058..27bb5eb2936d 100644 --- a/homeassistant/components/openai_conversation/entity.py +++ b/homeassistant/components/openai_conversation/entity.py @@ -523,7 +523,7 @@ class OpenAIBaseLLMEntity(Entity): stream=True, ) - if model_args["model"].startswith(("o", "gpt-5")): + if model_args["model"].startswith(("o", "gpt-5", "gpt-6")): reasoning: Reasoning = { "effort": options.get( CONF_REASONING_EFFORT, RECOMMENDED_REASONING_EFFORT @@ -545,7 +545,7 @@ class OpenAIBaseLLMEntity(Entity): model_args["include"] = ["reasoning.encrypted_content"] if ( - not model_args["model"].startswith("gpt-5") + not model_args["model"].startswith(("gpt-5", "gpt-6")) or model_args["reasoning"]["effort"] == "none" # type: ignore[index] ): model_args["top_p"] = options.get(CONF_TOP_P, RECOMMENDED_TOP_P) @@ -553,7 +553,7 @@ class OpenAIBaseLLMEntity(Entity): CONF_TEMPERATURE, RECOMMENDED_TEMPERATURE ) - if model_args["model"].startswith("gpt-5"): + if model_args["model"].startswith(("gpt-5", "gpt-6")): model_args["text"] = { "verbosity": options.get(CONF_VERBOSITY, RECOMMENDED_VERBOSITY) } @@ -561,7 +561,10 @@ class OpenAIBaseLLMEntity(Entity): if not model_args["model"].startswith( tuple(UNSUPPORTED_EXTENDED_CACHE_RETENTION_MODELS) ): - model_args["prompt_cache_retention"] = "24h" + if model_args["model"].startswith(("gpt-5.6", "gpt-6")): + model_args["prompt_cache_options"] = {"ttl": "30m"} + else: + model_args["prompt_cache_retention"] = "24h" tools: list[ToolParam] = [] if chat_log.llm_api: diff --git a/tests/components/openai_conversation/snapshots/test_conversation.ambr b/tests/components/openai_conversation/snapshots/test_conversation.ambr index dac962c59c7f..b71d0b21f8d0 100644 --- a/tests/components/openai_conversation/snapshots/test_conversation.ambr +++ b/tests/components/openai_conversation/snapshots/test_conversation.ambr @@ -298,13 +298,46 @@ ]) # --- # name: test_model_args[subentry_options0] + dict({ + 'max_output_tokens': 3000, + 'model': 'gpt-4o-mini', + 'service_tier': 'auto', + 'store': False, + 'stream': True, + 'temperature': 1.0, + 'top_p': 1.0, + }) +# --- +# name: test_model_args[subentry_options1] + dict({ + 'include': list([ + 'reasoning.encrypted_content', + ]), + 'max_output_tokens': 3000, + 'model': 'gpt-5.5', + 'prompt_cache_retention': '24h', + 'reasoning': dict({ + 'effort': 'low', + 'summary': 'auto', + }), + 'service_tier': 'auto', + 'store': False, + 'stream': True, + 'text': dict({ + 'verbosity': 'medium', + }), + }) +# --- +# name: test_model_args[subentry_options2] dict({ 'include': list([ 'reasoning.encrypted_content', ]), 'max_output_tokens': 3000, 'model': 'gpt-5.6-sol', - 'prompt_cache_retention': '24h', + 'prompt_cache_options': dict({ + 'ttl': '30m', + }), 'reasoning': dict({ 'effort': 'low', 'mode': 'pro', @@ -318,6 +351,96 @@ }), }) # --- +# name: test_model_args[subentry_options3] + dict({ + 'include': list([ + 'reasoning.encrypted_content', + ]), + 'max_output_tokens': 3000, + 'model': 'gpt-5.6-sol', + 'prompt_cache_options': dict({ + 'ttl': '30m', + }), + 'reasoning': dict({ + 'effort': 'none', + 'summary': 'auto', + }), + 'service_tier': 'auto', + 'store': False, + 'stream': True, + 'temperature': 0.5, + 'text': dict({ + 'verbosity': 'medium', + }), + 'top_p': 0.9, + }) +# --- +# name: test_model_args[subentry_options4] + dict({ + 'include': list([ + 'reasoning.encrypted_content', + ]), + 'max_output_tokens': 3000, + 'model': 'gpt-6-astra', + 'prompt_cache_options': dict({ + 'ttl': '30m', + }), + 'reasoning': dict({ + 'effort': 'low', + 'summary': 'auto', + }), + 'service_tier': 'auto', + 'store': False, + 'stream': True, + 'text': dict({ + 'verbosity': 'medium', + }), + }) +# --- +# name: test_model_args[subentry_options5] + dict({ + 'include': list([ + 'reasoning.encrypted_content', + ]), + 'max_output_tokens': 3000, + 'model': 'gpt-6-astra', + 'prompt_cache_options': dict({ + 'ttl': '30m', + }), + 'reasoning': dict({ + 'effort': 'max', + 'mode': 'pro', + 'summary': 'detailed', + }), + 'service_tier': 'auto', + 'store': False, + 'stream': True, + 'text': dict({ + 'verbosity': 'low', + }), + }) +# --- +# name: test_model_args[subentry_options6] + dict({ + 'include': list([ + 'reasoning.encrypted_content', + ]), + 'max_output_tokens': 3000, + 'model': 'gpt-6-astra', + 'prompt_cache_options': dict({ + 'ttl': '30m', + }), + 'reasoning': dict({ + 'effort': 'high', + }), + 'service_tier': 'auto', + 'store': False, + 'stream': True, + 'text': dict({ + 'verbosity': 'high', + }), + }) +# --- # name: test_web_search[False] list([ dict({ diff --git a/tests/components/openai_conversation/test_config_flow.py b/tests/components/openai_conversation/test_config_flow.py index 132befddc8d9..6b95171c92a1 100644 --- a/tests/components/openai_conversation/test_config_flow.py +++ b/tests/components/openai_conversation/test_config_flow.py @@ -275,6 +275,7 @@ async def test_subentry_unsupported_model( ("gpt-5.5", ["none", "low", "medium", "high", "xhigh"]), ("gpt-5.5-pro", ["medium", "high", "xhigh"]), ("gpt-5.6", ["none", "low", "medium", "high", "xhigh", "max"]), + ("gpt-6-astra", ["low", "medium", "high", "xhigh", "max"]), ], ) async def test_subentry_reasoning_effort_list( @@ -327,6 +328,7 @@ async def test_subentry_reasoning_effort_list( ("gpt-5", True), ("gpt-5-mini", True), ("gpt-5-pro", True), + ("gpt-6-astra", True), ("gpt-4o", False), ("gpt-4.1", False), ], @@ -379,6 +381,7 @@ async def test_subentry_reasoning_summary_visibility( ("o4-mini", ["off", "auto", "detailed"]), ("gpt-5", ["off", "auto", "concise", "detailed"]), ("gpt-5-mini", ["off", "auto", "concise", "detailed"]), + ("gpt-6-astra", ["off", "auto", "concise", "detailed"]), ], ) async def test_subentry_reasoning_summary_options( @@ -1090,15 +1093,46 @@ async def test_form_invalid_auth(hass: HomeAssistant, side_effect, error) -> Non CONF_WEB_SEARCH_INLINE_CITATIONS: True, }, ), + ( + {}, + ( + {CONF_RECOMMENDED: False}, + {CONF_CHAT_MODEL: "gpt-6-astra"}, + { + CONF_REASONING_EFFORT: "max", + CONF_REASONING_SUMMARY: "detailed", + CONF_PRO_MODE: True, + CONF_VERBOSITY: "low", + }, + ), + { + CONF_RECOMMENDED: False, + CONF_CHAT_MODEL: "gpt-6-astra", + CONF_MAX_TOKENS: RECOMMENDED_MAX_TOKENS, + CONF_TOP_P: RECOMMENDED_TOP_P, + CONF_TEMPERATURE: 1.0, + CONF_STORE_RESPONSES: False, + CONF_CODE_INTERPRETER: False, + CONF_REASONING_EFFORT: "max", + CONF_REASONING_SUMMARY: "detailed", + CONF_PRO_MODE: True, + CONF_VERBOSITY: "low", + CONF_SERVICE_TIER: "auto", + CONF_WEB_SEARCH: False, + CONF_WEB_SEARCH_CONTEXT_SIZE: "medium", + CONF_WEB_SEARCH_USER_LOCATION: False, + CONF_WEB_SEARCH_INLINE_CITATIONS: False, + }, + ), ], ) +@pytest.mark.usefixtures("mock_init_component") async def test_subentry_switching( hass: HomeAssistant, - mock_config_entry, - mock_init_component, - current_options, - new_options, - expected_options, + mock_config_entry: MockConfigEntry, + current_options: dict[str, str | float | bool | list[str]], + new_options: tuple[dict[str, str | float | bool | list[str]], ...], + expected_options: dict[str, str | float | bool | list[str]], ) -> None: """Test the subentry form.""" subentry = next( diff --git a/tests/components/openai_conversation/test_conversation.py b/tests/components/openai_conversation/test_conversation.py index 199ccbb23b00..c1be02036498 100644 --- a/tests/components/openai_conversation/test_conversation.py +++ b/tests/components/openai_conversation/test_conversation.py @@ -22,9 +22,13 @@ from homeassistant.components.openai_conversation.const import ( CONF_CHAT_MODEL, CONF_CODE_INTERPRETER, CONF_PRO_MODE, + CONF_REASONING_EFFORT, CONF_REASONING_SUMMARY, CONF_SERVICE_TIER, CONF_STORE_RESPONSES, + CONF_TEMPERATURE, + CONF_TOP_P, + CONF_VERBOSITY, CONF_WEB_SEARCH, CONF_WEB_SEARCH_CITY, CONF_WEB_SEARCH_CONTEXT_SIZE, @@ -821,7 +825,34 @@ async def test_flex_tier_retry( @pytest.mark.parametrize( - "subentry_options", [{CONF_CHAT_MODEL: "gpt-5.6-sol", CONF_PRO_MODE: True}] + "subentry_options", + [ + {CONF_CHAT_MODEL: "gpt-4o-mini"}, + {CONF_CHAT_MODEL: "gpt-5.5"}, + {CONF_CHAT_MODEL: "gpt-5.6-sol", CONF_PRO_MODE: True}, + { + CONF_CHAT_MODEL: "gpt-5.6-sol", + CONF_REASONING_EFFORT: "none", + CONF_TEMPERATURE: 0.5, + CONF_TOP_P: 0.9, + }, + {CONF_CHAT_MODEL: "gpt-6-astra"}, + { + CONF_CHAT_MODEL: "gpt-6-astra", + CONF_REASONING_EFFORT: "max", + CONF_PRO_MODE: True, + CONF_REASONING_SUMMARY: "detailed", + CONF_VERBOSITY: "low", + CONF_TEMPERATURE: 0.5, + CONF_TOP_P: 0.9, + }, + { + CONF_CHAT_MODEL: "gpt-6-astra", + CONF_REASONING_EFFORT: "high", + CONF_REASONING_SUMMARY: "off", + CONF_VERBOSITY: "high", + }, + ], ) @pytest.mark.usefixtures("mock_init_component") async def test_model_args( @@ -829,7 +860,7 @@ async def test_model_args( mock_config_entry: MockConfigEntry, mock_create_stream: AsyncMock, snapshot: SnapshotAssertion, - subentry_options: dict[str, str | bool], + subentry_options: dict[str, str | bool | float], ) -> None: """Test model arguments for various configuration."""