スキルセッションとセッションアトリビュートの管理



スキルセッションとセッションアトリビュートの管理

ユーザーとの対話のやり取りを行う間、スキルではスキルセッションを開いたままにしておくことができます。セッションが開いている間、ユーザーは呼び出し名を使わなくてもスキルに話しかけることができます。

スキルセッションのライフサイクル

  1. スキルセッションは、ユーザーがスキルを呼び出してAlexaがスキルにリクエストを送信すると開始します。リクエストにはsessionオブジェクトが含まれます。このオブジェクトには、新規のセッションを表すnewというブール値が入ります。

  2. スキルはリクエストを受け取り、Alexaがユーザーに答える応答を返します。

  3. 次に行われる処理は、スキルが返す応答shouldEndSessionパラメーターに入る値によって異なります。

    • true – セッションを終了します。それ以降は、スキルではなくAlexaがユーザーの発話を処理します。ユーザーが再度スキルを呼び出すと、Alexaは新規のセッションを作成します(つまり、ステップ1に戻ります)。
    • false – セッションは開いたままです。Alexaはマイクをオンにしてユーザーの応答を待ちます。ユーザーの応答が定義済みの対話モデルにマッピングされると、新しいインテントがスキルに送信されます(ステップ2に戻ります)。

      ただし、数秒待ってもユーザーからの応答がない場合、Alexaはマイクをオフにします。スキルが再プロンプトを指定した場合、Alexaは再度ユーザーに話しかけ、さらに数秒間マイクをオンにします。それでもユーザーが応答しない場合、セッションは正常終了します。

      画面付きのデバイスでスキルを使う場合、マイクがオフになった後も、短時間セッションが開いたままになる場合があります。これについては、画面付きデバイスがスキルセッションに与える影響を参照してください。

      また、唯一の例外はスキル内課金の購入フローを開始するディレクティブです。この場合、shouldEndSessionの値にかかわらず、ディレクティブが自動でセッションを終了します。購入フローが完了した後にスキルを再開するには、永続ストレージを使う必要があります。スキルコードにISPのサポートを追加するを参照してください。

    • undefined(値がセットされていないかnull) – セッションは、ユーザーとの対話に使うEchoデバイスの種類や応答の内容によって異なる動きをします。画面付きデバイスがスキルセッションに与える影響How an Input Handler Affects the Skill Session Lifecycle(英語)を参照してください。

画面付きデバイスがスキルセッションに与える影響

ユーザーが画面付きデバイスを使ってスキルを呼び出した場合、マイクがオフになった後も最大30秒間は画面が開いたままになります。このため、ユーザーは引き続き画面に表示されるスキル関連のコンテンツを見ることができます。また、ユーザーは、Alexaに呼びかけるウェイクワードに続けてスキルの対話モデルにマッピングされる発話を行うことで、スキルとの対話を続けることができます。

このようなセッションの延長は、以下の条件を満たす場合のみ行われます。

  • ユーザーが画面付きデバイス(Echo Showなど)を使ってスキルを呼び出した場合。
  • スキルが画面付きデバイスに対応している場合。画面付きデバイスに対応するには、開発者コンソールの ビルド > インターフェースページで次のいずれかのオプションを有効にします。
    • Displayインターフェースまたは
    • Alexa Presentation Language
  • スキルの応答に画面に表示するコンテンツが含まれる場合。コンテンツは、次のいずれかになります。
    • Displayテンプレートは、スキルの応答にDisplay.RenderTemplateディレクティブを返すと表示されます。
    • Alexa Presentation Languageドキュメントは、スキルの応答にAlexa.Presentation.APL.RenderDocumentディレクティブを返すと表示されます。
    • Alexaアプリカードは、スキルの標準的な応答cardオブジェクトが含まれる場合に表示されます。カードは本来Alexaアプリ用ですが、画面に表示するコンテンツがほかにない場合はカードコンテンツが表示されます。
  • 応答のshouldEndSession値がfalseundefined(未設定)のいずれかの場合。
    • undefinedの場合、延長セッションが約30秒間開いたままになります。
    • falseの場合、ユーザーに再度話しかけても応答がなかったときに延長セッションが始まります。この場合、セッションは20~30秒間開いたままとなります。

たとえば、画面付きデバイスをサポートし、画面にコンテンツを表示するスキルでは、画面付きデバイスで次のような対話が行われる可能性があります。

ユーザー: 宇宙博士を開いて。
スキルはLaunchRequestを受け取ると、テキストを読み上げて応答し、shouldEndSessionfalseにセットします。

Alexa: 宇宙博士へようこそ。私は、ある惑星からほかの惑星まで旅するのにかかる時間から、宇宙についてのジョークまで、宇宙に関するいろいろなことを知っています。何を知りたいですか?
画面には宇宙博士に関連するコンテンツ(Displayディレクティブから送られたDisplayテンプレートなど)が表示されます。

ユーザー:(ユーザーは何も答えず、数秒が経過します。)
Alexa: 宇宙について私に何か聞いてください。 (Alexaは応答に指定されたrepromptを読み上げます。)
ユーザー: えーっと…
さらに時間が経過します。

マイクはオフになりますが、セッションは開いたままです。宇宙博士に関するコンテンツは引き続き表示されます。

ユーザー: アレクサ、火星について教えて。 (スキルセッションがまだ有効なため、ユーザーは呼び出し名を使わずにスキルと対話します。)
AlexaはスキルにPlanetFactsインテントを送信します。リクエストはこれが新しいセッションではなく、継続セッションであることを表しています。

Alexa: 火星では…

セッションデータの保存

セッションデータを保存する必要がある場合は、セッションアトリビュートを使います。保存するデータでキーと値のペアのマップを作成します。このマップを応答のsessionAttributesプロパティに含めます。Alexaが同じセッションの一部として次のリクエストを送信すると、リクエストのsession.attributesプロパティには同じマップが含まれます。定義したキーを使って、マップのデータを取得します。

たとえば以下のようにします。

…これ以前の対話が行われています。

ユーザー: お気に入りの色は青色です
Alexaは、favoriteColorスロットに「青色」をセットしてスキルにFavoriteColorIntentを送信します。スキルはテキストを読み上げて応答し、キーがfavoriteColorのセッションアトリビュートの値を「青色」にセットします。

Alexa: わかりました。お気に入りの色は青色ですね。
ユーザー: もう一度、何色か教えてくれる?
AlexaはスキルにWhatsMyColorIntentインテントを送信します。リクエストには、favoriteColorセッションアトリビュートが含まれます。スキルはこのアトリビュートを取得して応答を作成します。

Alexa: あなたのお気に入りの色は青色です。

Alexa Skills Kit SDKが提供するAttributesManagerを使えば、応答にセッションアトリビュートを追加して、受信リクエストからアトリビュートを取得できます。

以下は、インテントハンドラーがデータをセッションアトリビュートに保存する方法の例です。この例では、FavoriteColorIntentfavoriteColorという必須スロットが1つあります。インテントには自動デリゲートが設定されているため、ユーザーが最初に値を提供しなかった場合はAlexaがユーザーにスロットの値をたずねます。

クリップボードにコピーされました。

このサンプルコードはAlexa Skills Kit SDK for Node.js (v2)を使用しています。

const FavoriteColorIntentHandler = {
  canHandle(handlerInput) {
    getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
    && getIntentName(handlerInput.requestEnvelope) === 'FavoriteColorIntent'
    && getDialogState(handlerInput.requestEnvelope) === 'COMPLETED';
  },
  handle(handlerInput) {
    const sessionAttributes = handlerInput.attributesManager.getSessionAttributes();
    const favoriteColor = getSlotValue(handlerInput.requestEnvelope, 'favoriteColor')
    sessionAttributes.favoriteColor = favoriteColor;
    handlerInput.attributesManager.setSessionAttributes(sessionAttributes);
 
    const speechText = `セッションアトリビュートに値${favoriteColor}を保存しました。
    アトリビュートを取得しますのであなたのお気に入りの色を私に聞いてください。`;
    const repromptText = `私のお気に入りの色は何、と聞いてみてください。`;
 
    return handlerInput.responseBuilder
      .speak(speechText)
      .reprompt(repromptText)
      .getResponse();
  }
};

クリップボードにコピーされました。

このサンプルコードはAlexa Skills Kit SDK for Pythonを使用しています。

from ask_sdk_core.dispatch_components import AbstractRequestHandler
from ask_sdk_core.utils import is_intent_name, get_dialog_state, get_slot_value
from ask_sdk_core.handler_input import HandlerInput
from ask_sdk_model import Response, DialogState
 
 
class FavoriteColorIntentHandler(AbstractRequestHandler):
    """FavoriteColorIntentのハンドラー"""
    def can_handle(self, handler_input):
        # type: (HandlerInput) -> bool
        return is_intent_name("FavoriteColorIntent")(
            handler_input) and get_dialog_state(
            handler_input=handler_input) == DialogState.COMPLETED
 
    def handle(self, handler_input):
        # type: (HandlerInput) -> Response
 
        # 受信リクエストから既存のアトリビュートを取得します
        session_attr = handler_input.attributes_manager.session_attributes
 
        # Get the slot value from the request and add it to the リクエストからスロット値を取得し、セッション
        # アトリビュートディクショナリーに追加します。ダイアログモデルとダイアログの
        # デリゲートのため、このコードはfavoriteColorスロットに
        # 値が含まれているときにだけ実行されます。nullチェックは必要ありません。
        fav_color = get_slot_value("favoriteColor")
        session_attr["favoriteColor"] = fav_color
 
        # SDKは次のインテントで値を使用できるように、
        # アトリビュートをセッションに自動で保存します
 
        speech_text = ("{}の値をセッションアトリビュートに保存しました。"
                       "アトリビュートを取得しますので、あなたのお気に入りの色を"
                       "私に聞いてください。").format(fav_color)
 
        reprompt_text = "私のお気に入りの色は何、と聞いてみてください。"
 
        return handler_input.response_builder.speak(speech_text).ask(
            reprompt_text).response

クリップボードにコピーされました。

このサンプルコードはAlexa Skills Kit SDK for Javaを使用しています。

package handlers;

import static com.amazon.ask.request.Predicates.intentName;

import java.util.Map;
import java.util.Optional;

import com.amazon.ask.attributes.AttributesManager;
import com.amazon.ask.dispatcher.request.handler.HandlerInput;
import com.amazon.ask.dispatcher.request.handler.impl.IntentRequestHandler;
import com.amazon.ask.model.DialogState;
import com.amazon.ask.model.IntentRequest;
import com.amazon.ask.model.Response;
import com.amazon.ask.request.RequestHelper;

public class FavoriteColorIntentHandler implements IntentRequestHandler {
    @Override
    public boolean canHandle(HandlerInput handlerInput, IntentRequest intentRequest) {
        // このインテントには必須スロットと自動デリゲートが設定されているため、
        // ハンドラーは完了したダイアログを処理するだけです。
        return handlerInput.matches(intentName("FavoriteColorIntent"))
                && intentRequest.getDialogState() == DialogState.COMPLETED;        
    }

    @Override
    public Optional<Response> handle(HandlerInput handlerInput, IntentRequest intentRequest) {

        RequestHelper requestHelper = RequestHelper.forHandlerInput(handlerInput);

        // 受信リクエストから既存のアトリビュートを取得します
        AttributesManager attributesManager = handlerInput.getAttributesManager();
        Map<String,Object> attributes = attributesManager.getSessionAttributes();
        
        // リクエストからスロット値を取得し、アトリビュートのマップに追加します。
        // ダイアログモデルとダイアログのデリゲートのため、このコードは
        // favoriteColorスロットに値が含まれる場合にのみ実行されます。nullチェックは
        // 必要ありません。
        Optional<String> favoriteColor = requestHelper.getSlotValue("favoriteColor");                
        attributes.put("favoriteColor", favoriteColor.get());
        
        // これによりアトリビュートがセッションに保存されるので、値は
        // 次のインテントで使用できます。
        attributesManager.setSessionAttributes(attributes);

        // 応答に再プロンプトを含めて、自動で
        // shouldEndSessionをfalseにセットします。
        return handlerInput.getResponseBuilder()
                .withSpeech("値" + favoriteColor.get() + 
                    "をセッションアトリビュートに保存しました。アトリビュートを取得しますので、" +
                    "あなたのお気に入りの色を私に聞いてください。" )
                .withReprompt("私のお気に入りの色は何、と聞いてみてください。")
                .build();
    }
}

クリップボードにコピーされました。

以下は、FavoriteColorIntentHandlerが送信するJSON応答です。sessionAttributesオブジェクトにはfavoriteColorアトリビュートが含まれています。

{
  "version": "1.0",
  "sessionAttributes": {
    "favoriteColor": "青色"
  },
  "response": {
    "outputSpeech": {
      "type": "SSML",
      "ssml": "<speak>青色の値をセッションアトリビュートに保存しました。アトリビュートを取得しますのであなたのお気に入りの色を私に聞いてください。</speak>"
    },
    "reprompt": {
      "outputSpeech": {
        "type": "SSML",
        "ssml": "<speak>私のお気に入りの色は何、と聞いてみてください。</speak>"
      }
    },
    "shouldEndSession": false
  }
}

以下は、インテントハンドラーがセッションアトリビュートに保存されたデータにアクセスする方法の例です。この例のWhatsMyColorIntentにはスロットがありません。セッションアトリビュートに以前保存したデータを取得して、応答に使います。データがまだない場合(ユーザーがFavoriteColorIntentを呼び出す前にこのインテントを呼び出したため)、ハンドラーはDialog.ElicitSlotディレクティブを使ってFavoriteColorIntentを呼び出し、ユーザーに足りないデータについてたずねます。

クリップボードにコピーされました。

このサンプルコードはAlexa Skills Kit SDK for Node.js (v2)を使用しています。

const WhatsMyColorIntentHandler = {
  canHandle(handlerInput) {
    getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
    && getIntentName(handlerInput.requestEnvelope) === 'WhatsMyColorIntent';
  },
  handle(handlerInput) {
    const sessionAttributes = handlerInput.attributesManager.getSessionAttributes();
    if (sessionAttributes.favoriteColor) {
      return handlerInput.responseBuilder
        .speak(`あなたのお気に入りの色は${sessionAttributes.favoriteColor}です`)
        .getResponse();
    } else {
      return handlerInput.responseBuilder
        .speak('最初に、あなたのお気に入りの色を私に教えてください。')
        .reprompt('あなたのお気に入りの色は何ですか?')
        .addElicitSlotDirective('favoriteColor')
        .getResponse();
    }
  }
}

クリップボードにコピーされました。

このサンプルコードはAlexa Skills Kit SDK for Pythonを使用しています。

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, Intent
from ask_sdk_model.dialog import ElicitSlotDirective
 
 
class WhatsMyColorIntentHandler(AbstractRequestHandler):
    """WhatsMyColorIntentのハンドラー"""
    def can_handle(self, handler_input):
        # type: (HandlerInput) -> bool
        return is_intent_name("WhatsMyColorIntent")(handler_input)
 
    def handle(self, handler_input):
        # type: (HandlerInput) -> Response
 
        session_attr = handler_input.attributes_manager.session_attributes
 
        # ユーザーはお気に入りの色を設定する前にこのインテントを呼び出す可能性があります。
        # セッションアトリビュートをまずチェックしてください。
        if "favoriteColor" in session_attr:
            fav_color = session_attr["favoriteColor"]
 
            return handler_input.response_builder.speak(
                "あなたのお気に入りの色は{}です。さようなら".format(
                    fav_color)).set_should_end_session(True).response
        else:
            # ユーザーはお気に入りの色を設定する前にこのインテントを呼び出したようです。
            # FavoriteColorIntentをトリガーしてユーザーに
            # favoriteColorスロットの値をたずねます。ElicitSlotディレクティブを使う場合、スキルには*ダイアログモデル*が
            # が必要です。
 
            return handler_input.response_builder.speak(
                "最初に、あなたのお気に入りの色を私に教えてください。").ask(
                "あなたのお気に入りの色は何ですか?").add_directive(
                directive=ElicitSlotDirective(
                    updated_intent=Intent(
                        name="FavoriteColorIntent"), 
                    slot_to_elicit="favoriteColor")).response

クリップボードにコピーされました。

このサンプルコードはAlexa Skills Kit SDK for Javaを使用しています。

package handlers;

import static com.amazon.ask.request.Predicates.intentName;

import java.util.Map;
import java.util.Optional;

import com.amazon.ask.attributes.AttributesManager;
import com.amazon.ask.dispatcher.request.handler.HandlerInput;
import com.amazon.ask.dispatcher.request.handler.impl.IntentRequestHandler;
import com.amazon.ask.model.Intent;
import com.amazon.ask.model.IntentRequest;
import com.amazon.ask.model.Response;

public class WhatsMyColorIntentHandler implements IntentRequestHandler {
    @Override
    public boolean canHandle(HandlerInput handlerInput, IntentRequest intentRequest) {
        return handlerInput.matches(intentName("WhatsMyColorIntent"));
    }

    @Override
    public Optional<Response> handle(HandlerInput handlerInput, IntentRequest intentRequest) {

        AttributesManager attributesManager = handlerInput.getAttributesManager();
        Map <String,Object> attributes = attributesManager.getSessionAttributes();

        // ユーザーはお気に入りの色を設定する前にこのインテントを呼び出す可能性があります。
        // セッションアトリビュートをまずチェックしてください。
        if (attributes.containsKey("favoriteColor")){
            String favoriteColor = attributes.get("favoriteColor").toString();

            return handlerInput.getResponseBuilder()
                    .withSpeech("あなたのお気に入りの色は" + favoriteColor + "です。さようなら")
                    .withShouldEndSession(true)
                    .build();
        } else {
            // ユーザーはお気に入りの色を設定する前にこのインテントを呼び出したようです。
            // FavoriteColorIntentをトリガーしてユーザーに
            // favoriteColorスロットの値をたずねます。ElicitSlotディレクティブを使う場合、スキルには*ダイアログモデル*が
            // が必要です。

            // インテントを作成します。
            Intent intent = Intent.builder()
                .withName("FavoriteColorIntent")
                .build();

            return handlerInput.getResponseBuilder()
                    .withSpeech("最初に、あなたのお気に入りの色を私に教えてください。")
                    .withReprompt("あなたのお気に入りの色は何ですか?")
                    .addElicitSlotDirective("favoriteColor", intent)
                    .build();
        }

    }
}

クリップボードにコピーされました。

以下のJSONは、ユーザーが色の名前を既に提供している場合に受け取ったIntentRequestを表しています。値はsession.attributesに保存されています。

{
  "version": "1.0",
  "session": {
    "new": false,
    "sessionId": "amzn1.echo-api.session.1",
    "application": {
      "applicationId": "amzn1.ask.skill.1"
    },
    "attributes": {
      "favoriteColor": "青色"
    },
    "user": {
      "userId": "amzn1.ask.account.1"
    }
  },
  "context": {},
  "request": {
    "type": "IntentRequest",
    "requestId": "amzn1.echo-api.request.1",
    "timestamp": "2019-02-13T04:22:36Z",
    "locale": "ja-JP",
    "intent": {
      "name": "WhatsMyColorIntent",
      "confirmationStatus": "NONE"
    }
  }
}

次のようなシナリオでは、セッションアトリビュートが有効です。

  • さまざまなスキル状態を処理するためにデータをトラッキングします。たとえば、ユーザーが既にゲームを開始しているか、新しいゲームを始められる状態かを示すstateアトリビュートなどです。アトリビュートを使用して、コードでハンドラーが特定のリクエストを処理できるかどうかを判断します。これは、スキルがユーザーに複数の「はい」か「いいえ」で答えられる質問をする場合に特に有効です。「はい」や「いいえ」がスキルの状態によって違う意味を持つ場合があるからです。
  • ゲームのスコアやカウンターを追跡します。
  • 追加の値をユーザーにたずねる際に、ユーザーが提供したスロット値を保存します。ダイアログモデルとダイアログのデリゲートを使って、セッションアトリビュートを使わずに行う方法もあります。Alexaにダイアログをデリゲートするを参照してください。

セッションアトリビュートの構造

キーと値のペアのマップとして構成できるデータであれば、より複雑なデータをセッションアトリビュートに渡すこともできます。たとえば、クイズゲームのスキルには、ゲームの現在のステータス、ユーザーの現在のスコア、今出題した質問の正解などをトラッキングするアトリビュートが必要になります。この例のquizitemアトリビュートは、現在の質問の正解を表します。

  {
  "sessionAttributes": {
    "quizscore": 0,
    "quizproperty": "STATEHOOD_YEAR",
    "response": "さて、 これからアメリカ合衆国に関する問題を10問出します。",
    "state": "_QUIZ",
    "counter": 1,
    "quizitem": {
      "name": "ネバダ",
      "abbreviation": "エヌブイ",
      "capital": "カーソンシティ",
      "statehoodYear": "1864年",
      "statehoodOrder": "36番目"
    }
  }
}

ゲーム状態に関するより複雑なセッションアトリビュートのサンプル全体については、クイズゲームのサンプルスキルを参照してください。

セッション間データの保存

セッションアトリビュートは、セッションが開いている間存在します。セッションが終了すると、そのセッションに関連するすべてのアトリビュートは失われます。複数のセッションにまたがってデータを記憶しておく必要がある場合、データをDynamoDBやS3などの永続ストレージに保存する必要があります。

ユーザーを識別するには、各リクエストで提供されるuserIdを使用します。指定したユーザーのuserIdは、ユーザーがAlexaアプリでスキルを有効にしたときに生成されます。ユーザーがスキルを無効にしない限り、そのユーザーからスキルへの後続のすべてのリクエストには同じuserIdが含まれます。

スキルセッション間でのアトリビュートの保持は、ASK SDKs for Node.js、Java、およびPythonに組み込まれています。詳細については、アトリビュートの管理に関するASK SDKドキュメントを参照してください。

ユーザーアトリビュートの保持に関するサンプルスキルについては、ハイ&ローゲームを参照してください。