Home Assistant Unofficial Reference 2024.12.1
config_flow.py
Go to the documentation of this file.
1 """Config flow for Anthropic integration."""
2 
3 from __future__ import annotations
4 
5 import logging
6 from types import MappingProxyType
7 from typing import Any
8 
9 import anthropic
10 import voluptuous as vol
11 
12 from homeassistant.config_entries import (
13  ConfigEntry,
14  ConfigFlow,
15  ConfigFlowResult,
16  OptionsFlow,
17 )
18 from homeassistant.const import CONF_API_KEY, CONF_LLM_HASS_API
19 from homeassistant.core import HomeAssistant
20 from homeassistant.helpers import llm
22  NumberSelector,
23  NumberSelectorConfig,
24  SelectOptionDict,
25  SelectSelector,
26  SelectSelectorConfig,
27  TemplateSelector,
28 )
29 
30 from .const import (
31  CONF_CHAT_MODEL,
32  CONF_MAX_TOKENS,
33  CONF_PROMPT,
34  CONF_RECOMMENDED,
35  CONF_TEMPERATURE,
36  DOMAIN,
37  RECOMMENDED_CHAT_MODEL,
38  RECOMMENDED_MAX_TOKENS,
39  RECOMMENDED_TEMPERATURE,
40 )
41 
42 _LOGGER = logging.getLogger(__name__)
43 
44 STEP_USER_DATA_SCHEMA = vol.Schema(
45  {
46  vol.Required(CONF_API_KEY): str,
47  }
48 )
49 
50 RECOMMENDED_OPTIONS = {
51  CONF_RECOMMENDED: True,
52  CONF_LLM_HASS_API: llm.LLM_API_ASSIST,
53  CONF_PROMPT: llm.DEFAULT_INSTRUCTIONS_PROMPT,
54 }
55 
56 
57 async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> None:
58  """Validate the user input allows us to connect.
59 
60  Data has the keys from STEP_USER_DATA_SCHEMA with values provided by the user.
61  """
62  client = anthropic.AsyncAnthropic(api_key=data[CONF_API_KEY])
63  await client.messages.create(
64  model="claude-3-haiku-20240307",
65  max_tokens=1,
66  messages=[{"role": "user", "content": "Hi"}],
67  timeout=10.0,
68  )
69 
70 
71 class AnthropicConfigFlow(ConfigFlow, domain=DOMAIN):
72  """Handle a config flow for Anthropic."""
73 
74  VERSION = 1
75 
76  async def async_step_user(
77  self, user_input: dict[str, Any] | None = None
78  ) -> ConfigFlowResult:
79  """Handle the initial step."""
80  errors = {}
81 
82  if user_input is not None:
83  try:
84  await validate_input(self.hass, user_input)
85  except anthropic.APITimeoutError:
86  errors["base"] = "timeout_connect"
87  except anthropic.APIConnectionError:
88  errors["base"] = "cannot_connect"
89  except anthropic.APIStatusError as e:
90  errors["base"] = "unknown"
91  if (
92  isinstance(e.body, dict)
93  and (error := e.body.get("error"))
94  and error.get("type") == "authentication_error"
95  ):
96  errors["base"] = "authentication_error"
97  except Exception:
98  _LOGGER.exception("Unexpected exception")
99  errors["base"] = "unknown"
100  else:
101  return self.async_create_entryasync_create_entryasync_create_entry(
102  title="Claude",
103  data=user_input,
104  options=RECOMMENDED_OPTIONS,
105  )
106 
107  return self.async_show_formasync_show_formasync_show_form(
108  step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors or None
109  )
110 
111  @staticmethod
113  config_entry: ConfigEntry,
114  ) -> OptionsFlow:
115  """Create the options flow."""
116  return AnthropicOptionsFlow(config_entry)
117 
118 
120  """Anthropic config flow options handler."""
121 
122  def __init__(self, config_entry: ConfigEntry) -> None:
123  """Initialize options flow."""
124  self.last_rendered_recommendedlast_rendered_recommended = config_entry.options.get(
125  CONF_RECOMMENDED, False
126  )
127 
128  async def async_step_init(
129  self, user_input: dict[str, Any] | None = None
130  ) -> ConfigFlowResult:
131  """Manage the options."""
132  options: dict[str, Any] | MappingProxyType[str, Any] = self.config_entryconfig_entryconfig_entry.options
133 
134  if user_input is not None:
135  if user_input[CONF_RECOMMENDED] == self.last_rendered_recommendedlast_rendered_recommended:
136  if user_input[CONF_LLM_HASS_API] == "none":
137  user_input.pop(CONF_LLM_HASS_API)
138  return self.async_create_entryasync_create_entry(title="", data=user_input)
139 
140  # Re-render the options again, now with the recommended options shown/hidden
141  self.last_rendered_recommendedlast_rendered_recommended = user_input[CONF_RECOMMENDED]
142 
143  options = {
144  CONF_RECOMMENDED: user_input[CONF_RECOMMENDED],
145  CONF_PROMPT: user_input[CONF_PROMPT],
146  CONF_LLM_HASS_API: user_input[CONF_LLM_HASS_API],
147  }
148 
149  suggested_values = options.copy()
150  if not suggested_values.get(CONF_PROMPT):
151  suggested_values[CONF_PROMPT] = llm.DEFAULT_INSTRUCTIONS_PROMPT
152 
153  schema = self.add_suggested_values_to_schemaadd_suggested_values_to_schema(
154  vol.Schema(anthropic_config_option_schema(self.hass, options)),
155  suggested_values,
156  )
157 
158  return self.async_show_formasync_show_form(
159  step_id="init",
160  data_schema=schema,
161  )
162 
163 
165  hass: HomeAssistant,
166  options: dict[str, Any] | MappingProxyType[str, Any],
167 ) -> dict:
168  """Return a schema for Anthropic completion options."""
169  hass_apis: list[SelectOptionDict] = [
171  label="No control",
172  value="none",
173  )
174  ]
175  hass_apis.extend(
177  label=api.name,
178  value=api.id,
179  )
180  for api in llm.async_get_apis(hass)
181  )
182 
183  schema = {
184  vol.Optional(CONF_PROMPT): TemplateSelector(),
185  vol.Optional(CONF_LLM_HASS_API, default="none"): SelectSelector(
186  SelectSelectorConfig(options=hass_apis)
187  ),
188  vol.Required(
189  CONF_RECOMMENDED, default=options.get(CONF_RECOMMENDED, False)
190  ): bool,
191  }
192 
193  if options.get(CONF_RECOMMENDED):
194  return schema
195 
196  schema.update(
197  {
198  vol.Optional(
199  CONF_CHAT_MODEL,
200  default=RECOMMENDED_CHAT_MODEL,
201  ): str,
202  vol.Optional(
203  CONF_MAX_TOKENS,
204  default=RECOMMENDED_MAX_TOKENS,
205  ): int,
206  vol.Optional(
207  CONF_TEMPERATURE,
208  default=RECOMMENDED_TEMPERATURE,
209  ): NumberSelector(NumberSelectorConfig(min=0, max=1, step=0.05)),
210  }
211  )
212  return schema
ConfigFlowResult async_step_user(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:78
OptionsFlow async_get_options_flow(ConfigEntry config_entry)
Definition: config_flow.py:114
ConfigFlowResult async_step_init(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:130
ConfigFlowResult async_create_entry(self, *str title, Mapping[str, Any] data, str|None description=None, Mapping[str, str]|None description_placeholders=None, Mapping[str, Any]|None options=None)
ConfigFlowResult async_show_form(self, *str|None step_id=None, vol.Schema|None data_schema=None, dict[str, str]|None errors=None, Mapping[str, str]|None description_placeholders=None, bool|None last_step=None, str|None preview=None)
None config_entry(self, ConfigEntry value)
vol.Schema add_suggested_values_to_schema(self, vol.Schema data_schema, Mapping[str, Any]|None suggested_values)
_FlowResultT async_show_form(self, *str|None step_id=None, vol.Schema|None data_schema=None, dict[str, str]|None errors=None, Mapping[str, str]|None description_placeholders=None, bool|None last_step=None, str|None preview=None)
_FlowResultT async_create_entry(self, *str|None title=None, Mapping[str, Any] data, str|None description=None, Mapping[str, str]|None description_placeholders=None)
dict anthropic_config_option_schema(HomeAssistant hass, dict[str, Any]|MappingProxyType[str, Any] options)
Definition: config_flow.py:167
None validate_input(HomeAssistant hass, dict[str, Any] data)
Definition: config_flow.py:57