o
    !ib                     @  sj  d Z ddl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rO	 ed
ZdZdZdZeG dd dZeG dd dZG dd de
Z G dd de Z!G dd de Z"e! Z#e" Z$eeedd+d!d"Z%eeedd+d#d$Z&d,d%d&Z'G d'd( d(e!Z(G d)d* d*e"Z)dS )-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                   @  s8   e Zd ZU dZded< ded< dZded< dddZdS )
CacheEntryz4A single cache entry with metadata for TTL tracking.r	   valuefloat
created_atNzOptional[Callable[[], Any]]refresh_functtl_secondsOptional[float]returnboolc                 C  s   |du rdS t   | j |kS )z/Check if entry is past its TTL (needs refresh).NF)timer   )selfr    r   [/var/www/html/crm_dreinet/venv_linux/lib/python3.10/site-packages/langsmith/prompt_cache.pyis_stale)   s   zCacheEntry.is_stale)r   r   r   r   )__name__
__module____qualname____doc____annotations__r   r   r   r   r   r   r   !   s   
 r   c                   @  s^   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	d
Z
e	dddZdS )CacheMetricszCache performance metrics.r   inthitsmisses	refreshesrefresh_errorsr   c                 C  s   | j | j S )z%Total cache requests (hits + misses).)r$   r%   r   r   r   r   total_requests9   s   zCacheMetrics.total_requestsr   c                 C  s   | j }|dkr| j| S dS )zCache hit rate (0.0 to 1.0).r   g        )r)   r$   )r   totalr   r   r   hit_rate>   s   zCacheMetrics.hit_rateN)r   r#   )r   r   )r   r   r   r    r$   r!   r%   r&   r'   propertyr)   r+   r   r   r   r   r"   0   s   
 r"   c                   @  s   e Zd ZdZg dZeeefd-ddZe	d.ddZ
d/ddZd0ddZd1ddZd2ddZd/dd Zd3d"d#Zd4d&d'Zd5d(d)Zd-d*d+Zd,S )6_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_metricsmax_sizer#   r   r   refresh_interval_secondsr   r   Nonec                 C  s.   t  | _t | _t | _| j|||d dS )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.
        r4   r   r5   N)r   r.   	threadingRLockr/   r"   r3   
_configurer   r4   r   r5   r   r   r   __init__U   s   

z_BasePromptCache.__init__r"   c                 C  s   | j S )zGet cache performance metrics.)r3   r(   r   r   r   metricsl   s   z_BasePromptCache.metricsc                 C  s   t  | _dS )zReset all metrics to zero.N)r"   r3   r(   r   r   r   reset_metricsq   s   z_BasePromptCache.reset_metricskeystrr   Callable[[], Any]Optional[Any]c                 C  s   | j dkrdS | j7 || jvr!| j jd7  _	 W d   dS | j| }||_| j| | j jd7  _|jW  d   S 1 sDw   Y  dS )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   )	r0   r/   r.   r3   r%   r   move_to_endr$   r   )r   r?   r   entryr   r   r   getu   s   


$z_BasePromptCache.getr   r	   c                 C  s   | j dkrdS | jA t }t|||d}|| jvr8t| j| j kr8tt| j}| j| t	
d|  || j|< | j| W d   dS 1 sNw   Y  dS )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: )r0   r/   r   r   r.   lennextiterpoploggerdebugrD   )r   r?   r   r   nowrE   
oldest_keyr   r   r   _set   s   
	
"z_BasePromptCache._setc                 C  s:   | j  | j|d W d   dS 1 sw   Y  dS )ziRemove a specific entry from cache.

        Args:
            key: The cache key to invalidate.
        N)r/   r.   rK   )r   r?   r   r   r   
invalidate   s   "z_BasePromptCache.invalidatec                 C  s6   | j  | j  W d   dS 1 sw   Y  dS )z$Clear all cache entries from memory.N)r/   r.   clearr(   r   r   r   rR      s   "z_BasePromptCache.clearlist[tuple[str, CacheEntry]]c                   sB    j   fdd j D W  d   S 1 sw   Y  dS )z.Get list of stale cache entries (thread-safe).c                   s$   g | ]\}}|  jr||fqS r   )r   r1   ).0r?   rE   r(   r   r   
<listcomp>   s    
z7_BasePromptCache._get_stale_entries.<locals>.<listcomp>N)r/   r.   itemsr(   r   r(   r   _get_stale_entries   s
   
$z#_BasePromptCache._get_stale_entriespathUnion[str, Path]c              
   C  sJ  ddl m} t|}|jjddd | j: i }| j D ]&\}}t|j	|j
r<t|j	dr6|j	jdd}n	|j	 }n|j	}|||< qd|i}W d	   n1 sRw   Y  |d
}z2t|d}	tj||	dd W d	   n1 suw   Y  || tdt| d|  W d	S  ty }
 z
| r|  |
d	}
~
w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 )	langsmithr[   r   parentmkdirr/   r.   rV   
isinstancer   PromptCommithasattrr^   dictwith_suffixopenr_   dumpreplacerL   rM   rH   	Exceptionexistsunlink)r   rX   
ls_schemasra   r?   rE   
value_datadata	temp_pathfer   r   r   rn      s6   



 z_BasePromptCache.dumpc                 C  s  ddl m} t|}| std|  dS zt|}t|}W d   n1 s-w   Y  W n! tj	t
fyT } ztd| d|  W Y d}~dS d}~ww |di }d}t }| jd | D ]W\}	}
t| j| jkrtd|   nBz$t|jd	r|j|
}n|j|
}t||d
}|| j|	< |d7 }W qi ty } ztd|	 d|  W Y d}~qid}~ww W d   n1 sw   Y  td| d|  |S )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   rZ   zCache file not found: NzFailed to load cache file : ra   z)Reached max cache size, stopping load at model_validate)r   r   rC   zFailed to load cache entry zLoaded z cache entries from )re   r[   r   rq   rL   rM   rm   r_   loadJSONDecodeErrorOSErrorwarningrF   r   r/   rV   rH   r.   r0   rj   ri   rz   	parse_objr   rp   )r   rX   rs   rw   ru   rx   ra   loadedrN   r?   rt   r   rE   r   r   r   r{      sN   

z_BasePromptCache.loadc                 C  s   || _ || _|| _d S )N)r0   r1   r2   r;   r   r   r   r:   &  s   
z_BasePromptCache._configureNr4   r#   r   r   r5   r   r   r6   )r   r"   r   r6   )r?   r@   r   rA   r   rB   r?   r@   r   r	   r   rA   r   r6   )r?   r@   r   r6   )r   rS   )rX   rY   r   r6   )rX   rY   r   r#   )r   r   r   r    	__slots__DEFAULT_PROMPT_CACHE_MAX_SIZE DEFAULT_PROMPT_CACHE_TTL_SECONDS-DEFAULT_PROMPT_CACHE_REFRESH_INTERVAL_SECONDSr<   r,   r=   r>   rF   rP   rQ   rR   rW   rn   r{   r:   r   r   r   r   r-   E   s$    




	

	
*7r-   c                      s   e Zd ZdZddgZeeedd# fddZd$ddZ	d%ddZ
d%ddZd%ddZd%ddZd%dd Zeeedd#d!d"Z  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_threadr7   r4   r#   r   r   r5   r   r   r6   c                  s&   t  j|||d t | _d| _dS )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.
        r7   N)superr<   r8   Eventr   r   r;   	__class__r   r   r<   K  s   

zPromptCache.__init__r?   r@   r   r	   r   rA   c                 C  s$   | j du r	|   | ||| dS )rG   N)r   _start_refresh_threadrP   r   r?   r   r   r   r   r   sete  s   
	zPromptCache.setc                 C  s   |    dS )gStop background refresh thread.

        Should be called when the client is being cleaned up.
        N)shutdownr(   r   r   r   stopr  s   zPromptCache.stopc                 C  s:   | j dur
| j   | jdur| jjdd d| _dS dS )r   Ng      @)timeout)r   r   r   joinr(   r   r   r   r   y  s   



zPromptCache.shutdownc                 C  sD   | j dur | j  tj| jddd| _| j  t	d dS dS )z5Start background thread for refreshing stale entries.NTzPromptCache-refresh)targetdaemonnamezStarted cache refresh thread)
r1   r   rR   r8   Thread_refresh_loopr   startrL   rM   r(   r   r   r   r     s   


z!PromptCache._start_refresh_threadc              
   C  sf   | j | js1z|   W n ty' } ztd|  W Y d}~nd}~ww | j | jrdS dS )z)Background loop to refresh stale entries.z(Unexpected error in cache refresh loop: N)r   waitr2   _refresh_stale_entriesrp   rL   	exceptionr   rx   r   r   r   r     s   zPromptCache._refresh_loopc                 C  s   |   }|sdS tdt| d |D ]U\}}| j r! dS |jdurjz| }| |||j | j j	d7  _	td|  W q t
yi } z| j jd7  _td| d|  W Y d}~qd}~ww qdS )z)Check for stale entries and refresh them.NzRefreshing  stale cache entriesrC   zRefreshed cache entry: zFailed to refresh cache entry ry   )rW   rL   rM   rH   r   is_setr   r   r3   r&   rp   r'   r~   r   stale_entriesr?   rE   	new_valuerx   r   r   r   r     s(   

"z"PromptCache._refresh_stale_entriesc                C  s   |    | j|||d dS )  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.
        r7   Nr   r:   r;   r   r   r   	configure  s   
zPromptCache.configurer   r   r   )r   r   r   r    r   r   r   r   r<   r   r   r   r   r   r   r   __classcell__r   r   r   r   r   1  s"    





	r   c                      s   e Zd ZdZdgZeeedd" fddZd#ddZ	d$ddZ
d$ddZd$ddZd$ddZd$ddZeeedd"d d!Z  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_taskr7   r4   r#   r   r   r5   r   r   r6   c                  s   t  j|||d d| _dS )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.
        r7   N)r   r<   r   r;   r   r   r   r<     s   
zAsyncPromptCache.__init__r?   r@   r   r	   r   Callable[[], Awaitable[Any]]c                   s,   | j du r|  I dH  | ||| dS )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   rP   r   r   r   r   aset  s   
zAsyncPromptCache.asetc                   s@   | j du rdS | jdurdS tj|  dd| _td dS )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)r1   r   asynciocreate_taskr   rL   rM   r(   r   r   r   r     s   

zAsyncPromptCache.startc                 C  s"   | j dur| j   d| _ dS dS )zStop background refresh task.

        Synchronous wrapper that cancels the refresh task.
        For proper cleanup in async context, use stop() instead.
        N)r   cancelr(   r   r   r   r     s   


zAsyncPromptCache.shutdownc                   sT   | j du rdS | j   z| j I dH  W n
 tjy   Y nw d| _ td dS )zlStop async background refresh loop.

        Cancels the refresh task and waits for it to complete.
        Nz Stopped async cache refresh task)r   r   r   CancelledErrorrL   rM   r(   r   r   r   r   $  s   

zAsyncPromptCache.stopc              
     sp   	 zt | jI dH  |  I dH  W n" t jy     ty6 } ztd|  W Y d}~nd}~ww q)z/Async background loop to refresh stale entries.TNz.Unexpected error in async cache refresh loop: )r   sleepr2   r   r   rp   rL   r   r   r   r   r   r   4  s   zAsyncPromptCache._refresh_loopc                   s   |   }|s	dS tdt| d |D ]S\}}|jduriz$| I dH }| |||jI dH  | j jd7  _td|  W q tyh } z| j j	d7  _	t
d| d|  W Y d}~qd}~ww qdS )z8Check for stale entries and refresh them asynchronously.NzAsync refreshing r   rC   zAsync refreshed cache entry: z$Failed to async refresh cache entry ry   )rW   rL   rM   rH   r   r   r3   r&   rp   r'   r~   r   r   r   r   r   @  s&   
"z'AsyncPromptCache._refresh_stale_entriesc                  s"   |   I dH  | ||| dS )r   Nr   r;   r   r   r   r   U  s   zAsyncPromptCache.configurer   )r?   r@   r   r	   r   r   r   r6   r   )r   r   r   r    r   r   r   r   r<   r   r   r   r   r   r   r   r   r   r   r   r   r     s"    






r   r7   r4   r#   r   r   r5   r   r   r6   c                 C  s   t j| ||d dS 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)
    r7   N)prompt_cache_singletonr   r7   r   r   r   configure_global_prompt_cachel  s
   
r   c                   s   t j| ||dI dH  dS r   )async_prompt_cache_singletonr   r7   r   r   r   #configure_global_async_prompt_cache  s   r   c                   C  s   t jdtdd d S )NzcThe 'Cache' class is deprecated and will be removed in a future version. Use 'PromptCache' instead.   )
stacklevel)warningswarnDeprecationWarningr   r   r   r   _deprecated_cache_class_warning  s
   
r   c                      ,   e Zd ZdZeeedd fddZ  ZS )Cachez:Deprecated alias for PromptCache. Use PromptCache instead.r7   r4   r#   r   r   r5   r   r   r6   c                     t   t j|||d dS )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.
        r7   Nr   r   r<   r;   r   r   r   r<        
zCache.__init__r   	r   r   r   r    r   r   r   r<   r   r   r   r   r   r         r   c                      r   )
AsyncCachezDDeprecated alias for AsyncPromptCache. Use AsyncPromptCache instead.r7   r4   r#   r   r   r5   r   r   r6   c                  r   )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.
        r7   Nr   r;   r   r   r   r<     r   zAsyncCache.__init__r   r   r   r   r   r   r     r   r   r   r   )*r    
__future__r   r   r_   loggingr8   r   r   abcr   collectionsr   collections.abcr   dataclassesr   pathlibr   typingr   r	   r
   r   r   	getLoggerrL   r   r   r   r   r"   r-   r   r   r   r   r   r   r   r   r   r   r   r   r   <module>   sV    
 m  #
	