@visibe.ai/node 0.1.16 → 0.1.18
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 +183 -97
- package/dist/cjs/integrations/langchain.js +49 -32
- package/dist/cjs/integrations/langgraph.js +16 -12
- package/dist/esm/index.js +150 -97
- package/dist/esm/integrations/langchain.js +48 -31
- package/dist/esm/integrations/langgraph.js +17 -13
- package/dist/types/integrations/langchain.d.ts +10 -4
- package/dist/types/integrations/langgraph.d.ts +1 -1
- package/package.json +1 -1
package/dist/cjs/index.js
CHANGED
|
@@ -1,4 +1,37 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
36
|
exports.Visibe = void 0;
|
|
4
37
|
exports.detectFrameworks = detectFrameworks;
|
|
@@ -10,30 +43,35 @@ const client_1 = require("./client");
|
|
|
10
43
|
// ---------------------------------------------------------------------------
|
|
11
44
|
let _globalClient = null;
|
|
12
45
|
let _shutdownRegistered = false;
|
|
13
|
-
// Saved
|
|
14
|
-
// Each is typed as `any` because we need to reassign imported class bindings.
|
|
46
|
+
// Saved originals so shutdown() can restore them.
|
|
15
47
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
16
48
|
let _originalOpenAI = null;
|
|
17
49
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
18
|
-
let _originalBedrockClient = null;
|
|
19
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
20
|
-
let _originalCompiledStateGraph = null;
|
|
21
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
22
50
|
let _originalAnthropic = null;
|
|
51
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
52
|
+
let _originalBedrockClient = null;
|
|
53
|
+
// Prototype-restore functions returned by patch helpers.
|
|
54
|
+
let _lgRestore = null;
|
|
55
|
+
let _lcRestore = null;
|
|
23
56
|
let _vercelAIRestore = null;
|
|
57
|
+
let _autoPatchedFrameworks = [];
|
|
58
|
+
const ALL_FRAMEWORKS = ['openai', 'anthropic', 'bedrock', 'langgraph', 'langchain', 'vercel_ai'];
|
|
24
59
|
// ---------------------------------------------------------------------------
|
|
25
|
-
// detectFrameworks()
|
|
60
|
+
// detectFrameworks() — synchronous, CJS-only legacy helper
|
|
61
|
+
// Auto-patching now uses dynamic import() and works in both CJS and ESM.
|
|
26
62
|
// ---------------------------------------------------------------------------
|
|
27
|
-
function tryRequire(pkg) {
|
|
28
|
-
try {
|
|
29
|
-
require(pkg);
|
|
30
|
-
return true;
|
|
31
|
-
}
|
|
32
|
-
catch {
|
|
33
|
-
return false;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
63
|
function detectFrameworks() {
|
|
64
|
+
const tryRequire = (pkg) => {
|
|
65
|
+
try {
|
|
66
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
67
|
+
if (typeof require !== 'undefined') {
|
|
68
|
+
require(pkg);
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch { /* not installed */ }
|
|
73
|
+
return false;
|
|
74
|
+
};
|
|
37
75
|
return {
|
|
38
76
|
openai: tryRequire('openai'),
|
|
39
77
|
langchain: tryRequire('@langchain/core'),
|
|
@@ -41,20 +79,32 @@ function detectFrameworks() {
|
|
|
41
79
|
bedrock: tryRequire('@aws-sdk/client-bedrock-runtime'),
|
|
42
80
|
vercel_ai: tryRequire('ai'),
|
|
43
81
|
anthropic: tryRequire('@anthropic-ai/sdk'),
|
|
44
|
-
// crewai and autogen are Python-only — no Node.js equivalent
|
|
45
82
|
};
|
|
46
83
|
}
|
|
47
84
|
// ---------------------------------------------------------------------------
|
|
48
|
-
// patchFramework() —
|
|
85
|
+
// patchFramework() — async, uses dynamic import() for CJS + ESM compat.
|
|
86
|
+
//
|
|
87
|
+
// Key design notes:
|
|
88
|
+
// - dynamic import() in CJS builds: TypeScript compiles to require()-based
|
|
89
|
+
// Promise, so we get the same mutable module object as require().
|
|
90
|
+
// - dynamic import() in ESM builds: gives the live ESM namespace (same
|
|
91
|
+
// instance as the user's own `import`).
|
|
92
|
+
// - Prototype patching (langgraph, langchain) works in both envs — prototypes
|
|
93
|
+
// are shared mutable objects regardless of CJS/ESM.
|
|
94
|
+
// - Module-export patching (openai, anthropic, bedrock) only works in CJS
|
|
95
|
+
// because ESM namespace objects are sealed. We try and silently skip in ESM.
|
|
49
96
|
// ---------------------------------------------------------------------------
|
|
50
|
-
|
|
97
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
98
|
+
const _setProp = (obj, key, val) => Object.defineProperty(obj, key, { value: val, configurable: true, writable: true, enumerable: true });
|
|
99
|
+
async function patchFramework(framework, client) {
|
|
51
100
|
try {
|
|
52
101
|
switch (framework) {
|
|
53
102
|
case 'openai': {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
103
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
104
|
+
const openaiModule = await Promise.resolve().then(() => __importStar(require('openai')));
|
|
105
|
+
_originalOpenAI = openaiModule.OpenAI ?? openaiModule.default;
|
|
106
|
+
if (!_originalOpenAI)
|
|
107
|
+
return;
|
|
58
108
|
const PatchedOpenAI = class OpenAI extends _originalOpenAI {
|
|
59
109
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
60
110
|
constructor(...args) {
|
|
@@ -65,19 +115,24 @@ function patchFramework(framework, client) {
|
|
|
65
115
|
catch { /* never crash new OpenAI() */ }
|
|
66
116
|
}
|
|
67
117
|
};
|
|
68
|
-
//
|
|
69
|
-
//
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
118
|
+
// In ESM, the namespace object is sealed — defineProperty throws TypeError.
|
|
119
|
+
// We catch that and skip (user must call client.instrument() explicitly).
|
|
120
|
+
try {
|
|
121
|
+
_setProp(openaiModule, 'OpenAI', PatchedOpenAI);
|
|
122
|
+
_setProp(openaiModule, 'default', PatchedOpenAI);
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
_originalOpenAI = null;
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
75
128
|
break;
|
|
76
129
|
}
|
|
77
130
|
case 'anthropic': {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
131
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
132
|
+
const anthropicModule = await Promise.resolve().then(() => __importStar(require('@anthropic-ai/sdk')));
|
|
133
|
+
_originalAnthropic = anthropicModule.Anthropic ?? anthropicModule.default;
|
|
134
|
+
if (!_originalAnthropic)
|
|
135
|
+
return;
|
|
81
136
|
const PatchedAnthropic = class Anthropic extends _originalAnthropic {
|
|
82
137
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
83
138
|
constructor(...args) {
|
|
@@ -88,17 +143,23 @@ function patchFramework(framework, client) {
|
|
|
88
143
|
catch { /* never crash new Anthropic() */ }
|
|
89
144
|
}
|
|
90
145
|
};
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
146
|
+
try {
|
|
147
|
+
_setProp(anthropicModule, 'Anthropic', PatchedAnthropic);
|
|
148
|
+
_setProp(anthropicModule, 'default', PatchedAnthropic);
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
_originalAnthropic = null;
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
96
154
|
break;
|
|
97
155
|
}
|
|
98
156
|
case 'bedrock': {
|
|
99
|
-
|
|
157
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
158
|
+
const bedrockModule = await Promise.resolve().then(() => __importStar(require('@aws-sdk/client-bedrock-runtime')));
|
|
100
159
|
_originalBedrockClient = bedrockModule.BedrockRuntimeClient;
|
|
101
|
-
|
|
160
|
+
if (!_originalBedrockClient)
|
|
161
|
+
return;
|
|
162
|
+
const PatchedBedrock = class BedrockRuntimeClient extends _originalBedrockClient {
|
|
102
163
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
103
164
|
constructor(...args) {
|
|
104
165
|
super(...args);
|
|
@@ -108,39 +169,61 @@ function patchFramework(framework, client) {
|
|
|
108
169
|
catch { /* never crash new BedrockRuntimeClient() */ }
|
|
109
170
|
}
|
|
110
171
|
};
|
|
172
|
+
try {
|
|
173
|
+
bedrockModule.BedrockRuntimeClient = PatchedBedrock;
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
_originalBedrockClient = null;
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
111
179
|
break;
|
|
112
180
|
}
|
|
113
181
|
case 'langgraph': {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
//
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
182
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
183
|
+
const lgModule = await Promise.resolve().then(() => __importStar(require('@langchain/langgraph')));
|
|
184
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
185
|
+
const { patchCompiledStateGraph } = await Promise.resolve().then(() => __importStar(require('./integrations/langgraph')));
|
|
186
|
+
// patchCompiledStateGraph modifies CompiledStateGraph.prototype — prototype
|
|
187
|
+
// patching works in ESM because prototypes are shared mutable objects.
|
|
188
|
+
_lgRestore = patchCompiledStateGraph(lgModule, client);
|
|
120
189
|
break;
|
|
121
190
|
}
|
|
122
191
|
case 'langchain': {
|
|
123
|
-
//
|
|
124
|
-
const { patchRunnableSequence } = require('./integrations/langchain');
|
|
125
|
-
|
|
126
|
-
|
|
192
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
193
|
+
const { patchRunnableSequence } = await Promise.resolve().then(() => __importStar(require('./integrations/langchain')));
|
|
194
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
195
|
+
const lcModule = await Promise.resolve().then(() => __importStar(require('@langchain/core/runnables')));
|
|
196
|
+
const result = patchRunnableSequence(lcModule, client);
|
|
197
|
+
if (typeof result === 'function')
|
|
198
|
+
_lcRestore = result;
|
|
127
199
|
break;
|
|
128
200
|
}
|
|
129
201
|
case 'vercel_ai': {
|
|
130
|
-
|
|
131
|
-
const
|
|
202
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
203
|
+
const { patchVercelAI } = await Promise.resolve().then(() => __importStar(require('./integrations/vercel-ai')));
|
|
204
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
205
|
+
const aiModule = await Promise.resolve().then(() => __importStar(require('ai')));
|
|
132
206
|
_vercelAIRestore = patchVercelAI(aiModule, client);
|
|
133
207
|
break;
|
|
134
208
|
}
|
|
135
209
|
}
|
|
136
|
-
// Record which frameworks were successfully patched for the startup log.
|
|
137
210
|
_autoPatchedFrameworks.push(framework);
|
|
138
211
|
}
|
|
139
212
|
catch {
|
|
140
213
|
// Package not installed or patch failed — skip silently.
|
|
141
214
|
}
|
|
142
215
|
}
|
|
143
|
-
|
|
216
|
+
// ---------------------------------------------------------------------------
|
|
217
|
+
// _autoPatch() — async; fires from init() without blocking it
|
|
218
|
+
// ---------------------------------------------------------------------------
|
|
219
|
+
async function _autoPatch(client, frameworks) {
|
|
220
|
+
for (const fw of frameworks) {
|
|
221
|
+
await patchFramework(fw, client);
|
|
222
|
+
}
|
|
223
|
+
if (_autoPatchedFrameworks.length > 0) {
|
|
224
|
+
console.log(`[Visibe] Auto-instrumented: ${_autoPatchedFrameworks.join(', ')}`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
144
227
|
// ---------------------------------------------------------------------------
|
|
145
228
|
// init()
|
|
146
229
|
// ---------------------------------------------------------------------------
|
|
@@ -150,17 +233,11 @@ function init(options) {
|
|
|
150
233
|
return _globalClient;
|
|
151
234
|
}
|
|
152
235
|
_globalClient = new client_1.Visibe(options ?? {});
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
}
|
|
159
|
-
// Register graceful shutdown handlers.
|
|
160
|
-
// NOTE: process.on('exit') fires synchronously — async HTTP requests cannot
|
|
161
|
-
// complete there. SIGTERM is what Docker/Kubernetes send before killing a
|
|
162
|
-
// container; without handling it all buffered spans are lost.
|
|
163
|
-
// We await shutdown() so the batcher's 300 ms window completes before exit.
|
|
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
|
+
const frameworksToTry = options?.frameworks ?? ALL_FRAMEWORKS;
|
|
240
|
+
_autoPatch(_globalClient, frameworksToTry).catch(() => { });
|
|
164
241
|
if (!_shutdownRegistered) {
|
|
165
242
|
const graceful = async () => { await shutdown(); process.exit(0); };
|
|
166
243
|
process.on('SIGTERM', graceful);
|
|
@@ -168,9 +245,6 @@ function init(options) {
|
|
|
168
245
|
process.on('beforeExit', () => { shutdown().catch(() => { }); });
|
|
169
246
|
_shutdownRegistered = true;
|
|
170
247
|
}
|
|
171
|
-
if (_autoPatchedFrameworks.length > 0) {
|
|
172
|
-
console.log(`[Visibe] Auto-instrumented: ${_autoPatchedFrameworks.join(', ')}`);
|
|
173
|
-
}
|
|
174
248
|
return _globalClient;
|
|
175
249
|
}
|
|
176
250
|
// ---------------------------------------------------------------------------
|
|
@@ -179,53 +253,65 @@ function init(options) {
|
|
|
179
253
|
async function shutdown() {
|
|
180
254
|
if (_globalClient === null)
|
|
181
255
|
return;
|
|
182
|
-
// Capture the client reference and clear global state immediately so that
|
|
183
|
-
// re-init() calls work without needing to await this function.
|
|
184
256
|
const client = _globalClient;
|
|
185
257
|
_globalClient = null;
|
|
186
258
|
_autoPatchedFrameworks = [];
|
|
187
|
-
// Restore patched
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
259
|
+
// Restore patched module exports (works in CJS; silently no-ops in ESM).
|
|
260
|
+
if (_originalOpenAI) {
|
|
261
|
+
try {
|
|
262
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
263
|
+
const m = await Promise.resolve().then(() => __importStar(require('openai')));
|
|
191
264
|
Object.defineProperty(m, 'OpenAI', { value: _originalOpenAI, configurable: true, writable: true, enumerable: true });
|
|
192
265
|
Object.defineProperty(m, 'default', { value: _originalOpenAI, configurable: true, writable: true, enumerable: true });
|
|
193
|
-
_originalOpenAI = null;
|
|
194
266
|
}
|
|
267
|
+
catch { /* ignore */ }
|
|
268
|
+
_originalOpenAI = null;
|
|
195
269
|
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
const m = require('@anthropic-ai/sdk');
|
|
270
|
+
if (_originalAnthropic) {
|
|
271
|
+
try {
|
|
272
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
273
|
+
const m = await Promise.resolve().then(() => __importStar(require('@anthropic-ai/sdk')));
|
|
200
274
|
Object.defineProperty(m, 'Anthropic', { value: _originalAnthropic, configurable: true, writable: true, enumerable: true });
|
|
201
275
|
Object.defineProperty(m, 'default', { value: _originalAnthropic, configurable: true, writable: true, enumerable: true });
|
|
202
|
-
_originalAnthropic = null;
|
|
203
276
|
}
|
|
277
|
+
catch { /* ignore */ }
|
|
278
|
+
_originalAnthropic = null;
|
|
204
279
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
require('@aws-sdk/client-bedrock-runtime')
|
|
209
|
-
|
|
280
|
+
if (_originalBedrockClient) {
|
|
281
|
+
try {
|
|
282
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
283
|
+
const m = await Promise.resolve().then(() => __importStar(require('@aws-sdk/client-bedrock-runtime')));
|
|
284
|
+
m.BedrockRuntimeClient = _originalBedrockClient;
|
|
210
285
|
}
|
|
286
|
+
catch { /* ignore */ }
|
|
287
|
+
_originalBedrockClient = null;
|
|
211
288
|
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
_originalCompiledStateGraph = null;
|
|
289
|
+
// Restore prototype patches via stored cleanup functions.
|
|
290
|
+
if (_lgRestore) {
|
|
291
|
+
try {
|
|
292
|
+
_lgRestore();
|
|
217
293
|
}
|
|
294
|
+
catch { /* ignore */ }
|
|
295
|
+
;
|
|
296
|
+
_lgRestore = null;
|
|
218
297
|
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
298
|
+
if (_lcRestore) {
|
|
299
|
+
try {
|
|
300
|
+
_lcRestore();
|
|
301
|
+
}
|
|
302
|
+
catch { /* ignore */ }
|
|
303
|
+
;
|
|
304
|
+
_lcRestore = null;
|
|
305
|
+
}
|
|
306
|
+
if (_vercelAIRestore) {
|
|
307
|
+
try {
|
|
222
308
|
_vercelAIRestore();
|
|
223
|
-
_vercelAIRestore = null;
|
|
224
309
|
}
|
|
310
|
+
catch { /* ignore */ }
|
|
311
|
+
;
|
|
312
|
+
_vercelAIRestore = null;
|
|
225
313
|
}
|
|
226
|
-
|
|
227
|
-
// Flush buffered spans and wait up to 300 ms for in-flight HTTP requests to
|
|
228
|
-
// complete. This prevents spans from being lost on SIGTERM.
|
|
314
|
+
// Flush buffered spans and wait up to 300 ms for in-flight HTTP requests.
|
|
229
315
|
await client.batcher.shutdown();
|
|
230
316
|
}
|
|
231
317
|
// ---------------------------------------------------------------------------
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.LangChainCallback = exports.activeLangChainStorage = void 0;
|
|
3
|
+
exports.LangChainCallback = exports.LANGGRAPH_INTERNAL_NODES = exports.activeLangChainStorage = void 0;
|
|
4
4
|
exports.patchRunnableSequence = patchRunnableSequence;
|
|
5
5
|
const node_async_hooks_1 = require("node:async_hooks");
|
|
6
6
|
const node_crypto_1 = require("node:crypto");
|
|
@@ -11,19 +11,19 @@ exports.activeLangChainStorage = new node_async_hooks_1.AsyncLocalStorage();
|
|
|
11
11
|
// ---------------------------------------------------------------------------
|
|
12
12
|
// LangChain token extraction
|
|
13
13
|
// Different providers nest token usage in different locations.
|
|
14
|
-
// Check in the order specified by the spec.
|
|
15
14
|
// ---------------------------------------------------------------------------
|
|
16
15
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
17
16
|
function extractTokenUsage(output) {
|
|
18
17
|
const usage = output?.llmOutput?.tokenUsage
|
|
19
18
|
?? output?.llmOutput?.usage
|
|
20
19
|
?? output?.generations?.[0]?.[0]?.generationInfo?.usage;
|
|
21
|
-
// Use ?? not || so token counts of 0 are preserved correctly.
|
|
22
20
|
return {
|
|
23
21
|
inputTokens: usage?.promptTokens ?? usage?.input_tokens ?? 0,
|
|
24
22
|
outputTokens: usage?.completionTokens ?? usage?.output_tokens ?? 0,
|
|
25
23
|
};
|
|
26
24
|
}
|
|
25
|
+
// Internal LangGraph system node names — never emit agent_start spans for these.
|
|
26
|
+
exports.LANGGRAPH_INTERNAL_NODES = new Set(['__start__', '__end__', 'LangGraph']);
|
|
27
27
|
// ---------------------------------------------------------------------------
|
|
28
28
|
// LangChainCallback
|
|
29
29
|
// ---------------------------------------------------------------------------
|
|
@@ -34,23 +34,21 @@ class LangChainCallback {
|
|
|
34
34
|
constructor(options) {
|
|
35
35
|
// Required by @langchain/core v1+ for proper callback registration.
|
|
36
36
|
// Without `name`, ensureHandler() wraps via fromMethods() which drops prototype methods.
|
|
37
|
-
// Without `awaitHandlers`, callbacks run in a background queue
|
|
38
|
-
//
|
|
37
|
+
// Without `awaitHandlers`, callbacks run in a background queue and fire after
|
|
38
|
+
// model.invoke() returns — causing spans to be missed on flush/completeTrace.
|
|
39
39
|
this.name = 'visibe-langchain-callback';
|
|
40
40
|
this.awaitHandlers = true;
|
|
41
41
|
this.raiseError = false;
|
|
42
42
|
// Maps LangChain runId → our spanId so we can set parent_span_id.
|
|
43
43
|
this.runIdToSpanId = new Map();
|
|
44
|
-
//
|
|
45
|
-
this.pendingLLMCalls = new Map();
|
|
44
|
+
// Pending LLM calls: runId → { startMs, model, inputText }
|
|
45
|
+
this.pendingLLMCalls = new Map();
|
|
46
46
|
this.pendingToolCalls = new Map();
|
|
47
47
|
this.stepCounter = 0;
|
|
48
|
-
//
|
|
49
|
-
this.seenAgents = new Set();
|
|
50
|
-
// Token / call accumulators — updated by handleLLMEnd, read by patchCompiledStateGraph
|
|
51
|
-
// and patchRunnableSequence to populate completeTrace totals.
|
|
48
|
+
// Token / call / cost accumulators — read by patchCompiledStateGraph / patchRunnableSequence.
|
|
52
49
|
this.totalInputTokens = 0;
|
|
53
50
|
this.totalOutputTokens = 0;
|
|
51
|
+
this.totalCost = 0;
|
|
54
52
|
this.llmCallCount = 0;
|
|
55
53
|
this.visibe = options.visibe;
|
|
56
54
|
this.traceId = options.traceId;
|
|
@@ -59,17 +57,40 @@ class LangChainCallback {
|
|
|
59
57
|
// ---------------------------------------------------------------------------
|
|
60
58
|
// LLM events
|
|
61
59
|
// ---------------------------------------------------------------------------
|
|
60
|
+
// Called for text-completion models.
|
|
62
61
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
63
|
-
async handleLLMStart(
|
|
64
|
-
|
|
62
|
+
async handleLLMStart(llm, prompts, runId) {
|
|
63
|
+
const model = llm?.kwargs?.model ?? llm?.kwargs?.model_name ?? undefined;
|
|
64
|
+
const inputText = Array.isArray(prompts) ? prompts.join('\n') : '';
|
|
65
|
+
this.pendingLLMCalls.set(runId, { startMs: Date.now(), model, inputText });
|
|
66
|
+
}
|
|
67
|
+
// Called for chat models (ChatOpenAI, ChatAnthropic, etc.).
|
|
68
|
+
// messages is BaseMessage[][] — one array per parallel completion request.
|
|
69
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
70
|
+
async handleChatModelStart(llm, messages, runId) {
|
|
71
|
+
const model = llm?.kwargs?.model ?? llm?.kwargs?.model_name ?? undefined;
|
|
72
|
+
let inputText = '';
|
|
73
|
+
try {
|
|
74
|
+
const msgs = messages?.[0] ?? [];
|
|
75
|
+
inputText = msgs.map((m) => {
|
|
76
|
+
const role = m?.getType?.() ?? m?._getType?.() ?? m?.role ?? 'user';
|
|
77
|
+
const content = typeof m?.content === 'string'
|
|
78
|
+
? m.content
|
|
79
|
+
: JSON.stringify(m?.content ?? '');
|
|
80
|
+
return `${role}: ${content}`;
|
|
81
|
+
}).join('\n');
|
|
82
|
+
}
|
|
83
|
+
catch { /* ignore serialisation errors */ }
|
|
84
|
+
this.pendingLLMCalls.set(runId, { startMs: Date.now(), model, inputText });
|
|
65
85
|
}
|
|
66
86
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
67
87
|
async handleLLMEnd(output, runId, parentRunId) {
|
|
68
|
-
const
|
|
88
|
+
const pending = this.pendingLLMCalls.get(runId) ?? { startMs: Date.now() };
|
|
69
89
|
this.pendingLLMCalls.delete(runId);
|
|
70
90
|
const { inputTokens, outputTokens } = extractTokenUsage(output);
|
|
71
91
|
const gen = output?.generations?.[0]?.[0];
|
|
72
|
-
|
|
92
|
+
// Prefer model saved at LLM-start, fall back to generationInfo, then agentName.
|
|
93
|
+
const model = pending.model ?? gen?.generationInfo?.model ?? this.agentName;
|
|
73
94
|
const cost = (0, utils_1.calculateCost)(model, inputTokens, outputTokens);
|
|
74
95
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
75
96
|
const rawText = gen?.text ?? gen?.message?.content ?? '';
|
|
@@ -85,16 +106,15 @@ class LangChainCallback {
|
|
|
85
106
|
status: 'success',
|
|
86
107
|
inputTokens,
|
|
87
108
|
outputTokens,
|
|
88
|
-
inputText: '',
|
|
109
|
+
inputText: pending.inputText ?? '',
|
|
89
110
|
outputText,
|
|
90
|
-
durationMs: Date.now() - startMs,
|
|
111
|
+
durationMs: Date.now() - pending.startMs,
|
|
91
112
|
});
|
|
92
113
|
this.visibe.batcher.add(this.traceId, span);
|
|
93
|
-
// Update local accumulators (used by patchCompiledStateGraph / patchRunnableSequence).
|
|
94
114
|
this.totalInputTokens += inputTokens;
|
|
95
115
|
this.totalOutputTokens += outputTokens;
|
|
116
|
+
this.totalCost += cost;
|
|
96
117
|
this.llmCallCount++;
|
|
97
|
-
// Notify track() accumulator if running inside a group tracker.
|
|
98
118
|
this._onLLMSpan?.(inputTokens, outputTokens, cost);
|
|
99
119
|
}
|
|
100
120
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -143,16 +163,15 @@ class LangChainCallback {
|
|
|
143
163
|
}
|
|
144
164
|
// ---------------------------------------------------------------------------
|
|
145
165
|
// Chain events
|
|
166
|
+
//
|
|
167
|
+
// In LangGraph v1.2+, the node key is passed as the 8th `name` parameter, NOT
|
|
168
|
+
// via chain.name. We always store runId → spanId so nested LLM/tool calls get
|
|
169
|
+
// a valid parent_span_id regardless of whether the chain is a user-defined node.
|
|
146
170
|
// ---------------------------------------------------------------------------
|
|
147
171
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
148
|
-
async handleChainStart(
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
if (chainName && !this.seenAgents.has(chainName)) {
|
|
152
|
-
this.seenAgents.add(chainName);
|
|
153
|
-
const spanId = this.nextSpanId();
|
|
154
|
-
this.runIdToSpanId.set(runId, spanId);
|
|
155
|
-
void parentRunId; // suppress unused warning
|
|
172
|
+
async handleChainStart(_chain, _inputs, runId, _parentRunId, _tags, _metadata, _runType, _name) {
|
|
173
|
+
if (!this.runIdToSpanId.has(runId)) {
|
|
174
|
+
this.runIdToSpanId.set(runId, this.nextSpanId());
|
|
156
175
|
}
|
|
157
176
|
}
|
|
158
177
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -179,7 +198,6 @@ function patchRunnableSequence(lcModule, visibe) {
|
|
|
179
198
|
const originalStream = RunnableSequence.prototype.stream;
|
|
180
199
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
181
200
|
RunnableSequence.prototype.invoke = async function (input, config) {
|
|
182
|
-
// If already inside a LangChain trace, pass through.
|
|
183
201
|
if (exports.activeLangChainStorage.getStore() !== undefined) {
|
|
184
202
|
return originalInvoke.call(this, input, config);
|
|
185
203
|
}
|
|
@@ -194,6 +212,7 @@ function patchRunnableSequence(lcModule, visibe) {
|
|
|
194
212
|
...(visibe.sessionId ? { session_id: visibe.sessionId } : {}),
|
|
195
213
|
});
|
|
196
214
|
const cb = new LangChainCallback({ visibe, traceId, agentName: 'langchain' });
|
|
215
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
197
216
|
let result;
|
|
198
217
|
let status = 'completed';
|
|
199
218
|
try {
|
|
@@ -210,6 +229,7 @@ function patchRunnableSequence(lcModule, visibe) {
|
|
|
210
229
|
ended_at: new Date().toISOString(),
|
|
211
230
|
duration_ms: Date.now() - startMs,
|
|
212
231
|
llm_call_count: cb.llmCallCount,
|
|
232
|
+
total_cost: cb.totalCost,
|
|
213
233
|
total_tokens: cb.totalInputTokens + cb.totalOutputTokens,
|
|
214
234
|
total_input_tokens: cb.totalInputTokens,
|
|
215
235
|
total_output_tokens: cb.totalOutputTokens,
|
|
@@ -236,9 +256,6 @@ function patchRunnableSequence(lcModule, visibe) {
|
|
|
236
256
|
const cb = new LangChainCallback({ visibe, traceId, agentName: 'langchain' });
|
|
237
257
|
let status = 'completed';
|
|
238
258
|
try {
|
|
239
|
-
// RunnableSequence.stream() is an async function (not async generator) returning
|
|
240
|
-
// a Promise<AsyncIterable>. activeLangChainStorage.run returns that Promise,
|
|
241
|
-
// so we must await before yield*.
|
|
242
259
|
const gen = await exports.activeLangChainStorage.run(cb, () => originalStream.call(this, input, _mergeCallbacks(config, cb)));
|
|
243
260
|
yield* gen;
|
|
244
261
|
}
|
|
@@ -253,6 +270,7 @@ function patchRunnableSequence(lcModule, visibe) {
|
|
|
253
270
|
ended_at: new Date().toISOString(),
|
|
254
271
|
duration_ms: Date.now() - startMs,
|
|
255
272
|
llm_call_count: cb.llmCallCount,
|
|
273
|
+
total_cost: cb.totalCost,
|
|
256
274
|
total_tokens: cb.totalInputTokens + cb.totalOutputTokens,
|
|
257
275
|
total_input_tokens: cb.totalInputTokens,
|
|
258
276
|
total_output_tokens: cb.totalOutputTokens,
|
|
@@ -267,7 +285,6 @@ function patchRunnableSequence(lcModule, visibe) {
|
|
|
267
285
|
// ---------------------------------------------------------------------------
|
|
268
286
|
// Private helpers
|
|
269
287
|
// ---------------------------------------------------------------------------
|
|
270
|
-
// Merge our callback into an existing LangChain config object.
|
|
271
288
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
272
289
|
function _mergeCallbacks(config, cb) {
|
|
273
290
|
if (!config)
|
|
@@ -13,24 +13,26 @@ class LangGraphCallback extends langchain_1.LangChainCallback {
|
|
|
13
13
|
super(options);
|
|
14
14
|
this.nodeNames = new Set(options.nodeNames ?? []);
|
|
15
15
|
}
|
|
16
|
-
// Override handleChainStart to emit agent_start for
|
|
16
|
+
// Override handleChainStart to emit agent_start spans for LangGraph nodes.
|
|
17
|
+
//
|
|
18
|
+
// In LangGraph v1.2+, the node key is the 8th `name` parameter (not chain.name).
|
|
19
|
+
// Internal system nodes (__start__, __end__, LangGraph) are filtered out.
|
|
17
20
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
18
|
-
async handleChainStart(chain, inputs, runId, parentRunId) {
|
|
19
|
-
//
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
async handleChainStart(chain, inputs, runId, parentRunId, tags, metadata, runType, name) {
|
|
22
|
+
// Always track the runId so child LLM calls can resolve parent_span_id.
|
|
23
|
+
await super.handleChainStart(chain, inputs, runId, parentRunId, tags, metadata, runType, name);
|
|
24
|
+
// The node key is passed as the 8th `name` parameter in LangGraph v1.2+.
|
|
25
|
+
// Fall back to chain.name for older versions.
|
|
26
|
+
const nodeName = name ?? chain?.name ?? '';
|
|
27
|
+
if (nodeName && !langchain_1.LANGGRAPH_INTERNAL_NODES.has(nodeName)) {
|
|
28
|
+
// Use the spanId already assigned by super for this runId.
|
|
29
|
+
const spanId = this.runIdToSpanId.get(runId) ?? this.nextSpanId();
|
|
23
30
|
this.runIdToSpanId.set(runId, spanId);
|
|
24
|
-
// Emit an agent_start span for this node.
|
|
25
|
-
// type MUST be exactly "agent_start" — the backend validates this string.
|
|
26
31
|
this.visibe.batcher.add(this.traceId, this.visibe.buildAgentStartSpan({
|
|
27
32
|
spanId,
|
|
28
|
-
agentName:
|
|
33
|
+
agentName: nodeName,
|
|
29
34
|
}));
|
|
30
|
-
// Don't call super — we've already set the runId mapping.
|
|
31
|
-
return;
|
|
32
35
|
}
|
|
33
|
-
await super.handleChainStart(chain, inputs, runId, parentRunId);
|
|
34
36
|
}
|
|
35
37
|
}
|
|
36
38
|
exports.LangGraphCallback = LangGraphCallback;
|
|
@@ -87,6 +89,7 @@ function patchCompiledStateGraph(lgModule, visibe) {
|
|
|
87
89
|
ended_at: new Date().toISOString(),
|
|
88
90
|
duration_ms: Date.now() - startMs,
|
|
89
91
|
llm_call_count: cb.llmCallCount,
|
|
92
|
+
total_cost: cb.totalCost,
|
|
90
93
|
total_tokens: cb.totalInputTokens + cb.totalOutputTokens,
|
|
91
94
|
total_input_tokens: cb.totalInputTokens,
|
|
92
95
|
total_output_tokens: cb.totalOutputTokens,
|
|
@@ -138,6 +141,7 @@ function patchCompiledStateGraph(lgModule, visibe) {
|
|
|
138
141
|
ended_at: new Date().toISOString(),
|
|
139
142
|
duration_ms: Date.now() - startMs,
|
|
140
143
|
llm_call_count: cb.llmCallCount,
|
|
144
|
+
total_cost: cb.totalCost,
|
|
141
145
|
total_tokens: cb.totalInputTokens + cb.totalOutputTokens,
|
|
142
146
|
total_input_tokens: cb.totalInputTokens,
|
|
143
147
|
total_output_tokens: cb.totalOutputTokens,
|