Lab 21.5: Building a Smart Support Router Challenge
Goal
Goal
In this lab, you will build a complex orchestration system using a Custom Agent. You will create a SmartRouterAgent that intercepts user requests, uses a fast LLM to classify the sentiment (angry, neutral, happy), and then deterministically routes the request to the appropriate specialist agent based on that sentiment.
This is a powerful pattern for customer support systems, where angry customers need immediate human escalation, while neutral queries can be handled by an AI.
Step 1: Create the Project Structure
-
Create a new project:
adk create support_routerWhen prompted, choose the Programmatic (Python script) option.
-
Navigate into the new directory:
cd support_router
Step 2: Implement the Custom Router
Exercise: Open agent.py. The two specialist agents (ai_support and human_escalation) have been provided for you.
Your task is to implement the SmartRouterAgent class, which must inherit from BaseAgent and override its _run_async_impl method.
# In agent.py (Starter Code)
from __future__ import annotations
from pydantic import BaseModel, Field
from google.adk.agents import Agent, BaseAgent
from google.adk.agents.invocation_context import InvocationContext
from google.adk.events import Event, EventActions
from typing import AsyncGenerator, Literal
# ===== Specialist Agents (Provided for you) =====
ai_support = Agent(
name="ai_support_bot",
model="gemini-2.5-flash",
instruction="You are a helpful customer support AI. Answer the user's technical questions clearly."
)
human_escalation = Agent(
name="human_escalation_team",
model="gemini-2.5-flash",
instruction="You are a human support representative. A customer is frustrated. Apologize profusely and tell them a human agent will call them immediately at the number on their account."
)
# ===== 1. Define Sentiment Schema =====
# TODO: We need a fast classification agent. Let's force it to output structured data.
# Define a Pydantic BaseModel called `SentimentClassification` with a single field
# `sentiment` that must be a Literal of "angry", "neutral", or "happy".
class SentimentClassification(BaseModel):
pass
# ===== 2. Create the Classifier Agent =====
# TODO: Create a very fast `LlmAgent` named `classifier`.
# Give it an instruction to "Classify the sentiment of the user's latest message."
# Use your `SentimentClassification` schema for its `output_schema` and
# set `output_key="user_sentiment"`.
classifier = None
# ===== 3. Build the Custom Router =====
# TODO: Create the `SmartRouterAgent` class inheriting from `BaseAgent`.
class SmartRouterAgent(BaseAgent):
# Accept our sub-components in the constructor
def __init__(self, name: str, classifier: Agent, ai_support: Agent, human_escalation: Agent, **kwargs):
super().__init__(name=name, **kwargs)
self.classifier = classifier
self.ai_support = ai_support
self.human_escalation = human_escalation
# TODO: Implement the engine
async def _run_async_impl(self, ctx: InvocationContext) -> AsyncGenerator[Event, None]:
pass
# Step 3a: Run the classifier agent to determine sentiment.
# Hint: Use `async for event in self.classifier.run_async(ctx):` but DO NOT yield
# these events to the user. We just want the classifier to populate the state silently.
# Step 3b: Read the classification from the state.
# Hint: `ctx.session.state.get("user_sentiment")`
# Step 3c: Routing Logic.
# If the sentiment dict has a "sentiment" value of "angry", choose the `human_escalation` agent.
# Otherwise, choose the `ai_support` agent.
# Step 3d: Execute the chosen agent.
# Hint: Use `async for event in chosen_agent.run_async(ctx):` and this time,
# YOU MUST yield the event so the user sees the final response!
# ===== COMPLETE SYSTEM =====
# Instantiate your custom router
support_system = SmartRouterAgent(
name="SupportSystem",
classifier=classifier,
ai_support=ai_support,
human_escalation=human_escalation
)
# Set the root agent for the ADK
root_agent = support_system
(Note: The full implementation is available in the lab-solution.md if you need a hint.)
Step 3: Run and Test the Router
- Set up your
.envfile. - Navigate to the parent directory (
cd ..) and start the Dev UI:adk web support_router - Interact with the system:
- Test 1 (Neutral): Send "How do I reset my password?". You should get a helpful technical response from the
ai_support_bot. - Test 2 (Angry): Send "THIS APP IS TERRIBLE! I WANT A REFUND NOW!". You should instantly get an apology and an escalation message from the
human_escalation_team.
- Test 1 (Neutral): Send "How do I reset my password?". You should get a helpful technical response from the
- Examine the Trace View:
- Look closely at the Trace. Notice how the
classifieragent runs first, but its output is hidden from the main chat interface. - Then, notice how your custom router decides which agent to execute next based on the hidden state.
- Look closely at the Trace. Notice how the
Lab Summary
You have successfully built a sophisticated routing orchestrator using a Custom Agent! You have learned:
- How to inherit from
BaseAgentand override_run_async_impl. - How to execute sub-agents silently to populate the session state.
- How to read from the
InvocationContext(ctx.session.state) to make deterministic routing decisions. - How to yield events from a delegated sub-agent back to the user interface.
Self-Reflection Questions
- Why did we build a custom
SmartRouterAgentinstead of just giving anLlmAgenta tool calledescalate_to_human? (Think about control and determinism). - In Step 3a, why did we loop over
self.classifier.run_async(ctx)but intentionally not yield the events? What would happen if we did yield them? - How could you extend this custom agent to also include a Loop Agent? (e.g., if the AI support bot's answer isn't helpful, loop back to the user for clarification before escalating).
🕵️ Hidden Solution 🕵️
Looking for the solution? Here's a hint (Base64 decode me):
L2RvYy1hZGstdHJhaW5pbmcvbW9kdWxlMjEuNS1jdXN0b20tYWdlbnRzL2xhYi1zb2x1dGlvbg==
The direct link is: Lab Solution