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
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:
@@ -0,0 +1,43 @@
|
||||
# ADK Workflow Sample: Node Retries
|
||||
|
||||
## Overview
|
||||
|
||||
In real-world applications, interacting with external APIs, databases, or third-party services can occasionally result in transient failures (e.g., temporary network outages, rate limits, or bad gateways).
|
||||
|
||||
The ADK framework allows you to easily handle these scenarios by wrapping the unreliable logic in a `@node` decorator configured with `RetryConfig`. If the node raises one of the expected exceptions, the workflow engine automatically pauses, waits for a backoff delay, and reschedules the node for another attempt.
|
||||
|
||||
When a node raises an exception, the framework automatically emits an error event (with `error_code` and `error_message`) so the error is visible in the event stream. If the node has retry configured, it will be retried after the backoff delay.
|
||||
|
||||
This sample demonstrates a `get_weather` node that intentionally fails randomly (70% chance) by raising an `HTTPError` representing a 500 Internal Server error. The framework gracefully recovers and eventually succeeds, passing the result to `report_weather`.
|
||||
|
||||
## Graph
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
START --> get_weather[get_weather <br/>Retries on HTTPError]
|
||||
get_weather --> report_weather
|
||||
```
|
||||
|
||||
## How To
|
||||
|
||||
1. **Import `RetryConfig`**: Ensure you import the configuration class to set your retry parameters.
|
||||
|
||||
```python
|
||||
from google.adk.workflow import RetryConfig
|
||||
```
|
||||
|
||||
1. **Configure the Decorator**: Apply the `@node` decorator to your Python function and specify the `retry_config` parameter with your desired logic (e.g., `max_attempts`, `initial_delay`).
|
||||
|
||||
```python
|
||||
@node(retry_config=RetryConfig(max_attempts=5, initial_delay=1))
|
||||
def get_weather(ctx: Context) -> str:
|
||||
# ... flaky logic here ...
|
||||
```
|
||||
|
||||
When an exception like `HTTPError` occurs, the ADK framework catches it, emits an error event, and processes the backoff delay automatically. As long as `max_attempts` hasn't been exceeded, the node executes again.
|
||||
|
||||
1. **Track Retries (Optional)**: If you need to know which attempt the node is currently running, you can access `ctx.attempt_count` from the `Context`.
|
||||
|
||||
```python
|
||||
yield Event(message=f"Getting weather... attempt {ctx.attempt_count}")
|
||||
```
|
||||
@@ -0,0 +1,49 @@
|
||||
# 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.
|
||||
|
||||
import random
|
||||
from urllib.error import HTTPError
|
||||
|
||||
from google.adk import Context
|
||||
from google.adk import Event
|
||||
from google.adk import Workflow
|
||||
from google.adk.workflow import node
|
||||
from google.adk.workflow import RetryConfig
|
||||
|
||||
|
||||
@node(retry_config=RetryConfig(max_attempts=5, initial_delay=1))
|
||||
def get_weather(ctx: Context) -> str:
|
||||
"""A mock task that fails randomly."""
|
||||
|
||||
yield Event(message=f"Getting weather... attempt {ctx.attempt_count}")
|
||||
if random.random() < 0.7: # 70% chance of failure
|
||||
raise HTTPError(
|
||||
url="http://mock-api.example.com",
|
||||
code=500,
|
||||
msg="Internal Server Error",
|
||||
hdrs={},
|
||||
fp=None,
|
||||
)
|
||||
|
||||
yield "sunny"
|
||||
|
||||
|
||||
def report_weather(node_input: str):
|
||||
yield Event(message=f"The weather is {node_input}")
|
||||
|
||||
|
||||
root_agent = Workflow(
|
||||
name="root_agent",
|
||||
edges=[("START", get_weather, report_weather)],
|
||||
)
|
||||
@@ -0,0 +1,131 @@
|
||||
{
|
||||
"appName": "retry",
|
||||
"events": [
|
||||
{
|
||||
"author": "user",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"text": "go"
|
||||
}
|
||||
],
|
||||
"role": "user"
|
||||
},
|
||||
"id": "e-1",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"author": "root_agent",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"text": "Getting weather... attempt 1"
|
||||
}
|
||||
],
|
||||
"role": "user"
|
||||
},
|
||||
"id": "e-2",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": "root_agent@1/get_weather@1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"author": "root_agent",
|
||||
"errorCode": "HTTPError",
|
||||
"errorMessage": "HTTP Error 500: Internal Server Error",
|
||||
"id": "e-3",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": "root_agent@1/get_weather@1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"author": "root_agent",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"text": "Getting weather... attempt 2"
|
||||
}
|
||||
],
|
||||
"role": "user"
|
||||
},
|
||||
"id": "e-4",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": "root_agent@1/get_weather@1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"author": "root_agent",
|
||||
"errorCode": "HTTPError",
|
||||
"errorMessage": "HTTP Error 500: Internal Server Error",
|
||||
"id": "e-5",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": "root_agent@1/get_weather@1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"author": "root_agent",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"text": "Getting weather... attempt 3"
|
||||
}
|
||||
],
|
||||
"role": "user"
|
||||
},
|
||||
"id": "e-6",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": "root_agent@1/get_weather@1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"author": "root_agent",
|
||||
"id": "e-7",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"outputFor": [
|
||||
"root_agent@1/get_weather@1"
|
||||
],
|
||||
"path": "root_agent@1/get_weather@1"
|
||||
},
|
||||
"output": "sunny"
|
||||
},
|
||||
{
|
||||
"author": "root_agent",
|
||||
"content": {
|
||||
"parts": [
|
||||
{
|
||||
"text": "The weather is sunny"
|
||||
}
|
||||
],
|
||||
"role": "user"
|
||||
},
|
||||
"id": "e-8",
|
||||
"invocationId": "i-1",
|
||||
"nodeInfo": {
|
||||
"path": "root_agent@1/report_weather@1"
|
||||
}
|
||||
}
|
||||
],
|
||||
"id": "c0e0c167-1e63-4e18-84b6-ad64ba59376f",
|
||||
"mocks": {
|
||||
"random.random": [
|
||||
0.5,
|
||||
0.5,
|
||||
0.8
|
||||
]
|
||||
},
|
||||
"state": {
|
||||
"__session_metadata__": {
|
||||
"displayName": "go"
|
||||
}
|
||||
},
|
||||
"userId": "user"
|
||||
}
|
||||
Reference in New Issue
Block a user