google / google/adk-docs

Question for multi-agents's Explicit Invocation example

Open
#235 3 comments 0 reactions 0 assignees View on GitHub
agents events question tools
Dominant language
Shell
Stars
1.5k
Forks
1.3k
Avg merge
7d 1h
Merged PRs (30d)
34

Description

**Describe the bug**

I try to follow the instruction [1] for using Explicit Invocation (AgentTool), within the example it return "Event(author=self.name, content=types.Content(parts=[types.Part.from_bytes(image_bytes, "image/png")]))", then i find out after the agent tool yield the event, although the event contain the image information within the part. but base on the here[2] , it modify the response to empty string result. After that __build_response_event function be executed within handle_function_calls_async [3], since the function_result is '', so it be rewritten into {'result':''} [4][5], then the event become [5], then this event be yield to parent agent.

Base on documentation here [7] "The function's return value must be a dictionary (dict)." & "If your function returns a non-dictionary type (e.g., a string, number, list), the ADK framework will automatically wrap it into a dictionary like {'result': your_original_return_value} before passing the result back to the model."

Does this mean when people use AgentTool, it can't support Multimodal usecase? because if i put this agent as subagent instead of tool, it can successfully propagate the event to next LLM call for parent agent.

I will suggest within we modify the logic like this, not sure it make sense or not.
```agent_tool.py#L169
if (
not last_event
or not last_event.content
or not last_event.content.parts
):
return last_event
```
Then
```functions.py#L441-L442
if isinstance(function_result, Event):
return function_result
if not isinstance(function_result, dict):
function_result = {'result': function_result}
```

[1] https://google.github.io/adk-docs/agents/multi-agents/#b-llm-driven-delegation-agent-transfer
[2] https://github.com/google/adk-python/blob/main/src/google/adk/tools/agent_tool.py#L169
[3] https://github.com/google/adk-python/blob/main/src/google/adk/flows/llm_flows/functions.py#L188
[4] https://github.com/google/adk-python/blob/main/src/google/adk/flows/llm_flows/functions.py#L441-L442
[5]

Image

[6]
```text
Event(content=Content(parts=[Part(video_metadata=None, thought=None, code_execution_result=None,
executable_code=None, file_data=None, function_call=None, function_response=FunctionResponse(id='adk-0fc4e9ca-
1b5d-4fe5-ade3-eab946e9042e', name='ImageGen', response={'result': ''}), inline_data=None, text=None)], role='user'),
grounding_metadata=None, partial=None, turn_complete=None, error_code=None, error_message=None,
interrupted=None, custom_metadata=None, invocation_id='e-aaed831c-ecd9-4e3e-8f8b-dd5c333e46f5', author='Artist',
actions=EventActions(skip_summarization=None, state_delta={}, artifact_delta={'image.png': 0}, transfer_to_agent=None,
escalate=None, requested_auth_configs={}), long_running_tool_ids=None, branch=None, id='8tIN8XlP',
timestamp=1746739473.350891)
```

[7] https://google.github.io/adk-docs/tools/#defining-effective-tool-functions

**To Reproduce**
```cmd
python -m venv .venv
source .venv/bin/activate
pip install google-adk==0.4.0
```

```python
# Conceptual Setup: Agent as a Tool
from google.adk.agents import LlmAgent, BaseAgent
from google.adk.tools import agent_tool
from pydantic import BaseModel
from google.adk.events import Event
from google.genai import types
from vertexai.preview.vision_models import ImageGenerationModel
from google.adk.models.lite_llm import LiteLlm
from google.adk.runners import InMemoryRunner
import asyncio
import base64
from google.adk.events.event_actions import EventActions
from google.adk.tools import load_artifacts

APP_NAME="image_gen_app"
USER_ID="foo"
SESSION_ID="123"
GEMINI_MODEL = "gemini-2.0-flash"

query="a cat wearing a hat"

# Define a target agent (could be LlmAgent or custom BaseAgent)
class ImageGeneratorAgent(BaseAgent): # Example custom agent
name: str = "ImageGen"
description: str = "Generates an image based on a prompt."
# ... internal logic ...
async def _run_async_impl(self, ctx): # Simplified run logic
prompt = ctx.session.state.get("image_prompt", "default prompt")
# ... generate image bytes ...
generation_model = ImageGenerationModel.from_pretrained("imagen-3.0-generate-002")
images = generation_model.generate_images(
prompt=prompt,
number_of_images=1,
aspect_ratio="1:1",
negative_prompt="",
person_generation="allow_adult",
safety_filter_level="block_few",
add_watermark=True,
)
image_bytes = images[0]._image_bytes

yield Event(
author=self.name,
content=types.Content(
parts=[
types.Part.from_bytes(
data=image_bytes,
mime_type="image/png"
)
]
)
)

image_agent = ImageGeneratorAgent()
image_tool = agent_tool.AgentTool(agent=image_agent) # Wrap the agent

# Parent agent uses the AgentTool
artist_agent = LlmAgent(
name="Artist",
model=GEMINI_MODEL,
instruction="Create a prompt and use the ImageGen tool to generate the image.",
tools=[image_tool] # Include the AgentTool
)
# Artist LLM generates a prompt, then calls:
# FunctionCall(name='ImageGen', args={'image_prompt': 'a cat wearing a hat'})
# Framework calls image_tool.run_async(...), which runs ImageGeneratorAgent.
# The resulting image Part is returned to the Artist agent as the tool result.

runner = InMemoryRunner(agent=artist_agent, app_name=APP_NAME)
session_service = runner.session_service
session = session_service.create_session(
app_name=APP_NAME,
user_id=USER_ID,
state={"image_prompt": query},
session_id=SESSION_ID
)

async def execute_runner(query, user_id, session_id):
initial_message = types.Content(role="user", parts=[types.Part(text=query)])
final_response_text = None
async for event in runner.run_async(
user_id=user_id,
session_id=session_id,
new_message=initial_message
):
breakpoint()
if event.is_final_response():
if event.content and event.content.parts:
final_response_text = event.content.parts[0].text
if not final_response_text:
print("error")
else:
print(final_response_text)

final_session_object = runner.session_service.get_session(
app_name=APP_NAME, user_id=user_id, session_id=session_id
)

asyncio.run(execute_runner(
query=query,
user_id=USER_ID,
session_id=SESSION_ID
))

```

**Expected behavior**

Hopefully we can get same behavior be no matter people put agent as subagent or agent as Tool?

**Versions**

- OS: [e.g. Windows, Mac, Linux] Linux
- ADK version: 0.4.0
- Python version: Python 3.10.12

**Additional context**

Add any other context about the problem here.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.