こんにちは。エクセルソフトの田淵です。
「Bot Framework Composer でチャットボットを作成し Xamarin アプリから接続する」の最終回です。
その1ではローカルで動作する Bot を Bot Framework Composer を使って作成する方法を記載しました。
その2では作成した Bot を Azure Bot Service で動かす方法を記載しました。
今回はやっと、Xamarin アプリから接続する方法です!
本エントリーは
の 21日目の投稿です。
完成品
こんな感じです。
ソースコード
ソースは以下にアップしてあります。
Direct Line API とは
公式ドキュメントにあるように、
認証>会話の開始>メッセージの送信>メッセージの受信
を行います。
開発用にということで、以下の記述があります。
Bot Framework では、C# と Node.js からの Direct Line API 3.0 へのアクセスを容易にするクライアント ライブラリが提供されています。
- Visual Studio プロジェクト内で .NET クライアント ライブラリを使用するには、Microsoft.Bot.Connector.DirectLine NuGet パッケージをインストールしてください。
が、この Microsoft.Bot.Connector.DirectLine が古いです。
Version | Downloads | Last updated |
---|---|---|
3.0.2 | 190,316 | 2017/05/10 |
もう少し公式ドキュメントを探すと、以下のドキュメントが見つかります。
ここには、
Direct Line App Service 拡張機能との対話は、従来の Direct Line とは異なる方法で行われます。これは、ほとんどの通信が WebSocket で行われるためです。 更新された Direct Line クライアントには、WebSocket の開始/終了、WebSocket 経由でのコマンドの送信、およびボットから返される Activity の受信のためのヘルパー クラスが含まれています。
とあるので、Microsoft.Bot.Connector.DirectLine v3.0.2 で Rest Client としてのライブラリは開発が止まっていて、WebSocket を使うライブラリとして新しくリリースされることになるのではないでしょうか。
まずは、今までの Rest Client のライブラリを使ったクライアントの作成をしてみました。
Xamarin 用コードの解説
基本的には @okazuki さんのソースの焼き直しですw
基本の流れ
私のソースはかずきさんのを参考にしながら Prism で作成してありますが、基本的な流れは最初にお伝えした
認証>会話の開始>メッセージの送信>メッセージの受信
です。具体的には以下のようなコードで実現できます。ManualConversationPage はコードビハインドで素直に以下のコードを叩いているので流れは分かりやすいかと思います。
using Microsoft.Bot.Connector.DirectLine; string watermark = ""; // クライアントを作成して会話を開始し、ConversationID を取得します。 var client = new DirectLineClient("<YOUR_SECRET>"); var conversation = await client.Conversations.StartConversationAsync(); var conversationId = conversation.ConversationId; // ConversationID を指定して会話を開始します。 // ポストした際に得られる responseId を控えておきます。 var response = await client.Conversations.PostActivityAsync( ConversationIDLabel.Text, new Activity { From = new ChannelAccount("<ANY_ACCOUNT>"), Text = "<ANY_TEXT>", Type = ActivityTypes.Message, }); var responseId = response.Id; // ConversationID を指定して、Activity(複数)を取得します。 // 毎回 ActivitySet の Watermark が更新されるのでそれを指定することで最新のやり取りのみを取得できます。 var activities = await client.Conversations.GetActivitiesAsync(conversationId, watermark); watermark = activities.Watermark; // 取得した Activities コレクションの中から主要なプロパティを表示します。 foreach (var act in activities.Activities) { Console.WriteLine($"Channel ID: {act.ChannelId} ID: {act.Id}, ReplyToID: {act.ReplyToId}, Text: {act.Text}\n"); } // responseId が `ReplyToId` と同じであれば Bot からの返信ということが分かります。 var result = activities.Activities.LastOrDefault(x => x.ReplyToId == responseId);
コメントにあるとおりポストしてポストの ID を取得して、 ReplyToId
が同じものがそのポストに対する返信。という流れなのでそれに沿ってアプリのビューを組み立てていけば OK です。
Rest Client では同じ会話の ID はすべて activities (Microsoft.Bot.Connector.DirectLine.ActivitySet)
の Activities (IList<Activity>)
に入りますが、ActivitySet.Watermark
が更新されるので、その値を GetActivitiesAsync
の第二引数に渡してあげることで最新の情報のみが取得できます。
Watermark を指定しないと以下のように全部取れます。それぞれの会話の ID は、最初に発行される ConversationId
に |0000001
のように数字が付与されたものになっています。
Thumbnail card を取得する方法
応用編として、Bot 作成の段階で Thumbnail Card を使っています。
Bot Framework Composer でチャットボットを作成し Xamarin アプリから接続する~その1 - Xamarin 日本語情報 の
[ThumbnailCard title = @{dialog.weather.name} の天気 text = 天気は {dialog.weather.weather[0].description}、@{dialog.weather.main.temp}℃です。 image = https://openweathermap.org/img/wn/{dialog.weather.weather[0].icon}@2x.png ]
の部分です。
Thumbnail Card を作ると、Text
プロパティではなく、上記の配列ほぼそのままの JSON が Activities.Attachments.Content
として取得できます。また、その際に通常は null
な AttachmentLayout
が list
になっています。(上記の JSON の通りであれば、images[]
となぜ image
だけ配列なのかさっぱり分からんのですが、weather[0].icon
を使っているからですかね…w)
そのため以下のようにして、Thumbnail Card だった場合は JSON をデシリアライズして Activity
をプロパティに持つ CardActivity
に移し替えて、それ以外は何もせずに CardActivity
に移し替えています。
foreach (var message in result.messages) { if (message.AttachmentLayout == "list") { var cardActivity = new CardActivity(message); var json = message.Attachments.FirstOrDefault()?.Content.ToString(); var cardInfo = JsonConvert.DeserializeObject<CardInfo>(json); cardActivity.CardTitle = cardInfo.CardTitle; cardActivity.CardText = cardInfo.CardText; cardActivity.CardImage = cardInfo.CardImages.FirstOrDefault()?.Url; Messages.Add(cardActivity); } else { Messages.Add(new CardActivity(message)); } }
CardActivity
クラスはこんな感じ。
public class CardActivity { public string CardTitle { get; set; } public string CardText { get; set; } public string CardImage { get; set; } public Activity Activity { get; } public CardActivity(Activity activity) { Activity = activity; } }
ListView の Template 切り替え
結果として、ListView の Template は 3つ作り(リンクは GitHub)、以下の Template Selector でテンプレートを切り分ける。という処理を行っています。
if (((CardActivity)item).Activity.AttachmentLayout == "list") return CardTemplate; return ((CardActivity)item).Activity.From.Id == "ytabuchichatbot" ? OutputTemplate : InputTemplate;
CardActivity か、それ以外。それ以外は Azure Bot Service の表示名が取得できる From.Id
で決め打ちしちゃっています。 Azure Bot Service の表示名は「Web アプリボット>設定」から確認できます。
最終的に View を表示している部分は以下です。
<StackLayout> <ListView VerticalOptions="FillAndExpand" HasUnevenRows="True" ItemTemplate="{StaticResource messageTemplateSelector}" ItemsSource="{Binding Messages}" SelectionMode="None" SeparatorVisibility="None" /> <StackLayout Margin="15,5" Orientation="Horizontal"> <Entry HorizontalOptions="FillAndExpand" Text="{Binding InputMessage, Mode=TwoWay}" /> <Button Padding="10" Command="{Binding SendCommand}" Text="Send" /> </StackLayout> </StackLayout>
まとめ
Bot Framework Composer で作成した Azure Bot Service に Direct Line API SDK を使ってアクセスする方法を紹介しました。
これで、WPF/UWP でも、ASP.NET でも専用の UI で Bot とやり取りすることができるようになりました!
(Rest API をループしてコールしているので、無駄が多いですね。今後は後述する WebSocket ベースでやるのが良いと思います。)
まとめのおまけ
Xamarin でできないかなーと探している時に正に!という公式ドキュメントを見つけました。
ボットが実行されるクロスプラットフォーム モバイル アプリの作成
この例では、クロスプラットフォーム モバイル アプリケーションを構築するための一般的ツールである Xamarin を使用して、ボットが実行されるモバイル アプリを作成します。
おお!
最初に、シンプルな Web ビュー コンポーネントを作成し、それを使って Web チャット コントロールをホストします。 次に、Azure portal を使用して、Web チャット チャネルを追加します。 次に、登録されている Web チャット URL を、Xamarin アプリ内の Web ビュー コントロールのソースとして指定します。
うん?
public class WebPage : ContentPage { public WebPage() { var browser = new WebView(); browser.Source = "https://webchat.botframework.com/embed/<YOUR SECRET KEY HERE>"; this.Content = browser; } }
Web チャット!笑
まぁ、Web チャットもかなりカスタマイズできるようなのでまずはこっちでやってみるというのも手ではないでしょうかw
Direct Line の新しい WebSocket ベースのライブラリ
最初の方にも記載しましたが、以下の公式ドキュメントで新しい WebSocket ベースのライブラリが紹介されています。
実際に .NET Core 2.2 のプロジェクトを作ってドキュメントの通りやってみました。
string endpoint = "https://<YOUR_BOT_HOST>.azurewebsites.net/.bot/"; string secret = "<YOUR_BOT_SECRET>"; var tokenClient = new DirectLineClient( new Uri(endpoint), new DirectLineClientCredentials(secret)); var conversation = await tokenClient.Tokens.GenerateTokenForNewConversationAsync(); // ←ここでエラー
2019年12月中旬現在では、トークンを取得するためにエンドポイントから Conversation を取得する上記の部分で、
$exception {"Response status code indicates server error: 404 (NotFound)."} Microsoft.Rest.TransientFaultHandling.HttpRequestWithStatusException
のエラーが出ていて、conversation が null のままになってしまいます。
エンドポイントを https://yt-bot-sample.azurewebsites.net/api/messages/.bot/
にしてみたり、ストリーミングエンドポイントにチェックが入っていなかったので付けてみたり、
F0(無料枠)から S1(一般枠 99.9% の SLA が付く代わりに 1,000メッセージあたり 56円が掛かる)にしてみたり、
してみましたが、404 が解消できませんでした。どなたか動いた方がいらっしゃったら本エントリーのコメントか、@ytabuchi までメンション頂けると嬉しいです。
以上です。