Enable minimal thinking budget by default for Anthropic integration (#167593)

This commit is contained in:
Denis Shulyaka
2026-04-07 13:36:49 +02:00
committed by GitHub
parent 6e30de3a1c
commit 1aa214fb61
6 changed files with 105 additions and 20 deletions
+3 -3
View File
@@ -36,13 +36,15 @@ class PromptCaching(StrEnum):
AUTOMATIC = "automatic"
MIN_THINKING_BUDGET = 1024
DEFAULT = {
CONF_CHAT_MODEL: "claude-haiku-4-5",
CONF_CODE_EXECUTION: False,
CONF_MAX_TOKENS: 3000,
CONF_PROMPT_CACHING: PromptCaching.PROMPT.value,
CONF_TEMPERATURE: 1.0,
CONF_THINKING_BUDGET: 0,
CONF_THINKING_BUDGET: MIN_THINKING_BUDGET,
CONF_THINKING_EFFORT: "low",
CONF_TOOL_SEARCH: False,
CONF_WEB_SEARCH: False,
@@ -50,8 +52,6 @@ DEFAULT = {
CONF_WEB_SEARCH_MAX_USES: 5,
}
MIN_THINKING_BUDGET = 1024
NON_THINKING_MODELS = [
"claude-3-haiku",
]
@@ -47,9 +47,9 @@
'type': 'text',
}),
]),
'temperature': 1.0,
'thinking': dict({
'type': 'disabled',
'budget_tokens': 1024,
'type': 'enabled',
}),
})
# ---
@@ -195,7 +195,7 @@
}),
])
# ---
# name: test_disabled_thinking
# name: test_disabled_thinking[subentry_data0]
list([
dict({
'content': '''
@@ -224,7 +224,72 @@
}),
])
# ---
# name: test_disabled_thinking.1
# name: test_disabled_thinking[subentry_data0].1
dict({
'container': None,
'max_tokens': 3000,
'messages': list([
dict({
'content': 'hello',
'role': 'user',
}),
dict({
'content': 'Hello, how can I help you today?',
'role': 'assistant',
}),
]),
'model': 'claude-haiku-4-5',
'stream': True,
'system': list([
dict({
'cache_control': dict({
'type': 'ephemeral',
}),
'text': '''
You are a voice assistant for Home Assistant.
Answer questions about the world truthfully.
Answer in plain text. Keep it simple and to the point.
Only if the user wants to control a device, tell them to expose entities to their voice assistant in Home Assistant.
''',
'type': 'text',
}),
]),
'temperature': 1.0,
'thinking': dict({
'type': 'disabled',
}),
})
# ---
# name: test_disabled_thinking[subentry_data1]
list([
dict({
'content': '''
You are a voice assistant for Home Assistant.
Answer questions about the world truthfully.
Answer in plain text. Keep it simple and to the point.
Only if the user wants to control a device, tell them to expose entities to their voice assistant in Home Assistant.
''',
'created': HAFakeDatetime(2024, 5, 24, 12, 0, tzinfo=datetime.timezone.utc),
'role': 'system',
}),
dict({
'attachments': None,
'content': 'hello',
'created': HAFakeDatetime(2024, 5, 24, 12, 0, tzinfo=datetime.timezone.utc),
'role': 'user',
}),
dict({
'agent_id': 'conversation.claude_conversation',
'content': 'Hello, how can I help you today?',
'created': HAFakeDatetime(2024, 5, 24, 12, 0, tzinfo=datetime.timezone.utc),
'native': None,
'role': 'assistant',
'thinking_content': None,
'tool_calls': None,
}),
])
# ---
# name: test_disabled_thinking[subentry_data1].1
dict({
'container': None,
'max_tokens': 3000,
+10 -2
View File
@@ -10,7 +10,10 @@ from syrupy.assertion import SnapshotAssertion
import voluptuous as vol
from homeassistant.components import ai_task, media_source
from homeassistant.components.anthropic.const import CONF_CHAT_MODEL
from homeassistant.components.anthropic.const import (
CONF_CHAT_MODEL,
CONF_THINKING_BUDGET,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import entity_registry as er, selector
@@ -127,6 +130,7 @@ async def test_generate_structured_data_legacy(
subentry,
data={
CONF_CHAT_MODEL: "claude-sonnet-4-0",
CONF_THINKING_BUDGET: 0,
},
)
@@ -183,7 +187,11 @@ async def test_generate_structured_data_legacy_tools(
hass.config_entries.async_update_subentry(
mock_config_entry,
subentry,
data={"chat_model": "claude-sonnet-4-0", "web_search": True},
data={
"chat_model": "claude-sonnet-4-0",
"web_search": True,
"thinking_budget": 0,
},
)
result = await ai_task.async_generate_data(
@@ -330,7 +330,7 @@ async def test_subentry_web_search_user_location(
"recommended": False,
"region": "California",
"temperature": 1.0,
"thinking_budget": 0,
"thinking_budget": 1024,
"timezone": "America/Los_Angeles",
"tool_search": False,
"user_location": True,
@@ -487,7 +487,7 @@ async def test_model_list_error(
CONF_TEMPERATURE: 1.0,
CONF_CHAT_MODEL: "claude-haiku-4-5",
CONF_MAX_TOKENS: DEFAULT[CONF_MAX_TOKENS],
CONF_THINKING_BUDGET: 0,
CONF_THINKING_BUDGET: DEFAULT[CONF_THINKING_BUDGET],
CONF_WEB_SEARCH: False,
CONF_WEB_SEARCH_MAX_USES: 10,
CONF_WEB_SEARCH_USER_LOCATION: False,
@@ -614,7 +614,7 @@ async def test_model_list_error(
CONF_TEMPERATURE: 0.3,
CONF_CHAT_MODEL: DEFAULT[CONF_CHAT_MODEL],
CONF_MAX_TOKENS: DEFAULT[CONF_MAX_TOKENS],
CONF_THINKING_BUDGET: 0,
CONF_THINKING_BUDGET: DEFAULT[CONF_THINKING_BUDGET],
CONF_WEB_SEARCH: False,
CONF_WEB_SEARCH_MAX_USES: 5,
CONF_WEB_SEARCH_USER_LOCATION: False,
@@ -788,7 +788,7 @@ async def test_creating_ai_task_subentry_advanced(
result["flow_id"],
{
CONF_CHAT_MODEL: "claude-sonnet-4-5",
CONF_MAX_TOKENS: 200,
CONF_MAX_TOKENS: 1200,
CONF_TEMPERATURE: 0.5,
},
)
@@ -809,13 +809,13 @@ async def test_creating_ai_task_subentry_advanced(
assert result4.get("data") == {
CONF_RECOMMENDED: False,
CONF_CHAT_MODEL: "claude-sonnet-4-5",
CONF_MAX_TOKENS: 200,
CONF_MAX_TOKENS: 1200,
CONF_TEMPERATURE: 0.5,
CONF_TOOL_SEARCH: False,
CONF_WEB_SEARCH: False,
CONF_WEB_SEARCH_MAX_USES: 5,
CONF_WEB_SEARCH_USER_LOCATION: False,
CONF_THINKING_BUDGET: 0,
CONF_THINKING_BUDGET: 1024,
CONF_CODE_EXECUTION: False,
CONF_PROMPT_CACHING: "prompt",
}
@@ -706,6 +706,21 @@ async def test_extended_thinking(
assert call_args == snapshot
@pytest.mark.parametrize(
"subentry_data",
[
{
CONF_LLM_HASS_API: "assist",
CONF_CHAT_MODEL: "claude-haiku-4-5",
CONF_THINKING_BUDGET: 0,
},
{
CONF_LLM_HASS_API: "assist",
CONF_CHAT_MODEL: "claude-opus-4-6",
CONF_THINKING_EFFORT: "none",
},
],
)
@freeze_time("2024-05-24 12:00:00")
async def test_disabled_thinking(
hass: HomeAssistant,
@@ -713,16 +728,13 @@ async def test_disabled_thinking(
mock_init_component,
mock_create_stream: AsyncMock,
snapshot: SnapshotAssertion,
subentry_data: dict[str, Any],
) -> None:
"""Test conversation with thinking effort disabled."""
hass.config_entries.async_update_subentry(
mock_config_entry,
next(iter(mock_config_entry.subentries.values())),
data={
CONF_LLM_HASS_API: "assist",
CONF_CHAT_MODEL: "claude-opus-4-6",
CONF_THINKING_EFFORT: "none",
},
data=subentry_data,
)
mock_create_stream.return_value = [