ai-experiments 2.0.2 → 2.1.1
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/CHANGELOG.md +15 -0
- package/examples.js +211 -0
- package/package.json +9 -3
- package/src/cartesian.js +215 -0
- package/src/chdb-storage.js +464 -0
- package/src/chdb-storage.ts +565 -0
- package/src/decide.js +328 -0
- package/src/experiment.js +291 -0
- package/src/index.js +20 -0
- package/src/index.ts +4 -0
- package/src/tracking.js +309 -0
- package/src/types.js +4 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# ai-experiments
|
|
2
2
|
|
|
3
|
+
## 2.1.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [6beb531]
|
|
8
|
+
- ai-functions@2.1.1
|
|
9
|
+
|
|
10
|
+
## 2.0.3
|
|
11
|
+
|
|
12
|
+
### Patch Changes
|
|
13
|
+
|
|
14
|
+
- Updated dependencies
|
|
15
|
+
- rpc.do@0.2.0
|
|
16
|
+
- ai-functions@2.0.3
|
|
17
|
+
|
|
3
18
|
## 2.0.2
|
|
4
19
|
|
|
5
20
|
### Patch Changes
|
package/examples.js
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Usage examples for ai-experiments
|
|
3
|
+
*
|
|
4
|
+
* These examples demonstrate the core APIs and patterns.
|
|
5
|
+
*/
|
|
6
|
+
import { Experiment, cartesian, decide, configureTracking, createMemoryBackend, } from './src/index.js';
|
|
7
|
+
// ============================================================================
|
|
8
|
+
// Example 1: Simple A/B Experiment
|
|
9
|
+
// ============================================================================
|
|
10
|
+
async function example1_simpleExperiment() {
|
|
11
|
+
console.log('\n=== Example 1: Simple A/B Experiment ===\n');
|
|
12
|
+
const results = await Experiment({
|
|
13
|
+
id: 'greeting-test',
|
|
14
|
+
name: 'Greeting Message Test',
|
|
15
|
+
description: 'Test different greeting messages',
|
|
16
|
+
variants: [
|
|
17
|
+
{
|
|
18
|
+
id: 'formal',
|
|
19
|
+
name: 'Formal Greeting',
|
|
20
|
+
config: { message: 'Good day, sir.' },
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
id: 'casual',
|
|
24
|
+
name: 'Casual Greeting',
|
|
25
|
+
config: { message: 'Hey there!' },
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
id: 'friendly',
|
|
29
|
+
name: 'Friendly Greeting',
|
|
30
|
+
config: { message: 'Hello friend!' },
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
execute: async (config) => {
|
|
34
|
+
// Simulate some work
|
|
35
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
36
|
+
return {
|
|
37
|
+
message: config.message,
|
|
38
|
+
wordCount: config.message.split(' ').length,
|
|
39
|
+
charCount: config.message.length,
|
|
40
|
+
};
|
|
41
|
+
},
|
|
42
|
+
metric: (result) => {
|
|
43
|
+
// Score based on message length (prefer shorter)
|
|
44
|
+
return 1 / result.charCount;
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
console.log('Experiment Results:');
|
|
48
|
+
console.log('- Best Variant:', results.bestVariant);
|
|
49
|
+
console.log('- Success Rate:', `${results.successCount}/${results.results.length}`);
|
|
50
|
+
console.log('- Total Duration:', `${results.totalDuration}ms`);
|
|
51
|
+
console.log('\nAll Results:');
|
|
52
|
+
results.results.forEach((r) => {
|
|
53
|
+
console.log(` ${r.variantName}: metric=${r.metricValue?.toFixed(4)}, duration=${r.duration}ms`);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
// ============================================================================
|
|
57
|
+
// Example 2: Parameter Grid with Cartesian Product
|
|
58
|
+
// ============================================================================
|
|
59
|
+
async function example2_parameterGrid() {
|
|
60
|
+
console.log('\n=== Example 2: Parameter Grid Experiment ===\n');
|
|
61
|
+
// Generate all combinations of parameters
|
|
62
|
+
const parameterGrid = cartesian({
|
|
63
|
+
temperature: [0.3, 0.7, 1.0],
|
|
64
|
+
maxTokens: [50, 100],
|
|
65
|
+
topP: [0.9, 1.0],
|
|
66
|
+
});
|
|
67
|
+
console.log(`Generated ${parameterGrid.length} parameter combinations:`);
|
|
68
|
+
parameterGrid.slice(0, 5).forEach((params, i) => {
|
|
69
|
+
console.log(` ${i + 1}.`, params);
|
|
70
|
+
});
|
|
71
|
+
console.log(' ...');
|
|
72
|
+
// Create variants from parameter combinations
|
|
73
|
+
const variants = parameterGrid.map((params, i) => ({
|
|
74
|
+
id: `variant-${i}`,
|
|
75
|
+
name: `T=${params.temperature} max=${params.maxTokens} topP=${params.topP}`,
|
|
76
|
+
config: params,
|
|
77
|
+
}));
|
|
78
|
+
const results = await Experiment({
|
|
79
|
+
id: 'parameter-sweep',
|
|
80
|
+
name: 'Parameter Sweep Experiment',
|
|
81
|
+
variants,
|
|
82
|
+
execute: async (config) => {
|
|
83
|
+
// Simulate AI generation with different parameters
|
|
84
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
85
|
+
const score = Math.random() * config.temperature + config.maxTokens / 1000;
|
|
86
|
+
return { score, config };
|
|
87
|
+
},
|
|
88
|
+
metric: (result) => result.score,
|
|
89
|
+
});
|
|
90
|
+
console.log(`\nBest parameters: ${results.bestVariant?.variantName}`);
|
|
91
|
+
console.log(`Best score: ${results.bestVariant?.metricValue.toFixed(4)}`);
|
|
92
|
+
}
|
|
93
|
+
// ============================================================================
|
|
94
|
+
// Example 3: Decision Making
|
|
95
|
+
// ============================================================================
|
|
96
|
+
async function example3_decisionMaking() {
|
|
97
|
+
console.log('\n=== Example 3: Decision Making ===\n');
|
|
98
|
+
// Simple decision with sync scoring
|
|
99
|
+
const result1 = await decide({
|
|
100
|
+
options: ['apple', 'banana', 'orange'],
|
|
101
|
+
score: (fruit) => {
|
|
102
|
+
const prices = { apple: 1.5, banana: 0.5, orange: 2.0 };
|
|
103
|
+
return 1 / prices[fruit]; // Lower price = higher score
|
|
104
|
+
},
|
|
105
|
+
context: 'Choosing fruit based on value',
|
|
106
|
+
});
|
|
107
|
+
console.log('Decision 1 (best value):');
|
|
108
|
+
console.log(` Selected: ${result1.selected}`);
|
|
109
|
+
console.log(` Score: ${result1.score.toFixed(4)}`);
|
|
110
|
+
// Decision with all options returned
|
|
111
|
+
const result2 = await decide({
|
|
112
|
+
options: ['fast', 'accurate', 'balanced'],
|
|
113
|
+
score: async (approach) => {
|
|
114
|
+
const scores = { fast: 0.7, accurate: 0.85, balanced: 0.9 };
|
|
115
|
+
return scores[approach];
|
|
116
|
+
},
|
|
117
|
+
returnAll: true,
|
|
118
|
+
});
|
|
119
|
+
console.log('\nDecision 2 (with all options):');
|
|
120
|
+
console.log(` Selected: ${result2.selected}`);
|
|
121
|
+
result2.allOptions?.forEach((opt) => {
|
|
122
|
+
console.log(` - ${opt.option}: ${opt.score}`);
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
// ============================================================================
|
|
126
|
+
// Example 4: Event Tracking
|
|
127
|
+
// ============================================================================
|
|
128
|
+
async function example4_eventTracking() {
|
|
129
|
+
console.log('\n=== Example 4: Event Tracking ===\n');
|
|
130
|
+
// Create a memory backend to collect events
|
|
131
|
+
const backend = createMemoryBackend();
|
|
132
|
+
configureTracking({ backend });
|
|
133
|
+
// Run a small experiment
|
|
134
|
+
await Experiment({
|
|
135
|
+
id: 'tracked-experiment',
|
|
136
|
+
name: 'Tracked Experiment',
|
|
137
|
+
variants: [
|
|
138
|
+
{ id: 'v1', name: 'Variant 1', config: { value: 1 } },
|
|
139
|
+
{ id: 'v2', name: 'Variant 2', config: { value: 2 } },
|
|
140
|
+
],
|
|
141
|
+
execute: async (config) => ({ result: config.value * 2 }),
|
|
142
|
+
metric: (result) => result.result,
|
|
143
|
+
});
|
|
144
|
+
// Get all tracked events
|
|
145
|
+
const events = backend.getEvents();
|
|
146
|
+
console.log(`Tracked ${events.length} events:`);
|
|
147
|
+
events.forEach((event) => {
|
|
148
|
+
console.log(` [${event.type}]`, JSON.stringify(event.data).slice(0, 80));
|
|
149
|
+
});
|
|
150
|
+
// Reset tracking to default
|
|
151
|
+
configureTracking({ enabled: true });
|
|
152
|
+
}
|
|
153
|
+
// ============================================================================
|
|
154
|
+
// Example 5: Advanced - Sequential vs Parallel Execution
|
|
155
|
+
// ============================================================================
|
|
156
|
+
async function example5_executionModes() {
|
|
157
|
+
console.log('\n=== Example 5: Sequential vs Parallel Execution ===\n');
|
|
158
|
+
const variants = [
|
|
159
|
+
{ id: 'v1', name: 'Variant 1', config: { delay: 100 } },
|
|
160
|
+
{ id: 'v2', name: 'Variant 2', config: { delay: 150 } },
|
|
161
|
+
{ id: 'v3', name: 'Variant 3', config: { delay: 200 } },
|
|
162
|
+
];
|
|
163
|
+
const execute = async (config) => {
|
|
164
|
+
await new Promise((resolve) => setTimeout(resolve, config.delay));
|
|
165
|
+
return { completed: true };
|
|
166
|
+
};
|
|
167
|
+
// Parallel execution (default)
|
|
168
|
+
const start1 = Date.now();
|
|
169
|
+
await Experiment({
|
|
170
|
+
id: 'parallel-test',
|
|
171
|
+
name: 'Parallel Execution',
|
|
172
|
+
variants,
|
|
173
|
+
execute,
|
|
174
|
+
});
|
|
175
|
+
const parallel_duration = Date.now() - start1;
|
|
176
|
+
// Sequential execution
|
|
177
|
+
const start2 = Date.now();
|
|
178
|
+
await Experiment({
|
|
179
|
+
id: 'sequential-test',
|
|
180
|
+
name: 'Sequential Execution',
|
|
181
|
+
variants,
|
|
182
|
+
execute,
|
|
183
|
+
}, { parallel: false });
|
|
184
|
+
const sequential_duration = Date.now() - start2;
|
|
185
|
+
console.log(`Parallel execution: ${parallel_duration}ms`);
|
|
186
|
+
console.log(`Sequential execution: ${sequential_duration}ms`);
|
|
187
|
+
console.log(`Speedup: ${(sequential_duration / parallel_duration).toFixed(2)}x`);
|
|
188
|
+
}
|
|
189
|
+
// ============================================================================
|
|
190
|
+
// Run all examples
|
|
191
|
+
// ============================================================================
|
|
192
|
+
async function main() {
|
|
193
|
+
// Disable verbose tracking for examples
|
|
194
|
+
configureTracking({ enabled: false });
|
|
195
|
+
try {
|
|
196
|
+
await example1_simpleExperiment();
|
|
197
|
+
await example2_parameterGrid();
|
|
198
|
+
await example3_decisionMaking();
|
|
199
|
+
await example4_eventTracking();
|
|
200
|
+
await example5_executionModes();
|
|
201
|
+
console.log('\n✅ All examples completed successfully!\n');
|
|
202
|
+
}
|
|
203
|
+
catch (error) {
|
|
204
|
+
console.error('\n❌ Error running examples:', error);
|
|
205
|
+
process.exit(1);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
// Run if this file is executed directly
|
|
209
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
210
|
+
main();
|
|
211
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-experiments",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.1",
|
|
4
4
|
"description": "AI-powered experimentation primitives for testing and evaluating models",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -9,6 +9,10 @@
|
|
|
9
9
|
".": {
|
|
10
10
|
"import": "./dist/index.js",
|
|
11
11
|
"types": "./dist/index.d.ts"
|
|
12
|
+
},
|
|
13
|
+
"./storage": {
|
|
14
|
+
"import": "./dist/chdb-storage.js",
|
|
15
|
+
"types": "./dist/chdb-storage.d.ts"
|
|
12
16
|
}
|
|
13
17
|
},
|
|
14
18
|
"scripts": {
|
|
@@ -20,12 +24,14 @@
|
|
|
20
24
|
"clean": "rm -rf dist"
|
|
21
25
|
},
|
|
22
26
|
"dependencies": {
|
|
23
|
-
"ai-functions": "2.
|
|
24
|
-
"
|
|
27
|
+
"ai-functions": "2.1.1",
|
|
28
|
+
"chdb": "^1.6.0"
|
|
25
29
|
},
|
|
26
30
|
"keywords": [
|
|
27
31
|
"ai",
|
|
28
32
|
"experiments",
|
|
33
|
+
"clickhouse",
|
|
34
|
+
"analytics",
|
|
29
35
|
"primitives"
|
|
30
36
|
],
|
|
31
37
|
"license": "MIT"
|
package/src/cartesian.js
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cartesian product utilities for parameter exploration
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Generate cartesian product of parameter sets
|
|
6
|
+
*
|
|
7
|
+
* Takes an object where each key maps to an array of possible values,
|
|
8
|
+
* and returns all possible combinations as an array of objects.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* import { cartesian } from 'ai-experiments'
|
|
13
|
+
*
|
|
14
|
+
* const combinations = cartesian({
|
|
15
|
+
* model: ['sonnet', 'opus', 'gpt-4o'],
|
|
16
|
+
* temperature: [0.3, 0.7, 1.0],
|
|
17
|
+
* maxTokens: [100, 500, 1000],
|
|
18
|
+
* })
|
|
19
|
+
*
|
|
20
|
+
* // Returns 27 combinations (3 * 3 * 3):
|
|
21
|
+
* // [
|
|
22
|
+
* // { model: 'sonnet', temperature: 0.3, maxTokens: 100 },
|
|
23
|
+
* // { model: 'sonnet', temperature: 0.3, maxTokens: 500 },
|
|
24
|
+
* // { model: 'sonnet', temperature: 0.3, maxTokens: 1000 },
|
|
25
|
+
* // { model: 'sonnet', temperature: 0.7, maxTokens: 100 },
|
|
26
|
+
* // ...
|
|
27
|
+
* // ]
|
|
28
|
+
*
|
|
29
|
+
* // Use with experiments:
|
|
30
|
+
* const variants = combinations.map((config, i) => ({
|
|
31
|
+
* id: `variant-${i}`,
|
|
32
|
+
* name: `${config.model} T=${config.temperature} max=${config.maxTokens}`,
|
|
33
|
+
* config,
|
|
34
|
+
* }))
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export function cartesian(params) {
|
|
38
|
+
const keys = Object.keys(params);
|
|
39
|
+
const values = keys.map((k) => params[k]);
|
|
40
|
+
// Handle empty input
|
|
41
|
+
if (keys.length === 0) {
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
// Handle single parameter
|
|
45
|
+
if (keys.length === 1) {
|
|
46
|
+
const key = keys[0];
|
|
47
|
+
return values[0].map((val) => ({ [key]: val }));
|
|
48
|
+
}
|
|
49
|
+
// Generate all combinations using recursive helper
|
|
50
|
+
const combinations = cartesianProduct(values);
|
|
51
|
+
// Map back to objects
|
|
52
|
+
return combinations.map((combo) => {
|
|
53
|
+
const obj = {};
|
|
54
|
+
keys.forEach((key, i) => {
|
|
55
|
+
obj[key] = combo[i];
|
|
56
|
+
});
|
|
57
|
+
return obj;
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Recursive cartesian product implementation
|
|
62
|
+
*/
|
|
63
|
+
function cartesianProduct(arrays) {
|
|
64
|
+
if (arrays.length === 0)
|
|
65
|
+
return [[]];
|
|
66
|
+
const [first, ...rest] = arrays;
|
|
67
|
+
// Base case: single array
|
|
68
|
+
if (rest.length === 0) {
|
|
69
|
+
return first.map((x) => [x]);
|
|
70
|
+
}
|
|
71
|
+
// Recursive case
|
|
72
|
+
const restProduct = cartesianProduct(rest);
|
|
73
|
+
const result = [];
|
|
74
|
+
for (const x of first) {
|
|
75
|
+
for (const restCombo of restProduct) {
|
|
76
|
+
result.push([x, ...restCombo]);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Generate a grid of parameter combinations with filtering
|
|
83
|
+
*
|
|
84
|
+
* Similar to cartesian(), but allows filtering out invalid combinations.
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* ```ts
|
|
88
|
+
* import { cartesianFilter } from 'ai-experiments'
|
|
89
|
+
*
|
|
90
|
+
* const combinations = cartesianFilter(
|
|
91
|
+
* {
|
|
92
|
+
* model: ['sonnet', 'opus'],
|
|
93
|
+
* temperature: [0.3, 0.7, 1.0],
|
|
94
|
+
* maxTokens: [100, 500],
|
|
95
|
+
* },
|
|
96
|
+
* // Filter out combinations where opus uses high temperature
|
|
97
|
+
* (combo) => !(combo.model === 'opus' && combo.temperature > 0.7)
|
|
98
|
+
* )
|
|
99
|
+
* ```
|
|
100
|
+
*/
|
|
101
|
+
export function cartesianFilter(params, filter) {
|
|
102
|
+
const allCombinations = cartesian(params);
|
|
103
|
+
return allCombinations.filter(filter);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Generate a random sample from the cartesian product
|
|
107
|
+
*
|
|
108
|
+
* Useful when the full cartesian product is too large to test all combinations.
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* ```ts
|
|
112
|
+
* import { cartesianSample } from 'ai-experiments'
|
|
113
|
+
*
|
|
114
|
+
* // Full product would be 1000 combinations (10 * 10 * 10)
|
|
115
|
+
* // Sample just 20 random combinations
|
|
116
|
+
* const sample = cartesianSample(
|
|
117
|
+
* {
|
|
118
|
+
* param1: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
|
|
119
|
+
* param2: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
|
|
120
|
+
* param3: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
|
|
121
|
+
* },
|
|
122
|
+
* 20
|
|
123
|
+
* )
|
|
124
|
+
* ```
|
|
125
|
+
*/
|
|
126
|
+
export function cartesianSample(params, sampleSize, options = {}) {
|
|
127
|
+
const { unique = true } = options;
|
|
128
|
+
// Generate all combinations first
|
|
129
|
+
const allCombinations = cartesian(params);
|
|
130
|
+
// If sample size is larger than available combinations, return all
|
|
131
|
+
if (sampleSize >= allCombinations.length) {
|
|
132
|
+
return allCombinations;
|
|
133
|
+
}
|
|
134
|
+
// Shuffle and take first n items
|
|
135
|
+
const shuffled = [...allCombinations];
|
|
136
|
+
// Simple Fisher-Yates shuffle
|
|
137
|
+
for (let i = shuffled.length - 1; i > 0; i--) {
|
|
138
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
139
|
+
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
|
|
140
|
+
}
|
|
141
|
+
return shuffled.slice(0, sampleSize);
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Count the total number of combinations without generating them
|
|
145
|
+
*
|
|
146
|
+
* Useful for checking if cartesian product is feasible before generating.
|
|
147
|
+
*
|
|
148
|
+
* @example
|
|
149
|
+
* ```ts
|
|
150
|
+
* import { cartesianCount } from 'ai-experiments'
|
|
151
|
+
*
|
|
152
|
+
* const count = cartesianCount({
|
|
153
|
+
* model: ['sonnet', 'opus', 'gpt-4o'],
|
|
154
|
+
* temperature: [0.3, 0.5, 0.7, 0.9],
|
|
155
|
+
* maxTokens: [100, 500, 1000, 2000],
|
|
156
|
+
* })
|
|
157
|
+
* // Returns 48 (3 * 4 * 4)
|
|
158
|
+
*
|
|
159
|
+
* if (count > 100) {
|
|
160
|
+
* console.log('Too many combinations, use cartesianSample instead')
|
|
161
|
+
* }
|
|
162
|
+
* ```
|
|
163
|
+
*/
|
|
164
|
+
export function cartesianCount(params) {
|
|
165
|
+
const keys = Object.keys(params);
|
|
166
|
+
if (keys.length === 0)
|
|
167
|
+
return 0;
|
|
168
|
+
return keys.reduce((total, key) => {
|
|
169
|
+
const arr = params[key];
|
|
170
|
+
return total * (arr?.length ?? 0);
|
|
171
|
+
}, 1);
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Generate cartesian product with labels for each dimension
|
|
175
|
+
*
|
|
176
|
+
* Returns combinations with additional metadata about which dimension each value came from.
|
|
177
|
+
*
|
|
178
|
+
* @example
|
|
179
|
+
* ```ts
|
|
180
|
+
* import { cartesianWithLabels } from 'ai-experiments'
|
|
181
|
+
*
|
|
182
|
+
* const labeled = cartesianWithLabels({
|
|
183
|
+
* model: ['sonnet', 'opus'],
|
|
184
|
+
* temperature: [0.3, 0.7],
|
|
185
|
+
* })
|
|
186
|
+
* // [
|
|
187
|
+
* // { values: { model: 'sonnet', temperature: 0.3 }, labels: { model: 0, temperature: 0 } },
|
|
188
|
+
* // { values: { model: 'sonnet', temperature: 0.7 }, labels: { model: 0, temperature: 1 } },
|
|
189
|
+
* // { values: { model: 'opus', temperature: 0.3 }, labels: { model: 1, temperature: 0 } },
|
|
190
|
+
* // { values: { model: 'opus', temperature: 0.7 }, labels: { model: 1, temperature: 1 } },
|
|
191
|
+
* // ]
|
|
192
|
+
* ```
|
|
193
|
+
*/
|
|
194
|
+
export function cartesianWithLabels(params) {
|
|
195
|
+
const keys = Object.keys(params);
|
|
196
|
+
const values = keys.map((k) => params[k]);
|
|
197
|
+
if (keys.length === 0) {
|
|
198
|
+
return [];
|
|
199
|
+
}
|
|
200
|
+
const combinations = cartesianProduct(values);
|
|
201
|
+
return combinations.map((combo) => {
|
|
202
|
+
const valuesObj = {};
|
|
203
|
+
const labelsObj = {};
|
|
204
|
+
keys.forEach((key, i) => {
|
|
205
|
+
const value = combo[i];
|
|
206
|
+
valuesObj[key] = value;
|
|
207
|
+
const arr = params[key];
|
|
208
|
+
labelsObj[key] = arr ? arr.indexOf(value) : -1;
|
|
209
|
+
});
|
|
210
|
+
return {
|
|
211
|
+
values: valuesObj,
|
|
212
|
+
labels: labelsObj,
|
|
213
|
+
};
|
|
214
|
+
});
|
|
215
|
+
}
|