はじめに
皆さん、こんにちは。
私は業務でデータ利活用基盤を取り扱っていること、2024 AWS Japan Top Engineer に選出されたということから、AWS GenU およびそれに必要なデータ基盤の探求 (Snowflake, dbt, Iceberg, etc) に取り組む必要があると考えています。
本投稿では、GenU のバックエンドである CDK コードを詳細に解説します。
自身そして閲覧して頂いた皆様の GenU への理解が少しでも深まり、生成 AI の民主化につながっていければと考えています。
前回までのおさらい
前回までで、以下が完了しました。
-
⑫GenerativeAiUseCasesStack > RagKnowledgeBase, UseCaseBuilder, Transcribe スタックの解説
GenU の CDK は最大で以下の 6 つの子スタックを作成します。 -
CloudFrontWafStack
-
RagKnowledgeBaseStack
-
AgentStack
-
GuardrailStack
-
GenerativeAiUseCasesStack
※メインスタック -
DashboardStack
今回は GenU 内の DashboardStack
スタックを解説していきたいと思います。
DashboardStack スタック
DashboardStack は CloudWatch ダッシュボードのスタックです。
アーキテクチャ図でいうと、以下の赤枠の部分にあたります。
以下のソースコードが DashboardStack の定義です。
const dashboardStack = params.dashboard
? new DashboardStack(
app,
`GenerativeAiUseCasesDashboardStack${params.env}`,
{
env: {
account: params.account,
region: params.modelRegion,
},
params: params,
userPool: generativeAiUseCasesStack.userPool,
userPoolClient: generativeAiUseCasesStack.userPoolClient,
appRegion: params.region,
crossRegionReferences: true,
}
)
: null;
DashboardStack リソースの実体は packages/cdk/lib/dashboard-stack.ts
にあります。
スタック作成時のパラメータ dashboard
(デフォルト値は false) が true の場合、以下のコードを実行し DashboardStack を作成します。
import { Stack, StackProps, CfnOutput, Duration } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as cw from 'aws-cdk-lib/aws-cloudwatch';
import * as logs from 'aws-cdk-lib/aws-logs';
import * as cognito from 'aws-cdk-lib/aws-cognito';
import { StackInput } from './stack-input';
export interface DashboardStackProps extends StackProps {
params: StackInput;
userPool: cognito.UserPool;
userPoolClient: cognito.UserPoolClient;
appRegion: string;
}
export class DashboardStack extends Stack {
public readonly logGroup: logs.LogGroup;
public readonly dashboard: cw.Dashboard;
constructor(scope: Construct, id: string, props: DashboardStackProps) {
super(scope, id, props);
const params = props.params;
// Bedrock のログの出力先として設定する LogGroup
const logGroup = new logs.LogGroup(this, 'LogGroup', {
// 1 年でリテンションする設定
retention: logs.RetentionDays.ONE_YEAR,
});
const inputTokenCounts = params.modelIds.map((modelId: string) => {
return new cw.Metric({
namespace: 'AWS/Bedrock',
metricName: 'InputTokenCount',
dimensionsMap: {
ModelId: modelId,
},
period: Duration.days(1),
statistic: 'Sum',
});
});
const outputTokenCounts = params.modelIds.map((modelId: string) => {
return new cw.Metric({
namespace: 'AWS/Bedrock',
metricName: 'OutputTokenCount',
dimensionsMap: {
ModelId: modelId,
},
period: Duration.days(1),
statistic: 'Sum',
});
});
const invocations = [
...params.modelIds,
...params.imageGenerationModelIds,
].map((modelId: string) => {
return new cw.Metric({
namespace: 'AWS/Bedrock',
metricName: 'Invocations',
dimensionsMap: {
ModelId: modelId,
},
period: Duration.days(1),
statistic: 'Sum',
});
});
const userPoolMetrics = [
'SignInSuccesses',
'TokenRefreshSuccesses',
'SignUpSuccesses',
].map((metricName: string) => {
return new cw.Metric({
namespace: 'AWS/Cognito',
metricName,
dimensionsMap: {
UserPool: props.userPool.userPoolId,
UserPoolClient: props.userPoolClient.userPoolClientId,
},
period: Duration.hours(1),
statistic: 'Sum',
region: props.appRegion,
});
});
const dashboard = new cw.Dashboard(this, 'Dashboard', {
defaultInterval: Duration.days(7),
});
dashboard.addWidgets(
new cw.TextWidget({
markdown: '**Amazon Bedrock Metrics**',
width: 18,
height: 1,
}),
new cw.TextWidget({
markdown: '**User Metrics**',
width: 6,
height: 1,
})
);
dashboard.addWidgets(
new cw.GraphWidget({
title: 'InputTokenCount (Daily)',
width: 6,
height: 6,
left: inputTokenCounts,
}),
new cw.GraphWidget({
title: 'OutputTokenCount (Daily)',
width: 6,
height: 6,
left: outputTokenCounts,
}),
new cw.GraphWidget({
title: 'Invocations (Daily)',
width: 6,
height: 6,
left: invocations,
}),
new cw.GraphWidget({
title: 'UserPool',
width: 6,
height: 6,
left: userPoolMetrics,
})
);
dashboard.addWidgets(
new cw.TextWidget({
markdown: '**Prompt Logs**',
width: 24,
height: 1,
})
);
// ログの出力から抜き出す
dashboard.addWidgets(
new cw.LogQueryWidget({
title: 'Prompt Logs',
width: 24,
height: 6,
logGroupNames: [logGroup.logGroupName],
view: cw.LogQueryVisualizationType.TABLE,
queryLines: [
"filter @logStream = 'aws/bedrock/modelinvocations'",
"filter schemaType like 'ModelInvocationLog'",
'filter concat(input.inputBodyJson.prompt, input.inputBodyJson.messages.0.content.0.text) not like /.*<conversation>.*/',
'sort @timestamp desc',
'fields @timestamp, concat(input.inputBodyJson.prompt, input.inputBodyJson.messages.0.content.0.text) as input, modelId',
],
})
);
this.logGroup = logGroup;
this.dashboard = dashboard;
new CfnOutput(this, 'BedrockLogGroup', {
value: this.logGroup.logGroupName,
});
new CfnOutput(this, 'DashboardName', {
value: this.dashboard.dashboardName,
});
new CfnOutput(this, 'DashboardUrl', {
value: `https://console.aws.amazon.com/cloudwatch/home#dashboards/dashboard/${this.dashboard.dashboardName}`,
});
}
}
この中では、CloudWatch ダッシュボードおよびダッシュボードに表示するロググループ、メトリクスを生成しています。
DashboardStack > LogGroup リソース
LogGroup は CloudWatch ロググループ です。
以下のソースコードが LogGroup の定義です。
// Bedrock のログの出力先として設定する LogGroup
const logGroup = new logs.LogGroup(this, 'LogGroup', {
// 1 年でリテンションする設定
retention: logs.RetentionDays.ONE_YEAR,
});
ここでは、以下の CloudWatch ロググループ を生成しています。
-
CloudWatch ロググループ
- ログの保持期間: 1 年間 (365 日)
DashboardStack > Metric リソース
Metric は CloudWatch メトリクス です。
以下のソースコードが Metric の定義です。
const inputTokenCounts = params.modelIds.map((modelId: string) => {
return new cw.Metric({
namespace: 'AWS/Bedrock',
metricName: 'InputTokenCount',
dimensionsMap: {
ModelId: modelId,
},
period: Duration.days(1),
statistic: 'Sum',
});
});
const outputTokenCounts = params.modelIds.map((modelId: string) => {
return new cw.Metric({
namespace: 'AWS/Bedrock',
metricName: 'OutputTokenCount',
dimensionsMap: {
ModelId: modelId,
},
period: Duration.days(1),
statistic: 'Sum',
});
});
const invocations = [
...params.modelIds,
...params.imageGenerationModelIds,
].map((modelId: string) => {
return new cw.Metric({
namespace: 'AWS/Bedrock',
metricName: 'Invocations',
dimensionsMap: {
ModelId: modelId,
},
period: Duration.days(1),
statistic: 'Sum',
});
});
const userPoolMetrics = [
'SignInSuccesses',
'TokenRefreshSuccesses',
'SignUpSuccesses',
].map((metricName: string) => {
return new cw.Metric({
namespace: 'AWS/Cognito',
metricName,
dimensionsMap: {
UserPool: props.userPool.userPoolId,
UserPoolClient: props.userPoolClient.userPoolClientId,
},
period: Duration.hours(1),
statistic: 'Sum',
region: props.appRegion,
});
});
ここでは、以下の 6 つのメトリクスを生成しています。
-
InputTokenCount
メトリクス- スタック作成時のパラメータ
modelIds
(デフォルト値は以下) の数だけ、メトリクスを作成us.anthropic.claude-3-5-sonnet-20241022-v2:0
us.anthropic.claude-3-5-haiku-20241022-v1:0
us.amazon.nova-pro-v1:0
us.amazon.nova-lite-v1:0
us.amazon.nova-micro-v1:0
- 名前空間:
'AWS/Bedrock'
- メトリクス: 入力テキストのトークン数
- モデル ID: 対象のモデルのモデル ID
- 期間: 1 日
- 統計: 合計値
- スタック作成時のパラメータ
-
OutputTokenCount
メトリクス- スタック作成時のパラメータ
modelIds
の数だけ、メトリクスを作成 - 名前空間:
'AWS/Bedrock'
- メトリクス: 出力テキストのトークン数
- モデル ID: 対象のモデルのモデル ID
- 期間: 1 日
- 統計: 合計値
- スタック作成時のパラメータ
-
Invocations
メトリクス- スタック作成時のパラメータ
modelIds
の数だけ、メトリクスを作成 - 名前空間:
'AWS/Bedrock'
- メトリクス: InvokeModel もしくは InvokeModelWithResponseStream の API 操作によるリクエストの数
- モデル ID: 対象のモデルのモデル ID
- 期間: 1 日
- 統計: 合計値
- スタック作成時のパラメータ
-
SignInSuccesses
メトリクス -
TokenRefreshSuccesses
メトリクス -
SignUpSuccesses
メトリクス
DashboardStack > Dashboard リソース
Dashboard は CloudWatch ダッシュボード です。
以下のソースコードが Dashboard の定義です。
const dashboard = new cw.Dashboard(this, 'Dashboard', {
defaultInterval: Duration.days(7),
});
dashboard.addWidgets(
new cw.TextWidget({
markdown: '**Amazon Bedrock Metrics**',
width: 18,
height: 1,
}),
new cw.TextWidget({
markdown: '**User Metrics**',
width: 6,
height: 1,
})
);
dashboard.addWidgets(
new cw.GraphWidget({
title: 'InputTokenCount (Daily)',
width: 6,
height: 6,
left: inputTokenCounts,
}),
new cw.GraphWidget({
title: 'OutputTokenCount (Daily)',
width: 6,
height: 6,
left: outputTokenCounts,
}),
new cw.GraphWidget({
title: 'Invocations (Daily)',
width: 6,
height: 6,
left: invocations,
}),
new cw.GraphWidget({
title: 'UserPool',
width: 6,
height: 6,
left: userPoolMetrics,
})
);
dashboard.addWidgets(
new cw.TextWidget({
markdown: '**Prompt Logs**',
width: 24,
height: 1,
})
);
// ログの出力から抜き出す
dashboard.addWidgets(
new cw.LogQueryWidget({
title: 'Prompt Logs',
width: 24,
height: 6,
logGroupNames: [logGroup.logGroupName],
view: cw.LogQueryVisualizationType.TABLE,
queryLines: [
"filter @logStream = 'aws/bedrock/modelinvocations'",
"filter schemaType like 'ModelInvocationLog'",
'filter concat(input.inputBodyJson.prompt, input.inputBodyJson.messages.0.content.0.text) not like /.*<conversation>.*/',
'sort @timestamp desc',
'fields @timestamp, concat(input.inputBodyJson.prompt, input.inputBodyJson.messages.0.content.0.text) as input, modelId',
],
})
);
ここでは、以下の CloudWatch ダッシュボードおよびウィジェットを生成しています。
-
CloudWatch ダッシュボード
- メトリクスの表示期間: 過去 7 日間
- 上から以下のウィジェットを追加
-
テキストウィジェット
- 幅 3 で
'Amazon Bedrock Metrics'
- 幅 1 で
'User Metrics'
- 幅 3 で
-
グラフウィジェット
- 幅 1 で
InputTokenCount
メトリクス - 幅 1 で
OutputTokenCount
メトリクス - 幅 1 で
Invocations
メトリクス - 幅 1 で
UserPool
メトリクス (SignInSuccesses
メトリクス,TokenRefreshSuccesses
メトリクス,SignUpSuccesses
メトリクスを 1 つのグラフに表示)
- 幅 1 で
-
テキストウィジェット
- 幅 4 で
'Prompt Logs'
- 幅 4 で
-
ログクエリウィジェット
- 幅 4
- タイトル:
'Prompt Logs'
- ロググループ名: DashboardStack > LogGroup リソースで生成した CloudWatch ロググループ
- ビュー: テーブルビュー
- クエリ:
filter @logStream = 'aws/bedrock/modelinvocations'
filter schemaType like 'ModelInvocationLog'
filter concat(input.inputBodyJson.prompt, input.inputBodyJson.messages.0.content.0.text) not like /.*<conversation>.*/
sort @timestamp desc
fields @timestamp, concat(input.inputBodyJson.prompt, input.inputBodyJson.messages.0.content.0.text) as input, modelId
- クエリ解説
- Bedrock モデルの推論ログを最新のものから表示する
-
<conversation>
を含むログは表示しない - 推論時間、インプット内容、モデル ID を表示する
-
これにて、GenU 内の全てのスタックの解説が終わりました。
次回はおまけとしてスタックからエクスポート (CfnOutput) される値を解説したいと思います。
(参考) GenU のバックエンド (CDK) 詳細解説投稿一覧
- ①AWS CDK のセットアップ
- ②AWS CDK の動作確認
- ③GenU の概要
- ④GenU CDK スタックの概要
- ⑤CloudFrontWafStack スタックの解説
- ⑥RagKnowledgeBaseStack スタックの解説
- ⑦WebSearchAgentStack スタックの解説
- ⑧GuardrailStack スタックの解説
- ⑨GenerativeAiUseCasesStack > Auth スタックの解説
- ⑩GenerativeAiUseCasesStack > Database, Api スタックの解説
- ⑪GenerativeAiUseCasesStack > CommonWebAcl, Web, Rag スタックの解説
- ⑫GenerativeAiUseCasesStack > RagKnowledgeBase, UseCaseBuilder, Transcribe スタックの解説
- ⑬DashBoard スタックの解説
- ⑭GenU の Outputs の解説