API GatewayとLambdaでChatGPTのAPIを使ってみる

API GatewayとLambdaでChatGPTのAPIを使ってみる
この記事をシェアする

はじめに

こんにちは!CloudBuildersのアミです。

この記事はクラウドビルダーズ Advent Calendar 2024の2日目の記事です。

タイトルの通り、今回はAPI GatewayとLambdaでChatGPTのAPIを使ってみようと思います。
すぐできちゃいそう~と思って始めたもののしっかりつまずいたので、記事にすることにしました。

ちなみにChatGPTはサービス名なので、厳密にはChatGPTではなくOpenAIのAPIですが、本記事ではイメージのしやすさ重視でChatGPTのAPIと表現します。

完成イメージ

フロント資材をCloudFront経由で取得して、クライアント側からAPIを呼び出します。
APIは直接API Gateway→Lambdaを経由して、LambdaからChatGPTのAPI呼び出します。

作ってみる

ChatGPT API (有料)

まずはChatGPTのAPIを使えるようにします!
必要な作業はAPI Keyの取得とクレジットの購入の2点です。
クレジットの購入はプリペイド方式で、最低でも5$の支払いが必要になります。でも5$だけで目一杯楽しめます!!
参考:料金

API Keyの取得

OpenAI developer platformにログインします。ChatGPTと同じアカウントでログイン可能です。
Dashboardの左ペインにあるAPI keysを選択して「+ Create new secret key」からAPI Keyの作成をします。

今回はほぼデフォルトで作成しました。

作成されたAPIキーは一度ウィンドウを閉じてしまうと再表示ができないので、忘れずに控えておきましょう。

クレジットの購入

画面右上の歯車マークを押して設定画面を開きます。
左ペインにあるBillingを選択して「Add payment details」からクレジットカードを登録します。

今回は個人利用なのでIndividualで登録しました。

クレジットカード情報画面(Individual)はこんな感じです。

クレジットカードの登録をするとそのままチャージ設定に進みます。
最低金額の5$にしてオートチャージを無効化しました。

これでChatGPTのAPIを使うための準備ができました!

Lambda

続いてLambdaからChatGPTのAPIを呼べるようにしていきます。

Secrets Managerの設定

先ほど作成したAPIキーを使用するので、予めSecrets Managerに登録しておきましょう。
Lambdaのコードの中ではキーに設定した文字列(gpt_api_key)を使用します。

項目設定値
シークレットのタイプその他のシークレットのタイプ
キー/値のペアキー:gpt_api_key
値:ChatGPTのAPIキー
暗号化キーaws/secretsmanager
シークレットの名前gpt_api_key
記載がない項目は全てデフォルトで作成

Lambda Layer作成

あとはこのままLambda関数を作成して終わりだと思っていたのですが、いざ実行してみると下記エラーが発生。。。

[ERROR] Runtime.ImportModuleError: Unable to import module 'lambda_function': No module named 'openai'

openaiというモジュールをLambdaレイヤーで自分で用意をする必要がありました。
AWS外のモジュールだからそりゃそうか~~と思いつつ、私は今回Lambdaで使用するPythonの実行環境がなく、今から用意するのも億劫だったのでどうしようか悩みどころでした。
結局、AWSのマネジメントコンソール上でCloudShellを使ってインストールして、一度ローカルに持ってきてLambdaレイヤーとして登録することにしました。

CloudShellで以下のコマンドを実行してopenaiのモジュールをインストール、zip化します。

mkdir python
pip install urllib3==1.26.16
pip install -t ./python openai==0.28
zip -r openai.zip ./python

バージョン指定をしているのは、Lambda関数の実行時に下記のエラーが発生したからです。

[ERROR] APIRemovedInV1: 

You tried to access openai.Completion, but this is no longer supported in openai>=1.0.0 - see the README at https://github.com/openai/openai-python for the API.

You can run `openai migrate` to automatically upgrade your codebase to use the 1.0.0 interface. 

Alternatively, you can pin your installation to the old version, e.g. `pip install openai==0.28`

無事にインストールができたら、CloudShellの右上にある「アクション」から「ファイルのダウンロード」を選択して、zip化したモジュールをダウンロードします。

あとはLambdaレイヤーを作成してopenai.zipをそのままアップロードしたらOKです。

項目設定値
名前Lambda_Layer_openai
アップロード方法.zip ファイルをアップロード(openai.zip)
互換性のあるアーキテクチャx86_64
互換性のあるランタイムランタイムPython 3.9
記載がない項目は全てデフォルトで作成

Lambda関数作成

やっとLambda関数の作成です(実際には先に作成していましたが)。今回は前述の通りPythonで作成しました。
実行ロールの内容は割愛させていただきますが、
・Secrets Managerの読み取り
・CloudWatch Logsのログストリーム作成、ログ送信
の許可が必要となります。

項目設定値
関数の作成一から作成
関数名LambdaDemo
ランタイムPython 3.9
アーキテクチャx86_64
実行ロール既存のロールを使用する
(Secrets Manager、CloudWatch Logsの操作を許可したロールを使用)
タイムアウト30秒
記載がない項目は全てデフォルトで作成

Lambdaレイヤーも登録します。

項目設定値
レイヤーソースカスタムレイヤー
カスタムレイヤーLambda_Layer_openai
バージョン1
記載がない項目は全てデフォルトで作成

ソースコードはこんな感じです。
chat_response内のパラメータでAPIの設定を変更できます。GPTのモデルはgpt-4にしました。
その他のパラメータもイイ感じに調整なんかしちゃったりしてLambda関数は完成です☺

import json
import boto3
import openai

# OpenAI APIキーをAWS Secrets Managerから取得
def get_openai_api_key():
    secretsmanager = boto3.client('secretsmanager')
    secret_name = "gpt_api_key"
    response = secretsmanager.get_secret_value(SecretId=secret_name)
    secret = json.loads(response['SecretString'])
    return secret['gpt_api_key']

# ChatGPT APIを呼び出す
def lambda_handler(event, context):
    # リクエストからテキストを取得
    body = json.loads(event['body'])
    original_text = body['text']

    # Secrets ManagerからOpenAI APIキーを取得
    openai.api_key = get_openai_api_key()
    
    # ChatGPTで回答を作成
    prompt = original_text
    chat_response = openai.ChatCompletion.create(
        model="gpt-4",
        messages=[
            {"role": "system", "content": "あなたは、どんなネガティブな話題もテンション高く、勢いでお祭り騒ぎに変える日本語アシスタントです。ため口で話し、絵文字をガンガン使って、ユーザーをとにかく盛り上げます!3~4文程度で、勢い第一、ちょっと適当でもOK。元気とノリで解決するスタイルを貫いてください!"},
            {"role": "user", "content": prompt}
        ],
        max_tokens=300
    )

    response_text = chat_response['choices'][0]['message']['content'].strip()
    
    # 結果を返す
    return {
        'statusCode': 200,
        'body': json.dumps({
            'response_text': response_text
        }),
        'headers': {
            'Content-Type': 'application/json',
            'Access-Control-Allow-Origin': '*'
        }
    }

API Gateway

次はAPI Gatewayを作成します。

API作成

APIを作成します。特段変わった設定などはないですが、APIタイプは要件に応じてREST APIも検討すると良いかと思います。

項目設定値
API タイプHTTP API
API名TestAPI
エンドポイントのタイプリージョン
記載がない項目は全てデフォルトで作成

ルートとメソッド設定

作成したAPIにルートとメソッドを設定します。今回は1つしか作成しないのでルートのパスは”/”で作っちゃいます。

項目設定値
メソッドPOST
ルート/
記載がない項目は全てデフォルトで作成

統合の作成

作成したルートにLambda関数を統合します。

項目設定値
この統合をルートにアタッチするPOST /
統合タイプLambda関数
統合ターゲット(使用するLambda関数とリージョン)
アクセス許可を呼び出すtrue
記載がない項目は全てデフォルトで作成

CORS有効化

フロント資材とAPIを別でホストしているため、CORSを有効化します。

項目設定値
Access-Control-Allow-Origin*
Access-Control-Allow-Headers*
Access-Control-Allow-Methods
POST
記載がない項目は全てデフォルトで作成

Auto Deployを設定していない場合はデプロイをして、API Gatewayも準備OKです!

S3

次はS3を作成してフロント資材を配置します。

バケット作成

ほぼデフォルト設定で作成しました。

項目設定値
バケットタイプ汎用
バケット名(任意のバケット名)
オブジェクト所有者ACL 無効
パブリックアクセスをすべて ブロックオン
記載がない項目は全てデフォルトで作成

資材アップロード

S3バケットが作成できたら、index.htmlとstyles.cssをアップロードします。
index.htmlは無駄に音声認識をいれちゃいました。

CSSは全部ChatGPTに作ってもらっちゃったので割愛します。。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>DemoApp</title>
    <link rel="stylesheet" href="styles.css">
</head>
<body>
    <div class="container">
        <div class="grid-container">
            <div class="character-container">
                <img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjpVGU-kSsQfe4Oq3p68XJiZ-HJbCdL0ASRrnemQvmHo_v3bLheV2MUV05wlRQdURhVHkHVnW7K6VQRJajJIR1NuHyMOnngZ2S-NwO2OQU5YQC2Mosb6qop5aCtb4YB2mCQxhdhxgZc1ZVs/s800/dance_party_bus.png" alt="" class="character-image">
                <div class="speech-bubble">
                    <p id="gpttext"></p>
                </div>
            </div>
        </div>

        <!-- 録音ボタン -->
        <button id="start-record-btn">録音開始</button>
        
        <!-- 音声認識結果表示 -->
        <p id="transcribed-text"></p>
    </div>

    <script>
        // 音声認識を実装(Web Speech APIを利用)
        const startRecordBtn = document.getElementById('start-record-btn');
        const transcribedTextElem = document.getElementById('transcribed-text');
        const gptTextElem = document.getElementById('gpttext');

        startRecordBtn.onclick = function () {
            const recognition = new (window.SpeechRecognition || window.webkitSpeechRecognition)();
            recognition.lang = 'ja-JP';

            recognition.onstart = function() {
                transcribedTextElem.textContent = "録音中...";
            };

            recognition.onspeechend = function() {
                recognition.stop();
            };

            recognition.onresult = function(event) {
                const transcribedText = event.results[0][0].transcript;
                transcribedTextElem.textContent = transcribedText;

                // GPT処理をバックエンドにリクエスト
                fetch('https://xxxxxxx', { //API Gatewayで作成したエンドポイントを指定
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json'
                    },
                    body: JSON.stringify({ text: transcribedText })
                })
                .then(response => response.json())
                .then(data => {
                    gptTextElem.textContent = data.response_text;
                })
                .catch(error => {
                    console.error('エラーが発生しました:', error);
                    gptTextElem.textContent = "処理に失敗しました";
                });
            };

            recognition.onerror = function(event) {
                transcribedTextElem.textContent = "音声認識に失敗しました: " + event.error;
            };

            recognition.start();
        };
    </script>
</body>
</html>

以上でS3もできました!もう一息です!

CloudFront

最後にCloudFrontを作成して完了です。

ディストリビューション作成

S3用のディストリビューションを作成します。キャッシュ周りの設定含め、ほぼデフォルトで作成しました。

項目設定値
オリジンドメイン作成したS3バケットを指定
オリジンアクセスOrigin access control settings(デフォルト設定でOACを作成)
Origin Shieldを有効化いいえ
WAF無効
デフォルトルートオブジェクトindex.html
記載がない項目は全てデフォルトで作成

バケットポリシー設定

OACを作成するとS3バケットポリシーが生成されるので、S3に移動してバケットポリシーを追加で設定します。

これでCloudFrontもできました!!

動かしてみる

ディストリビューションドメイン名でアクセスして、実際に使ってみます!!
録音開始ボタンを押して話しかけると認識して処理してくれます。

盛り上がってますね~~~

おわりに

この記事をシェアする
著者:アミ
好きなAWSサービスはAMIです!