braintrust 3.26.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 (61) 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 +3727 -1526
  4. package/dev/dist/index.mjs +2815 -614
  5. package/dist/apply-auto-instrumentation.js +372 -261
  6. package/dist/apply-auto-instrumentation.mjs +171 -60
  7. package/dist/auto-instrumentations/bundler/esbuild.cjs +458 -61
  8. package/dist/auto-instrumentations/bundler/esbuild.mjs +5 -4
  9. package/dist/auto-instrumentations/bundler/next.cjs +460 -63
  10. package/dist/auto-instrumentations/bundler/next.mjs +6 -5
  11. package/dist/auto-instrumentations/bundler/rollup.cjs +458 -61
  12. package/dist/auto-instrumentations/bundler/rollup.mjs +5 -4
  13. package/dist/auto-instrumentations/bundler/vite.cjs +487 -62
  14. package/dist/auto-instrumentations/bundler/vite.mjs +34 -5
  15. package/dist/auto-instrumentations/bundler/webpack-loader.cjs +444 -61
  16. package/dist/auto-instrumentations/bundler/webpack.cjs +458 -61
  17. package/dist/auto-instrumentations/bundler/webpack.mjs +6 -5
  18. package/dist/auto-instrumentations/{chunk-6E22MYSW.mjs → chunk-26PKVUKB.mjs} +109 -5
  19. package/dist/auto-instrumentations/{chunk-AKKPLXTP.mjs → chunk-2AMDGD65.mjs} +160 -57
  20. package/dist/auto-instrumentations/{chunk-V5LEODRX.mjs → chunk-HD35AM3M.mjs} +1 -1
  21. package/dist/auto-instrumentations/{chunk-XYN63IG5.mjs → chunk-I55G56ZL.mjs} +21 -58
  22. package/dist/auto-instrumentations/{chunk-NRE4QUQR.mjs → chunk-JPUFNW7X.mjs} +140 -0
  23. package/dist/auto-instrumentations/{chunk-XZHHE4Y3.mjs → chunk-NP7V4XB2.mjs} +157 -3
  24. package/dist/auto-instrumentations/hook.mjs +396 -32
  25. package/dist/auto-instrumentations/index.cjs +298 -2
  26. package/dist/auto-instrumentations/index.d.mts +5 -1
  27. package/dist/auto-instrumentations/index.d.ts +5 -1
  28. package/dist/auto-instrumentations/index.mjs +6 -2
  29. package/dist/auto-instrumentations/loader/cjs-patch.cjs +105 -57
  30. package/dist/auto-instrumentations/loader/cjs-patch.mjs +2 -2
  31. package/dist/auto-instrumentations/loader/esm-hook.mjs +1 -1
  32. package/dist/browser.d.mts +683 -56
  33. package/dist/browser.d.ts +683 -56
  34. package/dist/browser.js +4242 -655
  35. package/dist/browser.mjs +4242 -655
  36. package/dist/{chunk-VYNNEKSA.js → chunk-BBE7SNRV.js} +248 -10
  37. package/dist/{chunk-VRZZQPAA.js → chunk-OBBWQW6K.js} +3351 -1392
  38. package/dist/{chunk-XIZOM2HO.mjs → chunk-UPFNQCGB.mjs} +2419 -460
  39. package/dist/{chunk-PN7EWK66.mjs → chunk-ZHUHZWFY.mjs} +241 -3
  40. package/dist/cli.js +2867 -668
  41. package/dist/edge-light.d.mts +1 -1
  42. package/dist/edge-light.d.ts +1 -1
  43. package/dist/edge-light.js +4242 -655
  44. package/dist/edge-light.mjs +4242 -655
  45. package/dist/index.d.mts +1267 -640
  46. package/dist/index.d.ts +1267 -640
  47. package/dist/index.js +2107 -630
  48. package/dist/index.mjs +1649 -172
  49. package/dist/instrumentation/index.d.mts +198 -6
  50. package/dist/instrumentation/index.d.ts +198 -6
  51. package/dist/instrumentation/index.js +2661 -581
  52. package/dist/instrumentation/index.mjs +2661 -581
  53. package/dist/vitest-evals-reporter.js +45 -43
  54. package/dist/vitest-evals-reporter.mjs +5 -3
  55. package/dist/workerd.d.mts +1 -1
  56. package/dist/workerd.d.ts +1 -1
  57. package/dist/workerd.js +4242 -655
  58. package/dist/workerd.mjs +4242 -655
  59. package/package.json +2 -3
  60. package/util/dist/index.d.mts +1545 -100
  61. package/util/dist/index.d.ts +1545 -100
package/dist/index.mjs CHANGED
@@ -55,14 +55,17 @@ 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,
64
66
  braintrustStreamChunkSchema,
65
67
  buildBedrockRuntimeSpanInfo,
68
+ collectAnthropicSession,
66
69
  configureInstrumentation,
67
70
  configureNode,
68
71
  constructLogs3OverflowRequest,
@@ -104,7 +107,6 @@ import {
104
107
  loginToState,
105
108
  logs3OverflowUploadSchema,
106
109
  makeScorerPropagatedEvent,
107
- mergeDicts,
108
110
  newId,
109
111
  parseCachedHeader,
110
112
  parseTemplateFormat,
@@ -126,8 +128,10 @@ import {
126
128
  summarize,
127
129
  summarizeAndFlush,
128
130
  templateRegistry,
131
+ toNumericMetrics,
129
132
  traceable,
130
133
  traced,
134
+ uint8ArrayToBase64,
131
135
  updateSpan,
132
136
  uploadLogs3OverflowPayload,
133
137
  utf8ByteLength,
@@ -140,7 +144,7 @@ import {
140
144
  wrapMastraAgent,
141
145
  wrapTraced,
142
146
  zodToJsonSchema
143
- } from "./chunk-XIZOM2HO.mjs";
147
+ } from "./chunk-UPFNQCGB.mjs";
144
148
  import {
145
149
  INSTRUMENTATION_NAMES,
146
150
  __export,
@@ -149,6 +153,7 @@ import {
149
153
  bedrockRuntimeChannels,
150
154
  claudeAgentSDKChannels,
151
155
  cloudflareAgentsChannels,
156
+ cloudflareThinkChannels,
152
157
  cohereChannels,
153
158
  cursorSDKChannels,
154
159
  genkitChannels,
@@ -163,14 +168,16 @@ import {
163
168
  isomorph_default,
164
169
  langSmithChannels,
165
170
  mistralChannels,
171
+ ollamaChannels,
166
172
  openAIChannels,
167
173
  openAICodexChannels,
168
174
  openRouterAgentChannels,
169
175
  openRouterChannels,
170
176
  piCodingAgentChannels,
171
177
  strandsAgentSDKChannels,
178
+ voyageAIChannels,
172
179
  withSpanInstrumentationName
173
- } from "./chunk-PN7EWK66.mjs";
180
+ } from "./chunk-ZHUHZWFY.mjs";
174
181
 
175
182
  // src/exports.ts
176
183
  var exports_exports = {};
@@ -183,6 +190,8 @@ __export(exports_exports, {
183
190
  BRAINTRUST_PARENT_KEY: () => BRAINTRUST_PARENT_KEY,
184
191
  BaseAttachment: () => BaseAttachment,
185
192
  BaseExperiment: () => BaseExperiment,
193
+ BatchScorer: () => BatchScorer,
194
+ BatchTask: () => BatchTask,
186
195
  BraintrustLangChainCallbackHandler: () => BraintrustLangChainCallbackHandler,
187
196
  BraintrustMiddleware: () => BraintrustMiddleware,
188
197
  BraintrustObservabilityExporter: () => BraintrustObservabilityExporter,
@@ -196,6 +205,8 @@ __export(exports_exports, {
196
205
  DEFAULT_MAX_REQUEST_SIZE: () => DEFAULT_MAX_REQUEST_SIZE,
197
206
  Dataset: () => Dataset,
198
207
  DatasetPipeline: () => DatasetPipeline,
208
+ DurableEvalMemoryStore: () => DurableEvalMemoryStore,
209
+ DurableEvalRedisStore: () => DurableEvalRedisStore,
199
210
  ERR_PERMALINK: () => ERR_PERMALINK,
200
211
  Eval: () => Eval,
201
212
  EvalResultWithSummary: () => EvalResultWithSummary,
@@ -244,6 +255,7 @@ __export(exports_exports, {
244
255
  braintrustFlueObserver: () => braintrustFlueObserver,
245
256
  braintrustStreamChunkSchema: () => braintrustStreamChunkSchema,
246
257
  buildLocalSummary: () => buildLocalSummary,
258
+ collectAnthropicSession: () => collectAnthropicSession,
247
259
  configureInstrumentation: () => configureInstrumentation,
248
260
  constructLogs3OverflowRequest: () => constructLogs3OverflowRequest,
249
261
  createFinalValuePassThroughStream: () => createFinalValuePassThroughStream,
@@ -252,6 +264,7 @@ __export(exports_exports, {
252
264
  currentSpan: () => currentSpan,
253
265
  deepCopyEvent: () => deepCopyEvent,
254
266
  defaultErrorScoreHandler: () => defaultErrorScoreHandler,
267
+ defineDurableEval: () => defineDurableEval,
255
268
  deserializePlainStringAsJSON: () => deserializePlainStringAsJSON,
256
269
  devNullWritableStream: () => devNullWritableStream,
257
270
  evaluatorDefinitionSchema: () => evaluatorDefinitionSchema,
@@ -323,6 +336,7 @@ __export(exports_exports, {
323
336
  wrapClaudeAgentSDK: () => wrapClaudeAgentSDK,
324
337
  wrapCloudflareAIChat: () => wrapCloudflareAIChat,
325
338
  wrapCloudflareAgent: () => wrapCloudflareAgent,
339
+ wrapCloudflareThink: () => wrapCloudflareThink,
326
340
  wrapCohere: () => wrapCohere,
327
341
  wrapCopilotClient: () => wrapCopilotClient,
328
342
  wrapCursorSDK: () => wrapCursorSDK,
@@ -337,6 +351,7 @@ __export(exports_exports, {
337
351
  wrapLangSmithTraceable: () => wrapLangSmithTraceable,
338
352
  wrapMastraAgent: () => wrapMastraAgent,
339
353
  wrapMistral: () => wrapMistral,
354
+ wrapOllama: () => wrapOllama,
340
355
  wrapOpenAI: () => wrapOpenAI,
341
356
  wrapOpenAICodexSDK: () => wrapOpenAICodexSDK,
342
357
  wrapOpenAIv4: () => wrapOpenAIv4,
@@ -345,7 +360,8 @@ __export(exports_exports, {
345
360
  wrapPiCodingAgentSDK: () => wrapPiCodingAgentSDK,
346
361
  wrapStrandsAgentSDK: () => wrapStrandsAgentSDK,
347
362
  wrapTraced: () => wrapTraced,
348
- wrapVitest: () => wrapVitest
363
+ wrapVitest: () => wrapVitest,
364
+ wrapVoyageAI: () => wrapVoyageAI
349
365
  });
350
366
 
351
367
  // src/sandbox.ts
@@ -1606,7 +1622,7 @@ function normalizeUsageMetrics(usage, provider, providerMetadata) {
1606
1622
  cacheCreationTokens
1607
1623
  );
1608
1624
  Object.assign(metrics, cacheTokens);
1609
- Object.assign(metrics, finalizeAnthropicTokens(metrics));
1625
+ return toNumericMetrics(finalizeAnthropicTokens(metrics));
1610
1626
  }
1611
1627
  }
1612
1628
  return metrics;
@@ -3541,6 +3557,66 @@ function betaProxy(beta, anthropic) {
3541
3557
  if (prop === "messages") {
3542
3558
  return betaMessagesProxy(target.messages, anthropic);
3543
3559
  }
3560
+ if (prop === "sessions") {
3561
+ return target.sessions ? betaSessionsProxy(target.sessions) : target.sessions;
3562
+ }
3563
+ return Reflect.get(target, prop, receiver);
3564
+ }
3565
+ });
3566
+ }
3567
+ function betaSessionsProxy(sessions) {
3568
+ return new Proxy(sessions, {
3569
+ get(target, prop, receiver) {
3570
+ if (prop === "events") {
3571
+ return betaSessionEventsProxy(target.events);
3572
+ }
3573
+ if (prop === "threads") {
3574
+ return target.threads ? betaSessionThreadsProxy(target.threads) : target.threads;
3575
+ }
3576
+ return Reflect.get(target, prop, receiver);
3577
+ }
3578
+ });
3579
+ }
3580
+ function betaSessionEventsProxy(events) {
3581
+ return new Proxy(events, {
3582
+ get(target, prop, receiver) {
3583
+ if (prop === "stream") {
3584
+ return new TypedApplyProxy(target.stream, {
3585
+ apply(stream, thisArg, argArray) {
3586
+ return anthropicChannels.betaSessionsEventsStream.tracePromise(
3587
+ () => Reflect.apply(stream, thisArg, argArray),
3588
+ { arguments: argArray }
3589
+ );
3590
+ }
3591
+ });
3592
+ }
3593
+ return Reflect.get(target, prop, receiver);
3594
+ }
3595
+ });
3596
+ }
3597
+ function betaSessionThreadsProxy(threads) {
3598
+ return new Proxy(threads, {
3599
+ get(target, prop, receiver) {
3600
+ if (prop === "events") {
3601
+ return betaSessionThreadEventsProxy(target.events);
3602
+ }
3603
+ return Reflect.get(target, prop, receiver);
3604
+ }
3605
+ });
3606
+ }
3607
+ function betaSessionThreadEventsProxy(events) {
3608
+ return new Proxy(events, {
3609
+ get(target, prop, receiver) {
3610
+ if (prop === "stream") {
3611
+ return new TypedApplyProxy(target.stream, {
3612
+ apply(stream, thisArg, argArray) {
3613
+ return anthropicChannels.betaSessionsThreadsEventsStream.tracePromise(
3614
+ () => Reflect.apply(stream, thisArg, argArray),
3615
+ { arguments: argArray }
3616
+ );
3617
+ }
3618
+ });
3619
+ }
3544
3620
  return Reflect.get(target, prop, receiver);
3545
3621
  }
3546
3622
  });
@@ -3734,6 +3810,52 @@ function claudeAgentSDKProxy(sdk) {
3734
3810
  });
3735
3811
  }
3736
3812
 
3813
+ // src/wrappers/cloudflare-think.ts
3814
+ var WRAPPED_THINK = /* @__PURE__ */ Symbol.for("braintrust.cloudflare-think.wrapped");
3815
+ function wrapCloudflareThink(sdk) {
3816
+ if (!sdk || typeof sdk !== "object") {
3817
+ return sdk;
3818
+ }
3819
+ const thinkModule = sdk;
3820
+ if (typeof thinkModule.Think !== "function") {
3821
+ return sdk;
3822
+ }
3823
+ patchThinkClass(thinkModule.Think);
3824
+ return sdk;
3825
+ }
3826
+ function patchThinkClass(Think) {
3827
+ const prototype = Think.prototype;
3828
+ if (!prototype || prototype[WRAPPED_THINK]) {
3829
+ return;
3830
+ }
3831
+ const descriptor = Object.getOwnPropertyDescriptor(
3832
+ prototype,
3833
+ "_runInferenceLoop"
3834
+ );
3835
+ if (!descriptor || typeof descriptor.value !== "function") {
3836
+ return;
3837
+ }
3838
+ const original = descriptor.value;
3839
+ Object.defineProperty(prototype, "_runInferenceLoop", {
3840
+ ...descriptor,
3841
+ value: function wrappedCloudflareThinkRunInferenceLoop(input) {
3842
+ const args = [input];
3843
+ return cloudflareThinkChannels.runInferenceLoop.tracePromise(
3844
+ () => Reflect.apply(original, this, args),
3845
+ {
3846
+ arguments: args,
3847
+ self: this
3848
+ }
3849
+ );
3850
+ }
3851
+ });
3852
+ Object.defineProperty(prototype, WRAPPED_THINK, {
3853
+ configurable: false,
3854
+ enumerable: false,
3855
+ value: true
3856
+ });
3857
+ }
3858
+
3737
3859
  // src/wrappers/openai-codex.ts
3738
3860
  var WRAPPED_CLIENT = /* @__PURE__ */ Symbol.for("braintrust.openai-codex.wrapped-client");
3739
3861
  var WRAPPED_THREAD = /* @__PURE__ */ Symbol.for("braintrust.openai-codex.wrapped-thread");
@@ -4026,14 +4148,9 @@ function patchAgentSessionClass(AgentSession) {
4026
4148
  ...descriptor,
4027
4149
  value: function wrappedPiCodingAgentPrompt(text, options) {
4028
4150
  const args = [text, options];
4029
- return piCodingAgentChannels.prompt.tracePromise(
4030
- () => Reflect.apply(originalPrompt, this, args),
4031
- {
4032
- arguments: args,
4033
- self: this,
4034
- session: this
4035
- }
4036
- );
4151
+ return piCodingAgentChannels.prompt.invoke(originalPrompt, this, args, {
4152
+ session: this
4153
+ });
4037
4154
  }
4038
4155
  });
4039
4156
  Object.defineProperty(prototype, WRAPPED_PROMPT, {
@@ -5473,6 +5590,83 @@ function wrapAgentsStream(stream) {
5473
5590
  });
5474
5591
  }
5475
5592
 
5593
+ // src/wrappers/ollama.ts
5594
+ function wrapOllama(ollama) {
5595
+ if (isSupportedOllamaClient(ollama)) {
5596
+ return ollamaProxy(ollama);
5597
+ }
5598
+ debugLogger.warn("Unsupported Ollama library. Not wrapping.");
5599
+ return ollama;
5600
+ }
5601
+ var ollamaProxyCache = /* @__PURE__ */ new WeakMap();
5602
+ function isSupportedOllamaClient(value) {
5603
+ return isObject(value) && ["chat", "generate", "embed"].some(
5604
+ (name) => typeof value[name] === "function"
5605
+ );
5606
+ }
5607
+ function ollamaProxy(ollama) {
5608
+ const cached = ollamaProxyCache.get(ollama);
5609
+ if (cached) {
5610
+ return cached;
5611
+ }
5612
+ let chatSource;
5613
+ let wrappedChat;
5614
+ let generateSource;
5615
+ let wrappedGenerate;
5616
+ let embedSource;
5617
+ let wrappedEmbed;
5618
+ const proxy = new Proxy(ollama, {
5619
+ get(target, prop, receiver) {
5620
+ switch (prop) {
5621
+ case "chat": {
5622
+ const source = target.chat;
5623
+ if (source !== chatSource) {
5624
+ chatSource = source;
5625
+ wrappedChat = typeof source === "function" ? wrapChat(source.bind(target)) : source;
5626
+ }
5627
+ return wrappedChat;
5628
+ }
5629
+ case "generate": {
5630
+ const source = target.generate;
5631
+ if (source !== generateSource) {
5632
+ generateSource = source;
5633
+ wrappedGenerate = typeof source === "function" ? wrapGenerate2(source.bind(target)) : source;
5634
+ }
5635
+ return wrappedGenerate;
5636
+ }
5637
+ case "embed": {
5638
+ const source = target.embed;
5639
+ if (source !== embedSource) {
5640
+ embedSource = source;
5641
+ wrappedEmbed = typeof source === "function" ? wrapEmbed3(source.bind(target)) : source;
5642
+ }
5643
+ return wrappedEmbed;
5644
+ }
5645
+ default:
5646
+ return Reflect.get(target, prop, receiver);
5647
+ }
5648
+ }
5649
+ });
5650
+ ollamaProxyCache.set(ollama, proxy);
5651
+ ollamaProxyCache.set(proxy, proxy);
5652
+ return proxy;
5653
+ }
5654
+ function wrapChat(chat) {
5655
+ return (request) => ollamaChannels.chat.tracePromise(() => chat(request), {
5656
+ arguments: [request]
5657
+ });
5658
+ }
5659
+ function wrapGenerate2(generate) {
5660
+ return (request) => ollamaChannels.generate.tracePromise(() => generate(request), {
5661
+ arguments: [request]
5662
+ });
5663
+ }
5664
+ function wrapEmbed3(embed) {
5665
+ return (request) => ollamaChannels.embed.tracePromise(() => embed(request), {
5666
+ arguments: [request]
5667
+ });
5668
+ }
5669
+
5476
5670
  // src/wrappers/cohere.ts
5477
5671
  function wrapCohere(cohere) {
5478
5672
  if (isSupportedCohereClient(cohere)) {
@@ -5503,11 +5697,11 @@ function cohereProxy(cohere) {
5503
5697
  get(target, prop, receiver) {
5504
5698
  switch (prop) {
5505
5699
  case "chat":
5506
- return typeof target.chat === "function" ? wrapChat(target.chat.bind(target)) : target.chat;
5700
+ return typeof target.chat === "function" ? wrapChat2(target.chat.bind(target)) : target.chat;
5507
5701
  case "chatStream":
5508
5702
  return typeof target.chatStream === "function" ? wrapChatStream2(target.chatStream.bind(target)) : target.chatStream;
5509
5703
  case "embed":
5510
- return typeof target.embed === "function" ? wrapEmbed3(target.embed.bind(target)) : target.embed;
5704
+ return typeof target.embed === "function" ? wrapEmbed4(target.embed.bind(target)) : target.embed;
5511
5705
  case "rerank":
5512
5706
  return typeof target.rerank === "function" ? wrapRerank3(target.rerank.bind(target)) : target.rerank;
5513
5707
  default: {
@@ -5520,7 +5714,7 @@ function cohereProxy(cohere) {
5520
5714
  cohereProxyCache.set(cohere, proxy);
5521
5715
  return proxy;
5522
5716
  }
5523
- function wrapChat(chat) {
5717
+ function wrapChat2(chat) {
5524
5718
  return (request, options) => cohereChannels.chat.tracePromise(() => chat(request, options), {
5525
5719
  arguments: [request]
5526
5720
  });
@@ -5530,7 +5724,7 @@ function wrapChatStream2(chatStream) {
5530
5724
  arguments: [request]
5531
5725
  });
5532
5726
  }
5533
- function wrapEmbed3(embed) {
5727
+ function wrapEmbed4(embed) {
5534
5728
  return (request, options) => cohereChannels.embed.tracePromise(() => embed(request, options), {
5535
5729
  arguments: [request]
5536
5730
  });
@@ -5541,6 +5735,84 @@ function wrapRerank3(rerank) {
5541
5735
  });
5542
5736
  }
5543
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
+
5544
5816
  // src/wrappers/groq.ts
5545
5817
  function wrapGroq(groq) {
5546
5818
  if (isSupportedGroqClient(groq)) {
@@ -5552,14 +5824,14 @@ function wrapGroq(groq) {
5552
5824
  function isRecord4(value) {
5553
5825
  return typeof value === "object" && value !== null;
5554
5826
  }
5555
- function hasFunction5(value, methodName) {
5827
+ function hasFunction6(value, methodName) {
5556
5828
  return isRecord4(value) && methodName in value && typeof value[methodName] === "function";
5557
5829
  }
5558
5830
  function hasChat2(value) {
5559
- return isRecord4(value) && isRecord4(value.completions) && hasFunction5(value.completions, "create");
5831
+ return isRecord4(value) && isRecord4(value.completions) && hasFunction6(value.completions, "create");
5560
5832
  }
5561
5833
  function hasEmbeddings2(value) {
5562
- return hasFunction5(value, "create");
5834
+ return hasFunction6(value, "create");
5563
5835
  }
5564
5836
  function isSupportedGroqClient(value) {
5565
5837
  return isRecord4(value) && (value.chat !== void 0 && hasChat2(value.chat) || value.embeddings !== void 0 && hasEmbeddings2(value.embeddings));
@@ -6101,7 +6373,7 @@ async function flushExperimentWithSync(context, config) {
6101
6373
  // src/wrappers/shared/scorers.ts
6102
6374
  async function runScorers(args) {
6103
6375
  const { scorers, output, expected, input, metadata, span } = args;
6104
- const scorerArgs = {
6376
+ const scorerArgs2 = {
6105
6377
  output,
6106
6378
  expected,
6107
6379
  input,
@@ -6110,7 +6382,7 @@ async function runScorers(args) {
6110
6382
  await Promise.all(
6111
6383
  scorers.map(async (scorer) => {
6112
6384
  try {
6113
- const result = await scorer(scorerArgs);
6385
+ const result = await scorer(scorerArgs2);
6114
6386
  const scores = normalizeScores(result);
6115
6387
  if (scores.length > 0) {
6116
6388
  const accScores = {};
@@ -8469,6 +8741,26 @@ async function getExperimentParametersRef(parameters) {
8469
8741
  version: resolvedParameters.version
8470
8742
  };
8471
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
+ }
8472
8764
  function callEvaluatorData(data) {
8473
8765
  const dataResult = typeof data === "function" ? data() : data;
8474
8766
  let baseExperiment = void 0;
@@ -8486,6 +8778,48 @@ function isAsyncIterable2(value) {
8486
8778
  function isIterable(value) {
8487
8779
  return typeof value === "object" && value !== null && Symbol.iterator in value && typeof value[Symbol.iterator] === "function";
8488
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
+ }
8489
8823
  globalThis._evals = {
8490
8824
  functions: [],
8491
8825
  prompts: [],
@@ -8532,25 +8866,13 @@ async function Eval(name, evaluator, reporterOrOpts) {
8532
8866
  }
8533
8867
  const resolvedReporter = options.reporter || defaultReporter;
8534
8868
  try {
8535
- const { data, baseExperiment: defaultBaseExperiment } = callEvaluatorData(
8536
- 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) }
8537
8875
  );
8538
- const parameters = await getExperimentParametersRef(evaluator.parameters);
8539
- const experiment = options.parent || options.noSendLogs ? null : initExperiment2(evaluator.state, {
8540
- ...evaluator.projectId ? { projectId: evaluator.projectId } : { project: name },
8541
- experiment: evaluator.experimentName,
8542
- description: evaluator.description,
8543
- metadata: evaluator.metadata,
8544
- tags: evaluator.tags,
8545
- isPublic: evaluator.isPublic,
8546
- update: evaluator.update,
8547
- baseExperiment: evaluator.baseExperimentName ?? defaultBaseExperiment,
8548
- baseExperimentId: evaluator.baseExperimentId,
8549
- gitMetadataSettings: evaluator.gitMetadataSettings,
8550
- repoInfo: evaluator.repoInfo,
8551
- dataset: Dataset.isDataset(data) ? data : void 0,
8552
- parameters
8553
- });
8554
8876
  if (experiment && typeof process !== "undefined" && globalThis.BRAINTRUST_CONTEXT_MANAGER !== void 0) {
8555
8877
  await experiment._waitForId();
8556
8878
  }
@@ -8648,19 +8970,74 @@ function scorerName(scorer, scorer_idx) {
8648
8970
  function classifierName(classifier, classifier_idx) {
8649
8971
  return classifier.name || `classifier_${classifier_idx}`;
8650
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
+ }
8651
8997
  function buildSpanMetadata(results) {
8652
- return results.length === 1 ? results[0].metadata : results.reduce(
8653
- (prev, s) => mergeDicts(prev, { [s.name]: s.metadata }),
8654
- {}
8998
+ return results.length === 1 ? results[0].metadata : Object.fromEntries(
8999
+ results.map((result) => [result.name, result.metadata])
8655
9000
  );
8656
9001
  }
8657
9002
  function buildSpanScores(results) {
8658
- const scoresRecord = results.reduce(
8659
- (prev, s) => mergeDicts(prev, { [s.name]: s.score }),
8660
- {}
9003
+ const scoresRecord = Object.fromEntries(
9004
+ results.map((result) => [result.name, result.score])
8661
9005
  );
8662
9006
  return { resultMetadata: buildSpanMetadata(results), scoresRecord };
8663
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
+ }
8664
9041
  async function runInScorerSpan(rootSpan, spanName, spanType, propagatedEvent, eventInput, fn) {
8665
9042
  try {
8666
9043
  const value = await rootSpan.traced(fn, {
@@ -8705,6 +9082,27 @@ function toClassificationItem(c) {
8705
9082
  ...c.metadata !== void 0 ? { metadata: c.metadata } : {}
8706
9083
  };
8707
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
+ }
8708
9106
  function logScoringFailures(kind, failures, metadata, rootSpan, state) {
8709
9107
  if (!failures.length) return [];
8710
9108
  const errorMap = Object.fromEntries(
@@ -8752,54 +9150,14 @@ async function runEvaluatorInternal(experiment, evaluator, progressReporter, fil
8752
9150
  (evaluator.state ?? _internalGetGlobalState())?.spanCache?.start();
8753
9151
  }
8754
9152
  try {
8755
- if (typeof evaluator.data === "string") {
8756
- throw new Error("Unimplemented: string data paths");
8757
- }
8758
- let dataResult = typeof evaluator.data === "function" ? evaluator.data() : evaluator.data;
8759
9153
  parameters = await validateParameters(
8760
9154
  parameters ?? {},
8761
9155
  evaluator.parameters
8762
9156
  );
8763
- if ("_type" in dataResult) {
8764
- if (dataResult._type !== "BaseExperiment") {
8765
- throw new Error("Invalid _type");
8766
- }
8767
- if (!experiment) {
8768
- throw new Error(
8769
- "Cannot use BaseExperiment() without connecting to Braintrust (you most likely set --no-send-logs)"
8770
- );
8771
- }
8772
- let name = dataResult.name;
8773
- if (isEmpty(name)) {
8774
- const baseExperiment = await experiment.fetchBaseExperiment();
8775
- if (!baseExperiment) {
8776
- throw new Error("BaseExperiment() failed to fetch base experiment");
8777
- }
8778
- name = baseExperiment.name;
8779
- }
8780
- dataResult = initExperiment2(evaluator.state, {
8781
- ...evaluator.projectId ? { projectId: evaluator.projectId } : { project: evaluator.projectName },
8782
- experiment: name,
8783
- open: true
8784
- }).asDataset();
8785
- }
8786
- const resolvedDataResult = dataResult instanceof Promise ? await dataResult : dataResult;
8787
- const dataIterable = (() => {
8788
- if (isAsyncIterable2(resolvedDataResult)) {
8789
- return resolvedDataResult;
8790
- }
8791
- if (Array.isArray(resolvedDataResult) || isIterable(resolvedDataResult)) {
8792
- const iterable = resolvedDataResult;
8793
- return (async function* () {
8794
- for (const datum of iterable) {
8795
- yield datum;
8796
- }
8797
- })();
8798
- }
8799
- throw new Error(
8800
- "Evaluator data must be an array, iterable, or async iterable"
8801
- );
8802
- })();
9157
+ const dataIterable = await _internalResolveEvaluatorData(
9158
+ evaluator,
9159
+ experiment
9160
+ );
8803
9161
  progressReporter.start(evaluator.evalName, 0);
8804
9162
  const experimentIdPromise = experiment ? (async () => {
8805
9163
  try {
@@ -8868,57 +9226,45 @@ async function runEvaluatorInternal(experiment, evaluator, progressReporter, fil
8868
9226
  ensureSpansFlushed,
8869
9227
  state
8870
9228
  }) : void 0;
8871
- let metadata = {
8872
- ..."metadata" in datum ? datum.metadata : {}
8873
- };
9229
+ let metadata = {};
8874
9230
  const expected = "expected" in datum ? datum.expected : void 0;
8875
9231
  let output = void 0;
8876
9232
  let error = void 0;
8877
- let tags = [...datum.tags ?? []];
8878
- const scores = {};
8879
- const classifications = {};
9233
+ let tags = [];
9234
+ const scores = /* @__PURE__ */ Object.create(null);
9235
+ const classifications = /* @__PURE__ */ Object.create(null);
8880
9236
  const scorerNames = (evaluator.scores ?? []).map(scorerName);
8881
9237
  const classifierNames = (evaluator.classifiers ?? []).map(
8882
9238
  classifierName
8883
9239
  );
8884
9240
  let unhandledScores = scorerNames;
8885
9241
  try {
8886
- const meta = (o) => metadata = { ...metadata, ...o };
8887
- await rootSpan.traced(
8888
- async (span) => {
8889
- const hooksForTask = {
8890
- meta,
8891
- metadata,
8892
- expected,
8893
- span,
8894
- parameters: parameters ?? {},
8895
- reportProgress: (event) => {
8896
- stream?.({
8897
- ...event,
8898
- id: rootSpan.id,
8899
- origin: baseEvent.event?.origin,
8900
- name: evaluator.evalName,
8901
- object_type: "task"
8902
- });
8903
- },
8904
- trialIndex,
8905
- tags
8906
- };
8907
- const outputResult = evaluator.task(datum.input, hooksForTask);
8908
- if (outputResult instanceof Promise) {
8909
- output = await outputResult;
8910
- } else {
8911
- 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
+ });
8912
9257
  }
8913
- tags = hooksForTask.tags ?? [];
8914
- span.log({ output });
8915
- },
9258
+ ),
8916
9259
  {
8917
9260
  name: "task",
8918
9261
  spanAttributes: { type: "task" /* TASK */ },
8919
9262
  event: { input: datum.input }
8920
9263
  }
8921
9264
  );
9265
+ output = taskResult.output;
9266
+ metadata = taskResult.metadata;
9267
+ tags = taskResult.tags;
8922
9268
  if (tags.length) {
8923
9269
  rootSpan.log({ output, metadata, expected, tags });
8924
9270
  } else {
@@ -8928,20 +9274,18 @@ async function runEvaluatorInternal(experiment, evaluator, progressReporter, fil
8928
9274
  await rootSpan.flush();
8929
9275
  }
8930
9276
  const scoringArgs = {
9277
+ id: datum.id,
8931
9278
  input: datum.input,
8932
9279
  expected: "expected" in datum ? datum.expected : void 0,
8933
9280
  metadata,
8934
9281
  output,
9282
+ tags,
8935
9283
  trace
8936
9284
  };
8937
9285
  const { trace: _trace, ...scoringArgsForLogging } = scoringArgs;
8938
9286
  const propagatedEvent = makeScorerPropagatedEvent(
8939
9287
  await rootSpan.export()
8940
9288
  );
8941
- const getOtherFields = (s) => {
8942
- const { metadata: _metadata, name: _name, ...rest } = s;
8943
- return rest;
8944
- };
8945
9289
  const [scoreResults, classificationResults] = await Promise.all([
8946
9290
  Promise.all(
8947
9291
  (evaluator.scores ?? []).map(
@@ -8955,35 +9299,17 @@ async function runEvaluatorInternal(experiment, evaluator, progressReporter, fil
8955
9299
  const scoreValue = await Promise.resolve(
8956
9300
  score(scoringArgs)
8957
9301
  );
8958
- if (scoreValue === null) return null;
8959
- if (Array.isArray(scoreValue)) {
8960
- for (const s of scoreValue) {
8961
- if (!(typeof s === "object" && !isEmpty(s))) {
8962
- throw new Error(
8963
- `When returning an array of scores, each score must be a non-empty object. Got: ${JSON.stringify(s)}`
8964
- );
8965
- }
8966
- }
8967
- }
8968
- const results = Array.isArray(scoreValue) ? scoreValue : typeof scoreValue === "object" && !isEmpty(scoreValue) ? [scoreValue] : [
8969
- {
8970
- name: scorerNames[score_idx],
8971
- score: scoreValue
8972
- }
8973
- ];
8974
- const { resultMetadata, scoresRecord } = buildSpanScores(results);
8975
- const resultOutput = results.length === 1 ? getOtherFields(results[0]) : results.reduce(
8976
- (prev, s) => mergeDicts(prev, {
8977
- [s.name]: getOtherFields(s)
8978
- }),
8979
- {}
9302
+ const prepared = _internalPrepareEvaluatorScore(
9303
+ scoreValue,
9304
+ scorerNames[score_idx]
8980
9305
  );
9306
+ if (prepared.results === null) return null;
8981
9307
  span.log({
8982
- output: resultOutput,
8983
- metadata: resultMetadata,
8984
- scores: scoresRecord
9308
+ output: prepared.output,
9309
+ metadata: prepared.metadata,
9310
+ scores: prepared.scores
8985
9311
  });
8986
- return results;
9312
+ return prepared.results;
8987
9313
  }
8988
9314
  )
8989
9315
  )
@@ -9000,24 +9326,16 @@ async function runEvaluatorInternal(experiment, evaluator, progressReporter, fil
9000
9326
  const classifierValue = await Promise.resolve(
9001
9327
  classifier(scoringArgs)
9002
9328
  );
9003
- if (classifierValue === null) return null;
9004
- const rawResults = (Array.isArray(classifierValue) ? classifierValue : [classifierValue]).map(
9005
- (result) => validateClassificationResult(
9006
- result,
9007
- classifierNames[idx]
9008
- )
9009
- );
9010
- const resultOutput = rawResults.length === 1 ? toClassificationItem(rawResults[0]) : rawResults.reduce(
9011
- (prev, r) => mergeDicts(prev, {
9012
- [r.name]: toClassificationItem(r)
9013
- }),
9014
- {}
9329
+ const prepared = _internalPrepareEvaluatorClassification(
9330
+ classifierValue,
9331
+ classifierNames[idx]
9015
9332
  );
9333
+ if (prepared.results === null) return null;
9016
9334
  span.log({
9017
- output: resultOutput,
9018
- metadata: buildSpanMetadata(rawResults)
9335
+ output: prepared.output,
9336
+ metadata: prepared.metadata
9019
9337
  });
9020
- return rawResults;
9338
+ return prepared.results;
9021
9339
  }
9022
9340
  )
9023
9341
  )
@@ -9246,7 +9564,7 @@ function accumulateScores(accumulator, scores) {
9246
9564
  }
9247
9565
  }
9248
9566
  function ensureScoreAccumulator(results) {
9249
- const accumulator = {};
9567
+ const accumulator = /* @__PURE__ */ Object.create(null);
9250
9568
  for (const result of results) {
9251
9569
  accumulateScores(accumulator, result.scores);
9252
9570
  }
@@ -9389,6 +9707,1156 @@ var defaultReporter = {
9389
9707
  }
9390
9708
  };
9391
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
+
9392
10860
  // src/agent-assertions.ts
9393
10861
  function agentAssertionScorer(callback, options = {}) {
9394
10862
  return async (args) => {
@@ -10321,6 +11789,8 @@ export {
10321
11789
  BRAINTRUST_PARENT_KEY,
10322
11790
  BaseAttachment,
10323
11791
  BaseExperiment,
11792
+ BatchScorer,
11793
+ BatchTask,
10324
11794
  BraintrustLangChainCallbackHandler,
10325
11795
  BraintrustMiddleware,
10326
11796
  BraintrustObservabilityExporter,
@@ -10334,6 +11804,8 @@ export {
10334
11804
  DEFAULT_MAX_REQUEST_SIZE,
10335
11805
  Dataset,
10336
11806
  DatasetPipeline,
11807
+ DurableEvalMemoryStore,
11808
+ DurableEvalRedisStore,
10337
11809
  ERR_PERMALINK,
10338
11810
  Eval,
10339
11811
  EvalResultWithSummary,
@@ -10382,6 +11854,7 @@ export {
10382
11854
  braintrustFlueObserver,
10383
11855
  braintrustStreamChunkSchema,
10384
11856
  buildLocalSummary,
11857
+ collectAnthropicSession,
10385
11858
  configureInstrumentation,
10386
11859
  constructLogs3OverflowRequest,
10387
11860
  createFinalValuePassThroughStream,
@@ -10391,6 +11864,7 @@ export {
10391
11864
  deepCopyEvent,
10392
11865
  exports_exports as default,
10393
11866
  defaultErrorScoreHandler,
11867
+ defineDurableEval,
10394
11868
  deserializePlainStringAsJSON,
10395
11869
  devNullWritableStream,
10396
11870
  evaluatorDefinitionSchema,
@@ -10462,6 +11936,7 @@ export {
10462
11936
  wrapClaudeAgentSDK,
10463
11937
  wrapCloudflareAIChat,
10464
11938
  wrapCloudflareAgent,
11939
+ wrapCloudflareThink,
10465
11940
  wrapCohere,
10466
11941
  wrapCopilotClient,
10467
11942
  wrapCursorSDK,
@@ -10476,6 +11951,7 @@ export {
10476
11951
  wrapLangSmithTraceable,
10477
11952
  wrapMastraAgent,
10478
11953
  wrapMistral,
11954
+ wrapOllama,
10479
11955
  wrapOpenAI,
10480
11956
  wrapOpenAICodexSDK,
10481
11957
  wrapOpenAIv4,
@@ -10484,5 +11960,6 @@ export {
10484
11960
  wrapPiCodingAgentSDK,
10485
11961
  wrapStrandsAgentSDK,
10486
11962
  wrapTraced,
10487
- wrapVitest
11963
+ wrapVitest,
11964
+ wrapVoyageAI
10488
11965
  };