Skip to content

Commit c9b29bb

Browse files
committed
Merge branch 'main' into fix/memory-non-latin-search to resolve conflicts
2 parents ce930f2 + bfc236d commit c9b29bb

1,510 files changed

Lines changed: 128326 additions & 55357 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: 308 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,308 @@
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+
8+
- [ ] **Validation**: Does your graph follow all 7 validation rules? (e.g., no unconditional cycles)
9+
- [ ] **Custom Nodes**: If creating a custom node, did you override `get_name()` and `run()`?
10+
- [ ] **Dynamic Execution**: If using `run_node`, did you follow the rules in the dedicated dynamic-nodes reference?
11+
- [ ] **Waiting State**: Did you use `wait_for_output=True` if the node should stay in WAITING state until output is yielded?
12+
13+
## 💡 Quick Reference
14+
15+
- **Retry**: `RetryConfig(max_attempts=5, initial_delay=1.0)`
16+
- **Custom Node Fields**: `rerun_on_resume`, `wait_for_output`, `retry_config`, `timeout`
17+
18+
## Nested Workflows
19+
20+
A `Workflow` is both an agent and a node. Use one workflow inside another:
21+
22+
```python
23+
from google.adk.workflow import Workflow
24+
25+
# Inner workflow
26+
inner = Workflow(
27+
name="inner_pipeline",
28+
edges=[
29+
('START', step_a),
30+
(step_a, step_b),
31+
],
32+
)
33+
34+
# Outer workflow using inner as a node
35+
outer = Workflow(
36+
name="outer_pipeline",
37+
edges=[
38+
('START', pre_process),
39+
(pre_process, inner), # Nested workflow
40+
(inner, post_process),
41+
],
42+
)
43+
```
44+
45+
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.
46+
47+
## Dynamic Node Scheduling
48+
49+
Schedule nodes at runtime using `ctx.run_node()`.
50+
51+
See the dedicated [Dynamic Node Scheduling Reference](dynamic-nodes.md) for
52+
detailed rules, examples, and best practices.
53+
54+
## Retry Configuration
55+
56+
Configure automatic retry for nodes that may fail:
57+
58+
```python
59+
from google.adk.workflow import RetryConfig
60+
from google.adk.workflow import FunctionNode
61+
62+
retry = RetryConfig(
63+
max_attempts=5, # Max attempts (default: 5). 0 or 1 = no retry
64+
initial_delay=1.0, # Seconds before first retry (default: 1.0)
65+
max_delay=60.0, # Max seconds between retries (default: 60.0)
66+
backoff_factor=2.0, # Delay multiplier per attempt (default: 2.0)
67+
jitter=1.0, # Randomness factor (default: 1.0, 0.0 = none)
68+
exceptions=None, # Exception types to retry (None = all)
69+
)
70+
71+
node = FunctionNode(
72+
flaky_api_call,
73+
name="api_call",
74+
retry_config=retry,
75+
)
76+
```
77+
78+
### Retry delay formula
79+
80+
```
81+
delay = initial_delay * (backoff_factor ^ attempt)
82+
delay = min(delay, max_delay)
83+
delay = delay * (1 + random(0, jitter))
84+
```
85+
86+
### Accessing the attempt count
87+
88+
```python
89+
def my_node(ctx: Context, node_input: str) -> str:
90+
# attempt_count is 1 on the first try, ≥2 on retries
91+
if ctx.attempt_count > 1:
92+
print(f"Retry attempt {ctx.attempt_count}")
93+
return "result"
94+
```
95+
96+
## Custom Node Types
97+
98+
Subclass `BaseNode` for custom behavior:
99+
100+
```python
101+
from google.adk.workflow import BaseNode
102+
from google.adk.events.event import Event
103+
from google.adk.agents.context import Context
104+
from pydantic import ConfigDict, Field
105+
from typing import Any, AsyncGenerator
106+
from typing_extensions import override
107+
108+
class BatchProcessorNode(BaseNode):
109+
"""Processes items in batches."""
110+
model_config = ConfigDict(arbitrary_types_allowed=True)
111+
112+
name: str = Field(default="batch_processor")
113+
batch_size: int = Field(default=10)
114+
115+
def __init__(self, *, name: str = "batch_processor", batch_size: int = 10):
116+
super().__init__()
117+
object.__setattr__(self, 'name', name)
118+
object.__setattr__(self, 'batch_size', batch_size)
119+
120+
@override
121+
def get_name(self) -> str:
122+
return self.name
123+
124+
@override
125+
async def run(
126+
self,
127+
*,
128+
ctx: Context,
129+
node_input: Any,
130+
) -> AsyncGenerator[Any, None]:
131+
items = node_input if isinstance(node_input, list) else [node_input]
132+
results = []
133+
for i in range(0, len(items), self.batch_size):
134+
batch = items[i:i + self.batch_size]
135+
batch_result = await process_batch(batch)
136+
results.extend(batch_result)
137+
yield Event(output=results)
138+
```
139+
140+
### BaseNode Fields
141+
142+
| Field | Default | Description |
143+
|-------|---------|-------------|
144+
| `rerun_on_resume` | `False` | Whether to rerun after HITL interrupt |
145+
| `wait_for_output` | `False` | Node stays in WAITING state until it yields output (see below) |
146+
| `retry_config` | `None` | Retry configuration on failure |
147+
| `timeout` | `None` | Max seconds for node to complete |
148+
149+
### wait_for_output
150+
151+
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.
152+
153+
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.
154+
155+
```python
156+
from google.adk.workflow import BaseNode
157+
158+
class CollectorNode(BaseNode):
159+
wait_for_output: bool = True # Stay in WAITING until output is yielded
160+
161+
async def run(self, *, ctx, node_input):
162+
# Store partial input, don't yield output yet
163+
collected = ctx.state.get("collected", [])
164+
collected.append(node_input)
165+
yield Event(state={"collected": collected})
166+
167+
# Only yield output when we have enough
168+
if len(collected) >= 3:
169+
yield Event(output=collected)
170+
# Now node transitions to COMPLETED and triggers downstream
171+
```
172+
173+
Nodes with `wait_for_output=True` default:
174+
175+
- `JoinNode`: `True` (waits for all predecessors)
176+
- `LlmAgentWrapper` (task mode): `True` (set in `model_post_init`)
177+
- All other nodes: `False`
178+
179+
### Required Methods
180+
181+
| Method | Description |
182+
|--------|-------------|
183+
| `get_name() -> str` | Return the node name |
184+
| `run(*, ctx, node_input) -> AsyncGenerator` | Execute the node, yield events |
185+
186+
## ToolNode
187+
188+
Wrap an ADK tool as a workflow node:
189+
190+
```python
191+
from google.adk.workflow._tool_node import _ToolNode as ToolNode
192+
from google.adk.tools.function_tool import FunctionTool
193+
194+
def search(query: str) -> str:
195+
"""Search for information."""
196+
return f"Results for: {query}"
197+
198+
tool = FunctionTool(search)
199+
tool_node = ToolNode(tool, name="search_node")
200+
201+
agent = Workflow(
202+
name="with_tool",
203+
edges=[
204+
('START', prepare_query),
205+
(prepare_query, tool_node), # Input must be dict (tool args) or None
206+
(tool_node, process_results),
207+
],
208+
)
209+
```
210+
211+
**Important**: ToolNode input must be a dictionary of tool arguments or None.
212+
213+
## AgentNode
214+
215+
Wrap any `BaseAgent` (not just LlmAgent) as a workflow node:
216+
217+
```python
218+
from google.adk.workflow._agent_node import AgentNode
219+
from google.adk.agents.loop_agent import LoopAgent
220+
221+
loop = LoopAgent(
222+
name="refine_loop",
223+
sub_agents=[writer, reviewer],
224+
max_iterations=3,
225+
)
226+
227+
loop_node = AgentNode(agent=loop, name="refinement")
228+
229+
agent = Workflow(
230+
name="with_loop",
231+
edges=[
232+
('START', loop_node),
233+
(loop_node, final_step),
234+
],
235+
)
236+
```
237+
238+
## Graph Validation Rules
239+
240+
The workflow graph is validated on construction. These rules are enforced:
241+
242+
1. START node must exist
243+
2. START node must not have incoming edges
244+
3. All non-START nodes must be reachable (appear as `to_node` in some edge)
245+
4. No duplicate node names
246+
5. No duplicate edges
247+
6. At most one `__DEFAULT__` route per node
248+
7. No unconditional cycles (cycles must have at least one routed edge)
249+
250+
## Edge Construction Patterns
251+
252+
```python
253+
from google.adk.workflow import Edge
254+
from google.adk.workflow._workflow_graph import WorkflowGraph
255+
256+
# Tuple syntax (most common)
257+
edges = [
258+
('START', node_a), # Simple edge
259+
(node_a, node_b, "route"), # Routed edge
260+
(node_a, (node_b, node_c)), # Fan-out
261+
((node_b, node_c), join_node), # Fan-in
262+
]
263+
264+
# Sequence shorthand (tuple with 3+ elements creates chain)
265+
edges = [('START', node_a, node_b, node_c)]
266+
# Equivalent to: [('START', node_a), (node_a, node_b), (node_b, node_c)]
267+
268+
# Routing map (dict syntax)
269+
edges = [
270+
(classifier, {"success": handler_a, "error": handler_b}),
271+
]
272+
273+
# Edge objects (explicit)
274+
edges = [
275+
Edge(START, node_a),
276+
Edge(node_a, node_b, route="success"),
277+
]
278+
279+
# Edge.chain helper
280+
edges = Edge.chain('START', node_a, node_b, node_c)
281+
# Returns: [(START, node_a), (node_a, node_b), (node_b, node_c)]
282+
283+
# WorkflowGraph.from_edge_items
284+
graph = WorkflowGraph.from_edge_items([
285+
('START', node_a),
286+
(node_a, node_b),
287+
])
288+
agent = Workflow(name="my_workflow", graph=graph)
289+
```
290+
291+
## Source File Locations
292+
293+
| Component | File |
294+
|-----------|------|
295+
| Workflow | `src/google/adk/workflow/_workflow.py` |
296+
| WorkflowGraph, Edge | `src/google/adk/workflow/_workflow_graph.py` |
297+
| Context | `src/google/adk/agents/context.py` |
298+
| FunctionNode | `src/google/adk/workflow/_function_node.py` |
299+
| _LlmAgentWrapper | `src/google/adk/workflow/_llm_agent_wrapper.py` |
300+
| AgentNode | `src/google/adk/workflow/_agent_node.py` |
301+
| _ToolNode | `src/google/adk/workflow/_tool_node.py` |
302+
| JoinNode | `src/google/adk/workflow/_join_node.py` |
303+
| ParallelWorker | `src/google/adk/workflow/_parallel_worker.py` |
304+
| BaseNode, START | `src/google/adk/workflow/_base_node.py` |
305+
| @node decorator | `src/google/adk/workflow/_node.py` |
306+
| RetryConfig | `src/google/adk/workflow/_retry_config.py` |
307+
| Event | `src/google/adk/events/event.py` |
308+
| RequestInput | `src/google/adk/events/request_input.py` |

0 commit comments

Comments
 (0)