Lambdaを使用してLINEとChatGPTを連携してみた。

# はじめに
こんにちは、山田です。
今回はLambdaを使用して、LINEとChatGPTを連携させてみたので構築手順に関して記載していきます。
よろしくお願いします。

# 構成図
構成図は以下の通りです。
![空白の図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1096361/e9af6137-7b07-763e-c645-9a1df1bf9879.png)
AWSサービスとしては、``API Gateway``、``Lambda``、``DynamoDB``を使用します。
CahtGPTから得られた回答に関しては、DynamoDBに保存します。

# API Gatewayに関しての復習
API Gatewayは、Lambda関数をHTTPSプロトコル経由で呼び出すための仕組みです。

## HTTPAPIとRESTAPI
以下のHTTP APIとREST APIの主な違いについて記載します。

**デザイン**
REST APIは、リソースベースのデザインを採用しており、HTTPメソッドを使用してリソースに対するアクションを指定します。一方、HTTP APIは、機能ベースのデザインを採用しており、APIに対するアクションを指定するためにHTTPメソッドの代わりに操作名を使用します。

**レスポンス形式**
REST APIは、JSON、XML、HTML、またはテキスト形式で応答を返すことができますが、HTTP APIはJSON形式の応答のみをサポートしています。

**キャッシング**
HTTP APIは、HTTP/2プロトコルを使用するため、HTTP/1.1のREST APIよりも効率的にキャッシングができます。HTTP/2は、1つのコネクションで複数のリクエスト/レスポンスを処理できるため、リクエストのオーバーヘッドが減少し、レスポンスの応答速度が向上します。

**クライアントライブラリ**
HTTP APIは、クライアントライブラリがAWS SDK v3で提供されています。一方、REST APIは、AWS SDK v2やその他のHTTPクライアントライブラリを使用する必要があります。

**セキュリティ**
HTTP APIは、AWS Identity and Access Management（IAM）ロールとAWS PrivateLinkを使用して、APIエンドポイントを保護することができます。REST APIは、AWS Identity and Access Management（IAM）ロールとAPI Gatewayのカスタム認証を使用して、エンドポイントを保護することができます。

## Lambdaプロキシ統合
HTTP APIもREST APIもLambda統合プロキシをオンにすることができます。
Lambdaプロキシ統合は、AWS API Gatewayで利用可能な統合タイプの1つで、Lambda関数をAPI Gatewayに直接統合するために使用されます。この統合タイプを使用すると、API GatewayはLambda関数を呼び出してリクエストを処理し、Lambda関数が返すレスポンスをAPI Gatewayがクライアントに返します。
Lambdaプロキシ統合を使用すると、API Gatewayのデフォルトの統合レスポンスやマッピングテンプレートを使用する代わりに、``Lambda関数のレスポンスを直接返すことができます。``これにより、より柔軟性のあるレスポンス処理が可能になります。

Lambdaプロキシ統合がオフになっている場合、API Gatewayはレスポンスを作成するために、Lambda関数を呼び出すのではなく、API Gatewayの統合レスポンステンプレートを使用します。
つまり、``「どのURLを、どのLambdaに渡すのか」「受け取ったヘッダーやクエリ文字列などを、どのイベント引数に設定するのか」``等をすべて設定する必要があります。

# 構築手順
以下より、実際の構築手順について記載していきます。

## 事前作業
事前に以下のサイトよりアカウント登録し、LINE API KEYとOpenAI API KEYを取得しておきます。

https://developers.line.biz/ja/

https://openai.com/blog/openai-api

## DynamoDBの作成
DynamoDBのコンソール画面に移動し、「messageテーブル」と「sequenceテーブル」を作成します。
「sequenceテーブル」に関しては、以下のように変更します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1096361/752a3371-4593-14e8-1625-4bfe5fb5f6b2.png)

## Lambda関数の作成
AWSのマネジメントコンソールにログイン後、Lambdaのコンソール画面に移動します。
「関数の作成」をクリックし、ランタイムは「**python3.7**」を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1096361/38738320-0fe7-568a-e082-27ae948a6f07.png)

以下のコードを記載します。
```python
import json
import os
import requests
import boto3
import time
import decimal


def next_seq(table, tablename):
    # DynamoDBのテーブルを更新するためのupdate_item関数を呼び出す
    response= table.update_item(
        Key={
            'tablename' : tablename  # プライマリキーを指定する
        },
        UpdateExpression = "set seq = seq + :val",  # seq属性を更新する
        ExpressionAttributeValues={
            ':val' : 1  # seqに加算する値を指定する
        },
        ReturnValues='UPDATED_NEW'  # 更新後の項目値を返すように指定する
        )
    # 更新後のseqの値を返す
    return response['Attributes']['seq']


def lambda_handler(event, context):
    #環境変数より値取得
    chatgpt_api_key = os.environ['ChatGPT_API_KEY']
    line_api_key = os.environ['LINE_API_KEY']
    
    # LINE Messaging APIからのリクエストをパースする
    body = json.loads(event['body'])
    
    # LINE Messaging APIから送信されたイベントオブジェクトを取得する
    events = body.get('events', [])
    
    # イベントオブジェクトが空の場合は、何もしない
    if not events:
        return {
            'statusCode': 200,
            'body': json.dumps({'message': 'success'})
        }
    
    # イベントオブジェクトを処理する
    reply_token = events[0]['replyToken']
    message = events[0]['message']['text']
    user_id = events[0]['source']['userId']
    
    #ChatGptのリクエストURL
    url = "https://api.openai.com/v1/chat/completions"
    
    #ChatGPTに対するリクエストを作成する
    headers = {
        "Content-Type": "application/json",
        "Authorization": "Bearer " + chatgpt_api_key
    }
    
    #モデルは「gpt-3.5-turbo」を使用。
    payload = {
      "model": "gpt-3.5-turbo",
      "messages": [{"role": "user", "content": message}]
    }
    
    #質問に対する応答文取得。
    response = requests.post(url, headers=headers, data=json.dumps(payload))
    response_data = json.loads(response.content.decode('utf-8'))
    
    #改行コードを消す。
    response_data = response_data['choices'][0]['message']['content'].lstrip()
    
    #DynamoDBにメッセージの内容を書き込む処理を以下より記載。
    dynamodb = boto3.resource('dynamodb')
    
    #シーケンスデータ取得
    seqtable = dynamodb.Table('sequence')
    nextseq = next_seq(seqtable, 'message')
    
    #現在のUNIXスタンプを得る。
    now = time.time()
    
    #messageテーブルに登録する。
    messagetable = dynamodb.Table('message')
    messagetable.put_item(
        Item  = {
            'id': nextseq,
            'message': response_data,
            'accepted_at': decimal.Decimal(str(now))
        }
    )

    # LINE Messaging APIに対するレスポンスを作成する
    headers = {'Content-Type': 'application/json', 'Authorization': 'Bearer ' + line_api_key}
    data = {'replyToken': reply_token,
        'messages': [
            {
                'type': 'text',
                'text': response_data
            }
        ]
    }
    
    # LINE Messaging APIにレスポンスを送信する
    response = requests.post('https://api.line.me/v2/bot/message/reply', headers=headers, data=json.dumps(data))

    # レスポンスのステータスコードをログに出力する
    print(response.status_code)
    
    # Lambda関数のレスポンスを返す
    return {
        'statusCode': 200,
        'body': json.dumps({'message': 'success'})
    }
```
環境変数にLINEとOpenAIのAPI KEYの値を設定します。
![スクリーンショット 2023-03-08 211443.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1096361/0e6c0f37-7533-5863-c268-f4c5cc978132.png)

## API Gatewayの作成
API Gatewayのコンソール画面に移動します。
移動後に「APIを作成」をクリックし、HTTP APIを選択します。
![スクリーンショット 2023-03-08 210353.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1096361/1d14f273-60a3-3394-86f1-b0c992583c3c.png)
統合は「Lambda」を選択、作成したLAmbda関数を指定し、API名を付けたら「次へ」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1096361/c5c7845e-e310-c243-b99d-457e59372d1e.png)
後の設定はデフォルトのままとし、「作成」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1096361/e372c8a2-9cbc-3022-4011-0c8eaaa7fa92.png)

Lambda関数を確認し、トリガーにAPI Gatewwayが追加されていることを確認します。
![スクリーンショット 2023-03-08 212620.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1096361/109e5857-7727-bdac-ccec-3049b5549f0e.png)

# LINE Developers
LINE Developerにログインし、「**Webhook URL**」をAPI GatewayのURLに設定します。
![スクリーンショット 2023-03-08 213835.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1096361/42f7d837-8ada-ece7-5837-fd7b79e909f0.png)

# 動作確認
LINEでメッセージを送信し、回答が返ってくることを確認します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1096361/2a011e9c-e55b-33f9-ef67-8178e50d9b98.png)

DynamoDBを確認し、メッセージが保存されていればOKです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1096361/776e2184-0e6d-4e28-12f7-ed57432db30e.png)

# 終わりに
今回はLambdaを使用して、LINEとChatGPTを連携させる手順に関して記載しました。
次はChatGPT APIの使用料金をCloudWatchにて監視する方法について記載していこうと思います。
ありがとうございました。