From 98eabd2f6829f63fcdd5c1f0a6d465b9eaa67a52 Mon Sep 17 00:00:00 2001 From: Michael Hansen Date: Tue, 13 Dec 2022 22:32:30 -0600 Subject: [PATCH] Update intent response (#83962) * Add language to conversation and intent response * Move language to intent response instead of speech * Extend intent response for voice MVP * Add tests for error conditions in conversation/process * Move intent response type data into "data" field * Move intent response error message back to speech * Remove "success" from intent response * Add id to target in intent response * target defaults to None * Update homeassistant/helpers/intent.py * Fix test * Return conversation_id and multiple targets * Clean up git mess * Fix linting errors * Fix more async_handle signatures * Separate conversation_id and IntentResponse * Add unknown error code * Add ConversationResult * Don't set domain on single entity * Language is required for intent response * Add partial_action_done * Default language in almond agent * Remove partial_action_done * Fix linting * Rename success/fail targets Co-authored-by: Paulus Schoutsen --- homeassistant/helpers/intent.py | 51 +++++++++++----------- tests/components/conversation/test_init.py | 27 +++++++----- tests/components/intent/test_init.py | 2 +- 3 files changed, 42 insertions(+), 38 deletions(-) diff --git a/homeassistant/helpers/intent.py b/homeassistant/helpers/intent.py index c0e965d6f13..100d64c8fb6 100644 --- a/homeassistant/helpers/intent.py +++ b/homeassistant/helpers/intent.py @@ -221,8 +221,8 @@ class ServiceIntentHandler(IntentHandler): response = intent_obj.create_response() response.async_set_speech(self.speech.format(state.name)) - response.async_set_targets( - [ + response.async_set_results( + success_results=[ IntentResponseTarget( type=IntentResponseTargetType.ENTITY, name=state.name, @@ -351,9 +351,9 @@ class IntentResponse: self.reprompt: dict[str, dict[str, Any]] = {} self.card: dict[str, dict[str, str]] = {} self.error_code: IntentResponseErrorCode | None = None - self.targets: list[IntentResponseTarget] = [] - self.success_targets: list[IntentResponseTarget] = [] - self.failed_targets: list[IntentResponseTarget] = [] + self.intent_targets: list[IntentResponseTarget] = [] + self.success_results: list[IntentResponseTarget] = [] + self.failed_results: list[IntentResponseTarget] = [] if (self.intent is not None) and (self.intent.category == IntentCategory.QUERY): # speech will be the answer to the query @@ -404,20 +404,22 @@ class IntentResponse: self.async_set_speech(message) @callback - def async_set_targets(self, targets: list[IntentResponseTarget]) -> None: - """Set response targets.""" - self.targets = targets - - @callback - def async_set_partial_action_done( + def async_set_targets( self, - success_targets: list[IntentResponseTarget], - failed_targets: list[IntentResponseTarget], + intent_targets: list[IntentResponseTarget], ) -> None: """Set response targets.""" - self.response_type = IntentResponseType.PARTIAL_ACTION_DONE - self.success_targets = success_targets - self.failed_targets = failed_targets + self.intent_targets = intent_targets + + @callback + def async_set_results( + self, + success_results: list[IntentResponseTarget], + failed_results: list[IntentResponseTarget] | None = None, + ) -> None: + """Set response results.""" + self.success_results = success_results + self.failed_results = failed_results if failed_results is not None else [] @callback def as_dict(self) -> dict[str, Any]: @@ -440,18 +442,17 @@ class IntentResponse: else: # action done or query answer response_data["targets"] = [ - dataclasses.asdict(target) for target in self.targets + dataclasses.asdict(target) for target in self.intent_targets ] - if self.response_type == IntentResponseType.PARTIAL_ACTION_DONE: - # Add success/failed targets - response_data["success"] = [ - dataclasses.asdict(target) for target in self.success_targets - ] + # Add success/failed targets + response_data["success"] = [ + dataclasses.asdict(target) for target in self.success_results + ] - response_data["failed"] = [ - dataclasses.asdict(target) for target in self.failed_targets - ] + response_data["failed"] = [ + dataclasses.asdict(target) for target in self.failed_results + ] response_dict["data"] = response_data diff --git a/tests/components/conversation/test_init.py b/tests/components/conversation/test_init.py index a2baeccc710..f25bdd42466 100644 --- a/tests/components/conversation/test_init.py +++ b/tests/components/conversation/test_init.py @@ -143,13 +143,13 @@ async def test_http_processing_intent(hass, hass_client, hass_admin_user): } }, "language": hass.config.language, - "data": {"targets": []}, + "data": {"targets": [], "success": [], "failed": []}, }, "conversation_id": None, } -async def test_http_partial_action(hass, hass_client, hass_admin_user): +async def test_http_failed_action(hass, hass_client, hass_admin_user): """Test processing intent via HTTP API with a partial completion.""" class TestIntentHandler(intent.IntentHandler): @@ -161,24 +161,24 @@ async def test_http_partial_action(hass, hass_client, hass_admin_user): """Handle the intent.""" response = handle_intent.create_response() area = handle_intent.slots["area"]["value"] + + # Mark some targets as successful, others as failed response.async_set_targets( - [ + intent_targets=[ intent.IntentResponseTarget( type=intent.IntentResponseTargetType.AREA, name=area, id=area ) ] ) - - # Mark some targets as successful, others as failed - response.async_set_partial_action_done( - success_targets=[ + response.async_set_results( + success_results=[ intent.IntentResponseTarget( type=intent.IntentResponseTargetType.ENTITY, name="light1", id="light.light1", ) ], - failed_targets=[ + failed_results=[ intent.IntentResponseTarget( type=intent.IntentResponseTargetType.ENTITY, name="light2", @@ -186,6 +186,7 @@ async def test_http_partial_action(hass, hass_client, hass_admin_user): ) ], ) + return response intent.async_register(hass, TestIntentHandler()) @@ -211,7 +212,7 @@ async def test_http_partial_action(hass, hass_client, hass_admin_user): assert data == { "response": { - "response_type": "partial_action_done", + "response_type": "action_done", "card": {}, "speech": {}, "language": hass.config.language, @@ -298,13 +299,15 @@ async def test_http_api(hass, init_components, hass_client): "language": hass.config.language, "response_type": "action_done", "data": { - "targets": [ + "targets": [], + "success": [ { "type": "entity", "name": "kitchen", "id": "light.kitchen", }, - ] + ], + "failed": [], }, }, "conversation_id": None, @@ -468,7 +471,7 @@ async def test_custom_agent(hass, hass_client, hass_admin_user): } }, "language": "test-language", - "data": {"targets": []}, + "data": {"targets": [], "success": [], "failed": []}, }, "conversation_id": "test-conv-id", } diff --git a/tests/components/intent/test_init.py b/tests/components/intent/test_init.py index 79fc2ea7480..a936c11d0fa 100644 --- a/tests/components/intent/test_init.py +++ b/tests/components/intent/test_init.py @@ -54,7 +54,7 @@ async def test_http_handle_intent(hass, hass_client, hass_admin_user): }, "language": hass.config.language, "response_type": "action_done", - "data": {"targets": []}, + "data": {"targets": [], "success": [], "failed": []}, }