💪

[初心者]AWS Bedrock + Lambda + API GatewayでAPIでAIを触れるようにする

に公開

概要

APIURLとプロンプトでリクエストを投げるとAIが返答してくれるようなAPIを作る

プロンプトの投げ方
{
"body": "{"prompt":"TypeScriptの特徴を3つ教えて"}"
}

ステップ

  • Lambdaを作成する(1)
    • Lambda関数からBedrockのamazon.nova-2-multimodal-embeddings-v1:0モデルを呼び出す
  • Lambdaにポリシーをアタッチする(1と同時並行)
    • Lambda(Role) Attach > AI触れる権限(bedrock:InvokeModel), CloudWatchの権限
  • API GatewayでAPIを作成する
  • メソッドを作り、リクエストを投げれるようにする

早速やる

Lambda関数の作成

関数名: test-bedrock-AI
ランタイム: Node.js 22.x
アーキテクチャ: x86_64
(まだ作成ボタンは押さない)

IAMポリシーの作成

    {
        "Effect": "Allow",
        "Action": [
            "bedrock:InvokeModel",
            "bedrock:InvokeModelWithResponseStream"
        ],
        "Resource": [
            "arn:aws:bedrock:*::foundation-model/*",
            "arn:aws:bedrock:*:*:inference-profile/*"
        ]
    },
    {
        "Effect": "Allow",
        "Action": [
            "logs:CreateLogGroup",
            "logs:CreateLogStream",
            "logs:PutLogEvents"
        ],
        "Resource": "*"
    }

上記の内容でポリシーを作成する
ポリシー名: test-bedrock-AI-policy

このような許可で進みます

IAMロール作成とLambdaに付与する


基本設定

ポリシーをアタッチ

ロール名: test-bedrock-AI-role

ロールを作成

ロールを付与して関数を作成する
(ロールが検索でヒットしなかった場合リフレッシュする必要がある)

Lambda内Coding!! +a

import { BedrockRuntimeClient, InvokeModelCommand, ConversationRole, ConverseCommand } from "@aws-sdk/client-bedrock-runtime";

const client = new BedrockRuntimeClient({ region: "us-east-1" })
const modelId = "amazon.nova-pro-v1:0";

export const handler = async (event) => {
  const body = JSON.parse(event.body);
  const prompt = body.prompt
  const inputText = body.prompt;
  const message = {
    content: [{ text: inputText }],
    role: ConversationRole.USER
  };
  const request = {
    modelId,
    messages: [message],
    InferenceConfig: {
      maxTokens: 500,
      temperature: 0.5,
    }
  }


  try {
    const response = await client.send(new ConverseCommand(request));
    return {
      statusCode: 200,
      headers: {
        "Content-Type": "application/json"
      },
      body: JSON.stringify(response)
    }
  } catch (err) {
    console.error(`ERROR: Can't invoke '${modelId}'. Reason: ${error.message}`);
    return {
      statusCode: 500,
      headers: {
        "Content-Type": "application/json"
      },
      body: JSON.stringify(err)
    }
  }
};

デプロイする
テストを実行

エラーが発生!!

タイムアウトが起こっているので設定を変更する
設定 > 一般設定 > 編集

タイムアウトを30秒にする

そうすると...

できた!!

API Gatewayで公開!


REST APIで構築をする。

新しいAPI
API名: test-bedrock-AI-API
APIエンドポイントタイプ: リージョン
IPアドレスのタイプ: IPv4

"/" にメソッドを作成
メソッド: POST
統合タイプ: Lambda関数
Lambda プロキシ統合: True
Lambda関数: test-bedrock-AI(Lambda関数)のarn
メソッドを作成

APIを"production"というステージ名でデプロイ

コピーをしておく

Ubuntuで動作確認

Ubuntuを開く

curl -X POST \
-H "Content-Type: application/json" \
-d '{"body": "{\"prompt\":\"TypeScriptの特徴を3つ教えて\"}"}' \
https://qgc9rjv61e.execute-api.ap-northeast-1.amazonaws.com/production

上記のようにcurlコマンドを実行する
実行

{"message": "Internal server error"}

Internal Server Errorが起きますね。

ポリシーを更新する

"bedrock:ListFoundationModels": "*"
の権限を追加する

コード下部(errとerrorが統一されていなかった)

 } catch (err //ここをerrorに直す) {
    console.error(`ERROR: Can't invoke '${modelId}'. Reason: ${error.message}`);
    return {
      statusCode: 500,
      headers: {
        "Content-Type": "application/json"
      },
      body: JSON.stringify(err(//ここをerrorに直す))
    }
  }
};

Lambda, API Gatewayを再デプロイ

$ curl -X POST \
-H "Content-Type: application/json" \
-d '{"body": "{\"prompt\":\"TypeScriptの特徴を3つ教えて\"}"}' \
https://qgc9rjv61e.execute-api.ap-northeast-1.amazonaws.com/production
{}

何も帰ってこなくなった、、、

現在
LambdaではBedrockから受け取った生のresponseを持っている。
returnする中でJSON.stringifyを使用しているが、複雑すぎて正しく変換できない可能性があるらしい。そのためにAPI Gatewayが期待するシンプルなJSONボディの文字列として認識できずに、{}が返されてしまうらしい。
だから

try {
    const response = await client.send(new ConverseCommand(request));
    const modelOutputText = response.output?.message?.content?.[0]?.text;
    return {
      statusCode: 200,
      headers: {
        "Content-Type": "application/json"
      },
      body: JSON.stringify({
        result: modelOutputText || "応答なし"
      })
    }

このようにして回答部分を持ってきてレスポンスにする
Lambda, API Gatewayを再デプロイ
Body部分を変更する

API Gatewayに{"prompt":"..."}というJSONを直接送った場合、event.body は'{"prompt":"..."}'という文字列になるらしい。
だから{"body":}は消す必要がある


できたーーー!!!

つまったところまとめ

  • AIは即レスは無理 -> タイムアウトを30秒以上に!
  • JSON.stringify()への過信によるレスポンスの誤作動
  • API Gatewayのリクエストボディ処理への理解
    • event.bodyが必ずボディにつく
  • 頭の中での定義された定数名と実際の定数名の齟齬(err/error)

まとめ/振り返り

BedrockのAIにリクエスト投げるの結構難しかった。
エラー解決するのは楽しかった。
API Gatewayの理解も深まってよかった。やはり、定数名などは小さいミスだけど重大なエラーを引き起こしかねないので、これから十分注意していきたいと思った。

参照

Invoke Amazon Nova on Amazon Bedrock using Bedrock's Converse API
https://docs.aws.amazon.com/bedrock/latest/userguide/bedrock-runtime_example_bedrock-runtime_Converse_AmazonNovaText_section.html
Supported foundation models in Amazon Bedrock
https://docs.aws.amazon.com/bedrock/latest/userguide/models-supported.html

Discussion