こんにちは!金融ソリューション事業部の孫です。
現在、我々はChatGPT全般の活用を積極的に検討しており、その多様な可能性の中にはメタバースにおけるChatGPTの利用も含まれています。
この観点から、AWS上にサーバーレスアーキテクチャを採用したChatGPT活用アプリケーションの設計と構築を実施いたしました。
はじめに
ChatGPTベースのアプリケーションを構築する際には、2つの一般的な選択肢があります。
一つは既存のアプリケーションにOpenAI APIを直接統合すること、もう一つはOpenAI APIをラップしてインフラサービス化にすることです。
今回は、以下の点を考慮して、後者の選択をしました:
- セキュリティリスク:アプリケーション側でOpenAI API Keyを管理する必要があること
- コスト管理:OpenAIとのやり取りは有料で、ユーザーの質問の頻度と単語数を制御する必要があること
- 再利用可能性:開発したソリューションは他のプロジェクトに適用可能で、コストを節約できること
今回は、インフラストラクチャを設計する前に、以下の要件を明確にしました:
- 複数のユーザーが同時に一つのOpenAIアカウントを使用できること
- 従量課金制を実装し、コストを削減すること
- サーバーの維持コストを削減すること
- APIトラフィック制御を実装すること
- OpenAIへのリクエストの同時実行数を制限すること
これらの要件に基づいて、今回は以下のインフラストラクチャ設計を完成させました。
インフラストラクチャの全体像
本記事では、下記の構成図に基づき、構築作業を進めていきます。
- WebSocket API Gateway:クライアントのリクエストをバックエンドサービスにルーティングするために使用します。
- meta_connect_handler:ユーザーの認証機能を提供します。
- 本記事では、インフラ構築に焦点を当てるため、認証に関する実装は対象外としております
- meta_handler_chat:ユーザーのリクエストをAmazon SNSサービスに転送する機能を提供します。
- meta_chat:ユーザーからのリクエストをOpenAIサーバーに送信し、その結果を受け取る機能を提供します。
- meta_disconnect_handler:ユーザーがログアウトした後、その会話履歴を削除する機能を提供します。
- Dynamodb:ユーザーの会話履歴を保存するテーブルを作成します。
- SNS:ユーザーのリクエストを非同期に処理するために使用します。
上記のAWSアーキテクチャ図に記載した番号①〜⑨に沿って、処理の流れを説明します。
①: ユーザーはWebSocket URL接続を介してChatGPTサービスの利用を開始する
②: 接続は最初にWebSocketのデフォルトの/$Connect
ルートにルーティングされ、meta_connect_handler
で接続の認証を実施する
③: ユーザーは/sendprompt
ルートを通じてチャット内容をmeta_handler_chat
に送信する
④: meta_handler_chat
が処理した後、ユーザーリクエストはSNSサービスのトピックに送信する
⑤:SNSはユーザーリクエストをサブスクリプションmeta_chat
Functionに送信する
⑥: meta_chat
はLangchainを利用し、OpenAIとの対話機能をそろえ、会話の文脈はDynamoDBに保存される
⑦: OpenAIサーバーは質問回答を返し、WebSocket接続を介してユーザーに返却する
⑧: ユーザーがLogoutしたら、/$disconnect
ルートがトリガーされる
⑨: meta_disconnect_handler
はDynamoDBのユーザー会話の文脈を削除し、プロセスが終了する
実装手順
構築手順
以下の手順で構築を行います。
事前準備
- NodeJS開発環境のセットアップ
- AWSアカウントの作成
- OpenAIアカウントの作成
- OpenAI APIのアカウントを作成しAPI KEYを入手します
- API KEYの発行手順はこちらを参照してください
手順1. Lambda関数の作成
meta_connect_handler
AWS Lambdaコンソールでmeta_connect_handler
という名前のLambda関数を作成し、ランタイムとしてNode.js 18.xを選択します。
※上述した通り、本記事ではインフラ構築に焦点を当てるため、認証に関する実装は対象外です。デフォルトで生成されたコードをそのまま使用してください。meta_handler_chat
上記と同様にAWS Lambdaコンソールでmeta_handler_chat
という名前のLambda関数を作成します。
この関数はユーザーリクエストを受け取り、SNSサービスのトピックに送信する役割を果たします。
この関数にSNSのPublic権限を追加し、環境変数でSNSのARN値(SNS_TOPIC_ARN
)を設定してください。
具体的な実装コードは以下のとおりで、ユーザーのメッセージ(
event.body.user_msg
)を取得し、ユーザー接続情報とメッセージ内容を含むSNSのトピックを発行します。
// aws-sdkパッケージをインポートします import { SNSClient, PublishCommand } from "@aws-sdk/client-sns"; const client = new SNSClient(); const topicArn = process.env.SNS_TOPIC_ARN; export const handler = async(event) => { let body; if (typeof event.body === 'string') { body = JSON.parse(event.body); } else { body = event.body; } // ユーザー接続情報とメッセージ内容を含むSNSのトピックを発行します const command = new PublishCommand({ TopicArn:topicArn, Message:JSON.stringify({ requestContext:event.requestContext, payload: body.user_msg, }) }); let response; try{ await client.send(command); }catch (error) { console.log("Error:", JSON.stringify(error)); } };
meta_chat
同様な手順でAWS Lambdaコンソールでmeta_chat
という名前のLambda関数を作成します。
この関数はLangchainを使用してOpenAIサーバーとのインタラクションする役割を果たします。
DynamoDBおよびAPIGatewayへのアクセス権限を追加し、OpenAI API KEYを環境変数として設定してください。
また、Lambdaではlangchainパッケージがないため、ローカルでダウンロードし、Layerとして関数にアップロードする必要があります。
具体的な手順は以下のとおりです:- ローカルで
npm install langchain
を実行し、langchainをインストールします。 - 生成された
node_module
フォルダをZIPのパッケージ化します。 - ZIPファイルをLayerにアップロードし、関数でlayer依存関係を設定します。
注意:OpenAIサービスの実行時間は3秒を超える可能性があるため、関数の実行時間設定を調整し(3秒 → 1分 )、タイムアウトの問題を回避してください。
「コード」タブにて、下記のコードをコピーしてください。- ローカルで
// Langchainとaws-sdkパッケージをインポートします import { ChatOpenAI } from "langchain/chat_models/openai"; import { ConversationChain } from "langchain/chains"; import { BufferMemory } from "langchain/memory"; import { DynamoDBChatMessageHistory } from "langchain/stores/message/dynamodb"; import { ApiGatewayManagementApiClient, PostToConnectionCommand } from "@aws-sdk/client-apigatewaymanagementapi"; export const handler = async (event) => { console.log(JSON.stringify(event)); const body = JSON.parse(event.Records[0].Sns.Message); //SNSのトピックから必要な情報(クライアントの接続情報、ユーザーの質問内容のpayload)を読み取ります const requestContext = body.requestContext; const connectionId = requestContext.connectionId; const user_request = body.payload; // apigateway クライアントの初期化 const apigateway_client = new ApiGatewayManagementApiClient({ endpoint: 'https://'+requestContext.domainName + '/' + requestContext.stage, }); // 文脈保存先としてのDynamoDBを設定し、DynamoDBChatMessageHistoryを初期化します const chatHistory = new DynamoDBChatMessageHistory({ tableName: "Conversations", partitionKey: "ConnectionId", sessionId: connectionId, config: { region: "ap-northeast-1", }, }); // const memory = new BufferMemory({ chatHistory: chatHistory, }); // OpenAI model の初期化 const model = new ChatOpenAI({ modelName: "gpt-3.5-turbo", temperature: 0.5 }); // 初期化されたメモリおよびモデルを利用して、ConversationChainを初期化します。 const chain = new ConversationChain({ memory: memory, llm: model, verbose: true }); try { // OpenAIサーバーにリクエストを送信します const response = await chain.call({ input: user_request, }); // OpenAIからの回答を受け取り、それをクライアントに返却します const api_gateway_input = { ConnectionId: connectionId, Data: JSON.stringify(response) }; const command = new PostToConnectionCommand(api_gateway_input); await apigateway_client.send(command); } catch (error) { console.log("Error:", JSON.stringify(error)); } };
meta_disconnect_handler
meta_disconnect_handler
という名前のLambda関数を作成し、主にユーザーの会話履歴をクリアするために使用されます。
meta_chat
と同様に、DynamoDBへのアクセス権限を追加してください。
具体的なコードは以下のとおりです。
// Langchainパッケージをインポートします import { BufferMemory } from "langchain/memory"; import { DynamoDBChatMessageHistory } from "langchain/stores/message/dynamodb"; export const handler = async (event) => { console.log(JSON.stringify(event)); const requestContext = event.requestContext; const connectionId = requestContext.connectionId; const chatHistory = new DynamoDBChatMessageHistory({ tableName: "Conversations", partitionKey: "ConnectionId", sessionId: connectionId, config: { region: "ap-northeast-1", }, }); const memory = new BufferMemory({ chatHistory: chatHistory, }); //DynamoDBから該当するユーザーの会話文脈を削除します await memory.chatHistory.clear(); };
手順2. DynamoDBの作成
AWS管理コンソール上でAmazon DynamoDBコンソールを開き、Conversationsという名前のテーブルとパーティションキーをConnectionIdに設定します。
通常、パーティションキーはユーザーIDにすべきですが、今回ではログイン機能がないため、クライアント接続IDをパーティションキーとして使用します。
手順3. WebSocket API Gatewayの作成
ルーティングキー
$connect
、$disconnect
、およびsendprompt
を追加します。
Lambda関数の統合をそれぞれ設定します。
WebSocket URL(wss://)を記録します。
手順4. SNSトピックの作成
標準のSNSトピックを作成します。
サブスクリプションを作成し、ポリシーのフィールドでlambdaを選択し、
meta_chat
のARNリンクをエンドポイントのフィールドに貼り付けます。
SNSトピックARNをコピーし、
lambda_handle_chat
の環境変数SNS_TOPIC_ARN
の値に貼り付けます。
機能検証
セッションテスト
wscatツールのインストール
WebSocket URLへの接続
セッションの開始
- 質問1「what is your name?」および質問2「What did I ask you just now?」に対する回答の関連性を確認します(文脈確認)。
- 質問1「what is your name?」および質問2「What did I ask you just now?」に対する回答の関連性を確認します(文脈確認)。
DynamoDBのデータを確認
- DynamoDBで会話履歴を確認します。
- DynamoDBで会話履歴を確認します。
セッションの終了
- [ctrl + c]を押してセッションを終了し、再度DynamoDBを確認して、以前のセッション記録が削除されたことを確認します。
- [ctrl + c]を押してセッションを終了し、再度DynamoDBを確認して、以前のセッション記録が削除されたことを確認します。
複数のクライアントが同時にセッションを持つテスト
2つのクライアントで実施している会話がそれぞれ独立して行われることを確認するためのシミュレーションを行います。
終わりに
この記事では、AWS上でChatGPTベースのインフラストラクチャの設計および構築について取り上げました。
今後、本インフラ構成において以下の点についてさらなる改善を行う予定です。
- OpenAIのアクセスキーを保護するためにAWS Secret Serviceを使用します
- ユーザー数が増えるにつれて、OpenAIの使用制限に達した場合に備えて、シームレスなアカウント切り替えを実装します
- より正確な回答を提供するためにVectorDBを統合することを検討しています
- より対話型のユーザーエクスペリエンスを提供するために、ストリーミング応答モデルに移行します
これからもChatGPTの応用シナリオについてさらに探求し、その結果をAIに興味を持つ読者と継続的に共有し続ける予定です。
現在ISIDはweb3領域のグループ横断組織を立ち上げ、Web3およびメタバース領域のR&Dを行っております(カテゴリー「3DCG」の記事はこちら)。
もし本領域にご興味のある方や、一緒にチャレンジしていきたい方は、ぜひお気軽にご連絡ください!
私たちと同じチームで働いてくれる仲間を、是非お待ちしております!
ISID採用ページ(Web3/メタバース/AI)
執筆:@chen.sun、レビュー:@wakamoto.ryosuke
(Shodoで執筆されました)