-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Description
When using google.adk.agents.ParallelAgent
with multiple google.adk.agents.LlmAgent
instances as sub-agents, ValueError
exceptions from the opentelemetry.context
module are consistently logged. These errors occur after each sub-agent completes its processing. While the core functionality of the agents (e.g., LLM calls and output generation) appears to be unaffected, these errors create noise in the logs and suggest a potential issue with context management within the ADK framework or its interaction with opentelemetry
.
Steps to Reproduce:
- Define multiple
LlmAgent
instances, each with its own model, instructions, and output schema. - Define an
AsyncExitStack
and initialize tools (e.g.,MCPToolset
). - Instantiate a
ParallelAgent
, providing theLlmAgent
instances to itssub_agents
parameter. - The root agent setup returns the
ParallelAgent
instance and theAsyncExitStack
. - Run the agent (e.g., by making a request to the FastAPI endpoint if using
google.adk.cli.fast_api
).
Expected Behavior:
The ParallelAgent
and its sub-agents should execute without opentelemetry
context errors being logged.
Actual Behavior:
After each sub-agent finishes its execution and its output is generated, the following type of error is logged multiple times:
ERROR - __init__.py:157 - Failed to detach context
Traceback (most recent call last):
File "/path/to/your/env/lib/pythonX.Y/site-packages/opentelemetry/context/__init__.py", line 155, in detach
_RUNTIME_CONTEXT.detach(token)
File "/path/to/your/env/lib/pythonX.Y/site-packages/opentelemetry/context/contextvars_context.py", line 53, in detach
self._current_context.reset(token)
ValueError: <Token var=<ContextVar name='current_context' default={} at 0xXXXXXXXXX> at 0xYYYYYYYYY> was created in a different Context
(Note: Replace /path/to/your/env/lib/pythonX.Y/
and memory addresses with actuals from your logs if desired)
Environment:
- Operating System: macOS (darwin 23.5.0)
- Python Version: 3.12
- Google ADK Version: 0.4.0
- Minimal Reproducible Code (
agent.py
):import asyncio from google.adk.agents import LlmAgent, BaseAgent, ParallelAgent from pydantic import BaseModel, Field from google.adk.tools.mcp_tool.mcp_toolset import MCPToolset, SseServerParams from contextlib import AsyncExitStack import enum # ... (Out_of_office_category, Out_of_office_output_schema definitions) ... # ... (New_lead_category, New_lead_output_schema definitions) ... # ... (Book_meeting_category, Book_meeting_output_schema definitions) ... # ... (General_type_reply_category, General_type_reply_output_schema definitions) ... # [PASTE YOUR Enum and BaseModel Schema definitions here for completeness] class Out_of_office_category(enum.Enum): resignation = "resignation" holiday = "holiday" other = "other" not_out_of_office = "not_out_of_office" class Out_of_office_output_schema(BaseModel): out_of_office: bool = Field(description="Whether the email is out of office or not") category: Out_of_office_category = Field(description="The category of the email") reason: str = Field(description="The reason for the email being out of office") class New_lead_category(enum.Enum): not_right_person_add_new_lead_new_email = "not_right_person_add_new_lead_new_email" right_person_add_new_lead_new_email = "right_person_add_new_lead_new_email" right_person_add_new_lead_existing_email = "right_person_add_new_lead_existing_email" not_add_new_lead = "not_add_new_lead" class New_lead_output_schema(BaseModel): new_lead: bool = Field(description="Whether the email contains a new lead or not") new_lead_category: New_lead_category = Field(description="The category of the email") reason: str = Field(description="The reason for the email being a new lead or not") class Book_meeting_category(enum.Enum): book_meeting = "book_meeting" book_meeting_details = "book_meeting_details" not_book_meeting = "not_book_meeting" class Book_meeting_output_schema(BaseModel): book_meeting: bool = Field(description="Whether the email contains a request to book a meeting or not") book_meeting_category: Book_meeting_category = Field(description="The category of the email") reason: str = Field(description="The reason for the email being a book meeting or not") class General_type_reply_category(enum.Enum): positive = "positive" negative = "negative" class General_type_reply_output_schema(BaseModel): general_type_reply_category: General_type_reply_category = Field(description="The category of the email") reason: str = Field(description="The reason for the email being a positive or negative reply") async def reply_agent(): common_exit_stack = AsyncExitStack() tools, _ = await MCPToolset.from_server( connection_params=SseServerParams( url="http://44.214.120.160:8002/sse" # Example URL ), async_exit_stack=common_exit_stack ) out_of_office_agent = LlmAgent( model="gemini-2.0-flash", name="out_of_office_agent", description="check whether the email is out of office or not...", instruction=\"\"\"...\"\"\", # Shorten for brevity or refer to full agent code output_schema=Out_of_office_output_schema, output_key="out_of_office_agent" ) new_lead_agent = LlmAgent( model="gemini-2.0-flash", name="new_lead_agent", description="Check whether the email contains a new lead...", instruction=\"\"\"...\"\"\", output_schema=New_lead_output_schema, output_key="new_lead_agent" ) book_meeting_agent = LlmAgent( model="gemini-2.0-flash", name="book_meeting_agent", description="Check whether the email contains a request to book a meeting...", instruction=\"\"\"...\"\"\", output_schema=Book_meeting_output_schema, output_key="book_meeting_agent" ) general_type_reply_agent = LlmAgent( model="gemini-2.0-flash", name="general_type_reply_agent", description="Classify the email into 'positive', 'negative'...", instruction=\"\"\"...\"\"\", output_schema=General_type_reply_output_schema, output_key="general_type_reply_agent" ) return out_of_office_agent, new_lead_agent, book_meeting_agent, general_type_reply_agent, common_exit_stack async def main(): out_of_office_agent, new_lead_agent, book_meeting_agent, general_type_reply_agent, common_exit_stack = await reply_agent() reply_intent_agent = ParallelAgent( name="replyagent", description="Analyze the email and determine the intent of the email.", sub_agents=[out_of_office_agent, new_lead_agent, book_meeting_agent, general_type_reply_agent] ) return reply_intent_agent, common_exit_stack root_agent = main()
Additional Context:
- The sub-agents themselves successfully process inputs and produce the expected outputs via LLM calls. The
opentelemetry
errors seem to occur around the completion/logging phase of each sub-agent's lifecycle within theParallelAgent
.