pareta 1.4.0 → 2.1.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 CHANGED
@@ -40,7 +40,9 @@ head-to-head against frontier models on your own ground truth and prices every
40
40
  contender honestly:
41
41
 
42
42
  ```ts
43
- const set = await pa.evals.sets.create({ task: "invoice-extraction", items: [...] });
43
+ const set = await pa.evals.sets.create({
44
+ items: [...], intent: "extract vendor, total and date from each invoice",
45
+ });
44
46
  const run = await pa.evals.runs.create({
45
47
  evalSet: set.id, models: ["auto"], frontier: ["claude-opus-4-7"], wait: true,
46
48
  });
package/dist/index.cjs CHANGED
@@ -174,7 +174,7 @@ function parseSSE(reader, opts) {
174
174
  }
175
175
 
176
176
  // src/version.ts
177
- var VERSION = "1.4.0";
177
+ var VERSION = "2.1.0";
178
178
 
179
179
  // src/money.ts
180
180
  var MICRO_PER_CENT = 10000n;
@@ -255,6 +255,36 @@ var ChatCompletion = class extends BaseModel {
255
255
  get usage() {
256
256
  return new Usage(this.raw.usage ?? {});
257
257
  }
258
+ /** #164 receipt, held OFF `.raw` so `toDict()` stays the raw server JSON. */
259
+ _cost;
260
+ /** #164: attach the per-call receipt from the response headers (billed +
261
+ * the frontier counterfactual, micro-USD ints). Chainable. */
262
+ withCost(headers) {
263
+ const int = (name) => {
264
+ const v = headers?.get(name);
265
+ if (v == null) return null;
266
+ const n = parseInt(v, 10);
267
+ return Number.isNaN(n) ? null : n;
268
+ };
269
+ this._cost = {
270
+ billed: int("X-Pareta-Billed"),
271
+ frontier: int("X-Pareta-Frontier-Would-Have-Cost")
272
+ };
273
+ return this;
274
+ }
275
+ /** What Pareta charged, micro-USD (0 on an idempotent replay); null if absent. */
276
+ get billedMicroUsd() {
277
+ return this._cost?.billed ?? null;
278
+ }
279
+ /** What one list-priced frontier call would have cost, micro-USD. */
280
+ get frontierWouldHaveCostMicroUsd() {
281
+ return this._cost?.frontier ?? null;
282
+ }
283
+ /** frontier / billed — e.g. 17 = 17× cheaper; null if either is missing or billed is 0. */
284
+ get savingsFactor() {
285
+ const b = this.billedMicroUsd, f = this.frontierWouldHaveCostMicroUsd;
286
+ return b && f && b > 0 ? Math.round(f / b * 10) / 10 : null;
287
+ }
258
288
  };
259
289
  var ChatCompletionChunk = class extends ChatCompletion {
260
290
  };
@@ -341,6 +371,11 @@ var EvalSet = class extends BaseModel {
341
371
  get scoringStrategy() {
342
372
  return this.raw.scoring_strategy ?? null;
343
373
  }
374
+ /** The one-sentence success criterion this set was created with (CB1:
375
+ * DATA + INTENT). Required at create. */
376
+ get intent() {
377
+ return this.raw.intent ?? null;
378
+ }
344
379
  };
345
380
  function evalSetFromCreate(raw) {
346
381
  return new EvalSet(raw?.eval_set ?? {});
@@ -348,6 +383,60 @@ function evalSetFromCreate(raw) {
348
383
  function evalSetList(raw) {
349
384
  return (raw?.eval_sets ?? []).map((e) => new EvalSet(e));
350
385
  }
386
+ var ContractProposal = class extends BaseModel {
387
+ get taskId() {
388
+ return this.raw.task_id ?? null;
389
+ }
390
+ get confidence() {
391
+ return this.raw.confidence ?? null;
392
+ }
393
+ get evidence() {
394
+ return this.raw.evidence ?? {};
395
+ }
396
+ get warning() {
397
+ return this.raw.warning ?? null;
398
+ }
399
+ };
400
+ var ProposalResult = class extends BaseModel {
401
+ get proposals() {
402
+ return (this.raw.proposals ?? []).map((p) => new ContractProposal(p));
403
+ }
404
+ get homogeneous() {
405
+ return Boolean(this.raw.homogeneous);
406
+ }
407
+ get split() {
408
+ return this.raw.split ?? null;
409
+ }
410
+ get conflict() {
411
+ return this.raw.conflict ?? null;
412
+ }
413
+ get closestTask() {
414
+ return this.raw.closest_task ?? null;
415
+ }
416
+ get intent() {
417
+ return this.raw.intent ?? null;
418
+ }
419
+ get message() {
420
+ return this.raw.message ?? null;
421
+ }
422
+ /** True when the binder matched exactly ONE homogeneous SPECIFIC contract at
423
+ * high/medium confidence and no conflict/split — the case `create` auto-binds
424
+ * without a human confirming. The `custom-eval` universal FLOOR is EXCLUDED:
425
+ * the binder offers it only when no specific contract fits, and per the
426
+ * precision ladder the floor is a CHOICE — the user opts in with
427
+ * `task: "custom-eval"`, never a silent auto-bind. */
428
+ get isClean() {
429
+ const props = this.proposals;
430
+ return this.homogeneous && this.conflict == null && this.split == null && props.length === 1 && (props[0].confidence === "high" || props[0].confidence === "medium") && Boolean(props[0].taskId) && props[0].taskId !== "custom-eval";
431
+ }
432
+ /** The task `create` would auto-bind, or null if the result isn't clean. */
433
+ get boundTask() {
434
+ return this.isClean ? this.proposals[0].taskId : null;
435
+ }
436
+ };
437
+ function proposalResult(raw) {
438
+ return new ProposalResult(raw ?? {});
439
+ }
351
440
  var EvalItemResult = class extends BaseModel {
352
441
  get idx() {
353
442
  return this.raw.idx ?? null;
@@ -585,10 +674,14 @@ var Completions = class {
585
674
  cast: (raw) => new ChatCompletionChunk(raw)
586
675
  });
587
676
  }
677
+ let headers;
588
678
  return this.client.request("POST", PATH, {
589
679
  body,
590
- cast: (raw) => new ChatCompletion(raw)
591
- });
680
+ cast: (raw) => new ChatCompletion(raw),
681
+ onHeaders: (h) => {
682
+ headers = h;
683
+ }
684
+ }).then((completion) => completion.withCost(headers));
592
685
  }
593
686
  };
594
687
  var Chat = class {
@@ -641,6 +734,7 @@ var Tasks = class {
641
734
 
642
735
  // src/resources/evals.ts
643
736
  var BASE2 = "/v1/eval-sets";
737
+ var PROPOSE = "/v1/eval-sets/propose-contract";
644
738
  var RUNS = "/v1/eval-runs";
645
739
  var FRONTIER = "/v1/eval/frontier-models";
646
740
  var INLINE_MAX = 5 * 1024 * 1024;
@@ -684,14 +778,59 @@ async function toBlob(file, mimeOverride) {
684
778
  const mime = mimeOverride || "application/octet-stream";
685
779
  return { blob: new Blob([bytes], { type: mime }), filename: "upload", size: bytes.byteLength, mime };
686
780
  }
687
- function itemsJsonl(task, items, name) {
781
+ function requireIntent(intent) {
782
+ const s = (intent ?? "").trim();
783
+ if (!s) {
784
+ throw new ParetaError(
785
+ 'intent is required: one sentence describing what the model should do with each item (e.g. "extract vendor, total and date from each invoice"). Pass intent to evals.create / proposeContract.'
786
+ );
787
+ }
788
+ return s.slice(0, 500);
789
+ }
790
+ function itemsJsonl(task, items, intent, name) {
688
791
  if (!items || items.length === 0) throw new ParetaError("items is required and must be non-empty");
689
792
  const jsonl = items.map((it) => JSON.stringify(it)).join("\n");
690
793
  return {
691
794
  files: { items: { filename: `items.${task}.jsonl`, content: jsonl, contentType: "application/jsonl" } },
692
- data: { task_id: task, name: name || `sdk eval set (${items.length} items)` }
795
+ data: { task_id: task, intent, name: name || `sdk eval set (${items.length} items)` }
796
+ };
797
+ }
798
+ function proposeMultipart(items, intent) {
799
+ if (!items || items.length === 0) throw new ParetaError("items is required and must be non-empty");
800
+ const jsonl = items.map((it) => JSON.stringify(it)).join("\n");
801
+ return {
802
+ files: { items: { filename: "items.jsonl", content: jsonl, contentType: "application/jsonl" } },
803
+ data: { intent }
693
804
  };
694
805
  }
806
+ function bindError(result) {
807
+ const intent = result.intent ?? "";
808
+ if (result.conflict) {
809
+ const c = result.conflict;
810
+ return new ParetaError(
811
+ `intent '${intent}' describes a different job than the data's shape supports (reads as '${c.intended_task}': ${c.reasoning}). Pass a task to pin a contract, or revise the intent.`
812
+ );
813
+ }
814
+ if (result.split) {
815
+ const s = result.split;
816
+ return new ParetaError(
817
+ `the dataset looks MIXED \u2014 ${s.validated_n}/${s.total_n} items fit '${s.closest_task}', the rest a different shape. Split the set or pass a task.`
818
+ );
819
+ }
820
+ const props = result.proposals;
821
+ if (props.length === 1 && props[0].taskId === "custom-eval") {
822
+ const warn = props[0].warning ? ` (${props[0].warning})` : "";
823
+ return new ParetaError(
824
+ `no specific grading contract fits this data for intent '${intent}'. The custom-eval universal floor is available \u2014 it grades by judged win-rate vs the frontier anchor.${warn} Pass task: "custom-eval" to use it, or revise the data/intent for a specific contract.`
825
+ );
826
+ }
827
+ const options = props.map((p) => p.taskId).filter(Boolean);
828
+ if (options.length === 0 && result.closestTask) options.push(result.closestTask);
829
+ const hint = options.length ? ` Candidates: ${options.join(", ")}.` : "";
830
+ return new ParetaError(
831
+ `could not confidently bind a grading contract for intent '${intent}'.${hint} Pass a task to pin one, or inspect evals.proposeContract({ items, intent }).`
832
+ );
833
+ }
695
834
  function mergeCandidates(models, frontierIds) {
696
835
  const cands = [...models ?? [], ...frontierIds ?? []];
697
836
  if (cands.length === 0) throw new ParetaError("models is required (the open candidates to evaluate)");
@@ -712,10 +851,30 @@ var EvalSets = class {
712
851
  this.client = client;
713
852
  }
714
853
  client;
715
- create(params) {
716
- const { files, data } = itemsJsonl(params.task, params.items, params.name);
854
+ /**
855
+ * Persist an eval set from your rows. `intent` is REQUIRED (v2): one sentence
856
+ * on what the model should do with each item. `task` is OPTIONAL — omit it and
857
+ * the binder resolves your intent + the data's shape to a grading contract
858
+ * (auto-binds a clean single match; throws with the proposals otherwise). Pass
859
+ * `task` to pin one explicitly.
860
+ */
861
+ async create(params) {
862
+ const intent = requireIntent(params.intent);
863
+ let task = params.task;
864
+ if (task == null) {
865
+ const proposal = await this.proposeContract(params.items, intent);
866
+ task = proposal.boundTask ?? void 0;
867
+ if (task == null) throw bindError(proposal);
868
+ }
869
+ const { files, data } = itemsJsonl(task, params.items, intent, params.name);
717
870
  return this.client.request("POST", BASE2, { files, data, cast: evalSetFromCreate });
718
871
  }
872
+ /** Internal: the binder call `create` uses when no task is pinned. Public on
873
+ * `Evals` as `proposeContract`; kept here so `sets.create` can reach it. */
874
+ proposeContract(items, intent) {
875
+ const { files, data } = proposeMultipart(items, requireIntent(intent));
876
+ return this.client.request("POST", PROPOSE, { files, data, cast: proposalResult });
877
+ }
719
878
  list() {
720
879
  return this.client.request("GET", BASE2, { cast: evalSetList });
721
880
  }
@@ -783,10 +942,15 @@ var EvalRuns = class {
783
942
  async create(params) {
784
943
  let evalSet = params.evalSet;
785
944
  if (evalSet == null) {
786
- if (!(params.task && params.items)) {
787
- throw new ParetaError("pass evalSet: <id>, or task + items to create one");
945
+ if (!params.items) {
946
+ throw new ParetaError("pass evalSet: <id>, or items (+ intent) to create one");
788
947
  }
789
- evalSet = (await this.sets.create({ task: params.task, items: params.items, name: params.name })).id ?? void 0;
948
+ evalSet = (await this.sets.create({
949
+ items: params.items,
950
+ intent: params.intent,
951
+ task: params.task,
952
+ name: params.name
953
+ })).id ?? void 0;
790
954
  }
791
955
  const frontierIds = await this.frontierIds(params.frontier, evalSet, params.task);
792
956
  const candidateModelIds = mergeCandidates(params.models, frontierIds);
@@ -826,6 +990,15 @@ var Evals = class {
826
990
  this.client = client;
827
991
  }
828
992
  client;
993
+ /**
994
+ * Which grading contract fits your data under your stated `intent`? Stateless
995
+ * discovery — nothing is persisted. Returns a ProposalResult (ranked
996
+ * proposals, the auto-bind decision, conflict/split reporting). `sets.create`
997
+ * calls this under the hood; use it directly to preview the binding first.
998
+ */
999
+ proposeContract(params) {
1000
+ return this.sets.proposeContract(params.items, params.intent);
1001
+ }
829
1002
  /**
830
1003
  * The frontier (vendor) roster you can evaluate against. With `task`, each is
831
1004
  * annotated `benchmarked` + the roster is vision-filtered for document tasks.
@@ -1171,6 +1344,7 @@ var Pareta = class _Pareta {
1171
1344
  break;
1172
1345
  }
1173
1346
  if (res.ok) {
1347
+ if (opts.onHeaders) opts.onHeaders(res.headers);
1174
1348
  const text = await res.text();
1175
1349
  const raw = text ? JSON.parse(text) : {};
1176
1350
  return cast ? cast(raw) : raw;
@@ -1248,6 +1422,7 @@ exports.ChatCompletion = ChatCompletion;
1248
1422
  exports.ChatCompletionChunk = ChatCompletionChunk;
1249
1423
  exports.Choice = Choice;
1250
1424
  exports.ConflictError = ConflictError;
1425
+ exports.ContractProposal = ContractProposal;
1251
1426
  exports.Embeddings = Embeddings;
1252
1427
  exports.EndpointNotReadyError = EndpointNotReadyError;
1253
1428
  exports.EvalItemResult = EvalItemResult;
@@ -1267,6 +1442,7 @@ exports.NotFoundError = NotFoundError;
1267
1442
  exports.Pareta = Pareta;
1268
1443
  exports.ParetaError = ParetaError;
1269
1444
  exports.PermissionDeniedError = PermissionDeniedError;
1445
+ exports.ProposalResult = ProposalResult;
1270
1446
  exports.RateLimitError = RateLimitError;
1271
1447
  exports.Rerank = Rerank;
1272
1448
  exports.RerankResult = RerankResult;