Creating the concept of a group_id for the function calls.

This commit is contained in:
filipi87
2026-04-01 12:05:09 -03:00
parent 2458b9d42b
commit 63bbfc3b27
3 changed files with 27 additions and 3 deletions

View File

@@ -1921,6 +1921,9 @@ class FunctionCallInProgressFrame(ControlFrame, UninterruptibleFrame):
is_async: Whether this function call runs asynchronously. When True, is_async: Whether this function call runs asynchronously. When True,
the LLM continues the conversation immediately without waiting for the LLM continues the conversation immediately without waiting for
the result. The result is injected later via a developer message. the result. The result is injected later via a developer message.
group_id: Identifier shared by all function calls originating from the
same LLM response batch. Used to determine when the last call in a
group completes so the LLM can be triggered exactly once.
""" """
function_name: str function_name: str
@@ -1928,6 +1931,7 @@ class FunctionCallInProgressFrame(ControlFrame, UninterruptibleFrame):
arguments: Any arguments: Any
cancel_on_interruption: bool = False cancel_on_interruption: bool = False
is_async: bool = False is_async: bool = False
group_id: Optional[str] = None
@dataclass @dataclass

View File

@@ -1077,6 +1077,8 @@ class LLMAssistantAggregator(LLMContextAggregator):
in_progress_frame = self._function_calls_in_progress[frame.tool_call_id] in_progress_frame = self._function_calls_in_progress[frame.tool_call_id]
is_async = in_progress_frame.is_async if in_progress_frame else False is_async = in_progress_frame.is_async if in_progress_frame else False
group_id = in_progress_frame.group_id if in_progress_frame else None
del self._function_calls_in_progress[frame.tool_call_id] del self._function_calls_in_progress[frame.tool_call_id]
properties = frame.properties properties = frame.properties
@@ -1115,8 +1117,16 @@ class LLMAssistantAggregator(LLMContextAggregator):
# If the frame is indicating we should run the LLM, do it. # If the frame is indicating we should run the LLM, do it.
run_llm = frame.run_llm run_llm = frame.run_llm
else: else:
# If this is the last function call in progress, run the LLM. # Run the LLM when this is the last function call in the group
run_llm = not bool(self._function_calls_in_progress) # to complete. If group_id is set, only consider sibling calls;
# otherwise always execute as soon as we receive the result.
if group_id:
run_llm = not any(
f is not None and f.group_id == group_id
for f in self._function_calls_in_progress.values()
)
else:
run_llm = True
if run_llm and not self._user_speaking: if run_llm and not self._user_speaking:
await self.push_context_frame(FrameDirection.UPSTREAM) await self.push_context_frame(FrameDirection.UPSTREAM)

View File

@@ -7,8 +7,8 @@
"""Base classes for Large Language Model services with function calling support.""" """Base classes for Large Language Model services with function calling support."""
import asyncio import asyncio
import inspect
import json import json
import uuid
import warnings import warnings
from dataclasses import dataclass from dataclasses import dataclass
from typing import ( from typing import (
@@ -151,6 +151,9 @@ class FunctionCallRunnerItem:
arguments: The arguments for the function. arguments: The arguments for the function.
context: The LLM context. context: The LLM context.
run_llm: Optional flag to control LLM execution after function call. run_llm: Optional flag to control LLM execution after function call.
group_id: Shared identifier for all function calls from the same LLM
response batch. Used to trigger the LLM exactly once when the last
call in the group completes.
""" """
registry_item: FunctionCallRegistryItem registry_item: FunctionCallRegistryItem
@@ -159,6 +162,7 @@ class FunctionCallRunnerItem:
arguments: Mapping[str, Any] arguments: Mapping[str, Any]
context: OpenAILLMContext | LLMContext context: OpenAILLMContext | LLMContext
run_llm: Optional[bool] = None run_llm: Optional[bool] = None
group_id: Optional[str] = None
class LLMService(UserTurnCompletionLLMServiceMixin, AIService): class LLMService(UserTurnCompletionLLMServiceMixin, AIService):
@@ -695,6 +699,10 @@ class LLMService(UserTurnCompletionLLMServiceMixin, AIService):
await self.broadcast_frame(FunctionCallsStartedFrame, function_calls=function_calls) await self.broadcast_frame(FunctionCallsStartedFrame, function_calls=function_calls)
# All function calls from the same LLM response share a group_id so the
# aggregator can trigger the LLM exactly once when the last one completes.
group_id = str(uuid.uuid4())
runner_items = [] runner_items = []
for function_call in function_calls: for function_call in function_calls:
if function_call.function_name in self._functions.keys(): if function_call.function_name in self._functions.keys():
@@ -714,6 +722,7 @@ class LLMService(UserTurnCompletionLLMServiceMixin, AIService):
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,
group_id=group_id,
) )
) )
@@ -783,6 +792,7 @@ class LLMService(UserTurnCompletionLLMServiceMixin, AIService):
arguments=runner_item.arguments, arguments=runner_item.arguments,
cancel_on_interruption=item.cancel_on_interruption, cancel_on_interruption=item.cancel_on_interruption,
is_async=item.is_async, is_async=item.is_async,
group_id=runner_item.group_id,
) )
timeout_task: Optional[asyncio.Task] = None timeout_task: Optional[asyncio.Task] = None