@fallom/trace 0.2.14 → 0.2.16

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/dist/index.js CHANGED
@@ -590,9 +590,159 @@ async function datasetFromFallom(datasetKey, version, config) {
590
590
  );
591
591
  return items;
592
592
  }
593
+ var EvaluationDataset;
593
594
  var init_helpers = __esm({
594
595
  "src/evals/helpers.ts"() {
595
596
  "use strict";
597
+ EvaluationDataset = class {
598
+ constructor() {
599
+ this._goldens = [];
600
+ this._testCases = [];
601
+ this._datasetKey = null;
602
+ this._datasetName = null;
603
+ this._version = null;
604
+ }
605
+ /** List of golden records (inputs with optional expected outputs). */
606
+ get goldens() {
607
+ return this._goldens;
608
+ }
609
+ /** List of test cases (inputs with actual outputs from your LLM). */
610
+ get testCases() {
611
+ return this._testCases;
612
+ }
613
+ /** The Fallom dataset key if pulled from Fallom. */
614
+ get datasetKey() {
615
+ return this._datasetKey;
616
+ }
617
+ /**
618
+ * Pull a dataset from Fallom.
619
+ *
620
+ * @param alias - The dataset key/alias in Fallom
621
+ * @param version - Specific version to pull (default: latest)
622
+ * @returns Self for chaining
623
+ */
624
+ async pull(alias, version) {
625
+ const { _apiKey: _apiKey2, _baseUrl: _baseUrl2, _initialized: _initialized2 } = await Promise.resolve().then(() => (init_core(), core_exports));
626
+ if (!_initialized2) {
627
+ throw new Error("Fallom evals not initialized. Call evals.init() first.");
628
+ }
629
+ const params = new URLSearchParams({ include_entries: "true" });
630
+ if (version !== void 0) {
631
+ params.set("version", String(version));
632
+ }
633
+ const url = `${_baseUrl2}/api/datasets/${encodeURIComponent(alias)}?${params}`;
634
+ const response = await fetch(url, {
635
+ headers: {
636
+ Authorization: `Bearer ${_apiKey2}`,
637
+ "Content-Type": "application/json"
638
+ }
639
+ });
640
+ if (response.status === 404) {
641
+ throw new Error(`Dataset '${alias}' not found`);
642
+ } else if (response.status === 403) {
643
+ throw new Error(`Access denied to dataset '${alias}'`);
644
+ }
645
+ if (!response.ok) {
646
+ throw new Error(`Failed to fetch dataset: ${response.statusText}`);
647
+ }
648
+ const data = await response.json();
649
+ this._datasetKey = alias;
650
+ this._datasetName = data.dataset?.name || alias;
651
+ this._version = data.version?.version || null;
652
+ this._goldens = [];
653
+ for (const entry of data.entries || []) {
654
+ this._goldens.push({
655
+ input: entry.input || "",
656
+ expectedOutput: entry.output,
657
+ systemMessage: entry.systemMessage,
658
+ metadata: entry.metadata
659
+ });
660
+ }
661
+ console.log(
662
+ `\u2713 Pulled dataset '${this._datasetName}' (version ${this._version}) with ${this._goldens.length} goldens`
663
+ );
664
+ return this;
665
+ }
666
+ /**
667
+ * Add a golden record manually.
668
+ * @param golden - A Golden object
669
+ * @returns Self for chaining
670
+ */
671
+ addGolden(golden) {
672
+ this._goldens.push(golden);
673
+ return this;
674
+ }
675
+ /**
676
+ * Add multiple golden records.
677
+ * @param goldens - Array of Golden objects
678
+ * @returns Self for chaining
679
+ */
680
+ addGoldens(goldens) {
681
+ this._goldens.push(...goldens);
682
+ return this;
683
+ }
684
+ /**
685
+ * Add a test case with actual LLM output.
686
+ * @param testCase - An LLMTestCase object
687
+ * @returns Self for chaining
688
+ */
689
+ addTestCase(testCase) {
690
+ this._testCases.push(testCase);
691
+ return this;
692
+ }
693
+ /**
694
+ * Add multiple test cases.
695
+ * @param testCases - Array of LLMTestCase objects
696
+ * @returns Self for chaining
697
+ */
698
+ addTestCases(testCases) {
699
+ this._testCases.push(...testCases);
700
+ return this;
701
+ }
702
+ /**
703
+ * Automatically generate test cases by running all goldens through your LLM app.
704
+ *
705
+ * @param llmApp - A callable that takes messages and returns response
706
+ * @param options - Configuration options
707
+ * @returns Self for chaining
708
+ */
709
+ async generateTestCases(llmApp, options = {}) {
710
+ const { includeContext = false } = options;
711
+ console.log(`Generating test cases for ${this._goldens.length} goldens...`);
712
+ for (let i = 0; i < this._goldens.length; i++) {
713
+ const golden = this._goldens[i];
714
+ const messages = [];
715
+ if (golden.systemMessage) {
716
+ messages.push({ role: "system", content: golden.systemMessage });
717
+ }
718
+ messages.push({ role: "user", content: golden.input });
719
+ const response = await llmApp(messages);
720
+ const testCase = {
721
+ input: golden.input,
722
+ actualOutput: response.content,
723
+ expectedOutput: golden.expectedOutput,
724
+ systemMessage: golden.systemMessage,
725
+ context: includeContext ? response.context : golden.context,
726
+ metadata: golden.metadata
727
+ };
728
+ this._testCases.push(testCase);
729
+ console.log(
730
+ ` [${i + 1}/${this._goldens.length}] Generated output for: ${golden.input.slice(0, 50)}...`
731
+ );
732
+ }
733
+ console.log(`\u2713 Generated ${this._testCases.length} test cases`);
734
+ return this;
735
+ }
736
+ /** Clear all test cases (useful for re-running with different LLM). */
737
+ clearTestCases() {
738
+ this._testCases = [];
739
+ return this;
740
+ }
741
+ /** Return the number of goldens. */
742
+ get length() {
743
+ return this._goldens.length;
744
+ }
745
+ };
596
746
  }
597
747
  });
598
748
 
@@ -707,9 +857,22 @@ async function evaluate(options) {
707
857
  name,
708
858
  description,
709
859
  verbose = true,
860
+ testCases,
710
861
  _skipUpload = false
711
862
  } = options;
712
- const dataset = await resolveDataset(datasetInput);
863
+ let dataset;
864
+ if (testCases !== void 0 && testCases.length > 0) {
865
+ dataset = testCases.map((tc) => ({
866
+ input: tc.input,
867
+ output: tc.actualOutput,
868
+ systemMessage: tc.systemMessage,
869
+ metadata: tc.metadata
870
+ }));
871
+ } else if (datasetInput !== void 0) {
872
+ dataset = await resolveDataset(datasetInput);
873
+ } else {
874
+ throw new Error("Either 'dataset' or 'testCases' must be provided");
875
+ }
713
876
  for (const m of metrics) {
714
877
  if (typeof m === "string" && !AVAILABLE_METRICS.includes(m)) {
715
878
  throw new Error(
@@ -775,6 +938,9 @@ async function compareModels(options) {
775
938
  description,
776
939
  verbose = true
777
940
  } = options;
941
+ if (!datasetInput) {
942
+ throw new Error("'dataset' is required for compareModels()");
943
+ }
778
944
  const dataset = await resolveDataset(datasetInput);
779
945
  const results = {};
780
946
  if (includeProduction) {
@@ -1035,7 +1201,7 @@ var import_exporter_trace_otlp_http = require("@opentelemetry/exporter-trace-otl
1035
1201
  // node_modules/@opentelemetry/resources/build/esm/Resource.js
1036
1202
  var import_api = require("@opentelemetry/api");
1037
1203
 
1038
- // node_modules/@opentelemetry/resources/node_modules/@opentelemetry/semantic-conventions/build/esm/resource/SemanticResourceAttributes.js
1204
+ // node_modules/@opentelemetry/semantic-conventions/build/esm/resource/SemanticResourceAttributes.js
1039
1205
  var SemanticResourceAttributes = {
1040
1206
  /**
1041
1207
  * Name of the cloud provider.
@@ -2474,10 +2640,9 @@ function createGenerateTextWrapper(aiModule, sessionCtx, debug = false) {
2474
2640
  const result = await aiModule.generateText(wrappedParams);
2475
2641
  const endTime = Date.now();
2476
2642
  if (debug || isDebugMode()) {
2477
- console.log(
2478
- "\n\u{1F50D} [Fallom Debug] generateText raw result:",
2479
- JSON.stringify(result, null, 2)
2480
- );
2643
+ console.log("\n\u{1F50D} [Fallom Debug] generateText result:");
2644
+ console.log(" toolCalls:", result?.toolCalls?.length || 0);
2645
+ console.log(" steps:", result?.steps?.length || 0);
2481
2646
  }
2482
2647
  const modelId = result?.response?.modelId || params?.model?.modelId || String(params?.model || "unknown");
2483
2648
  const attributes = {
@@ -2496,15 +2661,15 @@ function createGenerateTextWrapper(aiModule, sessionCtx, debug = false) {
2496
2661
  const mapToolCall = (tc) => ({
2497
2662
  toolCallId: tc?.toolCallId,
2498
2663
  toolName: tc?.toolName,
2499
- args: tc?.args,
2500
- // The actual arguments passed to the tool!
2664
+ args: tc?.args ?? tc?.input,
2665
+ // v4: args, v5: input
2501
2666
  type: tc?.type
2502
2667
  });
2503
2668
  const mapToolResult = (tr) => ({
2504
2669
  toolCallId: tr?.toolCallId,
2505
2670
  toolName: tr?.toolName,
2506
- result: tr?.result,
2507
- // The actual result from the tool!
2671
+ result: tr?.result ?? tr?.output,
2672
+ // v4: result, v5: output
2508
2673
  type: tr?.type
2509
2674
  });
2510
2675
  attributes["fallom.raw.response"] = JSON.stringify({
@@ -2817,15 +2982,15 @@ function createStreamTextWrapper(aiModule, sessionCtx, debug = false) {
2817
2982
  const mapToolCall = (tc) => ({
2818
2983
  toolCallId: tc?.toolCallId,
2819
2984
  toolName: tc?.toolName,
2820
- args: tc?.args,
2821
- // The actual arguments passed to the tool!
2985
+ args: tc?.args ?? tc?.input,
2986
+ // v4: args, v5: input
2822
2987
  type: tc?.type
2823
2988
  });
2824
2989
  const mapToolResult = (tr) => ({
2825
2990
  toolCallId: tr?.toolCallId,
2826
2991
  toolName: tr?.toolName,
2827
- result: tr?.result,
2828
- // The actual result from the tool!
2992
+ result: tr?.result ?? tr?.output,
2993
+ // v4: result, v5: output
2829
2994
  type: tr?.type
2830
2995
  });
2831
2996
  attributes["fallom.raw.request"] = JSON.stringify({
@@ -3544,6 +3709,7 @@ var evals_exports = {};
3544
3709
  __export(evals_exports, {
3545
3710
  AVAILABLE_METRICS: () => AVAILABLE_METRICS,
3546
3711
  DEFAULT_JUDGE_MODEL: () => DEFAULT_JUDGE_MODEL,
3712
+ EvaluationDataset: () => EvaluationDataset,
3547
3713
  METRIC_PROMPTS: () => METRIC_PROMPTS,
3548
3714
  compareModels: () => compareModels,
3549
3715
  createCustomModel: () => createCustomModel,
package/dist/index.mjs CHANGED
@@ -5,6 +5,7 @@ import {
5
5
  import {
6
6
  AVAILABLE_METRICS,
7
7
  DEFAULT_JUDGE_MODEL,
8
+ EvaluationDataset,
8
9
  METRIC_PROMPTS,
9
10
  compareModels,
10
11
  createCustomModel,
@@ -18,7 +19,7 @@ import {
18
19
  init as init2,
19
20
  isCustomMetric,
20
21
  uploadResultsPublic
21
- } from "./chunk-2NGJF2JZ.mjs";
22
+ } from "./chunk-3HBKT4HK.mjs";
22
23
  import {
23
24
  __export
24
25
  } from "./chunk-7P6ASYW6.mjs";
@@ -40,7 +41,7 @@ import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
40
41
  // node_modules/@opentelemetry/resources/build/esm/Resource.js
41
42
  import { diag } from "@opentelemetry/api";
42
43
 
43
- // node_modules/@opentelemetry/resources/node_modules/@opentelemetry/semantic-conventions/build/esm/resource/SemanticResourceAttributes.js
44
+ // node_modules/@opentelemetry/semantic-conventions/build/esm/resource/SemanticResourceAttributes.js
44
45
  var SemanticResourceAttributes = {
45
46
  /**
46
47
  * Name of the cloud provider.
@@ -1479,10 +1480,9 @@ function createGenerateTextWrapper(aiModule, sessionCtx, debug = false) {
1479
1480
  const result = await aiModule.generateText(wrappedParams);
1480
1481
  const endTime = Date.now();
1481
1482
  if (debug || isDebugMode()) {
1482
- console.log(
1483
- "\n\u{1F50D} [Fallom Debug] generateText raw result:",
1484
- JSON.stringify(result, null, 2)
1485
- );
1483
+ console.log("\n\u{1F50D} [Fallom Debug] generateText result:");
1484
+ console.log(" toolCalls:", result?.toolCalls?.length || 0);
1485
+ console.log(" steps:", result?.steps?.length || 0);
1486
1486
  }
1487
1487
  const modelId = result?.response?.modelId || params?.model?.modelId || String(params?.model || "unknown");
1488
1488
  const attributes = {
@@ -1501,15 +1501,15 @@ function createGenerateTextWrapper(aiModule, sessionCtx, debug = false) {
1501
1501
  const mapToolCall = (tc) => ({
1502
1502
  toolCallId: tc?.toolCallId,
1503
1503
  toolName: tc?.toolName,
1504
- args: tc?.args,
1505
- // The actual arguments passed to the tool!
1504
+ args: tc?.args ?? tc?.input,
1505
+ // v4: args, v5: input
1506
1506
  type: tc?.type
1507
1507
  });
1508
1508
  const mapToolResult = (tr) => ({
1509
1509
  toolCallId: tr?.toolCallId,
1510
1510
  toolName: tr?.toolName,
1511
- result: tr?.result,
1512
- // The actual result from the tool!
1511
+ result: tr?.result ?? tr?.output,
1512
+ // v4: result, v5: output
1513
1513
  type: tr?.type
1514
1514
  });
1515
1515
  attributes["fallom.raw.response"] = JSON.stringify({
@@ -1822,15 +1822,15 @@ function createStreamTextWrapper(aiModule, sessionCtx, debug = false) {
1822
1822
  const mapToolCall = (tc) => ({
1823
1823
  toolCallId: tc?.toolCallId,
1824
1824
  toolName: tc?.toolName,
1825
- args: tc?.args,
1826
- // The actual arguments passed to the tool!
1825
+ args: tc?.args ?? tc?.input,
1826
+ // v4: args, v5: input
1827
1827
  type: tc?.type
1828
1828
  });
1829
1829
  const mapToolResult = (tr) => ({
1830
1830
  toolCallId: tr?.toolCallId,
1831
1831
  toolName: tr?.toolName,
1832
- result: tr?.result,
1833
- // The actual result from the tool!
1832
+ result: tr?.result ?? tr?.output,
1833
+ // v4: result, v5: output
1834
1834
  type: tr?.type
1835
1835
  });
1836
1836
  attributes["fallom.raw.request"] = JSON.stringify({
@@ -2546,6 +2546,7 @@ var evals_exports = {};
2546
2546
  __export(evals_exports, {
2547
2547
  AVAILABLE_METRICS: () => AVAILABLE_METRICS,
2548
2548
  DEFAULT_JUDGE_MODEL: () => DEFAULT_JUDGE_MODEL,
2549
+ EvaluationDataset: () => EvaluationDataset,
2549
2550
  METRIC_PROMPTS: () => METRIC_PROMPTS,
2550
2551
  compareModels: () => compareModels,
2551
2552
  createCustomModel: () => createCustomModel,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fallom/trace",
3
- "version": "0.2.14",
3
+ "version": "0.2.16",
4
4
  "description": "Model A/B testing and tracing for LLM applications. Zero latency, production-ready.",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -1,308 +0,0 @@
1
- var __defProp = Object.defineProperty;
2
- var __export = (target, all) => {
3
- for (var name in all)
4
- __defProp(target, name, { get: all[name], enumerable: true });
5
- };
6
-
7
- // src/models.ts
8
- var models_exports = {};
9
- __export(models_exports, {
10
- get: () => get,
11
- init: () => init
12
- });
13
- import { createHash } from "crypto";
14
- var apiKey = null;
15
- var baseUrl = "https://configs.fallom.com";
16
- var initialized = false;
17
- var syncInterval = null;
18
- var debugMode = false;
19
- var configCache = /* @__PURE__ */ new Map();
20
- var SYNC_TIMEOUT = 2e3;
21
- var RECORD_TIMEOUT = 1e3;
22
- function log(msg) {
23
- if (debugMode) {
24
- console.log(`[Fallom] ${msg}`);
25
- }
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
- }
78
- function init(options = {}) {
79
- apiKey = options.apiKey || process.env.FALLOM_API_KEY || null;
80
- baseUrl = options.baseUrl || process.env.FALLOM_CONFIGS_URL || process.env.FALLOM_BASE_URL || "https://configs.fallom.com";
81
- initialized = true;
82
- if (!apiKey) {
83
- return;
84
- }
85
- fetchConfigs().catch(() => {
86
- });
87
- if (!syncInterval) {
88
- syncInterval = setInterval(() => {
89
- fetchConfigs().catch(() => {
90
- });
91
- }, 3e4);
92
- syncInterval.unref();
93
- }
94
- }
95
- function ensureInit() {
96
- if (!initialized) {
97
- try {
98
- init();
99
- } catch {
100
- }
101
- }
102
- }
103
- async function fetchConfigs(timeout = SYNC_TIMEOUT) {
104
- if (!apiKey) {
105
- log("_fetchConfigs: No API key, skipping");
106
- return;
107
- }
108
- try {
109
- log(`Fetching configs from ${baseUrl}/configs`);
110
- const controller = new AbortController();
111
- const timeoutId = setTimeout(() => controller.abort(), timeout);
112
- const resp = await fetch(`${baseUrl}/configs`, {
113
- headers: { Authorization: `Bearer ${apiKey}` },
114
- signal: controller.signal
115
- });
116
- clearTimeout(timeoutId);
117
- log(`Response status: ${resp.status}`);
118
- if (resp.ok) {
119
- const data = await resp.json();
120
- const configs = data.configs || [];
121
- log(`Got ${configs.length} configs: ${configs.map((c) => c.key)}`);
122
- for (const c of configs) {
123
- const key = c.key;
124
- const version = c.version || 1;
125
- log(`Config '${key}' v${version}: ${JSON.stringify(c.variants)}`);
126
- if (!configCache.has(key)) {
127
- configCache.set(key, { versions: /* @__PURE__ */ new Map(), latest: null });
128
- }
129
- const cached = configCache.get(key);
130
- cached.versions.set(version, c);
131
- cached.latest = version;
132
- }
133
- } else {
134
- log(`Fetch failed: ${resp.statusText}`);
135
- }
136
- } catch (e) {
137
- log(`Fetch exception: ${e}`);
138
- }
139
- }
140
- async function fetchSpecificVersion(configKey, version, timeout = SYNC_TIMEOUT) {
141
- if (!apiKey) return null;
142
- try {
143
- const controller = new AbortController();
144
- const timeoutId = setTimeout(() => controller.abort(), timeout);
145
- const resp = await fetch(
146
- `${baseUrl}/configs/${configKey}/version/${version}`,
147
- {
148
- headers: { Authorization: `Bearer ${apiKey}` },
149
- signal: controller.signal
150
- }
151
- );
152
- clearTimeout(timeoutId);
153
- if (resp.ok) {
154
- const config = await resp.json();
155
- if (!configCache.has(configKey)) {
156
- configCache.set(configKey, { versions: /* @__PURE__ */ new Map(), latest: null });
157
- }
158
- configCache.get(configKey).versions.set(version, config);
159
- return config;
160
- }
161
- } catch {
162
- }
163
- return null;
164
- }
165
- async function get(configKey, sessionId, options = {}) {
166
- const { version, fallback, customerId, context, debug = false } = options;
167
- debugMode = debug;
168
- ensureInit();
169
- log(
170
- `get() called: configKey=${configKey}, sessionId=${sessionId}, fallback=${fallback}`
171
- );
172
- try {
173
- let configData = configCache.get(configKey);
174
- log(
175
- `Cache lookup for '${configKey}': ${configData ? "found" : "not found"}`
176
- );
177
- if (!configData) {
178
- log("Not in cache, fetching...");
179
- await fetchConfigs(SYNC_TIMEOUT);
180
- configData = configCache.get(configKey);
181
- log(
182
- `After fetch, cache lookup: ${configData ? "found" : "still not found"}`
183
- );
184
- }
185
- if (!configData) {
186
- log(`Config not found, using fallback: ${fallback}`);
187
- if (fallback) {
188
- console.warn(
189
- `[Fallom WARNING] Config '${configKey}' not found, using fallback model: ${fallback}`
190
- );
191
- return returnModel(configKey, sessionId, fallback, 0);
192
- }
193
- throw new Error(
194
- `Config '${configKey}' not found. Check that it exists in your Fallom dashboard.`
195
- );
196
- }
197
- let config;
198
- let targetVersion;
199
- if (version !== void 0) {
200
- config = configData.versions.get(version);
201
- if (!config) {
202
- config = await fetchSpecificVersion(configKey, version, SYNC_TIMEOUT) || void 0;
203
- }
204
- if (!config) {
205
- if (fallback) {
206
- console.warn(
207
- `[Fallom WARNING] Config '${configKey}' version ${version} not found, using fallback: ${fallback}`
208
- );
209
- return returnModel(configKey, sessionId, fallback, 0);
210
- }
211
- throw new Error(`Config '${configKey}' version ${version} not found.`);
212
- }
213
- targetVersion = version;
214
- } else {
215
- targetVersion = configData.latest;
216
- config = configData.versions.get(targetVersion);
217
- if (!config) {
218
- if (fallback) {
219
- console.warn(
220
- `[Fallom WARNING] Config '${configKey}' has no cached version, using fallback: ${fallback}`
221
- );
222
- return returnModel(configKey, sessionId, fallback, 0);
223
- }
224
- throw new Error(`Config '${configKey}' has no cached version.`);
225
- }
226
- }
227
- const variantsRaw = config.variants;
228
- const configVersion = config.version || targetVersion;
229
- const variants = Array.isArray(variantsRaw) ? variantsRaw : Object.values(variantsRaw);
230
- log(
231
- `Config found! Version: ${configVersion}, Variants: ${JSON.stringify(
232
- variants
233
- )}`
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
- }
241
- const hashBytes = createHash("md5").update(sessionId).digest();
242
- const hashVal = hashBytes.readUInt32BE(0) % 1e6;
243
- log(`Session hash: ${hashVal} (out of 1,000,000)`);
244
- let cumulative = 0;
245
- let assignedModel = variants[variants.length - 1].model;
246
- for (const v of variants) {
247
- const oldCumulative = cumulative;
248
- cumulative += v.weight * 1e4;
249
- log(
250
- `Variant ${v.model}: weight=${v.weight}%, range=${oldCumulative}-${cumulative}, hash=${hashVal}, match=${hashVal < cumulative}`
251
- );
252
- if (hashVal < cumulative) {
253
- assignedModel = v.model;
254
- break;
255
- }
256
- }
257
- log(`\u2705 Assigned model via weighted random: ${assignedModel}`);
258
- return returnModel(configKey, sessionId, assignedModel, configVersion);
259
- } catch (e) {
260
- if (e instanceof Error && e.message.includes("not found")) {
261
- throw e;
262
- }
263
- if (fallback) {
264
- console.warn(
265
- `[Fallom WARNING] Error getting model for '${configKey}': ${e}. Using fallback: ${fallback}`
266
- );
267
- return returnModel(configKey, sessionId, fallback, 0);
268
- }
269
- throw e;
270
- }
271
- }
272
- function returnModel(configKey, sessionId, model, version) {
273
- if (version > 0) {
274
- recordSession(configKey, version, sessionId, model).catch(() => {
275
- });
276
- }
277
- return model;
278
- }
279
- async function recordSession(configKey, version, sessionId, model) {
280
- if (!apiKey) return;
281
- try {
282
- const controller = new AbortController();
283
- const timeoutId = setTimeout(() => controller.abort(), RECORD_TIMEOUT);
284
- await fetch(`${baseUrl}/sessions`, {
285
- method: "POST",
286
- headers: {
287
- Authorization: `Bearer ${apiKey}`,
288
- "Content-Type": "application/json"
289
- },
290
- body: JSON.stringify({
291
- config_key: configKey,
292
- config_version: version,
293
- session_id: sessionId,
294
- assigned_model: model
295
- }),
296
- signal: controller.signal
297
- });
298
- clearTimeout(timeoutId);
299
- } catch {
300
- }
301
- }
302
-
303
- export {
304
- __export,
305
- init,
306
- get,
307
- models_exports
308
- };
@@ -1,8 +0,0 @@
1
- import {
2
- get,
3
- init
4
- } from "./chunk-KFD5AQ7V.mjs";
5
- export {
6
- get,
7
- init
8
- };