@tangle-network/agent-eval 0.145.19 → 0.145.20

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.
@@ -1,5 +1,5 @@
1
1
  import { i as isModelPriced, n as estimateCost } from "../metrics-Cl0L1KUy.js";
2
- import { t as runAgentMatrix } from "../matrix-BzQnu2S6.js";
2
+ import { r as withCellSpend, t as runAgentMatrix } from "../matrix-pWinRMPl.js";
3
3
  import { mkdirSync, writeFileSync } from "node:fs";
4
4
  import { join } from "node:path";
5
5
  //#region src/multishot/router.ts
@@ -398,10 +398,11 @@ const MULTISHOT_ROLES = /* @__PURE__ */ new Set([
398
398
  * scores as though the artifact was never produced; a non-finite `costUsd`
399
399
  * reaches `summary.totalCostUsd` and makes every cost number NaN.
400
400
  *
401
- * It does NOT protect spend. A rejected cell records `costUsd: 0`, so a shot
402
- * that spends before it returns a malformed result leaves that spend out of
403
- * the cumulative sum the cost ceiling reads. That is how the matrix records
404
- * every failed cell, not something this guard changes.
401
+ * A rejected cell is still billed: the matrix cell reads the shot's own
402
+ * `costUsd` when it is a usable amount and declares that spend on the throw,
403
+ * so money the shot spent before returning a malformed result stays in the
404
+ * cumulative sum the cost ceiling reads. A result whose `costUsd` is itself
405
+ * malformed carries no usable amount, and the cell records as `uncaptured`.
405
406
  *
406
407
  * Every required field of `MultishotMessage` and `MultishotArtifact` is
407
408
  * checked, including `toolCalls` elements and `invocation.args`. Optional
@@ -416,6 +417,16 @@ function assertMultishotShotResult(value) {
416
417
  assertFiniteCount(result.toolCalls, "toolCalls");
417
418
  assertFiniteCount(result.durationMs, "durationMs");
418
419
  assertFiniteCount(result.costUsd, "costUsd");
420
+ if (result.costProvenance !== void 0) assertCostProvenance(result.costProvenance);
421
+ }
422
+ function assertCostProvenance(value) {
423
+ const row = requireRow(value, "costProvenance");
424
+ if (row.kind !== "observed" && row.kind !== "estimated" && row.kind !== "uncaptured") throw new MultishotShotResultError(`costProvenance.kind must be observed, estimated or uncaptured, received ${describeValue(row.kind)}`);
425
+ if (row.kind === "uncaptured") {
426
+ if (row.usd !== null) throw new MultishotShotResultError(`uncaptured costProvenance.usd must be null, received ${describeValue(row.usd)}`);
427
+ return;
428
+ }
429
+ assertFiniteCount(row.usd, "costProvenance.usd");
419
430
  }
420
431
  function assertMessage(value, index) {
421
432
  const row = requireRow(value, `transcript[${index}]`);
@@ -460,6 +471,22 @@ function describeValue(value) {
460
471
  //#endregion
461
472
  //#region src/multishot/multishot.ts
462
473
  async function runMultishot(opts) {
474
+ const meter = {
475
+ costUsd: 0,
476
+ startedAt: Date.now(),
477
+ uncaptured: false
478
+ };
479
+ try {
480
+ return await runShotTurns(opts, meter);
481
+ } catch (err) {
482
+ throw withCellSpend(err, {
483
+ costUsd: meter.costUsd,
484
+ durationMs: Date.now() - meter.startedAt,
485
+ kind: meter.uncaptured ? "uncaptured" : "estimated"
486
+ });
487
+ }
488
+ }
489
+ async function runShotTurns(opts, meter) {
463
490
  const apiKey = opts.apiKey ?? requireRouterApiKey();
464
491
  const baseUrl = opts.baseUrl ?? defaultRouterBaseUrl();
465
492
  const maxTurns = opts.maxTurns ?? 10;
@@ -485,11 +512,9 @@ async function runMultishot(opts) {
485
512
  const agentTransport = opts.agentTransport ?? routerTransport;
486
513
  const driverTransport = opts.driverTransport ?? routerTransport;
487
514
  const shape = defaultShapeFromProfile(opts.profile, opts.shape);
488
- const start = Date.now();
489
515
  const transcript = [];
490
516
  const artifacts = [];
491
517
  let toolCalls = 0;
492
- let totalCostUsd = 0;
493
518
  const opener = shape.buildOpener(opts.persona);
494
519
  transcript.push({
495
520
  role: "user",
@@ -514,7 +539,8 @@ async function runMultishot(opts) {
514
539
  maxTokens: dispatchesThisTurn === 0 ? agentMaxTokens : toolFollowupMaxTokens,
515
540
  signal: opts.signal
516
541
  });
517
- totalCostUsd += agentCostUsd ?? estimateRouterCost(agentModel, agentUsage);
542
+ meter.costUsd += agentCostUsd ?? estimateRouterCost(agentModel, agentUsage);
543
+ if (agentCostUsd === void 0 && agentUsage === void 0) meter.uncaptured = true;
518
544
  const agentText = (agentMsg.content ?? "").trim();
519
545
  const agentToolCalls = (agentMsg.tool_calls ?? []).map((tc) => ({
520
546
  id: tc.id,
@@ -553,7 +579,7 @@ async function runMultishot(opts) {
553
579
  signal: opts.signal
554
580
  });
555
581
  toolResult = r.content;
556
- totalCostUsd += r.costUsd;
582
+ meter.costUsd += r.costUsd;
557
583
  const artifactType = artifactTypeFor(tc.name);
558
584
  if (artifactType) artifacts.push({
559
585
  type: artifactType,
@@ -590,9 +616,9 @@ async function runMultishot(opts) {
590
616
  turn,
591
617
  models: driverModels,
592
618
  maxTokens: driverMaxTokens,
593
- signal: opts.signal
619
+ signal: opts.signal,
620
+ meter
594
621
  });
595
- totalCostUsd += driver.costUsd;
596
622
  agentMessages.push({
597
623
  role: "user",
598
624
  content: driver.content
@@ -607,8 +633,15 @@ async function runMultishot(opts) {
607
633
  transcript,
608
634
  artifacts,
609
635
  toolCalls,
610
- durationMs: Date.now() - start,
611
- costUsd: totalCostUsd
636
+ durationMs: Date.now() - meter.startedAt,
637
+ costUsd: meter.costUsd,
638
+ costProvenance: meter.uncaptured ? {
639
+ kind: "uncaptured",
640
+ usd: null
641
+ } : {
642
+ kind: "estimated",
643
+ usd: meter.costUsd
644
+ }
612
645
  };
613
646
  }
614
647
  async function driverTurn(opts) {
@@ -637,11 +670,10 @@ async function driverTurn(opts) {
637
670
  maxTokens: opts.maxTokens,
638
671
  signal: opts.signal
639
672
  });
673
+ opts.meter.costUsd += costUsd ?? estimateRouterCost(model, usage);
674
+ if (costUsd === void 0 && usage === void 0) opts.meter.uncaptured = true;
640
675
  const content = (message.content ?? "").trim();
641
- if (content.length > 0) return {
642
- content,
643
- costUsd: costUsd ?? estimateRouterCost(model, usage)
644
- };
676
+ if (content.length > 0) return { content };
645
677
  }
646
678
  throw new MultishotDriverEmptyError(opts.turn);
647
679
  }
@@ -704,7 +736,9 @@ async function runMultishotMatrix(opts) {
704
736
  reps: opts.reps ?? 1,
705
737
  maxConcurrency: opts.maxConcurrency ?? 2,
706
738
  costCeiling: opts.costCeiling,
739
+ maxCellCostUsd: opts.maxCellCostUsd,
707
740
  async runCell(cell) {
741
+ const cellStartedAt = Date.now();
708
742
  const profile = cell.axes.profile?.value;
709
743
  const persona = cell.axes.persona?.value;
710
744
  const profileId = String(cell.axes.profile?.id ?? "unknown");
@@ -729,87 +763,111 @@ async function runMultishotMatrix(opts) {
729
763
  apiKey: opts.apiKey,
730
764
  baseUrl: opts.baseUrl
731
765
  });
732
- assertMultishotShotResult(sim);
733
- const codeArtifacts = sim.artifacts.filter((a) => codeTypes.has(a.type));
734
- const contentArtifacts = sim.artifacts.filter((a) => contentTypes.has(a.type));
735
- const [conversationRun, codeReviewRuns, contentReviewRuns] = await Promise.all([
736
- runJudge(withJudgeMaxTokens(opts.judges.conversation, opts.judgeMaxTokens), {
737
- transcript: sim.transcript,
738
- persona
739
- }),
740
- opts.judges.codeReview ? Promise.all(codeArtifacts.map((artifact) => runJudge(withJudgeMaxTokens(opts.judges.codeReview, opts.judgeMaxTokens), {
741
- artifact,
742
- persona
743
- }).then((result) => ({
744
- score: {
745
- ...result.score,
746
- turn: artifact.turn,
747
- type: artifact.type
766
+ const shotCostUsd = shotCostSubtotal(sim);
767
+ const shotCostComplete = sim?.costProvenance?.kind !== "uncaptured";
768
+ let judgeCostUsd = 0;
769
+ let judgeCostComplete = false;
770
+ let phase = "validate";
771
+ try {
772
+ assertMultishotShotResult(sim);
773
+ const codeArtifacts = sim.artifacts.filter((a) => codeTypes.has(a.type));
774
+ const contentArtifacts = sim.artifacts.filter((a) => contentTypes.has(a.type));
775
+ phase = "judging";
776
+ const [conversationRun, codeReviewRuns, contentReviewRuns] = await Promise.all([
777
+ runJudge(withJudgeMaxTokens(opts.judges.conversation, opts.judgeMaxTokens), {
778
+ transcript: sim.transcript,
779
+ persona
780
+ }),
781
+ opts.judges.codeReview ? Promise.all(codeArtifacts.map((artifact) => runJudge(withJudgeMaxTokens(opts.judges.codeReview, opts.judgeMaxTokens), {
782
+ artifact,
783
+ persona
784
+ }).then((result) => ({
785
+ score: {
786
+ ...result.score,
787
+ turn: artifact.turn,
788
+ type: artifact.type
789
+ },
790
+ cost: result.cost
791
+ })))) : Promise.resolve([]),
792
+ opts.judges.contentQuality ? Promise.all(contentArtifacts.map((artifact) => runJudge(withJudgeMaxTokens(opts.judges.contentQuality, opts.judgeMaxTokens), {
793
+ artifact,
794
+ persona
795
+ }).then((result) => ({
796
+ score: {
797
+ ...result.score,
798
+ turn: artifact.turn,
799
+ type: artifact.type
800
+ },
801
+ cost: result.cost
802
+ })))) : Promise.resolve([])
803
+ ]);
804
+ const judgeRuns = [
805
+ conversationRun,
806
+ ...codeReviewRuns,
807
+ ...contentReviewRuns
808
+ ];
809
+ judgeCostUsd = judgeRuns.reduce((sum, run) => sum + (run.cost.usd ?? 0), 0);
810
+ judgeCostComplete = judgeRuns.every((run) => run.cost.kind !== "uncaptured");
811
+ phase = "scoring";
812
+ const conversation = conversationRun.score;
813
+ const codeReviews = codeReviewRuns.map((run) => run.score);
814
+ const contentReviews = contentReviewRuns.map((run) => run.score);
815
+ const { composite, codeComposite, contentComposite, allJudgesFailed } = computeCellComposite({
816
+ conversation,
817
+ codeReviews: opts.judges.codeReview ? codeReviews : void 0,
818
+ contentReviews: opts.judges.contentQuality ? contentReviews : void 0
819
+ });
820
+ const cellScore = {
821
+ composite,
822
+ conversation
823
+ };
824
+ if (opts.judges.codeReview) cellScore.codeReview = {
825
+ perArtifact: codeReviews,
826
+ composite: codeComposite
827
+ };
828
+ if (opts.judges.contentQuality) cellScore.contentQuality = {
829
+ perArtifact: contentReviews,
830
+ composite: contentComposite
831
+ };
832
+ const cellDir = join(opts.runDir, profileId, personaId, `rep-${cell.rep}`);
833
+ mkdirSync(cellDir, { recursive: true });
834
+ writeFileSync(join(cellDir, "transcript.json"), JSON.stringify(sim.transcript, null, 2));
835
+ writeFileSync(join(cellDir, "artifacts.json"), JSON.stringify(sim.artifacts, null, 2));
836
+ writeFileSync(join(cellDir, "scores.json"), JSON.stringify(cellScore, null, 2));
837
+ const notes = [`convo=${conversation.composite.toFixed(1)}`];
838
+ if (opts.judges.codeReview) notes.push(`code=${codeComposite.toFixed(1)}`);
839
+ if (opts.judges.contentQuality) notes.push(`content=${contentComposite.toFixed(1)}`);
840
+ if (allJudgesFailed) notes.push("all-judges-failed");
841
+ if (!judgeCostComplete) notes.push("judge-cost-incomplete");
842
+ return {
843
+ output: {
844
+ turns: sim.transcript.length,
845
+ toolCalls: sim.toolCalls,
846
+ artifactCount: sim.artifacts.length
748
847
  },
749
- cost: result.cost
750
- })))) : Promise.resolve([]),
751
- opts.judges.contentQuality ? Promise.all(contentArtifacts.map((artifact) => runJudge(withJudgeMaxTokens(opts.judges.contentQuality, opts.judgeMaxTokens), {
752
- artifact,
753
- persona
754
- }).then((result) => ({
755
- score: {
756
- ...result.score,
757
- turn: artifact.turn,
758
- type: artifact.type
848
+ verdict: {
849
+ valid: composite >= 5,
850
+ score: composite,
851
+ notes: notes.join(" ")
759
852
  },
760
- cost: result.cost
761
- })))) : Promise.resolve([])
762
- ]);
763
- const conversation = conversationRun.score;
764
- const codeReviews = codeReviewRuns.map((run) => run.score);
765
- const contentReviews = contentReviewRuns.map((run) => run.score);
766
- const { composite, codeComposite, contentComposite, allJudgesFailed } = computeCellComposite({
767
- conversation,
768
- codeReviews: opts.judges.codeReview ? codeReviews : void 0,
769
- contentReviews: opts.judges.contentQuality ? contentReviews : void 0
770
- });
771
- const cellScore = {
772
- composite,
773
- conversation
774
- };
775
- if (opts.judges.codeReview) cellScore.codeReview = {
776
- perArtifact: codeReviews,
777
- composite: codeComposite
778
- };
779
- if (opts.judges.contentQuality) cellScore.contentQuality = {
780
- perArtifact: contentReviews,
781
- composite: contentComposite
782
- };
783
- const cellDir = join(opts.runDir, profileId, personaId, `rep-${cell.rep}`);
784
- mkdirSync(cellDir, { recursive: true });
785
- writeFileSync(join(cellDir, "transcript.json"), JSON.stringify(sim.transcript, null, 2));
786
- writeFileSync(join(cellDir, "artifacts.json"), JSON.stringify(sim.artifacts, null, 2));
787
- writeFileSync(join(cellDir, "scores.json"), JSON.stringify(cellScore, null, 2));
788
- const notes = [`convo=${conversation.composite.toFixed(1)}`];
789
- if (opts.judges.codeReview) notes.push(`code=${codeComposite.toFixed(1)}`);
790
- if (opts.judges.contentQuality) notes.push(`content=${contentComposite.toFixed(1)}`);
791
- if (allJudgesFailed) notes.push("all-judges-failed");
792
- const judgeRuns = [
793
- conversationRun,
794
- ...codeReviewRuns,
795
- ...contentReviewRuns
796
- ];
797
- const judgeCostUsd = judgeRuns.reduce((sum, run) => sum + (run.cost.usd ?? 0), 0);
798
- if (judgeRuns.some((run) => run.cost.kind === "uncaptured")) notes.push("judge-cost-incomplete");
799
- return {
800
- output: {
801
- turns: sim.transcript.length,
802
- toolCalls: sim.toolCalls,
803
- artifactCount: sim.artifacts.length
804
- },
805
- verdict: {
806
- valid: composite >= 5,
807
- score: composite,
808
- notes: notes.join(" ")
809
- },
810
- costUsd: sim.costUsd + judgeCostUsd,
811
- durationMs: sim.durationMs
812
- };
853
+ costUsd: sim.costUsd + judgeCostUsd,
854
+ costProvenance: judgeCostComplete && shotCostComplete ? {
855
+ kind: "estimated",
856
+ usd: sim.costUsd + judgeCostUsd
857
+ } : {
858
+ kind: "uncaptured",
859
+ usd: null
860
+ },
861
+ durationMs: sim.durationMs
862
+ };
863
+ } catch (err) {
864
+ if (shotCostUsd === void 0) throw err;
865
+ throw withCellSpend(err, {
866
+ costUsd: shotCostUsd + judgeCostUsd,
867
+ durationMs: Date.now() - cellStartedAt,
868
+ kind: shotCostComplete && (phase === "validate" || phase === "scoring" && judgeCostComplete) ? "estimated" : "uncaptured"
869
+ });
870
+ }
813
871
  }
814
872
  });
815
873
  const summary = {
@@ -817,6 +875,8 @@ async function runMultishotMatrix(opts) {
817
875
  passRate: matrix.summary.overallPassRate,
818
876
  meanScore: matrix.summary.overallMeanScore,
819
877
  totalCostUsd: matrix.summary.totalCostUsd,
878
+ costUncapturedCells: matrix.summary.costUncapturedCells,
879
+ ceilingChargedUsd: matrix.summary.ceilingChargedUsd,
820
880
  durationMs: matrix.summary.durationMs,
821
881
  runsExecuted: matrix.summary.runsExecuted,
822
882
  cellsSkipped: matrix.summary.cellsSkipped,
@@ -824,11 +884,14 @@ async function runMultishotMatrix(opts) {
824
884
  byPersona: matrix.byAxis.persona
825
885
  };
826
886
  writeFileSync(join(opts.runDir, "summary.json"), JSON.stringify(summary, null, 2));
887
+ const uncaptured = matrix.summary.costUncapturedCells;
888
+ const costLabel = uncaptured > 0 ? "Cost (at least)" : "Cost";
827
889
  const md = [
828
890
  `# Multishot matrix`,
829
891
  ``,
830
- `**Cells**: ${matrix.summary.totalCells} | **Pass rate**: ${(matrix.summary.overallPassRate * 100).toFixed(0)}% | **Mean**: ${matrix.summary.overallMeanScore.toFixed(2)} | **Cost**: $${matrix.summary.totalCostUsd.toFixed(2)} | **Duration**: ${(matrix.summary.durationMs / 1e3).toFixed(0)}s`,
892
+ `**Cells**: ${matrix.summary.totalCells} | **Pass rate**: ${(matrix.summary.overallPassRate * 100).toFixed(0)}% | **Mean**: ${matrix.summary.overallMeanScore.toFixed(2)} | **${costLabel}**: $${matrix.summary.totalCostUsd.toFixed(2)} | **Duration**: ${(matrix.summary.durationMs / 1e3).toFixed(0)}s`,
831
893
  ``,
894
+ ...uncaptured > 0 ? [`> ${uncaptured} of ${matrix.summary.runsExecuted} cells reported a cost subtotal, not a total. Real spend is higher than every cost figure below.`, ``] : [],
832
895
  `## By profile`,
833
896
  ``,
834
897
  "| profile | pass | mean | cost |",
@@ -845,6 +908,14 @@ async function runMultishotMatrix(opts) {
845
908
  writeFileSync(join(opts.runDir, "summary.md"), md.join("\n"));
846
909
  return { matrix };
847
910
  }
911
+ /** The shot's own spend, when the value it resolved with reports a usable
912
+ * amount. `undefined` when it does not — a malformed shot result is exactly
913
+ * the case where the number cannot be trusted, and billing a wrong figure is
914
+ * worse than recording the cell as uncaptured. */
915
+ function shotCostSubtotal(sim) {
916
+ const value = sim?.costUsd;
917
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
918
+ }
848
919
  function withJudgeMaxTokens(judge, maxTokens) {
849
920
  if (maxTokens === void 0 || judge.maxTokens !== void 0) return judge;
850
921
  return {