dd-trace 4.47.1 → 4.49.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 (156) hide show
  1. package/LICENSE-3rdparty.csv +1 -1
  2. package/ext/types.d.ts +1 -0
  3. package/ext/types.js +1 -0
  4. package/index.d.ts +361 -0
  5. package/package.json +18 -13
  6. package/packages/datadog-code-origin/index.js +38 -0
  7. package/packages/datadog-core/index.js +2 -2
  8. package/packages/datadog-core/src/utils/src/parse-tags.js +33 -0
  9. package/packages/datadog-esbuild/index.js +4 -2
  10. package/packages/datadog-instrumentations/src/amqplib.js +65 -5
  11. package/packages/datadog-instrumentations/src/avsc.js +37 -0
  12. package/packages/datadog-instrumentations/src/azure-functions.js +48 -0
  13. package/packages/datadog-instrumentations/src/child_process.js +144 -27
  14. package/packages/datadog-instrumentations/src/express.js +37 -4
  15. package/packages/datadog-instrumentations/src/fastify.js +12 -1
  16. package/packages/datadog-instrumentations/src/fs.js +27 -7
  17. package/packages/datadog-instrumentations/src/helpers/hooks.js +6 -0
  18. package/packages/datadog-instrumentations/src/helpers/register.js +9 -0
  19. package/packages/datadog-instrumentations/src/jest.js +2 -1
  20. package/packages/datadog-instrumentations/src/kafkajs.js +123 -63
  21. package/packages/datadog-instrumentations/src/mocha/common.js +1 -1
  22. package/packages/datadog-instrumentations/src/mocha/utils.js +2 -2
  23. package/packages/datadog-instrumentations/src/multer.js +37 -0
  24. package/packages/datadog-instrumentations/src/mysql2.js +220 -1
  25. package/packages/datadog-instrumentations/src/openai.js +2 -2
  26. package/packages/datadog-instrumentations/src/protobufjs.js +127 -0
  27. package/packages/datadog-instrumentations/src/url.js +84 -0
  28. package/packages/datadog-instrumentations/src/utils/src/extract-package-and-module-path.js +7 -4
  29. package/packages/datadog-instrumentations/src/winston.js +22 -0
  30. package/packages/datadog-plugin-amqplib/src/consumer.js +4 -4
  31. package/packages/datadog-plugin-avsc/src/index.js +9 -0
  32. package/packages/datadog-plugin-avsc/src/schema_iterator.js +169 -0
  33. package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +1 -0
  34. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +1 -0
  35. package/packages/datadog-plugin-aws-sdk/src/services/s3.js +1 -0
  36. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +1 -0
  37. package/packages/datadog-plugin-azure-functions/src/index.js +77 -0
  38. package/packages/datadog-plugin-fastify/src/code_origin.js +31 -0
  39. package/packages/datadog-plugin-fastify/src/index.js +10 -12
  40. package/packages/datadog-plugin-fastify/src/tracing.js +19 -0
  41. package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +8 -1
  42. package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +8 -0
  43. package/packages/datadog-plugin-grpc/src/client.js +3 -0
  44. package/packages/datadog-plugin-grpc/src/server.js +3 -0
  45. package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +6 -3
  46. package/packages/datadog-plugin-kafkajs/src/consumer.js +8 -4
  47. package/packages/datadog-plugin-kafkajs/src/producer.js +10 -4
  48. package/packages/datadog-plugin-mocha/src/index.js +4 -1
  49. package/packages/datadog-plugin-openai/src/index.js +9 -1015
  50. package/packages/datadog-plugin-openai/src/tracing.js +1023 -0
  51. package/packages/datadog-plugin-protobufjs/src/index.js +14 -0
  52. package/packages/datadog-plugin-protobufjs/src/schema_iterator.js +180 -0
  53. package/packages/dd-trace/src/appsec/addresses.js +8 -1
  54. package/packages/dd-trace/src/appsec/channels.js +7 -1
  55. package/packages/dd-trace/src/appsec/iast/analyzers/cookie-analyzer.js +13 -1
  56. package/packages/dd-trace/src/appsec/iast/analyzers/path-traversal-analyzer.js +8 -1
  57. package/packages/dd-trace/src/appsec/iast/iast-plugin.js +1 -1
  58. package/packages/dd-trace/src/appsec/iast/index.js +3 -0
  59. package/packages/dd-trace/src/appsec/iast/taint-tracking/csi-methods.js +1 -0
  60. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +55 -7
  61. package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +15 -0
  62. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +4 -2
  63. package/packages/dd-trace/src/appsec/index.js +61 -43
  64. package/packages/dd-trace/src/appsec/rasp/command_injection.js +49 -0
  65. package/packages/dd-trace/src/appsec/rasp/fs-plugin.js +99 -0
  66. package/packages/dd-trace/src/appsec/rasp/index.js +27 -10
  67. package/packages/dd-trace/src/appsec/rasp/lfi.js +112 -0
  68. package/packages/dd-trace/src/appsec/rasp/sql_injection.js +24 -4
  69. package/packages/dd-trace/src/appsec/rasp/ssrf.js +4 -3
  70. package/packages/dd-trace/src/appsec/rasp/utils.js +4 -2
  71. package/packages/dd-trace/src/appsec/recommended.json +3 -7
  72. package/packages/dd-trace/src/appsec/remote_config/capabilities.js +6 -1
  73. package/packages/dd-trace/src/appsec/remote_config/index.js +10 -0
  74. package/packages/dd-trace/src/appsec/reporter.js +17 -9
  75. package/packages/dd-trace/src/appsec/sdk/track_event.js +10 -3
  76. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +1 -1
  77. package/packages/dd-trace/src/appsec/waf/waf_manager.js +4 -0
  78. package/packages/dd-trace/src/azure_metadata.js +120 -0
  79. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +97 -0
  80. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +90 -0
  81. package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +2 -14
  82. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +19 -1
  83. package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +53 -0
  84. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +8 -1
  85. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +43 -0
  86. package/packages/dd-trace/src/ci-visibility/log-submission/log-submission-plugin.js +53 -0
  87. package/packages/dd-trace/src/config.js +86 -6
  88. package/packages/dd-trace/src/constants.js +3 -1
  89. package/packages/dd-trace/src/datastreams/pathway.js +1 -0
  90. package/packages/dd-trace/src/datastreams/schemas/schema_builder.js +25 -17
  91. package/packages/dd-trace/src/debugger/devtools_client/config.js +2 -0
  92. package/packages/dd-trace/src/debugger/devtools_client/index.js +52 -5
  93. package/packages/dd-trace/src/debugger/devtools_client/remote_config.js +4 -4
  94. package/packages/dd-trace/src/debugger/devtools_client/send.js +29 -2
  95. package/packages/dd-trace/src/debugger/devtools_client/snapshot/collector.js +187 -0
  96. package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +40 -0
  97. package/packages/dd-trace/src/debugger/devtools_client/snapshot/processor.js +252 -0
  98. package/packages/dd-trace/src/debugger/devtools_client/snapshot/symbols.js +6 -0
  99. package/packages/dd-trace/src/debugger/devtools_client/state.js +19 -4
  100. package/packages/dd-trace/src/debugger/index.js +10 -3
  101. package/packages/dd-trace/src/exporters/common/request.js +8 -34
  102. package/packages/dd-trace/src/exporters/common/url-to-http-options-polyfill.js +31 -0
  103. package/packages/dd-trace/src/llmobs/constants/tags.js +34 -0
  104. package/packages/dd-trace/src/llmobs/constants/text.js +6 -0
  105. package/packages/dd-trace/src/llmobs/constants/writers.js +13 -0
  106. package/packages/dd-trace/src/llmobs/index.js +103 -0
  107. package/packages/dd-trace/src/llmobs/noop.js +82 -0
  108. package/packages/dd-trace/src/llmobs/plugins/base.js +65 -0
  109. package/packages/dd-trace/src/llmobs/plugins/openai.js +205 -0
  110. package/packages/dd-trace/src/llmobs/sdk.js +377 -0
  111. package/packages/dd-trace/src/llmobs/span_processor.js +195 -0
  112. package/packages/dd-trace/src/llmobs/storage.js +7 -0
  113. package/packages/dd-trace/src/llmobs/tagger.js +322 -0
  114. package/packages/dd-trace/src/llmobs/util.js +176 -0
  115. package/packages/dd-trace/src/llmobs/writers/base.js +111 -0
  116. package/packages/dd-trace/src/llmobs/writers/evaluations.js +29 -0
  117. package/packages/dd-trace/src/llmobs/writers/spans/agentProxy.js +23 -0
  118. package/packages/dd-trace/src/llmobs/writers/spans/agentless.js +17 -0
  119. package/packages/dd-trace/src/llmobs/writers/spans/base.js +49 -0
  120. package/packages/dd-trace/src/noop/proxy.js +3 -0
  121. package/packages/dd-trace/src/noop/span.js +3 -0
  122. package/packages/dd-trace/src/opentelemetry/span.js +1 -1
  123. package/packages/dd-trace/src/opentelemetry/tracer.js +1 -0
  124. package/packages/dd-trace/src/opentracing/propagation/text_map.js +73 -12
  125. package/packages/dd-trace/src/opentracing/span.js +12 -0
  126. package/packages/dd-trace/src/opentracing/tracer.js +8 -1
  127. package/packages/dd-trace/src/payload-tagging/config/aws.json +71 -3
  128. package/packages/dd-trace/src/payload-tagging/index.js +1 -1
  129. package/packages/dd-trace/src/payload-tagging/jsonpath-plus.js +2094 -0
  130. package/packages/dd-trace/src/plugin_manager.js +4 -2
  131. package/packages/dd-trace/src/plugins/ci_plugin.js +2 -0
  132. package/packages/dd-trace/src/plugins/index.js +3 -0
  133. package/packages/dd-trace/src/plugins/log_plugin.js +1 -1
  134. package/packages/dd-trace/src/plugins/outbound.js +9 -0
  135. package/packages/dd-trace/src/plugins/schema.js +35 -0
  136. package/packages/dd-trace/src/plugins/util/ci.js +23 -1
  137. package/packages/dd-trace/src/plugins/util/serverless.js +7 -0
  138. package/packages/dd-trace/src/plugins/util/stacktrace.js +94 -0
  139. package/packages/dd-trace/src/plugins/util/tags.js +7 -0
  140. package/packages/dd-trace/src/plugins/util/test.js +20 -22
  141. package/packages/dd-trace/src/plugins/util/web.js +6 -4
  142. package/packages/dd-trace/src/priority_sampler.js +16 -0
  143. package/packages/dd-trace/src/profiling/config.js +3 -1
  144. package/packages/dd-trace/src/profiling/exporters/agent.js +7 -5
  145. package/packages/dd-trace/src/profiling/profiler.js +24 -14
  146. package/packages/dd-trace/src/profiling/profilers/events.js +3 -3
  147. package/packages/dd-trace/src/profiling/profilers/wall.js +95 -66
  148. package/packages/dd-trace/src/proxy.js +20 -1
  149. package/packages/dd-trace/src/service-naming/schemas/v0/index.js +2 -1
  150. package/packages/dd-trace/src/service-naming/schemas/v0/serverless.js +12 -0
  151. package/packages/dd-trace/src/service-naming/schemas/v1/index.js +2 -1
  152. package/packages/dd-trace/src/service-naming/schemas/v1/serverless.js +12 -0
  153. package/packages/dd-trace/src/span_processor.js +5 -0
  154. package/packages/dd-trace/src/telemetry/index.js +11 -1
  155. package/packages/datadog-core/src/storage/async_resource.js +0 -108
  156. package/packages/datadog-core/src/storage/index.js +0 -5
@@ -0,0 +1,34 @@
1
+ 'use strict'
2
+
3
+ module.exports = {
4
+ SPAN_KINDS: ['llm', 'agent', 'workflow', 'task', 'tool', 'embedding', 'retrieval'],
5
+ SPAN_KIND: '_ml_obs.meta.span.kind',
6
+ SESSION_ID: '_ml_obs.session_id',
7
+ METADATA: '_ml_obs.meta.metadata',
8
+ METRICS: '_ml_obs.metrics',
9
+ ML_APP: '_ml_obs.meta.ml_app',
10
+ PROPAGATED_PARENT_ID_KEY: '_dd.p.llmobs_parent_id',
11
+ PARENT_ID_KEY: '_ml_obs.llmobs_parent_id',
12
+ TAGS: '_ml_obs.tags',
13
+ NAME: '_ml_obs.name',
14
+ TRACE_ID: '_ml_obs.trace_id',
15
+ PROPAGATED_TRACE_ID_KEY: '_dd.p.llmobs_trace_id',
16
+ ROOT_PARENT_ID: 'undefined',
17
+
18
+ MODEL_NAME: '_ml_obs.meta.model_name',
19
+ MODEL_PROVIDER: '_ml_obs.meta.model_provider',
20
+
21
+ INPUT_DOCUMENTS: '_ml_obs.meta.input.documents',
22
+ INPUT_MESSAGES: '_ml_obs.meta.input.messages',
23
+ INPUT_VALUE: '_ml_obs.meta.input.value',
24
+
25
+ OUTPUT_DOCUMENTS: '_ml_obs.meta.output.documents',
26
+ OUTPUT_MESSAGES: '_ml_obs.meta.output.messages',
27
+ OUTPUT_VALUE: '_ml_obs.meta.output.value',
28
+
29
+ INPUT_TOKENS_METRIC_KEY: 'input_tokens',
30
+ OUTPUT_TOKENS_METRIC_KEY: 'output_tokens',
31
+ TOTAL_TOKENS_METRIC_KEY: 'total_tokens',
32
+
33
+ DROPPED_IO_COLLECTION_ERROR: 'dropped_io'
34
+ }
@@ -0,0 +1,6 @@
1
+ 'use strict'
2
+
3
+ module.exports = {
4
+ DROPPED_VALUE_TEXT: "[This value has been dropped because this span's size exceeds the 1MB size limit.]",
5
+ UNSERIALIZABLE_VALUE_TEXT: 'Unserializable value'
6
+ }
@@ -0,0 +1,13 @@
1
+ 'use strict'
2
+
3
+ module.exports = {
4
+ EVP_PROXY_AGENT_BASE_PATH: 'evp_proxy/v2',
5
+ EVP_PROXY_AGENT_ENDPOINT: 'evp_proxy/v2/api/v2/llmobs',
6
+ EVP_SUBDOMAIN_HEADER_NAME: 'X-Datadog-EVP-Subdomain',
7
+ EVP_SUBDOMAIN_HEADER_VALUE: 'llmobs-intake',
8
+ AGENTLESS_SPANS_ENDPOINT: '/api/v2/llmobs',
9
+ AGENTLESS_EVALULATIONS_ENDPOINT: '/api/intake/llm-obs/v1/eval-metric',
10
+
11
+ EVP_PAYLOAD_SIZE_LIMIT: 5 << 20, // 5MB (actual limit is 5.1MB)
12
+ EVP_EVENT_SIZE_LIMIT: (1 << 20) - 1024 // 999KB (actual limit is 1MB)
13
+ }
@@ -0,0 +1,103 @@
1
+ 'use strict'
2
+
3
+ const log = require('../log')
4
+ const { PROPAGATED_PARENT_ID_KEY } = require('./constants/tags')
5
+ const { storage } = require('./storage')
6
+
7
+ const LLMObsSpanProcessor = require('./span_processor')
8
+
9
+ const { channel } = require('dc-polyfill')
10
+ const spanProcessCh = channel('dd-trace:span:process')
11
+ const evalMetricAppendCh = channel('llmobs:eval-metric:append')
12
+ const flushCh = channel('llmobs:writers:flush')
13
+ const injectCh = channel('dd-trace:span:inject')
14
+
15
+ const LLMObsAgentlessSpanWriter = require('./writers/spans/agentless')
16
+ const LLMObsAgentProxySpanWriter = require('./writers/spans/agentProxy')
17
+ const LLMObsEvalMetricsWriter = require('./writers/evaluations')
18
+
19
+ /**
20
+ * Setting writers and processor globally when LLMObs is enabled
21
+ * We're setting these in this module instead of on the SDK.
22
+ * This is to isolate any subscribers and periodic tasks to this module,
23
+ * and not conditionally instantiate in the SDK, since the SDK is always instantiated
24
+ * if the tracer is `init`ed. But, in those cases, we don't want to start writers or subscribe
25
+ * to channels.
26
+ */
27
+ let spanProcessor
28
+ let spanWriter
29
+ let evalWriter
30
+
31
+ function enable (config) {
32
+ // create writers and eval writer append and flush channels
33
+ // span writer append is handled by the span processor
34
+ evalWriter = new LLMObsEvalMetricsWriter(config)
35
+ spanWriter = createSpanWriter(config)
36
+
37
+ evalMetricAppendCh.subscribe(handleEvalMetricAppend)
38
+ flushCh.subscribe(handleFlush)
39
+
40
+ // span processing
41
+ spanProcessor = new LLMObsSpanProcessor(config)
42
+ spanProcessor.setWriter(spanWriter)
43
+ spanProcessCh.subscribe(handleSpanProcess)
44
+
45
+ // distributed tracing for llmobs
46
+ injectCh.subscribe(handleLLMObsParentIdInjection)
47
+ }
48
+
49
+ function disable () {
50
+ if (evalMetricAppendCh.hasSubscribers) evalMetricAppendCh.unsubscribe(handleEvalMetricAppend)
51
+ if (flushCh.hasSubscribers) flushCh.unsubscribe(handleFlush)
52
+ if (spanProcessCh.hasSubscribers) spanProcessCh.unsubscribe(handleSpanProcess)
53
+ if (injectCh.hasSubscribers) injectCh.unsubscribe(handleLLMObsParentIdInjection)
54
+
55
+ spanWriter?.destroy()
56
+ evalWriter?.destroy()
57
+ spanProcessor?.setWriter(null)
58
+
59
+ spanWriter = null
60
+ evalWriter = null
61
+ }
62
+
63
+ // since LLMObs traces can extend between services and be the same trace,
64
+ // we need to propogate the parent id.
65
+ function handleLLMObsParentIdInjection ({ carrier }) {
66
+ const parent = storage.getStore()?.span
67
+ if (!parent) return
68
+
69
+ const parentId = parent?.context().toSpanId()
70
+
71
+ carrier['x-datadog-tags'] += `,${PROPAGATED_PARENT_ID_KEY}=${parentId}`
72
+ }
73
+
74
+ function createSpanWriter (config) {
75
+ const SpanWriter = config.llmobs.agentlessEnabled ? LLMObsAgentlessSpanWriter : LLMObsAgentProxySpanWriter
76
+ return new SpanWriter(config)
77
+ }
78
+
79
+ function handleFlush () {
80
+ try {
81
+ spanWriter.flush()
82
+ evalWriter.flush()
83
+ } catch (e) {
84
+ log.warn(`Failed to flush LLMObs spans and evaluation metrics: ${e.message}`)
85
+ }
86
+ }
87
+
88
+ function handleSpanProcess (data) {
89
+ spanProcessor.process(data)
90
+ }
91
+
92
+ function handleEvalMetricAppend (payload) {
93
+ try {
94
+ evalWriter.append(payload)
95
+ } catch (e) {
96
+ log.warn(`
97
+ Failed to append evaluation metric to LLM Observability writer, likely due to an unserializable property.
98
+ Evaluation metrics won't be sent to LLM Observability: ${e.message}
99
+ `)
100
+ }
101
+ }
102
+
103
+ module.exports = { enable, disable }
@@ -0,0 +1,82 @@
1
+ 'use strict'
2
+
3
+ class NoopLLMObs {
4
+ constructor (noopTracer) {
5
+ this._tracer = noopTracer
6
+ }
7
+
8
+ get enabled () {
9
+ return false
10
+ }
11
+
12
+ enable (options) {}
13
+
14
+ disable () {}
15
+
16
+ trace (options = {}, fn) {
17
+ if (typeof options === 'function') {
18
+ fn = options
19
+ options = {}
20
+ }
21
+
22
+ const name = options.name || options.kind || fn.name
23
+
24
+ return this._tracer.trace(name, options, fn)
25
+ }
26
+
27
+ wrap (options = {}, fn) {
28
+ if (typeof options === 'function') {
29
+ fn = options
30
+ options = {}
31
+ }
32
+
33
+ const name = options.name || options.kind || fn.name
34
+
35
+ return this._tracer.wrap(name, options, fn)
36
+ }
37
+
38
+ decorate (options = {}) {
39
+ const llmobs = this
40
+ return function (target, ctxOrPropertyKey, descriptor) {
41
+ if (!ctxOrPropertyKey) return target
42
+ if (typeof ctxOrPropertyKey === 'object') {
43
+ const ctx = ctxOrPropertyKey
44
+ if (ctx.kind !== 'method') return target
45
+
46
+ return llmobs.wrap({ name: ctx.name, ...options }, target)
47
+ } else {
48
+ const propertyKey = ctxOrPropertyKey
49
+ if (descriptor) {
50
+ if (typeof descriptor.value !== 'function') return descriptor
51
+
52
+ const original = descriptor.value
53
+ descriptor.value = llmobs.wrap({ name: propertyKey, ...options }, original)
54
+
55
+ return descriptor
56
+ } else {
57
+ if (typeof target[propertyKey] !== 'function') return target[propertyKey]
58
+
59
+ const original = target[propertyKey]
60
+ Object.defineProperty(target, propertyKey, {
61
+ ...Object.getOwnPropertyDescriptor(target, propertyKey),
62
+ value: llmobs.wrap({ name: propertyKey, ...options }, original)
63
+ })
64
+
65
+ return target
66
+ }
67
+ }
68
+ }
69
+ }
70
+
71
+ annotate (span, options) {}
72
+
73
+ exportSpan (span) {
74
+ return {}
75
+ }
76
+
77
+ submitEvaluation (llmobsSpanContext, options) {}
78
+
79
+ flush () {}
80
+ }
81
+
82
+ module.exports = NoopLLMObs
@@ -0,0 +1,65 @@
1
+ 'use strict'
2
+
3
+ const log = require('../../log')
4
+ const { storage } = require('../storage')
5
+
6
+ const TracingPlugin = require('../../plugins/tracing')
7
+ const LLMObsTagger = require('../tagger')
8
+
9
+ // we make this a `Plugin` so we don't have to worry about `finish` being called
10
+ class LLMObsPlugin extends TracingPlugin {
11
+ constructor (...args) {
12
+ super(...args)
13
+
14
+ this._tagger = new LLMObsTagger(this._tracerConfig, true)
15
+ }
16
+
17
+ getName () {}
18
+
19
+ setLLMObsTags (ctx) {
20
+ throw new Error('setLLMObsTags must be implemented by the subclass')
21
+ }
22
+
23
+ getLLMObsSPanRegisterOptions (ctx) {
24
+ throw new Error('getLLMObsSPanRegisterOptions must be implemented by the subclass')
25
+ }
26
+
27
+ start (ctx) {
28
+ const oldStore = storage.getStore()
29
+ const parent = oldStore?.span
30
+ const span = ctx.currentStore?.span
31
+
32
+ const registerOptions = this.getLLMObsSPanRegisterOptions(ctx)
33
+
34
+ this._tagger.registerLLMObsSpan(span, { parent, ...registerOptions })
35
+ }
36
+
37
+ asyncEnd (ctx) {
38
+ // even though llmobs span events won't be enqueued if llmobs is disabled
39
+ // we should avoid doing any computations here (these listeners aren't disabled)
40
+ const enabled = this._tracerConfig.llmobs.enabled
41
+ if (!enabled) return
42
+
43
+ const span = ctx.currentStore?.span
44
+ if (!span) {
45
+ log.debug(
46
+ `Tried to start an LLMObs span for ${this.constructor.name} without an active APM span.
47
+ Not starting LLMObs span.`
48
+ )
49
+ return
50
+ }
51
+
52
+ this.setLLMObsTags(ctx)
53
+ }
54
+
55
+ configure (config) {
56
+ // we do not want to enable any LLMObs plugins if it is disabled on the tracer
57
+ const llmobsEnabled = this._tracerConfig.llmobs.enabled
58
+ if (llmobsEnabled === false) {
59
+ config = typeof config === 'boolean' ? false : { ...config, enabled: false } // override to false
60
+ }
61
+ super.configure(config)
62
+ }
63
+ }
64
+
65
+ module.exports = LLMObsPlugin
@@ -0,0 +1,205 @@
1
+ 'use strict'
2
+
3
+ const LLMObsPlugin = require('./base')
4
+
5
+ class OpenAiLLMObsPlugin extends LLMObsPlugin {
6
+ static get prefix () {
7
+ return 'tracing:apm:openai:request'
8
+ }
9
+
10
+ getLLMObsSPanRegisterOptions (ctx) {
11
+ const resource = ctx.methodName
12
+ const methodName = gateResource(normalizeOpenAIResourceName(resource))
13
+ if (!methodName) return // we will not trace all openai methods for llmobs
14
+
15
+ const inputs = ctx.args[0] // completion, chat completion, and embeddings take one argument
16
+ const operation = getOperation(methodName)
17
+ const kind = operation === 'embedding' ? 'embedding' : 'llm'
18
+ const name = `openai.${methodName}`
19
+
20
+ return {
21
+ modelProvider: 'openai',
22
+ modelName: inputs.model,
23
+ kind,
24
+ name
25
+ }
26
+ }
27
+
28
+ setLLMObsTags (ctx) {
29
+ const span = ctx.currentStore?.span
30
+ const resource = ctx.methodName
31
+ const methodName = gateResource(normalizeOpenAIResourceName(resource))
32
+ if (!methodName) return // we will not trace all openai methods for llmobs
33
+
34
+ const inputs = ctx.args[0] // completion, chat completion, and embeddings take one argument
35
+ const response = ctx.result?.data // no result if error
36
+ const error = !!span.context()._tags.error
37
+
38
+ const operation = getOperation(methodName)
39
+
40
+ if (operation === 'completion') {
41
+ this._tagCompletion(span, inputs, response, error)
42
+ } else if (operation === 'chat') {
43
+ this._tagChatCompletion(span, inputs, response, error)
44
+ } else if (operation === 'embedding') {
45
+ this._tagEmbedding(span, inputs, response, error)
46
+ }
47
+
48
+ if (!error) {
49
+ const metrics = this._extractMetrics(response)
50
+ this._tagger.tagMetrics(span, metrics)
51
+ }
52
+ }
53
+
54
+ _extractMetrics (response) {
55
+ const metrics = {}
56
+ const tokenUsage = response.usage
57
+
58
+ if (tokenUsage) {
59
+ const inputTokens = tokenUsage.prompt_tokens
60
+ if (inputTokens) metrics.inputTokens = inputTokens
61
+
62
+ const outputTokens = tokenUsage.completion_tokens
63
+ if (outputTokens) metrics.outputTokens = outputTokens
64
+
65
+ const totalTokens = tokenUsage.total_toksn || (inputTokens + outputTokens)
66
+ if (totalTokens) metrics.totalTokens = totalTokens
67
+ }
68
+
69
+ return metrics
70
+ }
71
+
72
+ _tagEmbedding (span, inputs, response, error) {
73
+ const { model, ...parameters } = inputs
74
+
75
+ const metadata = {
76
+ encoding_format: parameters.encoding_format || 'float'
77
+ }
78
+ if (inputs.dimensions) metadata.dimensions = inputs.dimensions
79
+ this._tagger.tagMetadata(span, metadata)
80
+
81
+ let embeddingInputs = inputs.input
82
+ if (!Array.isArray(embeddingInputs)) embeddingInputs = [embeddingInputs]
83
+ const embeddingInput = embeddingInputs.map(input => ({ text: input }))
84
+
85
+ if (error) {
86
+ this._tagger.tagEmbeddingIO(span, embeddingInput, undefined)
87
+ return
88
+ }
89
+
90
+ const float = Array.isArray(response.data[0].embedding)
91
+ let embeddingOutput
92
+ if (float) {
93
+ const embeddingDim = response.data[0].embedding.length
94
+ embeddingOutput = `[${response.data.length} embedding(s) returned with size ${embeddingDim}]`
95
+ } else {
96
+ embeddingOutput = `[${response.data.length} embedding(s) returned]`
97
+ }
98
+
99
+ this._tagger.tagEmbeddingIO(span, embeddingInput, embeddingOutput)
100
+ }
101
+
102
+ _tagCompletion (span, inputs, response, error) {
103
+ let { prompt, model, ...parameters } = inputs
104
+ if (!Array.isArray(prompt)) prompt = [prompt]
105
+
106
+ const completionInput = prompt.map(p => ({ content: p }))
107
+
108
+ const completionOutput = error ? [{ content: '' }] : response.choices.map(choice => ({ content: choice.text }))
109
+
110
+ this._tagger.tagLLMIO(span, completionInput, completionOutput)
111
+ this._tagger.tagMetadata(span, parameters)
112
+ }
113
+
114
+ _tagChatCompletion (span, inputs, response, error) {
115
+ const { messages, model, ...parameters } = inputs
116
+
117
+ if (error) {
118
+ this._tagger.tagLLMIO(span, messages, [{ content: '' }])
119
+ return
120
+ }
121
+
122
+ const outputMessages = []
123
+ const { choices } = response
124
+ for (const choice of choices) {
125
+ const message = choice.message || choice.delta
126
+ const content = message.content || ''
127
+ const role = message.role
128
+
129
+ if (message.function_call) {
130
+ const functionCallInfo = {
131
+ name: message.function_call.name,
132
+ arguments: JSON.parse(message.function_call.arguments)
133
+ }
134
+ outputMessages.push({ content, role, toolCalls: [functionCallInfo] })
135
+ } else if (message.tool_calls) {
136
+ const toolCallsInfo = []
137
+ for (const toolCall of message.tool_calls) {
138
+ const toolCallInfo = {
139
+ arguments: JSON.parse(toolCall.function.arguments),
140
+ name: toolCall.function.name,
141
+ toolId: toolCall.id,
142
+ type: toolCall.type
143
+ }
144
+ toolCallsInfo.push(toolCallInfo)
145
+ }
146
+ outputMessages.push({ content, role, toolCalls: toolCallsInfo })
147
+ } else {
148
+ outputMessages.push({ content, role })
149
+ }
150
+ }
151
+
152
+ this._tagger.tagLLMIO(span, messages, outputMessages)
153
+
154
+ const metadata = Object.entries(parameters).reduce((obj, [key, value]) => {
155
+ if (!['tools', 'functions'].includes(key)) {
156
+ obj[key] = value
157
+ }
158
+
159
+ return obj
160
+ }, {})
161
+
162
+ this._tagger.tagMetadata(span, metadata)
163
+ }
164
+ }
165
+
166
+ // TODO: this will be moved to the APM integration
167
+ function normalizeOpenAIResourceName (resource) {
168
+ switch (resource) {
169
+ // completions
170
+ case 'completions.create':
171
+ return 'createCompletion'
172
+
173
+ // chat completions
174
+ case 'chat.completions.create':
175
+ return 'createChatCompletion'
176
+
177
+ // embeddings
178
+ case 'embeddings.create':
179
+ return 'createEmbedding'
180
+ default:
181
+ return resource
182
+ }
183
+ }
184
+
185
+ function gateResource (resource) {
186
+ return ['createCompletion', 'createChatCompletion', 'createEmbedding'].includes(resource)
187
+ ? resource
188
+ : undefined
189
+ }
190
+
191
+ function getOperation (resource) {
192
+ switch (resource) {
193
+ case 'createCompletion':
194
+ return 'completion'
195
+ case 'createChatCompletion':
196
+ return 'chat'
197
+ case 'createEmbedding':
198
+ return 'embedding'
199
+ default:
200
+ // should never happen
201
+ return 'unknown'
202
+ }
203
+ }
204
+
205
+ module.exports = OpenAiLLMObsPlugin