Skip to content

Commit 9b8a4aa

Browse files
xuanyang15copybara-github
authored andcommitted
chore: Add an sample agent for the ReflectAndRetryToolPlugin
PiperOrigin-RevId: 817024977
1 parent cac9fae commit 9b8a4aa

File tree

3 files changed

+145
-0
lines changed

3 files changed

+145
-0
lines changed
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# Reflect And Retry Tool Plugin
2+
3+
`ReflectAndRetryToolPlugin` provides self-healing, concurrent-safe error
4+
recovery for tool failures.
5+
6+
**Key Features:**
7+
8+
- **Concurrency Safe:** Uses locking to safely handle parallel tool
9+
executions
10+
- **Configurable Scope:** Tracks failures per-invocation (default) or globally
11+
using the `TrackingScope` enum.
12+
- **Extensible Scoping:** The `_get_scope_key` method can be overridden to
13+
implement custom tracking logic (e.g., per-user or per-session).
14+
- **Granular Tracking:** Failure counts are tracked per-tool within the
15+
defined scope. A success with one tool resets its counter without affecting
16+
others.
17+
- **Custom Error Extraction:** Supports detecting errors in normal tool
18+
responses that don't throw exceptions, by overriding the
19+
`extract_error_from_result` method.
20+
21+
## Samples
22+
23+
Here are some sample agents to demonstrate the usage of the plugin.
24+
25+
### Basic Usage
26+
27+
This is a hello world example to show the basic usage of the plugin. The
28+
`guess_number_tool` is hacked with both Exceptions and error responses. With the
29+
help of the `CustomRetryPlugin`, both above error types can lead to retries.
30+
31+
For example, here is the output from agent:
32+
33+
```
34+
I'll guess the number 50. Let's see how it is!
35+
My guess of 50 was too high! I'll try a smaller number this time. Let's go with 25.
36+
My guess of 25 was still too high! I'm going smaller. How about 10?
37+
Still too high! My guess of 10 was also too large. I'll try 5 this time.
38+
My guess of 5 is "almost valid"! That's good news, it means I'm getting very close. I'll try 4.
39+
My guess of 4 is still "almost valid," just like 5. It seems I'm still hovering around the right answer. Let's try 3!
40+
I guessed the number 3, and it is valid! I found it!
41+
```
42+
43+
You can run the agent with:
44+
45+
```bash
46+
$ adk web contributing/samples/plugin_reflect_tool_retry
47+
```
48+
49+
You can provide the following prompt to see the agent retrying tool calls:
50+
51+
```
52+
Please guess a number! Tell me what number you guess and how is it.
53+
```
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Copyright 2025 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from . import agent
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# Copyright 2025 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from typing import Any
16+
17+
from google.adk.agents import LlmAgent
18+
from google.adk.apps.app import App
19+
from google.adk.plugins import LoggingPlugin
20+
from google.adk.plugins import ReflectAndRetryToolPlugin
21+
22+
APP_NAME = "basic"
23+
USER_ID = "test_user"
24+
25+
26+
def guess_number_tool(query: int) -> dict[str, Any]:
27+
"""A tool that guesses a number.
28+
29+
Args:
30+
query: The number to guess.
31+
32+
Returns:
33+
A dictionary containing the status and result of the tool execution.
34+
"""
35+
target_number = 3
36+
if query == target_number:
37+
return {"status": "success", "result": "Number is valid."}
38+
39+
if abs(query - target_number) <= 2:
40+
return {"status": "error", "error_message": "Number is almost valid."}
41+
42+
if query > target_number:
43+
raise ValueError("Number is too large.")
44+
45+
if query < target_number:
46+
raise ValueError("Number is too small.")
47+
48+
raise ValueError("Number is invalid.")
49+
50+
51+
class CustomRetryPlugin(ReflectAndRetryToolPlugin):
52+
53+
async def extract_error_from_result(
54+
self, *, tool, tool_args, tool_context, result
55+
):
56+
return result if result.get("status") == "error" else None
57+
58+
59+
root_agent = LlmAgent(
60+
name="hello_world",
61+
description="Helpful agent",
62+
instruction="""Use guess_number_tool to guess a number.""",
63+
model="gemini-2.5-flash",
64+
tools=[guess_number_tool],
65+
)
66+
67+
68+
app = App(
69+
name=APP_NAME,
70+
root_agent=root_agent,
71+
plugins=[
72+
CustomRetryPlugin(
73+
max_retries=6, throw_exception_if_retry_exceeded=False
74+
),
75+
LoggingPlugin(),
76+
],
77+
)

0 commit comments

Comments
 (0)