@anvia/langfuse 0.6.1 → 1.0.0-rc.2

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,251 @@ 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
+ return {
452
+ anviaTraceValue: "omitted",
453
+ reason,
454
+ ...originalBytes === void 0 ? {} : { originalBytes }
455
+ };
456
+ }
457
+ function boundedPreview(value, originalBytes, maxBytes) {
458
+ let low = 0;
459
+ let high = value.length;
460
+ while (low < high) {
461
+ const middle = Math.ceil((low + high) / 2);
462
+ const candidate = {
463
+ anviaTraceValue: "truncated",
464
+ originalBytes,
465
+ preview: value.slice(0, middle)
466
+ };
467
+ if (utf8Bytes(JSON.stringify(candidate)) <= maxBytes) {
468
+ low = middle;
469
+ } else {
470
+ high = middle - 1;
471
+ }
472
+ }
473
+ return value.slice(0, low);
474
+ }
475
+ function utf8Bytes(value) {
476
+ return typeof Buffer === "undefined" ? new TextEncoder().encode(value).byteLength : Buffer.byteLength(value, "utf8");
477
+ }
478
+
479
+ // src/helpers.ts
480
+ function modelInputMessage(message) {
481
+ const { metadata: _metadata, ...result } = message;
482
+ return result;
483
+ }
484
+ function modelInputMessages(messages) {
485
+ return messages.map(modelInputMessage);
486
+ }
487
+ function modelParameters(request) {
488
+ const params = {};
489
+ if (request.temperature !== void 0) params.temperature = request.temperature;
490
+ if (request.maxTokens !== void 0) params.maxTokens = request.maxTokens;
491
+ if (request.toolChoice !== void 0) {
492
+ params.toolChoice = typeof request.toolChoice === "string" ? request.toolChoice : request.toolChoice.name;
493
+ }
494
+ return params;
495
+ }
496
+ function usageDetails(usage) {
497
+ if (usage.details !== void 0 && Object.keys(usage.details).length > 0) {
498
+ return { ...usage.details };
499
+ }
500
+ return {
501
+ input: usage.inputTokens,
502
+ output: usage.outputTokens,
503
+ total: usage.totalTokens
504
+ };
505
+ }
506
+ function usageDetailsFromRecord(usage) {
507
+ if (isRecord(usage.details)) {
508
+ const details = Object.fromEntries(
509
+ Object.entries(usage.details).filter(
510
+ (entry) => typeof entry[1] === "number" && Number.isFinite(entry[1]) && entry[1] >= 0
511
+ )
512
+ );
513
+ if (Object.keys(details).length > 0) {
514
+ return details;
515
+ }
516
+ }
517
+ return {
518
+ input: numberValue(usage.inputTokens) ?? 0,
519
+ output: numberValue(usage.outputTokens) ?? 0,
520
+ total: numberValue(usage.totalTokens) ?? (numberValue(usage.inputTokens) ?? 0) + (numberValue(usage.outputTokens) ?? 0)
521
+ };
522
+ }
523
+ function childMetadata(args, agentId, agentName, childTurn) {
524
+ return {
525
+ source: "agent_tool_event",
526
+ childAgentId: agentId,
527
+ childAgentName: agentName,
528
+ childTurn,
529
+ parentToolName: args.toolName,
530
+ parentInternalCallId: args.internalCallId,
531
+ parentToolCallId: args.toolCallId
532
+ };
533
+ }
534
+ function generationKey(agentId, turn) {
535
+ return `${agentId}:${turn}`;
536
+ }
537
+ function agentLabel(agentId, agentName) {
538
+ return (agentName ?? agentId).replaceAll(/\s+/g, "_");
539
+ }
540
+ function isRecord(value) {
541
+ return typeof value === "object" && value !== null && !Array.isArray(value);
542
+ }
543
+ function numberValue(value) {
544
+ return typeof value === "number" ? value : void 0;
545
+ }
546
+ function emptyToUndefined(value) {
547
+ return value === void 0 || value.length === 0 ? void 0 : value;
548
+ }
549
+ function errorMessage(error) {
550
+ return error instanceof Error ? error.message : String(error);
551
+ }
552
+
553
+ // src/config.ts
554
+ var langfuseResolvedConfigSymbol = /* @__PURE__ */ Symbol.for(
555
+ "@anvia/langfuse.resolvedConfig"
556
+ );
557
+ function resolveLangfuseConfig(options = {}, fallback) {
558
+ return {
559
+ publicKey: resolveStringOption(
560
+ options.publicKey,
561
+ fallback?.publicKey,
562
+ process.env.LANGFUSE_PUBLIC_KEY
563
+ ),
564
+ secretKey: resolveStringOption(
565
+ options.secretKey,
566
+ fallback?.secretKey,
567
+ process.env.LANGFUSE_SECRET_KEY
568
+ ),
569
+ baseUrl: resolveStringOption(options.baseUrl, fallback?.baseUrl, process.env.LANGFUSE_BASE_URL) ?? "https://cloud.langfuse.com",
570
+ environment: resolveStringOption(
571
+ options.environment,
572
+ fallback?.environment,
573
+ process.env.LANGFUSE_TRACING_ENVIRONMENT
574
+ ),
575
+ release: resolveStringOption(options.release, fallback?.release, process.env.LANGFUSE_RELEASE),
576
+ serviceName: resolveStringOption(
577
+ options.serviceName,
578
+ fallback?.serviceName,
579
+ process.env.LANGFUSE_SERVICE_NAME
580
+ ),
581
+ timeoutMs: options.timeoutMs ?? fallback?.timeoutMs ?? 3e4
582
+ };
583
+ }
584
+ function getResolvedLangfuseConfig(value) {
585
+ if (typeof value !== "object" || value === null) {
586
+ return void 0;
324
587
  }
325
- return body;
588
+ return value[langfuseResolvedConfigSymbol];
589
+ }
590
+ function resolveStringOption(option, fallback, envVar) {
591
+ return emptyToUndefined(option) ?? emptyToUndefined(fallback) ?? emptyToUndefined(envVar);
326
592
  }
327
593
 
328
594
  // src/dataset-client.ts
@@ -381,7 +647,8 @@ function createLangfuseDatasetClient(tracing, options = {}) {
381
647
  if (dataset.metadata !== void 0) result.metadata = dataset.metadata;
382
648
  return result;
383
649
  },
384
- async getDataset(name) {
650
+ async getDataset(options2) {
651
+ const { name } = options2;
385
652
  const items = [];
386
653
  let description;
387
654
  let metadata;
@@ -421,7 +688,8 @@ function createLangfuseDatasetClient(tracing, options = {}) {
421
688
  if (metadata !== void 0) dataset.metadata = metadata;
422
689
  return dataset;
423
690
  },
424
- async upsertItems(name, items) {
691
+ async upsertItems(options2) {
692
+ const { name, items } = options2;
425
693
  const url = `${baseUrl}/api/public/datasets/${encodeURIComponent(name)}/items`;
426
694
  await request(url, {
427
695
  method: "POST",
@@ -431,7 +699,7 @@ function createLangfuseDatasetClient(tracing, options = {}) {
431
699
  async runExperiment(opts) {
432
700
  let items = opts.items;
433
701
  if (items === void 0) {
434
- const dataset = await this.getDataset(opts.datasetName);
702
+ const dataset = await this.getDataset({ name: opts.datasetName });
435
703
  items = dataset.items;
436
704
  }
437
705
  if (items === void 0 || items.length === 0) {
@@ -512,7 +780,8 @@ import {
512
780
  } from "@anvia/core/evals";
513
781
  var DEFAULT_TRUNCATE_BYTES = 2048;
514
782
  function createLangfuseEvalReporter(tracing, options = {}) {
515
- const onMissingTrace = options.onMissingTrace ?? (options.strict === true ? "throw" : "ignore");
783
+ const onMissingTrace = options.onMissingTrace ?? "ignore";
784
+ const traceObserver = options.traceObserver ?? "langfuse";
516
785
  const truncateAt = options.truncateInputAt ?? DEFAULT_TRUNCATE_BYTES;
517
786
  const includeMessages = options.includeMessages ?? true;
518
787
  const includeContext = options.includeContext ?? false;
@@ -521,18 +790,20 @@ function createLangfuseEvalReporter(tracing, options = {}) {
521
790
  if (args.outcome.outcome === "invalid" && options.publishInvalid !== true) {
522
791
  return;
523
792
  }
524
- const trace = args.trace ?? resolveEvalTraceRef({
793
+ const trace2 = args.trace ?? resolveEvalTraceRef({
525
794
  output: args.output,
526
795
  input: args.case.input,
527
796
  metadata: args.case.metadata
528
797
  });
529
- if (trace?.traceId === void 0 || trace.traceId.length === 0) {
798
+ if (trace2?.traceId === void 0 || trace2.traceId.length === 0 || trace2.observer !== void 0 && trace2.observer !== traceObserver) {
530
799
  if (onMissingTrace === "throw") {
531
- throw new Error("Langfuse eval reporter requires traceId");
800
+ throw new Error(
801
+ `Langfuse eval reporter requires traceId from observer ${JSON.stringify(traceObserver)}`
802
+ );
532
803
  }
533
804
  if (onMissingTrace === "warn") {
534
805
  console.warn(
535
- "[anvia/langfuse] eval reporter dropped score because no traceId was found",
806
+ "[anvia/langfuse] eval reporter dropped score because no matching trace was found",
536
807
  { caseId: args.case.id, metric: args.metric.name }
537
808
  );
538
809
  }
@@ -551,11 +822,11 @@ function createLangfuseEvalReporter(tracing, options = {}) {
551
822
  });
552
823
  const configId = resolveConfigId(args.metric);
553
824
  const score = {
554
- traceId: trace.traceId,
825
+ traceId: trace2.traceId,
555
826
  name: args.metric.name,
556
827
  value: projection.value
557
828
  };
558
- if (trace.observationId !== void 0) score.observationId = trace.observationId;
829
+ if (trace2.observationId !== void 0) score.observationId = trace2.observationId;
559
830
  if (args.metric.dataType !== void 0) score.dataType = args.metric.dataType;
560
831
  if (configId !== void 0) score.configId = configId;
561
832
  if (projection.explanation !== void 0) score.comment = projection.explanation;
@@ -653,18 +924,20 @@ function readMessages(output) {
653
924
 
654
925
  // src/experiment-runner.ts
655
926
  import { runEvalSuite } from "@anvia/core/evals";
656
- async function runEvalAsExperiment(evalOptions, experimentOptions) {
927
+ async function runLangfuseEvalExperiment(client, options) {
928
+ const evalOptions = options.suite;
929
+ const experimentOptions = options.experiment;
657
930
  const clientOptions = {};
658
931
  if (experimentOptions.pageSize !== void 0) clientOptions.pageSize = experimentOptions.pageSize;
659
932
  if (experimentOptions.timeoutMs !== void 0)
660
933
  clientOptions.timeoutMs = experimentOptions.timeoutMs;
661
- const client = experimentOptions.client ?? createLangfuseDatasetClient(experimentOptions.tracing, clientOptions);
934
+ const datasetClient = client.datasetClient(clientOptions);
662
935
  const suiteOptions = experimentOptions.publishScores === true ? {
663
936
  ...evalOptions,
664
937
  reporters: [
665
938
  ...evalOptions.reporters ?? [],
666
939
  createLangfuseEvalReporter(
667
- experimentOptions.tracing,
940
+ client,
668
941
  experimentOptions.reporterOptions
669
942
  )
670
943
  ]
@@ -709,30 +982,30 @@ async function runEvalAsExperiment(evalOptions, experimentOptions) {
709
982
  };
710
983
  }
711
984
  const output = result.output ?? void 0;
712
- const trace = readTraceFromOutput(result.output);
713
- return { output, trace };
985
+ const trace2 = readTraceFromOutput(result.output);
986
+ return { output, trace: trace2 };
714
987
  }
715
988
  };
716
989
  if (experimentOptions.description !== void 0) {
717
990
  runOptions.description = experimentOptions.description;
718
991
  }
719
992
  if (experimentOptions.metadata !== void 0) runOptions.metadata = experimentOptions.metadata;
720
- const datasetRun = await client.runExperiment(runOptions);
993
+ const datasetRun = await datasetClient.runExperiment(runOptions);
721
994
  return { suite, datasetRun };
722
995
  }
723
996
  function readTraceFromOutput(output) {
724
997
  if (typeof output !== "object" || output === null || !("trace" in output)) {
725
998
  return void 0;
726
999
  }
727
- const trace = output.trace;
728
- if (typeof trace !== "object" || trace === null) {
1000
+ const trace2 = output.trace;
1001
+ if (typeof trace2 !== "object" || trace2 === null) {
729
1002
  return void 0;
730
1003
  }
731
- const traceId = trace.traceId;
1004
+ const traceId = trace2.traceId;
732
1005
  if (typeof traceId !== "string") {
733
1006
  return void 0;
734
1007
  }
735
- const observationId = trace.observationId;
1008
+ const observationId = trace2.observationId;
736
1009
  if (typeof observationId === "string") {
737
1010
  return { traceId, observationId };
738
1011
  }
@@ -765,7 +1038,8 @@ function createLangfusePromptClient(tracing, options = {}) {
765
1038
  }
766
1039
  return await response.json();
767
1040
  }
768
- async function getPrompt(name, opts = {}) {
1041
+ async function getPrompt(options2) {
1042
+ const { name, ...opts } = options2;
769
1043
  const key = `${name}::${opts.version ?? ""}::${opts.label ?? ""}`;
770
1044
  const ttl = opts.cacheTtlMs ?? defaultTtl;
771
1045
  if (opts.refresh !== true) {
@@ -794,352 +1068,113 @@ function createLangfusePromptClient(tracing, options = {}) {
794
1068
  cache.set(key, { prompt, expiresAt: Date.now() + ttl });
795
1069
  return prompt;
796
1070
  }
797
- function getPromptText(name, opts) {
798
- return getPrompt(name, opts).then((prompt) => {
1071
+ function getPromptText(options2) {
1072
+ return getPrompt(options2).then((prompt) => {
799
1073
  if (typeof prompt.prompt !== "string") {
800
- throw new Error(`Prompt ${name} is a chat prompt; expected text`);
1074
+ throw new Error(`Prompt ${options2.name} is a chat prompt; expected text`);
801
1075
  }
802
1076
  return prompt.prompt;
803
1077
  });
804
1078
  }
805
- function getPromptChat(name, opts) {
806
- return getPrompt(name, opts).then((prompt) => {
1079
+ function getPromptChat(options2) {
1080
+ return getPrompt(options2).then((prompt) => {
807
1081
  if (typeof prompt.prompt === "string") {
808
- throw new Error(`Prompt ${name} is a text prompt; expected chat`);
1082
+ throw new Error(`Prompt ${options2.name} is a text prompt; expected chat`);
809
1083
  }
810
1084
  return prompt.prompt;
811
1085
  });
812
1086
  }
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) };
1087
+ function refresh() {
1088
+ cache.clear();
997
1089
  }
998
- return {
999
- ...message,
1000
- content: redactMessageContent(message.content, redactStringFn)
1001
- };
1002
- }
1003
- function redactMessageContent(value, redactStringFn) {
1004
- return redactValue(value, 0, redactStringFn);
1090
+ return { getPrompt, getPromptText, getPromptChat, refresh };
1005
1091
  }
1006
- function isBase64DataUrl(value) {
1007
- return /^data:[^;,]+;base64,/i.test(value);
1092
+ function normalizePrompt(raw, type) {
1093
+ if (type === "text") {
1094
+ if (typeof raw === "string") return raw;
1095
+ throw new Error("Expected text prompt to be a string");
1096
+ }
1097
+ if (!Array.isArray(raw)) {
1098
+ throw new Error("Expected chat prompt to be an array of messages");
1099
+ }
1100
+ return raw.map((entry) => {
1101
+ if (typeof entry !== "object" || entry === null) {
1102
+ throw new Error("Expected chat message to be an object");
1103
+ }
1104
+ const role = entry.role;
1105
+ const content = entry.content;
1106
+ if (typeof content !== "string") {
1107
+ throw new Error("Expected chat message content to be a string");
1108
+ }
1109
+ if (role !== "system" && role !== "user" && role !== "assistant" && role !== "tool") {
1110
+ throw new Error(`Unexpected chat message role: ${String(role)}`);
1111
+ }
1112
+ return { role, content };
1113
+ });
1008
1114
  }
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
- );
1115
+ function buildAuthHeader2(publicKey, secretKey) {
1116
+ if (publicKey === void 0 || secretKey === void 0) {
1117
+ return {};
1030
1118
  }
1031
- return resolved;
1119
+ const encoded = Buffer.from(`${publicKey}:${secretKey}`).toString("base64");
1120
+ return { Authorization: `Basic ${encoded}` };
1032
1121
  }
1033
- function sanitizeTraceValue(value, maxBytes) {
1034
- validateCaptureMaxBytes(maxBytes);
1035
- const sanitized = sanitizeValue(value, 0, /* @__PURE__ */ new WeakSet());
1036
- let serialized;
1122
+ async function readErrorBody2(response) {
1037
1123
  try {
1038
- serialized = JSON.stringify(sanitized) ?? String(sanitized);
1124
+ return await response.text();
1039
1125
  } catch {
1040
- return omitted("unserializable");
1041
- }
1042
- const originalBytes = utf8Bytes(serialized);
1043
- if (originalBytes <= maxBytes) {
1044
- return sanitized;
1126
+ return "<unreadable>";
1045
1127
  }
1046
- const preview = boundedPreview(serialized, originalBytes, maxBytes);
1047
- return {
1048
- anviaTraceValue: "truncated",
1049
- originalBytes,
1050
- preview
1051
- };
1052
1128
  }
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;
1129
+
1130
+ // src/tracing.ts
1131
+ var LangfuseObservationFactory = class {
1132
+ constructor(tracer) {
1133
+ this.tracer = tracer;
1062
1134
  }
1063
- if (value instanceof ArrayBuffer || ArrayBuffer.isView(value)) {
1064
- const byteLength = value.byteLength;
1065
- return omitted("binary", byteLength);
1135
+ tracer;
1136
+ agent(name, attributes, parent) {
1137
+ return new LangfuseAgent({ otelSpan: this.startSpan(name, parent), attributes });
1066
1138
  }
1067
- if (value === null || typeof value !== "object") {
1068
- return value;
1139
+ span(name, attributes, parent) {
1140
+ return new LangfuseSpan({ otelSpan: this.startSpan(name, parent), attributes });
1069
1141
  }
1070
- if (seen.has(value)) {
1071
- return omitted("circular");
1142
+ generation(name, attributes, parent) {
1143
+ return new LangfuseGeneration({ otelSpan: this.startSpan(name, parent), attributes });
1072
1144
  }
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;
1145
+ tool(name, attributes, parent) {
1146
+ return new LangfuseTool({ otelSpan: this.startSpan(name, parent), attributes });
1078
1147
  }
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);
1148
+ guardrail(name, attributes, parent) {
1149
+ return new LangfuseGuardrail({ otelSpan: this.startSpan(name, parent), attributes });
1087
1150
  }
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
- }
1151
+ event(name, attributes, parent, timestamp) {
1152
+ const endTime = timestamp ?? /* @__PURE__ */ new Date();
1153
+ return new LangfuseEvent({
1154
+ otelSpan: this.startSpan(name, parent, timestamp),
1155
+ attributes,
1156
+ timestamp: endTime
1157
+ });
1113
1158
  }
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);
1159
+ startSpan(name, parent, startTime) {
1160
+ const parentContext = parent === void 0 ? ROOT_CONTEXT : "otelSpan" in parent ? trace.setSpan(ROOT_CONTEXT, parent.otelSpan) : trace.setSpanContext(ROOT_CONTEXT, parent);
1161
+ return this.tracer.startSpan(name, startTime === void 0 ? {} : { startTime }, parentContext);
1124
1162
  }
1125
1163
  };
1126
- var LangfuseAgentObserver = class {
1127
- processor;
1128
- sdk;
1164
+ var LangfuseClient = class {
1129
1165
  [langfuseResolvedConfigSymbol];
1130
1166
  publicKey;
1131
1167
  secretKey;
1132
1168
  baseUrl;
1133
1169
  serviceName;
1134
1170
  timeoutMs;
1135
- queue;
1136
- currentHandle;
1137
- redactor;
1138
- redactInputs;
1139
- redactOutputs;
1140
- captureMode;
1141
- captureMaxBytes;
1142
- constructor(options) {
1171
+ options;
1172
+ resource;
1173
+ initialization;
1174
+ closePromise;
1175
+ closed = false;
1176
+ constructor(options = {}) {
1177
+ this.options = options;
1143
1178
  const resolvedConfig = resolveLangfuseConfig(options);
1144
1179
  this[langfuseResolvedConfigSymbol] = resolvedConfig;
1145
1180
  this.publicKey = resolvedConfig.publicKey;
@@ -1147,50 +1182,82 @@ var LangfuseAgentObserver = class {
1147
1182
  this.baseUrl = resolvedConfig.baseUrl;
1148
1183
  this.serviceName = resolvedConfig.serviceName;
1149
1184
  this.timeoutMs = resolvedConfig.timeoutMs;
1150
- const processorOptions = {
1151
- baseUrl: this.baseUrl
1185
+ }
1186
+ observer(options = {}) {
1187
+ this.assertOpen();
1188
+ return new LangfuseAgentObserver(this, resolveLangfuseCapture(options));
1189
+ }
1190
+ evalReporter(options = {}) {
1191
+ this.assertOpen();
1192
+ const reporter = createLangfuseEvalReporter(this, options);
1193
+ return {
1194
+ report: (args) => {
1195
+ this.assertOpen();
1196
+ return reporter.report(args);
1197
+ }
1152
1198
  };
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]
1199
+ }
1200
+ promptClient(options = {}) {
1201
+ this.assertOpen();
1202
+ const prompts = createLangfusePromptClient(this, options);
1203
+ return {
1204
+ getPrompt: (getOptions) => {
1205
+ this.assertOpen();
1206
+ return prompts.getPrompt(getOptions);
1207
+ },
1208
+ getPromptText: (getOptions) => {
1209
+ this.assertOpen();
1210
+ return prompts.getPromptText(getOptions);
1211
+ },
1212
+ getPromptChat: (getOptions) => {
1213
+ this.assertOpen();
1214
+ return prompts.getPromptChat(getOptions);
1215
+ },
1216
+ refresh: () => {
1217
+ this.assertOpen();
1218
+ prompts.refresh();
1219
+ }
1162
1220
  };
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) {
1221
+ }
1222
+ datasetClient(options = {}) {
1223
+ this.assertOpen();
1224
+ const datasets = createLangfuseDatasetClient(this, options);
1225
+ const client = this;
1226
+ return {
1227
+ createDataset(dataset) {
1228
+ client.assertOpen();
1229
+ return datasets.createDataset(dataset);
1230
+ },
1231
+ getDataset(getOptions) {
1232
+ client.assertOpen();
1233
+ return datasets.getDataset(getOptions);
1234
+ },
1235
+ upsertItems(upsertOptions) {
1236
+ client.assertOpen();
1237
+ return datasets.upsertItems(upsertOptions);
1238
+ },
1239
+ runExperiment(experimentOptions) {
1240
+ client.assertOpen();
1241
+ return datasets.runExperiment(experimentOptions);
1242
+ }
1243
+ };
1244
+ }
1245
+ runEvalExperiment(options) {
1246
+ this.assertOpen();
1247
+ return runLangfuseEvalExperiment(this, options);
1248
+ }
1249
+ async startObservedRun(args, capture) {
1250
+ const resource = await this.resources();
1187
1251
  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 ?? {});
1252
+ const capturedInput = captureInput(
1253
+ {
1254
+ instructions: args.instructions,
1255
+ prompt: args.prompt,
1256
+ history: args.history
1257
+ },
1258
+ capture
1259
+ );
1260
+ const capturedTraceMetadata = captureInput(args.trace?.metadata ?? {}, capture);
1194
1261
  const metadata = {
1195
1262
  agentName: args.agentName,
1196
1263
  agentDescription: args.agentDescription,
@@ -1206,69 +1273,41 @@ var LangfuseAgentObserver = class {
1206
1273
  input: capturedInput,
1207
1274
  metadata
1208
1275
  };
1209
- if (args.trace?.version !== void 0) {
1210
- rootAttributes.version = args.trace.version;
1211
- }
1212
- const root = startObservation(
1276
+ if (args.trace?.version !== void 0) rootAttributes.version = args.trace.version;
1277
+ const root = resource.observations.agent(
1213
1278
  args.agentName ?? "agent.run",
1214
1279
  rootAttributes,
1215
- traceId === void 0 ? { asType: "agent" } : {
1216
- asType: "agent",
1217
- parentSpanContext: {
1218
- traceId,
1219
- spanId: "0000000000000001",
1220
- traceFlags: 1
1221
- }
1280
+ traceId === void 0 ? void 0 : {
1281
+ traceId,
1282
+ spanId: "0000000000000001",
1283
+ traceFlags: TraceFlags.SAMPLED
1222
1284
  }
1223
1285
  );
1224
1286
  applyTraceAttributes(root, args, capturedTraceMetadata);
1225
- const promptRef = resolvePromptRef(args);
1226
- const runObserver = new LangfuseRunObserver(
1287
+ return new LangfuseRunObserver(
1227
1288
  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
- }
1289
+ resource.observations,
1290
+ { traceId: root.traceId, observationId: root.id },
1291
+ resolvePromptRef(args),
1292
+ capture
1240
1293
  );
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
1294
  }
1252
1295
  async flush() {
1253
- await this.queue?.flush();
1254
- await this.processor.forceFlush();
1296
+ this.assertOpen();
1297
+ const resource = this.resource ?? (this.initialization === void 0 ? void 0 : await this.initialization);
1298
+ if (resource === void 0) return;
1299
+ await resource.queue?.flush();
1300
+ await resource.processor.forceFlush();
1255
1301
  }
1256
- async shutdown() {
1257
- await this.queue?.shutdown();
1258
- await this.sdk.shutdown();
1302
+ close() {
1303
+ this.closePromise ??= this.closeResources();
1304
+ return this.closePromise;
1259
1305
  }
1260
- async flushScores() {
1261
- await this.queue?.flush();
1306
+ async [Symbol.asyncDispose]() {
1307
+ await this.close();
1262
1308
  }
1263
1309
  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);
1310
+ return this.resource?.queue?.depth() ?? 0;
1272
1311
  }
1273
1312
  async score(args) {
1274
1313
  if (args.traceId === void 0 || args.traceId.length === 0) {
@@ -1278,12 +1317,100 @@ var LangfuseAgentObserver = class {
1278
1317
  throw new Error("Langfuse score requires publicKey and secretKey");
1279
1318
  }
1280
1319
  assertScoreValue(args.value, args.dataType);
1281
- if (this.queue !== null) {
1282
- this.queue.enqueue(args);
1320
+ const resource = await this.resources();
1321
+ if (resource.queue !== null) {
1322
+ resource.queue.enqueue(args);
1283
1323
  return;
1284
1324
  }
1285
1325
  await this.sendScore(args);
1286
1326
  }
1327
+ resources() {
1328
+ this.assertOpen();
1329
+ if (this.resource !== void 0) return Promise.resolve(this.resource);
1330
+ this.initialization ??= this.createResources().then((resource) => {
1331
+ this.resource = resource;
1332
+ return resource;
1333
+ }).catch((error) => {
1334
+ this.initialization = void 0;
1335
+ throw error;
1336
+ });
1337
+ return this.initialization;
1338
+ }
1339
+ async createResources() {
1340
+ const resolvedConfig = this[langfuseResolvedConfigSymbol];
1341
+ const processorOptions = {
1342
+ baseUrl: this.baseUrl
1343
+ };
1344
+ if (this.publicKey !== void 0) processorOptions.publicKey = this.publicKey;
1345
+ if (this.secretKey !== void 0) processorOptions.secretKey = this.secretKey;
1346
+ if (resolvedConfig.environment !== void 0) {
1347
+ processorOptions.environment = resolvedConfig.environment;
1348
+ }
1349
+ if (resolvedConfig.release !== void 0) processorOptions.release = resolvedConfig.release;
1350
+ const processor = new LangfuseSpanProcessor(processorOptions);
1351
+ const providerOptions = {
1352
+ spanProcessors: [processor]
1353
+ };
1354
+ if (this.serviceName !== void 0) {
1355
+ providerOptions.resource = resourceFromAttributes({
1356
+ [SEMRESATTRS_SERVICE_NAME]: this.serviceName
1357
+ });
1358
+ }
1359
+ let provider;
1360
+ try {
1361
+ provider = new NodeTracerProvider(providerOptions);
1362
+ } catch (error) {
1363
+ await processor.shutdown().catch(() => void 0);
1364
+ throw error;
1365
+ }
1366
+ try {
1367
+ const batchSize = this.options.scores?.batchSize ?? 0;
1368
+ const queue = batchSize > 0 && this.publicKey !== void 0 && this.secretKey !== void 0 ? new ScoreQueue({
1369
+ baseUrl: this.baseUrl,
1370
+ publicKey: this.publicKey,
1371
+ secretKey: this.secretKey,
1372
+ timeoutMs: this.timeoutMs,
1373
+ batchSize,
1374
+ flushIntervalMs: this.options.scores?.flushIntervalMs ?? 250,
1375
+ maxAttempts: scoreMaxAttempts(this.options.scores?.retries)
1376
+ }) : null;
1377
+ return {
1378
+ processor,
1379
+ provider,
1380
+ observations: new LangfuseObservationFactory(
1381
+ provider.getTracer("@anvia/langfuse", "1.0.0")
1382
+ ),
1383
+ queue
1384
+ };
1385
+ } catch (error) {
1386
+ await provider.shutdown().catch(() => void 0);
1387
+ throw error;
1388
+ }
1389
+ }
1390
+ async closeResources() {
1391
+ this.closed = true;
1392
+ const pending = this.resource === void 0 ? this.initialization : Promise.resolve(this.resource);
1393
+ if (pending === void 0) return;
1394
+ let resource;
1395
+ try {
1396
+ resource = await pending;
1397
+ } catch {
1398
+ return;
1399
+ }
1400
+ const settled = await Promise.allSettled([
1401
+ resource.queue?.shutdown() ?? Promise.resolve(),
1402
+ resource.provider.shutdown()
1403
+ ]);
1404
+ const failures = settled.flatMap(
1405
+ (result) => result.status === "rejected" ? [result.reason] : []
1406
+ );
1407
+ if (failures.length > 0) {
1408
+ throw new AggregateError(failures, "Failed to close LangfuseClient.");
1409
+ }
1410
+ }
1411
+ assertOpen() {
1412
+ if (this.closed) throw new Error("LangfuseClient is closed.");
1413
+ }
1287
1414
  async sendScore(args) {
1288
1415
  const body = buildScoreBody2(args);
1289
1416
  const response = await fetch(`${this.baseUrl}/api/public/scores`, {
@@ -1302,6 +1429,37 @@ var LangfuseAgentObserver = class {
1302
1429
  }
1303
1430
  }
1304
1431
  };
1432
+ var LangfuseAgentObserver = class {
1433
+ constructor(client, capture) {
1434
+ this.client = client;
1435
+ this.capture = capture;
1436
+ }
1437
+ client;
1438
+ capture;
1439
+ startRun(args) {
1440
+ return this.client.startObservedRun(args, this.capture);
1441
+ }
1442
+ };
1443
+ function resolveLangfuseCapture(options) {
1444
+ return {
1445
+ redactor: options.redactInputs !== void 0 || options.redactOutputs !== void 0 ? createPiiRedactor(options.redaction) : void 0,
1446
+ redactInputs: options.redactInputs,
1447
+ redactOutputs: options.redactOutputs,
1448
+ captureMode: options.captureMode ?? "safe",
1449
+ captureMaxBytes: validateCaptureMaxBytes(options.captureMaxBytes)
1450
+ };
1451
+ }
1452
+ function captureInput(value, capture) {
1453
+ const redacted = capture.redactor === void 0 || capture.redactInputs === void 0 ? value : applyRedaction(capture.redactor, value, capture.redactInputs);
1454
+ return sanitizeTraceValue(redacted, capture.captureMaxBytes);
1455
+ }
1456
+ function scoreMaxAttempts(retries) {
1457
+ if (retries === void 0) return 3;
1458
+ if (!Number.isSafeInteger(retries.maxAttempts) || retries.maxAttempts < 1) {
1459
+ throw new TypeError("Langfuse score retries.maxAttempts must be a positive integer.");
1460
+ }
1461
+ return retries.maxAttempts;
1462
+ }
1305
1463
  function assertScoreValue(value, dataType) {
1306
1464
  if (dataType === "NUMERIC") {
1307
1465
  if (typeof value !== "number") {
@@ -1352,7 +1510,7 @@ function applyTraceAttributes(root, args, capturedMetadata) {
1352
1510
  root.otelSpan.setAttribute(LangfuseOtelSpanAttributes.TRACE_SESSION_ID, args.trace.sessionId);
1353
1511
  }
1354
1512
  if (args.trace?.tags !== void 0) {
1355
- root.otelSpan.setAttribute(LangfuseOtelSpanAttributes.TRACE_TAGS, args.trace.tags);
1513
+ root.otelSpan.setAttribute(LangfuseOtelSpanAttributes.TRACE_TAGS, [...args.trace.tags]);
1356
1514
  }
1357
1515
  for (const [key, value] of Object.entries(
1358
1516
  isRecord(capturedMetadata) ? capturedMetadata : { value: capturedMetadata }
@@ -1441,20 +1599,15 @@ function asMetadata(value) {
1441
1599
  return isRecord(value) ? value : { value };
1442
1600
  }
1443
1601
  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
- }
1602
+ if (value === void 0) return void 0;
1450
1603
  const parsed = new Date(value);
1451
1604
  return Number.isNaN(parsed.getTime()) ? void 0 : parsed;
1452
1605
  }
1453
1606
  var LangfuseRunObserver = class {
1454
- constructor(root, trace, promptRef, redaction) {
1607
+ constructor(root, observations, trace2, promptRef, redaction) {
1455
1608
  this.root = root;
1456
- this.trace = trace;
1457
- this.handle = this.buildHandle();
1609
+ this.observations = observations;
1610
+ this.trace = trace2;
1458
1611
  this.promptRef = promptRef;
1459
1612
  this.redactor = redaction.redactor;
1460
1613
  this.redactInputs = redaction.redactInputs;
@@ -1463,13 +1616,9 @@ var LangfuseRunObserver = class {
1463
1616
  this.captureMaxBytes = redaction.captureMaxBytes;
1464
1617
  }
1465
1618
  root;
1619
+ observations;
1466
1620
  trace;
1467
1621
  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
1622
  promptRef;
1474
1623
  redactor;
1475
1624
  redactInputs;
@@ -1496,7 +1645,7 @@ var LangfuseRunObserver = class {
1496
1645
  safeInput.tools = args.request.tools;
1497
1646
  safeInput.providerTools = args.request.providerTools;
1498
1647
  safeInput.outputSchema = args.request.outputSchema;
1499
- safeInput.additionalParams = args.request.additionalParams;
1648
+ safeInput.providerOptions = args.request.providerOptions;
1500
1649
  }
1501
1650
  const metadata = {
1502
1651
  turn: args.turn,
@@ -1504,7 +1653,7 @@ var LangfuseRunObserver = class {
1504
1653
  toolNames: args.request.tools.map((tool) => tool.name),
1505
1654
  providerToolNames: args.request.providerTools?.map((tool) => tool.name) ?? [],
1506
1655
  hasOutputSchema: args.request.outputSchema !== void 0,
1507
- additionalParamKeys: isRecord(args.request.additionalParams) ? Object.keys(args.request.additionalParams) : []
1656
+ providerOptionKeys: isRecord(args.request.providerOptions) ? Object.keys(args.request.providerOptions) : []
1508
1657
  };
1509
1658
  if (this.captureMode === "full" && args.providerRequest !== void 0) {
1510
1659
  metadata.providerRequest = args.providerRequest;
@@ -1512,7 +1661,7 @@ var LangfuseRunObserver = class {
1512
1661
  if (args.modelInfo !== void 0) {
1513
1662
  const modelInfo = {
1514
1663
  provider: args.modelInfo.provider,
1515
- defaultModel: args.modelInfo.defaultModel
1664
+ modelId: args.modelInfo.modelId
1516
1665
  };
1517
1666
  if (args.modelInfo.capabilities !== void 0) {
1518
1667
  modelInfo.capabilities = args.modelInfo.capabilities;
@@ -1522,7 +1671,7 @@ var LangfuseRunObserver = class {
1522
1671
  Object.assign(metadata, promptMetadata(this.promptRef));
1523
1672
  const generationAttributes = {
1524
1673
  input: this.redactInputValue(safeInput),
1525
- model: args.request.model ?? args.modelInfo?.defaultModel ?? "default",
1674
+ model: args.modelInfo?.modelId ?? "unknown",
1526
1675
  modelParameters: modelParameters(args.request),
1527
1676
  metadata: asMetadata(this.redactInputValue(metadata))
1528
1677
  };
@@ -1533,9 +1682,11 @@ var LangfuseRunObserver = class {
1533
1682
  isFallback: false
1534
1683
  };
1535
1684
  }
1536
- const generation = turn.startObservation(`model.turn.${args.turn}`, generationAttributes, {
1537
- asType: "generation"
1538
- });
1685
+ const generation = this.observations.generation(
1686
+ `model.turn.${args.turn}`,
1687
+ generationAttributes,
1688
+ turn
1689
+ );
1539
1690
  return new LangfuseGenerationObserver(generation, this, /* @__PURE__ */ new Date());
1540
1691
  }
1541
1692
  startTool(args) {
@@ -1549,7 +1700,7 @@ var LangfuseRunObserver = class {
1549
1700
  if (args.toolDefinition !== void 0) metadata.toolDefinition = args.toolDefinition;
1550
1701
  if (args.toolMetadata !== void 0) metadata.toolMetadata = args.toolMetadata;
1551
1702
  }
1552
- const tool = turn.startObservation(
1703
+ const tool = this.observations.tool(
1553
1704
  `tool.${args.toolName}`,
1554
1705
  {
1555
1706
  input: this.redactInputValue({
@@ -1558,14 +1709,16 @@ var LangfuseRunObserver = class {
1558
1709
  }),
1559
1710
  metadata: asMetadata(this.redactInputValue(metadata))
1560
1711
  },
1561
- { asType: "tool" }
1712
+ turn
1562
1713
  );
1563
- return new LangfuseToolObserver(tool, this);
1714
+ return new LangfuseToolObserver(tool, this, this.observations);
1564
1715
  }
1565
1716
  end(args) {
1566
1717
  this.closeAllTurns();
1567
- const redactedOutput = this.redactOutputValue(args.output);
1718
+ const observedOutput = args.status === "completed" ? { status: args.status, output: args.output, text: args.text } : { status: args.status, stage: args.stage, text: args.text };
1719
+ const redactedOutput = this.redactOutputValue(observedOutput);
1568
1720
  const metadata = {
1721
+ status: args.status,
1569
1722
  usage: args.usage,
1570
1723
  messageCount: args.messages.length,
1571
1724
  sources: this.redactOutputValue(args.sources),
@@ -1578,7 +1731,6 @@ var LangfuseRunObserver = class {
1578
1731
  output: redactedOutput,
1579
1732
  metadata
1580
1733
  }).end();
1581
- this.clearCurrentHandle?.();
1582
1734
  }
1583
1735
  error(args) {
1584
1736
  this.closeAllTurns();
@@ -1598,7 +1750,6 @@ var LangfuseRunObserver = class {
1598
1750
  },
1599
1751
  metadata
1600
1752
  }).end();
1601
- this.clearCurrentHandle?.();
1602
1753
  }
1603
1754
  event(args) {
1604
1755
  const metadata = asMetadata(this.redactOutputValue(args.attributes ?? {}));
@@ -1607,43 +1758,19 @@ var LangfuseRunObserver = class {
1607
1758
  attributes.level = args.level;
1608
1759
  }
1609
1760
  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
- };
1761
+ this.observations.event(args.name, attributes, this.root, startTime);
1635
1762
  }
1636
1763
  turnSpan(turn) {
1637
1764
  const existing = this.turnSpans.get(turn);
1638
1765
  if (existing !== void 0) {
1639
1766
  return existing;
1640
1767
  }
1641
- const span = this.root.startObservation(
1768
+ const span = this.observations.span(
1642
1769
  `turn.${turn}`,
1643
1770
  {
1644
1771
  metadata: { turn }
1645
1772
  },
1646
- { asType: "span" }
1773
+ this.root
1647
1774
  );
1648
1775
  this.turnSpans.set(turn, span);
1649
1776
  return span;
@@ -1686,7 +1813,9 @@ var LangfuseGenerationObserver = class {
1686
1813
  });
1687
1814
  }
1688
1815
  end(args) {
1689
- const redactedText = this.run.redactOutputValue(textFromAssistantContent(args.response.choice));
1816
+ const redactedText = this.run.redactOutputValue(
1817
+ textFromObservedAssistantContent(args.response.choice)
1818
+ );
1690
1819
  const redactedChoice = this.run.redactOutputValue(args.response.choice);
1691
1820
  const metadata = { turn: args.turn };
1692
1821
  if (args.firstDeltaMs !== void 0) metadata.firstDeltaMs = args.firstDeltaMs;
@@ -1718,13 +1847,18 @@ var LangfuseGenerationObserver = class {
1718
1847
  }).end();
1719
1848
  }
1720
1849
  };
1850
+ function textFromObservedAssistantContent(content) {
1851
+ return content.flatMap((item) => item.type === "text" ? [item.text] : []).join("\n");
1852
+ }
1721
1853
  var LangfuseToolObserver = class {
1722
- constructor(tool, run) {
1854
+ constructor(tool, run, observations) {
1723
1855
  this.tool = tool;
1724
1856
  this.run = run;
1857
+ this.observations = observations;
1725
1858
  }
1726
1859
  tool;
1727
1860
  run;
1861
+ observations;
1728
1862
  childAgents = /* @__PURE__ */ new Map();
1729
1863
  childGenerations = /* @__PURE__ */ new Map();
1730
1864
  childTools = [];
@@ -1741,7 +1875,7 @@ var LangfuseToolObserver = class {
1741
1875
  if (child.type === "turn_start") {
1742
1876
  const promptMessage = isRecord(child.prompt) ? child.prompt : void 0;
1743
1877
  const historyMessages = Array.isArray(child.history) ? child.history.filter(isRecord) : [];
1744
- agent.startObservation(
1878
+ this.observations.event(
1745
1879
  `${agentLabel(agentId, agentName)}.turn.${childTurn}.start`,
1746
1880
  {
1747
1881
  input: this.run.redactInputValue({
@@ -1752,7 +1886,7 @@ var LangfuseToolObserver = class {
1752
1886
  this.run.redactInputValue(childMetadata(args, agentId, agentName, childTurn))
1753
1887
  )
1754
1888
  },
1755
- { asType: "event" }
1889
+ agent
1756
1890
  );
1757
1891
  return;
1758
1892
  }
@@ -1769,15 +1903,15 @@ var LangfuseToolObserver = class {
1769
1903
  input.tools = request.tools;
1770
1904
  input.providerTools = request.providerTools;
1771
1905
  input.outputSchema = request.outputSchema;
1772
- input.additionalParams = request.additionalParams;
1906
+ input.providerOptions = request.providerOptions;
1773
1907
  }
1774
1908
  const toolNames = Array.isArray(request.tools) ? request.tools.filter(isRecord).map((tool) => tool.name).filter((name) => typeof name === "string") : [];
1775
1909
  const providerToolNames = Array.isArray(request.providerTools) ? request.providerTools.filter(isRecord).map((tool) => tool.name).filter((name) => typeof name === "string") : [];
1776
- const generation = agent.startObservation(
1910
+ const generation = this.observations.generation(
1777
1911
  `${agentLabel(agentId, agentName)}.model.turn.${childTurn}`,
1778
1912
  {
1779
1913
  input: this.run.redactInputValue(input),
1780
- model: typeof request.model === "string" ? request.model : typeof modelInfo?.defaultModel === "string" ? modelInfo.defaultModel : "default",
1914
+ model: typeof modelInfo?.modelId === "string" ? modelInfo.modelId : "unknown",
1781
1915
  modelParameters: modelParameters(request),
1782
1916
  metadata: asMetadata(
1783
1917
  this.run.redactInputValue({
@@ -1790,7 +1924,7 @@ var LangfuseToolObserver = class {
1790
1924
  })
1791
1925
  )
1792
1926
  },
1793
- { asType: "generation" }
1927
+ agent
1794
1928
  );
1795
1929
  this.childGenerations.set(generationKey(agentId, childTurn), {
1796
1930
  generation,
@@ -1830,7 +1964,7 @@ var LangfuseToolObserver = class {
1830
1964
  if (child.type === "source" || child.type === "provider_tool_call") {
1831
1965
  const childGeneration = this.childGenerations.get(generationKey(agentId, childTurn));
1832
1966
  const parent = childGeneration?.generation ?? agent;
1833
- parent.startObservation(
1967
+ this.observations.event(
1834
1968
  `${agentLabel(agentId, agentName)}.${child.type}`,
1835
1969
  {
1836
1970
  output: this.run.redactOutputValue(
@@ -1840,12 +1974,12 @@ var LangfuseToolObserver = class {
1840
1974
  this.run.redactOutputValue(childMetadata(args, agentId, agentName, childTurn))
1841
1975
  )
1842
1976
  },
1843
- { asType: "event" }
1977
+ parent
1844
1978
  );
1845
1979
  return;
1846
1980
  }
1847
1981
  if (child.type === "guardrail_decision") {
1848
- agent.startObservation(
1982
+ this.observations.guardrail(
1849
1983
  `${agentLabel(agentId, agentName)}.guardrail`,
1850
1984
  {
1851
1985
  output: this.run.redactOutputValue(child.decision),
@@ -1853,7 +1987,7 @@ var LangfuseToolObserver = class {
1853
1987
  this.run.redactOutputValue(childMetadata(args, agentId, agentName, childTurn))
1854
1988
  )
1855
1989
  },
1856
- { asType: "guardrail" }
1990
+ agent
1857
1991
  ).end();
1858
1992
  return;
1859
1993
  }
@@ -1863,14 +1997,13 @@ var LangfuseToolObserver = class {
1863
1997
  output: this.run.redactOutputValue({ toolCall: child.toolCall })
1864
1998
  });
1865
1999
  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(
2000
+ const toolName = typeof toolCall.toolName === "string" ? toolCall.toolName : "tool";
2001
+ const toolCallId = typeof toolCall.toolCallId === "string" ? toolCall.toolCallId : typeof toolCall.callId === "string" ? toolCall.callId : void 0;
2002
+ const childTool = this.observations.tool(
1870
2003
  `${agentLabel(agentId, agentName)}.${toolName}`,
1871
2004
  {
1872
2005
  input: this.run.redactInputValue({
1873
- args: toolCallFunction?.arguments ?? {},
2006
+ args: toolCall.input,
1874
2007
  toolCall
1875
2008
  }),
1876
2009
  metadata: asMetadata(
@@ -1881,7 +2014,7 @@ var LangfuseToolObserver = class {
1881
2014
  })
1882
2015
  )
1883
2016
  },
1884
- { asType: "tool" }
2017
+ agent
1885
2018
  );
1886
2019
  const childToolRecord = {
1887
2020
  agentId,
@@ -1917,13 +2050,18 @@ var LangfuseToolObserver = class {
1917
2050
  return;
1918
2051
  }
1919
2052
  if (child.type === "final") {
2053
+ const result = isRecord(child.result) ? child.result : {};
1920
2054
  const update = {
1921
- output: this.run.redactOutputValue(child.output)
2055
+ output: this.run.redactOutputValue({
2056
+ status: result.status,
2057
+ output: result.output,
2058
+ text: result.text
2059
+ })
1922
2060
  };
1923
2061
  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);
2062
+ if (isRecord(result.usage)) metadata.usage = result.usage;
2063
+ if (this.run.isFullCapture() && Array.isArray(result.messages)) {
2064
+ metadata.messages = this.run.redactTranscript(result.messages);
1927
2065
  }
1928
2066
  if (Object.keys(metadata).length > 0) update.metadata = metadata;
1929
2067
  agent.update(update).end();
@@ -1989,14 +2127,14 @@ var LangfuseToolObserver = class {
1989
2127
  if (existing !== void 0) {
1990
2128
  return existing;
1991
2129
  }
1992
- const agent = this.tool.startObservation(
2130
+ const agent = this.observations.agent(
1993
2131
  `${agentLabel(agentId, agentName)}.run`,
1994
2132
  {
1995
2133
  metadata: asMetadata(
1996
2134
  this.run.redactInputValue(childMetadata(args, agentId, agentName, args.turn))
1997
2135
  )
1998
2136
  },
1999
- { asType: "agent" }
2137
+ this.tool
2000
2138
  );
2001
2139
  this.childAgents.set(agentId, agent);
2002
2140
  return agent;
@@ -2032,12 +2170,8 @@ var LangfuseToolObserver = class {
2032
2170
  };
2033
2171
  export {
2034
2172
  DEFAULT_PATTERNS,
2173
+ LangfuseClient,
2035
2174
  LangfuseScoreError,
2036
- createLangfuseDatasetClient,
2037
- createLangfuseEvalReporter,
2038
- createLangfusePromptClient,
2039
- createPiiRedactor,
2040
- langfuse,
2041
- runEvalAsExperiment
2175
+ createPiiRedactor
2042
2176
  };
2043
2177
  //# sourceMappingURL=index.js.map