async def handle_function_calls_async(
    invocation_context: InvocationContext,
    function_call_event: Event,
    tools_dict: dict[str, BaseTool],
    filters: Optional[set[str]] = None,
) -> Optional[Event]:
  """Calls the functions and returns the function response event."""
  from ...agents.llm_agent import LlmAgent

  agent = invocation_context.agent
  if not isinstance(agent, LlmAgent):
    return

  function_calls = function_call_event.get_function_calls()

  function_response_events: list[Event] = []
  for function_call in function_calls:
    if filters and function_call.id not in filters:
      continue
    tool, tool_context = _get_tool_and_context(
        invocation_context,
        function_call_event,
        function_call,
        tools_dict,
    )

    with tracer.start_as_current_span(f'execute_tool {tool.name}'):
      # do not use "args" as the variable name, because it is a reserved keyword
      # in python debugger.
      function_args = function_call.args or {}
      function_response: Optional[dict] = None

      for callback in agent.canonical_before_tool_callbacks:
        function_response = callback(
            tool=tool, args=function_args, tool_context=tool_context
        )
        if inspect.isawaitable(function_response):
          function_response = await function_response
        if function_response:
          break

      if not function_response:
        function_response = await __call_tool_async(
            tool, args=function_args, tool_context=tool_context
        )

      for callback in agent.canonical_after_tool_callbacks:
        altered_function_response = callback(
            tool=tool,
            args=function_args,
            tool_context=tool_context,
            tool_response=function_response,
        )
        if inspect.isawaitable(altered_function_response):
          altered_function_response = await altered_function_response
        if altered_function_response is not None:
          function_response = altered_function_response
          break

      if tool.is_long_running:
        # Allow long running function to return None to not provide function response.
        if not function_response:
          continue

      # Builds the function response event.
      function_response_event = __build_response_event(
          tool, function_response, tool_context, invocation_context
      )
      trace_tool_call(
          tool=tool,
          args=function_args,
          function_response_event=function_response_event,
      )
      function_response_events.append(function_response_event)

  if not function_response_events:
    return None
  merged_event = merge_parallel_function_response_events(
      function_response_events
  )

  if len(function_response_events) > 1:
    # this is needed for debug traces of parallel calls
    # individual response with tool.name is traced in __build_response_event
    # (we drop tool.name from span name here as this is merged event)
    with tracer.start_as_current_span('execute_tool (merged)'):
      trace_merged_tool_calls(
          response_event_id=merged_event.id,
          function_response_event=merged_event,
      )
  return merged_event
Calls the functions and returns the function response event.
src/google/adk/flows/llm_flows/functions.py