dd-trace 5.43.0 → 5.45.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -4
- package/packages/datadog-instrumentations/src/cucumber.js +61 -23
- package/packages/datadog-instrumentations/src/dd-trace-api.js +7 -0
- package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
- package/packages/datadog-instrumentations/src/helpers/register.js +1 -1
- package/packages/datadog-instrumentations/src/jest.js +134 -48
- package/packages/datadog-instrumentations/src/mocha/main.js +20 -4
- package/packages/datadog-instrumentations/src/mocha/utils.js +89 -30
- package/packages/datadog-instrumentations/src/mocha/worker.js +3 -1
- package/packages/datadog-instrumentations/src/playwright.js +97 -17
- package/packages/datadog-instrumentations/src/router.js +1 -0
- package/packages/datadog-instrumentations/src/tedious.js +13 -10
- package/packages/datadog-instrumentations/src/vitest.js +77 -17
- package/packages/datadog-plugin-cucumber/src/index.js +24 -1
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +69 -20
- package/packages/datadog-plugin-cypress/src/support.js +39 -10
- package/packages/datadog-plugin-google-cloud-vertexai/src/index.js +8 -186
- package/packages/datadog-plugin-google-cloud-vertexai/src/tracing.js +186 -0
- package/packages/datadog-plugin-google-cloud-vertexai/src/utils.js +19 -0
- package/packages/datadog-plugin-jest/src/index.js +38 -5
- package/packages/datadog-plugin-mocha/src/index.js +28 -5
- package/packages/datadog-plugin-playwright/src/index.js +22 -2
- package/packages/datadog-plugin-tedious/src/index.js +14 -9
- package/packages/datadog-plugin-vitest/src/index.js +46 -14
- package/packages/dd-trace/src/appsec/blocking.js +2 -0
- package/packages/dd-trace/src/appsec/graphql.js +3 -1
- package/packages/dd-trace/src/appsec/reporter.js +13 -8
- package/packages/dd-trace/src/appsec/sdk/track_event.js +7 -0
- package/packages/dd-trace/src/appsec/telemetry/common.js +6 -3
- package/packages/dd-trace/src/appsec/telemetry/index.js +28 -5
- package/packages/dd-trace/src/appsec/telemetry/user.js +9 -1
- package/packages/dd-trace/src/appsec/telemetry/waf.js +29 -9
- package/packages/dd-trace/src/appsec/waf/waf_manager.js +16 -7
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +5 -2
- package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +3 -1
- package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +4 -2
- package/packages/dd-trace/src/dogstatsd.js +94 -77
- package/packages/dd-trace/src/histogram.js +12 -23
- package/packages/dd-trace/src/llmobs/constants/tags.js +2 -0
- package/packages/dd-trace/src/llmobs/index.js +3 -0
- package/packages/dd-trace/src/llmobs/noop.js +3 -3
- package/packages/dd-trace/src/llmobs/plugins/base.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +2 -1
- package/packages/dd-trace/src/llmobs/plugins/vertexai.js +196 -0
- package/packages/dd-trace/src/llmobs/sdk.js +2 -0
- package/packages/dd-trace/src/llmobs/span_processor.js +6 -0
- package/packages/dd-trace/src/llmobs/tagger.js +8 -2
- package/packages/dd-trace/src/llmobs/telemetry.js +108 -1
- package/packages/dd-trace/src/llmobs/writers/base.js +4 -0
- package/packages/dd-trace/src/llmobs/writers/spans/base.js +10 -1
- package/packages/dd-trace/src/plugin_manager.js +0 -3
- package/packages/dd-trace/src/plugins/ci_plugin.js +16 -26
- package/packages/dd-trace/src/plugins/database.js +4 -4
- package/packages/dd-trace/src/plugins/plugin.js +2 -0
- package/packages/dd-trace/src/plugins/util/test.js +62 -1
- package/packages/dd-trace/src/remote_config/manager.js +5 -0
- package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +7 -6
- package/packages/dd-trace/src/telemetry/send-data.js +5 -1
|
@@ -194,7 +194,6 @@ class DogStatsDClient {
|
|
|
194
194
|
}
|
|
195
195
|
}
|
|
196
196
|
|
|
197
|
-
// TODO: Handle arrays of tags and tags translation.
|
|
198
197
|
class MetricsAggregationClient {
|
|
199
198
|
constructor (client) {
|
|
200
199
|
this._client = client
|
|
@@ -211,98 +210,132 @@ class MetricsAggregationClient {
|
|
|
211
210
|
}
|
|
212
211
|
|
|
213
212
|
reset () {
|
|
214
|
-
this._counters =
|
|
215
|
-
this._gauges =
|
|
216
|
-
this._histograms =
|
|
213
|
+
this._counters = new Map()
|
|
214
|
+
this._gauges = new Map()
|
|
215
|
+
this._histograms = new Map()
|
|
217
216
|
}
|
|
218
217
|
|
|
219
|
-
|
|
220
|
-
|
|
218
|
+
// TODO: Aggerate with a histogram and send the buckets to the client.
|
|
219
|
+
distribution (name, value, tags) {
|
|
220
|
+
this._client.distribution(name, value, tags)
|
|
221
221
|
}
|
|
222
222
|
|
|
223
|
-
boolean (name, value,
|
|
224
|
-
this.gauge(name, value ? 1 : 0,
|
|
223
|
+
boolean (name, value, tags) {
|
|
224
|
+
this.gauge(name, value ? 1 : 0, tags)
|
|
225
225
|
}
|
|
226
226
|
|
|
227
|
-
histogram (name, value,
|
|
228
|
-
|
|
227
|
+
histogram (name, value, tags) {
|
|
228
|
+
const node = this._ensureTree(this._histograms, name, tags, null)
|
|
229
229
|
|
|
230
|
-
if (!
|
|
231
|
-
|
|
230
|
+
if (!node.value) {
|
|
231
|
+
node.value = new Histogram()
|
|
232
232
|
}
|
|
233
233
|
|
|
234
|
-
|
|
234
|
+
node.value.record(value)
|
|
235
235
|
}
|
|
236
236
|
|
|
237
|
-
count (name, count,
|
|
238
|
-
if (typeof
|
|
239
|
-
monotonic =
|
|
240
|
-
|
|
237
|
+
count (name, count, tags = [], monotonic = true) {
|
|
238
|
+
if (typeof tags === 'boolean') {
|
|
239
|
+
monotonic = tags
|
|
240
|
+
tags = []
|
|
241
241
|
}
|
|
242
242
|
|
|
243
|
-
const
|
|
243
|
+
const container = monotonic ? this._counters : this._gauges
|
|
244
|
+
const node = this._ensureTree(container, name, tags, 0)
|
|
244
245
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
const value = map[name].get(tag) || 0
|
|
248
|
-
|
|
249
|
-
map[name].set(tag, value + count)
|
|
246
|
+
node.value = node.value + count
|
|
250
247
|
}
|
|
251
248
|
|
|
252
|
-
gauge (name, value,
|
|
253
|
-
|
|
254
|
-
|
|
249
|
+
gauge (name, value, tags) {
|
|
250
|
+
const node = this._ensureTree(this._gauges, name, tags, 0)
|
|
251
|
+
|
|
252
|
+
node.value = value
|
|
255
253
|
}
|
|
256
254
|
|
|
257
|
-
increment (name, count = 1,
|
|
258
|
-
this.count(name, count,
|
|
255
|
+
increment (name, count = 1, tags) {
|
|
256
|
+
this.count(name, count, tags)
|
|
259
257
|
}
|
|
260
258
|
|
|
261
|
-
decrement (name, count = 1,
|
|
262
|
-
this.count(name, -count,
|
|
259
|
+
decrement (name, count = 1, tags) {
|
|
260
|
+
this.count(name, -count, tags)
|
|
263
261
|
}
|
|
264
262
|
|
|
265
263
|
_captureGauges () {
|
|
266
|
-
|
|
267
|
-
this.
|
|
268
|
-
this._client.gauge(name, value, tag && [tag])
|
|
269
|
-
})
|
|
264
|
+
this._captureTree(this._gauges, (node, name, tags) => {
|
|
265
|
+
this._client.gauge(name, node.value, tags)
|
|
270
266
|
})
|
|
271
267
|
}
|
|
272
268
|
|
|
273
269
|
_captureCounters () {
|
|
274
|
-
|
|
275
|
-
this.
|
|
276
|
-
this._client.increment(name, value, tag && [tag])
|
|
277
|
-
})
|
|
270
|
+
this._captureTree(this._counters, (node, name, tags) => {
|
|
271
|
+
this._client.increment(name, node.value, tags)
|
|
278
272
|
})
|
|
279
273
|
|
|
280
|
-
this._counters
|
|
274
|
+
this._counters.clear()
|
|
281
275
|
}
|
|
282
276
|
|
|
283
277
|
_captureHistograms () {
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
const tags = tag && [tag]
|
|
278
|
+
this._captureTree(this._histograms, (node, name, tags) => {
|
|
279
|
+
let stats = node.value
|
|
287
280
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
281
|
+
// Stats can contain garbage data when a value was never recorded.
|
|
282
|
+
if (stats.count === 0) {
|
|
283
|
+
stats = { max: 0, min: 0, sum: 0, avg: 0, median: 0, p95: 0, count: 0 }
|
|
284
|
+
}
|
|
292
285
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
286
|
+
this._client.gauge(`${name}.min`, stats.min, tags)
|
|
287
|
+
this._client.gauge(`${name}.max`, stats.max, tags)
|
|
288
|
+
this._client.increment(`${name}.sum`, stats.sum, tags)
|
|
289
|
+
this._client.increment(`${name}.total`, stats.sum, tags)
|
|
290
|
+
this._client.gauge(`${name}.avg`, stats.avg, tags)
|
|
291
|
+
this._client.increment(`${name}.count`, stats.count, tags)
|
|
292
|
+
this._client.gauge(`${name}.median`, stats.median, tags)
|
|
293
|
+
this._client.gauge(`${name}.95percentile`, stats.p95, tags)
|
|
301
294
|
|
|
302
|
-
|
|
303
|
-
})
|
|
295
|
+
node.value.reset()
|
|
304
296
|
})
|
|
305
297
|
}
|
|
298
|
+
|
|
299
|
+
_captureTree (tree, fn) {
|
|
300
|
+
for (const [name, root] of tree) {
|
|
301
|
+
this._captureNode(root, name, [], fn)
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
_captureNode (node, name, tags, fn) {
|
|
306
|
+
if (node.touched) {
|
|
307
|
+
fn(node, name, tags)
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
for (const [tag, next] of node.nodes) {
|
|
311
|
+
this._captureNode(next, name, tags.concat(tag), fn)
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
_ensureTree (tree, name, tags, value) {
|
|
316
|
+
tags = tags ? [].concat(tags) : []
|
|
317
|
+
|
|
318
|
+
let node = this._ensureNode(tree, name, value)
|
|
319
|
+
|
|
320
|
+
for (const tag of tags) {
|
|
321
|
+
node = this._ensureNode(node.nodes, tag, value)
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
node.touched = true
|
|
325
|
+
|
|
326
|
+
return node
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
_ensureNode (container, key, value) {
|
|
330
|
+
let node = container.get(key)
|
|
331
|
+
|
|
332
|
+
if (!node) {
|
|
333
|
+
node = { nodes: new Map(), touched: false, value }
|
|
334
|
+
container.set(key, node)
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return node
|
|
338
|
+
}
|
|
306
339
|
}
|
|
307
340
|
|
|
308
341
|
/**
|
|
@@ -324,45 +357,29 @@ class CustomMetrics {
|
|
|
324
357
|
}
|
|
325
358
|
|
|
326
359
|
increment (stat, value = 1, tags) {
|
|
327
|
-
|
|
328
|
-
this._client.increment(stat, value, tag)
|
|
329
|
-
}
|
|
360
|
+
this._client.increment(stat, value, CustomMetrics.tagTranslator(tags))
|
|
330
361
|
}
|
|
331
362
|
|
|
332
363
|
decrement (stat, value = 1, tags) {
|
|
333
|
-
|
|
334
|
-
this._client.decrement(stat, value, tag)
|
|
335
|
-
}
|
|
364
|
+
this._client.decrement(stat, value, CustomMetrics.tagTranslator(tags))
|
|
336
365
|
}
|
|
337
366
|
|
|
338
367
|
gauge (stat, value, tags) {
|
|
339
|
-
|
|
340
|
-
this._client.gauge(stat, value, tag)
|
|
341
|
-
}
|
|
368
|
+
this._client.gauge(stat, value, CustomMetrics.tagTranslator(tags))
|
|
342
369
|
}
|
|
343
370
|
|
|
344
371
|
distribution (stat, value, tags) {
|
|
345
|
-
|
|
346
|
-
this._client.distribution(stat, value, tag)
|
|
347
|
-
}
|
|
372
|
+
this._client.distribution(stat, value, CustomMetrics.tagTranslator(tags))
|
|
348
373
|
}
|
|
349
374
|
|
|
350
375
|
histogram (stat, value, tags) {
|
|
351
|
-
|
|
352
|
-
this._client.histogram(stat, value, tag)
|
|
353
|
-
}
|
|
376
|
+
this._client.histogram(stat, value, CustomMetrics.tagTranslator(tags))
|
|
354
377
|
}
|
|
355
378
|
|
|
356
379
|
flush () {
|
|
357
380
|
return this._client.flush()
|
|
358
381
|
}
|
|
359
382
|
|
|
360
|
-
_normalizeTags (tags) {
|
|
361
|
-
tags = CustomMetrics.tagTranslator(tags)
|
|
362
|
-
|
|
363
|
-
return tags.length === 0 ? [undefined] : tags
|
|
364
|
-
}
|
|
365
|
-
|
|
366
383
|
/**
|
|
367
384
|
* Exposing { tagName: 'tagValue' } to the end user
|
|
368
385
|
* These are translated into [ 'tagName:tagValue' ] for internal use
|
|
@@ -7,39 +7,28 @@ class Histogram {
|
|
|
7
7
|
this.reset()
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
get min () { return this.
|
|
11
|
-
get max () { return this.
|
|
12
|
-
get avg () { return this.
|
|
13
|
-
get sum () { return this.
|
|
14
|
-
get count () { return this.
|
|
10
|
+
get min () { return this._sketch.count === 0 ? 0 : this._sketch.min }
|
|
11
|
+
get max () { return this._sketch.count === 0 ? 0 : this._sketch.max }
|
|
12
|
+
get avg () { return this._sketch.count === 0 ? 0 : this._sketch.sum / this._sketch.count }
|
|
13
|
+
get sum () { return this._sketch.sum }
|
|
14
|
+
get count () { return this._sketch.count }
|
|
15
15
|
get median () { return this.percentile(50) }
|
|
16
16
|
get p95 () { return this.percentile(95) }
|
|
17
17
|
|
|
18
18
|
percentile (percentile) {
|
|
19
|
-
return this.
|
|
19
|
+
return this._sketch.getValueAtQuantile(percentile / 100) || 0
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
} else {
|
|
26
|
-
this._min = Math.min(this._min, value)
|
|
27
|
-
this._max = Math.max(this._max, value)
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
this._count++
|
|
31
|
-
this._sum += value
|
|
22
|
+
merge (histogram) {
|
|
23
|
+
return this._sketch.merge(histogram._sketch)
|
|
24
|
+
}
|
|
32
25
|
|
|
33
|
-
|
|
26
|
+
record (value) {
|
|
27
|
+
this._sketch.accept(value)
|
|
34
28
|
}
|
|
35
29
|
|
|
36
30
|
reset () {
|
|
37
|
-
this.
|
|
38
|
-
this._max = 0
|
|
39
|
-
this._sum = 0
|
|
40
|
-
this._count = 0
|
|
41
|
-
|
|
42
|
-
this._histogram = new DDSketch()
|
|
31
|
+
this._sketch = new DDSketch()
|
|
43
32
|
}
|
|
44
33
|
}
|
|
45
34
|
|
|
@@ -4,6 +4,8 @@ module.exports = {
|
|
|
4
4
|
SPAN_KINDS: ['llm', 'agent', 'workflow', 'task', 'tool', 'embedding', 'retrieval'],
|
|
5
5
|
SPAN_KIND: '_ml_obs.meta.span.kind',
|
|
6
6
|
SESSION_ID: '_ml_obs.session_id',
|
|
7
|
+
DECORATOR: '_ml_obs.decorator',
|
|
8
|
+
INTEGRATION: '_ml_obs.integration',
|
|
7
9
|
METADATA: '_ml_obs.meta.metadata',
|
|
8
10
|
METRICS: '_ml_obs.metrics',
|
|
9
11
|
ML_APP: '_ml_obs.meta.ml_app',
|
|
@@ -6,6 +6,7 @@ const { storage } = require('./storage')
|
|
|
6
6
|
|
|
7
7
|
const LLMObsSpanProcessor = require('./span_processor')
|
|
8
8
|
|
|
9
|
+
const telemetry = require('./telemetry')
|
|
9
10
|
const { channel } = require('dc-polyfill')
|
|
10
11
|
const spanProcessCh = channel('dd-trace:span:process')
|
|
11
12
|
const evalMetricAppendCh = channel('llmobs:eval-metric:append')
|
|
@@ -29,6 +30,7 @@ let spanWriter
|
|
|
29
30
|
let evalWriter
|
|
30
31
|
|
|
31
32
|
function enable (config) {
|
|
33
|
+
const startTime = performance.now()
|
|
32
34
|
// create writers and eval writer append and flush channels
|
|
33
35
|
// span writer append is handled by the span processor
|
|
34
36
|
evalWriter = new LLMObsEvalMetricsWriter(config)
|
|
@@ -44,6 +46,7 @@ function enable (config) {
|
|
|
44
46
|
|
|
45
47
|
// distributed tracing for llmobs
|
|
46
48
|
injectCh.subscribe(handleLLMObsParentIdInjection)
|
|
49
|
+
telemetry.recordLLMObsEnabled(startTime, config)
|
|
47
50
|
}
|
|
48
51
|
|
|
49
52
|
function disable () {
|
|
@@ -43,14 +43,14 @@ class NoopLLMObs {
|
|
|
43
43
|
const ctx = ctxOrPropertyKey
|
|
44
44
|
if (ctx.kind !== 'method') return target
|
|
45
45
|
|
|
46
|
-
return llmobs.wrap({ name: ctx.name, ...options }, target)
|
|
46
|
+
return llmobs.wrap({ name: ctx.name, _decorator: true, ...options }, target)
|
|
47
47
|
} else {
|
|
48
48
|
const propertyKey = ctxOrPropertyKey
|
|
49
49
|
if (descriptor) {
|
|
50
50
|
if (typeof descriptor.value !== 'function') return descriptor
|
|
51
51
|
|
|
52
52
|
const original = descriptor.value
|
|
53
|
-
descriptor.value = llmobs.wrap({ name: propertyKey, ...options }, original)
|
|
53
|
+
descriptor.value = llmobs.wrap({ name: propertyKey, _decorator: true, ...options }, original)
|
|
54
54
|
|
|
55
55
|
return descriptor
|
|
56
56
|
} else {
|
|
@@ -59,7 +59,7 @@ class NoopLLMObs {
|
|
|
59
59
|
const original = target[propertyKey]
|
|
60
60
|
Object.defineProperty(target, propertyKey, {
|
|
61
61
|
...Object.getOwnPropertyDescriptor(target, propertyKey),
|
|
62
|
-
value: llmobs.wrap({ name: propertyKey, ...options }, original)
|
|
62
|
+
value: llmobs.wrap({ name: propertyKey, _decorator: true, ...options }, original)
|
|
63
63
|
})
|
|
64
64
|
|
|
65
65
|
return target
|
|
@@ -43,7 +43,7 @@ class LLMObsPlugin extends TracingPlugin {
|
|
|
43
43
|
llmobsStorage.enterWith({ span })
|
|
44
44
|
ctx.llmobs.parent = parent
|
|
45
45
|
|
|
46
|
-
this._tagger.registerLLMObsSpan(span, { parent, ...registerOptions })
|
|
46
|
+
this._tagger.registerLLMObsSpan(span, { parent, integration: this.constructor.id, ...registerOptions })
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
|
|
@@ -55,7 +55,8 @@ class BedrockRuntimeLLMObsPlugin extends BaseLLMObsPlugin {
|
|
|
55
55
|
modelName: modelName.toLowerCase(),
|
|
56
56
|
modelProvider: modelProvider.toLowerCase(),
|
|
57
57
|
kind: 'llm',
|
|
58
|
-
name: 'bedrock-runtime.command'
|
|
58
|
+
name: 'bedrock-runtime.command',
|
|
59
|
+
integration: 'bedrock'
|
|
59
60
|
})
|
|
60
61
|
|
|
61
62
|
const requestParams = extractRequestParams(request.params, modelProvider)
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const LLMObsPlugin = require('./base')
|
|
4
|
+
const {
|
|
5
|
+
extractModel,
|
|
6
|
+
extractSystemInstructions
|
|
7
|
+
} = require('../../../../datadog-plugin-google-cloud-vertexai/src/utils')
|
|
8
|
+
|
|
9
|
+
class VertexAILLMObsPlugin extends LLMObsPlugin {
|
|
10
|
+
static get id () { return 'vertexai' } // used for llmobs telemetry
|
|
11
|
+
static get prefix () {
|
|
12
|
+
return 'tracing:apm:vertexai:request'
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
getLLMObsSpanRegisterOptions (ctx) {
|
|
16
|
+
const history = ctx.instance?.historyInternal || []
|
|
17
|
+
ctx.history = history
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
kind: 'llm',
|
|
21
|
+
modelName: extractModel(ctx.instance),
|
|
22
|
+
modelProvider: 'google',
|
|
23
|
+
name: ctx.resource
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
setLLMObsTags (ctx) {
|
|
28
|
+
const span = ctx.currentStore?.span
|
|
29
|
+
if (!span) return
|
|
30
|
+
|
|
31
|
+
const { instance, result, request } = ctx
|
|
32
|
+
const history = ctx.history || []
|
|
33
|
+
const systemInstructions = extractSystemInstructions(instance)
|
|
34
|
+
|
|
35
|
+
const metadata = getMetadata(instance)
|
|
36
|
+
const inputMessages = extractInputMessages(request, history, systemInstructions)
|
|
37
|
+
const outputMessages = extractOutputMessages(result)
|
|
38
|
+
const metrics = extractMetrics(result)
|
|
39
|
+
|
|
40
|
+
this._tagger.tagLLMIO(span, inputMessages, outputMessages)
|
|
41
|
+
this._tagger.tagMetadata(span, metadata)
|
|
42
|
+
this._tagger.tagMetrics(span, metrics)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function getMetadata (instance) {
|
|
47
|
+
const metadata = {}
|
|
48
|
+
|
|
49
|
+
const modelConfig = instance.generationConfig
|
|
50
|
+
if (!modelConfig) return metadata
|
|
51
|
+
|
|
52
|
+
for (const [parameter, parameterKey] of [
|
|
53
|
+
['temperature', 'temperature'],
|
|
54
|
+
['maxOutputTokens', 'max_output_tokens'],
|
|
55
|
+
['candidateCount', 'candidate_count'],
|
|
56
|
+
['topP', 'top_p'],
|
|
57
|
+
['topK', 'top_k']
|
|
58
|
+
]) {
|
|
59
|
+
if (modelConfig[parameter]) {
|
|
60
|
+
metadata[parameterKey] = modelConfig[parameter]
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return metadata
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function extractInputMessages (request, history, systemInstructions) {
|
|
68
|
+
const contents = typeof request === 'string' || Array.isArray(request) ? request : request.contents
|
|
69
|
+
const messages = []
|
|
70
|
+
|
|
71
|
+
if (systemInstructions) {
|
|
72
|
+
for (const instruction of systemInstructions) {
|
|
73
|
+
messages.push({ content: instruction || '', role: 'system' })
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
for (const content of history) {
|
|
78
|
+
messages.push(...extractMessagesFromContent(content))
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (typeof contents === 'string') {
|
|
82
|
+
messages.push({ content: contents })
|
|
83
|
+
return messages
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (isPart(contents)) {
|
|
87
|
+
messages.push(extractMessageFromPart(contents))
|
|
88
|
+
return messages
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (!Array.isArray(contents)) {
|
|
92
|
+
messages.push({
|
|
93
|
+
content: '[Non-array content object: ' +
|
|
94
|
+
`${(typeof contents.toString === 'function' ? contents.toString() : String(contents))}]`
|
|
95
|
+
})
|
|
96
|
+
return messages
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
for (const content of contents) {
|
|
100
|
+
if (typeof content === 'string') {
|
|
101
|
+
messages.push({ content })
|
|
102
|
+
continue
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (isPart(content)) {
|
|
106
|
+
messages.push(extractMessageFromPart(content))
|
|
107
|
+
continue
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
messages.push(...extractMessagesFromContent(content))
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return messages
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function extractOutputMessages (result) {
|
|
117
|
+
if (!result) return [{ content: '' }]
|
|
118
|
+
const { response } = result
|
|
119
|
+
|
|
120
|
+
if (!response) return [{ content: '' }]
|
|
121
|
+
|
|
122
|
+
const outputMessages = []
|
|
123
|
+
const candidates = response.candidates || []
|
|
124
|
+
for (const candidate of candidates) {
|
|
125
|
+
const content = candidate.content || ''
|
|
126
|
+
outputMessages.push(...extractMessagesFromContent(content))
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return outputMessages
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function extractMessagesFromContent (content) {
|
|
133
|
+
const messages = []
|
|
134
|
+
|
|
135
|
+
const role = content.role || ''
|
|
136
|
+
const parts = content.parts || []
|
|
137
|
+
if (parts == null || parts.length === 0 || !Array.isArray(parts)) {
|
|
138
|
+
const message = {
|
|
139
|
+
content:
|
|
140
|
+
`[Non-text content object: ${(typeof content.toString === 'function' ? content.toString() : String(content))}]`
|
|
141
|
+
}
|
|
142
|
+
if (role) message.role = role
|
|
143
|
+
messages.push(message)
|
|
144
|
+
return messages
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
for (const part of parts) {
|
|
148
|
+
const message = extractMessageFromPart(part, role)
|
|
149
|
+
messages.push(message)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return messages
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function extractMessageFromPart (part, role) {
|
|
156
|
+
const text = part.text || ''
|
|
157
|
+
const functionCall = part.functionCall
|
|
158
|
+
const functionResponse = part.functionResponse
|
|
159
|
+
|
|
160
|
+
const message = { content: text }
|
|
161
|
+
if (role) message.role = role
|
|
162
|
+
if (functionCall) {
|
|
163
|
+
message.toolCalls = [{
|
|
164
|
+
name: functionCall.name,
|
|
165
|
+
arguments: functionCall.args
|
|
166
|
+
}]
|
|
167
|
+
}
|
|
168
|
+
if (functionResponse) {
|
|
169
|
+
message.content = `[tool result: ${functionResponse.response}]`
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return message
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function extractMetrics (result) {
|
|
176
|
+
if (!result) return {}
|
|
177
|
+
const { response } = result
|
|
178
|
+
|
|
179
|
+
if (!response) return {}
|
|
180
|
+
|
|
181
|
+
const tokenCounts = response.usageMetadata
|
|
182
|
+
const metrics = {}
|
|
183
|
+
if (tokenCounts) {
|
|
184
|
+
metrics.inputTokens = tokenCounts.promptTokenCount
|
|
185
|
+
metrics.outputTokens = tokenCounts.candidatesTokenCount
|
|
186
|
+
metrics.totalTokens = tokenCounts.totalTokenCount
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return metrics
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function isPart (part) {
|
|
193
|
+
return part.text || part.functionCall || part.functionResponse
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
module.exports = VertexAILLMObsPlugin
|
|
@@ -430,6 +430,7 @@ class LLMObs extends NoopLLMObs {
|
|
|
430
430
|
modelProvider,
|
|
431
431
|
sessionId,
|
|
432
432
|
mlApp,
|
|
433
|
+
_decorator,
|
|
433
434
|
...spanOptions
|
|
434
435
|
} = options
|
|
435
436
|
|
|
@@ -438,6 +439,7 @@ class LLMObs extends NoopLLMObs {
|
|
|
438
439
|
modelName,
|
|
439
440
|
modelProvider,
|
|
440
441
|
sessionId,
|
|
442
|
+
_decorator,
|
|
441
443
|
spanOptions
|
|
442
444
|
}
|
|
443
445
|
}
|
|
@@ -7,6 +7,7 @@ const {
|
|
|
7
7
|
METADATA,
|
|
8
8
|
INPUT_MESSAGES,
|
|
9
9
|
INPUT_VALUE,
|
|
10
|
+
INTEGRATION,
|
|
10
11
|
OUTPUT_MESSAGES,
|
|
11
12
|
INPUT_DOCUMENTS,
|
|
12
13
|
OUTPUT_DOCUMENTS,
|
|
@@ -26,6 +27,8 @@ const {
|
|
|
26
27
|
ERROR_STACK
|
|
27
28
|
} = require('../constants')
|
|
28
29
|
|
|
30
|
+
const telemetry = require('./telemetry')
|
|
31
|
+
|
|
29
32
|
const LLMObsTagger = require('./tagger')
|
|
30
33
|
|
|
31
34
|
const tracerVersion = require('../../../../package.json').version
|
|
@@ -48,6 +51,7 @@ class LLMObsSpanProcessor {
|
|
|
48
51
|
|
|
49
52
|
try {
|
|
50
53
|
const formattedEvent = this.format(span)
|
|
54
|
+
telemetry.incrementLLMObsSpanFinishedCount(span)
|
|
51
55
|
this._writer.append(formattedEvent)
|
|
52
56
|
} catch (e) {
|
|
53
57
|
// this should be a rare case
|
|
@@ -186,6 +190,8 @@ class LLMObsSpanProcessor {
|
|
|
186
190
|
const errType = span.context()._tags[ERROR_TYPE] || error?.name
|
|
187
191
|
if (errType) tags.error_type = errType
|
|
188
192
|
if (sessionId) tags.session_id = sessionId
|
|
193
|
+
const integration = LLMObsTagger.tagMap.get(span)?.[INTEGRATION]
|
|
194
|
+
if (integration) tags.integration = integration
|
|
189
195
|
const existingTags = LLMObsTagger.tagMap.get(span)?.[TAGS] || {}
|
|
190
196
|
if (existingTags) tags = { ...tags, ...existingTags }
|
|
191
197
|
return Object.entries(tags).map(([key, value]) => `${key}:${value ?? ''}`)
|
|
@@ -22,7 +22,9 @@ const {
|
|
|
22
22
|
ROOT_PARENT_ID,
|
|
23
23
|
INPUT_TOKENS_METRIC_KEY,
|
|
24
24
|
OUTPUT_TOKENS_METRIC_KEY,
|
|
25
|
-
TOTAL_TOKENS_METRIC_KEY
|
|
25
|
+
TOTAL_TOKENS_METRIC_KEY,
|
|
26
|
+
INTEGRATION,
|
|
27
|
+
DECORATOR
|
|
26
28
|
} = require('./constants/tags')
|
|
27
29
|
|
|
28
30
|
// global registry of LLMObs spans
|
|
@@ -51,7 +53,9 @@ class LLMObsTagger {
|
|
|
51
53
|
mlApp,
|
|
52
54
|
parent,
|
|
53
55
|
kind,
|
|
54
|
-
name
|
|
56
|
+
name,
|
|
57
|
+
integration,
|
|
58
|
+
_decorator
|
|
55
59
|
} = {}) {
|
|
56
60
|
if (!this._config.llmobs.enabled) return
|
|
57
61
|
if (!kind) return // do not register it in the map if it doesn't have an llmobs span kind
|
|
@@ -66,6 +70,8 @@ class LLMObsTagger {
|
|
|
66
70
|
|
|
67
71
|
sessionId = sessionId || registry.get(parent)?.[SESSION_ID]
|
|
68
72
|
if (sessionId) this._setTag(span, SESSION_ID, sessionId)
|
|
73
|
+
if (integration) this._setTag(span, INTEGRATION, integration)
|
|
74
|
+
if (_decorator) this._setTag(span, DECORATOR, _decorator)
|
|
69
75
|
|
|
70
76
|
if (!mlApp) mlApp = registry.get(parent)?.[ML_APP] || this._config.llmobs.mlApp
|
|
71
77
|
this._setTag(span, ML_APP, mlApp)
|