@@ -201,7 +201,7 @@ class LLMObs extends NoopLLMObs {
201
201
return this . _tracer . wrap ( name , spanOptions , wrapped )
202
202
}
203
203
204
- annotate ( span , options ) {
204
+ annotate ( span , options , autoinstrumented = false ) {
205
205
if ( ! this . enabled ) return
206
206
207
207
if ( ! span ) {
@@ -213,150 +213,184 @@ class LLMObs extends NoopLLMObs {
213
213
span = this . _active ( )
214
214
}
215
215
216
- if ( ! span ) {
217
- throw new Error ( 'No span provided and no active LLMObs-generated span found' )
218
- }
219
- if ( ! options ) {
220
- throw new Error ( 'No options provided for annotation.' )
221
- }
216
+ let err = ''
222
217
223
- if ( ! LLMObsTagger . tagMap . has ( span ) ) {
224
- throw new Error ( 'Span must be an LLMObs-generated span' )
225
- }
226
- if ( span . _duration !== undefined ) {
227
- throw new Error ( 'Cannot annotate a finished span' )
228
- }
218
+ try {
219
+ if ( ! span ) {
220
+ err = 'invalid_span_no_active_spans'
221
+ throw new Error ( 'No span provided and no active LLMObs-generated span found' )
222
+ }
223
+ if ( ! options ) {
224
+ err = 'invalid_options'
225
+ throw new Error ( 'No options provided for annotation.' )
226
+ }
229
227
230
- const spanKind = LLMObsTagger . tagMap . get ( span ) [ SPAN_KIND ]
231
- if ( ! spanKind ) {
232
- throw new Error ( 'LLMObs span must have a span kind specified' )
233
- }
228
+ if ( ! LLMObsTagger . tagMap . has ( span ) ) {
229
+ err = 'invalid_span_type'
230
+ throw new Error ( 'Span must be an LLMObs-generated span' )
231
+ }
232
+ if ( span . _duration !== undefined ) {
233
+ err = 'invalid_finished_span'
234
+ throw new Error ( 'Cannot annotate a finished span' )
235
+ }
234
236
235
- const { inputData, outputData, metadata, metrics, tags } = options
236
-
237
- if ( inputData || outputData ) {
238
- if ( spanKind === 'llm' ) {
239
- this . _tagger . tagLLMIO ( span , inputData , outputData )
240
- } else if ( spanKind === 'embedding' ) {
241
- this . _tagger . tagEmbeddingIO ( span , inputData , outputData )
242
- } else if ( spanKind === 'retrieval' ) {
243
- this . _tagger . tagRetrievalIO ( span , inputData , outputData )
244
- } else {
245
- this . _tagger . tagTextIO ( span , inputData , outputData )
237
+ const spanKind = LLMObsTagger . tagMap . get ( span ) [ SPAN_KIND ]
238
+ if ( ! spanKind ) {
239
+ err = 'invalid_no_span_kind'
240
+ throw new Error ( 'LLMObs span must have a span kind specified' )
246
241
}
247
- }
248
242
249
- if ( metadata ) {
250
- this . _tagger . tagMetadata ( span , metadata )
251
- }
243
+ const { inputData, outputData, metadata, metrics, tags } = options
252
244
253
- if ( metrics ) {
254
- this . _tagger . tagMetrics ( span , metrics )
255
- }
245
+ if ( inputData || outputData ) {
246
+ if ( spanKind === 'llm' ) {
247
+ this . _tagger . tagLLMIO ( span , inputData , outputData )
248
+ } else if ( spanKind === 'embedding' ) {
249
+ this . _tagger . tagEmbeddingIO ( span , inputData , outputData )
250
+ } else if ( spanKind === 'retrieval' ) {
251
+ this . _tagger . tagRetrievalIO ( span , inputData , outputData )
252
+ } else {
253
+ this . _tagger . tagTextIO ( span , inputData , outputData )
254
+ }
255
+ }
256
256
257
- if ( tags ) {
258
- this . _tagger . tagSpanTags ( span , tags )
257
+ if ( metadata ) {
258
+ this . _tagger . tagMetadata ( span , metadata )
259
+ }
260
+ if ( metrics ) {
261
+ this . _tagger . tagMetrics ( span , metrics )
262
+ }
263
+ if ( tags ) {
264
+ this . _tagger . tagSpanTags ( span , tags )
265
+ }
266
+ } catch ( e ) {
267
+ if ( e . ddErrorTag ) {
268
+ err = e . ddErrorTag
269
+ }
270
+ throw e
271
+ } finally {
272
+ if ( autoinstrumented === false ) {
273
+ telemetry . recordLLMObsAnnotate ( span , err )
274
+ }
259
275
}
260
276
}
261
277
262
278
exportSpan ( span ) {
263
279
span = span || this . _active ( )
264
-
265
- if ( ! span ) {
266
- throw new Error ( 'No span provided and no active LLMObs-generated span found' )
267
- }
268
-
269
- if ( ! ( span instanceof Span ) ) {
270
- throw new Error ( 'Span must be a valid Span object.' )
271
- }
272
-
273
- if ( ! LLMObsTagger . tagMap . has ( span ) ) {
274
- throw new Error ( 'Span must be an LLMObs-generated span' )
280
+ let err = ''
281
+ try {
282
+ if ( ! span ) {
283
+ err = 'no_active_span'
284
+ throw new Error ( 'No span provided and no active LLMObs-generated span found' )
285
+ }
286
+ if ( ! ( span instanceof Span ) ) {
287
+ err = 'invalid_span'
288
+ throw new Error ( 'Span must be a valid Span object.' )
289
+ }
290
+ if ( ! LLMObsTagger . tagMap . has ( span ) ) {
291
+ err = 'invalid_span'
292
+ throw new Error ( 'Span must be an LLMObs-generated span' )
293
+ }
294
+ } catch ( e ) {
295
+ telemetry . recordExportSpan ( span , err )
296
+ throw e
275
297
}
276
-
277
298
try {
278
299
return {
279
300
traceId : span . context ( ) . toTraceId ( true ) ,
280
301
spanId : span . context ( ) . toSpanId ( )
281
302
}
282
303
} catch {
283
- logger . warn ( 'Faild to export span. Span must be a valid Span object.' )
304
+ err = 'invalid_span'
305
+ logger . warn ( 'Failed to export span. Span must be a valid Span object.' )
306
+ } finally {
307
+ telemetry . recordExportSpan ( span , err )
284
308
}
285
309
}
286
310
287
311
submitEvaluation ( llmobsSpanContext , options = { } ) {
288
312
if ( ! this . enabled ) return
289
313
314
+ let err = ''
290
315
const { traceId, spanId } = llmobsSpanContext
291
- if ( ! traceId || ! spanId ) {
292
- throw new Error (
293
- 'spanId and traceId must both be specified for the given evaluation metric to be submitted.'
294
- )
295
- }
296
-
297
- const mlApp = options . mlApp || this . _config . llmobs . mlApp
298
- if ( ! mlApp ) {
299
- throw new Error (
300
- 'ML App name is required for sending evaluation metrics. Evaluation metric data will not be sent.'
301
- )
302
- }
303
-
304
- const timestampMs = options . timestampMs || Date . now ( )
305
- if ( typeof timestampMs !== 'number' || timestampMs < 0 ) {
306
- throw new Error ( 'timestampMs must be a non-negative integer. Evaluation metric data will not be sent' )
307
- }
316
+ try {
317
+ if ( ! traceId || ! spanId ) {
318
+ err = 'invalid_span'
319
+ throw new Error (
320
+ 'spanId and traceId must both be specified for the given evaluation metric to be submitted.'
321
+ )
322
+ }
323
+ const mlApp = options . mlApp || this . _config . llmobs . mlApp
324
+ if ( ! mlApp ) {
325
+ err = 'missing_ml_app'
326
+ throw new Error (
327
+ 'ML App name is required for sending evaluation metrics. Evaluation metric data will not be sent.'
328
+ )
329
+ }
308
330
309
- const { label, value, tags } = options
310
- const metricType = options . metricType ?. toLowerCase ( )
311
- if ( ! label ) {
312
- throw new Error ( 'label must be the specified name of the evaluation metric' )
313
- }
314
- if ( ! metricType || ! [ 'categorical' , 'score' ] . includes ( metricType ) ) {
315
- throw new Error ( 'metricType must be one of "categorical" or "score"' )
316
- }
331
+ const timestampMs = options . timestampMs || Date . now ( )
332
+ if ( typeof timestampMs !== 'number' || timestampMs < 0 ) {
333
+ err = 'invalid_timestamp'
334
+ throw new Error ( 'timestampMs must be a non-negative integer. Evaluation metric data will not be sent' )
335
+ }
317
336
318
- if ( metricType === 'categorical' && typeof value !== 'string' ) {
319
- throw new Error ( 'value must be a string for a categorical metric.' )
320
- }
321
- if ( metricType === 'score' && typeof value !== 'number' ) {
322
- throw new Error ( 'value must be a number for a score metric.' )
323
- }
337
+ const { label, value, tags } = options
338
+ const metricType = options . metricType ?. toLowerCase ( )
339
+ if ( ! label ) {
340
+ err = 'invalid_metric_label'
341
+ throw new Error ( 'label must be the specified name of the evaluation metric' )
342
+ }
343
+ if ( ! metricType || ! [ 'categorical' , 'score' ] . includes ( metricType ) ) {
344
+ err = 'invalid_metric_type'
345
+ throw new Error ( 'metricType must be one of "categorical" or "score"' )
346
+ }
347
+ if ( metricType === 'categorical' && typeof value !== 'string' ) {
348
+ err = 'invalid_metric_value'
349
+ throw new Error ( 'value must be a string for a categorical metric.' )
350
+ }
351
+ if ( metricType === 'score' && typeof value !== 'number' ) {
352
+ err = 'invalid_metric_value'
353
+ throw new Error ( 'value must be a number for a score metric.' )
354
+ }
324
355
325
- const evaluationTags = {
326
- 'ddtrace.version' : tracerVersion ,
327
- ml_app : mlApp
328
- }
356
+ const evaluationTags = {
357
+ 'ddtrace.version' : tracerVersion ,
358
+ ml_app : mlApp
359
+ }
329
360
330
- if ( tags ) {
331
- for ( const key in tags ) {
332
- const tag = tags [ key ]
333
- if ( typeof tag === 'string' ) {
334
- evaluationTags [ key ] = tag
335
- } else if ( typeof tag . toString === 'function' ) {
336
- evaluationTags [ key ] = tag . toString ( )
337
- } else if ( tag == null ) {
338
- evaluationTags [ key ] = Object . prototype . toString . call ( tag )
339
- } else {
340
- // should be a rare case
341
- // every object in JS has a toString, otherwise every primitive has its own toString
342
- // null and undefined are handled above
343
- throw new Error ( 'Failed to parse tags. Tags for evaluation metrics must be strings' )
361
+ if ( tags ) {
362
+ for ( const key in tags ) {
363
+ const tag = tags [ key ]
364
+ if ( typeof tag === 'string' ) {
365
+ evaluationTags [ key ] = tag
366
+ } else if ( typeof tag . toString === 'function' ) {
367
+ evaluationTags [ key ] = tag . toString ( )
368
+ } else if ( tag == null ) {
369
+ evaluationTags [ key ] = Object . prototype . toString . call ( tag )
370
+ } else {
371
+ // should be a rare case
372
+ // every object in JS has a toString, otherwise every primitive has its own toString
373
+ // null and undefined are handled above
374
+ err = 'invalid_tags'
375
+ throw new Error ( 'Failed to parse tags. Tags for evaluation metrics must be strings' )
376
+ }
344
377
}
345
378
}
346
- }
347
379
348
- const payload = {
349
- span_id : spanId ,
350
- trace_id : traceId ,
351
- label,
352
- metric_type : metricType ,
353
- ml_app : mlApp ,
354
- [ `${ metricType } _value` ] : value ,
355
- timestamp_ms : timestampMs ,
356
- tags : Object . entries ( evaluationTags ) . map ( ( [ key , value ] ) => `${ key } :${ value } ` )
380
+ const payload = {
381
+ span_id : spanId ,
382
+ trace_id : traceId ,
383
+ label,
384
+ metric_type : metricType ,
385
+ ml_app : mlApp ,
386
+ [ `${ metricType } _value` ] : value ,
387
+ timestamp_ms : timestampMs ,
388
+ tags : Object . entries ( evaluationTags ) . map ( ( [ key , value ] ) => `${ key } :${ value } ` )
389
+ }
390
+ evalMetricAppendCh . publish ( payload )
391
+ } finally {
392
+ telemetry . recordSubmitEvaluation ( options , err )
357
393
}
358
-
359
- evalMetricAppendCh . publish ( payload )
360
394
}
361
395
362
396
flush ( ) {
@@ -375,7 +409,7 @@ class LLMObs extends NoopLLMObs {
375
409
annotations . outputData = output
376
410
}
377
411
378
- this . annotate ( span , annotations )
412
+ this . annotate ( span , annotations , true )
379
413
}
380
414
381
415
_active ( ) {
0 commit comments