Thanks to visit codestin.com
Credit goes to github.com

Skip to content

opentelemetry ValueError: "Token was created in a different Context" with ParallelAgent and LlmAgent #860

@divanshu-goyal-rc

Description

@divanshu-goyal-rc

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:

  1. Define multiple LlmAgent instances, each with its own model, instructions, and output schema.
  2. Define an AsyncExitStack and initialize tools (e.g., MCPToolset).
  3. Instantiate a ParallelAgent, providing the LlmAgent instances to its sub_agents parameter.
  4. The root agent setup returns the ParallelAgent instance and the AsyncExitStack.
  5. 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 the ParallelAgent.

Metadata

Metadata

Assignees

Labels

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

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions