LUIS Appをプログラムから利用する方法

# はじめに

[前回](https://qiita.com/Shota_Fukuda/private/5a1e53a849b1dd30524d)LUISを使用して自然言語処理を試してみました。
しかしそのままではあまり意味がありません。
ですので今回は作成したLUIS Appをプログラムから利用していきます。

# BotFramework編

LUIS AppはMicrosoftのBotFrameworkから簡単に利用することが出来ます。

## LUISを利用したBotの作成

[Bot Frameworkと雑談対話APIを使用したチャットBot](https://qiita.com/Shota_Fukuda/items/9aead1445b902183d6ec)を参照して、テンプレートを利用したBotApplicationのプロジェクトの作成、NuGetパッケージの復元まで実施します。

```csharp:RootDialog.cs
    [Serializable]
    public class RootDialog : LuisDialog<object>
    {
       ～
    }
```

RootDialog.csの基底クラスを**IDialog\<object>**から**LuisDialog\<object>**に変更します。

基底クラスを変更した段階でテンプレートにより作成されている、**StartAsyncメソッド**と**MessageReceivedAsyncメソッド**は使用しないので削除します。

```csharp:RootDialog.cs
    [LuisModel("{Application ID}", "{Programmatic Key}")]
    [Serializable]
    public class RootDialog : LuisDialog<object>
    {
    }
```

RootDailogクラスのAttributeとして`LuisModel`を追加します。

Attributeのパラメータには

![image.png](https://qiita-image-store.s3.amazonaws.com/0/199859/01a58e18-1651-0e27-e9a5-0d79420834e0.png)

LUIS AppのSETTINGメニュー内のApplication ID

![image.png](https://qiita-image-store.s3.amazonaws.com/0/199859/8c2c224e-0125-116e-a31e-5e90872f0b2d.png)

User settings内のProgrammatic Key
を設定する。

```csharp:RootDialog.cs
        [LuisIntent("")]
        [LuisIntent("None")]
        public async Task None(IDialogContext context, LuisResult result)
        {
            await context.PostAsync($"インテント引っかからず");
            context.Done<object>(null);
        }

        [LuisIntent("ScheduleSearch")]
        public async Task ScheduleSearch(IDialogContext context, LuisResult result)
        {
            await context.PostAsync($"インテントScheduleSearchに引っかかる");
            await context.PostAsync($"スコアは{result.Intents[0].Score}です。");

            foreach (var entity in result.Entities)
            {
                await context.PostAsync($"エンティティ{entity.Type}");
                await context.PostAsync($"値は{entity.Entity}です。");
            }

            context.Done<object>(null);
        }
```

クラス内に**Noneメソッド**と**ScheduleSearchメソッド**を追加する。
各メソッドのAttributeとして`LuisIntent`が設定し、パラメータとしてはLUIS AppのIntentの名前を設定する。
`LuisIntent`Attributeを設定することにより、Botに対して入力された文章をLUIS Appが判断し、判断したIntentがAttirbuteに設定されているメソッドに対して処理が投げられる。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/199859/7fe78f17-36ca-a4e2-05af-9ed9725e2d40.png)

デバッグ実行し、BotFramework Emulatorで実行した結果です。
「山田さんの予定は」という文章を入力すると、ScheduleSearchインテントと判断し、Bot側のインテントとエンティティを返す処理が動作しています。
「こんにちは」という文章を入力すると、Noneインテントと判断し、Bot側でインテントに引っかからなかったことを返す処理が動作しています。

このようにBotFrameworkと連携する場合は、LUIS Appを使用していることを感じさせずにプログラムを作成することができます。

# REST API編

LUIS AppはREST APIの口も用意されているので色々なプラットフォームからも利用することが出来ます。

URLは
https://westus.api.cognitive.microsoft.com/luis/v2.0/apps/{ApplicationID}
となっています。
※リージョンがwestusの場合。無償アカウントの場合はこれになるはず。

パラメータは

| パラメータ|値|備考|
|:----------------|:-----------------|:------------------|
|subscription-key|{Programmatic Key}||
|verbose|true|falseにしたらどうなるか不明…|
|timezoneOffset|540|タイムゾーンによる時差を分で設定|
|q|LUIS Appに判断させる文章||

のように設定します。

```json:レスポンス
{
  "query": "山田さんの予定は",
  "topScoringIntent": {
    "intent": "ScheduleSearch",
    "score": 0.973934
  },
  "intents": [
    {
      "intent": "ScheduleSearch",
      "score": 0.973934
    },
    {
      "intent": "None",
      "score": 0.153738111
    }
  ],
  "entities": [
    {
      "entity": "山田",
      "type": "LastName",
      "startIndex": 0,
      "endIndex": 1,
      "score": 0.8487269
    }
  ]
}
```

実行するとレスポンスとしてJSONが返ってきます。

## REST APIを利用してハングアウトBotに自然言語処理を組み込む

[ハングアウトBotを作ってみる](https://qiita.com/Shota_Fukuda/items/bd36c543ac6fbefb685e)
[G Suite 組織内ユーザーの予定を名前から取得してみる](https://qiita.com/Shota_Fukuda/items/5c280cece174c58ac044)
で作成したG Suite組織内ユーザーの予定を取得するハングアウトBotに自然言語処理を組み込んで行きます。

と言ってもハングアウトBotのコマンドの`/bot コマンド パラメータ`という構成は変えようが無いので、入力した文字列をLUIS Appで処理した後、その内容次第でコマンドを生成する形にします。

```py:handlers.py
    @asyncio.coroutine
    def handle_command(self, event):
        
        …

        # ensure bot alias is always a list
        if not isinstance(self.bot_command, list):
            self.bot_command = [self.bot_command]

        # ↓ 追加処理 始まり
        # 自然言語処理で予定問い合わせか判断
        response = requests.get(
        'https://westus.api.cognitive.microsoft.com/luis/v2.0/apps/{ApplicationID}',
        params={'subscription-key': '{Programmatic Key}',
                'verbose': 'true',
                'timezoneOffset': '540',
                'q': event.text})
        luisJson = response.json()

        if luisJson['topScoringIntent']['intent'] == 'ScheduleSearch':
            createCommand = '/bot calendar '

        lastName = ''
        firstName = ''
        for entity in luisJson['entities']:
            if entity['type'] == 'LastName':
                lastName = entity['entity']

        if len(lastName) > 0:
            createCommand = createCommand + lastName
        else:
            createCommand = ''

        if len(createCommand) > 0:
            event.text = createCommand
        # ↑ 追加処理 終わり

```

処理を追加するのは**handlers.py**の**handle_commandメソッド**です。
handle_commandメソッドでは入力された文字列がハングアウトBotのコマンド構成と一致しているかを判断している箇所です。
そこで判断をする前にLUIS Appで入力された文字列を判断させ、「予定を聞く」文脈だった場合、`/bot Calendar 名字`のコマンドを生成し入力した文字列と置き換えるようにしています。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/199859/6290f643-e169-6989-811f-e9b30f7bbf29.png)

実際に実行してみるとハングアウトBotのコマンド構成で入力した場合と、「予定を聞く」文脈の文章を入力した場合の結果は同じになります。