Skip to content

Commit

Permalink
Alexa Intent: Use the 'id' field and expose nearest resolutions as va…
Browse files Browse the repository at this point in the history
…riables (#86709)

* Use the 'id' field and nearest resolutions
Expose nearest Resolution (ID and Value) as Variables

* Add more specific type hints

* Change type definition of request

* Add deprecation warning and remove variables

* Remove deprecation warning & update tests

* Fix wrong value assignment

* revert future changes
  • Loading branch information
AzonInc committed May 10, 2023
1 parent 97cac66 commit 0f2caf8
Show file tree
Hide file tree
Showing 2 changed files with 134 additions and 9 deletions.
30 changes: 21 additions & 9 deletions homeassistant/components/alexa/intent.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Support for Alexa skill service end point."""
import enum
import logging
from typing import Any

from homeassistant.components import http
from homeassistant.core import callback
Expand Down Expand Up @@ -180,12 +181,15 @@ async def async_handle_intent(hass, message):
return alexa_response.as_dict()


def resolve_slot_synonyms(key, request):
def resolve_slot_data(key: str, request: dict[str, Any]) -> dict[str, str]:
"""Check slot request for synonym resolutions."""
# Default to the spoken slot value if more than one or none are found. For
# Default to the spoken slot value if more than one or none are found. Always
# passes the id and name of the nearest possible slot resolution. For
# reference to the request object structure, see the Alexa docs:
# https://tinyurl.com/ybvm7jhs
resolved_value = request["value"]
resolved_data = {}
resolved_data["value"] = request["value"]
resolved_data["id"] = ""

if (
"resolutions" in request
Expand All @@ -200,20 +204,26 @@ def resolve_slot_synonyms(key, request):
if entry["status"]["code"] != SYN_RESOLUTION_MATCH:
continue

possible_values.extend([item["value"]["name"] for item in entry["values"]])
possible_values.extend([item["value"] for item in entry["values"]])

# Always set id if available, otherwise an empty string is used as id
if len(possible_values) >= 1:
# Set ID if available
if "id" in possible_values[0]:
resolved_data["id"] = possible_values[0]["id"]

# If there is only one match use the resolved value, otherwise the
# resolution cannot be determined, so use the spoken slot value
# resolution cannot be determined, so use the spoken slot value and empty string as id
if len(possible_values) == 1:
resolved_value = possible_values[0]
resolved_data["value"] = possible_values[0]["name"]
else:
_LOGGER.debug(
"Found multiple synonym resolutions for slot value: {%s: %s}",
key,
resolved_value,
resolved_data["value"],
)

return resolved_value
return resolved_data


class AlexaResponse:
Expand All @@ -237,8 +247,10 @@ def __init__(self, hass, intent_info):
continue

_key = key.replace(".", "_")
_slot_data = resolve_slot_data(key, value)

self.variables[_key] = resolve_slot_synonyms(key, value)
self.variables[_key] = _slot_data["value"]
self.variables[_key + "_Id"] = _slot_data["id"]

def add_card(self, card_type, title, content):
"""Add a card to the response."""
Expand Down
113 changes: 113 additions & 0 deletions tests/components/alexa/test_intent.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,12 @@ def mock_service(call):
"text": "You told us your sign is {{ ZodiacSign }}.",
}
},
"GetZodiacHoroscopeIDIntent": {
"speech": {
"type": "plain",
"text": "You told us your sign is {{ ZodiacSign_Id }}.",
}
},
"AMAZON.PlaybackAction<object@MusicCreativeWork>": {
"speech": {
"type": "plain",
Expand Down Expand Up @@ -299,6 +305,113 @@ async def test_intent_request_with_slots_and_synonym_resolution(alexa_client) ->
assert text == "You told us your sign is Virgo."


async def test_intent_request_with_slots_and_synonym_id_resolution(
alexa_client,
) -> None:
"""Test a request with slots, id and a name synonym."""
data = {
"version": "1.0",
"session": {
"new": False,
"sessionId": SESSION_ID,
"application": {"applicationId": APPLICATION_ID},
"attributes": {
"supportedHoroscopePeriods": {
"daily": True,
"weekly": False,
"monthly": False,
}
},
"user": {"userId": "amzn1.account.AM3B00000000000000000000000"},
},
"request": {
"type": "IntentRequest",
"requestId": REQUEST_ID,
"timestamp": "2015-05-13T12:34:56Z",
"intent": {
"name": "GetZodiacHoroscopeIDIntent",
"slots": {
"ZodiacSign": {
"name": "ZodiacSign",
"value": "V zodiac",
"resolutions": {
"resolutionsPerAuthority": [
{
"authority": AUTHORITY_ID,
"status": {"code": "ER_SUCCESS_MATCH"},
"values": [{"value": {"name": "Virgo", "id": "1"}}],
}
]
},
}
},
},
},
}
req = await _intent_req(alexa_client, data)
assert req.status == HTTPStatus.OK
data = await req.json()
text = data.get("response", {}).get("outputSpeech", {}).get("text")
assert text == "You told us your sign is 1."


async def test_intent_request_with_slots_and_multi_synonym_id_resolution(
alexa_client,
) -> None:
"""Test a request with slots and multiple name synonyms (id)."""
data = {
"version": "1.0",
"session": {
"new": False,
"sessionId": SESSION_ID,
"application": {"applicationId": APPLICATION_ID},
"attributes": {
"supportedHoroscopePeriods": {
"daily": True,
"weekly": False,
"monthly": False,
}
},
"user": {"userId": "amzn1.account.AM3B00000000000000000000000"},
},
"request": {
"type": "IntentRequest",
"requestId": REQUEST_ID,
"timestamp": "2015-05-13T12:34:56Z",
"intent": {
"name": "GetZodiacHoroscopeIDIntent",
"slots": {
"ZodiacSign": {
"name": "ZodiacSign",
"value": "Virgio Test",
"resolutions": {
"resolutionsPerAuthority": [
{
"authority": AUTHORITY_ID,
"status": {"code": "ER_SUCCESS_MATCH"},
"values": [
{"value": {"name": "Virgio Test", "id": "2"}}
],
},
{
"authority": AUTHORITY_ID,
"status": {"code": "ER_SUCCESS_MATCH"},
"values": [{"value": {"name": "Virgo", "id": "1"}}],
},
]
},
}
},
},
},
}
req = await _intent_req(alexa_client, data)
assert req.status == HTTPStatus.OK
data = await req.json()
text = data.get("response", {}).get("outputSpeech", {}).get("text")
assert text == "You told us your sign is 2."


async def test_intent_request_with_slots_and_multi_synonym_resolution(
alexa_client,
) -> None:
Expand Down

0 comments on commit 0f2caf8

Please sign in to comment.