Fix type errors in pipeline and add to pyright checked set

Use Sequence[FrameProcessor] instead of list[FrameProcessor] in Pipeline,
ServiceSwitcher, and ServiceSwitcherStrategy parameters to accept subtype
lists. Add cast() in LLMSwitcher for narrowed return types. Guard against
None in task_observer._send_to_proxy and replace hasattr with truthiness
check in task._cleanup.
This commit is contained in:
Mark Backman
2026-04-16 21:47:11 -04:00
parent 3127cc6161
commit ab91047300
6 changed files with 18 additions and 14 deletions

View File

@@ -9,7 +9,8 @@
"src/pipecat/frames", "src/pipecat/frames",
"src/pipecat/observers", "src/pipecat/observers",
"src/pipecat/extensions", "src/pipecat/extensions",
"src/pipecat/turns" "src/pipecat/turns",
"src/pipecat/pipeline"
], ],
"exclude": [ "exclude": [
"**/*_pb2.py", "**/*_pb2.py",

View File

@@ -6,7 +6,7 @@
"""LLM switcher for switching between different LLMs at runtime, with different switching strategies.""" """LLM switcher for switching between different LLMs at runtime, with different switching strategies."""
from typing import Any from typing import Any, cast
from pipecat.adapters.schemas.direct_function import DirectFunction from pipecat.adapters.schemas.direct_function import DirectFunction
from pipecat.pipeline.service_switcher import ( from pipecat.pipeline.service_switcher import (
@@ -47,7 +47,7 @@ class LLMSwitcher(ServiceSwitcher[StrategyType]):
Returns: Returns:
List of LLM services managed by this switcher. List of LLM services managed by this switcher.
""" """
return self.services return cast(list[LLMService], self.services)
@property @property
def active_llm(self) -> LLMService: def active_llm(self) -> LLMService:
@@ -56,7 +56,7 @@ class LLMSwitcher(ServiceSwitcher[StrategyType]):
Returns: Returns:
The currently active LLM service, or None if no LLM is active. The currently active LLM service, or None if no LLM is active.
""" """
return self.strategy.active_service return cast(LLMService, self.strategy.active_service)
async def run_inference(self, context: LLMContext, **kwargs) -> str | None: async def run_inference(self, context: LLMContext, **kwargs) -> str | None:
"""Run a one-shot, out-of-band (i.e. out-of-pipeline) inference with the given LLM context, using the currently active LLM. """Run a one-shot, out-of-band (i.e. out-of-pipeline) inference with the given LLM context, using the currently active LLM.

View File

@@ -11,7 +11,7 @@ in sequence and manages frame flow between them, along with helper classes
for pipeline source and sink operations. for pipeline source and sink operations.
""" """
from collections.abc import Callable, Coroutine from collections.abc import Callable, Coroutine, Sequence
from pipecat.frames.frames import Frame from pipecat.frames.frames import Frame
from pipecat.pipeline.base_pipeline import BasePipeline from pipecat.pipeline.base_pipeline import BasePipeline
@@ -98,7 +98,7 @@ class Pipeline(BasePipeline):
def __init__( def __init__(
self, self,
processors: list[FrameProcessor], processors: Sequence[FrameProcessor],
*, *,
source: FrameProcessor | None = None, source: FrameProcessor | None = None,
sink: FrameProcessor | None = None, sink: FrameProcessor | None = None,
@@ -106,7 +106,7 @@ class Pipeline(BasePipeline):
"""Initialize the pipeline with a list of processors. """Initialize the pipeline with a list of processors.
Args: Args:
processors: List of frame processors to connect in sequence. processors: Sequence of frame processors to connect in sequence.
source: An optional pipeline source processor. source: An optional pipeline source processor.
sink: An optional pipeline sink processor. sink: An optional pipeline sink processor.
""" """
@@ -116,7 +116,7 @@ class Pipeline(BasePipeline):
# downstream outside of the pipeline. # downstream outside of the pipeline.
self._source = source or PipelineSource(self.push_frame, name=f"{self}::Source") self._source = source or PipelineSource(self.push_frame, name=f"{self}::Source")
self._sink = sink or PipelineSink(self.push_frame, name=f"{self}::Sink") self._sink = sink or PipelineSink(self.push_frame, name=f"{self}::Sink")
self._processors: list[FrameProcessor] = [self._source] + processors + [self._sink] self._processors: list[FrameProcessor] = [self._source, *processors, self._sink]
self._link_processors() self._link_processors()

View File

@@ -6,6 +6,7 @@
"""Service switcher for switching between different services at runtime, with different switching strategies.""" """Service switcher for switching between different services at runtime, with different switching strategies."""
from collections.abc import Sequence
from typing import Any, Generic, TypeVar from typing import Any, Generic, TypeVar
from loguru import logger from loguru import logger
@@ -42,7 +43,7 @@ class ServiceSwitcherStrategy(BaseObject):
... ...
""" """
def __init__(self, services: list[FrameProcessor]): def __init__(self, services: Sequence[FrameProcessor]):
"""Initialize the service switcher strategy with a list of services. """Initialize the service switcher strategy with a list of services.
Note: Note:
@@ -56,7 +57,7 @@ class ServiceSwitcherStrategy(BaseObject):
if len(services) == 0: if len(services) == 0:
raise Exception(f"ServiceSwitcherStrategy needs at least one service") raise Exception(f"ServiceSwitcherStrategy needs at least one service")
self._services = services self._services = list(services)
self._active_service = services[0] self._active_service = services[0]
self._register_event_handler("on_service_switched") self._register_event_handler("on_service_switched")
@@ -223,7 +224,7 @@ class ServiceSwitcher(ParallelPipeline, Generic[StrategyType]):
def __init__( def __init__(
self, self,
services: list[FrameProcessor], services: Sequence[FrameProcessor],
strategy_type: type[StrategyType] = ServiceSwitcherStrategyManual, strategy_type: type[StrategyType] = ServiceSwitcherStrategyManual,
): ):
"""Initialize the service switcher with a list of services and a switching strategy. """Initialize the service switcher with a list of services and a switching strategy.
@@ -235,7 +236,7 @@ class ServiceSwitcher(ParallelPipeline, Generic[StrategyType]):
""" """
_strategy = strategy_type(services) _strategy = strategy_type(services)
super().__init__(*self._make_pipeline_definitions(services, _strategy)) super().__init__(*self._make_pipeline_definitions(services, _strategy))
self._services = services self._services = list(services)
self._strategy = _strategy self._strategy = _strategy
@property @property
@@ -250,7 +251,7 @@ class ServiceSwitcher(ParallelPipeline, Generic[StrategyType]):
@staticmethod @staticmethod
def _make_pipeline_definitions( def _make_pipeline_definitions(
services: list[FrameProcessor], strategy: ServiceSwitcherStrategy services: Sequence[FrameProcessor], strategy: ServiceSwitcherStrategy
) -> list[Any]: ) -> list[Any]:
pipelines = [] pipelines = []
for service in services: for service in services:

View File

@@ -742,7 +742,7 @@ class PipelineTask(BasePipelineTask):
await self._observer.cleanup() await self._observer.cleanup()
# End conversation tracing if it's active - this will also close any active turn span # End conversation tracing if it's active - this will also close any active turn span
if self._enable_tracing and hasattr(self, "_turn_trace_observer"): if self._enable_tracing and self._turn_trace_observer:
self._turn_trace_observer.end_conversation_tracing() self._turn_trace_observer.end_conversation_tracing()
# Cleanup pipeline processors. # Cleanup pipeline processors.

View File

@@ -173,6 +173,8 @@ class TaskObserver(BaseObserver):
return proxies return proxies
async def _send_to_proxy(self, data: Any): async def _send_to_proxy(self, data: Any):
if not self._proxies:
return
for proxy in self._proxies.values(): for proxy in self._proxies.values():
await proxy.queue.put(data) await proxy.queue.put(data)