@mastra/langfuse 0.0.2 → 0.0.5-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +27 -0
- package/dist/ai-tracing.d.ts +7 -0
- package/dist/ai-tracing.d.ts.map +1 -1
- package/dist/index.cjs +102 -24
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +103 -25
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
- package/src/ai-tracing.test.ts +175 -9
- package/src/ai-tracing.ts +126 -33
package/.turbo/turbo-build.log
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# @mastra/langfuse
|
|
2
2
|
|
|
3
|
+
## 0.0.5-alpha.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#7266](https://github.com/mastra-ai/mastra/pull/7266) [`979912c`](https://github.com/mastra-ai/mastra/commit/979912cfd180aad53287cda08af771df26454e2c) Thanks [@epinzur](https://github.com/epinzur)! - "Updated langfuse exporter to handle Event spans"
|
|
8
|
+
|
|
9
|
+
- Updated dependencies [[`ab48c97`](https://github.com/mastra-ai/mastra/commit/ab48c979098ea571faf998a55d3a00e7acd7a715), [`ff89505`](https://github.com/mastra-ai/mastra/commit/ff895057c8c7e91a5535faef46c5e5391085ddfa), [`183dc95`](https://github.com/mastra-ai/mastra/commit/183dc95596f391b977bd1a2c050b8498dac74891), [`a1111e2`](https://github.com/mastra-ai/mastra/commit/a1111e24e705488adfe5e0a6f20c53bddf26cb22), [`61debef`](https://github.com/mastra-ai/mastra/commit/61debefd80ad3a7ed5737e19df6a23d40091689a), [`9beaeff`](https://github.com/mastra-ai/mastra/commit/9beaeffa4a97b1d5fd01a7f8af8708b16067f67c), [`9eee594`](https://github.com/mastra-ai/mastra/commit/9eee594e35e0ca2a650fcc33fa82009a142b9ed0), [`979912c`](https://github.com/mastra-ai/mastra/commit/979912cfd180aad53287cda08af771df26454e2c), [`7dcf4c0`](https://github.com/mastra-ai/mastra/commit/7dcf4c04f44d9345b1f8bc5d41eae3f11ac61611), [`ad78bfc`](https://github.com/mastra-ai/mastra/commit/ad78bfc4ea6a1fff140432bf4f638e01af7af668), [`0ce418a`](https://github.com/mastra-ai/mastra/commit/0ce418a1ccaa5e125d4483a9651b635046152569), [`8387952`](https://github.com/mastra-ai/mastra/commit/838795227b4edf758c84a2adf6f7fba206c27719), [`5eca5d2`](https://github.com/mastra-ai/mastra/commit/5eca5d2655788863ea0442a46c9ef5d3c6dbe0a8)]:
|
|
10
|
+
- @mastra/core@0.15.3-alpha.4
|
|
11
|
+
|
|
12
|
+
## 0.0.4
|
|
13
|
+
|
|
14
|
+
### Patch Changes
|
|
15
|
+
|
|
16
|
+
- [`c6113ed`](https://github.com/mastra-ai/mastra/commit/c6113ed7f9df297e130d94436ceee310273d6430) Thanks [@wardpeet](https://github.com/wardpeet)! - Fix peerdpes for @mastra/core
|
|
17
|
+
|
|
18
|
+
- Updated dependencies []:
|
|
19
|
+
- @mastra/core@0.15.2
|
|
20
|
+
|
|
21
|
+
## 0.0.3
|
|
22
|
+
|
|
23
|
+
### Patch Changes
|
|
24
|
+
|
|
25
|
+
- [`95b2aa9`](https://github.com/mastra-ai/mastra/commit/95b2aa908230919e67efcac0d69005a2d5745298) Thanks [@wardpeet](https://github.com/wardpeet)! - Fix peerdeps @mastra/core
|
|
26
|
+
|
|
27
|
+
- Updated dependencies []:
|
|
28
|
+
- @mastra/core@0.15.1
|
|
29
|
+
|
|
3
30
|
## 0.0.2
|
|
4
31
|
|
|
5
32
|
### Patch Changes
|
package/dist/ai-tracing.d.ts
CHANGED
|
@@ -15,6 +15,8 @@ export interface LangfuseExporterConfig {
|
|
|
15
15
|
baseUrl: string;
|
|
16
16
|
/** Enable realtime mode - flushes after each event for immediate visibility */
|
|
17
17
|
realtime?: boolean;
|
|
18
|
+
/** Logger level for diagnostic messages (default: 'warn') */
|
|
19
|
+
logLevel?: 'debug' | 'info' | 'warn' | 'error';
|
|
18
20
|
/** Additional options to pass to the Langfuse client */
|
|
19
21
|
options?: any;
|
|
20
22
|
}
|
|
@@ -23,10 +25,15 @@ export declare class LangfuseExporter implements AITracingExporter {
|
|
|
23
25
|
private client;
|
|
24
26
|
private realtime;
|
|
25
27
|
private traceMap;
|
|
28
|
+
private logger;
|
|
26
29
|
constructor(config: LangfuseExporterConfig);
|
|
27
30
|
exportEvent(event: AITracingEvent): Promise<void>;
|
|
28
31
|
private handleSpanStarted;
|
|
29
32
|
private handleSpanUpdateOrEnd;
|
|
33
|
+
private handleEventSpan;
|
|
34
|
+
private initTrace;
|
|
35
|
+
private getTraceData;
|
|
36
|
+
private getLangfuseParent;
|
|
30
37
|
private buildTracePayload;
|
|
31
38
|
private buildSpanPayload;
|
|
32
39
|
shutdown(): Promise<void>;
|
package/dist/ai-tracing.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ai-tracing.d.ts","sourceRoot":"","sources":["../src/ai-tracing.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAsC,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"ai-tracing.d.ts","sourceRoot":"","sources":["../src/ai-tracing.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAsC,MAAM,yBAAyB,CAAC;AAMrH,MAAM,WAAW,sBAAsB;IACrC,uBAAuB;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,0BAA0B;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,wBAAwB;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,6DAA6D;IAC7D,QAAQ,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;IAC/C,wDAAwD;IACxD,OAAO,CAAC,EAAE,GAAG,CAAC;CACf;AAUD,qBAAa,gBAAiB,YAAW,iBAAiB;IACxD,IAAI,SAAc;IAClB,OAAO,CAAC,MAAM,CAAW;IACzB,OAAO,CAAC,QAAQ,CAAU;IAC1B,OAAO,CAAC,QAAQ,CAAgC;IAChD,OAAO,CAAC,MAAM,CAAgB;gBAElB,MAAM,EAAE,sBAAsB;IAWpC,WAAW,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;YAwBzC,iBAAiB;YAwBjB,qBAAqB;YAiCrB,eAAe;IA6B7B,OAAO,CAAC,SAAS;IAKjB,OAAO,CAAC,YAAY;IAgBpB,OAAO,CAAC,iBAAiB;IA4BzB,OAAO,CAAC,iBAAiB;IAqBzB,OAAO,CAAC,gBAAgB;IAmDlB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CAIhC"}
|
package/dist/index.cjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var aiTracing = require('@mastra/core/ai-tracing');
|
|
4
|
+
var logger = require('@mastra/core/logger');
|
|
4
5
|
var langfuse = require('langfuse');
|
|
5
6
|
|
|
6
7
|
// src/ai-tracing.ts
|
|
@@ -9,8 +10,10 @@ var LangfuseExporter = class {
|
|
|
9
10
|
client;
|
|
10
11
|
realtime;
|
|
11
12
|
traceMap = /* @__PURE__ */ new Map();
|
|
13
|
+
logger;
|
|
12
14
|
constructor(config) {
|
|
13
15
|
this.realtime = config.realtime ?? false;
|
|
16
|
+
this.logger = new logger.ConsoleLogger({ level: config.logLevel ?? "warn" });
|
|
14
17
|
this.client = new langfuse.Langfuse({
|
|
15
18
|
publicKey: config.publicKey,
|
|
16
19
|
secretKey: config.secretKey,
|
|
@@ -19,15 +22,19 @@ var LangfuseExporter = class {
|
|
|
19
22
|
});
|
|
20
23
|
}
|
|
21
24
|
async exportEvent(event) {
|
|
25
|
+
if (event.span.isEvent) {
|
|
26
|
+
await this.handleEventSpan(event.span);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
22
29
|
switch (event.type) {
|
|
23
30
|
case "span_started":
|
|
24
31
|
await this.handleSpanStarted(event.span);
|
|
25
32
|
break;
|
|
26
33
|
case "span_updated":
|
|
27
|
-
await this.handleSpanUpdateOrEnd(event.span,
|
|
34
|
+
await this.handleSpanUpdateOrEnd(event.span, false);
|
|
28
35
|
break;
|
|
29
36
|
case "span_ended":
|
|
30
|
-
await this.handleSpanUpdateOrEnd(event.span,
|
|
37
|
+
await this.handleSpanUpdateOrEnd(event.span, true);
|
|
31
38
|
break;
|
|
32
39
|
}
|
|
33
40
|
if (this.realtime) {
|
|
@@ -36,43 +43,114 @@ var LangfuseExporter = class {
|
|
|
36
43
|
}
|
|
37
44
|
async handleSpanStarted(span) {
|
|
38
45
|
if (span.isRootSpan) {
|
|
39
|
-
|
|
40
|
-
this.traceMap.set(span.trace.id, { trace, spans: /* @__PURE__ */ new Map() });
|
|
46
|
+
this.initTrace(span);
|
|
41
47
|
}
|
|
42
|
-
const
|
|
48
|
+
const method = "handleSpanStarted";
|
|
49
|
+
const traceData = this.getTraceData({ span, method });
|
|
43
50
|
if (!traceData) {
|
|
44
|
-
console.log("NO TRACE");
|
|
45
51
|
return;
|
|
46
52
|
}
|
|
47
|
-
const langfuseParent =
|
|
53
|
+
const langfuseParent = this.getLangfuseParent({ traceData, span, method });
|
|
54
|
+
if (!langfuseParent) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
48
57
|
const payload = this.buildSpanPayload(span, true);
|
|
49
58
|
const langfuseSpan = span.type === aiTracing.AISpanType.LLM_GENERATION ? langfuseParent.generation(payload) : langfuseParent.span(payload);
|
|
50
59
|
traceData.spans.set(span.id, langfuseSpan);
|
|
51
60
|
}
|
|
52
|
-
async handleSpanUpdateOrEnd(span,
|
|
53
|
-
const
|
|
61
|
+
async handleSpanUpdateOrEnd(span, isEnd) {
|
|
62
|
+
const method = isEnd ? "handleSpanEnd" : "handleSpanUpdate";
|
|
63
|
+
const traceData = this.getTraceData({ span, method });
|
|
54
64
|
if (!traceData) {
|
|
55
|
-
console.log("NO TRACE");
|
|
56
65
|
return;
|
|
57
66
|
}
|
|
58
67
|
const langfuseSpan = traceData.spans.get(span.id);
|
|
59
68
|
if (!langfuseSpan) {
|
|
60
|
-
|
|
69
|
+
this.logger.warn("Langfuse exporter: No Langfuse span found for span update/end", {
|
|
70
|
+
traceId: span.traceId,
|
|
71
|
+
spanId: span.id,
|
|
72
|
+
spanName: span.name,
|
|
73
|
+
spanType: span.type,
|
|
74
|
+
isRootSpan: span.isRootSpan,
|
|
75
|
+
parentSpanId: span.parent?.id,
|
|
76
|
+
availableSpanIds: Array.from(traceData.spans.keys()),
|
|
77
|
+
method
|
|
78
|
+
});
|
|
61
79
|
return;
|
|
62
80
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
81
|
+
langfuseSpan.update(this.buildSpanPayload(span, false));
|
|
82
|
+
if (isEnd && span.isRootSpan) {
|
|
83
|
+
traceData.trace.update({ output: span.output });
|
|
84
|
+
this.traceMap.delete(span.traceId);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
async handleEventSpan(span) {
|
|
88
|
+
if (span.isRootSpan) {
|
|
89
|
+
this.logger.debug("Langfuse exporter: Creating trace", {
|
|
90
|
+
traceId: span.traceId,
|
|
91
|
+
spanId: span.id,
|
|
92
|
+
spanName: span.name,
|
|
93
|
+
method: "handleEventSpan"
|
|
94
|
+
});
|
|
95
|
+
this.initTrace(span);
|
|
96
|
+
}
|
|
97
|
+
const method = "handleEventSpan";
|
|
98
|
+
const traceData = this.getTraceData({ span, method });
|
|
99
|
+
if (!traceData) {
|
|
100
|
+
return;
|
|
71
101
|
}
|
|
102
|
+
const langfuseParent = this.getLangfuseParent({ traceData, span, method });
|
|
103
|
+
if (!langfuseParent) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const payload = this.buildSpanPayload(span, true);
|
|
107
|
+
const langfuseEvent = langfuseParent.event(payload);
|
|
108
|
+
traceData.events.set(span.id, langfuseEvent);
|
|
109
|
+
}
|
|
110
|
+
initTrace(span) {
|
|
111
|
+
const trace = this.client.trace(this.buildTracePayload(span));
|
|
112
|
+
this.traceMap.set(span.traceId, { trace, spans: /* @__PURE__ */ new Map(), events: /* @__PURE__ */ new Map() });
|
|
113
|
+
}
|
|
114
|
+
getTraceData(options) {
|
|
115
|
+
const { span, method } = options;
|
|
116
|
+
if (this.traceMap.has(span.traceId)) {
|
|
117
|
+
return this.traceMap.get(span.traceId);
|
|
118
|
+
}
|
|
119
|
+
this.logger.warn("Langfuse exporter: No trace data found for span", {
|
|
120
|
+
traceId: span.traceId,
|
|
121
|
+
spanId: span.id,
|
|
122
|
+
spanName: span.name,
|
|
123
|
+
spanType: span.type,
|
|
124
|
+
isRootSpan: span.isRootSpan,
|
|
125
|
+
parentSpanId: span.parent?.id,
|
|
126
|
+
method
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
getLangfuseParent(options) {
|
|
130
|
+
const { traceData, span, method } = options;
|
|
131
|
+
const parentId = span.parent?.id;
|
|
132
|
+
if (!parentId) {
|
|
133
|
+
return traceData.trace;
|
|
134
|
+
}
|
|
135
|
+
if (traceData.spans.has(parentId)) {
|
|
136
|
+
return traceData.spans.get(parentId);
|
|
137
|
+
}
|
|
138
|
+
if (traceData.events.has(parentId)) {
|
|
139
|
+
return traceData.events.get(parentId);
|
|
140
|
+
}
|
|
141
|
+
this.logger.warn("Langfuse exporter: No parent data found for span", {
|
|
142
|
+
traceId: span.traceId,
|
|
143
|
+
spanId: span.id,
|
|
144
|
+
spanName: span.name,
|
|
145
|
+
spanType: span.type,
|
|
146
|
+
isRootSpan: span.isRootSpan,
|
|
147
|
+
parentSpanId: span.parent?.id,
|
|
148
|
+
method
|
|
149
|
+
});
|
|
72
150
|
}
|
|
73
151
|
buildTracePayload(span) {
|
|
74
152
|
const payload = {
|
|
75
|
-
id: span.
|
|
153
|
+
id: span.traceId,
|
|
76
154
|
name: span.name
|
|
77
155
|
};
|
|
78
156
|
const { userId, sessionId, ...remainingMetadata } = span.metadata ?? {};
|
|
@@ -81,8 +159,8 @@ var LangfuseExporter = class {
|
|
|
81
159
|
if (span.input) payload.input = span.input;
|
|
82
160
|
payload.metadata = {
|
|
83
161
|
spanType: span.type,
|
|
84
|
-
...
|
|
85
|
-
...
|
|
162
|
+
...span.attributes,
|
|
163
|
+
...remainingMetadata
|
|
86
164
|
};
|
|
87
165
|
return payload;
|
|
88
166
|
}
|
|
@@ -115,8 +193,8 @@ var LangfuseExporter = class {
|
|
|
115
193
|
}
|
|
116
194
|
payload.metadata = {
|
|
117
195
|
spanType: span.type,
|
|
118
|
-
...aiTracing.
|
|
119
|
-
...
|
|
196
|
+
...aiTracing.omitKeys(attributes, attributesToOmit),
|
|
197
|
+
...span.metadata
|
|
120
198
|
};
|
|
121
199
|
if (span.errorInfo) {
|
|
122
200
|
payload.level = "ERROR";
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/ai-tracing.ts"],"names":["Langfuse","AISpanType","sanitizeMetadata","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, sanitizeMetadata, omitKeys } from '@mastra/core/ai-tracing';\nimport { Langfuse } from 'langfuse';\nimport type { LangfuseTraceClient, LangfuseSpanClient, LangfuseGenerationClient } 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 /** 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};\n\ntype LangfuseSpan = LangfuseTraceClient | LangfuseSpanClient | LangfuseGenerationClient;\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\n constructor(config: LangfuseExporterConfig) {\n this.realtime = config.realtime ?? false;\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 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, true);\n break;\n case 'span_ended':\n await this.handleSpanUpdateOrEnd(event.span, false);\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 const trace = this.client.trace(this.buildTracePayload(span));\n this.traceMap.set(span.trace.id, { trace, spans: new Map() });\n }\n\n const traceData = this.traceMap.get(span.trace.id);\n if (!traceData) {\n console.log('NO TRACE');\n // TODO: log warning\n return;\n }\n\n const langfuseParent =\n span.parent && traceData.spans.has(span.parent.id)\n ? (traceData.spans.get(span.parent.id) as LangfuseSpan)\n : traceData.trace;\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, isUpdate: boolean): Promise<void> {\n const traceData = this.traceMap.get(span.trace.id);\n if (!traceData) {\n console.log('NO TRACE');\n // TODO: log warning\n return;\n }\n\n const langfuseSpan = traceData.spans.get(span.id);\n if (!langfuseSpan) {\n console.log('NO SPAN');\n // TODO: log warning\n return;\n }\n\n if (isUpdate) {\n langfuseSpan.update(this.buildSpanPayload(span, false));\n } else {\n langfuseSpan.end(this.buildSpanPayload(span, false));\n\n if (span.isRootSpan) {\n traceData.trace.update({ output: span.output });\n this.traceMap.delete(span.trace.id);\n }\n }\n }\n\n private buildTracePayload(span: AnyAISpan): Record<string, any> {\n const payload: Record<string, any> = {\n id: span.trace.id,\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 ...sanitizeMetadata(span.attributes),\n ...sanitizeMetadata(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 ...sanitizeMetadata(omitKeys(attributes, attributesToOmit)),\n ...sanitizeMetadata(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 await this.client.shutdownAsync();\n this.traceMap.clear();\n }\n}\n"]}
|
|
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 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 (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 availableSpanIds: Array.from(traceData.spans.keys()),\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 await this.client.shutdownAsync();\n this.traceMap.clear();\n }\n}\n"]}
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { AISpanType,
|
|
1
|
+
import { AISpanType, omitKeys } from '@mastra/core/ai-tracing';
|
|
2
|
+
import { ConsoleLogger } from '@mastra/core/logger';
|
|
2
3
|
import { Langfuse } from 'langfuse';
|
|
3
4
|
|
|
4
5
|
// src/ai-tracing.ts
|
|
@@ -7,8 +8,10 @@ var LangfuseExporter = class {
|
|
|
7
8
|
client;
|
|
8
9
|
realtime;
|
|
9
10
|
traceMap = /* @__PURE__ */ new Map();
|
|
11
|
+
logger;
|
|
10
12
|
constructor(config) {
|
|
11
13
|
this.realtime = config.realtime ?? false;
|
|
14
|
+
this.logger = new ConsoleLogger({ level: config.logLevel ?? "warn" });
|
|
12
15
|
this.client = new Langfuse({
|
|
13
16
|
publicKey: config.publicKey,
|
|
14
17
|
secretKey: config.secretKey,
|
|
@@ -17,15 +20,19 @@ var LangfuseExporter = class {
|
|
|
17
20
|
});
|
|
18
21
|
}
|
|
19
22
|
async exportEvent(event) {
|
|
23
|
+
if (event.span.isEvent) {
|
|
24
|
+
await this.handleEventSpan(event.span);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
20
27
|
switch (event.type) {
|
|
21
28
|
case "span_started":
|
|
22
29
|
await this.handleSpanStarted(event.span);
|
|
23
30
|
break;
|
|
24
31
|
case "span_updated":
|
|
25
|
-
await this.handleSpanUpdateOrEnd(event.span,
|
|
32
|
+
await this.handleSpanUpdateOrEnd(event.span, false);
|
|
26
33
|
break;
|
|
27
34
|
case "span_ended":
|
|
28
|
-
await this.handleSpanUpdateOrEnd(event.span,
|
|
35
|
+
await this.handleSpanUpdateOrEnd(event.span, true);
|
|
29
36
|
break;
|
|
30
37
|
}
|
|
31
38
|
if (this.realtime) {
|
|
@@ -34,43 +41,114 @@ var LangfuseExporter = class {
|
|
|
34
41
|
}
|
|
35
42
|
async handleSpanStarted(span) {
|
|
36
43
|
if (span.isRootSpan) {
|
|
37
|
-
|
|
38
|
-
this.traceMap.set(span.trace.id, { trace, spans: /* @__PURE__ */ new Map() });
|
|
44
|
+
this.initTrace(span);
|
|
39
45
|
}
|
|
40
|
-
const
|
|
46
|
+
const method = "handleSpanStarted";
|
|
47
|
+
const traceData = this.getTraceData({ span, method });
|
|
41
48
|
if (!traceData) {
|
|
42
|
-
console.log("NO TRACE");
|
|
43
49
|
return;
|
|
44
50
|
}
|
|
45
|
-
const langfuseParent =
|
|
51
|
+
const langfuseParent = this.getLangfuseParent({ traceData, span, method });
|
|
52
|
+
if (!langfuseParent) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
46
55
|
const payload = this.buildSpanPayload(span, true);
|
|
47
56
|
const langfuseSpan = span.type === AISpanType.LLM_GENERATION ? langfuseParent.generation(payload) : langfuseParent.span(payload);
|
|
48
57
|
traceData.spans.set(span.id, langfuseSpan);
|
|
49
58
|
}
|
|
50
|
-
async handleSpanUpdateOrEnd(span,
|
|
51
|
-
const
|
|
59
|
+
async handleSpanUpdateOrEnd(span, isEnd) {
|
|
60
|
+
const method = isEnd ? "handleSpanEnd" : "handleSpanUpdate";
|
|
61
|
+
const traceData = this.getTraceData({ span, method });
|
|
52
62
|
if (!traceData) {
|
|
53
|
-
console.log("NO TRACE");
|
|
54
63
|
return;
|
|
55
64
|
}
|
|
56
65
|
const langfuseSpan = traceData.spans.get(span.id);
|
|
57
66
|
if (!langfuseSpan) {
|
|
58
|
-
|
|
67
|
+
this.logger.warn("Langfuse exporter: No Langfuse span found for span update/end", {
|
|
68
|
+
traceId: span.traceId,
|
|
69
|
+
spanId: span.id,
|
|
70
|
+
spanName: span.name,
|
|
71
|
+
spanType: span.type,
|
|
72
|
+
isRootSpan: span.isRootSpan,
|
|
73
|
+
parentSpanId: span.parent?.id,
|
|
74
|
+
availableSpanIds: Array.from(traceData.spans.keys()),
|
|
75
|
+
method
|
|
76
|
+
});
|
|
59
77
|
return;
|
|
60
78
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
79
|
+
langfuseSpan.update(this.buildSpanPayload(span, false));
|
|
80
|
+
if (isEnd && span.isRootSpan) {
|
|
81
|
+
traceData.trace.update({ output: span.output });
|
|
82
|
+
this.traceMap.delete(span.traceId);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
async handleEventSpan(span) {
|
|
86
|
+
if (span.isRootSpan) {
|
|
87
|
+
this.logger.debug("Langfuse exporter: Creating trace", {
|
|
88
|
+
traceId: span.traceId,
|
|
89
|
+
spanId: span.id,
|
|
90
|
+
spanName: span.name,
|
|
91
|
+
method: "handleEventSpan"
|
|
92
|
+
});
|
|
93
|
+
this.initTrace(span);
|
|
94
|
+
}
|
|
95
|
+
const method = "handleEventSpan";
|
|
96
|
+
const traceData = this.getTraceData({ span, method });
|
|
97
|
+
if (!traceData) {
|
|
98
|
+
return;
|
|
69
99
|
}
|
|
100
|
+
const langfuseParent = this.getLangfuseParent({ traceData, span, method });
|
|
101
|
+
if (!langfuseParent) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const payload = this.buildSpanPayload(span, true);
|
|
105
|
+
const langfuseEvent = langfuseParent.event(payload);
|
|
106
|
+
traceData.events.set(span.id, langfuseEvent);
|
|
107
|
+
}
|
|
108
|
+
initTrace(span) {
|
|
109
|
+
const trace = this.client.trace(this.buildTracePayload(span));
|
|
110
|
+
this.traceMap.set(span.traceId, { trace, spans: /* @__PURE__ */ new Map(), events: /* @__PURE__ */ new Map() });
|
|
111
|
+
}
|
|
112
|
+
getTraceData(options) {
|
|
113
|
+
const { span, method } = options;
|
|
114
|
+
if (this.traceMap.has(span.traceId)) {
|
|
115
|
+
return this.traceMap.get(span.traceId);
|
|
116
|
+
}
|
|
117
|
+
this.logger.warn("Langfuse exporter: No trace data found for span", {
|
|
118
|
+
traceId: span.traceId,
|
|
119
|
+
spanId: span.id,
|
|
120
|
+
spanName: span.name,
|
|
121
|
+
spanType: span.type,
|
|
122
|
+
isRootSpan: span.isRootSpan,
|
|
123
|
+
parentSpanId: span.parent?.id,
|
|
124
|
+
method
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
getLangfuseParent(options) {
|
|
128
|
+
const { traceData, span, method } = options;
|
|
129
|
+
const parentId = span.parent?.id;
|
|
130
|
+
if (!parentId) {
|
|
131
|
+
return traceData.trace;
|
|
132
|
+
}
|
|
133
|
+
if (traceData.spans.has(parentId)) {
|
|
134
|
+
return traceData.spans.get(parentId);
|
|
135
|
+
}
|
|
136
|
+
if (traceData.events.has(parentId)) {
|
|
137
|
+
return traceData.events.get(parentId);
|
|
138
|
+
}
|
|
139
|
+
this.logger.warn("Langfuse exporter: No parent data found for span", {
|
|
140
|
+
traceId: span.traceId,
|
|
141
|
+
spanId: span.id,
|
|
142
|
+
spanName: span.name,
|
|
143
|
+
spanType: span.type,
|
|
144
|
+
isRootSpan: span.isRootSpan,
|
|
145
|
+
parentSpanId: span.parent?.id,
|
|
146
|
+
method
|
|
147
|
+
});
|
|
70
148
|
}
|
|
71
149
|
buildTracePayload(span) {
|
|
72
150
|
const payload = {
|
|
73
|
-
id: span.
|
|
151
|
+
id: span.traceId,
|
|
74
152
|
name: span.name
|
|
75
153
|
};
|
|
76
154
|
const { userId, sessionId, ...remainingMetadata } = span.metadata ?? {};
|
|
@@ -79,8 +157,8 @@ var LangfuseExporter = class {
|
|
|
79
157
|
if (span.input) payload.input = span.input;
|
|
80
158
|
payload.metadata = {
|
|
81
159
|
spanType: span.type,
|
|
82
|
-
...
|
|
83
|
-
...
|
|
160
|
+
...span.attributes,
|
|
161
|
+
...remainingMetadata
|
|
84
162
|
};
|
|
85
163
|
return payload;
|
|
86
164
|
}
|
|
@@ -113,8 +191,8 @@ var LangfuseExporter = class {
|
|
|
113
191
|
}
|
|
114
192
|
payload.metadata = {
|
|
115
193
|
spanType: span.type,
|
|
116
|
-
...
|
|
117
|
-
...
|
|
194
|
+
...omitKeys(attributes, attributesToOmit),
|
|
195
|
+
...span.metadata
|
|
118
196
|
};
|
|
119
197
|
if (span.errorInfo) {
|
|
120
198
|
payload.level = "ERROR";
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/ai-tracing.ts"],"names":[],"mappings":"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","file":"index.js","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, sanitizeMetadata, omitKeys } from '@mastra/core/ai-tracing';\nimport { Langfuse } from 'langfuse';\nimport type { LangfuseTraceClient, LangfuseSpanClient, LangfuseGenerationClient } 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 /** 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};\n\ntype LangfuseSpan = LangfuseTraceClient | LangfuseSpanClient | LangfuseGenerationClient;\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\n constructor(config: LangfuseExporterConfig) {\n this.realtime = config.realtime ?? false;\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 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, true);\n break;\n case 'span_ended':\n await this.handleSpanUpdateOrEnd(event.span, false);\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 const trace = this.client.trace(this.buildTracePayload(span));\n this.traceMap.set(span.trace.id, { trace, spans: new Map() });\n }\n\n const traceData = this.traceMap.get(span.trace.id);\n if (!traceData) {\n console.log('NO TRACE');\n // TODO: log warning\n return;\n }\n\n const langfuseParent =\n span.parent && traceData.spans.has(span.parent.id)\n ? (traceData.spans.get(span.parent.id) as LangfuseSpan)\n : traceData.trace;\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, isUpdate: boolean): Promise<void> {\n const traceData = this.traceMap.get(span.trace.id);\n if (!traceData) {\n console.log('NO TRACE');\n // TODO: log warning\n return;\n }\n\n const langfuseSpan = traceData.spans.get(span.id);\n if (!langfuseSpan) {\n console.log('NO SPAN');\n // TODO: log warning\n return;\n }\n\n if (isUpdate) {\n langfuseSpan.update(this.buildSpanPayload(span, false));\n } else {\n langfuseSpan.end(this.buildSpanPayload(span, false));\n\n if (span.isRootSpan) {\n traceData.trace.update({ output: span.output });\n this.traceMap.delete(span.trace.id);\n }\n }\n }\n\n private buildTracePayload(span: AnyAISpan): Record<string, any> {\n const payload: Record<string, any> = {\n id: span.trace.id,\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 ...sanitizeMetadata(span.attributes),\n ...sanitizeMetadata(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 ...sanitizeMetadata(omitKeys(attributes, attributesToOmit)),\n ...sanitizeMetadata(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 await this.client.shutdownAsync();\n this.traceMap.clear();\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/ai-tracing.ts"],"names":[],"mappings":"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","file":"index.js","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 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 (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 availableSpanIds: Array.from(traceData.spans.keys()),\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 await this.client.shutdownAsync();\n this.traceMap.clear();\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mastra/langfuse",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5-alpha.0",
|
|
4
4
|
"description": "Langfuse observability provider for Mastra - includes AI tracing and future observability features",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -29,12 +29,12 @@
|
|
|
29
29
|
"tsup": "^8.5.0",
|
|
30
30
|
"typescript": "^5.8.3",
|
|
31
31
|
"vitest": "^3.2.4",
|
|
32
|
-
"@internal/
|
|
33
|
-
"@internal/
|
|
34
|
-
"@mastra/core": "0.
|
|
32
|
+
"@internal/lint": "0.0.34",
|
|
33
|
+
"@internal/types-builder": "0.0.9",
|
|
34
|
+
"@mastra/core": "0.15.3-alpha.4"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
|
-
"@mastra/core": ">=0.
|
|
37
|
+
"@mastra/core": ">=0.15.3-0 <0.16.0-0"
|
|
38
38
|
},
|
|
39
39
|
"scripts": {
|
|
40
40
|
"build": "tsup --silent --config tsup.config.ts",
|
package/src/ai-tracing.test.ts
CHANGED
|
@@ -35,20 +35,21 @@ describe('LangfuseExporter', () => {
|
|
|
35
35
|
// Set up mocks
|
|
36
36
|
mockGeneration = {
|
|
37
37
|
update: vi.fn(),
|
|
38
|
-
|
|
38
|
+
event: vi.fn(),
|
|
39
39
|
};
|
|
40
40
|
|
|
41
41
|
mockSpan = {
|
|
42
42
|
update: vi.fn(),
|
|
43
|
-
end: vi.fn(),
|
|
44
43
|
generation: vi.fn().mockReturnValue(mockGeneration),
|
|
45
44
|
span: vi.fn(),
|
|
45
|
+
event: vi.fn(),
|
|
46
46
|
};
|
|
47
47
|
|
|
48
48
|
mockTrace = {
|
|
49
49
|
generation: vi.fn().mockReturnValue(mockGeneration),
|
|
50
50
|
span: vi.fn().mockReturnValue(mockSpan),
|
|
51
51
|
update: vi.fn(),
|
|
52
|
+
event: vi.fn(),
|
|
52
53
|
};
|
|
53
54
|
|
|
54
55
|
// Set up circular reference
|
|
@@ -456,7 +457,7 @@ describe('LangfuseExporter', () => {
|
|
|
456
457
|
});
|
|
457
458
|
|
|
458
459
|
describe('Span Ending', () => {
|
|
459
|
-
it('should
|
|
460
|
+
it('should update span with endTime on span end', async () => {
|
|
460
461
|
const span = createMockSpan({
|
|
461
462
|
id: 'test-span',
|
|
462
463
|
name: 'test',
|
|
@@ -477,10 +478,7 @@ describe('LangfuseExporter', () => {
|
|
|
477
478
|
span,
|
|
478
479
|
});
|
|
479
480
|
|
|
480
|
-
|
|
481
|
-
console.log(span.metadata);
|
|
482
|
-
|
|
483
|
-
expect(mockSpan.end).toHaveBeenCalledWith({
|
|
481
|
+
expect(mockSpan.update).toHaveBeenCalledWith({
|
|
484
482
|
endTime: span.endTime,
|
|
485
483
|
metadata: expect.objectContaining({
|
|
486
484
|
spanType: 'generic',
|
|
@@ -488,7 +486,7 @@ describe('LangfuseExporter', () => {
|
|
|
488
486
|
});
|
|
489
487
|
});
|
|
490
488
|
|
|
491
|
-
it('should
|
|
489
|
+
it('should update span with error information on span end', async () => {
|
|
492
490
|
const errorSpan = createMockSpan({
|
|
493
491
|
id: 'error-span',
|
|
494
492
|
name: 'failing-operation',
|
|
@@ -516,7 +514,7 @@ describe('LangfuseExporter', () => {
|
|
|
516
514
|
span: errorSpan,
|
|
517
515
|
});
|
|
518
516
|
|
|
519
|
-
expect(mockSpan.
|
|
517
|
+
expect(mockSpan.update).toHaveBeenCalledWith({
|
|
520
518
|
endTime: errorSpan.endTime,
|
|
521
519
|
metadata: expect.objectContaining({
|
|
522
520
|
spanType: 'tool_call',
|
|
@@ -526,6 +524,40 @@ describe('LangfuseExporter', () => {
|
|
|
526
524
|
statusMessage: 'Tool execution failed',
|
|
527
525
|
});
|
|
528
526
|
});
|
|
527
|
+
|
|
528
|
+
it('should update root trace and delete from traceMap when root span ends', async () => {
|
|
529
|
+
const rootSpan = createMockSpan({
|
|
530
|
+
id: 'root-span-id',
|
|
531
|
+
name: 'root-span',
|
|
532
|
+
type: AISpanType.AGENT_RUN,
|
|
533
|
+
isRoot: true,
|
|
534
|
+
attributes: {},
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
rootSpan.output = { result: 'success' };
|
|
538
|
+
rootSpan.endTime = new Date();
|
|
539
|
+
|
|
540
|
+
await exporter.exportEvent({
|
|
541
|
+
type: AITracingEventType.SPAN_STARTED,
|
|
542
|
+
span: rootSpan,
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
// Verify trace was created
|
|
546
|
+
expect((exporter as any).traceMap.has('root-span-id')).toBe(true);
|
|
547
|
+
|
|
548
|
+
await exporter.exportEvent({
|
|
549
|
+
type: AITracingEventType.SPAN_ENDED,
|
|
550
|
+
span: rootSpan,
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
// Should update trace with output
|
|
554
|
+
expect(mockTrace.update).toHaveBeenCalledWith({
|
|
555
|
+
output: { result: 'success' },
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
// Should remove trace from traceMap
|
|
559
|
+
expect((exporter as any).traceMap.has('root-span-id')).toBe(false);
|
|
560
|
+
});
|
|
529
561
|
});
|
|
530
562
|
|
|
531
563
|
describe('Error Handling', () => {
|
|
@@ -578,6 +610,138 @@ describe('LangfuseExporter', () => {
|
|
|
578
610
|
});
|
|
579
611
|
});
|
|
580
612
|
|
|
613
|
+
describe('Event Span Handling', () => {
|
|
614
|
+
let mockEvent: any;
|
|
615
|
+
|
|
616
|
+
beforeEach(() => {
|
|
617
|
+
mockEvent = {
|
|
618
|
+
update: vi.fn(),
|
|
619
|
+
};
|
|
620
|
+
mockTrace.event.mockReturnValue(mockEvent);
|
|
621
|
+
mockSpan.event.mockReturnValue(mockEvent);
|
|
622
|
+
mockGeneration.event.mockReturnValue(mockEvent);
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
it('should create Langfuse event for root event spans', async () => {
|
|
626
|
+
const eventSpan = createMockSpan({
|
|
627
|
+
id: 'event-span-id',
|
|
628
|
+
name: 'user-feedback',
|
|
629
|
+
type: AISpanType.GENERIC,
|
|
630
|
+
isRoot: true,
|
|
631
|
+
attributes: {
|
|
632
|
+
eventType: 'user_feedback',
|
|
633
|
+
rating: 5,
|
|
634
|
+
},
|
|
635
|
+
input: { message: 'Great response!' },
|
|
636
|
+
});
|
|
637
|
+
eventSpan.isEvent = true;
|
|
638
|
+
|
|
639
|
+
await exporter.exportEvent({
|
|
640
|
+
type: AITracingEventType.SPAN_STARTED,
|
|
641
|
+
span: eventSpan,
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
// Should create trace for root event span
|
|
645
|
+
expect(mockLangfuseClient.trace).toHaveBeenCalledWith({
|
|
646
|
+
id: 'event-span-id',
|
|
647
|
+
name: 'user-feedback',
|
|
648
|
+
input: { message: 'Great response!' },
|
|
649
|
+
metadata: {
|
|
650
|
+
spanType: 'generic',
|
|
651
|
+
eventType: 'user_feedback',
|
|
652
|
+
rating: 5,
|
|
653
|
+
},
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
// Should create Langfuse event
|
|
657
|
+
expect(mockTrace.event).toHaveBeenCalledWith({
|
|
658
|
+
id: 'event-span-id',
|
|
659
|
+
name: 'user-feedback',
|
|
660
|
+
startTime: eventSpan.startTime,
|
|
661
|
+
input: { message: 'Great response!' },
|
|
662
|
+
metadata: {
|
|
663
|
+
spanType: 'generic',
|
|
664
|
+
eventType: 'user_feedback',
|
|
665
|
+
rating: 5,
|
|
666
|
+
},
|
|
667
|
+
});
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
it('should create Langfuse event for child event spans', async () => {
|
|
671
|
+
// First create a root span
|
|
672
|
+
const rootSpan = createMockSpan({
|
|
673
|
+
id: 'root-span-id',
|
|
674
|
+
name: 'root-agent',
|
|
675
|
+
type: AISpanType.AGENT_RUN,
|
|
676
|
+
isRoot: true,
|
|
677
|
+
attributes: {},
|
|
678
|
+
});
|
|
679
|
+
|
|
680
|
+
await exporter.exportEvent({
|
|
681
|
+
type: AITracingEventType.SPAN_STARTED,
|
|
682
|
+
span: rootSpan,
|
|
683
|
+
});
|
|
684
|
+
|
|
685
|
+
// Then create a child event span
|
|
686
|
+
const childEventSpan = createMockSpan({
|
|
687
|
+
id: 'child-event-id',
|
|
688
|
+
name: 'tool-result',
|
|
689
|
+
type: AISpanType.GENERIC,
|
|
690
|
+
isRoot: false,
|
|
691
|
+
attributes: {
|
|
692
|
+
toolName: 'calculator',
|
|
693
|
+
success: true,
|
|
694
|
+
},
|
|
695
|
+
output: { result: 42 },
|
|
696
|
+
});
|
|
697
|
+
childEventSpan.isEvent = true;
|
|
698
|
+
childEventSpan.traceId = 'root-span-id';
|
|
699
|
+
childEventSpan.parent = { id: 'root-span-id' };
|
|
700
|
+
|
|
701
|
+
await exporter.exportEvent({
|
|
702
|
+
type: AITracingEventType.SPAN_STARTED,
|
|
703
|
+
span: childEventSpan,
|
|
704
|
+
});
|
|
705
|
+
|
|
706
|
+
// Should create event under the parent span
|
|
707
|
+
expect(mockSpan.event).toHaveBeenCalledWith({
|
|
708
|
+
id: 'child-event-id',
|
|
709
|
+
name: 'tool-result',
|
|
710
|
+
startTime: childEventSpan.startTime,
|
|
711
|
+
output: { result: 42 },
|
|
712
|
+
metadata: {
|
|
713
|
+
spanType: 'generic',
|
|
714
|
+
toolName: 'calculator',
|
|
715
|
+
success: true,
|
|
716
|
+
},
|
|
717
|
+
});
|
|
718
|
+
});
|
|
719
|
+
|
|
720
|
+
it('should handle event spans with missing parent gracefully', async () => {
|
|
721
|
+
const orphanEventSpan = createMockSpan({
|
|
722
|
+
id: 'orphan-event-id',
|
|
723
|
+
name: 'orphan-event',
|
|
724
|
+
type: AISpanType.GENERIC,
|
|
725
|
+
isRoot: false,
|
|
726
|
+
attributes: {},
|
|
727
|
+
});
|
|
728
|
+
orphanEventSpan.isEvent = true;
|
|
729
|
+
orphanEventSpan.traceId = 'missing-trace-id';
|
|
730
|
+
|
|
731
|
+
// Should not throw
|
|
732
|
+
await expect(
|
|
733
|
+
exporter.exportEvent({
|
|
734
|
+
type: AITracingEventType.SPAN_STARTED,
|
|
735
|
+
span: orphanEventSpan,
|
|
736
|
+
}),
|
|
737
|
+
).resolves.not.toThrow();
|
|
738
|
+
|
|
739
|
+
// Should not create any Langfuse objects
|
|
740
|
+
expect(mockTrace.event).not.toHaveBeenCalled();
|
|
741
|
+
expect(mockSpan.event).not.toHaveBeenCalled();
|
|
742
|
+
});
|
|
743
|
+
});
|
|
744
|
+
|
|
581
745
|
describe('Shutdown', () => {
|
|
582
746
|
it('should shutdown Langfuse client and clear maps', async () => {
|
|
583
747
|
// Add some data to internal maps
|
|
@@ -657,6 +821,8 @@ function createMockSpan({
|
|
|
657
821
|
error: vi.fn(),
|
|
658
822
|
update: vi.fn(),
|
|
659
823
|
createChildSpan: vi.fn(),
|
|
824
|
+
createEventSpan: vi.fn(),
|
|
825
|
+
isEvent: false,
|
|
660
826
|
} as AnyAISpan;
|
|
661
827
|
|
|
662
828
|
return mockSpan;
|
package/src/ai-tracing.ts
CHANGED
|
@@ -7,9 +7,10 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import type { AITracingExporter, AITracingEvent, AnyAISpan, LLMGenerationAttributes } from '@mastra/core/ai-tracing';
|
|
10
|
-
import { AISpanType,
|
|
10
|
+
import { AISpanType, omitKeys } from '@mastra/core/ai-tracing';
|
|
11
|
+
import { ConsoleLogger } from '@mastra/core/logger';
|
|
11
12
|
import { Langfuse } from 'langfuse';
|
|
12
|
-
import type { LangfuseTraceClient, LangfuseSpanClient, LangfuseGenerationClient } from 'langfuse';
|
|
13
|
+
import type { LangfuseTraceClient, LangfuseSpanClient, LangfuseGenerationClient, LangfuseEventClient } from 'langfuse';
|
|
13
14
|
|
|
14
15
|
export interface LangfuseExporterConfig {
|
|
15
16
|
/** Langfuse API key */
|
|
@@ -20,6 +21,8 @@ export interface LangfuseExporterConfig {
|
|
|
20
21
|
baseUrl: string;
|
|
21
22
|
/** Enable realtime mode - flushes after each event for immediate visibility */
|
|
22
23
|
realtime?: boolean;
|
|
24
|
+
/** Logger level for diagnostic messages (default: 'warn') */
|
|
25
|
+
logLevel?: 'debug' | 'info' | 'warn' | 'error';
|
|
23
26
|
/** Additional options to pass to the Langfuse client */
|
|
24
27
|
options?: any;
|
|
25
28
|
}
|
|
@@ -27,18 +30,21 @@ export interface LangfuseExporterConfig {
|
|
|
27
30
|
type TraceData = {
|
|
28
31
|
trace: LangfuseTraceClient; // Langfuse trace object
|
|
29
32
|
spans: Map<string, LangfuseSpanClient | LangfuseGenerationClient>; // Maps span.id to Langfuse span/generation
|
|
33
|
+
events: Map<string, LangfuseEventClient>; // Maps span.id to Langfuse event
|
|
30
34
|
};
|
|
31
35
|
|
|
32
|
-
type
|
|
36
|
+
type LangfuseParent = LangfuseTraceClient | LangfuseSpanClient | LangfuseGenerationClient | LangfuseEventClient;
|
|
33
37
|
|
|
34
38
|
export class LangfuseExporter implements AITracingExporter {
|
|
35
39
|
name = 'langfuse';
|
|
36
40
|
private client: Langfuse;
|
|
37
41
|
private realtime: boolean;
|
|
38
42
|
private traceMap = new Map<string, TraceData>();
|
|
43
|
+
private logger: ConsoleLogger;
|
|
39
44
|
|
|
40
45
|
constructor(config: LangfuseExporterConfig) {
|
|
41
46
|
this.realtime = config.realtime ?? false;
|
|
47
|
+
this.logger = new ConsoleLogger({ level: config.logLevel ?? 'warn' });
|
|
42
48
|
this.client = new Langfuse({
|
|
43
49
|
publicKey: config.publicKey,
|
|
44
50
|
secretKey: config.secretKey,
|
|
@@ -48,15 +54,20 @@ export class LangfuseExporter implements AITracingExporter {
|
|
|
48
54
|
}
|
|
49
55
|
|
|
50
56
|
async exportEvent(event: AITracingEvent): Promise<void> {
|
|
57
|
+
if (event.span.isEvent) {
|
|
58
|
+
await this.handleEventSpan(event.span);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
51
62
|
switch (event.type) {
|
|
52
63
|
case 'span_started':
|
|
53
64
|
await this.handleSpanStarted(event.span);
|
|
54
65
|
break;
|
|
55
66
|
case 'span_updated':
|
|
56
|
-
await this.handleSpanUpdateOrEnd(event.span,
|
|
67
|
+
await this.handleSpanUpdateOrEnd(event.span, false);
|
|
57
68
|
break;
|
|
58
69
|
case 'span_ended':
|
|
59
|
-
await this.handleSpanUpdateOrEnd(event.span,
|
|
70
|
+
await this.handleSpanUpdateOrEnd(event.span, true);
|
|
60
71
|
break;
|
|
61
72
|
}
|
|
62
73
|
|
|
@@ -68,21 +79,19 @@ export class LangfuseExporter implements AITracingExporter {
|
|
|
68
79
|
|
|
69
80
|
private async handleSpanStarted(span: AnyAISpan): Promise<void> {
|
|
70
81
|
if (span.isRootSpan) {
|
|
71
|
-
|
|
72
|
-
this.traceMap.set(span.trace.id, { trace, spans: new Map() });
|
|
82
|
+
this.initTrace(span);
|
|
73
83
|
}
|
|
84
|
+
const method = 'handleSpanStarted';
|
|
74
85
|
|
|
75
|
-
const traceData = this.
|
|
86
|
+
const traceData = this.getTraceData({ span, method });
|
|
76
87
|
if (!traceData) {
|
|
77
|
-
console.log('NO TRACE');
|
|
78
|
-
// TODO: log warning
|
|
79
88
|
return;
|
|
80
89
|
}
|
|
81
90
|
|
|
82
|
-
const langfuseParent =
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
91
|
+
const langfuseParent = this.getLangfuseParent({ traceData, span, method });
|
|
92
|
+
if (!langfuseParent) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
86
95
|
|
|
87
96
|
const payload = this.buildSpanPayload(span, true);
|
|
88
97
|
|
|
@@ -92,36 +101,120 @@ export class LangfuseExporter implements AITracingExporter {
|
|
|
92
101
|
traceData.spans.set(span.id, langfuseSpan);
|
|
93
102
|
}
|
|
94
103
|
|
|
95
|
-
private async handleSpanUpdateOrEnd(span: AnyAISpan,
|
|
96
|
-
const
|
|
104
|
+
private async handleSpanUpdateOrEnd(span: AnyAISpan, isEnd: boolean): Promise<void> {
|
|
105
|
+
const method = isEnd ? 'handleSpanEnd' : 'handleSpanUpdate';
|
|
106
|
+
|
|
107
|
+
const traceData = this.getTraceData({ span, method });
|
|
97
108
|
if (!traceData) {
|
|
98
|
-
console.log('NO TRACE');
|
|
99
|
-
// TODO: log warning
|
|
100
109
|
return;
|
|
101
110
|
}
|
|
102
111
|
|
|
103
112
|
const langfuseSpan = traceData.spans.get(span.id);
|
|
104
113
|
if (!langfuseSpan) {
|
|
105
|
-
|
|
106
|
-
|
|
114
|
+
this.logger.warn('Langfuse exporter: No Langfuse span found for span update/end', {
|
|
115
|
+
traceId: span.traceId,
|
|
116
|
+
spanId: span.id,
|
|
117
|
+
spanName: span.name,
|
|
118
|
+
spanType: span.type,
|
|
119
|
+
isRootSpan: span.isRootSpan,
|
|
120
|
+
parentSpanId: span.parent?.id,
|
|
121
|
+
availableSpanIds: Array.from(traceData.spans.keys()),
|
|
122
|
+
method,
|
|
123
|
+
});
|
|
107
124
|
return;
|
|
108
125
|
}
|
|
109
126
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
langfuseSpan.end(this.buildSpanPayload(span, false));
|
|
127
|
+
// use update for both update & end, so that we can use the
|
|
128
|
+
// end time we set when ending the span.
|
|
129
|
+
langfuseSpan.update(this.buildSpanPayload(span, false));
|
|
114
130
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
131
|
+
if (isEnd && span.isRootSpan) {
|
|
132
|
+
traceData.trace.update({ output: span.output });
|
|
133
|
+
this.traceMap.delete(span.traceId);
|
|
119
134
|
}
|
|
120
135
|
}
|
|
121
136
|
|
|
137
|
+
private async handleEventSpan(span: AnyAISpan): Promise<void> {
|
|
138
|
+
if (span.isRootSpan) {
|
|
139
|
+
this.logger.debug('Langfuse exporter: Creating trace', {
|
|
140
|
+
traceId: span.traceId,
|
|
141
|
+
spanId: span.id,
|
|
142
|
+
spanName: span.name,
|
|
143
|
+
method: 'handleEventSpan',
|
|
144
|
+
});
|
|
145
|
+
this.initTrace(span);
|
|
146
|
+
}
|
|
147
|
+
const method = 'handleEventSpan';
|
|
148
|
+
|
|
149
|
+
const traceData = this.getTraceData({ span, method });
|
|
150
|
+
if (!traceData) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const langfuseParent = this.getLangfuseParent({ traceData, span, method });
|
|
155
|
+
if (!langfuseParent) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const payload = this.buildSpanPayload(span, true);
|
|
160
|
+
|
|
161
|
+
const langfuseEvent = langfuseParent.event(payload);
|
|
162
|
+
|
|
163
|
+
traceData.events.set(span.id, langfuseEvent);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
private initTrace(span: AnyAISpan): void {
|
|
167
|
+
const trace = this.client.trace(this.buildTracePayload(span));
|
|
168
|
+
this.traceMap.set(span.traceId, { trace, spans: new Map(), events: new Map() });
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
private getTraceData(options: { span: AnyAISpan; method: string }): TraceData | undefined {
|
|
172
|
+
const { span, method } = options;
|
|
173
|
+
if (this.traceMap.has(span.traceId)) {
|
|
174
|
+
return this.traceMap.get(span.traceId);
|
|
175
|
+
}
|
|
176
|
+
this.logger.warn('Langfuse exporter: No trace data found for span', {
|
|
177
|
+
traceId: span.traceId,
|
|
178
|
+
spanId: span.id,
|
|
179
|
+
spanName: span.name,
|
|
180
|
+
spanType: span.type,
|
|
181
|
+
isRootSpan: span.isRootSpan,
|
|
182
|
+
parentSpanId: span.parent?.id,
|
|
183
|
+
method,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
private getLangfuseParent(options: {
|
|
188
|
+
traceData: TraceData;
|
|
189
|
+
span: AnyAISpan;
|
|
190
|
+
method: string;
|
|
191
|
+
}): LangfuseParent | undefined {
|
|
192
|
+
const { traceData, span, method } = options;
|
|
193
|
+
|
|
194
|
+
const parentId = span.parent?.id;
|
|
195
|
+
if (!parentId) {
|
|
196
|
+
return traceData.trace;
|
|
197
|
+
}
|
|
198
|
+
if (traceData.spans.has(parentId)) {
|
|
199
|
+
return traceData.spans.get(parentId);
|
|
200
|
+
}
|
|
201
|
+
if (traceData.events.has(parentId)) {
|
|
202
|
+
return traceData.events.get(parentId);
|
|
203
|
+
}
|
|
204
|
+
this.logger.warn('Langfuse exporter: No parent data found for span', {
|
|
205
|
+
traceId: span.traceId,
|
|
206
|
+
spanId: span.id,
|
|
207
|
+
spanName: span.name,
|
|
208
|
+
spanType: span.type,
|
|
209
|
+
isRootSpan: span.isRootSpan,
|
|
210
|
+
parentSpanId: span.parent?.id,
|
|
211
|
+
method,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
122
215
|
private buildTracePayload(span: AnyAISpan): Record<string, any> {
|
|
123
216
|
const payload: Record<string, any> = {
|
|
124
|
-
id: span.
|
|
217
|
+
id: span.traceId,
|
|
125
218
|
name: span.name,
|
|
126
219
|
};
|
|
127
220
|
|
|
@@ -133,8 +226,8 @@ export class LangfuseExporter implements AITracingExporter {
|
|
|
133
226
|
|
|
134
227
|
payload.metadata = {
|
|
135
228
|
spanType: span.type,
|
|
136
|
-
...
|
|
137
|
-
...
|
|
229
|
+
...span.attributes,
|
|
230
|
+
...remainingMetadata,
|
|
138
231
|
};
|
|
139
232
|
|
|
140
233
|
return payload;
|
|
@@ -179,8 +272,8 @@ export class LangfuseExporter implements AITracingExporter {
|
|
|
179
272
|
|
|
180
273
|
payload.metadata = {
|
|
181
274
|
spanType: span.type,
|
|
182
|
-
...
|
|
183
|
-
...
|
|
275
|
+
...omitKeys(attributes, attributesToOmit),
|
|
276
|
+
...span.metadata,
|
|
184
277
|
};
|
|
185
278
|
|
|
186
279
|
if (span.errorInfo) {
|