# Practical and Useful Patterns with ADK
## ๐๏ธ Take Full Control of Agent Behavior with Callbacks
Want to add guardrails before LLM calls? Validate tool inputs? Cache responses? ADK's **Callbacks** let you hook into every critical execution point! ๐ง
## ๐ Title
Callbacks
## ๐ URL
## ๐งฉ Overview
Callbacks are functions you attach to agents to observe, customize, and control behavior at specific execution points. The ADK framework automatically invokes them at key stages without requiring core framework modifications.
Six callback types are available:
- **before_agent / after_agent**: Before and after agent execution
- **before_model / after_model**: Before and after LLM calls
- **before_tool / after_tool**: Before and after tool execution
The key mechanism is **flow control via return values**: return `None` to continue normally, or return a specific object to skip the subsequent step entirely.
## ๐ How to Use
**Skip LLM call (input guardrail / cache) โ return `LlmResponse`:**
Define a `before_model_callback` function that accepts `CallbackContext` and `LlmRequest` and returns `Optional[LlmResponse]`. It extracts the user's last message via `llm_request.contents[-1].parts[0].text`, checks for a forbidden topic, and if found, returns an `LlmResponse` wrapping `Content(role="model")` with a rejection message to skip the LLM call. If the input is acceptable, it returns `None` to proceed normally.
**Skip tool execution (validation / mock) โ return `Dict`:**
Define a `before_tool_callback` that takes `context`, `tool`, and `args`, returning `Optional[Dict]`. It calls `validate_args(args)` and if validation fails, returns `{"error": "Invalid arguments"}` to skip tool execution. Otherwise, it returns `None` to proceed normally.
**Skip agent execution โ return `Content`:**
Define a `before_agent_callback` that takes `context` and returns `Optional[Content]`. It checks `is_authorized(context)`, and if the user lacks permission, returns `Content(role="model", parts=[Part(text="Access denied")])` to skip agent execution. If authorized, it returns `None` to continue.
## ๐ Practical Usage
**Combined input guardrail + response cache:**
Define a `smart_before_model` function taking `ctx` and `req`, returning `Optional[LlmResponse]`. In Step 1, it extracts user input from `req.contents[-1].parts[0].text` and checks for PII using `contains_pii()`. If PII is detected, it returns an `LlmResponse` with a rejection message to skip the LLM call. In Step 2, it generates a `cache_key` via `hash(user_input)` and looks up `ctx.state.get(f"cache:{cache_key}")`. On a cache hit, it returns the cached text wrapped in an `LlmResponse`. On a cache miss, it returns `None` to proceed to the LLM. Finally, an `LlmAgent` is created with `name="SecureAgent"`, `model="gemini-2.0-flash"`, and `before_model_callback=smart_before_model` to register this callback.
## ๐ก Use Cases
- ๐ก๏ธ **Input guardrails**: Block inappropriate inputs or prompt injection before LLM calls
- ๐พ **Response caching**: Serve cached responses for repeated queries to cut costs
- ๐ **Debug logging**: Record requests/responses at each execution point
- โ
**Tool validation**: Pre-validate tool arguments to prevent errors
- ๐งช **Test mocking**: Return mock responses instead of calling real tools
## โ ๏ธ Caveats
- Callbacks execute synchronously; avoid heavy operations like external API calls
- Returning a value from `before_*` completely skips downstream processing โ be intentional
- For cross-agent security guardrails, consider **Plugins** instead of per-agent callbacks
- Always wrap callback logic in try-except to prevent callback errors from crashing the entire agent
## โจ Closing
ADK Callbacks provide surgical control over agent execution flow. Guardrails, caching, logging, validation โ implement any cross-cutting concern without polluting core logic. Start with `before_model_callback` and build from there!
#
ADK# #
AIAgent#