@core-ai/langfuse 0.10.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/README.md +47 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +308 -0
- package/package.json +60 -0
package/README.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# @core-ai/langfuse
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@core-ai/langfuse)
|
|
4
|
+
|
|
5
|
+
Langfuse middleware for `@core-ai/core-ai`.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @core-ai/langfuse @langfuse/tracing @langfuse/otel @opentelemetry/sdk-node
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
`@langfuse/tracing` and `@langfuse/otel` are peer dependencies. `@opentelemetry/sdk-node`
|
|
14
|
+
is required to register the `LangfuseSpanProcessor`.
|
|
15
|
+
|
|
16
|
+
## Setup
|
|
17
|
+
|
|
18
|
+
Create an instrumentation file and import it at your application's entry point:
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import { NodeSDK } from '@opentelemetry/sdk-node';
|
|
22
|
+
import { LangfuseSpanProcessor } from '@langfuse/otel';
|
|
23
|
+
|
|
24
|
+
export const sdk = new NodeSDK({
|
|
25
|
+
spanProcessors: [new LangfuseSpanProcessor()],
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
sdk.start();
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Usage
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
import { wrapChatModel } from '@core-ai/core-ai';
|
|
35
|
+
import { createOpenAI } from '@core-ai/openai';
|
|
36
|
+
import { createLangfuseMiddleware } from '@core-ai/langfuse';
|
|
37
|
+
|
|
38
|
+
const openai = createOpenAI({ apiKey: process.env.OPENAI_API_KEY });
|
|
39
|
+
|
|
40
|
+
const model = wrapChatModel({
|
|
41
|
+
model: openai.chatModel('gpt-5-mini'),
|
|
42
|
+
middleware: createLangfuseMiddleware({ recordContent: true }),
|
|
43
|
+
});
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
`recordContent` is disabled by default. Enable it only when you want prompts and outputs
|
|
47
|
+
attached to Langfuse observations.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { EmbeddingModelMiddleware, ImageModelMiddleware, ChatModelMiddleware } from '@core-ai/core-ai';
|
|
2
|
+
|
|
3
|
+
type LangfuseMiddlewareOptions = {
|
|
4
|
+
recordContent?: boolean;
|
|
5
|
+
};
|
|
6
|
+
declare function createLangfuseMiddleware(options?: LangfuseMiddlewareOptions): ChatModelMiddleware;
|
|
7
|
+
declare function createLangfuseEmbeddingMiddleware(options?: LangfuseMiddlewareOptions): EmbeddingModelMiddleware;
|
|
8
|
+
declare function createLangfuseImageMiddleware(options?: LangfuseMiddlewareOptions): ImageModelMiddleware;
|
|
9
|
+
|
|
10
|
+
export { type LangfuseMiddlewareOptions, createLangfuseEmbeddingMiddleware, createLangfuseImageMiddleware, createLangfuseMiddleware };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { startActiveObservation } from "@langfuse/tracing";
|
|
3
|
+
function getErrorMessage(error) {
|
|
4
|
+
if (error instanceof Error) {
|
|
5
|
+
return error.message;
|
|
6
|
+
}
|
|
7
|
+
return String(error);
|
|
8
|
+
}
|
|
9
|
+
function compactRecord(record) {
|
|
10
|
+
const entries = Object.entries(record).filter(
|
|
11
|
+
([, value]) => value !== void 0 && value !== null
|
|
12
|
+
);
|
|
13
|
+
if (entries.length === 0) {
|
|
14
|
+
return void 0;
|
|
15
|
+
}
|
|
16
|
+
return Object.fromEntries(entries);
|
|
17
|
+
}
|
|
18
|
+
function createChatObservationName(model) {
|
|
19
|
+
return `chat ${model.modelId}`;
|
|
20
|
+
}
|
|
21
|
+
function createEmbeddingObservationName(model) {
|
|
22
|
+
return `embeddings ${model.modelId}`;
|
|
23
|
+
}
|
|
24
|
+
function createImageObservationName(model) {
|
|
25
|
+
return `image_generation ${model.modelId}`;
|
|
26
|
+
}
|
|
27
|
+
function createObservationAttributes(config) {
|
|
28
|
+
return {
|
|
29
|
+
model: config.modelId,
|
|
30
|
+
...config.modelParameters ? { modelParameters: config.modelParameters } : {},
|
|
31
|
+
...config.metadata ? { metadata: config.metadata } : {},
|
|
32
|
+
...config.recordContent && config.input !== void 0 ? {
|
|
33
|
+
input: config.input
|
|
34
|
+
} : {}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function createModelParameters(options) {
|
|
38
|
+
return compactRecord({
|
|
39
|
+
temperature: options.temperature,
|
|
40
|
+
maxTokens: options.maxTokens,
|
|
41
|
+
topP: options.topP
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
function createEmbeddingModelParameters(options) {
|
|
45
|
+
return compactRecord({
|
|
46
|
+
dimensions: options.dimensions
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
function createImageModelParameters(options) {
|
|
50
|
+
return compactRecord({
|
|
51
|
+
n: options.n,
|
|
52
|
+
size: options.size
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
function createChatUsageDetails(usage) {
|
|
56
|
+
return {
|
|
57
|
+
inputTokens: usage.inputTokens,
|
|
58
|
+
outputTokens: usage.outputTokens,
|
|
59
|
+
totalTokens: usage.inputTokens + usage.outputTokens,
|
|
60
|
+
promptTokens: usage.inputTokens,
|
|
61
|
+
completionTokens: usage.outputTokens,
|
|
62
|
+
cacheReadInputTokens: usage.inputTokenDetails.cacheReadTokens,
|
|
63
|
+
cacheWriteInputTokens: usage.inputTokenDetails.cacheWriteTokens,
|
|
64
|
+
...usage.outputTokenDetails.reasoningTokens !== void 0 ? {
|
|
65
|
+
reasoningOutputTokens: usage.outputTokenDetails.reasoningTokens
|
|
66
|
+
} : {}
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function createEmbeddingUsageDetails(usage) {
|
|
70
|
+
if (!usage) {
|
|
71
|
+
return void 0;
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
inputTokens: usage.inputTokens,
|
|
75
|
+
totalTokens: usage.inputTokens,
|
|
76
|
+
promptTokens: usage.inputTokens
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function createChatOutput(result) {
|
|
80
|
+
return {
|
|
81
|
+
parts: result.parts,
|
|
82
|
+
toolCalls: result.toolCalls,
|
|
83
|
+
finishReason: result.finishReason,
|
|
84
|
+
...result.content !== null ? { content: result.content } : {},
|
|
85
|
+
...result.reasoning !== null ? { reasoning: result.reasoning } : {}
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function createImageOutput(result) {
|
|
89
|
+
return result.images.map((image) => ({
|
|
90
|
+
hasBase64: image.base64 !== void 0,
|
|
91
|
+
...image.url ? { url: image.url } : {},
|
|
92
|
+
...image.revisedPrompt ? { revisedPrompt: image.revisedPrompt } : {}
|
|
93
|
+
}));
|
|
94
|
+
}
|
|
95
|
+
function createErrorAttributes(error, recordContent) {
|
|
96
|
+
const message = getErrorMessage(error);
|
|
97
|
+
return {
|
|
98
|
+
level: "ERROR",
|
|
99
|
+
statusMessage: message,
|
|
100
|
+
...recordContent ? {
|
|
101
|
+
output: { error: message }
|
|
102
|
+
} : {}
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
function updateObservation(observation, attributes) {
|
|
106
|
+
if (!attributes) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
observation.update(attributes);
|
|
110
|
+
}
|
|
111
|
+
function runGeneration(config) {
|
|
112
|
+
const { name, initialAttributes, createSuccessAttributes, recordContent, execute } = config;
|
|
113
|
+
return startActiveObservation(
|
|
114
|
+
name,
|
|
115
|
+
async (observation) => {
|
|
116
|
+
updateObservation(observation, initialAttributes);
|
|
117
|
+
try {
|
|
118
|
+
const result = await execute();
|
|
119
|
+
updateObservation(observation, createSuccessAttributes(result));
|
|
120
|
+
return result;
|
|
121
|
+
} catch (error) {
|
|
122
|
+
updateObservation(observation, createErrorAttributes(error, recordContent));
|
|
123
|
+
throw error;
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
{ asType: "generation" }
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
function runEmbedding(config) {
|
|
130
|
+
const { name, initialAttributes, createSuccessAttributes, recordContent, execute } = config;
|
|
131
|
+
return startActiveObservation(
|
|
132
|
+
name,
|
|
133
|
+
async (observation) => {
|
|
134
|
+
updateObservation(observation, initialAttributes);
|
|
135
|
+
try {
|
|
136
|
+
const result = await execute();
|
|
137
|
+
updateObservation(observation, createSuccessAttributes(result));
|
|
138
|
+
return result;
|
|
139
|
+
} catch (error) {
|
|
140
|
+
updateObservation(observation, createErrorAttributes(error, recordContent));
|
|
141
|
+
throw error;
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
{ asType: "embedding" }
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
function runGenerationStream(config) {
|
|
148
|
+
const {
|
|
149
|
+
name,
|
|
150
|
+
initialAttributes,
|
|
151
|
+
createSuccessAttributes,
|
|
152
|
+
recordContent,
|
|
153
|
+
execute,
|
|
154
|
+
getResult
|
|
155
|
+
} = config;
|
|
156
|
+
return startActiveObservation(
|
|
157
|
+
name,
|
|
158
|
+
async (observation) => {
|
|
159
|
+
updateObservation(observation, initialAttributes);
|
|
160
|
+
try {
|
|
161
|
+
const stream = await execute();
|
|
162
|
+
void getResult(stream).then((result) => {
|
|
163
|
+
updateObservation(observation, createSuccessAttributes(result));
|
|
164
|
+
}).catch((error) => {
|
|
165
|
+
updateObservation(observation, createErrorAttributes(error, recordContent));
|
|
166
|
+
}).finally(() => {
|
|
167
|
+
observation.end();
|
|
168
|
+
});
|
|
169
|
+
return stream;
|
|
170
|
+
} catch (error) {
|
|
171
|
+
updateObservation(observation, createErrorAttributes(error, recordContent));
|
|
172
|
+
observation.end();
|
|
173
|
+
throw error;
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
{ asType: "generation", endOnExit: false }
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
function createLangfuseMiddleware(options = {}) {
|
|
180
|
+
const { recordContent = false } = options;
|
|
181
|
+
return {
|
|
182
|
+
generate: ({ execute, options: generateOptions, model }) => runGeneration({
|
|
183
|
+
name: createChatObservationName(model),
|
|
184
|
+
initialAttributes: createObservationAttributes({
|
|
185
|
+
modelId: model.modelId,
|
|
186
|
+
modelParameters: createModelParameters(generateOptions),
|
|
187
|
+
metadata: generateOptions.metadata,
|
|
188
|
+
input: generateOptions.messages,
|
|
189
|
+
recordContent
|
|
190
|
+
}),
|
|
191
|
+
createSuccessAttributes: (result) => ({
|
|
192
|
+
usageDetails: createChatUsageDetails(result.usage),
|
|
193
|
+
...recordContent ? {
|
|
194
|
+
output: createChatOutput(result)
|
|
195
|
+
} : {}
|
|
196
|
+
}),
|
|
197
|
+
recordContent,
|
|
198
|
+
execute
|
|
199
|
+
}),
|
|
200
|
+
stream: ({ execute, options: generateOptions, model }) => runGenerationStream({
|
|
201
|
+
name: createChatObservationName(model),
|
|
202
|
+
initialAttributes: createObservationAttributes({
|
|
203
|
+
modelId: model.modelId,
|
|
204
|
+
modelParameters: createModelParameters(generateOptions),
|
|
205
|
+
metadata: generateOptions.metadata,
|
|
206
|
+
input: generateOptions.messages,
|
|
207
|
+
recordContent
|
|
208
|
+
}),
|
|
209
|
+
createSuccessAttributes: (result) => ({
|
|
210
|
+
usageDetails: createChatUsageDetails(result.usage),
|
|
211
|
+
...recordContent ? {
|
|
212
|
+
output: createChatOutput(result)
|
|
213
|
+
} : {}
|
|
214
|
+
}),
|
|
215
|
+
recordContent,
|
|
216
|
+
execute,
|
|
217
|
+
getResult: (chatStream) => chatStream.result
|
|
218
|
+
}),
|
|
219
|
+
generateObject: (args) => {
|
|
220
|
+
const { execute, options: generateOptions, model } = args;
|
|
221
|
+
return runGeneration({
|
|
222
|
+
name: createChatObservationName(model),
|
|
223
|
+
initialAttributes: createObservationAttributes({
|
|
224
|
+
modelId: model.modelId,
|
|
225
|
+
modelParameters: createModelParameters(generateOptions),
|
|
226
|
+
metadata: generateOptions.metadata,
|
|
227
|
+
input: generateOptions.messages,
|
|
228
|
+
recordContent
|
|
229
|
+
}),
|
|
230
|
+
createSuccessAttributes: (result) => ({
|
|
231
|
+
usageDetails: createChatUsageDetails(result.usage),
|
|
232
|
+
...recordContent ? {
|
|
233
|
+
output: result.object
|
|
234
|
+
} : {}
|
|
235
|
+
}),
|
|
236
|
+
recordContent,
|
|
237
|
+
execute
|
|
238
|
+
});
|
|
239
|
+
},
|
|
240
|
+
streamObject: (args) => {
|
|
241
|
+
const { execute, options: generateOptions, model } = args;
|
|
242
|
+
return runGenerationStream({
|
|
243
|
+
name: createChatObservationName(model),
|
|
244
|
+
initialAttributes: createObservationAttributes({
|
|
245
|
+
modelId: model.modelId,
|
|
246
|
+
modelParameters: createModelParameters(generateOptions),
|
|
247
|
+
metadata: generateOptions.metadata,
|
|
248
|
+
input: generateOptions.messages,
|
|
249
|
+
recordContent
|
|
250
|
+
}),
|
|
251
|
+
createSuccessAttributes: (result) => ({
|
|
252
|
+
usageDetails: createChatUsageDetails(result.usage),
|
|
253
|
+
...recordContent ? {
|
|
254
|
+
output: result.object
|
|
255
|
+
} : {}
|
|
256
|
+
}),
|
|
257
|
+
recordContent,
|
|
258
|
+
execute,
|
|
259
|
+
getResult: (objectStream) => objectStream.result
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
function createLangfuseEmbeddingMiddleware(options = {}) {
|
|
265
|
+
const { recordContent = false } = options;
|
|
266
|
+
return {
|
|
267
|
+
embed: ({ execute, options: embedOptions, model }) => runEmbedding({
|
|
268
|
+
name: createEmbeddingObservationName(model),
|
|
269
|
+
initialAttributes: createObservationAttributes({
|
|
270
|
+
modelId: model.modelId,
|
|
271
|
+
modelParameters: createEmbeddingModelParameters(embedOptions),
|
|
272
|
+
metadata: embedOptions.metadata,
|
|
273
|
+
input: embedOptions.input,
|
|
274
|
+
recordContent
|
|
275
|
+
}),
|
|
276
|
+
createSuccessAttributes: (result) => compactRecord({
|
|
277
|
+
usageDetails: createEmbeddingUsageDetails(result.usage)
|
|
278
|
+
}),
|
|
279
|
+
recordContent,
|
|
280
|
+
execute
|
|
281
|
+
})
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
function createLangfuseImageMiddleware(options = {}) {
|
|
285
|
+
const { recordContent = false } = options;
|
|
286
|
+
return {
|
|
287
|
+
generate: ({ execute, options: imageOptions, model }) => runGeneration({
|
|
288
|
+
name: createImageObservationName(model),
|
|
289
|
+
initialAttributes: createObservationAttributes({
|
|
290
|
+
modelId: model.modelId,
|
|
291
|
+
modelParameters: createImageModelParameters(imageOptions),
|
|
292
|
+
metadata: imageOptions.metadata,
|
|
293
|
+
input: imageOptions.prompt,
|
|
294
|
+
recordContent
|
|
295
|
+
}),
|
|
296
|
+
createSuccessAttributes: (result) => recordContent ? {
|
|
297
|
+
output: createImageOutput(result)
|
|
298
|
+
} : void 0,
|
|
299
|
+
recordContent,
|
|
300
|
+
execute
|
|
301
|
+
})
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
export {
|
|
305
|
+
createLangfuseEmbeddingMiddleware,
|
|
306
|
+
createLangfuseImageMiddleware,
|
|
307
|
+
createLangfuseMiddleware
|
|
308
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@core-ai/langfuse",
|
|
3
|
+
"version": "0.10.0",
|
|
4
|
+
"description": "Langfuse middleware for @core-ai/core-ai",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Omnifact (https://omnifact.ai)",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/agdevhq/core-ai.git",
|
|
10
|
+
"directory": "packages/langfuse"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"llm",
|
|
14
|
+
"ai",
|
|
15
|
+
"langfuse",
|
|
16
|
+
"observability"
|
|
17
|
+
],
|
|
18
|
+
"type": "module",
|
|
19
|
+
"main": "./dist/index.js",
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"exports": {
|
|
22
|
+
".": {
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"import": "./dist/index.js"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"dist",
|
|
29
|
+
"README.md",
|
|
30
|
+
"LICENSE"
|
|
31
|
+
],
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public",
|
|
34
|
+
"provenance": true
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "tsup",
|
|
38
|
+
"lint": "eslint src/ --max-warnings 0",
|
|
39
|
+
"check-types": "tsc --noEmit",
|
|
40
|
+
"test": "vitest run",
|
|
41
|
+
"test:watch": "vitest"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@core-ai/core-ai": "^0.10.0"
|
|
45
|
+
},
|
|
46
|
+
"peerDependencies": {
|
|
47
|
+
"@langfuse/otel": "^5.1.0",
|
|
48
|
+
"@langfuse/tracing": "^5.1.0"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@core-ai/eslint-config": "*",
|
|
52
|
+
"@core-ai/typescript-config": "*",
|
|
53
|
+
"@langfuse/otel": "^5.1.0",
|
|
54
|
+
"@langfuse/tracing": "^5.1.0",
|
|
55
|
+
"@opentelemetry/sdk-node": "^0.214.0",
|
|
56
|
+
"typescript": "^5.7.3",
|
|
57
|
+
"vitest": "^3.2.4",
|
|
58
|
+
"zod": "^4.3.6"
|
|
59
|
+
}
|
|
60
|
+
}
|