a2 Tech blog

試したこと・調べたこと・感じたことを発信するITエンジニアの日記です。仕事とは直接関係ないけど興味あることを模索していきます。

Microsoft Bot Serviceで作成したFAQボットをデバッグする

f:id:ninna2:20170815210633p:plain:w360

夏休みの宿題として、Microsoft Bot ServiceQnA Makerを使ってFAQボットを前回作成してみました。30分でノンプログラミングでできるので、興味がある人は是非試してみるとよいかなと思います。

ninna2.hatenablog.com

そんでもって、お盆休みなのでかなり手を抜いた感があったので、今回は少し技術的なことやっていこうと思います。少しだけですけどね。

技術的なことは書くと、読む人少ないんですよね。

当たり前ですけど、興味ある人限られてますしね。でも、読んでもらえなくても書くんです。1人でも読む人がいる限り。

というわけで、FAQボットをデバッグできるようにしていきましょう。 最近、わかったのですが、見切り発車で記事書いていく方が、自分として面白い。

それはたぶん先が読めないから、うまくいくかわからないからですね。 今回も見切り発車です。Bot Serviceはどうやってデバッグしていくのか、一緒に探索していきましょう。

続きを読む

30分でFAQボットを作る(Microsoft Bot Service × QnA Maker)

ここ最近、私の中で"チャットボット"がホットなテーマになってます。日本人ってチャット好きですよね。たぶん。

きっかけは1冊の本です。

チャットボット AIとロボットの進化が変革する未来

チャットボット AIとロボットの進化が変革する未来

お盆休みはだらだらと過ごしてしまったので、何か1つぐらいはブログ更新しないとなぁと思いつつ見切り発車で書いていきます。

というわけで30分でチャットボット

Azureが好きなので、Microsoft Bot ServiceQnA MakerでノンプログラミングでFAQチャットボットを作ってみます。ノンプログラミングです。お盆休みにあんまり頭を使わずにできる最大限の成果物。ブログ書きながら作っていこうと思います。なので最終的にうまくいくかどうかはわかりません。30分で終わるかどうかもわかりません。でもやってみます。

それでは始めていきます。

Microsoft Bot Serviceの作成から…

Azure Portalから、新規でBot Serviceを作成します。まだPreviewですね。新規作成は特に難しくなく作成、必要事項の入力とサクサクっと進めていきましょう。

f:id:ninna2:20170815200506p:plain

f:id:ninna2:20170815200735p:plain

新規作成すると、Azure App ServiceにBot Serviceがデプロイ開始されます。デプロイ時間はあっという間。トイレに行く暇もないぐらいすぐにデプロイ完了します。出来上がったApp Serviceを開くと、初期設定できます。まずは、アプリケーションIDの生成(Create Microsoft App ID and password)を行う必要があるので、アプリケーションIDとパスワードを生成します。パスワードは1回しか表示されないので、メモしておきます。

f:id:ninna2:20170815201542p:plain

f:id:ninna2:20170815201831p:plain

QnA Maker使うので、Question and Answerを選びます。

f:id:ninna2:20170815202243p:plain

これでCreateすればオッケーです。途中の入力項目は説明する必要もないと思うので説明はかなり省略してます。CreateするとQnA Makerのダイアログが出るので、"Sign in"して"I agree"して"OK"します。

f:id:ninna2:20170815202544p:plain

f:id:ninna2:20170815202722p:plain

これで、Bot Serviceの初期設定とQnA Makerの生成の両方ができます。少し時間がかかるので、グルグルしている間にトイレ行けます。戻ってきたら終わっているはずです。

f:id:ninna2:20170815203015p:plain

さて、これでBot Serviceは完成です。ソースコードがちらりと見えていますね。これは、Azure Functionsなので、ここから編集もすることができます。今回はノンプログラミングなので、ソースコードには見向きもしませんが。中身はすごくシンプルで、ただただ、QnA MakerのDialogを生成して呼んでいるだけのようです。ノンプログラミングなのでソースコード見なくてもいいですよ。

f:id:ninna2:20170815203422p:plain

QnA Makerを…

Bot Serviceからは一旦離れます。今の状態だと、"hi"と質問すると"hello"と返すという何とも残念なFAQボットちゃんなので、もう少し賢くしていきます。QnA Makerのページにアクセスして設定していきます。

QnA Maker

FAQの元ネタとなる情報をインプットしていく必要があるので、何かネタを…

最近、キングコング西野さんのことが気になっているので、彼が宣伝しているパインアメについてのFAQにしましょう。私にはステマソングを作る歌唱力もないので、その代わりにパインアメボットを作ってみるということで。ネタが決まったので、さっそくパインアメのサイトからFAQの情報を探します。下記にありました。ただ、少ない…もっと情報たくさんあった方が面白いけど、30分という時間では無理なので、このFAQを題材にすることにします。

よくある質問 - パイン株式会社

f:id:ninna2:20170815204653p:plain

この情報を、QnA Makerに読み込ませていく設定をしていきます。"Setting"のURLsにパインアメのサイトURLを入力します。そして、"Save and Retrain"します。パインアメの問い合わせ情報を学習してもらうわけです。これで少しパインアメについて賢くなったわけです。

f:id:ninna2:20170815203755p:plain

さて賢くなったかどうかテストしてみます。"Test"を開くと、チャット形式の画面になるので、そこから問い合わせ情報にあった内容を入れてみます。このとき、完全に一致する文章じゃなくてもある程度反応してくれます。よしよし、パインアメFAQボットの脳みそ部分ができてきました。

f:id:ninna2:20170815211604p:plain

テストがオッケーなら、Publishします。

Bot ServiceとQnA Makerの融合…

さて、QnA Makerで脳みそ部分ができたので、Bot Serviceと融合させて、パインアメにしていきましょう。あ、勝手に命名しました。ダサいってのはわかっていますが、名前を考える時間ないんので。

融合といっても、何もしなくてもBot ServiceとQnA Makerはつながってます。なので、もう完成なのです。さて、パインアメ子にアクセスしてみます。デフォルトでは、SkypeがChannel設定されているので、Skypeで会話してみます。

f:id:ninna2:20170815211817p:plain

ChannelsからSkypeを選択して、パインアメ子にコンタクトします。

f:id:ninna2:20170815210620p:plain

Skypeから適当に問い合わせをしてみると、ちゃんと答えてくれます。表示名がパインアメ子になってほしかったのに、適当に付けたIDになっているのが少し残念。なぜだ。

f:id:ninna2:20170815210633p:plain

というわけで、少しネタっぽくなってしまいましたが、Azure Bot Serviceを使うことで、簡単にFAQボットが作成できる基盤が構築できることがわかりました。簡単なものであれば、サクッとノンプログラミングでできてしまいますが、おそらくそれだけで対応できることなんて少ないと思うので、これを基本にプログラム追加して賢いFAQボットを作成していくのがよいかと思います。

もちろんもっと複雑なものは、Bot Frameworkでちゃんと作りこんでいくのも手としてありますので、私の書いた過去記事も参照してみてください。

ninna2.hatenablog.com

書評 生産性向上のための「仮説思考」(内田和成 著)

多くの情報を集めてよりよい判断が出来ているだろうか?情報集めに必死になって時間切れになっていないだろうか?働き方改革と銘打って勤務時間の短縮やテレワークなど様々な方法を企業は取り組んでいるが、個人個人がムダなことをそぎ落として、成果に直接結びつくことのみに注力することが一番大切なのではないかと思う。今回の私が読んだ「仮説思考」には方法がまさに記述されている。

エンジニアとして、「仮説思考」は身につけないといけない考え方なので、興味を持った方は是非読んでみてほしいと思います。

続きを読む

Microsoft Bot FrameworkとLUISで真面目にお天気Botを作ってみる

書店でチャットボットの書籍が目にとまり読んでみました。ついでに試してみたくなってやってみました。きっかけはそれだけですが、思った以上に作るの楽しいです。

ちなみに読んだ本はこれ

チャットボット AIとロボットの進化が変革する未来

チャットボット AIとロボットの進化が変革する未来

概要

機能としてはシンプルなBotに仕上がりました。基本ができれば、あとは好みで機能を拡張していけば良いです。少しでも引っかかる要素がある方は気軽に読んでください。

かなり長文になってます。

ゴール

表題の通り、作成するのはお天気ボットです。単に天気情報を教えてくれるだけの単純なボットです。一応、自然言語を解析しているので、ある程度の文章のばらつきや曖昧さがあっても反応はしてくれます。場所の情報がインプットされていなかったら聞いてくれます。

完成品のイメージです。1枚目がPCのSkypeから、2枚目、3枚目がスマホSkypeからです。天気情報のアイコンがかなり荒くなってしまいました。(画像のサイズの変え方がわからなかった)

f:id:ninna2:20170806004416p:plain:w360

北海道と指定すると、よくわからないところを検索結果として返して来ます。ちょっとおバカなボットです。

これは、天気APIに対する呼び出し方が悪いのでそこをもう少し工夫する必要がありそうです。まぁこれぐらいは愛嬌ということで、今はこのままです。

使用した技術要素

  • Microsoft Bot Framework
  • Azure App Service
  • Visual Studio Team Services(VSTS)
  • Translator Text API(Cognitive Service)
  • Language Understanding Intelligent Service(LUIS)
  • OpenWeatherMapAPI

Microsoft Bot Serviceを使おうかと思ったのですが、もう少し深く知りたかったので、Bot Frameworkを用いて1から作成することにしました。App Serviceは、今回作成したBotのデプロイ先として利用していて、VSTSソースコード管理しデプロイする構成を採用してます。自然言語の解析にはLUISを用いており、LUISに英語のテキストを渡すために、Cognitive ServiceのTranslator Text APIを使用しています。天気情報の取得には、OpenWeatherMapAPIを使うことにしました。

Bot Serviceについてもある程度試してみたのでまた今度、記載したいと思います。

目次

作成していった流れに沿って説明していきます。

  • Microsoft Bot Frameworkの開発環境構築
    • 環境設定
    • 初期作成
    • 実行及びデバッグ
    • Azure へデプロイするための環境設定
  • アプリケーションの概要
  • Translator Text API との連携部分の作成
    • Translator Text APIの登録
    • Translator Text APIとの連携
  • LUISとの連携部分の作成
    • LUISの登録
    • LUISとの連携
  • OpenWeatherMapAPIとの連携部分を作成
    • OpenWeatherMapAPIの登録
    • OpenWeatherMapAPIとの連携
  • Microsoft Bot Frameworkへ
    • Bot Frameworkへの登録
    • Azureへのデプロイ
    • Skypeからテスト

何度も言いますがかなり長文です。今までの中で群を抜いて長文なので読むのに時間がかかると思います。

Microsoft Bot Frameworkの開発環境構築

Bot Frameworkを開発するための環境設定を行なっていきます。MS公式サイトを参考に構築してます。

docs.microsoft.com

環境設定

IDE(統合開発環境)はVisual Studio 2017を使っていきます。VS2017をインストールします。私は、現時点でPreview版ですがVersion15.3をインストールしました。機能的には、Community版で問題ないかと思います。最新の情報は英語での記載が多いので、私はVS2017を英語表記で使ってます。ですので、説明も英語表記で記載しています。

インストールが完了したら各種extensionsを最新にしておきましょう。と書いてますが、特にUpdateが必要なものはなかったです。

Bot Frameworkを開発するために、Visual Studioのプロジェクトテンプレートをダウンロードします。 ダウンロードサイトは、下記です。(公式サイトから辿った方が良いかと思いますが、こちらにも記載しておきます)

それぞれダウンロードしたら、zip形式のままテンプレートの所定の格納先に配置します。テンプレートの配置先は[Options]-[Projects and Solutions]-[Locations]から確認することが出来ます。

f:id:ninna2:20170806004047p:plain

デフォルトでは、下記になっています。

%USERPROFILE%\Documents\Visual Studio 2017\Templates\ProjectTemplates\Visual C#\

初期作成

環境設定が出来たので、早速作成していきます。最初はとりあえず動くものを作成するところまでやっておき、少しずつ機能拡張して、完成イメージに近づけていきます。

環境設定でインストールしたVisual Studioを起動します。起動したら、新しくプロジェクトを作成していきます。テンプレートを正しく配置できていれば、Bot Applicationが選択できるようになっています。

f:id:ninna2:20170806004102p:plain

作成が出来れば、下記のように新しくソリューションとプロジェクトが構成されます。

f:id:ninna2:20170806004116p:plain:w360

NuGetでパッケージを最新化を行なっておきます。作成したプロジェクトを[右クリック]-[Manage NuGet]からBot Builderをアップデートします。執筆時点では、Version3.9が最新でした。

f:id:ninna2:20170806004124p:plain

これで概ね動くはずです。初期構成としてちゃんとソースコード管理を行いたいので、作成したソリューションとプロジェクトをVisual Studio Team Services(VSTS)のGit RepositoryへPushしておきます。細かい手順は、過去に記事にしている内容を参照いただければと思います。VSTSでプロジェクトを作成して、VS2017のTeam ExplorerからVSTSへConnectしてPushするだけです。

ninna2.hatenablog.com

実行及びデバッグ

作成したプロジェクトを実行していくのですが、Botにアクセスするためには、チャットをエミュレートするエミュレータが必要です。

公式のエミュレータとし“botframework-emulator”が提供されていますので、インストールします。現時点での最新は、Version3.5.29です。

github.com

作成したプロジェクトを実行していきます。実行は、VS2017から実行(F5)で出来ます。この際にデフォルトのブラウザが"Google Chrome"になっていたのでブラウザ側でエラー表記が出ました。

f:id:ninna2:20170806004137p:plain:w480

f:id:ninna2:20170806004149p:plain:w240

Bot自身の問題ではないので無視しても良いかとは思いますが、IEでアクセスするようすれば、正常にアクセス出来るのでその方が良いかと思います。

f:id:ninna2:20170806004157p:plain:w480

Botが起動したので、"botframework-emulator"からアクセスしてテストしてみます。emulatorの左上部にURLを入力する箇所があるので、規定値の"http://localhost:3979/api/messages"と入力してConnectします。AppIDやPasswordを入力する箇所がありますがローカルでのアクセスには使いませんのでブランクで構いません。接続後に、チャット入力(左下)してみると、入力した文字数を返してくれるはずです。

f:id:ninna2:20170807170804p:plain

この状態まで実行できていれば、デバッグもできますので、チャットで文字列を送信した後の処理で ブレイクポイントを貼っておけば止めれます。これでローカルで開発できるようになりました。

Azure へデプロイするための環境設定

ローカルでの開発、実行、デバッグができるようになったので、この初期BotアプリケーションをAzure上にデプロイするしていきます。初期の段階からデプロイする設定を固めておけば、後から困ることもないので、本格的に作成する前にしっかり設定を入れていきます。

Botアプリケーションは、Azure App Serviceで動きます。PaaS上で動くのでサーバの設定とかほとんど気にせず公開することが出来ます。ローカルからAzure App Serviceにデプロイする方法は3種類あります。

Visual Studio から直接Azure App Serviceへ

VS2017から直接アプリケーションを発行する方法です。個人的に開発して試しに乗せてみるみたいな使い方としては良いですが、ちゃんとした開発ではあまりやらない方法かと思います。なので、今回は割愛します。下記のサイトに詳しく書いてあるのでお手軽に試したい場合はやってみてください。

docs.microsoft.com

Azure Web Appのデプロイオプションで

Azure App Service(Web App)を先に作成し、リポジトリと連携させる方法です。ソースコード管理と連携しているので、リポジトリにPushするたびにデプロイされることになります。

設定については、公式サイトに載っているので参考にしてみてください。

github.com

Visual Studio Team ServicesでCI/CDパイプライン

VSTSのGit Ripositoryでソースコード管理をしている場合には、VSTSからビルド・デプロイする方法もあります。Buildテンプレートとして“Azure Web App"用のテンプレートがあるので、必要事項を入力していくだけで簡単にCI/CDパイプラインが構築できます。

f:id:ninna2:20170806004241p:plain

f:id:ninna2:20170806004309p:plain

Agentは"HostedVS2017"でビルドし、Trriger CI をOnにしておけば、Pushするたびにビルドが走り、Azureへデプロイするように構成を出来ます。ビルドとデプロイを同時に行う構成にしてますが、分けることも可能なので、ビルドして承認してデプロイみたいなフローも柔軟に組めます。

VSTSについては、過去記事にも取り上げているので参考にしてみてください。

ninna2.hatenablog.com

私はこの方法でデプロイの構成を組んでます。

AzureにデプロイしたBotのテスト

ローカルで実行した方法と同様に、Azureに載せた初期Botアプリケーションもbotframework-emulatorを起動してテストを行なってみます。

接続先を、Azure App ServiceのURLに"/api/messages"を付与したものに書き換えれば繋ぐことができるはずです。

f:id:ninna2:20170806004325p:plain

ここまで確認できれば、ローカルで開発し、PushするとAzureへのデプロイするところまでいつでも出来ます。

アプリケーションの概要

さて、開発環境も整いましたので、お天気Botを本格的にコーディングしていきます。どのような構成にしようか試行錯誤した結果、下記のような構成になりました。

f:id:ninna2:20170806010716p:plain

全体のソースコードGitHubに公開してます。

github.com

Botアプリケーションの処理の流れとしては、チャットから文字列の入力があるとMessageControllerに入力が行き、Translator Text APIを呼び出して日本語から英語に翻訳します。LUISは日本語対応しているのですが、"Prebuilt domains"としてすでに構築された"Weather"は英語でのみ提供されているので、日本語から英語に翻訳してLUISに渡します。LUISで自然言語の解釈をした上で天気情報だと判断したら天気APIを呼び出してます。Location情報が入力されていない場合には、入力を促すようにしています。Location情報が入力されたら、天気情報を取得します。天気情報を取得するAPIは、OpenWeatherMapAPIを使っていて天気情報と天気アイコンを取得して返してます。

このような流れのものを作っていきます。

Translator Text API との連携部分の作成

Translator Text API(Cognitive Service)はAzure上に構築した上で、APIKeyを取得する必要があります。ですので、まずは登録を行なって、その上で、Botに組み込んでいきます。

ちなみに、Translator Text APIの公式は下記です。

azure.microsoft.com

Translator Text APIの登録

Azure Portal(http://portal.azure.com)にアクセスして追加していきます。[+ New option]-[AI + Cognitive Services]-[Translator Text API]です。かなり説明を省略しましたが、わかるはずです。わからない場合は、Translator Text APIの公式を参照ください。デプロイできるとこのような感じでURLが付与されます。

f:id:ninna2:20170806004614p:plain

ここまでできるとほぼ使える状態なので、あとはAPIKeyの確認をします。Translator Text APIのKeyは10分しか有効期間がないらしく、アプリから実行する際には、10分おきに再取得する必要があるようです。実装もそうなってます。

Translator Text APIとの連携

Translator Text APIの実装は、下記のサイトを参考にさせてもらいました。参考にさせてもらったというかほとんど真似た、少し自分でわかりやすくしたという表現の方が正しいかもしれません。

GitHub - alyssaong1/BotTranslator: Language translation with the Microsoft Bot Framework using the Bing Translator API

ポイントを解説しておきます。スマホだとソースコード読みにくいですね。PCでじっくり見てもらった方がよいかもしれません。

Translator Text APIを呼び出すためのクラスとしてService/MessageTranslatorServiceを作成しています。中身はシンプルで、Translateというメソッドのみ提供しています。引数は翻訳するテキストをインプットにしています。

Translateの処理の中身は、Keyの有効期間を確認(RefreshTokenメソッドに実装)してHTTPClientで呼び出しているだけです。結果はXML形式で返ってくるので、文字列を取り出して返却するようにすれば完成です。

public async Task<string> Translate(string text, string FromLocale, string ToLocale)
{
    //token times out 10 minutes.
    await RefreshToken();

    using (var httpClient = new HttpClient())
    {
        string urlEncodedText = HttpUtility.UrlEncode(text);
        httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", Token);
        string url = $"{TranslateBaseURI}?text={urlEncodedText}&from={FromLocale}&to={ToLocale}";
        HttpResponseMessage response = await httpClient.GetAsync(url);

        if (response.IsSuccessStatusCode)
        {
            string responseContent = await response.Content.ReadAsStringAsync();
            XmlDocument document = new XmlDocument();
            document.LoadXml(responseContent);
            return document.InnerText;
        }
        else
        {
            return null;
        }
    }
}

このサービスの呼び出しは、Controller/MessagesControllerから行います。LUISを呼び出す前に翻訳を行います。activityの中身を直接翻訳して差し替えてます。

public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
    if (activity.Type == ActivityTypes.Message)
    {
        activity.Text = await MessageTranslatorService.Current.Translate(activity.Text);
        // LUISの呼び出し
    }
    // 後続処理

これで日本語で入力されたチャットメッセージをLUISに渡す前に翻訳してから、LUISに流すことができそうです。

ちなみに、Translator Text APIに限らずAPIKeyはWeb.Configに記載するようにしています。この辺りの実装方法はもっとちゃんと考えないといけないかもしれないです。(普通どうやるんだろ?環境変数?)

<appSettings>
    <!-- update these with your BotId, Microsoft App Id and your Microsoft App Password-->
    <add key="BotId" value=""/>
    <add key="MicrosoftAppId" value=""/>
    <add key="MicrosoftAppPassword" value=""/>
    <!-- Luis settings-->
    <add key="LuisAppId" value=""/>
    <add key="LuisAPIKey" value=""/>
    <!-- OpenWeatherMapAPI-->
    <add key="OpenWeatherAPIKey" value=""/>
    <!-- Translator Text API-->
    <add key="TranslatorAPIKey" value=""/>
</appSettings>

LUISとの連携部分の作成

Translator Text APIで入力された文字列の翻訳ができたので、英文を解析するLUIS部分の実装をやっていきます。LUISもAzureのサービスですので、まずは登録して使えるようにしてから、Botアプリケーションに組み込んでいきます。

LUISの登録

LUISを始めるには、公式ページ(https://www.luis.ai/home)から登録していきます。初回登録のやり方は割愛します。Azureのアカウントがあれば簡単に出来るはずです。

[New App]から、新しく作成していきます。今回は、英語版を使いたいので、CultureはEnglishにします。日本語がどれぐらい使えるのかはまた今度やってみたいと思います。

f:id:ninna2:20170806004430p:plain:w360

作成したAppを選択して、左のメニューから[Prebuilt domains]を選択します。事前構築済みのものが色々でてくるので、下の方にある"Weather"をAddします。

f:id:ninna2:20170806004448p:plain

これで天気に関するIntentsとEntitiesが作成されます。Intentsが文章の意味や意図の部分で、Entitiesがその中でのキーワードのような感じです。"Weather"では、Intentとして"Weather.GetCondition",“Weather.GetForcast"の2つが、Entityとして"Weather.Locaton"が登録されます。文章を解析して、意図としてConditionに関連しているのか、Forcastに関連しているのか判別してくれて、文章の中に、Locationに関するキーワードが含まれていて、それがなんなのかを解析してくれるというわけです。

Intentsが1枚目、Entitiesが2枚目です。

f:id:ninna2:20170806004459p:plain

f:id:ninna2:20170806004525p:plain

簡単にちゃんと文章(英語)が解析されるかテストしてみます。[Train & Test]で[Train]させてから、文章を入力します。仮に"what is the weather in kyoto?“とか入れてみたら、ちゃんと解析して判別してくれています。

f:id:ninna2:20170806004536p:plain

ここまで出来れば、Publishしてアプリケーションから使える状態にしていきます。[Publish App]-[Piblish]します。

f:id:ninna2:20170806004550p:plain

Endpoint URLが発行されるのでブラウザからアクセスしてみます。その際に"q=“の後ろに解析したい文章をつけると結果がちゃんと返ってきます。

f:id:ninna2:20170806004603p:plain

Endpoint URLの"apps/“の後ろの部分の文字列がAppID、"subscription-key="の後ろがAPIKeyになります。Web.configに記載します。

これで、LUISは使えるようになりました。

LUISとの連携

LUISとの連携部分は、LuisDialogというクラスを継承して作っていきます。すでにあるDialogは削除してしまって構いません。Dialog/RootLuisDialogというクラスを作成します。

[Serializable]
public class RootLuisDialog : LuisDialog<object>
{
・・・
}

LuisIntentというタグをつけることで、解析結果によって呼び出すメソッドを変えることが出来ます。今回は、2つのIntentどちらの場合も、天気APIを呼び出すようにするので、下記のようになります。どちらにも該当しない場合は"None"に振り分けられます。

[LuisIntent("Weather.GetForecast")]
[LuisIntent("Weather.GetCondition")]
public async Task getWeatherIntent(IDialogContext context, LuisResult result)
{
・・・
}

中の処理には、FormFlowを使っています。説明するのすごく難しいので、公式サイトの例を読んでもらうのが良いかと思います。

docs.microsoft.com

FormFlowを使うとあるクラス(今回の場合はWeatherQueryクラス)に定義されたプロパティを埋めていく処理を簡単(!?)に実装できます。

入力がされていないプロパティがあったら、入力を促すようにできるわけです。Locationは今回必須なので、解析した結果にLocationが入力されていない場合に、"どこの天気が知りたいの?"と聞くようにしています。全てのプロパティが揃えば、後続処理(天気APIの呼び出し)を行うようにしています。

[LuisIntent("Weather.GetForecast")]
[LuisIntent("Weather.GetCondition")]
public async Task getWeatherIntent(IDialogContext context, LuisResult result)
{
    var weatherQuery = new WeatherQuery();

    EntityRecommendation LocationRecommendation;
    if (result.TryFindEntity(ENTITY_LOCATION, out LocationRecommendation))
    {
        weatherQuery.Location = LocationRecommendation.Entity;
    }

    var weatherFormDialog = new FormDialog<WeatherQuery>(weatherQuery, this.BuildWeatherForm, FormOptions.PromptInStart, result.Entities);
    context.Call(weatherFormDialog, this.ResumeAfterFormDialog);
}

private IForm<WeatherQuery> BuildWeatherForm()
{
    OnCompletionAsyncDelegate<WeatherQuery> processWeatherSearch = async (context, state) =>
    {
        var message = "Searching...";
        if (!string.IsNullOrEmpty(state.Location))
        {
            message += $" in {state.Location}...";
        }
        await context.PostAsync(message);
    };

    return new FormBuilder<WeatherQuery>()
        .Field(nameof(WeatherQuery.Location))
        .OnCompletion(processWeatherSearch)
        .Build();
}

private async Task ResumeAfterFormDialog(IDialogContext context, IAwaitable<WeatherQuery> result)
{
    // 天気APIの呼び出し処理
}

連携の最後は、Controller/MessageControllerから呼び出すようにします。Translator Text APIで翻訳するの処理の後に処理を追加します。

public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
    if (activity.Type == ActivityTypes.Message)
    {
        activity.Text = await MessageTranslatorService.Current.Translate(activity.Text);
        await Conversation.SendAsync(activity, () => new Dialogs.RootLuisDialog());
    }
・・・

ポイントだけ説明しましたが、これでLUISとの連携ができました。Translator Text APIで翻訳した文章を自然言語の解析を行なって、文章の意味とキーワードを抽出できたので、天気情報を取得する処理に次はつなげていきたいと思います。

OpenWeatherMapAPIとの連携部分を作成

実装もかなり進んできました。LUISによる自然言語処理で言葉の意図とキーワードの抽出ができたので、その情報を元に天気情報を取得する部分を作っていきます。天気情報を取得するAPIはいくつかありますが、無料で使用することができるOpenWeatherMapAPIを使用します。無料版はいくつか制約がありますが無料版でいきたいと思います。

OpenWeatherMapAPIの登録

アクセスするにはやはりAPIKeyが必要なので、ユーザ登録をしてAPIKeyを取得します。OpenWeatherMapAPIの公式サイトのにアクセスします。

openweathermap.org

ユーザ登録はメールアドレスのみで出来ます。登録後にAPIKeyをメモっておいてください。

OpenWeatherMapAPIとの連携

実装は、下記のソースコードを大いに参考にさせていただきました。

GitHub - DiegoBao/OpenWeatherMapApi: C# Api Client for OpenWeatherMAP

Service/OpenWeatherMapServiceにAPIアクセス用のサービスクラスを作成しています。GetWeatherAsyncメソッドにて、OpenWeatherMapAPIにアクセスして情報を取得して、返ってきたjsonをクラスに読み込んでいます。処理としては単純です。

public async Task<WeatherInfo> GetWeatherAsync(WeatherQuery weatherQuery)
{
    using (var httpClient = new HttpClient())
    {
        httpClient.BaseAddress = new Uri(OpenWeatherBaseurl);
        httpClient.DefaultRequestHeaders.Add("x-api-key", this.ApiKey);
        string url = string.Format(Query, weatherQuery.Location, "Metric", "jp");
        HttpResponseMessage response = await httpClient.GetAsync(url);

        if (response.IsSuccessStatusCode)
        {
            var json = await response.Content.ReadAsStringAsync();
            JsonSerializerSettings settings = new JsonSerializerSettings();
            settings.Culture = new System.Globalization.CultureInfo("en-us");
            return JsonConvert.DeserializeObject<WeatherInfo>(json, settings);
        }
        else
        {
            return null;
        }
    }
}

呼び出し側は、FromFlowの処理とが終わった後の処理部分(RootLuisDialogクラスのResumeAfterFormDialog)で呼び出しています。呼び出し後に天気アイコンのIDから、アイコン画像を引っ張ってきています。

private async Task ResumeAfterFormDialog(IDialogContext context, IAwaitable<WeatherQuery> result)
{
    try
    {
        var searchQuery = await result;
        var weatherInfo = await weatherService.GetWeatherAsync(searchQuery);

        // 後続処理省略(アイコン情報の取得)
    }
}

これでTranslator Text APIで日本語から英語に翻訳して、そのセンテンスをLUISが解釈してOpenWeatherMapAPIにて天気情報を取得する一連の流れが出来ました。

Microsoft Bot Frameworkへ

お天気Botアプリケーションが完成したのでこいつをSkypeから呼べるようにしていきたいと思います。呼ぶためには、Bot Frameworkへの登録を行い、Connectorから呼び出します。

Bot Frameworkへの登録

Bot Frameworkへの登録は、下記から行います。Azureのアカウントがあればサインイン出来るはずです。

https://dev.botframework.com/

アクセスしたら[MyBots]から[Create Bot]を行います。必要事項は適時入力します。

f:id:ninna2:20170806004345p:plain

ここで重要なのはEndpointの設定です。Endpointは、Azure Web Appで公開しているURLを"https://“に変更して ”/api/messages"を付与したアドレスです。このアドレスが間違っているとうまく連携できません。

また、[Create Microsoft App ID and Password]から AppIDとPasswordを生成します。Passwordは1回しか表示されないのでメモるの忘れないでください。

全ての入力できたら[Register]で登録完了します。

Azureへのデプロイ

さて、登録すると[Test]というタブが現れるので早速テストしてみたくなるのですが、先ほど取得したKey情報をお天気Botアプリケーション側に埋め込まないと動きません。

Web.configにBotID(Bot handle)、AppID、passwordを記載して、Azure Web Appにデプロイしてください。私はVSTS経由でCIできるように設定しているので、VSTSのGit RipositoryにPushしたら自動的にデプロイされます。

Skypeからテスト

これで最後です。Channelから初期設定として存在しているSkypeをクリックしてSkypeと連携します。Skypeからお天気Botにメッセージを送って天気情報を返してくれたら成功です。うまくいかない場合は、Endpointが合っているか、AppIDなどの情報がちゃんと記載されているかを確認してみてください。

f:id:ninna2:20170806004416p:plain:w360

ひとまずこれで完成です。

かなり長々と書いてしまいました。何回かに分割して投稿した方が良かったかもしれないですが、Bot Frameworkを使って独自のBotを作るために必要な情報を盛り込んだつもりです。まだまだ機能拡張する余地はありますし、まだ天気情報しか返せないおバカなBotです。(北海道は全然違うとこの情報返してくるしね)

チャットボットはチャットというUIを使うので、画面設計というUI設計はないわけです。代わりに、会話部分がUIになるので、どのようなストーリで会話を組み立てていくのかという視点でのデザインが必要です。そこをいかに使用する人の立場でイメージ出来るのか次第なのかなと思いました。

お天気Botだとどういう使われ方をするのか、どういう展開で必要なインプットを引き出すのか、どういう情報を提供する必要があるのか、またそれが自然な会話の流れになっているか、みたいなところまで想像力を働かせていかないと良いものはできないですね。

Bot Framework よりも簡単に作成できるBot Serviceも興味あるので、近いうちにそちらも試してみようと思います。

長々とお付き合いいただきありがとうございました。では!

WindowsとServerspecでいろいろ

f:id:ninna2:20170626235006j:plain:w240

Windwos環境のインフラテストをするために、Serverspecでいろいろ試してみました。基礎的な使い方しか書いてません。他をググれば出てくることばかりです。本人の備忘程度に書いたと思っておいてください。高度な使い方は、もう少しちゃんと勉強してから改めて書こうと思います。どうすれば効率的にServerspecを使いこなせるかとか、こんな構成で書いていけばいいよ、みたいなもう少し高度なことまで将来的に書ければいいかな。とりあえず今回は基礎の基礎です。がっかりしないでね。

続きを読む

レビュー:Logicool Flowで複数PCを行き来するマウス/キーボード(M590/K375)を体験

f:id:ninna2:20170707214354j:plain:w360

複数のPCを使っていて、マウスとキーボードがデスクの上にたくさん。そんな人には是非オススメ。私もデスクには2台のデスクトップPCがあり、マウスが2つ、キーボードが2つ、場所を取っていました。1台のマウスとキーボードで2台のPCを制御出来ればなぁってずっと思ってました。

たまたまネットニュース(Lineで流れてくるニュースだったかな)を眺めていてLogicoolから新しいマウスが発表されるというのを知りました。普段だとさらっと流し読みしてふーんって感じでスルーすると思うのですが、なぜか目にとまりました。新製品は自分が常日頃こうできたらいいのになぁって思っていたことが出来るのではと感じ、早速、購入して試してみました。

今回はその使用した感想を書きます。購入を考えている人の役に立てばと思います。発売直後に購入したのですが忙しくてブログに書くのかなり遅れました。

続きを読む

Win32 OpenSSHがPermission deniedでつながらない場合の対処法

2017/05 中旬ぐらいにAzure上のWindows ServerにOpenSSHをインストールしてSSH Over HTTPで対象のサーバとSSHを確立して、SSHポート転送でRDPをフォワードするという方法の記事を書きました。同じような環境を作りたくて同じ手順で、再度構築したらはまってしまったので、備忘として残しておきます。

続きを読む