Serverless Framework + AWS LambdaでのChatGPT APIの作成方法（github actionsでの自動deploy）

こんにちは。プログラミングスクール[Happiness Chain](https://happiness-chain.com/)でメンターをしているryoです。

今回、Pyton on Django + Next.jsのアプリにて、ChatGPTを機能として盛り込むため、serverless framework + AWS Lambdaにて、ChatGPT APIを作成しましたのでそちらの手順を紹介します。

## serveless frameworkのプロジェクトを作成
※Node.jsとServerless Frameworkをインストールしていない場合はインストールします。

```
sls create --template aws-python3 --name chatgpt-api --path chatgpt-api
```
言語はpythonのため、テンプレートはaws-python3を使用します。
上記実行にて、handler.pyとserverless.ymlが含まれたディレクトリが作成されます。

## serverless.ymlを編集

最終的な内容は以下の通りです。
```serveless.yml
service: chatgpt-api
frameworkVersion: "3"

provider:
  name: aws
  runtime: python3.9
  stage: ${opt:stage, 'prod'}
  region: ${opt:region, "ap-northeast-1"}
  timeout: 180
  layers:
    - Ref: PythonRequirementsLambdaLayer

functions:
  get_text:
    handler: handler.chatgpt
    url:
      authorizer: aws_iam
      cors:
        allowCredentials: true
        allowedHeaders:
          - Content-Type
          - Authorization
        allowedMethods:
          - POST
        allowedOrigins:
          - http://localhost:3000
          - https://xxxxxx.com
        exposedResponseHeaders:
          - Special-Response-Header
        maxAge: 6000
custom:
  pythonRequirements:
    dockerizePip: true
    zip: true
    layer: true
plugins:
  - serverless-python-requirements
```

ChatGPTに利用するoepnaiのライブラリは重いため、serverless-python-requirementsというプラグインを使用します。
こちらを使用することで、外部ライブラリを関数に含めずデプロイできます。

```yml
custom:
  pythonRequirements:
　　　　　　　　# docker上にライブラリをインストールする、LambdaサーバのOSに依存しなくなる
    dockerizePip: true
    # ライブラリの圧縮
    zip: true
    # 依存ライブラリのレイヤー化
    layer: true
```
関数を外部から呼び出すために、選択肢として、以下の通りです。

- API Gateway
  - メリット
    - 複数関数ある（REST API）の場合、ひとつのhostnameから各関数にpathを割り振れる
    - ドメインをカスタマイズできる
  - デメリット
    - timeoutがmax30秒（stepfnuctionsを使えば回避できるが、構築するのにやや面倒。リクエストを2回送る必要あり、front側も一工夫必要。）


- Funtion URL
  - メリット
    - タイムアウトがmax3000秒
    - 構築が楽
  - デメリット
    - ドメインをカスタマイズできない

今回chatgptへのinput、outputの文量が1000文字以上見込まれ、処理が30秒を超えるため、Function URLを採用。
認証として、IAMを使用。
corsも設定。

```yml
functions:
  chatgpt:
    handler: handler.chatgpt
    url:
      authorizer: aws_iam
      cors:
        allowCredentials: true
        allowedHeaders:
          - Content-Type
          - Authorization
        allowedMethods:
          - POST
        allowedOrigins:
          - http://localhost:3000
          - https://xxxxxxxx.jp
        exposedResponseHeaders:
          - Special-Response-Header
        maxAge: 6000
```

## pluginをインストール

serverless-python-requirementsを使用するためpluginをインストールします。
```shell
serverless plugin install -n serverless-python-requirements
```

## requirements.txtを作成
```requirements.txt
openai

```

## handler.pyを編集

```handler.py
import json
import os

import openai

openai.api_key = os.environ["OPENAI_API_KEY"]


def chatgpt(event, context):
    body = json.loads(event["body"])
    if "user" not in body:
        return {
            'statusCode': 400,
            'headers': {
                "Access-Control-Allow-Headers": "Content-Type",
            },
            'body': "Arguments Error: user is required"
        }
    messages = []
    messages.append({"role": "user", "content": body["user"]})

    if "system" in body:
        messages.append({"role": "system", "content": body["system"]})

    if "assistant" in body:
        messages.append({"role": "assistant", "content": body["assistant"]})

    response = openai.ChatCompletion.create(
        model="gpt-4",
        messages=messages,
    )

    return {
        'statusCode': 200,
        'headers': {
            "Access-Control-Allow-Headers": "Content-Type",
        },
        'body': json.dumps({"text": response["choices"][0]["message"]["content"]})
    }

```

## AWS Configureの設定
```
aws configure
```

```
AWS Access Key ID:
AWS Secret Access Key:
Default region name:
Default output format:
````

## deploy
```
sls deploy
```

## 環境変数の設定
上記コマンドにてAWS Lambdaに関数が作成されるので、
そちらに環境変数を設定します。
![スクリーンショット 2023-10-02 12.29.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634526/b683c545-224c-3edc-0147-eb71e44eedf5.png)


## Postmanで動作確認
Authoriztion TypeとしてAWS Signatureを選択。
AccessKeyとSecretKeyはLambdaアクセス用に作成したIAMを設定。
AWS RegionとSevice Nameを設定。

![スクリーンショット 2023-10-02 11.41.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634526/9758c975-3bba-ff8e-a745-c624378bbbcc.png)

bodyとして以下のように記述することで、以下のようなレスポンスを得られます。

![スクリーンショット 2023-10-02 11.42.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634526/f15d8043-f5cb-539e-67ab-23e48bf039d6.png)

## github actionsでの自動deploy

[serverless/github-action](https://github.com/serverless/github-action)使用でのdeployにて不明なエラーがあったため、
github actions内でserverlessをインストールしてデプロイするようにしています。
デプロイ前に、AWSの認証をしており、githubのsecretsに設定する必要があります。
また、pluginを使用しているので、pluginのインストールもする必要があります。


```.github/workflows/deploy-serverless.yml
name: Deploy main branch

on:
  push:
    branches:
      - main

jobs:
  deploy:
    name: deploy
    runs-on: ubuntu-latest
    permissions:
      id-token: write
      contents: read
    strategy:
      matrix:
        node-version: [16.x]
    steps:
      - uses: actions/checkout@v3
      - name: Use Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.node-version }}
      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-region: ap-northeast-1
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
      - name: serverless config
        run: npx serverless config credentials --provider aws --key ${{ secrets.AWS_ACCESS_KEY_ID }} --secret ${{ secrets.AWS_SECRET_ACCESS_KEY }}
      - name: serverless install
        run: npm i -g serverless
      - name: serverless python requirements install
        run: serverless plugin install -n serverless-python-requirements
      - name: serverless deploy
        run: serverless deploy
```