@visibe.ai/node 0.1.20 → 0.1.22

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 CHANGED
@@ -190,12 +190,25 @@ async function patchFramework(framework, client) {
190
190
  }
191
191
  case 'langchain': {
192
192
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
193
- const { patchRunnableSequence } = await Promise.resolve().then(() => __importStar(require('./integrations/langchain')));
193
+ const { patchRunnableSequence, patchAgentExecutor } = await Promise.resolve().then(() => __importStar(require('./integrations/langchain')));
194
194
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
195
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;
196
+ const r1 = patchRunnableSequence(lcModule, client);
197
+ // Also patch AgentExecutor (langchain/agents) if installed — AgentExecutor is NOT a
198
+ // RunnableSequence so without this patch each agent loop iteration creates a separate trace.
199
+ let r2;
200
+ try {
201
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
202
+ const agentsModule = await Promise.resolve().then(() => __importStar(require('langchain/agents')));
203
+ r2 = patchAgentExecutor(agentsModule, client);
204
+ }
205
+ catch { /* langchain package not installed — skip */ }
206
+ _lcRestore = () => {
207
+ if (typeof r1 === 'function')
208
+ r1();
209
+ if (typeof r2 === 'function')
210
+ r2();
211
+ };
199
212
  break;
200
213
  }
201
214
  case 'vercel_ai': {
@@ -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");
@@ -31,6 +32,16 @@ class LangChainCallback {
31
32
  nextSpanId() {
32
33
  return `step_${++this.stepCounter}`;
33
34
  }
35
+ // Walk up the run parent chain to find the nearest agent name.
36
+ // Falls back to this.agentName if no ancestor has an associated agent.
37
+ _findAgentName(runId) {
38
+ if (!runId)
39
+ return this.agentName;
40
+ const name = this.runIdToAgentName.get(runId);
41
+ if (name)
42
+ return name;
43
+ return this._findAgentName(this.runIdToParent.get(runId));
44
+ }
34
45
  constructor(options) {
35
46
  // Required by @langchain/core v1+ for proper callback registration.
36
47
  // Without `name`, ensureHandler() wraps via fromMethods() which drops prototype methods.
@@ -41,6 +52,11 @@ class LangChainCallback {
41
52
  this.raiseError = false;
42
53
  // Maps LangChain runId → our spanId so we can set parent_span_id.
43
54
  this.runIdToSpanId = new Map();
55
+ // Tracks parent run IDs so we can walk up the chain hierarchy.
56
+ this.runIdToParent = new Map();
57
+ // Maps runId → agent name for chains that emitted an agent_start span.
58
+ // Used by _findAgentName() to resolve the correct agent for llm_call / tool_call spans.
59
+ this.runIdToAgentName = new Map();
44
60
  // Pending LLM calls: runId → { startMs, model, inputText }
45
61
  this.pendingLLMCalls = new Map();
46
62
  this.pendingToolCalls = new Map();
@@ -103,7 +119,7 @@ class LangChainCallback {
103
119
  const span = this.visibe.buildLLMSpan({
104
120
  spanId,
105
121
  parentSpanId,
106
- agentName: this.agentName,
122
+ agentName: this._findAgentName(parentRunId),
107
123
  model,
108
124
  status: 'success',
109
125
  inputTokens,
@@ -146,7 +162,7 @@ class LangChainCallback {
146
162
  spanId,
147
163
  parentSpanId,
148
164
  toolName: pending?.toolName ?? 'tool',
149
- agentName: this.agentName,
165
+ agentName: this._findAgentName(parentRunId),
150
166
  status: 'success',
151
167
  durationMs: pending ? Date.now() - pending.startMs : 0,
152
168
  inputText: pending?.inputText ?? '',
@@ -175,12 +191,16 @@ class LangChainCallback {
175
191
  if (!this.runIdToSpanId.has(runId)) {
176
192
  this.runIdToSpanId.set(runId, this.nextSpanId());
177
193
  }
194
+ if (_parentRunId) {
195
+ this.runIdToParent.set(runId, _parentRunId);
196
+ }
178
197
  // Emit agent_start for the root chain so LLM spans have a real parent and
179
198
  // the agents breakdown is populated. Subclasses that manage their own
180
199
  // agent_start spans (LangGraphCallback) set _emitRootAgentStart = false.
181
200
  if (this._emitRootAgentStart && !_parentRunId) {
182
201
  const spanId = this.runIdToSpanId.get(runId);
183
202
  const agentName = _name ?? _chain?.name ?? this.agentName;
203
+ this.runIdToAgentName.set(runId, agentName);
184
204
  this.visibe.batcher.add(this.traceId, this.visibe.buildAgentStartSpan({ spanId, agentName }));
185
205
  }
186
206
  }
@@ -293,6 +313,109 @@ function patchRunnableSequence(lcModule, visibe) {
293
313
  };
294
314
  }
295
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
+ // ---------------------------------------------------------------------------
296
419
  // Private helpers
297
420
  // ---------------------------------------------------------------------------
298
421
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -31,6 +31,8 @@ class LangGraphCallback extends langchain_1.LangChainCallback {
31
31
  // Use the spanId already assigned by super for this runId.
32
32
  const spanId = this.runIdToSpanId.get(runId) ?? this.nextSpanId();
33
33
  this.runIdToSpanId.set(runId, spanId);
34
+ // Register agent name so _findAgentName() resolves it for child llm_call / tool_call spans.
35
+ this.runIdToAgentName.set(runId, nodeName);
34
36
  this.visibe.batcher.add(this.traceId, this.visibe.buildAgentStartSpan({
35
37
  spanId,
36
38
  agentName: nodeName,
package/dist/esm/index.js CHANGED
@@ -151,12 +151,25 @@ async function patchFramework(framework, client) {
151
151
  }
152
152
  case 'langchain': {
153
153
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
154
- const { patchRunnableSequence } = await import('./integrations/langchain.js');
154
+ const { patchRunnableSequence, patchAgentExecutor } = await import('./integrations/langchain.js');
155
155
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
156
156
  const lcModule = await import('@langchain/core/runnables');
157
- const result = patchRunnableSequence(lcModule, client);
158
- if (typeof result === 'function')
159
- _lcRestore = result;
157
+ const r1 = patchRunnableSequence(lcModule, client);
158
+ // Also patch AgentExecutor (langchain/agents) if installed — AgentExecutor is NOT a
159
+ // RunnableSequence so without this patch each agent loop iteration creates a separate trace.
160
+ let r2;
161
+ try {
162
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
163
+ const agentsModule = await import('langchain/agents');
164
+ r2 = patchAgentExecutor(agentsModule, client);
165
+ }
166
+ catch { /* langchain package not installed — skip */ }
167
+ _lcRestore = () => {
168
+ if (typeof r1 === 'function')
169
+ r1();
170
+ if (typeof r2 === 'function')
171
+ r2();
172
+ };
160
173
  break;
161
174
  }
162
175
  case 'vercel_ai': {
@@ -27,6 +27,16 @@ export class LangChainCallback {
27
27
  nextSpanId() {
28
28
  return `step_${++this.stepCounter}`;
29
29
  }
30
+ // Walk up the run parent chain to find the nearest agent name.
31
+ // Falls back to this.agentName if no ancestor has an associated agent.
32
+ _findAgentName(runId) {
33
+ if (!runId)
34
+ return this.agentName;
35
+ const name = this.runIdToAgentName.get(runId);
36
+ if (name)
37
+ return name;
38
+ return this._findAgentName(this.runIdToParent.get(runId));
39
+ }
30
40
  constructor(options) {
31
41
  // Required by @langchain/core v1+ for proper callback registration.
32
42
  // Without `name`, ensureHandler() wraps via fromMethods() which drops prototype methods.
@@ -37,6 +47,11 @@ export class LangChainCallback {
37
47
  this.raiseError = false;
38
48
  // Maps LangChain runId → our spanId so we can set parent_span_id.
39
49
  this.runIdToSpanId = new Map();
50
+ // Tracks parent run IDs so we can walk up the chain hierarchy.
51
+ this.runIdToParent = new Map();
52
+ // Maps runId → agent name for chains that emitted an agent_start span.
53
+ // Used by _findAgentName() to resolve the correct agent for llm_call / tool_call spans.
54
+ this.runIdToAgentName = new Map();
40
55
  // Pending LLM calls: runId → { startMs, model, inputText }
41
56
  this.pendingLLMCalls = new Map();
42
57
  this.pendingToolCalls = new Map();
@@ -99,7 +114,7 @@ export class LangChainCallback {
99
114
  const span = this.visibe.buildLLMSpan({
100
115
  spanId,
101
116
  parentSpanId,
102
- agentName: this.agentName,
117
+ agentName: this._findAgentName(parentRunId),
103
118
  model,
104
119
  status: 'success',
105
120
  inputTokens,
@@ -142,7 +157,7 @@ export class LangChainCallback {
142
157
  spanId,
143
158
  parentSpanId,
144
159
  toolName: pending?.toolName ?? 'tool',
145
- agentName: this.agentName,
160
+ agentName: this._findAgentName(parentRunId),
146
161
  status: 'success',
147
162
  durationMs: pending ? Date.now() - pending.startMs : 0,
148
163
  inputText: pending?.inputText ?? '',
@@ -171,12 +186,16 @@ export class LangChainCallback {
171
186
  if (!this.runIdToSpanId.has(runId)) {
172
187
  this.runIdToSpanId.set(runId, this.nextSpanId());
173
188
  }
189
+ if (_parentRunId) {
190
+ this.runIdToParent.set(runId, _parentRunId);
191
+ }
174
192
  // Emit agent_start for the root chain so LLM spans have a real parent and
175
193
  // the agents breakdown is populated. Subclasses that manage their own
176
194
  // agent_start spans (LangGraphCallback) set _emitRootAgentStart = false.
177
195
  if (this._emitRootAgentStart && !_parentRunId) {
178
196
  const spanId = this.runIdToSpanId.get(runId);
179
197
  const agentName = _name ?? _chain?.name ?? this.agentName;
198
+ this.runIdToAgentName.set(runId, agentName);
180
199
  this.visibe.batcher.add(this.traceId, this.visibe.buildAgentStartSpan({ spanId, agentName }));
181
200
  }
182
201
  }
@@ -288,6 +307,109 @@ export function patchRunnableSequence(lcModule, visibe) {
288
307
  };
289
308
  }
290
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
+ // ---------------------------------------------------------------------------
291
413
  // Private helpers
292
414
  // ---------------------------------------------------------------------------
293
415
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -27,6 +27,8 @@ export class LangGraphCallback extends LangChainCallback {
27
27
  // Use the spanId already assigned by super for this runId.
28
28
  const spanId = this.runIdToSpanId.get(runId) ?? this.nextSpanId();
29
29
  this.runIdToSpanId.set(runId, spanId);
30
+ // Register agent name so _findAgentName() resolves it for child llm_call / tool_call spans.
31
+ this.runIdToAgentName.set(runId, nodeName);
30
32
  this.visibe.batcher.add(this.traceId, this.visibe.buildAgentStartSpan({
31
33
  spanId,
32
34
  agentName: nodeName,
@@ -10,6 +10,8 @@ export declare class LangChainCallback {
10
10
  protected readonly traceId: string;
11
11
  protected readonly agentName: string;
12
12
  protected runIdToSpanId: Map<string, string>;
13
+ protected runIdToParent: Map<string, string>;
14
+ protected runIdToAgentName: Map<string, string>;
13
15
  protected pendingLLMCalls: Map<string, {
14
16
  startMs: number;
15
17
  model?: string;
@@ -23,6 +25,7 @@ export declare class LangChainCallback {
23
25
  protected stepCounter: number;
24
26
  protected nextSpanId(): string;
25
27
  protected _emitRootAgentStart: boolean;
28
+ protected _findAgentName(runId?: string): string;
26
29
  totalInputTokens: number;
27
30
  totalOutputTokens: number;
28
31
  totalCost: number;
@@ -46,3 +49,4 @@ export declare class LangChainCallback {
46
49
  _onToolSpan?: () => void;
47
50
  }
48
51
  export declare function patchRunnableSequence(lcModule: any, visibe: Visibe): () => void;
52
+ export declare function patchAgentExecutor(agentsModule: any, visibe: Visibe): () => void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@visibe.ai/node",
3
- "version": "0.1.20",
3
+ "version": "0.1.22",
4
4
  "description": "AI Agent Observability — Track OpenAI, LangChain, LangGraph, Bedrock, Vercel AI, Anthropic",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",