3737 errAgentlessRequiresAPIKey = errors .New ("LLMOBs agentless mode requires a valid API key - set the DD_API_KEY env variable to configure one" )
3838 errMLAppRequired = errors .New ("ML App is required for sending LLM Observability data" )
3939 errAgentModeNotSupported = errors .New ("DD_LLMOBS_AGENTLESS_ENABLED has been configured to false but the agent is not available or does not support LLMObs" )
40+ errInvalidMetricLabel = errors .New ("label is required for evaluation metrics" )
41+ errFinishedSpan = errors .New ("span is already finished" )
42+ errEvalJoinBothPresent = errors .New ("provide either span/trace IDs or tag key/value, not both" )
43+ errEvalJoinNonePresent = errors .New ("must provide either span/trace IDs or tag key/value for joining" )
44+ errInvalidSpanJoin = errors .New ("both span and trace IDs are required for span-based joining" )
45+ errInvalidTagJoin = errors .New ("both tag key and value are required for tag-based joining" )
4046)
4147
4248const (
@@ -182,7 +188,11 @@ func newLLMObs(cfg *config.Config, tracer Tracer) (*LLMObs, error) {
182188
183189// Start starts the global LLMObs instance with the given configuration and tracer.
184190// Returns an error if LLMObs is already running or if configuration is invalid.
185- func Start (cfg config.Config , tracer Tracer ) error {
191+ func Start (cfg config.Config , tracer Tracer ) (err error ) {
192+ startTime := time .Now ()
193+ defer func () {
194+ trackLLMObsStart (startTime , err , cfg )
195+ }()
186196 mu .Lock ()
187197 defer mu .Unlock ()
188198
@@ -224,6 +234,7 @@ func ActiveLLMObs() (*LLMObs, error) {
224234func Flush () {
225235 if activeLLMObs != nil {
226236 activeLLMObs .Flush ()
237+ trackUserFlush ()
227238 }
228239}
229240
@@ -383,6 +394,7 @@ func (l *LLMObs) batchSend(params batchSendParams) {
383394 }
384395 if err := l .Transport .PushSpanEvents (ctx , events ); err != nil {
385396 log .Error ("llmobs: failed to push span events: %v" , err .Error ())
397+ trackDroppedPayload (len (events ), telemetryMetricDroppedSpanEvents , "transport_error" )
386398 } else {
387399 log .Debug ("llmobs: push span events success" )
388400 }
@@ -403,6 +415,7 @@ func (l *LLMObs) batchSend(params batchSendParams) {
403415 }
404416 if err := l .Transport .PushEvalMetrics (ctx , metrics ); err != nil {
405417 log .Error ("llmobs: failed to push eval metrics: %v" , err .Error ())
418+ trackDroppedPayload (len (metrics ), telemetryMetricDroppedEvalEvents , "transport_error" )
406419 } else {
407420 log .Debug ("llmobs: push eval metrics success" )
408421 }
@@ -583,20 +596,34 @@ func (l *LLMObs) llmobsSpanEvent(span *Span) *transport.LLMObsSpanEvent {
583596 Scope : span .scope ,
584597 }
585598 if b , err := json .Marshal (ev ); err == nil {
586- if len (b ) > sizeLimitEVPEvent {
599+ rawSize := len (b )
600+ trackSpanEventRawSize (ev , rawSize )
601+
602+ truncated := false
603+ if rawSize > sizeLimitEVPEvent {
587604 log .Warn (
588605 "llmobs: dropping llmobs span event input/output because its size (%s) exceeds the event size limit (5MB)" ,
589- readableBytes (len ( b ) ),
606+ readableBytes (rawSize ),
590607 )
591- dropSpanEventIO (ev )
608+ truncated = dropSpanEventIO (ev )
609+ if ! truncated {
610+ log .Debug ("llmobs: attempted to drop span event IO but it was not present" )
611+ }
612+ }
613+ actualSize := rawSize
614+ if truncated {
615+ if b , err := json .Marshal (ev ); err == nil {
616+ actualSize = len (b )
617+ }
592618 }
619+ trackSpanEventSize (ev , actualSize , truncated )
593620 }
594621 return ev
595622}
596623
597- func dropSpanEventIO (ev * transport.LLMObsSpanEvent ) {
624+ func dropSpanEventIO (ev * transport.LLMObsSpanEvent ) bool {
598625 if ev == nil {
599- return
626+ return false
600627 }
601628 droppedIO := false
602629 if _ , ok := ev .Meta ["input" ]; ok {
@@ -612,11 +639,14 @@ func dropSpanEventIO(ev *transport.LLMObsSpanEvent) {
612639 } else {
613640 log .Debug ("llmobs: attempted to drop span event IO but it was not present" )
614641 }
642+ return droppedIO
615643}
616644
617645// StartSpan starts a new LLMObs span with the given kind, name, and configuration.
618646// Returns the created span and a context containing the span.
619647func (l * LLMObs ) StartSpan (ctx context.Context , kind SpanKind , name string , cfg StartSpanConfig ) (* Span , context.Context ) {
648+ defer trackSpanStarted ()
649+
620650 spanName := name
621651 if spanName == "" {
622652 spanName = string (kind )
@@ -692,20 +722,38 @@ func (l *LLMObs) StartExperimentSpan(ctx context.Context, name string, experimen
692722
693723// SubmitEvaluation submits an evaluation metric for a span.
694724// The span can be identified either by span/trace IDs or by tag key-value pairs.
695- func (l * LLMObs ) SubmitEvaluation (cfg EvaluationConfig ) error {
696- // Validate exactly one join method is provided
697- hasSpanJoin := cfg .SpanID != "" && cfg .TraceID != ""
698- hasTagJoin := cfg .TagKey != "" && cfg .TagValue != ""
725+ func (l * LLMObs ) SubmitEvaluation (cfg EvaluationConfig ) (err error ) {
726+ var metric * transport.LLMObsMetric
727+ defer func () {
728+ trackSubmitEvaluationMetric (metric , err )
729+ }()
699730
731+ if cfg .Label == "" {
732+ return errInvalidMetricLabel
733+ }
734+ var (
735+ hasTagJoin bool
736+ hasSpanJoin bool
737+ )
738+ if cfg .SpanID != "" || cfg .TraceID != "" {
739+ if ! (cfg .SpanID != "" && cfg .TraceID != "" ) {
740+ return errInvalidSpanJoin
741+ }
742+ hasSpanJoin = true
743+ }
744+ if cfg .TagKey != "" || cfg .TagValue != "" {
745+ if ! (cfg .TagKey != "" && cfg .TagValue != "" ) {
746+ return errInvalidTagJoin
747+ }
748+ hasTagJoin = true
749+ }
700750 if hasSpanJoin && hasTagJoin {
701- return errors . New ( "provide either span/trace IDs or tag key/value, not both" )
751+ return errEvalJoinBothPresent
702752 }
703753 if ! hasSpanJoin && ! hasTagJoin {
704- return errors .New ("must provide either span/trace IDs or tag key/value for joining" )
705- }
706- if cfg .Label == "" {
707- return errors .New ("label is required for evaluation metrics" )
754+ return errEvalJoinNonePresent
708755 }
756+
709757 numValues := 0
710758 if cfg .CategoricalValue != nil {
711759 numValues ++
@@ -751,7 +799,7 @@ func (l *LLMObs) SubmitEvaluation(cfg EvaluationConfig) error {
751799 }
752800 tags = append (tags , fmt .Sprintf ("ddtrace.version:%s" , version .Tag ))
753801
754- metric : = & transport.LLMObsMetric {
802+ metric = & transport.LLMObsMetric {
755803 JoinOn : joinOn ,
756804 Label : cfg .Label ,
757805 MLApp : mlApp ,
0 commit comments