Add description to intent handlers and use in LLM helper (#117864)

This commit is contained in:
Michael Hansen 2024-05-21 11:54:34 -05:00 committed by GitHub
parent 0c37a065ad
commit 8079cc0464
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 74 additions and 6 deletions

View file

@ -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:

View file

@ -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,

View file

@ -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__(

View file

@ -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,

View file

@ -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",
),
)

View file

@ -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

View file

@ -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:

View file

@ -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:

View file

@ -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",
),
)

View file

@ -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:

View file

@ -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

View file

@ -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)

View file

@ -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"