@fallom/trace 0.2.4 → 0.2.6
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/README.md +66 -2
- package/dist/{chunk-W6M2RQ3W.mjs → chunk-KFD5AQ7V.mjs} +59 -2
- package/dist/index.d.mts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +59 -2
- package/dist/index.mjs +2 -2
- package/dist/{models-JKMOBZUO.mjs → models-SEFDGZU2.mjs} +1 -1
- package/package.json +1 -1
- package/dist/chunk-2BP4H4AD.mjs +0 -3012
- package/dist/chunk-6MSTRIK4.mjs +0 -255
- package/dist/chunk-7P6ASYW6.mjs +0 -9
- package/dist/chunk-H2EACSBT.mjs +0 -255
- package/dist/chunk-IGJD7GBO.mjs +0 -248
- package/dist/chunk-K7HYYE4Y.mjs +0 -2930
- package/dist/chunk-KAZ5NEU2.mjs +0 -2237
- package/dist/chunk-KMA4IPED.mjs +0 -252
- package/dist/chunk-VNUUS74T.mjs +0 -242
- package/dist/models-2Y6DRQPS.mjs +0 -9
- package/dist/models-BUHMMTWK.mjs +0 -9
- package/dist/models-JIO5LVMB.mjs +0 -8
- package/dist/prompts-67DJ33I4.mjs +0 -14
- package/dist/prompts-ODF4KO2E.mjs +0 -14
- package/dist/prompts-VAN5E3L4.mjs +0 -14
- package/dist/prompts-XSZHTCX7.mjs +0 -15
- package/dist/prompts-ZSLS4DHO.mjs +0 -14
package/README.md
CHANGED
|
@@ -187,6 +187,59 @@ const model = await models.get("summarizer-config", sessionId, {
|
|
|
187
187
|
});
|
|
188
188
|
```
|
|
189
189
|
|
|
190
|
+
### User Targeting (LaunchDarkly-style)
|
|
191
|
+
|
|
192
|
+
Override weighted distribution for specific users or segments. Targeting rules are evaluated client-side for zero latency.
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
import { models } from "@fallom/trace";
|
|
196
|
+
|
|
197
|
+
// Target specific users to specific variants
|
|
198
|
+
const model = await models.get("my-config", sessionId, {
|
|
199
|
+
fallback: "gpt-4o-mini",
|
|
200
|
+
customerId: "user-123", // For individual targeting
|
|
201
|
+
context: { // For rule-based targeting
|
|
202
|
+
plan: "enterprise",
|
|
203
|
+
region: "us-west",
|
|
204
|
+
},
|
|
205
|
+
});
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
**Evaluation order:**
|
|
209
|
+
1. **Individual Targets** - Exact match on `customerId` or any field
|
|
210
|
+
2. **Rules** - Condition-based targeting (all conditions must match)
|
|
211
|
+
3. **Fallback** - Weighted random distribution
|
|
212
|
+
|
|
213
|
+
**Configure targeting in the dashboard:**
|
|
214
|
+
|
|
215
|
+
```json
|
|
216
|
+
{
|
|
217
|
+
"enabled": true,
|
|
218
|
+
"individualTargets": [
|
|
219
|
+
{ "field": "customerId", "value": "vip-user-123", "variantIndex": 1 }
|
|
220
|
+
],
|
|
221
|
+
"rules": [
|
|
222
|
+
{
|
|
223
|
+
"conditions": [
|
|
224
|
+
{ "field": "plan", "operator": "eq", "value": "enterprise" }
|
|
225
|
+
],
|
|
226
|
+
"variantIndex": 1
|
|
227
|
+
}
|
|
228
|
+
]
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
**Supported operators:**
|
|
233
|
+
| Operator | Description | Example |
|
|
234
|
+
|----------|-------------|---------|
|
|
235
|
+
| `eq` | Equals | `plan = "enterprise"` |
|
|
236
|
+
| `neq` | Not equals | `plan ≠ "free"` |
|
|
237
|
+
| `in` | In list | `plan in ["enterprise", "business"]` |
|
|
238
|
+
| `nin` | Not in list | `region not in ["cn", "ru"]` |
|
|
239
|
+
| `contains` | Contains substring | `email contains "@acme.com"` |
|
|
240
|
+
| `startsWith` | Starts with | `region starts with "eu-"` |
|
|
241
|
+
| `endsWith` | Ends with | `email ends with ".gov"` |
|
|
242
|
+
|
|
190
243
|
## Prompt Management
|
|
191
244
|
|
|
192
245
|
Manage prompts centrally and A/B test them.
|
|
@@ -295,11 +348,22 @@ Get model assignment for A/B testing.
|
|
|
295
348
|
|
|
296
349
|
```typescript
|
|
297
350
|
const model = await models.get("my-config", sessionId, {
|
|
298
|
-
fallback: "gpt-4o-mini",
|
|
299
|
-
version: 2,
|
|
351
|
+
fallback: "gpt-4o-mini", // used if config not found
|
|
352
|
+
version: 2, // pin to specific config version
|
|
353
|
+
customerId: "user-123", // for individual targeting
|
|
354
|
+
context: { plan: "enterprise" }, // for rule-based targeting
|
|
355
|
+
debug: false, // enable debug logging
|
|
300
356
|
});
|
|
301
357
|
```
|
|
302
358
|
|
|
359
|
+
| Option | Type | Description |
|
|
360
|
+
|--------|------|-------------|
|
|
361
|
+
| `fallback` | `string` | Model to return if config not found |
|
|
362
|
+
| `version` | `number` | Pin to specific config version |
|
|
363
|
+
| `customerId` | `string` | User ID for individual targeting |
|
|
364
|
+
| `context` | `Record<string, string>` | Context for rule-based targeting |
|
|
365
|
+
| `debug` | `boolean` | Enable debug logging |
|
|
366
|
+
|
|
303
367
|
### `fallom.prompts.get(promptKey, options?)`
|
|
304
368
|
|
|
305
369
|
Get a managed prompt.
|
|
@@ -24,6 +24,57 @@ function log(msg) {
|
|
|
24
24
|
console.log(`[Fallom] ${msg}`);
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
|
+
function evaluateTargeting(targeting, customerId, context) {
|
|
28
|
+
if (!targeting || targeting.enabled === false) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
const evalContext = {
|
|
32
|
+
...context || {},
|
|
33
|
+
...customerId ? { customerId } : {}
|
|
34
|
+
};
|
|
35
|
+
log(`Evaluating targeting with context: ${JSON.stringify(evalContext)}`);
|
|
36
|
+
if (targeting.individualTargets) {
|
|
37
|
+
for (const target of targeting.individualTargets) {
|
|
38
|
+
const fieldValue = evalContext[target.field];
|
|
39
|
+
if (fieldValue === target.value) {
|
|
40
|
+
log(`Individual target matched: ${target.field}=${target.value} -> variant ${target.variantIndex}`);
|
|
41
|
+
return target.variantIndex;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (targeting.rules) {
|
|
46
|
+
for (const rule of targeting.rules) {
|
|
47
|
+
const allConditionsMatch = rule.conditions.every((condition) => {
|
|
48
|
+
const fieldValue = evalContext[condition.field];
|
|
49
|
+
if (fieldValue === void 0) return false;
|
|
50
|
+
switch (condition.operator) {
|
|
51
|
+
case "eq":
|
|
52
|
+
return fieldValue === condition.value;
|
|
53
|
+
case "neq":
|
|
54
|
+
return fieldValue !== condition.value;
|
|
55
|
+
case "in":
|
|
56
|
+
return Array.isArray(condition.value) && condition.value.includes(fieldValue);
|
|
57
|
+
case "nin":
|
|
58
|
+
return Array.isArray(condition.value) && !condition.value.includes(fieldValue);
|
|
59
|
+
case "contains":
|
|
60
|
+
return typeof condition.value === "string" && fieldValue.includes(condition.value);
|
|
61
|
+
case "startsWith":
|
|
62
|
+
return typeof condition.value === "string" && fieldValue.startsWith(condition.value);
|
|
63
|
+
case "endsWith":
|
|
64
|
+
return typeof condition.value === "string" && fieldValue.endsWith(condition.value);
|
|
65
|
+
default:
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
if (allConditionsMatch) {
|
|
70
|
+
log(`Rule matched: ${JSON.stringify(rule.conditions)} -> variant ${rule.variantIndex}`);
|
|
71
|
+
return rule.variantIndex;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
log("No targeting rules matched, falling back to weighted random");
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
27
78
|
function init(options = {}) {
|
|
28
79
|
apiKey = options.apiKey || process.env.FALLOM_API_KEY || null;
|
|
29
80
|
baseUrl = options.baseUrl || process.env.FALLOM_CONFIGS_URL || process.env.FALLOM_BASE_URL || "https://configs.fallom.com";
|
|
@@ -112,7 +163,7 @@ async function fetchSpecificVersion(configKey, version, timeout = SYNC_TIMEOUT)
|
|
|
112
163
|
return null;
|
|
113
164
|
}
|
|
114
165
|
async function get(configKey, sessionId, options = {}) {
|
|
115
|
-
const { version, fallback, debug = false } = options;
|
|
166
|
+
const { version, fallback, customerId, context, debug = false } = options;
|
|
116
167
|
debugMode = debug;
|
|
117
168
|
ensureInit();
|
|
118
169
|
log(
|
|
@@ -181,6 +232,12 @@ async function get(configKey, sessionId, options = {}) {
|
|
|
181
232
|
variants
|
|
182
233
|
)}`
|
|
183
234
|
);
|
|
235
|
+
const targetedVariantIndex = evaluateTargeting(config.targeting, customerId, context);
|
|
236
|
+
if (targetedVariantIndex !== null && variants[targetedVariantIndex]) {
|
|
237
|
+
const assignedModel2 = variants[targetedVariantIndex].model;
|
|
238
|
+
log(`\u2705 Assigned model via targeting: ${assignedModel2}`);
|
|
239
|
+
return returnModel(configKey, sessionId, assignedModel2, configVersion);
|
|
240
|
+
}
|
|
184
241
|
const hashBytes = createHash("md5").update(sessionId).digest();
|
|
185
242
|
const hashVal = hashBytes.readUInt32BE(0) % 1e6;
|
|
186
243
|
log(`Session hash: ${hashVal} (out of 1,000,000)`);
|
|
@@ -197,7 +254,7 @@ async function get(configKey, sessionId, options = {}) {
|
|
|
197
254
|
break;
|
|
198
255
|
}
|
|
199
256
|
}
|
|
200
|
-
log(`\u2705 Assigned model: ${assignedModel}`);
|
|
257
|
+
log(`\u2705 Assigned model via weighted random: ${assignedModel}`);
|
|
201
258
|
return returnModel(configKey, sessionId, assignedModel, configVersion);
|
|
202
259
|
} catch (e) {
|
|
203
260
|
if (e instanceof Error && e.message.includes("not found")) {
|
package/dist/index.d.mts
CHANGED
|
@@ -261,6 +261,8 @@ declare function init$2(options?: {
|
|
|
261
261
|
* @param options - Optional settings
|
|
262
262
|
* @param options.version - Pin to specific version (1, 2, etc). undefined = latest
|
|
263
263
|
* @param options.fallback - Model to return if config not found or Fallom is down
|
|
264
|
+
* @param options.customerId - User ID for individual targeting (e.g., "user-123")
|
|
265
|
+
* @param options.context - Additional context for rule-based targeting (e.g., { plan: "enterprise" })
|
|
264
266
|
* @param options.debug - Enable debug logging
|
|
265
267
|
* @returns Model string (e.g., "claude-opus", "gpt-4o")
|
|
266
268
|
* @throws Error if config not found AND no fallback provided
|
|
@@ -268,6 +270,8 @@ declare function init$2(options?: {
|
|
|
268
270
|
declare function get$1(configKey: string, sessionId: string, options?: {
|
|
269
271
|
version?: number;
|
|
270
272
|
fallback?: string;
|
|
273
|
+
customerId?: string;
|
|
274
|
+
context?: Record<string, string>;
|
|
271
275
|
debug?: boolean;
|
|
272
276
|
}): Promise<string>;
|
|
273
277
|
|
package/dist/index.d.ts
CHANGED
|
@@ -261,6 +261,8 @@ declare function init$2(options?: {
|
|
|
261
261
|
* @param options - Optional settings
|
|
262
262
|
* @param options.version - Pin to specific version (1, 2, etc). undefined = latest
|
|
263
263
|
* @param options.fallback - Model to return if config not found or Fallom is down
|
|
264
|
+
* @param options.customerId - User ID for individual targeting (e.g., "user-123")
|
|
265
|
+
* @param options.context - Additional context for rule-based targeting (e.g., { plan: "enterprise" })
|
|
264
266
|
* @param options.debug - Enable debug logging
|
|
265
267
|
* @returns Model string (e.g., "claude-opus", "gpt-4o")
|
|
266
268
|
* @throws Error if config not found AND no fallback provided
|
|
@@ -268,6 +270,8 @@ declare function init$2(options?: {
|
|
|
268
270
|
declare function get$1(configKey: string, sessionId: string, options?: {
|
|
269
271
|
version?: number;
|
|
270
272
|
fallback?: string;
|
|
273
|
+
customerId?: string;
|
|
274
|
+
context?: Record<string, string>;
|
|
271
275
|
debug?: boolean;
|
|
272
276
|
}): Promise<string>;
|
|
273
277
|
|
package/dist/index.js
CHANGED
|
@@ -31,6 +31,57 @@ function log3(msg) {
|
|
|
31
31
|
console.log(`[Fallom] ${msg}`);
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
|
+
function evaluateTargeting(targeting, customerId, context) {
|
|
35
|
+
if (!targeting || targeting.enabled === false) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
const evalContext = {
|
|
39
|
+
...context || {},
|
|
40
|
+
...customerId ? { customerId } : {}
|
|
41
|
+
};
|
|
42
|
+
log3(`Evaluating targeting with context: ${JSON.stringify(evalContext)}`);
|
|
43
|
+
if (targeting.individualTargets) {
|
|
44
|
+
for (const target of targeting.individualTargets) {
|
|
45
|
+
const fieldValue = evalContext[target.field];
|
|
46
|
+
if (fieldValue === target.value) {
|
|
47
|
+
log3(`Individual target matched: ${target.field}=${target.value} -> variant ${target.variantIndex}`);
|
|
48
|
+
return target.variantIndex;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (targeting.rules) {
|
|
53
|
+
for (const rule of targeting.rules) {
|
|
54
|
+
const allConditionsMatch = rule.conditions.every((condition) => {
|
|
55
|
+
const fieldValue = evalContext[condition.field];
|
|
56
|
+
if (fieldValue === void 0) return false;
|
|
57
|
+
switch (condition.operator) {
|
|
58
|
+
case "eq":
|
|
59
|
+
return fieldValue === condition.value;
|
|
60
|
+
case "neq":
|
|
61
|
+
return fieldValue !== condition.value;
|
|
62
|
+
case "in":
|
|
63
|
+
return Array.isArray(condition.value) && condition.value.includes(fieldValue);
|
|
64
|
+
case "nin":
|
|
65
|
+
return Array.isArray(condition.value) && !condition.value.includes(fieldValue);
|
|
66
|
+
case "contains":
|
|
67
|
+
return typeof condition.value === "string" && fieldValue.includes(condition.value);
|
|
68
|
+
case "startsWith":
|
|
69
|
+
return typeof condition.value === "string" && fieldValue.startsWith(condition.value);
|
|
70
|
+
case "endsWith":
|
|
71
|
+
return typeof condition.value === "string" && fieldValue.endsWith(condition.value);
|
|
72
|
+
default:
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
if (allConditionsMatch) {
|
|
77
|
+
log3(`Rule matched: ${JSON.stringify(rule.conditions)} -> variant ${rule.variantIndex}`);
|
|
78
|
+
return rule.variantIndex;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
log3("No targeting rules matched, falling back to weighted random");
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
34
85
|
function init2(options = {}) {
|
|
35
86
|
apiKey2 = options.apiKey || process.env.FALLOM_API_KEY || null;
|
|
36
87
|
baseUrl2 = options.baseUrl || process.env.FALLOM_CONFIGS_URL || process.env.FALLOM_BASE_URL || "https://configs.fallom.com";
|
|
@@ -119,7 +170,7 @@ async function fetchSpecificVersion(configKey, version, timeout = SYNC_TIMEOUT)
|
|
|
119
170
|
return null;
|
|
120
171
|
}
|
|
121
172
|
async function get(configKey, sessionId, options = {}) {
|
|
122
|
-
const { version, fallback, debug = false } = options;
|
|
173
|
+
const { version, fallback, customerId, context, debug = false } = options;
|
|
123
174
|
debugMode2 = debug;
|
|
124
175
|
ensureInit();
|
|
125
176
|
log3(
|
|
@@ -188,6 +239,12 @@ async function get(configKey, sessionId, options = {}) {
|
|
|
188
239
|
variants
|
|
189
240
|
)}`
|
|
190
241
|
);
|
|
242
|
+
const targetedVariantIndex = evaluateTargeting(config.targeting, customerId, context);
|
|
243
|
+
if (targetedVariantIndex !== null && variants[targetedVariantIndex]) {
|
|
244
|
+
const assignedModel2 = variants[targetedVariantIndex].model;
|
|
245
|
+
log3(`\u2705 Assigned model via targeting: ${assignedModel2}`);
|
|
246
|
+
return returnModel(configKey, sessionId, assignedModel2, configVersion);
|
|
247
|
+
}
|
|
191
248
|
const hashBytes = (0, import_crypto.createHash)("md5").update(sessionId).digest();
|
|
192
249
|
const hashVal = hashBytes.readUInt32BE(0) % 1e6;
|
|
193
250
|
log3(`Session hash: ${hashVal} (out of 1,000,000)`);
|
|
@@ -204,7 +261,7 @@ async function get(configKey, sessionId, options = {}) {
|
|
|
204
261
|
break;
|
|
205
262
|
}
|
|
206
263
|
}
|
|
207
|
-
log3(`\u2705 Assigned model: ${assignedModel}`);
|
|
264
|
+
log3(`\u2705 Assigned model via weighted random: ${assignedModel}`);
|
|
208
265
|
return returnModel(configKey, sessionId, assignedModel, configVersion);
|
|
209
266
|
} catch (e) {
|
|
210
267
|
if (e instanceof Error && e.message.includes("not found")) {
|
package/dist/index.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
__export,
|
|
3
3
|
init,
|
|
4
4
|
models_exports
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-KFD5AQ7V.mjs";
|
|
6
6
|
|
|
7
7
|
// src/trace.ts
|
|
8
8
|
var trace_exports = {};
|
|
@@ -1602,7 +1602,7 @@ var FallomSession = class {
|
|
|
1602
1602
|
configKey = this.ctx.configKey;
|
|
1603
1603
|
opts = configKeyOrOptions || {};
|
|
1604
1604
|
}
|
|
1605
|
-
const { get: get2 } = await import("./models-
|
|
1605
|
+
const { get: get2 } = await import("./models-SEFDGZU2.mjs");
|
|
1606
1606
|
return get2(configKey, this.ctx.sessionId, opts);
|
|
1607
1607
|
}
|
|
1608
1608
|
/**
|