ai-experiments 0.1.0 → 2.0.2
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/.turbo/turbo-build.log +5 -0
- package/CHANGELOG.md +15 -0
- package/README.md +306 -91
- package/dist/cartesian.d.ts +140 -0
- package/dist/cartesian.d.ts.map +1 -0
- package/dist/cartesian.js +216 -0
- package/dist/cartesian.js.map +1 -0
- package/dist/decide.d.ts +152 -0
- package/dist/decide.d.ts.map +1 -0
- package/dist/decide.js +329 -0
- package/dist/decide.js.map +1 -0
- package/dist/experiment.d.ts +53 -0
- package/dist/experiment.d.ts.map +1 -0
- package/dist/experiment.js +292 -0
- package/dist/experiment.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/tracking.d.ts +159 -0
- package/dist/tracking.d.ts.map +1 -0
- package/dist/tracking.js +310 -0
- package/dist/tracking.js.map +1 -0
- package/dist/types.d.ts +198 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/examples.ts +261 -0
- package/package.json +21 -39
- package/src/cartesian.ts +259 -0
- package/src/decide.ts +398 -0
- package/src/experiment.ts +358 -0
- package/src/index.ts +44 -0
- package/src/tracking.ts +339 -0
- package/src/types.ts +215 -0
- package/tsconfig.json +9 -0
package/dist/decide.js
ADDED
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decision making utilities
|
|
3
|
+
*/
|
|
4
|
+
import { track } from './tracking.js';
|
|
5
|
+
/**
|
|
6
|
+
* Make a decision by evaluating and scoring multiple options
|
|
7
|
+
*
|
|
8
|
+
* Scores each option and returns the best one (highest score).
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* import { decide } from 'ai-experiments'
|
|
13
|
+
*
|
|
14
|
+
* // Simple decision with sync scoring
|
|
15
|
+
* const result = await decide({
|
|
16
|
+
* options: ['apple', 'banana', 'orange'],
|
|
17
|
+
* score: (fruit) => {
|
|
18
|
+
* const prices = { apple: 1.5, banana: 0.5, orange: 2.0 }
|
|
19
|
+
* return 1 / prices[fruit] // Lower price = higher score
|
|
20
|
+
* },
|
|
21
|
+
* })
|
|
22
|
+
* console.log(result.selected) // 'banana'
|
|
23
|
+
*
|
|
24
|
+
* // Decision with async scoring (AI-based)
|
|
25
|
+
* const result = await decide({
|
|
26
|
+
* options: [
|
|
27
|
+
* { prompt: 'Summarize in one sentence' },
|
|
28
|
+
* { prompt: 'Provide a detailed summary' },
|
|
29
|
+
* { prompt: 'Extract key points only' },
|
|
30
|
+
* ],
|
|
31
|
+
* score: async (option) => {
|
|
32
|
+
* const result = await ai.generate(option)
|
|
33
|
+
* return evaluateQuality(result)
|
|
34
|
+
* },
|
|
35
|
+
* context: 'Choosing best summarization approach',
|
|
36
|
+
* })
|
|
37
|
+
*
|
|
38
|
+
* // Get all options sorted by score
|
|
39
|
+
* const result = await decide({
|
|
40
|
+
* options: ['fast', 'accurate', 'balanced'],
|
|
41
|
+
* score: (approach) => evaluateApproach(approach),
|
|
42
|
+
* returnAll: true,
|
|
43
|
+
* })
|
|
44
|
+
* console.log(result.allOptions)
|
|
45
|
+
* // [
|
|
46
|
+
* // { option: 'balanced', score: 0.9 },
|
|
47
|
+
* // { option: 'accurate', score: 0.85 },
|
|
48
|
+
* // { option: 'fast', score: 0.7 },
|
|
49
|
+
* // ]
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export async function decide(options) {
|
|
53
|
+
const { options: choices, score, context, returnAll = false } = options;
|
|
54
|
+
if (choices.length === 0) {
|
|
55
|
+
throw new Error('Cannot decide with empty options');
|
|
56
|
+
}
|
|
57
|
+
// Score all options
|
|
58
|
+
const scoredOptions = await Promise.all(choices.map(async (option) => {
|
|
59
|
+
const optionScore = await score(option);
|
|
60
|
+
return { option, score: optionScore };
|
|
61
|
+
}));
|
|
62
|
+
// Sort by score (highest first)
|
|
63
|
+
const sorted = scoredOptions.sort((a, b) => b.score - a.score);
|
|
64
|
+
// Select best option
|
|
65
|
+
const best = sorted[0];
|
|
66
|
+
// Track decision
|
|
67
|
+
track({
|
|
68
|
+
type: 'decision.made',
|
|
69
|
+
timestamp: new Date(),
|
|
70
|
+
data: {
|
|
71
|
+
context,
|
|
72
|
+
optionCount: choices.length,
|
|
73
|
+
selectedScore: best.score,
|
|
74
|
+
allScores: sorted.map((s) => s.score),
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
const result = {
|
|
78
|
+
selected: best.option,
|
|
79
|
+
score: best.score,
|
|
80
|
+
};
|
|
81
|
+
if (returnAll) {
|
|
82
|
+
result.allOptions = sorted;
|
|
83
|
+
}
|
|
84
|
+
return result;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Weighted random selection from options
|
|
88
|
+
*
|
|
89
|
+
* Each option has a weight, and selection probability is proportional to weight.
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* ```ts
|
|
93
|
+
* import { decideWeighted } from 'ai-experiments'
|
|
94
|
+
*
|
|
95
|
+
* const result = decideWeighted([
|
|
96
|
+
* { value: 'A', weight: 0.7 }, // 70% chance
|
|
97
|
+
* { value: 'B', weight: 0.2 }, // 20% chance
|
|
98
|
+
* { value: 'C', weight: 0.1 }, // 10% chance
|
|
99
|
+
* ])
|
|
100
|
+
*
|
|
101
|
+
* console.log(result) // Most likely 'A', but could be B or C
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
export function decideWeighted(options) {
|
|
105
|
+
if (options.length === 0) {
|
|
106
|
+
throw new Error('Cannot decide with empty options');
|
|
107
|
+
}
|
|
108
|
+
// Calculate total weight
|
|
109
|
+
const totalWeight = options.reduce((sum, opt) => sum + opt.weight, 0);
|
|
110
|
+
if (totalWeight <= 0) {
|
|
111
|
+
throw new Error('Total weight must be positive');
|
|
112
|
+
}
|
|
113
|
+
// Generate random value between 0 and totalWeight
|
|
114
|
+
const random = Math.random() * totalWeight;
|
|
115
|
+
// Find the option that corresponds to this random value
|
|
116
|
+
let cumulative = 0;
|
|
117
|
+
for (const option of options) {
|
|
118
|
+
cumulative += option.weight;
|
|
119
|
+
if (random <= cumulative) {
|
|
120
|
+
return option.value;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// Fallback (should not reach here due to floating point precision)
|
|
124
|
+
return options[options.length - 1].value;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Epsilon-greedy decision strategy
|
|
128
|
+
*
|
|
129
|
+
* With probability epsilon, select a random option (exploration).
|
|
130
|
+
* Otherwise, select the best option by score (exploitation).
|
|
131
|
+
*
|
|
132
|
+
* @example
|
|
133
|
+
* ```ts
|
|
134
|
+
* import { decideEpsilonGreedy } from 'ai-experiments'
|
|
135
|
+
*
|
|
136
|
+
* const result = await decideEpsilonGreedy({
|
|
137
|
+
* options: ['model-a', 'model-b', 'model-c'],
|
|
138
|
+
* score: async (model) => await evaluateModel(model),
|
|
139
|
+
* epsilon: 0.1, // 10% exploration, 90% exploitation
|
|
140
|
+
* })
|
|
141
|
+
* ```
|
|
142
|
+
*/
|
|
143
|
+
export async function decideEpsilonGreedy(options) {
|
|
144
|
+
const { epsilon, options: choices, score, context } = options;
|
|
145
|
+
if (epsilon < 0 || epsilon > 1) {
|
|
146
|
+
throw new Error('Epsilon must be between 0 and 1');
|
|
147
|
+
}
|
|
148
|
+
// Exploration: random selection
|
|
149
|
+
if (Math.random() < epsilon) {
|
|
150
|
+
const randomIndex = Math.floor(Math.random() * choices.length);
|
|
151
|
+
const selected = choices[randomIndex];
|
|
152
|
+
const selectedScore = await score(selected);
|
|
153
|
+
track({
|
|
154
|
+
type: 'decision.made',
|
|
155
|
+
timestamp: new Date(),
|
|
156
|
+
data: {
|
|
157
|
+
context,
|
|
158
|
+
strategy: 'epsilon-greedy-explore',
|
|
159
|
+
epsilon,
|
|
160
|
+
selectedScore,
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
return {
|
|
164
|
+
selected,
|
|
165
|
+
score: selectedScore,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
// Exploitation: best option
|
|
169
|
+
const result = await decide({ options: choices, score, context });
|
|
170
|
+
track({
|
|
171
|
+
type: 'decision.made',
|
|
172
|
+
timestamp: new Date(),
|
|
173
|
+
data: {
|
|
174
|
+
context,
|
|
175
|
+
strategy: 'epsilon-greedy-exploit',
|
|
176
|
+
epsilon,
|
|
177
|
+
selectedScore: result.score,
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
return result;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Thompson sampling decision strategy
|
|
184
|
+
*
|
|
185
|
+
* Bayesian approach to balancing exploration and exploitation.
|
|
186
|
+
* Each option has a Beta distribution representing our belief about its true score.
|
|
187
|
+
*
|
|
188
|
+
* @example
|
|
189
|
+
* ```ts
|
|
190
|
+
* import { decideThompsonSampling } from 'ai-experiments'
|
|
191
|
+
*
|
|
192
|
+
* // Track successes and failures for each option
|
|
193
|
+
* const priors = {
|
|
194
|
+
* 'variant-a': { alpha: 10, beta: 5 }, // 10 successes, 5 failures
|
|
195
|
+
* 'variant-b': { alpha: 8, beta: 3 }, // 8 successes, 3 failures
|
|
196
|
+
* 'variant-c': { alpha: 2, beta: 2 }, // 2 successes, 2 failures (uncertain)
|
|
197
|
+
* }
|
|
198
|
+
*
|
|
199
|
+
* const result = decideThompsonSampling(['variant-a', 'variant-b', 'variant-c'], priors)
|
|
200
|
+
* // More likely to select 'variant-b' (higher rate) but will sometimes explore 'variant-c'
|
|
201
|
+
* ```
|
|
202
|
+
*/
|
|
203
|
+
export function decideThompsonSampling(options, priors) {
|
|
204
|
+
if (options.length === 0) {
|
|
205
|
+
throw new Error('Cannot decide with empty options');
|
|
206
|
+
}
|
|
207
|
+
// Sample from Beta distribution for each option
|
|
208
|
+
const samples = options.map((option) => {
|
|
209
|
+
const { alpha, beta } = priors[option];
|
|
210
|
+
return {
|
|
211
|
+
option,
|
|
212
|
+
sample: sampleBeta(alpha, beta),
|
|
213
|
+
};
|
|
214
|
+
});
|
|
215
|
+
// Select option with highest sample
|
|
216
|
+
const best = samples.reduce((prev, current) => current.sample > prev.sample ? current : prev);
|
|
217
|
+
track({
|
|
218
|
+
type: 'decision.made',
|
|
219
|
+
timestamp: new Date(),
|
|
220
|
+
data: {
|
|
221
|
+
strategy: 'thompson-sampling',
|
|
222
|
+
selected: best.option,
|
|
223
|
+
sample: best.sample,
|
|
224
|
+
priors: priors[best.option],
|
|
225
|
+
},
|
|
226
|
+
});
|
|
227
|
+
return best.option;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Sample from Beta distribution using the ratio of two Gamma distributions
|
|
231
|
+
*/
|
|
232
|
+
function sampleBeta(alpha, beta) {
|
|
233
|
+
const x = sampleGamma(alpha, 1);
|
|
234
|
+
const y = sampleGamma(beta, 1);
|
|
235
|
+
return x / (x + y);
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Sample from Gamma distribution using Marsaglia and Tsang method
|
|
239
|
+
*/
|
|
240
|
+
function sampleGamma(shape, scale) {
|
|
241
|
+
// Simple implementation for shape >= 1
|
|
242
|
+
if (shape < 1) {
|
|
243
|
+
// Use the property: Gamma(a) = Gamma(a+1) * U^(1/a)
|
|
244
|
+
return sampleGamma(shape + 1, scale) * Math.pow(Math.random(), 1 / shape);
|
|
245
|
+
}
|
|
246
|
+
const d = shape - 1 / 3;
|
|
247
|
+
const c = 1 / Math.sqrt(9 * d);
|
|
248
|
+
while (true) {
|
|
249
|
+
let x;
|
|
250
|
+
let v;
|
|
251
|
+
do {
|
|
252
|
+
x = randomNormal();
|
|
253
|
+
v = 1 + c * x;
|
|
254
|
+
} while (v <= 0);
|
|
255
|
+
v = v * v * v;
|
|
256
|
+
const u = Math.random();
|
|
257
|
+
if (u < 1 - 0.0331 * x * x * x * x) {
|
|
258
|
+
return d * v * scale;
|
|
259
|
+
}
|
|
260
|
+
if (Math.log(u) < 0.5 * x * x + d * (1 - v + Math.log(v))) {
|
|
261
|
+
return d * v * scale;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Sample from standard normal distribution using Box-Muller transform
|
|
267
|
+
*/
|
|
268
|
+
function randomNormal() {
|
|
269
|
+
const u1 = Math.random();
|
|
270
|
+
const u2 = Math.random();
|
|
271
|
+
return Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Upper Confidence Bound (UCB) decision strategy
|
|
275
|
+
*
|
|
276
|
+
* Select the option with the highest upper confidence bound.
|
|
277
|
+
* Balances exploration and exploitation using confidence intervals.
|
|
278
|
+
*
|
|
279
|
+
* @example
|
|
280
|
+
* ```ts
|
|
281
|
+
* import { decideUCB } from 'ai-experiments'
|
|
282
|
+
*
|
|
283
|
+
* const stats = {
|
|
284
|
+
* 'variant-a': { mean: 0.85, count: 100 },
|
|
285
|
+
* 'variant-b': { mean: 0.82, count: 50 }, // Less data, higher uncertainty
|
|
286
|
+
* 'variant-c': { mean: 0.78, count: 10 }, // Very uncertain
|
|
287
|
+
* }
|
|
288
|
+
*
|
|
289
|
+
* const result = decideUCB(['variant-a', 'variant-b', 'variant-c'], stats, {
|
|
290
|
+
* explorationFactor: 2.0,
|
|
291
|
+
* totalCount: 160,
|
|
292
|
+
* })
|
|
293
|
+
* // Might select variant-c to explore more, despite lower mean
|
|
294
|
+
* ```
|
|
295
|
+
*/
|
|
296
|
+
export function decideUCB(options, stats, config) {
|
|
297
|
+
const { explorationFactor, totalCount } = config;
|
|
298
|
+
if (options.length === 0) {
|
|
299
|
+
throw new Error('Cannot decide with empty options');
|
|
300
|
+
}
|
|
301
|
+
// Calculate UCB for each option
|
|
302
|
+
const ucbScores = options.map((option) => {
|
|
303
|
+
const { mean, count } = stats[option];
|
|
304
|
+
// UCB = mean + c * sqrt(ln(N) / n)
|
|
305
|
+
const explorationBonus = explorationFactor * Math.sqrt(Math.log(totalCount) / Math.max(count, 1));
|
|
306
|
+
return {
|
|
307
|
+
option,
|
|
308
|
+
ucb: mean + explorationBonus,
|
|
309
|
+
mean,
|
|
310
|
+
count,
|
|
311
|
+
};
|
|
312
|
+
});
|
|
313
|
+
// Select option with highest UCB
|
|
314
|
+
const best = ucbScores.reduce((prev, current) => (current.ucb > prev.ucb ? current : prev));
|
|
315
|
+
track({
|
|
316
|
+
type: 'decision.made',
|
|
317
|
+
timestamp: new Date(),
|
|
318
|
+
data: {
|
|
319
|
+
strategy: 'ucb',
|
|
320
|
+
selected: best.option,
|
|
321
|
+
ucb: best.ucb,
|
|
322
|
+
mean: best.mean,
|
|
323
|
+
count: best.count,
|
|
324
|
+
explorationFactor,
|
|
325
|
+
},
|
|
326
|
+
});
|
|
327
|
+
return best.option;
|
|
328
|
+
}
|
|
329
|
+
//# sourceMappingURL=decide.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decide.js","sourceRoot":"","sources":["../src/decide.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Experiment execution and management
|
|
3
|
+
*/
|
|
4
|
+
import type { ExperimentConfig, ExperimentSummary, RunExperimentOptions, ExperimentVariant } from './types.js';
|
|
5
|
+
/**
|
|
6
|
+
* Create and run an A/B experiment with multiple variants
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { Experiment } from 'ai-experiments'
|
|
11
|
+
*
|
|
12
|
+
* const results = await Experiment({
|
|
13
|
+
* id: 'prompt-comparison',
|
|
14
|
+
* name: 'Prompt Engineering Test',
|
|
15
|
+
* variants: [
|
|
16
|
+
* {
|
|
17
|
+
* id: 'baseline',
|
|
18
|
+
* name: 'Baseline Prompt',
|
|
19
|
+
* config: { prompt: 'Summarize this text.' },
|
|
20
|
+
* },
|
|
21
|
+
* {
|
|
22
|
+
* id: 'detailed',
|
|
23
|
+
* name: 'Detailed Prompt',
|
|
24
|
+
* config: { prompt: 'Provide a comprehensive summary...' },
|
|
25
|
+
* },
|
|
26
|
+
* ],
|
|
27
|
+
* execute: async (config) => {
|
|
28
|
+
* return await ai.generate({ prompt: config.prompt })
|
|
29
|
+
* },
|
|
30
|
+
* metric: (result) => result.quality_score,
|
|
31
|
+
* })
|
|
32
|
+
*
|
|
33
|
+
* console.log('Best variant:', results.bestVariant)
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export declare function Experiment<TConfig = unknown, TResult = unknown>(config: ExperimentConfig<TConfig, TResult>, options?: RunExperimentOptions): Promise<ExperimentSummary<TResult>>;
|
|
37
|
+
/**
|
|
38
|
+
* Helper to create experiment variants from a parameter grid
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```ts
|
|
42
|
+
* const variants = createVariantsFromGrid({
|
|
43
|
+
* temperature: [0.3, 0.7, 1.0],
|
|
44
|
+
* model: ['sonnet', 'opus'],
|
|
45
|
+
* maxTokens: [100, 500],
|
|
46
|
+
* })
|
|
47
|
+
* // Returns 12 variants (3 * 2 * 2 combinations)
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
export declare function createVariantsFromGrid<T extends Record<string, unknown[]>>(grid: T): ExperimentVariant<{
|
|
51
|
+
[K in keyof T]: T[K][number];
|
|
52
|
+
}>[];
|
|
53
|
+
//# sourceMappingURL=experiment.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"experiment.d.ts","sourceRoot":"","sources":["../src/experiment.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EACV,gBAAgB,EAGhB,iBAAiB,EACjB,oBAAoB,EACpB,iBAAiB,EAClB,MAAM,YAAY,CAAA;AAGnB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAsB,UAAU,CAAC,OAAO,GAAG,OAAO,EAAE,OAAO,GAAG,OAAO,EACnE,MAAM,EAAE,gBAAgB,CAAC,OAAO,EAAE,OAAO,CAAC,EAC1C,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,iBAAiB,CAAC,OAAO,CAAC,CAAC,CAwHrC;AA8ID;;;;;;;;;;;;GAYG;AACH,wBAAgB,sBAAsB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,EACxE,IAAI,EAAE,CAAC,GACN,iBAAiB,CAAC;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;CAAE,CAAC,EAAE,CAkBvD"}
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Experiment execution and management
|
|
3
|
+
*/
|
|
4
|
+
import { randomUUID } from 'crypto';
|
|
5
|
+
import { track } from './tracking.js';
|
|
6
|
+
/**
|
|
7
|
+
* Create and run an A/B experiment with multiple variants
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* import { Experiment } from 'ai-experiments'
|
|
12
|
+
*
|
|
13
|
+
* const results = await Experiment({
|
|
14
|
+
* id: 'prompt-comparison',
|
|
15
|
+
* name: 'Prompt Engineering Test',
|
|
16
|
+
* variants: [
|
|
17
|
+
* {
|
|
18
|
+
* id: 'baseline',
|
|
19
|
+
* name: 'Baseline Prompt',
|
|
20
|
+
* config: { prompt: 'Summarize this text.' },
|
|
21
|
+
* },
|
|
22
|
+
* {
|
|
23
|
+
* id: 'detailed',
|
|
24
|
+
* name: 'Detailed Prompt',
|
|
25
|
+
* config: { prompt: 'Provide a comprehensive summary...' },
|
|
26
|
+
* },
|
|
27
|
+
* ],
|
|
28
|
+
* execute: async (config) => {
|
|
29
|
+
* return await ai.generate({ prompt: config.prompt })
|
|
30
|
+
* },
|
|
31
|
+
* metric: (result) => result.quality_score,
|
|
32
|
+
* })
|
|
33
|
+
*
|
|
34
|
+
* console.log('Best variant:', results.bestVariant)
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export async function Experiment(config, options = {}) {
|
|
38
|
+
const { parallel = true, maxConcurrency, stopOnError = false, context: contextData, onVariantStart, onVariantComplete, onVariantError, } = options;
|
|
39
|
+
const experimentStartTime = new Date();
|
|
40
|
+
// Track experiment start
|
|
41
|
+
track({
|
|
42
|
+
type: 'experiment.start',
|
|
43
|
+
timestamp: experimentStartTime,
|
|
44
|
+
data: {
|
|
45
|
+
experimentId: config.id,
|
|
46
|
+
experimentName: config.name,
|
|
47
|
+
variantCount: config.variants.length,
|
|
48
|
+
parallel,
|
|
49
|
+
...config.metadata,
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
const results = [];
|
|
53
|
+
// Execute variants
|
|
54
|
+
if (parallel) {
|
|
55
|
+
// Parallel execution with optional concurrency limit
|
|
56
|
+
const executeVariant = async (variant) => {
|
|
57
|
+
return runVariant(config, variant, contextData, {
|
|
58
|
+
onVariantStart,
|
|
59
|
+
onVariantComplete,
|
|
60
|
+
onVariantError,
|
|
61
|
+
});
|
|
62
|
+
};
|
|
63
|
+
if (maxConcurrency && maxConcurrency > 0) {
|
|
64
|
+
// Execute with concurrency limit
|
|
65
|
+
const chunks = chunkArray(config.variants, maxConcurrency);
|
|
66
|
+
for (const chunk of chunks) {
|
|
67
|
+
const chunkResults = await Promise.all(chunk.map(executeVariant));
|
|
68
|
+
results.push(...chunkResults);
|
|
69
|
+
// Stop on first error if configured
|
|
70
|
+
if (stopOnError && chunkResults.some((r) => !r.success)) {
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
// Execute all in parallel
|
|
77
|
+
const allResults = await Promise.all(config.variants.map(executeVariant));
|
|
78
|
+
results.push(...allResults);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
// Sequential execution
|
|
83
|
+
for (const variant of config.variants) {
|
|
84
|
+
const result = await runVariant(config, variant, contextData, {
|
|
85
|
+
onVariantStart,
|
|
86
|
+
onVariantComplete,
|
|
87
|
+
onVariantError,
|
|
88
|
+
});
|
|
89
|
+
results.push(result);
|
|
90
|
+
// Stop on first error if configured
|
|
91
|
+
if (stopOnError && !result.success) {
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const experimentEndTime = new Date();
|
|
97
|
+
const totalDuration = experimentEndTime.getTime() - experimentStartTime.getTime();
|
|
98
|
+
// Find best variant by metric
|
|
99
|
+
let bestVariant;
|
|
100
|
+
const successfulResults = results.filter((r) => r.success && r.metricValue !== undefined);
|
|
101
|
+
if (successfulResults.length > 0) {
|
|
102
|
+
const best = successfulResults.reduce((prev, current) => (current.metricValue ?? -Infinity) > (prev.metricValue ?? -Infinity) ? current : prev);
|
|
103
|
+
bestVariant = {
|
|
104
|
+
variantId: best.variantId,
|
|
105
|
+
variantName: best.variantName,
|
|
106
|
+
metricValue: best.metricValue,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
const summary = {
|
|
110
|
+
experimentId: config.id,
|
|
111
|
+
experimentName: config.name,
|
|
112
|
+
results,
|
|
113
|
+
bestVariant,
|
|
114
|
+
totalDuration,
|
|
115
|
+
successCount: results.filter((r) => r.success).length,
|
|
116
|
+
failureCount: results.filter((r) => !r.success).length,
|
|
117
|
+
startedAt: experimentStartTime,
|
|
118
|
+
completedAt: experimentEndTime,
|
|
119
|
+
};
|
|
120
|
+
// Track experiment completion
|
|
121
|
+
track({
|
|
122
|
+
type: 'experiment.complete',
|
|
123
|
+
timestamp: experimentEndTime,
|
|
124
|
+
data: {
|
|
125
|
+
experimentId: config.id,
|
|
126
|
+
experimentName: config.name,
|
|
127
|
+
successCount: summary.successCount,
|
|
128
|
+
failureCount: summary.failureCount,
|
|
129
|
+
totalDuration,
|
|
130
|
+
bestVariant: bestVariant?.variantId,
|
|
131
|
+
...config.metadata,
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
return summary;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Run a single variant
|
|
138
|
+
*/
|
|
139
|
+
async function runVariant(config, variant, contextData, callbacks) {
|
|
140
|
+
const runId = randomUUID();
|
|
141
|
+
const startTime = new Date();
|
|
142
|
+
const context = {
|
|
143
|
+
experimentId: config.id,
|
|
144
|
+
variantId: variant.id,
|
|
145
|
+
runId,
|
|
146
|
+
startedAt: startTime,
|
|
147
|
+
data: contextData,
|
|
148
|
+
};
|
|
149
|
+
// Track variant start
|
|
150
|
+
track({
|
|
151
|
+
type: 'variant.start',
|
|
152
|
+
timestamp: startTime,
|
|
153
|
+
data: {
|
|
154
|
+
experimentId: config.id,
|
|
155
|
+
variantId: variant.id,
|
|
156
|
+
variantName: variant.name,
|
|
157
|
+
runId,
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
callbacks.onVariantStart?.(variant.id, variant.name);
|
|
161
|
+
try {
|
|
162
|
+
// Execute the variant
|
|
163
|
+
const result = await config.execute(variant.config, context);
|
|
164
|
+
const endTime = new Date();
|
|
165
|
+
const duration = endTime.getTime() - startTime.getTime();
|
|
166
|
+
// Compute metric if provided
|
|
167
|
+
let metricValue;
|
|
168
|
+
if (config.metric) {
|
|
169
|
+
metricValue = await config.metric(result);
|
|
170
|
+
track({
|
|
171
|
+
type: 'metric.computed',
|
|
172
|
+
timestamp: new Date(),
|
|
173
|
+
data: {
|
|
174
|
+
experimentId: config.id,
|
|
175
|
+
variantId: variant.id,
|
|
176
|
+
runId,
|
|
177
|
+
metricValue,
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
const experimentResult = {
|
|
182
|
+
experimentId: config.id,
|
|
183
|
+
variantId: variant.id,
|
|
184
|
+
variantName: variant.name,
|
|
185
|
+
runId,
|
|
186
|
+
result,
|
|
187
|
+
metricValue,
|
|
188
|
+
duration,
|
|
189
|
+
startedAt: startTime,
|
|
190
|
+
completedAt: endTime,
|
|
191
|
+
success: true,
|
|
192
|
+
};
|
|
193
|
+
// Track variant completion
|
|
194
|
+
track({
|
|
195
|
+
type: 'variant.complete',
|
|
196
|
+
timestamp: endTime,
|
|
197
|
+
data: {
|
|
198
|
+
experimentId: config.id,
|
|
199
|
+
variantId: variant.id,
|
|
200
|
+
variantName: variant.name,
|
|
201
|
+
runId,
|
|
202
|
+
duration,
|
|
203
|
+
metricValue,
|
|
204
|
+
success: true,
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
callbacks.onVariantComplete?.(experimentResult);
|
|
208
|
+
return experimentResult;
|
|
209
|
+
}
|
|
210
|
+
catch (error) {
|
|
211
|
+
const endTime = new Date();
|
|
212
|
+
const duration = endTime.getTime() - startTime.getTime();
|
|
213
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
214
|
+
// Track variant error
|
|
215
|
+
track({
|
|
216
|
+
type: 'variant.error',
|
|
217
|
+
timestamp: endTime,
|
|
218
|
+
data: {
|
|
219
|
+
experimentId: config.id,
|
|
220
|
+
variantId: variant.id,
|
|
221
|
+
variantName: variant.name,
|
|
222
|
+
runId,
|
|
223
|
+
duration,
|
|
224
|
+
error: err.message,
|
|
225
|
+
stack: err.stack,
|
|
226
|
+
},
|
|
227
|
+
});
|
|
228
|
+
callbacks.onVariantError?.(variant.id, err);
|
|
229
|
+
return {
|
|
230
|
+
experimentId: config.id,
|
|
231
|
+
variantId: variant.id,
|
|
232
|
+
variantName: variant.name,
|
|
233
|
+
runId,
|
|
234
|
+
result: undefined,
|
|
235
|
+
duration,
|
|
236
|
+
startedAt: startTime,
|
|
237
|
+
completedAt: endTime,
|
|
238
|
+
error: err,
|
|
239
|
+
success: false,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Split array into chunks
|
|
245
|
+
*/
|
|
246
|
+
function chunkArray(array, size) {
|
|
247
|
+
const chunks = [];
|
|
248
|
+
for (let i = 0; i < array.length; i += size) {
|
|
249
|
+
chunks.push(array.slice(i, i + size));
|
|
250
|
+
}
|
|
251
|
+
return chunks;
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Helper to create experiment variants from a parameter grid
|
|
255
|
+
*
|
|
256
|
+
* @example
|
|
257
|
+
* ```ts
|
|
258
|
+
* const variants = createVariantsFromGrid({
|
|
259
|
+
* temperature: [0.3, 0.7, 1.0],
|
|
260
|
+
* model: ['sonnet', 'opus'],
|
|
261
|
+
* maxTokens: [100, 500],
|
|
262
|
+
* })
|
|
263
|
+
* // Returns 12 variants (3 * 2 * 2 combinations)
|
|
264
|
+
* ```
|
|
265
|
+
*/
|
|
266
|
+
export function createVariantsFromGrid(grid) {
|
|
267
|
+
const keys = Object.keys(grid);
|
|
268
|
+
const values = keys.map((k) => grid[k]);
|
|
269
|
+
// Generate all combinations
|
|
270
|
+
const combinations = cartesianProduct(values);
|
|
271
|
+
return combinations.map((combo, index) => {
|
|
272
|
+
const config = Object.fromEntries(keys.map((key, i) => [key, combo[i]]));
|
|
273
|
+
return {
|
|
274
|
+
id: `variant-${index}`,
|
|
275
|
+
name: keys.map((k, i) => `${String(k)}=${combo[i]}`).join(', '),
|
|
276
|
+
config,
|
|
277
|
+
};
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Cartesian product helper
|
|
282
|
+
*/
|
|
283
|
+
function cartesianProduct(arrays) {
|
|
284
|
+
if (arrays.length === 0)
|
|
285
|
+
return [[]];
|
|
286
|
+
if (arrays.length === 1)
|
|
287
|
+
return arrays[0].map((x) => [x]);
|
|
288
|
+
const [first, ...rest] = arrays;
|
|
289
|
+
const restProduct = cartesianProduct(rest);
|
|
290
|
+
return first.flatMap((x) => restProduct.map((arr) => [x, ...arr]));
|
|
291
|
+
}
|
|
292
|
+
//# sourceMappingURL=experiment.js.map
|