Vocabulary for this course

The terms the chapters assume, in plain words. You can open this from any chapter.

backoff
Waiting longer between each retry, so a service that is struggling gets room to recover.
context window
The most text, counted in tokens, that a model can consider in a single request.
cost
The money a request is billed for, usually based on how many input and output tokens it uses.
idempotency
The property that repeating the same request has the same effect as sending it once.
jitter
A small random amount added to a wait, so many clients do not retry at the same moment.
latency
The time from sending a request to receiving the full answer, including time spent waiting in a queue.
prompt caching
Reusing the stored copy a service keeps of unchanged leading text so later requests cost less and start faster.
rate limit
A cap on how many requests you may send in a period, after which the service refuses more.
schema
A written description of the fields a response must contain and the type of each value.
streaming
Receiving an answer in pieces as it is produced instead of waiting for the complete response.
structured output
A response constrained to a schema, so every field and type can be checked and used by code.
timeout
A limit on how long a request may wait before your code gives up and treats it as failed.
token
A small unit of text, such as part of a word, a whole word or punctuation, that a model reads and bills for.
tool call
A request from a model for your code to run a named function with arguments, with the result sent back.

LLM APIs from the ground up

Retries, timeouts and idempotency

A network call can fail in three ways: it can be slow, it can fail before the server acts, or it can fail after. Timeouts bound the first, retries help the second, and idempotency keys make the third safe.

Part 4 of 10

Beginner15 min readBack to Courses

Three timeouts, not one

A connect timeout bounds how long you wait to reach the server. A read timeout bounds how long you wait for the next byte. A total deadline bounds the whole operation, including retries. A single timeout number conflates them and usually ends up too generous for connect and too tight for generation. Separate values are not over-engineering; they are the difference between a slow network and a slow model.

Set the total deadline from the caller’s budget and derive the rest. A chat request that must answer in ten seconds cannot spend eight of them on a connect attempt, and a batch job with an hour can afford a much longer read. The deadline should be visible in the logs, because “it timed out” is not an explanation until you know which clock ran out.

The three clocks also fail differently. A connect timeout means the request never reached the model; a read timeout means it may have. That difference decides whether a retry is safe, and it is invisible if you collapse every timeout into one number. Log which clock expired, not just that one did.

Retry only what is retryable

A 429, a 500, a 502, a 503, a 504 and a timeout are worth another attempt. A 400, a 401, a 403, a 404 and a 422 are not: the request is wrong, and sending it again produces the same wrong request. Retrying a 401 turns a configuration error into a lockout. The status code is a contract, and treating every failure as transient is how a small bug becomes an incident.

The distinction lives in the status code and, for streaming, in whether the stream started. A failure before the first token is a normal retry; a failure after it is a partial result, which is a different problem. A client that retries a started stream duplicates text the user has already read, which is worse than an error.

One deadline, split by phase

import httpx

timeout = httpx.Timeout(connect=3.0, read=60.0, write=10.0, pool=3.0)

with httpx.Client(timeout=timeout) as client:
    response = client.post(
        url,
        json=body,
        headers={"Idempotency-Key": key},
    )
    response.raise_for_status()

Classify before you retry

Run the four scripts, then add 401 and 409 and decide where they belong. A 409 may mean the request already succeeded, which is not the same as a permanent failure.

Output
Click "Run" to execute your code

Idempotency makes a retry safe

A retry is only safe if repeating the request has the same effect as sending it once. A completion has no side effect, so repeating it is harmless apart from the cost, but a tool call that charges a card is not. If the first attempt succeeded and the response was lost, the retry charges again. The window is small, and it is exactly the window that produces duplicate charges.

An idempotency key is a client-generated id that the server stores with the result. Send the same key on the retry and the server returns the original result instead of performing the action twice. The key must be generated before the first attempt and reused, not regenerated per try. It should also be stored with the request so a process restart does not lose it.

A retry without an idempotency key is a duplicate waiting to happen. The window is small and real: the server finishes, the connection drops before the response arrives, and your retry does the work again.

Deadlines propagate

If a request has two seconds left, every downstream call should know it. A retry loop that starts a fresh sixty-second timeout inside a two-second budget has turned a deadline into a suggestion. The remaining time is a parameter, not a global; passing it explicitly is how the whole call graph agrees on when to stop.

Pass the remaining time down, and let the inner call fail fast when there is not enough of it. The worst outcome is not an error; it is a request that succeeds after the caller has given up, having spent the money and written the row. A late success is a side effect nobody is waiting for, and it is often the one that is hardest to undo.

Deadline propagation is easiest when the deadline is an absolute timestamp rather than a duration. A duration passed down the call graph starts counting again at every hop, which is how a two-second budget becomes a thirty-second request. An absolute time is comparable everywhere, and the remaining budget is one subtraction.

A key makes the retry free

Run the four charges. The second is a retry with no key and it charges twice; the fourth is a retry with the same key and it does not. Then remove the key from the last call and compare the total.

Output
Click "Run" to execute your code

Check what you learned

One question at a time. A wrong answer explains itself — take another go.

Part 1 of 5

Which status is worth retrying?