スキルコードにAPLサポートを追加する
ASK SDK v2は、Alexa Presentation Language(APL)のディレクティブとリクエストをサポートしています。ASK SDKはNode、Java、Pythonで使用できます。このトピックでは、ASK SDK v2でスキルをビルドする際にAPLのディレクティブとリクエストを使用する方法を概説し、いくつかサンプルを示します。
- SDKとAPLで開発を始める
- APLインターフェースに対応するようスキルを設定する
- 表示するAPLドキュメントを作成する
- ユーザーのデバイスがAPLに対応していることを確認する
- リクエストハンドラーの応答でRenderDocumentを返す
- UserEventリクエストを処理する
- リクエストハンドラーの応答でExecuteCommandsを返す
- 関連トピック
SDKとAPLで開発を始める
このトピックを理解するには以下が必要です。
- カスタムスキルの構成を理解している。
- コードでのリクエストの処理方法を理解している。
- Node、Java、PythonのいずれかのASK SDKに通じている。
- スキルに視覚要素とオーディオを追加するを読む。
- 次のASK SDKの最新バージョンを使用してスキルを開発している。
後出のサンプルコードは、Node、Java、Pythonの「ハローワールド」サンプルを基にしています。以下のセクションでは、このサンプルを修正し、元の「ハローワールド」の応答にAPLを追加する方法を示します。
APLインターフェースに対応するようスキルを設定する
コードにAPLディレクティブを追加する前に、Alexa.Presentation.APL
インターフェースに対応するようスキルを設定する必要があります。
Alexa.Presentation.APL
インターフェースが有効になっていない場合、送信したAPLディレクティブはすべてエラーになります。
表示するAPLドキュメントを作成する
画面にコンテンツを表示するには、APLドキュメントをJSON形式で作成し、コードで使用できるようにする必要があります。
APLドキュメントをJSONファイルとして保存する
APLドキュメント構造に従うJSONファイルとしてドキュメントを作成します。たとえば、以下をhelloworldDocument.json
という名前のファイルに保存します。
{
"type": "APL",
"version": "2022.1",
"description": "ハローワールドAPLドキュメント。",
"theme": "dark",
"mainTemplate": {
"parameters": [
"payload"
],
"items": [
{
"type": "Text",
"height": "100%",
"textAlign": "center",
"textAlignVertical": "center",
"text": "ハローワールド"
}
]
}
}
これはとてもシンプルなドキュメントで、単一のText
コンポーネントを画面上に配置します。実際に表示されるテキストは、text
プロパティで定義されている 「ハローワールド」です。このテキストは、textAlign
プロパティとtextAlignVertical
プロパティによりviewportの中央に配置されます。
コードがアクセスできる場所にドキュメントを配置する
スキルのインテントハンドラーが、ドキュメントを含むJSONファイルをRenderDocument
ディレクティブの一部として送信できる必要があります。そのため、このファイルはアクセスできる場所に配置してください。
ドキュメントのJSONファイルをindex.js
ファイルと同じフォルダに保存します。たとえば、Node.jsの「ハローワールド」スキルのディレクトリ構造では、helloworldDocument.json
ドキュメントがindex.js
と同じくlambda
フォルダに配置されています。
| skill.json
|
+---.ask
| config
|
+---hooks
| pre_deploy_hook.ps1
|
+---lambda
| helloworldDocument.json
| index.js
| package.json
| util.js
|
\---models
ja-JP.json
ドキュメントファイルがこの場所にあることで、ハンドラーはrequire()
関数を使用してファイルを読み込むことができます。読み込みについては後述します。
ドキュメントのJSONファイルをlambda_function.py
と同じレベルに保存します。たとえば、Pythonの「ハローワールド」スキルのディレクトリ構造では、helloworldDocument.jsonドキュメントがlambda_function.py
と同じくlambda/py
フォルダに配置されています。
| skill.json
|
+---.ask
| config
|
+---hooks
| pre_deploy_hook.ps1
|
+---lambda
| +---py
| | | lambda_function.py
| | | requirements.txt
| | | helloworldDocument.json
\---models
ja-JP.json
このように配置することで、ハンドラーはPythonのJSONクラスのloadメソッドと、ファイルI/Oのopenメソッドを使用して、ドキュメントファイルを読み込むことができます。読み込みについては後述します。
ドキュメントJSONファイルをsrc
と同じレベルのresources
フォルダに保存し、pom.xml
ファイルを更新して新しいresources
フォルダを含めます。
たとえば、Javaの「ハローワールド」スキルのディレクトリ構造において、helloworldDocument.json
ドキュメントはresources
フォルダにあります。
| pom.xml
| README.md
+---models
| ja-JP.json
|
+---resources
| helloworldDocument.json
|
+---src
| | HelloWorldStreamHandler.java
| |
| +---handlers
| | CancelandStopIntentHandler.java
| | FallbackIntentHandler.java
| | HelloWorldIntentHandler.java
| | HelpIntentHandler.java
| | LaunchRequestHandler.java
| | SessionEndedRequestHandler.java
| | StartOverIntentHandler.java
| |
| \---util
| Constants.java
| HelperMethods.java
これを基に、pom.xml
ファイルの<build>
セクションにresourcesフォルダを<resources>
項目として指定します。
<build>
<sourceDirectory>src</sourceDirectory>
<resources>
<resource>
<directory>resources</directory>
</resource>
</resources>
</build>
このように配置することで、ハンドラーはJavaのFile
クラスを使用してドキュメントファイルを読み込むことができます。読み込みについては後述します。
ユーザーのデバイスがAPLに対応していることを確認する
ユーザーは各種のAlexa搭載デバイスでスキルを呼び出すことができますが、画面付きと画面なしのデバイスがあります。応答にAPLディレクティブを含める前に、リクエストに含まれている対応インターフェースを必ずチェックし、デバイスでコンテンツを表示できることを確認してください。また、通常の音声出力がユーザーのデバイスに適していることも確認してください。たとえば、ユーザーのデバイスに画面がないのに「画面のXYZをご覧ください」などと発話することがないようにします。
デバイスがどのインターフェースに対応しているかは、すべてのリクエストに含まれているcontext.System.device.supportedInterfaces
オブジェクトで確認できます。ユーザーのデバイスがAPLに対応している場合、このオブジェクトにはAlexa.Presentation.APL
オブジェクトが含まれています。
このサンプルコードはAlexa Skills Kit SDK for Node.js(v2)を使用しています。
Node.js SDKにはgetSupportedInterfaces
関数が用意されており、ユーザーのデバイスが対応している全インターフェースのリストを取得できます。Alexa.Presentation.APL
インターフェース対応であることをこのリストで確認してから、APLディレクティブを送信してください。
このインターフェースの参照に必要な構文にご注意ください。名前にドット(.
)文字が含まれています。
// SDKのグローバル定数
const Alexa = require('ask-sdk-core');
//...
const HelloWorldIntentHandler = {
canHandle(handlerInput) {
// このハンドラーを使用するかどうかを判断するロジック
},
handle(handlerInput) {
// ...すべてのデバイスで使用されるその他のコード
// ユーザーのデバイスがAPL対応かどうかを確認
if (Alexa.getSupportedInterfaces(handlerInput.requestEnvelope)['Alexa.Presentation.APL']){
// APLディレクティブの送信コードをここに配置
}
}
このサンプルコードは、Alexa Skills Kit SDK for Pythonを使用しています。
utils
モジュールのget_supported_interfaces()
メソッドを使用して、デバイスが対応している全インターフェースのリストを含むSupportedInterfaces
オブジェクトを取得します。APL対応はalexa_presentation_apl
アトリビュートの値で確認します。デバイスがAPL対応ではない場合、このアトリビュートには値としてNone
が格納されています。
from ask_sdk_core.dispatch_components import AbstractRequestHandler
from ask_sdk_core.handler_input import HandlerInput
from ask_sdk_model import Response
# リクエストutilsを使用
from ask_sdk_core.utils import get_supported_interfaces
# その他のインポートはここで
class HelloWorldIntentHandler(AbstractRequestHandler):
def can_handle(self, handler_input):
# type: (HandlerInput) -> bool
# このハンドラーを使用するかどうかを判断するロジック
def handle(self, handler_input):
# type: (HandlerInput) -> Response
if get_supported_interfaces(
handler_input).alexa_presentation_apl is not None:
# APLディレクティブの送信コードをここに配置
このサンプルコードはAlexa Skills Kit SDK for Javaを使用しています。
RequestHelper
クラスのgetSupportedInterfaces()
メソッドを使用して、デバイスが対応している全インターフェースのリストを含むSupportedInterfaces
オブジェクトを取得します。getAlexaPresentationAPL()
を呼び出して、APL対応かどうかを確認します。デバイスがAPL対応ではない場合、このメソッドはnull
を返します。
// RequestHelperクラスを使用
import com.amazon.ask.request.RequestHelper;
// (その他のインポートはここで)
public class HelloWorldIntentHandler implements RequestHandler {
@Override
public boolean canHandle(HandlerInput input) {
// このハンドラーを使用するかどうかを判断するロジック
}
@Override
public Optional<Response> handle(HandlerInput input) {
// ...すべてのデバイスで使用されるその他のコード
// ユーザーのデバイスがAPL対応かどうかを確認
if (RequestHelper.forHandlerInput(input)
.getSupportedInterfaces()
.getAlexaPresentationAPL() != null) {
// APLディレクティブの送信コードをここに配置
}
}
}
Alexa.Presentation.APL
オブジェクトがcontext.System.device.supportedInterfaces
に含まれているのは、以下がどちらも成立している場合です。
- スキルがAPLに対応するよう設定されている。
- ユーザーのデバイスがAPLに対応している。
APL対応のはずのデバイス(Echo Showなど)でスキルを呼び出したのに、上記のif
文がfalse
を返す場合は、スキルがAPL対応として設定されていません。どちらもAPLに対応するようスキルを設定するに戻ってください。
リクエストハンドラーの応答でRenderDocumentを返す
受信したリクエストに対処するリクエストハンドラーをスキルコードに作成します。このハンドラーは、Alexaが発話するためのテキストを含む応答と、その他の指示を含むディレクティブを返します。APLコンテンツを表示するには、Alexa.Presentation.APL.RenderDocument
ディレクティブを含めます。このディレクティブのペイロードは、表示するAPLドキュメントです。
RenderDocument
に関する以下のガイドラインを参考にしてください。
RenderDocument
は、Alexa.Presentation.APL
インターフェース対応のデバイスにのみ返します。前述の説明に従って対応状況を確認してください。RenderDocument
ディレクティブをビルドする際、このディレクティブのdocument
プロパティに渡すことができる変数にドキュメントJSONファイルを読み込む必要があります。RenderDocument
ディレクティブには必須のtoken
プロパティがあります。ドキュメントを指定する文字列をここに設定してください。このトークンを使用して、ほかのディレクティブ(ExecuteCommands
など)のドキュメントを特定したり、viewportに現在表示されているドキュメントを確認したりできます。以下のサンプルコードでは、トークンが「helloworldToken
」に設定されています。
以下のサンプルは、前述のドキュメントを送信するシンプルなHelloWorldIntentHandler
のコードです。ユーザーのデバイスがAPL対応かどうかで、応答に含まれる発話が異なることにご注意ください。
このサンプルコードはAlexa Skills Kit SDK for Node.js(v2)を使用しています。
このハンドラーは、スキルがHelloWorldIntent
向けのIntentRequest
を受信すると実行されます。helloworldDocument.json
ファイルを変数に読み込むには、require()
関数を使用します。
const Alexa = require('ask-sdk-core');
// ハンドラーで使用するAPLドキュメントを読み込む
const helloworldDocument = require('./helloworldDocument.json');
// APLディレクティブの送信時に使用するトークン
const HELLO_WORLD_TOKEN = 'helloworldToken';
const HelloWorldIntentHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
&& Alexa.getIntentName(handlerInput.requestEnvelope) === 'HelloWorldIntent';
},
handle(handlerInput) {
let speakOutput = 'ハローワールド';
let responseBuilder = handlerInput.responseBuilder;
if (Alexa.getSupportedInterfaces(handlerInput.requestEnvelope)['Alexa.Presentation.APL']){
// RenderDocumentディレクティブをresponseBuilderに追加する
responseBuilder.addDirective({
type: 'Alexa.Presentation.APL.RenderDocument',
token: HELLO_WORLD_TOKEN,
document: helloworldDocument
});
// 発話を画面付きデバイス向けにする
speakOutput += "これでご挨拶が画面でも表示されるようになりました。"
} else {
// ユーザーのデバイスがAPL非対応であるため、発話を状況に合わせる
speakOutput += "このサンプルをEcho ShowやFire TVのような画面付きデバイスで実行すると、もっと面白いことが起こります。";
}
return responseBuilder
.speak(speakOutput)
.getResponse();
}
};
このサンプルコードは、Alexa Skills Kit SDK for Pythonを使用しています。
このハンドラーは、スキルがHelloWorldIntent
向けのIntentRequest
を受信すると実行されます。helloworldDocument.json
ファイルを変数に読み込むには、open
メソッドを使用してファイルを開き、json.load
メソッドを使用して内容をロードします。このサンプルで使用するユーティリティ関数_load_apl_document
は、ファイルパスを受け取り、ディレクティブで使用できるjsonドキュメントを返します。
ASK Python SDKには、RenderDocument
ディレクティブを作成して応答に追加するためのモデルクラスが用意されています。
import json
from ask_sdk_core.utils import (
is_intent_name, get_supported_interfaces)
from ask_sdk_core.handler_input import HandlerInput
from ask_sdk_core.dispatch_components import AbstractRequestHandler
from ask_sdk_model import Response
from ask_sdk_model.interfaces.alexa.presentation.apl import (
RenderDocumentDirective)
from typing import Dict, Any
# ハンドラーで使用するAPLドキュメントファイルのパス
hello_world_doc_path = "helloworldDocument.json"
# APLディレクティブの送信時に使用するトークン
HELLO_WORLD_TOKEN = "helloworldToken"
def _load_apl_document(file_path):
# type: (str) -> Dict[str, Any]
"""Load the apl json document at the path into a dict object."""
with open(file_path) as f:
return json.load(f)
class HelloWorldIntentHandler(AbstractRequestHandler):
"""ハローワールドインテント用ハンドラー。"""
def can_handle(self, handler_input):
# type: (HandlerInput) -> bool
return is_intent_name("HelloWorldIntent")(handler_input)
def handle(self, handler_input):
# type: (HandlerInput) -> Response
speak_output = "ハローワールド"
response_builder = handler_input.response_builder
if get_supported_interfaces(
handler_input).alexa_presentation_apl is not None:
response_builder.add_directive(
RenderDocumentDirective(
token=HELLO_WORLD_TOKEN,
document=_load_apl_document(hello_world_doc_path)
)
)
# 発話を画面付きデバイス向けにする
speak_output += ("これでご挨拶が画面でも表示されるように"
"なりました。")
else:
# ユーザーのデバイスがAPL非対応であるため、
# 発話を状況に合わせる
speak_output += ("このサンプルをEcho ShowやFire TVのような"
"画面付きデバイスで実行すると、もっと面白い"
"ことが起こります。")
return response_builder.speak(speak_output).response
このサンプルコードはAlexa Skills Kit SDK for Javaを使用しています。
このハンドラーは、スキルがHelloWorldIntent
向けのIntentRequest
を受信すると実行されます。helloworldDocument.json
ファイルを変数に読み込むには、JSONをパースしてString
とObject
のマップ(HashMap<String, Object>
)に変換します。このサンプルでは、File
クラスを使用してファイルを読み込み、ObjectMapper
クラスを使用してJSONをパースしてマップに変換します。
ASK Java SDKには、RenderDocument
ディレクティブを作成して応答に追加するためのビルダーメソッドが用意されています。
package handlers;
import static com.amazon.ask.request.Predicates.intentName;
import static util.Constants.HELLO_WORLD_TOKEN; // String "helloworldToken"
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import com.amazon.ask.dispatcher.request.handler.HandlerInput;
import com.amazon.ask.dispatcher.request.handler.RequestHandler;
import com.amazon.ask.exception.AskSdkException;
import com.amazon.ask.model.Response;
import com.amazon.ask.model.interfaces.alexa.presentation.apl.RenderDocumentDirective;
import com.amazon.ask.request.RequestHelper;
import com.amazon.ask.response.ResponseBuilder;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
public class HelloWorldIntentHandler implements RequestHandler {
@Override
public boolean canHandle(HandlerInput input) {
return input.matches(intentName("HelloWorldIntent"));
}
@Override
public Optional<Response> handle(HandlerInput input) {
String speechText = "ハローワールド";
ResponseBuilder responseBuilder = input.getResponseBuilder();
if (RequestHelper.forHandlerInput(input)
.getSupportedInterfaces()
.getAlexaPresentationAPL() != null) {
try {
// JSONドキュメントを取得し、文字列とオブジェクトをマッピングにする
ObjectMapper mapper = new ObjectMapper();
TypeReference<HashMap<String, Object>> documentMapType =
new TypeReference<HashMap<String, Object>>() {};
Map<String, Object> document = mapper.readValue(
new File("helloworldDocument.json"),
documentMapType);
// SDKのビルダーメソッドを使用してディレクティブを作成する
RenderDocumentDirective renderDocumentDirective = RenderDocumentDirective.builder()
.withToken(HELLO_WORLD_TOKEN)
.withDocument(document)
.build();
// ディレクティブをresponseBuilderに追加する
responseBuilder.addDirective(renderDocumentDirective);
// 発話を画面付きデバイス向けにする
speechText += "これでご挨拶が画面でも表示されるようになりました。";
} catch (IOException e) {
throw new AskSdkException("ハローワールドドキュメントの読み込み、または逆シリアル化に失敗しました", e);
}
} else {
// デバイスに画面がないため、発話出力を変更する
speechText += "このサンプルをEcho ShowやFire TVのような画面付きデバイスで実行すると、もっと面白いことが起こります。";
}
// 発話を追加して応答を返す
return responseBuilder
.withSpeech(speechText)
.withSimpleCard("APLを使用したハローワールド", speechText)
.build();
}
}
UserEventリクエストを処理する
スキルでAlexa.Presentation.APL.UserEvent
リクエストを使用すると、ユーザーがデバイス上で実行したアクションに対するメッセージを受信できます。これを行うには、APLドキュメントでSendEvent
コマンドを使用します。このコマンドは、スキルにUserEvent
リクエストを送信します。このリクエストに対するアクションを起こすには、スキルで標準リクエストハンドラーを使用します。
SendEvent
やUserEvent
は、一般にボタンなどのUI要素向けに使用されます。ユーザーがボタンを操作したとき、SendEvent
コマンドが実行されるようボタンを定義します。こうしておくと、スキルがUserEvent
を受信し、ボタン操作に対応できます。
以降のセクションでは、これらについて説明します。
- 前出の
helloworldDocument.json
ドキュメントを変更し、SendEvent
コマンドを追加する。 - 新しい
SendEvent
コマンドで生成されたUserEvent
を処理するハンドラーを追加する。
SendEventコマンドを実行するボタンのあるドキュメントを作成する
このサンプルドキュメントは、テキストの表示後、AlexaButton
レスポンシブ対応コンポーネントを表示します。AlexaButton
コンポーネントのprimaryAction
プロパティには、ユーザーのボタン操作に対して実行するコマンドを定義します。ここでは、ボタン操作に対して次の2つのコマンドを順に実行します。 まず、AnimateItem
を実行し、Text
コンポーネントの不透明度を変えてテキストをフェードさせます。次に、SendEvent
を実行し、UserEvent
リクエストをスキルに送信します。
このドキュメントを、"helloworldWithButtonDocument.json
"として前述の手順に従って保存します。
{
"type": "APL",
"version": "2022.1",
"description": "このAPLドキュメントはテキストを画面に表示するとともに、操作されるとスキルにメッセージを送信するボタンを用意します。ボタンは、alexa-layoutsパッケージに用意されている定義済みのレスポンシブ対応コンポーネントです。",
"import": [
{
"name": "alexa-layouts",
"version": "1.5.0"
}
],
"mainTemplate": {
"parameters": [
"payload"
],
"items": [
{
"type": "Container",
"height": "100vh",
"width": "100vw",
"items": [
{
"type": "Text",
"id": "helloTextComponent",
"height": "75%",
"text": "ハローワールド。 このAPLドキュメントにはalexa-layoutsパッケージのボタンが含まれています。ボタンを押して動作を確認してください。",
"textAlign": "center",
"textAlignVertical": "center",
"paddingLeft": "@spacingSmall",
"paddingRight": "@spacingSmall",
"paddingTop": "@spacingXLarge",
"style": "textStyleBody"
},
{
"type": "AlexaButton",
"alignSelf": "center",
"id": "fadeHelloTextButton",
"buttonText": "これはボタンです",
"primaryAction": [
{
"type": "AnimateItem",
"duration": 3000,
"componentId": "helloTextComponent",
"value": {
"property": "opacity",
"to": 0
}
},
{
"type": "SendEvent",
"arguments": [
"ユーザーがボタンを押しました"
]
}
]
}
]
}
]
}
}
このドキュメントを表示するには、インテントハンドラーからRenderDocument
を返します。このサンプルで表示されるドキュメントはhelloworldWithButtonDocument.json
、トークン
はhelloworldWithButtonToken
です。
このサンプルコードはAlexa Skills Kit SDK for Node.js(v2)を使用しています。
const Alexa = require('ask-sdk-core');
// ハンドラーで使用するAPLドキュメントを読み込む
const helloworldDocument = require('./helloworldDocument.json');
const helloworldWithButtonDocument = require('./helloworldWithButtonDocument.json');
// APLディレクティブの送信時に使用するトークン
const HELLO_WORLD_TOKEN = 'helloworldToken';
const HELLO_WORLD_WITH_BUTTON_TOKEN = 'helloworldWithButtonToken';
const HelloWorldWithButtonIntentHander = {
canHandle(handlerInput){
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
&& Alexa.getIntentName(handlerInput.requestEnvelope) === 'HelloWorldWithButtonIntent';
},
handle(handlerInput){
let speakOutput = "ハローワールド";
let responseBuilder = handlerInput.responseBuilder;
if (Alexa.getSupportedInterfaces(handlerInput.requestEnvelope)['Alexa.Presentation.APL']){
// RenderDocumentディレクティブをresponseBuilderに追加する
responseBuilder.addDirective({
type: 'Alexa.Presentation.APL.RenderDocument',
token: HELLO_WORLD_WITH_BUTTON_TOKEN,
document: helloworldWithButtonDocument,
});
// 発話を画面付きデバイス向けにする
speakOutput += "Alexa Presentation Languageへようこそ。ボタンを押して動作を確認してください。"
} else {
speakOutput += "このサンプルをEcho ShowやFire TVのような画面付きデバイスで実行すると、もっと面白いことが起こります。"
}
return responseBuilder
.speak(speakOutput)
//.reprompt('ユーザーが応答できるようセッションを開いたままにする場合は再プロンプトを追加してください')
.getResponse();
}
}
このサンプルコードは、Alexa Skills Kit SDK for Pythonを使用しています。
import json
from ask_sdk_core.utils import (
is_intent_name, get_supported_interfaces)
from ask_sdk_core.handler_input import HandlerInput
from ask_sdk_core.dispatch_components import AbstractRequestHandler
from ask_sdk_model import Response
from ask_sdk_model.interfaces.alexa.presentation.apl import (
RenderDocumentDirective)
from typing import Dict, Any
# ハンドラーで使用するAPLドキュメントファイルのパス
hello_world_doc_path = "helloworldDocument.json"
hello_world_button_doc_path = "helloworldWithButtonDocument.json"
# APLディレクティブの送信時に使用するトークン
HELLO_WORLD_TOKEN = "helloworldToken"
HELLO_WORLD_WITH_BUTTON_TOKEN = "helloworldWithButtonToken"
def _load_apl_document(file_path):
# type: (str) -> Dict[str, Any]
"""Load the apl json document at the path into a dict object."""
with open(file_path) as f:
return json.load(f)
class HelloWorldWithButtonIntentHandler(AbstractRequestHandler):
"""ハローワールドインテント用ハンドラー。"""
def can_handle(self, handler_input):
# type: (HandlerInput) -> bool
return is_intent_name("HelloWorldWithButtonIntent")(handler_input)
def handle(self, handler_input):
# type: (HandlerInput) -> Response
speak_output = "ハローワールド"
response_builder = handler_input.response_builder
if get_supported_interfaces(
handler_input).alexa_presentation_apl is not None:
response_builder.add_directive(
RenderDocumentDirective(
token=HELLO_WORLD_WITH_BUTTON_TOKEN,
document=_load_apl_document(hello_world_button_doc_path)
)
)
# 発話を画面付きデバイス向けにする
speak_output += ("Alexa Presentation Languageへようこそ。"
"ボタンを押して動作を確認してください。")
else:
# ユーザーのデバイスがAPL非対応であるため、
# 発話を状況に合わせる
speak_output += ("このサンプルをEcho ShowやFire TVのような"
"画面付きデバイスで実行すると、もっと面白い"
"ことが起こります。")
return response_builder.speak(speak_output).response
このサンプルコードはAlexa Skills Kit SDK for Javaを使用しています。
package handlers;
import static com.amazon.ask.request.Predicates.intentName;
import static util.Constants.HELLO_WORLD_WITH_BUTTON_TOKEN; // String "helloworldWithButtonToken"
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import com.amazon.ask.dispatcher.request.handler.HandlerInput;
import com.amazon.ask.dispatcher.request.handler.RequestHandler;
import com.amazon.ask.exception.AskSdkException;
import com.amazon.ask.model.Response;
import com.amazon.ask.model.interfaces.alexa.presentation.apl.RenderDocumentDirective;
import com.amazon.ask.request.RequestHelper;
import com.amazon.ask.response.ResponseBuilder;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
public class HelloWorldWithButtonIntentHandler implements RequestHandler {
@Override
public boolean canHandle(HandlerInput input) {
return input.matches(intentName("HelloWorldWithButtonIntent"));
}
@Override
public Optional<Response> handle(HandlerInput input) {
String speechText = "ハローワールド";
ResponseBuilder responseBuilder = input.getResponseBuilder();
if (RequestHelper.forHandlerInput(input)
.getSupportedInterfaces()
.getAlexaPresentationAPL() != null) {
try {
// ハローワールドAPLドキュメントを含むJSONファイルを
//「resources」フォルダから取得し、
// ディレクティブに追加する
ObjectMapper mapper = new ObjectMapper();
TypeReference<HashMap<String, Object>> documentMapType = new TypeReference<HashMap<String, Object>>() {
};
Map<String, Object> document = mapper.readValue(new File("helloworldWithButtonDocument.json"), documentMapType);
// ディレクティブをビルドする
RenderDocumentDirective renderDocumentDirective = RenderDocumentDirective.builder()
.withToken(HELLO_WORLD_WITH_BUTTON_TOKEN)
.withDocument(document)
.build();
responseBuilder.addDirective(renderDocumentDirective);
// 発話を更新して画面について言及する
speechText += "Alexa Presentation Languageへようこそ。ボタンを押して動作を確認してください。";
} catch (IOException e) {
throw new AskSdkException("ハローワールドドキュメントの読み込み、または逆シリアル化に失敗しました", e);
}
} else {
speechText += "このサンプルをEcho ShowやFire TVのような画面付きデバイスで実行すると、もっと面白いことが起こります。";
}
return responseBuilder
.withSpeech(speechText)
.withSimpleCard("APLを使用したハローワールド", speechText)
.build();
}
}
UserEventリクエストハンドラーを追加する
前出のサンプルでユーザーがボタンを操作すると、画面に表示されていたテキストがフェードアウトします(AnimateItem
コマンド)。続いて、AlexaがスキルにUserEvent
リクエストを送信します(SendEvent
コマンド)。スキルはそれに応答して、Alexaの標準的な発話や新しいAPLディレクティブなどのアクションを起こすことができます。
一般に、UserEvent
ハンドラーはイベントのトリガー元コンポーネントを特定する必要があります。これは、スキルに複数のドキュメントがあってボタンなどの各種要素を使用しており、そうした要素がリクエストをスキルに送信できる、という可能性があるからです。このようなリクエストの型は、すべてAlexa.Presentation.APL.UserEvent
です。イベントの識別には、リクエストのsource
プロパティまたはarguments
プロパティを使用できます。
たとえば、前出のドキュメントに定義されているボタン操作があると、次のリクエストが送信されます。
{
"request": {
"type": "Alexa.Presentation.APL.UserEvent",
"requestId": "amzn1.echo-api.request.1",
"timestamp": "2019-10-04T18:48:22Z",
"locale": "ja-JP",
"arguments": [
"ユーザーがボタンを押しました"
],
"components": {},
"source": {
"type": "TouchWrapper",
"handler": "Press",
"id": "fadeHelloTextButton",
"value": false
},
"token": "helloworldWithButtonToken"
}
}
arguments
プロパティには、SendEvent
コマンドのarguments
プロパティに定義されている引数の配列が含まれます。source
プロパティには、イベントをトリガーしたコンポーネントに関する情報としてコンポーネントのIDなどが含まれます。
したがって、ハンドラーでは、ドキュメントに定義されているコンポーネントのIDと同じrequest.source.id
を持つ任意のUserEvent
リクエストに対処できます。このハンドラーは、ユーザーによるボタン操作「fadeHelloTextButton」に対し、ユーザーがボタンを押したことに触れる発話で対処します。
このサンプルコードはAlexa Skills Kit SDK for Node.js(v2)を使用しています。
const HelloWorldButtonEventHandler = {
canHandle(handlerInput){
// APLスキルにはUserEventを生成するボタンが複数存在することがあるため、
// このイベントをトリガーしたボタン操作をイベントソースIDで判断し、
// 適切なハンドラーを使用します。このサンプルでドキュメントのAlexaButtonに
// 設定されているIDの文字列は「fadeHelloTextButton」です。
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'Alexa.Presentation.APL.UserEvent'
&& handlerInput.requestEnvelope.request.source.id === 'fadeHelloTextButton';
},
handle(handlerInput){
const speakOutput = "ボタン操作をありがとうございます。 もうお気づきかと思いますが、テキストが消えました。「最初から始めて」と命令すると再び表示されます。";
return handlerInput.responseBuilder
.speak(speakOutput)
.reprompt("テキストを再び表示するには、「最初から始めて」と言います。または、もう一度「こんにちは」と言ってください。")
.getResponse();
}
}
このサンプルコードは、Alexa Skills Kit SDK for Pythonを使用しています。
from ask_sdk_core.utils import (
is_request_type, is_intent_name, get_supported_interfaces)
from ask_sdk_core.handler_input import HandlerInput
from ask_sdk_core.dispatch_components import AbstractRequestHandler
from ask_sdk_model import Response
from ask_sdk_model.interfaces.alexa.presentation.apl import UserEvent
class HelloWorldButtonEventHandler(AbstractRequestHandler):
def can_handle(self, handler_input):
# type: (HandlerInput) -> bool
# APLスキルにはUserEventを生成するボタンが複数存在することがあるため、
# このイベントをトリガーしたボタン操作をイベントソースIDで判断し、
# 適切なハンドラーを使用します。
# このサンプルでドキュメントのAlexaButtonに設定されているIDの
# 文字列は「fadeHelloTextButton」です。
# user_event.sourceはdictオブジェクトです。idの取得には
# dictionaryのgetメソッドを使用します。
if is_request_type("Alexa.Presentation.APL.UserEvent")(handler_input):
user_event = handler_input.request_envelope.request # type: UserEvent
return user_event.source.get("id") == "fadeHelloTextButton"
else:
return False
def handle(self, handler_input):
# type: (HandlerInput) -> Response
speech_text = ("ボタン操作をありがとうございます。 もうお気づき"
"かと思いますが、テキストが消えました。「最初から"
"始めて」と命令すると再び表示されます。")
return handler_input.response_builder.speak(speech_text).ask(
"テキストを再び表示するには、「最初から始めて」と言います。"
"または、もう一度「こんにちは」と言ってください。").response
このサンプルコードはAlexa Skills Kit SDK for Javaを使用しています。
package handlers;
import java.util.Map;
import java.util.Optional;
import com.amazon.ask.dispatcher.request.handler.HandlerInput;
import com.amazon.ask.dispatcher.request.handler.impl.UserEventHandler;
import com.amazon.ask.model.Response;
import com.amazon.ask.model.interfaces.alexa.presentation.apl.UserEvent;
public class HelloWorldButtonEventHandler implements UserEventHandler {
@Override
public boolean canHandle(HandlerInput input, UserEvent userEvent) {
// これは型指定のあるハンドラーであるため、UserEvent専用として実行されます。
// APLスキルにはUserEventを生成するボタンが複数存在することがあるため、
// このイベントをトリガーしたボタン操作をイベントソースIDで判断し、
// 適切なハンドラーを使用します。このサンプルで
// 設定されているIDの文字列は「fadeHelloTextButton」です。
// userEvent.getSource()メソッドはObjectを返します。Mapにキャストすることで
// idを取得できます。
Map<String,Object> eventSourceObject = (Map<String,Object>) userEvent.getSource();
String eventSourceId = (String) eventSourceObject.get("id");
return eventSourceId.equals("fadeHelloTextButton");
}
@Override
public Optional<Response> handle(HandlerInput input, UserEvent userEvent) {
String speechText = "ボタン操作をありがとうございます。 もうお気づきかと思いますが、テキストが消えました。「最初から始めて」と命令すると再び表示されます。";
return input.getResponseBuilder()
.withSpeech(speechText)
.withReprompt("テキストを再び表示するには、「最初から始めて」と言います。または、もう一度「こんにちは」と言ってください。")
.build();
}
}
上記のコードは次のように発話するようAlexaに命令します。「ボタン操作をありがとうございます。 もうお気づきかと思いますが、テキストが消えました。『最初から始めて』と命令すると再び表示されます」
リクエストハンドラーの応答でExecuteCommandsを返す
スキルでは、Alexa.Presentation.APL.ExecuteCommands
ディレクティブを使用するAPLコマンドをトリガーできます。これにより、ユーザーとの対話への応答として、発話などのコマンドを実行できます。
ExecuteCommands
ディレクティブには、必須のtoken
プロパティがあります。これが、現在表示されているドキュメントのRenderDocument
ディレクティブに指定されているtoken
と一致する必要があります。トークンが一致しない場合、デバイスはコマンドを実行しません。ExecuteCommands
ディレクティブを送信して、以前のディレクティブでレンダリングされたドキュメントに対してこのディレクティブを実行するには、事前にリクエストのcontext
をチェックして、想定ドキュメントが表示されることを確認してください。
ExecuteCommands
ディレクティブは、RenderDocument
ディレクティブと同じ応答に含めることもできます。この場合も、token
が両ディレクティブで一致する必要があります。
このサンプルコードではStartOverIntentHandler
を追加します。ユーザーが「最初から始めて」と命令すると、スキルは現在の表示コンテキストをチェックし、デバイスに表示されているテキストのトークンが「helloworldWithButtonToken
」であるかどうかを確認します。確認できると、スキルはAnimateItem
コマンドを含むExecuteCommands
を送信します。このコマンドでは、Text
要素の不透明度を3秒かけて0から1に戻してフェードイン効果を実現します。
このサンプルコードはAlexa Skills Kit SDK for Node.js(v2)を使用しています。
const Alexa = require('ask-sdk-core');
// ハンドラーで使用するAPLドキュメントを読み込む
const helloworldDocument = require('./helloworldDocument.json');
const helloworldWithButtonDocument = require('./helloworldWithButtonDocument.json');
// APLディレクティブの送信時に使用するトークン
const HELLO_WORLD_TOKEN = 'helloworldToken';
const HELLO_WORLD_WITH_BUTTON_TOKEN = 'helloworldWithButtonToken';
const StartOverIntentHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
&& Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.StartOverIntent';
},
handle(handlerInput) {
let speakOutput = '';
let responseBuilder = handlerInput.responseBuilder;
if (Alexa.getSupportedInterfaces(handlerInput.requestEnvelope)['Alexa.Presentation.APL']) {
speakOutput = "";
// APLの視覚コンテキスト情報を取得し、デバイスがトークン
// HELLO_WORLD_WITH_BUTTON_TOKENを持つドキュメントを表示しているかどうかを判断します。
const contextApl = handlerInput.requestEnvelope.context['Alexa.Presentation.APL'];
if (contextApl && contextApl.token === HELLO_WORLD_WITH_BUTTON_TOKEN){
// ExecuteCommandsディレクティブをビルドします。この中で、Textコンポーネントの
// 不透明度を1に戻して再び表示されるようにします。ディレクティブに含まれるトークンが、
// 表示されているドキュメントのトークンと一致する必要があることに注意してください。
speakOutput = "では、テキストを再び表示してみましょう。";
// コンポーネントの不透明度を3秒かけて1に戻す
// APLコマンドを作成します
const animateItemCommand = {
type: "AnimateItem",
componentId: "helloTextComponent",
duration: 3000,
value: [
{
property: "opacity",
to: 1
}
]
}
// ExecuteCommandsディレクティブにコマンドを追加し、それを
// 応答に追加します。
responseBuilder.addDirective({
type: 'Alexa.Presentation.APL.ExecuteCommands',
token: HELLO_WORLD_WITH_BUTTON_TOKEN,
commands: [animateItemCommand]
})
} else {
// デバイスが想定したドキュメントを表示していないため、
// 状況に応じた出力音声を用意します。
speakOutput = "おや、リセット対象が何もありません。「ボタンインテントを使用するハローワールド」を呼び出してからボタンを押して、動作を確認してください。";
}
} else {
speakOutput = "こんにちは。このサンプルは画面付きデバイスを使うと、もっと面白いことが起こります。Echo Show、Echo Spot、Fire TVのいずれかのデバイスで試してください。";
}
return responseBuilder
.speak(speakOutput)
.getResponse();
}
};
このサンプルコードは、Alexa Skills Kit SDK for Pythonを使用しています。
import json
from ask_sdk_core.utils import (
is_request_type, is_intent_name, get_supported_interfaces)
from ask_sdk_core.handler_input import HandlerInput
from ask_sdk_core.dispatch_components import AbstractRequestHandler
from ask_sdk_model import Response
from ask_sdk_model.interfaces.alexa.presentation.apl import (
RenderDocumentDirective, AnimatedOpacityProperty, AnimateItemCommand,
ExecuteCommandsDirective, UserEvent)
from typing import Dict, Any
# ハンドラーで使用するAPLドキュメントファイルのパス
hello_world_doc_path = "helloworldDocument.json"
hello_world_button_doc_path = "helloworldWithButtonDocument.json"
# APLディレクティブの送信時に使用するトークン
HELLO_WORLD_TOKEN = "helloworldToken"
HELLO_WORLD_WITH_BUTTON_TOKEN = "helloworldWithButtonToken"
def _load_apl_document(file_path):
# type: (str) -> Dict[str, Any]
"""Load the apl json document at the path into a dict object."""
with open(file_path) as f:
return json.load(f)
class StartOverIntentHandler(AbstractRequestHandler):
"""StartOverIntentのハンドラーです。"""
def can_handle(self, handler_input):
# type: (HandlerInput) -> bool
return is_intent_name("AMAZON.StartOverIntent")(handler_input)
def handle(self, handler_input):
# type: (HandlerInput) -> Response
speak_output = None
response_builder = handler_input.response_builder
if get_supported_interfaces(
handler_input).alexa_presentation_apl is not None:
# JSONリクエストのAPL視覚コンテンツ情報を取得します。
# 現在表示されているドキュメントのIDが
# HELLO_WORLD_WITH_BUTTON_TOKENトークン("helloworldWithButtonToken")
# であることを確認します。
context_apl = handler_input.request_envelope.context.alexa_presentation_apl
if (context_apl is not None and
context_apl.token == HELLO_WORLD_WITH_BUTTON_TOKEN):
speak_output = ("では、テキストを再び表示"
"してみましょう。")
animate_item_command = AnimateItemCommand(
component_id="helloTextComponent",
duration=3000,
value=[AnimatedOpacityProperty(to=1.0)]
)
response_builder.add_directive(
ExecuteCommandsDirective(
token=HELLO_WORLD_WITH_BUTTON_TOKEN,
commands=[animate_item_command]
)
)
else:
# デバイスが想定したドキュメントを表示していないため、
# 状況に応じた出力音声を用意します。
speak_output = ("おや、リセット対象が何もありません。 "
"「ボタンインテントを使用するハローワールド」を"
"呼び出してからボタンを押して、動作を"
"確認してください。")
else:
# ユーザーのデバイスがAPL非対応であるため、
# 発話を状況に合わせる
speak_output += ("このサンプルを画面付きデバイスで実行すると"
"もっと面白いことが起こります。Echo Show、Echo Spot、"
"Fire TVのいずれかのデバイスで試してください")
return response_builder.speak(speak_output).response
このサンプルコードはAlexa Skills Kit SDK for Javaを使用しています。
package handlers;
import static com.amazon.ask.request.Predicates.intentName;
import static util.Constants.HELLO_WORLD_WITH_BUTTON_TOKEN; // String "helloworldWithButtonToken"
import java.util.Optional;
import com.amazon.ask.dispatcher.request.handler.HandlerInput;
import com.amazon.ask.dispatcher.request.handler.RequestHandler;
import com.amazon.ask.model.Response;
import com.amazon.ask.model.interfaces.alexa.presentation.apl.AnimateItemCommand;
import com.amazon.ask.model.interfaces.alexa.presentation.apl.AnimatedOpacityProperty;
import com.amazon.ask.model.interfaces.alexa.presentation.apl.ExecuteCommandsDirective;
import com.amazon.ask.request.RequestHelper;
import com.amazon.ask.response.ResponseBuilder;
import com.fasterxml.jackson.databind.JsonNode;
public class StartOverIntentHandler implements RequestHandler {
@Override
public boolean canHandle(HandlerInput input) {
return input.matches(intentName("AMAZON.StartOverIntent"));
}
@Override
public Optional<Response> handle(HandlerInput input) {
String speechText;
ResponseBuilder responseBuilder = input.getResponseBuilder();
if (RequestHelper.forHandlerInput(input)
.getSupportedInterfaces()
.getAlexaPresentationAPL() != null) {
// JSONリクエストのAPL視覚コンテンツ情報を取得します。
// 現在表示されているドキュメントのIDが
// HELLO_WORLD_WITH_BUTTON_TOKENトークン("helloworldWithButtonToken")
// であることを確認します。
JsonNode context = input.getRequestEnvelopeJson().get("context");
if (context.has("Alexa.Presentation.APL") &&
(context.get("Alexa.Presentation.APL")
.get("token")
.asText()
.equals(HELLO_WORLD_WITH_BUTTON_TOKEN))){
speechText = "では、テキストを再び表示してみましょう。";
AnimatedOpacityProperty animatedOpacityProperty = AnimatedOpacityProperty.builder()
.withTo(1.0)
.build();
AnimateItemCommand animateItemCommand = AnimateItemCommand.builder()
.withComponentId("helloTextComponent")
.withDuration(3000)
.addValueItem(animatedOpacityProperty)
.build();
// ExecuteCommandsのトークンが
// もともとドキュメントを表示していたRenderDocumentディレクティブに
// 指定されているトークンと一致することが必要です
ExecuteCommandsDirective executeCommandsDirective = ExecuteCommandsDirective.builder()
.withToken(HELLO_WORLD_WITH_BUTTON_TOKEN)
.addCommandsItem(animateItemCommand)
.build();
responseBuilder.addDirective(executeCommandsDirective);
} else {
// デバイスが想定したドキュメントを表示していないため、
// 状況に応じた出力音声を用意します。
speechText = "おや、リセット対象が何もありません。「ボタンインテントを使用するハローワールド」を呼び出してからボタンを押して、動作を確認してください。";
}
} else {
speechText = "こんにちは。このサンプルは画面付きデバイスを使うと、もっと面白いことが起こります。Echo Show、Echo Spot、Fire TVのいずれかのデバイスで試してください。";
}
return responseBuilder
.withSpeech(speechText)
.build();
}
}
関連トピック
- APLドキュメント
- Alexa.Presentation.APLインターフェース
- Alexa Skills Kit SDK
- AnimateItemコマンド
- SendEventコマンド
- サンプルスキル:Node.jsでAPLスキルを作成する
最終更新日: 2022 年 07 月 04 日