@theokit/sdk 4.4.2 → 4.5.1

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/dist/eval.d.cts CHANGED
@@ -31,6 +31,7 @@ export declare class Eval {
31
31
  */
32
32
  run(runOpts?: EvalRunOptions): Promise<EvalRun>;
33
33
  }
34
+ export { assertEval, EvalThresholdError } from "./internal/eval/assert.js";
34
35
  export { captureArtifact } from "./internal/eval/code-runner.js";
35
36
  export { EvalAlreadyRunningError } from "./internal/eval/single-flight.js";
36
37
  export { JsonlParseError, loadJsonl } from "./internal/persistence/jsonl.js";
package/dist/eval.d.ts CHANGED
@@ -31,6 +31,7 @@ export declare class Eval {
31
31
  */
32
32
  run(runOpts?: EvalRunOptions): Promise<EvalRun>;
33
33
  }
34
+ export { assertEval, EvalThresholdError } from "./internal/eval/assert.js";
34
35
  export { captureArtifact } from "./internal/eval/code-runner.js";
35
36
  export { EvalAlreadyRunningError } from "./internal/eval/single-flight.js";
36
37
  export { JsonlParseError, loadJsonl } from "./internal/persistence/jsonl.js";
package/dist/eval.js CHANGED
@@ -7253,11 +7253,11 @@ function truncateToBudget(sources, maxTokens) {
7253
7253
  if (total <= maxTokens) {
7254
7254
  return sources.map((src) => ({ name: src.name, tokens: src.tokens }));
7255
7255
  }
7256
- const floor = Math.min(MIN_SOURCE_TOKENS, Math.floor(maxTokens / sources.length));
7257
- const remaining = Math.max(0, maxTokens - floor * sources.length);
7256
+ const floor2 = Math.min(MIN_SOURCE_TOKENS, Math.floor(maxTokens / sources.length));
7257
+ const remaining = Math.max(0, maxTokens - floor2 * sources.length);
7258
7258
  return sources.map((src) => {
7259
7259
  const proportional = total === 0 ? 0 : Math.floor(src.tokens.length / total * remaining);
7260
- const allotted = Math.min(src.tokens.length, floor + proportional);
7260
+ const allotted = Math.min(src.tokens.length, floor2 + proportional);
7261
7261
  return { name: src.name, tokens: src.tokens.slice(0, allotted) };
7262
7262
  });
7263
7263
  }
@@ -18245,6 +18245,87 @@ function startEvalRunSpan(attrs) {
18245
18245
  };
18246
18246
  }
18247
18247
 
18248
+ // src/internal/eval/trials.ts
18249
+ var TRIAL_INDEX_KEY = "__evalRowIndex";
18250
+ var TRIAL_NUM_KEY = "__evalTrial";
18251
+ function expandForTrials(entries, trials) {
18252
+ const out = [];
18253
+ for (let i = 0; i < entries.length; i += 1) {
18254
+ const entry = entries[i];
18255
+ if (entry === void 0) continue;
18256
+ for (let t = 0; t < trials; t += 1) {
18257
+ out.push({
18258
+ ...entry,
18259
+ metadata: { ...entry.metadata ?? {}, [TRIAL_INDEX_KEY]: i, [TRIAL_NUM_KEY]: t }
18260
+ });
18261
+ }
18262
+ }
18263
+ return out;
18264
+ }
18265
+ function stripReserved(metadata) {
18266
+ if (metadata === void 0) return void 0;
18267
+ const { [TRIAL_INDEX_KEY]: _idx, [TRIAL_NUM_KEY]: _trial, ...rest } = metadata;
18268
+ return Object.keys(rest).length > 0 ? rest : void 0;
18269
+ }
18270
+ function sumDefined(values) {
18271
+ const present = values.filter((v) => typeof v === "number");
18272
+ return present.length > 0 ? present.reduce((acc, v) => acc + v, 0) : void 0;
18273
+ }
18274
+ function meanScoresByScorer(group, trials) {
18275
+ const names = [];
18276
+ const sums = /* @__PURE__ */ new Map();
18277
+ for (const row of group) {
18278
+ for (const s of row.scores) {
18279
+ if (!sums.has(s.name)) {
18280
+ names.push(s.name);
18281
+ sums.set(s.name, 0);
18282
+ }
18283
+ sums.set(s.name, (sums.get(s.name) ?? 0) + s.score);
18284
+ }
18285
+ }
18286
+ return names.map((name) => ({
18287
+ name,
18288
+ score: (sums.get(name) ?? 0) / trials,
18289
+ reason: `mean of ${trials} trials`
18290
+ }));
18291
+ }
18292
+ function collapseGroup(index, group, trials) {
18293
+ const first = group[0];
18294
+ const scores = meanScoresByScorer(group, trials);
18295
+ const allErrored = group.every((r) => r.error !== void 0);
18296
+ const meanScore = allErrored || scores.length === 0 ? 0 : scores.reduce((acc, s) => acc + s.score, 0) / scores.length;
18297
+ const firstOk = group.find((r) => r.error === void 0);
18298
+ const errorMsg = allErrored ? group.find((r) => r.error !== void 0)?.error : void 0;
18299
+ const metadata = stripReserved(first.metadata);
18300
+ const tokensIn = sumDefined(group.map((r) => r.tokensIn));
18301
+ const tokensOut = sumDefined(group.map((r) => r.tokensOut));
18302
+ return {
18303
+ index,
18304
+ input: first.input,
18305
+ output: firstOk?.output ?? first.output,
18306
+ ...first.expected !== void 0 ? { expected: first.expected } : {},
18307
+ scores,
18308
+ meanScore,
18309
+ durationMs: group.reduce((acc, r) => acc + r.durationMs, 0),
18310
+ ...tokensIn !== void 0 ? { tokensIn } : {},
18311
+ ...tokensOut !== void 0 ? { tokensOut } : {},
18312
+ ...errorMsg !== void 0 ? { error: errorMsg } : {},
18313
+ ...metadata !== void 0 ? { metadata } : {},
18314
+ trialCount: trials
18315
+ };
18316
+ }
18317
+ function collapseTrials(rows, trials) {
18318
+ const groups = /* @__PURE__ */ new Map();
18319
+ for (const row of rows) {
18320
+ const meta = row.metadata;
18321
+ const origIdx = typeof meta?.[TRIAL_INDEX_KEY] === "number" ? meta[TRIAL_INDEX_KEY] : row.index;
18322
+ const arr = groups.get(origIdx) ?? [];
18323
+ arr.push(row);
18324
+ groups.set(origIdx, arr);
18325
+ }
18326
+ return [...groups.entries()].sort((a, b) => a[0] - b[0]).map(([origIdx, group]) => collapseGroup(origIdx, group, trials));
18327
+ }
18328
+
18248
18329
  // src/internal/eval/runner.ts
18249
18330
  function safeHook(fn) {
18250
18331
  try {
@@ -18453,7 +18534,9 @@ async function runEval(options, runOpts) {
18453
18534
  const id = randomUUID();
18454
18535
  const startedAt = Date.now();
18455
18536
  const entries = await materializeDataset(options.dataset);
18456
- const indexed = entries.map((e) => ({ ...e }));
18537
+ const materialized = entries.map((e) => ({ ...e }));
18538
+ const trials = options.trials ?? 1;
18539
+ const indexed = trials > 1 ? expandForTrials(materialized, trials) : materialized;
18457
18540
  const scorers = normalizeScorers(options.scorers);
18458
18541
  const concurrency = options.concurrency ?? 4;
18459
18542
  const signal = runOpts?.signal;
@@ -18485,7 +18568,8 @@ async function runEval(options, runOpts) {
18485
18568
  const batchOpts = makeAgentForBatch(options.agent, indexed);
18486
18569
  rows = await runRowsViaBatch(indexed, batchOpts, scorers, concurrency, signal, onRow, sink);
18487
18570
  }
18488
- const aggregate = computeAggregate(rows);
18571
+ const finalRows = trials > 1 ? collapseTrials(rows, trials) : rows;
18572
+ const aggregate = computeAggregate(finalRows);
18489
18573
  const endedAt = Date.now();
18490
18574
  const run = {
18491
18575
  id,
@@ -18494,7 +18578,7 @@ async function runEval(options, runOpts) {
18494
18578
  endedAt,
18495
18579
  durationMs: endedAt - startedAt,
18496
18580
  aggregate,
18497
- rows,
18581
+ rows: finalRows,
18498
18582
  ...options.metadata !== void 0 ? { metadata: options.metadata } : {}
18499
18583
  };
18500
18584
  safeHook(() => hooks?.afterRun?.(run));
@@ -18509,6 +18593,59 @@ async function runEval(options, runOpts) {
18509
18593
  }
18510
18594
  }
18511
18595
 
18596
+ // src/internal/eval/assert.ts
18597
+ var EvalThresholdError = class extends Error {
18598
+ name = "EvalThresholdError";
18599
+ /** The eval's name (`EvalRun.name`). */
18600
+ evalName;
18601
+ /** Every unmet threshold, in check order. */
18602
+ failures;
18603
+ constructor(evalName, failures) {
18604
+ const lines = failures.map(
18605
+ (f) => ` - ${f.metric}: required ${f.threshold}, got ${Number.isNaN(f.actual) ? "n/a (scorer absent)" : f.actual}`
18606
+ );
18607
+ super(
18608
+ `Eval "${evalName}" failed ${failures.length} threshold${failures.length === 1 ? "" : "s"}:
18609
+ ${lines.join("\n")}`
18610
+ );
18611
+ this.evalName = evalName;
18612
+ this.failures = failures;
18613
+ }
18614
+ };
18615
+ function floor(metric, actual, threshold) {
18616
+ if (threshold === void 0 || actual >= threshold) return void 0;
18617
+ return { metric, threshold, actual };
18618
+ }
18619
+ function perScorerFailures(perScorer, thresholds) {
18620
+ if (thresholds === void 0) return [];
18621
+ const out = [];
18622
+ for (const [name, min] of Object.entries(thresholds)) {
18623
+ const stats = perScorer[name];
18624
+ if (stats === void 0 || stats.mean < min) {
18625
+ out.push({
18626
+ metric: `perScorer.${name}`,
18627
+ threshold: min,
18628
+ actual: stats === void 0 ? Number.NaN : stats.mean
18629
+ });
18630
+ }
18631
+ }
18632
+ return out;
18633
+ }
18634
+ function assertEval(run, thresholds) {
18635
+ const a = run.aggregate;
18636
+ const errorRatio = a.totalRows > 0 ? a.errorRows / a.totalRows : 0;
18637
+ const ceiling = thresholds.maxErrorRatio !== void 0 && errorRatio > thresholds.maxErrorRatio ? { metric: "errorRatio", threshold: thresholds.maxErrorRatio, actual: errorRatio } : void 0;
18638
+ const failures = [
18639
+ floor("meanScore", a.meanScore, thresholds.minMeanScore),
18640
+ floor("passRatio", a.passRatio, thresholds.minPassRatio),
18641
+ ceiling,
18642
+ ...perScorerFailures(a.perScorer, thresholds.perScorer)
18643
+ ].filter((f) => f !== void 0);
18644
+ if (failures.length > 0) {
18645
+ throw new EvalThresholdError(run.name, failures);
18646
+ }
18647
+ }
18648
+
18512
18649
  // src/sandbox/shell-escape.ts
18513
18650
  function shellEscapePosix(arg) {
18514
18651
  return `'${arg.replace(/'/g, "'\\''")}'`;
@@ -18528,6 +18665,27 @@ async function captureArtifact(sandbox, repoDir) {
18528
18665
  return { diff, applies: check.exitCode === 0 };
18529
18666
  }
18530
18667
 
18668
+ // src/internal/scorers/levenshtein.ts
18669
+ var LEVENSHTEIN_MAX_LEN = 4e3;
18670
+ function nextRow(prev, rowIndex, ai, b) {
18671
+ const curr = [rowIndex];
18672
+ for (let j = 1; j <= b.length; j += 1) {
18673
+ const cost = ai === b.charCodeAt(j - 1) ? 0 : 1;
18674
+ curr[j] = Math.min((prev[j] ?? 0) + 1, (curr[j - 1] ?? 0) + 1, (prev[j - 1] ?? 0) + cost);
18675
+ }
18676
+ return curr;
18677
+ }
18678
+ function levenshteinDistance(a, b) {
18679
+ const m = a.length;
18680
+ const n = b.length;
18681
+ if (m === 0) return n;
18682
+ if (n === 0) return m;
18683
+ let prev = [];
18684
+ for (let j = 0; j <= n; j += 1) prev[j] = j;
18685
+ for (let i = 1; i <= m; i += 1) prev = nextRow(prev, i, a.charCodeAt(i - 1), b);
18686
+ return prev[n] ?? 0;
18687
+ }
18688
+
18531
18689
  // src/internal/scorers/llm-judge.ts
18532
18690
  init_agent_factory_registry();
18533
18691
  function buildPrompt(subject, criteria, rubric, expected) {
@@ -18688,15 +18846,80 @@ var LocalSandbox = class extends SandboxBackend {
18688
18846
 
18689
18847
  // src/scorers.ts
18690
18848
  var JSON_SHAPE_MAX_BYTES = 1e6;
18849
+ function cosineSimilarity(a, b) {
18850
+ const n = Math.min(a.length, b.length);
18851
+ let dot = 0;
18852
+ let na = 0;
18853
+ let nb = 0;
18854
+ for (let i = 0; i < n; i += 1) {
18855
+ const av = a[i] ?? 0;
18856
+ const bv = b[i] ?? 0;
18857
+ dot += av * bv;
18858
+ na += av * av;
18859
+ nb += bv * bv;
18860
+ }
18861
+ if (na === 0 || nb === 0) return 0;
18862
+ return dot / (Math.sqrt(na) * Math.sqrt(nb));
18863
+ }
18864
+ function normalizeStringInputs(output, expected, caseSensitive) {
18865
+ if (typeof expected !== "string") return { score: 0, reason: "expected_not_string" };
18866
+ if (expected.length === 0) return { score: 0, reason: "expected_empty" };
18867
+ return {
18868
+ o: caseSensitive ? output : output.toLowerCase(),
18869
+ e: caseSensitive ? expected : expected.toLowerCase()
18870
+ };
18871
+ }
18872
+ function scoreLevenshtein(output, expected, caseSensitive, threshold) {
18873
+ const norm = normalizeStringInputs(output, expected, caseSensitive);
18874
+ if (!("o" in norm)) return norm;
18875
+ const { o, e } = norm;
18876
+ if (o.length > LEVENSHTEIN_MAX_LEN || e.length > LEVENSHTEIN_MAX_LEN) {
18877
+ return { score: 0, reason: "input_too_large" };
18878
+ }
18879
+ const sim = 1 - levenshteinDistance(o, e) / Math.max(o.length, e.length, 1);
18880
+ if (threshold === void 0) return { score: sim };
18881
+ return sim >= threshold ? { score: 1 } : { score: 0, reason: `sim=${sim.toFixed(3)}` };
18882
+ }
18883
+ function toFiniteNumber2(value) {
18884
+ if (typeof value === "number") return Number.isFinite(value) ? value : void 0;
18885
+ if (value === void 0 || value === null) return void 0;
18886
+ const n = Number(String(value).trim());
18887
+ return Number.isFinite(n) ? n : void 0;
18888
+ }
18889
+ function scoreNumericDiff(output, expected, tolerance) {
18890
+ const o = toFiniteNumber2(output);
18891
+ if (o === void 0) return { score: 0, reason: "output_not_numeric" };
18892
+ const e = toFiniteNumber2(expected);
18893
+ if (e === void 0) return { score: 0, reason: "expected_not_numeric" };
18894
+ if (tolerance !== void 0) {
18895
+ return Math.abs(o - e) <= tolerance ? { score: 1 } : { score: 0, reason: `abs_diff=${Math.abs(o - e)}` };
18896
+ }
18897
+ const denom = Math.max(Math.abs(o), Math.abs(e));
18898
+ if (denom === 0) return { score: 1 };
18899
+ return { score: Math.max(0, 1 - Math.abs(o - e) / denom) };
18900
+ }
18901
+ function embedderCreateOptions(opts) {
18902
+ return {
18903
+ ...opts.model !== void 0 ? { model: opts.model } : {},
18904
+ ...opts.apiKey !== void 0 ? { apiKey: opts.apiKey } : {},
18905
+ ...opts.baseUrl !== void 0 ? { baseUrl: opts.baseUrl } : {}
18906
+ };
18907
+ }
18908
+ function makeDefaultEmbedder(opts) {
18909
+ let runtime;
18910
+ return (texts) => {
18911
+ if (runtime === void 0) {
18912
+ runtime = openRouterMemoryEmbeddingProviderAdapter.create(embedderCreateOptions(opts));
18913
+ }
18914
+ return runtime.then((rt) => rt.embed(texts));
18915
+ };
18916
+ }
18691
18917
  function makeStringScorer(name, caseSensitive, compare) {
18692
18918
  return {
18693
18919
  name,
18694
18920
  score: (output, expected) => {
18695
- if (typeof expected !== "string") return { score: 0, reason: "expected_not_string" };
18696
- if (expected.length === 0) return { score: 0, reason: "expected_empty" };
18697
- const o = caseSensitive ? output : output.toLowerCase();
18698
- const e = caseSensitive ? expected : expected.toLowerCase();
18699
- return compare(o, e);
18921
+ const norm = normalizeStringInputs(output, expected, caseSensitive);
18922
+ return "o" in norm ? compare(norm.o, norm.e) : norm;
18700
18923
  }
18701
18924
  };
18702
18925
  }
@@ -18740,6 +18963,56 @@ var Scorers = {
18740
18963
  }
18741
18964
  };
18742
18965
  },
18966
+ /**
18967
+ * SE41 — normalized Levenshtein similarity: `1 - editDistance / max(len)`.
18968
+ * Deterministic (no LLM), so it always runs in CI. `threshold` binarizes.
18969
+ *
18970
+ * Refuses empty/non-string `expected` (EC-1 parity) and caps input at
18971
+ * {@link LEVENSHTEIN_MAX_LEN} chars to bound the O(n*m) cost on adversarial output.
18972
+ */
18973
+ levenshtein(opts = {}) {
18974
+ const caseSensitive = opts.caseSensitive ?? false;
18975
+ const { threshold } = opts;
18976
+ return {
18977
+ name: threshold !== void 0 ? `levenshtein(>=${threshold})` : "levenshtein",
18978
+ score: (output, expected) => scoreLevenshtein(output, expected, caseSensitive, threshold)
18979
+ };
18980
+ },
18981
+ /**
18982
+ * SE41 — numeric closeness. Parses `output` and `expected` as numbers and
18983
+ * scores continuous relative closeness `1 - |o-e| / max(|o|,|e|)` (both 0 ⇒ 1),
18984
+ * or a binary pass when `tolerance` is set. Deterministic (no LLM).
18985
+ */
18986
+ numericDiff(opts = {}) {
18987
+ const { tolerance } = opts;
18988
+ return {
18989
+ name: "numeric-diff",
18990
+ score: (output, expected) => scoreNumericDiff(output, expected, tolerance)
18991
+ };
18992
+ },
18993
+ /**
18994
+ * SE41 — semantic similarity via embeddings: cosine of `embed(output)` vs
18995
+ * `embed(expected)`, clamped to `[0, 1]` (negatives → 0). `threshold` binarizes.
18996
+ *
18997
+ * By default routes through OpenRouter's embeddings endpoint
18998
+ * (`OPENROUTER_API_KEY`); inject `embed` to use another provider or to test
18999
+ * deterministically. Each scored row costs one embeddings call.
19000
+ */
19001
+ embeddingSimilarity(opts) {
19002
+ const { threshold } = opts;
19003
+ const embed = opts.embed ?? makeDefaultEmbedder(opts);
19004
+ return {
19005
+ name: threshold !== void 0 ? `embedding-similarity(>=${threshold})` : "embedding-similarity",
19006
+ score: async (output, expected) => {
19007
+ if (typeof expected !== "string") return { score: 0, reason: "expected_not_string" };
19008
+ const [vo, ve] = await embed([output, expected]);
19009
+ if (vo === void 0 || ve === void 0) return { score: 0, reason: "embed_failed" };
19010
+ const score = Math.max(0, cosineSimilarity(vo, ve));
19011
+ if (threshold === void 0) return { score };
19012
+ return score >= threshold ? { score: 1 } : { score: 0, reason: `cos=${score.toFixed(3)}` };
19013
+ }
19014
+ };
19015
+ },
18743
19016
  /**
18744
19017
  * Parse `output` as JSON and validate against a Zod schema.
18745
19018
  *
@@ -18845,6 +19118,9 @@ var EvalOptionsSchema = z.object({
18845
19118
  // EC-3: concurrency MUST be a finite integer in [1, 64]. Bare `z.number()`
18846
19119
  // would let Infinity / 0 / negatives slip through.
18847
19120
  concurrency: z.number().int("concurrency must be an integer").min(1, "concurrency must be >= 1").max(64, "concurrency must be <= 64").optional(),
19121
+ // SE41: trials MUST be a finite integer in [1, 100]. Guards against 0
19122
+ // (no-op run) and unbounded fanout that would DoS the provider.
19123
+ trials: z.number().int("trials must be an integer").min(1, "trials must be >= 1").max(100, "trials must be <= 100").optional(),
18848
19124
  metadata: z.record(z.string(), z.unknown()).optional(),
18849
19125
  hooks: z.object({
18850
19126
  beforeRun: z.function().optional(),
@@ -18875,6 +19151,6 @@ var Eval = class _Eval {
18875
19151
  }
18876
19152
  };
18877
19153
 
18878
- export { Eval, EvalAlreadyRunningError, JsonlParseError, Scorers, captureArtifact, loadJsonl };
19154
+ export { Eval, EvalAlreadyRunningError, EvalThresholdError, JsonlParseError, Scorers, assertEval, captureArtifact, loadJsonl };
18879
19155
  //# sourceMappingURL=eval.js.map
18880
19156
  //# sourceMappingURL=eval.js.map