Skip to content

Allow customizing reflection guidance in ReflectAndRetryToolPlugin #7564

Closed

Description

@LucasBarzotto

🔴 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:

  1. Constructor arguments reflection_guidance and retry_exceeded_guidance. Each accepts either a static str or a callable (sync or async) that receives a context object and returns the guidance string.
  2. Public override hooks build_reflection_guidance(ctx) and build_retry_exceeded_guidance(ctx). By default they use the constructor arguments, or the current text if none is given. This matches the existing public hook extract_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_msg methods: this works, but it depends on private API and means duplicating the ToolFailureResponse construction.
  • 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

  1. sanketpatil06 commented on Oct 16, 2026

    @sanketpatil06

    Hi @LucasBarzotto, the request to allow customizing reflection guidance in ReflectAndRetryToolPlugin is a valid enhancement.
    Your proposed solution with reflection_guidance and retry_exceeded_guidance constructor arguments and build_reflection_guidance/build_retry_exceeded_guidance override hooks looks good.
    We welcome a pull request for this feature. Please ensure the implementation maintains backward compatibility and includes appropriate tests.

  2. added
    needs review[Status] The PR/issue is awaiting review from the maintainer
    on Oct 16, 2026
  3. reddynitish commented on Oct 18, 2026

    @reddynitish
    Contributor

    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.

  4. added a commit that references this issue on Nov 4, 2026
    03b4fbc
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

needs review[Status] The PR/issue is awaiting review from the maintainertools[Component] This issue is related to tools

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions