@mastra/langfuse 0.0.0-unified-sidebar-20251010130811 → 0.0.0-unified-workspace-snapshot-20260128233410
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 +567 -3
- package/README.md +50 -8
- package/dist/helpers.d.ts +79 -0
- package/dist/helpers.d.ts.map +1 -0
- package/dist/index.cjs +192 -173
- 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 +191 -173
- 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 +19 -13
- package/dist/ai-tracing.d.ts +0 -49
- 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,12 +66,22 @@ 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
|
|
43
85
|
- **Type-specific metadata**: Extracts relevant metadata for each span type (agents, tools, workflows)
|
|
44
86
|
- **Error tracking**: Automatic error status and message tracking
|
|
45
87
|
- **Hierarchical traces**: Maintains parent-child relationships
|
|
@@ -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,183 +1,132 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
4
|
-
var
|
|
3
|
+
var observability$1 = require('@mastra/core/observability');
|
|
4
|
+
var utils = require('@mastra/core/utils');
|
|
5
|
+
var observability = require('@mastra/observability');
|
|
5
6
|
var langfuse = require('langfuse');
|
|
6
7
|
|
|
7
|
-
// src/
|
|
8
|
-
|
|
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 {
|
|
9
41
|
name = "langfuse";
|
|
10
|
-
client;
|
|
11
|
-
realtime;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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";
|
|
58
|
+
this.setDisabled(
|
|
59
|
+
`Missing required credentials (publicKey: ${publicKeySource}, secretKey: ${secretKeySource}). Set LANGFUSE_PUBLIC_KEY and LANGFUSE_SECRET_KEY environment variables or pass them in config.`
|
|
60
|
+
);
|
|
23
61
|
return;
|
|
24
62
|
}
|
|
25
|
-
this
|
|
26
|
-
publicKey
|
|
27
|
-
secretKey
|
|
28
|
-
baseUrl
|
|
63
|
+
this.#client = new langfuse.Langfuse({
|
|
64
|
+
publicKey,
|
|
65
|
+
secretKey,
|
|
66
|
+
baseUrl,
|
|
29
67
|
...config.options
|
|
30
68
|
});
|
|
31
69
|
}
|
|
32
|
-
async
|
|
33
|
-
if (
|
|
34
|
-
|
|
35
|
-
}
|
|
36
|
-
if (event.exportedSpan.isEvent) {
|
|
37
|
-
await this.handleEventSpan(event.exportedSpan);
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
switch (event.type) {
|
|
41
|
-
case "span_started":
|
|
42
|
-
await this.handleSpanStarted(event.exportedSpan);
|
|
43
|
-
break;
|
|
44
|
-
case "span_updated":
|
|
45
|
-
await this.handleSpanUpdateOrEnd(event.exportedSpan, false);
|
|
46
|
-
break;
|
|
47
|
-
case "span_ended":
|
|
48
|
-
await this.handleSpanUpdateOrEnd(event.exportedSpan, true);
|
|
49
|
-
break;
|
|
50
|
-
}
|
|
51
|
-
if (this.realtime) {
|
|
52
|
-
await this.client.flushAsync();
|
|
70
|
+
async _postExportTracingEvent() {
|
|
71
|
+
if (this.#realtime) {
|
|
72
|
+
await this.#client?.flushAsync();
|
|
53
73
|
}
|
|
54
74
|
}
|
|
55
|
-
async
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
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 });
|
|
65
82
|
if (!langfuseParent) {
|
|
66
83
|
return;
|
|
67
84
|
}
|
|
68
|
-
const payload = this.buildSpanPayload(span, true);
|
|
69
|
-
|
|
70
|
-
traceData.spans.set(span.id, langfuseSpan);
|
|
71
|
-
traceData.activeSpans.add(span.id);
|
|
85
|
+
const payload = this.buildSpanPayload(span, true, traceData);
|
|
86
|
+
return langfuseParent.event(payload);
|
|
72
87
|
}
|
|
73
|
-
async
|
|
74
|
-
const
|
|
75
|
-
const
|
|
76
|
-
if (!
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
const langfuseSpan = traceData.spans.get(span.id);
|
|
80
|
-
if (!langfuseSpan) {
|
|
81
|
-
if (isEnd && span.isEvent) {
|
|
82
|
-
traceData.activeSpans.delete(span.id);
|
|
83
|
-
if (traceData.activeSpans.size === 0) {
|
|
84
|
-
this.traceMap.delete(span.traceId);
|
|
85
|
-
}
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
this.logger.warn("Langfuse exporter: No Langfuse span found for span update/end", {
|
|
89
|
-
traceId: span.traceId,
|
|
90
|
-
spanId: span.id,
|
|
91
|
-
spanName: span.name,
|
|
92
|
-
spanType: span.type,
|
|
93
|
-
isRootSpan: span.isRootSpan,
|
|
94
|
-
parentSpanId: span.parentSpanId,
|
|
95
|
-
method
|
|
96
|
-
});
|
|
88
|
+
async _buildSpan(args) {
|
|
89
|
+
const { span, traceData } = args;
|
|
90
|
+
const langfuseParent = traceData.getParentOrRoot({ span });
|
|
91
|
+
if (!langfuseParent) {
|
|
97
92
|
return;
|
|
98
93
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}
|
|
108
|
-
}
|
|
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;
|
|
109
102
|
}
|
|
110
|
-
async
|
|
111
|
-
|
|
112
|
-
|
|
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`, {
|
|
113
108
|
traceId: span.traceId,
|
|
114
|
-
spanId:
|
|
115
|
-
|
|
116
|
-
method: "handleEventSpan"
|
|
109
|
+
spanId: langfuseSpan.id,
|
|
110
|
+
method: "_updateSpan"
|
|
117
111
|
});
|
|
118
|
-
this.
|
|
119
|
-
|
|
120
|
-
const method = "handleEventSpan";
|
|
121
|
-
const traceData = this.getTraceData({ span, method });
|
|
122
|
-
if (!traceData) {
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
const langfuseParent = this.getLangfuseParent({ traceData, span, method });
|
|
126
|
-
if (!langfuseParent) {
|
|
127
|
-
return;
|
|
112
|
+
const updatePayload = this.buildSpanPayload(span, false, traceData);
|
|
113
|
+
langfuseSpan.update(updatePayload);
|
|
128
114
|
}
|
|
129
|
-
const payload = this.buildSpanPayload(span, true);
|
|
130
|
-
const langfuseEvent = langfuseParent.event(payload);
|
|
131
|
-
traceData.events.set(span.id, langfuseEvent);
|
|
132
|
-
if (!span.endTime) {
|
|
133
|
-
traceData.activeSpans.add(span.id);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
initTrace(span) {
|
|
137
|
-
const trace = this.client.trace(this.buildTracePayload(span));
|
|
138
|
-
this.traceMap.set(span.traceId, {
|
|
139
|
-
trace,
|
|
140
|
-
spans: /* @__PURE__ */ new Map(),
|
|
141
|
-
events: /* @__PURE__ */ new Map(),
|
|
142
|
-
activeSpans: /* @__PURE__ */ new Set(),
|
|
143
|
-
rootSpanId: span.id
|
|
144
|
-
});
|
|
145
115
|
}
|
|
146
|
-
|
|
147
|
-
const { span,
|
|
148
|
-
|
|
149
|
-
|
|
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 });
|
|
150
123
|
}
|
|
151
|
-
this.logger.warn("Langfuse exporter: No trace data found for span", {
|
|
152
|
-
traceId: span.traceId,
|
|
153
|
-
spanId: span.id,
|
|
154
|
-
spanName: span.name,
|
|
155
|
-
spanType: span.type,
|
|
156
|
-
isRootSpan: span.isRootSpan,
|
|
157
|
-
parentSpanId: span.parentSpanId,
|
|
158
|
-
method
|
|
159
|
-
});
|
|
160
124
|
}
|
|
161
|
-
|
|
162
|
-
const {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
}
|
|
167
|
-
if (traceData.spans.has(parentId)) {
|
|
168
|
-
return traceData.spans.get(parentId);
|
|
169
|
-
}
|
|
170
|
-
if (traceData.events.has(parentId)) {
|
|
171
|
-
return traceData.events.get(parentId);
|
|
172
|
-
}
|
|
173
|
-
this.logger.warn("Langfuse exporter: No parent data found for span", {
|
|
174
|
-
traceId: span.traceId,
|
|
175
|
-
spanId: span.id,
|
|
176
|
-
spanName: span.name,
|
|
177
|
-
spanType: span.type,
|
|
178
|
-
isRootSpan: span.isRootSpan,
|
|
179
|
-
parentSpanId: span.parentSpanId,
|
|
180
|
-
method
|
|
125
|
+
async _abortSpan(args) {
|
|
126
|
+
const { span, reason } = args;
|
|
127
|
+
span.end({
|
|
128
|
+
level: "ERROR",
|
|
129
|
+
statusMessage: reason.message
|
|
181
130
|
});
|
|
182
131
|
}
|
|
183
132
|
buildTracePayload(span) {
|
|
@@ -189,6 +138,7 @@ var LangfuseExporter = class {
|
|
|
189
138
|
if (userId) payload.userId = userId;
|
|
190
139
|
if (sessionId) payload.sessionId = sessionId;
|
|
191
140
|
if (span.input) payload.input = span.input;
|
|
141
|
+
if (span.tags?.length) payload.tags = span.tags;
|
|
192
142
|
payload.metadata = {
|
|
193
143
|
spanType: span.type,
|
|
194
144
|
...span.attributes,
|
|
@@ -196,37 +146,80 @@ var LangfuseExporter = class {
|
|
|
196
146
|
};
|
|
197
147
|
return payload;
|
|
198
148
|
}
|
|
199
|
-
|
|
149
|
+
/**
|
|
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
|
|
157
|
+
*/
|
|
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 });
|
|
171
|
+
}
|
|
172
|
+
return void 0;
|
|
173
|
+
}
|
|
174
|
+
buildSpanPayload(span, isCreate, traceData) {
|
|
200
175
|
const payload = {};
|
|
201
176
|
if (isCreate) {
|
|
202
177
|
payload.id = span.id;
|
|
203
178
|
payload.name = span.name;
|
|
204
179
|
payload.startTime = span.startTime;
|
|
205
|
-
if (span.input !== void 0) payload.input = span.input;
|
|
206
180
|
}
|
|
181
|
+
if (span.input !== void 0) payload.input = span.input;
|
|
207
182
|
if (span.output !== void 0) payload.output = span.output;
|
|
208
183
|
if (span.endTime !== void 0) payload.endTime = span.endTime;
|
|
209
184
|
const attributes = span.attributes ?? {};
|
|
185
|
+
const metadata = {
|
|
186
|
+
...span.metadata
|
|
187
|
+
};
|
|
210
188
|
const attributesToOmit = [];
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
189
|
+
const metadataToOmit = [];
|
|
190
|
+
if (span.type === observability$1.SpanType.MODEL_GENERATION) {
|
|
191
|
+
const modelAttr = attributes;
|
|
192
|
+
if (modelAttr.model !== void 0) {
|
|
193
|
+
payload.model = modelAttr.model;
|
|
215
194
|
attributesToOmit.push("model");
|
|
216
195
|
}
|
|
217
|
-
if (
|
|
218
|
-
payload.
|
|
196
|
+
if (modelAttr.usage !== void 0) {
|
|
197
|
+
payload.usageDetails = formatUsageMetrics(modelAttr.usage);
|
|
219
198
|
attributesToOmit.push("usage");
|
|
220
199
|
}
|
|
221
|
-
if (
|
|
222
|
-
payload.modelParameters =
|
|
200
|
+
if (modelAttr.parameters !== void 0) {
|
|
201
|
+
payload.modelParameters = modelAttr.parameters;
|
|
223
202
|
attributesToOmit.push("parameters");
|
|
224
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
|
+
}
|
|
225
218
|
}
|
|
226
219
|
payload.metadata = {
|
|
227
220
|
spanType: span.type,
|
|
228
|
-
...
|
|
229
|
-
...
|
|
221
|
+
...utils.omitKeys(attributes, attributesToOmit),
|
|
222
|
+
...utils.omitKeys(metadata, metadataToOmit)
|
|
230
223
|
};
|
|
231
224
|
if (span.errorInfo) {
|
|
232
225
|
payload.level = "ERROR";
|
|
@@ -242,9 +235,9 @@ var LangfuseExporter = class {
|
|
|
242
235
|
scorerName,
|
|
243
236
|
metadata
|
|
244
237
|
}) {
|
|
245
|
-
if (!this
|
|
238
|
+
if (!this.#client) return;
|
|
246
239
|
try {
|
|
247
|
-
await this
|
|
240
|
+
await this.#client.score({
|
|
248
241
|
id: `${traceId}-${scorerName}`,
|
|
249
242
|
traceId,
|
|
250
243
|
observationId: spanId,
|
|
@@ -263,14 +256,40 @@ var LangfuseExporter = class {
|
|
|
263
256
|
});
|
|
264
257
|
}
|
|
265
258
|
}
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
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();
|
|
269
270
|
}
|
|
270
|
-
this.traceMap.clear();
|
|
271
271
|
}
|
|
272
272
|
};
|
|
273
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
|
+
|
|
274
292
|
exports.LangfuseExporter = LangfuseExporter;
|
|
293
|
+
exports.withLangfusePrompt = withLangfusePrompt;
|
|
275
294
|
//# sourceMappingURL=index.cjs.map
|
|
276
295
|
//# sourceMappingURL=index.cjs.map
|