dd-trace 6.7.0 → 6.9.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/diagnose.js +27 -25
- package/ci/init.js +6 -6
- package/ci/runbook.md +114 -20
- package/ci/test-optimization-validation/approval-artifacts.js +36 -3
- package/ci/test-optimization-validation/approval.js +78 -22
- package/ci/test-optimization-validation/blocker-category.js +24 -0
- package/ci/test-optimization-validation/ci-discovery.js +23 -17
- package/ci/test-optimization-validation/ci-package-scripts.js +34 -6
- package/ci/test-optimization-validation/ci-remediation.js +1 -1
- package/ci/test-optimization-validation/cli.js +280 -52
- package/ci/test-optimization-validation/command-blocker.js +159 -20
- package/ci/test-optimization-validation/command-output-policy.js +14 -8
- package/ci/test-optimization-validation/command-runner.js +9 -7
- package/ci/test-optimization-validation/environment.js +4 -2
- package/ci/test-optimization-validation/executable.js +15 -13
- package/ci/test-optimization-validation/execution-lock.js +70 -0
- package/ci/test-optimization-validation/framework-adapters/cucumber-validation.json +3 -0
- package/ci/test-optimization-validation/framework-adapters/cucumber.js +657 -5
- package/ci/test-optimization-validation/framework-adapters/playwright.js +5 -5
- package/ci/test-optimization-validation/framework-adapters/vitest.js +517 -0
- package/ci/test-optimization-validation/generated-files.js +47 -37
- package/ci/test-optimization-validation/generated-test-contract.js +9 -7
- package/ci/test-optimization-validation/generated-verifier.js +24 -17
- package/ci/test-optimization-validation/literal-glob.js +52 -0
- package/ci/test-optimization-validation/manifest-loader.js +3 -0
- package/ci/test-optimization-validation/manifest-scaffold.js +301 -52
- package/ci/test-optimization-validation/manifest-schema.js +80 -17
- package/ci/test-optimization-validation/offline-fixtures.js +13 -4
- package/ci/test-optimization-validation/offline-output.js +6 -6
- package/ci/test-optimization-validation/package-check.js +94 -0
- package/ci/test-optimization-validation/plan-writer.js +183 -10
- package/ci/test-optimization-validation/preflight-runner.js +110 -31
- package/ci/test-optimization-validation/project-build-artifact.js +31 -0
- package/ci/test-optimization-validation/redaction.js +18 -27
- package/ci/test-optimization-validation/report-writer.js +182 -48
- package/ci/test-optimization-validation/result-semantics.js +20 -3
- package/ci/test-optimization-validation/runner-command.js +41 -21
- package/ci/test-optimization-validation/runner-contract.js +79 -43
- package/ci/test-optimization-validation/safe-files.js +4 -4
- package/ci/test-optimization-validation/scenarios/basic-reporting.js +60 -6
- package/ci/test-optimization-validation/scenarios/ci-wiring.js +141 -35
- package/ci/test-optimization-validation/scenarios/helpers.js +30 -6
- package/ci/test-optimization-validation/scenarios/test-management.js +2 -2
- package/ci/test-optimization-validation/static-diagnosis.js +10 -3
- package/ci/test-optimization-validation/test-output.js +2 -1
- package/ci/test-optimization-validation/validation-blocker.js +21 -0
- package/ci/vitest-no-worker-init-setup.mjs +377 -84
- package/index.d.ts +175 -14
- package/init.js +1 -17
- package/initialize.mjs +11 -0
- package/loader-hook.mjs +6 -4
- package/package.json +12 -4
- package/packages/datadog-core/src/utils/src/kebabcase.js +8 -2
- package/packages/datadog-core/src/utils/src/parse-tags.js +0 -1
- package/packages/datadog-esbuild/index.js +4 -2
- package/packages/datadog-esbuild/src/utils.js +17 -2
- package/packages/datadog-instrumentations/src/ai.js +61 -34
- package/packages/datadog-instrumentations/src/bluebird.js +6 -6
- package/packages/datadog-instrumentations/src/claude-agent-sdk.js +85 -9
- package/packages/datadog-instrumentations/src/cucumber.js +131 -51
- package/packages/datadog-instrumentations/src/dns.js +21 -2
- package/packages/datadog-instrumentations/src/electron.js +1 -0
- package/packages/datadog-instrumentations/src/fastify.js +3 -1
- package/packages/datadog-instrumentations/src/grpc/client.js +4 -2
- package/packages/datadog-instrumentations/src/helpers/callback-instrumentor.js +3 -1
- package/packages/datadog-instrumentations/src/helpers/extract-package-and-module-path.js +10 -0
- package/packages/datadog-instrumentations/src/helpers/hook.js +19 -2
- package/packages/datadog-instrumentations/src/helpers/hooks.js +4 -0
- package/packages/datadog-instrumentations/src/helpers/register.js +9 -3
- package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +34 -9
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +175 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/loader.mjs +20 -2
- package/packages/datadog-instrumentations/src/helpers/rewriter/targets.js +32 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +61 -0
- package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +50 -18
- package/packages/datadog-instrumentations/src/helpers/router-helper.js +38 -7
- package/packages/datadog-instrumentations/src/helpers/shared-utils.js +39 -0
- package/packages/datadog-instrumentations/src/http/client.js +1 -3
- package/packages/datadog-instrumentations/src/jest/coverage-backfill.js +1 -1
- package/packages/datadog-instrumentations/src/jest.js +986 -246
- package/packages/datadog-instrumentations/src/mocha/common.js +1 -3
- package/packages/datadog-instrumentations/src/mocha/main.js +109 -51
- package/packages/datadog-instrumentations/src/mocha/utils.js +120 -32
- package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +44 -0
- package/packages/datadog-instrumentations/src/mocha/worker.js +441 -43
- package/packages/datadog-instrumentations/src/moleculer/client.js +1 -1
- package/packages/datadog-instrumentations/src/mysql.js +1 -1
- package/packages/datadog-instrumentations/src/next.js +155 -23
- package/packages/datadog-instrumentations/src/openai.js +14 -0
- package/packages/datadog-instrumentations/src/pg.js +180 -6
- package/packages/datadog-instrumentations/src/playwright.js +176 -81
- package/packages/datadog-instrumentations/src/promise-js.js +4 -4
- package/packages/datadog-instrumentations/src/promise.js +3 -3
- package/packages/datadog-instrumentations/src/rhea.js +1 -1
- package/packages/datadog-instrumentations/src/router.js +195 -19
- package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +567 -65
- package/packages/datadog-instrumentations/src/vitest-main.js +438 -54
- package/packages/datadog-instrumentations/src/vitest-util.js +42 -7
- package/packages/datadog-instrumentations/src/vitest-worker.js +234 -17
- package/packages/datadog-instrumentations/src/webdriverio.js +1090 -0
- package/packages/datadog-instrumentations/src/when.js +3 -3
- package/packages/datadog-instrumentations/src/ws.js +5 -1
- package/packages/datadog-plugin-aerospike/src/index.js +1 -3
- package/packages/datadog-plugin-amqplib/src/client.js +1 -1
- package/packages/datadog-plugin-amqplib/src/producer.js +1 -1
- package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +59 -48
- package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +2 -2
- package/packages/datadog-plugin-aws-sdk/src/services/stepfunctions.js +2 -2
- package/packages/datadog-plugin-azure-event-hubs/src/producer.js +1 -3
- package/packages/datadog-plugin-azure-service-bus/src/producer.js +5 -3
- package/packages/datadog-plugin-bullmq/src/producer.js +3 -3
- package/packages/datadog-plugin-child_process/src/index.js +3 -3
- package/packages/datadog-plugin-child_process/src/scrub-cmd-params.js +6 -11
- package/packages/datadog-plugin-cucumber/src/index.js +5 -1
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +50 -46
- package/packages/datadog-plugin-cypress/src/source-map-utils.js +1 -1
- package/packages/datadog-plugin-cypress/src/support.js +7 -4
- package/packages/datadog-plugin-electron/src/net.js +10 -4
- package/packages/datadog-plugin-fastify/src/tracing.js +1 -1
- package/packages/datadog-plugin-fs/src/index.js +0 -4
- package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +14 -7
- package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +19 -10
- package/packages/datadog-plugin-graphql/src/execute.js +1 -0
- package/packages/datadog-plugin-graphql/src/tools/signature.js +1 -1
- package/packages/datadog-plugin-http/src/client.js +3 -2
- package/packages/datadog-plugin-http2/src/client.js +1 -1
- package/packages/datadog-plugin-jest/src/index.js +3 -21
- package/packages/datadog-plugin-jest/src/util.js +4 -1
- package/packages/datadog-plugin-kafkajs/src/producer.js +2 -1
- package/packages/datadog-plugin-langchain/src/tokens.js +2 -2
- package/packages/datadog-plugin-mocha/src/index.js +342 -6
- package/packages/datadog-plugin-next/src/index.js +57 -9
- package/packages/datadog-plugin-openai/src/tracing.js +4 -0
- package/packages/datadog-plugin-pg/src/index.js +65 -1
- package/packages/datadog-plugin-playwright/src/index.js +1 -1
- package/packages/datadog-plugin-prisma/src/index.js +5 -1
- package/packages/datadog-plugin-rhea/src/consumer.js +16 -13
- package/packages/datadog-plugin-rhea/src/producer.js +1 -1
- package/packages/datadog-plugin-undici/src/index.js +2 -1
- package/packages/datadog-plugin-vitest/src/index.js +179 -18
- package/packages/datadog-shimmer/src/shimmer.js +6 -2
- package/packages/datadog-webpack/index.js +2 -2
- package/packages/dd-trace/src/aiguard/sdk.js +3 -0
- package/packages/dd-trace/src/appsec/{blocking.js → blocking/index.js} +26 -6
- package/packages/dd-trace/src/appsec/downstream_requests.js +1 -1
- package/packages/dd-trace/src/appsec/handlers/auth.js +62 -0
- package/packages/dd-trace/src/appsec/handlers/http-request.js +215 -0
- package/packages/dd-trace/src/appsec/handlers/http-response.js +78 -0
- package/packages/dd-trace/src/appsec/handlers/http-shared.js +24 -0
- package/packages/dd-trace/src/appsec/handlers/payments.js +104 -0
- package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-base-analyzer.js +1 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-password-analyzer.js +1 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/hsts-header-missing-analyzer.js +1 -1
- package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +1 -1
- package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +1 -3
- package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +2 -2
- package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +2 -2
- package/packages/dd-trace/src/appsec/index.js +48 -476
- package/packages/dd-trace/src/appsec/sdk/track_event.js +10 -1
- package/packages/dd-trace/src/appsec/waf/waf_manager.js +1 -1
- package/packages/dd-trace/src/ci-visibility/coverage-report-discovery.js +46 -14
- package/packages/dd-trace/src/ci-visibility/efd-retry-policy.js +89 -0
- package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/sink.js +7 -6
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +29 -15
- package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +19 -1
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +25 -1
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/webdriverio.js +26 -0
- package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +9 -1
- package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +22 -7
- package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +9 -14
- package/packages/dd-trace/src/ci-visibility/requests/request.js +3 -3
- package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +21 -5
- package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +2 -2
- package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +5 -1
- package/packages/dd-trace/src/config/defaults.js +14 -6
- package/packages/dd-trace/src/config/generated-config-types.d.ts +4 -2
- package/packages/dd-trace/src/config/helper.js +2 -0
- package/packages/dd-trace/src/config/index.js +31 -5
- package/packages/dd-trace/src/config/parsers.js +19 -1
- package/packages/dd-trace/src/config/remote_config.js +1 -1
- package/packages/dd-trace/src/config/supported-configurations.json +13 -6
- package/packages/dd-trace/src/datastreams/encoding.js +3 -4
- package/packages/dd-trace/src/debugger/devtools_client/log.js +2 -1
- package/packages/dd-trace/src/debugger/devtools_client/probe_sampler.js +1 -4
- package/packages/dd-trace/src/debugger/devtools_client/send.js +1 -2
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +2 -2
- package/packages/dd-trace/src/debugger/devtools_client/snapshot/redaction.js +16 -2
- package/packages/dd-trace/src/debugger/devtools_client/status.js +1 -1
- package/packages/dd-trace/src/encode/coverage-ci-visibility.js +0 -1
- package/packages/dd-trace/src/encode/tags-processors.js +2 -2
- package/packages/dd-trace/src/exporters/common/url.js +3 -5
- package/packages/dd-trace/src/exporters/electron/index.js +4 -1
- package/packages/dd-trace/src/external-logger/src/index.js +1 -2
- package/packages/dd-trace/src/flare/index.js +2 -2
- package/packages/dd-trace/src/guardrails/apply-pm2-env.js +16 -0
- package/packages/dd-trace/src/guardrails/log.js +1 -10
- package/packages/dd-trace/src/id.js +1 -1
- package/packages/dd-trace/src/llmobs/constants/tags.js +1 -1
- package/packages/dd-trace/src/llmobs/eval-metric.js +249 -0
- package/packages/dd-trace/src/llmobs/experiments/client.js +4 -2
- package/packages/dd-trace/src/llmobs/experiments/dataset.js +66 -14
- package/packages/dd-trace/src/llmobs/experiments/experiment.js +289 -95
- package/packages/dd-trace/src/llmobs/experiments/index.js +66 -19
- package/packages/dd-trace/src/llmobs/experiments/noop.js +95 -10
- package/packages/dd-trace/src/llmobs/experiments/result.js +13 -2
- package/packages/dd-trace/src/llmobs/experiments/util.js +220 -0
- package/packages/dd-trace/src/llmobs/noop.js +2 -0
- package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/ai/util.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +5 -5
- package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/index.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/genai/index.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +1 -1
- package/packages/dd-trace/src/llmobs/sdk.js +152 -78
- package/packages/dd-trace/src/llmobs/span_processor.js +9 -7
- package/packages/dd-trace/src/llmobs/tagger.js +1 -2
- package/packages/dd-trace/src/llmobs/telemetry.js +18 -0
- package/packages/dd-trace/src/llmobs/writers/base.js +3 -0
- package/packages/dd-trace/src/llmobs/writers/spans.js +12 -6
- package/packages/dd-trace/src/llmobs/writers/util.js +2 -0
- package/packages/dd-trace/src/log/channels.js +1 -9
- package/packages/dd-trace/src/log/levels.js +12 -0
- package/packages/dd-trace/src/noop/proxy.js +2 -2
- package/packages/dd-trace/src/openfeature/writers/exposures.js +1 -1
- package/packages/dd-trace/src/openfeature/writers/util.js +1 -1
- package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +1 -1
- package/packages/dd-trace/src/opentelemetry/metrics/meter.js +8 -4
- package/packages/dd-trace/src/opentelemetry/metrics/otlp_transformer.js +1 -1
- package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +3 -2
- package/packages/dd-trace/src/opentelemetry/span.js +4 -0
- package/packages/dd-trace/src/opentracing/span.js +1 -1
- package/packages/dd-trace/src/opentracing/span_context.js +1 -1
- package/packages/dd-trace/src/plugins/ci_plugin.js +124 -23
- package/packages/dd-trace/src/plugins/index.js +2 -0
- package/packages/dd-trace/src/plugins/plugin.js +1 -1
- package/packages/dd-trace/src/plugins/tracing.js +1 -0
- package/packages/dd-trace/src/plugins/util/ci.js +13 -6
- package/packages/dd-trace/src/plugins/util/git.js +7 -5
- package/packages/dd-trace/src/plugins/util/http-otel-semantics.js +3 -3
- package/packages/dd-trace/src/plugins/util/ip_extractor.js +1 -1
- package/packages/dd-trace/src/plugins/util/llm.js +2 -2
- package/packages/dd-trace/src/plugins/util/stacktrace.js +3 -0
- package/packages/dd-trace/src/plugins/util/tags.js +2 -0
- package/packages/dd-trace/src/plugins/util/test.js +356 -86
- package/packages/dd-trace/src/plugins/util/url.js +5 -5
- package/packages/dd-trace/src/plugins/util/user-provided-git.js +1 -1
- package/packages/dd-trace/src/plugins/util/web.js +24 -2
- package/packages/dd-trace/src/priority_sampler.js +6 -3
- package/packages/dd-trace/src/profiler.js +1 -1
- package/packages/dd-trace/src/profiling/exporters/event_serializer.js +1 -1
- package/packages/dd-trace/src/profiling/profilers/events.js +2 -2
- package/packages/dd-trace/src/profiling/profilers/poisson.js +1 -1
- package/packages/dd-trace/src/profiling/profilers/shared.js +2 -2
- package/packages/dd-trace/src/proxy.js +1 -0
- package/packages/dd-trace/src/ritm.js +10 -20
- package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +4 -2
- package/packages/dd-trace/src/sampling_rule.js +4 -2
- package/packages/dd-trace/src/service-naming/schemas/v0/storage.js +1 -1
- package/packages/dd-trace/src/service-naming/schemas/v1/storage.js +1 -1
- package/packages/dd-trace/src/startup-log.js +3 -0
- package/packages/dd-trace/src/telemetry/logs/index.js +3 -0
- package/packages/dd-trace/src/telemetry/logs/log-collector.js +3 -1
- package/packages/dd-trace/src/telemetry/telemetry.js +1 -1
- package/packages/dd-trace/src/util.js +16 -0
- package/vendor/dist/@apm-js-collab/code-transformer/index.js +12 -12
- package/vendor/dist/pprof-format/index.js +1 -1
- package/vendor/dist/source-map/index.js +1 -1
- package/version.js +10 -8
- /package/packages/dd-trace/src/appsec/{blocked_templates.js → blocking/templates.js} +0 -0
|
@@ -3,35 +3,19 @@
|
|
|
3
3
|
const id = require('../../id')
|
|
4
4
|
|
|
5
5
|
const { API_BASE_PATH } = require('./client')
|
|
6
|
-
const { Row, ExperimentResult } = require('./result')
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
if (typeof value === 'string') return value.toLowerCase()
|
|
20
|
-
if (typeof value === 'object') return JSON.stringify(value).toLowerCase()
|
|
21
|
-
return String(value).toLowerCase()
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// Build the tag list, letting auto tags win over user tags on key conflict.
|
|
25
|
-
function buildTags (userTags, autoTags) {
|
|
26
|
-
const tags = new Map()
|
|
27
|
-
for (const [k, v] of Object.entries(userTags ?? {})) {
|
|
28
|
-
tags.set(k, `${k}:${v}`)
|
|
29
|
-
}
|
|
30
|
-
for (const [k, v] of Object.entries(autoTags)) {
|
|
31
|
-
tags.set(k, `${k}:${v}`)
|
|
32
|
-
}
|
|
33
|
-
return [...tags.values()]
|
|
34
|
-
}
|
|
6
|
+
const { Row, ExperimentResult, ExperimentRun } = require('./result')
|
|
7
|
+
const {
|
|
8
|
+
buildExperimentTagObject,
|
|
9
|
+
buildSpanMetadata,
|
|
10
|
+
buildTags,
|
|
11
|
+
hasEntries,
|
|
12
|
+
inferMetricType,
|
|
13
|
+
normalizeEvaluators,
|
|
14
|
+
normalizeJsonMetricValue,
|
|
15
|
+
sleep,
|
|
16
|
+
stringify,
|
|
17
|
+
validateEvaluatorName,
|
|
18
|
+
} = require('./util')
|
|
35
19
|
|
|
36
20
|
// One span per experiment row (LLM Obs experiment span wire format).
|
|
37
21
|
function toSpan (row, metadata, ids, spanName, userTags) {
|
|
@@ -40,7 +24,7 @@ function toSpan (row, metadata, ids, spanName, userTags) {
|
|
|
40
24
|
output: row.output ?? null,
|
|
41
25
|
expected_output: row.expectedOutput ?? null,
|
|
42
26
|
}
|
|
43
|
-
if (
|
|
27
|
+
if (hasEntries(metadata)) {
|
|
44
28
|
meta.metadata = metadata
|
|
45
29
|
}
|
|
46
30
|
if (row.isError) {
|
|
@@ -59,19 +43,26 @@ function toSpan (row, metadata, ids, spanName, userTags) {
|
|
|
59
43
|
meta,
|
|
60
44
|
tags: buildTags(userTags, {
|
|
61
45
|
experiment_id: ids.experimentId,
|
|
46
|
+
run_id: ids.runId,
|
|
47
|
+
run_iteration: ids.runIteration,
|
|
62
48
|
dataset_id: ids.datasetId,
|
|
63
49
|
dataset_record_id: ids.datasetRecordId,
|
|
64
50
|
}),
|
|
65
51
|
}
|
|
66
52
|
}
|
|
67
53
|
|
|
68
|
-
// One metric per evaluator per row.
|
|
69
|
-
function toMetric (
|
|
54
|
+
// One metric per evaluator per row or summary evaluator.
|
|
55
|
+
function toMetric (
|
|
56
|
+
label, value, errorMessage, spanId, traceId, timestampMs, experimentId, userTags, source = 'custom'
|
|
57
|
+
) {
|
|
70
58
|
const metric = {
|
|
59
|
+
metric_source: source,
|
|
71
60
|
label,
|
|
72
61
|
span_id: spanId,
|
|
62
|
+
trace_id: traceId,
|
|
73
63
|
timestamp_ms: timestampMs,
|
|
74
64
|
tags: buildTags(userTags, { experiment_id: experimentId }),
|
|
65
|
+
experiment_id: experimentId,
|
|
75
66
|
}
|
|
76
67
|
|
|
77
68
|
if (errorMessage !== null) {
|
|
@@ -84,35 +75,50 @@ function toMetric (label, value, errorMessage, spanId, timestampMs, experimentId
|
|
|
84
75
|
metric.metric_type = type
|
|
85
76
|
if (type === 'boolean') metric.boolean_value = value
|
|
86
77
|
else if (type === 'score') metric.score_value = value
|
|
87
|
-
else if (type === 'json') metric.json_value = value
|
|
78
|
+
else if (type === 'json') metric.json_value = normalizeJsonMetricValue(value)
|
|
88
79
|
else metric.categorical_value = stringify(value)
|
|
89
80
|
return metric
|
|
90
81
|
}
|
|
91
82
|
|
|
83
|
+
function createFallbackSpanContext (startNs) {
|
|
84
|
+
// Root-span id generation, same convention as opentracing/span.js: a single
|
|
85
|
+
// random 64-bit id for the span, reused as the trace id's low 64 bits with a
|
|
86
|
+
// start-time-derived high 64 bits (like the `_dd.p.tid` 128-bit trace id tag).
|
|
87
|
+
const spanIdentifier = id()
|
|
88
|
+
const traceIdHigh = Math.floor(startNs / 1e9).toString(16).padStart(8, '0').padEnd(16, '0')
|
|
89
|
+
const spanId = spanIdentifier.toString(16).padStart(16, '0')
|
|
90
|
+
const traceId = spanIdentifier.toTraceIdHex(traceIdHigh).padStart(32, '0')
|
|
91
|
+
return { spanId, traceId }
|
|
92
|
+
}
|
|
93
|
+
|
|
92
94
|
// Builder + run() orchestration: runs rows sequentially, emits one root span
|
|
93
|
-
// per dataset row, and posts
|
|
95
|
+
// per dataset row, and posts spans + metrics to the experiments events API.
|
|
94
96
|
class Experiment {
|
|
95
97
|
#client
|
|
98
|
+
#llmobs
|
|
96
99
|
#name
|
|
97
100
|
#description
|
|
98
101
|
#dataset
|
|
99
102
|
#task
|
|
100
103
|
#evaluators
|
|
104
|
+
#summaryEvaluators
|
|
101
105
|
#config
|
|
102
106
|
#tags
|
|
103
107
|
#experimentId
|
|
104
108
|
|
|
105
|
-
constructor (client, options = {}) {
|
|
109
|
+
constructor (client, options = {}, llmobs) {
|
|
106
110
|
if (!options.name) throw new Error('Experiment name is required')
|
|
107
111
|
if (!options.dataset) throw new Error('Experiment dataset is required')
|
|
108
112
|
if (typeof options.task !== 'function') throw new Error('Experiment task is required')
|
|
109
113
|
|
|
110
114
|
this.#client = client
|
|
115
|
+
this.#llmobs = llmobs
|
|
111
116
|
this.#name = options.name
|
|
112
117
|
this.#description = options.description ?? ''
|
|
113
118
|
this.#dataset = options.dataset
|
|
114
119
|
this.#task = options.task
|
|
115
|
-
this.#evaluators =
|
|
120
|
+
this.#evaluators = normalizeEvaluators(options.evaluators, 'row')
|
|
121
|
+
this.#summaryEvaluators = normalizeEvaluators(options.summaryEvaluators, 'summary')
|
|
116
122
|
this.#config = { ...options.config }
|
|
117
123
|
this.#tags = { ...options.tags }
|
|
118
124
|
this.#experimentId = null
|
|
@@ -131,7 +137,16 @@ class Experiment {
|
|
|
131
137
|
return `${this.#client.appBase}/llm/experiments/${this.#experimentId}`
|
|
132
138
|
}
|
|
133
139
|
|
|
134
|
-
async run () {
|
|
140
|
+
async run (options = {}) {
|
|
141
|
+
const {
|
|
142
|
+
maxRetries = 0,
|
|
143
|
+
retryDelay = (attempt) => 100 * (attempt + 1),
|
|
144
|
+
throwOnErrors = false,
|
|
145
|
+
} = options
|
|
146
|
+
|
|
147
|
+
if (maxRetries < 0) throw new Error('maxRetries must be >= 0')
|
|
148
|
+
if (typeof retryDelay !== 'function') throw new TypeError('retryDelay must be a function')
|
|
149
|
+
|
|
135
150
|
const projectId = await this.#client.ensureProjectId()
|
|
136
151
|
|
|
137
152
|
await this.#dataset.ensureCreatedAndPushed(projectId)
|
|
@@ -148,7 +163,11 @@ class Experiment {
|
|
|
148
163
|
dataset_id: datasetId,
|
|
149
164
|
description: this.#description,
|
|
150
165
|
ensure_unique: true,
|
|
166
|
+
run_count: 1,
|
|
167
|
+
metadata: { tags: buildTags(this.#tags, {}) },
|
|
151
168
|
}
|
|
169
|
+
const datasetVersion = this.#dataset.version()
|
|
170
|
+
if (datasetVersion !== null) attributes.dataset_version = datasetVersion
|
|
152
171
|
if (Object.keys(this.#config).length > 0) attributes.config = this.#config
|
|
153
172
|
|
|
154
173
|
let created
|
|
@@ -161,6 +180,8 @@ class Experiment {
|
|
|
161
180
|
}
|
|
162
181
|
this.#experimentId = created?.data?.id ?? null
|
|
163
182
|
const experimentId = this.#experimentId
|
|
183
|
+
const runId = id().toString(16).padStart(16, '0')
|
|
184
|
+
const runIteration = 0
|
|
164
185
|
|
|
165
186
|
try {
|
|
166
187
|
const records = this.#dataset.records()
|
|
@@ -168,83 +189,87 @@ class Experiment {
|
|
|
168
189
|
const rows = []
|
|
169
190
|
const spans = []
|
|
170
191
|
const metrics = []
|
|
192
|
+
const evaluatorResults = {}
|
|
193
|
+
const usesLLMObsTrace = Boolean(this.#llmobs?.enabled)
|
|
171
194
|
let hasRowError = false
|
|
172
195
|
|
|
173
196
|
for (let i = 0; i < records.length; i++) {
|
|
174
197
|
const record = records[i]
|
|
175
198
|
const datasetRecordId = i < recordIds.length ? recordIds[i] : ''
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
// Rows run one at a time by design.
|
|
192
|
-
// eslint-disable-next-line no-await-in-loop
|
|
193
|
-
output = await this.#task(record.input, this.#config)
|
|
194
|
-
} catch (err) {
|
|
195
|
-
errorType = err.name || 'Error'
|
|
196
|
-
errorMessage = err.message ?? String(err)
|
|
197
|
-
}
|
|
199
|
+
// Rows currently run sequentially by design; jobs/concurrency is a P1 follow-up.
|
|
200
|
+
// eslint-disable-next-line no-await-in-loop
|
|
201
|
+
const row = await this.#processRecord({
|
|
202
|
+
index: i,
|
|
203
|
+
record,
|
|
204
|
+
datasetRecordId,
|
|
205
|
+
projectId,
|
|
206
|
+
datasetId,
|
|
207
|
+
experimentId,
|
|
208
|
+
runId,
|
|
209
|
+
runIteration,
|
|
210
|
+
maxRetries,
|
|
211
|
+
retryDelay,
|
|
212
|
+
throwOnErrors,
|
|
213
|
+
})
|
|
198
214
|
|
|
199
|
-
const durationNs = Number(process.hrtime.bigint() - startHr)
|
|
200
|
-
const evaluations = {}
|
|
201
|
-
const evaluationErrors = {}
|
|
202
215
|
const timestampMs = Date.now()
|
|
203
|
-
|
|
204
216
|
for (const [label, evaluator] of this.#evaluators) {
|
|
205
|
-
if (
|
|
217
|
+
if (!evaluatorResults[label]) evaluatorResults[label] = []
|
|
218
|
+
if (row.isError) {
|
|
206
219
|
const msg = 'task error; evaluation skipped'
|
|
207
|
-
evaluationErrors[label] = msg
|
|
208
|
-
|
|
220
|
+
row.evaluationErrors[label] = msg
|
|
221
|
+
evaluatorResults[label].push(null)
|
|
222
|
+
metrics.push(toMetric(label, null, msg, row.spanId, row.traceId, timestampMs, experimentId, this.#tags))
|
|
209
223
|
continue
|
|
210
224
|
}
|
|
211
225
|
try {
|
|
212
226
|
// eslint-disable-next-line no-await-in-loop
|
|
213
|
-
const value = await
|
|
214
|
-
|
|
215
|
-
|
|
227
|
+
const value = await this.#runWithRetries(
|
|
228
|
+
() => evaluator(record.input, row.output, record.expectedOutput),
|
|
229
|
+
maxRetries,
|
|
230
|
+
retryDelay
|
|
231
|
+
)
|
|
232
|
+
row.evaluations[label] = value
|
|
233
|
+
evaluatorResults[label].push(value)
|
|
234
|
+
metrics.push(toMetric(label, value, null, row.spanId, row.traceId, timestampMs, experimentId, this.#tags))
|
|
216
235
|
} catch (err) {
|
|
236
|
+
if (throwOnErrors) throw err
|
|
217
237
|
const msg = err.message ?? String(err)
|
|
218
|
-
evaluationErrors[label] = msg
|
|
219
|
-
|
|
238
|
+
row.evaluationErrors[label] = msg
|
|
239
|
+
evaluatorResults[label].push(null)
|
|
240
|
+
metrics.push(toMetric(label, null, msg, row.spanId, row.traceId, timestampMs, experimentId, this.#tags))
|
|
220
241
|
}
|
|
221
242
|
}
|
|
222
243
|
|
|
223
|
-
const row = new Row({
|
|
224
|
-
index: i,
|
|
225
|
-
spanId,
|
|
226
|
-
traceId,
|
|
227
|
-
startNs,
|
|
228
|
-
durationNs,
|
|
229
|
-
input: record.input,
|
|
230
|
-
output,
|
|
231
|
-
expectedOutput: record.expectedOutput,
|
|
232
|
-
errorType,
|
|
233
|
-
errorMessage,
|
|
234
|
-
evaluations,
|
|
235
|
-
evaluationErrors,
|
|
236
|
-
})
|
|
237
244
|
rows.push(row)
|
|
238
|
-
if (row.isError ||
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
+
if (row.isError || hasEntries(row.evaluationErrors)) hasRowError = true
|
|
246
|
+
if (!usesLLMObsTrace) {
|
|
247
|
+
spans.push(toSpan(row, record.metadata, {
|
|
248
|
+
experimentId,
|
|
249
|
+
projectId,
|
|
250
|
+
datasetId,
|
|
251
|
+
datasetRecordId,
|
|
252
|
+
runId,
|
|
253
|
+
runIteration,
|
|
254
|
+
}, this.#task.name || this.#name, this.#tags))
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const summaryEvaluations = await this.#runSummaryEvaluators(rows, records, evaluatorResults, {
|
|
259
|
+
maxRetries,
|
|
260
|
+
retryDelay,
|
|
261
|
+
throwOnErrors,
|
|
262
|
+
experimentId,
|
|
263
|
+
metrics,
|
|
264
|
+
})
|
|
265
|
+
if (hasEntries(summaryEvaluations)) {
|
|
266
|
+
for (const value of Object.values(summaryEvaluations)) {
|
|
267
|
+
if (value?.error) hasRowError = true
|
|
268
|
+
}
|
|
245
269
|
}
|
|
246
270
|
|
|
247
271
|
await this.#postEvents(experimentId, spans, metrics)
|
|
272
|
+
this.#llmobs?.flush?.()
|
|
248
273
|
// A row error doesn't abort the run, but the experiment didn't succeed cleanly.
|
|
249
274
|
await this.#updateStatus(
|
|
250
275
|
experimentId,
|
|
@@ -252,16 +277,185 @@ class Experiment {
|
|
|
252
277
|
hasRowError ? 'one or more rows failed' : null
|
|
253
278
|
)
|
|
254
279
|
|
|
255
|
-
|
|
280
|
+
const run = new ExperimentRun({ runId, runIteration, rows, summaryEvaluations })
|
|
281
|
+
return new ExperimentResult(experimentId, rows, this.url(), [run], summaryEvaluations)
|
|
256
282
|
} catch (err) {
|
|
257
283
|
await this.#updateStatus(experimentId, 'failed', err.message ?? String(err))
|
|
258
284
|
throw err
|
|
259
285
|
}
|
|
260
286
|
}
|
|
261
287
|
|
|
288
|
+
async #processRecord ({
|
|
289
|
+
index,
|
|
290
|
+
record,
|
|
291
|
+
datasetRecordId,
|
|
292
|
+
projectId,
|
|
293
|
+
datasetId,
|
|
294
|
+
experimentId,
|
|
295
|
+
runId,
|
|
296
|
+
runIteration,
|
|
297
|
+
maxRetries,
|
|
298
|
+
retryDelay,
|
|
299
|
+
throwOnErrors,
|
|
300
|
+
}) {
|
|
301
|
+
const startNs = Date.now() * 1e6
|
|
302
|
+
const startHr = process.hrtime.bigint()
|
|
303
|
+
const fallbackContext = createFallbackSpanContext(startNs)
|
|
304
|
+
let spanContext = fallbackContext
|
|
305
|
+
let output = null
|
|
306
|
+
let errorType = null
|
|
307
|
+
let errorMessage = null
|
|
308
|
+
|
|
309
|
+
const autoTags = {
|
|
310
|
+
experiment_id: experimentId,
|
|
311
|
+
run_id: runId,
|
|
312
|
+
run_iteration: runIteration,
|
|
313
|
+
project_id: projectId,
|
|
314
|
+
dataset_id: datasetId,
|
|
315
|
+
dataset_record_id: datasetRecordId,
|
|
316
|
+
dataset_name: this.#dataset.name(),
|
|
317
|
+
experiment_name: this.#name,
|
|
318
|
+
}
|
|
319
|
+
const tags = buildExperimentTagObject(this.#tags, autoTags)
|
|
320
|
+
|
|
321
|
+
const execute = () => this.#runWithRetries(
|
|
322
|
+
() => this.#task(record.input, this.#config, record.metadata),
|
|
323
|
+
maxRetries,
|
|
324
|
+
retryDelay
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
if (this.#llmobs?.enabled) {
|
|
328
|
+
try {
|
|
329
|
+
await this.#llmobs.trace({
|
|
330
|
+
kind: 'experiment',
|
|
331
|
+
name: this.#task.name || this.#name,
|
|
332
|
+
}, async (span) => {
|
|
333
|
+
spanContext = this.#llmobs.exportSpan(span)
|
|
334
|
+
try {
|
|
335
|
+
output = await execute()
|
|
336
|
+
} catch (err) {
|
|
337
|
+
this.#llmobs.annotate(span, {
|
|
338
|
+
inputData: record.input,
|
|
339
|
+
outputData: output,
|
|
340
|
+
metadata: buildSpanMetadata(record.metadata, this.#config),
|
|
341
|
+
tags,
|
|
342
|
+
})
|
|
343
|
+
throw err
|
|
344
|
+
}
|
|
345
|
+
this.#llmobs.annotate(span, {
|
|
346
|
+
inputData: record.input,
|
|
347
|
+
outputData: output,
|
|
348
|
+
metadata: buildSpanMetadata(record.metadata, this.#config),
|
|
349
|
+
tags,
|
|
350
|
+
})
|
|
351
|
+
})
|
|
352
|
+
} catch (err) {
|
|
353
|
+
if (throwOnErrors) throw err
|
|
354
|
+
errorType = err.name || 'Error'
|
|
355
|
+
errorMessage = err.message ?? String(err)
|
|
356
|
+
}
|
|
357
|
+
} else {
|
|
358
|
+
try {
|
|
359
|
+
output = await execute()
|
|
360
|
+
} catch (err) {
|
|
361
|
+
if (throwOnErrors) throw err
|
|
362
|
+
errorType = err.name || 'Error'
|
|
363
|
+
errorMessage = err.message ?? String(err)
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const durationNs = Number(process.hrtime.bigint() - startHr)
|
|
368
|
+
return new Row({
|
|
369
|
+
index,
|
|
370
|
+
spanId: spanContext?.spanId ?? fallbackContext.spanId,
|
|
371
|
+
traceId: spanContext?.traceId ?? fallbackContext.traceId,
|
|
372
|
+
startNs,
|
|
373
|
+
durationNs,
|
|
374
|
+
input: record.input,
|
|
375
|
+
output,
|
|
376
|
+
expectedOutput: record.expectedOutput,
|
|
377
|
+
errorType,
|
|
378
|
+
errorMessage,
|
|
379
|
+
evaluations: {},
|
|
380
|
+
evaluationErrors: {},
|
|
381
|
+
})
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
async #runWithRetries (fn, maxRetries, retryDelay) {
|
|
385
|
+
let lastError
|
|
386
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
387
|
+
try {
|
|
388
|
+
// eslint-disable-next-line no-await-in-loop
|
|
389
|
+
return await fn()
|
|
390
|
+
} catch (err) {
|
|
391
|
+
lastError = err
|
|
392
|
+
if (attempt >= maxRetries) break
|
|
393
|
+
const delayMs = retryDelay(attempt)
|
|
394
|
+
if (typeof delayMs !== 'number' || delayMs < 0 || !Number.isFinite(delayMs)) {
|
|
395
|
+
throw new TypeError('retryDelay must return a non-negative finite number of milliseconds')
|
|
396
|
+
}
|
|
397
|
+
// eslint-disable-next-line no-await-in-loop
|
|
398
|
+
await sleep(delayMs)
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
throw lastError
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
async #runSummaryEvaluators (rows, records, evaluatorResults, options) {
|
|
405
|
+
if (this.#summaryEvaluators.length === 0) return {}
|
|
406
|
+
|
|
407
|
+
const inputs = rows.map(row => row.input)
|
|
408
|
+
const outputs = rows.map(row => row.output)
|
|
409
|
+
const expectedOutputs = rows.map(row => row.expectedOutput)
|
|
410
|
+
const metadata = records.map(record => buildSpanMetadata(record.metadata, this.#config))
|
|
411
|
+
const summaryEvaluations = {}
|
|
412
|
+
const timestampMs = Date.now()
|
|
413
|
+
|
|
414
|
+
for (const [label, evaluator] of this.#summaryEvaluators) {
|
|
415
|
+
try {
|
|
416
|
+
// eslint-disable-next-line no-await-in-loop
|
|
417
|
+
const value = await this.#runWithRetries(
|
|
418
|
+
() => evaluator(inputs, outputs, expectedOutputs, evaluatorResults, metadata),
|
|
419
|
+
options.maxRetries,
|
|
420
|
+
options.retryDelay
|
|
421
|
+
)
|
|
422
|
+
summaryEvaluations[label] = { value, error: null }
|
|
423
|
+
options.metrics.push(toMetric(
|
|
424
|
+
label,
|
|
425
|
+
value,
|
|
426
|
+
null,
|
|
427
|
+
'',
|
|
428
|
+
'',
|
|
429
|
+
timestampMs,
|
|
430
|
+
options.experimentId,
|
|
431
|
+
this.#tags,
|
|
432
|
+
'summary'
|
|
433
|
+
))
|
|
434
|
+
} catch (err) {
|
|
435
|
+
if (options.throwOnErrors) throw err
|
|
436
|
+
const msg = err.message ?? String(err)
|
|
437
|
+
summaryEvaluations[label] = { value: null, error: msg }
|
|
438
|
+
options.metrics.push(toMetric(
|
|
439
|
+
label,
|
|
440
|
+
null,
|
|
441
|
+
msg,
|
|
442
|
+
'',
|
|
443
|
+
'',
|
|
444
|
+
timestampMs,
|
|
445
|
+
options.experimentId,
|
|
446
|
+
this.#tags,
|
|
447
|
+
'summary'
|
|
448
|
+
))
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
return summaryEvaluations
|
|
452
|
+
}
|
|
453
|
+
|
|
262
454
|
async #postEvents (experimentId, spans, metrics) {
|
|
455
|
+
const attributes = { metrics }
|
|
456
|
+
if (spans.length > 0) attributes.spans = spans
|
|
263
457
|
await this.#client.request('POST', `${API_BASE_PATH}/experiments/${experimentId}/events`, {
|
|
264
|
-
data: { type: 'experiments', attributes
|
|
458
|
+
data: { type: 'experiments', attributes },
|
|
265
459
|
})
|
|
266
460
|
}
|
|
267
461
|
|
|
@@ -280,4 +474,4 @@ class Experiment {
|
|
|
280
474
|
}
|
|
281
475
|
}
|
|
282
476
|
|
|
283
|
-
module.exports = { Experiment }
|
|
477
|
+
module.exports = { Experiment, normalizeEvaluators, validateEvaluatorName }
|
|
@@ -26,9 +26,11 @@ async function retryWithBackoff (attempt, { maxTotalMs = 30_000, baseDelayMs = 2
|
|
|
26
26
|
// experiments against the LLM Obs backend using the tracer's own config.
|
|
27
27
|
class Experiments {
|
|
28
28
|
#client
|
|
29
|
+
#llmobs
|
|
29
30
|
#projectName
|
|
30
31
|
|
|
31
|
-
constructor (config) {
|
|
32
|
+
constructor (config, llmobs) {
|
|
33
|
+
this.#llmobs = llmobs
|
|
32
34
|
this.#projectName = config.llmobs?.mlApp || config.service
|
|
33
35
|
this.#client = new ExperimentsClient({
|
|
34
36
|
apiKey: config.DD_API_KEY,
|
|
@@ -39,21 +41,40 @@ class Experiments {
|
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
// Create a local dataset buffer. Pushed remotely on first experiment run.
|
|
42
|
-
createDataset (name,
|
|
43
|
-
|
|
44
|
+
createDataset (name, descriptionOrOptions = '') {
|
|
45
|
+
const options = typeof descriptionOrOptions === 'string'
|
|
46
|
+
? { description: descriptionOrOptions }
|
|
47
|
+
: (descriptionOrOptions ?? {})
|
|
48
|
+
const dataset = new Dataset(this.#client, name, options.description ?? '')
|
|
49
|
+
const recordIds = new Set()
|
|
50
|
+
if ((options.records) != null) {
|
|
51
|
+
for (const record of options.records) {
|
|
52
|
+
if (record.id !== undefined && (typeof record.id !== 'string' || record.id.length === 0)) {
|
|
53
|
+
throw new Error('record id must be a non-empty string')
|
|
54
|
+
}
|
|
55
|
+
if (record.id !== undefined) {
|
|
56
|
+
if (recordIds.has(record.id)) throw new Error(`Duplicate record id '${record.id}'`)
|
|
57
|
+
recordIds.add(record.id)
|
|
58
|
+
}
|
|
59
|
+
dataset.addRecord(new DatasetRecord(record.inputData, record.expectedOutput, record.metadata, record.id))
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return dataset
|
|
44
63
|
}
|
|
45
64
|
|
|
46
65
|
// Pull an existing dataset by name (with its records). Polls with exponential
|
|
47
66
|
// backoff to absorb read-after-write lag; pass `expectedRecordCount` to also
|
|
48
67
|
// wait until that many records are readable.
|
|
49
68
|
async pullDataset (name, options = {}) {
|
|
50
|
-
const { expectedRecordCount, maxWaitMs = 30_000 } = options
|
|
69
|
+
const { expectedRecordCount, maxWaitMs = 30_000, version } = options
|
|
51
70
|
const projectId = await this.#client.ensureProjectId()
|
|
52
71
|
|
|
53
72
|
let datasetId = null
|
|
54
73
|
let description = ''
|
|
55
74
|
let records = []
|
|
56
75
|
let recordIds = []
|
|
76
|
+
let datasetVersion = version ?? null
|
|
77
|
+
let latestVersion = null
|
|
57
78
|
let lastError = ''
|
|
58
79
|
|
|
59
80
|
const succeeded = await retryWithBackoff(async () => {
|
|
@@ -63,11 +84,16 @@ class Experiments {
|
|
|
63
84
|
'GET',
|
|
64
85
|
`${API_BASE_PATH}/${projectId}/datasets?filter[name]=${encodeURIComponent(name)}`
|
|
65
86
|
)
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
87
|
+
const datasets = listed?.data
|
|
88
|
+
if (datasets) {
|
|
89
|
+
for (const item of datasets) {
|
|
90
|
+
if (item?.attributes?.name === name) {
|
|
91
|
+
datasetId = String(item?.id ?? '')
|
|
92
|
+
description = String(item?.attributes?.description ?? '')
|
|
93
|
+
latestVersion = item?.attributes?.current_version ?? null
|
|
94
|
+
datasetVersion = version ?? latestVersion
|
|
95
|
+
break
|
|
96
|
+
}
|
|
71
97
|
}
|
|
72
98
|
}
|
|
73
99
|
if (datasetId === null) return false
|
|
@@ -78,16 +104,27 @@ class Experiments {
|
|
|
78
104
|
let cursor = ''
|
|
79
105
|
// Follow the meta.after / page[cursor] pagination until the last page.
|
|
80
106
|
for (;;) {
|
|
81
|
-
const query =
|
|
107
|
+
const query = new URLSearchParams()
|
|
108
|
+
if (cursor) query.set('page[cursor]', cursor)
|
|
109
|
+
if (datasetVersion !== null) query.set('filter[version]', String(datasetVersion))
|
|
82
110
|
// eslint-disable-next-line no-await-in-loop
|
|
83
111
|
const resp = await this.#client.request(
|
|
84
112
|
'GET',
|
|
85
|
-
`${API_BASE_PATH}/${projectId}/datasets/${datasetId}/records
|
|
113
|
+
`${API_BASE_PATH}/${projectId}/datasets/${datasetId}/records?${query.toString()}`
|
|
86
114
|
)
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
115
|
+
const recordData = resp?.data
|
|
116
|
+
if (recordData) {
|
|
117
|
+
for (const item of recordData) {
|
|
118
|
+
const attrs = item?.attributes ?? item
|
|
119
|
+
const recordId = String(item?.id ?? attrs?.id ?? '')
|
|
120
|
+
recs.push(new DatasetRecord(
|
|
121
|
+
attrs?.input ?? null,
|
|
122
|
+
attrs?.expected_output ?? null,
|
|
123
|
+
attrs?.metadata ?? {},
|
|
124
|
+
recordId === '' ? null : recordId
|
|
125
|
+
))
|
|
126
|
+
ids.push(recordId)
|
|
127
|
+
}
|
|
91
128
|
}
|
|
92
129
|
cursor = resp?.meta?.after ?? ''
|
|
93
130
|
if (!cursor) break
|
|
@@ -119,18 +156,28 @@ class Experiments {
|
|
|
119
156
|
)
|
|
120
157
|
}
|
|
121
158
|
|
|
122
|
-
return Dataset.fromExisting(
|
|
159
|
+
return Dataset.fromExisting(
|
|
160
|
+
this.#client,
|
|
161
|
+
name,
|
|
162
|
+
description,
|
|
163
|
+
datasetId,
|
|
164
|
+
projectId,
|
|
165
|
+
records,
|
|
166
|
+
recordIds,
|
|
167
|
+
datasetVersion,
|
|
168
|
+
latestVersion
|
|
169
|
+
)
|
|
123
170
|
}
|
|
124
171
|
|
|
125
172
|
// Build an experiment: { name, dataset, task, evaluators, description?, config?, tags? }.
|
|
126
173
|
experiment (options) {
|
|
127
|
-
return new Experiment(this.#client, options)
|
|
174
|
+
return new Experiment(this.#client, options, this.#llmobs)
|
|
128
175
|
}
|
|
129
176
|
}
|
|
130
177
|
|
|
131
178
|
// Factory used by the LLMObs SDK: returns a real Experiments instance when
|
|
132
179
|
// enabled and credentialed, otherwise a no-op that explains what's missing.
|
|
133
|
-
function createExperiments (config) {
|
|
180
|
+
function createExperiments (config, llmobs) {
|
|
134
181
|
if (!config.llmobs?.DD_LLMOBS_ENABLED) {
|
|
135
182
|
return new NoopExperiments('LLM Observability is not enabled')
|
|
136
183
|
}
|
|
@@ -146,7 +193,7 @@ function createExperiments (config) {
|
|
|
146
193
|
'then retry'
|
|
147
194
|
)
|
|
148
195
|
}
|
|
149
|
-
return new Experiments(config)
|
|
196
|
+
return new Experiments(config, llmobs)
|
|
150
197
|
}
|
|
151
198
|
|
|
152
199
|
module.exports = { Experiments, createExperiments }
|