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

166 lines
7.3 KiB
Markdown

# Function Nodes
In ADK, any standard Python function, coroutine, or generator can be used as a workflow node. The framework automatically wraps these callables under the hood, allowing you to build complex graphs with minimal boilerplate.
## Introduction
Function nodes are the most common and lightweight way to implement logic in ADK workflows. Instead of subclassing `BaseNode` for every step, you can write standard Python functions.
Developer problems solved:
- **Zero Boilerplate**: Write standard Python code without framework-specific class definitions.
- **Implicit Wrapping**: Pass functions directly to workflow edges; the framework handles integration automatically.
- **Declarative Signatures**: Access workflow state, input from predecessor nodes, or the execution context simply by declaring them in the function parameters.
## Get started
The following example demonstrates how to define standard Python functions and use them directly in a workflow chain.
```python
from google.adk import START, Workflow
# 1. Simple sequential steps
# The output of step_one is automatically passed as input to step_two
def step_one(node_input: str) -> str:
return f"{node_input} -> step_one"
def step_two(node_input: str) -> str:
return f"{node_input} -> step_two"
# 2. Step that accesses workflow state
# user_name is automatically resolved from ctx.state["user_name"]
def step_three(node_input: str, user_name: str) -> str:
return f"Hello {user_name}! {node_input}"
# Use the functions directly in the workflow edges
workflow = Workflow(
name="my_workflow",
edges=[
(START, step_one, step_two, step_three),
],
)
```
## How it works
When a workflow executes a function node, it performs several operations automatically:
### Parameter Resolution
The framework inspects the function signature to determine how to populate its arguments:
* **`ctx`** (or any parameter type-hinted as `Context`): Injects the workflow `Context` object.
* **`node_input`**: Injects the output value from the predecessor node.
* **Any other parameter**: Resolved by looking up the parameter name in `ctx.state` (or `node_input` if parameter binding is customized).
### Type Coercion
Input values are automatically validated and coerced to match the function's type hints using Pydantic:
* **Pydantic Models**: If a parameter is type-hinted as a Pydantic `BaseModel` (e.g., `node_input: MyModel`) and the input is a dictionary, it is auto-converted to the model instance.
* **Content to String**: If a parameter expects a `str` but receives a `types.Content` object (e.g. the raw user message from `START`), it automatically extracts and concatenates the text parts.
### Event Normalization
Return and yield values are normalized to `Event` objects:
* Returning or yielding `None` does not emit an output event, but execution continues downstream with `None` passed as the input to successor nodes.
* Raw values (strings, dicts, etc.) are wrapped in `Event(output=value)`.
* Pydantic models are serialized to dictionaries.
* State changes made via `ctx.state` during execution are automatically captured and attached to the event to be persisted.
## Configuration & Explicit Wrapping
While implicit wrapping works for most cases, you can wrap functions explicitly using the `FunctionNode` class or the `@node` decorator when you need to configure execution behavior.
Use explicit configuration when you need to define:
* `rerun_on_resume`: Control if the node should rerun when the workflow resumes (default is `False` for function nodes, meaning they complete with the resuming input).
* `retry_config`: Enable retries on failures.
* `timeout`: Set a maximum execution time.
* `auth_config`: Gate execution with user authentication.
### Using `@node` Decorator
```python
from google.adk.workflow import node
@node(rerun_on_resume=True)
def process_payment(node_input: dict) -> str:
# This node will rerun if the workflow is resumed after a pause
...
```
### Using `FunctionNode` Class
```python
from google.adk.workflow import FunctionNode, RetryConfig
def my_func(node_input: str) -> str:
...
# Wrap explicitly to configure retries
custom_node = FunctionNode(
my_func,
name="payment_step",
retry_config=RetryConfig(max_attempts=3),
)
```
## Advanced applications
### Emitting Message Events for Web UI
Only the `Event.message` (user-facing content) is rendered in the Web UI, while `Event.output` is internal and passed downstream. For terminal nodes or nodes producing user-visible intermediate results, yield both a message event and an output event:
```python
from google.adk.events.event import Event
async def summarize(ctx: Context, node_input: str):
result = f"Summary: {node_input}"
# Rendered in UI (message accepts a raw string and auto-wraps it)
yield Event(message=result)
# Passed to downstream nodes
yield Event(output=result)
```
### State Integration
You can update the shared workflow state in two ways: by mutating `ctx.state` directly, or by yielding/returning an `Event(state=...)`.
#### 1. Mutating `ctx.state` directly (Imperative)
This is the most common way when your function already accesses the context. Mutations are tracked and automatically persisted by the framework when the node finishes execution.
```python
def update_via_context(ctx: Context, node_input: str) -> str:
# State is updated immediately in memory
ctx.state["counter"] = ctx.state.get("counter", 0) + 1
return node_input
```
#### 2. Yielding/Returning `Event(state=...)` (Declarative)
This is useful if you want to declare state changes as events, or if your function does not need the `ctx` object otherwise.
```python
from google.adk.events.event import Event
def update_via_event(node_input: str):
# Returns the state change without needing 'ctx' in the signature
return Event(
output=node_input,
state={"last_processed": node_input}
)
```
#### Key Differences
| Feature | Mutating `ctx.state` | Yielding `Event(state=...)` |
| :--- | :--- | :--- |
| **Visibility** | Changes are visible **immediately** to subsequent lines in the same function. | Changes are only visible **after** the event is yielded and processed by the framework. |
| **Signature** | Requires `ctx: Context` in the function parameters. | Can be used in any function (no `ctx` required). |
| **Style** | Imperative state modification. | Declarative event-driven state update. |
## Limitations
- **Union Type Hints**: If `node_input` is hinted with a `Union` type (e.g. `str | dict`), the framework skips automatic type validation to avoid false positives. You must perform manual `isinstance` checks in the function body if you need to validate the input type.
## Related samples
The following samples demonstrate function node usage:
- [Node Output](../../../../contributing/samples/workflows/node_output/agent.py) - Auto type conversion to Pydantic models.
- [Route](../../../../contributing/samples/workflows/route/agent.py) - Yielding events with routes.
- [State](../../../../contributing/samples/workflows/state/agent.py) - Interacting with workflow state.
- [Auth API Key](../../../../contributing/samples/workflows/auth_api_key/agent.py) - Using authentication.
- [Request Input Advanced](../../../../contributing/samples/workflows/request_input_advanced/agent.py) - Human-in-the-loop with schemas.