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