Home Assistant Unofficial Reference 2024.12.1
agent_manager.py
Go to the documentation of this file.
1 """Agent foundation for conversation integration."""
2 
3 from __future__ import annotations
4 
5 import dataclasses
6 import logging
7 from typing import Any
8 
9 import voluptuous as vol
10 
11 from homeassistant.core import Context, HomeAssistant, async_get_hass, callback
12 from homeassistant.helpers import config_validation as cv, singleton
13 
14 from .const import (
15  DATA_COMPONENT,
16  DATA_DEFAULT_ENTITY,
17  HOME_ASSISTANT_AGENT,
18  OLD_HOME_ASSISTANT_AGENT,
19 )
20 from .entity import ConversationEntity
21 from .models import (
22  AbstractConversationAgent,
23  AgentInfo,
24  ConversationInput,
25  ConversationResult,
26 )
27 from .trace import (
28  ConversationTraceEvent,
29  ConversationTraceEventType,
30  async_conversation_trace,
31 )
32 
33 _LOGGER = logging.getLogger(__name__)
34 
35 
36 @singleton.singleton("conversation_agent")
37 @callback
38 def get_agent_manager(hass: HomeAssistant) -> AgentManager:
39  """Get the active agent."""
40  return AgentManager(hass)
41 
42 
43 def agent_id_validator(value: Any) -> str:
44  """Validate agent ID."""
45  hass = async_get_hass()
46  if async_get_agent(hass, cv.string(value)) is None:
47  raise vol.Invalid("invalid agent ID")
48  return value
49 
50 
51 @callback
53  hass: HomeAssistant, agent_id: str | None = None
54 ) -> AbstractConversationAgent | ConversationEntity | None:
55  """Get specified agent."""
56  if agent_id is None or agent_id in (HOME_ASSISTANT_AGENT, OLD_HOME_ASSISTANT_AGENT):
57  return hass.data[DATA_DEFAULT_ENTITY]
58 
59  if "." in agent_id:
60  return hass.data[DATA_COMPONENT].get_entity(agent_id)
61 
62  manager = get_agent_manager(hass)
63 
64  if not manager.async_is_valid_agent_id(agent_id):
65  return None
66 
67  return manager.async_get_agent(agent_id)
68 
69 
70 async def async_converse(
71  hass: HomeAssistant,
72  text: str,
73  conversation_id: str | None,
74  context: Context,
75  language: str | None = None,
76  agent_id: str | None = None,
77  device_id: str | None = None,
78 ) -> ConversationResult:
79  """Process text and get intent."""
80  agent = async_get_agent(hass, agent_id)
81 
82  if agent is None:
83  raise ValueError(f"Agent {agent_id} not found")
84 
85  if isinstance(agent, ConversationEntity):
86  agent.async_set_context(context)
87  method = agent.internal_async_process
88  else:
89  method = agent.async_process
90 
91  if language is None:
92  language = hass.config.language
93 
94  _LOGGER.debug("Processing in %s: %s", language, text)
95  conversation_input = ConversationInput(
96  text=text,
97  context=context,
98  conversation_id=conversation_id,
99  device_id=device_id,
100  language=language,
101  agent_id=agent_id,
102  )
103  with async_conversation_trace() as trace:
104  trace.add_event(
106  ConversationTraceEventType.ASYNC_PROCESS,
107  dataclasses.asdict(conversation_input),
108  )
109  )
110  result = await method(conversation_input)
111  trace.set_result(**result.as_dict())
112  return result
113 
114 
116  """Class to manage conversation agents."""
117 
118  def __init__(self, hass: HomeAssistant) -> None:
119  """Initialize the conversation agents."""
120  self.hasshass = hass
121  self._agents: dict[str, AbstractConversationAgent] = {}
122 
123  @callback
124  def async_get_agent(self, agent_id: str) -> AbstractConversationAgent | None:
125  """Get the agent."""
126  if agent_id not in self._agents:
127  raise ValueError(f"Agent {agent_id} not found")
128 
129  return self._agents[agent_id]
130 
131  @callback
132  def async_get_agent_info(self) -> list[AgentInfo]:
133  """List all agents."""
134  agents: list[AgentInfo] = []
135  for agent_id, agent in self._agents.items():
136  config_entry = self.hasshass.config_entries.async_get_entry(agent_id)
137 
138  # Guard against potential bugs in conversation agents where the agent is not
139  # removed from the manager when the config entry is removed
140  if config_entry is None:
141  _LOGGER.warning(
142  "Conversation agent %s is still loaded after config entry removal",
143  agent,
144  )
145  continue
146 
147  agents.append(
148  AgentInfo(
149  id=agent_id,
150  name=config_entry.title or config_entry.domain,
151  )
152  )
153  return agents
154 
155  @callback
156  def async_is_valid_agent_id(self, agent_id: str) -> bool:
157  """Check if the agent id is valid."""
158  return agent_id in self._agents
159 
160  @callback
161  def async_set_agent(self, agent_id: str, agent: AbstractConversationAgent) -> None:
162  """Set the agent."""
163  self._agents[agent_id] = agent
164 
165  @callback
166  def async_unset_agent(self, agent_id: str) -> None:
167  """Unset the agent."""
168  self._agents.pop(agent_id, None)
None async_set_agent(self, str agent_id, AbstractConversationAgent agent)
AbstractConversationAgent|None async_get_agent(self, str agent_id)
CalendarEntity get_entity(HomeAssistant hass, str entity_id)
Definition: trigger.py:96
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)
Generator[ConversationTrace] async_conversation_trace()
Definition: trace.py:97
HomeAssistant async_get_hass()
Definition: core.py:286