LLMService: add new FunctionCallsStartedFrame
This commit is contained in:
@@ -9,6 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
|
- Added a new frame `FunctionCallsStartedFrame`. This frame is pushed both
|
||||||
|
upstream and downstream from the LLM service to indicate that one or more
|
||||||
|
function calls are going to be executed.
|
||||||
|
|
||||||
- Added LLM services `on_function_calls_started` event. This event will be
|
- Added LLM services `on_function_calls_started` event. This event will be
|
||||||
triggered when the LLM service receives function calls from the model and is
|
triggered when the LLM service receives function calls from the model and is
|
||||||
going to start executing them.
|
going to start executing them.
|
||||||
|
|||||||
@@ -667,6 +667,32 @@ class MetricsFrame(SystemFrame):
|
|||||||
data: List[MetricsData]
|
data: List[MetricsData]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class FunctionCallFromLLM:
|
||||||
|
"""Represents a function call returned by the LLM to be registered for execution.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
function_name (str): The name of the function.
|
||||||
|
tool_call_id (str): A unique identifier for the function call.
|
||||||
|
arguments (Mapping[str, Any]): The arguments for the function.
|
||||||
|
context (OpenAILLMContext): The LLM context.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
function_name: str
|
||||||
|
tool_call_id: str
|
||||||
|
arguments: Mapping[str, Any]
|
||||||
|
context: Any
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class FunctionCallsStartedFrame(SystemFrame):
|
||||||
|
"""A frame signaling that one or more function call execution is going to
|
||||||
|
start."""
|
||||||
|
|
||||||
|
function_calls: Sequence[FunctionCallFromLLM]
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class FunctionCallInProgressFrame(SystemFrame):
|
class FunctionCallInProgressFrame(SystemFrame):
|
||||||
"""A frame signaling that a function call is in progress."""
|
"""A frame signaling that a function call is in progress."""
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ from pipecat.frames.frames import (
|
|||||||
FunctionCallCancelFrame,
|
FunctionCallCancelFrame,
|
||||||
FunctionCallInProgressFrame,
|
FunctionCallInProgressFrame,
|
||||||
FunctionCallResultFrame,
|
FunctionCallResultFrame,
|
||||||
|
FunctionCallsStartedFrame,
|
||||||
InterimTranscriptionFrame,
|
InterimTranscriptionFrame,
|
||||||
LLMFullResponseEndFrame,
|
LLMFullResponseEndFrame,
|
||||||
LLMFullResponseStartFrame,
|
LLMFullResponseStartFrame,
|
||||||
@@ -500,7 +501,7 @@ class LLMAssistantContextAggregator(LLMContextResponseAggregator):
|
|||||||
self._params.expect_stripped_words = kwargs["expect_stripped_words"]
|
self._params.expect_stripped_words = kwargs["expect_stripped_words"]
|
||||||
|
|
||||||
self._started = 0
|
self._started = 0
|
||||||
self._function_calls_in_progress: Dict[str, FunctionCallInProgressFrame] = {}
|
self._function_calls_in_progress: Dict[str, Optional[FunctionCallInProgressFrame]] = {}
|
||||||
self._context_updated_tasks: Set[asyncio.Task] = set()
|
self._context_updated_tasks: Set[asyncio.Task] = set()
|
||||||
|
|
||||||
async def handle_aggregation(self, aggregation: str):
|
async def handle_aggregation(self, aggregation: str):
|
||||||
@@ -538,6 +539,8 @@ class LLMAssistantContextAggregator(LLMContextResponseAggregator):
|
|||||||
self.set_tools(frame.tools)
|
self.set_tools(frame.tools)
|
||||||
elif isinstance(frame, LLMSetToolChoiceFrame):
|
elif isinstance(frame, LLMSetToolChoiceFrame):
|
||||||
self.set_tool_choice(frame.tool_choice)
|
self.set_tool_choice(frame.tool_choice)
|
||||||
|
elif isinstance(frame, FunctionCallsStartedFrame):
|
||||||
|
await self._handle_function_calls_started(frame)
|
||||||
elif isinstance(frame, FunctionCallInProgressFrame):
|
elif isinstance(frame, FunctionCallInProgressFrame):
|
||||||
await self._handle_function_call_in_progress(frame)
|
await self._handle_function_call_in_progress(frame)
|
||||||
elif isinstance(frame, FunctionCallResultFrame):
|
elif isinstance(frame, FunctionCallResultFrame):
|
||||||
@@ -574,6 +577,12 @@ class LLMAssistantContextAggregator(LLMContextResponseAggregator):
|
|||||||
self._started = 0
|
self._started = 0
|
||||||
self.reset()
|
self.reset()
|
||||||
|
|
||||||
|
async def _handle_function_calls_started(self, frame: FunctionCallsStartedFrame):
|
||||||
|
function_names = [f"{f.function_name}:{f.tool_call_id}" for f in frame.function_calls]
|
||||||
|
logger.debug(f"{self} FunctionCallsStartedFrame: {function_names}")
|
||||||
|
for function_call in frame.function_calls:
|
||||||
|
self._function_calls_in_progress[function_call.tool_call_id] = None
|
||||||
|
|
||||||
async def _handle_function_call_in_progress(self, frame: FunctionCallInProgressFrame):
|
async def _handle_function_call_in_progress(self, frame: FunctionCallInProgressFrame):
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"{self} FunctionCallInProgressFrame: [{frame.function_name}:{frame.tool_call_id}]"
|
f"{self} FunctionCallInProgressFrame: [{frame.function_name}:{frame.tool_call_id}]"
|
||||||
@@ -597,9 +606,10 @@ class LLMAssistantContextAggregator(LLMContextResponseAggregator):
|
|||||||
|
|
||||||
await self.handle_function_call_result(frame)
|
await self.handle_function_call_result(frame)
|
||||||
|
|
||||||
|
run_llm = False
|
||||||
|
|
||||||
# Run inference if the function call result requires it.
|
# Run inference if the function call result requires it.
|
||||||
if frame.result:
|
if frame.result:
|
||||||
run_llm = False
|
|
||||||
if properties and properties.run_llm is not None:
|
if properties and properties.run_llm is not None:
|
||||||
# If the tool call result has a run_llm property, use it.
|
# If the tool call result has a run_llm property, use it.
|
||||||
run_llm = properties.run_llm
|
run_llm = properties.run_llm
|
||||||
@@ -610,8 +620,8 @@ class LLMAssistantContextAggregator(LLMContextResponseAggregator):
|
|||||||
# If this is the last function call in progress, run the LLM.
|
# If this is the last function call in progress, run the LLM.
|
||||||
run_llm = not bool(self._function_calls_in_progress)
|
run_llm = not bool(self._function_calls_in_progress)
|
||||||
|
|
||||||
if run_llm:
|
if run_llm:
|
||||||
await self.push_context_frame(FrameDirection.UPSTREAM)
|
await self.push_context_frame(FrameDirection.UPSTREAM)
|
||||||
|
|
||||||
# Call the `on_context_updated` callback once the function call result
|
# Call the `on_context_updated` callback once the function call result
|
||||||
# is added to the context. Also, run this in a separate task to make
|
# is added to the context. Also, run this in a separate task to make
|
||||||
|
|||||||
@@ -18,9 +18,11 @@ from pipecat.frames.frames import (
|
|||||||
EndFrame,
|
EndFrame,
|
||||||
Frame,
|
Frame,
|
||||||
FunctionCallCancelFrame,
|
FunctionCallCancelFrame,
|
||||||
|
FunctionCallFromLLM,
|
||||||
FunctionCallInProgressFrame,
|
FunctionCallInProgressFrame,
|
||||||
FunctionCallResultFrame,
|
FunctionCallResultFrame,
|
||||||
FunctionCallResultProperties,
|
FunctionCallResultProperties,
|
||||||
|
FunctionCallsStartedFrame,
|
||||||
StartFrame,
|
StartFrame,
|
||||||
StartInterruptionFrame,
|
StartInterruptionFrame,
|
||||||
UserImageRequestFrame,
|
UserImageRequestFrame,
|
||||||
@@ -66,24 +68,6 @@ class FunctionCallParams:
|
|||||||
result_callback: FunctionCallResultCallback
|
result_callback: FunctionCallResultCallback
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class FunctionCallFromLLM:
|
|
||||||
"""Represents a function call returned by the LLM to be registered for execution.
|
|
||||||
|
|
||||||
Attributes:
|
|
||||||
function_name (str): The name of the function.
|
|
||||||
tool_call_id (str): A unique identifier for the function call.
|
|
||||||
arguments (Mapping[str, Any]): The arguments for the function.
|
|
||||||
context (OpenAILLMContext): The LLM context.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
function_name: str
|
|
||||||
tool_call_id: str
|
|
||||||
arguments: Mapping[str, Any]
|
|
||||||
context: OpenAILLMContext
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class FunctionCallRegistryItem:
|
class FunctionCallRegistryItem:
|
||||||
"""Represents an entry in our function call registry. This is what the user
|
"""Represents an entry in our function call registry. This is what the user
|
||||||
@@ -238,8 +222,13 @@ class LLMService(AIService):
|
|||||||
async def run_function_calls(self, function_calls: Sequence[FunctionCallFromLLM]):
|
async def run_function_calls(self, function_calls: Sequence[FunctionCallFromLLM]):
|
||||||
await self._call_event_handler("on_function_calls_started", function_calls)
|
await self._call_event_handler("on_function_calls_started", function_calls)
|
||||||
|
|
||||||
total_function_calls = len(function_calls)
|
# Push frame both downstream and upstream
|
||||||
for index, function_call in enumerate(function_calls):
|
started_frame_downstream = FunctionCallsStartedFrame(function_calls=function_calls)
|
||||||
|
started_frame_upstream = FunctionCallsStartedFrame(function_calls=function_calls)
|
||||||
|
await self.push_frame(started_frame_downstream, FrameDirection.DOWNSTREAM)
|
||||||
|
await self.push_frame(started_frame_upstream, FrameDirection.UPSTREAM)
|
||||||
|
|
||||||
|
for function_call in function_calls:
|
||||||
if function_call.function_name in self._functions.keys():
|
if function_call.function_name in self._functions.keys():
|
||||||
item = self._functions[function_call.function_name]
|
item = self._functions[function_call.function_name]
|
||||||
elif None in self._functions.keys():
|
elif None in self._functions.keys():
|
||||||
@@ -250,20 +239,12 @@ class LLMService(AIService):
|
|||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# If we are not running in parallel, run inference on the last
|
|
||||||
# function call. Otherwise, the last function call to finish is the
|
|
||||||
# one that will run the inference.
|
|
||||||
run_llm = None
|
|
||||||
if not self._run_in_parallel:
|
|
||||||
run_llm = index == total_function_calls - 1
|
|
||||||
|
|
||||||
runner_item = FunctionCallRunnerItem(
|
runner_item = FunctionCallRunnerItem(
|
||||||
registry_item=item,
|
registry_item=item,
|
||||||
function_name=function_call.function_name,
|
function_name=function_call.function_name,
|
||||||
tool_call_id=function_call.tool_call_id,
|
tool_call_id=function_call.tool_call_id,
|
||||||
arguments=function_call.arguments,
|
arguments=function_call.arguments,
|
||||||
context=function_call.context,
|
context=function_call.context,
|
||||||
run_llm=run_llm,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if self._run_in_parallel:
|
if self._run_in_parallel:
|
||||||
|
|||||||
Reference in New Issue
Block a user