Understanding “no capacity” and rate limits¶
A 429 means the provider rejected the request because of rate or capacity
limits. It does not by itself mean your model or server address is wrong.
Azure can report no_capacity even when both addresses support the model.
lm15 keeps the provider's message and adds useful evidence when available:
from lm15 import RateLimitError
try:
response = router.complete(request)
except RateLimitError as error:
print(error) # message, request ID, wait advice, limits
print(error.retry_after) # seconds, or None if no usable advice
print(error.request_id) # reference for provider support
print(dict(error.rate_limit_headers))
# Your application decides whether and when to retry.
For the Azure failure that prompted this feature, the headers included:
x-ratelimit-limit-requests: 1
x-ratelimit-remaining-requests: -1
x-ratelimit-reset-requests: 105
retry-after: 39
These numbers disagree with a simple “wait exactly one minute” assumption. They are the provider's reports, not a quota calculation performed by lm15. In our test, longer gaps restored success on both Azure addresses.
What the fields mean¶
retry_after: usable provider advice in seconds. A valid error-body value wins, thenRetry-After, then numericalretry-after-msorx-ms-retry-after-ms. No valid advice meansNone, not zero.rate_limit_headers: a read-only mapping of lowercase header names to tuples of original values. Multiple values and negative balances stay visible. Reset values stay unconverted: providers use seconds, duration strings, or timestamps. Do not treat them all as a number of seconds.request_id: the provider's reference, including Azure'sapim-request-idfallback, useful when reporting an issue.
Wait advice is not a guarantee of success. Neither this feature nor the
error's retryable classification adds automatic retries, increases quota,
purchases capacity, or changes the endpoint. Use bounded retries in your
application when appropriate; model calls can have costs and side effects.
Streaming and saved errors¶
The same diagnostics work for normal calls, async calls, and auxiliary
operations such as file upload or model listing. A rejected stream carries
metadata on the exception. An error inside HTTP 200 carries handshake
evidence in event.error.http_response; saving/reloading that event preserves
it, and response materialization carries it onto the exception. HTTP 200 is
not assigned as the error's status. Handshake headers are not a live quota
feed and may be older than the stream failure.
Privacy and limits¶
Only a documented set of rate-limit headers is retained—not cookies,
credentials, arbitrary headers, or x-ratelimit-key. Values are bounded to
four per name and 256 printable ASCII characters each; malformed/oversized
values are omitted, not turned into plausible numbers. Long error displays
show a bounded preview while retained values remain available on the error.
Provider error content still belongs under your application's logging policy.
The cross-language rules and exact header list live in lm15-contract/docs/error-diagnostics.md.