dd-trace 6.12.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/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 +127 -26
- package/package.json +10 -10
- package/packages/datadog-instrumentations/src/bunyan.js +4 -2
- 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/pino.js +15 -4
- package/packages/datadog-instrumentations/src/playwright.js +34 -20
- package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +7 -4
- 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 +9 -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/rule_manager.js +2 -0
- package/packages/dd-trace/src/ci-visibility/exporters/agentless/request-tracker.js +51 -26
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +5 -11
- package/packages/dd-trace/src/ci-visibility/final-flush.js +20 -0
- package/packages/dd-trace/src/ci-visibility/log-submission/log-submission-plugin.js +196 -29
- 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/devtools_client/send.js +4 -3
- 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/exporters/agent/index.js +22 -2
- package/packages/dd-trace/src/exporters/agent/writer.js +4 -4
- 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 +4 -0
- package/packages/dd-trace/src/llmobs/experiments/dataset.js +51 -2
- package/packages/dd-trace/src/llmobs/experiments/experiment.js +420 -105
- package/packages/dd-trace/src/llmobs/experiments/index.js +54 -42
- package/packages/dd-trace/src/llmobs/experiments/noop.js +39 -8
- package/packages/dd-trace/src/llmobs/experiments/result.js +1 -0
- package/packages/dd-trace/src/llmobs/experiments/util.js +21 -0
- 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/writers/exposures.js +8 -1
- 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 +410 -342
- 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/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
|
@@ -8,11 +8,13 @@ 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,
|
|
16
18
|
recordTagsToObject,
|
|
17
19
|
sleep,
|
|
18
20
|
stringify,
|
|
@@ -20,6 +22,8 @@ const {
|
|
|
20
22
|
validateEvaluatorName,
|
|
21
23
|
} = require('./util')
|
|
22
24
|
|
|
25
|
+
const TASK_ERROR_MESSAGE = 'task error; evaluation skipped'
|
|
26
|
+
|
|
23
27
|
// One span per experiment row (LLM Obs experiment span wire format).
|
|
24
28
|
function toSpan (row, metadata, ids, spanName, userTags, recordTags) {
|
|
25
29
|
const meta = {
|
|
@@ -46,6 +50,7 @@ function toSpan (row, metadata, ids, spanName, userTags, recordTags) {
|
|
|
46
50
|
dataset_record_id: ids.datasetRecordId,
|
|
47
51
|
dataset_name: ids.datasetName,
|
|
48
52
|
experiment_name: ids.experimentName,
|
|
53
|
+
project_name: ids.projectName,
|
|
49
54
|
})
|
|
50
55
|
|
|
51
56
|
return {
|
|
@@ -64,7 +69,7 @@ function toSpan (row, metadata, ids, spanName, userTags, recordTags) {
|
|
|
64
69
|
|
|
65
70
|
// One metric per evaluator per row or summary evaluator.
|
|
66
71
|
function toMetric (
|
|
67
|
-
label, value, errorMessage, spanId, traceId, timestampMs, experimentId, userTags, source = 'custom'
|
|
72
|
+
label, value, errorMessage, spanId, traceId, timestampMs, experimentId, userTags, source = 'custom', ids = {}
|
|
68
73
|
) {
|
|
69
74
|
const metric = {
|
|
70
75
|
metric_source: source,
|
|
@@ -72,7 +77,11 @@ function toMetric (
|
|
|
72
77
|
span_id: spanId,
|
|
73
78
|
trace_id: traceId,
|
|
74
79
|
timestamp_ms: timestampMs,
|
|
75
|
-
tags: buildTags(userTags, {
|
|
80
|
+
tags: buildTags(userTags, {
|
|
81
|
+
experiment_id: experimentId,
|
|
82
|
+
run_id: ids.runId,
|
|
83
|
+
run_iteration: ids.runIteration,
|
|
84
|
+
}),
|
|
76
85
|
experiment_id: experimentId,
|
|
77
86
|
}
|
|
78
87
|
|
|
@@ -124,8 +133,43 @@ function errorMessage (error) {
|
|
|
124
133
|
return error.message ?? String(error)
|
|
125
134
|
}
|
|
126
135
|
|
|
127
|
-
|
|
128
|
-
|
|
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.
|
|
129
173
|
class Experiment {
|
|
130
174
|
#client
|
|
131
175
|
#llmobs
|
|
@@ -139,6 +183,8 @@ class Experiment {
|
|
|
139
183
|
#config
|
|
140
184
|
#tags
|
|
141
185
|
#metadata
|
|
186
|
+
#runs
|
|
187
|
+
#projectName
|
|
142
188
|
#projectId
|
|
143
189
|
#experimentId
|
|
144
190
|
#runId
|
|
@@ -163,8 +209,11 @@ class Experiment {
|
|
|
163
209
|
this.#config = { ...options.config }
|
|
164
210
|
const filterTags = this.#dataset.filterTags?.() ?? []
|
|
165
211
|
if (filterTags.length > 0) this.#config.filtered_record_tags = filterTags
|
|
212
|
+
this.#projectName = options.projectName
|
|
166
213
|
this.#tags = { ...options.tags }
|
|
214
|
+
if (this.#projectName !== undefined) this.#tags.project_name = this.#projectName
|
|
167
215
|
this.#metadata = { ...options.metadata }
|
|
216
|
+
this.#runs = this.#external ? 1 : normalizePositiveInteger(options.runs ?? 1, 'runs')
|
|
168
217
|
this.#projectId = null
|
|
169
218
|
this.#experimentId = null
|
|
170
219
|
this.#runId = null
|
|
@@ -281,6 +330,7 @@ class Experiment {
|
|
|
281
330
|
projectId: this.#projectId,
|
|
282
331
|
datasetId: this.#dataset.id,
|
|
283
332
|
datasetRecordId: input.datasetRecordId,
|
|
333
|
+
projectName: this.#projectName,
|
|
284
334
|
runId: input.runId ?? this.#runId,
|
|
285
335
|
runIteration: input.runIteration ?? this.#runIteration,
|
|
286
336
|
}, input.name ?? this.#name, mergeTags(this.#tags, input.tags))
|
|
@@ -324,6 +374,8 @@ class Experiment {
|
|
|
324
374
|
log.warn('LLMObs experiments: skipping external metric %s because it has neither value nor error', metric.label)
|
|
325
375
|
continue
|
|
326
376
|
}
|
|
377
|
+
const metricTags = mergeTags(this.#tags, metric.tags)
|
|
378
|
+
if (this.#projectName !== undefined) metricTags.project_name = this.#projectName
|
|
327
379
|
payload.push(toMetric(
|
|
328
380
|
metric.label,
|
|
329
381
|
metric.value,
|
|
@@ -332,7 +384,7 @@ class Experiment {
|
|
|
332
384
|
span.traceId,
|
|
333
385
|
timestampMs(metric.timestamp),
|
|
334
386
|
experimentId,
|
|
335
|
-
|
|
387
|
+
metricTags,
|
|
336
388
|
metric.source ?? 'custom'
|
|
337
389
|
))
|
|
338
390
|
}
|
|
@@ -359,10 +411,12 @@ class Experiment {
|
|
|
359
411
|
maxRetries = 0,
|
|
360
412
|
retryDelay = (attempt) => 100 * (attempt + 1),
|
|
361
413
|
throwOnErrors = false,
|
|
414
|
+
concurrency = 10,
|
|
362
415
|
} = options
|
|
363
416
|
|
|
364
417
|
if (maxRetries < 0) throw new Error('maxRetries must be >= 0')
|
|
365
418
|
if (typeof retryDelay !== 'function') throw new TypeError('retryDelay must be a function')
|
|
419
|
+
const concurrencyLimit = normalizePositiveInteger(concurrency, 'concurrency')
|
|
366
420
|
|
|
367
421
|
const projectId = await this.#client.ensureProjectId()
|
|
368
422
|
|
|
@@ -380,13 +434,12 @@ class Experiment {
|
|
|
380
434
|
dataset_id: datasetId,
|
|
381
435
|
description: this.#description,
|
|
382
436
|
ensure_unique: true,
|
|
383
|
-
run_count:
|
|
437
|
+
run_count: this.#runs,
|
|
384
438
|
metadata: { tags: buildTags(this.#tags, {}) },
|
|
385
439
|
}
|
|
386
440
|
const datasetVersion = this.#dataset.version()
|
|
387
441
|
if (datasetVersion !== null) attributes.dataset_version = datasetVersion
|
|
388
|
-
|
|
389
|
-
if (Object.keys(this.#config).length > 0) attributes.config = this.#config
|
|
442
|
+
if (hasEntries(this.#config)) attributes.config = this.#config
|
|
390
443
|
|
|
391
444
|
let created
|
|
392
445
|
try {
|
|
@@ -396,28 +449,21 @@ class Experiment {
|
|
|
396
449
|
}
|
|
397
450
|
this.#experimentId = created.experimentId
|
|
398
451
|
const experimentId = this.#experimentId
|
|
399
|
-
const runId = id().toString(16).padStart(16, '0')
|
|
400
|
-
const runIteration = 0
|
|
401
452
|
|
|
402
453
|
try {
|
|
403
454
|
const records = this.#dataset.records()
|
|
404
455
|
const recordIds = this.#dataset.recordIds()
|
|
405
|
-
const rows = []
|
|
406
|
-
const spans = []
|
|
407
|
-
const metrics = []
|
|
408
|
-
const evaluatorResults = {}
|
|
409
456
|
const usesLLMObsTrace = Boolean(this.#llmobs?.enabled)
|
|
410
|
-
|
|
457
|
+
const runs = []
|
|
458
|
+
let hasRunError = false
|
|
411
459
|
|
|
412
|
-
for (let
|
|
413
|
-
const
|
|
414
|
-
const
|
|
415
|
-
// 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
|
|
416
463
|
// eslint-disable-next-line no-await-in-loop
|
|
417
|
-
const
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
datasetRecordId,
|
|
464
|
+
const result = await this.#runSingle({
|
|
465
|
+
records,
|
|
466
|
+
recordIds,
|
|
421
467
|
projectId,
|
|
422
468
|
datasetId,
|
|
423
469
|
experimentId,
|
|
@@ -426,80 +472,296 @@ class Experiment {
|
|
|
426
472
|
maxRetries,
|
|
427
473
|
retryDelay,
|
|
428
474
|
throwOnErrors,
|
|
475
|
+
concurrency: concurrencyLimit,
|
|
476
|
+
usesLLMObsTrace,
|
|
429
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
|
+
}
|
|
430
485
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
evaluatorResults[label].push(null)
|
|
438
|
-
metrics.push(toMetric(label, null, msg, row.spanId, row.traceId, timestampMs, experimentId, this.#tags))
|
|
439
|
-
continue
|
|
440
|
-
}
|
|
441
|
-
try {
|
|
442
|
-
// eslint-disable-next-line no-await-in-loop
|
|
443
|
-
const value = await this.#runWithRetries(
|
|
444
|
-
() => evaluator(record.input, row.output, record.expectedOutput),
|
|
445
|
-
maxRetries,
|
|
446
|
-
retryDelay
|
|
447
|
-
)
|
|
448
|
-
row.evaluations[label] = value
|
|
449
|
-
evaluatorResults[label].push(value)
|
|
450
|
-
metrics.push(toMetric(label, value, null, row.spanId, row.traceId, timestampMs, experimentId, this.#tags))
|
|
451
|
-
} catch (err) {
|
|
452
|
-
if (throwOnErrors) throw err
|
|
453
|
-
const msg = err.message ?? String(err)
|
|
454
|
-
row.evaluationErrors[label] = msg
|
|
455
|
-
evaluatorResults[label].push(null)
|
|
456
|
-
metrics.push(toMetric(label, null, msg, row.spanId, row.traceId, timestampMs, experimentId, this.#tags))
|
|
457
|
-
}
|
|
458
|
-
}
|
|
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
|
+
)
|
|
459
492
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
}
|
|
474
|
-
}
|
|
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
|
+
}
|
|
475
506
|
|
|
476
|
-
|
|
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,
|
|
477
534
|
maxRetries,
|
|
478
535
|
retryDelay,
|
|
479
536
|
throwOnErrors,
|
|
480
|
-
|
|
481
|
-
metrics,
|
|
537
|
+
limit,
|
|
482
538
|
})
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
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))
|
|
487
570
|
}
|
|
571
|
+
}
|
|
488
572
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
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,
|
|
493
662
|
experimentId,
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
663
|
+
runId,
|
|
664
|
+
runIteration,
|
|
665
|
+
maxRetries,
|
|
666
|
+
retryDelay,
|
|
667
|
+
throwOnErrors,
|
|
668
|
+
limit,
|
|
669
|
+
})
|
|
670
|
+
}
|
|
671
|
+
|
|
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
|
+
}
|
|
497
721
|
|
|
498
|
-
|
|
499
|
-
|
|
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
|
+
}
|
|
500
745
|
} catch (err) {
|
|
501
|
-
|
|
502
|
-
|
|
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
|
+
}
|
|
503
765
|
}
|
|
504
766
|
}
|
|
505
767
|
|
|
@@ -534,6 +796,7 @@ class Experiment {
|
|
|
534
796
|
dataset_name: this.#dataset.name(),
|
|
535
797
|
experiment_name: this.#name,
|
|
536
798
|
}
|
|
799
|
+
if (this.#projectName !== undefined) autoTags.project_name = this.#projectName
|
|
537
800
|
const tags = mergeTags(this.#tags, { ...recordTagsToObject(record.tags), ...autoTags })
|
|
538
801
|
|
|
539
802
|
const execute = () => this.#runWithRetries(
|
|
@@ -628,17 +891,65 @@ class Experiment {
|
|
|
628
891
|
const metadata = records.map(record => buildSpanMetadata(record.metadata, this.#config))
|
|
629
892
|
const summaryEvaluations = {}
|
|
630
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
|
+
}
|
|
631
911
|
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
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(
|
|
642
953
|
label,
|
|
643
954
|
value,
|
|
644
955
|
null,
|
|
@@ -647,13 +958,17 @@ class Experiment {
|
|
|
647
958
|
timestampMs,
|
|
648
959
|
options.experimentId,
|
|
649
960
|
this.#tags,
|
|
650
|
-
'summary'
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
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(
|
|
657
972
|
label,
|
|
658
973
|
null,
|
|
659
974
|
msg,
|
|
@@ -662,11 +977,11 @@ class Experiment {
|
|
|
662
977
|
timestampMs,
|
|
663
978
|
options.experimentId,
|
|
664
979
|
this.#tags,
|
|
665
|
-
'summary'
|
|
666
|
-
|
|
980
|
+
'summary',
|
|
981
|
+
{ runId: options.runId, runIteration: options.runIteration }
|
|
982
|
+
),
|
|
667
983
|
}
|
|
668
984
|
}
|
|
669
|
-
return summaryEvaluations
|
|
670
985
|
}
|
|
671
986
|
|
|
672
987
|
async #postEvents (experimentId, spans, metrics) {
|