
    k
i=                        U d Z ddlZddlZddlmZmZmZmZmZm	Z	 ddl
mZmZmZ ddlmZ ddlmZ ddlmZ dd	lmZ dd
lmZ  ej        e          Z eh d          Zee         ed<   dZ eedf         ed<    G d de          Z!dS )a^  Tool result cache middleware.

This module provides a middleware that caches tool call results
using exact-match key-value lookup in Redis. Tool caching is
deterministic: same tool + same args = same result. This uses
direct Redis GET/SET instead of vector similarity.

Compatible with LangChain's AgentMiddleware protocol for use with create_agent.
    N)Any	AwaitableCallableDictTupleUnion)ModelCallResultModelRequestModelResponse)ToolMessage)ToolCallRequest)Command   )AsyncRedisMiddleware)ToolCacheConfig>   nowdatetoday	timestampcurrent_datecurrent_timecurrent_timestampDEFAULT_VOLATILE_ARG_NAMES)	send_delete_create_update_remove_write_post_put_patch_.DEFAULT_SIDE_EFFECT_PREFIXESc            	           e Zd ZU dZeed<   deddf fdZddZdede	fd	Z
d
eeef         de	fdZdede	fdZd
eeef         deeef         fdZededeeef         fd            Zdede	fdZdedefdZdedefdZdedededefdZdedeegeeeef                  f         deeef         fdZdedeegee         f         defdZ  xZ!S )ToolResultCacheMiddlewareaP  Middleware that caches tool call results using exact-match lookup.

    Uses direct Redis GET/SET for deterministic tool result caching.
    When a tool is called with the same arguments as a previous call,
    the cached result is returned without executing the tool.

    Tool caching is especially useful for expensive operations like:
    - API calls to external services
    - Database queries
    - Web searches
    - Complex calculations

    Example:
        ```python
        from langgraph.middleware.redis import (
            ToolResultCacheMiddleware,
            ToolCacheConfig,
        )

        config = ToolCacheConfig(
            redis_url="redis://localhost:6379",
            cacheable_tools=["search", "calculate"],
            excluded_tools=["random_number"],
            ttl_seconds=3600,
        )

        middleware = ToolResultCacheMiddleware(config)

        async def execute_tool(request):
            # Your tool execution here
            return result

        # Use middleware
        result = await middleware.awrap_tool_call(request, execute_tool)
        ```
    _configconfigreturnNc                 X    t                                          |           || _        dS )zsInitialize the tool cache middleware.

        Args:
            config: Configuration for the tool cache.
        N)super__init__r&   )selfr'   	__class__s     C:\Users\Dell Inspiron 16\Desktop\tws\AgrotaPowerBi\back-agrota-powerbi\mcp-client-agrota\venv\Lib\site-packages\langgraph/middleware/redis/tool_cache.pyr+   z"ToolResultCacheMiddleware.__init__^   s(     	       c                 
   K   dS )u   Set up the tool cache.

        No additional setup needed — the tool cache uses the async Redis
        client from the base class directly for GET/SET operations.
        N )r,   s    r.   _setup_asyncz&ToolResultCacheMiddleware._setup_asyncg   s       	r/   	tool_namec                 R    | j         j        || j         j        v S || j         j        vS )zCheck if a tool's results should be cached based on config.

        Args:
            tool_name: The name of the tool.

        Returns:
            True if the tool's results should be cached, False otherwise.
        )r&   cacheable_toolsexcluded_tools)r,   r3   s     r.   _is_tool_cacheable_by_configz6ToolResultCacheMiddleware._is_tool_cacheable_by_configo   s1     <'3 <<<  ;;;r/   argsc                 b   | j         j        }|sdS |                                D ]\  }}||v r dS t          |t                    r|                     |          r dS :t          |t          t          f          r3|D ]0}t          |t                    r|                     |          r  dS 1dS )aW  Check if args contain volatile argument names at any nesting depth.

        Recurses into nested dicts and into dicts inside lists/tuples.

        Args:
            args: The tool arguments dict.

        Returns:
            True if any key in args (recursively) matches a configured
            volatile arg name, False otherwise.
        FT)r&   volatile_arg_namesitems
isinstancedict_has_volatile_argslisttuple)r,   r8   volatile_nameskeyvalueitems         r.   r>   z,ToolResultCacheMiddleware._has_volatile_args   s     8 	5**,, 		$ 		$JCn$$tt%&& $**511  44 ED%=11 $! $ $D!$-- $$2I2I$2O2O $#tttur/   c                 L    | j         j        }|sdS |                    |          S )zCheck if tool name starts with a configured side-effect prefix.

        Args:
            tool_name: The name of the tool.

        Returns:
            True if the tool name matches a side-effect prefix.
        F)r&   side_effect_prefixes
startswith)r,   r3   prefixess      r.   _has_side_effect_prefixz1ToolResultCacheMiddleware._has_side_effect_prefix   s/     <4 	5##H---r/   c                     t          |t                    si S | j        j        s|S fd|                                D             S )zReturn a copy of args with ignored names removed (top-level only).

        Args:
            args: The tool arguments dict.

        Returns:
            A new dict without the ignored keys, or the original if nothing
            to strip.
        c                 $    i | ]\  }}|v	||S r1   r1   ).0kvignoreds      r.   
<dictcomp>zAToolResultCacheMiddleware._strip_ignored_args.<locals>.<dictcomp>   s)    BBBA'1A1A11A1A1Ar/   )r<   r=   r&   ignored_arg_namesr;   )r,   r8   rO   s     @r.   _strip_ignored_argsz-ToolResultCacheMiddleware._strip_ignored_args   sU     $%% 	I,0 	KBBBBBBBBr/   requestc                     t          | t                    r|                     di           S t          | dd          }t          |t                    r|                    di           S i S )zExtract args dict from a request (dict or ToolCallRequest).

        Args:
            request: The tool call request.

        Returns:
            The arguments dict.
        r8   	tool_callN)r<   r=   getgetattr)rS   rU   s     r.   _extract_argsz'ToolResultCacheMiddleware._extract_args   sf     gt$$ 	+;;vr***G[$77	i&& 	-==,,,	r/   c                    t          |t                    r|                    dd          }d}nOt          |dd          }t          |t                    r|                    dd          nd}t          |dd          }i }|,t          |dd          pi }d|v rt	          |d                   S |                    d	          d
u rdS |                    d          d
u rdS |                    d          d
u r|                    d          d
u rd
S |                     |          rdS |                     |          }|                     |          rdS |                     |          S )u  Check if a tool's results should be cached.

        Uses a priority chain inspired by SQL function volatility and
        MCP ToolAnnotations:

        1. ``metadata["cacheable"]`` — explicit override (highest priority)
        2. ``metadata["destructive"]`` — never cache destructive tools
        3. ``metadata["volatile"]`` — never cache volatile tools
        4. ``metadata["read_only"] and metadata["idempotent"]`` — cache
        5. Side-effect prefix match — never cache
        6. Volatile arg name in call args — never cache
        7. Config whitelist / blacklist — existing fallback

        Args:
            request: The tool call request containing the tool object.

        Returns:
            True if the tool's results should be cached, False otherwise.
        r3    NrU   nametoolmetadata	cacheabledestructiveTFvolatile	read_only
idempotent)	r<   r=   rV   rW   boolrI   rX   r>   r7   )r,   rS   r3   r\   rU   r]   r8   s          r.   _is_tool_cacheablez,ToolResultCacheMiddleware._is_tool_cacheable   s   * gt$$ 	2K44IDDd;;I5?	45P5PX	fb111VXI7FD11D $&tZ66<"Hh&&H[1222 <<&&$..5 <<
##t++5 <<$$,,l1K1Kt1S1S4 ''	22 	5 !!'**""4(( 	5 00;;;r/   c                    t          |t                    r-|                    dd          }|                    di           }nWt          |dd          }t          |t                    r-|                    dd          }|                    di           }nd}i }|                     |          }t          j        |d          }| j        j         d	| d	| S )
a  Build a deterministic cache key from the tool request.

        Creates an exact-match Redis key from the tool name and sorted
        JSON arguments. This ensures that identical tool calls always
        produce the same key, and different calls always produce different keys.

        Args:
            request: The tool call request (dict or LangChain type).

        Returns:
            A deterministic string key for Redis GET/SET.
        r3   rZ   r8   rU   Nr[   T)	sort_keys:)	r<   r=   rV   rW   rR   jsondumpsr&   r[   )r,   rS   r3   r8   rU   effective_argsargs_strs          r.   _build_cache_keyz*ToolResultCacheMiddleware._build_cache_key  s     gt$$ 
	K44I;;vr**DDd;;I)T** %MM&"55	 }}VR00	 11$77 :n===,#<<i<<(<<<r/   rC   c                 >   t          |t          t          f          r<t          |dd          }t	          |          rt          j         |                      S 	 t          j        |          S # t          $ r$ t          j        t          |                    cY S w xY w)aJ  Serialize a tool result to a JSON string for caching.

        Supports LangChain ToolMessage/Command objects by converting
        them to JSON-compatible structures before encoding.

        Args:
            value: The tool result to serialize.

        Returns:
            A JSON string representation of the result.
        to_jsonN)	r<   LangChainToolMessager   rW   callablerh   ri   	TypeErrorstr)r,   rC   rn   s      r.   _serialize_tool_resultz0ToolResultCacheMiddleware._serialize_tool_result'  s     e2G<== 	-eY55G   -z'')),,,	*:e$$$ 	* 	* 	*:c%jj)))))	*s   A. .+BBcached_responsetool_call_idc                    t          |t                    r-	 t          j        |          }n# t          j        $ r |}Y nw xY w|}t          |t
                    r)|                    dt          j        |                    }n,t          |t                    r|}nt          j        |          }t          |||pd          S )a  Deserialize a cached tool result into a ToolMessage.

        Converts the cached JSON string back into a proper LangChain
        ToolMessage so it conforms to the AgentMiddleware protocol.

        Args:
            cached_response: The cached JSON string.
            tool_name: The name of the tool that produced this result.
            tool_call_id: The ID of the tool call this result is for.

        Returns:
            A LangChainToolMessage containing the cached result.
        contentrZ   )rw   r[   ru   )	r<   rr   rh   loadsJSONDecodeErrorr=   rV   ri   ro   )r,   rt   r3   ru   parsedrw   s         r.   _deserialize_tool_resultz2ToolResultCacheMiddleware._deserialize_tool_resultA  s    " os++ 	%)O44' ) ) )() %F fd## 	)jjDJv,>,>??GG$$ 	)GGj((G#%+
 
 
 	
s   , A A handlerc                 >  K   t          |t                    r-|                    dd          }|                    dd          }nWt          |dd          }t          |t                    r-|                    dd          }|                    dd          }nd}d}|r|                     |          s ||           d{V S |                                  d{V  |                     |          }	 | j                            |           d{V }|ft          |t                    r|	                    d          }t                              d|dd	                     |                     |||          S n<# t          $ r/}| j        s t                              d
|            Y d}~nd}~ww xY w ||           d{V }		 |                     |	          }
| j        j        .| j                            ||
| j        j                   d{V  n!| j                            ||
           d{V  t                              d|dd	                     n<# t          $ r/}| j        s t                              d|            Y d}~nd}~ww xY w|	S )a>  Wrap a tool call with exact-match caching.

        This method is part of the LangChain AgentMiddleware protocol.
        Checks the cache for an exact tool+args match. If found,
        returns the cached result. Otherwise, calls the handler and
        caches the result.

        Args:
            request: The tool call request.
            handler: The async function to execute the tool.

        Returns:
            The tool result (from cache or handler).

        Raises:
            Exception: If graceful_degradation is False and cache operations fail.
        r3   rZ   idrU   Nr[   zutf-8zTool cache hit for key: P   z*Tool cache check failed, calling handler: )exzTool cache stored for key: zTool cache store failed: )r<   r=   rV   rW   rd   _ensure_initialized_asyncrl   _redisbytesdecodeloggerdebugr{   	Exception_graceful_degradationwarningrs   r&   ttl_secondsset)r,   rS   r|   r3   ru   rU   	cache_keyrt   eresult
result_strs              r.   awrap_tool_callz)ToolResultCacheMiddleware.awrap_tool_callh  sJ     2 gt$$ 
	"K44I";;tR00LLd;;I)T** "%MM&"55	(}}T266	!  	* 7 7 @ @ 	* ))))))))),,.........))'22		M$(KOOI$>$>>>>>>>O*ou55 F&5&<&<W&E&EOH	#2#HHIII44#Y   +  	M 	M 	M- NNKKKLLLLLLLL	M ww''''''''	<44V<<J|'3koozdl.F &           kooi<<<<<<<<<LLGy"~GGHHHH 	< 	< 	<- NN:q::;;;;;;;;	<
 s2   4BE= =
F6%F11F6BI! !
J+%JJc                 (   K    ||           d{V S )av  Pass through model calls without caching.

        This method is part of the LangChain AgentMiddleware protocol.
        Tool cache middleware only caches tool calls, not model calls.

        Args:
            request: The model request.
            handler: The async function to call the model.

        Returns:
            The model response from the handler.
        Nr1   )r,   rS   r|   s      r.   awrap_model_callz*ToolResultCacheMiddleware.awrap_model_call  s*      " WW%%%%%%%%%r/   )r(   N)"__name__
__module____qualname____doc__r   __annotations__r+   r2   rr   rc   r7   r   r   r>   rI   rR   staticmethodr   rX   rd   rl   rs   ro   r{   r   r   r   r   r   r
   r   r	   r   __classcell__)r-   s   @r.   r%   r%   6   s        # #J  4         <c <d < < < < tCH~ $    6. . . . . .CS#X C4S> C C C C"  4S>    \ :</ :<d :< :< :< :<x= =C = = = =B*C *C * * * *4%
"%
/2%
BE%
	%
 %
 %
 %
NO O y/CW/L)MNN
O 
#W,	-O O O Ob&& <.)M*BBC& 
	& & & & & & & &r/   r%   )"r   rh   loggingtypingr   r   r   r   r   r   !langchain.agents.middleware.typesr	   r
   r   langchain_core.messagesr   ro   langgraph.prebuilt.tool_noder   langgraph.typesr   aior   typesr   	getLoggerr   r   	frozensetr   rr   r   r#   r%   r1   r/   r.   <module>r      s       ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?         
 H G G G G G 8 8 8 8 8 8 # # # # # # % % % % % % " " " " " "		8	$	$-6Y  
. 
. IcN 
 
 

1 eCHo 
 
 
T& T& T& T& T& 4 T& T& T& T& T&r/   