@fallom/trace 0.1.1 → 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 +92 -179
- package/dist/index.d.mts +76 -3
- package/dist/index.d.ts +76 -3
- package/dist/index.js +231 -40
- package/dist/index.mjs +231 -46
- package/package.json +6 -3
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,39 +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
|
-
const
|
|
732
|
-
if (
|
|
733
|
-
|
|
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));
|
|
734
766
|
}
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
apiKey,
|
|
738
|
-
disableBatch: true,
|
|
739
|
-
traceContent: captureContent
|
|
740
|
-
});
|
|
741
|
-
} catch {
|
|
767
|
+
} catch (e) {
|
|
768
|
+
log(` \u274C ${pkg} not installed`);
|
|
742
769
|
}
|
|
743
770
|
}
|
|
744
771
|
function setSession(configKey, sessionId) {
|
|
@@ -800,6 +827,169 @@ async function shutdown() {
|
|
|
800
827
|
initialized = false;
|
|
801
828
|
}
|
|
802
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
|
+
}
|
|
803
993
|
|
|
804
994
|
// src/models.ts
|
|
805
995
|
var models_exports = {};
|
|
@@ -812,12 +1002,12 @@ var apiKey2 = null;
|
|
|
812
1002
|
var baseUrl2 = "https://spans.fallom.com";
|
|
813
1003
|
var initialized2 = false;
|
|
814
1004
|
var syncInterval = null;
|
|
815
|
-
var
|
|
1005
|
+
var debugMode2 = false;
|
|
816
1006
|
var configCache = /* @__PURE__ */ new Map();
|
|
817
1007
|
var SYNC_TIMEOUT = 2e3;
|
|
818
1008
|
var RECORD_TIMEOUT = 1e3;
|
|
819
|
-
function
|
|
820
|
-
if (
|
|
1009
|
+
function log2(msg) {
|
|
1010
|
+
if (debugMode2) {
|
|
821
1011
|
console.log(`[Fallom] ${msg}`);
|
|
822
1012
|
}
|
|
823
1013
|
}
|
|
@@ -848,11 +1038,11 @@ function ensureInit() {
|
|
|
848
1038
|
}
|
|
849
1039
|
async function fetchConfigs(timeout = SYNC_TIMEOUT) {
|
|
850
1040
|
if (!apiKey2) {
|
|
851
|
-
|
|
1041
|
+
log2("_fetchConfigs: No API key, skipping");
|
|
852
1042
|
return;
|
|
853
1043
|
}
|
|
854
1044
|
try {
|
|
855
|
-
|
|
1045
|
+
log2(`Fetching configs from ${baseUrl2}/configs`);
|
|
856
1046
|
const controller = new AbortController();
|
|
857
1047
|
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
858
1048
|
const resp = await fetch(`${baseUrl2}/configs`, {
|
|
@@ -860,15 +1050,15 @@ async function fetchConfigs(timeout = SYNC_TIMEOUT) {
|
|
|
860
1050
|
signal: controller.signal
|
|
861
1051
|
});
|
|
862
1052
|
clearTimeout(timeoutId);
|
|
863
|
-
|
|
1053
|
+
log2(`Response status: ${resp.status}`);
|
|
864
1054
|
if (resp.ok) {
|
|
865
1055
|
const data = await resp.json();
|
|
866
1056
|
const configs = data.configs || [];
|
|
867
|
-
|
|
1057
|
+
log2(`Got ${configs.length} configs: ${configs.map((c) => c.key)}`);
|
|
868
1058
|
for (const c of configs) {
|
|
869
1059
|
const key = c.key;
|
|
870
1060
|
const version = c.version || 1;
|
|
871
|
-
|
|
1061
|
+
log2(`Config '${key}' v${version}: ${JSON.stringify(c.variants)}`);
|
|
872
1062
|
if (!configCache.has(key)) {
|
|
873
1063
|
configCache.set(key, { versions: /* @__PURE__ */ new Map(), latest: null });
|
|
874
1064
|
}
|
|
@@ -877,10 +1067,10 @@ async function fetchConfigs(timeout = SYNC_TIMEOUT) {
|
|
|
877
1067
|
cached.latest = version;
|
|
878
1068
|
}
|
|
879
1069
|
} else {
|
|
880
|
-
|
|
1070
|
+
log2(`Fetch failed: ${resp.statusText}`);
|
|
881
1071
|
}
|
|
882
1072
|
} catch (e) {
|
|
883
|
-
|
|
1073
|
+
log2(`Fetch exception: ${e}`);
|
|
884
1074
|
}
|
|
885
1075
|
}
|
|
886
1076
|
async function fetchSpecificVersion(configKey, version, timeout = SYNC_TIMEOUT) {
|
|
@@ -910,20 +1100,20 @@ async function fetchSpecificVersion(configKey, version, timeout = SYNC_TIMEOUT)
|
|
|
910
1100
|
}
|
|
911
1101
|
async function get(configKey, sessionId, options = {}) {
|
|
912
1102
|
const { version, fallback, debug = false } = options;
|
|
913
|
-
|
|
1103
|
+
debugMode2 = debug;
|
|
914
1104
|
ensureInit();
|
|
915
|
-
|
|
1105
|
+
log2(`get() called: configKey=${configKey}, sessionId=${sessionId}, fallback=${fallback}`);
|
|
916
1106
|
try {
|
|
917
1107
|
let configData = configCache.get(configKey);
|
|
918
|
-
|
|
1108
|
+
log2(`Cache lookup for '${configKey}': ${configData ? "found" : "not found"}`);
|
|
919
1109
|
if (!configData) {
|
|
920
|
-
|
|
1110
|
+
log2("Not in cache, fetching...");
|
|
921
1111
|
await fetchConfigs(SYNC_TIMEOUT);
|
|
922
1112
|
configData = configCache.get(configKey);
|
|
923
|
-
|
|
1113
|
+
log2(`After fetch, cache lookup: ${configData ? "found" : "still not found"}`);
|
|
924
1114
|
}
|
|
925
1115
|
if (!configData) {
|
|
926
|
-
|
|
1116
|
+
log2(`Config not found, using fallback: ${fallback}`);
|
|
927
1117
|
if (fallback) {
|
|
928
1118
|
console.warn(`[Fallom WARNING] Config '${configKey}' not found, using fallback model: ${fallback}`);
|
|
929
1119
|
return returnWithTrace(configKey, sessionId, fallback, 0);
|
|
@@ -961,22 +1151,22 @@ async function get(configKey, sessionId, options = {}) {
|
|
|
961
1151
|
const variantsRaw = config.variants;
|
|
962
1152
|
const configVersion = config.version || targetVersion;
|
|
963
1153
|
const variants = Array.isArray(variantsRaw) ? variantsRaw : Object.values(variantsRaw);
|
|
964
|
-
|
|
1154
|
+
log2(`Config found! Version: ${configVersion}, Variants: ${JSON.stringify(variants)}`);
|
|
965
1155
|
const hashBytes = (0, import_crypto.createHash)("md5").update(sessionId).digest();
|
|
966
1156
|
const hashVal = hashBytes.readUInt32BE(0) % 1e6;
|
|
967
|
-
|
|
1157
|
+
log2(`Session hash: ${hashVal} (out of 1,000,000)`);
|
|
968
1158
|
let cumulative = 0;
|
|
969
1159
|
let assignedModel = variants[variants.length - 1].model;
|
|
970
1160
|
for (const v of variants) {
|
|
971
1161
|
const oldCumulative = cumulative;
|
|
972
1162
|
cumulative += v.weight * 1e4;
|
|
973
|
-
|
|
1163
|
+
log2(`Variant ${v.model}: weight=${v.weight}%, range=${oldCumulative}-${cumulative}, hash=${hashVal}, match=${hashVal < cumulative}`);
|
|
974
1164
|
if (hashVal < cumulative) {
|
|
975
1165
|
assignedModel = v.model;
|
|
976
1166
|
break;
|
|
977
1167
|
}
|
|
978
1168
|
}
|
|
979
|
-
|
|
1169
|
+
log2(`\u2705 Assigned model: ${assignedModel}`);
|
|
980
1170
|
return returnWithTrace(configKey, sessionId, assignedModel, configVersion);
|
|
981
1171
|
} catch (e) {
|
|
982
1172
|
if (e instanceof Error && e.message.includes("not found")) {
|
|
@@ -1025,12 +1215,13 @@ async function recordSession(configKey, version, sessionId, model) {
|
|
|
1025
1215
|
}
|
|
1026
1216
|
|
|
1027
1217
|
// src/init.ts
|
|
1028
|
-
function init3(options = {}) {
|
|
1218
|
+
async function init3(options = {}) {
|
|
1029
1219
|
const baseUrl3 = options.baseUrl || process.env.FALLOM_BASE_URL || "https://spans.fallom.com";
|
|
1030
|
-
init({
|
|
1220
|
+
await init({
|
|
1031
1221
|
apiKey: options.apiKey,
|
|
1032
1222
|
baseUrl: baseUrl3,
|
|
1033
|
-
captureContent: options.captureContent
|
|
1223
|
+
captureContent: options.captureContent,
|
|
1224
|
+
debug: options.debug
|
|
1034
1225
|
});
|
|
1035
1226
|
init2({
|
|
1036
1227
|
apiKey: options.apiKey,
|