Ë
    tPêjT   ã                   óÆ   — U d Z ddl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 dd	lmZ dd
lmZ er	ddlmZ eZneZdZeed<   dZeed<    G d„ de«      Zy)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ÚFinal)Ú	DualCache)Úverbose_proxy_logger)ÚRateLimitType)ÚCustomLogger)ÚUserAPIKeyAuth)ÚProxyRateLimitError)Ú#resolve_llm_provider_for_rate_limit)Ú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
ÚMAX_ITERATIONS_INCREMENT_SCRIPTi  ÚDEFAULT_MAX_ITERATIONS_TTLc                   ó¦   — e Zd ZdZdefd„Zdededede	de
e	z  ez  d	z  f
d
„Zdede	d	z  fd„Zdeded	z  fd„Zde	de	fd„Zde	defd„Zde	defd„Zy	)Ú_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                 ó"  — || _         t        t        j                  dt        «      «      | _        | j                   j                  j                  �9| j                   j                  j                  j                  t        «      | _
        y d | _
        y )NÚLITELLM_MAX_ITERATIONS_TTL)r   ÚintÚosÚgetenvr   ÚttlÚ
dual_cacheÚredis_cacheÚasync_register_scriptr   Úincrement_script)Úselfr   s     úo/home/oem/CyberBrain26-unified/.venv/lib/python3.12/site-packages/litellm/proxy/hooks/max_iterations_limiter.pyÚ__init__z$_PROXY_MaxIterationsHandler.__init__G   so   € Ø$8ˆÔ!Ü”r—y‘yÐ!=Ô?YÓZÓ[ˆŒð ×$Ñ$×/Ñ/×;Ñ;ÐGØ$(×$=Ñ$=×$HÑ$H×$TÑ$T×$jÑ$jÜ/ó%ˆDÕ!ð %)ˆDÕ!ó    Úuser_api_key_dictÚcacheÚdataÚ	call_typeÚreturnNc           	   ƒ   ó¬  K  — | j                  |«      }|€y| j                  |«      }|€yt        j                  d||«       | j	                  |«      }| j                  |«      ƒ d{  –—† }||kD  rHt        |r|j                  d«      nd«      \  }	}
t        d|› d|› d|› d�t        j                  |	|
¬«      ‚t        j                  d	|||«       y7 Œj­w)
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=%sÚmodelz$Max iterations exceeded for session z. Current count: z, max_iterations: ú.)ÚdetailÚrate_limit_typer'   Úllm_providerz0MaxIterationsHandler: session_id=%s, count=%s/%s)Ú_get_session_idÚ_get_max_iterationsr   ÚdebugÚ_make_cache_keyÚ_increment_and_getr   Úgetr   r   ÚMAX_ITERATIONS)r   r!   r"   r#   r$   Ú
session_idÚmax_iterationsÚ	cache_keyÚcurrent_countÚresolved_modelr+   s              r   Úasync_pre_call_hookz/_PROXY_MaxIterationsHandler.async_pre_call_hookS   s  è ø€ ð !×0Ñ0°Ó6ˆ
ØÐØà $× 8Ñ 8Ð9JÓ KˆØÐ!Øä×"Ñ"ØDØØô	
ð  ×/Ñ/°
Ó;ˆ	Ø%)×%<Ñ%<¸YÓ%G×Gˆà˜>Ò)Ü+NÑdhÈtÏxÉxÐX_ÔO`ÐnrÓ+sÑ(ˆN˜LÜ%à:¸:¸,ð G&Ø&3 _Ð4FÀ~ÐFVÐVWðYô !.× <Ñ <Ø$Ø)ôð ô 	×"Ñ"Ø>ØØØô		
ð ð+  Hús   ‚A%CÁ'CÁ(A+Cc                 óÐ   — |j                  d«      xs i }|j                  d«      }|�t        |«      S |j                  d«      xs i }|j                  d«      }|�t        |«      S y)z)Extract session_id from request metadata.Úmetadatar3   NÚlitellm_metadata)r1   Ústr)r   r#   r:   r3   r;   s        r   r,   z+_PROXY_MaxIterationsHandler._get_session_idˆ   sm   € àŸ(™( :Ó.Ò4°"ˆØ—\‘\ ,Ó/ˆ
ØÐ!Ü�z“?Ð"ð #'§(¡(Ð+=Ó">Ò"DÀ"ÐØ%×)Ñ)¨,Ó7ˆ
ØÐ!Ü�z“?Ð"àr    c                 ó  — |j                   }|�Hddlm} |j                  |¬«      }|�.|j                  xs i }|j                  d«      }|�t        |«      S |j                  xs i }|j                  d«      }|�t        |«      S y)zPExtract max_iterations from agent litellm_params, with fallback to key metadata.Nr   )Úglobal_agent_registry)Úagent_idr4   )r?   Ú,litellm.proxy.agent_endpoints.agent_registryr>   Úget_agent_by_idÚlitellm_paramsr1   r   r:   )r   r!   r?   r>   ÚagentrB   r4   r:   s           r   r-   z/_PROXY_MaxIterationsHandler._get_max_iterations—   sœ   € ð ,×4Ñ4ˆØÐõð 1×@Ñ@È(Ð@ÓSˆEØÐ Ø(-×(<Ñ(<Ò(BÀ�Ø!/×!3Ñ!3Ð4DÓ!E�Ø!Ð-Ü˜~Ó.Ð.ð ,×4Ñ4Ò:¸ˆØ!Ÿ™Ð&6Ó7ˆØÐ%Ü�~Ó&Ð&Ør    r3   c                 ó   — 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}:count© )r   r3   s     r   r/   z+_PROXY_MaxIterationsHandler._make_cache_key®   s   € ð ' z l°(Ð;Ð;r    r5   c              ƒ   ó,  K  — | j                   �3	 | j                  |g| j                  g¬«      ƒ d{  –—† }t        |«      S | j                  |«      ƒ d{  –—† S 7 Œ(# t        $ r)}t	        j
                  dt        |«      «       Y d}~ŒHd}~ww xY w7 Œ;­w)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   r5   ÚresultÚes       r   r0   z._PROXY_MaxIterationsHandler._increment_and_get·   sœ   è ø€ ð × Ñ Ð,ð
Ø&*×&;Ñ&;Ø#˜ØŸ(™(˜ð '<ó '÷ !�ô ˜6“{Ð"ð ×.Ñ.¨yÓ9×9Ð9ð!ùô
 ò Ü$×,Ñ,ØWÜ˜“F÷ñ ûðúð :úsJ   ‚B�"A ²A³A ÁBÁBÁBÁA Á	BÁ&B
ÂBÂ
BÂBc              ƒ   óê   K  — | j                   j                  |dd¬«      ƒ d{  –—† }|�t        |«      nddz   }| j                   j                  ||| j                  dd¬«      ƒ d{  –—†  |S 7 ŒJ7 Œ­w)z.Increment counter in in-memory cache with TTL.NT)ÚkeyÚlitellm_parent_otel_spanÚ
local_onlyr   é   )rO   Úvaluer   rP   rQ   )r   Úasync_get_cacher   Úasync_set_cacher   )r   r5   ÚcurrentÚ	new_values       r   rK   z0_PROXY_MaxIterationsHandler._in_memory_incrementÎ   s�   è ø€ à#×8Ñ8×HÑHØØ%)Øð  Ió  
÷ 
ˆð
 -4Ð,?œC œLÀQÈ!ÑKˆ	Ø×'Ñ'×7Ñ7ØØØ—‘Ø%)Øð 8ó 
÷ 	
ð 	
ð Ðð
øð	
ús"   ‚"A3¤A/¥AA3Á(A1Á)A3Á1A3)Ú__name__Ú
__module__Ú__qualname__Ú__doc__r   r   r
   r   Údictr<   rI   r8   r,   r   r-   r/   r0   rK   rE   r    r   r   r   3   s¿   „ ñð&
)Ð-?ó 
)ð3à)ð3ð ð3ð ð	3ð
 ð3ð 
�S‰˜4Ñ	 $Ñ	&ó3ðj Dð ¨S°4©Zó ð°^ð ÈÈdÉ
ó ð.<¨#ð <°#ó <ð:°#ð :¸#ó :ð.°Cð ¸Cô r    r   )r[   r   Útypingr   r   r   Úlitellmr   Úlitellm._loggingr   Úlitellm.exceptionsr   Ú"litellm.integrations.custom_loggerr	   Úlitellm.proxy._typesr
   Ú1litellm.proxy.common_utils.proxy_rate_limit_errorr   Ú&litellm.proxy.hooks.rate_limiter_utilsr   Úlitellm.proxy.utilsr   Ú_InternalUsageCacher   Ú__annotations__r   r   rE   r    r   ú<module>rh      sf   ðò
ó 
ß ,Ñ ,å Ý 1Ý ,Ý ;Ý /Ý QÝ VáÝMà,ÑàÐð
*Ð  ó 
ð %)Ð ˜EÓ (ôj ,õ jr    