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:
Aleix Conchillo Flaqué
2026-05-13 19:15:33 -07:00
parent 7d28c46a5d
commit ef806163b2
5 changed files with 38 additions and 45 deletions

View File

@@ -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):

View File

@@ -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: