@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 ADDED
@@ -0,0 +1,47 @@
1
+ # @core-ai/langfuse
2
+
3
+ [![npm](https://img.shields.io/npm/v/@core-ai/langfuse.svg)](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.
@@ -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
+ }