初始化提交

This commit is contained in:
chenxin committed 2026-08-21 16:00:13 +08:00
commit 887c1f449e
963 files changed
+166869

No files matched your search

+55
View File
@@ -0,0 +1,55 @@
"""进程内 SSE 事件总线。
后台任务 ``append_event`` 写入 DB(持久化)后,同时 publish 到本总线;SSE generator
从内存队列实时读取并转发给前端,避免轮询 DB 带来的延迟与开销。
约束:仅进程内有效,进程重启即失。多进程 / 多副本部署需替换为 Redis Pub/Sub 等共享
通道;本地开发(单进程 uvicorn)适用。事件历史仍以 DB 为持久事实源,总线仅承担实时转发。
"""
from __future__ import annotations
import asyncio
from collections import defaultdict
from app.schemas.runtime import EventResponse
_TERMINAL_EVENT_TYPES = {"run.completed", "run.failed", "run.cancelled"}
class RunEventBus:
"""按 run_id 缓存事件并广播给订阅者。"""
def __init__(self) -> None:
self._events: dict[str, list[EventResponse]] = defaultdict(list)
self._subscribers: dict[str, list[asyncio.Queue[EventResponse]]] = defaultdict(list)
def publish(self, run_id: str, event: EventResponse) -> None:
self._events[run_id].append(event)
for queue in self._subscribers.get(run_id, []):
queue.put_nowait(event)
def list_after(self, run_id: str, *, after_sequence: int) -> list[EventResponse]:
"""返回 run 内 sequence 大于 after_sequence 的事件(用于断线重连补发)。"""
events = self._events.get(run_id, [])
if after_sequence <= 0:
return list(events)
return [event for event in events if event.sequence > after_sequence]
def subscribe(self, run_id: str) -> asyncio.Queue[EventResponse]:
queue: asyncio.Queue[EventResponse] = asyncio.Queue()
self._subscribers[run_id].append(queue)
return queue
def unsubscribe(self, run_id: str, queue: asyncio.Queue[EventResponse]) -> None:
subscribers = self._subscribers.get(run_id)
if subscribers is None:
return
self._subscribers[run_id] = [q for q in subscribers if q is not queue]
@staticmethod
def is_terminal(event: EventResponse) -> bool:
return event.type in _TERMINAL_EVENT_TYPES
bus = RunEventBus()