langsmith
Version:
Client library to connect to the LangSmith Observability and Evaluation Platform.
1,053 lines (1,052 loc) • 40.6 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.RunTree = void 0;
exports.convertToDottedOrderFormat = convertToDottedOrderFormat;
exports.isRunTree = isRunTree;
exports.isRunnableConfigLike = isRunnableConfigLike;
const client_js_1 = require("./client.cjs");
const env_js_1 = require("./env.cjs");
const error_js_1 = require("./utils/error.cjs");
const constants_js_1 = require("./singletons/constants.cjs");
const context_vars_js_1 = require("./utils/context_vars.cjs");
const env_js_2 = require("./utils/env.cjs");
const project_js_1 = require("./utils/project.cjs");
const env_js_3 = require("./utils/env.cjs");
const warn_js_1 = require("./utils/warn.cjs");
const _uuid_js_1 = require("./utils/_uuid.cjs");
const uuid_1 = require("uuid");
const TIMESTAMP_LENGTH = 36;
// DNS namespace for UUID v5 (same as Python's uuid.NAMESPACE_DNS)
const UUID_NAMESPACE_DNS = "6ba7b810-9dad-11d1-80b4-00c04fd430c8";
function getReplicaKey(replica) {
// Generate a unique key by hashing the replica's identifying properties
// This ensures each unique replica (combination of projectName, apiUrl, workspaceId, apiKey) gets a unique key
// Sort keys to ensure consistent hashing
const sortedKeys = Object.keys(replica).sort();
const keyData = sortedKeys
.map((key) => `${key}:${replica[key] ?? ""}`)
.join("|");
return (0, uuid_1.v5)(keyData, UUID_NAMESPACE_DNS);
}
function stripNonAlphanumeric(input) {
return input.replace(/[-:.]/g, "");
}
function getMicrosecondPrecisionDatestring(epoch, executionOrder = 1) {
// Date only has millisecond precision, so we use the microseconds to break
// possible ties, avoiding incorrect run order
const paddedOrder = executionOrder.toFixed(0).slice(0, 3).padStart(3, "0");
return `${new Date(epoch).toISOString().slice(0, -1)}${paddedOrder}Z`;
}
function convertToDottedOrderFormat(epoch, runId, executionOrder = 1) {
const microsecondPrecisionDatestring = getMicrosecondPrecisionDatestring(epoch, executionOrder);
return {
dottedOrder: stripNonAlphanumeric(microsecondPrecisionDatestring) + runId,
microsecondPrecisionDatestring,
};
}
const HEADER_SAFE_REPLICA_FIELDS = new Set([
"projectName",
"updates",
"reroot",
]);
function filterReplicaForHeaders(replica) {
const filtered = {};
for (const key of Object.keys(replica)) {
if (HEADER_SAFE_REPLICA_FIELDS.has(key)) {
filtered[key] = replica[key];
}
}
return filtered;
}
/**
* Baggage header information
*/
class Baggage {
constructor(metadata, tags, project_name, replicas) {
Object.defineProperty(this, "metadata", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "tags", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "project_name", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "replicas", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
this.metadata = metadata;
this.tags = tags;
this.project_name = project_name;
this.replicas = replicas;
}
static fromHeader(value) {
const items = value.split(",");
let metadata = {};
let tags = [];
let project_name;
let replicas;
for (const item of items) {
const [key, uriValue] = item.split("=");
const value = decodeURIComponent(uriValue);
if (key === "langsmith-metadata") {
metadata = JSON.parse(value);
}
else if (key === "langsmith-tags") {
tags = value.split(",");
}
else if (key === "langsmith-project") {
project_name = value;
}
else if (key === "langsmith-replicas") {
const parsed = JSON.parse(value);
replicas = parsed.map((replica) => {
if (Array.isArray(replica)) {
return replica;
}
return filterReplicaForHeaders(replica);
});
}
}
return new Baggage(metadata, tags, project_name, replicas);
}
toHeader() {
const items = [];
if (this.metadata && Object.keys(this.metadata).length > 0) {
items.push(`langsmith-metadata=${encodeURIComponent(JSON.stringify(this.metadata))}`);
}
if (this.tags && this.tags.length > 0) {
items.push(`langsmith-tags=${encodeURIComponent(this.tags.join(","))}`);
}
if (this.project_name) {
items.push(`langsmith-project=${encodeURIComponent(this.project_name)}`);
}
return items.join(",");
}
}
class RunTree {
constructor(originalConfig) {
Object.defineProperty(this, "id", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "name", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "run_type", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "project_name", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "parent_run", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "parent_run_id", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "child_runs", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "start_time", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "end_time", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "extra", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "tags", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "error", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "serialized", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "inputs", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "outputs", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "reference_example_id", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "client", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "events", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "trace_id", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "dotted_order", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "tracingEnabled", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "execution_order", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "child_execution_order", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
/**
* Attachments associated with the run.
* Each entry is a tuple of [mime_type, bytes]
*/
Object.defineProperty(this, "attachments", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
/**
* Projects to replicate this run to with optional updates.
*/
Object.defineProperty(this, "replicas", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "distributedParentId", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
/**
* @interface
*/
Object.defineProperty(this, "_serialized_start_time", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
/**
* @internal
*/
Object.defineProperty(this, "_awaitInputsOnPost", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
// If you pass in a run tree directly, return a shallow clone
if (isRunTree(originalConfig)) {
Object.assign(this, { ...originalConfig });
return;
}
const defaultConfig = RunTree.getDefaultConfig();
const { metadata, ...config } = originalConfig;
const client = config.client ?? RunTree.getSharedClient();
const dedupedMetadata = {
...metadata,
...config?.extra?.metadata,
};
config.extra = { ...config.extra, metadata: dedupedMetadata };
if ("id" in config && config.id == null) {
delete config.id;
}
Object.assign(this, { ...defaultConfig, ...config, client });
this.execution_order ??= 1;
this.child_execution_order ??= 1;
// Generate serialized start time for ID generation
if (!this.dotted_order) {
this._serialized_start_time = getMicrosecondPrecisionDatestring(this.start_time, this.execution_order);
}
// Generate id from serialized start_time if not provided
if (!this.id) {
this.id = (0, _uuid_js_1.uuid7FromTime)(this._serialized_start_time ?? this.start_time);
}
if (!this.trace_id) {
if (this.parent_run) {
this.trace_id = this.parent_run.trace_id ?? this.id;
}
else {
this.trace_id = this.id;
}
}
this.replicas = _ensureWriteReplicas(this.replicas);
// Now set the dotted order with the actual ID
if (!this.dotted_order) {
const { dottedOrder } = convertToDottedOrderFormat(this.start_time, this.id, this.execution_order);
if (this.parent_run) {
this.dotted_order = this.parent_run.dotted_order + "." + dottedOrder;
}
else {
this.dotted_order = dottedOrder;
}
}
}
set metadata(metadata) {
this.extra = {
...this.extra,
metadata: {
...this.extra?.metadata,
...metadata,
},
};
}
get metadata() {
return this.extra?.metadata;
}
static getDefaultConfig() {
const start_time = Date.now();
return {
run_type: "chain",
project_name: (0, project_js_1.getDefaultProjectName)(),
child_runs: [],
api_url: (0, env_js_2.getEnvironmentVariable)("LANGCHAIN_ENDPOINT") ?? "http://localhost:1984",
api_key: (0, env_js_2.getEnvironmentVariable)("LANGCHAIN_API_KEY"),
caller_options: {},
start_time,
serialized: {},
inputs: {},
extra: {},
};
}
static getSharedClient() {
if (!RunTree.sharedClient) {
RunTree.sharedClient = new client_js_1.Client();
}
return RunTree.sharedClient;
}
createChild(config) {
const child_execution_order = this.child_execution_order + 1;
// Handle replicas: if child has its own replicas, use those; otherwise inherit parent's (with reroot stripped)
// Reroot should only apply to the run where it's explicitly configured, not propagate down
const inheritedReplicas = this.replicas?.map((replica) => {
const { reroot, ...rest } = replica;
return rest;
});
const childReplicas = config.replicas ?? inheritedReplicas;
const child = new RunTree({
...config,
parent_run: this,
project_name: this.project_name,
replicas: childReplicas,
client: this.client,
tracingEnabled: this.tracingEnabled,
execution_order: child_execution_order,
child_execution_order: child_execution_order,
});
// Propagate all parent metadata; child metadata takes precedence.
const parentMeta = this.extra?.metadata ?? {};
const childMeta = child.extra?.metadata ?? {};
if (Object.keys(parentMeta).length > 0) {
child.extra = {
...child.extra,
metadata: { ...parentMeta, ...childMeta },
};
}
// Copy context vars over into the new run tree.
if (constants_js_1._LC_CONTEXT_VARIABLES_KEY in this) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
child[constants_js_1._LC_CONTEXT_VARIABLES_KEY] =
this[constants_js_1._LC_CONTEXT_VARIABLES_KEY];
}
const LC_CHILD = Symbol.for("lc:child_config");
const presentConfig = config.extra?.[LC_CHILD] ??
this.extra[LC_CHILD];
// tracing for LangChain is defined by the _parentRunId and runMap of the tracer
if (isRunnableConfigLike(presentConfig)) {
const newConfig = { ...presentConfig };
const callbacks = isCallbackManagerLike(newConfig.callbacks)
? newConfig.callbacks.copy?.()
: undefined;
if (callbacks) {
// update the parent run id
Object.assign(callbacks, { _parentRunId: child.id });
// only populate if we're in a newer LC.JS version
callbacks.handlers
?.find(isLangChainTracerLike)
?.updateFromRunTree?.(child);
newConfig.callbacks = callbacks;
}
child.extra[LC_CHILD] = newConfig;
}
// propagate child_execution_order upwards
const visited = new Set();
let current = this;
while (current != null && !visited.has(current.id)) {
visited.add(current.id);
current.child_execution_order = Math.max(current.child_execution_order, child_execution_order);
current = current.parent_run;
}
this.child_runs.push(child);
return child;
}
async end(outputs, error, endTime = Date.now(), metadata) {
this.outputs = this.outputs ?? outputs;
this.error = this.error ?? error;
this.end_time = this.end_time ?? endTime;
if (metadata && Object.keys(metadata).length > 0) {
this.extra = this.extra
? { ...this.extra, metadata: { ...this.extra.metadata, ...metadata } }
: { metadata };
}
}
_convertToCreate(run, runtimeEnv, excludeChildRuns = true) {
const runExtra = run.extra ?? {};
// Avoid overwriting the runtime environment if it's already set
if (runExtra?.runtime?.library === undefined) {
if (!runExtra.runtime) {
runExtra.runtime = {};
}
if (runtimeEnv) {
for (const [k, v] of Object.entries(runtimeEnv)) {
if (!runExtra.runtime[k]) {
runExtra.runtime[k] = v;
}
}
}
}
const parent_run_id = run.parent_run?.id ?? run.parent_run_id;
let child_runs;
if (!excludeChildRuns) {
child_runs = run.child_runs.map((child_run) => this._convertToCreate(child_run, runtimeEnv, excludeChildRuns));
}
else {
child_runs = [];
}
return {
id: run.id,
name: run.name,
start_time: run._serialized_start_time ?? run.start_time,
end_time: run.end_time,
run_type: run.run_type,
reference_example_id: run.reference_example_id,
extra: runExtra,
serialized: run.serialized,
error: run.error,
inputs: run.inputs,
outputs: run.outputs,
session_name: run.project_name,
child_runs: child_runs,
parent_run_id: parent_run_id,
trace_id: run.trace_id,
dotted_order: run.dotted_order,
tags: run.tags,
attachments: run.attachments,
events: run.events,
};
}
_sliceParentId(parentId, run) {
/**
* Slice the parent id from dotted order.
* Additionally check if the current run is a child of the parent. If so, update
* the parent_run_id to undefined, and set the trace id to the new root id after
* parent_id.
*/
if (run.dotted_order) {
const segs = run.dotted_order.split(".");
let startIdx = null;
// Find the index of the parent ID in the dotted order
for (let idx = 0; idx < segs.length; idx++) {
const segId = segs[idx].slice(-TIMESTAMP_LENGTH);
if (segId === parentId) {
startIdx = idx;
break;
}
}
if (startIdx !== null) {
// Trim segments to start after parent_id (exclusive)
const trimmedSegs = segs.slice(startIdx + 1);
// Rebuild dotted_order
run.dotted_order = trimmedSegs.join(".");
if (trimmedSegs.length > 0) {
run.trace_id = trimmedSegs[0].slice(-TIMESTAMP_LENGTH);
}
else {
run.trace_id = run.id;
}
}
}
if (run.parent_run_id === parentId) {
// We've found the new root node.
run.parent_run_id = undefined;
}
}
_setReplicaTraceRoot(replicaKey, traceRootId) {
// Set the replica trace root in context vars on this run and all descendants
const replicaTraceRoots = (0, context_vars_js_1.getContextVar)(this, constants_js_1._REPLICA_TRACE_ROOTS_KEY) ?? {};
replicaTraceRoots[replicaKey] = traceRootId;
(0, context_vars_js_1.setContextVar)(this, constants_js_1._REPLICA_TRACE_ROOTS_KEY, replicaTraceRoots);
// Recursively update all descendants to avoid race conditions
// around run tree creation vs processing time
for (const child of this.child_runs) {
child._setReplicaTraceRoot(replicaKey, traceRootId);
}
}
_remapForProject(params) {
const { projectName, runtimeEnv, excludeChildRuns = true, reroot = false, distributedParentId, apiUrl, apiKey, workspaceId, } = params;
const baseRun = this._convertToCreate(this, runtimeEnv, excludeChildRuns);
// Skip remapping if project name is the same
if (projectName === this.project_name) {
return {
...baseRun,
session_name: projectName,
};
}
// Apply reroot logic before ID remapping
if (reroot) {
if (distributedParentId) {
// If we have a distributed parent ID, slice at that point
this._sliceParentId(distributedParentId, baseRun);
}
else {
// If no distributed parent ID, simply make this run a root run
// by removing parent_run_id and resetting trace info
baseRun.parent_run_id = undefined;
// Keep the current run as the trace root
if (baseRun.dotted_order) {
// Reset dotted order to just this run
const segs = baseRun.dotted_order.split(".");
if (segs.length > 0) {
baseRun.dotted_order = segs[segs.length - 1];
baseRun.trace_id = baseRun.id;
}
}
}
// Store this run's original ID in context vars so descendants know the new trace root
// We store the original ID (before remapping) so it can be found in dotted_order
const replicaKey = getReplicaKey({
projectName,
apiUrl,
apiKey,
workspaceId,
});
this._setReplicaTraceRoot(replicaKey, baseRun.id);
}
// If an ancestor was rerooted for this replica, update trace_id and dotted_order
// to reflect the new trace hierarchy. This is tracked via context variables.
let ancestorRerootedTraceId;
if (!reroot) {
const replicaTraceRoots = (0, context_vars_js_1.getContextVar)(this, constants_js_1._REPLICA_TRACE_ROOTS_KEY) ?? {};
const replicaKey = getReplicaKey({
projectName,
apiUrl,
apiKey,
workspaceId,
});
ancestorRerootedTraceId = replicaTraceRoots[replicaKey];
if (ancestorRerootedTraceId) {
// An ancestor was rerooted for this replica, so set our trace_id
// to the ancestor's original (unmapped) ID. It will be remapped along with other IDs.
baseRun.trace_id = ancestorRerootedTraceId;
// Also slice the dotted_order to start from the new trace root
// This ensures descendants of a rerooted ancestor have correct hierarchy
if (baseRun.dotted_order) {
const segs = baseRun.dotted_order.split(".");
let rootIdx = null;
// Find the new trace root's segment in dotted_order
for (let idx = 0; idx < segs.length; idx++) {
const segId = segs[idx].slice(-TIMESTAMP_LENGTH);
if (segId === ancestorRerootedTraceId) {
rootIdx = idx;
break;
}
}
if (rootIdx !== null) {
// Keep segments from new trace root onwards
const trimmedSegs = segs.slice(rootIdx);
baseRun.dotted_order = trimmedSegs.join(".");
}
}
}
}
// Remap IDs for the replica using nonCryptographicUuid7Deterministic
// This ensures consistency across runs in the same replica while
// preserving UUID7 properties (time-ordering, monotonicity)
const oldId = baseRun.id;
const newId = (0, _uuid_js_1.nonCryptographicUuid7Deterministic)(oldId, projectName);
// Remap trace_id
let newTraceId;
if (baseRun.trace_id) {
newTraceId = (0, _uuid_js_1.nonCryptographicUuid7Deterministic)(baseRun.trace_id, projectName);
}
else {
newTraceId = newId;
}
// Remap parent_run_id
let newParentId;
if (baseRun.parent_run_id) {
newParentId = (0, _uuid_js_1.nonCryptographicUuid7Deterministic)(baseRun.parent_run_id, projectName);
}
// Remap dotted_order segments
let newDottedOrder;
if (baseRun.dotted_order) {
const segs = baseRun.dotted_order.split(".");
const remappedSegs = segs.map((seg) => {
// Extract the UUID from the segment (last TIMESTAMP_LENGTH characters)
const segId = seg.slice(-TIMESTAMP_LENGTH);
const remappedId = (0, _uuid_js_1.nonCryptographicUuid7Deterministic)(segId, projectName);
// Replace the UUID part while keeping the timestamp prefix
return seg.slice(0, -TIMESTAMP_LENGTH) + remappedId;
});
newDottedOrder = remappedSegs.join(".");
}
return {
...baseRun,
id: newId,
trace_id: newTraceId,
parent_run_id: newParentId,
dotted_order: newDottedOrder,
session_name: projectName,
};
}
async postRun(excludeChildRuns = true) {
// Applies when `processInputs` is an async function
if (this._awaitInputsOnPost) {
this.inputs = await this.inputs;
}
try {
const runtimeEnv = (0, env_js_2.getRuntimeEnvironment)();
if (this.replicas && this.replicas.length > 0) {
for (const { projectName, apiKey, apiUrl, workspaceId, reroot } of this
.replicas) {
const runCreate = this._remapForProject({
projectName: projectName ?? this.project_name,
runtimeEnv,
excludeChildRuns: true,
reroot,
distributedParentId: this.distributedParentId,
apiUrl,
apiKey,
workspaceId,
});
await this.client.createRun(runCreate, {
apiKey,
apiUrl,
workspaceId,
});
}
}
else {
const runCreate = this._convertToCreate(this, runtimeEnv, excludeChildRuns);
await this.client.createRun(runCreate);
}
if (!excludeChildRuns) {
(0, warn_js_1.warnOnce)("Posting with excludeChildRuns=false is deprecated and will be removed in a future version.");
for (const childRun of this.child_runs) {
await childRun.postRun(false);
}
}
this.child_runs = [];
}
catch (error) {
console.error(`Error in postRun for run ${this.id}:`, error);
}
}
async patchRun(options) {
if (this.replicas && this.replicas.length > 0) {
for (const { projectName, apiKey, apiUrl, workspaceId, updates, reroot, } of this.replicas) {
const runData = this._remapForProject({
projectName: projectName ?? this.project_name,
runtimeEnv: undefined,
excludeChildRuns: true,
reroot,
distributedParentId: this.distributedParentId,
apiUrl,
apiKey,
workspaceId,
});
const updatePayload = {
id: runData.id,
name: runData.name,
run_type: runData.run_type,
start_time: runData.start_time,
outputs: runData.outputs,
error: runData.error,
parent_run_id: runData.parent_run_id,
session_name: runData.session_name,
reference_example_id: runData.reference_example_id,
end_time: runData.end_time,
dotted_order: runData.dotted_order,
trace_id: runData.trace_id,
events: runData.events,
tags: runData.tags,
extra: runData.extra,
attachments: this.attachments,
...updates,
};
// Important that inputs is not a key in the run update
// if excluded because it will overwrite the run create if the
// two operations are merged during batching
if (!options?.excludeInputs) {
updatePayload.inputs = runData.inputs;
}
await this.client.updateRun(runData.id, updatePayload, {
apiKey,
apiUrl,
workspaceId,
});
}
}
else {
try {
const runUpdate = {
name: this.name,
run_type: this.run_type,
start_time: this._serialized_start_time ?? this.start_time,
end_time: this.end_time,
error: this.error,
outputs: this.outputs,
parent_run_id: this.parent_run?.id ?? this.parent_run_id,
reference_example_id: this.reference_example_id,
extra: this.extra,
events: this.events,
dotted_order: this.dotted_order,
trace_id: this.trace_id,
tags: this.tags,
attachments: this.attachments,
session_name: this.project_name,
};
// Important that inputs is not a key in the run update
// if excluded because it will overwrite the run create if the
// two operations are merged during batching
if (!options?.excludeInputs) {
runUpdate.inputs = this.inputs;
}
await this.client.updateRun(this.id, runUpdate);
}
catch (error) {
console.error(`Error in patchRun for run ${this.id}`, error);
}
}
this.child_runs = [];
}
toJSON() {
return this._convertToCreate(this, undefined, false);
}
/**
* Add an event to the run tree.
* @param event - A single event or string to add
*/
addEvent(event) {
if (!this.events) {
this.events = [];
}
if (typeof event === "string") {
this.events.push({
name: "event",
time: new Date().toISOString(),
message: event,
});
}
else {
this.events.push({
...event,
time: event.time ?? new Date().toISOString(),
});
}
}
static fromRunnableConfig(parentConfig, props) {
// We only handle the callback manager case for now
const callbackManager = parentConfig?.callbacks;
let parentRun;
let projectName;
let client;
let tracingEnabled = (0, env_js_1.isTracingEnabled)();
if (callbackManager) {
const parentRunId = callbackManager?.getParentRunId?.() ?? "";
const langChainTracer = callbackManager?.handlers?.find((handler) => handler?.name == "langchain_tracer");
parentRun = langChainTracer?.getRun?.(parentRunId);
projectName = langChainTracer?.projectName;
client = langChainTracer?.client;
tracingEnabled = tracingEnabled || !!langChainTracer;
}
if (!parentRun) {
return new RunTree({
...props,
client,
tracingEnabled,
project_name: projectName,
});
}
const parentRunTree = new RunTree({
name: parentRun.name,
id: parentRun.id,
trace_id: parentRun.trace_id,
dotted_order: parentRun.dotted_order,
client,
tracingEnabled,
project_name: projectName,
tags: [
...new Set((parentRun?.tags ?? []).concat(parentConfig?.tags ?? [])),
],
extra: {
metadata: {
...parentRun?.extra?.metadata,
...parentConfig?.metadata,
},
},
});
return parentRunTree.createChild(props);
}
static fromDottedOrder(dottedOrder) {
return this.fromHeaders({ "langsmith-trace": dottedOrder });
}
static fromHeaders(headers, inheritArgs) {
const rawHeaders = "get" in headers && typeof headers.get === "function"
? {
"langsmith-trace": headers.get("langsmith-trace"),
baggage: headers.get("baggage"),
}
: headers;
const headerTrace = rawHeaders["langsmith-trace"];
if (!headerTrace || typeof headerTrace !== "string")
return undefined;
const parentDottedOrder = headerTrace.trim();
const parsedDottedOrder = parentDottedOrder.split(".").map((part) => {
const [strTime, uuid] = part.split("Z");
return { strTime, time: Date.parse(strTime + "Z"), uuid };
});
const traceId = parsedDottedOrder[0].uuid;
const config = {
...inheritArgs,
name: inheritArgs?.["name"] ?? "parent",
run_type: inheritArgs?.["run_type"] ?? "chain",
start_time: inheritArgs?.["start_time"] ?? Date.now(),
id: parsedDottedOrder.at(-1)?.uuid,
trace_id: traceId,
dotted_order: parentDottedOrder,
};
if (rawHeaders["baggage"] && typeof rawHeaders["baggage"] === "string") {
const baggage = Baggage.fromHeader(rawHeaders["baggage"]);
config.metadata = baggage.metadata;
config.tags = baggage.tags;
config.project_name = baggage.project_name;
config.replicas = baggage.replicas;
}
const runTree = new RunTree(config);
// Set the distributed parent ID to this run's ID for rerooting
runTree.distributedParentId = runTree.id;
return runTree;
}
toHeaders(headers) {
const result = {
"langsmith-trace": this.dotted_order,
baggage: new Baggage(this.extra?.metadata, this.tags, this.project_name, this.replicas).toHeader(),
};
if (headers) {
for (const [key, value] of Object.entries(result)) {
headers.set(key, value);
}
}
return result;
}
}
exports.RunTree = RunTree;
Object.defineProperty(RunTree, "sharedClient", {
enumerable: true,
configurable: true,
writable: true,
value: null
});
function isRunTree(x) {
return (x != null &&
typeof x.createChild === "function" &&
typeof x.postRun === "function");
}
function isLangChainTracerLike(x) {
return (typeof x === "object" &&
x != null &&
typeof x.name === "string" &&
x.name === "langchain_tracer");
}
function containsLangChainTracerLike(x) {
return (Array.isArray(x) && x.some((callback) => isLangChainTracerLike(callback)));
}
function isCallbackManagerLike(x) {
return (typeof x === "object" &&
x != null &&
Array.isArray(x.handlers));
}
function isRunnableConfigLike(x) {
// Check that it's an object with a callbacks arg
// that has either a CallbackManagerLike object with a langchain tracer within it
// or an array with a LangChainTracerLike object within it
const callbacks = x?.callbacks;
return (x != null &&
typeof callbacks === "object" &&
// Callback manager with a langchain tracer
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(containsLangChainTracerLike(callbacks?.handlers) ||
// Or it's an array with a LangChainTracerLike object within it
containsLangChainTracerLike(callbacks)));
}
function _getWriteReplicasFromEnv() {
const envVar = (0, env_js_2.getEnvironmentVariable)("LANGSMITH_RUNS_ENDPOINTS");
if (!envVar)
return [];
try {
const parsed = JSON.parse(envVar);
if (Array.isArray(parsed)) {
const replicas = [];
for (const item of parsed) {
if (typeof item !== "object" || item === null) {
console.warn(`Invalid item type in LANGSMITH_RUNS_ENDPOINTS: ` +
`expected object, got ${typeof item}`);
continue;
}
if (typeof item.api_url !== "string") {
console.warn(`Invalid api_url type in LANGSMITH_RUNS_ENDPOINTS: ` +
`expected string, got ${typeof item.api_url}`);
continue;
}
if (typeof item.api_key !== "string") {
console.warn(`Invalid api_key type in LANGSMITH_RUNS_ENDPOINTS: ` +
`expected string, got ${typeof item.api_key}`);
continue;
}
replicas.push({
apiUrl: item.api_url.replace(/\/$/, ""),
apiKey: item.api_key,
});
}
return replicas;
}
else if (typeof parsed === "object" && parsed !== null) {
_checkEndpointEnvUnset(parsed);
const replicas = [];
for (const [url, key] of Object.entries(parsed)) {
const cleanUrl = url.replace(/\/$/, "");
if (typeof key === "string") {
replicas.push({
apiUrl: cleanUrl,
apiKey: key,
});
}
else {
console.warn(`Invalid value type in LANGSMITH_RUNS_ENDPOINTS for URL ${url}: ` +
`expected string, got ${typeof key}`);
continue;
}
}
return replicas;
}
else {
console.warn("Invalid LANGSMITH_RUNS_ENDPOINTS – must be valid JSON array of " +
`objects with api_url and api_key properties, or object mapping url->apiKey, got ${typeof parsed}`);
return [];
}
}
catch (e) {
if ((0, error_js_1.isConflictingEndpointsError)(e)) {
throw e;
}
console.warn("Invalid LANGSMITH_RUNS_ENDPOINTS – must be valid JSON array of " +
"objects with api_url and api_key properties, or object mapping url->apiKey");
return [];
}
}
function _ensureWriteReplicas(replicas) {
// If null -> fetch from env
if (replicas) {
return replicas.map((replica) => {
if (Array.isArray(replica)) {
return {
projectName: replica[0],
updates: replica[1],
};
}
return replica;
});
}
return _getWriteReplicasFromEnv();
}
function _checkEndpointEnvUnset(parsed) {
if (Object.keys(parsed).length > 0 &&
(0, env_js_3.getLangSmithEnvironmentVariable)("ENDPOINT")) {
throw new error_js_1.ConflictingEndpointsError();
}
}