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 +2 -1
- package/dist/evaluation/evaluate_comparative.cjs +2 -2
- package/dist/evaluation/evaluate_comparative.js +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/run_trees.cjs +212 -8
- package/dist/run_trees.d.ts +5 -0
- package/dist/run_trees.js +213 -9
- package/dist/singletons/constants.cjs +2 -1
- package/dist/singletons/constants.d.ts +1 -0
- package/dist/singletons/constants.js +1 -0
- package/dist/utils/async_caller.cjs +3 -3
- package/dist/utils/async_caller.js +2 -2
- package/dist/utils/context_vars.cjs +31 -0
- package/dist/utils/context_vars.d.ts +8 -0
- package/dist/utils/context_vars.js +27 -0
- package/dist/utils/is-network-error/index.cjs +42 -0
- package/dist/utils/is-network-error/index.d.ts +1 -0
- package/dist/utils/is-network-error/index.js +39 -0
- package/dist/utils/jestlike/reporter.d.ts +1 -1
- package/dist/utils/p-retry/index.cjs +200 -0
- package/dist/utils/p-retry/index.d.ts +5 -0
- package/dist/utils/p-retry/index.js +191 -0
- package/dist/vitest/reporter.cjs +6 -0
- package/dist/vitest/reporter.d.mts +6 -0
- package/dist/vitest/reporter.d.ts +2 -0
- package/dist/vitest/reporter.js +7 -1
- package/dist/vitest/reporter.mjs +20 -1
- package/dist/vitest/utils/reporter.cjs +14 -1
- package/dist/vitest/utils/reporter.d.ts +30 -0
- package/dist/vitest/utils/reporter.js +12 -0
- package/package.json +1 -2
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
|
-
|
|
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
|
|
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,
|
|
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.
|
|
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.
|
|
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.
|
|
7
|
+
export const __version__ = "0.3.82";
|
package/dist/run_trees.cjs
CHANGED
|
@@ -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:
|
|
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
|
-
|
|
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(
|
|
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
|
-
.
|
|
495
|
-
|
|
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
|
-
|
|
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 = {
|
package/dist/run_trees.d.ts
CHANGED
|
@@ -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:
|
|
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
|
-
|
|
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(
|
|
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
|
-
.
|
|
489
|
-
|
|
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
|
-
|
|
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");
|