From 7860e14618203a451f5e01c7882aac045e7d6b21 Mon Sep 17 00:00:00 2001 From: NingLyu Date: Tue, 31 Dec 2024 06:14:52 +0000 Subject: [PATCH] chore: refactor to fix throttle issue --- source/infrastructure/lib/api/api-stack.ts | 304 ++++-------------- source/infrastructure/lib/api/chat-history.ts | 68 ++++ .../lib/api/intention-management.ts | 245 ++++++++++++++ .../lib/api/model-management.ts | 82 +++++ .../lib/api/prompt-management.ts | 82 +++++ .../lib/model/model-construct.ts | 34 +- 6 files changed, 537 insertions(+), 278 deletions(-) create mode 100644 source/infrastructure/lib/api/chat-history.ts create mode 100644 source/infrastructure/lib/api/intention-management.ts create mode 100644 source/infrastructure/lib/api/model-management.ts create mode 100644 source/infrastructure/lib/api/prompt-management.ts diff --git a/source/infrastructure/lib/api/api-stack.ts b/source/infrastructure/lib/api/api-stack.ts index f3b6cfbe..35174d77 100644 --- a/source/infrastructure/lib/api/api-stack.ts +++ b/source/infrastructure/lib/api/api-stack.ts @@ -32,6 +32,10 @@ import { LambdaFunction } from "../shared/lambda-helper"; import { Constants } from "../shared/constants"; import { PythonFunction } from "@aws-cdk/aws-lambda-python-alpha"; import { BundlingFileAccess } from 'aws-cdk-lib/core'; +import { PromptApi } from "./prompt-management"; +import { IntentionApi } from "./intention-management"; +import { ModelApi } from "./model-management"; +import { ChatHistoryApi } from "./chat-history"; interface ApiStackProps extends StackProps { config: SystemConfig; @@ -336,60 +340,6 @@ export class ApiConstruct extends Construct { } if (props.config.chat.enabled) { - - const chatHistoryManagementLambda = new LambdaFunction(this, "ChatHistoryManagementLambda", { - code: Code.fromAsset(join(__dirname, "../../../lambda/chat_history")), - handler: "chat_history_management.lambda_handler", - environment: { - SESSIONS_TABLE_NAME: sessionsTableName, - MESSAGES_TABLE_NAME: messagesTableName, - SESSIONS_BY_TIMESTAMP_INDEX_NAME: "byTimestamp", - MESSAGES_BY_SESSION_ID_INDEX_NAME: "bySessionId", - }, - statements: [this.iamHelper.dynamodbStatement], - }); - - const promptManagementLambda = new LambdaFunction(this, "PromptManagementLambda", { - runtime: Runtime.PYTHON_3_12, - handler: "prompt_management.lambda_handler", - code: Code.fromAsset(join(__dirname, '../../../lambda/deployment_assets/lambda_assets/prompt_management.zip')), - environment: { - PROMPT_TABLE_NAME: props.chatStackOutputs.promptTableName, - }, - layers: [sharedLayer], - statements: [this.iamHelper.dynamodbStatement, this.iamHelper.logStatement], - }); - - const intentionLambda = new PythonFunction(this, "IntentionLambda", { - runtime: Runtime.PYTHON_3_12, - entry: join(__dirname, "../../../lambda/intention"), - index: "intention.py", - handler: "lambda_handler", - timeout: Duration.minutes(15), - vpc: vpc, - securityGroups: securityGroups, - environment: { - INTENTION_TABLE_NAME: props.chatStackOutputs.intentionTableName, - INDEX_TABLE_NAME: props.sharedConstructOutputs.indexTable.tableName, - CHATBOT_TABLE_NAME: props.sharedConstructOutputs.chatbotTable.tableName, - MODEL_TABLE_NAME: props.sharedConstructOutputs.modelTable.tableName, - S3_BUCKET: s3Bucket.bucketName, - EMBEDDING_MODEL_ENDPOINT: props.modelConstructOutputs.defaultEmbeddingModelName, - AOS_ENDPOINT: domainEndpoint, - KNOWLEDGE_BASE_ENABLED: props.config.knowledgeBase.enabled.toString(), - KNOWLEDGE_BASE_TYPE: JSON.stringify(props.config.knowledgeBase.knowledgeBaseType || {}), - BEDROCK_REGION: props.config.chat.bedrockRegion, - }, - layers: [sharedLayer], - }); - intentionLambda.addToRolePolicy(this.iamHelper.dynamodbStatement); - intentionLambda.addToRolePolicy(this.iamHelper.logStatement); - intentionLambda.addToRolePolicy(this.iamHelper.secretStatement); - intentionLambda.addToRolePolicy(this.iamHelper.esStatement); - intentionLambda.addToRolePolicy(this.iamHelper.s3Statement); - intentionLambda.addToRolePolicy(this.iamHelper.bedrockStatement); - intentionLambda.addToRolePolicy(this.iamHelper.endpointStatement); - const chatbotManagementLambda = new LambdaFunction(this, "ChatbotManagementLambda", { runtime: Runtime.PYTHON_3_12, code: Code.fromAsset(join(__dirname, "../../../lambda/etl")), @@ -404,32 +354,6 @@ export class ApiConstruct extends Construct { statements: [this.iamHelper.dynamodbStatement, this.iamHelper.logStatement], }); - - const modelLambda = new PythonFunction(this, "ModelLambda", { - runtime: Runtime.PYTHON_3_12, - entry: join(__dirname, "../../../lambda/model_management"), - index: "model_management.py", - handler: "lambda_handler", - timeout: Duration.minutes(15), - environment: { - MODEL_TABLE_NAME: props.sharedConstructOutputs.modelTable.tableName, - }, - layers: [sharedLayer], - }); - modelLambda.addToRolePolicy(this.iamHelper.dynamodbStatement); - modelLambda.addToRolePolicy(this.iamHelper.logStatement); - modelLambda.addToRolePolicy(this.iamHelper.s3Statement); - modelLambda.addToRolePolicy(this.iamHelper.codePipelineStatement); - modelLambda.addToRolePolicy(this.iamHelper.cfnStatement); - modelLambda.addToRolePolicy(this.iamHelper.stsStatement); - modelLambda.addToRolePolicy(this.iamHelper.cfnStatement); - - const apiResourceSessions = api.root.addResource("sessions"); - apiResourceSessions.addMethod("GET", new apigw.LambdaIntegration(chatHistoryManagementLambda.function), this.genMethodOption(api, auth, null),); - const apiResourceMessages = apiResourceSessions.addResource('{sessionId}').addResource("messages"); - apiResourceMessages.addMethod("GET", new apigw.LambdaIntegration(chatHistoryManagementLambda.function), this.genMethodOption(api, auth, null),); - const apiResourceMessageFeedback = apiResourceMessages.addResource("{messageId}").addResource("feedback"); - apiResourceMessageFeedback.addMethod("POST", new apigw.LambdaIntegration(chatHistoryManagementLambda.function), this.genMethodOption(api, auth, null),); const lambdaChatbotIntegration = new apigw.LambdaIntegration(chatbotManagementLambda.function, { proxy: true, @@ -549,174 +473,62 @@ export class ApiConstruct extends Construct { apiResourceChatbotProxy.addMethod("DELETE", lambdaChatbotIntegration, this.genMethodOption(api, auth, null),); apiResourceChatbotProxy.addMethod("GET", lambdaChatbotIntegration, this.genMethodOption(api, auth, null),); - // API Gateway Lambda Integration to manage prompt - const lambdaPromptIntegration = new apigw.LambdaIntegration(promptManagementLambda.function, { - proxy: true, - }); - - const apiResourcePromptManagement = api.root.addResource("prompt-management"); - - const apiResourcePromptManagementModels = apiResourcePromptManagement.addResource("models") - apiResourcePromptManagementModels.addMethod("GET", lambdaPromptIntegration, this.genMethodOption(api, auth, null)); - - const apiResourcePromptManagementScenes = apiResourcePromptManagement.addResource("scenes") - apiResourcePromptManagementScenes.addMethod("GET", lambdaPromptIntegration, this.genMethodOption(api, auth, null)); - - const apiResourcePrompt = apiResourcePromptManagement.addResource("prompts"); - apiResourcePrompt.addMethod("POST", lambdaPromptIntegration, this.genMethodOption(api, auth, null)); - apiResourcePrompt.addMethod("GET", lambdaPromptIntegration, this.genMethodOption(api, auth, null)); - - const apiResourcePromptProxy = apiResourcePrompt.addResource("{proxy+}") - apiResourcePromptProxy.addMethod("POST", lambdaPromptIntegration, this.genMethodOption(api, auth, null)); - apiResourcePromptProxy.addMethod("DELETE", lambdaPromptIntegration, this.genMethodOption(api, auth, null)); - apiResourcePromptProxy.addMethod("GET", lambdaPromptIntegration, this.genMethodOption(api, auth, null)); + const chatHistoryApi = new ChatHistoryApi( + scope, "ChatHistoryApi", { + api: api, + auth: auth, + messagesTableName: messagesTableName, + sessionsTableName: sessionsTableName, + iamHelper: this.iamHelper, + genMethodOption: this.genMethodOption, + }, + ); - // API Gateway Lambda Integration to manage model - const lambdaModelIntegration = new apigw.LambdaIntegration(modelLambda, { - proxy: true, - }); - const apiResourceModelManagement = api.root.addResource("model-management"); - const apiResourceModelManagementDeploy = apiResourceModelManagement.addResource("deploy") - apiResourceModelManagementDeploy.addMethod("POST", lambdaModelIntegration, this.genMethodOption(api, auth, null)); - const apiResourceModelManagementDestroy = apiResourceModelManagement.addResource("destroy") - apiResourceModelManagementDestroy.addMethod("POST", lambdaModelIntegration, this.genMethodOption(api, auth, null)); - const apiResourceModelManagementStatus = apiResourceModelManagement.addResource("status").addResource("{modelId}"); - apiResourceModelManagementStatus.addMethod("GET", lambdaModelIntegration, this.genMethodOption(api, auth, null)); - - // API Gateway Lambda Integration to manage intention - const lambdaIntentionIntegration = new apigw.LambdaIntegration(intentionLambda, { - proxy: true, - }); - const apiResourceIntentionManagement = api.root.addResource("intention"); - // apiResourceIntentionManagement.addMethod("DELETE", lambdaIntentionIntegration, this.genMethodOption(api, auth, null)) - const indexScan = apiResourceIntentionManagement.addResource("index-used-scan") - indexScan.addMethod("POST", lambdaIntentionIntegration, this.genMethodOption(api, auth, null)); - // apiResourceIntentionManagement.addMethod("DELETE", lambdaIntentionIntegration, this.genMethodOption(api, auth, null)); - const presignedUrl = apiResourceIntentionManagement.addResource("execution-presigned-url"); - presignedUrl.addMethod("POST", lambdaIntentionIntegration, { - ...this.genMethodOption(api, auth, { - data: { type: JsonSchemaType.STRING }, - message: { type: JsonSchemaType.STRING }, - s3Bucket: { type: JsonSchemaType.STRING }, - s3Prefix: { type: JsonSchemaType.STRING } - }), - requestModels: this.genRequestModel(api, { - "content_type": { "type": JsonSchemaType.STRING }, - "file_name": { "type": JsonSchemaType.STRING }, - }) - }) - const apiResourceDownload = apiResourceIntentionManagement.addResource("download-template"); - apiResourceDownload.addMethod("GET", lambdaIntentionIntegration, this.genMethodOption(api, auth, null)); - - // API Gateway Lambda Integration to manage knowledges - const apiResourceExecutionManagement = apiResourceIntentionManagement.addResource("executions"); - apiResourceExecutionManagement.addMethod("DELETE", lambdaIntentionIntegration, this.genMethodOption(api, auth, null)) - apiResourceExecutionManagement.addMethod("POST", lambdaIntentionIntegration, { - ...this.genMethodOption(api, auth, { - execution_id: { type: JsonSchemaType.STRING }, - input_payload: { - type: JsonSchemaType.OBJECT, - properties: { - tableItemId: { type: JsonSchemaType.STRING }, - chatbotId: { type: JsonSchemaType.STRING }, - groupName: { type: JsonSchemaType.STRING }, - index: { type: JsonSchemaType.STRING }, - model: { type: JsonSchemaType.STRING }, - fieldName: { type: JsonSchemaType.STRING } - } - }, - result: { type: JsonSchemaType.STRING } - }), - requestModels: this.genRequestModel(api, { - "chatbotId": { "type": JsonSchemaType.STRING }, - "index": { "type": JsonSchemaType.STRING }, - "model": { "type": JsonSchemaType.STRING }, - "s3Bucket": { "type": JsonSchemaType.STRING }, - "s3Prefix": { "type": JsonSchemaType.STRING } - }) - }); - apiResourceExecutionManagement.addMethod("GET", lambdaIntentionIntegration, { - ...this.genMethodOption(api, auth, { - Items: { - type: JsonSchemaType.ARRAY, items: { - type: JsonSchemaType.OBJECT, - properties: { - model: { type: JsonSchemaType.STRING }, - executionStatus: { type: JsonSchemaType.STRING }, - index: { type: JsonSchemaType.STRING }, - fileName: { type: JsonSchemaType.STRING }, - createTime: { type: JsonSchemaType.STRING }, - createBy: { type: JsonSchemaType.STRING }, - executionId: { type: JsonSchemaType.STRING }, - - chatbotId: { type: JsonSchemaType.STRING }, - details: { type: JsonSchemaType.STRING }, - tag: { type: JsonSchemaType.STRING }, - }, - required: ['model', - 'executionStatus', - 'index', - 'fileName', - 'createTime', - 'createBy', - 'executionId', - 'chatbotId', - 'details', - 'tag'], - } - }, - Count: { type: JsonSchemaType.INTEGER }, - Config: { - type: JsonSchemaType.OBJECT, - properties: { - MaxItems: { type: JsonSchemaType.INTEGER }, - PageSize: { type: JsonSchemaType.INTEGER }, - StartingToken: { type: JsonSchemaType.NULL } - } - } - }), - requestParameters: { - 'method.request.querystring.max_items': false, - 'method.request.querystring.page_size': false - } - }); - const apiGetIntentionById = apiResourceExecutionManagement.addResource("{executionId}"); - apiGetIntentionById.addMethod( - "GET", - lambdaIntentionIntegration, - { - ...this.genMethodOption(api, auth, { - Items: { - type: JsonSchemaType.ARRAY, - items: { - type: JsonSchemaType.OBJECT, - properties: { - s3Path: { type: JsonSchemaType.STRING }, - s3Prefix: { type: JsonSchemaType.STRING }, - createTime: { type: JsonSchemaType.STRING }, // Consider using format: 'date-time' - status: { type: JsonSchemaType.STRING }, - QAList: { - type: JsonSchemaType.ARRAY, - items: { - type: JsonSchemaType.OBJECT, - properties: { - question: { type: JsonSchemaType.STRING }, - intention: { type: JsonSchemaType.STRING }, - kwargs: { type: JsonSchemaType.STRING }, - } - } - } - }, - required: ['s3Path', 's3Prefix', 'createTime', 'status', 'executionId'], - } - }, - Count: { type: JsonSchemaType.INTEGER } - }), - requestParameters: { - 'method.request.path.intentionId': true - }, - } + const promptApi = new PromptApi( + scope, "PromptApi", { + api: api, + auth: auth, + promptTableName: props.chatStackOutputs.promptTableName, + sharedLayer: sharedLayer, + iamHelper: this.iamHelper, + genMethodOption: this.genMethodOption, + }, + ); + promptApi.node.addDependency(chatHistoryApi); + + const intentionApi = new IntentionApi( + scope, "IntentionApi", { + api: api, + auth: auth, + vpc: vpc!, + securityGroups: securityGroups!, + intentionTableName: props.chatStackOutputs.intentionTableName, + indexTable: props.sharedConstructOutputs.indexTable.tableName, + chatbotTable: props.sharedConstructOutputs.chatbotTable.tableName, + modelTable: props.sharedConstructOutputs.modelTable.tableName, + s3Bucket: s3Bucket.bucketName, + defaultEmbeddingModelName: props.modelConstructOutputs.defaultEmbeddingModelName, + domainEndpoint: domainEndpoint, + config: props.config, + sharedLayer: sharedLayer, + iamHelper: this.iamHelper, + genMethodOption: this.genMethodOption, + genRequestModel: this.genRequestModel, + }, + ); + intentionApi.node.addDependency(promptApi); + + const modelApi = new ModelApi( + scope, "ModelApi", { + api: api, + auth: auth, + modelTable: props.sharedConstructOutputs.modelTable.tableName, + sharedLayer: sharedLayer, + iamHelper: this.iamHelper, + genMethodOption: this.genMethodOption, + }, ); + modelApi.node.addDependency(intentionApi); // Define the API Gateway Lambda Integration with proxy and no integration responses const lambdaExecutorIntegration = new apigw.LambdaIntegration( diff --git a/source/infrastructure/lib/api/chat-history.ts b/source/infrastructure/lib/api/chat-history.ts new file mode 100644 index 00000000..525b6127 --- /dev/null +++ b/source/infrastructure/lib/api/chat-history.ts @@ -0,0 +1,68 @@ +/********************************************************************************************************************** + * 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 { Code } from "aws-cdk-lib/aws-lambda"; +import * as apigw from "aws-cdk-lib/aws-apigateway"; +import { Construct } from "constructs"; +import { join } from "path"; +import { IAMHelper } from "../shared/iam-helper"; +import { LambdaFunction } from "../shared/lambda-helper"; + + +export interface ChatHistoryApiProps { + api: apigw.RestApi; + auth: apigw.RequestAuthorizer; + iamHelper: IAMHelper; + messagesTableName: string; + sessionsTableName: string; + genMethodOption: any; +} + +export class ChatHistoryApi extends Construct { + private readonly api: apigw.RestApi; + private readonly auth: apigw.RequestAuthorizer; + private readonly messagesTableName: string; + private readonly sessionsTableName: string; + private readonly iamHelper: IAMHelper; + private readonly genMethodOption: any; + + constructor(scope: Construct, id: string, props: ChatHistoryApiProps) { + super(scope, id); + + this.api = props.api; + this.auth = props.auth; + this.messagesTableName = props.messagesTableName; + this.sessionsTableName = props.sessionsTableName; + this.iamHelper = props.iamHelper; + this.genMethodOption = props.genMethodOption; + + const chatHistoryManagementLambda = new LambdaFunction(this, "ChatHistoryManagementLambda", { + code: Code.fromAsset(join(__dirname, "../../../lambda/chat_history")), + handler: "chat_history_management.lambda_handler", + environment: { + SESSIONS_TABLE_NAME: this.sessionsTableName, + MESSAGES_TABLE_NAME: this.messagesTableName, + SESSIONS_BY_TIMESTAMP_INDEX_NAME: "byTimestamp", + MESSAGES_BY_SESSION_ID_INDEX_NAME: "bySessionId", + }, + statements: [this.iamHelper.dynamodbStatement], + }); + + const apiResourceSessions = this.api.root.addResource("sessions"); + apiResourceSessions.addMethod("GET", new apigw.LambdaIntegration(chatHistoryManagementLambda.function), this.genMethodOption(this.api, this.auth, null),); + const apiResourceMessages = apiResourceSessions.addResource('{sessionId}').addResource("messages"); + apiResourceMessages.addMethod("GET", new apigw.LambdaIntegration(chatHistoryManagementLambda.function), this.genMethodOption(this.api, this.auth, null),); + const apiResourceMessageFeedback = apiResourceMessages.addResource("{messageId}").addResource("feedback"); + apiResourceMessageFeedback.addMethod("POST", new apigw.LambdaIntegration(chatHistoryManagementLambda.function), this.genMethodOption(this.api, this.auth, null),); + } +} diff --git a/source/infrastructure/lib/api/intention-management.ts b/source/infrastructure/lib/api/intention-management.ts new file mode 100644 index 00000000..4826a66a --- /dev/null +++ b/source/infrastructure/lib/api/intention-management.ts @@ -0,0 +1,245 @@ +/********************************************************************************************************************** + * 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 { Runtime } from "aws-cdk-lib/aws-lambda"; +import { JsonSchemaType } from "aws-cdk-lib/aws-apigateway"; +import * as apigw from "aws-cdk-lib/aws-apigateway"; +import { Construct } from "constructs"; +import { join } from "path"; +import { PythonFunction } from "@aws-cdk/aws-lambda-python-alpha"; +import * as pyLambda from "@aws-cdk/aws-lambda-python-alpha"; +import { IAMHelper } from "../shared/iam-helper"; +import { Vpc, SecurityGroup } from 'aws-cdk-lib/aws-ec2'; +import { SystemConfig } from "../shared/types"; + + +export interface IntentionApiProps { + api: apigw.RestApi; + auth: apigw.RequestAuthorizer; + vpc: Vpc; + securityGroups: [SecurityGroup]; + intentionTableName: string; + indexTable: string; + chatbotTable: string; + modelTable: string; + s3Bucket: string; + defaultEmbeddingModelName: string; + domainEndpoint: string; + config: SystemConfig; + sharedLayer: pyLambda.PythonLayerVersion; + iamHelper: IAMHelper; + genMethodOption: any; + genRequestModel: any; +} + +export class IntentionApi extends Construct { + private readonly api: apigw.RestApi; + private readonly auth: apigw.RequestAuthorizer; + private readonly vpc: Vpc; + private readonly securityGroups: [SecurityGroup]; + private readonly sharedLayer: pyLambda.PythonLayerVersion; + private readonly iamHelper: IAMHelper; + private readonly intentionTableName: string; + private readonly indexTable: string; + private readonly chatbotTable: string; + private readonly modelTable: string; + private readonly s3Bucket: string; + private readonly defaultEmbeddingModelName: string; + private readonly domainEndpoint: string; + private readonly config: SystemConfig; + private readonly genMethodOption: any; + private readonly genRequestModel: any; + + constructor(scope: Construct, id: string, props: IntentionApiProps) { + super(scope, id); + + this.api = props.api; + this.auth = props.auth; + this.vpc = props.vpc; + this.securityGroups = props.securityGroups; + this.intentionTableName = props.intentionTableName; + this.indexTable = props.indexTable; + this.chatbotTable = props.chatbotTable; + this.modelTable = props.modelTable; + this.s3Bucket = props.s3Bucket; + this.defaultEmbeddingModelName = props.defaultEmbeddingModelName; + this.domainEndpoint = props.domainEndpoint; + this.config = props.config; + this.sharedLayer = props.sharedLayer; + this.iamHelper = props.iamHelper; + this.genMethodOption = props.genMethodOption; + this.genRequestModel = props.genRequestModel; + + const intentionLambda = new PythonFunction(scope, "IntentionLambda", { + runtime: Runtime.PYTHON_3_12, + entry: join(__dirname, "../../../lambda/intention"), + index: "intention.py", + handler: "lambda_handler", + timeout: Duration.minutes(15), + vpc: this.vpc, + securityGroups: this.securityGroups, + environment: { + INTENTION_TABLE_NAME: this.intentionTableName, + INDEX_TABLE_NAME: this.indexTable, + CHATBOT_TABLE_NAME: this.chatbotTable, + MODEL_TABLE_NAME: this.modelTable, + S3_BUCKET: this.s3Bucket, + EMBEDDING_MODEL_ENDPOINT: this.defaultEmbeddingModelName, + AOS_ENDPOINT: this.domainEndpoint, + KNOWLEDGE_BASE_ENABLED: this.config.knowledgeBase.enabled.toString(), + KNOWLEDGE_BASE_TYPE: JSON.stringify(this.config.knowledgeBase.knowledgeBaseType || {}), + BEDROCK_REGION: this.config.chat.bedrockRegion, + }, + layers: [this.sharedLayer], + }); + intentionLambda.addToRolePolicy(this.iamHelper.dynamodbStatement); + intentionLambda.addToRolePolicy(this.iamHelper.logStatement); + intentionLambda.addToRolePolicy(this.iamHelper.secretStatement); + intentionLambda.addToRolePolicy(this.iamHelper.esStatement); + intentionLambda.addToRolePolicy(this.iamHelper.s3Statement); + intentionLambda.addToRolePolicy(this.iamHelper.bedrockStatement); + intentionLambda.addToRolePolicy(this.iamHelper.endpointStatement); + + // API Gateway Lambda Integration to manage intention + const lambdaIntentionIntegration = new apigw.LambdaIntegration(intentionLambda, { + proxy: true, + }); + const apiResourceIntentionManagement = this.api.root.addResource("intention"); + const indexScan = apiResourceIntentionManagement.addResource("index-used-scan") + indexScan.addMethod("POST", lambdaIntentionIntegration, this.genMethodOption(this.api, this.auth, null)); + const presignedUrl = apiResourceIntentionManagement.addResource("execution-presigned-url"); + presignedUrl.addMethod("POST", lambdaIntentionIntegration, { + ...this.genMethodOption(this.api, this.auth, { + data: { type: JsonSchemaType.STRING }, + message: { type: JsonSchemaType.STRING }, + s3Bucket: { type: JsonSchemaType.STRING }, + s3Prefix: { type: JsonSchemaType.STRING } + }), + requestModels: this.genRequestModel(this.api, { + "content_type": { "type": JsonSchemaType.STRING }, + "file_name": { "type": JsonSchemaType.STRING }, + }) + }) + const apiResourceDownload = apiResourceIntentionManagement.addResource("download-template"); + apiResourceDownload.addMethod("GET", lambdaIntentionIntegration, this.genMethodOption(this.api, this.auth, null)); + + const apiResourceIntentionExecution = apiResourceIntentionManagement.addResource("executions"); + apiResourceIntentionExecution.addMethod("DELETE", lambdaIntentionIntegration, this.genMethodOption(this.api, this.auth, null)) + apiResourceIntentionExecution.addMethod("POST", lambdaIntentionIntegration, { + ...this.genMethodOption(this.api, this.auth, { + execution_id: { type: JsonSchemaType.STRING }, + input_payload: { + type: JsonSchemaType.OBJECT, + properties: { + tableItemId: { type: JsonSchemaType.STRING }, + chatbotId: { type: JsonSchemaType.STRING }, + groupName: { type: JsonSchemaType.STRING }, + index: { type: JsonSchemaType.STRING }, + model: { type: JsonSchemaType.STRING }, + fieldName: { type: JsonSchemaType.STRING } + } + }, + result: { type: JsonSchemaType.STRING } + }), + requestModels: this.genRequestModel(this.api, { + "chatbotId": { "type": JsonSchemaType.STRING }, + "index": { "type": JsonSchemaType.STRING }, + "model": { "type": JsonSchemaType.STRING }, + "s3Bucket": { "type": JsonSchemaType.STRING }, + "s3Prefix": { "type": JsonSchemaType.STRING } + }) + }); + apiResourceIntentionExecution.addMethod("GET", lambdaIntentionIntegration, { + ...this.genMethodOption(this.api, this.auth, { + Items: { + type: JsonSchemaType.ARRAY, items: { + type: JsonSchemaType.OBJECT, + properties: { + model: { type: JsonSchemaType.STRING }, + executionStatus: { type: JsonSchemaType.STRING }, + index: { type: JsonSchemaType.STRING }, + fileName: { type: JsonSchemaType.STRING }, + createTime: { type: JsonSchemaType.STRING }, + createBy: { type: JsonSchemaType.STRING }, + executionId: { type: JsonSchemaType.STRING }, + chatbotId: { type: JsonSchemaType.STRING }, + details: { type: JsonSchemaType.STRING }, + tag: { type: JsonSchemaType.STRING }, + }, + required: ['model', + 'executionStatus', + 'index', + 'fileName', + 'createTime', + 'createBy', + 'executionId', + 'chatbotId', + 'details', + 'tag'], + } + }, + Count: { type: JsonSchemaType.INTEGER }, + Config: { + type: JsonSchemaType.OBJECT, + properties: { + MaxItems: { type: JsonSchemaType.INTEGER }, + PageSize: { type: JsonSchemaType.INTEGER }, + StartingToken: { type: JsonSchemaType.NULL } + } + } + }), + requestParameters: { + 'method.request.querystring.max_items': false, + 'method.request.querystring.page_size': false + } + }); + const apiGetIntentionById = apiResourceIntentionExecution.addResource("{executionId}"); + apiGetIntentionById.addMethod( + "GET", + lambdaIntentionIntegration, + { + ...this.genMethodOption(this.api, this.auth, { + Items: { + type: JsonSchemaType.ARRAY, + items: { + type: JsonSchemaType.OBJECT, + properties: { + s3Path: { type: JsonSchemaType.STRING }, + s3Prefix: { type: JsonSchemaType.STRING }, + createTime: { type: JsonSchemaType.STRING }, // Consider using format: 'date-time' + status: { type: JsonSchemaType.STRING }, + QAList: { + type: JsonSchemaType.ARRAY, + items: { + type: JsonSchemaType.OBJECT, + properties: { + question: { type: JsonSchemaType.STRING }, + intention: { type: JsonSchemaType.STRING }, + kwargs: { type: JsonSchemaType.STRING }, + } + } + } + }, + required: ['s3Path', 's3Prefix', 'createTime', 'status', 'executionId'], + } + }, + Count: { type: JsonSchemaType.INTEGER } + }), + requestParameters: { + 'method.request.path.intentionId': true + }, + } + ); + } +} diff --git a/source/infrastructure/lib/api/model-management.ts b/source/infrastructure/lib/api/model-management.ts new file mode 100644 index 00000000..39f5f0f4 --- /dev/null +++ b/source/infrastructure/lib/api/model-management.ts @@ -0,0 +1,82 @@ +/********************************************************************************************************************** + * 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 { Runtime } from "aws-cdk-lib/aws-lambda"; +import * as apigw from "aws-cdk-lib/aws-apigateway"; +import { Construct } from "constructs"; +import { join } from "path"; +import { PythonFunction } from "@aws-cdk/aws-lambda-python-alpha"; +import * as pyLambda from "@aws-cdk/aws-lambda-python-alpha"; +import { IAMHelper } from "../shared/iam-helper"; + + +export interface ModelApiProps { + api: apigw.RestApi; + auth: apigw.RequestAuthorizer; + modelTable: string; + sharedLayer: pyLambda.PythonLayerVersion; + iamHelper: IAMHelper; + genMethodOption: any; +} + +export class ModelApi extends Construct { + private readonly api: apigw.RestApi; + private readonly auth: apigw.RequestAuthorizer; + private readonly sharedLayer: pyLambda.PythonLayerVersion; + private readonly modelTable: string; + private readonly iamHelper: IAMHelper; + private readonly genMethodOption: any; + + constructor(scope: Construct, id: string, props: ModelApiProps) { + super(scope, id); + + this.api = props.api; + this.auth = props.auth; + this.modelTable = props.modelTable; + this.sharedLayer = props.sharedLayer; + this.iamHelper = props.iamHelper; + this.genMethodOption = props.genMethodOption; + + const modelLambda = new PythonFunction(this, "ModelLambda", { + runtime: Runtime.PYTHON_3_12, + entry: join(__dirname, "../../../lambda/model_management"), + index: "model_management.py", + handler: "lambda_handler", + timeout: Duration.minutes(15), + environment: { + MODEL_TABLE_NAME: this.modelTable, + }, + layers: [this.sharedLayer], + }); + modelLambda.addToRolePolicy(this.iamHelper.dynamodbStatement); + modelLambda.addToRolePolicy(this.iamHelper.logStatement); + modelLambda.addToRolePolicy(this.iamHelper.s3Statement); + modelLambda.addToRolePolicy(this.iamHelper.codePipelineStatement); + modelLambda.addToRolePolicy(this.iamHelper.cfnStatement); + modelLambda.addToRolePolicy(this.iamHelper.stsStatement); + modelLambda.addToRolePolicy(this.iamHelper.cfnStatement); + + // API Gateway Lambda Integration to manage model + const lambdaModelIntegration = new apigw.LambdaIntegration(modelLambda, { + proxy: true, + }); + const apiResourceModelManagement = this.api.root.addResource("model-management"); + const apiResourceModelManagementDeploy = apiResourceModelManagement.addResource("deploy") + apiResourceModelManagementDeploy.addMethod("POST", lambdaModelIntegration, this.genMethodOption(this.api, this.auth, null)); + const apiResourceModelManagementDestroy = apiResourceModelManagement.addResource("destroy") + apiResourceModelManagementDestroy.addMethod("POST", lambdaModelIntegration, this.genMethodOption(this.api, this.auth, null)); + const apiResourceModelManagementStatus = apiResourceModelManagement.addResource("status").addResource("{modelId}"); + apiResourceModelManagementStatus.addMethod("GET", lambdaModelIntegration, this.genMethodOption(this.api, this.auth, null)); + } +} diff --git a/source/infrastructure/lib/api/prompt-management.ts b/source/infrastructure/lib/api/prompt-management.ts new file mode 100644 index 00000000..bbe39e7d --- /dev/null +++ b/source/infrastructure/lib/api/prompt-management.ts @@ -0,0 +1,82 @@ +/********************************************************************************************************************** + * 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 { Runtime, Code } from "aws-cdk-lib/aws-lambda"; +import * as apigw from "aws-cdk-lib/aws-apigateway"; +import { Construct } from "constructs"; +import { join } from "path"; +import { LambdaFunction } from "../shared/lambda-helper"; +import * as pyLambda from "@aws-cdk/aws-lambda-python-alpha"; +import { IAMHelper } from "../shared/iam-helper"; + + +export interface PromptApiProps { + api: apigw.RestApi; + auth: apigw.RequestAuthorizer; + promptTableName: string; + sharedLayer: pyLambda.PythonLayerVersion; + iamHelper: IAMHelper; + genMethodOption: any; +} + +export class PromptApi extends Construct { + private readonly api: apigw.RestApi; + private readonly auth: apigw.RequestAuthorizer; + private readonly promptTableName: string; + private readonly sharedLayer: pyLambda.PythonLayerVersion; + private readonly iamHelper: IAMHelper; + private readonly genMethodOption: any; + + constructor(scope: Construct, id: string, props: PromptApiProps) { + super(scope, id); + + this.api = props.api; + this.auth = props.auth; + this.promptTableName = props.promptTableName; + this.sharedLayer = props.sharedLayer; + this.iamHelper = props.iamHelper; + this.genMethodOption = props.genMethodOption; + + const promptManagementLambda = new LambdaFunction(scope, "PromptManagementLambda", { + runtime: Runtime.PYTHON_3_12, + handler: "prompt_management.lambda_handler", + code: Code.fromAsset(join(__dirname, '../../../lambda/deployment_assets/lambda_assets/prompt_management.zip')), + environment: { + PROMPT_TABLE_NAME: this.promptTableName, + }, + layers: [this.sharedLayer], + statements: [this.iamHelper.dynamodbStatement, this.iamHelper.logStatement], + }); + // API Gateway Lambda Integration to manage prompt + const lambdaPromptIntegration = new apigw.LambdaIntegration(promptManagementLambda.function, { + proxy: true, + }); + + const apiResourcePromptManagement = this.api.root.addResource("prompt-management"); + + const apiResourcePromptManagementModels = apiResourcePromptManagement.addResource("models") + apiResourcePromptManagementModels.addMethod("GET", lambdaPromptIntegration, this.genMethodOption(this.api, this.auth, null)); + + const apiResourcePromptManagementScenes = apiResourcePromptManagement.addResource("scenes") + apiResourcePromptManagementScenes.addMethod("GET", lambdaPromptIntegration, this.genMethodOption(this.api, this.auth, null)); + + const apiResourcePrompt = apiResourcePromptManagement.addResource("prompts"); + apiResourcePrompt.addMethod("POST", lambdaPromptIntegration, this.genMethodOption(this.api, this.auth, null)); + apiResourcePrompt.addMethod("GET", lambdaPromptIntegration, this.genMethodOption(this.api, this.auth, null)); + + const apiResourcePromptProxy = apiResourcePrompt.addResource("{proxy+}") + apiResourcePromptProxy.addMethod("POST", lambdaPromptIntegration, this.genMethodOption(this.api, this.auth, null)); + apiResourcePromptProxy.addMethod("DELETE", lambdaPromptIntegration, this.genMethodOption(this.api, this.auth, null)); + apiResourcePromptProxy.addMethod("GET", lambdaPromptIntegration, this.genMethodOption(this.api, this.auth, null)); + } +} diff --git a/source/infrastructure/lib/model/model-construct.ts b/source/infrastructure/lib/model/model-construct.ts index 6a20cf39..6f4f3146 100644 --- a/source/infrastructure/lib/model/model-construct.ts +++ b/source/infrastructure/lib/model/model-construct.ts @@ -22,7 +22,7 @@ import * as cr from "aws-cdk-lib/custom-resources"; import * as logs from "aws-cdk-lib/aws-logs"; import * as events from "aws-cdk-lib/aws-events"; import * as targets from "aws-cdk-lib/aws-events-targets"; -import { Architecture, Code, Function, Runtime } from "aws-cdk-lib/aws-lambda"; +import { Code, Function, Runtime } from "aws-cdk-lib/aws-lambda"; import { join } from "path"; import { SystemConfig } from "../shared/types"; @@ -134,37 +134,7 @@ export class ModelConstruct extends NestedStack implements ModelConstructOutputs }, }); rule.addTarget(new targets.LambdaFunction(modelTriggerLambda)); - - // const pipelineMonitorLambda = new Function(this, "PipelineMonitorLambda", { - // runtime: Runtime.PYTHON_3_12, - // handler: "pipeline_monitor.lambda_handler", - // code: Code.fromAsset(join(__dirname, "../../../lambda/pipeline_monitor")), - // timeout: Duration.minutes(10), - // memorySize: 512, - // environment: { - // DYNAMODB_TABLE: props.sharedConstructOutputs.modelTable.tableName, - // POST_LAMBDA: modelTriggerLambda.functionName, - // // Add a random UUID to force the custom resource to run on every deployment - // FORCE_UPDATE: new Date().toISOString() - // } - // }); - // pipelineMonitorLambda.addToRolePolicy(this.modelIamHelper.codePipelineStatement); - // pipelineMonitorLambda.addToRolePolicy(this.modelIamHelper.stsStatement); - - // // Create the custom resource provider to update open source model status - // const provider = new cr.Provider(this, "PipelineMonitorProvider", { - // onEventHandler: pipelineMonitorLambda, - // logRetention: logs.RetentionDays.ONE_WEEK - // }); - - // new CustomResource(this, "PipelineMonitorResource", { - // serviceToken: provider.serviceToken, - // resourceType: "Custom::CodePipelineMonitor", - // properties: { - // // Add a timestamp to force the custom resource to execute on every deployment - // UpdateTimestamp: new Date().toISOString() - // } - // }); + } }