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.
- package/LICENSE-3rdparty.csv +1 -1
- package/ext/types.d.ts +1 -0
- package/ext/types.js +1 -0
- package/index.d.ts +361 -0
- package/package.json +18 -13
- package/packages/datadog-code-origin/index.js +38 -0
- package/packages/datadog-core/index.js +2 -2
- package/packages/datadog-core/src/utils/src/parse-tags.js +33 -0
- package/packages/datadog-esbuild/index.js +4 -2
- package/packages/datadog-instrumentations/src/amqplib.js +65 -5
- package/packages/datadog-instrumentations/src/avsc.js +37 -0
- package/packages/datadog-instrumentations/src/azure-functions.js +48 -0
- package/packages/datadog-instrumentations/src/child_process.js +144 -27
- package/packages/datadog-instrumentations/src/express.js +37 -4
- package/packages/datadog-instrumentations/src/fastify.js +12 -1
- package/packages/datadog-instrumentations/src/fs.js +27 -7
- package/packages/datadog-instrumentations/src/helpers/hooks.js +6 -0
- package/packages/datadog-instrumentations/src/helpers/register.js +9 -0
- package/packages/datadog-instrumentations/src/jest.js +2 -1
- package/packages/datadog-instrumentations/src/kafkajs.js +123 -63
- package/packages/datadog-instrumentations/src/mocha/common.js +1 -1
- package/packages/datadog-instrumentations/src/mocha/utils.js +2 -2
- package/packages/datadog-instrumentations/src/multer.js +37 -0
- package/packages/datadog-instrumentations/src/mysql2.js +220 -1
- package/packages/datadog-instrumentations/src/openai.js +2 -2
- package/packages/datadog-instrumentations/src/protobufjs.js +127 -0
- package/packages/datadog-instrumentations/src/url.js +84 -0
- package/packages/datadog-instrumentations/src/utils/src/extract-package-and-module-path.js +7 -4
- package/packages/datadog-instrumentations/src/winston.js +22 -0
- package/packages/datadog-plugin-amqplib/src/consumer.js +4 -4
- package/packages/datadog-plugin-avsc/src/index.js +9 -0
- package/packages/datadog-plugin-avsc/src/schema_iterator.js +169 -0
- package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +1 -0
- package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +1 -0
- package/packages/datadog-plugin-aws-sdk/src/services/s3.js +1 -0
- package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +1 -0
- package/packages/datadog-plugin-azure-functions/src/index.js +77 -0
- package/packages/datadog-plugin-fastify/src/code_origin.js +31 -0
- package/packages/datadog-plugin-fastify/src/index.js +10 -12
- package/packages/datadog-plugin-fastify/src/tracing.js +19 -0
- package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +8 -1
- package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +8 -0
- package/packages/datadog-plugin-grpc/src/client.js +3 -0
- package/packages/datadog-plugin-grpc/src/server.js +3 -0
- package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +6 -3
- package/packages/datadog-plugin-kafkajs/src/consumer.js +8 -4
- package/packages/datadog-plugin-kafkajs/src/producer.js +10 -4
- package/packages/datadog-plugin-mocha/src/index.js +4 -1
- package/packages/datadog-plugin-openai/src/index.js +9 -1015
- package/packages/datadog-plugin-openai/src/tracing.js +1023 -0
- package/packages/datadog-plugin-protobufjs/src/index.js +14 -0
- package/packages/datadog-plugin-protobufjs/src/schema_iterator.js +180 -0
- package/packages/dd-trace/src/appsec/addresses.js +8 -1
- package/packages/dd-trace/src/appsec/channels.js +7 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/cookie-analyzer.js +13 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/path-traversal-analyzer.js +8 -1
- package/packages/dd-trace/src/appsec/iast/iast-plugin.js +1 -1
- package/packages/dd-trace/src/appsec/iast/index.js +3 -0
- package/packages/dd-trace/src/appsec/iast/taint-tracking/csi-methods.js +1 -0
- package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +55 -7
- package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +15 -0
- package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +4 -2
- package/packages/dd-trace/src/appsec/index.js +61 -43
- package/packages/dd-trace/src/appsec/rasp/command_injection.js +49 -0
- package/packages/dd-trace/src/appsec/rasp/fs-plugin.js +99 -0
- package/packages/dd-trace/src/appsec/rasp/index.js +27 -10
- package/packages/dd-trace/src/appsec/rasp/lfi.js +112 -0
- package/packages/dd-trace/src/appsec/rasp/sql_injection.js +24 -4
- package/packages/dd-trace/src/appsec/rasp/ssrf.js +4 -3
- package/packages/dd-trace/src/appsec/rasp/utils.js +4 -2
- package/packages/dd-trace/src/appsec/recommended.json +3 -7
- package/packages/dd-trace/src/appsec/remote_config/capabilities.js +6 -1
- package/packages/dd-trace/src/appsec/remote_config/index.js +10 -0
- package/packages/dd-trace/src/appsec/reporter.js +17 -9
- package/packages/dd-trace/src/appsec/sdk/track_event.js +10 -3
- package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +1 -1
- package/packages/dd-trace/src/appsec/waf/waf_manager.js +4 -0
- package/packages/dd-trace/src/azure_metadata.js +120 -0
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +97 -0
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +90 -0
- package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +2 -14
- package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +19 -1
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +53 -0
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +8 -1
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +43 -0
- package/packages/dd-trace/src/ci-visibility/log-submission/log-submission-plugin.js +53 -0
- package/packages/dd-trace/src/config.js +86 -6
- package/packages/dd-trace/src/constants.js +3 -1
- package/packages/dd-trace/src/datastreams/pathway.js +1 -0
- package/packages/dd-trace/src/datastreams/schemas/schema_builder.js +25 -17
- package/packages/dd-trace/src/debugger/devtools_client/config.js +2 -0
- package/packages/dd-trace/src/debugger/devtools_client/index.js +52 -5
- package/packages/dd-trace/src/debugger/devtools_client/remote_config.js +4 -4
- package/packages/dd-trace/src/debugger/devtools_client/send.js +29 -2
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/collector.js +187 -0
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +40 -0
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/processor.js +252 -0
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/symbols.js +6 -0
- package/packages/dd-trace/src/debugger/devtools_client/state.js +19 -4
- package/packages/dd-trace/src/debugger/index.js +10 -3
- package/packages/dd-trace/src/exporters/common/request.js +8 -34
- package/packages/dd-trace/src/exporters/common/url-to-http-options-polyfill.js +31 -0
- package/packages/dd-trace/src/llmobs/constants/tags.js +34 -0
- package/packages/dd-trace/src/llmobs/constants/text.js +6 -0
- package/packages/dd-trace/src/llmobs/constants/writers.js +13 -0
- package/packages/dd-trace/src/llmobs/index.js +103 -0
- package/packages/dd-trace/src/llmobs/noop.js +82 -0
- package/packages/dd-trace/src/llmobs/plugins/base.js +65 -0
- package/packages/dd-trace/src/llmobs/plugins/openai.js +205 -0
- package/packages/dd-trace/src/llmobs/sdk.js +377 -0
- package/packages/dd-trace/src/llmobs/span_processor.js +195 -0
- package/packages/dd-trace/src/llmobs/storage.js +7 -0
- package/packages/dd-trace/src/llmobs/tagger.js +322 -0
- package/packages/dd-trace/src/llmobs/util.js +176 -0
- package/packages/dd-trace/src/llmobs/writers/base.js +111 -0
- package/packages/dd-trace/src/llmobs/writers/evaluations.js +29 -0
- package/packages/dd-trace/src/llmobs/writers/spans/agentProxy.js +23 -0
- package/packages/dd-trace/src/llmobs/writers/spans/agentless.js +17 -0
- package/packages/dd-trace/src/llmobs/writers/spans/base.js +49 -0
- package/packages/dd-trace/src/noop/proxy.js +3 -0
- package/packages/dd-trace/src/noop/span.js +3 -0
- package/packages/dd-trace/src/opentelemetry/span.js +1 -1
- package/packages/dd-trace/src/opentelemetry/tracer.js +1 -0
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +73 -12
- package/packages/dd-trace/src/opentracing/span.js +12 -0
- package/packages/dd-trace/src/opentracing/tracer.js +8 -1
- package/packages/dd-trace/src/payload-tagging/config/aws.json +71 -3
- package/packages/dd-trace/src/payload-tagging/index.js +1 -1
- package/packages/dd-trace/src/payload-tagging/jsonpath-plus.js +2094 -0
- package/packages/dd-trace/src/plugin_manager.js +4 -2
- package/packages/dd-trace/src/plugins/ci_plugin.js +2 -0
- package/packages/dd-trace/src/plugins/index.js +3 -0
- package/packages/dd-trace/src/plugins/log_plugin.js +1 -1
- package/packages/dd-trace/src/plugins/outbound.js +9 -0
- package/packages/dd-trace/src/plugins/schema.js +35 -0
- package/packages/dd-trace/src/plugins/util/ci.js +23 -1
- package/packages/dd-trace/src/plugins/util/serverless.js +7 -0
- package/packages/dd-trace/src/plugins/util/stacktrace.js +94 -0
- package/packages/dd-trace/src/plugins/util/tags.js +7 -0
- package/packages/dd-trace/src/plugins/util/test.js +20 -22
- package/packages/dd-trace/src/plugins/util/web.js +6 -4
- package/packages/dd-trace/src/priority_sampler.js +16 -0
- package/packages/dd-trace/src/profiling/config.js +3 -1
- package/packages/dd-trace/src/profiling/exporters/agent.js +7 -5
- package/packages/dd-trace/src/profiling/profiler.js +24 -14
- package/packages/dd-trace/src/profiling/profilers/events.js +3 -3
- package/packages/dd-trace/src/profiling/profilers/wall.js +95 -66
- package/packages/dd-trace/src/proxy.js +20 -1
- package/packages/dd-trace/src/service-naming/schemas/v0/index.js +2 -1
- package/packages/dd-trace/src/service-naming/schemas/v0/serverless.js +12 -0
- package/packages/dd-trace/src/service-naming/schemas/v1/index.js +2 -1
- package/packages/dd-trace/src/service-naming/schemas/v1/serverless.js +12 -0
- package/packages/dd-trace/src/span_processor.js +5 -0
- package/packages/dd-trace/src/telemetry/index.js +11 -1
- package/packages/datadog-core/src/storage/async_resource.js +0 -108
- package/packages/datadog-core/src/storage/index.js +0 -5
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { SPAN_KIND, OUTPUT_VALUE } = require('./constants/tags')
|
|
4
|
+
|
|
5
|
+
const {
|
|
6
|
+
getFunctionArguments,
|
|
7
|
+
validateKind
|
|
8
|
+
} = require('./util')
|
|
9
|
+
const { isTrue } = require('../util')
|
|
10
|
+
|
|
11
|
+
const { storage } = require('./storage')
|
|
12
|
+
|
|
13
|
+
const Span = require('../opentracing/span')
|
|
14
|
+
|
|
15
|
+
const tracerVersion = require('../../../../package.json').version
|
|
16
|
+
const logger = require('../log')
|
|
17
|
+
|
|
18
|
+
const LLMObsTagger = require('./tagger')
|
|
19
|
+
|
|
20
|
+
// communicating with writer
|
|
21
|
+
const { channel } = require('dc-polyfill')
|
|
22
|
+
const evalMetricAppendCh = channel('llmobs:eval-metric:append')
|
|
23
|
+
const flushCh = channel('llmobs:writers:flush')
|
|
24
|
+
const NoopLLMObs = require('./noop')
|
|
25
|
+
|
|
26
|
+
class LLMObs extends NoopLLMObs {
|
|
27
|
+
constructor (tracer, llmobsModule, config) {
|
|
28
|
+
super(tracer)
|
|
29
|
+
|
|
30
|
+
this._config = config
|
|
31
|
+
this._llmobsModule = llmobsModule
|
|
32
|
+
this._tagger = new LLMObsTagger(config)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
get enabled () {
|
|
36
|
+
return this._config.llmobs.enabled
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
enable (options = {}) {
|
|
40
|
+
if (this.enabled) {
|
|
41
|
+
logger.debug('LLMObs is already enabled.')
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
logger.debug('Enabling LLMObs')
|
|
46
|
+
|
|
47
|
+
const { mlApp, agentlessEnabled } = options
|
|
48
|
+
|
|
49
|
+
const { DD_LLMOBS_ENABLED } = process.env
|
|
50
|
+
|
|
51
|
+
const llmobsConfig = {
|
|
52
|
+
mlApp,
|
|
53
|
+
agentlessEnabled
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const enabled = DD_LLMOBS_ENABLED == null || isTrue(DD_LLMOBS_ENABLED)
|
|
57
|
+
if (!enabled) {
|
|
58
|
+
logger.debug('LLMObs.enable() called when DD_LLMOBS_ENABLED is false. No action taken.')
|
|
59
|
+
return
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
this._config.llmobs.enabled = true
|
|
63
|
+
this._config.configure({ ...this._config, llmobs: llmobsConfig })
|
|
64
|
+
|
|
65
|
+
// configure writers and channel subscribers
|
|
66
|
+
this._llmobsModule.enable(this._config)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
disable () {
|
|
70
|
+
if (!this.enabled) {
|
|
71
|
+
logger.debug('LLMObs is already disabled.')
|
|
72
|
+
return
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
logger.debug('Disabling LLMObs')
|
|
76
|
+
|
|
77
|
+
this._config.llmobs.enabled = false
|
|
78
|
+
|
|
79
|
+
// disable writers and channel subscribers
|
|
80
|
+
this._llmobsModule.disable()
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
trace (options = {}, fn) {
|
|
84
|
+
if (typeof options === 'function') {
|
|
85
|
+
fn = options
|
|
86
|
+
options = {}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const kind = validateKind(options.kind) // will throw if kind is undefined or not an expected kind
|
|
90
|
+
|
|
91
|
+
// name is required for spans generated with `trace`
|
|
92
|
+
// while `kind` is required, this should never throw (as otherwise it would have thrown above)
|
|
93
|
+
const name = options.name || kind
|
|
94
|
+
if (!name) {
|
|
95
|
+
throw new Error('No span name provided for `trace`.')
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const {
|
|
99
|
+
spanOptions,
|
|
100
|
+
...llmobsOptions
|
|
101
|
+
} = this._extractOptions(options)
|
|
102
|
+
|
|
103
|
+
if (fn.length > 1) {
|
|
104
|
+
return this._tracer.trace(name, spanOptions, (span, cb) =>
|
|
105
|
+
this._activate(span, { kind, options: llmobsOptions }, () => fn(span, cb))
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return this._tracer.trace(name, spanOptions, span =>
|
|
110
|
+
this._activate(span, { kind, options: llmobsOptions }, () => fn(span))
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
wrap (options = {}, fn) {
|
|
115
|
+
if (typeof options === 'function') {
|
|
116
|
+
fn = options
|
|
117
|
+
options = {}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const kind = validateKind(options.kind) // will throw if kind is undefined or not an expected kind
|
|
121
|
+
let name = options.name || (fn?.name ? fn.name : undefined) || kind
|
|
122
|
+
|
|
123
|
+
if (!name) {
|
|
124
|
+
logger.warn('No span name provided for `wrap`. Defaulting to "unnamed-anonymous-function".')
|
|
125
|
+
name = 'unnamed-anonymous-function'
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const {
|
|
129
|
+
spanOptions,
|
|
130
|
+
...llmobsOptions
|
|
131
|
+
} = this._extractOptions(options)
|
|
132
|
+
|
|
133
|
+
const llmobs = this
|
|
134
|
+
|
|
135
|
+
function wrapped () {
|
|
136
|
+
const span = llmobs._tracer.scope().active()
|
|
137
|
+
|
|
138
|
+
const result = llmobs._activate(span, { kind, options: llmobsOptions }, () => {
|
|
139
|
+
if (!['llm', 'embedding'].includes(kind)) {
|
|
140
|
+
llmobs.annotate(span, { inputData: getFunctionArguments(fn, arguments) })
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return fn.apply(this, arguments)
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
if (result && typeof result.then === 'function') {
|
|
147
|
+
return result.then(value => {
|
|
148
|
+
if (value && !['llm', 'retrieval'].includes(kind) && !LLMObsTagger.tagMap.get(span)?.[OUTPUT_VALUE]) {
|
|
149
|
+
llmobs.annotate(span, { outputData: value })
|
|
150
|
+
}
|
|
151
|
+
return value
|
|
152
|
+
})
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (result && !['llm', 'retrieval'].includes(kind) && !LLMObsTagger.tagMap.get(span)?.[OUTPUT_VALUE]) {
|
|
156
|
+
llmobs.annotate(span, { outputData: result })
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return result
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return this._tracer.wrap(name, spanOptions, wrapped)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
annotate (span, options) {
|
|
166
|
+
if (!this.enabled) return
|
|
167
|
+
|
|
168
|
+
if (!span) {
|
|
169
|
+
span = this._active()
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if ((span && !options) && !(span instanceof Span)) {
|
|
173
|
+
options = span
|
|
174
|
+
span = this._active()
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (!span) {
|
|
178
|
+
throw new Error('No span provided and no active LLMObs-generated span found')
|
|
179
|
+
}
|
|
180
|
+
if (!options) {
|
|
181
|
+
throw new Error('No options provided for annotation.')
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (!LLMObsTagger.tagMap.has(span)) {
|
|
185
|
+
throw new Error('Span must be an LLMObs-generated span')
|
|
186
|
+
}
|
|
187
|
+
if (span._duration !== undefined) {
|
|
188
|
+
throw new Error('Cannot annotate a finished span')
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const spanKind = LLMObsTagger.tagMap.get(span)[SPAN_KIND]
|
|
192
|
+
if (!spanKind) {
|
|
193
|
+
throw new Error('LLMObs span must have a span kind specified')
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const { inputData, outputData, metadata, metrics, tags } = options
|
|
197
|
+
|
|
198
|
+
if (inputData || outputData) {
|
|
199
|
+
if (spanKind === 'llm') {
|
|
200
|
+
this._tagger.tagLLMIO(span, inputData, outputData)
|
|
201
|
+
} else if (spanKind === 'embedding') {
|
|
202
|
+
this._tagger.tagEmbeddingIO(span, inputData, outputData)
|
|
203
|
+
} else if (spanKind === 'retrieval') {
|
|
204
|
+
this._tagger.tagRetrievalIO(span, inputData, outputData)
|
|
205
|
+
} else {
|
|
206
|
+
this._tagger.tagTextIO(span, inputData, outputData)
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (metadata) {
|
|
211
|
+
this._tagger.tagMetadata(span, metadata)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (metrics) {
|
|
215
|
+
this._tagger.tagMetrics(span, metrics)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (tags) {
|
|
219
|
+
this._tagger.tagSpanTags(span, tags)
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
exportSpan (span) {
|
|
224
|
+
span = span || this._active()
|
|
225
|
+
|
|
226
|
+
if (!span) {
|
|
227
|
+
throw new Error('No span provided and no active LLMObs-generated span found')
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (!(span instanceof Span)) {
|
|
231
|
+
throw new Error('Span must be a valid Span object.')
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (!LLMObsTagger.tagMap.has(span)) {
|
|
235
|
+
throw new Error('Span must be an LLMObs-generated span')
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
try {
|
|
239
|
+
return {
|
|
240
|
+
traceId: span.context().toTraceId(true),
|
|
241
|
+
spanId: span.context().toSpanId()
|
|
242
|
+
}
|
|
243
|
+
} catch {
|
|
244
|
+
logger.warn('Faild to export span. Span must be a valid Span object.')
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
submitEvaluation (llmobsSpanContext, options = {}) {
|
|
249
|
+
if (!this.enabled) return
|
|
250
|
+
|
|
251
|
+
if (!this._config.apiKey) {
|
|
252
|
+
throw new Error(
|
|
253
|
+
'DD_API_KEY is required for sending evaluation metrics. Evaluation metric data will not be sent.\n' +
|
|
254
|
+
'Ensure this configuration is set before running your application.'
|
|
255
|
+
)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const { traceId, spanId } = llmobsSpanContext
|
|
259
|
+
if (!traceId || !spanId) {
|
|
260
|
+
throw new Error(
|
|
261
|
+
'spanId and traceId must both be specified for the given evaluation metric to be submitted.'
|
|
262
|
+
)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const mlApp = options.mlApp || this._config.llmobs.mlApp
|
|
266
|
+
if (!mlApp) {
|
|
267
|
+
throw new Error(
|
|
268
|
+
'ML App name is required for sending evaluation metrics. Evaluation metric data will not be sent.'
|
|
269
|
+
)
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const timestampMs = options.timestampMs || Date.now()
|
|
273
|
+
if (typeof timestampMs !== 'number' || timestampMs < 0) {
|
|
274
|
+
throw new Error('timestampMs must be a non-negative integer. Evaluation metric data will not be sent')
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const { label, value, tags } = options
|
|
278
|
+
const metricType = options.metricType?.toLowerCase()
|
|
279
|
+
if (!label) {
|
|
280
|
+
throw new Error('label must be the specified name of the evaluation metric')
|
|
281
|
+
}
|
|
282
|
+
if (!metricType || !['categorical', 'score'].includes(metricType)) {
|
|
283
|
+
throw new Error('metricType must be one of "categorical" or "score"')
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (metricType === 'categorical' && typeof value !== 'string') {
|
|
287
|
+
throw new Error('value must be a string for a categorical metric.')
|
|
288
|
+
}
|
|
289
|
+
if (metricType === 'score' && typeof value !== 'number') {
|
|
290
|
+
throw new Error('value must be a number for a score metric.')
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const evaluationTags = {
|
|
294
|
+
'dd-trace.version': tracerVersion,
|
|
295
|
+
ml_app: mlApp
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (tags) {
|
|
299
|
+
for (const key in tags) {
|
|
300
|
+
const tag = tags[key]
|
|
301
|
+
if (typeof tag === 'string') {
|
|
302
|
+
evaluationTags[key] = tag
|
|
303
|
+
} else if (typeof tag.toString === 'function') {
|
|
304
|
+
evaluationTags[key] = tag.toString()
|
|
305
|
+
} else if (tag == null) {
|
|
306
|
+
evaluationTags[key] = Object.prototype.toString.call(tag)
|
|
307
|
+
} else {
|
|
308
|
+
// should be a rare case
|
|
309
|
+
// every object in JS has a toString, otherwise every primitive has its own toString
|
|
310
|
+
// null and undefined are handled above
|
|
311
|
+
throw new Error('Failed to parse tags. Tags for evaluation metrics must be strings')
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const payload = {
|
|
317
|
+
span_id: spanId,
|
|
318
|
+
trace_id: traceId,
|
|
319
|
+
label,
|
|
320
|
+
metric_type: metricType,
|
|
321
|
+
ml_app: mlApp,
|
|
322
|
+
[`${metricType}_value`]: value,
|
|
323
|
+
timestamp_ms: timestampMs,
|
|
324
|
+
tags: Object.entries(evaluationTags).map(([key, value]) => `${key}:${value}`)
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
evalMetricAppendCh.publish(payload)
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
flush () {
|
|
331
|
+
if (!this.enabled) return
|
|
332
|
+
|
|
333
|
+
flushCh.publish()
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
_active () {
|
|
337
|
+
const store = storage.getStore()
|
|
338
|
+
return store?.span
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
_activate (span, { kind, options } = {}, fn) {
|
|
342
|
+
const parent = this._active()
|
|
343
|
+
if (this.enabled) storage.enterWith({ span })
|
|
344
|
+
|
|
345
|
+
this._tagger.registerLLMObsSpan(span, {
|
|
346
|
+
...options,
|
|
347
|
+
parent,
|
|
348
|
+
kind
|
|
349
|
+
})
|
|
350
|
+
|
|
351
|
+
try {
|
|
352
|
+
return fn()
|
|
353
|
+
} finally {
|
|
354
|
+
if (this.enabled) storage.enterWith({ span: parent })
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
_extractOptions (options) {
|
|
359
|
+
const {
|
|
360
|
+
modelName,
|
|
361
|
+
modelProvider,
|
|
362
|
+
sessionId,
|
|
363
|
+
mlApp,
|
|
364
|
+
...spanOptions
|
|
365
|
+
} = options
|
|
366
|
+
|
|
367
|
+
return {
|
|
368
|
+
mlApp,
|
|
369
|
+
modelName,
|
|
370
|
+
modelProvider,
|
|
371
|
+
sessionId,
|
|
372
|
+
spanOptions
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
module.exports = LLMObs
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
SPAN_KIND,
|
|
5
|
+
MODEL_NAME,
|
|
6
|
+
MODEL_PROVIDER,
|
|
7
|
+
METADATA,
|
|
8
|
+
INPUT_MESSAGES,
|
|
9
|
+
INPUT_VALUE,
|
|
10
|
+
OUTPUT_MESSAGES,
|
|
11
|
+
INPUT_DOCUMENTS,
|
|
12
|
+
OUTPUT_DOCUMENTS,
|
|
13
|
+
OUTPUT_VALUE,
|
|
14
|
+
METRICS,
|
|
15
|
+
ML_APP,
|
|
16
|
+
TAGS,
|
|
17
|
+
PARENT_ID_KEY,
|
|
18
|
+
SESSION_ID,
|
|
19
|
+
NAME
|
|
20
|
+
} = require('./constants/tags')
|
|
21
|
+
const { UNSERIALIZABLE_VALUE_TEXT } = require('./constants/text')
|
|
22
|
+
|
|
23
|
+
const {
|
|
24
|
+
ERROR_MESSAGE,
|
|
25
|
+
ERROR_TYPE,
|
|
26
|
+
ERROR_STACK
|
|
27
|
+
} = require('../constants')
|
|
28
|
+
|
|
29
|
+
const LLMObsTagger = require('./tagger')
|
|
30
|
+
|
|
31
|
+
const tracerVersion = require('../../../../package.json').version
|
|
32
|
+
const logger = require('../log')
|
|
33
|
+
|
|
34
|
+
class LLMObsSpanProcessor {
|
|
35
|
+
constructor (config) {
|
|
36
|
+
this._config = config
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
setWriter (writer) {
|
|
40
|
+
this._writer = writer
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// TODO: instead of relying on the tagger's weakmap registry, can we use some namespaced storage correlation?
|
|
44
|
+
process ({ span }) {
|
|
45
|
+
if (!this._config.llmobs.enabled) return
|
|
46
|
+
// if the span is not in our private tagger map, it is not an llmobs span
|
|
47
|
+
if (!LLMObsTagger.tagMap.has(span)) return
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
const formattedEvent = this.format(span)
|
|
51
|
+
this._writer.append(formattedEvent)
|
|
52
|
+
} catch (e) {
|
|
53
|
+
// this should be a rare case
|
|
54
|
+
// we protect against unserializable properties in the format function, and in
|
|
55
|
+
// safeguards in the tagger
|
|
56
|
+
logger.warn(`
|
|
57
|
+
Failed to append span to LLM Observability writer, likely due to an unserializable property.
|
|
58
|
+
Span won't be sent to LLM Observability: ${e.message}
|
|
59
|
+
`)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
format (span) {
|
|
64
|
+
const spanTags = span.context()._tags
|
|
65
|
+
const mlObsTags = LLMObsTagger.tagMap.get(span)
|
|
66
|
+
|
|
67
|
+
const spanKind = mlObsTags[SPAN_KIND]
|
|
68
|
+
|
|
69
|
+
const meta = { 'span.kind': spanKind, input: {}, output: {} }
|
|
70
|
+
const input = {}
|
|
71
|
+
const output = {}
|
|
72
|
+
|
|
73
|
+
if (['llm', 'embedding'].includes(spanKind)) {
|
|
74
|
+
meta.model_name = mlObsTags[MODEL_NAME] || 'custom'
|
|
75
|
+
meta.model_provider = (mlObsTags[MODEL_PROVIDER] || 'custom').toLowerCase()
|
|
76
|
+
}
|
|
77
|
+
if (mlObsTags[METADATA]) {
|
|
78
|
+
this._addObject(mlObsTags[METADATA], meta.metadata = {})
|
|
79
|
+
}
|
|
80
|
+
if (spanKind === 'llm' && mlObsTags[INPUT_MESSAGES]) {
|
|
81
|
+
input.messages = mlObsTags[INPUT_MESSAGES]
|
|
82
|
+
}
|
|
83
|
+
if (mlObsTags[INPUT_VALUE]) {
|
|
84
|
+
input.value = mlObsTags[INPUT_VALUE]
|
|
85
|
+
}
|
|
86
|
+
if (spanKind === 'llm' && mlObsTags[OUTPUT_MESSAGES]) {
|
|
87
|
+
output.messages = mlObsTags[OUTPUT_MESSAGES]
|
|
88
|
+
}
|
|
89
|
+
if (spanKind === 'embedding' && mlObsTags[INPUT_DOCUMENTS]) {
|
|
90
|
+
input.documents = mlObsTags[INPUT_DOCUMENTS]
|
|
91
|
+
}
|
|
92
|
+
if (mlObsTags[OUTPUT_VALUE]) {
|
|
93
|
+
output.value = mlObsTags[OUTPUT_VALUE]
|
|
94
|
+
}
|
|
95
|
+
if (spanKind === 'retrieval' && mlObsTags[OUTPUT_DOCUMENTS]) {
|
|
96
|
+
output.documents = mlObsTags[OUTPUT_DOCUMENTS]
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const error = spanTags.error || spanTags[ERROR_TYPE]
|
|
100
|
+
if (error) {
|
|
101
|
+
meta[ERROR_MESSAGE] = spanTags[ERROR_MESSAGE] || error.message || error.code
|
|
102
|
+
meta[ERROR_TYPE] = spanTags[ERROR_TYPE] || error.name
|
|
103
|
+
meta[ERROR_STACK] = spanTags[ERROR_STACK] || error.stack
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (input) meta.input = input
|
|
107
|
+
if (output) meta.output = output
|
|
108
|
+
|
|
109
|
+
const metrics = mlObsTags[METRICS] || {}
|
|
110
|
+
|
|
111
|
+
const mlApp = mlObsTags[ML_APP]
|
|
112
|
+
const sessionId = mlObsTags[SESSION_ID]
|
|
113
|
+
const parentId = mlObsTags[PARENT_ID_KEY]
|
|
114
|
+
|
|
115
|
+
const name = mlObsTags[NAME] || span._name
|
|
116
|
+
|
|
117
|
+
const llmObsSpanEvent = {
|
|
118
|
+
trace_id: span.context().toTraceId(true),
|
|
119
|
+
span_id: span.context().toSpanId(),
|
|
120
|
+
parent_id: parentId,
|
|
121
|
+
name,
|
|
122
|
+
tags: this._processTags(span, mlApp, sessionId, error),
|
|
123
|
+
start_ns: Math.round(span._startTime * 1e6),
|
|
124
|
+
duration: Math.round(span._duration * 1e6),
|
|
125
|
+
status: error ? 'error' : 'ok',
|
|
126
|
+
meta,
|
|
127
|
+
metrics,
|
|
128
|
+
_dd: {
|
|
129
|
+
span_id: span.context().toSpanId(),
|
|
130
|
+
trace_id: span.context().toTraceId(true)
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (sessionId) llmObsSpanEvent.session_id = sessionId
|
|
135
|
+
|
|
136
|
+
return llmObsSpanEvent
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// For now, this only applies to metadata, as we let users annotate this field with any object
|
|
140
|
+
// However, we want to protect against circular references or BigInts (unserializable)
|
|
141
|
+
// This function can be reused for other fields if needed
|
|
142
|
+
// Messages, Documents, and Metrics are safeguarded in `llmobs/tagger.js`
|
|
143
|
+
_addObject (obj, carrier) {
|
|
144
|
+
const seenObjects = new WeakSet()
|
|
145
|
+
seenObjects.add(obj) // capture root object
|
|
146
|
+
|
|
147
|
+
const isCircular = value => {
|
|
148
|
+
if (typeof value !== 'object') return false
|
|
149
|
+
if (seenObjects.has(value)) return true
|
|
150
|
+
seenObjects.add(value)
|
|
151
|
+
return false
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const add = (obj, carrier) => {
|
|
155
|
+
for (const key in obj) {
|
|
156
|
+
const value = obj[key]
|
|
157
|
+
if (!Object.prototype.hasOwnProperty.call(obj, key)) continue
|
|
158
|
+
if (typeof value === 'bigint' || isCircular(value)) {
|
|
159
|
+
// mark as unserializable instead of dropping
|
|
160
|
+
logger.warn(`Unserializable property found in metadata: ${key}`)
|
|
161
|
+
carrier[key] = UNSERIALIZABLE_VALUE_TEXT
|
|
162
|
+
continue
|
|
163
|
+
}
|
|
164
|
+
if (typeof value === 'object') {
|
|
165
|
+
add(value, carrier[key] = {})
|
|
166
|
+
} else {
|
|
167
|
+
carrier[key] = value
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
add(obj, carrier)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
_processTags (span, mlApp, sessionId, error) {
|
|
176
|
+
let tags = {
|
|
177
|
+
version: this._config.version,
|
|
178
|
+
env: this._config.env,
|
|
179
|
+
service: this._config.service,
|
|
180
|
+
source: 'integration',
|
|
181
|
+
ml_app: mlApp,
|
|
182
|
+
'dd-trace.version': tracerVersion,
|
|
183
|
+
error: Number(!!error) || 0,
|
|
184
|
+
language: 'javascript'
|
|
185
|
+
}
|
|
186
|
+
const errType = span.context()._tags[ERROR_TYPE] || error?.name
|
|
187
|
+
if (errType) tags.error_type = errType
|
|
188
|
+
if (sessionId) tags.session_id = sessionId
|
|
189
|
+
const existingTags = LLMObsTagger.tagMap.get(span)?.[TAGS] || {}
|
|
190
|
+
if (existingTags) tags = { ...tags, ...existingTags }
|
|
191
|
+
return Object.entries(tags).map(([key, value]) => `${key}:${value ?? ''}`)
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
module.exports = LLMObsSpanProcessor
|