Skip to content

Commit b569e40

Browse files
fylornclaude
andauthored
Judge a stream's speed on what it generates visibly (#133)
Fixes to how a stream's speed is judged against its provider's minimum: - Counts the upstream reports are no longer used. Gemini's running output count includes the thoughts it does not show; once it leaped ahead of the text, the text counted for nothing and a fast stream could be cut. - Summarised reasoning is left out: Responses reasoning summaries, Anthropic thinking blocks, Gemini thought parts and Bedrock reasoning content arrive in bursts while the model reasons at full speed. No second is judged while such a phase is open, and its time and text are left out of the window after. Reasoning streamed as generated (Chat reasoning_content, Responses reasoning_text) still counts. - A long pause is a quarter of the window, at most 5 s, and no second is judged whose last content is more than a second old: the burst an upstream sends after holding tokens back is waited for. - The answer's end is also seen by the format's last frame (Anthropic message_stop, Chat [DONE], Responses response.completed), not only by a stop reason. - The upstream's connection is dropped at the cut, before the error frame goes to the client. - The affinity is dropped and the speed written to the log row only when the stream's outcome is the cut. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent faa1ad5 commit b569e40

8 files changed

Lines changed: 550 additions & 146 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -98,19 +98,25 @@ endpoints of its own under `/api/admin/security/redact/exceptions`.
9898
`min_speed_tokens_per_sec` 1–1000 and `min_speed_window_secs` 5–120,
9999
default 20, on the provider endpoints; blank or `null` is off, the
100100
default). From the first token on, a stream is judged once a second:
101-
when the text it streamed over the last window (answer, reasoning and
102-
tool arguments, four ASCII characters or one other character to a
103-
token; Gemini's running count when higher) comes to fewer tokens per
104-
second than the minimum, the stream ends with a 503 error in the
101+
when the visible text it streamed over the last window (answer,
102+
reasoning streamed as generated, and tool arguments; four ASCII
103+
characters or one other character to a token) comes to fewer tokens
104+
per second than the minimum, the stream ends with a 503 error in the
105105
client's format (`overloaded_error` for Anthropic, `response.failed`
106106
for Responses, `UNAVAILABLE` for Gemini), the upstream connection is
107-
closed, and the route is charged a failure. Pauses of 5 s or more are
108-
left out of the window, and a window that is more than half pauses is
109-
not judged. The session's affinity to that route is dropped, the
110-
answer so far is billed as for any stream cut short, and the log row
111-
carries the error `UpstreamTooSlow` and the speed at the cut
112-
(`tokens_per_sec`). Whole answers and Responses WebSocket turns are
113-
not measured.
107+
closed, and the route is charged a failure. Left out of the window:
108+
pauses of a quarter of the window or more (at most 5 s), and
109+
summarised reasoning (Responses reasoning summaries, Anthropic thinking
110+
blocks, Gemini thought parts, Bedrock reasoning content), which arrives
111+
in bursts while the model reasons; a window more than half left out is
112+
not judged. Nor is a second while summarised reasoning is open, one
113+
whose last content is more than a second old, or anything after the
114+
upstream has ended the answer. Counts the upstream reports are not
115+
used: Gemini's includes its hidden thoughts. The session's affinity to
116+
that route is dropped, the answer so far is billed as for any stream
117+
cut short, and the log row carries the error `UpstreamTooSlow` and the
118+
speed at the cut (`tokens_per_sec`). Whole answers and Responses
119+
WebSocket turns are not measured.
114120

115121
### Changed
116122

‎crates/gateway/src/lifecycle/mod.rs‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -426,8 +426,10 @@ pub(crate) fn build_chat_pump(
426426
}) else {
427427
continue;
428428
};
429-
// Too slow: the client is told in its own format, and
430-
// returning drops the upstream's connection.
429+
// Too slow: the upstream's connection goes now, not once
430+
// the client has taken the error, which is said in its
431+
// own format.
432+
drop(source);
431433
let err = too_slow(&provider, rate, min);
432434
let mut out = shaper.process(&error_frame(client, err.status_code(), &err.to_string()));
433435
out.extend(shaper.finish());

0 commit comments

Comments
 (0)