Dialogflow(GCP) + Lambda + Line Messaging API でLine Bot作ってみた

### 背景

母のお仕事の手助けとして、簡単なファスティング計算サイトを学生時代作っていたのですが、Herokuの無料公開プランが終了し閲覧不可になっていました。
これを機に、母に一番馴染みがあるLineで同じようなことをできないかと思い作ってみました。

#### Dialogflow

GCPのチャットボットが簡単に作成できるサービス。
AWSでいうAmazon Lexのようなもの。
https://wk-partners.co.jp/homepage/blog/webservices/dialogflow/

今回のようなLine等サービスとの連携や、Webhook設定も比較的簡単に行えました。
また、料金もテキスト認識であれば**<span style="color:#d70910;">無料</span>**！！（1分あたり180リクエストまで）
Amazon Lexはお金かかっちゃうので、無料というところに惹かれました。

#### Line Messaging API

LineBotを作成し、応答メッセージの送受信を行うことができる。
応答のパターンもユーザーの反応や流れに合わせて対応可能。
メッセージタイプもテキスト、スタンプをはじめ様々存在する。
https://note.com/misuzu_neccos/n/n5b94a7099fb8

### 今回の構成

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532934/9ea81918-fede-f447-9a69-5a349b79b78e.png)

### 完成したもの

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532934/4b7af769-1c1f-1845-2294-60843fb0392e.png">


### 作り方

1. LINE Developerアカウントの作成~LINE Botの作成
3. DialogFlow Agentの作成
4. LINE BotとDialogFlowの接続
5. Lambda関数の作成
6. DialogFlowとLambda関数の接続

#### 1. LINE Developerアカウントの作成~LINE Botの作成

下記の記事を参考に、LINE Developerアカウントの作成、プロバイダーの作成、チャネルの作成まで行います。（割愛）
https://qiita.com/himarin269/items/5c0190a13f95b5538546#1-line-developers%E3%81%A7%E3%81%AEbot%E3%83%81%E3%83%A3%E3%83%8D%E3%83%AB%E9%96%8B%E8%A8%AD

#### 2. DialogFlow Agentの作成

##### 2-1. Googleアカウントで、DialogFlowコンソールへログイン

[こちら](https://cloud.google.com/dialogflow?hl=ja)からgoogleアカウントでコンソールにログインする。

##### 2-2. Agentの作成

「Create new agent」よりAgentの作成を行います。
Agentが1つのBotとなります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532934/da16304f-c7ed-3dc7-b325-46f08a2e65cd.png)

##### 2-3. Intentsの作成

「CREATE INTENT」よりIntentの作成をします。
Intentは1つの対話パターンのようなイメージ。
入力に対して、Agentに行わせたい指示内容になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532934/6c31bf97-7608-1620-9155-fbed104aa809.png)


今回は、「fasting-calc」という名前で作成

##### 2-4. Entityの登録

2-3で作成したIntentにて、Entityの登録を行います。
Entityとは、Intent内で意味を持つ単語になります。
今回のファスティング計算では、「体重」と「体脂肪率」のが必要になり、今回はこれがEntityにあたります。

先程のIntentの画面で、「Training phrases」→「ADD TRAINING PHRASES」をクリックして、学習させたいフレーズを入力します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532934/cb99a0b1-297e-64a8-61b7-7ddbffdf6361.png)


今回はひとまず、「体重60kg、体脂肪率30%の計算」と入力しました。

その後、どの部分がEntityとなるかを設定します。
文章内のEntityなる部分を範囲選択すると、「Action and parameters」に値が追加されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532934/8887b37e-c267-bd58-e6ea-ac47234c8156.png)


REQUIRED：そのEntityが必須であるかどうか
PARAMETER NAME：Entityの識別名
ENTITY：Entityの値の型（自分でカスタムしたりもできるそう）
PROMPTS：入力にEntityが含まれなかった場合の返却文（バリデーションも文言的な）

Training Phrasesは複数パターン追加できるので、単位の有無だったりいくつかパターンを追加しておくと、柔軟に学習してくれそうです。
※今回は省きました。

##### 2-5. Fulfillmentの設定

外部API等と連携する際に必要な設定になります。
今回は、入力された値をLambdaに渡して計算してもらう構成のためこちらをオンにします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532934/cf21e20b-6c99-68ad-d7b1-cbaae95b697b.png)

#### 3. LINE BotとDialogFlowの接続

Lambda関数を作成する前にLINE BotとDialogFlowの接続を行います。

##### DialogFlow側

「Integrations」というところにLINE連携の設定が用意されているのでひとまずオンにします。

そうすると、下記のような設定ダイアログが開くので、LINE Developersの画面に戻って、「Channel ID」「Channel Secret」「アクセストークン」をコピーし貼り付けます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532934/1b999539-b006-2c74-9f29-f9e3f5351ec8.png)


※注意点として、LINEのチャンネルアクセストークンを更新せずに利用するとなんかうまく行かなかったので、更新してから利用するのがよいかもです。

##### LINE Developers側

DialogFlowのWebhook URLをコピーし、貼り付けます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532934/65603146-dcd0-aa79-ef7c-b2a41471f867.png)

また、Webhookの送信もオンにします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532934/68d84969-1a2b-12b5-e570-4af6284b60f1.png)


この状態で動作確認をすると、LINE Botの入力に応じて、DialogFlowから応答が返されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532934/896d1977-1998-120f-562f-7f71bcec8225.png)


#### 4. Lambdaの作成

体重と体脂肪率の値から必要な計算を行いその結果を返すLambdaを作成します。
Return時に「fulfillmentText」に渡した値がユーザーへの返却値になります。

https://dev.classmethod.jp/articles/using_aws_lambda_for_fullfilment_of_dialogflow/

```js
exports.handler = async (event) => {
    console.log(event);
    
    const request = JSON.parse(event.body);
    
    console.log(request);
    
    const weight = request.queryResult.parameters.weight;
    const fat_per = request.queryResult.parameters.fatper;
    
    // 体脂肪量
    const body_fat_mass = Math.round(weight * fat_per / 10) / 10;
    
    // 除脂肪体重
    const lean_body_mass = Math.round((weight - body_fat_mass) * 10) / 10;
    
    // 基礎代謝量
    const basal_metabolic_rate = Math.round((28.5 * lean_body_mass) * 10) / 10;
    
    // 総消費カロリー
    const total_calories_burned = Math.round((basal_metabolic_rate / 0.6) * 10) / 10;
    
    // ファスティング時の必要摂取カロリー(小)
    const required_calorie_min = Math.round((total_calories_burned / 4) * 10) / 10;
    
    // ファスティング時の必要摂取カロリー(大)
    const required_calorie_max = Math.round((total_calories_burned / 3) * 10) / 10;
    
    // ファスティング時のMANA必要摂取量(小)
    const required_mana_min = Math.round(required_calorie_min / 2.17);
    
    // ファスティング時のMANA必要摂取量(大)
    const required_mana_max = Math.round(required_calorie_max / 2.17);
    
    // ファスティング時のKARA必要摂取量(小)
    const required_kara_min = Math.round(required_calorie_min / 2.22);
    
    // ファスティング時のKARA必要摂取量(大)
    const required_kara_max = Math.round(required_calorie_max / 2.22);
    
     const response = `体脂肪量：${body_fat_mass}kg
    
    除脂肪体重：${lean_body_mass}kg
    
    基礎代謝量：${basal_metabolic_rate}kcal
    
    総消費カロリー：${total_calories_burned}kcal
    
    ファスティング時の必要摂取カロリー(小)：${required_calorie_min}kcal
    
    ファスティング時の必要摂取カロリー(大)：${required_calorie_max}kcal
    
    ファスティング時のMANA必要摂取量(小)：${required_mana_min}ml
    
    ファスティング時のMANA必要摂取量(大)：${required_mana_max}ml
    
    ファスティング時のKARA必要摂取量(小)：${required_kara_min}ml
    
    ファスティング時のKARA必要摂取量(大)：${required_kara_max}ml`

    return {
        fulfillmentText: response
    };
};
```

DialogFlowから送られてくるリクエストは下記のような形になっています。

queryResultの部分に、2-4で定義したEntityが入ってきます。

```
{
  "responseId": "",
  "queryResult": {
    "queryText": "体重60kg、体脂肪率30%の計算",
    "parameters": {
      "weight": 60,
      "fatper": 30
    },
    "allRequiredParamsPresent": true,
    "fulfillmentMessages": [
      {
        "text": {
          "text": [
            ""
          ]
        }
      }
    ],
    "outputContexts": [
      {
        "name": "",
        "parameters": {
          "no-input": 0,
          "no-match": 0,
          "weight": 60,
          "weight.original": "60",
          "fatper": 30,
          "fatper.original": "30"
        }
      }
    ],
    "intent": {
      "name": "",
      "displayName": "fasting-calc"
    },
    "intentDetectionConfidence": 1,
    "languageCode": "en"
  },
  "originalDetectIntentRequest": {
    "source": "DIALOGFLOW_CONSOLE",
    "payload": {}
  },
  "session": ""
}
```

また、こちらは、DialogFlowの「DIAGNOSTIC INFO」から確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532934/1df73ef2-1add-c8a9-c5be-1e1e5dab33b1.png)



#### 5. DialogFlowとLambda関数の接続

最後にDialogFlowとLambda関数の接続をします。

「Fulfillment」のWebhookのURL欄にLambdaの関数URLを設定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/532934/d5db531b-be43-71ce-80a2-06d0124b0a5f.png)


<span style="font-size:110%;">**LINE Botで入力した値に対して、Lambda関数での計算結果が返ってくることを確認して終了**</span>


### まとめ

以前使用していたものが利用できなくなったことをきっかけに前よりもユーザー（母）にとって使いやすい仕組みにできたので良かったです！
あとほとんど無料！！
お年玉来るかな！！！！