chore: import upstream snapshot with attribution
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
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
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
# Workflow Graphs
|
||||
|
||||
In ADK 2.0, workflows are represented as directed graphs where execution flows from node to node along defined edges. This guide explains the core concepts of **nodes**, **edges**, and **graphs**, how to define them, and the validation rules enforced by the framework.
|
||||
|
||||
## Introduction
|
||||
|
||||
A workflow graph defines the execution plan for your multi-step agent interactions. It specifies:
|
||||
- What tasks to run (Nodes).
|
||||
- The order of execution (Edges).
|
||||
- How data flows and how branches fork or merge.
|
||||
|
||||
The graph structure is compiled and validated when you instantiate the `Workflow` class.
|
||||
|
||||
## Core Concepts
|
||||
|
||||
### Nodes (`NodeLike`)
|
||||
|
||||
A node represents a single unit of execution in the workflow. In ADK, you can use several types of objects as nodes (collectively referred to as `NodeLike`):
|
||||
|
||||
1. **Python Functions**: Sync or async functions (and generators) decorated with `@node`. They are automatically wrapped in a `FunctionNode`.
|
||||
2. **Agents**: `LlmAgent` instances (typically in `single_turn` mode). They are automatically wrapped in an internal `_LlmAgentWrapper`.
|
||||
3. **Tools**: `BaseTool` instances. They are wrapped in a `ToolNode`.
|
||||
4. **Workflows**: A `Workflow` is itself a `BaseNode` and can be nested as a child node in another workflow.
|
||||
5. **`START`**: A special sentinel node that marks the entry point of the workflow. Every graph must have exactly one edge starting from `START`.
|
||||
|
||||
### Edges (`Edge`)
|
||||
|
||||
An edge defines a transition from a source node (`from_node`) to a destination node (`to_node`).
|
||||
|
||||
#### Unconditional Edges
|
||||
By default, edges are unconditional. When the source node completes, execution immediately transitions to the destination node.
|
||||
|
||||
#### Conditional Edges (Routing)
|
||||
An edge can be associated with one or more **routes** (a string, integer, or boolean). The edge is only followed if the source node explicitly emits a matching route.
|
||||
|
||||
To emit a route, the source node must yield an `Event(route="my_route")` (or return/yield an object that maps to that route).
|
||||
|
||||
#### Default Route
|
||||
You can define a fallback edge using `DEFAULT_ROUTE` (imported as `from google.adk.workflow import DEFAULT_ROUTE` or using `"__DEFAULT__"`). This edge is followed if the source node emits a route, but no specific conditional edge matches it.
|
||||
|
||||
---
|
||||
|
||||
## Defining the Graph (Syntax)
|
||||
|
||||
You define the graph structure by passing a list of `edges` to the `Workflow` constructor. ADK supports two syntax styles:
|
||||
|
||||
### 1. Chain Tuples (Recommended)
|
||||
|
||||
Chain tuples provide a concise way to define sequential, parallel, and conditional transitions using Python tuples.
|
||||
|
||||
* **Sequential Chain**:
|
||||
```python
|
||||
edges=[
|
||||
(START, step_a, step_b, step_c),
|
||||
]
|
||||
```
|
||||
This defines: `START -> step_a -> step_b -> step_c`.
|
||||
|
||||
* **Parallel Fan-Out**: Use a tuple of nodes to split execution into parallel branches.
|
||||
```python
|
||||
edges=[
|
||||
(START, step_a, (step_b, step_c)),
|
||||
]
|
||||
```
|
||||
This defines: `START -> step_a`, and then `step_a -> step_b` AND `step_a -> step_c` in parallel.
|
||||
|
||||
* **Conditional Routing**: Use a dictionary (Routing Map) to define conditional branches.
|
||||
```python
|
||||
from google.adk.workflow import DEFAULT_ROUTE
|
||||
|
||||
edges=[
|
||||
(START, step_a, {
|
||||
"success": step_b,
|
||||
"failure": step_c,
|
||||
DEFAULT_ROUTE: fallback_step,
|
||||
}),
|
||||
]
|
||||
```
|
||||
If `step_a` yields `Event(route="success")`, it goes to `step_b`. If it yields `"failure"`, it goes to `step_c`. Any other route goes to `fallback_step`.
|
||||
|
||||
### 2. Explicit Edge Objects
|
||||
|
||||
For complex graphs or when you prefer explicit declarations, you can use `Edge` objects:
|
||||
|
||||
```python
|
||||
from google.adk.workflow import Edge, START
|
||||
|
||||
edges=[
|
||||
Edge(from_node=START, to_node=step_a),
|
||||
Edge(from_node=step_a, to_node=step_b, route="success"),
|
||||
Edge(from_node=step_a, to_node=step_c, route="failure"),
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Graph Validation
|
||||
|
||||
When a `Workflow` is initialized, it builds an internal `Graph` representation and runs `validate_graph()` to catch structural errors early. The following rules are strictly enforced:
|
||||
|
||||
### 1. Unique Node Names
|
||||
All distinct node objects in the graph must have unique names.
|
||||
* *Error*: If you have two different function nodes named `process_data`, validation will fail.
|
||||
* *Solution*: Ensure unique names, or reuse the exact same object instance if you want to route back to the same node.
|
||||
|
||||
### 2. Single START Entry Point
|
||||
The graph must contain the `START` node, and `START` must not have any incoming edges.
|
||||
* *Error*: A graph without `START` or with an edge pointing back to `START` will fail validation.
|
||||
|
||||
### 3. Connectivity (Reachability)
|
||||
All nodes in the graph must be reachable from the `START` node.
|
||||
* *Error*: If you define a node but do not connect it to the rest of the graph, validation will fail.
|
||||
|
||||
### 4. No Duplicate Edges
|
||||
You cannot define duplicate edges between the same two nodes.
|
||||
* *Error*: `Edge(from_node=A, to_node=B)` and `Edge(from_node=A, to_node=B)` in the same list will fail.
|
||||
|
||||
### 5. Default Route Constraints
|
||||
- A node can have at most one outgoing `DEFAULT_ROUTE` edge.
|
||||
- `DEFAULT_ROUTE` cannot be combined with other routes in a list (e.g., `route=["success", DEFAULT_ROUTE]` is invalid).
|
||||
|
||||
### 6. No Unconditional Cycles
|
||||
The graph must not contain cycles consisting entirely of unconditional edges (edges with no route).
|
||||
* *Allowed*: Conditional loops are allowed (e.g., `A -> B -> A` where `B -> A` is conditional on a route).
|
||||
* *Forbidden*: Unconditional loops (`A -> B -> A` with no routes) are rejected to prevent infinite execution loops.
|
||||
|
||||
### 7. Static Schema Matching
|
||||
If a node defines an `output_schema` and its successor defines an `input_schema`, they must match exactly.
|
||||
* *Error*: Schema mismatch on transition edges will fail validation.
|
||||
|
||||
### 8. Chat Agent Wiring
|
||||
`LlmAgent` instances configured with `mode='chat'` are only allowed to follow the `START` node.
|
||||
* *Reason*: Chat-mode agents manage their own conversational history and cannot consume direct inputs from preceding nodes in a workflow chain. For sequential steps, use `mode='single_turn'`.
|
||||
Reference in New Issue
Block a user