@mastra/langfuse 0.0.0-sidebar-window-undefined-fix-20251029233656 → 0.0.0-standard-schema-20260123120255
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 +435 -4
- package/README.md +49 -7
- package/dist/helpers.d.ts +79 -0
- package/dist/helpers.d.ts.map +1 -0
- package/dist/index.cjs +180 -201
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +179 -201
- package/dist/index.js.map +1 -1
- package/dist/metrics.d.ts +17 -0
- package/dist/metrics.d.ts.map +1 -0
- package/dist/tracing.d.ts +96 -0
- package/dist/tracing.d.ts.map +1 -0
- package/package.json +18 -12
- package/dist/ai-tracing.d.ts +0 -66
- package/dist/ai-tracing.d.ts.map +0 -1
package/README.md
CHANGED
|
@@ -10,22 +10,54 @@ npm install @mastra/langfuse
|
|
|
10
10
|
|
|
11
11
|
## Usage
|
|
12
12
|
|
|
13
|
+
### Zero-Config Setup
|
|
14
|
+
|
|
15
|
+
The exporter automatically reads credentials from environment variables:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# Required
|
|
19
|
+
LANGFUSE_PUBLIC_KEY=pk-lf-...
|
|
20
|
+
LANGFUSE_SECRET_KEY=sk-lf-...
|
|
21
|
+
|
|
22
|
+
# Optional - defaults to Langfuse cloud
|
|
23
|
+
LANGFUSE_BASE_URL=https://cloud.langfuse.com
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
import { LangfuseExporter } from '@mastra/langfuse';
|
|
28
|
+
|
|
29
|
+
const mastra = new Mastra({
|
|
30
|
+
...,
|
|
31
|
+
observability: {
|
|
32
|
+
configs: {
|
|
33
|
+
langfuse: {
|
|
34
|
+
serviceName: 'my-service',
|
|
35
|
+
exporters: [new LangfuseExporter()],
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Explicit Configuration
|
|
43
|
+
|
|
44
|
+
You can also pass credentials directly:
|
|
45
|
+
|
|
13
46
|
```typescript
|
|
14
47
|
import { LangfuseExporter } from '@mastra/langfuse';
|
|
15
48
|
|
|
16
|
-
// Use with Mastra
|
|
17
49
|
const mastra = new Mastra({
|
|
18
50
|
...,
|
|
19
51
|
observability: {
|
|
20
52
|
configs: {
|
|
21
53
|
langfuse: {
|
|
22
|
-
serviceName: 'service',
|
|
54
|
+
serviceName: 'my-service',
|
|
23
55
|
exporters: [
|
|
24
56
|
new LangfuseExporter({
|
|
25
|
-
publicKey:
|
|
26
|
-
secretKey:
|
|
27
|
-
baseUrl:
|
|
28
|
-
realtime: true,
|
|
57
|
+
publicKey: 'pk-lf-...',
|
|
58
|
+
secretKey: 'sk-lf-...',
|
|
59
|
+
baseUrl: 'https://cloud.langfuse.com', // Optional
|
|
60
|
+
realtime: true, // Optional - flush after each event
|
|
29
61
|
}),
|
|
30
62
|
],
|
|
31
63
|
},
|
|
@@ -34,9 +66,19 @@ const mastra = new Mastra({
|
|
|
34
66
|
});
|
|
35
67
|
```
|
|
36
68
|
|
|
69
|
+
### Configuration Options
|
|
70
|
+
|
|
71
|
+
| Option | Type | Description |
|
|
72
|
+
| ----------- | --------- | ---------------------------------------------------------------------------- |
|
|
73
|
+
| `publicKey` | `string` | Langfuse public key. Defaults to `LANGFUSE_PUBLIC_KEY` env var |
|
|
74
|
+
| `secretKey` | `string` | Langfuse secret key. Defaults to `LANGFUSE_SECRET_KEY` env var |
|
|
75
|
+
| `baseUrl` | `string` | Langfuse host URL. Defaults to `LANGFUSE_BASE_URL` env var or Langfuse cloud |
|
|
76
|
+
| `realtime` | `boolean` | Flush after each event for immediate visibility. Defaults to `false` |
|
|
77
|
+
| `options` | `object` | Additional options to pass to the Langfuse client |
|
|
78
|
+
|
|
37
79
|
## Features
|
|
38
80
|
|
|
39
|
-
###
|
|
81
|
+
### Tracing
|
|
40
82
|
|
|
41
83
|
- **Automatic span mapping**: Root spans become Langfuse traces
|
|
42
84
|
- **Model generation support**: `MODEL_GENERATION` spans become Langfuse generations with token usage
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Langfuse Tracing Options Helpers
|
|
3
|
+
*
|
|
4
|
+
* These helpers integrate with the `buildTracingOptions` pattern from
|
|
5
|
+
* `@mastra/observability` to add Langfuse-specific tracing features.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { buildTracingOptions } from '@mastra/observability';
|
|
10
|
+
* import { withLangfusePrompt } from '@mastra/langfuse';
|
|
11
|
+
*
|
|
12
|
+
* const prompt = await langfuse.getPrompt('my-prompt');
|
|
13
|
+
*
|
|
14
|
+
* const agent = new Agent({
|
|
15
|
+
* defaultGenerateOptions: {
|
|
16
|
+
* tracingOptions: buildTracingOptions(withLangfusePrompt(prompt)),
|
|
17
|
+
* },
|
|
18
|
+
* });
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
import type { TracingOptionsUpdater } from '@mastra/observability';
|
|
22
|
+
/**
|
|
23
|
+
* Langfuse prompt input - accepts either a Langfuse SDK prompt object
|
|
24
|
+
* or manual fields.
|
|
25
|
+
*/
|
|
26
|
+
export interface LangfusePromptInput {
|
|
27
|
+
/** Prompt name */
|
|
28
|
+
name?: string;
|
|
29
|
+
/** Prompt version */
|
|
30
|
+
version?: number;
|
|
31
|
+
/** Prompt UUID */
|
|
32
|
+
id?: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Adds Langfuse prompt metadata to the tracing options
|
|
36
|
+
* to enable Langfuse Prompt Tracing.
|
|
37
|
+
*
|
|
38
|
+
* The metadata is added under `metadata.langfuse.prompt` and includes:
|
|
39
|
+
* - `name` - Prompt name
|
|
40
|
+
* - `version` - Prompt version
|
|
41
|
+
* - `id` - Prompt UUID
|
|
42
|
+
*
|
|
43
|
+
* All fields are deeply merged with any existing metadata.
|
|
44
|
+
*
|
|
45
|
+
* @param prompt - A Langfuse prompt object (from `langfuse.getPrompt()`) or manual fields
|
|
46
|
+
* @returns A TracingOptionsUpdater function for use with `buildTracingOptions`
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```typescript
|
|
50
|
+
* import { buildTracingOptions } from '@mastra/observability';
|
|
51
|
+
* import { withLangfusePrompt } from '@mastra/langfuse';
|
|
52
|
+
* import { Langfuse } from 'langfuse';
|
|
53
|
+
*
|
|
54
|
+
* const langfuse = new Langfuse();
|
|
55
|
+
* const prompt = await langfuse.getPrompt('customer-support');
|
|
56
|
+
*
|
|
57
|
+
* // Use with buildTracingOptions
|
|
58
|
+
* const tracingOptions = buildTracingOptions(
|
|
59
|
+
* withLangfusePrompt(prompt),
|
|
60
|
+
* );
|
|
61
|
+
*
|
|
62
|
+
* // Or directly in agent config
|
|
63
|
+
* const agent = new Agent({
|
|
64
|
+
* name: 'support-agent',
|
|
65
|
+
* instructions: prompt.prompt,
|
|
66
|
+
* model: openai('gpt-4o'),
|
|
67
|
+
* defaultGenerateOptions: {
|
|
68
|
+
* tracingOptions: buildTracingOptions(withLangfusePrompt(prompt)),
|
|
69
|
+
* },
|
|
70
|
+
* });
|
|
71
|
+
*
|
|
72
|
+
* // Manual fields also work
|
|
73
|
+
* const tracingOptions = buildTracingOptions(
|
|
74
|
+
* withLangfusePrompt({ name: 'my-prompt', version: 1 }),
|
|
75
|
+
* );
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
export declare function withLangfusePrompt(prompt: LangfusePromptInput): TracingOptionsUpdater;
|
|
79
|
+
//# sourceMappingURL=helpers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../src/helpers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAEnE;;;GAGG;AACH,MAAM,WAAW,mBAAmB;IAClC,kBAAkB;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qBAAqB;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kBAAkB;IAClB,EAAE,CAAC,EAAE,MAAM,CAAC;CACb;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,mBAAmB,GAAG,qBAAqB,CAerF"}
|
package/dist/index.cjs
CHANGED
|
@@ -1,177 +1,132 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var observability$1 = require('@mastra/core/observability');
|
|
4
|
+
var utils = require('@mastra/core/utils');
|
|
5
|
+
var observability = require('@mastra/observability');
|
|
4
6
|
var langfuse = require('langfuse');
|
|
5
7
|
|
|
6
|
-
// src/
|
|
7
|
-
|
|
8
|
+
// src/tracing.ts
|
|
9
|
+
|
|
10
|
+
// src/metrics.ts
|
|
11
|
+
function formatUsageMetrics(usage) {
|
|
12
|
+
if (!usage) return {};
|
|
13
|
+
const metrics = {};
|
|
14
|
+
if (usage.inputTokens !== void 0) {
|
|
15
|
+
metrics.input = usage.inputTokens;
|
|
16
|
+
if (usage.inputDetails?.cacheWrite !== void 0) {
|
|
17
|
+
metrics.cache_write_input_tokens = usage.inputDetails.cacheWrite;
|
|
18
|
+
metrics.input -= metrics.cache_write_input_tokens;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
if (usage.inputDetails?.cacheRead !== void 0) {
|
|
22
|
+
metrics.cache_read_input_tokens = usage.inputDetails.cacheRead;
|
|
23
|
+
}
|
|
24
|
+
if (usage.outputTokens !== void 0) {
|
|
25
|
+
metrics.output = usage.outputTokens;
|
|
26
|
+
}
|
|
27
|
+
if (usage.outputDetails?.reasoning !== void 0) {
|
|
28
|
+
metrics.reasoning = usage.outputDetails.reasoning;
|
|
29
|
+
}
|
|
30
|
+
if (metrics.input != null && metrics.output != null) {
|
|
31
|
+
metrics.total = metrics.input + metrics.output;
|
|
32
|
+
if (metrics.cache_write_input_tokens != null) {
|
|
33
|
+
metrics.total += metrics.cache_write_input_tokens;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return metrics;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// src/tracing.ts
|
|
40
|
+
var LangfuseExporter = class extends observability.TrackingExporter {
|
|
8
41
|
name = "langfuse";
|
|
9
|
-
client;
|
|
10
|
-
realtime;
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
42
|
+
#client;
|
|
43
|
+
#realtime;
|
|
44
|
+
constructor(config = {}) {
|
|
45
|
+
const publicKey = config.publicKey ?? process.env.LANGFUSE_PUBLIC_KEY;
|
|
46
|
+
const secretKey = config.secretKey ?? process.env.LANGFUSE_SECRET_KEY;
|
|
47
|
+
const baseUrl = config.baseUrl ?? process.env.LANGFUSE_BASE_URL;
|
|
48
|
+
super({
|
|
49
|
+
...config,
|
|
50
|
+
publicKey,
|
|
51
|
+
secretKey,
|
|
52
|
+
baseUrl
|
|
53
|
+
});
|
|
54
|
+
this.#realtime = config.realtime ?? false;
|
|
55
|
+
if (!publicKey || !secretKey) {
|
|
56
|
+
const publicKeySource = config.publicKey ? "from config" : process.env.LANGFUSE_PUBLIC_KEY ? "from env" : "missing";
|
|
57
|
+
const secretKeySource = config.secretKey ? "from config" : process.env.LANGFUSE_SECRET_KEY ? "from env" : "missing";
|
|
16
58
|
this.setDisabled(
|
|
17
|
-
`Missing required credentials (publicKey: ${
|
|
59
|
+
`Missing required credentials (publicKey: ${publicKeySource}, secretKey: ${secretKeySource}). Set LANGFUSE_PUBLIC_KEY and LANGFUSE_SECRET_KEY environment variables or pass them in config.`
|
|
18
60
|
);
|
|
19
|
-
this.client = null;
|
|
20
61
|
return;
|
|
21
62
|
}
|
|
22
|
-
this
|
|
23
|
-
publicKey
|
|
24
|
-
secretKey
|
|
25
|
-
baseUrl
|
|
63
|
+
this.#client = new langfuse.Langfuse({
|
|
64
|
+
publicKey,
|
|
65
|
+
secretKey,
|
|
66
|
+
baseUrl,
|
|
26
67
|
...config.options
|
|
27
68
|
});
|
|
28
69
|
}
|
|
29
|
-
async
|
|
30
|
-
if (
|
|
31
|
-
await this
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
switch (event.type) {
|
|
35
|
-
case "span_started":
|
|
36
|
-
await this.handleSpanStarted(event.exportedSpan);
|
|
37
|
-
break;
|
|
38
|
-
case "span_updated":
|
|
39
|
-
await this.handleSpanUpdateOrEnd(event.exportedSpan, false);
|
|
40
|
-
break;
|
|
41
|
-
case "span_ended":
|
|
42
|
-
await this.handleSpanUpdateOrEnd(event.exportedSpan, true);
|
|
43
|
-
break;
|
|
44
|
-
}
|
|
45
|
-
if (this.realtime) {
|
|
46
|
-
await this.client.flushAsync();
|
|
70
|
+
async _postExportTracingEvent() {
|
|
71
|
+
if (this.#realtime) {
|
|
72
|
+
await this.#client?.flushAsync();
|
|
47
73
|
}
|
|
48
74
|
}
|
|
49
|
-
async
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
const langfuseParent = this.getLangfuseParent({ traceData, span, method });
|
|
75
|
+
async _buildRoot(args) {
|
|
76
|
+
const { span } = args;
|
|
77
|
+
return this.#client?.trace(this.buildTracePayload(span));
|
|
78
|
+
}
|
|
79
|
+
async _buildEvent(args) {
|
|
80
|
+
const { span, traceData } = args;
|
|
81
|
+
const langfuseParent = traceData.getParentOrRoot({ span });
|
|
59
82
|
if (!langfuseParent) {
|
|
60
83
|
return;
|
|
61
84
|
}
|
|
62
|
-
const payload = this.buildSpanPayload(span, true);
|
|
63
|
-
|
|
64
|
-
traceData.spans.set(span.id, langfuseSpan);
|
|
65
|
-
traceData.activeSpans.add(span.id);
|
|
85
|
+
const payload = this.buildSpanPayload(span, true, traceData);
|
|
86
|
+
return langfuseParent.event(payload);
|
|
66
87
|
}
|
|
67
|
-
async
|
|
68
|
-
const
|
|
69
|
-
const
|
|
70
|
-
if (!
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
const langfuseSpan = traceData.spans.get(span.id);
|
|
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
|
-
}
|
|
82
|
-
this.logger.warn("Langfuse exporter: No Langfuse span found for span update/end", {
|
|
83
|
-
traceId: span.traceId,
|
|
84
|
-
spanId: span.id,
|
|
85
|
-
spanName: span.name,
|
|
86
|
-
spanType: span.type,
|
|
87
|
-
isRootSpan: span.isRootSpan,
|
|
88
|
-
parentSpanId: span.parentSpanId,
|
|
89
|
-
method
|
|
90
|
-
});
|
|
88
|
+
async _buildSpan(args) {
|
|
89
|
+
const { span, traceData } = args;
|
|
90
|
+
const langfuseParent = traceData.getParentOrRoot({ span });
|
|
91
|
+
if (!langfuseParent) {
|
|
91
92
|
return;
|
|
92
93
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}
|
|
102
|
-
}
|
|
94
|
+
const payload = this.buildSpanPayload(span, true, traceData);
|
|
95
|
+
const langfuseSpan = span.type === observability$1.SpanType.MODEL_GENERATION ? langfuseParent.generation(payload) : langfuseParent.span(payload);
|
|
96
|
+
this.logger.debug(`${this.name}: built span`, {
|
|
97
|
+
traceId: span.traceId,
|
|
98
|
+
spanId: payload.id,
|
|
99
|
+
method: "_buildSpan"
|
|
100
|
+
});
|
|
101
|
+
return langfuseSpan;
|
|
103
102
|
}
|
|
104
|
-
async
|
|
105
|
-
|
|
106
|
-
|
|
103
|
+
async _updateSpan(args) {
|
|
104
|
+
const { span, traceData } = args;
|
|
105
|
+
const langfuseSpan = traceData.getSpan({ spanId: span.id });
|
|
106
|
+
if (langfuseSpan) {
|
|
107
|
+
this.logger.debug(`${this.name}: found span for update`, {
|
|
107
108
|
traceId: span.traceId,
|
|
108
|
-
spanId:
|
|
109
|
-
|
|
110
|
-
method: "handleEventSpan"
|
|
109
|
+
spanId: langfuseSpan.id,
|
|
110
|
+
method: "_updateSpan"
|
|
111
111
|
});
|
|
112
|
-
this.
|
|
113
|
-
|
|
114
|
-
const method = "handleEventSpan";
|
|
115
|
-
const traceData = this.getTraceData({ span, method });
|
|
116
|
-
if (!traceData) {
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
const langfuseParent = this.getLangfuseParent({ traceData, span, method });
|
|
120
|
-
if (!langfuseParent) {
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
const payload = this.buildSpanPayload(span, true);
|
|
124
|
-
const langfuseEvent = langfuseParent.event(payload);
|
|
125
|
-
traceData.events.set(span.id, langfuseEvent);
|
|
126
|
-
if (!span.endTime) {
|
|
127
|
-
traceData.activeSpans.add(span.id);
|
|
112
|
+
const updatePayload = this.buildSpanPayload(span, false, traceData);
|
|
113
|
+
langfuseSpan.update(updatePayload);
|
|
128
114
|
}
|
|
129
115
|
}
|
|
130
|
-
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
rootSpanId: span.id
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
getTraceData(options) {
|
|
141
|
-
const { span, method } = options;
|
|
142
|
-
if (this.traceMap.has(span.traceId)) {
|
|
143
|
-
return this.traceMap.get(span.traceId);
|
|
116
|
+
async _finishSpan(args) {
|
|
117
|
+
const { span, traceData } = args;
|
|
118
|
+
const langfuseSpan = traceData.getSpan({ spanId: span.id });
|
|
119
|
+
langfuseSpan?.update(this.buildSpanPayload(span, false, traceData));
|
|
120
|
+
if (span.isRootSpan) {
|
|
121
|
+
const langfuseRoot = traceData.getRoot();
|
|
122
|
+
langfuseRoot?.update({ output: span.output });
|
|
144
123
|
}
|
|
145
|
-
this.logger.warn("Langfuse exporter: No trace data found for span", {
|
|
146
|
-
traceId: span.traceId,
|
|
147
|
-
spanId: span.id,
|
|
148
|
-
spanName: span.name,
|
|
149
|
-
spanType: span.type,
|
|
150
|
-
isRootSpan: span.isRootSpan,
|
|
151
|
-
parentSpanId: span.parentSpanId,
|
|
152
|
-
method
|
|
153
|
-
});
|
|
154
124
|
}
|
|
155
|
-
|
|
156
|
-
const {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
}
|
|
161
|
-
if (traceData.spans.has(parentId)) {
|
|
162
|
-
return traceData.spans.get(parentId);
|
|
163
|
-
}
|
|
164
|
-
if (traceData.events.has(parentId)) {
|
|
165
|
-
return traceData.events.get(parentId);
|
|
166
|
-
}
|
|
167
|
-
this.logger.warn("Langfuse exporter: No parent data found for span", {
|
|
168
|
-
traceId: span.traceId,
|
|
169
|
-
spanId: span.id,
|
|
170
|
-
spanName: span.name,
|
|
171
|
-
spanType: span.type,
|
|
172
|
-
isRootSpan: span.isRootSpan,
|
|
173
|
-
parentSpanId: span.parentSpanId,
|
|
174
|
-
method
|
|
125
|
+
async _abortSpan(args) {
|
|
126
|
+
const { span, reason } = args;
|
|
127
|
+
span.end({
|
|
128
|
+
level: "ERROR",
|
|
129
|
+
statusMessage: reason.message
|
|
175
130
|
});
|
|
176
131
|
}
|
|
177
132
|
buildTracePayload(span) {
|
|
@@ -183,6 +138,7 @@ var LangfuseExporter = class extends aiTracing.BaseExporter {
|
|
|
183
138
|
if (userId) payload.userId = userId;
|
|
184
139
|
if (sessionId) payload.sessionId = sessionId;
|
|
185
140
|
if (span.input) payload.input = span.input;
|
|
141
|
+
if (span.tags?.length) payload.tags = span.tags;
|
|
186
142
|
payload.metadata = {
|
|
187
143
|
spanType: span.type,
|
|
188
144
|
...span.attributes,
|
|
@@ -191,81 +147,79 @@ var LangfuseExporter = class extends aiTracing.BaseExporter {
|
|
|
191
147
|
return payload;
|
|
192
148
|
}
|
|
193
149
|
/**
|
|
194
|
-
*
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
*
|
|
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
|
|
150
|
+
* Look up the Langfuse prompt from the closest span that has one.
|
|
151
|
+
* This enables prompt inheritance for MODEL_GENERATION spans when the prompt
|
|
152
|
+
* is set on a parent span (e.g., AGENT_RUN) rather than directly on the generation.
|
|
153
|
+
* This enables prompt linking when:
|
|
154
|
+
* - A workflow calls multiple agents, each with different prompts
|
|
155
|
+
* - Nested agents have different prompts
|
|
156
|
+
* - The prompt is set on AGENT_RUN but MODEL_GENERATION inherits it
|
|
204
157
|
*/
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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;
|
|
158
|
+
findLangfusePrompt(traceData, span) {
|
|
159
|
+
let currentSpanId = span.id;
|
|
160
|
+
while (currentSpanId) {
|
|
161
|
+
const providerMetadata = traceData.getMetadata({ spanId: currentSpanId });
|
|
162
|
+
if (providerMetadata?.prompt) {
|
|
163
|
+
this.logger.debug(`${this.name}: found prompt in provider metadata`, {
|
|
164
|
+
traceId: span.traceId,
|
|
165
|
+
spanId: span.id,
|
|
166
|
+
prompt: providerMetadata?.prompt
|
|
167
|
+
});
|
|
168
|
+
return providerMetadata.prompt;
|
|
169
|
+
}
|
|
170
|
+
currentSpanId = traceData.getParentId({ spanId: currentSpanId });
|
|
232
171
|
}
|
|
233
|
-
return
|
|
172
|
+
return void 0;
|
|
234
173
|
}
|
|
235
|
-
buildSpanPayload(span, isCreate) {
|
|
174
|
+
buildSpanPayload(span, isCreate, traceData) {
|
|
236
175
|
const payload = {};
|
|
237
176
|
if (isCreate) {
|
|
238
177
|
payload.id = span.id;
|
|
239
178
|
payload.name = span.name;
|
|
240
179
|
payload.startTime = span.startTime;
|
|
241
|
-
if (span.input !== void 0) payload.input = span.input;
|
|
242
180
|
}
|
|
181
|
+
if (span.input !== void 0) payload.input = span.input;
|
|
243
182
|
if (span.output !== void 0) payload.output = span.output;
|
|
244
183
|
if (span.endTime !== void 0) payload.endTime = span.endTime;
|
|
245
184
|
const attributes = span.attributes ?? {};
|
|
185
|
+
const metadata = {
|
|
186
|
+
...span.metadata
|
|
187
|
+
};
|
|
246
188
|
const attributesToOmit = [];
|
|
247
|
-
|
|
189
|
+
const metadataToOmit = [];
|
|
190
|
+
if (span.type === observability$1.SpanType.MODEL_GENERATION) {
|
|
248
191
|
const modelAttr = attributes;
|
|
249
192
|
if (modelAttr.model !== void 0) {
|
|
250
193
|
payload.model = modelAttr.model;
|
|
251
194
|
attributesToOmit.push("model");
|
|
252
195
|
}
|
|
253
196
|
if (modelAttr.usage !== void 0) {
|
|
254
|
-
|
|
255
|
-
if (normalizedUsage) {
|
|
256
|
-
payload.usage = normalizedUsage;
|
|
257
|
-
}
|
|
197
|
+
payload.usageDetails = formatUsageMetrics(modelAttr.usage);
|
|
258
198
|
attributesToOmit.push("usage");
|
|
259
199
|
}
|
|
260
200
|
if (modelAttr.parameters !== void 0) {
|
|
261
201
|
payload.modelParameters = modelAttr.parameters;
|
|
262
202
|
attributesToOmit.push("parameters");
|
|
263
203
|
}
|
|
204
|
+
const promptData = this.findLangfusePrompt(traceData, span);
|
|
205
|
+
const hasNameAndVersion = promptData?.name !== void 0 && promptData?.version !== void 0;
|
|
206
|
+
const hasId = promptData?.id !== void 0;
|
|
207
|
+
if (hasNameAndVersion || hasId) {
|
|
208
|
+
payload.prompt = {};
|
|
209
|
+
if (promptData?.name !== void 0) payload.prompt.name = promptData.name;
|
|
210
|
+
if (promptData?.version !== void 0) payload.prompt.version = promptData.version;
|
|
211
|
+
if (promptData?.id !== void 0) payload.prompt.id = promptData.id;
|
|
212
|
+
metadataToOmit.push("langfuse");
|
|
213
|
+
}
|
|
214
|
+
if (modelAttr.completionStartTime !== void 0) {
|
|
215
|
+
payload.completionStartTime = modelAttr.completionStartTime;
|
|
216
|
+
attributesToOmit.push("completionStartTime");
|
|
217
|
+
}
|
|
264
218
|
}
|
|
265
219
|
payload.metadata = {
|
|
266
220
|
spanType: span.type,
|
|
267
|
-
...
|
|
268
|
-
...
|
|
221
|
+
...utils.omitKeys(attributes, attributesToOmit),
|
|
222
|
+
...utils.omitKeys(metadata, metadataToOmit)
|
|
269
223
|
};
|
|
270
224
|
if (span.errorInfo) {
|
|
271
225
|
payload.level = "ERROR";
|
|
@@ -281,9 +235,9 @@ var LangfuseExporter = class extends aiTracing.BaseExporter {
|
|
|
281
235
|
scorerName,
|
|
282
236
|
metadata
|
|
283
237
|
}) {
|
|
284
|
-
if (!this
|
|
238
|
+
if (!this.#client) return;
|
|
285
239
|
try {
|
|
286
|
-
await this
|
|
240
|
+
await this.#client.score({
|
|
287
241
|
id: `${traceId}-${scorerName}`,
|
|
288
242
|
traceId,
|
|
289
243
|
observationId: spanId,
|
|
@@ -302,15 +256,40 @@ var LangfuseExporter = class extends aiTracing.BaseExporter {
|
|
|
302
256
|
});
|
|
303
257
|
}
|
|
304
258
|
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
259
|
+
/**
|
|
260
|
+
* Force flush any buffered data to Langfuse without shutting down.
|
|
261
|
+
*/
|
|
262
|
+
async _flush() {
|
|
263
|
+
if (this.#client) {
|
|
264
|
+
await this.#client.flushAsync();
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
async _postShutdown() {
|
|
268
|
+
if (this.#client) {
|
|
269
|
+
await this.#client.shutdownAsync();
|
|
308
270
|
}
|
|
309
|
-
this.traceMap.clear();
|
|
310
|
-
await super.shutdown();
|
|
311
271
|
}
|
|
312
272
|
};
|
|
313
273
|
|
|
274
|
+
// src/helpers.ts
|
|
275
|
+
function withLangfusePrompt(prompt) {
|
|
276
|
+
return (opts) => ({
|
|
277
|
+
...opts,
|
|
278
|
+
metadata: {
|
|
279
|
+
...opts.metadata,
|
|
280
|
+
langfuse: {
|
|
281
|
+
...opts.metadata?.langfuse,
|
|
282
|
+
prompt: {
|
|
283
|
+
...prompt.name !== void 0 && { name: prompt.name },
|
|
284
|
+
...prompt.version !== void 0 && { version: prompt.version },
|
|
285
|
+
...prompt.id !== void 0 && { id: prompt.id }
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
|
|
314
292
|
exports.LangfuseExporter = LangfuseExporter;
|
|
293
|
+
exports.withLangfusePrompt = withLangfusePrompt;
|
|
315
294
|
//# sourceMappingURL=index.cjs.map
|
|
316
295
|
//# sourceMappingURL=index.cjs.map
|