Agentic_RAG / agentic_rag /runtime /stream_bus.py
H022329's picture
Upload folder using huggingface_hub
6b62834 verified
Raw
History Blame Contribute Delete
2.18 kB
"""StreamBus — internal pub/sub event bus for streaming events to clients."""
import asyncio
from typing import AsyncIterator, Optional
from agentic_rag.data.models import AgentEvent
class StreamBus:
"""Async pub/sub event bus for agent streaming.
Agents publish events to topics (keyed by turn_id).
Entry points subscribe to relay events to SSE/WebSocket clients.
"""
def __init__(self):
self._subscribers: dict[str, list[asyncio.Queue]] = {}
def publish(self, turn_id: str, event: AgentEvent) -> None:
"""Publish an event to all subscribers of a turn."""
event.turn_id = turn_id
queues = self._subscribers.get(turn_id, [])
for q in queues:
try:
q.put_nowait(event)
except asyncio.QueueFull:
pass # Drop event if subscriber is too slow
async def subscribe(self, turn_id: str, buffer_size: int = 100) -> AsyncIterator[AgentEvent]:
"""Subscribe to events for a turn. Yields events as they arrive."""
queue: asyncio.Queue = asyncio.Queue(maxsize=buffer_size)
if turn_id not in self._subscribers:
self._subscribers[turn_id] = []
self._subscribers[turn_id].append(queue)
try:
while True:
try:
event = await asyncio.wait_for(queue.get(), timeout=60.0)
yield event
if event.event_type.value == "done":
break
except asyncio.TimeoutError:
yield AgentEvent(event_type="error", data={"message": "Stream timeout"})
break
finally:
self._subscribers[turn_id].remove(queue)
if not self._subscribers[turn_id]:
del self._subscribers[turn_id]
def cleanup(self, turn_id: str) -> None:
"""Remove all subscribers for a turn."""
self._subscribers.pop(turn_id, None)
# Global instance
_stream_bus: Optional[StreamBus] = None
def get_stream_bus() -> StreamBus:
global _stream_bus
if _stream_bus is None:
_stream_bus = StreamBus()
return _stream_bus