@uselemma/tracing 7.6.0 → 7.7.0
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 +61 -23
- package/dist/client.d.ts +4 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +16 -2
- package/dist/client.js.map +1 -1
- package/dist/langchain.d.ts +52 -13
- package/dist/langchain.d.ts.map +1 -1
- package/dist/langchain.js +1010 -129
- package/dist/langchain.js.map +1 -1
- package/dist/openai-agents.d.ts +4 -0
- package/dist/openai-agents.d.ts.map +1 -1
- package/dist/openai-agents.js +235 -26
- package/dist/openai-agents.js.map +1 -1
- package/dist/vercel-ai.d.ts +14 -0
- package/dist/vercel-ai.d.ts.map +1 -1
- package/dist/vercel-ai.js +453 -111
- package/dist/vercel-ai.js.map +1 -1
- package/examples/langchain.ts +21 -4
- package/examples/langgraph.ts +18 -2
- package/examples/openai-agents.ts +11 -2
- package/examples/vercel-ai-v6.ts +28 -13
- package/examples/vercel-ai-v7.ts +28 -13
- package/package.json +1 -1
package/dist/langchain.js
CHANGED
|
@@ -5,6 +5,48 @@ exports.langChain = langChain;
|
|
|
5
5
|
exports.langGraph = langGraph;
|
|
6
6
|
const client_1 = require("./client");
|
|
7
7
|
const tool_result_1 = require("./tool-result");
|
|
8
|
+
const KNOWN_PROVIDERS = [
|
|
9
|
+
"openai",
|
|
10
|
+
"anthropic",
|
|
11
|
+
"azure",
|
|
12
|
+
"azure_openai",
|
|
13
|
+
"google",
|
|
14
|
+
"google_genai",
|
|
15
|
+
"google_vertexai",
|
|
16
|
+
"vertexai",
|
|
17
|
+
"bedrock",
|
|
18
|
+
"amazon_bedrock",
|
|
19
|
+
"cohere",
|
|
20
|
+
"mistral",
|
|
21
|
+
"mistralai",
|
|
22
|
+
"groq",
|
|
23
|
+
"fireworks",
|
|
24
|
+
"together",
|
|
25
|
+
"ollama",
|
|
26
|
+
"huggingface",
|
|
27
|
+
"huggingface_hub",
|
|
28
|
+
"deepseek",
|
|
29
|
+
"xai",
|
|
30
|
+
"perplexity",
|
|
31
|
+
];
|
|
32
|
+
const CLASS_PROVIDER_HINTS = [
|
|
33
|
+
[/openai/i, "openai"],
|
|
34
|
+
[/anthropic|claude/i, "anthropic"],
|
|
35
|
+
[/azure/i, "azure"],
|
|
36
|
+
[/vertex/i, "google"],
|
|
37
|
+
[/google|gemini/i, "google"],
|
|
38
|
+
[/bedrock|amazon/i, "bedrock"],
|
|
39
|
+
[/cohere/i, "cohere"],
|
|
40
|
+
[/mistral/i, "mistral"],
|
|
41
|
+
[/groq/i, "groq"],
|
|
42
|
+
[/fireworks/i, "fireworks"],
|
|
43
|
+
[/together/i, "together"],
|
|
44
|
+
[/ollama/i, "ollama"],
|
|
45
|
+
[/hugging ?face|hf\b/i, "huggingface"],
|
|
46
|
+
[/deepseek/i, "deepseek"],
|
|
47
|
+
[/xai|grok/i, "xai"],
|
|
48
|
+
[/perplexity/i, "perplexity"],
|
|
49
|
+
];
|
|
8
50
|
function serializedName(serialized, fallback) {
|
|
9
51
|
if (typeof serialized?.name === "string" && serialized.name) {
|
|
10
52
|
return serialized.name;
|
|
@@ -15,17 +57,281 @@ function serializedName(serialized, fallback) {
|
|
|
15
57
|
}
|
|
16
58
|
return fallback;
|
|
17
59
|
}
|
|
18
|
-
function modelName(serialized) {
|
|
60
|
+
function modelName(serialized, extraParams) {
|
|
19
61
|
const kwargs = serialized?.kwargs;
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
62
|
+
const sources = [kwargs, serialized, extraParams];
|
|
63
|
+
for (const source of sources) {
|
|
64
|
+
if (!source)
|
|
65
|
+
continue;
|
|
66
|
+
for (const key of [
|
|
67
|
+
"model",
|
|
68
|
+
"modelName",
|
|
69
|
+
"model_name",
|
|
70
|
+
"model_id",
|
|
71
|
+
"modelId",
|
|
72
|
+
]) {
|
|
73
|
+
const value = source[key];
|
|
74
|
+
if (typeof value === "string" && value)
|
|
75
|
+
return value;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
function lookupString(sources, keys) {
|
|
81
|
+
for (const source of sources) {
|
|
82
|
+
if (!source)
|
|
83
|
+
continue;
|
|
84
|
+
for (const key of keys) {
|
|
85
|
+
const value = source[key];
|
|
86
|
+
if (typeof value === "string" && value)
|
|
87
|
+
return value;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
function tagValue(tags, keys) {
|
|
93
|
+
if (!tags?.length)
|
|
94
|
+
return undefined;
|
|
95
|
+
for (const key of keys) {
|
|
96
|
+
const prefix = `${key}:`;
|
|
97
|
+
for (const tag of tags) {
|
|
98
|
+
if (typeof tag !== "string")
|
|
99
|
+
continue;
|
|
100
|
+
if (tag.startsWith(prefix)) {
|
|
101
|
+
const value = tag.slice(prefix.length).trim();
|
|
102
|
+
if (value)
|
|
103
|
+
return value;
|
|
104
|
+
}
|
|
105
|
+
if (tag.startsWith(`${key}=`)) {
|
|
106
|
+
const value = tag.slice(key.length + 1).trim();
|
|
107
|
+
if (value)
|
|
108
|
+
return value;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
function messageContent(message) {
|
|
115
|
+
if (!message || typeof message !== "object")
|
|
116
|
+
return message;
|
|
117
|
+
const record = message;
|
|
118
|
+
if ("content" in record)
|
|
119
|
+
return record.content;
|
|
120
|
+
return message;
|
|
121
|
+
}
|
|
122
|
+
function messageRole(message) {
|
|
123
|
+
if (!message || typeof message !== "object")
|
|
124
|
+
return undefined;
|
|
125
|
+
const record = message;
|
|
126
|
+
if (typeof record.role === "string" && record.role)
|
|
127
|
+
return record.role;
|
|
128
|
+
const type = (typeof record.type === "string" && record.type) ||
|
|
129
|
+
(typeof record._type === "string" && record._type) ||
|
|
130
|
+
(typeof record.getType === "function"
|
|
131
|
+
? record.getType()
|
|
132
|
+
: undefined);
|
|
133
|
+
if (!type) {
|
|
134
|
+
const id = record.id;
|
|
135
|
+
if (Array.isArray(id) && id.length > 0) {
|
|
136
|
+
return roleFromClassName(String(id[id.length - 1]));
|
|
137
|
+
}
|
|
138
|
+
const name = typeof record.name === "string"
|
|
139
|
+
? record.name
|
|
140
|
+
: typeof record.constructor === "function" &&
|
|
141
|
+
typeof record.constructor.name === "string"
|
|
142
|
+
? record.constructor.name
|
|
143
|
+
: undefined;
|
|
144
|
+
return name ? roleFromClassName(name) : undefined;
|
|
145
|
+
}
|
|
146
|
+
switch (type) {
|
|
147
|
+
case "human":
|
|
148
|
+
case "user":
|
|
149
|
+
return "user";
|
|
150
|
+
case "ai":
|
|
151
|
+
case "assistant":
|
|
152
|
+
return "assistant";
|
|
153
|
+
case "system":
|
|
154
|
+
return "system";
|
|
155
|
+
case "tool":
|
|
156
|
+
return "tool";
|
|
157
|
+
case "function":
|
|
158
|
+
return "function";
|
|
159
|
+
case "developer":
|
|
160
|
+
return "developer";
|
|
161
|
+
default:
|
|
162
|
+
return roleFromClassName(type) ?? type;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
function roleFromClassName(name) {
|
|
166
|
+
const lower = name.toLowerCase();
|
|
167
|
+
if (lower.includes("human") || lower === "user")
|
|
168
|
+
return "user";
|
|
169
|
+
if (lower.includes("ai") || lower.includes("assistant"))
|
|
170
|
+
return "assistant";
|
|
171
|
+
if (lower.includes("system"))
|
|
172
|
+
return "system";
|
|
173
|
+
if (lower.includes("tool"))
|
|
174
|
+
return "tool";
|
|
175
|
+
if (lower.includes("function"))
|
|
176
|
+
return "function";
|
|
177
|
+
if (lower.includes("chat") && lower.includes("message"))
|
|
178
|
+
return undefined;
|
|
179
|
+
return undefined;
|
|
180
|
+
}
|
|
181
|
+
function toolCallsFromMessage(record) {
|
|
182
|
+
if (Array.isArray(record.tool_calls) && record.tool_calls.length) {
|
|
183
|
+
return record.tool_calls;
|
|
184
|
+
}
|
|
185
|
+
if (Array.isArray(record.toolCalls) && record.toolCalls.length) {
|
|
186
|
+
return record.toolCalls;
|
|
187
|
+
}
|
|
188
|
+
const additional = record.additional_kwargs;
|
|
189
|
+
if (additional && typeof additional === "object") {
|
|
190
|
+
const calls = additional.tool_calls;
|
|
191
|
+
if (Array.isArray(calls) && calls.length)
|
|
192
|
+
return calls;
|
|
193
|
+
}
|
|
194
|
+
const kwargs = record.kwargs;
|
|
195
|
+
if (kwargs && typeof kwargs === "object") {
|
|
196
|
+
const calls = kwargs.tool_calls;
|
|
197
|
+
if (Array.isArray(calls) && calls.length)
|
|
198
|
+
return calls;
|
|
199
|
+
}
|
|
200
|
+
return undefined;
|
|
201
|
+
}
|
|
202
|
+
/** Normalize LangChain message classes / dicts to `{ role, content, ... }`. */
|
|
203
|
+
function normalizeMessage(message) {
|
|
204
|
+
if (typeof message === "string") {
|
|
205
|
+
return { role: "user", content: message };
|
|
206
|
+
}
|
|
207
|
+
if (!message || typeof message !== "object") {
|
|
208
|
+
return { role: "user", content: message };
|
|
209
|
+
}
|
|
210
|
+
const record = message;
|
|
211
|
+
// Serialized LangChain messages often nest fields under `kwargs`.
|
|
212
|
+
const kwargs = record.kwargs && typeof record.kwargs === "object"
|
|
213
|
+
? record.kwargs
|
|
214
|
+
: undefined;
|
|
215
|
+
const content = "content" in record
|
|
216
|
+
? record.content
|
|
217
|
+
: kwargs && "content" in kwargs
|
|
218
|
+
? kwargs.content
|
|
219
|
+
: messageContent(message);
|
|
220
|
+
const role = messageRole(message) ?? messageRole(kwargs) ?? "user";
|
|
221
|
+
const normalized = {
|
|
222
|
+
role,
|
|
223
|
+
content,
|
|
224
|
+
};
|
|
225
|
+
const toolCalls = toolCallsFromMessage(record) ?? (kwargs ? toolCallsFromMessage(kwargs) : undefined);
|
|
226
|
+
if (toolCalls)
|
|
227
|
+
normalized.tool_calls = toolCalls;
|
|
228
|
+
const toolCallId = (typeof record.tool_call_id === "string" && record.tool_call_id) ||
|
|
229
|
+
(typeof record.toolCallId === "string" && record.toolCallId) ||
|
|
230
|
+
(kwargs && typeof kwargs.tool_call_id === "string"
|
|
231
|
+
? kwargs.tool_call_id
|
|
232
|
+
: undefined);
|
|
233
|
+
if (toolCallId)
|
|
234
|
+
normalized.tool_call_id = toolCallId;
|
|
235
|
+
const name = (typeof record.name === "string" && record.name) ||
|
|
236
|
+
(kwargs && typeof kwargs.name === "string" ? kwargs.name : undefined);
|
|
237
|
+
if (name && (role === "tool" || role === "function")) {
|
|
238
|
+
normalized.name = name;
|
|
239
|
+
}
|
|
240
|
+
return normalized;
|
|
241
|
+
}
|
|
242
|
+
function normalizeMessages(messages) {
|
|
243
|
+
return messages.map(normalizeMessage);
|
|
244
|
+
}
|
|
245
|
+
function asMessageList(input) {
|
|
246
|
+
if (Array.isArray(input))
|
|
247
|
+
return input;
|
|
248
|
+
if (input && typeof input === "object") {
|
|
249
|
+
const record = input;
|
|
250
|
+
if (Array.isArray(record.messages))
|
|
251
|
+
return record.messages;
|
|
252
|
+
if (Array.isArray(record.input))
|
|
253
|
+
return record.input;
|
|
254
|
+
}
|
|
255
|
+
return undefined;
|
|
256
|
+
}
|
|
257
|
+
/** Prefer the current user turn for the Lemma root input. */
|
|
258
|
+
function rootTraceInput(input) {
|
|
259
|
+
if (typeof input === "string")
|
|
260
|
+
return input;
|
|
261
|
+
const messages = asMessageList(input);
|
|
262
|
+
if (messages && messages.length > 0) {
|
|
263
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
264
|
+
const normalized = normalizeMessage(messages[i]);
|
|
265
|
+
if (normalized.role === "user")
|
|
266
|
+
return normalized.content;
|
|
267
|
+
}
|
|
268
|
+
return normalizeMessage(messages[messages.length - 1]).content;
|
|
269
|
+
}
|
|
270
|
+
if (input && typeof input === "object" && !Array.isArray(input)) {
|
|
271
|
+
const record = input;
|
|
272
|
+
for (const key of [
|
|
273
|
+
"input",
|
|
274
|
+
"question",
|
|
275
|
+
"query",
|
|
276
|
+
"prompt",
|
|
277
|
+
"text",
|
|
278
|
+
"user_input",
|
|
279
|
+
"userInput",
|
|
280
|
+
]) {
|
|
281
|
+
const value = record[key];
|
|
282
|
+
if (typeof value === "string" && value)
|
|
283
|
+
return value;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
return input;
|
|
287
|
+
}
|
|
288
|
+
function rootTraceOutput(output) {
|
|
289
|
+
if (output == null)
|
|
290
|
+
return output;
|
|
291
|
+
if (typeof output === "string")
|
|
292
|
+
return output;
|
|
293
|
+
if (output && typeof output === "object" && !Array.isArray(output)) {
|
|
294
|
+
const record = output;
|
|
295
|
+
// Preserve structured assistant payloads (e.g. tool_calls) already normalized.
|
|
296
|
+
if (record.role === "assistant" &&
|
|
297
|
+
(record.tool_calls != null || record.toolCalls != null)) {
|
|
298
|
+
return output;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
const messages = asMessageList(output);
|
|
302
|
+
if (messages && messages.length > 0) {
|
|
303
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
304
|
+
const normalized = normalizeMessage(messages[i]);
|
|
305
|
+
if (normalized.role === "assistant") {
|
|
306
|
+
return structuredAssistantOutput(normalized);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return structuredAssistantOutput(normalizeMessage(messages[messages.length - 1]));
|
|
310
|
+
}
|
|
311
|
+
if (output && typeof output === "object" && !Array.isArray(output)) {
|
|
312
|
+
const record = output;
|
|
313
|
+
for (const key of ["output", "answer", "result", "text", "content"]) {
|
|
314
|
+
const value = record[key];
|
|
315
|
+
if (typeof value === "string" && value)
|
|
316
|
+
return value;
|
|
317
|
+
if (value && typeof value === "object") {
|
|
318
|
+
const nested = value;
|
|
319
|
+
if (typeof nested.content === "string")
|
|
320
|
+
return nested.content;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
return output;
|
|
325
|
+
}
|
|
326
|
+
function structuredAssistantOutput(message) {
|
|
327
|
+
if (message.tool_calls) {
|
|
328
|
+
return {
|
|
329
|
+
role: "assistant",
|
|
330
|
+
content: message.content,
|
|
331
|
+
tool_calls: message.tool_calls,
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
return message.content;
|
|
29
335
|
}
|
|
30
336
|
function firstText(value) {
|
|
31
337
|
if (typeof value === "string")
|
|
@@ -43,21 +349,178 @@ function firstText(value) {
|
|
|
43
349
|
}
|
|
44
350
|
return undefined;
|
|
45
351
|
}
|
|
46
|
-
function
|
|
352
|
+
function generationMessage(item) {
|
|
353
|
+
if (!item || typeof item !== "object")
|
|
354
|
+
return undefined;
|
|
355
|
+
const record = item;
|
|
356
|
+
if (record.message != null)
|
|
357
|
+
return record.message;
|
|
358
|
+
// Prefer real chat messages; plain `{ text }` generation items are handled
|
|
359
|
+
// via firstText so we don't re-wrap them as role/content objects incorrectly.
|
|
360
|
+
if (typeof record.role === "string" ||
|
|
361
|
+
typeof record.type === "string" ||
|
|
362
|
+
typeof record._type === "string") {
|
|
363
|
+
return record;
|
|
364
|
+
}
|
|
365
|
+
if ("content" in record && typeof record.text !== "string")
|
|
366
|
+
return record;
|
|
367
|
+
return undefined;
|
|
368
|
+
}
|
|
369
|
+
function llmStructuredOutput(result) {
|
|
47
370
|
const generations = result.generations;
|
|
48
371
|
if (!Array.isArray(generations))
|
|
49
372
|
return result;
|
|
373
|
+
const messages = [];
|
|
374
|
+
for (const group of generations) {
|
|
375
|
+
if (!Array.isArray(group))
|
|
376
|
+
continue;
|
|
377
|
+
for (const item of group) {
|
|
378
|
+
const message = generationMessage(item);
|
|
379
|
+
if (message != null) {
|
|
380
|
+
messages.push(normalizeMessage(message));
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
const text = firstText(item);
|
|
384
|
+
if (text != null) {
|
|
385
|
+
messages.push({ role: "assistant", content: text });
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
if (messages.length === 1) {
|
|
390
|
+
return structuredAssistantOutput(messages[0]);
|
|
391
|
+
}
|
|
392
|
+
if (messages.length > 1)
|
|
393
|
+
return messages;
|
|
50
394
|
const text = generations.flat().map(firstText).filter(Boolean).join("");
|
|
51
395
|
return text || generations;
|
|
52
396
|
}
|
|
397
|
+
function llmOutputMessages(result) {
|
|
398
|
+
const generations = result.generations;
|
|
399
|
+
if (!Array.isArray(generations))
|
|
400
|
+
return undefined;
|
|
401
|
+
const messages = [];
|
|
402
|
+
for (const group of generations) {
|
|
403
|
+
if (!Array.isArray(group))
|
|
404
|
+
continue;
|
|
405
|
+
for (const item of group) {
|
|
406
|
+
const message = generationMessage(item);
|
|
407
|
+
if (message != null) {
|
|
408
|
+
messages.push(normalizeMessage(message));
|
|
409
|
+
continue;
|
|
410
|
+
}
|
|
411
|
+
const text = firstText(item);
|
|
412
|
+
if (text != null)
|
|
413
|
+
messages.push({ role: "assistant", content: text });
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
return messages.length ? messages : undefined;
|
|
417
|
+
}
|
|
418
|
+
function hasToolCalls(output) {
|
|
419
|
+
if (!output || typeof output !== "object")
|
|
420
|
+
return false;
|
|
421
|
+
if (Array.isArray(output))
|
|
422
|
+
return output.some((item) => hasToolCalls(item));
|
|
423
|
+
const record = output;
|
|
424
|
+
if (Array.isArray(record.tool_calls) && record.tool_calls.length > 0) {
|
|
425
|
+
return true;
|
|
426
|
+
}
|
|
427
|
+
if (Array.isArray(record.toolCalls) && record.toolCalls.length > 0) {
|
|
428
|
+
return true;
|
|
429
|
+
}
|
|
430
|
+
return false;
|
|
431
|
+
}
|
|
432
|
+
function providerFromId(id) {
|
|
433
|
+
if (!Array.isArray(id))
|
|
434
|
+
return undefined;
|
|
435
|
+
for (const part of id) {
|
|
436
|
+
if (typeof part !== "string")
|
|
437
|
+
continue;
|
|
438
|
+
const lower = part.toLowerCase().replace(/-/g, "_");
|
|
439
|
+
for (const provider of KNOWN_PROVIDERS) {
|
|
440
|
+
if (lower === provider || lower.includes(provider)) {
|
|
441
|
+
if (provider === "azure_openai")
|
|
442
|
+
return "azure";
|
|
443
|
+
if (provider === "google_genai" || provider === "google_vertexai") {
|
|
444
|
+
return "google";
|
|
445
|
+
}
|
|
446
|
+
if (provider === "amazon_bedrock")
|
|
447
|
+
return "bedrock";
|
|
448
|
+
if (provider === "mistralai")
|
|
449
|
+
return "mistral";
|
|
450
|
+
if (provider === "huggingface_hub")
|
|
451
|
+
return "huggingface";
|
|
452
|
+
return provider;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
// langchain_openai / langchain_anthropic package ids
|
|
456
|
+
const pkg = lower.match(/^langchain[_]?([a-z0-9]+)/);
|
|
457
|
+
if (pkg?.[1] && pkg[1] !== "core" && pkg[1] !== "community") {
|
|
458
|
+
return providerFromClassName(pkg[1]) ?? pkg[1];
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
return undefined;
|
|
462
|
+
}
|
|
463
|
+
function providerFromClassName(name) {
|
|
464
|
+
for (const [pattern, provider] of CLASS_PROVIDER_HINTS) {
|
|
465
|
+
if (pattern.test(name))
|
|
466
|
+
return provider;
|
|
467
|
+
}
|
|
468
|
+
return undefined;
|
|
469
|
+
}
|
|
470
|
+
function llmProvider(serialized, extraParams) {
|
|
471
|
+
const kwargs = serialized?.kwargs;
|
|
472
|
+
const sources = [kwargs, serialized, extraParams];
|
|
473
|
+
for (const source of sources) {
|
|
474
|
+
if (!source)
|
|
475
|
+
continue;
|
|
476
|
+
for (const key of [
|
|
477
|
+
"provider",
|
|
478
|
+
"ls_provider",
|
|
479
|
+
"llm_provider",
|
|
480
|
+
"llmProvider",
|
|
481
|
+
]) {
|
|
482
|
+
const value = source[key];
|
|
483
|
+
if (typeof value === "string" && value && value !== "langchain") {
|
|
484
|
+
return value;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
const fromId = providerFromId(serialized?.id);
|
|
489
|
+
if (fromId)
|
|
490
|
+
return fromId;
|
|
491
|
+
const className = serializedName(serialized, "");
|
|
492
|
+
const fromClass = className ? providerFromClassName(className) : undefined;
|
|
493
|
+
if (fromClass)
|
|
494
|
+
return fromClass;
|
|
495
|
+
const type = (typeof extraParams?._type === "string" && extraParams._type) ||
|
|
496
|
+
(typeof kwargs?._type === "string" && kwargs._type);
|
|
497
|
+
if (type) {
|
|
498
|
+
const fromType = providerFromClassName(type);
|
|
499
|
+
if (fromType)
|
|
500
|
+
return fromType;
|
|
501
|
+
}
|
|
502
|
+
return undefined;
|
|
503
|
+
}
|
|
53
504
|
function errorMessage(error) {
|
|
54
505
|
return error instanceof Error ? error.message : String(error);
|
|
55
506
|
}
|
|
507
|
+
function durationMs(start, end) {
|
|
508
|
+
return Math.max(0, end.getTime() - start.getTime());
|
|
509
|
+
}
|
|
510
|
+
function langchainAttributes(runId, parentRunId, runType) {
|
|
511
|
+
return Object.fromEntries(Object.entries({
|
|
512
|
+
"langchain.run_id": runId,
|
|
513
|
+
"langchain.parent_run_id": parentRunId,
|
|
514
|
+
"langchain.run_type": runType,
|
|
515
|
+
}).filter(([, value]) => value !== undefined && value !== null));
|
|
516
|
+
}
|
|
56
517
|
class LemmaLangChainCallbackHandler {
|
|
57
518
|
options;
|
|
58
519
|
name = "lemma";
|
|
59
520
|
lemma;
|
|
60
521
|
runs = new Map();
|
|
522
|
+
traces = new Map();
|
|
523
|
+
pending = new Set();
|
|
61
524
|
constructor(options = {}) {
|
|
62
525
|
this.options = options;
|
|
63
526
|
this.lemma = options.lemma;
|
|
@@ -71,205 +534,618 @@ class LemmaLangChainCallbackHandler {
|
|
|
71
534
|
});
|
|
72
535
|
return this.lemma;
|
|
73
536
|
}
|
|
74
|
-
|
|
75
|
-
return this.options.
|
|
537
|
+
recordInputs() {
|
|
538
|
+
return this.options.recordInputs !== false;
|
|
539
|
+
}
|
|
540
|
+
recordOutputs() {
|
|
541
|
+
return this.options.recordOutputs !== false;
|
|
542
|
+
}
|
|
543
|
+
resolveThreadId(metadata, tags) {
|
|
544
|
+
const key = this.options.threadIdKey ?? "threadId";
|
|
545
|
+
const keys = [key, "threadId", "thread_id", "conversation_id", "session_id"];
|
|
546
|
+
return (lookupString([metadata, this.options.metadata], keys) ??
|
|
547
|
+
tagValue(tags, keys));
|
|
548
|
+
}
|
|
549
|
+
resolveUserId(metadata, tags) {
|
|
550
|
+
if (this.options.userIdKey) {
|
|
551
|
+
return (lookupString([metadata, this.options.metadata], [this.options.userIdKey]) ?? tagValue(tags, [this.options.userIdKey]));
|
|
552
|
+
}
|
|
553
|
+
const keys = ["userId", "user_id", "resourceId"];
|
|
554
|
+
return (lookupString([metadata, this.options.metadata], keys) ??
|
|
555
|
+
tagValue(tags, keys));
|
|
556
|
+
}
|
|
557
|
+
applyIdentity(stored, metadata, tags) {
|
|
558
|
+
const threadId = this.resolveThreadId(metadata, tags);
|
|
559
|
+
const userId = this.resolveUserId(metadata, tags);
|
|
560
|
+
if (threadId)
|
|
561
|
+
stored.handle.threadId(threadId);
|
|
562
|
+
if (userId)
|
|
563
|
+
stored.handle.userId(userId);
|
|
76
564
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
565
|
+
noteBounds(stored, start, end) {
|
|
566
|
+
if (start) {
|
|
567
|
+
stored.earliestStart =
|
|
568
|
+
!stored.earliestStart || start < stored.earliestStart
|
|
569
|
+
? start
|
|
570
|
+
: stored.earliestStart;
|
|
571
|
+
}
|
|
572
|
+
if (end) {
|
|
573
|
+
stored.latestEnd =
|
|
574
|
+
!stored.latestEnd || end > stored.latestEnd ? end : stored.latestEnd;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
noteRootInput(stored, input) {
|
|
578
|
+
if (!this.recordInputs() || input == null || stored.hasRootInput)
|
|
579
|
+
return;
|
|
580
|
+
stored.rootInput = rootTraceInput(input);
|
|
581
|
+
stored.hasRootInput = true;
|
|
582
|
+
stored.handle.input(stored.rootInput);
|
|
583
|
+
}
|
|
584
|
+
noteRootOutput(stored, output) {
|
|
585
|
+
if (!this.recordOutputs() || output == null || stored.rootError)
|
|
586
|
+
return;
|
|
587
|
+
stored.rootOutput = rootTraceOutput(output);
|
|
588
|
+
}
|
|
589
|
+
noteRootError(stored, error) {
|
|
590
|
+
if (!error || stored.rootError)
|
|
591
|
+
return;
|
|
592
|
+
stored.rootError = error;
|
|
593
|
+
}
|
|
594
|
+
trackPending(promise) {
|
|
595
|
+
this.pending.add(promise);
|
|
596
|
+
void promise.finally(() => this.pending.delete(promise));
|
|
597
|
+
}
|
|
598
|
+
createOwnedTrace(runId, name, input, kind, metadata, tags) {
|
|
599
|
+
const startedAt = new Date();
|
|
600
|
+
const handle = this.getLemma().trace({
|
|
601
|
+
name,
|
|
602
|
+
input: this.recordInputs() ? rootTraceInput(input) : undefined,
|
|
81
603
|
metadata: {
|
|
82
604
|
...this.options.metadata,
|
|
83
605
|
...(metadata ?? {}),
|
|
84
606
|
langchainRunId: runId,
|
|
85
607
|
},
|
|
608
|
+
threadId: this.resolveThreadId(metadata, tags),
|
|
609
|
+
userId: this.resolveUserId(metadata, tags),
|
|
610
|
+
startedAt,
|
|
86
611
|
});
|
|
87
|
-
|
|
88
|
-
|
|
612
|
+
const stored = {
|
|
613
|
+
handle,
|
|
614
|
+
ended: false,
|
|
615
|
+
openedAt: startedAt,
|
|
616
|
+
earliestStart: startedAt,
|
|
617
|
+
hasRootInput: this.recordInputs() && input != null,
|
|
618
|
+
rootInput: this.recordInputs() ? rootTraceInput(input) : undefined,
|
|
619
|
+
};
|
|
620
|
+
this.traces.set(runId, stored);
|
|
621
|
+
const run = {
|
|
622
|
+
owningTraceId: runId,
|
|
623
|
+
rootRunId: runId,
|
|
624
|
+
kind,
|
|
625
|
+
startedAt,
|
|
626
|
+
ownsTrace: true,
|
|
627
|
+
};
|
|
628
|
+
this.runs.set(runId, run);
|
|
629
|
+
return { stored, run };
|
|
630
|
+
}
|
|
631
|
+
storedTrace(owningTraceId) {
|
|
632
|
+
return this.traces.get(owningTraceId);
|
|
89
633
|
}
|
|
90
|
-
|
|
91
|
-
if (!
|
|
634
|
+
parentRun(parentRunId) {
|
|
635
|
+
if (!parentRunId)
|
|
92
636
|
return undefined;
|
|
93
|
-
return this.runs.get(
|
|
637
|
+
return this.runs.get(parentRunId);
|
|
638
|
+
}
|
|
639
|
+
/**
|
|
640
|
+
* Resolve the parent attachment target.
|
|
641
|
+
* - Known parent → attach under that parent's owning trace.
|
|
642
|
+
* - Missing / unknown parent → create a NEW owned trace for this run
|
|
643
|
+
* (never overwrite another concurrent trace's state).
|
|
644
|
+
*/
|
|
645
|
+
resolveAttachment(runId, parentRunId, createRoot) {
|
|
646
|
+
const parent = this.parentRun(parentRunId);
|
|
647
|
+
if (!parent) {
|
|
648
|
+
const created = createRoot();
|
|
649
|
+
return {
|
|
650
|
+
stored: created.stored,
|
|
651
|
+
parentId: undefined,
|
|
652
|
+
ownsTrace: true,
|
|
653
|
+
owningTraceId: runId,
|
|
654
|
+
rootRunId: runId,
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
const stored = this.storedTrace(parent.owningTraceId);
|
|
658
|
+
if (!stored || stored.ended) {
|
|
659
|
+
// Parent bookkeeping is gone — start a fresh owned trace rather than
|
|
660
|
+
// leaking into / stealing another concurrent run's state.
|
|
661
|
+
const created = createRoot();
|
|
662
|
+
return {
|
|
663
|
+
stored: created.stored,
|
|
664
|
+
parentId: undefined,
|
|
665
|
+
ownsTrace: true,
|
|
666
|
+
owningTraceId: runId,
|
|
667
|
+
rootRunId: runId,
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
// Orphan-safe: only nest under a still-open parent handle; otherwise attach
|
|
671
|
+
// at the root of the same owned trace (parentId undefined).
|
|
672
|
+
return {
|
|
673
|
+
stored,
|
|
674
|
+
parentId: parent.handle?.id,
|
|
675
|
+
ownsTrace: false,
|
|
676
|
+
owningTraceId: parent.owningTraceId,
|
|
677
|
+
rootRunId: parent.rootRunId,
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
forgetTraceRuns(owningTraceId) {
|
|
681
|
+
for (const [runId, run] of this.runs) {
|
|
682
|
+
if (run.owningTraceId === owningTraceId)
|
|
683
|
+
this.runs.delete(runId);
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
async finalizeTrace(owningTraceId, stored) {
|
|
687
|
+
this.traces.delete(owningTraceId);
|
|
688
|
+
this.forgetTraceRuns(owningTraceId);
|
|
689
|
+
if (stored.ended)
|
|
690
|
+
return;
|
|
691
|
+
stored.ended = true;
|
|
692
|
+
const endedAt = stored.latestEnd ?? new Date();
|
|
693
|
+
const startedAt = stored.earliestStart ?? stored.openedAt ?? endedAt;
|
|
694
|
+
const timing = {
|
|
695
|
+
startedAt,
|
|
696
|
+
endedAt,
|
|
697
|
+
durationMs: durationMs(startedAt, endedAt),
|
|
698
|
+
};
|
|
699
|
+
if (stored.rootError) {
|
|
700
|
+
stored.handle.fail(this.recordOutputs() ? stored.rootError : "error");
|
|
701
|
+
const promise = stored.handle.end(timing);
|
|
702
|
+
this.trackPending(promise);
|
|
703
|
+
await promise;
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
if (!this.recordOutputs() || stored.rootOutput === undefined) {
|
|
707
|
+
const promise = stored.handle.end(timing);
|
|
708
|
+
this.trackPending(promise);
|
|
709
|
+
await promise;
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
const promise = stored.handle.end({
|
|
713
|
+
output: stored.rootOutput,
|
|
714
|
+
...timing,
|
|
715
|
+
});
|
|
716
|
+
this.trackPending(promise);
|
|
717
|
+
await promise;
|
|
718
|
+
}
|
|
719
|
+
maybeFinalizeOwner(run, endedAt) {
|
|
720
|
+
if (!run.ownsTrace)
|
|
721
|
+
return;
|
|
722
|
+
const stored = this.traces.get(run.owningTraceId);
|
|
723
|
+
if (!stored)
|
|
724
|
+
return;
|
|
725
|
+
this.noteBounds(stored, run.startedAt, endedAt);
|
|
726
|
+
const promise = this.finalizeTrace(run.owningTraceId, stored);
|
|
727
|
+
this.trackPending(promise);
|
|
728
|
+
return promise;
|
|
729
|
+
}
|
|
730
|
+
traceName(serialized, fallback) {
|
|
731
|
+
return this.options.agentName ?? serializedName(serialized, fallback);
|
|
94
732
|
}
|
|
95
|
-
handleChainStart(serialized, inputs, runId, parentRunId,
|
|
96
|
-
const
|
|
733
|
+
handleChainStart(serialized, inputs, runId, parentRunId, tags, metadata, runType, name) {
|
|
734
|
+
const startedAt = new Date();
|
|
735
|
+
const chainName = name ?? serializedName(serialized, "langchain-chain");
|
|
736
|
+
const parent = this.parentRun(parentRunId);
|
|
97
737
|
if (!parent) {
|
|
98
|
-
this.
|
|
738
|
+
this.createOwnedTrace(runId, this.traceName({ ...serialized, name: name ?? serialized?.name }, "langchain-run"), inputs, "chain", metadata, tags);
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
741
|
+
const stored = this.storedTrace(parent.owningTraceId);
|
|
742
|
+
if (!stored || stored.ended) {
|
|
743
|
+
this.createOwnedTrace(runId, this.traceName({ ...serialized, name: name ?? serialized?.name }, "langchain-run"), inputs, "chain", metadata, tags);
|
|
99
744
|
return;
|
|
100
745
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
746
|
+
this.applyIdentity(stored, metadata, tags);
|
|
747
|
+
this.noteBounds(stored, startedAt, undefined);
|
|
748
|
+
// Nested chains (incl. LangGraph nodes) become child spans; do not steal
|
|
749
|
+
// root input from intermediate node state after the root already set it.
|
|
750
|
+
const handle = stored.handle.startSpan({
|
|
751
|
+
name: chainName,
|
|
752
|
+
parentId: parent.handle?.id,
|
|
753
|
+
input: this.recordInputs() ? inputs : undefined,
|
|
104
754
|
metadata: this.options.metadata,
|
|
105
|
-
attributes:
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
755
|
+
attributes: langchainAttributes(runId, parentRunId, runType || "chain"),
|
|
756
|
+
startedAt,
|
|
757
|
+
});
|
|
758
|
+
this.runs.set(runId, {
|
|
759
|
+
owningTraceId: parent.owningTraceId,
|
|
760
|
+
rootRunId: parent.rootRunId,
|
|
761
|
+
handle,
|
|
762
|
+
kind: "chain",
|
|
763
|
+
startedAt,
|
|
764
|
+
parentRunId,
|
|
765
|
+
ownsTrace: false,
|
|
110
766
|
});
|
|
111
|
-
this.runs.set(runId, { handle, parentRunId, type: "chain" });
|
|
112
767
|
}
|
|
113
768
|
async handleChainEnd(outputs, runId) {
|
|
114
769
|
const run = this.runs.get(runId);
|
|
115
770
|
if (!run)
|
|
116
771
|
return;
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
772
|
+
const endedAt = new Date();
|
|
773
|
+
const stored = this.storedTrace(run.owningTraceId);
|
|
774
|
+
if (run.handle) {
|
|
775
|
+
run.handle.end({
|
|
776
|
+
output: this.recordOutputs() ? outputs : undefined,
|
|
777
|
+
endedAt,
|
|
778
|
+
durationMs: durationMs(run.startedAt, endedAt),
|
|
779
|
+
});
|
|
780
|
+
}
|
|
781
|
+
if (stored) {
|
|
782
|
+
this.noteBounds(stored, run.startedAt, endedAt);
|
|
783
|
+
if (run.ownsTrace) {
|
|
784
|
+
this.noteRootOutput(stored, outputs);
|
|
785
|
+
}
|
|
121
786
|
}
|
|
122
|
-
run.handle?.end({
|
|
123
|
-
output: this.options.recordOutputs === false ? undefined : outputs,
|
|
124
|
-
});
|
|
125
787
|
this.runs.delete(runId);
|
|
788
|
+
if (run.ownsTrace && stored) {
|
|
789
|
+
await this.finalizeTrace(run.owningTraceId, stored);
|
|
790
|
+
}
|
|
126
791
|
}
|
|
127
792
|
async handleChainError(error, runId) {
|
|
128
793
|
const run = this.runs.get(runId);
|
|
129
794
|
if (!run)
|
|
130
795
|
return;
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
796
|
+
const endedAt = new Date();
|
|
797
|
+
const message = errorMessage(error);
|
|
798
|
+
const stored = this.storedTrace(run.owningTraceId);
|
|
799
|
+
if (run.handle) {
|
|
800
|
+
run.handle.end({
|
|
801
|
+
status: "ERROR",
|
|
802
|
+
error: this.recordOutputs() ? message : undefined,
|
|
803
|
+
endedAt,
|
|
804
|
+
durationMs: durationMs(run.startedAt, endedAt),
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
if (stored) {
|
|
808
|
+
this.noteBounds(stored, run.startedAt, endedAt);
|
|
809
|
+
if (run.ownsTrace) {
|
|
810
|
+
this.noteRootError(stored, message);
|
|
811
|
+
}
|
|
136
812
|
}
|
|
137
|
-
run.handle?.end({ status: "ERROR", error: errorMessage(error) });
|
|
138
813
|
this.runs.delete(runId);
|
|
814
|
+
if (run.ownsTrace && stored) {
|
|
815
|
+
await this.finalizeTrace(run.owningTraceId, stored);
|
|
816
|
+
}
|
|
139
817
|
}
|
|
140
|
-
handleLLMStart(serialized, prompts, runId, parentRunId, extraParams) {
|
|
141
|
-
const
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
818
|
+
handleLLMStart(serialized, prompts, runId, parentRunId, extraParams, tags, metadata) {
|
|
819
|
+
const startedAt = new Date();
|
|
820
|
+
const attachment = this.resolveAttachment(runId, parentRunId, () => this.createOwnedTrace(runId, this.traceName(serialized, "langchain-llm"), prompts, "llm", metadata, tags));
|
|
821
|
+
// createOwnedTrace already registered the run when ownsTrace; update it.
|
|
822
|
+
if (attachment.ownsTrace) {
|
|
823
|
+
this.noteRootInput(attachment.stored, prompts);
|
|
824
|
+
}
|
|
825
|
+
this.applyIdentity(attachment.stored, metadata, tags);
|
|
826
|
+
this.noteBounds(attachment.stored, startedAt, undefined);
|
|
827
|
+
const provider = llmProvider(serialized, extraParams);
|
|
828
|
+
const model = modelName(serialized, extraParams);
|
|
829
|
+
const handle = attachment.stored.handle.startGeneration({
|
|
146
830
|
name: serializedName(serialized, "langchain-llm"),
|
|
147
|
-
|
|
831
|
+
parentId: attachment.parentId,
|
|
832
|
+
input: this.recordInputs() ? prompts : undefined,
|
|
148
833
|
metadata: this.options.metadata,
|
|
149
|
-
model
|
|
150
|
-
llmProvider:
|
|
151
|
-
llmInputMessages: this.
|
|
152
|
-
?
|
|
153
|
-
:
|
|
834
|
+
model,
|
|
835
|
+
llmProvider: provider,
|
|
836
|
+
llmInputMessages: this.recordInputs()
|
|
837
|
+
? prompts.map((content) => ({ role: "user", content }))
|
|
838
|
+
: undefined,
|
|
154
839
|
llmInvocationParameters: extraParams,
|
|
155
|
-
attributes:
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
840
|
+
attributes: langchainAttributes(runId, parentRunId, "llm"),
|
|
841
|
+
startedAt,
|
|
842
|
+
});
|
|
843
|
+
this.runs.set(runId, {
|
|
844
|
+
owningTraceId: attachment.owningTraceId,
|
|
845
|
+
rootRunId: attachment.rootRunId,
|
|
846
|
+
handle,
|
|
847
|
+
kind: "llm",
|
|
848
|
+
startedAt,
|
|
849
|
+
parentRunId,
|
|
850
|
+
ownsTrace: attachment.ownsTrace,
|
|
160
851
|
});
|
|
161
|
-
this.runs.set(runId, { handle, parentRunId, type: "llm" });
|
|
162
852
|
}
|
|
163
|
-
handleChatModelStart(serialized, messages, runId, parentRunId, extraParams) {
|
|
853
|
+
handleChatModelStart(serialized, messages, runId, parentRunId, extraParams, tags, metadata) {
|
|
854
|
+
const startedAt = new Date();
|
|
164
855
|
const flatMessages = messages.flat();
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
856
|
+
const normalized = this.recordInputs()
|
|
857
|
+
? normalizeMessages(flatMessages)
|
|
858
|
+
: undefined;
|
|
859
|
+
const attachment = this.resolveAttachment(runId, parentRunId, () => this.createOwnedTrace(runId, this.traceName(serialized, "langchain-chat-model"), flatMessages, "llm", metadata, tags));
|
|
860
|
+
if (attachment.ownsTrace) {
|
|
861
|
+
this.noteRootInput(attachment.stored, flatMessages);
|
|
862
|
+
}
|
|
863
|
+
this.applyIdentity(attachment.stored, metadata, tags);
|
|
864
|
+
this.noteBounds(attachment.stored, startedAt, undefined);
|
|
865
|
+
const provider = llmProvider(serialized, extraParams);
|
|
866
|
+
const model = modelName(serialized, extraParams);
|
|
867
|
+
const handle = attachment.stored.handle.startGeneration({
|
|
170
868
|
name: serializedName(serialized, "langchain-chat-model"),
|
|
171
|
-
|
|
869
|
+
parentId: attachment.parentId,
|
|
870
|
+
input: this.recordInputs() ? normalized : undefined,
|
|
172
871
|
metadata: this.options.metadata,
|
|
173
|
-
model
|
|
174
|
-
llmProvider:
|
|
175
|
-
llmInputMessages:
|
|
872
|
+
model,
|
|
873
|
+
llmProvider: provider,
|
|
874
|
+
llmInputMessages: normalized,
|
|
176
875
|
llmInvocationParameters: extraParams,
|
|
177
|
-
attributes:
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
876
|
+
attributes: langchainAttributes(runId, parentRunId, "llm"),
|
|
877
|
+
startedAt,
|
|
878
|
+
});
|
|
879
|
+
this.runs.set(runId, {
|
|
880
|
+
owningTraceId: attachment.owningTraceId,
|
|
881
|
+
rootRunId: attachment.rootRunId,
|
|
882
|
+
handle,
|
|
883
|
+
kind: "llm",
|
|
884
|
+
startedAt,
|
|
885
|
+
parentRunId,
|
|
886
|
+
ownsTrace: attachment.ownsTrace,
|
|
182
887
|
});
|
|
183
|
-
this.runs.set(runId, { handle, parentRunId, type: "llm" });
|
|
184
888
|
}
|
|
185
|
-
|
|
889
|
+
deferredOwnerFor(owningTraceId) {
|
|
890
|
+
for (const run of this.runs.values()) {
|
|
891
|
+
if (run.ownsTrace &&
|
|
892
|
+
run.deferFinalize &&
|
|
893
|
+
run.owningTraceId === owningTraceId) {
|
|
894
|
+
return run;
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
return undefined;
|
|
898
|
+
}
|
|
899
|
+
async handleLLMEnd(output, runId) {
|
|
186
900
|
const run = this.runs.get(runId);
|
|
187
901
|
if (!run?.handle)
|
|
188
902
|
return;
|
|
189
|
-
const
|
|
903
|
+
const endedAt = new Date();
|
|
904
|
+
const structured = llmStructuredOutput(output);
|
|
905
|
+
const outputMessages = llmOutputMessages(output);
|
|
906
|
+
const softError = (0, tool_result_1.toolResultError)(structured);
|
|
907
|
+
const awaitingTools = !softError && hasToolCalls(structured);
|
|
190
908
|
run.handle.end({
|
|
191
|
-
output: this.
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
909
|
+
output: this.recordOutputs() && !softError ? structured : undefined,
|
|
910
|
+
error: this.recordOutputs() ? (softError ?? undefined) : undefined,
|
|
911
|
+
status: softError ? "ERROR" : undefined,
|
|
912
|
+
endedAt,
|
|
913
|
+
durationMs: durationMs(run.startedAt, endedAt),
|
|
914
|
+
llmOutputMessages: this.recordOutputs() && !softError ? outputMessages : undefined,
|
|
195
915
|
});
|
|
916
|
+
const stored = this.storedTrace(run.owningTraceId);
|
|
917
|
+
if (stored) {
|
|
918
|
+
this.noteBounds(stored, run.startedAt, endedAt);
|
|
919
|
+
if (run.ownsTrace) {
|
|
920
|
+
if (softError)
|
|
921
|
+
this.noteRootError(stored, softError);
|
|
922
|
+
else
|
|
923
|
+
this.noteRootOutput(stored, structured);
|
|
924
|
+
}
|
|
925
|
+
else if (!softError) {
|
|
926
|
+
// Prefer chain-level root output; refresh from later generations.
|
|
927
|
+
this.noteRootOutput(stored, structured);
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
if (awaitingTools) {
|
|
931
|
+
// Keep the run stub so tool/follow-up generation callbacks can nest
|
|
932
|
+
// under this generation (whether or not this run owns the root trace).
|
|
933
|
+
if (run.ownsTrace)
|
|
934
|
+
run.deferFinalize = true;
|
|
935
|
+
return;
|
|
936
|
+
}
|
|
196
937
|
this.runs.delete(runId);
|
|
938
|
+
if (run.ownsTrace) {
|
|
939
|
+
await this.maybeFinalizeOwner(run, endedAt);
|
|
940
|
+
return;
|
|
941
|
+
}
|
|
942
|
+
// Final answer generation under a deferred owned LLM — close that root.
|
|
943
|
+
const deferred = this.deferredOwnerFor(run.owningTraceId);
|
|
944
|
+
if (deferred) {
|
|
945
|
+
this.runs.delete(deferred.rootRunId);
|
|
946
|
+
await this.maybeFinalizeOwner(deferred, endedAt);
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
async finalizeDeferredOwner(owningTraceId, endedAt, rootError) {
|
|
950
|
+
const deferred = this.deferredOwnerFor(owningTraceId);
|
|
951
|
+
if (!deferred)
|
|
952
|
+
return;
|
|
953
|
+
const stored = this.storedTrace(deferred.owningTraceId);
|
|
954
|
+
if (stored && rootError)
|
|
955
|
+
this.noteRootError(stored, rootError);
|
|
956
|
+
this.runs.delete(deferred.rootRunId);
|
|
957
|
+
await this.maybeFinalizeOwner(deferred, endedAt);
|
|
197
958
|
}
|
|
198
|
-
handleLLMError(error, runId) {
|
|
959
|
+
async handleLLMError(error, runId) {
|
|
199
960
|
const run = this.runs.get(runId);
|
|
200
|
-
|
|
961
|
+
if (!run)
|
|
962
|
+
return;
|
|
963
|
+
const endedAt = new Date();
|
|
964
|
+
const message = errorMessage(error);
|
|
965
|
+
run.handle?.end({
|
|
966
|
+
status: "ERROR",
|
|
967
|
+
error: this.recordOutputs() ? message : undefined,
|
|
968
|
+
endedAt,
|
|
969
|
+
durationMs: durationMs(run.startedAt, endedAt),
|
|
970
|
+
});
|
|
971
|
+
const stored = this.storedTrace(run.owningTraceId);
|
|
972
|
+
if (stored) {
|
|
973
|
+
this.noteBounds(stored, run.startedAt, endedAt);
|
|
974
|
+
if (run.ownsTrace)
|
|
975
|
+
this.noteRootError(stored, message);
|
|
976
|
+
}
|
|
201
977
|
this.runs.delete(runId);
|
|
978
|
+
if (run.ownsTrace) {
|
|
979
|
+
await this.maybeFinalizeOwner(run, endedAt);
|
|
980
|
+
return;
|
|
981
|
+
}
|
|
982
|
+
await this.finalizeDeferredOwner(run.owningTraceId, endedAt, message);
|
|
202
983
|
}
|
|
203
|
-
handleToolStart(serialized, input, runId, parentRunId) {
|
|
204
|
-
const
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
this.
|
|
984
|
+
handleToolStart(serialized, input, runId, parentRunId, tags, metadata) {
|
|
985
|
+
const startedAt = new Date();
|
|
986
|
+
const attachment = this.resolveAttachment(runId, parentRunId, () => this.createOwnedTrace(runId, this.traceName(serialized, "langchain-tool"), input, "tool", metadata, tags));
|
|
987
|
+
if (attachment.ownsTrace) {
|
|
988
|
+
this.noteRootInput(attachment.stored, input);
|
|
989
|
+
}
|
|
990
|
+
this.applyIdentity(attachment.stored, metadata, tags);
|
|
991
|
+
this.noteBounds(attachment.stored, startedAt, undefined);
|
|
208
992
|
const name = serializedName(serialized, "langchain-tool");
|
|
209
|
-
const handle =
|
|
993
|
+
const handle = attachment.stored.handle.startTool({
|
|
210
994
|
name,
|
|
995
|
+
parentId: attachment.parentId,
|
|
211
996
|
toolName: name,
|
|
212
|
-
input: this.
|
|
997
|
+
input: this.recordInputs() ? input : undefined,
|
|
213
998
|
metadata: this.options.metadata,
|
|
214
|
-
attributes:
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
999
|
+
attributes: langchainAttributes(runId, parentRunId, "tool"),
|
|
1000
|
+
startedAt,
|
|
1001
|
+
});
|
|
1002
|
+
this.runs.set(runId, {
|
|
1003
|
+
owningTraceId: attachment.owningTraceId,
|
|
1004
|
+
rootRunId: attachment.rootRunId,
|
|
1005
|
+
handle,
|
|
1006
|
+
kind: "tool",
|
|
1007
|
+
startedAt,
|
|
1008
|
+
parentRunId,
|
|
1009
|
+
ownsTrace: attachment.ownsTrace,
|
|
219
1010
|
});
|
|
220
|
-
this.runs.set(runId, { handle, parentRunId, type: "tool" });
|
|
221
1011
|
}
|
|
222
|
-
handleToolEnd(output, runId) {
|
|
1012
|
+
async handleToolEnd(output, runId) {
|
|
223
1013
|
const run = this.runs.get(runId);
|
|
224
1014
|
if (!run)
|
|
225
1015
|
return;
|
|
1016
|
+
const endedAt = new Date();
|
|
226
1017
|
const softError = (0, tool_result_1.toolResultError)(output);
|
|
227
1018
|
if (softError) {
|
|
228
1019
|
run.handle?.end({
|
|
229
1020
|
status: "ERROR",
|
|
230
|
-
error: this.
|
|
1021
|
+
error: this.recordOutputs() ? softError : undefined,
|
|
1022
|
+
endedAt,
|
|
1023
|
+
durationMs: durationMs(run.startedAt, endedAt),
|
|
231
1024
|
});
|
|
232
|
-
this.runs.delete(runId);
|
|
233
|
-
return;
|
|
234
1025
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
1026
|
+
else {
|
|
1027
|
+
run.handle?.end({
|
|
1028
|
+
output: this.recordOutputs() ? output : undefined,
|
|
1029
|
+
endedAt,
|
|
1030
|
+
durationMs: durationMs(run.startedAt, endedAt),
|
|
1031
|
+
});
|
|
1032
|
+
}
|
|
1033
|
+
const stored = this.storedTrace(run.owningTraceId);
|
|
1034
|
+
if (stored) {
|
|
1035
|
+
this.noteBounds(stored, run.startedAt, endedAt);
|
|
1036
|
+
if (run.ownsTrace) {
|
|
1037
|
+
if (softError)
|
|
1038
|
+
this.noteRootError(stored, softError);
|
|
1039
|
+
else
|
|
1040
|
+
this.noteRootOutput(stored, output);
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
238
1043
|
this.runs.delete(runId);
|
|
1044
|
+
await this.maybeFinalizeOwner(run, endedAt);
|
|
239
1045
|
}
|
|
240
|
-
handleToolError(error, runId) {
|
|
1046
|
+
async handleToolError(error, runId) {
|
|
241
1047
|
const run = this.runs.get(runId);
|
|
242
|
-
|
|
1048
|
+
if (!run)
|
|
1049
|
+
return;
|
|
1050
|
+
const endedAt = new Date();
|
|
1051
|
+
const message = errorMessage(error);
|
|
1052
|
+
run.handle?.end({
|
|
1053
|
+
status: "ERROR",
|
|
1054
|
+
error: this.recordOutputs() ? message : undefined,
|
|
1055
|
+
endedAt,
|
|
1056
|
+
durationMs: durationMs(run.startedAt, endedAt),
|
|
1057
|
+
});
|
|
1058
|
+
const stored = this.storedTrace(run.owningTraceId);
|
|
1059
|
+
if (stored) {
|
|
1060
|
+
this.noteBounds(stored, run.startedAt, endedAt);
|
|
1061
|
+
if (run.ownsTrace)
|
|
1062
|
+
this.noteRootError(stored, message);
|
|
1063
|
+
}
|
|
243
1064
|
this.runs.delete(runId);
|
|
1065
|
+
if (run.ownsTrace) {
|
|
1066
|
+
await this.maybeFinalizeOwner(run, endedAt);
|
|
1067
|
+
return;
|
|
1068
|
+
}
|
|
1069
|
+
await this.finalizeDeferredOwner(run.owningTraceId, endedAt, message);
|
|
244
1070
|
}
|
|
245
|
-
handleRetrieverStart(serialized, query, runId, parentRunId) {
|
|
246
|
-
const
|
|
247
|
-
const
|
|
248
|
-
|
|
249
|
-
this.
|
|
250
|
-
|
|
1071
|
+
handleRetrieverStart(serialized, query, runId, parentRunId, tags, metadata) {
|
|
1072
|
+
const startedAt = new Date();
|
|
1073
|
+
const attachment = this.resolveAttachment(runId, parentRunId, () => this.createOwnedTrace(runId, this.traceName(serialized, "langchain-retriever"), query, "retriever", metadata, tags));
|
|
1074
|
+
if (attachment.ownsTrace) {
|
|
1075
|
+
this.noteRootInput(attachment.stored, query);
|
|
1076
|
+
}
|
|
1077
|
+
this.applyIdentity(attachment.stored, metadata, tags);
|
|
1078
|
+
this.noteBounds(attachment.stored, startedAt, undefined);
|
|
1079
|
+
const handle = attachment.stored.handle.startSpan({
|
|
251
1080
|
name: serializedName(serialized, "langchain-retriever"),
|
|
252
|
-
|
|
1081
|
+
parentId: attachment.parentId,
|
|
1082
|
+
input: this.recordInputs() ? query : undefined,
|
|
253
1083
|
metadata: this.options.metadata,
|
|
254
|
-
attributes:
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
1084
|
+
attributes: langchainAttributes(runId, parentRunId, "retriever"),
|
|
1085
|
+
startedAt,
|
|
1086
|
+
});
|
|
1087
|
+
this.runs.set(runId, {
|
|
1088
|
+
owningTraceId: attachment.owningTraceId,
|
|
1089
|
+
rootRunId: attachment.rootRunId,
|
|
1090
|
+
handle,
|
|
1091
|
+
kind: "retriever",
|
|
1092
|
+
startedAt,
|
|
1093
|
+
parentRunId,
|
|
1094
|
+
ownsTrace: attachment.ownsTrace,
|
|
259
1095
|
});
|
|
260
|
-
this.runs.set(runId, { handle, parentRunId, type: "retriever" });
|
|
261
1096
|
}
|
|
262
|
-
handleRetrieverEnd(documents, runId) {
|
|
1097
|
+
async handleRetrieverEnd(documents, runId) {
|
|
263
1098
|
const run = this.runs.get(runId);
|
|
264
|
-
run
|
|
265
|
-
|
|
1099
|
+
if (!run)
|
|
1100
|
+
return;
|
|
1101
|
+
const endedAt = new Date();
|
|
1102
|
+
run.handle?.end({
|
|
1103
|
+
output: this.recordOutputs() ? documents : undefined,
|
|
1104
|
+
endedAt,
|
|
1105
|
+
durationMs: durationMs(run.startedAt, endedAt),
|
|
266
1106
|
});
|
|
1107
|
+
const stored = this.storedTrace(run.owningTraceId);
|
|
1108
|
+
if (stored) {
|
|
1109
|
+
this.noteBounds(stored, run.startedAt, endedAt);
|
|
1110
|
+
if (run.ownsTrace)
|
|
1111
|
+
this.noteRootOutput(stored, documents);
|
|
1112
|
+
}
|
|
267
1113
|
this.runs.delete(runId);
|
|
1114
|
+
await this.maybeFinalizeOwner(run, endedAt);
|
|
268
1115
|
}
|
|
269
|
-
handleRetrieverError(error, runId) {
|
|
1116
|
+
async handleRetrieverError(error, runId) {
|
|
270
1117
|
const run = this.runs.get(runId);
|
|
271
|
-
|
|
1118
|
+
if (!run)
|
|
1119
|
+
return;
|
|
1120
|
+
const endedAt = new Date();
|
|
1121
|
+
const message = errorMessage(error);
|
|
1122
|
+
run.handle?.end({
|
|
1123
|
+
status: "ERROR",
|
|
1124
|
+
error: this.recordOutputs() ? message : undefined,
|
|
1125
|
+
endedAt,
|
|
1126
|
+
durationMs: durationMs(run.startedAt, endedAt),
|
|
1127
|
+
});
|
|
1128
|
+
const stored = this.storedTrace(run.owningTraceId);
|
|
1129
|
+
if (stored) {
|
|
1130
|
+
this.noteBounds(stored, run.startedAt, endedAt);
|
|
1131
|
+
if (run.ownsTrace)
|
|
1132
|
+
this.noteRootError(stored, message);
|
|
1133
|
+
}
|
|
272
1134
|
this.runs.delete(runId);
|
|
1135
|
+
await this.maybeFinalizeOwner(run, endedAt);
|
|
1136
|
+
}
|
|
1137
|
+
/** Await outstanding terminal deliveries. */
|
|
1138
|
+
async flush() {
|
|
1139
|
+
await Promise.all([
|
|
1140
|
+
...Array.from(this.traces.entries(), ([id, stored]) => this.finalizeTrace(id, stored)),
|
|
1141
|
+
...Array.from(this.pending),
|
|
1142
|
+
]);
|
|
1143
|
+
}
|
|
1144
|
+
/** Finalize open traces and reset integration state. */
|
|
1145
|
+
async shutdown() {
|
|
1146
|
+
await this.flush();
|
|
1147
|
+
this.runs.clear();
|
|
1148
|
+
this.traces.clear();
|
|
273
1149
|
}
|
|
274
1150
|
}
|
|
275
1151
|
exports.LemmaLangChainCallbackHandler = LemmaLangChainCallbackHandler;
|
|
@@ -277,6 +1153,11 @@ exports.LangChainCallbackHandler = LemmaLangChainCallbackHandler;
|
|
|
277
1153
|
function langChain(options = {}) {
|
|
278
1154
|
return new LemmaLangChainCallbackHandler(options);
|
|
279
1155
|
}
|
|
1156
|
+
/**
|
|
1157
|
+
* LangGraph adapter: LangGraph emits LangChain callback events, so this is the
|
|
1158
|
+
* same handler with a LangGraph default trace name. Nesting, identity, and
|
|
1159
|
+
* finalization semantics match `langChain()`.
|
|
1160
|
+
*/
|
|
280
1161
|
function langGraph(options = {}) {
|
|
281
1162
|
return langChain({ agentName: "langgraph-agent", ...options });
|
|
282
1163
|
}
|