langsmith 0.1.32 → 0.1.34

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/anonymizer.cjs ADDED
@@ -0,0 +1 @@
1
+ module.exports = require('./dist/anonymizer/index.cjs');
@@ -0,0 +1 @@
1
+ export * from './dist/anonymizer/index.js'
@@ -0,0 +1 @@
1
+ export * from './dist/anonymizer/index.js'
package/anonymizer.js ADDED
@@ -0,0 +1 @@
1
+ export * from './dist/anonymizer/index.js'
@@ -0,0 +1,102 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createAnonymizer = void 0;
7
+ const lodash_set_1 = __importDefault(require("lodash.set"));
8
+ function extractStringNodes(data, options) {
9
+ const parsedOptions = { ...options, maxDepth: options.maxDepth ?? 10 };
10
+ const queue = [
11
+ [data, 0, ""],
12
+ ];
13
+ const result = [];
14
+ while (queue.length > 0) {
15
+ const task = queue.shift();
16
+ if (task == null)
17
+ continue;
18
+ const [value, depth, path] = task;
19
+ if (typeof value === "object" && value != null) {
20
+ if (depth >= parsedOptions.maxDepth)
21
+ continue;
22
+ for (const [key, nestedValue] of Object.entries(value)) {
23
+ queue.push([nestedValue, depth + 1, path ? `${path}.${key}` : key]);
24
+ }
25
+ }
26
+ else if (Array.isArray(value)) {
27
+ if (depth >= parsedOptions.maxDepth)
28
+ continue;
29
+ for (let i = 0; i < value.length; i++) {
30
+ queue.push([value[i], depth + 1, `${path}[${i}]`]);
31
+ }
32
+ }
33
+ else if (typeof value === "string") {
34
+ result.push({ value, path });
35
+ }
36
+ }
37
+ return result;
38
+ }
39
+ function deepClone(data) {
40
+ return JSON.parse(JSON.stringify(data));
41
+ }
42
+ function createAnonymizer(replacer, options) {
43
+ return (data) => {
44
+ let mutateValue = deepClone(data);
45
+ const nodes = extractStringNodes(mutateValue, {
46
+ maxDepth: options?.maxDepth,
47
+ });
48
+ const processor = Array.isArray(replacer)
49
+ ? (() => {
50
+ const replacers = replacer.map(({ pattern, type, replace }) => {
51
+ if (type != null && type !== "pattern")
52
+ throw new Error("Invalid anonymizer type");
53
+ return [
54
+ typeof pattern === "string"
55
+ ? new RegExp(pattern, "g")
56
+ : pattern,
57
+ replace ?? "[redacted]",
58
+ ];
59
+ });
60
+ if (replacers.length === 0)
61
+ throw new Error("No replacers provided");
62
+ return {
63
+ maskNodes: (nodes) => {
64
+ return nodes.reduce((memo, item) => {
65
+ const newValue = replacers.reduce((value, [regex, replace]) => {
66
+ const result = value.replace(regex, replace);
67
+ // make sure we reset the state of regex
68
+ regex.lastIndex = 0;
69
+ return result;
70
+ }, item.value);
71
+ if (newValue !== item.value) {
72
+ memo.push({ value: newValue, path: item.path });
73
+ }
74
+ return memo;
75
+ }, []);
76
+ },
77
+ };
78
+ })()
79
+ : typeof replacer === "function"
80
+ ? {
81
+ maskNodes: (nodes) => nodes.reduce((memo, item) => {
82
+ const newValue = replacer(item.value, item.path);
83
+ if (newValue !== item.value) {
84
+ memo.push({ value: newValue, path: item.path });
85
+ }
86
+ return memo;
87
+ }, []),
88
+ }
89
+ : replacer;
90
+ const toUpdate = processor.maskNodes(nodes);
91
+ for (const node of toUpdate) {
92
+ if (node.path === "") {
93
+ mutateValue = node.value;
94
+ }
95
+ else {
96
+ (0, lodash_set_1.default)(mutateValue, node.path, node.value);
97
+ }
98
+ }
99
+ return mutateValue;
100
+ };
101
+ }
102
+ exports.createAnonymizer = createAnonymizer;
@@ -0,0 +1,16 @@
1
+ export interface StringNode {
2
+ value: string;
3
+ path: string;
4
+ }
5
+ export interface StringNodeProcessor {
6
+ maskNodes: (nodes: StringNode[]) => StringNode[];
7
+ }
8
+ export interface StringNodeRule {
9
+ type?: "pattern";
10
+ pattern: RegExp | string;
11
+ replace?: string;
12
+ }
13
+ export type ReplacerType = ((value: string, path?: string) => string) | StringNodeRule[] | StringNodeProcessor;
14
+ export declare function createAnonymizer(replacer: ReplacerType, options?: {
15
+ maxDepth?: number;
16
+ }): <T>(data: T) => T;
@@ -0,0 +1,95 @@
1
+ import set from "lodash.set";
2
+ function extractStringNodes(data, options) {
3
+ const parsedOptions = { ...options, maxDepth: options.maxDepth ?? 10 };
4
+ const queue = [
5
+ [data, 0, ""],
6
+ ];
7
+ const result = [];
8
+ while (queue.length > 0) {
9
+ const task = queue.shift();
10
+ if (task == null)
11
+ continue;
12
+ const [value, depth, path] = task;
13
+ if (typeof value === "object" && value != null) {
14
+ if (depth >= parsedOptions.maxDepth)
15
+ continue;
16
+ for (const [key, nestedValue] of Object.entries(value)) {
17
+ queue.push([nestedValue, depth + 1, path ? `${path}.${key}` : key]);
18
+ }
19
+ }
20
+ else if (Array.isArray(value)) {
21
+ if (depth >= parsedOptions.maxDepth)
22
+ continue;
23
+ for (let i = 0; i < value.length; i++) {
24
+ queue.push([value[i], depth + 1, `${path}[${i}]`]);
25
+ }
26
+ }
27
+ else if (typeof value === "string") {
28
+ result.push({ value, path });
29
+ }
30
+ }
31
+ return result;
32
+ }
33
+ function deepClone(data) {
34
+ return JSON.parse(JSON.stringify(data));
35
+ }
36
+ export function createAnonymizer(replacer, options) {
37
+ return (data) => {
38
+ let mutateValue = deepClone(data);
39
+ const nodes = extractStringNodes(mutateValue, {
40
+ maxDepth: options?.maxDepth,
41
+ });
42
+ const processor = Array.isArray(replacer)
43
+ ? (() => {
44
+ const replacers = replacer.map(({ pattern, type, replace }) => {
45
+ if (type != null && type !== "pattern")
46
+ throw new Error("Invalid anonymizer type");
47
+ return [
48
+ typeof pattern === "string"
49
+ ? new RegExp(pattern, "g")
50
+ : pattern,
51
+ replace ?? "[redacted]",
52
+ ];
53
+ });
54
+ if (replacers.length === 0)
55
+ throw new Error("No replacers provided");
56
+ return {
57
+ maskNodes: (nodes) => {
58
+ return nodes.reduce((memo, item) => {
59
+ const newValue = replacers.reduce((value, [regex, replace]) => {
60
+ const result = value.replace(regex, replace);
61
+ // make sure we reset the state of regex
62
+ regex.lastIndex = 0;
63
+ return result;
64
+ }, item.value);
65
+ if (newValue !== item.value) {
66
+ memo.push({ value: newValue, path: item.path });
67
+ }
68
+ return memo;
69
+ }, []);
70
+ },
71
+ };
72
+ })()
73
+ : typeof replacer === "function"
74
+ ? {
75
+ maskNodes: (nodes) => nodes.reduce((memo, item) => {
76
+ const newValue = replacer(item.value, item.path);
77
+ if (newValue !== item.value) {
78
+ memo.push({ value: newValue, path: item.path });
79
+ }
80
+ return memo;
81
+ }, []),
82
+ }
83
+ : replacer;
84
+ const toUpdate = processor.maskNodes(nodes);
85
+ for (const node of toUpdate) {
86
+ if (node.path === "") {
87
+ mutateValue = node.value;
88
+ }
89
+ else {
90
+ set(mutateValue, node.path, node.value);
91
+ }
92
+ }
93
+ return mutateValue;
94
+ };
95
+ }
package/dist/client.cjs CHANGED
@@ -279,8 +279,10 @@ class Client {
279
279
  ...(config.callerOptions ?? {}),
280
280
  onFailedResponseHook: handle429,
281
281
  });
282
- this.hideInputs = config.hideInputs ?? defaultConfig.hideInputs;
283
- this.hideOutputs = config.hideOutputs ?? defaultConfig.hideOutputs;
282
+ this.hideInputs =
283
+ config.hideInputs ?? config.anonymizer ?? defaultConfig.hideInputs;
284
+ this.hideOutputs =
285
+ config.hideOutputs ?? config.anonymizer ?? defaultConfig.hideOutputs;
284
286
  this.autoBatchTracing = config.autoBatchTracing ?? this.autoBatchTracing;
285
287
  this.pendingAutoBatchedRunLimit =
286
288
  config.pendingAutoBatchedRunLimit ?? this.pendingAutoBatchedRunLimit;
@@ -1424,6 +1426,30 @@ class Client {
1424
1426
  yield* datasets;
1425
1427
  }
1426
1428
  }
1429
+ /**
1430
+ * Update a dataset
1431
+ * @param props The dataset details to update
1432
+ * @returns The updated dataset
1433
+ */
1434
+ async updateDataset(props) {
1435
+ const { datasetId, datasetName, ...update } = props;
1436
+ if (!datasetId && !datasetName) {
1437
+ throw new Error("Must provide either datasetName or datasetId");
1438
+ }
1439
+ const _datasetId = datasetId ?? (await this.readDataset({ datasetName })).id;
1440
+ (0, _uuid_js_1.assertUuid)(_datasetId);
1441
+ const response = await this.caller.call(fetch, `${this.apiUrl}/datasets/${_datasetId}`, {
1442
+ method: "PATCH",
1443
+ headers: { ...this.headers, "Content-Type": "application/json" },
1444
+ body: JSON.stringify(update),
1445
+ signal: AbortSignal.timeout(this.timeout_ms),
1446
+ ...this.fetchOptions,
1447
+ });
1448
+ if (!response.ok) {
1449
+ throw new Error(`Failed to update dataset ${_datasetId}: ${response.status} ${response.statusText}`);
1450
+ }
1451
+ return (await response.json());
1452
+ }
1427
1453
  async deleteDataset({ datasetId, datasetName, }) {
1428
1454
  let path = "/datasets";
1429
1455
  let datasetId_ = datasetId;
package/dist/client.d.ts CHANGED
@@ -7,8 +7,9 @@ interface ClientConfig {
7
7
  callerOptions?: AsyncCallerParams;
8
8
  timeout_ms?: number;
9
9
  webUrl?: string;
10
- hideInputs?: boolean;
11
- hideOutputs?: boolean;
10
+ anonymizer?: (values: KVMap) => KVMap;
11
+ hideInputs?: boolean | ((inputs: KVMap) => KVMap);
12
+ hideOutputs?: boolean | ((outputs: KVMap) => KVMap);
12
13
  autoBatchTracing?: boolean;
13
14
  pendingAutoBatchedRunLimit?: number;
14
15
  fetchOptions?: RequestInit;
@@ -386,6 +387,17 @@ export declare class Client {
386
387
  datasetName?: string;
387
388
  datasetNameContains?: string;
388
389
  }): AsyncIterable<Dataset>;
390
+ /**
391
+ * Update a dataset
392
+ * @param props The dataset details to update
393
+ * @returns The updated dataset
394
+ */
395
+ updateDataset(props: {
396
+ datasetId?: string;
397
+ datasetName?: string;
398
+ name?: string;
399
+ description?: string;
400
+ }): Promise<Dataset>;
389
401
  deleteDataset({ datasetId, datasetName, }: {
390
402
  datasetId?: string;
391
403
  datasetName?: string;
package/dist/client.js CHANGED
@@ -252,8 +252,10 @@ export class Client {
252
252
  ...(config.callerOptions ?? {}),
253
253
  onFailedResponseHook: handle429,
254
254
  });
255
- this.hideInputs = config.hideInputs ?? defaultConfig.hideInputs;
256
- this.hideOutputs = config.hideOutputs ?? defaultConfig.hideOutputs;
255
+ this.hideInputs =
256
+ config.hideInputs ?? config.anonymizer ?? defaultConfig.hideInputs;
257
+ this.hideOutputs =
258
+ config.hideOutputs ?? config.anonymizer ?? defaultConfig.hideOutputs;
257
259
  this.autoBatchTracing = config.autoBatchTracing ?? this.autoBatchTracing;
258
260
  this.pendingAutoBatchedRunLimit =
259
261
  config.pendingAutoBatchedRunLimit ?? this.pendingAutoBatchedRunLimit;
@@ -1397,6 +1399,30 @@ export class Client {
1397
1399
  yield* datasets;
1398
1400
  }
1399
1401
  }
1402
+ /**
1403
+ * Update a dataset
1404
+ * @param props The dataset details to update
1405
+ * @returns The updated dataset
1406
+ */
1407
+ async updateDataset(props) {
1408
+ const { datasetId, datasetName, ...update } = props;
1409
+ if (!datasetId && !datasetName) {
1410
+ throw new Error("Must provide either datasetName or datasetId");
1411
+ }
1412
+ const _datasetId = datasetId ?? (await this.readDataset({ datasetName })).id;
1413
+ assertUuid(_datasetId);
1414
+ const response = await this.caller.call(fetch, `${this.apiUrl}/datasets/${_datasetId}`, {
1415
+ method: "PATCH",
1416
+ headers: { ...this.headers, "Content-Type": "application/json" },
1417
+ body: JSON.stringify(update),
1418
+ signal: AbortSignal.timeout(this.timeout_ms),
1419
+ ...this.fetchOptions,
1420
+ });
1421
+ if (!response.ok) {
1422
+ throw new Error(`Failed to update dataset ${_datasetId}: ${response.status} ${response.statusText}`);
1423
+ }
1424
+ return (await response.json());
1425
+ }
1400
1426
  async deleteDataset({ datasetId, datasetName, }) {
1401
1427
  let path = "/datasets";
1402
1428
  let datasetId_ = datasetId;
package/dist/index.cjs CHANGED
@@ -6,4 +6,4 @@ Object.defineProperty(exports, "Client", { enumerable: true, get: function () {
6
6
  var run_trees_js_1 = require("./run_trees.cjs");
7
7
  Object.defineProperty(exports, "RunTree", { enumerable: true, get: function () { return run_trees_js_1.RunTree; } });
8
8
  // Update using yarn bump-version
9
- exports.__version__ = "0.1.32";
9
+ exports.__version__ = "0.1.34";
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  export { Client } from "./client.js";
2
2
  export type { Dataset, Example, TracerSession, Run, Feedback, RetrieverOutput, } from "./schemas.js";
3
3
  export { RunTree, type RunTreeConfig } from "./run_trees.js";
4
- export declare const __version__ = "0.1.32";
4
+ export declare const __version__ = "0.1.34";
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
1
  export { Client } from "./client.js";
2
2
  export { RunTree } from "./run_trees.js";
3
3
  // Update using yarn bump-version
4
- export const __version__ = "0.1.32";
4
+ export const __version__ = "0.1.34";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "langsmith",
3
- "version": "0.1.32",
3
+ "version": "0.1.34",
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": [
@@ -37,6 +37,10 @@
37
37
  "wrappers.js",
38
38
  "wrappers.d.ts",
39
39
  "wrappers.d.cts",
40
+ "anonymizer.cjs",
41
+ "anonymizer.js",
42
+ "anonymizer.d.ts",
43
+ "anonymizer.d.cts",
40
44
  "wrappers/openai.cjs",
41
45
  "wrappers/openai.js",
42
46
  "wrappers/openai.d.ts",
@@ -91,6 +95,7 @@
91
95
  "dependencies": {
92
96
  "@types/uuid": "^9.0.1",
93
97
  "commander": "^10.0.1",
98
+ "lodash.set": "^4.3.2",
94
99
  "p-queue": "^6.6.2",
95
100
  "p-retry": "4",
96
101
  "uuid": "^9.0.0"
@@ -104,6 +109,7 @@
104
109
  "@langchain/langgraph": "^0.0.19",
105
110
  "@tsconfig/recommended": "^1.0.2",
106
111
  "@types/jest": "^29.5.1",
112
+ "@types/lodash.set": "^4.3.9",
107
113
  "@typescript-eslint/eslint-plugin": "^5.59.8",
108
114
  "@typescript-eslint/parser": "^5.59.8",
109
115
  "babel-jest": "^29.5.0",
@@ -228,6 +234,15 @@
228
234
  "import": "./wrappers.js",
229
235
  "require": "./wrappers.cjs"
230
236
  },
237
+ "./anonymizer": {
238
+ "types": {
239
+ "import": "./anonymizer.d.ts",
240
+ "require": "./anonymizer.d.cts",
241
+ "default": "./anonymizer.d.ts"
242
+ },
243
+ "import": "./anonymizer.js",
244
+ "require": "./anonymizer.cjs"
245
+ },
231
246
  "./wrappers/openai": {
232
247
  "types": {
233
248
  "import": "./wrappers/openai.d.ts",