AWS Bedrock - ThrottlingException: too many tokens per day, please wait before trying again
Naprawa ThrottlingException 'too many tokens per day' w AWS Bedrock: analiza limitów dziennych, retry z exponential backoff, zwiększenie limitów, fallback na alternatywne modele.
ThrottlingException: Too many tokens per day, please wait before trying again. Problem pojawia się po osiągnięciu dziennego limitu tokenów dla danego modelu (np. Claude, Llama). Wszystkie kolejne żądania są odrzucane do resetu limitu o północy UTC. Aplikacja staje się niefunkcjonalna na resztę dnia.
Ten runbook opisuje diagnozę i obejście dziennego limitu tokenów w AWS Bedrock. Architekturę wnioskowania na AWS i porównanie kosztów opisujemy w artykule LLM Inference na AWS - Bedrock vs SageMaker vs Self-Hosted na EKS. Budowę pipeline’u RAG z Knowledge Base omawiamy w Architektura RAG na AWS. Jeśli potrzebujesz pomocy z optymalizacją kosztów i limitów Bedrock - umów konsultacje.
Objaw
Bedrock API zwraca błąd 429 z komunikatem wskazującym na dzienny limit tokenów:
# Typowy komunikat błędu w logach aplikacji:
# botocore.exceptions.ClientError: An error occurred (ThrottlingException) when calling
# the InvokeModel operation: Too many tokens per day, please wait before trying again.
# Warianty komunikatu:
# "Too many tokens per day, please wait before trying again."
# "You have exceeded your token quota for the day."
# "Rate exceeded: tokens per day limit reached for model anthropic.claude-3-5-sonnet-20241022-v2:0"
# Sprawdź CloudWatch metryki Bedrock - zużycie tokenów
aws cloudwatch get-metric-statistics \
--namespace AWS/Bedrock \
--metric-name InputTokenCount \
--dimensions Name=ModelId,Value=anthropic.claude-3-5-sonnet-20241022-v2:0 \
--start-time $(date -u -d '24 hours ago' +%Y-%m-%dT%H:%M:%S) \
--end-time $(date -u +%Y-%m-%dT%H:%M:%S) \
--period 3600 \
--statistics Sum \
--query 'Datapoints[*].{time:Timestamp,tokens:Sum}' | jq 'sort_by(.time)'
# Sprawdź OutputTokenCount analogicznie
aws cloudwatch get-metric-statistics \
--namespace AWS/Bedrock \
--metric-name OutputTokenCount \
--dimensions Name=ModelId,Value=anthropic.claude-3-5-sonnet-20241022-v2:0 \
--start-time $(date -u -d '24 hours ago' +%Y-%m-%dT%H:%M:%S) \
--end-time $(date -u +%Y-%m-%dT%H:%M:%S) \
--period 3600 \
--statistics Sum \
--query 'Datapoints[*].{time:Timestamp,tokens:Sum}' | jq 'sort_by(.time)'
# Sprawdź metrykę Invocation4xxErrors (liczy throttled requests)
aws cloudwatch get-metric-statistics \
--namespace AWS/Bedrock \
--metric-name Invocation4xxErrors \
--dimensions Name=ModelId,Value=anthropic.claude-3-5-sonnet-20241022-v2:0 \
--start-time $(date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%S) \
--end-time $(date -u +%Y-%m-%dT%H:%M:%S) \
--period 300 \
--statistics Sum
# Sprawdź aktualny service quota dla modelu
aws service-quotas get-service-quota \
--service-code bedrock \
--quota-code L-XXXXXXXX \
--query '{quotaName:QuotaName, value:Value, unit:Unit}'
Kluczowe rozróżnienie - Bedrock ma trzy rodzaje limitów:
| Typ limitu | Komunikat | Reset | Rozwiązanie |
|---|---|---|---|
| Tokens per minute (TPM) | “Too many requests” / rate limit | Po 60s | Retry z backoff |
| Requests per minute (RPM) | “Too many requests” | Po 60s | Retry z backoff |
| Tokens per day (TPD) | “Too many tokens per day” | Północ UTC | Ten runbook |
Przyczyna
AWS Bedrock nakłada trzy warstwy limitów na każdy model, per region, per konto:
1. Domyślny limit dzienny jest niski dla nowych kont:
Nowo aktywowane modele mają niskie domyślne limity dzienne. Np. Claude 3.5 Sonnet na nowym koncie może mieć limit 1M tokenów/dzień. Przy średnim zapytaniu RAG (~4K input + 1K output = 5K tokenów) to zaledwie 200 zapytań dziennie.
2. Input tokens + output tokens sumują się do limitu:
Limit „tokens per day” to suma input tokens (prompt) + output tokens (completion). Długie system prompts (typowe w RAG: 2-8K tokenów) zjadają limit szybciej niż się wydaje.
3. Brak widocznego ostrzeżenia przed osiągnięciem limitu:
Bedrock nie zwraca nagłówka z informacją o pozostałym limicie (w przeciwieństwie do OpenAI API). Jedynym sposobem monitorowania jest CloudWatch metrics. Bez alertu aplikacja nagle przestaje działać.
4. Reset o północy UTC - nie o lokalnej północy:
Limit resetuje się o 00:00 UTC. Dla zespołu w CET (UTC+1/+2) oznacza to reset o 01:00-02:00 w nocy. Aplikacja osiągająca limit o 16:00 czasu lokalnego jest niefunkcjonalna przez 9-10 godzin.
5. Provisioned Throughput (PT) nie pomaga z limitem dziennym:
PT daje gwarantowany throughput per minute, ale nie zwiększa limitu dziennego na on-demand. To osobna usługa z osobnym modelem rozliczania.
Rozwiązanie
A) Natychmiastowe: fallback na alternatywny model:
Gdy główny model osiągnął limit dzienny, przełącz ruch na model o niewykorzystanym limicie:
# fallback_client.py - multi-model fallback z automatycznym przełączaniem
import boto3
import json
from botocore.exceptions import ClientError
import time
class BedrockFallbackClient:
"""Klient Bedrock z automatycznym fallback na alternatywne modele."""
def __init__(self, region="eu-west-1"):
self.client = boto3.client("bedrock-runtime", region_name=region)
# Kolejność fallback: primary → secondary → tertiary
self.models = [
"anthropic.claude-3-5-sonnet-20241022-v2:0", # primary
"anthropic.claude-3-5-haiku-20241022-v1:0", # tańszy, szybszy
"anthropic.claude-3-haiku-20240307-v1:0", # starszy, osobny limit
"amazon.nova-pro-v1:0", # Amazon model, osobny limit
]
self.throttled_models = {} # model_id → timestamp when throttled
def invoke(self, messages: list, system: str = "", max_tokens: int = 1024):
"""Wywołaj model z automatycznym fallback przy throttlingu."""
for model_id in self.models:
# Pomiń modele throttled w ostatnich 5 minutach
if model_id in self.throttled_models:
if time.time() - self.throttled_models[model_id] < 300:
continue
else:
del self.throttled_models[model_id]
try:
response = self.client.converse(
modelId=model_id,
messages=messages,
system=[{"text": system}] if system else [],
inferenceConfig={"maxTokens": max_tokens}
)
return {
"content": response["output"]["message"]["content"][0]["text"],
"model_used": model_id,
"input_tokens": response["usage"]["inputTokens"],
"output_tokens": response["usage"]["outputTokens"],
}
except ClientError as e:
error_code = e.response["Error"]["Code"]
error_msg = e.response["Error"]["Message"]
if error_code == "ThrottlingException":
if "tokens per day" in error_msg.lower():
# Dzienny limit - oznacz model jako niedostępny
self.throttled_models[model_id] = time.time()
print(f"⚠️ Model {model_id} osiągnął dzienny limit. Fallback...")
continue
elif "too many requests" in error_msg.lower():
# Per-minute limit - retry z backoff
time.sleep(2)
continue
raise # Inny błąd - propaguj
raise RuntimeError("Wszystkie modele osiągnęły limit dzienny. Brak dostępnych fallback.")
# Użycie:
client = BedrockFallbackClient()
result = client.invoke(
messages=[{"role": "user", "content": [{"text": "Wyjaśnij architekturę RAG."}]}],
system="Jesteś ekspertem od architektury chmurowej."
)
print(f"Model: {result['model_used']}, Tokeny: {result['input_tokens']}+{result['output_tokens']}")
B) Monitoring i alerty - reaguj ZANIM osiągniesz limit:
# cloudwatch-alarm.yaml (CloudFormation)
Resources:
BedrockDailyTokenAlarm:
Type: AWS::CloudWatch::Alarm
Properties:
AlarmName: bedrock-daily-token-usage-80pct
AlarmDescription: "Bedrock token usage at 80% of daily limit"
Namespace: AWS/Bedrock
MetricName: InputTokenCount
Dimensions:
- Name: ModelId
Value: anthropic.claude-3-5-sonnet-20241022-v2:0
Statistic: Sum
Period: 86400 # 24h
EvaluationPeriods: 1
# Ustaw threshold na 80% Twojego dziennego limitu
# Np. limit = 2M tokenów → threshold = 1,600,000
Threshold: 1600000
ComparisonOperator: GreaterThanThreshold
TreatMissingData: notBreaching
AlarmActions:
- !Ref SNSAlertTopic
# token_budget_tracker.py - śledzenie budżetu dziennego w aplikacji
import boto3
from datetime import datetime, timezone
import os
class TokenBudgetTracker:
"""Śledzi zużycie tokenów i ostrzega przed osiągnięciem limitu."""
def __init__(self, daily_limit: int = 2_000_000):
self.daily_limit = daily_limit
self.cloudwatch = boto3.client("cloudwatch")
self._cache_timestamp = None
self._cached_usage = 0
def get_daily_usage(self, model_id: str) -> int:
"""Pobierz bieżące dzienne zużycie tokenów z CloudWatch."""
now = datetime.now(timezone.utc)
start_of_day = now.replace(hour=0, minute=0, second=0, microsecond=0)
# Cache na 5 minut
if self._cache_timestamp and (now - self._cache_timestamp).seconds < 300:
return self._cached_usage
input_resp = self.cloudwatch.get_metric_statistics(
Namespace="AWS/Bedrock",
MetricName="InputTokenCount",
Dimensions=[{"Name": "ModelId", "Value": model_id}],
StartTime=start_of_day,
EndTime=now,
Period=86400,
Statistics=["Sum"],
)
output_resp = self.cloudwatch.get_metric_statistics(
Namespace="AWS/Bedrock",
MetricName="OutputTokenCount",
Dimensions=[{"Name": "ModelId", "Value": model_id}],
StartTime=start_of_day,
EndTime=now,
Period=86400,
Statistics=["Sum"],
)
input_tokens = input_resp["Datapoints"][0]["Sum"] if input_resp["Datapoints"] else 0
output_tokens = output_resp["Datapoints"][0]["Sum"] if output_resp["Datapoints"] else 0
self._cached_usage = int(input_tokens + output_tokens)
self._cache_timestamp = now
return self._cached_usage
def remaining_budget(self, model_id: str) -> dict:
"""Sprawdź ile tokenów zostało do limitu."""
used = self.get_daily_usage(model_id)
remaining = max(0, self.daily_limit - used)
pct_used = (used / self.daily_limit) * 100
return {
"used": used,
"remaining": remaining,
"limit": self.daily_limit,
"pct_used": round(pct_used, 1),
"should_fallback": pct_used > 80,
}
C) Zwiększenie limitu - Service Quotas request:
# 1. Znajdź quota code dla Twojego modelu
aws service-quotas list-service-quotas \
--service-code bedrock \
--query 'Quotas[?contains(QuotaName, `token`) && contains(QuotaName, `day`)].{name:QuotaName, code:QuotaCode, value:Value}' \
-o table
# 2. Sprawdź aktualny limit
aws service-quotas get-service-quota \
--service-code bedrock \
--quota-code L-XXXXXXXX
# 3. Złóż wniosek o zwiększenie (np. 2M → 10M tokenów/dzień)
aws service-quotas request-service-quota-increase \
--service-code bedrock \
--quota-code L-XXXXXXXX \
--desired-value 10000000
# 4. Sprawdź status wniosku
aws service-quotas list-requested-service-quota-change-history-by-quota \
--service-code bedrock \
--quota-code L-XXXXXXXX \
--query 'RequestedQuotas[0].{status:Status, requested:DesiredValue, created:Created}'
D) Optymalizacja zużycia tokenów:
# Redukcja input tokens - kluczowe techniki
# 1. Skróć system prompt (największy wpływ)
# PRZED: 4000 tokenów system prompt × 1000 zapytań = 4M tokenów/dzień
# PO: 800 tokenów (zwięzły) × 1000 zapytań = 800K tokenów/dzień
# 2. Prompt caching (Bedrock wspiera dla Claude 3.5+)
# System prompt wysyłany raz, cache'owany przez 5 minut
response = client.converse(
modelId="anthropic.claude-3-5-sonnet-20241022-v2:0",
messages=messages,
system=[{
"text": long_system_prompt,
"cacheControl": {"type": "ephemeral"} # Cache na 5 min
}],
)
# Cache hit = 90% redukcja kosztów i zużycia tokenów dla system prompt
# 3. Ogranicz max_tokens w odpowiedzi
# Nie ustawiaj max_tokens=4096 jeśli potrzebujesz 200-tokenowej odpowiedzi
response = client.converse(
modelId="anthropic.claude-3-5-sonnet-20241022-v2:0",
messages=messages,
inferenceConfig={"maxTokens": 512} # Nie 4096
)
# 4. Filtruj kontekst RAG - wysyłaj tylko top 3 chunks, nie top 10
# PRZED: 10 chunks × 500 tokenów = 5000 tokenów kontekstu
# PO: 3 chunks × 500 tokenów = 1500 tokenów kontekstu
E) Multi-region routing - podwój limit:
# Bedrock limity są per-region. Użyj drugiego regionu jako overflow.
import boto3
import random
class MultiRegionBedrockClient:
"""Rozkłada ruch między regionami aby podwoić efektywny limit."""
def __init__(self, model_id: str):
self.model_id = model_id
self.regions = ["eu-west-1", "us-east-1"] # Uwaga na data residency!
self.clients = {
region: boto3.client("bedrock-runtime", region_name=region)
for region in self.regions
}
self.region_index = 0
def invoke(self, messages, system="", max_tokens=1024):
"""Round-robin między regionami."""
for attempt in range(len(self.regions)):
region = self.regions[self.region_index % len(self.regions)]
self.region_index += 1
try:
response = self.clients[region].converse(
modelId=self.model_id,
messages=messages,
system=[{"text": system}] if system else [],
inferenceConfig={"maxTokens": max_tokens}
)
return response
except self.clients[region].exceptions.ThrottlingException as e:
if "tokens per day" in str(e):
continue # Spróbuj następny region
raise
raise RuntimeError("Limit dzienny osiągnięty we wszystkich regionach")
Walidacja
# 1. Sprawdź bieżące zużycie po wdrożeniu optymalizacji
aws cloudwatch get-metric-statistics \
--namespace AWS/Bedrock \
--metric-name InputTokenCount \
--dimensions Name=ModelId,Value=anthropic.claude-3-5-sonnet-20241022-v2:0 \
--start-time $(date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%S) \
--end-time $(date -u +%Y-%m-%dT%H:%M:%S) \
--period 300 \
--statistics Sum
# Expected: wartości niższe niż przed optymalizacją (po punkcie D)
# 2. Zweryfikuj że fallback działa (test z niepoprawnym primary model)
python -c "
from fallback_client import BedrockFallbackClient
client = BedrockFallbackClient()
# Symuluj throttling primary modelu
client.throttled_models['anthropic.claude-3-5-sonnet-20241022-v2:0'] = __import__('time').time()
result = client.invoke(messages=[{'role':'user','content':[{'text':'test'}]}])
print(f'Fallback model: {result[\"model_used\"]}')
assert 'haiku' in result['model_used'] or 'nova' in result['model_used']
"
# 3. Sprawdź status quota increase request
aws service-quotas list-requested-service-quota-change-history-by-quota \
--service-code bedrock \
--quota-code L-XXXXXXXX \
--query 'RequestedQuotas[0].Status'
# Expected: "APPROVED" (może zająć 1-3 dni robocze)
# 4. Zweryfikuj alarm CloudWatch
aws cloudwatch describe-alarms \
--alarm-names bedrock-daily-token-usage-80pct \
--query 'MetricAlarms[0].{state:StateValue, threshold:Threshold}'
# Expected: StateValue = "OK" (jeśli nie jesteś blisko limitu)
# 5. Po resecie o północy UTC - zweryfikuj normalną pracę
aws cloudwatch get-metric-statistics \
--namespace AWS/Bedrock \
--metric-name Invocation4xxErrors \
--dimensions Name=ModelId,Value=anthropic.claude-3-5-sonnet-20241022-v2:0 \
--start-time $(date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%S) \
--end-time $(date -u +%Y-%m-%dT%H:%M:%S) \
--period 300 \
--statistics Sum
# Expected: 0 (brak błędów 4xx po resecie limitu)
Bedrock throttluje Twoją aplikację AI?
Umów bezpłatną 30-minutową rozmowę. Zaprojektujemy architekturę z fallback multi-model, prompt caching i monitoringiem budżetu tokenów, aby Twoja aplikacja nigdy nie stanęła z powodu limitów.