Amazon Web Services ブログ

Amazon Bedrock のエージェントがメモリ保持とコード解釈をサポートするようになりました (プレビュー中)

Agents for Amazon Bedrock を使用すると、生成人工知能 (AI) アプリケーションはさまざまなシステムやデータソースにわたって多段階のタスクを実行できます。数か月前、エージェントの作成と設定を簡素化しました。7月10日、フルマネージド型の 2 つの新機能をプレビューで紹介します。

複数のインタラクションにわたってメモリを保持 – エージェントは各ユーザーとの会話の概要を保持できるようになり、特にユーザーとのやり取りや、フライトの予約や保険金請求の処理などのエンタープライズオートメーションソリューションといった複雑なマルチステップタスクで、スムーズで適応性の高いエクスペリエンスを提供できるようになりました。

コード解釈のサポート – エージェントは、安全なサンドボックス環境内でコードスニペットを動的に生成して実行できるようになり、データ分析、データ視覚化、テキスト処理、方程式の解決、問題の最適化などの複雑なユースケースに対応できるようになりました。この機能をより使いやすくするために、ドキュメントをエージェントに直接アップロードする機能も追加しました。

これらの新機能の仕組みを詳しく見ていきましょう。

複数のインタラクションにわたるメモリの保持
メモリを保持することで、時間をかけて各ユーザーの固有のニーズや好みを学習し、それに適応するエージェントを構築できます。永続的なメモリを保持することで、エージェントはユーザーが中断したところからすぐに再開でき、特に複雑な複数ステップのタスクにおいて、会話やワークフローをスムーズに進めることができます。

ユーザーがフライトを予約するとします。メモリを保持できるため、エージェントは旅行の好みを把握し、その情報を利用してその後の予約リクエストを効率化し、パーソナライズされた効率的なエクスペリエンスを実現できます。例えば、ユーザーに適切な座席を自動的に提案したり、ユーザーが以前に選択したものと同様の食事を提案したりできます。

メモリ保持を利用してコンテキストを把握しやすくすると、ビジネスプロセスの自動化も簡単になります。例えば、企業が顧客フィードバックを処理するために使用するエージェントは、カスタムインテグレーションを処理しなくても、同じ顧客との過去および進行中のやり取りを認識できるようになりました。

各ユーザーの会話履歴とコンテキストは、固有のメモリ識別子 (ID) の下に安全に保存され、ユーザー間で完全に分離されます。メモリを保持することで、時間の経過とともに継続的に改善される、シームレスで適応性のあるパーソナライズされたエクスペリエンスを提供するエージェントを簡単に構築できます。これが実際にどのように機能するかを見てみましょう。

Amazon Bedrock のエージェントでメモリ保持を使用する
Amazon Bedrockコンソールで、ナビゲーションペインの [Builder Tools] (ビルダーツール) セクションから [Agents] (エージェント) を選択し、エージェントの作成を開始します。

エージェントについては、名前として agent-book-flight を使用し、説明としてこれを使用します。

フライトの予約を手伝ってあげましょう。

次に、エージェントビルダーで、Anthropic の Claude 3 Sonnet モデルを選択し 、次の指示を入力します。

フライトを予約するには、出発地と目的地の空港、およびフライトの離陸日時を知っておく必要があります。

追加設定では、ユーザー入力を有効にして、エージェントが明確な質問をして必要な入力を取り込めるようにしています。これは、フライトの予約リクエストで、出発地と目的地、フライトの日時などの必要な情報が不足している場合に役立ちます。

新しい [Memory] (メモリ) セクションでは、各セッションの終了時にメモリを有効にしてセッション概要を生成して保存し、メモリ持続時間はデフォルトの 30 日間とします。

コンソールのスクリーンショット。

次に、フライトを検索して予約するためのアクショングループを追加します。名前と次の説明として、「search-and-book-flights」を使用しています。

特定の日に 2 つの目的地間のフライトを検索し、特定のフライトを予約します。

次に、関数の詳細を含むアクショングループを定義し、新しい Lambda 関数を作成します。Lambda 関数は、このアクショングループのすべての関数のビジネスロジックを実装します。

このアクショングループに 2 つの関数を追加します。1 つはフライトを検索する関数、もう 1 つはフライトを予約する関数です。

最初の関数は search-for-flights で、次のような説明があります。

特定の日付の 2 つの目的地間のフライトを検索します。

この関数のすべてのパラメータは必須で、型は文字列です。パラメータの名前と説明は次のとおりです。

origin_airport – 出発地の IATA 空港コード
destination_airport –
目的地の IATA 空港コード
date –
YYYYMMDD 形式のフライトの日付

2 つ目の関数は book-flight で、次のような記述を使います。

特定の日時に 2 つの目的地間のフライトを予約します。

繰り返しますが、すべてのパラメータは必須で、タイプは文字列です。パラメータの名前と説明は次のとおりです。

origin_airport出発地の IATA 空港コード
destination_airport目的地の IATA 空港コード
dateYYYYMMDD 形式のフライトの日付
timeHHMM 形式のフライトの時間

エージェントの作成を完了するには、[Create] (作成) を選択します。

Lambda 関数のソースコードにアクセスするには、search-and-book-flights アクショングループを選択し、次に [View] (表示) (Lambda 関数の選択設定の近く) を選択します。通常は、この Lambda 関数を使用して、旅行予約プラットフォームなどの既存のシステムと統合します。ここでは、このコードを使用してエージェントの予約プラットフォームをシミュレートします。

import json
import random
from datetime import datetime, time, timedelta


def convert_params_to_dict(params_list):
    params_dict = {}
    for param in params_list:
        name = param.get("name")
        value = param.get("value")
        if name is not None:
            params_dict[name] = value
    return params_dict


def generate_random_times(date_str, num_flights, min_hours, max_hours):
    # Set seed based on input date
    seed = int(date_str)
    random.seed(seed)

    # Convert min_hours and max_hours to minutes
    min_minutes = min_hours * 60
    max_minutes = max_hours * 60

    # Generate random times
    random_times = set()
    while len(random_times) < num_flights:
        minutes = random.randint(min_minutes, max_minutes)
        hours, mins = divmod(minutes, 60)
        time_str = f"{hours:02d}{mins:02d}"
        random_times.add(time_str)

    return sorted(random_times)


def get_flights_for_date(date):
    num_flights = random.randint(1, 6) # Between 1 and 6 flights per day
    min_hours = 6 # 6am
    max_hours = 22 # 10pm
    flight_times = generate_random_times(date, num_flights, min_hours, max_hours)
    return flight_times
    
    
def get_days_between(start_date, end_date):
    # Convert string dates to datetime objects
    start = datetime.strptime(start_date, "%Y%m%d")
    end = datetime.strptime(end_date, "%Y%m%d")
    
    # Calculate the number of days between the dates
    delta = end - start
    
    # Generate a list of all dates between start and end (inclusive)
    date_list = [start + timedelta(days=i) for i in range(delta.days + 1)]
    
    # Convert datetime objects back to "YYYYMMDD" string format
    return [date.strftime("%Y%m%d") for date in date_list]


def lambda_handler(event, context):
    print(event)
    agent = event['agent']
    actionGroup = event['actionGroup']
    function = event['function']
    param = convert_params_to_dict(event.get('parameters', []))

    if actionGroup == 'search-and-book-flights':
        if function == 'search-for-flights':
            flight_times = get_flights_for_date(param['date'])
            body = f"On {param['date']} (YYYYMMDD), these are the flights from {param['origin_airport']} to {param['destination_airport']}:\n{json.dumps(flight_times)}"
        elif function == 'book-flight':
            body = f"Flight from {param['origin_airport']} to {param['destination_airport']} on {param['date']} (YYYYMMDD) at {param['time']} (HHMM) booked and confirmed."
        elif function == 'get-flights-in-date-range':
            days = get_days_between(param['start_date'], param['end_date'])
            flights = {}
            for day in days:
                flights[day] = get_flights_for_date(day)
            body = f"These are the times (HHMM) for all the flights from {param['origin_airport']} to {param['destination_airport']} between {param['start_date']} (YYYYMMDD) and {param['end_date']} (YYYYMMDD) in JSON format:\n{json.dumps(flights)}"
        else:
            body = f"Unknown function {function} for action group {actionGroup}."
    else:
        body = f"Unknown action group {actionGroup}."
    
    # Format the output as expected by the agent
    responseBody =  {
        "TEXT": {
            "body": body
        }
    }

    action_response = {
        'actionGroup': actionGroup,
        'function': function,
        'functionResponse': {
            'responseBody': responseBody
        }

    }

    dummy_function_response = {'response': action_response, 'messageVersion': event['messageVersion']}
    print(f"Response: {function_response}")

    return function_response

エージェントをコンソールでテストできるように準備し、次の質問をします。

2024 年 7 月 20 日にロンドンのヒースロー空港からローマのフィウミチーノ空港まで運航しているフライトはどれですか?

エージェントは時刻表を記載して返信します。エージェントが私の指示をどのように処理したかについての詳細情報を表示するには、[Show trace] (トレースを表示) を選択します。

[Trace] (トレース) タブでは、エージェントのオーケストレーションで使われた思考の連鎖を理解するためのトレースステップを調べます。例えば、エージェントが Lambda 関数を呼び出す前に空港名からコードへの変換 (ロンドンのヒースロー空港の場合は LHR、ローマのフィウミチーノ空港の場合は FCO) を処理したことがわかります。

新しい [Memory] (メモリ) タブに、メモリの内容が表示されます。コンソールは特定のテストメモリ ID を使用します。アプリケーションでは、ユーザーごとにメモリを分離しておくために、ユーザーごとに異なるメモリ ID を使用できます。

フライトのリストを見て、予約するように頼みます。

午後 6 時 2 分の便を予約してください。

エージェントは予約を確認する返信をします。

数分後、セッションの期限が切れると、[Memory] (メモリ) タブに会話の概要が表示されます。

コンソールのスクリーンショット。

ほうきアイコンを選択して新しい会話を開始し、それだけではエージェントに完全なコンテキストを提供できない質問をします。

フライト当日に利用できる他の便はありますか?

エージェントは、前回の会話で予約したフライトを思い出します。回答するために、エージェントがフライトの詳細を確認するよう求めてきています。Lambda 関数は単なるシミュレーションであり、どのデータベースにも予約情報を保存していないことに注意してください。フライトの詳細はエージェントのメモリから取得しました。

コンソールのスクリーンショット。

これらの値を確認して、その日の出発地と目的地の同じ他のフライトのリストを取得します。

はい、お願いします。

メモリ保持の利点をよりよく示すために、AWS SDK for Python (Boto3) を使用してエージェントを呼び出してみましょう。そのためには、まずエージェントエイリアスとバージョンを作成する必要があります。エージェント ID とエイリアス ID は、エージェントを呼び出すときに必要になるため、書き留めておきます。

エージェント呼び出しでは、メモリを使用するための新しい memoryId オプションを追加します。このオプションを含めると、次の 2 つのメリットが得られます。

  • その memoryId に保持されているメモリ (ある場合) は、エージェントが応答を改善するために使用されます。
  • 現在のセッションの会話の概要は、別のセッションで使用できるように、その memoryId に保持されます。

AWS SDK を使用すると、特定の memoryId のメモリのコンテンツを取得したり、削除したりすることもできます。

import random
import string
import boto3
import json

DEBUG = False # Enable debug to see all trace steps
DATE_FORMAT = "%Y-%m-%d %H:%M:%S"

AGENT_ID = 'URSVOGLFNX'
AGENT_ALIAS_ID = 'JHLX9ERCMD'

SESSION_ID_LENGTH = 10
SESSION_ID = "".join(
    random.choices(string.ascii_uppercase + string.digits, k=SESSION_ID_LENGTH)
)

# A unique identifier for each user
MEMORY_ID = 'danilop-92f79781-a3f3-4192-8de6-890b67c63d8b' 
bedrock_agent_runtime = boto3.client('bedrock-agent-runtime', region_name='us-east-1')


def invoke_agent(prompt, end_session=False):
    response = bedrock_agent_runtime.invoke_agent(
        agentId=AGENT_ID,
        agentAliasId=AGENT_ALIAS_ID,
        sessionId=SESSION_ID,
        inputText=prompt,
        memoryId=MEMORY_ID,
        enableTrace=DEBUG,
        endSession=end_session,
    )

    completion = ""

    for event in response.get('completion'):
        if DEBUG:
            print(event)
        if 'chunk' in event:
            chunk = event['chunk']
            completion += chunk['bytes'].decode()

    return completion


def delete_memory():
    try:
        response = bedrock_agent_runtime.delete_agent_memory(
            agentId=AGENT_ID,
            agentAliasId=AGENT_ALIAS_ID,
            memoryId=MEMORY_ID,
        )
    except Exception as e:
        print(e)
        return None
    if DEBUG:
        print(response)


def get_memory():
    response = bedrock_agent_runtime.get_agent_memory(
        agentId=AGENT_ID,
        agentAliasId=AGENT_ALIAS_ID,
        memoryId=MEMORY_ID,
        memoryType='SESSION_SUMMARY',
    )
    memory = ""
    for content in response['memoryContents']:
        if 'sessionSummary' in content:
            s = content['sessionSummary']
            memory += f"Session ID {s['sessionId']} from {s['sessionStartTime'].strftime(DATE_FORMAT)} to {s['sessionExpiryTime'].strftime(DATE_FORMAT)}\n"
            memory += s['summaryText'] + "\n"
    if memory == "":
        memory = "<no memory>"
    return memory


def main():
    print("Delete memory? (y/n)")
    if input() == 'y':
        delete_memory()

    print("Memory content:")
    print(get_memory())

    prompt = input('> ')
    if len(prompt) > 0:
        print(invoke_agent(prompt, end_session=False)) # Start a new session
        invoke_agent('end', end_session=True) # End the session

if __name__ == "__main__":
    main()

ラップトップから Python スクリプトを実行します。現在のメモリを削除して (今のところ空のはずですが)、特定の日に朝のフライトを予約するように依頼します。

Delete memory? (y/n)
y
Memory content:
<no memory>
> 2024 年 7 月 20 日の LHR から FCO への朝のフライトを予約してください。
2024 年 7 月 20 日 06:44 時のロンドンのヒースロー空港 (LHR) 発ローマのフィウミチーノ空港 (FCO) 行きの午前便を予約しました。

2、3 分待ってから、スクリプトを再実行します。このスクリプトは、実行されるたびに新しいセッションを作成します。今回は、メモリを削除せず、同じ memoryId での以前のやりとりの概要が表示されます。次に、私のフライトがいつ予定されているかを尋ねます。これは新しいセッションであっても、エージェントはメモリの内容から以前の予約を見つけます。

Delete memory? (y/n)
n
Memory content:
Session ID MM4YYW0DL2 from 2024-07-09 15:35:47 to 2024-07-09 15:35:58
ユーザーの目標は、2024 年 7 月 20 日に LHR から FCO への午前中のフライトを予約することでした。アシスタントは、2024 年 7 月 20 日の希望日に、LHR から FCO への午前 6 時 44 分のフライトを予約しました。アシスタントは、ユーザーのためにリクエストされた午前中のフライトを正常に予約しました。ユーザーは、2024 年 7 月 20 日にロンドンのヒースロー空港 (LHR) からローマのフィウミチーノ空港 (FCO) への午前中のフライト予約をリクエストしました。アシスタントは、指定されたルートと日付の午前 06 時 44 分発のフライトを予約しました。

> 私のフライトの出発便は何日ですか?
前回の会話から、2024 年 7 月 20 日にロンドンのヒースロー空港 (LHR) 発ローマのフィウミチーノ空港 (FCO) 行きの午前の便を予約したと記憶しています。2024 年 7 月 20 日のこの日付が、照会しているフライトと合致するかどうかを確認してください。

はい、それが私のフライトです。

ユースケースにもよりますが、メモリ保持機能を使うと、同じユーザーからの以前のインタラクションや好みを追跡し、セッションをまたいでシームレスなエクスペリエンスを提供できます。

セッション概要には、一般的な概要と、ユーザーとアシスタントの視点が含まれます。このセッションのように短いセッションでは、繰り返しが発生する可能性があります。

コード解釈サポート
Agents for Amazon Bedrock はコード解釈をサポートするようになったため、エージェントは安全なサンドボックス環境でコードスニペットを動的に生成して実行できるようになり、データ分析、視覚化、テキスト処理、方程式解決、最適化問題などの複雑なタスクを含め、対処できるユースケースが大幅に広がりました。

エージェントは、CSV、XLS、YAML、JSON、DOC、HTML、MD、TXT、PDF など、さまざまなデータタイプと形式の入力ファイルを処理できるようになりました。コード解釈により、エージェントはチャートも生成できるため、ユーザーエクスペリエンスが向上し、データ解釈が容易になります。

コード解釈は、大規模言語モデル (LLM) が特定の問題をより正確に解決するのに役立つと判断した場合にエージェントによって使用され、ユーザーが任意のコード生成を要求するシナリオでは設計上サポートされていません。セキュリティ上の理由から、各ユーザーセッションには分離されたサンドボックス化されたコードランタイム環境が用意されています。

簡単なテストを行って、これがエージェントが複雑なタスクを処理するのにどのように役立つかを見てみましょう。

Amazon Bedrock のエージェントでコード解釈を使用する
Amazon Bedrock コンソールで、前のデモ (agent-book-flight) と同じエージェントを選択し、[Edit in Agent Builder] (エージェントビルダーで編集) を選択します。エージェントビルダーでは、[Additional Settings] (追加設定) で [Code Interpreter] (コードインタープリター) を有効にして保存します。

コンソールのスクリーンショット。

エージェントを準備し、コンソールで直接テストします。まず、数学的な質問をします。

最初の 10 個の素数の合計を計算してください。

数秒後、エージェントから回答が届きます。

最初の 10 個の素数の合計は 129 です。

正確です。トレースを見て、エージェントはこの Python プログラムをビルドして実行し、私が尋ねたことを計算しました。

import math

def is_prime(n):
    if n < 2:
        return False
    for i in range(2, int(math.sqrt(n)) + 1):
        if n % i == 0:
            return False
    return True

primes = []
n = 2
while len(primes) < 10:
    if is_prime(n):
        primes.append(n)
    n += 1
    
print(f"The first 10 prime numbers are: {primes}")
print(f"The sum of the first 10 prime numbers is: {sum(primes)}")

さて、agent-book-flight エージェントの話に戻りましょう。長い期間にわたって利用できるフライトの全体像を把握したいと思います。そのためには、まず同じアクショングループに新しい関数を追加して、特定の日付範囲で利用できるすべてのフライトが得られるようにします。

新しい関数に get-flights-in-date-range という名前を付けて、次の説明を使います。

特定の日付範囲でそれぞれの日の、2 つの目的地間の全フライトを取得してください。

すべてのパラメータは必須で、文字列タイプです。パラメータ名と説明は次のとおりです。

origin_airport出発地の IATA 空港コード
destination_airport目的地の IATA 空港コード
開始日 – YYYYMMDD 形式のフライトの開始日
終了日YYYYMMDD 形式のフライトの終了日

前に紹介した Lambda 関数コードを見ると、このエージェント関数は既にサポートされていることがわかります。

エージェントが 1 回の関数呼び出しでより多くの情報を抽出できるようになったので、エージェントにフライト情報データをチャートで視覚化するように依頼します。

2024 年 8 月の最初の 10 日間の JFK から SEA への毎日のフライト数をチャートにしてください。

エージェントの返信には次のようなチャートが含まれています。

コンソールのスクリーンショット。

コンピュータに画像をダウンロードするためのリンクを選択します。

フライトチャート。

正解です。実際、Lambda 関数のシミュレーターは、チャートに示されているように 1 日に 1~6 回のフライトを生成します。

添付ファイルでコード解釈を使用する
コード解釈により、エージェントはデータから情報を処理して抽出できるため、エージェントを呼び出すときにドキュメントを含める機能を導入しました。例えば、さまざまなフライトで予約したフライトの数が記載された Excel ファイルがあります。

出発地 目的地 フライト数
LHR FCO 636
FCO LHR 456
JFK SEA 921
SEA JFK 544

テストインターフェイスのクリップアイコンを使用して、ファイルを添付して質問します (エージェントは太字で返信します)。

一番人気の路線はどれですか? そして、一番人気のない路線は?

分析によると、最も人気のある路線は 921 件の予約がある JFK->SEA で、最も人気のない路線は 456 件の予約がある FCO-> LHR です。

合計でいくつのフライトが予約されていますか?

全路線の予約便の総数は 2557 便です。

これらの路線で予約されたフライトの割合を合計数と比較したチャートを作成してください。

コードインタープリターで生成されたチャート

トレースを見ると、ファイルから情報を抽出してエージェントに渡すために使用された Python コードを確認できます。複数のファイルを添付して、さまざまなファイル形式を使用できます。これらのオプションは AWS SDK で利用可能で、エージェントはアプリケーションのファイルを使用できます。

知っておくべきこと
メモリ保持は、Amazon Bedrocks のエージェントおよび Anthropic の Claude 3 Sonnet または Haiku (プレビュー中にサポート対象のモデル) が利用できるすべての AWS リージョンでプレビュー版としてご利用いただけます。コード解釈は、米国東部 (バージニア北部)、米国西部 (オレゴン)、および欧州 (フランクフルト) リージョンでプレビュー版としてご利用いただけます。

プレビュー中にエージェントでメモリ保持とコード解釈を使用しても、追加料金はかかりません。エージェントをこれらの機能とともに使用する場合、通常のモデル使用料が適用されます。メモリ保持が有効になっている場合は、セッションの要約に使用されたモデルの料金が発生します。詳細については、Amazon Bedrock の料金ページをご覧ください。

詳細については、ユーザーガイドの「Amazon Bedrock のエージェント」セクションを参照してください。詳細な技術コンテンツや、他の企業が自社のソリューションで生成 AI をどのように使用しているかを知るには、community.aws にアクセスしてください。

Danilo

原文はこちらです。