@lmnr-ai/lmnr 0.4.10 → 0.4.11-alpha

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.js CHANGED
@@ -35,14 +35,17 @@ var require_package = __commonJS({
35
35
  "package.json"(exports2, module2) {
36
36
  module2.exports = {
37
37
  name: "@lmnr-ai/lmnr",
38
- version: "0.4.10",
38
+ version: "0.4.11-alpha",
39
39
  description: "TypeScript SDK for Laminar AI",
40
40
  main: "dist/index.js",
41
41
  types: "dist/index.d.ts",
42
42
  scripts: {
43
- build: "tsup src/index.ts --format esm,cjs --dts",
43
+ build: "tsup src/index.ts src/cli.ts --format esm,cjs --dts",
44
44
  test: 'echo "Error: no test specified" && exit 1'
45
45
  },
46
+ bin: {
47
+ lmnr: "./dist/cli.js"
48
+ },
46
49
  repository: {
47
50
  type: "git",
48
51
  url: "git+https://github.com/lmnr-ai/lmnr-ts.git"
@@ -68,6 +71,8 @@ var require_package = __commonJS({
68
71
  "@langchain/core": ">=0.0.11",
69
72
  "@pinecone-database/pinecone": ">=2.0.1",
70
73
  "@qdrant/js-client-rest": ">=1.5.0",
74
+ "@types/argparse": "^2.0.16",
75
+ "@types/cli-progress": "^3.11.6",
71
76
  "@types/node": ">=14.18.63",
72
77
  "@types/semver": ">=7.5.3",
73
78
  "@types/uuid": ">=9.0.6",
@@ -105,6 +110,9 @@ var require_package = __commonJS({
105
110
  "@traceloop/instrumentation-pinecone": "^0.11.1",
106
111
  "@traceloop/instrumentation-qdrant": "^0.11.1",
107
112
  "@traceloop/instrumentation-vertexai": "^0.11.1",
113
+ argparse: "^2.0.1",
114
+ "cli-progress": "^3.12.0",
115
+ esbuild: "^0.23.1",
108
116
  "posthog-node": ">=3.1.3",
109
117
  uuid: ">=9.0.0"
110
118
  }
@@ -116,8 +124,8 @@ var require_package = __commonJS({
116
124
  var src_exports = {};
117
125
  __export(src_exports, {
118
126
  Dataset: () => Dataset,
119
- Evaluation: () => Evaluation,
120
127
  Laminar: () => Laminar,
128
+ evaluate: () => evaluate,
121
129
  observe: () => observe
122
130
  });
123
131
  module.exports = __toCommonJS(src_exports);
@@ -661,6 +669,9 @@ var otelTraceIdToUUID = (traceId) => {
661
669
  }
662
670
  return id.replace(/^(.{8})(.{4})(.{4})(.{4})(.{12})$/, "$1-$2-$3-$4-$5");
663
671
  };
672
+ function isNumber(value) {
673
+ return typeof value === "number";
674
+ }
664
675
 
665
676
  // src/laminar.ts
666
677
  var import_exporter_trace_otlp_grpc = require("@opentelemetry/exporter-trace-otlp-grpc");
@@ -675,9 +686,8 @@ var Laminar = class {
675
686
  * If not specified, it will try to read from the LMNR_PROJECT_API_KEY environment variable.
676
687
  * @param env - Default environment passed to `run` and `evaluateEvent` requests,
677
688
  * unless overriden at request time. Usually, model provider keys are stored here.
678
- * @param baseUrl - Url of Laminar endpoint, or the custom open telemetry ingester.
679
- * If not specified, defaults to https://api.lmnr.ai:8443. For locally hosted Laminar,
680
- * default setting must be http://localhost:8001.
689
+ * @param baseUrl - Laminar API url.
690
+ * If not specified, defaults to https://api.lmnr.ai.
681
691
  * @param instrumentModules - List of modules to instrument.
682
692
  * If not specified, all auto-instrumentable modules will be instrumented, which include
683
693
  * LLM calls (OpenAI, Anthropic, etc), Langchain, VectorDB calls (Pinecone, Qdrant, etc).
@@ -722,7 +732,7 @@ var Laminar = class {
722
732
  const metadata = new import_grpc_js.Metadata();
723
733
  metadata.set("authorization", `Bearer ${this.projectApiKey}`);
724
734
  const exporter = new import_exporter_trace_otlp_grpc.OTLPTraceExporter({
725
- url: this.baseUrl,
735
+ url: `${this.baseUrl}:8443`,
726
736
  metadata
727
737
  });
728
738
  initialize({
@@ -926,11 +936,14 @@ var Laminar = class {
926
936
  name
927
937
  })
928
938
  });
939
+ if (!response.ok) {
940
+ throw new Error(`Failed to create evaluation ${name}. Response: ${response.statusText}`);
941
+ }
929
942
  return await response.json();
930
943
  }
931
- static async postEvaluationResults(evaluationName, data) {
944
+ static async postEvaluationResults(evaluationId, data) {
932
945
  const body = JSON.stringify({
933
- name: evaluationName,
946
+ evaluationId,
934
947
  points: data
935
948
  });
936
949
  const headers = this.getHeaders();
@@ -950,26 +963,27 @@ var Laminar = class {
950
963
  }
951
964
  ;
952
965
  }
953
- static async updateEvaluationStatus(evaluationName, status) {
966
+ /**
967
+ * Updates the status of an evaluation. Returns the updated evaluation object.
968
+ *
969
+ * @param evaluationId - The ID of the evaluation to update.
970
+ * @param status - The status to set for the evaluation.
971
+ */
972
+ static async updateEvaluationStatus(evaluationId, status) {
954
973
  const body = JSON.stringify({
955
- name: evaluationName,
956
974
  status
957
975
  });
958
976
  const headers = this.getHeaders();
959
- const url = `${this.baseUrl}/v1/evaluations`;
960
- try {
961
- const response = await fetch(url, {
962
- method: "PUT",
963
- headers,
964
- body
965
- });
966
- if (!response.ok) {
967
- console.error("Failed to update evaluation status. Response: ");
968
- response.text().then((text) => console.error(text));
969
- }
970
- } catch (error) {
971
- console.error("Failed to update evaluation status. Error: ", error);
977
+ const url = `${this.baseUrl}/v1/evaluations/${evaluationId}`;
978
+ const response = await fetch(url, {
979
+ method: "POST",
980
+ headers,
981
+ body
982
+ });
983
+ if (!response.ok) {
984
+ throw new Error(`Failed to update evaluation status ${evaluationId}. Response: ${await response.text()}`);
972
985
  }
986
+ return await response.json();
973
987
  }
974
988
  static getHeaders() {
975
989
  return {
@@ -978,12 +992,16 @@ var Laminar = class {
978
992
  };
979
993
  }
980
994
  };
981
- Laminar.baseUrl = "https://api.lmnr.ai:8443";
995
+ Laminar.baseUrl = "https://api.lmnr.ai";
982
996
  Laminar.env = {};
983
997
  Laminar.isInitialized = false;
984
998
 
985
999
  // src/evaluations.ts
1000
+ var import_cli_progress = __toESM(require("cli-progress"));
986
1001
  var DEFAULT_BATCH_SIZE = 5;
1002
+ var getEvaluationUrl = (projectId, evaluationId) => {
1003
+ return `https://www.lmnr.ai/project/${projectId}/evaluations/${evaluationId}`;
1004
+ };
987
1005
  var Dataset = class {
988
1006
  slice(start, end) {
989
1007
  const result = [];
@@ -993,6 +1011,41 @@ var Dataset = class {
993
1011
  return result;
994
1012
  }
995
1013
  };
1014
+ var EvaluationReporter = class {
1015
+ constructor() {
1016
+ this.cliProgress = new import_cli_progress.default.SingleBar({}, import_cli_progress.default.Presets.shades_classic);
1017
+ }
1018
+ start({ name, projectId, id, length }) {
1019
+ process.stdout.write(`
1020
+ Running evaluation ${name}...
1021
+
1022
+ `);
1023
+ process.stdout.write(`Check progress and results at ${getEvaluationUrl(projectId, id)}
1024
+
1025
+ `);
1026
+ this.cliProgress.start(length, 0);
1027
+ }
1028
+ update(batchLength) {
1029
+ this.cliProgress.update(batchLength);
1030
+ }
1031
+ // Call either error or stop, not both
1032
+ stopWithError(error) {
1033
+ this.cliProgress.stop();
1034
+ process.stdout.write(`
1035
+ Error: ${error.message}
1036
+ `);
1037
+ }
1038
+ // Call either error or stop, not both
1039
+ stop(averageScores) {
1040
+ this.cliProgress.stop();
1041
+ process.stdout.write("\nAverage scores:\n");
1042
+ for (const key in averageScores) {
1043
+ process.stdout.write(`${key}: ${averageScores[key]}
1044
+ `);
1045
+ }
1046
+ process.stdout.write("\n");
1047
+ }
1048
+ };
996
1049
  var Evaluation = class {
997
1050
  /**
998
1051
  * Create a new evaluation and prepare data.
@@ -1000,6 +1053,7 @@ var Evaluation = class {
1000
1053
  * @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.
1001
1054
  * @param props.executor The executor function. Takes the data point + any additional arguments and returns the output to evaluate.
1002
1055
  * @param props.evaluators List of evaluator functions. Each evaluator function takes the output of the executor and the target data, and returns.
1056
+ * @param props.config Optional override configurations for the evaluator.
1003
1057
  */
1004
1058
  constructor(name, {
1005
1059
  data,
@@ -1007,9 +1061,11 @@ var Evaluation = class {
1007
1061
  evaluators,
1008
1062
  config
1009
1063
  }) {
1064
+ this.isFinished = false;
1010
1065
  this.batchSize = DEFAULT_BATCH_SIZE;
1011
1066
  var _a;
1012
1067
  this.name = name;
1068
+ this.progressReporter = new EvaluationReporter();
1013
1069
  this.data = data;
1014
1070
  this.executor = executor;
1015
1071
  this.evaluators = Object.fromEntries(evaluators.map((e, i) => [e.name.length > 0 ? e.name : `evaluator_${i + 1}`, e]));
@@ -1022,26 +1078,42 @@ var Evaluation = class {
1022
1078
  /**
1023
1079
  * Runs the evaluation.
1024
1080
  *
1025
- * Creates a new evaluation if no evaluation with such name exists, or adds data to an existing one otherwise.
1081
+ * Creates a new evaluation.
1026
1082
  * Evaluates data points in batches of `batchSize`. The executor function is called on each data point
1027
1083
  * to get the output, and then evaluate it by each evaluator function.
1028
1084
  */
1029
1085
  async run() {
1030
- const response = await Laminar.createEvaluation(this.name);
1031
- const length = this.data instanceof Dataset ? this.data.size() : this.data.length;
1032
- for (let i = 0; i < length; i += this.batchSize) {
1086
+ if (this.isFinished) {
1087
+ throw new Error("Evaluation is already finished");
1088
+ }
1089
+ const evaluation = await Laminar.createEvaluation(this.name);
1090
+ this.progressReporter.start({ name: evaluation.name, projectId: evaluation.projectId, id: evaluation.id, length: this.getLength() });
1091
+ try {
1092
+ await this.evaluateInBatches(evaluation);
1093
+ } catch (e) {
1094
+ await Laminar.updateEvaluationStatus(evaluation.id, "Error");
1095
+ this.progressReporter.stopWithError(e);
1096
+ this.isFinished = true;
1097
+ }
1098
+ if (!this.isFinished) {
1099
+ const updatedEvaluation = await Laminar.updateEvaluationStatus(evaluation.id, "Finished");
1100
+ this.progressReporter.stop(updatedEvaluation.averageScores);
1101
+ this.isFinished = true;
1102
+ }
1103
+ }
1104
+ // TODO: Calculate duration of the evaluation and add it to the summary
1105
+ async evaluateInBatches(evaluation) {
1106
+ for (let i = 0; i < this.getLength(); i += this.batchSize) {
1033
1107
  const batch = this.data.slice(i, i + this.batchSize);
1034
1108
  try {
1035
- await this.evaluateBatch(batch);
1109
+ const results = await this.evaluateBatch(batch);
1110
+ await Laminar.postEvaluationResults(evaluation.id, results);
1036
1111
  } catch (e) {
1037
1112
  console.error(`Error evaluating batch: ${e}`);
1113
+ } finally {
1114
+ this.progressReporter.update(batch.length);
1038
1115
  }
1039
1116
  }
1040
- try {
1041
- await Laminar.updateEvaluationStatus(response.name, "Finished");
1042
- } catch (e) {
1043
- console.error(`Error updating evaluation status: ${e}`);
1044
- }
1045
1117
  }
1046
1118
  async evaluateBatch(batch) {
1047
1119
  const batchPromises = batch.map(async (datapoint) => {
@@ -1051,7 +1123,10 @@ var Evaluation = class {
1051
1123
  for (const evaluatorName of this.evaluatorNames) {
1052
1124
  const evaluator = this.evaluators[evaluatorName];
1053
1125
  const value = await evaluator(output, target);
1054
- if (typeof value === "number") {
1126
+ if (isNumber(value)) {
1127
+ if (isNaN(value)) {
1128
+ throw new Error(`Evaluator ${evaluatorName} returned NaN`);
1129
+ }
1055
1130
  scores[evaluatorName] = value;
1056
1131
  } else {
1057
1132
  scores = { ...scores, ...value };
@@ -1065,9 +1140,27 @@ var Evaluation = class {
1065
1140
  };
1066
1141
  });
1067
1142
  const results = await Promise.all(batchPromises);
1068
- return Laminar.postEvaluationResults(this.name, results);
1143
+ return results;
1144
+ }
1145
+ getLength() {
1146
+ return this.data instanceof Dataset ? this.data.size() : this.data.length;
1069
1147
  }
1070
1148
  };
1149
+ function evaluate(name, {
1150
+ data,
1151
+ executor,
1152
+ evaluators,
1153
+ config
1154
+ }) {
1155
+ (async () => {
1156
+ const evaluation = new Evaluation(name, { data, executor, evaluators, config });
1157
+ if (globalThis._set_global_evaluation) {
1158
+ globalThis._evaluation = evaluation;
1159
+ } else {
1160
+ await evaluation.run();
1161
+ }
1162
+ })();
1163
+ }
1071
1164
 
1072
1165
  // src/decorators.ts
1073
1166
  async function observe({
@@ -1087,8 +1180,8 @@ async function observe({
1087
1180
  // Annotate the CommonJS export names for ESM import in node:
1088
1181
  0 && (module.exports = {
1089
1182
  Dataset,
1090
- Evaluation,
1091
1183
  Laminar,
1184
+ evaluate,
1092
1185
  observe
1093
1186
  });
1094
1187
  //# sourceMappingURL=index.js.map