UNPKG

@langchain/langgraph

Version:

LangGraph

768 lines 30.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.CompiledStateGraph = exports.StateGraph = void 0; exports.typedNode = typedNode; /* eslint-disable @typescript-eslint/no-use-before-define */ const runnables_1 = require("@langchain/core/runnables"); const base_js_1 = require("../channels/base.cjs"); const graph_js_1 = require("./graph.cjs"); const write_js_1 = require("../pregel/write.cjs"); const read_js_1 = require("../pregel/read.cjs"); const named_barrier_value_js_1 = require("../channels/named_barrier_value.cjs"); const ephemeral_value_js_1 = require("../channels/ephemeral_value.cjs"); const utils_js_1 = require("../utils.cjs"); const constants_js_1 = require("../constants.cjs"); const errors_js_1 = require("../errors.cjs"); const annotation_js_1 = require("./annotation.cjs"); const base_js_2 = require("../managed/base.cjs"); const subgraph_js_1 = require("../pregel/utils/subgraph.cjs"); const state_js_1 = require("./zod/state.cjs"); const last_value_js_1 = require("../channels/last_value.cjs"); const ROOT = "__root__"; const PartialStateSchema = Symbol.for("langgraph.state.partial"); /** * A graph whose nodes communicate by reading and writing to a shared state. * Each node takes a defined `State` as input and returns a `Partial<State>`. * * Each state key can optionally be annotated with a reducer function that * will be used to aggregate the values of that key received from multiple nodes. * The signature of a reducer function is (left: Value, right: UpdateValue) => Value. * * See {@link Annotation} for more on defining state. * * After adding nodes and edges to your graph, you must call `.compile()` on it before * you can use it. * * @example * ```ts * import { * type BaseMessage, * AIMessage, * HumanMessage, * } from "@langchain/core/messages"; * import { StateGraph, Annotation } from "@langchain/langgraph"; * * // Define a state with a single key named "messages" that will * // combine a returned BaseMessage or arrays of BaseMessages * const StateAnnotation = Annotation.Root({ * sentiment: Annotation<string>, * messages: Annotation<BaseMessage[]>({ * reducer: (left: BaseMessage[], right: BaseMessage | BaseMessage[]) => { * if (Array.isArray(right)) { * return left.concat(right); * } * return left.concat([right]); * }, * default: () => [], * }), * }); * * const graphBuilder = new StateGraph(StateAnnotation); * * // A node in the graph that returns an object with a "messages" key * // will update the state by combining the existing value with the returned one. * const myNode = (state: typeof StateAnnotation.State) => { * return { * messages: [new AIMessage("Some new response")], * sentiment: "positive", * }; * }; * * const graph = graphBuilder * .addNode("myNode", myNode) * .addEdge("__start__", "myNode") * .addEdge("myNode", "__end__") * .compile(); * * await graph.invoke({ messages: [new HumanMessage("how are you?")] }); * * // { * // messages: [HumanMessage("how are you?"), AIMessage("Some new response")], * // sentiment: "positive", * // } * ``` */ class StateGraph extends graph_js_1.Graph { constructor(fields, configSchema) { super(); Object.defineProperty(this, "channels", { enumerable: true, configurable: true, writable: true, value: {} }); // TODO: this doesn't dedupe edges as in py, so worth fixing at some point Object.defineProperty(this, "waitingEdges", { enumerable: true, configurable: true, writable: true, value: new Set() }); /** @internal */ Object.defineProperty(this, "_schemaDefinition", { enumerable: true, configurable: true, writable: true, value: void 0 }); /** @internal */ Object.defineProperty(this, "_schemaRuntimeDefinition", { enumerable: true, configurable: true, writable: true, value: void 0 }); /** @internal */ Object.defineProperty(this, "_inputDefinition", { enumerable: true, configurable: true, writable: true, value: void 0 }); /** @internal */ Object.defineProperty(this, "_inputRuntimeDefinition", { enumerable: true, configurable: true, writable: true, value: void 0 }); /** @internal */ Object.defineProperty(this, "_outputDefinition", { enumerable: true, configurable: true, writable: true, value: void 0 }); /** @internal */ Object.defineProperty(this, "_outputRuntimeDefinition", { enumerable: true, configurable: true, writable: true, value: void 0 }); /** * Map schemas to managed values * @internal */ Object.defineProperty(this, "_schemaDefinitions", { enumerable: true, configurable: true, writable: true, value: new Map() }); /** @internal Used only for typing. */ Object.defineProperty(this, "_configSchema", { enumerable: true, configurable: true, writable: true, value: void 0 }); /** @internal */ Object.defineProperty(this, "_configRuntimeSchema", { enumerable: true, configurable: true, writable: true, value: void 0 }); if (isZodStateGraphArgsWithStateSchema(fields)) { const stateDef = (0, state_js_1.getChannelsFromZod)(fields.state); const inputDef = fields.input != null ? (0, state_js_1.getChannelsFromZod)(fields.input) : stateDef; const outputDef = fields.output != null ? (0, state_js_1.getChannelsFromZod)(fields.output) : stateDef; this._schemaDefinition = stateDef; this._schemaRuntimeDefinition = fields.state; this._inputDefinition = inputDef; this._inputRuntimeDefinition = fields.input ?? PartialStateSchema; this._outputDefinition = outputDef; this._outputRuntimeDefinition = fields.output ?? fields.state; } else if ((0, state_js_1.isAnyZodObject)(fields)) { const stateDef = (0, state_js_1.getChannelsFromZod)(fields); this._schemaDefinition = stateDef; this._schemaRuntimeDefinition = fields; this._inputDefinition = stateDef; this._inputRuntimeDefinition = PartialStateSchema; this._outputDefinition = stateDef; this._outputRuntimeDefinition = fields; } else if (isStateGraphArgsWithInputOutputSchemas(fields)) { this._schemaDefinition = fields.input.spec; this._inputDefinition = fields.input.spec; this._outputDefinition = fields.output.spec; } else if (isStateGraphArgsWithStateSchema(fields)) { this._schemaDefinition = fields.stateSchema.spec; this._inputDefinition = (fields.input?.spec ?? this._schemaDefinition); this._outputDefinition = (fields.output?.spec ?? this._schemaDefinition); } else if (isStateDefinition(fields) || isAnnotationRoot(fields)) { const spec = isAnnotationRoot(fields) ? fields.spec : fields; this._schemaDefinition = spec; } else if (isStateGraphArgs(fields)) { const spec = _getChannels(fields.channels); this._schemaDefinition = spec; } else { throw new Error("Invalid StateGraph input. Make sure to pass a valid Annotation.Root or Zod schema."); } this._inputDefinition ??= this._schemaDefinition; this._outputDefinition ??= this._schemaDefinition; this._addSchema(this._schemaDefinition); this._addSchema(this._inputDefinition); this._addSchema(this._outputDefinition); if ((0, state_js_1.isAnyZodObject)(configSchema)) { this._configRuntimeSchema = configSchema.passthrough(); } } get allEdges() { return new Set([ ...this.edges, ...Array.from(this.waitingEdges).flatMap(([starts, end]) => starts.map((start) => [start, end])), ]); } _addSchema(stateDefinition) { if (this._schemaDefinitions.has(stateDefinition)) { return; } // TODO: Support managed values this._schemaDefinitions.set(stateDefinition, stateDefinition); for (const [key, val] of Object.entries(stateDefinition)) { let channel; if (typeof val === "function") { channel = val(); } else { channel = val; } if (this.channels[key] !== undefined) { if (this.channels[key] !== channel) { if (!(0, base_js_2.isConfiguredManagedValue)(channel) && channel.lc_graph_name !== "LastValue") { throw new Error(`Channel "${key}" already exists with a different type.`); } } } else { this.channels[key] = channel; } } } addNode(...args) { function isMultipleNodes(args) { return args.length >= 1 && typeof args[0] !== "string"; } const nodes = (isMultipleNodes(args) // eslint-disable-line no-nested-ternary ? Array.isArray(args[0]) ? args[0] : Object.entries(args[0]).map(([key, action]) => [ key, action, // eslint-disable-next-line @typescript-eslint/no-explicit-any action[Symbol.for("langgraph.state.node")] ?? undefined, ]) : [[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) { if (key in this.channels) { throw new Error(`${key} is already being used as a state attribute (a.k.a. a channel), cannot also be used as a node name.`); } 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 || key === constants_js_1.START) { throw new Error(`Node \`${key}\` is reserved.`); } let inputSpec = this._schemaDefinition; if (options?.input !== undefined) { if ((0, state_js_1.isAnyZodObject)(options.input)) { inputSpec = (0, state_js_1.getChannelsFromZod)(options.input); } else if (options.input.spec !== undefined) { inputSpec = options.input.spec; } } if (inputSpec !== undefined) { this._addSchema(inputSpec); } let runnable; if (runnables_1.Runnable.isRunnable(action)) { runnable = action; } else if (typeof action === "function") { runnable = new utils_js_1.RunnableCallable({ func: action, name: key, trace: false, }); } else { runnable = (0, runnables_1._coerceToRunnable)(action); } let cachePolicy = options?.cachePolicy; if (typeof cachePolicy === "boolean") { cachePolicy = cachePolicy ? {} : undefined; } const nodeSpec = { runnable: runnable, retryPolicy: options?.retryPolicy, cachePolicy, metadata: options?.metadata, input: inputSpec ?? this._schemaDefinition, subgraphs: (0, subgraph_js_1.isPregelLike)(runnable) ? // eslint-disable-next-line @typescript-eslint/no-explicit-any [runnable] : options?.subgraphs, ends: options?.ends, defer: options?.defer, }; this.nodes[key] = nodeSpec; } return this; } addEdge(startKey, endKey) { if (typeof startKey === "string") { return super.addEdge(startKey, endKey); } if (this.compiled) { console.warn("Adding an edge to a graph that has already been compiled. This will " + "not be reflected in the compiled graph."); } for (const start of startKey) { if (start === constants_js_1.END) { throw new Error("END cannot be a start node"); } if (!Object.keys(this.nodes).some((node) => node === start)) { throw new Error(`Need to add a node named "${start}" first`); } } if (endKey === constants_js_1.END) { throw new Error("END cannot be an end node"); } if (!Object.keys(this.nodes).some((node) => node === endKey)) { throw new Error(`Need to add a node named "${endKey}" first`); } this.waitingEdges.add([startKey, endKey]); return this; } addSequence(nodes) { const parsedNodes = Array.isArray(nodes) ? nodes : Object.entries(nodes).map(([key, action]) => [ key, action, // eslint-disable-next-line @typescript-eslint/no-explicit-any action[Symbol.for("langgraph.state.node")] ?? undefined, ]); if (parsedNodes.length === 0) { throw new Error("Sequence requires at least one node."); } let previousNode; for (const [key, action, options] of parsedNodes) { if (key in this.nodes) { throw new Error(`Node names must be unique: node with the name "${key}" already exists.`); } const validKey = key; this.addNode(validKey, action, options); if (previousNode != null) { this.addEdge(previousNode, validKey); } previousNode = validKey; } return this; } compile({ checkpointer, store, cache, interruptBefore, interruptAfter, name, } = {}) { // validate the graph this.validate([ ...(Array.isArray(interruptBefore) ? interruptBefore : []), ...(Array.isArray(interruptAfter) ? interruptAfter : []), ]); // prepare output channels const outputKeys = Object.keys(this._schemaDefinitions.get(this._outputDefinition)); const outputChannels = outputKeys.length === 1 && outputKeys[0] === ROOT ? ROOT : outputKeys; const streamKeys = Object.keys(this.channels); const streamChannels = streamKeys.length === 1 && streamKeys[0] === ROOT ? ROOT : streamKeys; // create empty compiled graph const compiled = new CompiledStateGraph({ builder: this, checkpointer, interruptAfter, interruptBefore, autoValidate: false, nodes: {}, channels: { ...this.channels, [constants_js_1.START]: new ephemeral_value_js_1.EphemeralValue(), }, inputChannels: constants_js_1.START, outputChannels, streamChannels, streamMode: "updates", store, cache, name, }); // attach nodes, edges and branches compiled.attachNode(constants_js_1.START); for (const [key, node] of Object.entries(this.nodes)) { compiled.attachNode(key, node); } compiled.attachBranch(constants_js_1.START, constants_js_1.SELF, _getControlBranch(), { withReader: false, }); for (const [key] of Object.entries(this.nodes)) { compiled.attachBranch(key, constants_js_1.SELF, _getControlBranch(), { withReader: false, }); } for (const [start, end] of this.edges) { compiled.attachEdge(start, end); } for (const [starts, end] of this.waitingEdges) { compiled.attachEdge(starts, 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(); } } exports.StateGraph = StateGraph; function _getChannels(schema) { const channels = {}; for (const [name, val] of Object.entries(schema)) { if (name === ROOT) { channels[name] = (0, annotation_js_1.getChannel)(val); } else { const key = name; channels[name] = (0, annotation_js_1.getChannel)(val); } } return channels; } /** * Final result from building and compiling a {@link StateGraph}. * Should not be instantiated directly, only using the StateGraph `.compile()` * instance method. */ class CompiledStateGraph extends graph_js_1.CompiledGraph { attachNode(key, node) { let outputKeys; if (key === constants_js_1.START) { // Get input schema keys excluding managed values outputKeys = Object.entries(this.builder._schemaDefinitions.get(this.builder._inputDefinition)) .filter(([_, v]) => !(0, base_js_2.isConfiguredManagedValue)(v)) .map(([k]) => k); } else { outputKeys = Object.keys(this.builder.channels); } // eslint-disable-next-line @typescript-eslint/no-explicit-any function _getRoot(input) { if ((0, constants_js_1.isCommand)(input)) { if (input.graph === constants_js_1.Command.PARENT) { return null; } return input._updateAsTuples(); } else if (Array.isArray(input) && input.length > 0 && input.some((i) => (0, constants_js_1.isCommand)(i))) { const updates = []; for (const i of input) { if ((0, constants_js_1.isCommand)(i)) { if (i.graph === constants_js_1.Command.PARENT) { continue; } updates.push(...i._updateAsTuples()); } else { updates.push([ROOT, i]); } } return updates; } else if (input != null) { return [[ROOT, input]]; } return null; } // to avoid name collision below const nodeKey = key; // eslint-disable-next-line @typescript-eslint/no-explicit-any function _getUpdates(input) { if (!input) { return null; } else if ((0, constants_js_1.isCommand)(input)) { if (input.graph === constants_js_1.Command.PARENT) { return null; } return input._updateAsTuples().filter(([k]) => outputKeys.includes(k)); } else if (Array.isArray(input) && input.length > 0 && input.some(constants_js_1.isCommand)) { const updates = []; for (const item of input) { if ((0, constants_js_1.isCommand)(item)) { if (item.graph === constants_js_1.Command.PARENT) { continue; } updates.push(...item._updateAsTuples().filter(([k]) => outputKeys.includes(k))); } else { const itemUpdates = _getUpdates(item); if (itemUpdates) { updates.push(...(itemUpdates ?? [])); } } } return updates; } else if (typeof input === "object" && !Array.isArray(input)) { return Object.entries(input).filter(([k]) => outputKeys.includes(k)); } else { const typeofInput = Array.isArray(input) ? "array" : typeof input; throw new errors_js_1.InvalidUpdateError(`Expected node "${nodeKey.toString()}" to return an object or an array containing at least one Command object, received ${typeofInput}`, { lc_error_code: "INVALID_GRAPH_NODE_RETURN_VALUE", }); } } const stateWriteEntries = [ { value: write_js_1.PASSTHROUGH, mapper: new utils_js_1.RunnableCallable({ func: outputKeys.length && outputKeys[0] === ROOT ? _getRoot : _getUpdates, trace: false, recurse: false, }), }, ]; // add node and output channel if (key === constants_js_1.START) { this.nodes[key] = new read_js_1.PregelNode({ tags: [constants_js_1.TAG_HIDDEN], triggers: [constants_js_1.START], channels: [constants_js_1.START], writers: [new write_js_1.ChannelWrite(stateWriteEntries, [constants_js_1.TAG_HIDDEN])], }); } else { const inputDefinition = node?.input ?? this.builder._schemaDefinition; const inputValues = Object.fromEntries(Object.keys(this.builder._schemaDefinitions.get(inputDefinition)).map((k) => [k, k])); const isSingleInput = Object.keys(inputValues).length === 1 && ROOT in inputValues; const branchChannel = `branch:to:${key}`; this.channels[branchChannel] = node?.defer ? new last_value_js_1.LastValueAfterFinish() : new ephemeral_value_js_1.EphemeralValue(false); this.nodes[key] = new read_js_1.PregelNode({ triggers: [branchChannel], // read state keys channels: isSingleInput ? Object.keys(inputValues) : inputValues, // publish to state keys writers: [new write_js_1.ChannelWrite(stateWriteEntries, [constants_js_1.TAG_HIDDEN])], mapper: isSingleInput ? undefined : // eslint-disable-next-line @typescript-eslint/no-explicit-any (input) => { return Object.fromEntries(Object.entries(input).filter(([k]) => k in inputValues)); }, bound: node?.runnable, metadata: node?.metadata, retryPolicy: node?.retryPolicy, cachePolicy: node?.cachePolicy, subgraphs: node?.subgraphs, ends: node?.ends, }); } } attachEdge(starts, end) { if (end === constants_js_1.END) return; if (typeof starts === "string") { this.nodes[starts].writers.push(new write_js_1.ChannelWrite([{ channel: `branch:to:${end}`, value: null }], [constants_js_1.TAG_HIDDEN])); } else if (Array.isArray(starts)) { const channelName = `join:${starts.join("+")}:${end}`; // register channel this.channels[channelName] = this.builder.nodes[end].defer ? new named_barrier_value_js_1.NamedBarrierValueAfterFinish(new Set(starts)) : new named_barrier_value_js_1.NamedBarrierValue(new Set(starts)); // subscribe to channel this.nodes[end].triggers.push(channelName); // publish to channel for (const start of starts) { this.nodes[start].writers.push(new write_js_1.ChannelWrite([{ channel: channelName, value: start }], [constants_js_1.TAG_HIDDEN])); } } } attachBranch(start, _, branch, options = { withReader: true }) { const branchWriter = async (packets, config) => { const filteredPackets = packets.filter((p) => p !== constants_js_1.END); if (!filteredPackets.length) return; const writes = filteredPackets.map((p) => { if ((0, constants_js_1._isSend)(p)) return p; return { channel: p === constants_js_1.END ? p : `branch:to:${p}`, value: start }; }); await write_js_1.ChannelWrite.doWrite({ ...config, tags: (config.tags ?? []).concat([constants_js_1.TAG_HIDDEN]) }, writes); }; // attach branch publisher this.nodes[start].writers.push(branch.run(branchWriter, // reader options.withReader ? (config) => read_js_1.ChannelRead.doRead(config, this.streamChannels ?? this.outputChannels, true) : undefined)); } async _validateInput(input) { if (input == null) return input; const schema = (() => { const input = this.builder._inputRuntimeDefinition; const schema = this.builder._schemaRuntimeDefinition; const apply = (schema) => { if (schema == null) return undefined; return (0, state_js_1.applyZodPlugin)(schema, { reducer: true }); }; if ((0, state_js_1.isAnyZodObject)(input)) return apply(input); if (input === PartialStateSchema) return apply(schema)?.partial(); return undefined; })(); if ((0, constants_js_1.isCommand)(input)) { const parsedInput = input; if (input.update && schema != null) parsedInput.update = schema.parse(input.update); return parsedInput; } if (schema != null) return schema.parse(input); return input; } async _validateConfigurable(config) { const configSchema = this.builder._configRuntimeSchema; if ((0, state_js_1.isAnyZodObject)(configSchema)) configSchema.parse(config); return config; } } exports.CompiledStateGraph = CompiledStateGraph; function isStateDefinition(obj) { return (typeof obj === "object" && obj !== null && !Array.isArray(obj) && Object.keys(obj).length > 0 && Object.values(obj).every((v) => typeof v === "function" || (0, base_js_1.isBaseChannel)(v))); } function isAnnotationRoot(obj) { return (typeof obj === "object" && obj !== null && "lc_graph_name" in obj && obj.lc_graph_name === "AnnotationRoot"); } function isStateGraphArgs(obj) { return (typeof obj === "object" && obj !== null && obj.channels !== undefined); } function isStateGraphArgsWithStateSchema(obj) { return (typeof obj === "object" && obj !== null && obj.stateSchema !== undefined); } function isStateGraphArgsWithInputOutputSchemas(obj) { return (typeof obj === "object" && obj !== null && // eslint-disable-next-line @typescript-eslint/no-explicit-any obj.stateSchema === undefined && obj.input !== undefined && obj.output !== undefined); } function isZodStateGraphArgsWithStateSchema(value) { if (typeof value !== "object" || value == null) { return false; } if (!("state" in value) || !(0, state_js_1.isAnyZodObject)(value.state)) { return false; } if ("input" in value && !(0, state_js_1.isAnyZodObject)(value.input)) { return false; } if ("output" in value && !(0, state_js_1.isAnyZodObject)(value.output)) { return false; } return true; } // eslint-disable-next-line @typescript-eslint/no-explicit-any function _controlBranch(value) { if ((0, constants_js_1._isSend)(value)) { return [value]; } const commands = []; if ((0, constants_js_1.isCommand)(value)) { commands.push(value); } else if (Array.isArray(value)) { commands.push(...value.filter(constants_js_1.isCommand)); } const destinations = []; for (const command of commands) { if (command.graph === constants_js_1.Command.PARENT) { throw new errors_js_1.ParentCommand(command); } if ((0, constants_js_1._isSend)(command.goto)) { destinations.push(command.goto); } else if (typeof command.goto === "string") { destinations.push(command.goto); } else { if (Array.isArray(command.goto)) { destinations.push(...command.goto); } } } return destinations; } function _getControlBranch() { // eslint-disable-next-line @typescript-eslint/no-explicit-any const CONTROL_BRANCH_PATH = new utils_js_1.RunnableCallable({ func: _controlBranch, tags: [constants_js_1.TAG_HIDDEN], trace: false, recurse: false, name: "<control_branch>", }); return new graph_js_1.Branch({ path: CONTROL_BRANCH_PATH, }); } function typedNode(_state, _options) { return (func, options) => { Object.assign(func, { [Symbol.for("langgraph.state.node")]: options }); return func; }; } //# sourceMappingURL=state.js.map