Skip to content

Feature Request: Native RouterAgent (Tool-Calling Orchestrator) to replace JSON-parsing boilerplate #5032

Closed

Description

@itsParv

Is your feature request related to a problem? Please describe.
Currently in ADK, when developers need to conditionally route a user's prompt to different specialist agents (the Router Pattern), they are forced to write messy, error-prone Python wrapper functions.

To achieve this right now, developers must:

  1. Instruct a classifier_agent to output strict JSON.
  2. Manually capture the output and write try-except json.JSONDecodeError blocks.
  3. Write hardcoded Python if-elif-else statements to manually invoke the next agent in the sequence.

This leads to brittle code and misses out on ADK's native BaseAgent telemetry, suspending features, and stream graph continuity.

Describe the solution you'd like
I propose adding a native RouterAgent (inheriting from BaseAgent) directly to the google.adk.agents module, living alongside SequentialAgent and ParallelAgent.

From a developer API perspective, it would look as clean as this:

from google.adk.agents.router_agent import RouterAgent

router = RouterAgent(
    name="my_router",
    classifier_agent_name="classifier",
    routing_key="output_type",
    routes={
        "A": "diet_specialist",
        "B": "habitat_specialist"
    },
    sub_agents=[classifier, diet_specialist, habitat_specialist]
)

# Execution happens natively: Agent 1 -> (Router transparently parses) -> Agent 2
final_response = router(state)      

Describe alternatives you've considered
For maximum reliability and to avoid String/JSON hallucination entirely, the absolute smartest implementation of this RouterAgent class under the hood wouldn't use JSON string parsing at all. Instead, it would use Native LLM Tool Calling. The Router would convert the target sub_agents into callable functions, provide them to the classifier_agent, and cleanly map the resulting FunctionCall back to the ADK agent invocation.

Additional context
Since the Router pattern is one of the most fundamental multi-agent architectures (on par with Sequential/Parallel), supporting it natively out-of-the-box would massively improve the developer experience for medium-complexity ADK applications, eliminating the immediate need for users to jump into LangGraphAgent just for simple condition routing.

Would you like to implement this?
Yes! I have already begun developing this locally, ensuring that RouterAgent correctly inherits BaseAgent, utilizes BaseAgentState for resuming execution, and yields standard Event streams. If the core team is open to this addition, I would love to submit a Pull Request!

Activity

  1. added
    core[Component] This issue is related to the core interface and implementation
    on Mar 27, 2026
  2. added theissue type on Mar 27, 2026
  3. itsParv commented on Mar 28, 2026

    @itsParv
    Author

    Implementation submitted in #5043.

  4. self-assigned this
    on Mar 30, 2026
  5. klateefa commented on Mar 30, 2026

    @klateefa

    @itsParv Thanks for the update. The request is currently being reviewed, and I will let you know if anything further is needed. Thank you for your understanding.

  6. added
    needs review[Status] The PR/issue is awaiting review from the maintainer
    on Mar 30, 2026
  7. DeanChensj commented on Jul 27, 2026

    @DeanChensj
    Collaborator

    Thank you for the suggestion!
    ADK currently supports conditional routing via Workflows, which is the preferred way for agent orchestration (composite agents like SequentialAgent and ParallelAgent are deprecated in favor of Workflows).

    You can achieve routing without JSON parsing boilerplate by combining LlmAgent's output_schema with a simple transition function in a Workflow.

    Here is an example:

    from google.adk.workflow import Workflow, START
    from google.adk.agents.llm_agent import LlmAgent
    from google.adk.agents.context import Context
    from pydantic import BaseModel, Field
    
    # 1. Define the structured output for the classifier
    class RouterDecision(BaseModel):
      decision: str = Field(description="The target specialist to route to. Must be 'A' or 'B'.")
    
    classifier = LlmAgent(
        name="classifier",
        output_schema=RouterDecision,
        # ... classifier config ...
    )
    
    diet_specialist = LlmAgent(name="diet_specialist", ...)
    habitat_specialist = LlmAgent(name="habitat_specialist", ...)
    
    # 2. Define a simple function to set the route based on the classifier's output.
    # ADK automatically validates the output against RouterDecision and passes it as a RouterDecision instance.
    def route_decision(ctx: Context, classifier_output: RouterDecision):
      ctx.route = classifier_output.decision
      return classifier_output
    
    # 3. Define the workflow with conditional edges
    workflow = Workflow(
        name="my_router",
        edges=[
            (START, classifier),
            (classifier, route_decision),
            (route_decision, {
                "A": diet_specialist,
                "B": habitat_specialist
            })
        ]
    )

    Additionally, if you want the classifier to route by directly calling the specialists as tools (without explicit workflow edges), you can use the sub-agents feature. When you add sub-agents to an LlmAgent, they are automatically exposed as tools:

    classifier = LlmAgent(
        name="classifier",
        sub_agents=[diet_specialist, habitat_specialist],
        # instruct the model when to call diet_specialist or habitat_specialist
    )

    Since the functionality is supported via these native patterns, we are closing this feature request. Feel free to reopen if you have further questions!

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

Metadata

Metadata

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