506 lines
18 KiB
JavaScript
506 lines
18 KiB
JavaScript
|
import * as uuid from "uuid";
|
||
|
import { getEnvironmentVariable, getRuntimeEnvironment, } from "./utils/env.js";
|
||
|
import { Client } from "./client.js";
|
||
|
import { isTracingEnabled } from "./env.js";
|
||
|
import { warnOnce } from "./utils/warn.js";
|
||
|
function stripNonAlphanumeric(input) {
|
||
|
return input.replace(/[-:.]/g, "");
|
||
|
}
|
||
|
export function convertToDottedOrderFormat(epoch, runId, 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 (stripNonAlphanumeric(`${new Date(epoch).toISOString().slice(0, -1)}${paddedOrder}Z`) + runId);
|
||
|
}
|
||
|
/**
|
||
|
* Baggage header information
|
||
|
*/
|
||
|
class Baggage {
|
||
|
constructor(metadata, tags) {
|
||
|
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
|
||
|
});
|
||
|
this.metadata = metadata;
|
||
|
this.tags = tags;
|
||
|
}
|
||
|
static fromHeader(value) {
|
||
|
const items = value.split(",");
|
||
|
let metadata = {};
|
||
|
let tags = [];
|
||
|
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(",");
|
||
|
}
|
||
|
}
|
||
|
return new Baggage(metadata, tags);
|
||
|
}
|
||
|
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(","))}`);
|
||
|
}
|
||
|
return items.join(",");
|
||
|
}
|
||
|
}
|
||
|
export 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, "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
|
||
|
});
|
||
|
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 };
|
||
|
Object.assign(this, { ...defaultConfig, ...config, client });
|
||
|
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.execution_order ??= 1;
|
||
|
this.child_execution_order ??= 1;
|
||
|
if (!this.dotted_order) {
|
||
|
const currentDottedOrder = convertToDottedOrderFormat(this.start_time, this.id, this.execution_order);
|
||
|
if (this.parent_run) {
|
||
|
this.dotted_order =
|
||
|
this.parent_run.dotted_order + "." + currentDottedOrder;
|
||
|
}
|
||
|
else {
|
||
|
this.dotted_order = currentDottedOrder;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
static getDefaultConfig() {
|
||
|
return {
|
||
|
id: uuid.v4(),
|
||
|
run_type: "chain",
|
||
|
project_name: getEnvironmentVariable("LANGCHAIN_PROJECT") ??
|
||
|
getEnvironmentVariable("LANGCHAIN_SESSION") ?? // TODO: Deprecate
|
||
|
"default",
|
||
|
child_runs: [],
|
||
|
api_url: getEnvironmentVariable("LANGCHAIN_ENDPOINT") ?? "http://localhost:1984",
|
||
|
api_key: getEnvironmentVariable("LANGCHAIN_API_KEY"),
|
||
|
caller_options: {},
|
||
|
start_time: Date.now(),
|
||
|
serialized: {},
|
||
|
inputs: {},
|
||
|
extra: {},
|
||
|
};
|
||
|
}
|
||
|
static getSharedClient() {
|
||
|
if (!RunTree.sharedClient) {
|
||
|
RunTree.sharedClient = new Client();
|
||
|
}
|
||
|
return RunTree.sharedClient;
|
||
|
}
|
||
|
createChild(config) {
|
||
|
const child_execution_order = this.child_execution_order + 1;
|
||
|
const child = new RunTree({
|
||
|
...config,
|
||
|
parent_run: this,
|
||
|
project_name: this.project_name,
|
||
|
client: this.client,
|
||
|
tracingEnabled: this.tracingEnabled,
|
||
|
execution_order: child_execution_order,
|
||
|
child_execution_order: child_execution_order,
|
||
|
});
|
||
|
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()) {
|
||
|
this.outputs = this.outputs ?? outputs;
|
||
|
this.error = this.error ?? error;
|
||
|
this.end_time = this.end_time ?? endTime;
|
||
|
}
|
||
|
_convertToCreate(run, runtimeEnv, excludeChildRuns = true) {
|
||
|
const runExtra = run.extra ?? {};
|
||
|
if (!runExtra.runtime) {
|
||
|
runExtra.runtime = {};
|
||
|
}
|
||
|
if (runtimeEnv) {
|
||
|
for (const [k, v] of Object.entries(runtimeEnv)) {
|
||
|
if (!runExtra.runtime[k]) {
|
||
|
runExtra.runtime[k] = v;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
let child_runs;
|
||
|
let parent_run_id;
|
||
|
if (!excludeChildRuns) {
|
||
|
child_runs = run.child_runs.map((child_run) => this._convertToCreate(child_run, runtimeEnv, excludeChildRuns));
|
||
|
parent_run_id = undefined;
|
||
|
}
|
||
|
else {
|
||
|
parent_run_id = run.parent_run?.id;
|
||
|
child_runs = [];
|
||
|
}
|
||
|
const persistedRun = {
|
||
|
id: run.id,
|
||
|
name: run.name,
|
||
|
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,
|
||
|
};
|
||
|
return persistedRun;
|
||
|
}
|
||
|
async postRun(excludeChildRuns = true) {
|
||
|
try {
|
||
|
const runtimeEnv = await getRuntimeEnvironment();
|
||
|
const runCreate = await this._convertToCreate(this, runtimeEnv, true);
|
||
|
await this.client.createRun(runCreate);
|
||
|
if (!excludeChildRuns) {
|
||
|
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);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
catch (error) {
|
||
|
console.error(`Error in postRun for run ${this.id}:`, error);
|
||
|
}
|
||
|
}
|
||
|
async patchRun() {
|
||
|
try {
|
||
|
const runUpdate = {
|
||
|
end_time: this.end_time,
|
||
|
error: this.error,
|
||
|
inputs: this.inputs,
|
||
|
outputs: this.outputs,
|
||
|
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,
|
||
|
};
|
||
|
await this.client.updateRun(this.id, runUpdate);
|
||
|
}
|
||
|
catch (error) {
|
||
|
console.error(`Error in patchRun for run ${this.id}`, error);
|
||
|
}
|
||
|
}
|
||
|
toJSON() {
|
||
|
return this._convertToCreate(this, undefined, false);
|
||
|
}
|
||
|
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 = 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;
|
||
|
}
|
||
|
return new RunTree(config);
|
||
|
}
|
||
|
toHeaders(headers) {
|
||
|
const result = {
|
||
|
"langsmith-trace": this.dotted_order,
|
||
|
baggage: new Baggage(this.extra?.metadata, this.tags).toHeader(),
|
||
|
};
|
||
|
if (headers) {
|
||
|
for (const [key, value] of Object.entries(result)) {
|
||
|
headers.set(key, value);
|
||
|
}
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
}
|
||
|
Object.defineProperty(RunTree, "sharedClient", {
|
||
|
enumerable: true,
|
||
|
configurable: true,
|
||
|
writable: true,
|
||
|
value: null
|
||
|
});
|
||
|
export function isRunTree(x) {
|
||
|
return (x !== undefined &&
|
||
|
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));
|
||
|
}
|
||
|
export 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
|
||
|
return (x !== undefined &&
|
||
|
typeof x.callbacks === "object" &&
|
||
|
// Callback manager with a langchain tracer
|
||
|
(containsLangChainTracerLike(x.callbacks?.handlers) ||
|
||
|
// Or it's an array with a LangChainTracerLike object within it
|
||
|
containsLangChainTracerLike(x.callbacks)));
|
||
|
}
|