GenU のバックエンド (CDK) 詳細解説　⑫GenerativeAiUseCasesStack > RagKnowledgeBase, UseCaseBuilder, Transcribe スタックの確認

## はじめに

皆さん、こんにちは。

私は業務でデータ利活用基盤を取り扱っていること、2024 AWS Japan Top Engineer に選出されたということから、[AWS GenU](https://aws-samples.github.io/generative-ai-use-cases-jp/) およびそれに必要なデータ基盤の探求 ([Snowflake](https://www.snowflake.com/ja/), [dbt](https://www.getdbt.com/), [Iceberg](https://iceberg.apache.org/), etc) に取り組む必要があると考えています。

本投稿では、[GenU のバックエンドである CDK コード](https://github.com/aws-samples/generative-ai-use-cases-jp/tree/main/packages/cdk)を詳細に解説します。
自身そして閲覧して頂いた皆様の GenU への理解が少しでも深まり、生成 AI の民主化につながっていければと考えています。

## 前回までのおさらい

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

- [①AWS CDK のセットアップ](https://qiita.com/siruko/items/fd25fdcf89615cb85262)
- [②AWS CDK の動作確認](https://qiita.com/siruko/items/73169f986b4173e3d3a5)
- [③GenU の概要](https://qiita.com/siruko/items/625801b9e1847b305c1e)
- [④GenU CDK スタックの概要](https://qiita.com/siruko/items/8570ed43f7162ea4b907)
- [⑤CloudFrontWafStack スタックの解説](https://qiita.com/siruko/items/30439576ee7c63165d21)
- [⑥RagKnowledgeBaseStack スタックの解説](https://qiita.com/siruko/items/1223c9d22e73168a8809)
- [⑦WebSearchAgentStack スタックの解説](https://qiita.com/siruko/items/aef0a9599df60d47eb1e)
- [⑧GuardrailStack スタックの解説](https://qiita.com/siruko/items/d16e9fe27df4673d7554)
- [⑨GenerativeAiUseCasesStack > Auth スタックの解説](https://qiita.com/siruko/items/0c14040a1af132f42382)
- [⑩GenerativeAiUseCasesStack > Database, Api スタックの解説](https://qiita.com/siruko/items/5f249f11847f3829c18a)
- [⑪GenerativeAiUseCasesStack > CommonWebAcl, Web, Rag スタックの解説](https://qiita.com/siruko/items/1063daa2efe0b374a3fc)

GenU の CDK は最大で以下の 6 つの子スタックを作成します。

- `CloudFrontWafStack`
- `RagKnowledgeBaseStack`
- `AgentStack`
- `GuardrailStack`
- `GenerativeAiUseCasesStack` ※メインスタック
- `DashboardStack`

第 9 回から GenU 内の本丸である `GenerativeAiUseCasesStack` スタックを解説しています。

### GenerativeAiUseCasesStack スタック

GenerativeAiUseCasesStack スタックでは、以下のリソースを作成しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217144/21c083da-76e1-4c98-9eb1-0830d5ecd09d.png)

- `Auth`
- `Database`
- `Api`
- `CommonWebAcl`
- `Web`
- `Rag`
- `RagKnowledgeBase`
- `UseCaseBuilder`
- `Transcribe`

今回は `RagKnowledgeBase`, `UseCaseBuilder`, `Transcribe` リソースを解説していきます。

#### GenerativeAiUseCasesStack > RagKnowledgeBase リソース

RagKnowledgeBase リソースは、アーキテクチャ図でいうと以下の赤枠の部分にあたります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217144/22f72f47-0d47-4ca6-9614-2471d1ff8f54.png)

RagKnowledgeBase では ナレッジベースから情報を取得する API を定義しています。
以下のソースコードが RagKnowledgeBase の定義です。

```typescript:packages/cdk/lib/generative-ai-use-cases-stack.ts (抜粋)
    // RAG Knowledge Base
    if (params.ragKnowledgeBaseEnabled) {
      const knowledgeBaseId =
        params.ragKnowledgeBaseId || props.knowledgeBaseId;
      if (knowledgeBaseId) {
        new RagKnowledgeBase(this, 'RagKnowledgeBase', {
          modelRegion: params.modelRegion,
          knowledgeBaseId: knowledgeBaseId,
          userPool: auth.userPool,
          api: api.api,
        });
        // File API から data source の Bucket のファイルをダウンロードできるようにする
        if (props.knowledgeBaseDataSourceBucketName) {
          api.allowDownloadFile(props.knowledgeBaseDataSourceBucketName);
        }
      }
    }
```

RagKnowledgeBase リソースの実体は [`packages/cdk/lib/construct/rag-knowledge-base-stack.ts`](https://github.com/aws-samples/generative-ai-use-cases-jp/blob/56583580fbc767c70ca451a09cc98ce7c299b998/packages/cdk/lib/construct/rag-knowledge-base.ts) にあります。
スタック作成時のパラメータ `ragKnowledgeBaseEnabled` (デフォルト値は false) が true の場合、以下のコードを実行し RagKnowledgeBase を作成します。

```typescript:packages/cdk/lib/construct/rag-knowledge-base-stack.ts
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as iam from 'aws-cdk-lib/aws-iam';
import { UserPool } from 'aws-cdk-lib/aws-cognito';
import {
  AuthorizationType,
  CognitoUserPoolsAuthorizer,
  LambdaIntegration,
  RestApi,
} from 'aws-cdk-lib/aws-apigateway';
import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs';
import { Runtime } from 'aws-cdk-lib/aws-lambda';

export interface RagKnowledgeBaseProps {
  // Context Params
  modelRegion: string;

  // Resource
  knowledgeBaseId: string;
  userPool: UserPool;
  api: RestApi;
}

export class RagKnowledgeBase extends Construct {
  constructor(scope: Construct, id: string, props: RagKnowledgeBaseProps) {
    super(scope, id);

    const { modelRegion } = props;

    const retrieveFunction = new NodejsFunction(this, 'Retrieve', {
      runtime: Runtime.NODEJS_LATEST,
      entry: './lambda/retrieveKnowledgeBase.ts',
      timeout: cdk.Duration.minutes(15),
      environment: {
        KNOWLEDGE_BASE_ID: props.knowledgeBaseId,
        MODEL_REGION: modelRegion,
      },
    });

    retrieveFunction.role?.addToPrincipalPolicy(
      new iam.PolicyStatement({
        effect: iam.Effect.ALLOW,
        resources: [
          `arn:aws:bedrock:${modelRegion}:${cdk.Stack.of(this).account}:knowledge-base/${props.knowledgeBaseId ?? ''}`,
        ],
        actions: ['bedrock:Retrieve'],
      })
    );

    const authorizer = new CognitoUserPoolsAuthorizer(this, 'Authorizer', {
      cognitoUserPools: [props.userPool],
    });

    const commonAuthorizerProps = {
      authorizationType: AuthorizationType.COGNITO,
      authorizer,
    };
    const ragResource = props.api.root.addResource('rag-knowledge-base');

    // POST: /rag-knowledge-base/retrieve
    const retrieveResource = ragResource.addResource('retrieve');
    retrieveResource.addMethod(
      'POST',
      new LambdaIntegration(retrieveFunction),
      commonAuthorizerProps
    );
  }
}
```

この中では、ナレッジベースから情報を取得する API を生成しています。

- `NodejsFunction` の `Retrieve` 関数
  - ランタイムは NodeJS の最新版
  - ソースコードは[`packages/cdk/lambda/retrieveKnowledgeBase.ts`](https://github.com/aws-samples/generative-ai-use-cases-jp/blob/56583580fbc767c70ca451a09cc98ce7c299b998/packages/cdk/lambda/retrieveKnowledgeBase.ts)
  - タイムアウトは 15 分
  - 環境変数
    - `KNOWLEDGE_BASE_ID`: 以下が設定されている場合、設定 (上が優先)
      - スタック作成時のパラメータ `ragKnowledgeBaseId` (デフォルト値は null)
      - [`RagKnowledgeBaseStack`](https://qiita.com/siruko/items/1223c9d22e73168a8809) を作成している場合、その `knowledgeBaseId` を設定
    - `MODEL_REGION`: スタック作成時のパラメータ `modelRegion` (デフォルト値は `us-east-1`)
  - ロール
    - ナレッジベースリソースに対する `bedrock:Retrieve` を許可
  - 処理概要
    - ナレッジベースに対して[取得コマンド](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/bedrock-agent-runtime/command/RetrieveCommand/)を実行
      - `knowledgeBaseId`: 環境変数の `KNOWLEDGE_BASE_ID`
      - `retrievalQuery.text`: `event.body.query`
      - `retrievalConfiguration.vectorSearchConfiguration`:
        - `numberOfResults`: 固定値 `10` (取得するソースチャンクの数)
        - `overrideSearchType` 固定値 `'HYBRID'` (ベクター埋め込みと生のテキストの両方を使用する HYBRID 検索)
    - 戻り値に実行結果を設定して終了
- [`Api`](https://qiita.com/siruko/items/5f249f11847f3829c18a#generativeaiusecasesstack--api-%E3%83%AA%E3%82%BD%E3%83%BC%E3%82%B9) リソースの API エンドポイントに上記関数の呼び出しを追加する
  - API GW オーソライザに [`Auth`](https://qiita.com/siruko/items/0c14040a1af132f42382#generativeaiusecasesstack--auth-%E3%83%AA%E3%82%BD%E3%83%BC%E3%82%B9) リソースの Cognito ユーザプールを指定
  - `/rag-knowledge-base/retrieve` (POST) => `Retrieve` Lambda 関数を呼び出し

これで RagKnowledgeBase リソースの作成は完了です。

加えて、GenerativeAiUseCasesStack から[`allowDownloadFile関数`](https://qiita.com/siruko/items/5f249f11847f3829c18a#cdk-%E7%94%A8%E9%96%A2%E6%95%B0allowdownloadfile) を呼び出し、[ナレッジベースのデータソース](https://qiita.com/siruko/items/1223c9d22e73168a8809#ragknowledgebasestack--bucket-%E3%83%AA%E3%82%BD%E3%83%BC%E3%82%B9) (S3 バケット) 内のファイルの署名付き URL を作成できるようにしています。

#### GenerativeAiUseCasesStack > UseCaseBuilder リソース

UseCaseBuilder リソースは、アーキテクチャ図でいうと以下の赤枠の部分にあたります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217144/d41edea0-44af-407d-896e-edb4ddcf5f2e.png)

UseCaseBuilder は 生成 AI アプリケーションのユースケースを公開、利用できる機能です。
以下のソースコードが UseCaseBuilder の定義です。

```typescript:packages/cdk/lib/generative-ai-use-cases-stack.ts (抜粋)
    // Usecase builder
    if (params.useCaseBuilderEnabled) {
      new UseCaseBuilder(this, 'UseCaseBuilder', {
        userPool: auth.userPool,
        api: api.api,
      });
    }
```

UseCaseBuilder リソースの実体は [`packages/cdk/lib/construct/use-case-builder.ts`](https://github.com/aws-samples/generative-ai-use-cases-jp/blob/56583580fbc767c70ca451a09cc98ce7c299b998/packages/cdk/lib/construct/use-case-builder.ts) にあります。
スタック作成時のパラメータ `useCaseBuilderEnabled` (デフォルト値は true) が true の場合、以下のコードを実行し UseCaseBuilder を作成します。

```typescript:packages/cdk/lib/construct/use-case-builder.ts
import {
  RestApi,
  LambdaIntegration,
  CognitoUserPoolsAuthorizer,
  AuthorizationType,
} from 'aws-cdk-lib/aws-apigateway';
import { Construct } from 'constructs';
import {
  NodejsFunction,
  NodejsFunctionProps,
} from 'aws-cdk-lib/aws-lambda-nodejs';
import { Runtime } from 'aws-cdk-lib/aws-lambda';
import { Duration } from 'aws-cdk-lib';
import { UserPool } from 'aws-cdk-lib/aws-cognito';
import * as ddb from 'aws-cdk-lib/aws-dynamodb';

export interface UseCaseBuilderProps {
  userPool: UserPool;
  api: RestApi;
}
export class UseCaseBuilder extends Construct {
  constructor(scope: Construct, id: string, props: UseCaseBuilderProps) {
    super(scope, id);

    const { userPool, api } = props;

    const useCaseIdIndexName = 'UseCaseIdIndexName';
    const useCaseBuilderTable = new ddb.Table(this, 'UseCaseBuilderTable', {
      partitionKey: {
        name: 'id',
        type: ddb.AttributeType.STRING,
      },
      sortKey: {
        name: 'dataType',
        type: ddb.AttributeType.STRING,
      },
      billingMode: ddb.BillingMode.PAY_PER_REQUEST,
    });

    useCaseBuilderTable.addGlobalSecondaryIndex({
      indexName: useCaseIdIndexName,
      partitionKey: {
        name: 'useCaseId',
        type: ddb.AttributeType.STRING,
      },
      sortKey: {
        name: 'dataType',
        type: ddb.AttributeType.STRING,
      },
      projectionType: ddb.ProjectionType.ALL,
    });

    const commonProperty: NodejsFunctionProps = {
      runtime: Runtime.NODEJS_LATEST,
      timeout: Duration.minutes(15),
      environment: {
        USECASE_TABLE_NAME: useCaseBuilderTable.tableName,
        USECASE_ID_INDEX_NAME: useCaseIdIndexName,
      },
    };

    const commonPath = './lambda/useCaseBuilder';

    // UseCaseBuilder 関連の API を追加する
    const listUseCasesFunction = new NodejsFunction(this, 'ListUseCases', {
      ...commonProperty,
      entry: `${commonPath}/listUseCases.ts`,
    });
    useCaseBuilderTable.grantReadData(listUseCasesFunction);

    const listFavoriteUseCasesFunction = new NodejsFunction(
      this,
      'ListFavoriteUseCases',
      {
        ...commonProperty,
        entry: `${commonPath}/listFavoriteUseCases.ts`,
        environment: {
          ...commonProperty.environment,
          USECASE_ID_INDEX_NAME: useCaseIdIndexName,
        },
      }
    );
    useCaseBuilderTable.grantReadData(listFavoriteUseCasesFunction);

    const getUseCaseFunction = new NodejsFunction(this, 'GetUseCase', {
      ...commonProperty,
      entry: `${commonPath}/getUseCase.ts`,
    });
    useCaseBuilderTable.grantReadData(getUseCaseFunction);

    const createUseCaseFunction = new NodejsFunction(this, 'CreateUseCase', {
      ...commonProperty,
      entry: `${commonPath}/createUseCase.ts`,
    });
    useCaseBuilderTable.grantWriteData(createUseCaseFunction);

    const updateUseCaseFunction = new NodejsFunction(this, 'UpdateUseCase', {
      ...commonProperty,
      entry: `${commonPath}/updateUseCase.ts`,
    });
    useCaseBuilderTable.grantReadWriteData(updateUseCaseFunction);

    const deleteUseCaseFunction = new NodejsFunction(this, 'DeleteUseCase', {
      ...commonProperty,
      entry: `${commonPath}/deleteUseCase.ts`,
    });
    useCaseBuilderTable.grantReadWriteData(deleteUseCaseFunction);

    const toggleFavoriteFunction = new NodejsFunction(this, 'ToggleFavorite', {
      ...commonProperty,
      entry: `${commonPath}/toggleFavorite.ts`,
    });
    useCaseBuilderTable.grantReadWriteData(toggleFavoriteFunction);

    const toggleSharedFunction = new NodejsFunction(this, 'ToggleShared', {
      ...commonProperty,
      entry: `${commonPath}/toggleShared.ts`,
    });
    useCaseBuilderTable.grantReadWriteData(toggleSharedFunction);

    const listRecentlyUsedUseCasesFunction = new NodejsFunction(
      this,
      'ListRecentlyUsedUseCases',
      {
        ...commonProperty,
        entry: `${commonPath}/listRecentlyUsedUseCases.ts`,
      }
    );
    useCaseBuilderTable.grantReadData(listRecentlyUsedUseCasesFunction);

    const updateRecentlyUsedUseCaseFunction = new NodejsFunction(
      this,
      'UpdateRecentlyUsedUseCase',
      {
        ...commonProperty,
        entry: `${commonPath}/updateRecentlyUsedUseCase.ts`,
      }
    );
    useCaseBuilderTable.grantReadWriteData(updateRecentlyUsedUseCaseFunction);

    // API Gateway
    const authorizer = new CognitoUserPoolsAuthorizer(this, 'Authorizer', {
      cognitoUserPools: [userPool],
    });

    const commonAuthorizerProps = {
      authorizationType: AuthorizationType.COGNITO,
      authorizer,
    };
    const useCasesResource = api.root.addResource('usecases');

    // GET: /usecases
    useCasesResource.addMethod(
      'GET',
      new LambdaIntegration(listUseCasesFunction),
      commonAuthorizerProps
    );

    // POST: /usecases
    useCasesResource.addMethod(
      'POST',
      new LambdaIntegration(createUseCaseFunction),
      commonAuthorizerProps
    );

    const favoriteUseCaseResource = useCasesResource.addResource('favorite');

    // GET: /usecases/favorite
    favoriteUseCaseResource.addMethod(
      'GET',
      new LambdaIntegration(listFavoriteUseCasesFunction),
      commonAuthorizerProps
    );

    const useCaseResource = useCasesResource.addResource('{useCaseId}');

    // GET: /usecases/{useCaseId}
    useCaseResource.addMethod(
      'GET',
      new LambdaIntegration(getUseCaseFunction),
      commonAuthorizerProps
    );

    // PUT: /usecases/{useCaseId}
    useCaseResource.addMethod(
      'PUT',
      new LambdaIntegration(updateUseCaseFunction),
      commonAuthorizerProps
    );

    // DELETE: /usecases/{useCaseId}
    useCaseResource.addMethod(
      'DELETE',
      new LambdaIntegration(deleteUseCaseFunction),
      commonAuthorizerProps
    );

    const favoriteResource = useCaseResource.addResource('favorite');

    // PUT: /usecases/{useCaseId}/favorite
    favoriteResource.addMethod(
      'PUT',
      new LambdaIntegration(toggleFavoriteFunction),
      commonAuthorizerProps
    );

    const sharedResource = useCaseResource.addResource('shared');

    // PUT: /usecases/{useCaseId}/shared
    sharedResource.addMethod(
      'PUT',
      new LambdaIntegration(toggleSharedFunction),
      commonAuthorizerProps
    );

    const recentUseCasesResource = useCasesResource.addResource('recent');

    // GET: /usecases/recent
    recentUseCasesResource.addMethod(
      'GET',
      new LambdaIntegration(listRecentlyUsedUseCasesFunction),
      commonAuthorizerProps
    );

    const recentUseCaseResource =
      recentUseCasesResource.addResource('{useCaseId}');

    // PUT: /usecases/recent/{useCaseId}
    recentUseCaseResource.addMethod(
      'PUT',
      new LambdaIntegration(updateRecentlyUsedUseCaseFunction),
      commonAuthorizerProps
    );
  }
}
```

この中では、ナレッジベースから情報を取得する API を生成しています。

##### GenerativeAiUseCasesStack > UseCaseBuilder > Table リソース

[`Table`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_dynamodb.Table.html) はユースケース用の DynamoDB テーブルのリソースです。

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

```typescript:packages/cdk/lib/construct/transcribe.ts (抜粋)
    const useCaseIdIndexName = 'UseCaseIdIndexName';
    const useCaseBuilderTable = new ddb.Table(this, 'UseCaseBuilderTable', {
      partitionKey: {
        name: 'id',
        type: ddb.AttributeType.STRING,
      },
      sortKey: {
        name: 'dataType',
        type: ddb.AttributeType.STRING,
      },
      billingMode: ddb.BillingMode.PAY_PER_REQUEST,
    });

    useCaseBuilderTable.addGlobalSecondaryIndex({
      indexName: useCaseIdIndexName,
      partitionKey: {
        name: 'useCaseId',
        type: ddb.AttributeType.STRING,
      },
      sortKey: {
        name: 'dataType',
        type: ddb.AttributeType.STRING,
      },
      projectionType: ddb.ProjectionType.ALL,
    });
```

ここでは、以下のリソースを生成しています。

- `UseCaseBuilderTable` DynamoDB テーブル
  - パーティションキーに文字列型の `id` を指定
  - ソートキーに文字列型の `dataType` を指定
  - 請求モードに[`PAY_PER_REQUEST: オンデマンドキャパシティモード`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_dynamodb.BillingMode.html) を指定
  - GSI (グローバルセカンダリインデックス) `UseCaseIdIndexName`
    - GSI のパーティションキーに文字列型の `useCaseId` を指定
    - GSI のソートキーに文字列型の `dataType` を指定
    - ProjectionType に [`ALL: すべてのテーブル属性をインデックスに投影`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_dynamodb.ProjectionType.html)を指定

##### GenerativeAiUseCasesStack > UseCaseBuilder > NodejsFunction リソース

[`NodejsFunction`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_lambda_nodejs.NodejsFunction.html) はユースケース用の Lambda 関数のリソースです。

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

```typescript:packages/cdk/lib/construct/transcribe.ts (抜粋)

    const commonProperty: NodejsFunctionProps = {
      runtime: Runtime.NODEJS_LATEST,
      timeout: Duration.minutes(15),
      environment: {
        USECASE_TABLE_NAME: useCaseBuilderTable.tableName,
        USECASE_ID_INDEX_NAME: useCaseIdIndexName,
      },
    };

    const commonPath = './lambda/useCaseBuilder';

    // UseCaseBuilder 関連の API を追加する
    const listUseCasesFunction = new NodejsFunction(this, 'ListUseCases', {
      ...commonProperty,
      entry: `${commonPath}/listUseCases.ts`,
    });
    useCaseBuilderTable.grantReadData(listUseCasesFunction);

    const listFavoriteUseCasesFunction = new NodejsFunction(
      this,
      'ListFavoriteUseCases',
      {
        ...commonProperty,
        entry: `${commonPath}/listFavoriteUseCases.ts`,
        environment: {
          ...commonProperty.environment,
          USECASE_ID_INDEX_NAME: useCaseIdIndexName,
        },
      }
    );
    useCaseBuilderTable.grantReadData(listFavoriteUseCasesFunction);

    const getUseCaseFunction = new NodejsFunction(this, 'GetUseCase', {
      ...commonProperty,
      entry: `${commonPath}/getUseCase.ts`,
    });
    useCaseBuilderTable.grantReadData(getUseCaseFunction);

    const createUseCaseFunction = new NodejsFunction(this, 'CreateUseCase', {
      ...commonProperty,
      entry: `${commonPath}/createUseCase.ts`,
    });
    useCaseBuilderTable.grantWriteData(createUseCaseFunction);

    const updateUseCaseFunction = new NodejsFunction(this, 'UpdateUseCase', {
      ...commonProperty,
      entry: `${commonPath}/updateUseCase.ts`,
    });
    useCaseBuilderTable.grantReadWriteData(updateUseCaseFunction);

    const deleteUseCaseFunction = new NodejsFunction(this, 'DeleteUseCase', {
      ...commonProperty,
      entry: `${commonPath}/deleteUseCase.ts`,
    });
    useCaseBuilderTable.grantReadWriteData(deleteUseCaseFunction);

    const toggleFavoriteFunction = new NodejsFunction(this, 'ToggleFavorite', {
      ...commonProperty,
      entry: `${commonPath}/toggleFavorite.ts`,
    });
    useCaseBuilderTable.grantReadWriteData(toggleFavoriteFunction);

    const toggleSharedFunction = new NodejsFunction(this, 'ToggleShared', {
      ...commonProperty,
      entry: `${commonPath}/toggleShared.ts`,
    });
    useCaseBuilderTable.grantReadWriteData(toggleSharedFunction);

    const listRecentlyUsedUseCasesFunction = new NodejsFunction(
      this,
      'ListRecentlyUsedUseCases',
      {
        ...commonProperty,
        entry: `${commonPath}/listRecentlyUsedUseCases.ts`,
      }
    );
    useCaseBuilderTable.grantReadData(listRecentlyUsedUseCasesFunction);

    const updateRecentlyUsedUseCaseFunction = new NodejsFunction(
      this,
      'UpdateRecentlyUsedUseCase',
      {
        ...commonProperty,
        entry: `${commonPath}/updateRecentlyUsedUseCase.ts`,
      }
    );
    useCaseBuilderTable.grantReadWriteData(updateRecentlyUsedUseCaseFunction);
```

ここでは、以下のリソースを生成しています。

- 共通設定
  - ランタイムは NodeJS の最新版
  - タイムアウトは 15 分
  - 環境変数
    - `USECASE_TABLE_NAME`: DynamoDB テーブル `UseCaseBuilderTable` のテーブル名
    - `USECASE_ID_INDEX_NAME`: GSI (グローバルセカンダリインデックス) `UseCaseIdIndexName` のインデックス名
- `ListUseCases` 関数
  - ソースコードは[`packages/cdk/lambda/useCaseBuilder/listUseCases.ts`](https://github.com/aws-samples/generative-ai-use-cases-jp/blob/56583580fbc767c70ca451a09cc98ce7c299b998/packages/cdk/lambda/useCaseBuilder/listUseCases.ts)
  - 処理概要
    - Cognito ユーザ ID を取得
    - [`packages/cdk/lambda/useCaseBuilder/useCaseBuilderRepository.ts`](https://github.com/aws-samples/generative-ai-use-cases-jp/blob/56583580fbc767c70ca451a09cc98ce7c299b998/packages/cdk/lambda/useCaseBuilder/useCaseBuilderRepository.ts)の listUseCases 関数を呼び出して DynamoDB テーブルからデータを取得
      - Cognito ユーザに紐づくユースケース一覧を取得
        - `TableName`: ユースケースビルダー用 DynamnoDB テーブル名
        - `id`: `useCase#` + Cognito ユーザ ID
        - `dataType`: `useCase` で始まる
        - `取得件数 Limit`: 30 件 (マイユースケースのページあたりの件数)
        - `ExclusiveStartKey`: `event.queryStringParameters.exclusiveStartKey` があればそのキーから取得する
      - Cognito ユーザに紐づくお気に入りを全取得
        - `TableName`: ユースケースビルダー用 DynamnoDB テーブル名
        - `id`: `useCase#` + Cognito ユーザ ID
        - `dataType`: `favorite` で始まる
    - 戻り値に取得したデータを設定
- `ListFavoriteUseCases` 関数
  - ソースコードは[`packages/cdk/lambda/useCaseBuilder/listFavoriteUseCases.ts`](https://github.com/aws-samples/generative-ai-use-cases-jp/blob/56583580fbc767c70ca451a09cc98ce7c299b998/packages/cdk/lambda/useCaseBuilder/listFavoriteUseCases.ts)
  - 処理概要
    - Cognito ユーザ ID を取得
    - [`packages/cdk/lambda/useCaseBuilder/useCaseBuilderRepository.ts`](https://github.com/aws-samples/generative-ai-use-cases-jp/blob/56583580fbc767c70ca451a09cc98ce7c299b998/packages/cdk/lambda/useCaseBuilder/useCaseBuilderRepository.ts)の listFavoriteUseCases 関数を呼び出して DynamoDB テーブルからデータを取得
      - Cognito ユーザに紐づくお気に入り一覧を取得
        - `TableName`: ユースケースビルダー用 DynamnoDB テーブル名
        - `id`: `useCase#` + Cognito ユーザ ID
        - `dataType`: `favorite` で始まる
        - `取得件数 Limit`: 20 件 (お気に入りのページあたりの件数)
        - `ExclusiveStartKey`: クエリパラメータ文字列の `exclusiveStartKey` があればそのキーから取得する
      - 取得したユースケース一覧の情報を繰り返し取得
        - `TableName`: ユースケースビルダー用 DynamnoDB テーブル名
        - `IndexName`: ユースケースビルダー用 DynamnoDB テーブル GSI 名
        - `useCaseId`: 取得したユースケース ID
        - `dataType`: `useCase` で始まる
      - 自分が作成したユースケースもしくはシェアされているユースケースに絞る (後から非公開となったユースケースを除外)
    - 戻り値に取得したデータを設定
- `GetUseCase` 関数
  - ソースコードは[`packages/cdk/lambda/useCaseBuilder/getUseCase.ts`](https://github.com/aws-samples/generative-ai-use-cases-jp/blob/56583580fbc767c70ca451a09cc98ce7c299b998/packages/cdk/lambda/useCaseBuilder/getUseCase.ts)
  - 処理概要
    - Cognito ユーザ ID を取得
    - [`packages/cdk/lambda/useCaseBuilder/useCaseBuilderRepository.ts`](https://github.com/aws-samples/generative-ai-use-cases-jp/blob/56583580fbc767c70ca451a09cc98ce7c299b998/packages/cdk/lambda/useCaseBuilder/useCaseBuilderRepository.ts)の getUseCase 関数を呼び出して DynamoDB テーブルからデータを取得
      - 指定したユースケースの情報を取得
        - `TableName`: ユースケースビルダー用 DynamnoDB テーブル名
        - `IndexName`: ユースケースビルダー用 DynamnoDB テーブル GSI 名
        - `useCaseId`: パスパラメータの `useCaseId`
        - `dataType`: `useCase` で始まる
      - 自分が作成したユースケースもしくはシェアされているユースケースでない場合、null にする (後から非公開となったユースケースを除外)
    - 戻り値に取得したデータを設定
- `CreateUseCase` 関数
  - ソースコードは[`packages/cdk/lambda/useCaseBuilder/createUseCase.ts`](https://github.com/aws-samples/generative-ai-use-cases-jp/blob/56583580fbc767c70ca451a09cc98ce7c299b998/packages/cdk/lambda/useCaseBuilder/createUseCase.ts)
  - 処理概要
    - Cognito ユーザ ID を取得
    - [`packages/cdk/lambda/useCaseBuilder/useCaseBuilderRepository.ts`](https://github.com/aws-samples/generative-ai-use-cases-jp/blob/56583580fbc767c70ca451a09cc98ce7c299b998/packages/cdk/lambda/useCaseBuilder/useCaseBuilderRepository.ts)の createUseCase 関数を呼び出して DynamoDB テーブルにデータを登録
      - `TableName`: ユースケースビルダー用 DynamnoDB テーブル名
      - `id`: `useCase#` + Cognito ユーザ ID
      - `dataType`: `useCase#` + [現在日時のミリ秒](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Date/now)
      - `useCaseId`: [ランダム生成した UUID](https://developer.mozilla.org/ja/docs/Web/API/Crypto/randomUUID)
      - `title`: `event.body.title`
      - `description`: `event.body.description`
      - `promptTemplate`: `event.body.promptTemplate`
      - `inputExamples`: `event.body.inputExamples`
      - `fixedModelId`: `event.body.fixedModelId`
      - `fileUpload`: `event.body.fileUpload`
      - `isShared`: 固定値 `false`
- `UpdateUseCase` 関数
  - ソースコードは[`packages/cdk/lambda/useCaseBuilder/updateUseCase.ts`](https://github.com/aws-samples/generative-ai-use-cases-jp/blob/56583580fbc767c70ca451a09cc98ce7c299b998/packages/cdk/lambda/useCaseBuilder/updateUseCase.ts)
  - 処理概要
    - Cognito ユーザ ID を取得
    - [`packages/cdk/lambda/useCaseBuilder/useCaseBuilderRepository.ts`](https://github.com/aws-samples/generative-ai-use-cases-jp/blob/56583580fbc767c70ca451a09cc98ce7c299b998/packages/cdk/lambda/useCaseBuilder/useCaseBuilderRepository.ts)の updateUseCase 関数を呼び出して DynamoDB テーブルのデータを更新
      - 指定したユースケースの情報を取得
        - `TableName`: ユースケースビルダー用 DynamnoDB テーブル名
        - `IndexName`: ユースケースビルダー用 DynamnoDB テーブル GSI 名
        - `useCaseId`: パスパラメータの `useCaseId`
        - `dataType`: `useCase` で始まる
      - 存在チェックおよび自身のユースケースかをチェック
      - ユースケースの情報を更新
        - `TableName`: ユースケースビルダー用 DynamnoDB テーブル名
        - `id`: 取得した `id`
        - `dataType`: 取得した `dataType`
        - `title`: `event.body.title`
        - `description`: `event.body.description`
        - `promptTemplate`: `event.body.promptTemplate`
        - `inputExamples`: `event.body.inputExamples`
        - `fixedModelId`: `event.body.fixedModelId`
        - `fileUpload`: `event.body.fileUpload`
- `DeleteUseCase` 関数
  - ソースコードは[`packages/cdk/lambda/useCaseBuilder/deleteUseCase.ts`](https://github.com/aws-samples/generative-ai-use-cases-jp/blob/56583580fbc767c70ca451a09cc98ce7c299b998/packages/cdk/lambda/useCaseBuilder/deleteUseCase.ts)
  - 処理概要
    - Cognito ユーザ ID を取得
    - [`packages/cdk/lambda/useCaseBuilder/useCaseBuilderRepository.ts`](https://github.com/aws-samples/generative-ai-use-cases-jp/blob/56583580fbc767c70ca451a09cc98ce7c299b998/packages/cdk/lambda/useCaseBuilder/useCaseBuilderRepository.ts)の deleteUseCase 関数を呼び出して DynamoDB テーブルのデータを削除
      - 指定したユースケースの情報を取得
        - `TableName`: ユースケースビルダー用 DynamnoDB テーブル名
        - `IndexName`: ユースケースビルダー用 DynamnoDB テーブル GSI 名
        - `useCaseId`: パスパラメータの `useCaseId`
        - `dataType`: `useCase` で始まる
      - 存在チェックおよび自身のユースケースかをチェック
      - 指定したユースケースの全情報を取得 (`dataType` で絞り込まない)
        - `TableName`: ユースケースビルダー用 DynamnoDB テーブル名
        - `IndexName`: ユースケースビルダー用 DynamnoDB テーブル GSI 名
        - `useCaseId`: パスパラメータの `useCaseId`
      - 取得したユースケースを削除
        - `TableName`: ユースケースビルダー用 DynamnoDB テーブル名
        - `id`: 取得した `id`
        - `dataType`: `useCase#` + [現在日時のミリ秒](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Date/now)
- `ToggleFavorite` 関数
  - ソースコードは[`packages/cdk/lambda/useCaseBuilder/toggleFavorite.ts`](https://github.com/aws-samples/generative-ai-use-cases-jp/blob/56583580fbc767c70ca451a09cc98ce7c299b998/packages/cdk/lambda/useCaseBuilder/toggleFavorite.ts)
  - 処理概要
    - Cognito ユーザ ID を取得
    - [`packages/cdk/lambda/useCaseBuilder/useCaseBuilderRepository.ts`](https://github.com/aws-samples/generative-ai-use-cases-jp/blob/56583580fbc767c70ca451a09cc98ce7c299b998/packages/cdk/lambda/useCaseBuilder/useCaseBuilderRepository.ts)の toggleFavorite 関数を呼び出して DynamoDB テーブルのデータを更新
      - 指定したユースケースの情報を取得
        - `TableName`: ユースケースビルダー用 DynamnoDB テーブル名
        - `id`: `useCase#` + Cognito ユーザ ID
        - `useCaseId`: パスパラメータの `useCaseId`
        - `dataType`: `favorite` で始まる
      - ユースケースの情報が取得できた場合、お気に入りを解除 (データを削除)
        - `TableName`: ユースケースビルダー用 DynamnoDB テーブル名
        - `id`: 取得した `id`
        - `dataType`: 取得した `dataType`
      - ユースケースの情報が取得できなかった場合、お気に入りを登録
        - `TableName`: ユースケースビルダー用 DynamnoDB テーブル名
        - `id`: `useCase#` + Cognito ユーザ ID
        - `dataType`: `favorite#` + [現在日時のミリ秒](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Date/now)
        - `useCaseId`: 取得した `useCaseId`
- `ToggleShared` 関数
  - ソースコードは[`packages/cdk/lambda/useCaseBuilder/toggleShared.ts`](https://github.com/aws-samples/generative-ai-use-cases-jp/blob/56583580fbc767c70ca451a09cc98ce7c299b998/packages/cdk/lambda/useCaseBuilder/toggleShared.ts)
  - 処理概要
    - Cognito ユーザ ID を取得
    - [`packages/cdk/lambda/useCaseBuilder/useCaseBuilderRepository.ts`](https://github.com/aws-samples/generative-ai-use-cases-jp/blob/56583580fbc767c70ca451a09cc98ce7c299b998/packages/cdk/lambda/useCaseBuilder/useCaseBuilderRepository.ts)の toggleShared 関数を呼び出して DynamoDB テーブルのデータを更新
      - 指定したユースケースの情報を取得
        - `TableName`: ユースケースビルダー用 DynamnoDB テーブル名
        - `id`: `useCase#` + Cognito ユーザ ID
        - `useCaseId`: パスパラメータの `useCaseId`
        - `dataType`: `favorite` で始まる
      - 存在チェックおよび自身のユースケースかをチェックし、自身のものでない場合は `isShared: false` を返却
      - ユースケースの情報が取得できなかった場合、お気に入りを登録
        - `TableName`: ユースケースビルダー用 DynamnoDB テーブル名
        - `id`: 取得した `id`
        - `dataType`: 取得した `dataType`
        - `isShared`: 取得した `isShared` を反転して登録
- `ListRecentlyUsedUseCases` 関数
  - ソースコードは[`packages/cdk/lambda/useCaseBuilder/listRecentlyUsedUseCases.ts`](https://github.com/aws-samples/generative-ai-use-cases-jp/blob/56583580fbc767c70ca451a09cc98ce7c299b998/packages/cdk/lambda/useCaseBuilder/listRecentlyUsedUseCases.ts)
  - 処理概要
    - Cognito ユーザ ID を取得
    - [`packages/cdk/lambda/useCaseBuilder/useCaseBuilderRepository.ts`](https://github.com/aws-samples/generative-ai-use-cases-jp/blob/56583580fbc767c70ca451a09cc98ce7c299b998/packages/cdk/lambda/useCaseBuilder/useCaseBuilderRepository.ts)の listRecentlyUsedUseCases 関数を呼び出して DynamoDB テーブルからデータを取得
      - Cognito ユーザに紐づく利用履歴一覧を取得
        - `TableName`: ユースケースビルダー用 DynamnoDB テーブル名
        - `id`: `useCase#` + Cognito ユーザ ID
        - `dataType`: `recentlyUsed` で始まる
        - `取得件数 Limit`: 20 件 (利用履歴のページあたりの件数)
        - `ExclusiveStartKey`: クエリパラメータ文字列の `exclusiveStartKey` があればそのキーから取得する
      - 取得したユースケースの情報を取得
        - `TableName`: ユースケースビルダー用 DynamnoDB テーブル名
        - `IndexName`: ユースケースビルダー用 DynamnoDB テーブル GSI 名
        - `useCaseId`: パスパラメータの `useCaseId`
        - `dataType`: `useCase` で始まる
      - Cognito ユーザに紐づくお気に入りを全取得
        - `TableName`: ユースケースビルダー用 DynamnoDB テーブル名
        - `id`: `useCase#` + Cognito ユーザ ID
        - `dataType`: `favorite` で始まる
    - 戻り値に取得したデータを設定
- `UpdateRecentlyUsedUseCase` 関数
  - ソースコードは[`packages/cdk/lambda/useCaseBuilder/updateRecentlyUsedUseCase.ts`](https://github.com/aws-samples/generative-ai-use-cases-jp/blob/56583580fbc767c70ca451a09cc98ce7c299b998/packages/cdk/lambda/useCaseBuilder/updateRecentlyUsedUseCase.ts)
  - 処理概要
    - Cognito ユーザ ID を取得
    - [`packages/cdk/lambda/useCaseBuilder/useCaseBuilderRepository.ts`](https://github.com/aws-samples/generative-ai-use-cases-jp/blob/56583580fbc767c70ca451a09cc98ce7c299b998/packages/cdk/lambda/useCaseBuilder/useCaseBuilderRepository.ts)の updateRecentlyUsedUseCase 関数を呼び出して DynamoDB テーブルのデータを更新
      - Cognito ユーザに紐づく利用履歴を全取得
        - `TableName`: ユースケースビルダー用 DynamnoDB テーブル名
        - `id`: `useCase#` + Cognito ユーザ ID
        - `dataType`: `recentlyUsed` で始まる
      - 既に利用履歴がリミット (100 件) を超えている場合、利用履歴を 100 件にスライス
      - 利用履歴への登録対象のユースケースが既に利用履歴に含まれている場合、削除を行う
        - `TableName`: ユースケースビルダー用 DynamnoDB テーブル名
        - `id`: 取得した `id`
        - `dataType`: 取得した `dataType`
      - 利用履歴への登録を行う
        - `TableName`: ユースケースビルダー用 DynamnoDB テーブル名
        - `id`: `useCase#` + Cognito ユーザ ID
        - `dataType`: `recentlyUsed#` + [現在日時のミリ秒](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Date/now)
        - `useCaseId`: パスパラメータの `useCaseId`

##### GenerativeAiUseCasesStack > UseCaseBuilder > Lambda 関数を API に追加

続いて、Lambda 関数を `Api` リソースの API エンドポイントに追加します。

```typescript:packages/cdk/lib/construct/transcribe.ts (抜粋)
    // API Gateway
    const authorizer = new CognitoUserPoolsAuthorizer(this, 'Authorizer', {
      cognitoUserPools: [userPool],
    });

    const commonAuthorizerProps = {
      authorizationType: AuthorizationType.COGNITO,
      authorizer,
    };
    const useCasesResource = api.root.addResource('usecases');

    // GET: /usecases
    useCasesResource.addMethod(
      'GET',
      new LambdaIntegration(listUseCasesFunction),
      commonAuthorizerProps
    );

    // POST: /usecases
    useCasesResource.addMethod(
      'POST',
      new LambdaIntegration(createUseCaseFunction),
      commonAuthorizerProps
    );

    const favoriteUseCaseResource = useCasesResource.addResource('favorite');

    // GET: /usecases/favorite
    favoriteUseCaseResource.addMethod(
      'GET',
      new LambdaIntegration(listFavoriteUseCasesFunction),
      commonAuthorizerProps
    );

    const useCaseResource = useCasesResource.addResource('{useCaseId}');

    // GET: /usecases/{useCaseId}
    useCaseResource.addMethod(
      'GET',
      new LambdaIntegration(getUseCaseFunction),
      commonAuthorizerProps
    );

    // PUT: /usecases/{useCaseId}
    useCaseResource.addMethod(
      'PUT',
      new LambdaIntegration(updateUseCaseFunction),
      commonAuthorizerProps
    );

    // DELETE: /usecases/{useCaseId}
    useCaseResource.addMethod(
      'DELETE',
      new LambdaIntegration(deleteUseCaseFunction),
      commonAuthorizerProps
    );

    const favoriteResource = useCaseResource.addResource('favorite');

    // PUT: /usecases/{useCaseId}/favorite
    favoriteResource.addMethod(
      'PUT',
      new LambdaIntegration(toggleFavoriteFunction),
      commonAuthorizerProps
    );

    const sharedResource = useCaseResource.addResource('shared');

    // PUT: /usecases/{useCaseId}/shared
    sharedResource.addMethod(
      'PUT',
      new LambdaIntegration(toggleSharedFunction),
      commonAuthorizerProps
    );

    const recentUseCasesResource = useCasesResource.addResource('recent');

    // GET: /usecases/recent
    recentUseCasesResource.addMethod(
      'GET',
      new LambdaIntegration(listRecentlyUsedUseCasesFunction),
      commonAuthorizerProps
    );

    const recentUseCaseResource =
      recentUseCasesResource.addResource('{useCaseId}');

    // PUT: /usecases/recent/{useCaseId}
    recentUseCaseResource.addMethod(
      'PUT',
      new LambdaIntegration(updateRecentlyUsedUseCaseFunction),
      commonAuthorizerProps
    );
```

ここでは、[`Api`](https://qiita.com/siruko/items/5f249f11847f3829c18a#generativeaiusecasesstack--api-%E3%83%AA%E3%82%BD%E3%83%BC%E3%82%B9) リソースの API エンドポイントに上記関数の呼び出しを追加しています。

- API GW オーソライザに [`Auth`](https://qiita.com/siruko/items/0c14040a1af132f42382#generativeaiusecasesstack--auth-%E3%83%AA%E3%82%BD%E3%83%BC%E3%82%B9) リソースの Cognito ユーザプールを指定
- `/usecases` (GET) => `ListUseCases` Lambda 関数を呼び出し
- `/usecases/favorite` (GET) => `ListFavoriteUseCases` Lambda 関数を呼び出し
- `/usecases/{useCaseId}` (GET) => `GetUseCase` Lambda 関数を呼び出し
- `/usecases` (POST) => `CreateUseCase` Lambda 関数を呼び出し
- `/usecases/{useCaseId}` (PUT) => `UpdateUseCase` Lambda 関数を呼び出し
- `/usecases/{useCaseId}` (DELETE) => `DeleteUseCase` Lambda 関数を呼び出し
- `/usecases/{useCaseId}/favorite` (PUT) => `ToggleFavorite` Lambda 関数を呼び出し
- `/usecases/{useCaseId}/shared` (PUT) => `ToggleShared` Lambda 関数を呼び出し
- `/usecases/recent` (GET) => `ListRecentlyUsedUseCases` Lambda 関数を呼び出し
- `/usecases/recent/{useCaseId}` (PUT) => `UpdateRecentlyUsedUseCase` Lambda 関数を呼び出し

#### GenerativeAiUseCasesStack > Transcribe リソース

Transcribe リソースは、アーキテクチャ図でいうと以下の赤枠の部分にあたります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217144/5ae7cd72-927c-4ff1-baca-612ed8cd3dd6.png)

Transcribe は音声データの文字起こし機能です。
以下のソースコードが Transcribe の定義です。

```typescript:packages/cdk/lib/generative-ai-use-cases-stack.ts (抜粋)
    // Transcribe
    new Transcribe(this, 'Transcribe', {
      userPool: auth.userPool,
      idPool: auth.idPool,
      api: api.api,
    });
```

Transcribe リソースの実体は [`packages/cdk/lib/construct/transcribe.ts`](https://github.com/aws-samples/generative-ai-use-cases-jp/blob/56583580fbc767c70ca451a09cc98ce7c299b998/packages/cdk/lib/construct/transcribe.ts) にあります。

```typescript:packages/cdk/lib/construct/transcribe.ts
import { Duration, RemovalPolicy } from 'aws-cdk-lib';
import {
  AuthorizationType,
  CognitoUserPoolsAuthorizer,
  LambdaIntegration,
  RestApi,
} from 'aws-cdk-lib/aws-apigateway';
import { UserPool } from 'aws-cdk-lib/aws-cognito';
import { IdentityPool } from '@aws-cdk/aws-cognito-identitypool-alpha';
import { Effect, Policy, PolicyStatement } from 'aws-cdk-lib/aws-iam';
import { Runtime } from 'aws-cdk-lib/aws-lambda';
import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs';
import {
  BlockPublicAccess,
  Bucket,
  BucketEncryption,
  HttpMethods,
} from 'aws-cdk-lib/aws-s3';
import { Construct } from 'constructs';

export interface TranscribeProps {
  userPool: UserPool;
  idPool: IdentityPool;
  api: RestApi;
}

export class Transcribe extends Construct {
  constructor(scope: Construct, id: string, props: TranscribeProps) {
    super(scope, id);

    const audioBucket = new Bucket(this, 'AudioBucket', {
      encryption: BucketEncryption.S3_MANAGED,
      removalPolicy: RemovalPolicy.DESTROY,
      autoDeleteObjects: true,
      enforceSSL: true,
      blockPublicAccess: BlockPublicAccess.BLOCK_ALL,
    });
    audioBucket.addCorsRule({
      allowedOrigins: ['*'],
      allowedMethods: [HttpMethods.PUT],
      allowedHeaders: ['*'],
      exposedHeaders: [],
      maxAge: 3000,
    });

    const transcriptBucket = new Bucket(this, 'TranscriptBucket', {
      encryption: BucketEncryption.S3_MANAGED,
      removalPolicy: RemovalPolicy.DESTROY,
      autoDeleteObjects: true,
      enforceSSL: true,
      blockPublicAccess: BlockPublicAccess.BLOCK_ALL,
    });

    const getSignedUrlFunction = new NodejsFunction(this, 'GetSignedUrl', {
      runtime: Runtime.NODEJS_LATEST,
      entry: './lambda/getFileUploadSignedUrl.ts',
      timeout: Duration.minutes(15),
      environment: {
        BUCKET_NAME: audioBucket.bucketName,
      },
    });
    audioBucket.grantWrite(getSignedUrlFunction);

    const startTranscriptionFunction = new NodejsFunction(
      this,
      'StartTranscription',
      {
        runtime: Runtime.NODEJS_LATEST,
        entry: './lambda/startTranscription.ts',
        timeout: Duration.minutes(15),
        environment: {
          TRANSCRIPT_BUCKET_NAME: transcriptBucket.bucketName,
        },
        initialPolicy: [
          new PolicyStatement({
            effect: Effect.ALLOW,
            actions: ['transcribe:*'],
            resources: ['*'],
          }),
        ],
      }
    );
    audioBucket.grantRead(startTranscriptionFunction);
    transcriptBucket.grantWrite(startTranscriptionFunction);

    const getTranscriptionFunction = new NodejsFunction(
      this,
      'GetTranscription',
      {
        runtime: Runtime.NODEJS_LATEST,
        entry: './lambda/getTranscription.ts',
        timeout: Duration.minutes(15),
        initialPolicy: [
          new PolicyStatement({
            effect: Effect.ALLOW,
            actions: ['transcribe:*'],
            resources: ['*'],
          }),
        ],
      }
    );
    transcriptBucket.grantRead(getTranscriptionFunction);

    // API Gateway
    const authorizer = new CognitoUserPoolsAuthorizer(this, 'Authorizer', {
      cognitoUserPools: [props.userPool],
    });

    const commonAuthorizerProps = {
      authorizationType: AuthorizationType.COGNITO,
      authorizer,
    };
    const transcribeResource = props.api.root.addResource('transcribe');

    // POST: /transcribe/start
    transcribeResource
      .addResource('start')
      .addMethod(
        'POST',
        new LambdaIntegration(startTranscriptionFunction),
        commonAuthorizerProps
      );

    // POST: /transcribe/url
    transcribeResource
      .addResource('url')
      .addMethod(
        'POST',
        new LambdaIntegration(getSignedUrlFunction),
        commonAuthorizerProps
      );

    // GET: /transcribe/result/{jobName}
    transcribeResource
      .addResource('result')
      .addResource('{jobName}')
      .addMethod(
        'GET',
        new LambdaIntegration(getTranscriptionFunction),
        commonAuthorizerProps
      );

    // add Policy for Amplify User
    // grant access policy transcribe stream and translate
    props.idPool.authenticatedRole.attachInlinePolicy(
      new Policy(this, 'GrantAccessTranscribeStream', {
        statements: [
          new PolicyStatement({
            actions: ['transcribe:StartStreamTranscriptionWebSocket'],
            resources: ['*'],
          }),
        ],
      })
    );
  }
}
```

この中では、元となる音声データ用のバケットや、文字起こし処理および API エンドポイント、文字起こし結果を格納する S3 バケットを生成しています。

##### GenerativeAiUseCasesStack > Transcribe > Bucket リソース

[`Bucket`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_s3.Bucket.html) は文字起こし用の S3 バケットのリソースです。

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

```typescript:packages/cdk/lib/construct/transcribe.ts (抜粋)
    const audioBucket = new Bucket(this, 'AudioBucket', {
      encryption: BucketEncryption.S3_MANAGED,
      removalPolicy: RemovalPolicy.DESTROY,
      autoDeleteObjects: true,
      enforceSSL: true,
      blockPublicAccess: BlockPublicAccess.BLOCK_ALL,
    });
    audioBucket.addCorsRule({
      allowedOrigins: ['*'],
      allowedMethods: [HttpMethods.PUT],
      allowedHeaders: ['*'],
      exposedHeaders: [],
      maxAge: 3000,
    });

    const transcriptBucket = new Bucket(this, 'TranscriptBucket', {
      encryption: BucketEncryption.S3_MANAGED,
      removalPolicy: RemovalPolicy.DESTROY,
      autoDeleteObjects: true,
      enforceSSL: true,
      blockPublicAccess: BlockPublicAccess.BLOCK_ALL,
    });

```

ここでは、以下のリソースを生成しています。

- `AudioBucket` S3 バケット
  - S3 マネージドキーでの暗号化
  - スタック削除時はバケットを削除 (中に含まれるオブジェクトも削除)
  - SSL 接続を強制する
  - ブロックパブリックアクセスはブロック
  - [CORS 設定](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_s3.CorsRule.html)を許可する
    - すべてのオリジン
    - PUT リクエストを許可
    - すべての Access-Control-Request-Headers を許可
    - アクセスできるヘッダ情報はなし
    - ブラウザのキャッシュ時間は 50 分(3000 秒)
- `TranscriptBucket` S3 バケット
  - S3 マネージドキーでの暗号化
  - スタック削除時はバケットを削除 (中に含まれるオブジェクトも削除)
  - SSL 接続を強制する
  - ブロックパブリックアクセスはブロック

##### GenerativeAiUseCasesStack > Transcribe > NodejsFunction リソース

[`NodejsFunction`](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_lambda_nodejs.NodejsFunction.html) は文字起こし用の Lambda 関数です。

```typescript: packages/cdk/lib/construct/transcribe.ts (抜粋)
    const getSignedUrlFunction = new NodejsFunction(this, 'GetSignedUrl', {
      runtime: Runtime.NODEJS_LATEST,
      entry: './lambda/getFileUploadSignedUrl.ts',
      timeout: Duration.minutes(15),
      environment: {
        BUCKET_NAME: audioBucket.bucketName,
      },
    });
    audioBucket.grantWrite(getSignedUrlFunction);

    const startTranscriptionFunction = new NodejsFunction(
      this,
      'StartTranscription',
      {
        runtime: Runtime.NODEJS_LATEST,
        entry: './lambda/startTranscription.ts',
        timeout: Duration.minutes(15),
        environment: {
          TRANSCRIPT_BUCKET_NAME: transcriptBucket.bucketName,
        },
        initialPolicy: [
          new PolicyStatement({
            effect: Effect.ALLOW,
            actions: ['transcribe:*'],
            resources: ['*'],
          }),
        ],
      }
    );
    audioBucket.grantRead(startTranscriptionFunction);
    transcriptBucket.grantWrite(startTranscriptionFunction);

    const getTranscriptionFunction = new NodejsFunction(
      this,
      'GetTranscription',
      {
        runtime: Runtime.NODEJS_LATEST,
        entry: './lambda/getTranscription.ts',
        timeout: Duration.minutes(15),
        initialPolicy: [
          new PolicyStatement({
            effect: Effect.ALLOW,
            actions: ['transcribe:*'],
            resources: ['*'],
          }),
        ],
      }
    );
    transcriptBucket.grantRead(getTranscriptionFunction);
```

ここでは、以下のリソースを生成しています。

- `GetSignedUrl` 関数
  - ランタイムは NodeJS の最新版
  - ソースコードは[`packages/cdk/lambda/getFileUploadSignedUrl.ts`](https://github.com/aws-samples/generative-ai-use-cases-jp/blob/56583580fbc767c70ca451a09cc98ce7c299b998/packages/cdk/lambda/getFileUploadSignedUrl.ts)
  - タイムアウトは 15 分
  - 環境変数
    - `BUCKET_NAME`: `AudioBucket` S3 バケットのバケット名
  - 処理概要
    - オブジェクト[アップロード用の署名付き URL を取得](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/Package/-aws-sdk-s3-request-presigner/)
      - `Bucket`: 環境変数の `BUCKET_NAME`
      - `Key`: [ユニーク ID](https://www.npmjs.com/package/uuidv4) + `event.body.filename`
      - 有効期限は 1 時間 (3600 秒)
    - 戻り値にオブジェクトアップロード用の署名付き URL を設定して終了
- `StartTranscription` 関数
  - ランタイムは NodeJS の最新版
  - ソースコードは[`packages/cdk/lambda/startTranscription.ts`](https://github.com/aws-samples/generative-ai-use-cases-jp/blob/56583580fbc767c70ca451a09cc98ce7c299b998/packages/cdk/lambda/startTranscription.ts)
  - タイムアウトは 15 分
  - 環境変数
    - `TRANSCRIPT_BUCKET_NAME`: `TranscriptBucket` S3 バケットのバケット名
  - ロール
    - 全リソースへの `transcribe:*` アクションを許可
  - 処理概要
    - [文字起こしジョブの開始コマンド](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/transcribe/command/StartTranscriptionJobCommand/)を作成する
      - `IdentifyLanguage`: 固定値 `true`
      - `LanguageOptions`: 固定値 `['ja-JP', 'en-US']`
      - `Media`:
        - `MediaFileUri`: `event.body.audioUrl`
      - `TranscriptionJobName`: [ランダム ID](https://www.npmjs.com/package/uuid),
      - `Settings`:
        - `ShowSpeakerLabels`: `event.body.speakerLabel`
        - `MaxSpeakerLabels`: `event.body.speakerLabel` が設定されていれば `event.body.maxSpeakers` を設定
      - `OutputBucketName`: 環境変数の `TRANSCRIPT_BUCKET_NAME`
      - `Tags`:
        - `Key`: 固定値 `'userId'`
        - `Value`: オーソライザーの sub (ユーザごとのランダム ID) 値
    - コマンドを実行する
    - 文字起こしジョブ名を返却する
- `GetTranscription` 関数
  - ランタイムは NodeJS の最新版
  - ソースコードは[`packages/cdk/lambda/getTranscription.ts`](https://github.com/aws-samples/generative-ai-use-cases-jp/blob/56583580fbc767c70ca451a09cc98ce7c299b998/packages/cdk/lambda/getTranscription.ts)
  - タイムアウトは 15 分
  - ロール
    - 全リソースへの `transcribe:*` アクションを許可
  - 処理概要
    - [文字起こしジョブの情報取得コマンド](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/transcribe/command/GetTranscriptionJobCommand/)を作成する
      - `TranscriptionJobName`: パスパラメータの `jobName`
    - コマンドを実行する
    - 取得したジョブ情報の `Tag.userId` がログインユーザと異なる場合は 403 エラーを返却
    - ジョブステータスが `COMPLETED` の場合
      - ジョブ情報の `TranscriptFileUri` から 文字起こし結果が格納されている S3 バケット名とキー値を取得する
      - S3 バケットからオブジェクトを取得する
      - 文字起こし結果をフォーマットして返却
    - ジョブステータスが `COMPLETED` 以外の場合
      - ジョブステータスを返却する

##### GenerativeAiUseCasesStack > Transcribe > Lambda 関数を API に追加

続いて、Lambda 関数を `Api` リソースの API エンドポイントに追加します。

```typescript: packages/cdk/lib/construct/transcribe.ts (抜粋)
    // API Gateway
    const authorizer = new CognitoUserPoolsAuthorizer(this, 'Authorizer', {
      cognitoUserPools: [props.userPool],
    });

    const commonAuthorizerProps = {
      authorizationType: AuthorizationType.COGNITO,
      authorizer,
    };
    const transcribeResource = props.api.root.addResource('transcribe');

    // POST: /transcribe/start
    transcribeResource
      .addResource('start')
      .addMethod(
        'POST',
        new LambdaIntegration(startTranscriptionFunction),
        commonAuthorizerProps
      );

    // POST: /transcribe/url
    transcribeResource
      .addResource('url')
      .addMethod(
        'POST',
        new LambdaIntegration(getSignedUrlFunction),
        commonAuthorizerProps
      );

    // GET: /transcribe/result/{jobName}
    transcribeResource
      .addResource('result')
      .addResource('{jobName}')
      .addMethod(
        'GET',
        new LambdaIntegration(getTranscriptionFunction),
        commonAuthorizerProps
      );

    // add Policy for Amplify User
    // grant access policy transcribe stream and translate
    props.idPool.authenticatedRole.attachInlinePolicy(
      new Policy(this, 'GrantAccessTranscribeStream', {
        statements: [
          new PolicyStatement({
            actions: ['transcribe:StartStreamTranscriptionWebSocket'],
            resources: ['*'],
          }),
        ],
      })
    );
```

ここでは、以下のリソースを生成しています。

- [`Api`](https://qiita.com/siruko/items/5f249f11847f3829c18a#generativeaiusecasesstack--api-%E3%83%AA%E3%82%BD%E3%83%BC%E3%82%B9) リソースの API エンドポイントに上記関数の呼び出しを追加する
  - API GW オーソライザに [`Auth`](https://qiita.com/siruko/items/0c14040a1af132f42382#generativeaiusecasesstack--auth-%E3%83%AA%E3%82%BD%E3%83%BC%E3%82%B9) リソースの Cognito ユーザプールを指定
  - `/transcribe/url` (POST) => `GetSignedUrl` Lambda 関数を呼び出し
  - `/transcribe/start` (POST) => `StartTranscription` Lambda 関数を呼び出し
  - `/transcribe/result/{jobName}` (Get) => `GetTranscription` Lambda 関数を呼び出し

##### GenerativeAiUseCasesStack > Transcribe > Cognito 認証済ユーザへの権限追加

最後に、Cognito 認証済ユーザにトランスクリプト権限を追加します。

```typescript: packages/cdk/lib/construct/transcribe.ts (抜粋)
    // add Policy for Amplify User
    // grant access policy transcribe stream and translate
    props.idPool.authenticatedRole.attachInlinePolicy(
      new Policy(this, 'GrantAccessTranscribeStream', {
        statements: [
          new PolicyStatement({
            actions: ['transcribe:StartStreamTranscriptionWebSocket'],
            resources: ['*'],
          }),
        ],
      })
    );
```

ここでは、Cognito 認証済ユーザにトランスクリプト権限を追加しています。

- `GrantAccessTranscribeStream` ポリシー
  - すべてのリソースに対し `transcribe:StartStreamTranscriptionWebSocket` を許可

以上で `RagKnowledgeBase`, `UseCaseBuilder`, `Transcribe` リソースの解説は終了です。
これにて、`GenerativeAiUseCasesStack` スタックの解説が全て終わりました。
次回は最後のスタックである、GenU 内の `DashboardStack` スタックを解説したいと思います。

## (参考) GenU のバックエンド (CDK) 詳細解説投稿一覧

- [①AWS CDK のセットアップ](https://qiita.com/siruko/items/fd25fdcf89615cb85262)
- [②AWS CDK の動作確認](https://qiita.com/siruko/items/73169f986b4173e3d3a5)
- [③GenU の概要](https://qiita.com/siruko/items/625801b9e1847b305c1e)
- [④GenU CDK スタックの概要](https://qiita.com/siruko/items/8570ed43f7162ea4b907)
- [⑤CloudFrontWafStack スタックの解説](https://qiita.com/siruko/items/30439576ee7c63165d21)
- [⑥RagKnowledgeBaseStack スタックの解説](https://qiita.com/siruko/items/1223c9d22e73168a8809)
- [⑦WebSearchAgentStack スタックの解説](https://qiita.com/siruko/items/aef0a9599df60d47eb1e)
- [⑧GuardrailStack スタックの解説](https://qiita.com/siruko/items/d16e9fe27df4673d7554)
- [⑨GenerativeAiUseCasesStack > Auth スタックの解説](https://qiita.com/siruko/items/0c14040a1af132f42382)
- [⑩GenerativeAiUseCasesStack > Database, Api スタックの解説](https://qiita.com/siruko/items/5f249f11847f3829c18a)
- [⑪GenerativeAiUseCasesStack > CommonWebAcl, Web, Rag スタックの解説](https://qiita.com/siruko/items/1063daa2efe0b374a3fc)
- [⑫GenerativeAiUseCasesStack > RagKnowledgeBase, UseCaseBuilder, Transcribe スタックの解説](https://qiita.com/siruko/items/73d6006004caf8648594)
- [⑬DashBoard スタックの解説](https://qiita.com/siruko/items/73a75c7d0146d12ecbc3)
- [⑭GenU の Outputs の解説](https://qiita.com/siruko/items/afc14128a5b1a15ab69c)