@lmnr-ai/lmnr 0.4.2 → 0.4.4
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.d.mts +10 -5
- package/dist/index.d.ts +10 -5
- package/dist/index.js +22 -16
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +22 -16
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
- package/src/evaluations.ts +25 -23
- package/src/laminar.ts +13 -6
package/dist/index.d.mts
CHANGED
|
@@ -99,14 +99,19 @@ declare class Laminar {
|
|
|
99
99
|
*/
|
|
100
100
|
static run({ pipeline, inputs, env, metadata, currentSpanId, currentTraceId, }: PipelineRunRequest): Promise<PipelineRunResponse>;
|
|
101
101
|
/**
|
|
102
|
-
* Associates an event with the current span.
|
|
102
|
+
* Associates an event with the current span. If event with such name never
|
|
103
|
+
* existed, Laminar will create a new event and infer its type from the value.
|
|
104
|
+
* If the event already exists, Laminar will append the value to the event
|
|
105
|
+
* if and only if the value is of a matching type. Otherwise, the event won't
|
|
106
|
+
* be recorded. Supported types are string, number, and boolean. If the value
|
|
107
|
+
* is `null`, event is considered a boolean tag with the value of `true`.
|
|
103
108
|
*
|
|
104
109
|
* @param name - The name of the event.
|
|
105
|
-
* @param value - The value of the event. Must be a primitive type
|
|
110
|
+
* @param value - The value of the event. Must be a primitive type. If not specified, boolean true is assumed in the backend.
|
|
106
111
|
* @param timestamp - The timestamp of the event. If not specified, relies on the underlying OpenTelemetry implementation.
|
|
107
112
|
* If specified as an integer, it must be epoch nanoseconds.
|
|
108
113
|
*/
|
|
109
|
-
static event(name: string, value
|
|
114
|
+
static event(name: string, value?: AttributeValue, timestamp?: TimeInput): void;
|
|
110
115
|
/**
|
|
111
116
|
* Sends an event for evaluation to the Laminar backend.
|
|
112
117
|
*
|
|
@@ -178,7 +183,7 @@ type EvaluatorFunctionReturn = number | Record<string, number>;
|
|
|
178
183
|
* The score can be a single number or a record of string keys and number values. The latter is useful for evaluating
|
|
179
184
|
* multiple criteria in one go instead of running multiple evaluators.
|
|
180
185
|
*/
|
|
181
|
-
type EvaluatorFunction<O, T> = (output: O
|
|
186
|
+
type EvaluatorFunction<O, T> = (output: O, target: T, ...args: any[]) => EvaluatorFunctionReturn | Promise<EvaluatorFunctionReturn>;
|
|
182
187
|
interface EvaluatorConstructorProps<D, T, O> {
|
|
183
188
|
/**
|
|
184
189
|
* List of data points to evaluate. `data` is the input to the executor function, `target` is the input to the evaluator function.
|
|
@@ -187,7 +192,7 @@ interface EvaluatorConstructorProps<D, T, O> {
|
|
|
187
192
|
/**
|
|
188
193
|
* The executor function. Takes the data point + any additional arguments and returns the output to evaluate.
|
|
189
194
|
*/
|
|
190
|
-
executor: (data: D, ...args: any[]) => O
|
|
195
|
+
executor: (data: D, ...args: any[]) => O | Promise<O>;
|
|
191
196
|
/**
|
|
192
197
|
* List of evaluator functions. Each evaluator function takes the output of the executor _and_ the target data, and returns
|
|
193
198
|
* a score. The score can be a single number or a record of string keys and number values.
|
package/dist/index.d.ts
CHANGED
|
@@ -99,14 +99,19 @@ declare class Laminar {
|
|
|
99
99
|
*/
|
|
100
100
|
static run({ pipeline, inputs, env, metadata, currentSpanId, currentTraceId, }: PipelineRunRequest): Promise<PipelineRunResponse>;
|
|
101
101
|
/**
|
|
102
|
-
* Associates an event with the current span.
|
|
102
|
+
* Associates an event with the current span. If event with such name never
|
|
103
|
+
* existed, Laminar will create a new event and infer its type from the value.
|
|
104
|
+
* If the event already exists, Laminar will append the value to the event
|
|
105
|
+
* if and only if the value is of a matching type. Otherwise, the event won't
|
|
106
|
+
* be recorded. Supported types are string, number, and boolean. If the value
|
|
107
|
+
* is `null`, event is considered a boolean tag with the value of `true`.
|
|
103
108
|
*
|
|
104
109
|
* @param name - The name of the event.
|
|
105
|
-
* @param value - The value of the event. Must be a primitive type
|
|
110
|
+
* @param value - The value of the event. Must be a primitive type. If not specified, boolean true is assumed in the backend.
|
|
106
111
|
* @param timestamp - The timestamp of the event. If not specified, relies on the underlying OpenTelemetry implementation.
|
|
107
112
|
* If specified as an integer, it must be epoch nanoseconds.
|
|
108
113
|
*/
|
|
109
|
-
static event(name: string, value
|
|
114
|
+
static event(name: string, value?: AttributeValue, timestamp?: TimeInput): void;
|
|
110
115
|
/**
|
|
111
116
|
* Sends an event for evaluation to the Laminar backend.
|
|
112
117
|
*
|
|
@@ -178,7 +183,7 @@ type EvaluatorFunctionReturn = number | Record<string, number>;
|
|
|
178
183
|
* The score can be a single number or a record of string keys and number values. The latter is useful for evaluating
|
|
179
184
|
* multiple criteria in one go instead of running multiple evaluators.
|
|
180
185
|
*/
|
|
181
|
-
type EvaluatorFunction<O, T> = (output: O
|
|
186
|
+
type EvaluatorFunction<O, T> = (output: O, target: T, ...args: any[]) => EvaluatorFunctionReturn | Promise<EvaluatorFunctionReturn>;
|
|
182
187
|
interface EvaluatorConstructorProps<D, T, O> {
|
|
183
188
|
/**
|
|
184
189
|
* List of data points to evaluate. `data` is the input to the executor function, `target` is the input to the evaluator function.
|
|
@@ -187,7 +192,7 @@ interface EvaluatorConstructorProps<D, T, O> {
|
|
|
187
192
|
/**
|
|
188
193
|
* The executor function. Takes the data point + any additional arguments and returns the output to evaluate.
|
|
189
194
|
*/
|
|
190
|
-
executor: (data: D, ...args: any[]) => O
|
|
195
|
+
executor: (data: D, ...args: any[]) => O | Promise<O>;
|
|
191
196
|
/**
|
|
192
197
|
* List of evaluator functions. Each evaluator function takes the output of the executor _and_ the target data, and returns
|
|
193
198
|
* a score. The score can be a single number or a record of string keys and number values.
|
package/dist/index.js
CHANGED
|
@@ -194,10 +194,15 @@ var Laminar = class {
|
|
|
194
194
|
}
|
|
195
195
|
}
|
|
196
196
|
/**
|
|
197
|
-
* Associates an event with the current span.
|
|
197
|
+
* Associates an event with the current span. If event with such name never
|
|
198
|
+
* existed, Laminar will create a new event and infer its type from the value.
|
|
199
|
+
* If the event already exists, Laminar will append the value to the event
|
|
200
|
+
* if and only if the value is of a matching type. Otherwise, the event won't
|
|
201
|
+
* be recorded. Supported types are string, number, and boolean. If the value
|
|
202
|
+
* is `null`, event is considered a boolean tag with the value of `true`.
|
|
198
203
|
*
|
|
199
204
|
* @param name - The name of the event.
|
|
200
|
-
* @param value - The value of the event. Must be a primitive type
|
|
205
|
+
* @param value - The value of the event. Must be a primitive type. If not specified, boolean true is assumed in the backend.
|
|
201
206
|
* @param timestamp - The timestamp of the event. If not specified, relies on the underlying OpenTelemetry implementation.
|
|
202
207
|
* If specified as an integer, it must be epoch nanoseconds.
|
|
203
208
|
*/
|
|
@@ -210,9 +215,11 @@ var Laminar = class {
|
|
|
210
215
|
return;
|
|
211
216
|
}
|
|
212
217
|
const event = {
|
|
213
|
-
"lmnr.event.type": "default"
|
|
214
|
-
"lmnr.event.value": value
|
|
218
|
+
"lmnr.event.type": "default"
|
|
215
219
|
};
|
|
220
|
+
if (value !== void 0) {
|
|
221
|
+
event["lmnr.event.value"] = value;
|
|
222
|
+
}
|
|
216
223
|
currentSpan.addEvent(name, event, timestamp);
|
|
217
224
|
}
|
|
218
225
|
/**
|
|
@@ -387,23 +394,23 @@ var Evaluation = class {
|
|
|
387
394
|
*/
|
|
388
395
|
async run() {
|
|
389
396
|
const response = await Laminar.createEvaluation(this.name);
|
|
390
|
-
const batchPromises = [];
|
|
391
397
|
const length = this.data instanceof Dataset ? this.data.size() : this.data.length;
|
|
392
398
|
for (let i = 0; i < length; i += this.batchSize) {
|
|
393
399
|
const batch = this.data.slice(i, i + this.batchSize);
|
|
394
|
-
|
|
400
|
+
try {
|
|
401
|
+
await this.evaluateBatch(batch);
|
|
402
|
+
} catch (e) {
|
|
403
|
+
console.error(`Error evaluating batch: ${e}`);
|
|
404
|
+
}
|
|
395
405
|
}
|
|
396
406
|
try {
|
|
397
|
-
await Promise.all(batchPromises);
|
|
398
407
|
await Laminar.updateEvaluationStatus(response.name, "Finished");
|
|
399
|
-
console.log(`Evaluation ${response.id} complete`);
|
|
400
408
|
} catch (e) {
|
|
401
|
-
console.error(`Error
|
|
409
|
+
console.error(`Error updating evaluation status: ${e}`);
|
|
402
410
|
}
|
|
403
411
|
}
|
|
404
412
|
async evaluateBatch(batch) {
|
|
405
|
-
|
|
406
|
-
for (const datapoint of batch) {
|
|
413
|
+
const batchPromises = batch.map(async (datapoint) => {
|
|
407
414
|
const output = await this.executor(datapoint.data);
|
|
408
415
|
const target = datapoint.target;
|
|
409
416
|
let scores = {};
|
|
@@ -416,15 +423,14 @@ var Evaluation = class {
|
|
|
416
423
|
scores = { ...scores, ...value };
|
|
417
424
|
}
|
|
418
425
|
}
|
|
419
|
-
|
|
420
|
-
results.push({
|
|
426
|
+
return {
|
|
421
427
|
executorOutput: output,
|
|
422
428
|
data: datapoint.data,
|
|
423
429
|
target,
|
|
424
430
|
scores
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
;
|
|
431
|
+
};
|
|
432
|
+
});
|
|
433
|
+
const results = await Promise.all(batchPromises);
|
|
428
434
|
return Laminar.postEvaluationResults(this.name, results);
|
|
429
435
|
}
|
|
430
436
|
};
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/laminar.ts","../src/utils.ts","../src/evaluations.ts","../src/decorators.ts"],"sourcesContent":["\nexport {\n NodeInput,\n PipelineRunResponse,\n PipelineRunRequest,\n ChatMessage,\n Event,\n EvaluateEvent,\n} from './types';\n\nexport { Laminar } from './laminar';\nexport { Evaluation, Dataset, Datapoint } from './evaluations';\nexport { observe } from './decorators';\n","import { PipelineRunResponse, PipelineRunRequest, EvaluationDatapoint, EvaluationStatus } from './types';\nimport { AttributeValue, context, createContextKey, isSpanContextValid, TimeInput, trace } from '@opentelemetry/api';\nimport { initialize as traceloopInitialize } from '@traceloop/node-server-sdk'\nimport { otelSpanIdToUUID, otelTraceIdToUUID } from './utils';\n\n\n// quick patch to get the traceloop's default tracer, since their \n// `getTracer` function is not exported.\n// Another option would be to import directly \n// like so: `import { getTracer } from '@traceloop/node-server-sdk/dist/src/lib/tracing/tracing';`\n// which isn't too nice either.\nconst DEFAULT_TRACER_NAME = 'traceloop.tracer';\n\ninterface LaminarInitializeProps {\n projectApiKey?: string;\n env?: Record<string, string>;\n baseUrl?: string;\n}\n\nexport class Laminar {\n private static baseUrl: string = 'https://api.lmnr.ai';\n private static projectApiKey: string;\n private static env: Record<string, string> = {};\n private static isInitialized: boolean = false;\n\n /**\n * Initialize Laminar context across the application.\n * This method must be called before using any other Laminar methods or decorators.\n *\n * @param project_api_key - Laminar project api key. You can generate one by going\n * to the projects settings page on the Laminar dashboard.\n * If not specified, it will try to read from the LMNR_PROJECT_API_KEY environment variable.\n * @param env - Default environment passed to `run` and `evaluateEvent` requests,\n * unless overriden at request time. Usually, model provider keys are stored here.\n * @param baseUrl - Url of Laminar endpoint, or the custom open telemetry ingester.\n * If not specified, defaults to https://api.lmnr.ai. For locally hosted Laminar,\n * default setting must be http://localhost:8000.\n *\n * @throws {Error} - If project API key is not set\n */\n public static initialize({\n projectApiKey,\n env,\n baseUrl\n }: LaminarInitializeProps) {\n\n let key = projectApiKey ?? process.env.LMNR_PROJECT_API_KEY;\n if (key === undefined) {\n throw new Error(\n 'Please initialize the Laminar object with your project API key ' +\n 'or set the LMNR_PROJECT_API_KEY environment variable'\n );\n }\n this.projectApiKey = key;\n if (baseUrl) {\n this.baseUrl = baseUrl;\n }\n this.isInitialized = true;\n this.env = env ?? {};\n traceloopInitialize({\n apiKey: this.projectApiKey,\n baseUrl: this.baseUrl,\n silenceInitializationMessage: true,\n });\n }\n\n /**\n * Check if Laminar has been initialized. Utility to make sure other methods\n * are called after initialization.\n */\n public static initialized(): boolean {\n return this.isInitialized;\n }\n\n /**\n * Sets the environment that will be sent to Laminar requests.\n * \n * @param env - The environment variables to override. If not provided, the current environment will not be modified.\n */\n public static setEnv(env?: Record<string, string>) {\n if (env) {\n this.env = env;\n }\n }\n\n /**\n * Sets the project API key for authentication with Laminar.\n *\n * @param projectApiKey - The API key to be set. If not provided, the existing API key will not be modified.\n */\n public static setProjectApiKey(projectApiKey?: string) {\n if (projectApiKey) {\n this.projectApiKey = projectApiKey;\n }\n }\n\n /**\n * Runs the pipeline with the given inputs\n *\n * @param pipeline - The name of the Laminar pipeline. Pipeline must have a target version.\n * @param inputs - The inputs for the pipeline. Map from an input node name to input data.\n * @param env - The environment variables for the pipeline execution. Typically used for model provider keys.\n * @param metadata - Additional metadata for the pipeline run.\n * @param currentSpanId - The ID of the current span.\n * @param currentTraceId - The ID of the current trace.\n * @returns A promise that resolves to the response of the pipeline run.\n * @throws An error if the Laminar object is not initialized with a project API key. Or if the request fails.\n */\n public static async run({\n pipeline,\n inputs,\n env,\n metadata = {},\n currentSpanId,\n currentTraceId,\n }: PipelineRunRequest): Promise<PipelineRunResponse> {\n const currentSpan = trace.getActiveSpan();\n let parentSpanId: string | undefined;\n let traceId: string | undefined;\n if (currentSpan !== undefined && isSpanContextValid(currentSpan.spanContext())) {\n parentSpanId = currentSpanId ?? otelSpanIdToUUID(currentSpan.spanContext().spanId);\n traceId = currentTraceId ?? otelTraceIdToUUID(currentSpan.spanContext().traceId);\n } else {\n parentSpanId = currentSpanId;\n traceId = currentTraceId;\n }\n if (this.projectApiKey === undefined) {\n throw new Error(\n 'Please initialize the Laminar object with your project API key ' +\n 'or set the LMNR_PROJECT_API_KEY environment variable'\n );\n }\n const envirionment = env === undefined ? this.env : env;\n\n const response = await fetch(`${this.baseUrl}/v1/pipeline/run`, {\n method: 'POST',\n headers: this.getHeaders(),\n body: JSON.stringify({\n inputs,\n pipeline,\n env: envirionment,\n metadata,\n parentSpanId,\n traceId,\n })\n });\n\n if (!response.ok) {\n throw new Error(`Failed to run pipeline ${pipeline}. Response: ${response.statusText}`);\n }\n try {\n return await response.json() as PipelineRunResponse;\n } catch (error) {\n throw new Error(`Failed to parse response from pipeline ${pipeline}. Error: ${error}`);\n }\n }\n\n /**\n * Associates an event with the current span.\n *\n * @param name - The name of the event.\n * @param value - The value of the event. Must be a primitive type or a sequence of values of the same primitive type.\n * @param timestamp - The timestamp of the event. If not specified, relies on the underlying OpenTelemetry implementation.\n * If specified as an integer, it must be epoch nanoseconds.\n */\n public static event(\n name: string,\n value: AttributeValue,\n timestamp?: TimeInput,\n ) {\n const currentSpan = trace.getActiveSpan();\n if (currentSpan === undefined || !isSpanContextValid(currentSpan.spanContext())) {\n console.warn(\"Laminar().event()\\` called outside of span context.\" +\n ` Event '${name}' will not be recorded in the trace.` +\n \" Make sure to annotate the function with a decorator\"\n );\n return;\n }\n\n const event = {\n \"lmnr.event.type\": \"default\",\n \"lmnr.event.value\": value,\n }\n\n currentSpan.addEvent(name, event, timestamp);\n }\n\n /**\n * Sends an event for evaluation to the Laminar backend.\n * \n * @param name - The name of the event.\n * @param evaluator - The name of the pipeline that evaluates the event.\n * @param data - A map from input node name to its value in the evaluator pipeline.\n * @param env - Environment variables required to run the pipeline.\n * @param timestamp - If specified as an integer, it must be epoch nanoseconds.\n * If not specified, relies on the underlying OpenTelemetry implementation.\n */\n public static evaluateEvent(\n name: string,\n evaluator: string,\n data: Record<string, any>,\n env?: Record<string, string>,\n timestamp?: TimeInput,\n ) {\n const currentSpan = trace.getActiveSpan();\n\n if (currentSpan === undefined || !isSpanContextValid(currentSpan.spanContext())) {\n console.warn(\"Laminar().evaluateEvent()\\` called outside of span context.\" +\n ` Evaluate event '${name}' will not be recorded in the trace.` +\n \" Make sure to annotate the function with a decorator\"\n );\n return;\n }\n\n const event = {\n \"lmnr.event.type\": \"evaluate\",\n \"lmnr.event.evaluator\": evaluator,\n \"lmnr.event.data\": JSON.stringify(data),\n \"lmnr.event.env\": JSON.stringify(env ?? {}),\n }\n\n currentSpan.addEvent(name, event, timestamp);\n }\n\n /**\n * Sets the session information for the current span and returns the context to use for the following spans.\n * \n * Example:\n * ```typescript\n * import { context as contextApi } from '@opentelemetry/api';\n * import { Laminar } from '@lmnr-ai/laminar';\n * const context = Laminar.contextWithSession({ sessionId: \"1234\", userId: \"5678\" });\n * contextApi.with(context, () => {\n * // Your code here\n * });\n * ```\n * \n * @param sessionId - The session ID to associate with the span.\n * @param userId - The user ID to associate with the span.\n * @returns The updated context with the association properties.\n */\n public static contextWithSession({ sessionId, userId }: { sessionId?: string, userId?: string }) {\n\n const currentSpan = trace.getActiveSpan();\n if (currentSpan !== undefined && isSpanContextValid(currentSpan.spanContext())) {\n if (sessionId) {\n currentSpan.setAttribute(\"traceloop.association.properties.session_id\", sessionId);\n }\n if (userId) {\n currentSpan.setAttribute(\"traceloop.association.properties.user_id\", userId);\n }\n }\n let associationProperties = {};\n if (sessionId) {\n associationProperties = { ...associationProperties, \"session_id\": sessionId };\n }\n if (userId) {\n associationProperties = { ...associationProperties, \"user_id\": userId };\n }\n return context.active().setValue(createContextKey(\"association_properites\"), associationProperties);\n }\n\n public static async createEvaluation(name: string) {\n const response = await fetch(`${this.baseUrl}/v1/evaluations`, {\n method: 'POST',\n headers: this.getHeaders(),\n body: JSON.stringify({\n name,\n })\n });\n\n return await response.json();\n }\n\n public static async postEvaluationResults<D, T, O>(\n evaluationName: string,\n data: EvaluationDatapoint<D, T, O>[]\n ): Promise<void> {\n const body = JSON.stringify({\n name: evaluationName,\n points: data,\n });\n const headers = this.getHeaders();\n const url = `${this.baseUrl}/v1/evaluation-datapoints`;\n try {\n const response = await fetch(url, {\n method: \"POST\",\n headers,\n body,\n });\n if (!response.ok) {\n console.error(\"Failed to send evaluation results. Response: \");\n response.text().then((text) => console.error(text));\n }\n } catch (error) {\n console.error(\"Failed to send evaluation results. Error: \", error);\n };\n }\n\n public static async updateEvaluationStatus(\n evaluationName: string,\n status: EvaluationStatus,\n ): Promise<void> {\n const body = JSON.stringify({\n name: evaluationName,\n status,\n });\n const headers = this.getHeaders();\n const url = `${this.baseUrl}/v1/evaluations`;\n try {\n const response = await fetch(url, {\n method: \"PUT\",\n headers,\n body,\n });\n if (!response.ok) {\n console.error(\"Failed to update evaluation status. Response: \");\n response.text().then((text) => console.error(text));\n }\n } catch (error) {\n console.error(\"Failed to update evaluation status. Error: \", error);\n }\n }\n\n private static getHeaders() {\n return {\n \"Content-Type\": \"application/json\",\n \"Authorization\": `Bearer ${this.projectApiKey}`,\n };\n };\n}\n","import { v4 as uuidv4 } from 'uuid';\n\nexport type StringUUID = `${string}-${string}-${string}-${string}-${string}`;\n\nexport const newUUID = (): StringUUID => {\n // crypto.randomUUID is available in most of the modern browsers and node,\n // but is not available in \"insecure\" contexts, e.g. not https, not localhost\n // so we fallback to uuidv4 in those cases, which is less secure, but works\n // just fine.\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n return crypto.randomUUID();\n } else {\n return uuidv4() as `${string}-${string}-${string}-${string}-${string}`;\n }\n}\n\nexport const otelSpanIdToUUID = (spanId: string): string => {\n let id = spanId.toLowerCase();\n if (id.startsWith('0x')) {\n id = id.slice(2);\n }\n if (id.length !== 16) {\n console.warn(`Span ID ${spanId} is not 16 hex chars long. This is not a valid OpenTelemetry span ID.`);\n }\n\n if (!/^[0-9a-f]+$/.test(id)) {\n console.error(`Span ID ${spanId} is not a valid hex string. Generating a random UUID instead.`);\n return newUUID();\n }\n\n return id.padStart(32, '0').replace(/^(.{8})(.{4})(.{4})(.{4})(.{12})$/, '$1-$2-$3-$4-$5');\n}\n\nexport const otelTraceIdToUUID = (traceId: string): string => {\n let id = traceId.toLowerCase();\n if (id.startsWith('0x')) {\n id = id.slice(2);\n }\n if (id.length !== 32) {\n console.warn(`Trace ID ${traceId} is not 32 hex chars long. This is not a valid OpenTelemetry trace ID.`);\n }\n if (!/^[0-9a-f]+$/.test(id)) {\n console.error(`Trace ID ${traceId} is not a valid hex string. Generating a random UUID instead.`);\n return newUUID();\n }\n\n return id.replace(/^(.{8})(.{4})(.{4})(.{4})(.{12})$/, '$1-$2-$3-$4-$5');\n}\n","import { Laminar } from \"./laminar\";\nimport { CreateEvaluationResponse, EvaluationDatapoint } from \"./types\";\n\nconst DEFAULT_BATCH_SIZE = 5;\n\n/**\n * Configuration for the Evaluator\n */\ninterface EvaluatorConfig {\n batchSize?: number;\n projectApiKey?: string;\n baseUrl?: string;\n}\n\nexport abstract class Dataset<D, T> {\n public slice(start: number, end: number): Datapoint<D, T>[] {\n const result = [];\n for (let i = Math.max(start, 0); i < Math.min(end, this.size()); i++) {\n result.push(this.get(i));\n }\n return result;\n }\n public abstract size(): number;\n public abstract get(index: number): Datapoint<D, T>;\n}\n\n/**\n * Datapoint is a single data point in the evaluation. `D` is the type of the input data, `T` is the type of the target data.\n */\nexport type Datapoint<D, T> = {\n /**\n * input to the executor function. Must be a record with string keys and any values.\n */\n data: Record<string, any> & D;\n /**\n * input to the evaluator function (alongside the executor output).\n * Must be a record with string keys and any values.\n */\n target: Record<string, any> & T;\n}\n\ntype EvaluatorFunctionReturn = number | Record<string, number>;\n\n/**\n * EvaluatorFunction is a function that takes the output of the executor and the target data, and returns a score.\n * The score can be a single number or a record of string keys and number values. The latter is useful for evaluating\n * multiple criteria in one go instead of running multiple evaluators.\n */\ntype EvaluatorFunction<O, T> = (output: O | Promise<O>, target: T, ...args: any[]) => EvaluatorFunctionReturn | Promise<EvaluatorFunctionReturn>;\n\ninterface EvaluatorConstructorProps<D, T, O> {\n /**\n * List of data points to evaluate. `data` is the input to the executor function, `target` is the input to the evaluator function.\n */\n data: (Datapoint<D, T>[]) | Dataset<D, T>;\n /**\n * The executor function. Takes the data point + any additional arguments and returns the output to evaluate.\n */\n executor: (data: D, ...args: any[]) => O;\n /**\n * List of evaluator functions. Each evaluator function takes the output of the executor _and_ the target data, and returns\n * a score. The score can be a single number or a record of string keys and number values.\n * If the score is a single number, it will be named after the evaluator function. If the function is anonymous, it will be named\n * `evaluator_${index}`, where index is the index of the evaluator function in the list starting from 1.\n */\n evaluators: EvaluatorFunction<O, T>[];\n /**\n * Optional override configurations for the evaluator.\n */\n config?: EvaluatorConfig;\n}\n\nexport class Evaluation<D, T, O> {\n private name: string;\n private data: Datapoint<D, T>[] | Dataset<D, T>;\n private executor: (data: D, ...args: any[]) => O;\n private evaluators: Record<string, EvaluatorFunction<O, T>>;\n private evaluatorNames: string[];\n private batchSize: number = DEFAULT_BATCH_SIZE;\n\n /**\n * Create a new evaluation and prepare data.\n * @param name Name of the evaluation.\n * @param props.data List of data points to evaluate. `data` is the input to the executor function, `target` is the input to the evaluator function.\n * @param props.executor The executor function. Takes the data point + any additional arguments and returns the output to evaluate.\n * @param props.evaluators List of evaluator functions. Each evaluator function takes the output of the executor and the target data, and returns.\n */\n constructor(name: string, {\n data, executor, evaluators, config\n }: EvaluatorConstructorProps<D, T, O>) {\n this.name = name;\n this.data = data;\n this.executor = executor;\n this.evaluators = Object.fromEntries(evaluators.map((e, i) => [e.name.length > 0 ? e.name : `evaluator_${i + 1}`, e]));\n this.evaluatorNames = evaluators.map((e, i) => e.name.length > 0 ? e.name : `evaluator_${i + 1}`);\n if (config) {\n this.batchSize = config.batchSize ?? DEFAULT_BATCH_SIZE;\n }\n Laminar.initialize({ projectApiKey: config?.projectApiKey, baseUrl: config?.baseUrl });\n }\n\n /** \n * Runs the evaluation.\n *\n * Creates a new evaluation if no evaluation with such name exists, or adds data to an existing one otherwise.\n * Evaluates data points in batches of `batchSize`. The executor function is called on each data point\n * to get the output, and then evaluate it by each evaluator function.\n */\n public async run(): Promise<void> {\n const response = await Laminar.createEvaluation(this.name) as CreateEvaluationResponse;\n const batchPromises = [];\n const length = this.data instanceof Dataset ? this.data.size() : this.data.length;\n for (let i = 0; i < length; i += this.batchSize) {\n const batch = this.data.slice(i, i + this.batchSize);\n batchPromises.push(this.evaluateBatch(batch));\n }\n\n try {\n await Promise.all(batchPromises);\n await Laminar.updateEvaluationStatus(response.name, 'Finished');\n console.log(`Evaluation ${response.id} complete`);\n\n } catch (e) {\n console.error(`Error evaluating batch: ${e}`);\n }\n }\n\n private async evaluateBatch(batch: Datapoint<D, T>[]): Promise<void> {\n let results = [];\n for (const datapoint of batch) {\n const output = await this.executor(datapoint.data);\n const target = datapoint.target;\n\n // iterate in order of evaluators\n let scores: Record<string, EvaluatorFunctionReturn> = {};\n for (const evaluatorName of this.evaluatorNames) {\n const evaluator = this.evaluators[evaluatorName];\n const value = await evaluator(output, target);\n // if the evaluator returns a single number, use the evaluator name as the key\n if (typeof value === 'number') {\n scores[evaluatorName] = value;\n } else {\n // if the evaluator returns an object, use the object keys as the keys\n scores = { ...scores, ...value };\n }\n };\n\n results.push({\n executorOutput: output,\n data: datapoint.data,\n target,\n scores,\n } as EvaluationDatapoint<D, T, O>);\n };\n\n return Laminar.postEvaluationResults(this.name, results);\n }\n}\n","import { withTask } from '@traceloop/node-server-sdk'\nimport { Laminar } from './laminar';\n\ninterface ObserveOptions {\n name?: string;\n sessionId?: string;\n userId?: string;\n}\n\n/**\n * The main decorator entrypoint for Laminar. This is used to wrap\n * functions and methods to create spans.\n *\n * @param name - Name of the span. Function name is used if not specified.\n * @param user_id - User ID to associate with the span and the following context.\n * @param session_id - Session ID to associate with the span and the following context.\n * @returns Returns the result of the wrapped function.\n * @throws Exception - Re-throws the exception if the wrapped function throws an exception.\n * \n * @example\n * ```typescript\n * import { observe } from '@lmnr-ai/lmnr';\n * \n * await observe({ name: 'my_function' }, () => {\n * // Your code here\n * });\n */\nexport async function observe<A extends unknown[], F extends (...args: A) => ReturnType<F>>(\n {\n name,\n sessionId,\n userId,\n }: ObserveOptions, fn: F, ...args: A) {\n\n if (!Laminar.initialized()) {\n throw new Error('Laminar not initialized. Please call Laminar.initialize(projectApiKey) before using observe.');\n };\n let associationProperties = {};\n if (sessionId) {\n associationProperties = { ...associationProperties, \"session_id\": sessionId };\n }\n if (userId) {\n associationProperties = { ...associationProperties, \"user_id\": userId };\n }\n return withTask<A, F>({ name: name ?? fn.name, associationProperties }, fn, ...args);\n}\n"],"mappings":"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","names":["uuidv4","traceloopInitialize","import_node_server_sdk"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/laminar.ts","../src/utils.ts","../src/evaluations.ts","../src/decorators.ts"],"sourcesContent":["\nexport {\n NodeInput,\n PipelineRunResponse,\n PipelineRunRequest,\n ChatMessage,\n Event,\n EvaluateEvent,\n} from './types';\n\nexport { Laminar } from './laminar';\nexport { Evaluation, Dataset, Datapoint } from './evaluations';\nexport { observe } from './decorators';\n","import { PipelineRunResponse, PipelineRunRequest, EvaluationDatapoint, EvaluationStatus } from './types';\nimport { Attributes, AttributeValue, context, createContextKey, isSpanContextValid, TimeInput, trace } from '@opentelemetry/api';\nimport { initialize as traceloopInitialize } from '@traceloop/node-server-sdk'\nimport { otelSpanIdToUUID, otelTraceIdToUUID } from './utils';\n\n\n// quick patch to get the traceloop's default tracer, since their \n// `getTracer` function is not exported.\n// Another option would be to import directly \n// like so: `import { getTracer } from '@traceloop/node-server-sdk/dist/src/lib/tracing/tracing';`\n// which isn't too nice either.\nconst DEFAULT_TRACER_NAME = 'traceloop.tracer';\n\ninterface LaminarInitializeProps {\n projectApiKey?: string;\n env?: Record<string, string>;\n baseUrl?: string;\n}\n\nexport class Laminar {\n private static baseUrl: string = 'https://api.lmnr.ai';\n private static projectApiKey: string;\n private static env: Record<string, string> = {};\n private static isInitialized: boolean = false;\n\n /**\n * Initialize Laminar context across the application.\n * This method must be called before using any other Laminar methods or decorators.\n *\n * @param project_api_key - Laminar project api key. You can generate one by going\n * to the projects settings page on the Laminar dashboard.\n * If not specified, it will try to read from the LMNR_PROJECT_API_KEY environment variable.\n * @param env - Default environment passed to `run` and `evaluateEvent` requests,\n * unless overriden at request time. Usually, model provider keys are stored here.\n * @param baseUrl - Url of Laminar endpoint, or the custom open telemetry ingester.\n * If not specified, defaults to https://api.lmnr.ai. For locally hosted Laminar,\n * default setting must be http://localhost:8000.\n *\n * @throws {Error} - If project API key is not set\n */\n public static initialize({\n projectApiKey,\n env,\n baseUrl\n }: LaminarInitializeProps) {\n\n let key = projectApiKey ?? process.env.LMNR_PROJECT_API_KEY;\n if (key === undefined) {\n throw new Error(\n 'Please initialize the Laminar object with your project API key ' +\n 'or set the LMNR_PROJECT_API_KEY environment variable'\n );\n }\n this.projectApiKey = key;\n if (baseUrl) {\n this.baseUrl = baseUrl;\n }\n this.isInitialized = true;\n this.env = env ?? {};\n traceloopInitialize({\n apiKey: this.projectApiKey,\n baseUrl: this.baseUrl,\n silenceInitializationMessage: true,\n });\n }\n\n /**\n * Check if Laminar has been initialized. Utility to make sure other methods\n * are called after initialization.\n */\n public static initialized(): boolean {\n return this.isInitialized;\n }\n\n /**\n * Sets the environment that will be sent to Laminar requests.\n * \n * @param env - The environment variables to override. If not provided, the current environment will not be modified.\n */\n public static setEnv(env?: Record<string, string>) {\n if (env) {\n this.env = env;\n }\n }\n\n /**\n * Sets the project API key for authentication with Laminar.\n *\n * @param projectApiKey - The API key to be set. If not provided, the existing API key will not be modified.\n */\n public static setProjectApiKey(projectApiKey?: string) {\n if (projectApiKey) {\n this.projectApiKey = projectApiKey;\n }\n }\n\n /**\n * Runs the pipeline with the given inputs\n *\n * @param pipeline - The name of the Laminar pipeline. Pipeline must have a target version.\n * @param inputs - The inputs for the pipeline. Map from an input node name to input data.\n * @param env - The environment variables for the pipeline execution. Typically used for model provider keys.\n * @param metadata - Additional metadata for the pipeline run.\n * @param currentSpanId - The ID of the current span.\n * @param currentTraceId - The ID of the current trace.\n * @returns A promise that resolves to the response of the pipeline run.\n * @throws An error if the Laminar object is not initialized with a project API key. Or if the request fails.\n */\n public static async run({\n pipeline,\n inputs,\n env,\n metadata = {},\n currentSpanId,\n currentTraceId,\n }: PipelineRunRequest): Promise<PipelineRunResponse> {\n const currentSpan = trace.getActiveSpan();\n let parentSpanId: string | undefined;\n let traceId: string | undefined;\n if (currentSpan !== undefined && isSpanContextValid(currentSpan.spanContext())) {\n parentSpanId = currentSpanId ?? otelSpanIdToUUID(currentSpan.spanContext().spanId);\n traceId = currentTraceId ?? otelTraceIdToUUID(currentSpan.spanContext().traceId);\n } else {\n parentSpanId = currentSpanId;\n traceId = currentTraceId;\n }\n if (this.projectApiKey === undefined) {\n throw new Error(\n 'Please initialize the Laminar object with your project API key ' +\n 'or set the LMNR_PROJECT_API_KEY environment variable'\n );\n }\n const envirionment = env === undefined ? this.env : env;\n\n const response = await fetch(`${this.baseUrl}/v1/pipeline/run`, {\n method: 'POST',\n headers: this.getHeaders(),\n body: JSON.stringify({\n inputs,\n pipeline,\n env: envirionment,\n metadata,\n parentSpanId,\n traceId,\n })\n });\n\n if (!response.ok) {\n throw new Error(`Failed to run pipeline ${pipeline}. Response: ${response.statusText}`);\n }\n try {\n return await response.json() as PipelineRunResponse;\n } catch (error) {\n throw new Error(`Failed to parse response from pipeline ${pipeline}. Error: ${error}`);\n }\n }\n\n /**\n * Associates an event with the current span. If event with such name never\n * existed, Laminar will create a new event and infer its type from the value.\n * If the event already exists, Laminar will append the value to the event\n * if and only if the value is of a matching type. Otherwise, the event won't\n * be recorded. Supported types are string, number, and boolean. If the value\n * is `null`, event is considered a boolean tag with the value of `true`.\n *\n * @param name - The name of the event.\n * @param value - The value of the event. Must be a primitive type. If not specified, boolean true is assumed in the backend.\n * @param timestamp - The timestamp of the event. If not specified, relies on the underlying OpenTelemetry implementation.\n * If specified as an integer, it must be epoch nanoseconds.\n */\n public static event(\n name: string,\n value?: AttributeValue,\n timestamp?: TimeInput,\n ) {\n const currentSpan = trace.getActiveSpan();\n if (currentSpan === undefined || !isSpanContextValid(currentSpan.spanContext())) {\n console.warn(\"Laminar().event()\\` called outside of span context.\" +\n ` Event '${name}' will not be recorded in the trace.` +\n \" Make sure to annotate the function with a decorator\"\n );\n return;\n }\n\n const event: Attributes = {\n \"lmnr.event.type\": \"default\",\n }\n if (value !== undefined) {\n event[\"lmnr.event.value\"] = value;\n }\n\n currentSpan.addEvent(name, event, timestamp);\n }\n\n /**\n * Sends an event for evaluation to the Laminar backend.\n * \n * @param name - The name of the event.\n * @param evaluator - The name of the pipeline that evaluates the event.\n * @param data - A map from input node name to its value in the evaluator pipeline.\n * @param env - Environment variables required to run the pipeline.\n * @param timestamp - If specified as an integer, it must be epoch nanoseconds.\n * If not specified, relies on the underlying OpenTelemetry implementation.\n */\n public static evaluateEvent(\n name: string,\n evaluator: string,\n data: Record<string, any>,\n env?: Record<string, string>,\n timestamp?: TimeInput,\n ) {\n const currentSpan = trace.getActiveSpan();\n\n if (currentSpan === undefined || !isSpanContextValid(currentSpan.spanContext())) {\n console.warn(\"Laminar().evaluateEvent()\\` called outside of span context.\" +\n ` Evaluate event '${name}' will not be recorded in the trace.` +\n \" Make sure to annotate the function with a decorator\"\n );\n return;\n }\n\n const event = {\n \"lmnr.event.type\": \"evaluate\",\n \"lmnr.event.evaluator\": evaluator,\n \"lmnr.event.data\": JSON.stringify(data),\n \"lmnr.event.env\": JSON.stringify(env ?? {}),\n }\n\n currentSpan.addEvent(name, event, timestamp);\n }\n\n /**\n * Sets the session information for the current span and returns the context to use for the following spans.\n * \n * Example:\n * ```typescript\n * import { context as contextApi } from '@opentelemetry/api';\n * import { Laminar } from '@lmnr-ai/laminar';\n * const context = Laminar.contextWithSession({ sessionId: \"1234\", userId: \"5678\" });\n * contextApi.with(context, () => {\n * // Your code here\n * });\n * ```\n * \n * @param sessionId - The session ID to associate with the span.\n * @param userId - The user ID to associate with the span.\n * @returns The updated context with the association properties.\n */\n public static contextWithSession({ sessionId, userId }: { sessionId?: string, userId?: string }) {\n\n const currentSpan = trace.getActiveSpan();\n if (currentSpan !== undefined && isSpanContextValid(currentSpan.spanContext())) {\n if (sessionId) {\n currentSpan.setAttribute(\"traceloop.association.properties.session_id\", sessionId);\n }\n if (userId) {\n currentSpan.setAttribute(\"traceloop.association.properties.user_id\", userId);\n }\n }\n let associationProperties = {};\n if (sessionId) {\n associationProperties = { ...associationProperties, \"session_id\": sessionId };\n }\n if (userId) {\n associationProperties = { ...associationProperties, \"user_id\": userId };\n }\n return context.active().setValue(createContextKey(\"association_properites\"), associationProperties);\n }\n\n public static async createEvaluation(name: string) {\n const response = await fetch(`${this.baseUrl}/v1/evaluations`, {\n method: 'POST',\n headers: this.getHeaders(),\n body: JSON.stringify({\n name,\n })\n });\n\n return await response.json();\n }\n\n public static async postEvaluationResults<D, T, O>(\n evaluationName: string,\n data: EvaluationDatapoint<D, T, O>[]\n ): Promise<void> {\n const body = JSON.stringify({\n name: evaluationName,\n points: data,\n });\n const headers = this.getHeaders();\n const url = `${this.baseUrl}/v1/evaluation-datapoints`;\n try {\n const response = await fetch(url, {\n method: \"POST\",\n headers,\n body,\n });\n if (!response.ok) {\n console.error(\"Failed to send evaluation results. Response: \");\n response.text().then((text) => console.error(text));\n }\n } catch (error) {\n console.error(\"Failed to send evaluation results. Error: \", error);\n };\n }\n\n public static async updateEvaluationStatus(\n evaluationName: string,\n status: EvaluationStatus,\n ): Promise<void> {\n const body = JSON.stringify({\n name: evaluationName,\n status,\n });\n const headers = this.getHeaders();\n const url = `${this.baseUrl}/v1/evaluations`;\n try {\n const response = await fetch(url, {\n method: \"PUT\",\n headers,\n body,\n });\n if (!response.ok) {\n console.error(\"Failed to update evaluation status. Response: \");\n response.text().then((text) => console.error(text));\n }\n } catch (error) {\n console.error(\"Failed to update evaluation status. Error: \", error);\n }\n }\n\n private static getHeaders() {\n return {\n \"Content-Type\": \"application/json\",\n \"Authorization\": `Bearer ${this.projectApiKey}`,\n };\n };\n}\n","import { v4 as uuidv4 } from 'uuid';\n\nexport type StringUUID = `${string}-${string}-${string}-${string}-${string}`;\n\nexport const newUUID = (): StringUUID => {\n // crypto.randomUUID is available in most of the modern browsers and node,\n // but is not available in \"insecure\" contexts, e.g. not https, not localhost\n // so we fallback to uuidv4 in those cases, which is less secure, but works\n // just fine.\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n return crypto.randomUUID();\n } else {\n return uuidv4() as `${string}-${string}-${string}-${string}-${string}`;\n }\n}\n\nexport const otelSpanIdToUUID = (spanId: string): string => {\n let id = spanId.toLowerCase();\n if (id.startsWith('0x')) {\n id = id.slice(2);\n }\n if (id.length !== 16) {\n console.warn(`Span ID ${spanId} is not 16 hex chars long. This is not a valid OpenTelemetry span ID.`);\n }\n\n if (!/^[0-9a-f]+$/.test(id)) {\n console.error(`Span ID ${spanId} is not a valid hex string. Generating a random UUID instead.`);\n return newUUID();\n }\n\n return id.padStart(32, '0').replace(/^(.{8})(.{4})(.{4})(.{4})(.{12})$/, '$1-$2-$3-$4-$5');\n}\n\nexport const otelTraceIdToUUID = (traceId: string): string => {\n let id = traceId.toLowerCase();\n if (id.startsWith('0x')) {\n id = id.slice(2);\n }\n if (id.length !== 32) {\n console.warn(`Trace ID ${traceId} is not 32 hex chars long. This is not a valid OpenTelemetry trace ID.`);\n }\n if (!/^[0-9a-f]+$/.test(id)) {\n console.error(`Trace ID ${traceId} is not a valid hex string. Generating a random UUID instead.`);\n return newUUID();\n }\n\n return id.replace(/^(.{8})(.{4})(.{4})(.{4})(.{12})$/, '$1-$2-$3-$4-$5');\n}\n","import { Laminar } from \"./laminar\";\nimport { CreateEvaluationResponse, EvaluationDatapoint } from \"./types\";\n\nconst DEFAULT_BATCH_SIZE = 5;\n\n/**\n * Configuration for the Evaluator\n */\ninterface EvaluatorConfig {\n batchSize?: number;\n projectApiKey?: string;\n baseUrl?: string;\n}\n\nexport abstract class Dataset<D, T> {\n public slice(start: number, end: number): Datapoint<D, T>[] {\n const result = [];\n for (let i = Math.max(start, 0); i < Math.min(end, this.size()); i++) {\n result.push(this.get(i));\n }\n return result;\n }\n public abstract size(): number;\n public abstract get(index: number): Datapoint<D, T>;\n}\n\n/**\n * Datapoint is a single data point in the evaluation. `D` is the type of the input data, `T` is the type of the target data.\n */\nexport type Datapoint<D, T> = {\n /**\n * input to the executor function. Must be a record with string keys and any values.\n */\n data: Record<string, any> & D;\n /**\n * input to the evaluator function (alongside the executor output).\n * Must be a record with string keys and any values.\n */\n target: Record<string, any> & T;\n}\n\ntype EvaluatorFunctionReturn = number | Record<string, number>;\n\n/**\n * EvaluatorFunction is a function that takes the output of the executor and the target data, and returns a score.\n * The score can be a single number or a record of string keys and number values. The latter is useful for evaluating\n * multiple criteria in one go instead of running multiple evaluators.\n */\ntype EvaluatorFunction<O, T> = (output: O, target: T, ...args: any[]) => EvaluatorFunctionReturn | Promise<EvaluatorFunctionReturn>;\n\ninterface EvaluatorConstructorProps<D, T, O> {\n /**\n * List of data points to evaluate. `data` is the input to the executor function, `target` is the input to the evaluator function.\n */\n data: (Datapoint<D, T>[]) | Dataset<D, T>;\n /**\n * The executor function. Takes the data point + any additional arguments and returns the output to evaluate.\n */\n executor: (data: D, ...args: any[]) => O | Promise<O>;\n /**\n * List of evaluator functions. Each evaluator function takes the output of the executor _and_ the target data, and returns\n * a score. The score can be a single number or a record of string keys and number values.\n * If the score is a single number, it will be named after the evaluator function. If the function is anonymous, it will be named\n * `evaluator_${index}`, where index is the index of the evaluator function in the list starting from 1.\n */\n evaluators: EvaluatorFunction<O, T>[];\n /**\n * Optional override configurations for the evaluator.\n */\n config?: EvaluatorConfig;\n}\n\nexport class Evaluation<D, T, O> {\n private name: string;\n private data: Datapoint<D, T>[] | Dataset<D, T>;\n private executor: (data: D, ...args: any[]) => O | Promise<O>;\n private evaluators: Record<string, EvaluatorFunction<O, T>>;\n private evaluatorNames: string[];\n private batchSize: number = DEFAULT_BATCH_SIZE;\n\n /**\n * Create a new evaluation and prepare data.\n * @param name Name of the evaluation.\n * @param props.data List of data points to evaluate. `data` is the input to the executor function, `target` is the input to the evaluator function.\n * @param props.executor The executor function. Takes the data point + any additional arguments and returns the output to evaluate.\n * @param props.evaluators List of evaluator functions. Each evaluator function takes the output of the executor and the target data, and returns.\n */\n constructor(name: string, {\n data, executor, evaluators, config\n }: EvaluatorConstructorProps<D, T, O>) {\n this.name = name;\n this.data = data;\n this.executor = executor;\n this.evaluators = Object.fromEntries(evaluators.map((e, i) => [e.name.length > 0 ? e.name : `evaluator_${i + 1}`, e]));\n this.evaluatorNames = evaluators.map((e, i) => e.name.length > 0 ? e.name : `evaluator_${i + 1}`);\n if (config) {\n this.batchSize = config.batchSize ?? DEFAULT_BATCH_SIZE;\n }\n Laminar.initialize({ projectApiKey: config?.projectApiKey, baseUrl: config?.baseUrl });\n }\n\n /** \n * Runs the evaluation.\n *\n * Creates a new evaluation if no evaluation with such name exists, or adds data to an existing one otherwise.\n * Evaluates data points in batches of `batchSize`. The executor function is called on each data point\n * to get the output, and then evaluate it by each evaluator function.\n */\n public async run(): Promise<void> {\n const response = await Laminar.createEvaluation(this.name) as CreateEvaluationResponse;\n const length = this.data instanceof Dataset ? this.data.size() : this.data.length;\n for (let i = 0; i < length; i += this.batchSize) {\n const batch = this.data.slice(i, i + this.batchSize);\n try {\n await this.evaluateBatch(batch);\n } catch (e) {\n console.error(`Error evaluating batch: ${e}`);\n }\n }\n try {\n // After all batches are completed, update the evaluation status\n await Laminar.updateEvaluationStatus(response.name, 'Finished');\n } catch (e) {\n console.error(`Error updating evaluation status: ${e}`);\n }\n }\n \n private async evaluateBatch(batch: Datapoint<D, T>[]): Promise<void> {\n const batchPromises = batch.map(async (datapoint) => {\n const output = await this.executor(datapoint.data);\n const target = datapoint.target;\n \n let scores: Record<string, EvaluatorFunctionReturn> = {};\n for (const evaluatorName of this.evaluatorNames) {\n const evaluator = this.evaluators[evaluatorName];\n const value = await evaluator(output, target);\n \n // If the evaluator returns a single number, use the evaluator name as the key\n if (typeof value === 'number') {\n scores[evaluatorName] = value;\n } else {\n // If the evaluator returns an object, merge its keys with the existing scores (flatten)\n scores = { ...scores, ...value };\n }\n }\n \n return {\n executorOutput: output,\n data: datapoint.data,\n target,\n scores,\n } as EvaluationDatapoint<D, T, O>;\n });\n \n const results = await Promise.all(batchPromises);\n \n return Laminar.postEvaluationResults(this.name, results);\n }\n \n}\n","import { withTask } from '@traceloop/node-server-sdk'\nimport { Laminar } from './laminar';\n\ninterface ObserveOptions {\n name?: string;\n sessionId?: string;\n userId?: string;\n}\n\n/**\n * The main decorator entrypoint for Laminar. This is used to wrap\n * functions and methods to create spans.\n *\n * @param name - Name of the span. Function name is used if not specified.\n * @param user_id - User ID to associate with the span and the following context.\n * @param session_id - Session ID to associate with the span and the following context.\n * @returns Returns the result of the wrapped function.\n * @throws Exception - Re-throws the exception if the wrapped function throws an exception.\n * \n * @example\n * ```typescript\n * import { observe } from '@lmnr-ai/lmnr';\n * \n * await observe({ name: 'my_function' }, () => {\n * // Your code here\n * });\n */\nexport async function observe<A extends unknown[], F extends (...args: A) => ReturnType<F>>(\n {\n name,\n sessionId,\n userId,\n }: ObserveOptions, fn: F, ...args: A) {\n\n if (!Laminar.initialized()) {\n throw new Error('Laminar not initialized. Please call Laminar.initialize(projectApiKey) before using observe.');\n };\n let associationProperties = {};\n if (sessionId) {\n associationProperties = { ...associationProperties, \"session_id\": sessionId };\n }\n if (userId) {\n associationProperties = { ...associationProperties, \"user_id\": userId };\n }\n return withTask<A, F>({ name: name ?? fn.name, associationProperties }, fn, ...args);\n}\n"],"mappings":"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","names":["uuidv4","traceloopInitialize","import_node_server_sdk"]}
|
package/dist/index.mjs
CHANGED
|
@@ -165,10 +165,15 @@ var Laminar = class {
|
|
|
165
165
|
}
|
|
166
166
|
}
|
|
167
167
|
/**
|
|
168
|
-
* Associates an event with the current span.
|
|
168
|
+
* Associates an event with the current span. If event with such name never
|
|
169
|
+
* existed, Laminar will create a new event and infer its type from the value.
|
|
170
|
+
* If the event already exists, Laminar will append the value to the event
|
|
171
|
+
* if and only if the value is of a matching type. Otherwise, the event won't
|
|
172
|
+
* be recorded. Supported types are string, number, and boolean. If the value
|
|
173
|
+
* is `null`, event is considered a boolean tag with the value of `true`.
|
|
169
174
|
*
|
|
170
175
|
* @param name - The name of the event.
|
|
171
|
-
* @param value - The value of the event. Must be a primitive type
|
|
176
|
+
* @param value - The value of the event. Must be a primitive type. If not specified, boolean true is assumed in the backend.
|
|
172
177
|
* @param timestamp - The timestamp of the event. If not specified, relies on the underlying OpenTelemetry implementation.
|
|
173
178
|
* If specified as an integer, it must be epoch nanoseconds.
|
|
174
179
|
*/
|
|
@@ -181,9 +186,11 @@ var Laminar = class {
|
|
|
181
186
|
return;
|
|
182
187
|
}
|
|
183
188
|
const event = {
|
|
184
|
-
"lmnr.event.type": "default"
|
|
185
|
-
"lmnr.event.value": value
|
|
189
|
+
"lmnr.event.type": "default"
|
|
186
190
|
};
|
|
191
|
+
if (value !== void 0) {
|
|
192
|
+
event["lmnr.event.value"] = value;
|
|
193
|
+
}
|
|
187
194
|
currentSpan.addEvent(name, event, timestamp);
|
|
188
195
|
}
|
|
189
196
|
/**
|
|
@@ -358,23 +365,23 @@ var Evaluation = class {
|
|
|
358
365
|
*/
|
|
359
366
|
async run() {
|
|
360
367
|
const response = await Laminar.createEvaluation(this.name);
|
|
361
|
-
const batchPromises = [];
|
|
362
368
|
const length = this.data instanceof Dataset ? this.data.size() : this.data.length;
|
|
363
369
|
for (let i = 0; i < length; i += this.batchSize) {
|
|
364
370
|
const batch = this.data.slice(i, i + this.batchSize);
|
|
365
|
-
|
|
371
|
+
try {
|
|
372
|
+
await this.evaluateBatch(batch);
|
|
373
|
+
} catch (e) {
|
|
374
|
+
console.error(`Error evaluating batch: ${e}`);
|
|
375
|
+
}
|
|
366
376
|
}
|
|
367
377
|
try {
|
|
368
|
-
await Promise.all(batchPromises);
|
|
369
378
|
await Laminar.updateEvaluationStatus(response.name, "Finished");
|
|
370
|
-
console.log(`Evaluation ${response.id} complete`);
|
|
371
379
|
} catch (e) {
|
|
372
|
-
console.error(`Error
|
|
380
|
+
console.error(`Error updating evaluation status: ${e}`);
|
|
373
381
|
}
|
|
374
382
|
}
|
|
375
383
|
async evaluateBatch(batch) {
|
|
376
|
-
|
|
377
|
-
for (const datapoint of batch) {
|
|
384
|
+
const batchPromises = batch.map(async (datapoint) => {
|
|
378
385
|
const output = await this.executor(datapoint.data);
|
|
379
386
|
const target = datapoint.target;
|
|
380
387
|
let scores = {};
|
|
@@ -387,15 +394,14 @@ var Evaluation = class {
|
|
|
387
394
|
scores = { ...scores, ...value };
|
|
388
395
|
}
|
|
389
396
|
}
|
|
390
|
-
|
|
391
|
-
results.push({
|
|
397
|
+
return {
|
|
392
398
|
executorOutput: output,
|
|
393
399
|
data: datapoint.data,
|
|
394
400
|
target,
|
|
395
401
|
scores
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
;
|
|
402
|
+
};
|
|
403
|
+
});
|
|
404
|
+
const results = await Promise.all(batchPromises);
|
|
399
405
|
return Laminar.postEvaluationResults(this.name, results);
|
|
400
406
|
}
|
|
401
407
|
};
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/laminar.ts","../src/utils.ts","../src/evaluations.ts","../src/decorators.ts"],"sourcesContent":["import { PipelineRunResponse, PipelineRunRequest, EvaluationDatapoint, EvaluationStatus } from './types';\nimport { AttributeValue, context, createContextKey, isSpanContextValid, TimeInput, trace } from '@opentelemetry/api';\nimport { initialize as traceloopInitialize } from '@traceloop/node-server-sdk'\nimport { otelSpanIdToUUID, otelTraceIdToUUID } from './utils';\n\n\n// quick patch to get the traceloop's default tracer, since their \n// `getTracer` function is not exported.\n// Another option would be to import directly \n// like so: `import { getTracer } from '@traceloop/node-server-sdk/dist/src/lib/tracing/tracing';`\n// which isn't too nice either.\nconst DEFAULT_TRACER_NAME = 'traceloop.tracer';\n\ninterface LaminarInitializeProps {\n projectApiKey?: string;\n env?: Record<string, string>;\n baseUrl?: string;\n}\n\nexport class Laminar {\n private static baseUrl: string = 'https://api.lmnr.ai';\n private static projectApiKey: string;\n private static env: Record<string, string> = {};\n private static isInitialized: boolean = false;\n\n /**\n * Initialize Laminar context across the application.\n * This method must be called before using any other Laminar methods or decorators.\n *\n * @param project_api_key - Laminar project api key. You can generate one by going\n * to the projects settings page on the Laminar dashboard.\n * If not specified, it will try to read from the LMNR_PROJECT_API_KEY environment variable.\n * @param env - Default environment passed to `run` and `evaluateEvent` requests,\n * unless overriden at request time. Usually, model provider keys are stored here.\n * @param baseUrl - Url of Laminar endpoint, or the custom open telemetry ingester.\n * If not specified, defaults to https://api.lmnr.ai. For locally hosted Laminar,\n * default setting must be http://localhost:8000.\n *\n * @throws {Error} - If project API key is not set\n */\n public static initialize({\n projectApiKey,\n env,\n baseUrl\n }: LaminarInitializeProps) {\n\n let key = projectApiKey ?? process.env.LMNR_PROJECT_API_KEY;\n if (key === undefined) {\n throw new Error(\n 'Please initialize the Laminar object with your project API key ' +\n 'or set the LMNR_PROJECT_API_KEY environment variable'\n );\n }\n this.projectApiKey = key;\n if (baseUrl) {\n this.baseUrl = baseUrl;\n }\n this.isInitialized = true;\n this.env = env ?? {};\n traceloopInitialize({\n apiKey: this.projectApiKey,\n baseUrl: this.baseUrl,\n silenceInitializationMessage: true,\n });\n }\n\n /**\n * Check if Laminar has been initialized. Utility to make sure other methods\n * are called after initialization.\n */\n public static initialized(): boolean {\n return this.isInitialized;\n }\n\n /**\n * Sets the environment that will be sent to Laminar requests.\n * \n * @param env - The environment variables to override. If not provided, the current environment will not be modified.\n */\n public static setEnv(env?: Record<string, string>) {\n if (env) {\n this.env = env;\n }\n }\n\n /**\n * Sets the project API key for authentication with Laminar.\n *\n * @param projectApiKey - The API key to be set. If not provided, the existing API key will not be modified.\n */\n public static setProjectApiKey(projectApiKey?: string) {\n if (projectApiKey) {\n this.projectApiKey = projectApiKey;\n }\n }\n\n /**\n * Runs the pipeline with the given inputs\n *\n * @param pipeline - The name of the Laminar pipeline. Pipeline must have a target version.\n * @param inputs - The inputs for the pipeline. Map from an input node name to input data.\n * @param env - The environment variables for the pipeline execution. Typically used for model provider keys.\n * @param metadata - Additional metadata for the pipeline run.\n * @param currentSpanId - The ID of the current span.\n * @param currentTraceId - The ID of the current trace.\n * @returns A promise that resolves to the response of the pipeline run.\n * @throws An error if the Laminar object is not initialized with a project API key. Or if the request fails.\n */\n public static async run({\n pipeline,\n inputs,\n env,\n metadata = {},\n currentSpanId,\n currentTraceId,\n }: PipelineRunRequest): Promise<PipelineRunResponse> {\n const currentSpan = trace.getActiveSpan();\n let parentSpanId: string | undefined;\n let traceId: string | undefined;\n if (currentSpan !== undefined && isSpanContextValid(currentSpan.spanContext())) {\n parentSpanId = currentSpanId ?? otelSpanIdToUUID(currentSpan.spanContext().spanId);\n traceId = currentTraceId ?? otelTraceIdToUUID(currentSpan.spanContext().traceId);\n } else {\n parentSpanId = currentSpanId;\n traceId = currentTraceId;\n }\n if (this.projectApiKey === undefined) {\n throw new Error(\n 'Please initialize the Laminar object with your project API key ' +\n 'or set the LMNR_PROJECT_API_KEY environment variable'\n );\n }\n const envirionment = env === undefined ? this.env : env;\n\n const response = await fetch(`${this.baseUrl}/v1/pipeline/run`, {\n method: 'POST',\n headers: this.getHeaders(),\n body: JSON.stringify({\n inputs,\n pipeline,\n env: envirionment,\n metadata,\n parentSpanId,\n traceId,\n })\n });\n\n if (!response.ok) {\n throw new Error(`Failed to run pipeline ${pipeline}. Response: ${response.statusText}`);\n }\n try {\n return await response.json() as PipelineRunResponse;\n } catch (error) {\n throw new Error(`Failed to parse response from pipeline ${pipeline}. Error: ${error}`);\n }\n }\n\n /**\n * Associates an event with the current span.\n *\n * @param name - The name of the event.\n * @param value - The value of the event. Must be a primitive type or a sequence of values of the same primitive type.\n * @param timestamp - The timestamp of the event. If not specified, relies on the underlying OpenTelemetry implementation.\n * If specified as an integer, it must be epoch nanoseconds.\n */\n public static event(\n name: string,\n value: AttributeValue,\n timestamp?: TimeInput,\n ) {\n const currentSpan = trace.getActiveSpan();\n if (currentSpan === undefined || !isSpanContextValid(currentSpan.spanContext())) {\n console.warn(\"Laminar().event()\\` called outside of span context.\" +\n ` Event '${name}' will not be recorded in the trace.` +\n \" Make sure to annotate the function with a decorator\"\n );\n return;\n }\n\n const event = {\n \"lmnr.event.type\": \"default\",\n \"lmnr.event.value\": value,\n }\n\n currentSpan.addEvent(name, event, timestamp);\n }\n\n /**\n * Sends an event for evaluation to the Laminar backend.\n * \n * @param name - The name of the event.\n * @param evaluator - The name of the pipeline that evaluates the event.\n * @param data - A map from input node name to its value in the evaluator pipeline.\n * @param env - Environment variables required to run the pipeline.\n * @param timestamp - If specified as an integer, it must be epoch nanoseconds.\n * If not specified, relies on the underlying OpenTelemetry implementation.\n */\n public static evaluateEvent(\n name: string,\n evaluator: string,\n data: Record<string, any>,\n env?: Record<string, string>,\n timestamp?: TimeInput,\n ) {\n const currentSpan = trace.getActiveSpan();\n\n if (currentSpan === undefined || !isSpanContextValid(currentSpan.spanContext())) {\n console.warn(\"Laminar().evaluateEvent()\\` called outside of span context.\" +\n ` Evaluate event '${name}' will not be recorded in the trace.` +\n \" Make sure to annotate the function with a decorator\"\n );\n return;\n }\n\n const event = {\n \"lmnr.event.type\": \"evaluate\",\n \"lmnr.event.evaluator\": evaluator,\n \"lmnr.event.data\": JSON.stringify(data),\n \"lmnr.event.env\": JSON.stringify(env ?? {}),\n }\n\n currentSpan.addEvent(name, event, timestamp);\n }\n\n /**\n * Sets the session information for the current span and returns the context to use for the following spans.\n * \n * Example:\n * ```typescript\n * import { context as contextApi } from '@opentelemetry/api';\n * import { Laminar } from '@lmnr-ai/laminar';\n * const context = Laminar.contextWithSession({ sessionId: \"1234\", userId: \"5678\" });\n * contextApi.with(context, () => {\n * // Your code here\n * });\n * ```\n * \n * @param sessionId - The session ID to associate with the span.\n * @param userId - The user ID to associate with the span.\n * @returns The updated context with the association properties.\n */\n public static contextWithSession({ sessionId, userId }: { sessionId?: string, userId?: string }) {\n\n const currentSpan = trace.getActiveSpan();\n if (currentSpan !== undefined && isSpanContextValid(currentSpan.spanContext())) {\n if (sessionId) {\n currentSpan.setAttribute(\"traceloop.association.properties.session_id\", sessionId);\n }\n if (userId) {\n currentSpan.setAttribute(\"traceloop.association.properties.user_id\", userId);\n }\n }\n let associationProperties = {};\n if (sessionId) {\n associationProperties = { ...associationProperties, \"session_id\": sessionId };\n }\n if (userId) {\n associationProperties = { ...associationProperties, \"user_id\": userId };\n }\n return context.active().setValue(createContextKey(\"association_properites\"), associationProperties);\n }\n\n public static async createEvaluation(name: string) {\n const response = await fetch(`${this.baseUrl}/v1/evaluations`, {\n method: 'POST',\n headers: this.getHeaders(),\n body: JSON.stringify({\n name,\n })\n });\n\n return await response.json();\n }\n\n public static async postEvaluationResults<D, T, O>(\n evaluationName: string,\n data: EvaluationDatapoint<D, T, O>[]\n ): Promise<void> {\n const body = JSON.stringify({\n name: evaluationName,\n points: data,\n });\n const headers = this.getHeaders();\n const url = `${this.baseUrl}/v1/evaluation-datapoints`;\n try {\n const response = await fetch(url, {\n method: \"POST\",\n headers,\n body,\n });\n if (!response.ok) {\n console.error(\"Failed to send evaluation results. Response: \");\n response.text().then((text) => console.error(text));\n }\n } catch (error) {\n console.error(\"Failed to send evaluation results. Error: \", error);\n };\n }\n\n public static async updateEvaluationStatus(\n evaluationName: string,\n status: EvaluationStatus,\n ): Promise<void> {\n const body = JSON.stringify({\n name: evaluationName,\n status,\n });\n const headers = this.getHeaders();\n const url = `${this.baseUrl}/v1/evaluations`;\n try {\n const response = await fetch(url, {\n method: \"PUT\",\n headers,\n body,\n });\n if (!response.ok) {\n console.error(\"Failed to update evaluation status. Response: \");\n response.text().then((text) => console.error(text));\n }\n } catch (error) {\n console.error(\"Failed to update evaluation status. Error: \", error);\n }\n }\n\n private static getHeaders() {\n return {\n \"Content-Type\": \"application/json\",\n \"Authorization\": `Bearer ${this.projectApiKey}`,\n };\n };\n}\n","import { v4 as uuidv4 } from 'uuid';\n\nexport type StringUUID = `${string}-${string}-${string}-${string}-${string}`;\n\nexport const newUUID = (): StringUUID => {\n // crypto.randomUUID is available in most of the modern browsers and node,\n // but is not available in \"insecure\" contexts, e.g. not https, not localhost\n // so we fallback to uuidv4 in those cases, which is less secure, but works\n // just fine.\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n return crypto.randomUUID();\n } else {\n return uuidv4() as `${string}-${string}-${string}-${string}-${string}`;\n }\n}\n\nexport const otelSpanIdToUUID = (spanId: string): string => {\n let id = spanId.toLowerCase();\n if (id.startsWith('0x')) {\n id = id.slice(2);\n }\n if (id.length !== 16) {\n console.warn(`Span ID ${spanId} is not 16 hex chars long. This is not a valid OpenTelemetry span ID.`);\n }\n\n if (!/^[0-9a-f]+$/.test(id)) {\n console.error(`Span ID ${spanId} is not a valid hex string. Generating a random UUID instead.`);\n return newUUID();\n }\n\n return id.padStart(32, '0').replace(/^(.{8})(.{4})(.{4})(.{4})(.{12})$/, '$1-$2-$3-$4-$5');\n}\n\nexport const otelTraceIdToUUID = (traceId: string): string => {\n let id = traceId.toLowerCase();\n if (id.startsWith('0x')) {\n id = id.slice(2);\n }\n if (id.length !== 32) {\n console.warn(`Trace ID ${traceId} is not 32 hex chars long. This is not a valid OpenTelemetry trace ID.`);\n }\n if (!/^[0-9a-f]+$/.test(id)) {\n console.error(`Trace ID ${traceId} is not a valid hex string. Generating a random UUID instead.`);\n return newUUID();\n }\n\n return id.replace(/^(.{8})(.{4})(.{4})(.{4})(.{12})$/, '$1-$2-$3-$4-$5');\n}\n","import { Laminar } from \"./laminar\";\nimport { CreateEvaluationResponse, EvaluationDatapoint } from \"./types\";\n\nconst DEFAULT_BATCH_SIZE = 5;\n\n/**\n * Configuration for the Evaluator\n */\ninterface EvaluatorConfig {\n batchSize?: number;\n projectApiKey?: string;\n baseUrl?: string;\n}\n\nexport abstract class Dataset<D, T> {\n public slice(start: number, end: number): Datapoint<D, T>[] {\n const result = [];\n for (let i = Math.max(start, 0); i < Math.min(end, this.size()); i++) {\n result.push(this.get(i));\n }\n return result;\n }\n public abstract size(): number;\n public abstract get(index: number): Datapoint<D, T>;\n}\n\n/**\n * Datapoint is a single data point in the evaluation. `D` is the type of the input data, `T` is the type of the target data.\n */\nexport type Datapoint<D, T> = {\n /**\n * input to the executor function. Must be a record with string keys and any values.\n */\n data: Record<string, any> & D;\n /**\n * input to the evaluator function (alongside the executor output).\n * Must be a record with string keys and any values.\n */\n target: Record<string, any> & T;\n}\n\ntype EvaluatorFunctionReturn = number | Record<string, number>;\n\n/**\n * EvaluatorFunction is a function that takes the output of the executor and the target data, and returns a score.\n * The score can be a single number or a record of string keys and number values. The latter is useful for evaluating\n * multiple criteria in one go instead of running multiple evaluators.\n */\ntype EvaluatorFunction<O, T> = (output: O | Promise<O>, target: T, ...args: any[]) => EvaluatorFunctionReturn | Promise<EvaluatorFunctionReturn>;\n\ninterface EvaluatorConstructorProps<D, T, O> {\n /**\n * List of data points to evaluate. `data` is the input to the executor function, `target` is the input to the evaluator function.\n */\n data: (Datapoint<D, T>[]) | Dataset<D, T>;\n /**\n * The executor function. Takes the data point + any additional arguments and returns the output to evaluate.\n */\n executor: (data: D, ...args: any[]) => O;\n /**\n * List of evaluator functions. Each evaluator function takes the output of the executor _and_ the target data, and returns\n * a score. The score can be a single number or a record of string keys and number values.\n * If the score is a single number, it will be named after the evaluator function. If the function is anonymous, it will be named\n * `evaluator_${index}`, where index is the index of the evaluator function in the list starting from 1.\n */\n evaluators: EvaluatorFunction<O, T>[];\n /**\n * Optional override configurations for the evaluator.\n */\n config?: EvaluatorConfig;\n}\n\nexport class Evaluation<D, T, O> {\n private name: string;\n private data: Datapoint<D, T>[] | Dataset<D, T>;\n private executor: (data: D, ...args: any[]) => O;\n private evaluators: Record<string, EvaluatorFunction<O, T>>;\n private evaluatorNames: string[];\n private batchSize: number = DEFAULT_BATCH_SIZE;\n\n /**\n * Create a new evaluation and prepare data.\n * @param name Name of the evaluation.\n * @param props.data List of data points to evaluate. `data` is the input to the executor function, `target` is the input to the evaluator function.\n * @param props.executor The executor function. Takes the data point + any additional arguments and returns the output to evaluate.\n * @param props.evaluators List of evaluator functions. Each evaluator function takes the output of the executor and the target data, and returns.\n */\n constructor(name: string, {\n data, executor, evaluators, config\n }: EvaluatorConstructorProps<D, T, O>) {\n this.name = name;\n this.data = data;\n this.executor = executor;\n this.evaluators = Object.fromEntries(evaluators.map((e, i) => [e.name.length > 0 ? e.name : `evaluator_${i + 1}`, e]));\n this.evaluatorNames = evaluators.map((e, i) => e.name.length > 0 ? e.name : `evaluator_${i + 1}`);\n if (config) {\n this.batchSize = config.batchSize ?? DEFAULT_BATCH_SIZE;\n }\n Laminar.initialize({ projectApiKey: config?.projectApiKey, baseUrl: config?.baseUrl });\n }\n\n /** \n * Runs the evaluation.\n *\n * Creates a new evaluation if no evaluation with such name exists, or adds data to an existing one otherwise.\n * Evaluates data points in batches of `batchSize`. The executor function is called on each data point\n * to get the output, and then evaluate it by each evaluator function.\n */\n public async run(): Promise<void> {\n const response = await Laminar.createEvaluation(this.name) as CreateEvaluationResponse;\n const batchPromises = [];\n const length = this.data instanceof Dataset ? this.data.size() : this.data.length;\n for (let i = 0; i < length; i += this.batchSize) {\n const batch = this.data.slice(i, i + this.batchSize);\n batchPromises.push(this.evaluateBatch(batch));\n }\n\n try {\n await Promise.all(batchPromises);\n await Laminar.updateEvaluationStatus(response.name, 'Finished');\n console.log(`Evaluation ${response.id} complete`);\n\n } catch (e) {\n console.error(`Error evaluating batch: ${e}`);\n }\n }\n\n private async evaluateBatch(batch: Datapoint<D, T>[]): Promise<void> {\n let results = [];\n for (const datapoint of batch) {\n const output = await this.executor(datapoint.data);\n const target = datapoint.target;\n\n // iterate in order of evaluators\n let scores: Record<string, EvaluatorFunctionReturn> = {};\n for (const evaluatorName of this.evaluatorNames) {\n const evaluator = this.evaluators[evaluatorName];\n const value = await evaluator(output, target);\n // if the evaluator returns a single number, use the evaluator name as the key\n if (typeof value === 'number') {\n scores[evaluatorName] = value;\n } else {\n // if the evaluator returns an object, use the object keys as the keys\n scores = { ...scores, ...value };\n }\n };\n\n results.push({\n executorOutput: output,\n data: datapoint.data,\n target,\n scores,\n } as EvaluationDatapoint<D, T, O>);\n };\n\n return Laminar.postEvaluationResults(this.name, results);\n }\n}\n","import { withTask } from '@traceloop/node-server-sdk'\nimport { Laminar } from './laminar';\n\ninterface ObserveOptions {\n name?: string;\n sessionId?: string;\n userId?: string;\n}\n\n/**\n * The main decorator entrypoint for Laminar. This is used to wrap\n * functions and methods to create spans.\n *\n * @param name - Name of the span. Function name is used if not specified.\n * @param user_id - User ID to associate with the span and the following context.\n * @param session_id - Session ID to associate with the span and the following context.\n * @returns Returns the result of the wrapped function.\n * @throws Exception - Re-throws the exception if the wrapped function throws an exception.\n * \n * @example\n * ```typescript\n * import { observe } from '@lmnr-ai/lmnr';\n * \n * await observe({ name: 'my_function' }, () => {\n * // Your code here\n * });\n */\nexport async function observe<A extends unknown[], F extends (...args: A) => ReturnType<F>>(\n {\n name,\n sessionId,\n userId,\n }: ObserveOptions, fn: F, ...args: A) {\n\n if (!Laminar.initialized()) {\n throw new Error('Laminar not initialized. Please call Laminar.initialize(projectApiKey) before using observe.');\n };\n let associationProperties = {};\n if (sessionId) {\n associationProperties = { ...associationProperties, \"session_id\": sessionId };\n }\n if (userId) {\n associationProperties = { ...associationProperties, \"user_id\": userId };\n }\n return withTask<A, F>({ name: name ?? fn.name, associationProperties }, fn, ...args);\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/laminar.ts","../src/utils.ts","../src/evaluations.ts","../src/decorators.ts"],"sourcesContent":["import { PipelineRunResponse, PipelineRunRequest, EvaluationDatapoint, EvaluationStatus } from './types';\nimport { Attributes, AttributeValue, context, createContextKey, isSpanContextValid, TimeInput, trace } from '@opentelemetry/api';\nimport { initialize as traceloopInitialize } from '@traceloop/node-server-sdk'\nimport { otelSpanIdToUUID, otelTraceIdToUUID } from './utils';\n\n\n// quick patch to get the traceloop's default tracer, since their \n// `getTracer` function is not exported.\n// Another option would be to import directly \n// like so: `import { getTracer } from '@traceloop/node-server-sdk/dist/src/lib/tracing/tracing';`\n// which isn't too nice either.\nconst DEFAULT_TRACER_NAME = 'traceloop.tracer';\n\ninterface LaminarInitializeProps {\n projectApiKey?: string;\n env?: Record<string, string>;\n baseUrl?: string;\n}\n\nexport class Laminar {\n private static baseUrl: string = 'https://api.lmnr.ai';\n private static projectApiKey: string;\n private static env: Record<string, string> = {};\n private static isInitialized: boolean = false;\n\n /**\n * Initialize Laminar context across the application.\n * This method must be called before using any other Laminar methods or decorators.\n *\n * @param project_api_key - Laminar project api key. You can generate one by going\n * to the projects settings page on the Laminar dashboard.\n * If not specified, it will try to read from the LMNR_PROJECT_API_KEY environment variable.\n * @param env - Default environment passed to `run` and `evaluateEvent` requests,\n * unless overriden at request time. Usually, model provider keys are stored here.\n * @param baseUrl - Url of Laminar endpoint, or the custom open telemetry ingester.\n * If not specified, defaults to https://api.lmnr.ai. For locally hosted Laminar,\n * default setting must be http://localhost:8000.\n *\n * @throws {Error} - If project API key is not set\n */\n public static initialize({\n projectApiKey,\n env,\n baseUrl\n }: LaminarInitializeProps) {\n\n let key = projectApiKey ?? process.env.LMNR_PROJECT_API_KEY;\n if (key === undefined) {\n throw new Error(\n 'Please initialize the Laminar object with your project API key ' +\n 'or set the LMNR_PROJECT_API_KEY environment variable'\n );\n }\n this.projectApiKey = key;\n if (baseUrl) {\n this.baseUrl = baseUrl;\n }\n this.isInitialized = true;\n this.env = env ?? {};\n traceloopInitialize({\n apiKey: this.projectApiKey,\n baseUrl: this.baseUrl,\n silenceInitializationMessage: true,\n });\n }\n\n /**\n * Check if Laminar has been initialized. Utility to make sure other methods\n * are called after initialization.\n */\n public static initialized(): boolean {\n return this.isInitialized;\n }\n\n /**\n * Sets the environment that will be sent to Laminar requests.\n * \n * @param env - The environment variables to override. If not provided, the current environment will not be modified.\n */\n public static setEnv(env?: Record<string, string>) {\n if (env) {\n this.env = env;\n }\n }\n\n /**\n * Sets the project API key for authentication with Laminar.\n *\n * @param projectApiKey - The API key to be set. If not provided, the existing API key will not be modified.\n */\n public static setProjectApiKey(projectApiKey?: string) {\n if (projectApiKey) {\n this.projectApiKey = projectApiKey;\n }\n }\n\n /**\n * Runs the pipeline with the given inputs\n *\n * @param pipeline - The name of the Laminar pipeline. Pipeline must have a target version.\n * @param inputs - The inputs for the pipeline. Map from an input node name to input data.\n * @param env - The environment variables for the pipeline execution. Typically used for model provider keys.\n * @param metadata - Additional metadata for the pipeline run.\n * @param currentSpanId - The ID of the current span.\n * @param currentTraceId - The ID of the current trace.\n * @returns A promise that resolves to the response of the pipeline run.\n * @throws An error if the Laminar object is not initialized with a project API key. Or if the request fails.\n */\n public static async run({\n pipeline,\n inputs,\n env,\n metadata = {},\n currentSpanId,\n currentTraceId,\n }: PipelineRunRequest): Promise<PipelineRunResponse> {\n const currentSpan = trace.getActiveSpan();\n let parentSpanId: string | undefined;\n let traceId: string | undefined;\n if (currentSpan !== undefined && isSpanContextValid(currentSpan.spanContext())) {\n parentSpanId = currentSpanId ?? otelSpanIdToUUID(currentSpan.spanContext().spanId);\n traceId = currentTraceId ?? otelTraceIdToUUID(currentSpan.spanContext().traceId);\n } else {\n parentSpanId = currentSpanId;\n traceId = currentTraceId;\n }\n if (this.projectApiKey === undefined) {\n throw new Error(\n 'Please initialize the Laminar object with your project API key ' +\n 'or set the LMNR_PROJECT_API_KEY environment variable'\n );\n }\n const envirionment = env === undefined ? this.env : env;\n\n const response = await fetch(`${this.baseUrl}/v1/pipeline/run`, {\n method: 'POST',\n headers: this.getHeaders(),\n body: JSON.stringify({\n inputs,\n pipeline,\n env: envirionment,\n metadata,\n parentSpanId,\n traceId,\n })\n });\n\n if (!response.ok) {\n throw new Error(`Failed to run pipeline ${pipeline}. Response: ${response.statusText}`);\n }\n try {\n return await response.json() as PipelineRunResponse;\n } catch (error) {\n throw new Error(`Failed to parse response from pipeline ${pipeline}. Error: ${error}`);\n }\n }\n\n /**\n * Associates an event with the current span. If event with such name never\n * existed, Laminar will create a new event and infer its type from the value.\n * If the event already exists, Laminar will append the value to the event\n * if and only if the value is of a matching type. Otherwise, the event won't\n * be recorded. Supported types are string, number, and boolean. If the value\n * is `null`, event is considered a boolean tag with the value of `true`.\n *\n * @param name - The name of the event.\n * @param value - The value of the event. Must be a primitive type. If not specified, boolean true is assumed in the backend.\n * @param timestamp - The timestamp of the event. If not specified, relies on the underlying OpenTelemetry implementation.\n * If specified as an integer, it must be epoch nanoseconds.\n */\n public static event(\n name: string,\n value?: AttributeValue,\n timestamp?: TimeInput,\n ) {\n const currentSpan = trace.getActiveSpan();\n if (currentSpan === undefined || !isSpanContextValid(currentSpan.spanContext())) {\n console.warn(\"Laminar().event()\\` called outside of span context.\" +\n ` Event '${name}' will not be recorded in the trace.` +\n \" Make sure to annotate the function with a decorator\"\n );\n return;\n }\n\n const event: Attributes = {\n \"lmnr.event.type\": \"default\",\n }\n if (value !== undefined) {\n event[\"lmnr.event.value\"] = value;\n }\n\n currentSpan.addEvent(name, event, timestamp);\n }\n\n /**\n * Sends an event for evaluation to the Laminar backend.\n * \n * @param name - The name of the event.\n * @param evaluator - The name of the pipeline that evaluates the event.\n * @param data - A map from input node name to its value in the evaluator pipeline.\n * @param env - Environment variables required to run the pipeline.\n * @param timestamp - If specified as an integer, it must be epoch nanoseconds.\n * If not specified, relies on the underlying OpenTelemetry implementation.\n */\n public static evaluateEvent(\n name: string,\n evaluator: string,\n data: Record<string, any>,\n env?: Record<string, string>,\n timestamp?: TimeInput,\n ) {\n const currentSpan = trace.getActiveSpan();\n\n if (currentSpan === undefined || !isSpanContextValid(currentSpan.spanContext())) {\n console.warn(\"Laminar().evaluateEvent()\\` called outside of span context.\" +\n ` Evaluate event '${name}' will not be recorded in the trace.` +\n \" Make sure to annotate the function with a decorator\"\n );\n return;\n }\n\n const event = {\n \"lmnr.event.type\": \"evaluate\",\n \"lmnr.event.evaluator\": evaluator,\n \"lmnr.event.data\": JSON.stringify(data),\n \"lmnr.event.env\": JSON.stringify(env ?? {}),\n }\n\n currentSpan.addEvent(name, event, timestamp);\n }\n\n /**\n * Sets the session information for the current span and returns the context to use for the following spans.\n * \n * Example:\n * ```typescript\n * import { context as contextApi } from '@opentelemetry/api';\n * import { Laminar } from '@lmnr-ai/laminar';\n * const context = Laminar.contextWithSession({ sessionId: \"1234\", userId: \"5678\" });\n * contextApi.with(context, () => {\n * // Your code here\n * });\n * ```\n * \n * @param sessionId - The session ID to associate with the span.\n * @param userId - The user ID to associate with the span.\n * @returns The updated context with the association properties.\n */\n public static contextWithSession({ sessionId, userId }: { sessionId?: string, userId?: string }) {\n\n const currentSpan = trace.getActiveSpan();\n if (currentSpan !== undefined && isSpanContextValid(currentSpan.spanContext())) {\n if (sessionId) {\n currentSpan.setAttribute(\"traceloop.association.properties.session_id\", sessionId);\n }\n if (userId) {\n currentSpan.setAttribute(\"traceloop.association.properties.user_id\", userId);\n }\n }\n let associationProperties = {};\n if (sessionId) {\n associationProperties = { ...associationProperties, \"session_id\": sessionId };\n }\n if (userId) {\n associationProperties = { ...associationProperties, \"user_id\": userId };\n }\n return context.active().setValue(createContextKey(\"association_properites\"), associationProperties);\n }\n\n public static async createEvaluation(name: string) {\n const response = await fetch(`${this.baseUrl}/v1/evaluations`, {\n method: 'POST',\n headers: this.getHeaders(),\n body: JSON.stringify({\n name,\n })\n });\n\n return await response.json();\n }\n\n public static async postEvaluationResults<D, T, O>(\n evaluationName: string,\n data: EvaluationDatapoint<D, T, O>[]\n ): Promise<void> {\n const body = JSON.stringify({\n name: evaluationName,\n points: data,\n });\n const headers = this.getHeaders();\n const url = `${this.baseUrl}/v1/evaluation-datapoints`;\n try {\n const response = await fetch(url, {\n method: \"POST\",\n headers,\n body,\n });\n if (!response.ok) {\n console.error(\"Failed to send evaluation results. Response: \");\n response.text().then((text) => console.error(text));\n }\n } catch (error) {\n console.error(\"Failed to send evaluation results. Error: \", error);\n };\n }\n\n public static async updateEvaluationStatus(\n evaluationName: string,\n status: EvaluationStatus,\n ): Promise<void> {\n const body = JSON.stringify({\n name: evaluationName,\n status,\n });\n const headers = this.getHeaders();\n const url = `${this.baseUrl}/v1/evaluations`;\n try {\n const response = await fetch(url, {\n method: \"PUT\",\n headers,\n body,\n });\n if (!response.ok) {\n console.error(\"Failed to update evaluation status. Response: \");\n response.text().then((text) => console.error(text));\n }\n } catch (error) {\n console.error(\"Failed to update evaluation status. Error: \", error);\n }\n }\n\n private static getHeaders() {\n return {\n \"Content-Type\": \"application/json\",\n \"Authorization\": `Bearer ${this.projectApiKey}`,\n };\n };\n}\n","import { v4 as uuidv4 } from 'uuid';\n\nexport type StringUUID = `${string}-${string}-${string}-${string}-${string}`;\n\nexport const newUUID = (): StringUUID => {\n // crypto.randomUUID is available in most of the modern browsers and node,\n // but is not available in \"insecure\" contexts, e.g. not https, not localhost\n // so we fallback to uuidv4 in those cases, which is less secure, but works\n // just fine.\n if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n return crypto.randomUUID();\n } else {\n return uuidv4() as `${string}-${string}-${string}-${string}-${string}`;\n }\n}\n\nexport const otelSpanIdToUUID = (spanId: string): string => {\n let id = spanId.toLowerCase();\n if (id.startsWith('0x')) {\n id = id.slice(2);\n }\n if (id.length !== 16) {\n console.warn(`Span ID ${spanId} is not 16 hex chars long. This is not a valid OpenTelemetry span ID.`);\n }\n\n if (!/^[0-9a-f]+$/.test(id)) {\n console.error(`Span ID ${spanId} is not a valid hex string. Generating a random UUID instead.`);\n return newUUID();\n }\n\n return id.padStart(32, '0').replace(/^(.{8})(.{4})(.{4})(.{4})(.{12})$/, '$1-$2-$3-$4-$5');\n}\n\nexport const otelTraceIdToUUID = (traceId: string): string => {\n let id = traceId.toLowerCase();\n if (id.startsWith('0x')) {\n id = id.slice(2);\n }\n if (id.length !== 32) {\n console.warn(`Trace ID ${traceId} is not 32 hex chars long. This is not a valid OpenTelemetry trace ID.`);\n }\n if (!/^[0-9a-f]+$/.test(id)) {\n console.error(`Trace ID ${traceId} is not a valid hex string. Generating a random UUID instead.`);\n return newUUID();\n }\n\n return id.replace(/^(.{8})(.{4})(.{4})(.{4})(.{12})$/, '$1-$2-$3-$4-$5');\n}\n","import { Laminar } from \"./laminar\";\nimport { CreateEvaluationResponse, EvaluationDatapoint } from \"./types\";\n\nconst DEFAULT_BATCH_SIZE = 5;\n\n/**\n * Configuration for the Evaluator\n */\ninterface EvaluatorConfig {\n batchSize?: number;\n projectApiKey?: string;\n baseUrl?: string;\n}\n\nexport abstract class Dataset<D, T> {\n public slice(start: number, end: number): Datapoint<D, T>[] {\n const result = [];\n for (let i = Math.max(start, 0); i < Math.min(end, this.size()); i++) {\n result.push(this.get(i));\n }\n return result;\n }\n public abstract size(): number;\n public abstract get(index: number): Datapoint<D, T>;\n}\n\n/**\n * Datapoint is a single data point in the evaluation. `D` is the type of the input data, `T` is the type of the target data.\n */\nexport type Datapoint<D, T> = {\n /**\n * input to the executor function. Must be a record with string keys and any values.\n */\n data: Record<string, any> & D;\n /**\n * input to the evaluator function (alongside the executor output).\n * Must be a record with string keys and any values.\n */\n target: Record<string, any> & T;\n}\n\ntype EvaluatorFunctionReturn = number | Record<string, number>;\n\n/**\n * EvaluatorFunction is a function that takes the output of the executor and the target data, and returns a score.\n * The score can be a single number or a record of string keys and number values. The latter is useful for evaluating\n * multiple criteria in one go instead of running multiple evaluators.\n */\ntype EvaluatorFunction<O, T> = (output: O, target: T, ...args: any[]) => EvaluatorFunctionReturn | Promise<EvaluatorFunctionReturn>;\n\ninterface EvaluatorConstructorProps<D, T, O> {\n /**\n * List of data points to evaluate. `data` is the input to the executor function, `target` is the input to the evaluator function.\n */\n data: (Datapoint<D, T>[]) | Dataset<D, T>;\n /**\n * The executor function. Takes the data point + any additional arguments and returns the output to evaluate.\n */\n executor: (data: D, ...args: any[]) => O | Promise<O>;\n /**\n * List of evaluator functions. Each evaluator function takes the output of the executor _and_ the target data, and returns\n * a score. The score can be a single number or a record of string keys and number values.\n * If the score is a single number, it will be named after the evaluator function. If the function is anonymous, it will be named\n * `evaluator_${index}`, where index is the index of the evaluator function in the list starting from 1.\n */\n evaluators: EvaluatorFunction<O, T>[];\n /**\n * Optional override configurations for the evaluator.\n */\n config?: EvaluatorConfig;\n}\n\nexport class Evaluation<D, T, O> {\n private name: string;\n private data: Datapoint<D, T>[] | Dataset<D, T>;\n private executor: (data: D, ...args: any[]) => O | Promise<O>;\n private evaluators: Record<string, EvaluatorFunction<O, T>>;\n private evaluatorNames: string[];\n private batchSize: number = DEFAULT_BATCH_SIZE;\n\n /**\n * Create a new evaluation and prepare data.\n * @param name Name of the evaluation.\n * @param props.data List of data points to evaluate. `data` is the input to the executor function, `target` is the input to the evaluator function.\n * @param props.executor The executor function. Takes the data point + any additional arguments and returns the output to evaluate.\n * @param props.evaluators List of evaluator functions. Each evaluator function takes the output of the executor and the target data, and returns.\n */\n constructor(name: string, {\n data, executor, evaluators, config\n }: EvaluatorConstructorProps<D, T, O>) {\n this.name = name;\n this.data = data;\n this.executor = executor;\n this.evaluators = Object.fromEntries(evaluators.map((e, i) => [e.name.length > 0 ? e.name : `evaluator_${i + 1}`, e]));\n this.evaluatorNames = evaluators.map((e, i) => e.name.length > 0 ? e.name : `evaluator_${i + 1}`);\n if (config) {\n this.batchSize = config.batchSize ?? DEFAULT_BATCH_SIZE;\n }\n Laminar.initialize({ projectApiKey: config?.projectApiKey, baseUrl: config?.baseUrl });\n }\n\n /** \n * Runs the evaluation.\n *\n * Creates a new evaluation if no evaluation with such name exists, or adds data to an existing one otherwise.\n * Evaluates data points in batches of `batchSize`. The executor function is called on each data point\n * to get the output, and then evaluate it by each evaluator function.\n */\n public async run(): Promise<void> {\n const response = await Laminar.createEvaluation(this.name) as CreateEvaluationResponse;\n const length = this.data instanceof Dataset ? this.data.size() : this.data.length;\n for (let i = 0; i < length; i += this.batchSize) {\n const batch = this.data.slice(i, i + this.batchSize);\n try {\n await this.evaluateBatch(batch);\n } catch (e) {\n console.error(`Error evaluating batch: ${e}`);\n }\n }\n try {\n // After all batches are completed, update the evaluation status\n await Laminar.updateEvaluationStatus(response.name, 'Finished');\n } catch (e) {\n console.error(`Error updating evaluation status: ${e}`);\n }\n }\n \n private async evaluateBatch(batch: Datapoint<D, T>[]): Promise<void> {\n const batchPromises = batch.map(async (datapoint) => {\n const output = await this.executor(datapoint.data);\n const target = datapoint.target;\n \n let scores: Record<string, EvaluatorFunctionReturn> = {};\n for (const evaluatorName of this.evaluatorNames) {\n const evaluator = this.evaluators[evaluatorName];\n const value = await evaluator(output, target);\n \n // If the evaluator returns a single number, use the evaluator name as the key\n if (typeof value === 'number') {\n scores[evaluatorName] = value;\n } else {\n // If the evaluator returns an object, merge its keys with the existing scores (flatten)\n scores = { ...scores, ...value };\n }\n }\n \n return {\n executorOutput: output,\n data: datapoint.data,\n target,\n scores,\n } as EvaluationDatapoint<D, T, O>;\n });\n \n const results = await Promise.all(batchPromises);\n \n return Laminar.postEvaluationResults(this.name, results);\n }\n \n}\n","import { withTask } from '@traceloop/node-server-sdk'\nimport { Laminar } from './laminar';\n\ninterface ObserveOptions {\n name?: string;\n sessionId?: string;\n userId?: string;\n}\n\n/**\n * The main decorator entrypoint for Laminar. This is used to wrap\n * functions and methods to create spans.\n *\n * @param name - Name of the span. Function name is used if not specified.\n * @param user_id - User ID to associate with the span and the following context.\n * @param session_id - Session ID to associate with the span and the following context.\n * @returns Returns the result of the wrapped function.\n * @throws Exception - Re-throws the exception if the wrapped function throws an exception.\n * \n * @example\n * ```typescript\n * import { observe } from '@lmnr-ai/lmnr';\n * \n * await observe({ name: 'my_function' }, () => {\n * // Your code here\n * });\n */\nexport async function observe<A extends unknown[], F extends (...args: A) => ReturnType<F>>(\n {\n name,\n sessionId,\n userId,\n }: ObserveOptions, fn: F, ...args: A) {\n\n if (!Laminar.initialized()) {\n throw new Error('Laminar not initialized. Please call Laminar.initialize(projectApiKey) before using observe.');\n };\n let associationProperties = {};\n if (sessionId) {\n associationProperties = { ...associationProperties, \"session_id\": sessionId };\n }\n if (userId) {\n associationProperties = { ...associationProperties, \"user_id\": userId };\n }\n return withTask<A, F>({ name: name ?? fn.name, associationProperties }, fn, ...args);\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lmnr-ai/lmnr",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.4",
|
|
4
4
|
"description": "TypeScript SDK for Laminar AI",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -21,11 +21,11 @@
|
|
|
21
21
|
},
|
|
22
22
|
"homepage": "https://github.com/lmnr-ai/lmnr-ts#README",
|
|
23
23
|
"devDependencies": {
|
|
24
|
-
"@types/node": "^22.5.
|
|
24
|
+
"@types/node": "^22.5.4",
|
|
25
25
|
"@types/uuid": "^10.0.0",
|
|
26
26
|
"bufferutil": "^4.0.8",
|
|
27
|
-
"tsup": "^8.2.
|
|
28
|
-
"typescript": "^5.5.
|
|
27
|
+
"tsup": "^8.2.4",
|
|
28
|
+
"typescript": "^5.5.4"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@opentelemetry/api": "^1.9.0",
|
package/src/evaluations.ts
CHANGED
|
@@ -46,7 +46,7 @@ type EvaluatorFunctionReturn = number | Record<string, number>;
|
|
|
46
46
|
* The score can be a single number or a record of string keys and number values. The latter is useful for evaluating
|
|
47
47
|
* multiple criteria in one go instead of running multiple evaluators.
|
|
48
48
|
*/
|
|
49
|
-
type EvaluatorFunction<O, T> = (output: O
|
|
49
|
+
type EvaluatorFunction<O, T> = (output: O, target: T, ...args: any[]) => EvaluatorFunctionReturn | Promise<EvaluatorFunctionReturn>;
|
|
50
50
|
|
|
51
51
|
interface EvaluatorConstructorProps<D, T, O> {
|
|
52
52
|
/**
|
|
@@ -56,7 +56,7 @@ interface EvaluatorConstructorProps<D, T, O> {
|
|
|
56
56
|
/**
|
|
57
57
|
* The executor function. Takes the data point + any additional arguments and returns the output to evaluate.
|
|
58
58
|
*/
|
|
59
|
-
executor: (data: D, ...args: any[]) => O
|
|
59
|
+
executor: (data: D, ...args: any[]) => O | Promise<O>;
|
|
60
60
|
/**
|
|
61
61
|
* List of evaluator functions. Each evaluator function takes the output of the executor _and_ the target data, and returns
|
|
62
62
|
* a score. The score can be a single number or a record of string keys and number values.
|
|
@@ -73,7 +73,7 @@ interface EvaluatorConstructorProps<D, T, O> {
|
|
|
73
73
|
export class Evaluation<D, T, O> {
|
|
74
74
|
private name: string;
|
|
75
75
|
private data: Datapoint<D, T>[] | Dataset<D, T>;
|
|
76
|
-
private executor: (data: D, ...args: any[]) => O
|
|
76
|
+
private executor: (data: D, ...args: any[]) => O | Promise<O>;
|
|
77
77
|
private evaluators: Record<string, EvaluatorFunction<O, T>>;
|
|
78
78
|
private evaluatorNames: string[];
|
|
79
79
|
private batchSize: number = DEFAULT_BATCH_SIZE;
|
|
@@ -108,51 +108,53 @@ export class Evaluation<D, T, O> {
|
|
|
108
108
|
*/
|
|
109
109
|
public async run(): Promise<void> {
|
|
110
110
|
const response = await Laminar.createEvaluation(this.name) as CreateEvaluationResponse;
|
|
111
|
-
const batchPromises = [];
|
|
112
111
|
const length = this.data instanceof Dataset ? this.data.size() : this.data.length;
|
|
113
112
|
for (let i = 0; i < length; i += this.batchSize) {
|
|
114
113
|
const batch = this.data.slice(i, i + this.batchSize);
|
|
115
|
-
|
|
114
|
+
try {
|
|
115
|
+
await this.evaluateBatch(batch);
|
|
116
|
+
} catch (e) {
|
|
117
|
+
console.error(`Error evaluating batch: ${e}`);
|
|
118
|
+
}
|
|
116
119
|
}
|
|
117
|
-
|
|
118
120
|
try {
|
|
119
|
-
|
|
121
|
+
// After all batches are completed, update the evaluation status
|
|
120
122
|
await Laminar.updateEvaluationStatus(response.name, 'Finished');
|
|
121
|
-
console.log(`Evaluation ${response.id} complete`);
|
|
122
|
-
|
|
123
123
|
} catch (e) {
|
|
124
|
-
console.error(`Error
|
|
124
|
+
console.error(`Error updating evaluation status: ${e}`);
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
|
-
|
|
127
|
+
|
|
128
128
|
private async evaluateBatch(batch: Datapoint<D, T>[]): Promise<void> {
|
|
129
|
-
|
|
130
|
-
for (const datapoint of batch) {
|
|
129
|
+
const batchPromises = batch.map(async (datapoint) => {
|
|
131
130
|
const output = await this.executor(datapoint.data);
|
|
132
131
|
const target = datapoint.target;
|
|
133
|
-
|
|
134
|
-
// iterate in order of evaluators
|
|
132
|
+
|
|
135
133
|
let scores: Record<string, EvaluatorFunctionReturn> = {};
|
|
136
134
|
for (const evaluatorName of this.evaluatorNames) {
|
|
137
135
|
const evaluator = this.evaluators[evaluatorName];
|
|
138
136
|
const value = await evaluator(output, target);
|
|
139
|
-
|
|
137
|
+
|
|
138
|
+
// If the evaluator returns a single number, use the evaluator name as the key
|
|
140
139
|
if (typeof value === 'number') {
|
|
141
140
|
scores[evaluatorName] = value;
|
|
142
141
|
} else {
|
|
143
|
-
//
|
|
142
|
+
// If the evaluator returns an object, merge its keys with the existing scores (flatten)
|
|
144
143
|
scores = { ...scores, ...value };
|
|
145
144
|
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return {
|
|
149
148
|
executorOutput: output,
|
|
150
149
|
data: datapoint.data,
|
|
151
150
|
target,
|
|
152
151
|
scores,
|
|
153
|
-
} as EvaluationDatapoint<D, T, O
|
|
154
|
-
};
|
|
155
|
-
|
|
152
|
+
} as EvaluationDatapoint<D, T, O>;
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
const results = await Promise.all(batchPromises);
|
|
156
|
+
|
|
156
157
|
return Laminar.postEvaluationResults(this.name, results);
|
|
157
158
|
}
|
|
159
|
+
|
|
158
160
|
}
|
package/src/laminar.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { PipelineRunResponse, PipelineRunRequest, EvaluationDatapoint, EvaluationStatus } from './types';
|
|
2
|
-
import { AttributeValue, context, createContextKey, isSpanContextValid, TimeInput, trace } from '@opentelemetry/api';
|
|
2
|
+
import { Attributes, AttributeValue, context, createContextKey, isSpanContextValid, TimeInput, trace } from '@opentelemetry/api';
|
|
3
3
|
import { initialize as traceloopInitialize } from '@traceloop/node-server-sdk'
|
|
4
4
|
import { otelSpanIdToUUID, otelTraceIdToUUID } from './utils';
|
|
5
5
|
|
|
@@ -156,16 +156,21 @@ export class Laminar {
|
|
|
156
156
|
}
|
|
157
157
|
|
|
158
158
|
/**
|
|
159
|
-
* Associates an event with the current span.
|
|
159
|
+
* Associates an event with the current span. If event with such name never
|
|
160
|
+
* existed, Laminar will create a new event and infer its type from the value.
|
|
161
|
+
* If the event already exists, Laminar will append the value to the event
|
|
162
|
+
* if and only if the value is of a matching type. Otherwise, the event won't
|
|
163
|
+
* be recorded. Supported types are string, number, and boolean. If the value
|
|
164
|
+
* is `null`, event is considered a boolean tag with the value of `true`.
|
|
160
165
|
*
|
|
161
166
|
* @param name - The name of the event.
|
|
162
|
-
* @param value - The value of the event. Must be a primitive type
|
|
167
|
+
* @param value - The value of the event. Must be a primitive type. If not specified, boolean true is assumed in the backend.
|
|
163
168
|
* @param timestamp - The timestamp of the event. If not specified, relies on the underlying OpenTelemetry implementation.
|
|
164
169
|
* If specified as an integer, it must be epoch nanoseconds.
|
|
165
170
|
*/
|
|
166
171
|
public static event(
|
|
167
172
|
name: string,
|
|
168
|
-
value
|
|
173
|
+
value?: AttributeValue,
|
|
169
174
|
timestamp?: TimeInput,
|
|
170
175
|
) {
|
|
171
176
|
const currentSpan = trace.getActiveSpan();
|
|
@@ -177,9 +182,11 @@ export class Laminar {
|
|
|
177
182
|
return;
|
|
178
183
|
}
|
|
179
184
|
|
|
180
|
-
const event = {
|
|
185
|
+
const event: Attributes = {
|
|
181
186
|
"lmnr.event.type": "default",
|
|
182
|
-
|
|
187
|
+
}
|
|
188
|
+
if (value !== undefined) {
|
|
189
|
+
event["lmnr.event.value"] = value;
|
|
183
190
|
}
|
|
184
191
|
|
|
185
192
|
currentSpan.addEvent(name, event, timestamp);
|