Azure OpenAI Service - błąd 429 Rate Limit po przekroczeniu limitu tokenów lub zapytań
Rozwiązanie błędu 429 w Azure OpenAI: identyfikacja wąskiego gardła (TPM vs RPM), wdrożenie retry logic, zwiększenie quota lub przejście na PTU.
Ten runbook opisuje diagnozę i rozwiązanie problemu rate limitingu w Azure OpenAI Service. Szczegółową analizę kosztów i architektury modeli AI w chmurze znajdziesz w artykule Azure OpenAI vs modele self-hosted - koszty, latency i rezydencja danych w UE. Jeśli potrzebujesz pomocy z optymalizacją kosztów i wydajności AI workloadów - umów konsultacje.
Objaw
Aplikacja otrzymuje odpowiedź HTTP 429 z Azure OpenAI API. W logach widoczny jest komunikat RateLimitError lub 429 Too Many Requests:
# Sprawdź aktualne zużycie quota dla deploymentu
az cognitiveservices usage list \
--name my-openai-resource \
--resource-group rg-ai \
--query "[?name.value=='OpenAI.Standard.gpt-4o'].{model:name.value, current:currentValue, limit:limit}" -o table
# Sprawdź metryki rate limitingu z ostatnich 30 minut
az monitor metrics list \
--resource /subscriptions/<sub-id>/resourceGroups/rg-ai/providers/Microsoft.CognitiveServices/accounts/my-openai-resource \
--metric "AzureOpenAIRequests" \
--dimension StatusCode \
--interval PT1M \
--start-time $(date -u -d '-30 minutes' +%Y-%m-%dT%H:%M:%SZ) \
--query "value[0].timeseries[?metricValues[?statusCode=='429']]" -o json
# Lista deploymentów z ich limitami
az cognitiveservices account deployment list \
--name my-openai-resource \
--resource-group rg-ai \
--query "[].{model:properties.model.name, version:properties.model.version, capacity:sku.capacity, name:name}" -o table
W odpowiedzi HTTP z Azure OpenAI sprawdź nagłówki rate limit:
# Pojedyncze zapytanie testowe z wyświetleniem nagłówków
curl -s -D - https://my-openai-resource.openai.azure.com/openai/deployments/gpt-4o/chat/completions?api-version=2024-10-21 \
-H "Content-Type: application/json" \
-H "api-key: $AZURE_OPENAI_KEY" \
-d '{"messages":[{"role":"user","content":"test"}],"max_tokens":5}' \
2>&1 | grep -i "x-ratelimit\|retry-after"
# Spodziewane nagłówki:
# x-ratelimit-remaining-tokens: 0 <-- brak dostępnych tokenów
# x-ratelimit-remaining-requests: 0 <-- brak dostępnych zapytań
# retry-after: 12 <-- czas oczekiwania w sekundach
Jeśli x-ratelimit-remaining-tokens lub x-ratelimit-remaining-requests wynosi 0 - deployment wyczerpał swoją quota na bieżącą minutę.
Przyczyna
Azure OpenAI stosuje dwa niezależne limity, z których każdy może wyzwolić błąd 429:
-
TPM (Tokens Per Minute) - łączna liczba tokenów (prompt + completion) przetworzonych w ciągu minuty. Dla deploymentu GPT-4o domyślna quota w regionie Sweden Central to np. 80K TPM. Długie prompty (RAG z kontekstem dokumentów) szybko wyczerpują ten limit.
-
RPM (Requests Per Minute) - liczba wywołań API w ciągu minuty. Wartość RPM jest obliczana automatycznie jako 6× wartość TPM ÷ 1000. Dla 80K TPM = 480 RPM. Wiele krótkich zapytań (np. klasyfikacja zdań) może uderzyć w RPM zanim wyczerpie TPM.
Dodatkowe czynniki wpływające na problem:
-
Global Standard vs Data Zone deployments - typy deploymentu mają różne bazowe quota. Global Standard oferuje wyższe limity domyślne, ale Data Zone (z gwarancją regionu danych) ma niższe pule, szczególnie dla GPT-4o w regionach europejskich. Quota jest współdzielona między wszystkimi deploymentami tego samego modelu w ramach subskrypcji i regionu.
-
Skokowy wzrost ruchu (burst) - nawet jeśli średnie zużycie mieści się w limicie, nagły skok ruchu (np. cron job przetwarzający paczkę dokumentów) może wyczerpać quota w pierwszych sekundach minuty. Azure nie buforuje nadwyżki - od razu zwraca 429.
-
Token estimation mismatch - aplikacja nie uwzględnia tokenów z system promptu, function calling schema czy historii konwersacji. Rzeczywiste zużycie TPM jest 2-3× wyższe niż estymowane na podstawie samych wiadomości użytkownika.
Rozwiązanie
A) Retry z exponential backoff:
Najprostsze rozwiązanie na sporadyczne 429. Implementacja z biblioteką tenacity:
import openai
from tenacity import retry, wait_exponential, stop_after_attempt, retry_if_exception_type
from openai import AzureOpenAI, RateLimitError
client = AzureOpenAI(
azure_endpoint="https://my-openai-resource.openai.azure.com",
api_version="2024-10-21",
api_key=os.environ["AZURE_OPENAI_KEY"],
)
@retry(
retry=retry_if_exception_type(RateLimitError),
wait=wait_exponential(multiplier=1, min=2, max=60),
stop=stop_after_attempt(6),
before_sleep=lambda retry_state: print(
f"429 received, retry {retry_state.attempt_number}/6, "
f"waiting {retry_state.next_action.sleep}s..."
),
)
def call_openai(messages: list, model: str = "gpt-4o") -> str:
response = client.chat.completions.create(
model=model,
messages=messages,
max_tokens=1024,
)
return response.choices[0].message.content
# Alternatywnie - wykorzystaj nagłówek retry-after z odpowiedzi
@retry(
retry=retry_if_exception_type(RateLimitError),
wait=lambda retry_state: float(
getattr(retry_state.outcome.exception(), 'response', None)
and retry_state.outcome.exception().response.headers.get('retry-after', 5)
or 5
),
stop=stop_after_attempt(5),
)
def call_openai_with_retry_after(messages: list) -> str:
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
)
return response.choices[0].message.content
B) Zwiększenie quota przez Azure Portal / CLI:
# Sprawdź aktualne limity quota w regionie
az cognitiveservices model list \
--location swedencentral \
--query "[?model.name=='gpt-4o'].{model:model.name, version:model.version, maxCapacity:model.skus[0].capacity.maximum}" -o table
# Sprawdź aktualną capacity deploymentu
az cognitiveservices account deployment show \
--name my-openai-resource \
--resource-group rg-ai \
--deployment-name gpt-4o \
--query "{capacity:sku.capacity, model:properties.model.name}" -o json
# Zwiększ capacity deploymentu (np. z 80K do 150K TPM)
az cognitiveservices account deployment create \
--name my-openai-resource \
--resource-group rg-ai \
--deployment-name gpt-4o \
--model-name gpt-4o \
--model-version "2024-08-06" \
--model-format OpenAI \
--sku-capacity 150 \
--sku-name "GlobalStandard"
# Jeśli region nie ma wystarczającej capacity - sprawdź dostępność w innych regionach
az cognitiveservices model list \
--location eastus2 \
--query "[?model.name=='gpt-4o'].{version:model.version, available:model.skus[0].capacity.maximum}" -o table
Uwaga: Wartość
sku-capacitypodawana jest w tysiącach TPM. Wartość 150 oznacza 150 000 TPM.
C) Load balancing przez wiele deploymentów (LiteLLM):
Rozłóż ruch na kilka zasobów OpenAI w różnych regionach:
# litellm_config.yaml
model_list:
- model_name: gpt-4o
litellm_params:
model: azure/gpt-4o
api_base: https://openai-swedencentral.openai.azure.com/
api_key: os.environ/AZURE_OPENAI_KEY_SWEDEN
api_version: "2024-10-21"
model_info:
tpm: 80000
rpm: 480
- model_name: gpt-4o
litellm_params:
model: azure/gpt-4o
api_base: https://openai-eastus2.openai.azure.com/
api_key: os.environ/AZURE_OPENAI_KEY_EASTUS2
api_version: "2024-10-21"
model_info:
tpm: 80000
rpm: 480
- model_name: gpt-4o
litellm_params:
model: azure/gpt-4o
api_base: https://openai-francecentral.openai.azure.com/
api_key: os.environ/AZURE_OPENAI_KEY_FRANCE
api_version: "2024-10-21"
model_info:
tpm: 60000
rpm: 360
router_settings:
routing_strategy: "usage-based-routing-v2"
enable_pre_call_checks: true # sprawdza TPM/RPM przed wysłaniem
retry_after: 5
num_retries: 3
allowed_fails: 2
cooldown_time: 30 # sekundy cooldown po 429
# Uruchom LiteLLM proxy
litellm --config litellm_config.yaml --port 4000
# Test - zapytania automatycznie routowane do dostępnego deploymentu
curl http://localhost:4000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model":"gpt-4o","messages":[{"role":"user","content":"test"}]}'
D) Przejście na Provisioned Throughput Units (PTU):
Dla stabilnych, przewidywalnych workloadów produkcyjnych - PTU gwarantuje stałą przepustowość bez throttlingu:
# Sprawdź dostępność PTU w regionie
az cognitiveservices model list \
--location swedencentral \
--query "[?model.name=='gpt-4o'].model.skus[?name=='ProvisionedManaged'].{name:name, minCapacity:capacity.minimum, maxCapacity:capacity.maximum}" -o json
# Utwórz deployment typu Provisioned (minimalnie 50 PTU)
az cognitiveservices account deployment create \
--name my-openai-resource \
--resource-group rg-ai \
--deployment-name gpt-4o-ptu \
--model-name gpt-4o \
--model-version "2024-08-06" \
--model-format OpenAI \
--sku-capacity 50 \
--sku-name "ProvisionedManaged"
# Monitoruj utilization PTU (powinno być <80% dla rezerwy)
az monitor metrics list \
--resource /subscriptions/<sub-id>/resourceGroups/rg-ai/providers/Microsoft.CognitiveServices/accounts/my-openai-resource \
--metric "ProvisionedManagedUtilizationV2" \
--dimension ModelDeploymentName \
--interval PT1M \
--start-time $(date -u -d '-60 minutes' +%Y-%m-%dT%H:%M:%SZ) \
--query "value[0].timeseries[].data[-5:]" -o table
Uwaga kosztowa: PTU to zobowiązanie rezerwacyjne (billing hourly/monthly). 50 PTU dla GPT-4o to ~$2/godz. Opłaca się, gdy zużycie pay-as-you-go przekracza ~$1.5/godz. na danym deploymencie. Szczegóły w kalkulatorze: Azure OpenAI Pricing.
Walidacja
# 1. Wyślij serię zapytań testowych i sprawdź brak 429
for i in $(seq 1 20); do
STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
https://my-openai-resource.openai.azure.com/openai/deployments/gpt-4o/chat/completions?api-version=2024-10-21 \
-H "Content-Type: application/json" \
-H "api-key: $AZURE_OPENAI_KEY" \
-d '{"messages":[{"role":"user","content":"Say OK"}],"max_tokens":5}')
echo "Request $i: HTTP $STATUS"
sleep 0.5
done
# Expected: wszystkie odpowiedzi HTTP 200
# 2. Sprawdź metryki - brak nowych 429 w ostatnich 5 minutach
az monitor metrics list \
--resource /subscriptions/<sub-id>/resourceGroups/rg-ai/providers/Microsoft.CognitiveServices/accounts/my-openai-resource \
--metric "AzureOpenAIRequests" \
--dimension StatusCode \
--interval PT1M \
--start-time $(date -u -d '-5 minutes' +%Y-%m-%dT%H:%M:%SZ) \
--query "value[0].timeseries[].data[].{time:timeStamp, total:total}" -o table
# Expected: brak wierszy z StatusCode=429
# 3. Skonfiguruj alert na przyszłość
az monitor metrics alert create \
--name "alert-openai-429-rate-limit" \
--resource-group rg-ai \
--scopes /subscriptions/<sub-id>/resourceGroups/rg-ai/providers/Microsoft.CognitiveServices/accounts/my-openai-resource \
--condition "total AzureOpenAIRequests where StatusCode includes 429 > 10" \
--window-size 5m \
--evaluation-frequency 1m \
--severity 2 \
--action-group /subscriptions/<sub-id>/resourceGroups/rg-monitoring/providers/Microsoft.Insights/actionGroups/ag-platform-team \
--description "Azure OpenAI rate limiting - >10 requests z 429 w 5 minut"
Po wdrożeniu rozwiązania zweryfikuj w Application Insights, że success rate wywołań OpenAI wrócił do >99%. Dashboard:
// Application Insights - KQL query do monitorowania 429
dependencies
| where timestamp > ago(1h)
| where target contains "openai.azure.com"
| summarize
total=count(),
failed_429=countif(resultCode == "429"),
success_rate=round(100.0 * countif(resultCode == "200") / count(), 2)
by bin(timestamp, 5m)
| order by timestamp desc
Azure OpenAI throttluje Twoje zapytania?
Umów bezpłatną 30-minutową rozmowę. Przeanalizujemy Twoje wzorce ruchu, dobierzemy optymalną strategię quota i wdrożymy architekturę odporną na rate limiting.