@mastra/langfuse 0.0.0-iterate-traces-ui-again-20250912091900 → 0.0.0-main-test-20251105183450
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/CHANGELOG.md +290 -3
- package/README.md +1 -5
- package/dist/index.cjs +130 -37
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +129 -36
- package/dist/index.js.map +1 -1
- package/dist/tracing.d.ts +67 -0
- package/dist/tracing.d.ts.map +1 -0
- package/package.json +11 -7
- package/dist/ai-tracing.d.ts +0 -41
- package/dist/ai-tracing.d.ts.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -1,24 +1,23 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
4
|
-
var
|
|
3
|
+
var observability$1 = require('@mastra/core/observability');
|
|
4
|
+
var utils = require('@mastra/core/utils');
|
|
5
|
+
var observability = require('@mastra/observability');
|
|
5
6
|
var langfuse = require('langfuse');
|
|
6
7
|
|
|
7
|
-
// src/
|
|
8
|
-
var LangfuseExporter = class {
|
|
8
|
+
// src/tracing.ts
|
|
9
|
+
var LangfuseExporter = class extends observability.BaseExporter {
|
|
9
10
|
name = "langfuse";
|
|
10
11
|
client;
|
|
11
12
|
realtime;
|
|
12
13
|
traceMap = /* @__PURE__ */ new Map();
|
|
13
|
-
logger;
|
|
14
14
|
constructor(config) {
|
|
15
|
+
super(config);
|
|
15
16
|
this.realtime = config.realtime ?? false;
|
|
16
|
-
this.logger = new logger.ConsoleLogger({ level: config.logLevel ?? "warn" });
|
|
17
17
|
if (!config.publicKey || !config.secretKey) {
|
|
18
|
-
this.
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
});
|
|
18
|
+
this.setDisabled(
|
|
19
|
+
`Missing required credentials (publicKey: ${!!config.publicKey}, secretKey: ${!!config.secretKey})`
|
|
20
|
+
);
|
|
22
21
|
this.client = null;
|
|
23
22
|
return;
|
|
24
23
|
}
|
|
@@ -29,23 +28,20 @@ var LangfuseExporter = class {
|
|
|
29
28
|
...config.options
|
|
30
29
|
});
|
|
31
30
|
}
|
|
32
|
-
async
|
|
33
|
-
if (
|
|
34
|
-
|
|
35
|
-
}
|
|
36
|
-
if (event.span.isEvent) {
|
|
37
|
-
await this.handleEventSpan(event.span);
|
|
31
|
+
async _exportTracingEvent(event) {
|
|
32
|
+
if (event.exportedSpan.isEvent) {
|
|
33
|
+
await this.handleEventSpan(event.exportedSpan);
|
|
38
34
|
return;
|
|
39
35
|
}
|
|
40
36
|
switch (event.type) {
|
|
41
37
|
case "span_started":
|
|
42
|
-
await this.handleSpanStarted(event.
|
|
38
|
+
await this.handleSpanStarted(event.exportedSpan);
|
|
43
39
|
break;
|
|
44
40
|
case "span_updated":
|
|
45
|
-
await this.handleSpanUpdateOrEnd(event.
|
|
41
|
+
await this.handleSpanUpdateOrEnd(event.exportedSpan, false);
|
|
46
42
|
break;
|
|
47
43
|
case "span_ended":
|
|
48
|
-
await this.handleSpanUpdateOrEnd(event.
|
|
44
|
+
await this.handleSpanUpdateOrEnd(event.exportedSpan, true);
|
|
49
45
|
break;
|
|
50
46
|
}
|
|
51
47
|
if (this.realtime) {
|
|
@@ -66,8 +62,9 @@ var LangfuseExporter = class {
|
|
|
66
62
|
return;
|
|
67
63
|
}
|
|
68
64
|
const payload = this.buildSpanPayload(span, true);
|
|
69
|
-
const langfuseSpan = span.type ===
|
|
65
|
+
const langfuseSpan = span.type === observability$1.SpanType.MODEL_GENERATION ? langfuseParent.generation(payload) : langfuseParent.span(payload);
|
|
70
66
|
traceData.spans.set(span.id, langfuseSpan);
|
|
67
|
+
traceData.activeSpans.add(span.id);
|
|
71
68
|
}
|
|
72
69
|
async handleSpanUpdateOrEnd(span, isEnd) {
|
|
73
70
|
const method = isEnd ? "handleSpanEnd" : "handleSpanUpdate";
|
|
@@ -77,21 +74,33 @@ var LangfuseExporter = class {
|
|
|
77
74
|
}
|
|
78
75
|
const langfuseSpan = traceData.spans.get(span.id);
|
|
79
76
|
if (!langfuseSpan) {
|
|
77
|
+
if (isEnd && span.isEvent) {
|
|
78
|
+
traceData.activeSpans.delete(span.id);
|
|
79
|
+
if (traceData.activeSpans.size === 0) {
|
|
80
|
+
this.traceMap.delete(span.traceId);
|
|
81
|
+
}
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
80
84
|
this.logger.warn("Langfuse exporter: No Langfuse span found for span update/end", {
|
|
81
85
|
traceId: span.traceId,
|
|
82
86
|
spanId: span.id,
|
|
83
87
|
spanName: span.name,
|
|
84
88
|
spanType: span.type,
|
|
85
89
|
isRootSpan: span.isRootSpan,
|
|
86
|
-
parentSpanId: span.
|
|
90
|
+
parentSpanId: span.parentSpanId,
|
|
87
91
|
method
|
|
88
92
|
});
|
|
89
93
|
return;
|
|
90
94
|
}
|
|
91
95
|
langfuseSpan.update(this.buildSpanPayload(span, false));
|
|
92
|
-
if (isEnd
|
|
93
|
-
traceData.
|
|
94
|
-
|
|
96
|
+
if (isEnd) {
|
|
97
|
+
traceData.activeSpans.delete(span.id);
|
|
98
|
+
if (span.isRootSpan) {
|
|
99
|
+
traceData.trace.update({ output: span.output });
|
|
100
|
+
}
|
|
101
|
+
if (traceData.activeSpans.size === 0) {
|
|
102
|
+
this.traceMap.delete(span.traceId);
|
|
103
|
+
}
|
|
95
104
|
}
|
|
96
105
|
}
|
|
97
106
|
async handleEventSpan(span) {
|
|
@@ -116,10 +125,19 @@ var LangfuseExporter = class {
|
|
|
116
125
|
const payload = this.buildSpanPayload(span, true);
|
|
117
126
|
const langfuseEvent = langfuseParent.event(payload);
|
|
118
127
|
traceData.events.set(span.id, langfuseEvent);
|
|
128
|
+
if (!span.endTime) {
|
|
129
|
+
traceData.activeSpans.add(span.id);
|
|
130
|
+
}
|
|
119
131
|
}
|
|
120
132
|
initTrace(span) {
|
|
121
133
|
const trace = this.client.trace(this.buildTracePayload(span));
|
|
122
|
-
this.traceMap.set(span.traceId, {
|
|
134
|
+
this.traceMap.set(span.traceId, {
|
|
135
|
+
trace,
|
|
136
|
+
spans: /* @__PURE__ */ new Map(),
|
|
137
|
+
events: /* @__PURE__ */ new Map(),
|
|
138
|
+
activeSpans: /* @__PURE__ */ new Set(),
|
|
139
|
+
rootSpanId: span.id
|
|
140
|
+
});
|
|
123
141
|
}
|
|
124
142
|
getTraceData(options) {
|
|
125
143
|
const { span, method } = options;
|
|
@@ -132,13 +150,13 @@ var LangfuseExporter = class {
|
|
|
132
150
|
spanName: span.name,
|
|
133
151
|
spanType: span.type,
|
|
134
152
|
isRootSpan: span.isRootSpan,
|
|
135
|
-
parentSpanId: span.
|
|
153
|
+
parentSpanId: span.parentSpanId,
|
|
136
154
|
method
|
|
137
155
|
});
|
|
138
156
|
}
|
|
139
157
|
getLangfuseParent(options) {
|
|
140
158
|
const { traceData, span, method } = options;
|
|
141
|
-
const parentId = span.
|
|
159
|
+
const parentId = span.parentSpanId;
|
|
142
160
|
if (!parentId) {
|
|
143
161
|
return traceData.trace;
|
|
144
162
|
}
|
|
@@ -154,7 +172,7 @@ var LangfuseExporter = class {
|
|
|
154
172
|
spanName: span.name,
|
|
155
173
|
spanType: span.type,
|
|
156
174
|
isRootSpan: span.isRootSpan,
|
|
157
|
-
parentSpanId: span.
|
|
175
|
+
parentSpanId: span.parentSpanId,
|
|
158
176
|
method
|
|
159
177
|
});
|
|
160
178
|
}
|
|
@@ -174,6 +192,48 @@ var LangfuseExporter = class {
|
|
|
174
192
|
};
|
|
175
193
|
return payload;
|
|
176
194
|
}
|
|
195
|
+
/**
|
|
196
|
+
* Normalize usage data to handle both AI SDK v4 and v5 formats.
|
|
197
|
+
*
|
|
198
|
+
* AI SDK v4 uses: promptTokens, completionTokens
|
|
199
|
+
* AI SDK v5 uses: inputTokens, outputTokens
|
|
200
|
+
*
|
|
201
|
+
* This function normalizes to a unified format that Langfuse can consume,
|
|
202
|
+
* prioritizing v5 format while maintaining backward compatibility.
|
|
203
|
+
*
|
|
204
|
+
* @param usage - Token usage data from AI SDK (v4 or v5 format)
|
|
205
|
+
* @returns Normalized usage object, or undefined if no usage data available
|
|
206
|
+
*/
|
|
207
|
+
normalizeUsage(usage) {
|
|
208
|
+
if (!usage) return void 0;
|
|
209
|
+
const normalized = {};
|
|
210
|
+
const inputTokens = usage.inputTokens ?? usage.promptTokens;
|
|
211
|
+
if (inputTokens !== void 0) {
|
|
212
|
+
normalized.input = inputTokens;
|
|
213
|
+
}
|
|
214
|
+
const outputTokens = usage.outputTokens ?? usage.completionTokens;
|
|
215
|
+
if (outputTokens !== void 0) {
|
|
216
|
+
normalized.output = outputTokens;
|
|
217
|
+
}
|
|
218
|
+
if (usage.totalTokens !== void 0) {
|
|
219
|
+
normalized.total = usage.totalTokens;
|
|
220
|
+
} else if (normalized.input !== void 0 && normalized.output !== void 0) {
|
|
221
|
+
normalized.total = normalized.input + normalized.output;
|
|
222
|
+
}
|
|
223
|
+
if (usage.reasoningTokens !== void 0) {
|
|
224
|
+
normalized.reasoning = usage.reasoningTokens;
|
|
225
|
+
}
|
|
226
|
+
if (usage.cachedInputTokens !== void 0) {
|
|
227
|
+
normalized.cachedInput = usage.cachedInputTokens;
|
|
228
|
+
}
|
|
229
|
+
if (usage.promptCacheHitTokens !== void 0) {
|
|
230
|
+
normalized.promptCacheHit = usage.promptCacheHitTokens;
|
|
231
|
+
}
|
|
232
|
+
if (usage.promptCacheMissTokens !== void 0) {
|
|
233
|
+
normalized.promptCacheMiss = usage.promptCacheMissTokens;
|
|
234
|
+
}
|
|
235
|
+
return Object.keys(normalized).length > 0 ? normalized : void 0;
|
|
236
|
+
}
|
|
177
237
|
buildSpanPayload(span, isCreate) {
|
|
178
238
|
const payload = {};
|
|
179
239
|
if (isCreate) {
|
|
@@ -186,24 +246,27 @@ var LangfuseExporter = class {
|
|
|
186
246
|
if (span.endTime !== void 0) payload.endTime = span.endTime;
|
|
187
247
|
const attributes = span.attributes ?? {};
|
|
188
248
|
const attributesToOmit = [];
|
|
189
|
-
if (span.type ===
|
|
190
|
-
const
|
|
191
|
-
if (
|
|
192
|
-
payload.model =
|
|
249
|
+
if (span.type === observability$1.SpanType.MODEL_GENERATION) {
|
|
250
|
+
const modelAttr = attributes;
|
|
251
|
+
if (modelAttr.model !== void 0) {
|
|
252
|
+
payload.model = modelAttr.model;
|
|
193
253
|
attributesToOmit.push("model");
|
|
194
254
|
}
|
|
195
|
-
if (
|
|
196
|
-
|
|
255
|
+
if (modelAttr.usage !== void 0) {
|
|
256
|
+
const normalizedUsage = this.normalizeUsage(modelAttr.usage);
|
|
257
|
+
if (normalizedUsage) {
|
|
258
|
+
payload.usage = normalizedUsage;
|
|
259
|
+
}
|
|
197
260
|
attributesToOmit.push("usage");
|
|
198
261
|
}
|
|
199
|
-
if (
|
|
200
|
-
payload.modelParameters =
|
|
262
|
+
if (modelAttr.parameters !== void 0) {
|
|
263
|
+
payload.modelParameters = modelAttr.parameters;
|
|
201
264
|
attributesToOmit.push("parameters");
|
|
202
265
|
}
|
|
203
266
|
}
|
|
204
267
|
payload.metadata = {
|
|
205
268
|
spanType: span.type,
|
|
206
|
-
...
|
|
269
|
+
...utils.omitKeys(attributes, attributesToOmit),
|
|
207
270
|
...span.metadata
|
|
208
271
|
};
|
|
209
272
|
if (span.errorInfo) {
|
|
@@ -212,11 +275,41 @@ var LangfuseExporter = class {
|
|
|
212
275
|
}
|
|
213
276
|
return payload;
|
|
214
277
|
}
|
|
278
|
+
async addScoreToTrace({
|
|
279
|
+
traceId,
|
|
280
|
+
spanId,
|
|
281
|
+
score,
|
|
282
|
+
reason,
|
|
283
|
+
scorerName,
|
|
284
|
+
metadata
|
|
285
|
+
}) {
|
|
286
|
+
if (!this.client) return;
|
|
287
|
+
try {
|
|
288
|
+
await this.client.score({
|
|
289
|
+
id: `${traceId}-${scorerName}`,
|
|
290
|
+
traceId,
|
|
291
|
+
observationId: spanId,
|
|
292
|
+
name: scorerName,
|
|
293
|
+
value: score,
|
|
294
|
+
...metadata?.sessionId ? { sessionId: metadata.sessionId } : {},
|
|
295
|
+
metadata: { ...reason ? { reason } : {} },
|
|
296
|
+
dataType: "NUMERIC"
|
|
297
|
+
});
|
|
298
|
+
} catch (error) {
|
|
299
|
+
this.logger.error("Langfuse exporter: Error adding score to trace", {
|
|
300
|
+
error,
|
|
301
|
+
traceId,
|
|
302
|
+
spanId,
|
|
303
|
+
scorerName
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
}
|
|
215
307
|
async shutdown() {
|
|
216
308
|
if (this.client) {
|
|
217
309
|
await this.client.shutdownAsync();
|
|
218
310
|
}
|
|
219
311
|
this.traceMap.clear();
|
|
312
|
+
await super.shutdown();
|
|
220
313
|
}
|
|
221
314
|
};
|
|
222
315
|
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/ai-tracing.ts"],"names":["ConsoleLogger","Langfuse","AISpanType","omitKeys"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Langfuse Exporter for Mastra AI Tracing\n *\n * This exporter sends tracing data to Langfuse for AI observability.\n * Root spans start traces in Langfuse.\n * LLM_GENERATION spans become Langfuse generations, all others become spans.\n */\n\nimport type { AITracingExporter, AITracingEvent, AnyAISpan, LLMGenerationAttributes } from '@mastra/core/ai-tracing';\nimport { AISpanType, omitKeys } from '@mastra/core/ai-tracing';\nimport { ConsoleLogger } from '@mastra/core/logger';\nimport { Langfuse } from 'langfuse';\nimport type { LangfuseTraceClient, LangfuseSpanClient, LangfuseGenerationClient, LangfuseEventClient } from 'langfuse';\n\nexport interface LangfuseExporterConfig {\n /** Langfuse API key */\n publicKey?: string;\n /** Langfuse secret key */\n secretKey?: string;\n /** Langfuse host URL */\n baseUrl?: string;\n /** Enable realtime mode - flushes after each event for immediate visibility */\n realtime?: boolean;\n /** Logger level for diagnostic messages (default: 'warn') */\n logLevel?: 'debug' | 'info' | 'warn' | 'error';\n /** Additional options to pass to the Langfuse client */\n options?: any;\n}\n\ntype TraceData = {\n trace: LangfuseTraceClient; // Langfuse trace object\n spans: Map<string, LangfuseSpanClient | LangfuseGenerationClient>; // Maps span.id to Langfuse span/generation\n events: Map<string, LangfuseEventClient>; // Maps span.id to Langfuse event\n};\n\ntype LangfuseParent = LangfuseTraceClient | LangfuseSpanClient | LangfuseGenerationClient | LangfuseEventClient;\n\nexport class LangfuseExporter implements AITracingExporter {\n name = 'langfuse';\n private client: Langfuse;\n private realtime: boolean;\n private traceMap = new Map<string, TraceData>();\n private logger: ConsoleLogger;\n\n constructor(config: LangfuseExporterConfig) {\n this.realtime = config.realtime ?? false;\n this.logger = new ConsoleLogger({ level: config.logLevel ?? 'warn' });\n\n if (!config.publicKey || !config.secretKey) {\n this.logger.error('LangfuseExporter: Missing required credentials, exporter will be disabled', {\n hasPublicKey: !!config.publicKey,\n hasSecretKey: !!config.secretKey,\n });\n // Create a no-op client to prevent runtime errors\n this.client = null as any;\n return;\n }\n\n this.client = new Langfuse({\n publicKey: config.publicKey,\n secretKey: config.secretKey,\n baseUrl: config.baseUrl,\n ...config.options,\n });\n }\n\n async exportEvent(event: AITracingEvent): Promise<void> {\n if (!this.client) {\n // Exporter is disabled due to missing credentials\n return;\n }\n\n if (event.span.isEvent) {\n await this.handleEventSpan(event.span);\n return;\n }\n\n switch (event.type) {\n case 'span_started':\n await this.handleSpanStarted(event.span);\n break;\n case 'span_updated':\n await this.handleSpanUpdateOrEnd(event.span, false);\n break;\n case 'span_ended':\n await this.handleSpanUpdateOrEnd(event.span, true);\n break;\n }\n\n // Flush immediately in realtime mode for instant visibility\n if (this.realtime) {\n await this.client.flushAsync();\n }\n }\n\n private async handleSpanStarted(span: AnyAISpan): Promise<void> {\n if (span.isRootSpan) {\n this.initTrace(span);\n }\n const method = 'handleSpanStarted';\n\n const traceData = this.getTraceData({ span, method });\n if (!traceData) {\n return;\n }\n\n const langfuseParent = this.getLangfuseParent({ traceData, span, method });\n if (!langfuseParent) {\n return;\n }\n\n const payload = this.buildSpanPayload(span, true);\n\n const langfuseSpan =\n span.type === AISpanType.LLM_GENERATION ? langfuseParent.generation(payload) : langfuseParent.span(payload);\n\n traceData.spans.set(span.id, langfuseSpan);\n }\n\n private async handleSpanUpdateOrEnd(span: AnyAISpan, isEnd: boolean): Promise<void> {\n const method = isEnd ? 'handleSpanEnd' : 'handleSpanUpdate';\n\n const traceData = this.getTraceData({ span, method });\n if (!traceData) {\n return;\n }\n\n const langfuseSpan = traceData.spans.get(span.id);\n if (!langfuseSpan) {\n this.logger.warn('Langfuse exporter: No Langfuse span found for span update/end', {\n traceId: span.traceId,\n spanId: span.id,\n spanName: span.name,\n spanType: span.type,\n isRootSpan: span.isRootSpan,\n parentSpanId: span.parent?.id,\n method,\n });\n return;\n }\n\n // use update for both update & end, so that we can use the\n // end time we set when ending the span.\n langfuseSpan.update(this.buildSpanPayload(span, false));\n\n if (isEnd && span.isRootSpan) {\n traceData.trace.update({ output: span.output });\n this.traceMap.delete(span.traceId);\n }\n }\n\n private async handleEventSpan(span: AnyAISpan): Promise<void> {\n if (span.isRootSpan) {\n this.logger.debug('Langfuse exporter: Creating trace', {\n traceId: span.traceId,\n spanId: span.id,\n spanName: span.name,\n method: 'handleEventSpan',\n });\n this.initTrace(span);\n }\n const method = 'handleEventSpan';\n\n const traceData = this.getTraceData({ span, method });\n if (!traceData) {\n return;\n }\n\n const langfuseParent = this.getLangfuseParent({ traceData, span, method });\n if (!langfuseParent) {\n return;\n }\n\n const payload = this.buildSpanPayload(span, true);\n\n const langfuseEvent = langfuseParent.event(payload);\n\n traceData.events.set(span.id, langfuseEvent);\n }\n\n private initTrace(span: AnyAISpan): void {\n const trace = this.client.trace(this.buildTracePayload(span));\n this.traceMap.set(span.traceId, { trace, spans: new Map(), events: new Map() });\n }\n\n private getTraceData(options: { span: AnyAISpan; method: string }): TraceData | undefined {\n const { span, method } = options;\n if (this.traceMap.has(span.traceId)) {\n return this.traceMap.get(span.traceId);\n }\n this.logger.warn('Langfuse exporter: No trace data found for span', {\n traceId: span.traceId,\n spanId: span.id,\n spanName: span.name,\n spanType: span.type,\n isRootSpan: span.isRootSpan,\n parentSpanId: span.parent?.id,\n method,\n });\n }\n\n private getLangfuseParent(options: {\n traceData: TraceData;\n span: AnyAISpan;\n method: string;\n }): LangfuseParent | undefined {\n const { traceData, span, method } = options;\n\n const parentId = span.parent?.id;\n if (!parentId) {\n return traceData.trace;\n }\n if (traceData.spans.has(parentId)) {\n return traceData.spans.get(parentId);\n }\n if (traceData.events.has(parentId)) {\n return traceData.events.get(parentId);\n }\n this.logger.warn('Langfuse exporter: No parent data found for span', {\n traceId: span.traceId,\n spanId: span.id,\n spanName: span.name,\n spanType: span.type,\n isRootSpan: span.isRootSpan,\n parentSpanId: span.parent?.id,\n method,\n });\n }\n\n private buildTracePayload(span: AnyAISpan): Record<string, any> {\n const payload: Record<string, any> = {\n id: span.traceId,\n name: span.name,\n };\n\n const { userId, sessionId, ...remainingMetadata } = span.metadata ?? {};\n\n if (userId) payload.userId = userId;\n if (sessionId) payload.sessionId = sessionId;\n if (span.input) payload.input = span.input;\n\n payload.metadata = {\n spanType: span.type,\n ...span.attributes,\n ...remainingMetadata,\n };\n\n return payload;\n }\n\n private buildSpanPayload(span: AnyAISpan, isCreate: boolean): Record<string, any> {\n const payload: Record<string, any> = {};\n\n if (isCreate) {\n payload.id = span.id;\n payload.name = span.name;\n payload.startTime = span.startTime;\n if (span.input !== undefined) payload.input = span.input;\n }\n\n if (span.output !== undefined) payload.output = span.output;\n if (span.endTime !== undefined) payload.endTime = span.endTime;\n\n const attributes = (span.attributes ?? {}) as Record<string, any>;\n\n // Strip special fields from metadata if used in top-level keys\n const attributesToOmit: string[] = [];\n\n if (span.type === AISpanType.LLM_GENERATION) {\n const llmAttr = attributes as LLMGenerationAttributes;\n\n if (llmAttr.model !== undefined) {\n payload.model = llmAttr.model;\n attributesToOmit.push('model');\n }\n\n if (llmAttr.usage !== undefined) {\n payload.usage = llmAttr.usage;\n attributesToOmit.push('usage');\n }\n\n if (llmAttr.parameters !== undefined) {\n payload.modelParameters = llmAttr.parameters;\n attributesToOmit.push('parameters');\n }\n }\n\n payload.metadata = {\n spanType: span.type,\n ...omitKeys(attributes, attributesToOmit),\n ...span.metadata,\n };\n\n if (span.errorInfo) {\n payload.level = 'ERROR';\n payload.statusMessage = span.errorInfo.message;\n }\n\n return payload;\n }\n\n async shutdown(): Promise<void> {\n if (this.client) {\n await this.client.shutdownAsync();\n }\n this.traceMap.clear();\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/tracing.ts"],"names":["BaseExporter","Langfuse","SpanType","omitKeys"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Langfuse Exporter for Mastra Tracing\n *\n * This exporter sends tracing data to Langfuse for AI observability.\n * Root spans start traces in Langfuse.\n * MODEL_GENERATION spans become Langfuse generations, all others become spans.\n *\n * Compatible with both AI SDK v4 and v5:\n * - Handles both legacy token usage format (promptTokens/completionTokens)\n * and v5 format (inputTokens/outputTokens)\n * - Supports v5 reasoning tokens and cache-related metrics\n * - Adapts to v5 streaming protocol changes\n */\n\nimport type { TracingEvent, AnyExportedSpan, ModelGenerationAttributes } from '@mastra/core/observability';\nimport { SpanType } from '@mastra/core/observability';\nimport { omitKeys } from '@mastra/core/utils';\nimport { BaseExporter } from '@mastra/observability';\nimport type { BaseExporterConfig } from '@mastra/observability';\nimport { Langfuse } from 'langfuse';\nimport type { LangfuseTraceClient, LangfuseSpanClient, LangfuseGenerationClient, LangfuseEventClient } from 'langfuse';\n\nexport interface LangfuseExporterConfig extends BaseExporterConfig {\n /** Langfuse API key */\n publicKey?: string;\n /** Langfuse secret key */\n secretKey?: string;\n /** Langfuse host URL */\n baseUrl?: string;\n /** Enable realtime mode - flushes after each event for immediate visibility */\n realtime?: boolean;\n /** Additional options to pass to the Langfuse client */\n options?: any;\n}\n\ntype TraceData = {\n trace: LangfuseTraceClient; // Langfuse trace object\n spans: Map<string, LangfuseSpanClient | LangfuseGenerationClient>; // Maps span.id to Langfuse span/generation\n events: Map<string, LangfuseEventClient>; // Maps span.id to Langfuse event\n activeSpans: Set<string>; // Tracks which spans haven't ended yet\n rootSpanId?: string; // Track the root span ID\n};\n\ntype LangfuseParent = LangfuseTraceClient | LangfuseSpanClient | LangfuseGenerationClient | LangfuseEventClient;\n\n/**\n * Normalized token usage format compatible with Langfuse.\n * This unified format supports both AI SDK v4 and v5 token structures.\n *\n * @example\n * ```typescript\n * // AI SDK v4 format normalizes to:\n * { input: 100, output: 50, total: 150 }\n *\n * // AI SDK v5 format normalizes to:\n * { input: 120, output: 60, total: 180, reasoning: 1000, cachedInput: 50 }\n * ```\n */\ninterface NormalizedUsage {\n /**\n * Input tokens sent to the model\n * @source AI SDK v5: `inputTokens` | AI SDK v4: `promptTokens`\n */\n input?: number;\n\n /**\n * Output tokens received from the model\n * @source AI SDK v5: `outputTokens` | AI SDK v4: `completionTokens`\n */\n output?: number;\n\n /**\n * Total tokens (input + output + reasoning if applicable)\n * @source AI SDK v4 & v5: `totalTokens`\n */\n total?: number;\n\n /**\n * Reasoning tokens used by reasoning models\n * @source AI SDK v5: `reasoningTokens`\n * @since AI SDK v5.0.0\n * @example Models like o1-preview, o1-mini\n */\n reasoning?: number;\n\n /**\n * Cached input tokens (prompt cache hit)\n * @source AI SDK v5: `cachedInputTokens`\n * @since AI SDK v5.0.0\n * @example Anthropic's prompt caching, OpenAI prompt caching\n */\n cachedInput?: number;\n\n /**\n * Prompt cache hit tokens (legacy format)\n * @source AI SDK v4: `promptCacheHitTokens`\n * @deprecated Prefer `cachedInput` from v5 format\n */\n promptCacheHit?: number;\n\n /**\n * Prompt cache miss tokens (legacy format)\n * @source AI SDK v4: `promptCacheMissTokens`\n * @deprecated Prefer v5 format which uses `cachedInputTokens`\n */\n promptCacheMiss?: number;\n}\n\nexport class LangfuseExporter extends BaseExporter {\n name = 'langfuse';\n private client: Langfuse;\n private realtime: boolean;\n private traceMap = new Map<string, TraceData>();\n\n constructor(config: LangfuseExporterConfig) {\n super(config);\n\n this.realtime = config.realtime ?? false;\n\n if (!config.publicKey || !config.secretKey) {\n this.setDisabled(\n `Missing required credentials (publicKey: ${!!config.publicKey}, secretKey: ${!!config.secretKey})`,\n );\n // Create a no-op client to prevent runtime errors\n this.client = null as any;\n return;\n }\n\n this.client = new Langfuse({\n publicKey: config.publicKey,\n secretKey: config.secretKey,\n baseUrl: config.baseUrl,\n ...config.options,\n });\n }\n\n protected async _exportTracingEvent(event: TracingEvent): Promise<void> {\n if (event.exportedSpan.isEvent) {\n await this.handleEventSpan(event.exportedSpan);\n return;\n }\n\n switch (event.type) {\n case 'span_started':\n await this.handleSpanStarted(event.exportedSpan);\n break;\n case 'span_updated':\n await this.handleSpanUpdateOrEnd(event.exportedSpan, false);\n break;\n case 'span_ended':\n await this.handleSpanUpdateOrEnd(event.exportedSpan, true);\n break;\n }\n\n // Flush immediately in realtime mode for instant visibility\n if (this.realtime) {\n await this.client.flushAsync();\n }\n }\n\n private async handleSpanStarted(span: AnyExportedSpan): Promise<void> {\n if (span.isRootSpan) {\n this.initTrace(span);\n }\n const method = 'handleSpanStarted';\n\n const traceData = this.getTraceData({ span, method });\n if (!traceData) {\n return;\n }\n\n const langfuseParent = this.getLangfuseParent({ traceData, span, method });\n if (!langfuseParent) {\n return;\n }\n\n const payload = this.buildSpanPayload(span, true);\n\n const langfuseSpan =\n span.type === SpanType.MODEL_GENERATION ? langfuseParent.generation(payload) : langfuseParent.span(payload);\n\n traceData.spans.set(span.id, langfuseSpan);\n traceData.activeSpans.add(span.id); // Track as active\n }\n\n private async handleSpanUpdateOrEnd(span: AnyExportedSpan, isEnd: boolean): Promise<void> {\n const method = isEnd ? 'handleSpanEnd' : 'handleSpanUpdate';\n\n const traceData = this.getTraceData({ span, method });\n if (!traceData) {\n return;\n }\n\n const langfuseSpan = traceData.spans.get(span.id);\n if (!langfuseSpan) {\n // For event spans that only send SPAN_ENDED, we might not have the span yet\n if (isEnd && span.isEvent) {\n // Just make sure it's not in active spans\n traceData.activeSpans.delete(span.id);\n if (traceData.activeSpans.size === 0) {\n this.traceMap.delete(span.traceId);\n }\n return;\n }\n\n this.logger.warn('Langfuse exporter: No Langfuse span found for span update/end', {\n traceId: span.traceId,\n spanId: span.id,\n spanName: span.name,\n spanType: span.type,\n isRootSpan: span.isRootSpan,\n parentSpanId: span.parentSpanId,\n method,\n });\n return;\n }\n\n // use update for both update & end, so that we can use the\n // end time we set when ending the span.\n langfuseSpan.update(this.buildSpanPayload(span, false));\n\n if (isEnd) {\n // Remove from active spans\n traceData.activeSpans.delete(span.id);\n\n if (span.isRootSpan) {\n traceData.trace.update({ output: span.output });\n }\n\n // Only clean up the trace when ALL spans have ended\n if (traceData.activeSpans.size === 0) {\n this.traceMap.delete(span.traceId);\n }\n }\n }\n\n private async handleEventSpan(span: AnyExportedSpan): Promise<void> {\n if (span.isRootSpan) {\n this.logger.debug('Langfuse exporter: Creating trace', {\n traceId: span.traceId,\n spanId: span.id,\n spanName: span.name,\n method: 'handleEventSpan',\n });\n this.initTrace(span);\n }\n const method = 'handleEventSpan';\n\n const traceData = this.getTraceData({ span, method });\n if (!traceData) {\n return;\n }\n\n const langfuseParent = this.getLangfuseParent({ traceData, span, method });\n if (!langfuseParent) {\n return;\n }\n\n const payload = this.buildSpanPayload(span, true);\n\n const langfuseEvent = langfuseParent.event(payload);\n\n traceData.events.set(span.id, langfuseEvent);\n\n // Event spans are typically immediately ended, but let's track them properly\n if (!span.endTime) {\n traceData.activeSpans.add(span.id);\n }\n }\n\n private initTrace(span: AnyExportedSpan): void {\n const trace = this.client.trace(this.buildTracePayload(span));\n this.traceMap.set(span.traceId, {\n trace,\n spans: new Map(),\n events: new Map(),\n activeSpans: new Set(),\n rootSpanId: span.id,\n });\n }\n\n private getTraceData(options: { span: AnyExportedSpan; method: string }): TraceData | undefined {\n const { span, method } = options;\n\n if (this.traceMap.has(span.traceId)) {\n return this.traceMap.get(span.traceId);\n }\n\n this.logger.warn('Langfuse exporter: No trace data found for span', {\n traceId: span.traceId,\n spanId: span.id,\n spanName: span.name,\n spanType: span.type,\n isRootSpan: span.isRootSpan,\n parentSpanId: span.parentSpanId,\n method,\n });\n }\n\n private getLangfuseParent(options: {\n traceData: TraceData;\n span: AnyExportedSpan;\n method: string;\n }): LangfuseParent | undefined {\n const { traceData, span, method } = options;\n\n const parentId = span.parentSpanId;\n if (!parentId) {\n return traceData.trace;\n }\n if (traceData.spans.has(parentId)) {\n return traceData.spans.get(parentId);\n }\n if (traceData.events.has(parentId)) {\n return traceData.events.get(parentId);\n }\n this.logger.warn('Langfuse exporter: No parent data found for span', {\n traceId: span.traceId,\n spanId: span.id,\n spanName: span.name,\n spanType: span.type,\n isRootSpan: span.isRootSpan,\n parentSpanId: span.parentSpanId,\n method,\n });\n }\n\n private buildTracePayload(span: AnyExportedSpan): Record<string, any> {\n const payload: Record<string, any> = {\n id: span.traceId,\n name: span.name,\n };\n\n const { userId, sessionId, ...remainingMetadata } = span.metadata ?? {};\n\n if (userId) payload.userId = userId;\n if (sessionId) payload.sessionId = sessionId;\n if (span.input) payload.input = span.input;\n\n payload.metadata = {\n spanType: span.type,\n ...span.attributes,\n ...remainingMetadata,\n };\n\n return payload;\n }\n\n /**\n * Normalize usage data to handle both AI SDK v4 and v5 formats.\n *\n * AI SDK v4 uses: promptTokens, completionTokens\n * AI SDK v5 uses: inputTokens, outputTokens\n *\n * This function normalizes to a unified format that Langfuse can consume,\n * prioritizing v5 format while maintaining backward compatibility.\n *\n * @param usage - Token usage data from AI SDK (v4 or v5 format)\n * @returns Normalized usage object, or undefined if no usage data available\n */\n private normalizeUsage(usage: ModelGenerationAttributes['usage']): NormalizedUsage | undefined {\n if (!usage) return undefined;\n\n const normalized: NormalizedUsage = {};\n\n // Handle input tokens (v5 'inputTokens' or v4 'promptTokens')\n // Using ?? to prioritize v5 format while falling back to v4\n const inputTokens = usage.inputTokens ?? usage.promptTokens;\n if (inputTokens !== undefined) {\n normalized.input = inputTokens;\n }\n\n // Handle output tokens (v5 'outputTokens' or v4 'completionTokens')\n const outputTokens = usage.outputTokens ?? usage.completionTokens;\n if (outputTokens !== undefined) {\n normalized.output = outputTokens;\n }\n\n // Total tokens - calculate if not provided\n if (usage.totalTokens !== undefined) {\n normalized.total = usage.totalTokens;\n } else if (normalized.input !== undefined && normalized.output !== undefined) {\n normalized.total = normalized.input + normalized.output;\n }\n\n // AI SDK v5-specific: reasoning tokens\n if (usage.reasoningTokens !== undefined) {\n normalized.reasoning = usage.reasoningTokens;\n }\n\n // AI SDK v5-specific: cached tokens (cache hit)\n if (usage.cachedInputTokens !== undefined) {\n normalized.cachedInput = usage.cachedInputTokens;\n }\n\n // Legacy cache metrics (promptCacheHitTokens/promptCacheMissTokens)\n if (usage.promptCacheHitTokens !== undefined) {\n normalized.promptCacheHit = usage.promptCacheHitTokens;\n }\n if (usage.promptCacheMissTokens !== undefined) {\n normalized.promptCacheMiss = usage.promptCacheMissTokens;\n }\n\n return Object.keys(normalized).length > 0 ? normalized : undefined;\n }\n\n private buildSpanPayload(span: AnyExportedSpan, isCreate: boolean): Record<string, any> {\n const payload: Record<string, any> = {};\n\n if (isCreate) {\n payload.id = span.id;\n payload.name = span.name;\n payload.startTime = span.startTime;\n if (span.input !== undefined) payload.input = span.input;\n }\n\n if (span.output !== undefined) payload.output = span.output;\n if (span.endTime !== undefined) payload.endTime = span.endTime;\n\n const attributes = (span.attributes ?? {}) as Record<string, any>;\n\n // Strip special fields from metadata if used in top-level keys\n const attributesToOmit: string[] = [];\n\n if (span.type === SpanType.MODEL_GENERATION) {\n const modelAttr = attributes as ModelGenerationAttributes;\n\n if (modelAttr.model !== undefined) {\n payload.model = modelAttr.model;\n attributesToOmit.push('model');\n }\n\n if (modelAttr.usage !== undefined) {\n // Normalize usage to handle both v4 and v5 formats\n const normalizedUsage = this.normalizeUsage(modelAttr.usage);\n if (normalizedUsage) {\n payload.usage = normalizedUsage;\n }\n attributesToOmit.push('usage');\n }\n\n if (modelAttr.parameters !== undefined) {\n payload.modelParameters = modelAttr.parameters;\n attributesToOmit.push('parameters');\n }\n }\n\n payload.metadata = {\n spanType: span.type,\n ...omitKeys(attributes, attributesToOmit),\n ...span.metadata,\n };\n\n if (span.errorInfo) {\n payload.level = 'ERROR';\n payload.statusMessage = span.errorInfo.message;\n }\n\n return payload;\n }\n\n async addScoreToTrace({\n traceId,\n spanId,\n score,\n reason,\n scorerName,\n metadata,\n }: {\n traceId: string;\n spanId?: string;\n score: number;\n reason?: string;\n scorerName: string;\n metadata?: Record<string, any>;\n }): Promise<void> {\n if (!this.client) return;\n\n try {\n await this.client.score({\n id: `${traceId}-${scorerName}`,\n traceId,\n observationId: spanId,\n name: scorerName,\n value: score,\n ...(metadata?.sessionId ? { sessionId: metadata.sessionId } : {}),\n metadata: { ...(reason ? { reason } : {}) },\n dataType: 'NUMERIC',\n });\n } catch (error) {\n this.logger.error('Langfuse exporter: Error adding score to trace', {\n error,\n traceId,\n spanId,\n scorerName,\n });\n }\n }\n\n async shutdown(): Promise<void> {\n if (this.client) {\n await this.client.shutdownAsync();\n }\n this.traceMap.clear();\n await super.shutdown();\n }\n}\n"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Langfuse Observability Provider for Mastra
|
|
3
3
|
*
|
|
4
4
|
* This package provides Langfuse-specific observability features for Mastra applications.
|
|
5
|
-
* Currently includes
|
|
5
|
+
* Currently includes tracing support with plans for additional observability features.
|
|
6
6
|
*/
|
|
7
|
-
export * from './
|
|
7
|
+
export * from './tracing.js';
|
|
8
8
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,cAAc,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,cAAc,WAAW,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,22 +1,21 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { SpanType } from '@mastra/core/observability';
|
|
2
|
+
import { omitKeys } from '@mastra/core/utils';
|
|
3
|
+
import { BaseExporter } from '@mastra/observability';
|
|
3
4
|
import { Langfuse } from 'langfuse';
|
|
4
5
|
|
|
5
|
-
// src/
|
|
6
|
-
var LangfuseExporter = class {
|
|
6
|
+
// src/tracing.ts
|
|
7
|
+
var LangfuseExporter = class extends BaseExporter {
|
|
7
8
|
name = "langfuse";
|
|
8
9
|
client;
|
|
9
10
|
realtime;
|
|
10
11
|
traceMap = /* @__PURE__ */ new Map();
|
|
11
|
-
logger;
|
|
12
12
|
constructor(config) {
|
|
13
|
+
super(config);
|
|
13
14
|
this.realtime = config.realtime ?? false;
|
|
14
|
-
this.logger = new ConsoleLogger({ level: config.logLevel ?? "warn" });
|
|
15
15
|
if (!config.publicKey || !config.secretKey) {
|
|
16
|
-
this.
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
});
|
|
16
|
+
this.setDisabled(
|
|
17
|
+
`Missing required credentials (publicKey: ${!!config.publicKey}, secretKey: ${!!config.secretKey})`
|
|
18
|
+
);
|
|
20
19
|
this.client = null;
|
|
21
20
|
return;
|
|
22
21
|
}
|
|
@@ -27,23 +26,20 @@ var LangfuseExporter = class {
|
|
|
27
26
|
...config.options
|
|
28
27
|
});
|
|
29
28
|
}
|
|
30
|
-
async
|
|
31
|
-
if (
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
if (event.span.isEvent) {
|
|
35
|
-
await this.handleEventSpan(event.span);
|
|
29
|
+
async _exportTracingEvent(event) {
|
|
30
|
+
if (event.exportedSpan.isEvent) {
|
|
31
|
+
await this.handleEventSpan(event.exportedSpan);
|
|
36
32
|
return;
|
|
37
33
|
}
|
|
38
34
|
switch (event.type) {
|
|
39
35
|
case "span_started":
|
|
40
|
-
await this.handleSpanStarted(event.
|
|
36
|
+
await this.handleSpanStarted(event.exportedSpan);
|
|
41
37
|
break;
|
|
42
38
|
case "span_updated":
|
|
43
|
-
await this.handleSpanUpdateOrEnd(event.
|
|
39
|
+
await this.handleSpanUpdateOrEnd(event.exportedSpan, false);
|
|
44
40
|
break;
|
|
45
41
|
case "span_ended":
|
|
46
|
-
await this.handleSpanUpdateOrEnd(event.
|
|
42
|
+
await this.handleSpanUpdateOrEnd(event.exportedSpan, true);
|
|
47
43
|
break;
|
|
48
44
|
}
|
|
49
45
|
if (this.realtime) {
|
|
@@ -64,8 +60,9 @@ var LangfuseExporter = class {
|
|
|
64
60
|
return;
|
|
65
61
|
}
|
|
66
62
|
const payload = this.buildSpanPayload(span, true);
|
|
67
|
-
const langfuseSpan = span.type ===
|
|
63
|
+
const langfuseSpan = span.type === SpanType.MODEL_GENERATION ? langfuseParent.generation(payload) : langfuseParent.span(payload);
|
|
68
64
|
traceData.spans.set(span.id, langfuseSpan);
|
|
65
|
+
traceData.activeSpans.add(span.id);
|
|
69
66
|
}
|
|
70
67
|
async handleSpanUpdateOrEnd(span, isEnd) {
|
|
71
68
|
const method = isEnd ? "handleSpanEnd" : "handleSpanUpdate";
|
|
@@ -75,21 +72,33 @@ var LangfuseExporter = class {
|
|
|
75
72
|
}
|
|
76
73
|
const langfuseSpan = traceData.spans.get(span.id);
|
|
77
74
|
if (!langfuseSpan) {
|
|
75
|
+
if (isEnd && span.isEvent) {
|
|
76
|
+
traceData.activeSpans.delete(span.id);
|
|
77
|
+
if (traceData.activeSpans.size === 0) {
|
|
78
|
+
this.traceMap.delete(span.traceId);
|
|
79
|
+
}
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
78
82
|
this.logger.warn("Langfuse exporter: No Langfuse span found for span update/end", {
|
|
79
83
|
traceId: span.traceId,
|
|
80
84
|
spanId: span.id,
|
|
81
85
|
spanName: span.name,
|
|
82
86
|
spanType: span.type,
|
|
83
87
|
isRootSpan: span.isRootSpan,
|
|
84
|
-
parentSpanId: span.
|
|
88
|
+
parentSpanId: span.parentSpanId,
|
|
85
89
|
method
|
|
86
90
|
});
|
|
87
91
|
return;
|
|
88
92
|
}
|
|
89
93
|
langfuseSpan.update(this.buildSpanPayload(span, false));
|
|
90
|
-
if (isEnd
|
|
91
|
-
traceData.
|
|
92
|
-
|
|
94
|
+
if (isEnd) {
|
|
95
|
+
traceData.activeSpans.delete(span.id);
|
|
96
|
+
if (span.isRootSpan) {
|
|
97
|
+
traceData.trace.update({ output: span.output });
|
|
98
|
+
}
|
|
99
|
+
if (traceData.activeSpans.size === 0) {
|
|
100
|
+
this.traceMap.delete(span.traceId);
|
|
101
|
+
}
|
|
93
102
|
}
|
|
94
103
|
}
|
|
95
104
|
async handleEventSpan(span) {
|
|
@@ -114,10 +123,19 @@ var LangfuseExporter = class {
|
|
|
114
123
|
const payload = this.buildSpanPayload(span, true);
|
|
115
124
|
const langfuseEvent = langfuseParent.event(payload);
|
|
116
125
|
traceData.events.set(span.id, langfuseEvent);
|
|
126
|
+
if (!span.endTime) {
|
|
127
|
+
traceData.activeSpans.add(span.id);
|
|
128
|
+
}
|
|
117
129
|
}
|
|
118
130
|
initTrace(span) {
|
|
119
131
|
const trace = this.client.trace(this.buildTracePayload(span));
|
|
120
|
-
this.traceMap.set(span.traceId, {
|
|
132
|
+
this.traceMap.set(span.traceId, {
|
|
133
|
+
trace,
|
|
134
|
+
spans: /* @__PURE__ */ new Map(),
|
|
135
|
+
events: /* @__PURE__ */ new Map(),
|
|
136
|
+
activeSpans: /* @__PURE__ */ new Set(),
|
|
137
|
+
rootSpanId: span.id
|
|
138
|
+
});
|
|
121
139
|
}
|
|
122
140
|
getTraceData(options) {
|
|
123
141
|
const { span, method } = options;
|
|
@@ -130,13 +148,13 @@ var LangfuseExporter = class {
|
|
|
130
148
|
spanName: span.name,
|
|
131
149
|
spanType: span.type,
|
|
132
150
|
isRootSpan: span.isRootSpan,
|
|
133
|
-
parentSpanId: span.
|
|
151
|
+
parentSpanId: span.parentSpanId,
|
|
134
152
|
method
|
|
135
153
|
});
|
|
136
154
|
}
|
|
137
155
|
getLangfuseParent(options) {
|
|
138
156
|
const { traceData, span, method } = options;
|
|
139
|
-
const parentId = span.
|
|
157
|
+
const parentId = span.parentSpanId;
|
|
140
158
|
if (!parentId) {
|
|
141
159
|
return traceData.trace;
|
|
142
160
|
}
|
|
@@ -152,7 +170,7 @@ var LangfuseExporter = class {
|
|
|
152
170
|
spanName: span.name,
|
|
153
171
|
spanType: span.type,
|
|
154
172
|
isRootSpan: span.isRootSpan,
|
|
155
|
-
parentSpanId: span.
|
|
173
|
+
parentSpanId: span.parentSpanId,
|
|
156
174
|
method
|
|
157
175
|
});
|
|
158
176
|
}
|
|
@@ -172,6 +190,48 @@ var LangfuseExporter = class {
|
|
|
172
190
|
};
|
|
173
191
|
return payload;
|
|
174
192
|
}
|
|
193
|
+
/**
|
|
194
|
+
* Normalize usage data to handle both AI SDK v4 and v5 formats.
|
|
195
|
+
*
|
|
196
|
+
* AI SDK v4 uses: promptTokens, completionTokens
|
|
197
|
+
* AI SDK v5 uses: inputTokens, outputTokens
|
|
198
|
+
*
|
|
199
|
+
* This function normalizes to a unified format that Langfuse can consume,
|
|
200
|
+
* prioritizing v5 format while maintaining backward compatibility.
|
|
201
|
+
*
|
|
202
|
+
* @param usage - Token usage data from AI SDK (v4 or v5 format)
|
|
203
|
+
* @returns Normalized usage object, or undefined if no usage data available
|
|
204
|
+
*/
|
|
205
|
+
normalizeUsage(usage) {
|
|
206
|
+
if (!usage) return void 0;
|
|
207
|
+
const normalized = {};
|
|
208
|
+
const inputTokens = usage.inputTokens ?? usage.promptTokens;
|
|
209
|
+
if (inputTokens !== void 0) {
|
|
210
|
+
normalized.input = inputTokens;
|
|
211
|
+
}
|
|
212
|
+
const outputTokens = usage.outputTokens ?? usage.completionTokens;
|
|
213
|
+
if (outputTokens !== void 0) {
|
|
214
|
+
normalized.output = outputTokens;
|
|
215
|
+
}
|
|
216
|
+
if (usage.totalTokens !== void 0) {
|
|
217
|
+
normalized.total = usage.totalTokens;
|
|
218
|
+
} else if (normalized.input !== void 0 && normalized.output !== void 0) {
|
|
219
|
+
normalized.total = normalized.input + normalized.output;
|
|
220
|
+
}
|
|
221
|
+
if (usage.reasoningTokens !== void 0) {
|
|
222
|
+
normalized.reasoning = usage.reasoningTokens;
|
|
223
|
+
}
|
|
224
|
+
if (usage.cachedInputTokens !== void 0) {
|
|
225
|
+
normalized.cachedInput = usage.cachedInputTokens;
|
|
226
|
+
}
|
|
227
|
+
if (usage.promptCacheHitTokens !== void 0) {
|
|
228
|
+
normalized.promptCacheHit = usage.promptCacheHitTokens;
|
|
229
|
+
}
|
|
230
|
+
if (usage.promptCacheMissTokens !== void 0) {
|
|
231
|
+
normalized.promptCacheMiss = usage.promptCacheMissTokens;
|
|
232
|
+
}
|
|
233
|
+
return Object.keys(normalized).length > 0 ? normalized : void 0;
|
|
234
|
+
}
|
|
175
235
|
buildSpanPayload(span, isCreate) {
|
|
176
236
|
const payload = {};
|
|
177
237
|
if (isCreate) {
|
|
@@ -184,18 +244,21 @@ var LangfuseExporter = class {
|
|
|
184
244
|
if (span.endTime !== void 0) payload.endTime = span.endTime;
|
|
185
245
|
const attributes = span.attributes ?? {};
|
|
186
246
|
const attributesToOmit = [];
|
|
187
|
-
if (span.type ===
|
|
188
|
-
const
|
|
189
|
-
if (
|
|
190
|
-
payload.model =
|
|
247
|
+
if (span.type === SpanType.MODEL_GENERATION) {
|
|
248
|
+
const modelAttr = attributes;
|
|
249
|
+
if (modelAttr.model !== void 0) {
|
|
250
|
+
payload.model = modelAttr.model;
|
|
191
251
|
attributesToOmit.push("model");
|
|
192
252
|
}
|
|
193
|
-
if (
|
|
194
|
-
|
|
253
|
+
if (modelAttr.usage !== void 0) {
|
|
254
|
+
const normalizedUsage = this.normalizeUsage(modelAttr.usage);
|
|
255
|
+
if (normalizedUsage) {
|
|
256
|
+
payload.usage = normalizedUsage;
|
|
257
|
+
}
|
|
195
258
|
attributesToOmit.push("usage");
|
|
196
259
|
}
|
|
197
|
-
if (
|
|
198
|
-
payload.modelParameters =
|
|
260
|
+
if (modelAttr.parameters !== void 0) {
|
|
261
|
+
payload.modelParameters = modelAttr.parameters;
|
|
199
262
|
attributesToOmit.push("parameters");
|
|
200
263
|
}
|
|
201
264
|
}
|
|
@@ -210,11 +273,41 @@ var LangfuseExporter = class {
|
|
|
210
273
|
}
|
|
211
274
|
return payload;
|
|
212
275
|
}
|
|
276
|
+
async addScoreToTrace({
|
|
277
|
+
traceId,
|
|
278
|
+
spanId,
|
|
279
|
+
score,
|
|
280
|
+
reason,
|
|
281
|
+
scorerName,
|
|
282
|
+
metadata
|
|
283
|
+
}) {
|
|
284
|
+
if (!this.client) return;
|
|
285
|
+
try {
|
|
286
|
+
await this.client.score({
|
|
287
|
+
id: `${traceId}-${scorerName}`,
|
|
288
|
+
traceId,
|
|
289
|
+
observationId: spanId,
|
|
290
|
+
name: scorerName,
|
|
291
|
+
value: score,
|
|
292
|
+
...metadata?.sessionId ? { sessionId: metadata.sessionId } : {},
|
|
293
|
+
metadata: { ...reason ? { reason } : {} },
|
|
294
|
+
dataType: "NUMERIC"
|
|
295
|
+
});
|
|
296
|
+
} catch (error) {
|
|
297
|
+
this.logger.error("Langfuse exporter: Error adding score to trace", {
|
|
298
|
+
error,
|
|
299
|
+
traceId,
|
|
300
|
+
spanId,
|
|
301
|
+
scorerName
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
}
|
|
213
305
|
async shutdown() {
|
|
214
306
|
if (this.client) {
|
|
215
307
|
await this.client.shutdownAsync();
|
|
216
308
|
}
|
|
217
309
|
this.traceMap.clear();
|
|
310
|
+
await super.shutdown();
|
|
218
311
|
}
|
|
219
312
|
};
|
|
220
313
|
|