
    jib                       d Z ddlmZ ddlZddlmc mZ ddl	Z	ddl
Z
ddlZddlZddlZddl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 dd	lmZmZmZmZmZ er	  ej>                  d
      Z dZ!dZ"dZ#e G d d             Z$e G d d             Z% G d de      Z& G d de&      Z' G d de&      Z( e'       Z) e(       Z*e"e!e#d	 	 	 	 	 	 	 d dZ+e"e!e#d	 	 	 	 	 	 	 d dZ,d!dZ- G d de'      Z. G d de(      Z/y)"zPrompt caching module for LangSmith SDK.

This module provides thread-safe LRU caches with background refresh
for prompt caching. Includes both sync and async implementations.
    )annotationsN)ABC)OrderedDict)	Awaitable)	dataclass)Path)TYPE_CHECKINGAnyCallableOptionalUnionzlangsmith.cachei,  d   <   c                  <    e Zd ZU dZded<   ded<   dZded<   d
d	Zy)
CacheEntryz4A single cache entry with metadata for TTL tracking.r
   valuefloat
created_atNzOptional[Callable[[], Any]]refresh_funcc                P    |yt        j                          | j                  z
  |kD  S )z/Check if entry is past its TTL (needs refresh).F)timer   )selfttl_secondss     ^/home/jang/Projects/ai-researcher/.venv/lib/python3.12/site-packages/langsmith/prompt_cache.pyis_stalezCacheEntry.is_stale)   s%    		doo-<<    )r   Optional[float]returnbool)__name__
__module____qualname____doc____annotations__r   r    r   r   r   r   !   s     >J04L-4=r   r   c                  n    e Zd ZU dZdZded<   dZded<   dZded<   dZded<   e	dd       Z
e	dd	       Zy
)CacheMetricszCache performance metrics.r   inthitsmisses	refreshesrefresh_errorsc                4    | j                   | j                  z   S )z%Total cache requests (hits + misses).)r)   r*   r   s    r   total_requestszCacheMetrics.total_requests9   s     yy4;;&&r   c                F    | j                   }|dkD  r| j                  |z  S dS )zCache hit rate (0.0 to 1.0).r   g        )r/   r)   )r   totals     r   hit_ratezCacheMetrics.hit_rate>   s)     ##$)AItyy5 636r   N)r   r(   )r   r   )r    r!   r"   r#   r)   r$   r*   r+   r,   propertyr/   r2   r%   r   r   r'   r'   0   sQ    $D#MFCOIsNC' ' 7 7r   r'   c                      e Zd ZdZg dZeeef	 	 	 	 	 	 	 ddZe	dd       Z
ddZddZddZddZdd	Zdd
ZddZddZ	 	 	 	 	 	 	 	 ddZy)_BasePromptCachezBase class for prompt caches with shared LRU logic.

    Provides thread-safe in-memory LRU cache operations.
    Subclasses implement the background refresh mechanism.
    )_cache_lock	_max_size_ttl_seconds_refresh_interval_metricsc                    t               | _        t        j                         | _        t               | _        | j                  |||       y)ag  Initialize the base cache.

        Args:
            max_size: Maximum entries in cache (LRU eviction when exceeded).
            ttl_seconds: Time before entry is considered stale. Set to None for
                infinite TTL (entries never expire, no background refresh).
            refresh_interval_seconds: How often to check for stale entries.
        max_sizer   refresh_interval_secondsN)r   r6   	threadingRLockr7   r'   r;   
_configurer   r>   r   r?   s       r   __init__z_BasePromptCache.__init__U   s?     5@M__&
$#%= 	 	
r   c                    | j                   S )zGet cache performance metrics.)r;   r.   s    r   metricsz_BasePromptCache.metricsl   s     }}r   c                "    t               | _        y)zReset all metrics to zero.N)r'   r;   r.   s    r   reset_metricsz_BasePromptCache.reset_metricsq   s    $r   c                   | j                   dk(  ry| j                  5  || j                  vr)| j                  xj                  dz  c_        	 ddd       y| j                  |   }||_        | j                  j                  |       | j                  xj                  dz  c_        |j                  cddd       S # 1 sw Y   yxY w)a]  Get a value from cache.

        Args:
            key: The cache key (prompt identifier like "owner/name:hash").
            refresh_func: Function to refresh this cache entry when stale.

        Returns:
            The cached value or None if not found.
            Stale entries are still returned (background refresh handles updates).
        r   N   )	r8   r7   r6   r;   r*   r   move_to_endr)   r   )r   keyr   entrys       r   getz_BasePromptCache.getu   s     >>QZZ 	$++%$$)$	 	
 KK$E ".E KK##C(MM!#;;	 	 	s   /B:AB::Cc                    | j                   dk(  ry| j                  5  t        j                         }t        |||      }|| j                  vrst        | j                        | j                   k\  rQt        t        | j                              }| j                  j                  |       t        j                  d|        || j                  |<   | j                  j                  |       ddd       y# 1 sw Y   yxY w)Set a value in the cache.

        Args:
            key: The cache key (prompt identifier).
            value: The value to cache.
            refresh_func: Function to refresh this cache entry when stale.
        r   N)r   r   r   zEvicted oldest cache entry: )r8   r7   r   r   r6   lennextiterpoploggerdebugrK   )r   rL   r   r   nowrM   
oldest_keys          r   _setz_BasePromptCache._set   s     >>QZZ 	)))+CUsVE $++%#dkk*:dnn*L!$t{{"34

+;J<HI$DKKKK##C(	) 	) 	)s   CC44C=c                ~    | j                   5  | j                  j                  |d       ddd       y# 1 sw Y   yxY w)ziRemove a specific entry from cache.

        Args:
            key: The cache key to invalidate.
        N)r7   r6   rT   )r   rL   s     r   
invalidatez_BasePromptCache.invalidate   s2     ZZ 	'KKOOC&	' 	' 	's   3<c                z    | j                   5  | j                  j                          ddd       y# 1 sw Y   yxY w)z$Clear all cache entries from memory.N)r7   r6   clearr.   s    r   r]   z_BasePromptCache.clear   s.    ZZ 	 KK	  	  	 s   1:c                    | j                   5  | j                  j                         D cg c]$  \  }}|j                  | j                        r||f& c}}cddd       S c c}}w # 1 sw Y   yxY w)z.Get list of stale cache entries (thread-safe).N)r7   r6   itemsr   r9   )r   rL   rM   s      r   _get_stale_entriesz#_BasePromptCache._get_stale_entries   sg    ZZ 	 #'++"3"3"5C>>$"3"34 e	 		 	s   A')A!A'!A''A0c                V   ddl m} t        |      }|j                  j	                  dd       | j
                  5  i }| j                  j                         D ]  \  }}t        |j                  |j                        rNt        |j                  d      r|j                  j                  d      }n'|j                  j                         }n|j                  }|||<    d|i}d	d	d	       |j                  d
      }	 t        |d      5 }	t!        j"                  |	d       d	d	d	       |j%                  |       t&        j)                  dt+               d|        y	# 1 sw Y   ~xY w# 1 sw Y   KxY w# t,        $ r'}
|j/                         r|j1                          |
d	}
~
ww xY w)z{Dump cache contents to a JSON file for offline use.

        Args:
            path: Path to the output JSON file.
        r   schemasT)parentsexist_ok
model_dumpjson)modeentriesNz.tmpw   )indentzDumped z cache entries to )	langsmithrc   r   parentmkdirr7   r6   r_   
isinstancer   PromptCommithasattrrf   dictwith_suffixopenrg   dumpreplacerU   rV   rQ   	Exceptionexistsunlink)r   path
ls_schemasri   rL   rM   
value_datadata	temp_pathfes              r   rv   z_BasePromptCache.dump   su    	4Dz$6ZZ 	(G"kk//1 *
Uekk:+B+BCu{{L9%*[[%;%;%;%H
%*[[%5%5%7
 "'J)* w'D!	(& $$V,			i% -		$!,-d#LL73w<.0B4&IJ1	( 	(*- -  	!  "G		s<   B(E =E8 	E,"=E8  E),E51E8 8	F("F##F(c           	        ddl m} t        |      }|j                         st        j                  d|        y	 t        |      5 }t        j                  |      }ddd       j                  di       }d}t        j                         }| j                  5  |j                         D ]  \  }	}
t!        | j"                        | j$                  k\  rt        j                  d|         nq	 t'        |j(                  d	      r|j(                  j+                  |
      }n|j(                  j-                  |
      }t/        ||
      }|| j"                  |	<   |dz  } ddd       t        j                  d| d|        |S # 1 sw Y   'xY w# t        j                  t        f$ r%}t        j                  d| d|        Y d}~yd}~ww xY w# t0        $ r&}t        j                  d|	 d|        Y d}~Pd}~ww xY w# 1 sw Y   xY w)a%  Load cache contents from a JSON file.

        Args:
            path: Path to the JSON file to load.

        Returns:
            Number of entries loaded.

        Loaded entries get a fresh TTL starting from load time.
        If the file doesn't exist or is corrupted, returns 0.
        r   rb   zCache file not found: NzFailed to load cache file : ri   z)Reached max cache size, stopping load at model_validate)r   r   rJ   zFailed to load cache entry zLoaded z cache entries from )rm   rc   r   ry   rU   rV   ru   rg   loadJSONDecodeErrorOSErrorwarningrN   r   r7   r_   rQ   r6   r8   rr   rq   r   	parse_objr   rx   )r   r{   r|   r   r~   r   ri   loadedrW   rL   r}   r   rM   s                r   r   z_BasePromptCache.load   s    	4Dz{{}LL1$89	d $qyy|$ ((9b)iikZZ 	#*==? Zt{{#t~~5LL#LVH!UVz668HI * 7 7 F Fz R * 7 7 A A* M 'UsCE',DKK$aKF	* 	wvh&:4&ABA$ $$$g. 	NN7vRsCD	2 ! NN%@Rs#KL#	 	sg   F FF AH-A.GHFF G(GG	G?G:4H:G??HHc                .    || _         || _        || _        y )N)r8   r9   r:   rC   s       r   rB   z_BasePromptCache._configure&  s     "'!9r   Nr>   r(   r   r   r?   r   r   None)r   r'   r   r   )rL   strr   Callable[[], Any]r   zOptional[Any]rL   r   r   r
   r   r   r   r   )rL   r   r   r   )r   zlist[tuple[str, CacheEntry]])r{   Union[str, Path]r   r   )r{   r   r   r(   )r    r!   r"   r#   	__slots__DEFAULT_PROMPT_CACHE_MAX_SIZE DEFAULT_PROMPT_CACHE_TTL_SECONDS-DEFAULT_PROMPT_CACHE_REFRESH_INTERVAL_SECONDSrD   r3   rF   rH   rN   rY   r[   r]   r`   rv   r   rB   r%   r   r   r5   r5   E   s    I 6'G*W	

 %
 #(	

 

.  '>)4' 
(T5n:: %: #(	:
 
:r   r5   c                       e Zd ZdZddgZeeed	 	 	 	 	 	 	 d fdZddZ	ddZ
ddZdd	Zdd
ZddZeeed	 	 	 	 	 	 	 ddZ xZS )PromptCachea  Thread-safe LRU cache with background thread refresh.

    For use with the synchronous Client.

    Features:
    - In-memory LRU cache with configurable max size
    - Background thread for refreshing stale entries
    - Stale-while-revalidate: returns stale data while refresh happens
    - Thread-safe for concurrent access

    Example:
        >>> def fetch_prompt(key: str) -> PromptCommit:
        ...     return client._fetch_prompt_from_api(key)
        >>> cache = PromptCache(
        ...     max_size=100,
        ...     ttl_seconds=3600,
        ...     fetch_func=fetch_prompt,
        ... )
        >>> cache.set("my-prompt:latest", prompt_commit)
        >>> cached = cache.get("my-prompt:latest")
        >>> cache.shutdown()
    _shutdown_event_refresh_threadr=   c               j    t         |   |||       t        j                         | _        d| _        y)a  Initialize the sync prompt cache.

        Args:
            max_size: Maximum entries in cache (LRU eviction when exceeded).
            ttl_seconds: Time before entry is considered stale. Set to None for
                infinite TTL (offline mode - entries never expire).
                Default: 300 (5 minutes).
            refresh_interval_seconds: How often to check for stale entries.
        r=   N)superrD   r@   Eventr   r   r   r>   r   r?   	__class__s       r   rD   zPromptCache.__init__K  s9      	#%= 	 	

  )0;?r   c                b    | j                   | j                          | j                  |||       y)rP   N)r   _start_refresh_threadrY   r   rL   r   r   s       r   setzPromptCache.sete  s,     '&&(		#ul+r   c                $    | j                          y)gStop background refresh thread.

        Should be called when the client is being cleaned up.
        N)shutdownr.   s    r   stopzPromptCache.stopr  s    
 	r   c                    | j                   | j                   j                          | j                  $| j                  j                  d       d| _        yy)r   Ng      @)timeout)r   r   r   joinr.   s    r   r   zPromptCache.shutdowny  sR    
 +  $$&+  %%c%2#'D  ,r   c                    | j                   q| j                  j                          t        j                  | j
                  dd      | _        | j                  j                          t        j                  d       yy)z5Start background thread for refreshing stale entries.NTzPromptCache-refresh)targetdaemonnamezStarted cache refresh thread)
r9   r   r]   r@   Thread_refresh_loopr   startrU   rV   r.   s    r   r   z!PromptCache._start_refresh_thread  sh    (  &&(#,#3#3))*$D 
   &&(LL78 )r   c                   | j                   j                  | j                        s8	 | j                          | j                   j                  | j                        s7yy# t        $ r"}t
        j                  d|        Y d}~Nd}~ww xY w)z)Background loop to refresh stale entries.z(Unexpected error in cache refresh loop: N)r   waitr:   _refresh_stale_entriesrx   rU   	exceptionr   r   s     r   r   zPromptCache._refresh_loop  sw    &&++D,B,BCQ++- &&++D,B,BC  Q  #KA3!OPPQs   A 	B
(BB
c                >   | j                         }|syt        j                  dt        |       d       |D ]  \  }}| j                  j                         r y|j                  /	 |j                         }| j                  |||j                         | j                  xj                  dz  c_	        t        j                  d|         y# t        $ rD}| j                  xj                  dz  c_        t        j                  d| d|        Y d}~d}~ww xY w)z)Check for stale entries and refresh them.NzRefreshing  stale cache entriesrJ   zRefreshed cache entry: zFailed to refresh cache entry r   )r`   rU   rV   rQ   r   is_setr   r   r;   r+   rx   r,   r   r   stale_entriesrL   rM   	new_valuer   s         r   r   z"PromptCache._refresh_stale_entries  s   //1{3}#5"66JKL' 	PJC##**,!!-P % 2 2 4IHHS)U-?-?@MM++q0+LL#:3%!@A	P ! PMM00A50NN%CC51##NOOPs   (A$C	D:DDc               L    | j                          | j                  |||       y)  Reconfigure the cache parameters.

        Args:
            max_size: Maximum entries in cache (LRU eviction when exceeded).
            ttl_seconds: Time before entry is considered stale.
            refresh_interval_seconds: How often to check for stale entries.
        r=   Nr   rB   rC   s       r   	configurezPromptCache.configure  s'     			#%= 	 	
r   r   r   r   )r    r!   r"   r#   r   r   r   r   rD   r   r   r   r   r   r   r   __classcell__r   s   @r   r   r   1  s    . #$56I
 6'G*W@ @ %	@
 #(@ 
@4,	(
9QP4 6'G*W
 
 %	

 #(
 

r   r   c                       e Zd ZdZdgZeeed	 	 	 	 	 	 	 d fdZ	 	 	 	 	 	 	 	 ddZ	ddZ
ddZddZdd	Zdd
Zeeed	 	 	 	 	 	 	 ddZ xZS )AsyncPromptCachea  Thread-safe LRU cache with asyncio task refresh.

    For use with the asynchronous AsyncClient.

    Features:
    - In-memory LRU cache with configurable max size
    - Asyncio task for refreshing stale entries
    - Stale-while-revalidate: returns stale data while refresh happens
    - Thread-safe for concurrent access

    Example:
        >>> async def fetch_prompt(key: str) -> PromptCommit:
        ...     return await client._afetch_prompt_from_api(key)
        >>> cache = AsyncPromptCache(
        ...     max_size=100,
        ...     ttl_seconds=3600,
        ...     fetch_func=fetch_prompt,
        ... )
        >>> await cache.start()
        >>> cache.set("my-prompt:latest", prompt_commit)
        >>> cached = cache.get("my-prompt:latest")
        >>> await cache.stop()
    _refresh_taskr=   c               8    t         |   |||       d| _        y)ag  Initialize the async prompt cache.

        Args:
            max_size: Maximum entries in cache (LRU eviction when exceeded).
            ttl_seconds: Time before entry is considered stale. Set to None for
                infinite TTL (offline mode - entries never expire).
            refresh_interval_seconds: How often to check for stale entries.
        r=   N)r   rD   r   r   s       r   rD   zAsyncPromptCache.__init__  s+     	#%= 	 	

 <@r   c                ~   K   | j                   | j                          d{    | j                  |||       y7 w)zSet a value in the cache.

        Args:
            key: The cache key (prompt identifier).
            value: The value to cache.
            refresh_func: Async function to refresh this cache entry when stale.
        N)r   r   rY   r   s       r   asetzAsyncPromptCache.aset  s8      %**,		#ul+ s    =;=c                   K   | j                   y| j                  yt        j                  | j	                         d      | _        t
        j                  d       yw)zStart async background refresh loop.

        Must be called from an async context. Creates an asyncio task that
        periodically checks for stale entries and refreshes them.
        Does nothing if ttl_seconds is None (infinite TTL mode).
        NzAsyncPromptCache-refresh)r   z Started async cache refresh task)r9   r   asynciocreate_taskr   rU   rV   r.   s    r   r   zAsyncPromptCache.start  sW      $)$00 +
 	78s   AAc                `    | j                   "| j                   j                          d| _         yy)zStop background refresh task.

        Synchronous wrapper that cancels the refresh task.
        For proper cleanup in async context, use stop() instead.
        N)r   cancelr.   s    r   r   zAsyncPromptCache.shutdown  s/     )%%'!%D *r   c                   K   | j                   y| j                   j                          	 | j                    d{    d| _         t        j                  d       y7 !# t        j                  $ r Y 4w xY ww)zlStop async background refresh loop.

        Cancels the refresh task and waits for it to complete.
        Nz Stopped async cache refresh task)r   r   r   CancelledErrorrU   rV   r.   s    r   r   zAsyncPromptCache.stop$  sp     
 %!!#	$$$$ "78	 %%% 		s7   (A7A AA A7A A41A73A44A7c                  K   	 	 t        j                  | j                         d{    | j                          d{    A7 7 # t         j                  $ r  t
        $ r"}t        j                  d|        Y d}~>d}~ww xY ww)z/Async background loop to refresh stale entries.Nz.Unexpected error in async cache refresh loop: )r   sleepr:   r   r   rx   rU   r   r   s     r   r   zAsyncPromptCache._refresh_loop4  s     WmmD$:$:;;;11333 ;3))  W  #QRSQT!UVVWsO   B
"A	 AA	 A A	 B
A	 A	 	B%B=B
BB
c                6  K   | j                         }|syt        j                  dt        |       d       |D ]  \  }}|j                  	 |j	                          d{   }| j                  |||j                         d{    | j                  xj                  dz  c_        t        j                  d|         y7 c7 @# t        $ rD}| j                  xj                  dz  c_	        t        j                  d| d|        Y d}~d}~ww xY ww)z8Check for stale entries and refresh them asynchronously.NzAsync refreshing r   rJ   zAsync refreshed cache entry: z$Failed to async refresh cache entry r   )r`   rU   rV   rQ   r   r   r;   r+   rx   r,   r   r   s         r   r   z'AsyncPromptCache._refresh_stale_entries@  s    //1(]);(<<PQR' 
	VJC!!-V&+&8&8&: :I))CE4F4FGGGMM++q0+LL#@!FG
	V !;G ! VMM00A50NN%I#bQRPS#TUUVsT   A
DC	!C"$C	C;C	DC	C			D:DDDDc               f   K   | j                          d{    | j                  |||       y7 w)r   Nr   rC   s       r   r   zAsyncPromptCache.configureU  s-      iik+/GH 	s   1/1r   )rL   r   r   r
   r   zCallable[[], Awaitable[Any]]r   r   r   )r    r!   r"   r#   r   r   r   r   rD   r   r   r   r   r   r   r   r   r   s   @r   r   r     s    0 !!I
 6'G*W@ @ %	@
 #(@ 
@,,,",2N,	,9(&9 
WV0 6'G*WI I %	I
 #(I 
Ir   r   r=   c                4    t         j                  | ||       ya  Configure the global prompt cache.

    This should be called before any cache instances are created or used.

    Args:
        max_size: Maximum entries in cache (LRU eviction when exceeded).
        ttl_seconds: Time before entry is considered stale.
        refresh_interval_seconds: How often to check for stale entries.

    Example:
        >>> from langsmith import configure_global_prompt_cache
        >>> configure_global_prompt_cache(max_size=200, ttl_seconds=7200)
    r=   N)prompt_cache_singletonr   r=   s      r   configure_global_prompt_cacher   l  s     & $$!9 % r   c                P   K   t         j                  | ||       d{    y7 wr   )async_prompt_cache_singletonr   r=   s      r   #configure_global_async_prompt_cacher     s.     & '
0
0!9 1   s   &$&c                 <    t        j                  dt        d       y )NzcThe 'Cache' class is deprecated and will be removed in a future version. Use 'PromptCache' instead.   )
stacklevel)warningswarnDeprecationWarningr%   r   r   _deprecated_cache_class_warningr     s    MM	%	r   c                  <     e Zd ZdZeeed	 	 	 	 	 	 	 d fdZ xZS )Cachez:Deprecated alias for PromptCache. Use PromptCache instead.r=   c               >    t                t        | 	  |||       y)a  Initialize the deprecated Cache class.

        Args:
            max_size: Maximum entries in cache (LRU eviction when exceeded).
            ttl_seconds: Time before entry is considered stale.
            refresh_interval_seconds: How often to check for stale entries.
        r=   Nr   r   rD   r   s       r   rD   zCache.__init__  &     	()#%= 	 	
r   r   	r    r!   r"   r#   r   r   r   rD   r   r   s   @r   r   r     sC    D
 6'G*W
 
 %	

 #(
 

 
r   r   c                  <     e Zd ZdZeeed	 	 	 	 	 	 	 d fdZ xZS )
AsyncCachezDDeprecated alias for AsyncPromptCache. Use AsyncPromptCache instead.r=   c               >    t                t        | 	  |||       y)a  Initialize the deprecated AsyncCache class.

        Args:
            max_size: Maximum entries in cache (LRU eviction when exceeded).
            ttl_seconds: Time before entry is considered stale.
            refresh_interval_seconds: How often to check for stale entries.
        r=   Nr   r   s       r   rD   zAsyncCache.__init__  r   r   r   r   r   s   @r   r   r     sC    N
 6'G*W
 
 %	

 #(
 

 
r   r   r   r   )0r#   
__future__r   builtins@py_builtins_pytest.assertion.rewrite	assertionrewrite
@pytest_arr   rg   loggingr@   r   r   abcr   collectionsr   collections.abcr   dataclassesr   pathlibr   typingr	   r
   r   r   r   	getLoggerrU   r   r   r   r   r'   r5   r   r   r   r   r   r   r   r   r   r%   r   r   <module>r      sg   #          # % !  @ @			,	- $*   # 02 - = = = 7 7 7(i:s i:XR
" R
j^I' ^ID % /1 
 2#C&S	 ! $	
 
8 2#C&S	 ! $	
 
:
K 
2
! 
r   