[アップデート] Amazon Bedrock で新たに Session Management API がリリースされました
こんにちは!クラウド事業本部コンサルティング部のたかくに(@takakuni_)です。
Amazon Bedrock で新たに Session Management API がリリースされました。
Session Management API
Session Management API は LangGraph や LlamaIndex などの OSS フレームワークで作成した LLM アプリケーションのコンテキスト情報を保管するための API です。
従来、AWS 上で会話履歴などのコンテキスト情報を保管する場合、DynamoDB や RDBMS などを利用されていた方も多いのではないでしょうか。
今回の Session Management API は、上記の OSS ツールのコンテキスト情報を保管するために最適化された API になります。
次のリージョンでプレビューサポートです。
- オハイオ
- バージニア北部
- オレゴン
- ムンバイ
- ソウル
- シンガポール
- シドニー
- 東京
- カナダ
- フランクフルト
- アイルランド
- ロンドン
- パリ
- チューリッヒ
- サンパウロ
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日後に自動的に削除
- 明示的に削除も可能
制約付きですが、画像ファイルも埋め込み可能です。
- ステップ内に最大 20 枚まで含めることが可能
- サイズは 3.75 MB まで
- 高さ、幅は、それぞれ、8000 ピクセル以下であること
- 形式は以下をサポート
- PNG
- JPEG
- GIF
- WEBP
やってみた
それでは Session Management API を利用して、コンテキストを保持しつつ LLM アプリケーションを動作させてみます。
ドキュメントにサンプルコードがあったため、こちらに乗っかってみます。
リージョンの変更など、最終的に利用したコードは以下になります。
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_)でした!