ChatGPT の LINE bot(APIGatewa + Lambda) ハンズオン

## はじめに
みなさん「ChatGPT使い倒してますか？」、かくいう私は夜な夜なChatGPTに対して「友達の作り方」や「人に嫌われない方法とは？」なんかを聞いています。
そんな自分もエンジニアの端くれなので、とりあえず身近なところからということで LINEをつかってChatGPTを、より一層楽しめるようにしていきます。

------------

## 構成図
![](https://cloud5.jp/wp-content/uploads/2023/03/スクリーンショット-2023-03-12-17.06.55-640x202.png)

------------

## ハンズオン
### 構築のながれ
#### 1.LINE Developers での設定 :プロバイダー作成
#### 2.OpenAI　での設定 :API鍵作成
#### 3.ローカルでの設定 :LINE APIのSDK ＋　openai インストール
#### 4.AWS CloudFormationによる構築 :APIGateway + Lambda
#### 5.Lambdaにデプロイを実施
#### 6.LINE Webhook での設定

-----------

### 1.LINE Developers での設定 :プロバイダー作成
[AWS Lambdaを利用したLINEbotハンズオン](https://cloud5.jp/aws-lambda_line-api/)の実装「1.LINE Developersでプロバイダーの登録」に従いプロバイダーを設定する。

「3.Lambdaの設定（AWSコンソール）」の「3.5　環境変数の値を確認する」も参照して、後続作業で必要となる`チャンネルアクセストークン`や`チャンネルシークレット`も確認して、自身がわかる場所に保存する。

<br />

### 2.OpenAI　での設定 :API鍵作成
#### 2.1.OpenAI HPでアカウント作成
[OpenAI](https://openai.com/product)より、画面の「Get started」を押下してアカウントを作成する。
![](https://cloud5.jp/wp-content/uploads/2023/03/スクリーンショット-2023-03-12-10.50.32-640x175.png)

<br />

#### 2.2.秘密鍵作成
[API keys](https://platform.openai.com/account/api-keys)作成ページより、「Create new secret key」を押下して秘密鍵を作成する
![](https://cloud5.jp/wp-content/uploads/2023/03/スクリーンショット-2023-03-12-10.58.04-640x461.png)
<br />
APIキーが発行されるので、自身がわかる場所に保存する。
![](https://cloud5.jp/wp-content/uploads/2023/03/スクリーンショット-2023-03-12-11.00.46-640x358.png)
<br />



### 3.ローカルでの設定　　　：LINE APIのSDK ＋　ChatGPTのライブラリ openai インストール
①[LINE公式ドキュメント](https://developers.line.biz/ja/docs/downloads/)より、LINE APIのSDK（ソフトウェア開発キット）をインストール

②[OpenAI ドキュメント](https://platform.openai.com/docs/libraries)より、ライブラリ openai をインストール


```bash
# ①LINE APIのSDK　インストール（問題なく終了したら次のコマンドを実施する）
tetutetu214@mbp 20230312-chatgpt % python -m pip install line-bot-sdk -t .

# ②ライブラリ openai　インストール
tetutetu214@mbp 20230312-chatgpt % python -m pip install openai -t . 

```

<br />

### 4.AWS CloudFormationによる構築
#### 4.1.デプロイするベースのAPIGateway + LambdaをCFnで作成
**手順.3** で実施した①②を含んだコードを zip にまとめて反映するためのベースを作成する。

```yaml
AWSTemplateFormatVersion: '2010-09-09'
Description: 'HTTP API'
# ------------------------------------------------------------#
#  Metadata
# ------------------------------------------------------------#
Metadata:
  "AWS::CloudFormation::Interface":
    ParameterGroups:
      - Label:
          default: "Lambda Configuration"
        Parameters:
        - FunctionName
        - Description
        - Handler
        - MemorySize
        - Runtime
        - Timeout
        - TagsName

# ------------------------------------------------------------#
#  InputParameters
# ------------------------------------------------------------#
Parameters:
  FunctionName:
    Type: String
    Default: "cfn-chatgpt"
  Description:
    Type: String
    Default: "cfn-chatgpt"
  Handler:
    Type: String
    Default: "lambda_function.lambda_handler"
  MemorySize:
    Type: String
    Default: "128"
  Runtime:
    Type: String
    Default: "python3.9"
  Timeout:
    Type: String
    Default: "300"
  TagsName:
    Type: String
    Default: "inamura"
# ------------------------------------------------------------#
#  Resources
# ------------------------------------------------------------#
Resources:
# ------------------------------------------------------------#
#  Role Lambda
# ------------------------------------------------------------#
  LambdaRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub "${FunctionName}-role"
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Action: "sts:AssumeRole"
            Principal:
              Service: lambda.amazonaws.com
      Policies:
        - PolicyName: !Sub "${FunctionName}-policy"
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: "Allow"
                Action:
                  - "logs:CreateLogStream"
                  - "logs:PutLogEvents"
                  - "logs:CreateLogGroup"
                Resource: !Sub "arn:${AWS::Partition}:logs:*:*:*"
#  Lambda
# ------------------------------------------------------------#
  Lambda:
    Type: AWS::Lambda::Function
    Properties:
      Description: !Ref Description
      Code:
        ZipFile: |
            import json

            def lambda_handler(event, context):
            # TODO implement
                return {
                    'statusCode': 200,
                    'body': json.dumps('Hello from Lambda!')
                }
      Environment:
        Variables:
          LINE_CHANNEL_ACCESS_TOKEN: XXXXXXXXXX
          LINE_CHANNEL_SECRET: YYYYYYYYYY
          OPENAI_API_KEY: ZZZZZZZZZZ
      FunctionName: !Sub "${FunctionName}-lmd"
      Handler: !Ref Handler 
      MemorySize: !Ref MemorySize
      Runtime: !Ref Runtime
      Timeout: !Ref Timeout
      Role: !GetAtt LambdaRole.Arn
      Tags:
        - Key: "User"
          Value: !Ref TagsName

# ------------------------------------------------------------#
#  APIGateway
# ------------------------------------------------------------#
  HttpApi:
    Type: AWS::ApiGatewayV2::Api
    Properties:
      Name: !Sub "${FunctionName}-apigateway"
      ProtocolType: HTTP

  HttpApiDefaultStage:
    Type: AWS::ApiGatewayV2::Stage
    Properties:
      ApiId: !Ref HttpApi
      StageName: "default"
      AutoDeploy: true

  HttpApiHelloIntegration:
    Type: AWS::ApiGatewayV2::Integration
    Properties:
      ApiId: !Ref HttpApi
      IntegrationType: AWS_PROXY
      IntegrationUri: !GetAtt Lambda.Arn
      PayloadFormatVersion: '2.0'

  HttpApiHelloIntegrationPermission:
    Type: AWS::Lambda::Permission
    Properties:
      Action: lambda:InvokeFunction
      FunctionName: !GetAtt Lambda.Arn
      Principal: apigateway.amazonaws.com
      SourceArn: !Sub "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${HttpApi}/*/*/chatgpt"

  HttpApiHelloRoute:
    Type: AWS::ApiGatewayV2::Route
    Properties:
      ApiId: !Ref HttpApi
      RouteKey: ANY /chatgpt
      AuthorizationType: NONE
      Target: !Sub "integrations/${HttpApiHelloIntegration}"

```

<br />

#### 4.2.デプロイ後 Lambda の環境変数を設定
Lambdaの環境変数に今まで取得した値を入力して保存する
・X箇所に 1 で取得した LINEの`チャンネルアクセストークン`
・Y箇所に 1 で取得した LINEの`チャンネルシークレット`
・Z箇所に 2 で取得した ChatGPTの`API Key`

![](https://cloud5.jp/wp-content/uploads/2023/03/スクリーンショット-2023-03-12-14.51.52-640x511.png)
<br />

### 5.Lambdaにデプロイを実施
#### 5.1.デプロイ前に Lambdaで実装する内容を`lambda_function.py`で作成
下記ファイルを作成する
```python
import os
import sys
import logging
import openai

from linebot import (LineBotApi, WebhookHandler)
from linebot.models import (MessageEvent, TextMessage, TextSendMessage)
from linebot.exceptions import (LineBotApiError, InvalidSignatureError)

logger = logging.getLogger()
logger.setLevel(logging.ERROR)

#環境変数からLINEBotのチャンネルアクセストークンとシークレットを読込
#環境変数からChatGpt APIの鍵を読込
channel_secret = os.getenv('LINE_CHANNEL_SECRET', None)
channel_access_token = os.getenv('LINE_CHANNEL_ACCESS_TOKEN', None)
openai.api_key = os.getenv("OPENAI_API_KEY")

#トークンが確認できない場合エラー出力
if channel_secret is None:
    logger.error('Specify LINE_CHANNEL_SECRET as environment variable.')
    sys.exit(1)
if channel_access_token is None:
    logger.error('Specify LINE_CHANNEL_ACCESS_TOKEN as environment variable.')
    sys.exit(1)

#apiとhandlerの生成（チャンネルアクセストークンとシークレットを渡す）
line_bot_api = LineBotApi(channel_access_token)
handler = WebhookHandler(channel_secret)

#Lambdaのメインの動作
def lambda_handler(event, context):

#認証用のx-line-signatureヘッダー
    signature = event["headers"]["x-line-signature"]
    body = event["body"]

#リターン値の設定
    ok_json = {"isBase64Encoded": False,
               "statusCode": 200,
               "headers": {},
               "body": ""}
    error_json = {"isBase64Encoded": False,
                  "statusCode": 500,
                  "headers": {},
                  "body": "Error"}

#LINEからのメッセージを ChatGPTに送信、受信したテキストをLINEで返信
    @handler.add(MessageEvent, message=TextMessage)
    def message(line_event):
        text = line_event.message.text

        completion = openai.ChatCompletion.create(
          model="gpt-3.5-turbo",
          messages=[
            {"role": "user", "content": text}
          ]
        )
  #受信したテキストをCloudWatchLogsに出力する
        print(completion.choices[0].message.content)
        text=completion.choices[0].message.content.lstrip()

        line_bot_api.reply_message(line_event.reply_token, TextSendMessage(text=text))

#例外処理としての動作
    try:
        handler.handle(body, signature)
    except LineBotApiError as e:
        logger.error("Got exception from LINE Messaging API: %s\n" % e.message)
        for m in e.error.details:
            logger.error("  %s: %s" % (m.property, m.message))
        return error_json
    except InvalidSignatureError:
        return error_json

    return ok_json
```
<br />

#### 5.2.`lambda_function.py`を含めてzip化
赤枠内全てを選択して　zip　でまとめる(一番下の赤枠は 5.1で作成したコードを指す)

![](https://cloud5.jp/wp-content/uploads/2023/03/スクリーンショット-2023-03-12-14.59.19-311x640.png)
<br />
#### 5.3.Lambda  にデプロイ
zip を　Lambda にデプロイ

![](https://cloud5.jp/wp-content/uploads/2023/03/スクリーンショット-2023-03-12-15.15.26-640x142.png)
<br />

### 6.LINE Webhook での設定
#### 6.1.LINE Webhook設定値取得
Lambda設定 > `トリガー`より `APIGateway`エンドポイントの値を取得

![](https://cloud5.jp/wp-content/uploads/2023/03/スクリーンショット-2023-03-12-15.25.20-640x141.png)

<br />

#### 6.2.LINE Webhook設定
[LINE コンソール](https://developers.line.biz/console/)から、手順1 で作成したプロバイダーへ移動して、`Messaging API設定`を選択して赤枠部分にAPIエンドポイントを入力

![](https://cloud5.jp/wp-content/uploads/2023/03/スクリーンショット-2023-03-12-15.19.11-550x640.png)
<br />

#### 6.3.LINE Webhook設定の確認
`Webhook設定`配下の`検証`を押下して下記画面が出力されることを確認する
![](https://cloud5.jp/wp-content/uploads/2023/03/スクリーンショット-2023-03-12-15.31.51.png)

<br />


------------

### 挙動の確認
#### 1.LINEのQRコード読取りで友達登録

![](https://cloud5.jp/wp-content/uploads/2023/03/スクリーンショット-2023-03-12-15.30.24-640x575.png)
<br />

#### 2.ChatGPTとのやりとりが実現される

![](https://cloud5.jp/wp-content/uploads/2023/03/IMG_8695-360x640.png)
<br />
#### 3.無料利用枠を確認する
OpenAI HP より [Usage](https://platform.openai.com/account/usage)を確認することで利用金額を把握することが可能です。

------------

## さいごに
基本的にAPIに文章を送っただけですが、少しだけChatGPTとの仲が深まったように思えます。
ChatGPTのドキュメントを読めばもう少し気の利いた実装も出来そうですが、今回の検証はここまでとします。
ちなみに、友達の作り方は「少しの勇気と積極性で、自分の興味分野を開示する」必要があるみたいです。ChatGPTに従って、楽しく積極的にブログを通じて自分を発信できればいいかと思いました。