
    i5                        S r SSKJr  SSKrSSKrSSKrSSKJrJr  SSK	J
r
  SSKJr  \(       a  SSKJrJr  SSKJr  SS	KJr  SS
KJr   " S S\5      rg)z!Tool retry middleware for agents.    )annotationsN)TYPE_CHECKINGLiteral)ToolMessage)AgentMiddleware)	AwaitableCallable)Command)ToolCallRequest)BaseToolc            	         ^  \ rS rSrSrSS\4SSSSS	S
.                 SU 4S jjjrSS jrSS jrSS jr	SS jr
          SS jr      SS jr      SS jrSrU =r$ )ToolRetryMiddleware   a  Middleware that automatically retries failed tool calls with configurable backoff.

Supports retrying on specific exceptions and exponential backoff.

Examples:
    Basic usage with default settings (2 retries, exponential backoff):
    ```python
    from langchain.agents import create_agent
    from langchain.agents.middleware import ToolRetryMiddleware

    agent = create_agent(model, tools=[search_tool], middleware=[ToolRetryMiddleware()])
    ```

    Retry specific exceptions only:
    ```python
    from requests.exceptions import RequestException, Timeout

    retry = ToolRetryMiddleware(
        max_retries=4,
        retry_on=(RequestException, Timeout),
        backoff_factor=1.5,
    )
    ```

    Custom exception filtering:
    ```python
    from requests.exceptions import HTTPError


    def should_retry(exc: Exception) -> bool:
        # Only retry on 5xx errors
        if isinstance(exc, HTTPError):
            return 500 <= exc.status_code < 600
        return False


    retry = ToolRetryMiddleware(
        max_retries=3,
        retry_on=should_retry,
    )
    ```

    Apply to specific tools with custom error handling:
    ```python
    def format_error(exc: Exception) -> str:
        return "Database temporarily unavailable. Please try again later."


    retry = ToolRetryMiddleware(
        max_retries=4,
        tools=["search_database"],
        on_failure=format_error,
    )
    ```

    Apply to specific tools using BaseTool instances:
    ```python
    from langchain_core.tools import tool


    @tool
    def search_database(query: str) -> str:
        '''Search the database.'''
        return results


    retry = ToolRetryMiddleware(
        max_retries=4,
        tools=[search_database],  # Pass BaseTool instance
    )
    ```

    Constant backoff (no exponential growth):
    ```python
    retry = ToolRetryMiddleware(
        max_retries=5,
        backoff_factor=0.0,  # No exponential growth
        initial_delay=2.0,  # Always wait 2 seconds
    )
    ```

    Raise exception on failure:
    ```python
    retry = ToolRetryMiddleware(
        max_retries=2,
        on_failure="raise",  # Re-raise exception instead of returning message
    )
    ```
   Nreturn_messageg       @g      ?g      N@T)max_retriestoolsretry_on
on_failurebackoff_factorinitial_delay	max_delayjitterc                 > [         TU ]  5         US:  a  Sn	[        U	5      eUS:  a  Sn	[        U	5      eUS:  a  Sn	[        U	5      eUS:  a  Sn	[        U	5      eXl        U   Ub:  U V
s/ s H&  n
[	        U
[
        5      (       d  U
R                  OU
PM(     sn
U l        OSU l        / U l        X0l	        X@l
        XPl        X`l        Xpl        Xl        gs  sn
f )u8  Initialize ToolRetryMiddleware.

Args:
    max_retries: Maximum number of retry attempts after the initial call.
        Default is 2 retries (3 total attempts). Must be >= 0.
    tools: Optional list of tools or tool names to apply retry logic to.
        Can be a list of `BaseTool` instances or tool name strings.
        If `None`, applies to all tools. Default is `None`.
    retry_on: Either a tuple of exception types to retry on, or a callable
        that takes an exception and returns `True` if it should be retried.
        Default is to retry on all exceptions.
    on_failure: Behavior when all retries are exhausted. Options:
        - `"return_message"` (default): Return a ToolMessage with error details,
          allowing the LLM to handle the failure and potentially recover.
        - `"raise"`: Re-raise the exception, stopping agent execution.
        - Custom callable: Function that takes the exception and returns a string
          for the ToolMessage content, allowing custom error formatting.
    backoff_factor: Multiplier for exponential backoff. Each retry waits
        `initial_delay * (backoff_factor ** retry_number)` seconds.
        Set to 0.0 for constant delay. Default is 2.0.
    initial_delay: Initial delay in seconds before first retry. Default is 1.0.
    max_delay: Maximum delay in seconds between retries. Caps exponential
        backoff growth. Default is 60.0.
    jitter: Whether to add random jitter (±25%) to delay to avoid thundering herd.
        Default is `True`.

Raises:
    ValueError: If max_retries < 0 or delays are negative.
r   zmax_retries must be >= 0zinitial_delay must be >= 0zmax_delay must be >= 0zbackoff_factor must be >= 0N)super__init__
ValueErrorr   
isinstancestrname_tool_filterr   r   r   r   r   r   r   )selfr   r   r   r   r   r   r   r   msgtool	__class__s              `/home/james-whalen/.local/lib/python3.13/site-packages/langchain/agents/middleware/tool_retry.pyr   ToolRetryMiddleware.__init__r   s    V 	 ?,CS/!1.CS/!q=*CS/!A/CS/!& 	^c d^cVZ*T32G2GT!Q^c dD $D
 $,*" !es   +-Cc                :    U R                   c  gXR                   ;   $ )zCheck if retry logic should apply to this tool.

Args:
    tool_name: Name of the tool being called.

Returns:
    `True` if retry logic should apply, `False` otherwise.
T)r!   )r"   	tool_names     r&   _should_retry_tool&ToolRetryMiddleware._should_retry_tool   s"     $----    c                    [        U R                  5      (       a  U R                  U5      $ [        XR                  5      $ )zCheck if the exception should trigger a retry.

Args:
    exc: The exception that occurred.

Returns:
    `True` if the exception should be retried, `False` otherwise.
)callabler   r   )r"   excs     r&   _should_retry_exception+ToolRetryMiddleware._should_retry_exception   s1     DMM""==%%#}}--r,   c                (   U R                   S:X  a  U R                  nOU R                  U R                   U-  -  n[        X R                  5      nU R                  (       a2  US:  a,  US-  nU[
        R                  " U* U5      -   n[        SU5      nU$ )zCalculate delay for the given retry attempt.

Args:
    retry_number: The retry attempt number (0-indexed).

Returns:
    Delay in seconds before next retry.
g        r   g      ?)r   r   minr   r   randomuniformmax)r"   retry_numberdelayjitter_amounts       r&   _calculate_delay$ToolRetryMiddleware._calculate_delay   s     #%&&E&&$*=*=|*KLE E>>*;;519!DLMFNNM>=IIE5MEr,   c                \    [        U5      R                  nUS:X  a  SOSnSU SU SU SU 3$ )zFormat the failure message when retries are exhausted.

Args:
    tool_name: Name of the tool that failed.
    exc: The exception that caused the failure.
    attempts_made: Number of attempts actually made.

Returns:
    Formatted error message string.
   attemptattemptszTool 'z' failed after  z with )type__name__)r"   r)   r/   attempts_madeexc_typeattempt_words         r&   _format_failure_message+ToolRetryMiddleware._format_failure_message   sB     9%%$1Q$6yJ	{/-,vV^U_``r,   c                    U R                   S:X  a  Ue[        U R                   5      (       a  U R                  U5      nOU R                  XU5      n[        UUUSS9$ )av  Handle failure when all retries are exhausted.

Args:
    tool_name: Name of the tool that failed.
    tool_call_id: ID of the tool call (may be None).
    exc: The exception that caused the failure.
    attempts_made: Number of attempts actually made.

Returns:
    ToolMessage with error details.

Raises:
    Exception: If on_failure is "raise", re-raises the exception.
raiseerror)contenttool_call_idr    status)r   r.   rF   r   )r"   r)   rL   r/   rC   rK   s         r&   _handle_failure#ToolRetryMiddleware._handle_failure   s]    " ??g%IDOO$$ooc*G229=QG%	
 	
r,   c                   UR                   (       a  UR                   R                  OUR                  S   nU R                  U5      (       d  U" U5      $ UR                  S   n[	        U R
                  S-   5       H  n U" U5      s  $    Sn	[        U	5      e! [         a  nUS-   nU R                  U5      (       d  U R                  X4Xg5      s SnAs  $ XPR
                  :  a9  U R                  U5      nUS:  a  [        R                  " U5         SnAM   SnAM  U R                  X4Xg5      s SnAs  $ SnAff = f)a  Intercept tool execution and retry on failure.

Args:
    request: Tool call request with call dict, BaseTool, state, and runtime.
    handler: Callable to execute the tool (can be called multiple times).

Returns:
    ToolMessage or Command (the final result).
r    idr=   Nr   2Unexpected: retry loop completed without returning)r$   r    	tool_callr*   ranger   	Exceptionr0   rN   r:   timesleepRuntimeError
r"   requesthandlerr)   rL   r>   r/   rC   r8   r#   s
             r&   wrap_tool_call"ToolRetryMiddleware.wrap_tool_call   s%    *1GLL%%7;L;LV;T	 &&y117##((. T--12G]w'' 3. C3+  ] '! 33C88//	\\ --- 11':Eqy

5)) !
  //	\\#]s0    B
D=#,D8D=<D8D80D=8D=c                  #    UR                   (       a  UR                   R                  OUR                  S   nU R                  U5      (       d  U" U5      I Sh  vN $ UR                  S   n[	        U R
                  S-   5       H  n U" U5      I Sh  vN s  $    Sn	[        U	5      e NP N! [         a  nUS-   nU R                  U5      (       d  U R                  X4Xg5      s SnAs  $ XPR
                  :  aB  U R                  U5      nUS:  a%  [        R                  " U5      I Sh  vN     SnAM   SnAM  U R                  X4Xg5      s SnAs  $ SnAff = f7f)a  Intercept and control async tool execution with retry logic.

Args:
    request: Tool call request with call dict, BaseTool, state, and runtime.
    handler: Async callable to execute the tool and returns ToolMessage or Command.

Returns:
    ToolMessage or Command (the final result).
r    NrQ   r=   r   rR   )r$   r    rS   r*   rT   r   rU   r0   rN   r:   asynciorW   rX   rY   s
             r&   awrap_tool_call#ToolRetryMiddleware.awrap_tool_callQ  s8     *1GLL%%7;L;LV;T	 &&y11 )))((. T--12G]$W--- 3. C3; * . ] '! 33C88//	\\ --- 11':Eqy%mmE222 !
  //	\\#]s~   AEB+.E
B/B-B/E-B//
E9,E%E&E-?E,D/-E2E>EEEEE)	r!   r   r   r   r   r   r   r   r   )r   intr   zlist[BaseTool | str] | Noner   z9tuple[type[Exception], ...] | Callable[[Exception], bool]r   z?Literal['raise', 'return_message'] | Callable[[Exception], str]r   floatr   rc   r   rc   r   boolreturnNone)r)   r   re   rd   )r/   rU   re   rd   )r7   rb   re   rc   )r)   r   r/   rU   rC   rb   re   r   )
r)   r   rL   z
str | Noner/   rU   rC   rb   re   r   )rZ   r   r[   z2Callable[[ToolCallRequest], ToolMessage | Command]re   ToolMessage | Command)rZ   r   r[   z=Callable[[ToolCallRequest], Awaitable[ToolMessage | Command]]re   rg   )rB   
__module____qualname____firstlineno____doc__rU   r   r*   r0   r:   rF   rN   r\   r`   __static_attributes____classcell__)r%   s   @r&   r   r      s'   Xz -1OXl  #"J J +	J
 LJ LJ J J J J 
J JX..2a

,6
=F
WZ
	
@/  /  D/  
	/ b/  /  O/  
	/  / r,   r   )rk   
__future__r   r_   r4   rV   typingr   r   langchain_core.messagesr   !langchain.agents.middleware.typesr   collections.abcr   r	   langgraph.typesr
   r   langchain.toolsr   r    r,   r&   <module>rv      s:    ' "    ) / =3'A(i / i r,   