Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Intents package combines sentences/responses per language #109079

Merged
merged 2 commits into from Jan 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
52 changes: 26 additions & 26 deletions homeassistant/components/conversation/default_agent.py
Expand Up @@ -6,7 +6,6 @@
from collections.abc import Awaitable, Callable, Iterable
from dataclasses import dataclass
import functools
import itertools
import logging
from pathlib import Path
import re
Expand All @@ -28,7 +27,7 @@
recognize_all,
)
from hassil.util import merge_dict
from home_assistant_intents import get_domains_and_languages, get_intents
from home_assistant_intents import get_intents, get_languages
import yaml

from homeassistant import core, setup
Expand Down Expand Up @@ -156,7 +155,7 @@ def __init__(self, hass: core.HomeAssistant) -> None:
@property
def supported_languages(self) -> list[str]:
"""Return a list of supported languages."""
return get_domains_and_languages()["homeassistant"]
return get_languages()

async def async_initialize(self, config_intents: dict[str, Any] | None) -> None:
"""Initialize the default agent."""
Expand Down Expand Up @@ -387,27 +386,36 @@ def _recognize(
return maybe_result

# Try again with missing entities enabled
best_num_unmatched_entities = 0
for result in recognize_all(
user_input.text,
lang_intents.intents,
slot_lists=slot_lists,
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 result.text_chunks_matched < 1:
# Skip results that don't match any literal text
continue

# Don't count missing entities that couldn't be filled from context
num_unmatched_entities = 0
for entity in result.unmatched_entities_list:
if isinstance(entity, UnmatchedTextEntity):
if entity.text != MISSING_ENTITY:
num_unmatched_entities += 1
else:
num_unmatched_entities += 1

if maybe_result is None:
# First result
maybe_result = result
elif len(result.unmatched_entities) < len(maybe_result.unmatched_entities):
best_num_unmatched_entities = num_unmatched_entities
elif num_unmatched_entities < best_num_unmatched_entities:
# Fewer unmatched entities
maybe_result = result
elif len(result.unmatched_entities) == len(maybe_result.unmatched_entities):
best_num_unmatched_entities = num_unmatched_entities
elif num_unmatched_entities == best_num_unmatched_entities:
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
Expand Down Expand Up @@ -536,14 +544,12 @@ def _get_or_load_intents(
intents_dict = lang_intents.intents_dict
language_variant = lang_intents.language_variant

domains_langs = get_domains_and_languages()
supported_langs = set(get_languages())

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())
}
all_language_variants = {lang.lower(): lang for lang in supported_langs}

# en-US, en_US, en, ...
for maybe_variant in _get_language_variations(language):
Expand All @@ -558,23 +564,17 @@ def _get_or_load_intents(
)
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
# Load intents for this language variant
lang_variant_intents = get_intents(language_variant, json_load=json_load)

if lang_variant_intents:
# Merge sentences into existing dictionary
merge_dict(intents_dict, domain_intents)
merge_dict(intents_dict, lang_variant_intents)

# Will need to recreate graph
intents_changed = True
_LOGGER.debug(
"Loaded intents domain=%s, language=%s (%s)",
domain,
"Loaded intents language=%s (%s)",
language,
language_variant,
)
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/conversation/manifest.json
Expand Up @@ -7,5 +7,5 @@
"integration_type": "system",
"iot_class": "local_push",
"quality_scale": "internal",
"requirements": ["hassil==1.6.0", "home-assistant-intents==2024.1.2"]
"requirements": ["hassil==1.6.0", "home-assistant-intents==2024.1.29"]
}
2 changes: 1 addition & 1 deletion homeassistant/package_constraints.txt
Expand Up @@ -29,7 +29,7 @@ hass-nabucasa==0.75.1
hassil==1.6.0
home-assistant-bluetooth==1.12.0
home-assistant-frontend==20240112.0
home-assistant-intents==2024.1.2
home-assistant-intents==2024.1.29
httpx==0.26.0
ifaddr==0.2.0
janus==1.0.0
Expand Down
2 changes: 1 addition & 1 deletion requirements_all.txt
Expand Up @@ -1056,7 +1056,7 @@ holidays==0.41
home-assistant-frontend==20240112.0

# homeassistant.components.conversation
home-assistant-intents==2024.1.2
home-assistant-intents==2024.1.29

# homeassistant.components.home_connect
homeconnect==0.7.2
Expand Down
2 changes: 1 addition & 1 deletion requirements_test_all.txt
Expand Up @@ -849,7 +849,7 @@ holidays==0.41
home-assistant-frontend==20240112.0

# homeassistant.components.conversation
home-assistant-intents==2024.1.2
home-assistant-intents==2024.1.29

# homeassistant.components.home_connect
homeconnect==0.7.2
Expand Down
40 changes: 20 additions & 20 deletions tests/components/assist_pipeline/snapshots/test_init.ambr
Expand Up @@ -48,14 +48,14 @@
'card': dict({
}),
'data': dict({
'code': 'no_valid_targets',
'code': 'no_intent_match',
}),
'language': 'en',
'response_type': 'error',
'speech': dict({
'plain': dict({
'extra_data': None,
'speech': 'No device or entity named test transcript',
'speech': "Sorry, I couldn't understand that",
}),
}),
}),
Expand All @@ -67,17 +67,17 @@
'data': dict({
'engine': 'test',
'language': 'en-US',
'tts_input': 'No device or entity named test transcript',
'tts_input': "Sorry, I couldn't understand that",
'voice': 'james_earl_jones',
}),
'type': <PipelineEventType.TTS_START: 'tts-start'>,
}),
dict({
'data': dict({
'tts_output': dict({
'media_id': 'media-source://tts/test?message=No+device+or+entity+named+test+transcript&language=en-US&voice=james_earl_jones',
'media_id': "media-source://tts/test?message=Sorry,+I+couldn't+understand+that&language=en-US&voice=james_earl_jones",
'mime_type': 'audio/mpeg',
'url': '/api/tts_proxy/e5e8e318b536f0a5455f993243a34521e7ad4d6d_en-us_031e2ec052_test.mp3',
'url': '/api/tts_proxy/dae2cdcb27a1d1c3b07ba2c7db91480f9d4bfd8f_en-us_031e2ec052_test.mp3',
}),
}),
'type': <PipelineEventType.TTS_END: 'tts-end'>,
Expand Down Expand Up @@ -137,14 +137,14 @@
'card': dict({
}),
'data': dict({
'code': 'no_valid_targets',
'code': 'no_intent_match',
}),
'language': 'en-US',
'response_type': 'error',
'speech': dict({
'plain': dict({
'extra_data': None,
'speech': 'No device or entity named test transcript',
'speech': "Sorry, I couldn't understand that",
}),
}),
}),
Expand All @@ -156,17 +156,17 @@
'data': dict({
'engine': 'test',
'language': 'en-US',
'tts_input': 'No device or entity named test transcript',
'tts_input': "Sorry, I couldn't understand that",
'voice': 'Arnold Schwarzenegger',
}),
'type': <PipelineEventType.TTS_START: 'tts-start'>,
}),
dict({
'data': dict({
'tts_output': dict({
'media_id': 'media-source://tts/test?message=No+device+or+entity+named+test+transcript&language=en-US&voice=Arnold+Schwarzenegger',
'media_id': "media-source://tts/test?message=Sorry,+I+couldn't+understand+that&language=en-US&voice=Arnold+Schwarzenegger",
'mime_type': 'audio/mpeg',
'url': '/api/tts_proxy/e5e8e318b536f0a5455f993243a34521e7ad4d6d_en-us_2657c1a8ee_test.mp3',
'url': '/api/tts_proxy/dae2cdcb27a1d1c3b07ba2c7db91480f9d4bfd8f_en-us_2657c1a8ee_test.mp3',
}),
}),
'type': <PipelineEventType.TTS_END: 'tts-end'>,
Expand Down Expand Up @@ -226,14 +226,14 @@
'card': dict({
}),
'data': dict({
'code': 'no_valid_targets',
'code': 'no_intent_match',
}),
'language': 'en-US',
'response_type': 'error',
'speech': dict({
'plain': dict({
'extra_data': None,
'speech': 'No device or entity named test transcript',
'speech': "Sorry, I couldn't understand that",
}),
}),
}),
Expand All @@ -245,17 +245,17 @@
'data': dict({
'engine': 'test',
'language': 'en-US',
'tts_input': 'No device or entity named test transcript',
'tts_input': "Sorry, I couldn't understand that",
'voice': 'Arnold Schwarzenegger',
}),
'type': <PipelineEventType.TTS_START: 'tts-start'>,
}),
dict({
'data': dict({
'tts_output': dict({
'media_id': 'media-source://tts/test?message=No+device+or+entity+named+test+transcript&language=en-US&voice=Arnold+Schwarzenegger',
'media_id': "media-source://tts/test?message=Sorry,+I+couldn't+understand+that&language=en-US&voice=Arnold+Schwarzenegger",
'mime_type': 'audio/mpeg',
'url': '/api/tts_proxy/e5e8e318b536f0a5455f993243a34521e7ad4d6d_en-us_2657c1a8ee_test.mp3',
'url': '/api/tts_proxy/dae2cdcb27a1d1c3b07ba2c7db91480f9d4bfd8f_en-us_2657c1a8ee_test.mp3',
}),
}),
'type': <PipelineEventType.TTS_END: 'tts-end'>,
Expand Down Expand Up @@ -338,14 +338,14 @@
'card': dict({
}),
'data': dict({
'code': 'no_valid_targets',
'code': 'no_intent_match',
}),
'language': 'en',
'response_type': 'error',
'speech': dict({
'plain': dict({
'extra_data': None,
'speech': 'No device or entity named test transcript',
'speech': "Sorry, I couldn't understand that",
}),
}),
}),
Expand All @@ -357,17 +357,17 @@
'data': dict({
'engine': 'test',
'language': 'en-US',
'tts_input': 'No device or entity named test transcript',
'tts_input': "Sorry, I couldn't understand that",
'voice': 'james_earl_jones',
}),
'type': <PipelineEventType.TTS_START: 'tts-start'>,
}),
dict({
'data': dict({
'tts_output': dict({
'media_id': 'media-source://tts/test?message=No+device+or+entity+named+test+transcript&language=en-US&voice=james_earl_jones',
'media_id': "media-source://tts/test?message=Sorry,+I+couldn't+understand+that&language=en-US&voice=james_earl_jones",
'mime_type': 'audio/mpeg',
'url': '/api/tts_proxy/e5e8e318b536f0a5455f993243a34521e7ad4d6d_en-us_031e2ec052_test.mp3',
'url': '/api/tts_proxy/dae2cdcb27a1d1c3b07ba2c7db91480f9d4bfd8f_en-us_031e2ec052_test.mp3',
}),
}),
'type': <PipelineEventType.TTS_END: 'tts-end'>,
Expand Down