dd-trace 6.9.0 → 6.11.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/README.md +0 -17
- package/ci/vitest-no-worker-init-setup.mjs +112 -10
- package/index.d.ts +150 -3
- package/initialize.mjs +28 -17
- package/openfeature.js +2 -2
- package/package.json +14 -10
- package/packages/datadog-esbuild/index.js +0 -34
- package/packages/datadog-instrumentations/src/anthropic.js +210 -15
- package/packages/datadog-instrumentations/src/aws-sdk.js +9 -1
- package/packages/datadog-instrumentations/src/browser-bunyan.js +11 -0
- package/packages/datadog-instrumentations/src/bunyan.js +3 -16
- package/packages/datadog-instrumentations/src/confluentinc-kafka-javascript.js +111 -6
- package/packages/datadog-instrumentations/src/cucumber.js +26 -11
- package/packages/datadog-instrumentations/src/cypress-config.js +182 -16
- package/packages/datadog-instrumentations/src/cypress-legacy-finalizers.js +30 -0
- package/packages/datadog-instrumentations/src/cypress.js +2 -0
- package/packages/datadog-instrumentations/src/fastify.js +2 -0
- package/packages/datadog-instrumentations/src/helpers/bunyan.js +22 -0
- package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -1
- package/packages/datadog-instrumentations/src/helpers/pool-acquire.js +634 -0
- package/packages/datadog-instrumentations/src/helpers/register.js +10 -8
- package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +2 -1
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +81 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +1 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +83 -1
- package/packages/datadog-instrumentations/src/jest.js +26 -18
- package/packages/datadog-instrumentations/src/mariadb.js +278 -21
- package/packages/datadog-instrumentations/src/mocha/main.js +540 -56
- package/packages/datadog-instrumentations/src/mocha/utils.js +132 -25
- package/packages/datadog-instrumentations/src/mysql.js +45 -15
- package/packages/datadog-instrumentations/src/mysql2.js +119 -18
- package/packages/datadog-instrumentations/src/path-to-regexp.js +67 -1
- package/packages/datadog-instrumentations/src/pg.js +116 -92
- package/packages/datadog-instrumentations/src/playwright-reporter.js +99 -0
- package/packages/datadog-instrumentations/src/playwright.js +149 -2
- package/packages/datadog-instrumentations/src/router.js +2 -0
- package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +43 -4
- package/packages/datadog-instrumentations/src/vitest-main.js +180 -20
- package/packages/datadog-instrumentations/src/vitest-util.js +64 -5
- package/packages/datadog-instrumentations/src/vitest-worker.js +150 -25
- package/packages/datadog-instrumentations/src/webdriverio.js +31 -17
- package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/trace-checkpoint.js +12 -6
- package/packages/datadog-plugin-aws-sdk/src/base.js +1 -0
- package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +163 -44
- package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +4 -13
- package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +1 -5
- package/packages/datadog-plugin-azure-functions/src/index.js +1 -1
- package/packages/datadog-plugin-browser-bunyan/src/index.js +9 -0
- package/packages/datadog-plugin-bunyan/src/index.js +5 -8
- package/packages/datadog-plugin-cucumber/src/index.js +11 -0
- package/packages/datadog-plugin-cypress/src/after-run.js +5 -1
- package/packages/datadog-plugin-cypress/src/after-spec.js +5 -1
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +82 -30
- package/packages/datadog-plugin-cypress/src/finalization.js +76 -0
- package/packages/datadog-plugin-cypress/src/index.js +12 -6
- package/packages/datadog-plugin-cypress/src/plugin.js +16 -3
- package/packages/datadog-plugin-dd-trace-api/src/index.js +4 -0
- package/packages/datadog-plugin-electron/src/index.js +2 -0
- package/packages/datadog-plugin-fs/src/index.js +1 -1
- package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +31 -17
- package/packages/datadog-plugin-jest/src/index.js +11 -4
- package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +2 -3
- package/packages/datadog-plugin-kafkajs/src/consumer.js +2 -3
- package/packages/datadog-plugin-kafkajs/src/producer.js +3 -2
- package/packages/datadog-plugin-kafkajs/src/utils.js +65 -7
- package/packages/datadog-plugin-mocha/src/index.js +606 -40
- package/packages/datadog-plugin-mysql/src/index.js +37 -0
- package/packages/datadog-plugin-nats/src/index.js +1 -1
- package/packages/datadog-plugin-next/src/index.js +1 -19
- package/packages/datadog-plugin-openai/src/tracing.js +41 -30
- package/packages/datadog-plugin-openai-agents/src/integration.js +279 -69
- package/packages/datadog-plugin-openai-agents/src/processor.js +16 -4
- package/packages/datadog-plugin-pg/src/index.js +1 -0
- package/packages/datadog-plugin-playwright/src/index.js +42 -13
- package/packages/datadog-plugin-vitest/src/index.js +14 -20
- package/packages/datadog-plugin-ws/src/util.js +2 -1
- package/packages/datadog-webpack/index.js +0 -11
- package/packages/dd-trace/index.js +0 -1
- package/packages/dd-trace/src/agent/info.js +6 -5
- package/packages/dd-trace/src/aiguard/client.js +100 -1
- package/packages/dd-trace/src/aiguard/errors.js +41 -0
- package/packages/dd-trace/src/aiguard/evaluation.js +357 -0
- package/packages/dd-trace/src/aiguard/integrations/anthropic.js +70 -0
- package/packages/dd-trace/src/aiguard/integrations/index.js +4 -0
- package/packages/dd-trace/src/aiguard/messages/anthropic.js +423 -0
- package/packages/dd-trace/src/aiguard/messages/openai.js +1 -24
- package/packages/dd-trace/src/aiguard/messages/utils.js +28 -0
- package/packages/dd-trace/src/aiguard/messages/vercel-ai.js +1 -11
- package/packages/dd-trace/src/aiguard/sdk.js +22 -278
- package/packages/dd-trace/src/appsec/api_security/normalized-route.js +453 -0
- package/packages/dd-trace/src/appsec/handlers/http-request.js +13 -0
- package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +9 -12
- package/packages/dd-trace/src/carrier.js +356 -0
- package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +1 -0
- package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +27 -3
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +22 -6
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +21 -5
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/request-tracker.js +164 -0
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +22 -6
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +365 -32
- package/packages/dd-trace/src/ci-visibility/exporters/request.js +201 -0
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +5 -3
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +9 -5
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +2 -1
- package/packages/dd-trace/src/ci-visibility/requests/rate-limit.js +39 -0
- package/packages/dd-trace/src/ci-visibility/requests/request.js +3 -6
- package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +6 -2
- package/packages/dd-trace/src/config/generated-config-types.d.ts +7 -0
- package/packages/dd-trace/src/config/index.js +13 -2
- package/packages/dd-trace/src/config/major-overrides.js +7 -0
- package/packages/dd-trace/src/config/supported-configurations.json +24 -0
- package/packages/dd-trace/src/datastreams/context.js +6 -1
- package/packages/dd-trace/src/datastreams/index.js +2 -0
- package/packages/dd-trace/src/datastreams/manager.js +14 -3
- package/packages/dd-trace/src/datastreams/pathway.js +23 -22
- package/packages/dd-trace/src/datastreams/processor.js +16 -13
- package/packages/dd-trace/src/datastreams/size.js +5 -0
- package/packages/dd-trace/src/debugger/devtools_client/index.js +3 -2
- package/packages/dd-trace/src/exporters/agent/writer.js +49 -9
- package/packages/dd-trace/src/exporters/common/buffering-exporter.js +6 -4
- package/packages/dd-trace/src/exporters/common/writer.js +11 -6
- package/packages/dd-trace/src/llmobs/constants/tags.js +5 -0
- package/packages/dd-trace/src/llmobs/experiments/client.js +163 -5
- package/packages/dd-trace/src/llmobs/experiments/dataset.js +241 -87
- package/packages/dd-trace/src/llmobs/experiments/experiment.js +280 -20
- package/packages/dd-trace/src/llmobs/experiments/index.js +58 -59
- package/packages/dd-trace/src/llmobs/experiments/noop.js +71 -3
- package/packages/dd-trace/src/llmobs/experiments/result.js +1 -0
- package/packages/dd-trace/src/llmobs/experiments/util.js +41 -5
- package/packages/dd-trace/src/llmobs/index.js +34 -2
- package/packages/dd-trace/src/llmobs/plugins/ai/util.js +12 -3
- package/packages/dd-trace/src/llmobs/plugins/base.js +4 -2
- package/packages/dd-trace/src/llmobs/span_processor.js +16 -0
- package/packages/dd-trace/src/llmobs/tagger.js +96 -0
- package/packages/dd-trace/src/llmobs/util.js +78 -0
- package/packages/dd-trace/src/noop/proxy.js +3 -4
- package/packages/dd-trace/src/openfeature/flagging_provider.js +18 -2
- package/packages/dd-trace/src/openfeature/remote_config.js +1 -1
- package/packages/dd-trace/src/opentelemetry/context_manager.js +7 -7
- package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +2 -1
- package/packages/dd-trace/src/opentelemetry/logs/logger.js +67 -11
- package/packages/dd-trace/src/opentelemetry/logs/otlp_transformer.js +14 -3
- package/packages/dd-trace/src/opentelemetry/metrics/index.js +25 -12
- package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +2 -5
- package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_transformer.js +96 -43
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +126 -120
- package/packages/dd-trace/src/opentracing/propagation/text_map_dsm.js +4 -8
- package/packages/dd-trace/src/opentracing/tracer.js +5 -2
- package/packages/dd-trace/src/otel-thread-ctx.js +387 -0
- package/packages/dd-trace/src/plugin_manager.js +5 -3
- package/packages/dd-trace/src/plugins/ci_plugin.js +6 -1
- package/packages/dd-trace/src/plugins/index.js +1 -0
- package/packages/{datadog-plugin-jest/src/util.js → dd-trace/src/plugins/util/jest.js} +5 -65
- package/packages/dd-trace/src/plugins/util/test.js +93 -14
- package/packages/dd-trace/src/plugins/util/web.js +56 -2
- package/packages/dd-trace/src/profiling/profiler.js +1 -1
- package/packages/dd-trace/src/profiling/profilers/space.js +1 -3
- package/packages/dd-trace/src/profiling/profilers/wall.js +37 -90
- package/packages/dd-trace/src/profiling/webspan-utils.js +45 -0
- package/packages/dd-trace/src/proxy.js +38 -29
- package/packages/dd-trace/src/ritm.js +9 -2
- package/packages/dd-trace/src/serverless.js +37 -0
- package/packages/dd-trace/src/span_format.js +1 -0
- package/packages/dd-trace/src/span_stats.js +30 -3
- package/packages/dd-trace/src/storage-channels.js +86 -0
- package/packages/dd-trace/src/tracer.js +9 -2
- package/packages/dd-trace/src/tracer_metadata.js +14 -1
- package/packages/dd-trace/src/web-tags-cache.js +132 -0
- package/vendor/dist/@datadog/openfeature-node-server/LICENSE +201 -0
- package/vendor/dist/@datadog/openfeature-node-server/index.js +1 -0
- package/packages/datadog-instrumentations/src/helpers/optional-peer-bundler.js +0 -65
- package/packages/datadog-webpack/src/optional-peer-loader.js +0 -17
- package/packages/dd-trace/src/feature-registry.js +0 -29
- package/packages/dd-trace/src/openfeature/register.js +0 -35
- package/packages/dd-trace/src/openfeature/require-provider.js +0 -18
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const { inspect } = require('node:util')
|
|
4
4
|
const { channel } = require('dc-polyfill')
|
|
5
5
|
|
|
6
|
-
const
|
|
6
|
+
const commonRequest = require('../common/request')
|
|
7
7
|
const { logIntegrations, logAgentError } = require('../../startup-log')
|
|
8
8
|
const runtimeMetrics = require('../../runtime_metrics')
|
|
9
9
|
const log = require('../../log')
|
|
@@ -15,12 +15,15 @@ const METRIC_PREFIX = 'datadog.tracer.node.exporter.agent'
|
|
|
15
15
|
const firstFlushChannel = channel('dd-trace:exporter:first-flush')
|
|
16
16
|
|
|
17
17
|
class AgentWriter extends BaseWriter {
|
|
18
|
+
#request = commonRequest
|
|
19
|
+
#requestTracker
|
|
20
|
+
|
|
18
21
|
constructor (...args) {
|
|
19
22
|
super({
|
|
20
23
|
...args[0],
|
|
21
24
|
beforeFirstFlush: () => firstFlushChannel.publish(),
|
|
22
25
|
})
|
|
23
|
-
const { prioritySampler, lookup, protocolVersion, headers } = args[0]
|
|
26
|
+
const { prioritySampler, lookup, protocolVersion, headers, isTestOptimization } = args[0]
|
|
24
27
|
const AgentEncoder = getEncoder(protocolVersion)
|
|
25
28
|
|
|
26
29
|
this._prioritySampler = prioritySampler
|
|
@@ -28,13 +31,33 @@ class AgentWriter extends BaseWriter {
|
|
|
28
31
|
this._protocolVersion = protocolVersion
|
|
29
32
|
this._headers = headers
|
|
30
33
|
this._encoder = new AgentEncoder(this)
|
|
34
|
+
if (isTestOptimization) {
|
|
35
|
+
this.#request = require('../../ci-visibility/exporters/request')
|
|
36
|
+
const TestOptimizationRequestTracker = require('../../ci-visibility/exporters/agentless/request-tracker')
|
|
37
|
+
this.#requestTracker = new TestOptimizationRequestTracker(this)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Flushes payloads, including requests already in flight during Test Optimization finalization.
|
|
43
|
+
*
|
|
44
|
+
* @param {(error?: Error) => void} [done]
|
|
45
|
+
* @param {{ deadline?: number }} [options]
|
|
46
|
+
* @returns {void}
|
|
47
|
+
*/
|
|
48
|
+
flush (done, options) {
|
|
49
|
+
if (this.#requestTracker) {
|
|
50
|
+
this.#requestTracker.flush(done, options)
|
|
51
|
+
return
|
|
52
|
+
}
|
|
53
|
+
super.flush(done, options)
|
|
31
54
|
}
|
|
32
55
|
|
|
33
|
-
_sendPayload (data, count, done) {
|
|
56
|
+
_sendPayload (data, count, done, flushOptions) {
|
|
34
57
|
runtimeMetrics.increment(`${METRIC_PREFIX}.requests`, true)
|
|
35
58
|
|
|
36
59
|
const { _headers, _lookup, _protocolVersion, _url } = this
|
|
37
|
-
|
|
60
|
+
const onResponse = (err, res, status, headers) => {
|
|
38
61
|
if (status) {
|
|
39
62
|
runtimeMetrics.increment(`${METRIC_PREFIX}.responses`, true)
|
|
40
63
|
runtimeMetrics.increment(`${METRIC_PREFIX}.responses.by.status`, `status:${status}`, true)
|
|
@@ -49,7 +72,7 @@ class AgentWriter extends BaseWriter {
|
|
|
49
72
|
|
|
50
73
|
if (err) {
|
|
51
74
|
log.errorWithoutTelemetry('Error sending payload to the agent (status code: %s)', err.status, err)
|
|
52
|
-
done()
|
|
75
|
+
done(flushOptions?.deadline === undefined ? undefined : err)
|
|
53
76
|
return
|
|
54
77
|
}
|
|
55
78
|
|
|
@@ -74,7 +97,19 @@ class AgentWriter extends BaseWriter {
|
|
|
74
97
|
runtimeMetrics.increment(`${METRIC_PREFIX}.errors.by.name`, `name:${e.name}`, true)
|
|
75
98
|
}
|
|
76
99
|
done()
|
|
77
|
-
}
|
|
100
|
+
}
|
|
101
|
+
makeRequest(
|
|
102
|
+
_protocolVersion,
|
|
103
|
+
data,
|
|
104
|
+
count,
|
|
105
|
+
_url,
|
|
106
|
+
_headers,
|
|
107
|
+
_lookup,
|
|
108
|
+
flushOptions,
|
|
109
|
+
this.#request,
|
|
110
|
+
this.#requestTracker,
|
|
111
|
+
onResponse
|
|
112
|
+
)
|
|
78
113
|
}
|
|
79
114
|
}
|
|
80
115
|
|
|
@@ -84,7 +119,7 @@ function getEncoder (protocolVersion) {
|
|
|
84
119
|
: require('../../encode/0.4').AgentEncoder
|
|
85
120
|
}
|
|
86
121
|
|
|
87
|
-
function makeRequest (version, data, count, url, headers, lookup, cb) {
|
|
122
|
+
function makeRequest (version, data, count, url, headers, lookup, flushOptions, request, requestTracker, cb) {
|
|
88
123
|
const options = {
|
|
89
124
|
path: `/v${version}/traces`,
|
|
90
125
|
method: 'PUT',
|
|
@@ -100,16 +135,21 @@ function makeRequest (version, data, count, url, headers, lookup, cb) {
|
|
|
100
135
|
lookup,
|
|
101
136
|
url,
|
|
102
137
|
}
|
|
138
|
+
if (flushOptions?.deadline !== undefined) {
|
|
139
|
+
options.deadline = flushOptions.deadline
|
|
140
|
+
}
|
|
103
141
|
|
|
104
142
|
log.debug('Request to the agent: %j', options)
|
|
105
143
|
|
|
106
|
-
|
|
144
|
+
const onResponse = (err, res, status, headers) => {
|
|
107
145
|
logIntegrations()
|
|
108
146
|
if (status !== 404 && status !== 200 && err) {
|
|
109
147
|
logAgentError({ status, message: err.message ?? inspect(err) })
|
|
110
148
|
}
|
|
111
149
|
cb(err, res, status, headers)
|
|
112
|
-
}
|
|
150
|
+
}
|
|
151
|
+
if (requestTracker) requestTracker.send(request, data, options, onResponse)
|
|
152
|
+
else request(data, options, onResponse)
|
|
113
153
|
}
|
|
114
154
|
|
|
115
155
|
module.exports = AgentWriter
|
|
@@ -24,23 +24,25 @@ class BufferingExporter {
|
|
|
24
24
|
this._export(trace)
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
_export (payload, writer = this._writer, timerKey = '_timer') {
|
|
27
|
+
_export (payload, writer = this._writer, timerKey = '_timer', deferImmediateFlush = false) {
|
|
28
28
|
if (this._config.isCiVisibility) {
|
|
29
29
|
incrementCountMetric(TELEMETRY_EVENTS_ENQUEUED_FOR_SERIALIZATION, {}, payload.length)
|
|
30
30
|
}
|
|
31
|
-
writer.append(payload)
|
|
31
|
+
const appended = writer.append(payload)
|
|
32
32
|
|
|
33
33
|
const { flushInterval } = this._config
|
|
34
34
|
|
|
35
|
-
if (flushInterval === 0) {
|
|
35
|
+
if (flushInterval === 0 && !deferImmediateFlush) {
|
|
36
36
|
writer.flush()
|
|
37
|
-
} else if (this[timerKey] === undefined) {
|
|
37
|
+
} else if (flushInterval !== 0 && this[timerKey] === undefined) {
|
|
38
38
|
this[timerKey] = setTimeout(() => {
|
|
39
39
|
writer.flush()
|
|
40
40
|
this[timerKey] = undefined
|
|
41
41
|
}, flushInterval)
|
|
42
42
|
this[timerKey].unref?.()
|
|
43
43
|
}
|
|
44
|
+
|
|
45
|
+
return appended
|
|
44
46
|
}
|
|
45
47
|
|
|
46
48
|
getUncodedTraces () {
|
|
@@ -17,10 +17,10 @@ class Writer {
|
|
|
17
17
|
|
|
18
18
|
#isFirstFlush = true
|
|
19
19
|
|
|
20
|
-
flush (done = () => {}) {
|
|
20
|
+
flush (done = () => {}, options) {
|
|
21
21
|
const count = this._encoder.count()
|
|
22
22
|
|
|
23
|
-
if (!request.writable) {
|
|
23
|
+
if (!request.writable && options?.deadline === undefined) {
|
|
24
24
|
this._encoder.reset()
|
|
25
25
|
done()
|
|
26
26
|
} else if (count > 0) {
|
|
@@ -44,23 +44,28 @@ class Writer {
|
|
|
44
44
|
done()
|
|
45
45
|
return
|
|
46
46
|
}
|
|
47
|
-
|
|
47
|
+
if (options === undefined) {
|
|
48
|
+
this._sendPayload(payload, count, done)
|
|
49
|
+
} else {
|
|
50
|
+
this._sendPayload(payload, count, done, options)
|
|
51
|
+
}
|
|
48
52
|
} else {
|
|
49
53
|
done()
|
|
50
54
|
}
|
|
51
55
|
}
|
|
52
56
|
|
|
53
|
-
append (payload) {
|
|
54
|
-
if (!request.writable) {
|
|
57
|
+
append (payload, options) {
|
|
58
|
+
if (!request.writable && options?.deadline === undefined) {
|
|
55
59
|
// eslint-disable-next-line eslint-rules/eslint-log-printf-style
|
|
56
60
|
log.debug(() => `Maximum number of active requests reached. Payload discarded: ${safeJSONStringify(payload)}`)
|
|
57
|
-
return
|
|
61
|
+
return false
|
|
58
62
|
}
|
|
59
63
|
|
|
60
64
|
// eslint-disable-next-line eslint-rules/eslint-log-printf-style
|
|
61
65
|
log.debug(() => `Encoding payload: ${safeJSONStringify(payload)}`)
|
|
62
66
|
|
|
63
67
|
this._encode(payload)
|
|
68
|
+
return true
|
|
64
69
|
}
|
|
65
70
|
|
|
66
71
|
_encode (payload) {
|
|
@@ -16,6 +16,11 @@ module.exports = {
|
|
|
16
16
|
PROPAGATED_ML_APP_KEY: '_dd.p.llmobs_ml_app',
|
|
17
17
|
PROPAGATED_SESSION_ID_KEY: '_dd.p.llmobs_sid',
|
|
18
18
|
PARENT_ID_KEY: '_ml_obs.llmobs_parent_id',
|
|
19
|
+
|
|
20
|
+
PARENT_AGENT_NAME: '_ml_obs.parent_agent_name',
|
|
21
|
+
PARENT_AGENT_SPAN_ID: '_ml_obs.parent_agent_span_id',
|
|
22
|
+
PROPAGATED_PARENT_AGENT_ID_KEY: '_dd.p.llmobs_pagent_span_id',
|
|
23
|
+
PROPAGATED_PARENT_AGENT_NAME_KEY: '_dd.p.llmobs_pagent_name',
|
|
19
24
|
PROPAGATED_SAMPLE_RATE_KEY: '_dd.p.llmobs_sr',
|
|
20
25
|
PROPAGATED_SAMPLING_DECISION_KEY: '_dd.p.llmobs_sd',
|
|
21
26
|
SAMPLE_RATE: '_ml_obs.sample_rate',
|
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
// Control-plane HTTP client for LLM Obs Experiments. Uses the global `fetch`,
|
|
4
4
|
// so this module adds no new dependency; credentials and site come from config.
|
|
5
5
|
|
|
6
|
+
const { Dataset, DatasetRecord } = require('./dataset')
|
|
7
|
+
const { ExperimentResult } = require('./result')
|
|
8
|
+
|
|
6
9
|
const API_BASE_PATH = '/api/v2/llm-obs/v1'
|
|
7
10
|
|
|
8
11
|
// Control-plane host for a Datadog site, e.g.
|
|
@@ -22,12 +25,67 @@ function appHost (site) {
|
|
|
22
25
|
return site.split('.').length === 2 ? `app.${site}` : site
|
|
23
26
|
}
|
|
24
27
|
|
|
28
|
+
function datasetRecordFromResource (resource) {
|
|
29
|
+
const attrs = resource?.attributes ?? resource ?? {}
|
|
30
|
+
const id = String(resource?.id ?? attrs.id ?? '')
|
|
31
|
+
if (id === '') throw new Error('Dataset record response is missing an id')
|
|
32
|
+
return new DatasetRecord(
|
|
33
|
+
attrs.input ?? null,
|
|
34
|
+
attrs.expected_output ?? null,
|
|
35
|
+
attrs.metadata ?? {},
|
|
36
|
+
id
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function datasetVersionFromResource (resource) {
|
|
41
|
+
const attrs = resource?.attributes ?? resource ?? {}
|
|
42
|
+
return attrs.valid_from_version ?? attrs.version ?? null
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function datasetVersionFromResources (resources) {
|
|
46
|
+
const versions = resources
|
|
47
|
+
.map(datasetVersionFromResource)
|
|
48
|
+
.filter(version => version != null)
|
|
49
|
+
.map(Number)
|
|
50
|
+
.filter(Number.isFinite)
|
|
51
|
+
if (versions.length === 0) return null
|
|
52
|
+
return Math.max(...versions)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function datasetMutationResultFromResources (resources) {
|
|
56
|
+
return {
|
|
57
|
+
records: resources.map(datasetRecordFromResource),
|
|
58
|
+
version: datasetVersionFromResources(resources),
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function datasetFromResource (client, projectId, resource) {
|
|
63
|
+
const attrs = resource?.attributes ?? resource ?? {}
|
|
64
|
+
const version = attrs.current_version ?? null
|
|
65
|
+
return Dataset.fromExisting(
|
|
66
|
+
client,
|
|
67
|
+
String(attrs.name ?? ''),
|
|
68
|
+
String(attrs.description ?? ''),
|
|
69
|
+
resource?.id ?? attrs.id ?? null,
|
|
70
|
+
projectId,
|
|
71
|
+
[],
|
|
72
|
+
version,
|
|
73
|
+
version
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function experimentFromResource (client, resource) {
|
|
78
|
+
const id = resource?.id
|
|
79
|
+
return new ExperimentResult(id, [], id == null ? null : `${client.appBase}/llm/experiments/${id}`)
|
|
80
|
+
}
|
|
81
|
+
|
|
25
82
|
class ExperimentsClient {
|
|
26
83
|
#apiKey
|
|
27
84
|
#appKey
|
|
28
85
|
#site
|
|
29
86
|
#projectName
|
|
30
87
|
#timeout
|
|
88
|
+
apiBase
|
|
31
89
|
#cachedProjectId
|
|
32
90
|
|
|
33
91
|
constructor ({ apiKey, appKey, site, projectName, timeout = 30_000 } = {}) {
|
|
@@ -36,6 +94,7 @@ class ExperimentsClient {
|
|
|
36
94
|
this.#site = site
|
|
37
95
|
this.#projectName = projectName
|
|
38
96
|
this.#timeout = timeout
|
|
97
|
+
this.apiBase = `https://${apiHost(this.#site)}`
|
|
39
98
|
this.#cachedProjectId = null
|
|
40
99
|
}
|
|
41
100
|
|
|
@@ -61,7 +120,7 @@ class ExperimentsClient {
|
|
|
61
120
|
// Low-level request. Builds https://api.<site><path>, attaches both keys, and
|
|
62
121
|
// returns the parsed JSON body. Throws with status + body on a non-2xx.
|
|
63
122
|
async request (method, path, body) {
|
|
64
|
-
const url =
|
|
123
|
+
const url = `${this.apiBase}${path}`
|
|
65
124
|
const headers = {
|
|
66
125
|
'DD-API-KEY': this.#apiKey,
|
|
67
126
|
'DD-APPLICATION-KEY': this.#appKey,
|
|
@@ -92,6 +151,107 @@ class ExperimentsClient {
|
|
|
92
151
|
return text ? JSON.parse(text) : {}
|
|
93
152
|
}
|
|
94
153
|
|
|
154
|
+
jsonApiRequest (method, path, type, attributes) {
|
|
155
|
+
return this.request(method, path, {
|
|
156
|
+
data: { type, attributes },
|
|
157
|
+
})
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async createProject (name) {
|
|
161
|
+
const response = await this.jsonApiRequest('POST', `${API_BASE_PATH}/projects`, 'projects', { name })
|
|
162
|
+
return response?.data ?? null
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async createDataset (projectId, attributes) {
|
|
166
|
+
const response = await this.jsonApiRequest('POST', `${API_BASE_PATH}/${projectId}/datasets`, 'datasets', attributes)
|
|
167
|
+
return datasetFromResource(this, projectId, response?.data ?? null)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
deleteDataset (projectId, datasetId) {
|
|
171
|
+
return this.jsonApiRequest('POST', `${API_BASE_PATH}/${projectId}/datasets/delete`, 'datasets', {
|
|
172
|
+
type: 'soft',
|
|
173
|
+
dataset_ids: [datasetId],
|
|
174
|
+
})
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async listDatasets (projectId, options = {}) {
|
|
178
|
+
const query = new URLSearchParams()
|
|
179
|
+
if (options.name !== undefined) query.set('filter[name]', options.name)
|
|
180
|
+
const response = await this.request('GET', `${API_BASE_PATH}/${projectId}/datasets?${query.toString()}`)
|
|
181
|
+
const resources = Array.isArray(response?.data) ? response.data : []
|
|
182
|
+
return resources.map(resource => datasetFromResource(this, projectId, resource))
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async appendDatasetRecords (projectId, datasetId, records) {
|
|
186
|
+
const response = await this.jsonApiRequest(
|
|
187
|
+
'POST',
|
|
188
|
+
`${API_BASE_PATH}/${projectId}/datasets/${datasetId}/records`,
|
|
189
|
+
'datasets',
|
|
190
|
+
{ records }
|
|
191
|
+
)
|
|
192
|
+
// The append-records response has used both a top-level `records` array
|
|
193
|
+
// and JSON:API `data` resources. Accept either so generated/custom record
|
|
194
|
+
// ids are preserved for experiment row tagging.
|
|
195
|
+
const resources = Array.isArray(response?.records)
|
|
196
|
+
? response.records
|
|
197
|
+
: (Array.isArray(response?.data) ? response.data : [])
|
|
198
|
+
return resources.map(datasetRecordFromResource)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async batchUpdateDatasetRecords (projectId, datasetId, attributes) {
|
|
202
|
+
const response = await this.request(
|
|
203
|
+
'POST',
|
|
204
|
+
`${API_BASE_PATH}/${projectId}/datasets/${datasetId}/batch_update`,
|
|
205
|
+
{
|
|
206
|
+
data: {
|
|
207
|
+
type: 'datasets',
|
|
208
|
+
id: datasetId,
|
|
209
|
+
attributes: {
|
|
210
|
+
insert_records: attributes.insert_records ?? [],
|
|
211
|
+
update_records: attributes.update_records ?? [],
|
|
212
|
+
delete_records: attributes.delete_records ?? [],
|
|
213
|
+
deduplicate: attributes.deduplicate !== false,
|
|
214
|
+
create_new_version: attributes.create_new_version !== false,
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
}
|
|
218
|
+
)
|
|
219
|
+
const resources = Array.isArray(response?.records)
|
|
220
|
+
? response.records
|
|
221
|
+
: (Array.isArray(response?.data) ? response.data : [])
|
|
222
|
+
return datasetMutationResultFromResources(resources)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async listDatasetRecords (projectId, datasetId, options = {}) {
|
|
226
|
+
const query = new URLSearchParams()
|
|
227
|
+
if (options.cursor) query.set('page[cursor]', options.cursor)
|
|
228
|
+
if (options.version !== undefined && options.version !== null) query.set('filter[version]', String(options.version))
|
|
229
|
+
const response = await this.request(
|
|
230
|
+
'GET',
|
|
231
|
+
`${API_BASE_PATH}/${projectId}/datasets/${datasetId}/records?${query.toString()}`
|
|
232
|
+
)
|
|
233
|
+
const records = Array.isArray(response?.data) ? response.data.map(datasetRecordFromResource) : []
|
|
234
|
+
return { records, after: response?.meta?.after ?? '' }
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async createExperiment (attributes) {
|
|
238
|
+
const response = await this.jsonApiRequest('POST', `${API_BASE_PATH}/experiments`, 'experiments', attributes)
|
|
239
|
+
return experimentFromResource(this, response?.data ?? null)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
postExperimentEvents (experimentId, attributes) {
|
|
243
|
+
return this.jsonApiRequest(
|
|
244
|
+
'POST',
|
|
245
|
+
`${API_BASE_PATH}/experiments/${experimentId}/events`,
|
|
246
|
+
'experiments',
|
|
247
|
+
attributes
|
|
248
|
+
)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
updateExperiment (experimentId, attributes) {
|
|
252
|
+
return this.jsonApiRequest('PATCH', `${API_BASE_PATH}/experiments/${experimentId}`, 'experiments', attributes)
|
|
253
|
+
}
|
|
254
|
+
|
|
95
255
|
// Resolve the project id for `name`, creating it if absent. The create
|
|
96
256
|
// endpoint is get-or-create on name, so repeated calls return the same id.
|
|
97
257
|
// Cached after the first resolution.
|
|
@@ -100,14 +260,12 @@ class ExperimentsClient {
|
|
|
100
260
|
|
|
101
261
|
let response
|
|
102
262
|
try {
|
|
103
|
-
response = await this.
|
|
104
|
-
data: { type: 'projects', attributes: { name } },
|
|
105
|
-
})
|
|
263
|
+
response = await this.createProject(name)
|
|
106
264
|
} catch (err) {
|
|
107
265
|
throw new Error(`Failed to create or get project '${name}': ${err.message}`)
|
|
108
266
|
}
|
|
109
267
|
|
|
110
|
-
this.#cachedProjectId = response?.
|
|
268
|
+
this.#cachedProjectId = response?.id ?? null
|
|
111
269
|
return this.#cachedProjectId
|
|
112
270
|
}
|
|
113
271
|
}
|