[アップデート] Amazon Bedrock で新たに Session Management API がリリースされました

[アップデート] Amazon Bedrock で新たに Session Management API がリリースされました

Clock Icon2025.03.02

こんにちは!クラウド事業本部コンサルティング部のたかくに(@takakuni_)です。

Amazon Bedrock で新たに Session Management API がリリースされました。

https://aws.amazon.com/jp/about-aws/whats-new/2025/02/amazon-bedrock-session-management-apis-genai-applications-preview/

Session Management API

Session Management API は LangGraph や LlamaIndex などの OSS フレームワークで作成した LLM アプリケーションのコンテキスト情報を保管するための API です。

従来、AWS 上で会話履歴などのコンテキスト情報を保管する場合、DynamoDB や RDBMS などを利用されていた方も多いのではないでしょうか。

今回の Session Management API は、上記の OSS ツールのコンテキスト情報を保管するために最適化された API になります。

https://docs.aws.amazon.com/bedrock/latest/userguide/sessions.html

次のリージョンでプレビューサポートです。

  • オハイオ
  • バージニア北部
  • オレゴン
  • ムンバイ
  • ソウル
  • シンガポール
  • シドニー
  • 東京
  • カナダ
  • フランクフルト
  • アイルランド
  • ロンドン
  • パリ
  • チューリッヒ
  • サンパウロ

US East (Ohio), US East (N. Virginia), US West (Oregon), Asia Pacific (Mumbai), Asia Pacific (Seoul), Asia Pacific (Singapore), Asia Pacific (Sydney), Asia Pacific (Tokyo), Canada (Central), Europe (Frankfurt), Europe (Ireland), Europe (London), Europe (Paris), Europe (Zurich), and South America (São Paulo).

Session

Sessionはコンテキスト情報を保管する機能です。Session Management API は、次の制約事項に基づいて、セッション内にコンテキスト情報を保存できます。

  • セッション内の起動ステップ数:1000 ステップまで
  • 1 ステップあたりの最大サイズ:50 MB
  • セッションのタイムアウト:1 時間
  • 保持期間:30日後に自動的に削除
    • 明示的に削除も可能

https://docs.aws.amazon.com/bedrock/latest/userguide/sessions.html#sessions-considerations

制約付きですが、画像ファイルも埋め込み可能です。

  • ステップ内に最大 20 枚まで含めることが可能
    • サイズは 3.75 MB まで
    • 高さ、幅は、それぞれ、8000 ピクセル以下であること
  • 形式は以下をサポート
    • PNG
    • JPEG
    • GIF
    • WEBP

https://docs.aws.amazon.com/bedrock/latest/userguide/sessions-store-coversation.html

やってみた

それでは Session Management API を利用して、コンテキストを保持しつつ LLM アプリケーションを動作させてみます。

ドキュメントにサンプルコードがあったため、こちらに乗っかってみます。

https://docs.aws.amazon.com/bedrock/latest/userguide/sessions-opensource-library.html

リージョンの変更など、最終的に利用したコードは以下になります。

main.py
import boto3
from typing import Dict, TypedDict, Annotated, Sequence, Union
from langgraph.graph import StateGraph, END
from langgraph_checkpoint_aws.saver import BedrockSessionSaver
from langchain_core.messages import HumanMessage, AIMessage
import json

REGION="ap-northeast-1"

# Define state structure
class State(TypedDict):
    messages: Sequence[Union[HumanMessage, AIMessage]]
    current_question: str


# Function to get response from Claude
def get_response(messages):
    bedrock = boto3.client('bedrock-runtime', region_name=REGION)
    prompt = "\n".join([f"{'Human' if isinstance(m, HumanMessage) else 'Assistant'}: {m.content}"
                        for m in messages])

    response = bedrock.invoke_model(
        modelId="apac.anthropic.claude-3-5-sonnet-20241022-v2:0",
        body=json.dumps({
            "anthropic_version": "bedrock-2023-05-31",
            "max_tokens": 1000,
            "messages": [
                {
                    "role": "user",
                    "content": [
                        {
                            "type": "text",
                            "text": prompt
                        }
                    ]
                }
            ],
            "temperature": 0.7
        })
    )

    response_body = json.loads(response['body'].read())
    return response_body['content'][0]['text']


# Node function to process user question
def process_question(state: State) -> Dict:
    messages = list(state["messages"])
    messages.append(HumanMessage(content=state["current_question"]))

    # Get response from Claude
    response = get_response(messages)
    messages.append(AIMessage(content=response))

    # Print assistant's response
    print("\nAssistant:", response)

    # Get next user input
    next_question = input("\nYou: ").strip()

    return {
        "messages": messages,
        "current_question": next_question
    }


# Node function to check if conversation should continue
def should_continue(state: State) -> bool:
    # Check if the last message was from the user and contains 'quit'
    if state["current_question"].lower() == 'quit':
        return False
    return True


# Create the graph
def create_graph(session_saver):
    # Initialize state graph
    workflow = StateGraph(State)

    # Add nodes
    workflow.add_node("process_question", process_question)

    # Add conditional edges
    workflow.add_conditional_edges(
        "process_question",
        should_continue,
        {
            True: "process_question",
            False: END
        }
    )

    # Set entry point
    workflow.set_entry_point("process_question")

    return workflow.compile(session_saver)


def main():
    # Create a runtime client
    agent_run_time_client = boto3.client("bedrock-agent-runtime",
                                         region_name=REGION)
            
    # Initialize Bedrock session saver. The region must match the region used for the agent_run_time_client.
    session_saver = BedrockSessionSaver(region_name=REGION)

    # Create graph
    graph = create_graph(session_saver)

    # Create session
    session_id = agent_run_time_client.create_session()["sessionId"]
    print("Session started. Type 'quit' to end.")

    # Configure graph
    config = {"configurable": {"thread_id": session_id}}

    # Initial state
    state = {
        "messages": [],
        "current_question": "Hello! How can I help you today? (Type 'quit' to end)"
    }

    # Print initial greeting
    print(f"\nAssistant: {state['current_question']}")

    state["current_question"] = input("\nYou: ").strip()

    # Process the question through the graph
    graph.invoke(state, config)
    print("\nSession contents:")
    for i in graph.get_state_history(config, limit=3):
        print(i)


if __name__ == "__main__":
    main()

langgraph-checkpoint-aws のインストール

langgraph-checkpoint-aws は執筆時点では PyPI に登録されていないためインストールに失敗しました。

takakuni@Mac bedrock_session_management_api % poetry add langgraph-checkpoint-aws

Could not find a matching version of package langgraph-checkpoint-aws

そのため、 GitHub から直接インストールする形を採用しました。一般提供時にはこの辺りがクリアになっていると良いですね。

takakuni@Mac bedrock_session_management_api % poetry add git+https://github.com/langchain-ai/langchain-aws.git#subdirectory=libs/langgraph-checkpoint-aws

Updating dependencies
Resolving dependencies... (0.1s)

Package operations: 1 install, 0 updates, 0 removals

  - Installing langgraph-checkpoint-aws (0.1.0 aaa9c4a)

Writing lock file

プログラムを実行してみました。確かに私の名前を記憶して会話されていることがわかります。最後に quit を入力すると、コンテキストを出力するよう設定されていたため、会話履歴が出力されています。

(bedrock-session-management-api-py3.12) takakuni@Mac bedrock_session_management_api % python main.py
Session started. Type 'quit' to end.

Assistant: Hello! How can I help you today? (Type 'quit' to end)

You: こんにちは!私の名前は takakuni です。

Assistant: こんにちは、takakuniさん!
お話できて嬉しいです。今日は何かお手伝いできることはありますか?

You: あなたの名前を教えてください。

Assistant: 私の名前はClaudeです。Anthropicによって作られたAIアシスタントです。どうぞよろしくお願いします。

You: 私の名前は何でしたか?

Assistant: あなたの名前はtakakuniさんですね。

You: 私の年齢はいくつですか?

Assistant: 申し訳ありませんが、あなたの年齢については教えていただいていないので分かりません。

You: 私の職業は何ですか?

Assistant: 申し訳ありませんが、あなたの職業については教えていただいていないので分かりません。今の会話では、お名前が「takakuni」さんということ以外の個人情報は伺っていません。

You: quit

Session contents:
StateSnapshot(values={'messages': [HumanMessage(content='こんにちは!私の名前は takakuni です。', additional_kwargs={}, response_metadata={}), AIMessage(content='こんにちは、takakuniさん!\nお話できて嬉しいです。今日は何かお手伝いできることはありますか?', additional_kwargs={}, response_metadata={}), HumanMessage(content='あなたの名前を教えてください。', additional_kwargs={}, response_metadata={}), AIMessage(content='私の名前はClaudeです。Anthropicによって作られたAIアシスタントです。どうぞよろしくお願いします。', additional_kwargs={}, response_metadata={}), HumanMessage(content='私の名前は何でしたか?', additional_kwargs={}, response_metadata={}), AIMessage(content='あなたの名前はtakakuniさんですね。', additional_kwargs={}, response_metadata={}), HumanMessage(content='私の年齢はいくつですか?', additional_kwargs={}, response_metadata={}), AIMessage(content='申し訳ありませんが、あなたの年齢については教えていただいていないので分かりません。', additional_kwargs={}, response_metadata={}), HumanMessage(content='私の職業は何ですか?', additional_kwargs={}, response_metadata={}), AIMessage(content='申し訳ありませんが、あなたの職業については教えていただいていないので分かりません。今の会話では、お名前が「takakuni」さんということ以外の個人情報は伺っていません。', additional_kwargs={}, response_metadata={})], 'current_question': 'quit'}, next=(), config={'configurable': {'thread_id': '22067db8-6cba-4195-b8af-46feba523933', 'checkpoint_ns': '', 'checkpoint_id': '1eff7619-e813-6b00-8005-ce520ef41d24'}}, metadata={'source': 'loop', 'writes': {'process_question': {'messages': [HumanMessage(content='こんにちは!私の名前は takakuni です。', additional_kwargs={}, response_metadata={}), AIMessage(content='こんにちは、takakuniさん!\nお話できて嬉しいです。今日は何かお手伝いできることはありますか?', additional_kwargs={}, response_metadata={}), HumanMessage(content='あなたの名前を教えてください。', additional_kwargs={}, response_metadata={}), AIMessage(content='私の名前はClaudeです。Anthropicによって作られたAIアシスタントです。どうぞよろしくお願いします。', additional_kwargs={}, response_metadata={}), HumanMessage(content='私の名前は何でしたか?', additional_kwargs={}, response_metadata={}), AIMessage(content='あなたの名前はtakakuniさんですね。', additional_kwargs={}, response_metadata={}), HumanMessage(content='私の年齢はいくつですか?', additional_kwargs={}, response_metadata={}), AIMessage(content='申し訳ありませんが、あなたの年齢については教えていただいていないので分かりません。', additional_kwargs={}, response_metadata={}), HumanMessage(content='私の職業は何ですか?', additional_kwargs={}, response_metadata={}), AIMessage(content='申し訳ありませんが、あなたの職業については教えていただいていないので分かりません。今の会話では、お名前が「takakuni」さんということ以外の個人情報は伺っていません。', additional_kwargs={}, response_metadata={})], 'current_question': 'quit'}}, 'thread_id': '22067db8-6cba-4195-b8af-46feba523933', 'step': 5, 'parents': {}}, created_at='2025-03-02T12:26:51.493196+00:00', parent_config=None, tasks=())
StateSnapshot(values={'messages': [HumanMessage(content='こんにちは!私の名前は takakuni です。', additional_kwargs={}, response_metadata={}), AIMessage(content='こんにちは、takakuniさん!\nお話できて嬉しいです。今日は何かお手伝いできることはありますか?', additional_kwargs={}, response_metadata={}), HumanMessage(content='あなたの名前を教えてください。', additional_kwargs={}, response_metadata={}), AIMessage(content='私の名前はClaudeです。Anthropicによって作られたAIアシスタントです。どうぞよろしくお願いします。', additional_kwargs={}, response_metadata={}), HumanMessage(content='私の名前は何でしたか?', additional_kwargs={}, response_metadata={}), AIMessage(content='あなたの名前はtakakuniさんですね。', additional_kwargs={}, response_metadata={}), HumanMessage(content='私の年齢はいくつですか?', additional_kwargs={}, response_metadata={}), AIMessage(content='申し訳ありませんが、あなたの年齢については教えていただいていないので分かりません。', additional_kwargs={}, response_metadata={})], 'current_question': '私の職業は何ですか?'}, next=('process_question',), config={'configurable': {'thread_id': '22067db8-6cba-4195-b8af-46feba523933', 'checkpoint_ns': '', 'checkpoint_id': '1eff7619-8523-63d8-8004-852ebdb0e450'}}, metadata={'source': 'loop', 'writes': {'process_question': {'messages': [HumanMessage(content='こんにちは!私の名前は takakuni です。', additional_kwargs={}, response_metadata={}), AIMessage(content='こんにちは、takakuniさん!\nお話できて嬉しいです。今日は何かお手伝いできることはありますか?', additional_kwargs={}, response_metadata={}), HumanMessage(content='あなたの名前を教えてください。', additional_kwargs={}, response_metadata={}), AIMessage(content='私の名前はClaudeです。Anthropicによって作られたAIアシスタントです。どうぞよろしくお願いします。', additional_kwargs={}, response_metadata={}), HumanMessage(content='私の名前は何でしたか?', additional_kwargs={}, response_metadata={}), AIMessage(content='あなたの名前はtakakuniさんですね。', additional_kwargs={}, response_metadata={}), HumanMessage(content='私の年齢はいくつですか?', additional_kwargs={}, response_metadata={}), AIMessage(content='申し訳ありませんが、あなたの年齢については教えていただいていないので分かりません。', additional_kwargs={}, response_metadata={})], 'current_question': '私の職業は何ですか?'}}, 'thread_id': '22067db8-6cba-4195-b8af-46feba523933', 'step': 4, 'parents': {}}, created_at='2025-03-02T12:26:41.118649+00:00', parent_config=None, tasks=(PregelTask(id='778e0b07-aaa1-c9bb-f6f1-c48aad619890', name='process_question', path=('__pregel_pull', 'process_question'), error=None, interrupts=(), state=None, result={'messages': [HumanMessage(content='こんにちは!私の名前は takakuni です。', additional_kwargs={}, response_metadata={}), AIMessage(content='こんにちは、takakuniさん!\nお話できて嬉しいです。今日は何かお手伝いできることはありますか?', additional_kwargs={}, response_metadata={}), HumanMessage(content='あなたの名前を教えてください。', additional_kwargs={}, response_metadata={}), AIMessage(content='私の名前はClaudeです。Anthropicによって作られたAIアシスタントです。どうぞよろしくお願いします。', additional_kwargs={}, response_metadata={}), HumanMessage(content='私の名前は何でしたか?', additional_kwargs={}, response_metadata={}), AIMessage(content='あなたの名前はtakakuniさんですね。', additional_kwargs={}, response_metadata={}), HumanMessage(content='私の年齢はいくつですか?', additional_kwargs={}, response_metadata={}), AIMessage(content='申し訳ありませんが、あなたの年齢については教えていただいていないので分かりません。', additional_kwargs={}, response_metadata={}), HumanMessage(content='私の職業は何ですか?', additional_kwargs={}, response_metadata={}), AIMessage(content='申し訳ありませんが、あなたの職業については教えていただいていないので分かりません。今の会話では、お名前が「takakuni」さんということ以外の個人情報は伺っていません。', additional_kwargs={}, response_metadata={})], 'current_question': 'quit'}),))
StateSnapshot(values={'messages': [HumanMessage(content='こんにちは!私の名前は takakuni です。', additional_kwargs={}, response_metadata={}), AIMessage(content='こんにちは、takakuniさん!\nお話できて嬉しいです。今日は何かお手伝いできることはありますか?', additional_kwargs={}, response_metadata={}), HumanMessage(content='あなたの名前を教えてください。', additional_kwargs={}, response_metadata={}), AIMessage(content='私の名前はClaudeです。Anthropicによって作られたAIアシスタントです。どうぞよろしくお願いします。', additional_kwargs={}, response_metadata={}), HumanMessage(content='私の名前は何でしたか?', additional_kwargs={}, response_metadata={}), AIMessage(content='あなたの名前はtakakuniさんですね。', additional_kwargs={}, response_metadata={})], 'current_question': '私の年齢はいくつですか?'}, next=('process_question',), config={'configurable': {'thread_id': '22067db8-6cba-4195-b8af-46feba523933', 'checkpoint_ns': '', 'checkpoint_id': '1eff7619-27c1-646a-8003-3ade740e5ffd'}}, metadata={'source': 'loop', 'writes': {'process_question': {'messages': [HumanMessage(content='こんにちは!私の名前は takakuni です。', additional_kwargs={}, response_metadata={}), AIMessage(content='こんにちは、takakuniさん!\nお話できて嬉しいです。今日は何かお手伝いできることはありますか?', additional_kwargs={}, response_metadata={}), HumanMessage(content='あなたの名前を教えてください。', additional_kwargs={}, response_metadata={}), AIMessage(content='私の名前はClaudeです。Anthropicによって作られたAIアシスタントです。どうぞよろしくお願いします。', additional_kwargs={}, response_metadata={}), HumanMessage(content='私の名前は何でしたか?', additional_kwargs={}, response_metadata={}), AIMessage(content='あなたの名前はtakakuniさんですね。', additional_kwargs={}, response_metadata={})], 'current_question': '私の年齢はいくつですか?'}}, 'thread_id': '22067db8-6cba-4195-b8af-46feba523933', 'step': 3, 'parents': {}}, created_at='2025-03-02T12:26:31.326776+00:00', parent_config=None, tasks=(PregelTask(id='a2464a57-cca7-9af7-ba8f-7e7b3e20b561', name='process_question', path=('__pregel_pull', 'process_question'), error=None, interrupts=(), state=None, result={'messages': [HumanMessage(content='こんにちは!私の名前は takakuni です。', additional_kwargs={}, response_metadata={}), AIMessage(content='こんにちは、takakuniさん!\nお話できて嬉しいです。今日は何かお手伝いできることはありますか?', additional_kwargs={}, response_metadata={}), HumanMessage(content='あなたの名前を教えてください。', additional_kwargs={}, response_metadata={}), AIMessage(content='私の名前はClaudeです。Anthropicによって作られたAIアシスタントです。どうぞよろしくお願いします。', additional_kwargs={}, response_metadata={}), HumanMessage(content='私の名前は何でしたか?', additional_kwargs={}, response_metadata={}), AIMessage(content='あなたの名前はtakakuniさんですね。', additional_kwargs={}, response_metadata={}), HumanMessage(content='私の年齢はいくつですか?', additional_kwargs={}, response_metadata={}), AIMessage(content='申し訳ありませんが、あなたの年齢については教えていただいていないので分かりません。', additional_kwargs={}, response_metadata={})], 'current_question': '私の職業は何ですか?'}),))

ListSessions API を実行すると、検証分も含めて一覧が出ていますね。

[cloudshell-user@ip-10-130-51-220 ~]$ aws bedrock-agent-runtime list-sessions  
{
    "sessionSummaries": [
        {
            "createdAt": "2025-03-02T12:25:45.280014+00:00",
            "lastUpdatedAt": "2025-03-02T12:25:45.280014+00:00",
            "sessionArn": "arn:aws:bedrock:ap-northeast-1:123456789012:session/22067db8-6cba-4195-b8af-46feba523933",
            "sessionId": "22067db8-6cba-4195-b8af-46feba523933",
            "sessionStatus": "ACTIVE"
        },
        {
            "createdAt": "2025-03-02T12:25:02.333661+00:00",
            "lastUpdatedAt": "2025-03-02T12:25:02.333661+00:00",
            "sessionArn": "arn:aws:bedrock:ap-northeast-1:123456789012:session/c65f9fde-e4db-47cd-845d-513066a6fbd0",
            "sessionId": "c65f9fde-e4db-47cd-845d-513066a6fbd0",
            "sessionStatus": "ACTIVE"
        },
        {
            "createdAt": "2025-03-02T12:23:18.644941+00:00",
            "lastUpdatedAt": "2025-03-02T12:23:18.644941+00:00",
            "sessionArn": "arn:aws:bedrock:ap-northeast-1:123456789012:session/5a8da9ad-8359-49b0-990b-6b21f48a2727",
            "sessionId": "5a8da9ad-8359-49b0-990b-6b21f48a2727",
            "sessionStatus": "ACTIVE"
        },
        {
            "createdAt": "2025-03-02T10:30:26.818296+00:00",
            "lastUpdatedAt": "2025-03-02T10:30:26.818296+00:00",
            "sessionArn": "arn:aws:bedrock:ap-northeast-1:123456789012:session/1685f15e-d65b-42b9-a9c0-2a8848ff70c8",
            "sessionId": "1685f15e-d65b-42b9-a9c0-2a8848ff70c8",
            "sessionStatus": "ACTIVE"
        }
    ]
}

DeleteSession API で直近の会話履歴を削除してみます。どうやら、 ACTIVE な Session は削除できないため、状態の変更を行う必要があるようです。

[cloudshell-user@ip-10-130-51-220 ~]$ aws bedrock-agent-runtime delete-session --session-identifier "22067db8-6cba-4195-b8af-46feba523933"

An error occurred (ConflictException) when calling the DeleteSession operation: Cannot delete session in ACTIVE state

EndSession API でセッションを終了させます。

状態が ENDED になりました。なお、ENDED なセッションは ListSessions では表示されませんでした。

[cloudshell-user@ip-10-130-51-220 ~]$ aws bedrock-agent-runtime end-session --session-identifier "22067db8-6cba-4195-b8af-46feba523933"
{
    "sessionArn": "arn:aws:bedrock:ap-northeast-1:123456789012:session/22067db8-6cba-4195-b8af-46feba523933",
    "sessionId": "22067db8-6cba-4195-b8af-46feba523933",
    "sessionStatus": "ENDED"
}
[cloudshell-user@ip-10-130-51-220 ~]$ aws bedrock-agent-runtime list-sessions  
{
    "sessionSummaries": [
        {
            "createdAt": "2025-03-02T12:25:02.333661+00:00",
            "lastUpdatedAt": "2025-03-02T12:25:02.333661+00:00",
            "sessionArn": "arn:aws:bedrock:ap-northeast-1:123456789012:session/c65f9fde-e4db-47cd-845d-513066a6fbd0",
            "sessionId": "c65f9fde-e4db-47cd-845d-513066a6fbd0",
            "sessionStatus": "ACTIVE"
        },
        {
            "createdAt": "2025-03-02T12:23:18.644941+00:00",
            "lastUpdatedAt": "2025-03-02T12:23:18.644941+00:00",
            "sessionArn": "arn:aws:bedrock:ap-northeast-1:123456789012:session/5a8da9ad-8359-49b0-990b-6b21f48a2727",
            "sessionId": "5a8da9ad-8359-49b0-990b-6b21f48a2727",
            "sessionStatus": "ACTIVE"
        },
        {
            "createdAt": "2025-03-02T10:30:26.818296+00:00",
            "lastUpdatedAt": "2025-03-02T10:30:26.818296+00:00",
            "sessionArn": "arn:aws:bedrock:ap-northeast-1:123456789012:session/1685f15e-d65b-42b9-a9c0-2a8848ff70c8",
            "sessionId": "1685f15e-d65b-42b9-a9c0-2a8848ff70c8",
            "sessionStatus": "ACTIVE"
        }
    ]
}

DeleteSession を再度行うと、セッションの削除が行えました。(戻り値が返ってきていないですがおそらく削除できたのでしょう。)

[cloudshell-user@ip-10-130-51-220 ~]$ aws bedrock-agent-runtime delete-session --session-identifier "22067db8-6cba-4195-b8af-46feba523933"
[cloudshell-user@ip-10-130-51-220 ~]$ 

まとめ

以上、「Amazon Bedrock で新たに Session Management API がリリースされました。」でした。

OSS で実装する部分だと、保持期間周りの処理をどうするかがネックになりそうな印象でしたが、自動で削除できたりして良さげな感触を持ちました。

制約事項をクリアできれば立派な選択肢の1つになる気がしています。(一般提供時のコストも気になりますね。)

このブログがどなたかの参考になれば幸いです。クラウド事業本部コンサルティング部のたかくに(@takakuni_)でした!

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.