mirror of
https://github.com/home-assistant/core.git
synced 2025-07-21 12:17:07 +00:00
Improve LLM tool descriptions for brightness and volume percentage (#138685)
* Improve tool descriptions for brightness and volume percentage * Address lint errors * Update intent.py to revert of a light * Create explicit types to make intent slots more future proof * Remove comments about slot type --------- Co-authored-by: Franck Nijhof <git@frenck.dev>
This commit is contained in:
parent
f0c5e00cc1
commit
6675b497bd
@ -28,13 +28,21 @@ async def async_setup_intents(hass: HomeAssistant) -> None:
|
||||
DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
optional_slots={
|
||||
("color", ATTR_RGB_COLOR): color_util.color_name_to_rgb,
|
||||
("temperature", ATTR_COLOR_TEMP_KELVIN): cv.positive_int,
|
||||
("brightness", ATTR_BRIGHTNESS_PCT): vol.All(
|
||||
vol.Coerce(int), vol.Range(0, 100)
|
||||
"color": intent.IntentSlotInfo(
|
||||
service_data_name=ATTR_RGB_COLOR,
|
||||
value_schema=color_util.color_name_to_rgb,
|
||||
),
|
||||
"temperature": intent.IntentSlotInfo(
|
||||
service_data_name=ATTR_COLOR_TEMP_KELVIN,
|
||||
value_schema=cv.positive_int,
|
||||
),
|
||||
"brightness": intent.IntentSlotInfo(
|
||||
service_data_name=ATTR_BRIGHTNESS_PCT,
|
||||
description="The brightness percentage of the light between 0 and 100, where 0 is off and 100 is fully lit",
|
||||
value_schema=vol.All(vol.Coerce(int), vol.Range(0, 100)),
|
||||
),
|
||||
},
|
||||
description="Sets the brightness or color of a light",
|
||||
description="Sets the brightness percentage or color of a light",
|
||||
platforms={DOMAIN},
|
||||
),
|
||||
)
|
||||
|
@ -96,11 +96,16 @@ async def async_setup_intents(hass: HomeAssistant) -> None:
|
||||
required_states={MediaPlayerState.PLAYING},
|
||||
required_features=MediaPlayerEntityFeature.VOLUME_SET,
|
||||
required_slots={
|
||||
ATTR_MEDIA_VOLUME_LEVEL: vol.All(
|
||||
vol.Coerce(int), vol.Range(min=0, max=100), lambda val: val / 100
|
||||
)
|
||||
ATTR_MEDIA_VOLUME_LEVEL: intent.IntentSlotInfo(
|
||||
description="The volume percentage of the media player",
|
||||
value_schema=vol.All(
|
||||
vol.Coerce(int),
|
||||
vol.Range(min=0, max=100),
|
||||
lambda val: val / 100,
|
||||
),
|
||||
),
|
||||
},
|
||||
description="Sets the volume of a media player",
|
||||
description="Sets the volume percentage of a media player",
|
||||
platforms={DOMAIN},
|
||||
device_classes={MediaPlayerDeviceClass},
|
||||
),
|
||||
|
@ -38,7 +38,7 @@ from .typing import VolSchemaType
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
type _SlotsType = dict[str, Any]
|
||||
type _IntentSlotsType = dict[
|
||||
str | tuple[str, str], VolSchemaType | Callable[[Any], Any]
|
||||
str | tuple[str, str], IntentSlotInfo | VolSchemaType | Callable[[Any], Any]
|
||||
]
|
||||
|
||||
INTENT_TURN_OFF = "HassTurnOff"
|
||||
@ -874,6 +874,34 @@ def non_empty_string(value: Any) -> str:
|
||||
return value_str
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class IntentSlotInfo:
|
||||
"""Details about how intent slots are processed and validated."""
|
||||
|
||||
service_data_name: str | None = None
|
||||
"""Optional name of the service data input to map to this slot."""
|
||||
|
||||
description: str | None = None
|
||||
"""Human readable description of the slot."""
|
||||
|
||||
value_schema: VolSchemaType | Callable[[Any], Any] = vol.Any
|
||||
"""Validator for the slot."""
|
||||
|
||||
|
||||
def _convert_slot_info(
|
||||
key: str | tuple[str, str],
|
||||
value: IntentSlotInfo | VolSchemaType | Callable[[Any], Any],
|
||||
) -> tuple[str, IntentSlotInfo]:
|
||||
"""Create an IntentSlotInfo from the various supported input arguments."""
|
||||
if isinstance(value, IntentSlotInfo):
|
||||
if not isinstance(key, str):
|
||||
raise TypeError("Tuple key and IntentSlotDescription value not supported")
|
||||
return key, value
|
||||
if isinstance(key, tuple):
|
||||
return key[0], IntentSlotInfo(service_data_name=key[1], value_schema=value)
|
||||
return key, IntentSlotInfo(value_schema=value)
|
||||
|
||||
|
||||
class DynamicServiceIntentHandler(IntentHandler):
|
||||
"""Service Intent handler registration (dynamic).
|
||||
|
||||
@ -907,23 +935,14 @@ class DynamicServiceIntentHandler(IntentHandler):
|
||||
self.platforms = platforms
|
||||
self.device_classes = device_classes
|
||||
|
||||
self.required_slots: _IntentSlotsType = {}
|
||||
if required_slots:
|
||||
for key, value_schema in required_slots.items():
|
||||
if isinstance(key, str):
|
||||
# Slot name/service data key
|
||||
key = (key, key)
|
||||
|
||||
self.required_slots[key] = value_schema
|
||||
|
||||
self.optional_slots: _IntentSlotsType = {}
|
||||
if optional_slots:
|
||||
for key, value_schema in optional_slots.items():
|
||||
if isinstance(key, str):
|
||||
# Slot name/service data key
|
||||
key = (key, key)
|
||||
|
||||
self.optional_slots[key] = value_schema
|
||||
self.required_slots: dict[str, IntentSlotInfo] = dict(
|
||||
_convert_slot_info(key, value)
|
||||
for key, value in (required_slots or {}).items()
|
||||
)
|
||||
self.optional_slots: dict[str, IntentSlotInfo] = dict(
|
||||
_convert_slot_info(key, value)
|
||||
for key, value in (optional_slots or {}).items()
|
||||
)
|
||||
|
||||
@cached_property
|
||||
def slot_schema(self) -> dict:
|
||||
@ -964,16 +983,20 @@ class DynamicServiceIntentHandler(IntentHandler):
|
||||
if self.required_slots:
|
||||
slot_schema.update(
|
||||
{
|
||||
vol.Required(key[0]): validator
|
||||
for key, validator in self.required_slots.items()
|
||||
vol.Required(
|
||||
key, description=slot_info.description
|
||||
): slot_info.value_schema
|
||||
for key, slot_info in self.required_slots.items()
|
||||
}
|
||||
)
|
||||
|
||||
if self.optional_slots:
|
||||
slot_schema.update(
|
||||
{
|
||||
vol.Optional(key[0]): validator
|
||||
for key, validator in self.optional_slots.items()
|
||||
vol.Optional(
|
||||
key, description=slot_info.description
|
||||
): slot_info.value_schema
|
||||
for key, slot_info in self.optional_slots.items()
|
||||
}
|
||||
)
|
||||
|
||||
@ -1156,18 +1179,15 @@ class DynamicServiceIntentHandler(IntentHandler):
|
||||
|
||||
service_data: dict[str, Any] = {ATTR_ENTITY_ID: state.entity_id}
|
||||
if self.required_slots:
|
||||
service_data.update(
|
||||
{
|
||||
key[1]: intent_obj.slots[key[0]]["value"]
|
||||
for key in self.required_slots
|
||||
}
|
||||
)
|
||||
for key, slot_info in self.required_slots.items():
|
||||
service_data[slot_info.service_data_name or key] = intent_obj.slots[
|
||||
key
|
||||
]["value"]
|
||||
|
||||
if self.optional_slots:
|
||||
for key in self.optional_slots:
|
||||
value = intent_obj.slots.get(key[0])
|
||||
if value:
|
||||
service_data[key[1]] = value["value"]
|
||||
for key, slot_info in self.optional_slots.items():
|
||||
if value := intent_obj.slots.get(key):
|
||||
service_data[slot_info.service_data_name or key] = value["value"]
|
||||
|
||||
await self._run_then_background(
|
||||
hass.async_create_task_internal(
|
||||
|
Loading…
x
Reference in New Issue
Block a user