
    #jA                     B   U d Z ddlZddl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 ddlmZ ddlmZ dd	lmZmZ  ej        e          Zd
edefdZ G d d          Z G d d          Z e            aej        ez  ed<   daeed<    G d d          Z ddddddddddd
Z!	 	 d3ded ed!ee         d"eddf
d#Z"dej        ez  fd$Z#d4d%ed&edz  fd'Z$defd(Z%d)Z&d*edefd+Z' G d, d-          Z( G d. d/          Z)da*ee(         ed0<   de(e)z  fd1Z+de(fd2Z,dS )5a  
OpenTelemetry distributed tracing instrumentation for Hindsight API.

This module provides tracing for:
- LLM API calls with full prompts/completions following GenAI semantic conventions
- Token usage and model information
- Error tracking and finish reasons

Tracing is conditional and disabled by default. When enabled, traces are exported
to Langfuse (or any OTLP-compatible backend) via OTLP HTTP protocol.
    N)AnyOptional)trace)OTLPSpanExporter)Resource)TracerProvider)BatchSpanProcessor)Status
StatusCodeobjreturnc                    t          | t                    r| S t          | d          r|                                 S t          | d          r|                                 S t          | d          r&t	          j        |                                           S t          | d          r&t	          j        |                                           S t	          j        |           S )zASerialize an object for span recording, handling Pydantic models.model_dump_jsonjson
model_dumpdict)
isinstancestrhasattrr   r   dumpsr   r   )r   s    ]/home/rurouni/.hermes/hermes-agent/venv/lib/python3.11/site-packages/hindsight_api/tracing.py_serialize_for_spanr      s    #s 
s%&& %""$$$sF xxzzsL!! ,z#..**+++sF &z#((**%%%:c??    c                   *    e Zd ZdZdefdZdefdZdS )
NoOpTracerzWNo-op tracer that provides the same interface as OpenTelemetry Tracer but does nothing.namec                 8    ddl m} |d             } |            S )z6Return a no-op context manager that yields a NoOpSpan.r   )contextmanagerc               3   *   K   t                      V  d S NNoOpSpan r   r   noop_span_contextz;NoOpTracer.start_as_current_span.<locals>.noop_span_context8   s      **r   )
contextlibr   )selfr   kwargsr   r$   s        r   start_as_current_spanz NoOpTracer.start_as_current_span4   s>    ------		 	 
	 ! """r   c                     t                      S )zReturn a no-op span.r!   )r&   r   r'   s      r   
start_spanzNoOpTracer.start_span>   s    zzr   N)__name__
__module____qualname____doc__r   r(   r*   r#   r   r   r   r   1   sP        aa## # # # #s      r   r   c                   z    e Zd ZdZdededdfdZdeddfdZd	eddfd
Z	ddede
dz  ddfdZddedz  ddfdZdS )r"   zSNo-op span that provides the same interface as OpenTelemetry Span but does nothing.keyvaluer   Nc                     dS zNo-op.Nr#   )r&   r0   r1   s      r   set_attributezNoOpSpan.set_attributeF       r   statusc                     dS r3   r#   )r&   r6   s     r   
set_statuszNoOpSpan.set_statusJ   r5   r   	exceptionc                     dS r3   r#   )r&   r9   s     r   record_exceptionzNoOpSpan.record_exceptionN   r5   r   r   
attributesc                     dS r3   r#   )r&   r   r<   s      r   	add_eventzNoOpSpan.add_eventR   r5   r   end_timec                     dS r3   r#   )r&   r?   s     r   endzNoOpSpan.endV   r5   r   r    )r+   r,   r-   r.   r   r   r4   r8   	Exceptionr;   r   r>   intrA   r#   r   r   r"   r"   C   s        ]] S T         )      c td{ d     C$J $      r   r"   _tracerF_tracing_enabledc                   >    e Zd ZdZdZdZdZdZdZdZ	dZ
d	Zd
ZdZdZdS )GenAIAttributesz*GenAI semantic convention attribute names.zgen_ai.operation.namezgen_ai.provider.namezgen_ai.request.modelzgen_ai.response.modelzgen_ai.usage.input_tokenszgen_ai.usage.output_tokenszgen_ai.system_instructionszgen_ai.input.messageszgen_ai.output.messageszgen_ai.response.finish_reasonsz
error.typeN)r+   r,   r-   r.   OPERATION_NAMEPROVIDER_NAMEREQUEST_MODELRESPONSE_MODELUSAGE_INPUT_TOKENSUSAGE_OUTPUT_TOKENSSYSTEM_INSTRUCTIONSINPUT_MESSAGESOUTPUT_MESSAGESFINISH_REASONS
ERROR_TYPEr#   r   r   rG   rG   a   sZ        44 -N*M +M,N 56 7,N.O 6N JJJr   rG   openai	anthropicgooglegroqollamalmstudiomock)
rS   rT   geminivertexairV   rW   rX   zopenai-codexzclaude-coderY   developmentservice_nameendpointheadersdeployment_environmentc                 V   t          j        | d|d          }i }|r^|                    d          D ]H}d|v rB|                    dd          \  }}|                                ||                                <   I|                    d          r|n| d}	t          |	|          }
t          |          }|                    t          |
                     t          j
        |           t          j        t                    ad	at                              d
|	 d|             dS )a  
    Initialize OpenTelemetry tracing with OTLP exporter.

    Args:
        service_name: Name of the service for resource attributes
        endpoint: OTLP endpoint URL (e.g., https://cloud.langfuse.com/api/public/otel)
        headers: Optional headers in format "key1=value1,key2=value2"
        deployment_environment: Deployment environment (e.g., development, staging, production)
    z0.4.8)zservice.namezservice.versionzdeployment.environment.name,=   z
/v1/traces)r^   r_   )resourceTzTracing initialized: endpoint=z
, service=N)r   createsplitstripendswithr   r   add_span_processorr	   r   set_tracer_provider
get_tracerr+   rD   rE   loggerinfo)r]   r^   r_   r`   re   headers_dictpairr0   r1   otlp_endpointotlp_exporterproviders               r   initialize_tracingrt      sQ   $ (&+A	
 	
 H L :MM#&& 	: 	:Dd{{!ZZQ//
U,1KKMMSYY[[) !) 1 1, ? ?\HHE\E\E\M$  M x000H 2= A ABBB 
h''' x((G
KKXXX,XXYYYYYr   c                      t           S )z
    Get the global tracer instance.

    Returns a no-op tracer if tracing is disabled, so callers don't need to check for None.
    This improves code readability by allowing direct use without null checks.
    )rD   r#   r   r   rl   rl      s	     Nr   	operationbank_idc                     t           rt          ddlm}  |            S d|  }t                              |          }|r>t          |d          r.|                    d|            |r|                    d|           |S )a  
    Create a parent span for a Hindsight operation (retain, reflect, consolidation, etc.).

    This creates the span hierarchy:
    - hindsight.{operation} (parent)
      - chat {model} (child LLM calls)

    Args:
        operation: Operation name (retain, reflect, consolidation, mental_model_refresh)
        bank_id: Optional bank ID for context

    Returns:
        Span context manager
    Nr   )nullcontext
hindsight.r4   zhindsight.operationzhindsight.bank_id)rE   rD   r%   ry   r(   r   r4   )rv   rw   ry   	span_namespans        r   create_operation_spanr}      s      w******{}}(Y((I((33D  =o.. =0)<<< 	=2G<<<Kr   c                      t           S )zCheck if tracing is enabled.)rE   r#   r   r   is_tracing_enabledr      s    r   i contentc                     t          |           t          k    r+| dt                   dt          |           t          z
   dz   S | S )z'Truncate content if too large for span.Nz

[TRUNCATED: z chars omitted])lenMAX_CONTENT_LENGTH)r   s    r   _truncate_contentr      sH    
7||(((***+.sWPbAb.s.s.sssNr   c                   `   e Zd ZdZdej        fdZ	 	 	 ddedededee	eef                  d	e
e         d
ededede
e         de
e         de
ee	eef                           ddfdZdee	eef                  defdZde
e         de
e         defdZdee	eef                  de
e         fdZdS )LLMSpanRecorderzY
    Records OpenTelemetry spans for LLM calls following GenAI semantic conventions.
    tracerc                     || _         d S r    r   )r&   r   s     r   __init__zLLMSpanRecorder.__init__  s    r   Nrs   modelscopemessagesresponse_contentinput_tokensoutput_tokensdurationfinish_reasonerror
tool_callsr   c           
      T   	 t                               |                                |                                          }d}|rd| }n| d| }t          j                    }|t          |dz            z
  }| j                            ||d          5 }|                    t          j
        |           |                    t          j        |           |                    t          j        |           |                    t          j        |           |                    t          j        |           |                    t          j        |           |                    d|           |                    d|           |rX|                    d	t!          |                     d
 |D             }|                    dd                    |                     |                     |          }|                     ||	          }|                     |          }i }|r||t          j        <   |r||t          j        <   |r||t          j        <   |	r"t1          j        |	g          |t          j        <   |                    d|           |rt9          |          D ]s\  }}|                    dd          |                    dd          t1          j        |                    di                     d}|                    d| |           t|
r|                    t=          t>          j         tC          |
                               |                    t          j"        tG          |
          j$                   |%                    |
           n,|                    t=          t>          j&                             |'                    |           ddd           dS # 1 swxY w Y   dS # tP          $ r*}tR          *                    d| d           Y d}~dS d}~ww xY w)a  
        Record a completed LLM call as a span with GenAI semantic conventions.

        This creates a span AFTER the call completes, using timestamps to
        set the correct start/end times. This approach works better with
        the existing sync metrics recording pattern.

        Args:
            provider: Hindsight provider name
            model: Model name
            scope: Scope identifier (memory, reflect, consolidation, etc.)
            messages: Input messages (chat history)
            response_content: Response text from LLM
            input_tokens: Input token count
            output_tokens: Output token count
            duration: Call duration in seconds
            finish_reason: Reason the model stopped (stop, length, tool_calls, etc.)
            error: Exception if call failed
            tool_calls: List of tool calls made (for function calling)
        chatrz    i ʚ;F)
start_timeend_on_exitzhindsight.scopezhindsight.provider.internalzgen_ai.tool_calls.countc                 :    g | ]}|                     d d          S )r    )get).0tcs     r   
<listcomp>z3LLMSpanRecorder.record_llm_call.<locals>.<listcomp>Q  s&    !J!J!J"&&"4"4!J!J!Jr   zgen_ai.tool_calls.namesrb   z)gen_ai.client.inference.operation.details)r<   r   r   id	arguments)z	tool.nameztool.idztool.argumentszgen_ai.tool_call.)r?   NzFailed to record LLM span: T)exc_info)+PROVIDER_NAME_MAPPINGr   lowertimetime_nsrC   r   r(   r4   rG   rH   rI   rJ   rK   rL   rM   r   join_format_messages_format_output_extract_system_instructionsrO   rP   rN   r   r   rQ   r>   	enumerater8   r
   r   ERRORr   rR   typer+   r;   OKrA   rB   rm   r   )r&   rs   r   r   r   r   r   r   r   r   r   r   genai_provideroperation_namer{   end_time_nsstart_time_nsr|   
tool_namesinput_messages_jsonoutput_messages_jsonsystem_instructionsevent_attrsir   tool_event_attrses                              r   record_llm_callzLLMSpanRecorder.record_llm_call  s   DZ	K266x~~7G7GIYIYZZN $N  8000		  .7777	 ,..K'#h.F*G*GGM 22(! 3   B/ ""?#A>RRR""?#@.QQQ""?#@%HHH""?#A5III""?#E|TTT""?#FVVV ""#4e<<<""#@(KKK  X&&'@#j//RRR!J!Jz!J!J!JJ&&'@#((:BVBVWWW '+&;&;H&E&E#'+':':;K]'['[$ '+&G&G&Q&Q# !& VBUK >?' XCWK ?@& [GZK CD  ^BF*m_B]B]K >??*      ]!*:!6!6 ] ]2)+););')vvdB'7'7.2jR9P9P.Q.Q, ,(
 '>1'>'>K[\\\\  ;OOF:+;SZZ$H$HIII&&'A4;;CWXXX))%0000OOF:=$9$9::: +...EB/ B/ B/ B/ B/ B/ B/ B/ B/ B/ B/ B/ B/ B/ B/ B/ B/ B/H  	K 	K 	KLL:q::TLJJJJJJJJJ	Ks=   BO3 MO&O3 &O**O3 -O*.O3 3
P'=P""P'c                 p   	 g }|D ]h}|                     dd          }t          |t                    rt          |          }|                    |                     dd          |d           it          j        |          S # t          $ r(}t          	                    d|            Y d}~dS d}~ww xY w)	z
        Format messages into GenAI semantic convention format (JSON array).

        Returns JSON string representation of message array.
        r   r   roleuserr   r   z!Failed to format input messages: N[])
r   r   r   r   appendr   r   rB   rm   warning)r&   r   	formattedmsgr   r   s         r   r   z LLMSpanRecorder._format_messages  s    	I  '')R00gs++ 9/88G   # 7 7#*     :i((( 	 	 	NNBqBBCCC44444	s   B B 
B5B00B5r   c                     	 |dS t          |t                    rt          |          }t          j        d|dg          S # t
          $ r(}t                              d|            Y d}~dS d}~ww xY w)z<Format output message into GenAI semantic convention format.Nr   	assistantr   z!Failed to format output message: )r   r   r   r   r   rB   rm   r   )r&   r   r   r   s       r   r   zLLMSpanRecorder._format_output  s    	t '3'' 5+G44: !,#*     	 	 	NNBqBBCCC44444	s   A ;A 
A4A//A4c                 H   	 |D ]h}|                     d          dk    rM|                     dd          }t          |t                    rt          |          c S t          |          c S in4# t          $ r'}t
                              d|            Y d}~nd}~ww xY wdS )z5Extract system instructions from messages if present.r   systemr   r   z'Failed to extract system instructions: N)r   r   r   r   rB   rm   r   )r&   r   r   r   r   s        r   r   z,LLMSpanRecorder._extract_system_instructions  s    	J ( (776??h..!ggi44G!'3// :099999w<<'''	 /(  	J 	J 	JNNHQHHIIIIIIII	Jts$   AA. A. ,A. .
B8BB)NNN)r+   r,   r-   r.   r   Tracerr   r   listr   r   rC   floatrB   r   r   r   r   r   r#   r   r   r   r      s        u|     (,%)59|K |K|K |K 	|K
 tCH~&|K #3-|K |K |K |K  }|K 	"|K T$sCx.12|K 
|K |K |K |K|d38n)= #    4#  } 
	   4T$sCx.5I hWZm      r   r   c                       e Zd ZdZddZdS )NoOpLLMSpanRecorderz1No-op span recorder for when tracing is disabled.r   Nc                     dS r3   r#   )r&   r'   s     r   r   z#NoOpLLMSpanRecorder.record_llm_call  r5   r   )r   N)r+   r,   r-   r.   r   r#   r   r   r   r     s.        ;;     r   r   _span_recorderc                  :    t           t                      S t           S )z8Get the global span recorder (NoOp if tracing disabled).)r   r   r#   r   r   get_span_recorderr     s    "$$$r   c                  l    t                      } | t          d          t          |           at          S )z(Create and set the global span recorder.Nz9Tracing not initialized. Call initialize_tracing() first.)rl   RuntimeErrorr   r   r   s    r   create_span_recorderr     s4     \\F~VWWW$V,,Nr   )Nr\   r    )-r.   r   loggingr   typingr   r   opentelemetryr   5opentelemetry.exporter.otlp.proto.http.trace_exporterr   opentelemetry.sdk.resourcesr   opentelemetry.sdk.tracer   opentelemetry.sdk.trace.exportr	   opentelemetry.tracer
   r   	getLoggerr+   rm   r   r   r   r"   rD   r   __annotations__rE   boolrG   r   rt   rl   r}   r   r   r   r   r   r   r   r   r#   r   r   <module>r      sk  
 
 
                          R R R R R R 0 0 0 0 0 0 2 2 2 2 2 2 = = = = = = 2 2 2 2 2 2 2 2		8	$	$S S    *       $       2 &0Z\\
	" 1 1 1 $          :   " ""/	5Z 5Z5Z5Z c]5Z  	5Z
 
5Z 5Z 5Z 5ZpEL:-     S 3:    BD      s s    E E E E E E E EP        -1) 0 0 0?-@@    o      r   