UNPKG

@langchain/langgraph

Version:
457 lines (456 loc) 20.5 kB
const require_constants = require("../constants.cjs"); const require_errors = require("../errors.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"); let _langchain_core_runnables = require("@langchain/core/runnables"); let _langchain_core_runnables_graph = require("@langchain/core/runnables/graph"); let zod_v4 = require("zod/v4"); let _langchain_core_utils_uuid = require("@langchain/core/utils/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); 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 === "__end__")) throw new require_errors.InvalidUpdateError("Cannot send a packet to the END node"); return await writer(destinations, config) ?? 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) { return args.length >= 1 && typeof args[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 ["|", ":"]) 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 === "__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 === "__end__") throw new Error("END cannot be a start node"); if (endKey === "__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)) options.path = (0, _langchain_core_runnables._coerceToRunnable)(options.path); 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, transformers } = {}) { 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, streamTransformers: transformers }); 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, branch] of Object.entries(branches)) compiled.attachBranch(start, name, 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 !== "__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); if (Object.values(this.nodes).some((node) => node.isErrorHandler)) for (const node of Object.keys(this.nodes)) allTargets.add(node); for (const node of Object.keys(this.nodes)) { if (this.nodes[node].isErrorHandler) continue; 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 !== "__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; } withConfig(config) { return super.withConfig(config); } 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 === "__end__") { if (start === "__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 === "__start__" && !this.nodes["__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) => { return new require_write.ChannelWrite(dests.map((dest) => { if (require_constants._isSend(dest)) return dest; return { channel: dest === "__end__" ? require_constants.END : `branch:${start}:${name}:${dest}`, value: require_write.PASSTHROUGH }; }), [require_constants.TAG_HIDDEN]); })); const ends = branch.ends ? Object.values(branch.ends) : Object.keys(this.nodes); for (const end of ends) if (end !== "__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]))); const discoveredEdges = []; function addEdge(start, end, label, conditional = false) { if (end === "__end__" && endNodes["__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!`); discoveredEdges.push({ src: start, dest: end, conditional }); 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, _langchain_core_utils_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 { 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); addImplicitTerminalEndEdges(this.builder.nodes, discoveredEdges, addEdge); 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]))); const discoveredEdges = []; function addEdge(start, end, label, conditional = false) { if (end === "__end__" && endNodes["__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!`); discoveredEdges.push({ src: start, dest: end, conditional }); 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, _langchain_core_utils_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 { 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); addImplicitTerminalEndEdges(this.builder.nodes, discoveredEdges, addEdge); return graph; } }; function isCompiledGraph(x) { return typeof x.attachNode === "function" && typeof x.attachEdge === "function"; } function _escapeMermaidKeywords(key) { if (key === "subgraph") return `"${key}"`; return key; } /** * Add implicit edges to END for terminal nodes (targets with no outgoing edges). * * Only nodes reached by a non-conditional edge are considered, so * conditional-branch targets are not treated as implicit sinks. */ function addImplicitTerminalEndEdges(nodes, discovered, addEdge) { const sources = new Set(discovered.map((e) => e.src)); const nonConditionalDestinations = [...new Set(discovered.filter((e) => !e.conditional && e.dest !== "__end__").map((e) => e.dest))].sort(); for (const displayDest of nonConditionalDestinations) { if (sources.has(displayDest)) continue; const rawKey = Object.keys(nodes).find((k) => _escapeMermaidKeywords(k) === displayDest); if (rawKey !== void 0 && nodes[rawKey]?.isErrorHandler) continue; addEdge(displayDest, require_constants.END); } } //#endregion exports.Branch = Branch; exports.CompiledGraph = CompiledGraph; exports.Graph = Graph; //# sourceMappingURL=graph.cjs.map