PART 5 β Google ADK Deep Dive β
5.1 The ADK Philosophy β
ADK is built on two principles that differentiate it from simpler frameworks:
1. Event-Driven Architecture Every action is an immutable Event: user messages, agent replies, tool calls, state changes. The system processes events one by one in an Event Loop. This makes the system auditable, debuggable, and resumable.
2. Compute / Memory Separation
Compute (Agent Logic)
- Stateless
- Can crash β and that's okay
- Can scale horizontally
- Runs the ThinkβActβObserve loop
Memory (Session Service)
- Persistent across crashes
- Single source of truth
- Survives server restarts
- Stored in Postgres / Vertex AI
Why this matters: If a server crashes mid-workflow, ADK resumes from the last saved event. Disciplined separation of concerns β not magic. If a server crashes mid-workflow, ADK resumes from the last saved state. This is not magic β it is disciplined separation of concerns.
5.2 The Runtime Architecture β
The Event Loop β The Most Critical Concept in ADK β
Why this matters in production: Step 4 is where state is saved to a database. If the server dies between step 3 and 5, the state is already persisted β you replay from the last event.
5.3 Agent Types β Which One to Use β
The Intuition β
LlmAgent = The creative employee (unpredictable, flexible)
WorkflowAgent = The process robot (predictable, deterministic)
CustomAgent = The specialist contractor (anything you can code)LlmAgent β The Standard Agent β
agent = LlmAgent(
model="gemini-2.0-flash",
instruction="You are a helpful customer support agent...",
tools=[lookup_order, escalate_ticket],
output_key="agent_response" # auto-saves final answer to session.state
)- Non-deterministic β the LLM decides what to do
- Best for: language tasks, dynamic decisions, tool orchestration
WorkflowAgents β Deterministic Orchestrators β
# Assembly line β order matters
pipeline = SequentialAgent(sub_agents=[
ResearchAgent, # runs first
DraftAgent, # runs second, gets research in state
ReviewAgent # runs third, gets draft in state
])
# Parallel β gather data simultaneously
fetcher = ParallelAgent(sub_agents=[
DataSourceAgent1,
DataSourceAgent2,
DataSourceAgent3
])
# Quality loop β iterate until condition
refiner = LoopAgent(
sub_agent=CodeRefineAgent,
max_iterations=5
)CustomAgent β Full Control β
class MyOrchestrator(BaseAgent):
async def _run_async_impl(self, ctx: InvocationContext):
# Arbitrary Python logic β conditionals, loops, anything
if ctx.session.state.get('user_tier') == 'enterprise':
async for event in self.enterprise_agent.run_async(ctx):
yield event
else:
async for event in self.standard_agent.run_async(ctx):
yield eventUse when: your routing logic is too complex for a RouterAgent, or you need to mix deterministic and LLM-driven steps in ways WorkflowAgents cannot express.
5.4 Tools and MCP β
Function Tools β The Standard β
def lookup_order(order_id: str, tool_context: ToolContext) -> dict:
"""
Looks up the status of a customer order.
Use this when the user asks about their order, package, or shipment.
Args:
order_id: The order number (e.g., "ORD-12345")
"""
# The docstring IS the tool definition the LLM reads
result = db.query(order_id)
tool_context.state['last_lookup'] = order_id # update session state
return resultCritical
The docstring is not documentation for you β it is the tool specification for the LLM. Write it for the model: when to use this, what each param means, what format to expect.
MCP (Model Context Protocol) β Enterprise Integration β
Your Agent
|
McpToolset <-- connects to --> MCP Server (e.g., "Salesforce MCP Server")
|
list_tools() -> dynamically discovered
call_tool() -> executed on serverMCP is how you connect to enterprise systems (Salesforce, ServiceNow, SAP) without writing custom tool code for each. The MCP server exposes them; ADK discovers and uses them automatically.
Think of it like USB-C β one port standard for all devices.
5.5 Callbacks β Your Guardrails β
The Intuition β
Callbacks are security checkpoints at key execution stages. Like a TSA checkpoint β they run before/after critical operations and can block execution.
before_agent_callback -> Agent is about to start. Check user permissions?
before_model_callback -> About to call LLM. Scrub PII from input?
after_model_callback -> LLM responded. Filter toxic content?
before_tool_callback -> About to execute tool. Is user authorized?
after_tool_callback -> Tool returned data. Log it? Sanitize it?Control Mechanism β
def guard_pii(callback_context: CallbackContext, request: LlmRequest):
if contains_pii(request.user_input):
# Return a response --> BLOCKS the actual LLM call
return LlmResponse(text="I can't process personal information.")
return None # Return None --> ALLOWS execution to continueReturn None = proceed. Return anything = override.
Production Use Cases β
before_model_callback: PII scrubbing, prompt injection detectionbefore_tool_callback: Authorization check (is this user allowed to call DELETE?)after_model_callback: Content safety filtering on outputafter_tool_callback: Audit logging for compliance
Recall Hook β
Return None = let it through. Return object = intercept and replace.
Sources β
- Google ADK Documentation: ai.google.dev/adk
- Google ADK Whitepaper: Introduction to Agents
Have a production ADK pattern worth sharing? Add it here β reviewed before publishing.