langsmith 0.2.14-rc.0 → 0.2.14-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -8,4 +8,4 @@ Object.defineProperty(exports, "RunTree", { enumerable: true, get: function () {
8
8
  var fetch_js_1 = require("./singletons/fetch.cjs");
9
9
  Object.defineProperty(exports, "overrideFetchImplementation", { enumerable: true, get: function () { return fetch_js_1.overrideFetchImplementation; } });
10
10
  // Update using yarn bump-version
11
- exports.__version__ = "0.2.14-rc.0";
11
+ exports.__version__ = "0.2.14-rc.2";
package/dist/index.d.ts CHANGED
@@ -2,4 +2,4 @@ export { Client, type ClientConfig, type LangSmithTracingClientInterface, } from
2
2
  export type { Dataset, Example, TracerSession, Run, Feedback, RetrieverOutput, } from "./schemas.js";
3
3
  export { RunTree, type RunTreeConfig } from "./run_trees.js";
4
4
  export { overrideFetchImplementation } from "./singletons/fetch.js";
5
- export declare const __version__ = "0.2.14-rc.0";
5
+ export declare const __version__ = "0.2.14-rc.2";
package/dist/index.js CHANGED
@@ -2,4 +2,4 @@ export { Client, } from "./client.js";
2
2
  export { RunTree } from "./run_trees.js";
3
3
  export { overrideFetchImplementation } from "./singletons/fetch.js";
4
4
  // Update using yarn bump-version
5
- export const __version__ = "0.2.14-rc.0";
5
+ export const __version__ = "0.2.14-rc.2";
@@ -6,10 +6,13 @@ export declare const jestAsyncLocalStorageInstance: AsyncLocalStorage<{
6
6
  dataset?: Dataset | undefined;
7
7
  examples?: (Example & {
8
8
  inputHash: string;
9
+ outputHash: string;
9
10
  })[] | undefined;
10
11
  createdAt: string;
11
12
  project?: TracerSession | undefined;
12
13
  currentExample?: Partial<Example> | undefined;
13
- client?: Client | undefined;
14
+ client: Client;
15
+ suiteUuid: string;
16
+ suiteName: string;
14
17
  }>;
15
18
  export declare function trackingEnabled(): boolean;
@@ -1,4 +1,6 @@
1
1
  "use strict";
2
+ /* eslint-disable import/no-extraneous-dependencies */
3
+ /* eslint-disable @typescript-eslint/no-namespace */
2
4
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
5
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
6
  };
@@ -44,55 +46,75 @@ async function _createProject(client, datasetId) {
44
46
  throw new Error("Could not generate a unique experiment name within 10 attempts." +
45
47
  " Please try again.");
46
48
  }
49
+ const setupPromises = new Map();
50
+ async function runDatasetSetup(testClient, datasetName) {
51
+ let storageValue;
52
+ if (!(0, globals_js_1.trackingEnabled)()) {
53
+ storageValue = {
54
+ createdAt: new Date().toISOString(),
55
+ };
56
+ }
57
+ else {
58
+ let dataset;
59
+ try {
60
+ dataset = await testClient.readDataset({
61
+ datasetName,
62
+ });
63
+ }
64
+ catch (e) {
65
+ if (e.message.includes("not found")) {
66
+ dataset = await testClient.createDataset(datasetName, {
67
+ description: `Dataset for unit tests created on ${new Date().toISOString()}`,
68
+ });
69
+ }
70
+ else {
71
+ throw e;
72
+ }
73
+ }
74
+ const examplesList = testClient.listExamples({
75
+ datasetName,
76
+ });
77
+ const examples = [];
78
+ for await (const example of examplesList) {
79
+ const inputHash = crypto_1.default
80
+ .createHash("sha256")
81
+ .update(JSON.stringify(example.inputs))
82
+ .digest("hex");
83
+ const outputHash = crypto_1.default
84
+ .createHash("sha256")
85
+ .update(JSON.stringify(example.inputs))
86
+ .digest("hex");
87
+ examples.push({ ...example, inputHash, outputHash });
88
+ }
89
+ const project = await _createProject(testClient, dataset.id);
90
+ storageValue = {
91
+ dataset,
92
+ examples,
93
+ project,
94
+ client: testClient,
95
+ };
96
+ }
97
+ return storageValue;
98
+ }
47
99
  function wrapDescribeMethod(method) {
48
100
  return function (datasetName, fn, config) {
49
- const testClient = config?.client ?? run_trees_js_1.RunTree.getSharedClient();
50
101
  return method(datasetName, () => {
51
- (0, globals_1.beforeAll)(async () => {
52
- if ((0, globals_js_1.trackingEnabled)()) {
53
- globals_js_1.jestAsyncLocalStorageInstance.enterWith({
54
- createdAt: new Date().toISOString(),
55
- });
56
- }
57
- else {
58
- let dataset;
59
- try {
60
- dataset = await testClient.readDataset({
61
- datasetName,
62
- });
63
- }
64
- catch (e) {
65
- if (e.message.includes("not found")) {
66
- dataset = await testClient.createDataset(datasetName, {
67
- description: `Dataset for unit tests created on ${new Date().toISOString()}`,
68
- });
69
- }
70
- else {
71
- throw e;
72
- }
73
- }
74
- const examplesList = testClient.listExamples({
75
- datasetName,
76
- });
77
- const examples = [];
78
- for await (const example of examplesList) {
79
- const inputHash = crypto_1.default
80
- .createHash("sha256")
81
- .update(JSON.stringify(example.inputs))
82
- .digest("hex");
83
- examples.push({ ...example, inputHash });
84
- }
85
- const project = await _createProject(testClient, dataset.id);
86
- globals_js_1.jestAsyncLocalStorageInstance.enterWith({
87
- dataset,
88
- examples,
89
- createdAt: new Date().toISOString(),
90
- project,
91
- client: testClient,
92
- });
93
- }
94
- });
95
- fn();
102
+ const suiteUuid = (0, uuid_1.v4)();
103
+ /**
104
+ * We cannot rely on setting AsyncLocalStorage in beforeAll or beforeEach,
105
+ * due to https://github.com/jestjs/jest/issues/13653 and needing to use
106
+ * the janky .enterWith.
107
+ *
108
+ * We also cannot do async setup in describe due to Jest restrictions.
109
+ * However, .run works and since the below function does not contain synchronously,
110
+ * it works.
111
+ */
112
+ void globals_js_1.jestAsyncLocalStorageInstance.run({
113
+ suiteUuid,
114
+ suiteName: datasetName,
115
+ client: config?.client ?? run_trees_js_1.RunTree.getSharedClient(),
116
+ createdAt: new Date().toISOString(),
117
+ }, fn);
96
118
  });
97
119
  };
98
120
  }
@@ -102,37 +124,62 @@ const lsDescribe = Object.assign(wrapDescribeMethod(globals_1.describe), {
102
124
  });
103
125
  function wrapTestMethod(method) {
104
126
  return function (params, config) {
127
+ const context = globals_js_1.jestAsyncLocalStorageInstance.getStore();
128
+ // This typing is wrong, but necessary to avoid lint errors
129
+ // eslint-disable-next-line @typescript-eslint/no-misused-promises
105
130
  return async function (...args) {
106
131
  return method(args[0], async () => {
132
+ if (context === undefined) {
133
+ throw new Error(`Could not retrieve test context.\nPlease make sure you have tracing enabled and you are wrapping all of your test cases in an "ls.describe()" function.`);
134
+ }
135
+ // Because of https://github.com/jestjs/jest/issues/13653, we have to do asynchronous setup
136
+ // within the test itself
137
+ if (!setupPromises.get(context.suiteUuid)) {
138
+ setupPromises.set(context.suiteUuid, runDatasetSetup(context.client, context.suiteName));
139
+ }
140
+ const { examples, dataset, createdAt, project, client } = await setupPromises.get(context.suiteUuid);
107
141
  const testInput = typeof params === "string" ? {} : params.inputs;
108
142
  const testOutput = typeof params === "string" ? {} : params.outputs;
109
143
  const inputHash = crypto_1.default
110
144
  .createHash("sha256")
111
145
  .update(JSON.stringify(testInput))
112
146
  .digest("hex");
113
- const context = globals_js_1.jestAsyncLocalStorageInstance.getStore();
114
- if (context === undefined) {
115
- throw new Error(`Could not retrieve test context.\nPlease make sure you have tracing enabled and you are wrapping all of your test cases in an "ls.describe()" function.`);
116
- }
117
- const { examples, dataset, createdAt, project, client } = context;
147
+ const outputHash = crypto_1.default
148
+ .createHash("sha256")
149
+ .update(JSON.stringify(testOutput))
150
+ .digest("hex");
118
151
  if ((0, globals_js_1.trackingEnabled)()) {
119
- if (examples === undefined ||
120
- dataset === undefined ||
121
- project === undefined ||
122
- client === undefined) {
123
- throw new Error("Failed to initialize test tracking. Please contact us for help.");
152
+ const missingFields = [];
153
+ if (examples === undefined) {
154
+ missingFields.push("examples");
155
+ }
156
+ if (dataset === undefined) {
157
+ missingFields.push("dataset");
158
+ }
159
+ if (project === undefined) {
160
+ missingFields.push("project");
161
+ }
162
+ if (client === undefined) {
163
+ missingFields.push("client");
164
+ }
165
+ if (missingFields.length > 0) {
166
+ throw new Error(`Failed to initialize test tracking: Could not identify ${missingFields
167
+ .map((field) => `"${field}"`)
168
+ .join(", ")} while syncing to LangSmith. Please contact us for help.`);
124
169
  }
125
170
  const testClient = config?.client ?? client;
126
171
  let example = (examples ?? []).find((example) => {
127
- return example.inputHash === inputHash;
172
+ return (example.inputHash === inputHash &&
173
+ example.outputHash === outputHash);
128
174
  });
129
175
  if (example === undefined) {
130
176
  const newExample = await testClient.createExample(testInput, testOutput, {
131
177
  datasetId: dataset?.id,
132
178
  createdAt: new Date(createdAt ?? new Date()),
133
179
  });
134
- example = { ...newExample, inputHash };
180
+ example = { ...newExample, inputHash, outputHash };
135
181
  }
182
+ // .enterWith is OK here
136
183
  globals_js_1.jestAsyncLocalStorageInstance.enterWith({
137
184
  ...context,
138
185
  currentExample: example,
@@ -163,6 +210,7 @@ function wrapTestMethod(method) {
163
210
  await testClient.awaitPendingTraceBatches();
164
211
  }
165
212
  else {
213
+ // .enterWith is OK here
166
214
  globals_js_1.jestAsyncLocalStorageInstance.enterWith({
167
215
  ...context,
168
216
  currentExample: { inputs: testInput, outputs: testOutput },
@@ -4,14 +4,14 @@ import type { SimpleEvaluator } from "./vendor/gradedBy.js";
4
4
  declare global {
5
5
  namespace jest {
6
6
  interface AsymmetricMatchers {
7
- toBeRelativeCloseTo(expected: string, options: any): void;
8
- toBeAbsoluteCloseTo(expected: string, options: any): void;
9
- toBeSemanticCloseTo(expected: string, options: any): Promise<void>;
7
+ toBeRelativeCloseTo(expected: string, options?: any): void;
8
+ toBeAbsoluteCloseTo(expected: string, options?: any): void;
9
+ toBeSemanticCloseTo(expected: string, options?: any): Promise<void>;
10
10
  }
11
11
  interface Matchers<R> {
12
- toBeRelativeCloseTo(expected: string, options: any): R;
13
- toBeAbsoluteCloseTo(expected: string, options: any): R;
14
- toBeSemanticCloseTo(expected: string, options: any): Promise<R>;
12
+ toBeRelativeCloseTo(expected: string, options?: any): R;
13
+ toBeAbsoluteCloseTo(expected: string, options?: any): R;
14
+ toBeSemanticCloseTo(expected: string, options?: any): Promise<R>;
15
15
  gradedBy(evaluator: SimpleEvaluator): jest.Matchers<Promise<R>> & {
16
16
  not: jest.Matchers<Promise<R>>;
17
17
  resolves: jest.Matchers<Promise<R>>;
@@ -1,4 +1,6 @@
1
- import { expect, test, describe, beforeAll } from "@jest/globals";
1
+ /* eslint-disable import/no-extraneous-dependencies */
2
+ /* eslint-disable @typescript-eslint/no-namespace */
3
+ import { expect, test, describe } from "@jest/globals";
2
4
  import crypto from "crypto";
3
5
  import { v4 } from "uuid";
4
6
  import { traceable } from "../traceable.js";
@@ -39,55 +41,75 @@ async function _createProject(client, datasetId) {
39
41
  throw new Error("Could not generate a unique experiment name within 10 attempts." +
40
42
  " Please try again.");
41
43
  }
44
+ const setupPromises = new Map();
45
+ async function runDatasetSetup(testClient, datasetName) {
46
+ let storageValue;
47
+ if (!trackingEnabled()) {
48
+ storageValue = {
49
+ createdAt: new Date().toISOString(),
50
+ };
51
+ }
52
+ else {
53
+ let dataset;
54
+ try {
55
+ dataset = await testClient.readDataset({
56
+ datasetName,
57
+ });
58
+ }
59
+ catch (e) {
60
+ if (e.message.includes("not found")) {
61
+ dataset = await testClient.createDataset(datasetName, {
62
+ description: `Dataset for unit tests created on ${new Date().toISOString()}`,
63
+ });
64
+ }
65
+ else {
66
+ throw e;
67
+ }
68
+ }
69
+ const examplesList = testClient.listExamples({
70
+ datasetName,
71
+ });
72
+ const examples = [];
73
+ for await (const example of examplesList) {
74
+ const inputHash = crypto
75
+ .createHash("sha256")
76
+ .update(JSON.stringify(example.inputs))
77
+ .digest("hex");
78
+ const outputHash = crypto
79
+ .createHash("sha256")
80
+ .update(JSON.stringify(example.inputs))
81
+ .digest("hex");
82
+ examples.push({ ...example, inputHash, outputHash });
83
+ }
84
+ const project = await _createProject(testClient, dataset.id);
85
+ storageValue = {
86
+ dataset,
87
+ examples,
88
+ project,
89
+ client: testClient,
90
+ };
91
+ }
92
+ return storageValue;
93
+ }
42
94
  function wrapDescribeMethod(method) {
43
95
  return function (datasetName, fn, config) {
44
- const testClient = config?.client ?? RunTree.getSharedClient();
45
96
  return method(datasetName, () => {
46
- beforeAll(async () => {
47
- if (trackingEnabled()) {
48
- jestAsyncLocalStorageInstance.enterWith({
49
- createdAt: new Date().toISOString(),
50
- });
51
- }
52
- else {
53
- let dataset;
54
- try {
55
- dataset = await testClient.readDataset({
56
- datasetName,
57
- });
58
- }
59
- catch (e) {
60
- if (e.message.includes("not found")) {
61
- dataset = await testClient.createDataset(datasetName, {
62
- description: `Dataset for unit tests created on ${new Date().toISOString()}`,
63
- });
64
- }
65
- else {
66
- throw e;
67
- }
68
- }
69
- const examplesList = testClient.listExamples({
70
- datasetName,
71
- });
72
- const examples = [];
73
- for await (const example of examplesList) {
74
- const inputHash = crypto
75
- .createHash("sha256")
76
- .update(JSON.stringify(example.inputs))
77
- .digest("hex");
78
- examples.push({ ...example, inputHash });
79
- }
80
- const project = await _createProject(testClient, dataset.id);
81
- jestAsyncLocalStorageInstance.enterWith({
82
- dataset,
83
- examples,
84
- createdAt: new Date().toISOString(),
85
- project,
86
- client: testClient,
87
- });
88
- }
89
- });
90
- fn();
97
+ const suiteUuid = v4();
98
+ /**
99
+ * We cannot rely on setting AsyncLocalStorage in beforeAll or beforeEach,
100
+ * due to https://github.com/jestjs/jest/issues/13653 and needing to use
101
+ * the janky .enterWith.
102
+ *
103
+ * We also cannot do async setup in describe due to Jest restrictions.
104
+ * However, .run works and since the below function does not contain synchronously,
105
+ * it works.
106
+ */
107
+ void jestAsyncLocalStorageInstance.run({
108
+ suiteUuid,
109
+ suiteName: datasetName,
110
+ client: config?.client ?? RunTree.getSharedClient(),
111
+ createdAt: new Date().toISOString(),
112
+ }, fn);
91
113
  });
92
114
  };
93
115
  }
@@ -97,37 +119,62 @@ const lsDescribe = Object.assign(wrapDescribeMethod(describe), {
97
119
  });
98
120
  function wrapTestMethod(method) {
99
121
  return function (params, config) {
122
+ const context = jestAsyncLocalStorageInstance.getStore();
123
+ // This typing is wrong, but necessary to avoid lint errors
124
+ // eslint-disable-next-line @typescript-eslint/no-misused-promises
100
125
  return async function (...args) {
101
126
  return method(args[0], async () => {
127
+ if (context === undefined) {
128
+ throw new Error(`Could not retrieve test context.\nPlease make sure you have tracing enabled and you are wrapping all of your test cases in an "ls.describe()" function.`);
129
+ }
130
+ // Because of https://github.com/jestjs/jest/issues/13653, we have to do asynchronous setup
131
+ // within the test itself
132
+ if (!setupPromises.get(context.suiteUuid)) {
133
+ setupPromises.set(context.suiteUuid, runDatasetSetup(context.client, context.suiteName));
134
+ }
135
+ const { examples, dataset, createdAt, project, client } = await setupPromises.get(context.suiteUuid);
102
136
  const testInput = typeof params === "string" ? {} : params.inputs;
103
137
  const testOutput = typeof params === "string" ? {} : params.outputs;
104
138
  const inputHash = crypto
105
139
  .createHash("sha256")
106
140
  .update(JSON.stringify(testInput))
107
141
  .digest("hex");
108
- const context = jestAsyncLocalStorageInstance.getStore();
109
- if (context === undefined) {
110
- throw new Error(`Could not retrieve test context.\nPlease make sure you have tracing enabled and you are wrapping all of your test cases in an "ls.describe()" function.`);
111
- }
112
- const { examples, dataset, createdAt, project, client } = context;
142
+ const outputHash = crypto
143
+ .createHash("sha256")
144
+ .update(JSON.stringify(testOutput))
145
+ .digest("hex");
113
146
  if (trackingEnabled()) {
114
- if (examples === undefined ||
115
- dataset === undefined ||
116
- project === undefined ||
117
- client === undefined) {
118
- throw new Error("Failed to initialize test tracking. Please contact us for help.");
147
+ const missingFields = [];
148
+ if (examples === undefined) {
149
+ missingFields.push("examples");
150
+ }
151
+ if (dataset === undefined) {
152
+ missingFields.push("dataset");
153
+ }
154
+ if (project === undefined) {
155
+ missingFields.push("project");
156
+ }
157
+ if (client === undefined) {
158
+ missingFields.push("client");
159
+ }
160
+ if (missingFields.length > 0) {
161
+ throw new Error(`Failed to initialize test tracking: Could not identify ${missingFields
162
+ .map((field) => `"${field}"`)
163
+ .join(", ")} while syncing to LangSmith. Please contact us for help.`);
119
164
  }
120
165
  const testClient = config?.client ?? client;
121
166
  let example = (examples ?? []).find((example) => {
122
- return example.inputHash === inputHash;
167
+ return (example.inputHash === inputHash &&
168
+ example.outputHash === outputHash);
123
169
  });
124
170
  if (example === undefined) {
125
171
  const newExample = await testClient.createExample(testInput, testOutput, {
126
172
  datasetId: dataset?.id,
127
173
  createdAt: new Date(createdAt ?? new Date()),
128
174
  });
129
- example = { ...newExample, inputHash };
175
+ example = { ...newExample, inputHash, outputHash };
130
176
  }
177
+ // .enterWith is OK here
131
178
  jestAsyncLocalStorageInstance.enterWith({
132
179
  ...context,
133
180
  currentExample: example,
@@ -158,6 +205,7 @@ function wrapTestMethod(method) {
158
205
  await testClient.awaitPendingTraceBatches();
159
206
  }
160
207
  else {
208
+ // .enterWith is OK here
161
209
  jestAsyncLocalStorageInstance.enterWith({
162
210
  ...context,
163
211
  currentExample: { inputs: testInput, outputs: testOutput },
@@ -71,10 +71,6 @@ function expectWithGradedBy(expect) {
71
71
  const expectProxy = Object.assign((...args) => addGradedBy(expect(...args), args), // partially apply expect to get all matchers and chain them
72
72
  expect // clone additional properties on expect
73
73
  );
74
- // expectProxy.extend = (o: any) => {
75
- // expect.extend(o); // add new matchers to expect
76
- // expectProxy = Object.assign(expectProxy, expect); // clone new asymmetric matchers
77
- // };
78
74
  return expectProxy;
79
75
  }
80
76
  exports.default = expectWithGradedBy;
@@ -69,10 +69,6 @@ export default function expectWithGradedBy(expect) {
69
69
  const expectProxy = Object.assign((...args) => addGradedBy(expect(...args), args), // partially apply expect to get all matchers and chain them
70
70
  expect // clone additional properties on expect
71
71
  );
72
- // expectProxy.extend = (o: any) => {
73
- // expect.extend(o); // add new matchers to expect
74
- // expectProxy = Object.assign(expectProxy, expect); // clone new asymmetric matchers
75
- // };
76
72
  return expectProxy;
77
73
  }
78
74
  globalThis.expect = expectWithGradedBy(globalThis.expect);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "langsmith",
3
- "version": "0.2.14-rc.0",
3
+ "version": "0.2.14-rc.2",
4
4
  "description": "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform.",
5
5
  "packageManager": "yarn@1.22.19",
6
6
  "files": [