なんとなく動いた・・・で終わらないためのMicrosoft Bot Framework ～ そのいち Activity～


今更ながらMicrosoft Bot Framworkに触れてみた。基本的な使い方等は様々なサイトで紹介されているので割愛して、ここではBot FrameworkでTutorialを試して動作はしたけどちゃんと理解できているのか自身がない・・・という方を対象にしている。
なお、チュートリアルを試したことない方は [こちら](http://aka.ms/caapdxjp) のコンテンツがかなりお勧めである。Qiitaでも例えば[これ](http://qiita.com/annie/items/edc26c0ee9603e84a2e4)など多くの記事がある。

# 検証環境
* **Windows 10**
* **Visual Studio 2017**
* **Bot Framework Emulator**


# 
Visual Studioでソリューションを作成すると以下のような構成で展開される。ASP.NET  Web APIの開発経験がある方であればWeb ApiConfig.cs, Controllersがあることに気づくだろう。

![image](https://qiita-image-store.s3.amazonaws.com/0/33998/2644538b-8ea6-34ce-192e-9a04f1b18b5a.png)

デフォルトで作成されるMessagesControllerはSystem.Web.Http.ApiControllerを継承している。つまりASP.NET Web APIと同様の仕組みであることがわかる。また、Botの会話を処理する名前空間 Microsoft.Bot.Builder.Dialogs、Microsoft.Bot.Connectorが含まれていることも興味深い。


```csharp
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web.Http;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Connector;

namespace BotAppTest
{
    [BotAuthentication]
    public class MessagesController : ApiController
    {
        /// <summary>   
        /// POST: api/Messages
        /// Receive a message from a user and reply to it
        /// </summary>
        public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
        {
            if (activity.Type == ActivityTypes.Message)
            {
                await Conversation.SendAsync(activity, () => new Dialogs.RootDialog());
            }
            else
            {
                HandleSystemMessage(activity);
            }
            var response = Request.CreateResponse(HttpStatusCode.OK);
            return response;
        }

```

上記のソースコードからわかる通り、Bot Framework 3.0プロトコルのメッセージ交換で中心的な役割を果たすクラスが**Microsoft.Bot.Connector.Activity**である。Activityは次のプロパティで構成されている。

```csharp
        //     ID of the channel where the activity was sent
        [JsonProperty(PropertyName = "channelId")]
        public string ChannelId { get; set; }
        //     Sender address
        [JsonProperty(PropertyName = "from")]
        public ChannelAccount From { get; set; }
        //     Conversation
        [JsonProperty(PropertyName = "conversation")]
        public ConversationAccount Conversation { get; set; }
        //     (Outbound to bot only) Bot's address that received the message
        [JsonProperty(PropertyName = "recipient")]
        public ChannelAccount Recipient { get; set; }
        //     Format of text fields [plain|markdown] Default:markdown
        [JsonProperty(PropertyName = "textFormat")]
        public string TextFormat { get; set; }
        //     Hint for how to deal with multiple attachments: [list|carousel] Default:list
        [JsonProperty(PropertyName = "attachmentLayout")]
        public string AttachmentLayout { get; set; }
        //     Array of address added
        [JsonProperty(PropertyName = "membersAdded")]
        public IList<ChannelAccount> MembersAdded { get; set; }
        //     Array of addresses removed
        [JsonProperty(PropertyName = "membersRemoved")]
        public IList<ChannelAccount> MembersRemoved { get; set; }
        //     Conversations new topic name
        [JsonProperty(PropertyName = "topicName")]
        public string TopicName { get; set; }
        //     True if the previous history of the channel is disclosed
        [JsonProperty(PropertyName = "historyDisclosed")]
        public bool? HistoryDisclosed { get; set; }
        //     The language code of the Text field
        [JsonProperty(PropertyName = "locale")]
        public string Locale { get; set; }
        //     Content for the message
        [JsonProperty(PropertyName = "text")]
        public string Text { get; set; }
        //     SSML Speak for TTS audio response
        [JsonProperty(PropertyName = "speak")]
        public string Speak { get; set; }
        //     Indicates whether the bot is accepting, expecting, or ignoring input
        [JsonProperty(PropertyName = "inputHint")]
        public string InputHint { get; set; }
        //     Text to display if the channel cannot render cards
        [JsonProperty(PropertyName = "summary")]
        public string Summary { get; set; }
        //     SuggestedActions are used to provide keyboard/quickreply like behavior in many
        [JsonProperty(PropertyName = "suggestedActions")]
        public SuggestedActions SuggestedActions { get; set; }
        //     Attachments
        [JsonProperty(PropertyName = "attachments")]
        public IList<Attachment> Attachments { get; set; }
        //     Collection of Entity objects, each of which contains metadata about this activity.
        //     Each Entity object is typed.
        [JsonProperty(PropertyName = "entities")]
        public IList<Entity> Entities { get; set; }
        //     Channel-specific payload
        [JsonProperty(PropertyName = "channelData")]
        public object ChannelData { get; set; }
        //     ContactAdded/Removed action
        [JsonProperty(PropertyName = "action")]
        public string Action { get; set; }
        //     The original ID this message is a response to
        [JsonProperty(PropertyName = "replyToId")]
        public string ReplyToId { get; set; }
        //     Open-ended value
        [JsonProperty(PropertyName = "value")]
        public object Value { get; set; }
        [JsonProperty(PropertyName = "name")]
        public string Name { get; set; }
        [JsonProperty(PropertyName = "serviceUrl")]
        public string ServiceUrl { get; set; }
        //     Local time when message was sent (set by client, Ex: 2016-09-23T13:07:49.4714686-07:00)
        [JsonProperty(PropertyName = "localTimestamp")]
        public DateTimeOffset? LocalTimestamp { get; set; }
        //     UTC Time when message was sent (set by service)
        [JsonProperty(PropertyName = "timestamp")]
        public DateTime? Timestamp { get; set; }
        //     ID of this activity
        [JsonProperty(PropertyName = "id")]
        public string Id { get; set; }
        //     Reference to another conversation or activity
        [JsonProperty(PropertyName = "relatesTo")]
        public ConversationReference RelatesTo { get; set; }
        //     Code indicating why the conversation has ended
        [JsonProperty(PropertyName = "code")]
        public string Code { get; set; }
        //     The type of the activity [message|contactRelationUpdate|converationUpdate|typing|endOfConversation|event|invoke]
        [JsonProperty(PropertyName = "type")]
        public string Type { get; set; }
        //     Extension data for overflow of properties
        [JsonExtensionData(ReadData = true, WriteData = true)]
        public JObject Properties { get; set; }
```


Bot Framework 3.0 プロトコルでどのような通信が行われているのかHTTPメッセージの内容を確認してみよう。
下記がBot Framework Channel Emulatorで「Hello」と入力して送信した結果である。今回はEmulatorを使っているため  "channelId": "emulator"と設定されているが別のChannelを利用すればもちろん値が変わる。つまりBot Framework 3.0 プロトコルではこのパラメータで様々なChannelの使い分けが可能であり、Microsoft Bot FrameworkがMicorosoft TeamsやSlack、Facebook Messenger等に対応している所以でもある。

```json
POST http://localhost:3979/api/messages HTTP/1.1
host: localhost:3979
accept: application/json
content-type: application/json
content-length: 557
Connection: close

{
    "type": "message",
    "text": "hello",
    "from": {
        "id": "default-user",
        "name": "User"
    },
    "locale": "ja",
    "textFormat": "plain",
    "timestamp": "2017-07-12T05:53:28.679Z",
    "channelData": {
        "clientActivityId": "1499838802349.5943257054666484.0"
    },
    "entities": [
        {
            "type": "ClientCapabilities",
            "requiresBotState": true,
            "supportsTts": true,
            "supportsListening": true
        }
    ],
    "id": "lmjgh90h5gf2b74gc",
    "channelId": "emulator",
    "localTimestamp": "2017-07-12T14:53:28+09:00",
    "recipient": {
        "id": "03kffdb098892465f",
        "name": "Bot"
    },
    "conversation": {
        "id": "75g8agad4b86d56f4c"
    },
    "serviceUrl": "http://127.0.0.1:62142"
}

```

また、上記の**clientActivityId**はHTTPのSession IDのようなものでDialog全体のセッションを管理する。試しに再度「Hello」と送信すると以下のようにclientActivityIdの値が末尾のみ（0から2になった）かわる。これによって1つの会話を同じものとしてシーケンシャルに管理することが可能だ。ちなみにもう一度「Hello」と送信すると末尾の値が2から4になる。また、Bot Framework Channel Emulatorで「新しい会話をスタート：Start new conversation」を実行したり、再起動するとclientActivityIdが新たに採番され末尾の値は0に戻る。Webアプリケーションを作ったことがあるエンジニアであれば馴染みのあるセッション管理手法だろう。


```json
{
    "type": "message",
    "text": "hello",
    "from": {
        "id": "default-user",
        "name": "User"
    },
    "locale": "ja",
    "textFormat": "plain",
    "timestamp": "2017-07-12T06:12:53.664Z",
    "channelData": {
        "clientActivityId": "1499838802349.5943257054666484.2"
    },
    "id": "clm3g950j7cj408h6",
    "channelId": "emulator",
    "localTimestamp": "2017-07-12T15:12:53+09:00",
    "recipient": {
        "id": "03kffdb098892465f",
        "name": "Bot"
    },
    "conversation": {
        "id": "75g8agad4b86d56f4c"
    },
    "serviceUrl": "http://127.0.0.1:62142"
}

```


今回はメッセージ交換の基礎となるActivityについて解説した。次回はダイアログ（会話）について踏み込んで解説したいと思う。