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,75 @@
# Reflect And Retry Tool Plugin
`ReflectAndRetryToolPlugin` provides self-healing, concurrent-safe error
recovery for tool failures.
**Key Features:**
- **Concurrency Safe:** Uses locking to safely handle parallel tool
executions
- **Configurable Scope:** Tracks failures per-invocation (default) or globally
using the `TrackingScope` enum.
- **Extensible Scoping:** The `_get_scope_key` method can be overridden to
implement custom tracking logic (e.g., per-user or per-session).
- **Granular Tracking:** Failure counts are tracked per-tool within the
defined scope. A success with one tool resets its counter without affecting
others.
- **Custom Error Extraction:** Supports detecting errors in normal tool
responses that don't throw exceptions, by overriding the
`extract_error_from_result` method.
## Samples
Here are some sample agents to demonstrate the usage of the plugin.
### Basic Usage
This is a hello world example to show the basic usage of the plugin. The
`guess_number_tool` is hacked with both Exceptions and error responses. With the
help of the `CustomRetryPlugin`, both above error types can lead to retries.
For example, here is the output from agent:
```
I'll guess the number 50. Let's see how it is!
My guess of 50 was too high! I'll try a smaller number this time. Let's go with 25.
My guess of 25 was still too high! I'm going smaller. How about 10?
Still too high! My guess of 10 was also too large. I'll try 5 this time.
My guess of 5 is "almost valid"! That's good news, it means I'm getting very close. I'll try 4.
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!
I guessed the number 3, and it is valid! I found it!
```
You can run the agent with:
```bash
$ adk web contributing/samples/plugin_reflect_tool_retry
```
Select "basic" and provide the following prompt to see the agent retrying tool
calls:
```
Please guess a number! Tell me what number you guess and how is it.
```
### Hallucinating tool calls
The "hallucinating_func_name" agent is an example to show the plugin can retry
hallucinating tool calls.
For example, we used the `after_model_callback` to hack a tool call with the
wrong name then the agent can retry calling with the right tool name.
You can run the agent with:
```bash
$ adk web contributing/samples/plugin_reflect_tool_retry
```
Select "hallucinating_func_name" and provide the following prompt to see the
agent retrying tool calls:
```
Roll a 6 sided die
```
@@ -0,0 +1,15 @@
# 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 . import agent
@@ -0,0 +1,83 @@
# 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 typing import Any
from google.adk.agents import LlmAgent
from google.adk.apps.app import App
from google.adk.plugins import LoggingPlugin
from google.adk.plugins import ReflectAndRetryToolPlugin
APP_NAME = "basic"
USER_ID = "test_user"
def guess_number_tool(query: int) -> dict[str, Any]:
"""A tool that guesses a number.
Args:
query: The number to guess.
Returns:
A dictionary containing the status and result of the tool execution.
"""
target_number = 3
if query == target_number:
return {"status": "success", "result": "Number is valid."}
if abs(query - target_number) <= 2:
return {"status": "error", "error_message": "Number is almost valid."}
if query > target_number:
raise ValueError("Number is too large.")
if query < target_number:
raise ValueError("Number is too small.")
raise ValueError("Number is invalid.")
class CustomRetryPlugin(ReflectAndRetryToolPlugin):
async def extract_error_from_result(
self, *, tool, tool_args, tool_context, result
):
return result if result.get("status") == "error" else None
# Sample query: "guess a number between 1 and 50"
root_agent = LlmAgent(
name="hello_world",
description="Helpful agent",
instruction="""Your goal is to guess a secret positive integer by using the
`guess_number_tool`.
The tool will provide feedback on each guess.
Your objective is to keep guessing until guess_number_tool returns
'status: success'.
Start by guessing 50, and use the tool's feedback to adjust your guesses
and find the target number.""",
tools=[guess_number_tool],
)
app = App(
name=APP_NAME,
root_agent=root_agent,
plugins=[
CustomRetryPlugin(
max_retries=20, throw_exception_if_retry_exceeded=False
),
LoggingPlugin(),
],
)
@@ -0,0 +1,15 @@
# 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 . import agent
@@ -0,0 +1,82 @@
# 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 google.adk.agents import LlmAgent
from google.adk.agents.callback_context import CallbackContext
from google.adk.apps.app import App
from google.adk.models.llm_response import LlmResponse
from google.adk.plugins import ReflectAndRetryToolPlugin
from google.adk.tools.tool_context import ToolContext
APP_NAME = "hallucinating_func_name"
USER_ID = "test_user"
hallucinated = False # Whether the tool name is hallucinated
def roll_die(sides: int, tool_context: ToolContext) -> int:
"""Roll a die and return the rolled result.
Args:
sides: The integer number of sides the die has.
Returns:
An integer of the result of rolling the die.
"""
result = random.randint(1, sides)
if not "rolls" in tool_context.state:
tool_context.state["rolls"] = []
tool_context.state["rolls"] = tool_context.state["rolls"] + [result]
return result
def after_model_callback(
callback_context: CallbackContext, llm_response: LlmResponse
):
"""After model callback to produce one hallucinating tool call."""
global hallucinated
if hallucinated:
return None
if (
llm_response.content
and llm_response.content.parts
and llm_response.content.parts[0].function_call
and llm_response.content.parts[0].function_call.name == "roll_die"
):
llm_response.content.parts[0].function_call.name = "roll_die_wrong_name"
hallucinated = True
return None
root_agent = LlmAgent(
name="hello_world",
description="Helpful agent",
instruction="""Use guess_number_tool to guess a number.""",
tools=[roll_die],
after_model_callback=after_model_callback,
)
app = App(
name=APP_NAME,
root_agent=root_agent,
plugins=[
ReflectAndRetryToolPlugin(max_retries=3),
],
)