Ë
    tPêjôð  ã                   ó  — U d Z ddlmZm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mZmZmZ ddlmZ dd	lmZmZmZm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!m"Z"m#Z#m$Z$m%Z%m&Z& ddl'm(Z(m)Z) ddl*m+Z+ ddl,m-Z- ddl.m/Z/m0Z0 ddl1m2Z2 ddl3m4Z4 ddl5m6Z6 ddl7m8Z8m9Z9m:Z: ddl;m<Z<m=Z=m>Z>m?Z?m@Z@mAZAmBZBmCZCmDZDmEZEmFZFmGZGmHZHmIZImJZJmKZKmLZL erddlMmNZNmOZOmPZPmQZQmRZR ddlSmTZT ddlUmVZV n	 ddlMmNZNmOZOmPZPmQZQmRZR  eN«       ZXeeYd<    G d„ de«      ZZ G d„ de«      Z[ G d „ d!e«      Z\ G d"„ d#e«      Z] G d$„ d%e«      Z^ G d&„ d'e«      Z_ G d(„ d)e«      Z` G d*„ d+e«      Zad,d-d.eZfd/„Zbd,d-d.e\fd0„Zcd,d-d.e^fd1„Zdd,d-d.e`fd2„Zed,d-d.eafd3„Zfd,d-d4egd5ehd.eeegehf      fd6„Zid7e&d8egd
z  d.d
fd9„Zjd:eFd.ekegd;f   fd<„Zld:eFd7e&d=d>d.d
fd?„ZmeXjÝ                  d@dAg eOe+«      geEeRjÞ                  ¬B«      dCeDd7ee& eOe+«      f   d.eEfdD„«       ZpeXjÝ                  dEdAg eOe+«      geCeRjÞ                  ¬B«      dCeBd7ee& eOe+«      f   d.eCfdF„«       Zq G dG„ dHe«      Zr eeser   «      ZteeYdI<   dJegd.efdK„ZudLevdMevd.evfdN„ZwdOexdPexd.e@fdQ„ZydRerd.e?fdS„ZzdRerd.e=fdT„Z{dUeer   d.erfdV„Z|dWehd.ekegegf   d
z  fdX„Z}d=dYdZe~ekegegf      d.eke=d;f   fd[„ZeX�j                  d\d] eOe+«      fe>¬^«      	 	 d£d7ee& eOe+«      f   d_eegd
z   eQd`¬a«      f   dbeegd
z   eQdc¬a«      f   d.e>fdd„«       Z�d7e&deegd.d
fdf„Z‚d7e&deegd.d
fdg„Zƒd=d>dhegd.e„fdi„Z…djegd.e„fdk„Z†d=dYdjegdlegdmeegd
z     d.d
f
dn„Z‡dmeegd
z     d.egfdo„Zˆ e~dpdqh«      Z‰ee~e8      eYdr<   d=dYdhegd.ekekegegf   d;f   fds„ZŠd=dYdCeLd8egd
z  d.ekekegegf   d;f   fdt„Z‹d=dYdCeLdmeegd
z     d.d
fdu„ZŒdve�d.e„fdw„ZŽ G dx„ dye«      Z� eese�   «      Z�eeYdz<   d{Z‘eeYd|<   d}e‘z   Z’eeYd~<   de‘z   Z“eeYd€<   d�e‘z   Z”eeYd‚<   dƒZ•eeYd„<   d…Z–eeYd†<   d‡Z—eeYdˆ<   d‰Z˜eeYdŠ<   d‹Z™eeYdŒ<    G d�„ dŽe«      Zš G d�„ d�e«      Z› eese›   «      ZœeeYd‘<   d’Z�eeYd“<   d”ZžeeYd•<    G d–„ d—e«      ZŸ eeseŸ   «      Z eeYd˜<   dLexdMexd.evfd™„Z¡dUee�   d.ekeKd;f   fdš„Z¢ G d›„ dœe«      Z£ eese£   «      Z¤eeYd�<   d,d-džee£   d.eege›f   fdŸ„Z¥d egdžee£   d¡eege›f   d.eIfd¢„Z¦d£Z§eekegd
z  egd
z  f      eYd¤<   d,d-d¥eeI   d.ekeId;f   fd¦„Z¨d,d-džee£   d.eJd
z  fd§„Z©eXjÝ                  d¨d] eOe+«      feIeR�jT                  ¬B«      dCeLd7ee& eOe+«      f   d.eIfd©„«       Z«eX�j                  dªd] eOe+«      feseI   ¬^«      	 	 d¸d7ee& eOe+«      f   d«eegd
z   eQd¬¬a«      f   d­eex eQd®d¯d°¬±«      f   d.ekeId;f   fd²„«       Z¬eX�j                  d³d] eOe+«      feI¬^«      d´egd7ee& eOe+«      f   d.eIfdµ„«       Z­eXjÝ                  d¶d] eOe+«      feI¬^«      d´egd7ee& eOe+«      f   d.eIfd·„«       Z®y
# eW$ r Y �Œw xY w)¹zð
AUTO ROUTER MANAGEMENT ENDPOINTS

POST /auto_router/test_routing - Route one request through an unsaved complexity-router config
POST /auto_router/validate_complexity_router_config - Dry-run the complexity-router write gate without saving
é    )ÚMappingÚSequence)ÚdatetimeÚ	timedeltaÚtimezone)Úgroupby)Ú
attrgetter)ÚMappingProxyType)ÚTYPE_CHECKINGÚ	AnnotatedÚFinalÚProtocol)Úuuid4)Ú	BaseModelÚ
ConfigDictÚTypeAdapterÚfield_validatorN)Úverbose_proxy_logger)ÚBudgetExceededError©Újudge_target)ÚCommonProxyErrorsÚLiteLLM_TeamTableÚLitellmUserRolesÚProxyErrorTypesÚProxyExceptionÚUserAPIKeyAuth)Ú_virtual_key_max_budget_checkÚcan_key_call_resolved_model)Úuser_api_key_auth)ÚAUTOROUTER_BENCHMARKS_SQL)ÚLiteLLMProxyRequestSetupÚ*refresh_proxy_server_request_body_snapshot)ÚSupportsModelDump)ÚTeamRepository)ÚComplexityRouter)ÚStrategyRouterDependencyRoleÚclassify_strategy_router_modelÚstrategy_router_dependencies)ÚSHADOW_EVAL_TURN_VALVEÚAutoRouterBenchmarkGroupÚAutoRouterBenchmarksResponseÚAutoRouterBenchmarkTotalsÚAutoRouterCacheBucketÚAutoRouterCacheStatsÚAutoRouterRoutingTestRequestÚAutoRouterRoutingTestResponseÚ'ComplexityRouterConfigValidationRequestÚ(ComplexityRouterConfigValidationResponseÚRequestComplexityRouterConfigÚShadowEvalDirectionÚShadowEvalJobKeyResponseÚShadowEvalJobResponseÚShadowEvalResultÚShadowEvalSliceÚStartShadowEvalRequest)Ú	APIRouterÚDependsÚHTTPExceptionÚQueryÚstatus)ÚPrismaClient)ÚRouterÚrouterc                   ó,   — e Zd Zdeeef   dedz  fd„Zy)Ú
_TeamTableÚwhereÚreturnNc             ƒ   ó   K  — y ­w©N© ©ÚselfrE   s     ú}/home/oem/CyberBrain26-unified/.venv/lib/python3.12/site-packages/litellm/proxy/management_endpoints/auto_router_endpoints.pyÚfind_uniquez_TeamTable.find_uniqueT   ó   è ø� ùó   ‚)Ú__name__Ú
__module__Ú__qualname__r   ÚstrÚobjectr$   rM   rI   ó    rL   rD   rD   S   s   „ Ø`¨'°#°v°+Ñ*>Ð`ÐCTÐW[ÑC[Ô`rU   rD   c                   óv   — e Zd Zedefd„«       Zededz  fd„«       Zededz  fd„«       Zededz  fd„«       Zy)Ú_VerificationTokenRowrF   c                  ó   — y rH   rI   ©rK   s    rL   Útokenz_VerificationTokenRow.tokenX   s   € ØrU   Nc                  ó   — y rH   rI   rY   s    rL   Ú	key_aliasz_VerificationTokenRow.key_alias[   s   € Ø'*rU   c                  ó   — y rH   rI   rY   s    rL   Úkey_namez_VerificationTokenRow.key_name^   s   € Ø&)rU   c                  ó   — y rH   rI   rY   s    rL   Úteam_idz_VerificationTokenRow.team_ida   s   € Ø%(rU   )	rP   rQ   rR   ÚpropertyrS   rZ   r\   r^   r`   rI   rU   rL   rW   rW   W   sZ   „ ØØ�sÒó ØàØ*˜3 ™:Ò*ó Ø*àØ)˜# ™*Ò)ó Ø)àØ(˜˜t™Ò(ó Ù(rU   rW   c                   óL   — e Zd Zdeeef   dedz  fd„Zdeeef   dee   fd„Z	y)Ú_VerificationTokenTablerE   rF   Nc             ƒ   ó   K  — y ­wrH   rI   rJ   s     rL   rM   z#_VerificationTokenTable.find_uniquef   rN   rO   c             ƒ   ó   K  — y ­wrH   rI   rJ   s     rL   Ú	find_manyz!_VerificationTokenTable.find_manyh   rN   rO   )
rP   rQ   rR   r   rS   rT   rW   rM   r   rf   rI   rU   rL   rc   rc   e   s;   „ Ød¨'°#°v°+Ñ*>ÐdÐCXÐ[_ÑC_Ódàe¨°°V°Ñ(<ÐeÀÐJ_ÑA`ÔerU   rc   c                   ó"   — e Zd Zedefd„«       Zy)Ú_ShadowEvalJobRowrF   c                  ó   — y rH   rI   rY   s    rL   Úidz_ShadowEvalJobRow.idl   s   € ØrU   N)rP   rQ   rR   ra   rS   rj   rI   rU   rL   rh   rh   k   s   „ ØØ�CÒó ÙrU   rh   c                   óL   — e Zd Zdeeef   dee   fd„Zdeeeef      de	fd„Z
y)Ú_ShadowEvalJobTablerE   rF   c             ƒ   ó   K  — y ­wrH   rI   rJ   s     rL   rf   z_ShadowEvalJobTable.find_manyq   rN   rO   Údatac              ƒ   ó   K  — y ­wrH   rI   )rK   rn   s     rL   Úcreate_manyz_ShadowEvalJobTable.create_manys   rN   rO   N)rP   rQ   rR   r   rS   rT   r   rh   rf   Úintrp   rI   rU   rL   rl   rl   p   s9   „ Øa¨°°V°Ñ(<ÐaÀÐJ[ÑA\ÓaàQ h¨w°s¸F°{Ñ/CÑ&DÐQÈÔQrU   rl   c                   ó(   — e Zd Zededz  fd„«       Zy)Ú_ShadowEvalAttemptRowrF   Nc                  ó   — y rH   rI   rY   s    rL   Úerrorz_ShadowEvalAttemptRow.errorw   s   € Ø#&rU   )rP   rQ   rR   ra   rS   ru   rI   rU   rL   rs   rs   v   s   „ ØØ&�s˜T‘zÒ&ó Ù&rU   rs   c                   ó0   — e Zd Zdeeeef      dedefd„Z	y)Ú_ShadowEvalFunnelTablern   Úskip_duplicatesrF   c              ƒ   ó   K  — y ­wrH   rI   )rK   rn   rx   s      rL   rp   z"_ShadowEvalFunnelTable.create_many|   rN   rO   N)
rP   rQ   rR   r   r   rS   rT   Úboolrq   rp   rI   rU   rL   rw   rw   {   s$   „ Øh h¨w°s¸F°{Ñ/CÑ&DÐhÐW[ÐhÐ`cÔhrU   rw   c                   ó:   — e Zd Zdeeef   deeef   dedz  fd„Zy)Ú_ShadowEvalAttemptTablerE   ÚorderrF   Nc             ƒ   ó   K  — y ­wrH   rI   )rK   rE   r}   s      rL   Ú
find_firstz"_ShadowEvalAttemptTable.find_first€   s	   è ø€ à'*ùrO   )rP   rQ   rR   r   rS   rT   rs   r   rI   rU   rL   r|   r|      s6   „ ð+Ø  V Ñ,ð+Ø5<¸SÀ#¸XÑ5Fð+à	 Ñ	%ô+rU   r|   Úprisma_clientr@   rF   c                 ó,   — t        | «      j                  S rH   )r%   Útable©r€   s    rL   Ú_team_tabler„   …   s   € Ü˜-Ó(×.Ñ.Ð.rU   c                 ó.   — | j                   j                  S rH   )ÚdbÚlitellm_verificationtokenrƒ   s    rL   Ú_verification_tokensrˆ   ‰   ó   € Ø×Ñ×5Ñ5Ð5rU   c                 ó.   — | j                   j                  S rH   )r†   Úlitellm_shadowevaljobrƒ   s    rL   Ú_shadow_eval_jobsrŒ   �   s   € Ø×Ñ×1Ñ1Ð1rU   c                 ó.   — | j                   j                  S rH   )r†   Úlitellm_shadowevalfunnelrƒ   s    rL   Ú_shadow_eval_funnelr�   ‘   s   € Ø×Ñ×4Ñ4Ð4rU   c                 ó.   — | j                   j                  S rH   )r†   Úlitellm_shadowevalattemptrƒ   s    rL   Ú_shadow_eval_attemptsr’   •   r‰   rU   ÚqueryÚargsc              ‡   óX   K  —  | j                   j                  |g|¢­Ž ƒ d {  –—† S 7 Œ­wrH   )r†   Ú	query_raw)r€   r“   r”   s      rL   Ú
_query_rawr—   ™   s*   è ø€ Ø+�×!Ñ!×+Ñ+¨EÐ9°DÒ9×9Ð9Ð9ús   ‚!*£(¤*Úuser_api_key_dictr`   c              ƒ   óØ  K  — ddl m} ddlm}m} | j
                  t        j                  k(  ry|€t        ddd| j
                  › d�i¬	«      ‚|€'t        d
dt        j                  j                  i¬	«      ‚t        |«      j                  d|i¬«      ƒ d{  –—† }|€t        ddd|› d�i¬	«      ‚|j                  || t        j                   |j#                  «       «      |¬«       y7 ŒP­w)a–  Allow exactly the callers who could create this router.

    Both dry runs are gated like the write they rehearse rather than as reads: a proxy
    admin, or a team admin naming their own team, matching /model/new. Routing a test
    prompt can also spend money (an `llm` classifier config calls its classifier, a
    semantic config embeds the prompt), so a read-level gate would be too loose anyway.
    r   )ÚModelManagementAuthChecks)Úpremium_userr€   Né“  ru   zCUser does not have permission to dry-run an auto router. Your role=zD. Call as a PROXY_ADMIN, or as a team admin by specifying a team_id.©Ústatus_codeÚdetailéô  r`   ©rE   é�  zTeam id=z does not exist in db)r`   r˜   Úteam_objr›   )Ú=litellm.proxy.management_endpoints.model_management_endpointsrš   Úlitellm.proxy.proxy_serverr›   r€   Ú	user_roler   ÚPROXY_ADMINr=   r   Údb_not_connected_errorÚvaluer„   rM   Úcan_user_make_team_model_callr   Úmodel_validateÚ
model_dump)r˜   r`   rš   r›   r€   Úteam_rows         rL   Ú_authorize_router_dry_runr®   �   s!  è ø€ õ÷ Gà×"Ñ"Ô&6×&BÑ&BÒBØà€ÜØàÐ^Ð_p×_zÑ_zÐ^{ð  |@ð  Aðô
ð 	
ð ÐÜØàÔ*×AÑA×GÑGðô
ð 	
ô (¨Ó6×BÑBØ˜'Ð"ð Có ÷ €Hð ÐÜØà˜8 G 9Ð,AÐBðô
ð 	
ð ×;Ñ;ØØ+Ü"×1Ñ1°(×2EÑ2EÓ2GÓHØ!ð	 <õ ðús   ‚BC*ÂC(ÂAC*Úconfig.c                 ó¶   — t        d„ | j                  r"| j                  �| j                  j                  nd| j                  r| j
                  ndfD «       «      S )z¨The models the routing test itself would send a request to, and so spend on.

    Excludes every tier's models: the prompt is never sent to the model it routed to.
    c              3   ó$   K  — | ]  }|�|–— Œ
 y ­wrH   rI   )Ú.0Úmodels     rL   ú	<genexpr>z-_models_this_test_can_call.<locals>.<genexpr>Õ   s    è ø€ ò 	àð Ðô 	ñ	ùs   ‚N)ÚtupleÚuses_llm_classifierÚclassifier_llm_configr³   Úsemantic_keyword_matchingÚembedding_model)r¯   s    rL   Ú_models_this_test_can_callrº   Ð   s]   € ô
 ñ 	ð ×)Ò)¨f×.JÑ.JÐ.Vð ×(Ñ(×.Ò.àØ&,×&FÒ&FˆF×"Ò"ÈDð	
ô	ó 	ð 	rU   Ú
llm_routerrA   c              ƒ   óF  K  — t        | «      }|syddlm} |D ]#  }t        ||j                  ||¬«      ƒ d{  –—†  Œ% 	 t        ||¬«      ƒ d{  –—†  y7 Œ7 Œ# t        $ r;}t        |j                  t        j                  dt        j                  ¬«      |‚d}~ww xY w­w)a'  Hold a classifier or embedding call to the caller's model access and key budget.

    Those calls go through the router rather than through /v1/chat/completions, so the model
    checks a real request gets in user_api_key_auth would otherwise be skipped, letting a
    caller spend on a model their key cannot call, and this route is not an LLM API route, so
    the key's own budget is not checked either. Test Connection gets both for free by routing
    its calls through the proxy. Team and member budgets are already enforced on every route.
    Nr   )Úproxy_logging_obj)r³   Úllm_model_listÚvalid_tokenr»   )r¿   r½   )ÚmessageÚtypeÚparamÚcode)rº   r¥   r½   r   Ú
model_listr   r   r   rÀ   r   Úbudget_exceededr?   ÚHTTP_400_BAD_REQUEST)r¯   r˜   r»   Úmodelsr½   r³   Úes          rL   Ú$_authorize_models_this_test_can_callrÉ   á   sµ   è ø€ ô /¨vÓ6€FÙØå<àò 
ˆÜ)ØØ%×0Ñ0Ø)Ø!ô	
÷ 	
ñ 	
ð
ðÜ+Ø)Ø/ô
÷ 	
ñ 	
ð	
øð	
ùô ò ÜØ—I‘IÜ ×0Ñ0ØÜ×,Ñ,ô	
ð
 ð	ûðüsE   ‚6B!¸A¹B!Á A ÁAÁA ÁB!ÁA Á	BÁ#6BÂBÂB!z./auto_router/validate_complexity_router_configzmodel management)ÚtagsÚdependenciesÚresponse_modelrž   rn   c              ƒ   óš   K  — t        || j                  ¬«      ƒ d{  –—†  ddlm}  || j                  «      }t        |du |¬«      S 7 Œ+­w)a›  
    Validate a complexity-router config without saving it.

    Runs the same check every write path runs (the router's own pydantic model), so a form can
    show the backend's exact verdict while the operator is still editing rather than after a
    rejected save. Gated exactly like the save it rehearses: a proxy admin, or a team admin
    naming their own team. Nothing is created, routed, or billed.
    ©r˜   r`   Nr   )Ú'validate_complexity_router_config_write)Úvalidru   )r®   r`   Ú-litellm.router_utils.auto_router_model_namingrÏ   Úcomplexity_router_configr3   )rn   r˜   rÏ   ru   s       rL   Ú!validate_complexity_router_configrÓ   
  sN   è ø€ ô& $Ð6GÐQU×Q]ÑQ]Ô
^×^Ð^õñ ;¸4×;XÑ;XÓY€EÜ3¸%À4¸-ÈuÔUÐUð _ús   ‚A�A	ž,Az/auto_router/test_routingc           
   ƒ   ó¾  K  — ddl m}m}m}m}m}m} ddlm} t        || j                  ¬«      ƒ d{  –—†  |€'t        ddt        j                  j                  i¬«      ‚t        | j                   ||¬	«      ƒ d{  –—†  t#        | j$                  || j                   j'                  d
¬«      | j(                  d¬«      }	t+        j,                  i | j/                  «       ¥i ddidœ¥|d¬«      }
t1        |
«       	 |	j3                  | j$                  |
|
d   ¬«      ƒ d{  –—† }|�|j:                  €t        dddi¬«      ‚ |||||||| j                  |¬«      ƒ d{  –—† }t=        |j>                  |j>                  tA        |«      v |j:                  ¬«      S 7 �Œf7 �Œ 7 Œ€# t4        $ r.}t7        j8                  d|«       t        ddd|› �i¬«      |‚d}~ww xY w7 Œ}­w)a4  
    Route a single request through a complexity-router config and report where it landed.

    Answers "which model would this request get?" for a config that only exists in a form,
    so an auto router can be checked before it is created. The request is classified by the
    same pre-routing hook a live request runs, over the same messages, system prompt and tool
    definitions, then dropped: nothing is sent to the model it routed to, and no auto router is
    created. A heuristic config therefore spends nothing, while an `llm` classifier or semantic
    keyword matching bills its classifier/embedding call to the calling key, like Test Connection
    does.

    Send `messages` to classify a real turn, with `system` and `tools` beside it when the surface
    carries them top level, as Anthropic /v1/messages does. `prompt` is the single-ask shorthand and
    routes as one user turn with nothing around it.

    **Example Request:**
    ```json
    {
        "messages": [
            {"role": "system", "content": "You are a database migration assistant"},
            {"role": "user", "content": "the index is not unique"},
            {"role": "assistant", "content": "Then two workers can both insert. Add a unique index"},
            {"role": "user", "content": "ok do it"}
        ],
        "tools": [{"type": "function", "function": {"name": "Bash", "description": "Run a command"}}],
        "complexity_router_config": {
            "tiers": {"SIMPLE": ["gpt-4o-mini"], "REASONING": ["o3"]},
            "classifier_type": "heuristic"
        }
    }
    ```
    r   )Úgeneral_settingsr»   r€   r½   Úuser_api_key_cacheÚ
user_model)Úget_available_models_for_userrÎ   Nr    ru   r�   )r¯   r˜   r»   T)Úexclude_noneF)Ú
model_nameÚlitellm_router_instancerÒ   Údefault_modelÚderive_savings_baselineÚbody)ÚmetadataÚproxy_server_requestrß   )rn   r˜   Ú_metadata_variable_nameÚmessages)r³   Úrequest_kwargsrâ   z2Auto router routing test failed. Due to error - %sr¢   zCould not route this prompt: zVThe router made no decision for this prompt. Check that at least one tier has a model.)r˜   r»   rÕ   r×   r€   r½   r`   rÖ   )Úrouted_modelÚrouted_model_configuredÚrouting_decision)!r¥   rÕ   r»   r€   r½   rÖ   r×   Úlitellm.proxy.utilsrØ   r®   r`   r=   r   Úno_llm_routerr©   rÉ   rÒ   r&   Úrouter_namer¬   rÜ   r"   Ú)add_user_api_key_auth_to_request_metadataÚ	wire_bodyr#   Úasync_pre_routing_hookÚ	Exceptionr   Ú	exceptionræ   r1   r³   Ú	frozenset)rn   r˜   rÕ   r»   r€   r½   rÖ   r×   rØ   Úcomplexity_routerrã   Úhook_responserÈ   Úavailable_modelss                 rL   Úpreview_auto_router_routingró   '  s,  è ø€ ÷V÷ õ Bä
#Ð6GÐQU×Q]ÑQ]Ô
^×^Ð^àÐÜØàÔ*×8Ñ8×>Ñ>ðô
ð 	
ô /Ø×,Ñ,Ø+Øô÷ ð ô  0Ø×#Ñ#Ø *Ø!%×!>Ñ!>×!IÑ!IÐW[Ð!IÓ!\Ø×(Ñ(Ø %ô Ðô 5×^Ñ^ð
Ø�n‰nÓð
àØ%+¨T Nò
ð
 ,Ø *ô€Nô /¨~Ô>ðØ%6×%MÑ%MØ×"Ñ"Ø)Ø# JÑ/ð &Nó &
÷  
ˆð Ð × >Ñ >Ð FÜØàÐqðô
ð 	
ñ %BØ+ØØ)ØØ#Ø+Ø—‘Ø-ô	%÷ 	Ðô )Ø"×(Ñ(Ø -× 3Ñ 3´yÐAQÓ7RÐ RØ&×7Ñ7ôð ðI _ùðùð4 
ùô
 ò Ü×&Ñ&Ð'[Ð]^Ô_ÜØàÐ8¸¸Ð<ðô
ð
 ð	ûðúð"	úse   ‚1G³F´AGÁ<FÁ=A<GÃ:$F! ÄFÄF! Ä#:GÅGÅ<GÆGÆF! Æ!	GÆ*)GÇGÇGc                   óô   — e Zd ZU eed<   eed<   eeef   ed<   eed<   eed<   eed<   eed<   eed<   eed	<   eed
<   eed<   eed<   eed<   eed<   eed<   eed<   eed<   eed<   eed<   eed<   eed<   eed<   y)Ú_SessionAggRowré   Úrouter_typeÚ
tier_turnsÚsessionsÚturnsÚunordered_turnsÚcovered_turnsÚ
cache_hitsÚsame_model_turnsÚsame_model_hitsÚfirst_visit_turnsÚfirst_visit_hitsÚreturn_turnsÚreturn_hitsÚreturn_expired_missesÚreturn_within_ttl_missesÚttl_5m_turnsÚttl_1h_turnsÚtotal_tokensÚspendÚsaved_spendÚsession_secondsN)rP   rQ   rR   rS   Ú__annotations__r   rq   ÚfloatrI   rU   rL   rõ   rõ   §  s‹   … ØÓØÓØ˜˜S˜Ñ!Ó!ØƒMØƒJØÓØÓØƒOØÓØÓØÓØÓØÓØÓØÓØ!Ó!ØÓØÓØÓØƒLØÓØÔrU   rõ   Ú_SESSION_AGG_ROWSr©   c                 óÌ   — 	 t        j                  | d«      j                  t        j                  ¬«      }|j                  d ¬«      S # t
        $ r t        dd| › d�¬«      ‚w xY w)Nú%Y-%m-%d©Útzinfor¢   zInvalid date format: z. Expected: 'YYYY-MM-DD'r�   )r   ÚstrptimeÚreplacer   ÚutcÚ
ValueErrorr=   )r©   Úparseds     rL   Ú_parse_benchmark_dayr  Ã  sj   € ðmÜ ×)Ñ)¨%°Ó<×DÑDÌHÏLÉLÐDÓYˆð �>‰> ˆ>Ó&Ð&øô ò mÜ¨Ð6KÈEÈ7ÐRjÐ4kÔlÐlðmús   ‚4A ÁA#Ú	numeratorÚdenominatorc                 ó2   — |dk  ryt        d| z  |z  d«      S )Nr   ç        g      Y@é   )Úround©r  r  s     rL   Ú_pctr  Ë  s$   € Ø�aÒØÜ�˜Ñ" [Ñ0°!Ó4Ð4rU   rù   Úhitsc                 ó2   — t        | |t        || «      ¬«      S )N)rù   r   Úhit_rate_pct)r.   r  )rù   r   s     rL   Ú_cache_bucketr#  Ñ  s   € Ü  u°4ÄdÈ4ÐQVÓFWÔXÐXrU   Úrowc                 ó–  — | j                   | j                  z
  }| j                  | j                  z   }| j                  }t        || j                  |r| j                  |z  nd|r| j                  |z  nd|r| j                  |z  nd| j                  | j                  |t        | j                  |«      |r| j                  |z  ndt        t        | j                  | j                  «      t        | j                  | j                  «      t        | j                  | j                  «      t        | j                   | j"                  «      t        | j                   | j                  «      | j$                  | j&                  | j(                  t+        || j&                  z
  | j(                  z
  d«      | j,                  | j.                  ¬«      ¬«      S )Nr  r   )Úcoverage_pctr"  Ú
same_modelÚfirst_visitÚreturn_to_tierrú   Úreturn_misses_expiredÚreturn_misses_within_ttlÚreturn_misses_unknownr  r  )rø   rù   Úavg_turns_per_sessionÚavg_session_secondsÚavg_tokens_per_sessionr  r	  Úbaseline_spendÚ	saved_pctÚsaved_per_sessionÚcache)r  r  r  r	  rø   r-   rù   r
  r  r  r/   rû   rü   r#  rý   rþ   rÿ   r   rú   r  r  Úmaxr  r  )r$  Úreturn_missesr0  rø   s       rL   Ú_benchmark_totalsr6  Õ  sj  € Ø×+Ñ+¨c¯o©oÑ=€MØŸI™I¨¯©Ñ7€NØ—l‘l€HÜ$ØØ�i‰iÙ6>˜cŸi™i¨(Ò2ÀCÙ>F˜C×/Ñ/°(Ò:ÈCÙ>F˜s×/Ñ/°(Ò:ÈCØ�i‰iØ—O‘OØ%Ü�s—‘¨Ó7Ù8@˜#Ÿ/™/¨HÒ4ÀcÜ"Ü˜c×/Ñ/°·±Ó;Ü˜cŸn™n¨c×.?Ñ.?Ó@Ü$ S×%9Ñ%9¸3×;NÑ;NÓOÜ% c×&;Ñ&;¸S×=QÑ=QÓRÜ(¨×)9Ñ)9¸3¿?¹?ÓKØ×/Ñ/Ø"%×";Ñ";Ø%(×%AÑ%AÜ"% m°c×6OÑ6OÑ&OÐRU×RnÑRnÑ&nÐpqÓ"rØ×)Ñ)Ø×)Ñ)ô
ôð rU   c                 ób  — t        | «      }t        | j                  | j                  | j                  |j
                  |j                  |j                  |j                  |j                  |j                  |j                  |j                  |j                  |j                  |j                  ¬«      S )N)ré   rö   r÷   rø   rù   r-  r.  r/  r  r	  r0  r1  r2  r3  )r6  r+   ré   rö   r÷   rø   rù   r-  r.  r/  r  r	  r0  r1  r2  r3  )r$  Útotalss     rL   Ú_benchmark_groupr9  ô  s‰   € Ü% cÓ*€FÜ#Ø—O‘OØ—O‘OØ—>‘>Ø—‘Ø�l‰lØ$×:Ñ:Ø"×6Ñ6Ø%×<Ñ<Ø�l‰lØ×&Ñ&Ø×,Ñ,Ø×"Ñ"Ø ×2Ñ2Ø�l‰lôð rU   Úrowsc           	      ó
  — t        d+i dd“dd“dt        i «      “dt        d„ | D «       «      “dt        d„ | D «       «      “d	t        d
„ | D «       «      “dt        d„ | D «       «      “dt        d„ | D «       «      “dt        d„ | D «       «      “dt        d„ | D «       «      “dt        d„ | D «       «      “dt        d„ | D «       «      “dt        d„ | D «       «      “dt        d„ | D «       «      “dt        d„ | D «       «      “dt        d„ | D «       «      “dt        d „ | D «       «      “d!t        d"„ | D «       «      “d#t        d$„ | D «       «      “d%t        d&„ | D «       «      “d't        d(„ | D «       «      “d)t        d*„ | D «       «      “ŽS ),Nré   Ú rö   r÷   rø   c              3   ó4   K  — | ]  }|j                   –— Œ y ­wrH   )rø   ©r²   r$  s     rL   r´   z"_summed_agg_row.<locals>.<genexpr>  s   è ø€ Ò2 c�S—\•\Ñ2ùó   ‚rù   c              3   ó4   K  — | ]  }|j                   –— Œ y ­wrH   )rù   r>  s     rL   r´   z"_summed_agg_row.<locals>.<genexpr>  ó   è ø€ Ò, �#—)•)Ñ,ùr?  rú   c              3   ó4   K  — | ]  }|j                   –— Œ y ­wrH   )rú   r>  s     rL   r´   z"_summed_agg_row.<locals>.<genexpr>  ó   è ø€ Ò@°C˜C×/Õ/Ñ@ùr?  rû   c              3   ó4   K  — | ]  }|j                   –— Œ y ­wrH   )rû   r>  s     rL   r´   z"_summed_agg_row.<locals>.<genexpr>  s   è ø€ Ò<°˜#×+Õ+Ñ<ùr?  rü   c              3   ó4   K  — | ]  }|j                   –— Œ y ­wrH   )rü   r>  s     rL   r´   z"_summed_agg_row.<locals>.<genexpr>  s   è ø€ Ò6¨#�s—~•~Ñ6ùr?  rý   c              3   ó4   K  — | ]  }|j                   –— Œ y ­wrH   )rý   r>  s     rL   r´   z"_summed_agg_row.<locals>.<genexpr>  ó   è ø€ ÒB°c˜S×1Õ1ÑBùr?  rþ   c              3   ó4   K  — | ]  }|j                   –— Œ y ­wrH   )rþ   r>  s     rL   r´   z"_summed_agg_row.<locals>.<genexpr>  rC  r?  rÿ   c              3   ó4   K  — | ]  }|j                   –— Œ y ­wrH   )rÿ   r>  s     rL   r´   z"_summed_agg_row.<locals>.<genexpr>  s   è ø€ ÒD¸˜c×3Õ3ÑDùr?  r   c              3   ó4   K  — | ]  }|j                   –— Œ y ­wrH   )r   r>  s     rL   r´   z"_summed_agg_row.<locals>.<genexpr>  rG  r?  r  c              3   ó4   K  — | ]  }|j                   –— Œ y ­wrH   )r  r>  s     rL   r´   z"_summed_agg_row.<locals>.<genexpr>  ó   è ø€ Ò:¨c˜×)Õ)Ñ:ùr?  r  c              3   ó4   K  — | ]  }|j                   –— Œ y ­wrH   )r  r>  s     rL   r´   z"_summed_agg_row.<locals>.<genexpr>  ó   è ø€ Ò8¨C˜Ÿ�Ñ8ùr?  r  c              3   ó4   K  — | ]  }|j                   –— Œ y ­wrH   )r  r>  s     rL   r´   z"_summed_agg_row.<locals>.<genexpr>  s   è ø€ Ò!LÀ #×";Õ";Ñ!Lùr?  r  c              3   ó4   K  — | ]  }|j                   –— Œ y ­wrH   )r  r>  s     rL   r´   z"_summed_agg_row.<locals>.<genexpr>  s   è ø€ Ò$RÀc S×%AÕ%AÑ$Rùr?  r  c              3   ó4   K  — | ]  }|j                   –— Œ y ­wrH   )r  r>  s     rL   r´   z"_summed_agg_row.<locals>.<genexpr>  rL  r?  r  c              3   ó4   K  — | ]  }|j                   –— Œ y ­wrH   )r  r>  s     rL   r´   z"_summed_agg_row.<locals>.<genexpr>  rL  r?  r  c              3   ó4   K  — | ]  }|j                   –— Œ y ­wrH   )r  r>  s     rL   r´   z"_summed_agg_row.<locals>.<genexpr>  rL  r?  r  c              3   ó4   K  — | ]  }|j                   –— Œ y ­wrH   )r  r>  s     rL   r´   z"_summed_agg_row.<locals>.<genexpr>  rA  r?  r	  c              3   ó4   K  — | ]  }|j                   –— Œ y ­wrH   )r	  r>  s     rL   r´   z"_summed_agg_row.<locals>.<genexpr>  rN  r?  r
  c              3   ó4   K  — | ]  }|j                   –— Œ y ­wrH   )r
  r>  s     rL   r´   z"_summed_agg_row.<locals>.<genexpr>  rC  r?  rI   )rõ   r
   Úsum©r:  s    rL   Ú_summed_agg_rowrY    s½  € Üò Ùðáðô $ BÔ'ðô Ñ2¨TÔ2Ô2ð	ô
 Ñ, tÔ,Ô,ðô Ñ@¸4Ô@Ô@ðô Ñ<°tÔ<Ô<ðô Ñ6°Ô6Ô6ðô ÑB¸TÔBÔBðô Ñ@¸4Ô@Ô@ðô ÑD¸tÔDÔDðô ÑB¸TÔBÔBðô Ñ:°TÔ:Ô:ðô Ñ8°4Ô8Ô8ðô "Ñ!LÀtÔ!LÔLðô  "%Ñ$RÈTÔ$RÔ!Rð!ô" Ñ:°TÔ:Ô:ð#ô$ Ñ:°TÔ:Ô:ð%ô& Ñ:°TÔ:Ô:ð'ô( Ñ, tÔ,Ô,ð)ô* Ñ8°4Ô8Ô8ð+ô, Ñ@¸4Ô@Ô@ð-ð rU   Ú
deploymentc                 ó"  — t        | t        «      sy| j                  d«      }| j                  d«      }t        |t        «      rt        |t        «      r|sy|j                  d«      }t        |t        «      syt	        |«      }|�|dk(  rdS ||fS )a)  ``(model_name, kind)`` for a deployment whose routing the session rollup records.

    Kinds come from ``classify_strategy_router_model``, the same rule the Router registers a
    deployment by, so this arm cannot disagree with the arm that stamped ``router_type`` onto
    the session rows. Semantic auto-routers return None: they record no routing decision, so
    they can never own a session row, and ``AutoRouterBenchmarkGroup.router_type`` has no
    value for them. A permanent zero would read as "no traffic" rather than "not instrumented".
    NÚlitellm_paramsrÚ   r³   Úsemantic)Ú
isinstancer   ÚgetrS   r(   )rZ  r\  ré   r³   Úkinds        rL   Ú_strategy_router_keyra  #  s‰   € ô �j¤'Ô*ØØ&ŸN™NÐ+;Ó<€NØ#Ÿ™¨Ó5€KÜ�~¤wÔ/´J¸{ÌCÔ4PÑU`ØØ!×%Ñ% gÓ.€EÜ�eœSÔ!ØÜ0°Ó7€DØ�< 4¨:Ò#5ˆ4ÐN¸KÈÐ;NÐNrU   zRouter | NoneÚcoveredc                 ó®   ‡‡— | €yt        d«      Št        ˆfd„d„ | j                  xs dD «       D «       «      }t        ˆfd„t	        |«      D «       «      S )a  Zeroed groups for configured strategy routers the window's traffic did not cover.

    The dashboard's router picker has to list a router the moment it is created rather than
    once it has spent something, so the registry drives the list and the rollup only supplies
    the measures. ``_summed_agg_row`` over no sessions is already the zero element of the
    fold, so a group with every measure at zero costs one relabel rather than a literal that
    would go stale the next time the response grows a field.
    rI   c              3   ó.   •K  — | ]  }|�|‰vr|–— Œ y ­wrH   rI   )r²   Úkeyrb  s     €rL   r´   z&_idle_router_groups.<locals>.<genexpr>G  s%   øè ø€ ò àØˆ?˜s¨'Ñ1ô 	ñùó   ƒc              3   ó2   K  — | ]  }t        |«      –— Œ y ­wrH   )ra  )r²   rZ  s     rL   r´   z&_idle_router_groups.<locals>.<genexpr>I  s   è ø€ Òc¸Ô(¨×4Ñcùó   ‚c           
   3   ór   •K  — | ].  \  }}t        ‰j                  t        ||d œ«      ¬«      «      –— Œ0 y­w)©ré   rö   ©ÚupdateN)r9  Ú
model_copyr
   )r²   Únamer`  Úzeros      €rL   r´   z&_idle_router_groups.<locals>.<genexpr>L  s:   øè ø€ ò áˆD�$ô 	˜Ÿ™Ô0@ÐQUÐfjÑAkÓ0l˜Óm×nñùs   ƒ47)rY  rï   rÄ   rµ   Úsorted)r»   rb  Úidlero  s    ` @rL   Ú_idle_router_groupsrr  9  s`   ù€ ð ÐØÜ! "Ó%€DÜó ácÀz×G\ÑG\ÒGbÐ`bÔcôó €Dô
 ó ä  ›,ôó ð rU   z/auto_router/benchmarks)zauto router)rÊ   rË   rÌ   Ú
start_datez?YYYY-MM-DD UTC, inclusive (defaults to 30 days before end_date))ÚdescriptionÚend_datez-YYYY-MM-DD UTC, inclusive (defaults to today)c           
   ƒ   ó  K  — ddl m}m} t        | d«       |€%t	        dt
        j                  j                  ¬«      ‚|rt        |«      n6t        j                  t        j                  «      j                  ddddd¬«      }|rt        |«      n|t        d¬	«      z
  }||k  rt	        d
d¬«      ‚t        |t         |j#                  «       |t        d¬	«      z   j#                  «       «      ƒ d{  –—† }t$        j'                  |xs d«      }g d„ |D «       ¢t)        |t+        d„ |D «       «      «      ¢­}	t-        |j/                  d«      |j/                  d«      t1        |	«      t3        t5        |«      «      |	¬«      S 7 ŒŽ­w)a[  
    Benchmarks for the auto-router dashboard: session shape, savings against the configured
    baseline, and prompt-caching behaviour bucketed by what the router did.

    Reads the LiteLLM_AutoRouterSession rollup, folded once per request at spend-write time,
    so this endpoint never scans LiteLLM_SpendLogs. A session is in the window when it
    overlaps it: its last turn is on or after start_date and its first turn is on or before
    end_date. Overall hit rate is over telemetry-bearing turns; each bucket's hit rate is
    over that bucket's turns.

    The rollup supplies the measures, never the list. Which routers appear comes from the
    model registry, so one shows up as soon as it is configured and reads zero until it
    serves traffic, and `routers_in_scope` counts those too rather than only the routers the
    window recorded.
    r   ©r»   r€   z1view auto-router benchmarks across the deploymentNr    r�   )ÚhourÚminuteÚsecondÚmicrosecondr  é   ©Údaysr¢   z,end_date must not be earlier than start_dater  rI   c              3   ó2   K  — | ]  }t        |«      –— Œ y ­wrH   )r9  r>  s     rL   r´   z-get_auto_router_benchmarks.<locals>.<genexpr>…  s   è ø€ Ò	0 CÔ
˜3×
Ñ	0ùrh  c              3   óL   K  — | ]  }|j                   |j                  f–— Œ y ­wrH   rj  r>  s     rL   r´   z-get_auto_router_benchmarks.<locals>.<genexpr>†  s   è ø€ Ò2fÐZ]°C·O±OÀSÇ_Á_Ô3UÑ2fùs   ‚"$r  )rs  ru  Úrouters_in_scoper8  Úgroups)r¥   r»   r€   Ú_require_admin_viewerr=   r   r¨   r©   r  r   Únowr   r  r  r   r—   r!   Ú	isoformatr  Úvalidate_pythonrr  rï   r,   ÚstrftimeÚlenr6  rY  )
r˜   rs  ru  r»   r€   Úend_dayÚ	start_dayÚraw_rowsr:  r‚  s
             rL   Úget_auto_router_benchmarksrŒ  R  sk  è ø€ ÷8 EäÐ+Ð-`ÔaØÐÜ¨Ô4E×4\Ñ4\×4bÑ4bÔcÐcñ ô 	˜XÔ&ä�\‰\œ(Ÿ,™,Ó'×/Ñ/°Q¸qÈÐXYÐbfÐ/Ógð ñ
 <FÔ+¨JÔ7È7ÔU^ÐdfÔUgÑKg€IØ�ÒÜ¨Ð4bÔcÐcä&ØÜ!Ø×ÑÓØ	”9 !Ô$Ñ	$×/Ñ/Ó1ó	÷ €Hô $×3Ñ3°H²NÀÓC€DðÙ	0¨4Ô	0ðä	˜Z¬Ñ2fÐaeÔ2fÓ)fÓ	gñ€Fô (Ø×%Ñ% jÓ1Ø×!Ñ! *Ó-Ü˜V›Ü ¤°Ó!6Ó7Øôð ðús   ‚C,E?Ã.E=Ã/BE?Úactionc                 ó|   — | j                   t        j                  t        j                  fvrt	        dd|› �¬«      ‚y )Nrœ   zOnly proxy admin roles can r�   )r¦   r   r§   ÚPROXY_ADMIN_VIEW_ONLYr=   ©r˜   r�  s     rL   rƒ  rƒ  ™  sE   € Ø×"Ñ"Ü×$Ñ$Ü×.Ñ.ð+ñ ô ¨Ð6QÐRXÐQYÐ4ZÔ[Ð[ð	rU   c                 ó^   — | j                   t        j                  k7  rt        dd|› �¬«      ‚y )Nrœ   zOnly a proxy admin can r�   )r¦   r   r§   r=   r�  s     rL   Ú_require_admin_writerr’  ¡  s2   € Ø×"Ñ"Ô&6×&BÑ&BÒBÜ¨Ð6MÈfÈXÐ4VÔWÐWð CrU   ré   c                 ó„   ‡— t        ˆfd„| j                  | j                  | j                  | j                  fD «       «      S )Nc              3   ó&   •K  — | ]  }‰|v –— Œ
 y ­wrH   rI   )r²   Úregistryré   s     €rL   r´   z6_is_configured_pre_routing_strategy.<locals>.<genexpr>§  s   øè ø€ ò àð 	�xÔñùs   ƒ)ÚanyÚauto_routersÚcomplexity_routersÚadaptive_routersÚquality_routers)r»   ré   s    `rL   Ú#_is_configured_pre_routing_strategyr›  ¦  sE   ø€ Üó ð ×#Ñ#Ø×)Ñ)Ø×'Ñ'Ø×&Ñ&ð	
ôó ð rU   r³   c                 ó
  ‡— t        j                  | ¬«      \  }}}}|dk7  s t         j                  st         j                  ryddlm} ddlmŠ |j                  «       s|j                  «       ryt        ˆfd„dD «       «       S )	N)r³   Ú	anthropicFr   )ÚAnthropicModelInfo)Ú!secret_manager_would_be_consultedc              3   ó.   •K  — | ]  } ‰|«      –— Œ y ­wrH   rI   )r²   Úsecret_namerŸ  s     €rL   r´   z>_sdk_model_is_missing_anthropic_credentials.<locals>.<genexpr>»  s   øè ø€ ò Ø;FÑ)¨+×6ñùrf  )ÚANTHROPIC_API_KEYÚANTHROPIC_AUTH_TOKEN)ÚlitellmÚget_llm_providerÚanthropic_keyÚapi_keyÚ#litellm.llms.anthropic.common_utilsrž  Úlitellm.secret_managers.mainrŸ  Úget_api_keyÚget_auth_tokenr–  )r³   Ú_Úproviderrž  rŸ  s       @rL   Ú+_sdk_model_is_missing_anthropic_credentialsr®  ²  su   ø€ Ü×0Ñ0°uÔ=Ñ€A€x��AØ�;Ò¤'×"7Ò"7¼7¿?º?ØÝFÝNà×%Ñ%Ô'Ð+=×+LÑ+LÔ+NØÜó ØJwôó ð ð rU   Ú
field_nameÚteam_idsc                 óX  ‡ ‡— ‰ �t        ‰ ‰«      rt        d|› d‰› d�¬«      ‚t        ˆ ˆfd„|D «       «      }t        d„ |D «       «      }|rt        d|› d‰› d�t        |«      z   ¬«      ‚t        d	„ |D «       «      }|syt	        ‰«      syt        d|› d‰› d
�t        |«      z   ¬«      ‚)aV  Reject a model the dispatch path cannot resolve, at start rather than as a silently
    growing error count once the job is already sampling and billing. Both the judge and a
    reverse job's baseline must be plain models: an auto-router in either slot would
    re-route per turn, so the comparison would have no fixed arm to attribute results to.

    Resolvability is asked once per team the job samples for, because that is the identity
    the call carries: a name only one team can reach fails every turn for the other keys,
    which is the growing error count this check exists to prevent.Nr¢   z 'z-' is an auto-router; it must be a plain modelr�   c              3   ó<   •K  — | ]  }|t        ‰‰|«      f–— Œ y ­wrH   r   )r²   Úteamr»   r³   s     €€rL   r´   z(_validate_plain_model.<locals>.<genexpr>Ð  s   øè ø€ Ò^ÈT˜D¤,¨z¸5À$Ó"GÔHÑ^ùs   ƒc              3   óF   K  — | ]  \  }}|j                   d k(  sŒ|–— Œ y­w)ÚnothingN©Úvia©r²   r³  Útargets      rL   r´   z(_validate_plain_model.<locals>.<genexpr>Ñ  s    è ø€ Ò[©¨¨fÀ6Ç:Á:ÐQZÓCZœtÑ[ùó   ‚!š!z{' is neither a model configured on this proxy nor a provider-qualified public model name (e.g. 'anthropic/claude-sonnet-5')c              3   óF   K  — | ]  \  }}|j                   d k(  sŒ|–— Œ y­w)ÚsdkNr¶  r¸  s      rL   r´   z(_validate_plain_model.<locals>.<genexpr>Ú  s   è ø€ ÒU¡l d¨FÀÇÁÈuÓATœTÑUùrº  zm' uses the LiteLLM SDK but required credentials are not configured: ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN)r›  r=   rµ   Ú
_for_teamsr®  )r»   r³   r¯  r°  ÚtargetsÚunreachableÚ	sdk_teamss   ``     rL   Ú_validate_plain_modelrÁ  À  sé   ù€ ð ÐÔ"EÀjÐRWÔ"XÜØØ �\  E 7Ð*WÐXô
ð 	
ô Ô^ÐU]Ô^Ó^€GÜÑ[¸Ô[Ó[€KÙÜØà�,˜b  ð (Zð ZÜ\fÐgrÓ\sñtô
ð 	
ô ÑU°gÔUÓU€IÙØÜ6°uÔ=ØÜ
Øàˆl˜"˜U˜Gð $8ð 8Ü:DÀYÓ:OñPôð rU   c                 óh   — t        t        d„ | D «       «      «      }|rddj                  |«      › �S dS )zMName the teams a fault applies to, when it does not apply to every key alike.c              3   ó&   K  — | ]	  }|€Œ|–— Œ y ­wrH   rI   )r²   r³  s     rL   r´   z_for_teams.<locals>.<genexpr>ê  s   è ø€ ÒN¨¸TÑ=M¤ÑNùs   ‚Šz
 for team ú, r<  )rµ   rp  Újoin)r°  Únameds     rL   r½  r½  è  s5   € äœÑN°ÔNÓNÓO€EÙ.3ˆZ˜Ÿ	™	 %Ó(Ð)Ð*Ð;¸Ð;rU   ÚtierÚdefaultÚ_JUDGED_ROLESc                 ó€   — | �| j                  |¬«      nd}t        t        j                  d„ |xs dD «       «      «      S )a´  ``(role, model_name)`` for every model the router under evaluation can answer with.

    Drawn from ``strategy_router_dependencies``, the single answer to "what does this router
    call", so this cannot disagree with the health check's reading of the same deployment.
    Only the roles that SERVE are arms: the classifier and embedding models pick the tier,
    they never produce a response anyone judges, so a judge sharing them carries no
    self-preference.

    A semantic auto-router keeps its routes in an opaque config blob or a file, so only its
    default model is enumerable and the guard below is incomplete for it. That direction is
    deliberate: it can miss a collision, never invent one.

    Which tiers a router declares is a property of its config and not of who is calling, so
    this lookup is unscoped; what each tier NAME resolves to is the team-dependent half, and
    it belongs to the caller that compares them.
    N)rÚ   c              3   ó–   K  — | ]A  }t        |d    «      D ].  }|j                  t        v r|j                  |j                  f–— Œ0 ŒC y­w)r\  N)r)   ÚrolerÉ  rÚ   )r²   rZ  Ú
dependencys      rL   r´   z%_router_arm_models.<locals>.<genexpr>  sS   è ø€ ò 
àÜ:¸:ÐFVÑ;WÓXò
ð Ø�‰¤-Ñ/ð �_‰_˜j×3Ñ3Ô4ð
Ø4ñ
ùs   ‚AA	rI   )Úget_model_listrµ   ÚdictÚfromkeys)r»   ré   Údeploymentss      rL   Ú_router_arm_modelsrÒ  ñ  sL   € ð" OYÐNd˜×2Ñ2¸kÐ2ÔJÐjn€KÜÜ�‰ñ 
à)Ò/¨Rô
ó 	
óð rU   c                 óÞ   ‡ ‡‡— t        ‰ |j                  ‰«      j                  Št        ˆˆ ˆfd„g t	        ‰ |j
                  «      ¢|j                  �d|j                  ffnd¢­D «       «      S )aD  ``(role, model_name)`` for each arm the judge would also be, as one team's keys see it.

    Both sides resolve under the SAME team, since two names are the same model only for a
    caller who can reach both; resolving the judge for one team against an arm for another
    invents a collision no request could produce.
    c              3   ó`   •K  — | ]%  \  }}‰t        ‰|‰«      j                  z  r||f–— Œ' y ­wrH   )r   rÇ   )r²   rÌ  r³   Újudger»   r`   s      €€€rL   r´   z-_judge_collisions_for_team.<locals>.<genexpr>  s:   øè ø€ ò áˆD�%ð ”< 
¨E°7Ó;×BÑBÒBð 
ˆuŒñùó   ƒ+.ÚbaselinerI   )r   Újudge_modelrÇ   rµ   rÒ  ré   Úbaseline_model)r»   rn   r`   rÕ  s   ` `@rL   Ú_judge_collisions_for_teamrÚ    st   ú€ ô   
¨D×,<Ñ,<¸gÓF×MÑM€EÜõ ð
Ü 
¨D×,<Ñ,<Ó=ð
à6:×6IÑ6IÐ6U�
˜D×/Ñ/Ð0Ñ2Ð[]ñ
ôó ð rU   c                 óÊ   ‡ ‡— t        t        j                  ˆˆ fd„|D «       «      «      }|syt        dd‰j                  › d�dj                  d„ |D «       «      z   dz   ¬	«      ‚)
a�  Reject a judge that is one of the two arms it grades.

    A judge scores its own output higher than a rival's, so a run whose judge also serves an
    arm reports a win rate for that arm that measures the judge rather than the models, and
    the whole job's spend buys a result that has to be discarded. Both arms are in scope: the
    router answers with a tier or default model in either direction, and a reverse job's
    ``baseline_model`` is the fixed arm the router is compared against.

    Names are compared by what would ANSWER them, not by spelling: the shipped default judge
    ``anthropic/claude-sonnet-5`` collides with a tier deployment an admin named
    ``sonnet-tier``, and an alias collides with its target, neither of which a string
    comparison sees.

    A collision for ONE team is a collision for the job, because the verdicts every key
    produces land in the same win rates.
    c              3   óF   •K  — | ]  }t        ‰‰|«      D ]  }|–— Œ Œ y ­wrH   )rÚ  )r²   r`   Ú	collisionrn   r»   s      €€rL   r´   z5_validate_judge_is_not_a_candidate.<locals>.<genexpr>5  s5   øè ø€ ò 
Ø!Ô?YÐZdÐfjÐlsÓ?tò
Ø2;ŒIð
Øñ
ùs   ƒ!Nr¢   zjudge_model 'z'' is also an arm this job would judge: rÄ  c              3   ó2   K  — | ]  \  }}|› d |› d�–— Œ y­w)z model 'ú'NrI   )r²   rÌ  r³   s      rL   r´   z5_validate_judge_is_not_a_candidate.<locals>.<genexpr>?  s!   è ø€ ÒP±k°d¸E˜4˜& ¨¨¨qÔ1ÑPùrh  z†. A judge scores its own answers higher than a rival's, so the win rates would measure the judge; pick a judge that serves neither armr�   )rµ   rÏ  rÐ  r=   rØ  rÅ  )r»   rn   r°  Ú
collisionss   ``  rL   Ú"_validate_judge_is_not_a_candidaterá  !  sx   ù€ ô& Ü�‰ô 
Ø%-ô
ó 	
ó€Jñ
 ØÜ
Øà˜D×,Ñ,Ð-Ð-TÐUØ�i‰iÑPÀZÔPÓPñQðFñFôð rU   ru   c                 óš   — 	 ddl m} t        | |«      S # t        $ r- dt        | «      j	                  «       v xs dt        | «      v cY S w xY w)ag  Whether a Prisma create failed on a unique index. One active job per key and
    direction lives in a partial unique index (raw SQL in the migration; schema.prisma
    cannot express partial indexes), so the read-then-create check above it is advisory:
    two concurrent starts pass the read, and the loser must surface as the same 409
    rather than a 500.r   )ÚUniqueViolationErrorzunique constraintÚP2002)Úprisma.errorsrã  ÚImportErrorrS   Úlowerr^  )ru   rã  s     rL   Ú_is_unique_violationrè  F  sT   € ðRÝ6ô �eÐ1Ó2Ð2øô ò RØ"¤c¨%£j×&6Ñ&6Ó&8Ð8ÒQ¸GÄsÈ5ÃzÐ<QÒQðRús   ‚ ”3A
Á	A
c                   ón   — e Zd ZU eed<   eed<   eed<   eed<   eed<   edz  ed<   eed<   eed	<   eed
<   y)Ú_AttemptAggRowÚgrpÚ
turn_countÚ	real_winsÚshadow_winsÚtiesNÚavg_confidenceÚ
real_spendÚshadow_spendÚcache_hit_turns©rP   rQ   rR   rS   r  rq   r  rI   rU   rL   rê  rê  S  s8   … Ø	ƒHØƒOØƒNØÓØ
ƒIØ˜D‘LÓ ØÓØÓØÔrU   rê  Ú_ATTEMPT_AGG_ROWSaË  
    COUNT(*)::int AS turn_count,
    COUNT(*) FILTER (WHERE outcome = 'real')::int AS real_wins,
    COUNT(*) FILTER (WHERE outcome = 'shadow')::int AS shadow_wins,
    COUNT(*) FILTER (WHERE outcome = 'tie')::int AS ties,
    AVG(confidence)::float AS avg_confidence,
    COALESCE(SUM(real_cost + real_classifier_cost) FILTER (WHERE real_cost IS NOT NULL AND NOT real_cache_hit), 0)::float AS real_spend,
    COALESCE(SUM(shadow_cost + shadow_classifier_cost) FILTER (WHERE real_cost IS NOT NULL AND NOT real_cache_hit), 0)::float AS shadow_spend,
    COUNT(*) FILTER (WHERE real_cache_hit)::int AS cache_hit_turns
FROM "LiteLLM_ShadowEvalAttempt"
WHERE job_id = ANY($1::text[]) AND outcome != 'error'
GROUP BY 1
Ú_ATTEMPT_AGG_SELECTz-SELECT COALESCE(tier, 'UNCLASSIFIED') AS grp,Ú_ATTEMPT_AGG_BY_TIER_SQLz.SELECT COALESCE(real_model, 'unknown') AS grp,Ú_ATTEMPT_AGG_BY_MODEL_SQLzSELECT job_id AS grp,Ú_ATTEMPT_AGG_BY_LEG_SQLaù  
UPDATE "LiteLLM_ShadowEvalJob" j SET stopped_at = (NOW() AT TIME ZONE 'utc')
WHERE j.api_key_id = ANY($1::text[]) AND j.stopped_at IS NULL
  AND (
    j.ends_at <= (NOW() AT TIME ZONE 'utc')
    OR (SELECT COUNT(*) FROM "LiteLLM_ShadowEvalAttempt" a WHERE a.job_id = j.id) >= j.max_turns
    OR (
      j.max_budget IS NOT NULL
      AND (SELECT COALESCE(SUM(a.judge_cost + a.shadow_cost + a.shadow_classifier_cost), 0) FROM "LiteLLM_ShadowEvalAttempt" a WHERE a.job_id = j.id) >= j.max_budget
    )
  )
Ú_SWEEP_FINISHED_JOBS_SQLa  
SELECT
    COUNT(*) FILTER (WHERE outcome != 'error')::int AS judged_count,
    COUNT(*) FILTER (WHERE outcome = 'error')::int AS error_count,
    COALESCE(SUM(judge_cost), 0)::float AS judge_spend
FROM "LiteLLM_ShadowEvalAttempt"
WHERE job_id = ANY($1::text[])
Ú_ATTEMPT_TOTALS_SQLaO  
SELECT a.job_id, COUNT(*)::int AS attempt_count, COALESCE(SUM(a.judge_cost + a.shadow_cost + a.shadow_classifier_cost), 0)::float AS spend
FROM "LiteLLM_ShadowEvalAttempt" a
JOIN "LiteLLM_ShadowEvalJob" j ON j.id = a.job_id
WHERE a.job_id = ANY($1::text[]) AND (j.stopped_at IS NULL OR a.created_at <= j.stopped_at)
GROUP BY a.job_id
Ú_ATTEMPT_COUNTS_SQLa/  
SELECT COUNT(*)::int AS legs_with_rows,
    COALESCE(SUM(not_sampled), 0)::int AS not_sampled,
    COALESCE(SUM(unjudgeable), 0)::int AS unjudgeable,
    COALESCE(SUM(shed), 0)::int AS shed,
    COALESCE(SUM(withheld), 0)::int AS withheld
FROM "LiteLLM_ShadowEvalFunnel"
WHERE job_id = ANY($1::text[])
Ú_FUNNEL_TOTALS_SQLah  
UPDATE "LiteLLM_ShadowEvalJob"
SET stopped_by = $2, stopped_at = COALESCE(stopped_at, $3::timestamp)
WHERE group_id = $1 AND stopped_by IS NULL
  AND ends_at > (NOW() AT TIME ZONE 'utc')
  AND EXISTS (
    SELECT 1 FROM "LiteLLM_ShadowEvalJob" k
    WHERE k.group_id = $1 AND k.stopped_at IS NULL
      AND (SELECT COUNT(*) FROM "LiteLLM_ShadowEvalAttempt" a WHERE a.job_id = k.id) < k.max_turns
      AND (
        k.max_budget IS NULL
        OR (SELECT COALESCE(SUM(a.judge_cost + a.shadow_cost + a.shadow_classifier_cost), 0) FROM "LiteLLM_ShadowEvalAttempt" a WHERE a.job_id = k.id) < k.max_budget
      )
  )
Ú_STOP_JOB_SQLc                   ó@   — e Zd ZU eed<   eed<   eed<   eed<   eed<   y)Ú_FunnelTotalsRowÚlegs_with_rowsÚnot_sampledÚunjudgeableÚshedÚwithheldN)rP   rQ   rR   rq   r  rI   rU   rL   r   r   °  s   … ØÓØÓØÓØ
ƒIØ„MrU   r   c                   ó,   — e Zd ZU eed<   eed<   eed<   y)Ú_AttemptCountRowÚjob_idÚattempt_countr  Nrô  rI   rU   rL   r  r  ¸  s   … ØƒKØÓØ„LrU   r  Ú_ATTEMPT_COUNT_ROWSz°
SELECT * FROM "LiteLLM_ShadowEvalJob"
WHERE group_id IN (
    SELECT group_id FROM "LiteLLM_ShadowEvalJob"
    GROUP BY group_id ORDER BY MAX(created_at) DESC LIMIT $1::int
)
Ú_LIST_LEGS_SQLzÆ
SELECT * FROM "LiteLLM_ShadowEvalJob"
WHERE group_id IN (
    SELECT group_id FROM "LiteLLM_ShadowEvalJob" WHERE api_key_id = $2
    GROUP BY group_id ORDER BY MAX(created_at) DESC LIMIT $1::int
)
Ú_LIST_LEGS_BY_KEY_SQLc                   ó,   — e Zd ZU eed<   eed<   eed<   y)Ú_AttemptTotalsRowÚjudged_countÚerror_countÚjudge_spendN)rP   rQ   rR   rq   r  r  rI   rU   rL   r  r  Ò  s   … ØÓØÓØÔrU   r  Ú_ATTEMPT_TOTALS_ROWSc                 ó   — t        | |«      S rH   )r  r  s     rL   Ú_pct_ofr  Û  s   € Ü�	˜;Ó'Ð'rU   c                 ó@   — t        d„ t        | d„ d¬«      D «       «      S )Nc              3   óŒ  K  — | ]¼  }t        |j                  |j                  t        |j                  |j                  «      t        |j
                  |j                  «      t        |j                  |j                  «      t        |j                  xs d d«      |j                  |j                  |j                  ¬«	      –— Œ¾ y­w)r  é   )	Úgrouprì  Úreal_win_rate_pctÚshadow_win_rate_pctÚtie_rate_pctÚavg_judge_confidencerñ  rò  ró  N)r9   rë  rì  r  rí  rî  rï  r  rð  rñ  rò  ró  r>  s     rL   r´   z_slices.<locals>.<genexpr>à  s–   è ø€ ò ð ô 	Ø—'‘'Ø—~‘~Ü% c§m¡m°S·^±^ÓDÜ '¨¯©¸¿¹Ó HÜ  §¡¨3¯>©>Ó:Ü!& s×'9Ñ'9Ò'@¸SÀ!Ó!DØ—~‘~Ø×)Ñ)Ø×/Ñ/÷
	
ð 
	
ñùs   ‚CCc                 ó   — | j                   S rH   ©rì  )Úrs    rL   ú<lambda>z_slices.<locals>.<lambda>ì  s
   € ¨a¯l©l€ rU   T©re  Úreverse)rµ   rp  rX  s    rL   Ú_slicesr#  ß  s(   € Üñ ô ˜$Ñ$:ÀDÔIôó ð rU   c                   ó  — e Zd ZU dZ ed¬«      Zeed<   eed<   eed<   eed<   eed<   d	Z	ed	z  ed
<   eed<   e
ed<   eed<   d	Ze
d	z  ed<   eed<   eed<   d	Zed	z  ed<   d	Zed	z  ed<    eddd«      eded	z  ded	z  fd„«       «       Zy	)Ú_LegRowa:  One LiteLLM_ShadowEvalJob row, validated off the untyped prisma record. A row is
    one key's leg of a job; the legs of a job share group_id and identical config, written
    together by one create_many. The API's job id is the group id, so leg ids never leave
    the server (attempts reference them internally).T)Úfrom_attributesrj   Úgroup_idÚ
api_key_idré   Ú	directionNrÙ  rØ  Úshadow_percentageÚ	max_turnsÚ
max_budgetÚ
created_atÚends_atÚ
stopped_atÚ
stopped_byr©   rF   c                 ób   — |�|j                   �|S |j                  t        j                  ¬«      S )zöThe columns store naive UTC wall time (prisma's convention); prisma reads hand
        back aware datetimes while raw SQL reads hand back naive ones, so this boundary
        makes every read aware UTC before anything compares or serializes them.r  )r  r  r   r  )Úclsr©   s     rL   Ú_as_aware_utcz_LegRow._as_aware_utc  s,   € ð ˆ=˜EŸL™LÐ4ØˆLØ�}‰}¤H§L¡Lˆ}Ó1Ð1rU   )rP   rQ   rR   Ú__doc__r   Úmodel_configrS   r  r5   rÙ  r  rq   r,  r   r/  r0  r   Úclassmethodr3  rI   rU   rL   r%  r%  ð  s¼   … ñ8ñ
 ¨dÔ3€LàƒGØƒMØƒOØÓØ"Ó"Ø!%€N�C˜$‘JÓ%ØÓØÓØƒNØ#€J�˜‘Ó#ØÓØÓØ"&€J�˜4‘Ó&Ø!€J��d‘
Ó!á�\ 9¨lÓ;Øð2 (¨T¡/ð 2°hÀ±oò 2ó ó <ñ2rU   r%  Ú	_LEG_ROWSÚlegsc           
   ƒ   ó  K  — |st        i «      S t        j                  t        | t        |D �cg c]  }|j
                  ‘Œ c}«      ƒ d{  –—† xs d«      }t        |D �ci c]  }|j                  |“Œ c}«      S c c}w 7 Œ5c c}w ­w)aÖ  Each leg's attempt count and recorded spend by leg id, judged and errored alike, in
    one grouped read. They are the same figures the sampler budgets against max_turns and
    max_budget, so the derived status flips to completed exactly when sampling actually
    ends. A stamped leg's figures freeze at its stopped_at: in-flight attempts that land
    after the stamp are excluded, so they can never reclassify a leg that was stopped
    under budget as budget-spent.NrI   )r
   r
  r†  r—   rü  rj   r  )r€   r8  Úlegr:  r$  s        rL   Ú_leg_attempt_countsr;    s|   è ø€ ñ Ü Ó#Ð#Ü%×5Ñ5Ü˜Ô(;ÐPTÖ=UÈ¸c¿f»fÒ=UÓV×Vò 	Øó€Dô ¸Ö=°˜SŸZ™Z¨™_Ò=Ó>Ð>ùò >VÐVúò >ùs.   ‚,B®A<Á
BÁBÁBÁ!BÁ5BÂBr'  Úattempt_countsc                 ó&  ‡‡— |d   }t        | t        ˆˆfd„t        |d„ ¬«      D «       «      |j                  |j                  |j
                  |j                  |j                  |j                  |j                  t        d„ |D «       d«      ¬«
      S )a  The one constructor of a job response: the caller names the group and passes that
    group's legs. Config is read off the first leg because every leg carries the same copy,
    written by one create_many. No caller may serialize a raw row (that would leak a leg id
    as the job id).r   c              3   ó  •K  — | ]~  }t        |j                  |j                  |j                  |j                  ‰j                  |j                  «      xŠr‰j                  nd ‰rt        ‰j                  d«      nd¬«      –— Œ€ y­w)r   é   r  )r(  r+  r,  r/  r	  r  N)
r6   r(  r+  r,  r/  r_  rj   r	  r  r  )r²   r:  r<  Ústatss     €€rL   r´   z"_group_response.<locals>.<genexpr>/  s{   øè ø€ ò 

ð ô %ØŸ>™>ØŸ-™-ØŸ>™>ØŸ>™>Ø?M×?QÑ?QÐRU×RXÑRXÓ?YÐ6Y°eÐ6Y˜e×1Ò1Ð`aÙ/4”e˜EŸK™K¨Ô+¸#÷ð ñ

ùs   ƒBBc                 ó   — | j                   S rH   )r(  )r:  s    rL   r   z!_group_response.<locals>.<lambda>8  s
   € °C·N±N€ rU   ©re  c              3   óN   K  — | ]  }|j                   €Œ|j                   –— Œ y ­wrH   )r0  ©r²   r:  s     rL   r´   z"_group_response.<locals>.<genexpr>A  s   è ø€ ÒV¨C¸3¿>¹>Ñ;U˜Ÿ�ÑVùs   ‚%”%N)
r  Úkeysré   r)  rÙ  rØ  r*  r-  r.  r0  )r7   rµ   rp  ré   r)  rÙ  rØ  r*  r-  r.  Únext)r'  r8  r<  Úfirstr@  s     ` @rL   Ú_group_responserH  %  s‹   ù€ ð ˜‘7€EÜ ØÜô 

ô ˜dÑ(BÔCô

ó 

ð ×%Ñ%Ø—/‘/Ø×+Ñ+Ø×%Ñ%Ø×1Ñ1Ø×#Ñ#Ø—‘ÜÑV°4ÔVÐX\Ó]ô)ð rU   )NNÚ_NO_KEY_LABELSÚ	responsesc              ƒ   ó6  ‡K  — |syt        t        d„ |D «       «      «      }t        | «      j                  dd|ii¬«      ƒ d{  –—† }|xs dD �ci c]%  }|j                  |j
                  |j                  f“Œ' c}Št        ˆfd„|D «       «      S 7 ŒMc c}w ­w)z¢Resolve every scoped key's hash to its alias and masked name in one batched read,
    so the UI can say whose traffic a job shadows. Deleted keys resolve to None.rI   c              3   óV   K  — | ]!  }|j                   D ]  }|j                  –— Œ Œ# y ­wrH   )rE  r(  )r²   Úresponsere  s      rL   r´   z#_with_key_labels.<locals>.<genexpr>O  s(   è ø€ Ò$g¸ÐYa×YfÑYfÒ$gÐRU S§^¥^Ð$g ^Ñ$gùs   ‚')rZ   Úinr¡   Nc              3   ó~   •K  — | ]4  }|j                  d t        ˆfd„|j                  D «       «      i¬«      –— Œ6 y­w)rE  c              3   óÌ   •K  — | ][  }|j                  ‰j                  |j                  t        «      d    ‰j                  |j                  t        «      d   dœ¬«      –— Œ] y­w)r   r  )r\   r^   rk  N)rm  r_  r(  rI  )r²   re  Úlabelss     €rL   r´   z-_with_key_labels.<locals>.<genexpr>.<genexpr>Y  s^   øè ø€ ò ð ð —N‘Nà)/¯©°C·N±NÄNÓ)SÐTUÑ)VØ(.¯
©
°3·>±>Ä>Ó(RÐSTÑ(Uñ ð #÷ ñùs   ƒA!A$rk  N)rm  rµ   rE  )r²   rM  rQ  s     €rL   r´   z#_with_key_labels.<locals>.<genexpr>V  sM   øè ø€ ò ð ð 	×Ñàœó ð  (Ÿ}™}ôó ð
ð 	÷ 	
ñùs   ƒ:=)rp  rï   rˆ   rf   rZ   r\   r^   rµ   )r€   rJ  ÚtokensÚkey_rowsr$  rQ  s        @rL   Ú_with_key_labelsrT  H  s­   øè ø€ ñ
 ØÜœ9Ñ$gÀIÔ$gÓgÓh€FÜ0°Ó?×IÑIØ˜˜v˜Ð'ð Jó ÷ €Hð =EºNÈöBØ58ˆ�	‰	�C—M‘M 3§<¡<Ð0Ñ0òB€Fô ó ð "ôó ð ðúòBùs$   ƒABÁBÁBÁ*BÁ;BÂBc              ƒ   ób  ‡K  — |D �cg c]  }|j                   ‘Œ }}t        j                  t        | t        |«      ƒ d{  –—† xs d«      }|syt        j                  t        | t
        |«      ƒ d{  –—† xs d«      }t        |D �ci c]  }|j                   |j                  “Œ c}«      Št        j                  t        | t        |«      ƒ d{  –—† xs d«      }t        ˆfd„|D «       «      }t        d„ |D «       «      }t        | t        |«      ƒ d{  –—† }	|	rt        j                  |	d   «      nd}
|
�|
j                  t        |«      k(  r|
nd}t!        t#        |«      t#        |«      t#        |«      t%        t        d„ |D «       «      |«      t%        t        d„ |D «       «      |«      t        d„ |D «       «      t        d	„ |D «       «      |�|j&                  nd|�|j(                  nd|�|j*                  nd|�|j,                  ¬
«      S d¬
«      S c c}w 7 �ŒÞ7 �Œ®c c}w 7 �ŒX7 �Œ­w)aþ  All three stratifications of one job's verdicts. Tier answers "where does the router
    do well"; the model stratification groups by whichever model served the real arm, so it
    answers "which of the models these keys use today would the router beat" forward, and
    "for the turns the router sent to X, did X beat the baseline" in reverse; key answers
    "which key's traffic does the router suit". Reads are bounded by the job's own attempts
    (<= the sum of its keys' max_turns) via the job_id index.NrI   c              3   ó`   •K  — | ]%  }|j                  d ‰|j                     i¬«      –— Œ' y­w)rë  rk  N)rm  rë  )r²   r$  Ú
key_by_legs     €rL   r´   z'_shadow_eval_results.<locals>.<genexpr>|  s2   øè ø€ ò àð 	�‰˜u j°·±Ñ&9Ð:ˆ×;ñùrÖ  c              3   ó4   K  — | ]  }|j                   –— Œ y ­wrH   r  ©r²   r  s     rL   r´   z'_shadow_eval_results.<locals>.<genexpr>€  s   è ø€ Ò;¨a˜QŸ\�\Ñ;ùr?  r   c              3   ó4   K  — | ]  }|j                   –— Œ y ­wrH   )rî  rY  s     rL   r´   z'_shadow_eval_results.<locals>.<genexpr>Š  s   è ø€ Ò/OÀ!°·µÑ/Oùr?  c              3   ó4   K  — | ]  }|j                   –— Œ y ­wrH   )rï  rY  s     rL   r´   z'_shadow_eval_results.<locals>.<genexpr>‹  s   è ø€ Ò(A°A¨¯­Ñ(Aùr?  c              3   ó4   K  — | ]  }|j                   –— Œ y ­wrH   )rñ  rY  s     rL   r´   z'_shadow_eval_results.<locals>.<genexpr>Œ  s   è ø€ Ò=°˜qŸ|�|Ñ=ùr?  c              3   ó4   K  — | ]  }|j                   –— Œ y ­wrH   )rò  rY  s     rL   r´   z'_shadow_eval_results.<locals>.<genexpr>�  s   è ø€ Ò A°A §¥Ñ Aùr?  )Úby_tierÚby_current_modelÚby_keyÚoverall_shadow_win_rate_pctÚoverall_tie_rate_pctÚsampled_real_spendÚsampled_shadow_spendÚnot_sampled_countÚunjudgeable_countÚ
shed_countÚwithheld_count)rj   rõ  r†  r—   r÷  rø  r
   r(  rù  rµ   rW  rý  r   r«   r  rˆ  r8   r#  r  r  r  r  r  )r€   r8  r:  Úleg_idsr^  Úby_modelÚby_legr`  Útotal_turnsÚfunnel_rowsÚcountedÚfunnelrW  s               @rL   Ú_shadow_eval_resultsrp  h  s  øè ø€ ð )-Ö- �c—f“fÐ-€GÐ-Ü&×6Ñ6Ü˜Ô(@À'ÓJ×JÒPÈbó€Gñ ØÜ'×7Ñ7Ü˜Ô(AÀ7ÓK×KÒQÈró€Hô )ÈDÖ)QÀS¨#¯&©&°#·.±.Ñ*@Ò)QÓR€JÜ%×5Ñ5Ü˜Ô(?ÀÓI×IÒOÈRó€Fô ó àôó €Fô Ñ;°7Ô;Ó;€KÜ)¨-Ô9KÈWÓU×U€KÙHSÔ%×4Ñ4°[À±^ÔDÐY]€Gð  'Ð2°w×7MÑ7MÔQTÐU\ÓQ]Ò7]‘GÐcg€FÜÜ˜Ó Ü  Ó*Ü�v‹Ü$+¬CÑ/OÀwÔ/OÓ,OÐQ\Ó$]Ü$¤SÑ(A¸Ô(AÓ%AÀ;ÓOÜÑ=°WÔ=Ó=Ü Ñ A¸Ô AÓAØ06Ð0B˜&×,Ò,ÈØ06Ð0B˜&×,Ò,ÈØ"(Ð"4�6—;’;¸$Ø*0Ð*<�v—‘ôð ð CGôð ùò/ .àJùð
 	Lûâ)QàIùð Vûs_   ƒH/ˆH›%H/Á HÁ2H/Á3H!Á4H/Â	H$Â'*H/ÃH)ÃAH/ÄH,ÄDH/È!H/È$H/È,H/z/auto_router/shadow_eval/startc              ƒ   ó"	  ‡ ‡K  — ddl m}m} t        |d«       |€%t	        dt
        j                  j                  ¬«      ‚|�t        |‰ j                  «      st	        dd‰ j                  › d	�¬«      ‚t        |«      j                  d
dt        ‰ j                  «      ii¬«      ƒ d{  –—† }t        t        t!        ‰ j                  «      t!        d„ |xs dD «       «      z
  «      «      }|r t	        dddj#                  |«      › d�¬«      ‚t        t$        j'                  d„ |xs dD «       «      «      }t)        |‰ j*                  d|«       ‰ j,                  �t)        |‰ j,                  d|«       t/        |‰ |«       t        ‰ j                  «      }|j0                  j3                  t4        |«      ƒ d{  –—†  t7        |«      j                  d|i‰ j8                  ddœ¬«      ƒ d{  –—† }|r@t	        dd‰ j8                  › d�dj#                  t        d„ |D «       «      «      z   dz   ¬«      ‚t;        j<                  t>        j@                  «      }	tC        tE        «       «      }
|	tG        ‰ jH                  ¬«      z   }|
‰ j                  ‰ j8                  ‰ j,                  ‰ j*                  ‰ jJ                  tL        ‰ jN                  |jP                  |	|dœ}	 t        d„ ‰ j                  D «       «      }t7        |«      jS                  tU        |‰ j                  «      D ��cg c]  \  }}i |¥||dœ¥‘Œ c}}¬«      ƒ d{  –—†  	 t[        |«      jS                  |D �cg c]  }d"|i‘Œ c}d#¬$«      ƒ d{  –—†  ta        |D �ci c]  }|jb                  |“Œ c}«      Šte        |
t        ˆ ˆfd&„t        ‰ j                  «      D «       «      ‰ j                  ‰ j8                  ‰ j,                  ‰ j*                  ‰ jJ                  |	|¬'«	      S 7 �Œc7 �ŒW7 �Œ'c c}}w 7 ŒØ# tV        $ r-}tY        |«      s‚ t	        dd ‰ j8                  › d!�¬«      |‚d}~ww xY wc c}w 7 Œã# tV        $ r"}t]        j^                  d%|
|«       Y d}~�Œd}~ww xY wc c}w ­w)(ah  
    Start a shadow eval: duplicate a sampled slice of one or more keys' live traffic against
    a second arm, judge the two responses blind, and stratify win rates by tier, by the model
    that served the real arm, and by key.

    A forward job answers whether the keys should adopt router_name: it samples the requests
    the router did not serve and duplicates them through it. A reverse job answers whether a
    key already on the router still gains from it: it samples the requests the router did
    serve and duplicates them against baseline_model. A key can hold one active job per
    direction, so both questions can run at once.

    Shadow responses are never served to users. Each key samples until its recorded eval
    spend, the shadow and judge calls' own cost, reaches max_budget dollars, the job's
    window ends, or the job is stopped, so one key running out of budget does not end
    sampling for the others; sampling changes propagate to pods within about 10 seconds.
    Shadow and judge calls bill to the shadowed key but are excluded from request counts
    and auto-router adoption metrics.
    r   rw  zstart a shadow evalNr    r�   r¢   rß  z!' is not a configured auto-routerrZ   rN  r¡   c              3   ó4   K  — | ]  }|j                   –— Œ y ­wrH   )rZ   r>  s     rL   r´   z$start_shadow_eval.<locals>.<genexpr>¼  s   è ø€ ÒIpÐX[È#Ï)Í)ÑIpùr?  rI   zapi_key_ids not on this proxy: rÄ  zR; pass each key's token hash, the value the key list and key info endpoints reportc              3   ó4   K  — | ]  }|j                   –— Œ y ­wrH   )r`   r>  s     rL   r´   z$start_shadow_eval.<locals>.<genexpr>È  s   è ø€ Ò)R¸#¨#¯+­+Ñ)Rùr?  rØ  rÙ  )r(  r)  r/  i™  zAlready in an active z shadow eval job: c              3   óT   K  — | ]   }|j                   › d |j                  › d�–— Œ" y­w)z (job ú)N)r(  r'  r>  s     rL   r´   z$start_shadow_eval.<locals>.<genexpr>ß  s'   è ø€ Ò"_ÐPS c§n¡nÐ%5°V¸C¿L¹L¸>ÈÔ#KÑ"_ùs   ‚&(z. Stop it first.r}  )r'  ré   r)  rÙ  rØ  r*  r+  r,  Ú
created_byr-  r.  c              3   óB   K  — | ]  }t        t        «       «      –— Œ y ­wrH   )rS   r   )r²   r¬  s     rL   r´   z$start_shadow_eval.<locals>.<genexpr>÷  s   è ø€ ÒF°œs¤5£7Ÿ|ÑFùs   ‚)rj   r(  )rn   z'A requested key was claimed by another z- shadow eval job concurrently. Stop it first.r  T)rn   rx   z.shadow_eval: funnel seed failed for job %s: %sc           	   3   óŽ   •K  — | ]<  }t        |t        ‰j                  ‰|   j                  ‰|   j                  ¬ «      –— Œ> y­w))r(  r+  r,  r\   r^   N)r6   r*   r,  r\   r^   )r²   r(  rn   rQ  s     €€rL   r´   z$start_shadow_eval.<locals>.<genexpr>  sN   øè ø€ ò 	
ð ô %Ø%Ü0ØŸ?™?Ø  Ñ,×6Ñ6Ø 
Ñ+×4Ñ4÷ð ñ	
ùs   ƒAA)	r  rE  ré   r)  rÙ  rØ  r*  r-  r.  )3r¥   r»   r€   r’  r=   r   r¨   r©   r›  ré   rˆ   rf   ÚlistÚapi_key_idsrµ   rp  rï   rÅ  rÏ  rÐ  rÁ  rØ  rÙ  rá  r†   Úexecute_rawrú  rŒ   r)  r   r„  r   r  rS   r   r   Úduration_daysr*  r*   r,  Úuser_idrp   Úziprí   rè  r�   r   ru   r
   rZ   r7   )rn   r˜   r»   r€   Ú
token_rowsÚunknownr°  Ú	requestedÚclaimedr„  r'  r.  Úshared_configri  Úleg_idre  rÈ   Úseed_errr$  rQ  s   `                  @rL   Ústart_shadow_evalr†  •  sn  ùè ø€ ÷: EäÐ+Ð-BÔCØÐÜ¨Ô4E×4\Ñ4\×4bÑ4bÔcÐcØÐÔ!DÀZÐQU×QaÑQaÔ!bÜ¨°a¸×8HÑ8HÐ7IÐIjÐ4kÔlÐlÜ2°=ÓA×KÑKØ˜œt D×$4Ñ$4Ó5Ð6Ð7ð Ló ÷ €Jô œ6¤)¨D×,<Ñ,<Ó"=Ä	ÑIpÐ_iÒ_oÐmoÔIpÓ@pÑ"pÓqÓr€GÙÜØà1°$·)±)¸GÓ2DÐ1Eð FGð Gô
ð 	
ô œDŸM™MÑ)RÀÒAQÈrÔ)RÓRÓS€HÜ˜* d×&6Ñ&6¸ÀxÔPØ×ÑÐ&Ü˜j¨$×*=Ñ*=Ð?OÐQYÔZÜ& z°4¸ÔBô
 ˜D×,Ñ,Ó-€IØ
×
Ñ
×
&Ñ
&Ô'?ÀÓ
K×KÐKÜ,¨]Ó;×EÑEà Ð+ØŸ™Øñ
ð Fó ÷ €Gñ ÜØà'¨¯©Ð'7Ð7IÐJØ—)‘)œFÑ"_ÐW^Ô"_Ó_Ó`ñaà$ñ%ô
ð 	
ô —‘œhŸl™lÓ+€CÜœ%›'“l€HØœ9¨$×*<Ñ*<Ô=Ñ=€GàØ×'Ñ'Ø—^‘^Ø×-Ñ-Ø×'Ñ'Ø!×3Ñ3Ü+Ø—o‘oØ'×/Ñ/ØØñ€Mðô ÑF°T×5EÑ5EÔFÓFˆÜ Ó.×:Ñ:äVYÐZaÐcg×csÑcsÓVt÷ÙGRÀvÈsÐB�=ÐB¨¸cÔBóð ;ó 
÷ 	
ð 	
ð"iÜ! -Ó0×<Ñ<Ø3:Ö;¨�8˜VÒ$Ò;Ø ð =ó 
÷ 	
ð 	
ô %À
Ö%K¸ c§i¡i°¡nÒ%KÓL€FÜ ØÜô 	
ô % T×%5Ñ%5Ó6ô	
ó 	
ð ×$Ñ$Ø—.‘.Ø×*Ñ*Ø×$Ñ$Ø×0Ñ0ØØô%ð ðqùð2 LùðûóLð	
ùô
 ò Ü# AÔ&ØÜØà9¸$¿.¹.Ð9IÐIvÐwô
ð
 ð	ûðüò <ð	
ùô ò iÜ×"Ñ"Ð#SÐU]Ð_g×hÒhûðiüâ%KùsÊ   „B$RÂ(PÂ)DRÆ7PÆ82RÇ*PÇ+C-RËA	P Ì"P
Ì5P ÍPÍP ÍQ ÍQÍ*Q Í6QÍ7Q Í;	RÎR
ÎA4RÐRÐRÐP Ð	QÐ%(QÑQÑRÑQ Ñ	RÑ%RÑ<RÒRÒRz/auto_router/shadow_evalr(  z>Filter to jobs that shadow this key, alone or alongside othersÚlimitr  éÈ   zNewest jobs to return)ÚgeÚlert  c           
   ƒ   ól  ‡‡	K  — ddl m} t        | d«       |€%t        dt        j
                  j                  ¬«      ‚t        j                  |rt        |t        ||«      ƒ d{  –—† nt        |t        |«      ƒ d{  –—† xs d«      }t        t        t        |t        d«      ¬	«      t        d«      ¬	«      D ��ci c]  \  }}|t!        |«      “Œ c}}«      Št        ‰ˆfd
„d¬«      }t#        ||«      ƒ d{  –—† Š	t%        |t!        ˆˆ	fd„|D «       «      «      ƒ d{  –—† S 7 ŒÁ7 Œªc c}}w 7 Œ57 Œ­w)z List shadow eval jobs, newest first, each key with its attempt count so status is
    accurate. Judged counts, spend, and results ride the detail endpoint only.r   rƒ   úview shadow evalsNr    r�   rI   r'  rB  c                 ó.   •— t        d„ ‰|    D «       «      S )Nc              3   ó4   K  — | ]  }|j                   –— Œ y ­wrH   )r-  rD  s     rL   r´   z:list_shadow_eval_jobs.<locals>.<lambda>.<locals>.<genexpr>J  s   è ø€ Ò*X¸c¨3¯>­>Ñ*Xùr?  )r4  )r'  Úby_groups    €rL   r   z'list_shadow_eval_jobs.<locals>.<lambda>J  s   ø€ ¤sÑ*XÀXÈhÑEWÔ*XÓ'X€ rU   Tr!  c              3   ó>   •K  — | ]  }t        |‰|   ‰«      –— Œ y ­wrH   )rH  )r²   r'  r�  Úcountss     €€rL   r´   z(list_shadow_eval_jobs.<locals>.<genexpr>N  s    øè ø€ ÒpÐW_œ_¨X°xÀÑ7IÈ6×RÑpùs   ƒ)r¥   r€   rƒ  r=   r   r¨   r©   r7  r†  r—   r  r  r
   r   rp  r	   rµ   r;  rT  )
r˜   r(  r‡  r€   r8  r'  r  Únewest_firstr�  r‘  s
           @@rL   Úlist_shadow_eval_jobsr“  '  s-  ùè ø€ õ 9äÐ+Ð-@ÔAØÐÜ¨Ô4E×4\Ñ4\×4bÑ4bÔcÐcÜ×+Ñ+ñ ô ˜]Ô,AÀ5È*ÓU×UÐUä! -´ÀÓG×Gò	ð
 ó€Dô :Jô $+¬6°$¼JÀzÓ<RÔ+SÔYcÐdnÓYoÔ#p÷	
á�˜%ð ”e˜E“lÑ"ó	
ó:€Hô !ØÓXÐbfô€Lô .¨m¸TÓB×B€FÜ!Ø”uÔpÐcoÔpÓpó÷ ð ð! VøàGúó
	
ð CøðúsT   „A D4Á$D&Á%D4Á=D(Á>>D4Â<D*
Ã(D4Ã:D0Ã;&D4Ä!D2Ä"D4Ä(D4Ä*D4Ä2D4z!/auto_router/shadow_eval/{job_id}r  c              ƒ   óx  K  — ddl m} t        |d«       |€%t        dt        j
                  j                  ¬«      ‚t        j                  t        |«      j                  d| i¬«      ƒ d{  –—† xs d	«      }|st        d
d| › �¬«      ‚|D �cg c]  }|j                  ‘Œ }}t        j                  t        |t        |«      ƒ d{  –—† xs d	«      }t        |«      j!                  d|iddœddi¬«      ƒ d{  –—† }t#        |t%        | |t'        ||«      ƒ d{  –—† «      f«      ƒ d{  –—† }|d   j)                  |r|d   j*                  nd|r|d   j,                  nd|rt/        |d   j0                  d«      nd|r|j2                  ndt5        ||«      ƒ d{  –—† dœ¬«      S 7 �Œ=c c}w 7 Œé7 Œ¸7 Œ™7 ŒŠ7 Œ­w)zOOne job with derived counts, judge spend, latest error, and stratified results.r   rƒ   rŒ  Nr    r�   r'  r¡   rI   é”  úNo shadow eval job rN  ru   )r  Úoutcomer-  Údesc)rE   r}   r?  r  )r  r  r  Ú
last_errorÚresultsrk  )r¥   r€   rƒ  r=   r   r¨   r©   r7  r†  rŒ   rf   rj   r  r—   rû  r’   r   rT  rH  r;  rm  r  r  r  r  ru   rp  )	r  r˜   r€   r8  r:  ri  r8  Úlatest_errorÚlabeleds	            rL   Úget_shadow_eval_jobr�  R  sÏ  è ø€ õ 9äÐ+Ð-@ÔAØÐÜ¨Ô4E×4\Ñ4\×4bÑ4bÔcÐcÜ×+Ñ+Ü Ó.×8Ñ8Ø˜vÐ&ð 9ó 
÷ 	
ò 	ð ó	€Dñ Ü¨Ð6IÈ&ÈÐ4RÔSÐSØ(,Ö- �c—f“fÐ-€GÐ-Ü(×8Ñ8Ü˜Ô(;¸WÓE×EÒKÈó€Fô !6°mÓ D× OÑ OØ ˜°WÑ=Ø˜VÐ$ð !Pó !÷ €Lô ,Øœ¨°Ô<OÐP]Ð_cÓ<d×6dÓeÐgó÷ €Gð �1‰:× Ñ á6<˜F 1™I×2Ò2À!Ù4:˜6 !™9×0Ò0ÀÙ>Dœ5 ¨¡×!6Ñ!6¸Ô:È#Ù0<˜,×,Ò,À$Ü1°-ÀÓF×Fñ
ð !ó ð ð%	
ûò .àEøðøð
 7eøðøð Gúsy   ‚A)F:Á+F(Á,"F:ÂF+Â!%F:ÃF0Ã2F:Ã9F2Ã: F:ÄF4ÄF:Ä+F6Ä,A0F:ÆF8ÆF:Æ+F:Æ2F:Æ4F:Æ6F:Æ8F:z&/auto_router/shadow_eval/{job_id}/stopc              ƒ   óØ  K  — ddl m} t        |d«       |€%t        dt        j
                  j                  ¬«      ‚t        j                  t        j                  «      }|j                  xs d}|j                  j                  t        | ||j                  d¬«      j!                  «       «      ƒ d{  –—† }t"        j%                  t'        |«      j)                  d	| i¬
«      ƒ d{  –—† xs d«      }|st        dd| › �¬«      ‚t+        ||«      ƒ d{  –—† }t-        | ||«      }|dk(  rt        dd| › d|j.                  › �¬«      ‚t1        ||f«      ƒ d{  –—† }	|	d   S 7 Œ¯7 Œ}7 ŒQ7 Œ­w)aÕ  Stop an active shadow eval job, every key it scopes at once. Attempts are kept;
    sampling halts within ~10s. Keys that already stopped on their own budget keep the
    stopped_at they earned. The statement is the whole state machine: it claims the job
    only while a leg still samples inside the window with no stop recorded, so a racing
    operator, a same-instant budget spend, and a repeat stop all read the same 400 with
    the status the job actually holds.r   rƒ   zstop a shadow evalNr    r�   Úoperatorr  r'  r¡   rI   r•  r–  r¢   zJob z is already )r¥   r€   r’  r=   r   r¨   r©   r   r„  r   r  r}  r†   r{  rþ  r  r…  r7  r†  rŒ   rf   r;  rH  r?   rT  )
r  r˜   r€   ÚstamprŸ  r‚  r8  r‘  Úcurrentrœ  s
             rL   Ústop_shadow_eval_jobr¢  €  sc  è ø€ õ  9äÐ+Ð-AÔBØÐÜ¨Ô4E×4\Ñ4\×4bÑ4bÔcÐcÜ—<‘<¤§¡Ó-€EØ'×/Ñ/Ò=°:€HØ(×+Ñ+×7Ñ7Ü�v˜x¨¯©¸d¨Ó)C×)MÑ)MÓ)Oó÷ €Gô ×+Ñ+Ü Ó.×8Ñ8Ø˜vÐ&ð 9ó 
÷ 	
ò 	ð ó	€Dñ Ü¨Ð6IÈ&ÈÐ4RÔSÐSÜ-¨m¸TÓB×B€FÜ$ V¨T°6Ó:€GØ�!‚|Ü¨°d¸6¸(À,ÈwÏ~É~ÐN^Ð4_Ô`Ð`Ü+¨M¸G¸:ÓF×F€GØ�1‰:Ðð!øð	
øð Cøð GúsI   ‚B0E*Â2E"Â33E*Ã&E$Ã'-E*ÄE&ÄAE*ÅE(Å
E*Å$E*Å&E*Å(E*)Né2   )¯r4  Úcollections.abcr   r   r   r   r   Ú	itertoolsr   rŸ  r	   Útypesr
   Útypingr   r   r   r   Úuuidr   Úpydanticr   r   r   r   r¤  Úlitellm._loggingr   Úlitellm.exceptionsr   Ú$litellm.litellm_core_utils.llm_judger   Úlitellm.proxy._typesr   r   r   r   r   r   Úlitellm.proxy.auth.auth_checksr   r   Ú$litellm.proxy.auth.user_api_key_authr    Ú*litellm.proxy.db.autorouter_session_rollupr!   Ú$litellm.proxy.litellm_pre_call_utilsr"   r#   Ú$litellm.repositories.base_repositoryr$   Ú$litellm.repositories.team_repositoryr%   Ú)litellm.router_strategy.complexity_routerr&   rÑ   r'   r(   r)   Ú8litellm.types.management_endpoints.auto_router_endpointsr*   r+   r,   r-   r.   r/   r0   r1   r2   r3   r4   r5   r6   r7   r8   r9   r:   Úfastapir;   r<   r=   r>   r?   rç   r@   Úlitellm.routerrA   ræ  rB   r  rD   rW   rc   rh   rl   rs   rw   r|   r„   rˆ   rŒ   r�   r’   rS   rT   r—   r®   rµ   rº   rÉ   ÚpostÚHTTP_200_OKrÓ   ró   rõ   ry  r  r  r  r  rq   r#  r6  r9  rY  ra  rï   rr  r_  rŒ  rƒ  r’  rz   r›  r®  rÁ  r½  rÉ  rÒ  rÚ  rá  rí   rè  rê  rõ  rö  r÷  rø  rù  rú  rû  rü  rý  rþ  r   r  r
  r  r  r  r  r  r#  r%  r7  r;  rH  rI  rT  rp  ÚHTTP_201_CREATEDr†  r“  r�  r¢  rI   rU   rL   ú<module>r»     sŒ
  ðò÷ .ß 2Ñ 2Ý Ý Ý "ß <Ó <Ý ç HÓ Hã Ý 1Ý 2Ý =÷÷ ÷õ CÝ P÷õ CÝ ?Ý F÷ñ ÷
÷ ÷ ÷ õ ñ( ßHÕHå0Þ%ðßLÕLñ
 “€ˆÓ ôa�ô aô)˜Hô )ôf˜hô fô˜ô ô
R˜(ô Rô'˜Hô 'ô
i˜Xô iô+˜hô +ð/˜~ð /°*ó /ð6¨ð 6Ð;Ró 6ð2 ^ð 2Ð8Kó 2ð5 ~ð 5Ð:Pó 5ð6¨ð 6Ð<Só 6ð: Nð :¸3ð :Àvð :ÐRZÐ[bÐcfÐhnÐcnÑ[oÑRpó :ð0°~ð 0ÐPSÐVZÑPZð 0Ð_có 0ðfÐ'Dð ÈÈsÐTWÈxÉó ð"&Ø)ð&à%ð&ð ð&ð 
ó	&ðR ‡�Ø4Ø
Ð	ÙÐ+Ó,Ð-Ø;Ø×"Ñ"ð ó ðVØ
1ðVà  ±Ð9JÓ1KÐ!KÑLðVð .òVóðVð, ‡�ØØ
Ð	ÙÐ+Ó,Ð-Ø0Ø×"Ñ"ð ó ðvØ
&ðvà  ±Ð9JÓ1KÐ!KÑLðvð #òvóðvôr�Yô ñ2 ' t¨NÑ';Ó<Ð �5Ó <ð' ð '¨ó 'ð5�Eð 5¨ð 5°%ó 5ðY˜ð Y Cð YÐ,Aó Yð˜>ð Ð.Gó ð>˜.ð Ð-Eó ð(˜( >Ñ2ð °~ó ð6O Vð O°°c¸3°h±À$Ñ0Fó Oð,ØðØ*3°E¸#¸s¸(±OÑ*Dðà
Ð# SÐ(Ñ)óð2 ‡‚ØØ	ÙÐ+Ó,Ð.Ø/ð	 ó ð 	Øjnñ6Ø  ±Ð9JÓ1KÐ!KÑLð6àØˆd‰
‘EÐ&gÔhÐhñð6ð
 ˜˜d™
¡EÐ6eÔ$fÐfÑgð6ð "ò6óð6ðB\¨^ð \ÀSð \ÈTó \ðX¨^ð XÀSð XÈTó Xð
	°Hð 	È3ð 	ÐSWó 	ð°sð ¸tó ð%Øð%Ø(+ð%Ø9<ð%ØHPÐQTÐW[ÑQ[ÑH\ð%à	ó%ðP<˜ #¨¡*Ñ-ð <°#ó <ñ AJÈ6ÐS\ÐJ]Ó@^€ˆu�YÐ;Ñ<Ñ=Ó ^ð ?ð Àð ÈÈuÐUXÐZ]ÐU]ÉÐ`cÐOcÑIdó ð8ØðØ'=ðØHKÈdÉ
ðà
ˆ5��c�‰?˜CÐÑ óð("Øð"Ø'=ð"ØIQÐRUÐX\ÑR\ÑI]ð"à	ó"ðJ
3 	ð 
3¨dó 
3ô	�Yô 	ñ ' t¨NÑ';Ó<Ð �5Ó <ðÐ �Uó ð #RÐTgÑ"gÐ ˜%Ó gØ#SÐViÑ#iÐ ˜5Ó iØ!8Ð;NÑ!NÐ ˜Ó Nð
#Ð ˜%ó ðÐ �Uó ðÐ �Uó ðÐ �Eó ð€ˆuó ô"�yô ô�yô ñ )¨Ð.>Ñ)?Ó@Ð �UÓ @ð€�ó ð Ð �uó ô˜	ô ñ *¨$Ð/@Ñ*AÓBÐ �eÓ Bð(�sð (¨ð (°ó (ð�(˜>Ñ*ð ¨u°_ÀcÐ5IÑ/Jó ô"2ˆiô 2ñD ˜t G™}Ó-€	ˆ5Ó -ð?¨^ð ?À8ÈGÑCTð ?ÐY`ÐadÐfvÐavÑYwó ?ð ØðØ! 'Ñ*ðØ<CÀCÐIYÐDYÑ<Zðàóð@ 8D€��e˜C $™J¨¨d©
Ð2Ñ3Ñ4Ó CðØ!ðØ.6Ð7LÑ.Mðà
Ð  #Ð%Ñ&óð@*¨nð *ÀHÈWÑDUð *ÐZjÐmqÑZqó *ðZ ‡�Ø$Ø	ÙÐ+Ó,Ð.Ø(Ø×'Ò'ð ó ðHØ
 ðHà  ±Ð9JÓ1KÐ!KÑLðHð òHóðHðV ‡‚ØØ	ÙÐ+Ó,Ð.ØÐ-Ñ.ð	 ó ð 	ØVXñ"Ø  ±Ð9JÓ1KÐ!KÑLð"àØˆd‰
‘EÐ&fÔgÐgñð"ð
 �S™% 1¨Ð:QÔRÐRÑSð"ð Ð  #Ð%Ñ&ò"óð"ðJ ‡‚Ø'Ø	ÙÐ+Ó,Ð.Ø(ð	 ó ð%Øð%à  ±Ð9JÓ1KÐ!KÑLð%ð ò%óð%ðP ‡�Ø,Ø	ÙÐ+Ó,Ð.Ø(ð	 ó ð!Øð!à  ±Ð9JÓ1KÐ!KÑLð!ð ò!óñ!øðu) ò âðús   Ã(W> ×>XØX