Tighten the pipeline_task contract for processors and tools
`FrameProcessorSetup.pipeline_task` is now mandatory and `FrameProcessor.pipeline_task` raises if accessed before setup instead of returning `None`. `FunctionCallParams` gains a required `pipeline_task` field and `LLMService._run_function_call` populates it (plus reads `app_resources` directly off the pipeline task). Tests that build a processor or `FunctionCallParams` outside a real pipeline stub it with a `SimpleNamespace`.
This commit is contained in:
@@ -75,10 +75,10 @@ class FrameProcessorSetup:
|
||||
Parameters:
|
||||
clock: The clock instance for timing operations.
|
||||
task_manager: The task manager for handling async operations.
|
||||
observer: Optional observer for monitoring frame processing events.
|
||||
pipeline_task: The :class:`PipelineTask` running this pipeline. Stored
|
||||
on each processor as ``self.pipeline_task`` so processors can
|
||||
reach task-scoped state (e.g. ``self.pipeline_task.app_resources``).
|
||||
observer: Optional observer for monitoring frame processing events.
|
||||
tool_resources: Deprecated. :class:`PipelineTask` continues to populate
|
||||
this with ``app_resources`` so that custom :class:`FrameProcessor`
|
||||
subclasses whose ``setup()`` overrides read ``setup.tool_resources``
|
||||
@@ -93,8 +93,8 @@ class FrameProcessorSetup:
|
||||
|
||||
clock: BaseClock
|
||||
task_manager: BaseTaskManager
|
||||
pipeline_task: PipelineTask
|
||||
observer: BaseObserver | None = None
|
||||
pipeline_task: PipelineTask | None = None
|
||||
tool_resources: Any = None
|
||||
|
||||
def __getattribute__(self, name: str) -> Any:
|
||||
@@ -220,8 +220,9 @@ class FrameProcessor(BaseObject):
|
||||
# Observer
|
||||
self._observer: BaseObserver | None = None
|
||||
|
||||
# Pipeline Task
|
||||
self._pipeline_task: PipelineTask | None = None
|
||||
# Pipeline Task. Populated by ``setup()``; accessing the
|
||||
# ``pipeline_task`` property before setup raises.
|
||||
self._pipeline_task: PipelineTask | None = None # set in setup()
|
||||
|
||||
# Other properties
|
||||
self._enable_metrics = False
|
||||
@@ -366,7 +367,7 @@ class FrameProcessor(BaseObject):
|
||||
return self._report_only_initial_ttfb
|
||||
|
||||
@property
|
||||
def pipeline_task(self) -> PipelineTask | None:
|
||||
def pipeline_task(self) -> PipelineTask:
|
||||
"""Get the :class:`PipelineTask` this processor is running in.
|
||||
|
||||
Provides access to task-scoped state from inside a processor — most
|
||||
@@ -374,11 +375,10 @@ class FrameProcessor(BaseObject):
|
||||
shared bag of resources (DB handles, clients, feature flags, etc.).
|
||||
|
||||
Returns:
|
||||
The :class:`PipelineTask` instance that set up this processor,
|
||||
or ``None`` if the processor has not yet been set up by one
|
||||
(for example, before the task has started, or when the processor
|
||||
was instantiated in isolation).
|
||||
The :class:`PipelineTask` instance that set up this processor.
|
||||
"""
|
||||
if not self._pipeline_task:
|
||||
raise Exception(f"{self} pipeline task is still not set.")
|
||||
return self._pipeline_task
|
||||
|
||||
def processors_with_metrics(self):
|
||||
|
||||
@@ -15,6 +15,7 @@ import warnings
|
||||
from collections.abc import Awaitable, Callable, Mapping, Sequence
|
||||
from dataclasses import dataclass
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Generic,
|
||||
Protocol,
|
||||
@@ -70,6 +71,10 @@ from pipecat.utils.context.llm_context_summarization import (
|
||||
LLMContextSummarizationUtil,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pipecat.pipeline.task import PipelineTask
|
||||
|
||||
|
||||
# Type alias for a callable that handles LLM function calls.
|
||||
FunctionCallHandler = Callable[["FunctionCallParams"], Awaitable[None]]
|
||||
|
||||
@@ -126,6 +131,7 @@ class FunctionCallParams:
|
||||
# treat it invariantly, rejecting `LLMService[XAdapter]` at the call
|
||||
# sites that build FunctionCallParams.
|
||||
llm: LLMService[Any]
|
||||
pipeline_task: PipelineTask
|
||||
context: LLMContext
|
||||
result_callback: FunctionCallResultCallback
|
||||
app_resources: Any = None
|
||||
@@ -947,9 +953,6 @@ class LLMService(UserTurnCompletionLLMServiceMixin, AIService, Generic[TAdapter]
|
||||
# it starts would leave the coroutine in a "never awaited" state.
|
||||
await asyncio.sleep(0)
|
||||
|
||||
# _pipeline_task may be unset when the service is driven without a PipelineTask.
|
||||
app_resources = self._pipeline_task.app_resources if self._pipeline_task else None
|
||||
|
||||
try:
|
||||
if isinstance(item.handler, DirectFunctionWrapper):
|
||||
# Handler is a DirectFunctionWrapper
|
||||
@@ -960,9 +963,10 @@ class LLMService(UserTurnCompletionLLMServiceMixin, AIService, Generic[TAdapter]
|
||||
tool_call_id=runner_item.tool_call_id,
|
||||
arguments=runner_item.arguments,
|
||||
llm=self,
|
||||
pipeline_task=self.pipeline_task,
|
||||
context=runner_item.context,
|
||||
result_callback=function_call_result_callback,
|
||||
app_resources=app_resources,
|
||||
app_resources=self.pipeline_task.app_resources,
|
||||
),
|
||||
)
|
||||
else:
|
||||
@@ -972,9 +976,10 @@ class LLMService(UserTurnCompletionLLMServiceMixin, AIService, Generic[TAdapter]
|
||||
tool_call_id=runner_item.tool_call_id,
|
||||
arguments=runner_item.arguments,
|
||||
llm=self,
|
||||
pipeline_task=self.pipeline_task,
|
||||
context=runner_item.context,
|
||||
result_callback=function_call_result_callback,
|
||||
app_resources=app_resources,
|
||||
app_resources=self.pipeline_task.app_resources,
|
||||
)
|
||||
await item.handler(params)
|
||||
except Exception as e:
|
||||
|
||||
Reference in New Issue
Block a user