Upgrade to hassil 2.0 (#130544)
* Working on hassil 2.0 * Bump to hassil 2.0 * Update snapshots * Remove debug logging
This commit is contained in:
parent
4002bc3c25
commit
51c6ee97b1
12 changed files with 53 additions and 102 deletions
|
@ -16,11 +16,11 @@ from hassil.expression import Expression, ListReference, Sequence
|
|||
from hassil.intents import Intents, SlotList, TextSlotList, WildcardSlotList
|
||||
from hassil.recognize import (
|
||||
MISSING_ENTITY,
|
||||
MatchEntity,
|
||||
RecognizeResult,
|
||||
UnmatchedTextEntity,
|
||||
recognize_all,
|
||||
recognize_best,
|
||||
)
|
||||
from hassil.string_matcher import UnmatchedRangeEntity, UnmatchedTextEntity
|
||||
from hassil.util import merge_dict
|
||||
from home_assistant_intents import ErrorKey, get_intents, get_languages
|
||||
import yaml
|
||||
|
@ -499,6 +499,7 @@ class DefaultAgent(ConversationEntity):
|
|||
maybe_result: RecognizeResult | None = None
|
||||
best_num_matched_entities = 0
|
||||
best_num_unmatched_entities = 0
|
||||
best_num_unmatched_ranges = 0
|
||||
for result in recognize_all(
|
||||
user_input.text,
|
||||
lang_intents.intents,
|
||||
|
@ -517,10 +518,14 @@ class DefaultAgent(ConversationEntity):
|
|||
num_matched_entities += 1
|
||||
|
||||
num_unmatched_entities = 0
|
||||
num_unmatched_ranges = 0
|
||||
for unmatched_entity in result.unmatched_entities_list:
|
||||
if isinstance(unmatched_entity, UnmatchedTextEntity):
|
||||
if unmatched_entity.text != MISSING_ENTITY:
|
||||
num_unmatched_entities += 1
|
||||
elif isinstance(unmatched_entity, UnmatchedRangeEntity):
|
||||
num_unmatched_ranges += 1
|
||||
num_unmatched_entities += 1
|
||||
else:
|
||||
num_unmatched_entities += 1
|
||||
|
||||
|
@ -532,15 +537,24 @@ class DefaultAgent(ConversationEntity):
|
|||
(num_matched_entities == best_num_matched_entities)
|
||||
and (num_unmatched_entities < best_num_unmatched_entities)
|
||||
)
|
||||
or (
|
||||
# Prefer unmatched ranges
|
||||
(num_matched_entities == best_num_matched_entities)
|
||||
and (num_unmatched_entities == best_num_unmatched_entities)
|
||||
and (num_unmatched_ranges > best_num_unmatched_ranges)
|
||||
)
|
||||
or (
|
||||
# More literal text matched
|
||||
(num_matched_entities == best_num_matched_entities)
|
||||
and (num_unmatched_entities == best_num_unmatched_entities)
|
||||
and (num_unmatched_ranges == best_num_unmatched_ranges)
|
||||
and (result.text_chunks_matched > maybe_result.text_chunks_matched)
|
||||
)
|
||||
or (
|
||||
# Prefer match failures with entities
|
||||
(result.text_chunks_matched == maybe_result.text_chunks_matched)
|
||||
and (num_unmatched_entities == best_num_unmatched_entities)
|
||||
and (num_unmatched_ranges == best_num_unmatched_ranges)
|
||||
and (
|
||||
("name" in result.entities)
|
||||
or ("name" in result.unmatched_entities)
|
||||
|
@ -550,6 +564,7 @@ class DefaultAgent(ConversationEntity):
|
|||
maybe_result = result
|
||||
best_num_matched_entities = num_matched_entities
|
||||
best_num_unmatched_entities = num_unmatched_entities
|
||||
best_num_unmatched_ranges = num_unmatched_ranges
|
||||
|
||||
return maybe_result
|
||||
|
||||
|
@ -562,76 +577,15 @@ class DefaultAgent(ConversationEntity):
|
|||
language: str,
|
||||
) -> RecognizeResult | None:
|
||||
"""Search intents for a strict match to user input."""
|
||||
custom_found = False
|
||||
name_found = False
|
||||
best_results: list[RecognizeResult] = []
|
||||
best_name_quality: int | None = None
|
||||
best_text_chunks_matched: int | None = None
|
||||
for result in recognize_all(
|
||||
return recognize_best(
|
||||
user_input.text,
|
||||
lang_intents.intents,
|
||||
slot_lists=slot_lists,
|
||||
intent_context=intent_context,
|
||||
language=language,
|
||||
):
|
||||
# Prioritize user intents
|
||||
is_custom = (
|
||||
result.intent_metadata is not None
|
||||
and result.intent_metadata.get(METADATA_CUSTOM_SENTENCE)
|
||||
)
|
||||
|
||||
if custom_found and not is_custom:
|
||||
continue
|
||||
|
||||
if not custom_found and is_custom:
|
||||
custom_found = True
|
||||
# Clear builtin results
|
||||
name_found = False
|
||||
best_results = []
|
||||
best_name_quality = None
|
||||
best_text_chunks_matched = None
|
||||
|
||||
# Prioritize results with a "name" slot
|
||||
name = result.entities.get("name")
|
||||
is_name = name and not name.is_wildcard
|
||||
|
||||
if name_found and not is_name:
|
||||
continue
|
||||
|
||||
if not name_found and is_name:
|
||||
name_found = True
|
||||
# Clear non-name results
|
||||
best_results = []
|
||||
best_text_chunks_matched = None
|
||||
|
||||
if is_name:
|
||||
# Prioritize results with a better "name" slot
|
||||
name_quality = len(cast(MatchEntity, name).value.split())
|
||||
if (best_name_quality is None) or (name_quality > best_name_quality):
|
||||
best_name_quality = name_quality
|
||||
# Clear worse name results
|
||||
best_results = []
|
||||
best_text_chunks_matched = None
|
||||
elif name_quality < best_name_quality:
|
||||
continue
|
||||
|
||||
# Prioritize results with more literal text
|
||||
# This causes wildcards to match last.
|
||||
if (best_text_chunks_matched is None) or (
|
||||
result.text_chunks_matched > best_text_chunks_matched
|
||||
):
|
||||
best_results = [result]
|
||||
best_text_chunks_matched = result.text_chunks_matched
|
||||
elif result.text_chunks_matched == best_text_chunks_matched:
|
||||
# Accumulate results with the same number of literal text matched.
|
||||
# We will resolve the ambiguity below.
|
||||
best_results.append(result)
|
||||
|
||||
if best_results:
|
||||
# Successful strict match
|
||||
return best_results[0]
|
||||
|
||||
return None
|
||||
best_metadata_key=METADATA_CUSTOM_SENTENCE,
|
||||
best_slot_name="name",
|
||||
)
|
||||
|
||||
async def _build_speech(
|
||||
self,
|
||||
|
|
|
@ -6,12 +6,8 @@ from collections.abc import Iterable
|
|||
from typing import Any
|
||||
|
||||
from aiohttp import web
|
||||
from hassil.recognize import (
|
||||
MISSING_ENTITY,
|
||||
RecognizeResult,
|
||||
UnmatchedRangeEntity,
|
||||
UnmatchedTextEntity,
|
||||
)
|
||||
from hassil.recognize import MISSING_ENTITY, RecognizeResult
|
||||
from hassil.string_matcher import UnmatchedRangeEntity, UnmatchedTextEntity
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components import http, websocket_api
|
||||
|
|
|
@ -6,5 +6,5 @@
|
|||
"documentation": "https://www.home-assistant.io/integrations/conversation",
|
||||
"integration_type": "system",
|
||||
"quality_scale": "internal",
|
||||
"requirements": ["hassil==1.7.4", "home-assistant-intents==2024.11.6"]
|
||||
"requirements": ["hassil==2.0.1", "home-assistant-intents==2024.11.13"]
|
||||
}
|
||||
|
|
|
@ -4,7 +4,8 @@ from __future__ import annotations
|
|||
|
||||
from typing import Any
|
||||
|
||||
from hassil.recognize import PUNCTUATION, RecognizeResult
|
||||
from hassil.recognize import RecognizeResult
|
||||
from hassil.util import PUNCTUATION_ALL
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.const import CONF_COMMAND, CONF_PLATFORM
|
||||
|
@ -20,7 +21,7 @@ from .const import DATA_DEFAULT_ENTITY, DOMAIN
|
|||
def has_no_punctuation(value: list[str]) -> list[str]:
|
||||
"""Validate result does not contain punctuation."""
|
||||
for sentence in value:
|
||||
if PUNCTUATION.search(sentence):
|
||||
if PUNCTUATION_ALL.search(sentence):
|
||||
raise vol.Invalid("sentence should not contain punctuation")
|
||||
|
||||
return value
|
||||
|
|
|
@ -32,10 +32,10 @@ go2rtc-client==0.1.1
|
|||
ha-ffmpeg==3.2.2
|
||||
habluetooth==3.6.0
|
||||
hass-nabucasa==0.84.0
|
||||
hassil==1.7.4
|
||||
hassil==2.0.1
|
||||
home-assistant-bluetooth==1.13.0
|
||||
home-assistant-frontend==20241106.2
|
||||
home-assistant-intents==2024.11.6
|
||||
home-assistant-intents==2024.11.13
|
||||
httpx==0.27.2
|
||||
ifaddr==0.2.0
|
||||
Jinja2==3.1.4
|
||||
|
|
|
@ -1093,7 +1093,7 @@ hass-nabucasa==0.84.0
|
|||
hass-splunk==0.1.1
|
||||
|
||||
# homeassistant.components.conversation
|
||||
hassil==1.7.4
|
||||
hassil==2.0.1
|
||||
|
||||
# homeassistant.components.jewish_calendar
|
||||
hdate==0.10.9
|
||||
|
@ -1130,7 +1130,7 @@ holidays==0.60
|
|||
home-assistant-frontend==20241106.2
|
||||
|
||||
# homeassistant.components.conversation
|
||||
home-assistant-intents==2024.11.6
|
||||
home-assistant-intents==2024.11.13
|
||||
|
||||
# homeassistant.components.home_connect
|
||||
homeconnect==0.8.0
|
||||
|
|
|
@ -928,7 +928,7 @@ habluetooth==3.6.0
|
|||
hass-nabucasa==0.84.0
|
||||
|
||||
# homeassistant.components.conversation
|
||||
hassil==1.7.4
|
||||
hassil==2.0.1
|
||||
|
||||
# homeassistant.components.jewish_calendar
|
||||
hdate==0.10.9
|
||||
|
@ -956,7 +956,7 @@ holidays==0.60
|
|||
home-assistant-frontend==20241106.2
|
||||
|
||||
# homeassistant.components.conversation
|
||||
home-assistant-intents==2024.11.6
|
||||
home-assistant-intents==2024.11.13
|
||||
|
||||
# homeassistant.components.home_connect
|
||||
homeconnect==0.8.0
|
||||
|
|
|
@ -23,7 +23,7 @@ RUN --mount=from=ghcr.io/astral-sh/uv:0.5.0,source=/uv,target=/bin/uv \
|
|||
-c /usr/src/homeassistant/homeassistant/package_constraints.txt \
|
||||
-r /usr/src/homeassistant/requirements.txt \
|
||||
stdlib-list==0.10.0 pipdeptree==2.23.4 tqdm==4.66.5 ruff==0.7.3 \
|
||||
PyTurboJPEG==1.7.5 ha-ffmpeg==3.2.2 hassil==1.7.4 home-assistant-intents==2024.11.6 mutagen==1.47.0 pymicro-vad==1.0.1 pyspeex-noise==1.0.2
|
||||
PyTurboJPEG==1.7.5 ha-ffmpeg==3.2.2 hassil==2.0.1 home-assistant-intents==2024.11.13 mutagen==1.47.0 pymicro-vad==1.0.1 pyspeex-noise==1.0.2
|
||||
|
||||
LABEL "name"="hassfest"
|
||||
LABEL "maintainer"="Home Assistant <hello@home-assistant.io>"
|
||||
|
|
|
@ -697,7 +697,7 @@
|
|||
'speech': dict({
|
||||
'plain': dict({
|
||||
'extra_data': None,
|
||||
'speech': 'Sorry, I am not aware of any area called are',
|
||||
'speech': 'Sorry, I am not aware of any area called Are',
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
|
@ -741,7 +741,7 @@
|
|||
'speech': dict({
|
||||
'plain': dict({
|
||||
'extra_data': None,
|
||||
'speech': 'Sorry, I am not aware of any area called are',
|
||||
'speech': 'Sorry, I am not aware of any area called Are',
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
|
|
|
@ -639,7 +639,7 @@
|
|||
'details': dict({
|
||||
'brightness': dict({
|
||||
'name': 'brightness',
|
||||
'text': '100%',
|
||||
'text': '100',
|
||||
'value': 100,
|
||||
}),
|
||||
'name': dict({
|
||||
|
@ -654,7 +654,7 @@
|
|||
'match': True,
|
||||
'sentence_template': '[<numeric_value_set>] <name> brightness [to] <brightness>',
|
||||
'slots': dict({
|
||||
'brightness': '100%',
|
||||
'brightness': '100',
|
||||
'name': 'test light',
|
||||
}),
|
||||
'source': 'builtin',
|
||||
|
|
|
@ -770,8 +770,8 @@ async def test_error_no_device_on_floor_exposed(
|
|||
)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.conversation.default_agent.recognize_all",
|
||||
return_value=[recognize_result],
|
||||
"homeassistant.components.conversation.default_agent.recognize_best",
|
||||
return_value=recognize_result,
|
||||
):
|
||||
result = await conversation.async_converse(
|
||||
hass, "turn on test light on the ground floor", None, Context(), None
|
||||
|
@ -838,8 +838,8 @@ async def test_error_no_domain(hass: HomeAssistant) -> None:
|
|||
)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.conversation.default_agent.recognize_all",
|
||||
return_value=[recognize_result],
|
||||
"homeassistant.components.conversation.default_agent.recognize_best",
|
||||
return_value=recognize_result,
|
||||
):
|
||||
result = await conversation.async_converse(
|
||||
hass, "turn on the fans", None, Context(), None
|
||||
|
@ -873,8 +873,8 @@ async def test_error_no_domain_exposed(hass: HomeAssistant) -> None:
|
|||
)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.conversation.default_agent.recognize_all",
|
||||
return_value=[recognize_result],
|
||||
"homeassistant.components.conversation.default_agent.recognize_best",
|
||||
return_value=recognize_result,
|
||||
):
|
||||
result = await conversation.async_converse(
|
||||
hass, "turn on the fans", None, Context(), None
|
||||
|
@ -1047,8 +1047,8 @@ async def test_error_no_device_class(hass: HomeAssistant) -> None:
|
|||
)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.conversation.default_agent.recognize_all",
|
||||
return_value=[recognize_result],
|
||||
"homeassistant.components.conversation.default_agent.recognize_best",
|
||||
return_value=recognize_result,
|
||||
):
|
||||
result = await conversation.async_converse(
|
||||
hass, "open the windows", None, Context(), None
|
||||
|
@ -1096,8 +1096,8 @@ async def test_error_no_device_class_exposed(hass: HomeAssistant) -> None:
|
|||
)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.conversation.default_agent.recognize_all",
|
||||
return_value=[recognize_result],
|
||||
"homeassistant.components.conversation.default_agent.recognize_best",
|
||||
return_value=recognize_result,
|
||||
):
|
||||
result = await conversation.async_converse(
|
||||
hass, "open all the windows", None, Context(), None
|
||||
|
@ -1207,8 +1207,8 @@ async def test_error_no_device_class_on_floor_exposed(
|
|||
)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.conversation.default_agent.recognize_all",
|
||||
return_value=[recognize_result],
|
||||
"homeassistant.components.conversation.default_agent.recognize_best",
|
||||
return_value=recognize_result,
|
||||
):
|
||||
result = await conversation.async_converse(
|
||||
hass, "open ground floor windows", None, Context(), None
|
||||
|
@ -1229,8 +1229,8 @@ async def test_error_no_device_class_on_floor_exposed(
|
|||
async def test_error_no_intent(hass: HomeAssistant) -> None:
|
||||
"""Test response with an intent match failure."""
|
||||
with patch(
|
||||
"homeassistant.components.conversation.default_agent.recognize_all",
|
||||
return_value=[],
|
||||
"homeassistant.components.conversation.default_agent.recognize_best",
|
||||
return_value=None,
|
||||
):
|
||||
result = await conversation.async_converse(
|
||||
hass, "do something", None, Context(), None
|
||||
|
|
|
@ -56,7 +56,7 @@ async def test_converation_trace(
|
|||
"intent_name": "HassListAddItem",
|
||||
"slots": {
|
||||
"name": "Shopping List",
|
||||
"item": "apples ",
|
||||
"item": "apples",
|
||||
},
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue