Description
AiProxyExecutorService.executeDirectStream applies retry to the main provider Flux and then unconditionally switches to the fallback provider through onErrorResume.
If the main provider emits one or more chunks before failing, those chunks have already been written to the client SSE response. The fallback provider then starts a new completion from the beginning and appends its chunks to the already committed response.
This issue is distinct from #6647:
Related PR: #6341, reviewed at head 8fa93d7446e052f149d3740bbe7ff2052c375e23.
Affected code
shenyu-plugin-ai-proxy/.../AiProxyExecutorService.java: retryWhen(...).onErrorResume(...fallback...) wraps the whole streaming Flux.
shenyu-plugin-ai-proxy/.../AiProxyPlugin.java: the Flux is written directly to the committed SSE response.
Reproduction
An equivalent Reactor sequence:
main provider: emit "main-partial" -> error
retry: emit "main-partial" -> error
fallback: emit "fallback-completion"
produces:
main-partial, main-partial, fallback-completion
Even after #6647 removes the retry replay, an unconditional fallback after the first emitted chunk would still produce:
main-partial, fallback-completion
Impact
- Clients receive a syntactically valid SSE stream with semantically corrupted, mixed completion content.
- Tool calls or structured output can be duplicated or combined across providers.
- Multiple providers may bill for the same client request.
- The response is already committed, so the gateway cannot safely replace earlier chunks.
Expected behavior
Provider retry and fallback are allowed only before the first response chunk is emitted. After any chunk is sent, an upstream failure must terminate the current stream without starting another provider completion.
Acceptance criteria
Description
AiProxyExecutorService.executeDirectStreamapplies retry to the main provider Flux and then unconditionally switches to the fallback provider throughonErrorResume.If the main provider emits one or more chunks before failing, those chunks have already been written to the client SSE response. The fallback provider then starts a new completion from the beginning and appends its chunks to the already committed response.
This issue is distinct from #6647:
Related PR: #6341, reviewed at head
8fa93d7446e052f149d3740bbe7ff2052c375e23.Affected code
shenyu-plugin-ai-proxy/.../AiProxyExecutorService.java:retryWhen(...).onErrorResume(...fallback...)wraps the whole streaming Flux.shenyu-plugin-ai-proxy/.../AiProxyPlugin.java: the Flux is written directly to the committed SSE response.Reproduction
An equivalent Reactor sequence:
produces:
Even after #6647 removes the retry replay, an unconditional fallback after the first emitted chunk would still produce:
Impact
Expected behavior
Provider retry and fallback are allowed only before the first response chunk is emitted. After any chunk is sent, an upstream failure must terminate the current stream without starting another provider completion.
Acceptance criteria