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
108 lines
4.7 KiB
Markdown
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/)
|