Skip to content

Commit 1622793

Browse files
sasha-gitgboyangsvlJacksunweiGWealeswapydapy
committed
chore: switch main to v2.0.0 GA (transition to v2)
Co-authored-by: Bo Yang <ybo@google.com> Co-authored-by: Wei Sun (Jack) <weisun@google.com> Co-authored-by: George Weale <gweale@google.com> Co-authored-by: Swapnil Agarwal <swapnilag@google.com> Co-authored-by: Xuan Yang <xygoogle@google.com> Co-authored-by: Shangjie Chen <deanchen@google.com> Co-authored-by: Yifan Wang <wanyif@google.com> Co-authored-by: Kathy Wu <wukathy@google.com>
1 parent e13ada7 commit 1622793

1,119 files changed

Lines changed: 81506 additions & 15176 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
---
2+
name: adk-agent-builder
3+
description: Central hub for building, testing, and iterating on ADK agents. Trigger this skill when the user wants to create a new agent, configure modes (task, single-turn), or build graph-based workflows.
4+
---
5+
6+
# ADK Agent Builder
7+
8+
This file serves as a directory of specialized reference guides for developing agents with ADK. To avoid context pollution, read only the relevant reference file based on your current task.
9+
10+
## Core Concepts Directory
11+
12+
Refer to these files for foundational knowledge:
13+
- **Getting Started & Basic Agents**: [getting-started.md](references/getting-started.md)
14+
- Environment setup, API key configuration, and minimal agent definitions.
15+
- **Tool Catalog**: [tool-catalog.md](references/tool-catalog.md)
16+
- How to bind function tools, MCP tools, OpenAPI specs, and Google API tools.
17+
- **Agent Modes (Task / Single-Turn)**: [task-mode.md](references/task-mode.md)
18+
- Multi-turn structured delegation and autonomous single-turn execution patterns.
19+
20+
## Workflow & Graph Orchestration
21+
22+
Refer to these files when building complex graphs:
23+
- **Function Nodes**: [function-nodes.md](references/function-nodes.md)
24+
- How to use functions as nodes, type resolution, and generators.
25+
- **Routing & Conditions**: [routing-and-conditions.md](references/routing-and-conditions.md)
26+
- Edge patterns, dict-based routing, self-loops, and conditional execution.
27+
- **LLM Agent Nodes**: [llm-agent-nodes.md](references/llm-agent-nodes.md)
28+
- How to use LLM agents as workflow nodes, task wrappers, and handling output schemas.
29+
30+
## Advanced Orchestration Patterns
31+
32+
- **Parallel Processing & Fan-Out**: [parallel-and-fanout.md](references/parallel-and-fanout.md)
33+
- `ParallelWorker` for list splitting and concurrent processing, fan-out/join patterns.
34+
- **Human-in-the-Loop**: [human-in-the-loop.md](references/human-in-the-loop.md)
35+
- Pausing execution for user input, resumable workflows, and AuthConfig on nodes.
36+
- **Dynamic Nodes**: [dynamic-nodes.md](references/dynamic-nodes.md)
37+
- Scheduling nodes at runtime dynamically via `ctx.run_node()`.
38+
39+
## Infrastructure & Utilities
40+
41+
- **State & Events**: [state-and-events.md](references/state-and-events.md)
42+
- Using context API, sharing global state, and yield event structures.
43+
- **Multi-Agent Systems**: [multi-agent.md](references/multi-agent.md)
44+
- Hierarchical execution (e.g., `SequentialAgent`, `LoopAgent`, `ParallelAgent`).
45+
- **Testing Strategies**: [testing.md](references/testing.md)
46+
- Automated queries with `adk run`, unit tests, and integration testing with sample agents.
47+
48+
## Standards & Guidelines
49+
50+
- **Best Practices**: [best-practices.md](references/best-practices.md)
51+
- Critical rules (Pydantic schemas, content events, state-based data flow).
Lines changed: 303 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,303 @@
1+
# Advanced Workflow Patterns Reference
2+
3+
Nested workflows, dynamic nodes, retry configuration, custom node types, and graph construction.
4+
5+
## 📋 Agent Verification Checklist (Advanced Patterns)
6+
Use this checklist when implementing complex workflows:
7+
- [ ] **Validation**: Does your graph follow all 7 validation rules? (e.g., no unconditional cycles)
8+
- [ ] **Custom Nodes**: If creating a custom node, did you override `get_name()` and `run()`?
9+
- [ ] **Dynamic Execution**: If using `run_node`, did you follow the rules in the dedicated dynamic-nodes reference?
10+
- [ ] **Waiting State**: Did you use `wait_for_output=True` if the node should stay in WAITING state until output is yielded?
11+
12+
## 💡 Quick Reference
13+
- **Retry**: `RetryConfig(max_attempts=5, initial_delay=1.0)`
14+
- **Custom Node Fields**: `rerun_on_resume`, `wait_for_output`, `retry_config`, `timeout`
15+
16+
## Nested Workflows
17+
18+
A `Workflow` is both an agent and a node. Use one workflow inside another:
19+
20+
```python
21+
from google.adk.workflow import Workflow
22+
23+
# Inner workflow
24+
inner = Workflow(
25+
name="inner_pipeline",
26+
edges=[
27+
('START', step_a),
28+
(step_a, step_b),
29+
],
30+
)
31+
32+
# Outer workflow using inner as a node
33+
outer = Workflow(
34+
name="outer_pipeline",
35+
edges=[
36+
('START', pre_process),
37+
(pre_process, inner), # Nested workflow
38+
(inner, post_process),
39+
],
40+
)
41+
```
42+
43+
The inner workflow receives the predecessor's output as its START input and its terminal output flows to the next node in the outer workflow.
44+
45+
## Dynamic Node Scheduling
46+
47+
Schedule nodes at runtime using `ctx.run_node()`.
48+
49+
See the dedicated [Dynamic Node Scheduling Reference](file:///Users/deanchen/Desktop/adk-workflow/.agents/skills/adk-workflow/references/dynamic-nodes.md) for detailed rules, examples, and best practices.
50+
51+
## Retry Configuration
52+
53+
Configure automatic retry for nodes that may fail:
54+
55+
```python
56+
from google.adk.workflow import RetryConfig
57+
from google.adk.workflow import FunctionNode
58+
59+
retry = RetryConfig(
60+
max_attempts=5, # Max attempts (default: 5). 0 or 1 = no retry
61+
initial_delay=1.0, # Seconds before first retry (default: 1.0)
62+
max_delay=60.0, # Max seconds between retries (default: 60.0)
63+
backoff_factor=2.0, # Delay multiplier per attempt (default: 2.0)
64+
jitter=1.0, # Randomness factor (default: 1.0, 0.0 = none)
65+
exceptions=None, # Exception types to retry (None = all)
66+
)
67+
68+
node = FunctionNode(
69+
flaky_api_call,
70+
name="api_call",
71+
retry_config=retry,
72+
)
73+
```
74+
75+
### Retry delay formula
76+
77+
```
78+
delay = initial_delay * (backoff_factor ^ attempt)
79+
delay = min(delay, max_delay)
80+
delay = delay * (1 + random(0, jitter))
81+
```
82+
83+
### Accessing retry count
84+
85+
```python
86+
def my_node(ctx: Context, node_input: str) -> str:
87+
if ctx.retry_count > 0:
88+
print(f"Retry attempt {ctx.retry_count}")
89+
return "result"
90+
```
91+
92+
## Custom Node Types
93+
94+
Subclass `BaseNode` for custom behavior:
95+
96+
```python
97+
from google.adk.workflow import BaseNode
98+
from google.adk.events.event import Event
99+
from google.adk.agents.context import Context
100+
from pydantic import ConfigDict, Field
101+
from typing import Any, AsyncGenerator
102+
from typing_extensions import override
103+
104+
class BatchProcessorNode(BaseNode):
105+
"""Processes items in batches."""
106+
model_config = ConfigDict(arbitrary_types_allowed=True)
107+
108+
name: str = Field(default="batch_processor")
109+
batch_size: int = Field(default=10)
110+
111+
def __init__(self, *, name: str = "batch_processor", batch_size: int = 10):
112+
super().__init__()
113+
object.__setattr__(self, 'name', name)
114+
object.__setattr__(self, 'batch_size', batch_size)
115+
116+
@override
117+
def get_name(self) -> str:
118+
return self.name
119+
120+
@override
121+
async def run(
122+
self,
123+
*,
124+
ctx: Context,
125+
node_input: Any,
126+
) -> AsyncGenerator[Any, None]:
127+
items = node_input if isinstance(node_input, list) else [node_input]
128+
results = []
129+
for i in range(0, len(items), self.batch_size):
130+
batch = items[i:i + self.batch_size]
131+
batch_result = await process_batch(batch)
132+
results.extend(batch_result)
133+
yield Event(output=results)
134+
```
135+
136+
### BaseNode Fields
137+
138+
| Field | Default | Description |
139+
|-------|---------|-------------|
140+
| `rerun_on_resume` | `False` | Whether to rerun after HITL interrupt |
141+
| `wait_for_output` | `False` | Node stays in WAITING state until it yields output (see below) |
142+
| `retry_config` | `None` | Retry configuration on failure |
143+
| `timeout` | `None` | Max seconds for node to complete |
144+
145+
### wait_for_output
146+
147+
When `wait_for_output=True`, a node that finishes without yielding an `Event` with output moves to **WAITING** state instead of COMPLETED. Downstream nodes are **not** triggered. The node can then be re-triggered by upstream predecessors.
148+
149+
This is how `JoinNode` works internally — it runs once per predecessor, storing partial inputs, and only yields output (triggering downstream) when all predecessors have completed. `LlmAgentWrapper` in `task` mode also sets `wait_for_output=True` automatically.
150+
151+
```python
152+
from google.adk.workflow import BaseNode
153+
154+
class CollectorNode(BaseNode):
155+
wait_for_output: bool = True # Stay in WAITING until output is yielded
156+
157+
async def run(self, *, ctx, node_input):
158+
# Store partial input, don't yield output yet
159+
collected = ctx.state.get("collected", [])
160+
collected.append(node_input)
161+
yield Event(state={"collected": collected})
162+
163+
# Only yield output when we have enough
164+
if len(collected) >= 3:
165+
yield Event(output=collected)
166+
# Now node transitions to COMPLETED and triggers downstream
167+
```
168+
169+
Nodes with `wait_for_output=True` default:
170+
- `JoinNode`: `True` (waits for all predecessors)
171+
- `LlmAgentWrapper` (task mode): `True` (set in `model_post_init`)
172+
- All other nodes: `False`
173+
174+
### Required Methods
175+
176+
| Method | Description |
177+
|--------|-------------|
178+
| `get_name() -> str` | Return the node name |
179+
| `run(*, ctx, node_input) -> AsyncGenerator` | Execute the node, yield events |
180+
181+
## ToolNode
182+
183+
Wrap an ADK tool as a workflow node:
184+
185+
```python
186+
from google.adk.workflow._tool_node import _ToolNode as ToolNode
187+
from google.adk.tools.function_tool import FunctionTool
188+
189+
def search(query: str) -> str:
190+
"""Search for information."""
191+
return f"Results for: {query}"
192+
193+
tool = FunctionTool(search)
194+
tool_node = ToolNode(tool, name="search_node")
195+
196+
agent = Workflow(
197+
name="with_tool",
198+
edges=[
199+
('START', prepare_query),
200+
(prepare_query, tool_node), # Input must be dict (tool args) or None
201+
(tool_node, process_results),
202+
],
203+
)
204+
```
205+
206+
**Important**: ToolNode input must be a dictionary of tool arguments or None.
207+
208+
## AgentNode
209+
210+
Wrap any `BaseAgent` (not just LlmAgent) as a workflow node:
211+
212+
```python
213+
from google.adk.workflow._agent_node import AgentNode
214+
from google.adk.agents.loop_agent import LoopAgent
215+
216+
loop = LoopAgent(
217+
name="refine_loop",
218+
sub_agents=[writer, reviewer],
219+
max_iterations=3,
220+
)
221+
222+
loop_node = AgentNode(agent=loop, name="refinement")
223+
224+
agent = Workflow(
225+
name="with_loop",
226+
edges=[
227+
('START', loop_node),
228+
(loop_node, final_step),
229+
],
230+
)
231+
```
232+
233+
## Graph Validation Rules
234+
235+
The workflow graph is validated on construction. These rules are enforced:
236+
237+
1. START node must exist
238+
2. START node must not have incoming edges
239+
3. All non-START nodes must be reachable (appear as `to_node` in some edge)
240+
4. No duplicate node names
241+
5. No duplicate edges
242+
6. At most one `__DEFAULT__` route per node
243+
7. No unconditional cycles (cycles must have at least one routed edge)
244+
245+
## Edge Construction Patterns
246+
247+
```python
248+
from google.adk.workflow import Edge
249+
from google.adk.workflow._workflow_graph import WorkflowGraph
250+
251+
# Tuple syntax (most common)
252+
edges = [
253+
('START', node_a), # Simple edge
254+
(node_a, node_b, "route"), # Routed edge
255+
(node_a, (node_b, node_c)), # Fan-out
256+
((node_b, node_c), join_node), # Fan-in
257+
]
258+
259+
# Sequence shorthand (tuple with 3+ elements creates chain)
260+
edges = [('START', node_a, node_b, node_c)]
261+
# Equivalent to: [('START', node_a), (node_a, node_b), (node_b, node_c)]
262+
263+
# Routing map (dict syntax)
264+
edges = [
265+
(classifier, {"success": handler_a, "error": handler_b}),
266+
]
267+
268+
# Edge objects (explicit)
269+
edges = [
270+
Edge(START, node_a),
271+
Edge(node_a, node_b, route="success"),
272+
]
273+
274+
# Edge.chain helper
275+
edges = Edge.chain('START', node_a, node_b, node_c)
276+
# Returns: [(START, node_a), (node_a, node_b), (node_b, node_c)]
277+
278+
# WorkflowGraph.from_edge_items
279+
graph = WorkflowGraph.from_edge_items([
280+
('START', node_a),
281+
(node_a, node_b),
282+
])
283+
agent = Workflow(name="my_workflow", graph=graph)
284+
```
285+
286+
## Source File Locations
287+
288+
| Component | File |
289+
|-----------|------|
290+
| Workflow | `src/google/adk/workflow/_workflow.py` |
291+
| WorkflowGraph, Edge | `src/google/adk/workflow/_workflow_graph.py` |
292+
| Context | `src/google/adk/agents/context.py` |
293+
| FunctionNode | `src/google/adk/workflow/_function_node.py` |
294+
| _LlmAgentWrapper | `src/google/adk/workflow/_llm_agent_wrapper.py` |
295+
| AgentNode | `src/google/adk/workflow/_agent_node.py` |
296+
| _ToolNode | `src/google/adk/workflow/_tool_node.py` |
297+
| JoinNode | `src/google/adk/workflow/_join_node.py` |
298+
| ParallelWorker | `src/google/adk/workflow/_parallel_worker.py` |
299+
| BaseNode, START | `src/google/adk/workflow/_base_node.py` |
300+
| @node decorator | `src/google/adk/workflow/_node.py` |
301+
| RetryConfig | `src/google/adk/workflow/_retry_config.py` |
302+
| Event | `src/google/adk/events/event.py` |
303+
| RequestInput | `src/google/adk/events/request_input.py` |

0 commit comments

Comments
 (0)