langsmith 0.5.23 → 0.5.25

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.
Files changed (89) hide show
  1. package/dist/client.cjs +102 -11
  2. package/dist/client.d.ts +38 -0
  3. package/dist/client.js +103 -12
  4. package/dist/evaluation/_runner.cjs +3 -3
  5. package/dist/evaluation/_runner.js +1 -1
  6. package/dist/evaluation/evaluate_comparative.cjs +10 -10
  7. package/dist/evaluation/evaluate_comparative.js +1 -1
  8. package/dist/evaluation/evaluator.cjs +2 -2
  9. package/dist/evaluation/evaluator.js +1 -1
  10. package/dist/index.cjs +1 -1
  11. package/dist/index.d.ts +1 -1
  12. package/dist/index.js +1 -1
  13. package/dist/run_trees.cjs +8 -7
  14. package/dist/run_trees.d.ts +7 -0
  15. package/dist/run_trees.js +7 -6
  16. package/dist/schemas.d.ts +4 -0
  17. package/dist/singletons/otel.cjs +3 -2
  18. package/dist/singletons/otel.js +4 -3
  19. package/dist/traceable.cjs +1 -2
  20. package/dist/traceable.js +1 -2
  21. package/dist/utils/_uuid.cjs +2 -2
  22. package/dist/utils/_uuid.js +1 -1
  23. package/dist/utils/env.cjs +33 -0
  24. package/dist/utils/env.d.ts +9 -0
  25. package/dist/utils/env.js +32 -0
  26. package/dist/utils/fast-safe-stringify/index.cjs +10 -35
  27. package/dist/utils/fast-safe-stringify/index.d.ts +14 -1
  28. package/dist/utils/fast-safe-stringify/index.js +10 -35
  29. package/dist/utils/jestlike/index.cjs +5 -5
  30. package/dist/utils/jestlike/index.js +1 -1
  31. package/dist/utils/jestlike/vendor/evaluatedBy.cjs +3 -3
  32. package/dist/utils/jestlike/vendor/evaluatedBy.js +1 -1
  33. package/dist/utils/serialize_worker.cjs +389 -0
  34. package/dist/utils/serialize_worker.d.ts +67 -0
  35. package/dist/utils/serialize_worker.js +383 -0
  36. package/dist/utils/uuid/src/index.cjs +24 -0
  37. package/dist/utils/uuid/src/index.d.ts +10 -0
  38. package/dist/utils/uuid/src/index.js +9 -0
  39. package/dist/utils/uuid/src/max.cjs +3 -0
  40. package/dist/utils/uuid/src/max.d.ts +2 -0
  41. package/dist/utils/uuid/src/max.js +1 -0
  42. package/dist/utils/uuid/src/nil.cjs +3 -0
  43. package/dist/utils/uuid/src/nil.d.ts +2 -0
  44. package/dist/utils/uuid/src/nil.js +1 -0
  45. package/dist/utils/uuid/src/parse.cjs +23 -0
  46. package/dist/utils/uuid/src/parse.d.ts +3 -0
  47. package/dist/utils/uuid/src/parse.js +18 -0
  48. package/dist/utils/uuid/src/regex.cjs +3 -0
  49. package/dist/utils/uuid/src/regex.d.ts +2 -0
  50. package/dist/utils/uuid/src/regex.js +1 -0
  51. package/dist/utils/uuid/src/rng.cjs +10 -0
  52. package/dist/utils/uuid/src/rng.d.ts +1 -0
  53. package/dist/utils/uuid/src/rng.js +7 -0
  54. package/dist/utils/uuid/src/sha1.cjs +75 -0
  55. package/dist/utils/uuid/src/sha1.d.ts +2 -0
  56. package/dist/utils/uuid/src/sha1.js +73 -0
  57. package/dist/utils/uuid/src/stringify.cjs +55 -0
  58. package/dist/utils/uuid/src/stringify.d.ts +3 -0
  59. package/dist/utils/uuid/src/stringify.js +49 -0
  60. package/dist/utils/uuid/src/types.cjs +2 -0
  61. package/dist/utils/uuid/src/types.d.ts +22 -0
  62. package/dist/utils/uuid/src/types.js +1 -0
  63. package/dist/utils/uuid/src/v35.cjs +52 -0
  64. package/dist/utils/uuid/src/v35.d.ts +7 -0
  65. package/dist/utils/uuid/src/v35.js +44 -0
  66. package/dist/utils/uuid/src/v4.cjs +40 -0
  67. package/dist/utils/uuid/src/v4.d.ts +4 -0
  68. package/dist/utils/uuid/src/v4.js +35 -0
  69. package/dist/utils/uuid/src/v5.cjs +50 -0
  70. package/dist/utils/uuid/src/v5.d.ts +9 -0
  71. package/dist/utils/uuid/src/v5.js +9 -0
  72. package/dist/utils/uuid/src/v7.cjs +88 -0
  73. package/dist/utils/uuid/src/v7.d.ts +9 -0
  74. package/dist/utils/uuid/src/v7.js +82 -0
  75. package/dist/utils/uuid/src/validate.cjs +10 -0
  76. package/dist/utils/uuid/src/validate.d.ts +2 -0
  77. package/dist/utils/uuid/src/validate.js +5 -0
  78. package/dist/utils/uuid/src/version.cjs +13 -0
  79. package/dist/utils/uuid/src/version.d.ts +2 -0
  80. package/dist/utils/uuid/src/version.js +8 -0
  81. package/dist/utils/worker_threads.browser.cjs +16 -0
  82. package/dist/utils/worker_threads.browser.d.ts +14 -0
  83. package/dist/utils/worker_threads.browser.js +13 -0
  84. package/dist/utils/worker_threads.cjs +16 -0
  85. package/dist/utils/worker_threads.d.ts +13 -0
  86. package/dist/utils/worker_threads.js +13 -0
  87. package/dist/uuid.cjs +2 -2
  88. package/dist/uuid.js +1 -1
  89. package/package.json +4 -4
package/dist/client.cjs CHANGED
@@ -35,7 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.Client = exports.AutoBatchQueue = exports.DEFAULT_MAX_SIZE_BYTES = exports.DEFAULT_UNCOMPRESSED_BATCH_SIZE_LIMIT_BYTES = void 0;
37
37
  exports.mergeRuntimeEnvIntoRun = mergeRuntimeEnvIntoRun;
38
- const uuid = __importStar(require("uuid"));
38
+ const uuid = __importStar(require("./utils/uuid/src/index.cjs"));
39
39
  const translator_js_1 = require("./experimental/otel/translator.cjs");
40
40
  const otel_js_1 = require("./singletons/otel.cjs");
41
41
  const async_caller_js_1 = require("./utils/async_caller.cjs");
@@ -50,6 +50,7 @@ const index_js_2 = require("./utils/prompt_cache/index.cjs");
50
50
  const fsUtils = __importStar(require("./utils/fs.cjs"));
51
51
  const fetch_js_1 = require("./singletons/fetch.cjs");
52
52
  const index_js_3 = require("./utils/fast-safe-stringify/index.cjs");
53
+ const serialize_worker_js_1 = require("./utils/serialize_worker.cjs");
53
54
  /**
54
55
  * Catches timestamps without a timezone suffix.
55
56
  */
@@ -200,7 +201,7 @@ class AutoBatchQueue {
200
201
  // real serialization still happens later, off the hot path, when the
201
202
  // batch is assembled for sending.
202
203
  const size = (0, env_js_1.getLangSmithEnvironmentVariable)("PERF_OPTIMIZATION") === "true"
203
- ? (0, index_js_3.estimateSerializedSize)(item.item)
204
+ ? (0, index_js_3.estimateSerializedSize)(item.item).size
204
205
  : (0, index_js_3.serialize)(item.item, `Serializing run with id: ${item.item.id}`).length;
205
206
  // Check if adding this item would exceed the size limit
206
207
  // Allow the run if the queue is empty (to support large single traces)
@@ -265,9 +266,67 @@ class AutoBatchQueue {
265
266
  }
266
267
  exports.AutoBatchQueue = AutoBatchQueue;
267
268
  class Client {
269
+ get tracingMode() {
270
+ return this._tracingMode;
271
+ }
268
272
  get _fetch() {
269
273
  return this.fetchImplementation || (0, fetch_js_1._getFetchImplementation)(this.debug);
270
274
  }
275
+ /**
276
+ * Serialize a payload for tracing, optionally offloading the work to a
277
+ * Node worker thread when LANGSMITH_PERF_OPTIMIZATION=true and the runtime
278
+ * supports worker_threads.
279
+ *
280
+ * Falls back to synchronous serialization when:
281
+ * - the perf flag is off
282
+ * - manualFlushMode is enabled (serverless: worker boot cost > benefit)
283
+ * - worker_threads is unavailable (non-Node runtimes)
284
+ * - the payload contains values that can't be structured-cloned across
285
+ * threads (functions, non-cloneable class instances, streams, etc.)
286
+ * - the worker throws for any other reason
287
+ *
288
+ * In all fallback cases the returned bytes are identical to the sync path.
289
+ */
290
+ _trackDrain(promise) {
291
+ this._pendingDrains.add(promise);
292
+ promise.finally(() => {
293
+ this._pendingDrains.delete(promise);
294
+ });
295
+ }
296
+ async _serializeBody(payload, errorContext) {
297
+ const perfOptIn = (0, env_js_1.getLangSmithEnvironmentVariable)("PERF_OPTIMIZATION") === "true";
298
+ if (!perfOptIn || this.manualFlushMode) {
299
+ return (0, index_js_3.serialize)(payload, errorContext);
300
+ }
301
+ // Shape-aware gate: worker offload pays for itself only when the
302
+ // payload is dominated by one or more large strings (V8 can refcount
303
+ // those across isolates instead of copying). For structure-heavy
304
+ // payloads -- many keys, deep nesting, lots of small strings -- the
305
+ // structuredClone walk plus thread-hop cost exceeds the JSON.stringify
306
+ // cost we would pay inline, so we fall through to sync serialize.
307
+ if (!(0, serialize_worker_js_1.hasLargeString)(payload)) {
308
+ return (0, index_js_3.serialize)(payload, errorContext);
309
+ }
310
+ if (this._serializeWorker === undefined) {
311
+ this._serializeWorker = (0, serialize_worker_js_1.getSharedSerializeWorker)();
312
+ }
313
+ if (this._serializeWorker === null) {
314
+ return (0, index_js_3.serialize)(payload, errorContext);
315
+ }
316
+ try {
317
+ const bytes = await this._serializeWorker.serialize(payload);
318
+ if (bytes === null) {
319
+ // Worker subsystem unavailable; cache the null to skip re-entry.
320
+ this._serializeWorker = null;
321
+ return (0, index_js_3.serialize)(payload, errorContext);
322
+ }
323
+ return bytes;
324
+ }
325
+ catch {
326
+ // DataCloneError, worker crash, etc. Fall back silently.
327
+ return (0, index_js_3.serialize)(payload, errorContext);
328
+ }
329
+ }
271
330
  constructor(config = {}) {
272
331
  Object.defineProperty(this, "apiKey", {
273
332
  enumerable: true,
@@ -432,12 +491,35 @@ class Client {
432
491
  writable: true,
433
492
  value: false
434
493
  });
494
+ Object.defineProperty(this, "_serializeWorker", {
495
+ enumerable: true,
496
+ configurable: true,
497
+ writable: true,
498
+ value: void 0
499
+ });
500
+ /**
501
+ * Tracks in-flight drainAutoBatchQueue promises so awaitPendingTraceBatches
502
+ * can wait on them even if the flush involves async work (worker-thread
503
+ * serialize) that hasn't yet registered with batchIngestCaller.queue.
504
+ */
505
+ Object.defineProperty(this, "_pendingDrains", {
506
+ enumerable: true,
507
+ configurable: true,
508
+ writable: true,
509
+ value: new Set()
510
+ });
435
511
  Object.defineProperty(this, "langSmithToOTELTranslator", {
436
512
  enumerable: true,
437
513
  configurable: true,
438
514
  writable: true,
439
515
  value: void 0
440
516
  });
517
+ Object.defineProperty(this, "_tracingMode", {
518
+ enumerable: true,
519
+ configurable: true,
520
+ writable: true,
521
+ value: "langsmith"
522
+ });
441
523
  Object.defineProperty(this, "fetchImplementation", {
442
524
  enumerable: true,
443
525
  configurable: true,
@@ -557,7 +639,8 @@ class Client {
557
639
  this.batchSizeLimit = config.batchSizeLimit;
558
640
  this.fetchOptions = config.fetchOptions || {};
559
641
  this.manualFlushMode = config.manualFlushMode ?? this.manualFlushMode;
560
- if ((0, env_js_1.getOtelEnabled)()) {
642
+ this._tracingMode = (0, env_js_1.resolveTracingMode)(config.tracingMode);
643
+ if (this._tracingMode === "otel") {
561
644
  this.langSmithToOTELTranslator = new translator_js_1.LangSmithToOTELTranslator();
562
645
  }
563
646
  // Cache metadata env vars once during construction to avoid repeatedly scanning process.env
@@ -1096,18 +1179,18 @@ class Client {
1096
1179
  const sizeLimit = await this._getBatchSizeLimit();
1097
1180
  if (this.autoBatchQueue.sizeBytes > sizeLimitBytes ||
1098
1181
  this.autoBatchQueue.items.length > sizeLimit) {
1099
- void this.drainAutoBatchQueue({
1182
+ this._trackDrain(this.drainAutoBatchQueue({
1100
1183
  batchSizeLimitBytes: sizeLimitBytes,
1101
1184
  batchSizeLimit: sizeLimit,
1102
- });
1185
+ }));
1103
1186
  }
1104
1187
  if (this.autoBatchQueue.items.length > 0) {
1105
1188
  this.autoBatchTimeout = setTimeout(() => {
1106
1189
  this.autoBatchTimeout = undefined;
1107
- void this.drainAutoBatchQueue({
1190
+ this._trackDrain(this.drainAutoBatchQueue({
1108
1191
  batchSizeLimitBytes: sizeLimitBytes,
1109
1192
  batchSizeLimit: sizeLimit,
1110
- });
1193
+ }));
1111
1194
  }, this.autoBatchAggregationDelayMs);
1112
1195
  }
1113
1196
  return itemPromise;
@@ -1116,7 +1199,7 @@ class Client {
1116
1199
  const response = await this.caller.call(async () => {
1117
1200
  const res = await this._fetch(`${this.apiUrl}/info`, {
1118
1201
  method: "GET",
1119
- headers: { Accept: "application/json" },
1202
+ headers: { ...this._mergedHeaders, Accept: "application/json" },
1120
1203
  signal: AbortSignal.timeout(SERVER_INFO_REQUEST_TIMEOUT_MS),
1121
1204
  ...this.fetchOptions,
1122
1205
  });
@@ -1287,7 +1370,7 @@ class Client {
1287
1370
  .map((item) => item.id)
1288
1371
  .concat(batchChunks.patch.map((item) => item.id))
1289
1372
  .join(",");
1290
- await this._postBatchIngestRuns((0, index_js_3.serialize)(batchChunks, `Ingesting runs with ids: ${runIds}`), options);
1373
+ await this._postBatchIngestRuns(await this._serializeBody(batchChunks, `Ingesting runs with ids: ${runIds}`), options);
1291
1374
  }
1292
1375
  }
1293
1376
  async _postBatchIngestRuns(body, options) {
@@ -1388,7 +1471,7 @@ class Client {
1388
1471
  const { inputs, outputs, events, extra, error, serialized, attachments, ...payload } = originalPayload;
1389
1472
  const fields = { inputs, outputs, events, extra, error, serialized };
1390
1473
  // encode the main run payload
1391
- const stringifiedPayload = (0, index_js_3.serialize)(payload, `Serializing for multipart ingestion of run with id: ${payload.id}`);
1474
+ const stringifiedPayload = await this._serializeBody(payload, `Serializing for multipart ingestion of run with id: ${payload.id}`);
1392
1475
  accumulatedParts.push({
1393
1476
  name: `${method}.${payload.id}`,
1394
1477
  payload: new Blob([stringifiedPayload], {
@@ -1400,7 +1483,7 @@ class Client {
1400
1483
  if (value === undefined) {
1401
1484
  continue;
1402
1485
  }
1403
- const stringifiedValue = (0, index_js_3.serialize)(value, `Serializing ${key} for multipart ingestion of run with id: ${payload.id}`);
1486
+ const stringifiedValue = await this._serializeBody(value, `Serializing ${key} for multipart ingestion of run with id: ${payload.id}`);
1404
1487
  accumulatedParts.push({
1405
1488
  name: `${method}.${payload.id}.${key}`,
1406
1489
  payload: new Blob([stringifiedValue], {
@@ -4407,6 +4490,8 @@ class Client {
4407
4490
  commit_hash: result.commit_hash,
4408
4491
  manifest: result.manifest,
4409
4492
  examples: result.examples,
4493
+ hub_model_config: result.model_config,
4494
+ hub_model_provider: result.model_provider,
4410
4495
  };
4411
4496
  }
4412
4497
  async pullPromptCommit(promptIdentifier, options) {
@@ -4845,6 +4930,12 @@ class Client {
4845
4930
  * ```
4846
4931
  */
4847
4932
  await new Promise((resolve) => setTimeout(resolve, 1));
4933
+ // Wait for any in-flight drains (whose serialize work may still be
4934
+ // in-progress on a worker thread and not yet registered with the
4935
+ // batchIngestCaller queue) before checking queue idleness.
4936
+ while (this._pendingDrains.size > 0) {
4937
+ await Promise.all([...this._pendingDrains]);
4938
+ }
4848
4939
  await Promise.all([
4849
4940
  ...this.autoBatchQueue.items.map(({ itemPromise }) => itemPromise),
4850
4941
  this.batchIngestCaller.queue.onIdle(),
package/dist/client.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { OTELContext } from "./experimental/otel/types.js";
2
2
  import { AsyncCallerParams } from "./utils/async_caller.js";
3
3
  import { ComparativeExperiment, DataType, Dataset, DatasetDiffInfo, DatasetShareSchema, Example, ExampleCreate, ExampleUpdate, ExampleUpdateWithoutId, Feedback, FeedbackConfig, FeedbackIngestToken, KVMap, LangChainBaseMessage, LangSmithSettings, LikePromptResponse, Prompt, PromptCommit, PromptSortField, Run, RunCreate, RunUpdate, ScoreType, TimeDelta, TracerSession, TracerSessionResult, ValueType, AnnotationQueue, RunWithAnnotationQueueInfo, Attachments, UploadExamplesResponse, UpdateExamplesResponse, DatasetVersion, AnnotationQueueWithDetails, AnnotationQueueRubricItem, FeedbackConfigSchema, AgentContext, SkillContext, Entry } from "./schemas.js";
4
+ import { type TracingMode } from "./utils/env.js";
4
5
  import { EvaluationResult, EvaluationResults } from "./evaluation/evaluator.js";
5
6
  import { PromptCache } from "./utils/prompt_cache/index.js";
6
7
  export interface ClientConfig {
@@ -56,6 +57,17 @@ export interface ClientConfig {
56
57
  * By default, prompt caching is enabled globally.
57
58
  */
58
59
  disablePromptCache?: boolean;
60
+ /**
61
+ * Where to send traces. One of:
62
+ *
63
+ * - `"langsmith"` (default) — LangSmith REST API only.
64
+ * - `"otel"` — OpenTelemetry export only.
65
+ *
66
+ * Falls back to the `LANGSMITH_TRACING_MODE` env var, then to the
67
+ * legacy `OTEL_ENABLED` / `LANGSMITH_OTEL_ENABLED` env vars, then to
68
+ * `"langsmith"`.
69
+ */
70
+ tracingMode?: TracingMode;
59
71
  /**
60
72
  * Additional HTTP headers to include in all requests.
61
73
  * These headers will be merged with the default headers (User-Agent,
@@ -414,11 +426,37 @@ export declare class Client implements LangSmithTracingClientInterface {
414
426
  private _serverInfo;
415
427
  private _getServerInfoPromise?;
416
428
  private manualFlushMode;
429
+ private _serializeWorker?;
430
+ /**
431
+ * Tracks in-flight drainAutoBatchQueue promises so awaitPendingTraceBatches
432
+ * can wait on them even if the flush involves async work (worker-thread
433
+ * serialize) that hasn't yet registered with batchIngestCaller.queue.
434
+ */
435
+ private _pendingDrains;
417
436
  private langSmithToOTELTranslator?;
437
+ private _tracingMode;
438
+ get tracingMode(): TracingMode;
418
439
  private fetchImplementation?;
419
440
  private cachedLSEnvVarsForMetadata?;
420
441
  private _promptCache?;
421
442
  private get _fetch();
443
+ /**
444
+ * Serialize a payload for tracing, optionally offloading the work to a
445
+ * Node worker thread when LANGSMITH_PERF_OPTIMIZATION=true and the runtime
446
+ * supports worker_threads.
447
+ *
448
+ * Falls back to synchronous serialization when:
449
+ * - the perf flag is off
450
+ * - manualFlushMode is enabled (serverless: worker boot cost > benefit)
451
+ * - worker_threads is unavailable (non-Node runtimes)
452
+ * - the payload contains values that can't be structured-cloned across
453
+ * threads (functions, non-cloneable class instances, streams, etc.)
454
+ * - the worker throws for any other reason
455
+ *
456
+ * In all fallback cases the returned bytes are identical to the sync path.
457
+ */
458
+ private _trackDrain;
459
+ private _serializeBody;
422
460
  private multipartStreamingDisabled;
423
461
  private _multipartDisabled;
424
462
  private _runCompressionDisabled;
package/dist/client.js CHANGED
@@ -1,9 +1,9 @@
1
- import * as uuid from "uuid";
1
+ import * as uuid from "./utils/uuid/src/index.js";
2
2
  import { LangSmithToOTELTranslator, } from "./experimental/otel/translator.js";
3
3
  import { getDefaultOTLPTracerComponents, getOTELTrace, getOTELContext, } from "./singletons/otel.js";
4
4
  import { AsyncCaller } from "./utils/async_caller.js";
5
5
  import { convertLangChainMessageToExample, isLangChainMessage, } from "./utils/messages.js";
6
- import { getEnvironmentVariable, getLangSmithEnvVarsMetadata, getLangSmithEnvironmentVariable, getRuntimeEnvironment, getOtelEnabled, getEnv, } from "./utils/env.js";
6
+ import { getEnvironmentVariable, getLangSmithEnvVarsMetadata, getLangSmithEnvironmentVariable, getRuntimeEnvironment, getEnv, resolveTracingMode, } from "./utils/env.js";
7
7
  import { __version__ } from "./index.js";
8
8
  import { assertUuid } from "./utils/_uuid.js";
9
9
  import { warnOnce } from "./utils/warn.js";
@@ -13,6 +13,7 @@ import { promptCacheSingleton, } from "./utils/prompt_cache/index.js";
13
13
  import * as fsUtils from "./utils/fs.js";
14
14
  import { _shouldStreamForGlobalFetchImplementation, _getFetchImplementation, } from "./singletons/fetch.js";
15
15
  import { serialize as serializePayloadForTracing, estimateSerializedSize, } from "./utils/fast-safe-stringify/index.js";
16
+ import { getSharedSerializeWorker, hasLargeString, } from "./utils/serialize_worker.js";
16
17
  /**
17
18
  * Catches timestamps without a timezone suffix.
18
19
  */
@@ -163,7 +164,7 @@ export class AutoBatchQueue {
163
164
  // real serialization still happens later, off the hot path, when the
164
165
  // batch is assembled for sending.
165
166
  const size = getLangSmithEnvironmentVariable("PERF_OPTIMIZATION") === "true"
166
- ? estimateSerializedSize(item.item)
167
+ ? estimateSerializedSize(item.item).size
167
168
  : serializePayloadForTracing(item.item, `Serializing run with id: ${item.item.id}`).length;
168
169
  // Check if adding this item would exceed the size limit
169
170
  // Allow the run if the queue is empty (to support large single traces)
@@ -227,9 +228,67 @@ export class AutoBatchQueue {
227
228
  }
228
229
  }
229
230
  export class Client {
231
+ get tracingMode() {
232
+ return this._tracingMode;
233
+ }
230
234
  get _fetch() {
231
235
  return this.fetchImplementation || _getFetchImplementation(this.debug);
232
236
  }
237
+ /**
238
+ * Serialize a payload for tracing, optionally offloading the work to a
239
+ * Node worker thread when LANGSMITH_PERF_OPTIMIZATION=true and the runtime
240
+ * supports worker_threads.
241
+ *
242
+ * Falls back to synchronous serialization when:
243
+ * - the perf flag is off
244
+ * - manualFlushMode is enabled (serverless: worker boot cost > benefit)
245
+ * - worker_threads is unavailable (non-Node runtimes)
246
+ * - the payload contains values that can't be structured-cloned across
247
+ * threads (functions, non-cloneable class instances, streams, etc.)
248
+ * - the worker throws for any other reason
249
+ *
250
+ * In all fallback cases the returned bytes are identical to the sync path.
251
+ */
252
+ _trackDrain(promise) {
253
+ this._pendingDrains.add(promise);
254
+ promise.finally(() => {
255
+ this._pendingDrains.delete(promise);
256
+ });
257
+ }
258
+ async _serializeBody(payload, errorContext) {
259
+ const perfOptIn = getLangSmithEnvironmentVariable("PERF_OPTIMIZATION") === "true";
260
+ if (!perfOptIn || this.manualFlushMode) {
261
+ return serializePayloadForTracing(payload, errorContext);
262
+ }
263
+ // Shape-aware gate: worker offload pays for itself only when the
264
+ // payload is dominated by one or more large strings (V8 can refcount
265
+ // those across isolates instead of copying). For structure-heavy
266
+ // payloads -- many keys, deep nesting, lots of small strings -- the
267
+ // structuredClone walk plus thread-hop cost exceeds the JSON.stringify
268
+ // cost we would pay inline, so we fall through to sync serialize.
269
+ if (!hasLargeString(payload)) {
270
+ return serializePayloadForTracing(payload, errorContext);
271
+ }
272
+ if (this._serializeWorker === undefined) {
273
+ this._serializeWorker = getSharedSerializeWorker();
274
+ }
275
+ if (this._serializeWorker === null) {
276
+ return serializePayloadForTracing(payload, errorContext);
277
+ }
278
+ try {
279
+ const bytes = await this._serializeWorker.serialize(payload);
280
+ if (bytes === null) {
281
+ // Worker subsystem unavailable; cache the null to skip re-entry.
282
+ this._serializeWorker = null;
283
+ return serializePayloadForTracing(payload, errorContext);
284
+ }
285
+ return bytes;
286
+ }
287
+ catch {
288
+ // DataCloneError, worker crash, etc. Fall back silently.
289
+ return serializePayloadForTracing(payload, errorContext);
290
+ }
291
+ }
233
292
  constructor(config = {}) {
234
293
  Object.defineProperty(this, "apiKey", {
235
294
  enumerable: true,
@@ -394,12 +453,35 @@ export class Client {
394
453
  writable: true,
395
454
  value: false
396
455
  });
456
+ Object.defineProperty(this, "_serializeWorker", {
457
+ enumerable: true,
458
+ configurable: true,
459
+ writable: true,
460
+ value: void 0
461
+ });
462
+ /**
463
+ * Tracks in-flight drainAutoBatchQueue promises so awaitPendingTraceBatches
464
+ * can wait on them even if the flush involves async work (worker-thread
465
+ * serialize) that hasn't yet registered with batchIngestCaller.queue.
466
+ */
467
+ Object.defineProperty(this, "_pendingDrains", {
468
+ enumerable: true,
469
+ configurable: true,
470
+ writable: true,
471
+ value: new Set()
472
+ });
397
473
  Object.defineProperty(this, "langSmithToOTELTranslator", {
398
474
  enumerable: true,
399
475
  configurable: true,
400
476
  writable: true,
401
477
  value: void 0
402
478
  });
479
+ Object.defineProperty(this, "_tracingMode", {
480
+ enumerable: true,
481
+ configurable: true,
482
+ writable: true,
483
+ value: "langsmith"
484
+ });
403
485
  Object.defineProperty(this, "fetchImplementation", {
404
486
  enumerable: true,
405
487
  configurable: true,
@@ -519,7 +601,8 @@ export class Client {
519
601
  this.batchSizeLimit = config.batchSizeLimit;
520
602
  this.fetchOptions = config.fetchOptions || {};
521
603
  this.manualFlushMode = config.manualFlushMode ?? this.manualFlushMode;
522
- if (getOtelEnabled()) {
604
+ this._tracingMode = resolveTracingMode(config.tracingMode);
605
+ if (this._tracingMode === "otel") {
523
606
  this.langSmithToOTELTranslator = new LangSmithToOTELTranslator();
524
607
  }
525
608
  // Cache metadata env vars once during construction to avoid repeatedly scanning process.env
@@ -1058,18 +1141,18 @@ export class Client {
1058
1141
  const sizeLimit = await this._getBatchSizeLimit();
1059
1142
  if (this.autoBatchQueue.sizeBytes > sizeLimitBytes ||
1060
1143
  this.autoBatchQueue.items.length > sizeLimit) {
1061
- void this.drainAutoBatchQueue({
1144
+ this._trackDrain(this.drainAutoBatchQueue({
1062
1145
  batchSizeLimitBytes: sizeLimitBytes,
1063
1146
  batchSizeLimit: sizeLimit,
1064
- });
1147
+ }));
1065
1148
  }
1066
1149
  if (this.autoBatchQueue.items.length > 0) {
1067
1150
  this.autoBatchTimeout = setTimeout(() => {
1068
1151
  this.autoBatchTimeout = undefined;
1069
- void this.drainAutoBatchQueue({
1152
+ this._trackDrain(this.drainAutoBatchQueue({
1070
1153
  batchSizeLimitBytes: sizeLimitBytes,
1071
1154
  batchSizeLimit: sizeLimit,
1072
- });
1155
+ }));
1073
1156
  }, this.autoBatchAggregationDelayMs);
1074
1157
  }
1075
1158
  return itemPromise;
@@ -1078,7 +1161,7 @@ export class Client {
1078
1161
  const response = await this.caller.call(async () => {
1079
1162
  const res = await this._fetch(`${this.apiUrl}/info`, {
1080
1163
  method: "GET",
1081
- headers: { Accept: "application/json" },
1164
+ headers: { ...this._mergedHeaders, Accept: "application/json" },
1082
1165
  signal: AbortSignal.timeout(SERVER_INFO_REQUEST_TIMEOUT_MS),
1083
1166
  ...this.fetchOptions,
1084
1167
  });
@@ -1249,7 +1332,7 @@ export class Client {
1249
1332
  .map((item) => item.id)
1250
1333
  .concat(batchChunks.patch.map((item) => item.id))
1251
1334
  .join(",");
1252
- await this._postBatchIngestRuns(serializePayloadForTracing(batchChunks, `Ingesting runs with ids: ${runIds}`), options);
1335
+ await this._postBatchIngestRuns(await this._serializeBody(batchChunks, `Ingesting runs with ids: ${runIds}`), options);
1253
1336
  }
1254
1337
  }
1255
1338
  async _postBatchIngestRuns(body, options) {
@@ -1350,7 +1433,7 @@ export class Client {
1350
1433
  const { inputs, outputs, events, extra, error, serialized, attachments, ...payload } = originalPayload;
1351
1434
  const fields = { inputs, outputs, events, extra, error, serialized };
1352
1435
  // encode the main run payload
1353
- const stringifiedPayload = serializePayloadForTracing(payload, `Serializing for multipart ingestion of run with id: ${payload.id}`);
1436
+ const stringifiedPayload = await this._serializeBody(payload, `Serializing for multipart ingestion of run with id: ${payload.id}`);
1354
1437
  accumulatedParts.push({
1355
1438
  name: `${method}.${payload.id}`,
1356
1439
  payload: new Blob([stringifiedPayload], {
@@ -1362,7 +1445,7 @@ export class Client {
1362
1445
  if (value === undefined) {
1363
1446
  continue;
1364
1447
  }
1365
- const stringifiedValue = serializePayloadForTracing(value, `Serializing ${key} for multipart ingestion of run with id: ${payload.id}`);
1448
+ const stringifiedValue = await this._serializeBody(value, `Serializing ${key} for multipart ingestion of run with id: ${payload.id}`);
1366
1449
  accumulatedParts.push({
1367
1450
  name: `${method}.${payload.id}.${key}`,
1368
1451
  payload: new Blob([stringifiedValue], {
@@ -4369,6 +4452,8 @@ export class Client {
4369
4452
  commit_hash: result.commit_hash,
4370
4453
  manifest: result.manifest,
4371
4454
  examples: result.examples,
4455
+ hub_model_config: result.model_config,
4456
+ hub_model_provider: result.model_provider,
4372
4457
  };
4373
4458
  }
4374
4459
  async pullPromptCommit(promptIdentifier, options) {
@@ -4807,6 +4892,12 @@ export class Client {
4807
4892
  * ```
4808
4893
  */
4809
4894
  await new Promise((resolve) => setTimeout(resolve, 1));
4895
+ // Wait for any in-flight drains (whose serialize work may still be
4896
+ // in-progress on a worker thread and not yet registered with the
4897
+ // batchIngestCaller queue) before checking queue idleness.
4898
+ while (this._pendingDrains.size > 0) {
4899
+ await Promise.all([...this._pendingDrains]);
4900
+ }
4810
4901
  await Promise.all([
4811
4902
  ...this.autoBatchQueue.items.map(({ itemPromise }) => itemPromise),
4812
4903
  this.batchIngestCaller.queue.onIdle(),
@@ -13,7 +13,7 @@ const env_js_1 = require("../utils/env.cjs");
13
13
  const error_js_1 = require("../utils/error.cjs");
14
14
  const _random_name_js_1 = require("./_random_name.cjs");
15
15
  const evaluator_js_1 = require("./evaluator.cjs");
16
- const uuid_1 = require("uuid");
16
+ const index_js_2 = require("../utils/uuid/src/index.cjs");
17
17
  const evaluate_comparative_js_1 = require("./evaluate_comparative.cjs");
18
18
  const p_queue_js_1 = require("../utils/p-queue.cjs");
19
19
  // Implementation signature
@@ -202,7 +202,7 @@ class _ExperimentManager {
202
202
  this._experimentName = (0, _random_name_js_1.randomName)();
203
203
  }
204
204
  else if (typeof args.experiment === "string") {
205
- this._experimentName = `${args.experiment}-${(0, uuid_1.v4)().slice(0, 8)}`;
205
+ this._experimentName = `${args.experiment}-${(0, index_js_2.v4)().slice(0, 8)}`;
206
206
  }
207
207
  else {
208
208
  if (!args.experiment.name) {
@@ -276,7 +276,7 @@ class _ExperimentManager {
276
276
  catch (e) {
277
277
  // Naming collision
278
278
  if (e?.name === "LangSmithConflictError") {
279
- const ent = (0, uuid_1.v4)().slice(0, 6);
279
+ const ent = (0, index_js_2.v4)().slice(0, 6);
280
280
  this._experimentName = `${originalExperimentName}-${ent}`;
281
281
  }
282
282
  else {
@@ -7,7 +7,7 @@ import { getLangSmithEnvVarsMetadata } from "../utils/env.js";
7
7
  import { printErrorStackTrace } from "../utils/error.js";
8
8
  import { randomName } from "./_random_name.js";
9
9
  import { runEvaluator, } from "./evaluator.js";
10
- import { v4 as uuidv4 } from "uuid";
10
+ import { v4 as uuidv4 } from "../utils/uuid/src/index.js";
11
11
  import { evaluateComparative, } from "./evaluate_comparative.js";
12
12
  import { PQueue } from "../utils/p-queue.js";
13
13
  // Implementation signature
@@ -4,22 +4,22 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.evaluateComparative = evaluateComparative;
7
- const uuid_1 = require("uuid");
8
- const index_js_1 = require("../index.cjs");
7
+ const index_js_1 = require("../utils/uuid/src/index.cjs");
8
+ const index_js_2 = require("../index.cjs");
9
9
  const shuffle_js_1 = require("../utils/shuffle.cjs");
10
10
  const async_caller_js_1 = require("../utils/async_caller.cjs");
11
- const index_js_2 = __importDefault(require("../utils/p-retry/index.cjs"));
11
+ const index_js_3 = __importDefault(require("../utils/p-retry/index.cjs"));
12
12
  const traceable_js_1 = require("../traceable.cjs");
13
13
  function isExperimentResultsList(value) {
14
14
  return value.some((x) => typeof x !== "string");
15
15
  }
16
16
  async function loadExperiment(client, experiment) {
17
17
  const value = typeof experiment === "string" ? experiment : experiment.experimentName;
18
- return client.readProject((0, uuid_1.validate)(value) ? { projectId: value } : { projectName: value });
18
+ return client.readProject((0, index_js_1.validate)(value) ? { projectId: value } : { projectName: value });
19
19
  }
20
20
  async function loadTraces(client, experiment, options) {
21
21
  const executionOrder = options.loadNested ? undefined : 1;
22
- const runs = await client.listRuns((0, uuid_1.validate)(experiment)
22
+ const runs = await client.listRuns((0, index_js_1.validate)(experiment)
23
23
  ? { projectId: experiment, executionOrder }
24
24
  : { projectName: experiment, executionOrder });
25
25
  const treeMap = {};
@@ -56,7 +56,7 @@ async function evaluateComparative(experiments, options) {
56
56
  if (options.maxConcurrency && options.maxConcurrency < 0) {
57
57
  throw new Error("maxConcurrency must be a positive number.");
58
58
  }
59
- const client = options.client ?? new index_js_1.Client();
59
+ const client = options.client ?? new index_js_2.Client();
60
60
  const resolvedExperiments = await Promise.all(experiments);
61
61
  const projects = await (() => {
62
62
  if (!isExperimentResultsList(resolvedExperiments)) {
@@ -64,7 +64,7 @@ async function evaluateComparative(experiments, options) {
64
64
  }
65
65
  // if we know the number of runs beforehand, check if the
66
66
  // number of runs in the project matches the expected number of runs
67
- return Promise.all(resolvedExperiments.map((experiment) => (0, index_js_2.default)(async () => {
67
+ return Promise.all(resolvedExperiments.map((experiment) => (0, index_js_3.default)(async () => {
68
68
  const project = await loadExperiment(client, experiment);
69
69
  if (project.run_count !== experiment?.results.length) {
70
70
  throw new Error("Experiment is missing runs. Retrying.");
@@ -83,16 +83,16 @@ async function evaluateComparative(experiments, options) {
83
83
  console.warn("Detected multiple dataset versions used by experiments, which may lead to inaccurate results.");
84
84
  }
85
85
  const datasetVersion = projects.at(0)?.extra?.metadata?.dataset_version;
86
- const id = (0, uuid_1.v4)();
86
+ const id = (0, index_js_1.v4)();
87
87
  const experimentName = (() => {
88
88
  if (!options.experimentPrefix) {
89
89
  const names = projects
90
90
  .map((p) => p.name)
91
91
  .filter(Boolean)
92
92
  .join(" vs. ");
93
- return `${names}-${(0, uuid_1.v4)().slice(0, 4)}`;
93
+ return `${names}-${(0, index_js_1.v4)().slice(0, 4)}`;
94
94
  }
95
- return `${options.experimentPrefix}-${(0, uuid_1.v4)().slice(0, 4)}`;
95
+ return `${options.experimentPrefix}-${(0, index_js_1.v4)().slice(0, 4)}`;
96
96
  })();
97
97
  // TODO: add URL to the comparative experiment
98
98
  console.log(`Starting pairwise evaluation of: ${experimentName}`);
@@ -1,4 +1,4 @@
1
- import { v4 as uuid4, validate } from "uuid";
1
+ import { v4 as uuid4, validate } from "../utils/uuid/src/index.js";
2
2
  import { Client } from "../index.js";
3
3
  import { shuffle } from "../utils/shuffle.js";
4
4
  import { AsyncCaller } from "../utils/async_caller.js";
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.DynamicRunEvaluator = void 0;
4
4
  exports.runEvaluator = runEvaluator;
5
- const uuid_1 = require("uuid");
5
+ const index_js_1 = require("../utils/uuid/src/index.cjs");
6
6
  const traceable_js_1 = require("../traceable.cjs");
7
7
  /**
8
8
  * Wraps an evaluator function + implements the RunEvaluator interface.
@@ -67,7 +67,7 @@ class DynamicRunEvaluator {
67
67
  * @returns A promise that extracts to the evaluation result.
68
68
  */
69
69
  async evaluateRun(run, example, options) {
70
- let sourceRunId = (0, uuid_1.v7)();
70
+ let sourceRunId = (0, index_js_1.v7)();
71
71
  const metadata = {
72
72
  targetRunId: run.id,
73
73
  };
@@ -1,4 +1,4 @@
1
- import { v7 as uuidv7 } from "uuid";
1
+ import { v7 as uuidv7 } from "../utils/uuid/src/index.js";
2
2
  import { traceable } from "../traceable.js";
3
3
  /**
4
4
  * Wraps an evaluator function + implements the RunEvaluator interface.
package/dist/index.cjs CHANGED
@@ -18,4 +18,4 @@ Object.defineProperty(exports, "PromptCache", { enumerable: true, get: function
18
18
  Object.defineProperty(exports, "configureGlobalPromptCache", { enumerable: true, get: function () { return index_js_1.configureGlobalPromptCache; } });
19
19
  Object.defineProperty(exports, "promptCacheSingleton", { enumerable: true, get: function () { return index_js_1.promptCacheSingleton; } });
20
20
  // Update using pnpm bump-version
21
- exports.__version__ = "0.5.23";
21
+ exports.__version__ = "0.5.25";
package/dist/index.d.ts CHANGED
@@ -5,4 +5,4 @@ export { overrideFetchImplementation } from "./singletons/fetch.js";
5
5
  export { getDefaultProjectName } from "./utils/project.js";
6
6
  export { uuid7, uuid7FromTime } from "./uuid.js";
7
7
  export { Cache, PromptCache, type CacheConfig, type CacheMetrics, configureGlobalPromptCache, promptCacheSingleton, } from "./utils/prompt_cache/index.js";
8
- export declare const __version__ = "0.5.23";
8
+ export declare const __version__ = "0.5.25";
package/dist/index.js CHANGED
@@ -5,4 +5,4 @@ export { getDefaultProjectName } from "./utils/project.js";
5
5
  export { uuid7, uuid7FromTime } from "./uuid.js";
6
6
  export { Cache, PromptCache, configureGlobalPromptCache, promptCacheSingleton, } from "./utils/prompt_cache/index.js";
7
7
  // Update using pnpm bump-version
8
- export const __version__ = "0.5.23";
8
+ export const __version__ = "0.5.25";