Ë
    tPêjÅÏ  ã                   óH  — U d 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 ddl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 ddlZddlmZmZ dd	lmZ dd
lmZ ddlm Z m!Z! ddl"m#Z# ddl$m%Z% ddl&m'Z' ddl(m)Z)m*Z* ddl+m,Z, ddl-m.Z.m/Z/m0Z0m1Z1m2Z2 erddl3m4Z5 ddl6m7Z7 dZ8ee9d<   dZ:ee9d<   dZ;ee9d<   dZ<ee9d<   dZ=ee9d<    ei «      Z>ee
e?ef      e9d<    G d„ d e«      Z@ G d!„ d"ed#¬$«      ZA G d%„ d&ed#¬$«      ZB G d'„ d(eC«      ZDe G d)„ d*«      «       ZE G d+„ d,e e«      ZFy)-zGRubrik LiteLLM Plugin for prompt/response moderation and batch logging.é    N)ÚCounter)Ú	AwaitableÚMappingÚSequence)Ú	dataclass)ÚMappingProxyType)ÚTYPE_CHECKINGÚAnyÚFinalÚLiteralÚOptionalÚ	TypedDict)ÚNeverÚReadOnly)Úverbose_logger)ÚCustomBatchLogger)ÚCustomGuardrailÚModifyResponseException)Úsafe_deep_copy)ÚModelParamHelper)Úconvert_content_list_to_str)Úget_async_httpx_clientÚhttpxSpecialProvider)ÚGuardrailEventHooks)ÚChatCompletionMessageToolCallÚFunctionÚGenericGuardrailAPIInputsÚStandardLoggingPayloadÚ!StandardLoggingUserAPIKeyMetadata)ÚLogging)ÚUserAPIKeyAuthz/v1/after_completion/openai/v1Ú!_WEBHOOK_PATH_RESPONSE_MODERATIONz/v1/before_prompt/openai/v1Ú_WEBHOOK_PATH_PROMPT_MODERATIONz/v1/litellm/batchÚ_WEBHOOK_PATH_LOGGING_BATCHi'  Ú_MAX_QUEUE_SIZEg      N@Ú_DROP_WARNING_INTERVAL_SECONDSÚ_EMPTY_MAPPINGc                   ó   — e Zd ZU ee   ed<   y)Ú_ServiceToolCallÚidN)Ú__name__Ú
__module__Ú__qualname__r   ÚstrÚ__annotations__© ó    ú`/home/oem/CyberBrain26-unified/.venv/lib/python3.12/site-packages/litellm/integrations/rubrik.pyr)   r)   7   s   … Ø�‰Ôr1   r)   c                   ó4   — e Zd ZU ee   ed<   eee      ed<   y)Ú_ServiceMessageÚcontentÚ
tool_callsN)r+   r,   r-   r   r.   r/   r   r)   r0   r1   r2   r4   r4   ;   s   … Ø�c‰]ÓØ˜Ð"2Ñ3Ñ4Ô4r1   r4   F)Útotalc                   ó   — e Zd ZU ee   ed<   y)Ú_ServiceChoiceÚmessageN)r+   r,   r-   r   r4   r/   r0   r1   r2   r9   r9   @   s   … Ø�oÑ&Ô&r1   r9   c                   ó   — e Zd ZdZy)Ú#_MalformedToolBlockingResponseErrora  Raised when the response moderation service returns a structurally invalid
    response (e.g. empty ``choices``).

    Distinct from transient network/HTTP errors so callers can surface a
    louder, misconfiguration-style log instead of treating it as a routine
    fail-open.
    N)r+   r,   r-   Ú__doc__r0   r1   r2   r<   r<   D   s   „ òr1   r<   c                   ó   — e Zd ZU dZeed<   y)ÚBlockedResponseResultz‚Returned by _extract_response_block when the response was blocked
    (response text replaced, or at least one tool call removed).ÚexplanationN)r+   r,   r-   r=   r.   r/   r0   r1   r2   r?   r?   N   s   … ñDð Ôr1   r?   c                   óÎ  ‡ — e Zd Z	 	 dVdedz  dedz  fˆ fd„Zedee   fd„«       ZdWd„Z	dWd„Z
d	eddfd
„Zdej                  d   dz  fd„ZdWd„Zd„ Z	 dXdededed   ded   def
d„Zedee   dededefd„«       Zdededed   defd„Zdededed   defd„Zeded   deddfd„«       Zedee   deedf   fd„«       Z ede!defd„«       Z"ed ee   dz  defd!„«       Z#edee   d"ed#edz  de$eef   fd$„«       Z%ed%ee   dz  dee$ee!f   df   fd&„«       Z&ed'e$ee!f   deedf   fd(„«       Z'edede$eef   de$eef   fd)„«       Z(ed*e$ee!f   de$eef   dz  de$eef   fd+„«       Z)ed,edefd-„«       Z*ede$eef   d*e$eef   defd.„«       Z+edXd*e$eef   de$eef   dz  dedz  fd/„«       Z,ed0eeef   d1e$eef   ddfd2„«       Z-ed0eeef   d1e$eef   ddfd3„«       Z.d4e$eef   d5ede/dz  fd6„Z0dWd7„Z1dWd8„Z2d4e$eef   d5efd9„Z3d:„ Z4d;„ Z5	 dXded<e6d=d>d?edz  ddf
d@„Z7dddAdBdCedz  d=d>ddf
dD„Z8dddAdBd=d>de/fdE„Z9ed=d>de:fdF„«       Z;ed*e$ee!f   d=d>deeef   fdG„«       Z<dH„ Z=dI„ Z>dJ„ Z?dKed0e$eef   dLede$ee!f   fdM„Z@dNe$eef   de$eef   de$ee!f   fdO„ZAd0e$eef   de$ee!f   fdP„ZBedQe$ee!f   dedz  fdR„«       ZCedQe$ee!f   dSee   dTedeDdz  fdU„«       ZEˆ xZFS )YÚRubrikLoggerNÚapi_keyÚapi_basec                 ó¨  •— t        j                  «       | _        |j                  dd«       |j	                  d«      xs t
        j                  |d<   |j	                  d«      €d|d<   t        ‰| �   d| j                  t        | j                  «       «      dœ|¤Ž t        j                  d«       d| _        d| _        | j                  «        |xs t!        j"                  d	«      | _        | j$                  st        j&                  d
«       | j)                  «        t*        | _        d| _        d| _        |xs t!        j"                  d«      }|st3        d«      ‚|j5                  d«      j7                  d«      }| j9                  |«       t;        | j$                  rdd| j$                  › �dœnddi«      | _        | j?                  «       | _         y )NÚguardrail_nameÚrubrikÚ
event_hookÚ
default_onF)Ú
flush_lockÚsupported_event_hookszinitializing rubrik loggerTÚRUBRIK_API_KEYz@Rubrik: No API key configured. Requests will be unauthenticated.r   ç        ÚRUBRIK_WEBHOOK_URLzKRubrik webhook URL not configured. Set RUBRIK_WEBHOOK_URL or pass api_base.ú/z/v1zapplication/jsonzBearer )úContent-TypeÚAuthorizationrP   r0   )!ÚasyncioÚLockrJ   Ú
setdefaultÚgetr   Ú	post_callÚsuperÚ__init__ÚlistÚget_supported_event_hooksr   ÚdebugÚstreaming_end_of_stream_onlyÚ streaming_buffer_until_moderatedÚ_parse_sampling_rateÚosÚgetenvÚkeyÚwarningÚ_parse_batch_sizer%   Úmax_queue_sizeÚ_dropped_since_warningÚ_last_drop_warning_timeÚ
ValueErrorÚrstripÚremovesuffixÚ_setup_clientsr   Ú_headersÚ_start_periodic_flush_taskÚ_periodic_flush_task)ÚselfrC   rD   ÚkwargsÚ_webhook_urlÚ	__class__s        €r2   rX   zRubrikLogger.__init__W   s¦  ø€ ô "Ÿ,™,›.ˆŒØ×ÑÐ*¨HÔ5ð
  &Ÿz™z¨,Ó7ÒXÔ;N×;XÑ;Xˆˆ|ÑØ�:‰:�lÓ#Ð+Ø#(ˆF�<Ñ Ü‰Ñð 	
Ø—‘Ü"& t×'EÑ'EÓ'GÓ"Hñ	
ð ò	
ô 	×ÑÐ9Ô:ð -1ˆÔ)ð 15ˆÔ-à×!Ñ!Ô#àÒ9œbŸi™iÐ(8Ó9ˆŒØ�xŠxÜ×"Ñ"Ð#eÔfà×ÑÔ ô .ˆÔØ&'ˆÔ#Ø'*ˆÔ$àÒB¤2§9¡9Ð-AÓ#BˆÙÜÐjÓkÐkà#×*Ñ*¨3Ó/×<Ñ<¸UÓCˆØ×Ñ˜LÔ)ä+;à�xŠxð 0ÀGÈDÏHÉHÈ:ÐBVÒWà Ð"4Ð5ó,
ˆŒð @D×?^Ñ?^Ó?`ˆÕ!r1   Úreturnc                 óB   — t         j                  t         j                  gS )a  Return the guardrail event hooks this integration supports.

        Prompt moderation (``pre_call``) evaluates the user's message before
        the LLM is called. Response moderation (``post_call``) evaluates the
        assistant's reply and tool calls after the LLM returns.
        )r   Úpre_callrV   )Úclss    r2   rZ   z&RubrikLogger.get_supported_event_hooks¢   s   € ô $×,Ñ,Ô.A×.KÑ.KÐLÐLr1   c                 óX  — d| _         t        j                  d«      }|�g	 t        |j	                  «       «      }t        dt        d|«      «      | _         || j                   k7  r"t        j                  d|| j                   «       y y y # t        $ r t        j                  d|«       Y y w xY w)Ng      ð?ÚRUBRIK_SAMPLING_RATErM   z%RUBRIK_SAMPLING_RATE=%s clamped to %sz+Invalid RUBRIK_SAMPLING_RATE: %r, using 1.0)
Úsampling_rater_   r`   ÚfloatÚstripÚmaxÚminr   rb   rg   )rn   Úrbrk_sampling_rateÚparsed_rates      r2   r^   z!RubrikLogger._parse_sampling_rate¬   s¨   € Ø ˆÔÜ$&§I¡IÐ.DÓ$EÐØÐ)ðjÜ%*Ð+=×+CÑ+CÓ+EÓ%F�Ü%(¨¬c°#°{Ó.CÓ%D�Ô"Ø $×"4Ñ"4Ò4Ü"×*Ñ*Ð+RÐT_Ðae×asÑasÕtð 5ð	 *øô ò jÜ×&Ñ&Ð'TÐVhÖiðjús    A$B ÂB)Â(B)c                 óÜ   — t        j                  d«      }|r0	 t        |«      }|dk  rt        j                  d|«       y || _        y y # t        $ r t        j                  d|«       Y y w xY w)NÚRUBRIK_BATCH_SIZEr   z/RUBRIK_BATCH_SIZE=%r must be > 0, using defaultz,Invalid RUBRIK_BATCH_SIZE: %r, using default)r_   r`   Úintr   rb   Ú
batch_sizerg   )rn   Ú_batch_sizeÚparsed_sizes      r2   rc   zRubrikLogger._parse_batch_size¸   so   € ÜŸY™YÐ':Ó;ˆÙðdÜ%(¨Ó%5�Ø !Ò#Ü"×*Ñ*Ð+\Ð^iÕjà&1�D•Oð øô ò dÜ×&Ñ&Ð'UÐWbÖcðdús   ™&A	 Á A	 Á	A+Á*A+Úwebhook_urlc                 ó
  — |› t         › �| _        |› t        › �| _        |› t        › �| _        t        t        j                  ¬«      | _	        t        t        j                  dt        j                  dd¬«      i¬«      | _        y )N)Úllm_providerÚtimeoutg      @g       @)Úconnect)r‡   Úparams)r"   Úresponse_moderation_endpointr#   Úprompt_moderation_endpointr$   Úlogging_endpointr   r   ÚLoggingCallbackÚasync_httpx_clientÚhttpxÚTimeoutÚmoderation_client)rn   r…   s     r2   rj   zRubrikLogger._setup_clientsÄ   sx   € Ø/:¨mÔ<]Ð;^Ð,_ˆÔ)Ø-8¨MÔ:YÐ9ZÐ*[ˆÔ'Ø#. -Ô0KÐ/LÐ MˆÔä"8ÔFZ×FjÑFjÔ"kˆÔä!7Ü-×=Ñ=ØœuŸ}™}¨S¸#Ô>Ð?ô"
ˆÕr1   c                 óˆ   — 	 t        j                  «       }|j                  | j	                  «       «      S # t        $ r Y yw xY w)zIStart the periodic flush task only when an event loop is already running.N)rR   Úget_running_loopÚRuntimeErrorÚcreate_taskÚperiodic_flush)rn   Úloops     r2   rl   z'RubrikLogger._start_periodic_flush_taskÐ   sE   € ð	Ü!×2Ñ2Ó4ˆDð ×Ñ × 3Ñ 3Ó 5Ó6Ð6øô ò 	Ùð	ús   ‚5 µ	AÁ Ac                 ó|   — | j                   �| j                   j                  «       r| j                  «       | _         y y ©N)rm   Údonerl   ©rn   s    r2   Ú_ensure_periodic_flush_taskz(RubrikLogger._ensure_periodic_flush_taskØ   s6   € Ø×$Ñ$Ð,°×0IÑ0I×0NÑ0NÔ0PØ(,×(GÑ(GÓ(IˆDÕ%ð 1Qr1   c              ƒ   óL   K  — t        | dd«      }|�|j                  «        yy­w)a¡  Cancel the periodic flush task.

        ``moderation_client`` and ``async_httpx_client`` are shared objects
        from LiteLLM's global HTTP-client cache (``get_async_httpx_client``
        uses the same cache key for all instances with equal parameters).
        Closing them here would close the shared connection pool for every
        other logger instance; let LiteLLM manage their lifecycle instead.
        rm   N)ÚgetattrÚcancel)rn   Útasks     r2   ÚaclosezRubrikLogger.acloseÜ   s,   è ø€ ô 29¸Ð?UÐW[Ó1\ˆØÐØ�K‰K�Mð ùs   ‚"$ÚinputsÚrequest_dataÚ
input_type©ÚrequestÚresponseÚlogging_objÚLiteLLMLoggingObjc              ƒ   óÚ   K  — |dk(  r,| j                  | j                  |||«      |d«      ƒ d{  –—† S |dk(  r,| j                  | j                  |||«      |d«      ƒ d{  –—† S |S 7 Œ77 Œ­w)a{  Moderate prompts (request) and responses (response); fail-open.

        - ``request``: evaluate the prompt via the before_prompt webhook and
          block disallowed prompts before the model is called.
        - ``response``: evaluate the assistant's response text and tool calls
          via the after_completion webhook and block on a policy violation.

        litellm's guardrail-translation layer normalizes Anthropic and OpenAI
        requests/responses into ``inputs`` before this runs, so a single code
        path covers both wire formats. The configured guardrail ``mode``
        selects which surface(s) run.
        r§   zPrompt moderationNr¨   zResponse moderation)Ú_guardedÚ_moderate_promptÚ_moderate_response)rn   r£   r¤   r¥   r©   s        r2   Úapply_guardrailzRubrikLogger.apply_guardrailë   sˆ   è ø€ ð& ˜Ò"ØŸ™Ø×%Ñ% f¨l¸KÓHØØ#ó÷ ð ð
 ˜Ò#ØŸ™Ø×'Ñ'¨°¸kÓJØØ%ó÷ ð ð
 ˆðøðús!   ‚-A+¯A'°0A+Á A)Á!A+Á)A+ÚcoroÚlabelc              ƒ   óî   K  — 	 | ƒ d{  –—† S 7 Œ# t         $ r ‚ t        $ r$}t        j                  d|d¬«       |cY d}~S d}~wt        $ r%}t        j
                  d||d¬«       |cY d}~S d}~ww xY w­w)zÃAwait a moderation coroutine fail-open: re-raise an intentional
        block, log at critical for malformed service responses, and swallow
        any other error returning ``inputs`` unchanged.Nz§Response moderation service returned a malformed response: %s. Requests are NOT being checked -- verify the webhook configuration. Returning original inputs unchanged.T©Úexc_infoz8%s hook failed: %s. Returning original inputs unchanged.)r   r<   r   ÚcriticalÚ	ExceptionÚerror)r°   r£   r±   Úes       r2   r¬   zRubrikLogger._guarded  s~   è ø€ ð	Ø—:Ð�:ùÜ&ò 	ØÜ2ò 	ô
 ×#Ñ#ðFð Øõð �MûÜò 	Ü× Ñ ØJØØØõ	ð �Mûð	üsR   ‚A5„ ‰Š �A5Ž �A2¢A»A2¼A5ÁA2ÁA-Á'A2Á(A5Á-A2Á2A5c              ƒ   ól  K  — |j                  d«      }|j                  d«      }|s|s|S | j                  |xs d«      }| j                  |«      }|rt        |dt        «      nt        }|r|st        j                  d«       | j                  ||«      }	| j                  |||	«      }
| j                  ||«      }| j                  |
|«      ƒ d{  –—† }| j                  |||«      }|rG| j                  ||«      }| j                  ||«       t        |j                  ||| j                   ¬«      ‚|S 7 Œb­w)zŠSend response text + tool calls to the after_completion webhook and
        raise if either the response text or any tool call is blocked.r6   Útextsr0   Úmodel_call_detailsz^Rubrik: logging_obj present but model_call_details is empty -- request context will be missingN©r:   Úmodelr¤   rF   )rU   Ú_normalize_tool_callsÚ_join_textsrŸ   r'   r   rb   Ú_correlation_idÚ"_build_response_moderation_payloadÚ_extract_request_dataÚ%_post_to_response_moderation_endpointÚ_extract_response_blockÚ_resolve_modelÚ_stash_block_contextr   r@   rF   )rn   r£   r¤   r©   r6   rº   Úmessage_tool_callsÚsent_contentÚcall_detailsÚ
request_idÚresponse_dataÚreq_dataÚservice_responseÚblockedr½   s                  r2   r®   zRubrikLogger._moderate_response/  sA  è ø€ ð #ŸJ™J |Ó4ˆ
Ø—z‘z 'Ó*ˆÙ¡%ØˆMà$(×$>Ñ$>¸zÒ?OÈRÓ$PÐØ"×.Ñ.¨uÓ5ˆáU`”w˜{Ð,@Ä.ÔQÔftˆÙ™|Ü×"Ñ"Øpôð !×0Ñ0°¸|ÓLˆ
à#×FÑFÐGYÐ[gÐisÓtˆØ×4Ñ4°\À<ÓPˆà(,×(RÑ(RÐS`ÐbjÓ(k×"kÐØ×5Ñ5Ð6FÐHZÐ\hÓiˆáØ×.Ñ.¨|¸\ÓJˆEØ×%Ñ% k°<Ô@Ü)Ø×+Ñ+ØØ)Ø#×2Ñ2ô	ð ð ˆð #lús   ‚CD4ÃD2ÃA#D4c              ƒ   óÈ  K  — |j                  d«      }|s3|j                  d«      }|r dj                  d„ |D «       «      }|rd|dœg}|s|S | j                  ||«      }| j                  |«      ƒ d{  –—† }| j	                  |«      }	|	€|S |j                  d«      xs |j                  d«      xs d	}
| j                  ||«       t        |	|
|| j                  ¬
«      ‚7 Œl­w)zeSend the (normalized) prompt to the before_prompt webhook and raise
        if the prompt is blocked.Ústructured_messagesrº   ú
c              3   ó&   K  — | ]	  }|sŒ|–— Œ y ­wrš   r0   ©Ú.0Úts     r2   ú	<genexpr>z0RubrikLogger._moderate_prompt.<locals>.<genexpr>l  s   è ø€ Ò)@°ºa¬!Ñ)@ùó   ‚ŠÚuser©Úroler5   Nr½   Úunknownr¼   )rU   ÚjoinÚ _build_prompt_moderation_payloadÚ#_post_to_prompt_moderation_endpointÚ_extract_prompt_refusalrÆ   r   rF   )rn   r£   r¤   r©   Úmessagesrº   ÚjoinedÚpayloadrÍ   Úrefusalr½   s              r2   r­   zRubrikLogger._moderate_prompt]  só   è ø€ ð —:‘:Ð3Ó4ˆÙð "Ÿ:™: gÓ.ˆEÙØ $§	¡	Ñ)@°UÔ)@Ó @�ÙØ)/¸FÑ CÐD�HÙØˆMà×>Ñ>¸vÀ|ÓTˆØ(,×(PÑ(PÐQXÓ(Y×"YÐØ×5Ñ5Ð6FÓGˆØˆ?ØˆMà—z‘z 'Ó*ÒT¨l×.>Ñ.>¸wÓ.GÒTÈ9ˆØ×!Ñ! +¨|Ô<Ü%ØØØ%Ø×.Ñ.ô	
ð 	
ð #Zús   ‚A1C"Á3C Á4A-C"c                 ó†   — | €+t        j                  d|j                  d«      «       d|d<   yd| j                  d<   | |d<   y)aË  Stash signals so the deferred success-event skips this request and
        ``async_post_call_failure_hook`` can build the failure payload.

        - Sets a flag on ``logging_obj.model_call_details`` so the deferred
          success-event handler short-circuits.
        - Stashes a reference to ``logging_obj`` on ``request_data`` under a
          custom key. ``ProxyLogging.post_call_failure_hook`` pops only
          ``litellm_logging_obj`` before iterating callbacks, so this key
          survives.

        When ``logging_obj`` is ``None`` the success-event has no way to
        observe the block (the flag has nowhere to live), so we log an error
        instead of silently dropping the signal.
        Nz…Rubrik: moderation block fired with logging_obj=None for litellm_call_id=%s; cannot suppress success event or attach failure payload.Úlitellm_call_idÚ_rubrik_logging_objTÚ_rubrik_blocked)r   r·   rU   r»   )r©   r¤   s     r2   rÆ   z!RubrikLogger._stash_block_context�  s[   € ð& ÐÜ× Ñ ðKð × Ñ Ð!2Ó3ô	ð 37ˆLÐ.Ñ/ØØ<@ˆ×&Ñ&Ð'8Ñ9Ø.9ˆÐ*Ò+r1   r6   .c                 ó&   — t        d„ | D «       «      S )zHConvert tool_calls from inputs to ChatCompletionMessageToolCall objects.c              3   óF   K  — | ]  }t         j                  |«      –— Œ y ­wrš   )rB   Ú_normalize_tool_call©rÔ   Útcs     r2   rÖ   z5RubrikLogger._normalize_tool_calls.<locals>.<genexpr>£  s   è ø€ ÒP¸r”\×6Ñ6°r×:ÑPùs   ‚!©Útuple)r6   s    r2   r¾   z"RubrikLogger._normalize_tool_calls   s   € ô ÑPÀZÔPÓPÐPr1   rì   c                 ó  — t        | t        «      r| S t        | t        «      rr| j                  d«      xs t        }t        | j                  dd«      | j                  dd«      t        |j                  dd«      |j                  dd«      ¬«      ¬«      S t        | d«      rAt        | d«      r5t        | j                  xs dt        | dd «      xs d| j                  ¬«      S t        d	t        | «      j                  › d
| ›�«      ‚)NÚfunctionr*   Ú ÚtypeÚnameÚ	arguments)ró   rô   )r*   rò   rð   z#Cannot normalize tool_call of type ú: )Ú
isinstancer   ÚdictrU   r'   r   Úhasattrr*   rŸ   rð   Ú	TypeErrorrò   r+   )rì   Úfuncs     r2   rê   z!RubrikLogger._normalize_tool_call¥  sê   € ä�bÔ7Ô8ØˆIÜ�bœ$ÔØŸ&™& Ó,Ò>´ˆDÜ0Ø—6‘6˜$ Ó#Ø—V‘V˜F JÓ/Ü!ØŸ™ &¨"Ó-Ø"Ÿh™h {°BÓ7ôôð ô �2�tÔ¤¨¨ZÔ!8Ü0Ø—5‘5’;˜BÜ˜R ¨Ó.Ò<°*ØŸ™ôð ô
 Ð=¼dÀ2»h×>OÑ>OÐ=PÐPRÐSUÐRXÐYÓZÐZr1   rº   c                 ó8   — | sydj                  d„ | D «       «      S )z€Join response text segments into the single content string the
        webhook evaluates. Empty when there is no assistant text.rñ   rÑ   c              3   ó&   K  — | ]	  }|sŒ|–— Œ y ­wrš   r0   rÓ   s     r2   rÖ   z+RubrikLogger._join_texts.<locals>.<genexpr>Á  s   è ø€ Ò/˜qªQœÑ/ùr×   )rÜ   )rº   s    r2   r¿   zRubrikLogger._join_texts»  s   € ñ ØØ�y‰yÑ/ EÔ/Ó/Ð/r1   r5   rÊ   c                 óÌ   — d|xs ddœ}| rt        d„ | D «       «      |d<   |xs dt        j                  «       › �dt        t	        j                  «       «      dd	|| rdnd
dœgdœS )a  Build an OpenAI ChatCompletion-format dict (assistant text + tool
        calls) for the after_completion webhook.

        ``content`` is sent so the webhook can moderate the response text;
        ``None`` when the assistant produced no text (tool-call-only response).
        Ú	assistantNrÙ   c              3   ó@   K  — | ]  }|j                  d ¬«      –— Œ y­w)T)Úexclude_noneN)Ú
model_dumprë   s     r2   rÖ   zBRubrikLogger._build_response_moderation_payload.<locals>.<genexpr>Ô  s   è ø€ Ò)`Èr¨"¯-©-ÀT¨-×*JÑ)`ùs   ‚r6   ú	chatcmpl-zchat.completionrñ   r   Ústop)Úindexr:   Úfinish_reason)r*   ÚobjectÚcreatedr½   Úchoices)rî   ÚuuidÚuuid4r�   Útime)r6   r5   rÊ   r:   s       r2   rÁ   z/RubrikLogger._build_response_moderation_payloadÃ  s|   € ð  Ø’ $ñ-
ˆñ Ü$)Ñ)`ÐU_Ô)`Ó$`ˆG�LÑ!àÒ: )¬D¯J©J«L¨>Ð :Ø'Üœ4Ÿ9™9›;Ó'Øð Ø&Ù5?¡\ÀVñðñ
ð 	
r1   rà   c                 ó.   — t        d„ | xs dD «       «      S )aK  Collapse each message's content to a plain string for the webhook.

        litellm normalizes Anthropic ``/v1/messages`` requests to OpenAI shape,
        but a turn sent as content-parts (``[{"type": "text", ...}]``) stays a
        list. The before_prompt webhook reads ``content`` as a string and drops
        non-string content, so we flatten text parts here (images skipped, per
        ``convert_content_list_to_str``) -- otherwise block-content prompts
        would pass through unmoderated. Builds a new list; never mutates the
        shared ``structured_messages``.
        c              3   ó¶   K  — | ]Q  }t        |t        «      r?|j                  d «      dj                  d„ t        j                  |«      D «       «      dœ–— ŒS y­w)rÚ   rÑ   c              3   ó&   K  — | ]	  }|sŒ|–— Œ y ­wrš   r0   )rÔ   Úps     r2   rÖ   zJRubrikLogger._flatten_messages_for_moderation.<locals>.<genexpr>.<genexpr>ò  s   è ø€ Ò$b¨1Ò`a¤QÑ$bùr×   rÙ   N)rö   r÷   rU   rÜ   rB   Ú_moderation_text_parts)rÔ   r:   s     r2   rÖ   z@RubrikLogger._flatten_messages_for_moderation.<locals>.<genexpr>ï  sP   è ø€ ò 
ð
 Ü˜'¤4Ô(ð	  Ÿ™ FÓ+ØŸ9™9Ñ$b´×0SÑ0SÐT[Ó0\Ô$bÓbõñ
ùs   ‚AAr0   rí   )rà   s    r2   Ú _flatten_messages_for_moderationz-RubrikLogger._flatten_messages_for_moderationã  s$   € ô ñ 
ð
 $š> rô
ó 
ð 	
r1   r:   c                 óÖ   — | j                  d«      }t        | «      gd„ | j                  d«      xs dD «       ¢t        t        |t        «      r|j                  d«      ndxs d«      ‘­S )z‰Every attacker-controlled text segment of a message: its content plus
        the arguments of any tool call or deprecated function call.Úfunction_callc              3   ó¦   K  — | ]I  }t        |t        «      r7t        |j                  d «      xs t        j                  d«      xs d«      –— ŒK y­w)rð   rô   rñ   N)rö   r÷   r.   rU   r'   rë   s     r2   rÖ   z6RubrikLogger._moderation_text_parts.<locals>.<genexpr>   sE   è ø€ ò àÜ˜b¤$Ô'ô �R—V‘V˜JÓ'Ò9¬>×>Ñ>¸{ÓKÒQÈr×Rñùs   ‚AAr6   r0   rô   Nrñ   )rU   r   r.   rö   r÷   )r:   Úfcs     r2   r  z#RubrikLogger._moderation_text_partsø  sn   € ð —K‘K Ó0ˆô (¨Ó0ð	
ñà!Ÿ+™+ lÓ3Ò9°rôð	
ô ¬
°2´tÔ(<�—‘˜Ô$À$ÒMÈ2ÓNñ	
ð 		
r1   c                 óV  — | j                  d«      xs |j                  d«      xs dt        j                  | j                  d«      «      dœ}| j                  d«      }|�||d<   |j                  d«      }|r||d<   |j                  d«      xs |j                  d«      }|r||d<   |S )	a¡  Build the bare OpenAI request the before_prompt webhook consumes.

        Unlike the after_completion envelope, this endpoint takes a raw OpenAI
        chat-completions request. ``structured_messages`` is litellm's
        OpenAI-normalized view of the prompt, so this works for Anthropic
        ``/v1/messages`` requests too. Optional fields are sent only when
        present so the payload stays clean.
        r½   rñ   rÐ   )r½   rà   ÚtoolsrØ   Úcorrelation_keyrå   )rU   rB   r  )r£   r¤   râ   r  rØ   r  s         r2   rÝ   z-RubrikLogger._build_prompt_moderation_payload  s·   € ð —Z‘Z Ó(ÒK¨L×,<Ñ,<¸WÓ,EÒKÈÜ$×EÑEÀfÇjÁjÐQfÓFgÓhñ-
ˆð —z‘z 'Ó*ˆØÐØ$ˆG�GÑØ"×&Ñ& vÓ.ˆÙØ"ˆG�F‰Oð ".×!1Ñ!1Ð2CÓ!DÒ!kÈ×HXÑHXÐYjÓHkˆÙØ)8ˆGÐ%Ñ&Øˆr1   rÉ   c                 óî   — | s|st         S | xs t         } |xs t         }| j                  d«      xs t         }d|v r|d   }n|j                  d«      }| j                  d«      | j                  d«      |dœS )a=  Extract original request data from model_call_details for the
        response moderation service envelope.

        Includes the agent's declared ``tools`` (OpenAI-format) when available
        so the webhook's hallucination evaluator can compare returned tool calls
        against the declared tool list.
        Úoptional_paramsr  rà   r½   )rà   r½   r  )r'   rU   )rÉ   r¤   r  r  s       r2   rÂ   z"RubrikLogger._extract_request_data*  sˆ   € ñ ¡LÜ!Ð!Ø#Ò5¤~ˆØ#Ò5¤~ˆØ!-×!1Ñ!1Ð2CÓ!DÒ!VÌˆð �lÑ"Ø  Ñ)‰Eà#×'Ñ'¨Ó0ˆEð %×(Ñ(¨Ó4Ø!×%Ñ% gÓ.Øñ
ð 	
r1   Úproxy_server_requestc                 ób   — t        | t        «      s| S dD �ci c]  }|| v sŒ|| |   “Œ c}S c c}w )a  Allowlist only routing fields (``url``, ``method``) when forwarding
        ``proxy_server_request`` to an external webhook, dropping inbound
        ``headers`` (Authorization, Cookie, x-api-key, ...) and the raw
        request ``body`` so proxy credentials are not exfiltrated.)ÚurlÚmethod)rö   r÷   )r  ra   s     r2   Ú_sanitize_proxy_server_requestz+RubrikLogger._sanitize_proxy_server_requestP  s<   € ô Ð.´Ô5Ø'Ð'Ø:KÖk°3ÈsÐVjÒOj�Ð)¨#Ñ.Ñ.ÒkÐkùÒks   —	,¡,c                 óŠ   — | j                  d«      }|rt        |d«      rt        |dd«      }|xs dS |j                  dd«      S )z3Get the model name for the ModifyResponseException.r¨   r½   NrÛ   )rU   rø   rŸ   )r¤   rÉ   r¨   Úresponse_models       r2   rÅ   zRubrikLogger._resolve_modelZ  sL   € ð '×*Ñ*¨:Ó6ˆÙœ ¨'Ô2Ü07¸À'È4Ó0PˆNØ!Ò. YÐ.Ø×Ñ ¨Ó3Ð3r1   c                 óZ   — | j                  d«      xs |xs t        j                  d«      S )aa  The id that joins a blocked request's two S3 logs by filename: the
        moderation (``_blocking``) log and the failure (response) log.

        Always ``litellm_call_id``. It is assigned at request start and is
        present identically in both the guardrail path (``model_call_details``
        / ``request_data``) and the failure-hook path. Unlike ``response.id``
        or ``standard_logging_object["id"]`` it is immune to the race where a
        block fires before the response/logging object is populated, so the
        two logs correlate for every provider (OpenAI and Anthropic alike).
        rå   ©rU   r'   )rÉ   r¤   s     r2   rÀ   zRubrikLogger._correlation_ide  s,   € ð ×ÑÐ 1Ó2Òm°|Ò7UÄ~×6ZÑ6ZÐ[lÓ6mÐmr1   râ   Úsourcec                 ó6   — | j                  |«      }|r||d<   yy)aý  Pin ``payload["id"]`` to ``litellm_call_id`` in place so this log
        shares its S3 filename id with the moderation (``_blocking``) and
        failure logs for the same request -- for every provider.

        ``standard_logging_object["id"]`` is the provider response id
        (``response_obj.get("id", litellm_call_id)``), a ``chatcmpl-*`` value
        for OpenAI, which would not correlate. ``litellm_call_id`` is assigned
        at request start and is identical across all log paths. Falls back to
        the existing id when ``litellm_call_id`` is somehow absent rather than
        writing a null filename key.

        ``source`` may be ``model_call_details`` directly or a ``kwargs`` dict
        that aliases it -- same shape either way.
        r*   N)rÀ   )ru   râ   r$  Ú
correlateds       r2   Ú_apply_correlation_idz"RubrikLogger._apply_correlation_ids  s%   € ð   ×/Ñ/°Ó7ˆ
ÙØ&ˆG�DŠMð r1   c                 ó&  — |j                  d«      }|sy	 d|dœ}| j                  d«      }t        |t        «      r
|g|¢­| d<   yt        |t        t        f«      r||f| d<   yy# t
        $ r"}t        j                  d|d¬«       Y d}~yd}~ww xY w)aõ  Prepend ``source["system"]`` onto ``payload["messages"]``.

        Builds a NEW messages list rather than mutating ``payload["messages"]``
        in place. The fallback branch of ``_prepare_block_failure_payload``
        aliases ``call_details["messages"]`` directly, so an in-place
        ``list.insert(0, ...)`` would mutate the shared source dict.

        No-op if no system prompt is present. Tolerates list/dict/str
        message shapes; on unexpected shape, leaves payload alone.
        ÚsystemNrÙ   rà   z+Rubrik: failed to prepend system prompt: %sTr³   )rU   rö   rY   r÷   r.   r¶   r   rb   )râ   r$  Úsystem_promptÚsystem_scaffoldrà   r¸   s         r2   Ú_prepend_system_promptz#RubrikLogger._prepend_system_prompt‡  sŸ   € ð  &Ÿz™z¨(Ó3ˆÙØð	Ø.6À=Ñ%QˆOØ%Ÿk™k¨*Ó5ˆHÜ˜(¤DÔ)Ø'6Ð&B¸Ñ&B�˜
Ò#Ü˜H¤t¬S kÔ2Ø'6¸Ð&A�˜
Ò#ð 3øäò 	Ü×"Ñ"Ø=ØØ÷ò ûð	ús   –/A% ÁA% Á%	BÁ.BÂBro   Ú
event_typec              ƒ   óø   K  — t        j                   «       | j                  kD  r"t        j                  d|| j                  «       yt	        |d   «      }| j                  ||«       | j                  ||«       |S ­w)z+Shared logic for success logging (sampled).z-Skipping Rubrik %s logging (sampling_rate=%s)NÚstandard_logging_object)Úrandomrx   r   r[   r   r'  r,  )rn   ro   r-  Ústandard_logging_payloads       r2   Ú_prepare_log_payloadz!RubrikLogger._prepare_log_payload¤  sx   è ø€ ô �=‰=‹?˜T×/Ñ/Ò/Ü× Ñ Ð!PÐR\Ð^b×^pÑ^pÔqØô CQÐQWÐXqÑQrÓBsÐ à×"Ñ"Ð#;¸VÔDØ×#Ñ#Ð$<¸fÔEà'Ð'ùs   ‚A8A:c              ƒ   óü   K  — | j                  «        | j                  j                  |«       | j                  «        t	        | j                  «      | j
                  k\  r| j                  «       ƒ d {  –—†  y y 7 Œ­wrš   )r�   Ú	log_queueÚappendÚ_enforce_max_queue_sizeÚlenr‚   Úflush_queue©rn   râ   s     r2   Ú_append_and_maybe_flushz$RubrikLogger._append_and_maybe_flush´  s_   è ø€ Ø×(Ñ(Ô*Ø�‰×Ñ˜gÔ&Ø×$Ñ$Ô&Üˆt�~‰~Ó $§/¡/Ò1Ø×"Ñ"Ó$×$Ñ$ð 2Ø$ús   ‚A1A<Á3A:Á4A<c                 ód  — t        | j                  «      | j                  z
  }|dk  ry | j                  d |…= | xj                  |z  c_        t	        j                  «       }|| j
                  z
  t        k\  r:t        j                  d| j                  | j                  «       d| _        || _        y y )Nr   z¥Rubrik: log queue exceeded max_queue_size=%s; dropped %s oldest events since the last warning. The Rubrik webhook may be unhealthy or undersized for current traffic.)	r7  r4  rd   re   r  rf   r&   r   rb   )rn   ÚoverflowÚnows      r2   r6  z$RubrikLogger._enforce_max_queue_size»  s¥   € Ü˜dŸn™nÓ-°×0CÑ0CÑCˆØ�qŠ=ØØ�N‰N˜9˜H˜9Ð%Ø×#Ò# xÑ/Õ#Ü—Y‘Y“[ˆØ�×-Ñ-Ñ-Ô1OÒOÜ×"Ñ"ðBð ×#Ñ#Ø×+Ñ+ôð +,ˆDÔ'Ø+.ˆDÕ(ð Pr1   c              ƒ   óà   K  — 	 | j                  ||«      ƒ d {  –—† }|€y | j                  |«      ƒ d {  –—†  y 7 Œ!7 Œ# t        $ r#}t        j                  d||d¬«       Y d }~y d }~ww xY w­w)NzCRubrik %s logging hook failed: %s. Skipping logging for this event.Tr³   )r2  r:  r¶   r   r·   )rn   ro   r-  râ   r¸   s        r2   Ú_enqueue_log_eventzRubrikLogger._enqueue_log_eventÍ  sv   è ø€ ð	Ø#'×#<Ñ#<¸VÀZÓ#P×PˆGØˆØØ×.Ñ.¨wÓ7×7Ñ7ð Qøð 8ùÜò 	Ü× Ñ ØUØØØ÷	ò ûð	üsL   ‚A.„? ™;š?  A.¡? µ=¶? ºA.»? ½? ¿	A+ÁA&Á!A.Á&A+Á+A.c              ƒ   ó²   K  — |j                  d«      r&t        j                  d|j                  d«      «       y | j                  |d«      ƒ d {  –—†  y 7 Œ­w)Nrç   zERubrik: skipping success event for blocked request litellm_call_id=%srå   Úsuccess)rU   r   r[   r?  ©rn   ro   Úresponse_objÚ
start_timeÚend_times        r2   Úasync_log_success_eventz$RubrikLogger.async_log_success_eventÛ  sN   è ø€ ð �:‰:Ð'Ô(Ü× Ñ ØWØ—
‘
Ð,Ó-ôð Ø×%Ñ% f¨iÓ8×8Ò8ús   ‚AAÁAÁAc              ƒ   óD   K  — | j                  |d«      ƒ d {  –—†  y 7 Œ­w)NÚfailure)r?  rB  s        r2   Úasync_log_failure_eventz$RubrikLogger.async_log_failure_eventæ  s   è ø€ ð ×%Ñ% f¨iÓ8×8Ò8ús   ‚ ˜™ Úoriginal_exceptionÚuser_api_key_dictr!   Útraceback_strc              ƒ   óf  K  — t        |t        «      syt        |dd«      | j                  k7  ry|j	                  dd«      }|€Mt        j                  d|j                  d«      |j                  d«      |j                  t        |dd«      «       yd}| j                  ||||«      ƒ d{  –—†  y7 Œ­w)a  Log blocked requests signalled via ``ModifyResponseException``
        (prompt blocks, response/tool blocks, streaming blocks).

        Carries the stashed ``_rubrik_logging_obj``. For every other
        exception we no-op; LiteLLM's standard failure plumbing handles those.
        NrF   ræ   zsRubrik: block exception without stashed logging_obj. litellm_call_id=%s, model=%s, user_id=%s, raising_guardrail=%srå   r½   )
rö   r   rŸ   rF   Úpopr   rb   rU   Úuser_idÚ_build_and_enqueue_block_event)rn   r¤   rJ  rK  rL  r©   Úcall_ids          r2   Úasync_post_call_failure_hookz)RubrikLogger.async_post_call_failure_hookî  sÀ   è ø€ ô Ð,Ô.EÔFØô Ð%Ð'7¸Ó>À$×BUÑBUÒUØà)×-Ñ-Ð.CÀTÓJˆØÐô
 ×"Ñ"ð'ð
 × Ñ Ð!2Ó3Ø× Ñ  Ó)Ø!×)Ñ)ÜÐ*Ð,<¸dÓCô
ð à%)ˆØ×1Ñ1°+Ð?QÐSZÐ\mÓn×nÒnús   ‚B'B1Â)B/Â*B1Ú	exceptionr   rQ  c              ƒ   ó‚  K  — 	 |j                   }|j                  d«      }| j                  |||«      }	 | j                  |«      ƒ d {  –—†  y # t        t        t
        t        f$ r#}t        j                  d||d¬«       Y d }~y d }~ww xY w7 ŒD# t        $ r#}t        j                  d||d¬«       Y d }~y d }~ww xY w­w)Nrå   zbRubrik: failed to build blocked-tool payload for litellm_call_id=%s: %s. Event will NOT be logged.Tr³   zHRubrik: failed to enqueue blocked-tool event for litellm_call_id=%s: %s.)r»   rU   Ú_prepare_block_failure_payloadÚAttributeErrorÚImportErrorÚKeyErrorrù   r   r·   r:  r¶   )rn   r©   rS  rQ  rK  rÉ   râ   r¸   s           r2   rP  z+RubrikLogger._build_and_enqueue_block_event  sÄ   è ø€ ð	Ø"-×"@Ñ"@ˆLð #×&Ñ&Ð'8Ó9ˆGØ!×@Ñ@ÀÈiÐYjÓkˆGð	Ø×.Ñ.¨wÓ7×7Ñ7øô ¤¬X´yÐAò 	Ü× Ñ ØtØØØõ	ô ûð	úð 8ùÜò 	Ü× Ñ ØZØØØ÷	ò ûð	üsc   ‚B?„0A µB Á	BÁ
B ÁB?ÁBÁ(BÂB?ÂBÂB?ÂB Â	B<ÂB7Â2B?Â7B<Â<B?c                 óš  — |j                   }t        |«      j                  › d|j                  › �}|j	                  d«      }|�t        |«      }n7t        j                  d|j	                  d«      «       | j                  ||«      }||d<   | j                  |«      xs dt        j                  «       › �|d<   | j                  ||«       |S )a	  Build a failure-style payload using the exception text as response.

        Blocked-tool events are security-relevant and **bypass sampling**:
        every block is logged.

        A non-streaming block always takes the fallback, and not because of a
        race: registering a post_call guardrail sets ``_defer_async_logging``,
        which parks the success handler that would have written
        ``standard_logging_object``, and ``_flush_deferred_async_logging``
        returns early once an exception was raised. Streaming requests never set
        that flag, so a streamed block can arrive with the object already
        populated; the branch below covers it and wins over the fallback.

        For prompt blocks the LLM is never called, so ``standard_logging_object``
        is never populated. The fallback therefore must carry enough fields to
        pass the log processor's ``LogEntry`` schema (``BaseLogEntry`` requires
        ``metadata``, ``model_id``, ``model_group``, ``model_parameters``,
        ``startTime``, ``endTime``, and ``completionStartTime``). Without a
        parseable payload the log processor discards the entry with a parse
        error and no session is created, so prompt-moderation violations are
        silently dropped even though the ``_prompt_moderation.json`` forensic
        log is written correctly.

        Field sourcing for the fallback path:
        - ``model`` / ``model_group``: ``call_details["model"]`` -- this is the
          model-group name (e.g. "gpt-4o") set by the proxy before the guardrail
          fires. The router writes ``metadata["model_group"]`` only inside
          ``acompletion()``, which hasn't run yet for a prompt block.
        - ``model_id``: not available before the LLM returns hidden_params;
          defaults to empty string.
        - caller identity: ``_caller_metadata`` off the ``user_api_key_dict``
          the failure hook is handed. The enriched litellm metadata lives under
          ``call_details["litellm_params"]["metadata"]``, never at the top
          level, so the previous top-level read resolved to an empty string for
          every block.
        - time fields: ``call_details["start_time"]`` reused for all three;
          end/completion times are meaningless for a prompt block.
        rõ   r/  zyRubrik: standard_logging_object not yet on model_call_details for litellm_call_id=%s; using best-effort fallback payload.rå   r¨   r  r*   )r»   rò   r+   r:   rU   r   r   r[   Ú_build_fallback_payloadrÀ   r	  r
  r,  )rn   r©   rS  rK  rÉ   Úexception_textÚbaserâ   s           r2   rU  z+RubrikLogger._prepare_block_failure_payloadA  sÓ   € ðX *×<Ñ<ˆÜ#'¨	£?×#;Ñ#;Ð"<¸B¸y×?PÑ?PÐ>QÐ Rˆà"×&Ñ&Ð'@ÓAˆØÐÜ)7¸Ó)=‰Gä× Ñ ð6ð × Ñ Ð!2Ó3ô	ð ×2Ñ2°<ÐARÓSˆGà,ˆ�
Ñð ×,Ñ,¨\Ó:ÒXÀ	Ì$Ï*É*Ë,ÈÐ>Xˆ�‰Ø×#Ñ# G¨\Ô:àˆr1   c                 ó0   — ddl m} |j                  | «      S )a¹  Identify the caller whose request was blocked.

        Uses the same mapper the success path and the proxy spend logger use, so
        a block log and a success log agree on the caller key set.

        The import is deferred because ``litellm/integrations/`` is SDK-side
        while the mapper lives under ``proxy/``: ``rubrik.py`` is imported during
        guardrail discovery and must not pull proxy-only dependencies into its
        import chain. It is unguarded because the only dispatcher of this hook,
        ``ProxyLogging.post_call_failure_hook``, already imports fastapi at
        module scope, so there is no path where this hook runs and the mapper is
        missing.
        r   )ÚLiteLLMProxyRequestSetup)Ú$litellm.proxy.litellm_pre_call_utilsr^  Ú'get_sanitized_user_information_from_key)rK  r^  s     r2   Ú_caller_metadatazRubrikLogger._caller_metadataˆ  s   € õ 	Rà'×OÑOÐPaÓbÐbr1   c                 ój  — |j                  d«      }|�|j                  «       nd }|j                  d«      |j                  d«      xs d|j                  d«      xs ddt        j                  |j                  d«      xs t        «      ||||j                  d«      xs d| j                  |«      dd	œS )
NrD  rå   r½   rñ   r  rà   r0   rH  )r*   r½   Úmodel_groupÚmodel_idÚmodel_parametersÚ	startTimeÚendTimeÚcompletionStartTimerà   ÚmetadataÚstatus)rU   Ú	timestampr   Ú%get_standard_logging_model_parametersr'   ra  )ru   rÉ   rK  Ú
_raw_startÚ_starts        r2   rZ  z$RubrikLogger._build_fallback_payload›  s¿   € ð )×,Ñ,¨\Ó:ˆ
Ø2<Ð2H˜
×,Ñ,Ô.Èdˆà×"Ñ"Ð#4Ó5Ø!×%Ñ% gÓ.Ò4°"ð (×+Ñ+¨GÓ4Ò:¸àÜ 0× VÑ VØ× Ñ Ð!2Ó3ÒE´~ó!ð  ØØ#)Ø$×(Ñ(¨Ó4Ò:¸Ø×,Ñ,Ð->Ó?Øñ!
ð 	
r1   c              ƒ   ó˜  K  — 	 | j                   j                  | j                  || j                  ¬«      ƒ d {  –—† }|j	                  «        y 7 Œ# t
        j                  $ rE}t        j                  d|j                  j                  |j                  j                  «       ‚ d }~wt        $ r t        j                  d«       ‚ w xY w­w)N)r  ÚjsonÚheaderszRubrik HTTP Error: %s - %szRubrik Layer Error)r�   Úpostr�   rk   Úraise_for_statusr�   ÚHTTPStatusErrorr   rS  r¨   Ústatus_codeÚtextr¶   )rn   Údatar¨   r¸   s       r2   Ú_log_batch_to_rubrikz!RubrikLogger._log_batch_to_rubrikº  s°   è ø€ ð	Ø$(×$;Ñ$;×$@Ñ$@Ø×)Ñ)ØØŸ™ð %Aó %÷ ˆHð
 ×%Ñ%Õ'ðùô ×$Ñ$ò 	Ü×$Ñ$Ð%AÀ1Ç:Á:×CYÑCYÐ[\×[eÑ[e×[jÑ[jÔkØûÜò 	Ü×$Ñ$Ð%9Ô:Øð	üs9   ‚C
„5A ¹AºA ÁC
ÁA ÁCÁ$A B$Â$#CÃC
c              ƒ   ór   K  — | j                   sy| j                  | j                   ¬«      ƒ d{  –—†  y7 Œ­w)a@  Handles sending batches of responses to Rubrik.

        Note: the canonical flush path is :meth:`flush_queue`, which takes a
        single snapshot used for both sending and queue draining. This method
        is kept for direct callers / tests; it intentionally does NOT remove
        events from the queue.
        N©rw  )r4  rx  rœ   s    r2   Úasync_send_batchzRubrikLogger.async_send_batchÌ  s6   è ø€ ð �~Š~Øà×'Ñ'Ø—‘ð (ó 
÷ 	
ò 	
ús   ‚-7¯5°7c              ƒ   ó&  K  — | j                   €y| j                   4 ƒd{  –—†  | j                  s	 ddd«      ƒd{  –—†  yt        | j                  «      }t        j                  dt        |«      «       	 | j                  |¬«      ƒ d{  –—†  | j                  dt        |«      …= t        j                  «       | _	        ddd«      ƒd{  –—†  y7 Œ³7 Œ˜7 ŒJ# t        $ r Y ddd«      ƒd{  –—†7   yw xY w7 Œ+# 1 ƒd{  –—†7  sw Y   yxY w­w)aÇ  Snapshot, send, and drain in one consistent step.

        Overrides the base implementation so the same snapshot drives both
        the HTTP send and the queue truncation. This avoids the subtle
        coupling where the base class captures ``len(self.log_queue)``
        separately from the snapshot taken inside ``async_send_batch``,
        which could otherwise drift in a future refactor and cause
        duplicate deliveries to Rubrik.
        Nz#Rubrik: Flushing batch of %s eventsrz  )
rJ   r4  rY   r   r[   r7  rx  r¶   r  Úlast_flush_time)rn   Úsnapshots     r2   r8  zRubrikLogger.flush_queueÛ  s÷   è ø€ ð �?‰?Ð"Øà—?‘?÷ 	/ñ 	/Ø—>’>Ø÷	/÷ 	/ð 	/ô # 4§>¡>Ó2ˆHÜ× Ñ Ð!FÌÈHËÔVðØ×/Ñ/°XÐ/Ó>×>Ð>ð
 —‘˜¤ X£˜Ð/Ü#'§9¡9£;ˆDÔ ÷	/÷ 	/ñ 	/øð 	/øð ?ùÜò ð ÷	/÷ 	/ñ 	/ðúð	/ø÷ 	/÷ 	/ñ 	/üs§   ‚D C¡D¤C<²D½C¾DÁ4C<Á8CÂCÂCÂ1C<ÃDÃC:ÃDÃDÃCÃ	C7Ã#C<Ã$DÃ/C2Ã0DÃ6C7Ã7C<Ã:DÃ<DÄDÄDÄ
DÚendpointÚservice_namec              ƒ   óH  K  — t        j                  d||«       | j                  j                  ||| j                  ¬«      ƒ d{  –—† }|j                  «        |j                  «       }t        |t        «      s$t        |› dt        |«      j                  › d�«      ‚|S 7 ŒZ­w)záPOST ``payload`` to a Rubrik webhook and return its dict response.

        Raises:
            Exception: If the service is unavailable or returns an error.
            TypeError: If the response JSON is not a dict.
        zSending request to %s: %s)rp  rq  Nz returned non-dict JSON (z9); expected OpenAI chat completion shape or empty object.)r   r[   r’   rr  rk   rs  rp  rö   r÷   rù   rò   r+   )rn   r  râ   r€  Úhttp_responseÚresults         r2   Ú
_post_jsonzRubrikLogger._post_jsonø  s¬   è ø€ ô 	×ÑÐ8¸,ÈÔQØ%)×%;Ñ%;×%@Ñ%@ØØØ—M‘Mð &Aó &
÷  
ˆð
 	×&Ñ&Ô(Ø -× 2Ñ 2Ó 4ˆÜ˜&¤$Ô'ÜØ�.ð !Ü˜“L×)Ñ)Ð*ð +)ð)óð ð
 ˆð 
ús   ‚AB"ÁB ÁAB"rË   c              ƒ   ób   K  — ||dœ}| j                  | j                  |d«      ƒ d{  –—† S 7 Œ­w)az  Post the ``{request, response}`` envelope to the after_completion
        webhook and return its (possibly rewritten) response.

        Args:
            response_data: The OpenAI-formatted response payload to send.
            request_data: Original LLM request data to include alongside
                the response for additional context. Empty dict if unavailable.
        r¦   zResponse moderation serviceN)r„  r‹   )rn   rË   r¤   Úenvelopes       r2   rÃ   z2RubrikLogger._post_to_response_moderation_endpoint  s<   è ø€ ð '3ÀÑNˆØ—_‘_Ø×-Ñ-ØØ)ó
÷ 
ð 	
ð 
ús   ‚&/¨-©/c              ƒ   óX   K  — | j                  | j                  |d«      ƒ d{  –—† S 7 Œ­w)z�Post a bare OpenAI request to the before_prompt webhook.

        Returns ``{}`` (passthrough) or a synthetic chat.completion (block).
        zPrompt moderation serviceN)r„  rŒ   r9  s     r2   rÞ   z0RubrikLogger._post_to_prompt_moderation_endpoint#  s(   è ø€ ð
 —_‘_ T×%DÑ%DÀgÐOjÓk×kÐkÐkús   ‚!*£(¤*rÍ   c                 ó�   — | j                  d«      }|sy|d   j                  d«      xs t        }|j                  d«      }|xs dS )zøReturn the refusal text when the prompt was blocked, else None.

        The before_prompt webhook returns ``{}`` (passthrough) or a synthetic
        chat.completion whose ``choices[0].message.content`` is the refusal
        explanation.
        r  Nr   r:   r5   zRequest blocked by policy.r#  )rÍ   r  r:   r5   s       r2   rß   z$RubrikLogger._extract_prompt_refusal*  sL   € ð ;K×:NÑ:NÈyÓ:YˆÙØØ  ™Ÿ™¨	Ó2ÒD´nˆØ Ÿ™ YÓ/ˆØÒ6Ð6Ð6r1   Úall_tool_callsrÈ   c                 ó’  ‡— | j                  d«      xs d}|st        d«      ‚|d   j                  d«      xs t        }|j                  d«      xs d}|j                  d«      xs d}t        d	„ |D «       «      Št        d
„ |D «       «      }t	        |«      t	        |«      k  xs# t        ˆfd„|j                  «       D «       «       }t        |«      xr |j                  |› d�«      }	t        |«      xr
 ||k7  xr |	 }
|
rt        |xs d¬«      S |r2|	r|t	        |«      d j                  d«      }n|}t        |xs d¬«      S y)aÄ  Detect whether the webhook moderated the response text or tool calls.

        The after_completion webhook rewrites the response in place with no
        explicit "blocked" flag, so we infer a block by diffing what we sent
        against what came back:

        - Tool block: a tool call we sent is absent from the returned (allowed)
          set.
        - Text block: the returned content was REPLACED wholesale (a text
          violation), as opposed to having a tool-block explanation APPENDED to
          the original content. We tell them apart with ``startswith``, which
          mirrors the webhook's own append-vs-replace behavior.

        Returns None when nothing was moderated. A text block supersedes a tool
        block (mirroring the webhook, which drops tool calls on a text block).

        Expects service_response in OpenAI chat completion format:
            {"choices": [{"message": {"tool_calls": [...], "content": "..."}}]}
        r  r0   z3Response moderation service returned empty responser   r:   r6   r5   rñ   c              3   óJ   K  — | ]  }|j                  d «      sŒ|d    –— Œ y­w)r*   N©rU   rë   s     r2   rÖ   z7RubrikLogger._extract_response_block.<locals>.<genexpr>^  s#   è ø€ Ò9qÀrÐdf×djÑdjÐkoÕdp¸"¸T½(Ñ9qùs   ‚#™
#c              3   óN   K  — | ]  }|j                   sŒ|j                   –— Œ y ­wrš   )r*   rë   s     r2   rÖ   z7RubrikLogger._extract_response_block.<locals>.<genexpr>_  s   è ø€ Ò9bÀBÐ\^×\aÓ\a¸"¿%½%Ñ9bùs   ‚%”%c              3   óN   •K  — | ]  \  }}‰j                  |d «      |k\  –— Œ y­w)r   NrŒ  )rÔ   Útc_idÚcountÚreturned_id_countss      €r2   rÖ   z7RubrikLogger._extract_response_block.<locals>.<genexpr>c  s/   øè ø€ ò Y
Ù:F¸%ÀÐ×"Ñ" 5¨!Ó,°Õ5ñY
ùs   ƒ"%z

zResponse blocked by policy.)r@   NrÑ   zTool call blocked by policy.)rU   r<   r'   r   r7  ÚallÚitemsÚboolÚ
startswithr?   Úlstrip)rÍ   r‰  rÈ   r  r:   Úreturned_tool_callsÚreturned_contentÚrequired_id_countsÚtools_blockedÚappendedÚtext_blockedr@   r‘  s               @r2   rÄ   z$RubrikLogger._extract_response_block9  si  ø€ ð2 4D×3GÑ3GÈ	Ó3RÒ3XÐVXˆÙÜ5Ð6kÓlÐlà  ™Ÿ™¨	Ó2ÒD´nˆØ%,§[¡[°Ó%>Ò%DÀ"ÐØ")§+¡+¨iÓ"8Ò">¸BÐô 3:Ñ9qÐM`Ô9qÓ2qÐÜ29Ñ9bÈ.Ô9bÓ2bÐô  #Ð#6Ó7¼#¸nÓ:MÑMò  
ÔUXó Y
ØJ\×JbÑJbÓJdôY
ó V
ð R
ˆô ˜|Ó,ÒcÐ1A×1LÑ1LÐP\È~Ð]aÐMbÓ1cˆÜ" <Ó0ÒfÐ5EÈÑ5UÒfÐ^fÐZfˆáÜ(Ð5EÒ5fÐIfÔgÐgáÙð /¬s°<Ó/@Ð/BÐC×JÑJÈ4ÓP‘à.�Ü(°[Ò5bÐDbÔcÐcàr1   )NN)rr   Nrš   )Gr+   r,   r-   r.   rX   ÚclassmethodrY   r   rZ   r^   rc   rj   rR   ÚTaskrl   r�   r¢   r   r÷   r   r   r¯   Ústaticmethodr   r¬   r®   r­   rÆ   r   r  rî   r   r¾   r
   rê   r¿   r   rÁ   r  r  rÝ   rÂ   r  rÅ   rÀ   r'  r,  r   r2  r:  r6  r?  rF  rI  r¶   rR  rP  rU  r   ra  rZ  rx  r{  r8  r„  rÃ   rÞ   rß   r?   rÄ   Ú__classcell__)rq   s   @r2   rB   rB   V   se  ø„ ð #Ø#ñIaà�t‘ðIað ˜‘*õIaðV ðM¨$Ð/BÑ*Cò Mó ðMó
jó
dð

¨#ð 

°$ó 

ð7¨G¯L©L¸Ñ,>ÀÑ,Eó 7óJòð( 6:ñà)ðð ðð Ð1Ñ2ð	ð
 Ð1Ñ2ðð 
#óðB ð ØÐ1Ñ2ð à)ð ð ð ð 
#ò	 ó ð ðD,à)ð,ð ð,ð Ð1Ñ2ð	,ð
 
#ó,ð\"
à)ð"
ð ð"
ð Ð1Ñ2ð	"
ð
 
#ó"
ðH ð:ØÐ1Ñ2ð:àð:ð 
ò:ó ð:ð< ðQ¨(°6Ñ*:ð Q¸uÐEbÐdgÐEgÑ?hò Qó ðQð ð[ ð [Ð)Fò [ó ð[ð* ð0˜8 C™=¨4Ñ/ð 0°Cò 0ó ð0ð ð
ØÐ:Ñ;ð
àð
ð ˜$‘Jð
ð 
��f�Ñ	ò	
ó ð
ð> ð
°8¸FÑ3CÀdÑ3Jð 
ÈuÐU\Ð]`ÐbeÐ]eÑUfÐhkÐUkÑOlò 
ó ð
ð( ð
¨°°S°Ñ(9ð 
¸eÀCÈÀH¹oò 
ó ð
ð ðØ)ðà˜c 6˜kÑ*ðð 
��f�Ñ	òó ððB ð#
Ø˜c 3˜hÑ'ð#
à˜c 6˜kÑ*¨TÑ1ð#
ð 
��f�Ñ	ò#
ó ð#
ðJ ðl¸Vð lÈò ló ðlð ð4 W¨S°&¨[Ñ%9ð 4ÈÐQTÐVYÐQYÑIZð 4Ð_bò 4ó ð4ð ñn g¨c°3¨hÑ&7ð nÀwÈsÐTWÈxÑGXÐ[_ÑG_ð nÐknÐquÑkuò nó ðnð ð'¨D°°f°Ñ,=ð 'ÀwÈsÐTWÈxÑGXð 'Ð]aò 'ó ð'ð& ð¨¨S°&¨[Ñ(9ð À7È3ÐPVÈ;ÑCWð Ð\`ò ó ðð8(Ø˜c 6˜kÑ*ð(Ø8;ð(à	 $Ñ	&ó(ó %ó/ð$¨w°s¸F°{Ñ/Cð ÐQTó ò	9ò9ð %)ñ,oàð,oð &ð,oð ,ð	,oð
 ˜T‘zð,oð 
ó,oð\#à(ð#ð -ð#ð �t‘ð	#ð
 ,ð#ð 
ó#ðJEà(ðEð -ðEð ,ð	Eð
 
 óEðN ðcÐ,<ð cÐAbò có ðcð$ ð
à˜c 3˜hÑ'ð
ð ,ð
ð 
ˆc�6ˆkÑ	ò	
ó ð
ò<ò$
ò/ð:¨ð °w¸sÀF¸{Ñ7Kð Ð[^ð ÐcjÐknÐpsÐksÑctó ð.
à˜s F˜{Ñ+ð
ð ˜c 6˜kÑ*ð
ð 
��c�Ñ	ó	
ð(lÀÈÈfÈÑAUð lÐZaÐbeÐgjÐbjÑZkó lð ð7°'¸#¸s¸(Ñ2Cð 7ÈÈdÉ
ò 7ó ð7ð ðEØ! # s (Ñ+ðEà Ð!>Ñ?ðEð ðEð 
 Ñ	%ò	Eó ôEr1   rB   )Gr=   rR   r_   r0  r  r	  Úcollectionsr   Úcollections.abcr   r   r   Údataclassesr   Útypesr   Útypingr	   r
   r   r   r   r   r�   Útyping_extensionsr   r   Úlitellm._loggingr   Ú(litellm.integrations.custom_batch_loggerr   Ú%litellm.integrations.custom_guardrailr   r   Ú'litellm.litellm_core_utils.core_helpersr   Ú-litellm.litellm_core_utils.model_param_helperr   Ú8litellm.litellm_core_utils.prompt_templates.common_utilsr   Ú&litellm.llms.custom_httpx.http_handlerr   r   Úlitellm.types.guardrailsr   Úlitellm.types.utilsr   r   r   r   r   Ú*litellm.litellm_core_utils.litellm_loggingr    rª   Úlitellm.proxy._typesr!   r"   r/   r#   r$   r%   r&   r'   r.   r)   r4   r9   r¶   r<   r?   rB   r0   r1   r2   ú<module>r²     s  ðÚ Mã Û 	Û Û Û Ý ß 8Ñ 8Ý !Ý "ß J× Jã ß -å +Ý F÷õ CÝ Jõ÷õ 9÷õ ñ õõ 4à+KÐ ! 5Ó KØ)FÐ  Ó FØ%8Ð ˜UÓ 8Ø€�Ó Ø(,Ð  Ó ,Ù-=¸bÓ-A€��g˜c 5˜jÑ)Ñ*Ó Aô�yô ô5�i uõ 5ô
'�Y eõ 'ô¨)ô ð ÷ð ó ðôi�?Ð$5õ ir1   