@fallom/trace 0.1.0 → 0.1.3
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 +95 -168
- package/dist/index.d.mts +76 -3
- package/dist/index.d.ts +76 -3
- package/dist/index.js +232 -38
- package/dist/index.mjs +232 -44
- package/package.json +7 -4
package/dist/index.js
CHANGED
|
@@ -36,7 +36,10 @@ __export(trace_exports, {
|
|
|
36
36
|
runWithSession: () => runWithSession,
|
|
37
37
|
setSession: () => setSession,
|
|
38
38
|
shutdown: () => shutdown,
|
|
39
|
-
span: () => span
|
|
39
|
+
span: () => span,
|
|
40
|
+
wrapAnthropic: () => wrapAnthropic,
|
|
41
|
+
wrapGoogleAI: () => wrapGoogleAI,
|
|
42
|
+
wrapOpenAI: () => wrapOpenAI
|
|
40
43
|
});
|
|
41
44
|
var import_async_hooks = require("async_hooks");
|
|
42
45
|
var import_sdk_node = require("@opentelemetry/sdk-node");
|
|
@@ -672,16 +675,25 @@ var apiKey = null;
|
|
|
672
675
|
var baseUrl = "https://spans.fallom.com";
|
|
673
676
|
var initialized = false;
|
|
674
677
|
var captureContent = true;
|
|
678
|
+
var debugMode = false;
|
|
675
679
|
var sdk = null;
|
|
680
|
+
function log(...args) {
|
|
681
|
+
if (debugMode) console.log("[Fallom]", ...args);
|
|
682
|
+
}
|
|
676
683
|
var fallomSpanProcessor = {
|
|
677
684
|
onStart(span2, _parentContext) {
|
|
685
|
+
log("\u{1F4CD} Span started:", span2.name || "unknown");
|
|
678
686
|
const ctx = sessionStorage.getStore() || fallbackSession;
|
|
679
687
|
if (ctx) {
|
|
680
688
|
span2.setAttribute("fallom.config_key", ctx.configKey);
|
|
681
689
|
span2.setAttribute("fallom.session_id", ctx.sessionId);
|
|
690
|
+
log(" Added session context:", ctx.configKey, ctx.sessionId);
|
|
691
|
+
} else {
|
|
692
|
+
log(" No session context available");
|
|
682
693
|
}
|
|
683
694
|
},
|
|
684
|
-
onEnd(
|
|
695
|
+
onEnd(span2) {
|
|
696
|
+
log("\u2705 Span ended:", span2.name, "duration:", span2.duration);
|
|
685
697
|
},
|
|
686
698
|
shutdown() {
|
|
687
699
|
return Promise.resolve();
|
|
@@ -690,8 +702,10 @@ var fallomSpanProcessor = {
|
|
|
690
702
|
return Promise.resolve();
|
|
691
703
|
}
|
|
692
704
|
};
|
|
693
|
-
function init(options = {}) {
|
|
705
|
+
async function init(options = {}) {
|
|
694
706
|
if (initialized) return;
|
|
707
|
+
debugMode = options.debug ?? false;
|
|
708
|
+
log("\u{1F680} Initializing Fallom tracing...");
|
|
695
709
|
apiKey = options.apiKey || process.env.FALLOM_API_KEY || null;
|
|
696
710
|
baseUrl = options.baseUrl || process.env.FALLOM_BASE_URL || "https://spans.fallom.com";
|
|
697
711
|
const envCapture = process.env.FALLOM_CAPTURE_CONTENT?.toLowerCase();
|
|
@@ -706,36 +720,52 @@ function init(options = {}) {
|
|
|
706
720
|
);
|
|
707
721
|
}
|
|
708
722
|
initialized = true;
|
|
723
|
+
log("\u{1F4E1} Exporter URL:", `${baseUrl}/v1/traces`);
|
|
709
724
|
const exporter = new import_exporter_trace_otlp_http.OTLPTraceExporter({
|
|
710
725
|
url: `${baseUrl}/v1/traces`,
|
|
711
726
|
headers: {
|
|
712
727
|
Authorization: `Bearer ${apiKey}`
|
|
713
728
|
}
|
|
714
729
|
});
|
|
730
|
+
const instrumentations = await getInstrumentations();
|
|
731
|
+
log("\u{1F527} Loaded instrumentations:", instrumentations.length);
|
|
715
732
|
sdk = new import_sdk_node.NodeSDK({
|
|
716
733
|
resource: new Resource({
|
|
717
734
|
"service.name": "fallom-traced-app"
|
|
718
735
|
}),
|
|
719
736
|
traceExporter: exporter,
|
|
720
|
-
spanProcessor: fallomSpanProcessor
|
|
737
|
+
spanProcessor: fallomSpanProcessor,
|
|
738
|
+
instrumentations
|
|
721
739
|
});
|
|
722
740
|
sdk.start();
|
|
723
|
-
|
|
741
|
+
log("\u2705 SDK started");
|
|
724
742
|
process.on("SIGTERM", () => {
|
|
725
743
|
sdk?.shutdown().catch(console.error);
|
|
726
744
|
});
|
|
727
745
|
}
|
|
728
|
-
function
|
|
746
|
+
async function getInstrumentations() {
|
|
747
|
+
const instrumentations = [];
|
|
748
|
+
await tryAddInstrumentation(instrumentations, "@traceloop/instrumentation-openai", "OpenAIInstrumentation");
|
|
749
|
+
await tryAddInstrumentation(instrumentations, "@traceloop/instrumentation-anthropic", "AnthropicInstrumentation");
|
|
750
|
+
await tryAddInstrumentation(instrumentations, "@traceloop/instrumentation-cohere", "CohereInstrumentation");
|
|
751
|
+
await tryAddInstrumentation(instrumentations, "@traceloop/instrumentation-bedrock", "BedrockInstrumentation");
|
|
752
|
+
await tryAddInstrumentation(instrumentations, "@traceloop/instrumentation-google-generativeai", "GoogleGenerativeAIInstrumentation");
|
|
753
|
+
await tryAddInstrumentation(instrumentations, "@traceloop/instrumentation-azure", "AzureOpenAIInstrumentation");
|
|
754
|
+
await tryAddInstrumentation(instrumentations, "@traceloop/instrumentation-vertexai", "VertexAIInstrumentation");
|
|
755
|
+
return instrumentations;
|
|
756
|
+
}
|
|
757
|
+
async function tryAddInstrumentation(instrumentations, pkg, className) {
|
|
729
758
|
try {
|
|
730
|
-
const
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
}
|
|
738
|
-
} catch {
|
|
759
|
+
const mod = await import(pkg);
|
|
760
|
+
const InstrumentationClass = mod[className] || mod.default?.[className];
|
|
761
|
+
if (InstrumentationClass) {
|
|
762
|
+
instrumentations.push(new InstrumentationClass({ traceContent: captureContent }));
|
|
763
|
+
log(` \u2705 Loaded ${pkg}`);
|
|
764
|
+
} else {
|
|
765
|
+
log(` \u26A0\uFE0F ${pkg} loaded but ${className} not found. Available:`, Object.keys(mod));
|
|
766
|
+
}
|
|
767
|
+
} catch (e) {
|
|
768
|
+
log(` \u274C ${pkg} not installed`);
|
|
739
769
|
}
|
|
740
770
|
}
|
|
741
771
|
function setSession(configKey, sessionId) {
|
|
@@ -797,6 +827,169 @@ async function shutdown() {
|
|
|
797
827
|
initialized = false;
|
|
798
828
|
}
|
|
799
829
|
}
|
|
830
|
+
async function sendTrace(trace) {
|
|
831
|
+
try {
|
|
832
|
+
const controller = new AbortController();
|
|
833
|
+
const timeoutId = setTimeout(() => controller.abort(), 5e3);
|
|
834
|
+
await fetch(`${baseUrl}/v1/traces`, {
|
|
835
|
+
method: "POST",
|
|
836
|
+
headers: {
|
|
837
|
+
Authorization: `Bearer ${apiKey}`,
|
|
838
|
+
"Content-Type": "application/json"
|
|
839
|
+
},
|
|
840
|
+
body: JSON.stringify(trace),
|
|
841
|
+
signal: controller.signal
|
|
842
|
+
});
|
|
843
|
+
clearTimeout(timeoutId);
|
|
844
|
+
log("\u{1F4E4} Trace sent:", trace.name, trace.model);
|
|
845
|
+
} catch {
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
function wrapOpenAI(client) {
|
|
849
|
+
const originalCreate = client.chat.completions.create.bind(client.chat.completions);
|
|
850
|
+
client.chat.completions.create = async function(...args) {
|
|
851
|
+
const ctx = sessionStorage.getStore() || fallbackSession;
|
|
852
|
+
if (!ctx || !initialized) {
|
|
853
|
+
return originalCreate(...args);
|
|
854
|
+
}
|
|
855
|
+
const params = args[0] || {};
|
|
856
|
+
const startTime = Date.now();
|
|
857
|
+
try {
|
|
858
|
+
const response = await originalCreate(...args);
|
|
859
|
+
const endTime = Date.now();
|
|
860
|
+
sendTrace({
|
|
861
|
+
config_key: ctx.configKey,
|
|
862
|
+
session_id: ctx.sessionId,
|
|
863
|
+
name: "chat.completions.create",
|
|
864
|
+
model: response?.model || params?.model,
|
|
865
|
+
start_time: new Date(startTime).toISOString(),
|
|
866
|
+
end_time: new Date(endTime).toISOString(),
|
|
867
|
+
duration_ms: endTime - startTime,
|
|
868
|
+
status: "OK",
|
|
869
|
+
prompt_tokens: response?.usage?.prompt_tokens,
|
|
870
|
+
completion_tokens: response?.usage?.completion_tokens,
|
|
871
|
+
total_tokens: response?.usage?.total_tokens,
|
|
872
|
+
input: captureContent ? JSON.stringify(params?.messages) : void 0,
|
|
873
|
+
output: captureContent ? response?.choices?.[0]?.message?.content : void 0
|
|
874
|
+
}).catch(() => {
|
|
875
|
+
});
|
|
876
|
+
return response;
|
|
877
|
+
} catch (error) {
|
|
878
|
+
const endTime = Date.now();
|
|
879
|
+
sendTrace({
|
|
880
|
+
config_key: ctx.configKey,
|
|
881
|
+
session_id: ctx.sessionId,
|
|
882
|
+
name: "chat.completions.create",
|
|
883
|
+
model: params?.model,
|
|
884
|
+
start_time: new Date(startTime).toISOString(),
|
|
885
|
+
end_time: new Date(endTime).toISOString(),
|
|
886
|
+
duration_ms: endTime - startTime,
|
|
887
|
+
status: "ERROR",
|
|
888
|
+
error_message: error?.message
|
|
889
|
+
}).catch(() => {
|
|
890
|
+
});
|
|
891
|
+
throw error;
|
|
892
|
+
}
|
|
893
|
+
};
|
|
894
|
+
return client;
|
|
895
|
+
}
|
|
896
|
+
function wrapAnthropic(client) {
|
|
897
|
+
const originalCreate = client.messages.create.bind(client.messages);
|
|
898
|
+
client.messages.create = async function(...args) {
|
|
899
|
+
const ctx = sessionStorage.getStore() || fallbackSession;
|
|
900
|
+
if (!ctx || !initialized) {
|
|
901
|
+
return originalCreate(...args);
|
|
902
|
+
}
|
|
903
|
+
const params = args[0] || {};
|
|
904
|
+
const startTime = Date.now();
|
|
905
|
+
try {
|
|
906
|
+
const response = await originalCreate(...args);
|
|
907
|
+
const endTime = Date.now();
|
|
908
|
+
sendTrace({
|
|
909
|
+
config_key: ctx.configKey,
|
|
910
|
+
session_id: ctx.sessionId,
|
|
911
|
+
name: "messages.create",
|
|
912
|
+
model: response?.model || params?.model,
|
|
913
|
+
start_time: new Date(startTime).toISOString(),
|
|
914
|
+
end_time: new Date(endTime).toISOString(),
|
|
915
|
+
duration_ms: endTime - startTime,
|
|
916
|
+
status: "OK",
|
|
917
|
+
prompt_tokens: response?.usage?.input_tokens,
|
|
918
|
+
completion_tokens: response?.usage?.output_tokens,
|
|
919
|
+
total_tokens: (response?.usage?.input_tokens || 0) + (response?.usage?.output_tokens || 0),
|
|
920
|
+
input: captureContent ? JSON.stringify(params?.messages) : void 0,
|
|
921
|
+
output: captureContent ? response?.content?.[0]?.text : void 0
|
|
922
|
+
}).catch(() => {
|
|
923
|
+
});
|
|
924
|
+
return response;
|
|
925
|
+
} catch (error) {
|
|
926
|
+
const endTime = Date.now();
|
|
927
|
+
sendTrace({
|
|
928
|
+
config_key: ctx.configKey,
|
|
929
|
+
session_id: ctx.sessionId,
|
|
930
|
+
name: "messages.create",
|
|
931
|
+
model: params?.model,
|
|
932
|
+
start_time: new Date(startTime).toISOString(),
|
|
933
|
+
end_time: new Date(endTime).toISOString(),
|
|
934
|
+
duration_ms: endTime - startTime,
|
|
935
|
+
status: "ERROR",
|
|
936
|
+
error_message: error?.message
|
|
937
|
+
}).catch(() => {
|
|
938
|
+
});
|
|
939
|
+
throw error;
|
|
940
|
+
}
|
|
941
|
+
};
|
|
942
|
+
return client;
|
|
943
|
+
}
|
|
944
|
+
function wrapGoogleAI(model) {
|
|
945
|
+
const originalGenerate = model.generateContent.bind(model);
|
|
946
|
+
model.generateContent = async function(...args) {
|
|
947
|
+
const ctx = sessionStorage.getStore() || fallbackSession;
|
|
948
|
+
if (!ctx || !initialized) {
|
|
949
|
+
return originalGenerate(...args);
|
|
950
|
+
}
|
|
951
|
+
const startTime = Date.now();
|
|
952
|
+
try {
|
|
953
|
+
const response = await originalGenerate(...args);
|
|
954
|
+
const endTime = Date.now();
|
|
955
|
+
const result = response?.response;
|
|
956
|
+
const usage = result?.usageMetadata;
|
|
957
|
+
sendTrace({
|
|
958
|
+
config_key: ctx.configKey,
|
|
959
|
+
session_id: ctx.sessionId,
|
|
960
|
+
name: "generateContent",
|
|
961
|
+
model: model?.model || "gemini",
|
|
962
|
+
start_time: new Date(startTime).toISOString(),
|
|
963
|
+
end_time: new Date(endTime).toISOString(),
|
|
964
|
+
duration_ms: endTime - startTime,
|
|
965
|
+
status: "OK",
|
|
966
|
+
prompt_tokens: usage?.promptTokenCount,
|
|
967
|
+
completion_tokens: usage?.candidatesTokenCount,
|
|
968
|
+
total_tokens: usage?.totalTokenCount,
|
|
969
|
+
input: captureContent ? JSON.stringify(args[0]) : void 0,
|
|
970
|
+
output: captureContent ? result?.text?.() : void 0
|
|
971
|
+
}).catch(() => {
|
|
972
|
+
});
|
|
973
|
+
return response;
|
|
974
|
+
} catch (error) {
|
|
975
|
+
const endTime = Date.now();
|
|
976
|
+
sendTrace({
|
|
977
|
+
config_key: ctx.configKey,
|
|
978
|
+
session_id: ctx.sessionId,
|
|
979
|
+
name: "generateContent",
|
|
980
|
+
model: model?.model || "gemini",
|
|
981
|
+
start_time: new Date(startTime).toISOString(),
|
|
982
|
+
end_time: new Date(endTime).toISOString(),
|
|
983
|
+
duration_ms: endTime - startTime,
|
|
984
|
+
status: "ERROR",
|
|
985
|
+
error_message: error?.message
|
|
986
|
+
}).catch(() => {
|
|
987
|
+
});
|
|
988
|
+
throw error;
|
|
989
|
+
}
|
|
990
|
+
};
|
|
991
|
+
return model;
|
|
992
|
+
}
|
|
800
993
|
|
|
801
994
|
// src/models.ts
|
|
802
995
|
var models_exports = {};
|
|
@@ -809,12 +1002,12 @@ var apiKey2 = null;
|
|
|
809
1002
|
var baseUrl2 = "https://spans.fallom.com";
|
|
810
1003
|
var initialized2 = false;
|
|
811
1004
|
var syncInterval = null;
|
|
812
|
-
var
|
|
1005
|
+
var debugMode2 = false;
|
|
813
1006
|
var configCache = /* @__PURE__ */ new Map();
|
|
814
1007
|
var SYNC_TIMEOUT = 2e3;
|
|
815
1008
|
var RECORD_TIMEOUT = 1e3;
|
|
816
|
-
function
|
|
817
|
-
if (
|
|
1009
|
+
function log2(msg) {
|
|
1010
|
+
if (debugMode2) {
|
|
818
1011
|
console.log(`[Fallom] ${msg}`);
|
|
819
1012
|
}
|
|
820
1013
|
}
|
|
@@ -845,11 +1038,11 @@ function ensureInit() {
|
|
|
845
1038
|
}
|
|
846
1039
|
async function fetchConfigs(timeout = SYNC_TIMEOUT) {
|
|
847
1040
|
if (!apiKey2) {
|
|
848
|
-
|
|
1041
|
+
log2("_fetchConfigs: No API key, skipping");
|
|
849
1042
|
return;
|
|
850
1043
|
}
|
|
851
1044
|
try {
|
|
852
|
-
|
|
1045
|
+
log2(`Fetching configs from ${baseUrl2}/configs`);
|
|
853
1046
|
const controller = new AbortController();
|
|
854
1047
|
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
855
1048
|
const resp = await fetch(`${baseUrl2}/configs`, {
|
|
@@ -857,15 +1050,15 @@ async function fetchConfigs(timeout = SYNC_TIMEOUT) {
|
|
|
857
1050
|
signal: controller.signal
|
|
858
1051
|
});
|
|
859
1052
|
clearTimeout(timeoutId);
|
|
860
|
-
|
|
1053
|
+
log2(`Response status: ${resp.status}`);
|
|
861
1054
|
if (resp.ok) {
|
|
862
1055
|
const data = await resp.json();
|
|
863
1056
|
const configs = data.configs || [];
|
|
864
|
-
|
|
1057
|
+
log2(`Got ${configs.length} configs: ${configs.map((c) => c.key)}`);
|
|
865
1058
|
for (const c of configs) {
|
|
866
1059
|
const key = c.key;
|
|
867
1060
|
const version = c.version || 1;
|
|
868
|
-
|
|
1061
|
+
log2(`Config '${key}' v${version}: ${JSON.stringify(c.variants)}`);
|
|
869
1062
|
if (!configCache.has(key)) {
|
|
870
1063
|
configCache.set(key, { versions: /* @__PURE__ */ new Map(), latest: null });
|
|
871
1064
|
}
|
|
@@ -874,10 +1067,10 @@ async function fetchConfigs(timeout = SYNC_TIMEOUT) {
|
|
|
874
1067
|
cached.latest = version;
|
|
875
1068
|
}
|
|
876
1069
|
} else {
|
|
877
|
-
|
|
1070
|
+
log2(`Fetch failed: ${resp.statusText}`);
|
|
878
1071
|
}
|
|
879
1072
|
} catch (e) {
|
|
880
|
-
|
|
1073
|
+
log2(`Fetch exception: ${e}`);
|
|
881
1074
|
}
|
|
882
1075
|
}
|
|
883
1076
|
async function fetchSpecificVersion(configKey, version, timeout = SYNC_TIMEOUT) {
|
|
@@ -907,20 +1100,20 @@ async function fetchSpecificVersion(configKey, version, timeout = SYNC_TIMEOUT)
|
|
|
907
1100
|
}
|
|
908
1101
|
async function get(configKey, sessionId, options = {}) {
|
|
909
1102
|
const { version, fallback, debug = false } = options;
|
|
910
|
-
|
|
1103
|
+
debugMode2 = debug;
|
|
911
1104
|
ensureInit();
|
|
912
|
-
|
|
1105
|
+
log2(`get() called: configKey=${configKey}, sessionId=${sessionId}, fallback=${fallback}`);
|
|
913
1106
|
try {
|
|
914
1107
|
let configData = configCache.get(configKey);
|
|
915
|
-
|
|
1108
|
+
log2(`Cache lookup for '${configKey}': ${configData ? "found" : "not found"}`);
|
|
916
1109
|
if (!configData) {
|
|
917
|
-
|
|
1110
|
+
log2("Not in cache, fetching...");
|
|
918
1111
|
await fetchConfigs(SYNC_TIMEOUT);
|
|
919
1112
|
configData = configCache.get(configKey);
|
|
920
|
-
|
|
1113
|
+
log2(`After fetch, cache lookup: ${configData ? "found" : "still not found"}`);
|
|
921
1114
|
}
|
|
922
1115
|
if (!configData) {
|
|
923
|
-
|
|
1116
|
+
log2(`Config not found, using fallback: ${fallback}`);
|
|
924
1117
|
if (fallback) {
|
|
925
1118
|
console.warn(`[Fallom WARNING] Config '${configKey}' not found, using fallback model: ${fallback}`);
|
|
926
1119
|
return returnWithTrace(configKey, sessionId, fallback, 0);
|
|
@@ -958,22 +1151,22 @@ async function get(configKey, sessionId, options = {}) {
|
|
|
958
1151
|
const variantsRaw = config.variants;
|
|
959
1152
|
const configVersion = config.version || targetVersion;
|
|
960
1153
|
const variants = Array.isArray(variantsRaw) ? variantsRaw : Object.values(variantsRaw);
|
|
961
|
-
|
|
1154
|
+
log2(`Config found! Version: ${configVersion}, Variants: ${JSON.stringify(variants)}`);
|
|
962
1155
|
const hashBytes = (0, import_crypto.createHash)("md5").update(sessionId).digest();
|
|
963
1156
|
const hashVal = hashBytes.readUInt32BE(0) % 1e6;
|
|
964
|
-
|
|
1157
|
+
log2(`Session hash: ${hashVal} (out of 1,000,000)`);
|
|
965
1158
|
let cumulative = 0;
|
|
966
1159
|
let assignedModel = variants[variants.length - 1].model;
|
|
967
1160
|
for (const v of variants) {
|
|
968
1161
|
const oldCumulative = cumulative;
|
|
969
1162
|
cumulative += v.weight * 1e4;
|
|
970
|
-
|
|
1163
|
+
log2(`Variant ${v.model}: weight=${v.weight}%, range=${oldCumulative}-${cumulative}, hash=${hashVal}, match=${hashVal < cumulative}`);
|
|
971
1164
|
if (hashVal < cumulative) {
|
|
972
1165
|
assignedModel = v.model;
|
|
973
1166
|
break;
|
|
974
1167
|
}
|
|
975
1168
|
}
|
|
976
|
-
|
|
1169
|
+
log2(`\u2705 Assigned model: ${assignedModel}`);
|
|
977
1170
|
return returnWithTrace(configKey, sessionId, assignedModel, configVersion);
|
|
978
1171
|
} catch (e) {
|
|
979
1172
|
if (e instanceof Error && e.message.includes("not found")) {
|
|
@@ -1022,12 +1215,13 @@ async function recordSession(configKey, version, sessionId, model) {
|
|
|
1022
1215
|
}
|
|
1023
1216
|
|
|
1024
1217
|
// src/init.ts
|
|
1025
|
-
function init3(options = {}) {
|
|
1218
|
+
async function init3(options = {}) {
|
|
1026
1219
|
const baseUrl3 = options.baseUrl || process.env.FALLOM_BASE_URL || "https://spans.fallom.com";
|
|
1027
|
-
init({
|
|
1220
|
+
await init({
|
|
1028
1221
|
apiKey: options.apiKey,
|
|
1029
1222
|
baseUrl: baseUrl3,
|
|
1030
|
-
captureContent: options.captureContent
|
|
1223
|
+
captureContent: options.captureContent,
|
|
1224
|
+
debug: options.debug
|
|
1031
1225
|
});
|
|
1032
1226
|
init2({
|
|
1033
1227
|
apiKey: options.apiKey,
|