Replace set_registry with attach(*, registry, bus) on BaseTask
`BaseTask` no longer takes `bus=` in its constructor. Instead the runner now hands both the registry and the bus to a task via `task.attach(registry=..., bus=...)` (called from `PipelineRunner.spawn()`), and `bus` / `registry` are properties that raise if accessed before attach. `PipelineTask`, `LLMTask`, and `LLMContextTask` lose their `bus=` parameters to match, and `_BusEdgeProcessor` now stores only a task reference and reads `task.bus` lazily so bridged pipelines work even though the bus isn't known at construction time.
This commit is contained in:
@@ -161,7 +161,6 @@ class _BusEdgeProcessor(FrameProcessor, BusSubscriber):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
bus: TaskBus,
|
|
||||||
task: "BaseTask",
|
task: "BaseTask",
|
||||||
direction: FrameDirection,
|
direction: FrameDirection,
|
||||||
bridges: tuple[str, ...] = (),
|
bridges: tuple[str, ...] = (),
|
||||||
@@ -171,9 +170,11 @@ class _BusEdgeProcessor(FrameProcessor, BusSubscriber):
|
|||||||
"""Initialize the edge processor.
|
"""Initialize the edge processor.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
bus: The ``TaskBus`` to exchange frames with.
|
task: The owning task; the edge reads ``task.bus`` lazily
|
||||||
task: The owning task; ``task.name`` is the message source
|
so the bus only needs to be set (via
|
||||||
and ``task.active`` gates inbound frames.
|
:meth:`BaseTask.attach`) by the time the processor is
|
||||||
|
set up. ``task.name`` is the message source and
|
||||||
|
``task.active`` gates inbound frames.
|
||||||
direction: Direction this edge captures and forwards to the
|
direction: Direction this edge captures and forwards to the
|
||||||
bus. Inbound frames from the bus travelling in the
|
bus. Inbound frames from the bus travelling in the
|
||||||
opposite direction are injected here.
|
opposite direction are injected here.
|
||||||
@@ -184,7 +185,6 @@ class _BusEdgeProcessor(FrameProcessor, BusSubscriber):
|
|||||||
**kwargs: Additional arguments passed to ``FrameProcessor``.
|
**kwargs: Additional arguments passed to ``FrameProcessor``.
|
||||||
"""
|
"""
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
self._bus = bus
|
|
||||||
self._task = task
|
self._task = task
|
||||||
self._direction = direction
|
self._direction = direction
|
||||||
self._bridges = bridges
|
self._bridges = bridges
|
||||||
@@ -193,12 +193,12 @@ class _BusEdgeProcessor(FrameProcessor, BusSubscriber):
|
|||||||
async def setup(self, setup: FrameProcessorSetup):
|
async def setup(self, setup: FrameProcessorSetup):
|
||||||
"""Subscribe to the bus during processor setup."""
|
"""Subscribe to the bus during processor setup."""
|
||||||
await super().setup(setup)
|
await super().setup(setup)
|
||||||
await self._bus.subscribe(self)
|
await self._task.bus.subscribe(self)
|
||||||
|
|
||||||
async def cleanup(self):
|
async def cleanup(self):
|
||||||
"""Unsubscribe from the bus on cleanup."""
|
"""Unsubscribe from the bus on cleanup."""
|
||||||
await super().cleanup()
|
await super().cleanup()
|
||||||
await self._bus.unsubscribe(self)
|
await self._task.bus.unsubscribe(self)
|
||||||
|
|
||||||
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
||||||
"""Pass the frame through locally and forward matching ones to the bus."""
|
"""Pass the frame through locally and forward matching ones to the bus."""
|
||||||
@@ -212,7 +212,7 @@ class _BusEdgeProcessor(FrameProcessor, BusSubscriber):
|
|||||||
if self._exclude_frames and isinstance(frame, self._exclude_frames):
|
if self._exclude_frames and isinstance(frame, self._exclude_frames):
|
||||||
return
|
return
|
||||||
|
|
||||||
await self._bus.send(
|
await self._task.bus.send(
|
||||||
BusFrameMessage(source=self._task.name, frame=frame, direction=direction)
|
BusFrameMessage(source=self._task.name, frame=frame, direction=direction)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -152,7 +152,6 @@ class BaseTask(BaseObject, BusSubscriber):
|
|||||||
self,
|
self,
|
||||||
name: str | None = None,
|
name: str | None = None,
|
||||||
*,
|
*,
|
||||||
bus: TaskBus | None = None,
|
|
||||||
active: bool = True,
|
active: bool = True,
|
||||||
):
|
):
|
||||||
"""Initialize the BaseTask.
|
"""Initialize the BaseTask.
|
||||||
@@ -161,15 +160,15 @@ class BaseTask(BaseObject, BusSubscriber):
|
|||||||
name: Unique name for this task. If ``None``, an auto-generated
|
name: Unique name for this task. If ``None``, an auto-generated
|
||||||
name is used (useful for instances that don't participate
|
name is used (useful for instances that don't participate
|
||||||
in inter-task communication).
|
in inter-task communication).
|
||||||
bus: The `TaskBus` for inter-task communication. ``None``
|
|
||||||
means this task doesn't participate in inter-task
|
|
||||||
messaging; methods that require a bus will fail at
|
|
||||||
call time.
|
|
||||||
active: Whether the task starts active. Defaults to True.
|
active: Whether the task starts active. Defaults to True.
|
||||||
"""
|
"""
|
||||||
super().__init__(name=name)
|
super().__init__(name=name)
|
||||||
|
|
||||||
self._bus = bus
|
# Runner-provided context. Populated by ``attach()`` before
|
||||||
|
# ``run()`` is called. Accessing ``self.bus`` / ``self.registry``
|
||||||
|
# before ``attach()`` raises.
|
||||||
|
self._bus: TaskBus | None = None
|
||||||
|
self._registry: TaskRegistry | None = None
|
||||||
|
|
||||||
# Activation. Pending activation is deferred until the task
|
# Activation. Pending activation is deferred until the task
|
||||||
# starts, then on_activated fires.
|
# starts, then on_activated fires.
|
||||||
@@ -184,9 +183,6 @@ class BaseTask(BaseObject, BusSubscriber):
|
|||||||
self._started_at: float | None = None
|
self._started_at: float | None = None
|
||||||
self._finished_event: asyncio.Event = asyncio.Event()
|
self._finished_event: asyncio.Event = asyncio.Event()
|
||||||
|
|
||||||
# Registry is set by the runner via set_registry().
|
|
||||||
self._registry: TaskRegistry | None = None
|
|
||||||
|
|
||||||
# Job coordination. Worker state tracks active job requests
|
# Job coordination. Worker state tracks active job requests
|
||||||
# keyed by job_id, supporting multiple jobs in flight
|
# keyed by job_id, supporting multiple jobs in flight
|
||||||
# (e.g. parallel @job handlers). Each running handler has a
|
# (e.g. parallel @job handlers). Each running handler has a
|
||||||
@@ -223,8 +219,14 @@ class BaseTask(BaseObject, BusSubscriber):
|
|||||||
self._register_event_handler("on_task_failed")
|
self._register_event_handler("on_task_failed")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def bus(self) -> TaskBus | None:
|
def bus(self) -> TaskBus:
|
||||||
"""The bus instance for task communication."""
|
"""The bus this task is attached to.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
RuntimeError: If accessed before :meth:`attach` has been called.
|
||||||
|
"""
|
||||||
|
if self._bus is None:
|
||||||
|
raise RuntimeError(f"Task '{self}': bus is not set; call attach() first.")
|
||||||
return self._bus
|
return self._bus
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -243,8 +245,14 @@ class BaseTask(BaseObject, BusSubscriber):
|
|||||||
return self._parent
|
return self._parent
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def registry(self) -> TaskRegistry | None:
|
def registry(self) -> TaskRegistry:
|
||||||
"""The shared task registry, if set by a runner."""
|
"""The shared task registry this task is attached to.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
RuntimeError: If accessed before :meth:`attach` has been called.
|
||||||
|
"""
|
||||||
|
if self._registry is None:
|
||||||
|
raise RuntimeError(f"Task '{self}': registry is not set; call attach() first.")
|
||||||
return self._registry
|
return self._registry
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -276,13 +284,19 @@ class BaseTask(BaseObject, BusSubscriber):
|
|||||||
"""Active job groups launched by this task, keyed by job_id."""
|
"""Active job groups launched by this task, keyed by job_id."""
|
||||||
return self._job_groups
|
return self._job_groups
|
||||||
|
|
||||||
def set_registry(self, registry: TaskRegistry) -> None:
|
def attach(self, *, registry: TaskRegistry, bus: TaskBus) -> None:
|
||||||
"""Set the shared task registry.
|
"""Attach the task to a runner-provided registry and bus.
|
||||||
|
|
||||||
|
Called by the runner (typically from ``spawn()``) before the
|
||||||
|
task is run. After this call, :attr:`registry` and :attr:`bus`
|
||||||
|
return the provided instances.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
registry: The shared registry instance.
|
registry: The shared task registry.
|
||||||
|
bus: The shared task bus.
|
||||||
"""
|
"""
|
||||||
self._registry = registry
|
self._registry = registry
|
||||||
|
self._bus = bus
|
||||||
|
|
||||||
async def cleanup(self) -> None:
|
async def cleanup(self) -> None:
|
||||||
"""Clean up the task and release resources.
|
"""Clean up the task and release resources.
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ class PipelineRunner(BaseObject, BusSubscriber):
|
|||||||
if task.name in self._entries:
|
if task.name in self._entries:
|
||||||
logger.error(f"PipelineRunner '{self}': task '{task.name}' already exists, skipping")
|
logger.error(f"PipelineRunner '{self}': task '{task.name}' already exists, skipping")
|
||||||
return
|
return
|
||||||
task.set_registry(self._registry)
|
task.attach(registry=self._registry, bus=self._bus)
|
||||||
await self._registry.watch(task.name, self._on_local_task_ready)
|
await self._registry.watch(task.name, self._on_local_task_ready)
|
||||||
entry = _TaskEntry(task=task)
|
entry = _TaskEntry(task=task)
|
||||||
self._entries[task.name] = entry
|
self._entries[task.name] = entry
|
||||||
|
|||||||
@@ -208,7 +208,6 @@ class PipelineTask(BaseTask):
|
|||||||
*,
|
*,
|
||||||
additional_span_attributes: dict | None = None,
|
additional_span_attributes: dict | None = None,
|
||||||
app_resources: Any = None,
|
app_resources: Any = None,
|
||||||
bus: TaskBus | None = None,
|
|
||||||
bridged: tuple[str, ...] | None = None,
|
bridged: tuple[str, ...] | None = None,
|
||||||
cancel_on_idle_timeout: bool = True,
|
cancel_on_idle_timeout: bool = True,
|
||||||
cancel_timeout_secs: float = CANCEL_TIMEOUT_SECS,
|
cancel_timeout_secs: float = CANCEL_TIMEOUT_SECS,
|
||||||
@@ -243,14 +242,12 @@ class PipelineTask(BaseTask):
|
|||||||
``FunctionCallParams.app_resources``. The framework never
|
``FunctionCallParams.app_resources``. The framework never
|
||||||
copies or clears this object; the caller retains their handle
|
copies or clears this object; the caller retains their handle
|
||||||
and can read any mutations after the task finishes.
|
and can read any mutations after the task finishes.
|
||||||
bus: Optional :class:`~pipecat.bus.TaskBus` for inter-task
|
|
||||||
communication. ``None`` means this task doesn't
|
|
||||||
participate in inter-task messaging.
|
|
||||||
bridged: Bridge configuration. ``None`` means the pipeline
|
bridged: Bridge configuration. ``None`` means the pipeline
|
||||||
is not bridged. An empty tuple ``()`` wraps the pipeline
|
is not bridged. An empty tuple ``()`` wraps the pipeline
|
||||||
with bus edge processors that accept frames from all
|
with bus edge processors that accept frames from all
|
||||||
bridges. A tuple of names like ``("voice",)`` accepts
|
bridges. A tuple of names like ``("voice",)`` accepts
|
||||||
only frames from those bridges. Requires ``bus``.
|
only frames from those bridges. The bus comes from
|
||||||
|
:meth:`attach` (called by the runner).
|
||||||
cancel_on_idle_timeout: Whether the pipeline task should be cancelled if
|
cancel_on_idle_timeout: Whether the pipeline task should be cancelled if
|
||||||
the idle timeout is reached.
|
the idle timeout is reached.
|
||||||
cancel_timeout_secs: Timeout (in seconds) to wait for cancellation to happen
|
cancel_timeout_secs: Timeout (in seconds) to wait for cancellation to happen
|
||||||
@@ -281,10 +278,8 @@ class PipelineTask(BaseTask):
|
|||||||
Use ``app_resources`` instead. ``tool_resources`` will be
|
Use ``app_resources`` instead. ``tool_resources`` will be
|
||||||
removed in a future version.
|
removed in a future version.
|
||||||
"""
|
"""
|
||||||
super().__init__(name=name, bus=bus)
|
super().__init__(name=name)
|
||||||
self._bridged = bridged
|
self._bridged = bridged
|
||||||
if bridged is not None and bus is None:
|
|
||||||
raise ValueError(f"PipelineTask '{self}': ``bridged`` requires a ``bus`` to be set.")
|
|
||||||
if tool_resources is not None:
|
if tool_resources is not None:
|
||||||
with warnings.catch_warnings():
|
with warnings.catch_warnings():
|
||||||
warnings.simplefilter("always")
|
warnings.simplefilter("always")
|
||||||
@@ -406,11 +401,11 @@ class PipelineTask(BaseTask):
|
|||||||
|
|
||||||
# When bridged, wrap the user pipeline with bus edge processors
|
# When bridged, wrap the user pipeline with bus edge processors
|
||||||
# so frames tee onto the bus at the source/sink and incoming bus
|
# so frames tee onto the bus at the source/sink and incoming bus
|
||||||
# frames are injected back into the local pipeline.
|
# frames are injected back into the local pipeline. The edges
|
||||||
|
# read the task's bus lazily, so the bus only needs to be set
|
||||||
|
# (via ``attach()``) before ``run()`` is called.
|
||||||
if bridged is not None:
|
if bridged is not None:
|
||||||
assert bus is not None # validated above
|
|
||||||
edge_source = _BusEdgeProcessor(
|
edge_source = _BusEdgeProcessor(
|
||||||
bus=bus,
|
|
||||||
task=self,
|
task=self,
|
||||||
direction=FrameDirection.UPSTREAM,
|
direction=FrameDirection.UPSTREAM,
|
||||||
bridges=bridged,
|
bridges=bridged,
|
||||||
@@ -418,7 +413,6 @@ class PipelineTask(BaseTask):
|
|||||||
name=f"{self}::EdgeSource",
|
name=f"{self}::EdgeSource",
|
||||||
)
|
)
|
||||||
edge_sink = _BusEdgeProcessor(
|
edge_sink = _BusEdgeProcessor(
|
||||||
bus=bus,
|
|
||||||
task=self,
|
task=self,
|
||||||
direction=FrameDirection.DOWNSTREAM,
|
direction=FrameDirection.DOWNSTREAM,
|
||||||
bridges=bridged,
|
bridges=bridged,
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ self-contained conversation context, removing the need for subclasses
|
|||||||
to manually wire `LLMContextAggregatorPair`.
|
to manually wire `LLMContextAggregatorPair`.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from pipecat.bus import TaskBus
|
|
||||||
from pipecat.pipeline.pipeline import Pipeline
|
from pipecat.pipeline.pipeline import Pipeline
|
||||||
from pipecat.processors.aggregators.llm_context import LLMContext
|
from pipecat.processors.aggregators.llm_context import LLMContext
|
||||||
from pipecat.processors.aggregators.llm_response_universal import (
|
from pipecat.processors.aggregators.llm_response_universal import (
|
||||||
@@ -38,7 +37,6 @@ class LLMContextTask(LLMTask):
|
|||||||
|
|
||||||
task = LLMContextTask(
|
task = LLMContextTask(
|
||||||
"worker",
|
"worker",
|
||||||
bus=bus,
|
|
||||||
llm=OpenAILLMService(...),
|
llm=OpenAILLMService(...),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -51,7 +49,6 @@ class LLMContextTask(LLMTask):
|
|||||||
self,
|
self,
|
||||||
name: str,
|
name: str,
|
||||||
*,
|
*,
|
||||||
bus: TaskBus,
|
|
||||||
llm: LLMService,
|
llm: LLMService,
|
||||||
active: bool = False,
|
active: bool = False,
|
||||||
bridged: tuple[str, ...] | None = None,
|
bridged: tuple[str, ...] | None = None,
|
||||||
@@ -64,7 +61,6 @@ class LLMContextTask(LLMTask):
|
|||||||
|
|
||||||
Args:
|
Args:
|
||||||
name: Unique name for this task.
|
name: Unique name for this task.
|
||||||
bus: The `TaskBus` for inter-task communication.
|
|
||||||
llm: The LLM service.
|
llm: The LLM service.
|
||||||
active: Whether the task starts active. Defaults to False.
|
active: Whether the task starts active. Defaults to False.
|
||||||
bridged: Bridge configuration forwarded to ``PipelineTask``.
|
bridged: Bridge configuration forwarded to ``PipelineTask``.
|
||||||
@@ -95,7 +91,6 @@ class LLMContextTask(LLMTask):
|
|||||||
|
|
||||||
super().__init__(
|
super().__init__(
|
||||||
name,
|
name,
|
||||||
bus=bus,
|
|
||||||
llm=llm,
|
llm=llm,
|
||||||
pipeline=pipeline,
|
pipeline=pipeline,
|
||||||
active=active,
|
active=active,
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ from dataclasses import dataclass
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from pipecat.adapters.schemas.tools_schema import ToolsSchema
|
from pipecat.adapters.schemas.tools_schema import ToolsSchema
|
||||||
from pipecat.bus import TaskBus
|
|
||||||
from pipecat.frames.frames import (
|
from pipecat.frames.frames import (
|
||||||
ControlFrame,
|
ControlFrame,
|
||||||
Frame,
|
Frame,
|
||||||
@@ -79,8 +78,8 @@ class LLMTask(PipelineTask):
|
|||||||
self,
|
self,
|
||||||
name: str,
|
name: str,
|
||||||
*,
|
*,
|
||||||
bus: TaskBus,
|
|
||||||
llm: LLMService,
|
llm: LLMService,
|
||||||
|
pipeline: Pipeline | None = None,
|
||||||
active: bool = False,
|
active: bool = False,
|
||||||
bridged: tuple[str, ...] | None = None,
|
bridged: tuple[str, ...] | None = None,
|
||||||
defer_tool_frames: bool = True,
|
defer_tool_frames: bool = True,
|
||||||
@@ -89,9 +88,12 @@ class LLMTask(PipelineTask):
|
|||||||
|
|
||||||
Args:
|
Args:
|
||||||
name: Unique name for this task.
|
name: Unique name for this task.
|
||||||
bus: The `TaskBus` for inter-task communication.
|
|
||||||
llm: The LLM service. ``@tool`` decorated methods are
|
llm: The LLM service. ``@tool`` decorated methods are
|
||||||
automatically registered on it.
|
automatically registered on it.
|
||||||
|
pipeline: Optional pipeline override. When ``None``,
|
||||||
|
defaults to ``Pipeline([llm])``. Subclasses can pass a
|
||||||
|
custom pipeline that wraps the LLM with additional
|
||||||
|
processors.
|
||||||
active: Whether the task starts active. Defaults to False.
|
active: Whether the task starts active. Defaults to False.
|
||||||
bridged: Bridge configuration forwarded to ``PipelineTask``.
|
bridged: Bridge configuration forwarded to ``PipelineTask``.
|
||||||
Pass ``()`` to wrap the LLM pipeline with bus edge
|
Pass ``()`` to wrap the LLM pipeline with bus edge
|
||||||
@@ -111,12 +113,11 @@ class LLMTask(PipelineTask):
|
|||||||
self._llm = llm
|
self._llm = llm
|
||||||
self._register_tools(llm)
|
self._register_tools(llm)
|
||||||
|
|
||||||
pipeline = Pipeline([self._llm])
|
pipeline = pipeline if pipeline is not None else Pipeline([self._llm])
|
||||||
|
|
||||||
super().__init__(
|
super().__init__(
|
||||||
pipeline,
|
pipeline,
|
||||||
name=name,
|
name=name,
|
||||||
bus=bus,
|
|
||||||
bridged=bridged,
|
bridged=bridged,
|
||||||
exclude_frames=(PipelineFlushFrame,),
|
exclude_frames=(PipelineFlushFrame,),
|
||||||
enable_rtvi=False,
|
enable_rtvi=False,
|
||||||
|
|||||||
Reference in New Issue
Block a user