fix(sse): treat "length" stop_reason as legitimate in detectMalformedNonStream for Claude messages (#12935)

* fix(sse): treat "length" stop_reason as legitimate in detectMalformedNonStream for Claude messages

* docs(changelog): add fragment for length stop_reason fix

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>

---------

Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
Co-authored-by: jasminsehic <jasminsehic@users.noreply.github.com>
This commit is contained in:
Jasmin Sehic
2026-09-18 22:59:41 +08:00
committed by GitHub
parent 6788de8ef9
commit aecd50369b
3 changed files with 19 additions and 1 deletions

View File

@@ -0,0 +1 @@
- **fix(sse):** `detectMalformedNonStream` no longer flags a Claude-format message as malformed when the content array only contains empty text blocks or the `(empty response)` sentinel and `stop_reason` is `"length"` — a legitimate truncation (e.g. `ollama/qwen3:1.7b` exhausting its reasoning budget before producing visible text), not a real 502-worthy empty response.

View File

@@ -291,7 +291,11 @@ export function detectMalformedNonStream(
// text:""}] — one block, just with no visible text — which is the exact
// same legitimate truncated-completion shape, so the exemption must apply
// whenever there is no visible output, not only when content is [].
if (stopReason === "max_tokens" || stopReason === "tool_use") return null;
// "length" is the OpenAI-style spelling some Claude-compatible shims
// (ollama qwen3 with the reasoning budget exhausted) emit for the same
// truncated-completion case — sentinel content + stop_reason "length".
if (stopReason === "max_tokens" || stopReason === "tool_use" || stopReason === "length")
return null;
// content:[] with no stop_reason at all is non-terminal, not empty (#9971).
if (content.length === 0 && stopReason.length === 0) return null;
return "empty_choices";

View File

@@ -56,6 +56,19 @@ test("synthOpenAIErrorChunk references provider in message", () => {
);
});
test("detectMalformedNonStream allows Claude message with (empty response) + stop_reason=length (ollama qwen3)", () => {
const resp = {
type: "message",
content: [{ type: "text", text: "(empty response)" }],
stop_reason: "length",
};
assert.strictEqual(
detectMalformedNonStream(resp),
null,
"ollama reasoning truncation should not be empty_choices"
);
});
// ── (b) synthResponsesFailure matches a response.failed event ────────────────
test("synthResponsesFailure produces a response.failed SSE event", () => {