braintrust 3.27.0 → 3.28.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 (53) hide show
  1. package/dev/dist/index.d.mts +519 -186
  2. package/dev/dist/index.d.ts +519 -186
  3. package/dev/dist/index.js +1837 -1009
  4. package/dev/dist/index.mjs +1172 -344
  5. package/dist/apply-auto-instrumentation.js +262 -210
  6. package/dist/apply-auto-instrumentation.mjs +54 -2
  7. package/dist/auto-instrumentations/bundler/esbuild.cjs +81 -2
  8. package/dist/auto-instrumentations/bundler/esbuild.mjs +2 -2
  9. package/dist/auto-instrumentations/bundler/next.cjs +81 -2
  10. package/dist/auto-instrumentations/bundler/next.mjs +3 -3
  11. package/dist/auto-instrumentations/bundler/rollup.cjs +81 -2
  12. package/dist/auto-instrumentations/bundler/rollup.mjs +2 -2
  13. package/dist/auto-instrumentations/bundler/vite.cjs +81 -2
  14. package/dist/auto-instrumentations/bundler/vite.mjs +2 -2
  15. package/dist/auto-instrumentations/bundler/webpack-loader.cjs +81 -2
  16. package/dist/auto-instrumentations/bundler/webpack.cjs +81 -2
  17. package/dist/auto-instrumentations/bundler/webpack.mjs +3 -3
  18. package/dist/auto-instrumentations/{chunk-XEYKUBLY.mjs → chunk-26PKVUKB.mjs} +80 -2
  19. package/dist/auto-instrumentations/{chunk-BW33ULMW.mjs → chunk-HD35AM3M.mjs} +1 -1
  20. package/dist/auto-instrumentations/{chunk-ZNHTSSGI.mjs → chunk-NP7V4XB2.mjs} +2 -1
  21. package/dist/auto-instrumentations/hook.mjs +236 -30
  22. package/dist/auto-instrumentations/index.cjs +2 -1
  23. package/dist/auto-instrumentations/index.mjs +1 -1
  24. package/dist/browser.d.mts +628 -53
  25. package/dist/browser.d.ts +628 -53
  26. package/dist/browser.js +2301 -284
  27. package/dist/browser.mjs +2301 -284
  28. package/dist/{chunk-MF7NU6BT.js → chunk-BBE7SNRV.js} +34 -4
  29. package/dist/{chunk-QRHGVBKU.js → chunk-OBBWQW6K.js} +1799 -1023
  30. package/dist/{chunk-YKD22IMR.mjs → chunk-UPFNQCGB.mjs} +966 -190
  31. package/dist/{chunk-CZM5JIQL.mjs → chunk-ZHUHZWFY.mjs} +33 -3
  32. package/dist/cli.js +1224 -398
  33. package/dist/edge-light.d.mts +1 -1
  34. package/dist/edge-light.d.ts +1 -1
  35. package/dist/edge-light.js +2301 -284
  36. package/dist/edge-light.mjs +2301 -284
  37. package/dist/index.d.mts +1212 -637
  38. package/dist/index.d.ts +1212 -637
  39. package/dist/index.js +1880 -590
  40. package/dist/index.mjs +1450 -160
  41. package/dist/instrumentation/index.d.mts +190 -6
  42. package/dist/instrumentation/index.d.ts +190 -6
  43. package/dist/instrumentation/index.js +823 -116
  44. package/dist/instrumentation/index.mjs +823 -116
  45. package/dist/vitest-evals-reporter.js +16 -16
  46. package/dist/vitest-evals-reporter.mjs +2 -2
  47. package/dist/workerd.d.mts +1 -1
  48. package/dist/workerd.d.ts +1 -1
  49. package/dist/workerd.js +2301 -284
  50. package/dist/workerd.mjs +2301 -284
  51. package/package.json +2 -3
  52. package/util/dist/index.d.mts +1545 -100
  53. package/util/dist/index.d.ts +1545 -100
package/dist/index.mjs CHANGED
@@ -55,9 +55,11 @@ import {
55
55
  X_CACHED_HEADER,
56
56
  _exportsForTestingOnly,
57
57
  _internalGetGlobalState,
58
+ _internalResumeSpan,
58
59
  _internalSetInitialState,
59
60
  _internalStartSpanWithInitialMerge,
60
61
  addAzureBlobHeaders,
62
+ base64ToUint8Array,
61
63
  braintrustAISDKTelemetry,
62
64
  braintrustFlueInstrumentation,
63
65
  braintrustFlueObserver,
@@ -105,7 +107,6 @@ import {
105
107
  loginToState,
106
108
  logs3OverflowUploadSchema,
107
109
  makeScorerPropagatedEvent,
108
- mergeDicts,
109
110
  newId,
110
111
  parseCachedHeader,
111
112
  parseTemplateFormat,
@@ -127,8 +128,10 @@ import {
127
128
  summarize,
128
129
  summarizeAndFlush,
129
130
  templateRegistry,
131
+ toNumericMetrics,
130
132
  traceable,
131
133
  traced,
134
+ uint8ArrayToBase64,
132
135
  updateSpan,
133
136
  uploadLogs3OverflowPayload,
134
137
  utf8ByteLength,
@@ -141,7 +144,7 @@ import {
141
144
  wrapMastraAgent,
142
145
  wrapTraced,
143
146
  zodToJsonSchema
144
- } from "./chunk-YKD22IMR.mjs";
147
+ } from "./chunk-UPFNQCGB.mjs";
145
148
  import {
146
149
  INSTRUMENTATION_NAMES,
147
150
  __export,
@@ -172,8 +175,9 @@ import {
172
175
  openRouterChannels,
173
176
  piCodingAgentChannels,
174
177
  strandsAgentSDKChannels,
178
+ voyageAIChannels,
175
179
  withSpanInstrumentationName
176
- } from "./chunk-CZM5JIQL.mjs";
180
+ } from "./chunk-ZHUHZWFY.mjs";
177
181
 
178
182
  // src/exports.ts
179
183
  var exports_exports = {};
@@ -186,6 +190,8 @@ __export(exports_exports, {
186
190
  BRAINTRUST_PARENT_KEY: () => BRAINTRUST_PARENT_KEY,
187
191
  BaseAttachment: () => BaseAttachment,
188
192
  BaseExperiment: () => BaseExperiment,
193
+ BatchScorer: () => BatchScorer,
194
+ BatchTask: () => BatchTask,
189
195
  BraintrustLangChainCallbackHandler: () => BraintrustLangChainCallbackHandler,
190
196
  BraintrustMiddleware: () => BraintrustMiddleware,
191
197
  BraintrustObservabilityExporter: () => BraintrustObservabilityExporter,
@@ -199,6 +205,8 @@ __export(exports_exports, {
199
205
  DEFAULT_MAX_REQUEST_SIZE: () => DEFAULT_MAX_REQUEST_SIZE,
200
206
  Dataset: () => Dataset,
201
207
  DatasetPipeline: () => DatasetPipeline,
208
+ DurableEvalMemoryStore: () => DurableEvalMemoryStore,
209
+ DurableEvalRedisStore: () => DurableEvalRedisStore,
202
210
  ERR_PERMALINK: () => ERR_PERMALINK,
203
211
  Eval: () => Eval,
204
212
  EvalResultWithSummary: () => EvalResultWithSummary,
@@ -256,6 +264,7 @@ __export(exports_exports, {
256
264
  currentSpan: () => currentSpan,
257
265
  deepCopyEvent: () => deepCopyEvent,
258
266
  defaultErrorScoreHandler: () => defaultErrorScoreHandler,
267
+ defineDurableEval: () => defineDurableEval,
259
268
  deserializePlainStringAsJSON: () => deserializePlainStringAsJSON,
260
269
  devNullWritableStream: () => devNullWritableStream,
261
270
  evaluatorDefinitionSchema: () => evaluatorDefinitionSchema,
@@ -351,7 +360,8 @@ __export(exports_exports, {
351
360
  wrapPiCodingAgentSDK: () => wrapPiCodingAgentSDK,
352
361
  wrapStrandsAgentSDK: () => wrapStrandsAgentSDK,
353
362
  wrapTraced: () => wrapTraced,
354
- wrapVitest: () => wrapVitest
363
+ wrapVitest: () => wrapVitest,
364
+ wrapVoyageAI: () => wrapVoyageAI
355
365
  });
356
366
 
357
367
  // src/sandbox.ts
@@ -1612,7 +1622,7 @@ function normalizeUsageMetrics(usage, provider, providerMetadata) {
1612
1622
  cacheCreationTokens
1613
1623
  );
1614
1624
  Object.assign(metrics, cacheTokens);
1615
- Object.assign(metrics, finalizeAnthropicTokens(metrics));
1625
+ return toNumericMetrics(finalizeAnthropicTokens(metrics));
1616
1626
  }
1617
1627
  }
1618
1628
  return metrics;
@@ -5725,6 +5735,84 @@ function wrapRerank3(rerank) {
5725
5735
  });
5726
5736
  }
5727
5737
 
5738
+ // src/wrappers/voyageai.ts
5739
+ function wrapVoyageAI(client) {
5740
+ if (!isSupportedVoyageAIClient(client)) {
5741
+ console.warn("Unsupported Voyage AI library. Not wrapping.");
5742
+ return client;
5743
+ }
5744
+ return voyageAIProxy(client);
5745
+ }
5746
+ var voyageAIProxyCache = /* @__PURE__ */ new WeakMap();
5747
+ function isObject2(value) {
5748
+ return typeof value === "object" && value !== null;
5749
+ }
5750
+ function hasFunction5(value, name) {
5751
+ return typeof Reflect.get(value, name) === "function";
5752
+ }
5753
+ function isSupportedVoyageAIClient(value) {
5754
+ if (!isObject2(value)) {
5755
+ return false;
5756
+ }
5757
+ return hasFunction5(value, "embed") || hasFunction5(value, "multimodalEmbed") || hasFunction5(value, "rerank") || hasFunction5(value, "contextualizedEmbed");
5758
+ }
5759
+ function voyageAIProxy(client) {
5760
+ const cached = voyageAIProxyCache.get(client);
5761
+ if (cached) {
5762
+ return cached;
5763
+ }
5764
+ const proxy = new Proxy(client, {
5765
+ get(target, prop, receiver) {
5766
+ switch (prop) {
5767
+ case "embed":
5768
+ if (typeof target.embed !== "function") {
5769
+ return target.embed;
5770
+ }
5771
+ return (request, options) => voyageAIChannels.embed.invoke(
5772
+ target.embed,
5773
+ target,
5774
+ [request, options],
5775
+ {}
5776
+ );
5777
+ case "multimodalEmbed":
5778
+ if (typeof target.multimodalEmbed !== "function") {
5779
+ return target.multimodalEmbed;
5780
+ }
5781
+ return (request, options) => voyageAIChannels.multimodalEmbed.invoke(
5782
+ target.multimodalEmbed,
5783
+ target,
5784
+ [request, options],
5785
+ {}
5786
+ );
5787
+ case "rerank":
5788
+ if (typeof target.rerank !== "function") {
5789
+ return target.rerank;
5790
+ }
5791
+ return (request, options) => voyageAIChannels.rerank.invoke(
5792
+ target.rerank,
5793
+ target,
5794
+ [request, options],
5795
+ {}
5796
+ );
5797
+ case "contextualizedEmbed":
5798
+ if (typeof target.contextualizedEmbed !== "function") {
5799
+ return target.contextualizedEmbed;
5800
+ }
5801
+ return (request, options) => voyageAIChannels.contextualizedEmbed.invoke(
5802
+ target.contextualizedEmbed,
5803
+ target,
5804
+ [request, options],
5805
+ {}
5806
+ );
5807
+ default:
5808
+ return Reflect.get(target, prop, receiver);
5809
+ }
5810
+ }
5811
+ });
5812
+ voyageAIProxyCache.set(client, proxy);
5813
+ return proxy;
5814
+ }
5815
+
5728
5816
  // src/wrappers/groq.ts
5729
5817
  function wrapGroq(groq) {
5730
5818
  if (isSupportedGroqClient(groq)) {
@@ -5736,14 +5824,14 @@ function wrapGroq(groq) {
5736
5824
  function isRecord4(value) {
5737
5825
  return typeof value === "object" && value !== null;
5738
5826
  }
5739
- function hasFunction5(value, methodName) {
5827
+ function hasFunction6(value, methodName) {
5740
5828
  return isRecord4(value) && methodName in value && typeof value[methodName] === "function";
5741
5829
  }
5742
5830
  function hasChat2(value) {
5743
- return isRecord4(value) && isRecord4(value.completions) && hasFunction5(value.completions, "create");
5831
+ return isRecord4(value) && isRecord4(value.completions) && hasFunction6(value.completions, "create");
5744
5832
  }
5745
5833
  function hasEmbeddings2(value) {
5746
- return hasFunction5(value, "create");
5834
+ return hasFunction6(value, "create");
5747
5835
  }
5748
5836
  function isSupportedGroqClient(value) {
5749
5837
  return isRecord4(value) && (value.chat !== void 0 && hasChat2(value.chat) || value.embeddings !== void 0 && hasEmbeddings2(value.embeddings));
@@ -6285,7 +6373,7 @@ async function flushExperimentWithSync(context, config) {
6285
6373
  // src/wrappers/shared/scorers.ts
6286
6374
  async function runScorers(args) {
6287
6375
  const { scorers, output, expected, input, metadata, span } = args;
6288
- const scorerArgs = {
6376
+ const scorerArgs2 = {
6289
6377
  output,
6290
6378
  expected,
6291
6379
  input,
@@ -6294,7 +6382,7 @@ async function runScorers(args) {
6294
6382
  await Promise.all(
6295
6383
  scorers.map(async (scorer) => {
6296
6384
  try {
6297
- const result = await scorer(scorerArgs);
6385
+ const result = await scorer(scorerArgs2);
6298
6386
  const scores = normalizeScores(result);
6299
6387
  if (scores.length > 0) {
6300
6388
  const accScores = {};
@@ -8653,6 +8741,26 @@ async function getExperimentParametersRef(parameters) {
8653
8741
  version: resolvedParameters.version
8654
8742
  };
8655
8743
  }
8744
+ async function _internalInitEvaluatorExperiment(projectName, evaluator, data, options = {}) {
8745
+ if (options.disabled) return null;
8746
+ const { baseExperiment } = callEvaluatorData(data);
8747
+ const parameters = await getExperimentParametersRef(evaluator.parameters);
8748
+ return initExperiment2(evaluator.state, {
8749
+ ...evaluator.projectId ? { projectId: evaluator.projectId } : { project: projectName },
8750
+ experiment: options.experimentName ?? evaluator.experimentName,
8751
+ description: evaluator.description,
8752
+ metadata: evaluator.metadata,
8753
+ tags: evaluator.tags,
8754
+ isPublic: evaluator.isPublic,
8755
+ update: options.update ?? evaluator.update,
8756
+ baseExperiment: evaluator.baseExperimentName ?? baseExperiment,
8757
+ baseExperimentId: evaluator.baseExperimentId,
8758
+ gitMetadataSettings: evaluator.gitMetadataSettings,
8759
+ repoInfo: evaluator.repoInfo,
8760
+ dataset: Dataset.isDataset(data) ? data : void 0,
8761
+ parameters
8762
+ });
8763
+ }
8656
8764
  function callEvaluatorData(data) {
8657
8765
  const dataResult = typeof data === "function" ? data() : data;
8658
8766
  let baseExperiment = void 0;
@@ -8670,6 +8778,48 @@ function isAsyncIterable2(value) {
8670
8778
  function isIterable(value) {
8671
8779
  return typeof value === "object" && value !== null && Symbol.iterator in value && typeof value[Symbol.iterator] === "function";
8672
8780
  }
8781
+ async function _internalResolveEvaluatorData(evaluator, experiment) {
8782
+ if (typeof evaluator.data === "string") {
8783
+ throw new Error("Unimplemented: string data paths");
8784
+ }
8785
+ let dataResult = typeof evaluator.data === "function" ? evaluator.data() : evaluator.data;
8786
+ if ("_type" in dataResult) {
8787
+ if (dataResult._type !== "BaseExperiment") {
8788
+ throw new Error("Invalid _type");
8789
+ }
8790
+ if (!experiment) {
8791
+ throw new Error(
8792
+ "Cannot use BaseExperiment() without connecting to Braintrust (you most likely set --no-send-logs)"
8793
+ );
8794
+ }
8795
+ let name = dataResult.name;
8796
+ if (isEmpty(name)) {
8797
+ const baseExperiment = await experiment.fetchBaseExperiment();
8798
+ if (!baseExperiment) {
8799
+ throw new Error("BaseExperiment() failed to fetch base experiment");
8800
+ }
8801
+ name = baseExperiment.name;
8802
+ }
8803
+ dataResult = initExperiment2(evaluator.state, {
8804
+ ...evaluator.projectId ? { projectId: evaluator.projectId } : { project: evaluator.projectName },
8805
+ experiment: name,
8806
+ open: true
8807
+ }).asDataset();
8808
+ }
8809
+ const resolvedDataResult = dataResult instanceof Promise ? await dataResult : dataResult;
8810
+ if (isAsyncIterable2(resolvedDataResult)) {
8811
+ return resolvedDataResult;
8812
+ }
8813
+ if (Array.isArray(resolvedDataResult) || isIterable(resolvedDataResult)) {
8814
+ const iterable = resolvedDataResult;
8815
+ return (async function* () {
8816
+ for (const datum of iterable) yield datum;
8817
+ })();
8818
+ }
8819
+ throw new Error(
8820
+ "Evaluator data must be an array, iterable, or async iterable"
8821
+ );
8822
+ }
8673
8823
  globalThis._evals = {
8674
8824
  functions: [],
8675
8825
  prompts: [],
@@ -8716,25 +8866,13 @@ async function Eval(name, evaluator, reporterOrOpts) {
8716
8866
  }
8717
8867
  const resolvedReporter = options.reporter || defaultReporter;
8718
8868
  try {
8719
- const { data, baseExperiment: defaultBaseExperiment } = callEvaluatorData(
8720
- evaluator.data
8869
+ const { data } = callEvaluatorData(evaluator.data);
8870
+ const experiment = await _internalInitEvaluatorExperiment(
8871
+ name,
8872
+ evaluator,
8873
+ data,
8874
+ { disabled: Boolean(options.parent || options.noSendLogs) }
8721
8875
  );
8722
- const parameters = await getExperimentParametersRef(evaluator.parameters);
8723
- const experiment = options.parent || options.noSendLogs ? null : initExperiment2(evaluator.state, {
8724
- ...evaluator.projectId ? { projectId: evaluator.projectId } : { project: name },
8725
- experiment: evaluator.experimentName,
8726
- description: evaluator.description,
8727
- metadata: evaluator.metadata,
8728
- tags: evaluator.tags,
8729
- isPublic: evaluator.isPublic,
8730
- update: evaluator.update,
8731
- baseExperiment: evaluator.baseExperimentName ?? defaultBaseExperiment,
8732
- baseExperimentId: evaluator.baseExperimentId,
8733
- gitMetadataSettings: evaluator.gitMetadataSettings,
8734
- repoInfo: evaluator.repoInfo,
8735
- dataset: Dataset.isDataset(data) ? data : void 0,
8736
- parameters
8737
- });
8738
8876
  if (experiment && typeof process !== "undefined" && globalThis.BRAINTRUST_CONTEXT_MANAGER !== void 0) {
8739
8877
  await experiment._waitForId();
8740
8878
  }
@@ -8832,19 +8970,74 @@ function scorerName(scorer, scorer_idx) {
8832
8970
  function classifierName(classifier, classifier_idx) {
8833
8971
  return classifier.name || `classifier_${classifier_idx}`;
8834
8972
  }
8973
+ async function _internalRunEvaluatorTask(task, datum, trialIndex, parameters, span, reportProgress = () => void 0) {
8974
+ const metadata = {
8975
+ ..."metadata" in datum ? datum.metadata : {}
8976
+ };
8977
+ const hooks = {
8978
+ meta(value) {
8979
+ Object.assign(metadata, value);
8980
+ },
8981
+ metadata,
8982
+ expected: "expected" in datum ? datum.expected : void 0,
8983
+ span,
8984
+ parameters,
8985
+ reportProgress,
8986
+ trialIndex,
8987
+ tags: [...datum.tags ?? []]
8988
+ };
8989
+ const output = await task(datum.input, hooks);
8990
+ span.log({ output });
8991
+ return {
8992
+ output,
8993
+ metadata: hooks.metadata,
8994
+ tags: hooks.tags ?? []
8995
+ };
8996
+ }
8835
8997
  function buildSpanMetadata(results) {
8836
- return results.length === 1 ? results[0].metadata : results.reduce(
8837
- (prev, s) => mergeDicts(prev, { [s.name]: s.metadata }),
8838
- {}
8998
+ return results.length === 1 ? results[0].metadata : Object.fromEntries(
8999
+ results.map((result) => [result.name, result.metadata])
8839
9000
  );
8840
9001
  }
8841
9002
  function buildSpanScores(results) {
8842
- const scoresRecord = results.reduce(
8843
- (prev, s) => mergeDicts(prev, { [s.name]: s.score }),
8844
- {}
9003
+ const scoresRecord = Object.fromEntries(
9004
+ results.map((result) => [result.name, result.score])
8845
9005
  );
8846
9006
  return { resultMetadata: buildSpanMetadata(results), scoresRecord };
8847
9007
  }
9008
+ function _internalPrepareEvaluatorScore(scoreValue, name) {
9009
+ if (scoreValue === null) return { results: null };
9010
+ if (Array.isArray(scoreValue)) {
9011
+ for (const score of scoreValue) {
9012
+ if (!(typeof score === "object" && !isEmpty(score))) {
9013
+ throw new Error(
9014
+ `When returning an array of scores, each score must be a non-empty object. Got: ${JSON.stringify(score)}`
9015
+ );
9016
+ }
9017
+ }
9018
+ }
9019
+ let results;
9020
+ if (Array.isArray(scoreValue)) {
9021
+ results = scoreValue;
9022
+ } else if (typeof scoreValue === "object" && !isEmpty(scoreValue)) {
9023
+ results = [scoreValue];
9024
+ } else {
9025
+ results = [{ name, score: scoreValue }];
9026
+ }
9027
+ const { resultMetadata, scoresRecord } = buildSpanScores(results);
9028
+ const fields = (score) => {
9029
+ const { metadata: _metadata, name: _name, ...rest } = score;
9030
+ return rest;
9031
+ };
9032
+ return {
9033
+ results,
9034
+ output: results.length === 1 ? fields(results[0]) : Object.fromEntries(
9035
+ results.map((score) => [score.name ?? name, fields(score)])
9036
+ ),
9037
+ metadata: resultMetadata,
9038
+ scores: scoresRecord
9039
+ };
9040
+ }
8848
9041
  async function runInScorerSpan(rootSpan, spanName, spanType, propagatedEvent, eventInput, fn) {
8849
9042
  try {
8850
9043
  const value = await rootSpan.traced(fn, {
@@ -8889,6 +9082,27 @@ function toClassificationItem(c) {
8889
9082
  ...c.metadata !== void 0 ? { metadata: c.metadata } : {}
8890
9083
  };
8891
9084
  }
9085
+ function _internalPrepareEvaluatorClassification(value, name) {
9086
+ if (value === null) return { results: null };
9087
+ const results = (Array.isArray(value) ? value : [value]).map(
9088
+ (result) => validateClassificationResult(result, name)
9089
+ );
9090
+ const classifications = /* @__PURE__ */ Object.create(null);
9091
+ for (const result of results) {
9092
+ (classifications[result.name] ??= []).push(toClassificationItem(result));
9093
+ }
9094
+ return {
9095
+ results,
9096
+ output: results.length === 1 ? toClassificationItem(results[0]) : Object.fromEntries(
9097
+ results.map((result) => [
9098
+ result.name,
9099
+ toClassificationItem(result)
9100
+ ])
9101
+ ),
9102
+ metadata: buildSpanMetadata(results),
9103
+ classifications
9104
+ };
9105
+ }
8892
9106
  function logScoringFailures(kind, failures, metadata, rootSpan, state) {
8893
9107
  if (!failures.length) return [];
8894
9108
  const errorMap = Object.fromEntries(
@@ -8936,54 +9150,14 @@ async function runEvaluatorInternal(experiment, evaluator, progressReporter, fil
8936
9150
  (evaluator.state ?? _internalGetGlobalState())?.spanCache?.start();
8937
9151
  }
8938
9152
  try {
8939
- if (typeof evaluator.data === "string") {
8940
- throw new Error("Unimplemented: string data paths");
8941
- }
8942
- let dataResult = typeof evaluator.data === "function" ? evaluator.data() : evaluator.data;
8943
9153
  parameters = await validateParameters(
8944
9154
  parameters ?? {},
8945
9155
  evaluator.parameters
8946
9156
  );
8947
- if ("_type" in dataResult) {
8948
- if (dataResult._type !== "BaseExperiment") {
8949
- throw new Error("Invalid _type");
8950
- }
8951
- if (!experiment) {
8952
- throw new Error(
8953
- "Cannot use BaseExperiment() without connecting to Braintrust (you most likely set --no-send-logs)"
8954
- );
8955
- }
8956
- let name = dataResult.name;
8957
- if (isEmpty(name)) {
8958
- const baseExperiment = await experiment.fetchBaseExperiment();
8959
- if (!baseExperiment) {
8960
- throw new Error("BaseExperiment() failed to fetch base experiment");
8961
- }
8962
- name = baseExperiment.name;
8963
- }
8964
- dataResult = initExperiment2(evaluator.state, {
8965
- ...evaluator.projectId ? { projectId: evaluator.projectId } : { project: evaluator.projectName },
8966
- experiment: name,
8967
- open: true
8968
- }).asDataset();
8969
- }
8970
- const resolvedDataResult = dataResult instanceof Promise ? await dataResult : dataResult;
8971
- const dataIterable = (() => {
8972
- if (isAsyncIterable2(resolvedDataResult)) {
8973
- return resolvedDataResult;
8974
- }
8975
- if (Array.isArray(resolvedDataResult) || isIterable(resolvedDataResult)) {
8976
- const iterable = resolvedDataResult;
8977
- return (async function* () {
8978
- for (const datum of iterable) {
8979
- yield datum;
8980
- }
8981
- })();
8982
- }
8983
- throw new Error(
8984
- "Evaluator data must be an array, iterable, or async iterable"
8985
- );
8986
- })();
9157
+ const dataIterable = await _internalResolveEvaluatorData(
9158
+ evaluator,
9159
+ experiment
9160
+ );
8987
9161
  progressReporter.start(evaluator.evalName, 0);
8988
9162
  const experimentIdPromise = experiment ? (async () => {
8989
9163
  try {
@@ -9052,57 +9226,45 @@ async function runEvaluatorInternal(experiment, evaluator, progressReporter, fil
9052
9226
  ensureSpansFlushed,
9053
9227
  state
9054
9228
  }) : void 0;
9055
- let metadata = {
9056
- ..."metadata" in datum ? datum.metadata : {}
9057
- };
9229
+ let metadata = {};
9058
9230
  const expected = "expected" in datum ? datum.expected : void 0;
9059
9231
  let output = void 0;
9060
9232
  let error = void 0;
9061
- let tags = [...datum.tags ?? []];
9062
- const scores = {};
9063
- const classifications = {};
9233
+ let tags = [];
9234
+ const scores = /* @__PURE__ */ Object.create(null);
9235
+ const classifications = /* @__PURE__ */ Object.create(null);
9064
9236
  const scorerNames = (evaluator.scores ?? []).map(scorerName);
9065
9237
  const classifierNames = (evaluator.classifiers ?? []).map(
9066
9238
  classifierName
9067
9239
  );
9068
9240
  let unhandledScores = scorerNames;
9069
9241
  try {
9070
- const meta = (o) => metadata = { ...metadata, ...o };
9071
- await rootSpan.traced(
9072
- async (span) => {
9073
- const hooksForTask = {
9074
- meta,
9075
- metadata,
9076
- expected,
9077
- span,
9078
- parameters: parameters ?? {},
9079
- reportProgress: (event) => {
9080
- stream?.({
9081
- ...event,
9082
- id: rootSpan.id,
9083
- origin: baseEvent.event?.origin,
9084
- name: evaluator.evalName,
9085
- object_type: "task"
9086
- });
9087
- },
9088
- trialIndex,
9089
- tags
9090
- };
9091
- const outputResult = evaluator.task(datum.input, hooksForTask);
9092
- if (outputResult instanceof Promise) {
9093
- output = await outputResult;
9094
- } else {
9095
- output = outputResult;
9242
+ const taskResult = await rootSpan.traced(
9243
+ (span) => _internalRunEvaluatorTask(
9244
+ evaluator.task,
9245
+ datum,
9246
+ trialIndex,
9247
+ parameters ?? {},
9248
+ span,
9249
+ (event) => {
9250
+ stream?.({
9251
+ ...event,
9252
+ id: rootSpan.id,
9253
+ origin: baseEvent.event?.origin,
9254
+ name: evaluator.evalName,
9255
+ object_type: "task"
9256
+ });
9096
9257
  }
9097
- tags = hooksForTask.tags ?? [];
9098
- span.log({ output });
9099
- },
9258
+ ),
9100
9259
  {
9101
9260
  name: "task",
9102
9261
  spanAttributes: { type: "task" /* TASK */ },
9103
9262
  event: { input: datum.input }
9104
9263
  }
9105
9264
  );
9265
+ output = taskResult.output;
9266
+ metadata = taskResult.metadata;
9267
+ tags = taskResult.tags;
9106
9268
  if (tags.length) {
9107
9269
  rootSpan.log({ output, metadata, expected, tags });
9108
9270
  } else {
@@ -9112,20 +9274,18 @@ async function runEvaluatorInternal(experiment, evaluator, progressReporter, fil
9112
9274
  await rootSpan.flush();
9113
9275
  }
9114
9276
  const scoringArgs = {
9277
+ id: datum.id,
9115
9278
  input: datum.input,
9116
9279
  expected: "expected" in datum ? datum.expected : void 0,
9117
9280
  metadata,
9118
9281
  output,
9282
+ tags,
9119
9283
  trace
9120
9284
  };
9121
9285
  const { trace: _trace, ...scoringArgsForLogging } = scoringArgs;
9122
9286
  const propagatedEvent = makeScorerPropagatedEvent(
9123
9287
  await rootSpan.export()
9124
9288
  );
9125
- const getOtherFields = (s) => {
9126
- const { metadata: _metadata, name: _name, ...rest } = s;
9127
- return rest;
9128
- };
9129
9289
  const [scoreResults, classificationResults] = await Promise.all([
9130
9290
  Promise.all(
9131
9291
  (evaluator.scores ?? []).map(
@@ -9139,35 +9299,17 @@ async function runEvaluatorInternal(experiment, evaluator, progressReporter, fil
9139
9299
  const scoreValue = await Promise.resolve(
9140
9300
  score(scoringArgs)
9141
9301
  );
9142
- if (scoreValue === null) return null;
9143
- if (Array.isArray(scoreValue)) {
9144
- for (const s of scoreValue) {
9145
- if (!(typeof s === "object" && !isEmpty(s))) {
9146
- throw new Error(
9147
- `When returning an array of scores, each score must be a non-empty object. Got: ${JSON.stringify(s)}`
9148
- );
9149
- }
9150
- }
9151
- }
9152
- const results = Array.isArray(scoreValue) ? scoreValue : typeof scoreValue === "object" && !isEmpty(scoreValue) ? [scoreValue] : [
9153
- {
9154
- name: scorerNames[score_idx],
9155
- score: scoreValue
9156
- }
9157
- ];
9158
- const { resultMetadata, scoresRecord } = buildSpanScores(results);
9159
- const resultOutput = results.length === 1 ? getOtherFields(results[0]) : results.reduce(
9160
- (prev, s) => mergeDicts(prev, {
9161
- [s.name]: getOtherFields(s)
9162
- }),
9163
- {}
9302
+ const prepared = _internalPrepareEvaluatorScore(
9303
+ scoreValue,
9304
+ scorerNames[score_idx]
9164
9305
  );
9306
+ if (prepared.results === null) return null;
9165
9307
  span.log({
9166
- output: resultOutput,
9167
- metadata: resultMetadata,
9168
- scores: scoresRecord
9308
+ output: prepared.output,
9309
+ metadata: prepared.metadata,
9310
+ scores: prepared.scores
9169
9311
  });
9170
- return results;
9312
+ return prepared.results;
9171
9313
  }
9172
9314
  )
9173
9315
  )
@@ -9184,24 +9326,16 @@ async function runEvaluatorInternal(experiment, evaluator, progressReporter, fil
9184
9326
  const classifierValue = await Promise.resolve(
9185
9327
  classifier(scoringArgs)
9186
9328
  );
9187
- if (classifierValue === null) return null;
9188
- const rawResults = (Array.isArray(classifierValue) ? classifierValue : [classifierValue]).map(
9189
- (result) => validateClassificationResult(
9190
- result,
9191
- classifierNames[idx]
9192
- )
9193
- );
9194
- const resultOutput = rawResults.length === 1 ? toClassificationItem(rawResults[0]) : rawResults.reduce(
9195
- (prev, r) => mergeDicts(prev, {
9196
- [r.name]: toClassificationItem(r)
9197
- }),
9198
- {}
9329
+ const prepared = _internalPrepareEvaluatorClassification(
9330
+ classifierValue,
9331
+ classifierNames[idx]
9199
9332
  );
9333
+ if (prepared.results === null) return null;
9200
9334
  span.log({
9201
- output: resultOutput,
9202
- metadata: buildSpanMetadata(rawResults)
9335
+ output: prepared.output,
9336
+ metadata: prepared.metadata
9203
9337
  });
9204
- return rawResults;
9338
+ return prepared.results;
9205
9339
  }
9206
9340
  )
9207
9341
  )
@@ -9430,7 +9564,7 @@ function accumulateScores(accumulator, scores) {
9430
9564
  }
9431
9565
  }
9432
9566
  function ensureScoreAccumulator(results) {
9433
- const accumulator = {};
9567
+ const accumulator = /* @__PURE__ */ Object.create(null);
9434
9568
  for (const result of results) {
9435
9569
  accumulateScores(accumulator, result.scores);
9436
9570
  }
@@ -9573,6 +9707,1156 @@ var defaultReporter = {
9573
9707
  }
9574
9708
  };
9575
9709
 
9710
+ // src/durable-eval.ts
9711
+ var encoder = new TextEncoder();
9712
+ var decoder = new TextDecoder();
9713
+ var BATCH_TASK_KIND = "braintrust.durable.batch-task";
9714
+ var BATCH_SCORER_KIND = "braintrust.durable.batch-scorer";
9715
+ var DEFAULT_BATCH_SIZE = 1e3;
9716
+ var DurableEvalMemoryStore = class {
9717
+ values = /* @__PURE__ */ new Map();
9718
+ async read(key) {
9719
+ return this.values.get(key)?.slice();
9720
+ }
9721
+ async write(key, value) {
9722
+ this.values.set(key, value.slice());
9723
+ }
9724
+ async getOrSet(key, value) {
9725
+ const existing = this.values.get(key);
9726
+ if (existing) return { value: existing.slice(), created: false };
9727
+ this.values.set(key, value.slice());
9728
+ return { value: value.slice(), created: true };
9729
+ }
9730
+ };
9731
+ var DurableEvalRedisStore = class {
9732
+ client;
9733
+ keyPrefix;
9734
+ ttlMs;
9735
+ constructor(options) {
9736
+ this.client = options.client;
9737
+ this.keyPrefix = options.keyPrefix ?? "braintrust-eval:";
9738
+ this.ttlMs = options.ttlMs ?? 1e3 * 60 * 60 * 24 * 7;
9739
+ if (!Number.isInteger(this.ttlMs) || this.ttlMs < 1) {
9740
+ throw new Error("DurableEvalRedisStore ttlMs must be a positive integer");
9741
+ }
9742
+ }
9743
+ async read(key) {
9744
+ const value = await this.client.get(`${this.keyPrefix}${key}`);
9745
+ if (value == null) return void 0;
9746
+ if (typeof value !== "string") {
9747
+ throw new Error("DurableEvalRedisStore expected GET to return a string");
9748
+ }
9749
+ return base64ToUint8Array(value);
9750
+ }
9751
+ async write(key, value) {
9752
+ const client = this.client;
9753
+ const set = client.set;
9754
+ const redisKey = `${this.keyPrefix}${key}`;
9755
+ const encoded = uint8ArrayToBase64(value);
9756
+ if (typeof client.defineCommand === "function") {
9757
+ await set.call(client, redisKey, encoded, "PX", this.ttlMs);
9758
+ } else if (typeof client.sendCommand === "function") {
9759
+ await set.call(client, redisKey, encoded, { PX: this.ttlMs });
9760
+ } else if (typeof client.createScript === "function") {
9761
+ await set.call(client, redisKey, encoded, { px: this.ttlMs });
9762
+ } else {
9763
+ throw new Error(
9764
+ "DurableEvalRedisStore requires a node-redis, ioredis, or @upstash/redis client"
9765
+ );
9766
+ }
9767
+ }
9768
+ async getOrSet(key, value) {
9769
+ const redisKey = `${this.keyPrefix}${key}`;
9770
+ const encoded = uint8ArrayToBase64(value);
9771
+ const client = this.client;
9772
+ const set = client.set;
9773
+ let setOptions;
9774
+ if (typeof client.defineCommand === "function") {
9775
+ setOptions = ["PX", this.ttlMs, "NX", "GET"];
9776
+ } else if (typeof client.sendCommand === "function") {
9777
+ setOptions = [{ PX: this.ttlMs, NX: true, GET: true }];
9778
+ } else if (typeof client.createScript === "function") {
9779
+ setOptions = [{ px: this.ttlMs, nx: true, get: true }];
9780
+ } else {
9781
+ throw new Error(
9782
+ "DurableEvalRedisStore getOrSet requires a node-redis, ioredis, or @upstash/redis client"
9783
+ );
9784
+ }
9785
+ const existing = await set.call(client, redisKey, encoded, ...setOptions);
9786
+ if (existing === null) return { value: value.slice(), created: true };
9787
+ if (typeof existing !== "string") {
9788
+ throw new Error(
9789
+ "DurableEvalRedisStore expected atomic SET to return a string or null"
9790
+ );
9791
+ }
9792
+ return { value: base64ToUint8Array(existing), created: false };
9793
+ }
9794
+ };
9795
+ var BatchTask = class {
9796
+ constructor(processor) {
9797
+ this.processor = processor;
9798
+ }
9799
+ processor;
9800
+ kind = BATCH_TASK_KIND;
9801
+ };
9802
+ var BatchScorer = class {
9803
+ constructor(processor) {
9804
+ this.processor = processor;
9805
+ this.name = processor.name;
9806
+ }
9807
+ processor;
9808
+ kind = BATCH_SCORER_KIND;
9809
+ name;
9810
+ };
9811
+ function defineDurableEval(projectName, evaluator) {
9812
+ const definition = {
9813
+ projectName,
9814
+ evalName: evaluator.experimentName ?? projectName,
9815
+ evaluator
9816
+ };
9817
+ return {
9818
+ start: (options = {}) => startDurableEval(definition, options),
9819
+ status: (options) => getDurableEvalStatus(definition, options),
9820
+ poll: (options) => pollDurableEval(definition, options),
9821
+ processBatchResult: (result) => processDurableBatchResult(definition, result)
9822
+ };
9823
+ }
9824
+ async function startDurableEval(definition, options) {
9825
+ const store = definition.evaluator.store;
9826
+ const runId = newId();
9827
+ const key = runKey(definition.projectName, definition.evalName, runId);
9828
+ const { data } = callEvaluatorData(definition.evaluator.data);
9829
+ const parameters = await validateParameters(
9830
+ options.parameters ?? {},
9831
+ definition.evaluator.parameters
9832
+ );
9833
+ const experimentName = definition.evaluator.experimentName ?? `${definition.evalName}-${runId}`;
9834
+ const experiment = await _internalInitEvaluatorExperiment(
9835
+ definition.projectName,
9836
+ { ...definition.evaluator, data },
9837
+ data,
9838
+ {
9839
+ disabled: options.noSendLogs ?? false,
9840
+ experimentName,
9841
+ update: true
9842
+ }
9843
+ );
9844
+ if (!isBatchTask(definition.evaluator.task) && !(definition.evaluator.scores ?? []).some(isBatchScorer)) {
9845
+ const result = await runEvaluator(
9846
+ experiment,
9847
+ {
9848
+ ...definition.evaluator,
9849
+ projectName: definition.projectName,
9850
+ evalName: definition.evalName,
9851
+ data
9852
+ },
9853
+ {
9854
+ start: () => void 0,
9855
+ stop: () => void 0,
9856
+ increment: () => void 0
9857
+ },
9858
+ [],
9859
+ void 0,
9860
+ parameters,
9861
+ true,
9862
+ true
9863
+ );
9864
+ const state2 = {
9865
+ runId,
9866
+ experimentName,
9867
+ noSendLogs: options.noSendLogs ?? false,
9868
+ parameters: assertJsonValue(parameters, "eval parameters"),
9869
+ status: "completed",
9870
+ summary: result.summary,
9871
+ cases: [],
9872
+ batches: []
9873
+ };
9874
+ await experiment?.flush();
9875
+ await writeRunRecord(store, key, state2);
9876
+ return currentStatus(definition, state2);
9877
+ }
9878
+ const state = {
9879
+ runId,
9880
+ experimentName,
9881
+ noSendLogs: options.noSendLogs ?? false,
9882
+ parameters: assertJsonValue(parameters, "eval parameters"),
9883
+ status: "running",
9884
+ cases: await materializeCases(definition, data, experiment),
9885
+ batches: []
9886
+ };
9887
+ await writeCaseBaseRecords(store, key, state.cases);
9888
+ await writeRunRecord(store, key, state);
9889
+ return advanceDurableEval(definition, state, store, key, experiment);
9890
+ }
9891
+ async function getDurableEvalStatus(definition, options) {
9892
+ const store = definition.evaluator.store;
9893
+ const state = await readRunState(
9894
+ definition,
9895
+ store,
9896
+ runKey(definition.projectName, definition.evalName, options.runId)
9897
+ );
9898
+ if (!state) throw new Error(`Durable eval run ${options.runId} is missing`);
9899
+ return currentStatus(definition, state);
9900
+ }
9901
+ async function processDurableBatchResult(definition, result) {
9902
+ if (!result.batchId && !result.externalId) {
9903
+ throw new Error("Batch results require batchId or externalId");
9904
+ }
9905
+ const store = definition.evaluator.store;
9906
+ const key = runKey(definition.projectName, definition.evalName, result.runId);
9907
+ const state = await readRunState(definition, store, key);
9908
+ if (!state) throw new Error(`Durable eval run ${result.runId} is missing`);
9909
+ const byBatch = result.batchId ? state.batches.find((candidate) => candidate.id === result.batchId) : void 0;
9910
+ const byExternal = result.externalId ? state.batches.find(
9911
+ (candidate) => candidate.externalId === result.externalId
9912
+ ) : void 0;
9913
+ if (byBatch && byExternal && byBatch.id !== byExternal.id) {
9914
+ throw new Error("batchId and externalId identify different batches");
9915
+ }
9916
+ const batch = byBatch ?? byExternal;
9917
+ if (!batch) throw new Error("No submitted batch matches this result");
9918
+ if (batch.status !== "complete") {
9919
+ const records = await collectBatch(definition, state, batch);
9920
+ batch.status = "complete";
9921
+ await writeCaseRecords(store, key, records);
9922
+ await writeBatchRecords(store, key, [batch]);
9923
+ }
9924
+ return advanceDurableEval(
9925
+ definition,
9926
+ await readRunState(definition, store, key),
9927
+ store,
9928
+ key
9929
+ );
9930
+ }
9931
+ async function pollDurableEval(definition, options) {
9932
+ const store = definition.evaluator.store;
9933
+ const key = runKey(
9934
+ definition.projectName,
9935
+ definition.evalName,
9936
+ options.runId
9937
+ );
9938
+ const state = await readRunState(definition, store, key);
9939
+ if (!state) throw new Error(`Durable eval run ${options.runId} is missing`);
9940
+ const batches = state.batches.filter((batch) => {
9941
+ if (batch.status === "complete") return false;
9942
+ return processorForStage(definition, batch.kind, batch.scorerName).completion.mode === "poll";
9943
+ });
9944
+ const results = await Promise.all(
9945
+ batches.map(async (batch) => ({
9946
+ batch,
9947
+ result: await processorForStage(definition, batch.kind, batch.scorerName).completion.poll(batch.submissionData, {
9948
+ runId: state.runId,
9949
+ batchId: batch.id
9950
+ })
9951
+ }))
9952
+ );
9953
+ const changedCases = /* @__PURE__ */ new Map();
9954
+ const changedBatches = [];
9955
+ for (const { batch, result } of results) {
9956
+ if (result.status === "failed") throw asError(result.error);
9957
+ if (result.status !== "complete") continue;
9958
+ for (const record of await collectBatch(definition, state, batch)) {
9959
+ changedCases.set(record.id, record);
9960
+ }
9961
+ batch.status = "complete";
9962
+ changedBatches.push(batch);
9963
+ }
9964
+ if (changedBatches.length > 0) {
9965
+ await writeCaseRecords(store, key, [...changedCases.values()]);
9966
+ await writeBatchRecords(store, key, changedBatches);
9967
+ }
9968
+ const currentState = changedBatches.length > 0 ? await readRunState(definition, store, key) : state;
9969
+ return advanceDurableEval(definition, currentState, store, key);
9970
+ }
9971
+ async function openDurableExperiment(definition, state) {
9972
+ const data = [];
9973
+ return await _internalInitEvaluatorExperiment(
9974
+ definition.projectName,
9975
+ { ...definition.evaluator, data },
9976
+ data,
9977
+ {
9978
+ disabled: state.noSendLogs,
9979
+ experimentName: state.experimentName,
9980
+ update: true
9981
+ }
9982
+ );
9983
+ }
9984
+ async function advanceDurableEval(definition, state, store, key, existingExperiment) {
9985
+ if (state.status === "completed") return currentStatus(definition, state);
9986
+ const experiment = existingExperiment === void 0 ? await openDurableExperiment(definition, state) : existingExperiment;
9987
+ await runTaskStage(definition, state, store, key, experiment);
9988
+ await logCompletedTasks(definition, state, store, key, experiment);
9989
+ state = await readRunState(definition, store, key) ?? state;
9990
+ if (state.cases.some((record) => !record.taskComplete || !record.taskLogged)) {
9991
+ return currentStatus(definition, state);
9992
+ }
9993
+ await runScoreStages(definition, state, store, key, experiment);
9994
+ state = await readRunState(definition, store, key) ?? state;
9995
+ const scorerNames = resolveScorers(definition.evaluator.scores ?? []).map(
9996
+ ({ name }) => name
9997
+ );
9998
+ const classifierNames = (definition.evaluator.classifiers ?? []).map(
9999
+ classifierName
10000
+ );
10001
+ if (state.cases.some(
10002
+ (record) => scorerNames.some(
10003
+ (name) => !Object.hasOwn(record.scores, name) || !Object.hasOwn(record.loggedScores, name)
10004
+ ) || classifierNames.some(
10005
+ (name) => !Object.hasOwn(record.loggedClassifications, name)
10006
+ )
10007
+ )) {
10008
+ return currentStatus(definition, state);
10009
+ }
10010
+ if (!await claimAction(store, key, "finish")) {
10011
+ const latest = await readRunState(definition, store, key);
10012
+ return currentStatus(definition, latest ?? state);
10013
+ }
10014
+ state.summary = await finishExperiment(definition, state, experiment);
10015
+ state.status = "completed";
10016
+ await writeRunRecord(store, key, state);
10017
+ return currentStatus(definition, state);
10018
+ }
10019
+ function currentStatus(definition, state) {
10020
+ if (state.status === "completed") {
10021
+ if (!state.summary) {
10022
+ throw new Error(`Durable eval run ${state.runId} has no saved summary`);
10023
+ }
10024
+ return {
10025
+ status: "completed",
10026
+ runId: state.runId,
10027
+ pending: { poll: 0, webhook: 0 },
10028
+ summary: state.summary
10029
+ };
10030
+ }
10031
+ const pending = { poll: 0, webhook: 0 };
10032
+ for (const batch of state.batches) {
10033
+ if (batch.status === "complete") continue;
10034
+ pending[processorForStage(definition, batch.kind, batch.scorerName).completion.mode]++;
10035
+ }
10036
+ return { status: "waiting", runId: state.runId, pending };
10037
+ }
10038
+ async function startCaseRoot(definition, state, record, experiment) {
10039
+ if (!experiment) return NOOP_SPAN;
10040
+ const datum = record.datum;
10041
+ return _internalStartSpanWithInitialMerge({
10042
+ ...definition.evaluator.state ? { state: definition.evaluator.state } : {},
10043
+ parent: await experiment.export(),
10044
+ name: "eval",
10045
+ spanId: deterministicId(`${state.runId}:${record.id}:span`),
10046
+ spanAttributes: { type: "eval" /* EVAL */ },
10047
+ event: {
10048
+ id: deterministicId(`${state.runId}:${record.id}:row`),
10049
+ input: datum.input,
10050
+ expected: "expected" in datum ? datum.expected : void 0,
10051
+ tags: datum.tags,
10052
+ origin: datum.origin
10053
+ }
10054
+ });
10055
+ }
10056
+ async function logTaskResult(definition, state, record, experiment, task) {
10057
+ const datum = record.datum;
10058
+ const root = await startCaseRoot(definition, state, record, experiment);
10059
+ try {
10060
+ if (task) {
10061
+ const result = await root.traced(
10062
+ (span) => _internalRunEvaluatorTask(
10063
+ task,
10064
+ datum,
10065
+ record.trialIndex,
10066
+ state.parameters,
10067
+ span
10068
+ ),
10069
+ {
10070
+ name: "task",
10071
+ spanId: deterministicId(`${state.runId}:${record.id}:task`),
10072
+ spanAttributes: { type: "task" /* TASK */ },
10073
+ event: { input: datum.input }
10074
+ }
10075
+ );
10076
+ record.output = assertJsonValue(
10077
+ result.output,
10078
+ `task output for ${record.caseId}`
10079
+ );
10080
+ record.metadata = assertJsonValue(result.metadata, "task metadata");
10081
+ record.tags = result.tags;
10082
+ record.taskComplete = true;
10083
+ } else {
10084
+ await root.traced((span) => span.log({ output: record.output }), {
10085
+ name: "task",
10086
+ spanId: deterministicId(`${state.runId}:${record.id}:task`),
10087
+ spanAttributes: { type: "task" /* TASK */ },
10088
+ event: { input: datum.input }
10089
+ });
10090
+ }
10091
+ root.log({
10092
+ output: record.output,
10093
+ expected: "expected" in datum ? datum.expected : void 0,
10094
+ metadata: {
10095
+ ...record.metadata,
10096
+ durable_eval: {
10097
+ run_id: state.runId,
10098
+ case_id: record.caseId,
10099
+ trial_index: record.trialIndex
10100
+ }
10101
+ },
10102
+ tags: record.tags
10103
+ });
10104
+ record.rootSpan = await root.export();
10105
+ record.taskLogged = true;
10106
+ } catch (error) {
10107
+ logError(root, error);
10108
+ throw error;
10109
+ } finally {
10110
+ root.end();
10111
+ }
10112
+ }
10113
+ async function logCompletedTasks(definition, state, store, key, experiment) {
10114
+ const changed = [];
10115
+ for (const record of state.cases) {
10116
+ if (!record.taskComplete || record.taskLogged) continue;
10117
+ if (!await claimAction(store, key, "task-log", record.id)) continue;
10118
+ await logTaskResult(definition, state, record, experiment);
10119
+ changed.push(record);
10120
+ }
10121
+ if (changed.length > 0) {
10122
+ await experiment?.flush();
10123
+ await writeCaseRecords(store, key, changed);
10124
+ }
10125
+ }
10126
+ async function runTaskStage(definition, state, store, key, experiment) {
10127
+ if (isBatchTask(definition.evaluator.task)) {
10128
+ await ensureBatches(definition, state, store, key, "task");
10129
+ return;
10130
+ }
10131
+ const task = definition.evaluator.task;
10132
+ const changed = [];
10133
+ for (const record of state.cases) {
10134
+ if (record.taskComplete) continue;
10135
+ if (!await claimAction(store, key, "task", record.id)) continue;
10136
+ await logTaskResult(definition, state, record, experiment, task);
10137
+ changed.push(record);
10138
+ }
10139
+ if (changed.length > 0) {
10140
+ await experiment?.flush();
10141
+ await writeCaseRecords(store, key, changed);
10142
+ }
10143
+ }
10144
+ async function runScoreStages(definition, state, store, key, experiment) {
10145
+ const scorers = resolveScorers(definition.evaluator.scores ?? []);
10146
+ const changed = /* @__PURE__ */ new Map();
10147
+ const persistChangedCases = async () => {
10148
+ if (changed.size === 0) return;
10149
+ await experiment?.flush();
10150
+ await writeCaseRecords(store, key, [...changed.values()]);
10151
+ changed.clear();
10152
+ };
10153
+ for (const { name, scorer } of scorers) {
10154
+ if (isBatchScorer(scorer)) {
10155
+ for (const record of state.cases) {
10156
+ if (Object.hasOwn(record.scores, name) && !Object.hasOwn(record.loggedScores, name)) {
10157
+ if (!await claimAction(store, key, "score-log", record.id, name)) {
10158
+ continue;
10159
+ }
10160
+ await evaluateAndLogScore(
10161
+ definition,
10162
+ state,
10163
+ record,
10164
+ name,
10165
+ experiment
10166
+ );
10167
+ changed.set(record.id, record);
10168
+ }
10169
+ }
10170
+ await persistChangedCases();
10171
+ await ensureBatches(definition, state, store, key, "score", name);
10172
+ continue;
10173
+ }
10174
+ for (const record of state.cases) {
10175
+ if (Object.hasOwn(record.loggedScores, name)) continue;
10176
+ if (!await claimAction(store, key, "score", record.id, name)) continue;
10177
+ await evaluateAndLogScore(
10178
+ definition,
10179
+ state,
10180
+ record,
10181
+ name,
10182
+ experiment,
10183
+ scorer
10184
+ );
10185
+ changed.set(record.id, record);
10186
+ }
10187
+ }
10188
+ for (const [index, classifier] of (definition.evaluator.classifiers ?? []).entries()) {
10189
+ const name = classifierName(classifier, index);
10190
+ for (const record of state.cases) {
10191
+ if (Object.hasOwn(record.loggedClassifications, name)) continue;
10192
+ if (!await claimAction(store, key, "classification", record.id, name)) {
10193
+ continue;
10194
+ }
10195
+ await evaluateAndLogClassification(
10196
+ definition,
10197
+ state,
10198
+ record,
10199
+ name,
10200
+ classifier,
10201
+ experiment
10202
+ );
10203
+ changed.set(record.id, record);
10204
+ }
10205
+ }
10206
+ await persistChangedCases();
10207
+ }
10208
+ function scorerArgs(record) {
10209
+ const datum = record.datum;
10210
+ return {
10211
+ ...datum,
10212
+ metadata: record.metadata,
10213
+ output: record.output
10214
+ };
10215
+ }
10216
+ function resumeCaseRoot(definition, record, experiment) {
10217
+ if (!experiment) return NOOP_SPAN;
10218
+ if (!record.rootSpan) {
10219
+ throw new Error(`Durable eval case ${record.caseId} has no root span`);
10220
+ }
10221
+ return _internalResumeSpan({
10222
+ exported: record.rootSpan,
10223
+ state: definition.evaluator.state
10224
+ });
10225
+ }
10226
+ async function evaluateAndLogScore(definition, state, record, name, experiment, scorer) {
10227
+ const root = resumeCaseRoot(definition, record, experiment);
10228
+ try {
10229
+ const rootExport = await root.export();
10230
+ const prepared = await root.traced(
10231
+ async (span) => {
10232
+ const value = scorer ? await scorer(scorerArgs(record)) : record.scores[name];
10233
+ if (scorer) {
10234
+ record.scores[name] = assertJsonValue(value, `scorer ${name} output`);
10235
+ }
10236
+ const result = _internalPrepareEvaluatorScore(value, name);
10237
+ if (result.results !== null) {
10238
+ span.log({
10239
+ output: result.output,
10240
+ metadata: result.metadata,
10241
+ scores: result.scores
10242
+ });
10243
+ }
10244
+ return result;
10245
+ },
10246
+ {
10247
+ name,
10248
+ spanId: deterministicId(`${state.runId}:${record.id}:score:${name}`),
10249
+ spanAttributes: {
10250
+ type: "score" /* SCORE */,
10251
+ purpose: "scorer"
10252
+ },
10253
+ propagatedEvent: makeScorerPropagatedEvent(rootExport || void 0),
10254
+ event: { input: scorerArgs(record) }
10255
+ }
10256
+ );
10257
+ if (prepared.scores) root.log({ scores: prepared.scores });
10258
+ record.loggedScores[name] = true;
10259
+ } catch (error) {
10260
+ logError(root, error);
10261
+ throw error;
10262
+ } finally {
10263
+ root.end();
10264
+ }
10265
+ }
10266
+ async function evaluateAndLogClassification(definition, state, record, name, classifier, experiment) {
10267
+ const root = resumeCaseRoot(definition, record, experiment);
10268
+ try {
10269
+ const rootExport = await root.export();
10270
+ const prepared = await root.traced(
10271
+ async (span) => {
10272
+ const value = await classifier(scorerArgs(record));
10273
+ record.classifications[name] = assertJsonValue(
10274
+ value,
10275
+ `classifier ${name} output`
10276
+ );
10277
+ const result = _internalPrepareEvaluatorClassification(value, name);
10278
+ if (result.results !== null) {
10279
+ span.log({ output: result.output, metadata: result.metadata });
10280
+ }
10281
+ return result;
10282
+ },
10283
+ {
10284
+ name,
10285
+ spanId: deterministicId(
10286
+ `${state.runId}:${record.id}:classification:${name}`
10287
+ ),
10288
+ spanAttributes: {
10289
+ type: "classifier" /* CLASSIFIER */,
10290
+ purpose: "scorer"
10291
+ },
10292
+ propagatedEvent: makeScorerPropagatedEvent(rootExport || void 0),
10293
+ event: { input: scorerArgs(record) }
10294
+ }
10295
+ );
10296
+ if (prepared.classifications) {
10297
+ root.log({ classifications: prepared.classifications });
10298
+ }
10299
+ record.loggedClassifications[name] = true;
10300
+ } catch (error) {
10301
+ logError(root, error);
10302
+ throw error;
10303
+ } finally {
10304
+ root.end();
10305
+ }
10306
+ }
10307
+ async function ensureBatches(definition, state, store, key, kind, scorerName2) {
10308
+ const processor = processorForStage(definition, kind, scorerName2);
10309
+ const plans = plannedBatches(
10310
+ definition,
10311
+ state.runId,
10312
+ state.cases.map(({ id }) => id)
10313
+ );
10314
+ const casesById = new Map(state.cases.map((record) => [record.id, record]));
10315
+ for (const plan of plans) {
10316
+ if (plan.kind !== kind || plan.scorerName !== scorerName2) continue;
10317
+ if (state.batches.some(({ id }) => id === plan.id)) continue;
10318
+ const records = plan.itemIds.map((id) => casesById.get(id));
10319
+ const ready = records.every(
10320
+ (record) => kind === "task" ? !record.taskComplete : !Object.hasOwn(record.scores, scorerName2)
10321
+ );
10322
+ if (!ready) continue;
10323
+ const batchId = plan.id;
10324
+ const claim = await store.getOrSet(
10325
+ claimRecordKey(key, "batch", batchId),
10326
+ encoder.encode(batchId)
10327
+ );
10328
+ if (!claim.created) continue;
10329
+ const context = { runId: state.runId, batchId };
10330
+ const items = records.map(
10331
+ (record) => kind === "task" ? taskBatchItem(record, state.parameters) : scorerBatchItem(record)
10332
+ );
10333
+ const submissionData = assertJsonValue(
10334
+ await processor.submit(items, context),
10335
+ `submission data for batch ${batchId}`
10336
+ );
10337
+ const externalId = processor.completion.mode === "webhook" ? processor.completion.getExternalId(submissionData, context) : void 0;
10338
+ if (externalId !== void 0 && !externalId.trim()) {
10339
+ throw new Error(`Batch ${batchId} produced an empty externalId`);
10340
+ }
10341
+ const batch = {
10342
+ id: batchId,
10343
+ kind,
10344
+ scorerName: scorerName2,
10345
+ itemIds: records.map((record) => record.id),
10346
+ submissionData,
10347
+ externalId,
10348
+ status: "submitted"
10349
+ };
10350
+ state.batches.push(batch);
10351
+ await writeBatchRecords(store, key, [batch]);
10352
+ }
10353
+ }
10354
+ async function collectBatch(definition, state, batch) {
10355
+ const processor = processorForStage(definition, batch.kind, batch.scorerName);
10356
+ const context = { runId: state.runId, batchId: batch.id };
10357
+ const results = await processor.collect(batch.submissionData, context);
10358
+ if (!Array.isArray(results)) {
10359
+ throw new Error(`collect for batch ${batch.id} must return an array`);
10360
+ }
10361
+ const expectedIds = new Set(batch.itemIds);
10362
+ const seen = /* @__PURE__ */ new Set();
10363
+ const records = [];
10364
+ for (const result of results) {
10365
+ const id = resultItemId(result);
10366
+ if (!expectedIds.has(id)) {
10367
+ throw new Error(`Batch ${batch.id} returned unknown item ${id}`);
10368
+ }
10369
+ if (seen.has(id)) {
10370
+ throw new Error(`Batch ${batch.id} returned item ${id} more than once`);
10371
+ }
10372
+ seen.add(id);
10373
+ const record = state.cases.find((candidate) => candidate.id === id);
10374
+ records.push(record);
10375
+ if (batch.kind === "task") {
10376
+ record.output = assertJsonValue(
10377
+ result.output,
10378
+ `task output for item ${id}`
10379
+ );
10380
+ if ("metadata" in result && result.metadata !== void 0) {
10381
+ record.metadata = assertJsonValue(
10382
+ {
10383
+ ...record.metadata,
10384
+ ...result.metadata
10385
+ },
10386
+ `metadata for ${id}`
10387
+ );
10388
+ }
10389
+ if ("tags" in result && result.tags !== void 0)
10390
+ record.tags = result.tags;
10391
+ record.taskComplete = true;
10392
+ } else {
10393
+ record.scores[batch.scorerName] = assertJsonValue(
10394
+ result.score,
10395
+ `score output for item ${id}`
10396
+ );
10397
+ }
10398
+ }
10399
+ const missing = batch.itemIds.filter((id) => !seen.has(id));
10400
+ if (missing.length > 0) {
10401
+ throw new Error(
10402
+ `Batch ${batch.id} did not return results for: ${missing.join(", ")}`
10403
+ );
10404
+ }
10405
+ return records;
10406
+ }
10407
+ function processorForStage(definition, kind, scorerName2) {
10408
+ if (kind === "task") {
10409
+ if (!isBatchTask(definition.evaluator.task)) {
10410
+ throw new Error("Definition no longer contains the batch task");
10411
+ }
10412
+ return definition.evaluator.task.processor;
10413
+ }
10414
+ const scorer = resolveScorers(definition.evaluator.scores ?? []).find(
10415
+ ({ name }) => name === scorerName2
10416
+ )?.scorer;
10417
+ if (!isBatchScorer(scorer)) {
10418
+ throw new Error(`Definition no longer contains scorer ${scorerName2}`);
10419
+ }
10420
+ return scorer.processor;
10421
+ }
10422
+ async function materializeCases(definition, data, experiment) {
10423
+ const evaluator = definition.evaluator;
10424
+ const iterable = await _internalResolveEvaluatorData(
10425
+ {
10426
+ data,
10427
+ projectName: definition.projectName,
10428
+ projectId: evaluator.projectId,
10429
+ state: evaluator.state
10430
+ },
10431
+ experiment
10432
+ );
10433
+ const records = [];
10434
+ const seen = /* @__PURE__ */ new Set();
10435
+ for await (const datum of iterable) {
10436
+ const caseId = datum.id ?? datum.upsert_id ?? (evaluator.caseId ? await evaluator.caseId(datum) : void 0);
10437
+ if (!caseId) {
10438
+ throw new Error(
10439
+ "Every durable eval case requires id, upsert_id, or caseId"
10440
+ );
10441
+ }
10442
+ if (seen.has(caseId))
10443
+ throw new Error(`Duplicate durable eval case id: ${caseId}`);
10444
+ seen.add(caseId);
10445
+ const trialCount = datum.trialCount ?? evaluator.trialCount ?? 1;
10446
+ if (!Number.isInteger(trialCount) || trialCount < 1) {
10447
+ throw new Error(`Invalid trialCount for durable eval case ${caseId}`);
10448
+ }
10449
+ for (let trialIndex = 0; trialIndex < trialCount; trialIndex++) {
10450
+ records.push({
10451
+ id: `${caseId}:trial:${trialIndex}`,
10452
+ caseId,
10453
+ trialIndex,
10454
+ datum: assertJsonValue(datum, `case ${caseId}`),
10455
+ metadata: assertJsonValue(
10456
+ "metadata" in datum ? datum.metadata : {},
10457
+ `metadata for ${caseId}`
10458
+ ),
10459
+ tags: datum.tags,
10460
+ taskComplete: false,
10461
+ taskLogged: false,
10462
+ scores: /* @__PURE__ */ Object.create(null),
10463
+ loggedScores: /* @__PURE__ */ Object.create(null),
10464
+ classifications: /* @__PURE__ */ Object.create(null),
10465
+ loggedClassifications: /* @__PURE__ */ Object.create(null)
10466
+ });
10467
+ }
10468
+ }
10469
+ return records;
10470
+ }
10471
+ function taskBatchItem(record, parameters) {
10472
+ const datum = record.datum;
10473
+ return {
10474
+ id: record.id,
10475
+ input: datum.input,
10476
+ expected: "expected" in datum ? datum.expected : void 0,
10477
+ metadata: record.metadata,
10478
+ tags: record.tags,
10479
+ parameters,
10480
+ trialIndex: record.trialIndex
10481
+ };
10482
+ }
10483
+ function scorerBatchItem(record) {
10484
+ const datum = record.datum;
10485
+ return {
10486
+ id: record.id,
10487
+ input: datum.input,
10488
+ output: record.output,
10489
+ expected: "expected" in datum ? datum.expected : void 0,
10490
+ metadata: record.metadata,
10491
+ tags: record.tags,
10492
+ trialIndex: record.trialIndex
10493
+ };
10494
+ }
10495
+ async function finishExperiment(definition, state, experiment) {
10496
+ const scorerNames = resolveScorers(definition.evaluator.scores ?? []).map(
10497
+ ({ name }) => name
10498
+ );
10499
+ const results = state.cases.map((record) => {
10500
+ const datum = record.datum;
10501
+ const scores = Object.fromEntries(
10502
+ scorerNames.flatMap(
10503
+ (name) => Object.entries(
10504
+ _internalPrepareEvaluatorScore(
10505
+ record.scores[name],
10506
+ name
10507
+ ).scores ?? {}
10508
+ )
10509
+ )
10510
+ );
10511
+ const classifications = Object.fromEntries(
10512
+ Object.entries(record.classifications).flatMap(
10513
+ ([name, value]) => Object.entries(
10514
+ _internalPrepareEvaluatorClassification(value, name).classifications ?? {}
10515
+ )
10516
+ )
10517
+ );
10518
+ return {
10519
+ ...datum,
10520
+ output: record.output,
10521
+ metadata: record.metadata,
10522
+ tags: record.tags,
10523
+ scores,
10524
+ error: void 0,
10525
+ ...Object.keys(classifications).length > 0 ? { classifications } : {}
10526
+ };
10527
+ });
10528
+ if (!experiment) {
10529
+ return buildLocalSummary(
10530
+ {
10531
+ ...definition.evaluator,
10532
+ projectName: definition.projectName,
10533
+ evalName: state.experimentName
10534
+ },
10535
+ results
10536
+ );
10537
+ }
10538
+ await experiment.flush();
10539
+ let comparisonExperimentId = definition.evaluator.baseExperimentId;
10540
+ if (!comparisonExperimentId) {
10541
+ try {
10542
+ comparisonExperimentId = await experiment._getBaseExperimentId();
10543
+ } catch {
10544
+ comparisonExperimentId = void 0;
10545
+ }
10546
+ }
10547
+ return await experiment.summarize({
10548
+ summarizeScores: definition.evaluator.summarizeScores,
10549
+ ...comparisonExperimentId ? { comparisonExperimentId } : {}
10550
+ });
10551
+ }
10552
+ function resolveScorers(scorers) {
10553
+ return scorers.map((scorer, index) => ({
10554
+ name: isBatchScorer(scorer) ? scorer.name : scorer.name || `scorer_${index}`,
10555
+ scorer
10556
+ }));
10557
+ }
10558
+ function runKey(projectName, evalName, runId) {
10559
+ return `durable-eval/v1/runs/${contentVersion(encoder.encode(`${projectName}\0${evalName}\0${runId}`))}`;
10560
+ }
10561
+ function encodedKeyPart(value) {
10562
+ return uint8ArrayToBase64(encoder.encode(value)).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
10563
+ }
10564
+ function caseRecordKey(key, caseId, kind, ...names) {
10565
+ const suffix = names.map(encodedKeyPart).join("/");
10566
+ return `${key}/cases/${encodedKeyPart(caseId)}/${kind}${suffix ? `/${suffix}` : ""}`;
10567
+ }
10568
+ function batchRecordKey(key, batchId) {
10569
+ return `${key}/batches/${encodedKeyPart(batchId)}`;
10570
+ }
10571
+ function claimRecordKey(key, kind, ...parts) {
10572
+ const identity = stableStringify([kind, parts]);
10573
+ return `${key}/claims/${contentVersion(encoder.encode(identity))}`;
10574
+ }
10575
+ async function claimAction(store, key, kind, ...parts) {
10576
+ return (await store.getOrSet(
10577
+ claimRecordKey(key, kind, ...parts),
10578
+ encoder.encode("claimed")
10579
+ )).created;
10580
+ }
10581
+ function plannedBatches(definition, runId, caseIds) {
10582
+ const stages = [];
10583
+ if (isBatchTask(definition.evaluator.task)) stages.push({ kind: "task" });
10584
+ for (const { name, scorer } of resolveScorers(
10585
+ definition.evaluator.scores ?? []
10586
+ )) {
10587
+ if (isBatchScorer(scorer)) {
10588
+ stages.push({ kind: "score", scorerName: name });
10589
+ }
10590
+ }
10591
+ const plans = [];
10592
+ for (const { kind, scorerName: scorerName2 } of stages) {
10593
+ const batchSize = processorForStage(definition, kind, scorerName2).batchSize ?? DEFAULT_BATCH_SIZE;
10594
+ if (!Number.isInteger(batchSize) || batchSize < 1) {
10595
+ throw new Error(
10596
+ `Invalid batchSize for ${scorerName2 ?? "task"}: ${batchSize}`
10597
+ );
10598
+ }
10599
+ for (let offset = 0; offset < caseIds.length; offset += batchSize) {
10600
+ const itemIds = caseIds.slice(offset, offset + batchSize);
10601
+ plans.push({
10602
+ id: deterministicId(
10603
+ stableStringify([runId, kind, scorerName2, itemIds])
10604
+ ),
10605
+ kind,
10606
+ scorerName: scorerName2,
10607
+ itemIds
10608
+ });
10609
+ }
10610
+ }
10611
+ return plans;
10612
+ }
10613
+ async function readCaseRecord(definition, store, key, id) {
10614
+ const scorers = resolveScorers(definition.evaluator.scores ?? []);
10615
+ const classifiers = (definition.evaluator.classifiers ?? []).map(
10616
+ classifierName
10617
+ );
10618
+ const [
10619
+ base,
10620
+ task,
10621
+ taskLog,
10622
+ scoreValues,
10623
+ scoreLogValues,
10624
+ classificationValues,
10625
+ classificationLogValues
10626
+ ] = await Promise.all([
10627
+ readJson(store, caseRecordKey(key, id, "base")),
10628
+ readJson(store, caseRecordKey(key, id, "task")),
10629
+ readJson(store, caseRecordKey(key, id, "task-log")),
10630
+ Promise.all(
10631
+ scorers.map(async ({ name }) => ({
10632
+ name,
10633
+ value: await readJson(
10634
+ store,
10635
+ caseRecordKey(key, id, "score", name)
10636
+ )
10637
+ }))
10638
+ ),
10639
+ Promise.all(
10640
+ scorers.map(async ({ name }) => ({
10641
+ name,
10642
+ value: await readJson(
10643
+ store,
10644
+ caseRecordKey(key, id, "score-log", name)
10645
+ )
10646
+ }))
10647
+ ),
10648
+ Promise.all(
10649
+ classifiers.map(async (name) => ({
10650
+ name,
10651
+ value: await readJson(
10652
+ store,
10653
+ caseRecordKey(key, id, "classification", name)
10654
+ )
10655
+ }))
10656
+ ),
10657
+ Promise.all(
10658
+ classifiers.map(async (name) => ({
10659
+ name,
10660
+ value: await readJson(
10661
+ store,
10662
+ caseRecordKey(key, id, "classification-log", name)
10663
+ )
10664
+ }))
10665
+ )
10666
+ ]);
10667
+ if (!base) throw new Error(`Durable eval case ${id} is missing`);
10668
+ const scores = /* @__PURE__ */ Object.create(null);
10669
+ for (const { name, value } of scoreValues) {
10670
+ if (value !== void 0) scores[name] = value;
10671
+ }
10672
+ const loggedScores = /* @__PURE__ */ Object.create(null);
10673
+ for (const { name, value } of scoreLogValues) {
10674
+ if (value) loggedScores[name] = true;
10675
+ }
10676
+ const classifications = /* @__PURE__ */ Object.create(null);
10677
+ for (const { name, value } of classificationValues) {
10678
+ if (value !== void 0) classifications[name] = value;
10679
+ }
10680
+ const loggedClassifications = /* @__PURE__ */ Object.create(null);
10681
+ for (const { name, value } of classificationLogValues) {
10682
+ if (value) loggedClassifications[name] = true;
10683
+ }
10684
+ return {
10685
+ ...base,
10686
+ metadata: task?.metadata ?? base.metadata,
10687
+ tags: task ? task.tags : base.tags,
10688
+ taskComplete: task !== void 0,
10689
+ taskLogged: taskLog !== void 0,
10690
+ output: task?.output,
10691
+ rootSpan: taskLog?.rootSpan,
10692
+ scores,
10693
+ loggedScores,
10694
+ classifications,
10695
+ loggedClassifications
10696
+ };
10697
+ }
10698
+ async function readRunState(definition, store, key) {
10699
+ const record = await readJson(store, key);
10700
+ if (!record) return void 0;
10701
+ const plans = plannedBatches(definition, record.runId, record.caseIds);
10702
+ const [cases, batchRecords] = await Promise.all([
10703
+ Promise.all(
10704
+ record.caseIds.map((id) => readCaseRecord(definition, store, key, id))
10705
+ ),
10706
+ Promise.all(
10707
+ plans.map(async ({ id }) => {
10708
+ return readJson(store, batchRecordKey(key, id));
10709
+ })
10710
+ )
10711
+ ]);
10712
+ const batches = batchRecords.filter(
10713
+ (value) => value !== void 0
10714
+ );
10715
+ const { caseIds: _caseIds, ...state } = record;
10716
+ return { ...state, cases, batches };
10717
+ }
10718
+ async function writeRunRecord(store, key, state) {
10719
+ const { cases, batches: _batches, ...record } = state;
10720
+ await writeJson(store, key, {
10721
+ ...record,
10722
+ caseIds: cases.map(({ id }) => id)
10723
+ });
10724
+ }
10725
+ async function writeCaseBaseRecords(store, key, records) {
10726
+ await Promise.all(
10727
+ records.map(
10728
+ ({ id, caseId, trialIndex, datum, metadata, tags }) => writeJson(store, caseRecordKey(key, id, "base"), {
10729
+ id,
10730
+ caseId,
10731
+ trialIndex,
10732
+ datum,
10733
+ metadata,
10734
+ tags
10735
+ })
10736
+ )
10737
+ );
10738
+ }
10739
+ async function writeCaseRecords(store, key, records) {
10740
+ const writes = [];
10741
+ for (const record of records) {
10742
+ if (record.taskComplete) {
10743
+ writes.push(
10744
+ writeJson(store, caseRecordKey(key, record.id, "task"), {
10745
+ output: record.output,
10746
+ metadata: record.metadata,
10747
+ tags: record.tags,
10748
+ taskComplete: true
10749
+ })
10750
+ );
10751
+ }
10752
+ if (record.taskLogged) {
10753
+ writes.push(
10754
+ writeJson(store, caseRecordKey(key, record.id, "task-log"), {
10755
+ rootSpan: record.rootSpan,
10756
+ taskLogged: true
10757
+ })
10758
+ );
10759
+ }
10760
+ for (const [name, value] of Object.entries(record.scores)) {
10761
+ writes.push(
10762
+ writeJson(store, caseRecordKey(key, record.id, "score", name), value)
10763
+ );
10764
+ }
10765
+ for (const name of Object.keys(record.loggedScores)) {
10766
+ writes.push(
10767
+ writeJson(
10768
+ store,
10769
+ caseRecordKey(key, record.id, "score-log", name),
10770
+ true
10771
+ )
10772
+ );
10773
+ }
10774
+ for (const [name, value] of Object.entries(record.classifications)) {
10775
+ writes.push(
10776
+ writeJson(
10777
+ store,
10778
+ caseRecordKey(key, record.id, "classification", name),
10779
+ value
10780
+ )
10781
+ );
10782
+ }
10783
+ for (const name of Object.keys(record.loggedClassifications)) {
10784
+ writes.push(
10785
+ writeJson(
10786
+ store,
10787
+ caseRecordKey(key, record.id, "classification-log", name),
10788
+ true
10789
+ )
10790
+ );
10791
+ }
10792
+ }
10793
+ await Promise.all(writes);
10794
+ }
10795
+ async function writeBatchRecords(store, key, records) {
10796
+ await Promise.all(
10797
+ records.map(
10798
+ (record) => writeJson(store, batchRecordKey(key, record.id), record)
10799
+ )
10800
+ );
10801
+ }
10802
+ function deterministicId(value) {
10803
+ const hex = contentVersion(encoder.encode(value)).padEnd(32, "0").slice(0, 32);
10804
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-5${hex.slice(13, 16)}-a${hex.slice(17, 20)}-${hex.slice(20, 32)}`;
10805
+ }
10806
+ function contentVersion(value) {
10807
+ if (isomorph_default.hash) return isomorph_default.hash(decoder.decode(value));
10808
+ let hash = 2166136261;
10809
+ for (const byte of value) {
10810
+ hash ^= byte;
10811
+ hash = Math.imul(hash, 16777619);
10812
+ }
10813
+ return (hash >>> 0).toString(16).padStart(8, "0");
10814
+ }
10815
+ async function readJson(store, key) {
10816
+ const value = await store.read(key);
10817
+ return value ? JSON.parse(decoder.decode(value)) : void 0;
10818
+ }
10819
+ async function writeJson(store, key, value) {
10820
+ await store.write(key, encoder.encode(stableStringify(value)));
10821
+ }
10822
+ function stableStringify(value) {
10823
+ return JSON.stringify(value, (_key, nested) => {
10824
+ if (nested && typeof nested === "object" && !Array.isArray(nested)) {
10825
+ return Object.fromEntries(
10826
+ Object.entries(nested).sort(
10827
+ ([left], [right]) => left.localeCompare(right)
10828
+ )
10829
+ );
10830
+ }
10831
+ return nested;
10832
+ });
10833
+ }
10834
+ function assertJsonValue(value, label) {
10835
+ try {
10836
+ const serialized = JSON.stringify(value);
10837
+ if (serialized === void 0)
10838
+ throw new Error("value serializes to undefined");
10839
+ return JSON.parse(serialized);
10840
+ } catch (error) {
10841
+ throw new Error(`${label} must be JSON serializable`, { cause: error });
10842
+ }
10843
+ }
10844
+ function resultItemId(value) {
10845
+ if (typeof value !== "object" || value === null || !("id" in value) || typeof value.id !== "string") {
10846
+ throw new Error("Batch results must contain a string id");
10847
+ }
10848
+ return value.id;
10849
+ }
10850
+ function isBatchTask(value) {
10851
+ return typeof value === "object" && value !== null && "kind" in value && value.kind === BATCH_TASK_KIND;
10852
+ }
10853
+ function isBatchScorer(value) {
10854
+ return typeof value === "object" && value !== null && "kind" in value && value.kind === BATCH_SCORER_KIND;
10855
+ }
10856
+ function asError(error) {
10857
+ return error instanceof Error ? error : new Error(String(error));
10858
+ }
10859
+
9576
10860
  // src/agent-assertions.ts
9577
10861
  function agentAssertionScorer(callback, options = {}) {
9578
10862
  return async (args) => {
@@ -10505,6 +11789,8 @@ export {
10505
11789
  BRAINTRUST_PARENT_KEY,
10506
11790
  BaseAttachment,
10507
11791
  BaseExperiment,
11792
+ BatchScorer,
11793
+ BatchTask,
10508
11794
  BraintrustLangChainCallbackHandler,
10509
11795
  BraintrustMiddleware,
10510
11796
  BraintrustObservabilityExporter,
@@ -10518,6 +11804,8 @@ export {
10518
11804
  DEFAULT_MAX_REQUEST_SIZE,
10519
11805
  Dataset,
10520
11806
  DatasetPipeline,
11807
+ DurableEvalMemoryStore,
11808
+ DurableEvalRedisStore,
10521
11809
  ERR_PERMALINK,
10522
11810
  Eval,
10523
11811
  EvalResultWithSummary,
@@ -10576,6 +11864,7 @@ export {
10576
11864
  deepCopyEvent,
10577
11865
  exports_exports as default,
10578
11866
  defaultErrorScoreHandler,
11867
+ defineDurableEval,
10579
11868
  deserializePlainStringAsJSON,
10580
11869
  devNullWritableStream,
10581
11870
  evaluatorDefinitionSchema,
@@ -10671,5 +11960,6 @@ export {
10671
11960
  wrapPiCodingAgentSDK,
10672
11961
  wrapStrandsAgentSDK,
10673
11962
  wrapTraced,
10674
- wrapVitest
11963
+ wrapVitest,
11964
+ wrapVoyageAI
10675
11965
  };