Skip to content

fix: standardize SSE streaming across frontend and backend#405

Open
Namraa310806 wants to merge 4 commits into
FireFistisDead:masterfrom
Namraa310806:fix/sse-streaming-protocol
Open

fix: standardize SSE streaming across frontend and backend#405
Namraa310806 wants to merge 4 commits into
FireFistisDead:masterfrom
Namraa310806:fix/sse-streaming-protocol

Conversation

@Namraa310806
Copy link
Copy Markdown
Contributor

@Namraa310806 Namraa310806 commented May 31, 2026

Summary

This PR fixes a streaming protocol mismatch between the frontend, Express gateway, and FastAPI RAG service.

Previously, the frontend expected Server-Sent Events (SSE) formatted messages (data: ... and [DONE]), while the backend streamed raw text chunks. This inconsistency could cause incomplete rendering, missed completion events, hanging loading states, and unreliable streaming behavior.

Changes Made

  • Standardized /ask/stream to use SSE-compatible framing end-to-end.

  • Added SSE event formatting in the FastAPI streaming endpoint.

  • Added explicit completion signaling using:

    • data: [DONE]
  • Preserved incremental token streaming behavior.

  • Updated frontend stream parsing to properly handle:

    • SSE event boundaries
    • multi-line data payloads
    • completion events
    • stream error events
    • partial network chunks
  • Improved stream completion handling to ensure onDone() fires reliably.

  • Preserved existing abort/cancellation behavior.

  • Kept retrieval, generation, and answer quality logic unchanged.

Related issue

Fixes: #360

Testing

  • I ran the relevant checks locally
  • I verified the app still starts
  • I tested the affected flow end-to-end

Verified Scenarios

  • Streamed responses render incrementally.
  • Multi-chunk responses are displayed correctly.
  • Stream completion reliably triggers onDone().
  • Error events are surfaced correctly.
  • Stream cancellation continues to work.
  • Existing chat experience remains unchanged.
  • No missing chunks or stuck loading states were observed.

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas

Screenshots / recordings

N/A – Streaming protocol and backend/frontend compatibility fix.

Notes

This PR is intentionally focused on resolving the /ask/stream protocol mismatch.

No changes were made to:

  • authentication
  • upload workflows
  • session handling
  • embeddings
  • vectorstore logic
  • summarization
  • retrieval pipeline
  • answer generation logic

The change only standardizes the transport protocol used for streaming responses and improves stream lifecycle reliability.

Security

  • No sensitive data included

Summary by CodeRabbit

  • New Features

    • Streaming API now uses Server-Sent Events (SSE) with explicit event framing and terminal [DONE] markers.
    • Final answers and refusal messages are delivered as framed stream events for consistent client handling.
  • Bug Fixes

    • Client stream parsing updated to handle multi-line payloads, event types, and avoid duplicate completion callbacks.
    • Improved connection cleanup and cancellation to prevent orphaned upstream work and surface generation errors as stream events.

Copilot AI review requested due to automatic review settings May 31, 2026 04:52
@vercel
Copy link
Copy Markdown

vercel Bot commented May 31, 2026

@Namraa310806 is attempting to deploy a commit to the firefistisdead's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 31, 2026

Review Change Stack

Caution

Review failed

Failed to post review comments

📝 Walkthrough

Walkthrough

The PR standardizes /ask/stream end-to-end to Server-Sent Events: backend emits SSE frames for refusals, grounded answers, and tokens (with terminal data: [DONE] and event: error), the Node gateway proxies with AbortController and SSE headers, and the frontend parses multi-line SSE events and dispatches handlers.

Changes

SSE Streaming Standardization

Layer / File(s) Summary
Backend SSE Protocol and Framing
rag-service/main.py
FastAPI /ask/stream introduces SSE helper functions and emits SSE-framed payloads for refusals, grounded answers, and per-token LLM output; final framed answer is emitted, chat/session persisted, and stream terminated with data: [DONE]; exceptions emit event: error frames.
Gateway Cancellation and SSE Integration
server.js
Node gateway passes an AbortController signal to Axios, wires a guarded cleanup() to request/response close events to abort upstream and destroy streams, sets SSE-compatible response headers, forwards framed SSE, and writes SSE event: error when upstream fails after headers sent while ignoring cancellation errors.
Frontend SSE Event Parser
frontend/src/services/ragService.js
askStream validates res.body, reads via ReadableStream reader, buffers and splits on \n\n, parses optional event: and multi-line data: blocks, dispatches onError for event: error, onDone for [DONE], onChunk for other payloads, and tracks completion to avoid duplicate onDone calls.

Sequence Diagram

sequenceDiagram
  participant User as User/Client
  participant Frontend as Frontend Parser
  participant Gateway as Node Gateway
  participant Backend as FastAPI Service

  User->>Frontend: ask(question, onChunk, onDone)
  Frontend->>Gateway: POST /ask/stream
  Gateway->>Gateway: Create AbortController
  Gateway->>Backend: Axios with signal
  
  alt Evidence Gate Refused
    Backend->>Gateway: SSE: data: refusal\n\n
    Backend->>Gateway: SSE: data: [DONE]\n\n
  else Grounded Answer
    Backend->>Gateway: SSE: data: answer\n\n
    Backend->>Gateway: SSE: data: [DONE]\n\n
  else LLM Generation
    loop Each Token
      Backend->>Gateway: SSE: data: token\n\n
    end
    Backend->>Gateway: SSE: data: final_framed_answer\n\n
    Backend->>Gateway: SSE: data: [DONE]\n\n
  end
  
  Gateway->>Frontend: Stream SSE events
  loop Parse SSE Events
    Frontend->>Frontend: Buffer & split on \n\n
    Frontend->>Frontend: Parse event: and data: lines
    alt Event Type: error
      Frontend->>Frontend: onError()
    else Data: [DONE]
      Frontend->>Frontend: onDone()
    else Other Data
      Frontend->>Frontend: onChunk(text)
    end
  end
  
  alt Client Disconnect
    User->>Gateway: close
    Gateway->>Gateway: cleanup()
    Gateway->>Backend: abort()
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

level:intermediate, quality:clean

Suggested reviewers

  • FireFistisDead

Poem

🐰 I nibble bytes beneath the sun,

data: hops out, token by one,
event: error — a startled squeak,
data: [DONE] — the stream's soft peak,
A rabbit cheers: "SSE for fun!"

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: standardizing SSE streaming across frontend and backend, which is the core objective of the PR.
Description check ✅ Passed The PR description is comprehensive, covering summary, related issue, testing verification, checklist items, and security considerations. All key template sections are addressed.
Linked Issues check ✅ Passed The PR fully addresses issue #360 by implementing SSE framing end-to-end across all three components (frontend parser, Express gateway, and FastAPI service), including completion signaling via [DONE] markers.
Out of Scope Changes check ✅ Passed All changes are scoped to resolving the SSE streaming protocol mismatch. No unrelated modifications to authentication, uploads, session handling, embeddings, vectorstore, summarization, or retrieval pipeline logic were introduced.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added backend Express or API gateway work bug Something isn't working docs Documentation only feature A new feature or improvement fix A targeted fix or cleanup frontend Frontend-related work level:advanced rag-service FastAPI / model service work type:security type:testing enhancement New feature or request labels May 31, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Comment thread rag-service/main.py
yield _sse_frame("Generation error. Please try again.", event="error")

return StreamingResponse(_generate_and_stream(), media_type="text/plain; charset=utf-8")
return StreamingResponse(_generate_and_stream(), media_type="text/event-stream; charset=utf-8")
@github-actions github-actions Bot added the duplicate This issue or pull request already exists label May 31, 2026
Copilot AI review requested due to automatic review settings June 1, 2026 18:09
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.

Comment thread server.js
Comment on lines 1034 to +1047
ragResponse.data.on("error", (err) => {
if (upstreamAbort.signal.aborted || req.aborted) {
return;
}
console.error("Stream error from RAG service:", err.message);
if (!res.headersSent) {
res.status(502).json({ error: "Streaming response failed." });
} else {
res.write("event: error\ndata: Streaming response failed.\n\n");
res.end();
}
});

ragResponse.data.on("end", cleanup);
Comment thread rag-service/main.py
Comment on lines +3561 to +3564
yield _sse_done()
except Exception:
logger.exception("Stream generation failed session_id=%s", session_id)
yield _sse_frame("Generation error. Please try again.", event="error")
Comment on lines +145 to 156
buffer += decoder.decode(value, { stream: true });

let separatorIndex = buffer.indexOf('\n\n');
while (separatorIndex !== -1) {
const eventText = buffer.slice(0, separatorIndex);
buffer = buffer.slice(separatorIndex + 2);
handleEvent(eventText);
if (completed) {
return;
}
separatorIndex = buffer.indexOf('\n\n');
}
Comment on lines +151 to +154
handleEvent(eventText);
if (completed) {
return;
}
@Namraa310806
Copy link
Copy Markdown
Contributor Author

@FireFistisDead I have solve merge conflicts and copilot review chnages in all my PR's and they are ready to merge. Please review it.
PR's: #387 #388 #389 #390 #403 #404 #405

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend Express or API gateway work bug Something isn't working docs Documentation only duplicate This issue or pull request already exists enhancement New feature or request feature A new feature or improvement fix A targeted fix or cleanup frontend Frontend-related work level:advanced rag-service FastAPI / model service work type:security type:testing

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Frontend and backend streaming protocols are inconsistent in /ask/stream

3 participants