LambdaとAPI Gatewayを使用してChatBotを作成する【Ruby】

今回は、AWS Lambda + API Gatewayを使った簡単なChatBotを作ってみたので、その紹介をしていきます。
とりあえず動くというところをゴールにして作ったので、実際に運用するにはもう少し作り込みが必要です。チュートリアルとして参考にしてもらえればと思います。

## 前提条件
- LINE Developersのコンソールにアクセスできる（LINEのアカウントがあれば簡単に可能です）
- AWSコンソールで作業ができる
- Rubyのソースが読める（LambdaではRubyを使ってコードを書きます）

## 実装していく
### Lambdaの準備
まず、LINEから送られるメッセージのパラメータを検証するためのAWS Lambdaを準備しておきます。
関数の内容は以下の通りです。
``` lambda_function.rb
require 'json'

def lambda_handler(event:, context:)
    logger = Logger.new(STDOUT)
    log_formatter = proc { |severity, timestamp, _, msg|
        JSON.dump({ time: timestamp, level: severity, message: msg })
    }
    logger.formatter = log_formatter
    
    logger.info event
end
```
LINEから受け取ったパラメータの詳細を確認するために、eventの内容をログに書き出しておきます。ログの形式をJSON形式で書き出すようにしておくとCloudWatch Logsで確認する時に見やすくなるのでオススメです！

このLambda関数が通信を受けられるようにするために、前段にAPI Gatewayを設定します。
Lambdaの設定にあるトリガーの項目から、API Gatewayを利用したトリガーを追加してください。API Gatewayの設定内容は以下の通りです。
`インテント: 新規APIを作成`
`APIタイプ: HTTP API`
`セキュリティ: 開く`
![スクリーンショット 2023-03-23 15.30.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/268830/65de6240-62f1-3245-e417-b53a8d5bb69a.png)
APIエンドポイントは後から使うのでコピーしておいてください。

### LINEチャネルを作成する
メッセージを送るためのLINEチャネルを作成するために、LINE Developersのコンソールにアクセスします。
![スクリーンショット-2023-03-23-15.17.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/268830/024e5ff0-41a7-faf3-110a-7eb393421156.png)
プロバイダーを作成し、プロバイダーのチャネル設定からMessaging APIチャネルを作成します。チャネル名、チャネル説明、大業種、小業種等の項目の入力が必要です。内容は適宜埋めてください。
![スクリーンショット-2023-03-24-13.43.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/268830/eed37bbc-5776-cd02-e33d-689ea1143348.png)
チャネルの作成が完了すると上の画像のような画面が表示されるので、Messaging API設定から、Webhook URLの設定を行います。
![スクリーンショット-2023-03-24-14.14.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/268830/4cb83768-2e7b-d8bb-aa2d-df82ead74d5d.png)
Webhook URLには先ほど作成したAPI GatewayのAPIエンドポイントURLを指定してください。
![スクリーンショット 2023-03-24 14.04.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/268830/791d84b7-cd14-83ed-49b7-f98910aef791.png)
Webhookの利用はデフォルトはOFFになっているのでONにしておいてください。
検証ボタンを押して成功と表示されれば完了です。
![スクリーンショット 2023-03-23 15.30.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/268830/3e2de3f8-a0d6-41d8-1983-4a9fc8fd6674.png)
ついでに、LINEにメッセージを送る時に必要になるチャネルアクセストークンを発行しておきます。
![スクリーンショット-2023-03-23-15.20.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/268830/ea048cb8-ed2b-9906-1c49-b0edbc9f8442.png)

### LINEメッセージのパラメータを確認する
Messaging API設定の中にQRコードが表示されているので、それを利用して友達登録します。
とりあえずメッセージを送ってみると次のようになりました。
![Screenshot_20230323-154038.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/268830/6d975405-609a-0511-dfb4-706540009086.png)
LINE側で勝手にメッセージが返却されているので、応答メッセージの設定をOFFにしておきます。

Lambdaに送られたパラメータをCloudWatch Logsで見てみると、JSON型で送られてきていることが確認できました。
![スクリーンショット 2023-03-23 15.38.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/268830/ea7f114b-9f2f-f926-bec6-aac64ee18cc7.png)
`body`を`JSON.parse`を用いてログに書き出してみると、メッセージ送信時のパラメータが確認しやすくなりました。
![スクリーンショット-2023-03-24-13.20.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/268830/9d55a829-04de-6566-cf2c-65f255649b3a.png)


### メッセージを返す仕組みを作る
最後に、受け取ったメッセージに合わせて返答する仕組みを作っていきます。
LINEのチャネルアクセストークンはソースコード上に直接書きたくないので、今回はAWS Systems Managerのパラメータストアを使って管理するようにしました。
![スクリーンショット 2023-03-23 16.19.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/268830/7e6183a7-e2cc-9b5c-d940-3c4e13deb2b3.png)

Lambdaからパラメータを取得するために実行ロールにssm:GetParameterを許可する設定を加えています。
ソースコードは以下になります。
``` lambda_function.rb
require 'json'
require 'aws-sdk-ssm'

def lambda_handler(event:, context:)
    logger = Logger.new(STDOUT)
    log_formatter = proc { |severity, timestamp, _, msg|
        JSON.dump({time: timestamp, level: severity, message: msg})
    }
    logger.formatter = log_formatter

    lambda_event_body = JSON.parse(event['body'])
    logger.info lambda_event_body
    # lineから送られてきたイベントを取得
    line_event = lambda_event_body['events'][0]
    if line_event['type'] == 'message'
        # テキストメッセージのみを受け付ける
        return unless line_event['message']['type'] == 'text'

        line_user_id = line_event['source']['userId']
        # 受け取ったテキストメッセージの内容に合わせてメッセージを返却
        case line_event['message']['text']
        when 'おはよう'
            push_line_message(line_user_id, 'おはよう')
        when 'こんばんは'
            push_line_message(line_user_id, 'こんばんは')
        else
           push_line_message(line_user_id, 'こんにちは')
        end
    end
end

# ラインメッセージ送信処理
def push_line_message(line_user_id, message)
    ssm_client = Aws::SSM::Client.new
    access_token = ssm_client.get_parameter({ name: '/line/access_token', with_decryption: true }).parameter.value
    headers = {
        'Content-Type': 'application/json',
        'Authorization': "Bearer #{access_token}"
    }
    data = {
        'to': line_user_id,
        'messages': [{ 'type': 'text', 'text': message }]
    }
    http_post_request('https://api.line.me/v2/bot/message/push', headers, data)
end

def http_post_request(api_url, headers, data)
    uri = URI.parse(api_url)
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = uri.scheme === 'https'
    req = Net::HTTP::Post.new(uri.path)
    req.body = JSON.generate(data)
    req.initialize_http_header(headers)
    http.request(req)
end
```
難しいことは考えずに「おはよう」「こんにちは」「こんばんは」を返すようにしてみました。Lambdaの関数をデプロイ後、実際にメッセージを送ってみると、想定通りの返事がくることを確認できました！
![screen-20230324-104235_2_AdobeExpress (1).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/268830/be8ff576-09f3-2611-2696-7d39536bc92a.gif)

## 終わりに
今回作ったChatBotのメッセージ返却ロジックを作り込むだけでも十分使えるChatBotになっていくと思います。メッセージの返却部分に流行りのChatGPTのAPIを使ってみても面白いかなと考えています。

業務ではSlackを使っている人も多いと思います。SlackのChatBotを作る時も、LINEチャネルの代わりにSlackAppを作成して設定すること以外作り方は基本同じです。
上手く利用すれば、チャットを利用してのデプロイやサポートツールとしての利用等、各種ツールと組み合わせての業務効率化に役立つので是非試してみてください。

# 参考
- [Messaging APIを始めよう｜LINE Developers
](https://developers.line.biz/ja/docs/messaging-api/getting-started/)