diff --git a/homeassistant/components/climate/intent.py b/homeassistant/components/climate/intent.py index 632e678be94..a7bf3357f99 100644 --- a/homeassistant/components/climate/intent.py +++ b/homeassistant/components/climate/intent.py @@ -22,6 +22,7 @@ class GetTemperatureIntent(intent.IntentHandler): """Handle GetTemperature intents.""" intent_type = INTENT_GET_TEMPERATURE + description = "Gets the current temperature of a climate device or entity" slot_schema = {vol.Optional("area"): str, vol.Optional("name"): str} async def async_handle(self, intent_obj: intent.Intent) -> intent.IntentResponse: diff --git a/homeassistant/components/humidifier/intent.py b/homeassistant/components/humidifier/intent.py index 361de8e36db..ffe41b48c04 100644 --- a/homeassistant/components/humidifier/intent.py +++ b/homeassistant/components/humidifier/intent.py @@ -33,6 +33,7 @@ class HumidityHandler(intent.IntentHandler): """Handle set humidity intents.""" intent_type = INTENT_HUMIDITY + description = "Set desired humidity level" slot_schema = { vol.Required("name"): cv.string, vol.Required("humidity"): vol.All(vol.Coerce(int), vol.Range(0, 100)), @@ -85,6 +86,7 @@ class SetModeHandler(intent.IntentHandler): """Handle set humidity intents.""" intent_type = INTENT_MODE + description = "Set humidifier mode" slot_schema = { vol.Required("name"): cv.string, vol.Required("mode"): cv.string, diff --git a/homeassistant/components/intent/__init__.py b/homeassistant/components/intent/__init__.py index 31dee02c7e4..feac4ef05d9 100644 --- a/homeassistant/components/intent/__init__.py +++ b/homeassistant/components/intent/__init__.py @@ -73,15 +73,30 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: intent.async_register( hass, - OnOffIntentHandler(intent.INTENT_TURN_ON, HA_DOMAIN, SERVICE_TURN_ON), + OnOffIntentHandler( + intent.INTENT_TURN_ON, + HA_DOMAIN, + SERVICE_TURN_ON, + description="Turns on/opens a device or entity", + ), ) intent.async_register( hass, - OnOffIntentHandler(intent.INTENT_TURN_OFF, HA_DOMAIN, SERVICE_TURN_OFF), + OnOffIntentHandler( + intent.INTENT_TURN_OFF, + HA_DOMAIN, + SERVICE_TURN_OFF, + description="Turns off/closes a device or entity", + ), ) intent.async_register( hass, - intent.ServiceIntentHandler(intent.INTENT_TOGGLE, HA_DOMAIN, SERVICE_TOGGLE), + intent.ServiceIntentHandler( + intent.INTENT_TOGGLE, + HA_DOMAIN, + SERVICE_TOGGLE, + "Toggles a device or entity", + ), ) intent.async_register( hass, @@ -195,6 +210,7 @@ class GetStateIntentHandler(intent.IntentHandler): """Answer questions about entity states.""" intent_type = intent.INTENT_GET_STATE + description = "Gets or checks the state of a device or entity" slot_schema = { vol.Any("name", "area", "floor"): cv.string, vol.Optional("domain"): vol.All(cv.ensure_list, [cv.string]), @@ -314,6 +330,7 @@ class NevermindIntentHandler(intent.IntentHandler): """Takes no action.""" intent_type = intent.INTENT_NEVERMIND + description = "Cancels the current request and does nothing" async def async_handle(self, intent_obj: intent.Intent) -> intent.IntentResponse: """Doe not do anything, and produces an empty response.""" @@ -323,6 +340,8 @@ class NevermindIntentHandler(intent.IntentHandler): class SetPositionIntentHandler(intent.DynamicServiceIntentHandler): """Intent handler for setting positions.""" + description = "Sets the position of a device or entity" + def __init__(self) -> None: """Create set position handler.""" super().__init__( diff --git a/homeassistant/components/intent/timers.py b/homeassistant/components/intent/timers.py index e653ccfa930..837f4117c41 100644 --- a/homeassistant/components/intent/timers.py +++ b/homeassistant/components/intent/timers.py @@ -690,6 +690,7 @@ class StartTimerIntentHandler(intent.IntentHandler): """Intent handler for starting a new timer.""" intent_type = intent.INTENT_START_TIMER + description = "Starts a new timer" slot_schema = { vol.Required(vol.Any("hours", "minutes", "seconds")): cv.positive_int, vol.Optional("name"): cv.string, @@ -733,6 +734,7 @@ class CancelTimerIntentHandler(intent.IntentHandler): """Intent handler for cancelling a timer.""" intent_type = intent.INTENT_CANCEL_TIMER + description = "Cancels a timer" slot_schema = { vol.Any("start_hours", "start_minutes", "start_seconds"): cv.positive_int, vol.Optional("name"): cv.string, @@ -755,6 +757,7 @@ class IncreaseTimerIntentHandler(intent.IntentHandler): """Intent handler for increasing the time of a timer.""" intent_type = intent.INTENT_INCREASE_TIMER + description = "Adds more time to a timer" slot_schema = { vol.Any("hours", "minutes", "seconds"): cv.positive_int, vol.Any("start_hours", "start_minutes", "start_seconds"): cv.positive_int, @@ -779,6 +782,7 @@ class DecreaseTimerIntentHandler(intent.IntentHandler): """Intent handler for decreasing the time of a timer.""" intent_type = intent.INTENT_DECREASE_TIMER + description = "Removes time from a timer" slot_schema = { vol.Required(vol.Any("hours", "minutes", "seconds")): cv.positive_int, vol.Any("start_hours", "start_minutes", "start_seconds"): cv.positive_int, @@ -803,6 +807,7 @@ class PauseTimerIntentHandler(intent.IntentHandler): """Intent handler for pausing a running timer.""" intent_type = intent.INTENT_PAUSE_TIMER + description = "Pauses a running timer" slot_schema = { vol.Any("start_hours", "start_minutes", "start_seconds"): cv.positive_int, vol.Optional("name"): cv.string, @@ -825,6 +830,7 @@ class UnpauseTimerIntentHandler(intent.IntentHandler): """Intent handler for unpausing a paused timer.""" intent_type = intent.INTENT_UNPAUSE_TIMER + description = "Resumes a paused timer" slot_schema = { vol.Any("start_hours", "start_minutes", "start_seconds"): cv.positive_int, vol.Optional("name"): cv.string, @@ -847,6 +853,7 @@ class TimerStatusIntentHandler(intent.IntentHandler): """Intent handler for reporting the status of a timer.""" intent_type = intent.INTENT_TIMER_STATUS + description = "Reports the current status of timers" slot_schema = { vol.Any("start_hours", "start_minutes", "start_seconds"): cv.positive_int, vol.Optional("name"): cv.string, diff --git a/homeassistant/components/light/intent.py b/homeassistant/components/light/intent.py index 1092c42d6d2..a2824f7cc22 100644 --- a/homeassistant/components/light/intent.py +++ b/homeassistant/components/light/intent.py @@ -32,5 +32,6 @@ async def async_setup_intents(hass: HomeAssistant) -> None: vol.Coerce(int), vol.Range(0, 100) ), }, + description="Sets the brightness or color of a light", ), ) diff --git a/homeassistant/components/media_player/intent.py b/homeassistant/components/media_player/intent.py index da8da6c2c58..1c2de8371f1 100644 --- a/homeassistant/components/media_player/intent.py +++ b/homeassistant/components/media_player/intent.py @@ -65,6 +65,7 @@ async def async_setup_intents(hass: HomeAssistant) -> None: required_domains={DOMAIN}, required_features=MediaPlayerEntityFeature.NEXT_TRACK, required_states={MediaPlayerState.PLAYING}, + description="Skips a media player to the next item", ), ) intent.async_register( @@ -81,6 +82,7 @@ async def async_setup_intents(hass: HomeAssistant) -> None: vol.Coerce(int), vol.Range(min=0, max=100), lambda val: val / 100 ) }, + description="Sets the volume of a media player", ), ) @@ -97,6 +99,7 @@ class MediaPauseHandler(intent.ServiceIntentHandler): required_domains={DOMAIN}, required_features=MediaPlayerEntityFeature.PAUSE, required_states={MediaPlayerState.PLAYING}, + description="Pauses a media player", ) self.last_paused = last_paused @@ -130,6 +133,7 @@ class MediaUnpauseHandler(intent.ServiceIntentHandler): SERVICE_MEDIA_PLAY, required_domains={DOMAIN}, required_states={MediaPlayerState.PAUSED}, + description="Resumes a media player", ) self.last_paused = last_paused diff --git a/homeassistant/components/shopping_list/intent.py b/homeassistant/components/shopping_list/intent.py index 70a70467cbd..35bc2ff4787 100644 --- a/homeassistant/components/shopping_list/intent.py +++ b/homeassistant/components/shopping_list/intent.py @@ -22,6 +22,7 @@ class AddItemIntent(intent.IntentHandler): """Handle AddItem intents.""" intent_type = INTENT_ADD_ITEM + description = "Adds an item to the shopping list" slot_schema = {"item": cv.string} async def async_handle(self, intent_obj: intent.Intent) -> intent.IntentResponse: @@ -39,6 +40,7 @@ class ListTopItemsIntent(intent.IntentHandler): """Handle AddItem intents.""" intent_type = INTENT_LAST_ITEMS + description = "List the top five items on the shopping list" slot_schema = {"item": cv.string} async def async_handle(self, intent_obj: intent.Intent) -> intent.IntentResponse: diff --git a/homeassistant/components/todo/intent.py b/homeassistant/components/todo/intent.py index 81d5ca2ae0c..779c51b3bf7 100644 --- a/homeassistant/components/todo/intent.py +++ b/homeassistant/components/todo/intent.py @@ -21,6 +21,7 @@ class ListAddItemIntent(intent.IntentHandler): """Handle ListAddItem intents.""" intent_type = INTENT_LIST_ADD_ITEM + description = "Add item to a todo list" slot_schema = {"item": cv.string, "name": cv.string} async def async_handle(self, intent_obj: intent.Intent) -> intent.IntentResponse: diff --git a/homeassistant/components/vacuum/intent.py b/homeassistant/components/vacuum/intent.py index 534078ec8af..7ab5ab18374 100644 --- a/homeassistant/components/vacuum/intent.py +++ b/homeassistant/components/vacuum/intent.py @@ -13,11 +13,16 @@ async def async_setup_intents(hass: HomeAssistant) -> None: """Set up the vacuum intents.""" intent.async_register( hass, - intent.ServiceIntentHandler(INTENT_VACUUM_START, DOMAIN, SERVICE_START), + intent.ServiceIntentHandler( + INTENT_VACUUM_START, DOMAIN, SERVICE_START, description="Starts a vacuum" + ), ) intent.async_register( hass, intent.ServiceIntentHandler( - INTENT_VACUUM_RETURN_TO_BASE, DOMAIN, SERVICE_RETURN_TO_BASE + INTENT_VACUUM_RETURN_TO_BASE, + DOMAIN, + SERVICE_RETURN_TO_BASE, + description="Returns a vacuum to base", ), ) diff --git a/homeassistant/components/weather/intent.py b/homeassistant/components/weather/intent.py index c216fcda17d..92ffc851cc9 100644 --- a/homeassistant/components/weather/intent.py +++ b/homeassistant/components/weather/intent.py @@ -23,6 +23,7 @@ class GetWeatherIntent(intent.IntentHandler): """Handle GetWeather intents.""" intent_type = INTENT_GET_WEATHER + description = "Gets the current weather" slot_schema = {vol.Optional("name"): cv.string} async def async_handle(self, intent_obj: intent.Intent) -> intent.IntentResponse: diff --git a/homeassistant/helpers/intent.py b/homeassistant/helpers/intent.py index 3a616b5e29c..8f5ace63be8 100644 --- a/homeassistant/helpers/intent.py +++ b/homeassistant/helpers/intent.py @@ -725,6 +725,7 @@ class IntentHandler: intent_type: str platforms: Iterable[str] | None = [] + description: str | None = None @property def slot_schema(self) -> dict | None: @@ -784,6 +785,7 @@ class DynamicServiceIntentHandler(IntentHandler): required_domains: set[str] | None = None, required_features: int | None = None, required_states: set[str] | None = None, + description: str | None = None, ) -> None: """Create Service Intent Handler.""" self.intent_type = intent_type @@ -791,6 +793,7 @@ class DynamicServiceIntentHandler(IntentHandler): self.required_domains = required_domains self.required_features = required_features self.required_states = required_states + self.description = description self.required_slots: dict[tuple[str, str], vol.Schema] = {} if required_slots: @@ -1076,6 +1079,7 @@ class ServiceIntentHandler(DynamicServiceIntentHandler): required_domains: set[str] | None = None, required_features: int | None = None, required_states: set[str] | None = None, + description: str | None = None, ) -> None: """Create service handler.""" super().__init__( @@ -1086,6 +1090,7 @@ class ServiceIntentHandler(DynamicServiceIntentHandler): required_domains=required_domains, required_features=required_features, required_states=required_states, + description=description, ) self.domain = domain self.service = service diff --git a/homeassistant/helpers/llm.py b/homeassistant/helpers/llm.py index 2edc6d650f4..0442678e835 100644 --- a/homeassistant/helpers/llm.py +++ b/homeassistant/helpers/llm.py @@ -136,7 +136,9 @@ class IntentTool(Tool): ) -> None: """Init the class.""" self.name = intent_handler.intent_type - self.description = f"Execute Home Assistant {self.name} intent" + self.description = ( + intent_handler.description or f"Execute Home Assistant {self.name} intent" + ) if slot_schema := intent_handler.slot_schema: self.parameters = vol.Schema(slot_schema) diff --git a/tests/helpers/test_llm.py b/tests/helpers/test_llm.py index 8b3de48e5ae..b8f5755ae39 100644 --- a/tests/helpers/test_llm.py +++ b/tests/helpers/test_llm.py @@ -118,3 +118,21 @@ async def test_assist_api(hass: HomeAssistant) -> None: "response_type": "action_done", "speech": {}, } + + +async def test_assist_api_description(hass: HomeAssistant) -> None: + """Test intent description with Assist API.""" + + class MyIntentHandler(intent.IntentHandler): + intent_type = "test_intent" + description = "my intent handler" + + intent.async_register(hass, MyIntentHandler()) + + assert len(llm.async_get_apis(hass)) == 1 + api = llm.async_get_api(hass, "assist") + tools = api.async_get_tools() + assert len(tools) == 1 + tool = tools[0] + assert tool.name == "test_intent" + assert tool.description == "my intent handler"