Alexaから送信されたリクエストを処理する
カスタムスキルの場合、AWS Lambda関数かウェブサービスのいずれかを作成してAlexaからのリクエストを処理できます。このサービスのコードでは以下を行う必要があります。
- Alexaから受信するすべてのリクエストを認識します。
- 適切な応答を返します。
このドキュメントでは、リクエストを処理して応答を返す方法の詳細とコードの例を紹介します。
スキルから受け取ったリクエストを確認する
ウェブサービスまたはLambda関数は、リクエストを受け取る前に、そのリクエストがスキルから送信されたものであることを確認します。これにより、悪意あるユーザーがエンドポイントからスキルの設定を変更し、そのスキルを使ってサービスにリクエストを送るのを防ぐことができます。
この検証を行うために、Alexaから送信されるすべてのリクエストにはスキルIDが含まれています。このスキルIDを実際のスキルIDと照合することで、リクエストがサービスに対するものであることを確認できます。
スキルのIDを取得する
スキルIDは、開発者コンソールに表示されます。開発者コンソールを開いてスキルのリストを参照します。すべてのスキルには、名前の下にスキルIDの表示リンクが表示されます。このリンクをクリックしてIDを確認します。
スキルIDはエンドポイントページでも確認できます。ここには、クリップボードにコピーボタンも表示されます。
コードでスキルIDの検証を行う
以下の例のようにスキルIDを渡すと、Alexa Skills Kit SDKは自動でスキルIDを検証します。SDKを使用していない場合、JSONリクエストからIDを取得して独自のコードで比較を行います。
このサンプルコードはAlexa Skills Kit SDK for Node.js (v2)を使用しています。
コードでスキルIDを検証するには、スキルインスタンスの設定時にスキルIDをSkillBuilder.withSkillId
メソッドに渡します。このコード例では、"amzn1.ask.skill.1"
に一致しないスキルIDのリクエストをすべて拒否します。
const skillBuilder = Alexa.SkillBuilders.custom();
exports.handler = skillBuilder
.withSkillId("amzn1.ask.skill.1")
.addRequestHandlers(
HelloWorldIntentHandler,
LaunchRequestHandler,
HelpIntentHandler,
CancelAndStopIntentHandler,
SessionEndedRequestHandler,
)
.addErrorHandlers(ErrorHandler)
.addRequestInterceptors(LoggingRequestInterceptor)
.addResponseInterceptors(LoggingResponseInterceptor)
.lambda();
このサンプルコードはAlexa Skills Kit SDK for Pythonを使用しています。
コードでスキルIDを検証するには、スキルインスタンスの設定時にskill_id
アトリビュートをSkillBuilder
オブジェクトにセットします。このコード例では、"amzn1.ask.skill.1"
に一致しないスキルIDのリクエストをすべて拒否します。
from ask_sdk_core.skill_builder import SkillBuilder
sb = SkillBuilder()
# スキルIDを設定
sb.skill_id = "amzn.ask.skill.1"
# すべてのリクエストハンドラーをスキルに追加
sb.add_request_handler(LaunchRequestHandler())
sb.add_request_handler(HelloWorldIntentHandler())
sb.add_request_handler(HelpIntentHandler())
sb.add_request_handler(CancelAndStopIntentHandler())
sb.add_request_handler(SessionEndedRequestHandler())
sb.add_request_handler(FallbackIntentHandler())
# 例外ハンドラーをスキルに追加
sb.add_exception_handler(CatchAllExceptionHandler())
# ログリクエストインターセプターをスキルに追加
sb.add_global_request_interceptor(LogRequestInterceptor())
# ログレスポンスインターセプターをスキルに追加
sb.add_global_request_interceptor(LogResponseInterceptor())
# Lambdaに登録するLambdaハンドラーを提示
lambda_handler = sb.lambda_handler()
このサンプルコードはAlexa Skills Kit SDK for Javaを使用しています。
コードでスキルIDを検証するには、スキルインスタンスの設定時にスキルIDをSkillBuilder.withSkillId
メソッドに渡します。このコード例では、"amzn1.ask.skill.1"
に一致しないスキルIDのリクエストをすべて拒否します。
import com.amazon.ask.Skill;
import com.amazon.ask.SkillStreamHandler;
import com.amazon.ask.Skills;
import handlers.*;
import interceptors.*;
public class HandleReqCodeSamplesStreamHandler extends SkillStreamHandler {
private static Skill getSkill() {
return Skills.standard()
.withSkillId("amzn1.ask.skill.1")
.addRequestHandlers(
new HelloWorldIntentHandler(),
new LaunchRequestHandler(),
new HelpIntentHandler(),
new CancelandStopIntentHandler(),
new SessionEndedRequestHandler(),
new FallbackIntentHandler())
.addRequestInterceptors(
new LogRequestInterceptor()
)
.addResponseInterceptors(
new LogResponseInterceptor()
)
.build();
}
public HandleReqCodeSamplesStreamHandler() {
super(getSkill());
}
}
スキルIDには、context.System.application.applicationId
プロパティで受け取るリクエストでアクセスできます。スキルIDはsession
オブジェクトにもありますが、session
オブジェクトはcontext
オブジェクトと違い、受信するすべてのリクエストに含まれるわけではありません。
この例でのスキルIDは、amzn1.ask.skill.1
です。簡潔に表現するため、この例ではrequest
オブジェクトを省略しています。
{
"version": "1.0",
"context": {
"AudioPlayer": {
"playerActivity": "IDLE"
},
"System": {
"application": {
"applicationId": "amzn1.ask.skill.1"
},
"user": {
"userId": "amzn1.ask.account.1"
},
"device": {
"supportedInterfaces": {
"AudioPlayer": {}
}
}
}
},
"session": {
"new": true,
"sessionId": "amzn1.echo-api.session.1",
"application": {
"applicationId": "amzn1.ask.skill.1"
},
"user": {
"userId": "amzn1.ask.account.1"
},
"attributes": {}
},
"request": {}
}
スキルで処理する必要のあるリクエストのタイプ
Alexaは、スキルにさまざまなタイプのリクエストを送信します。リクエストは以下のいずれかを表します。
- ユーザーがしたいことを表します。たとえば、ゲームスキルの場合には新しいゲームを開始したいといった、ユーザーの意図です。この場合、
StartNewGameIntent
と呼ばれるIntentRequest
としてスキルに送られます。 - ユーザーに代わってAlexaが送信するイベントを表します。たとえば、
AudioPlayer.PlaybackStarted
リクエストでは、スキルが開始したオーディオストリームの再生をAlexaが開始したことをスキルに通知します。
スキルは、受け取ったすべてのリクエストに対して有効な応答を返す必要があります。
リクエストとリクエストタイプについて
カスタムスキルに送信されるリクエストには、type
を指定するrequest
オブジェクトとリクエストの詳細が含まれます。たとえば、ユーザーが具体的なリクエストなしでスキルを呼び出すと、スキルはLaunchRequest
を受け取ります。このリクエストのJSONコードは次のようになります(簡潔に表現するため、この例にはsession
オブジェクトとcontext
オブジェクトの一部のみを記載しています)。type
には、LaunchRequest
のリクエストタイプがセットされています。
{
"version": "1.0",
"request": {
"type": "LaunchRequest",
"requestId": "amzn1.echo-api.request.1",
"timestamp": "2016-10-27T18:21:44Z",
"locale": "ja-JP"
},
"session": {},
"context": {}
}
考えられるリクエストタイプ
スキルで使用する機能やインターフェースに応じて、処理するリクエストタイプは異なります。以下の表は、リクエストタイプの概要と処理が必要なケースを表しています。
リクエストタイプ | 説明 | 処理が必要かどうか |
---|---|---|
|
◯( |
|
オーディオストリーミングに |
〇(長いオーディオのストリーミングに |
|
ユーザーがスキルで処理が必要な可能性のあるリクエストを行った場合に送信されます。Alexaは、スキルに検出されたスロットを使用してインテントリクエストを理解して実行できるかどうかをたずねるためにこのリクエストを送信します。これは、通常の |
〇(スキルのCanFulfillIntentRequestオプションを有効にしている場合) |
|
|
〇(スキル内課金(ISP)を実装する場合) |
|
ユーザーがインテントのいずれかにマッピングされるコマンドを発話すると送信されます。リクエストにはインテント名が含まれます。 |
〇(スキルでは対話モデルに定義済みで、想定されるインテントをすべて処理する必要があります) |
|
ユーザーが具体的なコマンドを指定せずにスキルを呼び出す場合に送信されます。例: |
〇 |
|
セッション外でスキルに送信されたメッセージを処理します。 |
〇(スキルイベント、スキルメッセージREST APIなど、セッション外のリクエストが想定される場合) |
|
オーディオストリーミングに |
〇(オーディオのストリーミングに |
|
ユーザーがスキルを終了した、ユーザーがスキルに理解できない応答を返した、エラーが発生した、のいずれかにより現在開いているスキルセッションが閉じられた場合に送信されます。 |
〇 |
リクエストハンドラーについて
リクエストハンドラーは、さまざまな受信リクエストに対してアクションを実行するためのコードです。
Alexa Skills Kit SDKを使用する場合、RequestHandler
インターフェースを実装することでこれらのハンドラーを定義できます。定義すると、SDKは受信したリクエストを自動的に適切なハンドラーにルーティングします。SDKを使用しない場合、受信したリクエストをチェックして正しいハンドラーにルーティングするロジックを独自にコーディングする必要があります。
ハンドラーが処理できるリクエストを判断する
リクエストハンドラーは、それぞれに処理できるリクエストを判断する必要があります。各ハンドラーの要件は、スキルのデザインや機能によって異なります。応答のコードを他で再利用できる場合は、1つのハンドラーで複数のリクエストタイプを処理できるようコーディングすると効率的です。一般に、ハンドラーは次のような条件で決まります。
- リクエストタイプが条件の場合、そのハンドラーはリクエストタイプを見て、特定のタイプ(
LaunchRequest
など)のリクエストだけを処理します。 - リクエストに含まれるその他のデータが条件の場合、そのハンドラーは、特定のデータを含む
IntentRequest
(インテント名がHelloWorld
など)を処理します。 - リクエストに含まれるダイアログ状態データが条件の場合、そのハンドラーは、特定のダイアログ状態(インテント名が
PlanMyTrip
で、dialogState
がIN_PROGRESS
など)のIntentRequest
を処理します。 - 特定のスロット値が条件の場合、そのハンドラーは特定のスロット値(インテント名が
OrderIntent
でdrink
スロットの値が「コーヒー」など)を持つIntentRequest
を処理します。 - その他のスロット状態データが条件の場合、特定のセッションアトリビュートなどで判断します。
- リクエストのデータから取得できる任意の条件で判断します。
Alexa Skills Kit SDKを使ってcanHandle
メソッドを実装し、特定のハンドラーが処理できるリクエストを判断します。以下は、リクエストタイプに基づいて判断を行うシンプルなハンドラーを定義する方法の例です。
このサンプルコードはAlexa Skills Kit SDK for Node.js (v2)を使用しています。
この例では、LaunchRequestHandler
というリクエストハンドラーを定義しています。リクエストタイプがLaunchRequest
の場合、canHandle
メソッドはtrue
を返します。
const LaunchRequestHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'LaunchRequest';
},
// このハンドラーのその他のメソッド...
};
このサンプルコードはAlexa Skills Kit SDK for Pythonを使用しています。
この例では、LaunchRequestHandler
というリクエストハンドラーを定義しています。リクエストタイプがLaunchRequest
の場合、canHandle
メソッドはtrue
を返します。Python SDKが提供するユーティリティ関数のutils
クラスにより、受信リクエストの評価が簡単になります。
from ask_sdk_core.handler_input import HandlerInput
from ask_sdk_core.dispatch_components import AbstractRequestHandler
from ask_sdk_core.utils import is_request_type
class LaunchRequestHandler(AbstractRequestHandler):
"""スキルを起動するハンドラー"""
def can_handle(self, handler_input):
# type: (HandlerInput) -> bool
return is_request_type("LaunchRequest")(handler_input)
# このハンドラーのその他のメソッド
このサンプルコードはAlexa Skills Kit SDK for Javaを使用しています。
この例では、LaunchRequestHandler
というリクエストハンドラーを定義しています。リクエストタイプがLaunchRequest
の場合、canHandle
メソッドはtrue
を返します。Java SDKが提供するstaticメソッドのPredicates
クラスにより、受信リクエストの評価が簡単になります。
package handlers;
import com.amazon.ask.dispatcher.request.handler.HandlerInput;
import com.amazon.ask.dispatcher.request.handler.RequestHandler;
import com.amazon.ask.model.LaunchRequest;
import com.amazon.ask.model.Response;
import java.util.Optional;
import static com.amazon.ask.request.Predicates.requestType;
public class LaunchRequestHandler implements RequestHandler {
@Override
public boolean canHandle(HandlerInput input) {
return input.matches(requestType(LaunchRequest.class));
}
// このハンドラーのその他のメソッド...
}
スキルに送信されるJSONのrequest.type
プロパティに"LaunchRequest"
が指定されます。この値を取得し、リクエストタイプを評価して、リクエストを適切なハンドラーにルーティングするコードを書く必要があります。
{
"version": "1.0",
"request": {
"type": "LaunchRequest",
"requestId": "amzn1.echo-api.request.1",
"timestamp": "2016-10-27T18:21:44Z",
"locale": "ja-JP"
},
"session": {},
"context": {}
}
一般的に、インテントハンドラーはスキルのハンドラーの中で最も複雑です。詳細については、後述するインテントとスロットを処理するを参照してください。
リクエストを処理して応答を返す
スキルが受け取ったリクエストを特定のリクエストハンドラーにルーティングして以降は、ハンドラーがリクエストを処理して応答を返します。リクエストハンドラーのコードには、以下のようにスキルが実行する必要のある機能を記述します。
- ユーザーリクエストに含まれるデータを分析して、適切な応答を決定します。
- ダイアログのやり取りの一環としてユーザーに追加の情報をリクエストします。
- 別のAlexa APIを呼び出して、ユーザーに関する情報を取得したり、他の関数を実行したりします。以下はその例です。
- Alexa設定APIを呼び出して、ユーザーのタイムゾーンやその他の設定を取得します。
- Device Settings APIを呼び出して、デバイスに設定された住所を取得します。
- リマインダーAPIを呼び出して、ユーザーのリマインダーの作成や更新を行います。
- その他のAPIを呼び出して、リクエストを実行するための情報を取得します。たとえば、ハンドラーで天気予報サービスAPIを呼び出して予報を取得したり、フライト追跡APIを呼び出してフライトを検索したりできます。
ほとんどのハンドラーは、Alexaがユーザーに読み上げるテキストを返します。一部のハンドラーでは、より特殊な応答を返す必要があります。たとえば、CanFulfillIntentRequest
のハンドラーでは、スキルがユーザーのリクエストを受け入れられるかどうかを表すのに、独自のリクエスト形式を返す必要があります。ハンドラーで完了していないダイアログを処理する場合は、Dialog.Delegate
ディレクティブを返してAlexaにダイアログをデリゲートできます。
Alexa Skills Kit SDKを使っている場合、handle
メソッドを実装してリクエストを処理し、結果を返します。handle
メソッドはHandlerInput
オブジェクトを受け取ります。このオブジェクトには、スキルに送信された完全なJSONリクエストが含まれています。このオブジェクトを使って、リクエストのデータにアクセスします。
この例は、Alexaにシンプルなようこそメッセージを読み上げさせ、Alexaアプリにシンプルなカードを表示するハンドラーです。
このサンプルコードはAlexa Skills Kit SDK for Node.js (v2)を使用しています。
HandlerInput.responseBuilder
メソッドはResponseBuilder
オブジェクトを返します。このオブジェクトのメソッドを使って、テキスト読み上げとカード表示用の応答を作成します。
const LaunchRequestHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'LaunchRequest';
},
handle(handlerInput) {
const speechText = 'ようこそ、アレクサスキルキットへ。こんにちは、と言ってみてください。';
return handlerInput.responseBuilder
.speak(speechText)
.reprompt(speechText)
.withSimpleCard('ハローワールド', speechText)
.getResponse();
},
};
このサンプルコードはAlexa Skills Kit SDK for Pythonを使用しています。
handler_input.response_builder
メソッドは、ResponseFactory
オブジェクトを返します。このオブジェクトのメソッドを使って、テキスト読み上げとカード表示用の応答を作成します。
from ask_sdk_core.skill_builder import SkillBuilder
from ask_sdk_core.dispatch_components import AbstractRequestHandler
from ask_sdk_core.utils import is_request_type
from ask_sdk_core.handler_input import HandlerInput
from ask_sdk_model.ui import SimpleCard
from ask_sdk_model import Response
class LaunchRequestHandler(AbstractRequestHandler):
"""スキルを起動するハンドラーです。"""
def can_handle(self, handler_input):
# type: (HandlerInput) -> bool
return is_request_type("LaunchRequest")(handler_input)
def handle(self, handler_input):
# type: (HandlerInput) -> Response
speech_text = "ようこそ、アレクサスキルキットへ。こんにちは、と言ってみてください。"
handler_input.response_builder.speak(speech_text).ask(
"続けて、こんにちは、と言ってみてください。").set_card(
SimpleCard("ハローワールド", speech_text))
return handler_input.response_builder.response
このサンプルコードはAlexa Skills Kit SDK for Javaを使用しています。
HandlerInput.getResponseBuilder()
メソッドは、ResponseBuilder
オブジェクトを返します。このオブジェクトのメソッドを使って、テキスト読み上げとカード表示用の応答を作成します。
package handlers;
import com.amazon.ask.dispatcher.request.handler.HandlerInput;
import com.amazon.ask.dispatcher.request.handler.RequestHandler;
import com.amazon.ask.model.LaunchRequest;
import com.amazon.ask.model.Response;
import java.util.Optional;
import static com.amazon.ask.request.Predicates.requestType;
public class LaunchRequestHandler implements RequestHandler {
@Override
public boolean canHandle(HandlerInput input) {
return input.matches(requestType(LaunchRequest.class));
}
@Override
public Optional<Response> handle(HandlerInput input) {
String speechText = "ようこそ、アレクサスキルキットへ。こんにちは、と言ってみてください。";
return input.getResponseBuilder()
.withSpeech(speechText)
.withReprompt("続けて、こんにちは、と言ってみてください。")
.build();
}
}
スキルが返すJSONのresponse.outputSpeech
プロパティには、Alexaが読み上げるテキストが指定されています。
{
"response": {
"outputSpeech": {
"type": "SSML",
"ssml": "<speak>ようこそ、アレクサスキルキットへ。こんにちは、と言ってみてください。</speak>"
},
"reprompt": {
"outputSpeech": {
"type": "SSML",
"ssml": "<speak>続けて、こんにちは、と言ってみてください。</speak>"
}
},
"shouldEndSession": false
},
"version": "1.0",
"sessionAttributes": {}
}
応答の返し方の詳細については、後述する応答を返すを参照してください。
インテントとスロットを処理する
インテントとは、ユーザーがしたいアクションのことです。インテントは、対話モデルを作成する際に定義します。ユーザーがインテントを呼び出すと、スキルに次の情報が指定されたIntentRequest
を含むリクエストが送信されます。
{
"version": "1.0",
"request": {
"type": "IntentRequest",
"requestId": "amzn1.echo-api.request.1",
"timestamp": "2019-03-21T22:32:09Z",
"locale": "ja-JP",
"intent": {
"name": "HelloWorldWithNameIntent",
"confirmationStatus": "NONE",
"slots": {
"firstName": {
"name": "firstName",
"value": "恵美",
"confirmationStatus": "NONE"
},
"favoriteColor": {
"name": "favoriteColor",
"confirmationStatus": "NONE"
}
}
},
"dialogState": "STARTED"
},
"session": {},
"context": {}
}
スキルでは対話モデルに定義済みの想定されるインテントをすべて処理する必要があります。複数のインテントで使用されるハンドラーを作成することもできますが、通常は各インテントのハンドラーでこの処理を行います。前述のハンドラーが処理できるリクエストを判断するを参照してください。
詳細については、以下のセクションを参照してください。
リクエストからスロット値を取得する
インテントには1つ以上のスロットを含めることができます。スロットを使うと、都市名や特定の日付といった、可変情報をユーザーから取得できます。スロット値は通常、ユーザーのリクエストを実行するのに必要な重要な情報です。たとえば、PlanMyTrip
インテントは、ユーザーの出発地、目的地、出発日がわからなければフライトを検索できません。
対話モデルでインテントに定義したすべてのスロットは、IntentRequest
のrequest.intent.slots
プロパティに含まれます。slots
プロパティは、キーと値のペアのマップです。キーがスロット名、値がSlot
オブジェクトとなります。リクエストのSlot
オブジェクトには、少なくともname
プロパティが含まれます。ユーザーからスロット値が提供されない場合、value
プロパティは含まれません。
Alexa Skills Kit SDKには、より直接的にスロット値を取得できるユーティリティ関数やヘルパー関数が提供されています。以下の例を参照してください。
このサンプルコードはAlexa Skills Kit SDK for Node.js (v2)を使用しています。
このハンドラーでは、RequestEnvelopeUtils
で提供される関数を使ってスロット値を取得しています。スロットには、リクエストのintent
オブジェクトからアクセスすることもできます。値以外のデータも取得する必要がある場合は、この方法が便利です。
const {
getRequestType,
getIntentName,
getSlotValue,
getDialogState,
} = require('ask-sdk-core');
const HelloWorldWithNameIntentHandler = {
canHandle(handlerInput) {
return getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
&& getIntentName(handlerInput.requestEnvelope) === 'HelloWorldWithNameIntent';
},
handle(handlerInput) {
const firstNameValue = getSlotValue(handlerInput.requestEnvelope, 'firstName');
const speechText = firstNameValue
? `こんにちは、${firstNameValue}さん。`
: `こんにちは、 すみません、まだお名前を聞いていませんでした。`;
return handlerInput.responseBuilder
.speak(speechText)
.getResponse();
}
};
このサンプルコードはAlexa Skills Kit SDK for Pythonを使用しています。
このハンドラーは、get_slot_value
メソッドを使ってスロット値を取得しています。値以外のデータも取得する必要がある場合は、get_slot
メソッドを使用することもできます。
from ask_sdk_core.dispatch_components import AbstractRequestHandler
from ask_sdk_core.utils import is_intent_name, get_slot_value
from ask_sdk_core.handler_input import HandlerInput
from ask_sdk_model import Response
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
# このハンドラーは、ユーザー名が提供された場合に名前を呼びかけてあいさつします
# 名前が提供されない場合は、一般的なHello World応答のみを返します
first_name_value = get_slot_value(
handler_input=handler_input, slot_name="firstName")
if first_name_value:
speech_text = "こんにちは、{}さん。".format(first_name_value)
else:
speech_text = "こんにちは、 すみません、まだお名前を聞いていませんでした。"
return handler_input.response_builder.speak(speech_text).response
このサンプルコードはAlexa Skills Kit SDK for Javaを使用しています。
このハンドラーは、RequestHelper
メソッドを使ってスロット値を取得しています。スロットには、リクエストのintent
オブジェクトからアクセスすることもできます。値以外のデータもスロットから取得する必要がある場合は、この方法が便利です。
package handlers;
import static com.amazon.ask.request.Predicates.intentName;
import java.util.Optional;
import com.amazon.ask.dispatcher.request.handler.HandlerInput;
import com.amazon.ask.dispatcher.request.handler.impl.IntentRequestHandler;
import com.amazon.ask.model.IntentRequest;
import com.amazon.ask.model.Response;
import com.amazon.ask.request.RequestHelper;
public class HelloWorldWithNameIntentHandler implements IntentRequestHandler {
// このハンドラーではIntentRequestリクエストの処理だけを行うため、
// 汎用インターフェース(RequestHandler)の代わりにタイプを指定した
// IntentRequestHandlerインターフェースを実装します。これにより、
// リクエストを取得して、タイプを確認し、正しいタイプに割り付ける必要がなくなります。
@Override
public boolean canHandle(HandlerInput handlerInput, IntentRequest intentRequest) {
return handlerInput.matches(intentName("HelloWorldWithNameIntent"));
}
@Override
public Optional<Response> handle(HandlerInput handlerInput, IntentRequest intentRequest) {
// このハンドラーはユーザー名が提供された場合に名前を呼びかけてあいさつしますが、
// 名前が提供されない場合は一般的なハローワールド応答のみを返します
RequestHelper requestHelper = RequestHelper.forHandlerInput(handlerInput);
// ヘルパーメソッドを使って、Optionalにラップされたスロット値を取得します。
Optional<String> firstNameValue = requestHelper.getSlotValue("firstName");
// Optional.map()メソッドを使い、スロットに値が含まれているかどうかに基づいて
// 別の応答を作成します。
String speechText = firstNameValue.map(firstName -> "こんにちは、" + firstName + "さん。")
.orElse("こんにちは、 すみません、まだお名前を聞いていませんでした。");
return handlerInput.getResponseBuilder()
.withSpeech(speechText)
.build();
}
}
以下の例は、2つのスロットを持つリクエストのJSONコードです。firstName
スロットにはvalue
プロパティがありますが、favoriteColor
スロットにはありません。この例では、ユーザーがfirstName
スロットの値のみを提供し、favoriteColor
スロットの値は提供していません。
{
"version": "1.0",
"session": {},
"context": {},
"request": {
"type": "IntentRequest",
"requestId": "amzn1.echo-api.request.e8d12d6f-bb5b-48f2-8899-7cbbd764bf26",
"timestamp": "2019-02-07T21:21:06Z",
"locale": "ja-JP",
"intent": {
"name": "HelloWithNameIntent",
"slots": {
"firstName": {
"name": "firstName",
"value": "早紀",
"confirmationStatus": "NONE"
},
"favoriteColor": {
"name": "favoriteColor",
"confirmationStatus": "NONE"
}
},
"confirmationStatus": "NONE"
}
}
}
スロット値の解決を使用する
エンティティ解決をサポートするスロットの場合、Slot
オブジェクトには Resolutions
オブジェクトが含まれます。このオブジェクトには、スロットのエンティティ解決の結果が含まれます。カスタムスロットタイプ値に同義語と一意のIDが含まれる場合、エンティティ解決の結果は非常に有効です。複数の同義語を1つのIDにマッピングして、コード内で列挙値として活用できるためです。
詳細については、標準スロットタイプのエンティティ解決を参照してください。
ダイアログを使ってスロット値を収集する
ユーザーが1回の発話で必須のスロット値をすべて提供してくれることはほとんどありません。そのため、ダイアログのやり取りを何度か行って、ユーザーから追加の値を引き出す必要があります。その1つの方法がダイアログモデルを設定してから、ダイアログをAlexaにデリゲートすることです。これによりコードがシンプルになります。インテントハンドラーが、必須スロット値に有効な値が入っていることを確認する必要がなくなるためです。
ダイアログのデリゲートの例と詳細は、Alexaにダイアログをデリゲートするを参照してください。
プログレッシブ応答を送信する
ユーザーのリクエストに完全な応答を返す前に、スキルでAlexa APIを呼び出し、割り込みのSSMLコンテンツ(読み上げファイルや短い音声ファイルなど)を送ることができます。スキルが完全な応答を作成する前に集中処理を行う必要があるときに便利です。
たとえば、次のような対話が考えられます。
ユーザー: アレクサ、タクシー予約で空港までの配車を予約して。 (通常のIntentRequest
がタクシー予約スキルに送られます)
このインテントを満たすのに必要なすべての情報を収集するための以後のやりとり
Alexa: わかりました。配車の詳細を調べるまでお待ちください...(スキルが完全な応答を準備する間のプログレッシブ応答です。)
Alexa: お待たせしました。配車を予約しました。30分以内にご自宅に到着します。 (IntentRequest
への通常の応答)
プログレッシブ応答によって、完全な応答を待つまでの間、ユーザーを飽きさせずにいることができます。
プログレッシブ応答を送るには、プログレッシブ応答APIを呼び出して、SSMLでdirective
を送るか、Alexaがユーザーに話すプレーンテキストを送ります。完全な応答を返す前に、プログレッシブ応答APIに最大5つまでディレクティブを送信できます。
プログレッシブ応答APIを使うと、ハンドラーがプログレッシブ応答のAPI呼び出しを実行します。この場合でも、ほかのリクエストと同様、Alexaに最終的な応答を返す必要はあります。
詳細については、ユーザーにプログレッシブ応答を送信するを参照してください。
応答を返す
特定のリクエストのハンドラーが処理を完了したら、ハンドラーはAlexaに応答を送信します。応答は、Alexaに実行内容を指示するJSON構造です。LaunchRequest
とIntentRequest
タイプのリクエストへの応答には通常、少なくともAlexaが音声に変換してユーザーに読み上げるテキスト文字列が含まれます。これ以外の情報を応答として送信するスキルもあります。たとえば、Alexaアプリに表示するカード、オーディオを再生したり、画面付きデバイスに情報を表示したりするディレクティブなどです。リクエストのタイプによっては、特定の種類の応答しか送信できないものもあります。
Alexa Skills Kit SDKを使用している場合は、handle
メソッドがスキルの応答を返します。SDKには、ResponseBuilder
オブジェクトやhelperメソッドが提供されているため、JSON形式の応答を簡単に作成できます。JSON形式の応答の詳細については、応答の形式を参照してください。
音声とカードで応答する
シンプルな応答には音声に変換されるテキストだけが含まれる場合と、Alexaアプリに表示するカードも含まれる場合があります。これらの情報は、応答のoutputSpeech
プロパティとcard
プロパティに指定します。
このサンプルコードはAlexa Skills Kit SDK for Node.js (v2)を使用しています。
ResponseBuilder
オブジェクトのspeak
メソッドとwithSimpleCard
メソッドを使って、読み上げとカードのテキストを定義します。getResponse()
メソッドは、指定されたプロパティを含む応答を返します。
return handlerInput.responseBuilder
.speak("これはAlexaが読み上げるテキストです。Alexaアプリを開いてカードを見てください。")
.withSimpleCard(
"これはカードのタイトルです。",
"これはカードの内容です。このカードはプレーンテキストのコンテンツです。\r\nコンテンツが読みやすいように改行を使用して調整します。")
.getResponse();
このサンプルコードはAlexa Skills Kit SDK for Pythonを使用しています。
ResponseFactory
オブジェクトのspeak
メソッドとset_card
メソッドを使って、応答に含める読み上げとカードのテキストを定義します。
from ask_sdk_core.dispatch_components import AbstractRequestHandler
from ask_sdk_core.utils import is_intent_name
from ask_sdk_core.handler_input import HandlerInput
from ask_sdk_model import Response
from ask_sdk_model.ui import SimpleCard
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
speech_text = "これはAlexaが読み上げるテキストです。Alexaアプリを開いてカードを見てください。"
card_title = "これはカードのタイトルです"
card_text = “これはカードの内容です。このカードにはプレーンテキストのみ含まれます。\r\nコンテンツが読みやすいように改行を使用して調整します。"
return handler_input.response_builder.speak(speech_text).set_card(
SimpleCard(card_title, card_text)).response
このサンプルコードはAlexa Skills Kit SDK for Javaを使用しています。
ResponseBuilder
オブジェクトのwithSpeech()
メソッドとwithSimpleCard
メソッドを使って、読み上げとカードのテキストを定義します。build()
メソッドは、指定されたプロパティを含む応答を返します。
@Override
public Optional<Response> handle(HandlerInput handlerInput, IntentRequest intentRequest) {
String speechText ="これはAlexaが読み上げるテキストです。Alexaアプリを開いてカードを見てください。";
String cardTitle = "これはカードのタイトルです";
String cardText = “これはカードの内容です。このカードにはプレーンテキストのみ含まれます。\r\nコンテンツが読みやすいように改行を使用して調整します。";
return handlerInput.getResponseBuilder()
.withSpeech(speechText)
.withSimpleCard(cardTitle, cardText)
.build();
}
このJSON応答は、シンプルなプレーンテキストのoutputSpeech
文字列を返す方法の例です。この応答には、タイトルとシンプルなコンテンツを表示するカードも含まれています。
{
"version": "1.0",
"response": {
"outputSpeech": {
"type": "PlainText",
"text": "これはAlexaが読み上げるテキストです。Alexaアプリを開いてカードを見てください。"
},
"card": {
"type": "Simple",
"title": "これはカードのタイトルです",
"content": “これはカードの内容です。このカードにはプレーンテキストのみ含まれます。\r\nコンテンツが読みやすいように改行を使用して調整します。"
}
}
}
outputSpeech
は、プレーンテキストか音声合成マークアップ言語(SSML)のいずれかの形式で指定できます。SSMLは、合成音声の生成用にテキストをマークアップする標準的な手段を提供するマークアップ言語です。SSMLを使うと、短い音響効果を再生させたり、話し言葉を強調したり、発音を変えたりといった効果をAlexa音声に加えることができます。Alexa Skills Kitでサポートされているのは、SSML仕様で定義されているタグのサブセットです。
outputSpeech
でSSMLを使う場合、SSML
プロパティにテキストを指定し、type
をSSML
に設定し、SSMLマークアップを使ったテキストを<speak>
タグで囲みます。ヘルパーメソッドを使ってoutputSpeech
を設定すると、上記の処理はAlexa Skills Kit SDKで自動的に行われます。
SSMLアトリビュートを囲む際、二重引用符("
)を必ずエスケープするようにしてください。
このサンプルコードはAlexa Skills Kit SDK for Node.js (v2)を使用しています。
この例では、speechText
文字列にSSMLタグが含まれています。これにより、「こんにちは」という単語が強調され、「うふふ」というSpeechconが使われます。 speak
メソッドは自動的に文字列を<speak>
タグで囲み、ssml
プロパティを設定して、type
をSSML
にセットします。
const HelloSSMLResponseIntent = {
canHandle(handlerInput){
return handlerInput.requestEnvelope.request.type === 'IntentRequest'
&& handlerInput.requestEnvelope.request.intent.name === 'HelloSSMLResponseIntent';
},
handle(handlerInput){
const speechText =
"<emphasis level=\"strong\">こんにちは</emphasis>、皆さん" +
"<say-as interpret-as=\"interjection\">イェイ</say-as>";
return handlerInput.responseBuilder
.speak(speechText)
.withSimpleCard("ハローワールド!", "ハローワールド(気持ちを込めて)")
.getResponse();
}
}
このサンプルコードはAlexa Skills Kit SDK for Pythonを使用しています。
この例では、speech_text
文字列にSSMLタグが含まれています。これにより、「こんにちは」という単語が強調され、「うふふ」というSpeechconが使われます。 speak
メソッドは自動的に文字列を<speak>
タグで囲み、ssml
プロパティを設定して、type
をSSML
にセットします。
from ask_sdk_core.dispatch_components import AbstractRequestHandler
from ask_sdk_core.utils import is_intent_name
from ask_sdk_core.handler_input import HandlerInput
from ask_sdk_model import Response
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
speech_text = ('<emphasis level="strong">こんにちは</emphasis>、皆さん'
'<say-as interpret-as="interjection">うふふ</say-as>')
return handler_input.response_builder.speak(speech_text).response
このサンプルコードはAlexa Skills Kit SDK for Javaを使用しています。
この例では、speechText
文字列にSSMLタグが含まれています。これにより、「こんにちは」という単語が強調され、「うふふ」というSpeechconが使われます。 withSpeech
メソッドは自動的に文字列を<speak>
タグで囲み、ssml
プロパティを設定して、type
をSSML
にセットします。
@Override
public Optional<Response> handle(HandlerInput handlerInput) {
String speechText =
"<emphasis level=\"strong\">こんにちは</emphasis>、皆さん" +
"<say-as interpret-as=\"interjection\">うふふ</say-as>";
return handlerInput.getResponseBuilder()
.withSpeech(speechText)
.build();
}
このJSON応答では、outputSpeech
文字列で提供されるテキストにSSMLタグが含まれています。これにより、「こんにちは」という単語が強調され、「うふふ」というSpeechconが使われます。 SSMLが正しくレンダリングできるようにするには、outputSpeech
のtype
プロパティをSSML
に設定し、読み上げテキストをssml
プロパティで定義して<speak>
タグで囲む必要があります。
{
"version": "1.0",
"response": {
"outputSpeech": {
"type": "SSML",
"ssml": "<speak><emphasis level=\"strong\">こんにちは</emphasis>、皆さん。<say-as interpret-as=\"interjection\">イェイ</say-as>!</speak>"
},
"card": {
"type": "Simple",
"title": "ハローワールド",
"content": "ハローワールド(気持ちを込めて)"
}
}
}
ユーザーの応答を聞き、必要に応じて再プロンプトを出す
スキルが応答を返せば対話が終わるというスキルはほとんどありません。たいていは、スキルの応答でユーザーに何かを言うよう促し、スキルに送る新しいリクエストを引き出す必要があります。また、LaunchRequest
の応答で簡単なようこそメッセージを読み上げ、ユーザーにしたいことをたずねるという方法も一般的です。例:
ユーザー: 宇宙博士を開いて。
Alexa: 宇宙博士へようこそ。私は、ある惑星からほかの惑星まで旅するのにかかる時間から、宇宙についてのジョークまで、宇宙に関するいろいろなことを知っています。何を知りたいですか?
Alexaはユーザーの応答を待機中です。
ユーザー: 火星の天気を教えて。 (この応答はGetPlanetWeather
インテントにマッピングされます。)
Alexa: 火星の最高気温は摂氏21度、最低気温は摂氏マイナス126度です。晴れですが、午後ところにより砂嵐となるでしょう。 (GetPlanetWeather
インテントからの応答です。)
Alexaにユーザーの応答を待機させるには、以下の手順を実行します。
shouldEndSession
プロパティをfalse
に設定します。- 応答の
reprompt
プロパティに読み上げる内容またはオーディオを指定します。Alexaは、ユーザーの応答が理解できないときにこのテキストを読み上げるか、指定されたオーディオを再生します。- 読み上げるテキストを指定するには、
reprompt
オブジェクトのoutputSpeech
プロパティを使用します。 - APL for audioで作成されたオーディオを指定するには、
reprompt
オブジェクト内のディレクティブ配列でAlexa.Presentation.APLA.RenderDocument
ディレクティブ
を渡します。 outputSpeech
とディレクティブの両方を指定できます。Alexaが最初に音声で話し、次にオーディオを再生します。
- 読み上げるテキストを指定するには、
これは、Alexaにユーザーの応答を聞くように指示する際によくある手順です。
- Alexaは、スキルに
IntentRequest
、LaunchRequest
のいずれかを送信します。 - スキルは次のように応答します。
outputSpeech
オブジェクトのテキストを読み上げます。shouldEndSession
をfalse
にセットします。reprompt
オブジェクトのテキストまたはオーディオで再度ユーザーに問いかけます。
- Alexaは、
outputSpeech
に指定したテキストを読み上げてから、オーディオストリームを開いて、ユーザーの応答を数秒間待機します。デバイスによっては、このとき青いライトリングが点灯します。 - この後の状況としては以下のいずれかが考えられます。
- ユーザーが対話モデルに一致する何かを答えます。たとえば、インテントのいずれかにマッピングされている発話などです。この場合、Alexaはスキルにユーザーの応答を表す新しいリクエスト(新しい
IntentRequest
など)を送信します。再びステップ1からプロセスが開始します。 - ユーザーが何も言わないことも考えられます。この場合、Alexaは
reprompt
に定義したテキストまたはオーディオでユーザーに問いかけて、ストリームを再度開き、数秒間ユーザーの応答を待機します。 - ユーザーが対話モデルに一致しないことを言う場合も考えられます。この場合も、Alexaは
reprompt
に定義したテキストでユーザーに問いかけて、ストリームを再度開き、数秒間ユーザーの応答を待機します。
- ユーザーが対話モデルに一致する何かを答えます。たとえば、インテントのいずれかにマッピングされている発話などです。この場合、Alexaはスキルにユーザーの応答を表す新しいリクエスト(新しい
Alexaが再プロンプトを読み上げるのは1回だけです。それでもユーザーの応答が理解できない場合、対話全体が終了します。
ユーザー: 宇宙博士を開いて。
Alexa: 宇宙博士へようこそ。私は、ある惑星からほかの惑星まで旅するのにかかる時間から、宇宙についてのジョークまで、宇宙に関するいろいろなことを知っています。何を知りたいですか?
ユーザー: …(ユーザーが何かを言います。)
Alexa: 宇宙について私に何か聞いてください。
ユーザー: えーっと…
対話が終了します。
このサンプルコードはAlexa Skills Kit SDK for Node.js (v2)を使用しています。
ResponseBuilder
オブジェクトのreprompt
メソッドは再プロンプトのテキストを設定し、shouldEndSession
をfalse
にセットします。これにより、Alexaにユーザーの応答を待機するよう指示します。
const HelloWorldIntentHandler = {
canHandle(handlerInput) {
return getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
&& getIntentName(handlerInput.requestEnvelope) === 'HelloWorldIntent';
},
handle(handlerInput) {
const speechText = `宇宙博士へようこそ。私は、
ある惑星からほかの惑星まで旅するのにかかる時間から、宇宙についてのジョークまで、
宇宙に関するいろいろなことを知っています。何を知りたいですか?`;
const repromptText = '宇宙について私に何か聞いてください。';
return handlerInput.responseBuilder
.speak(speechText)
.reprompt(repromptText)
.getResponse();
}
};
このサンプルコードはAlexa Skills Kit SDK for Pythonを使用しています。
ResponseFactory
オブジェクトのask
メソッドは再プロンプトのテキストを設定し、shouldEndSession
をfalse
にセットします。これにより、Alexaにユーザーの応答を待機するよう指示します。
from ask_sdk_core.dispatch_components import AbstractRequestHandler
from ask_sdk_core.utils import is_intent_name
from ask_sdk_core.handler_input import HandlerInput
from ask_sdk_model import Response
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
speech_text = ("宇宙博士へようこそ。私は、"
"ある惑星からほかの惑星まで旅するのにかかる時間から、宇宙についてのジョークまで、"
"宇宙に関するいろいろなことを知っています。何を知りたいですか?")
reprompt_text = "宇宙について私に何か聞いてください。"
return handler_input.response_builder.speak(speech_text).ask(
reprompt_text).response
このサンプルコードはAlexa Skills Kit SDK for Javaを使用しています。
ResponseBuilder
オブジェクトのwithReprompt
メソッドは再プロンプトのテキストを設定し、shouldEndSession
をfalse
にセットします。これにより、Alexaにユーザーの応答を待機するよう指示します。
@Override
public Optional<Response> handle(HandlerInput handlerInput) {
String speechText = "宇宙博士へようこそ。私は、" +
"ある惑星からほかの惑星まで旅するのにかかる時間から、宇宙についてのジョークまで、" +
"宇宙に関するいろいろなことを知っています。何を知りたいですか?";
String repromptText = "宇宙について私に何か聞いてください。";
return handlerInput.getResponseBuilder()
.withSpeech(speechText)
.withReprompt(repromptText)
.build();
}
JSONでは、reprompt
プロパティに再プロンプトのテキストを設定し、shouldEndSession
をfalse
にセットします。これにより、Alexaにユーザーの応答を待機するよう指示します。
{
"version": "1.0",
"response": {
"outputSpeech": {
"type": "SSML",
"ssml": "<speak>宇宙博士へようこそ。私は、ある惑星からほかの惑星まで旅するのにかかる時間から、宇宙についてのジョークまで、宇宙に関するいろいろなことを知っています。何を知りたいですか?</speak>"
},
"card": {
"type": "Simple",
"title": "宇宙博士のサンプル",
"content": "宇宙博士へようこそ。私は、ある惑星からほかの惑星まで旅するのにかかる時間から、宇宙についてのジョークまで、宇宙に関するいろいろなことを知っています。何を知りたいですか?"
},
"reprompt": {
"outputSpeech": {
"type": "SSML",
"ssml": "<speak>宇宙について私に何か聞いてください。</speak>"
}
},
"shouldEndSession": false
}
}
この会話のやり取りの間、スキルセッションは開いたままです。セッションが閉じると、対話は終了します。セッションの詳細については、スキルセッションとセッションアトリビュートの管理を参照してください。
ディレクティブを使って応答する
応答にディレクティブを含めることで、Alexaにほかのアクションを実行させることができます。ディレクティブはインターフェースに含まれています。たとえば、AudioPlayer
インターフェースにはオーディオファイルの再生開始と停止のディレクティブが含まれています。ディレクティブの種類によって必要なプロパティが異なります。
ディレクティブを送信すると、スキルはインターフェース固有のリクエストを受け取ることができます。たとえば、AudioPlayer.Play
を使ってオーディオの再生を開始すると、スキルはスキルに再生が開始したことを知らせるAudioPlayer.PlaybackStarted
リクエストを受け取ります。
使用できるディレクティブと対応する応答の詳細については、関連するインターフェースのリファレンスを参照してください。
- Alexa.Presentation.APLインターフェース
- Alexa.Presentation.APLTインターフェース
- Alexa.Presentation.HTMLインターフェースのリファレンス
- AudioPlayerインターフェース
- Connectionsインターフェース
- Dialogインターフェース
- Messagingインターフェース
- PlaybackControllerインターフェース
- VideoAppインターフェース
応答にディレクティブを含めるには、応答のdirectives
配列にディレクティブを追加します。Alexa Skills Kit SDKには、応答にディレクティブを追加するヘルパーメソッドが用意されています。
入力エラーを処理する
音声インターフェースでは、ユーザーの無効なデータの入力を回避することができません。また、ユーザーの言葉を間違って解釈して、スロット値に誤りが生じる可能性もあります。こうしたエラーがないかをチェックし、適切に処理するコードを作成する必要があります。
カスタムスロットタイプの値
カスタムスロットタイプとして定義されたスロットが、そのタイプで定義された値のリストに含まれない値を受け取る場合があります。
たとえば、カスタムLIST_OF_SIGNS
スロットタイプで12個の星座を定義したとします。ユーザーが「青色のホロスコープを教えて」と言った場合、AlexaはSign
スロットに「青色」という単語が与えられたリクエストをサービスに送信します。この場合、Sign
の値を検証し、有効な星座ではないことをユーザーに伝えるコードが必要となります。
カスタムスロットタイプが列挙値として特定のリストに含まれる値だけを返すようにしたい場合は、スロットに検証ルールを設定してからダイアログをデリゲートします。これにより、Alexaはユーザーが無効な値を提供するとプロンプトを出すようになります。
空のスロット値
インテントに必須のスロット値がいくつかある場合、1回の発話ですべてをたずねるのではなく、何回かに分けてユーザーから情報を引き出すことができます。たとえば、PlanMyTrip
インテントには、fromCity
、toCity
、travelDate
というスロットがあります。3つすべてを1回の発話で求めるのは無理があるため、次のように発話を定義します。
{
"samples": [
"{travelDate}に{fromCity}から{toCity}に行きたい",
"{toCity}に行きたい",
"{travelDate}に旅行に行きます",
"{fromCity}からの旅行を計画します"
]
}
ユーザーが「札幌に行きたい」と言った場合、3つのスロットはすべてスキルに送られるIntentRequest
に含まれますが、値はtoCity
にしか入っていません。fromCity
とtravelDate
はいずれも空です。
{
"request": {
"type": "IntentRequest",
"requestId": "amzn1.echo-api.request.1",
"timestamp": "2019-03-23T00:34:14Z",
"locale": "ja-JP",
"intent": {
"name": "トリッププラン",
"confirmationStatus": "NONE",
"slots": {
"toCity": {
"name": "toCity",
"value": "札幌",
"confirmationStatus": "NONE"
},
"travelDate": {
"name": "travelDate",
"confirmationStatus": "NONE"
},
"fromCity": {
"name": "fromCity",
"confirmationStatus": "NONE"
}
}
},
"dialogState": "STARTED"
},
"version": "1.0",
"session": {},
"context": {}
}
このため、それらの値を使う前にスロットに値が入っているかどうかを確認します。リクエストからスロット値を取得するで説明した例では、値がnullではないことを確認しています。
または、Dialog
ディレクティブを使用して、次のようにスロット値を簡単に収集することもできます。Alexaにダイアログをデリゲートするを参照してください。
関連トピック
最終更新日: 2024 年 07 月 01 日