ai-experiments 0.1.0 → 2.0.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.
@@ -0,0 +1,5 @@
1
+
2
+ 
3
+ > ai-experiments@2.0.1 build /Users/nathanclevenger/projects/primitives.org.ai/packages/ai-experiments
4
+ > tsc
5
+
package/CHANGELOG.md ADDED
@@ -0,0 +1,8 @@
1
+ # ai-experiments
2
+
3
+ ## 2.0.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies
8
+ - ai-functions@2.0.1
package/README.md CHANGED
@@ -1,146 +1,361 @@
1
1
  # ai-experiments
2
2
 
3
- A minimalistic experiment runner for AI tasks.
3
+ AI-powered experimentation primitives for testing and evaluating models.
4
+
5
+ ## Overview
6
+
7
+ `ai-experiments` provides a comprehensive toolkit for A/B testing, parameter exploration, decision making, and tracking in AI applications. It follows the same patterns and conventions as `ai-functions` from the primitives monorepo.
4
8
 
5
9
  ## Installation
6
10
 
7
11
  ```bash
8
- npm install ai-experiments
9
- # or
10
- yarn add ai-experiments
11
- # or
12
12
  pnpm add ai-experiments
13
13
  ```
14
14
 
15
- ## Usage
15
+ ## Core APIs
16
16
 
17
- ### Basic Example
17
+ ### `Experiment()` - A/B Testing and Variant Evaluation
18
+
19
+ Run experiments with multiple variants to find the best configuration.
18
20
 
19
21
  ```typescript
20
- import { Experiment } from 'ai-experiments';
22
+ import { Experiment } from 'ai-experiments'
23
+
24
+ const results = await Experiment({
25
+ id: 'prompt-comparison',
26
+ name: 'Prompt Engineering Test',
27
+ variants: [
28
+ {
29
+ id: 'baseline',
30
+ name: 'Baseline Prompt',
31
+ config: { prompt: 'Summarize this text.' },
32
+ },
33
+ {
34
+ id: 'detailed',
35
+ name: 'Detailed Prompt',
36
+ config: { prompt: 'Provide a comprehensive summary...' },
37
+ },
38
+ ],
39
+ execute: async (config) => {
40
+ return await ai.generate({ prompt: config.prompt })
41
+ },
42
+ metric: (result) => result.quality_score,
43
+ })
44
+
45
+ console.log('Best variant:', results.bestVariant)
46
+ ```
21
47
 
22
- const result = await Experiment('simple-test', {
23
- models: ['gpt-4o'],
24
- temperature: 0.7,
25
- prompt: 'What is the capital of France?',
26
- });
48
+ **Options:**
49
+ - `parallel: true` - Run variants in parallel (default)
50
+ - `maxConcurrency: 5` - Limit concurrent executions
51
+ - `stopOnError: false` - Stop on first error
52
+ - Event callbacks: `onVariantStart`, `onVariantComplete`, `onVariantError`
27
53
 
28
- console.log(result);
29
- ```
54
+ ### `cartesian()` - Parameter Grid Exploration
30
55
 
31
- ### Using Parameter Combinations
56
+ Generate all combinations of parameters for exhaustive testing.
32
57
 
33
58
  ```typescript
34
- import { Experiment } from 'ai-experiments';
35
-
36
- const result = await Experiment('temperature-comparison', {
37
- models: ['gpt-4o', 'gpt-4o-mini'],
38
- temperature: [0, 0.3, 0.7, 1.0],
39
- prompt: 'Generate a creative story about a robot.',
40
- });
59
+ import { cartesian } from 'ai-experiments'
41
60
 
42
- // This will run 8 combinations (2 models × 4 temperatures)
43
- console.log(result);
61
+ const combinations = cartesian({
62
+ model: ['sonnet', 'opus', 'gpt-4o'],
63
+ temperature: [0.3, 0.7, 1.0],
64
+ maxTokens: [100, 500, 1000],
65
+ })
66
+ // Returns 27 combinations (3 × 3 × 3)
67
+
68
+ // Use with experiments:
69
+ const variants = combinations.map((config, i) => ({
70
+ id: `variant-${i}`,
71
+ name: `${config.model} T=${config.temperature}`,
72
+ config,
73
+ }))
44
74
  ```
45
75
 
46
- ### Using the Cartesian Function Directly
76
+ **Related functions:**
77
+ - `cartesianFilter()` - Filter invalid combinations
78
+ - `cartesianSample()` - Random sample when full product is too large
79
+ - `cartesianCount()` - Count combinations without generating them
80
+ - `cartesianWithLabels()` - Include dimension indices
47
81
 
48
- ```typescript
49
- import { cartesian } from 'ai-experiments';
82
+ ### `decide()` - Intelligent Decision Making
50
83
 
51
- const combinations = cartesian({
52
- model: ['gpt-4o', 'gpt-4o-mini'],
53
- temperature: [0, 0.7],
54
- maxTokens: [100, 500]
55
- });
84
+ Make decisions by scoring and comparing options.
56
85
 
57
- // Returns:
86
+ ```typescript
87
+ import { decide } from 'ai-experiments'
88
+
89
+ // Simple decision
90
+ const result = await decide({
91
+ options: ['fast', 'accurate', 'balanced'],
92
+ score: (approach) => evaluateApproach(approach),
93
+ context: 'Choosing summarization approach',
94
+ })
95
+
96
+ console.log(result.selected) // 'balanced'
97
+ console.log(result.score) // 0.9
98
+
99
+ // Return all options sorted by score
100
+ const result = await decide({
101
+ options: ['option-a', 'option-b', 'option-c'],
102
+ score: async (opt) => await scoreOption(opt),
103
+ returnAll: true,
104
+ })
105
+
106
+ console.log(result.allOptions)
58
107
  // [
59
- // { model: 'gpt-4o', temperature: 0, maxTokens: 100 },
60
- // { model: 'gpt-4o', temperature: 0, maxTokens: 500 },
61
- // { model: 'gpt-4o', temperature: 0.7, maxTokens: 100 },
62
- // { model: 'gpt-4o', temperature: 0.7, maxTokens: 500 },
63
- // { model: 'gpt-4o-mini', temperature: 0, maxTokens: 100 },
64
- // { model: 'gpt-4o-mini', temperature: 0, maxTokens: 500 },
65
- // { model: 'gpt-4o-mini', temperature: 0.7, maxTokens: 100 },
66
- // { model: 'gpt-4o-mini', temperature: 0.7, maxTokens: 500 }
108
+ // { option: 'option-b', score: 0.95 },
109
+ // { option: 'option-a', score: 0.82 },
110
+ // { option: 'option-c', score: 0.71 },
67
111
  // ]
68
112
  ```
69
113
 
70
- ### Using the Runner
114
+ **Advanced decision strategies:**
115
+ - `decideWeighted()` - Weighted random selection
116
+ - `decideEpsilonGreedy()` - Exploration vs exploitation
117
+ - `decideThompsonSampling()` - Bayesian bandit algorithm
118
+ - `decideUCB()` - Upper Confidence Bound
119
+
120
+ ### `track()` - Event Tracking
121
+
122
+ Track experiment events and metrics.
71
123
 
72
124
  ```typescript
73
- // vitest.config.ts
74
- import { defineConfig } from 'vitest/config';
75
- import { createRunner } from 'ai-experiments';
76
-
77
- export default createRunner({
78
- outputDir: '.ai/experiments',
79
- testMatch: ['**/*experiment*.(js|ts|mjs|cjs)'],
80
- watch: false,
81
- });
125
+ import { track, configureTracking, createFileBackend } from 'ai-experiments'
126
+
127
+ // Configure tracking backend
128
+ configureTracking({
129
+ backend: createFileBackend({ path: './experiments.jsonl' }),
130
+ metadata: { projectId: 'my-project' },
131
+ })
132
+
133
+ // Events are automatically tracked by Experiment()
134
+ // You can also track custom events:
135
+ track({
136
+ type: 'experiment.start',
137
+ timestamp: new Date(),
138
+ data: {
139
+ experimentId: 'my-experiment',
140
+ variantCount: 3,
141
+ },
142
+ })
82
143
  ```
83
144
 
84
- ## API Reference
145
+ **Built-in backends:**
146
+ - `createConsoleBackend()` - Log to console (default)
147
+ - `createMemoryBackend()` - Store events in memory
148
+ - `createBatchBackend()` - Batch events before sending
149
+ - `createFileBackend()` - Write to JSONL file
150
+
151
+ ## Usage Patterns
152
+
153
+ ### Pattern 1: Parameter Sweep
85
154
 
86
- ### Experiment
155
+ Test all combinations of hyperparameters:
87
156
 
88
157
  ```typescript
89
- function Experiment<T = any, E = any>(
90
- name: string,
91
- config: ExperimentConfig<T, E>
92
- ): Promise<ExperimentResult>
158
+ import { cartesian, Experiment } from 'ai-experiments'
159
+
160
+ const paramGrid = cartesian({
161
+ temperature: [0.3, 0.5, 0.7, 0.9],
162
+ topP: [0.9, 0.95, 1.0],
163
+ maxTokens: [100, 500, 1000],
164
+ })
165
+
166
+ const variants = paramGrid.map((params, i) => ({
167
+ id: `config-${i}`,
168
+ name: `T=${params.temperature} P=${params.topP} max=${params.maxTokens}`,
169
+ config: params,
170
+ }))
171
+
172
+ const results = await Experiment({
173
+ id: 'param-sweep',
174
+ name: 'Hyperparameter Optimization',
175
+ variants,
176
+ execute: async (config) => {
177
+ return await ai.generate({ ...config, prompt: 'Test prompt' })
178
+ },
179
+ metric: (result) => evaluateQuality(result),
180
+ })
181
+
182
+ console.log('Best config:', results.bestVariant)
93
183
  ```
94
184
 
95
- #### Parameters
185
+ ### Pattern 2: Progressive Testing
96
186
 
97
- - `name`: Name of the experiment
98
- - `config`: Configuration object with the following properties:
99
- - `models`: Array of model names to use
100
- - `temperature`: Number or array of temperature values
101
- - `seed` (optional): Number or array of seed values
102
- - `prompt` (optional): String or function that generates prompts
103
- - `inputs` (optional): Array or function that returns input values
104
- - `expected` (optional): Expected output for validation
105
- - `schema` (optional): Schema for structured output
187
+ Start with a sample, then test more if needed:
106
188
 
107
- #### Returns
189
+ ```typescript
190
+ import { cartesianSample, Experiment } from 'ai-experiments'
191
+
192
+ // Sample 20 random combinations from a large space
193
+ const sample = cartesianSample(
194
+ {
195
+ param1: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
196
+ param2: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
197
+ param3: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
198
+ },
199
+ 20
200
+ )
201
+
202
+ const results = await Experiment({
203
+ id: 'initial-sample',
204
+ name: 'Initial Random Sample',
205
+ variants: sample.map((config, i) => ({
206
+ id: `sample-${i}`,
207
+ name: `Sample ${i}`,
208
+ config,
209
+ })),
210
+ execute: async (config) => runTest(config),
211
+ metric: (result) => result.score,
212
+ })
213
+
214
+ // If results are promising, expand the search
215
+ if (results.bestVariant && results.bestVariant.metricValue > 0.8) {
216
+ console.log('Found promising region, expanding search...')
217
+ // Test more combinations near the best one
218
+ }
219
+ ```
108
220
 
109
- Promise that resolves to an `ExperimentResult` object with:
110
- - `name`: Name of the experiment
111
- - `results`: Array of results for each parameter combination
112
- - `totalTime`: Total time taken for the experiment
113
- - `timestamp`: ISO string of when the experiment was run
221
+ ### Pattern 3: Multi-Armed Bandit
114
222
 
115
- ### cartesian
223
+ Adaptively choose variants based on performance:
116
224
 
117
225
  ```typescript
118
- function cartesian<T extends Record<string, readonly any[]>>(
119
- spec: T
120
- ): Array<{ [K in keyof T]: T[K][number] }>
226
+ import { decideThompsonSampling, track } from 'ai-experiments'
227
+
228
+ // Track success/failure for each variant
229
+ const stats = {
230
+ 'variant-a': { alpha: 10, beta: 5 }, // 10 successes, 5 failures
231
+ 'variant-b': { alpha: 8, beta: 3 }, // 8 successes, 3 failures
232
+ 'variant-c': { alpha: 2, beta: 2 }, // 2 successes, 2 failures (uncertain)
233
+ }
234
+
235
+ // Thompson sampling balances exploration and exploitation
236
+ const selected = decideThompsonSampling(
237
+ ['variant-a', 'variant-b', 'variant-c'],
238
+ stats
239
+ )
240
+
241
+ // Update stats based on result
242
+ const result = await runVariant(selected)
243
+ if (result.success) {
244
+ stats[selected].alpha += 1
245
+ } else {
246
+ stats[selected].beta += 1
247
+ }
121
248
  ```
122
249
 
123
- #### Parameters
250
+ ### Pattern 4: Sequential Testing with Early Stopping
124
251
 
125
- - `spec`: Object with keys mapping to arrays of values
252
+ Stop testing once a clear winner emerges:
126
253
 
127
- #### Returns
254
+ ```typescript
255
+ import { Experiment } from 'ai-experiments'
256
+
257
+ let bestScore = 0
258
+ let testCount = 0
259
+ const maxTests = 100
260
+
261
+ const results = await Experiment(
262
+ {
263
+ id: 'sequential-test',
264
+ name: 'Sequential Testing',
265
+ variants: [...],
266
+ execute: async (config) => runTest(config),
267
+ metric: (result) => result.score,
268
+ },
269
+ {
270
+ parallel: false, // Sequential execution
271
+ onVariantComplete: (result) => {
272
+ testCount++
273
+ if (result.metricValue && result.metricValue > bestScore) {
274
+ bestScore = result.metricValue
275
+ }
276
+
277
+ // Stop if we found a really good result
278
+ if (bestScore > 0.95) {
279
+ console.log('Found excellent result, stopping early')
280
+ // In a real implementation, you'd need to handle early stopping
281
+ }
282
+ },
283
+ }
284
+ )
285
+ ```
128
286
 
129
- Array of objects representing all possible combinations of the input values.
287
+ ## TypeScript Types
130
288
 
131
- ### createRunner
289
+ The package is fully typed with comprehensive TypeScript definitions:
132
290
 
133
291
  ```typescript
134
- function createRunner(config?: RunnerConfig): VitestConfig
292
+ import type {
293
+ ExperimentConfig,
294
+ ExperimentResult,
295
+ ExperimentSummary,
296
+ ExperimentVariant,
297
+ DecisionResult,
298
+ TrackingEvent,
299
+ TrackingBackend,
300
+ } from 'ai-experiments'
135
301
  ```
136
302
 
137
- #### Parameters
303
+ ## Integration with ai-functions
138
304
 
139
- - `config` (optional): Configuration object with the following properties:
140
- - `outputDir` (optional): Directory where experiment results will be saved
141
- - `testMatch` (optional): Custom test matcher pattern
142
- - `watch` (optional): Whether to watch for file changes
305
+ Works seamlessly with `ai-functions` for AI-powered experiments:
306
+
307
+ ```typescript
308
+ import { generateObject } from 'ai-functions'
309
+ import { Experiment, cartesian } from 'ai-experiments'
310
+
311
+ const prompts = [
312
+ 'Summarize briefly',
313
+ 'Provide a detailed summary',
314
+ 'Extract key points',
315
+ ]
316
+
317
+ const models = ['sonnet', 'opus', 'gpt-4o']
318
+
319
+ // Test all combinations of prompts and models
320
+ const combinations = cartesian({ prompt: prompts, model: models })
321
+
322
+ const results = await Experiment({
323
+ id: 'prompt-model-test',
324
+ name: 'Prompt and Model Comparison',
325
+ variants: combinations.map((config, i) => ({
326
+ id: `combo-${i}`,
327
+ name: `${config.model}: "${config.prompt.slice(0, 20)}..."`,
328
+ config,
329
+ })),
330
+ execute: async (config) => {
331
+ return await generateObject({
332
+ model: config.model,
333
+ schema: { summary: 'The summary text' },
334
+ prompt: config.prompt,
335
+ })
336
+ },
337
+ metric: (result) => evaluateSummary(result.object.summary),
338
+ })
339
+
340
+ console.log('Best combination:', results.bestVariant)
341
+ ```
342
+
343
+ ## Examples
344
+
345
+ See [examples.ts](./examples.ts) for complete working examples demonstrating:
346
+ - Simple A/B experiments
347
+ - Parameter grid exploration
348
+ - Decision making strategies
349
+ - Event tracking
350
+ - Sequential vs parallel execution
351
+
352
+ Run the examples:
353
+
354
+ ```bash
355
+ pnpm build
356
+ node --import tsx examples.ts
357
+ ```
143
358
 
144
- #### Returns
359
+ ## License
145
360
 
146
- A Vitest configuration function that can be used in `vitest.config.ts`.
361
+ MIT
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Cartesian product utilities for parameter exploration
3
+ */
4
+ import type { CartesianParams, CartesianResult } from './types.js';
5
+ /**
6
+ * Generate cartesian product of parameter sets
7
+ *
8
+ * Takes an object where each key maps to an array of possible values,
9
+ * and returns all possible combinations as an array of objects.
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * import { cartesian } from 'ai-experiments'
14
+ *
15
+ * const combinations = cartesian({
16
+ * model: ['sonnet', 'opus', 'gpt-4o'],
17
+ * temperature: [0.3, 0.7, 1.0],
18
+ * maxTokens: [100, 500, 1000],
19
+ * })
20
+ *
21
+ * // Returns 27 combinations (3 * 3 * 3):
22
+ * // [
23
+ * // { model: 'sonnet', temperature: 0.3, maxTokens: 100 },
24
+ * // { model: 'sonnet', temperature: 0.3, maxTokens: 500 },
25
+ * // { model: 'sonnet', temperature: 0.3, maxTokens: 1000 },
26
+ * // { model: 'sonnet', temperature: 0.7, maxTokens: 100 },
27
+ * // ...
28
+ * // ]
29
+ *
30
+ * // Use with experiments:
31
+ * const variants = combinations.map((config, i) => ({
32
+ * id: `variant-${i}`,
33
+ * name: `${config.model} T=${config.temperature} max=${config.maxTokens}`,
34
+ * config,
35
+ * }))
36
+ * ```
37
+ */
38
+ export declare function cartesian<T extends CartesianParams>(params: T): CartesianResult<T>;
39
+ /**
40
+ * Generate a grid of parameter combinations with filtering
41
+ *
42
+ * Similar to cartesian(), but allows filtering out invalid combinations.
43
+ *
44
+ * @example
45
+ * ```ts
46
+ * import { cartesianFilter } from 'ai-experiments'
47
+ *
48
+ * const combinations = cartesianFilter(
49
+ * {
50
+ * model: ['sonnet', 'opus'],
51
+ * temperature: [0.3, 0.7, 1.0],
52
+ * maxTokens: [100, 500],
53
+ * },
54
+ * // Filter out combinations where opus uses high temperature
55
+ * (combo) => !(combo.model === 'opus' && combo.temperature > 0.7)
56
+ * )
57
+ * ```
58
+ */
59
+ export declare function cartesianFilter<T extends CartesianParams>(params: T, filter: (combo: {
60
+ [K in keyof T]: T[K][number];
61
+ }) => boolean): CartesianResult<T>;
62
+ /**
63
+ * Generate a random sample from the cartesian product
64
+ *
65
+ * Useful when the full cartesian product is too large to test all combinations.
66
+ *
67
+ * @example
68
+ * ```ts
69
+ * import { cartesianSample } from 'ai-experiments'
70
+ *
71
+ * // Full product would be 1000 combinations (10 * 10 * 10)
72
+ * // Sample just 20 random combinations
73
+ * const sample = cartesianSample(
74
+ * {
75
+ * param1: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
76
+ * param2: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
77
+ * param3: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
78
+ * },
79
+ * 20
80
+ * )
81
+ * ```
82
+ */
83
+ export declare function cartesianSample<T extends CartesianParams>(params: T, sampleSize: number, options?: {
84
+ /** Random seed for reproducibility */
85
+ seed?: number;
86
+ /** Whether to sample without replacement (default: true) */
87
+ unique?: boolean;
88
+ }): CartesianResult<T>;
89
+ /**
90
+ * Count the total number of combinations without generating them
91
+ *
92
+ * Useful for checking if cartesian product is feasible before generating.
93
+ *
94
+ * @example
95
+ * ```ts
96
+ * import { cartesianCount } from 'ai-experiments'
97
+ *
98
+ * const count = cartesianCount({
99
+ * model: ['sonnet', 'opus', 'gpt-4o'],
100
+ * temperature: [0.3, 0.5, 0.7, 0.9],
101
+ * maxTokens: [100, 500, 1000, 2000],
102
+ * })
103
+ * // Returns 48 (3 * 4 * 4)
104
+ *
105
+ * if (count > 100) {
106
+ * console.log('Too many combinations, use cartesianSample instead')
107
+ * }
108
+ * ```
109
+ */
110
+ export declare function cartesianCount<T extends CartesianParams>(params: T): number;
111
+ /**
112
+ * Generate cartesian product with labels for each dimension
113
+ *
114
+ * Returns combinations with additional metadata about which dimension each value came from.
115
+ *
116
+ * @example
117
+ * ```ts
118
+ * import { cartesianWithLabels } from 'ai-experiments'
119
+ *
120
+ * const labeled = cartesianWithLabels({
121
+ * model: ['sonnet', 'opus'],
122
+ * temperature: [0.3, 0.7],
123
+ * })
124
+ * // [
125
+ * // { values: { model: 'sonnet', temperature: 0.3 }, labels: { model: 0, temperature: 0 } },
126
+ * // { values: { model: 'sonnet', temperature: 0.7 }, labels: { model: 0, temperature: 1 } },
127
+ * // { values: { model: 'opus', temperature: 0.3 }, labels: { model: 1, temperature: 0 } },
128
+ * // { values: { model: 'opus', temperature: 0.7 }, labels: { model: 1, temperature: 1 } },
129
+ * // ]
130
+ * ```
131
+ */
132
+ export declare function cartesianWithLabels<T extends CartesianParams>(params: T): Array<{
133
+ values: {
134
+ [K in keyof T]: T[K][number];
135
+ };
136
+ labels: {
137
+ [K in keyof T]: number;
138
+ };
139
+ }>;
140
+ //# sourceMappingURL=cartesian.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cartesian.d.ts","sourceRoot":"","sources":["../src/cartesian.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAElE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,SAAS,CAAC,CAAC,SAAS,eAAe,EAAE,MAAM,EAAE,CAAC,GAAG,eAAe,CAAC,CAAC,CAAC,CA0BlF;AA4BD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,eAAe,EACvD,MAAM,EAAE,CAAC,EACT,MAAM,EAAE,CAAC,KAAK,EAAE;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;CAAE,KAAK,OAAO,GAC3D,eAAe,CAAC,CAAC,CAAC,CAGpB;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,eAAe,EACvD,MAAM,EAAE,CAAC,EACT,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE;IACP,sCAAsC;IACtC,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,4DAA4D;IAC5D,MAAM,CAAC,EAAE,OAAO,CAAA;CACZ,GACL,eAAe,CAAC,CAAC,CAAC,CAqBpB;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,eAAe,EAAE,MAAM,EAAE,CAAC,GAAG,MAAM,CAS3E;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,SAAS,eAAe,EAC3D,MAAM,EAAE,CAAC,GACR,KAAK,CAAC;IACP,MAAM,EAAE;SAAG,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;KAAE,CAAA;IACxC,MAAM,EAAE;SAAG,CAAC,IAAI,MAAM,CAAC,GAAG,MAAM;KAAE,CAAA;CACnC,CAAC,CA0BD"}