ゼファーネットのロゴ

1 対 1 の顧客インタラクションを変革する: AWS と生成 AI を使用して音声対応の注文処理エージェントを構築する |アマゾン ウェブ サービス

日付:

顧客と 1 対 1 でやり取りして注文を行う今日の状況では、ドライブスルー コーヒー ショップやファストフード店などの環境でも、引き続き人間の係員に依存するのが一般的です。この従来のアプローチにはいくつかの課題があります。手動プロセスに大きく依存し、顧客の需要の増加に合わせて効率的に拡張するのに苦労し、人的エラーが発生する可能性があり、利用可能な特定の時間内で運用する必要があります。さらに、競争の激しい市場では、手動プロセスのみに固執している企業は、効率的で競争力のあるサービスを提供することが困難になる可能性があります。技術の進歩にも関わらず、人間中心のモデルが依然として注文処理に深く根付いており、これらの制限が生じています。

1 対 1 の注文処理支援にテクノロジーを活用する見通しはかなり前からありました。しかし、既存のソリューションは多くの場合 2 つのカテゴリに分類されます。1 つはセットアップと維持に多大な時間と労力を必要とするルールベースのシステム、もう 1 つは顧客との人間のようなやり取りに必要な柔軟性に欠ける厳格なシステムです。その結果、企業や組織は、そのようなソリューションを迅速かつ効率的に導入するという課題に直面しています。幸いなことに、 generative AI & 大規模言語モデル (LLM)、自然言語を効率的に処理し、加速されたタイムラインを備えた自動システムを作成できるようになりました。

アマゾンの岩盤 は、AI21 Labs、Anthropic、Cohere、Meta、Stability AI、Amazon などの主要 AI 企業の高性能基盤モデル (FM) を単一の API 経由で提供するフルマネージド サービスです。また、幅広い機能セットを提供します。セキュリティ、プライバシー、責任ある AI を備えた生成 AI アプリケーションを構築する必要があります。 Amazon Bedrock に加えて、次のような他の AWS サービスを使用できます。 Amazon SageMaker ジャンプスタート & Amazon Lex 完全に自動化され、簡単に適応できる生成 AI 注文処理エージェントを作成します。

この投稿では、Amazon Lex、Amazon Bedrock、および Amazon Bedrock を使用して音声対応の注文処理エージェントを構築する方法を説明します。 AWSラムダ.

ソリューションの概要

次の図は、ソリューションアーキテクチャを示しています。

ワークフローは次の手順で構成されます。

  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 エンドポイントを呼び出して、顧客データベース システムからの注文合計を含む最終注文概要を生成します (たとえば、 Amazon DynamoDB).
  9. 注文の概要は、Amazon Lex 経由で顧客に返送されます。お客様がご注文を確認した後、注文が処理されます。

前提条件

この投稿は、アクティブな AWS アカウントがあり、次の概念とサービスに精通していることを前提としています。

また、Lambda 関数から Amazon Bedrock にアクセスするには、Lambda ランタイムに次のライブラリがあることを確認する必要があります。

  • boto3>=1.28.57
  • awscli>=1.29.57
  • ボトコア>=1.31.57

これは、 ラムダ層 または、必要なライブラリを備えた特定の AMI を使用します。

さらに、これらのライブラリは、Amazon Bedrock API を呼び出すときに必要になります。 Amazon SageMakerスタジオ。これは、次のコードを使用してセルを実行することで実行できます。

%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 テーブルを作成しましたが、以下を使用することもできます。 Amazon リレーショナル データベース サービス (アマゾン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 の Boto3 ライブラリを使用して DynamoDB への接続をセットアップする Python コードを作成します。このコード スニペットは、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 にアクセスし、プロンプト テンプレートだけでなく、後で Lambda 関数で使用できる完全な API 呼び出しコードを作成します。

  1. SageMaker コンソールで、既存の SageMaker Studio ドメインにアクセスするか、新しいドメインを作成して SageMaker ノートブックから Amazon Bedrock にアクセスします。

  1. SageMaker ドメインとユーザーを作成した後、ユーザーを選択し、 起動する & Studio。これにより、JupyterLab 環境が開きます。
  2. JupyterLab 環境の準備ができたら、新しいノートブックを開いて、必要なライブラリのインポートを開始します。

Amazon Bedrock Python SDK 経由で利用できる FM が多数あります。この例では、Anthropic が開発した強力な基本モデルである Claude V2 を使用します。

注文処理エージェントには、いくつかの異なるテンプレートが必要です。これはユースケースに応じて変わる可能性がありますが、複数の設定に適用できる一般的なワークフローを設計しました。このユースケースでは、Amazon Bedrock LLM テンプレートは次のことを実現します。

  • 顧客の意図を検証する
  • リクエストを検証する
  • 注文データ構造を作成する
  • 注文の概要を顧客に渡す
  1. モデルを呼び出すには、Boto3 から Bedrock-Runtime オブジェクトを作成します。

#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. 次に、次のコマンドを実行して、Lambda レイヤーにアップロードするための ZIP ファイルを作成します。
!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 Bedrock を呼び出すために、アーキテクチャの入出力インターフェイスを提供できる Amazon Lex ボットを構築します。 LLM がこの注文処理エージェントの会話部分を処理し、Lambda がワークフローを調整するため、3 つのインテントを持ち、スロットのないボットを作成できます。

  1. Amazon Lex コンソールで、次のメソッドを使用して新しいボットを作成します。 空のボットを作成する.

これで、エンドユーザーがボットとの会話を開始するための適切な最初の発話を含むインテントを追加できるようになりました。簡単な挨拶を使用し、エンドユーザーがリクエストを提供できるように最初のボット応答を追加します。ボットを作成するときは、インテントを含む Lambda コード フックを必ず使用してください。これにより、顧客、Amazon Lex、LLM 間のワークフローを調整する Lambda 関数がトリガーされます。

  1. 最初のインテントを追加します。これにより、ワークフローがトリガーされ、インテント検証プロンプトテンプレートを使用して Amazon Bedrock が呼び出され、顧客が達成しようとしていることを特定します。エンドユーザーが会話を始めるための簡単な発話をいくつか追加します。

ボット インテントでスロットや初期読み取りを使用する必要はありません。実際、2 番目または 3 番目のインテントに発話を追加する必要はありません。これは、LLM がプロセス全体を通じて Lambda をガイドするためです。

  1. 確認プロンプトを追加します。このメッセージは後で Lambda 関数でカスタマイズできます。

  1. コードフック選択 初期化と検証にLambda関数を使用する.

  1. 発話も初期応答も行わない 2 番目のインテントを作成します。これは PlaceOrder 意図。

顧客が注文しようとしていることを LLM が識別すると、Lambda 関数がこのインテントをトリガーし、メニューに対して顧客のリクエストを検証し、必要な情報が欠落していないことを確認します。これらすべてはプロンプト テンプレートに含まれるため、プロンプト テンプレートを変更することで、このワークフローをあらゆるユースケースに適応させることができることに注意してください。

  1. スロットは追加しませんが、確認プロンプトと拒否応答を追加します。

  1. 選択 初期化と検証にLambda関数を使用する.

  1. という名前の 3 番目のインテントを作成します。 ProcessOrder サンプル発話やスロットはありません。
  2. 最初の応答、確認プロンプト、拒否応答を追加します。

LLM が顧客リクエストを検証した後、Lambda 関数は注文を処理する 3 番目で最後のインテントをトリガーします。ここで、Lambda はオブジェクト作成者テンプレートを使用して注文 JSON データ構造を生成し、DynamoDB テーブルにクエリを実行します。その後、注文概要テンプレートを使用して注文全体と合計を要約し、Amazon Lex が注文を顧客に渡せるようにします。

  1. 選択 初期化と検証にLambda関数を使用する。これにより、顧客が最終確認を行った後に任意の Lambda 関数を使用して注文を処理できます。

  1. 3 つのインテントをすべて作成したら、ビジュアル ビルダーに移動して、 ValidateIntent、インテントへのステップを追加し、肯定的な確認の出力をそのステップに接続します。
  2. go-to インテントを追加した後、それを編集し、インテント名として PlaceOrder インテントを選択します。

  1. 同様に、Visual Builder に移動するには、 PlaceOrder 意図を確認し、肯定的な確認の出力を ProcessOrder ゴートゥの意図。編集する必要はありません。 ProcessOrder 意図。
  2. 次のセクションで詳しく説明するように、Amazon Lex を調整し、DynamoDB テーブルを呼び出す Lambda 関数を作成する必要があります。

Amazon Lex ボットを調整するための Lambda 関数を作成する

これで、Amazon Lex ボットとワークフローを調整する Lambda 関数を構築できるようになりました。次の手順を実行します。

  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 実行ロールには次の権限が必要です。

オーケストレーション Lambda 関数を Amazon Lex ボットにアタッチする

  1. 前のセクションで関数を作成した後、Amazon Lex コンソールに戻り、ボットに移動します。
  2. ESL, ビジネスESL <br> 中国語/フランス語、その他 ナビゲーション ペインで、 英語.
  3. ソース、注文処理ボットを選択します。
  4. ラムダ関数のバージョンまたはエイリアス、選択する $ LATEST.
  5. 選択する Save.

補助的な Lambda 関数を作成する

追加の Lambda 関数を作成するには、次の手順を実行します。

  1. 前に作成した DynamoDB テーブルにクエリを実行する Lambda 関数を作成します。
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 関数は、注文を処理するために必要な属性がすべて存在するかどうかを確認し、不足している情報を提供するよう顧客に求めます。顧客が不足している情報 (この場合はコーヒーのサイズ) を入力すると、注文合計と注文概要が表示されます。ご注文はお客様の最終確認に基づいて処理されます。

LLM の制限事項

LLM 出力は本質的に確率論的です。つまり、LLM からの結果は形式が異なる可能性があり、真実でないコンテンツ (幻覚) の形式でさえも変化する可能性があります。したがって、開発者は、これらのシナリオを処理し、エンド ユーザー エクスペリエンスの低下を回避するために、コード全体で優れたエラー処理ロジックに依存する必要があります。

クリーンアップ

このソリューションが不要になった場合は、次のリソースを削除できます。

  • ラムダ関数
  • アマゾンレックスボックス
  • DynamoDBテーブル
  • S3バケット

さらに、アプリケーションが不要になった場合は、SageMaker Studio インスタンスをシャットダウンします。

コスト評価

このソリューションで使用される主なサービスの価格情報については、次を参照してください。

Claude v2 はプロビジョニングを必要とせずに使用できるため、全体的なコストは最小限に抑えられることに注意してください。さらにコストを削減するために、DynamoDB テーブルをオンデマンド設定で構成できます。

まとめ

この投稿では、Amazon Lex、Amazon Bedrock、その他の AWS サービスを使用して音声対応 AI 注文処理エージェントを構築する方法を説明しました。クロードのような強力な生成 AI モデルを使用した迅速なエンジニアリングにより、大規模なトレーニング データを必要とせずに、注文処理のための堅牢な自然言語理解と会話フローがどのように可能になるかを示しました。

ソリューション アーキテクチャでは、Lambda、Amazon S3、DynamoDB などのサーバーレス コンポーネントを使用して、柔軟でスケーラブルな実装を可能にします。プロンプトテンプレートを Amazon S3 に保存すると、さまざまなユースケースに合わせてソリューションをカスタマイズできます。

次のステップには、より広範囲の顧客リクエストや特殊なケースを処理するためのエージェントの機能の拡張が含まれる可能性があります。プロンプト テンプレートは、エージェントのスキルを反復的に向上させる方法を提供します。追加のカスタマイズには、注文データと在庫、CRM、POS などのバックエンド システムの統合が含まれる場合があります。最後に、Amazon Lex のマルチチャネル機能を使用して、モバイルアプリ、ドライブスルー、キオスクなどのさまざまな顧客タッチポイントでエージェントを利用できるようになります。

詳細については、次の関連リソースを参照してください。

  • マルチチャネルボットの導入と管理:
  • クロードおよびその他のモデルの迅速なエンジニアリング:
  • スケーラブルな AI アシスタントのためのサーバーレス アーキテクチャ パターン:

著者について

ムミタ・ダッタ アマゾン ウェブ サービスのパートナー ソリューション アーキテクトです。彼女はその役割において、パートナーと緊密に連携して、クラウド展開を合理化し、運用効率を高めるスケーラブルで再利用可能な資産を開発しています。彼女は AI/ML コミュニティのメンバーであり、AWS の生成 AI 専門家です。余暇にはガーデニングやサイクリングを楽​​しんでいます。

フェルナンド・ランモーリア アマゾン ウェブ サービスのパートナー ソリューション アーキテクトであり、AWS パートナーと緊密に連携して、ビジネス ユニット全体で最先端の AI ソリューションの開発と導入を主導しています。クラウド アーキテクチャ、生成 AI、機械学習、データ分析の専門知識を持つ戦略的リーダー。彼は、市場開拓戦略の実行と、組織の目標に沿った影響力のある AI ソリューションの提供を専門としています。自由時間には、家族と時間を過ごしたり、他の国に旅行したりするのが大好きです。

ミトゥル・パテル アマゾン ウェブ サービスのシニア ソリューション アーキテクトです。クラウドテクノロジーのイネーブラーとしての役割において、彼は顧客と協力して目標と課題を理解し、AWS 製品で目的を達成するための規範的なガイダンスを提供します。彼は AI/ML コミュニティのメンバーであり、AWS の Generative AI アンバサダーです。自由時間には、ハイキングやサッカーを楽しんでいます。

スポット画像

最新のインテリジェンス

スポット画像