@fallom/trace 0.2.17 → 0.2.21
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/chunk-GZ6TE7G4.mjs +923 -0
- package/dist/chunk-XBZ3ESNV.mjs +824 -0
- package/dist/core-DUG2SP2V.mjs +21 -0
- package/dist/core-JLHYFVYS.mjs +21 -0
- package/dist/index.d.mts +64 -2
- package/dist/index.d.ts +64 -2
- package/dist/index.js +305 -114
- package/dist/index.mjs +137 -34
- package/package.json +1 -1
- package/dist/chunk-KFD5AQ7V.mjs +0 -308
- package/dist/models-SEFDGZU2.mjs +0 -8
package/dist/index.mjs
CHANGED
|
@@ -7,6 +7,8 @@ import {
|
|
|
7
7
|
DEFAULT_JUDGE_MODEL,
|
|
8
8
|
EvaluationDataset,
|
|
9
9
|
METRIC_PROMPTS,
|
|
10
|
+
buildGEvalPrompt,
|
|
11
|
+
calculateAggregateScores,
|
|
10
12
|
compareModels,
|
|
11
13
|
createCustomModel,
|
|
12
14
|
createModelFromCallable,
|
|
@@ -14,12 +16,14 @@ import {
|
|
|
14
16
|
customMetric,
|
|
15
17
|
datasetFromFallom,
|
|
16
18
|
datasetFromTraces,
|
|
19
|
+
detectRegression,
|
|
17
20
|
evaluate,
|
|
18
21
|
getMetricName,
|
|
19
22
|
init as init2,
|
|
20
23
|
isCustomMetric,
|
|
24
|
+
runGEval,
|
|
21
25
|
uploadResultsPublic
|
|
22
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-GZ6TE7G4.mjs";
|
|
23
27
|
import {
|
|
24
28
|
__export
|
|
25
29
|
} from "./chunk-7P6ASYW6.mjs";
|
|
@@ -41,7 +45,7 @@ import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
|
|
|
41
45
|
// node_modules/@opentelemetry/resources/build/esm/Resource.js
|
|
42
46
|
import { diag } from "@opentelemetry/api";
|
|
43
47
|
|
|
44
|
-
// node_modules/@opentelemetry/
|
|
48
|
+
// node_modules/@opentelemetry/semantic-conventions/build/esm/resource/SemanticResourceAttributes.js
|
|
45
49
|
var SemanticResourceAttributes = {
|
|
46
50
|
/**
|
|
47
51
|
* Name of the cloud provider.
|
|
@@ -1567,20 +1571,36 @@ function createGenerateTextWrapper(aiModule, sessionCtx, debug = false) {
|
|
|
1567
1571
|
tools: params?.tools ? Object.keys(params.tools) : void 0,
|
|
1568
1572
|
maxSteps: params?.maxSteps
|
|
1569
1573
|
});
|
|
1570
|
-
const mapToolCall = (tc) =>
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1574
|
+
const mapToolCall = (tc) => {
|
|
1575
|
+
let args2 = tc?.args ?? tc?.input;
|
|
1576
|
+
if (args2 === void 0 && tc) {
|
|
1577
|
+
const { type, toolCallId, toolName, providerExecuted, dynamic, invalid, error, providerMetadata, ...rest } = tc;
|
|
1578
|
+
if (Object.keys(rest).length > 0) {
|
|
1579
|
+
args2 = rest;
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
return {
|
|
1583
|
+
toolCallId: tc?.toolCallId,
|
|
1584
|
+
toolName: tc?.toolName,
|
|
1585
|
+
args: args2,
|
|
1586
|
+
type: tc?.type
|
|
1587
|
+
};
|
|
1588
|
+
};
|
|
1589
|
+
const mapToolResult = (tr) => {
|
|
1590
|
+
let result2 = tr?.result ?? tr?.output;
|
|
1591
|
+
if (result2 === void 0 && tr) {
|
|
1592
|
+
const { type, toolCallId, toolName, ...rest } = tr;
|
|
1593
|
+
if (Object.keys(rest).length > 0) {
|
|
1594
|
+
result2 = rest;
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
return {
|
|
1598
|
+
toolCallId: tr?.toolCallId,
|
|
1599
|
+
toolName: tr?.toolName,
|
|
1600
|
+
result: result2,
|
|
1601
|
+
type: tr?.type
|
|
1602
|
+
};
|
|
1603
|
+
};
|
|
1584
1604
|
attributes["fallom.raw.response"] = JSON.stringify({
|
|
1585
1605
|
text: result?.text,
|
|
1586
1606
|
finishReason: result?.finishReason,
|
|
@@ -1793,7 +1813,9 @@ function createStreamTextWrapper(aiModule, sessionCtx, debug = false) {
|
|
|
1793
1813
|
let wrappedParams = params;
|
|
1794
1814
|
if (params.tools && typeof params.tools === "object") {
|
|
1795
1815
|
const wrappedTools = {};
|
|
1796
|
-
for (const [toolName, tool] of Object.entries(
|
|
1816
|
+
for (const [toolName, tool] of Object.entries(
|
|
1817
|
+
params.tools
|
|
1818
|
+
)) {
|
|
1797
1819
|
if (tool && typeof tool.execute === "function") {
|
|
1798
1820
|
const originalExecute = tool.execute;
|
|
1799
1821
|
wrappedTools[toolName] = {
|
|
@@ -1876,10 +1898,54 @@ function createStreamTextWrapper(aiModule, sessionCtx, debug = false) {
|
|
|
1876
1898
|
"\u{1F50D} [Fallom Debug] streamText toolCalls:",
|
|
1877
1899
|
JSON.stringify(toolCalls, null, 2)
|
|
1878
1900
|
);
|
|
1901
|
+
if (toolCalls?.[0]) {
|
|
1902
|
+
console.log(
|
|
1903
|
+
"\u{1F50D} [Fallom Debug] streamText toolCalls[0] keys:",
|
|
1904
|
+
Object.keys(toolCalls[0])
|
|
1905
|
+
);
|
|
1906
|
+
console.log(
|
|
1907
|
+
"\u{1F50D} [Fallom Debug] streamText toolCalls[0] full:",
|
|
1908
|
+
JSON.stringify(
|
|
1909
|
+
toolCalls[0],
|
|
1910
|
+
Object.getOwnPropertyNames(toolCalls[0]),
|
|
1911
|
+
2
|
|
1912
|
+
)
|
|
1913
|
+
);
|
|
1914
|
+
}
|
|
1879
1915
|
console.log(
|
|
1880
1916
|
"\u{1F50D} [Fallom Debug] streamText steps count:",
|
|
1881
1917
|
steps?.length
|
|
1882
1918
|
);
|
|
1919
|
+
if (steps?.[0]?.toolCalls?.[0]) {
|
|
1920
|
+
const tc = steps[0].toolCalls[0];
|
|
1921
|
+
console.log(
|
|
1922
|
+
"\u{1F50D} [Fallom Debug] steps[0].toolCalls[0] keys:",
|
|
1923
|
+
Object.keys(tc)
|
|
1924
|
+
);
|
|
1925
|
+
console.log(
|
|
1926
|
+
"\u{1F50D} [Fallom Debug] steps[0].toolCalls[0].args (v4):",
|
|
1927
|
+
tc.args
|
|
1928
|
+
);
|
|
1929
|
+
console.log(
|
|
1930
|
+
"\u{1F50D} [Fallom Debug] steps[0].toolCalls[0].input (v5):",
|
|
1931
|
+
tc.input
|
|
1932
|
+
);
|
|
1933
|
+
}
|
|
1934
|
+
if (steps?.[0]?.toolResults?.[0]) {
|
|
1935
|
+
const tr = steps[0].toolResults[0];
|
|
1936
|
+
console.log(
|
|
1937
|
+
"\u{1F50D} [Fallom Debug] steps[0].toolResults[0] keys:",
|
|
1938
|
+
Object.keys(tr)
|
|
1939
|
+
);
|
|
1940
|
+
console.log(
|
|
1941
|
+
"\u{1F50D} [Fallom Debug] steps[0].toolResults[0].result (v4):",
|
|
1942
|
+
typeof tr.result === "string" ? tr.result.slice(0, 200) : tr.result
|
|
1943
|
+
);
|
|
1944
|
+
console.log(
|
|
1945
|
+
"\u{1F50D} [Fallom Debug] steps[0].toolResults[0].output (v5):",
|
|
1946
|
+
typeof tr.output === "string" ? tr.output.slice(0, 200) : tr.output
|
|
1947
|
+
);
|
|
1948
|
+
}
|
|
1883
1949
|
}
|
|
1884
1950
|
let providerMetadata = result?.experimental_providerMetadata;
|
|
1885
1951
|
if (providerMetadata && typeof providerMetadata.then === "function") {
|
|
@@ -1895,20 +1961,46 @@ function createStreamTextWrapper(aiModule, sessionCtx, debug = false) {
|
|
|
1895
1961
|
"fallom.is_streaming": true
|
|
1896
1962
|
};
|
|
1897
1963
|
if (captureContent2) {
|
|
1898
|
-
const mapToolCall = (tc) =>
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1964
|
+
const mapToolCall = (tc) => {
|
|
1965
|
+
let args2 = tc?.args ?? tc?.input;
|
|
1966
|
+
if (args2 === void 0 && tc) {
|
|
1967
|
+
const {
|
|
1968
|
+
type,
|
|
1969
|
+
toolCallId,
|
|
1970
|
+
toolName,
|
|
1971
|
+
providerExecuted,
|
|
1972
|
+
dynamic,
|
|
1973
|
+
invalid,
|
|
1974
|
+
error,
|
|
1975
|
+
providerMetadata: providerMetadata2,
|
|
1976
|
+
...rest
|
|
1977
|
+
} = tc;
|
|
1978
|
+
if (Object.keys(rest).length > 0) {
|
|
1979
|
+
args2 = rest;
|
|
1980
|
+
}
|
|
1981
|
+
}
|
|
1982
|
+
return {
|
|
1983
|
+
toolCallId: tc?.toolCallId,
|
|
1984
|
+
toolName: tc?.toolName,
|
|
1985
|
+
args: args2,
|
|
1986
|
+
type: tc?.type
|
|
1987
|
+
};
|
|
1988
|
+
};
|
|
1989
|
+
const mapToolResult = (tr) => {
|
|
1990
|
+
let result2 = tr?.result ?? tr?.output;
|
|
1991
|
+
if (result2 === void 0 && tr) {
|
|
1992
|
+
const { type, toolCallId, toolName, ...rest } = tr;
|
|
1993
|
+
if (Object.keys(rest).length > 0) {
|
|
1994
|
+
result2 = rest;
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
return {
|
|
1998
|
+
toolCallId: tr?.toolCallId,
|
|
1999
|
+
toolName: tr?.toolName,
|
|
2000
|
+
result: result2,
|
|
2001
|
+
type: tr?.type
|
|
2002
|
+
};
|
|
2003
|
+
};
|
|
1912
2004
|
attributes["fallom.raw.request"] = JSON.stringify({
|
|
1913
2005
|
prompt: params?.prompt,
|
|
1914
2006
|
messages: params?.messages,
|
|
@@ -1950,7 +2042,10 @@ function createStreamTextWrapper(aiModule, sessionCtx, debug = false) {
|
|
|
1950
2042
|
attributes["fallom.time_to_first_token_ms"] = firstTokenTime - startTime;
|
|
1951
2043
|
}
|
|
1952
2044
|
try {
|
|
1953
|
-
attributes["fallom.raw.metadata"] = JSON.stringify(
|
|
2045
|
+
attributes["fallom.raw.metadata"] = JSON.stringify(
|
|
2046
|
+
result,
|
|
2047
|
+
sanitizeMetadataOnly
|
|
2048
|
+
);
|
|
1954
2049
|
} catch {
|
|
1955
2050
|
}
|
|
1956
2051
|
const totalDurationMs = endTime - startTime;
|
|
@@ -1977,8 +2072,12 @@ function createStreamTextWrapper(aiModule, sessionCtx, debug = false) {
|
|
|
1977
2072
|
});
|
|
1978
2073
|
}
|
|
1979
2074
|
if (sortedToolTimings.length > 0) {
|
|
1980
|
-
const firstToolStart = Math.min(
|
|
1981
|
-
|
|
2075
|
+
const firstToolStart = Math.min(
|
|
2076
|
+
...sortedToolTimings.map((t) => t.startTime)
|
|
2077
|
+
);
|
|
2078
|
+
const lastToolEnd = Math.max(
|
|
2079
|
+
...sortedToolTimings.map((t) => t.endTime)
|
|
2080
|
+
);
|
|
1982
2081
|
if (firstToolStart > 10) {
|
|
1983
2082
|
waterfallTimings.phases.push({
|
|
1984
2083
|
type: "llm",
|
|
@@ -2643,6 +2742,8 @@ __export(evals_exports, {
|
|
|
2643
2742
|
DEFAULT_JUDGE_MODEL: () => DEFAULT_JUDGE_MODEL,
|
|
2644
2743
|
EvaluationDataset: () => EvaluationDataset,
|
|
2645
2744
|
METRIC_PROMPTS: () => METRIC_PROMPTS,
|
|
2745
|
+
buildGEvalPrompt: () => buildGEvalPrompt,
|
|
2746
|
+
calculateAggregateScores: () => calculateAggregateScores,
|
|
2646
2747
|
compareModels: () => compareModels,
|
|
2647
2748
|
createCustomModel: () => createCustomModel,
|
|
2648
2749
|
createModelFromCallable: () => createModelFromCallable,
|
|
@@ -2650,10 +2751,12 @@ __export(evals_exports, {
|
|
|
2650
2751
|
customMetric: () => customMetric,
|
|
2651
2752
|
datasetFromFallom: () => datasetFromFallom,
|
|
2652
2753
|
datasetFromTraces: () => datasetFromTraces,
|
|
2754
|
+
detectRegression: () => detectRegression,
|
|
2653
2755
|
evaluate: () => evaluate,
|
|
2654
2756
|
getMetricName: () => getMetricName,
|
|
2655
2757
|
init: () => init2,
|
|
2656
2758
|
isCustomMetric: () => isCustomMetric,
|
|
2759
|
+
runGEval: () => runGEval,
|
|
2657
2760
|
uploadResults: () => uploadResultsPublic
|
|
2658
2761
|
});
|
|
2659
2762
|
|
package/package.json
CHANGED
package/dist/chunk-KFD5AQ7V.mjs
DELETED
|
@@ -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
|
-
};
|