Fix intent loading and incorporate unmatched entities more (#108423)
* Incorporate unmatched entities more * Don't list targets when match is incomplete * Add test for out of range
This commit is contained in:
parent
c725238c20
commit
d8a1c58b12
9 changed files with 382 additions and 141 deletions
|
@ -6,6 +6,7 @@ from collections import defaultdict
|
|||
from collections.abc import Awaitable, Callable, Iterable
|
||||
from dataclasses import dataclass
|
||||
import functools
|
||||
import itertools
|
||||
import logging
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
@ -20,6 +21,7 @@ from hassil.intents import (
|
|||
WildcardSlotList,
|
||||
)
|
||||
from hassil.recognize import (
|
||||
MISSING_ENTITY,
|
||||
RecognizeResult,
|
||||
UnmatchedEntity,
|
||||
UnmatchedTextEntity,
|
||||
|
@ -75,7 +77,7 @@ class LanguageIntents:
|
|||
intents_dict: dict[str, Any]
|
||||
intent_responses: dict[str, Any]
|
||||
error_responses: dict[str, Any]
|
||||
loaded_components: set[str]
|
||||
language_variant: str | None
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
|
@ -181,9 +183,7 @@ class DefaultAgent(AbstractConversationAgent):
|
|||
lang_intents = self._lang_intents.get(language)
|
||||
|
||||
# Reload intents if missing or new components
|
||||
if lang_intents is None or (
|
||||
lang_intents.loaded_components - self.hass.config.components
|
||||
):
|
||||
if lang_intents is None:
|
||||
# Load intents in executor
|
||||
lang_intents = await self.async_get_or_load_intents(language)
|
||||
|
||||
|
@ -357,6 +357,13 @@ class DefaultAgent(AbstractConversationAgent):
|
|||
intent_context=intent_context,
|
||||
allow_unmatched_entities=True,
|
||||
):
|
||||
# Remove missing entities that couldn't be filled from context
|
||||
for entity_key, entity in list(result.unmatched_entities.items()):
|
||||
if isinstance(entity, UnmatchedTextEntity) and (
|
||||
entity.text == MISSING_ENTITY
|
||||
):
|
||||
result.unmatched_entities.pop(entity_key)
|
||||
|
||||
if maybe_result is None:
|
||||
# First result
|
||||
maybe_result = result
|
||||
|
@ -364,8 +371,11 @@ class DefaultAgent(AbstractConversationAgent):
|
|||
# Fewer unmatched entities
|
||||
maybe_result = result
|
||||
elif len(result.unmatched_entities) == len(maybe_result.unmatched_entities):
|
||||
if result.text_chunks_matched > maybe_result.text_chunks_matched:
|
||||
# More literal text chunks matched
|
||||
if (result.text_chunks_matched > maybe_result.text_chunks_matched) or (
|
||||
(result.text_chunks_matched == maybe_result.text_chunks_matched)
|
||||
and ("name" in result.unmatched_entities) # prefer entities
|
||||
):
|
||||
# More literal text chunks matched, but prefer entities to areas, etc.
|
||||
maybe_result = result
|
||||
|
||||
if (maybe_result is not None) and maybe_result.unmatched_entities:
|
||||
|
@ -484,84 +494,93 @@ class DefaultAgent(AbstractConversationAgent):
|
|||
|
||||
if lang_intents is None:
|
||||
intents_dict: dict[str, Any] = {}
|
||||
loaded_components: set[str] = set()
|
||||
language_variant: str | None = None
|
||||
else:
|
||||
intents_dict = lang_intents.intents_dict
|
||||
loaded_components = lang_intents.loaded_components
|
||||
language_variant = lang_intents.language_variant
|
||||
|
||||
# en-US, en_US, en, ...
|
||||
language_variations = list(_get_language_variations(language))
|
||||
domains_langs = get_domains_and_languages()
|
||||
|
||||
# Check if any new components have been loaded
|
||||
intents_changed = False
|
||||
for component in hass_components:
|
||||
if component in loaded_components:
|
||||
continue
|
||||
if not language_variant:
|
||||
# Choose a language variant upfront and commit to it for custom
|
||||
# sentences, etc.
|
||||
all_language_variants = {
|
||||
lang.lower(): lang for lang in itertools.chain(*domains_langs.values())
|
||||
}
|
||||
|
||||
# Don't check component again
|
||||
loaded_components.add(component)
|
||||
|
||||
# Check for intents for this component with the target language.
|
||||
# Try en-US, en, etc.
|
||||
for language_variation in language_variations:
|
||||
component_intents = get_intents(
|
||||
component, language_variation, json_load=json_load
|
||||
)
|
||||
if component_intents:
|
||||
# Merge sentences into existing dictionary
|
||||
merge_dict(intents_dict, component_intents)
|
||||
|
||||
# Will need to recreate graph
|
||||
intents_changed = True
|
||||
_LOGGER.debug(
|
||||
"Loaded intents component=%s, language=%s (%s)",
|
||||
component,
|
||||
language,
|
||||
language_variation,
|
||||
)
|
||||
# en-US, en_US, en, ...
|
||||
for maybe_variant in _get_language_variations(language):
|
||||
matching_variant = all_language_variants.get(maybe_variant.lower())
|
||||
if matching_variant:
|
||||
language_variant = matching_variant
|
||||
break
|
||||
|
||||
if not language_variant:
|
||||
_LOGGER.warning(
|
||||
"Unable to find supported language variant for %s", language
|
||||
)
|
||||
return None
|
||||
|
||||
# Load intents for all domains supported by this language variant
|
||||
for domain in domains_langs:
|
||||
domain_intents = get_intents(
|
||||
domain, language_variant, json_load=json_load
|
||||
)
|
||||
|
||||
if not domain_intents:
|
||||
continue
|
||||
|
||||
# Merge sentences into existing dictionary
|
||||
merge_dict(intents_dict, domain_intents)
|
||||
|
||||
# Will need to recreate graph
|
||||
intents_changed = True
|
||||
_LOGGER.debug(
|
||||
"Loaded intents domain=%s, language=%s (%s)",
|
||||
domain,
|
||||
language,
|
||||
language_variant,
|
||||
)
|
||||
|
||||
# Check for custom sentences in <config>/custom_sentences/<language>/
|
||||
if lang_intents is None:
|
||||
# Only load custom sentences once, otherwise they will be re-loaded
|
||||
# when components change.
|
||||
for language_variation in language_variations:
|
||||
custom_sentences_dir = Path(
|
||||
self.hass.config.path("custom_sentences", language_variation)
|
||||
)
|
||||
if custom_sentences_dir.is_dir():
|
||||
for custom_sentences_path in custom_sentences_dir.rglob("*.yaml"):
|
||||
with custom_sentences_path.open(
|
||||
encoding="utf-8"
|
||||
) as custom_sentences_file:
|
||||
# Merge custom sentences
|
||||
if isinstance(
|
||||
custom_sentences_yaml := yaml.safe_load(
|
||||
custom_sentences_file
|
||||
),
|
||||
dict,
|
||||
):
|
||||
merge_dict(intents_dict, custom_sentences_yaml)
|
||||
else:
|
||||
_LOGGER.warning(
|
||||
"Custom sentences file does not match expected format path=%s",
|
||||
custom_sentences_file.name,
|
||||
)
|
||||
custom_sentences_dir = Path(
|
||||
self.hass.config.path("custom_sentences", language_variant)
|
||||
)
|
||||
if custom_sentences_dir.is_dir():
|
||||
for custom_sentences_path in custom_sentences_dir.rglob("*.yaml"):
|
||||
with custom_sentences_path.open(
|
||||
encoding="utf-8"
|
||||
) as custom_sentences_file:
|
||||
# Merge custom sentences
|
||||
if isinstance(
|
||||
custom_sentences_yaml := yaml.safe_load(
|
||||
custom_sentences_file
|
||||
),
|
||||
dict,
|
||||
):
|
||||
merge_dict(intents_dict, custom_sentences_yaml)
|
||||
else:
|
||||
_LOGGER.warning(
|
||||
"Custom sentences file does not match expected format path=%s",
|
||||
custom_sentences_file.name,
|
||||
)
|
||||
|
||||
# Will need to recreate graph
|
||||
intents_changed = True
|
||||
_LOGGER.debug(
|
||||
"Loaded custom sentences language=%s (%s), path=%s",
|
||||
language,
|
||||
language_variation,
|
||||
custom_sentences_path,
|
||||
)
|
||||
|
||||
# Stop after first matched language variation
|
||||
break
|
||||
# Will need to recreate graph
|
||||
intents_changed = True
|
||||
_LOGGER.debug(
|
||||
"Loaded custom sentences language=%s (%s), path=%s",
|
||||
language,
|
||||
language_variant,
|
||||
custom_sentences_path,
|
||||
)
|
||||
|
||||
# Load sentences from HA config for default language only
|
||||
if self._config_intents and (language == self.hass.config.language):
|
||||
if self._config_intents and (
|
||||
self.hass.config.language in (language, language_variant)
|
||||
):
|
||||
merge_dict(
|
||||
intents_dict,
|
||||
{
|
||||
|
@ -598,7 +617,7 @@ class DefaultAgent(AbstractConversationAgent):
|
|||
intents_dict,
|
||||
intent_responses,
|
||||
error_responses,
|
||||
loaded_components,
|
||||
language_variant,
|
||||
)
|
||||
self._lang_intents[language] = lang_intents
|
||||
else:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue