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.
Files changed (58) hide show
  1. package/package.json +4 -4
  2. package/packages/datadog-instrumentations/src/cucumber.js +61 -23
  3. package/packages/datadog-instrumentations/src/dd-trace-api.js +7 -0
  4. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  5. package/packages/datadog-instrumentations/src/helpers/register.js +1 -1
  6. package/packages/datadog-instrumentations/src/jest.js +134 -48
  7. package/packages/datadog-instrumentations/src/mocha/main.js +20 -4
  8. package/packages/datadog-instrumentations/src/mocha/utils.js +89 -30
  9. package/packages/datadog-instrumentations/src/mocha/worker.js +3 -1
  10. package/packages/datadog-instrumentations/src/playwright.js +97 -17
  11. package/packages/datadog-instrumentations/src/router.js +1 -0
  12. package/packages/datadog-instrumentations/src/tedious.js +13 -10
  13. package/packages/datadog-instrumentations/src/vitest.js +77 -17
  14. package/packages/datadog-plugin-cucumber/src/index.js +24 -1
  15. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +69 -20
  16. package/packages/datadog-plugin-cypress/src/support.js +39 -10
  17. package/packages/datadog-plugin-google-cloud-vertexai/src/index.js +8 -186
  18. package/packages/datadog-plugin-google-cloud-vertexai/src/tracing.js +186 -0
  19. package/packages/datadog-plugin-google-cloud-vertexai/src/utils.js +19 -0
  20. package/packages/datadog-plugin-jest/src/index.js +38 -5
  21. package/packages/datadog-plugin-mocha/src/index.js +28 -5
  22. package/packages/datadog-plugin-playwright/src/index.js +22 -2
  23. package/packages/datadog-plugin-tedious/src/index.js +14 -9
  24. package/packages/datadog-plugin-vitest/src/index.js +46 -14
  25. package/packages/dd-trace/src/appsec/blocking.js +2 -0
  26. package/packages/dd-trace/src/appsec/graphql.js +3 -1
  27. package/packages/dd-trace/src/appsec/reporter.js +13 -8
  28. package/packages/dd-trace/src/appsec/sdk/track_event.js +7 -0
  29. package/packages/dd-trace/src/appsec/telemetry/common.js +6 -3
  30. package/packages/dd-trace/src/appsec/telemetry/index.js +28 -5
  31. package/packages/dd-trace/src/appsec/telemetry/user.js +9 -1
  32. package/packages/dd-trace/src/appsec/telemetry/waf.js +29 -9
  33. package/packages/dd-trace/src/appsec/waf/waf_manager.js +16 -7
  34. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +5 -2
  35. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +3 -1
  36. package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +4 -2
  37. package/packages/dd-trace/src/dogstatsd.js +94 -77
  38. package/packages/dd-trace/src/histogram.js +12 -23
  39. package/packages/dd-trace/src/llmobs/constants/tags.js +2 -0
  40. package/packages/dd-trace/src/llmobs/index.js +3 -0
  41. package/packages/dd-trace/src/llmobs/noop.js +3 -3
  42. package/packages/dd-trace/src/llmobs/plugins/base.js +1 -1
  43. package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +2 -1
  44. package/packages/dd-trace/src/llmobs/plugins/vertexai.js +196 -0
  45. package/packages/dd-trace/src/llmobs/sdk.js +2 -0
  46. package/packages/dd-trace/src/llmobs/span_processor.js +6 -0
  47. package/packages/dd-trace/src/llmobs/tagger.js +8 -2
  48. package/packages/dd-trace/src/llmobs/telemetry.js +108 -1
  49. package/packages/dd-trace/src/llmobs/writers/base.js +4 -0
  50. package/packages/dd-trace/src/llmobs/writers/spans/base.js +10 -1
  51. package/packages/dd-trace/src/plugin_manager.js +0 -3
  52. package/packages/dd-trace/src/plugins/ci_plugin.js +16 -26
  53. package/packages/dd-trace/src/plugins/database.js +4 -4
  54. package/packages/dd-trace/src/plugins/plugin.js +2 -0
  55. package/packages/dd-trace/src/plugins/util/test.js +62 -1
  56. package/packages/dd-trace/src/remote_config/manager.js +5 -0
  57. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +7 -6
  58. 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
- distribution (name, value, tag) {
220
- this._client.distribution(name, value, tag && [tag])
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, tag) {
224
- this.gauge(name, value ? 1 : 0, tag)
223
+ boolean (name, value, tags) {
224
+ this.gauge(name, value ? 1 : 0, tags)
225
225
  }
226
226
 
227
- histogram (name, value, tag) {
228
- this._histograms[name] = this._histograms[name] || new Map()
227
+ histogram (name, value, tags) {
228
+ const node = this._ensureTree(this._histograms, name, tags, null)
229
229
 
230
- if (!this._histograms[name].has(tag)) {
231
- this._histograms[name].set(tag, new Histogram())
230
+ if (!node.value) {
231
+ node.value = new Histogram()
232
232
  }
233
233
 
234
- this._histograms[name].get(tag).record(value)
234
+ node.value.record(value)
235
235
  }
236
236
 
237
- count (name, count, tag, monotonic = true) {
238
- if (typeof tag === 'boolean') {
239
- monotonic = tag
240
- tag = undefined
237
+ count (name, count, tags = [], monotonic = true) {
238
+ if (typeof tags === 'boolean') {
239
+ monotonic = tags
240
+ tags = []
241
241
  }
242
242
 
243
- const map = monotonic ? this._counters : this._gauges
243
+ const container = monotonic ? this._counters : this._gauges
244
+ const node = this._ensureTree(container, name, tags, 0)
244
245
 
245
- map[name] = map[name] || new Map()
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, tag) {
253
- this._gauges[name] = this._gauges[name] || new Map()
254
- this._gauges[name].set(tag, value)
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, tag) {
258
- this.count(name, count, tag)
255
+ increment (name, count = 1, tags) {
256
+ this.count(name, count, tags)
259
257
  }
260
258
 
261
- decrement (name, count = 1, tag) {
262
- this.count(name, -count, tag)
259
+ decrement (name, count = 1, tags) {
260
+ this.count(name, -count, tags)
263
261
  }
264
262
 
265
263
  _captureGauges () {
266
- Object.keys(this._gauges).forEach(name => {
267
- this._gauges[name].forEach((value, tag) => {
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
- Object.keys(this._counters).forEach(name => {
275
- this._counters[name].forEach((value, tag) => {
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
- Object.keys(this._histograms).forEach(name => {
285
- this._histograms[name].forEach((stats, tag) => {
286
- const tags = tag && [tag]
278
+ this._captureTree(this._histograms, (node, name, tags) => {
279
+ let stats = node.value
287
280
 
288
- // Stats can contain garbage data when a value was never recorded.
289
- if (stats.count === 0) {
290
- stats = { max: 0, min: 0, sum: 0, avg: 0, median: 0, p95: 0, count: 0, reset: stats.reset }
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
- this._client.gauge(`${name}.min`, stats.min, tags)
294
- this._client.gauge(`${name}.max`, stats.max, tags)
295
- this._client.increment(`${name}.sum`, stats.sum, tags)
296
- this._client.increment(`${name}.total`, stats.sum, tags)
297
- this._client.gauge(`${name}.avg`, stats.avg, tags)
298
- this._client.increment(`${name}.count`, stats.count, tags)
299
- this._client.gauge(`${name}.median`, stats.median, tags)
300
- this._client.gauge(`${name}.95percentile`, stats.p95, tags)
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
- stats.reset()
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
- for (const tag of this._normalizeTags(tags)) {
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
- for (const tag of this._normalizeTags(tags)) {
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
- for (const tag of this._normalizeTags(tags)) {
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
- for (const tag of this._normalizeTags(tags)) {
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
- for (const tag of this._normalizeTags(tags)) {
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._min }
11
- get max () { return this._max }
12
- get avg () { return this._count === 0 ? 0 : this._sum / this._count }
13
- get sum () { return this._sum }
14
- get count () { return this._count }
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._histogram.getValueAtQuantile(percentile / 100) || 0
19
+ return this._sketch.getValueAtQuantile(percentile / 100) || 0
20
20
  }
21
21
 
22
- record (value) {
23
- if (this._count === 0) {
24
- this._min = this._max = value
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
- this._histogram.accept(value)
26
+ record (value) {
27
+ this._sketch.accept(value)
34
28
  }
35
29
 
36
30
  reset () {
37
- this._min = 0
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)