Deadlines
Every route that runs the model holds a request to a deadline
(ADR 0058):
--server-request-timeout for one answered whole, --server-stream-timeout
for one that streams, both 2400 seconds by default and 0 for none, counted
from the request’s admission (a WebSocket response’s never from its queue, an
image job’s from its start). Past it the request is cancelled, its lane freed,
and the request log’s ignis.request.cancelled line says cause: timeout.
Before any ending the client gets everything the loop guard held back. What it
is answered is --server-timeout-reply:
| Route | partial (the default) |
error |
|---|---|---|
/v1/chat/completions, whole |
200, the answer so far with finish_reason: "length" and its usage, and the header ignis-stopped-by: request_timeout |
504 request_timeout |
/v1/chat/completions, streamed |
the length finish chunk, the usage chunk if include_usage, then [DONE] |
one {"error": {...}} chunk, type and code request_timeout, then [DONE], as an engine error ends a stream |
/v1/responses, whole |
200, status: "incomplete", incomplete_details.reason: "max_output_tokens", the output so far, and the same header |
504 request_timeout |
/v1/responses, streamed or WebSocket |
response.incomplete, as above |
response.failed with code request_timeout |
/v1/decide |
the question not_completed |
the same |
/v1/images/* |
504 request_timeout; streamed, the images already made, then an error event with request_timeout |
the same |
length and max_output_tokens are the only values the two APIs define for
an answer a limit cut, so every client already reads them; the header, on a
whole answer, and the log say it was the deadline. A browser on another origin
can read the header: the server exposes it, with ignis-model-status and
Retry-After (Access-Control-Expose-Headers). A tool call the deadline cut
mid-way is dropped whole. A decision has no partial answer, and an image cut
short is no image, so those two are errors either way. A chat request’s media
acquisition keeps its own deadline, --server-request-timeout from its start;
at 0 only each fetch’s timeout bounds it.
Upgrading from 0.7.1: the default deadline is 40 minutes, not 120 seconds; a
request past it is answered with what it generated, not a 504, unless
--server-timeout-reply error; a chat stream and an image job now have a
deadline; 0 is accepted and means none. A chat completion the engine let go
of without a finish, answered whole, is a 500 engine_error, no longer a
504 request_timeout logged as a timeout.
Readiness. A loaded model is not served the moment it is on the GPU: the
server first runs one two-token request through the scheduler so the decode
graphs are captured, and until that is done every /v1 route answers 503
with code server_not_ready and Retry-After: 5 (a preflight and /v1/docs
still answer). The ignis.process.ready log event, with warm_up_ms, marks
the moment it admits requests; make start waits for it, since it waits for
/v1/models to answer. The warm-up is one request in the counters, and the
CPU mock, which has nothing to capture, is ready at once.
Probes. Two, both outside /v1: neither asks for an API key, and neither
counts as a use of the model (idle unload does not see them).
GET /healthis liveness:200 {"status": "ok"}whenever the process answers, and it answers from its start: the API listener is bound before the start-up download and load (ignis.server.listeningnames its address), then through the warm-up, every switch and reload, and an idle unload’s load. Point a restarting probe here (KuberneteslivenessProbe), never at/ready: a loading server is alive, and restarting it would restart the load in a loop. It needs nostartupProbeorinitialDelaySecondsfor the load.GET /readyis readiness: whether to send it requests. It answers503 server_not_readywithRetry-After: 5through the start-up load and until that first warm-up has run; while the model loads at start, every path but/healthanswers that way, whatever the key. After that it answers200 {"status": "ok"}while a model serves or is on its way: through a switch, an idle unload and the load after it (failed too, since the next request tries it again), and a failed switch that is reloading the old model. The one exception is a failed switch that could reload nothing, which leaves no model serving and none loading until another switch succeeds. There it answers503 server_not_readywith noRetry-After, since asking again does not help and a switch does. Point a load balancer, or a KubernetesreadinessProbeorstartupProbe, here.
What the model itself is doing is GET /v1/models’s status.
