miso_soup3 Blog

主に ASP.NET 関連について書いています。

Action types of Adaptive Cards (in Microsoft Bot Framework WebChat)

Microsoft Bot Framework の Adaptive Cards で行えるアクションの種類を調べました。

環境

  • Server side
    • C#, Microsoft.Bot.Builder 3.8.4.0
  • Channel
    • Bot Framework Channel Emulator 3.5.29
    • WebChat 0.11.0

他の Rich Card とアクションの種類が違う

Bot Framework では、メッセージや画像の他に、カードの表現でメッセージを送信できます。そのカードには、ここのドキュメント の「Types of rich cards」にあるように、AdaptiveCard を含め、AnimationCard、AudioCard といった8個の種類があります。このうちの1つの Adaptive Card は、Build 2017 で発表されたものです。それまで固定のレイアウトしかできなかったカードですが、Adaptive Card では、開発者がある程度自由にレイアウトできます。

カードで行えるアクションは Adaptive Card とその他のカードで種類が違うようです。

  • Adaptive Card カード以外で使えるアクションの種類(参照
    • openUrl, imBack, postBack, call, playAudio, playVido, showImage, downloadFile, signin
  • Adaptive Card で使えるアクションの種類(参照 Action.*の項目
    • OpenUrl, Submit, Http, ShowCard

例えば、Adaptive Card 内で「imBack」の処理(imBackは、ボタンを選択するとその値のメッセージをユーザーから Bot に送信します)を行いたい場合は、Adaptive Card のアクションの種類「Submit」を利用して、同じような挙動を実現します。

※Adaptive Card は発表されたばかりなので、今後種類が増えていく可能性はあります。

※Adaptive Card の性質を考えると、既存のカードの実装を置き換えてもよさそうですが、実際にどのような実装になっているかはわからず。代わりに次のような記述を見つけました。

The existing Bot Builder SDK card types (Hero, Thumbnail, Audio, Video, Animation, SignIn, Receipt) are now implemented as Adaptive Cards. BotFramework-WebChat/AdaptiveCards.md at master · Microsoft/BotFramework-WebChat

アクションボタンのレイアウト

Adaptive Card のアクションは、一つのカードに対して複数のアクションを設定する、という形のようです。ですので、次の図のようにボタンが下部に配置されます。文章やとある Column の中にボタンを置く、といった自由なレイアウトはできませんでした。

f:id:miso_soup3:20170730154302p:plain

Channel が WebChat の場合のアクション

Channel を WebChat に限定し、Adaptive Card で使えるアクションがどのようなものか調べました。 WebChat の場合は、「Http」のアクションはサポートしていません。その他 WebChat での Adaptive Card については下記にあります。

BotFramework-WebChat/AdaptiveCards.md at master · Microsoft/BotFramework-WebChat

実装方法の調べ方としては、上のサイトに加え、Adaptive Card のスキーマの定義をみつつ(Adaptive Cards)、Adaptive Card の C# のサンプルをみる(BotBuilder-Samples/CSharp/cards-AdaptiveCards at master · Microsoft/BotBuilder-Samples)、が良さそうです。

OpenUrl

OpenUrl は、ボタンを選択するとブラウザでそのURLにアクセスします。

f:id:miso_soup3:20170730154315p:plain

コード

ShowCard

ShowCard は、ボタンを選択すると、別の Adaptive Card を表示します。

コード

f:id:miso_soup3:20170730154326p:plain

「コメント」をタップすると、下のように別の Adaptive Card が表示されます。

f:id:miso_soup3:20170730154341p:plain

こちらにもサンプルがあります。 ActivityUpdate | Adaptive Cards

Submit

Submit は、ボタンを選択すると、input 要素に入力された値を送信します。

f:id:miso_soup3:20170730154353p:plain

コード

こちらと同じサンプルです:InputForm | Adaptive Cards

入力された値を受け取るには、context.Wait(...) などでユーザーの IMessageActivity を受け取り、そのプロパティのValueから値を取得します。下記はそのサンプルです。

private async Task SelectedCardAsync(IDialogContext context, IAwaitable<string> result)
{
    //Adaptive Cardを送信する
    await context.PostAsync(AdaptiveCardGenerator.CreateAdaptiveCard(context, AdaptiveCardGenerator.CreateShowCard()));
    context.Wait(FormReceivedAsync);
}

/// <summary>
/// 入力フォームを受け取った後
/// </summary>
/// <param name="context"></param>
/// <param name="result"></param>
/// <returns></returns>
private async Task FormReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> result)
{
    var message = await result;

    if (message.Value != null)
    {
        dynamic value = message.Value;
        await context.PostAsync($"received: myName:{value.myName}, myEmail:{value.myEmail}, myTel:{value.myTel}. ");
    }

    //...

先の方で「imBack」のような挙動は Adapptive Card では「Submit」で代わりに実装する、と書きました。「Submit」では、input要素以外に任意のキーと値のペアを送信できるので(該当の場所のコード)、その値でユーザーの選択したボタンを区別し、分岐することにより「imBack」のような挙動を真似できます。

この Submit で注意したいことですが、私の環境の Bot Framework Channel Emulator 3.5.29 では入力値を受け取ることができませんでした(Value プロパティが null。値の送信もしてない様子)。しかし、ブラウザ上からの WebChat と、Bot Framework のポータルサイトの Test では、正常な動作を確認しました。下記のサイトでは、Mac では動いたけど Windows では動かない、といった記述があります。Emulator のバージョンの確認や再インストールで動いたとありますが、私の環境ではそれでもできませんでした。Adaptive card not working on Emulator · Issue #244 · Microsoft/BotFramework-Emulator

Synonyms in Azure Search - Japanese

Azure Search にシノニム機能が Public Preview として追加されたので、日本語で試しました。

※追記:2018/7/3 Public Previewが外れGAしました。

参照:

シノニム

シノニム - Wikipedia シノニム(synonym)とは、類語や同意語といった意味です。 検索エンジンでは、ある言葉で検索した場合に、違う言葉でヒットさせるための機能をいいます。

たとえば、ユーザーが「いか」と検索したときは「するめ」を表すドキュメントをヒットさせたい、といったときに、”「いか」は「するめ」”という意味のシノニム辞書を作成します。

Azure Search でのシノニム機能

Azure Search では、現在シノニム機能は Public Preview です。よってプロダクション環境での使用は推奨されません。 また、REST API (api-version=2016-09-01-Preview)と、.NET SDK にて使えます(ポータルでは対応していません)。(GAしました。API-Versionはドキュメントにて確認します)

ステップ

シノニム機能を使うには、次の2ステップを行います。

    1. シノニムマップを作成する。
    2. (この言葉はこの言葉にマップする、という情報を登録する)
    1. インデックス定義のフィールドに、シノニムマップを適用する。
    2. (このフィールドは、このシノニムマップを参照して検索できるようにする、という情報を登録する)

この2ステップは、Azure Search releases support for synonyms (public preview) | Blog | Microsoft Azure ここに記載されている API のことです。シノニムマップは、REST API の POST・PUT のメソッドにより作成・更新します。

その他

他に以下のような特徴があります(把握している範囲です)。

  • シノニムマップを更新した場合、インデックスの再構築やサービスが中断されるということは無い。
  • 1つのフィールドには、1つのシノニムマップしか適用できない(現在は)。
  • ヒットハイライティングとスコアリングプロファイルは、元のワードと同意語の両方を同等として扱う。
  • Filter, Facet, Suggestion は、シノニムマップが適用されない。
  • シノニムマップは、Solr の SynonymFilterFactory のフォーマットで記述する。

シノニムマップのフォーマットはこんな感じです:

インターネット,internet,ワイファイ,wifi\n
five star=>高級\n
旅亭,旅荘=>旅館

インデックスの再構築を行わない、ということは、あまりに複雑で大量なシノニムマップを作成すると、検索が遅くなるのかな?と推測しています。

コンソールアプリで試してみる

ということで、日本語で試してみました。.NET SDK を使い、コンソールアプリケーションを作成します。 Azrue Search が提供するサンプル こちらをベースに、日本語に書き換えてみました。

インデックスの定義や、ドキュメントの作成など全てコンソールアプリケーションから行っています。

手順

・Azure Search をプロビジョニングします。

Azrue Search が提供するサンプルを取得し、App.config にある設定(サービス名・管理キー・クエリキー)を書き換えます。

・Hotel.cs の Category と Tags のフィールドに [Analyzer(AnalyzerName.AsString.JaLucene)] を定義します。

[IsSearchable, IsFilterable, IsSortable, IsFacetable]
[Analyzer(AnalyzerName.AsString.JaLucene)]
public string Category { get; set; }

[IsSearchable, IsFilterable, IsFacetable]
[Analyzer(AnalyzerName.AsString.JaLucene)]
public string[] Tags { get; set; }

・ドキュメントを作成している UploadDocuments メソッドにて、次のように日本語に置き換えます。

private static void UploadDocuments(ISearchIndexClient indexClient)
{
    var hotels = new Hotel[]
    {
        new Hotel()
        {
            HotelName = "東京ホテル",
            Category = "ホテル",
            Tags = new[] { "ジャグジー", "夜景", "wifi", "送迎", "高級"},
            HotelId = "1",
//略
        },
        new Hotel()
        {
            HotelName = "神奈川旅館",
            Category = "旅館",
            Tags = new[] { "温泉", "内湯", "大浴場", "露天風呂"},
            HotelId = "2",
// 略

・シノニムマップを作成している UploadSynonyms メソッドにて、次のように記述します。

private static void UploadSynonyms(SearchServiceClient serviceClient)
{
    var synonymMap = new SynonymMap()
    {
        Name = "desc-synonymmap",
        Format = "solr",
        Synonyms = "インターネット,internet,ワイファイ,wifi\nfive star=>高級\n旅亭,旅荘=>旅館"
    };

    serviceClient.SynonymMaps.CreateOrUpdate(synonymMap);
    Console.WriteLine("UploadSynonyms");
}

コードは gist にアップしました:gist

こんな感じ

以上の手順により、次の2つのドキュメントを作成しています。

Name Category Tags
東京ホテル ホテル [ジャグジー, 夜景, wifi, 送迎, 高級]
神奈川旅館 旅館 [温泉, 内湯, 大浴場, 露天風呂]

シノニムマップはこのように。

インターネット,internet,ワイファイ,wifi\n
five star=>高級\n
旅亭,旅荘=>旅館

以下の検索に対応したいため、上記のようなシノニムマップを作成しました。

  • 「"five star"」(double quot付き)で検索すると「東京ホテル」がヒットする
  • 「インターネット」で検索すると「東京ホテル」がヒットする
  • 「旅亭」で検索すると「神奈川旅館」がヒットする
結果

シノニムを作成する前 は、検索しても該当しません。

1. Search the entire index for the phrase "five star":
no document matched

2. Search the entire index for the term 'インターネット':
no document matched

3. Search the entire index for the term '旅亭':
no document matched

4. Search the entire index for the terms 'インターネット' AND 'five star':
no document matched

シノニムを作成した後は、期待したように検索結果がヒットします。

1. Search the entire index for the phrase "five star":
Name: 東京ホテル        Category: ホテル        Tags: [ジャグジー, 夜景, wifi, 送迎, 高級]
2. Search the entire index for the term 'インターネット':
Name: 東京ホテル        Category: ホテル        Tags: [ジャグジー, 夜景, wifi, 送迎, 高級]
3. Search the entire index for the term '旅亭':
Name: 神奈川旅館        Category: 旅館  Tags: [温泉, 内湯, 大浴場, 露天風呂]
4. Search the entire index for the terms 'インターネット' AND 'five star':
Name: 東京ホテル        Category: ホテル        Tags: [ジャグジー, 夜景, wifi, 送迎, 高級]
シノニムマップを更新する

さらに、「リッチ」で検索したときに「高級」でヒットするように、シノニムマップを更新します。 次のように「リッチ」を追加して、UploadSynonyms メソッドを実行します。 これはつまり、API: PUT https://[servicename].search.windows.net/synonymmaps/desc-synonymmap?api-version=2016-09-01-Preview を送信します。インデックスの定義は更新していません。

private static void UploadSynonyms(SearchServiceClient serviceClient)
{
    var synonymMap = new SynonymMap()
    {
        Name = "desc-synonymmap",
        Format = "solr",
        Synonyms = "インターネット,internet,ワイファイ,wifi\nリッチ,five star=>高級\n旅亭,旅荘=>旅館"
    };

更新後、検索すると「リッチ」で「東京ホテル」がヒットするようになりました。

5. Search the entire index for the term 'リッチ':
Name: 東京ホテル        Category: ホテル        Tags: [ジャグジー, 夜景, wifi, 送迎, 高級]