4.7 KiB
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.
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:
- Waiting for Predecessors: It sets
_requires_all_predecessorstoTrue. The workflow orchestrator checks this property and ensures thatJoinNodeis only executed after all nodes pointing to it have completed. - Input Aggregation: When executed, the orchestrator provides
JoinNodewith 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. - Pass-through Execution: The
JoinNode's_run_implsimply yields this aggregated dictionary as its output event. - Branch Merging: In parallel execution, nodes might run in different branch contexts (e.g.,
NodeA@1,NodeB@1).JoinNodecomputes 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:
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
JoinNodeis 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
JoinNodehas a predecessor that is part of a conditional routing path, and that path is not taken, theJoinNodewill never trigger, and the workflow may hang or stall. All static predecessors defined in the graph for aJoinNodemust execute and complete.