@lupaflow/observability 0.1.1 → 0.1.2
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.cjs +183 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +52 -0
- package/dist/index.d.ts +52 -0
- package/dist/index.js +143 -0
- package/dist/index.js.map +1 -0
- package/package.json +3 -2
- package/tsconfig.json +0 -8
- package/tsup.config.ts +0 -9
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var src_exports = {};
|
|
32
|
+
__export(src_exports, {
|
|
33
|
+
Tracer: () => Tracer,
|
|
34
|
+
UsageTracker: () => UsageTracker,
|
|
35
|
+
globalTracer: () => globalTracer,
|
|
36
|
+
logger: () => logger
|
|
37
|
+
});
|
|
38
|
+
module.exports = __toCommonJS(src_exports);
|
|
39
|
+
|
|
40
|
+
// src/tracker.ts
|
|
41
|
+
var import_core = require("@lupaflow/core");
|
|
42
|
+
var TOKEN_COST_PER_1K = {
|
|
43
|
+
"openai/gpt-4o-mini": { input: 15e-5, output: 6e-4 },
|
|
44
|
+
"openai/gpt-4o": { input: 25e-4, output: 0.01 },
|
|
45
|
+
"llama-3.1-70b-versatile": { input: 59e-5, output: 79e-5 },
|
|
46
|
+
"llama-3.1-8b-instant": { input: 5e-5, output: 8e-5 },
|
|
47
|
+
"gemini-1.5-flash": { input: 75e-6, output: 3e-4 },
|
|
48
|
+
"gemini-1.5-pro": { input: 125e-5, output: 5e-3 }
|
|
49
|
+
};
|
|
50
|
+
function estimateCost(provider, model, tokens, isOutput = false) {
|
|
51
|
+
const key = model;
|
|
52
|
+
const rates = TOKEN_COST_PER_1K[key];
|
|
53
|
+
if (!rates) return 0;
|
|
54
|
+
const rate = isOutput ? rates.output : rates.input;
|
|
55
|
+
return tokens / 1e3 * rate;
|
|
56
|
+
}
|
|
57
|
+
var UsageTracker = class {
|
|
58
|
+
records = [];
|
|
59
|
+
totalTokens = 0;
|
|
60
|
+
totalCost = 0;
|
|
61
|
+
constructor() {
|
|
62
|
+
import_core.globalEventBus.on("provider:complete", (event) => {
|
|
63
|
+
const e = event;
|
|
64
|
+
const isOutput = e.name === "provider:complete";
|
|
65
|
+
const cost = estimateCost(e.provider, e.model, e.tokens, isOutput);
|
|
66
|
+
this.records.push({
|
|
67
|
+
provider: e.provider,
|
|
68
|
+
model: e.model,
|
|
69
|
+
tokens: e.tokens,
|
|
70
|
+
cost,
|
|
71
|
+
latencyMs: e.latencyMs,
|
|
72
|
+
timestamp: e.timestamp
|
|
73
|
+
});
|
|
74
|
+
this.totalTokens += e.tokens;
|
|
75
|
+
this.totalCost += cost;
|
|
76
|
+
import_core.globalEventBus.emit("llm:token", {
|
|
77
|
+
tokens: e.tokens,
|
|
78
|
+
cost
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
getTotalTokens() {
|
|
83
|
+
return this.totalTokens;
|
|
84
|
+
}
|
|
85
|
+
getTotalCost() {
|
|
86
|
+
return this.totalCost;
|
|
87
|
+
}
|
|
88
|
+
getRecords() {
|
|
89
|
+
return [...this.records];
|
|
90
|
+
}
|
|
91
|
+
getSummary() {
|
|
92
|
+
const calls = this.records.length;
|
|
93
|
+
const avgLatency = calls > 0 ? this.records.reduce((s, r) => s + r.latencyMs, 0) / calls : 0;
|
|
94
|
+
return {
|
|
95
|
+
totalTokens: this.totalTokens,
|
|
96
|
+
totalCost: this.totalCost,
|
|
97
|
+
totalCalls: calls,
|
|
98
|
+
avgLatencyMs: Math.round(avgLatency)
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
reset() {
|
|
102
|
+
this.records = [];
|
|
103
|
+
this.totalTokens = 0;
|
|
104
|
+
this.totalCost = 0;
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// src/logger.ts
|
|
109
|
+
var import_pino = __toESM(require("pino"), 1);
|
|
110
|
+
var import_core2 = require("@lupaflow/core");
|
|
111
|
+
var logger = (0, import_pino.default)({
|
|
112
|
+
transport: {
|
|
113
|
+
target: "pino-pretty",
|
|
114
|
+
options: {
|
|
115
|
+
colorize: true,
|
|
116
|
+
translateTime: "HH:MM:ss",
|
|
117
|
+
ignore: "pid,hostname"
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
import_core2.globalEventBus.onAny((event) => {
|
|
122
|
+
logger.debug({ event: event.name, id: event.id }, `Event: ${event.name}`);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// src/span.ts
|
|
126
|
+
var import_core3 = require("@lupaflow/core");
|
|
127
|
+
var Tracer = class {
|
|
128
|
+
spans = /* @__PURE__ */ new Map();
|
|
129
|
+
stack = [];
|
|
130
|
+
start(name, metadata) {
|
|
131
|
+
const id = (0, import_core3.generateId)();
|
|
132
|
+
const parentId = this.stack[this.stack.length - 1];
|
|
133
|
+
const span = {
|
|
134
|
+
id,
|
|
135
|
+
name,
|
|
136
|
+
parentId,
|
|
137
|
+
startTime: Date.now(),
|
|
138
|
+
status: "open",
|
|
139
|
+
metadata
|
|
140
|
+
};
|
|
141
|
+
this.spans.set(id, span);
|
|
142
|
+
this.stack.push(id);
|
|
143
|
+
return id;
|
|
144
|
+
}
|
|
145
|
+
end(id, metadata) {
|
|
146
|
+
const span = this.spans.get(id);
|
|
147
|
+
if (!span) return;
|
|
148
|
+
span.endTime = Date.now();
|
|
149
|
+
span.status = "closed";
|
|
150
|
+
span.metadata = { ...span.metadata, ...metadata };
|
|
151
|
+
this.stack = this.stack.filter((s) => s !== id);
|
|
152
|
+
}
|
|
153
|
+
error(id, error) {
|
|
154
|
+
const span = this.spans.get(id);
|
|
155
|
+
if (!span) return;
|
|
156
|
+
span.endTime = Date.now();
|
|
157
|
+
span.status = "error";
|
|
158
|
+
span.metadata = { ...span.metadata, error };
|
|
159
|
+
this.stack = this.stack.filter((s) => s !== id);
|
|
160
|
+
}
|
|
161
|
+
getTrace(id) {
|
|
162
|
+
return this.spans.get(id);
|
|
163
|
+
}
|
|
164
|
+
getActiveSpans() {
|
|
165
|
+
return Array.from(this.spans.values()).filter((s) => s.status === "open");
|
|
166
|
+
}
|
|
167
|
+
getAllSpans() {
|
|
168
|
+
return Array.from(this.spans.values());
|
|
169
|
+
}
|
|
170
|
+
clear() {
|
|
171
|
+
this.spans.clear();
|
|
172
|
+
this.stack = [];
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
var globalTracer = new Tracer();
|
|
176
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
177
|
+
0 && (module.exports = {
|
|
178
|
+
Tracer,
|
|
179
|
+
UsageTracker,
|
|
180
|
+
globalTracer,
|
|
181
|
+
logger
|
|
182
|
+
});
|
|
183
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/tracker.ts","../src/logger.ts","../src/span.ts"],"sourcesContent":["export { UsageTracker } from \"./tracker\"\nexport { logger } from \"./logger\"\nexport { Tracer, globalTracer } from \"./span\"\n","import { globalEventBus } from \"@lupaflow/core\"\nimport type { LupaEvent, ProviderCompleteEvent, LLMTokenEvent } from \"@lupaflow/types\"\n\ninterface UsageRecord {\n provider: string\n model: string\n tokens: number\n cost: number\n latencyMs: number\n timestamp: number\n}\n\nconst TOKEN_COST_PER_1K: Record<string, { input: number; output: number }> = {\n \"openai/gpt-4o-mini\": { input: 0.00015, output: 0.0006 },\n \"openai/gpt-4o\": { input: 0.0025, output: 0.01 },\n \"llama-3.1-70b-versatile\": { input: 0.00059, output: 0.00079 },\n \"llama-3.1-8b-instant\": { input: 0.00005, output: 0.00008 },\n \"gemini-1.5-flash\": { input: 0.000075, output: 0.0003 },\n \"gemini-1.5-pro\": { input: 0.00125, output: 0.005 },\n}\n\nfunction estimateCost(provider: string, model: string, tokens: number, isOutput = false): number {\n const key = model\n const rates = TOKEN_COST_PER_1K[key]\n if (!rates) return 0\n const rate = isOutput ? rates.output : rates.input\n return (tokens / 1000) * rate\n}\n\nexport class UsageTracker {\n private records: UsageRecord[] = []\n private totalTokens = 0\n private totalCost = 0\n\n constructor() {\n globalEventBus.on(\"provider:complete\", (event: LupaEvent) => {\n const e = event as ProviderCompleteEvent\n const isOutput = e.name === \"provider:complete\"\n const cost = estimateCost(e.provider, e.model, e.tokens, isOutput)\n\n this.records.push({\n provider: e.provider,\n model: e.model,\n tokens: e.tokens,\n cost,\n latencyMs: e.latencyMs,\n timestamp: e.timestamp,\n })\n\n this.totalTokens += e.tokens\n this.totalCost += cost\n\n globalEventBus.emit(\"llm:token\", {\n tokens: e.tokens,\n cost,\n } as LLMTokenEvent)\n })\n }\n\n getTotalTokens(): number {\n return this.totalTokens\n }\n\n getTotalCost(): number {\n return this.totalCost\n }\n\n getRecords(): UsageRecord[] {\n return [...this.records]\n }\n\n getSummary(): { totalTokens: number; totalCost: number; totalCalls: number; avgLatencyMs: number } {\n const calls = this.records.length\n const avgLatency = calls > 0 ? this.records.reduce((s, r) => s + r.latencyMs, 0) / calls : 0\n return {\n totalTokens: this.totalTokens,\n totalCost: this.totalCost,\n totalCalls: calls,\n avgLatencyMs: Math.round(avgLatency),\n }\n }\n\n reset(): void {\n this.records = []\n this.totalTokens = 0\n this.totalCost = 0\n }\n}\n","import pino from \"pino\"\nimport { globalEventBus } from \"@lupaflow/core\"\n\nconst logger = pino({\n transport: {\n target: \"pino-pretty\",\n options: {\n colorize: true,\n translateTime: \"HH:MM:ss\",\n ignore: \"pid,hostname\",\n },\n },\n})\n\nglobalEventBus.onAny((event) => {\n logger.debug({ event: event.name, id: event.id }, `Event: ${event.name}`)\n})\n\nexport { logger }\n","import { generateId } from \"@lupaflow/core\"\n\ninterface Span {\n id: string\n name: string\n parentId?: string\n startTime: number\n endTime?: number\n status: \"open\" | \"closed\" | \"error\"\n metadata?: Record<string, unknown>\n}\n\nexport class Tracer {\n private spans: Map<string, Span> = new Map()\n private stack: string[] = []\n\n start(name: string, metadata?: Record<string, unknown>): string {\n const id = generateId()\n const parentId = this.stack[this.stack.length - 1]\n const span: Span = {\n id,\n name,\n parentId,\n startTime: Date.now(),\n status: \"open\",\n metadata,\n }\n this.spans.set(id, span)\n this.stack.push(id)\n return id\n }\n\n end(id: string, metadata?: Record<string, unknown>): void {\n const span = this.spans.get(id)\n if (!span) return\n span.endTime = Date.now()\n span.status = \"closed\"\n span.metadata = { ...span.metadata, ...metadata }\n this.stack = this.stack.filter((s) => s !== id)\n }\n\n error(id: string, error: string): void {\n const span = this.spans.get(id)\n if (!span) return\n span.endTime = Date.now()\n span.status = \"error\"\n span.metadata = { ...span.metadata, error }\n this.stack = this.stack.filter((s) => s !== id)\n }\n\n getTrace(id: string): Span | undefined {\n return this.spans.get(id)\n }\n\n getActiveSpans(): Span[] {\n return Array.from(this.spans.values()).filter((s) => s.status === \"open\")\n }\n\n getAllSpans(): Span[] {\n return Array.from(this.spans.values())\n }\n\n clear(): void {\n this.spans.clear()\n this.stack = []\n }\n}\n\nexport const globalTracer = new Tracer()\n"],"mappings":"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","names":["import_core","pino","import_core"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import pino from 'pino';
|
|
2
|
+
|
|
3
|
+
interface UsageRecord {
|
|
4
|
+
provider: string;
|
|
5
|
+
model: string;
|
|
6
|
+
tokens: number;
|
|
7
|
+
cost: number;
|
|
8
|
+
latencyMs: number;
|
|
9
|
+
timestamp: number;
|
|
10
|
+
}
|
|
11
|
+
declare class UsageTracker {
|
|
12
|
+
private records;
|
|
13
|
+
private totalTokens;
|
|
14
|
+
private totalCost;
|
|
15
|
+
constructor();
|
|
16
|
+
getTotalTokens(): number;
|
|
17
|
+
getTotalCost(): number;
|
|
18
|
+
getRecords(): UsageRecord[];
|
|
19
|
+
getSummary(): {
|
|
20
|
+
totalTokens: number;
|
|
21
|
+
totalCost: number;
|
|
22
|
+
totalCalls: number;
|
|
23
|
+
avgLatencyMs: number;
|
|
24
|
+
};
|
|
25
|
+
reset(): void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
declare const logger: pino.Logger<never, boolean>;
|
|
29
|
+
|
|
30
|
+
interface Span {
|
|
31
|
+
id: string;
|
|
32
|
+
name: string;
|
|
33
|
+
parentId?: string;
|
|
34
|
+
startTime: number;
|
|
35
|
+
endTime?: number;
|
|
36
|
+
status: "open" | "closed" | "error";
|
|
37
|
+
metadata?: Record<string, unknown>;
|
|
38
|
+
}
|
|
39
|
+
declare class Tracer {
|
|
40
|
+
private spans;
|
|
41
|
+
private stack;
|
|
42
|
+
start(name: string, metadata?: Record<string, unknown>): string;
|
|
43
|
+
end(id: string, metadata?: Record<string, unknown>): void;
|
|
44
|
+
error(id: string, error: string): void;
|
|
45
|
+
getTrace(id: string): Span | undefined;
|
|
46
|
+
getActiveSpans(): Span[];
|
|
47
|
+
getAllSpans(): Span[];
|
|
48
|
+
clear(): void;
|
|
49
|
+
}
|
|
50
|
+
declare const globalTracer: Tracer;
|
|
51
|
+
|
|
52
|
+
export { Tracer, UsageTracker, globalTracer, logger };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import pino from 'pino';
|
|
2
|
+
|
|
3
|
+
interface UsageRecord {
|
|
4
|
+
provider: string;
|
|
5
|
+
model: string;
|
|
6
|
+
tokens: number;
|
|
7
|
+
cost: number;
|
|
8
|
+
latencyMs: number;
|
|
9
|
+
timestamp: number;
|
|
10
|
+
}
|
|
11
|
+
declare class UsageTracker {
|
|
12
|
+
private records;
|
|
13
|
+
private totalTokens;
|
|
14
|
+
private totalCost;
|
|
15
|
+
constructor();
|
|
16
|
+
getTotalTokens(): number;
|
|
17
|
+
getTotalCost(): number;
|
|
18
|
+
getRecords(): UsageRecord[];
|
|
19
|
+
getSummary(): {
|
|
20
|
+
totalTokens: number;
|
|
21
|
+
totalCost: number;
|
|
22
|
+
totalCalls: number;
|
|
23
|
+
avgLatencyMs: number;
|
|
24
|
+
};
|
|
25
|
+
reset(): void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
declare const logger: pino.Logger<never, boolean>;
|
|
29
|
+
|
|
30
|
+
interface Span {
|
|
31
|
+
id: string;
|
|
32
|
+
name: string;
|
|
33
|
+
parentId?: string;
|
|
34
|
+
startTime: number;
|
|
35
|
+
endTime?: number;
|
|
36
|
+
status: "open" | "closed" | "error";
|
|
37
|
+
metadata?: Record<string, unknown>;
|
|
38
|
+
}
|
|
39
|
+
declare class Tracer {
|
|
40
|
+
private spans;
|
|
41
|
+
private stack;
|
|
42
|
+
start(name: string, metadata?: Record<string, unknown>): string;
|
|
43
|
+
end(id: string, metadata?: Record<string, unknown>): void;
|
|
44
|
+
error(id: string, error: string): void;
|
|
45
|
+
getTrace(id: string): Span | undefined;
|
|
46
|
+
getActiveSpans(): Span[];
|
|
47
|
+
getAllSpans(): Span[];
|
|
48
|
+
clear(): void;
|
|
49
|
+
}
|
|
50
|
+
declare const globalTracer: Tracer;
|
|
51
|
+
|
|
52
|
+
export { Tracer, UsageTracker, globalTracer, logger };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
// src/tracker.ts
|
|
2
|
+
import { globalEventBus } from "@lupaflow/core";
|
|
3
|
+
var TOKEN_COST_PER_1K = {
|
|
4
|
+
"openai/gpt-4o-mini": { input: 15e-5, output: 6e-4 },
|
|
5
|
+
"openai/gpt-4o": { input: 25e-4, output: 0.01 },
|
|
6
|
+
"llama-3.1-70b-versatile": { input: 59e-5, output: 79e-5 },
|
|
7
|
+
"llama-3.1-8b-instant": { input: 5e-5, output: 8e-5 },
|
|
8
|
+
"gemini-1.5-flash": { input: 75e-6, output: 3e-4 },
|
|
9
|
+
"gemini-1.5-pro": { input: 125e-5, output: 5e-3 }
|
|
10
|
+
};
|
|
11
|
+
function estimateCost(provider, model, tokens, isOutput = false) {
|
|
12
|
+
const key = model;
|
|
13
|
+
const rates = TOKEN_COST_PER_1K[key];
|
|
14
|
+
if (!rates) return 0;
|
|
15
|
+
const rate = isOutput ? rates.output : rates.input;
|
|
16
|
+
return tokens / 1e3 * rate;
|
|
17
|
+
}
|
|
18
|
+
var UsageTracker = class {
|
|
19
|
+
records = [];
|
|
20
|
+
totalTokens = 0;
|
|
21
|
+
totalCost = 0;
|
|
22
|
+
constructor() {
|
|
23
|
+
globalEventBus.on("provider:complete", (event) => {
|
|
24
|
+
const e = event;
|
|
25
|
+
const isOutput = e.name === "provider:complete";
|
|
26
|
+
const cost = estimateCost(e.provider, e.model, e.tokens, isOutput);
|
|
27
|
+
this.records.push({
|
|
28
|
+
provider: e.provider,
|
|
29
|
+
model: e.model,
|
|
30
|
+
tokens: e.tokens,
|
|
31
|
+
cost,
|
|
32
|
+
latencyMs: e.latencyMs,
|
|
33
|
+
timestamp: e.timestamp
|
|
34
|
+
});
|
|
35
|
+
this.totalTokens += e.tokens;
|
|
36
|
+
this.totalCost += cost;
|
|
37
|
+
globalEventBus.emit("llm:token", {
|
|
38
|
+
tokens: e.tokens,
|
|
39
|
+
cost
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
getTotalTokens() {
|
|
44
|
+
return this.totalTokens;
|
|
45
|
+
}
|
|
46
|
+
getTotalCost() {
|
|
47
|
+
return this.totalCost;
|
|
48
|
+
}
|
|
49
|
+
getRecords() {
|
|
50
|
+
return [...this.records];
|
|
51
|
+
}
|
|
52
|
+
getSummary() {
|
|
53
|
+
const calls = this.records.length;
|
|
54
|
+
const avgLatency = calls > 0 ? this.records.reduce((s, r) => s + r.latencyMs, 0) / calls : 0;
|
|
55
|
+
return {
|
|
56
|
+
totalTokens: this.totalTokens,
|
|
57
|
+
totalCost: this.totalCost,
|
|
58
|
+
totalCalls: calls,
|
|
59
|
+
avgLatencyMs: Math.round(avgLatency)
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
reset() {
|
|
63
|
+
this.records = [];
|
|
64
|
+
this.totalTokens = 0;
|
|
65
|
+
this.totalCost = 0;
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// src/logger.ts
|
|
70
|
+
import pino from "pino";
|
|
71
|
+
import { globalEventBus as globalEventBus2 } from "@lupaflow/core";
|
|
72
|
+
var logger = pino({
|
|
73
|
+
transport: {
|
|
74
|
+
target: "pino-pretty",
|
|
75
|
+
options: {
|
|
76
|
+
colorize: true,
|
|
77
|
+
translateTime: "HH:MM:ss",
|
|
78
|
+
ignore: "pid,hostname"
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
globalEventBus2.onAny((event) => {
|
|
83
|
+
logger.debug({ event: event.name, id: event.id }, `Event: ${event.name}`);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// src/span.ts
|
|
87
|
+
import { generateId } from "@lupaflow/core";
|
|
88
|
+
var Tracer = class {
|
|
89
|
+
spans = /* @__PURE__ */ new Map();
|
|
90
|
+
stack = [];
|
|
91
|
+
start(name, metadata) {
|
|
92
|
+
const id = generateId();
|
|
93
|
+
const parentId = this.stack[this.stack.length - 1];
|
|
94
|
+
const span = {
|
|
95
|
+
id,
|
|
96
|
+
name,
|
|
97
|
+
parentId,
|
|
98
|
+
startTime: Date.now(),
|
|
99
|
+
status: "open",
|
|
100
|
+
metadata
|
|
101
|
+
};
|
|
102
|
+
this.spans.set(id, span);
|
|
103
|
+
this.stack.push(id);
|
|
104
|
+
return id;
|
|
105
|
+
}
|
|
106
|
+
end(id, metadata) {
|
|
107
|
+
const span = this.spans.get(id);
|
|
108
|
+
if (!span) return;
|
|
109
|
+
span.endTime = Date.now();
|
|
110
|
+
span.status = "closed";
|
|
111
|
+
span.metadata = { ...span.metadata, ...metadata };
|
|
112
|
+
this.stack = this.stack.filter((s) => s !== id);
|
|
113
|
+
}
|
|
114
|
+
error(id, error) {
|
|
115
|
+
const span = this.spans.get(id);
|
|
116
|
+
if (!span) return;
|
|
117
|
+
span.endTime = Date.now();
|
|
118
|
+
span.status = "error";
|
|
119
|
+
span.metadata = { ...span.metadata, error };
|
|
120
|
+
this.stack = this.stack.filter((s) => s !== id);
|
|
121
|
+
}
|
|
122
|
+
getTrace(id) {
|
|
123
|
+
return this.spans.get(id);
|
|
124
|
+
}
|
|
125
|
+
getActiveSpans() {
|
|
126
|
+
return Array.from(this.spans.values()).filter((s) => s.status === "open");
|
|
127
|
+
}
|
|
128
|
+
getAllSpans() {
|
|
129
|
+
return Array.from(this.spans.values());
|
|
130
|
+
}
|
|
131
|
+
clear() {
|
|
132
|
+
this.spans.clear();
|
|
133
|
+
this.stack = [];
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
var globalTracer = new Tracer();
|
|
137
|
+
export {
|
|
138
|
+
Tracer,
|
|
139
|
+
UsageTracker,
|
|
140
|
+
globalTracer,
|
|
141
|
+
logger
|
|
142
|
+
};
|
|
143
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/tracker.ts","../src/logger.ts","../src/span.ts"],"sourcesContent":["import { globalEventBus } from \"@lupaflow/core\"\nimport type { LupaEvent, ProviderCompleteEvent, LLMTokenEvent } from \"@lupaflow/types\"\n\ninterface UsageRecord {\n provider: string\n model: string\n tokens: number\n cost: number\n latencyMs: number\n timestamp: number\n}\n\nconst TOKEN_COST_PER_1K: Record<string, { input: number; output: number }> = {\n \"openai/gpt-4o-mini\": { input: 0.00015, output: 0.0006 },\n \"openai/gpt-4o\": { input: 0.0025, output: 0.01 },\n \"llama-3.1-70b-versatile\": { input: 0.00059, output: 0.00079 },\n \"llama-3.1-8b-instant\": { input: 0.00005, output: 0.00008 },\n \"gemini-1.5-flash\": { input: 0.000075, output: 0.0003 },\n \"gemini-1.5-pro\": { input: 0.00125, output: 0.005 },\n}\n\nfunction estimateCost(provider: string, model: string, tokens: number, isOutput = false): number {\n const key = model\n const rates = TOKEN_COST_PER_1K[key]\n if (!rates) return 0\n const rate = isOutput ? rates.output : rates.input\n return (tokens / 1000) * rate\n}\n\nexport class UsageTracker {\n private records: UsageRecord[] = []\n private totalTokens = 0\n private totalCost = 0\n\n constructor() {\n globalEventBus.on(\"provider:complete\", (event: LupaEvent) => {\n const e = event as ProviderCompleteEvent\n const isOutput = e.name === \"provider:complete\"\n const cost = estimateCost(e.provider, e.model, e.tokens, isOutput)\n\n this.records.push({\n provider: e.provider,\n model: e.model,\n tokens: e.tokens,\n cost,\n latencyMs: e.latencyMs,\n timestamp: e.timestamp,\n })\n\n this.totalTokens += e.tokens\n this.totalCost += cost\n\n globalEventBus.emit(\"llm:token\", {\n tokens: e.tokens,\n cost,\n } as LLMTokenEvent)\n })\n }\n\n getTotalTokens(): number {\n return this.totalTokens\n }\n\n getTotalCost(): number {\n return this.totalCost\n }\n\n getRecords(): UsageRecord[] {\n return [...this.records]\n }\n\n getSummary(): { totalTokens: number; totalCost: number; totalCalls: number; avgLatencyMs: number } {\n const calls = this.records.length\n const avgLatency = calls > 0 ? this.records.reduce((s, r) => s + r.latencyMs, 0) / calls : 0\n return {\n totalTokens: this.totalTokens,\n totalCost: this.totalCost,\n totalCalls: calls,\n avgLatencyMs: Math.round(avgLatency),\n }\n }\n\n reset(): void {\n this.records = []\n this.totalTokens = 0\n this.totalCost = 0\n }\n}\n","import pino from \"pino\"\nimport { globalEventBus } from \"@lupaflow/core\"\n\nconst logger = pino({\n transport: {\n target: \"pino-pretty\",\n options: {\n colorize: true,\n translateTime: \"HH:MM:ss\",\n ignore: \"pid,hostname\",\n },\n },\n})\n\nglobalEventBus.onAny((event) => {\n logger.debug({ event: event.name, id: event.id }, `Event: ${event.name}`)\n})\n\nexport { logger }\n","import { generateId } from \"@lupaflow/core\"\n\ninterface Span {\n id: string\n name: string\n parentId?: string\n startTime: number\n endTime?: number\n status: \"open\" | \"closed\" | \"error\"\n metadata?: Record<string, unknown>\n}\n\nexport class Tracer {\n private spans: Map<string, Span> = new Map()\n private stack: string[] = []\n\n start(name: string, metadata?: Record<string, unknown>): string {\n const id = generateId()\n const parentId = this.stack[this.stack.length - 1]\n const span: Span = {\n id,\n name,\n parentId,\n startTime: Date.now(),\n status: \"open\",\n metadata,\n }\n this.spans.set(id, span)\n this.stack.push(id)\n return id\n }\n\n end(id: string, metadata?: Record<string, unknown>): void {\n const span = this.spans.get(id)\n if (!span) return\n span.endTime = Date.now()\n span.status = \"closed\"\n span.metadata = { ...span.metadata, ...metadata }\n this.stack = this.stack.filter((s) => s !== id)\n }\n\n error(id: string, error: string): void {\n const span = this.spans.get(id)\n if (!span) return\n span.endTime = Date.now()\n span.status = \"error\"\n span.metadata = { ...span.metadata, error }\n this.stack = this.stack.filter((s) => s !== id)\n }\n\n getTrace(id: string): Span | undefined {\n return this.spans.get(id)\n }\n\n getActiveSpans(): Span[] {\n return Array.from(this.spans.values()).filter((s) => s.status === \"open\")\n }\n\n getAllSpans(): Span[] {\n return Array.from(this.spans.values())\n }\n\n clear(): void {\n this.spans.clear()\n this.stack = []\n }\n}\n\nexport const globalTracer = new Tracer()\n"],"mappings":"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","names":["globalEventBus"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lupaflow/observability",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": {
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
"import": "./dist/index.js"
|
|
17
17
|
}
|
|
18
18
|
},
|
|
19
|
-
|
|
19
|
+
"files": ["dist", "src", "package.json"],
|
|
20
|
+
"scripts": {
|
|
20
21
|
"build": "tsup",
|
|
21
22
|
"dev": "tsup --watch"
|
|
22
23
|
},
|
package/tsconfig.json
DELETED