和風網標誌

轉變一對一的客戶互動:利用 AWS 和生成式 AI 建立支援語音的訂單處理代理 |亞馬遜網路服務

日期:

在當今一對一的客戶互動下訂單的環境中,普遍的做法仍然依賴人工服務員,即使在得來速咖啡店和快餐店等環境中也是如此。這種傳統方法帶來了一些挑戰:它嚴重依賴手動流程,難以根據不斷增長的客戶需求有效地擴展,引入人為錯誤的可能性,並且在特定的可用時間內運作。此外,在競爭激烈的市場中,僅堅持手動流程的企業可能會發現提供高效且有競爭力的服務具有挑戰性。儘管技術取得了進步,但以人為中心的模型在訂單處理中仍然根深蒂固,導致了這些限制。

利用科技進行一對一訂單處理協助的前景已經存在一段時間了。然而,現有的解決方案通常可以分為兩類:基於規則的系統,需要大量時間和精力來設定和維護,或僵化的系統,缺乏與客戶進行類人互動所需的靈活性。因此,企業和組織在快速有效地實施此類解決方案方面面臨挑戰。幸運的是,隨著 生成AI大型語言模型 (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.

解決方案概述

下圖說明了我們的解決方案體系結構。

該工作流程包括以下步驟:

  1. 顧客使用 Amazon Lex 下訂單。
  2. Amazon Lex 機器人解釋客戶的意圖並觸發 DialogCodeHook.
  3. Lambda 函數從 Lambda 層提取適當的提示模板,並透過在關聯的提示模板中新增客戶輸入來格式化模型提示。
  4. RequestValidation 提示會使用菜單項目驗證訂單,並透過 Amazon Lex 讓客戶知道他們想要訂購的東西是否不屬於菜單,並將提供建議。該提示也對訂單完整性進行初步驗證。
  5. ObjectCreator 提示將自然語言請求轉換為資料結構(JSON 格式)。
  6. 客戶驗證器 Lambda 函數驗證訂單所需的屬性,並確認是否存在處理訂單所需的所有必要資訊。
  7. 客戶 Lambda 函數將資料結構作為處理訂單的輸入,並將訂單總額傳回編排 Lambda 函數。
  8. 編排 Lambda 函數呼叫 Amazon Bedrock LLM 終端節點來產生最終訂單摘要,包括來自客戶資料庫系統的訂單總額(例如, 亞馬遜DynamoDB).
  9. 訂單摘要透過 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 表(或根據您的使用案例需求自訂設定):

  1. 在DynamoDB控制台上,選擇 在導航窗格中。
  2. 選擇 創建表格.

  1. 表名稱,輸入名稱(例如, ItemDetails).
  2. 分區鍵,輸入密鑰(對於本文,我們使用 Item).
  3. 排序鍵,輸入密鑰(對於本文,我們使用 Size).
  4. 選擇 創建表格.

現在您可以將資料載入到 DynamoDB 表中。對於本文,我們使用 CSV 檔案。您可以在 SageMaker 筆記本中使用 Python 程式碼將資料載入到 DynamoDB 表。

首先,我們需要設定一個名為 dev 的設定檔。

  1. 在 SageMaker Studio 中開啟新終端機並執行以下命令:
aws configure --profile dev

此命令將提示您輸入 AWS 存取金鑰 ID、秘密存取金鑰、預設 AWS 區域和輸出格式。

  1. 返回 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 函數上使用。

  1. 在 SageMaker 控制台上,存取現有 SageMaker Studio 網域或建立新網域以從 SageMaker 筆電存取 Amazon Bedrock。

  1. 建立 SageMaker 網域和使用者後,選擇使用者並選擇 發佈會工作室。這將開啟 JupyterLab 環境。
  2. 當 JupyterLab 環境準備就緒時,開啟一個新筆記本並開始匯入必要的函式庫。

透過 Amazon Bedrock Python SDK 可以使用許多 FM。在本例中,我們使用 Claude V2,這是 Anthropic 開發的強大基礎模型。

訂單處理代理需要一些不同的範本。這可能會根據用例而改變,但我們設計了一個可以應用於多種設定的通用工作流程。對於此用例,Amazon Bedrock LLM 範本將完成以下任務:

  • 驗證客戶意圖
  • 驗證請求
  • 建立訂單資料結構
  • 將訂單摘要傳遞給客戶
  1. 若要呼叫該模型,請從 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 的提示工程指南,您可以快速建立可以完成任務的提示。

  1. 建立第一個提示範本以及實用程式函數,該函數將幫助為 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:"]}"


  1. 將此範本儲存到檔案中,以便上傳到 Amazon S3 並在需要時從 Lambda 函數呼叫。將範本儲存為文字檔案中的 JSON 序列化字串。前面的螢幕截圖也顯示了完成此操作的程式碼範例。
  2. 對其他範本重複相同的步驟。

以下是其他模板的一些螢幕截圖以及使用其中一些模板呼叫 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 層:

  1. 在 SageMaker Studio 中,建立一個新資料夾,其中包含名為 python.
  2. 將提示檔案複製到 python 文件夾中。

  1. 您可以透過執行以下命令將 ZIP 庫新增至您的筆記本實例。
!conda install -y -c conda-forge zip

  1. 現在,執行以下命令建立 ZIP 檔案以上傳到 Lambda 層。
!zip -r prompt_templates_layer.zip prompt_templates_layer/.

  1. 建立 ZIP 檔案後,您可以下載該檔案。前往 Lambda,透過直接上傳檔案或先上傳到 Amazon S3 來建立新層。
  2. 然後將此新層附加到編排 Lambda 函數。

現在,您的提示範本檔案會本機儲存在您的 Lambda 執行時間環境中。這將加快機器人運作過程中的速度。

使用所需的庫建立 Lambda 層

完成以下步驟以使用所需的庫建立 Lambda 層:

  1. 打開 AWS 雲9 實例環境,建立一個資料夾,其子資料夾名為 python.
  2. 在裡面打開一個終端 python 文件夾中。
  3. 從終端機執行以下命令:
pip install “boto3>=1.28.57” -t .
pip install “awscli>=1.29.57" -t .
pip install “botocore>=1.31.57” -t .

  1. cd .. 並將自己放置在新資料夾中,其中還有 python 子文件夾。
  2. 運行以下命令:
zip -r lambda-layer.zip

  1. 建立 ZIP 檔案後,您可以下載該檔案。前往 Lambda,透過直接上傳檔案或先上傳到 Amazon S3 來建立新層。
  2. 然後將此新層附加到編排 Lambda 函數。

在 Amazon Lex v2 中建立機器人

對於此用例,我們建立了一個 Amazon Lex 機器人,它可以為架構提供輸入/輸出接口,以便從任何接口使用語音或文字呼叫 Amazon Bedrock。由於 LLM 將處理該訂單處理代理的對話部分,而 Lambda 將編排工作流程,因此您可以建立一個具有三個意圖且沒有槽位的機器人。

  1. 在 Amazon Lex 控制台上,使用以下方法建立一個新機器人 創建一個空白機器人.

現在,您可以使用任何適當的初始話語來添加意圖,以便最終用戶開始與機器人對話。我們使用簡單的問候語並添加初始機器人回應,以便最終用戶可以提供他們的請求。建立機器人時,請確保使用具有意圖的 Lambda 程式碼掛鉤;這將觸發 Lambda 函數,該函數將協調客戶、Amazon Lex 和 LLM 之間的工作流程。

  1. 新增您的第一個意圖,這會觸發工作流程並使用意圖驗證提示範本來呼叫 Amazon Bedrock 並確定客戶想要完成的任務。添加一些簡單的話語供最終用戶開始對話。

您不需要在任何機器人意圖中使用任何插槽或初始讀取。事實上,您不需要向第二或第三個意圖添加話語。這是因為 LLM 將在整個過程中指導 Lambda。

  1. 新增確認提示。您可以稍後在 Lambda 函數中自訂此訊息。

  1. 代碼鉤, 選擇 使用 Lambda 函數進行初始化和驗證.

  1. 創建第二個意圖,無需言語,也無需初始回應。這是 PlaceOrder 意圖。

當 LLM 識別出客戶正在嘗試下訂單時,Lambda 函數將觸發此意圖並根據選單驗證客戶請求,並確保沒有遺失任何必要的資訊。請記住,所有這些都在提示範本上,因此您可以透過變更提示範本來調整此工作流程以適應任何用例。

  1. 不要添加任何插槽,但添加確認提示和拒絕響應。

  1. 選擇 使用 Lambda 函數進行初始化和驗證.

  1. 創建名為的第三個意圖 ProcessOrder 沒有範例話語,也沒有插槽。
  2. 新增初始回應、確認提示和拒絕回應。

LLM 驗證客戶請求後,Lambda 函數會觸發第三個也是最後一個意圖來處理訂單。在這裡,Lambda 將使用物件建立者範本產生訂單 JSON 資料結構以查詢 DynamoDB 表,然後使用訂單總計範本匯總整個訂單以及總計,以便 Amazon Lex 可以將其傳遞給客戶。

  1. 選擇 使用 Lambda 函數進行初始化和驗證。在客戶給予最終確認後,可以使用任何 Lambda 函數來處理訂單。

  1. 創建所有三個意圖後,轉到可視化生成器 ValidateIntent,新增一個轉到意圖步驟,並將肯定確認的輸出連接到該步驟。
  2. 新增轉到意圖後,對其進行編輯並選擇 PlaceOrder 意圖作為意圖名稱。

  1. 同樣,要使用可視化構建器 PlaceOrder 意圖並將肯定確認的輸出連接到 ProcessOrder 前往意圖。無需編輯 ProcessOrder 意圖。
  2. 現在您需要建立 Lambda 函數來編排 Amazon Lex 並呼叫 DynamoDB 表,如下節中詳述。

建立 Lambda 函數來編排 Amazon Lex 自動程序

現在您可以建立 Lambda 函數來編排 Amazon Lex 自動程式和工作流程。完成以下步驟:

  1. 使用標準執行策略建立 Lambda 函數,並讓 Lambda 為您建立角色。
  2. 在函數的程式碼視窗中,加入一些有用的實用函數:透過將 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

  1. 將您先前建立的 Lambda 層附加到此函數。
  2. 此外,將該圖層附加到您建立的提示範本。
  3. 在 Lambda 執行角色中,附加策略以存取先前建立的 Amazon Bedrock。

Lambda 執行角色應具有下列權限。

將 Orchestration Lambda 函數附加到 Amazon Lex 自動程序

  1. 在上一部分中建立函數後,返回 Amazon Lex 控制台並導航到您的機器人。
  2. 語言 在導航窗格中,選擇 English.
  3. 資源,選擇您的訂單處理機器人。
  4. Lambda函數版本或別名選擇 $最新.
  5. 選擇 節省.

建立輔助 Lambda 函數

完成以下步驟以建立其他 Lambda 函數:

  1. 建立一個 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)))

  1. 導航到 型號 Lambda 函數中的標籤並選擇 權限.
  2. 附加基於資源的策略聲明,允許訂單處理 Lambda 函數呼叫此函數。

  1. 導覽至此 Lambda 函數的 IAM 執行角色並新增存取 DynamoDB 表的策略。

  1. 建立另一個 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
        }

  1. 導航到 型號 Lambda 函數中的標籤並選擇 權限.
  2. 附加基於資源的策略聲明,允許訂單處理 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 大使。在空閒時間,他喜歡健行和踢足球。

現貨圖片

最新情報

現貨圖片