Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions runtime/src/orion/events/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

from pydantic import BaseModel, Field

from orion.transport.messages import MessageType


# ============================================================
# Event Status
Expand Down Expand Up @@ -49,5 +51,8 @@ class Event(BaseModel):
#: Human-readable description of the event.
message: str = ""

#: Message type
type: MessageType | None = None

#: Severity of the event.
status: EventStatus = EventStatus.INFO
11 changes: 10 additions & 1 deletion runtime/src/orion/events/events.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from orion.events.base import Event, EventStatus
from orion.transport.messages import MessageType

# ============================================================
# Pipeline Events
Expand All @@ -16,6 +17,7 @@ class VoicePipelineStartEvent(PipelineStartEvent):
class ChatPipelineStartEvent(PipelineStartEvent):
"""Published when a chat processing pipeline is started."""

type: MessageType = MessageType.SUBMIT_PROMPT
text: str


Expand Down Expand Up @@ -44,10 +46,13 @@ class PipelineRestartEvent(Event):
class VoiceRecordingStartEvent(Event):
"""Published when voice recording begins."""

type: MessageType = MessageType.VOICE_START


class VoiceRecordingCompletedEvent(Event):
"""Published when voice recording has completed."""

type: MessageType = MessageType.VOICE_END
audio_path: str | None = None


Expand Down Expand Up @@ -98,24 +103,28 @@ class AgentProcessingStartEvent(Event):
class ResponseStartedEvent(Event):
"""Published when the assistant starts generating a response."""

type: MessageType = MessageType.ASSISTANT_START


class ResponseChunkEvent(Event):
"""Published for each streamed response chunk."""

type: MessageType = MessageType.ASSISTANT_CHUNK
text: str


class ResponseCompletedEvent(Event):
"""Published when the assistant has finished generating a response."""

type: MessageType = MessageType.ASSISTANT_END
status: EventStatus = EventStatus.SUCCESS

text: str


class ResponseGenerationFailedEvent(Event):
"""Published when response generation fails."""

type: MessageType = MessageType.ERROR
status: EventStatus = EventStatus.ERROR
error: str

Expand Down
2 changes: 2 additions & 0 deletions runtime/src/orion/orchestrator/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from langchain_groq import ChatGroq

from orion.memory.module import MemoryModule
from orion.transport.bridge import IPCBridge


@dataclass(slots=True)
Expand All @@ -15,3 +16,4 @@ class OrchestratorConfig:

llm: ChatGroq
memory: MemoryModule
bridge: IPCBridge
50 changes: 37 additions & 13 deletions runtime/src/orion/orchestrator/orchestrator.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
from orion.bus.event_bus import EventBus
from __future__ import annotations

from orion.bus.event_bus import EventBus
from orion.orchestrator.config import OrchestratorConfig
from orion.services.logging import LoggingService
from orion.services.setup import ServiceContext, setup_services
from orion.runtime.lifecycle import Lifecycle
from orion.services.base import BaseService
from orion.services.ipc_publisher import IPCPublisherService
from orion.services.logging import LoggingService
from orion.services.setup import ServiceContext, setup_runtime_services


class Orchestrator(Lifecycle):
"""
Coordinates the ORION runtime.

Responsibilities:
- Startup / shutdown services
- Wire global observers
- Create runtime services
- Create global services
- Start and stop all services
- Register global observers
"""

def __init__(
Expand All @@ -23,9 +28,12 @@ def __init__(
self.bus = bus
self.config = config

self.logger = LoggingService()
self.runtime_services: list[BaseService] = []
self.global_services: list[BaseService] = []
self.services: list[BaseService] = []

self.bridge = self.config.bridge

self.services = []
self._started = False

async def startup(self) -> None:
Expand All @@ -36,33 +44,49 @@ async def startup(self) -> None:
if self._started:
return

service_context = ServiceContext(
context = ServiceContext(
llm=self.config.llm,
memory=self.config.memory,
)

self.runtime_services = setup_runtime_services(context)

self.global_services = [
LoggingService(),
IPCPublisherService(bridge=self.bridge),
# MetricsService(...),
# TracingService(...),
]

self.services = [
*setup_services(service_context),
self.logger,
*self.runtime_services,
*self.global_services,
]

for service in self.services:
await service.startup()

self.bus.subscribe_all(self.logger.handle)
for service in self.global_services:
self.bus.subscribe_all(service.handle)

self._started = True

async def shutdown(self) -> None:
"""
Gracefully shutdown the runtime.
Gracefully shutdown the ORION runtime.
"""

if not self._started:
return

for service in reversed(self.services):
try:
await service.shutdown()
except Exception as exc:
print(f"Failed to shutdown {service}: {exc}")
print(f"Failed to shutdown {service.__class__.__name__}: {exc}")

self.runtime_services.clear()
self.global_services.clear()
self.services.clear()

self._started = False
4 changes: 3 additions & 1 deletion runtime/src/orion/runtime/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,15 +80,17 @@ async def run() -> None:
planner=RetrievalPlanner(llm=llm),
)

bridge = IPCBridge(bus)

orchestrator = Orchestrator(
bus=bus,
config=OrchestratorConfig(
llm=llm,
memory=memory,
bridge=bridge,
),
)

bridge = IPCBridge(bus)

server = IPCServer(
socket_path="/tmp/orion.sock",
Expand Down
50 changes: 50 additions & 0 deletions runtime/src/orion/services/ipc_publisher.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
from __future__ import annotations

from orion.events.base import Event
from orion.services.base import BaseService
from orion.transport.bridge import IPCBridge
from orion.transport.messages import Envelope


class IPCPublisherService(BaseService):
"""
Publishes runtime events to connected IPC clients.

Events that do not define an IPC message type are considered
internal runtime events and are not forwarded to clients.
"""

service_name = "ipc"

def __init__(self, bridge: IPCBridge) -> None:
super().__init__()
self._bridge = bridge

async def handle(self, event: Event) -> None:
"""
Publish a runtime event over IPC.
"""

if event.type is None:
return

envelope = Envelope(
correlation_id=event.correlation_id,
type=event.type,
payload=event.model_dump(
exclude={
"event_id",
"correlation_id",
"session_id",
"timestamp",
"source",
"type",
},
exclude_none=True,
),
)

await self._bridge.send(
session_id=event.session_id,
envelope=envelope,
)
5 changes: 2 additions & 3 deletions runtime/src/orion/services/setup.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
from collections.abc import Sequence
from dataclasses import dataclass

from langchain_groq import ChatGroq
Expand All @@ -18,8 +17,8 @@ class ServiceContext:
memory: MemoryModule


def setup_services(ctx: ServiceContext) -> Sequence[BaseService]:
services = [
def setup_runtime_services(ctx: ServiceContext) -> list[BaseService]:
services: list[BaseService] = [
VoiceRecordingService(),
TranscriptGenerationService(),
AgentService(
Expand Down
Loading
Loading