Ë
    uPêjw(  ã                  óÒ   — U d Z ddlmZ ddlZddlZddl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mZmZ dd
lmZ dZded<   dd„Zdd„Z	 	 	 	 dd„Zdd„Z G d„ de«      Zy)u©  
Post-call hook for the adaptive router.

On each successful or failed completion, build a Turn from the request/response
and push it through `AdaptiveRouter.record_turn`. The router then updates the
in-memory bandit cell + session state and queues writes for the proxy flusher.

All work happens after the response has been returned to the caller. Any
exception is swallowed â€” signal recording must never break a request.
é    )ÚannotationsN)ÚAnyÚFinal)Úverbose_router_logger)ÚCustomLogger)ÚAdaptiveRouter)Úclassify_prompt)Ú ADAPTIVE_ROUTER_CHOSEN_MODEL_KEYÚADAPTIVE_ROUTER_RESPONSE_HEADERÚSIGNAL_GATE_MIN_MESSAGES)ÚTurn)Úuser_api_key_hashÚuser_api_key_team_idÚuser_api_key_user_idÚuser_api_key_end_user_idr   Ú_IDENTITY_FIELDSc           
     óÄ  ‡— | j                  d«      xs i }|j                  d«      }|rt        |«      S |j                  d«      xs i Št        ‰t        «      r1‰j                  d«      xs ‰j                  d«      }|rt        |«      S | j                  d«      xs g }t	        |«      t
        k  rydj                  ˆfd„t        D «       «      }|dt
         }|d	z   t        j                  |D �cg c]%  }|j                  d
«      |j                  d«      dœ‘Œ' c}dt        ¬«      z   }t        j                  |j                  d«      «      j                  «       S c c}w )u›  Pick a stable per-conversation key for owner-cache attribution.

    Order:
      1. Honor a client-supplied session id (`litellm_session_id` on either
         `litellm_params` or `litellm_params.metadata`, or `session_id` on
         metadata) â€” backward compat for callers already wired up.
      2. Otherwise derive a sha256 over (identity fields, first
         SIGNAL_GATE_MIN_MESSAGES messages) so the key is stable across turns
         and only materialises once there is enough context for the bandit to
         act on (matching the gate in the signal-processing path).

    Returns None if the conversation is shorter than SIGNAL_GATE_MIN_MESSAGES.
    Úlitellm_paramsÚlitellm_session_idÚmetadataÚ
session_idÚmessagesNú:c              3  ó~   •K  — | ]4  }t        ‰t        «      rt        ‰j                  |«      xs d «      nd –— Œ6 y­w)Ú N)Ú
isinstanceÚdictÚstrÚget)Ú.0Úfr   s     €úr/home/oem/CyberBrain26-unified/.venv/lib/python3.12/site-packages/litellm/router_strategy/adaptive_router/hooks.pyú	<genexpr>z'_resolve_session_key.<locals>.<genexpr>G   s3   øè ø€ ÒsÐ]^´jÀÌ4Ô6Pœ˜HŸL™L¨›OÒ1¨rÔ2ÐVXÓXÑsùs   ƒ:=ú|ÚroleÚcontent)r%   r&   T)Ú	sort_keysÚdefaultzutf-8)r   r   r   r   Úlenr   Újoinr   ÚjsonÚdumpsÚhashlibÚsha256ÚencodeÚ	hexdigest)	Úkwargsr   Úsidr   ÚidentityÚanchorÚmÚpayloadr   s	           @r"   Ú_resolve_session_keyr7   (   s:  ø€ ð #ŸJ™JÐ'7Ó8Ò>¸B€NØ
×
Ñ
Ð1Ó
2€CÙ
Ü�3‹xˆØ$×(Ñ(¨Ó4Ò:¸€HÜ�(œDÔ!Ø�l‰l˜<Ó(ÒN¨H¯L©LÐ9MÓ,NˆÙÜ�s“8ˆOà—j‘j Ó,Ò2°€HÜ
ˆ8ƒ}Ô/Ò/ð à�x‰xÓsÔbrÔsÓs€HØÐ6Ô6Ð7€FàØ
ñ	ä
�*‰*ØKQÖRÀa�a—e‘e˜F“m°·±°iÓ0@ÓAÒRØÜô
ñ	
ð ô �>‰>˜'Ÿ.™.¨Ó1Ó2×<Ñ<Ó>Ð>ùò Ss   Ã1*E
c                óB  — | sy t        | «      D ]Ž  }|j                  d«      dk(  sŒ|j                  d«      }t        |t        «      r|c S t        |t        «      rA|D ]<  }t        |t
        «      sŒ|j                  d«      dk(  sŒ)|j                  d«      c c S   y  y )Nr%   Úuserr&   ÚtypeÚtext)Úreversedr   r   r   Úlistr   )r   Úmsgr&   Úparts       r"   Ú_last_user_contentr@   U   s•   € ÙØÜ˜Ó!ò 
ˆØ�7‰7�6‹?˜fÓ$Ø—g‘g˜iÓ(ˆGÜ˜'¤3Ô'Ø’Ü˜'¤4Ô(à#ò 0�DÜ! $¬Õ-°$·(±(¸6Ó2BÀfÓ2LØ#Ÿx™x¨Ó/Ô/ð0ñ ð
ð ó    c                ó   — | sg S g }t        | «      D ]i  }t        |t        «      s nW|j                  d«      dk7  r nA|j                  d«      }t	        |j                  d«      «      }|j                  ||dœ«       Œk |j                  «        |S )u3  Extract the current turn's tool result payloads from the request messages.

    Tool results are `role == "tool"` messages that sit at the tail of the
    conversation â€” i.e. after the most recent assistant message with
    `tool_calls`, waiting for the model to produce a user-facing reply. Walk
    backwards from the end and collect the contiguous run of tool messages;
    stop at the first non-tool message.

    Each result is normalized to `{content, is_error}` â€” the only fields
    `signals._detect_failure` / `_detect_exhaustion` actually read.
    r%   Útoolr&   Úis_error)r&   rD   )r<   r   r   r   ÚboolÚappendÚreverse)r   Úresultsr>   r&   rD   s        r"   Ú_recent_tool_resultsrI   f   sŒ   € ñ Øˆ	Ø+-€GÜ˜Ó!ò 
CˆÜ˜#œtÔ$ÙØ�7‰7�6‹?˜fÒ$ÙØ—'‘'˜)Ó$ˆô ˜Ÿ™ 
Ó+Ó,ˆØ�‰ 7¸ÑAÕBð
Cð ‡O�OÔØ€NrA   c           	     ó¾  — | €dg fS 	 t        | dd«      xs | j                  d«      }|sdg fS |d   }t        |dd«      xs# t        |t        «      r|j                  d«      nd}|€dg fS t        |dd«      }|€!t        |t        «      r|j                  d«      }t        |dd«      }|€!t        |t        «      r|j                  d«      }g }|xs g D ]D  }t        |t        «      r|j                  |«       Œ%	 |j                  |j                  «       «       ŒF ||fS # t        $ r dg fcY S w xY w# t        $ r" |j                  t        |dd«      dd	œ«       Y Œˆw xY w)
zSReturn (assistant_text, tool_calls_list) extracted from a ModelResponse-ish object.NÚchoicesr   Úmessager&   Ú
tool_callsÚnamer   )rN   Ú	arguments)Úgetattrr   Ú	Exceptionr   r   rF   Ú
model_dump)Úresponse_objrK   r>   r&   Úraw_tool_callsrM   Útcs          r"   Ú!_assistant_content_and_tool_callsrV   †   s{  € àÐØ�RˆxˆðÜ  ¨y¸$Ó?Ò^À<×CSÑCSÐT]ÓC^ˆñ Ø�Rˆxˆà
�!‰*€CÜ
�#�y $Ó
'Ò
bÄ*ÈSÔRVÔBW¨C¯G©G°IÔ,>Ð]a€CØ
€{Ø�Rˆxˆä�c˜9 dÓ+€GØ€œ: c¬4Ô0Ø—'‘'˜)Ó$ˆä˜S ,°Ó5€NØÐ¤*¨S´$Ô"7ØŸ™ Ó.ˆØ.0€JØÒ" ò VˆÜ�bœ$ÔØ×Ñ˜bÕ!ðVØ×!Ñ! "§-¡-£/Õ2ðVð �JÐÐøô5 ò Ø�RˆxŠðûô0 ò VØ×!Ñ!¬7°2°v¸rÓ+BÐQSÑ"TÖUðVús#   ˆ D Ã9D1ÄD.Ä-D.Ä1(EÅEc                  ó`   — e Zd ZdZdd„Z	 	 d		 	 	 	 	 	 	 	 	 	 	 d
d„Zd„ Zd„ Z	 	 	 	 	 	 	 	 dd„Zy)ÚAdaptiveRouterPostCallHookzHOne hook instance per AdaptiveRouter. Registered into litellm.callbacks.c                ó   — || _         y )N)Úadaptive_router)ÚselfrZ   s     r"   Ú__init__z#AdaptiveRouterPostCallHook.__init__¬   s
   € Ø.ˆÕrA   Nc              ƒ  ó˜   K  — |j                  d«      xs i }t        |t        «      r|j                  t        «      nd}|syt        |iS ­w)u  
        Surface the chosen logical model as the `x-litellm-adaptive-router-model`
        response header for both streaming and non-streaming responses.

        `async_post_call_success_hook` fires after the stream is fully consumed,
        so writing to `_hidden_params["additional_headers"]` there is too late for
        streaming â€” the StreamingResponse headers are already frozen. This hook is
        called during header construction (before StreamingResponse is built), so
        the header is included for both paths.
        r   N)r   r   r   r
   r   )r[   ÚdataÚuser_api_key_dictÚresponseÚrequest_headersÚlitellm_call_infor   Úchosens           r"   Ú%async_post_call_response_headers_hookz@AdaptiveRouterPostCallHook.async_post_call_response_headers_hook¯   sI   è ø€ ð$ Ÿ(™( :Ó.Ò4°"ˆÜJTÐU]Ô_cÔJd˜Ÿ™Ô%EÔFÐjnˆÙØÜ/°Ð8Ð8ùs   ‚AA
c              ƒ  óH   K  — | j                  ||d¬«      ƒ d {  –—†  y 7 Œ­w)NéÈ   ©Úresponse_status)Ú_record)r[   r1   rS   Ú
start_timeÚend_times        r"   Úasync_log_success_eventz2AdaptiveRouterPostCallHook.async_log_success_eventÇ   s   è ø€ Ø�l‰l˜6 <ÀˆlÓE×EÒEús   ‚"š ›"c              ƒ  óÄ   K  — |j                  d«      }|€"|j                  d«      }|�t        |dd«      nd}| j                  ||t        |«      ¬«      ƒ d {  –—†  y 7 Œ­w)Nrh   Ú	exceptionÚstatus_codeiô  rg   )r   rP   ri   Úint)r[   r1   rS   rj   rk   ÚstatusÚexcs          r"   Úasync_log_failure_eventz2AdaptiveRouterPostCallHook.async_log_failure_eventÊ   sX   è ø€ Ø—‘Ð-Ó.ˆØˆ>ØŸ™ KÓ0ˆCØ9<¸”W˜S -°Ô5ÈcˆFØ�l‰l˜6 <ÄÀVÃˆlÓM×MÒMús   ‚AA ÁAÁA c              ƒ  óP  K  — 	 |j                  d«      xs g }t        |«      }|sy |j                  d«      xs i }|j                  d«      xs i }t        |t        «      r|j                  t        «      nd }|sy t        |«      }	t        |«      \  }
}t        |«      }t        |	xs d«      }t        |	t        |
t        «      r|
nd |||¬«      }| j                  j                  ||||¬«      ƒ d {  –—†  y 7 Œ# t        $ r }t        j                  d|«       Y d }~y d }~ww xY w­w)Nr   r   r   r   )Úuser_contentÚassistant_contentrM   Útool_resultsrh   )r   Ú
model_nameÚrequest_typeÚturnz5AdaptiveRouterPostCallHook: failed to record turn: %s)r   r7   r   r   r
   r@   rV   rI   r	   r   r   rZ   Úrecord_turnrQ   r   rn   )r[   r1   rS   rh   r   Úsession_keyr   r   Úcurrent_modelÚ	user_textÚassistant_textrM   rw   ry   rz   Úes                   r"   ri   z"AdaptiveRouterPostCallHook._recordÑ   s-  è ø€ ð$	hØ$Ÿj™j¨Ó4Ò:¸ˆHÜ!5°fÓ!=ˆKÙØð %+§J¡JÐ/?Ó$@Ò$FÀBˆNØ,×0Ñ0°Ó<ÒBÀˆHÜNXÐYaÔcgÔNh˜HŸL™LÔ)IÔJÐnrˆMÙ Øä1°(Ó;ˆIÜ)JÈ<Ó)XÑ&ˆN˜JÜ"6°xÓ"@ˆLä"1°)²/¸rÓ"BˆLÜØ&Ü5?ÀÔPSÔ5T¡>ÐZ^Ø%Ø)Ø /ôˆDð ×&Ñ&×2Ñ2Ø&Ø(Ø)Øð	 3ó ÷ ò ùô ò 	hÜ!×+Ñ+Ð,cÐef×gÑgûð	hüsX   ‚D&„"C: ¦D&§AC: Á:D&Á;A7C: Ã2C8Ã3C: Ã7D&Ã8C: Ã:	D#ÄDÄD&ÄD#Ä#D&)rZ   r   ÚreturnÚNone)NN)r^   údict[str, Any]r_   r   r`   r   ra   údict[str, str] | Nonerb   zdict[str, Any] | Noner�   r„   )r1   rƒ   rS   r   rh   rp   r�   r‚   )	Ú__name__Ú
__module__Ú__qualname__Ú__doc__r\   rd   rl   rs   ri   © rA   r"   rX   rX   ©   s�   „ ÙRó/ð 26Ø37ð9àð9ð ð9ð ð	9ð
 /ð9ð 1ð9ð 
ó9ò0FòNð*hàð*hð ð*hð ð	*hð
 
ô*hrA   rX   )r1   rƒ   r�   ú
str | None)r   úlist[dict[str, Any]] | Noner�   rŠ   )r   r‹   r�   zlist[dict[str, Any]])rS   r   r�   Útuple)rˆ   Ú
__future__r   r-   r+   Útypingr   r   Úlitellm._loggingr   Ú"litellm.integrations.custom_loggerr   Ú7litellm.router_strategy.adaptive_router.adaptive_routerr   Ú2litellm.router_strategy.adaptive_router.classifierr	   Ú.litellm.router_strategy.adaptive_router.configr
   r   r   Ú/litellm.router_strategy.adaptive_router.signalsr   r   Ú__annotations__r7   r@   rI   rV   rX   r‰   rA   r"   ú<module>r–      sz   ðò	õ #ã Û ß å 2Ý ;Ý RÝ N÷ñ õ
 Að
Ð �%ó ó*?óZð"Ø)ðàóó@ ôFRh õ RhrA   