@lmnr-ai/client 0.8.1
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/LICENSE +75 -0
- package/dist/index.cjs +692 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +381 -0
- package/dist/index.d.mts +381 -0
- package/dist/index.mjs +663 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +50 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,692 @@
|
|
|
1
|
+
//#region rolldown:runtime
|
|
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 __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
10
|
+
for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
11
|
+
key = keys[i];
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except) {
|
|
13
|
+
__defProp(to, key, {
|
|
14
|
+
get: ((k) => from[k]).bind(null, key),
|
|
15
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return to;
|
|
21
|
+
};
|
|
22
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
23
|
+
value: mod,
|
|
24
|
+
enumerable: true
|
|
25
|
+
}) : target, mod));
|
|
26
|
+
|
|
27
|
+
//#endregion
|
|
28
|
+
let dotenv = require("dotenv");
|
|
29
|
+
let path = require("path");
|
|
30
|
+
path = __toESM(path);
|
|
31
|
+
let pino = require("pino");
|
|
32
|
+
pino = __toESM(pino);
|
|
33
|
+
let pino_pretty = require("pino-pretty");
|
|
34
|
+
let uuid = require("uuid");
|
|
35
|
+
|
|
36
|
+
//#region package.json
|
|
37
|
+
var version = "0.8.1";
|
|
38
|
+
|
|
39
|
+
//#endregion
|
|
40
|
+
//#region src/version.ts
|
|
41
|
+
function getLangVersion() {
|
|
42
|
+
if (typeof process !== "undefined" && process.versions && process.versions.node) return `node-${process.versions.node}`;
|
|
43
|
+
if (typeof navigator !== "undefined" && navigator.userAgent) return `browser-${navigator.userAgent}`;
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
//#endregion
|
|
48
|
+
//#region src/resources/index.ts
|
|
49
|
+
var BaseResource = class {
|
|
50
|
+
constructor(baseHttpUrl, projectApiKey) {
|
|
51
|
+
this.baseHttpUrl = baseHttpUrl;
|
|
52
|
+
this.projectApiKey = projectApiKey;
|
|
53
|
+
}
|
|
54
|
+
headers() {
|
|
55
|
+
return {
|
|
56
|
+
Authorization: `Bearer ${this.projectApiKey}`,
|
|
57
|
+
"Content-Type": "application/json",
|
|
58
|
+
Accept: "application/json"
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
async handleError(response) {
|
|
62
|
+
const errorMsg = await response.text();
|
|
63
|
+
throw new Error(`${response.status} ${errorMsg}`);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
//#endregion
|
|
68
|
+
//#region src/resources/browser-events.ts
|
|
69
|
+
var BrowserEventsResource = class extends BaseResource {
|
|
70
|
+
constructor(baseHttpUrl, projectApiKey) {
|
|
71
|
+
super(baseHttpUrl, projectApiKey);
|
|
72
|
+
}
|
|
73
|
+
async send({ sessionId, traceId, events }) {
|
|
74
|
+
const payload = {
|
|
75
|
+
sessionId,
|
|
76
|
+
traceId,
|
|
77
|
+
events,
|
|
78
|
+
source: getLangVersion() ?? "javascript",
|
|
79
|
+
sdkVersion: version
|
|
80
|
+
};
|
|
81
|
+
const jsonString = JSON.stringify(payload);
|
|
82
|
+
const compressedStream = new Blob([jsonString], { type: "application/json" }).stream().pipeThrough(new CompressionStream("gzip"));
|
|
83
|
+
const compressedData = await new Response(compressedStream).arrayBuffer();
|
|
84
|
+
const response = await fetch(this.baseHttpUrl + "/v1/browser-sessions/events", {
|
|
85
|
+
method: "POST",
|
|
86
|
+
headers: {
|
|
87
|
+
...this.headers(),
|
|
88
|
+
"Content-Encoding": "gzip"
|
|
89
|
+
},
|
|
90
|
+
body: compressedData
|
|
91
|
+
});
|
|
92
|
+
if (!response.ok) await this.handleError(response);
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
//#endregion
|
|
97
|
+
//#region src/utils.ts
|
|
98
|
+
function initializeLogger(options) {
|
|
99
|
+
const colorize = options?.colorize ?? true;
|
|
100
|
+
const level = options?.level ?? process.env.LMNR_LOG_LEVEL?.toLowerCase()?.trim() ?? "info";
|
|
101
|
+
return (0, pino.default)({ level }, (0, pino_pretty.PinoPretty)({
|
|
102
|
+
colorize,
|
|
103
|
+
minimumLevel: level
|
|
104
|
+
}));
|
|
105
|
+
}
|
|
106
|
+
const logger$2 = initializeLogger();
|
|
107
|
+
const isStringUUID = (id) => /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(id);
|
|
108
|
+
const newUUID = () => {
|
|
109
|
+
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") return crypto.randomUUID();
|
|
110
|
+
else return (0, uuid.v4)();
|
|
111
|
+
};
|
|
112
|
+
const otelSpanIdToUUID = (spanId) => {
|
|
113
|
+
let id = spanId.toLowerCase();
|
|
114
|
+
if (id.startsWith("0x")) id = id.slice(2);
|
|
115
|
+
if (id.length !== 16) logger$2.warn(`Span ID ${spanId} is not 16 hex chars long. This is not a valid OpenTelemetry span ID.`);
|
|
116
|
+
if (!/^[0-9a-f]+$/.test(id)) {
|
|
117
|
+
logger$2.error(`Span ID ${spanId} is not a valid hex string. Generating a random UUID instead.`);
|
|
118
|
+
return newUUID();
|
|
119
|
+
}
|
|
120
|
+
return id.padStart(32, "0").replace(/^([0-9a-f]{8})([0-9a-f]{4})([0-9a-f]{4})([0-9a-f]{4})([0-9a-f]{12})$/, "$1-$2-$3-$4-$5");
|
|
121
|
+
};
|
|
122
|
+
const otelTraceIdToUUID = (traceId) => {
|
|
123
|
+
let id = traceId.toLowerCase();
|
|
124
|
+
if (id.startsWith("0x")) id = id.slice(2);
|
|
125
|
+
if (id.length !== 32) logger$2.warn(`Trace ID ${traceId} is not 32 hex chars long. This is not a valid OpenTelemetry trace ID.`);
|
|
126
|
+
if (!/^[0-9a-f]+$/.test(id)) {
|
|
127
|
+
logger$2.error(`Trace ID ${traceId} is not a valid hex string. Generating a random UUID instead.`);
|
|
128
|
+
return newUUID();
|
|
129
|
+
}
|
|
130
|
+
return id.replace(/^([0-9a-f]{8})([0-9a-f]{4})([0-9a-f]{4})([0-9a-f]{4})([0-9a-f]{12})$/, "$1-$2-$3-$4-$5");
|
|
131
|
+
};
|
|
132
|
+
const slicePayload = (value, length) => {
|
|
133
|
+
if (value === null || value === void 0) return value;
|
|
134
|
+
const str = JSON.stringify(value);
|
|
135
|
+
if (str.length <= length) return value;
|
|
136
|
+
return str.slice(0, length) + "...";
|
|
137
|
+
};
|
|
138
|
+
const loadEnv = (options) => {
|
|
139
|
+
const nodeEnv = process.env.NODE_ENV || "development";
|
|
140
|
+
const envDir = process.cwd();
|
|
141
|
+
const envFiles = [
|
|
142
|
+
".env",
|
|
143
|
+
".env.local",
|
|
144
|
+
`.env.${nodeEnv}`,
|
|
145
|
+
`.env.${nodeEnv}.local`
|
|
146
|
+
];
|
|
147
|
+
const logLevel = process.env.LMNR_LOG_LEVEL ?? "info";
|
|
148
|
+
const verbose = ["debug", "trace"].includes(logLevel.trim().toLowerCase());
|
|
149
|
+
const quiet = options?.quiet ?? !verbose;
|
|
150
|
+
(0, dotenv.config)({
|
|
151
|
+
path: options?.paths ?? envFiles.map((envFile) => path.resolve(envDir, envFile)),
|
|
152
|
+
quiet
|
|
153
|
+
});
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
//#endregion
|
|
157
|
+
//#region src/resources/datasets.ts
|
|
158
|
+
const logger$1 = initializeLogger();
|
|
159
|
+
const DEFAULT_DATASET_PULL_LIMIT = 100;
|
|
160
|
+
const DEFAULT_DATASET_PUSH_BATCH_SIZE = 100;
|
|
161
|
+
var DatasetsResource = class extends BaseResource {
|
|
162
|
+
constructor(baseHttpUrl, projectApiKey) {
|
|
163
|
+
super(baseHttpUrl, projectApiKey);
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* List all datasets.
|
|
167
|
+
*
|
|
168
|
+
* @returns {Promise<Dataset[]>} Array of datasets
|
|
169
|
+
*/
|
|
170
|
+
async listDatasets() {
|
|
171
|
+
const response = await fetch(this.baseHttpUrl + "/v1/datasets", {
|
|
172
|
+
method: "GET",
|
|
173
|
+
headers: this.headers()
|
|
174
|
+
});
|
|
175
|
+
if (!response.ok) await this.handleError(response);
|
|
176
|
+
return response.json();
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Get a dataset by name.
|
|
180
|
+
*
|
|
181
|
+
* @param {string} name - Name of the dataset
|
|
182
|
+
* @returns {Promise<Dataset[]>} Array of datasets with matching name
|
|
183
|
+
*/
|
|
184
|
+
async getDatasetByName(name) {
|
|
185
|
+
const params = new URLSearchParams({ name });
|
|
186
|
+
const response = await fetch(this.baseHttpUrl + `/v1/datasets?${params.toString()}`, {
|
|
187
|
+
method: "GET",
|
|
188
|
+
headers: this.headers()
|
|
189
|
+
});
|
|
190
|
+
if (!response.ok) await this.handleError(response);
|
|
191
|
+
return response.json();
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Push datapoints to a dataset.
|
|
195
|
+
*
|
|
196
|
+
* @param {Object} options - Push options
|
|
197
|
+
* @param {Datapoint<D, T>[]} options.points - Datapoints to push
|
|
198
|
+
* @param {string} [options.name] - Name of the dataset (either name or id must be provided)
|
|
199
|
+
* @param {StringUUID} [options.id] - ID of the dataset (either name or id must be provided)
|
|
200
|
+
* @param {number} [options.batchSize] - Batch size for pushing (default: 100)
|
|
201
|
+
* @param {boolean} [options.createDataset] - Whether to create the dataset if it doesn't exist
|
|
202
|
+
* @returns {Promise<PushDatapointsResponse | undefined>}
|
|
203
|
+
*/
|
|
204
|
+
async push({ points, name, id, batchSize = DEFAULT_DATASET_PUSH_BATCH_SIZE, createDataset = false }) {
|
|
205
|
+
if (!name && !id) throw new Error("Either name or id must be provided");
|
|
206
|
+
if (name && id) throw new Error("Only one of name or id must be provided");
|
|
207
|
+
if (createDataset && !name) throw new Error("Name must be provided when creating a new dataset");
|
|
208
|
+
const identifier = name ? { name } : { datasetId: id };
|
|
209
|
+
const totalBatches = Math.ceil(points.length / batchSize);
|
|
210
|
+
let response;
|
|
211
|
+
for (let i = 0; i < points.length; i += batchSize) {
|
|
212
|
+
const batchNum = Math.floor(i / batchSize) + 1;
|
|
213
|
+
logger$1.debug(`Pushing batch ${batchNum} of ${totalBatches}`);
|
|
214
|
+
const batch = points.slice(i, i + batchSize);
|
|
215
|
+
const fetchResponse = await fetch(this.baseHttpUrl + "/v1/datasets/datapoints", {
|
|
216
|
+
method: "POST",
|
|
217
|
+
headers: this.headers(),
|
|
218
|
+
body: JSON.stringify({
|
|
219
|
+
...identifier,
|
|
220
|
+
datapoints: batch.map((point) => ({
|
|
221
|
+
data: point.data,
|
|
222
|
+
target: point.target ?? {},
|
|
223
|
+
metadata: point.metadata ?? {}
|
|
224
|
+
})),
|
|
225
|
+
createDataset
|
|
226
|
+
})
|
|
227
|
+
});
|
|
228
|
+
if (fetchResponse.status !== 200 && fetchResponse.status !== 201) await this.handleError(fetchResponse);
|
|
229
|
+
response = await fetchResponse.json();
|
|
230
|
+
}
|
|
231
|
+
return response;
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Pull datapoints from a dataset.
|
|
235
|
+
*
|
|
236
|
+
* @param {Object} options - Pull options
|
|
237
|
+
* @param {string} [options.name] - Name of the dataset (either name or id must be provided)
|
|
238
|
+
* @param {StringUUID} [options.id] - ID of the dataset (either name or id must be provided)
|
|
239
|
+
* @param {number} [options.limit] - Maximum number of datapoints to return (default: 100)
|
|
240
|
+
* @param {number} [options.offset] - Offset for pagination (default: 0)
|
|
241
|
+
* @returns {Promise<GetDatapointsResponse<D, T>>}
|
|
242
|
+
*/
|
|
243
|
+
async pull({ name, id, limit = DEFAULT_DATASET_PULL_LIMIT, offset = 0 }) {
|
|
244
|
+
if (!name && !id) throw new Error("Either name or id must be provided");
|
|
245
|
+
if (name && id) throw new Error("Only one of name or id must be provided");
|
|
246
|
+
const paramsObj = {
|
|
247
|
+
offset: offset.toString(),
|
|
248
|
+
limit: limit.toString()
|
|
249
|
+
};
|
|
250
|
+
if (name) paramsObj.name = name;
|
|
251
|
+
else paramsObj.datasetId = id;
|
|
252
|
+
const params = new URLSearchParams(paramsObj);
|
|
253
|
+
const response = await fetch(this.baseHttpUrl + `/v1/datasets/datapoints?${params.toString()}`, {
|
|
254
|
+
method: "GET",
|
|
255
|
+
headers: this.headers()
|
|
256
|
+
});
|
|
257
|
+
if (!response.ok) await this.handleError(response);
|
|
258
|
+
return response.json();
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
//#endregion
|
|
263
|
+
//#region src/resources/evals.ts
|
|
264
|
+
const logger = initializeLogger();
|
|
265
|
+
const INITIAL_EVALUATION_DATAPOINT_MAX_DATA_LENGTH = 16e6;
|
|
266
|
+
var EvalsResource = class extends BaseResource {
|
|
267
|
+
constructor(baseHttpUrl, projectApiKey) {
|
|
268
|
+
super(baseHttpUrl, projectApiKey);
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Initialize an evaluation.
|
|
272
|
+
*
|
|
273
|
+
* @param {string} name - Name of the evaluation
|
|
274
|
+
* @param {string} groupName - Group name of the evaluation
|
|
275
|
+
* @param {Record<string, any>} metadata - Optional metadata
|
|
276
|
+
* @returns {Promise<InitEvaluationResponse>} Response from the evaluation initialization
|
|
277
|
+
*/
|
|
278
|
+
async init(name, groupName, metadata) {
|
|
279
|
+
const response = await fetch(this.baseHttpUrl + "/v1/evals", {
|
|
280
|
+
method: "POST",
|
|
281
|
+
headers: this.headers(),
|
|
282
|
+
body: JSON.stringify({
|
|
283
|
+
name: name ?? null,
|
|
284
|
+
groupName: groupName ?? null,
|
|
285
|
+
metadata: metadata ?? null
|
|
286
|
+
})
|
|
287
|
+
});
|
|
288
|
+
if (!response.ok) await this.handleError(response);
|
|
289
|
+
return response.json();
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Create a new evaluation and return its ID.
|
|
293
|
+
*
|
|
294
|
+
* @param {string} [name] - Optional name of the evaluation
|
|
295
|
+
* @param {string} [groupName] - An identifier to group evaluations
|
|
296
|
+
* @param {Record<string, any>} [metadata] - Optional metadata
|
|
297
|
+
* @returns {Promise<StringUUID>} The evaluation ID
|
|
298
|
+
*/
|
|
299
|
+
async create(args) {
|
|
300
|
+
return (await this.init(args?.name, args?.groupName, args?.metadata)).id;
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Create a new evaluation and return its ID.
|
|
304
|
+
* @deprecated use `create` instead.
|
|
305
|
+
*/
|
|
306
|
+
async createEvaluation(name, groupName, metadata) {
|
|
307
|
+
return (await this.init(name, groupName, metadata)).id;
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Create a datapoint for an evaluation.
|
|
311
|
+
*
|
|
312
|
+
* @param {Object} options - Create datapoint options
|
|
313
|
+
* @param {string} options.evalId - The evaluation ID
|
|
314
|
+
* @param {D} options.data - The input data for the executor
|
|
315
|
+
* @param {T} [options.target] - The target/expected output for evaluators
|
|
316
|
+
* @param {Record<string, any>} [options.metadata] - Optional metadata
|
|
317
|
+
* @param {number} [options.index] - Optional index of the datapoint
|
|
318
|
+
* @param {string} [options.traceId] - Optional trace ID
|
|
319
|
+
* @returns {Promise<StringUUID>} The datapoint ID
|
|
320
|
+
*/
|
|
321
|
+
async createDatapoint({ evalId, data, target, metadata, index, traceId }) {
|
|
322
|
+
const datapointId = newUUID();
|
|
323
|
+
const partialDatapoint = {
|
|
324
|
+
id: datapointId,
|
|
325
|
+
data,
|
|
326
|
+
target,
|
|
327
|
+
index: index ?? 0,
|
|
328
|
+
traceId: traceId ?? newUUID(),
|
|
329
|
+
executorSpanId: newUUID(),
|
|
330
|
+
metadata
|
|
331
|
+
};
|
|
332
|
+
await this.saveDatapoints({
|
|
333
|
+
evalId,
|
|
334
|
+
datapoints: [partialDatapoint]
|
|
335
|
+
});
|
|
336
|
+
return datapointId;
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Update a datapoint with evaluation results.
|
|
340
|
+
*
|
|
341
|
+
* @param {Object} options - Update datapoint options
|
|
342
|
+
* @param {string} options.evalId - The evaluation ID
|
|
343
|
+
* @param {string} options.datapointId - The datapoint ID
|
|
344
|
+
* @param {Record<string, number>} options.scores - The scores
|
|
345
|
+
* @param {O} [options.executorOutput] - The executor output
|
|
346
|
+
* @returns {Promise<void>}
|
|
347
|
+
*/
|
|
348
|
+
async updateDatapoint({ evalId, datapointId, scores, executorOutput }) {
|
|
349
|
+
const response = await fetch(this.baseHttpUrl + `/v1/evals/${evalId}/datapoints/${datapointId}`, {
|
|
350
|
+
method: "POST",
|
|
351
|
+
headers: this.headers(),
|
|
352
|
+
body: JSON.stringify({
|
|
353
|
+
executorOutput,
|
|
354
|
+
scores
|
|
355
|
+
})
|
|
356
|
+
});
|
|
357
|
+
if (!response.ok) await this.handleError(response);
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Save evaluation datapoints.
|
|
361
|
+
*
|
|
362
|
+
* @param {Object} options - Save datapoints options
|
|
363
|
+
* @param {string} options.evalId - ID of the evaluation
|
|
364
|
+
* @param {EvaluationDatapoint<D, T, O>[]} options.datapoints - Datapoint to add
|
|
365
|
+
* @param {string} [options.groupName] - Group name of the evaluation
|
|
366
|
+
* @returns {Promise<void>} Response from the datapoint addition
|
|
367
|
+
*/
|
|
368
|
+
async saveDatapoints({ evalId, datapoints, groupName }) {
|
|
369
|
+
const response = await fetch(this.baseHttpUrl + `/v1/evals/${evalId}/datapoints`, {
|
|
370
|
+
method: "POST",
|
|
371
|
+
headers: this.headers(),
|
|
372
|
+
body: JSON.stringify({
|
|
373
|
+
points: datapoints.map((d) => ({
|
|
374
|
+
...d,
|
|
375
|
+
data: slicePayload(d.data, INITIAL_EVALUATION_DATAPOINT_MAX_DATA_LENGTH),
|
|
376
|
+
target: slicePayload(d.target, INITIAL_EVALUATION_DATAPOINT_MAX_DATA_LENGTH),
|
|
377
|
+
executorOutput: slicePayload(d.executorOutput, INITIAL_EVALUATION_DATAPOINT_MAX_DATA_LENGTH)
|
|
378
|
+
})),
|
|
379
|
+
groupName: groupName ?? null
|
|
380
|
+
})
|
|
381
|
+
});
|
|
382
|
+
if (response.status === 413) return await this.retrySaveDatapoints({
|
|
383
|
+
evalId,
|
|
384
|
+
datapoints,
|
|
385
|
+
groupName
|
|
386
|
+
});
|
|
387
|
+
if (!response.ok) await this.handleError(response);
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Get evaluation datapoints.
|
|
391
|
+
*
|
|
392
|
+
* @deprecated Use `client.datasets.pull()` instead.
|
|
393
|
+
* @param {Object} options - Get datapoints options
|
|
394
|
+
* @param {string} options.datasetName - Name of the dataset
|
|
395
|
+
* @param {number} options.offset - Offset at which to start the query
|
|
396
|
+
* @param {number} options.limit - Maximum number of datapoints to return
|
|
397
|
+
* @returns {Promise<GetDatapointsResponse>} Response from the datapoint retrieval
|
|
398
|
+
*/
|
|
399
|
+
async getDatapoints({ datasetName, offset, limit }) {
|
|
400
|
+
logger.warn("evals.getDatapoints() is deprecated. Use client.datasets.pull() instead.");
|
|
401
|
+
const params = new URLSearchParams({
|
|
402
|
+
name: datasetName,
|
|
403
|
+
offset: offset.toString(),
|
|
404
|
+
limit: limit.toString()
|
|
405
|
+
});
|
|
406
|
+
const response = await fetch(this.baseHttpUrl + `/v1/datasets/datapoints?${params.toString()}`, {
|
|
407
|
+
method: "GET",
|
|
408
|
+
headers: this.headers()
|
|
409
|
+
});
|
|
410
|
+
if (!response.ok) await this.handleError(response);
|
|
411
|
+
return await response.json();
|
|
412
|
+
}
|
|
413
|
+
async retrySaveDatapoints({ evalId, datapoints, groupName, maxRetries = 25, initialLength = INITIAL_EVALUATION_DATAPOINT_MAX_DATA_LENGTH }) {
|
|
414
|
+
let length = initialLength;
|
|
415
|
+
let lastResponse = null;
|
|
416
|
+
for (let i = 0; i < maxRetries; i++) {
|
|
417
|
+
logger.debug(`Retrying save datapoints... ${i + 1} of ${maxRetries}, length: ${length}`);
|
|
418
|
+
const response = await fetch(this.baseHttpUrl + `/v1/evals/${evalId}/datapoints`, {
|
|
419
|
+
method: "POST",
|
|
420
|
+
headers: this.headers(),
|
|
421
|
+
body: JSON.stringify({
|
|
422
|
+
points: datapoints.map((d) => ({
|
|
423
|
+
...d,
|
|
424
|
+
data: slicePayload(d.data, length),
|
|
425
|
+
target: slicePayload(d.target, length),
|
|
426
|
+
executorOutput: slicePayload(d.executorOutput, length)
|
|
427
|
+
})),
|
|
428
|
+
groupName: groupName ?? null
|
|
429
|
+
})
|
|
430
|
+
});
|
|
431
|
+
lastResponse = response;
|
|
432
|
+
length = Math.floor(length / 2);
|
|
433
|
+
if (response.status !== 413) break;
|
|
434
|
+
}
|
|
435
|
+
if (lastResponse && !lastResponse.ok) await this.handleError(lastResponse);
|
|
436
|
+
}
|
|
437
|
+
};
|
|
438
|
+
|
|
439
|
+
//#endregion
|
|
440
|
+
//#region src/resources/evaluators.ts
|
|
441
|
+
var EvaluatorScoreSourceType = /* @__PURE__ */ function(EvaluatorScoreSourceType$1) {
|
|
442
|
+
EvaluatorScoreSourceType$1["Evaluator"] = "Evaluator";
|
|
443
|
+
EvaluatorScoreSourceType$1["Code"] = "Code";
|
|
444
|
+
return EvaluatorScoreSourceType$1;
|
|
445
|
+
}(EvaluatorScoreSourceType || {});
|
|
446
|
+
/**
|
|
447
|
+
* Resource for creating evaluator scores
|
|
448
|
+
*/
|
|
449
|
+
var EvaluatorsResource = class extends BaseResource {
|
|
450
|
+
constructor(baseHttpUrl, projectApiKey) {
|
|
451
|
+
super(baseHttpUrl, projectApiKey);
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Create a score for a span or trace
|
|
455
|
+
*
|
|
456
|
+
* @param {ScoreOptions} options - Score creation options
|
|
457
|
+
* @param {string} options.name - Name of the score
|
|
458
|
+
* @param {string} [options.traceId] - The trace ID to score (will be attached to top-level span)
|
|
459
|
+
* @param {string} [options.spanId] - The span ID to score
|
|
460
|
+
* @param {Record<string, any>} [options.metadata] - Additional metadata
|
|
461
|
+
* @param {number} options.score - The score value (float)
|
|
462
|
+
* @returns {Promise<void>}
|
|
463
|
+
*
|
|
464
|
+
* @example
|
|
465
|
+
* // Score by trace ID (will attach to root span)
|
|
466
|
+
* await evaluators.score({
|
|
467
|
+
* name: "quality",
|
|
468
|
+
* traceId: "trace-id-here",
|
|
469
|
+
* score: 0.95,
|
|
470
|
+
* metadata: { model: "gpt-4" }
|
|
471
|
+
* });
|
|
472
|
+
*
|
|
473
|
+
* @example
|
|
474
|
+
* // Score by span ID
|
|
475
|
+
* await evaluators.score({
|
|
476
|
+
* name: "relevance",
|
|
477
|
+
* spanId: "span-id-here",
|
|
478
|
+
* score: 0.87
|
|
479
|
+
* });
|
|
480
|
+
*/
|
|
481
|
+
async score(options) {
|
|
482
|
+
const { name, metadata, score } = options;
|
|
483
|
+
let payload;
|
|
484
|
+
if ("traceId" in options && options.traceId) {
|
|
485
|
+
const formattedTraceId = isStringUUID(options.traceId) ? options.traceId : otelTraceIdToUUID(options.traceId);
|
|
486
|
+
payload = {
|
|
487
|
+
name,
|
|
488
|
+
metadata,
|
|
489
|
+
score,
|
|
490
|
+
source: EvaluatorScoreSourceType.Code,
|
|
491
|
+
traceId: formattedTraceId
|
|
492
|
+
};
|
|
493
|
+
} else if ("spanId" in options && options.spanId) {
|
|
494
|
+
const formattedSpanId = isStringUUID(options.spanId) ? options.spanId : otelSpanIdToUUID(options.spanId);
|
|
495
|
+
payload = {
|
|
496
|
+
name,
|
|
497
|
+
metadata,
|
|
498
|
+
score,
|
|
499
|
+
source: EvaluatorScoreSourceType.Code,
|
|
500
|
+
spanId: formattedSpanId
|
|
501
|
+
};
|
|
502
|
+
} else throw new Error("Either 'traceId' or 'spanId' must be provided.");
|
|
503
|
+
const response = await fetch(this.baseHttpUrl + "/v1/evaluators/score", {
|
|
504
|
+
method: "POST",
|
|
505
|
+
headers: this.headers(),
|
|
506
|
+
body: JSON.stringify(payload)
|
|
507
|
+
});
|
|
508
|
+
if (!response.ok) await this.handleError(response);
|
|
509
|
+
}
|
|
510
|
+
};
|
|
511
|
+
|
|
512
|
+
//#endregion
|
|
513
|
+
//#region src/resources/rollout-sessions.ts
|
|
514
|
+
var RolloutSessionsResource = class extends BaseResource {
|
|
515
|
+
constructor(baseHttpUrl, projectApiKey) {
|
|
516
|
+
super(baseHttpUrl, projectApiKey);
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* Connects to the SSE stream for rollout debugging sessions
|
|
520
|
+
* Returns the Response object for streaming SSE events
|
|
521
|
+
*/
|
|
522
|
+
async connect({ sessionId, name, params, signal }) {
|
|
523
|
+
const response = await fetch(`${this.baseHttpUrl}/v1/rollouts/${sessionId}`, {
|
|
524
|
+
method: "POST",
|
|
525
|
+
headers: {
|
|
526
|
+
...this.headers(),
|
|
527
|
+
"Accept": "text/event-stream"
|
|
528
|
+
},
|
|
529
|
+
body: JSON.stringify({
|
|
530
|
+
name,
|
|
531
|
+
params
|
|
532
|
+
}),
|
|
533
|
+
signal
|
|
534
|
+
});
|
|
535
|
+
if (!response.ok) throw new Error(`SSE connection failed: ${response.status} ${response.statusText}`);
|
|
536
|
+
if (!response.body) throw new Error("No response body");
|
|
537
|
+
return response;
|
|
538
|
+
}
|
|
539
|
+
async delete({ sessionId }) {
|
|
540
|
+
const response = await fetch(`${this.baseHttpUrl}/v1/rollouts/${sessionId}`, {
|
|
541
|
+
method: "DELETE",
|
|
542
|
+
headers: this.headers()
|
|
543
|
+
});
|
|
544
|
+
if (!response.ok) await this.handleError(response);
|
|
545
|
+
}
|
|
546
|
+
async setStatus({ sessionId, status }) {
|
|
547
|
+
const response = await fetch(`${this.baseHttpUrl}/v1/rollouts/${sessionId}/status`, {
|
|
548
|
+
method: "PATCH",
|
|
549
|
+
headers: this.headers(),
|
|
550
|
+
body: JSON.stringify({ status })
|
|
551
|
+
});
|
|
552
|
+
if (!response.ok) await this.handleError(response);
|
|
553
|
+
}
|
|
554
|
+
async sendSpanUpdate({ sessionId, span }) {
|
|
555
|
+
const response = await fetch(`${this.baseHttpUrl}/v1/rollouts/${sessionId}/update`, {
|
|
556
|
+
method: "PATCH",
|
|
557
|
+
headers: this.headers(),
|
|
558
|
+
body: JSON.stringify({
|
|
559
|
+
type: "spanStart",
|
|
560
|
+
spanId: otelSpanIdToUUID(span.spanId),
|
|
561
|
+
traceId: otelTraceIdToUUID(span.traceId),
|
|
562
|
+
parentSpanId: span.parentSpanId ? otelSpanIdToUUID(span.parentSpanId) : void 0,
|
|
563
|
+
attributes: span.attributes,
|
|
564
|
+
startTime: span.startTime,
|
|
565
|
+
name: span.name,
|
|
566
|
+
spanType: span.spanType
|
|
567
|
+
})
|
|
568
|
+
});
|
|
569
|
+
if (!response.ok) await this.handleError(response);
|
|
570
|
+
}
|
|
571
|
+
};
|
|
572
|
+
|
|
573
|
+
//#endregion
|
|
574
|
+
//#region src/resources/sql.ts
|
|
575
|
+
var SqlResource = class extends BaseResource {
|
|
576
|
+
constructor(baseHttpUrl, projectApiKey) {
|
|
577
|
+
super(baseHttpUrl, projectApiKey);
|
|
578
|
+
}
|
|
579
|
+
async query(sql, parameters = {}) {
|
|
580
|
+
const response = await fetch(`${this.baseHttpUrl}/v1/sql/query`, {
|
|
581
|
+
method: "POST",
|
|
582
|
+
headers: { ...this.headers() },
|
|
583
|
+
body: JSON.stringify({
|
|
584
|
+
query: sql,
|
|
585
|
+
parameters
|
|
586
|
+
})
|
|
587
|
+
});
|
|
588
|
+
if (!response.ok) await this.handleError(response);
|
|
589
|
+
return (await response.json()).data;
|
|
590
|
+
}
|
|
591
|
+
};
|
|
592
|
+
|
|
593
|
+
//#endregion
|
|
594
|
+
//#region src/resources/tags.ts
|
|
595
|
+
/** Resource for tagging traces. */
|
|
596
|
+
var TagsResource = class extends BaseResource {
|
|
597
|
+
/** Resource for tagging traces. */
|
|
598
|
+
constructor(baseHttpUrl, projectApiKey) {
|
|
599
|
+
super(baseHttpUrl, projectApiKey);
|
|
600
|
+
}
|
|
601
|
+
/**
|
|
602
|
+
* Tag a trace with a list of tags. Note that the trace must be ended before
|
|
603
|
+
* tagging it. You may want to call `await Laminar.flush()` after the trace
|
|
604
|
+
* that you want to tag.
|
|
605
|
+
*
|
|
606
|
+
* @param {string | StringUUID} trace_id - The trace id to tag.
|
|
607
|
+
* @param {string[] | string} tags - The tag or list of tags to add to the trace.
|
|
608
|
+
* @returns {Promise<any>} The response from the server.
|
|
609
|
+
* @example
|
|
610
|
+
* ```javascript
|
|
611
|
+
* import { Laminar, observe, LaminarClient } from "@lmnr-ai/lmnr";
|
|
612
|
+
* Laminar.initialize();
|
|
613
|
+
* const client = new LaminarClient();
|
|
614
|
+
* let traceId: StringUUID | null = null;
|
|
615
|
+
* // Make sure this is called outside of traced context.
|
|
616
|
+
* await observe(
|
|
617
|
+
* {
|
|
618
|
+
* name: "my-trace",
|
|
619
|
+
* },
|
|
620
|
+
* async () => {
|
|
621
|
+
* traceId = await Laminar.getTraceId();
|
|
622
|
+
* await foo();
|
|
623
|
+
* },
|
|
624
|
+
* );
|
|
625
|
+
*
|
|
626
|
+
* // or make sure the trace is ended by this point.
|
|
627
|
+
* await Laminar.flush();
|
|
628
|
+
* if (traceId) {
|
|
629
|
+
* await client.tags.tag(traceId, ["tag1", "tag2"]);
|
|
630
|
+
* }
|
|
631
|
+
* ```
|
|
632
|
+
*/
|
|
633
|
+
async tag(trace_id, tags) {
|
|
634
|
+
const traceTags = Array.isArray(tags) ? tags : [tags];
|
|
635
|
+
const formattedTraceId = isStringUUID(trace_id) ? trace_id : otelTraceIdToUUID(trace_id);
|
|
636
|
+
const url = this.baseHttpUrl + "/v1/tag";
|
|
637
|
+
const payload = {
|
|
638
|
+
"traceId": formattedTraceId,
|
|
639
|
+
"names": traceTags
|
|
640
|
+
};
|
|
641
|
+
const response = await fetch(url, {
|
|
642
|
+
method: "POST",
|
|
643
|
+
headers: this.headers(),
|
|
644
|
+
body: JSON.stringify(payload)
|
|
645
|
+
});
|
|
646
|
+
if (!response.ok) await this.handleError(response);
|
|
647
|
+
return response.json();
|
|
648
|
+
}
|
|
649
|
+
};
|
|
650
|
+
|
|
651
|
+
//#endregion
|
|
652
|
+
//#region src/index.ts
|
|
653
|
+
var LaminarClient = class {
|
|
654
|
+
constructor({ baseUrl, projectApiKey, port } = {}) {
|
|
655
|
+
loadEnv();
|
|
656
|
+
this.projectApiKey = projectApiKey ?? process.env.LMNR_PROJECT_API_KEY;
|
|
657
|
+
const httpPort = port ?? (baseUrl?.match(/:\d{1,5}$/g) ? parseInt(baseUrl.match(/:\d{1,5}$/g)[0].slice(1)) : 443);
|
|
658
|
+
this.baseUrl = `${(baseUrl ?? process.env.LMNR_BASE_URL)?.replace(/\/$/, "").replace(/:\d{1,5}$/g, "") ?? "https://api.lmnr.ai"}:${httpPort}`;
|
|
659
|
+
this._browserEvents = new BrowserEventsResource(this.baseUrl, this.projectApiKey);
|
|
660
|
+
this._datasets = new DatasetsResource(this.baseUrl, this.projectApiKey);
|
|
661
|
+
this._evals = new EvalsResource(this.baseUrl, this.projectApiKey);
|
|
662
|
+
this._evaluators = new EvaluatorsResource(this.baseUrl, this.projectApiKey);
|
|
663
|
+
this._rolloutSessions = new RolloutSessionsResource(this.baseUrl, this.projectApiKey);
|
|
664
|
+
this._sql = new SqlResource(this.baseUrl, this.projectApiKey);
|
|
665
|
+
this._tags = new TagsResource(this.baseUrl, this.projectApiKey);
|
|
666
|
+
}
|
|
667
|
+
get browserEvents() {
|
|
668
|
+
return this._browserEvents;
|
|
669
|
+
}
|
|
670
|
+
get datasets() {
|
|
671
|
+
return this._datasets;
|
|
672
|
+
}
|
|
673
|
+
get evals() {
|
|
674
|
+
return this._evals;
|
|
675
|
+
}
|
|
676
|
+
get evaluators() {
|
|
677
|
+
return this._evaluators;
|
|
678
|
+
}
|
|
679
|
+
get rolloutSessions() {
|
|
680
|
+
return this._rolloutSessions;
|
|
681
|
+
}
|
|
682
|
+
get sql() {
|
|
683
|
+
return this._sql;
|
|
684
|
+
}
|
|
685
|
+
get tags() {
|
|
686
|
+
return this._tags;
|
|
687
|
+
}
|
|
688
|
+
};
|
|
689
|
+
|
|
690
|
+
//#endregion
|
|
691
|
+
exports.LaminarClient = LaminarClient;
|
|
692
|
+
//# sourceMappingURL=index.cjs.map
|