Skip to content

Commit 2ffda75

Browse files
he-yufengRC-CHN
andauthored
fix: send SSE heartbeat to prevent WebChat disconnect during compression (#7003)
* fix: send SSE heartbeat to prevent WebChat disconnect during compression When context compression triggers with slow reasoning models (e.g. deepseek-reasoner), the backend can go 30+ seconds without pushing any SSE data. The client-side EventSource / browser then assumes the connection is dead and disconnects, causing the WebUI to hang indefinitely since it never receives the eventual response. Fix: yield an SSE comment (`: heartbeat`) on every empty poll cycle. Comment lines are defined in the SSE spec as keep-alive signals -- the EventSource API ignores them but the HTTP connection stays open. Fixes #6938 * chore(dashboard): extract SSE heartbeat to constant --------- Co-authored-by: Yufeng He <40085740+universeplayer@users.noreply.github.com> Co-authored-by: RC-CHN <1051989940@qq.com>
1 parent bfd1294 commit 2ffda75

File tree

1 file changed

+11
-0
lines changed

1 file changed

+11
-0
lines changed

astrbot/dashboard/routes/chat.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@
2626

2727
from .route import Response, Route, RouteContext
2828

29+
# SSE heartbeat message to keep the connection alive during long-running operations
30+
SSE_HEARTBEAT = ": heartbeat\n\n"
31+
2932

3033
@asynccontextmanager
3134
async def track_conversation(convs: dict, conv_id: str):
@@ -40,6 +43,9 @@ async def _poll_webchat_stream_result(back_queue, username: str):
4043
try:
4144
result = await asyncio.wait_for(back_queue.get(), timeout=1)
4245
except asyncio.TimeoutError:
46+
# Return a sentinel so the caller can send an SSE heartbeat to
47+
# keep the connection alive during long-running operations (e.g.
48+
# context compression with reasoning models). See #6938.
4349
return None, False
4450
except asyncio.CancelledError:
4551
logger.debug(f"[WebChat] 用户 {username} 断开聊天长连接。")
@@ -364,6 +370,11 @@ async def stream():
364370
client_disconnected = True
365371
break
366372
if not result:
373+
# Send an SSE comment as keep-alive so the client
374+
# doesn't time out during slow backend ops like
375+
# context compression with reasoning models (#6938).
376+
if not client_disconnected:
377+
yield SSE_HEARTBEAT
367378
continue
368379

369380
if (

0 commit comments

Comments
 (0)