Skip to content

Commit 252dbb7

Browse files
authored
Anthropic: consolidate recommended values in a dict (home-assistant#156787)
1 parent d7ad0cb commit 252dbb7

File tree

5 files changed

+60
-68
lines changed

5 files changed

+60
-68
lines changed

homeassistant/components/anthropic/__init__.py

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,7 @@
1717
)
1818
from homeassistant.helpers.typing import ConfigType
1919

20-
from .const import (
21-
CONF_CHAT_MODEL,
22-
DEFAULT_CONVERSATION_NAME,
23-
DOMAIN,
24-
LOGGER,
25-
RECOMMENDED_CHAT_MODEL,
26-
)
20+
from .const import CONF_CHAT_MODEL, DEFAULT, DEFAULT_CONVERSATION_NAME, DOMAIN, LOGGER
2721

2822
PLATFORMS = (Platform.AI_TASK, Platform.CONVERSATION)
2923
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
@@ -46,9 +40,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: AnthropicConfigEntry) ->
4640
# Use model from first conversation subentry for validation
4741
subentries = list(entry.subentries.values())
4842
if subentries:
49-
model_id = subentries[0].data.get(CONF_CHAT_MODEL, RECOMMENDED_CHAT_MODEL)
43+
model_id = subentries[0].data.get(CONF_CHAT_MODEL, DEFAULT[CONF_CHAT_MODEL])
5044
else:
51-
model_id = RECOMMENDED_CHAT_MODEL
45+
model_id = DEFAULT[CONF_CHAT_MODEL]
5246
model = await client.models.retrieve(model_id=model_id, timeout=10.0)
5347
LOGGER.debug("Anthropic model: %s", model.display_name)
5448
except anthropic.AuthenticationError as err:

homeassistant/components/anthropic/config_flow.py

Lines changed: 23 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import json
77
import logging
88
import re
9-
from typing import Any
9+
from typing import Any, cast
1010

1111
import anthropic
1212
import voluptuous as vol
@@ -54,17 +54,11 @@
5454
CONF_WEB_SEARCH_REGION,
5555
CONF_WEB_SEARCH_TIMEZONE,
5656
CONF_WEB_SEARCH_USER_LOCATION,
57+
DEFAULT,
5758
DEFAULT_AI_TASK_NAME,
5859
DEFAULT_CONVERSATION_NAME,
5960
DOMAIN,
6061
NON_THINKING_MODELS,
61-
RECOMMENDED_CHAT_MODEL,
62-
RECOMMENDED_MAX_TOKENS,
63-
RECOMMENDED_TEMPERATURE,
64-
RECOMMENDED_THINKING_BUDGET,
65-
RECOMMENDED_WEB_SEARCH,
66-
RECOMMENDED_WEB_SEARCH_MAX_USES,
67-
RECOMMENDED_WEB_SEARCH_USER_LOCATION,
6862
WEB_SEARCH_UNSUPPORTED_MODELS,
6963
)
7064

@@ -76,13 +70,13 @@
7670
}
7771
)
7872

79-
RECOMMENDED_CONVERSATION_OPTIONS = {
73+
DEFAULT_CONVERSATION_OPTIONS = {
8074
CONF_RECOMMENDED: True,
8175
CONF_LLM_HASS_API: [llm.LLM_API_ASSIST],
8276
CONF_PROMPT: llm.DEFAULT_INSTRUCTIONS_PROMPT,
8377
}
8478

85-
RECOMMENDED_AI_TASK_OPTIONS = {
79+
DEFAULT_AI_TASK_OPTIONS = {
8680
CONF_RECOMMENDED: True,
8781
}
8882

@@ -136,13 +130,13 @@ async def async_step_user(
136130
subentries=[
137131
{
138132
"subentry_type": "conversation",
139-
"data": RECOMMENDED_CONVERSATION_OPTIONS,
133+
"data": DEFAULT_CONVERSATION_OPTIONS,
140134
"title": DEFAULT_CONVERSATION_NAME,
141135
"unique_id": None,
142136
},
143137
{
144138
"subentry_type": "ai_task_data",
145-
"data": RECOMMENDED_AI_TASK_OPTIONS,
139+
"data": DEFAULT_AI_TASK_OPTIONS,
146140
"title": DEFAULT_AI_TASK_NAME,
147141
"unique_id": None,
148142
},
@@ -180,9 +174,9 @@ async def async_step_user(
180174
) -> SubentryFlowResult:
181175
"""Add a subentry."""
182176
if self._subentry_type == "ai_task_data":
183-
self.options = RECOMMENDED_AI_TASK_OPTIONS.copy()
177+
self.options = DEFAULT_AI_TASK_OPTIONS.copy()
184178
else:
185-
self.options = RECOMMENDED_CONVERSATION_OPTIONS.copy()
179+
self.options = DEFAULT_CONVERSATION_OPTIONS.copy()
186180
return await self.async_step_init()
187181

188182
async def async_step_reconfigure(
@@ -283,19 +277,19 @@ async def async_step_advanced(
283277
step_schema: VolDictType = {
284278
vol.Optional(
285279
CONF_CHAT_MODEL,
286-
default=RECOMMENDED_CHAT_MODEL,
280+
default=DEFAULT[CONF_CHAT_MODEL],
287281
): SelectSelector(
288282
SelectSelectorConfig(
289283
options=await self._get_model_list(), custom_value=True
290284
)
291285
),
292286
vol.Optional(
293287
CONF_MAX_TOKENS,
294-
default=RECOMMENDED_MAX_TOKENS,
288+
default=DEFAULT[CONF_MAX_TOKENS],
295289
): int,
296290
vol.Optional(
297291
CONF_TEMPERATURE,
298-
default=RECOMMENDED_TEMPERATURE,
292+
default=DEFAULT[CONF_TEMPERATURE],
299293
): NumberSelector(NumberSelectorConfig(min=0, max=1, step=0.05)),
300294
}
301295

@@ -325,12 +319,14 @@ async def async_step_model(
325319

326320
if not model.startswith(tuple(NON_THINKING_MODELS)):
327321
step_schema[
328-
vol.Optional(CONF_THINKING_BUDGET, default=RECOMMENDED_THINKING_BUDGET)
322+
vol.Optional(
323+
CONF_THINKING_BUDGET, default=DEFAULT[CONF_THINKING_BUDGET]
324+
)
329325
] = vol.All(
330326
NumberSelector(
331327
NumberSelectorConfig(
332328
min=0,
333-
max=self.options.get(CONF_MAX_TOKENS, RECOMMENDED_MAX_TOKENS),
329+
max=self.options.get(CONF_MAX_TOKENS, DEFAULT[CONF_MAX_TOKENS]),
334330
)
335331
),
336332
vol.Coerce(int),
@@ -343,15 +339,15 @@ async def async_step_model(
343339
{
344340
vol.Optional(
345341
CONF_WEB_SEARCH,
346-
default=RECOMMENDED_WEB_SEARCH,
342+
default=DEFAULT[CONF_WEB_SEARCH],
347343
): bool,
348344
vol.Optional(
349345
CONF_WEB_SEARCH_MAX_USES,
350-
default=RECOMMENDED_WEB_SEARCH_MAX_USES,
346+
default=DEFAULT[CONF_WEB_SEARCH_MAX_USES],
351347
): int,
352348
vol.Optional(
353349
CONF_WEB_SEARCH_USER_LOCATION,
354-
default=RECOMMENDED_WEB_SEARCH_USER_LOCATION,
350+
default=DEFAULT[CONF_WEB_SEARCH_USER_LOCATION],
355351
): bool,
356352
}
357353
)
@@ -369,9 +365,10 @@ async def async_step_model(
369365
user_input = {}
370366

371367
if user_input is not None:
372-
if user_input.get(CONF_WEB_SEARCH, RECOMMENDED_WEB_SEARCH) and not errors:
368+
if user_input.get(CONF_WEB_SEARCH, DEFAULT[CONF_WEB_SEARCH]) and not errors:
373369
if user_input.get(
374-
CONF_WEB_SEARCH_USER_LOCATION, RECOMMENDED_WEB_SEARCH_USER_LOCATION
370+
CONF_WEB_SEARCH_USER_LOCATION,
371+
DEFAULT[CONF_WEB_SEARCH_USER_LOCATION],
375372
):
376373
user_input.update(await self._get_location_data())
377374

@@ -456,7 +453,7 @@ async def _get_location_data(self) -> dict[str, str]:
456453
}
457454
)
458455
response = await client.messages.create(
459-
model=RECOMMENDED_CHAT_MODEL,
456+
model=cast(str, DEFAULT[CONF_CHAT_MODEL]),
460457
messages=[
461458
{
462459
"role": "user",
@@ -471,7 +468,7 @@ async def _get_location_data(self) -> dict[str, str]:
471468
"content": "{", # hints the model to skip any preamble
472469
},
473470
],
474-
max_tokens=RECOMMENDED_MAX_TOKENS,
471+
max_tokens=cast(int, DEFAULT[CONF_MAX_TOKENS]),
475472
)
476473
_LOGGER.debug("Model response: %s", response.content)
477474
location_data = location_schema(

homeassistant/components/anthropic/const.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,25 +11,29 @@
1111
CONF_RECOMMENDED = "recommended"
1212
CONF_PROMPT = "prompt"
1313
CONF_CHAT_MODEL = "chat_model"
14-
RECOMMENDED_CHAT_MODEL = "claude-3-5-haiku-latest"
1514
CONF_MAX_TOKENS = "max_tokens"
16-
RECOMMENDED_MAX_TOKENS = 3000
1715
CONF_TEMPERATURE = "temperature"
18-
RECOMMENDED_TEMPERATURE = 1.0
1916
CONF_THINKING_BUDGET = "thinking_budget"
20-
RECOMMENDED_THINKING_BUDGET = 0
21-
MIN_THINKING_BUDGET = 1024
2217
CONF_WEB_SEARCH = "web_search"
23-
RECOMMENDED_WEB_SEARCH = False
2418
CONF_WEB_SEARCH_USER_LOCATION = "user_location"
25-
RECOMMENDED_WEB_SEARCH_USER_LOCATION = False
2619
CONF_WEB_SEARCH_MAX_USES = "web_search_max_uses"
27-
RECOMMENDED_WEB_SEARCH_MAX_USES = 5
2820
CONF_WEB_SEARCH_CITY = "city"
2921
CONF_WEB_SEARCH_REGION = "region"
3022
CONF_WEB_SEARCH_COUNTRY = "country"
3123
CONF_WEB_SEARCH_TIMEZONE = "timezone"
3224

25+
DEFAULT = {
26+
CONF_CHAT_MODEL: "claude-3-5-haiku-latest",
27+
CONF_MAX_TOKENS: 3000,
28+
CONF_TEMPERATURE: 1.0,
29+
CONF_THINKING_BUDGET: 0,
30+
CONF_WEB_SEARCH: False,
31+
CONF_WEB_SEARCH_USER_LOCATION: False,
32+
CONF_WEB_SEARCH_MAX_USES: 5,
33+
}
34+
35+
MIN_THINKING_BUDGET = 1024
36+
3337
NON_THINKING_MODELS = [
3438
"claude-3-5", # Both sonnet and haiku
3539
"claude-3-opus",

homeassistant/components/anthropic/entity.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -84,14 +84,11 @@
8484
CONF_WEB_SEARCH_REGION,
8585
CONF_WEB_SEARCH_TIMEZONE,
8686
CONF_WEB_SEARCH_USER_LOCATION,
87+
DEFAULT,
8788
DOMAIN,
8889
LOGGER,
8990
MIN_THINKING_BUDGET,
9091
NON_THINKING_MODELS,
91-
RECOMMENDED_CHAT_MODEL,
92-
RECOMMENDED_MAX_TOKENS,
93-
RECOMMENDED_TEMPERATURE,
94-
RECOMMENDED_THINKING_BUDGET,
9592
)
9693

9794
# Max number of back and forth with the LLM to generate a response
@@ -604,17 +601,19 @@ async def _async_handle_chat_log(
604601
raise TypeError("First message must be a system message")
605602
messages = _convert_content(chat_log.content[1:])
606603

607-
model = options.get(CONF_CHAT_MODEL, RECOMMENDED_CHAT_MODEL)
604+
model = options.get(CONF_CHAT_MODEL, DEFAULT[CONF_CHAT_MODEL])
608605

609606
model_args = MessageCreateParamsStreaming(
610607
model=model,
611608
messages=messages,
612-
max_tokens=options.get(CONF_MAX_TOKENS, RECOMMENDED_MAX_TOKENS),
609+
max_tokens=options.get(CONF_MAX_TOKENS, DEFAULT[CONF_MAX_TOKENS]),
613610
system=system.content,
614611
stream=True,
615612
)
616613

617-
thinking_budget = options.get(CONF_THINKING_BUDGET, RECOMMENDED_THINKING_BUDGET)
614+
thinking_budget = options.get(
615+
CONF_THINKING_BUDGET, DEFAULT[CONF_THINKING_BUDGET]
616+
)
618617
if (
619618
not model.startswith(tuple(NON_THINKING_MODELS))
620619
and thinking_budget >= MIN_THINKING_BUDGET
@@ -625,7 +624,7 @@ async def _async_handle_chat_log(
625624
else:
626625
model_args["thinking"] = ThinkingConfigDisabledParam(type="disabled")
627626
model_args["temperature"] = options.get(
628-
CONF_TEMPERATURE, RECOMMENDED_TEMPERATURE
627+
CONF_TEMPERATURE, DEFAULT[CONF_TEMPERATURE]
629628
)
630629

631630
tools: list[ToolUnionParam] = []

tests/components/anthropic/test_config_flow.py

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616

1717
from homeassistant import config_entries
1818
from homeassistant.components.anthropic.config_flow import (
19-
RECOMMENDED_AI_TASK_OPTIONS,
20-
RECOMMENDED_CONVERSATION_OPTIONS,
19+
DEFAULT_AI_TASK_OPTIONS,
20+
DEFAULT_CONVERSATION_OPTIONS,
2121
)
2222
from homeassistant.components.anthropic.const import (
2323
CONF_CHAT_MODEL,
@@ -33,12 +33,10 @@
3333
CONF_WEB_SEARCH_REGION,
3434
CONF_WEB_SEARCH_TIMEZONE,
3535
CONF_WEB_SEARCH_USER_LOCATION,
36+
DEFAULT,
3637
DEFAULT_AI_TASK_NAME,
3738
DEFAULT_CONVERSATION_NAME,
3839
DOMAIN,
39-
RECOMMENDED_CHAT_MODEL,
40-
RECOMMENDED_MAX_TOKENS,
41-
RECOMMENDED_THINKING_BUDGET,
4240
)
4341
from homeassistant.const import CONF_API_KEY, CONF_LLM_HASS_API, CONF_NAME
4442
from homeassistant.core import HomeAssistant
@@ -87,13 +85,13 @@ async def test_form(hass: HomeAssistant) -> None:
8785
assert result2["subentries"] == [
8886
{
8987
"subentry_type": "conversation",
90-
"data": RECOMMENDED_CONVERSATION_OPTIONS,
88+
"data": DEFAULT_CONVERSATION_OPTIONS,
9189
"title": DEFAULT_CONVERSATION_NAME,
9290
"unique_id": None,
9391
},
9492
{
9593
"subentry_type": "ai_task_data",
96-
"data": RECOMMENDED_AI_TASK_OPTIONS,
94+
"data": DEFAULT_AI_TASK_OPTIONS,
9795
"title": DEFAULT_AI_TASK_NAME,
9896
"unique_id": None,
9997
},
@@ -144,13 +142,13 @@ async def test_creating_conversation_subentry(
144142

145143
result2 = await hass.config_entries.subentries.async_configure(
146144
result["flow_id"],
147-
{CONF_NAME: "Mock name", **RECOMMENDED_CONVERSATION_OPTIONS},
145+
{CONF_NAME: "Mock name", **DEFAULT_CONVERSATION_OPTIONS},
148146
)
149147

150148
assert result2["type"] is FlowResultType.CREATE_ENTRY
151149
assert result2["title"] == "Mock name"
152150

153-
processed_options = RECOMMENDED_CONVERSATION_OPTIONS.copy()
151+
processed_options = DEFAULT_CONVERSATION_OPTIONS.copy()
154152
processed_options[CONF_PROMPT] = processed_options[CONF_PROMPT].strip()
155153

156154
assert result2["data"] == processed_options
@@ -475,7 +473,7 @@ async def test_model_list_error(
475473
CONF_PROMPT: "Speak like a pirate",
476474
CONF_TEMPERATURE: 1.0,
477475
CONF_CHAT_MODEL: "claude-3-opus",
478-
CONF_MAX_TOKENS: RECOMMENDED_MAX_TOKENS,
476+
CONF_MAX_TOKENS: DEFAULT[CONF_MAX_TOKENS],
479477
},
480478
),
481479
( # Model with web search options
@@ -512,7 +510,7 @@ async def test_model_list_error(
512510
CONF_PROMPT: "Speak like a pirate",
513511
CONF_TEMPERATURE: 1.0,
514512
CONF_CHAT_MODEL: "claude-3-5-haiku-latest",
515-
CONF_MAX_TOKENS: RECOMMENDED_MAX_TOKENS,
513+
CONF_MAX_TOKENS: DEFAULT[CONF_MAX_TOKENS],
516514
CONF_WEB_SEARCH: False,
517515
CONF_WEB_SEARCH_MAX_USES: 10,
518516
CONF_WEB_SEARCH_USER_LOCATION: False,
@@ -550,7 +548,7 @@ async def test_model_list_error(
550548
CONF_PROMPT: "Speak like a pirate",
551549
CONF_TEMPERATURE: 1.0,
552550
CONF_CHAT_MODEL: "claude-sonnet-4-5",
553-
CONF_MAX_TOKENS: RECOMMENDED_MAX_TOKENS,
551+
CONF_MAX_TOKENS: DEFAULT[CONF_MAX_TOKENS],
554552
CONF_THINKING_BUDGET: 2048,
555553
CONF_WEB_SEARCH: False,
556554
CONF_WEB_SEARCH_MAX_USES: 10,
@@ -577,8 +575,8 @@ async def test_model_list_error(
577575
CONF_RECOMMENDED: False,
578576
CONF_PROMPT: "Speak like a pirate",
579577
CONF_TEMPERATURE: 0.3,
580-
CONF_CHAT_MODEL: RECOMMENDED_CHAT_MODEL,
581-
CONF_MAX_TOKENS: RECOMMENDED_MAX_TOKENS,
578+
CONF_CHAT_MODEL: DEFAULT[CONF_CHAT_MODEL],
579+
CONF_MAX_TOKENS: DEFAULT[CONF_MAX_TOKENS],
582580
CONF_WEB_SEARCH: False,
583581
CONF_WEB_SEARCH_MAX_USES: 5,
584582
CONF_WEB_SEARCH_USER_LOCATION: False,
@@ -589,9 +587,9 @@ async def test_model_list_error(
589587
CONF_RECOMMENDED: False,
590588
CONF_PROMPT: "Speak like a pirate",
591589
CONF_TEMPERATURE: 0.3,
592-
CONF_CHAT_MODEL: RECOMMENDED_CHAT_MODEL,
593-
CONF_MAX_TOKENS: RECOMMENDED_MAX_TOKENS,
594-
CONF_THINKING_BUDGET: RECOMMENDED_THINKING_BUDGET,
590+
CONF_CHAT_MODEL: DEFAULT[CONF_CHAT_MODEL],
591+
CONF_MAX_TOKENS: DEFAULT[CONF_MAX_TOKENS],
592+
CONF_THINKING_BUDGET: DEFAULT[CONF_THINKING_BUDGET],
595593
CONF_WEB_SEARCH: False,
596594
CONF_WEB_SEARCH_MAX_USES: 5,
597595
CONF_WEB_SEARCH_USER_LOCATION: False,

0 commit comments

Comments
 (0)