在当今一对一的客户互动下订单的环境中,普遍的做法仍然依赖人工服务员,即使在得来速咖啡店和快餐店等环境中也是如此。这种传统方法带来了一些挑战:它严重依赖手动流程,难以根据不断增长的客户需求有效地扩展,引入人为错误的可能性,并且在特定的可用时间内运行。此外,在竞争激烈的市场中,仅坚持手动流程的企业可能会发现提供高效且有竞争力的服务具有挑战性。尽管技术取得了进步,但以人为中心的模型在订单处理中仍然根深蒂固,导致了这些局限性。
利用技术进行一对一订单处理协助的前景已经存在一段时间了。然而,现有的解决方案通常可以分为两类:基于规则的系统,需要大量时间和精力来设置和维护,或者僵化的系统,缺乏与客户进行类人交互所需的灵活性。因此,企业和组织在快速有效地实施此类解决方案方面面临着挑战。幸运的是,随着 生成式人工智能 和 大型语言模型 (LLM),现在可以创建能够有效处理自然语言并具有加速启动时间线的自动化系统。
亚马逊基岩 是一项完全托管的服务,通过单个 API 提供来自 AI21 Labs、Anthropic、Cohere、Meta、Stability AI 和 Amazon 等领先 AI 公司的高性能基础模型 (FM) 的选择,以及您可以使用的广泛功能。需要构建具有安全性、隐私性和负责任的人工智能的生成式人工智能应用程序。除了 Amazon Bedrock 之外,您还可以使用其他 AWS 服务,例如 亚马逊SageMaker JumpStart 和 亚马逊Lex 创建完全自动化且易于适应的生成式人工智能订单处理代理。
在这篇文章中,我们将向您展示如何使用 Amazon Lex、Amazon Bedrock 和 Amazon Lex 构建支持语音的订单处理代理。 AWS Lambda.
解决方案概述
下图说明了我们的解决方案体系结构。
工作流程包括以下步骤:
- 客户使用 Amazon Lex 下订单。
- Amazon Lex 机器人解释客户的意图并触发
DialogCodeHook
.
- Lambda 函数从 Lambda 层提取适当的提示模板,并通过在关联的提示模板中添加客户输入来格式化模型提示。
-
RequestValidation
提示会使用菜单项验证订单,并通过 Amazon Lex 让客户知道他们想要订购的东西是否不属于菜单,并将提供建议。该提示还对订单完整性进行初步验证。
-
ObjectCreator
提示将自然语言请求转换为数据结构(JSON 格式)。
- 客户验证器 Lambda 函数验证订单所需的属性,并确认是否存在处理订单所需的所有必要信息。
- 客户 Lambda 函数将数据结构作为处理订单的输入,并将订单总额传递回编排 Lambda 函数。
- 编排 Lambda 函数调用 Amazon Bedrock LLM 终端节点来生成最终订单摘要,包括来自客户数据库系统的订单总额(例如, Amazon DynamoDB).
- 订单摘要通过 Amazon Lex 返回给客户。客户确认订单后,订单将被处理。
先决条件
本文假设您拥有有效的 AWS 账户并熟悉以下概念和服务:
此外,为了从 Lambda 函数访问 Amazon Bedrock,您需要确保 Lambda 运行时具有以下库:
- boto3>=1.28.57
- awscli>=1.29.57
- botocore>=1.31.57
这可以通过 拉姆达层 或者通过使用具有所需库的特定 AMI。
此外,从以下位置调用 Amazon Bedrock API 时需要这些库: 亚马逊SageMaker Studio。这可以通过运行包含以下代码的单元来完成:
%pip install --no-build-isolation --force-reinstall
"boto3>=1.28.57"
"awscli>=1.29.57"
"botocore>=1.31.57"
最后,您创建以下策略,然后将其附加到访问 Amazon Bedrock 的任何角色:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Statement1",
"Effect": "Allow",
"Action": "bedrock:*",
"Resource": "*"
}
]
}
创建一个DynamoDB表
在我们的特定场景中,我们创建了一个 DynamoDB 表作为我们的客户数据库系统,但您也可以使用 亚马逊关系数据库服务 (亚马逊 RDS)。完成以下步骤来配置您的 DynamoDB 表(或根据您的使用案例需要自定义设置):
- 在DynamoDB控制台上,选择 表 在导航窗格中。
- 创建表格.
- 针对 表名,输入名称(例如,
ItemDetails
).
- 针对 分区键,输入密钥(对于本文,我们使用
Item
).
- 针对 排序键,输入密钥(对于本文,我们使用
Size
).
- 创建表格.
现在您可以将数据加载到 DynamoDB 表中。对于本文,我们使用 CSV 文件。您可以在 SageMaker 笔记本中使用 Python 代码将数据加载到 DynamoDB 表。
首先,我们需要设置一个名为 dev 的配置文件。
- 在 SageMaker Studio 中打开新终端并运行以下命令:
aws configure --profile dev
此命令将提示您输入 AWS 访问密钥 ID、秘密访问密钥、默认 AWS 区域和输出格式。
- 返回 SageMaker 笔记本并编写 Python 代码以使用 Python 中的 Boto3 库设置与 DynamoDB 的连接。此代码段使用名为 dev 的特定 AWS 配置文件创建一个会话,然后使用该会话创建一个 DynamoDB 客户端。以下是加载数据的代码示例:
%pip install boto3
import boto3
import csv
# Create a session using a profile named 'dev'
session = boto3.Session(profile_name='dev')
# Create a DynamoDB resource using the session
dynamodb = session.resource('dynamodb')
# Specify your DynamoDB table name
table_name = 'your_table_name'
table = dynamodb.Table(table_name)
# Specify the path to your CSV file
csv_file_path = 'path/to/your/file.csv'
# Read CSV file and put items into DynamoDB
with open(csv_file_path, 'r', encoding='utf-8-sig') as csvfile:
csvreader = csv.reader(csvfile)
# Skip the header row
next(csvreader, None)
for row in csvreader:
# Extract values from the CSV row
item = {
'Item': row[0], # Adjust the index based on your CSV structure
'Size': row[1],
'Price': row[2]
}
# Put item into DynamoDB
response = table.put_item(Item=item)
print(f"Item added: {response}")
print(f"CSV data has been loaded into the DynamoDB table: {table_name}")
或者,您可以使用 NoSQL 工作台 或其他工具将数据快速加载到您的 DynamoDB 表。
下面是示例数据插入表后的截图。
使用 Amazon Bedrock 调用 API 在 SageMaker 笔记本中创建模板
为了为此用例创建提示模板,我们使用 Amazon Bedrock。您可以从以下位置访问 Amazon Bedrock: AWS管理控制台 并通过 API 调用。在我们的示例中,我们通过 SageMaker Studio 笔记本方便地通过 API 访问 Amazon Bedrock,不仅创建提示模板,还创建完整的 API 调用代码,以便稍后在 Lambda 函数上使用。
- 在 SageMaker 控制台上,访问现有 SageMaker Studio 域或创建一个新域以从 SageMaker 笔记本访问 Amazon Bedrock。
- 创建 SageMaker 域和用户后,选择用户并选择 实行 和 GOHAT STUDIO。这将打开 JupyterLab 环境。
- 当 JupyterLab 环境准备就绪时,打开一个新笔记本并开始导入必要的库。
通过 Amazon Bedrock Python SDK 可以使用许多 FM。在本例中,我们使用 Claude V2,这是 Anthropic 开发的强大基础模型。
订单处理代理需要一些不同的模板。这可能会根据用例而改变,但我们设计了一个可以应用于多种设置的通用工作流程。对于此用例,Amazon Bedrock LLM 模板将完成以下任务:
- 验证客户意图
- 验证请求
- 创建订单数据结构
- 将订单摘要传递给客户
- 要调用该模型,请从 Boto3 创建一个基岩运行时对象。
#Model api request parameters
modelId = 'anthropic.claude-v2' # change this to use a different version from the model provider
accept = 'application/json'
contentType = 'application/json'
import boto3
import json
bedrock = boto3.client(service_name='bedrock-runtime')
让我们从处理意图验证器提示模板开始。这是一个迭代过程,但得益于 Anthropic 的提示工程指南,您可以快速创建可以完成任务的提示。
- 创建第一个提示模板以及实用程序函数,该函数将帮助为 API 调用准备正文。
以下是prompt_template_intent_validator.txt的代码:
"{"prompt": "Human: I will give you some instructions to complete my request.n<instructions>Given the Conversation between Human and Assistant, you need to identify the intent that the human wants to accomplish and respond appropriately. The valid intents are: Greeting,Place Order, Complain, Speak to Someone. Always put your response to the Human within the Response tags. Also add an XML tag to your output identifying the human intent.nHere are some examples:n<example><Conversation> H: hi there.nnA: Hi, how can I help you today?nnH: Yes. I would like a medium mocha please</Conversation>nnA:<intent>Place Order</intent><Response>nGot it.</Response></example>n<example><Conversation> H: hellonnA: Hi, how can I help you today?nnH: my coffee does not taste well can you please re-make it?</Conversation>nnA:<intent>Complain</intent><Response>nOh, I am sorry to hear that. Let me get someone to help you.</Response></example>n<example><Conversation> H: hinnA: Hi, how can I help you today?nnH: I would like to speak to someone else please</Conversation>nnA:<intent>Speak to Someone</intent><Response>nSure, let me get someone to help you.</Response></example>n<example><Conversation> H: howdynnA: Hi, how can I help you today?nnH:can I get a large americano with sugar and 2 mochas with no whipped cream</Conversation>nnA:<intent>Place Order</intent><Response>nSure thing! Please give me a moment.</Response></example>n<example><Conversation> H: hinn</Conversation>nnA:<intent>Greeting</intent><Response>nHi there, how can I help you today?</Response></example>n</instructions>nnPlease complete this request according to the instructions and examples provided above:<request><Conversation>REPLACEME</Conversation></request>nnAssistant:n", "max_tokens_to_sample": 250, "temperature": 1, "top_k": 250, "top_p": 0.75, "stop_sequences": ["nnHuman:", "nnhuman:", "nnCustomer:", "nncustomer:"]}"
- 将此模板保存到文件中,以便上传到 Amazon S3 并在需要时从 Lambda 函数调用。将模板另存为文本文件中的 JSON 序列化字符串。前面的屏幕截图也显示了完成此操作的代码示例。
- 对其他模板重复相同的步骤。
以下是其他模板的一些屏幕截图以及使用其中一些模板调用 Amazon Bedrock 时的结果。
以下是prompt_template_request_validator.txt的代码:
"{"prompt": "Human: I will give you some instructions to complete my request.n<instructions>Given the context do the following steps: 1. verify that the items in the input are valid. If customer provided an invalid item, recommend replacing it with a valid one. 2. verify that the customer has provided all the information marked as required. If the customer missed a required information, ask the customer for that information. 3. When the order is complete, provide a summary of the order and ask for confirmation always using this phrase: 'is this correct?' 4. If the customer confirms the order, Do not ask for confirmation again, just say the phrase inside the brackets [Great, Give me a moment while I try to process your order]</instructions>n<context>nThe VALID MENU ITEMS are: [latte, frappe, mocha, espresso, cappuccino, romano, americano].nThe VALID OPTIONS are: [splenda, stevia, raw sugar, honey, whipped cream, sugar, oat milk, soy milk, regular milk, skimmed milk, whole milk, 2 percent milk, almond milk].nThe required information is: size. Size can be: small, medium, large.nHere are some examples: <example>H: I would like a medium latte with 1 Splenda and a small romano with no sugar please.nnA: <Validation>:nThe Human is ordering a medium latte with one splenda. Latte is a valid menu item and splenda is a valid option. The Human is also ordering a small romano with no sugar. Romano is a valid menu item.</Validation>n<Response>nOk, I got: nt-Medium Latte with 1 Splenda and.nt-Small Romano with no Sugar.nIs this correct?</Response>nnH: yep.nnA:n<Response>nGreat, Give me a moment while I try to process your order</example>nn<example>H: I would like a cappuccino and a mocha please.nnA: <Validation>:nThe Human is ordering a cappuccino and a mocha. Both are valid menu items. The Human did not provide the size for the cappuccino. The human did not provide the size for the mocha. I will ask the Human for the required missing information.</Validation>n<Response>nSure thing, but can you please let me know the size for the Cappuccino and the size for the Mocha? We have Small, Medium, or Large.</Response></example>nn<example>H: I would like a small cappuccino and a large lemonade please.nnA: <Validation>:nThe Human is ordering a small cappuccino and a large lemonade. Cappuccino is a valid menu item. Lemonade is not a valid menu item. I will suggest the Human a replacement from our valid menu items.</Validation>n<Response>nSorry, we don't have Lemonades, would you like to order something else instead? Perhaps a Frappe or a Latte?</Response></example>nn<example>H: Can I get a medium frappuccino with sugar please?nnA: <Validation>:n The Human is ordering a Frappuccino. Frappuccino is not a valid menu item. I will suggest a replacement from the valid menu items in my context.</Validation>n<Response>nI am so sorry, but Frappuccino is not in our menu, do you want a frappe or a cappuccino instead? perhaps something else?</Response></example>nn<example>H: I want two large americanos and a small latte please.nnA: <Validation>:n The Human is ordering 2 Large Americanos, and a Small Latte. Americano is a valid menu item. Latte is a valid menu item.</Validation>n<Response>nOk, I got: nt-2 Large Americanos and.nt-Small Latte.nIs this correct?</Response>nnH: looks correct, yes.nnA:n<Response>nGreat, Give me a moment while I try to process your order.</Response></example>nn</Context>nnPlease complete this request according to the instructions and examples provided above:<request>REPLACEME</request>nnAssistant:n", "max_tokens_to_sample": 250, "temperature": 0.3, "top_k": 250, "top_p": 0.75, "stop_sequences": ["nnHuman:", "nnhuman:", "nnCustomer:", "nncustomer:"]}"
以下是我们使用此模板从 Amazon Bedrock 获得的回复。
以下是代码 prompt_template_object_creator.txt
:
"{"prompt": "Human: I will give you some instructions to complete my request.n<instructions>Given the Conversation between Human and Assistant, you need to create a json object in Response with the appropriate attributes.nHere are some examples:n<example><Conversation> H: I want a latte.nnA:nCan I have the size?nnH: Medium.nnA: So, a medium latte.nIs this Correct?nnH: Yes.</Conversation>nnA:<Response>{"1":{"item":"latte","size":"medium","addOns":[]}}</Response></example>n<example><Conversation> H: I want a large frappe and 2 small americanos with sugar.nnA: Okay, let me confirm:nn1 large frappenn2 small americanos with sugarnnIs this correct?nnH: Yes.</Conversation>nnA:<Response>{"1":{"item":"frappe","size":"large","addOns":[]},"2":{"item":"americano","size":"small","addOns":["sugar"]},"3":{"item":"americano","size":"small","addOns":["sugar"]}}</Response>n</example>n<example><Conversation> H: I want a medium americano.nnA: Okay, let me confirm:nn1 medium americanonnIs this correct?nnH: Yes.</Conversation>nnA:<Response>{"1":{"item":"americano","size":"medium","addOns":[]}}</Response></example>n<example><Conversation> H: I want a large latte with oatmilk.nnA: Okay, let me confirm:nnLarge latte with oatmilknnIs this correct?nnH: Yes.</Conversation>nnA:<Response>{"1":{"item":"latte","size":"large","addOns":["oatmilk"]}}</Response></example>n<example><Conversation> H: I want a small mocha with no whipped cream please.nnA: Okay, let me confirm:nnSmall mocha with no whipped creamnnIs this correct?nnH: Yes.</Conversation>nnA:<Response>{"1":{"item":"mocha","size":"small","addOns":["no whipped cream"]}}</Response>nn</example></instructions>nnPlease complete this request according to the instructions and examples provided above:<request><Conversation>REPLACEME</Conversation></request>nnAssistant:n", "max_tokens_to_sample": 250, "temperature": 0.3, "top_k": 250, "top_p": 0.75, "stop_sequences": ["nnHuman:", "nnhuman:", "nnCustomer:", "nncustomer:"]}"
以下是prompt_template_order_summary.txt的代码:
"{"prompt": "Human: I will give you some instructions to complete my request.n<instructions>Given the Conversation between Human and Assistant, you need to create a summary of the order with bullet points and include the order total.nHere are some examples:n<example><Conversation> H: I want a large frappe and 2 small americanos with sugar.nnA: Okay, let me confirm:nn1 large frappenn2 small americanos with sugarnnIs this correct?nnH: Yes.</Conversation>nn<OrderTotal>10.50</OrderTotal>nnA:<Response>nHere is a summary of your order along with the total:nn1 large frappenn2 small americanos with sugar.nYour Order total is $10.50</Response></example>n<example><Conversation> H: I want a medium americano.nnA: Okay, let me confirm:nn1 medium americanonnIs this correct?nnH: Yes.</Conversation>nn<OrderTotal>3.50</OrderTotal>nnA:<Response>nHere is a summary of your order along with the total:nn1 medium americano.nYour Order total is $3.50</Response></example>n<example><Conversation> H: I want a large latte with oat milk.nnA: Okay, let me confirm:nnLarge latte with oat milknnIs this correct?nnH: Yes.</Conversation>nn<OrderTotal>6.75</OrderTotal>nnA:<Response>nHere is a summary of your order along with the total:nnLarge latte with oat milk.nYour Order total is $6.75</Response></example>n<example><Conversation> H: I want a small mocha with no whipped cream please.nnA: Okay, let me confirm:nnSmall mocha with no whipped creamnnIs this correct?nnH: Yes.</Conversation>nn<OrderTotal>4.25</OrderTotal>nnA:<Response>nHere is a summary of your order along with the total:nnSmall mocha with no whipped cream.nYour Order total is $6.75</Response>nn</example>n</instructions>nnPlease complete this request according to the instructions and examples provided above:<request><Conversation>REPLACEME</Conversation>nn<OrderTotal>REPLACETOTAL</OrderTotal></request>nnAssistant:n", "max_tokens_to_sample": 250, "temperature": 0.3, "top_k": 250, "top_p": 0.75, "stop_sequences": ["nnHuman:", "nnhuman:", "nnCustomer:", "nncustomer:", "[Conversation]"]}"
如您所见,我们使用提示模板来验证菜单项、识别缺少的所需信息、创建数据结构并汇总订单。 Amazon Bedrock 上提供的基础模型非常强大,因此您可以通过这些模板完成更多任务。
您已完成提示的设计并将模板保存到文本文件。您现在可以开始创建 Amazon Lex 自动程序和关联的 Lambda 函数。
使用提示模板创建 Lambda 层
完成以下步骤来创建 Lambda 层:
- 在 SageMaker Studio 中,创建一个新文件夹,其中包含名为
python
.
- 将提示文件复制到
python
文件夹中。
- 您可以通过运行以下命令将 ZIP 库添加到您的笔记本实例。
!conda install -y -c conda-forge zip
- 现在,运行以下命令创建 ZIP 文件以上传到 Lambda 层。
!zip -r prompt_templates_layer.zip prompt_templates_layer/.
- 创建 ZIP 文件后,您可以下载该文件。转到 Lambda,通过直接上传文件或先上传到 Amazon S3 来创建新层。
- 然后将此新层附加到编排 Lambda 函数。
现在,您的提示模板文件本地存储在您的 Lambda 运行时环境中。这将加快机器人运行过程中的速度。
使用所需的库创建 Lambda 层
完成以下步骤以使用所需的库创建 Lambda 层:
- 打开 AWS 云9 实例环境,创建一个文件夹,其子文件夹名为
python
.
- 在里面打开一个终端
python
文件夹中。
- 从终端运行以下命令:
pip install “boto3>=1.28.57” -t .
pip install “awscli>=1.29.57" -t .
pip install “botocore>=1.31.57” -t .
- 运行
cd ..
并将自己放置在新文件夹中,其中还有 python
子文件夹。
- 运行以下命令:
- 创建 ZIP 文件后,您可以下载该文件。转到 Lambda,通过直接上传文件或先上传到 Amazon S3 来创建新层。
- 然后将此新层附加到编排 Lambda 函数。
在 Amazon Lex v2 中创建机器人
对于此用例,我们构建了一个 Amazon Lex 机器人,它可以为架构提供输入/输出接口,以便从任何接口使用语音或文本调用 Amazon Bedrock。由于 LLM 将处理该订单处理代理的对话部分,而 Lambda 将编排工作流程,因此您可以创建一个具有三个意图且没有槽位的机器人。
- 在 Amazon Lex 控制台上,使用以下方法创建一个新机器人 创建一个空白机器人.
现在,您可以使用任何适当的初始话语添加意图,以便最终用户开始与机器人对话。我们使用简单的问候语并添加初始机器人响应,以便最终用户可以提供他们的请求。创建机器人时,请确保使用具有意图的 Lambda 代码挂钩;这将触发 Lambda 函数,该函数将协调客户、Amazon Lex 和 LLM 之间的工作流程。
- 添加您的第一个意图,这会触发工作流程并使用意图验证提示模板来调用 Amazon Bedrock 并确定客户想要完成的任务。添加一些简单的话语供最终用户开始对话。
您不需要在任何机器人意图中使用任何插槽或初始读取。事实上,您不需要向第二个或第三个意图添加话语。这是因为 LLM 将在整个过程中指导 Lambda。
- 添加确认提示。您可以稍后在 Lambda 函数中自定义此消息。
- 下 代码钩, 选择 使用 Lambda 函数进行初始化和验证.
- 创建第二个意图,无需言语,也无需初始响应。这是
PlaceOrder
意图。
当 LLM 识别出客户正在尝试下订单时,Lambda 函数将触发此意图并根据菜单验证客户请求,并确保没有丢失任何必需的信息。请记住,所有这些都在提示模板上,因此您可以通过更改提示模板来调整此工作流程以适应任何用例。
- 不要添加任何槽位,但添加确认提示和拒绝响应。
- 选择 使用 Lambda 函数进行初始化和验证.
- 创建名为的第三个意图
ProcessOrder
没有示例话语,也没有插槽。
- 添加初始响应、确认提示和拒绝响应。
LLM 验证客户请求后,Lambda 函数会触发第三个也是最后一个意图来处理订单。在这里,Lambda 将使用对象创建者模板生成订单 JSON 数据结构以查询 DynamoDB 表,然后使用订单汇总模板汇总整个订单以及总计,以便 Amazon Lex 可以将其传递给客户。
- 选择 使用 Lambda 函数进行初始化和验证。在客户给出最终确认后,可以使用任何 Lambda 函数来处理订单。
- 创建所有三个意图后,转到可视化生成器
ValidateIntent
,添加一个转到意图步骤,并将肯定确认的输出连接到该步骤。
- 添加转到意图后,对其进行编辑并选择 PlaceOrder 意图作为意图名称。
- 同样,要使用可视化构建器
PlaceOrder
意图并将肯定确认的输出连接到 ProcessOrder
前往意图。无需编辑 ProcessOrder
意图。
- 您现在需要创建 Lambda 函数来编排 Amazon Lex 并调用 DynamoDB 表,如下节中详述。
创建 Lambda 函数来编排 Amazon Lex 自动程序
您现在可以构建 Lambda 函数来编排 Amazon Lex 自动程序和工作流程。完成以下步骤:
- 使用标准执行策略创建 Lambda 函数,并让 Lambda 为您创建角色。
- 在函数的代码窗口中,添加一些有用的实用函数:通过将 lex 上下文添加到模板来格式化提示、调用 Amazon Bedrock LLM API、从响应中提取所需的文本等等。请看下面的代码:
import json
import re
import boto3
import logging
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
bedrock = boto3.client(service_name='bedrock-runtime')
def CreatingCustomPromptFromLambdaLayer(object_key,replace_items):
folder_path = '/opt/order_processing_agent_prompt_templates/python/'
try:
file_path = folder_path + object_key
with open(file_path, "r") as file1:
raw_template = file1.read()
# Modify the template with the custom input prompt
#template['inputs'][0].insert(1, {"role": "user", "content": '### Input:n' + user_request})
for key,value in replace_items.items():
value = json.dumps(json.dumps(value).replace('"','')).replace('"','')
raw_template = raw_template.replace(key,value)
modified_prompt = raw_template
return modified_prompt
except Exception as e:
return {
'statusCode': 500,
'body': f'An error occurred: {str(e)}'
}
def CreatingCustomPrompt(object_key,replace_items):
logger.debug('replace_items is: {}'.format(replace_items))
#retrieve user request from intent_request
#we first propmt the model with current order
bucket_name = 'your-bucket-name'
#object_key = 'prompt_template_order_processing.txt'
try:
s3 = boto3.client('s3')
# Retrieve the existing template from S3
response = s3.get_object(Bucket=bucket_name, Key=object_key)
raw_template = response['Body'].read().decode('utf-8')
raw_template = json.loads(raw_template)
logger.debug('raw template is {}'.format(raw_template))
#template_json = json.loads(raw_template)
#logger.debug('template_json is {}'.format(template_json))
#template = json.dumps(template_json)
#logger.debug('template is {}'.format(template))
# Modify the template with the custom input prompt
#template['inputs'][0].insert(1, {"role": "user", "content": '### Input:n' + user_request})
for key,value in replace_items.items():
raw_template = raw_template.replace(key,value)
logger.debug("Replacing: {} nwith: {}".format(key,value))
modified_prompt = json.dumps(raw_template)
logger.debug("Modified template: {}".format(modified_prompt))
logger.debug("Modified template type is: {}".format(print(type(modified_prompt))))
#modified_template_json = json.loads(modified_prompt)
#logger.debug("Modified template json: {}".format(modified_template_json))
return modified_prompt
except Exception as e:
return {
'statusCode': 500,
'body': f'An error occurred: {str(e)}'
}
def validate_intent(intent_request):
logger.debug('starting validate_intent: {}'.format(intent_request))
#retrieve user request from intent_request
user_request = 'Human: ' + intent_request['inputTranscript'].lower()
#getting current context variable
current_session_attributes = intent_request['sessionState']['sessionAttributes']
if len(current_session_attributes) > 0:
full_context = current_session_attributes['fullContext'] + 'nn' + user_request
dialog_context = current_session_attributes['dialogContext'] + 'nn' + user_request
else:
full_context = user_request
dialog_context = user_request
#Preparing validation prompt by adding context to prompt template
object_key = 'prompt_template_intent_validator.txt'
#replace_items = {"REPLACEME":full_context}
#replace_items = {"REPLACEME":dialog_context}
replace_items = {"REPLACEME":dialog_context}
#validation_prompt = CreatingCustomPrompt(object_key,replace_items)
validation_prompt = CreatingCustomPromptFromLambdaLayer(object_key,replace_items)
#Prompting model for request validation
intent_validation_completion = prompt_bedrock(validation_prompt)
intent_validation_completion = re.sub(r'["]','',intent_validation_completion)
#extracting response from response completion and removing some special characters
validation_response = extract_response(intent_validation_completion)
validation_intent = extract_intent(intent_validation_completion)
#business logic depending on intents
if validation_intent == 'Place Order':
return validate_request(intent_request)
elif validation_intent in ['Complain','Speak to Someone']:
##adding session attributes to keep current context
full_context = full_context + 'nn' + intent_validation_completion
dialog_context = dialog_context + 'nnAssistant: ' + validation_response
intent_request['sessionState']['sessionAttributes']['fullContext'] = full_context
intent_request['sessionState']['sessionAttributes']['dialogContext'] = dialog_context
intent_request['sessionState']['sessionAttributes']['customerIntent'] = validation_intent
return close(intent_request['sessionState']['sessionAttributes'],intent_request['sessionState']['intent']['name'],'Fulfilled','Close',validation_response)
if validation_intent == 'Greeting':
##adding session attributes to keep current context
full_context = full_context + 'nn' + intent_validation_completion
dialog_context = dialog_context + 'nnAssistant: ' + validation_response
intent_request['sessionState']['sessionAttributes']['fullContext'] = full_context
intent_request['sessionState']['sessionAttributes']['dialogContext'] = dialog_context
intent_request['sessionState']['sessionAttributes']['customerIntent'] = validation_intent
return close(intent_request['sessionState']['sessionAttributes'],intent_request['sessionState']['intent']['name'],'InProgress','ConfirmIntent',validation_response)
def validate_request(intent_request):
logger.debug('starting validate_request: {}'.format(intent_request))
#retrieve user request from intent_request
user_request = 'Human: ' + intent_request['inputTranscript'].lower()
#getting current context variable
current_session_attributes = intent_request['sessionState']['sessionAttributes']
if len(current_session_attributes) > 0:
full_context = current_session_attributes['fullContext'] + 'nn' + user_request
dialog_context = current_session_attributes['dialogContext'] + 'nn' + user_request
else:
full_context = user_request
dialog_context = user_request
#Preparing validation prompt by adding context to prompt template
object_key = 'prompt_template_request_validator.txt'
replace_items = {"REPLACEME":dialog_context}
#validation_prompt = CreatingCustomPrompt(object_key,replace_items)
validation_prompt = CreatingCustomPromptFromLambdaLayer(object_key,replace_items)
#Prompting model for request validation
request_validation_completion = prompt_bedrock(validation_prompt)
request_validation_completion = re.sub(r'["]','',request_validation_completion)
#extracting response from response completion and removing some special characters
validation_response = extract_response(request_validation_completion)
##adding session attributes to keep current context
full_context = full_context + 'nn' + request_validation_completion
dialog_context = dialog_context + 'nnAssistant: ' + validation_response
intent_request['sessionState']['sessionAttributes']['fullContext'] = full_context
intent_request['sessionState']['sessionAttributes']['dialogContext'] = dialog_context
return close(intent_request['sessionState']['sessionAttributes'],'PlaceOrder','InProgress','ConfirmIntent',validation_response)
def process_order(intent_request):
logger.debug('starting process_order: {}'.format(intent_request))
#retrieve user request from intent_request
user_request = 'Human: ' + intent_request['inputTranscript'].lower()
#getting current context variable
current_session_attributes = intent_request['sessionState']['sessionAttributes']
if len(current_session_attributes) > 0:
full_context = current_session_attributes['fullContext'] + 'nn' + user_request
dialog_context = current_session_attributes['dialogContext'] + 'nn' + user_request
else:
full_context = user_request
dialog_context = user_request
# Preparing object creator prompt by adding context to prompt template
object_key = 'prompt_template_object_creator.txt'
replace_items = {"REPLACEME":dialog_context}
#object_creator_prompt = CreatingCustomPrompt(object_key,replace_items)
object_creator_prompt = CreatingCustomPromptFromLambdaLayer(object_key,replace_items)
#Prompting model for object creation
object_creation_completion = prompt_bedrock(object_creator_prompt)
#extracting response from response completion
object_creation_response = extract_response(object_creation_completion)
inputParams = json.loads(object_creation_response)
inputParams = json.dumps(json.dumps(inputParams))
logger.debug('inputParams is: {}'.format(inputParams))
client = boto3.client('lambda')
response = client.invoke(FunctionName = 'arn:aws:lambda:us-east-1:<AccountNumber>:function:aws-blog-order-validator',InvocationType = 'RequestResponse',Payload = inputParams)
responseFromChild = json.load(response['Payload'])
validationResult = responseFromChild['statusCode']
if validationResult == 205:
order_validation_error = responseFromChild['validator_response']
return close(intent_request['sessionState']['sessionAttributes'],'PlaceOrder','InProgress','ConfirmIntent',order_validation_error)
#invokes Order Processing lambda to query DynamoDB table and returns order total
response = client.invoke(FunctionName = 'arn:aws:lambda:us-east-1: <AccountNumber>:function:aws-blog-order-processing',InvocationType = 'RequestResponse',Payload = inputParams)
responseFromChild = json.load(response['Payload'])
orderTotal = responseFromChild['body']
###Prompting the model to summarize the order along with order total
object_key = 'prompt_template_order_summary.txt'
replace_items = {"REPLACEME":dialog_context,"REPLACETOTAL":orderTotal}
#order_summary_prompt = CreatingCustomPrompt(object_key,replace_items)
order_summary_prompt = CreatingCustomPromptFromLambdaLayer(object_key,replace_items)
order_summary_completion = prompt_bedrock(order_summary_prompt)
#extracting response from response completion
order_summary_response = extract_response(order_summary_completion)
order_summary_response = order_summary_response + '. Shall I finalize processing your order?'
##adding session attributes to keep current context
full_context = full_context + 'nn' + order_summary_completion
dialog_context = dialog_context + 'nnAssistant: ' + order_summary_response
intent_request['sessionState']['sessionAttributes']['fullContext'] = full_context
intent_request['sessionState']['sessionAttributes']['dialogContext'] = dialog_context
return close(intent_request['sessionState']['sessionAttributes'],'ProcessOrder','InProgress','ConfirmIntent',order_summary_response)
""" --- Main handler and Workflow functions --- """
def lambda_handler(event, context):
"""
Route the incoming request based on intent.
The JSON body of the request is provided in the event slot.
"""
logger.debug('event is: {}'.format(event))
return dispatch(event)
def dispatch(intent_request):
"""
Called when the user specifies an intent for this bot. If intent is not valid then returns error name
"""
logger.debug('intent_request is: {}'.format(intent_request))
intent_name = intent_request['sessionState']['intent']['name']
confirmation_state = intent_request['sessionState']['intent']['confirmationState']
# Dispatch to your bot's intent handlers
if intent_name == 'ValidateIntent' and confirmation_state == 'None':
return validate_intent(intent_request)
if intent_name == 'PlaceOrder' and confirmation_state == 'None':
return validate_request(intent_request)
elif intent_name == 'PlaceOrder' and confirmation_state == 'Confirmed':
return process_order(intent_request)
elif intent_name == 'PlaceOrder' and confirmation_state == 'Denied':
return close(intent_request['sessionState']['sessionAttributes'],intent_request['sessionState']['intent']['name'],'Fulfilled','Close','Got it. Let me know if I can help you with something else.')
elif intent_name == 'PlaceOrder' and confirmation_state not in ['Denied','Confirmed','None']:
return close(intent_request['sessionState']['sessionAttributes'],intent_request['sessionState']['intent']['name'],'Fulfilled','Close','Sorry. I am having trouble completing the request. Let me get someone to help you.')
logger.debug('exiting intent {} here'.format(intent_request['sessionState']['intent']['name']))
elif intent_name == 'ProcessOrder' and confirmation_state == 'None':
return validate_request(intent_request)
elif intent_name == 'ProcessOrder' and confirmation_state == 'Confirmed':
return close(intent_request['sessionState']['sessionAttributes'],intent_request['sessionState']['intent']['name'],'Fulfilled','Close','Perfect! Your order has been processed. Please proceed to payment.')
elif intent_name == 'ProcessOrder' and confirmation_state == 'Denied':
return close(intent_request['sessionState']['sessionAttributes'],intent_request['sessionState']['intent']['name'],'Fulfilled','Close','Got it. Let me know if I can help you with something else.')
elif intent_name == 'ProcessOrder' and confirmation_state not in ['Denied','Confirmed','None']:
return close(intent_request['sessionState']['sessionAttributes'],intent_request['sessionState']['intent']['name'],'Fulfilled','Close','Sorry. I am having trouble completing the request. Let me get someone to help you.')
logger.debug('exiting intent {} here'.format(intent_request['sessionState']['intent']['name']))
raise Exception('Intent with name ' + intent_name + ' not supported')
def prompt_bedrock(formatted_template):
logger.debug('prompt bedrock input is:'.format(formatted_template))
body = json.loads(formatted_template)
modelId = 'anthropic.claude-v2' # change this to use a different version from the model provider
accept = 'application/json'
contentType = 'application/json'
response = bedrock.invoke_model(body=body, modelId=modelId, accept=accept, contentType=contentType)
response_body = json.loads(response.get('body').read())
response_completion = response_body.get('completion')
logger.debug('response is: {}'.format(response_completion))
#print_ww(response_body.get('completion'))
#print(response_body.get('results')[0].get('outputText'))
return response_completion
#function to extract text between the <Response> and </Response> tags within model completion
def extract_response(response_completion):
if '<Response>' in response_completion:
customer_response = response_completion.replace('<Response>','||').replace('</Response>','').split('||')[1]
logger.debug('modified response is: {}'.format(response_completion))
return customer_response
else:
logger.debug('modified response is: {}'.format(response_completion))
return response_completion
#function to extract text between the <Response> and </Response> tags within model completion
def extract_intent(response_completion):
if '<intent>' in response_completion:
customer_intent = response_completion.replace('<intent>','||').replace('</intent>','||').split('||')[1]
return customer_intent
else:
return customer_intent
def close(session_attributes, intent, fulfillment_state, action_type, message):
#This function prepares the response in the appropiate format for Lex V2
response = {
"sessionState": {
"sessionAttributes":session_attributes,
"dialogAction": {
"type": action_type
},
"intent": {
"name":intent,
"state":fulfillment_state
},
},
"messages":
[{
"contentType":"PlainText",
"content":message,
}]
,
}
return response
- 将您之前创建的 Lambda 层附加到此函数。
- 此外,将该图层附加到您创建的提示模板。
- 在 Lambda 执行角色中,附加策略以访问之前创建的 Amazon Bedrock。
Lambda 执行角色应具有以下权限。
将 Orchestration Lambda 函数附加到 Amazon Lex 自动程序
- 在上一部分中创建函数后,返回 Amazon Lex 控制台并导航到您的机器人。
- 下 语言 在导航窗格中,选择 英语.
- 针对 来源,选择您的订单处理机器人。
- 针对 Lambda函数版本或别名,选择 $最新.
- 保存.
创建辅助 Lambda 函数
完成以下步骤以创建其他 Lambda 函数:
- 创建一个 Lambda 函数来查询您之前创建的 DynamoDB 表:
import json
import boto3
import logging
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
# Initialize the DynamoDB client
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('your-table-name')
def calculate_grand_total(input_data):
# Initialize the total price
total_price = 0
try:
# Loop through each item in the input JSON
for item_id, item_data in input_data.items():
item_name = item_data['item'].lower() # Convert item name to lowercase
item_size = item_data['size'].lower() # Convert item size to lowercase
# Query the DynamoDB table for the item based on Item and Size
response = table.get_item(
Key={'Item': item_name,
'Size': item_size}
)
# Check if the item was found in the table
if 'Item' in response:
item = response['Item']
price = float(item['Price'])
total_price += price # Add the item's price to the total
return total_price
except Exception as e:
raise Exception('An error occurred: {}'.format(str(e)))
def lambda_handler(event, context):
try:
# Parse the input JSON from the Lambda event
input_json = json.loads(event)
# Calculate the grand total
grand_total = calculate_grand_total(input_json)
# Return the grand total in the response
return {'statusCode': 200,'body': json.dumps(grand_total)}
except Exception as e:
return {
'statusCode': 500,
'body': json.dumps('An error occurred: {}'.format(str(e)))
- 导航到 配置 Lambda 函数中的选项卡并选择 权限.
- 附加基于资源的策略声明,允许订单处理 Lambda 函数调用此函数。
- 导航到此 Lambda 函数的 IAM 执行角色并添加访问 DynamoDB 表的策略。
- 创建另一个 Lambda 函数来验证是否从客户处传递了所有必需的属性。在以下示例中,我们验证是否捕获了订单的尺寸属性:
import json
import logging
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
def lambda_handler(event, context):
# Define customer orders from the input event
customer_orders = json.loads(event)
# Initialize a list to collect error messages
order_errors = {}
missing_size = []
error_messages = []
# Iterate through each order in customer_orders
for order_id, order in customer_orders.items():
if "size" not in order or order["size"] == "":
missing_size.append(order['item'])
order_errors['size'] = missing_size
if order_errors:
items_missing_size = order_errors['size']
error_message = f"could you please provide the size for the following items: {', '.join(items_missing_size)}?"
error_messages.append(error_message)
# Prepare the response message
if error_messages:
response_message = "n".join(error_messages)
return {
'statusCode': 205,
'validator_response': response_message
}
else:
response_message = "Order is validated successfully"
return {
'statusCode': 200,
'validator_response': response_message
}
- 导航到 配置 Lambda 函数中的选项卡并选择 权限.
- 附加基于资源的策略声明,允许订单处理 Lambda 函数调用此函数。
测试解决方案
现在,我们可以使用客户通过 Amazon Lex 下的示例订单来测试该解决方案。
对于我们的第一个例子,顾客想要一杯星冰乐,但菜单上没有。该模型在订单验证器模板的帮助下进行验证,并根据菜单提出一些建议。客户确认订单后,他们会收到订单总额和订单摘要的通知。订单将根据客户的最终确认进行处理。
在我们的下一个示例中,客户订购大杯卡布奇诺,然后将尺寸从大号修改为中号。该模型捕获所有必要的更改并请求客户确认订单。该模型呈现订单总额和订单摘要,并根据客户的最终确认处理订单。
对于我们的最后一个示例,客户下了多件商品的订单,但有几件商品缺少尺寸。模型和 Lambda 函数将验证是否存在处理订单所需的所有属性,然后要求客户提供缺少的信息。在客户提供缺失的信息(在本例中为咖啡的大小)后,他们会看到订单总额和订单摘要。订单将根据客户的最终确认进行处理。
法学硕士的局限性
法学硕士的输出本质上是随机的,这意味着我们的法学硕士的结果可能会在格式上有所不同,甚至可能会以不真实的内容(幻觉)的形式出现。因此,开发人员需要在整个代码中依赖良好的错误处理逻辑,以处理这些场景并避免最终用户体验下降。
清理
如果您不再需要此解决方案,可以删除以下资源:
- Lambda函数
- 亚马逊 Lex 盒子
- DynamoDB表
- S3斗
此外,如果不再需要该应用程序,请关闭 SageMaker Studio 实例。
成本评估
有关此解决方案使用的主要服务的定价信息,请参阅以下内容:
请注意,您可以使用 Claude v2,无需进行配置,因此总体成本保持在最低水平。为了进一步降低成本,您可以使用按需设置配置 DynamoDB 表。
结论
本文演示了如何使用 Amazon Lex、Amazon Bedrock 和其他 AWS 服务构建支持语音的 AI 订单处理代理。我们展示了如何使用 Claude 等强大的生成式 AI 模型进行快速工程,从而无需大量训练数据即可实现强大的自然语言理解和订单处理对话流。
该解决方案架构使用 Lambda、Amazon S3 和 DynamoDB 等无服务器组件来实现灵活且可扩展的实施。通过将提示模板存储在 Amazon S3 中,您可以针对不同的使用案例自定义解决方案。
下一步可能包括扩展代理的能力,以处理更广泛的客户请求和边缘情况。提示模板提供了一种迭代提高座席技能的方法。其他定制可能涉及将订单数据与库存、CRM 或 POS 等后端系统集成。最后,可以使用 Amazon Lex 的多渠道功能在各种客户接触点(例如移动应用程序、得来速、自助服务终端等)提供代理。
要了解更多信息,请参阅以下相关资源:
- 部署和管理多渠道机器人:
- 克劳德和其他模型的快速工程:
- 可扩展人工智能助手的无服务器架构模式:
作者简介
穆米塔·杜塔 是 Amazon Web Services 的合作伙伴解决方案架构师。在她的角色中,她与合作伙伴密切合作,开发可扩展和可重用的资产,以简化云部署并提高运营效率。她是 AI/ML 社区的成员,也是 AWS 的生成式 AI 专家。闲暇时,她喜欢园艺和骑自行车。
费尔南多·拉莫利亚 是 Amazon Web Services 的合作伙伴解决方案架构师,与 AWS 合作伙伴密切合作,带头跨业务部门开发和采用尖端 AI 解决方案。一位战略领导者,拥有云架构、生成式人工智能、机器学习和数据分析方面的专业知识。他专门执行市场战略并提供符合组织目标的有影响力的人工智能解决方案。在空闲时间,他喜欢与家人共度时光并前往其他国家旅行。
米图尔·帕特尔 是 Amazon Web Services 的高级解决方案架构师。作为云技术推动者,他与客户合作了解他们的目标和挑战,并提供规范性指导以通过 AWS 产品实现他们的目标。他是 AI/ML 社区的成员,也是 AWS 的生成式 AI 大使。在空闲时间,他喜欢徒步旅行和踢足球。