LLMService: use a single FunctionCallParams parameter for function calls

This commit is contained in:
Aleix Conchillo Flaqué
2025-04-25 08:58:06 -07:00
parent 1d863ee7de
commit 944bc23135
2 changed files with 88 additions and 14 deletions

View File

@@ -25,6 +25,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed ### Changed
- Function calls now receive a single parameter `FunctionCallParams` instead of
`(function_name, tool_call_id, args, llm, context, result_callback)` which is
now deprecated.
- Changed the user aggregator timeout for late transcriptions from 1.0s to 0.5s - Changed the user aggregator timeout for late transcriptions from 1.0s to 0.5s
(`LLMUserAggregatorParams.aggregation_timeout`). Sometimes, the STT services (`LLMUserAggregatorParams.aggregation_timeout`). Sometimes, the STT services
might give us more than one transcription which could come after the user might give us more than one transcription which could come after the user
@@ -52,6 +56,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Deprecated ### Deprecated
- Function calls with parameters `(function_name, tool_call_id, args, llm,
context, result_callback)` are deprectated, use a single `FunctionCallParams`
parameter instead.
- `TransportParams.camera_*` parameters are now deprecated, use - `TransportParams.camera_*` parameters are now deprecated, use
`TransportParams.video_*` instead. `TransportParams.video_*` instead.

View File

@@ -5,8 +5,9 @@
# #
import asyncio import asyncio
import inspect
from dataclasses import dataclass from dataclasses import dataclass
from typing import Any, Optional, Set, Tuple, Type from typing import Any, Awaitable, Callable, Mapping, Optional, Protocol, Set, Tuple, Type
from loguru import logger from loguru import logger
@@ -17,6 +18,7 @@ from pipecat.frames.frames import (
FunctionCallCancelFrame, FunctionCallCancelFrame,
FunctionCallInProgressFrame, FunctionCallInProgressFrame,
FunctionCallResultFrame, FunctionCallResultFrame,
FunctionCallResultProperties,
StartInterruptionFrame, StartInterruptionFrame,
UserImageRequestFrame, UserImageRequestFrame,
) )
@@ -28,14 +30,55 @@ from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContext
from pipecat.processors.frame_processor import FrameDirection from pipecat.processors.frame_processor import FrameDirection
from pipecat.services.ai_service import AIService from pipecat.services.ai_service import AIService
# Type alias for a callable that handles LLM function calls.
FunctionCallHandler = Callable[["FunctionCallParams"], Awaitable[None]]
# Type alias for a callback function that handles the result of an LLM function call.
class FunctionCallResultCallback(Protocol):
async def __call__(
self, result: Any, *, properties: Optional[FunctionCallResultProperties] = None
) -> None: ...
@dataclass @dataclass
class FunctionEntry: class FunctionCallEntry:
"""Represents an internal entry for a function call.
Attributes:
function_name (Optional[str]): The name of the function.
handler (FunctionCallHandler): The handler for processing function call parameters.
cancel_on_interruption (bool): Flag indicating whether to cancel the call on interruption.
"""
function_name: Optional[str] function_name: Optional[str]
callback: Any # TODO(aleix): add proper typing. handler: FunctionCallHandler
cancel_on_interruption: bool cancel_on_interruption: bool
@dataclass
class FunctionCallParams:
"""Parameters for a function call.
Attributes:
function_name (str): The name of the function being called.
arguments (Mapping[str, Any]): The arguments for the function.
tool_call_id (str): A unique identifier for the function call.
llm (LLMService): The LLMService instance being used.
context (OpenAILLMContext): The LLM context.
result_callback (FunctionCallResultCallback): Callback to handle the result of the function call.
"""
function_name: str
tool_call_id: str
arguments: Mapping[str, Any]
llm: "LLMService"
context: OpenAILLMContext
result_callback: FunctionCallResultCallback
class LLMService(AIService): class LLMService(AIService):
"""This class is a no-op but serves as a base class for LLM services.""" """This class is a no-op but serves as a base class for LLM services."""
@@ -78,16 +121,16 @@ class LLMService(AIService):
def register_function( def register_function(
self, self,
function_name: Optional[str], function_name: Optional[str],
callback: Any, handler: Any,
start_callback=None, start_callback=None,
*, *,
cancel_on_interruption: bool = False, cancel_on_interruption: bool = False,
): ):
# Registering a function with the function_name set to None will run that callback # Registering a function with the function_name set to None will run
# for all functions # that handler for all functions
self._functions[function_name] = FunctionEntry( self._functions[function_name] = FunctionCallEntry(
function_name=function_name, function_name=function_name,
callback=callback, handler=handler,
cancel_on_interruption=cancel_on_interruption, cancel_on_interruption=cancel_on_interruption,
) )
@@ -120,7 +163,7 @@ class LLMService(AIService):
context: OpenAILLMContext, context: OpenAILLMContext,
tool_call_id: str, tool_call_id: str,
function_name: str, function_name: str,
arguments: str, arguments: Mapping[str, Any],
run_llm: bool = True, run_llm: bool = True,
): ):
if not function_name in self._functions.keys() and not None in self._functions.keys(): if not function_name in self._functions.keys() and not None in self._functions.keys():
@@ -163,7 +206,7 @@ class LLMService(AIService):
context: OpenAILLMContext, context: OpenAILLMContext,
tool_call_id: str, tool_call_id: str,
function_name: str, function_name: str,
arguments: str, arguments: Mapping[str, Any],
run_llm: bool = True, run_llm: bool = True,
): ):
if function_name in self._functions.keys(): if function_name in self._functions.keys():
@@ -202,7 +245,9 @@ class LLMService(AIService):
await self.push_frame(progress_frame_upstream, FrameDirection.UPSTREAM) await self.push_frame(progress_frame_upstream, FrameDirection.UPSTREAM)
# Define a callback function that pushes a FunctionCallResultFrame upstream & downstream. # Define a callback function that pushes a FunctionCallResultFrame upstream & downstream.
async def function_call_result_callback(result, *, properties=None): async def function_call_result_callback(
result: Any, *, properties: Optional[FunctionCallResultProperties] = None
):
result_frame_downstream = FunctionCallResultFrame( result_frame_downstream = FunctionCallResultFrame(
function_name=function_name, function_name=function_name,
tool_call_id=tool_call_id, tool_call_id=tool_call_id,
@@ -221,9 +266,30 @@ class LLMService(AIService):
await self.push_frame(result_frame_downstream, FrameDirection.DOWNSTREAM) await self.push_frame(result_frame_downstream, FrameDirection.DOWNSTREAM)
await self.push_frame(result_frame_upstream, FrameDirection.UPSTREAM) await self.push_frame(result_frame_upstream, FrameDirection.UPSTREAM)
await entry.callback( signature = inspect.signature(entry.handler)
function_name, tool_call_id, arguments, self, context, function_call_result_callback if len(signature.parameters) > 1:
) import warnings
with warnings.catch_warnings():
warnings.simplefilter("always")
warnings.warn(
"Function calls with parameters `(function_name, tool_call_id, arguments, llm, context, result_callback)` are deprecated, use a single `FunctionCallParams` parameter instead.",
DeprecationWarning,
)
await entry.handler(
function_name, tool_call_id, arguments, self, context, function_call_result_callback
)
else:
params = FunctionCallParams(
function_name=function_name,
tool_call_id=tool_call_id,
arguments=arguments,
llm=self,
context=context,
result_callback=function_call_result_callback,
)
await entry.handler(params)
async def _cancel_function_call(self, function_name: str): async def _cancel_function_call(self, function_name: str):
cancelled_tasks = set() cancelled_tasks = set()