動的エンティティを使用してパーソナライズされた音声エクスペリエンスを作る

Justin Jeffress Apr 18, 2019
Share:
Dialog Management Tutorial Tips & Tools
Blog_Header_Post_Img

私は毎朝出勤するときに、同じコーヒーショップで緑茶ラテを買います。バリスタは過去の経験から私の欲しいものがわかっているので、入店するとすぐに注文を作り始めてくれます。注文を変えて意地悪してみようかとふと思ったりしますが、せっかく作ってくれたおいしい抹茶ラテを台無しにしてほしくはありません。

 

エクスペリエンスがシンプルにパーソナライズされているので、私はこの店にせっせと通い続けています。同じように、優れたAlexaスキルはパーソナライズされたユーザーエクスペリエンスを提供してくれます。ユーザーは繰り返しそのスキルを利用したいという気持ちになります。

 

新しい動的エンティティ機能を活用すると、対話モデルを編集しビルドし直して再認定をする必要はありません。実行時に対話モデルを調整してAlexaスキルのエクスペリエンスをパーソナライズすることができます。プログラムコードや、データベース、RESTful API呼び出しから取得したデータ構造を、既存のスロット値に実行時に適用することで、スキルが、ユーザーやコンテキスト(文脈)、会話の流れを処理することができます。スロット値や同義語をコンテキストに合わせることで、先ほどのコーヒーショップのように、ユーザーの好みと過去の対話に基づいてエクスペリエンスをパーソナライズすることができます。現在は永続アトリビュートを利用することでスキルセッション間のイベントをコーディングしてスキルに記憶させることができます。しかし、それには対話モデルの更新と、それに付随する認定の更新などの作業が必要です。動的エンティティの実装では、モデルの再ビルドや再認定は必要ありません。AWS Lambdaコードでディレクティブが更新されるとすぐに、スキルのスロットのカスタマイズが開始されます。動的エンティティの追加は簡単です。

 

動的エンティティのしくみ

コーヒーショップの例をもう一度見てみましょう。スキルでドリンクを注文するには、スロットタイプがdrinkTypeであるドリンクスロットが必要です。最初に、2つの値、コーヒーと紅茶でこのドリンクスロットを定義しました。次に、緑茶とウーロン茶を追加するよう商品を拡張しました。動的エンティティでは、モデルを手動で更新しても、認定の再申請は必要なく、実行中にこうしたことを行うことができます。

スキルでは、Dynamic.UpdateDynamicEntitiesディレクティブを返す必要があります。updateBehavior値をCLEARまたはREPLACEのいずれかに設定し、typesフィールドを使用して動的エンティティを設定することができます。Alexaサービスは、ディレクティブを返すときにdrinkTypeの新しいスロット値と同義語を登録します。これはサイレントプロセスで、ユーザーがスキルと対話する以外に何かをする必要はありません。

新しいスロット値と同義語が登録されると、動的エンティティに関連付けられたスロットを含むスキルへのリクエストには、drinkTypeの静的に定義された値と動的に定義された値の両方に基づいた解決済みの値が含まれるようになります。ユーザーの発した言葉が静的スロット値と動的スロット値のどちらに一致したのかは、スキルコードから確認することができます。ユーザーがスキルとの対話を終了すると、動的エンティティの有効期限が切れるため、次回にスキルが起動したときに動的エンティティを再登録する必要があります。

それでは、応答とリクエストから見ていきましょう。

 

応答: 動的エンティティの登録

drinkTypeの動的エンティティを登録すると、応答でこのディレクティブが返ります。

Copied to clipboard
...
'directives': [
    {
        'type': 'Dialog.UpdateDynamicEntities',
        'updateBehavior': 'REPLACE',
        'types': [...]
    }
]
...

typeとupdateBehaviorはとてもわかりやすいものです。一方、typesフィールドはスロットタイプを表す複合オブジェクトの配列です。スキルには複数のスロットタイプを持たせることができます。複数のスロットタイプ値と同義語を設定できるため、typesは配列になっています。types配列内のスロットタイプオブジェクトを見てみましょう。

Copied to clipboard
{
    'name': '[slotType]',
    'values': [
        {
            ...
        },
        ...
    ]
}

nameとvaluesという2つのフィールドがあります。nameフィールドは、更新したいスロットタイプの名前です。スロットタイプには複数の値を持たせることができるため、values配列で、スロット値と同義語を表す複合オブジェクトのリストを提供できます。values配列内のスロット値オブジェクトを詳しく見ていきましょう。

Copied to clipboard
{
    'id': '[slotValueId]',
    'name': {...}
}

各スロット値オブジェクトにはnameが1つ含まれます。これはオブジェクトであり、任意のIDを定義できます。IDは任意ですが、スロット値を使ってデータベースやRESTfulウェブサービス(Amazon DynamoDBなど)の内容を検索する予定であれば、IDを1つ定義するとよいでしょう。そうすると、コードにスロット値とデータベースID値を照合するディクショナリーを作成する必要がなくなります。

最後に、nameフィールドは複合オブジェクトであり、値、同義語の配列が含まれます。

Copied to clipboard
{
    'value': '[slotValue]',
    'synonyms': [
        '[同義語A]',
        '[同義語B]',
        ...
    ]
}
以上のことを総合し、drinkTypeに緑茶とウーロン茶を追加する場合のディレクティブは次のようになります。
...
'directives': [
    {
        'type': 'Dialog.UpdateDynamicEntities',
        'updateBehavior': 'REPLACE',
        'types': [
            {
                'name': 'drinkType',
                'values': [
                    {
                        'id': 'grnTea',
                        'name': {
                            'value': '緑茶',
                            'synonyms': [
                                '抹茶',
                            ]                            
                        }
                    },
                    {
                        'id': 'oolTea',
                        'name': {
                            'value': 'ウーロン茶',
                            'synonyms': [
                                '中国茶',
                                '青茶',
                            ]                            
                        }
                    }                        
                ]
            }
        ]
    }
]
...

リクエスト:動的に定義されたスロット値の受け取り

動的エンティティが登録されると、スキルコードに送信されたリクエストには、静的に定義された解決と動的に定義された解決の両方がresolutionsPerAuthorityという名前の配列に含まれます。注:同義語とスロット値のマッピングにエンティティ解決を使用したことがあれば、resolutionsPerAuthority配列には見覚えがあるでしょう。以前は、この配列の項目は1つだけでした。そのため、resolutionsPerAuthority[0]とハードコーディングしていました。しかし、静的エンティティと動的エンティティはこの配列に複数の項目があるため、ハードコーディングは適していません。

注意していただきたいのは、 配列の順序はないということです。動的エンティティが先に来ることもあれば、静的エンティティが先のこともあります。resolutionsPerAuthority[0]配列では順序に頼ることができないため、配列内の各オブジェクトのエンティティタイプをチェックする必要があります。

Copied to clipboard
静的:
    amzn1.er-authority.echo-sdk.amzn1.ask.skill.[skill_id].drinkType

動的:
    amzn1.er-authority.echo-sdk.dynamic.amzn1.ask.skill.[skill_id].drinkType

動的の方には「dynamic」という単語が含まれている点を除き、両者はまったく一緒だということにお気付きになったでしょうか。 コードでは、resolutionsPerAuthority内のオブジェクトが動的かどうかを判断するためには、このオブジェクトに「.er-authority.echo-sdk.dynamic」が含まれているかどうかをチェックするとよいでしょう。 「dynamic」が含まれているかチェックするだけではなぜいけないのでしょうか。 スロットタイプにdynamicTypeという名前を付けたケースを考えてみましょう。このケースでは、静的情報ソースと動的情報ソースの両方の文字列の末尾にdynamicTypeが含まれます。「dynamic」が含まれているかどうかのチェックだけでは、静的、動的ともにtrueが返ってきてしまい、欲しい結果が得られません。

ユーザーが抹茶を注文した場合を考えてみましょう。これは緑茶の同義語です。ちょうどdrinkTypeを動的に更新したばかりです。 リクエストを掘り下げ、OrderIntentを見てみましょう。resolutionsPerAuthorityには2つの情報ソースが含まれます。

Copied to clipboard
...
{
    "intent": {
        "name": "OrderIntent",
        "confirmationStatus": "NONE",
        "slots": {
            "drink": {
                "name": "drink",
                "value": "抹茶",
                "resolutions": {
                    "resolutionsPerAuthority": [
                        {
                            "authority": "amzn1.er-authority.echo-sdk.amzn1.ask.skill.[skill_id].drinkType",
                            "status": {
                                "code": "ER_SUCCESS_NO_MATCH"
                            }
                        },
                        {
                            "authority": "amzn1.er-authority.echo-sdk.dynamic.amzn1.ask.skill.[skill_id].drinkType",
                            "status": {
                                "code": "ER_SUCCESS_MATCH"
                            },
                            "values": [
                                {
                                    "value": {
                                        "name": "緑茶",
                                        "id": "grnTea"
                                    }
                                }
                            ]
                        }
                    ]
                },
                "confirmationStatus": "NONE",
                "source": "USER"
            }
        }
    }
}
...

最上位(intent.slots.drink)では、valueは「抹茶」です。これは、スキルがユーザーの注文をキャプチャしたことを意味します。また、resolutionsPerAuthorityが2つあることも確認できます。1つは静的で、もう1つは動的です。静的に定義されたdrinkTypeには緑茶という値もその同義語の抹茶も含まれていません。そのため、ステータスコードはER_SUCCES_NO_MATCHです。動的の方はER_SUCCESS_MATCHであり、緑茶という値とIDであるgrnTeaが含まれています。ドリンクメニューを動的に更新することができたようです。

では、期間限定の緑茶フラッペのドリンクプロモーションをスキルで提供しましょう。ウェブサービスを通してスキルが呼び出すデータベースにドリンクのエントリを追加できました。スキルが開いたときに、drinkTypeエンティティとして動的に登録された緑茶フラッペを使って販売メニューを更新しました。プロモーション期間が終わると、このエントリをデータベースから削除できます。リピーターが緑茶フラッペを注文しようとしても、静的エンティティにも動的エンティティにも存在しないため、そのアイテムはもう提供されていないことがわかります。

動的エンティティの使い方を把握しましたね。では制限事項について見ていきましょう。

 

制限事項

説明してきたように、動的エンティティは非常に強力ですが、認識しておくべき制限事項がいくつかあります。

 

1.エンティティの上限は100件

これには、各スロットタイプの値と同義語の組み合わせも含まれます。合計が100件を超えると、403エラーが返り、動的エンティティは登録されなくなりますが、静的エンティティは引き続き機能します。

 

2.追加ではない

動的エンティティは任意の応答から何度でも繰り返し更新できます。この時、以前に登録した動的エンティティは上書きされます。複数の応答を使って200件の動的エンティティを追加することはできませんが、スキルのコンテキストに基づいて100件のエンティティをロードしてユーザーの応答を処理し、別のコンテキストが入力されたら、新たな100件と入れ換えることができます。

 

3.ワンショットのサポートはなし

スキルがディレクティブを返すためには、まずリクエストを受け取る必要があります。つまり、動的エンティティはスキルが開かれるまで登録されません。ユーザーが「アレクサ、コーヒーショップを開いて」と言ってスキルを開くのが理想的です。すると、LaunchRequestがトリガーされるため、動的エンティティを登録できます。ユーザーが「アレクサ、コーヒーショップで緑茶が欲しい」と言ったとすると、動的エンティティが登録できるようになる前にOrderIntentがトリガーされます。この場合、ドリンクスロットは緑茶として解決されますが、スキルで使用できるのは静的エンティティのみです。ユーザーがプロモーションメニューからアイテムを注文した場合、その商品がまだ販売中かどうかを動的エンティティを使ってチェックすることができないため、手動でチェックする必要があります。

動的エンティティで対話をパーソナライズし、スロットタイプをコンテキストに合わせて精度を向上させると、スキルがいっそう魅力的になります。

 

パーソナライズ

私がいつものコーヒーショップに入ったとき、バリスタも私も、私が飲み物が欲しいこと、その飲み物は緑茶ラテであることを知っています。バリスタは他の顧客についても同じように推測ができます。そのため注文のプロセスがパーソナライズされて効率的になり、さらにユーザーを引き付けられます。

動的エンティティでは、各ユーザーの過去の注文履歴に基づいてスロット値を動的にマッピングすることで、これと同じことができます。動的エンティティを使用すると、スロット値をいつもの注文にマッピングできます。私の場合は緑茶ラテです。私はスキルと対話するときに、「いつもの」で注文することができます。ドリンクスロットに「いつもの」を入れて、緑茶ラテに解決することができます。

 

コンテキストに合わせる

スキルをコンテキストに合わせると、より精度が向上するため、いっそう魅力的になります。現在のスキルのコンテキストに合わせて動的エンティティを設定すると、スロットに細かくコンテキストを認識させることができます。たとえば、音声でバスの経路を検索できるスキルがあるとします。道路とバス停の名前は表記が難しくて読めず、正式な読み方で発話されないことがあります。デバイスアドレスAPIを使用すると、スキルからユーザーが所有するEchoデバイスの郵便番号を取得し、それを使用して特定の半径内のすべてのバス停と交差点を検索し、動的エンティティを使用してその道路の名前をスロットタイプに設定することができます。動的エンティティのセットははるかに小さいものの、ユーザーがスキルと対話している場所というコンテキストと関連性が高くなり、スキルが道路名を解釈するときの精度が向上します。

 

まとめ

初期のテスターの方にお試しいただいたところ、動的エンティティはユーザーを引き付けるために役立つ強力なツールだということがわかりました。短時間で、パーソナライズされた質の高いエクスペリエンスを提供できます。英国のネットスーパーOcadoJames Dimmock氏は「当社は100種類以上の牛乳を販売しており、多くのお客様がブランドや種類に強いこだわりを持っていらっしゃいます。Ocadoのスキルでは、動的エンティティを使用することで、お客様がお気に入りの商品をこれまで以上にすばやく簡単にカートに入れられるようになります。」と話しています。

また、音声開発者は動的エンティティを使ってビジネスモデルを改善しています。米国の音声開発会社Matchbox.ioの創業者であるJoel Wilson氏は、実行時にスキルを更新することで時間の短縮になり、業務をスピードアップできることを発見しました。Wilson氏は「これで、コンテンツが変わるたびに言語モデルを変更してスキルを再申請する必要がなくなるでしょう」と話してくれました。

この記事が、スキルに動的エンティティを組み込む際のヒントになれば幸いです。うまく行ったら、ぜひ教えてください。 私のTwitterアカウントはSleepyDeveloperですので、連絡をお待ちしています。

 

関連リソース