Home Assistant Unofficial Reference 2024.12.1
conversation.py
Go to the documentation of this file.
1 """Support for Wyoming intent recognition services."""
2 
3 import logging
4 
5 from wyoming.asr import Transcript
6 from wyoming.client import AsyncTcpClient
7 from wyoming.handle import Handled, NotHandled
8 from wyoming.info import HandleProgram, IntentProgram
9 from wyoming.intent import Intent, NotRecognized
10 
11 from homeassistant.components import conversation
12 from homeassistant.config_entries import ConfigEntry
13 from homeassistant.core import HomeAssistant
14 from homeassistant.helpers import intent
15 from homeassistant.helpers.entity_platform import AddEntitiesCallback
16 from homeassistant.util import ulid
17 
18 from .const import DOMAIN
19 from .data import WyomingService
20 from .error import WyomingError
21 from .models import DomainDataItem
22 
23 _LOGGER = logging.getLogger(__name__)
24 
25 
27  hass: HomeAssistant,
28  config_entry: ConfigEntry,
29  async_add_entities: AddEntitiesCallback,
30 ) -> None:
31  """Set up Wyoming conversation."""
32  item: DomainDataItem = hass.data[DOMAIN][config_entry.entry_id]
34  [
35  WyomingConversationEntity(config_entry, item.service),
36  ]
37  )
38 
39 
42 ):
43  """Wyoming conversation agent."""
44 
45  _attr_has_entity_name = True
46 
47  def __init__(
48  self,
49  config_entry: ConfigEntry,
50  service: WyomingService,
51  ) -> None:
52  """Set up provider."""
53  super().__init__()
54 
55  self.serviceservice = service
56 
57  self._intent_service_intent_service: IntentProgram | None = None
58  self._handle_service_handle_service: HandleProgram | None = None
59 
60  for maybe_intent in self.serviceservice.info.intent:
61  if maybe_intent.installed:
62  self._intent_service_intent_service = maybe_intent
63  break
64 
65  for maybe_handle in self.serviceservice.info.handle:
66  if maybe_handle.installed:
67  self._handle_service_handle_service = maybe_handle
68  break
69 
70  model_languages: set[str] = set()
71 
72  if self._intent_service_intent_service is not None:
73  for intent_model in self._intent_service_intent_service.models:
74  if intent_model.installed:
75  model_languages.update(intent_model.languages)
76 
77  self._attr_name_attr_name = self._intent_service_intent_service.name
78  self._attr_supported_features_attr_supported_features_attr_supported_features = (
79  conversation.ConversationEntityFeature.CONTROL
80  )
81  elif self._handle_service_handle_service is not None:
82  for handle_model in self._handle_service_handle_service.models:
83  if handle_model.installed:
84  model_languages.update(handle_model.languages)
85 
86  self._attr_name_attr_name = self._handle_service_handle_service.name
87 
88  self._supported_languages_supported_languages = list(model_languages)
89  self._attr_unique_id_attr_unique_id = f"{config_entry.entry_id}-conversation"
90 
91  @property
92  def supported_languages(self) -> list[str]:
93  """Return a list of supported languages."""
94  return self._supported_languages_supported_languages
95 
96  async def async_process(
97  self, user_input: conversation.ConversationInput
99  """Process a sentence."""
100  conversation_id = user_input.conversation_id or ulid.ulid_now()
101  intent_response = intent.IntentResponse(language=user_input.language)
102 
103  try:
104  async with AsyncTcpClient(self.serviceservice.host, self.serviceservice.port) as client:
105  await client.write_event(
106  Transcript(
107  user_input.text, context={"conversation_id": conversation_id}
108  ).event()
109  )
110 
111  while True:
112  event = await client.read_event()
113  if event is None:
114  _LOGGER.debug("Connection lost")
115  intent_response.async_set_error(
116  intent.IntentResponseErrorCode.UNKNOWN,
117  "Connection to service was lost",
118  )
120  response=intent_response,
121  conversation_id=user_input.conversation_id,
122  )
123 
124  if Intent.is_type(event.type):
125  # Success
126  recognized_intent = Intent.from_event(event)
127  _LOGGER.debug("Recognized intent: %s", recognized_intent)
128 
129  intent_type = recognized_intent.name
130  intent_slots = {
131  e.name: {"value": e.value}
132  for e in recognized_intent.entities
133  }
134  intent_response = await intent.async_handle(
135  self.hasshass,
136  DOMAIN,
137  intent_type,
138  intent_slots,
139  text_input=user_input.text,
140  language=user_input.language,
141  )
142 
143  if (not intent_response.speech) and recognized_intent.text:
144  intent_response.async_set_speech(recognized_intent.text)
145 
146  break
147 
148  if NotRecognized.is_type(event.type):
149  not_recognized = NotRecognized.from_event(event)
150  intent_response.async_set_error(
151  intent.IntentResponseErrorCode.NO_INTENT_MATCH,
152  not_recognized.text,
153  )
154  break
155 
156  if Handled.is_type(event.type):
157  # Success
158  handled = Handled.from_event(event)
159  intent_response.async_set_speech(handled.text)
160  break
161 
162  if NotHandled.is_type(event.type):
163  not_handled = NotHandled.from_event(event)
164  intent_response.async_set_error(
165  intent.IntentResponseErrorCode.FAILED_TO_HANDLE,
166  not_handled.text,
167  )
168  break
169 
170  except (OSError, WyomingError) as err:
171  _LOGGER.exception("Unexpected error while communicating with service")
172  intent_response.async_set_error(
173  intent.IntentResponseErrorCode.UNKNOWN,
174  f"Error communicating with service: {err}",
175  )
177  response=intent_response,
178  conversation_id=user_input.conversation_id,
179  )
180  except intent.IntentError as err:
181  _LOGGER.exception("Unexpected error while handling intent")
182  intent_response.async_set_error(
183  intent.IntentResponseErrorCode.FAILED_TO_HANDLE,
184  f"Error handling intent: {err}",
185  )
187  response=intent_response,
188  conversation_id=user_input.conversation_id,
189  )
190 
191  # Success
193  response=intent_response, conversation_id=conversation_id
194  )
None __init__(self, ConfigEntry config_entry, WyomingService service)
Definition: conversation.py:51
conversation.ConversationResult async_process(self, conversation.ConversationInput user_input)
Definition: conversation.py:98
None async_setup_entry(HomeAssistant hass, ConfigEntry config_entry, AddEntitiesCallback async_add_entities)
Definition: conversation.py:30