Skip to content

"list index out of range" when using an LlmAgent with tools and include_contents="none" inside a ParallelAgent #2404

@barbitoff

Description

@barbitoff

Describe the bug
I have a ParallelAgent, having a bunch of LlmAgents as sub-agents. All sub-agents use include_contents="none" and use some tools. Sometimes, the "list index out of range" error is raised:

  File "/myproject/.venv/lib/python3.13/site-packages/google/adk/agents/llm_agent.py", line 285, in _run_async_impl
    async for event in self._llm_flow.run_async(ctx):
      self.__maybe_save_output_to_state(event)
      yield event
  File "/myproject/.venv/lib/python3.13/site-packages/google/adk/flows/llm_flows/base_llm_flow.py", line 290, in run_async
    async for event in self._run_one_step_async(invocation_context):
      last_event = event
      yield event
  File "/myproject/.venv/lib/python3.13/site-packages/google/adk/flows/llm_flows/base_llm_flow.py", line 306, in _run_one_step_async
    async for event in self._preprocess_async(invocation_context, llm_request):
      yield event
  File "/myproject/.venv/lib/python3.13/site-packages/google/adk/flows/llm_flows/base_llm_flow.py", line 341, in _preprocess_async
    async for event in processor.run_async(invocation_context, llm_request):
      yield event
  File "/myproject/.venv/lib/python3.13/site-packages/google/adk/flows/llm_flows/contents.py", line 55, in run_async
    llm_request.contents = _get_current_turn_contents(
                           ~~~~~~~~~~~~~~~~~~~~~~~~~~^
        invocation_context.branch,
        ^^^^^^^^^^^^^^^^^^^^^^^^^^
        invocation_context.session.events,
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        agent.name,
        ^^^^^^^^^^^
    )
    ^
  File "/myproject/.venv/lib/python3.13/site-packages/google/adk/flows/llm_flows/contents.py", line 290, in _get_current_turn_contents
    return _get_contents(current_branch, events[i:], agent_name)
  File "/myproject/.venv/lib/python3.13/site-packages/google/adk/flows/llm_flows/contents.py", line 248, in _get_contents
    result_events = _rearrange_events_for_latest_function_response(
        filtered_events
    )
  File "/myproject/.venv/lib/python3.13/site-packages/google/adk/flows/llm_flows/contents.py", line 143, in _rearrange_events_for_latest_function_response
    function_calls = events[-2].get_function_calls()
                     ~~~~~~^^^^
IndexError: list index out of range

To Reproduce
Steps to reproduce the behavior:

Run the following script a couple of times (on my machine, it fails with probability ~30% with 4 agents and 90% with 10 agents, as in the script below):

import asyncio
import random
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService
from google.adk.agents import LlmAgent, ParallelAgent
from google.genai.types import Content, Part


async def main():
    def random_number_generator() -> str:
        """
        Tool generating random numbers as strings
        """
        return str(random.randint(1, 100))
    
    default_message = Content(
                    parts=[Part(text="Follow exactly the instruction provided")],
                    role="user",
                )

    def add_first_user_message(callback_context, llm_request):
        """
        A callback allowing to overcome the issue, that with include_contents='none' the agent
        strips also the initial user message, which leads to HTTP 400 from models (Gemini and Claude at least).
        """
        llm_request.contents.insert(0, default_message)

    # create 10 subagents to be run in parallel
    sub_agents = [
        # just a simple agent using a simple tool
        LlmAgent(
            model="gemini-2.0-flash",
            include_contents="none",
            before_model_callback=add_first_user_message,
            name=f"random_number_agent_{i}",
            instruction=(
                "Call the random_number_generator tool 10 times, concatenate the results "
                "and respond with the final string."
            ),
            tools=[random_number_generator],
        )
        for i in range(10)
    ]

    root_agent = ParallelAgent(
        name="parallel_agent",
        sub_agents=sub_agents,
    )

    runner = Runner(
        app_name=f"myapp",
        agent=root_agent,
        session_service=InMemorySessionService(),
    )

    session = await runner.session_service.create_session(
        app_name=runner.app_name,
        user_id="user1",
        state={},
    )

    async for _ in runner.run_async(
        user_id=session.user_id,
        session_id=session.id,
        new_message=default_message,
    ):
        pass

asyncio.run(main())

Of course, in this simplified scenario usage of include_contents="none" is not needed, because agents run in separate invocation context branches, and there are not agents before the ParallelAgent. But in my actual scenario, I have some agents before the ParallelAgent, and I want my parallel agents to be isolated from these preceding agents.

Expected behavior

LlmAgents are always executed successfully.

Desktop (please complete the following information):

  • OS: Ubuntu 24.04 under WSL
  • Python version(python -V): 3.13.3
  • ADK version(pip show google-adk): 1.9.0

Model Information:
Gemini-2.0-flash (on VertexAI), Claude 3.7 (on Bedrock)

Additional context
Presumably, the issue is caused by the following:

The function _get_current_turn_contents() in contents.py:

  • first selects events, related to the current turn by searching the last event from user/another agent and selecting all the events that are newer than this event
  • then it calls _get_contents(), which applies filtering of the events based on the branch

When running multiple agents in parallel, events coming from different agents can interleave. For example, the following list of events may be created:

  1. text event from user,
  2. function call from agent1
  3. function call from agent2
  4. function response from agent1
  5. function response from agent2

According to the current logic in _get_current_turn_contents(), in this case, for agent2, only the last event will be selected as belonging to the current turn. Which then will lead to an error in _rearrange_events_for_latest_function_response() function, because it expects a function_response event to be always preceded by a function_call event, but events[-2] will fail with "list index out of range" as there is only 1 element in the events list.

As a possible fix, _get_current_turn_contents() could be changed to respect the branch when selecting events, belonging to the current turn. In the case, described above, it will allow ignoring events from agent1 and select both function_response and function_call events for agent2.

Metadata

Metadata

Assignees

Labels

core[Component] This issue is related to the core interface and implementation

Type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions