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.
package/dist/traceable.js CHANGED
@@ -1,4 +1,4 @@
1
- import { AsyncLocalStorage } from "async_hooks";
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
2
  import { RunTree, isRunTree, isRunnableConfigLike, } from "./run_trees.js";
3
3
  import { getEnvironmentVariable } from "./utils/env.js";
4
4
  function isPromiseMethod(x) {
@@ -13,6 +13,22 @@ const isAsyncIterable = (x) => x != null &&
13
13
  typeof x === "object" &&
14
14
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
15
15
  typeof x[Symbol.asyncIterator] === "function";
16
+ const GeneratorFunction = function* () { }.constructor;
17
+ const isIteratorLike = (x) => x != null &&
18
+ typeof x === "object" &&
19
+ "next" in x &&
20
+ typeof x.next === "function";
21
+ const isGenerator = (x) =>
22
+ // eslint-disable-next-line no-instanceof/no-instanceof
23
+ x != null && typeof x === "function" && x instanceof GeneratorFunction;
24
+ const isThenable = (x) => x != null &&
25
+ typeof x === "object" &&
26
+ "then" in x &&
27
+ typeof x.then === "function";
28
+ const isReadableStream = (x) => x != null &&
29
+ typeof x === "object" &&
30
+ "getReader" in x &&
31
+ typeof x.getReader === "function";
16
32
  const tracingIsEnabled = (tracingEnabled) => {
17
33
  if (tracingEnabled !== undefined) {
18
34
  return tracingEnabled;
@@ -52,6 +68,141 @@ const getTracingRunTree = (runTree, inputs) => {
52
68
  runTree.inputs = handleRunInputs(inputs);
53
69
  return runTree;
54
70
  };
71
+ // idea: store the state of the promise outside
72
+ // but only when the promise is "consumed"
73
+ const getSerializablePromise = (arg) => {
74
+ const proxyState = { current: undefined };
75
+ const promiseProxy = new Proxy(arg, {
76
+ get(target, prop, receiver) {
77
+ if (prop === "then") {
78
+ const boundThen = arg[prop].bind(arg);
79
+ return (resolve, reject = (x) => {
80
+ throw x;
81
+ }) => {
82
+ return boundThen((value) => {
83
+ proxyState.current = ["resolve", value];
84
+ return resolve(value);
85
+ }, (error) => {
86
+ proxyState.current = ["reject", error];
87
+ return reject(error);
88
+ });
89
+ };
90
+ }
91
+ if (prop === "catch") {
92
+ const boundCatch = arg[prop].bind(arg);
93
+ return (reject) => {
94
+ return boundCatch((error) => {
95
+ proxyState.current = ["reject", error];
96
+ return reject(error);
97
+ });
98
+ };
99
+ }
100
+ if (prop === "toJSON") {
101
+ return () => {
102
+ if (!proxyState.current)
103
+ return undefined;
104
+ const [type, value] = proxyState.current ?? [];
105
+ if (type === "resolve")
106
+ return value;
107
+ return { error: value };
108
+ };
109
+ }
110
+ return Reflect.get(target, prop, receiver);
111
+ },
112
+ });
113
+ return promiseProxy;
114
+ };
115
+ const convertSerializableArg = (arg) => {
116
+ if (isReadableStream(arg)) {
117
+ const proxyState = [];
118
+ const transform = new TransformStream({
119
+ start: () => void 0,
120
+ transform: (chunk, controller) => {
121
+ proxyState.push(chunk);
122
+ controller.enqueue(chunk);
123
+ },
124
+ flush: () => void 0,
125
+ });
126
+ const pipeThrough = arg.pipeThrough(transform);
127
+ Object.assign(pipeThrough, { toJSON: () => proxyState });
128
+ return pipeThrough;
129
+ }
130
+ if (isAsyncIterable(arg)) {
131
+ const proxyState = { current: [] };
132
+ return new Proxy(arg, {
133
+ get(target, prop, receiver) {
134
+ if (prop === Symbol.asyncIterator) {
135
+ return () => {
136
+ const boundIterator = arg[Symbol.asyncIterator].bind(arg);
137
+ const iterator = boundIterator();
138
+ return new Proxy(iterator, {
139
+ get(target, prop, receiver) {
140
+ if (prop === "next" || prop === "return" || prop === "throw") {
141
+ const bound = iterator.next.bind(iterator);
142
+ return (...args) => {
143
+ // @ts-expect-error TS cannot infer the argument types for the bound function
144
+ const wrapped = getSerializablePromise(bound(...args));
145
+ proxyState.current.push(wrapped);
146
+ return wrapped;
147
+ };
148
+ }
149
+ if (prop === "return" || prop === "throw") {
150
+ return iterator.next.bind(iterator);
151
+ }
152
+ return Reflect.get(target, prop, receiver);
153
+ },
154
+ });
155
+ };
156
+ }
157
+ if (prop === "toJSON") {
158
+ return () => {
159
+ const onlyNexts = proxyState.current;
160
+ const serialized = onlyNexts.map((next) => next.toJSON());
161
+ const chunks = serialized.reduce((memo, next) => {
162
+ if (next?.value)
163
+ memo.push(next.value);
164
+ return memo;
165
+ }, []);
166
+ return chunks;
167
+ };
168
+ }
169
+ return Reflect.get(target, prop, receiver);
170
+ },
171
+ });
172
+ }
173
+ if (!Array.isArray(arg) && isIteratorLike(arg)) {
174
+ const proxyState = [];
175
+ return new Proxy(arg, {
176
+ get(target, prop, receiver) {
177
+ if (prop === "next" || prop === "return" || prop === "throw") {
178
+ const bound = arg[prop]?.bind(arg);
179
+ return (...args) => {
180
+ // @ts-expect-error TS cannot infer the argument types for the bound function
181
+ const next = bound?.(...args);
182
+ if (next != null)
183
+ proxyState.push(next);
184
+ return next;
185
+ };
186
+ }
187
+ if (prop === "toJSON") {
188
+ return () => {
189
+ const chunks = proxyState.reduce((memo, next) => {
190
+ if (next.value)
191
+ memo.push(next.value);
192
+ return memo;
193
+ }, []);
194
+ return chunks;
195
+ };
196
+ }
197
+ return Reflect.get(target, prop, receiver);
198
+ },
199
+ });
200
+ }
201
+ if (isThenable(arg)) {
202
+ return getSerializablePromise(arg);
203
+ }
204
+ return arg;
205
+ };
55
206
  /**
56
207
  * Higher-order function that takes function as input and returns a
57
208
  * "TraceableFunction" - a wrapped version of the input that
@@ -115,8 +266,13 @@ export function traceable(wrappedFunc, config) {
115
266
  ...runTreeConfig,
116
267
  };
117
268
  }
269
+ // TODO: deal with possible nested promises and async iterables
270
+ const processedArgs = args;
271
+ for (let i = 0; i < processedArgs.length; i++) {
272
+ processedArgs[i] = convertSerializableArg(processedArgs[i]);
273
+ }
118
274
  const [currentRunTree, rawInputs] = (() => {
119
- const [firstArg, ...restArgs] = args;
275
+ const [firstArg, ...restArgs] = processedArgs;
120
276
  // used for handoff between LangChain.JS and traceable functions
121
277
  if (isRunnableConfigLike(firstArg)) {
122
278
  return [
@@ -144,12 +300,12 @@ export function traceable(wrappedFunc, config) {
144
300
  const prevRunFromStore = asyncLocalStorage.getStore();
145
301
  if (prevRunFromStore) {
146
302
  return [
147
- getTracingRunTree(prevRunFromStore.createChild(ensuredConfig), args),
148
- args,
303
+ getTracingRunTree(prevRunFromStore.createChild(ensuredConfig), processedArgs),
304
+ processedArgs,
149
305
  ];
150
306
  }
151
- const currentRunTree = getTracingRunTree(new RunTree(ensuredConfig), args);
152
- return [currentRunTree, args];
307
+ const currentRunTree = getTracingRunTree(new RunTree(ensuredConfig), processedArgs);
308
+ return [currentRunTree, processedArgs];
153
309
  })();
154
310
  return asyncLocalStorage.run(currentRunTree, () => {
155
311
  const postRunPromise = currentRunTree?.postRun();
@@ -205,6 +361,17 @@ export function traceable(wrappedFunc, config) {
205
361
  await postRunPromise;
206
362
  await currentRunTree?.patchRun();
207
363
  }
364
+ function gatherAll(iterator) {
365
+ const chunks = [];
366
+ // eslint-disable-next-line no-constant-condition
367
+ while (true) {
368
+ const next = iterator.next();
369
+ chunks.push(next);
370
+ if (next.done)
371
+ break;
372
+ }
373
+ return chunks;
374
+ }
208
375
  let returnValue;
209
376
  try {
210
377
  returnValue = wrappedFunc(...rawInputs);
@@ -223,15 +390,30 @@ export function traceable(wrappedFunc, config) {
223
390
  const snapshot = AsyncLocalStorage.snapshot();
224
391
  return resolve(wrapAsyncGeneratorForTracing(rawOutput, snapshot));
225
392
  }
226
- else {
227
- try {
228
- await currentRunTree?.end(handleRunOutputs(rawOutput));
229
- await handleEnd();
230
- }
231
- finally {
232
- // eslint-disable-next-line no-unsafe-finally
233
- return rawOutput;
234
- }
393
+ if (isGenerator(wrappedFunc) && isIteratorLike(rawOutput)) {
394
+ const chunks = gatherAll(rawOutput);
395
+ await currentRunTree?.end(handleRunOutputs(await handleChunks(chunks.reduce((memo, { value, done }) => {
396
+ if (!done || typeof value !== "undefined") {
397
+ memo.push(value);
398
+ }
399
+ return memo;
400
+ }, []))));
401
+ await handleEnd();
402
+ return (function* () {
403
+ for (const ret of chunks) {
404
+ if (ret.done)
405
+ return ret.value;
406
+ yield ret.value;
407
+ }
408
+ })();
409
+ }
410
+ try {
411
+ await currentRunTree?.end(handleRunOutputs(rawOutput));
412
+ await handleEnd();
413
+ }
414
+ finally {
415
+ // eslint-disable-next-line no-unsafe-finally
416
+ return rawOutput;
235
417
  }
236
418
  }, async (error) => {
237
419
  await currentRunTree?.end(undefined, String(error));
@@ -281,11 +463,15 @@ export function isTraceableFunction(x
281
463
  return typeof x === "function" && "langsmith:traceable" in x;
282
464
  }
283
465
  function isKVMap(x) {
284
- return (typeof x === "object" &&
285
- x != null &&
286
- !Array.isArray(x) &&
287
- // eslint-disable-next-line no-instanceof/no-instanceof
288
- !(x instanceof Date));
466
+ if (typeof x !== "object" || x == null) {
467
+ return false;
468
+ }
469
+ const prototype = Object.getPrototypeOf(x);
470
+ return ((prototype === null ||
471
+ prototype === Object.prototype ||
472
+ Object.getPrototypeOf(prototype) === null) &&
473
+ !(Symbol.toStringTag in x) &&
474
+ !(Symbol.iterator in x));
289
475
  }
290
476
  export function wrapFunctionAndEnsureTraceable(target, options, name = "target") {
291
477
  if (typeof target === "function") {
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.shuffle = void 0;
4
+ function shuffle(array) {
5
+ let currentIndex = array.length;
6
+ while (currentIndex !== 0) {
7
+ const randomIndex = Math.floor(Math.random() * currentIndex);
8
+ currentIndex -= 1;
9
+ const tmp = array[currentIndex];
10
+ array[currentIndex] = array[randomIndex];
11
+ array[randomIndex] = tmp;
12
+ }
13
+ return array;
14
+ }
15
+ exports.shuffle = shuffle;
@@ -0,0 +1 @@
1
+ export declare function shuffle<T extends unknown[]>(array: T): T;
@@ -0,0 +1,11 @@
1
+ export function shuffle(array) {
2
+ let currentIndex = array.length;
3
+ while (currentIndex !== 0) {
4
+ const randomIndex = Math.floor(Math.random() * currentIndex);
5
+ currentIndex -= 1;
6
+ const tmp = array[currentIndex];
7
+ array[currentIndex] = array[randomIndex];
8
+ array[randomIndex] = tmp;
9
+ }
10
+ return array;
11
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "langsmith",
3
- "version": "0.1.23",
3
+ "version": "0.1.24",
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": [