Repository navigation
Description
🔴 Required Information
Is your feature request related to a specific problem?
ReflectAndRetryToolPlugin hardcodes the guidance it sends back to the LLM after a tool fails. This covers both the per-retry reflection message and the "retry limit exceeded" message. There is no supported way to change this text.
The default guidance is generic (a fixed 5-point checklist covering invalid parameters, preconditions, alternative approaches, simplifying the task and wrong function names). It doesn't fit every project.
The only way to change it today is to subclass and override the private methods _create_tool_reflection_response and _get_tool_retry_exceed_msg. These are not public API and may change without notice. Each one also builds the message text and the ToolFailureResponse envelope together, so changing the wording means copying the response-building code.
Describe the Solution You'd Like
Add a supported way to provide custom guidance text without touching the response envelope:
- Constructor arguments
reflection_guidanceandretry_exceeded_guidance. Each accepts either a staticstror a callable (sync or async) that receives a context object and returns the guidance string. - Public override hooks
build_reflection_guidance(ctx)andbuild_retry_exceeded_guidance(ctx). By default they use the constructor arguments, or the current text if none is given. This matches the existing public hookextract_error_from_result.
The ToolFailureResponse structure (response_type, error_type, error_details, retry_count) should stay fixed and owned by the plugin. after_tool_callback relies on response_type to recognize its own messages, so only the reflection_guidance text would be customizable.
The default behavior stays exactly the same, so the change is fully backward compatible.
Impact on your work
The default guidance can work against the agent in some cases. The retry-exceeded message ("Do not attempt to use the {tool} tool again for this task") stays in the session history, so the model may stop calling that tool in later turns, even after the per-invocation retry counter has reset. The per-retry message can also encourage the model to retry errors that are not retryable, such as permission or not-found errors. The only workaround today is a subclass that overrides private methods, which is fragile across ADK upgrades.
Willingness to contribute
No
🟡 Recommended Information
Describe Alternatives You've Considered
- Overriding the private
_create_tool_reflection_response/_get_tool_retry_exceed_msgmethods: this works, but it depends on private API and means duplicating theToolFailureResponseconstruction. - A
str.format-style template with placeholders such as{tool_name}and{error_details}: simple, but user templates often contain literal braces (JSON examples, code blocks), which break.format(). A callable avoids this and also allows guidance that depends on the tool, the error type or the session state. - Wrapping the plugin and rewriting its output in another callback: more complex, and it depends on the output format.
Proposed API / Implementation
from dataclasses import dataclass
from typing import Any, Awaitable, Callable, Union
@dataclass(frozen=True)
class ToolReflectionContext:
tool_name: str
tool_args: dict[str, Any]
error: Any
error_details: str # pre-formatted, e.g. "ValueError: ..."
retry_count: int
max_retries: int
tool_context: ToolContext
GuidanceProvider = Union[
str,
Callable[[ToolReflectionContext], Union[str, Awaitable[str]]],
]
class ReflectAndRetryToolPlugin(BasePlugin):
def __init__(
self,
name: str = "reflect_retry_tool_plugin",
max_retries: int = 3,
throw_exception_if_retry_exceeded: bool = True,
tracking_scope: TrackingScope = TrackingScope.INVOCATION,
reflection_guidance: GuidanceProvider | None = None,
retry_exceeded_guidance: GuidanceProvider | None = None,
): ...
async def build_reflection_guidance(self, ctx: ToolReflectionContext) -> str:
"""Override to customize; defaults to constructor arg or built-in text."""
async def build_retry_exceeded_guidance(self, ctx: ToolReflectionContext) -> str:
"""Override to customize; defaults to constructor arg or built-in text."""
# Usage:
def my_guidance(ctx: ToolReflectionContext) -> str:
return (
f"Tool `{ctx.tool_name}` failed ({ctx.error_details}). "
f"Attempt {ctx.retry_count}/{ctx.max_retries}. "
"Check the arguments against the schema and try again."
)
plugin = ReflectAndRetryToolPlugin(max_retries=3, reflection_guidance=my_guidance)Implementation note: the two methods that build the guidance are synchronous today. Supporting async providers means awaiting them from _handle_tool_error, which is already async.
Additional Context
This follows the existing ADK pattern of accepting Union[str, InstructionProvider], as in GlobalInstructionPlugin, and the public override hook extract_error_from_result on this same plugin. ReflectAndRetryModelPlugin has the same limitation and could get the same treatment in a follow-up.
Activity
sanketpatil06 commentedon Oct 16, 2026
Hi @LucasBarzotto, the request to allow customizing reflection guidance in
ReflectAndRetryToolPluginis a valid enhancement.Your proposed solution with
reflection_guidanceandretry_exceeded_guidanceconstructor arguments andbuild_reflection_guidance/build_retry_exceeded_guidanceoverride hooks looks good.We welcome a pull request for this feature. Please ensure the implementation maintains backward compatibility and includes appropriate tests.
reddynitish commentedon Oct 18, 2026
I would like to take this on following the maintainer-approved API above. I checked the timeline and open PRs and found no public competing implementation. I will preserve the existing default guidance and ToolFailureResponse fields, add the two guidance providers and public override hooks, and cover string, synchronous/asynchronous callable and subclass customization with backward-compatibility tests. I am starting locally with AI assistance (Codex and Gemini CLI). Please flag any existing private work so we can coordinate.
feat: let applications customize tool retry guidance