LLMService: use a single FunctionCallParams parameter for function calls
This commit is contained in:
@@ -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.
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user