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
- 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
(`LLMUserAggregatorParams.aggregation_timeout`). Sometimes, the STT services
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
- 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.video_*` instead.

View File

@@ -5,8 +5,9 @@
#
import asyncio
import inspect
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
@@ -17,6 +18,7 @@ from pipecat.frames.frames import (
FunctionCallCancelFrame,
FunctionCallInProgressFrame,
FunctionCallResultFrame,
FunctionCallResultProperties,
StartInterruptionFrame,
UserImageRequestFrame,
)
@@ -28,14 +30,55 @@ from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContext
from pipecat.processors.frame_processor import FrameDirection
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
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]
callback: Any # TODO(aleix): add proper typing.
handler: FunctionCallHandler
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):
"""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(
self,
function_name: Optional[str],
callback: Any,
handler: Any,
start_callback=None,
*,
cancel_on_interruption: bool = False,
):
# Registering a function with the function_name set to None will run that callback
# for all functions
self._functions[function_name] = FunctionEntry(
# Registering a function with the function_name set to None will run
# that handler for all functions
self._functions[function_name] = FunctionCallEntry(
function_name=function_name,
callback=callback,
handler=handler,
cancel_on_interruption=cancel_on_interruption,
)
@@ -120,7 +163,7 @@ class LLMService(AIService):
context: OpenAILLMContext,
tool_call_id: str,
function_name: str,
arguments: str,
arguments: Mapping[str, Any],
run_llm: bool = True,
):
if not function_name in self._functions.keys() and not None in self._functions.keys():
@@ -163,7 +206,7 @@ class LLMService(AIService):
context: OpenAILLMContext,
tool_call_id: str,
function_name: str,
arguments: str,
arguments: Mapping[str, Any],
run_llm: bool = True,
):
if function_name in self._functions.keys():
@@ -202,7 +245,9 @@ class LLMService(AIService):
await self.push_frame(progress_frame_upstream, FrameDirection.UPSTREAM)
# 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(
function_name=function_name,
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_upstream, FrameDirection.UPSTREAM)
await entry.callback(
function_name, tool_call_id, arguments, self, context, function_call_result_callback
)
signature = inspect.signature(entry.handler)
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):
cancelled_tasks = set()