✔️ 我们介绍Amazon Bedrock代理是什么。
✔️ 我们聊聊AWS的架构。
✔️ 我们来解析TypeScript和AWS CDK的代码。
✔️ 我们来做一些测试,看看它实际运行的效果。
Amazon Bedrock Agents 让您能够创建和自定义公司内的自主代理,这些代理能够为您完成任务。这些代理利用公司的数据和用户的输入,通过对话聊天和人工智能技术帮助员工完成任务。
它们就像协调者一样,管理基础模型、数据源和知识库、软件应用和用户对话之间的交互。此外,它们自动调用 API 来执行任务,并访问知识库来丰富与这些任务相关的信息。
在这篇文章中,我们将介绍一家名为LJ Resorts的虚构酒店和水疗公司,以便讨论其AWS架构和代码。客户可以通过我们提供的应用一次性预订所有项目,包括酒店住宿服务、水疗疗程和护理;还可以查询公司详情,如提供的护理服务种类、优惠信息和营业时间。
代码库位于这里。
Serverless Advocate 模式和解决方案库👇 在我们继续之前 — 请在领英上关注我,点击这里https://www.linkedin.com/in/lee-james-gilmore/ 以便获得关于无服务器技术的新闻和未来的博客文章。
亚马逊的Bedrock Agents是什么?🤖我们现在来聊聊Amazon Bedrock Agents是什么,它们怎么运作,先来看看几个重要的缩写。
缩写词在我们开始之前,先来解释一下这些缩写代表什么意思。
✔️ FMs — 基石模型。
在Amazon Bedrock中,FM代表基础模型(Foundational Model)。Amazon Bedrock通过一个简单的API接口,提供来自Amazon及其他第三方模型提供商的预训练深度学习模型的访问权限。
✔️ 行动小组。
您可以通过提供一个OpenAPI模式来定义代理可执行的任务的API操作,并提供一个Lambda函数来处理编排过程中根据模式识别的输入API操作和参数,并返回API调用的结果作为输出结果。
✔️ 指示如下:
你编写描述代理功能的指令。通过高级指令,你可以在编排的每一步进一步定制指令,并包含Lambda函数以解析每个步骤的输出。在我们这个例子中,它是:
🤖 “帮助客户预订酒店房间、水疗疗程和预订服务;同时根据日期和预订类型向他们提供任何特别优惠,在他们完成预订前,向他们说明任何营业时间和价格,还要留意我们的酒店规定。”
亚马逊Bedrock的代理工作原理是怎样的?如下的图所显示,我们从客户的输入信息开始,利用提示存储进行增强处理,用从会话存储中获取的先前对话历史作为上下文,最后调用基础模型。
接下来,编排程序解析响应,根据我们在OpenAPI规范文档中定义的操作,调用相应的Lambda动作组,并从Bedrock知识库中检索所需的额外信息。编排完成后,将结果返回给客户。
https://docs.aws.amazon.com/bedrock/latest/userguide/agents-how.html (网址保持不变)
现在让我们来看看这篇文章里我们要建的东西。我们就可以浏览代码、部署并测试这个应用。
要构建什么?🛠️那么现在,我们已经详细了解了亚马逊Bedrock中的代理,并且了解了它们的基本原理。接下来我们来看看这篇文章我们要搭建什么。
注: 在之前的文章中,我们已经介绍了如何配置 Amazon Bedrock 知识库,因此本文将不再赘述。
这是我们正在构建的一个例子。提示:在我们之前的帖子中提到了知识库。
我们在之前的文章中介绍了亚马逊Bedrock知识库系统,可以在这里查看具体内容。
Amazon Bedrock 知识库系统与私有数据通过 TypeScript 和 AWS CDK,你可以把知识库集成到 Amazon Bedrock 中,以提供基础模型的支持…blog.serverlessadvocate.com从上图可以看出:
- 客户通过提示触发Lambda函数的URL。
- 这会调用一个查询Lambda函数,该函数然后调用Amazon Bedrock代理并传入请求。
- 代理利用已部署的知识库(包含公司特有的数据)以及根据存储在S3桶中的OpenAPI规范的Lambda函数来对其他内部系统进行路由,进而完成客户的任务(它会为我们协调多个调用以完成任务)。
让我们在下一节来看一个互动的示例。现在我们已经讨论了高层次架构。
这样的互动可能是什么样子?下面是一个例子,展示了客户与代理聊天时可能的情况,这实际上是我们在示例系统中,使用真实提示和输出运行时的样子。
顾客:“嘿,现在酒店有什么特别的优惠活动吗?”
代理 :“以下酒店特别优惠目前可用: — 周一宅度假优惠:房费八折(仅限周一) — 最后一刻逃离:同日预订七五折优惠(仅限周二) — 长住优惠:三晚住宿八折(仅限周三) — 套房升级:免费升级到行政套房(仅限周四) — 周末特惠套餐:两晚住宿九折优惠(仅限周五)”
📘 注意 :上述互动请查阅我们的知识库以获取更多信息。
顾客 :“我想在2024年2月25日预订一个酒店房间,有什么房间可以预订?”
前台 :“2024年2月25日可用的房间如下: — 房间101(标准间,$100):温馨舒适的标准间,配有大号床。 — 房间102(标准间,$100):宽敞的标准间,配有两张双人床。 — 房间103(豪华间,$150):豪华的房间,配有特大号床和景观。 — 房间104(套房,$200):雅致的套房,配有独立起居和卧室。 — 房间105(标准间,$90):……”
➡️注意: 上述互动通过我们自己的Lambda函数调用了外部服务。
顾客: “我可以预定一间豪华房在2024年02月25日,160美元吗?”
前台服务员:「我已经为您在2024年2月25日预订了109号房间,这是一间配有豪华浴室的豪华套房,价格为160美金。如果您还需要其他帮助,请告诉我。」
➡️注意 : 上述交互通过我们的Lambda函数调用了我们的外部服务。
客人:“有什么 SPA 项目可以预约?”
代理 :“今天可用的水疗项目有:—— 瑞典按摩:全身按摩,帮助放松,缓解紧张。费用:80元 —— 面部护理:深层清洁面部护理,让肌肤恢复活力。费用:70元 —— 热石按摩:能舒缓肌肉,改善血液循环。费用:90元 —— 香薰按摩:使用芳香精油进行轻柔按摩,促进放松和提升幸福感。费用:85元 —— …..{其余内容省略}”
➡️ 请注意 :这次交流通过我们的Lambda函数调用外部服务。
顾客: “可以帮我预约一下热石按摩吗?”
服务员: “是的,我已经为您预订了热石按摩。这种按摩疗法使用加热的石头帮助放松肌肉并促进血液循环。费用是90美元,请问还需要其他帮助吗?如果您还需要其他帮助,请告诉我哦!”
➡️ 注意 :上述互动通过我们的Lambda函数调用了外部服务。
顾客:“你们的营业时间是几点呢?”
工作人员 :“LJ度假村的营业时间是:周一至周五:上午9点 — 下午6点 周六及周日:上午7点 — 晚上7点”
注意 :可以查阅我们的知识库获得更多相关信息。
顾客:“那天早上你们有安排吗?”
代理:“查看今天的可用时段,我发现有一个上午时段可用:上午场:上午来一场高尔夫球,享受美景。费用:80美元。如果您想预订上午的高尔夫时段,请告诉我。”
➡️ 注意 :上述互动我们通过Lambda函数调用了一下外部服务。
顾客:那天我可以预约一个80美元的时段吗?
代理:“是的,今天上午我为您预订了一个时段,费用是80美元。这个时段让您可以在上午打一场高尔夫,同时欣赏美景。如果您还需要其他帮助或者有什么问题,请随时告诉我!”
➡️ 提示 :上述互动通过我们的Lambda函数调用了我们的外部服务
客户: “我所有的预订加起来一共多少钱?”
客服:“根据我的对话历史,您预订了一个90美元的热石按摩疗程和一个80美元的早晨预约时段。您预订的总金额是90美元加80美元,共计170美元。”
如下序列图可以总结这一点。
这展示了Bedrock Agents的强大之处。原本需要一个人访问许多不同的系统并与客户面对面交流的工作,现在AI可以自动化整个流程,从头到尾。
我们现在看了客户和代理之间可能发生的互动,接下来咱们看看关键代码吧。
聊关键代码 👨💻好的,所以我们已经见识了这个基本示例的运行,现在我们来瞧瞧TypeScript和CDK的代码吧。请记住,完整的解决方案可以在那里找到:here。
有状态栈让我们从我们的Stateful
栈开始,先创建一个Lambda函数。
// 为代理创建Lambda函数 - 这个Lambda函数决定了提示与模式映射相关的外观
const actionGroupAgentLambda: nodeLambda.NodejsFunction =
new nodeLambda.NodejsFunction(本, 'AgentLambda', {
functionName: 'action-group-executor',
runtime: lambda.Runtime.NODEJS_20_X,
entry: path.join(
__dirname,
'./src/adapters/primary/action-group-executor/action-group-executor.adapter.ts'
),
memorySize: 1024,
handler: 'handler',
timeout: cdk.Duration.minutes(5), // 5分钟的持续时间
description: '操作组执行器的Lambda函数',
architecture: lambda.Architecture.ARM_64,
tracing: lambda.Tracing.ACTIVE,
bundling: {
minify: true,
},
environment: {
...lambdaConfig,
},
});
我们接着按照下面的方式创建Amazon Bedrock代理:
# 原始代码保持不变
(注:此处应删除“原始代码保持不变”以保持一致)
最终翻译如下:
我们接着按照下面的方式创建Amazon Bedrock代理:
// 创建Bedrock代理
const agent = new bedrock.Agent(this, 'BedrockAgent', {
name: 'Agent',
description: '用于酒店、水疗和高尔夫球预订的代理人。',
foundationModel: bedrock.BedrockFoundationModel.ANTHROPIC_CLAUDE_V2,
instruction:
'请帮助我们的客户预订酒店房间、水疗疗程和高尔夫球预订;同时根据日期和预订类型向他们提供任何特殊优惠,在他们完成预订之前让他们了解任何开放时间和价格,并考虑我们的酒店政策。',
idleSessionTTL: cdk.Duration.minutes(10),
knowledgeBases: [kb],
shouldPrepareAgent: true,
alias: 'Agent',
});
我们可以从上面的代码中看到,我们给Agent赋予了如下的关键属性:会话应持续多久,链接至我们的Amazon Bedrock知识库,FM类型(我们的情况为Claud V2),以及该Agent的指令。
我们接下来按照下面的方式创建我们的工作组:
// 添加用于预订的操作组
new bedrock.AgentActionGroup(this, 'AgentActionGroup', {
actionGroupName: 'agent-action-group',
description: '描述',
agent: agent,
apiSchema: bedrock.S3ApiSchema.fromAsset(
path.join(__dirname, './schema/api-schema.json')
),
actionGroupState: '启用',
actionGroupExecutor: actionGroupAgentLambda,
shouldPrepareAgent: true,
});
我们可以看到,我们提供了一个开放API规范,详细描述了代理可以做什么,以及作为动作代理的Lambda函数。现在我们来看一下这个开放API规范是什么样的。
{
"openapi": "3.0.0",
"info": {
"title": "LJ度假村酒店、水疗及高尔夫预订API",
"version": "1.0.0",
"description": "用于管理客户酒店、水疗及高尔夫预订的API。"
},
"paths": {
"/rooms": {
"get": {
"summary": "查看可用房间",
"description": "获取指定日期的所有可用房间",
"operationId": "getAllAvailableRooms",
"responses": {
"200": {
"description": "获取房间列表",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"type": "object",
"properties": {
"roomId": {
"type": "string",
"description": "房间唯一编号。"
},
"roomType": {
"type": "string",
"description": "房间类型。"
},
"roomDescription": {
"type": "string",
"description": "房间描述。"
},
"date": {
"type": "string",
"description": "房间空闲的日期。"
},
"cost": {
"type": "string",
"description": "房间每晚的费用。"
}
}
}
}
}
}
}
}
},
"post": {
"summary": "预订特定日期的可用房间",
"description": "预订特定日期的房间",
"operationId": "bookRoom",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"roomId": {
"type": "string",
"description": "预订房间的编号"
},
"date": {
"type": "string",
"description": "预订日期"
}
},
"required": ["roomId", "date"]
}
}
}
},
"responses": {
"200": {
"description": "房间预订成功了"
}
}
}
},
"/spa-sessions": {
"get": {
"summary": "查看所有可用的水疗项目",
"description": "获取指定日期的所有可用水疗项目",
"operationId": "getAllAvailableSpaTreatments",
"responses": {
"200": {
"description": "获取水疗项目列表",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"type": "object",
"properties": {
"treatmentId": {
"type": "string",
"description": "水疗项目的唯一编号。"
},
"treatmentType": {
"type": "string",
"description": "水疗项目种类。"
},
"treatmentDescription": {
"type": "string",
"description": "水疗项目的描述。"
},
"date": {
"type": "string",
"description": "预订日期"
},
"cost": {
"type": "string",
"description": "水疗项目的费用。"
}
}
}
}
}
}
}
}
},
"post": {
"summary": "预订特定日期的可用水疗项目",
"description": "预订特定日期的水疗项目",
"operationId": "bookSpaTreatment",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"treatmentId": {
"type": "string",
"description": "预订的水疗项目编号"
},
"date": {
"type": "string",
"description": "预订日期"
}
},
"required": ["treatmentId", "date"]
}
}
}
},
"responses": {
"200": {
"description": "水疗项目预订成功"
}
}
}
},
"/g-sessions": {
"get": {
"summary": "查看所有可用的高尔夫时段",
"description": "获取指定日期的所有可用高尔夫时段",
"operationId": "getAllAvailableGolfSessions",
"responses": {
"200": {
"description": "获取高尔夫时段列表",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"type": "object",
"properties": {
"sessionId": {
"type": "string",
"description": "高尔夫项目的唯一编号。"
},
"sessionType": {
"type": "string",
"description": "高尔夫项目种类。"
},
"sessionDescription": {
"type": "string",
"description": "高尔夫项目描述。"
},
"date": {
"type": "string",
"description": "预订日期"
},
"cost": {
"type": "string",
"description": "高尔夫项目的费用。"
}
}
}
}
}
}
}
}
},
"post": {
"summary": "预订特定日期的可用高尔夫时段",
"description": "预订特定日期的高尔夫时段",
"operationId": "bookGolfSession",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"sessionId": {
"type": "string",
"description": "预订的高尔夫项目编号"
},
"date": {
"type": "string",
.
"description": "预订日期"
}
},
"required": ["sessionId", "date"]
}
}
}
},
"responses": {
"200": {
"description": "高尔夫预订成功"
}
}
}
}
}
}
需要注意的是,我们的模型将用于确定应执行哪些任务的描述、路径、方法和操作ID。例如,当列出所有酒店房间时,它会使用特定的方法和操作ID(例如,HotelRoomListOperationId)。
说明 — “获取给定日期下所有可用房间的列表”。
操作ID — ‘getAllAvailableRooms
’。这里的操作ID是指获取所有可用房间的操作标识。
路径 — ‘/rooms
’。这里的路径指房间资源的URL路径。
方法 — ‘GET
’。这里的方法指HTTP请求的方法。
当我们的Lambda函数被调用时,它会根据必要的信息来决定还需要调用哪些其他的系统。
import {
MetricUnits,
Metrics,
logMetrics,
} from '@aws-lambda-powertools/metrics';
import { Tracer, captureLambdaHandler } from '@aws-lambda-powertools/tracer';
import { golfSessions, rooms, spaTreatments } from 'stateful/src/data';
import { injectLambdaContext } from '@aws-lambda-powertools/logger';
import middy from '@middy/core';
import { logger } from '@shared/index';
const tracer = new Tracer();
const metrics = new Metrics();
export const adapter = async ({
inputText,
apiPath,
httpMethod,
actionGroup,
messageVersion,
requestBody,
sessionAttributes,
promptSessionAttributes,
}: Event): Promise<Response> => {
let body;
let httpStatusCode = 200;
try {
logger.info(
`inputText: ${inputText}, apiPath: ${apiPath}, httpMethod: ${httpMethod}`
);
// 实际上,这些会调用我们其他的 Lambda 函数、数据库或 API/服务
switch (apiPath) {
case '/rooms':
if (httpMethod === 'GET') {
body = rooms;
} else if (httpMethod === 'POST') {
body = rooms.find((room) => room.roomId === '109');
}
break;
case '/spa-sessions':
if (httpMethod === 'GET') {
body = spaTreatments;
} else if (httpMethod === 'POST') {
body = spaTreatments.find(
(treatment) => treatment.treatmentId === '3'
);
}
break;
case '/golfSessions':
if (httpMethod === 'GET') {
body = golfSessions;
} else if (httpMethod === 'POST') {
body = golfSessions.find((session) => session.sessionId === '1');
}
break;
default:
httpStatusCode = 500;
body =
'抱歉,我目前无法协助您,请尝试以其他方式提问。';
break;
}
metrics.addMetric('成功动作组查询', MetricUnits.Count, 1);
return {
messageVersion,
response: {
apiPath,
actionGroup,
httpMethod,
httpStatusCode,
sessionAttributes,
promptSessionAttributes,
responseBody: {
'application-json': {
body: JSON.stringify(body),
},
},
},
};
} catch (error) {
let errorMessage = '未知错误';
if (error instanceof Error) errorMessage = error.message;
logger.error(errorMessage);
metrics.addMetric('动作组查询错误', MetricUnits.Count, 1);
throw new Error(errorMessage);
}
};
export const handler = middy(adapter) // 执行适配器
.use(injectLambdaContext(logger))
.use(captureLambdaHandler(tracer))
.use(logMetrics(metrics));
你可以看到我们在上面的例子中直接将返回的数据硬编码,而不是像我们通常会这样做,调用其他系统。这样的做法。
我们的硬编码的数据在这篇文章的/data/data.ts文件中的一个例子是
现在我们来看看用于查询我们代理的Stateless
栈。
我们首先创建一个用于查询的 Lambda 函数,并配置了流处理的 URL。
// 创建用于查询 Agent 的 lambda 函数
const queryModelLambda: nodeLambda.NodejsFunction =
new nodeLambda.NodejsFunction(this, 'QueryModelLambda', {
functionName: 'query-model-lambda',
runtime: lambda.Runtime.NODEJS_20_X,
entry: path.join(
__dirname,
'./src/adapters/primary/query-model/query-model.adapter.ts'
),
memorySize: 1024,
handler: 'handler',
timeout: cdk.Duration.minutes(3),
description: '查询模型 lambda 函数',
architecture: lambda.Architecture.ARM_64,
tracing: lambda.Tracing.ACTIVE,
bundling: {
minify: true,
},
environment: {
AGENT_ID: agentId,
AGENT_ALIAS_ID: agentAliasId,
...lambdaConfig,
},
});
// 查询 Lambda 添加流式响应的函数 URL
const queryModelLambdaUrl = queryModelLambda.addFunctionUrl({
authType: lambda.FunctionUrlAuthType.NONE,
invokeMode: lambda.InvokeMode.RESPONSE_STREAM,
cors: {
allowedOrigins: ['*'], // 允许的源
},
});
我们允许它调用代理,如下:
// 允许查询用的 lambda 函数查询我们的模型、知识库和代理
queryModelLambda.addToRolePolicy( // 添加到角色策略中
new iam.PolicyStatement({
actions: [
'bedrock:RetrieveAndGenerate', // 从 Bedrock 获取和生成数据
'bedrock:Retrieve', // 从 Bedrock 获取数据
'bedrock:InvokeModel', // 调用 Bedrock 模型
'bedrock:InvokeAgent', // 调用 Bedrock 代理
], // 操作权限
resources: ['*'], // 资源范围
})
);
我们现在来看看我们的Query Lambda功能,它通过函数URL接收用户的提示并调用代理程序。
import { MetricUnits, Metrics } from '@aws-lambda-powertools/metrics';
import {
BedrockAgentRuntimeClient,
InvokeAgentCommand,
InvokeAgentRequest,
InvokeAgentResponse,
} from '@aws-sdk/client-bedrock-agent-runtime';
import { ResponseStream, streamifyResponse } from 'lambda-stream';
import { config } from '@config';
import { ValidationError } from '@errors/validation-error';
import { logger } from '@shared/index';
import { APIGatewayProxyEventV2 } from 'aws-lambda';
const metrics = new Metrics();
const client = new BedrockAgentRuntimeClient();
const agentId = config.get('agentId');
const agentAliasId = config.get('agentAliasId');
function parseBase64(message: Uint8Array): string {
return Buffer.from(message).toString('utf-8');
}
export const queryModelAdapter = async (
{ body }: APIGatewayProxyEventV2,
responseStream: ResponseStream
): Promise<void> => {
try {
responseStream.setContentType('application/json');
if (!body) throw new ValidationError('没有请求正文');
const request = JSON.parse(body);
const { sessionAttributes, promptSessionAttributes, sessionId, prompt } = request;
const input: InvokeAgentRequest = {
sessionState: {
sessionAttributes,
promptSessionAttributes,
},
agentId,
agentAliasId,
sessionId,
inputText: prompt,
};
const command: InvokeAgentCommand = new InvokeAgentCommand(input);
const response: InvokeAgentResponse = await client.send(command);
const chunks = [];
const completion = response.completion || [];
for await (const chunk of completion) {
if (chunk.chunk && chunk.chunk.bytes) {
const parsed = parseBase64(chunk.chunk.bytes);
chunks.push(parsed);
}
}
const returnMessage = {
sessionId: response.sessionId,
contentType: response.contentType,
message: chunks.join(' '),
};
metrics.addMetric('SuccessfulQueryModel', MetricUnits.Count, 1);
// 注意:在示例中,我们没有使用流式处理,而是使用了 FURL 请求超时特性,但我们可以在循环中轻松地实现流处理
responseStream.write(returnMessage);
responseStream.end();
} catch (error) {
let errorMessage = '未知错误: ';
if (error instanceof Error) errorMessage += error.message;
logger.error(errorMessage);
metrics.addMetric('QueryModelError', MetricUnits.Count, 1);
responseStream.end();
throw error;
}
responseStream.end();
};
export const handler = streamifyResponse(queryModelAdapter);
从上面的代码我们可以看出,我们正在从Agent那里流式传输响应,这些响应以分段的形式在流中返回;然而,在这个例子中我们并没有实时更新用户的反馈,而是等待操作完成后返回一个JSON对象作为响应。让我们在下一节的实际测试中试试看!
测测应用 🧪 (注:此处的实验试管符号在中文里没有直接对应的表达,但在此处保留以保持原意的趣味性。)用 Postman 测试⚠️注意:_ “在部署示例应用程序之前,请注意,仅OpenSearch Serverless每月就需要花费700美元,不包括Bedrock、CloudWatch、Lambda、API网关等额外费用等。”
您可以使用位于 postman/Bedrock Agents.postman_collection.json
的 Postman 文件,并用您自己的 Lambda 函数 URL 信息来测试。
我们可以用下面的 JSON 示例来测试一下:
// JSON内容保持不变
(注:此处应保持 JSON 内容的原有格式和缩进,无需在代码块内添加注释)
...
{
"agentId": "agentId",
"agentAliasId": "agentAliasId",
"sessionId": "1f6aa00e-e585-49aa-aa2d-16adb64857c6",
"prompt": "2024年2月25日上午时段可以预约吗"
}
我们可以看到,我们的代理回应如下:
我们代理的一个示例通话及其相关回应。
在幕后,我们的智能代理已经安排了多个步骤,首先是查看那天可用的会议。
我们看到代理人首先发起一个请求来查看是否有可用的会话。
我们可以看到代理决定首先发起一个“GET
”请求到“/g-sessions/
”,这将会返回那一天高尔夫球场的时段。
然后它又向“/g-sessions/
” 发起了第二次操作请求,方法为“POST
”,以完成预订。
为课程预约安排的下一步
对话式AI及其自主代理的力量在这里得以展现,因为它已经协调了多项行动来帮助客户。
我们可以看到,我们的代理人已经帮我们预订了水疗中心和酒店。
我们现在可以用各种其他场景来测试这个功能,比如查看交易详情、预定水疗预约等等,——在评论里告诉我你们的体验如何!
结尾啦👋🏽 拜拜啦希望你看了这篇文章后会喜欢,要是你喜欢的话,欢迎分享一下并给点意见!
请去订阅我的YouTube频道,那里有类似的内容!
我会非常愿意在以下渠道与您建立联系:
https://www.linkedin.com/in/lee-james-gilmore/ (领英个人资料),https://twitter.com/LeeJamesGilmore (推特个人主页)
如果你喜欢这些文章,请关注我的主页Lee James Gilmore以获取更多文章/系列,并且别忘了打个招呼👋
请也在帖子底部使用“clap”功能来表达如果你喜欢它!(你可以多次点击鼓掌!)
我是...
“大家好,我是 Lee,我是一名 AWS 社区构建者、博主、AWS 认证云架构师,同时也是全球技术与架构负责人,基于英国;现在就职于英国的 City Electrical Factors 和美国的 City Electric Supply,已经工作了六年,主要在 AWS 上从事全栈 JavaScript 开发工作。”
我觉得自己是一名无服务器技术的倡导者,非常热爱这些领域,包括AWS、创新、软件架构和技术。
_** 以下信息纯属我个人的观点,对于因使用这些信息而产生的任何后果,我概不负责。_**
你可能也对以下内容感兴趣哦:
我所有的无服务器内容索引 🚀方便在一个地方轻松浏览,包括视频、博客文章等..blog.serverlessadvocate.com共同学习,写下你的评论
评论加载中...
作者其他优质文章