@axon-ai/openai-tracer 1.0.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 +276 -0
- package/dist/index.d.mts +214 -0
- package/dist/index.d.ts +214 -0
- package/dist/index.js +425 -0
- package/dist/index.mjs +386 -0
- package/package.json +48 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,425 @@
|
|
|
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 index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
OpenAITracer: () => OpenAITracer,
|
|
34
|
+
TracedOpenAI: () => TracedOpenAI,
|
|
35
|
+
createOpenAITracer: () => createOpenAITracer,
|
|
36
|
+
default: () => OpenAITracer
|
|
37
|
+
});
|
|
38
|
+
module.exports = __toCommonJS(index_exports);
|
|
39
|
+
|
|
40
|
+
// src/OpenAITracer.ts
|
|
41
|
+
var import_events = require("events");
|
|
42
|
+
var import_uuid = require("uuid");
|
|
43
|
+
var OpenAITracer = class extends import_events.EventEmitter {
|
|
44
|
+
/**
|
|
45
|
+
* Creates a new OpenAI tracer instance
|
|
46
|
+
*
|
|
47
|
+
* @param config - Configuration object for the tracer
|
|
48
|
+
* @param config.projectName - Project name for organizing traces (default: 'openai-agent')
|
|
49
|
+
* @param config.endpoint - Backend server endpoint (default: 'http://localhost:3000')
|
|
50
|
+
* @param config.metadata - Additional metadata to include with all events
|
|
51
|
+
* @param config.autoConnect - Whether to automatically connect to the server (default: true)
|
|
52
|
+
*/
|
|
53
|
+
constructor(config = {}) {
|
|
54
|
+
super();
|
|
55
|
+
this.eventQueue = [];
|
|
56
|
+
this.isConnected = false;
|
|
57
|
+
this.client = null;
|
|
58
|
+
this.flushInterval = null;
|
|
59
|
+
this.traceId = (0, import_uuid.v4)();
|
|
60
|
+
this.projectName = config.projectName || "openai-agent";
|
|
61
|
+
this.endpoint = config.endpoint || "http://localhost:3000";
|
|
62
|
+
this.metadata = config.metadata || {};
|
|
63
|
+
if (config.autoConnect !== false) {
|
|
64
|
+
this.connect();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Establishes WebSocket connection to the trace server
|
|
69
|
+
*
|
|
70
|
+
* This method creates a Socket.IO client connection to the backend server,
|
|
71
|
+
* sets up event listeners for connection status, and starts the periodic
|
|
72
|
+
* event flushing mechanism.
|
|
73
|
+
*
|
|
74
|
+
* @throws Error if connection fails
|
|
75
|
+
*/
|
|
76
|
+
async connect() {
|
|
77
|
+
try {
|
|
78
|
+
const { io } = await import("socket.io-client");
|
|
79
|
+
this.client = io(this.endpoint, {
|
|
80
|
+
transports: ["websocket", "polling"],
|
|
81
|
+
timeout: 5e3
|
|
82
|
+
});
|
|
83
|
+
this.client.on("connect", () => {
|
|
84
|
+
this.isConnected = true;
|
|
85
|
+
this.emit("connected");
|
|
86
|
+
console.log(`[OpenAI Tracer] Connected to ${this.endpoint}`);
|
|
87
|
+
});
|
|
88
|
+
this.client.on("disconnect", () => {
|
|
89
|
+
this.isConnected = false;
|
|
90
|
+
this.emit("disconnected");
|
|
91
|
+
console.log("[OpenAI Tracer] Disconnected from server");
|
|
92
|
+
});
|
|
93
|
+
this.client.on("connect_error", (error) => {
|
|
94
|
+
console.error("[OpenAI Tracer] Connection error:", error);
|
|
95
|
+
this.emit("error", error);
|
|
96
|
+
});
|
|
97
|
+
this.flushInterval = setInterval(() => {
|
|
98
|
+
this.flushQueue();
|
|
99
|
+
}, 1e3);
|
|
100
|
+
} catch (error) {
|
|
101
|
+
console.error("[OpenAI Tracer] Connection failed:", error);
|
|
102
|
+
this.emit("error", error);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Disconnect from the trace server
|
|
107
|
+
*/
|
|
108
|
+
disconnect() {
|
|
109
|
+
this.isConnected = false;
|
|
110
|
+
if (this.flushInterval) {
|
|
111
|
+
clearInterval(this.flushInterval);
|
|
112
|
+
this.flushInterval = null;
|
|
113
|
+
}
|
|
114
|
+
if (this.client) {
|
|
115
|
+
this.client.disconnect();
|
|
116
|
+
this.client = null;
|
|
117
|
+
}
|
|
118
|
+
this.emit("disconnected");
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Records the start of a function call execution
|
|
122
|
+
*
|
|
123
|
+
* This method creates and queues a function call start event, capturing
|
|
124
|
+
* the function name, arguments, model, messages, and available tools.
|
|
125
|
+
* It returns an event ID that can be used to correlate with the end event.
|
|
126
|
+
*
|
|
127
|
+
* @param functionName - Name of the function being called
|
|
128
|
+
* @param functionArguments - Arguments passed to the function
|
|
129
|
+
* @param model - OpenAI model being used (e.g., 'gpt-4', 'gpt-3.5-turbo')
|
|
130
|
+
* @param messages - Array of conversation messages
|
|
131
|
+
* @param tools - Optional array of available tools
|
|
132
|
+
* @returns Event ID for correlating with the corresponding end event
|
|
133
|
+
*/
|
|
134
|
+
traceFunctionCallStart(functionName, functionArguments, model, messages, tools) {
|
|
135
|
+
const eventId = (0, import_uuid.v4)();
|
|
136
|
+
const event = {
|
|
137
|
+
eventId,
|
|
138
|
+
traceId: this.traceId,
|
|
139
|
+
timestamp: Date.now(),
|
|
140
|
+
type: "function_call_start",
|
|
141
|
+
data: {
|
|
142
|
+
functionName,
|
|
143
|
+
arguments: JSON.stringify(functionArguments),
|
|
144
|
+
model,
|
|
145
|
+
messageCount: messages.length,
|
|
146
|
+
availableTools: tools?.map((t) => t.function.name) || [],
|
|
147
|
+
conversationContext: this.extractConversationContext(messages)
|
|
148
|
+
},
|
|
149
|
+
metadata: {
|
|
150
|
+
...this.metadata,
|
|
151
|
+
projectName: this.projectName
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
this.addEvent(event);
|
|
155
|
+
return eventId;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Track function call end
|
|
159
|
+
*/
|
|
160
|
+
traceFunctionCallEnd(eventId, result, cost, latency, tokens) {
|
|
161
|
+
const event = {
|
|
162
|
+
eventId: (0, import_uuid.v4)(),
|
|
163
|
+
traceId: this.traceId,
|
|
164
|
+
timestamp: Date.now(),
|
|
165
|
+
type: "function_call_end",
|
|
166
|
+
data: {
|
|
167
|
+
originalEventId: eventId,
|
|
168
|
+
result: typeof result === "string" ? result : JSON.stringify(result),
|
|
169
|
+
cost,
|
|
170
|
+
latency,
|
|
171
|
+
tokens: tokens || { prompt: 0, completion: 0, total: 0 },
|
|
172
|
+
success: true
|
|
173
|
+
},
|
|
174
|
+
metadata: {
|
|
175
|
+
...this.metadata,
|
|
176
|
+
projectName: this.projectName
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
this.addEvent(event);
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Track tool selection
|
|
183
|
+
*/
|
|
184
|
+
traceToolSelection(availableTools, selectedTool, reasoning, confidence) {
|
|
185
|
+
const event = {
|
|
186
|
+
eventId: (0, import_uuid.v4)(),
|
|
187
|
+
traceId: this.traceId,
|
|
188
|
+
timestamp: Date.now(),
|
|
189
|
+
type: "tool_selection",
|
|
190
|
+
data: {
|
|
191
|
+
availableTools: availableTools.map((t) => ({
|
|
192
|
+
name: t.function.name,
|
|
193
|
+
description: t.function.description
|
|
194
|
+
})),
|
|
195
|
+
selectedTool: {
|
|
196
|
+
name: selectedTool.function.name,
|
|
197
|
+
description: selectedTool.function.description
|
|
198
|
+
},
|
|
199
|
+
reasoning,
|
|
200
|
+
confidence,
|
|
201
|
+
selectionTime: Date.now()
|
|
202
|
+
},
|
|
203
|
+
metadata: {
|
|
204
|
+
...this.metadata,
|
|
205
|
+
projectName: this.projectName
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
this.addEvent(event);
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Track conversation turn
|
|
212
|
+
*/
|
|
213
|
+
traceConversationTurn(userMessage, assistantResponse, model, tokens, cost) {
|
|
214
|
+
const event = {
|
|
215
|
+
eventId: (0, import_uuid.v4)(),
|
|
216
|
+
traceId: this.traceId,
|
|
217
|
+
timestamp: Date.now(),
|
|
218
|
+
type: "conversation_turn",
|
|
219
|
+
data: {
|
|
220
|
+
userMessage,
|
|
221
|
+
assistantResponse,
|
|
222
|
+
model,
|
|
223
|
+
tokens: tokens || { prompt: 0, completion: 0, total: 0 },
|
|
224
|
+
cost: cost || 0,
|
|
225
|
+
turnNumber: this.getTurnNumber()
|
|
226
|
+
},
|
|
227
|
+
metadata: {
|
|
228
|
+
...this.metadata,
|
|
229
|
+
projectName: this.projectName
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
this.addEvent(event);
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Track error
|
|
236
|
+
*/
|
|
237
|
+
traceError(error, context, functionName, functionArguments) {
|
|
238
|
+
const event = {
|
|
239
|
+
eventId: (0, import_uuid.v4)(),
|
|
240
|
+
traceId: this.traceId,
|
|
241
|
+
timestamp: Date.now(),
|
|
242
|
+
type: "error",
|
|
243
|
+
data: {
|
|
244
|
+
error: {
|
|
245
|
+
message: error.message,
|
|
246
|
+
stack: error.stack,
|
|
247
|
+
name: error.name
|
|
248
|
+
},
|
|
249
|
+
context,
|
|
250
|
+
functionName,
|
|
251
|
+
arguments: functionArguments ? JSON.stringify(functionArguments) : void 0
|
|
252
|
+
},
|
|
253
|
+
metadata: {
|
|
254
|
+
...this.metadata,
|
|
255
|
+
projectName: this.projectName
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
this.addEvent(event);
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Add event to queue
|
|
262
|
+
*/
|
|
263
|
+
addEvent(event) {
|
|
264
|
+
this.eventQueue.push(event);
|
|
265
|
+
this.emit("event", event);
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Flush events to server
|
|
269
|
+
*/
|
|
270
|
+
async flushQueue() {
|
|
271
|
+
if (this.eventQueue.length === 0 || !this.isConnected || !this.client) {
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
const events = [...this.eventQueue];
|
|
275
|
+
this.eventQueue = [];
|
|
276
|
+
try {
|
|
277
|
+
console.log(`[OpenAI Tracer] Flushing ${events.length} events`);
|
|
278
|
+
this.client.emit("openai_events", {
|
|
279
|
+
traceId: this.traceId,
|
|
280
|
+
projectName: this.projectName,
|
|
281
|
+
events,
|
|
282
|
+
metadata: this.metadata
|
|
283
|
+
});
|
|
284
|
+
this.emit("events_sent", events);
|
|
285
|
+
} catch (error) {
|
|
286
|
+
console.error("[OpenAI Tracer] Failed to flush events:", error);
|
|
287
|
+
this.eventQueue.unshift(...events);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Extract conversation context from messages
|
|
292
|
+
*/
|
|
293
|
+
extractConversationContext(messages) {
|
|
294
|
+
return {
|
|
295
|
+
messageCount: messages.length,
|
|
296
|
+
lastUserMessage: messages.filter((m) => m.role === "user").pop()?.content,
|
|
297
|
+
hasSystemMessage: messages.some((m) => m.role === "system"),
|
|
298
|
+
hasToolCalls: messages.some((m) => m.tool_calls && m.tool_calls.length > 0)
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Get current turn number
|
|
303
|
+
*/
|
|
304
|
+
getTurnNumber() {
|
|
305
|
+
return this.eventQueue.filter((e) => e.type === "conversation_turn").length + 1;
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Calculate cost based on tokens and model
|
|
309
|
+
*/
|
|
310
|
+
calculateCost(tokens, model) {
|
|
311
|
+
const pricing = {
|
|
312
|
+
"gpt-4": { prompt: 0.03, completion: 0.06 },
|
|
313
|
+
"gpt-4-turbo": { prompt: 0.01, completion: 0.03 },
|
|
314
|
+
"gpt-3.5-turbo": { prompt: 1e-3, completion: 2e-3 },
|
|
315
|
+
"gpt-3.5-turbo-16k": { prompt: 3e-3, completion: 4e-3 }
|
|
316
|
+
};
|
|
317
|
+
const prices = pricing[model] || pricing["gpt-3.5-turbo"];
|
|
318
|
+
return (tokens.prompt * prices.prompt + tokens.completion * prices.completion) / 1e3;
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Get trace ID
|
|
322
|
+
*/
|
|
323
|
+
getTraceId() {
|
|
324
|
+
return this.traceId;
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Check if connected
|
|
328
|
+
*/
|
|
329
|
+
isConnectedToServer() {
|
|
330
|
+
return this.isConnected;
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Get current event queue size
|
|
334
|
+
*/
|
|
335
|
+
getQueueSize() {
|
|
336
|
+
return this.eventQueue.length;
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Clear event queue
|
|
340
|
+
*/
|
|
341
|
+
clearQueue() {
|
|
342
|
+
this.eventQueue = [];
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Shutdown tracer
|
|
346
|
+
*/
|
|
347
|
+
async shutdown() {
|
|
348
|
+
await this.flushQueue();
|
|
349
|
+
this.disconnect();
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
function createOpenAITracer(config) {
|
|
353
|
+
return new OpenAITracer(config);
|
|
354
|
+
}
|
|
355
|
+
var TracedOpenAI = class {
|
|
356
|
+
constructor(openaiClient, tracer) {
|
|
357
|
+
this.openai = openaiClient;
|
|
358
|
+
this.tracer = tracer;
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Traced chat completion with function calling
|
|
362
|
+
*/
|
|
363
|
+
async createChatCompletion(params) {
|
|
364
|
+
const startTime = Date.now();
|
|
365
|
+
const eventId = this.tracer.traceFunctionCallStart(
|
|
366
|
+
"createChatCompletion",
|
|
367
|
+
params,
|
|
368
|
+
params.model,
|
|
369
|
+
params.messages,
|
|
370
|
+
params.tools
|
|
371
|
+
);
|
|
372
|
+
try {
|
|
373
|
+
const response = await this.openai.chat.completions.create(params);
|
|
374
|
+
const endTime = Date.now();
|
|
375
|
+
const latency = endTime - startTime;
|
|
376
|
+
const tokens = response.usage ? {
|
|
377
|
+
prompt: response.usage.prompt_tokens,
|
|
378
|
+
completion: response.usage.completion_tokens,
|
|
379
|
+
total: response.usage.total_tokens
|
|
380
|
+
} : { prompt: 0, completion: 0, total: 0 };
|
|
381
|
+
const cost = this.tracer.calculateCost(tokens, params.model);
|
|
382
|
+
this.tracer.traceFunctionCallEnd(eventId, response, cost, latency, tokens);
|
|
383
|
+
if (response.choices[0]?.message?.tool_calls) {
|
|
384
|
+
const selectedTool = params.tools?.find(
|
|
385
|
+
(t) => t.function.name === response.choices[0].message.tool_calls[0].function.name
|
|
386
|
+
);
|
|
387
|
+
if (selectedTool) {
|
|
388
|
+
this.tracer.traceToolSelection(
|
|
389
|
+
params.tools || [],
|
|
390
|
+
selectedTool,
|
|
391
|
+
"Model selected tool based on user request",
|
|
392
|
+
0.9
|
|
393
|
+
// High confidence for model selection
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
const userMessage = params.messages.filter((m) => m.role === "user").pop()?.content || "";
|
|
398
|
+
const assistantResponse = response.choices[0]?.message?.content || "";
|
|
399
|
+
if (userMessage && assistantResponse) {
|
|
400
|
+
this.tracer.traceConversationTurn(
|
|
401
|
+
userMessage,
|
|
402
|
+
assistantResponse,
|
|
403
|
+
params.model,
|
|
404
|
+
tokens,
|
|
405
|
+
cost
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
return response;
|
|
409
|
+
} catch (error) {
|
|
410
|
+
this.tracer.traceError(
|
|
411
|
+
error,
|
|
412
|
+
"createChatCompletion",
|
|
413
|
+
"createChatCompletion",
|
|
414
|
+
params
|
|
415
|
+
);
|
|
416
|
+
throw error;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
};
|
|
420
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
421
|
+
0 && (module.exports = {
|
|
422
|
+
OpenAITracer,
|
|
423
|
+
TracedOpenAI,
|
|
424
|
+
createOpenAITracer
|
|
425
|
+
});
|