o
    ưi:E                    @   s  U d dl Z d dlZd dlZd dlZd dlZd dlZd dlZd dlZd dlZd dl	m
Z
m	Z	mZmZ d dlmZ d dlmZ d dlmZmZmZmZmZmZmZmZmZ d dlmZ d dlmZmZ d dl m!Z!m"Z"m#Z#m$Z$m%Z%m&Z& d d	l'm(Z( d d
l)m*Z*m+Z+ zd dl,m-Z- d dl.m/Z/ d dl0m1Z1 d dl2m3Z3 W n e4y   dZ-dZ1dZ3dZ/Y nw zd dl5Z5W n e4y   e4dw d dl6m7Z7m8Z8 d dlZd dl9Zd dl:Zd dlm;Z;m<Z<m=Z=m>Z>m?Z? d dl@mAZA d dlBmCZCmDZD d dlEmFZFmGZG d dlHmIZI d dlJmKZK d dlLmMZMmNZN d dlOmPZP d dlQmRZR d dlSmTZT d dl:mUZU d dlVmWZW d dlXmYZY d dlZm[Z[ d dl m\Z\m]Z]m^Z^m_Z_m`Z` d d lambZb d d!lcmdZdmeZe d d"lfmgZg d d#lhmiZi d d$ljmkZk d d%llmmZm d d&lnmoZo d d'lpmqZqmrZr d d(lsmtZt d d)lumvZv d d*lwmxZx d d+lymzZz d d,l{m|Z| d d-l}m~Z~ d d.lmZ d d/lmZmZmZ d d0lmZ d d1l)mZmZ erd d2lmZ d dl:mUZ eeef ZneZeo Zd3d4 Zd5d6 ZG d7d8 d8ZG d9d: d:Zd;d< Zd=ed>efd?d@ZeIdAdBZeIedC< dDZdEedFed>ee fdGdHZG dIdJ dJZdKedLeFdEefdMdNZ			ddOee dPee dQee fdRdSZdTefdUdVZdTed>efdWdXZG dYdZ dZZd[ed\ee[ d]efd^d_Zd[ed\ee[ d]efd`daZd[ed\ee[ d]efdbdcZddedeed]efdfdgZdhe
d>e
fdidjZdkedlee fdmdnZdkedlee d>ee fdodpZddqdrZdsdt Zd=eduee? d>efdvdwZd=edxed>efdydzZdded>efd{d|Zd>ee fd}d~Zd>ee fddZdded>e$fddZddee fddZdee duee? d>efddZded>efddZdeded>efddZddedee d>efddZd>ee fddZd>efddZdefddZded>efddZd>ee fddZ							ddddued dedee d[edJ d]ed: dee dedededed d>ee fddZ			ddedededee dued d>efddZdedee d>dfddZg dZeeeef  ed< 		ddedeee  ded>eeef fddZdS )    N)datedatetime	timedeltatimezone)MIMEMultipart)MIMEText)	TYPE_CHECKINGAnyDictListLiteralOptionalUnioncastoverload)+_custom_logger_compatible_callbacks_literal)DEFAULT_MODEL_CREATED_AT_TIMEMAX_TEAM_LIST_LIMIT)DB_CONNECTION_ERROR_TYPESCommonProxyErrorsProxyErrorTypesProxyExceptionSpendLogsMetadataSpendLogsPayloadGuardrailEventHooks)	CallTypesCallTypesLiteral)BaseEmailLogger)ResendEmailLogger)SendGridEmailLogger)SMTPEmailLoggerzEbackoff is not installed. Please install it via 'pip install backoff')HTTPExceptionstatus)EmbeddingResponseImageResponseModelResponseModelResponseStreamRouter)verbose_proxy_logger)ServiceLoggingServiceTypes)	DualCache
RedisCache)LimitedSizeOrderedDict)RejectedRequestError)CustomGuardrailModifyResponseException)CustomLogger)SlackAlerting)_add_langfuse_trace_id_to_alert)Logging)
safe_dumps)safe_json_loads)AsyncHTTPHandler)	AlertTypeCallInfoLiteLLM_VerificationTokenViewMemberUserAPIKeyAuth)RouteChecks)create_missing_viewsshould_create_missing_views)DBSpendUpdateWriter)PrismaDBExceptionHandler)log_db_metrics)PrismaWrapper)UnifiedLLMGuardrails)PROXY_HOOKSget_proxy_hook)_PROXY_CacheControlCheck)_PROXY_MaxBudgetLimiter)!_PROXY_MaxParallelRequestsHandler)LiteLLMProxyRequestSetup)PipelineExecutor)str_to_bool)DEFAULT_ALERT_TYPES)MCPDuringCallResponseObjectMCPPreCallRequestObjectMCPPreCallResponseObject)PipelineExecutionResult)LLMResponseTypesLoggedLiteLLMParams)Spanc                 C   s:   ddl }td| |  tjrtd|   dS dS )a  
    Prints the given `print_statement` to the console if `litellm.set_verbose` is True.
    Also logs the `print_statement` at the debug level using `verbose_proxy_logger`.

    :param print_statement: The statement to be printed and logged.
    :type print_statement: Any
    r   Nz{}
{}zLiteLLM Proxy: )	tracebackr)   debugformat
format_exclitellmZset_verboseprint)Zprint_statementrV    r\   J/home/app/Keep/.python/lib/python3.10/site-packages/litellm/proxy/utils.pyprint_verbose   s
   r^   c                   C   sR   t du rdS tdurtdrtS tdurtdrtS tdur'tdr'tS t S )z
    Determine which email logger class to use based on environment variables.
    Priority: SendGrid > Resend > SMTP > BaseEmailLogger (fallback)

    Returns:
        The email logger class to use, or None if BaseEmailLogger is not available
    NZSENDGRID_API_KEYZRESEND_API_KEY	SMTP_HOST)r   r    osgetenvr   r!   r\   r\   r\   r]   _get_email_logger_class   s   rb   c                	   @   s   e Zd ZdefddZ	ddeedf dedefd	d
Z		ddeedf deddfddZ
	ddedeedf deddfddZ		ddedee defddZ	ddedeedf defddZ	ddeddfddZ	ddedefddZdS )InternalUsageCache
dual_cachec                 C   s
   || _ d S Nrd   )selfrd   r\   r\   r]   __init__   s   
zInternalUsageCache.__init__Flitellm_parent_otel_spanN
local_onlyreturnc                    "   | j jd|||d|I d H S )N)keyrj   parent_otel_spanr\   )rd   async_get_cache)rg   rm   ri   rj   kwargsr\   r\   r]   ro         z"InternalUsageCache.async_get_cachec                    $   | j jd||||d|I d H S )N)rm   valuerj   ri   r\   )rd   async_set_cacherg   rm   rs   ri   rj   rp   r\   r\   r]   rt         z"InternalUsageCache.async_set_cache
cache_listc                    rl   )N)rw   rj   ri   r\   )rd   Zasync_set_cache_pipeline)rg   rw   ri   rj   rp   r\   r\   r]   async_batch_set_cache   rq   z(InternalUsageCache.async_batch_set_cachekeysrn   c                    s   | j j|||dI d H S )N)ry   rn   rj   )rd   async_batch_get_cache)rg   ry   rn   rj   r\   r\   r]   rz      s   z(InternalUsageCache.async_batch_get_cachers   c                    rr   )N)rm   rs   rj   rn   r\   )rd   async_increment_cacheru   r\   r\   r]   r{      rv   z(InternalUsageCache.async_increment_cachec                 K   s   | j jd|||d|S )N)rm   rs   rj   r\   )rd   	set_cache)rg   rm   rs   rj   rp   r\   r\   r]   r|     s   zInternalUsageCache.set_cachec                 K   s   | j jd||d|S )N)rm   rj   r\   )rd   	get_cache)rg   rm   rj   rp   r\   r\   r]   r}     s   zInternalUsageCache.get_cacheF)NF)__name__
__module____qualname__r,   rh   r   rU   boolr	   ro   rt   r   rx   listr   rz   floatr{   r|   r}   r\   r\   r\   r]   rc      sr    









rc   c                   @   s  e Zd ZdZ	ddedefddZdee dee	 fd	d
Z
							ddee dee dee	 deee  dee dee dee fddZddee fddZdedee fddZddee fddZdeded fd d!Zd"d# Zd$edefd%d&Zdee fd'd(Zd)edee fd*d+Zd,ed-edee fd.d/Zdee fd0d1Zd2ee d3edefd4d5Zd6ed7e de!eef fd8d9Z"d$edd:fd;d<Z#d=ee d>edefd?d@Z$dAdB Z%dCedefdDdEZ&	ddFdGdHedIedJee' dKe(d6ee defdLdMZ)	ddCedHedIedJee' dKe(d6ee defdNdOZ*dFe+dIedJee' dKe(dPe,dee fdQdRZ-dIedSedTedUedKe(ddfdVdWZ.dIeddfdXdYZ/dIedJe'dKedZedef
d[d\Z0e1d]edIed^edefd_d`Z2e3dJe'dIddKe(ddfdadbZ4e3dJe'dIedKe(defdcdbZ4dJe'dIee dKe(dee fdddbZ4dIedJee' dKe(fdedfZ5dgedhefdidjZ6dkedl dme7fdndoZ8	ddpedqedr dsedtee fdudvZ9	wddxedKefdydzZ:			ddted{e;dJe'd|ee< d}ee d~ee dee= fddZ>		dd{e;d|ee< d}ee defddZ?		ddtedJe'd}ee d{ee; fddZ@dIed6eAdJe'fddZB	ddIedJe'd6edee!eef  de!eef f
ddZCd6edefddZD	ddIed6eEeFeGeHeIf dJe'dee fddZJdJe'dtefddZKddIee fddZLdS )ProxyLoggingz
    Logging/Custom Handlers for proxy.

    Implemented mainly to:
    - log successful/failed db read/writes
    - support the max parallel request integration
    Fuser_api_key_cachepremium_userc                 C   s   i | _ || j d< ttddd| _t| j| _t | _t | _	d | _
d| _t| _d | _t| j| j
| jjd| _d | _td urMt }|d urM|| jjd| _|| _t | _t | _i | _d| _d| _d S )	Nr      )Zdefault_in_memory_ttlrf   i,  )alerting_thresholdalertinginternal_usage_cache)r   F)call_detailsrc   r,   r   rJ   Zmax_parallel_request_limiterrI   Zmax_budget_limiterrH   Zcache_control_checkr   r   rN   alert_typesalert_to_webhook_urlr3   rd   slack_alerting_instanceemail_logging_instancer   rb   r   r*   service_logging_objrA   db_spend_update_writerproxy_hook_mappingdaily_report_startedhanging_requests_check_started)rg   r   r   Zemail_logger_classr\   r\   r]   rh   )  s@   

zProxyLogging.__init__
llm_routerredis_usage_cachec                 C   s   | j j|d | j|d | j|d | j dur.d| j jv r.| js.t| j j|d d| _| j durKtj	| j jv rM| j
sOt| j j  d| _
dS dS dS dS )z0Initialize logging and alerting on proxy startupr   )redis_cacheNdaily_reportsT)r   update_values_init_litellm_callbacksr   r   asynciocreate_taskZ_run_scheduled_daily_reportr9   Zllm_requests_hangingr   Zhanging_request_checkZcheck_for_hanging_requests)rg   r   r   r\   r\   r]   startup_eventS  s:   



zProxyLogging.startup_eventNr   r   r   r   alerting_argsr   alert_type_configc           	      C   s   d}|d ur|| _ d}|d ur|| _d}|d ur|| _d}|d ur&|| _d}|d ur,d}|du rh| jj| j | j| j|| j|d | j d urhd| j v rhd| jv sYd| jv sYd| jv r`tj| j tj	| jj
 |d ur}|| jj_|| jj_|| jj_d S d S )NFT)r   r   r   r   r   r   slackr   Zoutage_alertsZregion_outage_alerts)r   r   r   r   r   r   rZ   logging_callback_manageradd_litellm_callbackZadd_litellm_success_callbackZ!response_taking_too_long_callbackr   rd   r   r   Zredis_update_bufferZpod_lock_manager)	rg   r   r   r   r   r   r   r   Zupdated_slack_alertingr\   r\   r]   r   |  sJ   
	




zProxyLogging.update_valuesc           	      C   s   ddl m} tD ]8}t|}ddl}||j}i }d|v r#| j|d< d|v r+||d< tt	|di |}t
j| || j|< qdS )z6
        Add proxy hooks to litellm.callbacks
        r   prisma_clientNr   r   r\   )litellm.proxy.proxy_serverr   rF   rG   inspectgetfullargspecargsr   r   r2   rZ   r   r   r   )	rg   r   r   hookZ
proxy_hookr   Zexpected_argsZpassed_in_argsZproxy_hook_objr\   r\   r]   _add_proxy_hooks  s   
zProxyLogging._add_proxy_hooksr   rk   c                 C   s   | j |S )z>
        Get a proxy hook from the proxy_hook_mapping
        )r   get)rg   r   r\   r\   r]   rG     s   zProxyLogging.get_proxy_hookc                 C   s   |  | tj| j i }ttjD ] \}}t|tr3tj	j
jtt|| jj|d}|d ur3|||< q| D ]	\}}|tj|< q8d S )N)r   r   )r   rZ   r   r   r   	enumerate	callbacks
isinstancestrlitellm_core_utilslitellm_loggingZ$_init_custom_logger_compatible_classr   r   r   rd   items)rg   r   Zstring_callbacks_to_replaceidxcallbackZinitialized_callbackr\   r\   r]   r     s    

z$ProxyLogging._init_litellm_callbackslitellm_call_idr#   )successfailc                    sD   | j d u rd S | j}|d7 }| jjd||d|d dI d H  d S )Nd   zrequest_status:{}T)rm   rs   rj   ttlri   )r   r   r   rt   rX   )rg   r   r#   r   r\   r\   r]   update_request_status  s   
z"ProxyLogging.update_request_statusc                 C   s.   |durt |dr| S t |dr|jS i S )z
        Helper function to convert UserAPIKeyAuth object to dictionary.
        Handles both Pydantic models and regular objects.
        N
model_dump__dict__)hasattrr   r   )rg   Zuser_api_key_auth_objr\   r\   r]   "_convert_user_api_key_auth_to_dict  s   

z/ProxyLogging._convert_user_api_key_auth_to_dictrp   c              
   C   st   ddl m} d|j d|j }|d|d}|g|dd|d	|d
|d|d|d|j|jd	}|S )z`
        Convert MCP tool call to LLM message format for existing guardrail validation.
        r   )ChatCompletionUserMessageTool: 
Arguments: user)rolecontentmodelzmcp-tool-calluser_api_key_user_iduser_api_key_team_iduser_api_key_end_user_iduser_api_key_hashuser_api_key_request_route)	messagesr   r   r   r   r   r   Zmcp_tool_nameZmcp_arguments)Zlitellm.types.llms.openair   	tool_name	argumentsr   )rg   request_objrp   r   Ztool_call_contentZsynthetic_messageZsynthetic_datar\   r\   r]   _convert_mcp_to_llm_format	  s"   
z'ProxyLogging._convert_mcp_to_llm_formatc           	   
   C   s,  ddl m} t|tr|dt|ddS t|tr|d}|rd|j d|j }|r4|d d	d
nd
}||kr|rHd|	 v sHd|	 v rO|ddddS z| 
||}|durb|dd|dW S td|  W dS  ty } ztd|  W Y d}~dS d}~ww t|tr|d|ddS dS )zK
        Convert LLM guardrail result back to MCP response format.
        r   )rQ   FN)should_proceederror_messagemodified_argumentsr   r   r   r    blockedZ	violationzContent blocked by guardrailTz<Could not parse modified arguments from guardrail response: z"Error parsing modified arguments: )litellm.types.mcprQ   r   	Exceptionr   dictr   r   r   lower(_extract_modified_arguments_from_contentr)   warningerror)	rg   
llm_resultr   rQ   modified_messagesoriginal_contentnew_contentmodified_argser\   r\   r]   #_convert_llm_result_to_mcp_response'  sf   



z0ProxyLogging._convert_llm_result_to_mcp_responsemasked_contentc           
      C   s&  ddl }td|  zk| d}t|D ]W\}}|dro|tdd  }td|  z||}td|  |W   W S  |j	yn }	 ztd| d	|	  | 
||jW  Y d}	~	  W S d}	~	ww qtd
 W dS  ty }	 ztd|	  W Y d}	~	dS d}	~	ww )zX
        Extract modified/masked arguments from the guardrail response content.
        r   Nz'Extracting modified args from content: 
z
Arguments:zFound arguments text: zSuccessfully parsed JSON args: z Failed to parse JSON arguments: z	, error: z2Could not find 'Arguments:' line in masked contentz%Error extracting modified arguments: )jsonr)   rW   stripsplitr   
startswithlenloadsJSONDecodeError_parse_arguments_manuallyr   r   r   r   )
rg   r   r   r   linesiline	args_textr   r   r\   r\   r]   r   u  sF   

z5ProxyLogging._extract_modified_arguments_from_contentr   original_argsc              
   C   s   ddl }z5| }| D ])\}}t|tr6d|| d}||||j}|r6|d	 }	|	r6|	||< q|W S  t
yT }
 ztd|
  W Y d}
~
dS d}
~
ww )z
        Try to manually parse arguments when JSON parsing fails.
        This is a fallback for cases where the guardrail modifies the format.
        r   Nz['\"]?z#['\"]?\s*:\s*['\"]?([^,'\"]*)['\"]?r   z"Error in manual argument parsing: )recopyr   r   r   escapesearch
IGNORECASEgroupr   r   r)   r   )rg   r   r   r   r   rm   original_valuepatternmatch	new_valuer   r\   r\   r]   r     s&   
z&ProxyLogging._parse_arguments_manuallyc                 C   s   t |trtdt|dS t |trG|d}|rGd|j d|j }|r-|d ddnd}||krG|r;d	| v rAtdd
dS tdddS t |trRtd|dS dS )zW
        Convert LLM guardrail result back to MCP during call response format.
        F)Zshould_continuer   r   r   r   r   r   r   r   z-Content blocked by guardrail during executionz.Content modified by guardrail during executionN)	r   r   rO   r   r   r   r   r   r   )rg   r   r   r   r   r   r\   r\   r]   *_convert_llm_result_to_mcp_during_response  s4   



z7ProxyLogging._convert_llm_result_to_mcp_during_responsedynamic_success_callbacksglobal_callbacksc                 C   s    |d u rt |S t t|| S re   )r   set)rg   r   r   r\   r\   r]   get_combined_callback_list  s   z'ProxyLogging.get_combined_callback_listresponseoriginal_requestc                 C   s    |j |jp|j|j|jd}|S )z
        Parse the response from the pre_mcp_tool_call_hook

        1. Check if the call should proceed
        2. Apply any argument modifications
        3. Handle validation errors
        )r   r   r   hidden_params)r   r   r   r   r  )rg   r  r  resultr\   r\   r]   !_parse_pre_mcp_call_hook_response  s   z.ProxyLogging._parse_pre_mcp_call_hook_responserP   c                 C   sR   ddl m} ddlm} | |d}||dd|di |d|| d	S )
zk
        Helper function to create MCPPreCallRequestObject from kwargs for standard pre_call_hook.
        r   )HiddenParams)rP   user_api_key_authnamer   r   server_name)r   r   r  r	  r  )Zlitellm.types.llms.baser  r   rP   r   r   )rg   rp   r  rP   user_api_key_auth_dictr\   r\   r]   &_create_mcp_request_object_from_kwargs  s   

z3ProxyLogging._create_mcp_request_object_from_kwargsresponse_dataoriginal_kwargsc                 C   s*   |s|S |  }|dr|d |d< |S )za
        Helper function to convert pre_call_hook response back to kwargs for MCP usage.
        r   r   )r   r   )rg   r  r  Zmodified_kwargsr\   r\   r]   $_convert_mcp_hook_response_to_kwargs'  s   
z1ProxyLogging._convert_mcp_hook_response_to_kwargsc                    s\   t |tr|t |tr|S t |tr,|dv r$t||ddd|dtdd|id|S )N)
completionZtext_completionr   r   )messager   Zllm_providerrequest_data  r   status_codedetail)r   r   r   r   r/   r   r"   )rg   r  data	call_typer\   r\   r]   process_pre_call_hook_response9  s   



z+ProxyLogging.process_pre_call_hook_responseguardrail_namec                    sB   ddl m} |du st|dsdS  fdd|jD }t|dkS )	z
        Check if load balancing should be used for this guardrail.

        Returns True if the router has multiple deployments for this guardrail name.
        r   r   Nguardrail_listFc                    s   g | ]}| d  kr|qS r  )r   ).0gr  r\   r]   
<listcomp>X  s
    zEProxyLogging._should_use_guardrail_load_balancing.<locals>.<listcomp>r   )r   r   r   r  r   )rg   r  r   Zmatchingr\   r  r]   $_should_use_guardrail_load_balancingJ  s   	
z1ProxyLogging._should_use_guardrail_load_balancingr   r0   	hook_typer  user_api_key_dictr  c           	         s   dt |jv }|r||d< |rtn|}|dkr'|j|| jd ||dI dH S |dkr6|j|||dI dH S |d	krE|j|||d
I dH S td| )a  
        Execute a single guardrail's hook.

        Args:
            callback: The guardrail callback to execute
            hook_type: One of "pre_call", "during_call", "post_call"
            data: Request data
            user_api_key_dict: User API key auth
            call_type: Type of call
            response: Response object (for post_call hooks)

        Returns:
            Result from the guardrail execution
        apply_guardrailguardrail_to_applypre_callr   r#  cacher  r  Nduring_callr  r#  r  	post_callr#  r  r  zUnknown hook_type: )typer   unified_guardrailasync_pre_call_hookr   async_moderation_hookasync_post_call_success_hook
ValueError)	rg   r   r"  r  r#  r  r  Zuse_unifiedtargetr\   r\   r]   _execute_guardrail_hook_  s2   z$ProxyLogging._execute_guardrail_hookc           
         sf   ddl m} |du rtd|j|d}|d}	|	du r%td| | j|	|||||dI dH S )	a  
        Execute a guardrail using the router's load balancing.

        Args:
            guardrail_name: Name of the guardrail
            hook_type: One of "pre_call", "during_call", "post_call"
            data: Request data
            user_api_key_dict: User API key auth
            call_type: Type of call
            response: Response object (for post_call hooks)

        Returns:
            Result from the guardrail execution
        r   r   NzRouter not initializedr  r   z!No callback found for guardrail: )r   r"  r  r#  r  r  )r   r   r2  Zget_available_guardrailr   r4  )
rg   r  r"  r  r#  r  r  r   Zselected_guardrailr   r\   r\   r]   &_execute_guardrail_with_load_balancing  s$   
z3ProxyLogging._execute_guardrail_with_load_balancing
event_typec                    s  ddl m} ddlm} ||ju r|tjjkr|j}|j	||ddur&dS |j
}t }	d}
d}zwz3|rH| |rH| j|d|||d	I dH }n| j|d|||d
I dH }|durd| j|||dI dH }W n tyy } zd}
t|j} d}~ww W t }||	 }|pt|d|jjpd}tjD ]}t||r|j|||
|dd  |S q|S t }||	 }|pt|d|jjpd}tjD ]}t||r|j|||
|dd  w qw )a  
        Process a guardrail callback during pre-call hook.

        Supports load balancing when multiple guardrail deployments exist.

        Args:
            callback: The CustomGuardrail callback to process
            data: The request data dictionary
            user_api_key_dict: User API key authentication details
            call_type: The type of API call being made

        Returns:
            Updated data dictionary if guardrail passes, None if guardrail should be skipped
        r   )PrometheusLoggerr   r  r6  TNr   r&  )r  r"  r  r#  r  )r   r"  r  r#  r  r  r  r  r   r  unknown)r  latency_secondsr#   
error_typer"  )Zlitellm.integrations.prometheusr7  litellm.types.guardrailsr   r&  r   call_mcp_toolrs   Zpre_mcp_callshould_run_guardrailr  timeperf_counterr!  r5  r4  r  r   r-  r   getattr	__class__rZ   r   r   Z_record_guardrail_metrics)rg   r   r  r#  r  r6  r7  r   r  Zguardrail_start_timer#   r<  r  r   Zguardrail_end_timer;  Zmetrics_guardrail_nameZprom_callbackr\   r\   r]   _process_guardrail_callback  s   
		





z(ProxyLogging._process_guardrail_callbacklitellm_logging_obj	prompt_idprompt_versionc                    sJ  ddl m}m} ddlm} ddlm}	 |du r!|||jd}
n|||d}
||
}|	|
}d}|durA|j
j}|dd |r|dur|j|d	d
|dg |	|dpYi ||||ddpci |ddpji |ddpqi d	I dH \}}}|| ||d	< ||d< |dd |dd |dd |dd dS dS dS )z&Process prompt template if applicable.r   )construct_versioned_prompt_idget_latest_version_prompt_id)IN_MEMORY_PROMPT_REGISTRY)!get_non_default_completion_paramsN)rF  Zall_prompt_ids)rF  versionrF  r   r   r   )rp   prompt_variablesprompt_labelrG  )	r   r   Znon_default_paramsrF  prompt_specZprompt_management_loggerrM  rN  rG  )Z&litellm.proxy.prompts.prompt_endpointsrH  rI  Z%litellm.proxy.prompts.prompt_registryrJ  Zlitellm.utilsrK  ZIN_MEMORY_PROMPTSZget_prompt_callback_by_idZget_prompt_by_idlitellm_paramsrF  popZ async_get_chat_completion_promptr   update)rg   r  rE  rF  rG  r  rH  rI  rJ  rK  Zlookup_prompt_idZcustom_loggerrO  Zlitellm_prompt_idr   r   optional_paramsr\   r\   r]   _process_prompt_template(  sX   





z%ProxyLogging._process_prompt_templatec                 C   s   ddl m} |dpi }|dpi }g }t|tr&d|v r&|dg }nt|tr5d|v r5|dg }|rxt|trzg }t|trNd|v rN|dg }nt|tr]d|v r]|dg }t|tsdg }|D ]}t|trw||vrw|||d qfdS dS dS )	z?Process guardrails from metadata and add to applied_guardrails.r   )*add_guardrail_to_applied_guardrails_headermetadatalitellm_metadata
guardrailsapplied_guardrails)r  r  N)Z)litellm.proxy.common_utils.callback_utilsrU  r   r   r   r   r   )rg   r  rU  Zmetadata_standardZmetadata_litellmZguardrails_in_metadatarY  r  r\   r\   r]   _process_guardrail_metadataf  s<   
z(ProxyLogging._process_guardrail_metadata
event_hookc           
   	      sz   | d| di pi }| d}|s|S |D ]"\}}|j|kr"qtj|j|j||||dI dH }	| j|	||d}q|S )a  
        Execute guardrail pipelines if any are configured for this request.

        Checks metadata for pipelines resolved by the policy engine
        and executes them. Handles the result (allow/block/modify_response).

        Returns the (possibly modified) data dict.
        rV  rW  Z_guardrail_pipelines)stepsmoder  r#  r  policy_nameN)r  r  r^  )r   r]  rL   Zexecute_stepsr\  _handle_pipeline_result)
rg   r  r#  r  r[  rV  Z	pipelinesr^  Zpipeliner  r\   r\   r]   _maybe_execute_pipelines  s,   

	z%ProxyLogging._maybe_execute_pipelinesr  r^  c                 C   s   | j dkr| jdur|| j |S | j dkr4dd | jD }dd| dd	||d
di}td|d| j dkrLt| jp>d|dd|d| dd|S )u   
        Handle a PipelineExecutionResult — allow, block, or modify_response.

        Returns data dict if allowed, raises on block/modify_response.
        ZallowNblockc                 S   s   g | ]}|j |j|jd qS ))Z	guardrailoutcomeaction)r  rb  Zaction_taken)r  srr\   r\   r]   r     s    z8ProxyLogging._handle_pipeline_result.<locals>.<listcomp>r   z'Content blocked by guardrail pipeline ''Zguardrail_pipeline_error)policystep_results)r  r-  Zpipeline_contextr  r  Zmodify_responsezResponse modified by pipeliner   r:  z	pipeline:)r  r   r  r  Zdetection_info)Zterminal_actionmodified_datarR  rg  r"   r1   Zmodify_response_messager   )r  r  r^  Zstep_results_serializableZerror_detailr\   r\   r]   r_    s6   


	



	z$ProxyLogging._handle_pipeline_resultc                       d S re   r\   rg   r#  r  r  r\   r\   r]   pre_call_hook     zProxyLogging.pre_call_hookc                    ri  re   r\   rj  r\   r\   r]   rk    rl  c              
      sb  t d | j|d |du rdS ttd |dd}|dd}|dd}|durF|durF|dks9|d	krF| j|||||d
I dH  z| j|||ddI dH }|d|di p^i }|dt }t	j
D ]}	t }
d}t|	trt	jjtt|	}n|	}|durt|tr|dur|jr|j|v rqi| j||||tjdI dH }|du rqi|}n>|durt|trdt|jv r|jjtjkr|dkr|du rqi|j|| jd ||dI dH }|dur| j|||dI dH }t }||
 }t| dr|dkr| jjt j!||jj" |j#|
|dI dH  qi|dur| $| |W S  t%y0 } z|d}~ww )z
        Allows users to modify/reject the incoming request to the proxy, without having to deal with parsing Request body.

        Covers:
        1. /chat/completions
        2. /embeddings
        3. /image/generation
        z#Inside Proxy Logging Pre-call hook!r  NLiteLLMLoggingObjrE  rF  rG  r  acompletion)r  rE  rF  rG  r  r&  )r  r#  r  r[  rV  rW  Z_pipeline_managed_guardrails)r   r  r#  r  r6  r/  r>  r   r'  r9  r   g{Gz?)servicedurationr  rn   
start_timeend_time)&r)   rW   #_init_response_taking_too_long_taskr   r   r   rT  r`  r  rZ   r   r@  r   r   r   r   "get_custom_logger_compatible_classr   r0   r  rD  r   r&  r2   varsrC  r/  r   r  r   r   Zasync_service_success_hookr+   ZPROXY_PRE_CALLr   rn   rZ  r   )rg   r#  r  r  rE  rF  rG  rV  Zpipeline_managedr   rr  	_callbackr  r  rs  rq  r   r\   r\   r]   rk    s   





	
c              
      s  g }t jD ]i}t|tro|jdu r t|dr |jdkr dS nddlm} |j	}|t
jjkr2|j}|j||ddur<q|t
jjkrH| |}n|}dt|jv rb|durb||d	< tj|||d
}	n|j|||d}	||	 q|rztj| I dH  W |S  ty }
 z|
d}
~
ww |S )zP
        Runs the CustomGuardrail's async_moderation_hook() in parallel
        Nmoderation_checkr&  r   r   r8  Tr$  r%  )r#  r  r  r*  )rZ   r   r   r0   r[  r   rx  r=  r   r)  r   r>  rs   Zduring_mcp_callr?  r   r-  r   r.  r0  appendr   gatherr   )rg   r  r#  r  Zguardrail_tasksr   r   r6  r  Zguardrail_taskr   r\   r\   r]   during_call_hook|  s^   




zProxyLogging.during_call_hookr   failing_modelc                    s4   | j d u rd S | jr| jj||dI d H  d S d S )N)r   r|  )r   r   failed_tracking_alert)rg   r   r|  r\   r\   r]   r}    s   
z"ProxyLogging.failed_tracking_alertr-  )	Ztoken_budgetZuser_budgetsoft_budgetZmax_budget_alertZteam_budgetZorganization_budgetZproxy_budgetZprojected_limit_exceededZproject_budget	user_infoc                    s   |dko|j d uot|j dk}| jd u r|sd S | jd ur4d| jv r4| jd ur4| jj||dI d H  | jd uo=d| jv p?|}|rT| jd urV| jj||dI d H  d S d S d S )Nr~  r   r   )r-  r  email)Zalert_emailsr   r   r   budget_alertsr   )rg   r-  r  Z is_soft_budget_with_alert_emailsZshould_send_emailr\   r\   r]   r    s0   
	zProxyLogging.budget_alertsr  level)ZLowZMediumHigh
alert_typer  c              	      sF  | j du rdS ddlm} | d}tdd}d| d| d| }|dur2|d	| d
7 }i }	i }
|durnt|dI dH }|durQ||	d< |d|7 }d|v rn|d dddurnt	|d d t
rn|d d }
| j D ]/}|dkr| jjd|||d|
d|	I dH  qq|dkrtjjdurtjj| qqtdqqdS )a  
        Alerting based on thresholds: - https://github.com/BerriAI/litellm/issues/1298

        - Responses taking too long
        - Requests are hanging
        - Calls are failing
        - DB Read/Writes are failing
        - Proxy Close to max budget
        - Key Close to max budget

        Parameters:
            level: str - Low|Medium|High - if calls might fail (Medium) or are failing (High); Currently, no alerts would be 'Low'.
            message: str - what is the alert about
        Nr   )r   z%H:%M:%SPROXY_BASE_URLzLevel: `z`
Timestamp: `z`

Message: z

Proxy URL: ``r  u   🪢 Langfuse Traceu   

🪢 Langfuse Trace: {}rV  alerting_metadatar   )r  r  r  r  r  Zsentryz#Missing SENTRY_DSN from environmentr\   )r   r   nowstrftimer`   ra   r4   rX   r   r   r   r   Z
send_alertrZ   utilsZsentry_sdk_instanceZcapture_messager   )rg   r  r  r  r  r   current_timeZ_proxy_base_urlZformatted_messageextra_kwargsr  _urlclientr\   r\   r]   alerting_handler  sN   

zProxyLogging.alerting_handlerr   rq  c                    s   t j| jvr	dS t|tr*t|jtr|j}nt|jtr%t	|j}n	t|}nt|}t|tr;||dd 7 }t
| jd| dt ji d t| dr^| jjtj|||dI dH  tjjrktjj|d dS dS )	z]
        Log failed db read/writes

        Currently only logs exceptions to sentry
        N  zDB read/write call failed: r  r  r  r  r  r   )rp  rq  r   r  )r   )r9   Zdb_exceptionsr   r   r"   r  r   r   r   dumpsr   r   r  r   r   Zasync_service_failure_hookr+   ZDBrZ   r  Zcapture_exception)rg   original_exceptionrq  r  traceback_strr   r\   r\   r]   failure_handlerE  s<   	



	zProxyLogging.failure_handlerr  r<  router  c                    s  | j |ddddI dH  tj| jv r>t|ts>	 t|dd}t|}|dur-||7 }t	
| jd| dd	tj|d
 | j|||jdrS| j||||dI dH  d}	tjD ]}
zjd}t|
trmtjjtt|
}n|
}|durt|trz|j||||dI dH }t|tr|	du r|}	W n1 ty } z|	du r|}	W Y d}~nd}~w ty } ztd|  W Y d}~nd}~ww W qX ty } ztd|  W Y d}~qXd}~ww |	S )a$  
        Allows users to raise custom exceptions/log when a call fails, without having to deal with parsing Request body.
        Callbacks can return or raise HTTPException to transform error responses sent to clients.

        Covers:
        1. /chat/completions
        2. /embeddings
        3. /image/generation

        Args:
            - request_data: dict - The request data.
            - original_exception: Exception - The original exception.
            - user_api_key_dict: UserAPIKeyAuth - The user api key dict.
            - error_type: Optional[ProxyErrorTypes] - The error type.
            - route: Optional[str] - The route.
            - traceback_str: Optional[str] - The traceback string, sometimes upstream endpoints might need to send the upstream traceback. In which case we use this

        Returns:
            - Optional[HTTPException]: If any callback returns or raises an HTTPException, the first one found is returned.
                                      Otherwise, returns None and the original exception is used.
        r   r   r   )r   r#   Nlitellm_debug_infozLLM API call failed: `r  r  r  )r  r<  r  )r  r#  r  r  )r  r#  r  r  z?[Non-Blocking] Error in async_post_call_failure_hook callback: zA[Non-Blocking] Error setting up post_call_failure_hook callback: )r   r   r9   Zllm_exceptionsr   r   r"   rB  r   r   r   r  _is_proxy_only_llm_api_errorZrequest_route _handle_logging_proxy_only_errorrZ   r   r   r   ru  r   r   r2   Zasync_post_call_failure_hookr   r)   	exception)rg   r  r  r#  r<  r  r  r  Zexception_strZtransformed_exceptionr   rw  Zhook_resultr   r\   r\   r]   post_call_failure_hooko  s    



z#ProxyLogging.post_call_failure_hookc                 C   s8   |du rdS t |st |sdS t|tp|tjkS )a  
        Return True if the error is a Proxy Only LLM API Error

        Prevents double logging of LLM API exceptions

        e.g should only return True for:
            - Authentication Errors from user_api_key_auth
            - HTTP HTTPException (rate limit errors)
        NF)r>   Zis_llm_api_routeZis_info_router   r"   r   Z
auth_error)rg   r  r<  r  r\   r\   r]   r    s   
z)ProxyLogging._is_proxy_only_llm_api_errorc                    s  | dd}|du rDddlm} t| |d< tj|d}tjj	d|p&dtj
 t d|\}}d	|vr=i |d	< |d	 | |duri }	i }
tj }| D ]\}}||v rb||
|< qU|d
krn|dkrn||	|< qU|j| d
d| dd|	|
d d}d|v rt|d tr|d }||jd< tjj|_n3d|v rt|d tr|d }||jd< tjj|_nd|v rt|d tr|d }||jd< tjj|_|j|dd |j|t dI dH  t j!|j"|t fd#  dS dS )z
        Handle logging for proxy only errors by calling `litellm_logging_obj.async_failure_handler`

        Is triggered when self._is_proxy_only_error() returns True
        rE  Nr   )uuidr   )r#  ZIGNORE_THIS)Zoriginal_functionZ	rules_objrr  rV  r   r   r   )r   r   rS  rP  r   promptinput)r  Zapi_key)r  Ztraceback_exception)r3  r   r\   )$r   Zlitellm._uuidr  r   uuid4rK   Z'get_sanitized_user_information_from_keyrZ   r  Zfunction_setupZRulesr   r  rR  rT   __annotations__ry   r   Zupdate_environment_variablesr   r   Zmodel_call_detailsr   ro  rs   r  Zatext_completionZ
aembeddingr&  Zasync_failure_handlerrV   rY   	threadingThreadr  start)rg   r  r#  r  r  rE  r  Zuser_api_key_logged_metadatar  Z_optional_paramsZ_litellm_paramsZlitellm_param_keyskvr  r\   r\   r]   r    s   










z-ProxyLogging._handle_logging_proxy_only_errorc              
      s6  ddl m} g }g }ztjD ]*}d}t|tr#tjjt	t
|}n|}|dur9t|tr4|| q|| q|D ]7}|j||jddurIq<d}	dt|jv rb||d< tj|||dI dH }	n|j|||dI dH }	|	durs|	}q<|D ]}|j|||dI dH }
|
dur|
}qvW |S  ty } z|d}~ww )	z
        Allow user to modify outgoing data

        Covers:
        1. /chat/completions
        2. /embeddings
        3. /image/generation
        4. /files
        r   r   Nr8  Tr$  r%  r,  )r=  r   rZ   r   r   r   r   r   ru  r   r   r0   ry  r?  r+  r-  r   r.  r1  r   )rg   r  r  r#  r   Zguardrail_callbacksZother_callbacksr   rw  Zguardrail_responsecallback_responser   r\   r\   r]   post_call_success_hookZ  sl   




z#ProxyLogging.post_call_success_hookrequest_headersc           
   
      s   i }z;t jD ]4}d}t|trt jjtt|}n|}|dur;t|t	r;|j
||||dI dH }|dur;|| qW |S  tyY }	 ztdt|	 W Y d}	~	|S d}	~	ww )z
        Calls async_post_call_response_headers_hook on all CustomLogger callbacks.
        Merges all returned header dicts (later callbacks override earlier ones).

        Returns:
            Dict[str, str]: Merged headers from all callbacks.
        N)r  r#  r  r  z,Error in post_call_response_headers_hook: %s)rZ   r   r   r   r   r   ru  r   r   r2   Z%async_post_call_response_headers_hookrR  r   r)   r  )
rg   r  r#  r  r  Zmerged_headersr   rw  r  r   r\   r\   r]   post_call_response_headers_hook  s:   


z,ProxyLogging.post_call_response_headers_hookc                    s   g d}t  fdd|D S )N)Zjsonrpcidr  c                 3   s    | ]}| v V  qd S re   r\   )r  rm   r  r\   r]   	<genexpr>  s    z9ProxyLogging.is_a2a_streaming_response.<locals>.<genexpr>)all)rg   r  Zexpected_keysr\   r  r]   is_a2a_streaming_response  s   z&ProxyLogging.is_a2a_streaming_response
str_so_farc                    sH  ddl m} d}t|ttfrtj|d}nt|tr+| |r+ddl	m
} ||}|durd}d}	tjD ]k}
z[d}t|
tr\ddlm} |	sPt||d}d	}	|
j||jd
d	ur\W q6t|
trltjjtt|
}n|
}|durt|tr|dur|| }n|}|j||dI dH }|dur|}W q6 ty } z|d}~ww |S )zy
        Allow user to modify outgoing streaming data -> per chunk

        Covers:
        1. /chat/completions
        r   r   N)Zresponse_obj)extract_text_from_a2a_responseFr   )r  r   Tr8  )r#  r  )r   r   r   r&   r'   rZ   Zget_response_stringr   r  Zlitellm.llms.a2a.common_utilsr  r   r0   r=  r   '_check_and_merge_model_level_guardrailsr?  r+  r   r   r   ru  r   r   r2   async_post_call_streaming_hookr   )rg   r  r  r#  r  r   Zresponse_strr  Z_cached_guardrail_dataZ_guardrail_data_computedr   rw  r   Zcomplete_responser  r   r\   r\   r]   r    sd   




z+ProxyLogging.async_post_call_streaming_hookc                 C  s   |}t jD ]X}d}t|trt jjtt|}n|}|dur^t|t	r^t|t
r2|j|tjdr^dt|jv rB|j|||d}qdt|jv rV||d< tj|||d}q|j|||d}q|2 z	3 dH W }|V  qa6 dS )z
        Allow user to modify outgoing streaming data -> Given a whole response iterator.
        This hook is best used when you need to modify multiple chunks of the response at once.

        Covers:
        1. /chat/completions
        Nr8  'async_post_call_streaming_iterator_hook)r#  r  r  r$  r%  )r#  r  r  )rZ   r   r   r   r   r   ru  r   r   r2   r0   r?  r   r+  r-  r   r  r.  )rg   r  r#  r  Zcurrent_responser   rw  chunkr\   r\   r]   r  $  sX   

		z4ProxyLogging.async_post_call_streaming_iterator_hookc                 C   s2   | j r| j jdurt| j j|d dS dS dS )z
        Initialize the response taking too long task if user is using slack alerting

        Only run task if user is using slack alerting

        This handles checking for if a request is hanging for too long
        Nr  )r   r   r   r   Zresponse_taking_too_long)rg   r  r\   r\   r]   rt  b  s   
z0ProxyLogging._init_response_taking_too_long_taskr~   )NNNNNNNre   )r   NNN)NN)Mr   r   r   __doc__r,   r   rh   r   r(   r-   r   r   r   r9   r   r   r   r   r2   rG   r   r   r   r   r   r	   r   r   r   r   r  rQ   rP   r
   r  r  r  r  r!  r=   r   r4  r5  r0   r   rD  rT  rZ  r`  staticmethodr_  r   rk  r{  r}  r:   r  r  r  r   r   r"   r  r  r  rS   r  r  r  r   r&   r$   r%   r'   r  r  rt  r\   r\   r\   r]   r      s|   
*
+

6

N
1
!
/






;
.
g
>(
)1
 
I

6
B
/
s
'
X
X

(

K
>r   c                 C   s   t d| d   d S )Nz!Backing off... this was attempt #tries)r^   )detailsr\   r\   r]   
on_backoffw  s   r  r  rk   c              	   C   sX   t | }| D ] \}}t|tr)z	t|||< W q	 ty(   d||< Y q	w q	|S Nzfailed-to-serialize-jsonr   deepcopyr   r   r   r   r  r   )r  db_datar  r  r\   r\   r]   jsonify_object|     

r  r  )max_size_deprecated_key_cache<   dbhashed_tokenc           
   
      s   t tj}| }t|}t|}|dur.|\}}}||k r(||k r(|S t|d z*| jj	|d|idddidI dH }|rS|j
rV|j
|t ft|< |j
W S W dS W dS  tyq }	 ztd|	 W Y d}	~	dS d}	~	ww )z
    Check if a token exists in the deprecated keys table and is still within its grace period.

    Returns the active_token_id if found and valid, otherwise None.
    Uses an in-memory cache to avoid DB queries on every auth request.
    Ngt)tokenZ	revoke_atactive_token_idT)whereselectz!Deprecated key lookup skipped: %s)r   r  r   utc	timestampr  r   rQ  Z#litellm_deprecatedverificationtoken
find_firstr  !_DEPRECATED_KEY_CACHE_TTL_SECONDSr   r)   rW   )
r  r  r  Znow_tscachedr  Zcache_expires_at_tsZrevoke_at_tsZdeprecated_rowr   r\   r\   r]   _lookup_deprecated_key  s>   




r  c                #   @   s0  e Zd ZU g Zeed< e Z	dde	de
dee fddZdeeef d	ed
 fddZde	fddZded	efddZejejeddeddd Zeejejeddedde	deded fddZd e	d	ee fd!d"Zejejeddede								#								$ddeee	ef  d%ee	 d&ee d'ee	 d(ee d)ee deed*  d+ed, d-ee d.ee d/ee  d0ee  d1ee! dee
 d2eee	  d3e"f d4d5Z#d6efd7d8Z$ejejeddeddeded9 fd:d;Z%ejejeddeddi dddd<dddf	dee	 ded=ee d%ee	 d'ee	 d+ed> deed?  d@ee dAee fdBdCZ&ejejedded				ddDee d(ee deedE  d%ee	 fdFdGZ'ejejeddeddHdI Z(ejejeddeddJdK Z)d	e fdLdMZ*d	e"fdNdOZ+e,d	e-fdPdQZ.dRe d	e"fdSdTZ/dRe dUej0d	dfdVdWZ1dXe d	dfdYdZZ2dRe d	e"fd[d\Z3dd]d^Z4dd_d`Z5ddadbZ6ddcddZ7ddedfZ8	ddgee9 d	dfdhdiZ:dje"dke	dgee9 d	e"fdldmZ;	n		ddke	dje"dgee9 doee9 d	e"f
dpdqZ<ddrdsZ=ddtduZ>ddvdwZ?ejejeddeddxdy Z@d	e fdzd{ZAejejeddeddd|d}ZBd~ee9 d	ee9 fddZCdee d	ee fddZD							dde	de	de de dee	 d~ee9 dee dee	 dee	 fddZE				ddee	 d0e d/e dee	 fddZFdd ZGdS )PrismaClientspend_log_transactionsNdatabase_urlproxy_logging_objhttp_clientc              
   C   s  || _ ttd| _td zddlm} W n" t	y: } zt
d|  t
d t
d t	dd }~ww |d urRt||d	| jd urL| jnd
d| _nt| | jd ur]| jnd
d| _t | _d | _d| _tdttdd| _tdttdd| _ttdddu | _tdttdd| _tdttdd| _tdttdd| _tdttdd| _d| _tdttd d!| _d"| _d| _d
| _ d
| _!d | _"td# d S )$NZIAM_TOKEN_DB_AUTHzCreating Prisma Client..r   )Prismaz Failed to import Prisma client: z9This usually means 'prisma generate' hasn't been run yet.z;Please run 'prisma generate' to generate the Prisma client.zCUnable to find Prisma binaries. Please run 'prisma generate' first.)httpF)Zoriginal_prismaiam_token_db_authg        r   Z!PRISMA_RECONNECT_COOLDOWN_SECONDSZ15   Z'PRISMA_HEALTH_WATCHDOG_INTERVAL_SECONDSZ30ZPRISMA_HEALTH_WATCHDOG_ENABLEDtrueTg      ?Z,PRISMA_HEALTH_WATCHDOG_PROBE_TIMEOUT_SECONDSz5.0g      ?Z)PRISMA_WATCHDOG_RECONNECT_TIMEOUT_SECONDSz30.0Z%PRISMA_AUTH_RECONNECT_TIMEOUT_SECONDSz2.0Z*PRISMA_AUTH_RECONNECT_LOCK_TIMEOUT_SECONDSz0.1Z%PRISMA_RECONNECT_ESCALATION_THRESHOLD3zSuccess - Created Prisma Client)#r  rM   r`   ra   r  r)   rW   Zprismar  r   r   rD   r  r   Lock_db_reconnect_lock_db_health_watchdog_task_db_last_reconnect_attempt_tsmaxint_db_reconnect_cooldown_seconds$_db_health_watchdog_interval_seconds_db_health_watchdog_enabledr   )_db_health_watchdog_probe_timeout_seconds&_db_watchdog_reconnect_timeout_secondsZ"_db_auth_reconnect_timeout_secondsZ'_db_auth_reconnect_lock_timeout_seconds_consecutive_reconnect_failures_reconnect_escalation_threshold_engine_pidfd_engine_pid_watching_engine_engine_confirmed_dead_engine_wait_thread)rg   r  r  r  r  r   r\   r\   r]   rh     s   


	

zPrismaClient.__init__payloadrk   )r   failurec              	   C   sb   z#| di }t|trttt|}n|}| ddkr!dW S dW S  tjtfy0   Y dS w )a  
        Determine if a request was successful or failed based on payload metadata.

        Args:
            payload (Union[dict, SpendLogsPayload]): Request payload containing metadata

        Returns:
            Literal["success", "failure"]: Request status
        rV  r#   r  r   )	r   r   r   r   r
   r   r   r   AttributeError)rg   r  Zpayload_metadataZpayload_metadata_jsonr\   r\   r]   get_request_status	  s"   

zPrismaClient.get_request_statusr  c                 C   s   t |  }|S re   hashlibsha256encode	hexdigest)rg   r  r  r\   r\   r]   
hash_token4	  s   zPrismaClient.hash_tokenr  c              	   C   sX   t |}| D ] \}}t|tr)z	t|||< W q	 ty(   d||< Y q	w q	|S r  r  )rg   r  r  r  r  r\   r\   r]   r  :	  r  zPrismaClient.jsonify_object   
   Z	max_triesZmax_timer  c                    sJ  zg d}d}d dd |D }tdd}| jd| d	| d
I dH }t|}|d d |kr<td W dS |d d rb||d d vrb|  I dH  | j	dI dH  td W dS t
| jdI dH }|ryt| jdI dH  W dS |d d rt|d d nt }t|}	|	| }
td|
 W dS  ty    w )a|  
        Checks if the LiteLLM_VerificationTokenView and MonthlyGlobalSpend exists in the user's db.

        LiteLLM_VerificationTokenView: This view is used for getting the token + team data in user_api_key_auth

        MonthlyGlobalSpend: This view is used for the admin view to see global spend for this month

        If the view doesn't exist, one will be created.
        )r;   ZMonthlyGlobalSpendZLast30dKeysBySpendZLast30dModelsBySpendZMonthlyGlobalSpendPerKeyZMonthlyGlobalSpendPerUserPerKeyZLast30dTopEndUsersSpendZDailyTagSpendr;   , c                 s   s    | ]	}d | d V  qdS )re  Nr\   )r  viewr\   r\   r]   r  g	  s    z1PrismaClient.check_view_exists.<locals>.<genexpr>DATABASE_SCHEMApublicz
                WITH existing_views AS (
                    SELECT viewname
                    FROM pg_views
                    WHERE schemaname = 'z,' AND viewname IN (
                        z
                    )
                )
                SELECT
                    (SELECT COUNT(*) FROM existing_views) AS view_count,
                    ARRAY_AGG(viewname) AS view_names
                FROM existing_views
                Nr   Z
view_countzAll necessary views exist!Z
view_namesa"  
                            CREATE VIEW "LiteLLM_VerificationTokenView" AS
                            SELECT
                            v.*,
                            t.spend AS team_spend,
                            t.max_budget AS team_max_budget,
                            t.tpm_limit AS team_tpm_limit,
                            t.rpm_limit AS team_rpm_limit
                            FROM "LiteLLM_VerificationToken" v
                            LEFT JOIN "LiteLLM_TeamTable" t ON v.team_id = t.team_id;
                        z,LiteLLM_VerificationTokenView Created in DB!)r  z

[93mNot all views exist in db, needed for UI 'Usage' tab. Missing={}.
Run 'create_views.py' from https://github.com/BerriAI/litellm/tree/main/db_scripts to create missing views.[0m
)joinr`   ra   r  	query_rawr   r)   infohealth_checkZexecute_rawr@   r?   r  r   rX   r   )rg   Zexpected_viewsZrequired_viewZexpected_views_strZ	pg_schemaretZexpected_total_viewsZshould_create_viewsZret_view_names_setZexpected_views_setZmissing_viewsr\   r\   r]   check_view_existsF	  sV   



 zPrismaClient.check_view_existsr      rm   rs   
table_name)usersry   configspendc                    s4  t   }zP|dkr| jjj||idI dH }|W S |dkr.| jjj||idI dH }|W S |dkrB| jjj||idI dH }|W S |dkrS| jjj||idI dH }|W S  ty } z8ddl}dt	| }t
| |d	t| }|d
 |  }	t   }
|
| }t| jj|||	dd |d}~ww )z4
        Generic implementation of get data
        r	  r  Nry   r
  r  r   z2LiteLLM Prisma Client Exception get_generic_data: z
Exception Type: {}r   get_generic_data)r  rq  r  r  )r@  r  litellm_usertabler  litellm_verificationtokenlitellm_configlr   rV   r   r)   r   rX   r-  rY   r   r   r  r  )rg   rm   rs   r  rr  r  r   rV   	error_msgerror_tracebackrs  	_durationr\   r\   r]   r  	  sT   
	zPrismaClient.get_generic_data	sql_queryc              
      s   z| j j|dI dH W S  tyF } z.t|}d|v rA|ddtt d  d}td | j j|dI dH W  Y d}~S  d}~ww )	a  
        Execute a query with automatic fallback for PostgreSQL cached plan errors.

        This handles the "cached plan must not change result type" error that occurs
        during rolling deployments when schema changes are applied while old pods
        still have cached query plans expecting the old schema.

        Args:
            sql_query: SQL query string to execute

        Returns:
            Query result or None

        Raises:
            Original exception if not a cached plan error
        queryNz'cached plan must not change result typeZSELECTzSELECT /* cache_invalidated_r  z */zPostgreSQL cached plan error detected for token lookup, retrying with fresh plan. This may occur during rolling deployments when schema changes are applied.)	r  Zquery_firstr   r   replacer  r@  r)   r   )rg   r  r   Z	error_strZsql_query_retryr\   r\   r]   &_query_first_with_cached_plan_fallback	  s"    z3PrismaClient._query_first_with_cached_plan_fallbackfind_uniqueTuser_iduser_id_listteam_idteam_id_listkey_val)	r   rm   r
  r  enduserbudgetteamuser_notificationcombined_view
query_type)r  find_allexpiresreset_atoffsetlimitrn   budget_id_listcheck_deprecatedc           %         s	  t  }t }d }zd }|d ur|d u s |d ur|dkr|d ur6t|tr6t|d}td|  |dkr~|d ur~|d u rMtddd| id| jj	j
d	|id
didI d H }|d urt|jd urrt|jtrr|j |_n!ttjd| d|dkr|d ur| jj	jd|id
didI d H }|d urt|dkr|D ]}t|jtr|j |_qn|dkr|d ur| jj	jd|id
didI d H }|d urt|dkr|D ]}t|jtr|j |_qn|dkr-|	d ur-|
d ur-| jj	jdd idd|	iigd|
iddI d H }|d ur,t|dkr,|D ]}t|jtr*|j |_qnh|dkri }|d uri |d	< t|trPt|d}|g|d	 d< n4t|trg }|D ]#}t|tsdJ |drw| j|d}|| qZ|| qZ||d	 d< | jj	jddi|d
didI d H }|d ur|W S ttjdd|d ur|d u s|d ur\|dkr\|dkr|d u rd|i}| jjj
|ddidI d H }|W S |dkr|d ur| jjj|dI d H }|W S |dkr|
d ur| jjjdd|
iidI d H }|W S |dkr'|d ur'| jjjdd|iidI d H }|W S |dkrY|	d urL| jjjddid dd idd|	iigid!I d H }|W S d"}| j|||I d H }|W S |dkrtd# |d ur|dkr| jjj
|d |d$ idI d H }|W S |dkr| jjj|d |d$ idI d H }|W S | jjjd%did&I d H }|W S |d'kr|
d ur|dkr| jjjd d(dd id)d*d iigidd|
iigidI d H }|W S W d S |d+kr|d ur|dkr | jjjd,d|iidI d H }|W S W d S |d-kr|dkr | jjj
d|id.didI d H }|W S |dkr<|
d ur<| jjjdd|
iidI d H }|W S |dkr[|d ur[| jjjd/d0|iid
didI d H }|W S |dkrw|d urw| jjjdd|iidI d H }|W S |dkr|d u r| jjjtd1I d H }|W S |d2kr|dkr| jjj
d|idI d H }|W S |dkr| jj I d H }|W S |d3kr|d urt|trt|d}td|  |dkr|d u rtddd| idd4| d5}| |I d H }|d u r,|d ur,|r,t| j|d6I d H }|r,| j |d3d||d7d8I d H }|d ur,td9 |d ur|d: d u r<g |d:< |d; d u rGd7|d;< d }|d< d ury|d d ury	 |d< D ]}|!dd urw|d |!dkrwt"dCi |}q\||d=< t#dCi |d>t i}|jd urt|jtr|j |_|W S W d S W d S  t$y } z7dd l%}d?| } | t| }!t&|! |!d@ |'  }"t|" t }#|#| }$t()| j*j+||$dA|"dB |d }~ww )DNrm   r  z%PrismaClient: find_unique for token: r  r  r   zNo token passed in. Token=r  r  litellm_budget_tableT)r  includezQAuthentication Error: invalid user key - user key does not exist in db. User Key=r&  r  r   r  r'  r  lt)ORbudget_reset_atr  insk-r  desc)orderr  r/  z=Authentication Error: invalid user key - token does not existr   Zorganization_membershipsr2  r1  )r6  r  a  
                        SELECT
                            u.*,
                            json_agg(v.key_alias) AS key_aliases
                        FROM
                            "LiteLLM_UserTable" u
                        LEFT JOIN "LiteLLM_VerificationToken" v ON u.user_id = v.user_id
                        GROUP BY
                            u.user_id
                        ORDER BY u.spend DESC
                        LIMIT $1
                        OFFSET $2
                        z-PrismaClient: get_data: table_name == 'spend'rs   Z	startTimer6  r!  ANDNOTZbudget_durationr   	budget_idr"  Zlitellm_model_tablemembershas)taker#  r$  aJ	  
                        SELECT 
                            v.*,
                            t.spend AS team_spend, 
                            t.max_budget AS team_max_budget,
                            t.soft_budget AS team_soft_budget,
                            t.tpm_limit AS team_tpm_limit,
                            t.rpm_limit AS team_rpm_limit,
                            t.models AS team_models,
                            t.metadata AS team_metadata,
                            t.blocked AS team_blocked,
                            t.team_alias AS team_alias,
                            t.metadata AS team_metadata,
                            t.members_with_roles AS team_members_with_roles,
                            t.object_permission_id AS team_object_permission_id,
                            t.organization_id as org_id,
                            tm.spend AS team_member_spend,
                            m.aliases AS team_model_aliases,
                            -- Added comma to separate b.* columns
                            b.max_budget AS litellm_budget_table_max_budget,
                            b.tpm_limit AS litellm_budget_table_tpm_limit,
                            b.rpm_limit AS litellm_budget_table_rpm_limit,
                            b.model_max_budget as litellm_budget_table_model_max_budget,
                            b.soft_budget as litellm_budget_table_soft_budget,
                            o.metadata as organization_metadata,
                            b2.max_budget as organization_max_budget,
                            b2.tpm_limit as organization_tpm_limit,
                            b2.rpm_limit as organization_rpm_limit
                        FROM "LiteLLM_VerificationToken" AS v
                        LEFT JOIN "LiteLLM_TeamTable" AS t ON v.team_id = t.team_id
                        LEFT JOIN "LiteLLM_TeamMembership" AS tm ON v.team_id = tm.team_id AND tm.user_id = v.user_id
                        LEFT JOIN "LiteLLM_ModelTable" m ON t.model_id = m.id
                        LEFT JOIN "LiteLLM_BudgetTable" AS b ON v.budget_id = b.budget_id
                        LEFT JOIN "LiteLLM_OrganizationTable" AS o ON v.organization_id = o.organization_id
                        LEFT JOIN "LiteLLM_BudgetTable" AS b2 ON o.budget_id = b2.budget_id
                        WHERE v.token = 'z'
                    )r  r  F)r  r  r%  rn   r  r,  z'Deprecated key used during grace periodteam_modelsZteam_blockedZteam_members_with_rolesteam_memberZlast_refreshed_atzHLiteLLM Prisma Client Exception: Error with `get_data`. Args passed in: r   get_datar  rq  r  r  r\   ),localsr@  r   r   _hash_token_if_neededr)   rW   r"   r  r  r  r'  r   	isoformatr#   ZHTTP_401_UNAUTHORIZED	find_manyr   r   r   r  ry  r  r  litellm_spendlogslitellm_budgettablelitellm_endusertablelitellm_teamtabler   litellm_usernotificationsr  r  r@  r   r<   r;   r   rV   r^   rY   r   r   r  r  )%rg   r  r  r  r  r  r  r  r%  r'  r(  r)  r*  rn   r  r+  r,  Zargs_passed_inrr  r  r  rZwhere_filterhashed_tokenstZ	new_tokenr  r  r?  tmr   rV   Zprisma_query_infor  r  rs  r  r\   r\   r]   r@  
  sf  )





	





.($























#&




r

zPrismaClient.get_datar  c                 C   s@   | j |d}|dd d urt|d trt|d |d< |S )Nrm  members_with_roles)r  r   r   r   r   r  )rg   r  r\   r\   r]   jsonify_team_object  s   z PrismaClient.jsonify_team_object)r   rm   r
  r  r"  r#  c                    s  t   }zEtd| |dkrG|d }| j|d}| j|d}||d< td | jjjd|ii |i ddd	id
I dH }t	d |W S |dkr| j|d}z| jj
jd|d ii |i ddI dH }W n" ty }	 zdt|	v rtddd|d  did|	d}	~	ww t	d |W S |dkr| j|d}| jjjd|d ii |i ddI dH }
t	d |
W S |dkr	 g }| D ]#\}}|}t|}| jjjd|i||dd|idd}|| qtj| I dH  t	d  W dS |d!kr!| j|d}| jjjd"|d" ii |i ddI dH }t	d# |W S |d$krI| j|d}| jjjd"|d" ii |i ddI dH }t	d% |W S W dS  ty }	 z.d&dl}d't|	 }t| |d( |  }t   }|| }t| jj|	|d)|d* |	d}	~	ww )+zN
        Add a key to the database. If it already exists, do nothing.
        zPrismaClient: insert_data: %srm   r  r-  rm  z:PrismaClient: Before upsert into litellm_verificationtokencreaterR  r.  T)r  r  r/  NzData Inserted into Keys Tabler   r  r  r  z\Foreign key constraint failed on the field: `LiteLLM_UserTable_organization_id_fkey (index)`r  r   z/Foreign Key Constraint failed. Organization ID=Zorganization_idzM does not exist in LiteLLM_OrganizationTable. Create via `/organization/new`.r  zData Inserted into User Tabler"  r  r  zData Inserted into Team Tabler
  
param_name)rU  param_valuerV  zData Inserted into Config Tabler  Z
request_idzData Inserted into Spend Tabler#  z&Data Inserted into Model Request Tabler   z0LiteLLM Prisma Client Exception in insert_data: r   insert_datarA  )r@  r)   rW   r  r  r^   r  r  upsertr  r  r   r   r"   rP  rI  r   r   r  r  ry  r   rz  rF  rJ  rV   rY   r   r  r  )rg   r  r  rr  r  r  r  Znew_verification_tokenZnew_user_rowr   Znew_team_rowtasksr  r  Zupdated_dataZupdated_table_rowZnew_spend_rowZnew_user_notification_rowrV   r  r  rs  r  r\   r\   r]   rW    s   













	zPrismaClient.insert_datarR  	data_list)rR  update_many)r   rm   r
  r  r"  r   r!  update_key_valuesupdate_key_values_custom_queryc
                    s6  t d|  t }
zQ| j|d}|dur| j|d}|durotd|  t|d}||d< | jjjd|ii |dI dH }t dd	|  d
  i }|duriz|	 }W n t
yh   | }Y nw ||dW S |dus|dur|dkr|dkr	 |du r|d }|du r|	dur|	}n|}| jjjd|ii |i |ddI dH }t dd|  d
  ||dW S |dus|dur;|dkr;|dkr;	 |du r|d }|du r|}d|vr|dur||d< d|v rt|d trt|d |d< d|v rt|d trt|d |d< | jjjd|ii |i |ddI dH }t dd|  d
  ||dW S |dur|dkr|dkr|durt|tr	 | j }t|D ]B\}}|jdrr| j|jd|_z| j|j	ddd}W n t
y   | j|jddd}Y nw |jjd|jii |d q_| I dH  td W dS |dur|dkr|dkr|durt|tr	 | j }t|D ]6\}}z| j|j	ddd}W n t
y   | j| d}Y nw |jjd|jii |i |dd q| I dH  t d W dS |dur|dkr|dkr|durt|tr	 | j }|D ]4}z| j|j	ddd}W n t
y\   | j| d}Y nw |jjd|jii |i |dd q<| I dH  t d W dS |dur|d kr|dkr|durt|tr	 | j }|D ]4}z| j|j	ddd}W n t
y   | j| d}Y nw |jjd!|jii |i |dd q| I dH  t d" W dS |durQ|dkrT|dkrW|durZt|tr]| j }t|D ]8\}}z| j|j	ddd#}W n t
y-   | j|jddd}Y nw |jjd|jii |i |dd q	| I dH  t d$ W dS W dS W dS W dS W dS W dS  t
y } z.d%dl }d&t!| }t| |d' |"  }t }||
 }t#$| j%j&||d(|d) |d}~ww )*z&
        Update existing data
        z'PrismaClient: update_data, table_name: rm  Nztoken: r-  r  rS  z[91mz DB Token Table update succeeded z[0m)r  r  r   rR  r  rQ  z!DB User Table - update succeeded )r  r  r"  r  rO  z!DB Team Table - update succeeded )r  r  rm   r[  r4  T)exclude_nonez([91mDB Token Table update succeeded[0mz-[91mDB User Table Batch update succeeded[0mr   z1[91mDB End User Table Batch update succeeded[0mr!  r:  z/[91mDB Budget Table Batch update succeeded[0mrT  z-[91mDB Team Table Batch update succeeded[0mr   z/LiteLLM Prisma Client Exception - update_data: r   update_datarA  )'r)   rW   r@  r  r^   rC  r  r  rR  r   r   r   r  rX  r  r   r   r   r  rI  batch_r   r  r   r  commitr  rH  rG  r:  rP  r  rV   r   rY   r   r   r  r  )rg   r  r  rZ  r  r  r%  r  r\  r]  rr  r  r  _dataZupdate_user_rowZupdate_team_rowbatcherr   rM  Z	data_jsonr   r   r!  r"  r   rV   r  r  rs  r  r\   r\   r]   r_  G  s  
	

	
















	








	








	










	
zPrismaClient.update_datatokens)r   rm   r
  r  r"  c                    s  t   }z|dur]t|tr]g }|D ]}t|tr&|dr&| j|d}n|}|| qi }	|durAddd|iid|igi}	ndd|ii}	| jjj	|	dI dH }
t
d	|
 d
|
iW S |dkr~|dur~t|tr~| jjj	dd|iidI dH  d|iW S |dkr|durt|tr| jjj	dd|iidI dH  W dS W dS W dS W dS  ty } z.ddl}dt| }t| |d |  }t   }|| }t| jj||d|d |d}~ww )za
        Allow user to delete a key(s)

        Ensure user owns that key, unless admin.
        Nr4  r-  r8  r  r3  r  r  zdeleted_tokens: %sZdeleted_keysr"  r  Zdeleted_teamsrm   r   z/LiteLLM Prisma Client Exception - delete_data: r   delete_datarA  )r@  r   r   r   r   r  ry  r  r  Zdelete_manyr)   rW   rI  r   rV   r^   rY   r   r   r  r  )rg   rd  r  r  r  rr  rL  r  r  Zfilter_queryZdeleted_tokensr   rV   r  r  rs  r  r\   r\   r]   re  p  sn   



zPrismaClient.delete_datac                    s   t   }ztd | j du r"td | j I d H  W d S W d S  ty^ } z.dd l}dt| }t	| |d |
  }t   }|| }t| jj||d|d |d }~ww )	Nz:PrismaClient: connect() called Attempting to Connect to DBFz;PrismaClient: DB not connected, Attempting to Connect to DBr   z+LiteLLM Prisma Client Exception connect(): r   connectrA  )r@  r)   rW   r  Zis_connectedrf  r   rV   r   r^   rY   r   r   r  r  rg   rr  r   rV   r  r  rs  r  r\   r\   r]   rf    s<   zPrismaClient.connectc                    s   t   }z| j I d H  W d S  tyJ } z.dd l}dt| }t| |d |  }t   }|| }t	| j
j||d|d |d }~ww )Nr   .LiteLLM Prisma Client Exception disconnect(): r   
disconnectrA  )r@  r  ri  r   rV   r   r^   rY   r   r   r  r  rg  r\   r\   r]   ri    s,   zPrismaClient.disconnectc              	   C   sR   z| j jj}|d urt|dd nd }|d ur|jW S W dS  ttfy(   Y dS w )Nprocessr   )r  Z_original_prismaZ_enginerB  pidr  	TypeError)rg   Zenginerj  r\   r\   r]   _get_engine_pid  s   
zPrismaClient._get_engine_pidc              	   C   sN   | j dkrdS z
t| j d W dS  ty   Y dS  ttfy&   Y dS w )Nr   TF)r  r`   killProcessLookupErrorPermissionErrorOSErrorrg   r\   r\   r]   _is_engine_alive  s   
zPrismaClient._is_engine_alivec                  C   sN   t  } 	 ztdtj\}}|dkrW | S | | W n
 ty%   Y | S w q)zReap ALL zombie child processes via waitpid(-1, WNOHANG).

        Returns a set of reaped PIDs.  As PID 1 in Docker (or any
        process that spawns children), we must reap ALL terminated
        children to prevent zombie accumulation.
        Tr  r   )r  r`   waitpidWNOHANGaddChildProcessError)Zreapedrk  _r\   r\   r]   _reap_all_zombies  s   zPrismaClient._reap_all_zombiesrk  c                 C   s   zt |t j\}}W n ty   td| Y dS w ||kr=td| d| _|   | 	  t
| jddd dS zt
 }W n
 tyM   Y dS w tj| j||fdd| d}|  || _dS )	a  Watch engine PID via os.waitpid() in a dedicated thread.

        The thread blocks on os.waitpid(pid, 0) which is a kernel-level
        wait and with zero CPU overhead, instant detection when the process exits.
        When the process dies, the thread notifies the asyncio event loop
        via call_soon_threadsafe.

        Returns True if the thread was started, False on failure.
        z6PID %s is not a child process; skipping waitpid watch.Fz7prisma-query-engine PID %s already dead at watch start.Tengine_process_deathreasonforcezprisma-engine-waitpid-)r3  r   daemonr
  )r`   rt  ru  rw  r)   rW   r   r  ry  _cleanup_engine_watcherr   r   attempt_db_reconnectget_running_loopRuntimeErrorr  r  _waitpid_thread_funcr  r  )rg   rk  Z	probe_pidrx  loopthreadr\   r\   r]   _try_waitpid_watch*  sH   
zPrismaClient._try_waitpid_watchr  c                 C   s^   zt |d W n ty   Y n	 ty   Y nw z
|| j| W dS  ty.   Y dS w )a>  Thread function: block until engine PID exits, then notify event loop.

        Note: uvloop/libuv may reap the child first via waitpid(-1, WNOHANG)
        in its SIGCHLD handler. In that case our waitpid raises ChildProcessError.
        we still notify the event loop because the engine is dead either way.
        r   N)r`   rt  rw  rq  call_soon_threadsafe_on_engine_death_from_threadr  )rg   rk  r  r\   r\   r]   r  Z  s   z!PrismaClient._waitpid_thread_funcdead_pidc                 C   sR   | j rdS || jkrdS td| d| _ |   |   t| jddd dS )zMCalled on the event loop thread when the waitpid thread detects engine death.NzIprisma-query-engine PID %s exited (waitpid thread); triggering reconnect.Trz  r{  )	r  r  r)   r   ry  r  r   r   r  rg   r  r\   r\   r]   r  l  s"   
z)PrismaClient._on_engine_death_from_threadc                 C   sd   t tdsdS d}zt|d}t || j || _W dS  ty1   |dkr.t	| Y dS w )z
        Watch engine PID via pidfd_open + asyncio event loop reader.

        Returns True if pidfd watch was set up, False if unavailable or failed.
        Broad OSError catch handles both ENOSYS and SECCOMP-blocked syscalls.
        
pidfd_openFr  r   T)
r   r`   r  r   r  
add_reader_on_pidfd_readabler  rq  close)rg   rk  fdr\   r\   r]   _try_pidfd_watch  s   

zPrismaClient._try_pidfd_watchc                 C   s   | j r3| jdkr1z
t | j W n	 ty   Y nw zt| j W n	 ty-   Y nw d| _dS | j	}t
d| d| _ |   |   t| jddd dS )zpidfd became readable: engine process exited or became zombie.

        Sets _engine_confirmed_dead BEFORE cleanup so _run_reconnect_cycle
        takes the heavy path (recreate Prisma client + re-arm watcher).
        r   r  NzFprisma-query-engine PID %s exited (pidfd event); triggering reconnect.Trz  r{  )r  r  r   r  remove_readerr   r`   r  rq  r  r)   r   ry  r  r   r  r  r\   r\   r]   r    s8   
zPrismaClient._on_pidfd_readablec              	      s   | j ra| jdkrcz	t| jd W n< ty8   td| j d| _|   | 	  | j
dddI dH  Y dS  ttfyN   td| j | 	  Y dS w tdI dH  | j re| jdks	dS dS dS dS )	zpoll via os.kill(pid, 0) every 1s.
        Only used when BOTH waitpid thread and pidfd are unavailable
        (e.g., PID is not our child process and pidfd_open fails)
        r   z6prisma-query-engine PID %s gone; triggering reconnect.Trz  r{  Nz+Cannot signal PID %s; stopping engine poll.r   )r  r  r`   rn  ro  r)   r   r  ry  r  r  rp  rq  rW   r   sleeprr  r\   r\   r]   _poll_engine_proc  s6    zPrismaClient._poll_engine_procc                 C   sr   d| _ | jdkr1z
t | j W n	 ty   Y nw zt| j W n	 ty-   Y nw d| _d| _	d| _
dS )zKClean up pidfd reader, waitpid thread ref, or stop polling and reset state.Fr   r  N)r  r  r   r  r  r   r`   r  rq  r  r  rr  r\   r\   r]   r    s   

z$PrismaClient._cleanup_engine_watcherc                    s   | j s| jdks| jdurdS |  }|dkrtd dS || _d| _td| | 	|}|r4dn| 
|}|rCtd| dS |rMtd| dS td| d	| _ t|   dS )
ak  
        Start watching the Prisma query engine process for death.

        Detection priority:
        1. os.waitpid() in a dedicated thread, works with all event loops.
        2. pidfd_open kernel fd registered with asyncio.
        3. os.kill(pid, 0) polling (1s), last-resort fallback when neither
           waitpid thread nor pidfd are available.

        r   NzKCould not find prisma-query-engine PID; engine death detection unavailable.Fz$Found prisma-query-engine at PID %s.z*Watching engine PID %s via waitpid thread.z!Watching engine PID %s via pidfd.z+Watching engine PID %s via os.kill polling.T)r  r  r  rm  r)   rW   r  r  r  r  r  r   r   r  )rg   rk  Z
waitpid_okZpidfd_okr\   r\   r]   _start_engine_watcher  s2   

z"PrismaClient._start_engine_watcherc                 C   s   |    d| _td dS )z<Stop watching the engine process and clean up all resources.FzStopped engine process watcher.N)r  r  r)   rW   rr  r\   r\   r]   _stop_engine_watcher	  s   z!PrismaClient._stop_engine_watchertimeout_secondsc                    s   |dur|n j } jp jdko   }|rA j}td|       d _d fdd}tj	| |dI dH  dS t
d	 d fd
d}tj	| |dI dH  dS )ah  
        Run a reconnect cycle with a single overall timeout budget.

        Uses the _engine_confirmed_dead flag (set by waitpid thread / pidfd / poll
        handlers) to choose between heavy reconnect (engine dead -- recreate
        Prisma client, re-arm watcher) and lightweight reconnect (network
        blip -- disconnect, connect, SELECT 1).
        Nr   z1prisma-query-engine PID %s is dead; reconnecting.Frk   c                     sH   t dd} | std td j| I d H    I d H  d S )NZDATABASE_URLr   z4DATABASE_URL not set; cannot recreate Prisma client.zDATABASE_URL not set)r`   ra   r)   r   r  r  Zrecreate_prisma_clientr  )Zdb_urlrr  r\   r]   _do_heavy_reconnect,  s   
z>PrismaClient._run_reconnect_cycle.<locals>._do_heavy_reconnecttimeoutz9Performing Prisma DB reconnect (engine alive or unknown).c               
      sn   z
 j  I d H  W n ty# }  ztd|  W Y d } ~ nd } ~ ww  j  I d H   j dI d H  d S )Nz:Prisma DB disconnect before reconnect failed (ignored): %sSELECT 1)r  ri  r   r)   rW   rf  r  )Zdisconnect_errrr  r\   r]   _do_direct_reconnect8  s   z?PrismaClient._run_reconnect_cycle.<locals>._do_direct_reconnectrk   N)r  r  r  rs  r)   r   ry  r  r   wait_forrW   )rg   r  Zeffective_timeoutZengine_is_deadr  r  r  r\   rr  r]   _run_reconnect_cycle  s(   
z!PrismaClient._run_reconnect_cycler}  r|  c              
      s  t   }|du r|| j | jk rtd| dS | j| jkr*td| j| d| _td| d}zIz| j	|dI d H  d}d| _t
d| W n" tyk } z|  jd	7  _td
| j|| W Y d }~nd }~ww W t   | _|S W t   | _|S t   | _w )NFzDSkipping DB reconnect attempt inside lock due to cooldown. reason=%szFEscalating to heavy reconnect after %d consecutive failures. reason=%sTz)Attempting Prisma DB reconnect. reason=%s)r  r   z(Prisma DB reconnect succeeded. reason=%sr   z?Prisma DB reconnect failed (%d consecutive). reason=%s error=%s)r@  r  r  r)   rW   r  r  r   r  r  r  r   r   )rg   r}  r|  r  r  Zreconnect_succeededZreconnect_errr\   r\   r]   _attempt_reconnect_inside_lockF  s\   

z+PrismaClient._attempt_reconnect_inside_lockFlock_timeout_secondsc              
      s  t   }|du r|j jk rtd| dS |du rDj4 I dH  |||I dH W  d  I dH  S 1 I dH s?w   Y  d dtf fdd}t	| }tj
|h|tjdI dH \}}	||vr|  z|I dH  W n tjy~   Y n	 ty   Y nw  rzj  W n	 ty   Y nw td|| dS z|  W n ty }
 ztd	||
 W Y d}
~
dS d}
~
ww z|||I dH W j  S j  w )
z
        Attempt to reconnect the Prisma client in a singleflight manner.

        Returns:
            bool: True if reconnection succeeded, else False.
        Fz8Skipping DB reconnect attempt due to cooldown. reason=%sNrk   c                      s   j  I d H  d dS NT)r  acquirer\   Zlock_acquired_by_timeout_taskrg   r\   r]   _acquire_reconnect_lock  s   zBPrismaClient.attempt_db_reconnect.<locals>._acquire_reconnect_lock)r  return_whenzTSkipping DB reconnect attempt due to lock acquisition timeout. reason=%s timeout=%sszOSkipping DB reconnect attempt due to lock acquisition error. reason=%s error=%s)r@  r  r  r)   rW   r  r  r   r   r   waitFIRST_COMPLETEDcancelCancelledErrorr   releaser  r  )rg   r|  r}  r  r  r  r  Zacquire_taskdoneZ_pendingZlock_acquire_errr\   r  r]   r  ~  st   ,z!PrismaClient.attempt_db_reconnectc                    sd   | j durtd dS | jdurdS t|  | _td| j| j	| j
| j |  I dH  dS )zStart background tasks that monitor DB health:
        - A periodic SELECT 1 probe that triggers reconnect on network/connection failure.
        - A process-level watcher that detects engine death via waitpid thread, pidfd, or os.kill polling.TzEPrisma DB health watchdog disabled via PRISMA_HEALTH_WATCHDOG_ENABLEDNzrStarted Prisma DB health watchdog (interval=%ss, reconnect_cooldown=%ss, probe_timeout=%ss, reconnect_timeout=%ss))r  r)   rW   r  r   r   _db_health_watchdog_loopr  r  r  r  r  r  rr  r\   r\   r]   start_db_health_watchdog_task  s&   

z*PrismaClient.start_db_health_watchdog_taskc                    s\   |    | jdu rdS | j  z| jI dH  W n
 tjy#   Y nw d| _td dS )z;Stop DB health watchdog task and engine watcher gracefully.Nz!Stopped Prisma DB health watchdog)r  r  r  r   r  r)   r  rr  r\   r\   r]   stop_db_health_watchdog_task  s   

z)PrismaClient.stop_db_health_watchdog_taskc              
      s   	 zt | jI d H  t j| jd| jdI d H  W n9 t jy'   Y d S  tyU } z#t	|t j
s9t|rE| jd| jdI d H  ntd| W Y d }~nd }~ww q)NTr  r  Z#db_health_watchdog_connection_error)r|  r  z3Prisma DB health watchdog observed non-DB error: %s)r   r  r  r  r  r  r  r  r   r   TimeoutErrorrB   Zis_database_connection_errorr  r  r)   rW   )rg   r   r\   r\   r]   r    s4   
z%PrismaClient._db_health_watchdog_loopc           
         s   t   }zd}| j|I dH }|W S  tyM } z.ddl}dt| }t| |d |  }t   }|| }	t	| j
j||	d|d |d}~ww )z=
        Health check endpoint for the prisma client
        r  Nr   rh  r   r  rA  )r@  r  r  r   rV   r   r^   rY   r   r   r  r  )
rg   rr  r  r  r   rV   r  r  rs  r  r\   r\   r]   r  	  s0   zPrismaClient.health_checkc              
      sp   t jt jtddtddtf fdd}z| I dH W S  ty7 } ztd|  W Y d}~d	S d}~ww )
zd
        Get the row count from LiteLLM_SpendLogs table using PostgreSQL system statistics.
        r  r  r  rk   c                     s&   d}  j j| dI d H }|d d S )Nz
            SELECT reltuples::BIGINT
            FROM pg_class
            WHERE oid = '"LiteLLM_SpendLogs"'::regclass;
            r  r   Z	reltuples)r  r  )r  r  rr  r\   r]   _fetch_row_count3  s   z@PrismaClient._get_spend_logs_row_count.<locals>._fetch_row_countNz+Error getting LiteLLM_SpendLogs row count: r   )backoffon_exceptionexpor   r  r  r)   r   )rg   r  r   r\   rr  r]   _get_spend_logs_row_count.  s$   	z&PrismaClient._get_spend_logs_row_countc                    s.   ddl m} |  I dH }|jd|d dS )z
        Set the `LiteLLM_SpendLogs`row count in proxy state.

        This is used later to determine if we should run expensive UI Usage queries.
        r   )proxy_stateNZspend_logs_row_count)Zvariable_namers   )r   r  r  Zset_proxy_state_variable)rg   r  Z_num_spend_logs_rowsr\   r\   r]   (_set_spend_logs_row_count_in_proxy_stateK  s   
z5PrismaClient._set_spend_logs_row_count_in_proxy_stateresponse_time_msc              	   C   sf   |du rdS zt |}||kr|t dt dfvr|W S dW S  ttfy2   td|  Y dS w )z&Validate and clean response time valueNinfz-infz Invalid response_time_ms value: )r   r2  rl  r)   r   )rg   r  rs   r\   r\   r]   _validate_response_timea  s   z$PrismaClient._validate_response_timer  c              
   C   sT   t |tsdS ztt|W S  ty) } ztd|  W Y d}~dS d}~ww )zClean and validate details JSONNzFailed to clean details JSON: )r   r   r7   r6   r   r)   r   )rg   r  r   r\   r\   r]   _clean_detailst  s   
zPrismaClient._clean_detailsr   
model_namer#   healthy_countunhealthy_countr   
checked_bymodel_idc
              
      s   zSt |t |t|t|d}
|rt |dd nd| || ||r*t |nd|	r1t |	ndd}|
dd | D  td|
  | jj	j
|
dI dH W S  tyr } ztd	| d
|  W Y d}~dS d}~ww )z$Save health check result to database)r  r#   r  r  Ni  )r   r  r  r  r  c                 S   s   i | ]\}}|d ur||qS re   r\   )r  r  r  r\   r\   r]   
<dictcomp>  s    z9PrismaClient.save_health_check_result.<locals>.<dictcomp>zSaving health check data: rm  z+Error saving health check result for model : )r   r  r  r  rR  r   r)   rW   r  litellm_healthchecktablerR  r   r   )rg   r  r#   r  r  r   r  r  r  r  Zhealth_check_dataZoptional_fieldsr   r\   r\   r]   save_health_check_result~  s2   		z%PrismaClient.save_health_check_resultr   status_filterc              
      s~   z!i }|r
||d< |r||d< | j jj|ddi||dI dH }|W S  ty> } ztd|  g W  Y d}~S d}~ww )zB
        Get health check history with optional filtering
        r  r#   
checked_atr5  )r  r6  r=  skipNz$Error getting health check history: )r  r  rE  r   r)   r   )rg   r  r*  r)  r  Zwhere_clauseresultsr   r\   r\   r]   get_health_check_history  s&   
z%PrismaClient.get_health_check_historyc              
      s   z2| j jjddidI dH }i }|D ]}|jr|j|jf}nd|jf}||vr,|||< qt| W S  tyO } zt	d|  g W  Y d}~S d}~ww )z<
        Get the latest health check for each model
        r  r5  r7  Nz(Error getting all latest health checks: )
r  r  rE  r  r  r   valuesr   r)   r   )rg   Z
all_checksZlatest_checkscheckrm   r   r\   r\   r]   get_all_latest_health_checks  s&   
z)PrismaClient.get_all_latest_health_checksre   )NNNNNNNr  NNNNNNNT)NNNNr  FNN)r   r   NNNNN)Nr   r   N)Hr   r   r   r  r   r  r   r  _spend_log_transactions_lockr   r   r   r	   rh   r   r   r   r   r  r  r  r  r  r  r   r  r  rC   r  r  r   r   r  rU   r   r@  rP  rW  r_  re  rf  ri  rm  rs  r  r  ry  r  AbstractEventLoopr  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    sn  
 
P

#
\0
'
   "	 		  $	
I


0

"


%
7
;

P



	

/
r  r  r(  c                    s   |  d}|j |d}|du rA|j| dI dH }|durAtd| dt|  t|drAtt|drA| }|j||dd	 dS )
zE
    Check if a user_id exists in cache,
    if not retrieve it.
    Z_user_api_key_user_id)rm   N)r  z
User Row: z	, type = model_dump_jsoniX  )rm   rs   r   )	r}   r@  r^   r-  r   callablerB  r  r|   )r  r(  r  	cache_keyr  Zuser_rowZcache_valuer\   r\   r]   _cache_user_row  s   
r  receiver_emailsubjecthtmlc              
      s  t d}tt dd}t d}t d}t dd}|du r&td| du r1td	|  |du r<td
| |du rGtd| t }||d< | |d< ||d< td||  |du retd|t|d z;t	j
||d)}	t dddkr|	  |r|r|	j||d |	j||| d W d   W dS 1 sw   Y  W dS  ty }
 ztdt|
  W Y d}
~
dS d}
~
ww )zl
    smtp_host,
    smtp_port,
    smtp_username,
    smtp_password,
    sender_name,
    sender_email,
    r_   	SMTP_PORTZ587ZSMTP_USERNAMEZSMTP_PASSWORDZSMTP_SENDER_EMAILNz4Trying to use SMTP, but SMTP_SENDER_EMAIL is not setz+No receiver email provided for SMTP email. z$No subject provided for SMTP email. z&No HTML body provided for SMTP email. FromToSubjectzsending email from %s to %sz,Trying to use SMTP, but SMTP_HOST is not setr  )hostportZSMTP_TLSTrueFalse)r   password)msgZ	from_addrZto_addrsz*An error occurred while sending the email:)r`   ra   r  r2  r   r)   rW   attachr   smtplibSMTPstarttlsloginsend_messager   r  r   )r  r  r  Z	smtp_hostZ	smtp_portZsmtp_usernameZsmtp_passwordZsender_emailZemail_messageserverr   r\   r\   r]   
send_email  sb   


&
r  r  c                 C   s   dd l }||   }|S )Nr   r  )r  r  r  r\   r\   r]   r  F  s   r  c                 C   s   |  dr
t| dS | S )z`
    Hash the token if it's a string and starts with "sk-"

    Else return the token as is
    r4  r-  )r   r  r-  r\   r\   r]   rC  O  s   

rC  c                   @   s~   e Zd Zededededeee	f fddZ
e	ddededee ded	eeeeef   f
d
dZedefddZdS )ProxyUpdateSpendn_retry_timesr   r  end_user_list_transactionsc                    sx  t | d D ]}t }zm|jjtddd4 I d H P}| 4 I d H /}| D ]\}}	tjd ur4	 |j	j
d|i||	dddd	|	iid
d q*W d   I d H  n1 I d H sZw   Y  W d   I d H  W  d S 1 I d H srw   Y  W  d S  ty }
 z|| krt|
||d td| I d H  W Y d }
~
qd }
~
w ty }
 zt|
||d W Y d }
~
qd }
~
ww d S )Nr   r  )secondsr  r  F)r  r  r   r  	incrementrQ  rS  r   rr  r  r  )ranger@  r  Ztxr   r`  r   rZ   Zmax_end_user_budgetrH  rX  r   $_raise_failed_update_spend_exceptionr   r  r   )r  r   r  r  r   rr  Ztransactionrc  Zend_user_idZresponse_costr   r\   r\   r]   update_end_user_spend\  sX   

* z&ProxyUpdateSpend.update_end_user_spendNdb_writer_clientlogs_to_processc                    s  d}d}d}|d u r; j 4 I d H   jd | } jt|d   _W d   I d H  n1 I d H s4w   Y  d}t|dkrItdt| t }zzt| d D ]}	ztdd }
t|dkr|
d ur|d ur|
	d	sv|
d	7 }
t
d
|
 t|}|j|
d |ddidI d H }~|jdkr	 nctdt||D ],}||||  } fdd|D } jjj|ddI d H  t
dt| d ~~q j 4 I d H  t j}W d   I d H  n1 I d H sw   Y  t
t| d|  W  n9 ty8 } z,|	d u rd}	td|	d | t|t| |	| kr$ td|	 I d H  W Y d }~qVd }~ww W n tyT } zt|||d W Y d }~nd }~ww W |r\~d S d S |rb~w )Nr  '  FTr   z6Spend tracking - processing %d spend logs for DB writer   ZSPEND_LOGS_URL/zbase_url: {}zspend/updatezContent-Typezapplication/json)urlr  headers   c                    s   g | ]	}  i |qS r\   )r  )r  entryr   r\   r]   r     s    z6ProxyUpdateSpend.update_spend_logs.<locals>.<listcomp>)r  Zskip_duplicateszFlushed z logs to the DB.z% logs processed. Remaining in queue: z]Spend tracking - DB connection error writing spend logs, retry %d/%d. logs_count=%d, error=%sr  r  )r  r  r   r)   r  r@  r  r`   ra   endswithrW   rX   r   r  postr  r  rF  Zcreate_manyr   r   r   r   r  r   r  )r  r   r  r  r  Z
BATCH_SIZEMAX_LOGS_PER_INTERVALZpopped_batchrr  r   base_urlZ	json_datar  jbatchZbatch_with_datesZremaining_countr   r\   r   r]   update_spend_logs  s   
(



(

 
z"ProxyUpdateSpend.update_spend_logsrk   c                  C   s"   ddl m}  | ddu rdS dS )z
        returns True if should not update spend in db
        Skips writing spend logs and updates to key, team, user spend to DB
        r   general_settingsdisable_spend_updatesTF)r   r  r   r   r\   r\   r]   r    s   z&ProxyUpdateSpend.disable_spend_updatesre   )r   r   r   r  r  r  r   r
   r   r   r  r   r8   r   r	   r  r   r  r\   r\   r\   r]   r  [  s4    
,cr  r   r  r  c              	      s   d}|j j| ||dI dH  | j4 I dH  t| j}W d  I dH  n1 I dH s,w   Y  td| |dkrIt| ||dI dH  dS dS )z
    Batch write updates to db.

    Triggered every minute.

    Requires:
    user_id_list: dict,
    keys_list: list,
    team_list: list,
    spend_logs: list,
    r  )r   r  r  NzSpend Logs transactions: {}r   r   r  r  )	r   Z#db_update_spend_transaction_handlerr  r   r  r)   rW   rX   update_spend_logs_job)r   r  r  r  
queue_sizer\   r\   r]   update_spend  s$   (r  c              
      s|  d}d}| j 4 I dH  t| j}W d  I dH  n1 I dH s"w   Y  |dkr-dS | j 4 I dH  | jd| }| jt|d | _W d  I dH  n1 I dH sVw   Y  tj|| |||dI dH  zddlm} || |dI dH  W n ty } zt	d| W Y d}~nd}~ww zdd	l
m}	 |	| |dI dH  W dS  ty }
 zt	d
|
 W Y d}
~
dS d}
~
ww )z
    Job to process spend_log_transactions queue.

    This job is triggered based on queue size rather than time.
    Pops the batch once, writes spend logs, then runs guardrail usage tracking.
    r  r  Nr   )r  r   r  r  r  )"process_spend_logs_guardrail_usage)r   r  z@Spend tracking - guardrail usage tracking failed (non-fatal): %s)process_spend_logs_tool_usagez;Spend tracking - tool usage tracking failed (non-fatal): %s)r  r   r  r  r  Z'litellm.proxy.guardrails.usage_trackingr  r   r)   r   Z%litellm.proxy.db.spend_log_tool_indexr  )r   r  r  r  r  r  r  r  Zguardrail_tracking_errr  Ztool_tracking_errr\   r\   r]   r  $  sb   (
(	r  c              
      st  ddl m}m} |}|}d}d}|}	td| d| d 	 zh| j4 I d	H  t| j}
W d	  I d	H  n1 I d	H s>w   Y  |
dkrx|
|krZtd
|
 d| d |}	ntd
|
 d| d t	|	| |}	t
| ||dI d	H  nt	|	| |}	t|	I d	H  W n0 ty } z$tdt| dt   t	|	| |}	t|	I d	H  W Y d	}~nd	}~ww q )aB  
    Background task that monitors the spend_log_transactions queue size
    and triggers processing when the threshold is reached.

    Args:
        prisma_client: Prisma client instance
        db_writer_client: Optional HTTP handler for external spend logs endpoint
        proxy_logging_obj: Proxy logging object
    r   )SPEND_LOG_QUEUE_POLL_INTERVALSPEND_LOG_QUEUE_SIZE_THRESHOLDg      >@g      ?z.Starting spend logs queue monitor (threshold: z, poll_interval: zs)TNzSpend logs queue size (z) reached threshold (z), triggering processingz) below threshold (z), processing with backoffr  z#Error in spend logs queue monitor: r   )litellm.constantsr	  r
  r)   r  r  r   r  rW   minr  r   r  r   r   r   rV   rY   )r   r  r  r	  r
  	thresholdZbase_intervalZmax_backoffZbackoff_multiplierZcurrent_intervalr  r   r\   r\   r]   _monitor_spend_logs_queuee  sZ   (r  r   rr  c                 C   sR   ddl }dt|  }|d |  }t }|| }t|j| |d|d | )z
    Raise an exception for failed update spend logs

    - Calls proxy_logging_obj.failure_handler to log the error
    - Ensures error messages says "Non-Blocking"
    r   NzC[Non-Blocking]LiteLLM Prisma Client Exception - update spend logs: r   r  rA  )rV   r   rY   r@  r   r   r  )r   rr  r  rV   r  r  rs  r  r\   r\   r]   r    s   	r  todayc                 C   sD   | j dkrt| jd ddtdd S t| j| j d dtdd S )N   r   days)monthr   yearr   )r  r\   r\   r]   _get_month_end_date  s   
r  current_spendsoft_budget_limitc                 C   sh   |d u rdS t  }t|}|| j}|jdkr| }n| |jd  }| ||  }||kr2td dS dS )NFr   z*Projected spend exceeds soft budget limit!T)r   r  r  r  dayr^   )r  r  r  	end_monthremaining_daysZdaily_spend_estimateprojected_spendr\   r\   r]   _is_projected_spend_over_limit  s   

r  c           
      C   s   |d u rd S t  }t|}|| j}|jdkr| }n| |jd  }| ||  }||krS|dkr6|}||fS ||  }|dkrD|}||fS || }	|t|	d }||fS d S )Nr   r   r  )r   r  r  r  r  r   )
r  r  r  r  r  Zdaily_spendr  Zlimit_exceed_dateZremaining_budgetZapprox_daysr\   r\   r]   _get_projected_spend_over_limit  s*   


r  c                 C   s^   | d u s|d u s|d u rd S d|v r-| d}|d }||vr-td|  d| d| dd S )Nmodelsr   zInvalid model for team r  z.  Valid models for team are: r   )rQ  r   )r  Zteam_configr  Zvalid_modelsZmodel_in_requestr\   r\   r]   _is_valid_team_configs  s   
r  c                 C   s   t |  d S )Ng    eA)r  r  )dtr\   r\   r]   _to_ns  s   r!  r   c                 C   sz   |du r| S |  dpi }| dpi }| dd}|du r | S |j|d}|du r,| S |j d}|du r8| S t| |S )aM  
    Check if the model has guardrails defined and merge them with existing guardrails in the request data.

    Args:
        data: The request data dict
        llm_router: The LLM router instance to get deployment info from

    Returns:
        Modified data dict with merged guardrails (if any model-level guardrails exist)
    NrV  
model_infor  )r  rX  )r   Zget_deploymentrP  _merge_guardrails_with_existing)r  r   rV  r"  r  Z
deploymentmodel_level_guardrailsr\   r\   r]   r     s   
r  r$  c                 C   sh   |   }|di }|dg }t|ts|r|gng }t|ts(|r&|gng }tt|| |d< |S )a  
    Merge model-level guardrails with any existing guardrails in the request data.

    Args:
        data: The request data dict
        model_level_guardrails: Guardrails defined at the model level

    Returns:
        Modified data dict with merged guardrails in metadata
    rV  rX  )r   
setdefaultr   r   r   r  )r  r$  rh  rV  Zexisting_guardrailsr\   r\   r]   r#  F  s   

r#  c                 C   s   d}t | trFt | jtr| j}|S t | jtr t| j}|S t| dr@t| dd }t |tr4|}|S t |tr>t|}|S t| }|S t| }|S )Nr   r  )	r   r"   r  r   r   r   r  r   rB  )r   r   _errorr\   r\   r]   get_error_message_strd  s(   




r'  c                  C   ,   t d } r	| S tt ddu rdS dS )z
    Get the Redoc URL from the environment variables.

    - If REDOC_URL is set, return it.
    - If NO_REDOC is True, return None.
    - Otherwise, default to "/redoc".
    Z	REDOC_URLZNO_REDOCTNz/redocr`   ra   rM   )Z	redoc_urlr\   r\   r]   _get_redoc_urlx  
   r*  c                  C   r(  )z
    Get the docs (Swagger UI) URL from the environment variables.

    - If DOCS_URL is set, return it.
    - If NO_DOCS is True, return None.
    - Otherwise, default to "/".
    ZDOCS_URLZNO_DOCSTNr  r)  )Zdocs_urlr\   r\   r]   _get_docs_url  r+  r,  c                 C   s   ddl m} td|   t| tr/tt| ddt|  dt	j
t| ddt| d	|jd
S t| tr6| S tdt|  t	j
t| dd|jd
S )zg
    Returns an Exception as ProxyException, this ensures all exceptions are OpenAI API compatible
    r   )r#   zException: r  zerror()paramNoner  )r  r-  r.  codezInternal Server Error, )fastapir#   r)   r  r   r"   r   rB  r   r   internal_server_errorHTTP_500_INTERNAL_SERVER_ERROR)r   r#   r\   r\   r]   handle_exception_on_proxy  s"   




r4  featurec                 C   sL   ddl m} | rd|  dtjj }ndtjj }|s$tdd|idd	S )
zC
    Raises an HTTPException if the user is not a premium user
    r   )r   z=This feature is only available for LiteLLM Enterprise users: z. z=This feature is only available for LiteLLM Enterprise users. i  r   r  N)r   r   r   Znot_premium_userrs   r"   )r5  r   Z
detail_msgr\   r\   r]   _premium_user_check  s   r6  r   c                 C   s8   | du s|du r
dS |  }t|}d}| |v rd}|S )zD
    Returns True if the model is in the llm_router model names
    NFT)get_model_namesr  )r   r   Zmodel_namesZmodel_names_setZ
is_in_listr\   r\   r]   is_known_model  s   r8  
index_namec                 C   s   t jdu rdS | t j v S )zZ
    Returns True if the vector store index is in the llm_router vector store indexes
    NF)rZ   Zvector_store_index_registryZget_vector_store_indexes)r9  r\   r\   r]   is_known_vector_store_index  s   
r:  	base_pathr  c                 C   s\   |  d} |d}| s|rd| S dS |s| S | d| r%| }|S |  d| }|S )Nr  )rstriplstripr  )r;  r  Z
final_pathr\   r\   r]   
join_paths  s   

r>  request_base_urlc                 C   sV   t  }|d ur
|}n| }t }|d ur&|dkr!t||}t||S t||S t||S )Nr   )get_proxy_base_urlget_server_root_pathr>  )r?  r  Zserver_base_urlr  Zserver_root_pathZintermediate_urlr\   r\   r]   get_custom_url  s   



rB  c                   C   s
   t dS )z@
    Get the proxy base url from the environment variables.
    r  r`   ra   r\   r\   r\   r]   r@    s   
r@  c                   C   s   t ddS )z
    Get the server root path from the environment variables.

    - If SERVER_ROOT_PATH is set, return it.
    - Otherwise, default to "/".
    ZSERVER_ROOT_PATHr   rC  r\   r\   r\   r]   rA    s   rA  r  c                 C   s*   ddl m} |d u rttjd| id|S )Nr   r   r   r  )r   r   r"   r#   r3  )r  r   r\   r\   r]   get_prisma_client_or_throw  s   rD  rm   c                 C   sX   ddl }t| tsdS dt|   krdkr*n dS |d| r"dS |d| r*dS dS )	z
    Validates API key format:
    - sk- keys: must match ^sk-[A-Za-z0-9_-]+$
    - hashed keys: must match ^[a-fA-F0-9]{64}$
    - Length between 20 and 100 characters
    r   NFr  r   z^sk-[A-Za-z0-9_-]+$Tz^[a-fA-F0-9]{64}$)r   r   r   r   r   )rm   r   r\   r\   r]   is_valid_api_key'  s   
rE  c            
      C   s   ddl } td}td}td}td}td}|r`|r`|r`| j|}|r1| j|nd}| j|}|rJd	| d
| d| d| }	nd	| d| d| }	|r^|	d| 7 }	|	S dS )z
    Construct a DATABASE_URL from individual environment variables.
    Returns:
        Optional[str]: The constructed DATABASE_URL or None if required variables are missing
    r   NZDATABASE_HOSTZDATABASE_USERNAMEZDATABASE_PASSWORDZDATABASE_NAMEr  r   zpostgresql://:@r  z?schema=)urllib.parser`   ra   parse
quote_plus)
urllibZdatabase_hostZdatabase_usernameZdatabase_passwordZdatabase_nameZdatabase_schemaZdatabase_username_encZdatabase_password_encZdatabase_name_encr  r\   r\   r]   $construct_database_url_from_env_vars:  s$   




rL  Fr#  r=   r(   r  
user_modelr  include_model_access_groupsonly_model_access_groupsreturn_wildcard_routesr   r,   c                    s   ddl m} ddlm}m}m} ddlm} |du r g }i }n| }|	 }|| |||d}| j
}|rT|rT|rT|
rTg }||||
|dI dH }|| |dI dH  |j}|||||d	}||||||d
d|	||||d
}|S )a  
    Get the list of models available to a user based on their API key and team permissions.

    Args:
        user_api_key_dict: User API key authentication object
        llm_router: LiteLLM router instance
        general_settings: General settings from config
        user_model: User-specific model
        prisma_client: Prisma client for database operations
        proxy_logging_obj: Proxy logging object
        team_id: Specific team ID to check (optional)
        include_model_access_groups: Whether to include model access groups
        only_model_access_groups: Whether to only return model access groups
        return_wildcard_routes: Whether to return wildcard routes

    Returns:
        List of model names available to the user
    r   )get_team_object)get_complete_model_listget_key_modelsget_team_models)validate_membershipN)r#  proxy_model_listmodel_access_groupsrN  )r  r   r   r  )r#  Z
team_table)r>  rV  rW  rN  infer_model_from_keysF)

key_modelsr>  rV  rM  rX  rP  r   rW  rN  rO  )Zlitellm.proxy.auth.auth_checksrQ  litellm.proxy.auth.model_checksrR  rS  rT  Z1litellm.proxy.management_endpoints.team_endpointsrU  r7  Zget_model_access_groupsr>  r  r   )r#  r   r  rM  r   r  r  rN  rO  rP  r   rQ  rR  rS  rT  rU  rV  rW  rY  r>  Zteam_objectZ
all_modelsr\   r\   r]   get_available_models_for_user_  sZ   
r[  r  providerinclude_metadatafallback_typec                 C   sv   ddl m} | dt|d}|r9i }|dur|nd}g d}	||	vr*tdd	|	 d
|| ||d}
|
|d< ||d< |S )aW  
    Create a standardized model info response.

    Args:
        model_id: The model ID
        provider: The model provider
        include_metadata: Whether to include metadata
        fallback_type: Type of fallbacks to include
        llm_router: LiteLLM router instance

    Returns:
        Dictionary containing model information
    r   )get_all_fallbacksr   )r  objectcreatedZowned_byNgeneral)rb  Zcontext_windowZcontent_policyr  z'Invalid fallback_type. Must be one of: r  )r   r   r^  	fallbacksrV  )rZ  r_  r   r"   )r  r\  r]  r^  r   r_  r"  rV  Zeffective_fallback_typeZvalid_fallback_typesrc  r\   r\   r]   create_model_info_response  s0   rd  available_modelsc                    sl   d| v r'dd |  dD } fdd|D }|r%tddd|dd
S |  vr4tdd	| dd
S )aZ  
    Validate that a model is accessible to the user.
    Supports batch requests with comma-separated model IDs.

    Args:
        model_id: The model ID to validate (can be comma-separated for batch requests)
        available_models: List of models available to the user

    Raises:
        HTTPException: If the model is not accessible
    ,c                 S   s   g | ]}|  qS r\   )r   r  mr\   r\   r]   r     s    z)validate_model_access.<locals>.<listcomp>c                    s   g | ]}| vr|qS r\   r\   rg  re  r\   r]   r   	  s    i  z=The following model(s) do not exist or are not accessible: {}r  r  z2The model `{}` does not exist or is not accessibleN)r   r"   rX   r  )r  re  r  Zinaccessible_modelsr\   ri  r]   validate_model_access  s&   	rj  ))r  r   )r  r   )deltar   _PRESERVED_NONE_FIELDSTobjpreserve_fieldsexclude_unsetc                 C   s   | j d|d}|d}|s|S | j}t||D ]/\}}tD ](\}}	||}
|
du r,q|	|
vrFt||d}|durFt||	rFt||	|
|	< qq|S )a  
    Serialize a Pydantic model to a dictionary while preserving specific fields
    even if they are None.

    Fields listed in _PRESERVED_NONE_FIELDS are restored after
    model_dump(exclude_none=True) strips them.

    Args:
        obj: The Pydantic BaseModel instance to serialize
        preserve_fields: Deprecated, kept for backward compatibility.
        exclude_unset: Whether to exclude fields that were not explicitly set

    Returns:
        Dictionary representation with None values excluded except for preserved fields
    T)r^  ro  choicesN)r   r   rp  ziprl  rB  r   )rm  rn  ro  r  rp  Zobj_choicesZ
choice_objZchoice_dictZ
sub_object
field_nameZsub_dictZsub_objr\   r\   r]    model_dump_with_preserved_fields#  s"   

	rs  r  re   )NNNFFFNr  r  )r   r   r  r   r`   r  r  r@  rV   r   r   r   r   Zemail.mime.multipartr   Zemail.mime.textr   typingr   r	   r
   r   r   r   r   r   r   rZ   r   r  r   r   Zlitellm.proxy._typesr   r   r   r   r   r   r=  r   Zlitellm.types.utilsr   r   Z>litellm_enterprise.enterprise_callbacks.send_emails.base_emailr   Z@litellm_enterprise.enterprise_callbacks.send_emails.resend_emailr   ZBlitellm_enterprise.enterprise_callbacks.send_emails.sendgrid_emailr    Z>litellm_enterprise.enterprise_callbacks.send_emails.smtp_emailr!   ImportErrorr  r1  r"   r#   Zlitellm.litellm_core_utilsZ*litellm.litellm_core_utils.litellm_loggingr$   r%   r&   r'   r(   Zlitellm._loggingr)   Zlitellm._service_loggerr*   r+   Zlitellm.caching.cachingr,   r-   Zlitellm.caching.dual_cacher.   Zlitellm.exceptionsr/   Z%litellm.integrations.custom_guardrailr0   r1   Z"litellm.integrations.custom_loggerr2   Z1litellm.integrations.SlackAlerting.slack_alertingr3   Z(litellm.integrations.SlackAlerting.utilsr4   r5   Z*litellm.litellm_core_utils.safe_json_dumpsr6   Z*litellm.litellm_core_utils.safe_json_loadsr7   Z&litellm.llms.custom_httpx.http_handlerr8   r9   r:   r;   r<   r=   Zlitellm.proxy.auth.route_checksr>   Zlitellm.proxy.db.create_viewsr?   r@   Z'litellm.proxy.db.db_spend_update_writerrA   Z"litellm.proxy.db.exception_handlerrB   Zlitellm.proxy.db.log_db_metricsrC   Zlitellm.proxy.db.prisma_clientrD   ZLlitellm.proxy.guardrails.guardrail_hooks.unified_guardrail.unified_guardrailrE   Zlitellm.proxy.hooksrF   rG   Z'litellm.proxy.hooks.cache_control_checkrH   Z&litellm.proxy.hooks.max_budget_limiterrI   Z,litellm.proxy.hooks.parallel_request_limiterrJ   Z$litellm.proxy.litellm_pre_call_utilsrK   Z-litellm.proxy.policy_engine.pipeline_executorrL   Zlitellm.secret_managers.mainrM   Z)litellm.types.integrations.slack_alertingrN   r   rO   rP   rQ   Z0litellm.types.proxy.policy_engine.pipeline_typesrR   rS   rT   Zopentelemetry.tracerU   _Spanrn  r.  r^   rb   rc   r   r  r   r  r  r  r  r   r  r  r  r  r  rC  r  r  r  r  r   r   r  r  r  tupler  r  r!  r  r#  r'  r*  r,  r4  r6  r   r8  r:  r>  rB  r@  rA  rD  rE  rL  r[  rd  rj  rl  rs  r\   r\   r\   r]   <module>   s   
 , h              e
-                ;
I	  
*
A
H



"
&

*	

a
:
%	

