dd-trace 6.18.0 → 6.20.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.
Files changed (123) hide show
  1. package/LICENSE-3rdparty.csv +1 -0
  2. package/ci/vitest-no-worker-init-setup.mjs +22 -12
  3. package/index.d.ts +166 -24
  4. package/package.json +7 -7
  5. package/packages/datadog-esbuild/index.js +9 -6
  6. package/packages/datadog-instrumentations/src/ai.js +26 -0
  7. package/packages/datadog-instrumentations/src/anthropic.js +111 -9
  8. package/packages/datadog-instrumentations/src/aws-sdk.js +2 -2
  9. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +5 -1
  10. package/packages/datadog-instrumentations/src/cucumber.js +40 -4
  11. package/packages/datadog-instrumentations/src/helpers/hooks.js +0 -7
  12. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentation-registry.js +2 -2
  13. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/ai.js +25 -0
  14. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +8 -0
  15. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +2 -0
  16. package/packages/datadog-instrumentations/src/jest/session-error.js +102 -0
  17. package/packages/datadog-instrumentations/src/jest.js +3 -10
  18. package/packages/datadog-instrumentations/src/playwright.js +26 -0
  19. package/packages/datadog-instrumentations/src/vitest-main.js +4 -1
  20. package/packages/datadog-instrumentations/src/vitest-worker.js +34 -3
  21. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +83 -29
  22. package/packages/datadog-plugin-cucumber/src/index.js +19 -4
  23. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +7 -0
  24. package/packages/datadog-plugin-fetch/src/index.js +3 -0
  25. package/packages/datadog-plugin-openai/src/stream-helpers.js +50 -1
  26. package/packages/datadog-plugin-oracledb/src/connection-parser.js +3 -1
  27. package/packages/datadog-plugin-undici/src/index.js +5 -0
  28. package/packages/dd-trace/src/aiguard/client.js +38 -19
  29. package/packages/dd-trace/src/aiguard/integrations/anthropic.js +29 -4
  30. package/packages/dd-trace/src/aiguard/integrations/index.js +1 -1
  31. package/packages/dd-trace/src/aiguard/integrations/openai.js +20 -56
  32. package/packages/dd-trace/src/aiguard/integrations/stream.js +60 -0
  33. package/packages/dd-trace/src/aiguard/messages/anthropic.js +64 -0
  34. package/packages/dd-trace/src/config/generated-config-types.d.ts +8 -4
  35. package/packages/dd-trace/src/config/remote_config.js +12 -0
  36. package/packages/dd-trace/src/config/supported-configurations.json +26 -5
  37. package/packages/dd-trace/src/constants.js +2 -0
  38. package/packages/dd-trace/src/debugger/constants.js +13 -4
  39. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +14 -1
  40. package/packages/dd-trace/src/debugger/devtools_client/condition.js +109 -6
  41. package/packages/dd-trace/src/debugger/devtools_client/config.js +2 -0
  42. package/packages/dd-trace/src/debugger/devtools_client/index.js +65 -6
  43. package/packages/dd-trace/src/debugger/devtools_client/probe_sampler.js +22 -10
  44. package/packages/dd-trace/src/debugger/devtools_client/remote_config.js +12 -8
  45. package/packages/dd-trace/src/debugger/devtools_client/send.js +14 -0
  46. package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +43 -5
  47. package/packages/dd-trace/src/debugger/devtools_client/snapshot/processor.js +3 -8
  48. package/packages/dd-trace/src/debugger/devtools_client/snapshot/redaction.js +2 -122
  49. package/packages/dd-trace/src/debugger/guardrail-metrics.js +0 -2
  50. package/packages/dd-trace/src/debugger/index.js +60 -28
  51. package/packages/dd-trace/src/debugger/inspect-segment.js +92 -23
  52. package/packages/dd-trace/src/debugger/pause-duration-histogram.js +74 -0
  53. package/packages/dd-trace/src/debugger/probe_sampler.js +192 -52
  54. package/packages/dd-trace/src/debugger/redaction.js +139 -0
  55. package/packages/dd-trace/src/knuth-hash.js +17 -0
  56. package/packages/dd-trace/src/llmobs/constants/tags.js +33 -8
  57. package/packages/dd-trace/src/llmobs/experiments/client.js +66 -2
  58. package/packages/dd-trace/src/llmobs/experiments/evaluator.js +138 -0
  59. package/packages/dd-trace/src/llmobs/experiments/experiment.js +213 -90
  60. package/packages/dd-trace/src/llmobs/experiments/index.js +40 -1
  61. package/packages/dd-trace/src/llmobs/experiments/noop.js +53 -2
  62. package/packages/dd-trace/src/llmobs/experiments/remote-evaluator.js +110 -0
  63. package/packages/dd-trace/src/llmobs/experiments/util.js +34 -6
  64. package/packages/dd-trace/src/llmobs/gen-ai-tags.js +127 -0
  65. package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +18 -0
  66. package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +33 -8
  67. package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +77 -41
  68. package/packages/dd-trace/src/llmobs/plugins/base.js +154 -15
  69. package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +162 -10
  70. package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/index.js +65 -16
  71. package/packages/dd-trace/src/llmobs/plugins/genai/index.js +14 -0
  72. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/chat_model.js +34 -35
  73. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +10 -0
  74. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +25 -2
  75. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +8 -7
  76. package/packages/dd-trace/src/llmobs/plugins/openai/index.js +15 -2
  77. package/packages/dd-trace/src/llmobs/plugins/openai/realtime.js +5 -0
  78. package/packages/dd-trace/src/llmobs/plugins/vertexai.js +7 -0
  79. package/packages/dd-trace/src/llmobs/prompts/prompt.js +2 -1
  80. package/packages/dd-trace/src/llmobs/sdk.js +19 -3
  81. package/packages/dd-trace/src/llmobs/span_processor.js +52 -91
  82. package/packages/dd-trace/src/llmobs/tagger.js +37 -36
  83. package/packages/dd-trace/src/llmobs/writers/base.js +1 -6
  84. package/packages/dd-trace/src/openfeature/constants/constants.js +10 -0
  85. package/packages/dd-trace/src/openfeature/flagging_provider.js +14 -1
  86. package/packages/dd-trace/src/openfeature/writers/base.js +39 -18
  87. package/packages/dd-trace/src/openfeature/writers/flag-eval-evp-hook.js +105 -0
  88. package/packages/dd-trace/src/openfeature/writers/flag-evaluation-aggregation.js +190 -0
  89. package/packages/dd-trace/src/openfeature/writers/flag-evaluation-consumer.js +233 -0
  90. package/packages/dd-trace/src/openfeature/writers/flag-evaluation-context.js +262 -0
  91. package/packages/dd-trace/src/openfeature/writers/flag-evaluation-payload.js +181 -0
  92. package/packages/dd-trace/src/openfeature/writers/flag-evaluation-pii.js +108 -0
  93. package/packages/dd-trace/src/openfeature/writers/flag-evaluation-telemetry.js +154 -0
  94. package/packages/dd-trace/src/openfeature/writers/flag-evaluation-worker.js +77 -0
  95. package/packages/dd-trace/src/openfeature/writers/flag-evaluations.js +398 -0
  96. package/packages/dd-trace/src/openfeature/writers/util.js +5 -4
  97. package/packages/dd-trace/src/opentelemetry/context_manager.js +3 -0
  98. package/packages/dd-trace/src/opentelemetry/span_context.js +6 -2
  99. package/packages/dd-trace/src/opentelemetry/trace/index.js +5 -0
  100. package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +6 -0
  101. package/packages/dd-trace/src/opentelemetry/tracer.js +16 -18
  102. package/packages/dd-trace/src/opentracing/propagation/text_map.js +116 -25
  103. package/packages/dd-trace/src/opentracing/propagation/tracestate.js +80 -16
  104. package/packages/dd-trace/src/opentracing/span.js +14 -9
  105. package/packages/dd-trace/src/opentracing/tracer.js +10 -3
  106. package/packages/dd-trace/src/otel-sampling.js +171 -0
  107. package/packages/dd-trace/src/plugins/index.js +0 -1
  108. package/packages/dd-trace/src/plugins/util/test.js +4 -0
  109. package/packages/dd-trace/src/priority_sampler.js +155 -33
  110. package/packages/dd-trace/src/profiler.js +51 -5
  111. package/packages/dd-trace/src/profiling/profiler.js +24 -20
  112. package/packages/dd-trace/src/remote_config/capabilities.js +9 -0
  113. package/packages/dd-trace/src/ritm.js +12 -9
  114. package/packages/dd-trace/src/sampler.js +2 -5
  115. package/packages/dd-trace/src/sampling_rule.js +4 -8
  116. package/packages/dd-trace/src/span_format.js +6 -0
  117. package/packages/dd-trace/src/span_processor.js +32 -3
  118. package/packages/dd-trace/src/standalone/index.js +5 -4
  119. package/packages/dd-trace/src/standalone/tracesource_priority_sampler.js +25 -5
  120. package/packages/dd-trace/src/telemetry/metrics.js +4 -3
  121. package/vendor/dist/@datadog/openfeature-node-server/index.js +1 -1
  122. package/packages/datadog-instrumentations/src/postgres.js +0 -7
  123. package/packages/datadog-instrumentations/src/supabase.js +0 -15
@@ -2,7 +2,17 @@
2
2
 
3
3
  const id = require('../../id')
4
4
  const log = require('../../log')
5
+ const { validateAssessment, validateReasoning } = require('../eval-metric')
5
6
 
7
+ const {
8
+ BaseEvaluator,
9
+ BaseSummaryEvaluator,
10
+ EvaluatorContext,
11
+ EvaluatorResult,
12
+ MultiEvaluatorResult,
13
+ SummaryEvaluatorContext,
14
+ } = require('./evaluator')
15
+ const { RemoteEvaluator, RemoteEvaluatorError } = require('./remote-evaluator')
6
16
  const { Row, ExperimentResult, ExperimentRun } = require('./result')
7
17
  const {
8
18
  buildSpanMetadata,
@@ -69,7 +79,17 @@ function toSpan (row, metadata, ids, spanName, userTags, recordTags) {
69
79
 
70
80
  // One metric per evaluator per row or summary evaluator.
71
81
  function toMetric (
72
- label, value, errorMessage, spanId, traceId, timestampMs, experimentId, userTags, source = 'custom', ids = {}
82
+ label,
83
+ value,
84
+ errorMessage,
85
+ spanId,
86
+ traceId,
87
+ timestampMs,
88
+ experimentId,
89
+ userTags,
90
+ source = 'custom',
91
+ ids = {},
92
+ extras = {}
73
93
  ) {
74
94
  const metric = {
75
95
  metric_source: source,
@@ -77,17 +97,30 @@ function toMetric (
77
97
  span_id: spanId,
78
98
  trace_id: traceId,
79
99
  timestamp_ms: timestampMs,
80
- tags: buildTags(userTags, {
100
+ tags: buildTags({ ...userTags, ...extras.tags }, {
81
101
  experiment_id: experimentId,
82
102
  run_id: ids.runId,
83
103
  run_iteration: ids.runIteration,
104
+ project_name: userTags.project_name,
84
105
  }),
85
106
  experiment_id: experimentId,
86
107
  }
87
108
 
109
+ if (extras.reasoning !== undefined) {
110
+ validateReasoning(extras.reasoning)
111
+ metric.reasoning = extras.reasoning
112
+ }
113
+ if (extras.assessment !== undefined) {
114
+ validateAssessment(extras.assessment)
115
+ metric.assessment = extras.assessment
116
+ }
117
+ if (extras.metadata !== undefined) metric.metadata = extras.metadata
118
+ if (extras.status !== undefined) metric.status = extras.status
119
+ if (extras.evalSourceType !== undefined) metric.eval_source_type = extras.evalSourceType
120
+
88
121
  if (errorMessage !== null) {
89
122
  metric.metric_type = 'categorical'
90
- metric.error = { message: errorMessage }
123
+ metric.error = extras.error ?? { message: errorMessage }
91
124
  return metric
92
125
  }
93
126
 
@@ -100,6 +133,31 @@ function toMetric (
100
133
  return metric
101
134
  }
102
135
 
136
+ /**
137
+ * @param {unknown} result
138
+ * @param {string} label
139
+ * @returns {Array<{label: string, value: unknown, extras: object}>}
140
+ */
141
+ function extractEvaluatorResults (result, label) {
142
+ if (!(result instanceof MultiEvaluatorResult)) {
143
+ return [{
144
+ label,
145
+ value: result instanceof EvaluatorResult ? result.value : result,
146
+ extras: result instanceof EvaluatorResult ? result : {},
147
+ }]
148
+ }
149
+
150
+ const results = []
151
+ for (const [key, value] of Object.entries(result.values)) {
152
+ results.push({
153
+ label: result.prefix ? `${label}-${key}` : key,
154
+ value: value instanceof EvaluatorResult ? value.value : value,
155
+ extras: value instanceof EvaluatorResult ? value : {},
156
+ })
157
+ }
158
+ return results
159
+ }
160
+
103
161
  function createFallbackSpanContext (startNs) {
104
162
  // Root-span id generation, same convention as opentracing/span.js: a single
105
163
  // random 64-bit id for the span, reused as the trace id's low 64 bits with a
@@ -543,13 +601,15 @@ class Experiment {
543
601
  const metrics = []
544
602
  const evaluatorResults = {}
545
603
  let hasRowError = false
546
- for (const [label] of this.#evaluators) evaluatorResults[label] = []
547
604
 
548
605
  for (let i = 0; i < results.length; i++) {
549
606
  const result = results[i]
550
607
  const row = result.row
551
608
  rows[i] = row
552
- for (const [label] of this.#evaluators) {
609
+ for (const label of Object.keys(result.evaluatorValues)) {
610
+ if (!Object.hasOwn(evaluatorResults, label)) evaluatorResults[label] = new Array(i).fill(null)
611
+ }
612
+ for (const label of Object.keys(evaluatorResults)) {
553
613
  const value = Object.hasOwn(result.evaluatorValues, label) ? result.evaluatorValues[label] : null
554
614
  evaluatorResults[label].push(value)
555
615
  }
@@ -671,8 +731,13 @@ class Experiment {
671
731
 
672
732
  const evaluatorResults = await Promise.all(pending)
673
733
  for (const result of evaluatorResults) {
674
- if (result.metric !== null) metrics.push(result.metric)
675
- evaluatorValues[result.label] = result.value
734
+ for (const value of result.values) {
735
+ if (Object.hasOwn(evaluatorValues, value.label)) {
736
+ throw new Error(`Evaluator metric label '${value.label}' was emitted more than once`)
737
+ }
738
+ if (value.metric !== null) metrics.push(value.metric)
739
+ evaluatorValues[value.label] = value.value
740
+ }
676
741
  if (result.error !== undefined && firstError === undefined) firstError = result.error
677
742
  }
678
743
  if (firstError !== undefined) throw firstError
@@ -703,64 +768,101 @@ class Experiment {
703
768
  row.evaluationErrors[label] = TASK_ERROR_MESSAGE
704
769
  return {
705
770
  label,
706
- value: null,
707
- metric: toMetric(
771
+ values: [{
708
772
  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
- ),
773
+ value: null,
774
+ metric: toMetric(
775
+ label,
776
+ null,
777
+ TASK_ERROR_MESSAGE,
778
+ row.spanId,
779
+ row.traceId,
780
+ timestampMs,
781
+ experimentId,
782
+ this.#tags,
783
+ 'custom',
784
+ { runId, runIteration }
785
+ ),
786
+ }],
787
+ error: undefined,
719
788
  }
720
789
  }
721
790
 
791
+ const isRemoteEvaluator = evaluator instanceof RemoteEvaluator
722
792
  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
- ),
793
+ let evaluate
794
+ if (evaluator instanceof BaseEvaluator) {
795
+ const context = new EvaluatorContext({
796
+ inputData: record.input,
797
+ outputData: row.output,
798
+ expectedOutput: record.expectedOutput,
799
+ metadata: buildSpanMetadata(record.metadata, this.#config),
800
+ spanId: row.spanId,
801
+ traceId: row.traceId,
802
+ })
803
+ evaluate = isRemoteEvaluator
804
+ ? () => evaluator.evaluate(context, this.#client)
805
+ : () => evaluator.evaluate(context)
806
+ } else {
807
+ evaluate = () => evaluator(record.input, row.output, record.expectedOutput)
808
+ }
809
+ const result = await limit(() => this.#runWithRetries(evaluate, maxRetries, retryDelay), throwOnErrors)
810
+ const values = []
811
+ for (const evaluatorResult of extractEvaluatorResults(result, label)) {
812
+ row.evaluations[evaluatorResult.label] = evaluatorResult.value
813
+ values.push({
814
+ label: evaluatorResult.label,
815
+ value: evaluatorResult.value,
816
+ metric: toMetric(
817
+ evaluatorResult.label,
818
+ evaluatorResult.value,
819
+ null,
820
+ row.spanId,
821
+ row.traceId,
822
+ timestampMs,
823
+ experimentId,
824
+ this.#tags,
825
+ 'custom',
826
+ { runId, runIteration },
827
+ evaluatorResult.extras
828
+ ),
829
+ })
744
830
  }
831
+ return { label, values, error: undefined }
745
832
  } catch (err) {
746
833
  if (throwOnErrors) throw err
747
- const msg = err.message ?? String(err)
834
+ const backendError = err instanceof RemoteEvaluatorError && hasEntries(err.backendError)
835
+ ? err.backendError
836
+ : undefined
837
+ const msg = backendError?.message ?? err.message ?? String(err)
748
838
  row.evaluationErrors[label] = msg
839
+ const extras = isRemoteEvaluator
840
+ ? {
841
+ error: backendError,
842
+ status: err.status ?? 'ERROR',
843
+ evalSourceType: 'managed',
844
+ }
845
+ : {}
749
846
  return {
750
847
  label,
751
- value: null,
752
- metric: toMetric(
848
+ values: [{
753
849
  label,
754
- null,
755
- msg,
756
- row.spanId,
757
- row.traceId,
758
- timestampMs,
759
- experimentId,
760
- this.#tags,
761
- 'custom',
762
- { runId, runIteration }
763
- ),
850
+ value: null,
851
+ metric: toMetric(
852
+ label,
853
+ null,
854
+ msg,
855
+ row.spanId,
856
+ row.traceId,
857
+ timestampMs,
858
+ experimentId,
859
+ this.#tags,
860
+ 'custom',
861
+ { runId, runIteration },
862
+ extras
863
+ ),
864
+ }],
865
+ error: undefined,
764
866
  }
765
867
  }
766
868
  }
@@ -892,7 +994,6 @@ class Experiment {
892
994
  const summaryEvaluations = {}
893
995
  const timestampMs = Date.now()
894
996
  const pending = new Array(this.#summaryEvaluators.length)
895
- let firstError
896
997
 
897
998
  for (let i = 0; i < this.#summaryEvaluators.length; i++) {
898
999
  const [label, evaluator] = this.#summaryEvaluators[i]
@@ -917,14 +1018,14 @@ class Experiment {
917
1018
  throw err
918
1019
  }
919
1020
  for (const result of results) {
920
- if (result.error !== undefined) {
921
- if (firstError === undefined) firstError = result.error
922
- continue
1021
+ for (const value of result.values) {
1022
+ if (Object.hasOwn(summaryEvaluations, value.label)) {
1023
+ throw new Error(`Summary evaluator metric label '${value.label}' was emitted more than once`)
1024
+ }
1025
+ summaryEvaluations[value.label] = value.evaluation
1026
+ options.metrics.push(value.metric)
923
1027
  }
924
- summaryEvaluations[result.label] = result.evaluation
925
- options.metrics.push(result.metric)
926
1028
  }
927
- if (firstError !== undefined) throw firstError
928
1029
 
929
1030
  return summaryEvaluations
930
1031
  }
@@ -941,45 +1042,67 @@ class Experiment {
941
1042
  options,
942
1043
  }) {
943
1044
  try {
944
- const value = await options.limit(() => this.#runWithRetries(
945
- () => evaluator(inputs, outputs, expectedOutputs, evaluatorResults, metadata),
1045
+ const context = new SummaryEvaluatorContext({
1046
+ inputs,
1047
+ outputs,
1048
+ expectedOutputs,
1049
+ evaluationResults: evaluatorResults,
1050
+ metadata,
1051
+ })
1052
+ const evaluate = evaluator instanceof BaseSummaryEvaluator
1053
+ ? () => evaluator.evaluate(context)
1054
+ : () => evaluator(inputs, outputs, expectedOutputs, evaluatorResults, metadata)
1055
+ const result = await options.limit(() => this.#runWithRetries(
1056
+ evaluate,
946
1057
  options.maxRetries,
947
1058
  options.retryDelay
948
1059
  ), options.throwOnErrors)
949
- return {
950
- label,
951
- evaluation: { value, error: null },
952
- metric: toMetric(
953
- label,
954
- value,
955
- null,
956
- '',
957
- '',
958
- timestampMs,
959
- options.experimentId,
960
- this.#tags,
961
- 'summary',
962
- { runId: options.runId, runIteration: options.runIteration }
963
- ),
1060
+ const values = []
1061
+ for (const evaluatorResult of extractEvaluatorResults(result, label)) {
1062
+ const evaluation = { value: evaluatorResult.value, error: null }
1063
+ if (evaluatorResult.extras.reasoning !== undefined) evaluation.reasoning = evaluatorResult.extras.reasoning
1064
+ if (evaluatorResult.extras.assessment !== undefined) evaluation.assessment = evaluatorResult.extras.assessment
1065
+ if (evaluatorResult.extras.metadata !== undefined) evaluation.metadata = evaluatorResult.extras.metadata
1066
+ if (evaluatorResult.extras.tags !== undefined) evaluation.tags = evaluatorResult.extras.tags
1067
+ values.push({
1068
+ label: evaluatorResult.label,
1069
+ evaluation,
1070
+ metric: toMetric(
1071
+ evaluatorResult.label,
1072
+ evaluatorResult.value,
1073
+ null,
1074
+ '',
1075
+ '',
1076
+ timestampMs,
1077
+ options.experimentId,
1078
+ this.#tags,
1079
+ 'summary',
1080
+ { runId: options.runId, runIteration: options.runIteration },
1081
+ evaluatorResult.extras
1082
+ ),
1083
+ })
964
1084
  }
1085
+ return { values }
965
1086
  } catch (err) {
966
1087
  if (options.throwOnErrors) throw err
967
1088
  const msg = err.message ?? String(err)
968
1089
  return {
969
- label,
970
- evaluation: { value: null, error: msg },
971
- metric: toMetric(
1090
+ values: [{
972
1091
  label,
973
- null,
974
- msg,
975
- '',
976
- '',
977
- timestampMs,
978
- options.experimentId,
979
- this.#tags,
980
- 'summary',
981
- { runId: options.runId, runIteration: options.runIteration }
982
- ),
1092
+ evaluation: { value: null, error: msg },
1093
+ metric: toMetric(
1094
+ label,
1095
+ null,
1096
+ msg,
1097
+ '',
1098
+ '',
1099
+ timestampMs,
1100
+ options.experimentId,
1101
+ this.#tags,
1102
+ 'summary',
1103
+ { runId: options.runId, runIteration: options.runIteration }
1104
+ ),
1105
+ }],
983
1106
  }
984
1107
  }
985
1108
  }
@@ -4,6 +4,8 @@ const log = require('../../log')
4
4
  const { ExperimentsClient } = require('./client')
5
5
  const { Dataset } = require('./dataset')
6
6
  const { Experiment, ExternalExperiment } = require('./experiment')
7
+ const evaluatorTypes = require('./evaluator')
8
+ const remoteEvaluatorTypes = require('./remote-evaluator')
7
9
  const { validateTagsList } = require('./util')
8
10
  const NoopExperiments = require('./noop')
9
11
 
@@ -40,6 +42,38 @@ class Experiments {
40
42
  this.#client = this.#clientForProject(this.#projectName)
41
43
  }
42
44
 
45
+ get BaseEvaluator () {
46
+ return evaluatorTypes.BaseEvaluator
47
+ }
48
+
49
+ get BaseSummaryEvaluator () {
50
+ return evaluatorTypes.BaseSummaryEvaluator
51
+ }
52
+
53
+ get EvaluatorContext () {
54
+ return evaluatorTypes.EvaluatorContext
55
+ }
56
+
57
+ get SummaryEvaluatorContext () {
58
+ return evaluatorTypes.SummaryEvaluatorContext
59
+ }
60
+
61
+ get EvaluatorResult () {
62
+ return evaluatorTypes.EvaluatorResult
63
+ }
64
+
65
+ get MultiEvaluatorResult () {
66
+ return evaluatorTypes.MultiEvaluatorResult
67
+ }
68
+
69
+ get RemoteEvaluator () {
70
+ return remoteEvaluatorTypes.RemoteEvaluator
71
+ }
72
+
73
+ get RemoteEvaluatorError () {
74
+ return remoteEvaluatorTypes.RemoteEvaluatorError
75
+ }
76
+
43
77
  /**
44
78
  * @param {string} projectName
45
79
  * @returns {ExperimentsClient}
@@ -220,4 +254,9 @@ function createExperiments (config, llmobs) {
220
254
  return new Experiments(config, llmobs)
221
255
  }
222
256
 
223
- module.exports = { Experiments, createExperiments }
257
+ module.exports = {
258
+ Experiments,
259
+ createExperiments,
260
+ ...evaluatorTypes,
261
+ ...remoteEvaluatorTypes,
262
+ }
@@ -1,7 +1,25 @@
1
1
  'use strict'
2
2
 
3
3
  const log = require('../../log')
4
- const { ExternalExperiment } = require('./experiment')
4
+
5
+ let ExternalExperiment
6
+ let evaluatorTypes
7
+ let remoteEvaluatorTypes
8
+
9
+ function getExternalExperiment () {
10
+ ExternalExperiment ??= require('./experiment').ExternalExperiment
11
+ return ExternalExperiment
12
+ }
13
+
14
+ function getEvaluatorTypes () {
15
+ evaluatorTypes ??= require('./evaluator')
16
+ return evaluatorTypes
17
+ }
18
+
19
+ function getRemoteEvaluatorTypes () {
20
+ remoteEvaluatorTypes ??= require('./remote-evaluator')
21
+ return remoteEvaluatorTypes
22
+ }
5
23
 
6
24
  const NOOP_EXPERIMENT_ID = '00000000-0000-0000-0000-000000000000'
7
25
  const NOOP_SPAN_ID = '0000000000000000'
@@ -246,6 +264,38 @@ class NoopExperiments {
246
264
  log.warn('LLMObs experiments unavailable: %s', this.#reason)
247
265
  }
248
266
 
267
+ get BaseEvaluator () {
268
+ return getEvaluatorTypes().BaseEvaluator
269
+ }
270
+
271
+ get BaseSummaryEvaluator () {
272
+ return getEvaluatorTypes().BaseSummaryEvaluator
273
+ }
274
+
275
+ get EvaluatorContext () {
276
+ return getEvaluatorTypes().EvaluatorContext
277
+ }
278
+
279
+ get SummaryEvaluatorContext () {
280
+ return getEvaluatorTypes().SummaryEvaluatorContext
281
+ }
282
+
283
+ get EvaluatorResult () {
284
+ return getEvaluatorTypes().EvaluatorResult
285
+ }
286
+
287
+ get MultiEvaluatorResult () {
288
+ return getEvaluatorTypes().MultiEvaluatorResult
289
+ }
290
+
291
+ get RemoteEvaluator () {
292
+ return getRemoteEvaluatorTypes().RemoteEvaluator
293
+ }
294
+
295
+ get RemoteEvaluatorError () {
296
+ return getRemoteEvaluatorTypes().RemoteEvaluatorError
297
+ }
298
+
249
299
  createDataset (name, options = {}) {
250
300
  this.#warn()
251
301
  return new NoopDataset(name, options)
@@ -267,7 +317,8 @@ class NoopExperiments {
267
317
  */
268
318
  startExperiment (options = {}) {
269
319
  this.#warn()
270
- return Promise.resolve(new ExternalExperiment(new NoopExperiment(options.name, true)))
320
+ const NoopExternalExperiment = getExternalExperiment()
321
+ return Promise.resolve(new NoopExternalExperiment(new NoopExperiment(options.name, true)))
271
322
  }
272
323
  }
273
324
 
@@ -0,0 +1,110 @@
1
+ 'use strict'
2
+
3
+ const { BaseEvaluator, EvaluatorResult } = require('./evaluator')
4
+ const { hasEntries } = require('./util')
5
+
6
+ /**
7
+ * @typedef {{value?: unknown, reasoning?: string, assessment?: string, status?: string}} RemoteEvaluatorResponse
8
+ * @typedef {{evaluatorInfer: (evalName: string, context: object) => Promise<RemoteEvaluatorResponse>}}
9
+ * RemoteEvaluatorClient
10
+ */
11
+
12
+ /**
13
+ * @param {import('./evaluator').EvaluatorContext} context
14
+ */
15
+ function defaultContextTransform (context) {
16
+ const transformed = {
17
+ span_input: context.inputData,
18
+ span_output: context.outputData,
19
+ }
20
+
21
+ const meta = {}
22
+ let hasMeta = false
23
+ if (context.expectedOutput != null) {
24
+ meta.expected_output = context.expectedOutput
25
+ hasMeta = true
26
+ }
27
+ if (hasEntries(context.metadata)) {
28
+ meta.metadata = context.metadata
29
+ hasMeta = true
30
+ }
31
+ if (hasMeta) transformed.meta = meta
32
+
33
+ if (context.spanId) transformed.span_id = context.spanId
34
+ if (context.traceId) transformed.trace_id = context.traceId
35
+
36
+ return transformed
37
+ }
38
+
39
+ class RemoteEvaluatorResult extends EvaluatorResult {
40
+ /**
41
+ * @param {unknown} value
42
+ * @param {{reasoning?: string, assessment?: string, status?: string}} [options]
43
+ */
44
+ constructor (value, { reasoning, assessment, status } = {}) {
45
+ super(value, { reasoning, assessment })
46
+ this.status = status
47
+ this.evalSourceType = 'managed'
48
+ }
49
+ }
50
+
51
+ class RemoteEvaluatorError extends Error {
52
+ /**
53
+ * @param {string} message
54
+ * @param {{status?: string, backendError?: object}} [options]
55
+ */
56
+ constructor (message, { status = 'ERROR', backendError = {} } = {}) {
57
+ super(message)
58
+ this.name = 'RemoteEvaluatorError'
59
+ this.status = status
60
+ this.backendError = backendError
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Evaluator that references an LLM-as-a-judge evaluator configured in Datadog.
66
+ */
67
+ class RemoteEvaluator extends BaseEvaluator {
68
+ #evalName
69
+ #transformFn
70
+
71
+ /**
72
+ * @param {{evalName: string, transformFn?: (context: import('./evaluator').EvaluatorContext) => object}} options
73
+ */
74
+ constructor (options) {
75
+ super()
76
+ const { evalName, transformFn } = options ?? {}
77
+ if (typeof evalName !== 'string' || evalName.trim() === '') {
78
+ throw new TypeError('evalName must be a non-empty string')
79
+ }
80
+ if (transformFn !== undefined && typeof transformFn !== 'function') {
81
+ throw new TypeError('transformFn must be a function')
82
+ }
83
+
84
+ this.name = evalName.trim()
85
+ this.#evalName = this.name
86
+ this.#transformFn = transformFn ?? defaultContextTransform
87
+ }
88
+
89
+ /**
90
+ * @param {import('./evaluator').EvaluatorContext} context
91
+ * @param {RemoteEvaluatorClient} [client]
92
+ */
93
+ evaluate (context, client) {
94
+ if (client === undefined || typeof client.evaluatorInfer !== 'function') {
95
+ throw new Error('RemoteEvaluator can only be evaluated as part of an experiment')
96
+ }
97
+
98
+ const transformed = this.#transformFn(context)
99
+ return client.evaluatorInfer(this.#evalName, transformed).then(result => new RemoteEvaluatorResult(
100
+ result?.value,
101
+ {
102
+ reasoning: result?.reasoning,
103
+ assessment: result?.assessment,
104
+ status: result?.status,
105
+ }
106
+ ))
107
+ }
108
+ }
109
+
110
+ module.exports = { RemoteEvaluator, RemoteEvaluatorError }