langsmith 0.3.81 → 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.81";
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.81";
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.81";
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,
@@ -348,11 +369,18 @@ class RunTree {
348
369
  }
349
370
  createChild(config) {
350
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;
351
379
  const child = new RunTree({
352
380
  ...config,
353
381
  parent_run: this,
354
382
  project_name: this.project_name,
355
- replicas: this.replicas,
383
+ replicas: childReplicas,
356
384
  client: this.client,
357
385
  tracingEnabled: this.tracingEnabled,
358
386
  execution_order: child_execution_order,
@@ -452,10 +480,166 @@ class RunTree {
452
480
  events: run.events,
453
481
  };
454
482
  }
455
- _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;
456
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
+ }
457
637
  return {
458
638
  ...baseRun,
639
+ id: newId,
640
+ trace_id: newTraceId,
641
+ parent_run_id: newParentId,
642
+ dotted_order: newDottedOrder,
459
643
  session_name: projectName,
460
644
  };
461
645
  }
@@ -463,9 +647,18 @@ class RunTree {
463
647
  try {
464
648
  const runtimeEnv = (0, env_js_2.getRuntimeEnvironment)();
465
649
  if (this.replicas && this.replicas.length > 0) {
466
- for (const { projectName, apiKey, apiUrl, workspaceId } of this
650
+ for (const { projectName, apiKey, apiUrl, workspaceId, reroot } of this
467
651
  .replicas) {
468
- 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
+ });
469
662
  await this.client.createRun(runCreate, {
470
663
  apiKey,
471
664
  apiUrl,
@@ -490,9 +683,17 @@ class RunTree {
490
683
  }
491
684
  async patchRun(options) {
492
685
  if (this.replicas && this.replicas.length > 0) {
493
- for (const { projectName, apiKey, apiUrl, workspaceId, updates } of this
494
- .replicas) {
495
- 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
+ });
496
697
  const updatePayload = {
497
698
  id: runData.id,
498
699
  name: runData.name,
@@ -660,7 +861,10 @@ class RunTree {
660
861
  config.project_name = baggage.project_name;
661
862
  config.replicas = baggage.replicas;
662
863
  }
663
- 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;
664
868
  }
665
869
  toHeaders(headers) {
666
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?: {
package/dist/run_trees.js CHANGED
@@ -1,12 +1,27 @@
1
1
  import { Client } from "./client.js";
2
2
  import { isTracingEnabled } from "./env.js";
3
3
  import { isConflictingEndpointsError, ConflictingEndpointsError, } from "./utils/error.js";
4
- import { _LC_CONTEXT_VARIABLES_KEY } from "./singletons/constants.js";
4
+ import { _LC_CONTEXT_VARIABLES_KEY, _REPLICA_TRACE_ROOTS_KEY, } from "./singletons/constants.js";
5
+ import { getContextVar, setContextVar } from "./utils/context_vars.js";
5
6
  import { getEnvironmentVariable, getRuntimeEnvironment, } from "./utils/env.js";
6
7
  import { getDefaultProjectName } from "./utils/project.js";
7
8
  import { getLangSmithEnvironmentVariable } from "./utils/env.js";
8
9
  import { warnOnce } from "./utils/warn.js";
9
10
  import { uuid7FromTime } from "./utils/_uuid.js";
11
+ import { v5 as uuidv5 } from "uuid";
12
+ const TIMESTAMP_LENGTH = 36;
13
+ // DNS namespace for UUID v5 (same as Python's uuid.NAMESPACE_DNS)
14
+ const UUID_NAMESPACE_DNS = "6ba7b810-9dad-11d1-80b4-00c04fd430c8";
15
+ function getReplicaKey(replica) {
16
+ // Generate a unique key by hashing the replica's identifying properties
17
+ // This ensures each unique replica (combination of projectName, apiUrl, workspaceId, apiKey) gets a unique key
18
+ // Sort keys to ensure consistent hashing
19
+ const sortedKeys = Object.keys(replica).sort();
20
+ const keyData = sortedKeys
21
+ .map((key) => `${key}:${replica[key] ?? ""}`)
22
+ .join("|");
23
+ return uuidv5(keyData, UUID_NAMESPACE_DNS);
24
+ }
10
25
  function stripNonAlphanumeric(input) {
11
26
  return input.replace(/[-:.]/g, "");
12
27
  }
@@ -254,6 +269,12 @@ export class RunTree {
254
269
  writable: true,
255
270
  value: void 0
256
271
  });
272
+ Object.defineProperty(this, "distributedParentId", {
273
+ enumerable: true,
274
+ configurable: true,
275
+ writable: true,
276
+ value: void 0
277
+ });
257
278
  Object.defineProperty(this, "_serialized_start_time", {
258
279
  enumerable: true,
259
280
  configurable: true,
@@ -342,11 +363,18 @@ export class RunTree {
342
363
  }
343
364
  createChild(config) {
344
365
  const child_execution_order = this.child_execution_order + 1;
366
+ // Handle replicas: if child has its own replicas, use those; otherwise inherit parent's (with reroot stripped)
367
+ // Reroot should only apply to the run where it's explicitly configured, not propagate down
368
+ const inheritedReplicas = this.replicas?.map((replica) => {
369
+ const { reroot, ...rest } = replica;
370
+ return rest;
371
+ });
372
+ const childReplicas = config.replicas ?? inheritedReplicas;
345
373
  const child = new RunTree({
346
374
  ...config,
347
375
  parent_run: this,
348
376
  project_name: this.project_name,
349
- replicas: this.replicas,
377
+ replicas: childReplicas,
350
378
  client: this.client,
351
379
  tracingEnabled: this.tracingEnabled,
352
380
  execution_order: child_execution_order,
@@ -446,10 +474,166 @@ export class RunTree {
446
474
  events: run.events,
447
475
  };
448
476
  }
449
- _remapForProject(projectName, runtimeEnv, excludeChildRuns = true) {
477
+ _sliceParentId(parentId, run) {
478
+ /**
479
+ * Slice the parent id from dotted order.
480
+ * Additionally check if the current run is a child of the parent. If so, update
481
+ * the parent_run_id to undefined, and set the trace id to the new root id after
482
+ * parent_id.
483
+ */
484
+ if (run.dotted_order) {
485
+ const segs = run.dotted_order.split(".");
486
+ let startIdx = null;
487
+ // Find the index of the parent ID in the dotted order
488
+ for (let idx = 0; idx < segs.length; idx++) {
489
+ const segId = segs[idx].slice(-TIMESTAMP_LENGTH);
490
+ if (segId === parentId) {
491
+ startIdx = idx;
492
+ break;
493
+ }
494
+ }
495
+ if (startIdx !== null) {
496
+ // Trim segments to start after parent_id (exclusive)
497
+ const trimmedSegs = segs.slice(startIdx + 1);
498
+ // Rebuild dotted_order
499
+ run.dotted_order = trimmedSegs.join(".");
500
+ if (trimmedSegs.length > 0) {
501
+ run.trace_id = trimmedSegs[0].slice(-TIMESTAMP_LENGTH);
502
+ }
503
+ else {
504
+ run.trace_id = run.id;
505
+ }
506
+ }
507
+ }
508
+ if (run.parent_run_id === parentId) {
509
+ // We've found the new root node.
510
+ run.parent_run_id = undefined;
511
+ }
512
+ }
513
+ _setReplicaTraceRoot(replicaKey, traceRootId) {
514
+ // Set the replica trace root in context vars on this run and all descendants
515
+ const replicaTraceRoots = getContextVar(this, _REPLICA_TRACE_ROOTS_KEY) ?? {};
516
+ replicaTraceRoots[replicaKey] = traceRootId;
517
+ setContextVar(this, _REPLICA_TRACE_ROOTS_KEY, replicaTraceRoots);
518
+ // Recursively update all descendants to avoid race conditions
519
+ // around run tree creation vs processing time
520
+ for (const child of this.child_runs) {
521
+ child._setReplicaTraceRoot(replicaKey, traceRootId);
522
+ }
523
+ }
524
+ _remapForProject(params) {
525
+ const { projectName, runtimeEnv, excludeChildRuns = true, reroot = false, distributedParentId, apiUrl, apiKey, workspaceId, } = params;
450
526
  const baseRun = this._convertToCreate(this, runtimeEnv, excludeChildRuns);
527
+ // Skip remapping if project name is the same
528
+ if (projectName === this.project_name) {
529
+ return {
530
+ ...baseRun,
531
+ session_name: projectName,
532
+ };
533
+ }
534
+ // Apply reroot logic before ID remapping
535
+ if (reroot) {
536
+ if (distributedParentId) {
537
+ // If we have a distributed parent ID, slice at that point
538
+ this._sliceParentId(distributedParentId, baseRun);
539
+ }
540
+ else {
541
+ // If no distributed parent ID, simply make this run a root run
542
+ // by removing parent_run_id and resetting trace info
543
+ baseRun.parent_run_id = undefined;
544
+ // Keep the current run as the trace root
545
+ if (baseRun.dotted_order) {
546
+ // Reset dotted order to just this run
547
+ const segs = baseRun.dotted_order.split(".");
548
+ if (segs.length > 0) {
549
+ baseRun.dotted_order = segs[segs.length - 1];
550
+ baseRun.trace_id = baseRun.id;
551
+ }
552
+ }
553
+ }
554
+ // Store this run's original ID in context vars so descendants know the new trace root
555
+ // We store the original ID (before remapping) so it can be found in dotted_order
556
+ const replicaKey = getReplicaKey({
557
+ projectName,
558
+ apiUrl,
559
+ apiKey,
560
+ workspaceId,
561
+ });
562
+ this._setReplicaTraceRoot(replicaKey, baseRun.id);
563
+ }
564
+ // If an ancestor was rerooted for this replica, update trace_id and dotted_order
565
+ // to reflect the new trace hierarchy. This is tracked via context variables.
566
+ let ancestorRerootedTraceId;
567
+ if (!reroot) {
568
+ const replicaTraceRoots = getContextVar(this, _REPLICA_TRACE_ROOTS_KEY) ?? {};
569
+ const replicaKey = getReplicaKey({
570
+ projectName,
571
+ apiUrl,
572
+ apiKey,
573
+ workspaceId,
574
+ });
575
+ ancestorRerootedTraceId = replicaTraceRoots[replicaKey];
576
+ if (ancestorRerootedTraceId) {
577
+ // An ancestor was rerooted for this replica, so set our trace_id
578
+ // to the ancestor's original (unmapped) ID. It will be remapped along with other IDs.
579
+ baseRun.trace_id = ancestorRerootedTraceId;
580
+ // Also slice the dotted_order to start from the new trace root
581
+ // This ensures descendants of a rerooted ancestor have correct hierarchy
582
+ if (baseRun.dotted_order) {
583
+ const segs = baseRun.dotted_order.split(".");
584
+ let rootIdx = null;
585
+ // Find the new trace root's segment in dotted_order
586
+ for (let idx = 0; idx < segs.length; idx++) {
587
+ const segId = segs[idx].slice(-TIMESTAMP_LENGTH);
588
+ if (segId === ancestorRerootedTraceId) {
589
+ rootIdx = idx;
590
+ break;
591
+ }
592
+ }
593
+ if (rootIdx !== null) {
594
+ // Keep segments from new trace root onwards
595
+ const trimmedSegs = segs.slice(rootIdx);
596
+ baseRun.dotted_order = trimmedSegs.join(".");
597
+ }
598
+ }
599
+ }
600
+ }
601
+ // Remap IDs for the replica using uuid5 (deterministic)
602
+ // This ensures consistency across runs in the same replica
603
+ const oldId = baseRun.id;
604
+ const newId = uuidv5(`${oldId}:${projectName}`, UUID_NAMESPACE_DNS);
605
+ // Remap trace_id
606
+ let newTraceId;
607
+ if (baseRun.trace_id) {
608
+ newTraceId = uuidv5(`${baseRun.trace_id}:${projectName}`, UUID_NAMESPACE_DNS);
609
+ }
610
+ else {
611
+ newTraceId = newId;
612
+ }
613
+ // Remap parent_run_id
614
+ let newParentId;
615
+ if (baseRun.parent_run_id) {
616
+ newParentId = uuidv5(`${baseRun.parent_run_id}:${projectName}`, UUID_NAMESPACE_DNS);
617
+ }
618
+ // Remap dotted_order segments
619
+ let newDottedOrder;
620
+ if (baseRun.dotted_order) {
621
+ const segs = baseRun.dotted_order.split(".");
622
+ const remappedSegs = segs.map((seg) => {
623
+ // Extract the UUID from the segment (last TIMESTAMP_LENGTH characters)
624
+ const segId = seg.slice(-TIMESTAMP_LENGTH);
625
+ const remappedId = uuidv5(`${segId}:${projectName}`, UUID_NAMESPACE_DNS);
626
+ // Replace the UUID part while keeping the timestamp prefix
627
+ return seg.slice(0, -TIMESTAMP_LENGTH) + remappedId;
628
+ });
629
+ newDottedOrder = remappedSegs.join(".");
630
+ }
451
631
  return {
452
632
  ...baseRun,
633
+ id: newId,
634
+ trace_id: newTraceId,
635
+ parent_run_id: newParentId,
636
+ dotted_order: newDottedOrder,
453
637
  session_name: projectName,
454
638
  };
455
639
  }
@@ -457,9 +641,18 @@ export class RunTree {
457
641
  try {
458
642
  const runtimeEnv = getRuntimeEnvironment();
459
643
  if (this.replicas && this.replicas.length > 0) {
460
- for (const { projectName, apiKey, apiUrl, workspaceId } of this
644
+ for (const { projectName, apiKey, apiUrl, workspaceId, reroot } of this
461
645
  .replicas) {
462
- const runCreate = this._remapForProject(projectName ?? this.project_name, runtimeEnv, true);
646
+ const runCreate = this._remapForProject({
647
+ projectName: projectName ?? this.project_name,
648
+ runtimeEnv,
649
+ excludeChildRuns: true,
650
+ reroot,
651
+ distributedParentId: this.distributedParentId,
652
+ apiUrl,
653
+ apiKey,
654
+ workspaceId,
655
+ });
463
656
  await this.client.createRun(runCreate, {
464
657
  apiKey,
465
658
  apiUrl,
@@ -484,9 +677,17 @@ export class RunTree {
484
677
  }
485
678
  async patchRun(options) {
486
679
  if (this.replicas && this.replicas.length > 0) {
487
- for (const { projectName, apiKey, apiUrl, workspaceId, updates } of this
488
- .replicas) {
489
- const runData = this._remapForProject(projectName ?? this.project_name);
680
+ for (const { projectName, apiKey, apiUrl, workspaceId, updates, reroot, } of this.replicas) {
681
+ const runData = this._remapForProject({
682
+ projectName: projectName ?? this.project_name,
683
+ runtimeEnv: undefined,
684
+ excludeChildRuns: true,
685
+ reroot,
686
+ distributedParentId: this.distributedParentId,
687
+ apiUrl,
688
+ apiKey,
689
+ workspaceId,
690
+ });
490
691
  const updatePayload = {
491
692
  id: runData.id,
492
693
  name: runData.name,
@@ -654,7 +855,10 @@ export class RunTree {
654
855
  config.project_name = baggage.project_name;
655
856
  config.replicas = baggage.replicas;
656
857
  }
657
- return new RunTree(config);
858
+ const runTree = new RunTree(config);
859
+ // Set the distributed parent ID to this run's ID for rerooting
860
+ runTree.distributedParentId = runTree.id;
861
+ return runTree;
658
862
  }
659
863
  toHeaders(headers) {
660
864
  const result = {
@@ -1,4 +1,5 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports._LC_CONTEXT_VARIABLES_KEY = void 0;
3
+ exports._REPLICA_TRACE_ROOTS_KEY = exports._LC_CONTEXT_VARIABLES_KEY = void 0;
4
4
  exports._LC_CONTEXT_VARIABLES_KEY = Symbol.for("lc:context_variables");
5
+ exports._REPLICA_TRACE_ROOTS_KEY = Symbol.for("langsmith:replica_trace_roots");
@@ -1 +1,2 @@
1
1
  export declare const _LC_CONTEXT_VARIABLES_KEY: unique symbol;
2
+ export declare const _REPLICA_TRACE_ROOTS_KEY: unique symbol;
@@ -1 +1,2 @@
1
1
  export const _LC_CONTEXT_VARIABLES_KEY = Symbol.for("lc:context_variables");
2
+ export const _REPLICA_TRACE_ROOTS_KEY = Symbol.for("langsmith:replica_trace_roots");