@langfuse/openai 4.0.0-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.
@@ -0,0 +1,217 @@
1
+ export const parseInputArgs = (args) => {
2
+ let params = {};
3
+ params = {
4
+ frequency_penalty: args.frequency_penalty,
5
+ logit_bias: args.logit_bias,
6
+ logprobs: args.logprobs,
7
+ max_tokens: args.max_tokens,
8
+ n: args.n,
9
+ presence_penalty: args.presence_penalty,
10
+ seed: args.seed,
11
+ stop: args.stop,
12
+ stream: args.stream,
13
+ temperature: args.temperature,
14
+ top_p: args.top_p,
15
+ user: args.user,
16
+ response_format: args.response_format,
17
+ top_logprobs: args.top_logprobs,
18
+ };
19
+ let input = args.input;
20
+ if (args &&
21
+ typeof args === "object" &&
22
+ !Array.isArray(args) &&
23
+ "messages" in args) {
24
+ input = {};
25
+ input.messages = args.messages;
26
+ if ("function_call" in args) {
27
+ input.function_call = args.function_call;
28
+ }
29
+ if ("functions" in args) {
30
+ input.functions = args.functions;
31
+ }
32
+ if ("tools" in args) {
33
+ input.tools = args.tools;
34
+ }
35
+ if ("tool_choice" in args) {
36
+ input.tool_choice = args.tool_choice;
37
+ }
38
+ }
39
+ else if (!input) {
40
+ input = args.prompt;
41
+ }
42
+ return {
43
+ model: args.model,
44
+ input: input,
45
+ modelParameters: params,
46
+ };
47
+ };
48
+ export const parseCompletionOutput = (res) => {
49
+ var _a;
50
+ if (res instanceof Object &&
51
+ "output_text" in res &&
52
+ res["output_text"] !== "") {
53
+ return res["output_text"];
54
+ }
55
+ if (typeof res === "object" &&
56
+ res &&
57
+ "output" in res &&
58
+ Array.isArray(res["output"])) {
59
+ const output = res["output"];
60
+ if (output.length > 1) {
61
+ return output;
62
+ }
63
+ if (output.length === 1) {
64
+ return output[0];
65
+ }
66
+ return null;
67
+ }
68
+ if (!(res instanceof Object && "choices" in res && Array.isArray(res.choices))) {
69
+ return "";
70
+ }
71
+ return "message" in res.choices[0]
72
+ ? res.choices[0].message
73
+ : ((_a = res.choices[0].text) !== null && _a !== void 0 ? _a : "");
74
+ };
75
+ export const parseUsageDetails = (completionUsage) => {
76
+ if ("prompt_tokens" in completionUsage) {
77
+ const { prompt_tokens, completion_tokens, total_tokens, completion_tokens_details, prompt_tokens_details, } = completionUsage;
78
+ return {
79
+ input: prompt_tokens,
80
+ output: completion_tokens,
81
+ total: total_tokens,
82
+ ...Object.fromEntries(Object.entries(prompt_tokens_details !== null && prompt_tokens_details !== void 0 ? prompt_tokens_details : {}).map(([key, value]) => [
83
+ `input_${key}`,
84
+ value,
85
+ ])),
86
+ ...Object.fromEntries(Object.entries(completion_tokens_details !== null && completion_tokens_details !== void 0 ? completion_tokens_details : {}).map(([key, value]) => [
87
+ `output_${key}`,
88
+ value,
89
+ ])),
90
+ };
91
+ }
92
+ else if ("input_tokens" in completionUsage) {
93
+ const { input_tokens, output_tokens, total_tokens, input_tokens_details, output_tokens_details, } = completionUsage;
94
+ return {
95
+ input: input_tokens,
96
+ output: output_tokens,
97
+ total: total_tokens,
98
+ ...Object.fromEntries(Object.entries(input_tokens_details !== null && input_tokens_details !== void 0 ? input_tokens_details : {}).map(([key, value]) => [
99
+ `input_${key}`,
100
+ value,
101
+ ])),
102
+ ...Object.fromEntries(Object.entries(output_tokens_details !== null && output_tokens_details !== void 0 ? output_tokens_details : {}).map(([key, value]) => [
103
+ `output_${key}`,
104
+ value,
105
+ ])),
106
+ };
107
+ }
108
+ };
109
+ export const parseUsageDetailsFromResponse = (res) => {
110
+ if (hasCompletionUsage(res)) {
111
+ return parseUsageDetails(res.usage);
112
+ }
113
+ };
114
+ export const parseChunk = (rawChunk) => {
115
+ var _a, _b;
116
+ let isToolCall = false;
117
+ const _chunk = rawChunk;
118
+ const chunkData = (_a = _chunk === null || _chunk === void 0 ? void 0 : _chunk.choices) === null || _a === void 0 ? void 0 : _a[0];
119
+ try {
120
+ if ("delta" in chunkData &&
121
+ "tool_calls" in chunkData.delta &&
122
+ Array.isArray(chunkData.delta.tool_calls)) {
123
+ isToolCall = true;
124
+ return { isToolCall, data: chunkData.delta.tool_calls[0] };
125
+ }
126
+ if ("delta" in chunkData) {
127
+ return { isToolCall, data: ((_b = chunkData.delta) === null || _b === void 0 ? void 0 : _b.content) || "" };
128
+ }
129
+ if ("text" in chunkData) {
130
+ return { isToolCall, data: chunkData.text || "" };
131
+ }
132
+ }
133
+ catch { }
134
+ return { isToolCall: false, data: "" };
135
+ };
136
+ // Type guard to check if an unknown object is a UsageResponse
137
+ function hasCompletionUsage(obj) {
138
+ return (obj instanceof Object &&
139
+ "usage" in obj &&
140
+ obj.usage instanceof Object &&
141
+ // Completion API Usage format
142
+ ((typeof obj.usage.prompt_tokens === "number" &&
143
+ typeof obj.usage.completion_tokens === "number" &&
144
+ typeof obj.usage.total_tokens === "number") ||
145
+ // Response API Usage format
146
+ (typeof obj.usage.input_tokens === "number" &&
147
+ typeof obj.usage.output_tokens === "number" &&
148
+ typeof obj.usage.total_tokens === "number")));
149
+ }
150
+ export const getToolCallOutput = (toolCallChunks) => {
151
+ var _a, _b;
152
+ let name = "";
153
+ let toolArguments = "";
154
+ for (const toolCall of toolCallChunks) {
155
+ name = ((_a = toolCall.function) === null || _a === void 0 ? void 0 : _a.name) || name;
156
+ toolArguments += ((_b = toolCall.function) === null || _b === void 0 ? void 0 : _b.arguments) || "";
157
+ }
158
+ return {
159
+ tool_calls: [
160
+ {
161
+ function: {
162
+ name,
163
+ arguments: toolArguments,
164
+ },
165
+ },
166
+ ],
167
+ };
168
+ };
169
+ export const parseModelDataFromResponse = (res) => {
170
+ if (typeof res !== "object" || res === null) {
171
+ return {
172
+ model: undefined,
173
+ modelParameters: undefined,
174
+ metadata: undefined,
175
+ };
176
+ }
177
+ const model = "model" in res ? res["model"] : undefined;
178
+ const modelParameters = {};
179
+ const modelParamKeys = [
180
+ "max_output_tokens",
181
+ "parallel_tool_calls",
182
+ "store",
183
+ "temperature",
184
+ "tool_choice",
185
+ "top_p",
186
+ "truncation",
187
+ "user",
188
+ ];
189
+ const metadata = {};
190
+ const metadataKeys = [
191
+ "reasoning",
192
+ "incomplete_details",
193
+ "instructions",
194
+ "previous_response_id",
195
+ "tools",
196
+ "metadata",
197
+ "status",
198
+ "error",
199
+ ];
200
+ for (const key of modelParamKeys) {
201
+ const val = key in res ? res[key] : null;
202
+ if (val !== null && val !== undefined) {
203
+ modelParameters[key] = val;
204
+ }
205
+ }
206
+ for (const key of metadataKeys) {
207
+ const val = key in res ? res[key] : null;
208
+ if (val) {
209
+ metadata[key] = val;
210
+ }
211
+ }
212
+ return {
213
+ model,
214
+ modelParameters: Object.keys(modelParameters).length > 0 ? modelParameters : undefined,
215
+ metadata: Object.keys(metadata).length > 0 ? metadata : undefined,
216
+ };
217
+ };
@@ -0,0 +1,7 @@
1
+ import type { LangfuseConfig } from "./types.js";
2
+ type GenericMethod = (...args: unknown[]) => unknown;
3
+ export declare const withTracing: <T extends GenericMethod>(tracedMethod: T, config?: LangfuseConfig & Required<{
4
+ generationName: string;
5
+ }>) => ((...args: Parameters<T>) => Promise<ReturnType<T>>);
6
+ export {};
7
+ //# sourceMappingURL=traceMethod.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"traceMethod.d.ts","sourceRoot":"","sources":["../src/traceMethod.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAGjD,KAAK,aAAa,GAAG,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC;AAErD,eAAO,MAAM,WAAW,GAAI,CAAC,SAAS,aAAa,EACjD,cAAc,CAAC,EACf,SAAS,cAAc,GAAG,QAAQ,CAAC;IAAE,cAAc,EAAE,MAAM,CAAA;CAAE,CAAC,KAC7D,CAAC,CAAC,GAAG,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAErD,CAAC"}
@@ -0,0 +1,138 @@
1
+ import { startGeneration } from "@langfuse/tracing";
2
+ import { getToolCallOutput, parseChunk, parseCompletionOutput, parseInputArgs, parseUsageDetails, parseModelDataFromResponse, parseUsageDetailsFromResponse, } from "./parseOpenAI.js";
3
+ import { isAsyncIterable } from "./utils.js";
4
+ export const withTracing = (tracedMethod, config) => {
5
+ return (...args) => wrapMethod(tracedMethod, config, ...args);
6
+ };
7
+ const wrapMethod = (tracedMethod, config, ...args) => {
8
+ var _a, _b;
9
+ const { model, input, modelParameters } = parseInputArgs((_a = args[0]) !== null && _a !== void 0 ? _a : {});
10
+ const finalModelParams = { ...modelParameters, response_format: null };
11
+ const finalMetadata = {
12
+ ...config === null || config === void 0 ? void 0 : config.generationMetadata,
13
+ response_format: "response_format" in modelParameters
14
+ ? modelParameters.response_format
15
+ : undefined,
16
+ };
17
+ const generation = startGeneration((_b = config === null || config === void 0 ? void 0 : config.generationName) !== null && _b !== void 0 ? _b : "OpenAI-completion", {
18
+ model,
19
+ input,
20
+ modelParameters: finalModelParams,
21
+ prompt: config === null || config === void 0 ? void 0 : config.langfusePrompt,
22
+ metadata: finalMetadata,
23
+ }, { parentSpanContext: config === null || config === void 0 ? void 0 : config.parentSpanContext }).updateTrace({
24
+ userId: config === null || config === void 0 ? void 0 : config.userId,
25
+ sessionId: config === null || config === void 0 ? void 0 : config.sessionId,
26
+ tags: config === null || config === void 0 ? void 0 : config.tags,
27
+ name: config === null || config === void 0 ? void 0 : config.traceName,
28
+ });
29
+ try {
30
+ const res = tracedMethod(...args);
31
+ // Handle stream responses
32
+ if (isAsyncIterable(res)) {
33
+ return wrapAsyncIterable(res, generation);
34
+ }
35
+ if (res instanceof Promise) {
36
+ const wrappedPromise = res
37
+ .then((result) => {
38
+ if (isAsyncIterable(result)) {
39
+ return wrapAsyncIterable(result, generation);
40
+ }
41
+ const output = parseCompletionOutput(result);
42
+ const usageDetails = parseUsageDetailsFromResponse(result);
43
+ const { model: modelFromResponse, modelParameters: modelParametersFromResponse, metadata: metadataFromResponse, } = parseModelDataFromResponse(result);
44
+ generation
45
+ .update({
46
+ output,
47
+ usageDetails,
48
+ model: modelFromResponse,
49
+ modelParameters: modelParametersFromResponse,
50
+ metadata: metadataFromResponse,
51
+ })
52
+ .end();
53
+ return result;
54
+ })
55
+ .catch((err) => {
56
+ generation
57
+ .update({
58
+ statusMessage: String(err),
59
+ level: "ERROR",
60
+ costDetails: {
61
+ input: 0,
62
+ output: 0,
63
+ total: 0,
64
+ },
65
+ })
66
+ .end();
67
+ throw err;
68
+ });
69
+ return wrappedPromise;
70
+ }
71
+ return res;
72
+ }
73
+ catch (error) {
74
+ generation
75
+ .update({
76
+ statusMessage: String(error),
77
+ level: "ERROR",
78
+ costDetails: {
79
+ input: 0,
80
+ output: 0,
81
+ total: 0,
82
+ },
83
+ })
84
+ .end();
85
+ throw error;
86
+ }
87
+ };
88
+ function wrapAsyncIterable(iterable, generation) {
89
+ async function* tracedOutputGenerator() {
90
+ const response = iterable;
91
+ const textChunks = [];
92
+ const toolCallChunks = [];
93
+ let usage = null;
94
+ let completionStartTime = undefined;
95
+ let usageDetails = undefined;
96
+ let output = null;
97
+ for await (const rawChunk of response) {
98
+ completionStartTime = completionStartTime !== null && completionStartTime !== void 0 ? completionStartTime : new Date();
99
+ // Handle Response API chunks
100
+ if (typeof rawChunk === "object" && rawChunk && "response" in rawChunk) {
101
+ const result = rawChunk["response"];
102
+ output = parseCompletionOutput(result);
103
+ usageDetails = parseUsageDetailsFromResponse(result);
104
+ const { model: modelFromResponse, modelParameters: modelParametersFromResponse, metadata: metadataFromResponse, } = parseModelDataFromResponse(result);
105
+ generation.update({
106
+ model: modelFromResponse,
107
+ modelParameters: modelParametersFromResponse,
108
+ metadata: metadataFromResponse,
109
+ });
110
+ }
111
+ if (typeof rawChunk === "object" &&
112
+ rawChunk != null &&
113
+ "usage" in rawChunk) {
114
+ usage = rawChunk.usage;
115
+ }
116
+ const processedChunk = parseChunk(rawChunk);
117
+ if (!processedChunk.isToolCall) {
118
+ textChunks.push(processedChunk.data);
119
+ }
120
+ else {
121
+ toolCallChunks.push(processedChunk.data);
122
+ }
123
+ yield rawChunk;
124
+ }
125
+ output =
126
+ output !== null && output !== void 0 ? output : (toolCallChunks.length > 0
127
+ ? getToolCallOutput(toolCallChunks)
128
+ : textChunks.join(""));
129
+ generation
130
+ .update({
131
+ output,
132
+ completionStartTime,
133
+ usageDetails: usageDetails !== null && usageDetails !== void 0 ? usageDetails : (usage ? parseUsageDetails(usage) : undefined),
134
+ })
135
+ .end();
136
+ }
137
+ return tracedOutputGenerator();
138
+ }
@@ -0,0 +1,16 @@
1
+ import { SpanContext } from "@opentelemetry/api";
2
+ export type LangfuseConfig = {
3
+ parentSpanContext?: SpanContext;
4
+ traceName?: string;
5
+ sessionId?: string;
6
+ userId?: string;
7
+ tags?: string[];
8
+ generationName?: string;
9
+ generationMetadata?: Record<string, unknown>;
10
+ langfusePrompt?: {
11
+ name: string;
12
+ version: number;
13
+ isFallback: boolean;
14
+ };
15
+ };
16
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEjD,MAAM,MAAM,cAAc,GAAG;IAC3B,iBAAiB,CAAC,EAAE,WAAW,CAAC;IAChC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAEhB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC7C,cAAc,CAAC,EAAE;QACf,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,UAAU,EAAE,OAAO,CAAC;KACrB,CAAC;CACH,CAAC"}
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ export declare const isAsyncIterable: (x: unknown) => x is AsyncIterable<unknown>;
2
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,eAAe,GAAI,GAAG,OAAO,KAAG,CAAC,IAAI,aAAa,CAAC,OAAO,CAGf,CAAC"}
package/dist/utils.js ADDED
@@ -0,0 +1,3 @@
1
+ export const isAsyncIterable = (x) => x != null &&
2
+ typeof x === "object" &&
3
+ typeof x[Symbol.asyncIterator] === "function";
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@langfuse/openai",
3
+ "version": "4.0.0-alpha.0",
4
+ "description": "Langfuse integration for OpenAI SDK",
5
+ "type": "module",
6
+ "sideEffects": false,
7
+ "main": "./dist/index.cjs",
8
+ "module": "./dist/index.mjs",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.mjs",
14
+ "require": "./dist/index.cjs"
15
+ }
16
+ },
17
+ "author": "Langfuse",
18
+ "license": "MIT",
19
+ "files": [
20
+ "dist"
21
+ ],
22
+ "dependencies": {
23
+ "@langfuse/core": "^4.0.0-alpha.0",
24
+ "@langfuse/tracing": "^4.0.0-alpha.0"
25
+ },
26
+ "devDependencies": {
27
+ "openai": "^5.0.0"
28
+ },
29
+ "scripts": {
30
+ "build": "tsup",
31
+ "test": "vitest run",
32
+ "test:watch": "vitest",
33
+ "format": "prettier --write \"src/**/*.ts\"",
34
+ "format:check": "prettier --check \"src/**/*.ts\"",
35
+ "clean": "rm -rf dist"
36
+ }
37
+ }