feat: Added a new tutorial on plugins, covering global callback management and practical examples for logging, monitoring, and request modification for google ADK crash course

This commit is contained in:
Shubhamsaboo 2025-08-07 01:02:36 -05:00
parent 7753b7d451
commit 3478406ea6
6 changed files with 383 additions and 1 deletions

View file

@ -0,0 +1,3 @@
# If using Gemini via Google AI Studio
GOOGLE_GENAI_USE_VERTEXAI=False
GOOGLE_API_KEY="your-api-key"

View file

@ -0,0 +1,200 @@
# 🔌 Tutorial 7: Plugins
## 🎯 What You'll Learn
- **Plugin Fundamentals**: Understanding what plugins are and how they work
- **Global Callback Management**: Using plugins for cross-cutting concerns
- **Practical Plugin Examples**: Logging, monitoring, and request modification
## 💡 Core Concept: Plugins
Plugins in Google ADK are custom code modules that can be executed at various stages of an agent workflow lifecycle using callback hooks. Unlike regular callbacks that are configured on individual agents or tools, plugins are registered once on the `Runner` and apply globally to every agent, tool, and LLM call managed by that runner.
### **Plugin vs Callback Comparison**
```
┌─────────────────┐ ┌─────────────────┐
│ Regular │ │ Plugin │
│ Callbacks │ │ Callbacks │
├─────────────────┤ ├─────────────────┤
│ • Per agent │ │ • Global scope │
│ • Per tool │ │ • Runner level │
│ • Specific task │ │ • Cross-cutting │
│ • Local effect │ │ • Reusable │
└─────────────────┘ └─────────────────┘
```
### **Plugin Lifecycle Hooks**
```
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ User Message │───▶│ Runner Start │───▶│ Agent Execute │
│ Callback │ │ Callback │ │ Callback │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Model Callback │ │ Tool Callback │ │ Event Callback │
│ (before/after)│ │ (before/after)│ │ (modify event)│
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Error Handling │ │ Runner End │ │ Cleanup Tasks │
│ Callback │ │ Callback │ │ & Reporting │
└─────────────────┘ └─────────────────┘ └─────────────────┘
```
### **Why Use Plugins?**
- **Cross-cutting Concerns**: Implement functionality that applies across your entire application
- **Reusability**: Package related callback functions together for reuse
- **Global Monitoring**: Track all agent, tool, and model interactions
- **Policy Enforcement**: Implement security guardrails and access controls
- **Logging & Metrics**: Centralized logging and performance monitoring
- **Request/Response Modification**: Dynamically modify inputs and outputs
## 📖 Tutorial Overview
This tutorial demonstrates how to create and use plugins in Google ADK through a practical example that combines multiple use cases:
### **Demo Plugin Features**
1. **Request Logging**: Log all user messages with timestamps
2. **Request Modification**: Add timestamp context to user messages
3. **Agent Tracking**: Count and monitor agent executions
4. **Tool Monitoring**: Track tool usage and handle errors
5. **Final Reporting**: Generate summary of plugin activity
## 📁 Project Structure
```
7_plugins/
├── README.md # This file - concept explanation
├── agent.py # Agent implementation with plugin
├── app.py # Streamlit interface
├── requirements.txt # Dependencies
└── plugin_example.py # Standalone plugin demonstration
```
## 🎯 Learning Objectives
By the end of this tutorial, you'll understand:
- ✅ **Plugin Architecture**: How plugins extend the BasePlugin class
- ✅ **Global Scope**: How plugins apply across all agents and tools
- ✅ **Callback Hooks**: Available plugin callback methods and their timing
- ✅ **Practical Applications**: Real-world use cases for plugins
- ✅ **Error Handling**: How to implement graceful error recovery
- ✅ **Simple Implementation**: Easy-to-understand plugin structure
## 🚀 Getting Started
### **Prerequisites**
- Python 3.11+
- Google AI Studio API key
- Basic understanding of Google ADK (Tutorials 1-6)
### **Setup**
1. **Get API Key**: Visit [Google AI Studio](https://aistudio.google.com/)
2. **Create .env file**: Create a file named `.env` in this directory with:
```
GOOGLE_API_KEY=your_google_ai_studio_api_key_here
```
3. **Install dependencies**: `pip install -r requirements.txt`
**Important**:
- Make sure your `.env` file is in the same directory as the tutorial files
- Replace `your_google_ai_studio_api_key_here` with your actual API key
- The `.env` file should not be committed to version control
### **Run Tutorial**
```bash
cd 7_plugins
streamlit run app.py
```
## 🔧 Key Concepts
### **Plugin Class Structure**
```python
from google.adk.plugins.base_plugin import BasePlugin
class MyPlugin(BasePlugin):
def __init__(self):
super().__init__(name="my_plugin")
# Initialize plugin state
async def before_agent_callback(self, *, agent, callback_context):
# Called before each agent execution
pass
async def after_model_callback(self, *, callback_context, llm_response):
# Called after each model call
pass
```
### **Plugin Registration**
```python
from google.adk.runners import InMemoryRunner
runner = InMemoryRunner(
agent=my_agent,
app_name="my_app",
plugins=[MyPlugin()] # Register plugins here
)
```
### **Available Callback Hooks**
- `on_user_message_callback`: Modify user input
- `before_run_callback`: Setup before execution
- `before_agent_callback` / `after_agent_callback`: Agent lifecycle
- `before_model_callback` / `after_model_callback`: Model interactions
- `before_tool_callback` / `after_tool_callback`: Tool execution
- `on_model_error_callback` / `on_tool_error_callback`: Error handling
- `on_event_callback`: Modify events before streaming
- `after_run_callback`: Cleanup after execution
## 🎯 Use Cases
### **Common Plugin Applications**
1. **Logging & Tracing**: Detailed logs for debugging and analysis
2. **Policy Enforcement**: Security guardrails and access controls
3. **Monitoring & Metrics**: Performance tracking and analytics
4. **Response Caching**: Cache expensive operations
5. **Request/Response Modification**: Add context or standardize outputs
6. **Error Recovery**: Graceful handling of failures
7. **Usage Analytics**: Track patterns and usage statistics
## 🚨 Important Notes
- **Plugin Precedence**: Plugin callbacks run **before** agent-level callbacks
- **Global Scope**: Plugins affect **all** agents, tools, and models in the runner
- **Web Interface**: Plugins are **not supported** by the ADK web interface
- **Error Handling**: Plugin error callbacks can suppress exceptions and provide fallbacks
## 📚 Next Steps
After completing this tutorial, you can:
- Create custom plugins for your specific use cases
- Implement monitoring and analytics plugins
- Build security and policy enforcement plugins
- Explore advanced plugin patterns and combinations
## 🔧 Troubleshooting
### **Common Issues**
**"Missing key inputs argument" Error**
- Ensure you have created a `.env` file with your Google AI Studio API key
- Verify the API key is valid and has the necessary permissions
- Check that the `.env` file is in the same directory as the tutorial files
**Import Errors**
- Make sure you have installed all dependencies: `pip install -r requirements.txt`
- Verify you're using Python 3.11 or higher
**Plugin Not Working**
- Remember that plugins are not supported by the ADK web interface
- Ensure you're running the agent through the Streamlit app or Python script
## 🔗 Additional Resources
- [Google ADK Plugins Documentation](https://google.github.io/adk-docs/plugins/)
- [Plugin Callback Hooks](https://google.github.io/adk-docs/plugins/#plugin-callback-hooks)
- [BasePlugin API Reference](https://google.github.io/adk-docs/api/python/google.adk.plugins.base_plugin.BasePlugin)

View file

@ -0,0 +1,105 @@
import asyncio
from datetime import datetime
from typing import Optional, Dict, Any
from google.adk.agents import LlmAgent
from google.adk.agents.base_agent import BaseAgent
from google.adk.agents.callback_context import CallbackContext
from google.adk.plugins.base_plugin import BasePlugin
from google.adk.runners import InMemoryRunner
from google.adk.tools.base_tool import BaseTool
from google.adk.tools.tool_context import ToolContext
from google.genai import types
from dotenv import load_dotenv
# Load environment variables (API key)
load_dotenv()
# ============================================================================
# PLUGIN DEFINITION
# ============================================================================
# Plugins extend BasePlugin and provide global callbacks across all agents/tools
class SimplePlugin(BasePlugin):
def __init__(self) -> None:
super().__init__(name="simple_plugin")
# Track usage statistics across all executions
self.agent_count = 0
self.tool_count = 0
# Called when user sends a message - can modify the input
async def on_user_message_callback(self, *, invocation_context, user_message: types.Content) -> Optional[types.Content]:
timestamp = datetime.now().strftime("%H:%M:%S")
print(f"🔍 [Plugin] User message at {timestamp}")
# Add timestamp to each message part for context
modified_parts = [types.Part(text=f"[{timestamp}] {part.text}") for part in user_message.parts if hasattr(part, 'text')]
return types.Content(role='user', parts=modified_parts)
# Called before each agent execution - good for logging and setup
async def before_agent_callback(self, *, agent: BaseAgent, callback_context: CallbackContext) -> None:
self.agent_count += 1
print(f"🤖 [Plugin] Agent {agent.name} starting (count: {self.agent_count})")
# Called before each tool execution - track tool usage
async def before_tool_callback(self, *, tool: BaseTool, tool_args: Dict[str, Any], tool_context: ToolContext) -> None:
self.tool_count += 1
print(f"🔧 [Plugin] Tool {tool.name} starting (count: {self.tool_count})")
# Called after the entire run completes - generate final report
async def after_run_callback(self, *, invocation_context) -> None:
print(f"📊 [Plugin] Final Report: {self.agent_count} agents, {self.tool_count} tools")
# ============================================================================
# TOOL DEFINITION
# ============================================================================
# This tool can fail (division by zero) to demonstrate error handling
async def calculator_tool(tool_context: ToolContext, operation: str, a: float, b: float) -> Dict[str, Any]:
print(f"🔧 [Tool] Calculator: {operation}({a}, {b})")
if operation == "divide" and b == 0:
raise ValueError("Division by zero is not allowed")
# Dictionary of operations for cleaner code
ops = {"add": lambda x, y: x + y, "subtract": lambda x, y: x - y, "multiply": lambda x, y: x * y, "divide": lambda x, y: x / y}
if operation not in ops:
raise ValueError(f"Unknown operation: {operation}")
return {"operation": operation, "a": a, "b": b, "result": ops[operation](a, b)}
# ============================================================================
# AGENT AND RUNNER SETUP
# ============================================================================
# Create agent with the calculator tool
agent = LlmAgent(name="plugin_demo_agent", model="gemini-2.0-flash",
instruction="You are a helpful assistant that can perform calculations. Use the calculator_tool when needed.",
tools=[calculator_tool])
# Create runner and register the plugin - this makes the plugin global
runner = InMemoryRunner(agent=agent, app_name="plugin_demo_app", plugins=[SimplePlugin()])
# ============================================================================
# AGENT EXECUTION FUNCTION
# ============================================================================
async def run_agent(message: str) -> str:
# Session management for conversation state
user_id, session_id = "demo_user", "demo_session"
session_service = runner.session_service
# Get or create session (required for ADK)
session = await session_service.get_session(app_name="plugin_demo_app", user_id=user_id, session_id=session_id)
if not session:
session = await session_service.create_session(app_name="plugin_demo_app", user_id=user_id, session_id=session_id)
# Create user message content
user_content = types.Content(role='user', parts=[types.Part(text=message)])
# Run agent and collect response - plugin callbacks will fire automatically
response_text = ""
async for event in runner.run_async(user_id=user_id, session_id=session_id, new_message=user_content):
if event.content and event.content.parts:
for part in event.content.parts:
if hasattr(part, 'text') and part.text:
response_text += part.text
return response_text if response_text else "No response received from agent."
# ============================================================================
# MAIN EXECUTION
# ============================================================================
if __name__ == "__main__":
# Test the plugin functionality
asyncio.run(run_agent("what is 2 + 2?"))

View file

@ -0,0 +1,64 @@
import streamlit as st
import asyncio
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent))
from agent import run_agent
st.set_page_config(page_title="Google ADK Plugins Tutorial", page_icon="🔌")
st.title("🔌 Google ADK Plugins Tutorial")
st.markdown("Demonstrates plugins for cross-cutting concerns like logging and monitoring.")
test_scenarios = {
"Normal Conversation": "Hello! How are you?",
"Simple Calculation": "Calculate 15 + 27",
"Error Handling": "What is 10 divided by 0?"
}
selected_scenario = st.selectbox("Choose a test scenario:", list(test_scenarios.keys()))
if st.button("🚀 Run Test"):
with st.spinner("Running..."):
try:
response = asyncio.run(run_agent(test_scenarios[selected_scenario]))
st.success("**Agent Response:**")
st.write(response)
except Exception as e:
st.error(f"Error: {str(e)}")
st.markdown("---")
custom_message = st.text_area("Or enter your own message:", placeholder="Type here...")
if st.button("🚀 Run Custom Message"):
if custom_message.strip():
with st.spinner("Processing..."):
try:
response = asyncio.run(run_agent(custom_message))
st.success("**Agent Response:**")
st.write(response)
except Exception as e:
st.error(f"Error: {str(e)}")
else:
st.warning("Please enter a message.")
with st.expander("📚 About Plugins"):
st.markdown("""
**Plugins** are custom code modules that execute at various stages of agent workflow lifecycle.
**Key Features:**
- 🔍 Request logging and modification
- 🤖 Agent execution tracking
- 🔧 Tool usage monitoring
- 📊 Final reporting and analytics
**Plugin Callbacks:**
- `on_user_message_callback()` - Modify user input
- `before_agent_callback()` - Track agent starts
- `before_tool_callback()` - Track tool usage
- `after_run_callback()` - Generate reports
""")
st.markdown("---")
st.markdown("*Part of the Google ADK Crash Course*")

View file

@ -0,0 +1,4 @@
google-genai>=1.28.0
google-adk>=1.9.0
streamlit>=1.47.1
python-dotenv>=1.1.1

View file

@ -48,7 +48,13 @@ This crash course covers the essential concepts of Google ADK through hands-on t
- **[6.2 LLM Interaction Callbacks](./6_callbacks/6_2_llm_interaction_callbacks/README.md)** - Track model requests and responses
- **[6.3 Tool Execution Callbacks](./6_callbacks/6_3_tool_execution_callbacks/README.md)** - Monitor tool calls and results
7. **More tutorials coming soon!**
7. **[7_plugins](./7_plugins/README.md)** - Plugin system for cross-cutting concerns
- Global callback management
- Request/response modification
- Error handling and logging
- Usage analytics and monitoring
8. **More tutorials coming soon!**
## 🛠️ Prerequisites