Providing a way to defer the function call results.
This commit is contained in:
@@ -1466,6 +1466,7 @@ class UserImageRequestFrame(SystemFrame):
|
|||||||
video_source: Specific video source to capture from.
|
video_source: Specific video source to capture from.
|
||||||
function_name: Name of function that generated this request (if any).
|
function_name: Name of function that generated this request (if any).
|
||||||
tool_call_id: Tool call ID if generated by function call (if any).
|
tool_call_id: Tool call ID if generated by function call (if any).
|
||||||
|
result_callback: Optional callback to invoke when the image is retrieved.
|
||||||
context: [DEPRECATED] Optional context for the image request.
|
context: [DEPRECATED] Optional context for the image request.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -1475,6 +1476,7 @@ class UserImageRequestFrame(SystemFrame):
|
|||||||
video_source: Optional[str] = None
|
video_source: Optional[str] = None
|
||||||
function_name: Optional[str] = None
|
function_name: Optional[str] = None
|
||||||
tool_call_id: Optional[str] = None
|
tool_call_id: Optional[str] = None
|
||||||
|
result_callback: Optional[Any] = None
|
||||||
context: Optional[Any] = None
|
context: Optional[Any] = None
|
||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
|
|||||||
@@ -1042,6 +1042,11 @@ class LLMAssistantContextAggregator(LLMContextResponseAggregator):
|
|||||||
|
|
||||||
del self._function_calls_in_progress[frame.request.tool_call_id]
|
del self._function_calls_in_progress[frame.request.tool_call_id]
|
||||||
|
|
||||||
|
# Call the result_callback if provided. This signals that the image
|
||||||
|
# has been retrieved and the function call can now complete.
|
||||||
|
if frame.request and frame.request.result_callback:
|
||||||
|
await frame.request.result_callback(None)
|
||||||
|
|
||||||
await self.handle_user_image_frame(frame)
|
await self.handle_user_image_frame(frame)
|
||||||
await self.push_aggregation()
|
await self.push_aggregation()
|
||||||
await self.push_context_frame(FrameDirection.UPSTREAM)
|
await self.push_context_frame(FrameDirection.UPSTREAM)
|
||||||
|
|||||||
@@ -941,16 +941,18 @@ class LLMAssistantAggregator(LLMContextAggregator):
|
|||||||
async def _handle_user_image_frame(self, frame: UserImageRawFrame):
|
async def _handle_user_image_frame(self, frame: UserImageRawFrame):
|
||||||
image_appended = False
|
image_appended = False
|
||||||
|
|
||||||
# Check if this image is a result of a function call if so, let's cache.
|
# Check if this image is a result of a function call.
|
||||||
# TODO(aleix): The function call might have already been executed
|
|
||||||
# because FunctionCallResultFrame was just faster, in that case we just
|
|
||||||
# push the context frame now.
|
|
||||||
if (
|
if (
|
||||||
frame.request
|
frame.request
|
||||||
and frame.request.tool_call_id
|
and frame.request.tool_call_id
|
||||||
and frame.request.tool_call_id in self._function_calls_in_progress
|
and frame.request.tool_call_id in self._function_calls_in_progress
|
||||||
):
|
):
|
||||||
self._function_calls_image_results[frame.request.tool_call_id] = frame
|
self._function_calls_image_results[frame.request.tool_call_id] = frame
|
||||||
|
|
||||||
|
# Call the result_callback if provided. This signals that the image
|
||||||
|
# has been retrieved and the function call can now complete.
|
||||||
|
if frame.request.result_callback:
|
||||||
|
await frame.request.result_callback(None)
|
||||||
else:
|
else:
|
||||||
image_appended = await self._maybe_append_image_to_context(frame)
|
image_appended = await self._maybe_append_image_to_context(frame)
|
||||||
|
|
||||||
|
|||||||
@@ -170,17 +170,22 @@ class LLMService(AIService):
|
|||||||
# However, subclasses should override this with a more specific adapter when necessary.
|
# However, subclasses should override this with a more specific adapter when necessary.
|
||||||
adapter_class: Type[BaseLLMAdapter] = OpenAILLMAdapter
|
adapter_class: Type[BaseLLMAdapter] = OpenAILLMAdapter
|
||||||
|
|
||||||
def __init__(self, run_in_parallel: bool = True, **kwargs):
|
def __init__(
|
||||||
|
self, run_in_parallel: bool = True, function_call_timeout_secs: float = 10.0, **kwargs
|
||||||
|
):
|
||||||
"""Initialize the LLM service.
|
"""Initialize the LLM service.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
run_in_parallel: Whether to run function calls in parallel or sequentially.
|
run_in_parallel: Whether to run function calls in parallel or sequentially.
|
||||||
Defaults to True.
|
Defaults to True.
|
||||||
|
function_call_timeout_secs: Timeout in seconds for deferred function calls.
|
||||||
|
Defaults to 10.0 seconds.
|
||||||
**kwargs: Additional arguments passed to the parent AIService.
|
**kwargs: Additional arguments passed to the parent AIService.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
self._run_in_parallel = run_in_parallel
|
self._run_in_parallel = run_in_parallel
|
||||||
|
self._function_call_timeout_secs = function_call_timeout_secs
|
||||||
self._start_callbacks = {}
|
self._start_callbacks = {}
|
||||||
self._adapter = self.adapter_class()
|
self._adapter = self.adapter_class()
|
||||||
self._functions: Dict[Optional[str], FunctionCallRegistryItem] = {}
|
self._functions: Dict[Optional[str], FunctionCallRegistryItem] = {}
|
||||||
@@ -596,14 +601,26 @@ class LLMService(AIService):
|
|||||||
cancel_on_interruption=item.cancel_on_interruption,
|
cancel_on_interruption=item.cancel_on_interruption,
|
||||||
)
|
)
|
||||||
|
|
||||||
callback_executed = False
|
# Start a timeout task for deferred function calls
|
||||||
|
async def timeout_handler():
|
||||||
|
await asyncio.sleep(self._function_call_timeout_secs)
|
||||||
|
logger.warning(
|
||||||
|
f"{self} Function call [{runner_item.function_name}:{runner_item.tool_call_id}] timed out after {self._function_call_timeout_secs} seconds"
|
||||||
|
)
|
||||||
|
await function_call_result_callback(None)
|
||||||
|
|
||||||
|
timeout_task = self.create_task(timeout_handler())
|
||||||
|
|
||||||
# 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(
|
async def function_call_result_callback(
|
||||||
result: Any, *, properties: Optional[FunctionCallResultProperties] = None
|
result: Any, *, properties: Optional[FunctionCallResultProperties] = None
|
||||||
):
|
):
|
||||||
nonlocal callback_executed
|
nonlocal timeout_task
|
||||||
callback_executed = True
|
|
||||||
|
# Cancel timeout task if it exists
|
||||||
|
if timeout_task and not timeout_task.done():
|
||||||
|
await self.cancel_task(timeout_task)
|
||||||
|
|
||||||
await self.broadcast_frame(
|
await self.broadcast_frame(
|
||||||
FunctionCallResultFrame,
|
FunctionCallResultFrame,
|
||||||
function_name=runner_item.function_name,
|
function_name=runner_item.function_name,
|
||||||
@@ -653,9 +670,6 @@ class LLMService(AIService):
|
|||||||
error_message = f"Error executing function call [{runner_item.function_name}]: {e}"
|
error_message = f"Error executing function call [{runner_item.function_name}]: {e}"
|
||||||
logger.error(f"{self} {error_message}")
|
logger.error(f"{self} {error_message}")
|
||||||
await self.push_error(error_msg=error_message, exception=e, fatal=False)
|
await self.push_error(error_msg=error_message, exception=e, fatal=False)
|
||||||
finally:
|
|
||||||
if not callback_executed:
|
|
||||||
await function_call_result_callback(None)
|
|
||||||
|
|
||||||
async def _cancel_function_call(self, function_name: Optional[str]):
|
async def _cancel_function_call(self, function_name: Optional[str]):
|
||||||
cancelled_tasks = set()
|
cancelled_tasks = set()
|
||||||
|
|||||||
Reference in New Issue
Block a user