textopt 0.0.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/LICENSE +21 -0
- package/README.md +509 -0
- package/dist/bootstrap-search/index.cjs +308 -0
- package/dist/bootstrap-search/index.d.cts +162 -0
- package/dist/bootstrap-search/index.d.mts +162 -0
- package/dist/bootstrap-search/index.mjs +307 -0
- package/dist/cache-CuSo0NJ8.d.cts +24 -0
- package/dist/cache-CuSo0NJ8.d.mts +24 -0
- package/dist/concurrency-C-cFzWW2.cjs +44 -0
- package/dist/concurrency-D58PWeSk.mjs +39 -0
- package/dist/demos-B0pVQjYC.d.mts +88 -0
- package/dist/demos-B9BJiNKz.cjs +143 -0
- package/dist/demos-BTuzFNsp.d.cts +88 -0
- package/dist/demos-Degx6UmP.mjs +126 -0
- package/dist/evaluation-BV0nSZVx.mjs +521 -0
- package/dist/evaluation-OZOp6TB7.cjs +598 -0
- package/dist/file-cache.cjs +70 -0
- package/dist/file-cache.d.cts +21 -0
- package/dist/file-cache.d.mts +21 -0
- package/dist/file-cache.mjs +69 -0
- package/dist/gepa/index.cjs +1671 -0
- package/dist/gepa/index.d.cts +385 -0
- package/dist/gepa/index.d.mts +385 -0
- package/dist/gepa/index.mjs +1652 -0
- package/dist/index.cjs +266 -0
- package/dist/index.d.cts +221 -0
- package/dist/index.d.mts +221 -0
- package/dist/index.mjs +245 -0
- package/dist/math-COOofUyv.cjs +101 -0
- package/dist/math-DhrDmpFS.mjs +78 -0
- package/dist/mipro/index.cjs +739 -0
- package/dist/mipro/index.d.cts +372 -0
- package/dist/mipro/index.d.mts +372 -0
- package/dist/mipro/index.mjs +736 -0
- package/dist/opro/index.cjs +487 -0
- package/dist/opro/index.d.cts +230 -0
- package/dist/opro/index.d.mts +230 -0
- package/dist/opro/index.mjs +485 -0
- package/dist/optimizer-B7SpRwl7.d.cts +288 -0
- package/dist/optimizer-DqCoth_w.d.mts +288 -0
- package/dist/random-search/index.cjs +321 -0
- package/dist/random-search/index.d.cts +156 -0
- package/dist/random-search/index.d.mts +156 -0
- package/dist/random-search/index.mjs +319 -0
- package/dist/reflection-CQToe-5B.d.cts +283 -0
- package/dist/reflection-Cr_upzU0.d.mts +283 -0
- package/dist/reflection-DRfbk6hu.cjs +249 -0
- package/dist/reflection-mwMhrjs_.mjs +214 -0
- package/dist/rng-BR5MOedA.d.cts +22 -0
- package/dist/rng-BR5MOedA.d.mts +22 -0
- package/dist/rng-DbA_rPIo.cjs +67 -0
- package/dist/rng-Dtc5eZ_W.mjs +62 -0
- package/dist/sampling-CfHt7Gue.mjs +59 -0
- package/dist/sampling-DFo_7RNJ.d.mts +23 -0
- package/dist/sampling-Dars7ctR.cjs +64 -0
- package/dist/sampling-axOwfZf5.d.cts +23 -0
- package/dist/simba/index.cjs +709 -0
- package/dist/simba/index.d.cts +289 -0
- package/dist/simba/index.d.mts +289 -0
- package/dist/simba/index.mjs +700 -0
- package/dist/testing.cjs +155 -0
- package/dist/testing.d.cts +53 -0
- package/dist/testing.d.mts +53 -0
- package/dist/testing.mjs +148 -0
- package/dist/text--v4Ffbus.mjs +21 -0
- package/dist/text-CK_HB3su.cjs +26 -0
- package/dist/types-CWv4IQFF.d.cts +129 -0
- package/dist/types-CWv4IQFF.d.mts +129 -0
- package/package.json +135 -0
package/dist/testing.cjs
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_reflection = require("./reflection-DRfbk6hu.cjs");
|
|
3
|
+
//#region src/testing.ts
|
|
4
|
+
const KEYWORD_EXAMPLES = [
|
|
5
|
+
{
|
|
6
|
+
question: "How do I reset a device?",
|
|
7
|
+
required: ["hold", "ten seconds"]
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
question: "How do I contact support?",
|
|
11
|
+
required: ["ticket", "portal"]
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
question: "What is the refund window?",
|
|
15
|
+
required: ["thirty days"]
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
question: "How do I upgrade a plan?",
|
|
19
|
+
required: ["billing", "prorated"]
|
|
20
|
+
}
|
|
21
|
+
];
|
|
22
|
+
/** Terms the keyword metric rewards, interleaved with terms it ignores. */
|
|
23
|
+
const SAMPLING_POOL = [
|
|
24
|
+
"hold",
|
|
25
|
+
"sprocket",
|
|
26
|
+
"ten seconds",
|
|
27
|
+
"lorem",
|
|
28
|
+
"ticket",
|
|
29
|
+
"widget",
|
|
30
|
+
"portal",
|
|
31
|
+
"colour",
|
|
32
|
+
"thirty days",
|
|
33
|
+
"ipsum",
|
|
34
|
+
"billing",
|
|
35
|
+
"gizmo",
|
|
36
|
+
"prorated",
|
|
37
|
+
"flange"
|
|
38
|
+
];
|
|
39
|
+
function createKeywordAdapter() {
|
|
40
|
+
return {
|
|
41
|
+
evaluate: ({ batch, candidate }) => {
|
|
42
|
+
const answer = Object.values(candidate).join(" ");
|
|
43
|
+
const trajectories = [];
|
|
44
|
+
const scores = [];
|
|
45
|
+
const feedback = [];
|
|
46
|
+
for (const example of batch) {
|
|
47
|
+
const missing = example.required.filter((term) => !answer.toLowerCase().includes(term.toLowerCase()));
|
|
48
|
+
const score = (example.required.length - missing.length) / example.required.length;
|
|
49
|
+
scores.push(score);
|
|
50
|
+
feedback.push(missing.length === 0 ? "All required terms present." : `Missing required terms: ${missing.join(", ")}`);
|
|
51
|
+
trajectories.push({
|
|
52
|
+
question: example.question,
|
|
53
|
+
instruction: answer,
|
|
54
|
+
answer,
|
|
55
|
+
missing
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
outputs: trajectories.map((trajectory) => trajectory.answer),
|
|
60
|
+
scores,
|
|
61
|
+
feedback,
|
|
62
|
+
trajectories
|
|
63
|
+
};
|
|
64
|
+
},
|
|
65
|
+
makeReflectiveDataset: ({ batch, evaluation, componentsToUpdate }) => {
|
|
66
|
+
const records = batch.map((example, index) => ({
|
|
67
|
+
inputs: { question: example.question },
|
|
68
|
+
generatedOutputs: evaluation.outputs[index] ?? "",
|
|
69
|
+
feedback: evaluation.feedback?.[index] ?? "",
|
|
70
|
+
score: evaluation.scores[index]
|
|
71
|
+
}));
|
|
72
|
+
return Object.fromEntries(componentsToUpdate.map((component) => [component, records]));
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* A deterministic stand-in for a reflection model: it reads the feedback in the
|
|
78
|
+
* prompt and folds the missing terms into the current instruction.
|
|
79
|
+
*/
|
|
80
|
+
function createKeywordReflector() {
|
|
81
|
+
return async ({ prompt }) => {
|
|
82
|
+
const current = extractCurrentInstruction(prompt);
|
|
83
|
+
return `\`\`\`\n${[current, ...extractMissingTerms(prompt).filter((term) => !current.toLowerCase().includes(term.toLowerCase()))].filter(Boolean).join(" ")}\n\`\`\``;
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* A stand-in for a model asked to rewrite text it has been told nothing about.
|
|
88
|
+
* It appends one term per call, cycling a pool in which only every other entry
|
|
89
|
+
* is useful — which is what blind proposal actually is: a draw from a space
|
|
90
|
+
* where some samples happen to help.
|
|
91
|
+
*
|
|
92
|
+
* Feedback-driven reflectors read the prompt; this one deliberately does not,
|
|
93
|
+
* so a search using it cannot benefit from evidence even if it is offered.
|
|
94
|
+
*/
|
|
95
|
+
function createSamplingReflector(args = {}) {
|
|
96
|
+
const { pool = SAMPLING_POOL } = args;
|
|
97
|
+
let cursor = 0;
|
|
98
|
+
return async ({ prompt }) => {
|
|
99
|
+
const term = pool[cursor % pool.length];
|
|
100
|
+
cursor += 1;
|
|
101
|
+
return `\`\`\`\n${[extractCurrentInstruction(prompt), term].filter(Boolean).join(" ")}\n\`\`\``;
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* A stand-in for a model that reads a score history and climbs it: it takes
|
|
106
|
+
* the highest-scoring attempt it is shown, keeps it, and extends it by one
|
|
107
|
+
* term. Unlike `createSamplingReflector` it depends on the prompt carrying
|
|
108
|
+
* scores, so a search that shows it none makes no progress with it.
|
|
109
|
+
*/
|
|
110
|
+
function createHillClimbingReflector(args = {}) {
|
|
111
|
+
const { pool = SAMPLING_POOL } = args;
|
|
112
|
+
let cursor = 0;
|
|
113
|
+
return async ({ prompt }) => {
|
|
114
|
+
const best = extractBestAttempt(prompt);
|
|
115
|
+
const term = pool[cursor % pool.length];
|
|
116
|
+
cursor += 1;
|
|
117
|
+
return `\`\`\`\n${[best, term].filter(Boolean).join(" ")}\n\`\`\``;
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
/** A reflection model that always proposes something strictly worse. */
|
|
121
|
+
function createDegradingReflector() {
|
|
122
|
+
return async () => "```\nno useful information\n```";
|
|
123
|
+
}
|
|
124
|
+
function extractCurrentInstruction(prompt) {
|
|
125
|
+
return prompt.match(/<current_instruction>\n([\s\S]*?)\n<\/current_instruction>/)?.[1]?.trim() ?? "";
|
|
126
|
+
}
|
|
127
|
+
/** The instruction beside the highest score in a scored-attempt prompt. */
|
|
128
|
+
function extractBestAttempt(prompt) {
|
|
129
|
+
let bestScore = Number.NEGATIVE_INFINITY;
|
|
130
|
+
let best = "";
|
|
131
|
+
for (const match of prompt.matchAll(/score:\s*([\d.]+)[\s\S]*?<instruction>\n?([\s\S]*?)\n?<\/instruction>/g)) {
|
|
132
|
+
const score = Number(match[1]);
|
|
133
|
+
if (score > bestScore) {
|
|
134
|
+
bestScore = score;
|
|
135
|
+
best = (match[2] ?? "").trim();
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return best;
|
|
139
|
+
}
|
|
140
|
+
function extractMissingTerms(prompt) {
|
|
141
|
+
const terms = /* @__PURE__ */ new Set();
|
|
142
|
+
for (const match of prompt.matchAll(/Missing required terms: ([^"\\\n]+)/g)) for (const term of (match[1] ?? "").split(",")) {
|
|
143
|
+
const trimmed = term.trim();
|
|
144
|
+
if (trimmed.length > 0) terms.add(trimmed);
|
|
145
|
+
}
|
|
146
|
+
return [...terms];
|
|
147
|
+
}
|
|
148
|
+
//#endregion
|
|
149
|
+
exports.KEYWORD_EXAMPLES = KEYWORD_EXAMPLES;
|
|
150
|
+
exports.buildReflectionPrompt = require_reflection.buildReflectionPrompt;
|
|
151
|
+
exports.createDegradingReflector = createDegradingReflector;
|
|
152
|
+
exports.createHillClimbingReflector = createHillClimbingReflector;
|
|
153
|
+
exports.createKeywordAdapter = createKeywordAdapter;
|
|
154
|
+
exports.createKeywordReflector = createKeywordReflector;
|
|
155
|
+
exports.createSamplingReflector = createSamplingReflector;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { u as TextModel } from "./types-CWv4IQFF.cjs";
|
|
2
|
+
import { i as buildReflectionPrompt, m as GepaAdapter } from "./reflection-CQToe-5B.cjs";
|
|
3
|
+
//#region src/testing.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* A deterministic, LLM-free system under optimization.
|
|
6
|
+
*
|
|
7
|
+
* It exists so the engine, and any adapter you write, can be exercised end to
|
|
8
|
+
* end in milliseconds: the "model" answers with the candidate text itself and
|
|
9
|
+
* the metric rewards covering the required terms. Optimization therefore has a
|
|
10
|
+
* real gradient to climb without a single network call.
|
|
11
|
+
*/
|
|
12
|
+
interface KeywordExample {
|
|
13
|
+
question: string;
|
|
14
|
+
required: string[];
|
|
15
|
+
}
|
|
16
|
+
interface KeywordTrajectory {
|
|
17
|
+
question: string;
|
|
18
|
+
instruction: string;
|
|
19
|
+
answer: string;
|
|
20
|
+
missing: string[];
|
|
21
|
+
}
|
|
22
|
+
declare const KEYWORD_EXAMPLES: KeywordExample[];
|
|
23
|
+
declare function createKeywordAdapter(): GepaAdapter<KeywordExample, KeywordTrajectory, string>;
|
|
24
|
+
/**
|
|
25
|
+
* A deterministic stand-in for a reflection model: it reads the feedback in the
|
|
26
|
+
* prompt and folds the missing terms into the current instruction.
|
|
27
|
+
*/
|
|
28
|
+
declare function createKeywordReflector(): TextModel;
|
|
29
|
+
/**
|
|
30
|
+
* A stand-in for a model asked to rewrite text it has been told nothing about.
|
|
31
|
+
* It appends one term per call, cycling a pool in which only every other entry
|
|
32
|
+
* is useful — which is what blind proposal actually is: a draw from a space
|
|
33
|
+
* where some samples happen to help.
|
|
34
|
+
*
|
|
35
|
+
* Feedback-driven reflectors read the prompt; this one deliberately does not,
|
|
36
|
+
* so a search using it cannot benefit from evidence even if it is offered.
|
|
37
|
+
*/
|
|
38
|
+
declare function createSamplingReflector(args?: {
|
|
39
|
+
pool?: readonly string[];
|
|
40
|
+
}): TextModel;
|
|
41
|
+
/**
|
|
42
|
+
* A stand-in for a model that reads a score history and climbs it: it takes
|
|
43
|
+
* the highest-scoring attempt it is shown, keeps it, and extends it by one
|
|
44
|
+
* term. Unlike `createSamplingReflector` it depends on the prompt carrying
|
|
45
|
+
* scores, so a search that shows it none makes no progress with it.
|
|
46
|
+
*/
|
|
47
|
+
declare function createHillClimbingReflector(args?: {
|
|
48
|
+
pool?: readonly string[];
|
|
49
|
+
}): TextModel;
|
|
50
|
+
/** A reflection model that always proposes something strictly worse. */
|
|
51
|
+
declare function createDegradingReflector(): TextModel;
|
|
52
|
+
//#endregion
|
|
53
|
+
export { KEYWORD_EXAMPLES, KeywordExample, KeywordTrajectory, buildReflectionPrompt, createDegradingReflector, createHillClimbingReflector, createKeywordAdapter, createKeywordReflector, createSamplingReflector };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { u as TextModel } from "./types-CWv4IQFF.mjs";
|
|
2
|
+
import { i as buildReflectionPrompt, m as GepaAdapter } from "./reflection-Cr_upzU0.mjs";
|
|
3
|
+
//#region src/testing.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* A deterministic, LLM-free system under optimization.
|
|
6
|
+
*
|
|
7
|
+
* It exists so the engine, and any adapter you write, can be exercised end to
|
|
8
|
+
* end in milliseconds: the "model" answers with the candidate text itself and
|
|
9
|
+
* the metric rewards covering the required terms. Optimization therefore has a
|
|
10
|
+
* real gradient to climb without a single network call.
|
|
11
|
+
*/
|
|
12
|
+
interface KeywordExample {
|
|
13
|
+
question: string;
|
|
14
|
+
required: string[];
|
|
15
|
+
}
|
|
16
|
+
interface KeywordTrajectory {
|
|
17
|
+
question: string;
|
|
18
|
+
instruction: string;
|
|
19
|
+
answer: string;
|
|
20
|
+
missing: string[];
|
|
21
|
+
}
|
|
22
|
+
declare const KEYWORD_EXAMPLES: KeywordExample[];
|
|
23
|
+
declare function createKeywordAdapter(): GepaAdapter<KeywordExample, KeywordTrajectory, string>;
|
|
24
|
+
/**
|
|
25
|
+
* A deterministic stand-in for a reflection model: it reads the feedback in the
|
|
26
|
+
* prompt and folds the missing terms into the current instruction.
|
|
27
|
+
*/
|
|
28
|
+
declare function createKeywordReflector(): TextModel;
|
|
29
|
+
/**
|
|
30
|
+
* A stand-in for a model asked to rewrite text it has been told nothing about.
|
|
31
|
+
* It appends one term per call, cycling a pool in which only every other entry
|
|
32
|
+
* is useful — which is what blind proposal actually is: a draw from a space
|
|
33
|
+
* where some samples happen to help.
|
|
34
|
+
*
|
|
35
|
+
* Feedback-driven reflectors read the prompt; this one deliberately does not,
|
|
36
|
+
* so a search using it cannot benefit from evidence even if it is offered.
|
|
37
|
+
*/
|
|
38
|
+
declare function createSamplingReflector(args?: {
|
|
39
|
+
pool?: readonly string[];
|
|
40
|
+
}): TextModel;
|
|
41
|
+
/**
|
|
42
|
+
* A stand-in for a model that reads a score history and climbs it: it takes
|
|
43
|
+
* the highest-scoring attempt it is shown, keeps it, and extends it by one
|
|
44
|
+
* term. Unlike `createSamplingReflector` it depends on the prompt carrying
|
|
45
|
+
* scores, so a search that shows it none makes no progress with it.
|
|
46
|
+
*/
|
|
47
|
+
declare function createHillClimbingReflector(args?: {
|
|
48
|
+
pool?: readonly string[];
|
|
49
|
+
}): TextModel;
|
|
50
|
+
/** A reflection model that always proposes something strictly worse. */
|
|
51
|
+
declare function createDegradingReflector(): TextModel;
|
|
52
|
+
//#endregion
|
|
53
|
+
export { KEYWORD_EXAMPLES, KeywordExample, KeywordTrajectory, buildReflectionPrompt, createDegradingReflector, createHillClimbingReflector, createKeywordAdapter, createKeywordReflector, createSamplingReflector };
|
package/dist/testing.mjs
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { n as buildReflectionPrompt } from "./reflection-mwMhrjs_.mjs";
|
|
2
|
+
//#region src/testing.ts
|
|
3
|
+
const KEYWORD_EXAMPLES = [
|
|
4
|
+
{
|
|
5
|
+
question: "How do I reset a device?",
|
|
6
|
+
required: ["hold", "ten seconds"]
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
question: "How do I contact support?",
|
|
10
|
+
required: ["ticket", "portal"]
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
question: "What is the refund window?",
|
|
14
|
+
required: ["thirty days"]
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
question: "How do I upgrade a plan?",
|
|
18
|
+
required: ["billing", "prorated"]
|
|
19
|
+
}
|
|
20
|
+
];
|
|
21
|
+
/** Terms the keyword metric rewards, interleaved with terms it ignores. */
|
|
22
|
+
const SAMPLING_POOL = [
|
|
23
|
+
"hold",
|
|
24
|
+
"sprocket",
|
|
25
|
+
"ten seconds",
|
|
26
|
+
"lorem",
|
|
27
|
+
"ticket",
|
|
28
|
+
"widget",
|
|
29
|
+
"portal",
|
|
30
|
+
"colour",
|
|
31
|
+
"thirty days",
|
|
32
|
+
"ipsum",
|
|
33
|
+
"billing",
|
|
34
|
+
"gizmo",
|
|
35
|
+
"prorated",
|
|
36
|
+
"flange"
|
|
37
|
+
];
|
|
38
|
+
function createKeywordAdapter() {
|
|
39
|
+
return {
|
|
40
|
+
evaluate: ({ batch, candidate }) => {
|
|
41
|
+
const answer = Object.values(candidate).join(" ");
|
|
42
|
+
const trajectories = [];
|
|
43
|
+
const scores = [];
|
|
44
|
+
const feedback = [];
|
|
45
|
+
for (const example of batch) {
|
|
46
|
+
const missing = example.required.filter((term) => !answer.toLowerCase().includes(term.toLowerCase()));
|
|
47
|
+
const score = (example.required.length - missing.length) / example.required.length;
|
|
48
|
+
scores.push(score);
|
|
49
|
+
feedback.push(missing.length === 0 ? "All required terms present." : `Missing required terms: ${missing.join(", ")}`);
|
|
50
|
+
trajectories.push({
|
|
51
|
+
question: example.question,
|
|
52
|
+
instruction: answer,
|
|
53
|
+
answer,
|
|
54
|
+
missing
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
outputs: trajectories.map((trajectory) => trajectory.answer),
|
|
59
|
+
scores,
|
|
60
|
+
feedback,
|
|
61
|
+
trajectories
|
|
62
|
+
};
|
|
63
|
+
},
|
|
64
|
+
makeReflectiveDataset: ({ batch, evaluation, componentsToUpdate }) => {
|
|
65
|
+
const records = batch.map((example, index) => ({
|
|
66
|
+
inputs: { question: example.question },
|
|
67
|
+
generatedOutputs: evaluation.outputs[index] ?? "",
|
|
68
|
+
feedback: evaluation.feedback?.[index] ?? "",
|
|
69
|
+
score: evaluation.scores[index]
|
|
70
|
+
}));
|
|
71
|
+
return Object.fromEntries(componentsToUpdate.map((component) => [component, records]));
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* A deterministic stand-in for a reflection model: it reads the feedback in the
|
|
77
|
+
* prompt and folds the missing terms into the current instruction.
|
|
78
|
+
*/
|
|
79
|
+
function createKeywordReflector() {
|
|
80
|
+
return async ({ prompt }) => {
|
|
81
|
+
const current = extractCurrentInstruction(prompt);
|
|
82
|
+
return `\`\`\`\n${[current, ...extractMissingTerms(prompt).filter((term) => !current.toLowerCase().includes(term.toLowerCase()))].filter(Boolean).join(" ")}\n\`\`\``;
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* A stand-in for a model asked to rewrite text it has been told nothing about.
|
|
87
|
+
* It appends one term per call, cycling a pool in which only every other entry
|
|
88
|
+
* is useful — which is what blind proposal actually is: a draw from a space
|
|
89
|
+
* where some samples happen to help.
|
|
90
|
+
*
|
|
91
|
+
* Feedback-driven reflectors read the prompt; this one deliberately does not,
|
|
92
|
+
* so a search using it cannot benefit from evidence even if it is offered.
|
|
93
|
+
*/
|
|
94
|
+
function createSamplingReflector(args = {}) {
|
|
95
|
+
const { pool = SAMPLING_POOL } = args;
|
|
96
|
+
let cursor = 0;
|
|
97
|
+
return async ({ prompt }) => {
|
|
98
|
+
const term = pool[cursor % pool.length];
|
|
99
|
+
cursor += 1;
|
|
100
|
+
return `\`\`\`\n${[extractCurrentInstruction(prompt), term].filter(Boolean).join(" ")}\n\`\`\``;
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* A stand-in for a model that reads a score history and climbs it: it takes
|
|
105
|
+
* the highest-scoring attempt it is shown, keeps it, and extends it by one
|
|
106
|
+
* term. Unlike `createSamplingReflector` it depends on the prompt carrying
|
|
107
|
+
* scores, so a search that shows it none makes no progress with it.
|
|
108
|
+
*/
|
|
109
|
+
function createHillClimbingReflector(args = {}) {
|
|
110
|
+
const { pool = SAMPLING_POOL } = args;
|
|
111
|
+
let cursor = 0;
|
|
112
|
+
return async ({ prompt }) => {
|
|
113
|
+
const best = extractBestAttempt(prompt);
|
|
114
|
+
const term = pool[cursor % pool.length];
|
|
115
|
+
cursor += 1;
|
|
116
|
+
return `\`\`\`\n${[best, term].filter(Boolean).join(" ")}\n\`\`\``;
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
/** A reflection model that always proposes something strictly worse. */
|
|
120
|
+
function createDegradingReflector() {
|
|
121
|
+
return async () => "```\nno useful information\n```";
|
|
122
|
+
}
|
|
123
|
+
function extractCurrentInstruction(prompt) {
|
|
124
|
+
return prompt.match(/<current_instruction>\n([\s\S]*?)\n<\/current_instruction>/)?.[1]?.trim() ?? "";
|
|
125
|
+
}
|
|
126
|
+
/** The instruction beside the highest score in a scored-attempt prompt. */
|
|
127
|
+
function extractBestAttempt(prompt) {
|
|
128
|
+
let bestScore = Number.NEGATIVE_INFINITY;
|
|
129
|
+
let best = "";
|
|
130
|
+
for (const match of prompt.matchAll(/score:\s*([\d.]+)[\s\S]*?<instruction>\n?([\s\S]*?)\n?<\/instruction>/g)) {
|
|
131
|
+
const score = Number(match[1]);
|
|
132
|
+
if (score > bestScore) {
|
|
133
|
+
bestScore = score;
|
|
134
|
+
best = (match[2] ?? "").trim();
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return best;
|
|
138
|
+
}
|
|
139
|
+
function extractMissingTerms(prompt) {
|
|
140
|
+
const terms = /* @__PURE__ */ new Set();
|
|
141
|
+
for (const match of prompt.matchAll(/Missing required terms: ([^"\\\n]+)/g)) for (const term of (match[1] ?? "").split(",")) {
|
|
142
|
+
const trimmed = term.trim();
|
|
143
|
+
if (trimmed.length > 0) terms.add(trimmed);
|
|
144
|
+
}
|
|
145
|
+
return [...terms];
|
|
146
|
+
}
|
|
147
|
+
//#endregion
|
|
148
|
+
export { KEYWORD_EXAMPLES, buildReflectionPrompt, createDegradingReflector, createHillClimbingReflector, createKeywordAdapter, createKeywordReflector, createSamplingReflector };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
//#region src/text.ts
|
|
2
|
+
const LANGUAGE_TAG = /^[a-zA-Z0-9_+.-]*\n/;
|
|
3
|
+
const DANGLING_OPEN_FENCE = /^\s*```\S*\n?/;
|
|
4
|
+
const DANGLING_CLOSE_FENCE = /\n?```\s*$/;
|
|
5
|
+
/**
|
|
6
|
+
* Pull the proposed text out of the reflection model's response.
|
|
7
|
+
*
|
|
8
|
+
* Spans the *first* fence to the *last* one rather than matching blocks
|
|
9
|
+
* individually: proposed instructions routinely contain their own fenced
|
|
10
|
+
* examples, and per-block matching would silently return only the trailing
|
|
11
|
+
* fragment. A response with a single fence was truncated mid-generation, so the
|
|
12
|
+
* stray fence is stripped and the partial text kept.
|
|
13
|
+
*/
|
|
14
|
+
function parseProposedText(response) {
|
|
15
|
+
const start = response.indexOf("```");
|
|
16
|
+
const end = response.lastIndexOf("```");
|
|
17
|
+
if (start !== -1 && start !== end) return response.slice(start + 3, end).replace(LANGUAGE_TAG, "").trim();
|
|
18
|
+
return response.replace(DANGLING_OPEN_FENCE, "").replace(DANGLING_CLOSE_FENCE, "").trim();
|
|
19
|
+
}
|
|
20
|
+
//#endregion
|
|
21
|
+
export { parseProposedText as t };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
//#region src/text.ts
|
|
2
|
+
const LANGUAGE_TAG = /^[a-zA-Z0-9_+.-]*\n/;
|
|
3
|
+
const DANGLING_OPEN_FENCE = /^\s*```\S*\n?/;
|
|
4
|
+
const DANGLING_CLOSE_FENCE = /\n?```\s*$/;
|
|
5
|
+
/**
|
|
6
|
+
* Pull the proposed text out of the reflection model's response.
|
|
7
|
+
*
|
|
8
|
+
* Spans the *first* fence to the *last* one rather than matching blocks
|
|
9
|
+
* individually: proposed instructions routinely contain their own fenced
|
|
10
|
+
* examples, and per-block matching would silently return only the trailing
|
|
11
|
+
* fragment. A response with a single fence was truncated mid-generation, so the
|
|
12
|
+
* stray fence is stripped and the partial text kept.
|
|
13
|
+
*/
|
|
14
|
+
function parseProposedText(response) {
|
|
15
|
+
const start = response.indexOf("```");
|
|
16
|
+
const end = response.lastIndexOf("```");
|
|
17
|
+
if (start !== -1 && start !== end) return response.slice(start + 3, end).replace(LANGUAGE_TAG, "").trim();
|
|
18
|
+
return response.replace(DANGLING_OPEN_FENCE, "").replace(DANGLING_CLOSE_FENCE, "").trim();
|
|
19
|
+
}
|
|
20
|
+
//#endregion
|
|
21
|
+
Object.defineProperty(exports, "parseProposedText", {
|
|
22
|
+
enumerable: true,
|
|
23
|
+
get: function() {
|
|
24
|
+
return parseProposedText;
|
|
25
|
+
}
|
|
26
|
+
});
|
|
@@ -0,0 +1,129 @@
|
|
|
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 };
|