996 lines
36 KiB
Markdown
996 lines
36 KiB
Markdown
# ADK Python Cheatsheet
|
|
|
|
## 1. Core Concepts & Project Structure
|
|
|
|
### Essential Primitives
|
|
|
|
* **`Agent`**: The core intelligent unit. Can be `LlmAgent` (LLM-driven) or `BaseAgent` (custom/workflow).
|
|
* **`Tool`**: Callable function providing external capabilities (`FunctionTool`, `AgentTool`, etc.).
|
|
* **`Session`**: A stateful conversation thread with history (`events`) and short-term memory (`state`).
|
|
* **`State`**: Key-value dictionary within a `Session` for transient conversation data.
|
|
* **`Runner`**: The execution engine; orchestrates agent activity and event flow.
|
|
* **`Event`**: Atomic unit of communication; carries content and side-effect `actions`.
|
|
|
|
### Standard Project Layout
|
|
|
|
```
|
|
your_project_root/
|
|
├── <agent_name>/ or app/ # Agent code directory
|
|
│ ├── __init__.py
|
|
│ ├── agent.py # Contains root_agent definition
|
|
│ ├── tools.py # Custom tool functions
|
|
│ └── .env # Environment variables
|
|
├── tests/
|
|
│ ├── eval/
|
|
│ │ ├── eval_config.yaml # Eval criteria and thresholds
|
|
│ │ └── datasets/ # Eval datasets (JSON)
|
|
│ ├── integration/
|
|
│ └── unit/
|
|
└── pyproject.toml or requirements.txt
|
|
```
|
|
|
|
---
|
|
|
|
## 2. Agent Definitions (`LlmAgent`)
|
|
|
|
### Basic Setup
|
|
|
|
```python
|
|
from google.adk.agents import Agent
|
|
|
|
def get_weather(city: str) -> dict:
|
|
"""Returns weather for a city."""
|
|
return {"status": "success", "weather": "sunny", "temp": 72}
|
|
|
|
my_agent = Agent(
|
|
name="weather_agent",
|
|
model="gemini-3.7-flash",
|
|
instruction="You help users check the weather. Use the get_weather tool.",
|
|
description="Provides weather information.", # Important for multi-agent delegation
|
|
tools=[get_weather]
|
|
)
|
|
```
|
|
|
|
### Key Configuration Options
|
|
|
|
```python
|
|
from google.genai import types as genai_types
|
|
from google.adk.agents import Agent
|
|
|
|
agent = Agent(
|
|
name="my_agent",
|
|
model="gemini-3.7-flash",
|
|
instruction="Your instructions here. Use {state_key} for dynamic injection.",
|
|
description="Description for delegation.",
|
|
|
|
# LLM generation parameters
|
|
generate_content_config=genai_types.GenerateContentConfig(
|
|
temperature=0.2,
|
|
max_output_tokens=1024,
|
|
),
|
|
|
|
# Save final output to state
|
|
output_key="agent_response",
|
|
|
|
# Control history sent to LLM
|
|
include_contents='default', # 'default' or 'none'
|
|
|
|
# Delegation control
|
|
disallow_transfer_to_parent=False,
|
|
disallow_transfer_to_peers=False,
|
|
|
|
# Sub-agents for delegation
|
|
sub_agents=[specialist_agent],
|
|
|
|
# Tools
|
|
tools=[my_tool],
|
|
|
|
# Callbacks
|
|
before_agent_callback=my_callback,
|
|
after_agent_callback=my_callback,
|
|
before_model_callback=my_callback,
|
|
after_model_callback=my_callback,
|
|
before_tool_callback=my_callback,
|
|
after_tool_callback=my_callback,
|
|
)
|
|
```
|
|
|
|
### Structured Output with Pydantic
|
|
|
|
> **Warning**: Using `output_schema` disables tool calling and delegation.
|
|
|
|
```python
|
|
from pydantic import BaseModel, Field
|
|
from typing import Literal
|
|
|
|
class Evaluation(BaseModel):
|
|
grade: Literal["pass", "fail"] = Field(description="The evaluation result.")
|
|
comment: str = Field(description="Explanation of the grade.")
|
|
|
|
evaluator = Agent(
|
|
name="evaluator",
|
|
model="gemini-3.7-flash",
|
|
instruction="Evaluate the input and provide structured feedback.",
|
|
output_schema=Evaluation,
|
|
output_key="evaluation_result",
|
|
)
|
|
```
|
|
|
|
### Instruction Best Practices
|
|
|
|
```python
|
|
# Use dynamic state injection with {state_key} placeholders
|
|
instruction = """
|
|
You are a {role} assistant.
|
|
User preferences: {user_preferences}
|
|
|
|
Rules:
|
|
- Always use tools when available
|
|
- Never make up information
|
|
"""
|
|
```
|
|
|
|
---
|
|
|
|
## 3. Orchestration with Workflow Agents
|
|
|
|
Workflow agents provide deterministic control flow without LLM orchestration.
|
|
|
|
> These are `BaseAgent`-family composites (`SequentialAgent`, `ParallelAgent`, `LoopAgent`). For the new graph-based Workflow API introduced in ADK 2.0, see `references/adk-workflows.md`.
|
|
|
|
### SequentialAgent
|
|
|
|
Executes sub-agents in order. State changes propagate to subsequent agents.
|
|
|
|
```python
|
|
from google.adk.agents import SequentialAgent, Agent
|
|
|
|
summarizer = Agent(
|
|
name="summarizer",
|
|
model="gemini-3.7-flash",
|
|
instruction="Summarize the input.",
|
|
output_key="summary"
|
|
)
|
|
|
|
question_gen = Agent(
|
|
name="question_generator",
|
|
model="gemini-3.7-flash",
|
|
instruction="Generate questions based on: {summary}"
|
|
)
|
|
|
|
pipeline = SequentialAgent(
|
|
name="pipeline",
|
|
sub_agents=[summarizer, question_gen],
|
|
)
|
|
```
|
|
|
|
### ParallelAgent
|
|
|
|
Executes sub-agents concurrently. Use distinct `output_key`s to avoid race conditions.
|
|
|
|
```python
|
|
from google.adk.agents import ParallelAgent, SequentialAgent, Agent
|
|
|
|
fetch_a = Agent(name="fetch_a", ..., output_key="data_a")
|
|
fetch_b = Agent(name="fetch_b", ..., output_key="data_b")
|
|
|
|
merger = Agent(
|
|
name="merger",
|
|
instruction="Combine data_a: {data_a} and data_b: {data_b}"
|
|
)
|
|
|
|
pipeline = SequentialAgent(
|
|
name="full_pipeline",
|
|
sub_agents=[
|
|
ParallelAgent(name="fetchers", sub_agents=[fetch_a, fetch_b]),
|
|
merger
|
|
]
|
|
)
|
|
```
|
|
|
|
### LoopAgent
|
|
|
|
Repeats sub-agents until `max_iterations` or an event with `escalate=True`.
|
|
|
|
```python
|
|
from google.adk.agents import LoopAgent
|
|
|
|
refinement_loop = LoopAgent(
|
|
name="refinement_loop",
|
|
sub_agents=[evaluator, refiner, escalation_checker],
|
|
max_iterations=5,
|
|
)
|
|
```
|
|
|
|
For a production LoopAgent with EscalationChecker, BuiltInPlanner, and grounding citations, look it up in the topic index in `references/samples.md`.
|
|
|
|
---
|
|
|
|
## 4. Multi-Agent Systems & Communication
|
|
|
|
### Communication Methods
|
|
|
|
1. **Shared State**: Agents read/write `session.state`. Use `output_key` for convenience.
|
|
|
|
2. **LLM Delegation**: Agent transfers control to a sub-agent based on reasoning.
|
|
```python
|
|
coordinator = Agent(
|
|
name="coordinator",
|
|
instruction="Route to sales_agent for sales, support_agent for help.",
|
|
sub_agents=[sales_agent, support_agent],
|
|
)
|
|
```
|
|
|
|
3. **AgentTool**: Invoke another agent as a tool (parent stays in control).
|
|
```python
|
|
from google.adk.tools import AgentTool
|
|
|
|
root = Agent(
|
|
name="root",
|
|
tools=[AgentTool(specialist_agent)],
|
|
)
|
|
```
|
|
|
|
4. **Task Delegation (ADK 2.0)**: Set `mode` on a sub-agent for structured, schema-typed delegation — the coordinator gets a `request_task_{name}` tool; the sub-agent returns typed output via the auto-injected `finish_task` tool.
|
|
```python
|
|
from pydantic import BaseModel
|
|
|
|
class ResearchOutput(BaseModel):
|
|
summary: str
|
|
|
|
researcher = Agent(
|
|
name="researcher",
|
|
model="gemini-3.7-flash",
|
|
mode="task", # 'chat' (default) | 'task' | 'single_turn'
|
|
output_schema=ResearchOutput,
|
|
description="Researches a topic.", # required for delegation
|
|
instruction="Research the topic, then call finish_task.",
|
|
)
|
|
coordinator = Agent(name="coordinator", model="gemini-3.7-flash", sub_agents=[researcher])
|
|
```
|
|
Modes: `task` (multi-turn, structured I/O) · `single_turn` (autonomous, no user turn). Sub-agents need a `description`; default I/O schemas (`goal`/`background` in, `result` out) are used if none set. Disabled inside graph `Workflow`s.
|
|
|
|
---
|
|
|
|
## 5. Building Custom Agents (`BaseAgent`)
|
|
|
|
For custom orchestration logic beyond workflow agents.
|
|
|
|
```python
|
|
from google.adk.agents import BaseAgent
|
|
from google.adk.agents.invocation_context import InvocationContext
|
|
from google.adk.events import Event, EventActions
|
|
from typing import AsyncGenerator
|
|
|
|
class ConditionalRouter(BaseAgent):
|
|
async def _run_async_impl(
|
|
self, ctx: InvocationContext
|
|
) -> AsyncGenerator[Event, None]:
|
|
# Read state
|
|
user_type = ctx.session.state.get("user_type", "regular")
|
|
|
|
# Custom routing logic
|
|
if user_type == "premium":
|
|
agent = self.premium_agent
|
|
else:
|
|
agent = self.regular_agent
|
|
|
|
# Run selected agent
|
|
async for event in agent.run_async(ctx):
|
|
yield event
|
|
|
|
class EscalationChecker(BaseAgent):
|
|
"""Stops a LoopAgent when condition is met."""
|
|
async def _run_async_impl(
|
|
self, ctx: InvocationContext
|
|
) -> AsyncGenerator[Event, None]:
|
|
result = ctx.session.state.get("evaluation")
|
|
if result and result.get("grade") == "pass":
|
|
yield Event(author=self.name, actions=EventActions(escalate=True))
|
|
else:
|
|
yield Event(author=self.name)
|
|
```
|
|
|
|
---
|
|
|
|
## 6. Models Configuration
|
|
|
|
### Google Gemini (Default)
|
|
|
|
```python
|
|
# AI Studio (dev): in the project .env, comment the GOOGLE_* lines and
|
|
# uncomment GEMINI_API_KEY (GOOGLE_API_KEY is also accepted).
|
|
|
|
# Vertex AI (prod)
|
|
# Set: GOOGLE_CLOUD_PROJECT, GOOGLE_CLOUD_LOCATION, GOOGLE_GENAI_USE_VERTEXAI=True
|
|
|
|
agent = Agent(model="gemini-3.7-flash", ...)
|
|
```
|
|
|
|
### Other Models via LiteLLM
|
|
|
|
```python
|
|
from google.adk.models.lite_llm import LiteLlm
|
|
|
|
agent = Agent(model=LiteLlm(model="openai/gpt-4o"), ...)
|
|
agent = Agent(model=LiteLlm(model="anthropic/claude-sonnet-4-20250514"), ...)
|
|
agent = Agent(model=LiteLlm(model="ollama_chat/llama3:instruct"), ...)
|
|
```
|
|
|
|
### Vertex AI Native Models
|
|
|
|
```python
|
|
from google.adk.models import Gemini
|
|
|
|
# Vertex AI hosted Gemini (set GOOGLE_GENAI_USE_VERTEXAI=True)
|
|
agent = Agent(model=Gemini(model="gemini-3.7-flash"), ...)
|
|
```
|
|
|
|
Provider guides: [Anthropic](https://adk.dev/agents/models/anthropic/index.md), [Ollama](https://adk.dev/agents/models/ollama/index.md), [vLLM](https://adk.dev/agents/models/vllm/index.md), [LiteLLM](https://adk.dev/agents/models/litellm/index.md)
|
|
|
|
---
|
|
|
|
## 7. Tools: The Agent's Capabilities
|
|
|
|
### Function Tool Basics
|
|
|
|
```python
|
|
from google.adk.tools import ToolContext
|
|
|
|
def search_database(
|
|
query: str,
|
|
limit: int,
|
|
tool_context: ToolContext # Optional, for state access
|
|
) -> dict:
|
|
"""Searches the database for records matching the query.
|
|
|
|
Args:
|
|
query: The search query string.
|
|
limit: Maximum number of results to return.
|
|
|
|
Returns:
|
|
dict with 'status' and 'results' keys.
|
|
"""
|
|
# Access state if needed
|
|
user_id = tool_context.state.get("user_id")
|
|
|
|
# Tool logic here
|
|
results = db.search(query, limit=limit, user=user_id)
|
|
|
|
return {"status": "success", "results": results}
|
|
```
|
|
|
|
**Tool Rules:**
|
|
- Use clear docstrings (sent to LLM)
|
|
- Type hints required, NO default values
|
|
- Return a dict (JSON-serializable)
|
|
- Don't mention `tool_context` in docstring
|
|
|
|
### ToolContext Capabilities
|
|
|
|
```python
|
|
async def my_tool(query: str, tool_context: ToolContext) -> dict:
|
|
# Read/write state
|
|
tool_context.state["key"] = "value"
|
|
|
|
# Trigger escalation (stops LoopAgent)
|
|
tool_context.actions.escalate = True
|
|
|
|
# Artifacts — see Artifacts section below for full API
|
|
await tool_context.save_artifact("file.txt", part)
|
|
|
|
# Memory search
|
|
results = await tool_context.search_memory("query")
|
|
|
|
return {"status": "success"}
|
|
```
|
|
|
|
### Built-in Tools
|
|
|
|
```python
|
|
from google.adk.tools import google_search
|
|
from google.adk.tools import VertexAiSearchTool
|
|
from google.adk.tools.load_web_page import load_web_page
|
|
from google.adk.code_executors import BuiltInCodeExecutor
|
|
|
|
# Google Search grounding
|
|
agent = Agent(tools=[google_search], ...)
|
|
|
|
# Agent Platform Search grounding (your own data)
|
|
agent = Agent(tools=[VertexAiSearchTool(data_store_id="projects/P/locations/L/collections/default_collection/dataStores/DS")], ...)
|
|
|
|
# Web page loading
|
|
agent = Agent(tools=[load_web_page], ...)
|
|
|
|
# Code execution (model-internal)
|
|
agent = Agent(code_executor=BuiltInCodeExecutor(), ...)
|
|
|
|
# Managed sandbox (Vertex AI Code Interpreter). For a per-user sandbox an agent works
|
|
# in across sessions, this primitive is not it — see the topic index in references/samples.md
|
|
# from google.adk.code_executors import VertexAiCodeExecutor
|
|
# agent = Agent(code_executor=VertexAiCodeExecutor(optimize_data_file=True, stateful=True), ...)
|
|
|
|
```
|
|
|
|
> **`google_search` is model-internal grounding, not a regular tool.** Mixing it with FunctionTools disables Automatic Function Calling (AFC) for all tools. If you need search alongside custom tools, consider a sub-agent architecture or a custom search function — see the [deep-search sample](https://github.com/google/adk-samples/tree/main/core/python/deep-search) for a working pattern. For eval implications, see the eval guide's `builtin-tools-eval` reference.
|
|
|
|
### Tool Confirmation
|
|
|
|
```python
|
|
from google.adk.tools import FunctionTool
|
|
|
|
# Simple confirmation
|
|
sensitive_tool = FunctionTool(delete_record, require_confirmation=True)
|
|
|
|
# Conditional confirmation
|
|
def needs_approval(amount: float, **kwargs) -> bool:
|
|
return amount > 1000
|
|
|
|
transfer_tool = FunctionTool(transfer_money, require_confirmation=needs_approval)
|
|
```
|
|
|
|
### Human-in-the-Loop (pause & resume)
|
|
|
|
Pause a run to ask the user something, then resume. This is a general runtime feature (not workflow-specific). Enable resumption at the app level:
|
|
|
|
```python
|
|
from google.adk.apps import App, ResumabilityConfig
|
|
|
|
app = App(name="my_app", root_agent=root_agent,
|
|
resumability_config=ResumabilityConfig(is_resumable=True))
|
|
```
|
|
|
|
- **Let the model ask:** add the built-in `request_input` tool (`from google.adk.tools import request_input`) to `tools=` — the model calls it when it needs clarification.
|
|
- **Approval gate inside a tool:** `tool_context.request_confirmation(hint="Approve this transfer?")`, or `FunctionTool(fn, require_confirmation=...)` (above).
|
|
- **Custom long-running tool:** wrap a function with `LongRunningFunctionTool(fn)` to pause until an external result arrives.
|
|
|
|
The user's reply is read from `ctx.resume_inputs` (available on `ToolContext` and `CallbackContext`). Inside graph workflows the same mechanism is node-based — see `adk-workflows.md` §7.
|
|
|
|
### Tool Authentication
|
|
|
|
| Auth Type | Pattern |
|
|
|-----------|---------|
|
|
| API Key | `token_to_scheme_credential("apikey", "query", "apikey", "KEY")` → `auth_scheme, auth_credential` |
|
|
| Service Account | `service_account_dict_to_scheme_credential(config, scopes=[...])` → `auth_scheme, auth_credential` |
|
|
| OAuth2 / OIDC | `AuthCredential(auth_type=AuthCredentialTypes.OAUTH2, oauth2=OAuth2Auth(client_id=..., client_secret=...))` |
|
|
| Custom FunctionTool | `tool_context.request_credential(AuthConfig(...))` to initiate, `tool_context.get_auth_response(AuthConfig(...))` to retrieve |
|
|
|
|
Helpers: `from google.adk.tools.openapi_tool.auth.auth_helpers import token_to_scheme_credential, service_account_dict_to_scheme_credential`. Pass `auth_scheme` + `auth_credential` to `OpenAPIToolset(...)`. [Full docs](https://adk.dev/tools-custom/authentication/)
|
|
|
|
### OpenAPI Tools
|
|
|
|
```python
|
|
from google.adk.tools.openapi_tool.openapi_spec_parser.openapi_toolset import OpenAPIToolset
|
|
|
|
toolset = OpenAPIToolset(spec_str=open("openapi.json").read(), spec_str_type="json")
|
|
agent = Agent(name="api_agent", tools=[toolset], ...)
|
|
```
|
|
|
|
Pass `auth_scheme` + `auth_credential` from the auth helpers above for authenticated APIs. Tool names derive from `operationId` (snake_case, max 60 chars). [Full docs](https://adk.dev/tools-custom/openapi-tools/index.md)
|
|
|
|
### MCP Tools
|
|
|
|
Connect to MCP servers to use external tools (needs the `mcp` extra: scaffolded projects ship `google-adk[gcp,otel-gcp]`, so add `mcp` and re-sync). Use `StdioConnectionParams` for local dev, `StreamableHTTPConnectionParams` for remote HTTP servers.
|
|
|
|
```python
|
|
from google.adk.tools.mcp_tool import McpToolset
|
|
from google.adk.tools.mcp_tool.mcp_session_manager import StdioConnectionParams, StreamableHTTPConnectionParams
|
|
from mcp import StdioServerParameters
|
|
|
|
# Local MCP server via stdio
|
|
agent = Agent(
|
|
name="my_agent",
|
|
tools=[
|
|
McpToolset(
|
|
connection_params=StdioConnectionParams(
|
|
server_params=StdioServerParameters(
|
|
command="npx",
|
|
args=["-y", "@modelcontextprotocol/server-filesystem", "/absolute/path"],
|
|
),
|
|
),
|
|
tool_filter=["list_directory", "read_file"], # optional: restrict exposed tools
|
|
)
|
|
],
|
|
...
|
|
)
|
|
|
|
# Remote MCP server, e.g. Cloud Run with --no-allow-unauthenticated. ID tokens
|
|
# expire in ~1h, so mint per call via `header_provider` (ADK calls it on every
|
|
# tool call); a static `headers` dict goes stale. Audience = root, not /mcp.
|
|
from google.auth.transport.requests import Request
|
|
from google.oauth2.id_token import fetch_id_token
|
|
|
|
McpToolset(
|
|
connection_params=StreamableHTTPConnectionParams(url=f"{MCP_SERVER_URL}/mcp"),
|
|
header_provider=lambda ctx: {
|
|
"Authorization": f"Bearer {fetch_id_token(Request(), MCP_SERVER_URL)}"
|
|
},
|
|
)
|
|
```
|
|
|
|
**Gotchas:**
|
|
- Paths must be absolute, not relative.
|
|
- Agent definition must be synchronous (not async) for deployment.
|
|
- Node.js/npx required for npm-based MCP servers — add to Dockerfile if containerizing.
|
|
|
|
---
|
|
|
|
## 8. Context, State, and Memory
|
|
|
|
| Need | Solution |
|
|
|---|---|
|
|
| Within one conversation (task data, form state) | Session state — see [State Prefixes](#state-prefixes) and [Session Service Options](#session-service-options) below |
|
|
| Across conversations (remember interactions, learn over time) | Memory Bank — see [Memory](#memory-long-term-knowledge) below |
|
|
|
|
### State Prefixes
|
|
|
|
```python
|
|
# Session-specific (default)
|
|
state["booking_step"] = 2
|
|
|
|
# User-persistent (across sessions)
|
|
state["user:preferred_language"] = "en"
|
|
|
|
# App-wide (all users)
|
|
state["app:total_queries"] = 1000
|
|
|
|
# Temporary (current invocation only)
|
|
state["temp:intermediate_result"] = data
|
|
```
|
|
|
|
### Session Service Options
|
|
|
|
```python
|
|
from google.adk.sessions import InMemorySessionService
|
|
# For dev: InMemorySessionService()
|
|
# For prod: VertexAiSessionService(), DatabaseSessionService()
|
|
```
|
|
|
|
### Session Rewind
|
|
|
|
Roll back a session to the state before a specific invocation (useful for debugging or user-initiated undo):
|
|
|
|
```python
|
|
from google.adk.runners import InMemoryRunner
|
|
|
|
runner = InMemoryRunner(agent=root_agent, app_name="my_app")
|
|
|
|
# Rewind to state before a given invocation
|
|
await runner.rewind_async(
|
|
user_id=user_id,
|
|
session_id=session.id,
|
|
rewind_before_invocation_id=invocation_id, # exclusive: state before this call
|
|
)
|
|
```
|
|
|
|
> **Note**: Restores session-level state and artifacts only; app/user-scoped state is unaffected.
|
|
|
|
### Artifacts (File Storage)
|
|
|
|
Store and retrieve binary data (PDFs, images, audio) scoped to session or user:
|
|
|
|
```python
|
|
from google.adk.artifacts import InMemoryArtifactService, GcsArtifactService
|
|
from google.genai import types
|
|
|
|
# Configure runner with artifact service
|
|
runner = Runner(
|
|
agent=root_agent,
|
|
app_name="app",
|
|
session_service=session_service,
|
|
artifact_service=InMemoryArtifactService(), # or GcsArtifactService(bucket_name="my-bucket")
|
|
)
|
|
|
|
# In a tool or callback:
|
|
async def save_file(data: bytes, tool_context: ToolContext) -> dict:
|
|
part = types.Part(inline_data=types.Blob(mime_type="application/pdf", data=data))
|
|
version = await tool_context.save_artifact("report.pdf", part) # session-scoped
|
|
await tool_context.save_artifact("user:profile.png", part) # user-scoped
|
|
artifact = await tool_context.load_artifact("report.pdf") # latest version
|
|
artifact_v0 = await tool_context.load_artifact("report.pdf", version=0)
|
|
names = await tool_context.list_artifacts()
|
|
return {"status": "saved", "version": version}
|
|
```
|
|
|
|
**Namespace prefixes:** plain name = session-scoped · `"user:"` = persistent across sessions
|
|
|
|
### Memory (Long-term Knowledge)
|
|
|
|
#### InMemoryMemoryService (Dev)
|
|
|
|
In-memory implementation for local development. Memories don't persist across restarts.
|
|
|
|
```python
|
|
from google.adk.memory import InMemoryMemoryService
|
|
|
|
memory_service = InMemoryMemoryService()
|
|
# Add session to memory after conversation
|
|
await memory_service.add_session_to_memory(session)
|
|
# Search later
|
|
results = await memory_service.search_memory(app_name=app_name, user_id=user_id, query="query")
|
|
```
|
|
|
|
#### Memory Bank (Long-term Memory)
|
|
|
|
Managed cross-session memory that persists user preferences, remembers facts across sessions, and learns from conversations over time. See the [`cross-session-memory` recipe](https://github.com/google/adk-samples/tree/main/core/python/cross-session-memory) for a complete implementation.
|
|
|
|
```python
|
|
from google.adk.agents.callback_context import CallbackContext
|
|
from google.adk.tools.preload_memory_tool import PreloadMemoryTool
|
|
|
|
# PreloadMemoryTool retrieves memories at the start of each turn and injects
|
|
# them into the system instruction. Alternative: LoadMemoryTool() — the model
|
|
# calls it on-demand when it decides memories are needed.
|
|
root_agent = Agent(
|
|
...,
|
|
tools=[PreloadMemoryTool()],
|
|
after_agent_callback=generate_memories_callback,
|
|
)
|
|
|
|
# Alternative: callback_context.add_events_to_memory(events=...) to send only
|
|
# a subset of events, which is better for incremental processing.
|
|
async def generate_memories_callback(callback_context: CallbackContext):
|
|
"""Sends the session's events to Memory Bank for memory generation."""
|
|
await callback_context.add_session_to_memory()
|
|
return None
|
|
```
|
|
|
|
### Context Caching
|
|
|
|
Cache large context windows (system prompt + docs) to reduce latency and cost. Transparent to agent code.
|
|
|
|
```python
|
|
from google.adk.apps import App
|
|
from google.adk.agents.context_cache_config import ContextCacheConfig
|
|
|
|
app = App(
|
|
name="my_app",
|
|
root_agent=root_agent,
|
|
context_cache_config=ContextCacheConfig(
|
|
min_tokens=2048, # only cache if context exceeds this
|
|
ttl_seconds=1800, # cache lifetime (default 1800)
|
|
cache_intervals=10, # re-cache every N invocations
|
|
),
|
|
)
|
|
```
|
|
|
|
### Context Compaction
|
|
|
|
Prevent context overflow on long sessions by compacting older events into summaries. Use **token-based** compaction: it triggers on actual prompt-token volume, so it handles unpredictable inputs (pasted code, large tool results) better than a fixed turn count.
|
|
|
|
```python
|
|
from google.adk.apps import App
|
|
from google.adk.apps.app import EventsCompactionConfig
|
|
from google.adk.apps.llm_event_summarizer import LlmEventSummarizer
|
|
from google.adk.models import Gemini
|
|
|
|
app = App(
|
|
name="my_app",
|
|
root_agent=root_agent,
|
|
events_compaction_config=EventsCompactionConfig(
|
|
token_threshold=32000, # compact once prompt tokens reach this
|
|
event_retention_size=5, # keep the last 5 raw events un-compacted
|
|
# Optional: custom summarizer model
|
|
summarizer=LlmEventSummarizer(llm=Gemini(model="gemini-3.7-flash")),
|
|
),
|
|
)
|
|
```
|
|
|
|
### App Name
|
|
|
|
The `App(name=...)` parameter **must match the agent directory name** (default: `app`). A mismatch causes "Session not found" errors during evaluation because the runner infers the app name from the directory path.
|
|
|
|
```python
|
|
# CORRECT — matches the "app" directory
|
|
app = App(name="app", root_agent=root_agent)
|
|
|
|
# WRONG — causes eval failures
|
|
app = App(name="my_custom_agent", root_agent=root_agent)
|
|
```
|
|
|
|
---
|
|
|
|
## 9. Callbacks
|
|
|
|
### Callback Types
|
|
|
|
```python
|
|
from google.adk.agents.callback_context import CallbackContext
|
|
from google.adk.models.llm_request import LlmRequest
|
|
from google.adk.models.llm_response import LlmResponse
|
|
from google.adk.tools import BaseTool, ToolContext
|
|
from google.genai import types as genai_types
|
|
|
|
# Callbacks are invoked by keyword — parameter names must match exactly.
|
|
|
|
# Agent lifecycle
|
|
async def before_agent_callback(callback_context: CallbackContext) -> None:
|
|
callback_context.state["started"] = True
|
|
|
|
async def after_agent_callback(callback_context: CallbackContext) -> genai_types.Content | None:
|
|
# Return None to continue, or Content to override
|
|
return None
|
|
|
|
# Model interaction
|
|
async def before_model_callback(callback_context: CallbackContext, llm_request: LlmRequest) -> LlmResponse | None:
|
|
# Return None to continue, or LlmResponse to skip model call
|
|
return None
|
|
|
|
async def after_model_callback(callback_context: CallbackContext, llm_response: LlmResponse) -> LlmResponse | None:
|
|
# Return None to continue, or modified LlmResponse
|
|
return None
|
|
|
|
# Tool execution
|
|
async def before_tool_callback(tool: BaseTool, args: dict, tool_context: ToolContext) -> dict | None:
|
|
# Return None to continue, or dict to skip tool and use as result
|
|
return None
|
|
|
|
async def after_tool_callback(tool: BaseTool, args: dict, tool_context: ToolContext, tool_response: dict) -> dict | None:
|
|
# Return None to continue, or modified dict
|
|
return None
|
|
```
|
|
|
|
### Common Pattern
|
|
|
|
```python
|
|
# Initialize state before agent runs
|
|
async def init_state(callback_context: CallbackContext) -> None:
|
|
if "preferences" not in callback_context.state:
|
|
callback_context.state["preferences"] = {}
|
|
|
|
agent = Agent(before_agent_callback=init_state, ...)
|
|
```
|
|
|
|
---
|
|
|
|
## 10. Plugins
|
|
|
|
Global callback hooks across all agents/tools/LLMs. Use for cross-cutting concerns (logging, guardrails); use callbacks for per-agent logic.
|
|
|
|
```python
|
|
from google.adk.plugins.base_plugin import BasePlugin
|
|
from google.adk.apps import App
|
|
|
|
class MyPlugin(BasePlugin):
|
|
async def before_model_callback(self, *, callback_context, llm_request):
|
|
return None # return None to observe, return value to intervene
|
|
|
|
# Register via App — plugins run BEFORE agent-level callbacks
|
|
app = App(name="my_app", root_agent=root_agent, plugins=[MyPlugin()])
|
|
runner = Runner(app=app, session_service=...)
|
|
```
|
|
|
|
Built-in plugins: `ReflectAndRetryToolPlugin` (retry failed tools), `BigQueryAgentAnalyticsPlugin` (log to BQ), `ContextFilterPlugin` (reduce context size), `GlobalInstructionPlugin` (shared system prompt), `SaveFilesAsArtifactsPlugin`, `LoggingPlugin`, `DebugLoggingPlugin`, `MultimodalToolResultsPlugin`.
|
|
|
|
Hooks: `before/after_agent_callback`, `before/after_model_callback`, `before/after_tool_callback`, `on_model_error_callback`, `on_tool_error_callback`, `on_user_message_callback`, `before/after_run_callback`, `on_event_callback`. [Full docs](https://adk.dev/plugins/index.md)
|
|
|
|
### Safety Guardrails
|
|
|
|
Use `before_model_callback` to filter input or `after_model_callback` to filter output. Return `None` to pass through, or return a modified `LlmResponse` to block/replace. Evaluate with the `safety` metric. [Full docs](https://adk.dev/safety/index.md)
|
|
|
|
---
|
|
|
|
## 11. A2A Protocol
|
|
|
|
Requires `pip install google-adk[a2a]`.
|
|
|
|
```python
|
|
# Expose an agent as an A2A service
|
|
# Prefer scaffolding over manual code — scaffold a normal `adk` agent; A2A is built in (see /google-agents-cli-scaffold)
|
|
from google.adk.a2a.utils.agent_to_a2a import to_a2a
|
|
from a2a.types import AgentCard
|
|
to_a2a(root_agent, port=8001)
|
|
|
|
# Consume a remote A2A agent
|
|
from google.adk.agents.remote_a2a_agent import RemoteA2aAgent, AGENT_CARD_WELL_KNOWN_PATH
|
|
remote = RemoteA2aAgent(
|
|
name="remote_agent",
|
|
description="...",
|
|
agent_card=f"http://remote-host:8001{AGENT_CARD_WELL_KNOWN_PATH}",
|
|
)
|
|
```
|
|
|
|
### A2UI
|
|
|
|
Agents can return declarative UI via [a2ui](https://github.com/google/A2UI) (cards, forms, charts; rendered client-side over A2A) instead of plain text. Public preview; current release v0.9.1 (v1.0 release candidate). Docs: https://github.com/google/A2UI/tree/main/docs · ADK guide: https://adk.dev/integrations/a2ui/index.md
|
|
|
|
```python
|
|
# pip install a2ui-agent-sdk
|
|
from a2ui.core.schema.manager import A2uiSchemaManager
|
|
from a2ui.basic_catalog.provider import BasicCatalog
|
|
from a2ui.a2a import create_a2ui_part, parse_response_to_parts
|
|
|
|
# 1. Build the system prompt from a component catalog
|
|
manager = A2uiSchemaManager(...) # loads catalog(s) + few-shot examples
|
|
instruction = manager.generate_system_prompt(...)
|
|
|
|
# 2. Use it as the agent instruction
|
|
root_agent = Agent(name="ui_agent", model="gemini-3.7-flash", instruction=instruction)
|
|
|
|
# 3. Validate the model's JSON output, then wrap as an A2A DataPart
|
|
# (MIME application/a2ui+json) via a2ui.a2a before streaming to the client.
|
|
```
|
|
|
|
Runnable samples: https://github.com/google/A2UI/tree/main/samples/agent/adk
|
|
|
|
---
|
|
|
|
## 12. Event-Driven / Ambient Agents
|
|
|
|
Ambient agents process events (Pub/Sub, Eventarc, schedules) autonomously. ADK provides built-in trigger endpoints that handle payload decoding, session creation, concurrency, and retries.
|
|
|
|
> **Deployment:** `trigger_sources` registers `/apps/{app}/trigger/*` on the standard FastAPI app, so it works on **all** targets. On **Cloud Run** / **GKE** the endpoints are public HTTP routes you point a Pub/Sub push subscription or Eventarc trigger at. On **Agent Runtime** the same routes are reachable through Agent Engine's `/api` passthrough (`https://{location}-aiplatform.googleapis.com/reasoningEngines/v1/{resource}/api/apps/{app}/trigger/pubsub`). The scaffolded `fast_api_app.py` does not pass `trigger_sources` by default — add it to enable these endpoints.
|
|
|
|
```python
|
|
from google.adk.cli.fast_api import get_fast_api_app
|
|
|
|
app = get_fast_api_app(
|
|
agents_dir=AGENTS_DIR,
|
|
web=False,
|
|
trigger_sources=["pubsub", "eventarc"], # enables /apps/{app}/trigger/pubsub and /apps/{app}/trigger/eventarc
|
|
)
|
|
```
|
|
|
|
```bash
|
|
# CLI equivalent for local dev
|
|
adk api_server --trigger_sources "pubsub,eventarc" path/to/your/agent
|
|
```
|
|
|
|
Trigger endpoints handle: base64 decoding, CloudEvent parsing, per-event session creation (UUID), concurrency semaphore, and exponential backoff on transient errors.
|
|
|
|
| Setting | Default | Environment Variable |
|
|
|---------|---------|----------------------|
|
|
| Max concurrent invocations | 10 | `ADK_TRIGGER_MAX_CONCURRENT` |
|
|
| Max retry attempts | 3 | `ADK_TRIGGER_MAX_RETRIES` |
|
|
| Base backoff delay | 1.0s | `ADK_TRIGGER_RETRY_BASE_DELAY` |
|
|
| Max backoff delay | 30.0s | `ADK_TRIGGER_RETRY_MAX_DELAY` |
|
|
|
|
Sessions are ephemeral by default (`InMemorySessionService`); use `DatabaseSessionService` for audit trails. Pub/Sub and Eventarc have a 10-minute processing limit. For non-GCP sources, use `adk api_server --auto_create_session` with the `/run` endpoint instead.
|
|
|
|
**Scheduled / cron execution:** Use Cloud Scheduler to publish to a Pub/Sub topic on a cron schedule, then connect the topic to the agent's `/apps/{app}/trigger/pubsub` endpoint. This is how you implement "run daily at 8 PM" — no custom scheduling code needed.
|
|
|
|
Since ambient agents have no interactive user, route outputs via structured logging (JSON stdout → Cloud Logging → Cloud Monitoring alerts), Pub/Sub, or tool-based integrations (email, Jira, Slack).
|
|
|
|
**Before implementing an ambient agent, clone and study the production sample** — it covers trigger wiring, middleware, structured logging, and Terraform. Look it up in the topic index in `references/samples.md`. [Full docs](https://adk.dev/runtime/ambient-agents/).
|
|
|
|
---
|
|
|
|
## 13. Managed Agents (server-hosted, first-party)
|
|
|
|
> **Requires ADK ≥ 2.4.0.** `ManagedAgent` connects to Google's first-party, server-hosted agents (e.g. the Antigravity agent) via the Managed Agents API: reasoning, tools, and execution all run in Google's managed environment, so there's no local sandbox to provision. It's a `BaseAgent`, so a standard `Runner` runs it like any other agent.
|
|
|
|
### When to use it
|
|
|
|
- **Managed agent** — powerful out-of-the-box capabilities (server-side web search, code execution) without operating the environment yourself. Trade-off: predefined server-side toolset, no client-side tools, runs only in the managed environment.
|
|
- **`LlmAgent` (§2)** — when you need control over the model, instructions, custom/MCP tools, or where execution happens.
|
|
|
|
### Setup
|
|
|
|
Two backends — satisfy the prerequisites for whichever you use, then supply an `agent_id`:
|
|
- **Gemini API:** set `GEMINI_API_KEY`. Use an out-of-the-box id (e.g. `antigravity-preview-05-2026`) or create your own (see below).
|
|
- **Agent Platform (GEAP, formerly Vertex):** authenticate with ADC (`gcloud auth application-default login`). The Managed Agents API is served only from the `global` location, and `ManagedAgent` enforces it.
|
|
|
|
### Create & use
|
|
|
|
```python
|
|
from google import genai
|
|
from google.adk.agents import ManagedAgent
|
|
from google.adk.tools import google_search
|
|
|
|
# Create your own agent (google-genai SDK, NOT ADK — ManagedAgent has no create()).
|
|
# Get-or-create keeps it idempotent; or skip entirely and use an out-of-the-box id like "antigravity-preview-05-2026".
|
|
client = genai.Client()
|
|
if "researcher" not in {a.id for a in (client.agents.list().agents or [])}: # id must be unique, no gemini-/google-/... prefixes
|
|
client.agents.create(
|
|
id="researcher", base_agent="antigravity-preview-05-2026",
|
|
system_instruction="Answer with fresh, grounded info from the web.",
|
|
)
|
|
|
|
# Connect + use. A ManagedAgent is a BaseAgent: set it as root_agent, drop it in a
|
|
# workflow, or wrap it as AgentTool. Only server-side tools are allowed.
|
|
managed = ManagedAgent(
|
|
name="researcher", agent_id="researcher",
|
|
environment={"type": "remote"}, # tools run in the managed sandbox
|
|
tools=[google_search], # or types.Tool(code_execution=types.ToolCodeExecution())
|
|
)
|
|
```
|
|
|
|
### Limits
|
|
|
|
- **Client-side tools raise `NotImplementedError`:** Python functions/callables and client-side MCP (`McpToolset`). Server-side tools work — ADK built-ins, raw `types.Tool` configs, and server-side remote MCP via `RemoteMcpServer`.
|
|
- **Backends differ:** the Gemini API and GEAP behave slightly differently today — test against your target backend.
|
|
|
|
Docs: [Gemini API agents](https://ai.google.dev/gemini-api/docs/agents) · [Agent Platform managed agents](https://docs.cloud.google.com/gemini-enterprise-agent-platform/build/managed-agents) · [Interactions API](https://ai.google.dev/gemini-api/docs/interactions-overview) · [building custom agents](https://ai.google.dev/gemini-api/docs/custom-agents). Samples: [basic](https://github.com/google/adk-python/tree/main/contributing/samples/managed_agent/basic), [code execution](https://github.com/google/adk-python/tree/main/contributing/samples/managed_agent/code_execution).
|
|
|
|
---
|
|
|
|
## Quick Reference
|
|
|
|
### Running Agents Programmatically
|
|
|
|
```python
|
|
from google.adk.runners import Runner
|
|
from google.adk.sessions import InMemorySessionService
|
|
from google.genai import types
|
|
|
|
session_service = InMemorySessionService()
|
|
await session_service.create_session(app_name="app", user_id="user", session_id="s1")
|
|
runner = Runner(agent=my_agent, app_name="app", session_service=session_service)
|
|
|
|
async for event in runner.run_async(
|
|
user_id="user", session_id="s1",
|
|
new_message=types.Content(role="user", parts=[types.Part.from_text(text="Hello!")]),
|
|
):
|
|
if event.is_final_response():
|
|
print(event.content.parts[0].text)
|
|
```
|
|
|
|
|
|
### ADK Built-in Tool Imports (Precision Required)
|
|
|
|
```python
|
|
# CORRECT - imports the tool instance
|
|
from google.adk.tools.load_web_page import load_web_page
|
|
|
|
# WRONG - imports the module, not the tool
|
|
from google.adk.tools import load_web_page
|
|
```
|
|
|
|
Pass the imported tool directly to `tools=[load_web_page]`, not `tools=[load_web_page.load_web_page]`.
|
|
|
|
### Factory Functions for Sub-agents
|
|
|
|
Use factory functions (not module-level instances) to avoid "agent already has a parent" errors. Always **call** the factory — passing the function reference fails with `ValidationError: Input should be a valid dictionary or instance of BaseAgent`.
|
|
|
|
```python
|
|
def create_researcher():
|
|
return Agent(name="researcher", ...)
|
|
|
|
root_agent = SequentialAgent(
|
|
sub_agents=[create_researcher(), create_analyst()], # call the functions!
|
|
...
|
|
)
|
|
```
|
|
|
|
Data flows between sequential sub-agents via conversation history and `output_key` state.
|
|
|
|
### Further Reading
|
|
|
|
- [ADK Documentation](https://adk.dev/llms.txt)
|
|
- [ADK Samples](https://github.com/google/adk-samples)
|
|
- `references/samples.md` — topic index of the reference recipes, and how to clone one
|
|
|
|
---
|
|
|
|
## Inspecting ADK Source Code
|
|
|
|
When you need to look up ADK internals, inspect the installed package directly:
|
|
|
|
```bash
|
|
# Find the ADK package location (use "uv run python" if using uv)
|
|
python -c "import google.adk; print(google.adk.__path__[0])"
|
|
```
|
|
|
|
### ADK Package Directory Map
|
|
|
|
```
|
|
google/adk/
|
|
├── agents/ # Agent types (LlmAgent, BaseAgent, SequentialAgent, etc.)
|
|
├── tools/ # Tool implementations (FunctionTool, google_search, etc.)
|
|
├── sessions/ # Session services (InMemory, Database, VertexAI)
|
|
├── memory/ # Memory services
|
|
├── runners.py # Runner and execution engine
|
|
├── events/ # Event types and actions
|
|
├── models/ # Model integrations (Gemini, LiteLLM, etc.)
|
|
├── code_executors/ # Code execution (BuiltInCodeExecutor, etc.)
|
|
├── evaluation/ # Eval framework (criteria, evaluators, etc.)
|
|
├── cli/ # ADK CLI internals (used by agents-cli playground, eval, etc.)
|
|
├── flows/ # LLM flow implementations
|
|
├── artifacts/ # Artifact services
|
|
└── auth/ # Authentication helpers
|
|
```
|
|
|
|
Use Glob/Grep/Read on the installed package to find exact implementations, method signatures, and configuration options.
|
|
|
|
For the full ADK documentation index, use `curl https://adk.dev/llms.txt`.
|