kun432's blog

Alexaなどスマートスピーカーの話題中心に、Voiceflowの日本語情報を発信してます。たまにAWSやkubernetesなど。

カスタムスロットタイプのスキル間での共有を試してみた

2020/3/4追記 タイトルを少し変更しました

Alexa Developer Blogで紹介されていたこの記事を試してみました。

f:id:kun432:20200313132705p:plain

https://developer.amazon.com/en-US/blogs/alexa/alexa-skills-kit/2020/03/create-shared-slots-across-your-skills-to-optimize-productivity-and-customer-experience:text

ビルトインで用意されているスロットタイプで足りない場合、カスタムスロットタイプを使うことになるわけですが、似たようなスキルを作るとカスタムスロットタイプも作るわけで、作ることもそうですがメンテナンスするのが面倒です。そこを独立させてしまえばメンテが簡単になるわけですね。ということでやってみました。

いくつかやり方が紹介されてますが、動的エンティティはちょっと置いといて、以前からある「参照ベースのカタログ」を使う方法と今回発表された「スロットタイプ」を共有する方法を試してみたいと思います。

参照ベースのカタログ管理

公式のドキュメントはこちらです。

カタログは、データベースとまではいかないですが、リスト的なものを外部で管理するための仕組みです。ユースケースとして、今回のカスタムスロットタイプ値をスキルの外において、複数のスキルからそれを参照すれば、リストの管理が一元的にできて、スキルからは呼び出すだけということになるかと思います。

では早速やってみましょう。例としてLastNameJPという日本語の名字のカタログを作りたいと思います。現時点でカタログへのアクセスにはask-cliが必要ですので、必要な人はインストールしてください。

まず最初にカタログの定義を作成します。ask api create-model-catalogを使います。-nでカタログ名、-dでカタログの説明を記載します。ただしこの名前と説明はスキル内では必要ありません。

$ ask api create-model-catalog -n JP_LAST_NAME -d "日本人の典型的な名字です。"
{
  "catalogId": "amzn1.ask.interactionModel.catalog.xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}

すると、カタログIDが作成されます。この以降の作業はこのIDを使って操作を行います。

登録されているカタログの一覧を表示します。一覧を表示するには、ask api list-model-catalogsを使います。

$ ask api list-model-catalogs
{
  "catalogs": [
    {
      "catalogId": "amzn1.ask.interactionModel.catalog.xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
      "name": "JP_LAST_NAME"
    }
  ],
  "isTruncated": false,
  "totalCount": 1
}

はい、先程登録したカタログが表示されました。ちなみにカタログはベンダーID、つまり、開発者アカウントごとに管理されます。

次にカタログのバージョンを作成します。カタログのデータは常にバージョンとひも付きます。つまり、新規作成するとバージョン1、更新するたびにインクリメントされていくというわけです。

以下のようなカタログデータをjp-last-name.jsonとして用意します。

{
  "values": 
  [
    {
        "id": "id1",
        "name": {
            "value": "佐藤"
        }
    },
    {
        "id": "id2",
        "name": {
            "value": "鈴木"
        }
    },
    {
        "id": "id3",
        "name": {
            "value": "高橋"
        }
    },
    {
        "id": "id4",
        "name": {
            "value": "田中"
        }
    },
    {
        "id": "id5",
        "name": {
            "value": "伊藤"
        }
    },
    {
        "id": "id6",
        "name": {
            "value": "渡辺"
        }
    },
    {
        "id": "id7",
        "name": {
            "value": "山本”
        }
    },
    {
        "id": "id8",
        "name": {
            "value": "中村"
        }
    },
    {
        "id": "id9",
        "name": {
            "value": "小林"
        }
    },
    {
        "id": "id10",
        "name": {
            "value": "加藤"
        }
    },
    {
        "id": "id11",
        "name": {
            "value": "吉田"
        }
    },
    {
        "id": "id12",
        "name": {
            "value": "山田"
        }
    }
]
}

そしてこのファイルをインターネット上でパブリックにアクセスできるところで公開します。今回はAmazon S3にアップロードしました。

そしてカタログバージョン用の定義ファイルを作ります。catalog.jsonとしました。この中でカタログデータファイルのURLを指定します。

{
  "type": "URL",
  "url": "https://xxxxxxxxx.s3-ap-northeast-1.amazonaws.com/jp-last-name.json"
}

ではいよいよバージョンを登録します。ask api create-model-catalog-versionを使います。

$ ask api create-model-catalog-version -c amzn1.ask.interactionModel.catalog.xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx -f ./catalog.json
catalog version submitted
Please use the following command to track the to track the create version status
 get-model-catalog-update-status -c amzn1.ask.interactionModel.catalog.xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx -u xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx-Time-2020-03-08T15-01-19.600

このとき、更新リクエストIDというものが発行されるので、ask api get-model-catalog-update-statusで更新ID・カタログIDを渡すと、カタログバージョンの作成状況を確認できます。

$ ask api get-model-catalog-update-status -c amzn1.ask.interactionModel.catalog.xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx -u xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx-Time-2020-03-08T15-01-19.600
{
  "lastUpdateRequest": {
    "errors": [],
    "status": "SUCCEEDED",
    "version": "1"
  }
}

上記のようにバージョン番号が発行され、SUCCEEDEDになっていればOKです。

登録されたカタログバージョンを見てみます。ask api get-model-catalog-versionを使います。

$ ask api get-model-catalog-version -c amzn1.ask.interactionModel.catalog.xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx --catalog-version 1
{
  "source": {
    "type": "URL",
    "url": "https://xxxxxxxxx.s3-ap-northeast-1.amazonaws.com/jp-last-name.json"
  },
  "version": "1"

バージョン1が登録されています。

では、スキルから使ってみましょう。今回はAlexa開発者コンソールでHello Worldスキルのテンプレートから新規にスキルを作成してやってみます。JSONエディターを開いてみると、以下のように何もスロットが設定されていませんね。

            "types": []

これを以下のように変更します。

            "types": [
                {
                    "name": "jpLastName",
                    "valueSupplier": {
                     "type": "CatalogValueSupplier",
                        "valueCatalog": {
                            "id": "amzn1.ask.interactionModel.catalog.xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
                            "version": "1"
                        }
                    }
                }
            ]

nameがスキル内でのスロット名になります。valueSupplierにカタログの情報を入れていきます。カタログIDとバージョンですね。追加したらモデルを保存・ビルドします。ここ少し注意なんですが、保存・ビルドを行うタイミングでIDが空になって姉妹エラーになることがあります。その場合はもう一度IDを入力して保存・ビルドしてください。

では、対話モデルを設定して実際に使ってみましょう。

lastname_intentというインテントを作成し、サンプル発話とスロットを作成し、先程JSONエディターで登録したスロットタイプを選択します。再度、モデルの保存とビルドを行っておきます。

f:id:kun432:20200313003629p:plain

ではテストしてみましょう。実際のスキルのテストではなく、発話プロファイラを使ってみたいと思います。右上の「モデルの評価」をクリックして、発話プロファイラにサンプル発話を入力してみます。まずはスロットタイプにに登録されている「佐藤」さんから。

f:id:kun432:20200313004025p:plain

はい、きちんとスロットとして認識していますね。次はスロットタイプに登録されていない「清水」さんで。

f:id:kun432:20200314005252p:plain

こちらもスロットとして認識はしていますが、下の方を見てください。

f:id:kun432:20200313004644p:plain

この違いがわかりますか?カスタムスロット値として登録しておいた場合は、インテント・スロットの認識が一つしかないのに対して、カスタムスロット値が登録されていない値の場合は他の候補が表示されます。つまり、カタログとして登録したカスタムスロットをきちんと認識していると考えられますね。

このようにして、カスタムスロットタイプをカタログとしてスキルとは別の場所に登録しておくことで、スキルとは別に管理ができる、つまり、複数のスキルで共有することができるというわけですね。

実際にはカタログのデータはバージョンで管理をします。スキル内からもバージョンを指定する必要があるため、カタログのデータを更新すれば勝手に変わる、というわけではなく、スキル側でもバージョンを更新して対話モデルの再ビルドが必要になります。ただ現在はLive Updateにより、カスタムスロット値の更新程度であれば、審査に時間がかかることなく更新が可能になっているので、タイムリーに対応ができると思います。

カスタムスロット値の共有

先ほど紹介した通り、カタログを使ってカスタムスロットを登録しておけばスキル間での共有が可能です。じゃあこっちは何なのか?というと、実はこちらも中身的にはカタログと似たような仕組みになっていて、ただし、明確にカスタムスロット用として新たに追加されたもののようです。では早速見ていきましょう。

こちらもask-cliを使いますが、ask-cli-1.7.23以降になります。で、微妙にコマンドが違います。

まず、同じようにスロットタイプの定義を作成します。ask api create-model-slot-typeを使います。

$ ask api create-model-slot-type -n "JP_LAST_NAME2" -d "日本の典型的な名字です"
{
  "slotType": {
    "id": "amzn1.ask.interactionModel.slotType.xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
  }
}

カタログとは別のスロットタイプIDが発行されます。スロットタイプの一覧を取得するask api list-model-slot-typesを実行します。

$ ask api list-model-slot-types
{
  "slotTypes": [
    {
      "_links": {
        "self": {
          "href": "/v1/skills/api/custom/interactionModel/slotTypes/amzn1.ask.interactionModel.slotType.xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
        }
      },
      "description": "日本の典型的な名字です",
      "id": "amzn1.ask.interactionModel.slotType.xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
      "name": "JP_LAST_NAME2"
    }
  ]
}

はい、登録されていますね。次に

スロット単体での確認はask api get-model-slot-type を使います。このとき、スロットタイプIDは-tで指定します。

$ ask api get-model-slot-type -t amzn1.ask.interactionModel.slotType.xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
{
  "slotType": {
    "description": "日本の典型的な名字です",
    "name": "JP_LAST_NAME2"
  }
}

ではスロットタイプ値を登録します。カタログの場合はファイルをインターネット上に公開してURLを指定する形でした。スロットタイプの場合も同じことができますが、こちらはローカルのファイルで登録することもできます。

以下のようなJSONファイルを用意します。

{
      "valueSupplier": {
        "type": "InlineValueSupplier",
    "values": [
      {
          "id": "id1",
          "name": {
              "value": "佐藤"
          }
      },
      {
          "id": "id2",
          "name": {
              "value": "鈴木"
          }
      },
      {
          "id": "id3",
          "name": {
              "value": "高橋"
          }
      },
      {
          "id": "id4",
          "name": {
              "value": "田中"
          }
      },
      {
          "id": "id5",
          "name": {
              "value": "伊藤"
          }
      },
      {
          "id": "id6",
          "name": {
              "value": "渡辺"
          }
      },
      {
          "id": "id7",
          "name": {
              "value": "山本"
          }
      },
      {
          "id": "id8",
          "name": {
              "value": "中村"
          }
      },
      {
          "id": "id9",
          "name": {
              "value": "小林"
          }
      },
      {
          "id": "id10",
          "name": {
              "value": "加藤"
          }
      },
      {
          "id": "id11",
          "name": {
              "value": "吉田"
          }
      },
      {
          "id": "id12",
          "name": {
              "value": "山田"
          }
      }
    ]
      }
}

上記にあるInlineValueSupplierというのがファイルの中に書いているという意味になります。それではバージョンを作成します。バージョンの作成はask api create-model-slot-type-versionを使います。-t でスロットタイプID、-fで上記のJSONファイルのパスを指定します。

$ ask api create-model-slot-type-version -t amzn1.ask.interactionModel.slotType.xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx -f ./jp-last-name-slot.json
Slot type version submitted.
Please use the following command to track the status
 ask api get-model-slot-type-update-status -t amzn1.ask.interactionModel.slotType.xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx -u xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

ask api get-model-slot-type-update-statusで更新状況を見てみましょう。

$ ask api get-model-slot-type-update-status -t amzn1.ask.interactionModel.slotType.xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx -u xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
  "updateRequest": {
    "status": "SUCCEEDED",
    "version": "1"
  }
}

バージョン1が発行されました。ask api get-model-slot-type-versionでバージョンの内容を取得してみます。

$ ask api get-model-slot-type-version -t amzn1.ask.interactionModel.slotType.xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx --slot-type-version 1
{
  "slotType": {
    "id": "amzn1.ask.interactionModel.slotType.xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "definition": {
      "valueSupplier": {
        "type": "InlineValueSupplier",
        "values": [
          {
            "id": "id1",
            "name": {
              "value": "佐藤"
            }
          },
          {
            "id": "id2",
            "name": {
              "value": "鈴木"
            }
          },
          {
            "id": "id3",
            "name": {
              "value": "高橋"
            }
          },
          {
            "id": "id4",
            "name": {
              "value": "田中"
            }
          },
          {
            "id": "id5",
            "name": {
              "value": "伊藤"
            }
          },
          {
            "id": "id6",
            "name": {
              "value": "渡辺"
            }
          },
          {
            "id": "id7",
            "name": {
              "value": "山本"
            }
          },
          {
            "id": "id8",
            "name": {
              "value": "中村"
            }
          },
          {
            "id": "id9",
            "name": {
              "value": "小林"
            }
          },
          {
            "id": "id10",
            "name": {
              "value": "加藤"
            }
          },
          {
            "id": "id11",
            "name": {
              "value": "吉田"
            }
          },
          {
            "id": "id12",
            "name": {
              "value": "山田"
            }
          }
        ]
      }
    },
    "version": "1"
  }
}

はい、これでスロットタイプが登録できました。

では、これをスロットから使います・・・といいたいところですが、ドキュメントにはこの指定方法が載っていません・・・

で、いろいろ調べたところ、以下にサンプルがありました。

これを見る限り、以下のように登録するようです。

   "intents": [
        {
          "name": "IngredientIntent",
          "slots": [
            {
              "name": "Ingredient",
              "slotTypeReference": {
                "id": "amzn1.ask.interactionModel.slotType.xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
                "version": "1"
              }
            }
          ],
          "samples": [
            "Add {Ingredient} to my shopping list",
            "Add {Ingredient} to my ingredient list",
            "Add {Ingredient} to my list"
          ]
        }
      ],

カタログの場合は、types、つまり対話モデルの中でスロットタイプを指すオブジェクトになっていましたが、スロットタイプの場合はインテントに紐付いたスロットとして登録するようです。

では実際にスキル内から使ってみましょうということで、同じようにJSONエディターに登録してみたのですが、何度やっても正しく反映されません・・・エラーも出ないので記載は多分あっているのではないか?と思うのですが、こればっかりはわかりようがないので情報が出てくるのを待ちたいと思います。

まとめ

ということで、カスタムスロットをスキル間で共有することができる「カタログ」に関連した機能のご紹介でした。カスタムスロットを共有できるのはパッと聞いただけだと便利そうと思う反面、コマンドもしくはAPIでしかできないのでとても面倒です。

じゃあこれ誰が嬉しいのか?というと、一番嬉しいのはSMAPIを使う人、端的に言うと、VoiceflowとかVoiceAppsとかみたいなスキル作成サービスを作っているところだと思います。

Alexaスキル管理API(SMAPI)は、Alexaスキル管理タスクをプログラムで実行できるRESTful HTTPインターフェースを提供します。スキル管理タスクには、スキルの新規作成や対話モデルの更新などがあります。APIは、Login with Amazonを使用して呼び出し元を認証します。認証された開発者はすべて、ユーザーに代わってAlexaスキルの作成や更新を行うツールやサービスの開発ができるようになります。ASK CLIは、そうしたツールの1つです。

VoiceflowのUI上で作ったスキルをAlexa開発者コンソールにアップロードする際、おそらくこの仕組を使っていると思われるのですが、ここにカスタムスロット値をスキルとは別に管理できるような機能が提供された、ということだと考えてます。

実はこれは結構インパクトあるのではないかと考えています。というのも、Voiceflowでもカスタムスロットを作ることはできますが、すべてGUIでの登録、かつ、CSV一括インポートみたいな機能はありません。小さなスキルでは問題ないですが、カスタムスロットの値が多くなってきたり、かつ、スキルごとにこれを作り直したり、メンテナンスしたり、となってくるとかなり辛いんですね・・・なのでこの機能が取り込まれると非常に効率的になるではないかなと思います。

それはそれとして、もちろんAlexa開発者コンソールでも使えるようになるのを期待して待ちたいと思います。