Supershipの名畑です。スナックバス江は肩の力を抜いて見られるのでいいですね。年齢を重ねる毎にこういう作品が好きになっていきます。
はじめに
AWS(Amazon Web Services)でWebアプリを作ることがあるのですが、その過程で触れてきたものについて、せっかくなので要点だけを抽出して残しておくことにしました。
特に目新しい内容はありませんが、AWSやOpenAIのアカウント作成も含めて一通りの流れを残すので、いい具合の備忘になればと。
今回はLambdaを経由してOpenAIのAPIを叩くまでをまとめます。
Lambdaとは
AWS Lambda は、サーバーをプロビジョニングまたは管理せずにコードを実行できるようにするコンピューティングサービスです。
Lambda は可用性の高いコンピューティングインフラストラクチャでコードを実行し、コンピューティングリソースに関するすべての管理を行います。これには、サーバーおよびオペレーティングシステムのメンテナンス、容量のプロビジョニングおよび自動スケーリング、さらにログ記録などが含まれます。Lambda で必要なことは、サポートするいずれかの言語ランタイムにコードを与えることだけです。
私の環境
OSやブラウザ依存の部分はそこまで多くないですが、一応残しておきます。
MacOS Ventureで、ブラウザはChromeです。
AWSのアカウントを作る
私はすでにAWSアカウント取得済みでしたが、初めての方は「AWS アカウント作成の流れ」に従ってアカウントを作成してください。
AWS CLIをインストールする
AWS CLI(AWS コマンドラインインターフェイス)を入れておきます。
今回の内容であればなくても完結させられはするのですが、私は手元のコードをコードアップロードする等がしたいので入れました。
リンク先から自環境に適したインストーラをダウンロードします。
CLIは動かすためにリージョン(ロケーションの単位、東京であればap-northeast-1)の登録などが必要なのですが、configureコマンドで設定するのが早いでしょうか。
詳しくは下記あたりをご覧ください。
OpenAIのアカウント作成とAPI Keyの取得
OpenAIのアカウント作成はOpenAI platformで行えます。
API呼び出しに用いるAPI keyの取得はサインイン後のAPI keysのページで行えます。Create new secret keyを押して、名前(optional)をつけるだけです。
API keyは作成時しか画面に表示されませんので気をつけてください。画面上から後で知る方法はありません。
OpenAIのSDKのダウンロード
後ほどLambdaでOpenAIのAPI呼び出しを行います。
そのためにOpenAIのSDKをダウンロードしておきます。
ただし、普通にダウンロードして普通にアップロードをすると、Lambdaからの呼び出しのタイミングで下記のエラーが発生します。
{
"errorMessage": "Unable to import module 'LambdaFunctionOverHttps': No module named 'pydantic_core._pydantic_core'",
"errorType": "Runtime.ImportModuleError",
"requestId": "略",
"stackTrace": []
}
どうやらこちらのissueでやりとりされているもののようです。
「AWS Lambda Layers + OpenAI でつまづいた件」という記事で同種のエラーを上げられている方もいらっしゃいました。
これを回避するため、今回はfastapiのバージョンを下げた上でダウンロードします。
まず、私の環境ではPythonは3.10です。今回は全環境を3.10で統一することとします。
$ python --version
Python 3.10.12
次にSDKのダウンロードです。fastapiのバージョンを指定した上で、あらかじめ用意しておいたpythonというフォルダにダウンロードします。
$ python -m pip install -t ./python fastapi==0.99.0 openai
これをzip形式でopenai.zipとして固めておきます。後ほど使います。
$ zip -r openai.zip ./python
Lambda関数の作成
ブラウザのコンソールでLambdaを開いて「関数の作成」をクリックします。
「一から作成」を選び、関数名はopenai-sampleとしました。
ランタイムはPython 3.10を選びました。
これで「関数の作成」をクリックするとLambda関数が作られます。
デフォルトでのコードは下記です。
import json
def lambda_handler(event, context):
# TODO implement
return {
'statusCode': 200,
'body': json.dumps('Hello from Lambda!')
}
OpenAIのSDKのアップロード
Lambdaでサイドメニューからレイヤーを選んで「レイヤーの作成」をクリックします。
名前はopenaiとしておきます。
先ほど作成したopenai.zipをアップロードするファイルとして選びます。
互換性のあるアーキテクチャはx86_64とarm64を選びます。
互換性のあるランタイムはpython3.10とします。
最後に作成をクリックします。
スクリーンショット上では私が色々といじったのでバージョンが8となっていますが、実際の初期値は1です。バージョン番号はレイヤーを更新する度にアップデートされていきます。
この状態で先ほどのコード編集画面に戻り「レイヤーの追加」でカスタムレイヤーからopenaiを選びます。
すると下記のスクリーンショットのように追加されます。
環境変数の設定
API_KEYをコードに直接記述するのはよろしくないので、Lambdaの設定の環境変数で
- キー:OPENAI_API_KEY
- 値:OpenAIで取得したAPI-Key
を設定しておきます。スクリーンショットのhogeを実際の値にしましょう。
タイムアウトの変更
Lambdaの「設定」の「基本設定」で「タイムアウト値」を3秒から伸ばしておきます。
3秒だとレスポンスがほとんど返ってこないためです。
今回は60秒としておきました。
Pythonのコードを書く
import openai
import json
import os
from openai import OpenAI
openai.api_key = os.environ['OPENAI_API_KEY'] # 環境変数から取得
def lambda_handler(event, context):
try:
client = OpenAI()
completion = client.chat.completions.create(
model="gpt-4",
messages=event
)
except Exception as e:
raise e
return {
'statusCode': 200,
'body': json.dumps(completion.choices[0].message.content)
}
受け取った内容をそのままOpenAIのGPT4に投げるだけのコードです。
実際は受け取った内容に応じて分岐するでしょうし、エラー処理もきちんと書く必要があるでしょう。今はExceptionをそのまま投げているだけなので無意味な処理です。
今回は本題ではないので深くは触れませんが、OpenAIのAPIについて詳しくはAPI referenceをご覧ください。
コンソール上で直接このコードを貼り付けても大丈夫ですが、今回はせっかくなのでローカルからアップロードします。
lambda_function.pyというファイル名でローカルで保存します。
ファイル名はLambdaでのデフォルトです。コードの画面で確認できます。
まず、zipで圧縮します。
$ zip function.zip ./lambda_function.py
次にアップロードします。
$ aws lambda update-function-code --function-name openai-sample --zip-file fileb://function.zip
Lambdaのコードが更新されていることがコンソールでわかります。
Lambda関数を呼び出してみる
下記の内容をinput.txtというファイル名で保存しておきます。OpenAIのAPIに渡す内容をそのまま書いたものです。
[
{
"role": "user",
"content":"あなたはOpenAIのAPIですか?"
}
]
ターミナルからコマンドを叩いてみます。input.txtの内容をLambdaに渡して、レスポンスをoutput.txtに出力するコマンドです。
$ aws lambda invoke --function-name openai-sample --payload file://input.txt output.txt --cli-binary-format raw-in-base64-out
しばらく待つとレスポンスがありました。
{
"StatusCode": 200,
"ExecutedVersion": "$LATEST"
}
output.txtの中身をcatで見てみます。
$ cat output.txt
{"statusCode": 200, "body": "\"\\u306f\\u3044\\u3001\\u305d\\u306e\\u901a\\u308a\\u3067\\u3059\\u3002\\u79c1\\u306fOpenAI\\u306e\\u4eba\\u5de5\\u77e5\\u80fd\\u30a2\\u30b7\\u30b9\\u30bf\\u30f3\\u30c8\\u3067\\u3001\\u8c4a\\u5bcc\\u306a\\u77e5\\u8b58\\u3068\\u6a5f\\u68b0\\u5b66\\u7fd2\\u306e\\u529b\\u3092\\u4f7f\\u3063\\u3066\\u3001\\u30e6\\u30fc\\u30b6\\u30fc\\u304b\\u3089\\u306e\\u3055\\u307e\\u3056\\u307e\\u306a\\u8cea\\u554f\\u3084\\u8981\\u6c42\\u306b\\u5fdc\\u3048\\u308b\\u3053\\u3068\\u304c\\u3067\\u304d\\u307e\\u3059\\u3002\""}%
Unicodeをデコードしないと読めないですね。
もっと楽な手もあるかもしれませんが、sedとnkfでデコードして出力します。
$ cat output.txt | sed 's/\\\\\u\(....\)/\&#x\1;/g' | nkf --numchar-input -w
{"statusCode": 200, "body": "\"はい、その通りです。私はOpenAIの人工知能アシスタントで、豊富な知識と機械学習の力を使って、ユーザーからのさまざまな質問や要求に応えることができます。\""}%
bodyの中身が
はい、その通りです。私はOpenAIの人工知能アシスタントで、豊富な知識と機械学習の力を使って、ユーザーからのさまざまな質問や要求に応えることができます。
こうなっていますので、無事にOpenAIのAPIの呼び出しが行えていそうです。
デコードに用いたnkfはデフォルトだとインストールされていませんので、ご注意ください。もしも使うならbrew等でインストールしましょう。
$ brew install nkf
最後に
AWSに触れる度、本当に便利な時代になったものだと実感します。
次回は今回作成したLambda関数とAPI Gatewayを接続してみる予定です。
そうすることによってURLを指定してLambda関数を叩けますので。今はLambda関数URLがあるので、用途によってはこれだけで充分かもしれませんが。
宣伝
SupershipのQiita Organizationを合わせてご覧いただけますと嬉しいです。他のメンバーの記事も多数あります。
Supershipではプロダクト開発やサービス開発に関わる方を絶賛募集しております。
興味がある方はSupership株式会社 採用サイトよりご確認ください。