Google ADK Development Skill — Python + Gemini + FastAPI
Iron Law
NEVER call the real Gemini API in unit tests. Always use InMemoryRunner for tests. Production agents use Runner with real session services. Mixing these means real API costs, flaky tests, and non-deterministic CI.
NEVER connect to external services from a FunctionTool. All external I/O (HTTP, DB, vector DB, cloud APIs) belongs in MCPTools accessed via McpToolset. FunctionTools contain ONLY pure logic or session state access.
ALWAYS use McpToolset for MCP connections — never instantiate McpToolset(connection_params=...) directly in agent code; use the factory provided by your project. Rules:
tool_filter is MANDATORY and non-empty in every McpToolset call — limits which MCP tools the LLM can call; empty list raises ValueError at startup
- NEVER bypass McpToolset with direct
httpx/requests calls — all external I/O routes through the MCP server via McpToolset
Quick Scaffold (Two Options)
Option A: agent-starter-pack (Recommended for production)
uvx agent-starter-pack create my-agent --agent adk --prototype --agent-guidance-filename CLAUDE.md -y
# Templates: adk (default), adk_a2a (A2A protocol), agentic_rag (RAG)
Generates project with Terraform, Dockerfile, CI/CD, eval harness. Use enhance to add deployment later.
Option B: Manual setup (Simpler, no deployment scaffold)
uv init my-adk-service && cd my-adk-service
uv add google-adk "google-genai>=1.0.0" "fastapi>=0.135.2" "uvicorn[standard]" pydantic pydantic-settings structlog
uv add --dev pytest pytest-asyncio httpx ruff mypy
Option C: Enhance existing project (add deployment to scaffolded project)
uvx agent-starter-pack enhance .
Choose deployment target when prompted: cloud_run or agent_engine.
Process
- Write DESIGN_SPEC.md — Before any code, write a spec covering: purpose, example use cases, required tools, safety constraints, success criteria, edge cases. Save as
DESIGN_SPEC.md in the project root. This is your contract — all implementation must align with it.
- Scaffold —
uv init + install google-adk + google-genai; confirm uv add "google-adk>=1.28.0" resolves without error
- Configure —
.env with GOOGLE_API_KEY / GOOGLE_CLOUD_PROJECT; load via pydantic-settings BaseSettings; never hardcode keys
- Define Agent —
Agent(name, model, instruction, tools) using model="gemini-3.1-flash"; write docstrings on every tool function
- Compose Agents — use
SequentialAgent, ParallelAgent, or LoopAgent for multi-step workflows; set output_key on each sub-agent that passes state downstream
- Define Tools — plain Python functions with type hints and docstrings; use
pydantic.BaseModel for complex inputs; accept tool_context: ToolContext to read/write session state
- Add Callbacks —
before_model_callback, after_model_callback, before_tool_callback, after_tool_callback, on_model_error_callback, on_tool_error_callback for rate limiting, logging, and structured error handling
- Session Management —
InMemorySessionService() for dev/test; VertexAiSessionService(project_id, location) for production; always call create_session before first runner.run
- Add Memory —
InMemoryMemoryService for dev; pass memory_service to Runner; add load_memory built-in tool to agents that need long-term recall
- Expose API — FastAPI routes using
runner.run_async() with StreamingResponse for SSE; one Runner instance per app lifecycle
- Write Tests —
InMemoryRunner for unit tests; pytest-asyncio for async tests; test tools in isolation, then agent routing end-to-end
- Deploy — Docker +
uvicorn; inject GOOGLE_API_KEY as env var; use GOOGLE_CLOUD_PROJECT + GOOGLE_CLOUD_LOCATION for Vertex AI session service in prod
Key Patterns
| Pattern |
Implementation |
Reference |
| Single Agent |
Agent(name, model, instruction, tools) |
adk-core-patterns.md |
| Sequential Pipeline |
SequentialAgent(sub_agents=[a, b, c]) + output_key per agent |
adk-agent-types.md |
| Parallel Analysis |
ParallelAgent(sub_agents=[...]) + output_key per agent |
adk-agent-types.md |
| Iterative Refinement |
LoopAgent(sub_agents=[...], max_iterations=N) + exit_loop |
adk-agent-types.md |
| Agent Handoff |
transfer_to_agent built-in + sub_agents=[...] on root |
adk-agent-handoff.md |
| Custom Tools |
Plain function with docstring + ToolContext for state |
adk-tools-basic.md |
| MCP Integration |
McpToolset(connection_params=StdioConnectionParams(...)) |
adk-tools-basic.md |
| Structured Output |
output_schema=PydanticModel + output_key="key" |
adk-core-patterns.md |
| Callbacks |
before_model_callback, after_model_callback, before_tool_callback |
adk-tools-callbacks.md |
| Session State |
tool_context.state["key"] read/write |
adk-core-patterns.md |
| Memory |
InMemoryMemoryService + load_memory tool |
adk-memory-artifacts.md |
| Testing |
InMemoryRunner + pytest-asyncio |
adk-testing.md |
| FastAPI SSE |
runner.run_async() + StreamingResponse |
adk-fastapi-integration.md |
| Evaluation |
adk eval + evalset schema + LLM-as-judge |
See Google ADK docs |
| Deployment |
Agent Engine, Cloud Run, CI/CD |
See Google ADK docs |
| Observability |
Cloud Trace, prompt logging, agent analytics |
See Google ADK docs |
R1/R2 Tool Placement Rule
Before writing any tool, apply this binary test:
"Does this tool perform I/O outside the agent process (DB, HTTP, vector DB, cloud APIs)?"
| Answer |
Tool type |
Location |
| YES |
R1 — MCPTool |
MCP server — agent accesses via McpToolset |
| NO |
R2 — FunctionTool |
Agent tools directory — pure logic only |
ToolContext exception: If a tool needs BOTH external data AND ToolContext session state, it MUST be a FunctionTool — only in-process tools can access ToolContext. The FunctionTool reads data from session state previously populated by an MCPTool.
NEVER put import httpx, import sqlalchemy, or import requests in FunctionTool files.
Eval-First Development
Write golden test cases BEFORE writing agent code. This prevents writing code that passes no evaluation criteria.
Order of operations:
- Define
tests/golden/agents/<agent_name>/ directory
- Write at minimum: one happy-path case, one error-path case, one edge-case
- Each case: input → expected output with
confidence_min, contains_keywords, or pattern_* assertions
- Run eval skeleton to confirm test infrastructure works
- THEN implement the agent
- Iterate until all golden cases pass
Eval Skeleton (generate this first, before implementing the agent)
AGENT=my_agent # replace with your agent name
mkdir -p tests/golden/agents/$AGENT tests/evals
# Placeholder evalset
cat > tests/evals/$AGENT.evalset.json << 'JSON'
[
{
"name": "happy_path_1",
"input": { "query": "your test input here" },
"expected_tool_use": [{ "tool_name": "your_tool_name" }],
"expected_final_response": { "contains": "expected keyword" }
}
]
JSON
# Eval config with accuracy threshold
cat > tests/evals/eval_config.json << 'JSON'
{
"criteria": [
{ "type": "tool_trajectory_avg_score", "config": { "match_type": "IN_ORDER" } },
{ "type": "final_response_match_v2", "config": { "threshold": 0.8 } }
]
}
JSON
Code Preservation Rules
- NEVER change the model in existing code unless explicitly asked — changing
gemini-3.1-flash to another model is a breaking change
- NEVER rewrite working agent code — if the agent works, refactor incrementally
- NEVER remove tools from an agent without explicit approval — tools are part of the agent's contract
- NEVER rename output_key values — downstream agents reference them by name
Documentation Sources
| Source |
URL / Tool |
Purpose |
| Google ADK Python |
https://context7.com/google/adk-python/llms.txt |
Official ADK API reference |
| Google GenAI types |
Context7 MCP — resolve google-genai |
types.Part, types.Content, GenerateContentConfig |
| Pydantic v2 |
Context7 MCP — resolve pydantic |
BaseModel, Field, validators |
| ADK Docs MCP |
adk-docs MCP server (installed) |
Live ADK documentation |
Reference Files
| File |
Contents |
reference/adk-core-patterns.md |
Agent config, Runner patterns (sync/async), App class, session management |
reference/adk-structured-output.md |
Session state access, structured output schemas, UserContent construction |
reference/adk-agent-types.md |
SequentialAgent, ParallelAgent, LoopAgent, composition patterns |
reference/adk-agent-handoff.md |
Agent handoff via transfer_to_agent, output_key state passing rules |
reference/adk-tools-basic.md |
FunctionTool, ToolContext, async tools, Pydantic inputs, McpToolset (all 4 connection modes) |
reference/adk-tools-callbacks.md |
Callbacks: before/after model, on_model_error, before/after tool, on_tool_error |
reference/adk-memory-artifacts.md |
Memory services, load_memory tool, artifact storage, semantic search |
reference/adk-fastapi-integration.md |
FastAPI + StreamingResponse SSE, lifespan runner setup, request/response models |
reference/adk-testing.md |
InMemoryRunner unit tests, pytest-asyncio patterns, tool isolation, agent routing |
reference/adk-project-config.md |
pyproject.toml, .env setup, directory structure, Dockerfile, logging, commands |
reference/adk-gemini-prompt-templates.md |
Gemini-specific LlmAgent instruction templates — base structure, RAG with citations, constitutional AI (2-agent SequentialAgent), Tree-of-Thoughts, multi-step analysis, model selection guide (Flash vs Pro) |
Common Commands
# Run dev server via ADK web UI
adk web
# Run FastAPI app with uvicorn
uvicorn src.main:app --reload --port 8000
# Run tests
uv run pytest tests/ -v
# Type check
uv run mypy src/
# Lint + format
uv run ruff check src/ && uv run ruff format src/
# Install all deps from lockfile
uv sync
Error Handling
ADK-specific error handling rules:
- Provider errors — wrap
runner.run() / runner.run_async() in try/except; catch google.api_core.exceptions.GoogleAPICallError; log with full context (user_id, session_id, model); rethrow or return structured error response — never swallow
- Tool errors — use
on_tool_error_callback to intercept and log; return a descriptive error string from tools (ADK surfaces it to the model); never return empty string or None silently
- Loop limits —
LoopAgent stops at max_iterations; ensure exit_loop is called by the agent's instruction before the limit; log when loop exits by limit vs. by tool call
- Callback abort — returning a non-None value from
before_model_callback skips the model call; document this explicitly in the callback with a comment
- Session not found — always call
session_service.get_session() before runner.run(); if None, call create_session() first
All error paths must:
- Log with structured logger (structlog) including
user_id, session_id, agent_name
- Either rethrow or return an error state — no silent empty returns
- Surface the failure to the user (API error response, SSE error event)
Post-Code Review
After implementing any ADK agent feature:
- Dispatch
agentic-ai-reviewer agent — pass the agent graph structure and tool implementations
- Dispatch
security-reviewer — flag any tool that calls external APIs or handles user PII
- Confirm: no hardcoded API keys, all inputs validated, all error paths logged
1---2name: google-adk3description: Google ADK (Agent Development Kit) Python skill. Use when building AI agents with google-adk, Gemini models, SequentialAgent, ParallelAgent, LoopAgent, FunctionTool, McpToolset, session management, memory services, artifact storage, callbacks, or FastAPI integration for ADK agents.4---56# Google ADK Development Skill — Python + Gemini + FastAPI78## Iron Law910**NEVER call the real Gemini API in unit tests.** Always use `InMemoryRunner` for tests. Production agents use `Runner` with real session services. Mixing these means real API costs, flaky tests, and non-deterministic CI.1112**NEVER connect to external services from a FunctionTool.** All external I/O (HTTP, DB, vector DB, cloud APIs) belongs in MCPTools accessed via `McpToolset`. FunctionTools contain ONLY pure logic or session state access.1314**ALWAYS use `McpToolset` for MCP connections** — never instantiate `McpToolset(connection_params=...)` directly in agent code; use the factory provided by your project. Rules:15- **`tool_filter` is MANDATORY and non-empty** in every `McpToolset` call — limits which MCP tools the LLM can call; empty list raises `ValueError` at startup16- **NEVER bypass McpToolset** with direct `httpx`/`requests` calls — all external I/O routes through the MCP server via McpToolset1718## Quick Scaffold (Two Options)1920### Option A: agent-starter-pack (Recommended for production)2122```bash23uvx agent-starter-pack create my-agent --agent adk --prototype --agent-guidance-filename CLAUDE.md -y24# Templates: adk (default), adk_a2a (A2A protocol), agentic_rag (RAG)25```2627Generates project with Terraform, Dockerfile, CI/CD, eval harness. Use `enhance` to add deployment later.2829### Option B: Manual setup (Simpler, no deployment scaffold)3031```bash32uv init my-adk-service && cd my-adk-service33uv add google-adk "google-genai>=1.0.0" "fastapi>=0.135.2" "uvicorn[standard]" pydantic pydantic-settings structlog34uv add --dev pytest pytest-asyncio httpx ruff mypy35```3637### Option C: Enhance existing project (add deployment to scaffolded project)3839```bash40uvx agent-starter-pack enhance .41```4243Choose deployment target when prompted: `cloud_run` or `agent_engine`.4445## Process46470. **Write DESIGN_SPEC.md** — Before any code, write a spec covering: purpose, example use cases, required tools, safety constraints, success criteria, edge cases. Save as `DESIGN_SPEC.md` in the project root. This is your contract — all implementation must align with it.481. **Scaffold** — `uv init` + install `google-adk` + `google-genai`; confirm `uv add "google-adk>=1.28.0"` resolves without error492. **Configure** — `.env` with `GOOGLE_API_KEY` / `GOOGLE_CLOUD_PROJECT`; load via `pydantic-settings` `BaseSettings`; never hardcode keys503. **Define Agent** — `Agent(name, model, instruction, tools)` using `model="gemini-3.1-flash"`; write docstrings on every tool function514. **Compose Agents** — use `SequentialAgent`, `ParallelAgent`, or `LoopAgent` for multi-step workflows; set `output_key` on each sub-agent that passes state downstream525. **Define Tools** — plain Python functions with type hints and docstrings; use `pydantic.BaseModel` for complex inputs; accept `tool_context: ToolContext` to read/write session state536. **Add Callbacks** — `before_model_callback`, `after_model_callback`, `before_tool_callback`, `after_tool_callback`, `on_model_error_callback`, `on_tool_error_callback` for rate limiting, logging, and structured error handling547. **Session Management** — `InMemorySessionService()` for dev/test; `VertexAiSessionService(project_id, location)` for production; always call `create_session` before first `runner.run`558. **Add Memory** — `InMemoryMemoryService` for dev; pass `memory_service` to `Runner`; add `load_memory` built-in tool to agents that need long-term recall569. **Expose API** — FastAPI routes using `runner.run_async()` with `StreamingResponse` for SSE; one `Runner` instance per app lifecycle5710. **Write Tests** — `InMemoryRunner` for unit tests; `pytest-asyncio` for async tests; test tools in isolation, then agent routing end-to-end5811. **Deploy** — Docker + `uvicorn`; inject `GOOGLE_API_KEY` as env var; use `GOOGLE_CLOUD_PROJECT` + `GOOGLE_CLOUD_LOCATION` for Vertex AI session service in prod5960## Key Patterns6162| Pattern | Implementation | Reference |63|---------|---------------|-----------|64| Single Agent | `Agent(name, model, instruction, tools)` | `adk-core-patterns.md` |65| Sequential Pipeline | `SequentialAgent(sub_agents=[a, b, c])` + `output_key` per agent | `adk-agent-types.md` |66| Parallel Analysis | `ParallelAgent(sub_agents=[...])` + `output_key` per agent | `adk-agent-types.md` |67| Iterative Refinement | `LoopAgent(sub_agents=[...], max_iterations=N)` + `exit_loop` | `adk-agent-types.md` |68| Agent Handoff | `transfer_to_agent` built-in + `sub_agents=[...]` on root | `adk-agent-handoff.md` |69| Custom Tools | Plain function with docstring + `ToolContext` for state | `adk-tools-basic.md` |70| MCP Integration | `McpToolset(connection_params=StdioConnectionParams(...))` | `adk-tools-basic.md` |71| Structured Output | `output_schema=PydanticModel` + `output_key="key"` | `adk-core-patterns.md` |72| Callbacks | `before_model_callback`, `after_model_callback`, `before_tool_callback` | `adk-tools-callbacks.md` |73| Session State | `tool_context.state["key"]` read/write | `adk-core-patterns.md` |74| Memory | `InMemoryMemoryService` + `load_memory` tool | `adk-memory-artifacts.md` |75| Testing | `InMemoryRunner` + pytest-asyncio | `adk-testing.md` |76| FastAPI SSE | `runner.run_async()` + `StreamingResponse` | `adk-fastapi-integration.md` |77| Evaluation | `adk eval` + evalset schema + LLM-as-judge | See Google ADK docs |78| Deployment | Agent Engine, Cloud Run, CI/CD | See Google ADK docs |79| Observability | Cloud Trace, prompt logging, agent analytics | See Google ADK docs |8081## R1/R2 Tool Placement Rule8283Before writing any tool, apply this binary test:8485> "Does this tool perform I/O outside the agent process (DB, HTTP, vector DB, cloud APIs)?"8687| Answer | Tool type | Location |88|--------|-----------|----------|89| **YES** | **R1 — MCPTool** | MCP server — agent accesses via `McpToolset` |90| **NO** | **R2 — FunctionTool** | Agent tools directory — pure logic only |9192**ToolContext exception:** If a tool needs BOTH external data AND `ToolContext` session state, it MUST be a FunctionTool — only in-process tools can access `ToolContext`. The FunctionTool reads data from session state previously populated by an MCPTool.9394**NEVER put `import httpx`, `import sqlalchemy`, or `import requests` in FunctionTool files.**9596## Eval-First Development9798Write golden test cases BEFORE writing agent code. This prevents writing code that passes no evaluation criteria.99100**Order of operations:**1011. Define `tests/golden/agents/<agent_name>/` directory1022. Write at minimum: one happy-path case, one error-path case, one edge-case1033. Each case: input → expected output with `confidence_min`, `contains_keywords`, or `pattern_*` assertions1044. Run eval skeleton to confirm test infrastructure works1055. THEN implement the agent1066. Iterate until all golden cases pass107108### Eval Skeleton (generate this first, before implementing the agent)109110```bash111AGENT=my_agent # replace with your agent name112mkdir -p tests/golden/agents/$AGENT tests/evals113114# Placeholder evalset115cat > tests/evals/$AGENT.evalset.json << 'JSON'116[117 {118 "name": "happy_path_1",119 "input": { "query": "your test input here" },120 "expected_tool_use": [{ "tool_name": "your_tool_name" }],121 "expected_final_response": { "contains": "expected keyword" }122 }123]124JSON125126# Eval config with accuracy threshold127cat > tests/evals/eval_config.json << 'JSON'128{129 "criteria": [130 { "type": "tool_trajectory_avg_score", "config": { "match_type": "IN_ORDER" } },131 { "type": "final_response_match_v2", "config": { "threshold": 0.8 } }132 ]133}134JSON135```136137## Code Preservation Rules138139- **NEVER change the model** in existing code unless explicitly asked — changing `gemini-3.1-flash` to another model is a breaking change140- **NEVER rewrite working agent code** — if the agent works, refactor incrementally141- **NEVER remove tools** from an agent without explicit approval — tools are part of the agent's contract142- **NEVER rename output_key values** — downstream agents reference them by name143144## Documentation Sources145146| Source | URL / Tool | Purpose |147|--------|-----------|---------|148| Google ADK Python | `https://context7.com/google/adk-python/llms.txt` | Official ADK API reference |149| Google GenAI types | Context7 MCP — resolve `google-genai` | `types.Part`, `types.Content`, `GenerateContentConfig` |150| Pydantic v2 | Context7 MCP — resolve `pydantic` | `BaseModel`, `Field`, validators |151| ADK Docs MCP | `adk-docs` MCP server (installed) | Live ADK documentation |152153## Reference Files154155| File | Contents |156|------|----------|157| `reference/adk-core-patterns.md` | Agent config, Runner patterns (sync/async), App class, session management |158| `reference/adk-structured-output.md` | Session state access, structured output schemas, UserContent construction |159| `reference/adk-agent-types.md` | SequentialAgent, ParallelAgent, LoopAgent, composition patterns |160| `reference/adk-agent-handoff.md` | Agent handoff via transfer_to_agent, output_key state passing rules |161| `reference/adk-tools-basic.md` | FunctionTool, ToolContext, async tools, Pydantic inputs, McpToolset (all 4 connection modes) |162| `reference/adk-tools-callbacks.md` | Callbacks: before/after model, on_model_error, before/after tool, on_tool_error |163| `reference/adk-memory-artifacts.md` | Memory services, load_memory tool, artifact storage, semantic search |164| `reference/adk-fastapi-integration.md` | FastAPI + StreamingResponse SSE, lifespan runner setup, request/response models |165| `reference/adk-testing.md` | InMemoryRunner unit tests, pytest-asyncio patterns, tool isolation, agent routing |166| `reference/adk-project-config.md` | pyproject.toml, .env setup, directory structure, Dockerfile, logging, commands |167| `reference/adk-gemini-prompt-templates.md` | Gemini-specific `LlmAgent` instruction templates — base structure, RAG with citations, constitutional AI (2-agent SequentialAgent), Tree-of-Thoughts, multi-step analysis, model selection guide (Flash vs Pro) |168169## Common Commands170171```bash172# Run dev server via ADK web UI173adk web174175# Run FastAPI app with uvicorn176uvicorn src.main:app --reload --port 8000177178# Run tests179uv run pytest tests/ -v180181# Type check182uv run mypy src/183184# Lint + format185uv run ruff check src/ && uv run ruff format src/186187# Install all deps from lockfile188uv sync189```190191## Error Handling192193ADK-specific error handling rules:194195- **Provider errors** — wrap `runner.run()` / `runner.run_async()` in try/except; catch `google.api_core.exceptions.GoogleAPICallError`; log with full context (user_id, session_id, model); rethrow or return structured error response — never swallow196- **Tool errors** — use `on_tool_error_callback` to intercept and log; return a descriptive error string from tools (ADK surfaces it to the model); never return empty string or `None` silently197- **Loop limits** — `LoopAgent` stops at `max_iterations`; ensure `exit_loop` is called by the agent's instruction before the limit; log when loop exits by limit vs. by tool call198- **Callback abort** — returning a non-None value from `before_model_callback` skips the model call; document this explicitly in the callback with a comment199- **Session not found** — always call `session_service.get_session()` before `runner.run()`; if `None`, call `create_session()` first200201All error paths must:2021. Log with structured logger (structlog) including `user_id`, `session_id`, `agent_name`2032. Either rethrow or return an error state — no silent empty returns2043. Surface the failure to the user (API error response, SSE error event)205206## Post-Code Review207208After implementing any ADK agent feature:2092101. Dispatch `agentic-ai-reviewer` agent — pass the agent graph structure and tool implementations2112. Dispatch `security-reviewer` — flag any tool that calls external APIs or handles user PII2123. Confirm: no hardcoded API keys, all inputs validated, all error paths logged