@visibe.ai/node 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +330 -0
- package/dist/cjs/api.js +92 -0
- package/dist/cjs/client.js +242 -0
- package/dist/cjs/index.js +216 -0
- package/dist/cjs/integrations/anthropic.js +277 -0
- package/dist/cjs/integrations/base.js +32 -0
- package/dist/cjs/integrations/bedrock.js +442 -0
- package/dist/cjs/integrations/group-context.js +10 -0
- package/dist/cjs/integrations/langchain.js +274 -0
- package/dist/cjs/integrations/langgraph.js +173 -0
- package/dist/cjs/integrations/openai.js +447 -0
- package/dist/cjs/integrations/vercel-ai.js +261 -0
- package/dist/cjs/types/index.js +5 -0
- package/dist/cjs/utils.js +122 -0
- package/dist/esm/api.js +87 -0
- package/dist/esm/client.js +238 -0
- package/dist/esm/index.js +209 -0
- package/dist/esm/integrations/anthropic.js +272 -0
- package/dist/esm/integrations/base.js +28 -0
- package/dist/esm/integrations/bedrock.js +438 -0
- package/dist/esm/integrations/group-context.js +7 -0
- package/dist/esm/integrations/langchain.js +269 -0
- package/dist/esm/integrations/langgraph.js +168 -0
- package/dist/esm/integrations/openai.js +442 -0
- package/dist/esm/integrations/vercel-ai.js +258 -0
- package/dist/esm/types/index.js +4 -0
- package/dist/esm/utils.js +116 -0
- package/dist/types/api.d.ts +27 -0
- package/dist/types/client.d.ts +50 -0
- package/dist/types/index.d.ts +7 -0
- package/dist/types/integrations/anthropic.d.ts +9 -0
- package/dist/types/integrations/base.d.ts +17 -0
- package/dist/types/integrations/bedrock.d.ts +11 -0
- package/dist/types/integrations/group-context.d.ts +12 -0
- package/dist/types/integrations/langchain.d.ts +40 -0
- package/dist/types/integrations/langgraph.d.ts +13 -0
- package/dist/types/integrations/openai.d.ts +11 -0
- package/dist/types/integrations/vercel-ai.d.ts +2 -0
- package/dist/types/types/index.d.ts +21 -0
- package/dist/types/utils.d.ts +23 -0
- package/package.json +80 -0
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LangChainCallback = exports.activeLangChainStorage = void 0;
|
|
4
|
+
exports.patchRunnableSequence = patchRunnableSequence;
|
|
5
|
+
const node_async_hooks_1 = require("node:async_hooks");
|
|
6
|
+
const node_crypto_1 = require("node:crypto");
|
|
7
|
+
const utils_1 = require("../utils");
|
|
8
|
+
// Re-export the storage so openai.ts and langgraph.ts can share the same instance.
|
|
9
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
10
|
+
exports.activeLangChainStorage = new node_async_hooks_1.AsyncLocalStorage();
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// LangChain token extraction
|
|
13
|
+
// Different providers nest token usage in different locations.
|
|
14
|
+
// Check in the order specified by the spec.
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
17
|
+
function extractTokenUsage(output) {
|
|
18
|
+
const usage = output?.llmOutput?.tokenUsage
|
|
19
|
+
?? output?.llmOutput?.usage
|
|
20
|
+
?? output?.generations?.[0]?.[0]?.generationInfo?.usage;
|
|
21
|
+
// Use ?? not || so token counts of 0 are preserved correctly.
|
|
22
|
+
return {
|
|
23
|
+
inputTokens: usage?.promptTokens ?? usage?.input_tokens ?? 0,
|
|
24
|
+
outputTokens: usage?.completionTokens ?? usage?.output_tokens ?? 0,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
// LangChainCallback
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
class LangChainCallback {
|
|
31
|
+
nextSpanId() {
|
|
32
|
+
return `step_${++this.stepCounter}`;
|
|
33
|
+
}
|
|
34
|
+
constructor(options) {
|
|
35
|
+
// Required by @langchain/core v1+ for proper callback registration.
|
|
36
|
+
// Without `name`, ensureHandler() wraps via fromMethods() which drops prototype methods.
|
|
37
|
+
// Without `awaitHandlers`, callbacks run in a background queue (p-queue) and fire
|
|
38
|
+
// after model.invoke() returns — causing spans to be missed on flush/completeTrace.
|
|
39
|
+
this.name = 'visibe-langchain-callback';
|
|
40
|
+
this.awaitHandlers = true;
|
|
41
|
+
this.raiseError = false;
|
|
42
|
+
// Maps LangChain runId → our spanId so we can set parent_span_id.
|
|
43
|
+
this.runIdToSpanId = new Map();
|
|
44
|
+
// Tracks start times so we can compute durationMs.
|
|
45
|
+
this.pendingLLMCalls = new Map(); // runId → startMs
|
|
46
|
+
this.pendingToolCalls = new Map();
|
|
47
|
+
this.stepCounter = 0;
|
|
48
|
+
// Agents we have already emitted agent_start spans for.
|
|
49
|
+
this.seenAgents = new Set();
|
|
50
|
+
// Token / call accumulators — updated by handleLLMEnd, read by patchCompiledStateGraph
|
|
51
|
+
// and patchRunnableSequence to populate completeTrace totals.
|
|
52
|
+
this.totalInputTokens = 0;
|
|
53
|
+
this.totalOutputTokens = 0;
|
|
54
|
+
this.llmCallCount = 0;
|
|
55
|
+
this.visibe = options.visibe;
|
|
56
|
+
this.traceId = options.traceId;
|
|
57
|
+
this.agentName = options.agentName;
|
|
58
|
+
}
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
// LLM events
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
63
|
+
async handleLLMStart(_llm, _messages, runId) {
|
|
64
|
+
this.pendingLLMCalls.set(runId, Date.now());
|
|
65
|
+
}
|
|
66
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
67
|
+
async handleLLMEnd(output, runId, parentRunId) {
|
|
68
|
+
const startMs = this.pendingLLMCalls.get(runId) ?? Date.now();
|
|
69
|
+
this.pendingLLMCalls.delete(runId);
|
|
70
|
+
const { inputTokens, outputTokens } = extractTokenUsage(output);
|
|
71
|
+
const gen = output?.generations?.[0]?.[0];
|
|
72
|
+
const model = gen?.generationInfo?.model ?? this.agentName;
|
|
73
|
+
const cost = (0, utils_1.calculateCost)(model, inputTokens, outputTokens);
|
|
74
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
75
|
+
const rawText = gen?.text ?? gen?.message?.content ?? '';
|
|
76
|
+
const outputText = typeof rawText === 'string' ? rawText : JSON.stringify(rawText);
|
|
77
|
+
const spanId = this.nextSpanId();
|
|
78
|
+
this.runIdToSpanId.set(runId, spanId);
|
|
79
|
+
const parentSpanId = parentRunId ? this.runIdToSpanId.get(parentRunId) : undefined;
|
|
80
|
+
const span = this.visibe.buildLLMSpan({
|
|
81
|
+
spanId,
|
|
82
|
+
parentSpanId,
|
|
83
|
+
agentName: this.agentName,
|
|
84
|
+
model,
|
|
85
|
+
status: 'success',
|
|
86
|
+
inputTokens,
|
|
87
|
+
outputTokens,
|
|
88
|
+
inputText: '', // LangChain doesn't surface the raw prompt here
|
|
89
|
+
outputText,
|
|
90
|
+
durationMs: Date.now() - startMs,
|
|
91
|
+
});
|
|
92
|
+
this.visibe.batcher.add(this.traceId, span);
|
|
93
|
+
// Update local accumulators (used by patchCompiledStateGraph / patchRunnableSequence).
|
|
94
|
+
this.totalInputTokens += inputTokens;
|
|
95
|
+
this.totalOutputTokens += outputTokens;
|
|
96
|
+
this.llmCallCount++;
|
|
97
|
+
// Notify track() accumulator if running inside a group tracker.
|
|
98
|
+
this._onLLMSpan?.(inputTokens, outputTokens, cost);
|
|
99
|
+
}
|
|
100
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
101
|
+
async handleLLMError(err, runId) {
|
|
102
|
+
this.pendingLLMCalls.delete(runId);
|
|
103
|
+
this.visibe.batcher.add(this.traceId, this.visibe.buildErrorSpan({
|
|
104
|
+
spanId: this.nextSpanId(),
|
|
105
|
+
errorType: err?.constructor?.name ?? 'Error',
|
|
106
|
+
errorMessage: err?.message ?? String(err),
|
|
107
|
+
}));
|
|
108
|
+
}
|
|
109
|
+
// ---------------------------------------------------------------------------
|
|
110
|
+
// Tool events
|
|
111
|
+
// ---------------------------------------------------------------------------
|
|
112
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
113
|
+
async handleToolStart(_tool, input, runId) {
|
|
114
|
+
this.pendingToolCalls.set(runId, { startMs: Date.now(), inputText: input });
|
|
115
|
+
}
|
|
116
|
+
async handleToolEnd(output, runId, parentRunId) {
|
|
117
|
+
const pending = this.pendingToolCalls.get(runId);
|
|
118
|
+
this.pendingToolCalls.delete(runId);
|
|
119
|
+
const spanId = this.nextSpanId();
|
|
120
|
+
this.runIdToSpanId.set(runId, spanId);
|
|
121
|
+
const parentSpanId = parentRunId ? this.runIdToSpanId.get(parentRunId) : undefined;
|
|
122
|
+
const span = this.visibe.buildToolSpan({
|
|
123
|
+
spanId,
|
|
124
|
+
parentSpanId,
|
|
125
|
+
toolName: 'tool',
|
|
126
|
+
agentName: this.agentName,
|
|
127
|
+
status: 'success',
|
|
128
|
+
durationMs: pending ? Date.now() - pending.startMs : 0,
|
|
129
|
+
inputText: pending?.inputText ?? '',
|
|
130
|
+
outputText: output,
|
|
131
|
+
});
|
|
132
|
+
this.visibe.batcher.add(this.traceId, span);
|
|
133
|
+
this._onToolSpan?.();
|
|
134
|
+
}
|
|
135
|
+
async handleToolError(err, runId) {
|
|
136
|
+
this.pendingToolCalls.delete(runId);
|
|
137
|
+
this.visibe.batcher.add(this.traceId, this.visibe.buildErrorSpan({
|
|
138
|
+
spanId: this.nextSpanId(),
|
|
139
|
+
errorType: err?.constructor?.name ?? 'Error',
|
|
140
|
+
errorMessage: err?.message ?? String(err),
|
|
141
|
+
}));
|
|
142
|
+
}
|
|
143
|
+
// ---------------------------------------------------------------------------
|
|
144
|
+
// Chain events
|
|
145
|
+
// ---------------------------------------------------------------------------
|
|
146
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
147
|
+
async handleChainStart(chain, _inputs, runId, parentRunId) {
|
|
148
|
+
// Emit an agent_start span the first time we see a named chain.
|
|
149
|
+
const chainName = chain?.id?.at(-1) ?? '';
|
|
150
|
+
if (chainName && !this.seenAgents.has(chainName)) {
|
|
151
|
+
this.seenAgents.add(chainName);
|
|
152
|
+
const spanId = this.nextSpanId();
|
|
153
|
+
this.runIdToSpanId.set(runId, spanId);
|
|
154
|
+
void parentRunId; // suppress unused warning
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
158
|
+
async handleChainEnd(_outputs, _runId) { }
|
|
159
|
+
async handleChainError(err, _runId) {
|
|
160
|
+
this.visibe.batcher.add(this.traceId, this.visibe.buildErrorSpan({
|
|
161
|
+
spanId: this.nextSpanId(),
|
|
162
|
+
errorType: err?.constructor?.name ?? 'Error',
|
|
163
|
+
errorMessage: err?.message ?? String(err),
|
|
164
|
+
}));
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
exports.LangChainCallback = LangChainCallback;
|
|
168
|
+
// ---------------------------------------------------------------------------
|
|
169
|
+
// patchRunnableSequence — patches RunnableSequence so pipe() chains are
|
|
170
|
+
// automatically instrumented. Called from index.ts patchFramework().
|
|
171
|
+
// ---------------------------------------------------------------------------
|
|
172
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
173
|
+
function patchRunnableSequence(lcModule, visibe) {
|
|
174
|
+
const RunnableSequence = lcModule?.RunnableSequence;
|
|
175
|
+
if (!RunnableSequence)
|
|
176
|
+
return () => { };
|
|
177
|
+
const originalInvoke = RunnableSequence.prototype.invoke;
|
|
178
|
+
const originalStream = RunnableSequence.prototype.stream;
|
|
179
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
180
|
+
RunnableSequence.prototype.invoke = async function (input, config) {
|
|
181
|
+
// If already inside a LangChain trace, pass through.
|
|
182
|
+
if (exports.activeLangChainStorage.getStore() !== undefined) {
|
|
183
|
+
return originalInvoke.call(this, input, config);
|
|
184
|
+
}
|
|
185
|
+
const traceId = (0, node_crypto_1.randomUUID)();
|
|
186
|
+
const startedAt = new Date().toISOString();
|
|
187
|
+
const startMs = Date.now();
|
|
188
|
+
await visibe.apiClient.createTrace({
|
|
189
|
+
trace_id: traceId,
|
|
190
|
+
name: 'langchain',
|
|
191
|
+
framework: 'langchain',
|
|
192
|
+
started_at: startedAt,
|
|
193
|
+
...(visibe.sessionId ? { session_id: visibe.sessionId } : {}),
|
|
194
|
+
});
|
|
195
|
+
const cb = new LangChainCallback({ visibe, traceId, agentName: 'langchain' });
|
|
196
|
+
let result;
|
|
197
|
+
let status = 'completed';
|
|
198
|
+
try {
|
|
199
|
+
result = await exports.activeLangChainStorage.run(cb, () => originalInvoke.call(this, input, _mergeCallbacks(config, cb)));
|
|
200
|
+
}
|
|
201
|
+
catch (err) {
|
|
202
|
+
status = 'failed';
|
|
203
|
+
throw err;
|
|
204
|
+
}
|
|
205
|
+
finally {
|
|
206
|
+
visibe.batcher.flush();
|
|
207
|
+
await visibe.apiClient.completeTrace(traceId, {
|
|
208
|
+
status,
|
|
209
|
+
ended_at: new Date().toISOString(),
|
|
210
|
+
duration_ms: Date.now() - startMs,
|
|
211
|
+
total_tokens: cb.totalInputTokens + cb.totalOutputTokens,
|
|
212
|
+
total_input_tokens: cb.totalInputTokens,
|
|
213
|
+
total_output_tokens: cb.totalOutputTokens,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
return result;
|
|
217
|
+
};
|
|
218
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
219
|
+
RunnableSequence.prototype.stream = async function* (input, config) {
|
|
220
|
+
if (exports.activeLangChainStorage.getStore() !== undefined) {
|
|
221
|
+
yield* originalStream.call(this, input, config);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
const traceId = (0, node_crypto_1.randomUUID)();
|
|
225
|
+
const startedAt = new Date().toISOString();
|
|
226
|
+
const startMs = Date.now();
|
|
227
|
+
await visibe.apiClient.createTrace({
|
|
228
|
+
trace_id: traceId,
|
|
229
|
+
name: 'langchain',
|
|
230
|
+
framework: 'langchain',
|
|
231
|
+
started_at: startedAt,
|
|
232
|
+
...(visibe.sessionId ? { session_id: visibe.sessionId } : {}),
|
|
233
|
+
});
|
|
234
|
+
const cb = new LangChainCallback({ visibe, traceId, agentName: 'langchain' });
|
|
235
|
+
let status = 'completed';
|
|
236
|
+
try {
|
|
237
|
+
// RunnableSequence.stream() is an async function (not async generator) returning
|
|
238
|
+
// a Promise<AsyncIterable>. activeLangChainStorage.run returns that Promise,
|
|
239
|
+
// so we must await before yield*.
|
|
240
|
+
const gen = await exports.activeLangChainStorage.run(cb, () => originalStream.call(this, input, _mergeCallbacks(config, cb)));
|
|
241
|
+
yield* gen;
|
|
242
|
+
}
|
|
243
|
+
catch (err) {
|
|
244
|
+
status = 'failed';
|
|
245
|
+
throw err;
|
|
246
|
+
}
|
|
247
|
+
finally {
|
|
248
|
+
visibe.batcher.flush();
|
|
249
|
+
await visibe.apiClient.completeTrace(traceId, {
|
|
250
|
+
status,
|
|
251
|
+
ended_at: new Date().toISOString(),
|
|
252
|
+
duration_ms: Date.now() - startMs,
|
|
253
|
+
total_tokens: cb.totalInputTokens + cb.totalOutputTokens,
|
|
254
|
+
total_input_tokens: cb.totalInputTokens,
|
|
255
|
+
total_output_tokens: cb.totalOutputTokens,
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
return () => {
|
|
260
|
+
RunnableSequence.prototype.invoke = originalInvoke;
|
|
261
|
+
RunnableSequence.prototype.stream = originalStream;
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
// ---------------------------------------------------------------------------
|
|
265
|
+
// Private helpers
|
|
266
|
+
// ---------------------------------------------------------------------------
|
|
267
|
+
// Merge our callback into an existing LangChain config object.
|
|
268
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
269
|
+
function _mergeCallbacks(config, cb) {
|
|
270
|
+
if (!config)
|
|
271
|
+
return { callbacks: [cb] };
|
|
272
|
+
const existing = Array.isArray(config.callbacks) ? config.callbacks : [];
|
|
273
|
+
return { ...config, callbacks: [...existing, cb] };
|
|
274
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LangGraphCallback = void 0;
|
|
4
|
+
exports.patchCompiledStateGraph = patchCompiledStateGraph;
|
|
5
|
+
const node_crypto_1 = require("node:crypto");
|
|
6
|
+
const langchain_1 = require("./langchain");
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// LangGraphCallback
|
|
9
|
+
// Extends LangChainCallback and adds node-level agent_start spans.
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
class LangGraphCallback extends langchain_1.LangChainCallback {
|
|
12
|
+
constructor(options) {
|
|
13
|
+
super(options);
|
|
14
|
+
this.nodeNames = new Set(options.nodeNames ?? []);
|
|
15
|
+
}
|
|
16
|
+
// Override handleChainStart to emit agent_start for known graph nodes.
|
|
17
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
18
|
+
async handleChainStart(chain, inputs, runId, parentRunId) {
|
|
19
|
+
// LangGraph may surface the node key in chain.name (run name) or chain.id[-1] (class name).
|
|
20
|
+
const chainName = chain?.name ?? chain?.id?.at(-1) ?? '';
|
|
21
|
+
if (chainName && this.nodeNames.has(chainName)) {
|
|
22
|
+
const spanId = this.nextSpanId();
|
|
23
|
+
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
|
+
this.visibe.batcher.add(this.traceId, this.visibe.buildAgentStartSpan({
|
|
27
|
+
spanId,
|
|
28
|
+
agentName: chainName,
|
|
29
|
+
}));
|
|
30
|
+
// Don't call super — we've already set the runId mapping.
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
await super.handleChainStart(chain, inputs, runId, parentRunId);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
exports.LangGraphCallback = LangGraphCallback;
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
// patchCompiledStateGraph
|
|
39
|
+
// Patches the CompiledStateGraph class so every graph created after init()
|
|
40
|
+
// is automatically instrumented. Class-level patching is required — patching
|
|
41
|
+
// a single instance only instruments that one instance.
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
44
|
+
function patchCompiledStateGraph(lgModule, visibe) {
|
|
45
|
+
const CompiledStateGraph = lgModule?.CompiledStateGraph;
|
|
46
|
+
if (!CompiledStateGraph)
|
|
47
|
+
return () => { };
|
|
48
|
+
const originalInvoke = CompiledStateGraph.prototype.invoke;
|
|
49
|
+
const originalStream = CompiledStateGraph.prototype.stream;
|
|
50
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
51
|
+
CompiledStateGraph.prototype.invoke = async function (input, config) {
|
|
52
|
+
if (langchain_1.activeLangChainStorage.getStore() !== undefined) {
|
|
53
|
+
return originalInvoke.call(this, input, config);
|
|
54
|
+
}
|
|
55
|
+
const traceId = (0, node_crypto_1.randomUUID)();
|
|
56
|
+
const startedAt = new Date().toISOString();
|
|
57
|
+
const startMs = Date.now();
|
|
58
|
+
const graphName = this.name ?? 'langgraph';
|
|
59
|
+
await visibe.apiClient.createTrace({
|
|
60
|
+
trace_id: traceId,
|
|
61
|
+
name: graphName,
|
|
62
|
+
framework: 'langgraph',
|
|
63
|
+
started_at: startedAt,
|
|
64
|
+
...(visibe.sessionId ? { session_id: visibe.sessionId } : {}),
|
|
65
|
+
});
|
|
66
|
+
// Collect node names from the graph's node registry.
|
|
67
|
+
const nodeNames = _extractNodeNames(this);
|
|
68
|
+
const cb = new LangGraphCallback({
|
|
69
|
+
visibe,
|
|
70
|
+
traceId,
|
|
71
|
+
agentName: graphName,
|
|
72
|
+
nodeNames,
|
|
73
|
+
});
|
|
74
|
+
let result;
|
|
75
|
+
let status = 'completed';
|
|
76
|
+
try {
|
|
77
|
+
result = await langchain_1.activeLangChainStorage.run(cb, () => originalInvoke.call(this, input, _mergeCallbacks(config, cb)));
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
status = 'failed';
|
|
81
|
+
throw err;
|
|
82
|
+
}
|
|
83
|
+
finally {
|
|
84
|
+
visibe.batcher.flush();
|
|
85
|
+
await visibe.apiClient.completeTrace(traceId, {
|
|
86
|
+
status,
|
|
87
|
+
ended_at: new Date().toISOString(),
|
|
88
|
+
duration_ms: Date.now() - startMs,
|
|
89
|
+
total_tokens: cb.totalInputTokens + cb.totalOutputTokens,
|
|
90
|
+
total_input_tokens: cb.totalInputTokens,
|
|
91
|
+
total_output_tokens: cb.totalOutputTokens,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
return result;
|
|
95
|
+
};
|
|
96
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
97
|
+
CompiledStateGraph.prototype.stream = async function* (input, config) {
|
|
98
|
+
if (langchain_1.activeLangChainStorage.getStore() !== undefined) {
|
|
99
|
+
// Pregel.stream() is a regular async function returning a Promise<AsyncIterable>.
|
|
100
|
+
// We must await it before yield*.
|
|
101
|
+
yield* (await originalStream.call(this, input, config));
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const traceId = (0, node_crypto_1.randomUUID)();
|
|
105
|
+
const startedAt = new Date().toISOString();
|
|
106
|
+
const startMs = Date.now();
|
|
107
|
+
const graphName = this.name ?? 'langgraph';
|
|
108
|
+
await visibe.apiClient.createTrace({
|
|
109
|
+
trace_id: traceId,
|
|
110
|
+
name: graphName,
|
|
111
|
+
framework: 'langgraph',
|
|
112
|
+
started_at: startedAt,
|
|
113
|
+
...(visibe.sessionId ? { session_id: visibe.sessionId } : {}),
|
|
114
|
+
});
|
|
115
|
+
const nodeNames = _extractNodeNames(this);
|
|
116
|
+
const cb = new LangGraphCallback({
|
|
117
|
+
visibe,
|
|
118
|
+
traceId,
|
|
119
|
+
agentName: graphName,
|
|
120
|
+
nodeNames,
|
|
121
|
+
});
|
|
122
|
+
let status = 'completed';
|
|
123
|
+
try {
|
|
124
|
+
// activeLangChainStorage.run returns the callback's return value synchronously,
|
|
125
|
+
// which is a Promise<AsyncIterable> from Pregel.stream(). Await before yield*.
|
|
126
|
+
const gen = await langchain_1.activeLangChainStorage.run(cb, () => originalStream.call(this, input, _mergeCallbacks(config, cb)));
|
|
127
|
+
yield* gen;
|
|
128
|
+
}
|
|
129
|
+
catch (err) {
|
|
130
|
+
status = 'failed';
|
|
131
|
+
throw err;
|
|
132
|
+
}
|
|
133
|
+
finally {
|
|
134
|
+
visibe.batcher.flush();
|
|
135
|
+
await visibe.apiClient.completeTrace(traceId, {
|
|
136
|
+
status,
|
|
137
|
+
ended_at: new Date().toISOString(),
|
|
138
|
+
duration_ms: Date.now() - startMs,
|
|
139
|
+
total_tokens: cb.totalInputTokens + cb.totalOutputTokens,
|
|
140
|
+
total_input_tokens: cb.totalInputTokens,
|
|
141
|
+
total_output_tokens: cb.totalOutputTokens,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
return () => {
|
|
146
|
+
CompiledStateGraph.prototype.invoke = originalInvoke;
|
|
147
|
+
CompiledStateGraph.prototype.stream = originalStream;
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
// ---------------------------------------------------------------------------
|
|
151
|
+
// Private helpers
|
|
152
|
+
// ---------------------------------------------------------------------------
|
|
153
|
+
// Extract the set of node names from a compiled graph's internal node registry.
|
|
154
|
+
// LangGraph stores nodes in `this.nodes` (a Map or plain object keyed by name).
|
|
155
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
156
|
+
function _extractNodeNames(graph) {
|
|
157
|
+
try {
|
|
158
|
+
const nodes = graph.nodes;
|
|
159
|
+
if (nodes instanceof Map)
|
|
160
|
+
return Array.from(nodes.keys());
|
|
161
|
+
if (nodes && typeof nodes === 'object')
|
|
162
|
+
return Object.keys(nodes);
|
|
163
|
+
}
|
|
164
|
+
catch { /* ignore */ }
|
|
165
|
+
return [];
|
|
166
|
+
}
|
|
167
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
168
|
+
function _mergeCallbacks(config, cb) {
|
|
169
|
+
if (!config)
|
|
170
|
+
return { callbacks: [cb] };
|
|
171
|
+
const existing = Array.isArray(config.callbacks) ? config.callbacks : [];
|
|
172
|
+
return { ...config, callbacks: [...existing, cb] };
|
|
173
|
+
}
|