lightrace 0.1.1 → 0.1.3

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.
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Extends `BaseCallbackHandler` from `@langchain/core` to automatically
5
5
  * capture chains, LLM calls, tool invocations, and retriever operations
6
- * as Lightrace trace observations.
6
+ * as Lightrace trace observations via OpenTelemetry spans.
7
7
  *
8
8
  * @example
9
9
  * ```ts
@@ -16,6 +16,7 @@
16
16
  */
17
17
  import { BaseCallbackHandler } from "@langchain/core/callbacks/base";
18
18
  import { generateId, jsonSerializable } from "../utils.js";
19
+ import * as attrs from "../otel-exporter.js";
19
20
  import { Lightrace } from "../client.js";
20
21
  export class LightraceCallbackHandler extends BaseCallbackHandler {
21
22
  name = "lightrace";
@@ -30,8 +31,10 @@ export class LightraceCallbackHandler extends BaseCallbackHandler {
30
31
  sessionId;
31
32
  traceName;
32
33
  rootMetadata;
33
- // Exporter
34
- exporter = null;
34
+ // OTel exporter
35
+ otelExporter = null;
36
+ /** Root span for the current trace. */
37
+ rootSpan = null;
35
38
  /** The trace ID from the most recently completed root run. */
36
39
  lastTraceId = null;
37
40
  constructor(opts) {
@@ -41,23 +44,43 @@ export class LightraceCallbackHandler extends BaseCallbackHandler {
41
44
  this.traceName = opts?.traceName;
42
45
  this.rootMetadata = opts?.metadata;
43
46
  const client = opts?.client ?? Lightrace.getInstance();
44
- this.exporter = client?.getExporter() ?? null;
47
+ this.otelExporter = client?.getOtelExporter() ?? null;
45
48
  }
46
- // ── helpers ──────────────────────────────────────────────────────────
49
+ // -- helpers ----------------------------------------------------------------
47
50
  getParentObservationId(observationId) {
48
51
  return this.runParents.get(observationId) ?? null;
49
52
  }
50
- emit(event) {
51
- this.exporter?.enqueue(event);
53
+ /**
54
+ * Normalize IO data by converting BaseMessage-like objects to plain
55
+ * {role, content, tool_calls?} objects. Recurses into arrays and plain objects.
56
+ */
57
+ normalizeIO(data) {
58
+ if (data === null || data === undefined)
59
+ return data;
60
+ if (Array.isArray(data))
61
+ return data.map((d) => this.normalizeIO(d));
62
+ if (typeof data === "object") {
63
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
64
+ const obj = data;
65
+ // Detect BaseMessage-like objects
66
+ if ("content" in obj && ("type" in obj || "_getType" in obj)) {
67
+ const role = typeof obj._getType === "function" ? obj._getType() : (obj.type ?? "unknown");
68
+ return {
69
+ role,
70
+ content: obj.content,
71
+ ...(obj.tool_calls?.length ? { tool_calls: obj.tool_calls } : {}),
72
+ };
73
+ }
74
+ // Recurse into plain objects
75
+ const result = {};
76
+ for (const [k, v] of Object.entries(obj)) {
77
+ result[k] = this.normalizeIO(v);
78
+ }
79
+ return result;
80
+ }
81
+ return data;
52
82
  }
53
- emitObservation(run, endTime, output, level, statusMessage, extra) {
54
- const eventTypeMap = {
55
- span: "span-create",
56
- generation: "generation-create",
57
- event: "event-create",
58
- tool: "tool-create",
59
- chain: "chain-create",
60
- };
83
+ endObservationSpan(run, output, level, statusMessage, extra) {
61
84
  const obsTypeMap = {
62
85
  span: "SPAN",
63
86
  generation: "GENERATION",
@@ -65,62 +88,116 @@ export class LightraceCallbackHandler extends BaseCallbackHandler {
65
88
  tool: "TOOL",
66
89
  chain: "CHAIN",
67
90
  };
68
- const body = {
69
- id: run.observationId,
70
- traceId: this._traceId,
71
- type: obsTypeMap[run.type] ?? run.type,
72
- name: run.name,
73
- startTime: run.startTime.toISOString(),
74
- endTime: endTime.toISOString(),
75
- input: jsonSerializable(run.input),
76
- output: jsonSerializable(output),
77
- metadata: run.metadata ?? null,
78
- model: run.model ?? null,
79
- level,
80
- statusMessage,
81
- parentObservationId: this.getParentObservationId(run.observationId) ?? null,
82
- ...extra,
83
- };
84
- this.emit({
85
- id: generateId(),
86
- type: eventTypeMap[run.type] ?? "span-create",
87
- timestamp: run.startTime.toISOString(),
88
- body,
89
- });
91
+ const span = run.span;
92
+ span.setAttribute(attrs.OBSERVATION_TYPE, obsTypeMap[run.type] ?? run.type);
93
+ span.setAttribute(attrs.OBSERVATION_INPUT, attrs.safeJson(jsonSerializable(run.input)));
94
+ span.setAttribute(attrs.OBSERVATION_OUTPUT, attrs.safeJson(jsonSerializable(output)));
95
+ span.setAttribute(attrs.OBSERVATION_LEVEL, level);
96
+ if (run.metadata) {
97
+ span.setAttribute(attrs.OBSERVATION_METADATA, attrs.safeJson(run.metadata));
98
+ }
99
+ if (run.model) {
100
+ span.setAttribute(attrs.OBSERVATION_MODEL, run.model);
101
+ }
102
+ if (statusMessage) {
103
+ span.setAttribute(attrs.OBSERVATION_STATUS_MESSAGE, statusMessage);
104
+ }
105
+ if (run.modelParameters && Object.keys(run.modelParameters).length > 0) {
106
+ span.setAttribute(attrs.OBSERVATION_MODEL_PARAMETERS, attrs.safeJson(run.modelParameters));
107
+ }
108
+ if (extra) {
109
+ // Usage details from extra (promptTokens, completionTokens, totalTokens)
110
+ const usageDetails = {};
111
+ if (extra.promptTokens !== undefined)
112
+ usageDetails.promptTokens = extra.promptTokens;
113
+ if (extra.completionTokens !== undefined)
114
+ usageDetails.completionTokens = extra.completionTokens;
115
+ if (extra.totalTokens !== undefined)
116
+ usageDetails.totalTokens = extra.totalTokens;
117
+ if (Object.keys(usageDetails).length > 0) {
118
+ span.setAttribute(attrs.OBSERVATION_USAGE_DETAILS, attrs.safeJson(usageDetails));
119
+ }
120
+ }
121
+ span.end();
90
122
  }
91
123
  extractModelName(serialized) {
124
+ if (!serialized)
125
+ return undefined;
92
126
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
93
127
  const s = serialized;
94
128
  return (s?.kwargs?.model_name ?? s?.kwargs?.model ?? s?.kwargs?.modelName ?? s?.name ?? undefined);
95
129
  }
130
+ extractModelParameters(extraParams) {
131
+ const invocationParams = extraParams?.["invocation_params"];
132
+ const modelParameters = {};
133
+ for (const key of [
134
+ "temperature",
135
+ "max_tokens",
136
+ "top_p",
137
+ "frequency_penalty",
138
+ "presence_penalty",
139
+ ]) {
140
+ const val = invocationParams?.[key];
141
+ if (val !== undefined && val !== null)
142
+ modelParameters[key] = val;
143
+ }
144
+ return modelParameters;
145
+ }
96
146
  isAgent(serialized) {
147
+ if (!serialized)
148
+ return false;
97
149
  const name = (serialized.id?.join("/") ?? "").toLowerCase();
98
150
  const sName = (serialized.name ?? "").toLowerCase();
99
151
  return name.includes("agent") || sName.includes("agent");
100
152
  }
101
153
  registerRun(runId, parentRunId, info) {
154
+ const tracer = this.otelExporter?.tracer;
102
155
  const isRoot = !this.rootRunId;
103
156
  if (isRoot) {
104
157
  this.rootRunId = runId;
105
158
  this._traceId = generateId();
106
- // Emit root trace-create event
107
- this.emit({
108
- id: generateId(),
109
- type: "trace-create",
110
- timestamp: info.startTime.toISOString(),
111
- body: {
112
- id: this._traceId,
113
- name: this.traceName ?? info.name,
114
- timestamp: info.startTime.toISOString(),
115
- userId: this.userId,
116
- sessionId: this.sessionId,
117
- metadata: this.rootMetadata ?? null,
118
- input: jsonSerializable(info.input),
119
- },
120
- });
159
+ // Create root span representing the trace
160
+ if (tracer) {
161
+ this.rootSpan = tracer.startSpan(this.traceName ?? info.name, {
162
+ startTime: info.startTime,
163
+ });
164
+ this.rootSpan.setAttribute(attrs.AS_ROOT, "true");
165
+ this.rootSpan.setAttribute(attrs.TRACE_NAME, this.traceName ?? info.name);
166
+ this.rootSpan.setAttribute(attrs.TRACE_INPUT, attrs.safeJson(jsonSerializable(info.input)));
167
+ if (this.userId)
168
+ this.rootSpan.setAttribute(attrs.TRACE_USER_ID, this.userId);
169
+ if (this.sessionId)
170
+ this.rootSpan.setAttribute(attrs.TRACE_SESSION_ID, this.sessionId);
171
+ if (this.rootMetadata) {
172
+ this.rootSpan.setAttribute(attrs.TRACE_METADATA, attrs.safeJson(this.rootMetadata));
173
+ }
174
+ }
121
175
  }
122
176
  const observationId = generateId();
123
- const runInfo = { ...info, observationId, parentRunId };
177
+ // Create an OTel span for this observation
178
+ let span;
179
+ if (tracer) {
180
+ span = tracer.startSpan(info.name, { startTime: info.startTime });
181
+ }
182
+ else {
183
+ // Dummy span if no tracer -- will be a no-op
184
+ span = {
185
+ setAttribute: () => span,
186
+ setAttributes: () => span,
187
+ addEvent: () => span,
188
+ setStatus: () => span,
189
+ end: () => { },
190
+ isRecording: () => false,
191
+ recordException: () => { },
192
+ spanContext: () => ({
193
+ traceId: "",
194
+ spanId: "",
195
+ traceFlags: 0,
196
+ }),
197
+ updateName: () => span,
198
+ };
199
+ }
200
+ const runInfo = { ...info, observationId, parentRunId, span };
124
201
  this.runs.set(runId, runInfo);
125
202
  this.runParents.set(observationId, parentRunId ? this.runs.get(parentRunId)?.observationId : undefined);
126
203
  return runInfo;
@@ -129,25 +206,14 @@ export class LightraceCallbackHandler extends BaseCallbackHandler {
129
206
  const run = this.runs.get(runId);
130
207
  if (!run)
131
208
  return;
132
- const endTime = new Date();
133
- this.emitObservation(run, endTime, output, level, statusMessage, extra);
134
- // If this is the root run completing, update the trace and reset
209
+ this.endObservationSpan(run, output, level, statusMessage, extra);
210
+ // If this is the root run completing, end the root span and reset
135
211
  if (runId === this.rootRunId) {
136
- // Emit trace-create with output (update)
137
- this.emit({
138
- id: generateId(),
139
- type: "trace-create",
140
- timestamp: run.startTime.toISOString(),
141
- body: {
142
- id: this._traceId,
143
- name: this.traceName ?? run.name,
144
- timestamp: run.startTime.toISOString(),
145
- output: jsonSerializable(output),
146
- userId: this.userId,
147
- sessionId: this.sessionId,
148
- metadata: this.rootMetadata ?? null,
149
- },
150
- });
212
+ if (this.rootSpan) {
213
+ this.rootSpan.setAttribute(attrs.TRACE_OUTPUT, attrs.safeJson(jsonSerializable(output)));
214
+ this.rootSpan.end();
215
+ this.rootSpan = null;
216
+ }
151
217
  this.lastTraceId = this._traceId;
152
218
  this.rootRunId = null;
153
219
  this._traceId = null;
@@ -159,176 +225,314 @@ export class LightraceCallbackHandler extends BaseCallbackHandler {
159
225
  this.runs.delete(runId);
160
226
  }
161
227
  }
162
- // ── Chain callbacks ──────────────────────────────────────────────────
163
- async handleChainStart(chain, inputs, runId, parentRunId, tags, metadata) {
164
- const name = chain.name ??
165
- chain.id?.[chain.id.length - 1] ??
166
- "Chain";
167
- const type = this.isAgent(chain) ? "chain" : "chain";
168
- const mergedMetadata = { ...metadata, ...(tags?.length ? { tags } : {}) };
169
- this.registerRun(runId, parentRunId, {
170
- type,
171
- name,
172
- startTime: new Date(),
173
- input: inputs,
174
- metadata: Object.keys(mergedMetadata).length > 0 ? mergedMetadata : undefined,
175
- });
228
+ // -- Chain callbacks --------------------------------------------------------
229
+ async handleChainStart(chain, inputs, runId, parentRunId, tags, metadata, _runType, name) {
230
+ try {
231
+ const resolvedName = name ??
232
+ chain?.name ??
233
+ chain?.id?.[chain.id.length - 1] ??
234
+ "Chain";
235
+ const type = this.isAgent(chain) ? "chain" : "chain";
236
+ const mergedMetadata = { ...metadata, ...(tags?.length ? { tags } : {}) };
237
+ this.registerRun(runId, parentRunId, {
238
+ type,
239
+ name: resolvedName,
240
+ startTime: new Date(),
241
+ input: this.normalizeIO(inputs ?? {}),
242
+ metadata: Object.keys(mergedMetadata).length > 0 ? mergedMetadata : undefined,
243
+ });
244
+ }
245
+ catch (e) {
246
+ console.warn("[lightrace] Error in handleChainStart:", e);
247
+ }
176
248
  }
177
249
  async handleChainEnd(outputs, runId, _parentRunId) {
178
- this.endRun(runId, outputs);
250
+ try {
251
+ this.endRun(runId, this.normalizeIO(outputs ?? null));
252
+ }
253
+ catch (e) {
254
+ console.warn("[lightrace] Error in handleChainEnd:", e);
255
+ }
179
256
  }
180
257
  async handleChainError(error, runId, _parentRunId) {
181
- // LangGraph GraphBubbleUp is a control flow error, not an actual error
182
- if (error?.constructor?.name === "GraphBubbleUp" || error?.name === "GraphBubbleUp") {
183
- this.endRun(runId, null, "DEFAULT", null);
184
- return;
258
+ try {
259
+ // LangGraph GraphBubbleUp is a control flow error, not an actual error
260
+ if (error?.constructor?.name === "GraphBubbleUp" || error?.name === "GraphBubbleUp") {
261
+ this.endRun(runId, null, "DEFAULT", null);
262
+ return;
263
+ }
264
+ this.endRun(runId, null, "ERROR", error?.message ?? String(error));
265
+ }
266
+ catch (e) {
267
+ console.warn("[lightrace] Error in handleChainError:", e);
185
268
  }
186
- this.endRun(runId, null, "ERROR", error.message ?? String(error));
187
269
  }
188
- // ── LLM callbacks ───────────────────────────────────────────────────
189
- async handleLLMStart(llm, prompts, runId, parentRunId, _extraParams, _tags, metadata) {
190
- const model = this.extractModelName(llm);
191
- const name = model ??
192
- llm.name ??
193
- llm.id?.[llm.id.length - 1] ??
194
- "LLM";
195
- this.registerRun(runId, parentRunId, {
196
- type: "generation",
197
- name,
198
- startTime: new Date(),
199
- input: prompts.length === 1 ? prompts[0] : prompts,
200
- model,
201
- metadata,
202
- });
270
+ // -- LLM callbacks ----------------------------------------------------------
271
+ async handleLLMStart(llm, prompts, runId, parentRunId, extraParams, _tags, metadata, name) {
272
+ try {
273
+ const model = this.extractModelName(llm);
274
+ const resolvedName = name ??
275
+ model ??
276
+ llm?.name ??
277
+ llm?.id?.[llm.id.length - 1] ??
278
+ "LLM";
279
+ const modelParameters = this.extractModelParameters(extraParams);
280
+ this.registerRun(runId, parentRunId, {
281
+ type: "generation",
282
+ name: resolvedName,
283
+ startTime: new Date(),
284
+ input: prompts?.length === 1 ? prompts[0] : (prompts ?? []),
285
+ model,
286
+ metadata,
287
+ modelParameters,
288
+ });
289
+ }
290
+ catch (e) {
291
+ console.warn("[lightrace] Error in handleLLMStart:", e);
292
+ }
203
293
  }
204
- async handleChatModelStart(llm, messages, runId, parentRunId, _extraParams, _tags, metadata) {
205
- const model = this.extractModelName(llm);
206
- const name = model ??
207
- llm.name ??
208
- llm.id?.[llm.id.length - 1] ??
209
- "ChatModel";
210
- // Convert messages to a simpler role/content format
211
- const formattedMessages = messages.map((msgGroup) => msgGroup.map((msg) => ({
212
- role: msg._getType(),
213
- content: msg.content,
214
- ...(msg.name ? { name: msg.name } : {}),
215
- })));
216
- this.registerRun(runId, parentRunId, {
217
- type: "generation",
218
- name,
219
- startTime: new Date(),
220
- input: formattedMessages.length === 1 ? formattedMessages[0] : formattedMessages,
221
- model,
222
- metadata,
223
- });
294
+ async handleChatModelStart(llm, messages, runId, parentRunId, extraParams, _tags, metadata, name) {
295
+ try {
296
+ const model = this.extractModelName(llm);
297
+ const resolvedName = name ??
298
+ model ??
299
+ llm?.name ??
300
+ llm?.id?.[llm.id.length - 1] ??
301
+ "ChatModel";
302
+ const modelParameters = this.extractModelParameters(extraParams);
303
+ // Convert messages to a simpler role/content format
304
+ const formattedMessages = (messages ?? []).map((msgGroup) => (msgGroup ?? []).map((msg) => {
305
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
306
+ const msgAny = msg;
307
+ const role = typeof msgAny?._getType === "function"
308
+ ? msgAny._getType()
309
+ : (msgAny?.type ?? "unknown");
310
+ return {
311
+ role,
312
+ content: msg?.content,
313
+ ...(msg?.name ? { name: msg.name } : {}),
314
+ ...(msgAny?.tool_calls?.length ? { tool_calls: msgAny.tool_calls } : {}),
315
+ };
316
+ }));
317
+ this.registerRun(runId, parentRunId, {
318
+ type: "generation",
319
+ name: resolvedName,
320
+ startTime: new Date(),
321
+ input: formattedMessages.length === 1 ? formattedMessages[0] : formattedMessages,
322
+ model,
323
+ metadata,
324
+ modelParameters,
325
+ });
326
+ }
327
+ catch (e) {
328
+ console.warn("[lightrace] Error in handleChatModelStart:", e);
329
+ }
224
330
  }
225
331
  async handleLLMEnd(output, runId, _parentRunId) {
226
- const extra = {};
227
- // Extract token usage
228
- const usage = output.llmOutput?.tokenUsage ?? output.llmOutput?.estimatedTokens ?? null;
229
- if (usage) {
230
- if (usage.promptTokens !== undefined)
231
- extra.promptTokens = usage.promptTokens;
232
- if (usage.completionTokens !== undefined)
233
- extra.completionTokens = usage.completionTokens;
234
- if (usage.totalTokens !== undefined)
235
- extra.totalTokens = usage.totalTokens;
236
- }
237
- // Add TTFT if we have it
238
- const ttftStart = this.completionStartTimes.get(runId);
239
- const run = this.runs.get(runId);
240
- if (ttftStart && run) {
241
- const ttft = ttftStart.getTime() - run.startTime.getTime();
242
- if (!run.metadata)
243
- run.metadata = {};
244
- run.metadata.timeToFirstToken = ttft;
245
- }
246
- // Extract output text
247
- const generations = output.generations;
248
- let outputText;
249
- if (generations.length === 1 && generations[0].length === 1) {
250
- const gen = generations[0][0];
251
- // ChatGeneration has a `message` field, base Generation only has `text`
252
- const genAny = gen;
253
- outputText = genAny.message ?? gen.text;
332
+ try {
333
+ const extra = {};
334
+ const run = this.runs.get(runId);
335
+ // Extract token usage from multiple sources
336
+ const llmUsage = output?.llmOutput?.tokenUsage ?? output?.llmOutput?.usage ?? null;
337
+ // Also check generation-level usage_metadata (e.g. Anthropic, Google)
338
+ const lastGen = output?.generations?.at(-1)?.at(-1);
339
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
340
+ const msgUsage = lastGen?.message?.usage_metadata;
341
+ const usage = llmUsage ?? msgUsage;
342
+ if (usage) {
343
+ const promptTokens = usage.promptTokens ?? usage.prompt_tokens ?? usage.input_tokens ?? undefined;
344
+ const completionTokens = usage.completionTokens ?? usage.completion_tokens ?? usage.output_tokens ?? undefined;
345
+ const totalTokens = usage.totalTokens ?? usage.total_tokens ?? undefined;
346
+ if (promptTokens !== undefined)
347
+ extra.promptTokens = promptTokens;
348
+ if (completionTokens !== undefined)
349
+ extra.completionTokens = completionTokens;
350
+ if (totalTokens !== undefined)
351
+ extra.totalTokens = totalTokens;
352
+ else if (promptTokens !== undefined && completionTokens !== undefined)
353
+ extra.totalTokens = promptTokens + completionTokens;
354
+ }
355
+ // Extract model name from response if not set at start
356
+ if (run && !run.model) {
357
+ const modelFromResponse = output?.llmOutput?.model_name ?? output?.llmOutput?.model ?? undefined;
358
+ if (modelFromResponse) {
359
+ run.model = modelFromResponse;
360
+ // Also update name if it was a default
361
+ if (run.name === "LLM" || run.name === "ChatModel") {
362
+ run.name = modelFromResponse;
363
+ }
364
+ }
365
+ }
366
+ // Add TTFT if we have it
367
+ const ttftStart = this.completionStartTimes.get(runId);
368
+ if (ttftStart && run) {
369
+ const ttft = ttftStart.getTime() - run.startTime.getTime();
370
+ if (!run.metadata)
371
+ run.metadata = {};
372
+ run.metadata.timeToFirstToken = ttft;
373
+ }
374
+ // Extract output -- handle ChatGeneration (has .message) vs Generation (only .text)
375
+ let outputData;
376
+ const generations = output?.generations;
377
+ if (generations && generations.length > 0) {
378
+ const singleGen = generations.length === 1 && generations[0].length === 1;
379
+ if (singleGen) {
380
+ const gen = generations[0][0];
381
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
382
+ const genAny = gen;
383
+ if (genAny?.message) {
384
+ const msg = genAny.message;
385
+ const role = typeof msg._getType === "function" ? msg._getType() : (msg.type ?? "assistant");
386
+ outputData = {
387
+ role,
388
+ content: msg.content,
389
+ ...(msg.tool_calls?.length ? { tool_calls: msg.tool_calls } : {}),
390
+ };
391
+ }
392
+ else {
393
+ outputData = gen?.text;
394
+ }
395
+ }
396
+ else {
397
+ outputData = generations.map((g) => g.map((gg) => {
398
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
399
+ const ggAny = gg;
400
+ if (ggAny?.message) {
401
+ const msg = ggAny.message;
402
+ const role = typeof msg._getType === "function" ? msg._getType() : (msg.type ?? "assistant");
403
+ return {
404
+ role,
405
+ content: msg.content,
406
+ ...(msg.tool_calls?.length ? { tool_calls: msg.tool_calls } : {}),
407
+ };
408
+ }
409
+ return gg?.text;
410
+ }));
411
+ }
412
+ }
413
+ else {
414
+ outputData = null;
415
+ }
416
+ this.endRun(runId, outputData, "DEFAULT", null, extra);
417
+ this.completionStartTimes.delete(runId);
254
418
  }
255
- else {
256
- outputText = generations.map((g) => g.map((gg) => {
257
- const ggAny = gg;
258
- return ggAny.message ?? gg.text;
259
- }));
419
+ catch (e) {
420
+ console.warn("[lightrace] Error in handleLLMEnd:", e);
260
421
  }
261
- this.endRun(runId, outputText, "DEFAULT", null, extra);
262
- this.completionStartTimes.delete(runId);
263
422
  }
264
423
  async handleLLMNewToken(_token, _idx, runId) {
265
- if (!this.completionStartTimes.has(runId)) {
266
- this.completionStartTimes.set(runId, new Date());
424
+ try {
425
+ if (!this.completionStartTimes.has(runId)) {
426
+ this.completionStartTimes.set(runId, new Date());
427
+ }
428
+ }
429
+ catch (e) {
430
+ console.warn("[lightrace] Error in handleLLMNewToken:", e);
267
431
  }
268
432
  }
269
433
  async handleLLMError(error, runId, _parentRunId) {
270
- this.endRun(runId, null, "ERROR", error.message ?? String(error));
271
- this.completionStartTimes.delete(runId);
434
+ try {
435
+ this.endRun(runId, null, "ERROR", error?.message ?? String(error));
436
+ this.completionStartTimes.delete(runId);
437
+ }
438
+ catch (e) {
439
+ console.warn("[lightrace] Error in handleLLMError:", e);
440
+ }
272
441
  }
273
- // ── Tool callbacks ──────────────────────────────────────────────────
274
- async handleToolStart(tool, input, runId, parentRunId, _tags, metadata) {
275
- const name = tool.name ??
276
- tool.id?.[tool.id.length - 1] ??
277
- "Tool";
278
- // Try to parse input as JSON
279
- let parsedInput = input;
442
+ // -- Tool callbacks ---------------------------------------------------------
443
+ async handleToolStart(tool, input, runId, parentRunId, _tags, metadata, name) {
280
444
  try {
281
- parsedInput = JSON.parse(input);
282
- }
283
- catch {
284
- // keep as string
285
- }
286
- this.registerRun(runId, parentRunId, {
287
- type: "tool",
288
- name,
289
- startTime: new Date(),
290
- input: parsedInput,
291
- metadata,
292
- });
445
+ const resolvedName = name ??
446
+ tool?.name ??
447
+ tool?.id?.[tool.id.length - 1] ??
448
+ "Tool";
449
+ // Try to parse input as JSON
450
+ let parsedInput = input;
451
+ if (typeof input === "string") {
452
+ try {
453
+ parsedInput = JSON.parse(input);
454
+ }
455
+ catch {
456
+ // keep as string
457
+ }
458
+ }
459
+ this.registerRun(runId, parentRunId, {
460
+ type: "tool",
461
+ name: resolvedName,
462
+ startTime: new Date(),
463
+ input: parsedInput,
464
+ metadata,
465
+ });
466
+ }
467
+ catch (e) {
468
+ console.warn("[lightrace] Error in handleToolStart:", e);
469
+ }
293
470
  }
294
471
  async handleToolEnd(output, runId, _parentRunId) {
295
- // Try to parse output as JSON
296
- let parsedOutput = output;
297
472
  try {
298
- parsedOutput = JSON.parse(output);
473
+ // Try to parse output as JSON
474
+ let parsedOutput = output;
475
+ if (typeof output === "string") {
476
+ try {
477
+ parsedOutput = JSON.parse(output);
478
+ }
479
+ catch {
480
+ // keep as string
481
+ }
482
+ }
483
+ this.endRun(runId, parsedOutput);
299
484
  }
300
- catch {
301
- // keep as string
485
+ catch (e) {
486
+ console.warn("[lightrace] Error in handleToolEnd:", e);
302
487
  }
303
- this.endRun(runId, parsedOutput);
304
488
  }
305
489
  async handleToolError(error, runId, _parentRunId) {
306
- this.endRun(runId, null, "ERROR", error.message ?? String(error));
490
+ try {
491
+ this.endRun(runId, null, "ERROR", error?.message ?? String(error));
492
+ }
493
+ catch (e) {
494
+ console.warn("[lightrace] Error in handleToolError:", e);
495
+ }
307
496
  }
308
- // ── Retriever callbacks ─────────────────────────────────────────────
497
+ // -- Retriever callbacks ----------------------------------------------------
309
498
  async handleRetrieverStart(retriever, query, runId, parentRunId, _tags, metadata) {
310
- const name = retriever.name ??
311
- retriever.id?.[retriever.id.length - 1] ??
312
- "Retriever";
313
- this.registerRun(runId, parentRunId, {
314
- type: "span",
315
- name,
316
- startTime: new Date(),
317
- input: query,
318
- metadata,
319
- });
499
+ try {
500
+ const name = retriever?.name ??
501
+ retriever?.id?.[retriever.id.length - 1] ??
502
+ "Retriever";
503
+ this.registerRun(runId, parentRunId, {
504
+ type: "span",
505
+ name,
506
+ startTime: new Date(),
507
+ input: query,
508
+ metadata,
509
+ });
510
+ }
511
+ catch (e) {
512
+ console.warn("[lightrace] Error in handleRetrieverStart:", e);
513
+ }
320
514
  }
321
515
  async handleRetrieverEnd(documents, runId, _parentRunId) {
322
- const output = documents.map((doc) => ({
323
- pageContent: doc.pageContent,
324
- metadata: doc.metadata,
325
- }));
326
- this.endRun(runId, output);
516
+ try {
517
+ const output = (documents ?? []).map((doc) => ({
518
+ pageContent: doc?.pageContent,
519
+ metadata: doc?.metadata,
520
+ }));
521
+ this.endRun(runId, output);
522
+ }
523
+ catch (e) {
524
+ console.warn("[lightrace] Error in handleRetrieverEnd:", e);
525
+ }
327
526
  }
328
527
  async handleRetrieverError(error, runId, _parentRunId) {
329
- this.endRun(runId, null, "ERROR", error.message ?? String(error));
528
+ try {
529
+ this.endRun(runId, null, "ERROR", error?.message ?? String(error));
530
+ }
531
+ catch (e) {
532
+ console.warn("[lightrace] Error in handleRetrieverError:", e);
533
+ }
330
534
  }
331
- // ── Accessors ───────────────────────────────────────────────────────
535
+ // -- Accessors --------------------------------------------------------------
332
536
  /** Get the current active trace ID (null if no run is active). */
333
537
  get traceId() {
334
538
  return this._traceId;