textopt 0.0.0 → 0.2.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.
Files changed (65) hide show
  1. package/README.md +65 -25
  2. package/dist/bootstrap-search/index.cjs +159 -73
  3. package/dist/bootstrap-search/index.d.cts +32 -10
  4. package/dist/bootstrap-search/index.d.mts +32 -10
  5. package/dist/bootstrap-search/index.mjs +150 -66
  6. package/dist/demos-9v5ts7F3.cjs +244 -0
  7. package/dist/{demos-B0pVQjYC.d.mts → demos-ASsSXYXA.d.mts} +10 -3
  8. package/dist/demos-Brobjfuc.mjs +215 -0
  9. package/dist/{demos-BTuzFNsp.d.cts → demos-ByaLZy-Z.d.cts} +10 -3
  10. package/dist/file-cache.cjs +27 -8
  11. package/dist/file-cache.d.cts +13 -0
  12. package/dist/file-cache.d.mts +13 -0
  13. package/dist/file-cache.mjs +27 -8
  14. package/dist/gepa/index.cjs +128 -80
  15. package/dist/gepa/index.d.cts +15 -7
  16. package/dist/gepa/index.d.mts +15 -7
  17. package/dist/gepa/index.mjs +101 -55
  18. package/dist/index.cjs +157 -30
  19. package/dist/index.d.cts +177 -7
  20. package/dist/index.d.mts +177 -7
  21. package/dist/index.mjs +139 -18
  22. package/dist/{math-COOofUyv.cjs → math-BhlziRPc.cjs} +60 -9
  23. package/dist/math-Dqme4rYz.mjs +123 -0
  24. package/dist/mipro/index.cjs +104 -70
  25. package/dist/mipro/index.d.cts +17 -14
  26. package/dist/mipro/index.d.mts +17 -14
  27. package/dist/mipro/index.mjs +90 -58
  28. package/dist/opro/index.cjs +136 -51
  29. package/dist/opro/index.d.cts +17 -9
  30. package/dist/opro/index.d.mts +17 -9
  31. package/dist/opro/index.mjs +121 -38
  32. package/dist/{optimizer-B7SpRwl7.d.cts → optimizer-4Zv-Zt2t.d.cts} +90 -5
  33. package/dist/{optimizer-DqCoth_w.d.mts → optimizer-Ds5mzYjz.d.mts} +90 -5
  34. package/dist/random-search/index.cjs +99 -49
  35. package/dist/random-search/index.d.cts +15 -13
  36. package/dist/random-search/index.d.mts +15 -13
  37. package/dist/random-search/index.mjs +89 -41
  38. package/dist/{reflection-Cr_upzU0.d.mts → reflection-CMezGu6u.d.mts} +38 -14
  39. package/dist/{reflection-CQToe-5B.d.cts → reflection-D0A7eahD.d.cts} +38 -14
  40. package/dist/reporting-bq007_2z.d.cts +294 -0
  41. package/dist/reporting-bq007_2z.d.mts +294 -0
  42. package/dist/simba/index.cjs +216 -83
  43. package/dist/simba/index.d.cts +53 -13
  44. package/dist/simba/index.d.mts +53 -13
  45. package/dist/simba/index.mjs +206 -75
  46. package/dist/testing.cjs +1 -0
  47. package/dist/testing.d.cts +5 -3
  48. package/dist/testing.d.mts +5 -3
  49. package/dist/testing.mjs +1 -1
  50. package/dist/{evaluation-OZOp6TB7.cjs → warnings-CWRJF-jA.cjs} +228 -5
  51. package/dist/{evaluation-BV0nSZVx.mjs → warnings-OxvDi9kN.mjs} +175 -6
  52. package/docs/adapters.md +169 -0
  53. package/docs/benchmark.md +90 -0
  54. package/docs/data-prep.md +113 -0
  55. package/docs/distillation.md +128 -0
  56. package/docs/evaluation.md +87 -0
  57. package/docs/metric-preflight.md +132 -0
  58. package/docs/optimizers.md +293 -0
  59. package/docs/tuning.md +130 -0
  60. package/package.json +6 -4
  61. package/dist/demos-B9BJiNKz.cjs +0 -143
  62. package/dist/demos-Degx6UmP.mjs +0 -126
  63. package/dist/math-DhrDmpFS.mjs +0 -78
  64. package/dist/types-CWv4IQFF.d.cts +0 -129
  65. package/dist/types-CWv4IQFF.d.mts +0 -129
@@ -1,143 +0,0 @@
1
- const require_evaluation = require("./evaluation-OZOp6TB7.cjs");
2
- //#region src/demos.ts
3
- const DEMO_OPEN = "<demo>";
4
- const DEMO_CLOSE = "</demo>";
5
- const DEMO_BLOCK = /<demo>\s*([\s\S]*?)\s*<\/demo>/g;
6
- const DEMO_PARTS = /<input>\s*([\s\S]*?)\s*<\/input>\s*<output>\s*([\s\S]*?)\s*<\/output>/;
7
- const DEFAULT_MAX_DEMOS = 4;
8
- /**
9
- * Harvest demonstrations by running a candidate over the training set and keeping
10
- * the rollouts the metric rewarded.
11
- *
12
- * The cheapest signal in the whole library: a rollout that scored well is
13
- * already paid for, and turning it into a few-shot block costs one pass over
14
- * the data rather than a search. Instruction search and demonstrations pull on
15
- * different parts of a model's behaviour — instructions on what to do,
16
- * examples on what the output should look like — so a seed carrying both
17
- * starts somewhere neither reaches alone.
18
- */
19
- async function bootstrapDemos(args) {
20
- const { adapter, candidate, trainingSet, minScore, maxDemos = DEFAULT_MAX_DEMOS, batchSize = maxDemos, maxMetricCalls = trainingSet.length, rng, renderDemo, signal } = args;
21
- if (trainingSet.length === 0) throw new Error("bootstrapDemos requires a non-empty trainingSet");
22
- const budget = require_evaluation.createBudget({ maxMetricCalls });
23
- const evaluator = require_evaluation.createEvaluator({
24
- adapter,
25
- budget,
26
- ...signal === void 0 ? {} : { signal }
27
- });
28
- const order = rng === void 0 ? [...trainingSet] : rng.shuffle(trainingSet);
29
- const demos = [];
30
- let attempted = 0;
31
- for (let start = 0; start < order.length; start += batchSize) {
32
- if (demos.length >= maxDemos || signal?.aborted) break;
33
- const batch = order.slice(start, start + Math.min(batchSize, budget.remaining()));
34
- if (batch.length === 0) break;
35
- const evaluation = await evaluator.evaluateTraced({
36
- candidate,
37
- batch,
38
- split: "train",
39
- phase: "seed",
40
- candidateId: null,
41
- iteration: 0
42
- });
43
- if (evaluation === null) break;
44
- attempted += batch.length;
45
- for (let index = 0; index < batch.length; index += 1) {
46
- const score = evaluation.scores[index];
47
- if (!(minScore === void 0 ? score > 0 : score >= minScore) || demos.length >= maxDemos) continue;
48
- demos.push({
49
- input: batch[index],
50
- output: evaluation.outputs[index],
51
- score
52
- });
53
- }
54
- }
55
- return {
56
- demos,
57
- block: formatDemos(demos, renderDemo === void 0 ? {} : { render: renderDemo }),
58
- metricCalls: budget.spent(),
59
- attempted
60
- };
61
- }
62
- /**
63
- * Render demos as the text a candidate component holds.
64
- *
65
- * Delimited rather than free-form so `parseDemos` can read them back: a demo
66
- * component is edited over the course of a run, and a block that cannot be
67
- * parsed can only be replaced wholesale, throwing away every example found
68
- * before it.
69
- */
70
- function formatDemos(demos, options = {}) {
71
- const { render = renderDefault } = options;
72
- if (demos.length === 0) return "";
73
- return demos.map((demo, index) => `${DEMO_OPEN}\n${render({
74
- demo,
75
- index
76
- })}\n${DEMO_CLOSE}`).join("\n");
77
- }
78
- /**
79
- * Recover the demos from a formatted block, ignoring anything written around
80
- * them. Text a model rewrote and mangled yields the demos it left intact
81
- * rather than throwing: a malformed example is worth less than the rest of the
82
- * block, not more than it.
83
- */
84
- function parseDemos(text) {
85
- const demos = [];
86
- for (const match of text.matchAll(DEMO_BLOCK)) {
87
- const parts = (match[1] ?? "").match(DEMO_PARTS);
88
- if (parts === null) continue;
89
- const input = parseValue(parts[1] ?? "");
90
- const output = parseValue(parts[2] ?? "");
91
- demos.push({
92
- input,
93
- output
94
- });
95
- }
96
- return demos;
97
- }
98
- function renderDefault(args) {
99
- const { demo } = args;
100
- return [
101
- "<input>",
102
- serialize(demo.input),
103
- "</input>",
104
- "<output>",
105
- serialize(demo.output),
106
- "</output>"
107
- ].join("\n");
108
- }
109
- /** Strings stay as they are; anything else is shown as JSON. */
110
- function serialize(value) {
111
- if (typeof value === "string") return value;
112
- try {
113
- return JSON.stringify(value, null, 2) ?? String(value);
114
- } catch {
115
- return String(value);
116
- }
117
- }
118
- function parseValue(text) {
119
- try {
120
- return JSON.parse(text);
121
- } catch {
122
- return text;
123
- }
124
- }
125
- //#endregion
126
- Object.defineProperty(exports, "bootstrapDemos", {
127
- enumerable: true,
128
- get: function() {
129
- return bootstrapDemos;
130
- }
131
- });
132
- Object.defineProperty(exports, "formatDemos", {
133
- enumerable: true,
134
- get: function() {
135
- return formatDemos;
136
- }
137
- });
138
- Object.defineProperty(exports, "parseDemos", {
139
- enumerable: true,
140
- get: function() {
141
- return parseDemos;
142
- }
143
- });
@@ -1,126 +0,0 @@
1
- import { p as createBudget, r as createEvaluator } from "./evaluation-BV0nSZVx.mjs";
2
- //#region src/demos.ts
3
- const DEMO_OPEN = "<demo>";
4
- const DEMO_CLOSE = "</demo>";
5
- const DEMO_BLOCK = /<demo>\s*([\s\S]*?)\s*<\/demo>/g;
6
- const DEMO_PARTS = /<input>\s*([\s\S]*?)\s*<\/input>\s*<output>\s*([\s\S]*?)\s*<\/output>/;
7
- const DEFAULT_MAX_DEMOS = 4;
8
- /**
9
- * Harvest demonstrations by running a candidate over the training set and keeping
10
- * the rollouts the metric rewarded.
11
- *
12
- * The cheapest signal in the whole library: a rollout that scored well is
13
- * already paid for, and turning it into a few-shot block costs one pass over
14
- * the data rather than a search. Instruction search and demonstrations pull on
15
- * different parts of a model's behaviour — instructions on what to do,
16
- * examples on what the output should look like — so a seed carrying both
17
- * starts somewhere neither reaches alone.
18
- */
19
- async function bootstrapDemos(args) {
20
- const { adapter, candidate, trainingSet, minScore, maxDemos = DEFAULT_MAX_DEMOS, batchSize = maxDemos, maxMetricCalls = trainingSet.length, rng, renderDemo, signal } = args;
21
- if (trainingSet.length === 0) throw new Error("bootstrapDemos requires a non-empty trainingSet");
22
- const budget = createBudget({ maxMetricCalls });
23
- const evaluator = createEvaluator({
24
- adapter,
25
- budget,
26
- ...signal === void 0 ? {} : { signal }
27
- });
28
- const order = rng === void 0 ? [...trainingSet] : rng.shuffle(trainingSet);
29
- const demos = [];
30
- let attempted = 0;
31
- for (let start = 0; start < order.length; start += batchSize) {
32
- if (demos.length >= maxDemos || signal?.aborted) break;
33
- const batch = order.slice(start, start + Math.min(batchSize, budget.remaining()));
34
- if (batch.length === 0) break;
35
- const evaluation = await evaluator.evaluateTraced({
36
- candidate,
37
- batch,
38
- split: "train",
39
- phase: "seed",
40
- candidateId: null,
41
- iteration: 0
42
- });
43
- if (evaluation === null) break;
44
- attempted += batch.length;
45
- for (let index = 0; index < batch.length; index += 1) {
46
- const score = evaluation.scores[index];
47
- if (!(minScore === void 0 ? score > 0 : score >= minScore) || demos.length >= maxDemos) continue;
48
- demos.push({
49
- input: batch[index],
50
- output: evaluation.outputs[index],
51
- score
52
- });
53
- }
54
- }
55
- return {
56
- demos,
57
- block: formatDemos(demos, renderDemo === void 0 ? {} : { render: renderDemo }),
58
- metricCalls: budget.spent(),
59
- attempted
60
- };
61
- }
62
- /**
63
- * Render demos as the text a candidate component holds.
64
- *
65
- * Delimited rather than free-form so `parseDemos` can read them back: a demo
66
- * component is edited over the course of a run, and a block that cannot be
67
- * parsed can only be replaced wholesale, throwing away every example found
68
- * before it.
69
- */
70
- function formatDemos(demos, options = {}) {
71
- const { render = renderDefault } = options;
72
- if (demos.length === 0) return "";
73
- return demos.map((demo, index) => `${DEMO_OPEN}\n${render({
74
- demo,
75
- index
76
- })}\n${DEMO_CLOSE}`).join("\n");
77
- }
78
- /**
79
- * Recover the demos from a formatted block, ignoring anything written around
80
- * them. Text a model rewrote and mangled yields the demos it left intact
81
- * rather than throwing: a malformed example is worth less than the rest of the
82
- * block, not more than it.
83
- */
84
- function parseDemos(text) {
85
- const demos = [];
86
- for (const match of text.matchAll(DEMO_BLOCK)) {
87
- const parts = (match[1] ?? "").match(DEMO_PARTS);
88
- if (parts === null) continue;
89
- const input = parseValue(parts[1] ?? "");
90
- const output = parseValue(parts[2] ?? "");
91
- demos.push({
92
- input,
93
- output
94
- });
95
- }
96
- return demos;
97
- }
98
- function renderDefault(args) {
99
- const { demo } = args;
100
- return [
101
- "<input>",
102
- serialize(demo.input),
103
- "</input>",
104
- "<output>",
105
- serialize(demo.output),
106
- "</output>"
107
- ].join("\n");
108
- }
109
- /** Strings stay as they are; anything else is shown as JSON. */
110
- function serialize(value) {
111
- if (typeof value === "string") return value;
112
- try {
113
- return JSON.stringify(value, null, 2) ?? String(value);
114
- } catch {
115
- return String(value);
116
- }
117
- }
118
- function parseValue(text) {
119
- try {
120
- return JSON.parse(text);
121
- } catch {
122
- return text;
123
- }
124
- }
125
- //#endregion
126
- export { formatDemos as n, parseDemos as r, bootstrapDemos as t };
@@ -1,78 +0,0 @@
1
- //#region src/math.ts
2
- function argmax(values) {
3
- let bestIndex = 0;
4
- let bestValue = Number.NEGATIVE_INFINITY;
5
- for (let index = 0; index < values.length; index += 1) {
6
- const value = values[index];
7
- if (value > bestValue) {
8
- bestValue = value;
9
- bestIndex = index;
10
- }
11
- }
12
- return bestIndex;
13
- }
14
- /** Mean over the values that exist; unscored instances are not zeros. */
15
- function mean(values) {
16
- let total = 0;
17
- let count = 0;
18
- for (const value of values) if (value !== void 0) {
19
- total += value;
20
- count += 1;
21
- }
22
- return count === 0 ? 0 : total / count;
23
- }
24
- function sum(values) {
25
- let total = 0;
26
- for (const value of values) total += value;
27
- return total;
28
- }
29
- /**
30
- * One-sided p-value from a paired sign-flip (permutation) test: how often a
31
- * total this large arises when each difference is equally likely to have come
32
- * out the other way. Exact by enumeration for small samples, normal-approximated
33
- * past `maxExact` where 2^n stops being cheap.
34
- *
35
- * Paired and distribution-free, which is what a comparison of two runs over the
36
- * same instances needs — the scores are not independent draws and are not
37
- * normal, so a t-test on them is answering a different question.
38
- */
39
- function signFlipPValue(args) {
40
- const { differences, observed, maxExact } = args;
41
- const moved = differences.filter((difference) => difference !== 0);
42
- if (moved.length === 0) return 1;
43
- if (moved.length > maxExact) return normalTailProbability({
44
- differences: moved,
45
- observed
46
- });
47
- const assignments = 2 ** moved.length;
48
- let atLeastAsExtreme = 0;
49
- for (let mask = 0; mask < assignments; mask += 1) {
50
- let total = 0;
51
- for (let index = 0; index < moved.length; index += 1) {
52
- const sign = mask >> index & 1 ? -1 : 1;
53
- total += sign * moved[index];
54
- }
55
- if (total >= observed) atLeastAsExtreme += 1;
56
- }
57
- return atLeastAsExtreme / assignments;
58
- }
59
- /**
60
- * The same tail probability from a normal approximation, for batches too large
61
- * to enumerate. Under the sign-flip null each difference has mean zero and
62
- * variance equal to its square, so the total's variance is their sum.
63
- */
64
- function normalTailProbability(args) {
65
- const { differences, observed } = args;
66
- const variance = differences.reduce((total, difference) => total + difference * difference, 0);
67
- if (variance === 0) return observed > 0 ? 0 : 1;
68
- return 1 - standardNormalCdf(observed / Math.sqrt(variance));
69
- }
70
- /** Abramowitz and Stegun 7.1.26, which is accurate to about 1e-7. */
71
- function standardNormalCdf(value) {
72
- const sign = value < 0 ? -1 : 1;
73
- const scaled = Math.abs(value) / Math.SQRT2;
74
- const t = 1 / (1 + .3275911 * scaled);
75
- return .5 * (1 + sign * (1 - t * (.254829592 + t * (-.284496736 + t * (1.421413741 + t * (-1.453152027 + t * 1.061405429)))) * Math.exp(-scaled * scaled)));
76
- }
77
- //#endregion
78
- export { sum as i, mean as n, signFlipPValue as r, argmax as t };
@@ -1,129 +0,0 @@
1
- //#region src/types.d.ts
2
- /**
3
- * A candidate is a map of named text components to their current text. This is
4
- * the unit of optimization — prompts, instructions, code, tool descriptions,
5
- * anything expressible as a named string.
6
- *
7
- * `K` is the union of component names, inferred from the seed candidate, so a
8
- * misspelled component is a compile error rather than a silent no-op.
9
- */
10
- type Candidate<K extends string = string> = Record<K, string>;
11
- /**
12
- * What one rollout consumed. Every field is optional because providers report
13
- * different subsets, and a partial reading is still worth more than none.
14
- */
15
- interface RolloutUsage {
16
- inputTokens?: number;
17
- outputTokens?: number;
18
- /** Defaults to the sum of the two token counts when they are reported. */
19
- totalTokens?: number;
20
- costUsd?: number;
21
- }
22
- /** Usage summed over a run, alongside the rollouts that produced it. */
23
- interface UsageTotals {
24
- inputTokens: number;
25
- outputTokens: number;
26
- totalTokens: number;
27
- costUsd: number;
28
- /** Fresh rollouts counted here. Cached instances buy nothing. */
29
- rollouts: number;
30
- }
31
- /**
32
- * Result of running a candidate over a batch of data instances.
33
- *
34
- * `scores` is the load-bearing field: one number per instance, higher is
35
- * better. `feedback` is a per-instance textual diagnosis of what went wrong,
36
- * which a reflective optimizer reads to write a better candidate.
37
- */
38
- interface EvaluationBatch<Trajectory = unknown, Output = unknown> {
39
- outputs: Output[];
40
- scores: number[];
41
- /**
42
- * What each rollout consumed. Rollout counts are the budget, but they are a
43
- * poor proxy for spend: reflective search grows the text it optimizes, so
44
- * the same rollout costs more late in a run than early in it.
45
- */
46
- usage?: RolloutUsage[];
47
- feedback?: string[];
48
- trajectories?: Trajectory[];
49
- objectiveScores?: Record<string, number>[];
50
- /**
51
- * Per-instance: true when the score reflects an infrastructure failure
52
- * rather than the candidate's behaviour. Transient scores are never written
53
- * to the evaluation cache.
54
- */
55
- transient?: boolean[];
56
- }
57
- /**
58
- * What a per-instance scorer returns. Shared by every adapter so scorers are
59
- * portable between them — a Braintrust scorer works in a LangChain run.
60
- */
61
- interface ScoreResult {
62
- score: number;
63
- feedback?: string;
64
- objectiveScores?: Record<string, number>;
65
- /** What this rollout consumed, when the caller can see it. */
66
- usage?: RolloutUsage;
67
- /**
68
- * Marks a score produced by an infrastructure failure — a rate limit, a
69
- * network blip, a provider 5xx — rather than by the candidate. Without this
70
- * the engine cannot tell such a zero from a genuine one, and would cache it
71
- * permanently against the candidate.
72
- */
73
- transient?: boolean;
74
- }
75
- interface EvaluateArgs<Datum, K extends string = string> {
76
- batch: readonly Datum[];
77
- candidate: Candidate<K>;
78
- captureTraces: boolean;
79
- /**
80
- * Where this batch sits in the run. Forward it to whatever tracing the
81
- * system under optimization already has — without it a run is thousands of
82
- * indistinguishable rollouts, and no trace can be tied back to the iteration
83
- * whose score moved.
84
- */
85
- run: EvaluationContext;
86
- signal?: AbortSignal;
87
- }
88
- /**
89
- * Identifies one evaluation within a run. `candidateId` is null while the
90
- * candidate is still a proposal being screened on a minibatch: it has no
91
- * record, and inventing an id for it would collide with the one it gets if it
92
- * is accepted.
93
- */
94
- interface EvaluationContext {
95
- iteration: number;
96
- phase: EvaluationPhase;
97
- split: EvaluationSplit;
98
- candidateId: number | null;
99
- }
100
- type EvaluationPhase = "seed" | "minibatch" | "validation" | "test";
101
- /**
102
- * Which dataset an instance id was drawn from. Each split numbers its ids
103
- * independently, so the same id can name three different instances; the cache
104
- * key has to keep them apart.
105
- */
106
- type EvaluationSplit = "train" | "val" | "test";
107
- /**
108
- * The single integration seam between an optimizer and a system under
109
- * optimization. Everything framework-specific — LangChain, the AI SDK,
110
- * Braintrust — lives in an implementation of this interface.
111
- */
112
- interface Adapter<Datum, Trajectory = unknown, Output = unknown, K extends string = string> {
113
- evaluate(args: EvaluateArgs<Datum, K>): Promise<EvaluationBatch<Trajectory, Output>> | EvaluationBatch<Trajectory, Output>;
114
- }
115
- /** Provider-agnostic text model: text in, text out. */
116
- type TextModel = (args: {
117
- prompt: string;
118
- signal?: AbortSignal;
119
- }) => Promise<string>;
120
- /**
121
- * The component names of a candidate, as the union they were inferred from.
122
- *
123
- * `Object.keys` widens a closed key union back to `string`. This is the one
124
- * place that narrowing happens, so every other caller stays assertion-free.
125
- * Accepts a partial so it also names the components of a component patch.
126
- */
127
- declare function componentNames<K extends string>(candidate: Partial<Candidate<K>>): K[];
128
- //#endregion
129
- export { EvaluationContext as a, RolloutUsage as c, UsageTotals as d, componentNames as f, EvaluationBatch as i, ScoreResult as l, Candidate as n, EvaluationPhase as o, EvaluateArgs as r, EvaluationSplit as s, Adapter as t, TextModel as u };
@@ -1,129 +0,0 @@
1
- //#region src/types.d.ts
2
- /**
3
- * A candidate is a map of named text components to their current text. This is
4
- * the unit of optimization — prompts, instructions, code, tool descriptions,
5
- * anything expressible as a named string.
6
- *
7
- * `K` is the union of component names, inferred from the seed candidate, so a
8
- * misspelled component is a compile error rather than a silent no-op.
9
- */
10
- type Candidate<K extends string = string> = Record<K, string>;
11
- /**
12
- * What one rollout consumed. Every field is optional because providers report
13
- * different subsets, and a partial reading is still worth more than none.
14
- */
15
- interface RolloutUsage {
16
- inputTokens?: number;
17
- outputTokens?: number;
18
- /** Defaults to the sum of the two token counts when they are reported. */
19
- totalTokens?: number;
20
- costUsd?: number;
21
- }
22
- /** Usage summed over a run, alongside the rollouts that produced it. */
23
- interface UsageTotals {
24
- inputTokens: number;
25
- outputTokens: number;
26
- totalTokens: number;
27
- costUsd: number;
28
- /** Fresh rollouts counted here. Cached instances buy nothing. */
29
- rollouts: number;
30
- }
31
- /**
32
- * Result of running a candidate over a batch of data instances.
33
- *
34
- * `scores` is the load-bearing field: one number per instance, higher is
35
- * better. `feedback` is a per-instance textual diagnosis of what went wrong,
36
- * which a reflective optimizer reads to write a better candidate.
37
- */
38
- interface EvaluationBatch<Trajectory = unknown, Output = unknown> {
39
- outputs: Output[];
40
- scores: number[];
41
- /**
42
- * What each rollout consumed. Rollout counts are the budget, but they are a
43
- * poor proxy for spend: reflective search grows the text it optimizes, so
44
- * the same rollout costs more late in a run than early in it.
45
- */
46
- usage?: RolloutUsage[];
47
- feedback?: string[];
48
- trajectories?: Trajectory[];
49
- objectiveScores?: Record<string, number>[];
50
- /**
51
- * Per-instance: true when the score reflects an infrastructure failure
52
- * rather than the candidate's behaviour. Transient scores are never written
53
- * to the evaluation cache.
54
- */
55
- transient?: boolean[];
56
- }
57
- /**
58
- * What a per-instance scorer returns. Shared by every adapter so scorers are
59
- * portable between them — a Braintrust scorer works in a LangChain run.
60
- */
61
- interface ScoreResult {
62
- score: number;
63
- feedback?: string;
64
- objectiveScores?: Record<string, number>;
65
- /** What this rollout consumed, when the caller can see it. */
66
- usage?: RolloutUsage;
67
- /**
68
- * Marks a score produced by an infrastructure failure — a rate limit, a
69
- * network blip, a provider 5xx — rather than by the candidate. Without this
70
- * the engine cannot tell such a zero from a genuine one, and would cache it
71
- * permanently against the candidate.
72
- */
73
- transient?: boolean;
74
- }
75
- interface EvaluateArgs<Datum, K extends string = string> {
76
- batch: readonly Datum[];
77
- candidate: Candidate<K>;
78
- captureTraces: boolean;
79
- /**
80
- * Where this batch sits in the run. Forward it to whatever tracing the
81
- * system under optimization already has — without it a run is thousands of
82
- * indistinguishable rollouts, and no trace can be tied back to the iteration
83
- * whose score moved.
84
- */
85
- run: EvaluationContext;
86
- signal?: AbortSignal;
87
- }
88
- /**
89
- * Identifies one evaluation within a run. `candidateId` is null while the
90
- * candidate is still a proposal being screened on a minibatch: it has no
91
- * record, and inventing an id for it would collide with the one it gets if it
92
- * is accepted.
93
- */
94
- interface EvaluationContext {
95
- iteration: number;
96
- phase: EvaluationPhase;
97
- split: EvaluationSplit;
98
- candidateId: number | null;
99
- }
100
- type EvaluationPhase = "seed" | "minibatch" | "validation" | "test";
101
- /**
102
- * Which dataset an instance id was drawn from. Each split numbers its ids
103
- * independently, so the same id can name three different instances; the cache
104
- * key has to keep them apart.
105
- */
106
- type EvaluationSplit = "train" | "val" | "test";
107
- /**
108
- * The single integration seam between an optimizer and a system under
109
- * optimization. Everything framework-specific — LangChain, the AI SDK,
110
- * Braintrust — lives in an implementation of this interface.
111
- */
112
- interface Adapter<Datum, Trajectory = unknown, Output = unknown, K extends string = string> {
113
- evaluate(args: EvaluateArgs<Datum, K>): Promise<EvaluationBatch<Trajectory, Output>> | EvaluationBatch<Trajectory, Output>;
114
- }
115
- /** Provider-agnostic text model: text in, text out. */
116
- type TextModel = (args: {
117
- prompt: string;
118
- signal?: AbortSignal;
119
- }) => Promise<string>;
120
- /**
121
- * The component names of a candidate, as the union they were inferred from.
122
- *
123
- * `Object.keys` widens a closed key union back to `string`. This is the one
124
- * place that narrowing happens, so every other caller stays assertion-free.
125
- * Accepts a partial so it also names the components of a component patch.
126
- */
127
- declare function componentNames<K extends string>(candidate: Partial<Candidate<K>>): K[];
128
- //#endregion
129
- export { EvaluationContext as a, RolloutUsage as c, UsageTotals as d, componentNames as f, EvaluationBatch as i, ScoreResult as l, Candidate as n, EvaluationPhase as o, EvaluateArgs as r, EvaluationSplit as s, Adapter as t, TextModel as u };