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:
parent
7753b7d451
commit
3478406ea6
6 changed files with 383 additions and 1 deletions
|
|
@ -0,0 +1,3 @@
|
|||
# If using Gemini via Google AI Studio
|
||||
GOOGLE_GENAI_USE_VERTEXAI=False
|
||||
GOOGLE_API_KEY="your-api-key"
|
||||
|
|
@ -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)
|
||||
|
|
@ -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?"))
|
||||
|
|
@ -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*")
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
google-genai>=1.28.0
|
||||
google-adk>=1.9.0
|
||||
streamlit>=1.47.1
|
||||
python-dotenv>=1.1.1
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue