mirror of
https://github.com/home-assistant/core.git
synced 2026-08-24 10:13:52 -05:00
OpenAI GPT-5.6 support (#176450)
This commit is contained in:
@@ -48,6 +48,7 @@ from .const import (
|
||||
CONF_CODE_INTERPRETER,
|
||||
CONF_IMAGE_MODEL,
|
||||
CONF_MAX_TOKENS,
|
||||
CONF_PRO_MODE,
|
||||
CONF_REASONING_EFFORT,
|
||||
CONF_REASONING_SUMMARY,
|
||||
CONF_RECOMMENDED,
|
||||
@@ -77,6 +78,7 @@ from .const import (
|
||||
RECOMMENDED_CONVERSATION_OPTIONS,
|
||||
RECOMMENDED_IMAGE_MODEL,
|
||||
RECOMMENDED_MAX_TOKENS,
|
||||
RECOMMENDED_PRO_MODE,
|
||||
RECOMMENDED_REASONING_EFFORT,
|
||||
RECOMMENDED_REASONING_SUMMARY,
|
||||
RECOMMENDED_SERVICE_TIER,
|
||||
@@ -421,6 +423,18 @@ class OpenAISubentryFlowHandler(ConfigSubentryFlow):
|
||||
elif CONF_REASONING_EFFORT in options:
|
||||
options.pop(CONF_REASONING_EFFORT)
|
||||
|
||||
if model.startswith("gpt-5.6"):
|
||||
step_schema.update(
|
||||
{
|
||||
vol.Optional(
|
||||
CONF_PRO_MODE,
|
||||
default=RECOMMENDED_PRO_MODE,
|
||||
): bool,
|
||||
}
|
||||
)
|
||||
elif CONF_PRO_MODE in options:
|
||||
options.pop(CONF_PRO_MODE)
|
||||
|
||||
if model.startswith("gpt-5"):
|
||||
step_schema.update(
|
||||
{
|
||||
@@ -592,6 +606,7 @@ class OpenAISubentryFlowHandler(ConfigSubentryFlow):
|
||||
return []
|
||||
|
||||
models_reasoning_map: dict[str | tuple[str, ...], list[str]] = {
|
||||
"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"): [
|
||||
"none",
|
||||
|
||||
@@ -20,6 +20,7 @@ CONF_IMAGE_MODEL = "image_model"
|
||||
CONF_CODE_INTERPRETER = "code_interpreter"
|
||||
CONF_FILENAMES = "filenames"
|
||||
CONF_MAX_TOKENS = "max_tokens"
|
||||
CONF_PRO_MODE = "pro_mode"
|
||||
CONF_REASONING_EFFORT = "reasoning_effort"
|
||||
CONF_REASONING_SUMMARY = "reasoning_summary"
|
||||
CONF_RECOMMENDED = "recommended"
|
||||
@@ -41,6 +42,7 @@ RECOMMENDED_CODE_INTERPRETER = False
|
||||
RECOMMENDED_CHAT_MODEL = "gpt-4o-mini"
|
||||
RECOMMENDED_IMAGE_MODEL = "gpt-image-2"
|
||||
RECOMMENDED_MAX_TOKENS = 3000
|
||||
RECOMMENDED_PRO_MODE = False
|
||||
RECOMMENDED_REASONING_EFFORT = "low"
|
||||
RECOMMENDED_STORE_RESPONSES = False
|
||||
RECOMMENDED_REASONING_SUMMARY = "auto"
|
||||
|
||||
@@ -73,6 +73,7 @@ from .const import (
|
||||
CONF_CODE_INTERPRETER,
|
||||
CONF_IMAGE_MODEL,
|
||||
CONF_MAX_TOKENS,
|
||||
CONF_PRO_MODE,
|
||||
CONF_REASONING_EFFORT,
|
||||
CONF_REASONING_SUMMARY,
|
||||
CONF_SERVICE_TIER,
|
||||
@@ -93,6 +94,7 @@ from .const import (
|
||||
RECOMMENDED_CHAT_MODEL,
|
||||
RECOMMENDED_IMAGE_MODEL,
|
||||
RECOMMENDED_MAX_TOKENS,
|
||||
RECOMMENDED_PRO_MODE,
|
||||
RECOMMENDED_REASONING_EFFORT,
|
||||
RECOMMENDED_REASONING_SUMMARY,
|
||||
RECOMMENDED_SERVICE_TIER,
|
||||
@@ -497,7 +499,7 @@ class OpenAIBaseLLMEntity(Entity):
|
||||
entry_type=dr.DeviceEntryType.SERVICE,
|
||||
)
|
||||
|
||||
async def _async_handle_chat_log(
|
||||
async def _async_handle_chat_log( # noqa: C901
|
||||
self,
|
||||
chat_log: conversation.ChatLog,
|
||||
structure_name: str | None = None,
|
||||
@@ -528,11 +530,16 @@ class OpenAIBaseLLMEntity(Entity):
|
||||
if not model_args["model"].startswith("gpt-5-pro")
|
||||
else "high", # GPT-5 pro only supports reasoning.effort: high
|
||||
}
|
||||
|
||||
reasoning_summary = options.get(
|
||||
CONF_REASONING_SUMMARY, RECOMMENDED_REASONING_SUMMARY
|
||||
)
|
||||
if reasoning_summary != "off":
|
||||
reasoning["summary"] = reasoning_summary
|
||||
|
||||
if options.get(CONF_PRO_MODE, RECOMMENDED_PRO_MODE):
|
||||
reasoning["mode"] = "pro"
|
||||
|
||||
model_args["reasoning"] = reasoning
|
||||
model_args["include"] = ["reasoning.encrypted_content"]
|
||||
|
||||
|
||||
@@ -71,6 +71,7 @@
|
||||
"code_interpreter": "[%key:component::openai_conversation::config_subentries::conversation::step::model::data::code_interpreter%]",
|
||||
"image_model": "[%key:component::openai_conversation::config_subentries::conversation::step::model::data::image_model%]",
|
||||
"inline_citations": "[%key:component::openai_conversation::config_subentries::conversation::step::model::data::inline_citations%]",
|
||||
"pro_mode": "[%key:component::openai_conversation::config_subentries::conversation::step::model::data::pro_mode%]",
|
||||
"reasoning_effort": "[%key:component::openai_conversation::config_subentries::conversation::step::model::data::reasoning_effort%]",
|
||||
"reasoning_summary": "[%key:component::openai_conversation::config_subentries::conversation::step::model::data::reasoning_summary%]",
|
||||
"search_context_size": "[%key:component::openai_conversation::config_subentries::conversation::step::model::data::search_context_size%]",
|
||||
@@ -82,6 +83,7 @@
|
||||
"code_interpreter": "[%key:component::openai_conversation::config_subentries::conversation::step::model::data_description::code_interpreter%]",
|
||||
"image_model": "[%key:component::openai_conversation::config_subentries::conversation::step::model::data_description::image_model%]",
|
||||
"inline_citations": "[%key:component::openai_conversation::config_subentries::conversation::step::model::data_description::inline_citations%]",
|
||||
"pro_mode": "[%key:component::openai_conversation::config_subentries::conversation::step::model::data_description::pro_mode%]",
|
||||
"reasoning_effort": "[%key:component::openai_conversation::config_subentries::conversation::step::model::data_description::reasoning_effort%]",
|
||||
"reasoning_summary": "[%key:component::openai_conversation::config_subentries::conversation::step::model::data_description::reasoning_summary%]",
|
||||
"search_context_size": "[%key:component::openai_conversation::config_subentries::conversation::step::model::data_description::search_context_size%]",
|
||||
@@ -138,6 +140,7 @@
|
||||
"code_interpreter": "Enable code interpreter tool",
|
||||
"image_model": "Image generation model",
|
||||
"inline_citations": "Include links in web search results",
|
||||
"pro_mode": "Pro mode",
|
||||
"reasoning_effort": "Reasoning effort",
|
||||
"reasoning_summary": "Reasoning summary",
|
||||
"search_context_size": "Search context size",
|
||||
@@ -149,6 +152,7 @@
|
||||
"code_interpreter": "This tool, also known as the python tool to the model, allows it to run code to answer questions",
|
||||
"image_model": "The model to use when generating images",
|
||||
"inline_citations": "If disabled, additional prompt is added to ask the model to not include source citations",
|
||||
"pro_mode": "Perform more model work to improve reliability on difficult tasks and return a single final answer",
|
||||
"reasoning_effort": "How many reasoning tokens the model should generate before creating a response to the prompt",
|
||||
"reasoning_summary": "Controls the length and detail of reasoning summaries provided by the model",
|
||||
"search_context_size": "High level guidance for the amount of context window space to use for the search",
|
||||
@@ -233,6 +237,7 @@
|
||||
"options": {
|
||||
"high": "[%key:common::state::high%]",
|
||||
"low": "[%key:common::state::low%]",
|
||||
"max": "Max",
|
||||
"medium": "[%key:common::state::medium%]",
|
||||
"minimal": "Minimal",
|
||||
"none": "None",
|
||||
|
||||
@@ -92,7 +92,7 @@ def mock_config_entry(
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_config_entry_with_assist(
|
||||
async def mock_config_entry_with_assist(
|
||||
hass: HomeAssistant, mock_config_entry: MockConfigEntry
|
||||
) -> MockConfigEntry:
|
||||
"""Mock a config entry with assist."""
|
||||
@@ -101,11 +101,12 @@ def mock_config_entry_with_assist(
|
||||
next(iter(mock_config_entry.subentries.values())),
|
||||
data={CONF_LLM_HASS_API: llm.LLM_API_ASSIST},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
return mock_config_entry
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_config_entry_with_reasoning_model(
|
||||
async def mock_config_entry_with_reasoning_model(
|
||||
hass: HomeAssistant, mock_config_entry: MockConfigEntry
|
||||
) -> MockConfigEntry:
|
||||
"""Mock a config entry with assist."""
|
||||
@@ -114,6 +115,7 @@ def mock_config_entry_with_reasoning_model(
|
||||
next(iter(mock_config_entry.subentries.values())),
|
||||
data={CONF_LLM_HASS_API: llm.LLM_API_ASSIST, CONF_CHAT_MODEL: "gpt-5-mini"},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
return mock_config_entry
|
||||
|
||||
|
||||
|
||||
@@ -297,6 +297,27 @@
|
||||
}),
|
||||
])
|
||||
# ---
|
||||
# name: test_model_args[subentry_options0]
|
||||
dict({
|
||||
'include': list([
|
||||
'reasoning.encrypted_content',
|
||||
]),
|
||||
'max_output_tokens': 3000,
|
||||
'model': 'gpt-5.6-sol',
|
||||
'prompt_cache_retention': '24h',
|
||||
'reasoning': dict({
|
||||
'effort': 'low',
|
||||
'mode': 'pro',
|
||||
'summary': 'auto',
|
||||
}),
|
||||
'service_tier': 'auto',
|
||||
'store': False,
|
||||
'stream': True,
|
||||
'text': dict({
|
||||
'verbosity': 'medium',
|
||||
}),
|
||||
})
|
||||
# ---
|
||||
# name: test_web_search[False]
|
||||
list([
|
||||
dict({
|
||||
|
||||
@@ -16,6 +16,7 @@ from homeassistant.components.openai_conversation.const import (
|
||||
CONF_CODE_INTERPRETER,
|
||||
CONF_IMAGE_MODEL,
|
||||
CONF_MAX_TOKENS,
|
||||
CONF_PRO_MODE,
|
||||
CONF_REASONING_EFFORT,
|
||||
CONF_REASONING_SUMMARY,
|
||||
CONF_RECOMMENDED,
|
||||
@@ -273,6 +274,7 @@ async def test_subentry_unsupported_model(
|
||||
("gpt-5.4-pro", ["medium", "high", "xhigh"]),
|
||||
("gpt-5.5", ["none", "low", "medium", "high", "xhigh"]),
|
||||
("gpt-5.5-pro", ["medium", "high", "xhigh"]),
|
||||
("gpt-5.6", ["none", "low", "medium", "high", "xhigh", "max"]),
|
||||
],
|
||||
)
|
||||
async def test_subentry_reasoning_effort_list(
|
||||
@@ -466,6 +468,8 @@ async def test_subentry_reasoning_summary_default_sanitized_on_model_switch(
|
||||
@pytest.mark.parametrize(
|
||||
("model", "service_tier_options"),
|
||||
[
|
||||
("gpt-5.6", ["auto", "flex", "default", "priority"]),
|
||||
("gpt-5.5", ["auto", "flex", "default", "priority"]),
|
||||
("gpt-5.4", ["auto", "flex", "default", "priority"]),
|
||||
("gpt-5.4-pro", ["auto", "flex", "default", "priority"]),
|
||||
("gpt-5.2", ["auto", "flex", "default", "priority"]),
|
||||
@@ -817,12 +821,12 @@ async def test_form_invalid_auth(hass: HomeAssistant, side_effect, error) -> Non
|
||||
},
|
||||
{
|
||||
CONF_TEMPERATURE: 0.8,
|
||||
CONF_CHAT_MODEL: "gpt-5",
|
||||
CONF_CHAT_MODEL: "gpt-5.6",
|
||||
CONF_TOP_P: 0.9,
|
||||
CONF_MAX_TOKENS: 1000,
|
||||
},
|
||||
{
|
||||
CONF_REASONING_EFFORT: "minimal",
|
||||
CONF_REASONING_EFFORT: "max",
|
||||
CONF_REASONING_SUMMARY: RECOMMENDED_REASONING_SUMMARY,
|
||||
CONF_CODE_INTERPRETER: False,
|
||||
CONF_VERBOSITY: "high",
|
||||
@@ -831,17 +835,18 @@ async def test_form_invalid_auth(hass: HomeAssistant, side_effect, error) -> Non
|
||||
CONF_WEB_SEARCH_CONTEXT_SIZE: "low",
|
||||
CONF_WEB_SEARCH_USER_LOCATION: False,
|
||||
CONF_WEB_SEARCH_INLINE_CITATIONS: True,
|
||||
CONF_PRO_MODE: True,
|
||||
},
|
||||
),
|
||||
{
|
||||
CONF_RECOMMENDED: False,
|
||||
CONF_PROMPT: "Speak like a pirate",
|
||||
CONF_TEMPERATURE: 0.8,
|
||||
CONF_CHAT_MODEL: "gpt-5",
|
||||
CONF_CHAT_MODEL: "gpt-5.6",
|
||||
CONF_TOP_P: 0.9,
|
||||
CONF_MAX_TOKENS: 1000,
|
||||
CONF_STORE_RESPONSES: False,
|
||||
CONF_REASONING_EFFORT: "minimal",
|
||||
CONF_REASONING_EFFORT: "max",
|
||||
CONF_REASONING_SUMMARY: RECOMMENDED_REASONING_SUMMARY,
|
||||
CONF_CODE_INTERPRETER: False,
|
||||
CONF_VERBOSITY: "high",
|
||||
@@ -850,6 +855,7 @@ async def test_form_invalid_auth(hass: HomeAssistant, side_effect, error) -> Non
|
||||
CONF_WEB_SEARCH_CONTEXT_SIZE: "low",
|
||||
CONF_WEB_SEARCH_USER_LOCATION: False,
|
||||
CONF_WEB_SEARCH_INLINE_CITATIONS: True,
|
||||
CONF_PRO_MODE: True,
|
||||
},
|
||||
),
|
||||
# Test that old options are removed after reconfiguration
|
||||
@@ -966,7 +972,7 @@ async def test_form_invalid_auth(hass: HomeAssistant, side_effect, error) -> Non
|
||||
CONF_PROMPT: "Speak like a pirate",
|
||||
CONF_LLM_HASS_API: ["assist"],
|
||||
CONF_TEMPERATURE: 0.8,
|
||||
CONF_CHAT_MODEL: "gpt-5",
|
||||
CONF_CHAT_MODEL: "gpt-5.6",
|
||||
CONF_TOP_P: 0.9,
|
||||
CONF_MAX_TOKENS: 1000,
|
||||
CONF_REASONING_EFFORT: "low",
|
||||
@@ -974,6 +980,7 @@ async def test_form_invalid_auth(hass: HomeAssistant, side_effect, error) -> Non
|
||||
CONF_SERVICE_TIER: "flex",
|
||||
CONF_CODE_INTERPRETER: True,
|
||||
CONF_VERBOSITY: "medium",
|
||||
CONF_PRO_MODE: True,
|
||||
},
|
||||
(
|
||||
{
|
||||
|
||||
@@ -21,6 +21,7 @@ from homeassistant.components.intent import async_register_timer_handler
|
||||
from homeassistant.components.openai_conversation.const import (
|
||||
CONF_CHAT_MODEL,
|
||||
CONF_CODE_INTERPRETER,
|
||||
CONF_PRO_MODE,
|
||||
CONF_REASONING_SUMMARY,
|
||||
CONF_SERVICE_TIER,
|
||||
CONF_STORE_RESPONSES,
|
||||
@@ -817,3 +818,46 @@ async def test_flex_tier_retry(
|
||||
)
|
||||
assert mock_create_stream.mock_calls[0][2]["service_tier"] == "flex"
|
||||
assert mock_create_stream.mock_calls[1][2]["service_tier"] == "default"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"subentry_options", [{CONF_CHAT_MODEL: "gpt-5.6-sol", CONF_PRO_MODE: True}]
|
||||
)
|
||||
async def test_model_args(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_init_component,
|
||||
mock_create_stream: AsyncMock,
|
||||
snapshot: SnapshotAssertion,
|
||||
subentry_options: dict,
|
||||
) -> None:
|
||||
"""Test model arguments for various configuration."""
|
||||
|
||||
subentry = next(
|
||||
entry
|
||||
for entry in mock_config_entry.subentries.values()
|
||||
if entry.subentry_type == "conversation"
|
||||
)
|
||||
hass.config_entries.async_update_subentry(
|
||||
mock_config_entry,
|
||||
subentry,
|
||||
data=subentry_options,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
mock_create_stream.return_value = [
|
||||
create_message_item(id="msg_A", text="Hi!", output_index=0),
|
||||
]
|
||||
|
||||
result = await conversation.async_converse(
|
||||
hass,
|
||||
"Hello",
|
||||
None,
|
||||
Context(),
|
||||
agent_id="conversation.openai_conversation",
|
||||
)
|
||||
|
||||
model_args = mock_create_stream.call_args.kwargs.copy()
|
||||
model_args.pop("input")
|
||||
assert model_args.pop("user") == result.conversation_id
|
||||
assert model_args == snapshot
|
||||
|
||||
Reference in New Issue
Block a user