1. 项目概述:当CDK遇见生成式AI

如果你是一名在AWS上构建应用的开发者,最近肯定被各种生成式AI(Generative AI)的新闻和工具轮番轰炸过。从ChatGPT的对话能力,到Midjourney的图片生成,再到各种代码助手,这股浪潮已经实实在在地拍到了我们开发者的工位上。AWS作为云服务巨头,自然不会缺席,它提供了一整套名为“Amazon Bedrock”的托管服务,让你可以安全、便捷地调用来自Anthropic、Meta、Cohere等多家顶尖公司的AI模型。

但问题来了:怎么把这些强大的AI能力,优雅、高效、可维护地集成到我们自己的云应用里?难道每次都要手动去AWS控制台点来点去,然后写一堆胶水代码把Lambda、API Gateway、IAM权限拼凑起来吗?这显然不符合我们追求“基础设施即代码”(IaC)和自动化部署的现代工程实践。

这就是 awslabs/generative-ai-cdk-constructs 这个项目诞生的背景。简单说,它是AWS实验室(AWSLabs)官方出品的一个AWS CDK构造库(Construct Library)。CDK,即Cloud Development Kit,允许你用熟悉的编程语言(如TypeScript、Python)来定义云资源。而这个构造库,则是在CDK之上,专门为生成式AI应用场景预置好的一组“乐高积木”。

你可以把它想象成一个“生成式AI应用快速启动工具箱”。它把创建Bedrock模型访问、搭建聊天机器人后端、实现文件内容解析与问答(RAG)、管理对话历史等常见且繁琐的任务,封装成了几个高级别的、开箱即用的CDK构造。你的角色从“泥瓦匠”(手动砌每一块砖)变成了“建筑师”(用预制件快速搭建稳固的房子)。我花了些时间深入研究并实际部署了几个用例,发现它确实能极大提升从创意到可运行原型的速度,并且保证了底层架构符合AWS的最佳实践。

2. 核心构造解析:你的AI应用积木箱

这个库目前提供了几个核心的构造(Construct),每一个都瞄准了一个具体的生成式AI集成模式。理解它们是高效使用这个库的关键。

2.1 BedrockKnowledgeBase :构建专属知识库的利器

这是我认为最实用、最能体现价值的构造。它完整实现了一个基于Amazon Bedrock Knowledge Bases的检索增强生成(RAG)系统。RAG的核心思想是,让大模型在回答问题时,能够参考你提供的私有文档(如公司手册、产品文档、支持案例),从而生成更准确、更相关、且能溯源的内容。

手动搭建一个生产可用的RAG系统非常复杂:你需要一个向量数据库(如OpenSearch),一个文档爬取和分块的流水线,一个将文本转换成向量的嵌入模型(Embedding Model),以及一套将用户问题、检索到的文档片段和提示词组合起来发给大模型的逻辑。 BedrockKnowledgeBase 把这个过程浓缩成了几行代码。

import { BedrockKnowledgeBase } from ‘aws-cdk-lib/generative-ai-constructs’;

const kb = new BedrockKnowledgeBase(this, ‘MyKB’, {
  embeddingsModel: BedrockFoundationModel.TITAN_EMBEDDINGS_G1_TEXT_V1, // 使用Titan Embeddings模型
  vectorStore: {
    // 使用托管的OpenSearch Serverless作为向量存储
    openSearchServerless: {
      collectionName: ‘my-vector-collection’,
      vectorField: ‘embedding’,
      textField: ‘content’,
    },
  },
  instruction: ‘请根据提供的文档内容,用中文回答用户的问题。如果文档中没有相关信息,请如实告知。’,
});

上面这段CDK代码,会在你的AWS账户中自动创建并配置好一个Bedrock知识库。接下来,你只需要通过SDK或控制台,将你的PDF、TXT、HTML、Word等格式的文档同步到指定的S3桶,Bedrock服务就会自动完成后续的所有处理:文本提取、分块、向量化、索引。

注意 : BedrockKnowledgeBase 构造目前主要与Bedrock Knowledge Bases服务深度集成。这意味着你的向量存储选择被限定在AWS托管的选项内,主要是OpenSearch Serverless和Pinecone(通过AWS Marketplace)。如果你已有的向量数据库是自建的Chroma或Weaviate,则需要通过自定义Lambda函数来桥接。

2.2 ChatBot :快速部署一个智能对话接口

如果你想快速创建一个具有记忆功能的聊天机器人API, ChatBot 构造是你的首选。它不仅仅是一个简单的模型调用包装,而是集成了对话历史管理、流式响应等关键功能。

import { ChatBot } from ‘aws-cdk-lib/generative-ai-constructs’;

const chatbot = new ChatBot(this, ‘MyChatBot’, {
  foundationModel: BedrockFoundationModel.CLAUDE_3_SONNET, // 使用Claude 3 Sonnet模型
  conversationHistory: {
    // 启用对话历史,并存储在DynamoDB中
    storageType: ConversationHistoryStorageType.DYNAMODB,
    tableName: ‘ChatHistoryTable’,
  },
  streaming: true, // 启用流式响应,提升用户体验
});

部署后,这个构造会生成一个API Gateway端点和一个处理请求的Lambda函数。前端应用只需要向这个端点发送POST请求(包含用户消息和可选的会话ID),就能收到模型生成的回复。对话历史会被自动保存到DynamoDB,并在同一会话ID的后续请求中被检索出来,作为上下文提供给模型,从而实现连贯的多轮对话。

实操心得 : ChatBot 构造默认生成的API接口比较简单,通常只包含一个 /chat 路径。在实际项目中,你可能需要根据业务逻辑添加鉴权(如Cognito)、输入验证、或与其他服务(如用户系统)的集成。这时,更常见的做法是将这个构造生成的Lambda函数ARN作为“AI处理核心”,嵌入到你自己的、更复杂的API Gateway或AppSync架构中。

2.3 Bedrock :基础模型调用的安全通道

当你需要更灵活地调用Bedrock模型,而不是使用预置的聊天或知识库模式时, Bedrock 构造提供了最基础的集成能力。它的主要价值在于帮你处理好最麻烦的部分:IAM权限。

import { Bedrock } from ‘aws-cdk-lib/generative-ai-constructs’;

const bedrock = new Bedrock(this, ‘InvokeModel’, {
  // 允许调用指定的模型
  models: [BedrockFoundationModel.CLAUDE_3_HAIKU],
});

// 你的Lambda函数可以通过授予的权限直接调用Bedrock API
const myFunction = new lambda.Function(this, ‘MyFunction’, {
  // ... 其他配置
  environment: {
    // 可以传递模型ID等信息
  },
});

// 自动附加正确的调用权限
bedrock.grantInvokeModel(myFunction);

使用这个构造,你无需再手动编写复杂的IAM策略语句来允许Lambda或ECS任务调用 bedrock:InvokeModel ,并且精确限定到某个具体的模型ID。它遵循了最小权限原则,是构建自定义AI工作流(如内容审核、文本摘要、分类)的一个干净利落的基础组件。

3. 实战:从零构建一个智能文档问答API

理论讲得再多,不如动手做一遍。我们用一个完整的例子,串联起上述构造,构建一个后端API:用户上传PDF到S3,系统自动将其内容录入知识库;用户可以通过聊天接口,针对已录入的文档提问。

3.1 项目初始化与架构设计

首先,确保你已安装AWS CDK并配置好凭证。我们使用TypeScript来创建项目。

mkdir smart-doc-qa && cd smart-doc-qa
cdk init app --language typescript
npm install @aws-cdk/generative-ai-constructs

我们的架构将包含以下核心组件:

  1. 一个S3桶,用于存储用户上传的原始文档。
  2. 一个 BedrockKnowledgeBase ,作为处理和分析文档的大脑。
  3. 一个 ChatBot ,提供问答接口。
  4. 一个“文档同步”Lambda函数,当新文档上传到S3时,自动触发Bedrock知识库的数据源同步。
  5. (可选)一个前端上传界面或使用AWS Amplify。

3.2 基础设施代码实现

在 lib/smart-doc-qa-stack.ts 中,我们开始编写核心栈。

import * as cdk from ‘aws-cdk-lib’;
import * as s3 from ‘aws-cdk-lib/aws-s3’;
import * as lambda from ‘aws-cdk-lib/aws-lambda’;
import * as events from ‘aws-cdk-lib/aws-events’;
import * as targets from ‘aws-cdk-lib/aws-events-targets’;
import * as iam from ‘aws-cdk-lib/aws-iam’;
import { Construct } from ‘constructs’;
import { BedrockKnowledgeBase, ChatBot, BedrockFoundationModel } from ‘@aws-cdk/generative-ai-constructs’;

export class SmartDocQaStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // 1. 创建存储原始文档的S3桶
    const documentBucket = new s3.Bucket(this, ‘DocumentBucket’, {
      autoDeleteObjects: true, // 仅为演示,生产环境请谨慎设置
      removalPolicy: cdk.RemovalPolicy.DESTROY,
      eventBridgeEnabled: true, // 启用EventBridge事件,用于触发同步
    });

    // 2. 创建Bedrock知识库
    const knowledgeBase = new BedrockKnowledgeBase(this, ‘DocKnowledgeBase’, {
      knowledgeBaseName: ‘CompanyDocumentsKB’,
      embeddingsModel: BedrockFoundationModel.TITAN_EMBEDDINGS_G1_TEXT_V1,
      vectorStore: {
        openSearchServerless: {
          collectionName: ‘doc-vectors’,
          vectorField: ‘embedding’,
          textField: ‘content’,
        },
      },
      instruction: ‘你是一个专业的文档助手。请严格根据提供的公司内部文档内容,用清晰、准确的中文回答用户的问题。如果文档中没有足够信息来回答问题,请直接说“根据现有文档,我无法回答这个问题”,不要编造信息。回答时请引用相关的文档片段。‘,
      dataSource: {
        // 指定知识库从哪个S3桶拉取数据
        s3Bucket: documentBucket,
        s3Prefix: ‘uploads/’, // 可以指定子目录
      },
    });

    // 3. 创建问答聊天机器人
    const chatbot = new ChatBot(this, ‘DocChatBot’, {
      chatbotName: ‘DocumentQABot’,
      foundationModel: BedrockFoundationModel.CLAUDE_3_SONNET,
      knowledgeBases: [knowledgeBase], // **关键步骤**:将聊天机器人与知识库关联
      conversationHistory: {
        storageType: ConversationHistoryStorageType.DYNAMODB,
        tableName: ‘ChatHistoryTable’,
      },
      streaming: true,
    });

    // 4. 创建文档同步Lambda函数
    const syncFunction = new lambda.Function(this, ‘SyncTriggerFunction’, {
      runtime: lambda.Runtime.PYTHON_3_12,
      handler: ‘index.handler’,
      code: lambda.Code.fromAsset(‘lambda/sync-trigger’),
      environment: {
        KNOWLEDGE_BASE_ID: knowledgeBase.knowledgeBaseId,
        DATA_SOURCE_ID: knowledgeBase.dataSourceId,
        REGION: this.region,
      },
    });

    // 授予Lambda函数调用Bedrock知识库同步API的权限
    syncFunction.addToRolePolicy(new iam.PolicyStatement({
      actions: [‘bedrock:StartIngestionJob’],
      resources: [knowledgeBase.knowledgeBaseArn],
    }));

    // 5. 配置S3事件触发Lambda
    documentBucket.addEventNotification(
      s3.EventType.OBJECT_CREATED,
      new s3n.LambdaDestination(syncFunction),
      { prefix: ‘uploads/’ } // 仅监听uploads/目录下的新文件
    );
  }
}

3.3 Lambda函数代码详解

上面的CDK栈引用了一个Lambda函数。我们需要在 lambda/sync-trigger 目录下创建 index.py 文件。

import os
import boto3
import json

bedrock_agent = boto3.client(‘bedrock-agent’, region_name=os.environ[‘REGION’])

def handler(event, context):
    print(“Received S3 event: “, json.dumps(event))
    
    # 从环境变量获取知识库和数据源ID
    knowledge_base_id = os.environ[‘KNOWLEDGE_BASE_ID’]
    data_source_id = os.environ[‘DATA_SOURCE_ID’]
    
    try:
        # 调用Bedrock Agent的StartIngestionJob API,触发知识库同步
        response = bedrock_agent.start_ingestion_job(
            knowledgeBaseId=knowledge_base_id,
            dataSourceId=data_source_id
        )
        job_id = response[‘ingestionJob’][‘ingestionJobId’]
        print(f”Successfully started ingestion job: {job_id}“)
        return {
            ‘statusCode’: 200,
            ‘body’: json.dumps(f’Sync job {job_id} started.’)
        }
    except Exception as e:
        print(f”Error starting ingestion job: {e}“)
        return {
            ‘statusCode’: 500,
            ‘body’: json.dumps(f’Failed to start sync: {str(e)}’)
        }

这个函数非常简单:当有新的文档被上传到S3的 uploads/ 路径下时,它会被触发,然后调用Bedrock的API,告诉指定的知识库:“有新的数据来了,请开始处理(爬取、分块、向量化、索引)。”

3.4 部署与测试

在项目根目录下,运行部署命令:

cdk bootstrap  # 如果在该区域首次部署CDK
cdk deploy

部署成功后,CDK会输出两个关键信息:

  1. DocumentBucket 的桶名。
  2. DocChatBot 的API Gateway端点URL。

测试流程如下:

  1. 上传文档 :将一个PDF文件(比如一份产品说明书)上传到输出中给出的S3桶的 uploads/ 文件夹下。你可以使用AWS控制台或AWS CLI ( aws s3 cp your-file.pdf s3://<bucket-name>/uploads/ )。
  2. 等待同步 :上传后,等待几分钟让Lambda触发同步,并由Bedrock完成文档处理。你可以在AWS控制台的Bedrock Knowledge Bases页面查看同步状态。
  3. 发起问答 :使用 curl 或Postman向聊天机器人端点发送请求。
curl -X POST “https://<your-api-id>.execute-api.<region>.amazonaws.com/prod/chat” \
  -H “Content-Type: application/json” \
  -d ‘{
    “sessionId”: “user-123”,
    “message”: “请问我们产品的保修期是多久?”
  }’

如果一切顺利,你将收到一个基于你上传的PDF内容生成的、带有引用的回答。

4. 深入原理:构造库如何简化你的工作

使用这个构造库,你节省的远不止是几十行CDK代码。它背后封装了一系列AWS最佳实践和安全考量。

4.1 权限管理的自动化与最小化 IAM是AWS安全的核心,也是最容易出错的地方。手动为Lambda函数配置调用Bedrock的权限,你需要知道确切的模型ARN、动作( InvokeModel , InvokeModelWithResponseStream )等。 Bedrock 和 ChatBot 构造通过 grantInvokeModel 这类方法,自动生成精确的策略。例如, ChatBot 构造不仅会授予模型调用权限,还会为关联的DynamoDB表创建读写策略。这确保了你的应用遵循最小权限原则,减少了安全风险。

4.2 复杂服务关系的预配置 一个 BedrockKnowledgeBase 背后涉及至少5个AWS服务:Bedrock(模型)、S3(数据源)、OpenSearch Serverless(向量存储)、KMS(加密)、IAM(权限)。手动用CDK的底层 Cfn* 资源来组装它们,需要深入理解这些服务之间的依赖关系和配置参数。构造库将这些关系内化,提供了经过验证的默认配置。比如,它会自动为OpenSearch Serverless集合配置与Bedrock服务主体的正确信任关系,这是手动配置时一个常见的坑点。

4.3 成本与性能的平衡点 构造库的默认选择往往考虑了成本与性能的平衡。例如, BedrockKnowledgeBase 默认使用OpenSearch Serverless,这是一种按实际计算和存储用量收费的托管服务。对于大多数中小型知识库应用来说,这比自建OpenSearch集群或使用按小时计费的实例要划算得多,也免去了容量规划和集群维护的负担。当然,如果你有极高的吞吐量需求,构造库也允许你通过 vectorStore 属性进行更精细的配置。

5. 进阶使用与自定义扩展

虽然开箱即用很方便,但真实项目总有特殊需求。这个构造库在设计时也考虑到了扩展性。

5.1 自定义提示词工程 ChatBot 和 BedrockKnowledgeBase 都允许你传入自定义的 instruction (系统提示词)。这是控制AI行为的最重要杠杆。不要满足于默认提示词。根据你的场景精心设计提示词,能极大提升回答质量。例如,在客服场景中,你可以加入“语气必须友好、耐心”、“如果用户问题涉及售后流程,请引导用户提供订单号”等具体指令。

5.2 与其他CDK构造混合使用 不要把这些生成式AI构造看成黑盒。它们本身就是标准的CDK构造,可以和你已有的任何CDK资源无缝结合。比如:

  • 与Cognito集成 :在 ChatBot 生成的API Gateway前,加上一个Cognito用户池授权器,实现用户认证。
  • 添加WAF :为API Gateway附加AWS WAF,防止恶意攻击。
  • 启用X-Ray追踪 :为Lambda函数启用AWS X-Ray,追踪整个AI调用链路的性能和错误。
import * as apigateway from ‘aws-cdk-lib/aws-apigateway’;
import * as cognito from ‘aws-cdk-lib/aws-cognito’;

const userPool = new cognito.UserPool(this, ‘UserPool’);
const authorizer = new apigateway.CognitoUserPoolsAuthorizer(this, ‘ChatAuthorizer’, {
  cognitoUserPools: [userPool],
});

// 假设我们通过属性拿到了chatbot内部的API
const internalApi = chatbot.api; // 这需要构造库暴露此属性,请查阅最新文档
internalApi.addMethod(‘POST’, new apigateway.LambdaIntegration(chatbot.handler), {
  authorizer,
  authorizationType: apigateway.AuthorizationType.COGNITO,
});

5.3 处理复杂业务逻辑 当你的聊天机器人需要查询数据库、调用外部API后再结合AI生成回答时,预置的 ChatBot 构造可能就不够用了。这时,更佳的模式是使用基础的 Bedrock 构造来获得模型调用权限,然后在你自己的、业务逻辑复杂的Lambda函数中,编排整个工作流。

const bedrockConstruct = new Bedrock(this, ‘MyBedrock’, {
  models: [BedrockFoundationModel.CLAUDE_3_HAIKU],
});

const businessLogicFunction = new lambda.Function(this, ‘BusinessLogic’, {
  runtime: lambda.Runtime.PYTHON_3_12,
  handler: ‘index.handler’,
  code: lambda.Code.fromAsset(‘lambda/business’),
  environment: {
    MODEL_ID: BedrockFoundationModel.CLAUDE_3_HAIKU.modelId,
  },
});

// 只授予模型调用权限,业务逻辑完全自己控制
bedrockConstruct.grantInvokeModel(businessLogicFunction);
// 再授予函数其他必要的权限,如DynamoDB读、SQS发送等

6. 常见问题、成本考量与优化建议

在实际使用中,你可能会遇到以下问题:

6.1 知识库同步失败 这是最常见的问题。首先去Bedrock控制台的知识库页面,查看同步作业的详细状态和日志。常见原因有:

  • S3权限不足 :确保Bedrock服务角色有权限读取你指定的S3桶和前缀。构造库通常会尝试自动设置,但跨账户或复杂桶策略时可能失败。
  • 文件格式不支持或损坏 :确保上传的是Bedrock支持的格式(PDF, TXT, DOCX等),并且文件没有损坏。
  • 文件过大或过于复杂 :对于超大PDF或扫描件图片过多的文件,处理可能会超时或失败。考虑在同步前对文档进行预处理(如拆分大文件、OCR识别扫描件)。

6.2 回答质量不佳 如果模型回答不准确或胡言乱语:

  • 检查检索效果 :在知识库控制台测试检索,看对于你的问题,系统返回的文档片段是否相关。如果不相关,可能需要调整文档分块大小(chunk size)或重叠度(overlap),这些参数可以在创建数据源时配置。
  • 优化提示词 : instruction 至关重要。明确告诉模型你的文档领域、回答风格、以及如何处理未知问题。多次迭代测试你的提示词。
  • 选择合适模型 :不同的基础模型在理解指令、逻辑推理和中文能力上差异很大。Claude 3系列通常指令遵循能力很强,而Llama 3可能在某些创意任务上表现更好。通过Bedrock的模型测试功能进行对比。

6.3 成本控制 生成式AI应用的成本主要来自三部分:

  1. 模型推理 :按输入/输出的Token数计费。使用Haiku等小型模型进行简单任务,使用Sonnet/Opus处理复杂任务,是控制成本的有效策略。在 ChatBot 构造中,你可以根据请求路径或用户等级,动态选择不同的模型。
  2. 向量数据库 :OpenSearch Serverless按计算单元(OCU)和存储收费。保持高效的索引策略,定期清理过时数据,对于不活跃的知识库可以考虑暂停计算资源。
  3. Embedding(向量化) :文档初次同步和更新时会产生Embedding成本。Titan Embeddings按Token计费。避免频繁同步未变化的文档,对新增内容进行增量同步。

一个实用的优化技巧是:在 ChatBot 中实现“缓存层”。对于常见、重复的问题,可以将问答对缓存到DynamoDB或ElastiCache中,下次直接返回缓存结果,避免不必要的模型调用。

6.4 延迟与超时 流式响应( streaming: true )能极大改善用户感知的延迟,因为用户可以边生成边看到结果。但要注意,API Gateway和Lambda都有默认的超时限制(29秒和15分钟)。对于处理长文档或复杂推理,可能需要调整Lambda超时时间,或者将耗时任务拆分为异步流程。

最后,这个 awslabs/generative-ai-cdk-constructs 项目目前仍处于开发者预览阶段,这意味着API和功能可能会发生变化。在将其用于生产环境前,务必仔细阅读官方文档和发布说明,并做好充分的测试。不过,它无疑为我们在AWS上快速、规范地拥抱生成式AI,指明了一条高效的道路。它把云资源的复杂性封装起来,让我们能更专注于提示词工程、业务逻辑和用户体验这些真正创造价值的部分。

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐