0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

API Gateway+LambdaでBedrock を使った生成AIのAPIを実装する

Last updated at Posted at 2025-01-02

記事の概要

Bedrock APIを実行するPythonコードをLambdaにデプロイし、API Gateway 経由で実行できるようにするまでの手順を紹介します。

  • 前提として、実行するLLMがBedrockの「モデルアクセス」でアクセス付与されていることを確認してください。

image (22).png

ソースコードの用意

まずは実行するソースコードを用意します。今回は公式ドキュメントに記載されているサンプルコードをLambdaで動かしてみたいと思います。

LambdaでPythonコードを実行する場合、Lambda関数ハンドラーを定義する必要があるため、今回はサンプルコードの処理をそのままハンドラー関数として定義しました。lambda_handler() が関数のデフォルト名です。

実際の開発ではLambdaハンドラーとコアロジックは分離することが推奨されます
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/python-handler.html#python-best-practices

その他、実行環境に合わせてリージョン、モデルID、プロンプトをサンプルコードから変更しています。実際のコードは記事の最後に記載しています。

model-idの確認方法

指定するLLMのmodel-idの確認方法ですが、現状はAWS CLIで確認するしかないと思われます。他の方法(ドキュメント等で確認)をご存じの方はぜひ教えてくださいmm

今回はCloudShellを起動して、以下のCLIを実行して確認しました。

aws bedrock list-foundation-models | \
jq -r '.modelSummaries[] | select(.inferenceTypesSupported[] == "ON_DEMAND") | [
.modelName,
.providerName,
.modelId
] | @tsv' | column -t -s $'\t'

テーブル形式で出力するように指定しているので、以下のように結果が表示されます。 今回はclaude3.5 sonnetを指定しました。

図1-1.PNG

Lambdaの作成

AWSマネジメントコンソールでLambda関数を作成します。

  • Lambdaの画面で「関数を作成」ボタンをクリック
  • 関数名は任意の名前、ランタイムは「Python 3.13」を指定、その他の設定はデフォルトのまま

image (4).png

これでLambda関数が作成されました。BedrockのLLMを実行するために、デフォルトの設定からIAMロールとタイムアウトの設定を変更します。

IAMロールの変更

今回デプロイするコードでは、Bedrock APIを実行するため、LambdaがBedrockへアクセスできるようにIAMロールをデフォルトの設定から変更します。

  • 作成されたLambda関数の画面の下部で「設定」タブ>「アクセス権限」をクリックし、ロール名のリンクをクリック

image (6).png

  • IAMロールの画面に遷移するので、「許可」のタブで「許可を追加」> 「ポリシーをアタッチ」をクリック

image (7).png

  • アタッチするポリシーとして「AmazonBedrockfullAccess」という名前のマネージドポリシーを選択

image (8).png

実際の開発では最低限のアクセス許可のみを追加することが推奨されます

タイムアウトの変更

デフォルトのままだとタイムアウトが3秒ですが、LLMからの回答にはもう少し時間がかかるのでタイムアウトを変更します。

  • 作成されたLambda関数の画面の下部で「設定」タブ>「一般設定」をクリックし、「編集」ボタンをクリックして、タイムアウトを1分に変更

image (5).png

デプロイとテスト

ここまでで、Lambdaの設定が完了したので、Lambdaをデプロイしてみます。

  • 「コード」タブの左側の「Deploy」ボタンをクリック

image (9).png

デプロイしたLambda関数の動作確認としてテストを行います。

  • 「テスト」タブでイベント名に任意の名前を入力し、「テスト」ボタンをクリック

image (10).png

成功すると、以下のように結果が表示されます。

image (11).png

API Gatewayの作成

ここまででLambda関数はデプロイされましたが、インターネット経由でAPIとして実行できる形にはなっていないため、API GatewayでAPIとして公開できるように設定していきます。

  • API Gatewayの画面で「HTTP API」>「構築」ボタンをクリック
  • 統合で「Lambda」を選択し、作成したLambda関数を指定する。API名は任意の値を指定

image (12).png

  • デプロイしているコードはレスポンスの取得のみなので、メソッドは「GET」を指定(ANYでも可)

image (13).png

  • その他はデフォルトのまま「作成」ボタンをクリック

これでAPI gatewayの設定は完了です。

APIの実行

実際にAPIとして実行してみたいと思います。実行する際のURLは、以下のように確認します。

  • 作成したAPIの画面で、左側のセクションで「Deploy」> 「Stage」をクリックし、ステージで $default を選択

image (14).png

「URLを呼び出す」の下にURLが記載されています。確認したURLに/<設定したAPI名>を付与して、ブラウザなどで実行します。CloudShellなどの環境から curl コマンドを実行してもOKです。

実行すると以下のようにレスポンスが表示されます。

image (15).png

LLMへの問い合わせをリクエストボディに含める

ここまでで、BedrockのLLMからの回答を取得するAPIを実装しました。実装したソースコードには、あらかじめLLMへの問い合わせ内容が定義されていますが、実際にはAPIを実行する際に指定させる形式が多いです。

ここからは、API実行時のリクエストボディにLLMへの問い合わせ内容を含めて、その問い合わせ内容へのLLMの回答を取得するAPIに変更していきます。

Pythonコードの変更

Lambdaのソースコードは以下のように、変数 prompt 部分を変更します。

ソースコード変更箇所
-    prompt = "Bedrockは大阪リージョンで利用可能ですか"
+    prompt = event['body']

変更前は事前にテキストで問い合わせを定義していましたが、変更後は event['body'] でAPI実行時のリクエストボディから、問い合わせ内容を取得します。
ソースコード全量は記事の最後に掲載しています。(変更箇所は上記のdiffのみです)

Lambdaテストイベントの作成

Lambdaのソースコードをデプロイしたら、API Gatewayの設定変更に移ってもよいですが、一度テストで動作確認したいと思います。

リクエストボディの値を受け取るようなソースコードに変更したので、それに合わせてLambdaのテストの内容も変更します。

  • 「テスト」タブで「新しいイベントを作成」を選択し、イベント名は任意の名前を設定し、テンプレートに「apigateway-http-api-proxy」を選択

image (16).png

  • イベントJSONに以下を入力
{
  "version": "2.0",
  "routeKey": "$default",
  "rawPath": "/path/to/resource",
  "rawQueryString": "parameter1=value1&parameter1=value2&parameter2=value",
  "cookies": [
    "cookie1",
    "cookie2"
  ],
  "headers": {
    "Header1": "value1",
    "Header2": "value1,value2"
  },
  "queryStringParameters": {
    "parameter1": "value1,value2",
    "parameter2": "value"
  },
  "requestContext": {
    "accountId": "123456789012",
    "apiId": "api-id",
    "authentication": {
      "clientCert": {
        "clientCertPem": "CERT_CONTENT",
        "subjectDN": "www.example.com",
        "issuerDN": "Example issuer",
        "serialNumber": "a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1",
        "validity": {
          "notBefore": "May 28 12:30:02 2019 GMT",
          "notAfter": "Aug  5 09:36:04 2021 GMT"
        }
      }
    },
    "authorizer": {
      "jwt": {
        "claims": {
          "claim1": "value1",
          "claim2": "value2"
        },
        "scopes": [
          "scope1",
          "scope2"
        ]
      }
    },
    "domainName": "id.execute-api.us-east-1.amazonaws.com",
    "domainPrefix": "id",
    "http": {
      "method": "POST",
      "path": "/path/to/resource",
      "protocol": "HTTP/1.1",
      "sourceIp": "192.168.0.1/32",
      "userAgent": "agent"
    },
    "requestId": "id",
    "routeKey": "$default",
    "stage": "$default",
    "time": "12/Mar/2020:19:03:58 +0000",
    "timeEpoch": 1583348638390
  },
  "body": "bedrockは東京リージョンで利用可能ですか",
  "pathParameters": {
    "parameter1": "value1"
  },
  "isBase64Encoded": true,
  "stageVariables": {
    "stageVariable1": "value1",
    "stageVariable2": "value2"
  }
}

これでテストを実行すると、イベントJSONの "body" の値に対する回答が表示されます。

image (17).png

イベントJSONでは、Lambdaを実行した際にLambda関数へ渡されるイベントを定義しています。API Gateway+Lambdaの構成の場合、Lambdaにはevent.body の形式(Pythonコードでは event['body'] で記載)でリクエストボディが渡されるため、"body" の値として "bedrockは東京リージョンで利用可能ですか" を定義しています。

API Gatewayの変更

ソースコードのデプロイ・テストができたので、API Gatewayの設定を変更します。

最初はメソッドでGETを指定していましたが、リクエストボディを受け取るためPOSTに変更します。※はじめに「ANY」メソッドを指定している場合は変更は不要です

  • 作成したAPIの画面で、「Develop」>「Routes」>「GET」メソッドを
    クリックし、「編集」ボタンをクリック

image (19).png

  • メソッドを「POST」に変更し、「保存」ボタンをクリック

image (20).png

APIの実行

APIを実行してみます。今回はリクエストボディを指定する必要があるため、CloudShellを起動して curl コマンドを実行しました。

curl -X POST -H "Content-Type: application/json" -d '{"<問い合わせ内容>"}' <APIのURL>/<API名>

「Bedrockは東京リージョンで利用可能ですか」という問い合わせをしたところ、以下の回答がえられました。

image (21).png

Pythonコード

  • はじめにデプロイしたPythonコードは以下です。
import boto3
import json
from botocore.exceptions import ClientError

def lambda_handler(event, context):
    # Create a Bedrock Runtime client in the AWS Region of your choice.
    client = boto3.client("bedrock-runtime", region_name="ap-northeast-1")
    # Set the model ID
    model_id = "anthropic.claude-3-5-sonnet-20240620-v1:0"
    # Define the prompt for the model.
    prompt = "Bedrockは大阪リージョンで利用可能ですか"
    # Format the request payload using the model's native structure.
    native_request = {
        "anthropic_version": "bedrock-2023-05-31",
        "max_tokens": 512,
        "temperature": 0.5,
        "messages": [
            {
                "role": "user",
                "content": [{"type": "text", "text": prompt}],
            }
        ],
    }
    # Convert the native request to JSON.
    request = json.dumps(native_request)
    try:
        # Invoke the model with the request.
        response = client.invoke_model(modelId=model_id, body=request)
    except (ClientError, Exception) as e:
        print(f"ERROR: Can't invoke '{model_id}'. Reason: {e}")
        exit(1)
    # Decode the response body.
    model_response = json.loads(response["body"].read())
    # Extract and print the response text.
    response_text = model_response["content"][0]["text"]
    print(response_text)
    
    return response_text
  • 変更後のPythonコードは以下です
import boto3
import json
from botocore.exceptions import ClientError

def lambda_handler(event, context):
    # Create a Bedrock Runtime client in the AWS Region of your choice.
    client = boto3.client("bedrock-runtime", region_name="ap-northeast-1")
    # Set the model ID
    model_id = "anthropic.claude-3-5-sonnet-20240620-v1:0"
    # Define the prompt for the model.
    prompt = event['body']
    # Format the request payload using the model's native structure.
    native_request = {
        "anthropic_version": "bedrock-2023-05-31",
        "max_tokens": 512,
        "temperature": 0.5,
        "messages": [
            {
                "role": "user",
                "content": [{"type": "text", "text": prompt}],
            }
        ],
    }
    # Convert the native request to JSON.
    request = json.dumps(native_request)
    try:
        # Invoke the model with the request.
        response = client.invoke_model(modelId=model_id, body=request)
    except (ClientError, Exception) as e:
        print(f"ERROR: Can't invoke '{model_id}'. Reason: {e}")
        exit(1)
    # Decode the response body.
    model_response = json.loads(response["body"].read())
    # Extract and print the response text.
    response_text = model_response["content"][0]["text"]
    print(response_text)
    
    return response_text

この記事はここまでとなります。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?