Skip to content

An assistant message with 'tool _calls' must be followed by tool messages responding to each 'tool _call_id' #153

Closed

Description

@marianocodes

Describe the bug
I have a very simple agent that can get information about candidates and a role.

Getting info about the candidate works
Getting info about the role works
But getting both and compare the results, doesn't work.

I get this error

litellm.BadRequestError: OpenAlException - An assistant message with 'tool _calls' must be followed by tool messages responding to each 'tool _call_id'. The following tool_call_ids did not have response messages: call_kOvB3zQZ5K0dХQfrzPoQmOAj

To Reproduce
Steps to reproduce the behavior:

  1. Run the flow below and ask "tell me whether carlos or juan is better for the role"
from google.adk.agents import Agent
from google.adk.models.lite_llm import LiteLlm

def get_candidate_info(names: list[str]) -> str:
    """Provide information about one or more candidates"""
    if len(names) == 0:
        return "No candidate names provided"
    
    if len(names) == 1:
        return f"Juan is great Full-stack engineer with 10 years of experience, love to work with modern frameworks and languages like React, Next, Typescript, and Python. He is a great team player and always willing to help the team."

    if len(names) > 1:
        return f"Carlos is great Full-stack engineer with 10 years of experience, love to work with modern frameworks and languages like React, Next, Typescript, and Python. He is a great team player and always willing to help the team. Juan is a devops engineer with 5 years of experience, he is expert in kubernetes and docker. He is a great team player and always willing to help the team."


def get_role_info() -> str:
  """Provide information about a open role looking for engineers"""
  return "Google is looking for a Full-stack engineer with 10 years of experience, love to work with modern frameworks and languages like React, Next, Typescript, and Python. He is a great team player and always willing to help the team."


root_agent = Agent(
    name="root_agent",
    model=LiteLlm(model="openai/gpt-4o"),
    description=(
        "Agent to answer questions about a candidate"
    ),
    instruction=(
        "You are a helpful assistant. Your primary goal is to provide information about a candidate or a role. "+
        "When the user asks a question about a candidate, "+
        "you MUST use the 'get_candidate_info' tool to find the information. "+
        "When the user asks a question about a role, "+
        "you MUST use the 'get_role_info' tool to find the information. "+
        "When the user ask to compare candidates with a role you have to use get_candidate_info, and get_role_info tools to find the information and the compare the candidates with the role. "+
        "Analyze the tool's response: if the status is 'error', inform the user politely about the error message. "+
        "If the status is 'success', based on the results of the tools, clearly and concisely present the information to the user. "+
        "Only use the tool when a candidate or a role is mentioned."
    ),
    tools=[get_candidate_info, get_role_info],
)

Expected behavior
The agent gets the results of each tool and compare the results

Desktop (please complete the following information):

  • OS: [e.g. iOS] Mac OS Sequioa 15.1
  • Python version(python -V): 3.10
  • ADK version(pip show google-adk): 0.1.0

Additional context
The error is obvious, the root agent call both tools (and they actually return the data) but when receives back breaks. It happens the same when using subagents either using sub_agents= or subagents as tool

Activity

  1. wizard-spin commented on Apr 14, 2025

    @wizard-spin

    I have also encountered this issue. It appears that the tools only work with agents that use Gemini models. They don't function properly with non-Gemini models are used via Litellm. As a temporary solution, I have been using sug-agent that uses gemini model specifically to utilize the tool until this is resolved. and use this sub agent as a tool in the root_agent.

    Example:

    `# Google search tool is not supported for model openai/gpt-4o-mini, therefore using sub-agent for it.

    The sub-agent is a pre-built agent that can perform Google searches and return results.

    basic_search_agent = Agent(
    name="basic_search_agent",
    model="gemini-2.0-flash-exp",
    description="Agent to answer questions using Google Search.",
    instruction="I can answer your questions by searching the internet. Just ask me anything!",
    # google_search is a pre-built tool which allows the agent to perform Google searches.
    tools=[google_search]
    )

    --- Example Agent using OpenAI's GPT-4o ---

    (Requires OPENAI_API_KEY)

    root_agent = LlmAgent(
    model=LiteLlm(model="openai/gpt-4o-mini"), # LiteLLM model string format
    name="openai_web_search_summarization_agent",
    description= "This agent is an advanced AI orchestrator that accepts a user’s query, delegates it to a sub-agent for web searching, and returns a concise summary. It clarifies ambiguities by highlighting conflicting information and prompts the user for details. By leveraging automated search and synthesis, the agent provides contextually relevant answers while maintaining transparency about uncertainties.",
    instruction=prompt.SUMMARIZATION_INSTR,
    tools=[agent_tool.AgentTool(agent=basic_search_agent)],
    )`

  2. 2manoj1 commented on Apr 14, 2025

    @2manoj1

    I'm running into an issue using LlmAgent with multiple sub_agents. When both sub-agents try to respond (i.e., tool calls are made by both agents), I get this error from OpenAI:

    litellm.BadRequestError: OpenAIException - An assistant message with 'tool_calls' must be followed by tool messages responding to each 'tool_call_id'.
    orchestrator_agent = LlmAgent(
          name="OrchestratorAgent",
          model=LiteLlm(model="openai/gpt-4o"),
          instruction=(
              "You are an orchestrator agent that delegates queries to specialized agents. "
              "Use weather_agent for weather-related queries and rag_agent for other queries."
          ),
          sub_agents=[weather_agent, rag_agent],
      )

    It seems like the orchestration isn't handling the tool_call_id resolution correctly when more than one agent is involved.

    Please let me know if this is a known issue or if there's a workaround!

  3. ar0it commented on Apr 17, 2025

    @ar0it

    For me the issue seems to occur when several function_calls happen in a single part

    {"parts":[
       {"function_call":{"id":"call_9cyc70mY3tGQVGbeRNfRYRTE",...,"name":"browser_agent"}},
       {"function_call":{"id":"call_OgkaDMKVKO2u54LUdRA6vWKe",...,"name":"browser_agent"}},
       {"function_call":{"id":"call_itIN9OM4yYoMdWPMprJjSohF",...,"name":"browser_agent"}},
       {"function_call":{"id":"call_DCUUeE0MLWE7Qip2mQ2nPJCn",...,"name":"browser_agent"}}
       ],"role":"model"}
    

    A workaround could be to intercept the tool call and let only one function call through:

    def enforce_single_tool_call(
        callback_context: CallbackContext, llm_response: LlmResponse
    ) -> Optional[LlmResponse]:
        """
        after_model_callback to ensure only one tool call is processed per turn.
        Stores remaining calls in state['pending_tool_calls'].
        """
        if not llm_response or not llm_response.content or not llm_response.content.parts:
            return None # No response or no parts, proceed normally
    
        function_call_parts: list[types.Part] = [
            part for part in llm_response.content.parts if part.function_call
        ]
    
        if len(function_call_parts) > 1:
            print(f"Intercepted {len(function_call_parts)} tool calls. Processing only the first.")
    
            first_call_part = function_call_parts[0]
            remaining_calls = [
                part.function_call for part in function_call_parts[1:]
            ]
            callback_context.state['pending_tool_calls'] = remaining_calls
            print(f"Stored {len(remaining_calls)} pending calls in state.")
    
    
            # Create a new response with only the first call
            new_content = types.Content(
                parts=[first_call_part],
                role=llm_response.content.role # Keep original role
            )
            modified_response = LlmResponse(
                content=new_content,
            )
            return modified_response
    
        return None
    
    research_agent = Agent(
        name="research_agent", # Use snake_case name
        model=research_agent_model,  # Use the imported LiteLlm instance
        description="Performs deep research using available tools like web browsing",
        instruction=prompt.RESEARCH_AGENT_INSTR,
        tools=[
            AgentTool(browser_agent),
        ],
        after_model_callback=enforce_single_tool_call
    )
    
  4. added
    models[Component] This issue is related to model support
    and removed on Apr 27, 2025
  5. added theissue type on May 3, 2025
  6. hangfei commented on May 31, 2025

    @hangfei
    Collaborator

    This should be fixed in latest wheel. Please retry. Feel free to re-open if you still experience this issue.

  7. momo-tuannguyen12 commented on Jun 27, 2025

    @momo-tuannguyen12

    This should be fixed in latest wheel. Please retry. Feel free to re-open if you still experience this issue.

    which version fixes that issue?

  8. jpmc216 commented on Aug 6, 2025

    @jpmc216

    I am using latest google-adk version (1.9.0) and I still get the issue

    litellm.exceptions.BadRequestError: litellm.BadRequestError: AzureException BadRequestError - An assistant message with 'tool_calls' must be followed by tool messages responding to each 'tool_call_id'. The following tool_call_ids did not have response messages:

  9. gsagrawal-binocs commented on Aug 19, 2025

    @gsagrawal-binocs

    i am using 1.10.0 and still getting this issue.

  10. Kk12suthar commented on Aug 20, 2025

    @Kk12suthar

    i am also experincing this issue with all models other than gemini

    why other models reuqires id as string for making a tool call @hangfei please fix it asap, its a major obstacle for my app.

  11. samalwa commented on Sep 1, 2025

    @samalwa

    Facing same issue. is this resolved? please help

  12. digi0pz commented on Nov 12, 2025

    @digi0pz

    We are still facing this issue especially after adding system_instruction. Is this still open?

  13. OV-CPSO commented on Dec 2, 2025

    @OV-CPSO

    I am facing this issue. Can we reopen this issue?

  14. aleianno90 commented on Dec 15, 2025

    @aleianno90

    Facing same issue.

  15. simoneb commented on Dec 22, 2025

    @simoneb

    Can you please reopen this issue?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

models[Component] This issue is related to model support

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions