Repository: awslabs/generative-ai-cdk-constructs (ts)

Issue:
Observability: Add pre-built dashboards for basic infrastructure and model metrics
### Describe the feature

When using a model from Bedrock or hosted in SageMaker, provide a pre-built CloudWatch dashboard that shows the essential infrastructure and model metrics. For Bedrock, this would include things like invocations, latency, and token count. For SageMaker we would have invocations and latency, and perhaps in future provide an extension to track token count. For multi-tenant use cases, we could also track per-tenant dimensions to help with cost allocation, to the extent that the services support this. 

Other components like vector databases would support both infrastructure metrics (e..g, CPU overhead on an OpenSearch collection) and metrics like number of retrieval requests.

### Use Case

As with any workload, observability is key to understanding workload behavior. In the case of GenAI, surfacing the basic metrics via CloudWatch dashboards and alarms helps the operator understand performance problems (e.g., unusually high invocation latency) and more general trends (average number of model requests).

### Proposed Solution

Provide L2 constructs for dashboards for major components, like models hosted in Bedrock or SageMaker, or vector databases in AOSS. The simplest usage might be:

`new BedrockModelDashboard(this, "ModelDashboard", {
  modelId: "anthropic.claude-3-haiku-20240307-v1:0",
})`

One potential drawback is that the native metrics for Bedrock don't support dimensions beyond model ID. If a single account is hosting multiple workloads in the same region, the Bedrock metrics would be aggregated across all workloads. If in future Bedrock offers more dimensions, we can take advantage of them. We can also consider more advanced constructs that provide their own way to meter things like token count at a more granular level.

### Other Information

I have a reference implementation available in a private repo, and plan to publish it soon. It includes pre-baked CloudWatch dashboards for Bedrock and AOSS.

### Acknowledgements

- [X] I may be able to implement this feature request
- [ ] This feature might incur a breaking change

Pull request:
feat(construct): add cloudwatch dashboard for bedrock
Fixes #634 

First iteration of the ticket mentioned above. Provide first a component level construct to help monitor metrics from Amazon Bedrock model usage through an Amazon CloudWatch dashboard

![Screenshot 2024-08-22 at 11 49 30 AM](https://github.com/user-attachments/assets/5402b512-3c1f-422d-8743-4203c07c81e4)

![Screenshot 2024-08-22 at 11 45 33 AM](https://github.com/user-attachments/assets/1f1b5a1e-eaca-4386-ae4a-481292931932)


---
By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of the project license.


Patch:
diff --git a/README.md b/README.md
index 7de634b..03e2811 100644
--- a/README.md
+++ b/README.md
@@ -107,6 +107,7 @@ The following constructs are available in the library:
 | [SageMaker model deployment (Custom)](./src/patterns/gen-ai/aws-model-deployment-sagemaker/README_custom_sagemaker_endpoint.md) | Deploy a foundation model from an S3 location to an Amazon SageMaker endpoint. | Amazon SageMaker |
 | [Content Generation](./src/patterns/gen-ai/aws-contentgen-appsync-lambda/README.md) | Generate images from text using Amazon titan-image-generator-v1 or stability.stable-diffusion-xl model.  | AWS Lambda, Amazon Bedrock, AWS AppSync |
 | [Web crawler](./src/patterns/gen-ai/aws-web-crawler/README.md) | Crawl websites and RSS feeds on a schedule and store changeset data in an Amazon Simple Storage Service bucket.  | AWS Lambda, AWS Batch, AWS Fargate, Amazon DynamoDB  |
+| [Amazon Bedrock Monitoring (Amazon CloudWatch Dashboard)](./src/patterns/gen-ai/aws-bedrock-cw-dashboard/README.md) | Amazon CloudWatch dashboard to monitor model usage from Amazon Bedrock. | Amazon CloudWatch |
 
 ### L2 Constructs
 
diff --git a/apidocs/README.md b/apidocs/README.md
index 04508b1..4ef71bd 100644
--- a/apidocs/README.md
+++ b/apidocs/README.md
@@ -20,6 +20,7 @@
 ## Classes
 
 - [BaseClass](classes/BaseClass.md)
+- [BedrockCwDashboard](classes/BedrockCwDashboard.md)
 - [ContainerImage](classes/ContainerImage.md)
 - [ContentGenerationAppSyncLambda](classes/ContentGenerationAppSyncLambda.md)
 - [CustomSageMakerEndpoint](classes/CustomSageMakerEndpoint.md)
@@ -39,6 +40,7 @@
 
 - [AsyncInferenceConfig](interfaces/AsyncInferenceConfig.md)
 - [BaseClassProps](interfaces/BaseClassProps.md)
+- [BedrockCwDashboardProps](interfaces/BedrockCwDashboardProps.md)
 - [ContainerImageConfig](interfaces/ContainerImageConfig.md)
 - [ContentGenerationAppSyncLambdaProps](interfaces/ContentGenerationAppSyncLambdaProps.md)
 - [CrawlerTarget](interfaces/CrawlerTarget.md)
@@ -51,6 +53,7 @@
 - [JumpStartSageMakerEndpointProps](interfaces/JumpStartSageMakerEndpointProps.md)
 - [LangchainLayerProps](interfaces/LangchainLayerProps.md)
 - [LangchainProps](interfaces/LangchainProps.md)
+- [ModelMonitoringProps](interfaces/ModelMonitoringProps.md)
 - [QaAppsyncOpensearchProps](interfaces/QaAppsyncOpensearchProps.md)
 - [RagAppsyncStepfnOpensearchProps](interfaces/RagAppsyncStepfnOpensearchProps.md)
 - [SummarizationAppsyncStepfnProps](interfaces/SummarizationAppsyncStepfnProps.md)
diff --git a/apidocs/classes/BedrockCwDashboard.md b/apidocs/classes/BedrockCwDashboard.md
new file mode 100644
index 0000000..0a84d8c
--- /dev/null
+++ b/apidocs/classes/BedrockCwDashboard.md
@@ -0,0 +1,153 @@
+[**@cdklabs/generative-ai-cdk-constructs**](../README.md) • **Docs**
+
+***
+
+[@cdklabs/generative-ai-cdk-constructs](../README.md) / BedrockCwDashboard
+
+# Class: BedrockCwDashboard
+
+The BedrockCwDashboard class.
+
+## Extends
+
+- `Construct`
+
+## Constructors
+
+### new BedrockCwDashboard()
+
+> **new BedrockCwDashboard**(`scope`, `id`, `props`): [`BedrockCwDashboard`](BedrockCwDashboard.md)
+
+Constructs a new instance of the BedrockCwDashboard class.
+
+#### Parameters
+
+• **scope**: `Construct`
+
+represents the scope for all the resources.
+
+• **id**: `string`
+
+this is a a scope-unique id.
+
+• **props**: [`BedrockCwDashboardProps`](../interfaces/BedrockCwDashboardProps.md)
+
+user provided props for the construct.
+
+#### Returns
+
+[`BedrockCwDashboard`](BedrockCwDashboard.md)
+
+#### Since
+
+0.0.0
+
+#### Overrides
+
+`Construct.constructor`
+
+## Properties
+
+### dashboard
+
+> `readonly` **dashboard**: `Dashboard`
+
+Returns the instance of CloudWatch dashboard used by the construct
+
+***
+
+### node
+
+> `readonly` **node**: `Node`
+
+The tree node.
+
+#### Inherited from
+
+`Construct.node`
+
+## Methods
+
+### addAllModelsMonitoring()
+
+> **addAllModelsMonitoring**(`props`): `void`
+
+#### Parameters
+
+• **props**: [`ModelMonitoringProps`](../interfaces/ModelMonitoringProps.md)
+
+#### Returns
+
+`void`
+
+***
+
+### addModelMonitoring()
+
+> **addModelMonitoring**(`modelName`, `modelId`, `props`): `void`
+
+#### Parameters
+
+• **modelName**: `string`
+
+• **modelId**: `string`
+
+• **props**: [`ModelMonitoringProps`](../interfaces/ModelMonitoringProps.md)
+
+#### Returns
+
+`void`
+
+***
+
+### toString()
+
+> **toString**(): `string`
+
+Returns a string representation of this construct.
+
+#### Returns
+
+`string`
+
+#### Inherited from
+
+`Construct.toString`
+
+***
+
+### isConstruct()
+
+> `static` **isConstruct**(`x`): `x is Construct`
+
+Checks if `x` is a construct.
+
+Use this method instead of `instanceof` to properly detect `Construct`
+instances, even when the construct library is symlinked.
+
+Explanation: in JavaScript, multiple copies of the `constructs` library on
+disk are seen as independent, completely different libraries. As a
+consequence, the class `Construct` in each copy of the `constructs` library
+is seen as a different class, and an instance of one class will not test as
+`instanceof` the other class. `npm install` will not create installations
+like this, but users may manually symlink construct libraries together or
+use a monorepo tool: in those cases, multiple copies of the `constructs`
+library can be accidentally installed, and `instanceof` will behave
+unpredictably. It is safest to avoid using `instanceof`, and using
+this type-testing method instead.
+
+#### Parameters
+
+• **x**: `any`
+
+Any object
+
+#### Returns
+
+`x is Construct`
+
+true if `x` is an object created from a class which extends `Construct`.
+
+#### Inherited from
+
+`Construct.isConstruct`
diff --git a/apidocs/interfaces/BedrockCwDashboardProps.md b/apidocs/interfaces/BedrockCwDashboardProps.md
new file mode 100644
index 0000000..83562fd
--- /dev/null
+++ b/apidocs/interfaces/BedrockCwDashboardProps.md
@@ -0,0 +1,40 @@
+[**@cdklabs/generative-ai-cdk-constructs**](../README.md) • **Docs**
+
+***
+
+[@cdklabs/generative-ai-cdk-constructs](../README.md) / BedrockCwDashboardProps
+
+# Interface: BedrockCwDashboardProps
+
+The properties for the BedrockCwDashboardProps class.
+
+## Properties
+
+### dashboardName?
+
+> `readonly` `optional` **dashboardName**: `string`
+
+Optional A name for the dashboard which will be created.
+If existingDashboard is defined, this value will be ignored.
+If not provided, the construct will create a new dashboard named 'BedrockMetricsDashboard'
+
+#### Default
+
+```ts
+- none
+```
+
+***
+
+### existingDashboard?
+
+> `readonly` `optional` **existingDashboard**: `Dashboard`
+
+Optional An existing dashboard where metrics will be added to.
+If not provided, the construct will create a new dashboard
+
+#### Default
+
+```ts
+- none
+```
diff --git a/apidocs/interfaces/ModelMonitoringProps.md b/apidocs/interfaces/ModelMonitoringProps.md
new file mode 100644
index 0000000..7c48f8e
--- /dev/null
+++ b/apidocs/interfaces/ModelMonitoringProps.md
@@ -0,0 +1,15 @@
+[**@cdklabs/generative-ai-cdk-constructs**](../README.md) • **Docs**
+
+***
+
+[@cdklabs/generative-ai-cdk-constructs](../README.md) / ModelMonitoringProps
+
+# Interface: ModelMonitoringProps
+
+The properties for the ModelMonitoringProps class.
+
+## Properties
+
+### period?
+
+> `readonly` `optional` **period**: `Duration`
diff --git a/src/index.ts b/src/index.ts
index 4f1b67e..db17a7c 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -28,3 +28,4 @@ export * from './common/props/DockerLambdaCustomProps';
 export * from './common/props/LangchainProps';
 export * from './common/base-class/base-class';
 export * from './common/base-class/construct-name-enum';
+export * from './patterns/gen-ai/aws-bedrock-cw-dashboard';
diff --git a/src/patterns/gen-ai/aws-bedrock-cw-dashboard/README.md b/src/patterns/gen-ai/aws-bedrock-cw-dashboard/README.md
new file mode 100644
index 0000000..3fcc062
--- /dev/null
+++ b/src/patterns/gen-ai/aws-bedrock-cw-dashboard/README.md
@@ -0,0 +1,214 @@
+# aws-bedrock-cw-dashboard
+
+<!--BEGIN STABILITY BANNER-->
+
+---
+
+![Stability: Experimental](https://img.shields.io/badge/stability-Experimental-important.svg?style=for-the-badge)
+
+> All classes are under active development and subject to non-backward compatible changes or removal in any
+> future version. These are not subject to the [Semantic Versioning](https://semver.org/) model.
+> This means that while you may use them, you may need to update your source code when upgrading to a newer version of this package.
+
+---
+
+<!--END STABILITY BANNER-->
+
+
+| **Language**                                                                                   | **Package**                             |
+| :----------------------------------------------------------------------------------------------- | ----------------------------------------- |
+| ![Typescript Logo](https://docs.aws.amazon.com/cdk/api/latest/img/typescript32.png) TypeScript | `@cdklabs/generative-ai-cdk-constructs` |
+| ![Python Logo](https://docs.aws.amazon.com/cdk/api/latest/img/python32.png) Python             | `cdklabs.generative_ai_cdk_constructs`  |
+
+## Table of contents
+
+- [aws-bedrock-cw-dashboard](#aws-qa-appsync-opensearch)
+  - [Table of contents](#table-of-contents)
+  - [Credits](#credits)
+  - [Overview](#overview)
+  - [Initializer](#initializer)
+  - [Pattern Construct Props](#pattern-construct-props)
+  - [Pattern Properties](#pattern-properties)
+  - [Methods](#methods)
+  - [Default properties](#default-properties)
+  - [Cost](#cost)
+  - [Security](#security)
+  - [Supported AWS Regions](#supported-aws-regions)
+  - [Quotas](#quotas)
+
+## Credits
+
+Thanks to @jimini55, @scoropeza, @PaulVincent707, @Ishanrpatel, @lowelljehu and @rddefauw for the initial version of this construct.
+
+## Overview
+
+This construct provides an Amazon CloudWatch dashboard to monitor metrics on Amazon Bedrock models usage. The specific list of metrics created by this construct is available [here](#default-properties).
+
+> **Note:** Native metrics for Amazon Bedrock don't support dimensions beyond model ID. If a single account is hosting multiple workloads in the same region, the Bedrock metrics would be aggregated across all workloads.
+
+Here is a minimal deployable pattern definition:
+
+TypeScript
+
+```typescript
+import { Construct } from 'constructs';
+import { Stack, StackProps, Aws } from 'aws-cdk-lib';
+import { BedrockCwDashboard } from '@cdklabs/generative-ai-cdk-constructs';
+
+const bddashboard = new BedrockCwDashboard(this, 'BedrockDashboardConstruct', {});
+
+// provides monitoring for a specific model
+bddashboard.addModelMonitoring('claude3haiku', 'anthropic.claude-3-haiku-20240307-v1:0', {});
+
+// provides monitoring of all models
+bddashboard.addAllModelsMonitoring({});
+```
+
+Optionally, you can also use the [Bedrock models](../../../cdk-lib/bedrock/models.ts) to access the modelId:
+
+```typescript
+
+import { bedrock, BedrockCwDashboard } from '@cdklabs/generative-ai-cdk-constructs';
+
+...
+
+// provides monitoring for a specific model
+bddashboard.addModelMonitoring(
+    'claude3haiku', 
+    bedrock.BedrockFoundationModel.ANTHROPIC_CLAUDE_HAIKU_V1_0.modelId, 
+    {}
+);
+
+...
+
+```
+
+Python
+
+```python
+from constructs import Construct
+from cdklabs.generative_ai_cdk_constructs import BedrockCwDashboard
+
+bddashboard = BedrockCwDashboard(self, 'BedrockDashboardConstruct')
+
+# provides monitoring for a specific model
+bddashboard.add_model_monitoring(
+    model_name: 'claude3haiku',
+    model_id: 'anthropic.claude-3-haiku-20240307-v1:0'
+)
+
+# provides monitoring of all models
+bddashboard.add_all_models_monitoring()
+```
+
+## Initializer
+
+```
+new BedrockCwDashboard(scope: Construct, id: string, props: BedrockCwDashboardProps)
+```
+
+Parameters
+
+- scope [Construct](https://docs.aws.amazon.com/cdk/api/v2/docs/constructs.Construct.html)
+- id string
+- props BedrockCwDashboardProps
+
+## Pattern Construct Props
+
+
+| **Name**                               | **Type**                                                                                                                                               | **Required**                                              | **Description**                                                                                                                                                                                                                                                                                                                                                                                               |
+| :--------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| existingDashboard               | [aws_cloudwatch.Dashboard](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_cloudwatch.Dashboard.html)                                | ![Optional](https://img.shields.io/badge/optional-4169E1) | Existing dashboard to be used by the construct. **Mutually exclusive** with ```dashboardName``` - only one should be specified.                                                                                                                                                                                                                                                           |
+| dashboardName | string | ![Optional](https://img.shields.io/badge/optional-4169E1) | A name for the dashboard which will be created. If not provided, the construct will create a new dashboard named 'BedrockMetricsDashboard'. **Mutually exclusive** with ```existingDashboard``` - only one should be specified.                                                                                                                                                                                                                                                            |
+
+## Pattern Properties
+
+
+| **Name**                     | **Type**                                                                                                                  | **Description**                                                                                                                                                                |
+| :----------------------------- | :-------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| dashboard                          | [aws_cloudwatch.Dashboard](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_cloudwatch.Dashboard.html)                                     | The CloudWatch Dashboard used by the construct (whether created by the construct or provided by the client)                                                                                     |
+
+## Methods
+
+### addModelMonitoring()
+
+Provide metrics for a specific model id in Bedrock
+
+@param {string} modelName - Model name as it will appear in the dashboard row widget.
+
+@param {string} modelId - Bedrock model id as defined in https://docs.aws.amazon.com/bedrock/latest/userguide/model-ids.html
+
+@param {ModelMonitoringProps} props - user provided props for the monitoring.
+
+### addAllModelsMonitoring()
+
+Add a new row to the dashboard providing metrics across all model ids in Bedrock 
+
+@param {ModelMonitoringProps} props - user provided props for the monitoring.
+
+## Default properties
+
+Out-of-the-box implementation of the construct without any override will set the following defaults:
+
+### Dashboard
+
+- Dashboard name is ```BedrockMetricsDashboard```
+
+### addModelMonitoring
+
+- Period (the period over which the specified statistic is applied) is set to one hour
+- The following metrics are displayed for the model specified:
+    - InputTokenCount
+    - OutputTokenCount
+    - InvocationLatency (min, max, average)
+    - Invocations (sample count)
+    - InvocationClientErrors
+
+### addAllModelsMonitoring
+
+- Period (the period over which the specified statistic is applied) is set to one hour
+- The following metrics are displayed for all models:
+    - InputTokenCount
+    - OutputTokenCount
+    - InvocationLatency (min, max, average)
+    - Invocations (sample count)
+    - InvocationClientErrors
+
+## Cost
+
+You are responsible for the cost of the AWS services used while running this construct.
+
+We recommend creating a budget through [AWS Cost Explorer](http://aws.amazon.com/aws-cost-management/aws-cost-explorer/) to help manage costs. Prices are subject to change. For full details, refer to the pricing webpage for each AWS service used in this solution:
+
+- [Amazon CloudWatch pricing](https://aws.amazon.com/cloudwatch/pricing/)
+
+## Security
+
+When you build systems on AWS infrastructure, security responsibilities are shared between you and AWS. This [shared responsibility](http://aws.amazon.com/compliance/shared-responsibility-model/) model reduces your operational burden because AWS operates, manages, and controls the components including the host operating system, virtualization layer, and physical security of the facilities in which the services operate. For more information about AWS security, visit [AWS Cloud Security](http://aws.amazon.com/security/).
+
+Optionnaly, you can provide existing resources to the constructs (marked optional in the construct pattern props). If you chose to do so, please refer to the official documentation on best practices to secure each service:
+
+- [Amazon CloudWatch](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/security.html)
+
+If you grant access to a user to your account where this construct is deployed, this user may access information stored by the construct (Amazon CloudWatch logs). To help secure your AWS resources, please follow the best practices for [AWS Identity and Access Management (IAM)](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html).
+
+AWS CloudTrail provides a number of security features to consider as you develop and implement your own security policies. Please follow the related best practices through the [official documentation](https://docs.aws.amazon.com/awscloudtrail/latest/userguide/best-practices-security.html).
+
+## Supported AWS Regions
+
+This solution depends uses the Amazon Bedrock and Amazon CloudWatch services, which are not currently available in all AWS Regions. You must launch this construct in an AWS Region where these services are available. For the most current availability of AWS services by Region, see the [AWS Regional Services List](https://aws.amazon.com/about-aws/global-infrastructure/regional-product-services/).
+
+> **Note**
+> You need to explicity enable access to models before they are available for use in Amazon Bedrock. Please follow the [Amazon Bedrock User Guide](https://docs.aws.amazon.com/bedrock/latest/userguide/model-access.html) for steps related to enabling model access.
+
+## Quotas
+
+Service quotas, also referred to as limits, are the maximum number of service resources or operations for your AWS account.
+
+Make sure you have sufficient quota for each of the services implemented in this solution. For more information, refer to [AWS service quotas](https://docs.aws.amazon.com/general/latest/gr/aws_service_limits.html).
+
+To view the service quotas for all AWS services in the documentation without switching pages, view the information in the [Service endpoints and quotas](https://docs.aws.amazon.com/general/latest/gr/aws-general.pdf#aws-service-information) page in the PDF instead.
+
+---
+
+&copy; Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
diff --git a/src/patterns/gen-ai/aws-bedrock-cw-dashboard/index.ts b/src/patterns/gen-ai/aws-bedrock-cw-dashboard/index.ts
new file mode 100644
index 0000000..d1de52a
--- /dev/null
+++ b/src/patterns/gen-ai/aws-bedrock-cw-dashboard/index.ts
@@ -0,0 +1,338 @@
+/**
+ *  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
+ *  with the License. A copy of the License is located at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES
+ *  OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions
+ *  and limitations under the License.
+ */
+import { Duration } from 'aws-cdk-lib';
+import {
+  Metric,
+  Dashboard,
+  GraphWidget,
+  SingleValueWidget,
+  Row,
+  Stats,
+  TextWidget,
+  Column,
+} from 'aws-cdk-lib/aws-cloudwatch';
+import { Construct } from 'constructs';
+
+/**
+ * The properties for the ModelMonitoringProps class.
+ */
+export interface ModelMonitoringProps {
+  /*
+    * Optional - The period over which the specified statistic is applied.
+    * @default - 1 hour
+    */
+  readonly period?: Duration;
+}
+
+/**
+ * The properties for the BedrockCwDashboardProps class.
+ */
+export interface BedrockCwDashboardProps {
+
+  /**
+   * Optional An existing dashboard where metrics will be added to.
+   * If not provided, the construct will create a new dashboard
+   *
+   * @default - none
+   */
+  readonly existingDashboard?: Dashboard;
+
+  /**
+   * Optional A name for the dashboard which will be created.
+   * If existingDashboard is defined, this value will be ignored.
+   * If not provided, the construct will create a new dashboard named 'BedrockMetricsDashboard'
+   *
+   * @default - none
+   */
+  readonly dashboardName?: string;
+}
+
+/**
+ * The BedrockCwDashboard class.
+ */
+export class BedrockCwDashboard extends Construct {
+
+  /**
+   * Returns the instance of CloudWatch dashboard used by the construct
+   */
+  public readonly dashboard: Dashboard;
+
+  /**
+   * Constructs a new instance of the BedrockCwDashboard class.
+   * @param {cdk.App} scope - represents the scope for all the resources.
+   * @param {string} id - this is a a scope-unique id.
+   * @param {BedrockCwDashboardProps} props - user provided props for the construct.
+   * @since 0.0.0
+   * @public
+   */
+  constructor(scope: Construct, id: string, props: BedrockCwDashboardProps) {
+    super(scope, id);
+
+    this.dashboard = props.existingDashboard ?? new Dashboard(this, 'BedrockMetricsDashboard', {
+      dashboardName: props.dashboardName ?? 'BedrockMetricsDashboard',
+    });
+  }
+
+  /* Provide metrics for a specific model id in Bedrock
+   * @param {string} modelName - Model name as it will appear in the dashboard row widget.
+   * @param {string} modelId - Bedrock model id as defined in https://docs.aws.amazon.com/bedrock/latest/userguide/model-ids.html
+   * @param {ModelMonitoringProps} props - user provided props for the monitoring.
+  */
+  public addModelMonitoring(modelName: string, modelId: string, props: ModelMonitoringProps) {
+
+    const period = props.period ?? Duration.hours(1);
+
+    const modelInputTokensMetric = new Metric({
+      namespace: 'AWS/Bedrock',
+      metricName: 'InputTokenCount',
+      dimensionsMap: {
+        ModelId: modelId,
+      },
+      statistic: Stats.SUM,
+      period: period,
+    });
+
+    const modelOutputTokensMetric = new Metric({
+      namespace: 'AWS/Bedrock',
+      metricName: 'OutputTokenCount',
+      dimensionsMap: {
+        ModelId: modelId,
+      },
+      statistic: Stats.SUM,
+      period: period,
+    });
+
+    const modelLatencyAvgMetric = new Metric({
+      namespace: 'AWS/Bedrock',
+      metricName: 'InvocationLatency',
+      dimensionsMap: {
+        ModelId: modelId,
+      },
+      statistic: Stats.AVERAGE,
+      period: period,
+    });
+
+    const modelLatencyMinMetric = new Metric({
+      namespace: 'AWS/Bedrock',
+      metricName: 'InvocationLatency',
+      dimensionsMap: {
+        ModelId: modelId,
+      },
+      statistic: Stats.MINIMUM,
+      period: period,
+    });
+
+    const modelLatencyMaxMetric = new Metric({
+      namespace: 'AWS/Bedrock',
+      metricName: 'InvocationLatency',
+      dimensionsMap: {
+        ModelId: modelId,
+      },
+      statistic: Stats.MAXIMUM,
+      period: period,
+    });
+
+    const modelInvocationsCountMetric = new Metric({
+      namespace: 'AWS/Bedrock',
+      metricName: 'Invocations',
+      dimensionsMap: {
+        ModelId: modelId,
+      },
+      statistic: Stats.SAMPLE_COUNT,
+      period: period,
+    });
+
+    const modelInvocationsClientErrorsMetric = new Metric({
+      namespace: 'AWS/Bedrock',
+      metricName: 'InvocationClientErrors',
+      dimensionsMap: {
+        ModelId: modelId,
+      },
+      statistic: Stats.SAMPLE_COUNT,
+      period: period,
+    });
+
+    this.dashboard.addWidgets(
+      new Row(
+        new TextWidget({
+          markdown: `# ${modelName}`,
+          width: 24,
+        }),
+      ),
+    );
+
+    this.dashboard.addWidgets(
+      new Row(
+        new SingleValueWidget({
+          title: 'Average Latency',
+          metrics: [modelLatencyAvgMetric],
+          width: 8,
+        }),
+        new SingleValueWidget({
+          title: 'Min Latency',
+          metrics: [modelLatencyMinMetric],
+          width: 8,
+        }),
+        new SingleValueWidget({
+          title: 'Max Latency',
+          metrics: [modelLatencyMaxMetric],
+          width: 8,
+        }),
+      ),
+    );
+
+    this.dashboard.addWidgets(
+      new Row(
+        new GraphWidget({
+          title: 'Input and Output Token Counts',
+          left: [modelInputTokensMetric],
+          right: [modelOutputTokensMetric],
+          period: period,
+          width: 12,
+          height: 10,
+        }),
+      ),
+    );
+
+    this.dashboard.addWidgets(
+      new Row(
+        new SingleValueWidget({
+          title: 'Invocations',
+          metrics: [modelInvocationsCountMetric],
+          width: 12,
+        }),
+        new SingleValueWidget({
+          title: 'Client Errors',
+          metrics: [modelInvocationsClientErrorsMetric],
+          width: 12,
+        }),
+      ),
+    );
+  }
+
+  /* Add a new row to the dashboard providing metrics across all model ids in Bedrock
+  * @param {ModelMonitoringProps} props - user provided props for the monitoring.
+  */
+  public addAllModelsMonitoring(props: ModelMonitoringProps) {
+
+    const period = props.period ?? Duration.hours(1);
+
+    // Metrics across all Model Ids
+    const inputTokensAllModelsMetric = new Metric({
+      namespace: 'AWS/Bedrock',
+      metricName: 'InputTokenCount',
+      statistic: Stats.SUM,
+      period: period,
+    });
+
+    const outputTokensAllModelsMetric = new Metric({
+      namespace: 'AWS/Bedrock',
+      metricName: 'OutputTokenCount',
+      statistic: Stats.SUM,
+      period: period,
+    });
+
+    const latencyAvgAllModelsMetric = new Metric({
+      namespace: 'AWS/Bedrock',
+      metricName: 'InvocationLatency',
+      statistic: Stats.AVERAGE,
+      period: period,
+    });
+
+    const latencyMinAllModelsMetric = new Metric({
+      namespace: 'AWS/Bedrock',
+      metricName: 'InvocationLatency',
+      statistic: Stats.MINIMUM,
+      period: period,
+    });
+
+    const latencyMaxAllModelsMetric = new Metric({
+      namespace: 'AWS/Bedrock',
+      metricName: 'InvocationLatency',
+      statistic: Stats.MAXIMUM,
+      period: period,
+    });
+
+    const invocationsCountAllModelsMetric = new Metric({
+      namespace: 'AWS/Bedrock',
+      metricName: 'Invocations',
+      statistic: Stats.SAMPLE_COUNT,
+      period: period,
+    });
+
+    const invocationsClientErrorsAllModelsMetric = new Metric({
+      namespace: 'AWS/Bedrock',
+      metricName: 'InvocationClientErrors',
+      statistic: Stats.SAMPLE_COUNT,
+      period: period,
+    });
+
+    this.dashboard.addWidgets(
+      new Row(
+        new TextWidget({
+          markdown: '# Metrics Across All Models',
+          width: 24,
+        }),
+      ),
+    );
+    this.dashboard.addWidgets(
+      new Row(
+        new SingleValueWidget({
+          title: 'Average Latency (All Models)',
+          metrics: [latencyAvgAllModelsMetric],
+          width: 8,
+        }),
+        new SingleValueWidget({
+          title: 'Min Latency (All Models)',
+          metrics: [latencyMinAllModelsMetric],
+          width: 8,
+        }),
+        new SingleValueWidget({
+          title: 'Max Latency (All Models)',
+          metrics: [latencyMaxAllModelsMetric],
+          width: 8,
+        }),
+      ),
+    );
+    this.dashboard.addWidgets(
+      new Row(
+        new Column(
+          new GraphWidget({
+            title: 'Input and Output Tokens (All Models)',
+            left: [inputTokensAllModelsMetric],
+            right: [outputTokensAllModelsMetric],
+            period: period,
+            width: 12,
+          }),
+        ),
+        new Column(
+          new Row(
+            new SingleValueWidget({
+              title: 'Invocations (All Models)',
+              metrics: [invocationsCountAllModelsMetric],
+              width: 12,
+            }),
+          ),
+          new Row(
+            new SingleValueWidget({
+              title: 'Client Errors (All Models)',
+              metrics: [invocationsClientErrorsAllModelsMetric],
+              width: 12,
+            }),
+          ),
+        ),
+      ),
+    );
+  }
+}
+


Test patch:
diff --git a/test/patterns/gen-ai/aws-bedrock-cw-dashboard/aws-bedrock-cw-dashboard.test.ts b/test/patterns/gen-ai/aws-bedrock-cw-dashboard/aws-bedrock-cw-dashboard.test.ts
new file mode 100644
index 0000000..de767a0
--- /dev/null
+++ b/test/patterns/gen-ai/aws-bedrock-cw-dashboard/aws-bedrock-cw-dashboard.test.ts
@@ -0,0 +1,141 @@
+/**
+ *  Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
+ *  with the License. A copy of the License is located at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES
+ *  OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions
+ *  and limitations under the License.
+ */
+import * as cdk from 'aws-cdk-lib';
+import { Template } from 'aws-cdk-lib/assertions';
+import {
+  Dashboard,
+} from 'aws-cdk-lib/aws-cloudwatch';
+import { AwsSolutionsChecks } from 'cdk-nag';
+import {
+  BedrockCwDashboard,
+} from '../../../../src/patterns/gen-ai/aws-bedrock-cw-dashboard';
+
+describe('CloudWatch Dashboard with default settings', () => {
+  let app: cdk.App;
+  let cwDsTestTemplate: Template;
+  let cwDsTestConstruct: BedrockCwDashboard;
+
+  afterAll(() => {
+    console.log('Test completed');
+  });
+
+  beforeAll(() => {
+    app = new cdk.App();
+    cdk.Aspects.of(app).add(new AwsSolutionsChecks());
+    const bdDshTestStack = new cdk.Stack(app, 'undefined', {
+      env: { account: cdk.Aws.ACCOUNT_ID, region: cdk.Aws.REGION },
+    });
+
+    cwDsTestConstruct = new BedrockCwDashboard(bdDshTestStack, 'test', {});
+    cwDsTestTemplate = Template.fromStack(bdDshTestStack);
+  });
+
+  test('Dashboard is not null', () => {
+    expect(cwDsTestConstruct.dashboard).not.toBeNull;
+  });
+
+  test('Dashboard count', () => {
+    cwDsTestTemplate.resourceCountIs('AWS::CloudWatch::Dashboard', 1);
+  });
+});
+
+describe('CloudWatch Dashboard with one model monitored', () => {
+  let app: cdk.App;
+  let cwDsTestTemplate: Template;
+  let cwDsTestConstruct: BedrockCwDashboard;
+  let modelName: string;
+  let modelId: string;
+  let defaultDashboardName: string;
+
+  afterAll(() => {
+    console.log('Test completed');
+  });
+
+  beforeAll(() => {
+    app = new cdk.App();
+    cdk.Aspects.of(app).add(new AwsSolutionsChecks());
+    const bdDshTestStack = new cdk.Stack(app, 'undefined', {
+      env: { account: cdk.Aws.ACCOUNT_ID, region: cdk.Aws.REGION },
+    });
+
+    modelName = 'claude3haiku';
+    modelId = 'anthropic.claude-3-haiku-20240307-v1:0';
+    defaultDashboardName = 'BedrockMetricsDashboard';
+
+    cwDsTestConstruct = new BedrockCwDashboard(bdDshTestStack, 'test', {});
+    cwDsTestConstruct.addModelMonitoring(
+      modelName,
+      modelId,
+      {},
+    );
+    cwDsTestTemplate = Template.fromStack(bdDshTestStack);
+  });
+
+  test('Dashboard is not null', () => {
+    expect(cwDsTestConstruct.dashboard).not.toBeNull;
+  });
+
+  test('Dashboard count', () => {
+    cwDsTestTemplate.resourceCountIs('AWS::CloudWatch::Dashboard', 1);
+  });
+
+  test('Dashboard Properties', () => {
+    cwDsTestTemplate.hasResourceProperties('AWS::CloudWatch::Dashboard', {
+      DashboardName: defaultDashboardName,
+    });
+  });
+});
+
+describe('CloudWatch Dashboard with existing dashboard', () => {
+  let app: cdk.App;
+  let cwDsTestTemplate: Template;
+  let cwDsTestConstruct: BedrockCwDashboard;
+  let dashboardName: string;
+
+  afterAll(() => {
+    console.log('Test completed');
+  });
+
+  beforeAll(() => {
+    app = new cdk.App();
+    cdk.Aspects.of(app).add(new AwsSolutionsChecks());
+    const bdDshTestStack = new cdk.Stack(app, 'undefined', {
+      env: { account: cdk.Aws.ACCOUNT_ID, region: cdk.Aws.REGION },
+    });
+
+    dashboardName = 'mydashboardname';
+
+    const existingDashboard = new Dashboard(bdDshTestStack, 'mydashboard', {
+      dashboardName: dashboardName,
+    });
+
+    cwDsTestConstruct = new BedrockCwDashboard(bdDshTestStack, 'test', {
+      existingDashboard: existingDashboard,
+    });
+    cwDsTestTemplate = Template.fromStack(bdDshTestStack);
+  });
+
+  test('Dashboard is not null', () => {
+    expect(cwDsTestConstruct.dashboard).not.toBeNull;
+  });
+
+  test('Dashboard count', () => {
+    cwDsTestTemplate.resourceCountIs('AWS::CloudWatch::Dashboard', 1);
+  });
+
+  test('Dashboard Properties', () => {
+    cwDsTestTemplate.hasResourceProperties('AWS::CloudWatch::Dashboard', {
+      DashboardName: dashboardName,
+    });
+  });
+});