
    6Qj                         U d dl Z d dlZddlmZ ddlmZ  e j                  e      Z e	       a
e	e   ed<   i aeeef   ed<   ded	efd
Zded	efdZded	efdZ G d de      Zy)    N   )LLMGenerationError   )LLMProvider
_exhausted_rate_limited_untilexcreturnc                    	 ddl }t        | |j                        ryt        | |j                        r?t	        | dd      xs d}|dv ryt        |       j                         }d|v xs
 d|v xs d	|v S 	 	 ddl}t        | |j                        ryt        | |j                  |j                  f      r)t        |       j                         }d
|v xs
 d|v xs d|v S 	 y# t        $ r Y tw xY w# t        $ r Y yw xY w)zaReturns True for unrecoverable billing/quota errors that should permanently blacklist a provider.r   NTcode insufficient_quotaquota_exceededquotaexceeded your currentbillingcreditbalancetoo lowF)
openai
isinstanceAuthenticationErrorRateLimitErrorgetattrstrlowerImportError	anthropicBadRequestError)r	   r   r   msgr   s        E/var/www/html/meridian/meridian-ai/app/providers/fallback_provider.py_is_credit_exhaustedr#      s   c6556c60013+1rD??c(.."Cc>W%<%CWyTWGWW 2c9889cI44i6O6OPQc(.."Cs?Ji3&6J)s:JJ R     s5   C +C 	(C 4C+ A
C+ 	C('C(+	C76C7c                    	 ddl }t        | |j                        r;t        | dd      xs d}|dvr&t	        |       j                         }d|vr	d|vrd|vry		 ddl}t        | |j                        r&t	        |       j                         }d
|vr	d|vrd|vry	t        | dd      xs t        | dd      }|dk(  rt        |        S y# t        $ r Y zw xY w# t        $ r Y Hw xY w)zXReturns True for transient 429 rate-limit errors (retry next provider, don't blacklist).r   Nr   r   r   r   r   r   Tr   r   r   status_codestatusi  F)	r   r   r   r   r   r   r   r   r#   )r	   r   r   r!   r   r&   s         r"   _is_rate_limitedr'   ,   s    	c60013+1rDCC#hnn&#%)3*>CZbeCec9334c(.."Cs"y';	QT@T S-.N'#x2NF}',,,!    s$   AC ?C 	CC	C! C!c                     t        | dd      }|Mt        |di       xs i }|j                  d      xs |j                  d      }|r	 t        dt        |            S y# t        t
        f$ r Y yw xY w)zExtract how many seconds until this provider's rate limit resets.
    Falls back to 60s (typical for Gemini/OpenAI free tier per-minute windows).
    responseNheaderszretry-afterzRetry-Afterr   <   )r   getmaxint
ValueError	TypeError)r	   r)   r*   raws       r"   _extract_retry_afterr2   I   s     sJ-H(Ir28bkk-(FGKK,F1c#h''  	* s   A A0/A0c                       e Zd ZdZdeeeef      ddfdZe	dedz  fd       Z
e	dee   fd       Ze	dee   fd       Ze	dee   fd	       Ze	dee   fd
       Zddedz  ddfdZdedee   defdZy)FallbackProvideruJ  Tries providers in priority order.

    - Credit/quota exhausted → permanently blacklist the provider for this process lifetime.
    - Rate limited (429, transient) → skip to next provider without blacklisting; records
      when the rate-limited provider should be available again.
    - Any other error → re-raise immediately (don't try fallbacks for unexpected failures).

    After a successful call, `last_used` and `skipped` expose which provider handled the
    request and which ones were bypassed (with retry-after info), so callers can surface
    this to the user.
    	providersr
   Nc                 .    || _         d | _        g | _        y N)
_providers
_last_used_skipped)selfr5   s     r"   __init__zFallbackProvider.__init__f   s    #&*$&    c                     | j                   S r7   )r9   r;   s    r"   	last_usedzFallbackProvider.last_usedn   s    r=   c                     | j                   S r7   )r:   r?   s    r"   skippedzFallbackProvider.skippedr   s    }}r=   c                 L    | j                   D cg c]  \  }}|	 c}}S c c}}w r7   )r8   )r;   n_s      r"   nameszFallbackProvider.namesx   s    "oo.da...s    c                     t        j                          }| j                  D cg c]*  \  }}|t        vst        j	                  |d      |k  s)|, c}}S c c}}w )Nr   )timer8   r   r   r,   )r;   nowrD   rE   s       r"   activezFallbackProvider.active|   sH    iik"oonda*1DI\I`I`abdeIfjmImnnns   AAAc                 v    t         D cg c]#  t        fd| j                  D              s"% c}S c c}w )Nc              3   .   K   | ]  \  }}|k(    y wr7    ).0namerE   rD   s      r"   	<genexpr>z-FallbackProvider.exhausted.<locals>.<genexpr>   s     ,V74Q$Y,Vs   )r   anyr8   )r;   rD   s    `r"   	exhaustedzFallbackProvider.exhausted   s(    %Wa,Vdoo,V)VWWWs   #66rO   c                     |r,t         j                  |       t        j                  |d       yt         j	                          t        j	                          y)zHClear exhausted/rate-limited state (all providers, or just one by name).N)r   discardr   popclear)r;   rO   s     r"   resetzFallbackProvider.reset   s=     t$##D$/%%'r=   system_promptmessagesc           
         d | _         g | _        d }t        j                         }| j                  D ]  \  }}|t        v r6t
        j                  d|       | j                  j                  |dd d       Et        j                  |d      |kD  rLt        t        |   |z
        }t
        j                  d||       | j                  j                  |d|d       	 |j                  ||      }|| _         | j                  r2t
        j                  d|| j                  D 	cg c]  }	|	d   	 c}	       |c S  | j                  D 	cg c]  }	|	d   	 }}	t'        dddj)                  |       d| d      c c}	w # t        $ r}
t        |
      rNt
        j                  d	||
       t        j!                  |       | j                  j                  |dd d       |
}n\t#        |
      rPt%        |
      }||z   t        |<   t
        j                  d
||       | j                  j                  |d|d       |
}n Y d }
~
d }
~
ww xY wc c}	w )Nz Skipping exhausted provider '%s'credit_exhausted)rO   reasonretry_after_secondsr   z.Skipping rate-limited provider '%s' (%ds left)rate_limitedz)LLM call succeeded via '%s' (skipped: %s)rO   u7   Provider '%s' out of credit — blacklisting. Error: %su7   Provider '%s' rate-limited for %ds — trying fallback.z"All LLM providers are unavailable.zProviders tried: z, z. Last error: z<. Add credit to a provider or wait for rate limits to reset.)detail)r9   r:   rH   r8   r   loggerdebugappendr   r,   r.   complete_jsoninfo	Exceptionr#   warningaddr'   r2   r   join)r;   rX   rY   last_excrI   rO   provider	secs_leftresultsr	   retry_afterskipped_namess                r"   rc   zFallbackProvider.complete_json   s+   %)iik"oo  	ND(z!?F$$d>Pim%no"&&tQ/#5 3D 9C ?@	MtU^_$$dnen%op!//xH"&==KK KTgkgtgtSubcTUV\T]Suv# 	D -1MM:q6:: 0#DIIm$<#= >'j )MM
 	
' Tv  ',NN#\^bdghNN4(MM(($BTmq)rs"H%c*"6s";K03k0A'-NN#\^bdopMM(($.it)uv"H ;s2   AE< E7,E<I7E<<	IB5I  Ir7   )__name__
__module____qualname____doc__listtupler   r   r<   propertyr@   dictrB   rF   rJ   rR   rW   rc   rM   r=   r"   r4   r4   Y   s    
'$uS+-='>"? 'D ' 3:   d  
 /tCy / / oS	 o o X49 X X(#* ( (0
3 0
$t* 0
 0
r=   r4   )loggingrH   
exceptionsr   baser   	getLoggerrp   r`   setr   r   __annotations__r   rw   floatre   boolr#   r'   r.   r2   r4   rM   r=   r"   <module>r      s      + 			8	$ u
CH  )+ T#u*% *i D 8)  :i C  h
{ h
r=   