langsmith 0.1.23 → 0.1.24

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.
@@ -0,0 +1,195 @@
1
+ import { v4 as uuid4, validate } from "uuid";
2
+ import { Client } from "../index.js";
3
+ import { shuffle } from "../utils/shuffle.js";
4
+ import { AsyncCaller } from "../utils/async_caller.js";
5
+ import pRetry from "p-retry";
6
+ import { getCurrentRunTree, traceable } from "../traceable.js";
7
+ function isExperimentResultsList(value) {
8
+ return value.some((x) => typeof x !== "string");
9
+ }
10
+ async function loadExperiment(client, experiment) {
11
+ const value = typeof experiment === "string" ? experiment : experiment.experimentName;
12
+ return client.readProject(validate(value) ? { projectId: value } : { projectName: value });
13
+ }
14
+ async function loadTraces(client, experiment, options) {
15
+ const executionOrder = options.loadNested ? undefined : 1;
16
+ const runs = await client.listRuns(validate(experiment)
17
+ ? { projectId: experiment, executionOrder }
18
+ : { projectName: experiment, executionOrder });
19
+ const treeMap = {};
20
+ const runIdMap = {};
21
+ const results = [];
22
+ for await (const run of runs) {
23
+ if (run.parent_run_id != null) {
24
+ treeMap[run.parent_run_id] ??= [];
25
+ treeMap[run.parent_run_id].push(run);
26
+ }
27
+ else {
28
+ results.push(run);
29
+ }
30
+ runIdMap[run.id] = run;
31
+ }
32
+ for (const [parentRunId, childRuns] of Object.entries(treeMap)) {
33
+ const parentRun = runIdMap[parentRunId];
34
+ parentRun.child_runs = childRuns.sort((a, b) => {
35
+ if (a.dotted_order == null || b.dotted_order == null)
36
+ return 0;
37
+ return a.dotted_order.localeCompare(b.dotted_order);
38
+ });
39
+ }
40
+ return results;
41
+ }
42
+ export async function evaluateComparative(experiments, options) {
43
+ if (experiments.length < 2) {
44
+ throw new Error("Comparative evaluation requires at least 2 experiments.");
45
+ }
46
+ if (!options.evaluators.length) {
47
+ throw new Error("At least one evaluator is required for comparative evaluation.");
48
+ }
49
+ if (options.maxConcurrency && options.maxConcurrency < 0) {
50
+ throw new Error("maxConcurrency must be a positive number.");
51
+ }
52
+ const client = options.client ?? new Client();
53
+ const resolvedExperiments = await Promise.all(experiments);
54
+ const projects = await (() => {
55
+ if (!isExperimentResultsList(resolvedExperiments)) {
56
+ return Promise.all(resolvedExperiments.map((experiment) => loadExperiment(client, experiment)));
57
+ }
58
+ // if we know the number of runs beforehand, check if the
59
+ // number of runs in the project matches the expected number of runs
60
+ return Promise.all(resolvedExperiments.map((experiment) => pRetry(async () => {
61
+ const project = await loadExperiment(client, experiment);
62
+ if (project.run_count !== experiment?.results.length) {
63
+ throw new Error("Experiment is missing runs. Retrying.");
64
+ }
65
+ return project;
66
+ }, { factor: 2, minTimeout: 1000, retries: 10 })));
67
+ })();
68
+ if (new Set(projects.map((p) => p.reference_dataset_id)).size > 1) {
69
+ throw new Error("All experiments must have the same reference dataset.");
70
+ }
71
+ const referenceDatasetId = projects.at(0)?.reference_dataset_id;
72
+ if (!referenceDatasetId) {
73
+ throw new Error("Reference dataset is required for comparative evaluation.");
74
+ }
75
+ if (new Set(projects.map((p) => p.extra?.metadata?.dataset_version)).size > 1) {
76
+ console.warn("Detected multiple dataset versions used by experiments, which may lead to inaccurate results.");
77
+ }
78
+ const datasetVersion = projects.at(0)?.extra?.metadata?.dataset_version;
79
+ const id = uuid4();
80
+ const experimentName = (() => {
81
+ if (!options.experimentPrefix) {
82
+ const names = projects
83
+ .map((p) => p.name)
84
+ .filter(Boolean)
85
+ .join(" vs. ");
86
+ return `${names}-${uuid4().slice(0, 4)}`;
87
+ }
88
+ return `${options.experimentPrefix}-${uuid4().slice(0, 4)}`;
89
+ })();
90
+ // TODO: add URL to the comparative experiment
91
+ console.log(`Starting pairwise evaluation of: ${experimentName}`);
92
+ const comparativeExperiment = await client.createComparativeExperiment({
93
+ id,
94
+ name: experimentName,
95
+ experimentIds: projects.map((p) => p.id),
96
+ description: options.description,
97
+ metadata: options.metadata,
98
+ referenceDatasetId: projects.at(0)?.reference_dataset_id,
99
+ });
100
+ const viewUrl = await (async () => {
101
+ const projectId = projects.at(0)?.id ?? projects.at(1)?.id;
102
+ const datasetId = comparativeExperiment?.reference_dataset_id;
103
+ if (projectId && datasetId) {
104
+ const hostUrl = (await client.getProjectUrl({ projectId }))
105
+ .split("/projects/p/")
106
+ .at(0);
107
+ const result = new URL(`${hostUrl}/datasets/${datasetId}/compare`);
108
+ result.searchParams.set("selectedSessions", projects.map((p) => p.id).join(","));
109
+ result.searchParams.set("comparativeExperiment", comparativeExperiment.id);
110
+ return result.toString();
111
+ }
112
+ return null;
113
+ })();
114
+ if (viewUrl != null) {
115
+ console.log(`View results at: ${viewUrl}`);
116
+ }
117
+ const experimentRuns = await Promise.all(projects.map((p) => loadTraces(client, p.id, { loadNested: !!options.loadNested })));
118
+ let exampleIdsIntersect;
119
+ for (const runs of experimentRuns) {
120
+ const exampleIdsSet = new Set(runs
121
+ .map((r) => r.reference_example_id)
122
+ .filter((x) => x != null));
123
+ if (!exampleIdsIntersect) {
124
+ exampleIdsIntersect = exampleIdsSet;
125
+ }
126
+ else {
127
+ exampleIdsIntersect = new Set([...exampleIdsIntersect].filter((x) => exampleIdsSet.has(x)));
128
+ }
129
+ }
130
+ const exampleIds = [...(exampleIdsIntersect ?? [])];
131
+ if (!exampleIds.length) {
132
+ throw new Error("No examples found in common between experiments.");
133
+ }
134
+ const exampleMap = {};
135
+ for (let start = 0; start < exampleIds.length; start += 99) {
136
+ const exampleIdsChunk = exampleIds.slice(start, start + 99);
137
+ for await (const example of client.listExamples({
138
+ datasetId: referenceDatasetId,
139
+ exampleIds: exampleIdsChunk,
140
+ asOf: datasetVersion,
141
+ })) {
142
+ exampleMap[example.id] = example;
143
+ }
144
+ }
145
+ const runMapByExampleId = {};
146
+ for (const runs of experimentRuns) {
147
+ for (const run of runs) {
148
+ if (run.reference_example_id == null ||
149
+ !exampleIds.includes(run.reference_example_id)) {
150
+ continue;
151
+ }
152
+ runMapByExampleId[run.reference_example_id] ??= [];
153
+ runMapByExampleId[run.reference_example_id].push(run);
154
+ }
155
+ }
156
+ const caller = new AsyncCaller({ maxConcurrency: options.maxConcurrency });
157
+ async function evaluateAndSubmitFeedback(runs, example, evaluator) {
158
+ const expectedRunIds = new Set(runs.map((r) => r.id));
159
+ const result = await evaluator(options.randomizeOrder ? shuffle(runs) : runs, example);
160
+ for (const [runId, score] of Object.entries(result.scores)) {
161
+ // validate if the run id
162
+ if (!expectedRunIds.has(runId)) {
163
+ throw new Error(`Returning an invalid run id ${runId} from evaluator.`);
164
+ }
165
+ await client.createFeedback(runId, result.key, {
166
+ score,
167
+ sourceRunId: result.source_run_id,
168
+ comparativeExperimentId: comparativeExperiment.id,
169
+ });
170
+ }
171
+ return result;
172
+ }
173
+ const tracedEvaluators = options.evaluators.map((evaluator) => traceable(async (runs, example) => {
174
+ const evaluatorRun = getCurrentRunTree();
175
+ const result = await evaluator(runs, example);
176
+ // sanitise the payload before sending to LangSmith
177
+ evaluatorRun.inputs = { runs: runs, example: example };
178
+ evaluatorRun.outputs = result;
179
+ return {
180
+ ...result,
181
+ source_run_id: result.source_run_id ?? evaluatorRun.id,
182
+ };
183
+ }, {
184
+ project_name: "evaluators",
185
+ name: evaluator.name || "evaluator",
186
+ }));
187
+ const promises = Object.entries(runMapByExampleId).flatMap(([exampleId, runs]) => {
188
+ const example = exampleMap[exampleId];
189
+ if (!example)
190
+ throw new Error(`Example ${exampleId} not found.`);
191
+ return tracedEvaluators.map((evaluator) => caller.call(evaluateAndSubmitFeedback, runs, exampleMap[exampleId], evaluator));
192
+ });
193
+ const results = await Promise.all(promises);
194
+ return { experimentName, results };
195
+ }
@@ -14,11 +14,10 @@ class DynamicRunEvaluator {
14
14
  writable: true,
15
15
  value: void 0
16
16
  });
17
- const wrappedFunc = (input) => {
18
- const runAndExample = input.langSmithRunAndExample;
19
- return evaluator(...Object.values(runAndExample));
20
- };
21
- this.func = wrappedFunc;
17
+ this.func = ((input) => {
18
+ const { run, example } = input.langSmithRunAndExample;
19
+ return evaluator(run, example);
20
+ });
22
21
  }
23
22
  coerceEvaluationResults(results, sourceRunId) {
24
23
  if ("results" in results) {
@@ -57,17 +56,15 @@ class DynamicRunEvaluator {
57
56
  if ("session_id" in run) {
58
57
  metadata["experiment"] = run.session_id;
59
58
  }
60
- const wrappedTraceableFunc = (0, traceable_js_1.wrapFunctionAndEnsureTraceable)(this.func, options || {}, "evaluator");
59
+ if (typeof this.func !== "function") {
60
+ throw new Error("Target must be runnable function");
61
+ }
62
+ const wrappedTraceableFunc = (0, traceable_js_1.traceable)(this.func, { project_name: "evaluators", name: "evaluator", ...options });
63
+ const result = (await wrappedTraceableFunc(
61
64
  // Pass data via `langSmithRunAndExample` key to avoid conflicts with other
62
65
  // inputs. This key is extracted in the wrapped function, with `run` and
63
66
  // `example` passed to evaluator function as arguments.
64
- const langSmithRunAndExample = {
65
- run,
66
- example,
67
- };
68
- const result = (await wrappedTraceableFunc({ langSmithRunAndExample }, {
69
- metadata,
70
- }));
67
+ { langSmithRunAndExample: { run, example } }, { metadata }));
71
68
  // Check the one required property of EvaluationResult since 'instanceof' is not possible
72
69
  if ("key" in result) {
73
70
  if (!result.sourceRunId) {
@@ -1,5 +1,5 @@
1
1
  import { v4 as uuidv4 } from "uuid";
2
- import { wrapFunctionAndEnsureTraceable } from "../traceable.js";
2
+ import { traceable } from "../traceable.js";
3
3
  /**
4
4
  * Wraps an evaluator function + implements the RunEvaluator interface.
5
5
  */
@@ -11,11 +11,10 @@ export class DynamicRunEvaluator {
11
11
  writable: true,
12
12
  value: void 0
13
13
  });
14
- const wrappedFunc = (input) => {
15
- const runAndExample = input.langSmithRunAndExample;
16
- return evaluator(...Object.values(runAndExample));
17
- };
18
- this.func = wrappedFunc;
14
+ this.func = ((input) => {
15
+ const { run, example } = input.langSmithRunAndExample;
16
+ return evaluator(run, example);
17
+ });
19
18
  }
20
19
  coerceEvaluationResults(results, sourceRunId) {
21
20
  if ("results" in results) {
@@ -54,17 +53,15 @@ export class DynamicRunEvaluator {
54
53
  if ("session_id" in run) {
55
54
  metadata["experiment"] = run.session_id;
56
55
  }
57
- const wrappedTraceableFunc = wrapFunctionAndEnsureTraceable(this.func, options || {}, "evaluator");
56
+ if (typeof this.func !== "function") {
57
+ throw new Error("Target must be runnable function");
58
+ }
59
+ const wrappedTraceableFunc = traceable(this.func, { project_name: "evaluators", name: "evaluator", ...options });
60
+ const result = (await wrappedTraceableFunc(
58
61
  // Pass data via `langSmithRunAndExample` key to avoid conflicts with other
59
62
  // inputs. This key is extracted in the wrapped function, with `run` and
60
63
  // `example` passed to evaluator function as arguments.
61
- const langSmithRunAndExample = {
62
- run,
63
- example,
64
- };
65
- const result = (await wrappedTraceableFunc({ langSmithRunAndExample }, {
66
- metadata,
67
- }));
64
+ { langSmithRunAndExample: { run, example } }, { metadata }));
68
65
  // Check the one required property of EvaluationResult since 'instanceof' is not possible
69
66
  if ("key" in result) {
70
67
  if (!result.sourceRunId) {
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.evaluate = exports.StringEvaluator = void 0;
3
+ exports.evaluateComparative = exports.evaluate = exports.StringEvaluator = void 0;
4
4
  var string_evaluator_js_1 = require("./string_evaluator.cjs");
5
5
  Object.defineProperty(exports, "StringEvaluator", { enumerable: true, get: function () { return string_evaluator_js_1.StringEvaluator; } });
6
6
  var _runner_js_1 = require("./_runner.cjs");
7
7
  Object.defineProperty(exports, "evaluate", { enumerable: true, get: function () { return _runner_js_1.evaluate; } });
8
+ var evaluate_comparative_js_1 = require("./evaluate_comparative.cjs");
9
+ Object.defineProperty(exports, "evaluateComparative", { enumerable: true, get: function () { return evaluate_comparative_js_1.evaluateComparative; } });
@@ -1,3 +1,4 @@
1
1
  export { RunEvaluator, EvaluationResult } from "./evaluator.js";
2
2
  export { StringEvaluator, GradingFunctionParams, GradingFunctionResult, } from "./string_evaluator.js";
3
3
  export { evaluate, type EvaluateOptions } from "./_runner.js";
4
+ export { evaluateComparative } from "./evaluate_comparative.js";
@@ -1,2 +1,3 @@
1
1
  export { StringEvaluator, } from "./string_evaluator.js";
2
2
  export { evaluate } from "./_runner.js";
3
+ export { evaluateComparative } from "./evaluate_comparative.js";
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.23";
9
+ exports.__version__ = "0.1.24";
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, } from "./schemas.js";
3
3
  export { RunTree, type RunTreeConfig } from "./run_trees.js";
4
- export declare const __version__ = "0.1.23";
4
+ export declare const __version__ = "0.1.24";
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.23";
4
+ export const __version__ = "0.1.24";
@@ -339,6 +339,7 @@ class RunTree {
339
339
  const runUpdate = {
340
340
  end_time: this.end_time,
341
341
  error: this.error,
342
+ inputs: this.inputs,
342
343
  outputs: this.outputs,
343
344
  parent_run_id: this.parent_run?.id,
344
345
  reference_example_id: this.reference_example_id,
package/dist/run_trees.js CHANGED
@@ -312,6 +312,7 @@ export class RunTree {
312
312
  const runUpdate = {
313
313
  end_time: this.end_time,
314
314
  error: this.error,
315
+ inputs: this.inputs,
315
316
  outputs: this.outputs,
316
317
  parent_run_id: this.parent_run?.id,
317
318
  reference_example_id: this.reference_example_id,
package/dist/schemas.d.ts CHANGED
@@ -276,4 +276,21 @@ export interface DatasetDiffInfo {
276
276
  examples_added: string[];
277
277
  examples_removed: string[];
278
278
  }
279
+ export interface ComparisonEvaluationResult {
280
+ key: string;
281
+ scores: Record<string, ScoreType>;
282
+ source_run_id?: string;
283
+ }
284
+ export interface ComparativeExperiment {
285
+ id: string;
286
+ name: string;
287
+ description: string;
288
+ tenant_id: string;
289
+ created_at: string;
290
+ modified_at: string;
291
+ reference_dataset_id: string;
292
+ extra?: Record<string, unknown>;
293
+ experiments_info?: Array<Record<string, unknown>>;
294
+ feedback_stats?: Record<string, unknown>;
295
+ }
279
296
  export {};
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.wrapFunctionAndEnsureTraceable = exports.isTraceableFunction = exports.getCurrentRunTree = exports.traceable = exports.ROOT = void 0;
4
- const async_hooks_1 = require("async_hooks");
4
+ const node_async_hooks_1 = require("node:async_hooks");
5
5
  const run_trees_js_1 = require("./run_trees.cjs");
6
6
  const env_js_1 = require("./utils/env.cjs");
7
7
  function isPromiseMethod(x) {
@@ -10,12 +10,28 @@ function isPromiseMethod(x) {
10
10
  }
11
11
  return false;
12
12
  }
13
- const asyncLocalStorage = new async_hooks_1.AsyncLocalStorage();
13
+ const asyncLocalStorage = new node_async_hooks_1.AsyncLocalStorage();
14
14
  exports.ROOT = Symbol("langsmith:traceable:root");
15
15
  const isAsyncIterable = (x) => x != null &&
16
16
  typeof x === "object" &&
17
17
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
18
18
  typeof x[Symbol.asyncIterator] === "function";
19
+ const GeneratorFunction = function* () { }.constructor;
20
+ const isIteratorLike = (x) => x != null &&
21
+ typeof x === "object" &&
22
+ "next" in x &&
23
+ typeof x.next === "function";
24
+ const isGenerator = (x) =>
25
+ // eslint-disable-next-line no-instanceof/no-instanceof
26
+ x != null && typeof x === "function" && x instanceof GeneratorFunction;
27
+ const isThenable = (x) => x != null &&
28
+ typeof x === "object" &&
29
+ "then" in x &&
30
+ typeof x.then === "function";
31
+ const isReadableStream = (x) => x != null &&
32
+ typeof x === "object" &&
33
+ "getReader" in x &&
34
+ typeof x.getReader === "function";
19
35
  const tracingIsEnabled = (tracingEnabled) => {
20
36
  if (tracingEnabled !== undefined) {
21
37
  return tracingEnabled;
@@ -55,6 +71,141 @@ const getTracingRunTree = (runTree, inputs) => {
55
71
  runTree.inputs = handleRunInputs(inputs);
56
72
  return runTree;
57
73
  };
74
+ // idea: store the state of the promise outside
75
+ // but only when the promise is "consumed"
76
+ const getSerializablePromise = (arg) => {
77
+ const proxyState = { current: undefined };
78
+ const promiseProxy = new Proxy(arg, {
79
+ get(target, prop, receiver) {
80
+ if (prop === "then") {
81
+ const boundThen = arg[prop].bind(arg);
82
+ return (resolve, reject = (x) => {
83
+ throw x;
84
+ }) => {
85
+ return boundThen((value) => {
86
+ proxyState.current = ["resolve", value];
87
+ return resolve(value);
88
+ }, (error) => {
89
+ proxyState.current = ["reject", error];
90
+ return reject(error);
91
+ });
92
+ };
93
+ }
94
+ if (prop === "catch") {
95
+ const boundCatch = arg[prop].bind(arg);
96
+ return (reject) => {
97
+ return boundCatch((error) => {
98
+ proxyState.current = ["reject", error];
99
+ return reject(error);
100
+ });
101
+ };
102
+ }
103
+ if (prop === "toJSON") {
104
+ return () => {
105
+ if (!proxyState.current)
106
+ return undefined;
107
+ const [type, value] = proxyState.current ?? [];
108
+ if (type === "resolve")
109
+ return value;
110
+ return { error: value };
111
+ };
112
+ }
113
+ return Reflect.get(target, prop, receiver);
114
+ },
115
+ });
116
+ return promiseProxy;
117
+ };
118
+ const convertSerializableArg = (arg) => {
119
+ if (isReadableStream(arg)) {
120
+ const proxyState = [];
121
+ const transform = new TransformStream({
122
+ start: () => void 0,
123
+ transform: (chunk, controller) => {
124
+ proxyState.push(chunk);
125
+ controller.enqueue(chunk);
126
+ },
127
+ flush: () => void 0,
128
+ });
129
+ const pipeThrough = arg.pipeThrough(transform);
130
+ Object.assign(pipeThrough, { toJSON: () => proxyState });
131
+ return pipeThrough;
132
+ }
133
+ if (isAsyncIterable(arg)) {
134
+ const proxyState = { current: [] };
135
+ return new Proxy(arg, {
136
+ get(target, prop, receiver) {
137
+ if (prop === Symbol.asyncIterator) {
138
+ return () => {
139
+ const boundIterator = arg[Symbol.asyncIterator].bind(arg);
140
+ const iterator = boundIterator();
141
+ return new Proxy(iterator, {
142
+ get(target, prop, receiver) {
143
+ if (prop === "next" || prop === "return" || prop === "throw") {
144
+ const bound = iterator.next.bind(iterator);
145
+ return (...args) => {
146
+ // @ts-expect-error TS cannot infer the argument types for the bound function
147
+ const wrapped = getSerializablePromise(bound(...args));
148
+ proxyState.current.push(wrapped);
149
+ return wrapped;
150
+ };
151
+ }
152
+ if (prop === "return" || prop === "throw") {
153
+ return iterator.next.bind(iterator);
154
+ }
155
+ return Reflect.get(target, prop, receiver);
156
+ },
157
+ });
158
+ };
159
+ }
160
+ if (prop === "toJSON") {
161
+ return () => {
162
+ const onlyNexts = proxyState.current;
163
+ const serialized = onlyNexts.map((next) => next.toJSON());
164
+ const chunks = serialized.reduce((memo, next) => {
165
+ if (next?.value)
166
+ memo.push(next.value);
167
+ return memo;
168
+ }, []);
169
+ return chunks;
170
+ };
171
+ }
172
+ return Reflect.get(target, prop, receiver);
173
+ },
174
+ });
175
+ }
176
+ if (!Array.isArray(arg) && isIteratorLike(arg)) {
177
+ const proxyState = [];
178
+ return new Proxy(arg, {
179
+ get(target, prop, receiver) {
180
+ if (prop === "next" || prop === "return" || prop === "throw") {
181
+ const bound = arg[prop]?.bind(arg);
182
+ return (...args) => {
183
+ // @ts-expect-error TS cannot infer the argument types for the bound function
184
+ const next = bound?.(...args);
185
+ if (next != null)
186
+ proxyState.push(next);
187
+ return next;
188
+ };
189
+ }
190
+ if (prop === "toJSON") {
191
+ return () => {
192
+ const chunks = proxyState.reduce((memo, next) => {
193
+ if (next.value)
194
+ memo.push(next.value);
195
+ return memo;
196
+ }, []);
197
+ return chunks;
198
+ };
199
+ }
200
+ return Reflect.get(target, prop, receiver);
201
+ },
202
+ });
203
+ }
204
+ if (isThenable(arg)) {
205
+ return getSerializablePromise(arg);
206
+ }
207
+ return arg;
208
+ };
58
209
  /**
59
210
  * Higher-order function that takes function as input and returns a
60
211
  * "TraceableFunction" - a wrapped version of the input that
@@ -118,8 +269,13 @@ function traceable(wrappedFunc, config) {
118
269
  ...runTreeConfig,
119
270
  };
120
271
  }
272
+ // TODO: deal with possible nested promises and async iterables
273
+ const processedArgs = args;
274
+ for (let i = 0; i < processedArgs.length; i++) {
275
+ processedArgs[i] = convertSerializableArg(processedArgs[i]);
276
+ }
121
277
  const [currentRunTree, rawInputs] = (() => {
122
- const [firstArg, ...restArgs] = args;
278
+ const [firstArg, ...restArgs] = processedArgs;
123
279
  // used for handoff between LangChain.JS and traceable functions
124
280
  if ((0, run_trees_js_1.isRunnableConfigLike)(firstArg)) {
125
281
  return [
@@ -147,12 +303,12 @@ function traceable(wrappedFunc, config) {
147
303
  const prevRunFromStore = asyncLocalStorage.getStore();
148
304
  if (prevRunFromStore) {
149
305
  return [
150
- getTracingRunTree(prevRunFromStore.createChild(ensuredConfig), args),
151
- args,
306
+ getTracingRunTree(prevRunFromStore.createChild(ensuredConfig), processedArgs),
307
+ processedArgs,
152
308
  ];
153
309
  }
154
- const currentRunTree = getTracingRunTree(new run_trees_js_1.RunTree(ensuredConfig), args);
155
- return [currentRunTree, args];
310
+ const currentRunTree = getTracingRunTree(new run_trees_js_1.RunTree(ensuredConfig), processedArgs);
311
+ return [currentRunTree, processedArgs];
156
312
  })();
157
313
  return asyncLocalStorage.run(currentRunTree, () => {
158
314
  const postRunPromise = currentRunTree?.postRun();
@@ -208,6 +364,17 @@ function traceable(wrappedFunc, config) {
208
364
  await postRunPromise;
209
365
  await currentRunTree?.patchRun();
210
366
  }
367
+ function gatherAll(iterator) {
368
+ const chunks = [];
369
+ // eslint-disable-next-line no-constant-condition
370
+ while (true) {
371
+ const next = iterator.next();
372
+ chunks.push(next);
373
+ if (next.done)
374
+ break;
375
+ }
376
+ return chunks;
377
+ }
211
378
  let returnValue;
212
379
  try {
213
380
  returnValue = wrappedFunc(...rawInputs);
@@ -216,25 +383,40 @@ function traceable(wrappedFunc, config) {
216
383
  returnValue = Promise.reject(err);
217
384
  }
218
385
  if (isAsyncIterable(returnValue)) {
219
- const snapshot = async_hooks_1.AsyncLocalStorage.snapshot();
386
+ const snapshot = node_async_hooks_1.AsyncLocalStorage.snapshot();
220
387
  return wrapAsyncGeneratorForTracing(returnValue, snapshot);
221
388
  }
222
389
  const tracedPromise = new Promise((resolve, reject) => {
223
390
  Promise.resolve(returnValue)
224
391
  .then(async (rawOutput) => {
225
392
  if (isAsyncIterable(rawOutput)) {
226
- const snapshot = async_hooks_1.AsyncLocalStorage.snapshot();
393
+ const snapshot = node_async_hooks_1.AsyncLocalStorage.snapshot();
227
394
  return resolve(wrapAsyncGeneratorForTracing(rawOutput, snapshot));
228
395
  }
229
- else {
230
- try {
231
- await currentRunTree?.end(handleRunOutputs(rawOutput));
232
- await handleEnd();
233
- }
234
- finally {
235
- // eslint-disable-next-line no-unsafe-finally
236
- return rawOutput;
237
- }
396
+ if (isGenerator(wrappedFunc) && isIteratorLike(rawOutput)) {
397
+ const chunks = gatherAll(rawOutput);
398
+ await currentRunTree?.end(handleRunOutputs(await handleChunks(chunks.reduce((memo, { value, done }) => {
399
+ if (!done || typeof value !== "undefined") {
400
+ memo.push(value);
401
+ }
402
+ return memo;
403
+ }, []))));
404
+ await handleEnd();
405
+ return (function* () {
406
+ for (const ret of chunks) {
407
+ if (ret.done)
408
+ return ret.value;
409
+ yield ret.value;
410
+ }
411
+ })();
412
+ }
413
+ try {
414
+ await currentRunTree?.end(handleRunOutputs(rawOutput));
415
+ await handleEnd();
416
+ }
417
+ finally {
418
+ // eslint-disable-next-line no-unsafe-finally
419
+ return rawOutput;
238
420
  }
239
421
  }, async (error) => {
240
422
  await currentRunTree?.end(undefined, String(error));
@@ -287,11 +469,15 @@ function isTraceableFunction(x
287
469
  }
288
470
  exports.isTraceableFunction = isTraceableFunction;
289
471
  function isKVMap(x) {
290
- return (typeof x === "object" &&
291
- x != null &&
292
- !Array.isArray(x) &&
293
- // eslint-disable-next-line no-instanceof/no-instanceof
294
- !(x instanceof Date));
472
+ if (typeof x !== "object" || x == null) {
473
+ return false;
474
+ }
475
+ const prototype = Object.getPrototypeOf(x);
476
+ return ((prototype === null ||
477
+ prototype === Object.prototype ||
478
+ Object.getPrototypeOf(prototype) === null) &&
479
+ !(Symbol.toStringTag in x) &&
480
+ !(Symbol.iterator in x));
295
481
  }
296
482
  function wrapFunctionAndEnsureTraceable(target, options, name = "target") {
297
483
  if (typeof target === "function") {