UNPKG

@langchain/langgraph

Version:

LangGraph

729 lines 30.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.CompiledGraph = exports.Graph = exports.Branch = void 0; /* eslint-disable @typescript-eslint/no-use-before-define */ const runnables_1 = require("@langchain/core/runnables"); const graph_1 = require("@langchain/core/runnables/graph"); const zod_1 = require("zod"); const uuid_1 = require("uuid"); const read_js_1 = require("../pregel/read.cjs"); const index_js_1 = require("../pregel/index.cjs"); const ephemeral_value_js_1 = require("../channels/ephemeral_value.cjs"); const write_js_1 = require("../pregel/write.cjs"); const constants_js_1 = require("../constants.cjs"); const utils_js_1 = require("../utils.cjs"); const errors_js_1 = require("../errors.cjs"); const subgraph_js_1 = require("../pregel/utils/subgraph.cjs"); class Branch { constructor(options) { Object.defineProperty(this, "path", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "ends", { enumerable: true, configurable: true, writable: true, value: void 0 }); if (runnables_1.Runnable.isRunnable(options.path)) { this.path = options.path; } else { this.path = (0, runnables_1._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 write_js_1.ChannelWrite.registerWriter(new utils_js_1.RunnableCallable({ name: "<branch_run>", trace: false, func: async (input, config) => { try { return await this._route(input, config, writer, reader); // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (e) { // Detect & warn if NodeInterrupt is thrown in a conditional edge if (e.name === errors_js_1.NodeInterrupt.unminifiable_name) { console.warn("[WARN]: 'NodeInterrupt' thrown in conditional edge. This is likely a bug in your graph implementation.\n" + "NodeInterrupt should only be thrown inside a node, not in edge conditions."); } throw e; } }, })); } async _route(input, config, writer, reader // eslint-disable-next-line @typescript-eslint/no-explicit-any ) { 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) => ((0, constants_js_1._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(constants_js_1._isSend).some((packet) => packet.node === constants_js_1.END)) { throw new errors_js_1.InvalidUpdateError("Cannot send a packet to the END node"); } const writeResult = await writer(destinations, config); return writeResult ?? input; } } exports.Branch = Branch; class Graph { constructor() { Object.defineProperty(this, "nodes", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "edges", { enumerable: true, configurable: true, writable: true, value: void 0 }); // eslint-disable-next-line @typescript-eslint/no-explicit-any Object.defineProperty(this, "branches", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "entryPoint", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "compiled", { enumerable: true, configurable: true, writable: true, value: false }); this.nodes = {}; this.edges = 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) // eslint-disable-line no-nested-ternary ? 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 [ constants_js_1.CHECKPOINT_NAMESPACE_SEPARATOR, constants_js_1.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 === constants_js_1.END) { throw new Error(`Node \`${key}\` is reserved.`); } const runnable = (0, runnables_1._coerceToRunnable)( // Account for arbitrary state due to Send API action); this.nodes[key] = { runnable, metadata: options?.metadata, subgraphs: (0, subgraph_js_1.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 === constants_js_1.END) { throw new Error("END cannot be a start node"); } if (endKey === constants_js_1.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: 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 (!runnables_1.Runnable.isRunnable(options.path)) { const pathDisplayValues = Array.isArray(options.pathMap) ? options.pathMap.join(",") : Object.keys(options.pathMap ?? {}).join(","); options.path = (0, runnables_1._coerceToRunnable)(options.path).withConfig({ runName: `Branch<${options.source}${pathDisplayValues !== "" ? `,${pathDisplayValues}` : ""}>`.slice(0, 63), }); } // find a name for condition const name = options.path.getName() === "RunnableLambda" ? "condition" : options.path.getName(); // validate condition if (this.branches[options.source] && this.branches[options.source][name]) { throw new Error(`Condition \`${name}\` already present for node \`${source}\``); } // save it 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(constants_js_1.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, constants_js_1.END); } compile({ checkpointer, interruptBefore, interruptAfter, name, } = {}) { // validate the graph this.validate([ ...(Array.isArray(interruptBefore) ? interruptBefore : []), ...(Array.isArray(interruptAfter) ? interruptAfter : []), ]); // create empty compiled graph const compiled = new CompiledGraph({ builder: this, checkpointer, interruptAfter, interruptBefore, autoValidate: false, nodes: {}, channels: { [constants_js_1.START]: new ephemeral_value_js_1.EphemeralValue(), [constants_js_1.END]: new ephemeral_value_js_1.EphemeralValue(), }, inputChannels: constants_js_1.START, outputChannels: constants_js_1.END, streamChannels: [], streamMode: "values", name, }); // attach nodes, edges and branches 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) { // assemble sources const allSources = new Set([...this.allEdges].map(([src, _]) => src)); for (const [start] of Object.entries(this.branches)) { allSources.add(start); } // validate sources for (const source of allSources) { if (source !== constants_js_1.START && !(source in this.nodes)) { throw new Error(`Found edge starting at unknown node \`${source}\``); } } // assemble targets 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(constants_js_1.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); } } // validate targets for (const node of Object.keys(this.nodes)) { if (!allTargets.has(node)) { throw new errors_js_1.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 !== constants_js_1.END && !(target in this.nodes)) { throw new Error(`Found edge ending at unknown node \`${target}\``); } } // validate interrupts if (interrupt) { for (const node of interrupt) { if (!(node in this.nodes)) { throw new Error(`Interrupt node \`${node}\` is not present`); } } } this.compiled = true; } } exports.Graph = Graph; class CompiledGraph extends index_js_1.Pregel { constructor({ builder, ...rest }) { super(rest); Object.defineProperty(this, "builder", { enumerable: true, configurable: true, writable: true, value: void 0 }); this.builder = builder; } attachNode(key, node) { this.channels[key] = new ephemeral_value_js_1.EphemeralValue(); this.nodes[key] = new read_js_1.PregelNode({ channels: [], triggers: [], metadata: node.metadata, subgraphs: node.subgraphs, ends: node.ends, }) .pipe(node.runnable) .pipe(new write_js_1.ChannelWrite([{ channel: key, value: write_js_1.PASSTHROUGH }], [constants_js_1.TAG_HIDDEN])); this.streamChannels.push(key); } attachEdge(start, end) { if (end === constants_js_1.END) { if (start === constants_js_1.START) { throw new Error("Cannot have an edge from START to END"); } this.nodes[start].writers.push(new write_js_1.ChannelWrite([{ channel: constants_js_1.END, value: write_js_1.PASSTHROUGH }], [constants_js_1.TAG_HIDDEN])); } else { this.nodes[end].triggers.push(start); this.nodes[end].channels.push(start); } } attachBranch(start, name, branch) { // add hidden start node if (start === constants_js_1.START && !this.nodes[constants_js_1.START]) { this.nodes[constants_js_1.START] = index_js_1.Channel.subscribeTo(constants_js_1.START, { tags: [constants_js_1.TAG_HIDDEN] }); } // attach branch writer this.nodes[start].pipe(branch.run((dests) => { const writes = dests.map((dest) => { if ((0, constants_js_1._isSend)(dest)) { return dest; } return { channel: dest === constants_js_1.END ? constants_js_1.END : `branch:${start}:${name}:${dest}`, value: write_js_1.PASSTHROUGH, }; }); return new write_js_1.ChannelWrite(writes, [constants_js_1.TAG_HIDDEN]); })); // attach branch readers const ends = branch.ends ? Object.values(branch.ends) : Object.keys(this.nodes); for (const end of ends) { if (end !== constants_js_1.END) { const channelName = `branch:${start}:${name}:${end}`; this.channels[channelName] = new ephemeral_value_js_1.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 graph_1.Graph(); const startNodes = { [constants_js_1.START]: graph.addNode({ schema: zod_1.z.any(), }, constants_js_1.START), }; const endNodes = {}; // eslint-disable-next-line @typescript-eslint/no-explicit-any let subgraphs = {}; if (xray) { subgraphs = Object.fromEntries((await (0, utils_js_1.gatherIterator)(this.getSubgraphsAsync())).filter( // eslint-disable-next-line @typescript-eslint/no-explicit-any (x) => isCompiledGraph(x[1]))); } function addEdge(start, end, label, conditional = false) { if (end === constants_js_1.END && endNodes[constants_js_1.END] === undefined) { endNodes[constants_js_1.END] = graph.addNode({ schema: zod_1.z.any() }, constants_js_1.END); } if (startNodes[start] === undefined) { return; } if (endNodes[end] === undefined) { throw new Error(`End node ${end} not found!`); } return graph.addEdge(startNodes[start], endNodes[end], label !== end ? label : undefined, 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] !== undefined ? 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 === undefined) { throw new Error(`Could not extend subgraph "${key}" due to missing entrypoint.`); } // TODO: Remove default name once we stop supporting core 0.2.0 // eslint-disable-next-line no-inner-declarations function _isRunnableInterface( // eslint-disable-next-line @typescript-eslint/no-explicit-any thing) { return thing ? thing.lc_runnable : false; } // eslint-disable-next-line no-inner-declarations function _nodeDataStr(id, data) { if (id !== undefined && !(0, uuid_1.validate)(id)) { return id; } else if (_isRunnableInterface(data)) { try { let dataStr = data.getName(); dataStr = dataStr.startsWith("Runnable") ? dataStr.slice("Runnable".length) : dataStr; return dataStr; } catch (error) { return data.getName(); } } else { return data.name ?? "UnknownSchema"; } } // TODO: Remove casts when we stop supporting core 0.2.0 if (s !== undefined) { startNodes[displayKey] = { name: _nodeDataStr(s.id, s.data), ...s, }; } endNodes[displayKey] = { name: _nodeDataStr(e.id, e.data), ...e, }; } else { // TODO: Remove when we stop supporting core 0.2.0 // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore const newNode = graph.addNode(node, displayKey, metadata); startNodes[displayKey] = newNode; endNodes[displayKey] = newNode; } } else { // TODO: Remove when we stop supporting core 0.2.0 // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore 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)])), [constants_js_1.END]: constants_js_1.END, }; for (const branch of Object.values(branches)) { let ends; if (branch.ends !== undefined) { 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 !== undefined) { for (const end of node.ends) { addEdge(_escapeMermaidKeywords(key), _escapeMermaidKeywords(end), undefined, 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 graph_1.Graph(); const startNodes = { [constants_js_1.START]: graph.addNode({ schema: zod_1.z.any(), }, constants_js_1.START), }; const endNodes = {}; // eslint-disable-next-line @typescript-eslint/no-explicit-any let subgraphs = {}; if (xray) { subgraphs = Object.fromEntries((0, utils_js_1.gatherIteratorSync)(this.getSubgraphs()).filter( // eslint-disable-next-line @typescript-eslint/no-explicit-any (x) => isCompiledGraph(x[1]))); } function addEdge(start, end, label, conditional = false) { if (end === constants_js_1.END && endNodes[constants_js_1.END] === undefined) { endNodes[constants_js_1.END] = graph.addNode({ schema: zod_1.z.any() }, constants_js_1.END); } return graph.addEdge(startNodes[start], endNodes[end], label !== end ? label : undefined, 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] !== undefined ? 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 === undefined) { throw new Error(`Could not extend subgraph "${key}" due to missing entrypoint.`); } // TODO: Remove default name once we stop supporting core 0.2.0 // eslint-disable-next-line no-inner-declarations function _isRunnableInterface( // eslint-disable-next-line @typescript-eslint/no-explicit-any thing) { return thing ? thing.lc_runnable : false; } // eslint-disable-next-line no-inner-declarations function _nodeDataStr(id, data) { if (id !== undefined && !(0, uuid_1.validate)(id)) { return id; } else if (_isRunnableInterface(data)) { try { let dataStr = data.getName(); dataStr = dataStr.startsWith("Runnable") ? dataStr.slice("Runnable".length) : dataStr; return dataStr; } catch (error) { return data.getName(); } } else { return data.name ?? "UnknownSchema"; } } // TODO: Remove casts when we stop supporting core 0.2.0 if (s !== undefined) { startNodes[displayKey] = { name: _nodeDataStr(s.id, s.data), ...s, }; } endNodes[displayKey] = { name: _nodeDataStr(e.id, e.data), ...e, }; } else { // TODO: Remove when we stop supporting core 0.2.0 // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore const newNode = graph.addNode(node, displayKey, metadata); startNodes[displayKey] = newNode; endNodes[displayKey] = newNode; } } else { // TODO: Remove when we stop supporting core 0.2.0 // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore 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)])), [constants_js_1.END]: constants_js_1.END, }; for (const branch of Object.values(branches)) { let ends; if (branch.ends !== undefined) { ends = branch.ends; } else { ends = defaultEnds; } for (const [label, end] of Object.entries(ends)) { addEdge(_escapeMermaidKeywords(start), _escapeMermaidKeywords(end), label, true); } } } return graph; } } exports.CompiledGraph = CompiledGraph; // eslint-disable-next-line @typescript-eslint/no-explicit-any function isCompiledGraph(x) { return ( // eslint-disable-next-line @typescript-eslint/no-explicit-any typeof x.attachNode === "function" && // eslint-disable-next-line @typescript-eslint/no-explicit-any typeof x.attachEdge === "function"); } function _escapeMermaidKeywords(key) { if (key === "subgraph") { return `"${key}"`; } return key; } //# sourceMappingURL=graph.js.map