
AgentCore Gatewayに追加されたレート制限機能を試してみた
はじめに
こんにちは、チーズナンが大好きなコンサル部の神野です。
2026年8月6日に、Amazon Bedrock AgentCore GatewayへRate Limiting(レート制限)といった新機能が追加されました!
なんだかGatewayの名にふさわしい機能が追加されましたね!早速試してみます!
前提
検証に使用した環境は下記の通りです。
| 項目 | 内容 |
|---|---|
| リージョン | ap-northeast-1(東京) |
| boto3 / botocore | 1.43.67 |
| Python / パッケージ管理 | 3.12 / uv |
| Gateway | AgentCore Gateway(MCP、AWS_IAM認証) |
| ターゲット | Inference Target(Bedrock Mantleコネクタ) |
まずレート制限がどんな機能か簡単に紹介します。
レート制限
Gatewayを通るトラフィックを「ディメンション」といった単位でグルーピングし、そのグループごとに許可レートを設定できる機能です。
ざっと下記のような特徴があります。
- ディメンションキーで分け方を決める。
- ターゲット名(targetName)、モデルID(qualifiedModelId)のほか、$.context.jwt.sub のようにJWTクレームを指定して呼び出し元ユーザー単位でも分割可能
- エントリで各グループのレートを決める。
- リクエスト数のほか、推論ターゲット向けのトークン数、同時接続数も制限できる
- レートに0を設定すれば特定の呼び出し元だけブロックすることも可能
- 複数のレート制限を設定した場合はAND評価で、すべてを通過したリクエストだけが実行される
ユースケースとしては、バックエンドの保護、ユーザー単位のクォータ、推論コストの上限(TPM制御)あたりが想定されているみたいです。確かにLLMに接続する推論ターゲットならかなり使い所出てきそうですね!
公式ドキュメントはこちらです。
どんなものか概要がわかったところで試してみます!
検証用Gatewayの準備
検証には、Bedrock Mantleコネクタの推論ターゲット(Inference Target)を1つ設定したGatewayを作成して試してみます。GatewayをLLM APIの入り口として使える機能で、詳しくは下記の記事で紹介しています。
IAMロールの作成
まずGatewayのサービスロールを作成します。信頼ポリシーで bedrock-agentcore.amazonaws.com を許可し、権限ポリシーにはBedrockの呼び出しと、 Mantle の権限を付けます。
# 信頼ポリシー
aws iam create-role \
--role-name rate-limit-demo-gateway-role \
--assume-role-policy-document '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {"Service": "bedrock-agentcore.amazonaws.com"},
"Action": "sts:AssumeRole",
"Condition": {"StringEquals": {"aws:SourceAccount": "<ACCOUNT_ID>"}}
}]
}'
# 権限ポリシー
aws iam put-role-policy \
--role-name rate-limit-demo-gateway-role \
--policy-name inference-permissions \
--policy-document '{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"bedrock:InvokeModel",
"bedrock:InvokeModelWithResponseStream"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": "bedrock-mantle:*",
"Resource": "arn:aws:bedrock-mantle:ap-northeast-1:<ACCOUNT_ID>:project/*"
}
]
}'
GatewayとInference Targetの作成
続いてGatewayと推論ターゲットを作成します。
import boto3
client = boto3.client("bedrock-agentcore-control", region_name="ap-northeast-1")
# Gateway作成
gateway = client.create_gateway(
name="rate-limit-demo-gateway",
roleArn="arn:aws:iam::<ACCOUNT_ID>:role/rate-limit-demo-gateway-role",
protocolType="MCP",
authorizerType="AWS_IAM",
)
print(gateway["gatewayId"], gateway["gatewayUrl"])
# Bedrock Mantleコネクタの推論ターゲット登録
client.create_gateway_target(
gatewayIdentifier=gateway["gatewayId"],
name="bedrock-mantle",
credentialProviderConfigurations=[
{"credentialProviderType": "GATEWAY_IAM_ROLE"}
],
targetConfiguration={
"inference": {"connector": {"source": {"connectorId": "bedrock-mantle"}}}
},
)
boto3はuvで最新版を指定して実行します。
uv run --with 'boto3>=1.43.67' setup.py
TPM制限を設定してみる
モデル単位のディメンション(qualifiedModelId)で、トークン消費量を300トークン/分に制限してみます。
import boto3
client = boto3.client("bedrock-agentcore-control", region_name="ap-northeast-1")
response = client.create_gateway_rate_limit(
gatewayIdentifier="rate-limit-demo-gateway-xxxx",
dimensionKeys=["qualifiedModelId"],
description="Per-model TPM limit for demo",
entries=[
{
"dimensions": {"qualifiedModelId": "*"},
"tokens": [{"rate": 300, "period": "minute"}],
}
],
)
print(response["rateLimitId"])
uv run --with 'boto3>=1.43.67' create_rate_limit.py
今回はトークン数を指定しています。
ワイルドカード(*)にしているので全モデル共通の制限になりますが、モデルIDを名指しすれば「Opusは絞ってHaikuは緩める」といった使い分けも可能です。
反映には少しだけ時間かかるので数分待ってから試してみましょう。
ステータスコード429を発生させてみる
制限が効いている状態で、Gatewayの /inference/v1/messages 経由でClaude Haiku 4.5を呼び出します。1回でおよそ210トークン(入力21 + 出力191)消費するリクエストを連打してみると・・・
--- call 1: HTTP 200 (成功)
--- call 2: HTTP 200 (成功)
--- call 3: HTTP 200 (成功)
--- call 4: HTTP 429
{"type":"error","error":{"type":"rate_limit_error","message":"Token rate limit exceeded",
"limitKey":"0bqftbvw7w","metric":"tokens","retryAfter":0.2}}
HTTP 429で止まりましたね!
超過したメトリクスがtokensであること、どのレート制限に当たったか(limitKey)、待つべき秒数(retryAfter)が返却されました。エラーの形式が、呼び出したAnthropic Messages API標準の rate_limit_error になっているため、Anthropic SDKやOpenAI SDKの組み込みリトライがそのまま反応できる形ですね。
300トークン/分という設定は毎秒5トークンずつ補充されるトークンバケットとして動作しているようで、429の直後でも少し待つと次のリクエストが成功しました。
1分間の固定ウィンドウで厳密に300、という動きではないので、厳密なカウントではなくスロットリング考えてみると良いかもしれませんね。
requestsとtokensは併用できる
トークン数(tokens)だけでなくリクエスト数(requests)も、1つのエントリに同時に指定できます。
この場合はAND評価になり、どちらかの上限に達した時点で429になります。回数の上限をRPMで、コストの上限をTPMで止めたい場合はこのように表現できます。
entries=[
{
"dimensions": {"qualifiedModelId": "*"},
"requests": [{"rate": 2, "period": "minute"}],
"tokens": [{"rate": 300, "period": "minute"}],
}
]
こちらも試してみます。今度はトークンをほとんど消費しない小さなリクエスト(1回あたり約32トークン)を連打して、TPMに届く前にRPMへ到達させてみると・・・
--- call 6: HTTP 200 (成功)
--- call 7: HTTP 429
{"type":"error","error":{"type":"rate_limit_error","message":"Rate limit exceeded",
"limitKey":"0bqftbvw7w","metric":"requests","retryAfter":30.0}}
今度は metric が requests になりました!
トークン消費は合計200程度でTPM(300)には届いていないので、リクエスト数の制限が先に発動した形です。同じレート制限でも、どちらの上限に当たったかがエラーから判別できるようになっていますね。retryAfter: 30.0 も、2回/分 = 30秒に1回の補充という動きと整合しているように見えます。
モデルごとに上限を変えられる
エントリは複数書けるので、モデルごとに違う上限を設定できます。
試しにHaikuだけ rate 0(=完全ブロック)にして、他のモデルはワイルドカードで100回/分のままにしてみました。
entries=[
{
"dimensions": {"qualifiedModelId": "anthropic.claude-haiku-4-5"},
"requests": [{"rate": 0, "period": "minute"}],
},
{
"dimensions": {"qualifiedModelId": "*"},
"requests": [{"rate": 100, "period": "minute"}],
},
]
haiku : HTTP 429 {"metric":"requests","retryAfter":60.0}
deepseek : HTTP 200
deepseek : HTTP 200
deepseek : HTTP 200
Haikuは1回目からブロックされる一方、DeepSeekは同じタイミングで問題なくリクエストが成功しました!
高価なモデルだけ厳しく制限する、特定モデルを一時的に止めるといった使い分けができそうですね!
MCP Targetでも同様に制限できる
今回は推論ターゲットで試しましたが、Lambdaなどのツールを公開するMCPターゲットにも同じ仕組みで制限をかけられます。ディメンションに targetName を使い、requests でリクエスト数を制限する形です。
response = client.create_gateway_rate_limit(
gatewayIdentifier="my-gateway-xxxx",
dimensionKeys=["targetName"],
description="Per-target RPM limit",
entries=[
{
"dimensions": {"targetName": "SupportTarget"},
"requests": [{"rate": 3, "period": "minute"}],
},
{
"dimensions": {"targetName": "*"},
"requests": [{"rate": 100, "period": "minute"}],
},
],
)
推論ターゲットと違うのは、tokens が使えない(トークンという概念がないので)点と、ディメンションに targetName を使う点くらいです。ちなみに toolName を指定すればツール単位まで絞り込めます。
実際にLambdaのモックツールを3回/分に制限して連打したところ、同様にHTTP 429が返却されることを確認できました!
ツール実行のレート制限を外側から制御したい際に便利な印象です!
注意点
個人的に気になった注意点を箇条書きでまとめています。
- レート制限はデフォルトでフェイルオープンです。
- 制限するサービス側に問題がある場合やディメンションを解決できない場合、リクエストは成功します。セキュリティの境界としてではなく、流量制御として使うのが正しい位置づけみたいです。
- 瞬間的には設定レートを超えて通ることがあります。
- トークン制限は実行前に入力トークンを見積もり、応答が返ってきてから実際の消費量を記録する方式です。応答が返りきるまでの短い間は消費量が確定しないため、連打すると一時的に超過します。今回の検証で300トークン/分の設定なのに4回目(約840トークン相当)まで通ったのはこのためかと思われます。
- 上限を1トークンもオーバーさせたくない用途(課金の確定制御など)では、バックエンド側のチェックと併用してください
- リクエスト数でも同じことが起きます。しばらく呼び出していない状態から2回/分の制限に対して連打したところ、14回連続で通りました。低いレートを設定してもバケットに溜まった分は一気に消費できてしまうので、少なくとも数分は様子を見て判断するのがおすすめです
- トークン制限は実行前に入力トークンを見積もり、応答が返ってきてから実際の消費量を記録する方式です。応答が返りきるまでの短い間は消費量が確定しないため、連打すると一時的に超過します。今回の検証で300トークン/分の設定なのに4回目(約840トークン相当)まで通ったのはこのためかと思われます。
- Gatewayにはもともと、AWSが管理する上限(サービスクォータ)があります。
- レート制限はその内側をさらに絞るための機能なので、クォータより大きい値を設定しても効果はありません。実際に適用されるのは自分で設定した値とサービスクォータの小さい方です
- レート制限はAgentCore Policyのポリシー評価より先に実行されます。
- 制限に達したリクエストはポリシー評価に進む前に429で弾かれるため、Policyと併用する場合の切り分けの参考にしてください
フェイルオープンやトークン制限の消費量記録の仕組みはベストプラクティスのドキュメントに、サービスクォータの値はクォータのドキュメントに記載があります。
また、公式ブログではJWTクレームでユーザー層(Basic / Advanced / Beta)を分けて層ごとに異なる上限を設ける、といった一歩進んだ設計例が紹介されています。マルチテナントでの本格的な設計を考える際はこちらが参考になります。
おわりに
レート制限が実装されてゲートウェイの設定だけで実現できるようになりました!どんどんゲートウェイらしくなってきますね・・・!!!
今回試したモデル単位のTPM制限はLLMコストの上限設定として活用できる機能だと思いますし、JWTクレーム単位のディメンションを使えばマルチテナントSaaSでのユーザー単位クォータにも応用できます。
同時に発表されたTemporal Policiesも検証中なので、近日中に別記事で書きます。よりGatewayがエージェントの行動を統制する場所として厚みを増していますね!
本記事が少しでも参考になりましたら幸いです。
最後までご覧いただきありがとうございました!







