検索
特集

C#によるAlexaスキル実装:スロットとセッションを活用した対話の実現特集:はじめてのAlexaスキル開発(2/3 ページ)

「スロット」を使い、ユーザーとの対話から必要な情報を取得したり、その情報を1つのセッションで保持して使い回したりする方法を解説する。

Share
Tweet
LINE
Hatena

Lambda関数の実装

 このスキルから呼び出されるLambda関数は次のように実装した(AWS ToolkitやAlexa.NETパッケージのインストールなどについては前回を参照されたい)。

public SkillResponse FunctionHandler(SkillRequest input, ILambdaContext context)
{
  SkillResponse response = new SkillResponse
  {
    Version = "1.0",
    Response = new ResponseBody()
  };

  if (input.Request.Type == "LaunchRequest")
  {
    // LaunchRequestならウエルカムメッセージを表示
    return ProcRequest(response,
      "インサイダードットネット天気予報サンプルスキルです");
  }

  IntentRequest ir = input.Request as IntentRequest;

  if (ir == null) return null;

  var name = ir.Intent.Name;

  // 「キャンセル」「ストップ」などを処理
  if (name == "AMAZON.CancelIntent" || name == "AMAZON.StopIntent")
    return ProcRequest(response, "終了します", true);

  if (ir.DialogState != "COMPLETED")
  {
    // ダイアログモデルを利用して、スロットに情報を取得
    IList<IDirective> list = new List<IDirective>();
    list.Add(new DialogDelegate());
    response.Response.Directives = list;
    return response;
  }

  // スロットの値はIntent.Slotsディクショナリから得られる
  var city = ir.Intent.Slots["city"].Value;
  var day = ir.Intent.Slots["day"].Value;
  if (day == null)
    day = DateTime.Now.ToString("yyyy-MM-dd");

  response.Response.OutputSpeech = new PlainTextOutputSpeech
  {
    Text = $"{day} の {city} の天気ですね"
  };
  return response;
}

private static SkillResponse ProcRequest(SkillResponse response, string msg,
  bool shouldEndSession = false)
{
  response.Response.OutputSpeech = new PlainTextOutputSpeech
  {
    Text = msg;
  };
  response.Response.ShouldEndSession = shouldEndSession;
  return response;
}

Alexaのスキルから呼び出されるLambda関数

 少々長い関数になってしまったので、ざっくりとその内容を説明しておこう。最初にユーザーに返送するSkillResponseオブジェクトを作成しておいて、その後の処理に応じて、その内容を変化させ(Responseプロパティに読み上げるべきメッセージを設定したり、セッションを終了させるためのフラグをセットしたりしている)、最後にそれを返送するのがLambda関数でやっていることだ。

 最初の方では、LaunchRequestリクエスト(「○○を開いて」と言われたときに送信されるリクエスト)と、IntentRequestのうちAlexaの組み込みインテントであるAMAZON.CancelIntent/AMAZON.StopIntentの2つのインテントを処理している。後者の2つは、ユーザーが「キャンセル」「ストップ」などと発話したときにセッションを終了させる(Response.ShouldEndSessionプロパティをtrueにして、「終了します」とユーザーに応答する)。

 重要なのは以下の部分だ。

if (ir.DialogState != "COMPLETED")
{
  // ダイアログモデルを利用して、スロットに情報を取得
  IList<IDirective> list = new List<IDirective>();
  list.Add(new DialogDelegate());
  response.Response.Directives = list;
  return response;
}

ダイアログモデルを使って、スロットに情報を取得

 ドキュメントを読むと「スキルでダイアログモデルを使用するには、Dialog.Delegateディレクティブを返してください」とあるのだが、これを実際に行っているのが上に示したコードだ。Alexa.NETではDialog.DelegateはDialogDelegateクラスとして提供されているので、それをリストに格納して返送している。

 必須の情報が得られていない場合は、IntentRequestオブジェクトのDialogStateプロパティの値が「COMPLETED」以外の値となっているので、その間は、この処理が繰り返される(WeatherForecastIntentインテントが何度も送信されて、この関数が呼び出され、DialogStateプロパティの値が「COMPLETED」になるまでそこから先に進まない)。

 必要な情報がそろったら、IntentRequestオブジェクトのIntent.Slotsプロパティにインデックスとしてスロット名を指定してアクセスすることで、cityスロットとdayスロットの値を取得するだけだ。dayスロットは空の場合があるので、そのときには現在の日付を得ている。

// スロットの値はIntent.Slotsディクショナリから得られる
var city = ir.Intent.Slots["city"].Value;
var day = ir.Intent.Slots["day"].Value;
if (day == null)
  day = DateTime.Now.ToString("yyyy-MM-dd");

response.Response.OutputSpeech = new PlainTextOutputSpeech
{
  Text = $"{day} の {city} の天気ですね"
};
return response;

cityスロットとdayスロットの値を得て、ユーザーへの返答を組み立てる

 Alexaシミュレーターでテストしている様子を以下に示す。

Alexaシミュレーターでテストをしているところ
Alexaシミュレーターでテストをしているところ

 「明日の天気」と尋ねただけだと「どこの天気ですか」とプロンプトが表示されている点と、「大阪の天気は?」と尋ねるとデフォルトの日付が使われている点に注意されたい。だが、コンテキストを考えると、「明日の東京の天気は?」に続けて「大阪の天気は?」ときたら、それは「明日の大阪の天気」を聞いていると考えるのが普通だ(と思う)。そこで、日付が省略された場合、既に日付を指定して天気を尋ねられていたら、その日付を使用するようにしてみよう。そのために使うのがセッション情報のattributesプロパティだ。

セッション内で情報を保持する

 既に述べたように、AlexaスキルからLambda関数に渡されるリクエスト情報(Alexa.NETではSkillRequestオブジェクト)にはSessionプロパティがあり、その中に(Alexa Skills Kitのattributesプロパティに対応する)Attributesプロパティがある。これはDictionary<string, object>型のディクショナリであり、文字列のキーと任意のオブジェクトを保存できる。

 これを使って、dayスロットに得られた情報を保存しておき、後続の問い合わせでdayスロットが空の場合には、保存しておいた値を使おうということだ。実際のコードを以下に示す。

public SkillResponse FunctionHandler(SkillRequest input, ILambdaContext context)
{
  Dictionary<string, object> attribs = input.Session.Attributes;
  if (attribs == null)
    attribs = new Dictionary<string, object>();

  SkillResponse response = new SkillResponse
  {
    Version = "1.0",
    Response = new ResponseBody(),
    SessionAttributes = attribs
  };

  if (input.Request.Type == "LaunchRequest")
  { 
    …… 省略 ……
  }

  …… 省略 ……

  if (ir.DialogState != "COMPLETED")
  {
    …… 省略 ……
  }

  var city = ir.Intent.Slots["city"].Value;
  var day = ir.Intent.Slots["day"].Value;
  if (day == null)
  {
    attribs.TryGetValue("day", out var tmp);
    if (tmp == null)
      day = DateTime.Now.ToString("yyyy-MM-dd");
    else
      day = (string)tmp;
  }
  attribs["day"] = day;

  response.Response.OutputSpeech = new PlainTextOutputSpeech
  {
    Text = $"{day} の {city} の天気ですね"
  };
  return response;
}

修正後のコード

 上のコードで強調表示されているのが、Attributesプロパティを使用している部分だ。このプロパティがnullであれば、新規に作成しておく。また、ユーザーに返送するSkillResponseオブジェクトには、SessionAttributesプロパティがあるので、これに作成したディクショナリを保存しておく(これでデータをセッション内で使い回せるようになる)。

 後は、「dayスロットの値が空」かつ「以前のdayスロットの値がAttributesプロパティに保存されている」ときにはそれを利用して、保存されていなければ今日の日付を使うようにしている。最後には、ディクショナリの値を上書きして、後続の対話でその情報を使えるようにしている。

 Alexaシミュレーターでのテストの様子を以下に示す。

日付情報をセッションで保持するようにした
日付情報をセッションで保持するようにした

 上の画像を見ると、2番目の「大阪の天気は?」という問い掛けでは、前回の日付情報が使われるようになったことが分かる。

 ここまで、スロットとセッション情報の使い方について見てきたが、最後にスロットタイプをカスタマイズする場合について簡単に見ておこう。

Copyright© Digital Advantage Corp. All Rights Reserved.

ページトップに戻る