@visibe.ai/node 0.1.17 → 0.1.19
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/integrations/langchain.js +49 -32
- package/dist/cjs/integrations/langgraph.js +16 -12
- 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 +2 -29
|
@@ -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,
|
|
@@ -7,19 +7,19 @@ export const activeLangChainStorage = new AsyncLocalStorage();
|
|
|
7
7
|
// ---------------------------------------------------------------------------
|
|
8
8
|
// LangChain token extraction
|
|
9
9
|
// Different providers nest token usage in different locations.
|
|
10
|
-
// Check in the order specified by the spec.
|
|
11
10
|
// ---------------------------------------------------------------------------
|
|
12
11
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
13
12
|
function extractTokenUsage(output) {
|
|
14
13
|
const usage = output?.llmOutput?.tokenUsage
|
|
15
14
|
?? output?.llmOutput?.usage
|
|
16
15
|
?? output?.generations?.[0]?.[0]?.generationInfo?.usage;
|
|
17
|
-
// Use ?? not || so token counts of 0 are preserved correctly.
|
|
18
16
|
return {
|
|
19
17
|
inputTokens: usage?.promptTokens ?? usage?.input_tokens ?? 0,
|
|
20
18
|
outputTokens: usage?.completionTokens ?? usage?.output_tokens ?? 0,
|
|
21
19
|
};
|
|
22
20
|
}
|
|
21
|
+
// Internal LangGraph system node names — never emit agent_start spans for these.
|
|
22
|
+
export const LANGGRAPH_INTERNAL_NODES = new Set(['__start__', '__end__', 'LangGraph']);
|
|
23
23
|
// ---------------------------------------------------------------------------
|
|
24
24
|
// LangChainCallback
|
|
25
25
|
// ---------------------------------------------------------------------------
|
|
@@ -30,23 +30,21 @@ export class LangChainCallback {
|
|
|
30
30
|
constructor(options) {
|
|
31
31
|
// Required by @langchain/core v1+ for proper callback registration.
|
|
32
32
|
// Without `name`, ensureHandler() wraps via fromMethods() which drops prototype methods.
|
|
33
|
-
// Without `awaitHandlers`, callbacks run in a background queue
|
|
34
|
-
//
|
|
33
|
+
// Without `awaitHandlers`, callbacks run in a background queue and fire after
|
|
34
|
+
// model.invoke() returns — causing spans to be missed on flush/completeTrace.
|
|
35
35
|
this.name = 'visibe-langchain-callback';
|
|
36
36
|
this.awaitHandlers = true;
|
|
37
37
|
this.raiseError = false;
|
|
38
38
|
// Maps LangChain runId → our spanId so we can set parent_span_id.
|
|
39
39
|
this.runIdToSpanId = new Map();
|
|
40
|
-
//
|
|
41
|
-
this.pendingLLMCalls = new Map();
|
|
40
|
+
// Pending LLM calls: runId → { startMs, model, inputText }
|
|
41
|
+
this.pendingLLMCalls = new Map();
|
|
42
42
|
this.pendingToolCalls = new Map();
|
|
43
43
|
this.stepCounter = 0;
|
|
44
|
-
//
|
|
45
|
-
this.seenAgents = new Set();
|
|
46
|
-
// Token / call accumulators — updated by handleLLMEnd, read by patchCompiledStateGraph
|
|
47
|
-
// and patchRunnableSequence to populate completeTrace totals.
|
|
44
|
+
// Token / call / cost accumulators — read by patchCompiledStateGraph / patchRunnableSequence.
|
|
48
45
|
this.totalInputTokens = 0;
|
|
49
46
|
this.totalOutputTokens = 0;
|
|
47
|
+
this.totalCost = 0;
|
|
50
48
|
this.llmCallCount = 0;
|
|
51
49
|
this.visibe = options.visibe;
|
|
52
50
|
this.traceId = options.traceId;
|
|
@@ -55,17 +53,40 @@ export class LangChainCallback {
|
|
|
55
53
|
// ---------------------------------------------------------------------------
|
|
56
54
|
// LLM events
|
|
57
55
|
// ---------------------------------------------------------------------------
|
|
56
|
+
// Called for text-completion models.
|
|
58
57
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
59
|
-
async handleLLMStart(
|
|
60
|
-
|
|
58
|
+
async handleLLMStart(llm, prompts, runId) {
|
|
59
|
+
const model = llm?.kwargs?.model ?? llm?.kwargs?.model_name ?? undefined;
|
|
60
|
+
const inputText = Array.isArray(prompts) ? prompts.join('\n') : '';
|
|
61
|
+
this.pendingLLMCalls.set(runId, { startMs: Date.now(), model, inputText });
|
|
62
|
+
}
|
|
63
|
+
// Called for chat models (ChatOpenAI, ChatAnthropic, etc.).
|
|
64
|
+
// messages is BaseMessage[][] — one array per parallel completion request.
|
|
65
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
66
|
+
async handleChatModelStart(llm, messages, runId) {
|
|
67
|
+
const model = llm?.kwargs?.model ?? llm?.kwargs?.model_name ?? undefined;
|
|
68
|
+
let inputText = '';
|
|
69
|
+
try {
|
|
70
|
+
const msgs = messages?.[0] ?? [];
|
|
71
|
+
inputText = msgs.map((m) => {
|
|
72
|
+
const role = m?.getType?.() ?? m?._getType?.() ?? m?.role ?? 'user';
|
|
73
|
+
const content = typeof m?.content === 'string'
|
|
74
|
+
? m.content
|
|
75
|
+
: JSON.stringify(m?.content ?? '');
|
|
76
|
+
return `${role}: ${content}`;
|
|
77
|
+
}).join('\n');
|
|
78
|
+
}
|
|
79
|
+
catch { /* ignore serialisation errors */ }
|
|
80
|
+
this.pendingLLMCalls.set(runId, { startMs: Date.now(), model, inputText });
|
|
61
81
|
}
|
|
62
82
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
63
83
|
async handleLLMEnd(output, runId, parentRunId) {
|
|
64
|
-
const
|
|
84
|
+
const pending = this.pendingLLMCalls.get(runId) ?? { startMs: Date.now() };
|
|
65
85
|
this.pendingLLMCalls.delete(runId);
|
|
66
86
|
const { inputTokens, outputTokens } = extractTokenUsage(output);
|
|
67
87
|
const gen = output?.generations?.[0]?.[0];
|
|
68
|
-
|
|
88
|
+
// Prefer model saved at LLM-start, fall back to generationInfo, then agentName.
|
|
89
|
+
const model = pending.model ?? gen?.generationInfo?.model ?? this.agentName;
|
|
69
90
|
const cost = calculateCost(model, inputTokens, outputTokens);
|
|
70
91
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
71
92
|
const rawText = gen?.text ?? gen?.message?.content ?? '';
|
|
@@ -81,16 +102,15 @@ export class LangChainCallback {
|
|
|
81
102
|
status: 'success',
|
|
82
103
|
inputTokens,
|
|
83
104
|
outputTokens,
|
|
84
|
-
inputText: '',
|
|
105
|
+
inputText: pending.inputText ?? '',
|
|
85
106
|
outputText,
|
|
86
|
-
durationMs: Date.now() - startMs,
|
|
107
|
+
durationMs: Date.now() - pending.startMs,
|
|
87
108
|
});
|
|
88
109
|
this.visibe.batcher.add(this.traceId, span);
|
|
89
|
-
// Update local accumulators (used by patchCompiledStateGraph / patchRunnableSequence).
|
|
90
110
|
this.totalInputTokens += inputTokens;
|
|
91
111
|
this.totalOutputTokens += outputTokens;
|
|
112
|
+
this.totalCost += cost;
|
|
92
113
|
this.llmCallCount++;
|
|
93
|
-
// Notify track() accumulator if running inside a group tracker.
|
|
94
114
|
this._onLLMSpan?.(inputTokens, outputTokens, cost);
|
|
95
115
|
}
|
|
96
116
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -139,16 +159,15 @@ export class LangChainCallback {
|
|
|
139
159
|
}
|
|
140
160
|
// ---------------------------------------------------------------------------
|
|
141
161
|
// Chain events
|
|
162
|
+
//
|
|
163
|
+
// In LangGraph v1.2+, the node key is passed as the 8th `name` parameter, NOT
|
|
164
|
+
// via chain.name. We always store runId → spanId so nested LLM/tool calls get
|
|
165
|
+
// a valid parent_span_id regardless of whether the chain is a user-defined node.
|
|
142
166
|
// ---------------------------------------------------------------------------
|
|
143
167
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
144
|
-
async handleChainStart(
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
if (chainName && !this.seenAgents.has(chainName)) {
|
|
148
|
-
this.seenAgents.add(chainName);
|
|
149
|
-
const spanId = this.nextSpanId();
|
|
150
|
-
this.runIdToSpanId.set(runId, spanId);
|
|
151
|
-
void parentRunId; // suppress unused warning
|
|
168
|
+
async handleChainStart(_chain, _inputs, runId, _parentRunId, _tags, _metadata, _runType, _name) {
|
|
169
|
+
if (!this.runIdToSpanId.has(runId)) {
|
|
170
|
+
this.runIdToSpanId.set(runId, this.nextSpanId());
|
|
152
171
|
}
|
|
153
172
|
}
|
|
154
173
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -174,7 +193,6 @@ export function patchRunnableSequence(lcModule, visibe) {
|
|
|
174
193
|
const originalStream = RunnableSequence.prototype.stream;
|
|
175
194
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
176
195
|
RunnableSequence.prototype.invoke = async function (input, config) {
|
|
177
|
-
// If already inside a LangChain trace, pass through.
|
|
178
196
|
if (activeLangChainStorage.getStore() !== undefined) {
|
|
179
197
|
return originalInvoke.call(this, input, config);
|
|
180
198
|
}
|
|
@@ -189,6 +207,7 @@ export function patchRunnableSequence(lcModule, visibe) {
|
|
|
189
207
|
...(visibe.sessionId ? { session_id: visibe.sessionId } : {}),
|
|
190
208
|
});
|
|
191
209
|
const cb = new LangChainCallback({ visibe, traceId, agentName: 'langchain' });
|
|
210
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
192
211
|
let result;
|
|
193
212
|
let status = 'completed';
|
|
194
213
|
try {
|
|
@@ -205,6 +224,7 @@ export function patchRunnableSequence(lcModule, visibe) {
|
|
|
205
224
|
ended_at: new Date().toISOString(),
|
|
206
225
|
duration_ms: Date.now() - startMs,
|
|
207
226
|
llm_call_count: cb.llmCallCount,
|
|
227
|
+
total_cost: cb.totalCost,
|
|
208
228
|
total_tokens: cb.totalInputTokens + cb.totalOutputTokens,
|
|
209
229
|
total_input_tokens: cb.totalInputTokens,
|
|
210
230
|
total_output_tokens: cb.totalOutputTokens,
|
|
@@ -231,9 +251,6 @@ export function patchRunnableSequence(lcModule, visibe) {
|
|
|
231
251
|
const cb = new LangChainCallback({ visibe, traceId, agentName: 'langchain' });
|
|
232
252
|
let status = 'completed';
|
|
233
253
|
try {
|
|
234
|
-
// RunnableSequence.stream() is an async function (not async generator) returning
|
|
235
|
-
// a Promise<AsyncIterable>. activeLangChainStorage.run returns that Promise,
|
|
236
|
-
// so we must await before yield*.
|
|
237
254
|
const gen = await activeLangChainStorage.run(cb, () => originalStream.call(this, input, _mergeCallbacks(config, cb)));
|
|
238
255
|
yield* gen;
|
|
239
256
|
}
|
|
@@ -248,6 +265,7 @@ export function patchRunnableSequence(lcModule, visibe) {
|
|
|
248
265
|
ended_at: new Date().toISOString(),
|
|
249
266
|
duration_ms: Date.now() - startMs,
|
|
250
267
|
llm_call_count: cb.llmCallCount,
|
|
268
|
+
total_cost: cb.totalCost,
|
|
251
269
|
total_tokens: cb.totalInputTokens + cb.totalOutputTokens,
|
|
252
270
|
total_input_tokens: cb.totalInputTokens,
|
|
253
271
|
total_output_tokens: cb.totalOutputTokens,
|
|
@@ -262,7 +280,6 @@ export function patchRunnableSequence(lcModule, visibe) {
|
|
|
262
280
|
// ---------------------------------------------------------------------------
|
|
263
281
|
// Private helpers
|
|
264
282
|
// ---------------------------------------------------------------------------
|
|
265
|
-
// Merge our callback into an existing LangChain config object.
|
|
266
283
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
267
284
|
function _mergeCallbacks(config, cb) {
|
|
268
285
|
if (!config)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
|
-
import { LangChainCallback, activeLangChainStorage } from './langchain.js';
|
|
2
|
+
import { LangChainCallback, activeLangChainStorage, LANGGRAPH_INTERNAL_NODES } from './langchain.js';
|
|
3
3
|
// ---------------------------------------------------------------------------
|
|
4
4
|
// LangGraphCallback
|
|
5
5
|
// Extends LangChainCallback and adds node-level agent_start spans.
|
|
@@ -9,24 +9,26 @@ export class LangGraphCallback extends LangChainCallback {
|
|
|
9
9
|
super(options);
|
|
10
10
|
this.nodeNames = new Set(options.nodeNames ?? []);
|
|
11
11
|
}
|
|
12
|
-
// Override handleChainStart to emit agent_start for
|
|
12
|
+
// Override handleChainStart to emit agent_start spans for LangGraph nodes.
|
|
13
|
+
//
|
|
14
|
+
// In LangGraph v1.2+, the node key is the 8th `name` parameter (not chain.name).
|
|
15
|
+
// Internal system nodes (__start__, __end__, LangGraph) are filtered out.
|
|
13
16
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
14
|
-
async handleChainStart(chain, inputs, runId, parentRunId) {
|
|
15
|
-
//
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
async handleChainStart(chain, inputs, runId, parentRunId, tags, metadata, runType, name) {
|
|
18
|
+
// Always track the runId so child LLM calls can resolve parent_span_id.
|
|
19
|
+
await super.handleChainStart(chain, inputs, runId, parentRunId, tags, metadata, runType, name);
|
|
20
|
+
// The node key is passed as the 8th `name` parameter in LangGraph v1.2+.
|
|
21
|
+
// Fall back to chain.name for older versions.
|
|
22
|
+
const nodeName = name ?? chain?.name ?? '';
|
|
23
|
+
if (nodeName && !LANGGRAPH_INTERNAL_NODES.has(nodeName)) {
|
|
24
|
+
// Use the spanId already assigned by super for this runId.
|
|
25
|
+
const spanId = this.runIdToSpanId.get(runId) ?? this.nextSpanId();
|
|
19
26
|
this.runIdToSpanId.set(runId, spanId);
|
|
20
|
-
// Emit an agent_start span for this node.
|
|
21
|
-
// type MUST be exactly "agent_start" — the backend validates this string.
|
|
22
27
|
this.visibe.batcher.add(this.traceId, this.visibe.buildAgentStartSpan({
|
|
23
28
|
spanId,
|
|
24
|
-
agentName:
|
|
29
|
+
agentName: nodeName,
|
|
25
30
|
}));
|
|
26
|
-
// Don't call super — we've already set the runId mapping.
|
|
27
|
-
return;
|
|
28
31
|
}
|
|
29
|
-
await super.handleChainStart(chain, inputs, runId, parentRunId);
|
|
30
32
|
}
|
|
31
33
|
}
|
|
32
34
|
// ---------------------------------------------------------------------------
|
|
@@ -82,6 +84,7 @@ export function patchCompiledStateGraph(lgModule, visibe) {
|
|
|
82
84
|
ended_at: new Date().toISOString(),
|
|
83
85
|
duration_ms: Date.now() - startMs,
|
|
84
86
|
llm_call_count: cb.llmCallCount,
|
|
87
|
+
total_cost: cb.totalCost,
|
|
85
88
|
total_tokens: cb.totalInputTokens + cb.totalOutputTokens,
|
|
86
89
|
total_input_tokens: cb.totalInputTokens,
|
|
87
90
|
total_output_tokens: cb.totalOutputTokens,
|
|
@@ -133,6 +136,7 @@ export function patchCompiledStateGraph(lgModule, visibe) {
|
|
|
133
136
|
ended_at: new Date().toISOString(),
|
|
134
137
|
duration_ms: Date.now() - startMs,
|
|
135
138
|
llm_call_count: cb.llmCallCount,
|
|
139
|
+
total_cost: cb.totalCost,
|
|
136
140
|
total_tokens: cb.totalInputTokens + cb.totalOutputTokens,
|
|
137
141
|
total_input_tokens: cb.totalInputTokens,
|
|
138
142
|
total_output_tokens: cb.totalOutputTokens,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
2
|
import type { Visibe } from '../client';
|
|
3
3
|
export declare const activeLangChainStorage: AsyncLocalStorage<LangChainCallback>;
|
|
4
|
+
export declare const LANGGRAPH_INTERNAL_NODES: Set<string>;
|
|
4
5
|
export declare class LangChainCallback {
|
|
5
6
|
readonly name = "visibe-langchain-callback";
|
|
6
7
|
readonly awaitHandlers = true;
|
|
@@ -9,7 +10,11 @@ export declare class LangChainCallback {
|
|
|
9
10
|
protected readonly traceId: string;
|
|
10
11
|
protected readonly agentName: string;
|
|
11
12
|
protected runIdToSpanId: Map<string, string>;
|
|
12
|
-
protected pendingLLMCalls: Map<string,
|
|
13
|
+
protected pendingLLMCalls: Map<string, {
|
|
14
|
+
startMs: number;
|
|
15
|
+
model?: string;
|
|
16
|
+
inputText?: string;
|
|
17
|
+
}>;
|
|
13
18
|
protected pendingToolCalls: Map<string, {
|
|
14
19
|
startMs: number;
|
|
15
20
|
inputText: string;
|
|
@@ -17,22 +22,23 @@ export declare class LangChainCallback {
|
|
|
17
22
|
}>;
|
|
18
23
|
protected stepCounter: number;
|
|
19
24
|
protected nextSpanId(): string;
|
|
20
|
-
protected seenAgents: Set<string>;
|
|
21
25
|
totalInputTokens: number;
|
|
22
26
|
totalOutputTokens: number;
|
|
27
|
+
totalCost: number;
|
|
23
28
|
llmCallCount: number;
|
|
24
29
|
constructor(options: {
|
|
25
30
|
visibe: Visibe;
|
|
26
31
|
traceId: string;
|
|
27
32
|
agentName: string;
|
|
28
33
|
});
|
|
29
|
-
handleLLMStart(
|
|
34
|
+
handleLLMStart(llm: any, prompts: any[], runId: string): Promise<void>;
|
|
35
|
+
handleChatModelStart(llm: any, messages: any[][], runId: string): Promise<void>;
|
|
30
36
|
handleLLMEnd(output: any, runId: string, parentRunId?: string): Promise<void>;
|
|
31
37
|
handleLLMError(err: Error, runId: string): Promise<void>;
|
|
32
38
|
handleToolStart(tool: any, input: string, runId: string): Promise<void>;
|
|
33
39
|
handleToolEnd(output: string, runId: string, parentRunId?: string): Promise<void>;
|
|
34
40
|
handleToolError(err: Error, runId: string): Promise<void>;
|
|
35
|
-
handleChainStart(
|
|
41
|
+
handleChainStart(_chain: any, _inputs: any, runId: string, _parentRunId?: string, _tags?: string[], _metadata?: any, _runType?: string, _name?: string): Promise<void>;
|
|
36
42
|
handleChainEnd(_outputs: any, _runId: string): Promise<void>;
|
|
37
43
|
handleChainError(err: Error, _runId: string): Promise<void>;
|
|
38
44
|
_onLLMSpan?: (inputTokens: number, outputTokens: number, cost: number) => void;
|
|
@@ -8,6 +8,6 @@ export declare class LangGraphCallback extends LangChainCallback {
|
|
|
8
8
|
agentName: string;
|
|
9
9
|
nodeNames?: string[];
|
|
10
10
|
});
|
|
11
|
-
handleChainStart(chain: any, inputs: any, runId: string, parentRunId?: string): Promise<void>;
|
|
11
|
+
handleChainStart(chain: any, inputs: any, runId: string, parentRunId?: string, tags?: string[], metadata?: any, runType?: string, name?: string): Promise<void>;
|
|
12
12
|
}
|
|
13
13
|
export declare function patchCompiledStateGraph(lgModule: any, visibe: Visibe): () => void;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@visibe.ai/node",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.19",
|
|
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",
|
|
@@ -40,35 +40,8 @@
|
|
|
40
40
|
"anthropic"
|
|
41
41
|
],
|
|
42
42
|
"license": "MIT",
|
|
43
|
-
"peerDependencies": {
|
|
44
|
-
"@anthropic-ai/sdk": ">=0.20.0",
|
|
45
|
-
"@aws-sdk/client-bedrock-runtime": ">=3.0.0",
|
|
46
|
-
"@langchain/core": ">=0.1.0",
|
|
47
|
-
"@langchain/langgraph": ">=0.1.0",
|
|
48
|
-
"ai": ">=3.0.0",
|
|
49
|
-
"openai": ">=4.0.0"
|
|
50
|
-
},
|
|
51
|
-
"peerDependenciesMetadata": {
|
|
52
|
-
"openai": {
|
|
53
|
-
"optional": true
|
|
54
|
-
},
|
|
55
|
-
"@langchain/core": {
|
|
56
|
-
"optional": true
|
|
57
|
-
},
|
|
58
|
-
"@langchain/langgraph": {
|
|
59
|
-
"optional": true
|
|
60
|
-
},
|
|
61
|
-
"@aws-sdk/client-bedrock-runtime": {
|
|
62
|
-
"optional": true
|
|
63
|
-
},
|
|
64
|
-
"ai": {
|
|
65
|
-
"optional": true
|
|
66
|
-
},
|
|
67
|
-
"@anthropic-ai/sdk": {
|
|
68
|
-
"optional": true
|
|
69
|
-
}
|
|
70
|
-
},
|
|
71
43
|
"devDependencies": {
|
|
44
|
+
"@aws-sdk/client-bedrock-runtime": "^3.0.0",
|
|
72
45
|
"@langchain/openai": "^1.2.9",
|
|
73
46
|
"@types/jest": "^30.0.0",
|
|
74
47
|
"@types/node": "^20.0.0",
|