Home Assistant Unofficial Reference 2024.12.1
config_flow.py
Go to the documentation of this file.
1 """Config flow for OpenAI Conversation integration."""
2 
3 from __future__ import annotations
4 
5 import logging
6 from types import MappingProxyType
7 from typing import Any
8 
9 import openai
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 from homeassistant.helpers.typing import VolDictType
30 
31 from .const import (
32  CONF_CHAT_MODEL,
33  CONF_MAX_TOKENS,
34  CONF_PROMPT,
35  CONF_RECOMMENDED,
36  CONF_TEMPERATURE,
37  CONF_TOP_P,
38  DOMAIN,
39  RECOMMENDED_CHAT_MODEL,
40  RECOMMENDED_MAX_TOKENS,
41  RECOMMENDED_TEMPERATURE,
42  RECOMMENDED_TOP_P,
43 )
44 
45 _LOGGER = logging.getLogger(__name__)
46 
47 STEP_USER_DATA_SCHEMA = vol.Schema(
48  {
49  vol.Required(CONF_API_KEY): str,
50  }
51 )
52 
53 RECOMMENDED_OPTIONS = {
54  CONF_RECOMMENDED: True,
55  CONF_LLM_HASS_API: llm.LLM_API_ASSIST,
56  CONF_PROMPT: llm.DEFAULT_INSTRUCTIONS_PROMPT,
57 }
58 
59 
60 async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> None:
61  """Validate the user input allows us to connect.
62 
63  Data has the keys from STEP_USER_DATA_SCHEMA with values provided by the user.
64  """
65  client = openai.AsyncOpenAI(api_key=data[CONF_API_KEY])
66  await hass.async_add_executor_job(client.with_options(timeout=10.0).models.list)
67 
68 
69 class OpenAIConfigFlow(ConfigFlow, domain=DOMAIN):
70  """Handle a config flow for OpenAI Conversation."""
71 
72  VERSION = 1
73 
74  async def async_step_user(
75  self, user_input: dict[str, Any] | None = None
76  ) -> ConfigFlowResult:
77  """Handle the initial step."""
78  if user_input is None:
79  return self.async_show_formasync_show_formasync_show_form(
80  step_id="user", data_schema=STEP_USER_DATA_SCHEMA
81  )
82 
83  errors: dict[str, str] = {}
84 
85  try:
86  await validate_input(self.hass, user_input)
87  except openai.APIConnectionError:
88  errors["base"] = "cannot_connect"
89  except openai.AuthenticationError:
90  errors["base"] = "invalid_auth"
91  except Exception:
92  _LOGGER.exception("Unexpected exception")
93  errors["base"] = "unknown"
94  else:
95  return self.async_create_entryasync_create_entryasync_create_entry(
96  title="ChatGPT",
97  data=user_input,
98  options=RECOMMENDED_OPTIONS,
99  )
100 
101  return self.async_show_formasync_show_formasync_show_form(
102  step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
103  )
104 
105  @staticmethod
107  config_entry: ConfigEntry,
108  ) -> OptionsFlow:
109  """Create the options flow."""
110  return OpenAIOptionsFlow(config_entry)
111 
112 
114  """OpenAI config flow options handler."""
115 
116  def __init__(self, config_entry: ConfigEntry) -> None:
117  """Initialize options flow."""
118  self.last_rendered_recommendedlast_rendered_recommended = config_entry.options.get(
119  CONF_RECOMMENDED, False
120  )
121 
122  async def async_step_init(
123  self, user_input: dict[str, Any] | None = None
124  ) -> ConfigFlowResult:
125  """Manage the options."""
126  options: dict[str, Any] | MappingProxyType[str, Any] = self.config_entryconfig_entryconfig_entry.options
127 
128  if user_input is not None:
129  if user_input[CONF_RECOMMENDED] == self.last_rendered_recommendedlast_rendered_recommended:
130  if user_input[CONF_LLM_HASS_API] == "none":
131  user_input.pop(CONF_LLM_HASS_API)
132  return self.async_create_entryasync_create_entry(title="", data=user_input)
133 
134  # Re-render the options again, now with the recommended options shown/hidden
135  self.last_rendered_recommendedlast_rendered_recommended = user_input[CONF_RECOMMENDED]
136 
137  options = {
138  CONF_RECOMMENDED: user_input[CONF_RECOMMENDED],
139  CONF_PROMPT: user_input[CONF_PROMPT],
140  CONF_LLM_HASS_API: user_input[CONF_LLM_HASS_API],
141  }
142 
143  schema = openai_config_option_schema(self.hass, options)
144  return self.async_show_formasync_show_form(
145  step_id="init",
146  data_schema=vol.Schema(schema),
147  )
148 
149 
151  hass: HomeAssistant,
152  options: dict[str, Any] | MappingProxyType[str, Any],
153 ) -> VolDictType:
154  """Return a schema for OpenAI completion options."""
155  hass_apis: list[SelectOptionDict] = [
157  label="No control",
158  value="none",
159  )
160  ]
161  hass_apis.extend(
163  label=api.name,
164  value=api.id,
165  )
166  for api in llm.async_get_apis(hass)
167  )
168 
169  schema: VolDictType = {
170  vol.Optional(
171  CONF_PROMPT,
172  description={
173  "suggested_value": options.get(
174  CONF_PROMPT, llm.DEFAULT_INSTRUCTIONS_PROMPT
175  )
176  },
177  ): TemplateSelector(),
178  vol.Optional(
179  CONF_LLM_HASS_API,
180  description={"suggested_value": options.get(CONF_LLM_HASS_API)},
181  default="none",
182  ): SelectSelector(SelectSelectorConfig(options=hass_apis)),
183  vol.Required(
184  CONF_RECOMMENDED, default=options.get(CONF_RECOMMENDED, False)
185  ): bool,
186  }
187 
188  if options.get(CONF_RECOMMENDED):
189  return schema
190 
191  schema.update(
192  {
193  vol.Optional(
194  CONF_CHAT_MODEL,
195  description={"suggested_value": options.get(CONF_CHAT_MODEL)},
196  default=RECOMMENDED_CHAT_MODEL,
197  ): str,
198  vol.Optional(
199  CONF_MAX_TOKENS,
200  description={"suggested_value": options.get(CONF_MAX_TOKENS)},
201  default=RECOMMENDED_MAX_TOKENS,
202  ): int,
203  vol.Optional(
204  CONF_TOP_P,
205  description={"suggested_value": options.get(CONF_TOP_P)},
206  default=RECOMMENDED_TOP_P,
207  ): NumberSelector(NumberSelectorConfig(min=0, max=1, step=0.05)),
208  vol.Optional(
209  CONF_TEMPERATURE,
210  description={"suggested_value": options.get(CONF_TEMPERATURE)},
211  default=RECOMMENDED_TEMPERATURE,
212  ): NumberSelector(NumberSelectorConfig(min=0, max=2, step=0.05)),
213  }
214  )
215  return schema
ConfigFlowResult async_step_user(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:76
ConfigFlowResult async_step_init(self, dict[str, Any]|None user_input=None)
Definition: config_flow.py:124
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)
_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)
None validate_input(HomeAssistant hass, dict[str, Any] data)
Definition: config_flow.py:60
VolDictType openai_config_option_schema(HomeAssistant hass, dict[str, Any]|MappingProxyType[str, Any] options)
Definition: config_flow.py:153