Home Assistant Unofficial Reference 2024.12.1
__init__.py
Go to the documentation of this file.
1 """Support for functionality to have conversations with Home Assistant."""
2 
3 from __future__ import annotations
4 
5 import logging
6 import re
7 from typing import Literal
8 
9 import voluptuous as vol
10 
11 from homeassistant.config_entries import ConfigEntry
12 from homeassistant.const import MATCH_ALL
13 from homeassistant.core import (
14  HomeAssistant,
15  ServiceCall,
16  ServiceResponse,
17  SupportsResponse,
18  callback,
19 )
20 from homeassistant.exceptions import HomeAssistantError
21 from homeassistant.helpers import config_validation as cv, intent
22 from homeassistant.helpers.entity_component import EntityComponent
23 from homeassistant.helpers.typing import ConfigType
24 from homeassistant.loader import bind_hass
25 
26 from .agent_manager import (
27  AgentInfo,
28  agent_id_validator,
29  async_converse,
30  async_get_agent,
31  get_agent_manager,
32 )
33 from .const import (
34  ATTR_AGENT_ID,
35  ATTR_CONVERSATION_ID,
36  ATTR_LANGUAGE,
37  ATTR_TEXT,
38  DATA_COMPONENT,
39  DATA_DEFAULT_ENTITY,
40  DOMAIN,
41  HOME_ASSISTANT_AGENT,
42  OLD_HOME_ASSISTANT_AGENT,
43  SERVICE_PROCESS,
44  SERVICE_RELOAD,
45  ConversationEntityFeature,
46 )
47 from .default_agent import DefaultAgent, async_setup_default_agent
48 from .entity import ConversationEntity
49 from .http import async_setup as async_setup_conversation_http
50 from .models import AbstractConversationAgent, ConversationInput, ConversationResult
51 from .trace import ConversationTraceEventType, async_conversation_trace_append
52 
53 __all__ = [
54  "DOMAIN",
55  "HOME_ASSISTANT_AGENT",
56  "OLD_HOME_ASSISTANT_AGENT",
57  "ConversationEntity",
58  "ConversationEntityFeature",
59  "ConversationInput",
60  "ConversationResult",
61  "ConversationTraceEventType",
62  "async_conversation_trace_append",
63  "async_converse",
64  "async_get_agent_info",
65  "async_set_agent",
66  "async_setup",
67  "async_unset_agent",
68 ]
69 
70 _LOGGER = logging.getLogger(__name__)
71 
72 REGEX_TYPE = type(re.compile(""))
73 
74 SERVICE_PROCESS_SCHEMA = vol.Schema(
75  {
76  vol.Required(ATTR_TEXT): cv.string,
77  vol.Optional(ATTR_LANGUAGE): cv.string,
78  vol.Optional(ATTR_AGENT_ID): agent_id_validator,
79  vol.Optional(ATTR_CONVERSATION_ID): cv.string,
80  }
81 )
82 
83 
84 SERVICE_RELOAD_SCHEMA = vol.Schema(
85  {
86  vol.Optional(ATTR_LANGUAGE): cv.string,
87  vol.Optional(ATTR_AGENT_ID): agent_id_validator,
88  }
89 )
90 
91 CONFIG_SCHEMA = vol.Schema(
92  {
93  vol.Optional(DOMAIN): vol.Schema(
94  {
95  vol.Optional("intents"): vol.Schema(
96  {cv.string: vol.All(cv.ensure_list, [cv.string])}
97  )
98  }
99  )
100  },
101  extra=vol.ALLOW_EXTRA,
102 )
103 
104 
105 @callback
106 @bind_hass
108  hass: HomeAssistant,
109  config_entry: ConfigEntry,
110  agent: AbstractConversationAgent,
111 ) -> None:
112  """Set the agent to handle the conversations."""
113  get_agent_manager(hass).async_set_agent(config_entry.entry_id, agent)
114 
115 
116 @callback
117 @bind_hass
119  hass: HomeAssistant,
120  config_entry: ConfigEntry,
121 ) -> None:
122  """Set the agent to handle the conversations."""
123  get_agent_manager(hass).async_unset_agent(config_entry.entry_id)
124 
125 
126 @callback
128  hass: HomeAssistant, agent_id: str | None = None
129 ) -> set[str] | Literal["*"]:
130  """Return languages supported by conversation agents.
131 
132  If an agent is specified, returns a set of languages supported by that agent.
133  If no agent is specified, return a set with the union of languages supported by
134  all conversation agents.
135  """
136  agent_manager = get_agent_manager(hass)
137  agents: list[ConversationEntity | AbstractConversationAgent]
138 
139  if agent_id:
140  agent = async_get_agent(hass, agent_id)
141 
142  if agent is None:
143  raise ValueError(f"Agent {agent_id} not found")
144 
145  # Shortcut
146  if agent.supported_languages == MATCH_ALL:
147  return MATCH_ALL
148 
149  agents = [agent]
150 
151  else:
152  agents = list(hass.data[DATA_COMPONENT].entities)
153  for info in agent_manager.async_get_agent_info():
154  agent = agent_manager.async_get_agent(info.id)
155  assert agent is not None
156 
157  # Shortcut
158  if agent.supported_languages == MATCH_ALL:
159  return MATCH_ALL
160 
161  agents.append(agent)
162 
163  languages: set[str] = set()
164 
165  for agent in agents:
166  for language_tag in agent.supported_languages:
167  languages.add(language_tag)
168 
169  return languages
170 
171 
172 @callback
174  hass: HomeAssistant,
175  agent_id: str | None = None,
176 ) -> AgentInfo | None:
177  """Get information on the agent or None if not found."""
178  agent = async_get_agent(hass, agent_id)
179 
180  if agent is None:
181  return None
182 
183  if isinstance(agent, ConversationEntity):
184  name = agent.name
185  if not isinstance(name, str):
186  name = agent.entity_id
187  return AgentInfo(id=agent.entity_id, name=name)
188 
189  manager = get_agent_manager(hass)
190 
191  for agent_info in manager.async_get_agent_info():
192  if agent_info.id == agent_id:
193  return agent_info
194 
195  return None
196 
197 
199  hass: HomeAssistant, agent_id: str | None, language: str
200 ) -> None:
201  """Prepare given agent."""
202  agent = async_get_agent(hass, agent_id)
203 
204  if agent is None:
205  raise ValueError("Invalid agent specified")
206 
207  await agent.async_prepare(language)
208 
209 
211  hass: HomeAssistant, user_input: ConversationInput
212 ) -> str | None:
213  """Try to match input against sentence triggers and return response text.
214 
215  Returns None if no match occurred.
216  """
217  default_agent = async_get_agent(hass)
218  assert isinstance(default_agent, DefaultAgent)
219 
220  return await default_agent.async_handle_sentence_triggers(user_input)
221 
222 
224  hass: HomeAssistant, user_input: ConversationInput
225 ) -> intent.IntentResponse | None:
226  """Try to match input against registered intents and return response.
227 
228  Returns None if no match occurred.
229  """
230  default_agent = async_get_agent(hass)
231  assert isinstance(default_agent, DefaultAgent)
232 
233  return await default_agent.async_handle_intents(user_input)
234 
235 
236 async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
237  """Register the process service."""
238  entity_component = EntityComponent[ConversationEntity](_LOGGER, DOMAIN, hass)
239  hass.data[DATA_COMPONENT] = entity_component
240 
242  hass, entity_component, config.get(DOMAIN, {}).get("intents", {})
243  )
244 
245  # Temporary migration. We can remove this in 2024.10
246  from homeassistant.components.assist_pipeline import ( # pylint: disable=import-outside-toplevel
247  async_migrate_engine,
248  )
249 
251  hass, "conversation", OLD_HOME_ASSISTANT_AGENT, HOME_ASSISTANT_AGENT
252  )
253 
254  async def handle_process(service: ServiceCall) -> ServiceResponse:
255  """Parse text into commands."""
256  text = service.data[ATTR_TEXT]
257  _LOGGER.debug("Processing: <%s>", text)
258  try:
259  result = await async_converse(
260  hass=hass,
261  text=text,
262  conversation_id=service.data.get(ATTR_CONVERSATION_ID),
263  context=service.context,
264  language=service.data.get(ATTR_LANGUAGE),
265  agent_id=service.data.get(ATTR_AGENT_ID),
266  )
267  except intent.IntentHandleError as err:
268  raise HomeAssistantError(f"Error processing {text}: {err}") from err
269 
270  if service.return_response:
271  return result.as_dict()
272 
273  return None
274 
275  async def handle_reload(service: ServiceCall) -> None:
276  """Reload intents."""
277  await hass.data[DATA_DEFAULT_ENTITY].async_reload(
278  language=service.data.get(ATTR_LANGUAGE)
279  )
280 
281  hass.services.async_register(
282  DOMAIN,
283  SERVICE_PROCESS,
284  handle_process,
285  schema=SERVICE_PROCESS_SCHEMA,
286  supports_response=SupportsResponse.OPTIONAL,
287  )
288  hass.services.async_register(
289  DOMAIN, SERVICE_RELOAD, handle_reload, schema=SERVICE_RELOAD_SCHEMA
290  )
291  async_setup_conversation_http(hass)
292 
293  return True
294 
295 
296 async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
297  """Set up a config entry."""
298  return await hass.data[DATA_COMPONENT].async_setup_entry(entry)
299 
300 
301 async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
302  """Unload a config entry."""
303  return await hass.data[DATA_COMPONENT].async_unload_entry(entry)
None async_migrate_engine(HomeAssistant hass, Literal["conversation", "stt", "tts", "wake_word"] engine_type, str old_value, str new_value)
Definition: pipeline.py:1878
web.Response get(self, web.Request request, str config_key)
Definition: view.py:88
AgentManager get_agent_manager(HomeAssistant hass)
ConversationResult async_converse(HomeAssistant hass, str text, str|None conversation_id, Context context, str|None language=None, str|None agent_id=None, str|None device_id=None)
AbstractConversationAgent|ConversationEntity|None async_get_agent(HomeAssistant hass, str|None agent_id=None)
None async_setup_default_agent(core.HomeAssistant hass, EntityComponent[ConversationEntity] entity_component, dict[str, Any] config_intents)
str|None async_handle_sentence_triggers(HomeAssistant hass, ConversationInput user_input)
Definition: __init__.py:212
AgentInfo|None async_get_agent_info(HomeAssistant hass, str|None agent_id=None)
Definition: __init__.py:176
bool async_unload_entry(HomeAssistant hass, ConfigEntry entry)
Definition: __init__.py:301
None async_unset_agent(HomeAssistant hass, ConfigEntry config_entry)
Definition: __init__.py:121
intent.IntentResponse|None async_handle_intents(HomeAssistant hass, ConversationInput user_input)
Definition: __init__.py:225
None async_set_agent(HomeAssistant hass, ConfigEntry config_entry, AbstractConversationAgent agent)
Definition: __init__.py:111
None async_prepare_agent(HomeAssistant hass, str|None agent_id, str language)
Definition: __init__.py:200
bool async_setup(HomeAssistant hass, ConfigType config)
Definition: __init__.py:236
bool async_setup_entry(HomeAssistant hass, ConfigEntry entry)
Definition: __init__.py:296
set[str]|Literal["*"] async_get_conversation_languages(HomeAssistant hass, str|None agent_id=None)
Definition: __init__.py:129
None async_reload(HomeAssistant hass, ServiceCall service_call)
Definition: __init__.py:74