langsmith 0.3.80 → 0.3.82

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/README.md CHANGED
@@ -31,7 +31,8 @@ Sign up for [LangSmith](https://smith.langchain.com/) using your GitHub, Discord
31
31
 
32
32
  Then, create a unique API key on the [Settings Page](https://smith.langchain.com/settings).
33
33
 
34
- Note: Save the API Key in a secure location. It will not be shown again.
34
+ > [!NOTE]
35
+ > Save the API Key in a secure location. It will not be shown again.
35
36
 
36
37
  ## 2. Log Traces
37
38
 
@@ -8,7 +8,7 @@ const uuid_1 = require("uuid");
8
8
  const index_js_1 = 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 p_retry_1 = __importDefault(require("p-retry"));
11
+ const index_js_2 = __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");
@@ -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, p_retry_1.default)(async () => {
67
+ return Promise.all(resolvedExperiments.map((experiment) => (0, index_js_2.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.");
@@ -2,7 +2,7 @@ import { v4 as uuid4, validate } from "uuid";
2
2
  import { Client } from "../index.js";
3
3
  import { shuffle } from "../utils/shuffle.js";
4
4
  import { AsyncCaller } from "../utils/async_caller.js";
5
- import pRetry from "p-retry";
5
+ import pRetry from "../utils/p-retry/index.js";
6
6
  import { getCurrentRunTree, traceable } from "../traceable.js";
7
7
  function isExperimentResultsList(value) {
8
8
  return value.some((x) => typeof x !== "string");
package/dist/index.cjs CHANGED
@@ -13,4 +13,4 @@ var uuid_js_1 = require("./uuid.cjs");
13
13
  Object.defineProperty(exports, "uuid7", { enumerable: true, get: function () { return uuid_js_1.uuid7; } });
14
14
  Object.defineProperty(exports, "uuid7FromTime", { enumerable: true, get: function () { return uuid_js_1.uuid7FromTime; } });
15
15
  // Update using yarn bump-version
16
- exports.__version__ = "0.3.80";
16
+ exports.__version__ = "0.3.82";
package/dist/index.d.ts CHANGED
@@ -4,4 +4,4 @@ export { RunTree, type RunTreeConfig } from "./run_trees.js";
4
4
  export { overrideFetchImplementation } from "./singletons/fetch.js";
5
5
  export { getDefaultProjectName } from "./utils/project.js";
6
6
  export { uuid7, uuid7FromTime } from "./uuid.js";
7
- export declare const __version__ = "0.3.80";
7
+ export declare const __version__ = "0.3.82";
package/dist/index.js CHANGED
@@ -4,4 +4,4 @@ export { overrideFetchImplementation } from "./singletons/fetch.js";
4
4
  export { getDefaultProjectName } from "./utils/project.js";
5
5
  export { uuid7, uuid7FromTime } from "./uuid.js";
6
6
  // Update using yarn bump-version
7
- export const __version__ = "0.3.80";
7
+ export const __version__ = "0.3.82";
@@ -8,11 +8,26 @@ const client_js_1 = require("./client.cjs");
8
8
  const env_js_1 = require("./env.cjs");
9
9
  const error_js_1 = require("./utils/error.cjs");
10
10
  const constants_js_1 = require("./singletons/constants.cjs");
11
+ const context_vars_js_1 = require("./utils/context_vars.cjs");
11
12
  const env_js_2 = require("./utils/env.cjs");
12
13
  const project_js_1 = require("./utils/project.cjs");
13
14
  const env_js_3 = require("./utils/env.cjs");
14
15
  const warn_js_1 = require("./utils/warn.cjs");
15
16
  const _uuid_js_1 = require("./utils/_uuid.cjs");
17
+ const uuid_1 = require("uuid");
18
+ const TIMESTAMP_LENGTH = 36;
19
+ // DNS namespace for UUID v5 (same as Python's uuid.NAMESPACE_DNS)
20
+ const UUID_NAMESPACE_DNS = "6ba7b810-9dad-11d1-80b4-00c04fd430c8";
21
+ function getReplicaKey(replica) {
22
+ // Generate a unique key by hashing the replica's identifying properties
23
+ // This ensures each unique replica (combination of projectName, apiUrl, workspaceId, apiKey) gets a unique key
24
+ // Sort keys to ensure consistent hashing
25
+ const sortedKeys = Object.keys(replica).sort();
26
+ const keyData = sortedKeys
27
+ .map((key) => `${key}:${replica[key] ?? ""}`)
28
+ .join("|");
29
+ return (0, uuid_1.v5)(keyData, UUID_NAMESPACE_DNS);
30
+ }
16
31
  function stripNonAlphanumeric(input) {
17
32
  return input.replace(/[-:.]/g, "");
18
33
  }
@@ -260,6 +275,12 @@ class RunTree {
260
275
  writable: true,
261
276
  value: void 0
262
277
  });
278
+ Object.defineProperty(this, "distributedParentId", {
279
+ enumerable: true,
280
+ configurable: true,
281
+ writable: true,
282
+ value: void 0
283
+ });
263
284
  Object.defineProperty(this, "_serialized_start_time", {
264
285
  enumerable: true,
265
286
  configurable: true,
@@ -293,9 +314,6 @@ class RunTree {
293
314
  if (!this.id) {
294
315
  this.id = (0, _uuid_js_1.uuid7FromTime)(this._serialized_start_time ?? this.start_time);
295
316
  }
296
- if (config.id) {
297
- (0, _uuid_js_1.warnIfNotUuidV7)(config.id, "run_id");
298
- }
299
317
  if (!this.trace_id) {
300
318
  if (this.parent_run) {
301
319
  this.trace_id = this.parent_run.trace_id ?? this.id;
@@ -304,12 +322,6 @@ class RunTree {
304
322
  this.trace_id = this.id;
305
323
  }
306
324
  }
307
- else if (config.trace_id) {
308
- (0, _uuid_js_1.warnIfNotUuidV7)(config.trace_id, "trace_id");
309
- }
310
- if (config.parent_run_id) {
311
- (0, _uuid_js_1.warnIfNotUuidV7)(config.parent_run_id, "parent_run_id");
312
- }
313
325
  this.replicas = _ensureWriteReplicas(this.replicas);
314
326
  // Now set the dotted order with the actual ID
315
327
  if (!this.dotted_order) {
@@ -357,11 +369,18 @@ class RunTree {
357
369
  }
358
370
  createChild(config) {
359
371
  const child_execution_order = this.child_execution_order + 1;
372
+ // Handle replicas: if child has its own replicas, use those; otherwise inherit parent's (with reroot stripped)
373
+ // Reroot should only apply to the run where it's explicitly configured, not propagate down
374
+ const inheritedReplicas = this.replicas?.map((replica) => {
375
+ const { reroot, ...rest } = replica;
376
+ return rest;
377
+ });
378
+ const childReplicas = config.replicas ?? inheritedReplicas;
360
379
  const child = new RunTree({
361
380
  ...config,
362
381
  parent_run: this,
363
382
  project_name: this.project_name,
364
- replicas: this.replicas,
383
+ replicas: childReplicas,
365
384
  client: this.client,
366
385
  tracingEnabled: this.tracingEnabled,
367
386
  execution_order: child_execution_order,
@@ -461,10 +480,166 @@ class RunTree {
461
480
  events: run.events,
462
481
  };
463
482
  }
464
- _remapForProject(projectName, runtimeEnv, excludeChildRuns = true) {
483
+ _sliceParentId(parentId, run) {
484
+ /**
485
+ * Slice the parent id from dotted order.
486
+ * Additionally check if the current run is a child of the parent. If so, update
487
+ * the parent_run_id to undefined, and set the trace id to the new root id after
488
+ * parent_id.
489
+ */
490
+ if (run.dotted_order) {
491
+ const segs = run.dotted_order.split(".");
492
+ let startIdx = null;
493
+ // Find the index of the parent ID in the dotted order
494
+ for (let idx = 0; idx < segs.length; idx++) {
495
+ const segId = segs[idx].slice(-TIMESTAMP_LENGTH);
496
+ if (segId === parentId) {
497
+ startIdx = idx;
498
+ break;
499
+ }
500
+ }
501
+ if (startIdx !== null) {
502
+ // Trim segments to start after parent_id (exclusive)
503
+ const trimmedSegs = segs.slice(startIdx + 1);
504
+ // Rebuild dotted_order
505
+ run.dotted_order = trimmedSegs.join(".");
506
+ if (trimmedSegs.length > 0) {
507
+ run.trace_id = trimmedSegs[0].slice(-TIMESTAMP_LENGTH);
508
+ }
509
+ else {
510
+ run.trace_id = run.id;
511
+ }
512
+ }
513
+ }
514
+ if (run.parent_run_id === parentId) {
515
+ // We've found the new root node.
516
+ run.parent_run_id = undefined;
517
+ }
518
+ }
519
+ _setReplicaTraceRoot(replicaKey, traceRootId) {
520
+ // Set the replica trace root in context vars on this run and all descendants
521
+ const replicaTraceRoots = (0, context_vars_js_1.getContextVar)(this, constants_js_1._REPLICA_TRACE_ROOTS_KEY) ?? {};
522
+ replicaTraceRoots[replicaKey] = traceRootId;
523
+ (0, context_vars_js_1.setContextVar)(this, constants_js_1._REPLICA_TRACE_ROOTS_KEY, replicaTraceRoots);
524
+ // Recursively update all descendants to avoid race conditions
525
+ // around run tree creation vs processing time
526
+ for (const child of this.child_runs) {
527
+ child._setReplicaTraceRoot(replicaKey, traceRootId);
528
+ }
529
+ }
530
+ _remapForProject(params) {
531
+ const { projectName, runtimeEnv, excludeChildRuns = true, reroot = false, distributedParentId, apiUrl, apiKey, workspaceId, } = params;
465
532
  const baseRun = this._convertToCreate(this, runtimeEnv, excludeChildRuns);
533
+ // Skip remapping if project name is the same
534
+ if (projectName === this.project_name) {
535
+ return {
536
+ ...baseRun,
537
+ session_name: projectName,
538
+ };
539
+ }
540
+ // Apply reroot logic before ID remapping
541
+ if (reroot) {
542
+ if (distributedParentId) {
543
+ // If we have a distributed parent ID, slice at that point
544
+ this._sliceParentId(distributedParentId, baseRun);
545
+ }
546
+ else {
547
+ // If no distributed parent ID, simply make this run a root run
548
+ // by removing parent_run_id and resetting trace info
549
+ baseRun.parent_run_id = undefined;
550
+ // Keep the current run as the trace root
551
+ if (baseRun.dotted_order) {
552
+ // Reset dotted order to just this run
553
+ const segs = baseRun.dotted_order.split(".");
554
+ if (segs.length > 0) {
555
+ baseRun.dotted_order = segs[segs.length - 1];
556
+ baseRun.trace_id = baseRun.id;
557
+ }
558
+ }
559
+ }
560
+ // Store this run's original ID in context vars so descendants know the new trace root
561
+ // We store the original ID (before remapping) so it can be found in dotted_order
562
+ const replicaKey = getReplicaKey({
563
+ projectName,
564
+ apiUrl,
565
+ apiKey,
566
+ workspaceId,
567
+ });
568
+ this._setReplicaTraceRoot(replicaKey, baseRun.id);
569
+ }
570
+ // If an ancestor was rerooted for this replica, update trace_id and dotted_order
571
+ // to reflect the new trace hierarchy. This is tracked via context variables.
572
+ let ancestorRerootedTraceId;
573
+ if (!reroot) {
574
+ const replicaTraceRoots = (0, context_vars_js_1.getContextVar)(this, constants_js_1._REPLICA_TRACE_ROOTS_KEY) ?? {};
575
+ const replicaKey = getReplicaKey({
576
+ projectName,
577
+ apiUrl,
578
+ apiKey,
579
+ workspaceId,
580
+ });
581
+ ancestorRerootedTraceId = replicaTraceRoots[replicaKey];
582
+ if (ancestorRerootedTraceId) {
583
+ // An ancestor was rerooted for this replica, so set our trace_id
584
+ // to the ancestor's original (unmapped) ID. It will be remapped along with other IDs.
585
+ baseRun.trace_id = ancestorRerootedTraceId;
586
+ // Also slice the dotted_order to start from the new trace root
587
+ // This ensures descendants of a rerooted ancestor have correct hierarchy
588
+ if (baseRun.dotted_order) {
589
+ const segs = baseRun.dotted_order.split(".");
590
+ let rootIdx = null;
591
+ // Find the new trace root's segment in dotted_order
592
+ for (let idx = 0; idx < segs.length; idx++) {
593
+ const segId = segs[idx].slice(-TIMESTAMP_LENGTH);
594
+ if (segId === ancestorRerootedTraceId) {
595
+ rootIdx = idx;
596
+ break;
597
+ }
598
+ }
599
+ if (rootIdx !== null) {
600
+ // Keep segments from new trace root onwards
601
+ const trimmedSegs = segs.slice(rootIdx);
602
+ baseRun.dotted_order = trimmedSegs.join(".");
603
+ }
604
+ }
605
+ }
606
+ }
607
+ // Remap IDs for the replica using uuid5 (deterministic)
608
+ // This ensures consistency across runs in the same replica
609
+ const oldId = baseRun.id;
610
+ const newId = (0, uuid_1.v5)(`${oldId}:${projectName}`, UUID_NAMESPACE_DNS);
611
+ // Remap trace_id
612
+ let newTraceId;
613
+ if (baseRun.trace_id) {
614
+ newTraceId = (0, uuid_1.v5)(`${baseRun.trace_id}:${projectName}`, UUID_NAMESPACE_DNS);
615
+ }
616
+ else {
617
+ newTraceId = newId;
618
+ }
619
+ // Remap parent_run_id
620
+ let newParentId;
621
+ if (baseRun.parent_run_id) {
622
+ newParentId = (0, uuid_1.v5)(`${baseRun.parent_run_id}:${projectName}`, UUID_NAMESPACE_DNS);
623
+ }
624
+ // Remap dotted_order segments
625
+ let newDottedOrder;
626
+ if (baseRun.dotted_order) {
627
+ const segs = baseRun.dotted_order.split(".");
628
+ const remappedSegs = segs.map((seg) => {
629
+ // Extract the UUID from the segment (last TIMESTAMP_LENGTH characters)
630
+ const segId = seg.slice(-TIMESTAMP_LENGTH);
631
+ const remappedId = (0, uuid_1.v5)(`${segId}:${projectName}`, UUID_NAMESPACE_DNS);
632
+ // Replace the UUID part while keeping the timestamp prefix
633
+ return seg.slice(0, -TIMESTAMP_LENGTH) + remappedId;
634
+ });
635
+ newDottedOrder = remappedSegs.join(".");
636
+ }
466
637
  return {
467
638
  ...baseRun,
639
+ id: newId,
640
+ trace_id: newTraceId,
641
+ parent_run_id: newParentId,
642
+ dotted_order: newDottedOrder,
468
643
  session_name: projectName,
469
644
  };
470
645
  }
@@ -472,9 +647,18 @@ class RunTree {
472
647
  try {
473
648
  const runtimeEnv = (0, env_js_2.getRuntimeEnvironment)();
474
649
  if (this.replicas && this.replicas.length > 0) {
475
- for (const { projectName, apiKey, apiUrl, workspaceId } of this
650
+ for (const { projectName, apiKey, apiUrl, workspaceId, reroot } of this
476
651
  .replicas) {
477
- const runCreate = this._remapForProject(projectName ?? this.project_name, runtimeEnv, true);
652
+ const runCreate = this._remapForProject({
653
+ projectName: projectName ?? this.project_name,
654
+ runtimeEnv,
655
+ excludeChildRuns: true,
656
+ reroot,
657
+ distributedParentId: this.distributedParentId,
658
+ apiUrl,
659
+ apiKey,
660
+ workspaceId,
661
+ });
478
662
  await this.client.createRun(runCreate, {
479
663
  apiKey,
480
664
  apiUrl,
@@ -499,9 +683,17 @@ class RunTree {
499
683
  }
500
684
  async patchRun(options) {
501
685
  if (this.replicas && this.replicas.length > 0) {
502
- for (const { projectName, apiKey, apiUrl, workspaceId, updates } of this
503
- .replicas) {
504
- const runData = this._remapForProject(projectName ?? this.project_name);
686
+ for (const { projectName, apiKey, apiUrl, workspaceId, updates, reroot, } of this.replicas) {
687
+ const runData = this._remapForProject({
688
+ projectName: projectName ?? this.project_name,
689
+ runtimeEnv: undefined,
690
+ excludeChildRuns: true,
691
+ reroot,
692
+ distributedParentId: this.distributedParentId,
693
+ apiUrl,
694
+ apiKey,
695
+ workspaceId,
696
+ });
505
697
  const updatePayload = {
506
698
  id: runData.id,
507
699
  name: runData.name,
@@ -669,7 +861,10 @@ class RunTree {
669
861
  config.project_name = baggage.project_name;
670
862
  config.replicas = baggage.replicas;
671
863
  }
672
- return new RunTree(config);
864
+ const runTree = new RunTree(config);
865
+ // Set the distributed parent ID to this run's ID for rerooting
866
+ runTree.distributedParentId = runTree.id;
867
+ return runTree;
673
868
  }
674
869
  toHeaders(headers) {
675
870
  const result = {
@@ -31,6 +31,7 @@ export interface RunTreeConfig {
31
31
  dotted_order?: string;
32
32
  attachments?: Attachments;
33
33
  replicas?: Replica[];
34
+ distributedParentId?: string;
34
35
  }
35
36
  export interface RunnableConfigLike {
36
37
  /**
@@ -61,6 +62,7 @@ type WriteReplica = {
61
62
  projectName?: string;
62
63
  updates?: KVMap | undefined;
63
64
  fromEnv?: boolean;
65
+ reroot?: boolean;
64
66
  };
65
67
  type Replica = ProjectReplica | WriteReplica;
66
68
  export declare class RunTree implements BaseRun {
@@ -97,6 +99,7 @@ export declare class RunTree implements BaseRun {
97
99
  * Projects to replicate this run to with optional updates.
98
100
  */
99
101
  replicas?: WriteReplica[];
102
+ distributedParentId?: string;
100
103
  private _serialized_start_time;
101
104
  constructor(originalConfig: RunTreeConfig | RunTree);
102
105
  set metadata(metadata: KVMap);
@@ -106,6 +109,8 @@ export declare class RunTree implements BaseRun {
106
109
  createChild(config: RunTreeConfig): RunTree;
107
110
  end(outputs?: KVMap, error?: string, endTime?: number, metadata?: KVMap): Promise<void>;
108
111
  private _convertToCreate;
112
+ private _sliceParentId;
113
+ private _setReplicaTraceRoot;
109
114
  private _remapForProject;
110
115
  postRun(excludeChildRuns?: boolean): Promise<void>;
111
116
  patchRun(options?: {