Files
wehub-resource-sync ec2b666284
Continuous Integration / Pre-commit Linter (push) Waiting to run
Continuous Integration / Mypy Check (Python 3.10) (push) Waiting to run
Continuous Integration / Mypy Check (Python 3.11) (push) Waiting to run
Continuous Integration / Mypy Check (Python 3.12) (push) Waiting to run
Continuous Integration / Mypy Check (Python 3.13) (push) Waiting to run
Continuous Integration / Unit Tests (Python 3.10) (push) Waiting to run
Continuous Integration / Unit Tests (Python 3.11) (push) Waiting to run
Continuous Integration / Unit Tests (Python 3.12) (push) Waiting to run
Continuous Integration / Unit Tests (Python 3.13) (push) Waiting to run
Continuous Integration / Unit Tests (Python 3.14) (push) Waiting to run
Continuous Integration / A2A v0.3 Tests (Python 3.10) (push) Waiting to run
Continuous Integration / A2A v0.3 Tests (Python 3.11) (push) Waiting to run
Continuous Integration / A2A v0.3 Tests (Python 3.12) (push) Waiting to run
Continuous Integration / A2A v0.3 Tests (Python 3.13) (push) Waiting to run
Continuous Integration / A2A v0.3 Tests (Python 3.14) (push) Waiting to run
Copybara PR Handler / close-imported-pr (push) Waiting to run
chore: import upstream snapshot with attribution
2026-07-13 13:25:13 +08:00

108 lines
4.7 KiB
Markdown

# JoinNode
`JoinNode` is a built-in workflow node used to synchronize parallel execution paths (fan-out/fan-in) by waiting for all its predecessor nodes to complete.
## Introduction
In complex workflows, you may want to run multiple tasks in parallel to improve efficiency or perform independent operations, and then aggregate their results before proceeding. `JoinNode` solves this by acting as a synchronization barrier. It waits for all incoming edges to complete and aggregates their outputs into a single dictionary, which is then passed to the downstream node.
Key features:
- **Synchronization**: Automatically pauses execution of downstream paths until all parallel predecessor branches have completed.
- **Aggregation**: Combines outputs from multiple nodes into a single structured dictionary.
- **Branch Resolution**: Computes a common branch prefix for the output event, merging parallel branches.
## Get started
The following example demonstrates a simple fan-out/fan-in workflow where three tasks run in parallel, and their results are aggregated by a `JoinNode`.
```python
from typing import Any
from google.adk import Event, Workflow
from google.adk.workflow import JoinNode
# Define parallel tasks
def make_uppercase(node_input: str) -> str:
return node_input.upper()
def count_characters(node_input: str) -> int:
return len(node_input)
def reverse_string(node_input: str) -> str:
return node_input[::-1]
# Define the JoinNode
join_node = JoinNode(name="join_for_results")
# Define the aggregation node
async def aggregate(node_input: dict[str, Any]):
yield Event(
message=(
f"Uppercase: {node_input['make_uppercase']}\n"
f"Character Count: {node_input['count_characters']}\n"
f"Reversed: {node_input['reverse_string']}\n"
),
)
# Build the workflow
root_agent = Workflow(
name="root_agent",
edges=[(
"START",
(make_uppercase, count_characters, reverse_string),
join_node,
aggregate,
)],
)
```
## How it works
`JoinNode` inherits from `BaseNode` and overrides key behaviors to support synchronization:
1. **Waiting for Predecessors**: It sets `_requires_all_predecessors` to `True`. The workflow orchestrator checks this property and ensures that `JoinNode` is only executed after all nodes pointing to it have completed.
2. **Input Aggregation**: When executed, the orchestrator provides `JoinNode` with a dictionary containing the outputs of all its predecessors. The keys of this dictionary are the names of the predecessor nodes, and the values are their respective outputs.
3. **Pass-through Execution**: The `JoinNode`'s `_run_impl` simply yields this aggregated dictionary as its output event.
4. **Branch Merging**: In parallel execution, nodes might run in different branch contexts (e.g., `NodeA@1`, `NodeB@1`). `JoinNode` computes the common branch prefix of all incoming triggers. If they ran in parallel branches of the same iteration, they are merged back into the parent branch context.
## Configuration options
`JoinNode` does not introduce new configuration options beyond what is inherited from `BaseNode`. However, it overrides the behavior of `input_schema`:
| Option | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `input_schema` | `SchemaType` | `None` | Schema to validate **individual** trigger inputs (outputs of predecessor nodes), not the aggregated dictionary. |
### Input Schema Validation
When `input_schema` is set on a `JoinNode`, it validates each predecessor's output individually as it arrives (or during aggregation).
- If a predecessor's output is a dictionary, it is validated against the `input_schema`.
- If a predecessor's output is `None`, validation is skipped for that input.
- If validation fails for any input, the workflow execution fails.
Example using `input_schema`:
```python
from pydantic import BaseModel
from google.adk.workflow import JoinNode
class ProcessedData(BaseModel):
value: int
status: str
# This JoinNode will ensure that every predecessor node outputs data
# that conforms to the ProcessedData schema.
validation_join = JoinNode(
name="validation_join",
input_schema=ProcessedData
)
```
## Limitations
- **Dictionary Output**: The output of `JoinNode` is always a dictionary with predecessor node names as keys. If you need a different format, you must use a downstream node to transform it.
- **Conditional Routing**: If a `JoinNode` has a predecessor that is part of a conditional routing path, and that path is not taken, the `JoinNode` will never trigger, and the workflow may hang or stall. All static predecessors defined in the graph for a `JoinNode` must execute and complete.
## Related samples
- [Fan-Out / Fan-In Sample](../../../../contributing/samples/workflows/fan_out_fan_in/)