@anvia/langfuse 0.3.9 → 0.4.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/dist/index.js CHANGED
@@ -20,21 +20,30 @@ function modelParameters(request) {
20
20
  return params;
21
21
  }
22
22
  function usageDetails(usage) {
23
+ if (usage.details !== void 0 && Object.keys(usage.details).length > 0) {
24
+ return { ...usage.details };
25
+ }
23
26
  return {
24
- inputTokens: usage.inputTokens,
25
- outputTokens: usage.outputTokens,
26
- totalTokens: usage.totalTokens,
27
- cachedInputTokens: usage.cachedInputTokens,
28
- cacheCreationInputTokens: usage.cacheCreationInputTokens
27
+ input: usage.inputTokens,
28
+ output: usage.outputTokens,
29
+ total: usage.totalTokens
29
30
  };
30
31
  }
31
32
  function usageDetailsFromRecord(usage) {
33
+ if (isRecord(usage.details)) {
34
+ const details = Object.fromEntries(
35
+ Object.entries(usage.details).filter(
36
+ (entry) => typeof entry[1] === "number" && Number.isFinite(entry[1]) && entry[1] >= 0
37
+ )
38
+ );
39
+ if (Object.keys(details).length > 0) {
40
+ return details;
41
+ }
42
+ }
32
43
  return {
33
- inputTokens: numberValue(usage.inputTokens) ?? 0,
34
- outputTokens: numberValue(usage.outputTokens) ?? 0,
35
- totalTokens: numberValue(usage.totalTokens) ?? (numberValue(usage.inputTokens) ?? 0) + (numberValue(usage.outputTokens) ?? 0),
36
- cachedInputTokens: numberValue(usage.cachedInputTokens) ?? 0,
37
- cacheCreationInputTokens: numberValue(usage.cacheCreationInputTokens) ?? 0
44
+ input: numberValue(usage.inputTokens) ?? 0,
45
+ output: numberValue(usage.outputTokens) ?? 0,
46
+ total: numberValue(usage.totalTokens) ?? (numberValue(usage.inputTokens) ?? 0) + (numberValue(usage.outputTokens) ?? 0)
38
47
  };
39
48
  }
40
49
  function childMetadata(args, agentId, agentName, childTurn) {
@@ -121,6 +130,8 @@ var LangfuseScoreError = class extends Error {
121
130
  }
122
131
  }
123
132
  };
133
+ var RetryableLangfuseScoreError = class extends LangfuseScoreError {
134
+ };
124
135
  var defaultSleep = (ms) => new Promise((resolve) => {
125
136
  setTimeout(resolve, ms);
126
137
  });
@@ -139,6 +150,7 @@ var ScoreQueue = class {
139
150
  pending = [];
140
151
  timer = null;
141
152
  flushing = null;
153
+ closed = false;
142
154
  baseUrl;
143
155
  publicKey;
144
156
  secretKey;
@@ -164,9 +176,12 @@ var ScoreQueue = class {
164
176
  this.clearTimer = options.clearTimer ?? defaultClearTimer;
165
177
  }
166
178
  enqueue(score) {
179
+ if (this.closed) {
180
+ throw new Error("Langfuse score queue is shut down");
181
+ }
167
182
  this.pending.push(score);
168
183
  if (this.pending.length >= this.batchSize) {
169
- void this.flush();
184
+ this.triggerBackgroundFlush();
170
185
  return;
171
186
  }
172
187
  this.scheduleTimer();
@@ -175,24 +190,36 @@ var ScoreQueue = class {
175
190
  return this.pending.length;
176
191
  }
177
192
  async flush() {
178
- if (this.flushing !== null) {
179
- await this.flushing;
180
- return;
181
- }
182
- if (this.pending.length === 0) {
183
- this.clearScheduledTimer();
184
- return;
185
- }
186
- const batch = this.pending.splice(0, this.pending.length);
187
193
  this.clearScheduledTimer();
188
- this.flushing = this.sendBatch(batch).finally(() => {
189
- this.flushing = null;
190
- });
191
- await this.flushing;
194
+ while (true) {
195
+ if (this.flushing !== null) {
196
+ await this.flushing;
197
+ if (this.pending.length === 0) {
198
+ return;
199
+ }
200
+ continue;
201
+ }
202
+ if (this.pending.length === 0) {
203
+ return;
204
+ }
205
+ const draining = this.drain();
206
+ this.flushing = draining;
207
+ try {
208
+ await draining;
209
+ } finally {
210
+ if (this.flushing === draining) {
211
+ this.flushing = null;
212
+ }
213
+ }
214
+ }
192
215
  }
193
216
  async shutdown() {
217
+ this.closed = true;
194
218
  this.clearScheduledTimer();
195
- await this.flush();
219
+ try {
220
+ await this.flush();
221
+ } catch {
222
+ }
196
223
  }
197
224
  scheduleTimer() {
198
225
  if (this.timer !== null) {
@@ -200,9 +227,13 @@ var ScoreQueue = class {
200
227
  }
201
228
  this.timer = this.setTimer(() => {
202
229
  this.timer = null;
203
- void this.flush();
230
+ this.triggerBackgroundFlush();
204
231
  }, this.flushIntervalMs);
205
232
  }
233
+ triggerBackgroundFlush() {
234
+ void this.flush().catch(() => {
235
+ });
236
+ }
206
237
  clearScheduledTimer() {
207
238
  if (this.timer !== null) {
208
239
  this.clearTimer(this.timer);
@@ -245,12 +276,35 @@ var ScoreQueue = class {
245
276
  await this.sleep(computeBackoff(attempt));
246
277
  }
247
278
  }
248
- throw new LangfuseScoreError(
279
+ throw new RetryableLangfuseScoreError(
249
280
  `Langfuse score batch failed after ${this.maxRetries} attempts`,
250
281
  scores,
251
282
  lastError
252
283
  );
253
284
  }
285
+ async drain() {
286
+ let permanentError;
287
+ while (this.pending.length > 0) {
288
+ const batch = this.pending.splice(0, this.batchSize);
289
+ try {
290
+ await this.sendBatch(batch);
291
+ } catch (error) {
292
+ if (error instanceof RetryableLangfuseScoreError) {
293
+ this.pending.unshift(...batch);
294
+ throw error;
295
+ }
296
+ if (error instanceof LangfuseScoreError) {
297
+ permanentError ??= error;
298
+ continue;
299
+ }
300
+ this.pending.unshift(...batch);
301
+ throw error;
302
+ }
303
+ }
304
+ if (permanentError !== void 0) {
305
+ throw permanentError;
306
+ }
307
+ }
254
308
  };
255
309
  function buildScoreBody(score) {
256
310
  const body = {
@@ -947,29 +1001,42 @@ function cloneRegex(source, flags) {
947
1001
  }
948
1002
  function redactValue(value, depth, redactStringFn) {
949
1003
  if (depth > MAX_DEPTH) return value;
950
- if (typeof value === "string") return redactStringFn(value);
1004
+ if (typeof value === "string") {
1005
+ return isBase64DataUrl(value) ? value : redactStringFn(value);
1006
+ }
951
1007
  if (Array.isArray(value))
952
1008
  return value.map((entry) => redactValue(entry, depth + 1, redactStringFn));
953
1009
  if (value !== null && typeof value === "object") {
1010
+ if (value instanceof ArrayBuffer || ArrayBuffer.isView(value)) {
1011
+ return value;
1012
+ }
1013
+ const record = value;
954
1014
  const out = {};
955
- for (const [key, entry] of Object.entries(value)) {
956
- out[key] = redactValue(entry, depth + 1, redactStringFn);
1015
+ for (const [key, entry] of Object.entries(record)) {
1016
+ if (key === "data" && typeof entry === "string" && (record.type === "base64" || record.type === "image" || record.type === "encrypted" || record.type === "redacted")) {
1017
+ out[key] = entry;
1018
+ } else {
1019
+ out[key] = redactValue(entry, depth + 1, redactStringFn);
1020
+ }
957
1021
  }
958
1022
  return out;
959
1023
  }
960
1024
  return value;
961
1025
  }
962
1026
  function redactMessage(message, redactStringFn) {
963
- if (!Array.isArray(message.content)) return message;
964
- const newContent = message.content.map((part) => {
965
- if (part === null || typeof part !== "object") return part;
966
- const rec = part;
967
- if (rec.type === "text" && typeof rec.text === "string") {
968
- return { ...part, text: redactStringFn(rec.text) };
969
- }
970
- return part;
971
- });
972
- return { ...message, content: newContent };
1027
+ if (message.role === "system") {
1028
+ return { ...message, content: redactStringFn(message.content) };
1029
+ }
1030
+ return {
1031
+ ...message,
1032
+ content: redactMessageContent(message.content, redactStringFn)
1033
+ };
1034
+ }
1035
+ function redactMessageContent(value, redactStringFn) {
1036
+ return redactValue(value, 0, redactStringFn);
1037
+ }
1038
+ function isBase64DataUrl(value) {
1039
+ return /^data:[^;,]+;base64,/i.test(value);
973
1040
  }
974
1041
 
975
1042
  // src/tracing.ts
@@ -982,6 +1049,107 @@ import {
982
1049
  import { resourceFromAttributes } from "@opentelemetry/resources";
983
1050
  import { NodeSDK } from "@opentelemetry/sdk-node";
984
1051
  import { SEMRESATTRS_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
1052
+
1053
+ // src/capture.ts
1054
+ var DEFAULT_CAPTURE_MAX_BYTES = 262144;
1055
+ var MIN_CAPTURE_MAX_BYTES = 96;
1056
+ function validateCaptureMaxBytes(value) {
1057
+ const resolved = value ?? DEFAULT_CAPTURE_MAX_BYTES;
1058
+ if (!Number.isInteger(resolved) || resolved < MIN_CAPTURE_MAX_BYTES) {
1059
+ throw new TypeError(
1060
+ `Langfuse captureMaxBytes must be an integer of at least ${MIN_CAPTURE_MAX_BYTES}`
1061
+ );
1062
+ }
1063
+ return resolved;
1064
+ }
1065
+ function sanitizeTraceValue(value, maxBytes) {
1066
+ validateCaptureMaxBytes(maxBytes);
1067
+ const sanitized = sanitizeValue(value, 0, /* @__PURE__ */ new WeakSet());
1068
+ let serialized;
1069
+ try {
1070
+ serialized = JSON.stringify(sanitized) ?? String(sanitized);
1071
+ } catch {
1072
+ return omitted("unserializable");
1073
+ }
1074
+ const originalBytes = utf8Bytes(serialized);
1075
+ if (originalBytes <= maxBytes) {
1076
+ return sanitized;
1077
+ }
1078
+ const preview = boundedPreview(serialized, originalBytes, maxBytes);
1079
+ return {
1080
+ anviaTraceValue: "truncated",
1081
+ originalBytes,
1082
+ preview
1083
+ };
1084
+ }
1085
+ function sanitizeValue(value, depth, seen) {
1086
+ if (depth > 16) {
1087
+ return omitted("depth");
1088
+ }
1089
+ if (typeof value === "string") {
1090
+ if (/^data:[^;,]+;base64,/i.test(value)) {
1091
+ return omitted("base64", utf8Bytes(value));
1092
+ }
1093
+ return value;
1094
+ }
1095
+ if (value instanceof ArrayBuffer || ArrayBuffer.isView(value)) {
1096
+ const byteLength = value.byteLength;
1097
+ return omitted("binary", byteLength);
1098
+ }
1099
+ if (value === null || typeof value !== "object") {
1100
+ return value;
1101
+ }
1102
+ if (seen.has(value)) {
1103
+ return omitted("circular");
1104
+ }
1105
+ seen.add(value);
1106
+ if (Array.isArray(value)) {
1107
+ const result2 = value.map((entry) => sanitizeValue(entry, depth + 1, seen));
1108
+ seen.delete(value);
1109
+ return result2;
1110
+ }
1111
+ const record = value;
1112
+ const result = {};
1113
+ for (const [key, entry] of Object.entries(record)) {
1114
+ if (key === "data" && typeof entry === "string" && (record.type === "base64" || record.type === "image" && typeof record.mediaType === "string")) {
1115
+ result[key] = omitted("base64", utf8Bytes(entry));
1116
+ continue;
1117
+ }
1118
+ result[key] = sanitizeValue(entry, depth + 1, seen);
1119
+ }
1120
+ seen.delete(value);
1121
+ return result;
1122
+ }
1123
+ function omitted(reason, originalBytes) {
1124
+ return {
1125
+ anviaTraceValue: "omitted",
1126
+ reason,
1127
+ ...originalBytes === void 0 ? {} : { originalBytes }
1128
+ };
1129
+ }
1130
+ function boundedPreview(value, originalBytes, maxBytes) {
1131
+ let low = 0;
1132
+ let high = value.length;
1133
+ while (low < high) {
1134
+ const middle = Math.ceil((low + high) / 2);
1135
+ const candidate = {
1136
+ anviaTraceValue: "truncated",
1137
+ originalBytes,
1138
+ preview: value.slice(0, middle)
1139
+ };
1140
+ if (utf8Bytes(JSON.stringify(candidate)) <= maxBytes) {
1141
+ low = middle;
1142
+ } else {
1143
+ high = middle - 1;
1144
+ }
1145
+ }
1146
+ return value.slice(0, low);
1147
+ }
1148
+ function utf8Bytes(value) {
1149
+ return typeof Buffer === "undefined" ? new TextEncoder().encode(value).byteLength : Buffer.byteLength(value, "utf8");
1150
+ }
1151
+
1152
+ // src/tracing.ts
985
1153
  var langfuse = {
986
1154
  create(options = {}) {
987
1155
  return new LangfuseAgentObserver(options);
@@ -1001,6 +1169,8 @@ var LangfuseAgentObserver = class {
1001
1169
  redactor;
1002
1170
  redactInputs;
1003
1171
  redactOutputs;
1172
+ captureMode;
1173
+ captureMaxBytes;
1004
1174
  constructor(options) {
1005
1175
  const resolvedConfig = resolveLangfuseConfig(options);
1006
1176
  this[langfuseResolvedConfigSymbol] = resolvedConfig;
@@ -1041,23 +1211,31 @@ var LangfuseAgentObserver = class {
1041
1211
  }) : null;
1042
1212
  this.redactInputs = options.redactInputs;
1043
1213
  this.redactOutputs = options.redactOutputs;
1214
+ this.captureMode = options.captureMode ?? "safe";
1215
+ this.captureMaxBytes = validateCaptureMaxBytes(options.captureMaxBytes);
1044
1216
  this.redactor = options.redactInputs !== void 0 || options.redactOutputs !== void 0 ? createPiiRedactor(options.redaction) : void 0;
1045
1217
  }
1046
1218
  async startRun(args) {
1047
1219
  const traceId = args.trace?.traceId;
1048
- const redactedInput = this.maybeRedactInput({
1220
+ const capturedInput = this.captureInput({
1221
+ instructions: args.instructions,
1049
1222
  prompt: args.prompt,
1050
1223
  history: args.history
1051
1224
  });
1225
+ const capturedTraceMetadata = this.captureInput(args.trace?.metadata ?? {});
1052
1226
  const metadata = {
1053
1227
  agentName: args.agentName,
1054
1228
  agentDescription: args.agentDescription,
1055
1229
  maxTurns: args.maxTurns
1056
1230
  };
1057
1231
  if (this.serviceName !== void 0) metadata.serviceName = this.serviceName;
1058
- Object.assign(metadata, args.trace?.metadata);
1232
+ if (isRecord(capturedTraceMetadata)) {
1233
+ Object.assign(metadata, capturedTraceMetadata);
1234
+ } else {
1235
+ metadata.traceMetadata = capturedTraceMetadata;
1236
+ }
1059
1237
  const rootAttributes = {
1060
- input: redactedInput,
1238
+ input: capturedInput,
1061
1239
  metadata
1062
1240
  };
1063
1241
  if (args.trace?.version !== void 0) {
@@ -1075,7 +1253,7 @@ var LangfuseAgentObserver = class {
1075
1253
  }
1076
1254
  }
1077
1255
  );
1078
- applyTraceAttributes(root, args);
1256
+ applyTraceAttributes(root, args, capturedTraceMetadata);
1079
1257
  const promptRef = resolvePromptRef(args);
1080
1258
  const runObserver = new LangfuseRunObserver(
1081
1259
  root,
@@ -1087,7 +1265,9 @@ var LangfuseAgentObserver = class {
1087
1265
  {
1088
1266
  redactor: this.redactor,
1089
1267
  redactInputs: this.redactInputs,
1090
- redactOutputs: this.redactOutputs
1268
+ redactOutputs: this.redactOutputs,
1269
+ captureMode: this.captureMode,
1270
+ captureMaxBytes: this.captureMaxBytes
1091
1271
  }
1092
1272
  );
1093
1273
  this.currentHandle = runObserver.getHandle();
@@ -1118,13 +1298,9 @@ var LangfuseAgentObserver = class {
1118
1298
  getCurrentTrace() {
1119
1299
  return this.currentHandle;
1120
1300
  }
1121
- maybeRedactInput(value) {
1122
- if (this.redactor === void 0 || this.redactInputs === void 0) return value;
1123
- return applyRedaction(this.redactor, value, this.redactInputs);
1124
- }
1125
- maybeRedactOutput(value) {
1126
- if (this.redactor === void 0 || this.redactOutputs === void 0) return value;
1127
- return applyRedaction(this.redactor, value, this.redactOutputs);
1301
+ captureInput(value) {
1302
+ const redacted = this.redactor === void 0 || this.redactInputs === void 0 ? value : applyRedaction(this.redactor, value, this.redactInputs);
1303
+ return sanitizeTraceValue(redacted, this.captureMaxBytes);
1128
1304
  }
1129
1305
  async score(args) {
1130
1306
  if (args.traceId === void 0 || args.traceId.length === 0) {
@@ -1196,7 +1372,7 @@ function buildScoreBody2(score) {
1196
1372
  }
1197
1373
  return body;
1198
1374
  }
1199
- function applyTraceAttributes(root, args) {
1375
+ function applyTraceAttributes(root, args, capturedMetadata) {
1200
1376
  const traceName = args.trace?.name ?? args.agentName;
1201
1377
  if (traceName !== void 0) {
1202
1378
  root.otelSpan.setAttribute(LangfuseOtelSpanAttributes.TRACE_NAME, traceName);
@@ -1210,7 +1386,9 @@ function applyTraceAttributes(root, args) {
1210
1386
  if (args.trace?.tags !== void 0) {
1211
1387
  root.otelSpan.setAttribute(LangfuseOtelSpanAttributes.TRACE_TAGS, args.trace.tags);
1212
1388
  }
1213
- for (const [key, value] of Object.entries(args.trace?.metadata ?? {})) {
1389
+ for (const [key, value] of Object.entries(
1390
+ isRecord(capturedMetadata) ? capturedMetadata : { value: capturedMetadata }
1391
+ )) {
1214
1392
  const serialized = serializeMetadataValue(value);
1215
1393
  if (serialized === void 0) {
1216
1394
  continue;
@@ -1238,9 +1416,6 @@ function applyRedaction(redactor, value, mode) {
1238
1416
  if (typeof value === "string") {
1239
1417
  return redactor.redactString(value);
1240
1418
  }
1241
- if (Array.isArray(value)) {
1242
- return redactor.redactMessages(value);
1243
- }
1244
1419
  if (value !== null && typeof value === "object") {
1245
1420
  return redactor.redactObject(value);
1246
1421
  }
@@ -1250,6 +1425,9 @@ function resolvePromptRef(args) {
1250
1425
  if (args.promptRef !== void 0) {
1251
1426
  return args.promptRef;
1252
1427
  }
1428
+ if (args.trace?.promptRef !== void 0) {
1429
+ return args.trace.promptRef;
1430
+ }
1253
1431
  const metadata = args.trace?.metadata;
1254
1432
  if (metadata === void 0) {
1255
1433
  return void 0;
@@ -1291,6 +1469,19 @@ function serializeMetadataValue(value) {
1291
1469
  return "<failed to serialize>";
1292
1470
  }
1293
1471
  }
1472
+ function asMetadata(value) {
1473
+ return isRecord(value) ? value : { value };
1474
+ }
1475
+ function eventStartTime(value) {
1476
+ if (value instanceof Date) {
1477
+ return Number.isNaN(value.getTime()) ? void 0 : value;
1478
+ }
1479
+ if (typeof value !== "string") {
1480
+ return void 0;
1481
+ }
1482
+ const parsed = new Date(value);
1483
+ return Number.isNaN(parsed.getTime()) ? void 0 : parsed;
1484
+ }
1294
1485
  var LangfuseRunObserver = class {
1295
1486
  constructor(root, trace, promptRef, redaction) {
1296
1487
  this.root = root;
@@ -1300,6 +1491,8 @@ var LangfuseRunObserver = class {
1300
1491
  this.redactor = redaction.redactor;
1301
1492
  this.redactInputs = redaction.redactInputs;
1302
1493
  this.redactOutputs = redaction.redactOutputs;
1494
+ this.captureMode = redaction.captureMode;
1495
+ this.captureMaxBytes = redaction.captureMaxBytes;
1303
1496
  }
1304
1497
  root;
1305
1498
  trace;
@@ -1313,24 +1506,41 @@ var LangfuseRunObserver = class {
1313
1506
  redactor;
1314
1507
  redactInputs;
1315
1508
  redactOutputs;
1509
+ captureMode;
1510
+ captureMaxBytes;
1316
1511
  redactInputValue(value) {
1317
- if (this.redactor === void 0 || this.redactInputs === void 0) return value;
1318
- return applyRedaction(this.redactor, value, this.redactInputs);
1512
+ const redacted = this.redactor === void 0 || this.redactInputs === void 0 ? value : applyRedaction(this.redactor, value, this.redactInputs);
1513
+ return sanitizeTraceValue(redacted, this.captureMaxBytes);
1319
1514
  }
1320
1515
  redactOutputValue(value) {
1321
- if (this.redactor === void 0 || this.redactOutputs === void 0) return value;
1322
- return applyRedaction(this.redactor, value, this.redactOutputs);
1516
+ const redacted = this.redactor === void 0 || this.redactOutputs === void 0 ? value : applyRedaction(this.redactor, value, this.redactOutputs);
1517
+ return sanitizeTraceValue(redacted, this.captureMaxBytes);
1323
1518
  }
1324
1519
  startGeneration(args) {
1325
1520
  this.closeEarlierTurns(args.turn);
1326
1521
  const turn = this.turnSpan(args.turn);
1327
- const redactedChatHistory = this.redactInputValue(modelInputMessages(args.request.chatHistory));
1522
+ const safeInput = {
1523
+ instructions: args.request.instructions,
1524
+ messages: modelInputMessages(args.request.chatHistory)
1525
+ };
1526
+ if (this.captureMode === "full") {
1527
+ safeInput.documents = args.request.documents;
1528
+ safeInput.tools = args.request.tools;
1529
+ safeInput.providerTools = args.request.providerTools;
1530
+ safeInput.outputSchema = args.request.outputSchema;
1531
+ safeInput.additionalParams = args.request.additionalParams;
1532
+ }
1328
1533
  const metadata = {
1329
1534
  turn: args.turn,
1330
- toolCount: args.request.tools.length,
1331
- hasOutputSchema: args.request.outputSchema !== void 0
1535
+ documentCount: args.request.documents.length,
1536
+ toolNames: args.request.tools.map((tool) => tool.name),
1537
+ providerToolNames: args.request.providerTools?.map((tool) => tool.name) ?? [],
1538
+ hasOutputSchema: args.request.outputSchema !== void 0,
1539
+ additionalParamKeys: isRecord(args.request.additionalParams) ? Object.keys(args.request.additionalParams) : []
1332
1540
  };
1333
- if (args.providerRequest !== void 0) metadata.providerRequest = args.providerRequest;
1541
+ if (this.captureMode === "full" && args.providerRequest !== void 0) {
1542
+ metadata.providerRequest = args.providerRequest;
1543
+ }
1334
1544
  if (args.modelInfo !== void 0) {
1335
1545
  const modelInfo = {
1336
1546
  provider: args.modelInfo.provider,
@@ -1342,36 +1552,43 @@ var LangfuseRunObserver = class {
1342
1552
  metadata.modelInfo = modelInfo;
1343
1553
  }
1344
1554
  Object.assign(metadata, promptMetadata(this.promptRef));
1345
- const generation = turn.startObservation(
1346
- `model.turn.${args.turn}`,
1347
- {
1348
- input: redactedChatHistory,
1349
- model: args.request.model ?? "default",
1350
- modelParameters: modelParameters(args.request),
1351
- metadata
1352
- },
1353
- { asType: "generation" }
1354
- );
1355
- return new LangfuseGenerationObserver(generation, this);
1555
+ const generationAttributes = {
1556
+ input: this.redactInputValue(safeInput),
1557
+ model: args.request.model ?? args.modelInfo?.defaultModel ?? "default",
1558
+ modelParameters: modelParameters(args.request),
1559
+ metadata: asMetadata(this.redactInputValue(metadata))
1560
+ };
1561
+ if (this.promptRef?.version !== void 0) {
1562
+ generationAttributes.prompt = {
1563
+ name: this.promptRef.name,
1564
+ version: this.promptRef.version,
1565
+ isFallback: false
1566
+ };
1567
+ }
1568
+ const generation = turn.startObservation(`model.turn.${args.turn}`, generationAttributes, {
1569
+ asType: "generation"
1570
+ });
1571
+ return new LangfuseGenerationObserver(generation, this, /* @__PURE__ */ new Date());
1356
1572
  }
1357
1573
  startTool(args) {
1358
1574
  const turn = this.turnSpan(args.turn);
1359
- const redactedArgs = this.redactInputValue(args.args);
1360
1575
  const metadata = {
1361
1576
  turn: args.turn,
1362
1577
  internalCallId: args.internalCallId,
1363
1578
  toolCallId: args.toolCallId
1364
1579
  };
1365
- if (args.toolDefinition !== void 0) metadata.toolDefinition = args.toolDefinition;
1366
- if (args.toolMetadata !== void 0) metadata.toolMetadata = args.toolMetadata;
1580
+ if (this.captureMode === "full") {
1581
+ if (args.toolDefinition !== void 0) metadata.toolDefinition = args.toolDefinition;
1582
+ if (args.toolMetadata !== void 0) metadata.toolMetadata = args.toolMetadata;
1583
+ }
1367
1584
  const tool = turn.startObservation(
1368
1585
  `tool.${args.toolName}`,
1369
1586
  {
1370
- input: {
1371
- args: redactedArgs,
1587
+ input: this.redactInputValue({
1588
+ args: args.args,
1372
1589
  toolCall: args.toolCall
1373
- },
1374
- metadata
1590
+ }),
1591
+ metadata: asMetadata(this.redactInputValue(metadata))
1375
1592
  },
1376
1593
  { asType: "tool" }
1377
1594
  );
@@ -1380,34 +1597,52 @@ var LangfuseRunObserver = class {
1380
1597
  end(args) {
1381
1598
  this.closeAllTurns();
1382
1599
  const redactedOutput = this.redactOutputValue(args.output);
1600
+ const metadata = {
1601
+ usage: args.usage,
1602
+ messageCount: args.messages.length,
1603
+ sources: this.redactOutputValue(args.sources),
1604
+ providerToolCalls: this.redactOutputValue(args.providerToolCalls)
1605
+ };
1606
+ if (this.captureMode === "full") {
1607
+ metadata.messages = this.redactTranscript(args.messages);
1608
+ }
1383
1609
  this.root.update({
1384
1610
  output: redactedOutput,
1385
- metadata: {
1386
- usage: args.usage,
1387
- messages: args.messages
1388
- }
1611
+ metadata
1389
1612
  }).end();
1390
1613
  this.clearCurrentHandle?.();
1391
1614
  }
1392
1615
  error(args) {
1393
1616
  this.closeAllTurns();
1617
+ const redactedError = this.redactOutputValue(errorMessage(args.error));
1618
+ const metadata = {
1619
+ usage: args.usage,
1620
+ messageCount: args.messages.length
1621
+ };
1622
+ if (this.captureMode === "full") {
1623
+ metadata.messages = this.redactTranscript(args.messages);
1624
+ }
1394
1625
  this.root.update({
1395
1626
  level: "ERROR",
1396
- statusMessage: errorMessage(args.error),
1627
+ statusMessage: typeof redactedError === "string" ? redactedError : "Agent run failed",
1397
1628
  output: {
1398
- error: errorMessage(args.error)
1629
+ error: redactedError
1399
1630
  },
1400
- metadata: {
1401
- usage: args.usage,
1402
- messages: args.messages
1403
- }
1631
+ metadata
1404
1632
  }).end();
1405
1633
  this.clearCurrentHandle?.();
1406
1634
  }
1407
1635
  event(args) {
1408
- const metadata = {};
1409
- Object.assign(metadata, args.attributes);
1410
- this.root.startObservation(args.name, { metadata }, { asType: "event" }).end();
1636
+ const metadata = asMetadata(this.redactOutputValue(args.attributes ?? {}));
1637
+ const attributes = { metadata };
1638
+ if (args.level !== void 0) {
1639
+ attributes.level = args.level;
1640
+ }
1641
+ const startTime = eventStartTime(args.timestamp);
1642
+ this.root.startObservation(args.name, attributes, {
1643
+ asType: "event",
1644
+ ...startTime === void 0 ? {} : { startTime }
1645
+ });
1411
1646
  }
1412
1647
  getHandle() {
1413
1648
  return this.handle;
@@ -1417,13 +1652,15 @@ var LangfuseRunObserver = class {
1417
1652
  traceId: this.trace.traceId ?? "",
1418
1653
  observationId: this.trace.observationId ?? "",
1419
1654
  addAttributes: (attributes) => {
1420
- this.root.update({ metadata: attributes });
1655
+ this.root.update({ metadata: asMetadata(this.redactOutputValue(attributes)) });
1421
1656
  this.setCurrentHandle?.(this.handle);
1422
1657
  },
1423
1658
  addEvent: (name, attributes) => {
1424
- const metadata = {};
1425
- Object.assign(metadata, attributes);
1426
- this.root.startObservation(name, { metadata }, { asType: "event" }).end();
1659
+ this.root.startObservation(
1660
+ name,
1661
+ { metadata: asMetadata(this.redactOutputValue(attributes ?? {})) },
1662
+ { asType: "event" }
1663
+ );
1427
1664
  this.setCurrentHandle?.(this.handle);
1428
1665
  }
1429
1666
  };
@@ -1457,17 +1694,27 @@ var LangfuseRunObserver = class {
1457
1694
  }
1458
1695
  this.turnSpans.clear();
1459
1696
  }
1697
+ isFullCapture() {
1698
+ return this.captureMode === "full";
1699
+ }
1700
+ redactTranscript(messages) {
1701
+ const inputRedacted = this.redactor === void 0 || this.redactInputs === void 0 ? messages : applyRedaction(this.redactor, messages, this.redactInputs);
1702
+ const outputRedacted = this.redactor === void 0 || this.redactOutputs === void 0 ? inputRedacted : applyRedaction(this.redactor, inputRedacted, this.redactOutputs);
1703
+ return sanitizeTraceValue(outputRedacted, this.captureMaxBytes);
1704
+ }
1460
1705
  };
1461
1706
  var LangfuseGenerationObserver = class {
1462
- constructor(generation, run) {
1707
+ constructor(generation, run, startedAt) {
1463
1708
  this.generation = generation;
1464
1709
  this.run = run;
1710
+ this.startedAt = startedAt;
1465
1711
  }
1466
1712
  generation;
1467
1713
  run;
1714
+ startedAt;
1468
1715
  update(args) {
1469
1716
  this.generation.update({
1470
- output: { delta: args.delta }
1717
+ output: this.run.redactOutputValue({ delta: args.delta })
1471
1718
  });
1472
1719
  }
1473
1720
  end(args) {
@@ -1475,21 +1722,30 @@ var LangfuseGenerationObserver = class {
1475
1722
  const redactedChoice = this.run.redactOutputValue(args.response.choice);
1476
1723
  const metadata = { turn: args.turn };
1477
1724
  if (args.firstDeltaMs !== void 0) metadata.firstDeltaMs = args.firstDeltaMs;
1478
- this.generation.update({
1479
- output: {
1480
- messageId: args.response.messageId,
1481
- content: redactedChoice,
1482
- text: redactedText
1483
- },
1725
+ const output = {
1726
+ messageId: args.response.messageId,
1727
+ content: redactedChoice,
1728
+ text: redactedText,
1729
+ sources: this.run.redactOutputValue(args.response.sources),
1730
+ providerToolCalls: this.run.redactOutputValue(args.response.providerToolCalls)
1731
+ };
1732
+ const completionStartTime = args.firstDeltaMs === void 0 ? void 0 : new Date(this.startedAt.getTime() + args.firstDeltaMs);
1733
+ const update = {
1734
+ output,
1484
1735
  usageDetails: usageDetails(args.response.usage),
1485
1736
  metadata
1486
- }).end();
1737
+ };
1738
+ if (completionStartTime !== void 0) {
1739
+ update.completionStartTime = completionStartTime;
1740
+ }
1741
+ this.generation.update(update).end();
1487
1742
  }
1488
1743
  error(args) {
1744
+ const redactedError = this.run.redactOutputValue(errorMessage(args.error));
1489
1745
  this.generation.update({
1490
1746
  level: "ERROR",
1491
- statusMessage: errorMessage(args.error),
1492
- output: { error: errorMessage(args.error) },
1747
+ statusMessage: typeof redactedError === "string" ? redactedError : "Generation failed",
1748
+ output: { error: redactedError },
1493
1749
  metadata: { turn: args.turn }
1494
1750
  }).end();
1495
1751
  }
@@ -1515,36 +1771,129 @@ var LangfuseToolObserver = class {
1515
1771
  const childTurn = typeof child.turn === "number" ? child.turn : args.turn;
1516
1772
  const agent = this.childAgent(agentId, agentName, args);
1517
1773
  if (child.type === "turn_start") {
1774
+ const promptMessage = isRecord(child.prompt) ? child.prompt : void 0;
1775
+ const historyMessages = Array.isArray(child.history) ? child.history.filter(isRecord) : [];
1776
+ agent.startObservation(
1777
+ `${agentLabel(agentId, agentName)}.turn.${childTurn}.start`,
1778
+ {
1779
+ input: this.run.redactInputValue({
1780
+ prompt: promptMessage === void 0 ? void 0 : modelInputMessage(promptMessage),
1781
+ history: modelInputMessages(historyMessages)
1782
+ }),
1783
+ metadata: asMetadata(
1784
+ this.run.redactInputValue(childMetadata(args, agentId, agentName, childTurn))
1785
+ )
1786
+ },
1787
+ { asType: "event" }
1788
+ );
1789
+ return;
1790
+ }
1791
+ if (child.type === "generation_start" && isRecord(child.request)) {
1792
+ const request = child.request;
1793
+ const modelInfo = isRecord(child.modelInfo) ? child.modelInfo : void 0;
1794
+ const messages = Array.isArray(request.chatHistory) ? modelInputMessages(request.chatHistory.filter(isRecord)) : [];
1795
+ const input = {
1796
+ instructions: typeof request.instructions === "string" ? request.instructions : void 0,
1797
+ messages
1798
+ };
1799
+ if (this.run.isFullCapture()) {
1800
+ input.documents = request.documents;
1801
+ input.tools = request.tools;
1802
+ input.providerTools = request.providerTools;
1803
+ input.outputSchema = request.outputSchema;
1804
+ input.additionalParams = request.additionalParams;
1805
+ }
1806
+ const toolNames = Array.isArray(request.tools) ? request.tools.filter(isRecord).map((tool) => tool.name).filter((name) => typeof name === "string") : [];
1807
+ const providerToolNames = Array.isArray(request.providerTools) ? request.providerTools.filter(isRecord).map((tool) => tool.name).filter((name) => typeof name === "string") : [];
1518
1808
  const generation = agent.startObservation(
1519
1809
  `${agentLabel(agentId, agentName)}.model.turn.${childTurn}`,
1520
1810
  {
1521
- input: {
1522
- prompt: modelInputMessage(child.prompt),
1523
- history: modelInputMessages(child.history)
1524
- },
1525
- metadata: childMetadata(args, agentId, agentName, childTurn)
1811
+ input: this.run.redactInputValue(input),
1812
+ model: typeof request.model === "string" ? request.model : typeof modelInfo?.defaultModel === "string" ? modelInfo.defaultModel : "default",
1813
+ modelParameters: modelParameters(request),
1814
+ metadata: asMetadata(
1815
+ this.run.redactInputValue({
1816
+ ...childMetadata(args, agentId, agentName, childTurn),
1817
+ documentCount: Array.isArray(request.documents) ? request.documents.length : 0,
1818
+ toolNames,
1819
+ providerToolNames,
1820
+ hasOutputSchema: request.outputSchema !== void 0,
1821
+ modelInfo
1822
+ })
1823
+ )
1526
1824
  },
1527
1825
  { asType: "generation" }
1528
1826
  );
1529
- this.childGenerations.set(generationKey(agentId, childTurn), generation);
1827
+ this.childGenerations.set(generationKey(agentId, childTurn), {
1828
+ generation,
1829
+ startedAt: /* @__PURE__ */ new Date()
1830
+ });
1530
1831
  return;
1531
1832
  }
1532
1833
  if (child.type === "turn_end") {
1533
- const generation = this.childGenerations.get(generationKey(agentId, childTurn));
1534
- if (generation !== void 0) {
1834
+ const childGeneration = this.childGenerations.get(generationKey(agentId, childTurn));
1835
+ if (childGeneration !== void 0) {
1535
1836
  const update = {
1536
- output: child.response,
1537
- metadata: childMetadata(args, agentId, agentName, childTurn)
1837
+ output: this.run.redactOutputValue(child.response),
1838
+ metadata: asMetadata(
1839
+ this.run.redactOutputValue(childMetadata(args, agentId, agentName, childTurn))
1840
+ )
1538
1841
  };
1539
1842
  if (isRecord(child.response) && isRecord(child.response.usage)) {
1540
1843
  update.usageDetails = usageDetailsFromRecord(child.response.usage);
1541
1844
  }
1542
- generation.update(update).end();
1845
+ if (typeof child.firstDeltaMs === "number") {
1846
+ update.completionStartTime = new Date(
1847
+ childGeneration.startedAt.getTime() + child.firstDeltaMs
1848
+ );
1849
+ }
1850
+ childGeneration.generation.update(update).end();
1543
1851
  this.childGenerations.delete(generationKey(agentId, childTurn));
1544
1852
  }
1545
1853
  return;
1546
1854
  }
1855
+ if (child.type === "text_delta" || child.type === "reasoning_delta" || child.type === "tool_call_delta") {
1856
+ const childGeneration = this.childGenerations.get(generationKey(agentId, childTurn));
1857
+ childGeneration?.generation.update({
1858
+ output: this.run.redactOutputValue({ delta: child })
1859
+ });
1860
+ return;
1861
+ }
1862
+ if (child.type === "source" || child.type === "provider_tool_call") {
1863
+ const childGeneration = this.childGenerations.get(generationKey(agentId, childTurn));
1864
+ const parent = childGeneration?.generation ?? agent;
1865
+ parent.startObservation(
1866
+ `${agentLabel(agentId, agentName)}.${child.type}`,
1867
+ {
1868
+ output: this.run.redactOutputValue(
1869
+ child.type === "source" ? child.source : child.toolCall
1870
+ ),
1871
+ metadata: asMetadata(
1872
+ this.run.redactOutputValue(childMetadata(args, agentId, agentName, childTurn))
1873
+ )
1874
+ },
1875
+ { asType: "event" }
1876
+ );
1877
+ return;
1878
+ }
1879
+ if (child.type === "guardrail_decision") {
1880
+ agent.startObservation(
1881
+ `${agentLabel(agentId, agentName)}.guardrail`,
1882
+ {
1883
+ output: this.run.redactOutputValue(child.decision),
1884
+ metadata: asMetadata(
1885
+ this.run.redactOutputValue(childMetadata(args, agentId, agentName, childTurn))
1886
+ )
1887
+ },
1888
+ { asType: "guardrail" }
1889
+ ).end();
1890
+ return;
1891
+ }
1547
1892
  if (child.type === "tool_call" && isRecord(child.toolCall)) {
1893
+ const childGeneration = this.childGenerations.get(generationKey(agentId, childTurn));
1894
+ childGeneration?.generation.update({
1895
+ output: this.run.redactOutputValue({ toolCall: child.toolCall })
1896
+ });
1548
1897
  const toolCall = child.toolCall;
1549
1898
  const toolCallFunction = isRecord(toolCall.function) ? toolCall.function : void 0;
1550
1899
  const toolName = typeof toolCallFunction?.name === "string" ? toolCallFunction.name : "tool";
@@ -1552,15 +1901,17 @@ var LangfuseToolObserver = class {
1552
1901
  const childTool = agent.startObservation(
1553
1902
  `${agentLabel(agentId, agentName)}.${toolName}`,
1554
1903
  {
1555
- input: {
1904
+ input: this.run.redactInputValue({
1556
1905
  args: toolCallFunction?.arguments ?? {},
1557
1906
  toolCall
1558
- },
1559
- metadata: {
1560
- ...childMetadata(args, agentId, agentName, childTurn),
1561
- toolName,
1562
- toolCallId
1563
- }
1907
+ }),
1908
+ metadata: asMetadata(
1909
+ this.run.redactInputValue({
1910
+ ...childMetadata(args, agentId, agentName, childTurn),
1911
+ toolName,
1912
+ toolCallId
1913
+ })
1914
+ )
1564
1915
  },
1565
1916
  { asType: "tool" }
1566
1917
  );
@@ -1581,30 +1932,49 @@ var LangfuseToolObserver = class {
1581
1932
  if (childTool !== void 0) {
1582
1933
  childTool.ended = true;
1583
1934
  childTool.tool.update({
1584
- output: typeof child.result === "string" ? child.result : child,
1585
- metadata: {
1586
- ...childMetadata(args, agentId, agentName, childTurn),
1587
- toolName,
1588
- toolCallId,
1589
- internalCallId: typeof child.internalCallId === "string" ? child.internalCallId : void 0,
1590
- args: typeof child.args === "string" ? child.args : void 0
1591
- }
1935
+ output: this.run.redactOutputValue(
1936
+ typeof child.result === "string" ? child.result : child
1937
+ ),
1938
+ metadata: asMetadata(
1939
+ this.run.redactInputValue({
1940
+ ...childMetadata(args, agentId, agentName, childTurn),
1941
+ toolName,
1942
+ toolCallId,
1943
+ internalCallId: typeof child.internalCallId === "string" ? child.internalCallId : void 0,
1944
+ args: typeof child.args === "string" ? child.args : void 0
1945
+ })
1946
+ )
1592
1947
  }).end();
1593
1948
  }
1594
1949
  return;
1595
1950
  }
1596
1951
  if (child.type === "final") {
1597
- const update = { output: child.output };
1598
- if (isRecord(child.usage)) update.metadata = { usage: child.usage };
1952
+ const update = {
1953
+ output: this.run.redactOutputValue(child.output)
1954
+ };
1955
+ const metadata = {};
1956
+ if (isRecord(child.usage)) metadata.usage = child.usage;
1957
+ if (this.run.isFullCapture() && Array.isArray(child.messages)) {
1958
+ metadata.messages = this.run.redactTranscript(child.messages);
1959
+ }
1960
+ if (Object.keys(metadata).length > 0) update.metadata = metadata;
1599
1961
  agent.update(update).end();
1600
1962
  this.childAgents.delete(agentId);
1601
1963
  return;
1602
1964
  }
1603
1965
  if (child.type === "error") {
1966
+ const redactedError = this.run.redactOutputValue(errorMessage(child.error));
1967
+ const childGeneration = this.childGenerations.get(generationKey(agentId, childTurn));
1968
+ childGeneration?.generation.update({
1969
+ level: "ERROR",
1970
+ statusMessage: typeof redactedError === "string" ? redactedError : "Generation failed",
1971
+ output: { error: redactedError }
1972
+ }).end();
1973
+ this.childGenerations.delete(generationKey(agentId, childTurn));
1604
1974
  const update = {
1605
1975
  level: "ERROR",
1606
- statusMessage: errorMessage(child.error),
1607
- output: { error: errorMessage(child.error) }
1976
+ statusMessage: typeof redactedError === "string" ? redactedError : "Child agent failed",
1977
+ output: { error: redactedError }
1608
1978
  };
1609
1979
  if (isRecord(child.usage)) update.metadata = { usage: child.usage };
1610
1980
  agent.update(update).end();
@@ -1634,10 +2004,11 @@ var LangfuseToolObserver = class {
1634
2004
  }
1635
2005
  error(args) {
1636
2006
  this.endOpenChildren();
2007
+ const redactedError = this.run.redactOutputValue(errorMessage(args.error));
1637
2008
  this.tool.update({
1638
2009
  level: "ERROR",
1639
- statusMessage: errorMessage(args.error),
1640
- output: { error: errorMessage(args.error) },
2010
+ statusMessage: typeof redactedError === "string" ? redactedError : "Tool failed",
2011
+ output: { error: redactedError },
1641
2012
  metadata: {
1642
2013
  turn: args.turn,
1643
2014
  internalCallId: args.internalCallId,
@@ -1653,7 +2024,9 @@ var LangfuseToolObserver = class {
1653
2024
  const agent = this.tool.startObservation(
1654
2025
  `${agentLabel(agentId, agentName)}.run`,
1655
2026
  {
1656
- metadata: childMetadata(args, agentId, agentName, args.turn)
2027
+ metadata: asMetadata(
2028
+ this.run.redactInputValue(childMetadata(args, agentId, agentName, args.turn))
2029
+ )
1657
2030
  },
1658
2031
  { asType: "agent" }
1659
2032
  );
@@ -1674,7 +2047,7 @@ var LangfuseToolObserver = class {
1674
2047
  }
1675
2048
  endOpenChildren() {
1676
2049
  for (const generation of this.childGenerations.values()) {
1677
- generation.end();
2050
+ generation.generation.end();
1678
2051
  }
1679
2052
  this.childGenerations.clear();
1680
2053
  for (const tool of this.childTools) {