Powertools for AWS LambdaでBedrock Agents用のLambdaコードがシンプルに書けるようになりました
お疲れさまです。とーちです。
Powertools for AWS LambdaでBedrock Agents との統合を簡単にするためのBedrock Agents Function ユーティリティが提供されたとのことで、どういったものか試してみました。
Powertools for AWS Lambdaって?
まず、今回のアップデートを理解するために、Powertools for AWS Lambdaについて簡単に説明しておきましょう。
Powertools for AWS Lambdaは、AWS Lambdaでアプリを作るときに便利な機能をまとめたツール集です。サーバーレス開発でよく必要になる機能を標準化・簡素化してくれます。
主な機能としては以下のようなものがあります
- 構造化ログ:主要な Lambda 関数要素の詳細を使用して構造化ログを充実させるツール
- メトリクス:CloudWatch のカスタムメトリクスの作成
- トレーシング:Lambda関数ハンドラと同期関数および非同期関数をトレースするためのデコレータ
- パラメータ取得:AWS SSM パラメータストア、AWS Secrets Managerなどからパラメータを取得する関数
また対応言語としては以下のようなものがあります
- Python
- TypeScript (Node.js)
- .NET
- Java
例えばTypeScript用のPowertools for AWS Lambdaのドキュメントは以下です。
Powertools for AWS Lambdaの使い方
肝心の使い方ですが、TypeScript版ですと以下のように2通りの方法があります。
1. NPMパッケージを使ったインストール
npm(Node.jsのパッケージ管理ツール)を使って必要な機能だけを選んでインストールできます。例えば以下のようにコマンドを実行すると、プロジェクトのpackage.jsonに自動で追加され、TypeScriptコード内で使えるようになります。
npm install @aws-lambda-powertools/logger
npm install @aws-lambda-powertools/tracer
npm install @aws-lambda-powertools/metrics
2. Lambdaレイヤー
また事前にライブラリをパッケージ化してAWS上に保管することができるLambdaレイヤーで使用することもできます。
こちらの場合のおおまかな使用方法は以下の通りです
- AWSコンソールでLambda関数の設定画面を開き「レイヤー」セクションで「レイヤーの追加」をクリック
- Powertools用の公式レイヤーARNを指定して追加
レイヤーARNは以下のページに記載があります。
今回のアップデートについて
さて、今回Powertools for AWS Lambdaに「Bedrock Agents Function ユーティリティ」という新機能が追加されました。これは、Bedrock AgentとLambda関数を繋げる作業を簡単にしてくれるツールで、以下の3つの特徴を持っています
- 大規模言語モデル(LLM)エージェント用のツールを簡単に公開
- ツール名と機能の詳細に基づいた自動ルーティング
- 適切なエラー処理とレスポンスのフォーマット
従来の方法との比較
これまでBedrock AgentとLambdaを連携させるには、こんな感じの複雑なコードを書く必要がありました
// 従来:手動でイベント処理とレスポンス構築
export const handler = async (event: any, context: Context) => {
// 手動でイベントから情報を取得
const actionGroup = event.actionGroup;
const function_ = event.function;
const parameters = event.parameters;
// 手動でレスポンス構造を組み立て
return {
messageVersion: '1.0',
response: {
actionGroup: actionGroup,
function: function_,
functionResponse: {
responseBody: {
TEXT: { body: "処理結果" }
}
}
},
sessionAttributes: event.sessionAttributes,
promptSessionAttributes: event.promptSessionAttributes
};
};
新しいユーティリティを使った場合
新しいユーティリティを使うと、こんな感じでシンプルになります
// AWS Lambda Powertoolsから必要なモジュールをインポート
import { BedrockAgentFunctionResolver } from '@aws-lambda-powertools/event-handler/bedrock-agent';
import type { Context } from 'aws-lambda';
// Bedrock Agentとやり取りするためのオブジェクトを作成
const app = new BedrockAgentFunctionResolver();
// Bedrock Agentが呼び出せる「ツール」を登録
app.tool<{ city: string }>(
async ({ city }) => {
// 実際の処理:都市名から空港コードを取得
return {
city,
airportCode: 'XYZ',
};
},
{
// Bedrock Agentがこのツールを呼び出すときの名前
name: 'getAirportCodeForCity',
// このツールが何をするかの説明文
description: 'Get the airport code for a given city',
}
);
// Lambda関数のメイン処理
// Bedrock Agentからリクエストが来たときに最初に実行される
export const handler = async (event: unknown, context: Context) =>
// appが自動でリクエストを処理して結果を返す
app.resolve(event, context);
面倒な入力出力のイベント処理をPowertools for AWS Lambdaが代わりに処理してくれるということですね。
自動ルーティングの仕組み
ツール名の自動ルーティングの部分ですが、以下のような流れで決定されます
1. LLMによる判断
ユーザー: 「東京の天気は?」
↓
Bedrock Agent (LLM): 「天気の質問だから getWeatherForCity を使おう」
2. アクション選択
Bedrock Agentがアクショングループから適切なアクションを選択します
- 利用可能なアクション: getWeatherForCity
- 選択されたアクション: getWeatherForCity
3. Lambda関数呼び出し
Bedrock AgentがLambda関数を呼び出します。その際に、以下のような情報をeventとしてLambda関数に渡します
{
"messageVersion": "1.0",
"function": "getWeatherForCity",
"parameters": [
{
"name": "city",
"type": "string",
"value": "Seattle"
}
],
"inputText": "",
"sessionId": "***",
"agent": {
"name": "weatherAgent",
"version": "DRAFT",
"id": "CNNUNJVP3A",
"alias": "TSTALIASID"
},
"actionGroup": "weatherActionGroup",
"sessionAttributes": {},
"promptSessionAttributes": {}
}
4. Lambda内でのルーティング
Powertools for AWS LambdaのBedrockAgentFunctionResolverは、以下の流れでツール関数をルーティングしています
app.resolve()
を実行した際に、上記のeventを引数として指定- 上記eventの中のfunctionフィールドの値を、
app.tool()
で登録されたツールのnameと照合 - 上記eventの中のparametersの値をツール関数の入力と一致するように適切な型に変換
- 変換したパラメータを引数として一致したツール関数を実行
注意点として、Bedrock Agentのアクショングループ関数名の定義とLambda関数内でapp.tool()
により登録する際のツール名は大文字小文字を含め完全一致が必要でした(実際にサンプルコードを変更することで確認)
やってみた
それでは実際にやってみましょう。今回登場した機能を試せるサンプルコードが以下で公開されています。
事前準備
前提としてAmazon Bedrock の基盤モデルへのアクセスをリクエストしておきましょう。
サンプルコードではamazon.nova-pro-v1:0
を使っていたのでこの記事でもこのモデルを使用していきます。マネジメントコンソールでモデルへのアクセスが有効になっていることを確認します。
またリージョンについてはサンプル通りにus-west-2で実行することにしました。
ちなみに東京リージョンだとcdkのデプロイに失敗しました。どうも以下の部分で正しいinference-profileのARNを生成できてないのが問題のようです
サンプルコードのセットアップ
上記のリポジトリをcloneして、依存関係のインストール等を行います
git clone git@github.com:aws-samples/sample-bedrock-agent-powertools-for-aws.git bd-agt-test
npm ci
CDKでのデプロイ
続いてCDKでAWSリソースをデプロイします。AWS_REGIONの環境変数を使ってデプロイ先のリージョンを指定してください。cdk bootstrap
をまだやってない場合はそれも合わせて実施します
export AWS_REGION=us-west-2
cdk bootstrap aws://***/us-west-2
npm run cdk deploy -- --context modelId=amazon.nova-pro-v1:0 --context region=us-west-2
作成されるリソース
CDKでは以下のようなリソースが作成されています
- AWS::Logs::LogGroup: Lambda関数用のCloudWatchロググループ
- AWS::IAM::Role: Lambda実行用のIAMロール
- AWS::IAM::Policy: Lambda用のgeo-places権限ポリシー
- AWS::Lambda::Function: 天気情報取得用のLambda関数、ここでPowertools for AWS Lambdaが使用されている
- AWS::Lambda::Permission: BedrockからLambda呼び出し用の権限
- AWS::IAM::Role: Bedrock Agent実行用のIAMロール
- AWS::Bedrock::Agent: 天気予報エージェント
また、Bedrockエージェントの設定ですが、以下のようになっています。
アクショングループ関数は以下の設定です。ここの名前とパラメータ名についてはLambda関数内で登録するtool名及びパラメータ名と完全一致する必要があります。
Powertools for AWS Lambdaのパッケージ構成
今回のサンプルでは、Powertools for AWS Lambdaはnpmによりインストールされているので、package-lock.json
にパッケージが含まれています。以下の3つがPowertools for AWS Lambdaのパッケージです。この中のevent-handlerが今回のアップデート対象になります
- @aws-lambda-powertools/commons (v2.21.0): 共通ユーティリティ
- @aws-lambda-powertools/event-handler (v2.21.0): イベントハンドリング用
- @aws-lambda-powertools/logger (v2.21.0): 構造化ログ出力用
実際のコードでの使用箇所
実際のコードでの使用箇所を見ていきましょう。
1. Bedrock Agent Event Handler (src/weather.ts)の追加
import { BedrockAgentFunctionResolver } from '@aws-lambda-powertools/event-handler/bedrock-agent';```typescript
import { BedrockAgentFunctionResolver } from '@aws-lambda-powertools/event-handler/bedrock-agent';
import type { BedrockAgentFunctionEvent } from '@aws-lambda-powertools/event-handler/types';
import { getPlaceInfo, getWeatherForCoordinates } from './utils.js';
const app = new BedrockAgentFunctionResolver({ logger });
ここでは、Powertools Event HandlerからBedrockAgentFunctionResolverクラスをインポートしています。このリゾルバにtoolとして各関数を登録することで、リゾルバ側でLambda関数にきたリクエストを自動解析し適切なtool(関数)にルーティングします。
また、import type { BedrockAgentFunctionEvent }
ではBedrock Agentから送られてくるイベントの型定義をインポートしています。
new BedrockAgentFunctionResolver({ logger });
で、BedrockAgentFunctionResolverの新しいインスタンスを作成しています。
2. ツール関数の定義
app.tool<{ city: string }>(
async ({ city }) => {
logger.appendKeys({ city, tool: 'getWeatherForCity' });
logger.info('getWeatherForCity called');
try {
const { latitude, longitude, fullName } = await getPlaceInfo(city);
const weatherData = await getWeatherForCoordinates({
latitude,
longitude,
});
logger.info('Weather data retrieved successfully');
return { fullName, weatherData };
} catch (error) {
logger.error('error retrieving weather', { error });
return 'Sorry, I could not find the weather for that city.';
} finally {
logger.removeKeys(['city', 'tool']);
}
},
{
name: 'getWeatherForCity',
description: 'Get weather for a specific city',
}
);
先ほどインスタンス化したapp
でapp.tool()
関数を実行することにより()
内の関数をtoolとして追加しています。
大まかに構造を書くと以下のような形になります。(正直なところ私もジェネリクス(<{ プロパティ名: 型 }>
のこと)の辺りの理解は曖昧ですが、構造としてはこうなるということで)app.tool()
関数の引数としては第一引数に具体的な処理(関数)、第二引数にツールの名前や説明が来るということですね。
app.tool<{ プロパティ名: 型 }>(
// ↓ この型定義が下の引数の型を決める
async ({ プロパティ名 }) => { /* 処理 */ },
{ name: 'ツール名', description: '説明' }
);
それで肝心の処理の部分はというと、getPlaceInfo関数にcity(都市名)を渡すことでlatitude, longitude, fullName(緯度、経度、フルネーム)を受取り、緯度経度をgetWeatherForCoordinates関数に渡すことで、気象データを返すのでそれとフルネームを関数の返り値として返しているというものになります。
要するに都市名を渡すとその都市の気象情報を返す関数を定義し、それを、getWeatherForCityという名前でLLMからツールとして使えるように登録しているということです。
3. メインハンドラー
export const handler = async (event: unknown, context: Context) => {
// ↓ Powertools for AWS Lambda を使ったログ出力処理
logger.logEventIfEnabled(event);
// ↓ Powertools for AWS Lambda を使ったログのトレースの有効化
logger.setCorrelationId((event as BedrockAgentFunctionEvent).sessionId);
logger.appendKeys({ requestId: context.awsRequestId });
// ↓ 実際のビジネスロジック
return app.resolve(event as BedrockAgentFunctionEvent, context);
};
loggerの部分はPowertools for AWS Lambdaを使ったログ出力処理やログのトレースの有効化をしているものになります。
app.resolve()
はBedrock Agentからのリクエストを受け取って、適切なツール関数を呼び出し、結果を返す関数です。引数としては、LambdaがBedrock Agentから受け取ったeventとcontextをそのまま渡す形となっています。
実際の動作確認
デプロイが完了したら、実際にBedrock Agentを使って天気情報を取得してみましょう。
AWSコンソールのBedrock Agentページから作成されたエージェントを確認し、テスト機能を使って以下のようなメッセージを送信してみると、ちゃんと天気情報が回答されていました。
Lambdaのログを見ると以下のように構造化され分かりやすいログが出力されていますね。
まとめ
というわけで、Powertools for AWS Lambdaの新機能「Bedrock Agents Function ユーティリティ」について紹介しました。
正直私はこれまで、Bedrock Agentを触ってなかったのですが、そんな私からみてもだいぶ簡単にコードが書けるようになったんじゃないかと感じました。また今回は紹介していませんが、エラーハンドリングやログ周りも楽に実装できるようになっているので、Bedrock Agentの開発をしている方はぜひチェックしていただければと思います。
以上、とーちでした。