@axlsdk/eval 0.25.3 → 0.26.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/README.md +5 -0
- package/dist/{chunk-F4MJ26YK.js → chunk-3XLPDV3O.js} +1 -1
- package/dist/chunk-3XLPDV3O.js.map +1 -0
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1 -1
- package/dist/index.cjs +421 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +43 -3
- package/dist/index.d.ts +43 -3
- package/dist/index.js +408 -3
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/dist/chunk-F4MJ26YK.js.map +0 -1
package/dist/cli.js
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -42,11 +42,15 @@ __export(index_exports, {
|
|
|
42
42
|
evaluateScorerTolerance: () => evaluateScorerTolerance,
|
|
43
43
|
isBudgetStopped: () => isBudgetStopped,
|
|
44
44
|
llmScorer: () => llmScorer,
|
|
45
|
+
loadModelCalls: () => loadModelCalls,
|
|
46
|
+
modelCallDataset: () => modelCallDataset,
|
|
45
47
|
normalizeScorerResult: () => normalizeScorerResult,
|
|
46
48
|
pairedBootstrapCI: () => pairedBootstrapCI,
|
|
47
49
|
parseRequestRecords: () => parseRequestRecords,
|
|
48
50
|
readAccounting: () => readAccounting,
|
|
51
|
+
recordedModelCall: () => recordedModelCall,
|
|
49
52
|
refusedWork: () => refusedWork,
|
|
53
|
+
replayModelCall: () => replayModelCall,
|
|
50
54
|
rescore: () => rescore,
|
|
51
55
|
resolveCaptureLimits: () => resolveCaptureLimits,
|
|
52
56
|
runEval: () => runEval,
|
|
@@ -150,6 +154,401 @@ function dataset(config) {
|
|
|
150
154
|
};
|
|
151
155
|
}
|
|
152
156
|
|
|
157
|
+
// src/model-calls.ts
|
|
158
|
+
var import_promises2 = require("fs/promises");
|
|
159
|
+
var import_zod = require("zod");
|
|
160
|
+
var import_axl = require("@axlsdk/axl");
|
|
161
|
+
function invalid(location, message) {
|
|
162
|
+
throw new import_axl.AxlError("INVALID_MODEL_CALL_SOURCE", `${location}: ${message}`);
|
|
163
|
+
}
|
|
164
|
+
function versionOf(value) {
|
|
165
|
+
return value !== null && typeof value === "object" ? Object.getOwnPropertyDescriptor(value, "v")?.value : void 0;
|
|
166
|
+
}
|
|
167
|
+
function parseJson(value, location) {
|
|
168
|
+
try {
|
|
169
|
+
return JSON.parse(value);
|
|
170
|
+
} catch {
|
|
171
|
+
return invalid(location, "invalid JSON");
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
function arrayLength(source, location) {
|
|
175
|
+
let array;
|
|
176
|
+
let descriptor;
|
|
177
|
+
try {
|
|
178
|
+
array = Array.isArray(source);
|
|
179
|
+
if (array) descriptor = Object.getOwnPropertyDescriptor(source, "length");
|
|
180
|
+
} catch {
|
|
181
|
+
return invalid(location, "array could not be inspected");
|
|
182
|
+
}
|
|
183
|
+
if (!array || !descriptor || !("value" in descriptor)) invalid(location, "expected an array");
|
|
184
|
+
const length = descriptor.value;
|
|
185
|
+
if (typeof length !== "number" || !Number.isSafeInteger(length) || length < 0)
|
|
186
|
+
invalid(location, "invalid array length");
|
|
187
|
+
return length;
|
|
188
|
+
}
|
|
189
|
+
function arraySlot(source, index, location) {
|
|
190
|
+
let descriptor;
|
|
191
|
+
try {
|
|
192
|
+
descriptor = Object.getOwnPropertyDescriptor(source, String(index));
|
|
193
|
+
} catch {
|
|
194
|
+
return invalid(location, "array entry could not be inspected");
|
|
195
|
+
}
|
|
196
|
+
if (!descriptor || !("value" in descriptor))
|
|
197
|
+
invalid(location, "array entry is missing or is an accessor");
|
|
198
|
+
return descriptor.value;
|
|
199
|
+
}
|
|
200
|
+
function* locateArray(source, at) {
|
|
201
|
+
const length = arrayLength(source, "source");
|
|
202
|
+
for (let index = 0; index < length; index++) {
|
|
203
|
+
const location = at(index);
|
|
204
|
+
yield { value: arraySlot(source, index, location), position: index, location };
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
async function loadModelCalls(source) {
|
|
208
|
+
let located;
|
|
209
|
+
if (typeof source === "string") {
|
|
210
|
+
const content = (await (0, import_promises2.readFile)(source, "utf8")).replace(/^\uFEFF/, "");
|
|
211
|
+
if (content.trimStart().startsWith("[")) {
|
|
212
|
+
const parsed = parseJson(content, `file ${source}`);
|
|
213
|
+
if (!Array.isArray(parsed)) invalid(`file ${source}`, "expected a JSON array");
|
|
214
|
+
located = locateArray(parsed, (index) => `entry ${index + 1} of ${source}`);
|
|
215
|
+
} else {
|
|
216
|
+
const rows = content.split(/\r?\n/);
|
|
217
|
+
if (content.endsWith("\n")) rows.pop();
|
|
218
|
+
located = rows.flatMap((row, index) => {
|
|
219
|
+
const location = `line ${index + 1} of ${source}`;
|
|
220
|
+
if (row.trim() === "") return [];
|
|
221
|
+
return [{ value: parseJson(row, location), position: index, location }];
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
} else {
|
|
225
|
+
located = locateArray(source, (index) => `item ${index + 1}`);
|
|
226
|
+
}
|
|
227
|
+
const journal = [];
|
|
228
|
+
const folded = [];
|
|
229
|
+
const firstJournal = /* @__PURE__ */ new Map();
|
|
230
|
+
let format;
|
|
231
|
+
for (const entry of located) {
|
|
232
|
+
let version;
|
|
233
|
+
try {
|
|
234
|
+
version = versionOf(entry.value);
|
|
235
|
+
} catch {
|
|
236
|
+
invalid(entry.location, "record version could not be inspected");
|
|
237
|
+
}
|
|
238
|
+
if (version === 1 || version === 2) {
|
|
239
|
+
if (format !== void 0 && format !== version)
|
|
240
|
+
invalid(entry.location, "one source cannot mix v1 journal lines with v2 records");
|
|
241
|
+
format = version;
|
|
242
|
+
}
|
|
243
|
+
if (version === 1) {
|
|
244
|
+
const parsed = import_axl.capturedJournalLineSchema.safeParse(entry.value);
|
|
245
|
+
if (!parsed.success) invalid(entry.location, parsed.error.message);
|
|
246
|
+
journal.push({ ...entry, value: parsed.data, operationId: parsed.data.operationId });
|
|
247
|
+
if (!firstJournal.has(parsed.data.operationId))
|
|
248
|
+
firstJournal.set(parsed.data.operationId, entry);
|
|
249
|
+
} else if (version === 2) {
|
|
250
|
+
const parsed = import_axl.foldedOperationRecordSchema.safeParse(entry.value);
|
|
251
|
+
if (!parsed.success) invalid(entry.location, parsed.error.message);
|
|
252
|
+
folded.push({ record: parsed.data, location: entry.location, position: entry.position });
|
|
253
|
+
} else {
|
|
254
|
+
invalid(entry.location, "unsupported capture version");
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
if (journal.length) {
|
|
258
|
+
let records;
|
|
259
|
+
try {
|
|
260
|
+
records = (0, import_axl.foldJournal)(journal.map((entry) => entry.value));
|
|
261
|
+
} catch (error) {
|
|
262
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
263
|
+
const line = /\bat line (\d+)\b/.exec(message) ?? /^Invalid capture journal line (\d+):/.exec(message);
|
|
264
|
+
const indexed = line ? journal[Number(line[1]) - 1] : void 0;
|
|
265
|
+
const matched = indexed ?? [...journal].reverse().find((entry) => {
|
|
266
|
+
const marker = `capture operation ${entry.operationId}`;
|
|
267
|
+
const at = message.indexOf(marker);
|
|
268
|
+
if (at < 0) return false;
|
|
269
|
+
const suffix = message.slice(at + marker.length);
|
|
270
|
+
return suffix === "" || suffix.startsWith(":") || suffix.startsWith(" at line");
|
|
271
|
+
});
|
|
272
|
+
invalid(matched?.location ?? journal[0].location, message);
|
|
273
|
+
}
|
|
274
|
+
for (const record of records) {
|
|
275
|
+
const first = firstJournal.get(record.id);
|
|
276
|
+
folded.push({ record, location: first.location, position: first.position });
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
folded.sort((a, b) => a.position - b.position);
|
|
280
|
+
const seen = /* @__PURE__ */ new Map();
|
|
281
|
+
for (const item of folded) {
|
|
282
|
+
const previous = seen.get(item.record.id);
|
|
283
|
+
if (previous)
|
|
284
|
+
invalid(item.location, `duplicate operation id ${item.record.id} (first at ${previous})`);
|
|
285
|
+
seen.set(item.record.id, item.location);
|
|
286
|
+
}
|
|
287
|
+
return folded.map((item) => item.record);
|
|
288
|
+
}
|
|
289
|
+
var modelCallInputSchema = import_zod.z.object({ recorded: import_axl.modelCallRecordSchema }).transform(
|
|
290
|
+
({ recorded }) => ({
|
|
291
|
+
...recorded.request ? { request: recorded.request } : {},
|
|
292
|
+
ids: recorded.ids,
|
|
293
|
+
attributes: recorded.attributes,
|
|
294
|
+
recorded
|
|
295
|
+
})
|
|
296
|
+
);
|
|
297
|
+
function modelCallDataset(records, options) {
|
|
298
|
+
const items = [];
|
|
299
|
+
const length = arrayLength(records, "records");
|
|
300
|
+
for (let index = 0; index < length; index++) {
|
|
301
|
+
const parsed = import_axl.foldedOperationRecordSchema.safeParse(
|
|
302
|
+
arraySlot(records, index, `record ${index + 1}`)
|
|
303
|
+
);
|
|
304
|
+
if (!parsed.success) invalid(`record ${index + 1}`, parsed.error.message);
|
|
305
|
+
const record = parsed.data;
|
|
306
|
+
if (options?.filter && !options.filter(record)) continue;
|
|
307
|
+
if (record.kind === "external")
|
|
308
|
+
throw new import_axl.AxlError(
|
|
309
|
+
"MODEL_CALL_EXPECTED",
|
|
310
|
+
`record ${index + 1} (${record.id}) is external; supply a filter to exclude it`
|
|
311
|
+
);
|
|
312
|
+
const input = {
|
|
313
|
+
...record.request ? { request: record.request } : {},
|
|
314
|
+
ids: record.ids,
|
|
315
|
+
attributes: record.attributes,
|
|
316
|
+
recorded: record
|
|
317
|
+
};
|
|
318
|
+
items.push({
|
|
319
|
+
input,
|
|
320
|
+
...options?.annotate ? { annotations: options.annotate(record, index) } : {}
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
return dataset({
|
|
324
|
+
name: "model-calls",
|
|
325
|
+
schema: modelCallInputSchema,
|
|
326
|
+
annotations: import_zod.z.custom(),
|
|
327
|
+
items
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
function readInput(input) {
|
|
331
|
+
const candidate = input !== null && typeof input === "object" ? Object.getOwnPropertyDescriptor(input, "recorded")?.value : void 0;
|
|
332
|
+
if (candidate !== null && typeof candidate === "object" && Object.getOwnPropertyDescriptor(candidate, "kind")?.value === "external")
|
|
333
|
+
throw new import_axl.AxlError(
|
|
334
|
+
"MODEL_CALL_EXPECTED",
|
|
335
|
+
"external operations cannot be scored or replayed as model calls"
|
|
336
|
+
);
|
|
337
|
+
const parsed = import_axl.modelCallRecordSchema.safeParse(candidate);
|
|
338
|
+
if (!parsed.success)
|
|
339
|
+
throw new import_axl.AxlError(
|
|
340
|
+
"INVALID_MODEL_CALL_INPUT",
|
|
341
|
+
`expected a validated model-call input: ${parsed.error.message}`
|
|
342
|
+
);
|
|
343
|
+
return parsed.data;
|
|
344
|
+
}
|
|
345
|
+
function modelCallOutput(response, record) {
|
|
346
|
+
const output = {
|
|
347
|
+
content: response.content,
|
|
348
|
+
...response.thinking ? { thinking: response.thinking } : {},
|
|
349
|
+
...response.tool_calls?.length ? { tool_calls: response.tool_calls } : {},
|
|
350
|
+
...response.finishReason !== void 0 ? { finishReason: response.finishReason } : {},
|
|
351
|
+
...response.rawFinishReason !== void 0 ? { rawFinishReason: response.rawFinishReason } : {},
|
|
352
|
+
...response.responseId !== void 0 ? { responseId: response.responseId } : {},
|
|
353
|
+
...response.responseModel !== void 0 ? { responseModel: response.responseModel } : {}
|
|
354
|
+
};
|
|
355
|
+
const format = record.request?.responseFormat?.type;
|
|
356
|
+
if (format !== "json_object" && format !== "json_schema") return output;
|
|
357
|
+
if (output.content === "" && output.tool_calls) return output;
|
|
358
|
+
try {
|
|
359
|
+
return { ...output, json: JSON.parse(output.content) };
|
|
360
|
+
} catch {
|
|
361
|
+
throw new import_axl.AxlError(
|
|
362
|
+
"INVALID_MODEL_CALL_JSON",
|
|
363
|
+
`model call ${record.id} did not return valid JSON`
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
function providerOutput(response, record) {
|
|
368
|
+
return modelCallOutput(
|
|
369
|
+
{
|
|
370
|
+
content: response.content,
|
|
371
|
+
...response.thinking_content !== void 0 ? { thinking: response.thinking_content } : {},
|
|
372
|
+
...response.tool_calls !== void 0 ? { tool_calls: response.tool_calls } : {},
|
|
373
|
+
...response.finishReason !== void 0 ? { finishReason: response.finishReason } : {},
|
|
374
|
+
...response.rawFinishReason !== void 0 ? { rawFinishReason: response.rawFinishReason } : {},
|
|
375
|
+
...response.responseId !== void 0 ? { responseId: response.responseId } : {},
|
|
376
|
+
...response.responseModel !== void 0 ? { responseModel: response.responseModel } : {}
|
|
377
|
+
},
|
|
378
|
+
record
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
function recordedModelCall() {
|
|
382
|
+
return async (input) => {
|
|
383
|
+
const record = readInput(input);
|
|
384
|
+
if (record.captured.redacted)
|
|
385
|
+
throw new import_axl.AxlError("RECORDED_OUTPUT_UNAVAILABLE", `model call ${record.id} was redacted`);
|
|
386
|
+
if (record.outcome.status !== "ok" || !("response" in record.outcome))
|
|
387
|
+
throw new import_axl.AxlError(
|
|
388
|
+
"RECORDED_OUTPUT_UNAVAILABLE",
|
|
389
|
+
`model call ${record.id} has no complete successful output (${record.outcome.status})`
|
|
390
|
+
);
|
|
391
|
+
return { output: modelCallOutput(record.outcome.response, record) };
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
function replayRequest(record) {
|
|
395
|
+
const request = record.request;
|
|
396
|
+
if (!request || !record.captured.replayable || record.captured.continuityCaptured !== true || record.captured.redacted || record.captured.truncated || record.captured.omitted.length > 0 || (request.providerOptionKeys?.length ?? 0) > 0)
|
|
397
|
+
throw new import_axl.AxlError(
|
|
398
|
+
"MODEL_CALL_NOT_REPLAYABLE",
|
|
399
|
+
`model call ${record.id} does not contain an exact replayable request`
|
|
400
|
+
);
|
|
401
|
+
return request;
|
|
402
|
+
}
|
|
403
|
+
function chatOptions(request, model) {
|
|
404
|
+
if (!request.options.model)
|
|
405
|
+
throw new import_axl.AxlError("INVALID_MODEL_CALL_INPUT", "captured request model is empty");
|
|
406
|
+
const effort = request.options.effort;
|
|
407
|
+
if (effort !== void 0 && !["none", "low", "medium", "high", "xhigh", "max"].includes(effort))
|
|
408
|
+
throw new import_axl.AxlError("INVALID_MODEL_CALL_INPUT", `unsupported captured effort ${effort}`);
|
|
409
|
+
const toolChoice = request.options.toolChoice;
|
|
410
|
+
if (toolChoice !== void 0 && toolChoice !== "auto" && toolChoice !== "none" && toolChoice !== "required" && !(toolChoice !== null && typeof toolChoice === "object" && !Array.isArray(toolChoice) && toolChoice.type === "function" && Object.keys(toolChoice).every((key) => key === "type" || key === "function") && toolChoice.function !== null && typeof toolChoice.function === "object" && !Array.isArray(toolChoice.function) && typeof toolChoice.function.name === "string" && Object.keys(toolChoice.function).every((key) => key === "name")))
|
|
411
|
+
throw new import_axl.AxlError("INVALID_MODEL_CALL_INPUT", "unsupported captured tool choice");
|
|
412
|
+
return {
|
|
413
|
+
model,
|
|
414
|
+
...request.options.temperature !== void 0 ? { temperature: request.options.temperature } : {},
|
|
415
|
+
...request.options.maxTokens !== void 0 ? { maxTokens: request.options.maxTokens } : {},
|
|
416
|
+
...request.options.thinkingBudget !== void 0 ? { thinkingBudget: request.options.thinkingBudget } : {},
|
|
417
|
+
...request.options.includeThoughts !== void 0 ? { includeThoughts: request.options.includeThoughts } : {},
|
|
418
|
+
...request.options.stop !== void 0 ? { stop: [...request.options.stop] } : {},
|
|
419
|
+
...request.options.promptCache !== void 0 ? { promptCache: request.options.promptCache } : {},
|
|
420
|
+
...effort !== void 0 ? { effort } : {},
|
|
421
|
+
...toolChoice !== void 0 ? { toolChoice } : {},
|
|
422
|
+
...request.tools ? {
|
|
423
|
+
tools: request.tools.map((tool) => ({
|
|
424
|
+
type: "function",
|
|
425
|
+
function: {
|
|
426
|
+
name: tool.name,
|
|
427
|
+
description: tool.description ?? "",
|
|
428
|
+
parameters: tool.parameters,
|
|
429
|
+
...tool.strict !== void 0 ? { strict: tool.strict } : {}
|
|
430
|
+
}
|
|
431
|
+
}))
|
|
432
|
+
} : {},
|
|
433
|
+
...request.responseFormat ? { responseFormat: request.responseFormat } : {}
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
function chatMessages(request) {
|
|
437
|
+
return request.messages.map((message) => {
|
|
438
|
+
if (!["system", "user", "assistant", "tool"].includes(message.role))
|
|
439
|
+
throw new import_axl.AxlError("MODEL_CALL_NOT_REPLAYABLE", "captured message cannot be reconstructed");
|
|
440
|
+
let content;
|
|
441
|
+
if (message.input) {
|
|
442
|
+
const text = message.content ?? "";
|
|
443
|
+
let offset = 0;
|
|
444
|
+
const parts = [];
|
|
445
|
+
for (const part of message.input.parts) {
|
|
446
|
+
if (part.type !== "text" || offset + part.characters > text.length)
|
|
447
|
+
throw new import_axl.AxlError(
|
|
448
|
+
"MODEL_CALL_NOT_REPLAYABLE",
|
|
449
|
+
"captured content parts cannot be reconstructed"
|
|
450
|
+
);
|
|
451
|
+
parts.push({ type: "text", text: text.slice(offset, offset + part.characters) });
|
|
452
|
+
offset += part.characters;
|
|
453
|
+
}
|
|
454
|
+
if (offset !== text.length)
|
|
455
|
+
throw new import_axl.AxlError(
|
|
456
|
+
"MODEL_CALL_NOT_REPLAYABLE",
|
|
457
|
+
"captured text lengths do not match content"
|
|
458
|
+
);
|
|
459
|
+
content = parts;
|
|
460
|
+
} else if (message.content !== null) {
|
|
461
|
+
content = message.content;
|
|
462
|
+
} else {
|
|
463
|
+
throw new import_axl.AxlError("MODEL_CALL_NOT_REPLAYABLE", "captured message content is absent");
|
|
464
|
+
}
|
|
465
|
+
return {
|
|
466
|
+
role: message.role,
|
|
467
|
+
content,
|
|
468
|
+
...message.name !== void 0 ? { name: message.name } : {},
|
|
469
|
+
...message.agent !== void 0 ? { agent: message.agent } : {},
|
|
470
|
+
...message.origin !== void 0 ? { origin: message.origin } : {},
|
|
471
|
+
...message.tool_calls !== void 0 ? { tool_calls: message.tool_calls } : {},
|
|
472
|
+
...message.tool_call_id !== void 0 ? { tool_call_id: message.tool_call_id } : {},
|
|
473
|
+
...message.providerMetadata !== void 0 ? { providerMetadata: message.providerMetadata } : {}
|
|
474
|
+
};
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
function replayModelCall(options = {}) {
|
|
478
|
+
return async (input, runtime) => {
|
|
479
|
+
const record = readInput(input);
|
|
480
|
+
const request = replayRequest(record);
|
|
481
|
+
const messages = chatMessages(request);
|
|
482
|
+
const capturedOptions = chatOptions(request, request.options.model);
|
|
483
|
+
if (!runtime)
|
|
484
|
+
throw new import_axl.AxlError("MODEL_CALL_RUNTIME_REQUIRED", "replay requires an AxlRuntime");
|
|
485
|
+
const uri = options.model ?? `${record.provider}:${request.options.model}`;
|
|
486
|
+
if (!uri.includes(":"))
|
|
487
|
+
throw new import_axl.AxlError("MODEL_CALL_MODEL_UNRESOLVED", "model override must use provider:model");
|
|
488
|
+
let resolved;
|
|
489
|
+
try {
|
|
490
|
+
resolved = runtime.resolveProvider(uri);
|
|
491
|
+
} catch {
|
|
492
|
+
throw new import_axl.AxlError(
|
|
493
|
+
"MODEL_CALL_MODEL_UNRESOLVED",
|
|
494
|
+
`cannot resolve ${uri}; supply an explicit provider:model override`
|
|
495
|
+
);
|
|
496
|
+
}
|
|
497
|
+
if (!options.model && resolved.provider.name !== record.provider)
|
|
498
|
+
throw new import_axl.AxlError(
|
|
499
|
+
"MODEL_CALL_MODEL_UNRESOLVED",
|
|
500
|
+
`resolved ${uri} to a different provider; supply an explicit override`
|
|
501
|
+
);
|
|
502
|
+
const callOptions = {
|
|
503
|
+
...capturedOptions,
|
|
504
|
+
model: options.model ? resolved.model : request.options.model
|
|
505
|
+
};
|
|
506
|
+
if (record.kind === "chat") {
|
|
507
|
+
const response = await resolved.provider.chat(messages, callOptions);
|
|
508
|
+
return { output: providerOutput(response, record) };
|
|
509
|
+
}
|
|
510
|
+
let content = "";
|
|
511
|
+
let thinking = "";
|
|
512
|
+
const calls = /* @__PURE__ */ new Map();
|
|
513
|
+
let done;
|
|
514
|
+
for await (const chunk of resolved.provider.stream(messages, callOptions)) {
|
|
515
|
+
if (chunk.type === "text_delta") content += chunk.content;
|
|
516
|
+
else if (chunk.type === "thinking_delta") thinking += chunk.content;
|
|
517
|
+
else if (chunk.type === "tool_call_delta") {
|
|
518
|
+
const call = calls.get(chunk.id) ?? { id: chunk.id, name: "", arguments: "" };
|
|
519
|
+
if (chunk.name) call.name = chunk.name;
|
|
520
|
+
if (chunk.arguments) call.arguments += chunk.arguments;
|
|
521
|
+
calls.set(chunk.id, call);
|
|
522
|
+
} else if (chunk.type === "done") {
|
|
523
|
+
done = chunk;
|
|
524
|
+
break;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
if (!done)
|
|
528
|
+
throw new import_axl.AxlError("MODEL_CALL_STREAM_INCOMPLETE", "replayed stream ended without done");
|
|
529
|
+
return {
|
|
530
|
+
output: providerOutput(
|
|
531
|
+
{
|
|
532
|
+
content,
|
|
533
|
+
...thinking ? { thinking_content: thinking } : {},
|
|
534
|
+
...calls.size ? {
|
|
535
|
+
tool_calls: [...calls.values()].map((call) => ({
|
|
536
|
+
id: call.id,
|
|
537
|
+
type: "function",
|
|
538
|
+
function: { name: call.name, arguments: call.arguments }
|
|
539
|
+
}))
|
|
540
|
+
} : {},
|
|
541
|
+
...done.finishReason !== void 0 ? { finishReason: done.finishReason } : {},
|
|
542
|
+
...done.rawFinishReason !== void 0 ? { rawFinishReason: done.rawFinishReason } : {},
|
|
543
|
+
...done.responseId !== void 0 ? { responseId: done.responseId } : {},
|
|
544
|
+
...done.responseModel !== void 0 ? { responseModel: done.responseModel } : {}
|
|
545
|
+
},
|
|
546
|
+
record
|
|
547
|
+
)
|
|
548
|
+
};
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
|
|
153
552
|
// src/scorer.ts
|
|
154
553
|
function normalizeScorerResult(result) {
|
|
155
554
|
return typeof result === "number" ? { score: result } : result;
|
|
@@ -174,16 +573,16 @@ function scorer(config) {
|
|
|
174
573
|
}
|
|
175
574
|
|
|
176
575
|
// src/llm-scorer.ts
|
|
177
|
-
var
|
|
178
|
-
var
|
|
576
|
+
var import_zod2 = require("zod");
|
|
577
|
+
var import_axl2 = require("@axlsdk/axl");
|
|
179
578
|
function isZodIssueLike(value) {
|
|
180
579
|
if (!value || typeof value !== "object") return false;
|
|
181
580
|
const issue = value;
|
|
182
581
|
return Array.isArray(issue.path) && issue.path.every((segment) => typeof segment === "string" || typeof segment === "number") && typeof issue.message === "string";
|
|
183
582
|
}
|
|
184
583
|
function llmScorer(config) {
|
|
185
|
-
const schema = config.schema ??
|
|
186
|
-
const schemaJson = JSON.stringify((0,
|
|
584
|
+
const schema = config.schema ?? import_zod2.z.object({ score: import_zod2.z.number().min(0).max(1), reasoning: import_zod2.z.string() });
|
|
585
|
+
const schemaJson = JSON.stringify((0, import_axl2.zodToJsonSchema)(schema), null, 2);
|
|
187
586
|
return {
|
|
188
587
|
name: config.name,
|
|
189
588
|
description: config.description,
|
|
@@ -232,7 +631,7 @@ ${JSON.stringify(annotations, null, 2)}` : "",
|
|
|
232
631
|
const responseCost = response.cost;
|
|
233
632
|
let validated;
|
|
234
633
|
try {
|
|
235
|
-
const parsed = JSON.parse((0,
|
|
634
|
+
const parsed = JSON.parse((0, import_axl2.extractJson)(response.content));
|
|
236
635
|
validated = schema.parse(parsed);
|
|
237
636
|
} catch (err) {
|
|
238
637
|
attachScorerErrorCost(err, responseCost);
|
|
@@ -263,7 +662,7 @@ function defineEval(config) {
|
|
|
263
662
|
}
|
|
264
663
|
|
|
265
664
|
// src/runner.ts
|
|
266
|
-
var
|
|
665
|
+
var import_axl4 = require("@axlsdk/axl");
|
|
267
666
|
|
|
268
667
|
// src/utils.ts
|
|
269
668
|
function computeStats(scores) {
|
|
@@ -333,7 +732,7 @@ async function mapWithConcurrency(items, concurrency, task) {
|
|
|
333
732
|
}
|
|
334
733
|
|
|
335
734
|
// src/accounting.ts
|
|
336
|
-
var
|
|
735
|
+
var import_axl3 = require("@axlsdk/axl");
|
|
337
736
|
var USAGE_KEYS = [
|
|
338
737
|
"inputTokens",
|
|
339
738
|
"outputTokens",
|
|
@@ -395,14 +794,14 @@ async function trackScope(runtime, fn, options) {
|
|
|
395
794
|
function parseBudget(budget) {
|
|
396
795
|
const match = typeof budget === "string" ? budget.match(/^\$?(\d+(?:\.\d+)?)$/) : null;
|
|
397
796
|
if (!match) {
|
|
398
|
-
throw new
|
|
797
|
+
throw new import_axl3.AxlError(
|
|
399
798
|
"INVALID_BUDGET",
|
|
400
799
|
`Invalid budget "${String(budget)}": expected a non-negative USD amount like "$1" or "0.50".`
|
|
401
800
|
);
|
|
402
801
|
}
|
|
403
802
|
const limit = Number(match[1]);
|
|
404
803
|
if (!Number.isFinite(limit)) {
|
|
405
|
-
throw new
|
|
804
|
+
throw new import_axl3.AxlError("INVALID_BUDGET", `Invalid budget "${budget}": not a finite amount.`);
|
|
406
805
|
}
|
|
407
806
|
return limit;
|
|
408
807
|
}
|
|
@@ -410,7 +809,7 @@ function usableCost(value) {
|
|
|
410
809
|
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : 0;
|
|
411
810
|
}
|
|
412
811
|
function isAdmissionDenied(err) {
|
|
413
|
-
return (0,
|
|
812
|
+
return (0, import_axl3.isAdmissionDeniedError)(err);
|
|
414
813
|
}
|
|
415
814
|
function readAccounting(result) {
|
|
416
815
|
if (result?.accounting) return result.accounting;
|
|
@@ -750,7 +1149,7 @@ async function stageCapture(runtime, ownerId, option) {
|
|
|
750
1149
|
const staged = await runtime.stageDiagnosticArtifact({ kind: "eval", id: ownerId });
|
|
751
1150
|
return {
|
|
752
1151
|
artifactId: staged.artifactId,
|
|
753
|
-
channel: new
|
|
1152
|
+
channel: new import_axl4.RequestCaptureChannel({
|
|
754
1153
|
sink: staged.sink,
|
|
755
1154
|
...limits,
|
|
756
1155
|
// Redaction is the RUNTIME's policy, not the caller's: a run cannot opt
|
|
@@ -818,7 +1217,7 @@ function attachOperationRefs(items, channel) {
|
|
|
818
1217
|
}
|
|
819
1218
|
var MAX_CAUSE_DEPTH = 8;
|
|
820
1219
|
function isProviderErrorLike(value) {
|
|
821
|
-
if (value instanceof
|
|
1220
|
+
if (value instanceof import_axl4.ProviderError) return true;
|
|
822
1221
|
if (typeof value !== "object" || value === null) return false;
|
|
823
1222
|
const candidate = value;
|
|
824
1223
|
return candidate.code === "PROVIDER_ERROR" && candidate.name === "ProviderError";
|
|
@@ -826,7 +1225,7 @@ function isProviderErrorLike(value) {
|
|
|
826
1225
|
function isTimeoutErrorLike(value) {
|
|
827
1226
|
if (typeof value !== "object" || value === null) return false;
|
|
828
1227
|
const candidate = value;
|
|
829
|
-
return candidate.name === "TimeoutError" && (value instanceof
|
|
1228
|
+
return candidate.name === "TimeoutError" && (value instanceof import_axl4.TimeoutError || candidate.code === "TIMEOUT");
|
|
830
1229
|
}
|
|
831
1230
|
function describeTimeoutFailure(error) {
|
|
832
1231
|
const breakdown = error.breakdown;
|
|
@@ -872,12 +1271,12 @@ async function runEval(config, executeWorkflow, runtime, options) {
|
|
|
872
1271
|
const id = (0, import_node_crypto.randomUUID)();
|
|
873
1272
|
const itemErrorRateLimit = config.failOnItemErrorRate ?? DEFAULT_ITEM_ERROR_RATE_LIMIT;
|
|
874
1273
|
if (!isErrorRateLimit(itemErrorRateLimit)) {
|
|
875
|
-
throw new
|
|
1274
|
+
throw new import_axl4.AxlError(
|
|
876
1275
|
"INVALID_ITEM_ERROR_RATE",
|
|
877
1276
|
`Invalid failOnItemErrorRate (${String(itemErrorRateLimit)}): expected a number in [0, 1]; 1 disables the gate.`
|
|
878
1277
|
);
|
|
879
1278
|
}
|
|
880
|
-
const admission = config.budget != null ? new
|
|
1279
|
+
const admission = config.budget != null ? new import_axl4.AdmissionController({ limit: parseBudget(config.budget) }) : void 0;
|
|
881
1280
|
const capture = await stageCapture(runtime, id, options?.captureRequests);
|
|
882
1281
|
let items;
|
|
883
1282
|
try {
|
|
@@ -1672,7 +2071,7 @@ function evaluateItemErrorRateGate(baseline, candidate, maxItemErrorRate = DEFAU
|
|
|
1672
2071
|
}
|
|
1673
2072
|
|
|
1674
2073
|
// src/rescore.ts
|
|
1675
|
-
var
|
|
2074
|
+
var import_axl5 = require("@axlsdk/axl");
|
|
1676
2075
|
var import_node_crypto2 = require("crypto");
|
|
1677
2076
|
var COPY_SHARE_OF_RUN_BOUND = 0.75;
|
|
1678
2077
|
function describeFailure(error) {
|
|
@@ -1686,7 +2085,7 @@ async function beginCapture(source, ownerId, runtime, options) {
|
|
|
1686
2085
|
if (!limits) return {};
|
|
1687
2086
|
const open = (artifactId, sink, carriedBytes) => ({
|
|
1688
2087
|
artifactId,
|
|
1689
|
-
channel: new
|
|
2088
|
+
channel: new import_axl5.RequestCaptureChannel({
|
|
1690
2089
|
sink,
|
|
1691
2090
|
...limits,
|
|
1692
2091
|
carriedBytes,
|
|
@@ -1769,7 +2168,7 @@ async function rescore(result, scorers, runtime, options) {
|
|
|
1769
2168
|
const rescoredId = (0, import_node_crypto2.randomUUID)();
|
|
1770
2169
|
const concurrency = options?.concurrency ?? 5;
|
|
1771
2170
|
const scorerConcurrency = options?.scorerConcurrency ?? 5;
|
|
1772
|
-
const admission = options?.budget != null ? new
|
|
2171
|
+
const admission = options?.budget != null ? new import_axl5.AdmissionController({ limit: parseBudget(options.budget) }) : void 0;
|
|
1773
2172
|
let closedBy;
|
|
1774
2173
|
const noteClosure = (source) => {
|
|
1775
2174
|
if (closedBy === void 0 && admission?.closed) closedBy = source;
|
|
@@ -2045,11 +2444,15 @@ function aggregateRuns(runs) {
|
|
|
2045
2444
|
evaluateScorerTolerance,
|
|
2046
2445
|
isBudgetStopped,
|
|
2047
2446
|
llmScorer,
|
|
2447
|
+
loadModelCalls,
|
|
2448
|
+
modelCallDataset,
|
|
2048
2449
|
normalizeScorerResult,
|
|
2049
2450
|
pairedBootstrapCI,
|
|
2050
2451
|
parseRequestRecords,
|
|
2051
2452
|
readAccounting,
|
|
2453
|
+
recordedModelCall,
|
|
2052
2454
|
refusedWork,
|
|
2455
|
+
replayModelCall,
|
|
2053
2456
|
rescore,
|
|
2054
2457
|
resolveCaptureLimits,
|
|
2055
2458
|
runEval,
|