Model how long your worst-case LLM request really takes end to end, check it against every
timeout sitting between your code and the API, and find out whether streaming saves you or does nothing at all.
No network callsNo API keyNo backendPlanning estimate, not a guaranteeFacts confirmed 2026-08-07
This tool measures nothing. It never sends a request, to your provider or to anyone else.
Time-to-first-token and tokens-per-second are numbers you enter from your own observation.
Every prefilled figure is a placeholder. There are no benchmark tables here, no per-model throughput numbers
and no latency comparison between providers or models, because publishing throughput figures nobody measured
would be fabricated benchmarking.
01
The request model
All six figures below are yours. The tool does not infer time-to-first-token from your input
token count and does not know how fast any model is. Measure once against your own route, paste the numbers in,
then use the scenario multipliers to ask what the bad day looks like.
illustrative placeholder - replace with your own measurement
illustrative placeholder - replace with your own measurement
illustrative placeholder - these burn wall clock while emitting nothing visible
illustrative placeholder - used only by the SDK rescaling check below
illustrative placeholder - replace with your own measurement
illustrative placeholder - replace with your own measurement
illustrative placeholder - your own headroom for a busy provider
pick the one that matches how you measured, so nothing is counted twice
selects the three multipliers below; edit any of them to go custom
above 1 means slower
below 1 means slower, so worst case is below 1
above 1 means a longer queue
02
The timeout stack
Every layer between your process and the provider gets a row. The kind column is the
whole lesson. An idle timeout is reset by every byte received. A total timeout counts
wall clock from the start of the request and is reset by nothing. An execution ceiling meters
compute time and ignores the distinction entirely. Streaming defeats exactly one of those three.
2 is the documented default in every current Anthropic SDK (confirmed 2026-08-07)
this tool does not compute backoff - bring the worst-case wait from retry-backoff-calculator
see trap (c) below
Blank or zero renders UNKNOWN, never PASS.
Layer
Kind
Value
Unit
Retries stack
Verdict
Actions
Unit converter
The Anthropic TypeScript SDK takes milliseconds. Python and Ruby take seconds.
A value copied between them is off by a factor of a thousand.
Does streaming help?
Kind
Non-streaming
Streaming
Helps?
Idle
Dies at the limit - nothing arrives until the end
Timer resets on every byte received
YES
Total
Dies at the limit
Dies at the same limit
NO
Execution
Dies at the limit
Dies at the same limit
NO
"Just use streaming" is the wrong answer in two of the three rows.
Retry-multiplied budget
Timeouts are themselves retried, so the number that actually crosses an infrastructure ceiling is
the per-attempt duration times the number of attempts. Idle timeouts reset on each fresh attempt, so only layers
whose budget accumulates across the retry loop are drawn here.
03
The client-library trap panel
These four are properties of the libraries themselves rather than claims about any vendor's
infrastructure, which is why each one carries the package and version it was read in. Library behaviour changes:
re-check these against the version you actually have installed.
(a) The timeout you set is per chunk, not a deadline
observed in requests 2.34.2 and httpx 0.28.1, confirmed 2026-08-07
The timeouts people reach for in the two dominant Python HTTP libraries are read timeouts that reset every
time bytes arrive. The requests documentation defines the read timeout as "the number of seconds that the
client will wait between bytes sent from the server" and states plainly that "neither the connect nor
read timeouts are wall clock". The httpx documentation defines its read timeout as "the maximum duration to
wait for a chunk of data to be received" and offers four knobs - connect, read, write and pool.
The failure: a response that trickles, or a proxy that dribbles keep-alive bytes, blocks your
call indefinitely under a setting that reads like a hard deadline. Neither library documents a
total-wall-clock option at all, so there is nothing to switch on.
Fix: keep a monotonic clock at the loop level and bail out yourself, or wrap the
whole call in an async timeout.
import time
import httpx
DEADLINE_SECONDS = 600.0 # your wall-clock budget, enforced by you
def stream_with_deadline(url, payload):
started = time.monotonic()
# 30.0 here is a PER-CHUNK read timeout. It is not a deadline.
with httpx.stream("POST", url, json=payload,
timeout=httpx.Timeout(30.0)) as response:
for chunk in response.iter_bytes():
elapsed = time.monotonic() - started
if elapsed > DEADLINE_SECONDS:
raise TimeoutError(
"wall-clock budget exceeded after "
+ str(round(elapsed, 1)) + "s"
)
yield chunk
# async equivalent:
# await asyncio.wait_for(do_request(), timeout=DEADLINE_SECONDS)
Every one of those SDKs documents a 10 minute default, and each expresses it differently. (Go scopes its
10 minutes to non-streaming Messages requests and documents no default timeout at all for anything else.)
Python takes
a float in seconds (or an httpx.Timeout). Ruby takes seconds. TypeScript takes
milliseconds - its own example reads timeout: 20 * 1000 for twenty seconds. Go takes a
time.Duration through option.WithRequestTimeout, and that one is a
per-retry timeout rather than a whole-call one. Java takes a Duration, C# a
TimeSpan.
The failure: a value copied from a Python service into a TypeScript one is off by a factor of
a thousand in the direction that hurts. 600 meant as ten minutes becomes six hundred milliseconds.
Fix: the converter beside the stack table, and the warning this tool raises when a
millisecond field holds a number that looks like somebody's seconds value.
The effective deadline is often not the one you configured. The TypeScript SDK documents that for a large
max_tokens on a non-streaming request the default timeout is computed as
(60 * 60 * maxTokens) / 128000 seconds, floored at ten minutes and reaching up to sixty. The Java
SDK documents the mirror image: the same shape of formula applies to streaming requests, while its
non-streaming default scales from a thirty second minimum to a ten minute maximum.
Meanwhile the Python SDK refuses rather than hangs: it "will throw a ValueError if a non-streaming
request is expected to take longer than approximately 10 minutes", disabled by passing stream=True
or by overriding the timeout. The TypeScript, Go and Java SDKs document the same guard.
Fix: set the timeout explicitly so the default never applies, and tick the
rescaling option in section 02 to see what your configured value actually becomes.
All six SDKs document the same policy: connection errors, 408, 409, 429 and 5xx are retried automatically,
two times by default. The Python and TypeScript docs both add the sentence that catches people
out - "requests that time out are retried twice by default". The Ruby docs list timeouts explicitly among the
retried conditions.
The failure: your worst-case wall clock is not the timeout, it is the timeout times three.
That product is usually what crosses the serverless ceiling or the gateway limit, long after the per-attempt
number looked comfortable.
Fix: set the retry count deliberately, and budget the whole loop rather than one
attempt. Feed the worst-case backoff wait from
retry-backoff-calculator into the
backoff field in section 02.
04
Output
A planning estimate you can paste into a ticket. It is arithmetic over numbers you supplied, not a
guarantee about your production stack.
What to change, ordered by effect
Generated from your stack, not from a fixed list. Changes that move the layer which kills the
request first are listed before changes that gain more seconds elsewhere, because a change downstream of the
binding layer buys nothing until the binding layer is fixed. Within each group the list is ordered by seconds
of runway gained.
Markdown summary
REF
Dated vendor reference
This table is reference material, not part of the arithmetic above. Every one of
these values is configurable and varies by plan tier and by how somebody set it up years ago. The tool computes
only against the numbers in your stack table. Confirm each of these in your own configuration and correct the row
if it differs - a stale row here can never make the maths above wrong, because the maths above does not read it.
Not every timeout tells you which kind it is. The name "Proxy Read Timeout" reads like an idle
timer, but Cloudflare's own 524 page does not say whether the countdown restarts when the origin sends bytes.
What it does say is that if your requests exceed 125 seconds, "for example, streaming", you have to raise the
value, which is not how an idle timer that streaming defeats would behave. Treat it as a total limit until you
have confirmed otherwise on your own zone, and set the kind in your stack table accordingly. That caution
generalises: when a vendor names a timeout but does not document its reset behaviour, the safe assumption is the
one that does not depend on streaming saving you.
The buffering trap. nginx documents proxy_buffering as defaulting to on,
in which case it "receives a response from the proxied server as soon as possible, saving it into the buffers".
Only with buffering off is "the response passed to a client synchronously, immediately as it is received". If a
proxy in your path buffers, the client sees nothing until the response completes, and streaming defeats no idle
timeout anywhere downstream of it. Check that setting before trusting any streaming verdict on this page.
On heartbeats. Server-sent events allow comment frames - lines that begin with a colon and carry
no data - and a stream that emits them keeps an idle timer alive during a long silent thinking window. Whether
your provider actually emits them, and whether every proxy in the path forwards them instead of swallowing or
buffering them, is something you have to confirm for your own stack. This tool does not assume either way, and
does not claim any particular provider sends them.