@anvia/langfuse 0.6.1 → 1.0.0-rc.10

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
@@ -1,120 +1,155 @@
1
- // src/helpers.ts
2
- function modelInputMessage(message) {
3
- if (message.metadata === void 0) {
4
- return message;
1
+ // src/redaction.ts
2
+ var DEFAULT_REPLACEMENT = "[REDACTED]";
3
+ var MAX_DEPTH = 16;
4
+ function createPiiRedactor(options = {}) {
5
+ const patterns = options.patterns ?? DEFAULT_PATTERNS;
6
+ const replacement = options.replacement ?? DEFAULT_REPLACEMENT;
7
+ const compiled = patterns.map((p) => ({
8
+ name: p.name,
9
+ regex: cloneRegex(p.regex, "g")
10
+ }));
11
+ const patternNamesList = patterns.map((p) => p.name);
12
+ function redactString(input) {
13
+ if (typeof input !== "string") return input;
14
+ let out = input;
15
+ for (const { name, regex } of compiled) {
16
+ out = applyPattern(out, name, regex, replacement);
17
+ }
18
+ return out;
5
19
  }
6
- const result = { ...message };
7
- delete result.metadata;
8
- return result;
9
- }
10
- function modelInputMessages(messages) {
11
- return messages.map(modelInputMessage);
12
- }
13
- function modelParameters(request) {
14
- const params = {};
15
- if (request.temperature !== void 0) params.temperature = request.temperature;
16
- if (request.maxTokens !== void 0) params.maxTokens = request.maxTokens;
17
- if (request.toolChoice !== void 0) {
18
- params.toolChoice = typeof request.toolChoice === "string" ? request.toolChoice : request.toolChoice.name;
20
+ function redactObject(input) {
21
+ return redactValue(input, 0, redactString);
19
22
  }
20
- return params;
23
+ function redactMessages(input) {
24
+ return input.map((message) => redactMessage(message, redactString));
25
+ }
26
+ function patternNames() {
27
+ return patternNamesList;
28
+ }
29
+ return { redactString, redactObject, redactMessages, patternNames };
21
30
  }
22
- function usageDetails(usage) {
23
- if (usage.details !== void 0 && Object.keys(usage.details).length > 0) {
24
- return { ...usage.details };
31
+ var DEFAULT_PATTERNS = [
32
+ { name: "email", regex: /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g },
33
+ { name: "creditCard", regex: /\b(?:\d[ -]?){13,19}\b/g },
34
+ { name: "ipv4", regex: /\b(?:\d{1,3}\.){3}\d{1,3}\b/g },
35
+ {
36
+ name: "phone",
37
+ regex: /(?<!\d)(?:\+\d{1,3}[\s.-]?)?(?:\(\d{2,4}\)[\s.-]?)?\d{3,4}[\s.-]?\d{3,4}(?:[\s.-]?\d{3,4})?(?!\d)/g
38
+ },
39
+ { name: "jwt", regex: /\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g },
40
+ {
41
+ name: "apiKey",
42
+ regex: /\b(?:sk|pk|api|key|token)[-_][A-Za-z0-9]{16,}\b/gi
25
43
  }
26
- return {
27
- input: usage.inputTokens,
28
- output: usage.outputTokens,
29
- total: usage.totalTokens
30
- };
44
+ ];
45
+ function applyPattern(input, name, regex, replacement) {
46
+ if (name === "creditCard") {
47
+ return redactCreditCards(input, replacement);
48
+ }
49
+ return input.replace(regex, replacement);
31
50
  }
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;
51
+ function redactCreditCards(input, replacement) {
52
+ let out = "";
53
+ let i = 0;
54
+ while (i < input.length) {
55
+ const ch = input.charAt(i);
56
+ if (/\d/.test(ch)) {
57
+ const { length, valid } = longestLuhnChunk(input.slice(i));
58
+ if (valid) {
59
+ out += replacement;
60
+ i += length;
61
+ continue;
62
+ }
41
63
  }
64
+ out += ch;
65
+ i += 1;
42
66
  }
43
- return {
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)
47
- };
48
- }
49
- function childMetadata(args, agentId, agentName, childTurn) {
50
- return {
51
- source: "agent_tool_event",
52
- childAgentId: agentId,
53
- childAgentName: agentName,
54
- childTurn,
55
- parentToolName: args.toolName,
56
- parentInternalCallId: args.internalCallId,
57
- parentToolCallId: args.toolCallId
58
- };
59
- }
60
- function generationKey(agentId, turn) {
61
- return `${agentId}:${turn}`;
67
+ return out;
62
68
  }
63
- function agentLabel(agentId, agentName) {
64
- return (agentName ?? agentId).replaceAll(/\s+/g, "_");
69
+ function longestLuhnChunk(s) {
70
+ let length = 0;
71
+ let bestValid = 0;
72
+ while (length < s.length && length < 40) {
73
+ const ch = s.charAt(length);
74
+ if (!/\d/.test(ch) && ch !== "-") break;
75
+ length += 1;
76
+ const candidate = s.slice(0, length).replace(/\D/g, "");
77
+ if (candidate.length >= 13 && candidate.length <= 19) {
78
+ if (startsWithKnownPrefix(candidate) && passesLuhn(candidate)) {
79
+ bestValid = length;
80
+ }
81
+ }
82
+ }
83
+ return { length: bestValid, valid: bestValid > 0 };
65
84
  }
66
- function isRecord(value) {
67
- return typeof value === "object" && value !== null && !Array.isArray(value);
85
+ function startsWithKnownPrefix(digits) {
86
+ if (digits.startsWith("4")) return true;
87
+ const two = digits.slice(0, 2);
88
+ if (two === "51" || two === "52" || two === "53" || two === "54" || two === "55") return true;
89
+ const four = digits.slice(0, 4);
90
+ if (four === "2221" || four === "2720") return true;
91
+ const twoAgain = digits.slice(0, 2);
92
+ if (twoAgain === "34" || twoAgain === "37") return true;
93
+ if (four === "6011" || twoAgain === "65") return true;
94
+ return digits.startsWith("35");
68
95
  }
69
- function numberValue(value) {
70
- return typeof value === "number" ? value : void 0;
96
+ function passesLuhn(digits) {
97
+ if (!/^\d+$/.test(digits)) return false;
98
+ let sum = 0;
99
+ let alt = false;
100
+ for (let i = digits.length - 1; i >= 0; i -= 1) {
101
+ const raw = digits.charCodeAt(i) - 48;
102
+ let value = raw;
103
+ if (alt) {
104
+ value *= 2;
105
+ if (value > 9) value -= 9;
106
+ }
107
+ sum += value;
108
+ alt = !alt;
109
+ }
110
+ return sum % 10 === 0;
71
111
  }
72
- function emptyToUndefined(value) {
73
- return value === void 0 || value.length === 0 ? void 0 : value;
112
+ function cloneRegex(source, flags) {
113
+ return new RegExp(source.source, flags + source.flags.replace(/g/g, ""));
74
114
  }
75
- function errorMessage(error) {
76
- return error instanceof Error ? error.message : String(error);
115
+ function redactValue(value, depth, redactStringFn) {
116
+ if (depth > MAX_DEPTH) return value;
117
+ if (typeof value === "string") {
118
+ return isBase64DataUrl(value) ? value : redactStringFn(value);
119
+ }
120
+ if (Array.isArray(value))
121
+ return value.map((entry) => redactValue(entry, depth + 1, redactStringFn));
122
+ if (value !== null && typeof value === "object") {
123
+ if (value instanceof ArrayBuffer || ArrayBuffer.isView(value)) {
124
+ return value;
125
+ }
126
+ const record = value;
127
+ const out = {};
128
+ for (const [key, entry] of Object.entries(record)) {
129
+ if (key === "data" && typeof entry === "string" && (record.type === "base64" || record.type === "image" || record.type === "encrypted" || record.type === "redacted")) {
130
+ out[key] = entry;
131
+ } else {
132
+ out[key] = redactValue(entry, depth + 1, redactStringFn);
133
+ }
134
+ }
135
+ return out;
136
+ }
137
+ return value;
77
138
  }
78
-
79
- // src/config.ts
80
- var langfuseResolvedConfigSymbol = /* @__PURE__ */ Symbol.for(
81
- "@anvia/langfuse.resolvedConfig"
82
- );
83
- function resolveLangfuseConfig(options = {}, fallback) {
139
+ function redactMessage(message, redactStringFn) {
140
+ if (message.role === "system") {
141
+ return { ...message, content: redactStringFn(message.content) };
142
+ }
84
143
  return {
85
- publicKey: resolveStringOption(
86
- options.publicKey,
87
- fallback?.publicKey,
88
- process.env.LANGFUSE_PUBLIC_KEY
89
- ),
90
- secretKey: resolveStringOption(
91
- options.secretKey,
92
- fallback?.secretKey,
93
- process.env.LANGFUSE_SECRET_KEY
94
- ),
95
- baseUrl: resolveStringOption(options.baseUrl, fallback?.baseUrl, process.env.LANGFUSE_BASE_URL) ?? "https://cloud.langfuse.com",
96
- environment: resolveStringOption(
97
- options.environment,
98
- fallback?.environment,
99
- process.env.LANGFUSE_TRACING_ENVIRONMENT
100
- ),
101
- release: resolveStringOption(options.release, fallback?.release, process.env.LANGFUSE_RELEASE),
102
- serviceName: resolveStringOption(
103
- options.serviceName,
104
- fallback?.serviceName,
105
- process.env.LANGFUSE_SERVICE_NAME
106
- ),
107
- timeoutMs: options.timeoutMs ?? fallback?.timeoutMs ?? 3e4
144
+ ...message,
145
+ content: redactMessageContent(message.content, redactStringFn)
108
146
  };
109
147
  }
110
- function getResolvedLangfuseConfig(value) {
111
- if (typeof value !== "object" || value === null) {
112
- return void 0;
113
- }
114
- return value[langfuseResolvedConfigSymbol];
148
+ function redactMessageContent(value, redactStringFn) {
149
+ return redactValue(value, 0, redactStringFn);
115
150
  }
116
- function resolveStringOption(option, fallback, envVar) {
117
- return emptyToUndefined(option) ?? emptyToUndefined(fallback) ?? emptyToUndefined(envVar);
151
+ function isBase64DataUrl(value) {
152
+ return /^data:[^;,]+;base64,/i.test(value);
118
153
  }
119
154
 
120
155
  // src/scoring.ts
@@ -157,7 +192,7 @@ var ScoreQueue = class {
157
192
  timeoutMs;
158
193
  batchSize;
159
194
  flushIntervalMs;
160
- maxRetries;
195
+ maxAttempts;
161
196
  fetchImpl;
162
197
  sleep;
163
198
  setTimer;
@@ -169,7 +204,7 @@ var ScoreQueue = class {
169
204
  this.timeoutMs = options.timeoutMs;
170
205
  this.batchSize = options.batchSize;
171
206
  this.flushIntervalMs = options.flushIntervalMs;
172
- this.maxRetries = options.maxRetries;
207
+ this.maxAttempts = options.maxAttempts;
173
208
  this.fetchImpl = options.fetchImpl ?? fetch;
174
209
  this.sleep = options.sleep ?? defaultSleep;
175
210
  this.setTimer = options.setTimer ?? defaultSetTimer;
@@ -216,10 +251,7 @@ var ScoreQueue = class {
216
251
  async shutdown() {
217
252
  this.closed = true;
218
253
  this.clearScheduledTimer();
219
- try {
220
- await this.flush();
221
- } catch {
222
- }
254
+ await this.flush();
223
255
  }
224
256
  scheduleTimer() {
225
257
  if (this.timer !== null) {
@@ -243,7 +275,7 @@ var ScoreQueue = class {
243
275
  async sendBatch(scores) {
244
276
  const body = scores.map((score) => buildScoreBody(score));
245
277
  let lastError;
246
- for (let attempt = 0; attempt < this.maxRetries; attempt += 1) {
278
+ for (let attempt = 0; attempt < this.maxAttempts; attempt += 1) {
247
279
  try {
248
280
  const response = await this.fetchImpl(`${this.baseUrl}/api/public/scores`, {
249
281
  method: "POST",
@@ -272,12 +304,12 @@ var ScoreQueue = class {
272
304
  }
273
305
  lastError = error;
274
306
  }
275
- if (attempt < this.maxRetries - 1) {
307
+ if (attempt < this.maxAttempts - 1) {
276
308
  await this.sleep(computeBackoff(attempt));
277
309
  }
278
310
  }
279
311
  throw new RetryableLangfuseScoreError(
280
- `Langfuse score batch failed after ${this.maxRetries} attempts`,
312
+ `Langfuse score batch failed after ${this.maxAttempts} attempts`,
281
313
  scores,
282
314
  lastError
283
315
  );
@@ -312,17 +344,252 @@ function buildScoreBody(score) {
312
344
  name: score.name,
313
345
  value: score.value
314
346
  };
315
- if (score.observationId !== void 0) body.observationId = score.observationId;
316
- if (score.dataType !== void 0) body.dataType = score.dataType;
317
- if (score.comment !== void 0) body.comment = score.comment;
318
- if (score.metadata !== void 0) body.metadata = score.metadata;
319
- const configId = score.configId ?? score.scoreConfigId;
320
- if (configId !== void 0) body.configId = configId;
321
- if (score.environment !== void 0) body.environment = score.environment;
322
- if (score.timestamp !== void 0) {
323
- body.timestamp = score.timestamp instanceof Date ? score.timestamp.toISOString() : score.timestamp;
347
+ if (score.observationId !== void 0) body.observationId = score.observationId;
348
+ if (score.dataType !== void 0) body.dataType = score.dataType;
349
+ if (score.comment !== void 0) body.comment = score.comment;
350
+ if (score.metadata !== void 0) body.metadata = score.metadata;
351
+ const configId = score.configId ?? score.scoreConfigId;
352
+ if (configId !== void 0) body.configId = configId;
353
+ if (score.environment !== void 0) body.environment = score.environment;
354
+ if (score.timestamp !== void 0) {
355
+ body.timestamp = score.timestamp instanceof Date ? score.timestamp.toISOString() : score.timestamp;
356
+ }
357
+ return body;
358
+ }
359
+
360
+ // src/tracing.ts
361
+ import { LangfuseSpanProcessor } from "@langfuse/otel";
362
+ import {
363
+ LangfuseAgent,
364
+ LangfuseEvent,
365
+ LangfuseGeneration,
366
+ LangfuseGuardrail,
367
+ LangfuseOtelSpanAttributes,
368
+ LangfuseSpan,
369
+ LangfuseTool
370
+ } from "@langfuse/tracing";
371
+ import {
372
+ ROOT_CONTEXT,
373
+ TraceFlags,
374
+ trace
375
+ } from "@opentelemetry/api";
376
+ import { resourceFromAttributes } from "@opentelemetry/resources";
377
+ import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
378
+ import { SEMRESATTRS_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
379
+
380
+ // src/capture.ts
381
+ var DEFAULT_CAPTURE_MAX_BYTES = 262144;
382
+ var MIN_CAPTURE_MAX_BYTES = 96;
383
+ function validateCaptureMaxBytes(value) {
384
+ const resolved = value ?? DEFAULT_CAPTURE_MAX_BYTES;
385
+ if (!Number.isInteger(resolved) || resolved < MIN_CAPTURE_MAX_BYTES) {
386
+ throw new TypeError(
387
+ `Langfuse captureMaxBytes must be an integer of at least ${MIN_CAPTURE_MAX_BYTES}`
388
+ );
389
+ }
390
+ return resolved;
391
+ }
392
+ function sanitizeTraceValue(value, maxBytes) {
393
+ validateCaptureMaxBytes(maxBytes);
394
+ const sanitized = sanitizeValue(value, 0, /* @__PURE__ */ new WeakSet());
395
+ let serialized;
396
+ try {
397
+ serialized = JSON.stringify(sanitized) ?? String(sanitized);
398
+ } catch {
399
+ return omitted("unserializable");
400
+ }
401
+ const originalBytes = utf8Bytes(serialized);
402
+ if (originalBytes <= maxBytes) {
403
+ return sanitized;
404
+ }
405
+ const preview = boundedPreview(serialized, originalBytes, maxBytes);
406
+ return {
407
+ anviaTraceValue: "truncated",
408
+ originalBytes,
409
+ preview
410
+ };
411
+ }
412
+ function sanitizeValue(value, depth, seen) {
413
+ if (depth > 16) {
414
+ return omitted("depth");
415
+ }
416
+ if (typeof value === "string") {
417
+ if (/^data:[^;,]+;base64,/i.test(value)) {
418
+ return omitted("base64", utf8Bytes(value));
419
+ }
420
+ return value;
421
+ }
422
+ if (value instanceof ArrayBuffer || ArrayBuffer.isView(value)) {
423
+ const byteLength = value.byteLength;
424
+ return omitted("binary", byteLength);
425
+ }
426
+ if (value === null || typeof value !== "object") {
427
+ return value;
428
+ }
429
+ if (seen.has(value)) {
430
+ return omitted("circular");
431
+ }
432
+ seen.add(value);
433
+ if (Array.isArray(value)) {
434
+ const result2 = value.map((entry) => sanitizeValue(entry, depth + 1, seen));
435
+ seen.delete(value);
436
+ return result2;
437
+ }
438
+ const record = value;
439
+ const result = {};
440
+ for (const [key, entry] of Object.entries(record)) {
441
+ if (key === "data" && typeof entry === "string" && (record.type === "base64" || record.type === "image" && typeof record.mediaType === "string")) {
442
+ result[key] = omitted("base64", utf8Bytes(entry));
443
+ continue;
444
+ }
445
+ result[key] = sanitizeValue(entry, depth + 1, seen);
446
+ }
447
+ seen.delete(value);
448
+ return result;
449
+ }
450
+ function omitted(reason, originalBytes) {
451
+ const value = {
452
+ anviaTraceValue: "omitted",
453
+ reason
454
+ };
455
+ if (originalBytes !== void 0) value.originalBytes = originalBytes;
456
+ return value;
457
+ }
458
+ function boundedPreview(value, originalBytes, maxBytes) {
459
+ let low = 0;
460
+ let high = value.length;
461
+ while (low < high) {
462
+ const middle = Math.ceil((low + high) / 2);
463
+ const candidate = {
464
+ anviaTraceValue: "truncated",
465
+ originalBytes,
466
+ preview: value.slice(0, middle)
467
+ };
468
+ if (utf8Bytes(JSON.stringify(candidate)) <= maxBytes) {
469
+ low = middle;
470
+ } else {
471
+ high = middle - 1;
472
+ }
473
+ }
474
+ return value.slice(0, low);
475
+ }
476
+ function utf8Bytes(value) {
477
+ return typeof Buffer === "undefined" ? new TextEncoder().encode(value).byteLength : Buffer.byteLength(value, "utf8");
478
+ }
479
+
480
+ // src/helpers.ts
481
+ function modelInputMessage(message) {
482
+ const { metadata: _metadata, ...result } = message;
483
+ return result;
484
+ }
485
+ function modelInputMessages(messages) {
486
+ return messages.map(modelInputMessage);
487
+ }
488
+ function modelParameters(request) {
489
+ const params = {};
490
+ if (request.temperature !== void 0) params.temperature = request.temperature;
491
+ if (request.maxTokens !== void 0) params.maxTokens = request.maxTokens;
492
+ if (request.toolChoice !== void 0) {
493
+ params.toolChoice = typeof request.toolChoice === "string" ? request.toolChoice : request.toolChoice.name;
494
+ }
495
+ return params;
496
+ }
497
+ function usageDetails(usage) {
498
+ if (usage.details !== void 0 && Object.keys(usage.details).length > 0) {
499
+ return { ...usage.details };
500
+ }
501
+ return {
502
+ input: usage.inputTokens,
503
+ output: usage.outputTokens,
504
+ total: usage.totalTokens
505
+ };
506
+ }
507
+ function usageDetailsFromRecord(usage) {
508
+ if (isRecord(usage.details)) {
509
+ const details = Object.fromEntries(
510
+ Object.entries(usage.details).filter(
511
+ (entry) => typeof entry[1] === "number" && Number.isFinite(entry[1]) && entry[1] >= 0
512
+ )
513
+ );
514
+ if (Object.keys(details).length > 0) {
515
+ return details;
516
+ }
517
+ }
518
+ return {
519
+ input: numberValue(usage.inputTokens) ?? 0,
520
+ output: numberValue(usage.outputTokens) ?? 0,
521
+ total: numberValue(usage.totalTokens) ?? (numberValue(usage.inputTokens) ?? 0) + (numberValue(usage.outputTokens) ?? 0)
522
+ };
523
+ }
524
+ function childMetadata(args, agentId, agentName, childTurn) {
525
+ return {
526
+ source: "agent_tool_event",
527
+ childAgentId: agentId,
528
+ childAgentName: agentName,
529
+ childTurn,
530
+ parentToolName: args.toolName,
531
+ parentInternalCallId: args.internalCallId,
532
+ parentToolCallId: args.toolCallId
533
+ };
534
+ }
535
+ function generationKey(agentId, turn) {
536
+ return `${agentId}:${turn}`;
537
+ }
538
+ function agentLabel(agentId, agentName) {
539
+ return (agentName ?? agentId).replaceAll(/\s+/g, "_");
540
+ }
541
+ function isRecord(value) {
542
+ return typeof value === "object" && value !== null && !Array.isArray(value);
543
+ }
544
+ function numberValue(value) {
545
+ return typeof value === "number" ? value : void 0;
546
+ }
547
+ function emptyToUndefined(value) {
548
+ return value === void 0 || value.length === 0 ? void 0 : value;
549
+ }
550
+ function errorMessage(error) {
551
+ return error instanceof Error ? error.message : String(error);
552
+ }
553
+
554
+ // src/config.ts
555
+ var langfuseResolvedConfigSymbol = /* @__PURE__ */ Symbol.for(
556
+ "@anvia/langfuse.resolvedConfig"
557
+ );
558
+ function resolveLangfuseConfig(options = {}, fallback) {
559
+ return {
560
+ publicKey: resolveStringOption(
561
+ options.publicKey,
562
+ fallback?.publicKey,
563
+ process.env.LANGFUSE_PUBLIC_KEY
564
+ ),
565
+ secretKey: resolveStringOption(
566
+ options.secretKey,
567
+ fallback?.secretKey,
568
+ process.env.LANGFUSE_SECRET_KEY
569
+ ),
570
+ baseUrl: resolveStringOption(options.baseUrl, fallback?.baseUrl, process.env.LANGFUSE_BASE_URL) ?? "https://cloud.langfuse.com",
571
+ environment: resolveStringOption(
572
+ options.environment,
573
+ fallback?.environment,
574
+ process.env.LANGFUSE_TRACING_ENVIRONMENT
575
+ ),
576
+ release: resolveStringOption(options.release, fallback?.release, process.env.LANGFUSE_RELEASE),
577
+ serviceName: resolveStringOption(
578
+ options.serviceName,
579
+ fallback?.serviceName,
580
+ process.env.LANGFUSE_SERVICE_NAME
581
+ ),
582
+ timeoutMs: options.timeoutMs ?? fallback?.timeoutMs ?? 3e4
583
+ };
584
+ }
585
+ function getResolvedLangfuseConfig(value) {
586
+ if (typeof value !== "object" || value === null) {
587
+ return void 0;
324
588
  }
325
- return body;
589
+ return value[langfuseResolvedConfigSymbol];
590
+ }
591
+ function resolveStringOption(option, fallback, envVar) {
592
+ return emptyToUndefined(option) ?? emptyToUndefined(fallback) ?? emptyToUndefined(envVar);
326
593
  }
327
594
 
328
595
  // src/dataset-client.ts
@@ -381,7 +648,8 @@ function createLangfuseDatasetClient(tracing, options = {}) {
381
648
  if (dataset.metadata !== void 0) result.metadata = dataset.metadata;
382
649
  return result;
383
650
  },
384
- async getDataset(name) {
651
+ async getDataset(options2) {
652
+ const { name } = options2;
385
653
  const items = [];
386
654
  let description;
387
655
  let metadata;
@@ -421,7 +689,8 @@ function createLangfuseDatasetClient(tracing, options = {}) {
421
689
  if (metadata !== void 0) dataset.metadata = metadata;
422
690
  return dataset;
423
691
  },
424
- async upsertItems(name, items) {
692
+ async upsertItems(options2) {
693
+ const { name, items } = options2;
425
694
  const url = `${baseUrl}/api/public/datasets/${encodeURIComponent(name)}/items`;
426
695
  await request(url, {
427
696
  method: "POST",
@@ -431,7 +700,7 @@ function createLangfuseDatasetClient(tracing, options = {}) {
431
700
  async runExperiment(opts) {
432
701
  let items = opts.items;
433
702
  if (items === void 0) {
434
- const dataset = await this.getDataset(opts.datasetName);
703
+ const dataset = await this.getDataset({ name: opts.datasetName });
435
704
  items = dataset.items;
436
705
  }
437
706
  if (items === void 0 || items.length === 0) {
@@ -512,7 +781,8 @@ import {
512
781
  } from "@anvia/core/evals";
513
782
  var DEFAULT_TRUNCATE_BYTES = 2048;
514
783
  function createLangfuseEvalReporter(tracing, options = {}) {
515
- const onMissingTrace = options.onMissingTrace ?? (options.strict === true ? "throw" : "ignore");
784
+ const onMissingTrace = options.onMissingTrace ?? "ignore";
785
+ const traceObserver = options.traceObserver ?? "langfuse";
516
786
  const truncateAt = options.truncateInputAt ?? DEFAULT_TRUNCATE_BYTES;
517
787
  const includeMessages = options.includeMessages ?? true;
518
788
  const includeContext = options.includeContext ?? false;
@@ -521,18 +791,20 @@ function createLangfuseEvalReporter(tracing, options = {}) {
521
791
  if (args.outcome.outcome === "invalid" && options.publishInvalid !== true) {
522
792
  return;
523
793
  }
524
- const trace = args.trace ?? resolveEvalTraceRef({
794
+ const trace2 = args.trace ?? resolveEvalTraceRef({
525
795
  output: args.output,
526
796
  input: args.case.input,
527
797
  metadata: args.case.metadata
528
798
  });
529
- if (trace?.traceId === void 0 || trace.traceId.length === 0) {
799
+ if (trace2?.traceId === void 0 || trace2.traceId.length === 0 || trace2.observer !== void 0 && trace2.observer !== traceObserver) {
530
800
  if (onMissingTrace === "throw") {
531
- throw new Error("Langfuse eval reporter requires traceId");
801
+ throw new Error(
802
+ `Langfuse eval reporter requires traceId from observer ${JSON.stringify(traceObserver)}`
803
+ );
532
804
  }
533
805
  if (onMissingTrace === "warn") {
534
806
  console.warn(
535
- "[anvia/langfuse] eval reporter dropped score because no traceId was found",
807
+ "[anvia/langfuse] eval reporter dropped score because no matching trace was found",
536
808
  { caseId: args.case.id, metric: args.metric.name }
537
809
  );
538
810
  }
@@ -551,11 +823,11 @@ function createLangfuseEvalReporter(tracing, options = {}) {
551
823
  });
552
824
  const configId = resolveConfigId(args.metric);
553
825
  const score = {
554
- traceId: trace.traceId,
826
+ traceId: trace2.traceId,
555
827
  name: args.metric.name,
556
828
  value: projection.value
557
829
  };
558
- if (trace.observationId !== void 0) score.observationId = trace.observationId;
830
+ if (trace2.observationId !== void 0) score.observationId = trace2.observationId;
559
831
  if (args.metric.dataType !== void 0) score.dataType = args.metric.dataType;
560
832
  if (configId !== void 0) score.configId = configId;
561
833
  if (projection.explanation !== void 0) score.comment = projection.explanation;
@@ -653,18 +925,20 @@ function readMessages(output) {
653
925
 
654
926
  // src/experiment-runner.ts
655
927
  import { runEvalSuite } from "@anvia/core/evals";
656
- async function runEvalAsExperiment(evalOptions, experimentOptions) {
928
+ async function runLangfuseEvalExperiment(client, options) {
929
+ const evalOptions = options.suite;
930
+ const experimentOptions = options.experiment;
657
931
  const clientOptions = {};
658
932
  if (experimentOptions.pageSize !== void 0) clientOptions.pageSize = experimentOptions.pageSize;
659
933
  if (experimentOptions.timeoutMs !== void 0)
660
934
  clientOptions.timeoutMs = experimentOptions.timeoutMs;
661
- const client = experimentOptions.client ?? createLangfuseDatasetClient(experimentOptions.tracing, clientOptions);
935
+ const datasetClient = client.datasetClient(clientOptions);
662
936
  const suiteOptions = experimentOptions.publishScores === true ? {
663
937
  ...evalOptions,
664
938
  reporters: [
665
939
  ...evalOptions.reporters ?? [],
666
940
  createLangfuseEvalReporter(
667
- experimentOptions.tracing,
941
+ client,
668
942
  experimentOptions.reporterOptions
669
943
  )
670
944
  ]
@@ -709,30 +983,30 @@ async function runEvalAsExperiment(evalOptions, experimentOptions) {
709
983
  };
710
984
  }
711
985
  const output = result.output ?? void 0;
712
- const trace = readTraceFromOutput(result.output);
713
- return { output, trace };
986
+ const trace2 = readTraceFromOutput(result.output);
987
+ return { output, trace: trace2 };
714
988
  }
715
989
  };
716
990
  if (experimentOptions.description !== void 0) {
717
991
  runOptions.description = experimentOptions.description;
718
992
  }
719
993
  if (experimentOptions.metadata !== void 0) runOptions.metadata = experimentOptions.metadata;
720
- const datasetRun = await client.runExperiment(runOptions);
994
+ const datasetRun = await datasetClient.runExperiment(runOptions);
721
995
  return { suite, datasetRun };
722
996
  }
723
997
  function readTraceFromOutput(output) {
724
998
  if (typeof output !== "object" || output === null || !("trace" in output)) {
725
999
  return void 0;
726
1000
  }
727
- const trace = output.trace;
728
- if (typeof trace !== "object" || trace === null) {
1001
+ const trace2 = output.trace;
1002
+ if (typeof trace2 !== "object" || trace2 === null) {
729
1003
  return void 0;
730
1004
  }
731
- const traceId = trace.traceId;
1005
+ const traceId = trace2.traceId;
732
1006
  if (typeof traceId !== "string") {
733
1007
  return void 0;
734
1008
  }
735
- const observationId = trace.observationId;
1009
+ const observationId = trace2.observationId;
736
1010
  if (typeof observationId === "string") {
737
1011
  return { traceId, observationId };
738
1012
  }
@@ -765,7 +1039,8 @@ function createLangfusePromptClient(tracing, options = {}) {
765
1039
  }
766
1040
  return await response.json();
767
1041
  }
768
- async function getPrompt(name, opts = {}) {
1042
+ async function getPrompt(options2) {
1043
+ const { name, ...opts } = options2;
769
1044
  const key = `${name}::${opts.version ?? ""}::${opts.label ?? ""}`;
770
1045
  const ttl = opts.cacheTtlMs ?? defaultTtl;
771
1046
  if (opts.refresh !== true) {
@@ -794,352 +1069,113 @@ function createLangfusePromptClient(tracing, options = {}) {
794
1069
  cache.set(key, { prompt, expiresAt: Date.now() + ttl });
795
1070
  return prompt;
796
1071
  }
797
- function getPromptText(name, opts) {
798
- return getPrompt(name, opts).then((prompt) => {
1072
+ function getPromptText(options2) {
1073
+ return getPrompt(options2).then((prompt) => {
799
1074
  if (typeof prompt.prompt !== "string") {
800
- throw new Error(`Prompt ${name} is a chat prompt; expected text`);
1075
+ throw new Error(`Prompt ${options2.name} is a chat prompt; expected text`);
801
1076
  }
802
1077
  return prompt.prompt;
803
1078
  });
804
1079
  }
805
- function getPromptChat(name, opts) {
806
- return getPrompt(name, opts).then((prompt) => {
1080
+ function getPromptChat(options2) {
1081
+ return getPrompt(options2).then((prompt) => {
807
1082
  if (typeof prompt.prompt === "string") {
808
- throw new Error(`Prompt ${name} is a text prompt; expected chat`);
1083
+ throw new Error(`Prompt ${options2.name} is a text prompt; expected chat`);
809
1084
  }
810
1085
  return prompt.prompt;
811
1086
  });
812
1087
  }
813
- function refresh() {
814
- cache.clear();
815
- }
816
- return { getPrompt, getPromptText, getPromptChat, refresh };
817
- }
818
- function normalizePrompt(raw, type) {
819
- if (type === "text") {
820
- if (typeof raw === "string") return raw;
821
- throw new Error("Expected text prompt to be a string");
822
- }
823
- if (!Array.isArray(raw)) {
824
- throw new Error("Expected chat prompt to be an array of messages");
825
- }
826
- return raw.map((entry) => {
827
- if (typeof entry !== "object" || entry === null) {
828
- throw new Error("Expected chat message to be an object");
829
- }
830
- const role = entry.role;
831
- const content = entry.content;
832
- if (typeof content !== "string") {
833
- throw new Error("Expected chat message content to be a string");
834
- }
835
- if (role !== "system" && role !== "user" && role !== "assistant" && role !== "tool") {
836
- throw new Error(`Unexpected chat message role: ${String(role)}`);
837
- }
838
- return { role, content };
839
- });
840
- }
841
- function buildAuthHeader2(publicKey, secretKey) {
842
- if (publicKey === void 0 || secretKey === void 0) {
843
- return {};
844
- }
845
- const encoded = Buffer.from(`${publicKey}:${secretKey}`).toString("base64");
846
- return { Authorization: `Basic ${encoded}` };
847
- }
848
- async function readErrorBody2(response) {
849
- try {
850
- return await response.text();
851
- } catch {
852
- return "<unreadable>";
853
- }
854
- }
855
-
856
- // src/redaction.ts
857
- var DEFAULT_REPLACEMENT = "[REDACTED]";
858
- var MAX_DEPTH = 16;
859
- function createPiiRedactor(options = {}) {
860
- const patterns = options.patterns ?? DEFAULT_PATTERNS;
861
- const replacement = options.replacement ?? DEFAULT_REPLACEMENT;
862
- const compiled = patterns.map((p) => ({
863
- name: p.name,
864
- regex: cloneRegex(p.regex, "g")
865
- }));
866
- const patternNamesList = patterns.map((p) => p.name);
867
- function redactString(input) {
868
- if (typeof input !== "string") return input;
869
- let out = input;
870
- for (const { name, regex } of compiled) {
871
- out = applyPattern(out, name, regex, replacement);
872
- }
873
- return out;
874
- }
875
- function redactObject(input) {
876
- return redactValue(input, 0, redactString);
877
- }
878
- function redactMessages(input) {
879
- return input.map((message) => redactMessage(message, redactString));
880
- }
881
- function patternNames() {
882
- return patternNamesList;
883
- }
884
- return { redactString, redactObject, redactMessages, patternNames };
885
- }
886
- var DEFAULT_PATTERNS = [
887
- { name: "email", regex: /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g },
888
- { name: "creditCard", regex: /\b(?:\d[ -]?){13,19}\b/g },
889
- { name: "ipv4", regex: /\b(?:\d{1,3}\.){3}\d{1,3}\b/g },
890
- {
891
- name: "phone",
892
- regex: /(?<!\d)(?:\+\d{1,3}[\s.-]?)?(?:\(\d{2,4}\)[\s.-]?)?\d{3,4}[\s.-]?\d{3,4}(?:[\s.-]?\d{3,4})?(?!\d)/g
893
- },
894
- { name: "jwt", regex: /\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g },
895
- {
896
- name: "apiKey",
897
- regex: /\b(?:sk|pk|api|key|token)[-_][A-Za-z0-9]{16,}\b/gi
898
- }
899
- ];
900
- function applyPattern(input, name, regex, replacement) {
901
- if (name === "creditCard") {
902
- return redactCreditCards(input, replacement);
903
- }
904
- return input.replace(regex, replacement);
905
- }
906
- function redactCreditCards(input, replacement) {
907
- let out = "";
908
- let i = 0;
909
- while (i < input.length) {
910
- const ch = input.charAt(i);
911
- if (/\d/.test(ch)) {
912
- const { length, valid } = longestLuhnChunk(input.slice(i));
913
- if (valid) {
914
- out += replacement;
915
- i += length;
916
- continue;
917
- }
918
- }
919
- out += ch;
920
- i += 1;
921
- }
922
- return out;
923
- }
924
- function longestLuhnChunk(s) {
925
- let length = 0;
926
- let bestValid = 0;
927
- while (length < s.length && length < 40) {
928
- const ch = s.charAt(length);
929
- if (!/\d/.test(ch) && ch !== "-") break;
930
- length += 1;
931
- const candidate = s.slice(0, length).replace(/\D/g, "");
932
- if (candidate.length >= 13 && candidate.length <= 19) {
933
- if (startsWithKnownPrefix(candidate) && passesLuhn(candidate)) {
934
- bestValid = length;
935
- }
936
- }
937
- }
938
- return { length: bestValid, valid: bestValid > 0 };
939
- }
940
- function startsWithKnownPrefix(digits) {
941
- if (digits.startsWith("4")) return true;
942
- const two = digits.slice(0, 2);
943
- if (two === "51" || two === "52" || two === "53" || two === "54" || two === "55") return true;
944
- const four = digits.slice(0, 4);
945
- if (four === "2221" || four === "2720") return true;
946
- const twoAgain = digits.slice(0, 2);
947
- if (twoAgain === "34" || twoAgain === "37") return true;
948
- if (four === "6011" || twoAgain === "65") return true;
949
- return digits.startsWith("35");
950
- }
951
- function passesLuhn(digits) {
952
- if (!/^\d+$/.test(digits)) return false;
953
- let sum = 0;
954
- let alt = false;
955
- for (let i = digits.length - 1; i >= 0; i -= 1) {
956
- const raw = digits.charCodeAt(i) - 48;
957
- let value = raw;
958
- if (alt) {
959
- value *= 2;
960
- if (value > 9) value -= 9;
961
- }
962
- sum += value;
963
- alt = !alt;
964
- }
965
- return sum % 10 === 0;
966
- }
967
- function cloneRegex(source, flags) {
968
- return new RegExp(source.source, flags + source.flags.replace(/g/g, ""));
969
- }
970
- function redactValue(value, depth, redactStringFn) {
971
- if (depth > MAX_DEPTH) return value;
972
- if (typeof value === "string") {
973
- return isBase64DataUrl(value) ? value : redactStringFn(value);
974
- }
975
- if (Array.isArray(value))
976
- return value.map((entry) => redactValue(entry, depth + 1, redactStringFn));
977
- if (value !== null && typeof value === "object") {
978
- if (value instanceof ArrayBuffer || ArrayBuffer.isView(value)) {
979
- return value;
980
- }
981
- const record = value;
982
- const out = {};
983
- for (const [key, entry] of Object.entries(record)) {
984
- if (key === "data" && typeof entry === "string" && (record.type === "base64" || record.type === "image" || record.type === "encrypted" || record.type === "redacted")) {
985
- out[key] = entry;
986
- } else {
987
- out[key] = redactValue(entry, depth + 1, redactStringFn);
988
- }
989
- }
990
- return out;
991
- }
992
- return value;
993
- }
994
- function redactMessage(message, redactStringFn) {
995
- if (message.role === "system") {
996
- return { ...message, content: redactStringFn(message.content) };
997
- }
998
- return {
999
- ...message,
1000
- content: redactMessageContent(message.content, redactStringFn)
1001
- };
1002
- }
1003
- function redactMessageContent(value, redactStringFn) {
1004
- return redactValue(value, 0, redactStringFn);
1088
+ function refresh() {
1089
+ cache.clear();
1090
+ }
1091
+ return { getPrompt, getPromptText, getPromptChat, refresh };
1005
1092
  }
1006
- function isBase64DataUrl(value) {
1007
- return /^data:[^;,]+;base64,/i.test(value);
1093
+ function normalizePrompt(raw, type) {
1094
+ if (type === "text") {
1095
+ if (typeof raw === "string") return raw;
1096
+ throw new Error("Expected text prompt to be a string");
1097
+ }
1098
+ if (!Array.isArray(raw)) {
1099
+ throw new Error("Expected chat prompt to be an array of messages");
1100
+ }
1101
+ return raw.map((entry) => {
1102
+ if (typeof entry !== "object" || entry === null) {
1103
+ throw new Error("Expected chat message to be an object");
1104
+ }
1105
+ const role = entry.role;
1106
+ const content = entry.content;
1107
+ if (typeof content !== "string") {
1108
+ throw new Error("Expected chat message content to be a string");
1109
+ }
1110
+ if (role !== "system" && role !== "user" && role !== "assistant" && role !== "tool") {
1111
+ throw new Error(`Unexpected chat message role: ${String(role)}`);
1112
+ }
1113
+ return { role, content };
1114
+ });
1008
1115
  }
1009
-
1010
- // src/tracing.ts
1011
- import { textFromAssistantContent } from "@anvia/core/completion";
1012
- import { LangfuseSpanProcessor } from "@langfuse/otel";
1013
- import {
1014
- LangfuseOtelSpanAttributes,
1015
- startObservation
1016
- } from "@langfuse/tracing";
1017
- import { resourceFromAttributes } from "@opentelemetry/resources";
1018
- import { NodeSDK } from "@opentelemetry/sdk-node";
1019
- import { SEMRESATTRS_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
1020
-
1021
- // src/capture.ts
1022
- var DEFAULT_CAPTURE_MAX_BYTES = 262144;
1023
- var MIN_CAPTURE_MAX_BYTES = 96;
1024
- function validateCaptureMaxBytes(value) {
1025
- const resolved = value ?? DEFAULT_CAPTURE_MAX_BYTES;
1026
- if (!Number.isInteger(resolved) || resolved < MIN_CAPTURE_MAX_BYTES) {
1027
- throw new TypeError(
1028
- `Langfuse captureMaxBytes must be an integer of at least ${MIN_CAPTURE_MAX_BYTES}`
1029
- );
1116
+ function buildAuthHeader2(publicKey, secretKey) {
1117
+ if (publicKey === void 0 || secretKey === void 0) {
1118
+ return {};
1030
1119
  }
1031
- return resolved;
1120
+ const encoded = Buffer.from(`${publicKey}:${secretKey}`).toString("base64");
1121
+ return { Authorization: `Basic ${encoded}` };
1032
1122
  }
1033
- function sanitizeTraceValue(value, maxBytes) {
1034
- validateCaptureMaxBytes(maxBytes);
1035
- const sanitized = sanitizeValue(value, 0, /* @__PURE__ */ new WeakSet());
1036
- let serialized;
1123
+ async function readErrorBody2(response) {
1037
1124
  try {
1038
- serialized = JSON.stringify(sanitized) ?? String(sanitized);
1125
+ return await response.text();
1039
1126
  } catch {
1040
- return omitted("unserializable");
1041
- }
1042
- const originalBytes = utf8Bytes(serialized);
1043
- if (originalBytes <= maxBytes) {
1044
- return sanitized;
1127
+ return "<unreadable>";
1045
1128
  }
1046
- const preview = boundedPreview(serialized, originalBytes, maxBytes);
1047
- return {
1048
- anviaTraceValue: "truncated",
1049
- originalBytes,
1050
- preview
1051
- };
1052
1129
  }
1053
- function sanitizeValue(value, depth, seen) {
1054
- if (depth > 16) {
1055
- return omitted("depth");
1056
- }
1057
- if (typeof value === "string") {
1058
- if (/^data:[^;,]+;base64,/i.test(value)) {
1059
- return omitted("base64", utf8Bytes(value));
1060
- }
1061
- return value;
1130
+
1131
+ // src/tracing.ts
1132
+ var LangfuseObservationFactory = class {
1133
+ constructor(tracer) {
1134
+ this.tracer = tracer;
1062
1135
  }
1063
- if (value instanceof ArrayBuffer || ArrayBuffer.isView(value)) {
1064
- const byteLength = value.byteLength;
1065
- return omitted("binary", byteLength);
1136
+ tracer;
1137
+ agent(name, attributes, parent) {
1138
+ return new LangfuseAgent({ otelSpan: this.startSpan(name, parent), attributes });
1066
1139
  }
1067
- if (value === null || typeof value !== "object") {
1068
- return value;
1140
+ span(name, attributes, parent) {
1141
+ return new LangfuseSpan({ otelSpan: this.startSpan(name, parent), attributes });
1069
1142
  }
1070
- if (seen.has(value)) {
1071
- return omitted("circular");
1143
+ generation(name, attributes, parent) {
1144
+ return new LangfuseGeneration({ otelSpan: this.startSpan(name, parent), attributes });
1072
1145
  }
1073
- seen.add(value);
1074
- if (Array.isArray(value)) {
1075
- const result2 = value.map((entry) => sanitizeValue(entry, depth + 1, seen));
1076
- seen.delete(value);
1077
- return result2;
1146
+ tool(name, attributes, parent) {
1147
+ return new LangfuseTool({ otelSpan: this.startSpan(name, parent), attributes });
1078
1148
  }
1079
- const record = value;
1080
- const result = {};
1081
- for (const [key, entry] of Object.entries(record)) {
1082
- if (key === "data" && typeof entry === "string" && (record.type === "base64" || record.type === "image" && typeof record.mediaType === "string")) {
1083
- result[key] = omitted("base64", utf8Bytes(entry));
1084
- continue;
1085
- }
1086
- result[key] = sanitizeValue(entry, depth + 1, seen);
1149
+ guardrail(name, attributes, parent) {
1150
+ return new LangfuseGuardrail({ otelSpan: this.startSpan(name, parent), attributes });
1087
1151
  }
1088
- seen.delete(value);
1089
- return result;
1090
- }
1091
- function omitted(reason, originalBytes) {
1092
- return {
1093
- anviaTraceValue: "omitted",
1094
- reason,
1095
- ...originalBytes === void 0 ? {} : { originalBytes }
1096
- };
1097
- }
1098
- function boundedPreview(value, originalBytes, maxBytes) {
1099
- let low = 0;
1100
- let high = value.length;
1101
- while (low < high) {
1102
- const middle = Math.ceil((low + high) / 2);
1103
- const candidate = {
1104
- anviaTraceValue: "truncated",
1105
- originalBytes,
1106
- preview: value.slice(0, middle)
1107
- };
1108
- if (utf8Bytes(JSON.stringify(candidate)) <= maxBytes) {
1109
- low = middle;
1110
- } else {
1111
- high = middle - 1;
1112
- }
1152
+ event(name, attributes, parent, timestamp) {
1153
+ const endTime = timestamp ?? /* @__PURE__ */ new Date();
1154
+ return new LangfuseEvent({
1155
+ otelSpan: this.startSpan(name, parent, timestamp),
1156
+ attributes,
1157
+ timestamp: endTime
1158
+ });
1113
1159
  }
1114
- return value.slice(0, low);
1115
- }
1116
- function utf8Bytes(value) {
1117
- return typeof Buffer === "undefined" ? new TextEncoder().encode(value).byteLength : Buffer.byteLength(value, "utf8");
1118
- }
1119
-
1120
- // src/tracing.ts
1121
- var langfuse = {
1122
- create(options = {}) {
1123
- return new LangfuseAgentObserver(options);
1160
+ startSpan(name, parent, startTime) {
1161
+ const parentContext = parent === void 0 ? ROOT_CONTEXT : "otelSpan" in parent ? trace.setSpan(ROOT_CONTEXT, parent.otelSpan) : trace.setSpanContext(ROOT_CONTEXT, parent);
1162
+ return this.tracer.startSpan(name, startTime === void 0 ? {} : { startTime }, parentContext);
1124
1163
  }
1125
1164
  };
1126
- var LangfuseAgentObserver = class {
1127
- processor;
1128
- sdk;
1165
+ var LangfuseClient = class {
1129
1166
  [langfuseResolvedConfigSymbol];
1130
1167
  publicKey;
1131
1168
  secretKey;
1132
1169
  baseUrl;
1133
1170
  serviceName;
1134
1171
  timeoutMs;
1135
- queue;
1136
- currentHandle;
1137
- redactor;
1138
- redactInputs;
1139
- redactOutputs;
1140
- captureMode;
1141
- captureMaxBytes;
1142
- constructor(options) {
1172
+ options;
1173
+ resource;
1174
+ initialization;
1175
+ closePromise;
1176
+ closed = false;
1177
+ constructor(options = {}) {
1178
+ this.options = options;
1143
1179
  const resolvedConfig = resolveLangfuseConfig(options);
1144
1180
  this[langfuseResolvedConfigSymbol] = resolvedConfig;
1145
1181
  this.publicKey = resolvedConfig.publicKey;
@@ -1147,50 +1183,82 @@ var LangfuseAgentObserver = class {
1147
1183
  this.baseUrl = resolvedConfig.baseUrl;
1148
1184
  this.serviceName = resolvedConfig.serviceName;
1149
1185
  this.timeoutMs = resolvedConfig.timeoutMs;
1150
- const processorOptions = {
1151
- baseUrl: this.baseUrl
1186
+ }
1187
+ observer(options = {}) {
1188
+ this.assertOpen();
1189
+ return new LangfuseAgentObserver(this, resolveLangfuseCapture(options));
1190
+ }
1191
+ evalReporter(options = {}) {
1192
+ this.assertOpen();
1193
+ const reporter = createLangfuseEvalReporter(this, options);
1194
+ return {
1195
+ report: (args) => {
1196
+ this.assertOpen();
1197
+ return reporter.report(args);
1198
+ }
1152
1199
  };
1153
- if (this.publicKey !== void 0) processorOptions.publicKey = this.publicKey;
1154
- if (this.secretKey !== void 0) processorOptions.secretKey = this.secretKey;
1155
- if (resolvedConfig.environment !== void 0) {
1156
- processorOptions.environment = resolvedConfig.environment;
1157
- }
1158
- if (resolvedConfig.release !== void 0) processorOptions.release = resolvedConfig.release;
1159
- this.processor = new LangfuseSpanProcessor(processorOptions);
1160
- const sdkOptions = {
1161
- spanProcessors: [this.processor]
1200
+ }
1201
+ promptClient(options = {}) {
1202
+ this.assertOpen();
1203
+ const prompts = createLangfusePromptClient(this, options);
1204
+ return {
1205
+ getPrompt: (getOptions) => {
1206
+ this.assertOpen();
1207
+ return prompts.getPrompt(getOptions);
1208
+ },
1209
+ getPromptText: (getOptions) => {
1210
+ this.assertOpen();
1211
+ return prompts.getPromptText(getOptions);
1212
+ },
1213
+ getPromptChat: (getOptions) => {
1214
+ this.assertOpen();
1215
+ return prompts.getPromptChat(getOptions);
1216
+ },
1217
+ refresh: () => {
1218
+ this.assertOpen();
1219
+ prompts.refresh();
1220
+ }
1162
1221
  };
1163
- if (this.serviceName !== void 0) {
1164
- sdkOptions.resource = resourceFromAttributes({
1165
- [SEMRESATTRS_SERVICE_NAME]: this.serviceName
1166
- });
1167
- }
1168
- this.sdk = new NodeSDK(sdkOptions);
1169
- this.sdk.start();
1170
- const batchSize = options.scoreBatchSize ?? 0;
1171
- this.queue = batchSize > 0 && this.publicKey !== void 0 && this.secretKey !== void 0 ? new ScoreQueue({
1172
- baseUrl: this.baseUrl,
1173
- publicKey: this.publicKey,
1174
- secretKey: this.secretKey,
1175
- timeoutMs: this.timeoutMs,
1176
- batchSize,
1177
- flushIntervalMs: options.scoreFlushIntervalMs ?? 250,
1178
- maxRetries: options.scoreMaxRetries ?? 3
1179
- }) : null;
1180
- this.redactInputs = options.redactInputs;
1181
- this.redactOutputs = options.redactOutputs;
1182
- this.captureMode = options.captureMode ?? "safe";
1183
- this.captureMaxBytes = validateCaptureMaxBytes(options.captureMaxBytes);
1184
- this.redactor = options.redactInputs !== void 0 || options.redactOutputs !== void 0 ? createPiiRedactor(options.redaction) : void 0;
1185
- }
1186
- async startRun(args) {
1222
+ }
1223
+ datasetClient(options = {}) {
1224
+ this.assertOpen();
1225
+ const datasets = createLangfuseDatasetClient(this, options);
1226
+ const client = this;
1227
+ return {
1228
+ createDataset(dataset) {
1229
+ client.assertOpen();
1230
+ return datasets.createDataset(dataset);
1231
+ },
1232
+ getDataset(getOptions) {
1233
+ client.assertOpen();
1234
+ return datasets.getDataset(getOptions);
1235
+ },
1236
+ upsertItems(upsertOptions) {
1237
+ client.assertOpen();
1238
+ return datasets.upsertItems(upsertOptions);
1239
+ },
1240
+ runExperiment(experimentOptions) {
1241
+ client.assertOpen();
1242
+ return datasets.runExperiment(experimentOptions);
1243
+ }
1244
+ };
1245
+ }
1246
+ runEvalExperiment(options) {
1247
+ this.assertOpen();
1248
+ return runLangfuseEvalExperiment(this, options);
1249
+ }
1250
+ async startObservedRun(args, capture) {
1251
+ const resource = await this.resources();
1187
1252
  const traceId = args.trace?.traceId;
1188
- const capturedInput = this.captureInput({
1189
- instructions: args.instructions,
1190
- prompt: args.prompt,
1191
- history: args.history
1192
- });
1193
- const capturedTraceMetadata = this.captureInput(args.trace?.metadata ?? {});
1253
+ const capturedInput = captureInput(
1254
+ {
1255
+ instructions: args.instructions,
1256
+ prompt: args.prompt,
1257
+ history: args.history
1258
+ },
1259
+ capture
1260
+ );
1261
+ const capturedTraceMetadata = captureInput(args.trace?.metadata ?? {}, capture);
1194
1262
  const metadata = {
1195
1263
  agentName: args.agentName,
1196
1264
  agentDescription: args.agentDescription,
@@ -1206,69 +1274,41 @@ var LangfuseAgentObserver = class {
1206
1274
  input: capturedInput,
1207
1275
  metadata
1208
1276
  };
1209
- if (args.trace?.version !== void 0) {
1210
- rootAttributes.version = args.trace.version;
1211
- }
1212
- const root = startObservation(
1277
+ if (args.trace?.version !== void 0) rootAttributes.version = args.trace.version;
1278
+ const root = resource.observations.agent(
1213
1279
  args.agentName ?? "agent.run",
1214
1280
  rootAttributes,
1215
- traceId === void 0 ? { asType: "agent" } : {
1216
- asType: "agent",
1217
- parentSpanContext: {
1218
- traceId,
1219
- spanId: "0000000000000001",
1220
- traceFlags: 1
1221
- }
1281
+ traceId === void 0 ? void 0 : {
1282
+ traceId,
1283
+ spanId: "0000000000000001",
1284
+ traceFlags: TraceFlags.SAMPLED
1222
1285
  }
1223
1286
  );
1224
1287
  applyTraceAttributes(root, args, capturedTraceMetadata);
1225
- const promptRef = resolvePromptRef(args);
1226
- const runObserver = new LangfuseRunObserver(
1288
+ return new LangfuseRunObserver(
1227
1289
  root,
1228
- {
1229
- traceId: root.traceId,
1230
- observationId: root.id
1231
- },
1232
- promptRef,
1233
- {
1234
- redactor: this.redactor,
1235
- redactInputs: this.redactInputs,
1236
- redactOutputs: this.redactOutputs,
1237
- captureMode: this.captureMode,
1238
- captureMaxBytes: this.captureMaxBytes
1239
- }
1290
+ resource.observations,
1291
+ { traceId: root.traceId, observationId: root.id },
1292
+ resolvePromptRef(args),
1293
+ capture
1240
1294
  );
1241
- this.currentHandle = runObserver.getHandle();
1242
- runObserver.setCurrentHandle = (handle) => {
1243
- this.currentHandle = handle;
1244
- };
1245
- runObserver.clearCurrentHandle = () => {
1246
- if (this.currentHandle === runObserver.getHandle()) {
1247
- this.currentHandle = void 0;
1248
- }
1249
- };
1250
- return runObserver;
1251
1295
  }
1252
1296
  async flush() {
1253
- await this.queue?.flush();
1254
- await this.processor.forceFlush();
1297
+ this.assertOpen();
1298
+ const resource = this.resource ?? (this.initialization === void 0 ? void 0 : await this.initialization);
1299
+ if (resource === void 0) return;
1300
+ await resource.queue?.flush();
1301
+ await resource.processor.forceFlush();
1255
1302
  }
1256
- async shutdown() {
1257
- await this.queue?.shutdown();
1258
- await this.sdk.shutdown();
1303
+ close() {
1304
+ this.closePromise ??= this.closeResources();
1305
+ return this.closePromise;
1259
1306
  }
1260
- async flushScores() {
1261
- await this.queue?.flush();
1307
+ async [Symbol.asyncDispose]() {
1308
+ await this.close();
1262
1309
  }
1263
1310
  scoreQueueDepth() {
1264
- return this.queue?.depth() ?? 0;
1265
- }
1266
- getCurrentTrace() {
1267
- return this.currentHandle;
1268
- }
1269
- captureInput(value) {
1270
- const redacted = this.redactor === void 0 || this.redactInputs === void 0 ? value : applyRedaction(this.redactor, value, this.redactInputs);
1271
- return sanitizeTraceValue(redacted, this.captureMaxBytes);
1311
+ return this.resource?.queue?.depth() ?? 0;
1272
1312
  }
1273
1313
  async score(args) {
1274
1314
  if (args.traceId === void 0 || args.traceId.length === 0) {
@@ -1278,12 +1318,100 @@ var LangfuseAgentObserver = class {
1278
1318
  throw new Error("Langfuse score requires publicKey and secretKey");
1279
1319
  }
1280
1320
  assertScoreValue(args.value, args.dataType);
1281
- if (this.queue !== null) {
1282
- this.queue.enqueue(args);
1321
+ const resource = await this.resources();
1322
+ if (resource.queue !== null) {
1323
+ resource.queue.enqueue(args);
1283
1324
  return;
1284
1325
  }
1285
1326
  await this.sendScore(args);
1286
1327
  }
1328
+ resources() {
1329
+ this.assertOpen();
1330
+ if (this.resource !== void 0) return Promise.resolve(this.resource);
1331
+ this.initialization ??= this.createResources().then((resource) => {
1332
+ this.resource = resource;
1333
+ return resource;
1334
+ }).catch((error) => {
1335
+ this.initialization = void 0;
1336
+ throw error;
1337
+ });
1338
+ return this.initialization;
1339
+ }
1340
+ async createResources() {
1341
+ const resolvedConfig = this[langfuseResolvedConfigSymbol];
1342
+ const processorOptions = {
1343
+ baseUrl: this.baseUrl
1344
+ };
1345
+ if (this.publicKey !== void 0) processorOptions.publicKey = this.publicKey;
1346
+ if (this.secretKey !== void 0) processorOptions.secretKey = this.secretKey;
1347
+ if (resolvedConfig.environment !== void 0) {
1348
+ processorOptions.environment = resolvedConfig.environment;
1349
+ }
1350
+ if (resolvedConfig.release !== void 0) processorOptions.release = resolvedConfig.release;
1351
+ const processor = new LangfuseSpanProcessor(processorOptions);
1352
+ const providerOptions = {
1353
+ spanProcessors: [processor]
1354
+ };
1355
+ if (this.serviceName !== void 0) {
1356
+ providerOptions.resource = resourceFromAttributes({
1357
+ [SEMRESATTRS_SERVICE_NAME]: this.serviceName
1358
+ });
1359
+ }
1360
+ let provider;
1361
+ try {
1362
+ provider = new NodeTracerProvider(providerOptions);
1363
+ } catch (error) {
1364
+ await processor.shutdown().catch(() => void 0);
1365
+ throw error;
1366
+ }
1367
+ try {
1368
+ const batchSize = this.options.scores?.batchSize ?? 0;
1369
+ const queue = batchSize > 0 && this.publicKey !== void 0 && this.secretKey !== void 0 ? new ScoreQueue({
1370
+ baseUrl: this.baseUrl,
1371
+ publicKey: this.publicKey,
1372
+ secretKey: this.secretKey,
1373
+ timeoutMs: this.timeoutMs,
1374
+ batchSize,
1375
+ flushIntervalMs: this.options.scores?.flushIntervalMs ?? 250,
1376
+ maxAttempts: scoreMaxAttempts(this.options.scores?.retries)
1377
+ }) : null;
1378
+ return {
1379
+ processor,
1380
+ provider,
1381
+ observations: new LangfuseObservationFactory(
1382
+ provider.getTracer("@anvia/langfuse", "1.0.0")
1383
+ ),
1384
+ queue
1385
+ };
1386
+ } catch (error) {
1387
+ await provider.shutdown().catch(() => void 0);
1388
+ throw error;
1389
+ }
1390
+ }
1391
+ async closeResources() {
1392
+ this.closed = true;
1393
+ const pending = this.resource === void 0 ? this.initialization : Promise.resolve(this.resource);
1394
+ if (pending === void 0) return;
1395
+ let resource;
1396
+ try {
1397
+ resource = await pending;
1398
+ } catch {
1399
+ return;
1400
+ }
1401
+ const settled = await Promise.allSettled([
1402
+ resource.queue?.shutdown() ?? Promise.resolve(),
1403
+ resource.provider.shutdown()
1404
+ ]);
1405
+ const failures = settled.flatMap(
1406
+ (result) => result.status === "rejected" ? [result.reason] : []
1407
+ );
1408
+ if (failures.length > 0) {
1409
+ throw new AggregateError(failures, "Failed to close LangfuseClient.");
1410
+ }
1411
+ }
1412
+ assertOpen() {
1413
+ if (this.closed) throw new Error("LangfuseClient is closed.");
1414
+ }
1287
1415
  async sendScore(args) {
1288
1416
  const body = buildScoreBody2(args);
1289
1417
  const response = await fetch(`${this.baseUrl}/api/public/scores`, {
@@ -1302,6 +1430,37 @@ var LangfuseAgentObserver = class {
1302
1430
  }
1303
1431
  }
1304
1432
  };
1433
+ var LangfuseAgentObserver = class {
1434
+ constructor(client, capture) {
1435
+ this.client = client;
1436
+ this.capture = capture;
1437
+ }
1438
+ client;
1439
+ capture;
1440
+ startRun(args) {
1441
+ return this.client.startObservedRun(args, this.capture);
1442
+ }
1443
+ };
1444
+ function resolveLangfuseCapture(options) {
1445
+ return {
1446
+ redactor: options.redactInputs !== void 0 || options.redactOutputs !== void 0 ? createPiiRedactor(options.redaction) : void 0,
1447
+ redactInputs: options.redactInputs,
1448
+ redactOutputs: options.redactOutputs,
1449
+ captureMode: options.captureMode ?? "safe",
1450
+ captureMaxBytes: validateCaptureMaxBytes(options.captureMaxBytes)
1451
+ };
1452
+ }
1453
+ function captureInput(value, capture) {
1454
+ const redacted = capture.redactor === void 0 || capture.redactInputs === void 0 ? value : applyRedaction(capture.redactor, value, capture.redactInputs);
1455
+ return sanitizeTraceValue(redacted, capture.captureMaxBytes);
1456
+ }
1457
+ function scoreMaxAttempts(retries) {
1458
+ if (retries === void 0) return 3;
1459
+ if (!Number.isSafeInteger(retries.maxAttempts) || retries.maxAttempts < 1) {
1460
+ throw new TypeError("Langfuse score retries.maxAttempts must be a positive integer.");
1461
+ }
1462
+ return retries.maxAttempts;
1463
+ }
1305
1464
  function assertScoreValue(value, dataType) {
1306
1465
  if (dataType === "NUMERIC") {
1307
1466
  if (typeof value !== "number") {
@@ -1352,7 +1511,7 @@ function applyTraceAttributes(root, args, capturedMetadata) {
1352
1511
  root.otelSpan.setAttribute(LangfuseOtelSpanAttributes.TRACE_SESSION_ID, args.trace.sessionId);
1353
1512
  }
1354
1513
  if (args.trace?.tags !== void 0) {
1355
- root.otelSpan.setAttribute(LangfuseOtelSpanAttributes.TRACE_TAGS, args.trace.tags);
1514
+ root.otelSpan.setAttribute(LangfuseOtelSpanAttributes.TRACE_TAGS, [...args.trace.tags]);
1356
1515
  }
1357
1516
  for (const [key, value] of Object.entries(
1358
1517
  isRecord(capturedMetadata) ? capturedMetadata : { value: capturedMetadata }
@@ -1441,20 +1600,15 @@ function asMetadata(value) {
1441
1600
  return isRecord(value) ? value : { value };
1442
1601
  }
1443
1602
  function eventStartTime(value) {
1444
- if (value instanceof Date) {
1445
- return Number.isNaN(value.getTime()) ? void 0 : value;
1446
- }
1447
- if (typeof value !== "string") {
1448
- return void 0;
1449
- }
1603
+ if (value === void 0) return void 0;
1450
1604
  const parsed = new Date(value);
1451
1605
  return Number.isNaN(parsed.getTime()) ? void 0 : parsed;
1452
1606
  }
1453
1607
  var LangfuseRunObserver = class {
1454
- constructor(root, trace, promptRef, redaction) {
1608
+ constructor(root, observations, trace2, promptRef, redaction) {
1455
1609
  this.root = root;
1456
- this.trace = trace;
1457
- this.handle = this.buildHandle();
1610
+ this.observations = observations;
1611
+ this.trace = trace2;
1458
1612
  this.promptRef = promptRef;
1459
1613
  this.redactor = redaction.redactor;
1460
1614
  this.redactInputs = redaction.redactInputs;
@@ -1463,13 +1617,9 @@ var LangfuseRunObserver = class {
1463
1617
  this.captureMaxBytes = redaction.captureMaxBytes;
1464
1618
  }
1465
1619
  root;
1620
+ observations;
1466
1621
  trace;
1467
1622
  turnSpans = /* @__PURE__ */ new Map();
1468
- // Assigned by LangfuseAgentObserver.startRun so that the run can
1469
- // publish trace-handle updates back to the agent observer.
1470
- setCurrentHandle;
1471
- clearCurrentHandle;
1472
- handle;
1473
1623
  promptRef;
1474
1624
  redactor;
1475
1625
  redactInputs;
@@ -1496,7 +1646,7 @@ var LangfuseRunObserver = class {
1496
1646
  safeInput.tools = args.request.tools;
1497
1647
  safeInput.providerTools = args.request.providerTools;
1498
1648
  safeInput.outputSchema = args.request.outputSchema;
1499
- safeInput.additionalParams = args.request.additionalParams;
1649
+ safeInput.providerOptions = args.request.providerOptions;
1500
1650
  }
1501
1651
  const metadata = {
1502
1652
  turn: args.turn,
@@ -1504,7 +1654,7 @@ var LangfuseRunObserver = class {
1504
1654
  toolNames: args.request.tools.map((tool) => tool.name),
1505
1655
  providerToolNames: args.request.providerTools?.map((tool) => tool.name) ?? [],
1506
1656
  hasOutputSchema: args.request.outputSchema !== void 0,
1507
- additionalParamKeys: isRecord(args.request.additionalParams) ? Object.keys(args.request.additionalParams) : []
1657
+ providerOptionKeys: isRecord(args.request.providerOptions) ? Object.keys(args.request.providerOptions) : []
1508
1658
  };
1509
1659
  if (this.captureMode === "full" && args.providerRequest !== void 0) {
1510
1660
  metadata.providerRequest = args.providerRequest;
@@ -1512,7 +1662,7 @@ var LangfuseRunObserver = class {
1512
1662
  if (args.modelInfo !== void 0) {
1513
1663
  const modelInfo = {
1514
1664
  provider: args.modelInfo.provider,
1515
- defaultModel: args.modelInfo.defaultModel
1665
+ modelId: args.modelInfo.modelId
1516
1666
  };
1517
1667
  if (args.modelInfo.capabilities !== void 0) {
1518
1668
  modelInfo.capabilities = args.modelInfo.capabilities;
@@ -1522,7 +1672,7 @@ var LangfuseRunObserver = class {
1522
1672
  Object.assign(metadata, promptMetadata(this.promptRef));
1523
1673
  const generationAttributes = {
1524
1674
  input: this.redactInputValue(safeInput),
1525
- model: args.request.model ?? args.modelInfo?.defaultModel ?? "default",
1675
+ model: args.modelInfo?.modelId ?? "unknown",
1526
1676
  modelParameters: modelParameters(args.request),
1527
1677
  metadata: asMetadata(this.redactInputValue(metadata))
1528
1678
  };
@@ -1533,9 +1683,11 @@ var LangfuseRunObserver = class {
1533
1683
  isFallback: false
1534
1684
  };
1535
1685
  }
1536
- const generation = turn.startObservation(`model.turn.${args.turn}`, generationAttributes, {
1537
- asType: "generation"
1538
- });
1686
+ const generation = this.observations.generation(
1687
+ `model.turn.${args.turn}`,
1688
+ generationAttributes,
1689
+ turn
1690
+ );
1539
1691
  return new LangfuseGenerationObserver(generation, this, /* @__PURE__ */ new Date());
1540
1692
  }
1541
1693
  startTool(args) {
@@ -1549,7 +1701,7 @@ var LangfuseRunObserver = class {
1549
1701
  if (args.toolDefinition !== void 0) metadata.toolDefinition = args.toolDefinition;
1550
1702
  if (args.toolMetadata !== void 0) metadata.toolMetadata = args.toolMetadata;
1551
1703
  }
1552
- const tool = turn.startObservation(
1704
+ const tool = this.observations.tool(
1553
1705
  `tool.${args.toolName}`,
1554
1706
  {
1555
1707
  input: this.redactInputValue({
@@ -1558,18 +1710,22 @@ var LangfuseRunObserver = class {
1558
1710
  }),
1559
1711
  metadata: asMetadata(this.redactInputValue(metadata))
1560
1712
  },
1561
- { asType: "tool" }
1713
+ turn
1562
1714
  );
1563
- return new LangfuseToolObserver(tool, this);
1715
+ return new LangfuseToolObserver(tool, this, this.observations);
1564
1716
  }
1565
1717
  end(args) {
1566
1718
  this.closeAllTurns();
1567
- const redactedOutput = this.redactOutputValue(args.output);
1719
+ const observedOutput = args.status === "completed" ? { status: args.status, output: args.output, text: args.text } : args.status === "blocked" ? { status: args.status, stage: args.stage, text: args.text } : { status: args.status, interaction: args.interaction, text: args.text };
1720
+ const redactedOutput = this.redactOutputValue(observedOutput);
1568
1721
  const metadata = {
1722
+ runId: args.runId,
1723
+ status: args.status,
1569
1724
  usage: args.usage,
1570
1725
  messageCount: args.messages.length,
1571
1726
  sources: this.redactOutputValue(args.sources),
1572
- providerToolCalls: this.redactOutputValue(args.providerToolCalls)
1727
+ providerToolCalls: this.redactOutputValue(args.providerToolCalls),
1728
+ resumedFrom: this.redactOutputValue(args.resumedFrom)
1573
1729
  };
1574
1730
  if (this.captureMode === "full") {
1575
1731
  metadata.messages = this.redactTranscript(args.messages);
@@ -1578,7 +1734,6 @@ var LangfuseRunObserver = class {
1578
1734
  output: redactedOutput,
1579
1735
  metadata
1580
1736
  }).end();
1581
- this.clearCurrentHandle?.();
1582
1737
  }
1583
1738
  error(args) {
1584
1739
  this.closeAllTurns();
@@ -1598,7 +1753,6 @@ var LangfuseRunObserver = class {
1598
1753
  },
1599
1754
  metadata
1600
1755
  }).end();
1601
- this.clearCurrentHandle?.();
1602
1756
  }
1603
1757
  event(args) {
1604
1758
  const metadata = asMetadata(this.redactOutputValue(args.attributes ?? {}));
@@ -1607,43 +1761,19 @@ var LangfuseRunObserver = class {
1607
1761
  attributes.level = args.level;
1608
1762
  }
1609
1763
  const startTime = eventStartTime(args.timestamp);
1610
- this.root.startObservation(args.name, attributes, {
1611
- asType: "event",
1612
- ...startTime === void 0 ? {} : { startTime }
1613
- });
1614
- }
1615
- getHandle() {
1616
- return this.handle;
1617
- }
1618
- buildHandle() {
1619
- return {
1620
- traceId: this.trace.traceId ?? "",
1621
- observationId: this.trace.observationId ?? "",
1622
- addAttributes: (attributes) => {
1623
- this.root.update({ metadata: asMetadata(this.redactOutputValue(attributes)) });
1624
- this.setCurrentHandle?.(this.handle);
1625
- },
1626
- addEvent: (name, attributes) => {
1627
- this.root.startObservation(
1628
- name,
1629
- { metadata: asMetadata(this.redactOutputValue(attributes ?? {})) },
1630
- { asType: "event" }
1631
- );
1632
- this.setCurrentHandle?.(this.handle);
1633
- }
1634
- };
1764
+ this.observations.event(args.name, attributes, this.root, startTime);
1635
1765
  }
1636
1766
  turnSpan(turn) {
1637
1767
  const existing = this.turnSpans.get(turn);
1638
1768
  if (existing !== void 0) {
1639
1769
  return existing;
1640
1770
  }
1641
- const span = this.root.startObservation(
1771
+ const span = this.observations.span(
1642
1772
  `turn.${turn}`,
1643
1773
  {
1644
1774
  metadata: { turn }
1645
1775
  },
1646
- { asType: "span" }
1776
+ this.root
1647
1777
  );
1648
1778
  this.turnSpans.set(turn, span);
1649
1779
  return span;
@@ -1686,7 +1816,9 @@ var LangfuseGenerationObserver = class {
1686
1816
  });
1687
1817
  }
1688
1818
  end(args) {
1689
- const redactedText = this.run.redactOutputValue(textFromAssistantContent(args.response.choice));
1819
+ const redactedText = this.run.redactOutputValue(
1820
+ textFromObservedAssistantContent(args.response.choice)
1821
+ );
1690
1822
  const redactedChoice = this.run.redactOutputValue(args.response.choice);
1691
1823
  const metadata = { turn: args.turn };
1692
1824
  if (args.firstDeltaMs !== void 0) metadata.firstDeltaMs = args.firstDeltaMs;
@@ -1718,13 +1850,18 @@ var LangfuseGenerationObserver = class {
1718
1850
  }).end();
1719
1851
  }
1720
1852
  };
1853
+ function textFromObservedAssistantContent(content) {
1854
+ return content.flatMap((item) => item.type === "text" ? [item.text] : []).join("\n");
1855
+ }
1721
1856
  var LangfuseToolObserver = class {
1722
- constructor(tool, run) {
1857
+ constructor(tool, run, observations) {
1723
1858
  this.tool = tool;
1724
1859
  this.run = run;
1860
+ this.observations = observations;
1725
1861
  }
1726
1862
  tool;
1727
1863
  run;
1864
+ observations;
1728
1865
  childAgents = /* @__PURE__ */ new Map();
1729
1866
  childGenerations = /* @__PURE__ */ new Map();
1730
1867
  childTools = [];
@@ -1741,7 +1878,7 @@ var LangfuseToolObserver = class {
1741
1878
  if (child.type === "turn_start") {
1742
1879
  const promptMessage = isRecord(child.prompt) ? child.prompt : void 0;
1743
1880
  const historyMessages = Array.isArray(child.history) ? child.history.filter(isRecord) : [];
1744
- agent.startObservation(
1881
+ this.observations.event(
1745
1882
  `${agentLabel(agentId, agentName)}.turn.${childTurn}.start`,
1746
1883
  {
1747
1884
  input: this.run.redactInputValue({
@@ -1752,7 +1889,7 @@ var LangfuseToolObserver = class {
1752
1889
  this.run.redactInputValue(childMetadata(args, agentId, agentName, childTurn))
1753
1890
  )
1754
1891
  },
1755
- { asType: "event" }
1892
+ agent
1756
1893
  );
1757
1894
  return;
1758
1895
  }
@@ -1769,15 +1906,15 @@ var LangfuseToolObserver = class {
1769
1906
  input.tools = request.tools;
1770
1907
  input.providerTools = request.providerTools;
1771
1908
  input.outputSchema = request.outputSchema;
1772
- input.additionalParams = request.additionalParams;
1909
+ input.providerOptions = request.providerOptions;
1773
1910
  }
1774
1911
  const toolNames = Array.isArray(request.tools) ? request.tools.filter(isRecord).map((tool) => tool.name).filter((name) => typeof name === "string") : [];
1775
1912
  const providerToolNames = Array.isArray(request.providerTools) ? request.providerTools.filter(isRecord).map((tool) => tool.name).filter((name) => typeof name === "string") : [];
1776
- const generation = agent.startObservation(
1913
+ const generation = this.observations.generation(
1777
1914
  `${agentLabel(agentId, agentName)}.model.turn.${childTurn}`,
1778
1915
  {
1779
1916
  input: this.run.redactInputValue(input),
1780
- model: typeof request.model === "string" ? request.model : typeof modelInfo?.defaultModel === "string" ? modelInfo.defaultModel : "default",
1917
+ model: typeof modelInfo?.modelId === "string" ? modelInfo.modelId : "unknown",
1781
1918
  modelParameters: modelParameters(request),
1782
1919
  metadata: asMetadata(
1783
1920
  this.run.redactInputValue({
@@ -1790,7 +1927,7 @@ var LangfuseToolObserver = class {
1790
1927
  })
1791
1928
  )
1792
1929
  },
1793
- { asType: "generation" }
1930
+ agent
1794
1931
  );
1795
1932
  this.childGenerations.set(generationKey(agentId, childTurn), {
1796
1933
  generation,
@@ -1830,7 +1967,7 @@ var LangfuseToolObserver = class {
1830
1967
  if (child.type === "source" || child.type === "provider_tool_call") {
1831
1968
  const childGeneration = this.childGenerations.get(generationKey(agentId, childTurn));
1832
1969
  const parent = childGeneration?.generation ?? agent;
1833
- parent.startObservation(
1970
+ this.observations.event(
1834
1971
  `${agentLabel(agentId, agentName)}.${child.type}`,
1835
1972
  {
1836
1973
  output: this.run.redactOutputValue(
@@ -1840,12 +1977,12 @@ var LangfuseToolObserver = class {
1840
1977
  this.run.redactOutputValue(childMetadata(args, agentId, agentName, childTurn))
1841
1978
  )
1842
1979
  },
1843
- { asType: "event" }
1980
+ parent
1844
1981
  );
1845
1982
  return;
1846
1983
  }
1847
1984
  if (child.type === "guardrail_decision") {
1848
- agent.startObservation(
1985
+ this.observations.guardrail(
1849
1986
  `${agentLabel(agentId, agentName)}.guardrail`,
1850
1987
  {
1851
1988
  output: this.run.redactOutputValue(child.decision),
@@ -1853,7 +1990,7 @@ var LangfuseToolObserver = class {
1853
1990
  this.run.redactOutputValue(childMetadata(args, agentId, agentName, childTurn))
1854
1991
  )
1855
1992
  },
1856
- { asType: "guardrail" }
1993
+ agent
1857
1994
  ).end();
1858
1995
  return;
1859
1996
  }
@@ -1863,14 +2000,13 @@ var LangfuseToolObserver = class {
1863
2000
  output: this.run.redactOutputValue({ toolCall: child.toolCall })
1864
2001
  });
1865
2002
  const toolCall = child.toolCall;
1866
- const toolCallFunction = isRecord(toolCall.function) ? toolCall.function : void 0;
1867
- const toolName = typeof toolCallFunction?.name === "string" ? toolCallFunction.name : "tool";
1868
- const toolCallId = typeof toolCall.callId === "string" ? toolCall.callId : typeof toolCall.id === "string" ? toolCall.id : void 0;
1869
- const childTool = agent.startObservation(
2003
+ const toolName = typeof toolCall.toolName === "string" ? toolCall.toolName : "tool";
2004
+ const toolCallId = typeof toolCall.toolCallId === "string" ? toolCall.toolCallId : typeof toolCall.callId === "string" ? toolCall.callId : void 0;
2005
+ const childTool = this.observations.tool(
1870
2006
  `${agentLabel(agentId, agentName)}.${toolName}`,
1871
2007
  {
1872
2008
  input: this.run.redactInputValue({
1873
- args: toolCallFunction?.arguments ?? {},
2009
+ args: toolCall.input,
1874
2010
  toolCall
1875
2011
  }),
1876
2012
  metadata: asMetadata(
@@ -1881,7 +2017,7 @@ var LangfuseToolObserver = class {
1881
2017
  })
1882
2018
  )
1883
2019
  },
1884
- { asType: "tool" }
2020
+ agent
1885
2021
  );
1886
2022
  const childToolRecord = {
1887
2023
  agentId,
@@ -1917,13 +2053,18 @@ var LangfuseToolObserver = class {
1917
2053
  return;
1918
2054
  }
1919
2055
  if (child.type === "final") {
2056
+ const result = isRecord(child.result) ? child.result : {};
1920
2057
  const update = {
1921
- output: this.run.redactOutputValue(child.output)
2058
+ output: this.run.redactOutputValue({
2059
+ status: result.status,
2060
+ output: result.output,
2061
+ text: result.text
2062
+ })
1922
2063
  };
1923
2064
  const metadata = {};
1924
- if (isRecord(child.usage)) metadata.usage = child.usage;
1925
- if (this.run.isFullCapture() && Array.isArray(child.messages)) {
1926
- metadata.messages = this.run.redactTranscript(child.messages);
2065
+ if (isRecord(result.usage)) metadata.usage = result.usage;
2066
+ if (this.run.isFullCapture() && Array.isArray(result.messages)) {
2067
+ metadata.messages = this.run.redactTranscript(result.messages);
1927
2068
  }
1928
2069
  if (Object.keys(metadata).length > 0) update.metadata = metadata;
1929
2070
  agent.update(update).end();
@@ -1970,6 +2111,23 @@ var LangfuseToolObserver = class {
1970
2111
  }
1971
2112
  this.tool.update(attributes).end();
1972
2113
  }
2114
+ suspend(args) {
2115
+ this.endOpenChildren();
2116
+ this.tool.update({
2117
+ output: this.run.redactOutputValue({
2118
+ status: "suspended",
2119
+ interaction: args.interaction
2120
+ }),
2121
+ metadata: {
2122
+ turn: args.turn,
2123
+ internalCallId: args.internalCallId,
2124
+ interactionId: args.interaction.id,
2125
+ interactionType: args.interaction.type
2126
+ },
2127
+ level: "WARNING",
2128
+ statusMessage: "Tool call suspended for human interaction"
2129
+ }).end();
2130
+ }
1973
2131
  error(args) {
1974
2132
  this.endOpenChildren();
1975
2133
  const redactedError = this.run.redactOutputValue(errorMessage(args.error));
@@ -1989,14 +2147,14 @@ var LangfuseToolObserver = class {
1989
2147
  if (existing !== void 0) {
1990
2148
  return existing;
1991
2149
  }
1992
- const agent = this.tool.startObservation(
2150
+ const agent = this.observations.agent(
1993
2151
  `${agentLabel(agentId, agentName)}.run`,
1994
2152
  {
1995
2153
  metadata: asMetadata(
1996
2154
  this.run.redactInputValue(childMetadata(args, agentId, agentName, args.turn))
1997
2155
  )
1998
2156
  },
1999
- { asType: "agent" }
2157
+ this.tool
2000
2158
  );
2001
2159
  this.childAgents.set(agentId, agent);
2002
2160
  return agent;
@@ -2032,12 +2190,8 @@ var LangfuseToolObserver = class {
2032
2190
  };
2033
2191
  export {
2034
2192
  DEFAULT_PATTERNS,
2193
+ LangfuseClient,
2035
2194
  LangfuseScoreError,
2036
- createLangfuseDatasetClient,
2037
- createLangfuseEvalReporter,
2038
- createLangfusePromptClient,
2039
- createPiiRedactor,
2040
- langfuse,
2041
- runEvalAsExperiment
2195
+ createPiiRedactor
2042
2196
  };
2043
2197
  //# sourceMappingURL=index.js.map