judgeval 0.1.33
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/LICENSE.md +202 -0
- package/README.md +340 -0
- package/dist/clients.d.ts +7 -0
- package/dist/clients.js +78 -0
- package/dist/clients.js.map +1 -0
- package/dist/common/integrations/langgraph.d.ts +40 -0
- package/dist/common/integrations/langgraph.js +444 -0
- package/dist/common/integrations/langgraph.js.map +1 -0
- package/dist/common/logger-instance.d.ts +3 -0
- package/dist/common/logger-instance.js +64 -0
- package/dist/common/logger-instance.js.map +1 -0
- package/dist/common/logger.d.ts +54 -0
- package/dist/common/logger.js +221 -0
- package/dist/common/logger.js.map +1 -0
- package/dist/common/tracer.d.ts +205 -0
- package/dist/common/tracer.js +1035 -0
- package/dist/common/tracer.js.map +1 -0
- package/dist/constants.d.ts +51 -0
- package/dist/constants.js +344 -0
- package/dist/constants.js.map +1 -0
- package/dist/data/example.d.ts +70 -0
- package/dist/data/example.js +125 -0
- package/dist/data/example.js.map +1 -0
- package/dist/data/result.d.ts +51 -0
- package/dist/data/result.js +83 -0
- package/dist/data/result.js.map +1 -0
- package/dist/evaluation-run.d.ts +44 -0
- package/dist/evaluation-run.js +136 -0
- package/dist/evaluation-run.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +73 -0
- package/dist/index.js.map +1 -0
- package/dist/judgment-client.d.ts +179 -0
- package/dist/judgment-client.js +1038 -0
- package/dist/judgment-client.js.map +1 -0
- package/dist/rules.d.ts +120 -0
- package/dist/rules.js +322 -0
- package/dist/rules.js.map +1 -0
- package/dist/run-evaluation.d.ts +78 -0
- package/dist/run-evaluation.js +618 -0
- package/dist/run-evaluation.js.map +1 -0
- package/dist/scorers/api-scorer.d.ts +79 -0
- package/dist/scorers/api-scorer.js +291 -0
- package/dist/scorers/api-scorer.js.map +1 -0
- package/dist/scorers/base-scorer.d.ts +100 -0
- package/dist/scorers/base-scorer.js +190 -0
- package/dist/scorers/base-scorer.js.map +1 -0
- package/dist/scorers/exact-match-scorer.d.ts +10 -0
- package/dist/scorers/exact-match-scorer.js +84 -0
- package/dist/scorers/exact-match-scorer.js.map +1 -0
- package/package.json +88 -0
|
@@ -0,0 +1,1035 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.currentSpanAsyncLocalStorage = exports.currentTraceAsyncLocalStorage = exports.TraceManagerClient = exports.TraceClient = exports.Tracer = exports.wrap = void 0;
|
|
16
|
+
// Core Node.js imports
|
|
17
|
+
const async_hooks_1 = require("async_hooks");
|
|
18
|
+
const uuid_1 = require("uuid");
|
|
19
|
+
// Installed SDKs
|
|
20
|
+
const openai_1 = __importDefault(require("openai"));
|
|
21
|
+
const sdk_1 = __importDefault(require("@anthropic-ai/sdk"));
|
|
22
|
+
const together_ai_1 = __importDefault(require("together-ai")); // Use default import
|
|
23
|
+
// Local Imports
|
|
24
|
+
const constants_1 = require("../constants");
|
|
25
|
+
const logger_instance_1 = __importDefault(require("./logger-instance")); // Use the shared winston logger instance
|
|
26
|
+
// --- API Interaction Client ---
|
|
27
|
+
/**
|
|
28
|
+
* Client for interacting with Judgment trace API endpoints.
|
|
29
|
+
*/
|
|
30
|
+
class TraceManagerClient {
|
|
31
|
+
constructor(apiKey, organizationId) {
|
|
32
|
+
if (!apiKey) {
|
|
33
|
+
throw new Error("TraceManagerClient requires a Judgment API key.");
|
|
34
|
+
}
|
|
35
|
+
if (!organizationId) {
|
|
36
|
+
throw new Error("TraceManagerClient requires a Judgment Organization ID.");
|
|
37
|
+
}
|
|
38
|
+
this.apiKey = apiKey;
|
|
39
|
+
this.organizationId = organizationId;
|
|
40
|
+
}
|
|
41
|
+
_fetch(url_1) {
|
|
42
|
+
return __awaiter(this, arguments, void 0, function* (url, options = {}) {
|
|
43
|
+
const headers = Object.assign({ 'Content-Type': 'application/json', 'Authorization': `Bearer ${this.apiKey}`, 'X-Organization-Id': this.organizationId }, (options.headers || {}));
|
|
44
|
+
try {
|
|
45
|
+
// Use isomorphic fetch (available globally in modern Node.js and browsers)
|
|
46
|
+
const response = yield fetch(url, Object.assign(Object.assign({}, options), { headers: headers }));
|
|
47
|
+
if (!response.ok) {
|
|
48
|
+
const errorBody = yield response.text();
|
|
49
|
+
console.error(`API Error (${response.status}) for ${options.method || 'GET'} ${url}: ${errorBody}`);
|
|
50
|
+
throw new Error(`Judgment API request failed: ${response.status} ${response.statusText} - ${errorBody}`);
|
|
51
|
+
}
|
|
52
|
+
// Handle cases where the response might be empty (e.g., 204 No Content on DELETE)
|
|
53
|
+
if (response.status === 204) {
|
|
54
|
+
return null; // Indicate success with no content
|
|
55
|
+
}
|
|
56
|
+
return yield response.json();
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
console.error(`Network or fetch error during ${options.method || 'GET'} ${url}:`, error);
|
|
60
|
+
// Re-throw or handle as appropriate for the application context
|
|
61
|
+
throw error;
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
fetchTrace(traceId) {
|
|
66
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
67
|
+
return this._fetch(constants_1.JUDGMENT_TRACES_FETCH_API_URL, {
|
|
68
|
+
method: 'POST',
|
|
69
|
+
body: JSON.stringify({ trace_id: traceId }),
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
saveTrace(traceData, emptySave) {
|
|
74
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
75
|
+
const response = yield this._fetch(constants_1.JUDGMENT_TRACES_SAVE_API_URL, {
|
|
76
|
+
method: 'POST',
|
|
77
|
+
body: JSON.stringify(traceData),
|
|
78
|
+
});
|
|
79
|
+
// Optionally log the UI URL like the Python version
|
|
80
|
+
if (!emptySave && (response === null || response === void 0 ? void 0 : response.ui_results_url)) {
|
|
81
|
+
// Use console.info or a dedicated logger for user-facing messages
|
|
82
|
+
// Note: We can't replicate Rich library's colored link easily in standard console
|
|
83
|
+
console.info(`
|
|
84
|
+
🔍 View trace: ${response.ui_results_url}
|
|
85
|
+
`);
|
|
86
|
+
}
|
|
87
|
+
return response;
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
deleteTrace(traceId) {
|
|
91
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
92
|
+
// Assuming DELETE method is correct based on REST principles for the delete endpoint
|
|
93
|
+
return this._fetch(constants_1.JUDGMENT_TRACES_DELETE_API_URL, {
|
|
94
|
+
method: 'DELETE',
|
|
95
|
+
body: JSON.stringify({ trace_ids: [traceId] }),
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
deleteTraces(traceIds) {
|
|
100
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
101
|
+
return this._fetch(constants_1.JUDGMENT_TRACES_DELETE_API_URL, {
|
|
102
|
+
method: 'DELETE',
|
|
103
|
+
body: JSON.stringify({ trace_ids: traceIds }),
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
addTraceToEvalQueue(traceData) {
|
|
108
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
109
|
+
// Ensure traceData has the necessary structure for the queue endpoint
|
|
110
|
+
return this._fetch(constants_1.JUDGMENT_TRACES_ADD_TO_EVAL_QUEUE_API_URL, {
|
|
111
|
+
method: 'POST',
|
|
112
|
+
body: JSON.stringify(traceData), // Send the full trace data as per Python impl
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
exports.TraceManagerClient = TraceManagerClient;
|
|
118
|
+
// --- Context Management ---
|
|
119
|
+
// Holds the active TraceClient instance for the current async context
|
|
120
|
+
const currentTraceAsyncLocalStorage = new async_hooks_1.AsyncLocalStorage();
|
|
121
|
+
exports.currentTraceAsyncLocalStorage = currentTraceAsyncLocalStorage;
|
|
122
|
+
// Holds the ID of the currently active span within a trace
|
|
123
|
+
const currentSpanAsyncLocalStorage = new async_hooks_1.AsyncLocalStorage();
|
|
124
|
+
exports.currentSpanAsyncLocalStorage = currentSpanAsyncLocalStorage;
|
|
125
|
+
// --- Helper Functions ---
|
|
126
|
+
// Helper function to sanitize names (e.g., replace spaces with underscores)
|
|
127
|
+
function sanitizeName(name) {
|
|
128
|
+
// Replace spaces with underscores and remove potentially problematic characters
|
|
129
|
+
// You can adjust the regex further if other characters cause issues.
|
|
130
|
+
return name.replace(/\s+/g, '_').replace(/[^a-zA-Z0-9_.-]/g, '');
|
|
131
|
+
}
|
|
132
|
+
// --- Core Trace Classes ---
|
|
133
|
+
/**
|
|
134
|
+
* Represents an ongoing trace context.
|
|
135
|
+
*/
|
|
136
|
+
class TraceClient {
|
|
137
|
+
constructor(config) {
|
|
138
|
+
var _a, _b, _c, _d, _e;
|
|
139
|
+
// Internal state
|
|
140
|
+
this.entries = [];
|
|
141
|
+
this.spanDepths = {};
|
|
142
|
+
this.traceManager = null; // Can be null if monitoring disabled
|
|
143
|
+
this.traceId = config.traceId || (0, uuid_1.v4)();
|
|
144
|
+
this.originalName = config.name || 'default_trace'; // Store original
|
|
145
|
+
this.name = sanitizeName(this.originalName); // Use sanitized name internally
|
|
146
|
+
// If the sanitized name is empty, fallback to a default
|
|
147
|
+
if (!this.name) {
|
|
148
|
+
console.warn(`Original trace name "${this.originalName}" sanitized to empty string. Using default_trace_${this.traceId.substring(0, 8)}.`);
|
|
149
|
+
this.name = `default_trace_${this.traceId.substring(0, 8)}`;
|
|
150
|
+
}
|
|
151
|
+
this.projectName = (_a = config.projectName) !== null && _a !== void 0 ? _a : config.tracer.projectName;
|
|
152
|
+
this.overwrite = (_b = config.overwrite) !== null && _b !== void 0 ? _b : false;
|
|
153
|
+
this.rules = (_c = config.rules) !== null && _c !== void 0 ? _c : [];
|
|
154
|
+
// Determine effective monitoring status based on tracer and API keys
|
|
155
|
+
let effectiveMonitoring = (_d = config.enableMonitoring) !== null && _d !== void 0 ? _d : config.tracer.enableMonitoring;
|
|
156
|
+
if (effectiveMonitoring && (!config.apiKey || !config.organizationId)) {
|
|
157
|
+
console.warn(`TraceClient ${this.traceId}: Monitoring requires JUDGMENT_API_KEY and JUDGMENT_ORG_ID. Disabling monitoring for this trace.`);
|
|
158
|
+
effectiveMonitoring = false;
|
|
159
|
+
}
|
|
160
|
+
this.enableMonitoring = effectiveMonitoring;
|
|
161
|
+
// Evaluations depend on monitoring
|
|
162
|
+
this.enableEvaluations = effectiveMonitoring && ((_e = config.enableEvaluations) !== null && _e !== void 0 ? _e : config.tracer.enableEvaluations);
|
|
163
|
+
this.parentTraceId = config.parentTraceId;
|
|
164
|
+
this.parentName = config.parentName;
|
|
165
|
+
this.apiKey = config.apiKey;
|
|
166
|
+
this.organizationId = config.organizationId;
|
|
167
|
+
this.startTime = Date.now() / 1000;
|
|
168
|
+
if (this.enableMonitoring) {
|
|
169
|
+
this.traceManager = new TraceManagerClient(this.apiKey, this.organizationId);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
addEntry(entry) {
|
|
173
|
+
if (!this.enableMonitoring)
|
|
174
|
+
return;
|
|
175
|
+
entry.timestamp = entry.timestamp || Date.now() / 1000;
|
|
176
|
+
this.entries.push(entry);
|
|
177
|
+
}
|
|
178
|
+
recordInput(inputs) {
|
|
179
|
+
var _a, _b;
|
|
180
|
+
const spanId = currentSpanAsyncLocalStorage.getStore();
|
|
181
|
+
if (!spanId || !this.enableMonitoring)
|
|
182
|
+
return;
|
|
183
|
+
const enterEntry = this.entries.find(e => e.span_id === spanId && e.type === 'enter');
|
|
184
|
+
const functionName = (enterEntry === null || enterEntry === void 0 ? void 0 : enterEntry.function) || 'unknown_function';
|
|
185
|
+
const depth = (_b = (_a = enterEntry === null || enterEntry === void 0 ? void 0 : enterEntry.depth) !== null && _a !== void 0 ? _a : this.spanDepths[spanId]) !== null && _b !== void 0 ? _b : 0;
|
|
186
|
+
const spanType = (enterEntry === null || enterEntry === void 0 ? void 0 : enterEntry.span_type) || 'span';
|
|
187
|
+
this.addEntry({
|
|
188
|
+
type: 'input',
|
|
189
|
+
span_id: spanId,
|
|
190
|
+
inputs,
|
|
191
|
+
function: functionName,
|
|
192
|
+
depth: depth,
|
|
193
|
+
span_type: spanType
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
recordOutput(output) {
|
|
197
|
+
var _a, _b, _c;
|
|
198
|
+
const spanId = currentSpanAsyncLocalStorage.getStore();
|
|
199
|
+
if (!spanId || !this.enableMonitoring)
|
|
200
|
+
return;
|
|
201
|
+
const enterEntry = this.entries.find(e => e.span_id === spanId && e.type === 'enter');
|
|
202
|
+
const functionName = (enterEntry === null || enterEntry === void 0 ? void 0 : enterEntry.function) || 'unknown_function';
|
|
203
|
+
const depth = (_b = (_a = enterEntry === null || enterEntry === void 0 ? void 0 : enterEntry.depth) !== null && _a !== void 0 ? _a : this.spanDepths[spanId]) !== null && _b !== void 0 ? _b : 0;
|
|
204
|
+
const spanType = (enterEntry === null || enterEntry === void 0 ? void 0 : enterEntry.span_type) || 'span';
|
|
205
|
+
let entryType = 'output';
|
|
206
|
+
let outputData = output;
|
|
207
|
+
if (output instanceof Error) {
|
|
208
|
+
entryType = 'error';
|
|
209
|
+
outputData = {
|
|
210
|
+
name: output.name,
|
|
211
|
+
message: output.message,
|
|
212
|
+
stack: (_c = output.stack) === null || _c === void 0 ? void 0 : _c.substring(0, 1000)
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
else if (output === null || output === void 0 ? void 0 : output.error) {
|
|
216
|
+
entryType = 'error';
|
|
217
|
+
outputData = output;
|
|
218
|
+
}
|
|
219
|
+
this.addEntry({
|
|
220
|
+
type: entryType,
|
|
221
|
+
span_id: spanId,
|
|
222
|
+
output: outputData,
|
|
223
|
+
function: functionName,
|
|
224
|
+
depth: depth,
|
|
225
|
+
span_type: spanType
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
runInSpan(name, options, func) {
|
|
229
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
230
|
+
var _a;
|
|
231
|
+
if (!this.enableMonitoring) {
|
|
232
|
+
const result = func();
|
|
233
|
+
return result instanceof Promise ? yield result : result;
|
|
234
|
+
}
|
|
235
|
+
const startTime = Date.now() / 1000;
|
|
236
|
+
const spanId = (0, uuid_1.v4)();
|
|
237
|
+
const parentSpanId = currentSpanAsyncLocalStorage.getStore();
|
|
238
|
+
const spanType = options.spanType || 'span';
|
|
239
|
+
let currentDepth = 0;
|
|
240
|
+
if (parentSpanId && this.spanDepths[parentSpanId] !== undefined) {
|
|
241
|
+
currentDepth = this.spanDepths[parentSpanId] + 1;
|
|
242
|
+
}
|
|
243
|
+
this.spanDepths[spanId] = currentDepth;
|
|
244
|
+
this.addEntry({
|
|
245
|
+
type: 'enter',
|
|
246
|
+
function: name,
|
|
247
|
+
span_id: spanId,
|
|
248
|
+
depth: currentDepth,
|
|
249
|
+
timestamp: startTime,
|
|
250
|
+
span_type: spanType,
|
|
251
|
+
parent_span_id: parentSpanId
|
|
252
|
+
});
|
|
253
|
+
let result;
|
|
254
|
+
try {
|
|
255
|
+
result = yield currentSpanAsyncLocalStorage.run(spanId, () => __awaiter(this, void 0, void 0, function* () {
|
|
256
|
+
const res = func();
|
|
257
|
+
return res instanceof Promise ? yield res : res;
|
|
258
|
+
}));
|
|
259
|
+
return result;
|
|
260
|
+
}
|
|
261
|
+
catch (error) {
|
|
262
|
+
this.recordOutput({
|
|
263
|
+
error: String(error),
|
|
264
|
+
error_details: error instanceof Error
|
|
265
|
+
? { name: error.name, message: error.message, stack: (_a = error.stack) === null || _a === void 0 ? void 0 : _a.substring(0, 500) }
|
|
266
|
+
: { detail: String(error) }
|
|
267
|
+
});
|
|
268
|
+
throw error;
|
|
269
|
+
}
|
|
270
|
+
finally {
|
|
271
|
+
const endTime = Date.now() / 1000;
|
|
272
|
+
const duration = endTime - startTime;
|
|
273
|
+
this.addEntry({
|
|
274
|
+
type: 'exit',
|
|
275
|
+
function: name,
|
|
276
|
+
span_id: spanId,
|
|
277
|
+
depth: currentDepth,
|
|
278
|
+
timestamp: endTime,
|
|
279
|
+
duration: duration,
|
|
280
|
+
span_type: spanType
|
|
281
|
+
});
|
|
282
|
+
delete this.spanDepths[spanId];
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
getDuration() {
|
|
287
|
+
return (Date.now() / 1000) - this.startTime;
|
|
288
|
+
}
|
|
289
|
+
condenseTrace(rawEntries) {
|
|
290
|
+
var _a, _b, _c, _d, _e;
|
|
291
|
+
const spansById = {};
|
|
292
|
+
for (const entry of rawEntries) {
|
|
293
|
+
const spanId = entry.span_id;
|
|
294
|
+
if (!spanId)
|
|
295
|
+
continue;
|
|
296
|
+
if (!spansById[spanId]) {
|
|
297
|
+
spansById[spanId] = {
|
|
298
|
+
span_id: spanId,
|
|
299
|
+
function: entry.function || 'unknown',
|
|
300
|
+
depth: (_a = entry.depth) !== null && _a !== void 0 ? _a : 0,
|
|
301
|
+
timestamp: (_b = entry.timestamp) !== null && _b !== void 0 ? _b : 0,
|
|
302
|
+
parent_span_id: entry.parent_span_id,
|
|
303
|
+
span_type: entry.span_type || 'span',
|
|
304
|
+
inputs: null,
|
|
305
|
+
output: null,
|
|
306
|
+
evaluation_runs: [],
|
|
307
|
+
duration: null,
|
|
308
|
+
children: []
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
const currentSpanData = spansById[spanId];
|
|
312
|
+
switch (entry.type) {
|
|
313
|
+
case 'enter':
|
|
314
|
+
currentSpanData.function = entry.function || currentSpanData.function;
|
|
315
|
+
currentSpanData.depth = (_c = entry.depth) !== null && _c !== void 0 ? _c : currentSpanData.depth;
|
|
316
|
+
currentSpanData.timestamp = (_d = entry.timestamp) !== null && _d !== void 0 ? _d : currentSpanData.timestamp;
|
|
317
|
+
currentSpanData.parent_span_id = entry.parent_span_id;
|
|
318
|
+
currentSpanData.span_type = entry.span_type || currentSpanData.span_type;
|
|
319
|
+
currentSpanData.start_time = entry.timestamp;
|
|
320
|
+
break;
|
|
321
|
+
case 'exit':
|
|
322
|
+
currentSpanData.duration = (_e = entry.duration) !== null && _e !== void 0 ? _e : currentSpanData.duration;
|
|
323
|
+
currentSpanData.end_time = entry.timestamp;
|
|
324
|
+
if (currentSpanData.duration === null && currentSpanData.start_time && currentSpanData.end_time) {
|
|
325
|
+
currentSpanData.duration = currentSpanData.end_time - currentSpanData.start_time;
|
|
326
|
+
}
|
|
327
|
+
break;
|
|
328
|
+
case 'input':
|
|
329
|
+
if (currentSpanData.inputs === null && entry.inputs) {
|
|
330
|
+
currentSpanData.inputs = entry.inputs;
|
|
331
|
+
}
|
|
332
|
+
else if (typeof currentSpanData.inputs === 'object' && typeof entry.inputs === 'object') {
|
|
333
|
+
currentSpanData.inputs = Object.assign(Object.assign({}, currentSpanData.inputs), entry.inputs);
|
|
334
|
+
}
|
|
335
|
+
break;
|
|
336
|
+
case 'output':
|
|
337
|
+
case 'error':
|
|
338
|
+
currentSpanData.output = entry.output;
|
|
339
|
+
break;
|
|
340
|
+
case 'evaluation':
|
|
341
|
+
if (entry.evaluation_runs) {
|
|
342
|
+
currentSpanData.evaluation_runs.push(...entry.evaluation_runs);
|
|
343
|
+
}
|
|
344
|
+
break;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
const spansList = Object.values(spansById).map(span => {
|
|
348
|
+
if (span.duration === null && span.start_time && span.end_time) {
|
|
349
|
+
span.duration = span.end_time - span.start_time;
|
|
350
|
+
}
|
|
351
|
+
delete span.start_time;
|
|
352
|
+
delete span.end_time;
|
|
353
|
+
return span;
|
|
354
|
+
});
|
|
355
|
+
const childrenMap = {};
|
|
356
|
+
const roots = [];
|
|
357
|
+
const spanMap = {};
|
|
358
|
+
const sortedCondensedList = [];
|
|
359
|
+
const visited = new Set();
|
|
360
|
+
for (const span of spansList) {
|
|
361
|
+
spanMap[span.span_id] = span;
|
|
362
|
+
const parentId = span.parent_span_id;
|
|
363
|
+
if (parentId === undefined || parentId === null) {
|
|
364
|
+
roots.push(span);
|
|
365
|
+
}
|
|
366
|
+
else {
|
|
367
|
+
if (!childrenMap[parentId]) {
|
|
368
|
+
childrenMap[parentId] = [];
|
|
369
|
+
}
|
|
370
|
+
childrenMap[parentId].push(span);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
roots.sort((a, b) => a.timestamp - b.timestamp);
|
|
374
|
+
for (const parentId in childrenMap) {
|
|
375
|
+
childrenMap[parentId].sort((a, b) => a.timestamp - b.timestamp);
|
|
376
|
+
}
|
|
377
|
+
function buildFlatListDfs(span) {
|
|
378
|
+
if (visited.has(span.span_id))
|
|
379
|
+
return;
|
|
380
|
+
visited.add(span.span_id);
|
|
381
|
+
sortedCondensedList.push(span);
|
|
382
|
+
const children = childrenMap[span.span_id] || [];
|
|
383
|
+
for (const child of children) {
|
|
384
|
+
buildFlatListDfs(child);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
for (const rootSpan of roots) {
|
|
388
|
+
buildFlatListDfs(rootSpan);
|
|
389
|
+
}
|
|
390
|
+
for (const span of spansList) {
|
|
391
|
+
if (!visited.has(span.span_id)) {
|
|
392
|
+
console.warn(`Orphaned span detected: ${span.span_id}, adding to end of list.`);
|
|
393
|
+
buildFlatListDfs(span);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
return sortedCondensedList;
|
|
397
|
+
}
|
|
398
|
+
save() {
|
|
399
|
+
return __awaiter(this, arguments, void 0, function* (emptySave = false) {
|
|
400
|
+
if (!this.enableMonitoring || !this.traceManager) {
|
|
401
|
+
return null;
|
|
402
|
+
}
|
|
403
|
+
const totalDuration = this.getDuration();
|
|
404
|
+
const condensedEntries = this.condenseTrace(this.entries);
|
|
405
|
+
const tokenCounts = {
|
|
406
|
+
prompt_tokens: 0, completion_tokens: 0, total_tokens: 0,
|
|
407
|
+
prompt_tokens_cost_usd: 0.0, completion_tokens_cost_usd: 0.0, total_cost_usd: 0.0
|
|
408
|
+
};
|
|
409
|
+
condensedEntries.forEach(entry => {
|
|
410
|
+
var _a, _b;
|
|
411
|
+
if (entry.span_type === 'llm' && ((_a = entry.output) === null || _a === void 0 ? void 0 : _a.usage)) {
|
|
412
|
+
const usage = entry.output.usage;
|
|
413
|
+
let promptTokens = 0;
|
|
414
|
+
let completionTokens = 0;
|
|
415
|
+
if (usage.prompt_tokens !== undefined || usage.completion_tokens !== undefined) {
|
|
416
|
+
promptTokens = usage.prompt_tokens || 0;
|
|
417
|
+
completionTokens = usage.completion_tokens || 0;
|
|
418
|
+
}
|
|
419
|
+
else if (usage.input_tokens !== undefined || usage.output_tokens !== undefined) {
|
|
420
|
+
promptTokens = usage.input_tokens || 0;
|
|
421
|
+
completionTokens = usage.output_tokens || 0;
|
|
422
|
+
usage.prompt_tokens = promptTokens;
|
|
423
|
+
usage.completion_tokens = completionTokens;
|
|
424
|
+
delete usage.input_tokens;
|
|
425
|
+
delete usage.output_tokens;
|
|
426
|
+
}
|
|
427
|
+
tokenCounts.prompt_tokens += promptTokens;
|
|
428
|
+
tokenCounts.completion_tokens += completionTokens;
|
|
429
|
+
tokenCounts.total_tokens += usage.total_tokens || (promptTokens + completionTokens);
|
|
430
|
+
const modelName = ((_b = entry.inputs) === null || _b === void 0 ? void 0 : _b.model) || "";
|
|
431
|
+
if (modelName) {
|
|
432
|
+
try {
|
|
433
|
+
const promptCost = 0.0;
|
|
434
|
+
const completionCost = 0.0;
|
|
435
|
+
const callTotalCost = promptCost + completionCost;
|
|
436
|
+
usage.prompt_tokens_cost_usd = promptCost;
|
|
437
|
+
usage.completion_tokens_cost_usd = completionCost;
|
|
438
|
+
usage.total_cost_usd = callTotalCost;
|
|
439
|
+
tokenCounts.prompt_tokens_cost_usd += promptCost;
|
|
440
|
+
tokenCounts.completion_tokens_cost_usd += completionCost;
|
|
441
|
+
tokenCounts.total_cost_usd += callTotalCost;
|
|
442
|
+
}
|
|
443
|
+
catch (e) {
|
|
444
|
+
console.warn(`Error calculating cost for model '${modelName}':`, e);
|
|
445
|
+
usage.prompt_tokens_cost_usd = null;
|
|
446
|
+
usage.completion_tokens_cost_usd = null;
|
|
447
|
+
usage.total_cost_usd = null;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
else {
|
|
451
|
+
usage.prompt_tokens_cost_usd = null;
|
|
452
|
+
usage.completion_tokens_cost_usd = null;
|
|
453
|
+
usage.total_cost_usd = null;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
});
|
|
457
|
+
// Convert rules array to a dictionary (Record<string, Rule>)
|
|
458
|
+
const rulesDict = {};
|
|
459
|
+
this.rules.forEach(rule => {
|
|
460
|
+
var _a;
|
|
461
|
+
// Use rule_id if available, otherwise fallback to name
|
|
462
|
+
const key = (_a = rule.rule_id) !== null && _a !== void 0 ? _a : rule.name;
|
|
463
|
+
rulesDict[key] = rule;
|
|
464
|
+
});
|
|
465
|
+
const traceData = {
|
|
466
|
+
trace_id: this.traceId,
|
|
467
|
+
name: this.name,
|
|
468
|
+
project_name: this.projectName,
|
|
469
|
+
created_at: new Date(this.startTime * 1000).toISOString(),
|
|
470
|
+
duration: totalDuration,
|
|
471
|
+
token_counts: tokenCounts,
|
|
472
|
+
entries: condensedEntries,
|
|
473
|
+
rules: rulesDict,
|
|
474
|
+
empty_save: emptySave,
|
|
475
|
+
overwrite: this.overwrite,
|
|
476
|
+
parent_trace_id: this.parentTraceId,
|
|
477
|
+
parent_name: this.parentName
|
|
478
|
+
};
|
|
479
|
+
try {
|
|
480
|
+
yield this.traceManager.saveTrace(traceData, emptySave);
|
|
481
|
+
logger_instance_1.default.info(`Trace ${this.traceId} saved successfully.`);
|
|
482
|
+
if (!emptySave && this.enableEvaluations) {
|
|
483
|
+
try {
|
|
484
|
+
yield this.traceManager.addTraceToEvalQueue(traceData);
|
|
485
|
+
logger_instance_1.default.info(`Trace ${this.traceId} added to evaluation queue.`);
|
|
486
|
+
}
|
|
487
|
+
catch (evalError) {
|
|
488
|
+
logger_instance_1.default.warn(`Failed to add trace ${this.traceId} to evaluation queue.`, { error: evalError instanceof Error ? evalError.message : String(evalError) });
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
return { traceId: this.traceId, traceData };
|
|
492
|
+
}
|
|
493
|
+
catch (error) {
|
|
494
|
+
logger_instance_1.default.error(`Failed to save trace ${this.traceId}.`, { error: error instanceof Error ? error.message : String(error) });
|
|
495
|
+
return null;
|
|
496
|
+
}
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
print() {
|
|
500
|
+
if (!this.enableMonitoring) {
|
|
501
|
+
// Keep console.log for direct user output when print() is called
|
|
502
|
+
console.log("Monitoring was disabled. No trace entries recorded.");
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
if (this.entries.length === 0) {
|
|
506
|
+
// Keep console.log for direct user output when print() is called
|
|
507
|
+
console.log("No trace entries recorded.");
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
// Keep console.log for direct user output when print() is called
|
|
511
|
+
console.log(`\n--- Trace Details: ${this.name} (ID: ${this.traceId}) ---`);
|
|
512
|
+
this.entries.forEach(entry => {
|
|
513
|
+
var _a;
|
|
514
|
+
const indent = " ".repeat((_a = entry.depth) !== null && _a !== void 0 ? _a : 0);
|
|
515
|
+
const timeStr = entry.timestamp ? `@ ${new Date(entry.timestamp * 1000).toISOString()}` : '';
|
|
516
|
+
const shortSpanId = entry.span_id ? `(id: ${entry.span_id.substring(0, 8)}...)` : '';
|
|
517
|
+
const shortParentId = entry.parent_span_id ? `(parent: ${entry.parent_span_id.substring(0, 8)}...)` : '';
|
|
518
|
+
try {
|
|
519
|
+
switch (entry.type) {
|
|
520
|
+
case 'enter':
|
|
521
|
+
console.log(`${indent}→ ${entry.function || 'unknown'} ${shortSpanId} ${shortParentId} [${entry.span_type || 'span'}] ${timeStr}`);
|
|
522
|
+
break;
|
|
523
|
+
case 'exit':
|
|
524
|
+
const durationStr = entry.duration !== undefined ? `(${entry.duration.toFixed(3)}s)` : '';
|
|
525
|
+
// Keep console.log
|
|
526
|
+
console.log(`${indent}← ${entry.function || 'unknown'} ${shortSpanId} ${durationStr} ${timeStr}`);
|
|
527
|
+
break;
|
|
528
|
+
case 'input':
|
|
529
|
+
let inputStr = JSON.stringify(entry.inputs);
|
|
530
|
+
if (inputStr && inputStr.length > 200) {
|
|
531
|
+
inputStr = inputStr.substring(0, 197) + '...';
|
|
532
|
+
}
|
|
533
|
+
// Keep console.log
|
|
534
|
+
console.log(`${indent} Input (for ${shortSpanId}): ${inputStr || '{}'}`);
|
|
535
|
+
break;
|
|
536
|
+
case 'output':
|
|
537
|
+
case 'error':
|
|
538
|
+
let outputStr = JSON.stringify(entry.output);
|
|
539
|
+
if (outputStr && outputStr.length > 200) {
|
|
540
|
+
outputStr = outputStr.substring(0, 197) + '...';
|
|
541
|
+
}
|
|
542
|
+
const prefix = entry.type === 'error' ? 'Error' : 'Output';
|
|
543
|
+
// Keep console.log
|
|
544
|
+
console.log(`${indent} ${prefix} (for ${shortSpanId}): ${outputStr || 'null'}`);
|
|
545
|
+
break;
|
|
546
|
+
case 'evaluation':
|
|
547
|
+
let evalStr = JSON.stringify(entry.evaluation_runs);
|
|
548
|
+
if (evalStr && evalStr.length > 200) {
|
|
549
|
+
evalStr = evalStr.substring(0, 197) + '...';
|
|
550
|
+
}
|
|
551
|
+
// Keep console.log
|
|
552
|
+
console.log(`${indent} Evaluation (for ${shortSpanId}): ${evalStr || '[]'}`);
|
|
553
|
+
break;
|
|
554
|
+
default:
|
|
555
|
+
// Keep console.log
|
|
556
|
+
console.log(`${indent}? Unknown entry type: ${JSON.stringify(entry)}`);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
catch (stringifyError) {
|
|
560
|
+
const errorMessage = stringifyError instanceof Error ? stringifyError.message : String(stringifyError);
|
|
561
|
+
// Keep console.log
|
|
562
|
+
console.log(`${indent}! Error formatting entry: ${errorMessage}`);
|
|
563
|
+
console.log(`${indent} Raw entry:`, entry);
|
|
564
|
+
}
|
|
565
|
+
});
|
|
566
|
+
// Keep console.log
|
|
567
|
+
console.log(`--- End Trace: ${this.name} ---`);
|
|
568
|
+
}
|
|
569
|
+
delete() {
|
|
570
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
571
|
+
if (!this.enableMonitoring || !this.traceManager) {
|
|
572
|
+
logger_instance_1.default.warn(`Cannot delete trace ${this.traceId}, monitoring disabled or manager missing.`);
|
|
573
|
+
return null;
|
|
574
|
+
}
|
|
575
|
+
try {
|
|
576
|
+
const result = yield this.traceManager.deleteTrace(this.traceId);
|
|
577
|
+
logger_instance_1.default.info(`Trace ${this.traceId} deleted successfully.`);
|
|
578
|
+
return result;
|
|
579
|
+
}
|
|
580
|
+
catch (error) {
|
|
581
|
+
logger_instance_1.default.error(`Failed to delete trace ${this.traceId}.`, { error: error instanceof Error ? error.message : String(error) });
|
|
582
|
+
throw error; // Re-throw after logging
|
|
583
|
+
}
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* Asynchronously evaluate an example using the provided scorers,
|
|
588
|
+
* embedding the evaluation request into the trace data.
|
|
589
|
+
* Ported from the Python SDK's async_evaluate method.
|
|
590
|
+
*
|
|
591
|
+
* @param scorers Array of scorers to use for evaluation (currently assumes APIJudgmentScorer)
|
|
592
|
+
* @param options Evaluation options including input, outputs, and metadata
|
|
593
|
+
* @returns Promise that resolves when the evaluation entry has been added to the trace
|
|
594
|
+
*/
|
|
595
|
+
asyncEvaluate(scorers_1) {
|
|
596
|
+
return __awaiter(this, arguments, void 0, function* (
|
|
597
|
+
// TODO: Allow JudgevalScorer type if rules are not used?
|
|
598
|
+
scorers, options = {}) {
|
|
599
|
+
var _a;
|
|
600
|
+
if (!this.enableEvaluations) {
|
|
601
|
+
logger_instance_1.default.warn("Evaluations are disabled. Skipping async evaluation.");
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
if (!scorers || scorers.length === 0) {
|
|
605
|
+
logger_instance_1.default.warn("No scorers provided. Skipping async evaluation.");
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
const startTime = Date.now() / 1000; // Record start time in seconds
|
|
609
|
+
// Create example structure matching Python/backend expectations
|
|
610
|
+
const example = {
|
|
611
|
+
input: options.input || "",
|
|
612
|
+
actual_output: options.actualOutput || "",
|
|
613
|
+
expected_output: options.expectedOutput || "",
|
|
614
|
+
context: options.context || [],
|
|
615
|
+
retrieval_context: options.retrievalContext || [],
|
|
616
|
+
tools_called: options.toolsCalled || [],
|
|
617
|
+
expected_tools: options.expectedTools || [],
|
|
618
|
+
additional_metadata: options.additionalMetadata || {},
|
|
619
|
+
trace_id: this.traceId
|
|
620
|
+
};
|
|
621
|
+
try {
|
|
622
|
+
// Get the current span ID from the context
|
|
623
|
+
const currentSpanId = currentSpanAsyncLocalStorage.getStore();
|
|
624
|
+
if (!currentSpanId) {
|
|
625
|
+
logger_instance_1.default.warn("No active span found for async evaluation. Evaluation will not be associated with a specific step.");
|
|
626
|
+
// Decide if we should proceed or return. For now, proceed without span association.
|
|
627
|
+
// return;
|
|
628
|
+
}
|
|
629
|
+
// Determine function name and depth (best effort if spanId is missing)
|
|
630
|
+
let functionName = "unknown_function";
|
|
631
|
+
let entrySpanType = "evaluation";
|
|
632
|
+
let currentDepth = 0;
|
|
633
|
+
if (currentSpanId) {
|
|
634
|
+
currentDepth = (_a = this.spanDepths[currentSpanId]) !== null && _a !== void 0 ? _a : 0;
|
|
635
|
+
for (let i = this.entries.length - 1; i >= 0; i--) {
|
|
636
|
+
const entry = this.entries[i];
|
|
637
|
+
if (entry.span_id === currentSpanId && entry.type === 'enter') {
|
|
638
|
+
functionName = entry.function || "unknown_function";
|
|
639
|
+
// Keep span_type as 'evaluation' for the entry itself
|
|
640
|
+
break;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
// --- Create evaluation run name (similar to Python) ---
|
|
645
|
+
// Capitalize scorer names
|
|
646
|
+
const scorerNames = scorers.map(scorer => {
|
|
647
|
+
var _a;
|
|
648
|
+
// Attempt to get score_type, fallback to class name or Unknown
|
|
649
|
+
const name = (scorer === null || scorer === void 0 ? void 0 : scorer.scoreType) || ((_a = scorer === null || scorer === void 0 ? void 0 : scorer.constructor) === null || _a === void 0 ? void 0 : _a.name) || "Unknown";
|
|
650
|
+
return name.charAt(0).toUpperCase() + name.slice(1);
|
|
651
|
+
}).join(',');
|
|
652
|
+
// Use trace name and shortened span ID (or trace ID if no span)
|
|
653
|
+
const idPart = currentSpanId ? currentSpanId.substring(0, 8) : this.traceId.substring(0, 8);
|
|
654
|
+
const evalName = `${this.name.charAt(0).toUpperCase() + this.name.slice(1)}-${idPart}-[${scorerNames}]`;
|
|
655
|
+
// --- End eval name creation ---
|
|
656
|
+
// Process rules (currently just using this.rules directly)
|
|
657
|
+
const loadedRules = this.rules; // TODO: Add ScorerWrapper-like processing if needed in TS
|
|
658
|
+
// Construct the evaluation payload
|
|
659
|
+
const evalRunPayload = {
|
|
660
|
+
organization_id: this.organizationId,
|
|
661
|
+
log_results: options.logResults !== false, // Default to true
|
|
662
|
+
project_name: this.projectName,
|
|
663
|
+
eval_name: evalName,
|
|
664
|
+
examples: [example],
|
|
665
|
+
scorers: scorers, // Pass scorers directly
|
|
666
|
+
model: options.model || "",
|
|
667
|
+
metadata: {}, // Matches Python tracer
|
|
668
|
+
judgment_api_key: this.apiKey,
|
|
669
|
+
override: this.overwrite, // Use trace's overwrite setting
|
|
670
|
+
rules: loadedRules // Pass the processed rules
|
|
671
|
+
};
|
|
672
|
+
// Add evaluation entry using the helper method
|
|
673
|
+
this._addEvalRun(evalRunPayload, startTime);
|
|
674
|
+
}
|
|
675
|
+
catch (error) {
|
|
676
|
+
console.error(`Failed during asyncEvaluate execution: ${error instanceof Error ? error.message : String(error)}`);
|
|
677
|
+
// Decide if we should re-throw or just log
|
|
678
|
+
}
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
/**
|
|
682
|
+
* Private helper to add an evaluation entry to the trace.
|
|
683
|
+
* This mirrors the structure of Python's add_eval_run.
|
|
684
|
+
*
|
|
685
|
+
* @param evalRunPayload The constructed payload for the evaluation.
|
|
686
|
+
* @param startTime The start time (in seconds) of the evaluation process.
|
|
687
|
+
*/
|
|
688
|
+
_addEvalRun(evalRunPayload, startTime) {
|
|
689
|
+
var _a;
|
|
690
|
+
const currentSpanId = currentSpanAsyncLocalStorage.getStore();
|
|
691
|
+
if (!currentSpanId) {
|
|
692
|
+
// If no span ID, the evaluation entry won't be linked to a specific span
|
|
693
|
+
// This might happen if asyncEvaluate is called outside a runInSpan context
|
|
694
|
+
console.warn("Adding evaluation entry without an active span ID.");
|
|
695
|
+
}
|
|
696
|
+
let functionName = "unknown_function";
|
|
697
|
+
let currentDepth = 0; // Default depth if no span
|
|
698
|
+
if (currentSpanId) {
|
|
699
|
+
currentDepth = (_a = this.spanDepths[currentSpanId]) !== null && _a !== void 0 ? _a : 0;
|
|
700
|
+
// Find the function name associated with the current span_id
|
|
701
|
+
for (let i = this.entries.length - 1; i >= 0; i--) {
|
|
702
|
+
const entry = this.entries[i];
|
|
703
|
+
if (entry.span_id === currentSpanId && entry.type === 'enter') {
|
|
704
|
+
functionName = entry.function || "unknown_function";
|
|
705
|
+
break;
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
const duration = (Date.now() / 1000) - startTime;
|
|
710
|
+
// Add evaluation entry to the trace
|
|
711
|
+
this.addEntry({
|
|
712
|
+
type: "evaluation",
|
|
713
|
+
function: functionName,
|
|
714
|
+
span_id: currentSpanId, // May be undefined
|
|
715
|
+
depth: currentDepth,
|
|
716
|
+
timestamp: Date.now() / 1000,
|
|
717
|
+
evaluation_runs: [evalRunPayload], // Embed the payload
|
|
718
|
+
duration: duration,
|
|
719
|
+
span_type: "evaluation"
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
// OPTIONAL: Add a method to get the original name if needed elsewhere
|
|
723
|
+
getOriginalName() {
|
|
724
|
+
return this.originalName;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
exports.TraceClient = TraceClient;
|
|
728
|
+
/**
|
|
729
|
+
* Singleton Tracer class. Manages overall tracing configuration and trace creation.
|
|
730
|
+
*/
|
|
731
|
+
class Tracer {
|
|
732
|
+
constructor(config) {
|
|
733
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
|
|
734
|
+
this.initialized = false;
|
|
735
|
+
const isNode = typeof process !== 'undefined' && process.versions != null && process.versions.node != null;
|
|
736
|
+
const envApiKey = isNode ? (_a = process.env) === null || _a === void 0 ? void 0 : _a.JUDGMENT_API_KEY : undefined;
|
|
737
|
+
const envOrgId = isNode ? (_b = process.env) === null || _b === void 0 ? void 0 : _b.JUDGMENT_ORG_ID : undefined;
|
|
738
|
+
const envProjectName = isNode ? (_c = process.env) === null || _c === void 0 ? void 0 : _c.JUDGMENT_PROJECT_NAME : undefined;
|
|
739
|
+
const envMonitoring = isNode ? (_d = process.env) === null || _d === void 0 ? void 0 : _d.JUDGMENT_MONITORING : 'true';
|
|
740
|
+
const envEvaluations = isNode ? (_e = process.env) === null || _e === void 0 ? void 0 : _e.JUDGMENT_EVALUATIONS : 'true';
|
|
741
|
+
this.apiKey = (_g = (_f = config === null || config === void 0 ? void 0 : config.apiKey) !== null && _f !== void 0 ? _f : envApiKey) !== null && _g !== void 0 ? _g : '';
|
|
742
|
+
this.organizationId = (_j = (_h = config === null || config === void 0 ? void 0 : config.organizationId) !== null && _h !== void 0 ? _h : envOrgId) !== null && _j !== void 0 ? _j : '';
|
|
743
|
+
this.projectName = (_l = (_k = config === null || config === void 0 ? void 0 : config.projectName) !== null && _k !== void 0 ? _k : envProjectName) !== null && _l !== void 0 ? _l : 'default_project';
|
|
744
|
+
this.defaultRules = (_m = config === null || config === void 0 ? void 0 : config.rules) !== null && _m !== void 0 ? _m : [];
|
|
745
|
+
let effectiveMonitoring = (_o = config === null || config === void 0 ? void 0 : config.enableMonitoring) !== null && _o !== void 0 ? _o : ((envMonitoring === null || envMonitoring === void 0 ? void 0 : envMonitoring.toLowerCase()) !== 'false');
|
|
746
|
+
if (effectiveMonitoring && (!this.apiKey || !this.organizationId)) {
|
|
747
|
+
console.warn("JUDGMENT_API_KEY or JUDGMENT_ORG_ID missing. Monitoring disabled.");
|
|
748
|
+
effectiveMonitoring = false;
|
|
749
|
+
}
|
|
750
|
+
this.enableMonitoring = effectiveMonitoring;
|
|
751
|
+
this.enableEvaluations = effectiveMonitoring && ((_p = config === null || config === void 0 ? void 0 : config.enableEvaluations) !== null && _p !== void 0 ? _p : ((envEvaluations === null || envEvaluations === void 0 ? void 0 : envEvaluations.toLowerCase()) !== 'false'));
|
|
752
|
+
this.initialized = true;
|
|
753
|
+
}
|
|
754
|
+
static getInstance(config) {
|
|
755
|
+
if (!Tracer.instance) {
|
|
756
|
+
Tracer.instance = new Tracer(config);
|
|
757
|
+
}
|
|
758
|
+
else if (config && !Tracer.instance.initialized) {
|
|
759
|
+
console.warn("Tracer getInstance called with config after implicit initialization. Re-initializing.");
|
|
760
|
+
Tracer.instance = new Tracer(config);
|
|
761
|
+
}
|
|
762
|
+
else if (config && Tracer.instance.initialized) {
|
|
763
|
+
if (config.projectName && config.projectName !== Tracer.instance.projectName) {
|
|
764
|
+
console.warn(`Attempting to re-initialize Tracer with different project_name. Original will be used: '${Tracer.instance.projectName}'.`);
|
|
765
|
+
}
|
|
766
|
+
if (config.rules && config.rules.length > 0 && Tracer.instance.defaultRules.length === 0) {
|
|
767
|
+
try {
|
|
768
|
+
console.warn("Setting default rules on Tracer instance after initial creation.");
|
|
769
|
+
Tracer.instance.defaultRules = config.rules;
|
|
770
|
+
}
|
|
771
|
+
catch (e) {
|
|
772
|
+
console.error("Failed to set default rules after tracer initialization:", e);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
else if (config.rules && JSON.stringify(config.rules) !== JSON.stringify(Tracer.instance.defaultRules)) {
|
|
776
|
+
console.warn("Attempting to change default rules on Tracer after initialization. Original rules will be used.");
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
return Tracer.instance;
|
|
780
|
+
}
|
|
781
|
+
getCurrentTrace() {
|
|
782
|
+
return currentTraceAsyncLocalStorage.getStore();
|
|
783
|
+
}
|
|
784
|
+
_startTraceInternal(config) {
|
|
785
|
+
var _a, _b;
|
|
786
|
+
const parentTrace = this.getCurrentTrace();
|
|
787
|
+
const traceSpecificRules = (_a = config.rules) !== null && _a !== void 0 ? _a : [];
|
|
788
|
+
const effectiveRulesMap = new Map();
|
|
789
|
+
this.defaultRules.forEach(rule => { var _a; return effectiveRulesMap.set((_a = rule.rule_id) !== null && _a !== void 0 ? _a : rule.name, rule); });
|
|
790
|
+
traceSpecificRules.forEach(rule => { var _a; return effectiveRulesMap.set((_a = rule.rule_id) !== null && _a !== void 0 ? _a : rule.name, rule); });
|
|
791
|
+
const effectiveRules = Array.from(effectiveRulesMap.values());
|
|
792
|
+
const traceClient = new TraceClient({
|
|
793
|
+
tracer: this,
|
|
794
|
+
name: config.name,
|
|
795
|
+
projectName: (_b = config.projectName) !== null && _b !== void 0 ? _b : this.projectName,
|
|
796
|
+
overwrite: config.overwrite,
|
|
797
|
+
rules: effectiveRules,
|
|
798
|
+
enableMonitoring: this.enableMonitoring,
|
|
799
|
+
enableEvaluations: this.enableEvaluations,
|
|
800
|
+
parentTraceId: parentTrace === null || parentTrace === void 0 ? void 0 : parentTrace.traceId,
|
|
801
|
+
parentName: parentTrace === null || parentTrace === void 0 ? void 0 : parentTrace.name,
|
|
802
|
+
apiKey: this.apiKey,
|
|
803
|
+
organizationId: this.organizationId,
|
|
804
|
+
});
|
|
805
|
+
if (traceClient.enableMonitoring) {
|
|
806
|
+
traceClient.save(true).catch(err => {
|
|
807
|
+
console.error(`>>> Tracer: Error saving empty trace for ${traceClient.traceId}:`, err);
|
|
808
|
+
});
|
|
809
|
+
}
|
|
810
|
+
return traceClient;
|
|
811
|
+
}
|
|
812
|
+
runInTrace(config, func) {
|
|
813
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
814
|
+
var _a;
|
|
815
|
+
const traceClient = this._startTraceInternal(config);
|
|
816
|
+
const shouldCreateRootSpan = (_a = config.createRootSpan) !== null && _a !== void 0 ? _a : true;
|
|
817
|
+
return yield currentTraceAsyncLocalStorage.run(traceClient, () => __awaiter(this, void 0, void 0, function* () {
|
|
818
|
+
let result;
|
|
819
|
+
try {
|
|
820
|
+
if (shouldCreateRootSpan) {
|
|
821
|
+
result = yield traceClient.runInSpan(config.name, { spanType: 'chain' }, () => __awaiter(this, void 0, void 0, function* () {
|
|
822
|
+
const funcResult = func(traceClient);
|
|
823
|
+
return funcResult instanceof Promise ? yield funcResult : funcResult;
|
|
824
|
+
}));
|
|
825
|
+
}
|
|
826
|
+
else {
|
|
827
|
+
const funcResult = func(traceClient);
|
|
828
|
+
result = funcResult instanceof Promise ? yield funcResult : funcResult;
|
|
829
|
+
}
|
|
830
|
+
if (traceClient.enableMonitoring) {
|
|
831
|
+
yield traceClient.save(false).catch(saveErr => {
|
|
832
|
+
console.error(`Failed to save completed trace '${config.name}' (${traceClient.traceId}):`, saveErr);
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
return result;
|
|
836
|
+
}
|
|
837
|
+
catch (error) {
|
|
838
|
+
console.error(`Error during traced execution of '${config.name}' (${traceClient.traceId}):`, error);
|
|
839
|
+
if (traceClient.enableMonitoring) {
|
|
840
|
+
yield traceClient.save(false).catch(saveErr => {
|
|
841
|
+
console.error(`Failed to save trace '${config.name}' after error:`, saveErr);
|
|
842
|
+
});
|
|
843
|
+
}
|
|
844
|
+
throw error;
|
|
845
|
+
}
|
|
846
|
+
}));
|
|
847
|
+
});
|
|
848
|
+
}
|
|
849
|
+
observe(options) {
|
|
850
|
+
if (!this.enableMonitoring) {
|
|
851
|
+
return (func) => func;
|
|
852
|
+
}
|
|
853
|
+
return (func) => {
|
|
854
|
+
const spanName = (options === null || options === void 0 ? void 0 : options.name) || func.name || 'anonymous_function';
|
|
855
|
+
const spanType = (options === null || options === void 0 ? void 0 : options.spanType) || 'span';
|
|
856
|
+
const wrapper = (...args) => {
|
|
857
|
+
const currentTrace = this.getCurrentTrace();
|
|
858
|
+
if (!currentTrace) {
|
|
859
|
+
return this.runInTrace({
|
|
860
|
+
name: spanName,
|
|
861
|
+
createRootSpan: false
|
|
862
|
+
}, (traceClient) => __awaiter(this, void 0, void 0, function* () {
|
|
863
|
+
const executionLogic = () => {
|
|
864
|
+
const result = func(...args);
|
|
865
|
+
return result instanceof Promise ? result : Promise.resolve(result);
|
|
866
|
+
};
|
|
867
|
+
return traceClient.runInSpan(spanName, { spanType }, () => __awaiter(this, void 0, void 0, function* () {
|
|
868
|
+
const serializableArgs = args.map(arg => { try {
|
|
869
|
+
return JSON.parse(JSON.stringify(arg));
|
|
870
|
+
}
|
|
871
|
+
catch (_a) {
|
|
872
|
+
return String(arg);
|
|
873
|
+
} });
|
|
874
|
+
traceClient.recordInput({ args: serializableArgs });
|
|
875
|
+
try {
|
|
876
|
+
const finalResult = yield executionLogic();
|
|
877
|
+
traceClient.recordOutput(finalResult);
|
|
878
|
+
return finalResult;
|
|
879
|
+
}
|
|
880
|
+
catch (error) {
|
|
881
|
+
console.error(`Error captured by observe decorator (root) in span '${spanName}':`, error);
|
|
882
|
+
throw error;
|
|
883
|
+
}
|
|
884
|
+
}));
|
|
885
|
+
}));
|
|
886
|
+
}
|
|
887
|
+
else {
|
|
888
|
+
const executionLogic = () => {
|
|
889
|
+
const result = func(...args);
|
|
890
|
+
return result instanceof Promise ? result : Promise.resolve(result);
|
|
891
|
+
};
|
|
892
|
+
return currentTrace.runInSpan(spanName, { spanType }, () => __awaiter(this, void 0, void 0, function* () {
|
|
893
|
+
const serializableArgs = args.map(arg => { try {
|
|
894
|
+
return JSON.parse(JSON.stringify(arg));
|
|
895
|
+
}
|
|
896
|
+
catch (_a) {
|
|
897
|
+
return String(arg);
|
|
898
|
+
} });
|
|
899
|
+
currentTrace.recordInput({ args: serializableArgs });
|
|
900
|
+
try {
|
|
901
|
+
const finalResult = yield executionLogic();
|
|
902
|
+
currentTrace.recordOutput(finalResult);
|
|
903
|
+
return finalResult;
|
|
904
|
+
}
|
|
905
|
+
catch (error) {
|
|
906
|
+
console.error(`Error captured by observe decorator (nested) in span '${spanName}':`, error);
|
|
907
|
+
throw error;
|
|
908
|
+
}
|
|
909
|
+
}));
|
|
910
|
+
}
|
|
911
|
+
};
|
|
912
|
+
Object.defineProperty(wrapper, 'name', { value: func.name, configurable: true });
|
|
913
|
+
return wrapper;
|
|
914
|
+
};
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
exports.Tracer = Tracer;
|
|
918
|
+
// --- Helper Functions for Wrapping LLM Clients --- //
|
|
919
|
+
function _getClientConfig(client) {
|
|
920
|
+
var _a, _b, _c, _d, _e;
|
|
921
|
+
if (client instanceof openai_1.default && typeof ((_b = (_a = client === null || client === void 0 ? void 0 : client.chat) === null || _a === void 0 ? void 0 : _a.completions) === null || _b === void 0 ? void 0 : _b.create) === 'function') {
|
|
922
|
+
return { spanName: "OPENAI_API_CALL", originalMethod: client.chat.completions.create.bind(client.chat.completions) };
|
|
923
|
+
}
|
|
924
|
+
else if (client instanceof sdk_1.default && typeof ((_c = client === null || client === void 0 ? void 0 : client.messages) === null || _c === void 0 ? void 0 : _c.create) === 'function') {
|
|
925
|
+
return { spanName: "ANTHROPIC_API_CALL", originalMethod: client.messages.create.bind(client.messages) };
|
|
926
|
+
}
|
|
927
|
+
// Use type assertion for older Together AI version
|
|
928
|
+
else if (client instanceof together_ai_1.default && typeof ((_d = client === null || client === void 0 ? void 0 : client.completions) === null || _d === void 0 ? void 0 : _d.create) === 'function') {
|
|
929
|
+
return { spanName: "TOGETHER_API_CALL", originalMethod: client.completions.create.bind(client.completions) };
|
|
930
|
+
}
|
|
931
|
+
logger_instance_1.default.warn("Cannot wrap client: Unsupported type or incompatible SDK structure.", { clientType: (_e = client === null || client === void 0 ? void 0 : client.constructor) === null || _e === void 0 ? void 0 : _e.name });
|
|
932
|
+
return null;
|
|
933
|
+
}
|
|
934
|
+
function _formatInputData(client, args) {
|
|
935
|
+
const params = args[0] || {};
|
|
936
|
+
try {
|
|
937
|
+
// Handle Together client potentially having different input structure
|
|
938
|
+
if (client instanceof openai_1.default || client instanceof together_ai_1.default) {
|
|
939
|
+
return { model: params.model, messages: params.messages, /* other potential params */ };
|
|
940
|
+
}
|
|
941
|
+
else if (client instanceof sdk_1.default) {
|
|
942
|
+
return { model: params.model, messages: params.messages, max_tokens: params.max_tokens, };
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
catch (e) {
|
|
946
|
+
logger_instance_1.default.error("Error formatting LLM input:", { error: e instanceof Error ? e.message : String(e), params });
|
|
947
|
+
return { raw_params: params };
|
|
948
|
+
}
|
|
949
|
+
return { raw_params: params };
|
|
950
|
+
}
|
|
951
|
+
function _formatOutputData(client, response) {
|
|
952
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
953
|
+
try {
|
|
954
|
+
// Separate handling for OpenAI and Together, assuming Together might differ
|
|
955
|
+
if (client instanceof openai_1.default && ((_b = (_a = response === null || response === void 0 ? void 0 : response.choices) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.message)) {
|
|
956
|
+
const message = response.choices[0].message;
|
|
957
|
+
return { content: message.content, usage: response.usage, };
|
|
958
|
+
}
|
|
959
|
+
else if (client instanceof together_ai_1.default && ((_d = (_c = response === null || response === void 0 ? void 0 : response.choices) === null || _c === void 0 ? void 0 : _c[0]) === null || _d === void 0 ? void 0 : _d.message)) {
|
|
960
|
+
// Assume Together v0.5.1 has a similar structure for now, but access defensively
|
|
961
|
+
const message = response.choices[0].message;
|
|
962
|
+
return { content: message === null || message === void 0 ? void 0 : message.content, usage: response === null || response === void 0 ? void 0 : response.usage, }; // Optional chaining for safety
|
|
963
|
+
}
|
|
964
|
+
else if (client instanceof sdk_1.default && ((_e = response === null || response === void 0 ? void 0 : response.content) === null || _e === void 0 ? void 0 : _e[0])) {
|
|
965
|
+
const textContent = response.content.filter((block) => block.type === 'text').map((block) => block.text).join('');
|
|
966
|
+
// Reconstruct usage for Anthropic if needed
|
|
967
|
+
const usage = {
|
|
968
|
+
input_tokens: (_f = response.usage) === null || _f === void 0 ? void 0 : _f.input_tokens,
|
|
969
|
+
output_tokens: (_g = response.usage) === null || _g === void 0 ? void 0 : _g.output_tokens,
|
|
970
|
+
total_tokens: (((_h = response.usage) === null || _h === void 0 ? void 0 : _h.input_tokens) || 0) + (((_j = response.usage) === null || _j === void 0 ? void 0 : _j.output_tokens) || 0)
|
|
971
|
+
};
|
|
972
|
+
return { content: textContent, usage: usage, };
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
catch (e) {
|
|
976
|
+
logger_instance_1.default.error("Error formatting LLM output:", { error: e instanceof Error ? e.message : String(e) });
|
|
977
|
+
return { formatting_error: String(e), raw_response: response };
|
|
978
|
+
}
|
|
979
|
+
// Return raw if structure doesn't match known patterns
|
|
980
|
+
return { raw_response: response };
|
|
981
|
+
}
|
|
982
|
+
// --- The Wrap Function --- //
|
|
983
|
+
function wrap(client) {
|
|
984
|
+
var _a, _b, _c;
|
|
985
|
+
const tracer = Tracer.getInstance();
|
|
986
|
+
if (!tracer.enableMonitoring) {
|
|
987
|
+
logger_instance_1.default.info("Global monitoring disabled, client wrapping skipped.");
|
|
988
|
+
return client;
|
|
989
|
+
}
|
|
990
|
+
const config = _getClientConfig(client);
|
|
991
|
+
if (!config) {
|
|
992
|
+
return client;
|
|
993
|
+
}
|
|
994
|
+
const { spanName, originalMethod } = config;
|
|
995
|
+
const tracedMethod = (...args) => __awaiter(this, void 0, void 0, function* () {
|
|
996
|
+
const currentTrace = tracer.getCurrentTrace();
|
|
997
|
+
if (!currentTrace || !currentTrace.enableMonitoring) {
|
|
998
|
+
return originalMethod(...args);
|
|
999
|
+
}
|
|
1000
|
+
return yield currentTrace.runInSpan(spanName, { spanType: 'llm' }, () => __awaiter(this, void 0, void 0, function* () {
|
|
1001
|
+
const inputData = _formatInputData(client, args);
|
|
1002
|
+
currentTrace.recordInput(inputData);
|
|
1003
|
+
try {
|
|
1004
|
+
const response = yield originalMethod(...args);
|
|
1005
|
+
const outputData = _formatOutputData(client, response);
|
|
1006
|
+
currentTrace.recordOutput(outputData);
|
|
1007
|
+
return response;
|
|
1008
|
+
}
|
|
1009
|
+
catch (error) {
|
|
1010
|
+
currentTrace.recordOutput(error); // Record error object in output
|
|
1011
|
+
throw error;
|
|
1012
|
+
}
|
|
1013
|
+
}));
|
|
1014
|
+
});
|
|
1015
|
+
// Apply the wrapper
|
|
1016
|
+
if (client instanceof openai_1.default && ((_a = client.chat) === null || _a === void 0 ? void 0 : _a.completions)) {
|
|
1017
|
+
client.chat.completions.create = tracedMethod;
|
|
1018
|
+
}
|
|
1019
|
+
else if (client instanceof sdk_1.default && client.messages) {
|
|
1020
|
+
client.messages.create = tracedMethod;
|
|
1021
|
+
}
|
|
1022
|
+
// Use type assertion for older Together AI version
|
|
1023
|
+
else if (client instanceof together_ai_1.default && client.completions) {
|
|
1024
|
+
client.completions.create = tracedMethod;
|
|
1025
|
+
}
|
|
1026
|
+
else {
|
|
1027
|
+
// Log if we couldn't apply the wrapper despite getting a config
|
|
1028
|
+
logger_instance_1.default.error("Failed to apply wrapper: Could not find method to replace after config check.", { clientType: (_b = client === null || client === void 0 ? void 0 : client.constructor) === null || _b === void 0 ? void 0 : _b.name });
|
|
1029
|
+
return client;
|
|
1030
|
+
}
|
|
1031
|
+
logger_instance_1.default.info(`Successfully wrapped client: ${(_c = client === null || client === void 0 ? void 0 : client.constructor) === null || _c === void 0 ? void 0 : _c.name}`);
|
|
1032
|
+
return client;
|
|
1033
|
+
}
|
|
1034
|
+
exports.wrap = wrap;
|
|
1035
|
+
//# sourceMappingURL=tracer.js.map
|