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?

GenU のバックエンド (CDK) 詳細解説 ⑬DashBoard スタックの確認

Last updated at Posted at 2025-03-29

はじめに

皆さん、こんにちは。

私は業務でデータ利活用基盤を取り扱っていること、2024 AWS Japan Top Engineer に選出されたということから、AWS GenU およびそれに必要なデータ基盤の探求 (Snowflake, dbt, Iceberg, etc) に取り組む必要があると考えています。

本投稿では、GenU のバックエンドである CDK コードを詳細に解説します。
自身そして閲覧して頂いた皆様の GenU への理解が少しでも深まり、生成 AI の民主化につながっていければと考えています。

前回までのおさらい

前回までで、以下が完了しました。

今回は GenU 内の DashboardStack スタックを解説していきたいと思います。

DashboardStack スタック

DashboardStack は CloudWatch ダッシュボードのスタックです。
アーキテクチャ図でいうと、以下の赤枠の部分にあたります。

image.png

以下のソースコードが DashboardStack の定義です。

packages/cdk/lib/create-stacks.ts (抜粋)
  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 を作成します。

packages/cdk/lib/dashboard-stack.ts
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 の定義です。

packages/cdk/lib/dashboard-stack.ts (抜粋)
    // Bedrock のログの出力先として設定する LogGroup
    const logGroup = new logs.LogGroup(this, 'LogGroup', {
      // 1 年でリテンションする設定
      retention: logs.RetentionDays.ONE_YEAR,
    });

ここでは、以下の CloudWatch ロググループ を生成しています。

DashboardStack > Metric リソース

Metric は CloudWatch メトリクス です。
以下のソースコードが Metric の定義です。

packages/cdk/lib/dashboard-stack.ts (抜粋)
    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 メトリクス
    • 名前空間: 'AWS/Cognito'
    • メトリクス: Cognito ユーザープールに対して正常に行われたユーザー認証リクエストの合計数
    • ユーザプール ID: Auth リソースの Cognito ユーザプール ID
    • ユーザプールクライアント: Auth リソースの Cognito アプリケーションクライアント ID
    • 期間: 1 時間
    • 統計: 合計値
    • リージョン: スタック作成時のパラメータ region (デフォルト値は以下のいずれか)
      • 環境変数の CDK_DEFAULT_REGION
      • 固定値 'us-east-1'
  • TokenRefreshSuccesses メトリクス
    • 名前空間: 'AWS/Cognito'
    • メトリクス: Cognito ユーザープールに対する更新リクエストのうち、正常に行われたものの合計数
    • ユーザプール ID: Auth リソースの Cognito ユーザプール ID
    • ユーザプールクライアント: Auth リソースの Cognito アプリケーションクライアント ID
    • 期間: 1 時間
    • 統計: 合計値
    • リージョン: スタック作成時のパラメータ region (デフォルト値は以下のいずれか)
      • 環境変数の CDK_DEFAULT_REGION
      • 固定値 'us-east-1'
  • SignUpSuccesses メトリクス
    • 名前空間: 'AWS/Cognito'
    • メトリクス: Cognito ユーザープールに対して正常に行われたユーザー登録リクエストの合計数
    • ユーザプール ID: Auth リソースの Cognito ユーザプール ID
    • ユーザプールクライアント: Auth リソースの Cognito アプリケーションクライアント ID
    • 期間: 1 時間
    • 統計: 合計値
    • リージョン: スタック作成時のパラメータ region (デフォルト値は以下のいずれか)
      • 環境変数の CDK_DEFAULT_REGION
      • 固定値 'us-east-1'

DashboardStack > Dashboard リソース

Dashboard は CloudWatch ダッシュボード です。
以下のソースコードが Dashboard の定義です。

packages/cdk/lib/dashboard-stack.ts (抜粋)
    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'
      • グラフウィジェット
        • 幅 1 で InputTokenCount メトリクス
        • 幅 1 で OutputTokenCount メトリクス
        • 幅 1 で Invocations メトリクス
        • 幅 1 で UserPool メトリクス (SignInSuccesses メトリクス, TokenRefreshSuccesses メトリクス, SignUpSuccesses メトリクスを 1 つのグラフに表示)
      • テキストウィジェット
        • 幅 4 で 'Prompt Logs'
      • ログクエリウィジェット
        • 幅 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) 詳細解説投稿一覧

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?