Prioritize custom intents over builtin (#120358)
parent
6689dbbcc6
commit
46dcf1dc44
|
@ -419,6 +419,7 @@ class DefaultAgent(ConversationEntity):
|
|||
language: str,
|
||||
) -> RecognizeResult | None:
|
||||
"""Search intents for a match to user input."""
|
||||
custom_result: RecognizeResult | None = None
|
||||
name_result: RecognizeResult | None = None
|
||||
best_results: list[RecognizeResult] = []
|
||||
best_text_chunks_matched: int | None = None
|
||||
|
@ -429,6 +430,20 @@ class DefaultAgent(ConversationEntity):
|
|||
intent_context=intent_context,
|
||||
language=language,
|
||||
):
|
||||
# User intents have highest priority
|
||||
if (result.intent_metadata is not None) and result.intent_metadata.get(
|
||||
METADATA_CUSTOM_SENTENCE
|
||||
):
|
||||
if (custom_result is None) or (
|
||||
result.text_chunks_matched > custom_result.text_chunks_matched
|
||||
):
|
||||
custom_result = result
|
||||
|
||||
# Clear builtin results
|
||||
best_results = []
|
||||
name_result = None
|
||||
continue
|
||||
|
||||
# Prioritize results with a "name" slot, but still prefer ones with
|
||||
# more literal text matched.
|
||||
if (
|
||||
|
@ -453,6 +468,10 @@ class DefaultAgent(ConversationEntity):
|
|||
# We will resolve the ambiguity below.
|
||||
best_results.append(result)
|
||||
|
||||
if custom_result is not None:
|
||||
# Prioritize user intents
|
||||
return custom_result
|
||||
|
||||
if name_result is not None:
|
||||
# Prioritize matches with entity names above area names
|
||||
return name_result
|
||||
|
@ -718,11 +737,22 @@ class DefaultAgent(ConversationEntity):
|
|||
if self._config_intents and (
|
||||
self.hass.config.language in (language, language_variant)
|
||||
):
|
||||
hass_config_path = self.hass.config.path()
|
||||
merge_dict(
|
||||
intents_dict,
|
||||
{
|
||||
"intents": {
|
||||
intent_name: {"data": [{"sentences": sentences}]}
|
||||
intent_name: {
|
||||
"data": [
|
||||
{
|
||||
"sentences": sentences,
|
||||
"metadata": {
|
||||
METADATA_CUSTOM_SENTENCE: True,
|
||||
METADATA_CUSTOM_FILE: hass_config_path,
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
||||
for intent_name, sentences in self._config_intents.items()
|
||||
}
|
||||
},
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
"""The tests for the Conversation component."""
|
||||
|
||||
from http import HTTPStatus
|
||||
import os
|
||||
import tempfile
|
||||
from typing import Any
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
import voluptuous as vol
|
||||
import yaml
|
||||
|
||||
from homeassistant.components import conversation
|
||||
from homeassistant.components.conversation import default_agent
|
||||
|
@ -1389,3 +1392,103 @@ async def test_ws_hass_agent_debug_sentence_trigger(
|
|||
|
||||
# Trigger should not have been executed
|
||||
assert len(calls) == 0
|
||||
|
||||
|
||||
async def test_custom_sentences_priority(
|
||||
hass: HomeAssistant,
|
||||
hass_client: ClientSessionGenerator,
|
||||
hass_admin_user: MockUser,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Test that user intents from custom_sentences have priority over builtin intents/sentences."""
|
||||
with tempfile.NamedTemporaryFile(
|
||||
mode="w+",
|
||||
encoding="utf-8",
|
||||
suffix=".yaml",
|
||||
dir=os.path.join(hass.config.config_dir, "custom_sentences", "en"),
|
||||
) as custom_sentences_file:
|
||||
# Add a custom sentence that would match a builtin sentence.
|
||||
# Custom sentences have priority.
|
||||
yaml.dump(
|
||||
{
|
||||
"language": "en",
|
||||
"intents": {
|
||||
"CustomIntent": {"data": [{"sentences": ["turn on the lamp"]}]}
|
||||
},
|
||||
},
|
||||
custom_sentences_file,
|
||||
)
|
||||
custom_sentences_file.flush()
|
||||
custom_sentences_file.seek(0)
|
||||
|
||||
assert await async_setup_component(hass, "homeassistant", {})
|
||||
assert await async_setup_component(hass, "conversation", {})
|
||||
assert await async_setup_component(hass, "light", {})
|
||||
assert await async_setup_component(hass, "intent", {})
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
"intent_script",
|
||||
{
|
||||
"intent_script": {
|
||||
"CustomIntent": {"speech": {"text": "custom response"}}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
# Ensure that a "lamp" exists so that we can verify the custom intent
|
||||
# overrides the builtin sentence.
|
||||
hass.states.async_set("light.lamp", "off")
|
||||
|
||||
client = await hass_client()
|
||||
resp = await client.post(
|
||||
"/api/conversation/process",
|
||||
json={
|
||||
"text": "turn on the lamp",
|
||||
"language": hass.config.language,
|
||||
},
|
||||
)
|
||||
assert resp.status == HTTPStatus.OK
|
||||
data = await resp.json()
|
||||
assert data["response"]["response_type"] == "action_done"
|
||||
assert data["response"]["speech"]["plain"]["speech"] == "custom response"
|
||||
|
||||
|
||||
async def test_config_sentences_priority(
|
||||
hass: HomeAssistant,
|
||||
hass_client: ClientSessionGenerator,
|
||||
hass_admin_user: MockUser,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Test that user intents from configuration.yaml have priority over builtin intents/sentences."""
|
||||
# Add a custom sentence that would match a builtin sentence.
|
||||
# Custom sentences have priority.
|
||||
assert await async_setup_component(hass, "homeassistant", {})
|
||||
assert await async_setup_component(hass, "intent", {})
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
"conversation",
|
||||
{"conversation": {"intents": {"CustomIntent": ["turn on the lamp"]}}},
|
||||
)
|
||||
assert await async_setup_component(hass, "light", {})
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
"intent_script",
|
||||
{"intent_script": {"CustomIntent": {"speech": {"text": "custom response"}}}},
|
||||
)
|
||||
|
||||
# Ensure that a "lamp" exists so that we can verify the custom intent
|
||||
# overrides the builtin sentence.
|
||||
hass.states.async_set("light.lamp", "off")
|
||||
|
||||
client = await hass_client()
|
||||
resp = await client.post(
|
||||
"/api/conversation/process",
|
||||
json={
|
||||
"text": "turn on the lamp",
|
||||
"language": hass.config.language,
|
||||
},
|
||||
)
|
||||
assert resp.status == HTTPStatus.OK
|
||||
data = await resp.json()
|
||||
assert data["response"]["response_type"] == "action_done"
|
||||
assert data["response"]["speech"]["plain"]["speech"] == "custom response"
|
||||
|
|
Loading…
Reference in New Issue