diff --git a/apidocs/namespaces/bedrock/README.md b/apidocs/namespaces/bedrock/README.md index 1975a2fc..8d094266 100644 --- a/apidocs/namespaces/bedrock/README.md +++ b/apidocs/namespaces/bedrock/README.md @@ -54,6 +54,7 @@ - [KnowledgeBase](classes/KnowledgeBase.md) - [ParsingStategy](classes/ParsingStategy.md) - [Prompt](classes/Prompt.md) +- [PromptBase](classes/PromptBase.md) - [PromptVariant](classes/PromptVariant.md) - [PromptVersion](classes/PromptVersion.md) - [S3ApiSchema](classes/S3ApiSchema.md) @@ -96,6 +97,7 @@ - [KnowledgeBaseProps](interfaces/KnowledgeBaseProps.md) - [LambdaCustomTransformationProps](interfaces/LambdaCustomTransformationProps.md) - [PIIFilter](interfaces/PIIFilter.md) +- [PromptAttributes](interfaces/PromptAttributes.md) - [PromptConfiguration](interfaces/PromptConfiguration.md) - [PromptOverrideConfiguration](interfaces/PromptOverrideConfiguration.md) - [PromptProps](interfaces/PromptProps.md) diff --git a/apidocs/namespaces/bedrock/classes/Prompt.md b/apidocs/namespaces/bedrock/classes/Prompt.md index 1b059f2a..c54a0a08 100644 --- a/apidocs/namespaces/bedrock/classes/Prompt.md +++ b/apidocs/namespaces/bedrock/classes/Prompt.md @@ -50,12 +50,26 @@ https://docs.aws.amazon.com/bedrock/latest/userguide/prompt-management.html ## Properties -### encryptionKey? +### \_hash -> `readonly` `optional` **encryptionKey**: `IKey` +> `protected` `readonly` **\_hash**: `string` + +**`Internal`** + +The computed hash of the prompt properties. + +*** + +### kmsKey? + +> `readonly` `optional` **kmsKey**: `IKey` The KMS key that the prompt is encrypted with. +#### Implementation of + +[`IPrompt`](../interfaces/IPrompt.md).[`kmsKey`](../interfaces/IPrompt.md#kmskey) + *** ### node @@ -114,6 +128,18 @@ The name of the prompt. *** +### promptVersion + +> **promptVersion**: `string` + +The version of the prompt. + +#### Implementation of + +[`IPrompt`](../interfaces/IPrompt.md).[`promptVersion`](../interfaces/IPrompt.md#promptversion) + +*** + ### variants > `readonly` **variants**: [`PromptVariant`](PromptVariant.md)[] @@ -140,7 +166,7 @@ Adds a prompt variant. ### createVersion() -> **createVersion**(`description`?): `void` +> **createVersion**(`description`?): `string` Creates a prompt version, a static snapshot of your prompt that can be deployed to production. @@ -151,7 +177,7 @@ deployed to production. #### Returns -`void` +`string` *** @@ -171,13 +197,17 @@ Returns a string representation of this construct. *** -### fromPromptArn() +### fromPromptAttributes() -> `static` **fromPromptArn**(`promptArn`): [`IPrompt`](../interfaces/IPrompt.md) +> `static` **fromPromptAttributes**(`scope`, `id`, `attrs`): [`IPrompt`](../interfaces/IPrompt.md) #### Parameters -• **promptArn**: `string` +• **scope**: `Construct` + +• **id**: `string` + +• **attrs**: [`PromptAttributes`](../interfaces/PromptAttributes.md) #### Returns diff --git a/apidocs/namespaces/bedrock/classes/PromptBase.md b/apidocs/namespaces/bedrock/classes/PromptBase.md new file mode 100644 index 00000000..e6ab6dcb --- /dev/null +++ b/apidocs/namespaces/bedrock/classes/PromptBase.md @@ -0,0 +1,404 @@ +[**@cdklabs/generative-ai-cdk-constructs**](../../../README.md) • **Docs** + +*** + +[@cdklabs/generative-ai-cdk-constructs](../../../README.md) / [bedrock](../README.md) / PromptBase + +# Class: `abstract` PromptBase + +Abstract base class for a Prompt. +Contains methods and attributes valid for Promtps either created with CDK or imported. + +## Extends + +- `Resource` + +## Implements + +- [`IPrompt`](../interfaces/IPrompt.md) + +## Constructors + +### new PromptBase() + +> **new PromptBase**(`scope`, `id`, `props`?): [`PromptBase`](PromptBase.md) + +#### Parameters + +• **scope**: `Construct` + +• **id**: `string` + +• **props?**: `ResourceProps` + +#### Returns + +[`PromptBase`](PromptBase.md) + +#### Inherited from + +`Resource.constructor` + +## Properties + +### env + +> `readonly` **env**: `ResourceEnvironment` + +The environment this resource belongs to. +For resources that are created and managed by the CDK +(generally, those created by creating new class instances like Role, Bucket, etc.), +this is always the same as the environment of the stack they belong to; +however, for imported resources +(those obtained from static methods like fromRoleArn, fromBucketName, etc.), +that might be different than the stack they were imported into. + +#### Inherited from + +`Resource.env` + +*** + +### kmsKey? + +> `abstract` `readonly` `optional` **kmsKey**: `IKey` + +Optional KMS encryption key associated with this prompt. + +#### Implementation of + +[`IPrompt`](../interfaces/IPrompt.md).[`kmsKey`](../interfaces/IPrompt.md#kmskey) + +*** + +### node + +> `readonly` **node**: `Node` + +The tree node. + +#### Inherited from + +`Resource.node` + +*** + +### physicalName + +> `protected` `readonly` **physicalName**: `string` + +Returns a string-encoded token that resolves to the physical name that +should be passed to the CloudFormation resource. + +This value will resolve to one of the following: +- a concrete value (e.g. `"my-awesome-bucket"`) +- `undefined`, when a name should be generated by CloudFormation +- a concrete name generated automatically during synthesis, in + cross-environment scenarios. + +#### Inherited from + +`Resource.physicalName` + +*** + +### promptArn + +> `abstract` `readonly` **promptArn**: `string` + +The ARN of the prompt. + +#### Example + +```ts +"arn:aws:bedrock:us-east-1:123456789012:prompt/PROMPT12345" +``` + +#### Implementation of + +[`IPrompt`](../interfaces/IPrompt.md).[`promptArn`](../interfaces/IPrompt.md#promptarn) + +*** + +### promptId + +> `abstract` `readonly` **promptId**: `string` + +The ID of the prompt. + +#### Example + +```ts +"PROMPT12345" +``` + +#### Implementation of + +[`IPrompt`](../interfaces/IPrompt.md).[`promptId`](../interfaces/IPrompt.md#promptid) + +*** + +### promptVersion + +> `abstract` **promptVersion**: `string` + +The version of the prompt. + +#### Default + +```ts +- "DRAFT" +``` + +#### Implementation of + +[`IPrompt`](../interfaces/IPrompt.md).[`promptVersion`](../interfaces/IPrompt.md#promptversion) + +*** + +### stack + +> `readonly` **stack**: `Stack` + +The stack in which this resource is defined. + +#### Inherited from + +`Resource.stack` + +## Methods + +### \_enableCrossEnvironment() + +> **\_enableCrossEnvironment**(): `void` + +**`Internal`** + +Called when this resource is referenced across environments +(account/region) to order to request that a physical name will be generated +for this resource during synthesis, so the resource can be referenced +through its absolute name/arn. + +#### Returns + +`void` + +#### Inherited from + +`Resource._enableCrossEnvironment` + +*** + +### applyRemovalPolicy() + +> **applyRemovalPolicy**(`policy`): `void` + +Apply the given removal policy to this resource + +The Removal Policy controls what happens to this resource when it stops +being managed by CloudFormation, either because you've removed it from the +CDK application or because you've made a change that requires the resource +to be replaced. + +The resource can be deleted (`RemovalPolicy.DESTROY`), or left in your AWS +account for data recovery and cleanup later (`RemovalPolicy.RETAIN`). + +#### Parameters + +• **policy**: `RemovalPolicy` + +#### Returns + +`void` + +#### Inherited from + +`Resource.applyRemovalPolicy` + +*** + +### generatePhysicalName() + +> `protected` **generatePhysicalName**(): `string` + +#### Returns + +`string` + +#### Inherited from + +`Resource.generatePhysicalName` + +*** + +### getResourceArnAttribute() + +> `protected` **getResourceArnAttribute**(`arnAttr`, `arnComponents`): `string` + +Returns an environment-sensitive token that should be used for the +resource's "ARN" attribute (e.g. `bucket.bucketArn`). + +Normally, this token will resolve to `arnAttr`, but if the resource is +referenced across environments, `arnComponents` will be used to synthesize +a concrete ARN with the resource's physical name. Make sure to reference +`this.physicalName` in `arnComponents`. + +#### Parameters + +• **arnAttr**: `string` + +The CFN attribute which resolves to the ARN of the resource. +Commonly it will be called "Arn" (e.g. `resource.attrArn`), but sometimes +it's the CFN resource's `ref`. + +• **arnComponents**: `ArnComponents` + +The format of the ARN of this resource. You must +reference `this.physicalName` somewhere within the ARN in order for +cross-environment references to work. + +#### Returns + +`string` + +#### Inherited from + +`Resource.getResourceArnAttribute` + +*** + +### getResourceNameAttribute() + +> `protected` **getResourceNameAttribute**(`nameAttr`): `string` + +Returns an environment-sensitive token that should be used for the +resource's "name" attribute (e.g. `bucket.bucketName`). + +Normally, this token will resolve to `nameAttr`, but if the resource is +referenced across environments, it will be resolved to `this.physicalName`, +which will be a concrete name. + +#### Parameters + +• **nameAttr**: `string` + +The CFN attribute which resolves to the resource's name. +Commonly this is the resource's `ref`. + +#### Returns + +`string` + +#### Inherited from + +`Resource.getResourceNameAttribute` + +*** + +### grantGet() + +> **grantGet**(`grantee`): `Grant` + +Grant the given identity permissions to get the prompt. + +#### Parameters + +• **grantee**: `IGrantable` + +#### Returns + +`Grant` + +*** + +### toString() + +> **toString**(): `string` + +Returns a string representation of this construct. + +#### Returns + +`string` + +#### Inherited from + +`Resource.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 + +`Resource.isConstruct` + +*** + +### isOwnedResource() + +> `static` **isOwnedResource**(`construct`): `boolean` + +Returns true if the construct was created by CDK, and false otherwise + +#### Parameters + +• **construct**: `IConstruct` + +#### Returns + +`boolean` + +#### Inherited from + +`Resource.isOwnedResource` + +*** + +### isResource() + +> `static` **isResource**(`construct`): `construct is Resource` + +Check whether the given construct is a Resource + +#### Parameters + +• **construct**: `IConstruct` + +#### Returns + +`construct is Resource` + +#### Inherited from + +`Resource.isResource` diff --git a/apidocs/namespaces/bedrock/interfaces/IPrompt.md b/apidocs/namespaces/bedrock/interfaces/IPrompt.md index 847df574..b10602a0 100644 --- a/apidocs/namespaces/bedrock/interfaces/IPrompt.md +++ b/apidocs/namespaces/bedrock/interfaces/IPrompt.md @@ -10,6 +10,14 @@ Represents a Prompt, either created with CDK or imported. ## Properties +### kmsKey? + +> `readonly` `optional` **kmsKey**: `IKey` + +Optional KMS encryption key associated with this prompt. + +*** + ### promptArn > `readonly` **promptArn**: `string` @@ -35,3 +43,17 @@ The ID of the prompt. ```ts "PROMPT12345" ``` + +*** + +### promptVersion + +> **promptVersion**: `string` + +The version of the prompt. + +#### Default + +```ts +- "DRAFT" +``` diff --git a/apidocs/namespaces/bedrock/interfaces/PromptAttributes.md b/apidocs/namespaces/bedrock/interfaces/PromptAttributes.md new file mode 100644 index 00000000..8acc87a3 --- /dev/null +++ b/apidocs/namespaces/bedrock/interfaces/PromptAttributes.md @@ -0,0 +1,47 @@ +[**@cdklabs/generative-ai-cdk-constructs**](../../../README.md) • **Docs** + +*** + +[@cdklabs/generative-ai-cdk-constructs](../../../README.md) / [bedrock](../README.md) / PromptAttributes + +# Interface: PromptAttributes + +*************************************************************************** + ATTRS FOR IMPORTED CONSTRUCT +*************************************************************************** + +## Properties + +### kmsKey? + +> `readonly` `optional` **kmsKey**: `IKey` + +Optional KMS encryption key associated with this prompt. + +*** + +### promptArn + +> `readonly` **promptArn**: `string` + +The ARN of the prompt. + +#### Example + +```ts +"arn:aws:bedrock:us-east-1:123456789012:prompt/PROMPT12345" +``` + +*** + +### promptVersion? + +> `readonly` `optional` **promptVersion**: `string` + +The version of the prompt. + +#### Default + +```ts +- "DRAFT" +``` diff --git a/apidocs/namespaces/bedrock/interfaces/PromptProps.md b/apidocs/namespaces/bedrock/interfaces/PromptProps.md index 9f9dd5b2..c7242333 100644 --- a/apidocs/namespaces/bedrock/interfaces/PromptProps.md +++ b/apidocs/namespaces/bedrock/interfaces/PromptProps.md @@ -6,6 +6,10 @@ # Interface: PromptProps +*************************************************************************** + PROPS FOR NEW CONSTRUCT +*************************************************************************** + ## Properties ### defaultVariant? @@ -36,9 +40,9 @@ A description of what the prompt does. *** -### encryptionKey? +### kmsKey? -> `readonly` `optional` **encryptionKey**: `IKey` +> `readonly` `optional` **kmsKey**: `IKey` The KMS key that the prompt is encrypted with. diff --git a/apidocs/namespaces/bedrock/interfaces/TextPromptVariantProps.md b/apidocs/namespaces/bedrock/interfaces/TextPromptVariantProps.md index 2e8c711c..b3b6f8e2 100644 --- a/apidocs/namespaces/bedrock/interfaces/TextPromptVariantProps.md +++ b/apidocs/namespaces/bedrock/interfaces/TextPromptVariantProps.md @@ -33,11 +33,20 @@ model, a custom model, or a provisioned model. *** -### templateConfiguration? +### promptText -> `readonly` `optional` **templateConfiguration**: `TextPromptTemplateConfigurationProperty` +> `readonly` **promptText**: `string` -Template Configuration for the text prompt +The text prompt. Variables are used by encolsing its name with double curly braces +as in `{{variable_name}}`. + +*** + +### promptVariables + +> `readonly` **promptVariables**: `string`[] + +The variables in the prompt template that can be filled in at runtime. *** diff --git a/src/cdk-lib/bedrock/README.md b/src/cdk-lib/bedrock/README.md index 1c933832..34ac28a3 100644 --- a/src/cdk-lib/bedrock/README.md +++ b/src/cdk-lib/bedrock/README.md @@ -1100,14 +1100,16 @@ const importedGuardrail = bedrock.Guardrail.fromGuardrailAttributes(stack, "Test }); // Importing Guardrails created through the L1 CDK CfnGuardrail construct -const cfnGuardrail = new CfnGuardrail(this, 'MyCfnGuardrail', { - blockedInputMessaging: 'blockedInputMessaging', - blockedOutputsMessaging: 'blockedOutputsMessaging', - name: 'namemycfnguardrails', +const cfnGuardrail = new CfnGuardrail(this, "MyCfnGuardrail", { + blockedInputMessaging: "blockedInputMessaging", + blockedOutputsMessaging: "blockedOutputsMessaging", + name: "namemycfnguardrails", wordPolicyConfig: { - wordsConfig: [{ - text: 'drugs', - }], + wordsConfig: [ + { + text: "drugs", + }, + ], }, }); @@ -1219,10 +1221,8 @@ const claudeModel = BedrockFoundationModel.ANTHROPIC_CLAUDE_SONNET_V1_0.asIModel const variant1 = PromptVariant.text({ variantName: "variant1", model: claudeModel, - templateConfiguration: { - inputVariables: [{ name: "topic" }], - text: "This is my first text prompt. Please summarize our conversation on: {{topic}}.", - }, + promptVariables: ["topic"], + promptText: "This is my first text prompt. Please summarize our conversation on: {{topic}}.", inferenceConfiguration: { temperature: 1.0, topP: 0.999, @@ -1258,10 +1258,8 @@ Example of `PromptVariant`: const variant2 = PromptVariant.text({ variantName: "variant2", model: claudeModel, - templateConfiguration: { - inputVariables: [{ name: "topic" }], - text: "This is my second text prompt. Please summarize our conversation on: {{topic}}.", - }, + promptVariables: [ "topic" ], + promptText: "This is my second text prompt. Please summarize our conversation on: {{topic}}.", inferenceConfiguration: { temperature: 0.5, topP: 0.999, @@ -1282,7 +1280,8 @@ prompt and update your application with the most appropriate version for your use-case. You can create a Prompt version by using the `PromptVersion` class or by using the `.createVersion(..)` -on a `Prompt` object. +on a `Prompt` object. It is recommended to use the `.createVersion(..)` method. It uses a hash based mechanism +to update the version whenever a certain configuration property changes. **TypeScript** diff --git a/src/cdk-lib/bedrock/index.ts b/src/cdk-lib/bedrock/index.ts index e4b07b47..721a97e1 100644 --- a/src/cdk-lib/bedrock/index.ts +++ b/src/cdk-lib/bedrock/index.ts @@ -19,8 +19,8 @@ export * from './api-schema'; export * from './guardrails/guardrail-filters'; export * from './guardrails/guardrails'; export * from './models'; -export * from './prompt'; -export * from './prompt-version'; +export * from './prompts/prompt'; +export * from './prompts/prompt-version'; export * from './data-sources/base-data-source'; export * from './data-sources/chunking'; export * from './data-sources/parsing'; diff --git a/src/cdk-lib/bedrock/prompt-version.ts b/src/cdk-lib/bedrock/prompts/prompt-version.ts similarity index 100% rename from src/cdk-lib/bedrock/prompt-version.ts rename to src/cdk-lib/bedrock/prompts/prompt-version.ts diff --git a/src/cdk-lib/bedrock/prompt.ts b/src/cdk-lib/bedrock/prompts/prompt.ts similarity index 63% rename from src/cdk-lib/bedrock/prompt.ts rename to src/cdk-lib/bedrock/prompts/prompt.ts index 1d4a4593..754e88a2 100644 --- a/src/cdk-lib/bedrock/prompt.ts +++ b/src/cdk-lib/bedrock/prompts/prompt.ts @@ -11,9 +11,11 @@ * and limitations under the License. */ -import { Arn, ArnFormat, aws_kms as kms, Lazy, aws_bedrock as bedrock } from 'aws-cdk-lib'; +import { Arn, ArnFormat, aws_kms as kms, Lazy, aws_bedrock as bedrock, Resource } from 'aws-cdk-lib'; import { IModel } from 'aws-cdk-lib/aws-bedrock'; +import { Grant, IGrantable } from 'aws-cdk-lib/aws-iam'; import { IKey } from 'aws-cdk-lib/aws-kms'; +import { md5hash } from 'aws-cdk-lib/core/lib/helpers-internal'; import { Construct } from 'constructs'; export enum PromptTemplateType { @@ -37,14 +39,17 @@ export interface TextPromptVariantProps extends CommonPromptVariantProps { * Inference configuration for the Text Prompt */ readonly inferenceConfiguration?: bedrock.CfnPrompt.PromptModelInferenceConfigurationProperty; - /** - * Template Configuration for the text prompt + * The variables in the prompt template that can be filled in at runtime. + */ + readonly promptVariables: string[]; + /** + * The text prompt. Variables are used by encolsing its name with double curly braces + * as in `{{variable_name}}`. */ - readonly templateConfiguration?: bedrock.CfnPrompt.TextPromptTemplateConfigurationProperty; + readonly promptText: string; } - /** * Variants are specific sets of inputs that guide FMs on Amazon Bedrock to * generate an appropriate response or output for a given task or instruction. @@ -66,7 +71,12 @@ export abstract class PromptVariant { text: { ...props.inferenceConfiguration }, }, templateConfiguration: { - text: { ...props.templateConfiguration }, + text: { + inputVariables: props.promptVariables.flatMap((variable: string) => { + return { name: variable }; + }), + text: props.promptText, + }, }, }; } @@ -97,10 +107,12 @@ export abstract class PromptVariant { // ------------------------------------------------------ // Constructor // ------------------------------------------------------ - protected constructor() { } - + protected constructor() {} } +/****************************************************************************** + * COMMON + *****************************************************************************/ /** * Represents a Prompt, either created with CDK or imported. */ @@ -115,8 +127,43 @@ export interface IPrompt { * @example "PROMPT12345" */ readonly promptId: string; + /** + * Optional KMS encryption key associated with this prompt. + */ + readonly kmsKey?: IKey; + /** + * The version of the prompt. + * @default - "DRAFT" + */ + promptVersion: string; } +/** + * Abstract base class for a Prompt. + * Contains methods and attributes valid for Promtps either created with CDK or imported. + */ +export abstract class PromptBase extends Resource implements IPrompt { + public abstract readonly promptArn: string; + public abstract readonly promptId: string; + public abstract readonly kmsKey?: IKey; + public abstract promptVersion: string; + + /** + * Grant the given identity permissions to get the prompt. + */ + public grantGet(grantee: IGrantable): Grant { + return Grant.addToPrincipal({ + grantee, + resourceArns: [this.promptArn], + actions: ['bedrock:GetPrompt'], + scope: this, + }); + } +} + +/****************************************************************************** + * PROPS FOR NEW CONSTRUCT + *****************************************************************************/ export interface PromptProps { /** @@ -132,7 +179,7 @@ export interface PromptProps { * The KMS key that the prompt is encrypted with. * @default - AWS owned and managed key. */ - readonly encryptionKey?: kms.IKey; + readonly kmsKey?: kms.IKey; /** * The Prompt Variant that will be used by default. * @default - No default variant provided. @@ -144,9 +191,31 @@ export interface PromptProps { * variant for your use case. Maximum of 3 variants. */ readonly variants?: PromptVariant[]; +} +/****************************************************************************** + * ATTRS FOR IMPORTED CONSTRUCT + *****************************************************************************/ +export interface PromptAttributes { + /** + * The ARN of the prompt. + * @example "arn:aws:bedrock:us-east-1:123456789012:prompt/PROMPT12345" + */ + readonly promptArn: string; + /** + * Optional KMS encryption key associated with this prompt. + */ + readonly kmsKey?: IKey; + /** + * The version of the prompt. + * @default - "DRAFT" + */ + readonly promptVersion?: string; } +/****************************************************************************** + * NEW CONSTRUCT DEFINITION + *****************************************************************************/ /** * Prompts are a specific set of inputs that guide FMs on Amazon Bedrock to * generate an appropriate response or output for a given task or instruction. @@ -158,12 +227,16 @@ export class Prompt extends Construct implements IPrompt { // ------------------------------------------------------ // Import Methods // ------------------------------------------------------ - public static fromPromptArn(promptArn: string): IPrompt { - const formattedArn = Arn.split(promptArn, ArnFormat.SLASH_RESOURCE_NAME); - return { - promptArn: promptArn, - promptId: formattedArn.resourceName!, - }; + public static fromPromptAttributes(scope: Construct, id: string, attrs: PromptAttributes): IPrompt { + const formattedArn = Arn.split(attrs.promptArn, ArnFormat.SLASH_RESOURCE_NAME); + class Import extends PromptBase { + public readonly promptArn = attrs.promptArn; + public readonly promptId = formattedArn.resourceName!; + public readonly promptVersion = attrs.promptVersion ?? 'DRAFT'; + public readonly kmsKey = attrs.kmsKey; + } + + return new Import(scope, id); } // ------------------------------------------------------ // Attributes @@ -175,7 +248,7 @@ export class Prompt extends Construct implements IPrompt { /** * The KMS key that the prompt is encrypted with. */ - public readonly encryptionKey?: IKey; + public readonly kmsKey?: IKey; /** * The ARN of the prompt. * @example "arn:aws:bedrock:us-east-1:123456789012:prompt/PROMPT12345" @@ -186,10 +259,19 @@ export class Prompt extends Construct implements IPrompt { * @example "PROMPT12345" */ public readonly promptId: string; + /** + * The version of the prompt. + */ + public promptVersion: string; /** * The variants of the prompt. */ readonly variants: PromptVariant[]; + /** + * The computed hash of the prompt properties. + * @internal + */ + protected readonly _hash: string; /** * L1 resource */ @@ -204,7 +286,7 @@ export class Prompt extends Construct implements IPrompt { // Set properties or defaults // ------------------------------------------------------ this.promptName = props.promptName; - this.encryptionKey = props.encryptionKey; + this.kmsKey = props.kmsKey; this.variants = props.variants ?? []; // ------------------------------------------------------ @@ -214,20 +296,29 @@ export class Prompt extends Construct implements IPrompt { this.node.addValidation({ validate: () => this.validatePromptVariants() }); // ------------------------------------------------------ - // L1 Instantiation + // CFN Props - With Lazy support // ------------------------------------------------------ - this._resource = new bedrock.CfnPrompt(this, 'Prompt', { - customerEncryptionKeyArn: this.encryptionKey?.keyArn, + let cfnProps: bedrock.CfnPromptProps = { + customerEncryptionKeyArn: this.kmsKey?.keyArn, defaultVariant: props.defaultVariant?.name, description: props.description, name: props.promptName, variants: Lazy.any({ - produce: () => (this.variants), + produce: () => this.variants, }), - }); + }; + + // Hash calculation useful for versioning of the guardrail + this._hash = md5hash(JSON.stringify(cfnProps)); + + // ------------------------------------------------------ + // L1 Instantiation + // ------------------------------------------------------ + this._resource = new bedrock.CfnPrompt(this, 'Prompt', cfnProps); this.promptArn = this._resource.attrArn; this.promptId = this._resource.attrId; + this.promptVersion = this._resource.attrVersion; } // ------------------------------------------------------ @@ -242,7 +333,9 @@ export class Prompt extends Construct implements IPrompt { const matchesPattern = /^([0-9a-zA-Z][_-]?){1,100}$/.test(this.promptName); if (!matchesPattern) { - errors.push('Valid characters are a-z, A-Z, 0-9, _ (underscore) and - (hyphen). And must not begin with a hyphen'); + errors.push( + 'Valid characters are a-z, A-Z, 0-9, _ (underscore) and - (hyphen). And must not begin with a hyphen', + ); } if (errors.length > 0) { errors.unshift(`Invalid prompt name (value: ${this.promptName})`); @@ -256,7 +349,9 @@ export class Prompt extends Construct implements IPrompt { private validatePromptVariants() { const errors: string[] = []; if (this.variants.length > 3) { - errors.push(`Error: Too many variants specified. The maximum allowed is 3, but you have provided ${this.variants.length} variants.`); + errors.push( + `Error: Too many variants specified. The maximum allowed is 3, but you have provided ${this.variants.length} variants.`, + ); } return errors; } @@ -268,11 +363,13 @@ export class Prompt extends Construct implements IPrompt { * Creates a prompt version, a static snapshot of your prompt that can be * deployed to production. */ - public createVersion(description?: string) { - new bedrock.CfnPromptVersion(this, `PromptVersion-${description}`, { + public createVersion(description?: string): string { + const version = new bedrock.CfnPromptVersion(this, `PromptVersion-${this._hash}`, { promptArn: this.promptArn, description, }); + this.promptVersion = version.attrVersion; + return this.promptVersion; } /** @@ -281,4 +378,4 @@ export class Prompt extends Construct implements IPrompt { public addVariant(variant: PromptVariant) { this.variants.push(variant); } -} \ No newline at end of file +} diff --git a/test/cdk-lib/bedrock/prompts.test.ts b/test/cdk-lib/bedrock/prompts.test.ts index f04a4583..16fcae64 100644 --- a/test/cdk-lib/bedrock/prompts.test.ts +++ b/test/cdk-lib/bedrock/prompts.test.ts @@ -16,8 +16,7 @@ import { expect as cdkExpect, haveResource, haveResourceLike } from '@aws-cdk/as import * as cdk from 'aws-cdk-lib'; import { aws_bedrock as cdk_bedrock } from 'aws-cdk-lib'; import * as kms from 'aws-cdk-lib/aws-kms'; -import { Prompt, PromptVariant } from '../../../src/cdk-lib/bedrock/prompt'; - +import { Prompt, PromptVariant } from '../../../src/cdk-lib/bedrock/prompts/prompt'; describe('Prompt', () => { let app: cdk.App; @@ -27,7 +26,7 @@ describe('Prompt', () => { app = new cdk.App(); stack = new cdk.Stack(app, 'TestStack'); }); - + // -------------------------------------------------------------------------- test('creates a Prompt with custom encryption key', () => { // GIVEN const cmk = kms.Key.fromKeyArn(stack, 'cmk', 'arn:aws:kms:region:XXXXX:key/12345678-1234-1234-1234-123456789012'); @@ -35,7 +34,7 @@ describe('Prompt', () => { const prompt = new Prompt(stack, 'prompt1', { promptName: 'prompt1', description: 'my cmk prompt', - encryptionKey: cmk, + kmsKey: cmk, }); // WHEN & THEN @@ -49,15 +48,18 @@ describe('Prompt', () => { expect(prompt.promptName).toEqual('prompt1'); }); + // -------------------------------------------------------------------------- test('creates a Prompt with one variant', () => { // GIVEN const variant1 = PromptVariant.text({ variantName: 'variant1', - model: cdk_bedrock.FoundationModel.fromFoundationModelId(stack, 'model1', cdk_bedrock.FoundationModelIdentifier.ANTHROPIC_CLAUDE_3_SONNET_20240229_V1_0), - templateConfiguration: { - inputVariables: [{ name: 'topic' }], - text: 'This is my first text prompt. Please summarize our conversation on {{topic}}.', - }, + model: cdk_bedrock.FoundationModel.fromFoundationModelId( + stack, + 'model1', + cdk_bedrock.FoundationModelIdentifier.ANTHROPIC_CLAUDE_3_SONNET_20240229_V1_0, + ), + promptVariables: ['topic'], + promptText: 'This is my first text prompt. Please summarize our conversation on {{topic}}.', inferenceConfiguration: { temperature: 1.0, topP: 0.999, @@ -103,6 +105,7 @@ describe('Prompt', () => { ); }); + // -------------------------------------------------------------------------- test('creates a prompt version', () => { // GIVEN const prompt = new Prompt(stack, 'prompt1', { @@ -126,6 +129,7 @@ describe('Prompt', () => { ); }); + // -------------------------------------------------------------------------- test('throws on invalid prompt name', () => { //GIVEN new Prompt(stack, 'prompt1', { @@ -136,28 +140,83 @@ describe('Prompt', () => { expect(() => app.synth()).toThrow(); }); + // -------------------------------------------------------------------------- test('throws on invalid prompt variant number', () => { //GIVEN - const variants = [1, 2, 3, 4].map(id => (PromptVariant.text({ - variantName: `variant${id}`, - model: cdk_bedrock.FoundationModel.fromFoundationModelId(stack, `model${id}`, cdk_bedrock.FoundationModelIdentifier.ANTHROPIC_CLAUDE_3_SONNET_20240229_V1_0), - templateConfiguration: { - inputVariables: [{ name: 'topic' }], - text: `This is my text prompt ${id}. Please summarize our conversation on {{topic}}.`, - }, - inferenceConfiguration: { - temperature: 1.0, - topP: 0.999, - maxTokens: 2000, - topK: 250, - }, - }))); + const variants = [1, 2, 3, 4].map((id) => + PromptVariant.text({ + variantName: `variant${id}`, + model: cdk_bedrock.FoundationModel.fromFoundationModelId( + stack, + `model${id}`, + cdk_bedrock.FoundationModelIdentifier.ANTHROPIC_CLAUDE_3_SONNET_20240229_V1_0, + ), + promptVariables: ['topic'], + promptText: `This is my text prompt ${id}. Please summarize our conversation on {{topic}}.`, + inferenceConfiguration: { + temperature: 1.0, + topP: 0.999, + maxTokens: 2000, + topK: 250, + }, + }), + ); new Prompt(stack, 'prompt1', { promptName: 'my-prompt', description: 'my prompt', variants, }); // THEN - expect(() => app.synth()).toThrow('Error: Too many variants specified. The maximum allowed is 3, but you have provided 4 variants.'); + expect(() => app.synth()).toThrow( + 'Error: Too many variants specified. The maximum allowed is 3, but you have provided 4 variants.', + ); + }); +}); + +describe('Prompt-Import', () => { + let app: cdk.App; + let stack: cdk.Stack; + + beforeEach(() => { + app = new cdk.App(); + stack = new cdk.Stack(app, 'TestStack'); + }); + + // -------------------------------------------------------------------------- + test('Basic Import - ARN', () => { + const prompt = Prompt.fromPromptAttributes(stack, 'TestPrompt', { + promptArn: 'arn:aws:bedrock:us-east-1:123456789012:prompt/PROMPT12345', + }); + + expect(prompt.promptArn).toBe('arn:aws:bedrock:us-east-1:123456789012:prompt/PROMPT12345'); + expect(prompt.promptId).toBe('PROMPT12345'); + expect(prompt.promptVersion).toBe('DRAFT'); + expect(prompt.kmsKey).toBeUndefined(); + }); + + // -------------------------------------------------------------------------- + test('Basic Import - ARN with version', () => { + const prompt = Prompt.fromPromptAttributes(stack, 'TestPrompt', { + promptArn: 'arn:aws:bedrock:us-east-1:123456789012:prompt/PROMPT12345', + promptVersion: '1', + }); + + expect(prompt.promptArn).toBe('arn:aws:bedrock:us-east-1:123456789012:prompt/PROMPT12345'); + expect(prompt.promptId).toBe('PROMPT12345'); + expect(prompt.promptVersion).toBe('1'); + expect(prompt.kmsKey).toBeUndefined(); + }); + + // -------------------------------------------------------------------------- + test('Basic Import - ARN + KMS', () => { + const prompt = Prompt.fromPromptAttributes(stack, 'TestPrompt', { + promptArn: 'arn:aws:bedrock:us-east-1:123456789012:prompt/PROMPT12345', + kmsKey: kms.Key.fromKeyArn(stack, 'cmk', 'arn:aws:kms:region:XXXXX:key/12345678-1234-1234-1234-123456789012'), + }); + + expect(prompt.promptArn).toBe('arn:aws:bedrock:us-east-1:123456789012:prompt/PROMPT12345'); + expect(prompt.promptId).toBe('PROMPT12345'); + expect(prompt.promptVersion).toBe('DRAFT'); + expect(prompt.kmsKey?.keyArn).toBe('arn:aws:kms:region:XXXXX:key/12345678-1234-1234-1234-123456789012'); }); -}); \ No newline at end of file +});