Add targeted entities to sentence debug API (#95480)

* Return targets with debug sentence API

* Update test

* Update homeassistant/components/conversation/__init__.py

* Include area/domain in test sentences

---------

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
This commit is contained in:
Michael Hansen 2023-06-28 17:34:43 -05:00 committed by GitHub
parent 0b81550092
commit 487dd3f956
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 116 additions and 1 deletions

View File

@ -8,6 +8,7 @@ import logging
import re import re
from typing import Any, Literal from typing import Any, Literal
from hassil.recognize import RecognizeResult
import voluptuous as vol import voluptuous as vol
from homeassistant import core from homeassistant import core
@ -353,6 +354,10 @@ async def websocket_hass_agent_debug(
} }
for entity_key, entity in result.entities.items() for entity_key, entity in result.entities.items()
}, },
"targets": {
state.entity_id: {"matched": is_matched}
for state, is_matched in _get_debug_targets(hass, result)
},
} }
if result is not None if result is not None
else None else None
@ -362,6 +367,49 @@ async def websocket_hass_agent_debug(
) )
def _get_debug_targets(
hass: HomeAssistant,
result: RecognizeResult,
) -> Iterable[tuple[core.State, bool]]:
"""Yield state/is_matched pairs for a hassil recognition."""
entities = result.entities
name: str | None = None
area_name: str | None = None
domains: set[str] | None = None
device_classes: set[str] | None = None
state_names: set[str] | None = None
if "name" in entities:
name = str(entities["name"].value)
if "area" in entities:
area_name = str(entities["area"].value)
if "domain" in entities:
domains = set(cv.ensure_list(entities["domain"].value))
if "device_class" in entities:
device_classes = set(cv.ensure_list(entities["device_class"].value))
if "state" in entities:
# HassGetState only
state_names = set(cv.ensure_list(entities["state"].value))
states = intent.async_match_states(
hass,
name=name,
area_name=area_name,
domains=domains,
device_classes=device_classes,
)
for state in states:
# For queries, a target is "matched" based on its state
is_matched = (state_names is None) or (state.state in state_names)
yield state, is_matched
class ConversationProcessView(http.HomeAssistantView): class ConversationProcessView(http.HomeAssistantView):
"""View to process text.""" """View to process text."""

View File

@ -382,6 +382,11 @@
'intent': dict({ 'intent': dict({
'name': 'HassTurnOn', 'name': 'HassTurnOn',
}), }),
'targets': dict({
'light.kitchen': dict({
'matched': True,
}),
}),
}), }),
dict({ dict({
'entities': dict({ 'entities': dict({
@ -394,6 +399,60 @@
'intent': dict({ 'intent': dict({
'name': 'HassTurnOff', 'name': 'HassTurnOff',
}), }),
'targets': dict({
'light.kitchen': dict({
'matched': True,
}),
}),
}),
dict({
'entities': dict({
'area': dict({
'name': 'area',
'text': 'kitchen',
'value': 'kitchen',
}),
'domain': dict({
'name': 'domain',
'text': '',
'value': 'light',
}),
}),
'intent': dict({
'name': 'HassTurnOn',
}),
'targets': dict({
'light.kitchen': dict({
'matched': True,
}),
}),
}),
dict({
'entities': dict({
'area': dict({
'name': 'area',
'text': 'kitchen',
'value': 'kitchen',
}),
'domain': dict({
'name': 'domain',
'text': 'lights',
'value': 'light',
}),
'state': dict({
'name': 'state',
'text': 'on',
'value': 'on',
}),
}),
'intent': dict({
'name': 'HassGetState',
}),
'targets': dict({
'light.kitchen': dict({
'matched': False,
}),
}),
}), }),
None, None,
]), ]),

View File

@ -1652,16 +1652,22 @@ async def test_ws_hass_agent_debug(
hass: HomeAssistant, hass: HomeAssistant,
init_components, init_components,
hass_ws_client: WebSocketGenerator, hass_ws_client: WebSocketGenerator,
area_registry: ar.AreaRegistry,
entity_registry: er.EntityRegistry, entity_registry: er.EntityRegistry,
snapshot: SnapshotAssertion, snapshot: SnapshotAssertion,
) -> None: ) -> None:
"""Test homeassistant agent debug websocket command.""" """Test homeassistant agent debug websocket command."""
client = await hass_ws_client(hass) client = await hass_ws_client(hass)
kitchen_area = area_registry.async_create("kitchen")
entity_registry.async_get_or_create( entity_registry.async_get_or_create(
"light", "demo", "1234", suggested_object_id="kitchen" "light", "demo", "1234", suggested_object_id="kitchen"
) )
entity_registry.async_update_entity("light.kitchen", aliases={"my cool light"}) entity_registry.async_update_entity(
"light.kitchen",
aliases={"my cool light"},
area_id=kitchen_area.id,
)
hass.states.async_set("light.kitchen", "off") hass.states.async_set("light.kitchen", "off")
on_calls = async_mock_service(hass, LIGHT_DOMAIN, "turn_on") on_calls = async_mock_service(hass, LIGHT_DOMAIN, "turn_on")
@ -1673,6 +1679,8 @@ async def test_ws_hass_agent_debug(
"sentences": [ "sentences": [
"turn on my cool light", "turn on my cool light",
"turn my cool light off", "turn my cool light off",
"turn on all lights in the kitchen",
"how many lights are on in the kitchen?",
"this will not match anything", # null in results "this will not match anything", # null in results
], ],
} }