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.
- package/dev/dist/index.d.mts +519 -186
- package/dev/dist/index.d.ts +519 -186
- package/dev/dist/index.js +1837 -1009
- package/dev/dist/index.mjs +1172 -344
- package/dist/apply-auto-instrumentation.js +262 -210
- package/dist/apply-auto-instrumentation.mjs +54 -2
- package/dist/auto-instrumentations/bundler/esbuild.cjs +81 -2
- package/dist/auto-instrumentations/bundler/esbuild.mjs +2 -2
- package/dist/auto-instrumentations/bundler/next.cjs +81 -2
- package/dist/auto-instrumentations/bundler/next.mjs +3 -3
- package/dist/auto-instrumentations/bundler/rollup.cjs +81 -2
- package/dist/auto-instrumentations/bundler/rollup.mjs +2 -2
- package/dist/auto-instrumentations/bundler/vite.cjs +81 -2
- package/dist/auto-instrumentations/bundler/vite.mjs +2 -2
- package/dist/auto-instrumentations/bundler/webpack-loader.cjs +81 -2
- package/dist/auto-instrumentations/bundler/webpack.cjs +81 -2
- package/dist/auto-instrumentations/bundler/webpack.mjs +3 -3
- package/dist/auto-instrumentations/{chunk-XEYKUBLY.mjs → chunk-26PKVUKB.mjs} +80 -2
- package/dist/auto-instrumentations/{chunk-BW33ULMW.mjs → chunk-HD35AM3M.mjs} +1 -1
- package/dist/auto-instrumentations/{chunk-ZNHTSSGI.mjs → chunk-NP7V4XB2.mjs} +2 -1
- package/dist/auto-instrumentations/hook.mjs +236 -30
- package/dist/auto-instrumentations/index.cjs +2 -1
- package/dist/auto-instrumentations/index.mjs +1 -1
- package/dist/browser.d.mts +628 -53
- package/dist/browser.d.ts +628 -53
- package/dist/browser.js +2301 -284
- package/dist/browser.mjs +2301 -284
- package/dist/{chunk-MF7NU6BT.js → chunk-BBE7SNRV.js} +34 -4
- package/dist/{chunk-QRHGVBKU.js → chunk-OBBWQW6K.js} +1799 -1023
- package/dist/{chunk-YKD22IMR.mjs → chunk-UPFNQCGB.mjs} +966 -190
- package/dist/{chunk-CZM5JIQL.mjs → chunk-ZHUHZWFY.mjs} +33 -3
- package/dist/cli.js +1224 -398
- package/dist/edge-light.d.mts +1 -1
- package/dist/edge-light.d.ts +1 -1
- package/dist/edge-light.js +2301 -284
- package/dist/edge-light.mjs +2301 -284
- package/dist/index.d.mts +1212 -637
- package/dist/index.d.ts +1212 -637
- package/dist/index.js +1880 -590
- package/dist/index.mjs +1450 -160
- package/dist/instrumentation/index.d.mts +190 -6
- package/dist/instrumentation/index.d.ts +190 -6
- package/dist/instrumentation/index.js +823 -116
- package/dist/instrumentation/index.mjs +823 -116
- package/dist/vitest-evals-reporter.js +16 -16
- package/dist/vitest-evals-reporter.mjs +2 -2
- package/dist/workerd.d.mts +1 -1
- package/dist/workerd.d.ts +1 -1
- package/dist/workerd.js +2301 -284
- package/dist/workerd.mjs +2301 -284
- package/package.json +2 -3
- package/util/dist/index.d.mts +1545 -100
- 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-
|
|
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-
|
|
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
|
-
|
|
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
|
|
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) &&
|
|
5831
|
+
return isRecord4(value) && isRecord4(value.completions) && hasFunction6(value.completions, "create");
|
|
5744
5832
|
}
|
|
5745
5833
|
function hasEmbeddings2(value) {
|
|
5746
|
-
return
|
|
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
|
|
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(
|
|
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
|
|
8720
|
-
|
|
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 :
|
|
8837
|
-
(
|
|
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 =
|
|
8843
|
-
(
|
|
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
|
-
|
|
8948
|
-
|
|
8949
|
-
|
|
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 = [
|
|
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
|
|
9071
|
-
|
|
9072
|
-
|
|
9073
|
-
|
|
9074
|
-
|
|
9075
|
-
|
|
9076
|
-
|
|
9077
|
-
|
|
9078
|
-
|
|
9079
|
-
|
|
9080
|
-
|
|
9081
|
-
|
|
9082
|
-
|
|
9083
|
-
|
|
9084
|
-
|
|
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
|
-
|
|
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
|
-
|
|
9143
|
-
|
|
9144
|
-
|
|
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:
|
|
9167
|
-
metadata:
|
|
9168
|
-
scores:
|
|
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
|
-
|
|
9188
|
-
|
|
9189
|
-
|
|
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:
|
|
9202
|
-
metadata:
|
|
9335
|
+
output: prepared.output,
|
|
9336
|
+
metadata: prepared.metadata
|
|
9203
9337
|
});
|
|
9204
|
-
return
|
|
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
|
};
|