Skip to content

When setting the include_contents parameters to none for LlmAgent, the behavior is not aligned with the declaration in the manual. #3535

Description

@GabrielWithTina

Describe the bug
When setting the include_contents parameters to none for LlmAgent, the behavior is not aligned with the declaration in the manual.

According to the docstring in the code

include_contents: Literal['default', 'none'] = 'default'
  """Controls content inclusion in model requests.

  Options:
    default: Model receives relevant conversation history
    none: Model receives no prior history, operates solely on current instruction and input
  """

The issue is that if a sub-agent is transferred to take over control, the context should include the user input. But the actual context for this sub-agent doesn't contain the latest user input, but the last transfer_to_agent context from the parent agent.

To Reproduce
Code to reproduce

import os
import asyncio
from google.adk.agents import Agent
from google.adk.models.lite_llm import LiteLlm # For multi-model support
from google.adk.sessions import InMemorySessionService
from google.adk.runners import Runner
from google.genai import types # For creating message Content/Parts
from google.adk.tools import ToolContext

# @title Define Tools for Greeting and Farewell Agents
from typing import Optional # Make sure to import Optional

# Ensure 'get_weather' from Step 1 is available if running this step independently.
# def get_weather(city: str) -> dict: ... (from Step 1)
from agent_team.config import MODEL_GEMINI_2_0_FLASH, MODEL_GPT_4O, MODEL_CLAUDE_SONNET
from agent_team.weatheragent import get_weather # Import the tool from weatheragent
from agent_team.agent import call_agent_async # Import the async function to call agents

import litellm
litellm._turn_on_debug()

import logging
logging.basicConfig(
    level=logging.INFO,  # Set log level
    filename="/tmp/subagents.log",  # Log file path
    filemode="w",  # "a" for append, "w" for overwrite each run
    format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
    force=True,
)

def say_hello(tool_context: ToolContext, name: Optional[str] = None) -> str:
    """Provides a simple greeting. If a name is provided, it will be used.

    Args:
        name (str, optional): The name of the person to greet. Defaults to a generic greeting if not provided.

    Returns:
        str: A friendly greeting message.
    """
    if name:
        greeting = f"Hello, {name}!"
        print(f"--- Tool: say_hello called with name: {name} ---")
    else:
        greeting = "Hello there!" # Default greeting if name is None or not explicitly passed
        print(f"--- Tool: say_hello called without a specific name (name_arg_value: {name}) ---")
    return " ".join([greeting, tool_context.state["default_greeting"]])

def say_goodbye() -> str:
    """Provides a simple farewell message to conclude the conversation."""
    print(f"--- Tool: say_goodbye called ---")
    return "Goodbye! Have a great day."

print("Greeting and Farewell tools defined.")

# Optional self-test
# print(say_hello("Alice"))
# print(say_hello()) # Test with no argument (should use default "Hello there!")
# print(say_hello(name=None)) # Test with name explicitly as None (should use default "Hello there!")

# @title Define Greeting and Farewell Sub-Agents

# If you want to use models other than Gemini, Ensure LiteLlm is imported and API keys are set (from Step 0/2)
# from google.adk.models.lite_llm import LiteLlm
# MODEL_GPT_4O, MODEL_CLAUDE_SONNET etc. should be defined
# Or else, continue to use: model = MODEL_GEMINI_2_0_FLASH

# --- Greeting Agent ---
greeting_agent = None
try:
    greeting_agent = Agent(
        # Using a potentially different/cheaper model for a simple task
        # model = MODEL_GEMINI_2_0_FLASH,
        model=LiteLlm(model=MODEL_GPT_4O), # If you would like to experiment with other models
        name="greeting_agent",
        instruction="You are the Greeting Agent. Your ONLY task is to provide a friendly greeting to the user. "
                    "Use the 'say_hello' tool to generate the greeting. "
                    "If the user provides their name, make sure to pass it to the tool. "
                    "Do not engage in any other conversation or tasks.",
        description="Handles simple greetings and hellos using the 'say_hello' tool.", # Crucial for delegation
        tools=[say_hello],
    )
    print(f"✅ Agent '{greeting_agent.name}' created using model '{greeting_agent.model}'.")
except Exception as e:
    print(f"❌ Could not create Greeting agent. Check API Key ({greeting_agent.model}). Error: {e}")

# --- Farewell Agent ---
farewell_agent = None
try:
    farewell_agent = Agent(
        # Can use the same or a different model
        # model = MODEL_GEMINI_2_0_FLASH,
        model=LiteLlm(model=MODEL_GPT_4O), # If you would like to experiment with other models
        name="farewell_agent",
        instruction="You are the Farewell Agent. Your ONLY task is to provide a polite goodbye message. "
                    "Use the 'say_goodbye' tool when the user indicates they are leaving or ending the conversation "
                    "(e.g., using words like 'bye', 'goodbye', 'thanks bye', 'see you'). "
                    "Do not perform any other actions.",
        description="Handles simple farewells and goodbyes using the 'say_goodbye' tool.", # Crucial for delegation
        tools=[say_goodbye],

        include_contents='none',
    )
    print(f"✅ Agent '{farewell_agent.name}' created using model '{farewell_agent.model}'.")
except Exception as e:
    print(f"❌ Could not create Farewell agent. Check API Key ({farewell_agent.model}). Error: {e}")

# @title Define the Root Agent with Sub-Agents

# Ensure sub-agents were created successfully before defining the root agent.
# Also ensure the original 'get_weather' tool is defined.
root_agent = None
runner_root = None # Initialize runner

if greeting_agent and farewell_agent and 'get_weather' in globals():
    # Let's use a capable Gemini model for the root agent to handle orchestration
    # root_agent_model = MODEL_GEMINI_2_0_FLASH
    root_agent_model = LiteLlm(model=MODEL_GPT_4O)

    weather_agent_team = Agent(
        name="weather_agent_v2", # Give it a new version name
        model=root_agent_model,
        description="The main coordinator agent. Handles weather requests and delegates greetings/farewells to specialists.",
        instruction="You are the main Weather Agent coordinating a team. Your primary responsibility is to provide weather information. "
                    "Use the 'get_weather' tool ONLY for specific weather requests (e.g., 'weather in London'). "
                    "You have specialized sub-agents: "
                    "1. 'greeting_agent': Handles simple greetings like 'Hi', 'Hello'. Delegate to it for these. "
                    "2. 'farewell_agent': Handles simple farewells like 'Bye', 'See you'. Delegate to it for these. "
                    "Analyze the user's query. If it's a greeting, delegate to 'greeting_agent'. If it's a farewell, delegate to 'farewell_agent'. "
                    "If it's a weather request, handle it yourself using 'get_weather'. "
                    "For anything else, respond appropriately or state you cannot handle it.",
        tools=[get_weather], # Root agent still needs the weather tool for its core task
        # Key change: Link the sub-agents here!
        sub_agents=[greeting_agent, farewell_agent]
    )
    print(f"✅ Root Agent '{weather_agent_team.name}' created using model '{root_agent_model}' with sub-agents: {[sa.name for sa in weather_agent_team.sub_agents]}")

else:
    print("❌ Cannot create root agent because one or more sub-agents failed to initialize or 'get_weather' tool is missing.")
    if not greeting_agent: print(" - Greeting Agent is missing.")
    if not farewell_agent: print(" - Farewell Agent is missing.")
    if 'get_weather' not in globals(): print(" - get_weather function is missing.")    

# @title Interact with the Agent Team
import asyncio # Ensure asyncio is imported

# Ensure the root agent (e.g., 'weather_agent_team' or 'root_agent' from the previous cell) is defined.
# Ensure the call_agent_async function is defined.

# Check if the root agent variable exists before defining the conversation function
root_agent_var_name = 'root_agent' # Default name from Step 3 guide
if 'weather_agent_team' in globals(): # Check if user used this name instead
    root_agent_var_name = 'weather_agent_team'
elif 'root_agent' not in globals():
    print("⚠️ Root agent ('root_agent' or 'weather_agent_team') not found. Cannot define run_team_conversation.")
    # Assign a dummy value to prevent NameError later if the code block runs anyway
    root_agent = None # Or set a flag to prevent execution

# Only define and run if the root agent exists
if root_agent_var_name in globals() and globals()[root_agent_var_name]:
    # Define the main async function for the conversation logic.
    # The 'await' keywords INSIDE this function are necessary for async operations.
    async def run_team_conversation():
        print("\n--- Testing Agent Team Delegation ---")
        session_service = InMemorySessionService()
        APP_NAME = "weather_tutorial_agent_team"
        USER_ID = "user_1_agent_team"
        SESSION_ID = "session_001_agent_team"
        init_state = {
            "default_greeting": "Hi, there, I am so happy to spend this time with you."
        }
        session = await session_service.create_session(
            app_name=APP_NAME, 
            user_id=USER_ID, 
            session_id=SESSION_ID,
            state=init_state,
        )

        print(f"Session created: App='{APP_NAME}', User='{USER_ID}', Session='{SESSION_ID}'")

        actual_root_agent = globals()[root_agent_var_name]
        runner_agent_team = Runner( # Or use InMemoryRunner
            agent=actual_root_agent,
            app_name=APP_NAME,
            session_service=session_service,
        )
        print(f"Runner created for agent '{actual_root_agent.name}'.")

        # --- Interactions using await (correct within async def) ---
        # await call_agent_async(query = "Hello there!",
        #                        runner=runner_agent_team,
        #                        user_id=USER_ID,
        #                        session_id=SESSION_ID)
        # await call_agent_async(query = "What is the weather in New York?",
        #                        runner=runner_agent_team,
        #                        user_id=USER_ID,
        #                        session_id=SESSION_ID)
        # await call_agent_async(query = "Thanks, bye!",
        #                        runner=runner_agent_team,
        #                        user_id=USER_ID,
        #                        session_id=SESSION_ID)

        print("Type your queries or 'quit' to exit.")
        while True:
            try:
                query = input("\nQuery: ").strip()
                if query.lower() == 'quit':
                    break
                await call_agent_async(
                                query=query,
                                runner=runner_agent_team,
                                user_id=USER_ID,
                                session_id=SESSION_ID,
                                )                
            except Exception as e:
                print(f"\nError: {str(e)}")

    # --- Execute the `run_team_conversation` async function ---
    # Choose ONE of the methods below based on your environment.
    # Note: This may require API keys for the models used!

    # METHOD 1: Direct await (Default for Notebooks/Async REPLs)
    # If your environment supports top-level await (like Colab/Jupyter notebooks),
    # it means an event loop is already running, so you can directly await the function.
    # print("Attempting execution using 'await' (default for notebooks)...")
    # await run_team_conversation()

    # METHOD 2: asyncio.run (For Standard Python Scripts [.py])
    # If running this code as a standard Python script from your terminal,
    # the script context is synchronous. `asyncio.run()` is needed to
    # create and manage an event loop to execute your async function.
    # To use this method:
    # 1. Comment out the `await run_team_conversation()` line above.
    # 2. Uncomment the following block:
    
    import asyncio
    if __name__ == "__main__": # Ensures this runs only when script is executed directly
        print("Executing using 'asyncio.run()' (for standard Python scripts)...")
        try:
            # This creates an event loop, runs your async function, and closes the loop.
            asyncio.run(run_team_conversation())
        except Exception as e:
            print(f"An error occurred: {e}")
    

else:
    # This message prints if the root agent variable wasn't found earlier
    print("\n⚠️ Skipping agent team conversation execution as the root agent was not successfully defined in a previous step.")    

input queries:

  • Hello John
  • GoodBye John

Expected behavior
The last LLM request from farewell_agent is as follows: it doesn't include the user query GoodBye John. Instead, it includes the last transfer-to-agent call context.

{
  "model": "gpt-4.1",
  "messages": [
    {
      "role": "system",
      "content": "You are the Farewell Agent. Your ONLY task is to provide a polite goodbye message. Use the 'say_goodbye' tool when the user indicates they are leaving or ending the conversation (e.g., using words like 'bye', 'goodbye', 'thanks bye', 'see you'). Do not perform any other actions.\n\nYou are an agent. Your internal name is \"farewell_agent\".\n\n The description about you is \"Handles simple farewells and goodbyes using the 'say_goodbye' tool.\"\n\n\nYou have a list of other agents to transfer to:\n\n\nAgent name: weather_agent_v2\nAgent description: The main coordinator agent. Handles weather requests and delegates greetings/farewells to specialists.\n\n\nAgent name: greeting_agent\nAgent description: Handles simple greetings and hellos using the 'say_hello' tool.\n\n\nIf you are the best to answer the question according to your description, you\ncan answer it.\n\nIf another agent is better for answering the question according to its\ndescription, call `transfer_to_agent` function to transfer the\nquestion to that agent. When transferring, do not generate any text other than\nthe function call.\n\n**NOTE**: the only available agents for `transfer_to_agent` function are `greeting_agent`, `weather_agent_v2`.\n\nIf neither you nor the other agents are best for the question, transfer to your parent agent weather_agent_v2."
    },
    {
      "role": "user",
      "content": [
        {
          "type": "text",
          "text": "For context:"
        },
        {
          "type": "text",
          "text": "[greeting_agent] `transfer_to_agent` tool returned result: {'result': None}"
        }
      ]
    },
    {
      "role": "assistant",
      "tool_calls": [
        {
          "type": "function",
          "id": "call_pLlNJ1idzJyvFuBL7EJYDuq5",
          "function": {
            "name": "say_goodbye",
            "arguments": "{}"
          }
        }
      ]
    },
    {
      "role": "tool",
      "tool_call_id": "call_pLlNJ1idzJyvFuBL7EJYDuq5",
      "content": "{\"result\": \"Goodbye! Have a great day.\"}"
    }
  ],
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "transfer_to_agent",
        "description": "Transfer the question to another agent.\n\n  This tool hands off control to another agent when it's more suitable to\n  answer the user's question according to the agent's description.\n\n  Args:\n    agent_name: the agent name to transfer to.\n  ",
        "parameters": {
          "type": "object",
          "properties": {
            "agent_name": {
              "type": "string"
            }
          },
          "required": ["agent_name"]
        }
      }
    },
    {
      "type": "function",
      "function": {
        "name": "say_goodbye",
        "description": "Provides a simple farewell message to conclude the conversation.",
        "parameters": {
          "type": "object",
          "properties": {}
        }
      }
    }
  ],
  "extra_body": {}
}

Although the say_hello function are called, but this behavor hides the fact that there is no latest user input to be included

Desktop (please complete the following information):

  • OS: WSL Ubuntu on Windows 11
  • Python version: 3.12.11
  • ADK version: 1.16.0

Model Information:

  • Are you using LiteLLM: Yes
  • Which model is being used: openai/gpt-4.1

Metadata

Metadata

Assignees

Labels

core[Component] This issue is related to the core interface and implementationneeds review[Status] The PR/issue is awaiting review from the maintainer

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions