@observyze/sdk 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +175 -10
- package/dist/chunk-FQBYUOJB.mjs +316 -0
- package/dist/{chunk-YRMQCX2P.mjs → chunk-YSYKPKKX.mjs} +5 -20
- package/dist/index--b41_E-1.d.mts +440 -0
- package/dist/index--b41_E-1.d.ts +440 -0
- package/dist/{index-DEorAmFu.d.mts → index-Cr-FN-y5.d.mts} +89 -89
- package/dist/{index-DEorAmFu.d.ts → index-Cr-FN-y5.d.ts} +89 -89
- package/dist/index-D4UXMom5.d.mts +429 -0
- package/dist/index-D4UXMom5.d.ts +429 -0
- package/dist/index.d.mts +4 -37
- package/dist/index.d.ts +4 -37
- package/dist/index.js +630 -377
- package/dist/index.mjs +333 -70
- package/dist/opentelemetry/index.d.mts +2 -1
- package/dist/opentelemetry/index.d.ts +2 -1
- package/dist/opentelemetry/index.js +7 -4
- package/dist/opentelemetry/index.mjs +1 -1
- package/package.json +13 -6
- package/LICENSE +0 -21
package/dist/index.js
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
-
var __esm = (fn, res) => function __init() {
|
|
7
|
-
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
8
|
-
};
|
|
9
8
|
var __export = (target, all) => {
|
|
10
9
|
for (var name in all)
|
|
11
10
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -18,303 +17,38 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
18
17
|
}
|
|
19
18
|
return to;
|
|
20
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
|
+
));
|
|
21
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
22
29
|
|
|
23
|
-
// src/types.ts
|
|
24
|
-
var SpanType, TraceStatus;
|
|
25
|
-
var init_types = __esm({
|
|
26
|
-
"src/types.ts"() {
|
|
27
|
-
"use strict";
|
|
28
|
-
SpanType = /* @__PURE__ */ ((SpanType3) => {
|
|
29
|
-
SpanType3["LLM"] = "llm";
|
|
30
|
-
SpanType3["TOOL"] = "tool";
|
|
31
|
-
SpanType3["AGENT"] = "agent";
|
|
32
|
-
SpanType3["CHAIN"] = "chain";
|
|
33
|
-
SpanType3["RETRIEVAL"] = "retrieval";
|
|
34
|
-
return SpanType3;
|
|
35
|
-
})(SpanType || {});
|
|
36
|
-
TraceStatus = /* @__PURE__ */ ((TraceStatus2) => {
|
|
37
|
-
TraceStatus2["SUCCESS"] = "success";
|
|
38
|
-
TraceStatus2["ERROR"] = "error";
|
|
39
|
-
TraceStatus2["TIMEOUT"] = "timeout";
|
|
40
|
-
TraceStatus2["RUNNING"] = "running";
|
|
41
|
-
return TraceStatus2;
|
|
42
|
-
})(TraceStatus || {});
|
|
43
|
-
}
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
// src/instrumentation/openai.ts
|
|
47
|
-
function wrapOpenAI(client, nwClient) {
|
|
48
|
-
const anyClient = client;
|
|
49
|
-
const originalCreate = client.chat.completions.create.bind(client.chat.completions);
|
|
50
|
-
client.chat.completions.create = async function(params, options) {
|
|
51
|
-
const trace = nwClient.startTrace(`openai.chat.completions.create`, {
|
|
52
|
-
provider: "openai",
|
|
53
|
-
model: params.model
|
|
54
|
-
});
|
|
55
|
-
const span = trace.startSpan("chat.completions.create", "llm" /* LLM */);
|
|
56
|
-
span.setMetadata("model", params.model);
|
|
57
|
-
span.setMetadata("provider", "openai");
|
|
58
|
-
if (params.temperature !== void 0) span.setMetadata("temperature", params.temperature);
|
|
59
|
-
if (params.max_tokens !== void 0) span.setMetadata("max_tokens", params.max_tokens);
|
|
60
|
-
span.setInput({
|
|
61
|
-
model: params.model,
|
|
62
|
-
messages: params.messages,
|
|
63
|
-
temperature: params.temperature,
|
|
64
|
-
max_tokens: params.max_tokens
|
|
65
|
-
});
|
|
66
|
-
const startTime = Date.now();
|
|
67
|
-
try {
|
|
68
|
-
const response = await originalCreate(params, options);
|
|
69
|
-
if (params.stream) {
|
|
70
|
-
return wrapOpenAIStream(response, span, trace, startTime);
|
|
71
|
-
}
|
|
72
|
-
const completionResponse = response;
|
|
73
|
-
const latency = Date.now() - startTime;
|
|
74
|
-
span.setOutput({
|
|
75
|
-
id: completionResponse.id,
|
|
76
|
-
model: completionResponse.model,
|
|
77
|
-
choices: completionResponse.choices
|
|
78
|
-
});
|
|
79
|
-
if (completionResponse.usage) {
|
|
80
|
-
span.setTokens({
|
|
81
|
-
input: completionResponse.usage.prompt_tokens,
|
|
82
|
-
output: completionResponse.usage.completion_tokens,
|
|
83
|
-
total: completionResponse.usage.total_tokens
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
span.setMetadata("latency_ms", latency);
|
|
87
|
-
span.end();
|
|
88
|
-
trace.end();
|
|
89
|
-
return response;
|
|
90
|
-
} catch (error) {
|
|
91
|
-
const latency = Date.now() - startTime;
|
|
92
|
-
span.setMetadata("latency_ms", latency);
|
|
93
|
-
span.setError(error);
|
|
94
|
-
span.end();
|
|
95
|
-
trace.end();
|
|
96
|
-
throw error;
|
|
97
|
-
}
|
|
98
|
-
};
|
|
99
|
-
return client;
|
|
100
|
-
}
|
|
101
|
-
function wrapOpenAIStream(stream, span, trace, startTime) {
|
|
102
|
-
const bufferedChunks = [];
|
|
103
|
-
let streamId = "";
|
|
104
|
-
let streamModel = "";
|
|
105
|
-
return {
|
|
106
|
-
[Symbol.asyncIterator]: async function* () {
|
|
107
|
-
try {
|
|
108
|
-
for await (const chunk of stream) {
|
|
109
|
-
if (chunk.id) streamId = chunk.id;
|
|
110
|
-
if (chunk.model) streamModel = chunk.model;
|
|
111
|
-
const delta = chunk.choices[0]?.delta;
|
|
112
|
-
if (delta?.content) {
|
|
113
|
-
bufferedChunks.push(delta.content);
|
|
114
|
-
}
|
|
115
|
-
yield chunk;
|
|
116
|
-
}
|
|
117
|
-
const latency = Date.now() - startTime;
|
|
118
|
-
const completeOutput = bufferedChunks.join("");
|
|
119
|
-
span.setOutput({
|
|
120
|
-
id: streamId,
|
|
121
|
-
model: streamModel,
|
|
122
|
-
content: completeOutput
|
|
123
|
-
});
|
|
124
|
-
span.setMetadata("latency_ms", latency);
|
|
125
|
-
span.setMetadata("streaming", true);
|
|
126
|
-
span.end();
|
|
127
|
-
trace.end();
|
|
128
|
-
} catch (error) {
|
|
129
|
-
const latency = Date.now() - startTime;
|
|
130
|
-
span.setMetadata("latency_ms", latency);
|
|
131
|
-
span.setError(error);
|
|
132
|
-
span.end();
|
|
133
|
-
trace.end();
|
|
134
|
-
throw error;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
var init_openai = __esm({
|
|
140
|
-
"src/instrumentation/openai.ts"() {
|
|
141
|
-
"use strict";
|
|
142
|
-
init_types();
|
|
143
|
-
}
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
// src/instrumentation/anthropic.ts
|
|
147
|
-
function wrapAnthropic(client, nwClient) {
|
|
148
|
-
const anyClient = client;
|
|
149
|
-
const originalCreate = client.messages.create.bind(client.messages);
|
|
150
|
-
client.messages.create = async function(params, options) {
|
|
151
|
-
const trace = nwClient.startTrace(`anthropic.messages.create`, {
|
|
152
|
-
provider: "anthropic",
|
|
153
|
-
model: params.model
|
|
154
|
-
});
|
|
155
|
-
const span = trace.startSpan("messages.create", "llm" /* LLM */);
|
|
156
|
-
span.setMetadata("model", params.model);
|
|
157
|
-
span.setMetadata("provider", "anthropic");
|
|
158
|
-
if (params.temperature !== void 0) span.setMetadata("temperature", params.temperature);
|
|
159
|
-
if (params.max_tokens !== void 0) span.setMetadata("max_tokens", params.max_tokens);
|
|
160
|
-
if (params.system !== void 0) span.setMetadata("system", params.system);
|
|
161
|
-
span.setInput({
|
|
162
|
-
model: params.model,
|
|
163
|
-
messages: params.messages,
|
|
164
|
-
max_tokens: params.max_tokens,
|
|
165
|
-
temperature: params.temperature,
|
|
166
|
-
system: params.system
|
|
167
|
-
});
|
|
168
|
-
const startTime = Date.now();
|
|
169
|
-
try {
|
|
170
|
-
const response = await originalCreate(params, options);
|
|
171
|
-
if (params.stream) {
|
|
172
|
-
return wrapAnthropicStream(response, span, trace, startTime);
|
|
173
|
-
}
|
|
174
|
-
const messageResponse = response;
|
|
175
|
-
const latency = Date.now() - startTime;
|
|
176
|
-
span.setOutput({
|
|
177
|
-
id: messageResponse.id,
|
|
178
|
-
model: messageResponse.model,
|
|
179
|
-
role: messageResponse.role,
|
|
180
|
-
content: messageResponse.content,
|
|
181
|
-
stop_reason: messageResponse.stop_reason
|
|
182
|
-
});
|
|
183
|
-
if (messageResponse.usage) {
|
|
184
|
-
span.setTokens({
|
|
185
|
-
input: messageResponse.usage.input_tokens,
|
|
186
|
-
output: messageResponse.usage.output_tokens,
|
|
187
|
-
total: messageResponse.usage.input_tokens + messageResponse.usage.output_tokens
|
|
188
|
-
});
|
|
189
|
-
}
|
|
190
|
-
span.setMetadata("latency_ms", latency);
|
|
191
|
-
span.end();
|
|
192
|
-
trace.end();
|
|
193
|
-
return response;
|
|
194
|
-
} catch (error) {
|
|
195
|
-
const latency = Date.now() - startTime;
|
|
196
|
-
span.setMetadata("latency_ms", latency);
|
|
197
|
-
span.setError(error);
|
|
198
|
-
span.end();
|
|
199
|
-
trace.end();
|
|
200
|
-
throw error;
|
|
201
|
-
}
|
|
202
|
-
};
|
|
203
|
-
return client;
|
|
204
|
-
}
|
|
205
|
-
function wrapAnthropicStream(stream, span, trace, startTime) {
|
|
206
|
-
const bufferedChunks = [];
|
|
207
|
-
let messageId = "";
|
|
208
|
-
let messageModel = "";
|
|
209
|
-
let stopReason = null;
|
|
210
|
-
let inputTokens = 0;
|
|
211
|
-
let outputTokens = 0;
|
|
212
|
-
return {
|
|
213
|
-
[Symbol.asyncIterator]: async function* () {
|
|
214
|
-
try {
|
|
215
|
-
for await (const event of stream) {
|
|
216
|
-
if (event.type === "message_start" && event.message) {
|
|
217
|
-
messageId = event.message.id;
|
|
218
|
-
messageModel = event.message.model;
|
|
219
|
-
if (event.message.usage) {
|
|
220
|
-
inputTokens = event.message.usage.input_tokens;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
if (event.type === "content_block_delta" && event.delta?.text) {
|
|
224
|
-
bufferedChunks.push(event.delta.text);
|
|
225
|
-
}
|
|
226
|
-
if (event.type === "message_delta" && event.delta) {
|
|
227
|
-
if (event.delta.stop_reason) {
|
|
228
|
-
stopReason = event.delta.stop_reason;
|
|
229
|
-
}
|
|
230
|
-
if (event.usage?.output_tokens) {
|
|
231
|
-
outputTokens = event.usage.output_tokens;
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
yield event;
|
|
235
|
-
}
|
|
236
|
-
const latency = Date.now() - startTime;
|
|
237
|
-
const completeOutput = bufferedChunks.join("");
|
|
238
|
-
span.setOutput({
|
|
239
|
-
id: messageId,
|
|
240
|
-
model: messageModel,
|
|
241
|
-
content: completeOutput,
|
|
242
|
-
stop_reason: stopReason
|
|
243
|
-
});
|
|
244
|
-
if (inputTokens > 0 || outputTokens > 0) {
|
|
245
|
-
span.setTokens({
|
|
246
|
-
input: inputTokens,
|
|
247
|
-
output: outputTokens,
|
|
248
|
-
total: inputTokens + outputTokens
|
|
249
|
-
});
|
|
250
|
-
}
|
|
251
|
-
span.setMetadata("latency_ms", latency);
|
|
252
|
-
span.setMetadata("streaming", true);
|
|
253
|
-
span.end();
|
|
254
|
-
trace.end();
|
|
255
|
-
} catch (error) {
|
|
256
|
-
const latency = Date.now() - startTime;
|
|
257
|
-
span.setMetadata("latency_ms", latency);
|
|
258
|
-
span.setError(error);
|
|
259
|
-
span.end();
|
|
260
|
-
trace.end();
|
|
261
|
-
throw error;
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
};
|
|
265
|
-
}
|
|
266
|
-
var init_anthropic = __esm({
|
|
267
|
-
"src/instrumentation/anthropic.ts"() {
|
|
268
|
-
"use strict";
|
|
269
|
-
init_types();
|
|
270
|
-
}
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
// src/instrumentation/index.ts
|
|
274
|
-
var instrumentation_exports = {};
|
|
275
|
-
__export(instrumentation_exports, {
|
|
276
|
-
wrap: () => wrap,
|
|
277
|
-
wrapAnthropic: () => wrapAnthropic,
|
|
278
|
-
wrapOpenAI: () => wrapOpenAI
|
|
279
|
-
});
|
|
280
|
-
function wrap(client, nwClient) {
|
|
281
|
-
if ("chat" in client && client.chat && "completions" in client.chat) {
|
|
282
|
-
return wrapOpenAI(client, nwClient);
|
|
283
|
-
}
|
|
284
|
-
if ("messages" in client && client.messages && "create" in client.messages) {
|
|
285
|
-
return wrapAnthropic(client, nwClient);
|
|
286
|
-
}
|
|
287
|
-
throw new Error(
|
|
288
|
-
"Observyze SDK: Unsupported client type. Supported clients: OpenAI, Anthropic"
|
|
289
|
-
);
|
|
290
|
-
}
|
|
291
|
-
var init_instrumentation = __esm({
|
|
292
|
-
"src/instrumentation/index.ts"() {
|
|
293
|
-
"use strict";
|
|
294
|
-
init_openai();
|
|
295
|
-
init_anthropic();
|
|
296
|
-
init_openai();
|
|
297
|
-
init_anthropic();
|
|
298
|
-
}
|
|
299
|
-
});
|
|
300
|
-
|
|
301
30
|
// src/index.ts
|
|
302
31
|
var index_exports = {};
|
|
303
32
|
__export(index_exports, {
|
|
304
33
|
ObservyzeClient: () => ObservyzeClient,
|
|
305
34
|
ObservyzeSpanExporter: () => ObservyzeSpanExporter,
|
|
306
35
|
Span: () => Span,
|
|
307
|
-
SpanType: () => SpanType,
|
|
36
|
+
SpanType: () => import_types.SpanType,
|
|
308
37
|
Trace: () => Trace,
|
|
309
|
-
TraceStatus: () => TraceStatus,
|
|
38
|
+
TraceStatus: () => import_types.TraceStatus,
|
|
310
39
|
wrap: () => wrap,
|
|
311
40
|
wrapAnthropic: () => wrapAnthropic,
|
|
312
41
|
wrapOpenAI: () => wrapOpenAI
|
|
313
42
|
});
|
|
314
43
|
module.exports = __toCommonJS(index_exports);
|
|
315
44
|
|
|
45
|
+
// src/client.ts
|
|
46
|
+
var import_debug3 = __toESM(require("debug"));
|
|
47
|
+
|
|
48
|
+
// src/types.ts
|
|
49
|
+
var import_types = require("@observyze/types");
|
|
50
|
+
|
|
316
51
|
// src/trace.ts
|
|
317
|
-
init_types();
|
|
318
52
|
var import_crypto = require("crypto");
|
|
319
53
|
function generateId() {
|
|
320
54
|
return `${Date.now()}-${(0, import_crypto.randomUUID)().substring(0, 8)}`;
|
|
@@ -417,7 +151,7 @@ var Trace = class {
|
|
|
417
151
|
organization_id: organizationId,
|
|
418
152
|
project_id: projectId,
|
|
419
153
|
name,
|
|
420
|
-
status:
|
|
154
|
+
status: import_types.TraceStatus.RUNNING,
|
|
421
155
|
start_time: new Date(this.startTime),
|
|
422
156
|
end_time: new Date(this.startTime),
|
|
423
157
|
// Will be updated on end()
|
|
@@ -459,73 +193,352 @@ var Trace = class {
|
|
|
459
193
|
if (!this.data.tags.includes(tag)) {
|
|
460
194
|
this.data.tags.push(tag);
|
|
461
195
|
}
|
|
462
|
-
return this;
|
|
463
|
-
}
|
|
464
|
-
/**
|
|
465
|
-
* Add multiple tags at once
|
|
466
|
-
*/
|
|
467
|
-
addTags(tags) {
|
|
468
|
-
tags.forEach((tag) => this.addTag(tag));
|
|
469
|
-
return this;
|
|
470
|
-
}
|
|
471
|
-
/**
|
|
472
|
-
* Set the user ID associated with this trace
|
|
473
|
-
*/
|
|
474
|
-
setUserId(userId) {
|
|
475
|
-
this.data.user_id = userId;
|
|
476
|
-
return this;
|
|
477
|
-
}
|
|
478
|
-
/**
|
|
479
|
-
* Set the session ID associated with this trace
|
|
480
|
-
*/
|
|
481
|
-
setSessionId(sessionId) {
|
|
482
|
-
this.data.session_id = sessionId;
|
|
483
|
-
return this;
|
|
196
|
+
return this;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Add multiple tags at once
|
|
200
|
+
*/
|
|
201
|
+
addTags(tags) {
|
|
202
|
+
tags.forEach((tag) => this.addTag(tag));
|
|
203
|
+
return this;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Set the user ID associated with this trace
|
|
207
|
+
*/
|
|
208
|
+
setUserId(userId) {
|
|
209
|
+
this.data.user_id = userId;
|
|
210
|
+
return this;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Set the session ID associated with this trace
|
|
214
|
+
*/
|
|
215
|
+
setSessionId(sessionId) {
|
|
216
|
+
this.data.session_id = sessionId;
|
|
217
|
+
return this;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* End the trace with a final status
|
|
221
|
+
*/
|
|
222
|
+
end(status = import_types.TraceStatus.SUCCESS) {
|
|
223
|
+
if (this.ended) {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
const endTime = Date.now();
|
|
227
|
+
this.data.end_time = new Date(endTime);
|
|
228
|
+
this.data.duration_ms = endTime - this.startTime;
|
|
229
|
+
this.data.status = status;
|
|
230
|
+
this.data.spans = this.spans.map((span) => span.toJSON());
|
|
231
|
+
this.ended = true;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Get the trace ID
|
|
235
|
+
*/
|
|
236
|
+
get id() {
|
|
237
|
+
return this.data.trace_id;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Check if the trace has ended
|
|
241
|
+
*/
|
|
242
|
+
get isEnded() {
|
|
243
|
+
return this.ended;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Get the trace data for serialization
|
|
247
|
+
*/
|
|
248
|
+
toJSON() {
|
|
249
|
+
return { ...this.data };
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
// src/instrumentation/openai.ts
|
|
254
|
+
var import_debug = __toESM(require("debug"));
|
|
255
|
+
var log = (0, import_debug.default)("observyze:sdk");
|
|
256
|
+
function wrapOpenAI(client, nwClient) {
|
|
257
|
+
const anyClient = client;
|
|
258
|
+
if (nwClient.getConfig().enableProxyRedirect && anyClient.baseURL && anyClient.apiKey) {
|
|
259
|
+
const isAlreadyRedirected = anyClient.baseURL.includes("/api/v1/proxy/openai");
|
|
260
|
+
if (!isAlreadyRedirected) {
|
|
261
|
+
const originalApiKey = anyClient.apiKey;
|
|
262
|
+
anyClient.baseURL = `${nwClient.getConfig().endpoint}/api/v1/proxy/openai/v1`;
|
|
263
|
+
anyClient.apiKey = nwClient.getConfig().apiKey;
|
|
264
|
+
anyClient.defaultHeaders = {
|
|
265
|
+
...anyClient.defaultHeaders,
|
|
266
|
+
"x-provider-key": originalApiKey
|
|
267
|
+
};
|
|
268
|
+
if (nwClient.getConfig().debug) {
|
|
269
|
+
log("[Observyze SDK] Transparently redirected OpenAI client to proxy gateway:", anyClient.baseURL);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
const originalCreate = client.chat.completions.create.bind(client.chat.completions);
|
|
274
|
+
client.chat.completions.create = async function(params, options) {
|
|
275
|
+
const isProxyRedirected = nwClient.getConfig().enableProxyRedirect && anyClient.baseURL?.includes("/api/v1/proxy/openai");
|
|
276
|
+
if (isProxyRedirected) {
|
|
277
|
+
return originalCreate(params, options);
|
|
278
|
+
}
|
|
279
|
+
const trace = nwClient.startTrace(`openai.chat.completions.create`, {
|
|
280
|
+
provider: "openai",
|
|
281
|
+
model: params.model
|
|
282
|
+
});
|
|
283
|
+
const span = trace.startSpan("chat.completions.create", import_types.SpanType.LLM);
|
|
284
|
+
span.setMetadata("model", params.model);
|
|
285
|
+
span.setMetadata("provider", "openai");
|
|
286
|
+
if (params.temperature !== void 0) span.setMetadata("temperature", params.temperature);
|
|
287
|
+
if (params.max_tokens !== void 0) span.setMetadata("max_tokens", params.max_tokens);
|
|
288
|
+
span.setInput({
|
|
289
|
+
model: params.model,
|
|
290
|
+
messages: params.messages,
|
|
291
|
+
temperature: params.temperature,
|
|
292
|
+
max_tokens: params.max_tokens
|
|
293
|
+
});
|
|
294
|
+
const startTime = Date.now();
|
|
295
|
+
try {
|
|
296
|
+
const response = await originalCreate(params, options);
|
|
297
|
+
if (params.stream) {
|
|
298
|
+
return wrapOpenAIStream(response, span, trace, startTime);
|
|
299
|
+
}
|
|
300
|
+
const completionResponse = response;
|
|
301
|
+
const latency = Date.now() - startTime;
|
|
302
|
+
span.setOutput({
|
|
303
|
+
id: completionResponse.id,
|
|
304
|
+
model: completionResponse.model,
|
|
305
|
+
choices: completionResponse.choices
|
|
306
|
+
});
|
|
307
|
+
if (completionResponse.usage) {
|
|
308
|
+
span.setTokens({
|
|
309
|
+
input: completionResponse.usage.prompt_tokens,
|
|
310
|
+
output: completionResponse.usage.completion_tokens,
|
|
311
|
+
total: completionResponse.usage.total_tokens
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
span.setMetadata("latency_ms", latency);
|
|
315
|
+
span.end();
|
|
316
|
+
trace.end();
|
|
317
|
+
return response;
|
|
318
|
+
} catch (error) {
|
|
319
|
+
const latency = Date.now() - startTime;
|
|
320
|
+
span.setMetadata("latency_ms", latency);
|
|
321
|
+
span.setError(error);
|
|
322
|
+
span.end();
|
|
323
|
+
trace.end();
|
|
324
|
+
throw error;
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
return client;
|
|
328
|
+
}
|
|
329
|
+
function wrapOpenAIStream(stream, span, trace, startTime) {
|
|
330
|
+
const bufferedChunks = [];
|
|
331
|
+
let streamId = "";
|
|
332
|
+
let streamModel = "";
|
|
333
|
+
return {
|
|
334
|
+
[Symbol.asyncIterator]: async function* () {
|
|
335
|
+
try {
|
|
336
|
+
for await (const chunk of stream) {
|
|
337
|
+
if (chunk.id) streamId = chunk.id;
|
|
338
|
+
if (chunk.model) streamModel = chunk.model;
|
|
339
|
+
const delta = chunk.choices[0]?.delta;
|
|
340
|
+
if (delta?.content) {
|
|
341
|
+
bufferedChunks.push(delta.content);
|
|
342
|
+
}
|
|
343
|
+
yield chunk;
|
|
344
|
+
}
|
|
345
|
+
const latency = Date.now() - startTime;
|
|
346
|
+
const completeOutput = bufferedChunks.join("");
|
|
347
|
+
span.setOutput({
|
|
348
|
+
id: streamId,
|
|
349
|
+
model: streamModel,
|
|
350
|
+
content: completeOutput
|
|
351
|
+
});
|
|
352
|
+
span.setMetadata("latency_ms", latency);
|
|
353
|
+
span.setMetadata("streaming", true);
|
|
354
|
+
span.end();
|
|
355
|
+
trace.end();
|
|
356
|
+
} catch (error) {
|
|
357
|
+
const latency = Date.now() - startTime;
|
|
358
|
+
span.setMetadata("latency_ms", latency);
|
|
359
|
+
span.setError(error);
|
|
360
|
+
span.end();
|
|
361
|
+
trace.end();
|
|
362
|
+
throw error;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// src/instrumentation/anthropic.ts
|
|
369
|
+
var import_debug2 = __toESM(require("debug"));
|
|
370
|
+
var log2 = (0, import_debug2.default)("observyze:sdk");
|
|
371
|
+
function wrapAnthropic(client, nwClient) {
|
|
372
|
+
const anyClient = client;
|
|
373
|
+
if (nwClient.getConfig().enableProxyRedirect && anyClient.baseURL && anyClient.apiKey) {
|
|
374
|
+
const isAlreadyRedirected = anyClient.baseURL.includes("/api/v1/proxy/anthropic");
|
|
375
|
+
if (!isAlreadyRedirected) {
|
|
376
|
+
const originalApiKey = anyClient.apiKey;
|
|
377
|
+
anyClient.baseURL = `${nwClient.getConfig().endpoint}/api/v1/proxy/anthropic/v1`;
|
|
378
|
+
anyClient.apiKey = nwClient.getConfig().apiKey;
|
|
379
|
+
anyClient.defaultHeaders = {
|
|
380
|
+
...anyClient.defaultHeaders,
|
|
381
|
+
"x-provider-key": originalApiKey
|
|
382
|
+
};
|
|
383
|
+
if (nwClient.getConfig().debug) {
|
|
384
|
+
log2("[Observyze SDK] Transparently redirected Anthropic client to proxy gateway:", anyClient.baseURL);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
484
387
|
}
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
return;
|
|
388
|
+
const originalCreate = client.messages.create.bind(client.messages);
|
|
389
|
+
client.messages.create = async function(params, options) {
|
|
390
|
+
const isProxyRedirected = nwClient.getConfig().enableProxyRedirect && anyClient.baseURL?.includes("/api/v1/proxy/anthropic");
|
|
391
|
+
if (isProxyRedirected) {
|
|
392
|
+
return originalCreate(params, options);
|
|
491
393
|
}
|
|
492
|
-
const
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
394
|
+
const trace = nwClient.startTrace(`anthropic.messages.create`, {
|
|
395
|
+
provider: "anthropic",
|
|
396
|
+
model: params.model
|
|
397
|
+
});
|
|
398
|
+
const span = trace.startSpan("messages.create", import_types.SpanType.LLM);
|
|
399
|
+
span.setMetadata("model", params.model);
|
|
400
|
+
span.setMetadata("provider", "anthropic");
|
|
401
|
+
if (params.temperature !== void 0) span.setMetadata("temperature", params.temperature);
|
|
402
|
+
if (params.max_tokens !== void 0) span.setMetadata("max_tokens", params.max_tokens);
|
|
403
|
+
if (params.system !== void 0) span.setMetadata("system", params.system);
|
|
404
|
+
span.setInput({
|
|
405
|
+
model: params.model,
|
|
406
|
+
messages: params.messages,
|
|
407
|
+
max_tokens: params.max_tokens,
|
|
408
|
+
temperature: params.temperature,
|
|
409
|
+
system: params.system
|
|
410
|
+
});
|
|
411
|
+
const startTime = Date.now();
|
|
412
|
+
try {
|
|
413
|
+
const response = await originalCreate(params, options);
|
|
414
|
+
if (params.stream) {
|
|
415
|
+
return wrapAnthropicStream(response, span, trace, startTime);
|
|
416
|
+
}
|
|
417
|
+
const messageResponse = response;
|
|
418
|
+
const latency = Date.now() - startTime;
|
|
419
|
+
span.setOutput({
|
|
420
|
+
id: messageResponse.id,
|
|
421
|
+
model: messageResponse.model,
|
|
422
|
+
role: messageResponse.role,
|
|
423
|
+
content: messageResponse.content,
|
|
424
|
+
stop_reason: messageResponse.stop_reason
|
|
425
|
+
});
|
|
426
|
+
if (messageResponse.usage) {
|
|
427
|
+
span.setTokens({
|
|
428
|
+
input: messageResponse.usage.input_tokens,
|
|
429
|
+
output: messageResponse.usage.output_tokens,
|
|
430
|
+
total: messageResponse.usage.input_tokens + messageResponse.usage.output_tokens
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
span.setMetadata("latency_ms", latency);
|
|
434
|
+
span.end();
|
|
435
|
+
trace.end();
|
|
436
|
+
return response;
|
|
437
|
+
} catch (error) {
|
|
438
|
+
const latency = Date.now() - startTime;
|
|
439
|
+
span.setMetadata("latency_ms", latency);
|
|
440
|
+
span.setError(error);
|
|
441
|
+
span.end();
|
|
442
|
+
trace.end();
|
|
443
|
+
throw error;
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
return client;
|
|
447
|
+
}
|
|
448
|
+
function wrapAnthropicStream(stream, span, trace, startTime) {
|
|
449
|
+
const bufferedChunks = [];
|
|
450
|
+
let messageId = "";
|
|
451
|
+
let messageModel = "";
|
|
452
|
+
let stopReason = null;
|
|
453
|
+
let inputTokens = 0;
|
|
454
|
+
let outputTokens = 0;
|
|
455
|
+
return {
|
|
456
|
+
[Symbol.asyncIterator]: async function* () {
|
|
457
|
+
try {
|
|
458
|
+
for await (const event of stream) {
|
|
459
|
+
if (event.type === "message_start" && event.message) {
|
|
460
|
+
messageId = event.message.id;
|
|
461
|
+
messageModel = event.message.model;
|
|
462
|
+
if (event.message.usage) {
|
|
463
|
+
inputTokens = event.message.usage.input_tokens;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
if (event.type === "content_block_delta" && event.delta?.text) {
|
|
467
|
+
bufferedChunks.push(event.delta.text);
|
|
468
|
+
}
|
|
469
|
+
if (event.type === "message_delta" && event.delta) {
|
|
470
|
+
if (event.delta.stop_reason) {
|
|
471
|
+
stopReason = event.delta.stop_reason;
|
|
472
|
+
}
|
|
473
|
+
if (event.usage?.output_tokens) {
|
|
474
|
+
outputTokens = event.usage.output_tokens;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
yield event;
|
|
478
|
+
}
|
|
479
|
+
const latency = Date.now() - startTime;
|
|
480
|
+
const completeOutput = bufferedChunks.join("");
|
|
481
|
+
span.setOutput({
|
|
482
|
+
id: messageId,
|
|
483
|
+
model: messageModel,
|
|
484
|
+
content: completeOutput,
|
|
485
|
+
stop_reason: stopReason
|
|
486
|
+
});
|
|
487
|
+
if (inputTokens > 0 || outputTokens > 0) {
|
|
488
|
+
span.setTokens({
|
|
489
|
+
input: inputTokens,
|
|
490
|
+
output: outputTokens,
|
|
491
|
+
total: inputTokens + outputTokens
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
span.setMetadata("latency_ms", latency);
|
|
495
|
+
span.setMetadata("streaming", true);
|
|
496
|
+
span.end();
|
|
497
|
+
trace.end();
|
|
498
|
+
} catch (error) {
|
|
499
|
+
const latency = Date.now() - startTime;
|
|
500
|
+
span.setMetadata("latency_ms", latency);
|
|
501
|
+
span.setError(error);
|
|
502
|
+
span.end();
|
|
503
|
+
trace.end();
|
|
504
|
+
throw error;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// src/instrumentation/index.ts
|
|
511
|
+
function wrap(client, nwClient) {
|
|
512
|
+
if ("chat" in client && client.chat && "completions" in client.chat) {
|
|
513
|
+
return wrapOpenAI(client, nwClient);
|
|
510
514
|
}
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
*/
|
|
514
|
-
toJSON() {
|
|
515
|
-
return { ...this.data };
|
|
515
|
+
if ("messages" in client && client.messages && "create" in client.messages) {
|
|
516
|
+
return wrapAnthropic(client, nwClient);
|
|
516
517
|
}
|
|
517
|
-
|
|
518
|
+
throw new Error(
|
|
519
|
+
"Observyze SDK: Unsupported client type. Supported clients: OpenAI, Anthropic"
|
|
520
|
+
);
|
|
521
|
+
}
|
|
518
522
|
|
|
519
523
|
// src/client.ts
|
|
520
|
-
|
|
524
|
+
var import_fs = __toESM(require("fs"));
|
|
525
|
+
var import_path = __toESM(require("path"));
|
|
526
|
+
var log3 = (0, import_debug3.default)("observyze:sdk");
|
|
521
527
|
var DEFAULT_CONFIG = {
|
|
522
|
-
endpoint: "
|
|
528
|
+
endpoint: "http://localhost:3001",
|
|
523
529
|
batchSize: 100,
|
|
524
530
|
flushInterval: 5e3,
|
|
525
531
|
enableAutoInstrumentation: true,
|
|
526
532
|
debug: false,
|
|
527
533
|
dryRun: false,
|
|
528
|
-
enablePiiRedaction: true
|
|
534
|
+
enablePiiRedaction: true,
|
|
535
|
+
hallucinationThreshold: 0.8,
|
|
536
|
+
safetyThreshold: 0.9,
|
|
537
|
+
confidenceThreshold: 0.4,
|
|
538
|
+
evalEndpoint: process.env.EVAL_ENDPOINT || (process.env.NODE_ENV === "production" ? "https://api.observyze.com" : "http://localhost:3001"),
|
|
539
|
+
enableCircuitBreaker: true,
|
|
540
|
+
failClosed: true,
|
|
541
|
+
enableProxyRedirect: true
|
|
529
542
|
};
|
|
530
543
|
var ObservyzeClient = class _ObservyzeClient {
|
|
531
544
|
config;
|
|
@@ -534,7 +547,37 @@ var ObservyzeClient = class _ObservyzeClient {
|
|
|
534
547
|
isShuttingDown = false;
|
|
535
548
|
MAX_QUEUE_SIZE = 1e3;
|
|
536
549
|
RETRY_DELAYS = [1e3, 2e3, 4e3, 8e3, 16e3, 3e4];
|
|
537
|
-
|
|
550
|
+
/**
|
|
551
|
+
* Parse a JSON API error response and extract trace_id, error code, and message.
|
|
552
|
+
* The api-gateway error handler includes these fields in every error response.
|
|
553
|
+
*/
|
|
554
|
+
static parseApiError(_response, body) {
|
|
555
|
+
try {
|
|
556
|
+
const parsed = JSON.parse(body);
|
|
557
|
+
const error = parsed.error || parsed;
|
|
558
|
+
return {
|
|
559
|
+
traceId: error.trace_id || "unknown",
|
|
560
|
+
code: error.code || "UNKNOWN_ERROR",
|
|
561
|
+
message: error.message || body.slice(0, 200)
|
|
562
|
+
};
|
|
563
|
+
} catch {
|
|
564
|
+
return {
|
|
565
|
+
traceId: "unknown",
|
|
566
|
+
code: "UNKNOWN_ERROR",
|
|
567
|
+
message: body.slice(0, 200)
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
/**
|
|
572
|
+
* Format an API error into a user-friendly message with trace_id for correlation.
|
|
573
|
+
* Example output:
|
|
574
|
+
* "Observyze API error (401 [ref: err_a1b2c3d4]): MISSING_PROVIDER_KEY — No API key configured..."
|
|
575
|
+
*/
|
|
576
|
+
static formatApiError(response, body) {
|
|
577
|
+
const { traceId, code, message } = _ObservyzeClient.parseApiError(response, body);
|
|
578
|
+
const prefix = traceId !== "unknown" ? ` [ref: ${traceId}]` : "";
|
|
579
|
+
return `Observyze API error (${response.status}${prefix}): ${code} \u2014 ${message}`;
|
|
580
|
+
}
|
|
538
581
|
constructor(config) {
|
|
539
582
|
if (!config.apiKey) {
|
|
540
583
|
throw new Error("Observyze SDK: apiKey is required");
|
|
@@ -547,7 +590,7 @@ var ObservyzeClient = class _ObservyzeClient {
|
|
|
547
590
|
};
|
|
548
591
|
this.startFlushTimer();
|
|
549
592
|
if (this.config.debug) {
|
|
550
|
-
|
|
593
|
+
log3("[Observyze SDK] Initialized with config:", {
|
|
551
594
|
endpoint: this.config.endpoint,
|
|
552
595
|
batchSize: this.config.batchSize,
|
|
553
596
|
flushInterval: this.config.flushInterval,
|
|
@@ -568,7 +611,7 @@ var ObservyzeClient = class _ObservyzeClient {
|
|
|
568
611
|
trace.setMetadataAll(metadata);
|
|
569
612
|
}
|
|
570
613
|
const originalEnd = trace.end.bind(trace);
|
|
571
|
-
trace.end = (status =
|
|
614
|
+
trace.end = (status = import_types.TraceStatus.SUCCESS) => {
|
|
572
615
|
originalEnd(status);
|
|
573
616
|
this.bufferTrace(trace);
|
|
574
617
|
};
|
|
@@ -580,23 +623,23 @@ var ObservyzeClient = class _ObservyzeClient {
|
|
|
580
623
|
bufferTrace(trace) {
|
|
581
624
|
if (!trace.isEnded) {
|
|
582
625
|
if (this.config.debug) {
|
|
583
|
-
|
|
626
|
+
log3.extend("warn")("[Observyze SDK] Attempted to buffer a trace that has not ended");
|
|
584
627
|
}
|
|
585
628
|
return;
|
|
586
629
|
}
|
|
587
630
|
if (this.traceBuffer.length >= this.MAX_QUEUE_SIZE) {
|
|
588
631
|
if (this.config.debug) {
|
|
589
|
-
|
|
632
|
+
log3.extend("warn")(`[Observyze SDK] Queue at max capacity (${this.MAX_QUEUE_SIZE}), dropping oldest trace`);
|
|
590
633
|
}
|
|
591
634
|
this.traceBuffer.shift();
|
|
592
635
|
}
|
|
593
636
|
this.traceBuffer.push(trace);
|
|
594
637
|
if (this.config.debug) {
|
|
595
|
-
|
|
638
|
+
log3(`[Observyze SDK] Buffered trace ${trace.id} (${this.traceBuffer.length}/${this.config.batchSize})`);
|
|
596
639
|
}
|
|
597
640
|
if (this.traceBuffer.length >= this.config.batchSize) {
|
|
598
641
|
this.flush().catch((err) => {
|
|
599
|
-
|
|
642
|
+
log3.extend("error")("[Observyze SDK] Error flushing buffer:", err);
|
|
600
643
|
});
|
|
601
644
|
}
|
|
602
645
|
}
|
|
@@ -610,7 +653,7 @@ var ObservyzeClient = class _ObservyzeClient {
|
|
|
610
653
|
this.flushTimer = setInterval(() => {
|
|
611
654
|
if (this.traceBuffer.length > 0) {
|
|
612
655
|
this.flush().catch((err) => {
|
|
613
|
-
|
|
656
|
+
log3.extend("error")("[Observyze SDK] Error in auto-flush:", err);
|
|
614
657
|
});
|
|
615
658
|
}
|
|
616
659
|
}, this.config.flushInterval);
|
|
@@ -627,11 +670,11 @@ var ObservyzeClient = class _ObservyzeClient {
|
|
|
627
670
|
}
|
|
628
671
|
const tracesToSend = this.traceBuffer.splice(0, this.config.batchSize);
|
|
629
672
|
if (this.config.debug) {
|
|
630
|
-
|
|
673
|
+
log3(`[Observyze SDK] Flushing ${tracesToSend.length} traces`);
|
|
631
674
|
}
|
|
632
675
|
if (this.config.dryRun) {
|
|
633
676
|
if (this.config.debug) {
|
|
634
|
-
|
|
677
|
+
log3("[Observyze SDK] Dry-run mode: traces not sent");
|
|
635
678
|
}
|
|
636
679
|
return;
|
|
637
680
|
}
|
|
@@ -642,15 +685,15 @@ var ObservyzeClient = class _ObservyzeClient {
|
|
|
642
685
|
if (remainingSpace > 0) {
|
|
643
686
|
this.traceBuffer.unshift(...tracesToSend.slice(0, remainingSpace));
|
|
644
687
|
if (this.config.debug) {
|
|
645
|
-
|
|
688
|
+
log3(`[Observyze SDK] Re-queued ${Math.min(tracesToSend.length, remainingSpace)} traces after failure`);
|
|
646
689
|
}
|
|
647
690
|
} else {
|
|
648
691
|
if (this.config.debug) {
|
|
649
|
-
|
|
692
|
+
log3.extend("warn")(`[Observyze SDK] Queue full, dropped ${tracesToSend.length} traces`);
|
|
650
693
|
}
|
|
651
694
|
}
|
|
652
695
|
if (this.config.debug) {
|
|
653
|
-
|
|
696
|
+
log3.extend("error")("[Observyze SDK] Failed to send traces after retries:", error);
|
|
654
697
|
}
|
|
655
698
|
throw error;
|
|
656
699
|
}
|
|
@@ -680,10 +723,11 @@ var ObservyzeClient = class _ObservyzeClient {
|
|
|
680
723
|
});
|
|
681
724
|
if (!response.ok) {
|
|
682
725
|
const errorBody = await response.text();
|
|
683
|
-
|
|
726
|
+
const formatted = _ObservyzeClient.formatApiError(response, errorBody);
|
|
727
|
+
throw new Error(`[Observyze SDK] Trace ingestion failed. ${formatted}`);
|
|
684
728
|
}
|
|
685
729
|
if (this.config.debug) {
|
|
686
|
-
|
|
730
|
+
log3(`[Observyze SDK] Successfully sent ${traces.length} traces${attempt > 0 ? ` (after ${attempt} retries)` : ""}`);
|
|
687
731
|
}
|
|
688
732
|
return;
|
|
689
733
|
} catch (error) {
|
|
@@ -693,7 +737,7 @@ var ObservyzeClient = class _ObservyzeClient {
|
|
|
693
737
|
}
|
|
694
738
|
const delay = this.RETRY_DELAYS[attempt];
|
|
695
739
|
if (this.config.debug) {
|
|
696
|
-
|
|
740
|
+
log3.extend("warn")(`[Observyze SDK] Attempt ${attempt + 1} failed, retrying in ${delay}ms...`, error);
|
|
697
741
|
}
|
|
698
742
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
699
743
|
}
|
|
@@ -709,7 +753,7 @@ var ObservyzeClient = class _ObservyzeClient {
|
|
|
709
753
|
}
|
|
710
754
|
this.isShuttingDown = true;
|
|
711
755
|
if (this.config.debug) {
|
|
712
|
-
|
|
756
|
+
log3("[Observyze SDK] Shutting down...");
|
|
713
757
|
}
|
|
714
758
|
if (this.flushTimer) {
|
|
715
759
|
clearInterval(this.flushTimer);
|
|
@@ -718,10 +762,10 @@ var ObservyzeClient = class _ObservyzeClient {
|
|
|
718
762
|
try {
|
|
719
763
|
await this.flush();
|
|
720
764
|
} catch (error) {
|
|
721
|
-
|
|
765
|
+
log3.extend("error")("[Observyze SDK] Error during shutdown flush:", error);
|
|
722
766
|
}
|
|
723
767
|
if (this.config.debug) {
|
|
724
|
-
|
|
768
|
+
log3("[Observyze SDK] Shutdown complete");
|
|
725
769
|
}
|
|
726
770
|
}
|
|
727
771
|
/**
|
|
@@ -758,8 +802,7 @@ var ObservyzeClient = class _ObservyzeClient {
|
|
|
758
802
|
* ```
|
|
759
803
|
*/
|
|
760
804
|
wrap(client) {
|
|
761
|
-
|
|
762
|
-
return wrapClient(client, this);
|
|
805
|
+
return wrap(client, this);
|
|
763
806
|
}
|
|
764
807
|
/**
|
|
765
808
|
* Sync local agent .history file to Observyze cloud
|
|
@@ -770,13 +813,11 @@ var ObservyzeClient = class _ObservyzeClient {
|
|
|
770
813
|
if (typeof process === "undefined" || !process.versions?.node) {
|
|
771
814
|
throw new Error("syncLocalHistory is only available in Node.js environments");
|
|
772
815
|
}
|
|
773
|
-
const
|
|
774
|
-
|
|
775
|
-
const fullPath = path.resolve(process.cwd(), filePath);
|
|
776
|
-
if (!fs.existsSync(fullPath)) {
|
|
816
|
+
const fullPath = import_path.default.resolve(process.cwd(), filePath);
|
|
817
|
+
if (!import_fs.default.existsSync(fullPath)) {
|
|
777
818
|
throw new Error(`History file not found: ${fullPath}`);
|
|
778
819
|
}
|
|
779
|
-
const content =
|
|
820
|
+
const content = import_fs.default.readFileSync(fullPath, "utf-8");
|
|
780
821
|
let items = [];
|
|
781
822
|
try {
|
|
782
823
|
items = JSON.parse(content);
|
|
@@ -787,7 +828,7 @@ var ObservyzeClient = class _ObservyzeClient {
|
|
|
787
828
|
items = [items];
|
|
788
829
|
}
|
|
789
830
|
if (this.config.debug) {
|
|
790
|
-
|
|
831
|
+
log3(`[Observyze SDK] Syncing ${items.length} traces from ${filePath}`);
|
|
791
832
|
}
|
|
792
833
|
for (let i = 0; i < items.length; i += this.config.batchSize) {
|
|
793
834
|
const batch = items.slice(i, i + this.config.batchSize);
|
|
@@ -801,14 +842,15 @@ var ObservyzeClient = class _ObservyzeClient {
|
|
|
801
842
|
});
|
|
802
843
|
if (!response.ok) {
|
|
803
844
|
const errorBody = await response.text();
|
|
804
|
-
|
|
845
|
+
const formatted = _ObservyzeClient.formatApiError(response, errorBody);
|
|
846
|
+
throw new Error(`[Observyze SDK] Local history sync failed. ${formatted}`);
|
|
805
847
|
}
|
|
806
848
|
if (this.config.debug) {
|
|
807
|
-
|
|
849
|
+
log3(`[Observyze SDK] Synced batch of ${batch.length} traces from local history`);
|
|
808
850
|
}
|
|
809
851
|
}
|
|
810
852
|
} catch (err) {
|
|
811
|
-
|
|
853
|
+
log3.extend("error")("[Observyze SDK] Failed to sync local history:", err);
|
|
812
854
|
throw err;
|
|
813
855
|
}
|
|
814
856
|
}
|
|
@@ -905,14 +947,225 @@ var ObservyzeClient = class _ObservyzeClient {
|
|
|
905
947
|
}
|
|
906
948
|
return data;
|
|
907
949
|
}
|
|
950
|
+
/**
|
|
951
|
+
* Phase 4: Autonomous Circuit Breakers (Requirement 4.1)
|
|
952
|
+
* Evaluate a trace or text for hallucination in real-time.
|
|
953
|
+
* If hallucination score > hallucinationThreshold, the SDK blocks execution.
|
|
954
|
+
*
|
|
955
|
+
* Returns GuardrailResult with score: null when evaluation couldn't be performed.
|
|
956
|
+
* In failClosed mode, null scores result in blocked execution.
|
|
957
|
+
* In failOpen mode, null scores allow execution through.
|
|
958
|
+
*/
|
|
959
|
+
async checkGuardrails(content) {
|
|
960
|
+
if (!this.config.enableCircuitBreaker) {
|
|
961
|
+
return { pass: true, score: 0, safetyScore: 0, evaluationSource: "disabled" };
|
|
962
|
+
}
|
|
963
|
+
try {
|
|
964
|
+
if (this.config.debug) {
|
|
965
|
+
log3(`[Observyze Guardrail] Analyzing payload for hallucination anomalies...`);
|
|
966
|
+
}
|
|
967
|
+
const evalEndpoint = this.config.evalEndpoint;
|
|
968
|
+
const payload = typeof content === "string" ? { text: content, organization_id: this.config.organizationId } : { trace: content, organization_id: this.config.organizationId };
|
|
969
|
+
const controller = new AbortController();
|
|
970
|
+
const timeout = setTimeout(() => controller.abort(), 5e3);
|
|
971
|
+
let evalResult = null;
|
|
972
|
+
try {
|
|
973
|
+
const response = await fetch(`${evalEndpoint}/api/v1/evaluate/hallucination`, {
|
|
974
|
+
method: "POST",
|
|
975
|
+
headers: {
|
|
976
|
+
"Content-Type": "application/json",
|
|
977
|
+
"Authorization": `Bearer ${this.config.apiKey}`
|
|
978
|
+
},
|
|
979
|
+
body: JSON.stringify(payload),
|
|
980
|
+
signal: controller.signal
|
|
981
|
+
});
|
|
982
|
+
clearTimeout(timeout);
|
|
983
|
+
if (response.ok) {
|
|
984
|
+
evalResult = await response.json();
|
|
985
|
+
} else {
|
|
986
|
+
const errorBody = await response.text();
|
|
987
|
+
const { traceId, code, message } = _ObservyzeClient.parseApiError(response, errorBody);
|
|
988
|
+
if (this.config.debug) {
|
|
989
|
+
log3.extend("warn")(`[Observyze Guardrail] Eval returned ${response.status} [${code}] (ref: ${traceId}): ${message}`);
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
} catch (fetchError) {
|
|
993
|
+
clearTimeout(timeout);
|
|
994
|
+
if (fetchError.name === "AbortError") {
|
|
995
|
+
if (this.config.debug) {
|
|
996
|
+
log3.extend("warn")("[Observyze Guardrail] Evaluation timed out after 5s");
|
|
997
|
+
}
|
|
998
|
+
} else if (this.config.debug) {
|
|
999
|
+
log3.extend("warn")("[Observyze Guardrail] Evaluation request failed:", fetchError.message);
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
let hallucinationScore = 0;
|
|
1003
|
+
let safetyScore = 0;
|
|
1004
|
+
let evaluationSource = "live";
|
|
1005
|
+
let confidence = null;
|
|
1006
|
+
if (evalResult) {
|
|
1007
|
+
hallucinationScore = evalResult.score ?? evalResult.hallucination_score ?? null;
|
|
1008
|
+
safetyScore = evalResult.safety_score ?? 0;
|
|
1009
|
+
evaluationSource = evalResult.evaluation_source ?? "live";
|
|
1010
|
+
confidence = evalResult.confidence ?? null;
|
|
1011
|
+
if (hallucinationScore === null) {
|
|
1012
|
+
if (this.config.failClosed) {
|
|
1013
|
+
if (this.config.debug) {
|
|
1014
|
+
log3.extend("warn")("[Observyze Guardrail] Eval returned null score \u2014 failing closed (blocking)");
|
|
1015
|
+
}
|
|
1016
|
+
return {
|
|
1017
|
+
pass: false,
|
|
1018
|
+
score: null,
|
|
1019
|
+
confidence: null,
|
|
1020
|
+
safetyScore: null,
|
|
1021
|
+
evaluationSource: "error",
|
|
1022
|
+
fallbackReason: evalResult.message || "Evaluation failed to produce a score",
|
|
1023
|
+
reason: "Evaluation service failed to produce a score. Fail-closed: execution blocked."
|
|
1024
|
+
};
|
|
1025
|
+
}
|
|
1026
|
+
if (this.config.debug) {
|
|
1027
|
+
log3("[Observyze Guardrail] Eval returned null score \u2014 allowing (fail-open)");
|
|
1028
|
+
}
|
|
1029
|
+
return {
|
|
1030
|
+
pass: true,
|
|
1031
|
+
score: null,
|
|
1032
|
+
confidence: null,
|
|
1033
|
+
safetyScore: null,
|
|
1034
|
+
evaluationSource: "error",
|
|
1035
|
+
fallbackReason: evalResult.message || "Evaluation failed to produce a score"
|
|
1036
|
+
};
|
|
1037
|
+
}
|
|
1038
|
+
} else if (this.config.failClosed) {
|
|
1039
|
+
if (this.config.debug) {
|
|
1040
|
+
log3.extend("warn")("[Observyze Guardrail] Eval unavailable \u2014 failing closed (blocking)");
|
|
1041
|
+
}
|
|
1042
|
+
return {
|
|
1043
|
+
pass: false,
|
|
1044
|
+
score: null,
|
|
1045
|
+
confidence: null,
|
|
1046
|
+
safetyScore: null,
|
|
1047
|
+
evaluationSource: "error",
|
|
1048
|
+
fallbackReason: "Evaluation service unreachable",
|
|
1049
|
+
reason: "Evaluation service unreachable. Fail-closed: execution blocked."
|
|
1050
|
+
};
|
|
1051
|
+
} else {
|
|
1052
|
+
if (this.config.debug) {
|
|
1053
|
+
log3("[Observyze Guardrail] Eval unavailable \u2014 allowing (fail-open)");
|
|
1054
|
+
}
|
|
1055
|
+
return {
|
|
1056
|
+
pass: true,
|
|
1057
|
+
score: null,
|
|
1058
|
+
confidence: null,
|
|
1059
|
+
safetyScore: null,
|
|
1060
|
+
evaluationSource: "error",
|
|
1061
|
+
fallbackReason: "Evaluation service unreachable"
|
|
1062
|
+
};
|
|
1063
|
+
}
|
|
1064
|
+
const hallThreshold = this.config.hallucinationThreshold;
|
|
1065
|
+
const safeThreshold = this.config.safetyThreshold;
|
|
1066
|
+
const confThreshold = this.config.confidenceThreshold;
|
|
1067
|
+
if (confidence !== null && confidence < confThreshold) {
|
|
1068
|
+
if (hallucinationScore >= hallThreshold) {
|
|
1069
|
+
if (this.config.debug) {
|
|
1070
|
+
log3.extend("warn")(`[Observyze Guardrail] High score (${hallucinationScore.toFixed(2)}) but low confidence (${confidence.toFixed(2)}). Alerting only.`);
|
|
1071
|
+
}
|
|
1072
|
+
return {
|
|
1073
|
+
pass: true,
|
|
1074
|
+
score: hallucinationScore,
|
|
1075
|
+
confidence,
|
|
1076
|
+
safetyScore,
|
|
1077
|
+
evaluationSource,
|
|
1078
|
+
reason: `Score ${hallucinationScore.toFixed(2)} but confidence ${confidence.toFixed(2)} is low. Execution allowed with alert.`
|
|
1079
|
+
};
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
if (hallucinationScore >= hallThreshold) {
|
|
1083
|
+
if (this.config.debug) {
|
|
1084
|
+
log3.extend("warn")(`[Observyze Guardrail] Hallucination circuit breached! Score: ${hallucinationScore.toFixed(2)} >= ${hallThreshold}`);
|
|
1085
|
+
}
|
|
1086
|
+
return {
|
|
1087
|
+
pass: false,
|
|
1088
|
+
score: hallucinationScore,
|
|
1089
|
+
confidence,
|
|
1090
|
+
safetyScore,
|
|
1091
|
+
evaluationSource,
|
|
1092
|
+
reason: `Hallucination score ${hallucinationScore.toFixed(2)} exceeds threshold ${hallThreshold}. Execution blocked for human review.`
|
|
1093
|
+
};
|
|
1094
|
+
}
|
|
1095
|
+
if (safetyScore !== null && safetyScore >= safeThreshold) {
|
|
1096
|
+
if (this.config.debug) {
|
|
1097
|
+
log3.extend("warn")(`[Observyze Guardrail] Safety circuit breached! Score: ${safetyScore.toFixed(2)} >= ${safeThreshold}`);
|
|
1098
|
+
}
|
|
1099
|
+
return {
|
|
1100
|
+
pass: false,
|
|
1101
|
+
score: hallucinationScore,
|
|
1102
|
+
confidence,
|
|
1103
|
+
safetyScore,
|
|
1104
|
+
evaluationSource,
|
|
1105
|
+
reason: `Safety score ${safetyScore.toFixed(2)} exceeds threshold ${safeThreshold}. Execution blocked for safety review.`
|
|
1106
|
+
};
|
|
1107
|
+
}
|
|
1108
|
+
return { pass: true, score: hallucinationScore, confidence, safetyScore, evaluationSource };
|
|
1109
|
+
} catch (err) {
|
|
1110
|
+
log3.extend("error")("[Observyze Guardrail] Failed to evaluate:", err);
|
|
1111
|
+
if (this.config.failClosed) {
|
|
1112
|
+
return { pass: false, score: null, confidence: null, safetyScore: null, evaluationSource: "error", fallbackReason: "Guardrail exception", reason: "Guardrail error \u2014 fail-closed: execution blocked." };
|
|
1113
|
+
}
|
|
1114
|
+
return { pass: true, score: null, confidence: null, safetyScore: null, evaluationSource: "error", fallbackReason: "Guardrail exception" };
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
/**
|
|
1118
|
+
* Phase 4: Autonomous Circuit Breakers
|
|
1119
|
+
* Execute an agent action wrapped with the Circuit Breaker.
|
|
1120
|
+
* Pauses execution if hallucination score >= hallucinationThreshold and requests human review.
|
|
1121
|
+
* @throws Error when execution is blocked by circuit breaker
|
|
1122
|
+
*/
|
|
1123
|
+
async executeWithCircuitBreaker(agentExecution, traceContext) {
|
|
1124
|
+
if (!this.config.enableCircuitBreaker) {
|
|
1125
|
+
return await agentExecution();
|
|
1126
|
+
}
|
|
1127
|
+
const guardResult = await this.checkGuardrails(traceContext || "execution context");
|
|
1128
|
+
if (!guardResult.pass) {
|
|
1129
|
+
const error = new Error(`[Observyze] Execution Blocked by Autonomous Circuit Breaker. Hallucination: ${guardResult.score?.toFixed(2) ?? "N/A"}, Safety: ${(guardResult.safetyScore ?? 0)?.toFixed(2) ?? "N/A"}. Reason: ${guardResult.reason}. Human approval required before agent can continue.`);
|
|
1130
|
+
if (this.config.debug) {
|
|
1131
|
+
log3.extend("error")("[Observyze CircuitBreaker] Execution blocked:", error.message);
|
|
1132
|
+
}
|
|
1133
|
+
throw error;
|
|
1134
|
+
}
|
|
1135
|
+
if (this.config.debug) {
|
|
1136
|
+
log3(`[Observyze CircuitBreaker] Execution allowed. Hallucination: ${guardResult.score?.toFixed(2) ?? "N/A"}, Safety: ${(guardResult.safetyScore ?? 0)?.toFixed(2) ?? "N/A"}`);
|
|
1137
|
+
}
|
|
1138
|
+
return await agentExecution();
|
|
1139
|
+
}
|
|
1140
|
+
/**
|
|
1141
|
+
* Phase 4: Bug Bounty Protocol (Automated) (Requirement 4.2)
|
|
1142
|
+
* Automatically shard persistent failure cases to external security researcher endpoints (e.g. HackerOne wrapper)
|
|
1143
|
+
*/
|
|
1144
|
+
async reportBugBounty(traceId, securityEndpoint, failureContext) {
|
|
1145
|
+
try {
|
|
1146
|
+
if (this.config.debug) {
|
|
1147
|
+
log3(`[Observyze SDK] Sharding persistent failure case ${traceId} to Bug Bounty Protocol endpoint...`);
|
|
1148
|
+
}
|
|
1149
|
+
await fetch(securityEndpoint, {
|
|
1150
|
+
method: "POST",
|
|
1151
|
+
headers: { "Content-Type": "application/json" },
|
|
1152
|
+
body: JSON.stringify({
|
|
1153
|
+
alert: "persistent_failure_sharded",
|
|
1154
|
+
trace_id: traceId,
|
|
1155
|
+
context: failureContext,
|
|
1156
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1157
|
+
})
|
|
1158
|
+
});
|
|
1159
|
+
if (this.config.debug) {
|
|
1160
|
+
log3(`[Observyze SDK] Bug Bounty payload successfully transmitted.`);
|
|
1161
|
+
}
|
|
1162
|
+
} catch (err) {
|
|
1163
|
+
log3.extend("error")("[Observyze Bug Bounty] Failed to shard failure case:", err);
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
908
1166
|
};
|
|
909
1167
|
|
|
910
|
-
// src/index.ts
|
|
911
|
-
init_types();
|
|
912
|
-
init_instrumentation();
|
|
913
|
-
|
|
914
1168
|
// src/opentelemetry/exporter.ts
|
|
915
|
-
init_types();
|
|
916
1169
|
var ObservyzeSpanExporter = class {
|
|
917
1170
|
client;
|
|
918
1171
|
config;
|
|
@@ -982,9 +1235,9 @@ var ObservyzeSpanExporter = class {
|
|
|
982
1235
|
const statusCode = status?.code;
|
|
983
1236
|
if (statusCode === 2) {
|
|
984
1237
|
oSpan.setError(new Error(status?.message || "OTel span error"));
|
|
985
|
-
trace.end(
|
|
1238
|
+
trace.end(import_types.TraceStatus.ERROR);
|
|
986
1239
|
} else {
|
|
987
|
-
trace.end(
|
|
1240
|
+
trace.end(import_types.TraceStatus.SUCCESS);
|
|
988
1241
|
}
|
|
989
1242
|
}
|
|
990
1243
|
resultCallback({ code: 0 });
|