介绍
在这篇文章中,我将探讨如何实现一个允许我们发送问题并接收答案的服务。发送问题将作为提示发送到使用 Bedrock 的特定模型。
为了最大限度地降低实施成本,我将利用 API Gateway、AWS Lambda 和 Amazon Bedrock 的服务。
语境
当我们想要实现生成式人工智能服务时,有几种可能的途径,我们必须花时间了解最低要求,然后再做决定。
在这篇文章中,我将演示如何部署一个简单的 GenAI 解决方案,我们可以将其连接到我们当前的解决方案,或者通过 API 网关将其暴露给第三方。
TLDR
此实现的所有内容都可以在此存储库中找到。
范围
对于本次实现,我将定义一组非常简单的需求。
下图展示了我们将要构建的实现方案。
高层图
功能需求
我们应该能够发送提示并从 Nova Micro 收到模型完成形式的响应。
我们应该能够公开一个 HTTP 端点来触发生成响应的过程。
为了便于成本计算,我们估计每月将处理 100 个请求。
非功能性需求
由于我们使用的是 AWS 无服务器服务,我们可以依靠其内置功能来满足我们的非功能性需求。
为了实现自动化,部署应完全由 GitHub Actions 处理。在可用性方面,API 需要保持每月 99.9% 或更高的正常运行时间。
在安全方面,我们需要对 Bedrock 进行 IAM 范围的访问控制,使用 OpenID Connect 进行身份验证,并确保所有流量都仅使用 HTTPS。
最后,为了便于观察,我们必须有结构化的日志来捕获元数据、令牌使用情况和任何错误,所有这些都要通过 CloudWatch 仪表板进行可视化。
简单的。
超出范围是什么?
在此实现中,我将排除身份验证、清理、授权以及应用于请求的任何安全流程。这种方法使我能够专注于 GenAI 的总体实现流程。
每项服务的成本细分
根据预定义的数据量,我们可以估计每次请求平均使用 22 个输入令牌和 232 个输出令牌。
Amazon Bedrock(Nova Micro)
Nova Micro 模型每月处理 2200 个输入代币和 23200 个输出代币,每月运行成本约为 0.003 美元。输出代币的成本约为输入代币的 4 倍。
AWS Lambda
假设只有 100 次调用,每次调用耗时 3 到 5 秒**,内存为 512MB,我们远未达到免费套餐的限制。Lambda 每月提供 100 万次请求和 40 万 GB 秒的免费流量。
API 网关
第一年,我们可以免费获得 100 万次请求。之后,每月费用可能约为 0.0004 美元。
亚马逊 ECR
ECR 中那 300MB 的 Docker 镜像每月大约只需 1 美分。在 500MB 的免费额度用完后,我们实际需要为大约 136MB 的存储空间付费。
当规模扩大时会发生什么?
如果每月请求量达到 1,000 次,费用约为 0.04 美元。如果达到 10,000 次请求,费用约为 0.39 美元。即使达到 100,000 次请求,我们每月也只需支付约 3.76 美元。
好的,我们继续。
执行
要开始实施,我们可以先创建代理。
创建代理
要创建代理,我首先需要设置完整的 TypeScript 项目。
我们可以按照这些说明快速推进,并安装必要的依赖项,以便项目能够正常运行。
您可以根据自己的需要选择最合适的配置。
mkdir -p handler terraform
cd handler
pnpm init -y
pnpm --package=typescript dlx tsc --init
mkdir -p src tests
touch src/{app,env,index}.ts
pnpm add -D @types/node tsx typescript
pnpm add ai @ai-sdk/amazon-bedrock
pnpm add zod dotenv
在这个项目中,我将定义三个基本部分:
与 AWS Lambda兼容的逻辑。
将请求作为提示进行管理的逻辑,根据这些提示请求文本生成,并处理它们未来可能的发展演变。
使用@ai-sdk基岩调用生成实际文本的核心逻辑。
以下是项目主要文件的概要。
import { main } from "./app";
export const handler = async (event: any, context: any) => {
try {
const body = event.body ? JSON.parse(event.body) : {};
const prompt = body.prompt ?? "Welcome from Warike technologies - GenAI solutions architecture";
const response = await main(prompt);
return {
statusCode: 200,
body: JSON.stringify({
success: true,
data: response,
}),
};
} catch (error) {
console.error('Error in Lambda handler:', error);
return {
statusCode: 500,
body: JSON.stringify({
success: false,
error: error instanceof Error ? error.message : 'An unexpected error occurred'
}),
};
}
};
// app.ts
import { generateResponse } from "./utils/bedrock";
export async function main(prompt: string) {
try {
console.log('🚀 Starting Bedrock:');
return await generateResponse(prompt)
} catch (error) {
console.error('An unexpected error occurred running workflow:', error);
throw error;
}
}
// utils/bedrock.ts
import { config } from "./config";
import { createAmazonBedrock } from '@ai-sdk/amazon-bedrock';
import { generateText } from 'ai';
export async function generateResponse(prompt: string){
const { regionId, modelId } = config({ });
try {
const bedrock = createAmazonBedrock({
region: regionId
});
const { text, usage } = await generateText({
model: bedrock(modelId),
system: "You are a helpful assistant.",
prompt: [
{ role: "user", content: prompt },
],
});
console.log(`model: {modelId}, \\n response: {text}, usage: ${JSON.stringify(usage)}`);
return text;
} catch (error) {
console.log(`ERROR: Can't invoke '{modelId}'. Reason: {error}`);
}
}
为了进行本地测试,我定义了以下环境变量。我们可以使用 AWS Bedrock API 密钥进行测试。
AWS_REGION=us-west-2
AWS_BEDROCK_MODEL='amazon.nova-micro-v1:0'
AWS_BEARER_TOKEN_BEDROCK='aws_bearer_token_bedrock'
强烈建议仅使用短期 API 密钥,以避免损害系统安全。
定义基础设施
现在系统的逻辑已经可以正常运行,我们可以创建它的 Dockerfile,这将有助于将其部署到 AWS Lambda。
---- Build Stage ----
FROM node:22-alpine AS builder
WORKDIR /usr/src/app
RUN corepack enable
COPY package.json pnpm-lock.yaml* ./
RUN pnpm install --frozen-lockfile
COPY . .
RUN pnpm run build
---- Runtime Stage ----
FROM public.ecr.aws/lambda/nodejs:22
WORKDIR ${LAMBDA_TASK_ROOT}
COPY --from=builder /usr/src/app/dist/src ./
COPY --from=builder /usr/src/app/node_modules ./node_modules
CMD [ "index.handler" ]
所有组件都已准备就绪,我们可以开始在 Terraform 中定义资源了。
通过 API 网关公开服务
我们将首先定义 API 网关。我们将使用 HTTP 协议,并且只专注于创建 API、其阶段以及后续与 Lambda 的集成。
locals {
api_gateway_name = "dev-http-${local.project_name}"
}
module "warike_development_api_gw" {
source = "terraform-aws-modules/apigateway-v2/aws"
version = "5.4.1"
name = local.api_gateway_name
description = "API Gateway for ${local.project_name}"
protocol_type = "HTTP"
create_domain_name = false
create_certificate = false
create_domain_records = false
cors_configuration = {
allow_headers = ["*"]
allow_methods = ["*"]
allow_origins = ["*"]
}
Access logs
stage_name = "dev"
stage_description = "Development API Gateway"
stage_access_log_settings = {
create_log_group = false
destination_arn = aws_cloudwatch_log_group.warike_development_api_gw_logs.arn
format = jsonencode({
context = {
requestId = "$context.requestId"
requestTime = "$context.requestTime"
protocol = "$context.protocol"
httpMethod = "$context.httpMethod"
resourcePath = "$context.resourcePath"
routeKey = "$context.routeKey"
status = "$context.status"
responseLength = "$context.responseLength"
integrationErrorMessage = "$context.integrationErrorMessage"
error = {
message = "$context.error.message"
responseType = "$context.error.responseType"
}
identity = {
sourceIP = "$context.identity.sourceIp"
}
integration = {
error = "$context.integration.error"
integrationStatus = "$context.integration.integrationStatus"
}
}
})
}
Routes & Integration
routes = {
"POST /" = {
integration = {
uri = module.warike_development_lambda.lambda_function_arn
payload_format_version = "2.0"
}
}
}
stage_tags = merge(local.tags, {
Name = "${local.api_gateway_name}-dev"
})
tags = merge(local.tags, {
Name = local.api_gateway_name
})
depends_on = [
aws_cloudwatch_log_group.warike_development_api_gw_logs,
]
}
resource "aws_cloudwatch_log_group" "warike_development_api_gw_logs" {
name = "/aws/api-gw/${local.api_gateway_name}"
retention_in_days = 7
}
连接到 Amazon Bedrock
接下来,为了利用 Amazon Bedrock,此实现将使用美国地区的 Amazon Nova Micro 推理配置文件。
locals {
model_id = "amazon.nova-micro-v1:0"
}
data "aws_bedrock_inference_profile" "warike_development_lambda_bedrock_model" {
inference_profile_id = "us.${local.model_id}"
}
Bedrock Policy
data "aws_iam_policy_document" "warike_development_lambda_bedrock_policy_doc" {
statement {
effect = "Allow"
actions = [
"bedrock:InvokeModel",
]
resources = ["*"]
}
}
Lambda 服务
最后,我们将创建与我们创建的所有组件以及存储库中其他组件关联的 Lambda 函数。
locals {
lambda_function_name = local.project_name
lambda_env_vars = {
AWS_BEDROCK_MODEL = data.aws_bedrock_inference_profile.warike_development_lambda_bedrock_model.inference_profile_arn
}
}
module "warike_development_lambda" {
source = "terraform-aws-modules/lambda/aws"
version = "8.1.2"
function_name = local.lambda_function_name
description = "Lambda function for ${local.project_name}"
image_uri = "${aws_ecr_repository.warike_development_ecr.repository_url}:latest"
package_type = "Image"
create_package = false
ignore_source_code_hash = true
memory_size = 128
timeout = 900
environment_variables = merge(
local.lambda_env_vars,
{}
)
create_role = false
lambda_role = aws_iam_role.warike_development_lambda_role.arn
Cloudwatch logging
use_existing_cloudwatch_log_group = true
logging_log_group = aws_cloudwatch_log_group.warike_development_lambda_logs.name
logging_log_format = "JSON"
logging_application_log_level = "INFO"
logging_system_log_level = "WARN"
function URL
create_lambda_function_url = false
depends_on = [
aws_cloudwatch_log_group.warike_development_lambda_logs,
null_resource.warike_development_seed_ecr_image
]
tags = merge(local.tags, {
Name = local.lambda_function_name
})
}
resource "null_resource" "warike_development_seed_ecr_image" {
provisioner "local-exec" {
command = <<EOT
aws ecr get-login-password --region {local.aws_region} --profile {local.aws_profile} \
| docker login --username AWS --password-stdin {data.aws_caller_identity.current.account_id}.dkr.ecr.{local.aws_region}.amazonaws.com
docker pull public.ecr.aws/lambda/nodejs:22
docker tag public.ecr.aws/lambda/nodejs:22 ${aws_ecr_repository.warike_development_ecr.repository_url}:latest
docker push ${aws_ecr_repository.warike_development_ecr.repository_url}:latest
EOT
}
depends_on = [aws_ecr_repository.warike_development_ecr]
}
重要提示:warike_development_seed_ecr_image需要您本地运行 Docker。
我们继续前进。
创建资源
资源创建完成后,您应该会看到类似这样的消息:
terraform apply
...
Apply complete! Resources: 26 added, 0 changed, 0 destroyed.
使用 GitHub + ECR 配置 CI/CD
此外,我们需要一个包含我们项目的 Docker 镜像。以下 GitHub 流水线将允许我们构建镜像并将其推送到 ECR,然后将其部署到 Lambda。
name: Lambda CI/CD www.hdpjng.com Common
'on':
workflow_call:
inputs:
app-name:
type: string
lambda-function-secret-name:
required: true
type: string
pnpm-version:
required: false
type: number
default: 10
node-version:
required: false
type: number
default: 22
secrets:
PROJECT_NAME:
required: true
AWS_OIDC_ROLE_ARN:
required: true
AWS_REGION:
required: true
ECR_REPOSITORY:
required: true
AWS_LAMBDA_FUNCTION_NAME:
required: true
AWS_LAMBDA_FUNCTION_ROLE_ARN:
required: true
jobs:
build:
name: Build and Test
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./handler
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: ${{ inputs.pnpm-version }}
- name: Use Node.js ${{ inputs.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
- name: Install Dependencies
run: pnpm install --frozen-lockfile
- name: Scan for critical vulnerabilities
run: pnpm audit --audit-level=critical
- name: Run Tests
env:
DOTENV_QUIET: true
run: pnpm test:ci
- name: Build
run: pnpm run build
build-docker:
name: Build and Push Docker Image
runs-on: ubuntu-latest
needs: build
permissions:
id-token: write
contents: read
outputs:
sha: ${{ steps.vars.outputs.sha }}
defaults:
run:
working-directory: ./handler
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_OIDC_ROLE_ARN }}
aws-region: ${{ secrets.AWS_REGION }}
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v2
with:
mask-password: 'true'
- name: Set commit-sha
id: vars
run: |
calculatedSha=(git rev-parse --short {{ github.sha }})
echo "sha={calculatedSha}" \>\> GITHUB_OUTPUT
- name: Build and Push Docker Image
env:
DOCKER_IMAGE: {{ secrets.ECR_REPOSITORY }}:{{ inputs.app-name }}-${{ steps.vars.outputs.sha }}
run: |
echo "Building Docker image $DOCKER_IMAGE"
docker build -t $DOCKER_IMAGE .
docker tag DOCKER_IMAGE "{{ secrets.ECR_REPOSITORY }}:{{ inputs.app-name }}-{{ steps.vars.outputs.sha }}"
docker tag DOCKER_IMAGE "{{ secrets.ECR_REPOSITORY }}:latest"
docker push $DOCKER_IMAGE
docker push "{{ secrets.ECR_REPOSITORY }}:{{ inputs.app-name }}-${{ steps.vars.outputs.sha }}"
docker push "${{ secrets.ECR_REPOSITORY }}:latest"
deploy-prod:
name: Deploy Production Lambda
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
needs: build-docker
permissions:
id-token: write
contents: read
defaults:
run:
working-directory: ./handler
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_OIDC_ROLE_ARN }}
aws-region: ${{ secrets.AWS_REGION }}
- name: Deploy Production Lambda
uses: aws-actions/aws-lambda-deploy@v1.1.0
with:
function-name: ${{ secrets.AWS_LAMBDA_FUNCTION_NAME }}
package-type: Image
image-uri: {{ secrets.ECR_REPOSITORY }}:{{ inputs.app-name }}-${{ needs.build-docker.outputs.sha }}
如果一切顺利,我们将能够测试亚马逊基岩版。
GitHub Actions
测试
我们来做个测试。在终端上,我们可以发出以下查询并观察结果:
curl -sS "https://123456.execute-api.us-west-2.amazonaws.com/dev/" \
-H "Content-Type: application/json" \
-d '{"prompt":"Heeey hoe gaat het?"}' | jq
预期输出结果类似于这样:
{
"success": true,
"data": "Hoi! Het gaat prima, bedankt voor het vragen. Hoe gaat het met jou? Is er iets waar ik je kan helpen of iets waar je graag over wilt praten?"
}
我认为这是一个成功。
可观测性
值得一提的是,我们可以通过CloudWatch监控任何错误,所以我们不必摸着石头过河。
CloudWatch 控制面板
打扫
最后,我们清理资源。
terraform destroy
...
Destroy complete! Resources: 26 destroyed.
结论
总的来说,使用 AWS 无服务器组件实现 GenAI 服务非常简单直接。
API Gateway、AWS Lambda 和 Amazon Bedrock 的组合,特别是搭配 Nova Micro 模型,不仅功能强大,而且极具成本效益。分析表明,即使手动大幅扩展流量,该解决方案的成本仍然非常低。
通过利用 Terraform 进行基础设施管理,利用 GitHub Actions 进行 CI/CD 流水线,我们实现了强大且完全自动化的部署流程。
最后,即使排除身份验证等因素,它也能为构建更复杂的生成式人工智能应用程序提供坚实且可扩展的基础。