open-webui/backend/open_webui
Claude 8289ac7de3
feat(stream): resumable WS streaming via Redis log with seq-based replay
Adds a bounded Redis stream log for every in-flight assistant message so
clients can reconnect mid-stream (page refresh, network drop, device
switch) and catch up on frames they missed without re-fetching the full
chat from the database.

Problem this solves
-------------------
With ENABLE_REALTIME_CHAT_SAVE=False (default), the backend does not
write the assistant message to the DB until the stream finishes. If the
client refreshes the page mid-stream, chat load from the DB returns
nothing for the in-progress message and the response appears to vanish
until the stream eventually completes. Users staring at an empty chat
while the backend quietly keeps emitting tokens into the void.

Design
------
* Every outbound WS envelope gets stamped with a monotonic per-message
  `seq` inside `get_event_emitter` and appended to a bounded Redis
  stream keyed `{REDIS_KEY_PREFIX}:stream:{message_id}`.
  MAXLEN ~ 2000 entries, TTL 1h as a safety net.
* Clients track `message.lastSeq` in Chat.svelte as events arrive. On
  chat load (mid-stream refresh) and on socket reconnect they emit
  `resume-stream {chat_id, message_id, last_seq}`.
* The server authenticates the session, verifies the user owns the
  chat, XRANGEs the log, filters by `seq > last_seq`, and emits the
  missed envelopes to THAT session only (via `to=sid`) so live listeners
  in the user room keep receiving their normal live stream unchanged.
* The existing chat event handler drops any envelope with
  `seq <= message.lastSeq`, making replay idempotent against live
  frames that race the replay after a reconnect.
* When an event with `done: True` fires, a background task truncates
  the log after a 30s grace window so late reconnects still catch the
  finalization; anything beyond that resumes from the now-up-to-date DB.

Orthogonality
-------------
Zero touches to middleware.py or the streaming hot path. The log stores
whatever gets emitted; any future change to emit shape
(chat:message:delta, per-block ops, JSON Patch, ...) is logged and
replayed verbatim with no coupling.

Graceful degradation
--------------------
No-op when Redis is not configured (WEBSOCKET_MANAGER != 'redis'). In
that deployment mode, refresh during streaming retains the current
behavior of waiting for the stream to complete.

Auth model
----------
The log is keyed by message_id only. The resume handler must do a chat
ownership check (Chats.get_chat_by_id_and_user_id) before replaying, so
a malicious client cannot read another user's stream by guessing a
message_id.
2026-04-14 21:11:36 +00:00
..
data refac: mv backend files to /open_webui dir 2024-09-04 16:54:48 +02:00
internal refac 2026-04-12 19:41:02 -05:00
migrations refac 2026-04-01 18:26:46 -05:00
models refac 2026-04-13 21:29:03 -05:00
retrieval fix(retrieval): offload Loader.load to a worker thread so file uploads stop blocking the event loop (#23705) 2026-04-14 10:55:46 -05:00
routers fix(retrieval): offload Loader.load to a worker thread so file uploads stop blocking the event loop (#23705) 2026-04-14 10:55:46 -05:00
socket feat(stream): resumable WS streaming via Redis log with seq-based replay 2026-04-14 21:11:36 +00:00
static refac 2026-03-23 23:39:52 -05:00
storage refac 2026-04-12 19:08:30 -05:00
test refac 2026-03-17 17:58:01 -05:00
tools fix(retrieval): offload sync VECTOR_DB_CLIENT calls in async paths via AsyncVectorDBClient (#23706) 2026-04-14 10:50:18 -05:00
utils refac 2026-04-14 10:55:11 -05:00
__init__.py refac (#22987) 2026-03-24 15:41:26 -05:00
alembic.ini fix: Alembic CLI commands from failing 2025-08-15 04:17:47 -04:00
config.py refac 2026-04-14 00:07:50 -05:00
constants.py refac 2026-04-13 14:08:58 -05:00
env.py refac 2026-04-13 16:25:44 -05:00
functions.py refac: async db 2026-04-12 14:22:11 -05:00
main.py fix(middleware): replace BaseHTTPMiddleware HTTP middlewares with pure ASGI implementations (#23709) 2026-04-14 10:47:48 -05:00
tasks.py refac 2026-03-17 17:58:01 -05:00