@langfuse/client 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.
package/dist/index.cjs ADDED
@@ -0,0 +1,908 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ ChatMessageType: () => ChatMessageType,
34
+ ChatPromptClient: () => ChatPromptClient,
35
+ DatasetManager: () => DatasetManager,
36
+ LangfuseClient: () => LangfuseClient,
37
+ MediaManager: () => MediaManager,
38
+ PromptManager: () => PromptManager,
39
+ ScoreManager: () => ScoreManager,
40
+ TextPromptClient: () => TextPromptClient
41
+ });
42
+ module.exports = __toCommonJS(index_exports);
43
+
44
+ // src/LangfuseClient.ts
45
+ var import_core5 = require("@langfuse/core");
46
+
47
+ // src/dataset/index.ts
48
+ var DatasetManager = class {
49
+ constructor(params) {
50
+ this.apiClient = params.apiClient;
51
+ }
52
+ async get(name, options) {
53
+ var _a;
54
+ const dataset = await this.apiClient.datasets.get(name);
55
+ const items = [];
56
+ let page = 1;
57
+ while (true) {
58
+ const itemsResponse = await this.apiClient.datasetItems.list({
59
+ datasetName: name,
60
+ limit: (_a = options == null ? void 0 : options.fetchItemsPageSize) != null ? _a : 50,
61
+ page
62
+ });
63
+ items.push(...itemsResponse.data);
64
+ if (itemsResponse.meta.totalPages <= page) {
65
+ break;
66
+ }
67
+ page++;
68
+ }
69
+ const returnDataset = {
70
+ ...dataset,
71
+ items: items.map((item) => ({
72
+ ...item,
73
+ link: this.createDatasetItemLinkFunction(item)
74
+ }))
75
+ };
76
+ return returnDataset;
77
+ }
78
+ createDatasetItemLinkFunction(item) {
79
+ const linkFunction = async (obj, runName, runArgs) => {
80
+ return await this.apiClient.datasetRunItems.create({
81
+ runName,
82
+ datasetItemId: item.id,
83
+ traceId: obj.otelSpan.spanContext().traceId,
84
+ runDescription: runArgs == null ? void 0 : runArgs.description,
85
+ metadata: runArgs == null ? void 0 : runArgs.metadata
86
+ });
87
+ };
88
+ return linkFunction;
89
+ }
90
+ };
91
+
92
+ // src/media/index.ts
93
+ var import_core = require("@langfuse/core");
94
+ var MediaManager = class _MediaManager {
95
+ constructor(params) {
96
+ this.apiClient = params.apiClient;
97
+ }
98
+ /**
99
+ * Replaces the media reference strings in an object with base64 data URIs for the media content.
100
+ *
101
+ * This method recursively traverses an object (up to a maximum depth of 10) looking for media reference strings
102
+ * in the format "@@@langfuseMedia:...@@@". When found, it fetches the actual media content using the provided
103
+ * Langfuse client and replaces the reference string with a base64 data URI.
104
+ *
105
+ * If fetching media content fails for a reference string, a warning is logged and the reference string is left unchanged.
106
+ *
107
+ * @param params - Configuration object
108
+ * @param params.obj - The object to process. Can be a primitive value, array, or nested object
109
+ * @param params.resolveWith - The representation of the media content to replace the media reference string with. Currently only "base64DataUri" is supported.
110
+ * @param params.maxDepth - Optional. Default is 10. The maximum depth to traverse the object.
111
+ *
112
+ * @returns A deep copy of the input object with all media references replaced with base64 data URIs where possible
113
+ *
114
+ * @example
115
+ * ```typescript
116
+ * const obj = {
117
+ * image: "@@@langfuseMedia:type=image/jpeg|id=123|source=bytes@@@",
118
+ * nested: {
119
+ * pdf: "@@@langfuseMedia:type=application/pdf|id=456|source=bytes@@@"
120
+ * }
121
+ * };
122
+ *
123
+ * const result = await LangfuseMedia.resolveMediaReferences({
124
+ * obj,
125
+ * langfuseClient
126
+ * });
127
+ *
128
+ * // Result:
129
+ * // {
130
+ * // image: "data:image/jpeg;base64,/9j/4AAQSkZJRg...",
131
+ * // nested: {
132
+ * // pdf: "data:application/pdf;base64,JVBERi0xLjcK..."
133
+ * // }
134
+ * // }
135
+ * ```
136
+ */
137
+ async resolveReferences(params) {
138
+ const { obj, maxDepth = 10 } = params;
139
+ const traverse = async (obj2, depth) => {
140
+ if (depth > maxDepth) {
141
+ return obj2;
142
+ }
143
+ if (typeof obj2 === "string") {
144
+ const regex = /@@@langfuseMedia:.+?@@@/g;
145
+ const referenceStringMatches = obj2.match(regex);
146
+ if (!referenceStringMatches) {
147
+ return obj2;
148
+ }
149
+ let result = obj2;
150
+ const referenceStringToMediaContentMap = /* @__PURE__ */ new Map();
151
+ await Promise.all(
152
+ referenceStringMatches.map(async (referenceString) => {
153
+ try {
154
+ const parsedMediaReference = _MediaManager.parseReferenceString(referenceString);
155
+ const mediaData = await this.apiClient.media.get(
156
+ parsedMediaReference.mediaId
157
+ );
158
+ const mediaContent = await fetch(mediaData.url, {
159
+ method: "GET",
160
+ headers: {}
161
+ });
162
+ if (mediaContent.status !== 200) {
163
+ throw new Error("Failed to fetch media content");
164
+ }
165
+ const uint8Content = new Uint8Array(
166
+ await mediaContent.arrayBuffer()
167
+ );
168
+ const base64MediaContent = (0, import_core.uint8ArrayToBase64)(uint8Content);
169
+ const base64DataUri = `data:${mediaData.contentType};base64,${base64MediaContent}`;
170
+ referenceStringToMediaContentMap.set(
171
+ referenceString,
172
+ base64DataUri
173
+ );
174
+ } catch (error) {
175
+ (0, import_core.getGlobalLogger)().warn(
176
+ "Error fetching media content for reference string",
177
+ referenceString,
178
+ error
179
+ );
180
+ }
181
+ })
182
+ );
183
+ for (const [
184
+ referenceString,
185
+ base64MediaContent
186
+ ] of referenceStringToMediaContentMap.entries()) {
187
+ result = result.replaceAll(referenceString, base64MediaContent);
188
+ }
189
+ return result;
190
+ }
191
+ if (Array.isArray(obj2)) {
192
+ return Promise.all(
193
+ obj2.map(async (item) => await traverse(item, depth + 1))
194
+ );
195
+ }
196
+ if (typeof obj2 === "object" && obj2 !== null) {
197
+ return Object.fromEntries(
198
+ await Promise.all(
199
+ Object.entries(obj2).map(async ([key, value]) => [
200
+ key,
201
+ await traverse(value, depth + 1)
202
+ ])
203
+ )
204
+ );
205
+ }
206
+ return obj2;
207
+ };
208
+ return traverse(obj, 0);
209
+ }
210
+ /**
211
+ * Parses a media reference string into a ParsedMediaReference.
212
+ *
213
+ * Example reference string:
214
+ * "@@@langfuseMedia:type=image/jpeg|id=some-uuid|source=base64DataUri@@@"
215
+ *
216
+ * @param referenceString - The reference string to parse.
217
+ * @returns An object with the mediaId, source, and contentType.
218
+ *
219
+ * @throws Error if the reference string is invalid or missing required fields.
220
+ */
221
+ static parseReferenceString(referenceString) {
222
+ const prefix = "@@@langfuseMedia:";
223
+ const suffix = "@@@";
224
+ if (!referenceString.startsWith(prefix)) {
225
+ throw new Error(
226
+ "Reference string does not start with '@@@langfuseMedia:type='"
227
+ );
228
+ }
229
+ if (!referenceString.endsWith(suffix)) {
230
+ throw new Error("Reference string does not end with '@@@'");
231
+ }
232
+ const content = referenceString.slice(prefix.length, -suffix.length);
233
+ const pairs = content.split("|");
234
+ const parsedData = {};
235
+ for (const pair of pairs) {
236
+ const [key, value] = pair.split("=", 2);
237
+ parsedData[key] = value;
238
+ }
239
+ if (!("type" in parsedData && "id" in parsedData && "source" in parsedData)) {
240
+ throw new Error("Missing required fields in reference string");
241
+ }
242
+ return {
243
+ mediaId: parsedData["id"],
244
+ source: parsedData["source"],
245
+ contentType: parsedData["type"]
246
+ };
247
+ }
248
+ };
249
+
250
+ // src/prompt/promptManager.ts
251
+ var import_core3 = require("@langfuse/core");
252
+
253
+ // src/prompt/promptCache.ts
254
+ var import_core2 = require("@langfuse/core");
255
+ var DEFAULT_PROMPT_CACHE_TTL_SECONDS = 60;
256
+ var LangfusePromptCacheItem = class {
257
+ constructor(value, ttlSeconds) {
258
+ this.value = value;
259
+ this._expiry = Date.now() + ttlSeconds * 1e3;
260
+ }
261
+ get isExpired() {
262
+ return Date.now() > this._expiry;
263
+ }
264
+ };
265
+ var LangfusePromptCache = class {
266
+ constructor() {
267
+ this._cache = /* @__PURE__ */ new Map();
268
+ this._defaultTtlSeconds = DEFAULT_PROMPT_CACHE_TTL_SECONDS;
269
+ this._refreshingKeys = /* @__PURE__ */ new Map();
270
+ }
271
+ getIncludingExpired(key) {
272
+ var _a;
273
+ return (_a = this._cache.get(key)) != null ? _a : null;
274
+ }
275
+ createKey(params) {
276
+ const { name, version, label } = params;
277
+ const parts = [name];
278
+ if (version !== void 0) {
279
+ parts.push("version:" + version.toString());
280
+ } else if (label !== void 0) {
281
+ parts.push("label:" + label);
282
+ } else {
283
+ parts.push("label:production");
284
+ }
285
+ return parts.join("-");
286
+ }
287
+ set(key, value, ttlSeconds) {
288
+ const effectiveTtlSeconds = ttlSeconds != null ? ttlSeconds : this._defaultTtlSeconds;
289
+ this._cache.set(
290
+ key,
291
+ new LangfusePromptCacheItem(value, effectiveTtlSeconds)
292
+ );
293
+ }
294
+ addRefreshingPromise(key, promise) {
295
+ this._refreshingKeys.set(key, promise);
296
+ promise.then(() => {
297
+ this._refreshingKeys.delete(key);
298
+ }).catch(() => {
299
+ this._refreshingKeys.delete(key);
300
+ });
301
+ }
302
+ isRefreshing(key) {
303
+ return this._refreshingKeys.has(key);
304
+ }
305
+ invalidate(promptName) {
306
+ (0, import_core2.getGlobalLogger)().debug(
307
+ "Invalidating cache keys for",
308
+ promptName,
309
+ this._cache.keys()
310
+ );
311
+ for (const key of this._cache.keys()) {
312
+ if (key.startsWith(promptName)) {
313
+ this._cache.delete(key);
314
+ }
315
+ }
316
+ }
317
+ };
318
+
319
+ // src/prompt/promptClients.ts
320
+ var import_mustache = __toESM(require("mustache"), 1);
321
+
322
+ // src/prompt/types.ts
323
+ var ChatMessageType = /* @__PURE__ */ ((ChatMessageType2) => {
324
+ ChatMessageType2["ChatMessage"] = "chatmessage";
325
+ ChatMessageType2["Placeholder"] = "placeholder";
326
+ return ChatMessageType2;
327
+ })(ChatMessageType || {});
328
+
329
+ // src/prompt/promptClients.ts
330
+ import_mustache.default.escape = function(text) {
331
+ return text;
332
+ };
333
+ var BasePromptClient = class {
334
+ constructor(prompt, isFallback = false, type) {
335
+ this.name = prompt.name;
336
+ this.version = prompt.version;
337
+ this.config = prompt.config;
338
+ this.labels = prompt.labels;
339
+ this.tags = prompt.tags;
340
+ this.isFallback = isFallback;
341
+ this.type = type;
342
+ this.commitMessage = prompt.commitMessage;
343
+ }
344
+ _transformToLangchainVariables(content) {
345
+ const jsonEscapedContent = this.escapeJsonForLangchain(content);
346
+ return jsonEscapedContent.replace(/\{\{(\w+)\}\}/g, "{$1}");
347
+ }
348
+ /**
349
+ * Escapes every curly brace that is part of a JSON object by doubling it.
350
+ *
351
+ * A curly brace is considered “JSON-related” when, after skipping any immediate
352
+ * whitespace, the next non-whitespace character is a single (') or double (") quote.
353
+ *
354
+ * Braces that are already doubled (e.g. `{{variable}}` placeholders) are left untouched.
355
+ *
356
+ * @param text - Input string that may contain JSON snippets.
357
+ * @returns The string with JSON-related braces doubled.
358
+ */
359
+ escapeJsonForLangchain(text) {
360
+ var _a;
361
+ const out = [];
362
+ const stack = [];
363
+ let i = 0;
364
+ const n = text.length;
365
+ while (i < n) {
366
+ const ch = text[i];
367
+ if (ch === "{") {
368
+ if (i + 1 < n && text[i + 1] === "{") {
369
+ out.push("{{");
370
+ i += 2;
371
+ continue;
372
+ }
373
+ let j = i + 1;
374
+ while (j < n && /\s/.test(text[j])) {
375
+ j++;
376
+ }
377
+ const isJson = j < n && (text[j] === "'" || text[j] === '"');
378
+ out.push(isJson ? "{{" : "{");
379
+ stack.push(isJson);
380
+ i += 1;
381
+ continue;
382
+ }
383
+ if (ch === "}") {
384
+ if (i + 1 < n && text[i + 1] === "}") {
385
+ out.push("}}");
386
+ i += 2;
387
+ continue;
388
+ }
389
+ const isJson = (_a = stack.pop()) != null ? _a : false;
390
+ out.push(isJson ? "}}" : "}");
391
+ i += 1;
392
+ continue;
393
+ }
394
+ out.push(ch);
395
+ i += 1;
396
+ }
397
+ return out.join("");
398
+ }
399
+ };
400
+ var TextPromptClient = class extends BasePromptClient {
401
+ constructor(prompt, isFallback = false) {
402
+ super(prompt, isFallback, "text");
403
+ this.promptResponse = prompt;
404
+ this.prompt = prompt.prompt;
405
+ }
406
+ compile(variables, _placeholders) {
407
+ return import_mustache.default.render(this.promptResponse.prompt, variables != null ? variables : {});
408
+ }
409
+ getLangchainPrompt(_options) {
410
+ return this._transformToLangchainVariables(this.prompt);
411
+ }
412
+ toJSON() {
413
+ return JSON.stringify({
414
+ name: this.name,
415
+ prompt: this.prompt,
416
+ version: this.version,
417
+ isFallback: this.isFallback,
418
+ tags: this.tags,
419
+ labels: this.labels,
420
+ type: this.type,
421
+ config: this.config
422
+ });
423
+ }
424
+ };
425
+ var ChatPromptClient = class _ChatPromptClient extends BasePromptClient {
426
+ constructor(prompt, isFallback = false) {
427
+ const normalizedPrompt = _ChatPromptClient.normalizePrompt(prompt.prompt);
428
+ const typedPrompt = {
429
+ ...prompt,
430
+ prompt: normalizedPrompt
431
+ };
432
+ super(typedPrompt, isFallback, "chat");
433
+ this.promptResponse = typedPrompt;
434
+ this.prompt = normalizedPrompt;
435
+ }
436
+ static normalizePrompt(prompt) {
437
+ return prompt.map((item) => {
438
+ if ("type" in item) {
439
+ return item;
440
+ } else {
441
+ return {
442
+ type: "chatmessage" /* ChatMessage */,
443
+ ...item
444
+ };
445
+ }
446
+ });
447
+ }
448
+ compile(variables, placeholders) {
449
+ const messagesWithPlaceholdersReplaced = [];
450
+ const placeholderValues = placeholders != null ? placeholders : {};
451
+ for (const item of this.prompt) {
452
+ if ("type" in item && item.type === "placeholder" /* Placeholder */) {
453
+ const placeholderValue = placeholderValues[item.name];
454
+ if (Array.isArray(placeholderValue) && placeholderValue.length > 0 && placeholderValue.every(
455
+ (msg) => typeof msg === "object" && "role" in msg && "content" in msg
456
+ )) {
457
+ messagesWithPlaceholdersReplaced.push(
458
+ ...placeholderValue
459
+ );
460
+ } else if (Array.isArray(placeholderValue) && placeholderValue.length === 0) {
461
+ } else if (placeholderValue !== void 0) {
462
+ messagesWithPlaceholdersReplaced.push(
463
+ JSON.stringify(placeholderValue)
464
+ );
465
+ } else {
466
+ messagesWithPlaceholdersReplaced.push(
467
+ item
468
+ );
469
+ }
470
+ } else if ("role" in item && "content" in item && item.type === "chatmessage" /* ChatMessage */) {
471
+ messagesWithPlaceholdersReplaced.push({
472
+ role: item.role,
473
+ content: item.content
474
+ });
475
+ }
476
+ }
477
+ return messagesWithPlaceholdersReplaced.map((item) => {
478
+ if (typeof item === "object" && item !== null && "role" in item && "content" in item) {
479
+ return {
480
+ ...item,
481
+ content: import_mustache.default.render(item.content, variables != null ? variables : {})
482
+ };
483
+ } else {
484
+ return item;
485
+ }
486
+ });
487
+ }
488
+ getLangchainPrompt(options) {
489
+ var _a;
490
+ const messagesWithPlaceholdersReplaced = [];
491
+ const placeholderValues = (_a = options == null ? void 0 : options.placeholders) != null ? _a : {};
492
+ for (const item of this.prompt) {
493
+ if ("type" in item && item.type === "placeholder" /* Placeholder */) {
494
+ const placeholderValue = placeholderValues[item.name];
495
+ if (Array.isArray(placeholderValue) && placeholderValue.length > 0 && placeholderValue.every(
496
+ (msg) => typeof msg === "object" && "role" in msg && "content" in msg
497
+ )) {
498
+ messagesWithPlaceholdersReplaced.push(
499
+ ...placeholderValue.map((msg) => {
500
+ return {
501
+ role: msg.role,
502
+ content: this._transformToLangchainVariables(msg.content)
503
+ };
504
+ })
505
+ );
506
+ } else if (Array.isArray(placeholderValue) && placeholderValue.length === 0) {
507
+ } else if (placeholderValue !== void 0) {
508
+ messagesWithPlaceholdersReplaced.push(
509
+ JSON.stringify(placeholderValue)
510
+ );
511
+ } else {
512
+ messagesWithPlaceholdersReplaced.push({
513
+ variableName: item.name,
514
+ optional: false
515
+ });
516
+ }
517
+ } else if ("role" in item && "content" in item && item.type === "chatmessage" /* ChatMessage */) {
518
+ messagesWithPlaceholdersReplaced.push({
519
+ role: item.role,
520
+ content: this._transformToLangchainVariables(item.content)
521
+ });
522
+ }
523
+ }
524
+ return messagesWithPlaceholdersReplaced;
525
+ }
526
+ toJSON() {
527
+ return JSON.stringify({
528
+ name: this.name,
529
+ prompt: this.promptResponse.prompt.map((item) => {
530
+ if ("type" in item && item.type === "chatmessage" /* ChatMessage */) {
531
+ const { type: _, ...messageWithoutType } = item;
532
+ return messageWithoutType;
533
+ }
534
+ return item;
535
+ }),
536
+ version: this.version,
537
+ isFallback: this.isFallback,
538
+ tags: this.tags,
539
+ labels: this.labels,
540
+ type: this.type,
541
+ config: this.config
542
+ });
543
+ }
544
+ };
545
+
546
+ // src/prompt/promptManager.ts
547
+ var PromptManager = class {
548
+ constructor(params) {
549
+ const { apiClient } = params;
550
+ this.apiClient = apiClient;
551
+ this.cache = new LangfusePromptCache();
552
+ }
553
+ get logger() {
554
+ return (0, import_core3.getGlobalLogger)();
555
+ }
556
+ async create(body) {
557
+ var _a;
558
+ const requestBody = body.type === "chat" ? {
559
+ ...body,
560
+ prompt: body.prompt.map((item) => {
561
+ if ("type" in item && item.type === "placeholder" /* Placeholder */) {
562
+ return {
563
+ type: "placeholder" /* Placeholder */,
564
+ name: item.name
565
+ };
566
+ } else {
567
+ return { type: "chatmessage" /* ChatMessage */, ...item };
568
+ }
569
+ })
570
+ } : {
571
+ ...body,
572
+ type: (_a = body.type) != null ? _a : "text"
573
+ };
574
+ const promptResponse = await this.apiClient.prompts.create(requestBody);
575
+ if (promptResponse.type === "chat") {
576
+ return new ChatPromptClient(promptResponse);
577
+ }
578
+ return new TextPromptClient(promptResponse);
579
+ }
580
+ async update(params) {
581
+ const { name, version, newLabels } = params;
582
+ const newPrompt = await this.apiClient.promptVersion.update(name, version, {
583
+ newLabels
584
+ });
585
+ this.cache.invalidate(name);
586
+ return newPrompt;
587
+ }
588
+ async get(name, options) {
589
+ var _a;
590
+ const cacheKey = this.cache.createKey({
591
+ name,
592
+ label: options == null ? void 0 : options.label
593
+ });
594
+ const cachedPrompt = this.cache.getIncludingExpired(cacheKey);
595
+ if (!cachedPrompt || (options == null ? void 0 : options.cacheTtlSeconds) === 0) {
596
+ try {
597
+ return await this.fetchPromptAndUpdateCache({
598
+ name,
599
+ version: options == null ? void 0 : options.version,
600
+ label: options == null ? void 0 : options.label,
601
+ cacheTtlSeconds: options == null ? void 0 : options.cacheTtlSeconds,
602
+ maxRetries: options == null ? void 0 : options.maxRetries,
603
+ fetchTimeoutMs: options == null ? void 0 : options.fetchTimeoutMs
604
+ });
605
+ } catch (err) {
606
+ if (options == null ? void 0 : options.fallback) {
607
+ const sharedFallbackParams = {
608
+ name,
609
+ version: (_a = options == null ? void 0 : options.version) != null ? _a : 0,
610
+ labels: options.label ? [options.label] : [],
611
+ cacheTtlSeconds: options == null ? void 0 : options.cacheTtlSeconds,
612
+ config: {},
613
+ tags: []
614
+ };
615
+ if (options.type === "chat") {
616
+ return new ChatPromptClient(
617
+ {
618
+ ...sharedFallbackParams,
619
+ type: "chat",
620
+ prompt: options.fallback.map((msg) => ({
621
+ type: "chatmessage" /* ChatMessage */,
622
+ ...msg
623
+ }))
624
+ },
625
+ true
626
+ );
627
+ } else {
628
+ return new TextPromptClient(
629
+ {
630
+ ...sharedFallbackParams,
631
+ type: "text",
632
+ prompt: options.fallback
633
+ },
634
+ true
635
+ );
636
+ }
637
+ }
638
+ throw err;
639
+ }
640
+ }
641
+ if (cachedPrompt.isExpired) {
642
+ if (!this.cache.isRefreshing(cacheKey)) {
643
+ const refreshPromptPromise = this.fetchPromptAndUpdateCache({
644
+ name,
645
+ version: options == null ? void 0 : options.version,
646
+ label: options == null ? void 0 : options.label,
647
+ cacheTtlSeconds: options == null ? void 0 : options.cacheTtlSeconds,
648
+ maxRetries: options == null ? void 0 : options.maxRetries,
649
+ fetchTimeoutMs: options == null ? void 0 : options.fetchTimeoutMs
650
+ }).catch(() => {
651
+ this.logger.warn(
652
+ `Failed to refresh prompt cache '${cacheKey}', stale cache will be used until next refresh succeeds.`
653
+ );
654
+ });
655
+ this.cache.addRefreshingPromise(cacheKey, refreshPromptPromise);
656
+ }
657
+ return cachedPrompt.value;
658
+ }
659
+ return cachedPrompt.value;
660
+ }
661
+ async fetchPromptAndUpdateCache(params) {
662
+ const cacheKey = this.cache.createKey(params);
663
+ try {
664
+ const {
665
+ name,
666
+ version,
667
+ cacheTtlSeconds,
668
+ label,
669
+ maxRetries,
670
+ fetchTimeoutMs
671
+ } = params;
672
+ const data = await this.apiClient.prompts.get(
673
+ name,
674
+ {
675
+ version,
676
+ label
677
+ },
678
+ {
679
+ maxRetries,
680
+ timeoutInSeconds: fetchTimeoutMs ? fetchTimeoutMs / 1e3 : void 0
681
+ }
682
+ );
683
+ let prompt;
684
+ if (data.type === "chat") {
685
+ prompt = new ChatPromptClient(data);
686
+ } else {
687
+ prompt = new TextPromptClient(data);
688
+ }
689
+ this.cache.set(cacheKey, prompt, cacheTtlSeconds);
690
+ return prompt;
691
+ } catch (error) {
692
+ this.logger.error(`Error fetching prompt '${cacheKey}':`, error);
693
+ throw error;
694
+ }
695
+ }
696
+ };
697
+
698
+ // src/score/index.ts
699
+ var import_core4 = require("@langfuse/core");
700
+ var import_api = require("@opentelemetry/api");
701
+ var MAX_QUEUE_SIZE = 1e5;
702
+ var MAX_BATCH_SIZE = 100;
703
+ var ScoreManager = class {
704
+ constructor(params) {
705
+ this.eventQueue = [];
706
+ this.flushPromise = null;
707
+ this.flushTimer = null;
708
+ this.apiClient = params.apiClient;
709
+ const envFlushAtCount = (0, import_core4.getEnv)("LANGFUSE_FLUSH_AT");
710
+ const envFlushIntervalSeconds = (0, import_core4.getEnv)("LANGFUSE_FLUSH_INTERVAL");
711
+ this.flushAtCount = envFlushAtCount ? Number(envFlushAtCount) : 10;
712
+ this.flushIntervalSeconds = envFlushIntervalSeconds ? Number(envFlushIntervalSeconds) : 1;
713
+ }
714
+ get logger() {
715
+ return (0, import_core4.getGlobalLogger)();
716
+ }
717
+ create(data) {
718
+ var _a, _b;
719
+ const scoreData = {
720
+ ...data,
721
+ id: (_a = data.id) != null ? _a : (0, import_core4.generateUUID)(),
722
+ environment: (_b = data.environment) != null ? _b : (0, import_core4.getEnv)("LANGFUSE_TRACING_ENVIRONMENT")
723
+ };
724
+ const scoreIngestionEvent = {
725
+ id: (0, import_core4.generateUUID)(),
726
+ type: "score-create",
727
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
728
+ body: scoreData
729
+ };
730
+ if (this.eventQueue.length >= MAX_QUEUE_SIZE) {
731
+ this.logger.error(
732
+ `Score queue is at max size ${MAX_QUEUE_SIZE}. Dropping score.`
733
+ );
734
+ return;
735
+ }
736
+ this.eventQueue.push(scoreIngestionEvent);
737
+ if (this.eventQueue.length >= this.flushAtCount) {
738
+ this.flushPromise = this.flush();
739
+ } else if (!this.flushTimer) {
740
+ this.flushTimer = (0, import_core4.safeSetTimeout)(() => {
741
+ this.flushPromise = this.flush();
742
+ }, this.flushIntervalSeconds * 1e3);
743
+ }
744
+ }
745
+ observation(observation, data) {
746
+ const { spanId, traceId } = observation.otelSpan.spanContext();
747
+ this.create({
748
+ ...data,
749
+ traceId,
750
+ observationId: spanId
751
+ });
752
+ }
753
+ trace(observation, data) {
754
+ const { traceId } = observation.otelSpan.spanContext();
755
+ this.create({
756
+ ...data,
757
+ traceId
758
+ });
759
+ }
760
+ activeObservation(data) {
761
+ const currentOtelSpan = import_api.trace.getActiveSpan();
762
+ if (!currentOtelSpan) {
763
+ this.logger.warn("No active span in context to score.");
764
+ return;
765
+ }
766
+ const { spanId, traceId } = currentOtelSpan.spanContext();
767
+ this.create({
768
+ ...data,
769
+ traceId,
770
+ observationId: spanId
771
+ });
772
+ }
773
+ activeTrace(data) {
774
+ const currentOtelSpan = import_api.trace.getActiveSpan();
775
+ if (!currentOtelSpan) {
776
+ this.logger.warn("No active span in context to score trace.");
777
+ return;
778
+ }
779
+ const { traceId } = currentOtelSpan.spanContext();
780
+ this.create({
781
+ ...data,
782
+ traceId
783
+ });
784
+ }
785
+ async handleFlush() {
786
+ try {
787
+ if (this.flushTimer) {
788
+ clearTimeout(this.flushTimer);
789
+ this.flushTimer = null;
790
+ }
791
+ const promises = [];
792
+ while (this.eventQueue.length > 0) {
793
+ const batch = this.eventQueue.splice(0, MAX_BATCH_SIZE);
794
+ promises.push(
795
+ this.apiClient.ingestion.batch({ batch }).then((res) => {
796
+ var _a;
797
+ if (((_a = res.errors) == null ? void 0 : _a.length) > 0) {
798
+ this.logger.error("Error ingesting scores:", res.errors);
799
+ }
800
+ }).catch((err) => {
801
+ this.logger.error("Failed to export score batch:", err);
802
+ })
803
+ );
804
+ }
805
+ await Promise.all(promises);
806
+ } catch (err) {
807
+ this.logger.error("Error flushing Score Manager: ", err);
808
+ } finally {
809
+ this.flushPromise = null;
810
+ }
811
+ }
812
+ async flush() {
813
+ var _a;
814
+ return (_a = this.flushPromise) != null ? _a : this.handleFlush();
815
+ }
816
+ async shutdown() {
817
+ await this.flush();
818
+ }
819
+ };
820
+
821
+ // src/LangfuseClient.ts
822
+ var LangfuseClient = class {
823
+ constructor(params) {
824
+ this.projectId = null;
825
+ var _a, _b, _c, _d, _e, _f, _g;
826
+ const logger = (0, import_core5.getGlobalLogger)();
827
+ const publicKey = (_a = params == null ? void 0 : params.publicKey) != null ? _a : (0, import_core5.getEnv)("LANGFUSE_PUBLIC_KEY");
828
+ const secretKey = (_b = params == null ? void 0 : params.secretKey) != null ? _b : (0, import_core5.getEnv)("LANGFUSE_SECRET_KEY");
829
+ this.baseUrl = (_e = (_d = (_c = params == null ? void 0 : params.baseUrl) != null ? _c : (0, import_core5.getEnv)("LANGFUSE_BASE_URL")) != null ? _d : (0, import_core5.getEnv)("LANGFUSE_BASEURL")) != null ? _e : (
830
+ // legacy v2
831
+ "https://cloud.langfuse.com"
832
+ );
833
+ if (!publicKey) {
834
+ logger.warn(
835
+ "No public key provided in constructor or as LANGFUSE_PUBLIC_KEY env var. Client operations will fail."
836
+ );
837
+ }
838
+ if (!secretKey) {
839
+ logger.warn(
840
+ "No secret key provided in constructor or as LANGFUSE_SECRET_KEY env var. Client operations will fail."
841
+ );
842
+ }
843
+ const timeoutSeconds = (_g = params == null ? void 0 : params.timeout) != null ? _g : Number((_f = (0, import_core5.getEnv)("LANGFUSE_TIMEOUT")) != null ? _f : 5);
844
+ this.api = new import_core5.LangfuseAPIClient({
845
+ baseUrl: this.baseUrl,
846
+ username: publicKey,
847
+ password: secretKey,
848
+ xLangfusePublicKey: publicKey,
849
+ xLangfuseSdkVersion: import_core5.LANGFUSE_SDK_VERSION,
850
+ xLangfuseSdkName: "javascript",
851
+ environment: "",
852
+ // noop as baseUrl is set
853
+ headers: params == null ? void 0 : params.additionalHeaders
854
+ });
855
+ logger.debug("Initialized LangfuseClient with params:", {
856
+ publicKey,
857
+ baseUrl: this.baseUrl,
858
+ timeoutSeconds
859
+ });
860
+ this.prompt = new PromptManager({ apiClient: this.api });
861
+ this.dataset = new DatasetManager({ apiClient: this.api });
862
+ this.score = new ScoreManager({ apiClient: this.api });
863
+ this.media = new MediaManager({ apiClient: this.api });
864
+ this.getPrompt = this.prompt.get.bind(this.prompt);
865
+ this.createPrompt = this.prompt.create.bind(this.prompt);
866
+ this.updatePrompt = this.prompt.update.bind(this.prompt);
867
+ this.getDataset = this.dataset.get;
868
+ this.fetchTrace = this.api.trace.get;
869
+ this.fetchTraces = this.api.trace.list;
870
+ this.fetchObservation = this.api.observations.get;
871
+ this.fetchObservations = this.api.observations.getMany;
872
+ this.fetchSessions = this.api.sessions.get;
873
+ this.getDatasetRun = this.api.datasets.getRun;
874
+ this.getDatasetRuns = this.api.datasets.getRuns;
875
+ this.createDataset = this.api.datasets.create;
876
+ this.getDatasetItem = this.api.datasetItems.get;
877
+ this.createDatasetItem = this.api.datasetItems.create;
878
+ this.fetchMedia = this.api.media.get;
879
+ this.resolveMediaReferences = this.media.resolveReferences;
880
+ }
881
+ async flush() {
882
+ return this.score.flush();
883
+ }
884
+ async shutdown() {
885
+ return this.score.shutdown();
886
+ }
887
+ async getTraceUrl(traceId) {
888
+ let projectId = this.projectId;
889
+ if (!projectId) {
890
+ projectId = (await this.api.projects.get()).data[0].id;
891
+ this.projectId = projectId;
892
+ }
893
+ const traceUrl = `${this.baseUrl}/project/${projectId}/traces/${traceId}`;
894
+ return traceUrl;
895
+ }
896
+ };
897
+ // Annotate the CommonJS export names for ESM import in node:
898
+ 0 && (module.exports = {
899
+ ChatMessageType,
900
+ ChatPromptClient,
901
+ DatasetManager,
902
+ LangfuseClient,
903
+ MediaManager,
904
+ PromptManager,
905
+ ScoreManager,
906
+ TextPromptClient
907
+ });
908
+ //# sourceMappingURL=index.cjs.map