mirror of
https://github.com/home-assistant/core.git
synced 2025-07-24 21:57:51 +00:00
Alexa Intent: Use the 'id' field and expose nearest resolutions as variables (#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
This commit is contained in:
parent
97cac66195
commit
0f2caf864a
@ -1,6 +1,7 @@
|
|||||||
"""Support for Alexa skill service end point."""
|
"""Support for Alexa skill service end point."""
|
||||||
import enum
|
import enum
|
||||||
import logging
|
import logging
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from homeassistant.components import http
|
from homeassistant.components import http
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
@ -180,12 +181,15 @@ async def async_handle_intent(hass, message):
|
|||||||
return alexa_response.as_dict()
|
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."""
|
"""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:
|
# reference to the request object structure, see the Alexa docs:
|
||||||
# https://tinyurl.com/ybvm7jhs
|
# https://tinyurl.com/ybvm7jhs
|
||||||
resolved_value = request["value"]
|
resolved_data = {}
|
||||||
|
resolved_data["value"] = request["value"]
|
||||||
|
resolved_data["id"] = ""
|
||||||
|
|
||||||
if (
|
if (
|
||||||
"resolutions" in request
|
"resolutions" in request
|
||||||
@ -200,20 +204,26 @@ def resolve_slot_synonyms(key, request):
|
|||||||
if entry["status"]["code"] != SYN_RESOLUTION_MATCH:
|
if entry["status"]["code"] != SYN_RESOLUTION_MATCH:
|
||||||
continue
|
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
|
# 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:
|
if len(possible_values) == 1:
|
||||||
resolved_value = possible_values[0]
|
resolved_data["value"] = possible_values[0]["name"]
|
||||||
else:
|
else:
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"Found multiple synonym resolutions for slot value: {%s: %s}",
|
"Found multiple synonym resolutions for slot value: {%s: %s}",
|
||||||
key,
|
key,
|
||||||
resolved_value,
|
resolved_data["value"],
|
||||||
)
|
)
|
||||||
|
|
||||||
return resolved_value
|
return resolved_data
|
||||||
|
|
||||||
|
|
||||||
class AlexaResponse:
|
class AlexaResponse:
|
||||||
@ -237,8 +247,10 @@ class AlexaResponse:
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
_key = key.replace(".", "_")
|
_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):
|
def add_card(self, card_type, title, content):
|
||||||
"""Add a card to the response."""
|
"""Add a card to the response."""
|
||||||
|
@ -77,6 +77,12 @@ def alexa_client(event_loop, hass, hass_client):
|
|||||||
"text": "You told us your sign is {{ ZodiacSign }}.",
|
"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>": {
|
"AMAZON.PlaybackAction<object@MusicCreativeWork>": {
|
||||||
"speech": {
|
"speech": {
|
||||||
"type": "plain",
|
"type": "plain",
|
||||||
@ -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."
|
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(
|
async def test_intent_request_with_slots_and_multi_synonym_resolution(
|
||||||
alexa_client,
|
alexa_client,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user