Skip to content

Commit 9beec74

Browse files
authored
Merge branch 'main' into fix/non-english-eval-rouge
2 parents f5e5c40 + 5cfef01 commit 9beec74

103 files changed

Lines changed: 4663 additions & 35033 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.

.agents/skills/adk-agent-builder/references/advanced-patterns.md

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@ Nested workflows, dynamic nodes, retry configuration, custom node types, and gra
44

55
## 📋 Agent Verification Checklist (Advanced Patterns)
66
Use this checklist when implementing complex workflows:
7+
78
- [ ] **Validation**: Does your graph follow all 7 validation rules? (e.g., no unconditional cycles)
89
- [ ] **Custom Nodes**: If creating a custom node, did you override `get_name()` and `run()`?
910
- [ ] **Dynamic Execution**: If using `run_node`, did you follow the rules in the dedicated dynamic-nodes reference?
1011
- [ ] **Waiting State**: Did you use `wait_for_output=True` if the node should stay in WAITING state until output is yielded?
1112

1213
## 💡 Quick Reference
14+
1315
- **Retry**: `RetryConfig(max_attempts=5, initial_delay=1.0)`
1416
- **Custom Node Fields**: `rerun_on_resume`, `wait_for_output`, `retry_config`, `timeout`
1517

@@ -46,7 +48,8 @@ The inner workflow receives the predecessor's output as its START input and its
4648

4749
Schedule nodes at runtime using `ctx.run_node()`.
4850

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.
51+
See the dedicated [Dynamic Node Scheduling Reference](dynamic-nodes.md) for
52+
detailed rules, examples, and best practices.
5053

5154
## Retry Configuration
5255

@@ -80,12 +83,13 @@ delay = min(delay, max_delay)
8083
delay = delay * (1 + random(0, jitter))
8184
```
8285

83-
### Accessing retry count
86+
### Accessing the attempt count
8487

8588
```python
8689
def my_node(ctx: Context, node_input: str) -> str:
87-
if ctx.retry_count > 0:
88-
print(f"Retry attempt {ctx.retry_count}")
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}")
8993
return "result"
9094
```
9195

@@ -167,6 +171,7 @@ class CollectorNode(BaseNode):
167171
```
168172

169173
Nodes with `wait_for_output=True` default:
174+
170175
- `JoinNode`: `True` (waits for all predecessors)
171176
- `LlmAgentWrapper` (task mode): `True` (set in `model_post_init`)
172177
- All other nodes: `False`

.agents/skills/adk-agent-builder/references/import-paths.md

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## 📋 Agent Verification Checklist (Imports)
44
Use this checklist to ensure you are using the most idiomatic import paths:
5+
56
- [ ] **Canonical Imports**: Did you use the short canonical imports where available (e.g., `from google.adk import Agent`) instead of the verbose ones?
67
- [ ] **Avoid Deprecated**: Are you avoiding deprecated paths (e.g., use `McpToolset` instead of `MCPToolset`)?
78

@@ -34,16 +35,25 @@ from google.adk.workflow import node, RetryConfig, Edge, JoinNode
3435

3536
## Workflow Nodes
3637

37-
| Component | Import |
38-
|-----------|--------|
39-
| `FunctionNode` | `from google.adk.workflow import FunctionNode` |
40-
| `_LlmAgentWrapper` (private, auto-used) | `from google.adk.workflow._llm_agent_wrapper import _LlmAgentWrapper` |
41-
| `AgentNode` | `from google.adk.workflow._agent_node import AgentNode` |
42-
| `_ToolNode` (private) | `from google.adk.workflow._tool_node import _ToolNode` |
43-
| `JoinNode` | `from google.adk.workflow import JoinNode` |
44-
| `ParallelWorker` | `from google.adk.workflow._parallel_worker import ParallelWorker` |
45-
| `BaseNode`, `START` | `from google.adk.workflow import BaseNode, START` |
46-
| `@node` decorator | `from google.adk.workflow import node` |
38+
| Component | Import |
39+
| ----------------------------------- | -------------------------------------- |
40+
| `FunctionNode` | `from google.adk.workflow import |
41+
: : FunctionNode` :
42+
| `_LlmAgentWrapper` (private, | `from |
43+
: auto-used) : google.adk.workflow._llm_agent_wrapper :
44+
: : import _LlmAgentWrapper` :
45+
| `AgentNode` | `from google.adk.workflow._agent_node |
46+
: : import AgentNode` :
47+
| `_ToolNode` (private) | `from google.adk.workflow._tool_node |
48+
: : import _ToolNode` :
49+
| `JoinNode` | `from google.adk.workflow import |
50+
: : JoinNode` :
51+
| Parallel-worker behavior (no public | Set `parallel_worker=True` on `@node` |
52+
: class) : or `LlmAgent`; the framework wraps :
53+
: : with an internal `_ParallelWorker` :
54+
| `BaseNode`, `START` | `from google.adk.workflow import |
55+
: : BaseNode, START` :
56+
| `@node` decorator | `from google.adk.workflow import node` |
4757

4858
## Workflow Events and Context
4959

.agents/skills/adk-agent-builder/references/llm-agent-nodes.md

Lines changed: 29 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@ Embed LLM-powered agents as nodes in workflow graphs.
44

55
## 📋 Agent Verification Checklist (LLM Nodes)
66
Use this checklist to verify your LLM agent configuration:
7+
78
- [ ] **Output Type**: If no `output_schema` is set, downstream now receives `str` (auto-extracted from `types.Content`). You can safely type-hint `node_input: str`.
89
- [ ] **State Serialization**: If this agent feeds into a `JoinNode`, did you set `output_schema` to avoid non-serializable `types.Content` errors?
910
- [ ] **Instructions**: Are `{var}` templates used in instructions resolving ONLY from `ctx.state`? (Not `node_input`)
1011
- [ ] **Config**: Are instructions, tools, and response schema set on the `LlmAgent` directly, and NOT in `generate_content_config`?
1112

1213
## 💡 Quick Reference
14+
1315
- **Chat Mode**: Default. Multi-turn, keeps session history.
1416
- **Single-Turn Mode**: Isolated. Set `mode="single_turn"` or rely on auto-wrapping defaults.
1517
- **Task Mode**: Multi-turn within a task. Set `mode="task"`.
@@ -70,25 +72,25 @@ agent = Workflow(
7072
)
7173
```
7274

73-
## LLM Agent Output Types (Critical)
75+
## LLM Agent Output Types
7476

75-
**LlmAgentWrapper auto-extracts text and outputs `str` when no `output_schema` is set.** Previously, it outputted `types.Content` causing type errors. Now, if you type-hint `node_input: str`, it will work correctly for standard text output.
77+
When an `LlmAgent` runs as a workflow node, `process_llm_agent_output` (in
78+
`_llm_agent_wrapper.py`) sets `event.output` to:
7679

77-
**Solutions (pick one):**
80+
- The **concatenated text** of the model's response (a `str`) — when
81+
`output_schema` is not set.
82+
- The **validated dict** (`model_dump()` of the Pydantic model) — when
83+
`output_schema=MyModel` is set.
7884

79-
1. **Use `Any` and extract text** (recommended for function nodes after LLM agents):
85+
A downstream function node typed `node_input: str` therefore works in the
86+
default case, and `node_input: dict` works when `output_schema` is set.
8087

81-
```python
82-
from typing import Any
83-
from google.genai import types
84-
85-
def process_llm_output(node_input: Any) -> str:
86-
if isinstance(node_input, types.Content):
87-
return ''.join(p.text for p in (node_input.parts or []) if p.text)
88-
return str(node_input) if node_input is not None else ''
89-
```
90-
91-
2. **Use `output_schema`** on the LLM agent to get a parsed `dict` instead:
88+
**Observability caveat:** the value above is set on the event internally and
89+
forwarded to the next node, but `event.output` is **`None`** when you observe it
90+
from `runner.run_async(...)` for the LLM agent's own event — the framework
91+
clears it before the event reaches user code. Don't write tests that assert on
92+
`event.output` for an LLM agent's event; assert on the downstream node's output,
93+
on `session.state[output_key]`, or on `event.content.parts[*].text` instead.
9294

9395
```python
9496
from pydantic import BaseModel
@@ -104,23 +106,28 @@ writer = LlmAgent(
104106
output_schema=CodeOutput,
105107
)
106108

107-
# Downstream node receives dict: {"code": "...", "language": "python"}
109+
# Downstream node receives a dict: {"code": "...", "language": "python"}
108110
def process_code(node_input: dict) -> str:
109111
return node_input["code"]
110112
```
111113

112114
**Summary of LLM agent node output types:**
113115

114-
| LLM Agent Config | `node_input` Type for Next Node |
115-
|-----------------|-------------------------------|
116-
| No `output_schema` | `types.Content` |
117-
| With `output_schema` | `dict` (parsed from Pydantic model) |
116+
LLM Agent Config | `node_input` Type for Next Node
117+
-------------------- | -----------------------------------
118+
No `output_schema` | `str` (concatenated model text)
119+
With `output_schema` | `dict` (parsed from Pydantic model)
118120

119-
**State serialization warning:** When LLM agents feed into a `JoinNode`, the JoinNode stores intermediate results in session state. Without `output_schema`, this stores `types.Content` objects which are **not JSON-serializable** and will cause `TypeError` with SQLite/database session services. Always use `output_schema` on LLM agents that feed into a JoinNode.
121+
**Prefer `output_schema` when downstream nodes need structured access.** Strings
122+
are fine for pass-through text, but a typed dict is easier to consume and is
123+
required when the predecessor feeds a `JoinNode` whose results land in a
124+
persistent session service (raw text is fine; objects that aren't
125+
JSON-serializable break `DatabaseSessionService`).
120126

121127
## Auto-Wrapping Behavior
122128

123129
When you place an `LlmAgent` in workflow edges, it is auto-wrapped as `_LlmAgentWrapper`. The wrapper:
130+
124131
- Defaults to `single_turn` mode (agent sees only current input, not session history)
125132
- Sets `rerun_on_resume=True` (reruns after HITL interrupts)
126133
- Creates a content branch for isolation between parallel LLM agents
@@ -260,6 +267,7 @@ agent = LlmAgent(
260267
```
261268

262269
Tools can be:
270+
263271
- Python functions (auto-wrapped as `FunctionTool`)
264272
- `BaseTool` instances
265273
- `BaseToolset` instances (e.g., MCP toolsets)

.agents/skills/adk-agent-builder/references/parallel-and-fanout.md

Lines changed: 18 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,27 @@ Execute multiple nodes concurrently and collect their results.
44

55
## 📋 Agent Verification Checklist (Parallel & Fan-Out)
66
Use this checklist when implementing parallel patterns:
7+
78
- [ ] **JoinNode Serialization**: If LLM agents feed into a `JoinNode`, did you set `output_schema` on them to prevent JSON serialization errors?
89
- [ ] **ParallelWorker Usage**: Did you avoid using `parallel_worker=True` on fan-out nodes? (It expects a list input)
910
- [ ] **Multi-Trigger vs Join**: Do you understand that Multi-Trigger fires downstream once per branch, while JoinNode waits and fires once with merged dict?
1011

1112
## 💡 Quick Reference
13+
1214
- **Fan-Out (Tuple)**: `('START', (node_a, node_b))`
1315
- **Fan-In (JoinNode)**: `((node_a, node_b), join_node)`
1416
- **List Worker**: `@node(parallel_worker=True)` (Takes list, outputs list)
1517

1618
## Imports
1719

1820
```python
19-
from google.adk.workflow import Workflow
20-
from google.adk.workflow._parallel_worker import ParallelWorker
21-
from google.adk.workflow import JoinNode
22-
from google.adk.workflow import node
21+
from google.adk.workflow import Workflow, JoinNode, node
2322
```
2423

24+
Parallel-worker behavior is opted into via the `parallel_worker=True` flag on
25+
`@node` or `LlmAgent`. The underlying wrapper class is internal — don't import
26+
it directly.
27+
2528
## Fan-Out: Multiple Branches
2629

2730
Send output to multiple nodes simultaneously using tuple syntax:
@@ -91,51 +94,43 @@ def final_processor(node_input: dict) -> str:
9194

9295
**Serialization warning:** JoinNode stores partial inputs in session state while waiting. If predecessors are LLM agents without `output_schema`, the stored values are `types.Content` objects which are **not JSON-serializable**. This causes `TypeError` with SQLite/database session services. Fix: use `output_schema` on LLM agents feeding into a JoinNode.
9396

94-
## ParallelWorker: Process Lists in Parallel
97+
## Parallel workers: process lists in parallel
9598

96-
Apply the same node to each item in a list concurrently:
99+
Apply the same node to each item in a list concurrently by setting the
100+
`parallel_worker=True` flag. The framework wraps the node internally — there is
101+
no public `ParallelWorker` class to import.
97102

98103
```python
104+
from google.adk.workflow import node, Workflow
105+
106+
@node(parallel_worker=True)
99107
def process_item(node_input: int) -> int:
100108
return node_input * 2
101109

102-
parallel = ParallelWorker(node(process_item))
103-
104110
def produce_list(node_input: str) -> list:
105111
return [1, 2, 3, 4, 5]
106112

107113
agent = Workflow(
108114
name="parallel_processing",
109115
edges=[
110116
('START', produce_list),
111-
(produce_list, parallel),
117+
(produce_list, process_item),
112118
],
113119
)
114120
# Output: [2, 4, 6, 8, 10]
115121
```
116122

117-
### ParallelWorker Details
123+
### Behavior
118124

119125
- Input: a **list** (or single item, which gets wrapped in a list)
120126
- Output: a **list** of results in the same order as inputs
121127
- Empty list input produces empty list output
122128
- Each item is processed by a dynamically created worker node
123-
- Workers are named `{parent_name}__{index}` (e.g., `process_item__0`)
124129
- Default `rerun_on_resume=True`
125130

126-
### ParallelWorker with @node Decorator
127-
128-
```python
129-
@node(parallel_worker=True)
130-
def process_item(node_input: int) -> int:
131-
return node_input * 2
132-
133-
# Equivalent to: ParallelWorker(FunctionNode(process_item_fn))
134-
```
135-
136-
### ParallelWorker with Agents
131+
### Parallel workers with Agents
137132

138-
Set `parallel_worker=True` directly on an Agent:
133+
Set `parallel_worker=True` directly on an Agent — no extra wrapping needed:
139134

140135
```python
141136
from google.adk import Agent
@@ -155,12 +150,6 @@ agent = Workflow(
155150
)
156151
```
157152

158-
Or wrap manually:
159-
160-
```python
161-
parallel_analyzer = ParallelWorker(analyzer)
162-
```
163-
164153
**Do NOT use `parallel_worker=True` on fan-out nodes.** Fan-out edges `(a, (b, c, d))` already run nodes in parallel. Adding `parallel_worker=True` makes the node expect a list input and iterate over it — if it receives a single value or None, it produces no output and the JoinNode gets nothing.
165154

166155
## Multi-Trigger (Fan-Out to Shared Downstream)

.agents/skills/adk-agent-builder/references/state-and-events.md

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@ Manage shared state across workflow nodes and understand the event system.
44

55
## 📋 Agent Verification Checklist (State & Events)
66
Use this checklist when working with state and events:
7+
78
- [ ] **State Updates**: Did you use `Event(state=...)` for state updates? (Captures delta in event history)
89
- [ ] **Parameter Resolution**: Are custom parameters named after keys in `ctx.state`?
910
- [ ] **Output Serialization**: Is `event.output` JSON-serializable? (Required for DB session services)
1011
- [ ] **Web UI Display**: Did you use `Event(message=...)` for output meant for users?
1112

1213
## 💡 Quick Reference (Resolution Order)
14+
1315
1. **`ctx`**: Workflow `Context` object.
1416
2. **`node_input`**: Predecessor output.
1517
3. **Other names**: Looked up from `ctx.state[param_name]`.
@@ -33,9 +35,9 @@ def my_node(ctx: Context, node_input: str) -> str:
3335
invocation_id = ctx.invocation_id
3436

3537
# Get node metadata
36-
node_path = ctx.node_path # e.g., "MyWorkflow/my_node"
37-
triggered_by = ctx.triggered_by # Name of predecessor node
38-
retry_count = ctx.retry_count # 0 on first attempt
38+
node_path = ctx.node_path # e.g., "MyWorkflow/my_node"
39+
run_id = ctx.run_id # this node-run's identifier
40+
attempt = ctx.attempt_count # 1 on first attempt, ≥1 thereafter
3941

4042
return f"Processed: {value}"
4143
```
@@ -57,21 +59,21 @@ def my_node(ctx: Context, node_input: str) -> str:
5759

5860
### Workflow-Only Properties
5961

60-
| Property | Type | Description |
61-
|----------|------|-------------|
62-
| `node_path` | `str` | Full path of current node (e.g., "WorkflowA/node1") |
63-
| `execution_id` | `str` | Unique ID for this execution |
64-
| `triggered_by` | `str` | Name of node that triggered current node |
65-
| `in_nodes` | `frozenset[str]` | Names of all predecessor nodes |
66-
| `resume_inputs` | `dict[str, Any]` | Inputs for resuming (keyed by interrupt_id) |
67-
| `retry_count` | `int` | Number of times this node has been retried |
62+
| Property | Type | Description |
63+
| --------------- | ---------------- | ------------------------------------- |
64+
| `node_path` | `str` | Full path of current node (e.g., |
65+
: : : "WorkflowA/node1") :
66+
| `run_id` | `str` | Identifier for this node-run (e.g., |
67+
: : : `"1"`, `"2"`) :
68+
| `attempt_count` | `int` | Retry attempt number (1 on first try) |
69+
| `resume_inputs` | `dict[str, Any]` | Inputs for resuming (keyed by |
70+
: : : interrupt_id) :
6871

6972
### Workflow-Only Methods
7073

7174
| Method | Returns | Description |
7275
|--------|---------|-------------|
7376
| `run_node(node, node_input, *, name)` | `Any` | Execute a node dynamically (requires `rerun_on_resume=True`) |
74-
| `get_next_child_execution_id(name)` | `str` | Generate a deterministic child execution ID |
7577

7678
## State Management
7779

@@ -92,6 +94,7 @@ def node_a(ctx: Context, node_input: str) -> str:
9294
```
9395

9496
**Why `Event(state=...)` is preferred:**
97+
9598
- State deltas are persisted in event history as `event.actions.state_delta`
9699
- Non-resumable HITL can reconstruct state by replaying events
97100
- Makes state changes explicit and traceable
@@ -120,6 +123,7 @@ def my_node(node_input: str, user_name: str, threshold: float) -> str:
120123
```
121124

122125
Resolution order:
126+
123127
1. `ctx` -> Context object
124128
2. `node_input` -> predecessor output
125129
3. Other names -> `ctx.state[param_name]` (with auto type conversion)

0 commit comments

Comments
 (0)