o
    ii                    @   s	  d dl Z d dlZd dlmZ d dlmZ d dlZd dlZd dlZd dlZd dl	Z	d dl
Z
d dlmZ 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 d dl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 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( d dl&m)Z) d dl*m+Z+ d dl,m-Z- d dl.m/Z/ d dl.m0Z0 d dl1m2Z2 d dl3m4Z4 d dl3m5Z5 d dl6m7Z8 d d l6m9Z9 d d!l:m;Z; d d"l<m=Z= d d#l>m?Z? d d$l>m@Z@ d d%l>mAZA d d&lBmCZD d d'lBmEZF d d(lGmHZH d d)lGmIZI d d*lGmJZJ d d+lGmKZK d d,lGmLZL d d-lGmMZM d d.lGmNZN d d/lGmOZO d d0lGmPZP d d1lGmQZQ d d2lGmRZR d d3lGmSZS d d4lGmTZT d d5lGmUZU d d6lGmVZV d d7lGmWZW d d8lGmXZX d d9lGmYZY d d:lGmZZZ d d;lGm[Z[ d d<lGm\Z\ d d=lGm]Z] d d>lGm^Z^ d d?lGm_Z_ d d@lGm`Z` d dAlGmaZa d dBlGmbZb d dClGmcZc d dDlGmdZd d dElGmeZe d dFlGmfZf d dGlGmgZg d dHlGmhZh d dIlGmiZi d dJlGmjZj d dKlGmkZk d dLlGmlZl d dMlGmmZm d dNlGmnZn d dOlGmoZo d dPlGmpZp d dQlGmqZq d dRlGmrZr d dSlGmsZs d dTlGmtZt d dUlGmuZu d dVlGmvZv d dWlGmwZw d dXlGmxZx d dYlGmyZy d dZlGmzZz d d[lGm{Z{ d d\lGm|Z| d d]l}m~Z~ d d^lmZ d d_lmZ d d`lmZ d dalmZ d dblmZ d dclmZ d ddlmZ d delmZ d dflmZ d dglmZ d dhlmZ d dilmZ d djlmZ d dklmZ d dllmZ d dmlmZ d dnlmZ d dolmZ d dplmZ d dqlmZ d drlmZ d dslmZ d dtlmZ d dulmZ d dvlmZ d dwlmZ d dxlmZ d dylmZ d dzlmZ d d{lmZ d d|lmZ d d}lmZ d d~lmZ d dlmZ d dlmZ d dlmZ d dlmZ d dlmZ d dlmZ d dlmZ d dlmZ d dlmZ d dlmZ d dlmZ d dlmZ d dlmZ d dlmZ d dlmZ d dlmZ d dlmZ d dlmZ d dlmZ d dlmZ d dlmZ d dlmZ d dlmZ d dlmZ d dlmZ d dlmZ d dlmZ d dlmZ d dlmZ d dlmZ d dlmZ d dlmZ d dlmZ d dlmZ d dlmZ d dlmZ d dlmZ e-eуZi ddddddddddddddddddddddddddddddddddddddddddZddhZdZdZdededdfddƄZdededdfddɄZdededdfdd˄ZG dd̈́ deۃZG ddτ deۃZG ddф deۃZG ddӄ deۃZG ddՄ deۃZeG ddׄ d׃Zdedededeeed ed f fdd݄Zdededededed ded defddZG dd de4Ze e_dS )    N)	dataclass)field)Any)Callable)Literal)Optional)Sequence)Union)cast)config)patch)APMTracingEnabledFilter)Context)Span)Tracer)	ERROR_MSG)ERROR_STACK)
ERROR_TYPE)	SpanTypes)atexit)core)forksafe)ensure_text)
get_logger)generate_128bit_trace_id)
rand64bits)remoteconfig_poller)Service)ServiceStatusError)
get_config)telemetry_writer)TELEMETRY_APM_PRODUCT)RLock)asbool)format_trace_id)parse_tags_str)
_constants)
_telemetry)AGENT_MANIFEST)ANNOTATIONS_CONTEXT_ID)	DECORATOR)DEFAULT_PROJECT_NAME)DEFAULT_PROMPTS_CACHE_TTL)DEFAULT_PROMPTS_TIMEOUT) DISPATCH_ON_GUARDRAIL_SPAN_START)DISPATCH_ON_LLM_SPAN_FINISH)DISPATCH_ON_LLM_TOOL_CHOICE)$DISPATCH_ON_OPENAI_AGENT_SPAN_FINISH)DISPATCH_ON_TOOL_CALL)!DISPATCH_ON_TOOL_CALL_OUTPUT_USED)EXPERIMENT_CONFIG)EXPERIMENT_CSV_FIELD_MAX_SIZE)EXPERIMENT_DATASET_NAME_KEY)EXPERIMENT_EXPECTED_OUTPUT)EXPERIMENT_ID_KEY)EXPERIMENT_NAME_KEY)EXPERIMENT_PROJECT_ID_KEY)EXPERIMENT_PROJECT_NAME_KEY)EXPERIMENT_RUN_ID_KEY)EXPERIMENT_RUN_ITERATION_KEY)EXPERIMENTS_INPUT)EXPERIMENTS_OUTPUT)INPUT_DOCUMENTS)INPUT_MESSAGES)INPUT_PROMPT)INPUT_VALUE) INSTRUMENTATION_METHOD_ANNOTATED)INTEGRATION)LLMOBS_STRUCT)LLMOBS_TRACE_ID)MCP_TOOL_CALL_INTENT)METADATA)METRICS)ML_APP)
MODEL_NAME)MODEL_PROVIDER)OUTPUT_DOCUMENTS)OUTPUT_MESSAGES)OUTPUT_VALUE)PARENT_ID_KEY)&PROMPT_TRACKING_INSTRUMENTATION_METHOD)PROPAGATED_LLMOBS_TRACE_ID_KEY)PROPAGATED_ML_APP_KEY)PROPAGATED_PARENT_ID_KEY)ROOT_PARENT_ID)
SESSION_ID)	SPAN_KIND)
SPAN_LINKS)!SPAN_START_WHILE_DISABLED_WARNING)TAGS)TOOL_DEFINITIONS)LLMObsContextProvider)EvaluatorRunner)AsyncEvaluatorType)AsyncSummaryEvaluatorType)AsyncTaskType)BaseAsyncEvaluator)BaseAsyncSummaryEvaluator)BaseEvaluator)BaseSummaryEvaluator)
ConfigType)Dataset)DatasetRecord)DatasetRecordInputType)EvaluatorType)
Experiment)ExperimentResult)JSONType)Project)SummaryEvaluatorType)SyncExperiment)TaskType)"_deep_eval_async_evaluator_wrapper)_deep_eval_evaluator_wrapper)_get_base_url)_is_deep_eval_evaluator)PromptOptimization)validate_dataset)validate_dataset_split)validate_evaluators)validate_optimization_task)validate_task)validate_test_dataset)ManagedPrompt)	WarmCache)PromptManager)AnnotationContext)LinkTracker)_batched)_get_llmobs_data_metastruct)_get_ml_app)_get_nearest_llmobs_ancestor)_get_parent_prompt)_get_session_id)_get_span_kind)_get_span_name)_is_evaluation_span)_validate_prompt)add_span_link)enforce_message_role)get_span_links)	safe_json)LLMObsEvalMetricWriter)LLMObsEvaluationMetricEvent)LLMObsExperimentsClient)LLMObsSpanData)LLMObsSpanEvent)LLMObsSpanWriter)should_use_agentless)ExportedLLMObsSpan)Message)Prompt)PromptFallback)_ErrorField)_Meta)_MetaIO)
_SpanField)	Documents)Messages)extract_tool_definitions)HTTPPropagator)__version__	anthropicbedrockbotocoreopenai	langchain
google_adkgoogle_genaivertexai	langgraphlitellmcrewaiopenai_agentsmcppydantic_aiclaude_agent_sdkrequestshttpxurllib3grpcflask	starlettefastapiaiohttpasynciofutures)r   r   r   r   r   r   r   
input_datar   )r   output_dataexpected_output)inputsoutputsexpected_outputsevaluators_resultstaskis_asyncreturnc                    sX   t | std|rt| stdt| }|j t fddtD s*tdd S )Nz!task must be a callable function.z4task must be an async function (coroutine function).c                 3       | ]}| v V  qd S N .0paramparamsr   J/home/ubuntu/.local/lib/python3.10/site-packages/ddtrace/llmobs/_llmobs.py	<genexpr>       z+_validate_task_signature.<locals>.<genexpr>z=Task function must have 'input_data' and 'config' parameters.)	callable	TypeErrorr   iscoroutinefunctioninspect	signature
parametersall_TASK_REQUIRED_PARAMS)r   r   sigr   r   r   _validate_task_signature   s   
r   	evaluatorc                    s   t f}|r	t tf}t| |rd S t| rd S t| s,|r$td|  dtd|  dt| }|j t	 fddt
D sHtdtt
d S )Nz
Evaluator zE must be callable or an instance of BaseEvaluator/BaseAsyncEvaluator.z2 must be callable or an instance of BaseEvaluator.c                 3   r   r   r   r   r   r   r   r      r   z0_validate_evaluator_signature.<locals>.<genexpr>z+Evaluator function must have parameters {}.)rd   rb   
isinstanceru   r   r   r   r   r   r   _EVALUATOR_REQUIRED_PARAMSformattupler   r   valid_base_classesr   r   r   r   _validate_evaluator_signature   s$   


r   c                    s   t f}|r	t tf}t| |rd S t| s&|rtd|  dtd|  dt| }|j t fddt	D sBtd
tt	d S )NzSummary evaluator zS must be callable or an instance of BaseSummaryEvaluator/BaseAsyncSummaryEvaluator.z9 must be callable or an instance of BaseSummaryEvaluator.c                 3   r   r   r   r   r   r   r   r     r   z8_validate_summary_evaluator_signature.<locals>.<genexpr>z3Summary evaluator function must have parameters {}.)re   rc   r   r   r   r   r   r   r   "_SUMMARY_EVALUATOR_REQUIRED_PARAMSr   r   r   r   r   r   %_validate_summary_evaluator_signature   s$   


r   c                   @      e Zd ZdZdS )LLMObsExportSpanErrorz#Error raised when exporting a span.N__name__
__module____qualname____doc__r   r   r   r   r         r   c                   @   r   )LLMObsAnnotateSpanErrorz$Error raised when annotating a span.Nr   r   r   r   r   r     r   r   c                   @   r   )LLMObsSubmitEvaluationErrorz+Error raised when submitting an evaluation.Nr   r   r   r   r   r   %  r   r   c                   @   r   )#LLMObsInjectDistributedHeadersErrorz0Error raised when injecting distributed headers.Nr   r   r   r   r   r   +  r   r   c                   @   r   )%LLMObsActivateDistributedHeadersErrorz1Error raised when activating distributed headers.Nr   r   r   r   r   r   1  r   r   c                   @   sn   e Zd ZU dZeedZee ed< eedZ	ee ed< ee
dZe
eef ed< dedee fdd	Zd
S )
LLMObsSpana  LLMObs span object.

    Passed to the `span_processor` function in the `enable` or `register_processor` methods.

    Example::
        def span_processor(span: LLMObsSpan) -> Optional[LLMObsSpan]:
            # Modify input/output
            if span.get_tag("omit_span") == "1":
                return None
            if span.get_tag("no_input") == "1":
                span.input = []
            return span
    )default_factoryinputoutput_tagskeyr   c                 C   s   | j |S )zGet a tag from the span.

        :param str key: The key of the tag to get.
        :return: The value of the tag or None if the tag does not exist.
        :rtype: Optional[str]
        )r   get)selfr   r   r   r   get_tagK  s   zLLMObsSpan.get_tagN)r   r   r   r   r   listr   r   __annotations__r   dictr   strr   r   r   r   r   r   r   7  s   
 r   	span_kindllmobs_inputllmobs_output)valuemessages c           
      C   s   t  }d}d}|tj}|dur!d}tt|ddpdddg|_|tj}| dkr6|dur6d}t||_|tj}|durPd}tt|ddpJdddg|_	|tj}	| dkre|	dured}t|	|_	|||fS )	zBuild an LLMObsSpan populated for the user span processor.

    Routes input/output to messages or value depending on span kind.
    Returns (llmobs_span, input_type, output_type).
    r  Nr  Fensure_asciicontentrolellmr  )
r   r   rF   VALUEr   r   r   MESSAGESr   r   )
r   r   r  llmobs_span
input_typeoutput_typeinput_valueinput_messagesoutput_valueoutput_messagesr   r   r   _build_llmobs_spanU  s(   



r  spanr  llmobs_metar  r  c                 C   s  | tjpt }| tjpt }tt|d||| tjp d| tjp'd	 | tj
p0i | tjp7g t| tjp?dt| tpGd| tpMd| tpSddd	}| tj}	|	duru|dkrutd |d	 tjd n|	du r|dkrt| }
|
dur|
|d	 d
< | tj}| jtr|dkr|dur||d< |dkr|j|d	 d< n|dkr|jr|jd  dd|d	 d< |dkr|j|d d< |S |dkr|jr|jd  dd|d d< |S )z5Build and return the full meta dict for a span event.kindr  custommessagestacktype)	r  r   r   
model_namemodel_providermetadatatool_definitionsintenterrorNr
  ^Dropping prompt on non-LLM span kind, annotating prompts is only supported for LLM span kinds.r   prompt
experimentr   r  r  r   r  r   )r   rF   INPUTr   OUTPUTr   r   rL   rM   lowerrI   r\   r   INTENTr   r   r   r   r   PROMPTlogwarningpopr   EXPECTED_OUTPUTcontextget_baggage_itemr8   r   r   )r  r  r  r   r  r  r   r  metainput_promptparent_promptr   r   r   r   _build_span_metaz  sL   	
r5  c                "       s  e Zd ZU dZdZeddZee	d< ede
Zee	d< 		ddee d	eeegee f  d
df fddZded
dfddZded
dfddZded
dfddZded
ee fddZdeded
ee fddZdeded
ee fddZded
ee fddZe			ddededee d edee d
ee fd!d"Zded
dfd#d$Zdd%d&Z dd'd(Z!dd)d*Z"e#		+											ddee d,ed-ee d.ee$e  d/ee d0ee d1ee d2ee d3ee d4ee d	eeegee f  d5ee d6ed
dfd7d8Z%edd9d:Z&d;e'ee(f d
dfd<d=Z)d;e'ee(f d
dfd>d?Z*e#		dd@e+dedAee dBee'eef  d
e'eef f
dCdDZ,e#			ddEed2ee dFee- dGeee  d
e.f
dHdIZ/e#					+ddEed2ee dJedKeee0  dLedMed
e.fdNdOZ1e#			P			+ddQedEedRee dSeee  dTeee  dUedJed2ee dMed
e.fdVdWZ2e#dXed
dfdYdZZ3e#			[			dd\ed]ee4ee5 ge6f d^eeee5gef d_e.d`e7e8 dae7e9 dbeee'ee(f gef  dcee'ee'ee(f f ge:f dde5d2ee dGee'eef  dee-dfeee'ee'ee(f f gef  dge;ee<e:dhf f diee d
e=f djdkZ>e#						ldd\ed]e?d_e.d`e7e8 dJed2ee dGee'eef  ddee5 daee7e9  dmee- d
e@fdndoZAe#						ldd\ed]eBd_e.d`e7e;e8eCf  dJed2ee dGee'eef  ddee5 daee7e;e9eDf   dmee- d
eEfdpdqZFe#					ldd\ed_e.dJed2ee dGee'eef  ddee5 dmee- d
eEfdrdsZGe#	l		t	ddued]ee4ee5 ge6f dvee0 d`e7e;e8eCf  dwe-dxedyee- dGee'eef  d
e<eEeHf fdzd{ZIe#dd|eeegee f  d
dfd}d~ZJe#ded
efddZKe#dddZLdd ZMe#				ddGee'ee(f  dee;e'eNf  d\ee deeeO  d
ePf
ddZQdZReeS e	d< eT ZUe#d
eSfddZVe#		ddedeeWd  deXd
eYfddZZe#ddeded
dfddZ[e#	ddedeeWd  d
eeY fddZ\e#d
eSfddZ]e#dddZ^edddZ_e#ddee d
eeO fddZ`d
ee fddZad
eeb fddZcded
dfddZd						dded\ee dee dee dee dee ded
efddZee#						ddee d\ee dee dee dee ded
efddZfe#				dd\ee dee dee ded
ef
ddZge#				dd\ee dee dee ded
ef
ddZhe#				dd\ee dee dee ded
ef
ddZie#				dd\ee dee dee ded
ef
ddZje#						ddee d\ee dee dee dee ded
efddZke#				dd\ee dee dee ded
ef
ddZle#										dd\ee dee dee duee dee dyee- dEee d2ee dee dee d
efddZme#											ddee dee' dee( dee( dee'ee(f  dee'ee(f  dGee'ee(f  deee'ee(f   dee deeeO  ded
dfddȄZne#dd
e<ee ee f fddʄZoe#dd
e<ee ee f fdd̄Zpe#dd
e<ee ee f fdd΄Zqe#dddЄZre#ddd҄Zsedede'ee(f d
dfddՄZte#								ddedede;ee-e:ef dee' dee'eef  dGee'eef  dee dee- dee'eeuf  dee dee d
dfdd܄Zve#debde'eef d
dfddZwe#dde'eef dee d
e'eef fddZxe#de'eef debd
dfddZye#	dde'eef debded
dfddZze#de'eef d
dfddZ{  Z|S )LLMObsNF
DD_APP_KEYr  _app_keyDD_LLMOBS_PROJECT_NAME_project_nametracerspan_processorr   c                    s  t t|   |ptj| _t | _|| _tj	d urtj	nd}t
ttddttdd|d| _tttddttdd|d| _tttdd| d| _tttddttdd| jt| jd	d
dd| _t| j t | _g | _t | _d S )NT_DD_LLMOBS_WRITER_INTERVALg      ?_DD_LLMOBS_WRITER_TIMEOUTg      @)intervaltimeoutis_agentless_DD_LLMOBS_EVALUATOR_INTERVAL)r?  llmobs_servicer  )name_id)r?  r@  r8  _default_projectrA  ) superr6  __init__ddtracer;  r]   _llmobs_context_provider_user_span_processorr   _llmobs_agentless_enabledr   floatosgetenv_llmobs_span_writerr   _llmobs_eval_metric_writerr^   _evaluator_runnerr   r8  rn   r:  _dne_clientr   register_child_after_forkr   _link_tracker_annotationsr"   _annotation_context_lock)r   r;  r<  agentless_enabled	__class__r   r   rH    s<   zLLMObs.__init__r  c                 C   s:   | j r|jtjkr| | t  | | d S d S d S r   )enabled	span_typer   LLM_activate_llmobs_span	telemetryrecord_span_started_do_annotationsr   r  r   r   r   _on_span_start  s
   
zLLMObs._on_span_startc                 C   s2   | j r|jtjkr| | t| d S d S d S r   )r\  r]  r   r^  _submit_llmobs_spanr`  record_span_createdrc  r   r   r   _on_span_finish  s   
zLLMObs._on_span_finishc              
   C   s  d}zkz4|  |}|du r0W W |r(|tdkr*t|s,| jr.| j|| dS dS dS dS dS | j| W n ttt	fyK   t
jd|dd Y nw W |rf|tdkrht|sj| jrl| j|| dS dS dS dS dS |r|tdkrt|s| jr| j|| w w w w w )z>Generate and submit an LLMObs span event to be sent to LLMObs.Nr
  zLError generating LLMObs span event for span %s, likely due to malformed spanTexc_info)_llmobs_span_event_get_ctx_itemrX   r   rR  enqueuerP  KeyErrorr   
ValueErrorr,  r#  )r   r  
span_eventr   r   r   re    s:   
	
zLLMObs._submit_llmobs_spanc                 C   s"   t |}|r| ||S | |S )zWGenerate LLMObs span event using either the meta_struct path or the legacy _store path.)r   "_build_span_event_from_meta_struct _build_span_event_from_ctx_items)r   r  llmobs_datar   r   r   rj    s   
zLLMObs._llmobs_span_eventr  rr  c              
   C   s   | j du r|S d}z\z7ttttf |tji |_|  |}|du r,W W t	| dS t
|ts9tdt| |W W t	| S  tye } ztd| j | d}|W  Y d}~W t	| S d}~ww t	| w )zRun the user span processor.

        Returns the possibly mutated span, or None if the span should be dropped.
        On error, logs and returns the original span unchanged.
        NF=User span processor must return an LLMObsSpan or None, got %r'Error in LLMObs span processor (%r): %rT)rK  r
   r   r   r   rF   r[   r   r`  #record_llmobs_user_processor_calledr   r   r   r  	Exceptionr,  r#  )r   r  rr  r#  resulter   r   r   _apply_user_span_processor  s*   

	
z!LLMObs._apply_user_span_processorc                 C   s  | tjpt }| tjpt }| tjpt }t|}|s%tdt	|}|d u r1t
d|t| | tjp>t}| tj}	|	d u rMt
d|dkrXtt|f t|||\}
}}| |
|}|d u rmd S |}
t||
||||}| tjpi }t|}| |||d|}t|}t|jt|jt|jd}|jt rd|d< |	t|j|t!||j"t#t$|j%|j&rd	nd
|||pd|||d}| tj'}|r||d< |S )N#Span kind not found in span contextxML app is required for sending LLM Observability data. Ensure this configuration is set before running your application.4Failed to extract LLMObs trace ID from span context.r
  Tspan_idtrace_idapm_trace_idexperimentsscoper#  okr  )r  r~  	parent_idrD  start_nsdurationstatusr2  metrics
session_idtags
span_links_ddr   )(r   rF   METAr   r'  r   r(  r   rm  r   rn  _set_ctx_itemrK   	PARENT_IDrV   TRACE_IDr   dispatchr/   r  ry  r5  rJ   r   _llmobs_tagsr   r   r~  r$   r  r0  r1  r8   r   r  r
   intduration_nsr#  CONFIG)r   r  rr  r  r   r  r   ml_appr  llmobs_trace_idr  r  r  user_processed_spanr2  r  r  r  r  	_dd_attrsllmobs_span_eventexperiment_configr   r   r   rp    sh   
z)LLMObs._build_span_event_from_meta_structc           "      C   s  | t}|std|dkrtt|f t }t|jt	|j
t	|j
d}tt|dt t d}|dv rS| tdurS| tpEd|d	< | tpNd
 |d< | tpYi }|dkr~| tdur~t|d }trq|ni }| t|d< ||d< ||d< d}	d}
| tdurd}	tt| tddpdddg|_|jtrd|d< |dkr| t}|r||d< | t}|r||d< | t}|r||d< | t }|dkr|durd}	t!t"t t#||_| t$durd}
tt| t$ddpdddg|_%| t&}|dkr|durd}
t!t"t t#||_%|dkr7| t'dur7| t'p2g |d d< |dkrP| t(durP| t(pKg |d d< | t)durt| t)}|dkrht*+d nLt!t,|}||d d< n@|dkrt-|}|durt.|}|r|t/j0i t/j1i }t|tr|t/j2nd}n| t)}|dur||d d< | t3dur| t3pg |d < | t4}|durt||d!< |j5rt6|7t8pd|7t9pd|7t:pdd"|d#< | j;rYd}zWz2t!tttf | t<|_=| ;|}|du rW W t>?| dS t|ts+t@d$tA| |}W n tByK } zt*5d%| j;| d&}W Y d}~nd}~ww W t>?| nt>?| w |jdur~|	dkrl|j|d d< n|	dkr~|jd' d(d|d d< |j%dur|
dkr|j%|d d< n|
dkr|j%d' d(d|d d< |d s|Cd |d s|Cd | tDpi }tE|}|du rtFd)|GtH| | tIptJ}| tK}|du rtFd*t	|t|j|tL||jMt!tN|jO|j5r d#nd+||g |d,}tP|}|dur|GtQ| ||d-< | R|||dd|d.< | tS} t| t"r8| r8| |d/< | tT}!|!rD|!|d0< |S )1zMBuild span event from ctx_item data (legacy path for backward compatibility).rz  r
  r}  r  )r  r   r   )r
  	embeddingNr  r  r  r  agentr  agent_manifestr   r  Fr  r  r  r  r&  r   r   r   r  r  	documents	retrievalr$  r%  r!  r"  r  r#  rs  rt  Tr   r  r{  r|  r  )r  r~  r  rD  r  r  r  r2  r  r  r  r  r  r  r   )Urk  rX   rm  r   r  r/   r   r   r~  r$   r  r   r   r   rL   rM   r)  rI   r(   r   r   r   rC   r   r   r   r0  r1  r8   r7   r>   r?   rA   r
   r   r   rP   r   rO   r@   rN   rB   r,  r-  r   r   r   rF   r  r'  r+  r\   rH   r#  r   r   r   r   r   rK  r[   r   r`  ru  r   r  rv  r.  rJ   r   rn  r  rK   rQ   rV   rG   r   r  r  r  r   rW   r  rY   r4   )"r   r  r   r  r  r2  r   _dd_valmetadata_ddr  r  r   r   r   r  r  prompt_json_strprompt_dictparent_spanparent_llmobs_dataparent_llmobs_inputr4  r"  r#  user_llmobs_spanrx  r  r  r  r  r  r  r  r  r   r   r   rq  Z  sF  

































z'LLMObs._build_span_event_from_ctx_itemsr  r  use_meta_structc              
   C   s  t j}i |t jp
dt jpd| jpdd|td| jd}| t}|r'||d< |r-||d< d }|rI|rI|	t
ji }	|		drF|		d|d< |	}n| trU| t|d< | t}t| rcd|tj< |d url|| | jt}
|
r|d|vr||
|d< | jt}|rd	|vr||d	< | jt}|rd
|vr||d
< | jt}|rd|vr||d< | jt}|rd|vr||d< | jt}|rd|vr||d< | jt}|rd|vr||d< dd | D S )Nr  integrationpython)versionenvservicesourcer  ddtrace.versionlanguager#  
error_typer  ragasexperiment_idrun_idrun_iterationdataset_nameproject_name
project_idexperiment_namec                 S      g | ]
\}}d  ||qS z{}:{}r   r   kvr   r   r   
<listcomp>d      z'LLMObs._llmobs_tags.<locals>.<listcomp>)r   r  r  r  r  r   r#  r   r   r   rF   r[   rk  rE   r   	constantsRUNNER_IS_INTEGRATION_SPAN_TAGupdater0  r1  r8   r<   r=   r6   r;   r:   r9   items)r  r  r  r  rr  dd_tagsr  err_typeexisting_tagsllmobs_tagsr  r  r  r  r  r  r  r   r   r   r    sj   





zLLMObs._llmobs_tagsc                 C   s   |j tjkrd S | jj }|d u rd S |t}| j$ | jj	D ]\}}}||kr7| j
|fi |ddi q!W d    d S 1 sCw   Y  d S )N_suppress_span_kind_errorT)r]  r   r^  	_instancer;  current_trace_contextr1  r)   rX  rW  annotate)r   r  current_contextcurrent_context_id_
context_idannotation_kwargsr   r   r   rb  f  s   
"zLLMObs._do_annotationsc                 C   s@   | j  | _ | j | _| j | _d t_| jr|   d S d S r   )rP  recreaterQ  rR  r6  _prompt_managerr\  _start_servicer   r   r   r   rU  t  s   zLLMObs._child_after_forkc                 C   sf   z| j   | j  W n ty   td Y nw z| j  W d S  ty2   td Y d S w )NzError starting LLMObs writerszError starting evaluator runner)rP  startrQ  r   r,  debugrR  r  r   r   r   r  |  s   
zLLMObs._start_servicec                 C   s\  z| j   | jj  | jj  W n ty!   td Y nw z| j  | j  W n ty<   td Y nw t	
d| j t	
d| j t	
d| j t	
d| j t	
d| j t	
d| jj t	
d	| j t	
d
| j t	
t| jj t	
t| jj t	
t| jj t	
t| jj t	
t| jj t	
t| jj  t!"| j# d S )NzError stopping evaluator runnerzError stopping LLMObs writerstrace.span_starttrace.span_finishhttp.span_inject!http.activate_distributed_headersthreading.submitthreading.executionasyncio.create_taskasyncio.execute_task)$rR  stopr  rP  periodicrQ  r   r,  r  r   reset_listenersrd  rg  _inject_llmobs_context._activate_llmobs_distributed_context_soft_fail_current_trace_contextrJ  activate_on_asyncio_create_task_on_asyncio_execute_taskr0   rV  on_llm_tool_choicer2   on_tool_callr3   on_tool_call_output_usedr.   on_guardrail_span_startr/   on_llm_span_finishr1   on_openai_agent_span_finishr   
unregisterrU  r  r   r   r   _stop_service  sJ   

zLLMObs._stop_serviceTintegrations_enabledrY  instrumented_proxy_urlssiteapi_keyapp_keyr  r  r  _tracer_autoc                 C   s  | j rtd| j dS |   tdr#ttds#td dS |p'tj	t_	|p-tj
t_
|p3| j| _|p;| jp;t| _|	pAtjt_|
pGtjt_|pMtjt_|pStjt_d}t }zt|durd|ntjdt_tjrtj
svd}tdtj	sd}td	td
sdt_td t  td |r|   | ||d| _t }| jjj j!"| d| _ | j#  t$%d| jj& t$%d| jj' t$%d| j( t$%d| j) t$%d| jj*d t$%d| jj+j, t$%d| jj- t$%d| jj. t$%t/| jj0j1 t$%t2| jj0j3 t$%t4| jj0j5 t$%t6| jj0j7 t$%t8| jj0j9 t$%t:| jj0j; t<=| j t>t?j@d td| jtj | j tj|tjd}td| W tAB|tjtj	||tjtj dS tAB|tjtj	||tjtj w )a  
        Enable LLM Observability tracing.

        :param str ml_app: The name of your ml application.
        :param bool integrations_enabled: set to `true` to enable LLM integrations.
        :param bool agentless_enabled: set to `true` to disable sending data that requires a Datadog Agent.
        :param set[str] instrumented_proxy_urls: A set of instrumented proxy URLs to help detect when to emit LLM spans.
        :param str site: Your datadog site.
        :param str api_key: Your datadog api key.
        :param str app_key: Your datadog application key.
        :param str project_name: Your project name used for experiments.
        :param str env: Your environment name.
        :param str service: Your service name.
        :param Callable[[LLMObsSpan], Optional[LLMObsSpan]] span_processor: A function that takes an LLMObsSpan and
            returns an LLMObsSpan or None. If None is returned, the span will be omitted and not sent to LLMObs.
        z%s already enabledNDD_LLMOBS_ENABLEDz_LLMObs.enable() called when DD_LLMOBS_ENABLED is set to false or 0, not starting LLMObs service)user_defined_agentless_enabledmissing_api_keyzDD_API_KEY is required for sending LLMObs data when agentless mode is enabled. Ensure this configuration is set before running your application.missing_sitezDD_SITE is required for sending LLMObs data when agentless mode is enabled. Ensure this configuration is set before running your application.DD_REMOTE_CONFIGURATION_ENABLEDFzQRemote configuration disabled because DD_LLMOBS_AGENTLESS_ENABLED is set to true.T)r;  r<  r  r  r  r  r  
llmobs_ctxr  r  r  z'%s enabled; instrumented_proxy_urls: %s)llmobs_enabledllmobs_ml_appr  llmobs_agentless_enabledzLLMObs configurations: %s)Cr\  r,  r  r   _warn_if_litellm_was_importedrN  rO  r#   r   _dd_site_dd_api_keyr8  r:  r+   r  r  _llmobs_ml_app_llmobs_instrumented_proxy_urlstimetime_nsr   rL  rn  _remote_config_enabledr   disabler    enable_agentless_client_patch_integrationsr  r   r;  _span_aggregatordd_processorsappendr  r   onrd  rg  r  r  r  rJ  r  r  r  r0   rV  r  r2   r  r3   r  r.   r  r/   r  r1   r  r   rT  product_activatedr!   LLMOBSr`  record_llmobs_enabled)clsr  r  rY  r  r  r  r  r  r  r  r<  r  r  r#  r  
apm_filterllmobs_infor   r   r   enable  s   !




zLLMObs.enablec                  C   s4   dt jv rdd l} t| ddstd d S d S d S )Nr   r   _datadog_patchFa_  LLMObs.enable() called after litellm was imported but before it was patched. This may cause tracing issues if you are importing patched methods like 'litellm.completion' directly. To ensure proper tracing, either run your application with ddtrace-run, call ddtrace.patch_all() before importing litellm, or enable LLMObs before importing other modules.)sysmodulesr   getattrr,  r-  )r   r   r   r   r  U  s   
z$LLMObs._warn_if_litellm_was_imported	task_datac                 C   s   |   |d< dS )z<Propagates llmobs active trace context across asyncio tasks.r  N)r  )r   r  r   r   r   r  c  s   zLLMObs._on_asyncio_create_taskc                 C   s&   | d}|dur| j| dS dS )zDActivates llmobs active trace context across asyncio task execution.r  N)r   rJ  r  )r   r  r  r   r   r   r  g  s   
zLLMObs._on_asyncio_execute_taskr   	eval_namevariable_mappingc                 C   sf   | j r| j jstd|j|||d}| j j| t }tj	|d |
 d}d| d| iS )NSLLMObs is not enabled. Ensure LLM Observability is enabled via `LLMObs.enable(...)`)r  r  r  r  )evalNameapplicationNameui_urlz/llm/evaluations/custom?)r  r\  rn  _build_publish_payloadrS  publish_custom_evaluatorrt   urllibparse	urlencodestrip)r  r   r  r  r  evaluation_payloadbase_urlqueryr   r   r   publish_evaluatorm  s   zLLMObs.publish_evaluatorr  r  r  c                 C   s8   |d urt |tstd| jj||p| j||}|S )Nzqtags must be a list of strings in the format of tag key value pairs. Example: tags=["key1:value1", "key2:value2"])r   r   rn  r  rS  dataset_get_with_recordsr:  )r  r  r  r  r  dsr   r   r   pull_dataset  s   zLLMObs.pull_datasetdescriptionrecordsbulk_uploaddeduplicatec                 C   s   |du rg }| j j|||}t|dkrf|r*|D ]}|| q|j|dd |S ttt||j	 }	tt||	 }
t
d|	|
 d}t||
D ]}|D ]}|| qQ|j||dd}|red}qM|S )a  Creates a Dataset to run Experiments on.

        :param dataset_name: The name of the dataset.
        :param project_name: The name of the project to save the dataset to.
        :param description: The description of the dataset.
        :param records: Optional records to initialize the dataset with.
        :param deduplicate:
            Wether to deduplicate the records or not. If bulk_upload is True, deduplication occurs
            within the uploaded data, not existing data already stored on the sever.
        :param bulk_upload:
            - True:
                Uploads all records in a single request. This method does not support deduplication
                against existing data and is best suited for initial uploads.
            - False:
                Splits the data into batches and uploads them individually. This method supports
                deduplication against existing records but does not provide transactional guarantees
                when the same dataset is modified concurrently by multiple clients.
        Nr   T)r4  r3  z.batched upload num_batches :%d, batch_size: %dF)r4  create_new_versionr3  )r  rS  dataset_createlenr  pushmathceilr   BATCH_UPDATE_THRESHOLDr,  r  r   _push)r  r  r  r1  r2  r3  r4  r/  recordnum_batches
batch_sizer5  record_batchdata_changedr   r   r   create_dataset  s:   zLLMObs.create_dataset,csv_pathinput_data_columnsexpected_output_columnsmetadata_columnscsv_delimiterc
                    s  |d u rg }d u rg t  }
t t g }zt|dd}|  }|s+td|d t j||d}|j	d u r@td|j	  fdd|D } fdd|D }fd	dD }t
 fd
d|D rptd| t
 fdd|D rtd| t
 fddD rtd| |D ]$|tfdd|D fdd|D fddD g dd d qW d    n1 sw   Y  W t |
 nt |
 w | jj|||}|D ]}|| qt|dkr| jjj|j|j|	d |S )Nr)modez2CSV file appears to be empty or header is missing.r   )	delimiterc                       g | ]}| vr|qS r   r   r   colheader_columnsr   r   r        z2LLMObs.create_dataset_from_csv.<locals>.<listcomp>c                    rL  r   r   rM  rO  r   r   r    rQ  c                    rL  r   r   rM  )rG  r   r   r    rQ  c                 3       | ]}| vV  qd S r   r   rM  rO  r   r   r     r   z1LLMObs.create_dataset_from_csv.<locals>.<genexpr>z'Input columns not found in CSV header: c                 3   rR  r   r   rM  rO  r   r   r     r   z1Expected output columns not found in CSV header: c                 3   rR  r   r   rM  rO  r   r   r     r   z*Metadata columns not found in CSV header: c                       i | ]}| | qS r   r   rM  rowr   r   
<dictcomp>      z2LLMObs.create_dataset_from_csv.<locals>.<dictcomp>c                    rS  r   r   rM  rT  r   r   rV    rW  c                    rS  r   r   rM  rT  r   r   rV    rW  r  )r   r   r   r  	record_idcanonical_id)r4  )csvfield_size_limitr5   openreadliner)  rn  seek
DictReader
fieldnamesanyr  rh   r  rS  r6  r7  dataset_bulk_uploadrE  _records)r  rD  r  rE  rF  rG  rH  r1  r  r4  original_field_size_limitr2  csvfiler  rowsmissing_input_columnsmissing_output_columnsmissing_metadata_columnsr/  rI  r   )rP  rG  rU  r   create_dataset_from_csv  s^   


&zLLMObs.create_dataset_from_csv
dataset_idc                 C   s   | j j|S r   )r  rS  dataset_delete)r  rk  r   r   r   _delete_dataset  s   zLLMObs._delete_dataset   rD  r   optimization_taskdataset
evaluatorssummary_evaluatorslabelization_functioncompute_scorer   max_iterationsstopping_conditiondataset_split.test_datasetc                 C   s  t | t| t| t| t|| t| d}|dur&| j||
d}tdi d|d|d|d|d|d|
p_| jd	|	d
|d|d|d| j	d|d|d|d|d|S d	|	d
|d|d|d| j	d|d|d|d|d|S )a  Initialize a PromptOptimization to iteratively improve prompts using experiments.

        PromptOptimization runs a baseline experiment with an initial prompt, then uses an
        optimization task to iteratively suggest improvements based on evaluation results.

        :param name: The name of the prompt optimization run.
        :param task: The task function to execute on the dataset. Must accept parameters ``input_data``
                     (dict with input data for the task) and ``config`` (configuration dictionary).
                     Should return the task output as a JSON-serializable value.
        :param optimization_task: Function that calls an LLM to generate improved prompts.
                                  Must accept parameters ``system_prompt`` (str), ``user_prompt`` (str),
                                  and ``config`` (dict). Should return the new prompt (str).
                                  The system_prompt contains optimization instructions
                                  loaded from template, and user_prompt contains the current prompt with
                                  evaluation examples.
        :param dataset: The dataset to run experiments on, created with ``LLMObs.create_dataset()``
                       or ``LLMObs.pull_dataset()``.
        :param evaluators: A list of evaluators to measure task performance. Can be either
                          class-based evaluators (inheriting from BaseEvaluator) or function-based
                          evaluators that accept (input_data, output_data, expected_output) parameters.
                          Should return a JSON-serializable value or an EvaluatorResult with the evaluation results.
        :param summary_evaluators: list of summary evaluators (REQUIRED). Can be either
                                   class-based evaluators (inheriting from BaseSummaryEvaluator) or function-based
                                   evaluators that accept (inputs: list, outputs: list, expected_outputs: list,
                                   evaluations: dict) and return aggregated metrics.
        :param labelization_function: Function to generate labels from individual experiment results (REQUIRED).
                                     Takes an individual result dict (containing "evaluations" key) and returns
                                     a string label. Used to categorize examples shown to the optimization LLM.
                                     Example: ``lambda r: "Very good" if r["evaluations"]["score"] >= 0.8 else "Bad"``
        :param compute_score: Function to compute the score for each iteration (REQUIRED).
                             Takes summary_evaluations dict from the experiment result and returns a float score.
                             Used to determine which iteration performed best.
        :param config: Configuration dictionary for the optimization. Must contain:
                      - ``prompt`` (mandatory): Initial prompt template
                      - ``model_name`` (optional): Model to use for task execution
                      - ``evaluation_output_format`` (optional): the output format wanted
                      - ``runs`` (optional): The number of times to run the experiment, or, run the task for every
                                             dataset record the defined number of times.
                      Additional config values are passed through to the task function.
        :param project_name: The name of the project to organize optimization runs. Defaults to the
                            project name set in ``LLMObs.enable()``.
        :param tags: A dictionary of string key-value tag pairs to associate with the optimization.
        :param max_iterations: Maximum number of optimization iterations to run. Default is 5.
        :param stopping_condition: Optional function to determine when to stop optimization early.
                                   Takes summary_evaluations dict from the experiment result and returns True if
                                   optimization should stop.
        :param dataset_split: Controls dataset splitting. Accepts:
            - ``False`` (default): No splitting, use full dataset for everything.
            - ``True``: Split with default ratios (60/20/20 without test_dataset, 80/20 with).
            - ``(train, valid, test)`` tuple: Custom 3-way split ratios. Must sum to 1.0.
              Cannot be combined with ``test_dataset``.
            - ``(train, valid)`` tuple: Custom 2-way split ratios. Must sum to 1.0.
              Requires ``test_dataset`` for the test set.
        :param test_dataset: Optional name of a separate test dataset. When provided, the dataset is
                            pulled automatically, the main dataset is split into train/valid (80/20),
                            and the test dataset is used for the final unbiased score.
                            Implicitly enables dataset splitting.
        :return: PromptOptimization object. Call ``.run()`` to execute the optimization.
        :raises TypeError: If task, optimization_task, evaluators, or dataset have incorrect types
                          or signatures.
        :raises ValueError: If config is missing required keys.

        Example::

            def my_task(input_data, config):
                prompt = config["prompt"]
                question = input_data["question"]
                # Use prompt and question to generate answer
                return {"answer": generate_answer(prompt, question)}

            def optimization_task(system_prompt, user_prompt, config):
                # Call LLM to analyze results and generate improved prompt
                import openai
                client = openai.OpenAI(api_key="your-api-key")
                response = client.chat.completions.create(
                    model="gpt-4",
                    messages=[
                        {"role": "system", "content": system_prompt},
                        {"role": "user", "content": user_prompt},
                    ],
                    response_format={"type": "json_object"},
                )
                new_prompt = json.loads(response.choices[0].message.content)["new_prompt"]
                return new_prompt

            def accuracy_evaluator(input_data, output_data, expected_output):
                is_correct = output_data["answer"] == expected_output["answer"]
                return "correct" if is_correct else "incorrect"

            def summary_evaluator(inputs, outputs, expected_outputs, evaluations):
                correct_count = sum(1 for e in evaluations if e == "correct")
                return {"accuracy": correct_count / len(evaluations)}

            def compute_score(summary_evaluations):
                return summary_evaluations["summary_evaluator"]["value"]["accuracy"]

            def labelization_function(individual_result):
                eval_value = individual_result["evaluations"]["accuracy_evaluator"]["value"]
                return "Correct answer" if eval_value == "correct" else "Incorrect answer"

            dataset = LLMObs.create_dataset(name="qa_pairs", ...)
            opt = LLMObs._prompt_optimization(
                name="optimize_qa",
                task=my_task,
                optimization_task=optimization_task,
                dataset=dataset,
                evaluators=[accuracy_evaluator],
                labelization_function=labelization_function,
                compute_score=compute_score,
                summary_evaluators=[summary_evaluator],
                config={
                    "prompt": "Answer the question: {question}",
                    "model_name": "gpt-3.5-turbo",
                    "runs": 10
                }
            )
            results = opt.run()
        N)r  r  rD  r   ro  rp  rq  r  r   rr  rt  rs  _llmobs_instancer  ru  rv  rw  rx  r   )
r{   rz   rw   r|   rx   ry   r0  rv   r:  r  )r  rD  r   ro  rp  rq  rr  rs  rt  r   r  r  ru  rv  rw  rx  pulled_test_datasetr   r   r   _prompt_optimization  s    


	
	
zLLMObs._prompt_optimization   runsc                 C   s   t |dd t|tstd|stdt|}t|D ]\}}t|dd t|r2t|||< qq|	rBt	dd |	D sBtd|	rO|	D ]}t
|dd qFt|||||pX| j|||| j|	|
dS )	a   Initializes an Experiment to run a task on a Dataset and evaluators.

        :param name: The name of the experiment.
        :param task: The task function to run. Must accept parameters ``input_data`` and ``config``.
        :param dataset: The dataset to run the experiment on, created with LLMObs.pull/create_dataset().
        :param evaluators: A list of evaluator functions or BaseEvaluator instances to evaluate the task output.
                           Function-based evaluators must accept parameters ``input_data``, ``output_data``,
                           and ``expected_output``.
                           Class-based evaluators must inherit from BaseEvaluator and implement the evaluate method.
        :param project_name: The name of the project to save the experiment to.
        :param description: A description of the experiment.
        :param tags: A dictionary of string key-value tag pairs to associate with the experiment.
        :param config: A configuration dictionary describing the experiment.
        :param summary_evaluators: A list of summary evaluator functions or BaseSummaryEvaluator instances to evaluate
                                   the task results and evaluations to produce a single value.
                                   Function-based summary evaluators must accept parameters ``inputs``, ``outputs``,
                                   ``expected_outputs``, ``evaluators_results``.
                                   Class-based summary evaluators must inherit from BaseSummaryEvaluator and implement
                                   the evaluate method which receives a SummaryEvaluatorContext.
        :param runs: The number of times to run the experiment, or, run the task for every dataset record the defined
                     number of times.
        Fr   )Dataset must be an LLMObs Dataset object.zKEvaluators must be a list of callable functions or BaseEvaluator instances.c                 s   s"    | ]}t |pt|tV  qd S r   )r   r   re   )r   summary_evaluatorr   r   r   r     s
    
z$LLMObs.experiment.<locals>.<genexpr>zZSummary evaluators must be a list of callable functions or BaseSummaryEvaluator instances.r  r  r1  r   ry  rr  r}  )r   r   rg   r   r   	enumerater   ru   rs   r   r   rp   r:  r  r  rD  r   rp  rq  r1  r  r  r   rr  r}  evaluators_listidxr   r  r   r   r   r&    sD   $
zLLMObs.experimentc                 C   s   t |dd t|tstd|stdt|}t|D ]\}}t|dd t|r2t|||< qq|	r@|	D ]}t	|dd q7t
|||||pI| j|||| j|	|
dS )aE  Initializes an Experiment to run an async task on a Dataset with evaluators.

        This is the async version of experiment() that supports async tasks, evaluators, and summary evaluators.
        Sync evaluators are also supported and will be run via asyncio.to_thread().

        :param name: The name of the experiment.
        :param task: The async task function to run. Must be an async function accepting parameters
                     ``input_data`` and ``config``.
        :param dataset: The dataset to run the experiment on, created with LLMObs.pull/create_dataset().
        :param evaluators: A list of evaluator functions or BaseEvaluator/BaseAsyncEvaluator instances.
                           Supports both sync and async evaluators. Sync evaluators will be run in a thread pool.
                           Function-based evaluators must accept parameters ``input_data``, ``output_data``,
                           and ``expected_output``.
                           Class-based evaluators must inherit from BaseEvaluator or BaseAsyncEvaluator
                           and implement the evaluate method.
        :param project_name: The name of the project to save the experiment to.
        :param description: A description of the experiment.
        :param tags: A dictionary of string key-value tag pairs to associate with the experiment.
        :param config: A configuration dictionary describing the experiment.
        :param summary_evaluators: A list of summary evaluator functions or BaseSummaryEvaluator/
                                   BaseAsyncSummaryEvaluator instances. Supports both sync and async.
                                   Function-based summary evaluators must accept parameters ``inputs``, ``outputs``,
                                   ``expected_outputs``, ``evaluators_results``.
                                   Class-based summary evaluators must inherit from BaseSummaryEvaluator or
                                   BaseAsyncSummaryEvaluator and implement the evaluate method.
        :param runs: The number of times to run the experiment.
        Tr~  r  z`Evaluators must be a list of callable functions, BaseEvaluator, or BaseAsyncEvaluator instances.r  )r   r   rg   r   r   r  r   ru   rr   r   rk   r:  r  r  r   r   r   async_experiment
  s<   )
zLLMObs.async_experimentc           	      C   s:   t |t j|g |p
| j|||| j|dd}|jddd |S )NT)r  r  r1  r   ry  r}  is_distributedr   F)ensure_unique)rk   _NO_OP_TASKr:  r  _setup_experiment)	r  rD  rp  r1  r  r  r   r}  r&  r   r   r   _distributed_experimentQ  s$   zLLMObs._distributed_experimentr   r  dataset_recordsjobsraise_errorsr  c	                 C   s   | j r| j jstd| j j|}	| j |	_||	j_||	_||	_	|	
|||}
zt  W n ty>   t|
}Y |	|fS w dd l}|jjdd}|tj|
 }W d    |	|fS 1 saw   Y  |	|fS )Nr   r   r|  )max_workers)r  r\  rn  rS  experiment_getry  _datasetrc  _task_evaluators_run_task_single_iterationr   get_running_loopRuntimeErrorrunconcurrent.futuresr   ThreadPoolExecutorsubmitrw  )r  r  r   r  rq  r  r  r  r  r&  cororesults
concurrentpoolr   r   r   _run_for_experimento  s*   
zLLMObs._run_for_experiment	processorc                 C   s   || j _dS )a  Register a processor to be called on each LLMObs span.

        This can be used to modify the span before it is sent to LLMObs. For example, you can modify the input/output.
        You can also return None to omit the span entirely from being sent to LLMObs.

        To deregister the processor, call `register_processor(None)`.

        :param processor: A function that takes an LLMObsSpan and returns an LLMObsSpan or None.
                         If None is returned, the span will be omitted and not sent to LLMObs.
        N)r  rK  )r  r  r   r   r   register_processor  s   zLLMObs.register_processorr  c                 C   s   |t vrdS t | tj v S NF)SUPPORTED_LLMOBS_INTEGRATIONSrI  _monkey_get_patched_modules)r  r  r   r   r   _integration_is_enabled  s   zLLMObs._integration_is_enabledc                 C   sh   | j std| j d S td| j t| j | j  d| _ d | _	t
tjd td| j d S )Nz%s not enabledzDisabling %sFz%s disabled)r\  r,  r  r   r   r  r  r  r  r  r    r  r!   r  )r  r   r   r   r    s   
zLLMObs.disablec                    s@   |sd S  fdd|D }  t}|r|| } t| d S )Nc                    s<   g | ]}|d  t  d  kr|d t  d kr|qS r~  r  )r6  export_span)r   	span_linkr  r   r   r    s    z*LLMObs._tag_span_links.<locals>.<listcomp>)rk  rY   r  )r   r  r  current_span_linksr   r  r   _tag_span_links  s   

zLLMObs._tag_span_linksr%  _linked_spansc                    sP   t  ddifdd fdd}fdd}t||S )	a  
        Sets specified attributes on all LLMObs spans created while the returned AnnotationContext is active.
        Annotations are applied in the order in which annotation contexts are entered.

        :param tags: Dictionary of JSON serializable key-value tag pairs to set or update on the LLMObs span
                     regarding the span's context.
        :param prompt: A dictionary that represents the prompt used for an LLM call in the following form:
                        `{
                            "id": "...",
                            "version": "...",
                            "chat_template": [{"content": "...", "role": "..."}, ...],
                            "variables": {"variable_1": "...", ...}}`.
                            "tags": {"key1": "value1", "key2": "value2"},
                        }`
                        Can also be set using the `ddtrace.llmobs.utils.Prompt` constructor class.
                        For managed prompts, use `prompt.to_annotation_dict(**variables)`.
                        - This argument is only applicable to LLM spans.
                        - The dictionary may contain optional keys relevant to Templates and RAG applications:
                            `rag_context_variables` - a list of variable key names that contain ground
                                                        truth context information
                            `rag_query_variables` - a list of variable key names that contains query
                                                        information for an LLM call
        :param name: set to override the span name for any spans annotated within the returned context.
        created_contextNc                     sx   j j }  }| d u r(tdd} | t| d| _j jj|  | d< |S | 	ts5| t| |S | 	t}|S )NF)	is_remoteTr  )
r  r;  r  r   set_baggage_itemr)   _reactivatecontext_providerr  r1  )current_ctxctx_idannotation_idr  stater   r   get_annotations_context_id  s   


z=LLMObs.annotation_context.<locals>.get_annotations_context_idc               
      sR   j j  } j j|  df W d    d S 1 s"w   Y  d S )N)r  r%  _namer  )r  rX  rW  r  )r  )r  r  r  r  rD  r%  r  r   r   register_annotation  s   
"z6LLMObs.annotation_context.<locals>.register_annotationc                     s   j j( tj jD ]\} \}}}| krj j|   nqtd W d    n1 s/w   Y  d d urVdd _j jj	
 }|d u rXj jj	d  d S d S d S )Nz Failed to pop annotation contextr  F)r  rX  r  rW  r.  r,  r  r  r;  r  activer  )ir   r  current_activer  r   r   deregister_annotation  s    

	
z8LLMObs.annotation_context.<locals>.deregister_annotation)r   r   )r  r  r%  rD  r  r  r  r   )r  r  r  r  rD  r%  r  r  r   annotation_context  s   !
zLLMObs.annotation_contextr  c                 C   sJ   | j  | j}|du r|  }|| _|W  d   S 1 sw   Y  dS )z5Thread-safe get-or-initialize for the prompt manager.N)_prompt_manager_lockr  _initialize_prompt_manager)r  managerr   r   r   _ensure_prompt_manager   s   $zLLMObs._ensure_prompt_manager	prompt_idlabel)development
productionfallbackc                 C   s   |   }||||S )a  
        Retrieve a prompt template from the Datadog Prompt Registry.

        :param prompt_id: The unique identifier of the prompt in the registry
        :param label: Deployment label (e.g., "production", "development"). If not provided, returns the latest version.
        :param fallback: Fallback to use if prompt cannot be fetched (cold start + API failure).
                         Can be a template string, message list, Prompt dict, or a callable that
                         returns any of those.

        :returns: A ManagedPrompt object with template and rendering methods
        :raises ValueError: If the prompt cannot be fetched and no fallback is provided

        Example::

            # Simple usage - returns the latest version
            prompt = LLMObs.get_prompt("greeting")
            messages = prompt.format(user="Alice")

            # With explicit label and fallback
            prompt = LLMObs.get_prompt(
                "greeting",
                label="production",
                fallback="Hello {{user}}, how can I help?"
            )

            # Use with annotation_context for observability
            # Pass the same variables to both format() and to_annotation_dict()
            prompt = LLMObs.get_prompt("greeting")
            variables = {"user": "Alice"}
            with LLMObs.annotation_context(prompt=prompt.to_annotation_dict(**variables)):
                openai.chat.completions.create(
                    messages=prompt.format(**variables)
                )
        )r  
get_prompt)r  r  r  r  prompt_managerr   r   r   r  *  s   )zLLMObs.get_prompthotwarmc                 C   sD   | j dur| j j||d dS |r td}t|d}|  dS dS )zClear the prompt cache.

        Args:
            hot: If True, clear the hot (in-memory) cache. Defaults to True.
            warm: If True, clear the warm (file-based) cache. Defaults to True.
        N)r  r  DD_LLMOBS_PROMPTS_CACHE_DIR)	cache_dir)r  clear_cache_get_configr~   clear)r  r  r  r  
warm_cacher   r   r   clear_prompt_cacheV  s   

zLLMObs.clear_prompt_cachec                 C   s   |   }|||S )aZ  Force refresh a specific prompt from the registry.

        Fetches the prompt synchronously and updates both caches.

        Args:
            prompt_id: The prompt identifier.
            label: The prompt label. If not provided, returns the latest version.

        Returns:
            The refreshed prompt, or None if fetch failed.
        )r  refresh_prompt)r  r  r  r  r   r   r   r  f  s   zLLMObs.refresh_promptc                 C   sf   t j}|s	tdtdtt}tddt}td}tdtt}tdp(dt j }t	||||||d	S )
z1Initialize the prompt manager with configuration.z.DD_API_KEY is required for the Prompt RegistryDD_LLMOBS_PROMPTS_CACHE_TTL$DD_LLMOBS_PROMPTS_FILE_CACHE_ENABLEDFr  DD_LLMOBS_PROMPTS_TIMEOUTDD_LLMOBS_OVERRIDE_ORIGINzhttps://api.)r  r+  	cache_ttlfile_cache_enabledr  r@  )
r   r  rn  r  r,   rM  r#   r-   r  r   )r  r  r  r  r  r@  r+  r   r   r   r  z  s    z!LLMObs._initialize_prompt_managerc                 C   s   | j du rtd dS d}z| jj  W n ty(   d}tjddd Y nw z| jj  | jj  W n tyI   d}tjd	dd Y nw t	
| dS )
z[
        Flushes any remaining spans and evaluation metrics to the LLMObs backend.
        FzNflushing when LLMObs is disabled. No spans or evaluation metrics will be sent.Nevaluator_flush_errorzFailed to run evaluator runner.Trh  writer_flush_errorz4Failed to flush LLMObs spans and evaluation metrics.)r\  r,  r-  r  rR  r  rv  rP  rQ  r`  record_user_flush)r  r#  r   r   r   flush  s$   

zLLMObs.flushc                  C   s   dd t  D } |  D ]\}}d|  }|tjv r%ttj| | |< qtd}t|}| 	dd | D  t
dddi|  dd	 |  D }td
| dS )z~
        Patch LLM integrations. Ensure that we do not ignore DD_TRACE_<MODULE>_ENABLED or DD_PATCH_MODULES settings.
        c                 S   s"   i | ]}||d krddgndqS )r   zbedrock-runtimezbedrock-agent-runtimeTr   )r   r  r   r   r   rV    s    z.LLMObs._patch_integrations.<locals>.<dictcomp>zDD_TRACE_%s_ENABLEDDD_PATCH_MODULESc                 S   s&   i | ]\}}|t  v r|t|qS r   )r  valuesr#   r  r   r   r   rV    s   & r  Tc                 S   s   g | ]\}}|r|qS r   r   r  r   r   r   r    rQ  z.LLMObs._patch_integrations.<locals>.<listcomp>zPatched LLM integrations: %sNr   )r  r  r  upperrN  environr#   rO  r%   r  r   r,  r  )integrations_to_patchmoduler  env_vardd_patch_modulesdd_patch_modules_to_strllm_patched_modulesr   r   r   r    s    

zLLMObs._patch_integrationsc              
   C   s   | j s
td dS |du r!| j }|du r!t|d tdd}z6z%|jt	j
kr1d}tdtt|jt|tp>|jdW W t|| S  ttfyY   d}tddw t|| w )	zReturns a simple representation of a span to export its span and trace IDs.
        If no span is provided, the current active LLMObs-type span will be used.
        zNLLMObs.export_span() called when LLMObs is disabled. No span will be exported.Nno_active_spanzNo span provided and no active LLMObs-generated span found. Ensure you pass the span explicitly using LLMObs.export_span(span=<your_span>) when exporting from a different thread or async task than where the span was created.invalid_span&Span must be an LLMObs-generated span.r  z8Failed to export span. Span must be a valid Span object.)r\  r,  r-  r  _current_spanr`  record_span_exportedr   r]  r   r^  r   r   r~  r$   rk  rG   r  r   AttributeError)r  r  r#  r   r   r   r    s2   


zLLMObs.export_spanc                 C   s   | j  }t|tr|S dS )zReturns the currently active LLMObs-generated span.
        Note that there may be an active span represented by a context object
        (i.e. a distributed trace) which will not be returned by this method.
        N)rJ  r  r   r   )r   r  r   r   r   r    s   
zLLMObs._current_spanc                 C   sN   | j  }t|tr|S t|tr%|j}t|tpt|j	|j
t< |S dS )z1Returns the context for the current LLMObs trace.N)rJ  r  r   r   r   r0  r   rk  rG   r  _metarS   )r   r  r0  r   r   r   r    s   



zLLMObs._current_trace_contextc                 C   s   | j  }|r2|tt|j t|tr|t	n|j
t}|dur(t|n|j}|t	| n|tt |t	t  | j | dS )z\Propagate the llmobs parent span's ID as the new span's parent ID and activate the new span.N)rJ  r  r  rQ   r   r~  r   r   rk  rG   r  r   rS   r  r  rV   r   r  )r   r  llmobs_parentparent_llmobs_trace_idr  r   r   r   r_    s   

zLLMObs._activate_llmobs_spanoperation_kindr  r  
_decoratorc           	      C   s   |d u r|}| j j||tjd}| js|S |t| |d ur%|t| |d ur/|t| |d ur5|nt	|}|d urC|t
| |d urI|nt|}|d u rUtd|t|t|t|i td||| |S )N)resourcer]  zml_app is required for sending LLM Observability data. Ensure the name of your LLM application is set via `DD_LLMOBS_ML_APP` or `LLMObs.enable(ml_app='...')`before running your application.z3Starting LLMObs span: %s, span_kind: %s, ml_app: %s)r;  tracer   r^  r\  r  rX   rL   rM   r   rW   r   rn  _set_ctx_itemsr*   rK   r,  r  )	r   r  rD  r  r  r  r  r  r  r   r   r   _start_span  s4   
zLLMObs._start_spanc              	   C   F   | j du r
tt |du rd}|du rd}| jjd||||||dS )a  
        Trace an invocation call to an LLM where inputs and outputs are represented as text.

        :param str model_name: The name of the invoked LLM. If not provided, a default value of "custom" will be set.
        :param str name: The name of the traced operation. If not provided, a default value of "llm" will be set.
        :param str model_provider: The name of the invoked LLM provider (ex: openai, bedrock).
                                   If not provided, a default value of "custom" will be set.
        :param str session_id: The ID of the underlying user session. Required for tracking sessions.
        :param str ml_app: The name of the ML application that the agent is orchestrating. If not provided, the default
                           value will be set to the value of `DD_LLMOBS_ML_APP`.

        :returns: The Span object representing the traced operation.
        FNr  r
  r  r  r  r  r  r\  r,  r-  rZ   r  r  r  r  rD  r  r  r  r  r   r   r   r
  2  s   

z
LLMObs.llmc                 C   *   | j du r
tt | jjd||||dS )a(  
        Trace a call to an external interface or API.

        :param str name: The name of the traced operation. If not provided, a default value of "tool" will be set.
        :param str session_id: The ID of the underlying user session. Required for tracking sessions.
        :param str ml_app: The name of the ML application that the agent is orchestrating. If not provided, the default
                           value will be set to the value of `DD_LLMOBS_ML_APP`.

        :returns: The Span object representing the traced operation.
        FtoolrD  r  r  r  r  r  rD  r  r  r  r   r   r   r  Y     

zLLMObs.toolc                 C   r  )aK  
        Trace a standalone non-LLM operation which does not involve an external request.

        :param str name: The name of the traced operation. If not provided, a default value of "task" will be set.
        :param str session_id: The ID of the underlying user session. Required for tracking sessions.
        :param str ml_app: The name of the ML application that the agent is orchestrating. If not provided, the default
                           value will be set to the value of `DD_LLMOBS_ML_APP`.

        :returns: The Span object representing the traced operation.
        Fr   r  r  r  r   r   r   r   u  r   zLLMObs.taskc                 C   r  )aj  
        Trace a dynamic workflow in which an embedded language model (agent) decides what sequence of actions to take.

        :param str name: The name of the traced operation. If not provided, a default value of "agent" will be set.
        :param str session_id: The ID of the underlying user session. Required for tracking sessions.
        :param str ml_app: The name of the ML application that the agent is orchestrating. If not provided, the default
                           value will be set to the value of `DD_LLMOBS_ML_APP`.

        :returns: The Span object representing the traced operation.
        Fr  r  r  r  r   r   r   r    r   zLLMObs.agentc                 C   r  )a3  
        Trace a predefined or static sequence of operations.

        :param str name: The name of the traced operation. If not provided, a default value of "workflow" will be set.
        :param str session_id: The ID of the underlying user session. Required for tracking sessions.
        :param str ml_app: The name of the ML application that the agent is orchestrating. If not provided, the default
                           value will be set to the value of `DD_LLMOBS_ML_APP`.

        :returns: The Span object representing the traced operation.
        Fworkflowr  r  r  r   r   r   r    r   zLLMObs.workflowc              	   C   r  )a  
        Trace a call to an embedding model or function to create an embedding.

        :param str model_name: The name of the invoked embedding model.
                               If not provided, a default value of "custom" will be set.
        :param str name: The name of the traced operation. If not provided, a default value of "embedding" will be set.
        :param str model_provider: The name of the invoked LLM provider (ex: openai, bedrock).
                                   If not provided, a default value of "custom" will be set.
        :param str session_id: The ID of the underlying user session. Required for tracking sessions.
        :param str ml_app: The name of the ML application that the agent is orchestrating. If not provided, the default
                           value will be set to the value of `DD_LLMOBS_ML_APP`.

        :returns: The Span object representing the traced operation.
        FNr  r  r  r  r  r   r   r   r    s   

zLLMObs.embeddingc                 C   r  )al  
        Trace a vector search operation involving a list of documents being returned from an external knowledge base.

        :param str name: The name of the traced operation. If not provided, a default value of "workflow" will be set.
        :param str session_id: The ID of the underlying user session. Required for tracking sessions.
        :param str ml_app: The name of the ML application that the agent is orchestrating. If not provided, the default
                           value will be set to the value of `DD_LLMOBS_ML_APP`.

        :returns: The Span object representing the traced operation.
        Fr  r  r  r  r   r   r   r    r   zLLMObs.retrievalr  r  r  c                 C   s   | j du r
tt | jjd|||d}|r|jt| |r&|jt	| |dur1|jt
| |r:|jt| |	rC|jt|	 |rL|jt| |
rU|jt|
 |S )a  
        Trace an LLM experiment, only used internally by the experiments SDK.
        :param str name: The name of the traced operation. If not provided, a default value of "agent" will be set.
        :param str session_id: The ID of the underlying user session. Required for tracking sessions.
        :param str ml_app: The name of the ML application that the agent is orchestrating. If not provided, the default
                           value will be set to the value of `DD_LLMOBS_ML_APP`.
        :param str experiment_id: The ID of the experiment to associate with this span and its children.
        :returns: The Span object representing the traced operation.
        Fr&  )rD  r  r  N)r\  r,  r-  rZ   r  r  r0  r  r8   r<   r=   r6   r:   r;   r9   )r  rD  r  r  r  r  r  r  r  r  r  r  r   r   r   _experiment	  s$   

zLLMObs._experimentr   r   r   r  r!  r  r  c              
   C   s  d}z[|du r| j  }|du rd}td|jtjkr#d}td|jr,d}td|durBt|t	s;d}td	| 
|t| |durct|t	rVtd
d | D s\d}td| 
|t| |durt|t	srd}td|d}|r|tt| | 
|t| |durt|}|r|t| |t}|	dur|	|_|durzt|dd}| 
|t| | 
|ttti W n ttfy } z	d}tdt|d}~ww |s|stdd}|dus|dur0|dkr| j|||d\}}n6|dkr
| j |||d\}}n&|dkr| j!|||d\}}n|dkr(| j"|||d n| j#|||d |
rOt|
t$rO|
D ]}t%||dd|d dd!d" q;|rVt|W t&'|| dS t&'|| w )#aT  
        Sets metadata, inputs, outputs, tags, and metrics as provided for a given LLMObs span.
        Note that with the exception of tags, this method will override any existing values for the provided fields.

        :param Span span: Span to annotate. If no span is provided, the current active span will be used.
                          Must be an LLMObs-type span, i.e. generated by the LLMObs SDK.
        :param prompt: A dictionary that represents the prompt used for an LLM call in the following form:
                        `{
                            "id": "...",
                            "template": "...",
                            "chat_template": [{"content": "...", "role": "..."}, ...])
                            "version": "...",
                            "variables": {"variable_1": "...", ...},
                            tags": {"tag_1": "...", ...},
                        }`.
                        Can also be set using the `ddtrace.llmobs.utils.Prompt` constructor class.
                        - This argument is only applicable to LLM spans.
                        - The dictionary may contain two optional keys relevant to RAG applications:
                            `rag_context_variables` - a list of variable key names that contain ground
                                                        truth context information
                            `rag_query_variables` - a list of variable key names that contains query
                                                        information for an LLM call
        :param input_data: A single input string, dictionary, or a list of dictionaries based on the span kind:
                           - llm spans: accepts a string, or a dictionary of form {"content": "...", "role": "...",
                                        "tool_calls": ..., "tool_results": ...}, where "tool_calls" are an optional
                                        list of tool call dictionaries with required keys: "name", "arguments", and
                                        optional keys: "tool_id", "type", and "tool_results" are an optional list of
                                        tool result dictionaries with required key: "result", and optional keys:
                                        "name", "tool_id", "type" for function calling scenarios.
                           - embedding spans: accepts a string, list of strings, or a dictionary of form
                                              {"text": "...", ...} or a list of dictionaries with the same signature.
                           - other: any JSON serializable type.
        :param output_data: A single output string, dictionary, or a list of dictionaries based on the span kind:
                           - llm spans: accepts a string, or a dictionary of form {"content": "...", "role": "...",
                                        "tool_calls": ...}, where "tool_calls" are an optional list of tool call
                                        dictionaries with required keys: "name", "arguments", and optional keys:
                                        "tool_id", "type" for function calling scenarios.
                           - retrieval spans: a dictionary containing any of the key value pairs
                                              {"name": str, "id": str, "text": str, "score": float},
                                              or a list of dictionaries with the same signature.
                           - other: any JSON serializable type.
        :param metadata: Dictionary of JSON serializable key-value metadata pairs relevant to the input/output operation
                         described by the LLMObs span.
        :param tags: Dictionary of JSON serializable key-value tag pairs to set or update on the LLMObs span
                     regarding the span's context.
        :param tool_definitions: list of tool definition dictionaries for tool calling scenarios.
                            - This argument is only applicable to LLM spans.
                            - Each tool definition is a dictionary containing a required "name" (string),
                                   and optional "description" (string) and "schema" (JSON serializable dictionary) keys.
        :param metrics: Dictionary of JSON serializable key-value metric pairs,
                        such as `{prompt,completion,total}_tokens`.
        Ninvalid_span_no_active_spanszNo span provided and no active LLMObs-generated span found. Ensure you pass the span explicitly using LLMObs.annotate(span=<your_span>, ...) when annotating from a different thread or async task than where the span was created.invalid_span_typer  invalid_finished_spanz Cannot annotate a finished span.invalid_metadatazmetadata must be a dictionaryc                 s   s    | ]
}t |ttfV  qd S r   )r   r  rM  )r   r  r   r   r   r   	  s    z"LLMObs.annotate.<locals>.<genexpr>invalid_metricszAmetrics must be a dictionary of string key - numeric value pairs.invalid_tagszEspan tags must be a dictionary of string key - primitive value pairs.r  F)strict_validationinvalid_promptz%Failed to validate prompt with error:zBSpan kind not specified, skipping annotation for input/output datar
  )r  r  r  )input_documentsoutput_textr  )
input_textoutput_documentsr&  )r  r  r~  r  r  r   r   )(r  r  r   r]  r   r^  finishedr   r   r   _set_dict_attributerI   r   r  rJ   r   r  rW   r   r[   r   r\   rk  rX   rD  r   rB   rR   rD   rn  r   _tag_llm_io_tag_embedding_io_tag_retrieval_io_tag_freeform_io_tag_text_ior   r   r`  record_llmobs_annotate)r  r  r%  r   r   r   r  r  r!  r  r  r  r#  r  validated_tool_definitionsr   validated_promptrx  annotation_error_messagelinked_spanr   r   r   r  @	  s   C

 







zLLMObs.annotatec                 C   s   |dur$zt |tst|}|jr|t|j W n
 ty#   Y dS w |du r*dS zt |ts4t|}|js:W dS |t|j W dS  tyM   Y dS w )zTags input/output messages for LLM-kind spans.
        Will be mapped to span's `meta.{input,output}.messages` fields.
        N)zFailed to parse input messages.invalid_io_messagesNN)z Failed to parse output messages.r  )r   r   r  r  rA   r   rO   )r  r  r  r  r   r   r   r  	  s,   

zLLMObs._tag_llm_ioc                 C   sh   |dur$zt |tst|}|jr|t|j W n
 ty#   Y dS w |du r*dS |tt| dS )zTags input documents and output text for embedding-kind spans.
        Will be mapped to span's `meta.{input,output}.text` fields.
        N)z Failed to parse input documents.invalid_embedding_ior  )r   r   r  r  r@   r   rP   r   )r  r  r  r  r   r   r   r  	  s   
zLLMObs._tag_embedding_ioc                 C   sl   |dur| tt| |du rdS zt|tst|}|js"W dS | t|j W dS  ty5   Y dS w )zTags input text and output documents for retrieval-kind spans.
        Will be mapped to span's `meta.{input,output}.text` fields.
        Nr  )z!Failed to parse output documents.invalid_retrieval_io)r  rC   r   r   r   r  rN   r   )r  r  r  r  r   r   r   r  
  s   
zLLMObs._tag_retrieval_ioc                 C   s8   |dur| tt| |dur| tt| dS dS )zTags input/output values for non-LLM kind spans.
        Will be mapped to span's `meta.{input,output}.values` fields.
        N)r  rC   r   rP   r  r  r  r  r   r   r   r  "
  s
   zLLMObs._tag_text_ioc                 C   s0   |dur
| t| |dur| t| dS dS )a	  Tags input/output values for experient spans.
        Will be mapped to span's `meta.{input,output}` fields.
        this is meant to be non restrictive on user's data, experiments allow
        arbitrary structured or non structured IO values in its spans
        N)r  r>   r?   r  r   r   r   r  ,
  s
   zLLMObs._tag_freeform_ior  c                 C   s(   |  |pi }|| | || dS )zSets a given LLM Obs span attribute with a dictionary key/values.
        If the attribute is already set on the span, the new dict with be merged with the existing
        dict.
        N)rk  r  r  )r  r   r  existing_valuer   r   r   r  8
  s   
zLLMObs._set_dict_attributemetric_typespan_with_tag_valuetimestamp_ms
assessment	reasoningc                 C   s|  | j du rtdd dS d}i }z|du|duA }|s#d}td|durGt|tr<t|dtr<t|dtsBd	}td
||d< n,|durst|tr`t|dtr`t|dtsfd}td|d|dd|d< |rw|nt	t

 d }t|t	r|dk rd}td|sd}tdd|v rd}td| }|dvrd}td|dkrt|tsd}td |d!krt|t	tfsd}td"|d#krt|tsd}td$|d%krt|tsd}td&|durt|tstd'|r|ntj}|s	d(}td)t|d*}|r3| D ]\}}zt||t|< W q ty2   d+}td,w tjr;d-|d.< d/|d0t|d1|d2|d3||d4|d5d6d7 | D i}|
rot|
tre|
d8vrkd9}td:|
|d;< |rt|ts~d<}td=||d>< |	rt|	tsd?}td@t|	}	|	rt|	trt|	|dA< | jj| W t||| dS t||| w )Ba  
        Submits a custom evaluation metric for a given span.

        :param str label: The name of the evaluation metric.
        :param str metric_type: The type of the evaluation metric. One of "categorical", "score", "boolean".
        :param value: The value of the evaluation metric.
                      Must be a string (categorical), integer (score), float (score), or boolean (boolean).
        :param dict span: A dictionary of shape {'span_id': str, 'trace_id': str} uniquely identifying
                            the span associated with this evaluation.
        :param dict span_with_tag_value: A dictionary with the format {'tag_key': str, 'tag_value': str}
                            uniquely identifying the span associated with this evaluation.
        :param tags: A dictionary of string key-value pairs to tag the evaluation metric with.
        :param str ml_app: The name of the ML application
        :param int timestamp_ms: The unix timestamp in milliseconds when the evaluation metric result was generated.
                                    If not set, the current time will be used.
        :param dict metadata: A JSON serializable dictionary of key-value metadata pairs relevant to the
                                evaluation metric.
        :param str assessment: An assessment of this evaluation. Must be either "pass" or "fail".
        :param str reasoning: An explanation of the evaluation result.
        Fz>LLMObs.submit_evaluation() called when LLMObs is not enabled. z(Evaluation metric data will not be sent.N&provided_both_span_and_tag_joining_keyz`Exactly one of `span` or `span_with_tag_value` must be specified to submit an evaluation metric.r~  r  r  z`span` must be a dictionary containing both span_id and trace_id keys. LLMObs.export_span() can be used to generate this dictionary from a given span.r  tag_key	tag_valueinvalid_joining_keyza`span_with_tag_value` must be a dict with keys 'tag_key' and 'tag_value' containing string values)r   r  tagi  r   invalid_timestampzTtimestamp_ms must be a non-negative integer. Evaluation metric data will not be sentinvalid_metric_labelz:label must be the specified name of the evaluation metric..invalid_label_valuez#label value must not contain a '.'.)categoricalscorebooleanjsoninvalid_metric_typezHmetric_type must be one of 'categorical', 'score', 'boolean', or 'json'.r/  invalid_metric_valuez0value must be a string for a categorical metric.r0  z5value must be an integer or float for a score metric.r1  z-value must be a boolean for a boolean metric.r2  z'value must be a dict for a json metric.z4tags must be a dictionary of string key-value pairs.missing_ml_appzML App name is required for sending evaluation metrics. Evaluation metric data will not be sent. Ensure this configuration is set before running your application.)r  r  r  zBFailed to parse tags. Tags for evaluation metrics must be strings.otelr  join_onr  r!  r#  z{}_valuer  r  c                 S   r  r  r  r  r   r   r   r  
  r  z,LLMObs.submit_evaluation.<locals>.<listcomp>)passfailinvalid_assessmentzGFailed to parse assessment. assessment must be either 'pass' or 'fail'.r$  invalid_reasoningz6Failed to parse reasoning. reasoning must be a string.r%  r  z.metadata must be json serializable dictionary.r   )r\  r,  r  rn  r   r   r   r   r   r  r  r)  rM  boolr   r   r  r   r  r   _otel_trace_enabledr   r   r2  loadsr  rQ  rl  r`  record_llmobs_submit_evaluation)r  r  r!  r  r  r"  r  r  r#  r   r$  r%  r#  r7  has_exactly_one_joining_keyevaluation_tagsr  r  evaluation_metricr   r   r   submit_evaluationB
  s   
#



"zLLMObs.submit_evaluationspan_contextrequest_headersc                 C   s   | j du rd S | jj }t|tr|jn|}|d ur t|jnt	}d }t|tr4|
t}|
t}n|d urK|jtp@tj}|jtpIt }ntj}t }||jt< t||jt< |d urh||jt< d S d S r  )r\  r  rJ  r  r   r   r0  r   r~  rV   rk  rK   rG   r  r   rT   r   r  rS   r   rU   )r  rD  rE  active_spanactive_contextr  r  r  r   r   r   r  
  s&   



zLLMObs._inject_llmobs_contextc              	   C   s   | j du rtd |S d}z7t|tsd}td|du r$| jj }|du r.d}tdt|t	s7tdt
|j| |W t| S t| w )	zFInjects the span's distributed context into the given request headers.FzpLLMObs.inject_distributed_headers() called when LLMObs is not enabled. Distributed context will not be injected.Ninvalid_request_headersz?request_headers must be a dictionary of string key-value pairs.r  z4No span provided and no currently active span found.zKspan must be a valid Span object. Distributed context will not be injected.)r\  r,  r-  r   r   r   r  r;  current_spanr   r   injectr0  r`  !record_inject_distributed_headers)r  rE  r  r#  r   r   r   inject_distributed_headers  s0   


z!LLMObs.inject_distributed_headersr0  c                 C   s   | j ||dd d S )NT
_soft_fail)$_activate_llmobs_distributed_contextr  rE  r0  r   r   r   r  4  s   z5LLMObs._activate_llmobs_distributed_context_soft_failrN  c           	   	   C   sh  d }z| j du rW t| d S |jr|js+d}|r'td W t| d S td|j	t
}|d u rDd}td W t| d S zt|}W n tya   d}td Y W t| d S w |j	t}|d u rtd t|j|d	}t|j|jt< | jj| d
}W t| d S t|j|d	}t||jt< | jj| W t| d S t| w )NFmissing_contextz5Failed to extract trace/span ID from request headers.missing_parent_idz8Failed to extract LLMObs parent ID from request headers.invalid_parent_idz6Failed to parse LLMObs parent ID from request headers.zFailed to extract LLMObs trace ID from request headers. Expected string, got None. Defaulting to the corresponding APM trace ID.)r  r~  missing_parent_llmobs_trace_id)r\  r`  #record_activate_distributed_headersr  r~  r,  r-  r   r  r   rU   r  r  rn  rS   r   r   r  rJ  r  )	r  rE  r0  rN  r#  
_parent_idr  r  llmobs_contextr   r   r   rO  8  sR   
!


z+LLMObs._activate_llmobs_distributed_contextc                 C   sH   | j du rtd dS t|}| jjj| | jj	||dd dS )z
        Activates distributed tracing headers for the current request.

        :param request_headers: A dictionary containing the headers for the current request.
        FzsLLMObs.activate_distributed_headers() called when LLMObs is not enabled. Distributed context will not be activated.NrM  )
r\  r,  r-  r   extractr  r;  r  r  rO  rP  r   r   r   activate_distributed_headersb  s   

z#LLMObs.activate_distributed_headersr  )NFN)r   N)NTNNNNNNNNNNF)NNN)Nr  NFT)NNrC  r  NT)NNrn  NFN)r  NNNNr|  )r  NNNr|  )r|  Fr   Nr   )NNNN)TT)NNNNNF)NNNF)
NNNNNNNNNN)NNNNNNNNNNF)NNNNNNNN)F)}r   r   r   r  r\  rN  rO  r8  r   r   r+   r:  r   r   r   r   rH  r   rd  rg  re  r   rj  r   ry  rp  rq  staticmethodr<  r   r  rb  rU  r  r  classmethodsetr  r  r   r   r  r  rd   r-  r  rg   r0  rh   rB  rj  rm  ri   rf   rm   r   rj   ro   rM  r	   r   rv   r{  rq   rp   r&  ra   r_   r`   rk   r  r  rl   r  r  r  r  r  r   r   r   r  r  r   r"   r  r  r   r   r}   r  r  r  r  r  r  r  r  r   r  r_  r  r
  r  r   r  r  r  r  r  r  r  r  r  r  r  r  objectrC  r  rL  r  rO  rY  __classcell__r   r   rZ  r   r6    s2  
 &C DH


,
	
 !


;

	
G	
  '	

F	
F		

(
\	
+
	
*&'	
2	

 $"""	 		
 9 . 
)$r6  )r   rZ  dataclassesr   r   r   r2  r9  rN  r  r  typingr   r   r   r   r   r	   r
   urllib.parser&  rI  r   r   ddtrace._trace.apm_filterr   ddtrace._trace.contextr   ddtrace._trace.spanr   ddtrace._trace.tracerr   ddtrace.constantsr   r   r   ddtrace.extr   ddtrace.internalr   r   r   ddtrace.internal.compatr   ddtrace.internal.loggerr   ddtrace.internal.nativer   r   $ddtrace.internal.remoteconfig.workerr   ddtrace.internal.servicer   r   ddtrace.internal.telemetryr   r  r    $ddtrace.internal.telemetry.constantsr!   ddtrace.internal.threadsr"   ddtrace.internal.utils.formatsr#   r$   r%   ddtrace.llmobsr&   r  r'   r`  ddtrace.llmobs._constantsr(   r)   r*   r+   r,   r-   r.   r/   r0   r1   r2   r3   r4   r5   r6   r7   r8   r9   r:   r;   r<   r=   r>   r?   r@   rA   rB   rC   rD   rE   rF   rG   rH   rI   rJ   rK   rL   rM   rN   rO   rP   rQ   rR   rS   rT   rU   rV   rW   rX   rY   rZ   r[   r\   ddtrace.llmobs._contextr]   !ddtrace.llmobs._evaluators.runnerr^   ddtrace.llmobs._experimentr_   r`   ra   rb   rc   rd   re   rf   rg   rh   ri   rj   rk   rl   rm   rn   ro   rp   rq   rr   rs   rt   ru   #ddtrace.llmobs._prompt_optimizationrv   rw   rx   ry   rz   r{   r|   ddtrace.llmobs._promptsr}   ddtrace.llmobs._prompts.cacher~   ddtrace.llmobs._prompts.managerr   ddtrace.llmobs._utilsr   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   ddtrace.llmobs._writerr   r   r   r   r   r   r   ddtrace.llmobs.typesr   r   r   r   r   r   r   r   ddtrace.llmobs.utilsr   r   r   ddtrace.propagation.httpr   ddtrace.versionr   r   r,  r  r   r   r   r<  r   r   r   rv  r   r   r   r   r   r   r   r   r  r5  r6  r  r   r   r   r   <module>   s$   	

%
4                   Z