@langchain/langgraph
Version:
LangGraph
423 lines (421 loc) • 19.5 kB
JavaScript
const require_rolldown_runtime = require('../_virtual/rolldown_runtime.cjs');
const require_errors = require('../errors.cjs');
const require_constants = require('../constants.cjs');
const require_utils = require('../utils.cjs');
const require_write = require('../pregel/write.cjs');
const require_read = require('../pregel/read.cjs');
const require_subgraph = require('../pregel/utils/subgraph.cjs');
const require_pregel_index = require('../pregel/index.cjs');
const require_ephemeral_value = require('../channels/ephemeral_value.cjs');
const __langchain_core_runnables = require_rolldown_runtime.__toESM(require("@langchain/core/runnables"));
const __langchain_core_runnables_graph = require_rolldown_runtime.__toESM(require("@langchain/core/runnables/graph"));
const zod_v4 = require_rolldown_runtime.__toESM(require("zod/v4"));
const uuid = require_rolldown_runtime.__toESM(require("uuid"));
//#region src/graph/graph.ts
var Branch = class {
path;
ends;
constructor(options) {
if (__langchain_core_runnables.Runnable.isRunnable(options.path)) this.path = options.path;
else this.path = (0, __langchain_core_runnables._coerceToRunnable)(options.path).withConfig({ runName: `Branch` });
this.ends = Array.isArray(options.pathMap) ? options.pathMap.reduce((acc, n) => {
acc[n] = n;
return acc;
}, {}) : options.pathMap;
}
run(writer, reader) {
return require_write.ChannelWrite.registerWriter(new require_utils.RunnableCallable({
name: "<branch_run>",
trace: false,
func: async (input, config) => {
try {
return await this._route(input, config, writer, reader);
} catch (e) {
if (e.name === require_errors.NodeInterrupt.unminifiable_name) console.warn("[WARN]: 'NodeInterrupt' thrown in conditional edge. This is likely a bug in your graph implementation.\nNodeInterrupt should only be thrown inside a node, not in edge conditions.");
throw e;
}
}
}));
}
async _route(input, config, writer, reader) {
let result = await this.path.invoke(reader ? reader(config) : input, config);
if (!Array.isArray(result)) result = [result];
let destinations;
if (this.ends) destinations = result.map((r) => require_constants._isSend(r) ? r : this.ends[r]);
else destinations = result;
if (destinations.some((dest) => !dest)) throw new Error("Branch condition returned unknown or null destination");
if (destinations.filter(require_constants._isSend).some((packet) => packet.node === require_constants.END)) throw new require_errors.InvalidUpdateError("Cannot send a packet to the END node");
const writeResult = await writer(destinations, config);
return writeResult ?? input;
}
};
var Graph = class {
nodes;
edges;
branches;
entryPoint;
compiled = false;
constructor() {
this.nodes = {};
this.edges = /* @__PURE__ */ new Set();
this.branches = {};
}
warnIfCompiled(message) {
if (this.compiled) console.warn(message);
}
get allEdges() {
return this.edges;
}
addNode(...args) {
function isMutlipleNodes(args$1) {
return args$1.length >= 1 && typeof args$1[0] !== "string";
}
const nodes = isMutlipleNodes(args) ? Array.isArray(args[0]) ? args[0] : Object.entries(args[0]) : [[
args[0],
args[1],
args[2]
]];
if (nodes.length === 0) throw new Error("No nodes provided in `addNode`");
for (const [key, action, options] of nodes) {
for (const reservedChar of [require_constants.CHECKPOINT_NAMESPACE_SEPARATOR, require_constants.CHECKPOINT_NAMESPACE_END]) if (key.includes(reservedChar)) throw new Error(`"${reservedChar}" is a reserved character and is not allowed in node names.`);
this.warnIfCompiled(`Adding a node to a graph that has already been compiled. This will not be reflected in the compiled graph.`);
if (key in this.nodes) throw new Error(`Node \`${key}\` already present.`);
if (key === require_constants.END) throw new Error(`Node \`${key}\` is reserved.`);
const runnable = (0, __langchain_core_runnables._coerceToRunnable)(action);
this.nodes[key] = {
runnable,
metadata: options?.metadata,
subgraphs: require_subgraph.isPregelLike(runnable) ? [runnable] : options?.subgraphs,
ends: options?.ends
};
}
return this;
}
addEdge(startKey, endKey) {
this.warnIfCompiled(`Adding an edge to a graph that has already been compiled. This will not be reflected in the compiled graph.`);
if (startKey === require_constants.END) throw new Error("END cannot be a start node");
if (endKey === require_constants.START) throw new Error("START cannot be an end node");
if (Array.from(this.edges).some(([start]) => start === startKey) && !("channels" in this)) throw new Error(`Already found path for ${startKey}. For multiple edges, use StateGraph.`);
this.edges.add([startKey, endKey]);
return this;
}
addConditionalEdges(source, path, pathMap) {
const options = typeof source === "object" ? source : {
source,
path,
pathMap
};
this.warnIfCompiled("Adding an edge to a graph that has already been compiled. This will not be reflected in the compiled graph.");
if (!__langchain_core_runnables.Runnable.isRunnable(options.path)) {
const pathDisplayValues = Array.isArray(options.pathMap) ? options.pathMap.join(",") : Object.keys(options.pathMap ?? {}).join(",");
options.path = (0, __langchain_core_runnables._coerceToRunnable)(options.path).withConfig({ runName: `Branch<${options.source}${pathDisplayValues !== "" ? `,${pathDisplayValues}` : ""}>`.slice(0, 63) });
}
const name = options.path.getName() === "RunnableLambda" ? "condition" : options.path.getName();
if (this.branches[options.source] && this.branches[options.source][name]) throw new Error(`Condition \`${name}\` already present for node \`${source}\``);
this.branches[options.source] ??= {};
this.branches[options.source][name] = new Branch(options);
return this;
}
/**
* @deprecated use `addEdge(START, key)` instead
*/
setEntryPoint(key) {
this.warnIfCompiled("Setting the entry point of a graph that has already been compiled. This will not be reflected in the compiled graph.");
return this.addEdge(require_constants.START, key);
}
/**
* @deprecated use `addEdge(key, END)` instead
*/
setFinishPoint(key) {
this.warnIfCompiled("Setting a finish point of a graph that has already been compiled. This will not be reflected in the compiled graph.");
return this.addEdge(key, require_constants.END);
}
compile({ checkpointer, interruptBefore, interruptAfter, name } = {}) {
this.validate([...Array.isArray(interruptBefore) ? interruptBefore : [], ...Array.isArray(interruptAfter) ? interruptAfter : []]);
const compiled = new CompiledGraph({
builder: this,
checkpointer,
interruptAfter,
interruptBefore,
autoValidate: false,
nodes: {},
channels: {
[require_constants.START]: new require_ephemeral_value.EphemeralValue(),
[require_constants.END]: new require_ephemeral_value.EphemeralValue()
},
inputChannels: require_constants.START,
outputChannels: require_constants.END,
streamChannels: [],
streamMode: "values",
name
});
for (const [key, node] of Object.entries(this.nodes)) compiled.attachNode(key, node);
for (const [start, end] of this.edges) compiled.attachEdge(start, end);
for (const [start, branches] of Object.entries(this.branches)) for (const [name$1, branch] of Object.entries(branches)) compiled.attachBranch(start, name$1, branch);
return compiled.validate();
}
validate(interrupt) {
const allSources = new Set([...this.allEdges].map(([src, _]) => src));
for (const [start] of Object.entries(this.branches)) allSources.add(start);
for (const source of allSources) if (source !== require_constants.START && !(source in this.nodes)) throw new Error(`Found edge starting at unknown node \`${source}\``);
const allTargets = new Set([...this.allEdges].map(([_, target]) => target));
for (const [start, branches] of Object.entries(this.branches)) for (const branch of Object.values(branches)) if (branch.ends != null) for (const end of Object.values(branch.ends)) allTargets.add(end);
else {
allTargets.add(require_constants.END);
for (const node of Object.keys(this.nodes)) if (node !== start) allTargets.add(node);
}
for (const node of Object.values(this.nodes)) for (const target of node.ends ?? []) allTargets.add(target);
for (const node of Object.keys(this.nodes)) if (!allTargets.has(node)) throw new require_errors.UnreachableNodeError([
`Node \`${node}\` is not reachable.`,
"",
"If you are returning Command objects from your node,",
"make sure you are passing names of potential destination nodes as an \"ends\" array",
"into \".addNode(..., { ends: [\"node1\", \"node2\"] })\"."
].join("\n"), { lc_error_code: "UNREACHABLE_NODE" });
for (const target of allTargets) if (target !== require_constants.END && !(target in this.nodes)) throw new Error(`Found edge ending at unknown node \`${target}\``);
if (interrupt) {
for (const node of interrupt) if (!(node in this.nodes)) throw new Error(`Interrupt node \`${node}\` is not present`);
}
this.compiled = true;
}
};
var CompiledGraph = class extends require_pregel_index.Pregel {
builder;
constructor({ builder,...rest }) {
super(rest);
this.builder = builder;
}
attachNode(key, node) {
this.channels[key] = new require_ephemeral_value.EphemeralValue();
this.nodes[key] = new require_read.PregelNode({
channels: [],
triggers: [],
metadata: node.metadata,
subgraphs: node.subgraphs,
ends: node.ends
}).pipe(node.runnable).pipe(new require_write.ChannelWrite([{
channel: key,
value: require_write.PASSTHROUGH
}], [require_constants.TAG_HIDDEN]));
this.streamChannels.push(key);
}
attachEdge(start, end) {
if (end === require_constants.END) {
if (start === require_constants.START) throw new Error("Cannot have an edge from START to END");
this.nodes[start].writers.push(new require_write.ChannelWrite([{
channel: require_constants.END,
value: require_write.PASSTHROUGH
}], [require_constants.TAG_HIDDEN]));
} else {
this.nodes[end].triggers.push(start);
this.nodes[end].channels.push(start);
}
}
attachBranch(start, name, branch) {
if (start === require_constants.START && !this.nodes[require_constants.START]) this.nodes[require_constants.START] = require_pregel_index.Channel.subscribeTo(require_constants.START, { tags: [require_constants.TAG_HIDDEN] });
this.nodes[start].pipe(branch.run((dests) => {
const writes = dests.map((dest) => {
if (require_constants._isSend(dest)) return dest;
return {
channel: dest === require_constants.END ? require_constants.END : `branch:${start}:${name}:${dest}`,
value: require_write.PASSTHROUGH
};
});
return new require_write.ChannelWrite(writes, [require_constants.TAG_HIDDEN]);
}));
const ends = branch.ends ? Object.values(branch.ends) : Object.keys(this.nodes);
for (const end of ends) if (end !== require_constants.END) {
const channelName = `branch:${start}:${name}:${end}`;
this.channels[channelName] = new require_ephemeral_value.EphemeralValue();
this.nodes[end].triggers.push(channelName);
this.nodes[end].channels.push(channelName);
}
}
/**
* Returns a drawable representation of the computation graph.
*/
async getGraphAsync(config) {
const xray = config?.xray;
const graph = new __langchain_core_runnables_graph.Graph();
const startNodes = { [require_constants.START]: graph.addNode({ schema: zod_v4.z.any() }, require_constants.START) };
const endNodes = {};
let subgraphs = {};
if (xray) subgraphs = Object.fromEntries((await require_utils.gatherIterator(this.getSubgraphsAsync())).filter((x) => isCompiledGraph(x[1])));
function addEdge(start, end, label, conditional = false) {
if (end === require_constants.END && endNodes[require_constants.END] === void 0) endNodes[require_constants.END] = graph.addNode({ schema: zod_v4.z.any() }, require_constants.END);
if (startNodes[start] === void 0) return;
if (endNodes[end] === void 0) throw new Error(`End node ${end} not found!`);
return graph.addEdge(startNodes[start], endNodes[end], label !== end ? label : void 0, conditional);
}
for (const [key, nodeSpec] of Object.entries(this.builder.nodes)) {
const displayKey = _escapeMermaidKeywords(key);
const node = nodeSpec.runnable;
const metadata = nodeSpec.metadata ?? {};
if (this.interruptBefore?.includes(key) && this.interruptAfter?.includes(key)) metadata.__interrupt = "before,after";
else if (this.interruptBefore?.includes(key)) metadata.__interrupt = "before";
else if (this.interruptAfter?.includes(key)) metadata.__interrupt = "after";
if (xray) {
const newXrayValue = typeof xray === "number" ? xray - 1 : xray;
const drawableSubgraph = subgraphs[key] !== void 0 ? await subgraphs[key].getGraphAsync({
...config,
xray: newXrayValue
}) : node.getGraph(config);
drawableSubgraph.trimFirstNode();
drawableSubgraph.trimLastNode();
if (Object.keys(drawableSubgraph.nodes).length > 1) {
const [e, s] = graph.extend(drawableSubgraph, displayKey);
if (e === void 0) throw new Error(`Could not extend subgraph "${key}" due to missing entrypoint.`);
function _isRunnableInterface(thing) {
return thing ? thing.lc_runnable : false;
}
function _nodeDataStr(id, data) {
if (id !== void 0 && !(0, uuid.validate)(id)) return id;
else if (_isRunnableInterface(data)) try {
let dataStr = data.getName();
dataStr = dataStr.startsWith("Runnable") ? dataStr.slice(8) : dataStr;
return dataStr;
} catch (error) {
return data.getName();
}
else return data.name ?? "UnknownSchema";
}
if (s !== void 0) startNodes[displayKey] = {
name: _nodeDataStr(s.id, s.data),
...s
};
endNodes[displayKey] = {
name: _nodeDataStr(e.id, e.data),
...e
};
} else {
const newNode = graph.addNode(node, displayKey, metadata);
startNodes[displayKey] = newNode;
endNodes[displayKey] = newNode;
}
} else {
const newNode = graph.addNode(node, displayKey, metadata);
startNodes[displayKey] = newNode;
endNodes[displayKey] = newNode;
}
}
const sortedEdges = [...this.builder.allEdges].sort(([a], [b]) => {
if (a < b) return -1;
else if (b > a) return 1;
else return 0;
});
for (const [start, end] of sortedEdges) addEdge(_escapeMermaidKeywords(start), _escapeMermaidKeywords(end));
for (const [start, branches] of Object.entries(this.builder.branches)) {
const defaultEnds = {
...Object.fromEntries(Object.keys(this.builder.nodes).filter((k) => k !== start).map((k) => [_escapeMermaidKeywords(k), _escapeMermaidKeywords(k)])),
[require_constants.END]: require_constants.END
};
for (const branch of Object.values(branches)) {
let ends;
if (branch.ends !== void 0) ends = branch.ends;
else ends = defaultEnds;
for (const [label, end] of Object.entries(ends)) addEdge(_escapeMermaidKeywords(start), _escapeMermaidKeywords(end), label, true);
}
}
for (const [key, node] of Object.entries(this.builder.nodes)) if (node.ends !== void 0) for (const end of node.ends) addEdge(_escapeMermaidKeywords(key), _escapeMermaidKeywords(end), void 0, true);
return graph;
}
/**
* Returns a drawable representation of the computation graph.
*
* @deprecated Use getGraphAsync instead. The async method will be the default in the next minor core release.
*/
getGraph(config) {
const xray = config?.xray;
const graph = new __langchain_core_runnables_graph.Graph();
const startNodes = { [require_constants.START]: graph.addNode({ schema: zod_v4.z.any() }, require_constants.START) };
const endNodes = {};
let subgraphs = {};
if (xray) subgraphs = Object.fromEntries(require_utils.gatherIteratorSync(this.getSubgraphs()).filter((x) => isCompiledGraph(x[1])));
function addEdge(start, end, label, conditional = false) {
if (end === require_constants.END && endNodes[require_constants.END] === void 0) endNodes[require_constants.END] = graph.addNode({ schema: zod_v4.z.any() }, require_constants.END);
return graph.addEdge(startNodes[start], endNodes[end], label !== end ? label : void 0, conditional);
}
for (const [key, nodeSpec] of Object.entries(this.builder.nodes)) {
const displayKey = _escapeMermaidKeywords(key);
const node = nodeSpec.runnable;
const metadata = nodeSpec.metadata ?? {};
if (this.interruptBefore?.includes(key) && this.interruptAfter?.includes(key)) metadata.__interrupt = "before,after";
else if (this.interruptBefore?.includes(key)) metadata.__interrupt = "before";
else if (this.interruptAfter?.includes(key)) metadata.__interrupt = "after";
if (xray) {
const newXrayValue = typeof xray === "number" ? xray - 1 : xray;
const drawableSubgraph = subgraphs[key] !== void 0 ? subgraphs[key].getGraph({
...config,
xray: newXrayValue
}) : node.getGraph(config);
drawableSubgraph.trimFirstNode();
drawableSubgraph.trimLastNode();
if (Object.keys(drawableSubgraph.nodes).length > 1) {
const [e, s] = graph.extend(drawableSubgraph, displayKey);
if (e === void 0) throw new Error(`Could not extend subgraph "${key}" due to missing entrypoint.`);
function _isRunnableInterface(thing) {
return thing ? thing.lc_runnable : false;
}
function _nodeDataStr(id, data) {
if (id !== void 0 && !(0, uuid.validate)(id)) return id;
else if (_isRunnableInterface(data)) try {
let dataStr = data.getName();
dataStr = dataStr.startsWith("Runnable") ? dataStr.slice(8) : dataStr;
return dataStr;
} catch (error) {
return data.getName();
}
else return data.name ?? "UnknownSchema";
}
if (s !== void 0) startNodes[displayKey] = {
name: _nodeDataStr(s.id, s.data),
...s
};
endNodes[displayKey] = {
name: _nodeDataStr(e.id, e.data),
...e
};
} else {
const newNode = graph.addNode(node, displayKey, metadata);
startNodes[displayKey] = newNode;
endNodes[displayKey] = newNode;
}
} else {
const newNode = graph.addNode(node, displayKey, metadata);
startNodes[displayKey] = newNode;
endNodes[displayKey] = newNode;
}
}
const sortedEdges = [...this.builder.allEdges].sort(([a], [b]) => {
if (a < b) return -1;
else if (b > a) return 1;
else return 0;
});
for (const [start, end] of sortedEdges) addEdge(_escapeMermaidKeywords(start), _escapeMermaidKeywords(end));
for (const [start, branches] of Object.entries(this.builder.branches)) {
const defaultEnds = {
...Object.fromEntries(Object.keys(this.builder.nodes).filter((k) => k !== start).map((k) => [_escapeMermaidKeywords(k), _escapeMermaidKeywords(k)])),
[require_constants.END]: require_constants.END
};
for (const branch of Object.values(branches)) {
let ends;
if (branch.ends !== void 0) ends = branch.ends;
else ends = defaultEnds;
for (const [label, end] of Object.entries(ends)) addEdge(_escapeMermaidKeywords(start), _escapeMermaidKeywords(end), label, true);
}
}
return graph;
}
};
function isCompiledGraph(x) {
return typeof x.attachNode === "function" && typeof x.attachEdge === "function";
}
function _escapeMermaidKeywords(key) {
if (key === "subgraph") return `"${key}"`;
return key;
}
//#endregion
exports.Branch = Branch;
exports.CompiledGraph = CompiledGraph;
exports.Graph = Graph;
//# sourceMappingURL=graph.cjs.map