dd-trace 6.11.0 → 6.13.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 +5 -0
- package/ci/test-optimization-validation/framework-adapters/cypress.js +9 -11
- package/ci/test-optimization-validation/scenarios/ci-wiring.js +5 -4
- package/index.d.ts +143 -25
- package/package.json +26 -25
- package/packages/datadog-instrumentations/src/bunyan.js +4 -2
- package/packages/datadog-instrumentations/src/cucumber.js +4 -4
- package/packages/datadog-instrumentations/src/helpers/bunyan.js +12 -3
- package/packages/datadog-instrumentations/src/helpers/pool-acquire.js +1 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +28 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +1 -0
- package/packages/datadog-instrumentations/src/jest.js +140 -18
- package/packages/datadog-instrumentations/src/mariadb-bundle.js +1597 -0
- package/packages/datadog-instrumentations/src/mariadb.js +7 -0
- package/packages/datadog-instrumentations/src/mocha/utils.js +5 -2
- package/packages/datadog-instrumentations/src/pino.js +15 -4
- package/packages/datadog-instrumentations/src/playwright-reporter.js +149 -1
- package/packages/datadog-instrumentations/src/playwright.js +67 -53
- package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +13 -12
- package/packages/datadog-instrumentations/src/vitest-worker.js +11 -0
- package/packages/datadog-instrumentations/src/webdriverio.js +26 -0
- package/packages/datadog-instrumentations/src/winston.js +6 -3
- package/packages/datadog-plugin-cypress/src/support.js +1 -0
- package/packages/datadog-plugin-http/src/client.js +2 -15
- package/packages/datadog-plugin-http2/src/client.js +2 -15
- package/packages/datadog-plugin-openai-agents/src/integration.js +15 -11
- package/packages/datadog-plugin-playwright/src/index.js +13 -1
- package/packages/datadog-plugin-undici/src/index.js +2 -15
- package/packages/dd-trace/src/aiguard/evaluation.js +94 -20
- package/packages/dd-trace/src/aiguard/integrations/vercel-ai.js +2 -3
- package/packages/dd-trace/src/aiguard/messages/anthropic.js +21 -9
- package/packages/dd-trace/src/aiguard/noop.js +9 -1
- package/packages/dd-trace/src/aiguard/redaction.js +183 -0
- package/packages/dd-trace/src/aiguard/sdk.js +7 -2
- package/packages/dd-trace/src/aiguard/tags.js +2 -0
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +10 -9
- package/packages/dd-trace/src/appsec/reporter.js +2 -0
- package/packages/dd-trace/src/appsec/rule_manager.js +2 -0
- package/packages/dd-trace/src/appsec/telemetry/index.js +13 -0
- package/packages/dd-trace/src/appsec/telemetry/rasp.js +16 -0
- package/packages/dd-trace/src/appsec/telemetry/waf.js +24 -0
- package/packages/dd-trace/src/carrier.js +107 -48
- package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +5 -5
- package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +2 -0
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +5 -3
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/request-tracker.js +51 -26
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +4 -2
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +5 -11
- package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +6 -4
- package/packages/dd-trace/src/ci-visibility/final-flush.js +20 -0
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +5 -3
- package/packages/dd-trace/src/ci-visibility/log-submission/log-submission-plugin.js +196 -29
- package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +4 -2
- package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +4 -2
- package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +4 -2
- package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +5 -5
- package/packages/dd-trace/src/config/generated-config-types.d.ts +6 -0
- package/packages/dd-trace/src/config/index.js +2 -1
- package/packages/dd-trace/src/config/supported-configurations.json +27 -0
- package/packages/dd-trace/src/debugger/constants.js +1 -0
- package/packages/dd-trace/src/debugger/devtools_client/condition.js +1 -1
- package/packages/dd-trace/src/debugger/devtools_client/index.js +3 -10
- package/packages/dd-trace/src/debugger/devtools_client/send.js +4 -3
- package/packages/dd-trace/src/debugger/index.js +4 -2
- package/packages/dd-trace/src/debugger/inspect-segment.js +154 -0
- package/packages/dd-trace/src/dogstatsd.js +45 -18
- package/packages/dd-trace/src/encode/agentless-json.js +5 -4
- package/packages/dd-trace/src/evp_proxy/constants.js +8 -0
- package/packages/dd-trace/src/evp_proxy/direct.js +64 -0
- package/packages/dd-trace/src/evp_proxy/discovery.js +133 -0
- package/packages/dd-trace/src/evp_proxy/path.js +22 -0
- package/packages/dd-trace/src/exporters/agent/index.js +22 -2
- package/packages/dd-trace/src/exporters/agent/writer.js +4 -4
- package/packages/dd-trace/src/exporters/common/request.js +5 -2
- package/packages/dd-trace/src/exporters/common/writer.js +24 -2
- package/packages/dd-trace/src/exporters/span-stats/index.js +23 -4
- package/packages/dd-trace/src/external-logger/src/index.js +4 -3
- package/packages/dd-trace/src/flush.js +102 -0
- package/packages/dd-trace/src/llmobs/experiments/client.js +10 -2
- package/packages/dd-trace/src/llmobs/experiments/dataset.js +301 -14
- package/packages/dd-trace/src/llmobs/experiments/experiment.js +440 -112
- package/packages/dd-trace/src/llmobs/experiments/index.js +68 -44
- package/packages/dd-trace/src/llmobs/experiments/noop.js +103 -12
- package/packages/dd-trace/src/llmobs/experiments/result.js +1 -0
- package/packages/dd-trace/src/llmobs/experiments/util.js +81 -4
- package/packages/dd-trace/src/llmobs/index.js +80 -17
- package/packages/dd-trace/src/llmobs/noop.js +2 -1
- package/packages/dd-trace/src/llmobs/plugins/anthropic/util.js +8 -4
- package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/index.js +6 -3
- package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +13 -6
- package/packages/dd-trace/src/llmobs/plugins/openai-agents/utils.js +5 -7
- package/packages/dd-trace/src/llmobs/writers/base.js +60 -24
- package/packages/dd-trace/src/noop/span.js +5 -0
- package/packages/dd-trace/src/openfeature/constants/constants.js +0 -18
- package/packages/dd-trace/src/openfeature/index.js +7 -4
- package/packages/dd-trace/src/openfeature/writers/base.js +153 -11
- package/packages/dd-trace/src/openfeature/writers/exposures.js +70 -15
- package/packages/dd-trace/src/openfeature/writers/util.js +97 -15
- package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +45 -3
- package/packages/dd-trace/src/opentelemetry/logs/index.js +4 -1
- package/packages/dd-trace/src/opentelemetry/logs/logger_provider.js +7 -4
- package/packages/dd-trace/src/opentelemetry/metrics/index.js +3 -1
- package/packages/dd-trace/src/opentelemetry/metrics/meter_provider.js +8 -0
- package/packages/dd-trace/src/opentelemetry/metrics/otlp_http_metric_exporter.js +4 -2
- package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +4 -2
- package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +25 -5
- package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +61 -25
- package/packages/dd-trace/src/opentelemetry/tracer.js +26 -2
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +429 -352
- package/packages/dd-trace/src/opentracing/span.js +28 -0
- package/packages/dd-trace/src/otel-thread-ctx.js +23 -5
- package/packages/dd-trace/src/plugin_manager.js +2 -0
- package/packages/dd-trace/src/plugins/util/status-validator.js +98 -0
- package/packages/dd-trace/src/plugins/util/test.js +74 -21
- package/packages/dd-trace/src/plugins/util/web.js +2 -65
- package/packages/dd-trace/src/profiling/oom.js +4 -3
- package/packages/dd-trace/src/profiling/profiler.js +7 -0
- package/packages/dd-trace/src/profiling/profilers/events.js +3 -3
- package/packages/dd-trace/src/profiling/profilers/wall.js +14 -14
- package/packages/dd-trace/src/proxy.js +36 -8
- package/packages/dd-trace/src/runtime_metrics/index.js +5 -0
- package/packages/dd-trace/src/runtime_metrics/otlp_runtime_metrics.js +5 -0
- package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +17 -6
- package/packages/dd-trace/src/serverless/telemetry-delivery-tracker.js +55 -0
- package/packages/dd-trace/src/serverless/vercel.js +122 -0
- package/packages/dd-trace/src/serverless.js +37 -20
- package/packages/dd-trace/src/span_stats.js +40 -3
- package/packages/dd-trace/src/telemetry/metrics.js +62 -8
- package/packages/dd-trace/src/telemetry/send-data.js +1 -0
- package/packages/dd-trace/src/tracer.js +22 -0
- package/packages/dd-trace/src/web-tags-cache.js +67 -13
- package/vendor/dist/@datadog/openfeature-node-server/index.js +1 -1
- package/vendor/dist/@datadog/sketches-js/index.js +1 -1
- package/vendor/dist/https-proxy-agent/LICENSE +22 -0
- package/vendor/dist/https-proxy-agent/index.js +4 -0
- package/vendor/dist/protobufjs/index.js +1 -1
- package/vendor/dist/protobufjs/minimal/index.js +1 -1
- package/vendor/dist/proxy-from-env/LICENSE +20 -0
- package/vendor/dist/proxy-from-env/index.js +1 -0
|
@@ -8,19 +8,24 @@ const {
|
|
|
8
8
|
buildSpanMetadata,
|
|
9
9
|
buildTags,
|
|
10
10
|
durationNs,
|
|
11
|
+
generateRunId,
|
|
11
12
|
hasEntries,
|
|
12
13
|
inferMetricType,
|
|
13
|
-
normalizeEvaluators,
|
|
14
14
|
mergeTags,
|
|
15
|
+
normalizeEvaluators,
|
|
15
16
|
normalizeJsonMetricValue,
|
|
17
|
+
normalizePositiveInteger,
|
|
18
|
+
recordTagsToObject,
|
|
16
19
|
sleep,
|
|
17
20
|
stringify,
|
|
18
21
|
timestampMs,
|
|
19
22
|
validateEvaluatorName,
|
|
20
23
|
} = require('./util')
|
|
21
24
|
|
|
25
|
+
const TASK_ERROR_MESSAGE = 'task error; evaluation skipped'
|
|
26
|
+
|
|
22
27
|
// One span per experiment row (LLM Obs experiment span wire format).
|
|
23
|
-
function toSpan (row, metadata, ids, spanName, userTags) {
|
|
28
|
+
function toSpan (row, metadata, ids, spanName, userTags, recordTags) {
|
|
24
29
|
const meta = {
|
|
25
30
|
input: row.input ?? null,
|
|
26
31
|
output: row.output ?? null,
|
|
@@ -33,6 +38,21 @@ function toSpan (row, metadata, ids, spanName, userTags) {
|
|
|
33
38
|
meta.error = { type: row.errorType ?? '', message: row.errorMessage ?? '', stack: row.errorStack ?? '' }
|
|
34
39
|
}
|
|
35
40
|
|
|
41
|
+
const tags = buildTags({
|
|
42
|
+
...userTags,
|
|
43
|
+
...recordTagsToObject(recordTags),
|
|
44
|
+
}, {
|
|
45
|
+
experiment_id: ids.experimentId,
|
|
46
|
+
run_id: ids.runId,
|
|
47
|
+
run_iteration: ids.runIteration,
|
|
48
|
+
project_id: ids.projectId,
|
|
49
|
+
dataset_id: ids.datasetId,
|
|
50
|
+
dataset_record_id: ids.datasetRecordId,
|
|
51
|
+
dataset_name: ids.datasetName,
|
|
52
|
+
experiment_name: ids.experimentName,
|
|
53
|
+
project_name: ids.projectName,
|
|
54
|
+
})
|
|
55
|
+
|
|
36
56
|
return {
|
|
37
57
|
span_id: row.spanId,
|
|
38
58
|
trace_id: row.traceId,
|
|
@@ -43,19 +63,13 @@ function toSpan (row, metadata, ids, spanName, userTags) {
|
|
|
43
63
|
duration: row.durationNs,
|
|
44
64
|
status: row.isError ? 'error' : 'ok',
|
|
45
65
|
meta,
|
|
46
|
-
tags
|
|
47
|
-
experiment_id: ids.experimentId,
|
|
48
|
-
run_id: ids.runId,
|
|
49
|
-
run_iteration: ids.runIteration,
|
|
50
|
-
dataset_id: ids.datasetId,
|
|
51
|
-
dataset_record_id: ids.datasetRecordId,
|
|
52
|
-
}),
|
|
66
|
+
tags,
|
|
53
67
|
}
|
|
54
68
|
}
|
|
55
69
|
|
|
56
70
|
// One metric per evaluator per row or summary evaluator.
|
|
57
71
|
function toMetric (
|
|
58
|
-
label, value, errorMessage, spanId, traceId, timestampMs, experimentId, userTags, source = 'custom'
|
|
72
|
+
label, value, errorMessage, spanId, traceId, timestampMs, experimentId, userTags, source = 'custom', ids = {}
|
|
59
73
|
) {
|
|
60
74
|
const metric = {
|
|
61
75
|
metric_source: source,
|
|
@@ -63,7 +77,11 @@ function toMetric (
|
|
|
63
77
|
span_id: spanId,
|
|
64
78
|
trace_id: traceId,
|
|
65
79
|
timestamp_ms: timestampMs,
|
|
66
|
-
tags: buildTags(userTags, {
|
|
80
|
+
tags: buildTags(userTags, {
|
|
81
|
+
experiment_id: experimentId,
|
|
82
|
+
run_id: ids.runId,
|
|
83
|
+
run_iteration: ids.runIteration,
|
|
84
|
+
}),
|
|
67
85
|
experiment_id: experimentId,
|
|
68
86
|
}
|
|
69
87
|
|
|
@@ -115,8 +133,43 @@ function errorMessage (error) {
|
|
|
115
133
|
return error.message ?? String(error)
|
|
116
134
|
}
|
|
117
135
|
|
|
118
|
-
|
|
119
|
-
|
|
136
|
+
function createLimiter (concurrency) {
|
|
137
|
+
const waiting = []
|
|
138
|
+
let active = 0
|
|
139
|
+
let cancellation
|
|
140
|
+
|
|
141
|
+
const limit = async function limit (fn, cancelOnError = false) {
|
|
142
|
+
if (cancellation !== undefined) throw cancellation.error
|
|
143
|
+
if (active >= concurrency) {
|
|
144
|
+
await new Promise((resolve, reject) => waiting.push({ resolve, reject }))
|
|
145
|
+
}
|
|
146
|
+
if (cancellation !== undefined) throw cancellation.error
|
|
147
|
+
active++
|
|
148
|
+
try {
|
|
149
|
+
return await fn()
|
|
150
|
+
} catch (error) {
|
|
151
|
+
if (cancelOnError) limit.cancel(error)
|
|
152
|
+
throw error
|
|
153
|
+
} finally {
|
|
154
|
+
active--
|
|
155
|
+
const next = waiting.shift()
|
|
156
|
+
if (next !== undefined) next.resolve()
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
limit.cancel = (error) => {
|
|
161
|
+
if (cancellation !== undefined) return
|
|
162
|
+
cancellation = { error }
|
|
163
|
+
const queued = [...waiting]
|
|
164
|
+
waiting.length = 0
|
|
165
|
+
for (const waiter of queued) waiter.reject(error)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return limit
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Builder + run() orchestration: emits one root span per dataset row and
|
|
172
|
+
// posts spans + metrics to the experiments events API.
|
|
120
173
|
class Experiment {
|
|
121
174
|
#client
|
|
122
175
|
#llmobs
|
|
@@ -130,6 +183,8 @@ class Experiment {
|
|
|
130
183
|
#config
|
|
131
184
|
#tags
|
|
132
185
|
#metadata
|
|
186
|
+
#runs
|
|
187
|
+
#projectName
|
|
133
188
|
#projectId
|
|
134
189
|
#experimentId
|
|
135
190
|
#runId
|
|
@@ -152,8 +207,13 @@ class Experiment {
|
|
|
152
207
|
this.#evaluators = normalizeEvaluators(options.evaluators, 'row')
|
|
153
208
|
this.#summaryEvaluators = normalizeEvaluators(options.summaryEvaluators, 'summary')
|
|
154
209
|
this.#config = { ...options.config }
|
|
210
|
+
const filterTags = this.#dataset.filterTags?.() ?? []
|
|
211
|
+
if (filterTags.length > 0) this.#config.filtered_record_tags = filterTags
|
|
212
|
+
this.#projectName = options.projectName
|
|
155
213
|
this.#tags = { ...options.tags }
|
|
214
|
+
if (this.#projectName !== undefined) this.#tags.project_name = this.#projectName
|
|
156
215
|
this.#metadata = { ...options.metadata }
|
|
216
|
+
this.#runs = this.#external ? 1 : normalizePositiveInteger(options.runs ?? 1, 'runs')
|
|
157
217
|
this.#projectId = null
|
|
158
218
|
this.#experimentId = null
|
|
159
219
|
this.#runId = null
|
|
@@ -270,6 +330,7 @@ class Experiment {
|
|
|
270
330
|
projectId: this.#projectId,
|
|
271
331
|
datasetId: this.#dataset.id,
|
|
272
332
|
datasetRecordId: input.datasetRecordId,
|
|
333
|
+
projectName: this.#projectName,
|
|
273
334
|
runId: input.runId ?? this.#runId,
|
|
274
335
|
runIteration: input.runIteration ?? this.#runIteration,
|
|
275
336
|
}, input.name ?? this.#name, mergeTags(this.#tags, input.tags))
|
|
@@ -313,6 +374,8 @@ class Experiment {
|
|
|
313
374
|
log.warn('LLMObs experiments: skipping external metric %s because it has neither value nor error', metric.label)
|
|
314
375
|
continue
|
|
315
376
|
}
|
|
377
|
+
const metricTags = mergeTags(this.#tags, metric.tags)
|
|
378
|
+
if (this.#projectName !== undefined) metricTags.project_name = this.#projectName
|
|
316
379
|
payload.push(toMetric(
|
|
317
380
|
metric.label,
|
|
318
381
|
metric.value,
|
|
@@ -321,7 +384,7 @@ class Experiment {
|
|
|
321
384
|
span.traceId,
|
|
322
385
|
timestampMs(metric.timestamp),
|
|
323
386
|
experimentId,
|
|
324
|
-
|
|
387
|
+
metricTags,
|
|
325
388
|
metric.source ?? 'custom'
|
|
326
389
|
))
|
|
327
390
|
}
|
|
@@ -348,10 +411,12 @@ class Experiment {
|
|
|
348
411
|
maxRetries = 0,
|
|
349
412
|
retryDelay = (attempt) => 100 * (attempt + 1),
|
|
350
413
|
throwOnErrors = false,
|
|
414
|
+
concurrency = 10,
|
|
351
415
|
} = options
|
|
352
416
|
|
|
353
417
|
if (maxRetries < 0) throw new Error('maxRetries must be >= 0')
|
|
354
418
|
if (typeof retryDelay !== 'function') throw new TypeError('retryDelay must be a function')
|
|
419
|
+
const concurrencyLimit = normalizePositiveInteger(concurrency, 'concurrency')
|
|
355
420
|
|
|
356
421
|
const projectId = await this.#client.ensureProjectId()
|
|
357
422
|
|
|
@@ -369,13 +434,12 @@ class Experiment {
|
|
|
369
434
|
dataset_id: datasetId,
|
|
370
435
|
description: this.#description,
|
|
371
436
|
ensure_unique: true,
|
|
372
|
-
run_count:
|
|
437
|
+
run_count: this.#runs,
|
|
373
438
|
metadata: { tags: buildTags(this.#tags, {}) },
|
|
374
439
|
}
|
|
375
440
|
const datasetVersion = this.#dataset.version()
|
|
376
441
|
if (datasetVersion !== null) attributes.dataset_version = datasetVersion
|
|
377
|
-
|
|
378
|
-
if (Object.keys(this.#config).length > 0) attributes.config = this.#config
|
|
442
|
+
if (hasEntries(this.#config)) attributes.config = this.#config
|
|
379
443
|
|
|
380
444
|
let created
|
|
381
445
|
try {
|
|
@@ -385,28 +449,21 @@ class Experiment {
|
|
|
385
449
|
}
|
|
386
450
|
this.#experimentId = created.experimentId
|
|
387
451
|
const experimentId = this.#experimentId
|
|
388
|
-
const runId = id().toString(16).padStart(16, '0')
|
|
389
|
-
const runIteration = 0
|
|
390
452
|
|
|
391
453
|
try {
|
|
392
454
|
const records = this.#dataset.records()
|
|
393
455
|
const recordIds = this.#dataset.recordIds()
|
|
394
|
-
const rows = []
|
|
395
|
-
const spans = []
|
|
396
|
-
const metrics = []
|
|
397
|
-
const evaluatorResults = {}
|
|
398
456
|
const usesLLMObsTrace = Boolean(this.#llmobs?.enabled)
|
|
399
|
-
|
|
457
|
+
const runs = []
|
|
458
|
+
let hasRunError = false
|
|
400
459
|
|
|
401
|
-
for (let
|
|
402
|
-
const
|
|
403
|
-
const
|
|
404
|
-
// Rows currently run sequentially by design; jobs/concurrency is a P1 follow-up.
|
|
460
|
+
for (let runIndex = 0; runIndex < this.#runs; runIndex++) {
|
|
461
|
+
const runId = generateRunId()
|
|
462
|
+
const runIteration = runIndex + 1
|
|
405
463
|
// eslint-disable-next-line no-await-in-loop
|
|
406
|
-
const
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
datasetRecordId,
|
|
464
|
+
const result = await this.#runSingle({
|
|
465
|
+
records,
|
|
466
|
+
recordIds,
|
|
410
467
|
projectId,
|
|
411
468
|
datasetId,
|
|
412
469
|
experimentId,
|
|
@@ -415,78 +472,296 @@ class Experiment {
|
|
|
415
472
|
maxRetries,
|
|
416
473
|
retryDelay,
|
|
417
474
|
throwOnErrors,
|
|
475
|
+
concurrency: concurrencyLimit,
|
|
476
|
+
usesLLMObsTrace,
|
|
418
477
|
})
|
|
478
|
+
runs.push(result.run)
|
|
479
|
+
// Submit each run before starting the next iteration so results are available incrementally.
|
|
480
|
+
// eslint-disable-next-line no-await-in-loop
|
|
481
|
+
await this.#postEvents(experimentId, result.spans, result.metrics)
|
|
482
|
+
this.#llmobs?.flush?.()
|
|
483
|
+
if (result.hasRowError) hasRunError = true
|
|
484
|
+
}
|
|
419
485
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
evaluatorResults[label].push(null)
|
|
427
|
-
metrics.push(toMetric(label, null, msg, row.spanId, row.traceId, timestampMs, experimentId, this.#tags))
|
|
428
|
-
continue
|
|
429
|
-
}
|
|
430
|
-
try {
|
|
431
|
-
// eslint-disable-next-line no-await-in-loop
|
|
432
|
-
const value = await this.#runWithRetries(
|
|
433
|
-
() => evaluator(record.input, row.output, record.expectedOutput),
|
|
434
|
-
maxRetries,
|
|
435
|
-
retryDelay
|
|
436
|
-
)
|
|
437
|
-
row.evaluations[label] = value
|
|
438
|
-
evaluatorResults[label].push(value)
|
|
439
|
-
metrics.push(toMetric(label, value, null, row.spanId, row.traceId, timestampMs, experimentId, this.#tags))
|
|
440
|
-
} catch (err) {
|
|
441
|
-
if (throwOnErrors) throw err
|
|
442
|
-
const msg = err.message ?? String(err)
|
|
443
|
-
row.evaluationErrors[label] = msg
|
|
444
|
-
evaluatorResults[label].push(null)
|
|
445
|
-
metrics.push(toMetric(label, null, msg, row.spanId, row.traceId, timestampMs, experimentId, this.#tags))
|
|
446
|
-
}
|
|
447
|
-
}
|
|
486
|
+
// A row error doesn't abort the run, but the experiment didn't succeed cleanly.
|
|
487
|
+
await this.#updateStatus(
|
|
488
|
+
experimentId,
|
|
489
|
+
hasRunError ? 'failed' : 'completed',
|
|
490
|
+
hasRunError ? 'one or more rows failed' : null
|
|
491
|
+
)
|
|
448
492
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
493
|
+
const firstRun = runs[0]
|
|
494
|
+
return new ExperimentResult(
|
|
495
|
+
experimentId,
|
|
496
|
+
firstRun?.rows ?? [],
|
|
497
|
+
this.url(),
|
|
498
|
+
runs,
|
|
499
|
+
firstRun?.summaryEvaluations ?? {}
|
|
500
|
+
)
|
|
501
|
+
} catch (err) {
|
|
502
|
+
await this.#updateStatus(experimentId, 'failed', err.message ?? String(err))
|
|
503
|
+
throw err
|
|
504
|
+
}
|
|
505
|
+
}
|
|
462
506
|
|
|
463
|
-
|
|
507
|
+
async #runSingle ({
|
|
508
|
+
records,
|
|
509
|
+
recordIds,
|
|
510
|
+
projectId,
|
|
511
|
+
datasetId,
|
|
512
|
+
experimentId,
|
|
513
|
+
runId,
|
|
514
|
+
runIteration,
|
|
515
|
+
maxRetries,
|
|
516
|
+
retryDelay,
|
|
517
|
+
throwOnErrors,
|
|
518
|
+
concurrency,
|
|
519
|
+
usesLLMObsTrace,
|
|
520
|
+
}) {
|
|
521
|
+
const limit = createLimiter(concurrency)
|
|
522
|
+
const results = await this.#mapRecords(records, (index) => {
|
|
523
|
+
const record = records[index]
|
|
524
|
+
const datasetRecordId = index < recordIds.length ? recordIds[index] : ''
|
|
525
|
+
return this.#processRecordWithEvaluators({
|
|
526
|
+
index,
|
|
527
|
+
record,
|
|
528
|
+
datasetRecordId,
|
|
529
|
+
projectId,
|
|
530
|
+
datasetId,
|
|
531
|
+
experimentId,
|
|
532
|
+
runId,
|
|
533
|
+
runIteration,
|
|
464
534
|
maxRetries,
|
|
465
535
|
retryDelay,
|
|
466
536
|
throwOnErrors,
|
|
467
|
-
|
|
468
|
-
metrics,
|
|
537
|
+
limit,
|
|
469
538
|
})
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
539
|
+
}, limit, concurrency, throwOnErrors)
|
|
540
|
+
|
|
541
|
+
const rows = new Array(results.length)
|
|
542
|
+
const spans = []
|
|
543
|
+
const metrics = []
|
|
544
|
+
const evaluatorResults = {}
|
|
545
|
+
let hasRowError = false
|
|
546
|
+
for (const [label] of this.#evaluators) evaluatorResults[label] = []
|
|
547
|
+
|
|
548
|
+
for (let i = 0; i < results.length; i++) {
|
|
549
|
+
const result = results[i]
|
|
550
|
+
const row = result.row
|
|
551
|
+
rows[i] = row
|
|
552
|
+
for (const [label] of this.#evaluators) {
|
|
553
|
+
const value = Object.hasOwn(result.evaluatorValues, label) ? result.evaluatorValues[label] : null
|
|
554
|
+
evaluatorResults[label].push(value)
|
|
555
|
+
}
|
|
556
|
+
for (const metric of result.metrics) metrics.push(metric)
|
|
557
|
+
if (result.hasRowError) hasRowError = true
|
|
558
|
+
if (!usesLLMObsTrace) {
|
|
559
|
+
spans.push(toSpan(row, records[i].metadata, {
|
|
560
|
+
experimentId,
|
|
561
|
+
projectId,
|
|
562
|
+
datasetId,
|
|
563
|
+
datasetRecordId: i < recordIds.length ? recordIds[i] : '',
|
|
564
|
+
datasetName: this.#dataset.name(),
|
|
565
|
+
experimentName: this.#name,
|
|
566
|
+
projectName: this.#projectName,
|
|
567
|
+
runId,
|
|
568
|
+
runIteration,
|
|
569
|
+
}, this.#task.name || this.#name, this.#tags, records[i].tags))
|
|
474
570
|
}
|
|
571
|
+
}
|
|
475
572
|
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
573
|
+
const summaryEvaluations = await this.#runSummaryEvaluators(rows, records, evaluatorResults, {
|
|
574
|
+
maxRetries,
|
|
575
|
+
retryDelay,
|
|
576
|
+
throwOnErrors,
|
|
577
|
+
experimentId,
|
|
578
|
+
runId,
|
|
579
|
+
runIteration,
|
|
580
|
+
metrics,
|
|
581
|
+
limit,
|
|
582
|
+
})
|
|
583
|
+
if (hasEntries(summaryEvaluations)) {
|
|
584
|
+
for (const value of Object.values(summaryEvaluations)) {
|
|
585
|
+
if (value?.error !== null && value?.error !== undefined) hasRowError = true
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
return {
|
|
590
|
+
run: new ExperimentRun({ runId, runIteration, hasError: hasRowError, rows, summaryEvaluations }),
|
|
591
|
+
spans,
|
|
592
|
+
metrics,
|
|
593
|
+
hasRowError,
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
async #mapRecords (records, processRecord, limit, concurrency, throwOnErrors) {
|
|
598
|
+
const results = new Array(records.length)
|
|
599
|
+
let nextIndex = 0
|
|
600
|
+
|
|
601
|
+
const worker = async () => {
|
|
602
|
+
while (nextIndex < records.length) {
|
|
603
|
+
const index = nextIndex++
|
|
604
|
+
// eslint-disable-next-line no-await-in-loop -- each worker processes one record at a time
|
|
605
|
+
results[index] = await processRecord(index)
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
const workers = new Array(Math.min(concurrency, records.length))
|
|
610
|
+
for (let i = 0; i < workers.length; i++) workers[i] = worker()
|
|
611
|
+
|
|
612
|
+
try {
|
|
613
|
+
await Promise.all(workers)
|
|
614
|
+
} catch (error) {
|
|
615
|
+
if (throwOnErrors) limit.cancel(error)
|
|
616
|
+
throw error
|
|
617
|
+
}
|
|
618
|
+
return results
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
async #processRecordWithEvaluators ({
|
|
622
|
+
index,
|
|
623
|
+
record,
|
|
624
|
+
datasetRecordId,
|
|
625
|
+
projectId,
|
|
626
|
+
datasetId,
|
|
627
|
+
experimentId,
|
|
628
|
+
runId,
|
|
629
|
+
runIteration,
|
|
630
|
+
maxRetries,
|
|
631
|
+
retryDelay,
|
|
632
|
+
throwOnErrors,
|
|
633
|
+
limit,
|
|
634
|
+
}) {
|
|
635
|
+
const row = await limit(() => this.#processRecord({
|
|
636
|
+
index,
|
|
637
|
+
record,
|
|
638
|
+
datasetRecordId,
|
|
639
|
+
projectId,
|
|
640
|
+
datasetId,
|
|
641
|
+
experimentId,
|
|
642
|
+
runId,
|
|
643
|
+
runIteration,
|
|
644
|
+
maxRetries,
|
|
645
|
+
retryDelay,
|
|
646
|
+
throwOnErrors,
|
|
647
|
+
}), throwOnErrors)
|
|
648
|
+
const timestampMs = Date.now()
|
|
649
|
+
const metrics = []
|
|
650
|
+
const evaluatorValues = {}
|
|
651
|
+
let firstError
|
|
652
|
+
|
|
653
|
+
const pending = new Array(this.#evaluators.length)
|
|
654
|
+
for (let i = 0; i < this.#evaluators.length; i++) {
|
|
655
|
+
const [label, evaluator] = this.#evaluators[i]
|
|
656
|
+
pending[i] = this.#runRowEvaluator({
|
|
657
|
+
label,
|
|
658
|
+
evaluator,
|
|
659
|
+
row,
|
|
660
|
+
record,
|
|
661
|
+
timestampMs,
|
|
480
662
|
experimentId,
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
663
|
+
runId,
|
|
664
|
+
runIteration,
|
|
665
|
+
maxRetries,
|
|
666
|
+
retryDelay,
|
|
667
|
+
throwOnErrors,
|
|
668
|
+
limit,
|
|
669
|
+
})
|
|
670
|
+
}
|
|
484
671
|
|
|
485
|
-
|
|
486
|
-
|
|
672
|
+
const evaluatorResults = await Promise.all(pending)
|
|
673
|
+
for (const result of evaluatorResults) {
|
|
674
|
+
if (result.metric !== null) metrics.push(result.metric)
|
|
675
|
+
evaluatorValues[result.label] = result.value
|
|
676
|
+
if (result.error !== undefined && firstError === undefined) firstError = result.error
|
|
677
|
+
}
|
|
678
|
+
if (firstError !== undefined) throw firstError
|
|
679
|
+
|
|
680
|
+
return {
|
|
681
|
+
row,
|
|
682
|
+
metrics,
|
|
683
|
+
evaluatorValues,
|
|
684
|
+
hasRowError: row.isError || hasEntries(row.evaluationErrors),
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
async #runRowEvaluator ({
|
|
689
|
+
label,
|
|
690
|
+
evaluator,
|
|
691
|
+
row,
|
|
692
|
+
record,
|
|
693
|
+
timestampMs,
|
|
694
|
+
experimentId,
|
|
695
|
+
runId,
|
|
696
|
+
runIteration,
|
|
697
|
+
maxRetries,
|
|
698
|
+
retryDelay,
|
|
699
|
+
throwOnErrors,
|
|
700
|
+
limit,
|
|
701
|
+
}) {
|
|
702
|
+
if (row.isError) {
|
|
703
|
+
row.evaluationErrors[label] = TASK_ERROR_MESSAGE
|
|
704
|
+
return {
|
|
705
|
+
label,
|
|
706
|
+
value: null,
|
|
707
|
+
metric: toMetric(
|
|
708
|
+
label,
|
|
709
|
+
null,
|
|
710
|
+
TASK_ERROR_MESSAGE,
|
|
711
|
+
row.spanId,
|
|
712
|
+
row.traceId,
|
|
713
|
+
timestampMs,
|
|
714
|
+
experimentId,
|
|
715
|
+
this.#tags,
|
|
716
|
+
'custom',
|
|
717
|
+
{ runId, runIteration }
|
|
718
|
+
),
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
try {
|
|
723
|
+
const value = await limit(() => this.#runWithRetries(
|
|
724
|
+
() => evaluator(record.input, row.output, record.expectedOutput),
|
|
725
|
+
maxRetries,
|
|
726
|
+
retryDelay
|
|
727
|
+
), throwOnErrors)
|
|
728
|
+
row.evaluations[label] = value
|
|
729
|
+
return {
|
|
730
|
+
label,
|
|
731
|
+
value,
|
|
732
|
+
metric: toMetric(
|
|
733
|
+
label,
|
|
734
|
+
value,
|
|
735
|
+
null,
|
|
736
|
+
row.spanId,
|
|
737
|
+
row.traceId,
|
|
738
|
+
timestampMs,
|
|
739
|
+
experimentId,
|
|
740
|
+
this.#tags,
|
|
741
|
+
'custom',
|
|
742
|
+
{ runId, runIteration }
|
|
743
|
+
),
|
|
744
|
+
}
|
|
487
745
|
} catch (err) {
|
|
488
|
-
|
|
489
|
-
|
|
746
|
+
if (throwOnErrors) throw err
|
|
747
|
+
const msg = err.message ?? String(err)
|
|
748
|
+
row.evaluationErrors[label] = msg
|
|
749
|
+
return {
|
|
750
|
+
label,
|
|
751
|
+
value: null,
|
|
752
|
+
metric: toMetric(
|
|
753
|
+
label,
|
|
754
|
+
null,
|
|
755
|
+
msg,
|
|
756
|
+
row.spanId,
|
|
757
|
+
row.traceId,
|
|
758
|
+
timestampMs,
|
|
759
|
+
experimentId,
|
|
760
|
+
this.#tags,
|
|
761
|
+
'custom',
|
|
762
|
+
{ runId, runIteration }
|
|
763
|
+
),
|
|
764
|
+
}
|
|
490
765
|
}
|
|
491
766
|
}
|
|
492
767
|
|
|
@@ -521,7 +796,8 @@ class Experiment {
|
|
|
521
796
|
dataset_name: this.#dataset.name(),
|
|
522
797
|
experiment_name: this.#name,
|
|
523
798
|
}
|
|
524
|
-
|
|
799
|
+
if (this.#projectName !== undefined) autoTags.project_name = this.#projectName
|
|
800
|
+
const tags = mergeTags(this.#tags, { ...recordTagsToObject(record.tags), ...autoTags })
|
|
525
801
|
|
|
526
802
|
const execute = () => this.#runWithRetries(
|
|
527
803
|
() => this.#task(record.input, this.#config, record.metadata),
|
|
@@ -615,17 +891,65 @@ class Experiment {
|
|
|
615
891
|
const metadata = records.map(record => buildSpanMetadata(record.metadata, this.#config))
|
|
616
892
|
const summaryEvaluations = {}
|
|
617
893
|
const timestampMs = Date.now()
|
|
894
|
+
const pending = new Array(this.#summaryEvaluators.length)
|
|
895
|
+
let firstError
|
|
896
|
+
|
|
897
|
+
for (let i = 0; i < this.#summaryEvaluators.length; i++) {
|
|
898
|
+
const [label, evaluator] = this.#summaryEvaluators[i]
|
|
899
|
+
pending[i] = this.#runSummaryEvaluator({
|
|
900
|
+
label,
|
|
901
|
+
evaluator,
|
|
902
|
+
inputs,
|
|
903
|
+
outputs,
|
|
904
|
+
expectedOutputs,
|
|
905
|
+
evaluatorResults,
|
|
906
|
+
metadata,
|
|
907
|
+
timestampMs,
|
|
908
|
+
options,
|
|
909
|
+
})
|
|
910
|
+
}
|
|
618
911
|
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
912
|
+
let results
|
|
913
|
+
try {
|
|
914
|
+
results = await Promise.all(pending)
|
|
915
|
+
} catch (err) {
|
|
916
|
+
if (options.throwOnErrors) options.limit.cancel(err)
|
|
917
|
+
throw err
|
|
918
|
+
}
|
|
919
|
+
for (const result of results) {
|
|
920
|
+
if (result.error !== undefined) {
|
|
921
|
+
if (firstError === undefined) firstError = result.error
|
|
922
|
+
continue
|
|
923
|
+
}
|
|
924
|
+
summaryEvaluations[result.label] = result.evaluation
|
|
925
|
+
options.metrics.push(result.metric)
|
|
926
|
+
}
|
|
927
|
+
if (firstError !== undefined) throw firstError
|
|
928
|
+
|
|
929
|
+
return summaryEvaluations
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
async #runSummaryEvaluator ({
|
|
933
|
+
label,
|
|
934
|
+
evaluator,
|
|
935
|
+
inputs,
|
|
936
|
+
outputs,
|
|
937
|
+
expectedOutputs,
|
|
938
|
+
evaluatorResults,
|
|
939
|
+
metadata,
|
|
940
|
+
timestampMs,
|
|
941
|
+
options,
|
|
942
|
+
}) {
|
|
943
|
+
try {
|
|
944
|
+
const value = await options.limit(() => this.#runWithRetries(
|
|
945
|
+
() => evaluator(inputs, outputs, expectedOutputs, evaluatorResults, metadata),
|
|
946
|
+
options.maxRetries,
|
|
947
|
+
options.retryDelay
|
|
948
|
+
), options.throwOnErrors)
|
|
949
|
+
return {
|
|
950
|
+
label,
|
|
951
|
+
evaluation: { value, error: null },
|
|
952
|
+
metric: toMetric(
|
|
629
953
|
label,
|
|
630
954
|
value,
|
|
631
955
|
null,
|
|
@@ -634,13 +958,17 @@ class Experiment {
|
|
|
634
958
|
timestampMs,
|
|
635
959
|
options.experimentId,
|
|
636
960
|
this.#tags,
|
|
637
|
-
'summary'
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
961
|
+
'summary',
|
|
962
|
+
{ runId: options.runId, runIteration: options.runIteration }
|
|
963
|
+
),
|
|
964
|
+
}
|
|
965
|
+
} catch (err) {
|
|
966
|
+
if (options.throwOnErrors) throw err
|
|
967
|
+
const msg = err.message ?? String(err)
|
|
968
|
+
return {
|
|
969
|
+
label,
|
|
970
|
+
evaluation: { value: null, error: msg },
|
|
971
|
+
metric: toMetric(
|
|
644
972
|
label,
|
|
645
973
|
null,
|
|
646
974
|
msg,
|
|
@@ -649,11 +977,11 @@ class Experiment {
|
|
|
649
977
|
timestampMs,
|
|
650
978
|
options.experimentId,
|
|
651
979
|
this.#tags,
|
|
652
|
-
'summary'
|
|
653
|
-
|
|
980
|
+
'summary',
|
|
981
|
+
{ runId: options.runId, runIteration: options.runIteration }
|
|
982
|
+
),
|
|
654
983
|
}
|
|
655
984
|
}
|
|
656
|
-
return summaryEvaluations
|
|
657
985
|
}
|
|
658
986
|
|
|
659
987
|
async #postEvents (experimentId, spans, metrics) {
|