30分で完了、AWS SAMを用いてLLM APIを構築しよう
この記事は「BEMALab アドベントカレンダー 2024」18日目の記事です。
はじめに
初めまして、DevOpsリードカンパニーの蔡(サイ)です。
現在、生成AIの話題が非常に盛んで、社内で生成AIサービスを自社構築したいというニーズも大幅に増加しています。
特に、APIを呼び出すだけの小規模な機能が求められるケースが多いです。小規模な機能のみ提供する場合、AWSのサーバーレスサービスは、EC2のような仮想マシンを提供するサービスと比べて、非常にコスト効率が良いです。
同時に、AWSのBedrockサービスも多くのLLMモデルをサポートしています。そこで、今回はAWSのサーバーレスサービスを活用してLLMサービスを簡単かつ迅速に構築する方法についてご紹介します。
AWS SAMについて
AWS Serverless Application Model (AWS SAM) は、Infrastructure as Code (IaC) を使用した、サーバーレスアプリケーション構築のためのオープンソースのフレームワークです。AWSサーバーレスサービスを迅速に開発、テスト、デプロイがすることが可能です。以下の内容では、SAMを利用してLLM APIを構築する方法について紹介します。
AWS Bedrockについて
Amazon Bedrockは、複数のAI企業の基盤モデルを単一のAPIで利用できるフルマネージドサービスです。モデルの評価、微調整、RAG(Retrieval-Augmented Generation)の実装が可能で、AWSの他のサービスとも統合できます。AIアプリケーションの構築が容易になります。
作業の事前準備
今回の構築環境は "Ubuntu 22.04 LTS" です。
構築作業の前に、以下を参照ください。
インストール完了後、「sam --version」コマンドを実行してバージョン番号が表示されれば、インストールは成功です。
手順1:SAMでAPI GatewayとLambda Functionの構築
"sam init" コマンドを実行すると、プロジェクトテンプレートを選択するための選択肢が表示されます。今回は、表示されたオプションの中から1番目を選択します。
多数のプロジェクトテンプレートオプションが表示されました。この中から、10番目のオプションを選択します。
開発環境に合わせて実行環境を選択します。今回の開発環境にはすでにPython 3.12がインストールされているので、5を選択します。
次に、以下の監視およびログ設定に関する質問が表示されます:
- X-Rayの有効化
- CloudWatch Application Insightsの有効化
- ログのJSON形式での出力
これらの設定については、すべて「いいえ」(または「N」)を選択します。
続いてプロジェクト名を設定します。プロジェクト名を設定した後は、今回生成したテンプレートの内容が画面に表示されます。これで基本の設定が完了です。テンプレートから生成されたファイルはプロジェクトと同名のフォルダに格納されています。
リソース構築開始
手順2:template.yamlを編集
"sam-llm-api"フォルダー内の template.yaml ファイルの中身を確認します。template.yaml は AWS SAM の中核となる設定ファイルです。サービスの構成、権限の設定、環境変数の定義、デプロイの設定、すべて template.yaml で設定します。
下記は編集済みの template.yaml です。使用しないリソースが多いため、たくさんのセクションを削除しました。今回はDynamoDB等のデータベースを利用しませんので、テーブルに接続する設定のリソースを削除しました。Resources セクションの中で、TableConnector と SampleTable の設定を削除しました。最初は Lambda 関数が三つありましたが、今回は POST メソッドを使用する putItemFunction だけを残します。利用する開発環境は ARM の CPU であるため、"Architectures: - arm64" の設定を追加しました。Outputs セクションに表示される Endpoint は、後ほど公開される API の Endpoint です。
手順3:BedrockのLLMモデルへ接続権限の設定
Amazon Bedrockのモデルを使用する前に、いくつかの注意点があります:
- Bedrockのモデルへのアクセスを有効にする必要があります。
- ユーザーにAmazon Bedrockのモデルを使用する権限を付与する必要があります。
- ユーザーはSDKに接続するための認証情報を取得する必要があります。
- 利用可能なモデルはリージョンごとの制限がありますので、Bedrock内に記載されてる情報を参考にして選ぶ必要があります。今回はAnthropic社のclaude-3-sonnet-20240229モデルを選択します。リージョンを「us-east-1」に設定しました。
環境変数の設定は以下のように記入します。紙幅の制限があるため、今回は直接 template.yaml にキーを記入しています。実際のデプロイ時には、もっと安全性が高い方法でキーを置き換えてください。
手順4:Lambda Functionの作成
Lambdaのソースコードは "sam-llm-api" フォルダーの下の src/handlers フォルダー内に格納されています。今回は put_item.py (putItemHandler) を使用します。
LLMのモデルとしてClaudeを使用するため、接続APIには Anthropic Claude Messages API を使用します。ソースコードは公式のサンプルコードをそのまま使用しました。作成したLambda関数は以下の通りです。
system_prompt の設定では、LLMに与える前提条件を指定できます。LLMモデルが指示に正しく従うかどうかをテストするため、日本語でのみ回答するという条件を設定しました。
import os
import boto3
import json
from botocore.exceptions import ClientError
def generate_message(bedrock_runtime, model_id, system_prompt, messages, max_tokens):
body=json.dumps(
{
"anthropic_version": "bedrock-2023-05-31",
"max_tokens": max_tokens,
"system": system_prompt,
"messages": messages
}
)
response = bedrock_runtime.invoke_model(body=body, modelId=model_id)
response_body = json.loads(response.get('body').read())
return response_body
def putItemHandler(event, context):
if event["httpMethod"] != "POST":
raise Exception(f"putItemHandler only accept POST method, you tried: {event.httpMethod}")
try:
# Create an Amazon Bedrock Runtime client.
brt = boto3.client(
'bedrock-runtime',
aws_access_key_id=os.getenv('ACCESS_KEY'),
aws_secret_access_key=os.getenv('SECRET_KEY'),
region_name=os.getenv('ACCESS_REGION')
)
# Set the model ID, e.g., Claude 3 Haiku.
max_tokens = 5000
model_id = os.getenv('MODEL_ID')
system_prompt="日本語だけで回答してください。"
body = json.loads(event["body"])
# Prompt with user turn only.
user_message = {"role": "user", "content":body["body"]["prompt"]}
messages = [user_message]
response_content = generate_message(brt, model_id, system_prompt, messages, max_tokens)
result = response_content["content"][0]["text"]
return {
'statusCode': 200,
'headers': {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
},
'body': json.dumps({
'message': 'Success',
'data': result,
})
}
except KeyError as err:
return {
'statusCode': 500,
'headers': {
'Content-Type': 'application/json'
},
'body': json.dumps({
'error': 'Internal Server Error',
'message': str(err),
})
}
except (ClientError, Exception) as err:
return {
'statusCode': 500,
'headers': {
'Content-Type': 'application/json'
},
'body': json.dumps({
'error': 'Internal Server Error',
'message': str(err),
})
}
手順5:リソースをビルド
"sam build" コマンドを実行すると、SAMがコードをビルドします。以下のメッセージが表示されれば、ビルドは成功です。ソースコードを変更した場合は、毎回再ビルドが必要になります。、ビルドが成功した後、ターミナルで "Command you can use next"の説明が現れます。こちらのコマンドを利用することで、ローカル環境でLambda関数とAPIのさまざまなテストが可能です。
手順6:ローカルでLambda Function をテスト
events フォルダー内に、イベント実行時に使用する JSON ファイルが格納されています。今回は event-post-item.json に以下のような JSON データを記入します。
この JSON データでは、LLM モデルに「中国語で自己紹介できますか」と質問します。
"sam local invoke -e ./events/event-post-item.json" コマンドを実行すると、event-post-item.json のデータが Lambda 関数に渡されます。
テスト結果がターミナルに表示されました。レスポンスのメッセージは Unicodeエスケープされているので読めませんが、LLMモデルから正常にレスポンスを返されました。これにより、BedrockのLLMモデルへの接続が成功したことを確認できました。
手順7:ローカルでAPIをテスト
"sam local start-api" コマンドを実行すると、SAMがDockerコンテナ内でシミュレーション環境を起動します。その後、Lambda関数のAPIエンドポイントが表示されます。今回のローカルエンドポイントは http://127.0.0.1:3000/ です。
Thunder Client(Postmanと同様の機能)でAPIエンドポイントにPOSTリクエストを送信した後、LLMモデルから応答がありました。LLMモデルに日本語限定の条件を設定しているため、中国語での自己紹介はできませんでした。
AWS 環境へデプロイ
ローカルでテストがすべて完了しました。後にAWSの環境にデプロイしたいと思います。"sam deploy"コマンドを使用します。samはCloudFormationを通じてアプリケーションをAWSにデプロイします。
リソースのデプロイ状況はすべてターミナルに表示されます。最後の「Value」に表示されるのはAPI Gatewayのエンドポイントです。
このエンドポイントにPOSTリクエストを送信してみます。今回はLLMに日本語でLLMモデルについて説明するようお願いしましたが、正常に応答が返ってきました。今回はプロンプトに対して日本語で適切に回答しました。
最後に、AWSにログインし、AWS コンソールでAPI GatewayとLambda Functionのデプロイが完了したことを確認します。
さいごに
AWS SAMは、サーバーレスアプリケーションの構築からデプロイまでを簡単に実現できる強力なIaCツールです。生成AIサービスを迅速に構築する方法を探す中で、AWS SAMが適しているかどうか試してみることにしました。その結果、生成AIとAWS Bedrockを組み合わせたLLM APIを効率的に構築できることがわかりました。
今後は、データベースや複数のモデルで構築したエージェントなどとの連携運用も試してみたいと考えています。AWS SAMは、生成AIの活用を進めたいエンジニアや企業にとって優れた選択肢となるでしょう。ぜひ実践して、次世代のAI活用に向けた一歩を踏み出してください。
この記事を書いた人
Advent Calendar!
Advent Calendar 2024開催中!