chore: import upstream snapshot with attribution
Continuous Integration / Pre-commit Linter (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.10) (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.11) (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.12) (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.13) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.10) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.11) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.12) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.13) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.14) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.10) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.11) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.12) (push) Has been cancelled
Copybara PR Handler / close-imported-pr (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.13) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.14) (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:25:13 +08:00
commit ec2b666284
2231 changed files with 491535 additions and 0 deletions
@@ -0,0 +1,61 @@
# Dynamic Fan-Out / Fan-In with Dynamic Nodes
## Overview
This sample demonstrates how to perform **Dynamic Fan-Out and Fan-In** using ADK's dynamic node scheduling (`ctx.run_node()`).
Unlike static graph-based parallel execution (which requires pre-defined branches), this pattern allows you to determine the number of parallel tasks at runtime based on the input data.
## Sample Inputs
- `AI, Cloud Computing, Quantum Computing`
- `Python, Go, Rust, TypeScript`
## Graph
```mermaid
graph TD
START --> Orchestrator
Orchestrator --> Gen_0[Generator Task 0]
Orchestrator --> Gen_1[Generator Task 1]
Orchestrator --> Gen_N[Generator Task N]
Gen_0 --> Aggregator[Orchestrator Fan-In]
Gen_1 --> Aggregator
Gen_N --> Aggregator
```
## How To
Key techniques demonstrated in this sample:
1. **Dynamic Scheduling**: Using a loop to create tasks via `ctx.run_node()`.
1. **Context Isolation**: Using `sub_branch` in `run_node` to isolate events for each parallel task, preventing context contamination.
1. **`rerun_on_resume=True`**: Required on the orchestrator node to support resumption if any child node interrupts.
### Code Snippet
```python
# Fan-out: Schedule a dynamic node for each topic
tasks = []
for i, topic in enumerate(topics):
tasks.append(
ctx.run_node(
generator,
node_input=topic,
sub_branch=f"branch_{i}"
)
)
# Wait for all tasks to complete
results = await asyncio.gather(*tasks)
```
## Pro Tip: Custom `run_id`
ADK auto-generates numeric IDs (e.g., `@1`), but you can pass a custom `run_id` to improve log readability (e.g., `generator@task_AI`) or map events to business keys.
**Rules**:
- **Unique**: Must be unique per node for fresh executions (otherwise returns cached results).
- **Non-Numeric**: Must contain non-numeric characters to avoid collision with auto-generated IDs.
@@ -0,0 +1,69 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
import asyncio
from google.adk import Agent
from google.adk import Context
from google.adk import Event
from google.adk import Workflow
from google.adk.workflow import node
# Worker agent to generate a headline for a single topic
generator = Agent(
name="generator",
instruction=(
"Write a catchy one-line headline about the topic provided in the user"
" message."
),
)
@node(rerun_on_resume=True)
async def orchestrator(ctx: Context, node_input: str) -> str:
"""Orchestrator node that performs dynamic fan-out and fan-in."""
# Split input comma-separated string into topics
topics = [t.strip() for t in node_input.split(",") if t.strip()]
yield Event(message=f"Processing {len(topics)} topics in parallel.")
# Fan-out: Schedule a dynamic node for each topic
tasks = []
for i, topic in enumerate(topics):
tasks.append(
ctx.run_node(
generator,
node_input=topic,
use_sub_branch=True,
)
)
# Wait for all tasks to complete
results = await asyncio.gather(*tasks)
# Fan-in: Aggregate results
aggregated = "### Aggregated Headlines\n\n"
aggregated += "| Topic | Headline |\n"
aggregated += "| :--- | :--- |\n"
for topic, headline in zip(topics, results):
aggregated += f"| {topic} | {headline} |\n"
yield Event(message=aggregated)
root_agent = Workflow(
name="dynamic_fan_out_fan_in",
edges=[("START", orchestrator)],
)