o
    ưi                     @   s   d Z ddlZddlmZ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r;dd	lmZ eZneZd
ZdZG dd deZdS )a  
Max Iterations Limiter for LiteLLM Proxy.

Enforces a per-session cap on the number of LLM calls an agentic loop can make.
Callers send a `session_id` with each request (via `x-litellm-session-id` header
or `metadata.session_id`), and this hook counts calls per session. When the count
exceeds `max_iterations` (configured in agent litellm_params or key metadata), returns 429.

Works across multiple proxy instances via DualCache (in-memory + Redis).
Follows the same pattern as parallel_request_limiter_v3.py.
    N)TYPE_CHECKINGAnyOptionalUnion)HTTPException)	DualCache)verbose_proxy_logger)CustomLogger)UserAPIKeyAuth)InternalUsageCachez
local key = KEYS[1]
local ttl = tonumber(ARGV[1])

local current = redis.call('INCR', key)
if current == 1 then
    redis.call('EXPIRE', key, ttl)
end

return current
i  c                   @   s   e Zd ZdZdefddZdededede	d	e
eee	ef  f
d
dZded	e
e	 fddZded	e
e fddZde	d	e	fddZde	d	efddZde	d	efddZdS )_PROXY_MaxIterationsHandlera  
    Pre-call hook that enforces max_iterations per session.

    Configuration:
        - max_iterations: set in agent litellm_params (preferred)
          e.g. litellm_params={"max_iterations": 25}
          Falls back to key metadata max_iterations for backwards compatibility.
        - session_id: sent by caller via x-litellm-session-id header or
          metadata.session_id in request body

    Cache key pattern:
        {session_iterations:<session_id>}:count

    Multi-instance support:
        Uses Redis Lua script for atomic increment (same pattern as
        parallel_request_limiter_v3). Falls back to in-memory cache
        when Redis is unavailable.
    internal_usage_cachec                 C   sF   || _ ttdt| _| j jjd ur| j jjt	| _
d S d | _
d S )NZLITELLM_MAX_ITERATIONS_TTL)r   intosgetenvDEFAULT_MAX_ITERATIONS_TTLttlZ
dual_cacheredis_cacheZasync_register_scriptMAX_ITERATIONS_INCREMENT_SCRIPTincrement_script)selfr    r   a/home/app/Keep/.python/lib/python3.10/site-packages/litellm/proxy/hooks/max_iterations_limiter.py__init__F   s   


z$_PROXY_MaxIterationsHandler.__init__user_api_key_dictcachedata	call_typereturnc           	   	      s   |  |}|du rdS | |}|du rdS td|| | |}| |I dH }||kr?tdd| d| d| ddtd	||| dS )
z
        Check session iteration count before making the API call.

        Extracts session_id from request metadata and max_iterations from
        agent litellm_params. If the session has exceeded max_iterations, raises 429.
        Nz6MaxIterationsHandler: session_id=%s, max_iterations=%si  z$Max iterations exceeded for session z. Current count: z, max_iterations: .)status_codedetailz0MaxIterationsHandler: session_id=%s, count=%s/%s)_get_session_id_get_max_iterationsr   debug_make_cache_key_increment_and_getr   )	r   r   r   r   r   
session_idmax_iterations	cache_keyZcurrent_countr   r   r   async_pre_call_hookV   s<   


z/_PROXY_MaxIterationsHandler.async_pre_call_hookc                 C   sT   | dpi }| d}|durt|S | dpi }| d}|dur(t|S dS )z)Extract session_id from request metadata.metadatar'   Nlitellm_metadata)getstr)r   r   r+   r'   r,   r   r   r   r"      s   

z+_PROXY_MaxIterationsHandler._get_session_idc                 C   sz   |j }|dur)ddlm} |j|d}|dur)|jpi }|d}|dur)t|S |jp-i }|d}|dur;t|S dS )zPExtract max_iterations from agent litellm_params, with fallback to key metadata.Nr   )global_agent_registry)agent_idr(   )r0   Z,litellm.proxy.agent_endpoints.agent_registryr/   Zget_agent_by_idlitellm_paramsr-   r   r+   )r   r   r0   r/   Zagentr1   r(   r+   r   r   r   r#      s   



z/_PROXY_MaxIterationsHandler._get_max_iterationsr'   c                 C   s   d| dS )z
        Create cache key for session iteration counter.

        Uses Redis hash-tag pattern {session_iterations:<session_id>} so all
        keys for a session land on the same Redis Cluster slot.
        z{session_iterations:z}:countr   )r   r'   r   r   r   r%      s   z+_PROXY_MaxIterationsHandler._make_cache_keyr)   c              
      sv   | j dur3z| j |g| jgdI dH }t|W S  ty2 } ztdt| W Y d}~nd}~ww | |I dH S )z
        Atomically increment the session counter and return the new value.

        Tries Redis first (via registered Lua script for atomicity across
        instances), falls back to in-memory cache.
        N)keysargszAMaxIterationsHandler: Redis failed, falling back to in-memory: %s)r   r   r   	Exceptionr   warningr.   _in_memory_increment)r   r)   resulter   r   r   r&      s    

z._PROXY_MaxIterationsHandler._increment_and_getc                    sT   | j j|dddI dH }|durt|ndd }| j j||| jdddI dH  |S )z.Increment counter in in-memory cache with TTL.NT)keylitellm_parent_otel_span
local_onlyr      )r9   valuer   r:   r;   )r   Zasync_get_cacher   Zasync_set_cacher   )r   r)   current	new_valuer   r   r   r6      s   z0_PROXY_MaxIterationsHandler._in_memory_incrementN)__name__
__module____qualname____doc__r   r   r
   r   dictr.   r   r   r4   r*   r"   r   r#   r%   r&   r6   r   r   r   r   r   2   s.    
2
	r   )rC   r   typingr   r   r   r   Zfastapir   Zlitellmr   Zlitellm._loggingr   Z"litellm.integrations.custom_loggerr	   Zlitellm.proxy._typesr
   Zlitellm.proxy.utilsr   Z_InternalUsageCacher   r   r   r   r   r   r   <module>   s    