Ë
    tPêj¾Œ  ã                   óÖ   — 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mZ ddl	Z	ddl
mZ ddlmZ ddlmZ ddlmZ ddlmZ dd	lmZmZmZ 	 ddlmZ  G d
„ de«      Zy# e$ r dZY Œw xY w)a¢  
New Relic AI Monitoring Integration for LiteLLM

This module provides integration with New Relic's AI Monitoring feature to track
LLM requests, responses, and usage metrics.

Environment Variables (consumed by the New Relic agent at process bootstrap -
set via container env, or before invoking `newrelic-admin run-program`):
    NEW_RELIC_LICENSE_KEY: Your New Relic license key (required)
    NEW_RELIC_APP_NAME: Your application name (required)

UI- and runtime-toggleable:
    NEW_RELIC_AI_MONITORING_RECORD_CONTENT_ENABLED: Whether to record message
        content (optional, default: true)

Configuration:
    Message logging can be controlled via (both must agree to record):
    1. turn_off_message_logging parameter - pass via callback initialization or config YAML
    2. NEW_RELIC_AI_MONITORING_RECORD_CONTENT_ENABLED env var

    Default behavior: Messages ARE recorded unless explicitly disabled by either method
    Either method can disable recording - both must enable for recording to occur

Usage - Python SDK:
    import litellm
    litellm.callbacks = ["newrelic"]

    # Or with explicit configuration:
    from litellm.integrations.newrelic import NewRelicLogger
    litellm.callbacks = [NewRelicLogger(turn_off_message_logging=True)]

Usage - Proxy Server (config.yaml):
    litellm_settings:
      callbacks: ["newrelic"]
      newrelic_params:
        turn_off_message_logging: true  # Disable message content recording

    # Or disable via environment variable:
    # export NEW_RELIC_AI_MONITORING_RECORD_CONTENT_ENABLED=false

    # Ensure New Relic agent is initialized (use newrelic-admin or initialize manually)
    # newrelic-admin run-program python your_app.py
é    N)ÚAnyÚFinal)Úverbose_logger)ÚCustomLogger)Úshould_redact_message_logging)ÚIntegrationHealthCheckStatus)ÚNewRelicInitParams)ÚMessageÚModelResponseÚStandardLoggingPayloadc                   óö  ‡ — e Zd ZU dZdZeed<    ej                  «       Z	ˆ fd„Z
defd„Zedefd„«       Zd8ded	edefd
„Zdefd„Zd„ Zd„ Z	 d9dededz  defd„Zdededefd„Z	 d9dededz  defd„Z	 d9dedededz  deeef   fd„Z	 d9dededz  deeef   fd„Zdedefd„Zdedefd„Z 	 d9dededededz  dedz  f
d„Z!	 d9dededz  deeef   fd„Z"de#ez  defd„Z$	 d9deded ed!ededz  de%eeef      fd"„Z&	 	 d:d#ed$edz  d%ed ed!ed&ed'ed(eeef   d)edz  d*eeef   dz  fd+„Z'd#ed,ed$edz  d-e%eeef      fd.„Z(d/„ Z)	 	 d:dedededz  dedz  fd0„Z*de+fd1„Z,d2„ Z-d3„ Z.d4„ Z/d5„ Z0d6„ Z1d7„ Z2ˆ xZ3S );ÚNewRelicLoggera	  
    New Relic logger for LiteLLM to send AI monitoring events.

    This logger creates two types of New Relic custom events:
    1. LlmChatCompletionSummary - One per completion request
    2. LlmChatCompletionMessage - One per message (request and response)
    g        Ú_last_metric_emission_timec                 ó¨  •— | j                  «       }|j                  «       D ]  \  }}|j                  ||«       Œ t        ‰| �  di |¤Ž t        j                  d«      | _        t        j                  d«      | _        | j                  r| j                  st        j                  d«       d| _        y t        €t        j                  d«       d| _        y 	 t        j                  d¬«       d| _        t        j                  d	| j                  | j                   «       y # t"        $ r'}t        j                  d
|«       d| _        Y d }~y d }~ww xY w)NÚNEW_RELIC_LICENSE_KEYÚNEW_RELIC_APP_NAMEz€New Relic integration requires NEW_RELIC_LICENSE_KEY and NEW_RELIC_APP_NAME environment variables. Integration will be disabled.FúŒNew Relic Python agent not installed. Review the New Relic integration documentation at https://docs.litellm.ai/docs/observability/newrelic.r   )ÚtimeoutTzFNew Relic AI Monitoring initialized for app: %s, content recording: %szGFailed to initialize New Relic agent: %s. Integration will be disabled.© )Ú_get_newrelic_paramsÚitemsÚ
setdefaultÚsuperÚ__init__ÚosÚgetenvÚlicense_keyÚapp_namer   ÚwarningÚenabledÚ_newrelic_agentÚerrorÚregister_applicationÚinfoÚrecord_contentÚ	Exception)ÚselfÚkwargsÚdict_newrelic_paramsÚkÚvÚeÚ	__class__s         €úk/home/oem/CyberBrain26-unified/.venv/lib/python3.12/site-packages/litellm/integrations/newrelic/newrelic.pyr   zNewRelicLogger.__init__P   s4  ø€ ð '+×&?Ñ&?Ó&AÐð
 )×.Ñ.Ó0ò 	$‰DˆAˆqØ×Ñ˜a Õ#ð	$ô 	‰ÑÑ"˜6Ò"ô Ÿ9™9Ð%<Ó=ˆÔÜŸ	™	Ð"6Ó7ˆŒð ×Ò t§}¢}Ü×"Ñ"ðZôð !ˆD�LÜÐ$Ü× Ñ ð _ôð !ˆD�Lð%ô  ×4Ñ4¸QÕ?à#�”Ü×#Ñ#Ø\Ø—M‘MØ×'Ñ'õøô
 ò %Ü×$Ñ$Ð%nÐpqÔrØ$�—‘ûð%ús   ÃAD! Ä!	EÄ*EÅEÚreturnc                 ó0  — i }t         j                  �ƒt        t         j                  t        «      r t         j                  j	                  «       }|S t        t         j                  t
        «      r't        di t         j                  ¤Žj	                  «       }|S )zª
        Get the newrelic_params from litellm.newrelic_params

        These are params specific to initializing the NewRelicLogger e.g. turn_off_message_logging
        r   )ÚlitellmÚnewrelic_paramsÚ
isinstancer	   Ú
model_dumpÚdict)r'   r)   s     r.   r   z#NewRelicLogger._get_newrelic_params   sy   € ð &(ÐÜ×"Ñ"Ð.Üœ'×1Ñ1Ô3EÔFÜ'.×'>Ñ'>×'IÑ'IÓ'KÐ$ð $Ð#ô œG×3Ñ3´TÔ:ä'9Ñ'T¼G×<SÑ<SÑ'T×'_Ñ'_Ó'aÐ$Ø#Ð#ó    c                 óD   — | j                    xr | j                  dd«      S )a¤  Whether to record message content in New Relic.

        Both turn_off_message_logging param AND NEW_RELIC_AI_MONITORING_RECORD_CONTENT_ENABLED
        env var must agree to record content. If either disables recording, content will not
        be recorded. Read at call time so UI config changes take effect without a restart.
        Default: True (record content) unless explicitly disabled by either method.
        Ú.NEW_RELIC_AI_MONITORING_RECORD_CONTENT_ENABLEDT)Úturn_off_message_loggingÚ_parse_bool_env)r'   s    r.   r%   zNewRelicLogger.record_contentŽ   s,   € ð ×1Ñ1Ð1ò 
°t×7KÑ7KØ<¸dó8
ð 	
r6   Úvar_nameÚdefaultc                 ó¸   — t        j                  |«      }|s|S |j                  «       j                  «       }|dv ry|dv ryt	        j
                  d|||«       |S )u‘  Parse a boolean environment variable.

        Accepts true/false, 1/0, yes/no, on/off (case-insensitive,
        whitespace-tolerant) â€” matching the convention used in
        ``litellm/__init__.py`` and the standard library's
        ``configparser.BOOLEAN_STATES``. Unrecognised values log a
        warning and fall back to ``default`` rather than silently
        flipping user intent.
        )Ú1ÚtrueÚyesÚonT)Ú0ÚfalseÚnoÚoffFzj%s=%r is not a recognised boolean (accepts true/false, 1/0, yes/no, on/off). Falling back to default (%s).)r   r   ÚstripÚlowerr   r   )r'   r;   r<   ÚrawÚvalues        r.   r:   zNewRelicLogger._parse_bool_env›   sg   € ô —Y‘Y˜xÓ(ˆÙØˆNØ—y‘y“{×(Ñ(Ó*ˆØÐ.Ñ.ØØÐ/Ñ/ØÜ×ÑØxØØØô		
ð ˆr6   c                 óx   — 	 ddl m}  |d«      S # t        $ r }t        j                  d|«       Y d}~yd}~ww xY w)z£
        Get litellm version for supportability metrics.

        Returns:
            Version string (e.g., "1.80.0") or "unknown" if unable to determine
        r   )Úversionr1   z'Unable to determine litellm version: %sNÚunknown)Úimportlib.metadatarK   r&   r   r   )r'   rK   r,   s      r.   Ú_get_litellm_versionz#NewRelicLogger._get_litellm_versionµ   s9   € ð	Ý2á˜9Ó%Ð%øÜò 	Ü×"Ñ"Ð#LÈaÔPÜûð	ús   ‚ �	9™4´9c                 ó‚  — 	 | j                  «       }d|› �}t        j                  «       }t        j                  «       t        _        |r5|j                  r)|j                  |d«       t        j                  d|«       yt        j                  d«       y# t        $ r }t        j                  d|«       Y d}~yd}~ww xY w)a�  
        Emit New Relic supportability metric for LiteLLM usage.

        Per spec, this metric should be emitted at least once every 27 hours
        to indicate the library is in use. Format:
        Supportability/Python/ML/LiteLLM/{version}

        This method updates _last_metric_emission_time and should
        be called within a lock when checking periodic emission.
        z!Supportability/Python/ML/LiteLLM/é   z+Emitted New Relic supportability metric: %sz@New Relic application is not enabled; skipping metric recording.z(Failed to emit supportability metric: %sN)rN   r!   ÚapplicationÚtimer   r   r    Úrecord_custom_metricr   r$   r&   r   )r'   Úlitellm_versionÚmetric_nameÚappr,   s        r.   Ú_emit_supportability_metricz*NewRelicLogger._emit_supportability_metricÄ   s£   € ð	RØ%)×%>Ñ%>Ó%@ˆOØ#DÀ_ÐDUÐ!VˆKô )×4Ñ4Ó6ˆCô
 9=¿	¹	»ŒNÔ5á�s—{’{Ø×(Ñ(¨°aÔ8Ü×#Ñ#Ð$QÐS^Õ_ä×#Ñ#Ð$fÕgøäò 	RÜ×"Ñ"Ð#MÈq×QÑQûð	Rús   ‚A<B Á?B Â	B>ÂB9Â9B>c                 ó   — t        j                   «       }|t        j                  z
  }|dk\  rVt        j                  5  t        j                   «       }|t        j                  z
  }|dk\  r| j	                  «        ddd«       yy# 1 sw Y   yxY w)zæ
        Check if 27 hours have passed since last metric emission and re-emit if needed.

        Uses a mutex to ensure only one thread emits the metric even if multiple
        requests are being processed concurrently.
        i°{ N)rR   r   r   Ú_metric_lockrW   )r'   Úcurrent_timeÚtime_since_last_emissions      r.   Ú_check_and_emit_periodic_metricz.NewRelicLogger._check_and_emit_periodic_metricä   s€   € ô —y‘y“{ˆØ#/´.×2[Ñ2[Ñ#[Ð à# uÒ,ä×,Ñ,ñ 7ä#Ÿy™y›{�Ø+7¼.×:cÑ:cÑ+cÐ(à+¨uÒ4Ø×4Ñ4Ô6÷7ð 7ð -÷7ð 7ús   ½=BÂBNr(   Ústandard_logging_objectc                 ó  — d}	 |j                  d«      xs i }|j                  d«      xs i }|j                  d«      xs i }t        d„ |j                  «       D «       d«      }|r$|j                  d«      }t	        |«      dk(  r|d   }|s|r|j                  d	«      }	|	r|	}|s4t        j                  «       j                  }t        j                  d|«       |S # t
        $ r }
t        j                  d
|
«       Y d}
~
Œ\d}
~
ww xY w)a_  
        Get the New Relic trace ID for AI monitoring events.

        This integration runs in LiteLLM's async logging worker, outside the
        New Relic agent's current transaction. Because we can't call
        `newrelic.agent.current_trace_id()` to let the agent populate the
        trace_id on AIM custom events, we manually simulate what the agent
        would do. An AIM event without a trace_id is malformed per the NR
        schema, so this method always returns a valid string.

        Resolution order:
        1. W3C traceparent header (litellm_params.metadata.headers.traceparent) -
           what the agent would link to if we were in-transaction.
        2. StandardLoggingPayload.trace_id - LiteLLM's internal trace for
           retry/fallback grouping.
        3. Generated UUID - synthetic grouping key when upstream context is
           absent or parsing it fails.

        Span IDs are intentionally not emitted: any span ID recoverable from
        the inbound traceparent is the caller's parent span, not ours.

        Returns:
            trace_id: always a non-empty string.
        NÚlitellm_paramsÚmetadataÚheadersc              3   óN   K  — | ]  \  }}|j                  «       d k(  sŒ|–— Œ y­w)ÚtraceparentN)rG   )Ú.0r*   r+   s      r.   ú	<genexpr>z4NewRelicLogger._get_trace_context.<locals>.<genexpr>  s"   è ø€ Ò&c©T¨Q°ÈÏÉË	ÐUbÓHb¤qÑ&cùs   ‚%ž%ú-é   rP   Útrace_idzAUnable to parse New Relic trace context from upstream sources: %sz”New Relic trace_id not available from distributed tracing headers or StandardLoggingPayload. Generated trace_id=%s for AI monitoring event grouping.)ÚgetÚnextr   ÚsplitÚlenr&   r   r   ÚuuidÚuuid4ÚhexÚdebug)r'   r(   r]   rh   r_   r`   ra   rc   ÚpartsÚslo_trace_idr,   s              r.   Ú_get_trace_contextz!NewRelicLogger._get_trace_contextù   s  € ð:  $ˆð	kØ$*§J¡JÐ/?Ó$@Ò$FÀBˆNØ,×0Ñ0°Ó<ÒBÀˆHØ%Ÿ\™\¨)Ó4Ò:¸ˆGä!%Ñ&c°W·]±]³_Ô&cÐeiÓ!jˆKáð  +×0Ñ0°Ó5�Ü�u“: ’?Ø$ Q™x�HáÑ 7Ø&=×&AÑ&AÀ*Ó&M�ÙØ+�Hñ
 Ü—z‘z“|×'Ñ'ˆHÜ× Ñ ð gØôð
 ˆøô ò 	kÜ×"Ñ"Ð#fÐhi×jÑjûð	kús   „BC Ã	DÃ$C?Ã?DÚresponse_objc                 ó”   — d}|r|j                  d«      }|s|j                  d«      }|st        t        j                  «       «      }|S )zU
        Extract completion ID from kwargs or response_obj, or generate one.
        NÚidÚlitellm_call_id)ri   Ústrrm   rn   )r'   r(   rt   Úcompletion_ids       r.   Ú_extract_completion_idz%NewRelicLogger._extract_completion_id6  sI   € ð ˆáØ(×,Ñ,¨TÓ2ˆMáØ"ŸJ™JÐ'8Ó9ˆMñ Ü¤§
¡
£Ó-ˆMàÐr6   c                 ó†   — |r|j                  d«      }|r|S |j                  di «      xs i }|j                  d«      xs dS )z;Extract vendor/provider, preferring StandardLoggingPayload.Úcustom_llm_providerr_   r1   ©ri   )r'   r(   r]   Úvendorr_   s        r.   Ú_get_vendorzNewRelicLogger._get_vendorH  sM   € ñ #Ø3×7Ñ7Ð8MÓNˆFÙØ�Ø &§
¡
Ð+;¸RÓ @Ò FÀBˆØ×!Ñ!Ð"7Ó8ÒE¸IÐEr6   c                 óÊ   — d}|r|j                  d«      }|rt        |«      }|st        |j                  d«      xs d«      }t        |j                  d«      xs |«      }||fS )zÃ
        Extract request and response model names, preferring StandardLoggingPayload
        for the request model.

        Returns:
            Tuple of (request_model, response_model)
        NÚmodelrL   )ri   rx   )r'   r(   rt   r]   Úrequest_modelÚ	slo_modelÚresponse_models          r.   Ú_get_model_nameszNewRelicLogger._get_model_namesU  sh   € ð ˆÙ"Ø6×:Ñ:¸7ÓCˆIÙÜ # I£�ÙÜ §
¡
¨7Ó 3Ò @°yÓAˆMÜ%(¨×)9Ñ)9¸'Ó)BÒ)SÀmÓ%TˆØ˜nÐ,Ð,r6   c                 ól  — |rZ|j                  d«      }|j                  d«      }|j                  d«      }t        d„ |||fD «       «      r|xs d|xs d|xs ddœS |j                  dd«      }|sddddœS |j                  d«      xs d|j                  d«      xs d|j                  d«      xs ddœS )	z<Extract usage statistics, preferring StandardLoggingPayload.Úprompt_tokensÚcompletion_tokensÚtotal_tokensc              3   ó$   K  — | ]  }|d u–— Œ
 y ­w©Nr   )rd   Úxs     r.   re   z0NewRelicLogger._extract_usage.<locals>.<genexpr>v  s   è ø€ ÒF Q�1˜D”=ÑFùs   ‚r   )r‡   rˆ   r‰   ÚusageN)ri   Úany)r'   rt   r]   ÚpromptÚ
completionÚtotalr�   s          r.   Ú_extract_usagezNewRelicLogger._extract_usagel  sÑ   € ñ #Ø3×7Ñ7¸ÓHˆFØ 7× ;Ñ ;Ð<OÓ PˆJØ2×6Ñ6°~ÓFˆEÜÑF¨6°:¸uÐ*EÔFÔFà%+¢[¨qØ)3ª°qØ$)¢J¨Qñð ð $×'Ñ'¨°Ó6ˆÙØ%&¸QÐPQÑRÐRð #ŸY™Y Ó7Ò<¸1Ø!&§¡Ð+>Ó!?Ò!DÀ1Ø!ŸI™I nÓ5Ò:¸ñ
ð 	
r6   c                 ó~   — |j                  d«      xs g }|r&t        |«      dkD  r|d   j                  d«      xs dS y)zŸ
        Extract finish reason from first choice in the response.

        Returns "unknown" if choices are not present or finish_reason is not found.
        Úchoicesr   Úfinish_reasonrL   )ri   rl   )r'   rt   r”   s      r.   Ú_get_finish_reasonz!NewRelicLogger._get_finish_reason‡  sB   € ð &×)Ñ)¨)Ó4Ò:¸ˆÙ”s˜7“| aÒ'Ø˜1‘:—>‘> /Ó2Ò?°iÐ?Ør6   Útc                 ó\   — t        |d«      r|j                  «       dz  S t        |«      dz  S )z<Convert a datetime or float timestamp to epoch milliseconds.Ú	timestampç     @�@)Úhasattrr™   Úfloat)r'   r—   s     r.   Ú_to_epoch_mszNewRelicLogger._to_epoch_ms’  s,   € ä�1�kÔ"Ø—;‘;“= 6Ñ)Ð)Ü�Q‹x˜&Ñ Ð r6   Ú
start_timeÚend_timec                 óÔ   — |r!|j                  d«      }|�t        |«      dz  S |j                  d«      }|�t        |«      S |�%|�#| j                  |«      | j                  |«      z
  S y)zú
        Extract duration in milliseconds.

        Resolution order:
        1. StandardLoggingPayload.response_time (already computed by LiteLLM)
        2. llm_api_duration_ms from kwargs
        3. Calculated from start_time and end_time
        Úresponse_timeNrš   Úllm_api_duration_ms)ri   rœ   r�   )r'   r(   rž   rŸ   r]   r¡   Úduration_mss          r.   Ú_get_durationzNewRelicLogger._get_duration˜  s|   € ñ #Ø#:×#>Ñ#>¸Ó#OˆMØÐ(Ü˜]Ó+¨fÑ4Ð4à#ŸZ™ZÐ(=Ó>ˆØÐ"Ü˜Ó%Ð%àÐ! hÐ&:Ø×$Ñ$ XÓ.°×1BÑ1BÀ:Ó1NÑNÐNàr6   c                 óÄ   — |r|j                  d«      xs i }n|j                  d«      xs i }i }|j                  d«      }|�||d<   |j                  d«      }|�||d<   |S )zÖ
        Extract request parameters like temperature and max_tokens, preferring
        StandardLoggingPayload.model_parameters.

        Returns dict with available parameters, omitting those not present.
        Úmodel_parametersÚoptional_paramsÚtemperatureÚ
max_tokensr}   )r'   r(   r]   Úsource_paramsÚparamsr¨   r©   s          r.   Ú_get_request_paramsz"NewRelicLogger._get_request_paramsµ  s{   € ñ #Ø3×7Ñ7Ð8JÓKÒQÈr‰Mà"ŸJ™JÐ'8Ó9Ò?¸RˆMàˆà*×.Ñ.¨}Ó=ˆØÐ"Ø$/ˆF�=Ñ!à)×-Ñ-¨lÓ;ˆ
ØÐ!Ø#-ˆF�<Ñ àˆr6   Úmessagec                 ór  — |j                  d«      }|j                  d«      r	 t        j                  |d   «      S |€yt        |t        «      r	 t        j                  |«      S t        |t        «      st	        |«      S |S # t        $ r t	        |d   «      cY S w xY w# t        $ r t	        |«      cY S w xY w)zÝ
        Extract content from a message, handling various formats.

        Handles tool calls, multimodal content (as JSON), and standard text content.
        Returns empty string if content is None or missing.
        ÚcontentÚ
tool_callsÚ )ri   ÚjsonÚdumpsr&   rx   r3   Úlist)r'   r­   r¯   s      r.   Ú_extract_message_contentz'NewRelicLogger._extract_message_contentÑ  s¹   € ð !Ÿ™ YÓ/ˆð �;‰;�|Ô$ð2Ü—z‘z '¨,Ñ"7Ó8Ð8ð
 ˆ?Øô �gœtÔ$ð$Ü—z‘z 'Ó*Ð*ô
 ˜'¤3Ô'Ü�w“<Ðàˆøô% ò 2Ü˜7 <Ñ0Ó1Ò1ð2ûô ò $Ü˜7“|Ò#ð$ús#   ¤B ÁB ÂBÂBÂB6Â5B6r„   r~   c                 óª  — g }d}d}|r|j                  d«      }|s|j                  d«      }d}	|r|j                  d«      }	|	s|j                  d«      }	| j                  xr t        |«       }
|r|j                  d«      nd}t        |t        «      r|}n|j                  d«      xs g }|D ]g  }|j                  d«      xs d	|||d
œ}|�t        | j                  |«      «      |d<   |
r| j                  |«      |d<   |j                  |«       |dz  }Œi |j                  d«      xs g }|r¤t        |«      dkD  r–|D ]‘  }|j                  dd«      xs |j                  dd«      }|sŒ,|j                  d«      xs d|||ddœ}|	�t        | j                  |	«      «      |d<   |
r| j                  |«      |d<   |j                  |«       |dz  }Œ“ |S )aÈ  
        Extract all messages (request + response) with sequence numbers and timestamps.

        Processes request messages from StandardLoggingPayload.messages (preferred) or
        kwargs["messages"] (fallback), and response messages from response_obj["choices"].
        Assigns sequential numbers starting at 0.
        Adds timestamps from StandardLoggingPayload (preferred) or kwargs if available
        (converted to epoch milliseconds).
        r   NÚ	startTimerž   ÚendTimerŸ   ÚmessagesÚroleÚuser)rº   Úsequenceúresponse.modelr~   r™   r¯   rP   r”   r­   ÚdeltaÚ	assistantT)rº   r¼   r½   r~   Úis_response)
ri   r%   r   r3   r´   Úintr�   rµ   Úappendrl   )r'   r(   rt   r„   r~   r]   r¹   r¼   rž   rŸ   r%   Úslo_messagesÚrequest_messagesÚmsgÚmessage_datar”   Úchoicer­   s                     r.   Ú_extract_all_messagesz$NewRelicLogger._extract_all_messagesò  s  € ð" ˆØˆð ˆ
Ù"Ø0×4Ñ4°[ÓAˆJÙØŸ™ LÓ1ˆJàˆÙ"Ø.×2Ñ2°9Ó=ˆHÙØ—z‘z *Ó-ˆHð !%× 3Ñ 3Ò aÔ<YÐZ`Ó<aÐ8aˆñ JaÐ5×9Ñ9¸*ÔEÐfjˆÜ�l¤DÔ)Ø+Ñà%Ÿz™z¨*Ó5Ò;¸ÐØ#ò 	ˆCàŸ™ ›Ò1¨6Ø$Ø"0Ø ñ	ˆLð Ð%Ü,/°×0AÑ0AÀ*Ó0MÓ,N�˜[Ñ)áØ*.×*GÑ*GÈÓ*L�˜YÑ'à�O‰O˜LÔ)Ø˜‰M‰Hð!	ð& &×)Ñ)¨)Ó4Ò:¸ˆÙ”s˜7“| aÒ'Ø!ò "�à Ÿ*™* Y°Ó5ÒR¸¿¹ÀGÈTÓ9R�Úà '§¡¨FÓ 3Ò B°{Ø$,Ø*8Ø"(Ø'+ñ$�Lð  Ð+Ü47¸×8IÑ8IÈ(Ó8SÓ4T˜ [Ñ1á%Ø26×2OÑ2OÐPWÓ2X˜ YÑ/à—O‘O LÔ1Ø ‘M‘Hð)"ð, ˆr6   Ú
request_idrh   r‚   r•   Únum_messagesr�   ÚdurationÚrequest_paramsc                 ó’  — 	 |||||||d|d   |d   |d   dœ}|r||d<   |	�|	|d<   |
rd	|
v r|
d	   |d
<   d|
v r|
d   |d<   t        j                  «       }|r|j                  r|j                  d|«       yt	        j
                  d«       y# t        $ r1}t	        j
                  d|«       | j                  d«       Y d}~yd}~ww xY w)z3Record LlmChatCompletionSummary event to New Relic.r1   r‡   rˆ   r‰   )rv   rÉ   zrequest.modelr½   zresponse.choices.finish_reasonzresponse.number_of_messagesr~   Úingest_sourcezresponse.usage.prompt_tokensz response.usage.completion_tokenszresponse.usage.total_tokensrh   NrË   r¨   zrequest.temperaturer©   zrequest.max_tokensÚLlmChatCompletionSummaryzGNew Relic application is not enabled; skipping summary event recording.z,Failed to record New Relic summary event: %sÚnewrelic)r!   rQ   r    Úrecord_custom_eventr   r   r&   Úhandle_callback_failure)r'   rÉ   rh   r‚   r„   r~   r•   rÊ   r�   rË   rÌ   Ú
event_datarV   r,   s                 r.   Ú_record_summary_eventz$NewRelicLogger._record_summary_eventO  sý   € ð&	5à Ø(Ø!.Ø"0Ø2?Ø/;Ø Ø!*Ø05°oÑ0FØ49Ð:MÑ4NØ/4°^Ñ/Dñ!ˆJñ Ø)1�
˜:Ñ&àÐ#Ø)1�
˜:Ñ&ñ Ø  NÑ2Ø8FÀ}Ñ8U�JÐ4Ñ5Ø >Ñ1Ø7EÀlÑ7S�JÐ3Ñ4ä(×4Ñ4Ó6ˆCá�s—{’{Ø×'Ñ'Ð(BÀJÕOä×&Ñ&Ð'pÕqøäò 	5Ü×"Ñ"Ð#QÐSTÔUØ×(Ñ(¨×4Ñ4ûð	5ús   ‚A3B Á6B Â	CÂ'CÃCÚllm_response_idr¹   c                 óÊ  — 	 t        j                  «       }|r|j                  st        j                  d«       y|D ]h  }|d   }|› d|› �|||d   ||d   |d   dd	d
œ	}|r||d<   d|v r|d   |d<   |j                  d«      rd|d<   d|v r|d   |d<   |j                  d|«       Œj y# t        $ r1}	t        j                  d|	«       | j                  d«       Y d}	~	yd}	~	ww xY w)a{  Record LlmChatCompletionMessage events to New Relic.

        Args:
            request_id: Agent-generated UUID that links to Summary event's id
            llm_response_id: LLM's response ID (e.g., "chatcmpl-...") for message id format
            trace_id: Trace ID for distributed tracing (None if not available)
            messages: List of message dicts to record
        zGNew Relic application is not enabled; skipping message event recording.Nr¼   rf   rº   r½   r~   r1   r   )	rv   rÉ   ry   rº   r¼   r½   r~   rÎ   Útoken_countrh   r¯   rÀ   Tr™   ÚLlmChatCompletionMessagez-Failed to record New Relic message events: %srÐ   )	r!   rQ   r    r   r   ri   rÑ   r&   rÒ   )
r'   rÉ   rÕ   rh   r¹   rV   r­   r¼   rÓ   r,   s
             r.   Ú_record_message_eventsz%NewRelicLogger._record_message_events…  s)  € ð,	5Ü(×4Ñ4Ó6ˆCá˜CŸKšKÜ×&Ñ&Ð'pÔqØà#ò !P�Ø" :Ñ.�à,Ð-¨Q¨x¨jÐ9Ø",Ø%/Ø# F™OØ (Ø&-Ð.>Ñ&?Ø% hÑ/Ø%.Ø#$ñ
�
ñ Ø-5�J˜zÑ*ð  Ñ'Ø,3°IÑ,>�J˜yÑ)ð —;‘;˜}Ô-Ø04�J˜}Ñ-ð  'Ñ)Ø.5°kÑ.B�J˜{Ñ+à×'Ñ'Ð(BÀJÕOñC!PøôF ò 	5Ü×"Ñ"Ð#RÐTUÔVØ×(Ñ(¨×4Ñ4ûð	5ús   ‚7B( ºA-B( Â(	C"Â1'CÃC"c                 ó&  — 	 | j                   sy| j                  «        t        j                  «       }|r |j                   r|j	                  dd«       yyy# t
        $ r1}t        j                  d|«       | j                  d«       Y d}~yd}~ww xY w)z!Record error metric to New Relic.NzLLM/LiteLLM/ErrorrP   z+Failed to record New Relic error metric: %srÐ   )	r    r\   r!   rQ   rS   r&   r   r   rÒ   ©r'   rV   r,   s      r.   Ú_record_error_metricz#NewRelicLogger._record_error_metricÂ  s   € ð	5Ø—<’<Øà×0Ñ0Ô2ä(×4Ñ4Ó6ˆCÙ�s—{’{Ø×(Ñ(Ð)<¸aÕ@ð #ˆsøäò 	5Ü×"Ñ"Ð#PÐRSÔTØ×(Ñ(¨×4Ñ4ûð	5ús   ‚A �AA Á	BÁ'BÂBc                 ó`  — | j                   sy| j                  «        |j                  d«      }| j                  ||«      }t	        t        j                  «       «      }| j                  ||«      }| j                  ||«      }	| j                  |||«      \  }
}| j                  ||«      }| j                  |«      }| j                  ||||«      }| j                  ||«      }| j                  ||||	|«      }| j                  |||
||	|t!        |«      |||¬«
       | j#                  ||||¬«       y)z}
        Core logic for processing successful LLM calls.
        Used by both sync and async success event handlers.
        Nr]   )
rÉ   rh   r‚   r„   r~   r•   rÊ   r�   rË   rÌ   )rÉ   rÕ   rh   r¹   )r    r\   ri   rs   rx   rm   rn   rz   r   r…   r’   r–   r¤   r¬   rÈ   rÔ   rl   rÙ   )r'   r(   rt   rž   rŸ   r]   rh   rÉ   rÕ   r~   r‚   r„   r�   r•   rË   rÌ   r¹   s                    r.   Ú_process_successzNewRelicLogger._process_successÑ  s[  € ð �|Š|Øð 	×,Ñ,Ô.ð IOÏ
É
ÐSlÓHmÐð ×1Ñ1°&Ð:QÓRˆô  ¤§
¡
£Ó-ˆ
ð "&×!<Ñ!<¸VÀ\Ó!RˆØ×(Ñ(¨Ð1HÓIˆØ(,×(=Ñ(=¸fÀlÐTkÓ(lÑ%ˆ�~Ø×*Ñ*¨<Ð9PÓQˆØ#×6Ñ6°|ÓDˆð ×,Ñ,¨V°ZÀÐKbÓcˆØ $× 8Ñ 8¸ÐAXÓ Yˆð ×-Ñ-¨f°lÀNÐTZÐ\sÓtˆð 	×"Ñ"Ø!ØØ'Ø)ØØ'Ü˜X›ØØØ)ð 	#ô 	
ð 	×#Ñ#Ø!Ø+ØØð	 	$õ 	
r6   c              ƒ   ót  K  — | j                   st        dd¬«      S 	 t        j                  «       }|r|j                   st        dd¬«      S |j	                  dd| j
                  dt        j                  «       dœ«       t        d	d
¬«      S # t        $ r }t        dt        |«      ¬«      cY d
}~S d
}~ww xY w­w)aQ  
        Check if the New Relic integration is healthy.

        Verifies that the integration is enabled and the New Relic agent
        has an active, connected application, then records a small
        `LiteLLMConnectionTest` custom event so the user can confirm the
        end-to-end pipeline in the New Relic UI via NRQL:
        `SELECT * FROM LiteLLMConnectionTest SINCE 1 hour ago`.

        The `LiteLLMConnectionTest` event type is intentionally outside the
        `Llm*` family that AI Monitoring queries, so test events do not
        appear in AI Monitoring dashboards.
        Ú	unhealthyz‰New Relic integration is disabled. Check that NEW_RELIC_LICENSE_KEY and NEW_RELIC_APP_NAME are set and the newrelic package is installed.)ÚstatusÚerror_messager   ÚLiteLLMConnectionTestTzlitellm-proxy)Úis_test_eventr   Úsourcer™   ÚhealthyN)	r    r   r!   rQ   rÑ   r   rR   r&   rx   rÛ   s      r.   Úasync_health_checkz!NewRelicLogger.async_health_check  sº   è ø€ ð �|Š|Ü/Ø"ð1ôð ð	Ü(×4Ñ4Ó6ˆCÙ˜CŸKšKÜ3Ø&ð gôð ð ×#Ñ#Ø'à%)Ø $§¡Ø-Ü!%§¡£ñ	ôô 0°yÐPTÔUÐUøÜò 	Ü/Ø"Ü! !›fôõ ûð	üs@   ‚B8�.B ÁB8Á?B ÂB8Â	B5ÂB0Â*B5Â+B8Â0B5Â5B8c                  ó   — y©zUnused per spec.Nr   )r'   r�   r¹   r(   s       r.   Úlog_pre_api_callzNewRelicLogger.log_pre_api_callB  ó   � r6   c                  ó   — yré   r   )r'   r(   rt   rž   rŸ   s        r.   Úlog_post_api_callz NewRelicLogger.log_post_api_callE  rë   r6   c                 ó¨   — 	 | j                  ||||«       y# t        $ r1}t        j                  d|«       | j	                  d«       Y d}~yd}~ww xY w)zÕ
        Main success path for non-streaming requests.

        Note: New Relic's record_custom_event is synchronous but non-blocking
        (in-memory operation), so it's safe to call from sync context.
        z(Error in New Relic log_success_event: %srÐ   N©rÞ   r&   r   r   rÒ   ©r'   r(   rt   rž   rŸ   r,   s         r.   Úlog_success_eventz NewRelicLogger.log_success_eventH  sM   € ð	5Ø×!Ñ! &¨,¸
ÀHÕMøÜò 	5Ü×"Ñ"Ð#MÈqÔQØ×(Ñ(¨×4Ñ4ûð	5ús   ‚ —	A 'AÁAc              ƒ   ó°   K  — 	 | j                  ||||«       y# t        $ r1}t        j                  d|«       | j	                  d«       Y d}~yd}~ww xY w­w)zÐ
        Main success path for async/streaming requests.

        Note: New Relic's SDK is thread-safe and record_custom_event is fast,
        so we can call it directly without asyncio.to_thread().
        z.Error in New Relic async_log_success_event: %srÐ   Nrï   rð   s         r.   Úasync_log_success_eventz&NewRelicLogger.async_log_success_eventU  sQ   è ø€ ð	5Ø×!Ñ! &¨,¸
ÀHÕMøÜò 	5Ü×"Ñ"Ð#SÐUVÔWØ×(Ñ(¨×4Ñ4ûð	5üs*   ‚A„ ˜A™	A¢'AÁ	AÁAÁAc                 ó    — 	 | j                  «        y# t        $ r1}t        j                  d|«       | j	                  d«       Y d}~yd}~ww xY w)zŽ
        Log error metric for failed LLM calls (sync).

        Per spec: Do not send AI events on failure, only record error metric.
        z(Error in New Relic log_failure_event: %srÐ   N©rÜ   r&   r   r   rÒ   rð   s         r.   Úlog_failure_eventz NewRelicLogger.log_failure_eventb  sE   € ð	5Ø×%Ñ%Õ'øäò 	5Ü×"Ñ"Ð#MÈqÔQØ×(Ñ(¨×4Ñ4ûð	5ús   ‚ “	Aœ'AÁAc              ƒ   ó¨   K  — 	 | j                  «        y# t        $ r1}t        j                  d|«       | j	                  d«       Y d}~yd}~ww xY w­w)z�
        Log error metric for failed LLM calls (async).

        Per spec: Do not send AI events on failure, only record error metric.
        z.Error in New Relic async_log_failure_event: %srÐ   Nrõ   rð   s         r.   Úasync_log_failure_eventz&NewRelicLogger.async_log_failure_evento  sI   è ø€ ð	5Ø×%Ñ%Õ'øäò 	5Ü×"Ñ"Ð#SÐUVÔWØ×(Ñ(¨×4Ñ4ûð	5üs*   ‚A„ ”A•	Až'A
ÁAÁ
AÁA)Fr‹   )NN)4Ú__name__Ú
__module__Ú__qualname__Ú__doc__r   rœ   Ú__annotations__Ú	threadingÚLockrY   r   r5   r   ÚpropertyÚboolr%   rx   r:   rN   rW   r\   r   rs   r   rz   r   Útupler…   rÁ   r’   r–   r   r�   r¤   r¬   r
   rµ   r´   rÈ   rÔ   rÙ   rÜ   rÞ   r   rç   rê   rí   rñ   ró   rö   rø   Ú__classcell__)r-   s   @r.   r   r   B   sÀ  ø… ñð ),Ð Ó+Ø!�9—>‘>Ó#€Lô-%ð^$ dó $ð ð

 ò 

ó ð

ñ¨ð °dð Àtó ð4 có òRò@7ð0 BFñ;àð;ð "8¸$Ñ!>ð;ð 
ó	;ðz¨Tð Àð ÐSVó ð* BFñFàðFð "8¸$Ñ!>ðFð 
ó	Fð" BFñ	-àð-ð $ð-ð "8¸$Ñ!>ð	-ð
 
ˆs�Cˆx‰ó-ð4 BFñ
à#ð
ð "8¸$Ñ!>ð
ð 
ˆc�3ˆh‰ó	
ð6	¨}ð 	Àó 	ð!˜cð ! eó !ð BFñàðð ðð ð	ð
 "8¸$Ñ!>ðð 
�‰óð@ BFñàðð "8¸$Ñ!>ðð 
ˆc�3ˆh‰ó	ð8°¸$±ð À3ó ðN BFñ[àð[ð $ð[ð ð	[ð
 ð[ð "8¸$Ñ!>ð[ð 
ˆd�3˜�8‰nÑ	ó[ðN "&Ø04ñ45àð45ð ˜‘*ð45ð ð	45ð
 ð45ð ð45ð ð45ð ð45ð �C˜�H‰~ð45ð ˜$‘,ð45ð ˜S #˜X™¨Ñ-ó45ðl;5àð;5ð ð;5ð ˜‘*ð	;5ð
 �t˜C ˜H‘~Ñ&ó;5òz5ð& $(Ø!%ñ=
àð=
ð $ð=
ð ˜D‘Lð	=
ð
 ˜$‘,ó=
ð~.Ð*Fó .òdòò5ò5ò5ö5r6   r   )rü   r²   r   rþ   rR   rm   Útypingr   r   r1   Úlitellm._loggingr   Ú"litellm.integrations.custom_loggerr   Ú*litellm.litellm_core_utils.redact_messagesr   Ú,litellm.types.integrations.base_health_checkr   Ú#litellm.types.integrations.newrelicr	   Úlitellm.types.utilsr
   r   r   Únewrelic.agentÚagentr!   ÚImportErrorr   r   r6   r.   ú<module>r     sc   ðñ*óX Û 	Û Û Û ß ã Ý +Ý ;Ý TÝ UÝ Bß NÑ NðÝ,ô
x5�\õ x5øð	 ò Ø‚Oðús   ÁA ÁA(Á'A(