@visibe.ai/node 0.1.21 → 0.1.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/index.js +121 -7
- package/dist/cjs/integrations/langchain.js +104 -0
- package/dist/cjs/integrations/openai.js +6 -4
- package/dist/esm/index.js +121 -7
- package/dist/esm/integrations/langchain.js +103 -0
- package/dist/esm/integrations/openai.js +6 -4
- package/dist/types/integrations/langchain.d.ts +1 -0
- package/package.json +1 -1
package/dist/cjs/index.js
CHANGED
|
@@ -100,6 +100,8 @@ async function patchFramework(framework, client) {
|
|
|
100
100
|
try {
|
|
101
101
|
switch (framework) {
|
|
102
102
|
case 'openai': {
|
|
103
|
+
if (_originalOpenAI)
|
|
104
|
+
break; // already patched by _syncPatch()
|
|
103
105
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
104
106
|
const openaiModule = await Promise.resolve().then(() => __importStar(require('openai')));
|
|
105
107
|
_originalOpenAI = openaiModule.OpenAI ?? openaiModule.default;
|
|
@@ -128,6 +130,8 @@ async function patchFramework(framework, client) {
|
|
|
128
130
|
break;
|
|
129
131
|
}
|
|
130
132
|
case 'anthropic': {
|
|
133
|
+
if (_originalAnthropic)
|
|
134
|
+
break; // already patched by _syncPatch()
|
|
131
135
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
132
136
|
const anthropicModule = await Promise.resolve().then(() => __importStar(require('@anthropic-ai/sdk')));
|
|
133
137
|
_originalAnthropic = anthropicModule.Anthropic ?? anthropicModule.default;
|
|
@@ -154,6 +158,8 @@ async function patchFramework(framework, client) {
|
|
|
154
158
|
break;
|
|
155
159
|
}
|
|
156
160
|
case 'bedrock': {
|
|
161
|
+
if (_originalBedrockClient)
|
|
162
|
+
break; // already patched by _syncPatch()
|
|
157
163
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
158
164
|
const bedrockModule = await Promise.resolve().then(() => __importStar(require('@aws-sdk/client-bedrock-runtime')));
|
|
159
165
|
_originalBedrockClient = bedrockModule.BedrockRuntimeClient;
|
|
@@ -190,12 +196,25 @@ async function patchFramework(framework, client) {
|
|
|
190
196
|
}
|
|
191
197
|
case 'langchain': {
|
|
192
198
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
193
|
-
const { patchRunnableSequence } = await Promise.resolve().then(() => __importStar(require('./integrations/langchain')));
|
|
199
|
+
const { patchRunnableSequence, patchAgentExecutor } = await Promise.resolve().then(() => __importStar(require('./integrations/langchain')));
|
|
194
200
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
195
201
|
const lcModule = await Promise.resolve().then(() => __importStar(require('@langchain/core/runnables')));
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
|
|
202
|
+
const r1 = patchRunnableSequence(lcModule, client);
|
|
203
|
+
// Also patch AgentExecutor (langchain/agents) if installed — AgentExecutor is NOT a
|
|
204
|
+
// RunnableSequence so without this patch each agent loop iteration creates a separate trace.
|
|
205
|
+
let r2;
|
|
206
|
+
try {
|
|
207
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
208
|
+
const agentsModule = await Promise.resolve().then(() => __importStar(require('langchain/agents')));
|
|
209
|
+
r2 = patchAgentExecutor(agentsModule, client);
|
|
210
|
+
}
|
|
211
|
+
catch { /* langchain package not installed — skip */ }
|
|
212
|
+
_lcRestore = () => {
|
|
213
|
+
if (typeof r1 === 'function')
|
|
214
|
+
r1();
|
|
215
|
+
if (typeof r2 === 'function')
|
|
216
|
+
r2();
|
|
217
|
+
};
|
|
199
218
|
break;
|
|
200
219
|
}
|
|
201
220
|
case 'vercel_ai': {
|
|
@@ -225,6 +244,98 @@ async function _autoPatch(client, frameworks) {
|
|
|
225
244
|
}
|
|
226
245
|
}
|
|
227
246
|
// ---------------------------------------------------------------------------
|
|
247
|
+
// _syncPatch() — synchronous CJS patching.
|
|
248
|
+
//
|
|
249
|
+
// In CJS builds, require() is synchronous so we can patch framework modules
|
|
250
|
+
// inline inside init() BEFORE it returns. This means instances created right
|
|
251
|
+
// after init() (e.g. `const client = new OpenAI()`) are already using the
|
|
252
|
+
// patched class — no async race condition.
|
|
253
|
+
//
|
|
254
|
+
// In ESM builds, require is undefined — the entire function silently no-ops
|
|
255
|
+
// and _autoPatch() handles instrumentation via dynamic import() instead.
|
|
256
|
+
// ---------------------------------------------------------------------------
|
|
257
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
258
|
+
function _makePatchedClass(Orig, client) {
|
|
259
|
+
return class Patched extends Orig {
|
|
260
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
261
|
+
constructor(...args) {
|
|
262
|
+
super(...args);
|
|
263
|
+
try {
|
|
264
|
+
client.instrument(this);
|
|
265
|
+
}
|
|
266
|
+
catch { /* never crash constructor */ }
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
function _syncPatch(client, frameworksToTry) {
|
|
271
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
272
|
+
if (typeof require === 'undefined')
|
|
273
|
+
return;
|
|
274
|
+
for (const fw of frameworksToTry) {
|
|
275
|
+
try {
|
|
276
|
+
switch (fw) {
|
|
277
|
+
case 'openai': {
|
|
278
|
+
if (_originalOpenAI)
|
|
279
|
+
break;
|
|
280
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-explicit-any
|
|
281
|
+
const m = require('openai');
|
|
282
|
+
const Orig = m.OpenAI ?? m.default;
|
|
283
|
+
if (!Orig)
|
|
284
|
+
break;
|
|
285
|
+
_originalOpenAI = Orig;
|
|
286
|
+
const Patched = _makePatchedClass(Orig, client);
|
|
287
|
+
try {
|
|
288
|
+
_setProp(m, 'OpenAI', Patched);
|
|
289
|
+
_setProp(m, 'default', Patched);
|
|
290
|
+
}
|
|
291
|
+
catch {
|
|
292
|
+
_originalOpenAI = null;
|
|
293
|
+
}
|
|
294
|
+
break;
|
|
295
|
+
}
|
|
296
|
+
case 'anthropic': {
|
|
297
|
+
if (_originalAnthropic)
|
|
298
|
+
break;
|
|
299
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-explicit-any
|
|
300
|
+
const m = require('@anthropic-ai/sdk');
|
|
301
|
+
const Orig = m.Anthropic ?? m.default;
|
|
302
|
+
if (!Orig)
|
|
303
|
+
break;
|
|
304
|
+
_originalAnthropic = Orig;
|
|
305
|
+
const Patched = _makePatchedClass(Orig, client);
|
|
306
|
+
try {
|
|
307
|
+
_setProp(m, 'Anthropic', Patched);
|
|
308
|
+
_setProp(m, 'default', Patched);
|
|
309
|
+
}
|
|
310
|
+
catch {
|
|
311
|
+
_originalAnthropic = null;
|
|
312
|
+
}
|
|
313
|
+
break;
|
|
314
|
+
}
|
|
315
|
+
case 'bedrock': {
|
|
316
|
+
if (_originalBedrockClient)
|
|
317
|
+
break;
|
|
318
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-explicit-any
|
|
319
|
+
const m = require('@aws-sdk/client-bedrock-runtime');
|
|
320
|
+
const Orig = m.BedrockRuntimeClient;
|
|
321
|
+
if (!Orig)
|
|
322
|
+
break;
|
|
323
|
+
_originalBedrockClient = Orig;
|
|
324
|
+
const Patched = _makePatchedClass(Orig, client);
|
|
325
|
+
try {
|
|
326
|
+
m.BedrockRuntimeClient = Patched;
|
|
327
|
+
}
|
|
328
|
+
catch {
|
|
329
|
+
_originalBedrockClient = null;
|
|
330
|
+
}
|
|
331
|
+
break;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
catch { /* package not installed — skip */ }
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
// ---------------------------------------------------------------------------
|
|
228
339
|
// init()
|
|
229
340
|
// ---------------------------------------------------------------------------
|
|
230
341
|
function init(options) {
|
|
@@ -233,10 +344,13 @@ function init(options) {
|
|
|
233
344
|
return _globalClient;
|
|
234
345
|
}
|
|
235
346
|
_globalClient = new client_1.Visibe(options ?? {});
|
|
236
|
-
// Fire async patching — works in both CJS and ESM via dynamic import().
|
|
237
|
-
// Patching typically completes within microseconds (cached modules) so there
|
|
238
|
-
// is no practical race condition for normal usage patterns.
|
|
239
347
|
const frameworksToTry = options?.frameworks ?? ALL_FRAMEWORKS;
|
|
348
|
+
// Synchronous CJS path — patches module exports inline so instances created
|
|
349
|
+
// right after init() (e.g. `const client = new OpenAI()`) are instrumented.
|
|
350
|
+
// In ESM, require is undefined and this silently no-ops.
|
|
351
|
+
_syncPatch(_globalClient, frameworksToTry);
|
|
352
|
+
// Async path — handles ESM and framework detection for LangChain/LangGraph
|
|
353
|
+
// (prototype patches don't need to be synchronous).
|
|
240
354
|
_autoPatch(_globalClient, frameworksToTry).catch(() => { });
|
|
241
355
|
if (!_shutdownRegistered) {
|
|
242
356
|
const graceful = async () => { await shutdown(); process.exit(0); };
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.LangChainCallback = exports.LANGGRAPH_INTERNAL_NODES = exports.activeLangChainStorage = void 0;
|
|
4
4
|
exports.patchRunnableSequence = patchRunnableSequence;
|
|
5
|
+
exports.patchAgentExecutor = patchAgentExecutor;
|
|
5
6
|
const node_async_hooks_1 = require("node:async_hooks");
|
|
6
7
|
const node_crypto_1 = require("node:crypto");
|
|
7
8
|
const utils_1 = require("../utils");
|
|
@@ -312,6 +313,109 @@ function patchRunnableSequence(lcModule, visibe) {
|
|
|
312
313
|
};
|
|
313
314
|
}
|
|
314
315
|
// ---------------------------------------------------------------------------
|
|
316
|
+
// patchAgentExecutor — patches AgentExecutor from `langchain/agents` so the
|
|
317
|
+
// entire multi-step agent loop is captured as ONE trace.
|
|
318
|
+
//
|
|
319
|
+
// AgentExecutor is NOT a RunnableSequence — it extends Runnable directly and
|
|
320
|
+
// calls the inner agent RunnableSequence in a loop. Without this patch each
|
|
321
|
+
// iteration fires patchRunnableSequence and creates a separate trace.
|
|
322
|
+
// ---------------------------------------------------------------------------
|
|
323
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
324
|
+
function patchAgentExecutor(agentsModule, visibe) {
|
|
325
|
+
const AgentExecutor = agentsModule?.AgentExecutor;
|
|
326
|
+
if (!AgentExecutor)
|
|
327
|
+
return () => { };
|
|
328
|
+
const originalInvoke = AgentExecutor.prototype.invoke;
|
|
329
|
+
const originalStream = AgentExecutor.prototype.stream;
|
|
330
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
331
|
+
AgentExecutor.prototype.invoke = async function (input, config) {
|
|
332
|
+
if (exports.activeLangChainStorage.getStore() !== undefined) {
|
|
333
|
+
return originalInvoke.call(this, input, config);
|
|
334
|
+
}
|
|
335
|
+
const traceId = (0, node_crypto_1.randomUUID)();
|
|
336
|
+
const startedAt = new Date().toISOString();
|
|
337
|
+
const startMs = Date.now();
|
|
338
|
+
const agentName = this.name ?? 'langchain';
|
|
339
|
+
await visibe.apiClient.createTrace({
|
|
340
|
+
trace_id: traceId,
|
|
341
|
+
name: agentName,
|
|
342
|
+
framework: 'langchain',
|
|
343
|
+
started_at: startedAt,
|
|
344
|
+
...(visibe.sessionId ? { session_id: visibe.sessionId } : {}),
|
|
345
|
+
});
|
|
346
|
+
const cb = new LangChainCallback({ visibe, traceId, agentName });
|
|
347
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
348
|
+
let result;
|
|
349
|
+
let status = 'completed';
|
|
350
|
+
try {
|
|
351
|
+
result = await exports.activeLangChainStorage.run(cb, () => originalInvoke.call(this, input, _mergeCallbacks(config, cb)));
|
|
352
|
+
}
|
|
353
|
+
catch (err) {
|
|
354
|
+
status = 'failed';
|
|
355
|
+
throw err;
|
|
356
|
+
}
|
|
357
|
+
finally {
|
|
358
|
+
visibe.batcher.flush();
|
|
359
|
+
await visibe.apiClient.completeTrace(traceId, {
|
|
360
|
+
status,
|
|
361
|
+
ended_at: new Date().toISOString(),
|
|
362
|
+
duration_ms: Date.now() - startMs,
|
|
363
|
+
llm_call_count: cb.llmCallCount,
|
|
364
|
+
total_cost: cb.totalCost,
|
|
365
|
+
total_tokens: cb.totalInputTokens + cb.totalOutputTokens,
|
|
366
|
+
total_input_tokens: cb.totalInputTokens,
|
|
367
|
+
total_output_tokens: cb.totalOutputTokens,
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
return result;
|
|
371
|
+
};
|
|
372
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
373
|
+
AgentExecutor.prototype.stream = async function* (input, config) {
|
|
374
|
+
if (exports.activeLangChainStorage.getStore() !== undefined) {
|
|
375
|
+
yield* (await originalStream.call(this, input, config));
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
const traceId = (0, node_crypto_1.randomUUID)();
|
|
379
|
+
const startedAt = new Date().toISOString();
|
|
380
|
+
const startMs = Date.now();
|
|
381
|
+
const agentName = this.name ?? 'langchain';
|
|
382
|
+
await visibe.apiClient.createTrace({
|
|
383
|
+
trace_id: traceId,
|
|
384
|
+
name: agentName,
|
|
385
|
+
framework: 'langchain',
|
|
386
|
+
started_at: startedAt,
|
|
387
|
+
...(visibe.sessionId ? { session_id: visibe.sessionId } : {}),
|
|
388
|
+
});
|
|
389
|
+
const cb = new LangChainCallback({ visibe, traceId, agentName });
|
|
390
|
+
let status = 'completed';
|
|
391
|
+
try {
|
|
392
|
+
const gen = await exports.activeLangChainStorage.run(cb, () => originalStream.call(this, input, _mergeCallbacks(config, cb)));
|
|
393
|
+
yield* gen;
|
|
394
|
+
}
|
|
395
|
+
catch (err) {
|
|
396
|
+
status = 'failed';
|
|
397
|
+
throw err;
|
|
398
|
+
}
|
|
399
|
+
finally {
|
|
400
|
+
visibe.batcher.flush();
|
|
401
|
+
await visibe.apiClient.completeTrace(traceId, {
|
|
402
|
+
status,
|
|
403
|
+
ended_at: new Date().toISOString(),
|
|
404
|
+
duration_ms: Date.now() - startMs,
|
|
405
|
+
llm_call_count: cb.llmCallCount,
|
|
406
|
+
total_cost: cb.totalCost,
|
|
407
|
+
total_tokens: cb.totalInputTokens + cb.totalOutputTokens,
|
|
408
|
+
total_input_tokens: cb.totalInputTokens,
|
|
409
|
+
total_output_tokens: cb.totalOutputTokens,
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
return () => {
|
|
414
|
+
AgentExecutor.prototype.invoke = originalInvoke;
|
|
415
|
+
AgentExecutor.prototype.stream = originalStream;
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
// ---------------------------------------------------------------------------
|
|
315
419
|
// Private helpers
|
|
316
420
|
// ---------------------------------------------------------------------------
|
|
317
421
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -152,8 +152,9 @@ class OpenAIIntegration extends base_1.BaseIntegration {
|
|
|
152
152
|
throw err;
|
|
153
153
|
}
|
|
154
154
|
const model = response.model ?? params.model ?? 'unknown';
|
|
155
|
-
|
|
156
|
-
const
|
|
155
|
+
// Support both openai v4 (prompt_tokens/completion_tokens) and v5/v6 (input_tokens/output_tokens)
|
|
156
|
+
const inputTokens = response.usage?.prompt_tokens ?? response.usage?.input_tokens ?? 0;
|
|
157
|
+
const outputTokens = response.usage?.completion_tokens ?? response.usage?.output_tokens ?? 0;
|
|
157
158
|
const cost = (0, utils_1.calculateCost)(model, inputTokens, outputTokens);
|
|
158
159
|
const choice = response.choices?.[0];
|
|
159
160
|
const rawContent = choice?.message?.content;
|
|
@@ -244,9 +245,10 @@ class OpenAIIntegration extends base_1.BaseIntegration {
|
|
|
244
245
|
if (delta)
|
|
245
246
|
outputText += delta;
|
|
246
247
|
// Last chunk carries usage when stream_options.include_usage is set
|
|
248
|
+
// Support both openai v4 (prompt_tokens/completion_tokens) and v5/v6 (input_tokens/output_tokens)
|
|
247
249
|
if (chunk.usage) {
|
|
248
|
-
inputTokens = chunk.usage.prompt_tokens ?? 0;
|
|
249
|
-
outputTokens = chunk.usage.completion_tokens ?? 0;
|
|
250
|
+
inputTokens = chunk.usage.prompt_tokens ?? chunk.usage.input_tokens ?? 0;
|
|
251
|
+
outputTokens = chunk.usage.completion_tokens ?? chunk.usage.output_tokens ?? 0;
|
|
250
252
|
}
|
|
251
253
|
}
|
|
252
254
|
return result;
|
package/dist/esm/index.js
CHANGED
|
@@ -61,6 +61,8 @@ async function patchFramework(framework, client) {
|
|
|
61
61
|
try {
|
|
62
62
|
switch (framework) {
|
|
63
63
|
case 'openai': {
|
|
64
|
+
if (_originalOpenAI)
|
|
65
|
+
break; // already patched by _syncPatch()
|
|
64
66
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
65
67
|
const openaiModule = await import('openai');
|
|
66
68
|
_originalOpenAI = openaiModule.OpenAI ?? openaiModule.default;
|
|
@@ -89,6 +91,8 @@ async function patchFramework(framework, client) {
|
|
|
89
91
|
break;
|
|
90
92
|
}
|
|
91
93
|
case 'anthropic': {
|
|
94
|
+
if (_originalAnthropic)
|
|
95
|
+
break; // already patched by _syncPatch()
|
|
92
96
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
93
97
|
const anthropicModule = await import('@anthropic-ai/sdk');
|
|
94
98
|
_originalAnthropic = anthropicModule.Anthropic ?? anthropicModule.default;
|
|
@@ -115,6 +119,8 @@ async function patchFramework(framework, client) {
|
|
|
115
119
|
break;
|
|
116
120
|
}
|
|
117
121
|
case 'bedrock': {
|
|
122
|
+
if (_originalBedrockClient)
|
|
123
|
+
break; // already patched by _syncPatch()
|
|
118
124
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
119
125
|
const bedrockModule = await import('@aws-sdk/client-bedrock-runtime');
|
|
120
126
|
_originalBedrockClient = bedrockModule.BedrockRuntimeClient;
|
|
@@ -151,12 +157,25 @@ async function patchFramework(framework, client) {
|
|
|
151
157
|
}
|
|
152
158
|
case 'langchain': {
|
|
153
159
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
154
|
-
const { patchRunnableSequence } = await import('./integrations/langchain.js');
|
|
160
|
+
const { patchRunnableSequence, patchAgentExecutor } = await import('./integrations/langchain.js');
|
|
155
161
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
156
162
|
const lcModule = await import('@langchain/core/runnables');
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
163
|
+
const r1 = patchRunnableSequence(lcModule, client);
|
|
164
|
+
// Also patch AgentExecutor (langchain/agents) if installed — AgentExecutor is NOT a
|
|
165
|
+
// RunnableSequence so without this patch each agent loop iteration creates a separate trace.
|
|
166
|
+
let r2;
|
|
167
|
+
try {
|
|
168
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
169
|
+
const agentsModule = await import('langchain/agents');
|
|
170
|
+
r2 = patchAgentExecutor(agentsModule, client);
|
|
171
|
+
}
|
|
172
|
+
catch { /* langchain package not installed — skip */ }
|
|
173
|
+
_lcRestore = () => {
|
|
174
|
+
if (typeof r1 === 'function')
|
|
175
|
+
r1();
|
|
176
|
+
if (typeof r2 === 'function')
|
|
177
|
+
r2();
|
|
178
|
+
};
|
|
160
179
|
break;
|
|
161
180
|
}
|
|
162
181
|
case 'vercel_ai': {
|
|
@@ -186,6 +205,98 @@ async function _autoPatch(client, frameworks) {
|
|
|
186
205
|
}
|
|
187
206
|
}
|
|
188
207
|
// ---------------------------------------------------------------------------
|
|
208
|
+
// _syncPatch() — synchronous CJS patching.
|
|
209
|
+
//
|
|
210
|
+
// In CJS builds, require() is synchronous so we can patch framework modules
|
|
211
|
+
// inline inside init() BEFORE it returns. This means instances created right
|
|
212
|
+
// after init() (e.g. `const client = new OpenAI()`) are already using the
|
|
213
|
+
// patched class — no async race condition.
|
|
214
|
+
//
|
|
215
|
+
// In ESM builds, require is undefined — the entire function silently no-ops
|
|
216
|
+
// and _autoPatch() handles instrumentation via dynamic import() instead.
|
|
217
|
+
// ---------------------------------------------------------------------------
|
|
218
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
219
|
+
function _makePatchedClass(Orig, client) {
|
|
220
|
+
return class Patched extends Orig {
|
|
221
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
222
|
+
constructor(...args) {
|
|
223
|
+
super(...args);
|
|
224
|
+
try {
|
|
225
|
+
client.instrument(this);
|
|
226
|
+
}
|
|
227
|
+
catch { /* never crash constructor */ }
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
function _syncPatch(client, frameworksToTry) {
|
|
232
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
233
|
+
if (typeof require === 'undefined')
|
|
234
|
+
return;
|
|
235
|
+
for (const fw of frameworksToTry) {
|
|
236
|
+
try {
|
|
237
|
+
switch (fw) {
|
|
238
|
+
case 'openai': {
|
|
239
|
+
if (_originalOpenAI)
|
|
240
|
+
break;
|
|
241
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-explicit-any
|
|
242
|
+
const m = require('openai');
|
|
243
|
+
const Orig = m.OpenAI ?? m.default;
|
|
244
|
+
if (!Orig)
|
|
245
|
+
break;
|
|
246
|
+
_originalOpenAI = Orig;
|
|
247
|
+
const Patched = _makePatchedClass(Orig, client);
|
|
248
|
+
try {
|
|
249
|
+
_setProp(m, 'OpenAI', Patched);
|
|
250
|
+
_setProp(m, 'default', Patched);
|
|
251
|
+
}
|
|
252
|
+
catch {
|
|
253
|
+
_originalOpenAI = null;
|
|
254
|
+
}
|
|
255
|
+
break;
|
|
256
|
+
}
|
|
257
|
+
case 'anthropic': {
|
|
258
|
+
if (_originalAnthropic)
|
|
259
|
+
break;
|
|
260
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-explicit-any
|
|
261
|
+
const m = require('@anthropic-ai/sdk');
|
|
262
|
+
const Orig = m.Anthropic ?? m.default;
|
|
263
|
+
if (!Orig)
|
|
264
|
+
break;
|
|
265
|
+
_originalAnthropic = Orig;
|
|
266
|
+
const Patched = _makePatchedClass(Orig, client);
|
|
267
|
+
try {
|
|
268
|
+
_setProp(m, 'Anthropic', Patched);
|
|
269
|
+
_setProp(m, 'default', Patched);
|
|
270
|
+
}
|
|
271
|
+
catch {
|
|
272
|
+
_originalAnthropic = null;
|
|
273
|
+
}
|
|
274
|
+
break;
|
|
275
|
+
}
|
|
276
|
+
case 'bedrock': {
|
|
277
|
+
if (_originalBedrockClient)
|
|
278
|
+
break;
|
|
279
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-explicit-any
|
|
280
|
+
const m = require('@aws-sdk/client-bedrock-runtime');
|
|
281
|
+
const Orig = m.BedrockRuntimeClient;
|
|
282
|
+
if (!Orig)
|
|
283
|
+
break;
|
|
284
|
+
_originalBedrockClient = Orig;
|
|
285
|
+
const Patched = _makePatchedClass(Orig, client);
|
|
286
|
+
try {
|
|
287
|
+
m.BedrockRuntimeClient = Patched;
|
|
288
|
+
}
|
|
289
|
+
catch {
|
|
290
|
+
_originalBedrockClient = null;
|
|
291
|
+
}
|
|
292
|
+
break;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
catch { /* package not installed — skip */ }
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
// ---------------------------------------------------------------------------
|
|
189
300
|
// init()
|
|
190
301
|
// ---------------------------------------------------------------------------
|
|
191
302
|
export function init(options) {
|
|
@@ -194,10 +305,13 @@ export function init(options) {
|
|
|
194
305
|
return _globalClient;
|
|
195
306
|
}
|
|
196
307
|
_globalClient = new Visibe(options ?? {});
|
|
197
|
-
// Fire async patching — works in both CJS and ESM via dynamic import().
|
|
198
|
-
// Patching typically completes within microseconds (cached modules) so there
|
|
199
|
-
// is no practical race condition for normal usage patterns.
|
|
200
308
|
const frameworksToTry = options?.frameworks ?? ALL_FRAMEWORKS;
|
|
309
|
+
// Synchronous CJS path — patches module exports inline so instances created
|
|
310
|
+
// right after init() (e.g. `const client = new OpenAI()`) are instrumented.
|
|
311
|
+
// In ESM, require is undefined and this silently no-ops.
|
|
312
|
+
_syncPatch(_globalClient, frameworksToTry);
|
|
313
|
+
// Async path — handles ESM and framework detection for LangChain/LangGraph
|
|
314
|
+
// (prototype patches don't need to be synchronous).
|
|
201
315
|
_autoPatch(_globalClient, frameworksToTry).catch(() => { });
|
|
202
316
|
if (!_shutdownRegistered) {
|
|
203
317
|
const graceful = async () => { await shutdown(); process.exit(0); };
|
|
@@ -307,6 +307,109 @@ export function patchRunnableSequence(lcModule, visibe) {
|
|
|
307
307
|
};
|
|
308
308
|
}
|
|
309
309
|
// ---------------------------------------------------------------------------
|
|
310
|
+
// patchAgentExecutor — patches AgentExecutor from `langchain/agents` so the
|
|
311
|
+
// entire multi-step agent loop is captured as ONE trace.
|
|
312
|
+
//
|
|
313
|
+
// AgentExecutor is NOT a RunnableSequence — it extends Runnable directly and
|
|
314
|
+
// calls the inner agent RunnableSequence in a loop. Without this patch each
|
|
315
|
+
// iteration fires patchRunnableSequence and creates a separate trace.
|
|
316
|
+
// ---------------------------------------------------------------------------
|
|
317
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
318
|
+
export function patchAgentExecutor(agentsModule, visibe) {
|
|
319
|
+
const AgentExecutor = agentsModule?.AgentExecutor;
|
|
320
|
+
if (!AgentExecutor)
|
|
321
|
+
return () => { };
|
|
322
|
+
const originalInvoke = AgentExecutor.prototype.invoke;
|
|
323
|
+
const originalStream = AgentExecutor.prototype.stream;
|
|
324
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
325
|
+
AgentExecutor.prototype.invoke = async function (input, config) {
|
|
326
|
+
if (activeLangChainStorage.getStore() !== undefined) {
|
|
327
|
+
return originalInvoke.call(this, input, config);
|
|
328
|
+
}
|
|
329
|
+
const traceId = randomUUID();
|
|
330
|
+
const startedAt = new Date().toISOString();
|
|
331
|
+
const startMs = Date.now();
|
|
332
|
+
const agentName = this.name ?? 'langchain';
|
|
333
|
+
await visibe.apiClient.createTrace({
|
|
334
|
+
trace_id: traceId,
|
|
335
|
+
name: agentName,
|
|
336
|
+
framework: 'langchain',
|
|
337
|
+
started_at: startedAt,
|
|
338
|
+
...(visibe.sessionId ? { session_id: visibe.sessionId } : {}),
|
|
339
|
+
});
|
|
340
|
+
const cb = new LangChainCallback({ visibe, traceId, agentName });
|
|
341
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
342
|
+
let result;
|
|
343
|
+
let status = 'completed';
|
|
344
|
+
try {
|
|
345
|
+
result = await activeLangChainStorage.run(cb, () => originalInvoke.call(this, input, _mergeCallbacks(config, cb)));
|
|
346
|
+
}
|
|
347
|
+
catch (err) {
|
|
348
|
+
status = 'failed';
|
|
349
|
+
throw err;
|
|
350
|
+
}
|
|
351
|
+
finally {
|
|
352
|
+
visibe.batcher.flush();
|
|
353
|
+
await visibe.apiClient.completeTrace(traceId, {
|
|
354
|
+
status,
|
|
355
|
+
ended_at: new Date().toISOString(),
|
|
356
|
+
duration_ms: Date.now() - startMs,
|
|
357
|
+
llm_call_count: cb.llmCallCount,
|
|
358
|
+
total_cost: cb.totalCost,
|
|
359
|
+
total_tokens: cb.totalInputTokens + cb.totalOutputTokens,
|
|
360
|
+
total_input_tokens: cb.totalInputTokens,
|
|
361
|
+
total_output_tokens: cb.totalOutputTokens,
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
return result;
|
|
365
|
+
};
|
|
366
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
367
|
+
AgentExecutor.prototype.stream = async function* (input, config) {
|
|
368
|
+
if (activeLangChainStorage.getStore() !== undefined) {
|
|
369
|
+
yield* (await originalStream.call(this, input, config));
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
const traceId = randomUUID();
|
|
373
|
+
const startedAt = new Date().toISOString();
|
|
374
|
+
const startMs = Date.now();
|
|
375
|
+
const agentName = this.name ?? 'langchain';
|
|
376
|
+
await visibe.apiClient.createTrace({
|
|
377
|
+
trace_id: traceId,
|
|
378
|
+
name: agentName,
|
|
379
|
+
framework: 'langchain',
|
|
380
|
+
started_at: startedAt,
|
|
381
|
+
...(visibe.sessionId ? { session_id: visibe.sessionId } : {}),
|
|
382
|
+
});
|
|
383
|
+
const cb = new LangChainCallback({ visibe, traceId, agentName });
|
|
384
|
+
let status = 'completed';
|
|
385
|
+
try {
|
|
386
|
+
const gen = await activeLangChainStorage.run(cb, () => originalStream.call(this, input, _mergeCallbacks(config, cb)));
|
|
387
|
+
yield* gen;
|
|
388
|
+
}
|
|
389
|
+
catch (err) {
|
|
390
|
+
status = 'failed';
|
|
391
|
+
throw err;
|
|
392
|
+
}
|
|
393
|
+
finally {
|
|
394
|
+
visibe.batcher.flush();
|
|
395
|
+
await visibe.apiClient.completeTrace(traceId, {
|
|
396
|
+
status,
|
|
397
|
+
ended_at: new Date().toISOString(),
|
|
398
|
+
duration_ms: Date.now() - startMs,
|
|
399
|
+
llm_call_count: cb.llmCallCount,
|
|
400
|
+
total_cost: cb.totalCost,
|
|
401
|
+
total_tokens: cb.totalInputTokens + cb.totalOutputTokens,
|
|
402
|
+
total_input_tokens: cb.totalInputTokens,
|
|
403
|
+
total_output_tokens: cb.totalOutputTokens,
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
};
|
|
407
|
+
return () => {
|
|
408
|
+
AgentExecutor.prototype.invoke = originalInvoke;
|
|
409
|
+
AgentExecutor.prototype.stream = originalStream;
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
// ---------------------------------------------------------------------------
|
|
310
413
|
// Private helpers
|
|
311
414
|
// ---------------------------------------------------------------------------
|
|
312
415
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -148,8 +148,9 @@ export class OpenAIIntegration extends BaseIntegration {
|
|
|
148
148
|
throw err;
|
|
149
149
|
}
|
|
150
150
|
const model = response.model ?? params.model ?? 'unknown';
|
|
151
|
-
|
|
152
|
-
const
|
|
151
|
+
// Support both openai v4 (prompt_tokens/completion_tokens) and v5/v6 (input_tokens/output_tokens)
|
|
152
|
+
const inputTokens = response.usage?.prompt_tokens ?? response.usage?.input_tokens ?? 0;
|
|
153
|
+
const outputTokens = response.usage?.completion_tokens ?? response.usage?.output_tokens ?? 0;
|
|
153
154
|
const cost = calculateCost(model, inputTokens, outputTokens);
|
|
154
155
|
const choice = response.choices?.[0];
|
|
155
156
|
const rawContent = choice?.message?.content;
|
|
@@ -240,9 +241,10 @@ export class OpenAIIntegration extends BaseIntegration {
|
|
|
240
241
|
if (delta)
|
|
241
242
|
outputText += delta;
|
|
242
243
|
// Last chunk carries usage when stream_options.include_usage is set
|
|
244
|
+
// Support both openai v4 (prompt_tokens/completion_tokens) and v5/v6 (input_tokens/output_tokens)
|
|
243
245
|
if (chunk.usage) {
|
|
244
|
-
inputTokens = chunk.usage.prompt_tokens ?? 0;
|
|
245
|
-
outputTokens = chunk.usage.completion_tokens ?? 0;
|
|
246
|
+
inputTokens = chunk.usage.prompt_tokens ?? chunk.usage.input_tokens ?? 0;
|
|
247
|
+
outputTokens = chunk.usage.completion_tokens ?? chunk.usage.output_tokens ?? 0;
|
|
246
248
|
}
|
|
247
249
|
}
|
|
248
250
|
return result;
|
package/package.json
CHANGED