@langchain/langgraph
Version:
LangGraph
454 lines (453 loc) • 16.3 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Command = exports.Send = exports.CHECKPOINT_NAMESPACE_END = exports.CHECKPOINT_NAMESPACE_SEPARATOR = exports.RESERVED = exports.NULL_TASK_ID = exports.TASK_NAMESPACE = exports.PULL = exports.PUSH = exports.TASKS = exports.SELF = exports.TAG_NOSTREAM = exports.TAG_HIDDEN = exports.RECURSION_LIMIT_DEFAULT = exports.RUNTIME_PLACEHOLDER = exports.PREVIOUS = exports.RETURN = exports.NO_WRITES = exports.RESUME = exports.INTERRUPT = exports.CONFIG_KEY_ABORT_SIGNALS = exports.CONFIG_KEY_CHECKPOINT_MAP = exports.CONFIG_KEY_NODE_FINISHED = exports.CONFIG_KEY_CHECKPOINT_NS = exports.CONFIG_KEY_CHECKPOINT_ID = exports.CONFIG_KEY_PREVIOUS_STATE = exports.CONFIG_KEY_SCRATCHPAD = exports.CONFIG_KEY_RESUME_VALUE = exports.CONFIG_KEY_STREAM = exports.CONFIG_KEY_TASK_ID = exports.CONFIG_KEY_RESUMING = exports.CONFIG_KEY_CHECKPOINTER = exports.CONFIG_KEY_READ = exports.CONFIG_KEY_CALL = exports.CONFIG_KEY_SEND = exports.CACHE_NS_WRITES = exports.ERROR = exports.COPY = exports.INPUT = exports.END = exports.START = void 0;
exports._isSendInterface = _isSendInterface;
exports._isSend = _isSend;
exports.isInterrupted = isInterrupted;
exports.isCommand = isCommand;
exports._deserializeCommandSendObjectGraph = _deserializeCommandSendObjectGraph;
/** Special reserved node name denoting the start of a graph. */
exports.START = "__start__";
/** Special reserved node name denoting the end of a graph. */
exports.END = "__end__";
exports.INPUT = "__input__";
exports.COPY = "__copy__";
exports.ERROR = "__error__";
/** Special reserved cache namespaces */
exports.CACHE_NS_WRITES = "__pregel_ns_writes";
exports.CONFIG_KEY_SEND = "__pregel_send";
/** config key containing function used to call a node (push task) */
exports.CONFIG_KEY_CALL = "__pregel_call";
exports.CONFIG_KEY_READ = "__pregel_read";
exports.CONFIG_KEY_CHECKPOINTER = "__pregel_checkpointer";
exports.CONFIG_KEY_RESUMING = "__pregel_resuming";
exports.CONFIG_KEY_TASK_ID = "__pregel_task_id";
exports.CONFIG_KEY_STREAM = "__pregel_stream";
exports.CONFIG_KEY_RESUME_VALUE = "__pregel_resume_value";
exports.CONFIG_KEY_SCRATCHPAD = "__pregel_scratchpad";
/** config key containing state from previous invocation of graph for the given thread */
exports.CONFIG_KEY_PREVIOUS_STATE = "__pregel_previous";
exports.CONFIG_KEY_CHECKPOINT_ID = "checkpoint_id";
exports.CONFIG_KEY_CHECKPOINT_NS = "checkpoint_ns";
exports.CONFIG_KEY_NODE_FINISHED = "__pregel_node_finished";
// this one is part of public API
exports.CONFIG_KEY_CHECKPOINT_MAP = "checkpoint_map";
exports.CONFIG_KEY_ABORT_SIGNALS = "__pregel_abort_signals";
/** Special channel reserved for graph interrupts */
exports.INTERRUPT = "__interrupt__";
/** Special channel reserved for graph resume */
exports.RESUME = "__resume__";
/** Special channel reserved for cases when a task exits without any writes */
exports.NO_WRITES = "__no_writes__";
/** Special channel reserved for graph return */
exports.RETURN = "__return__";
/** Special channel reserved for graph previous state */
exports.PREVIOUS = "__previous__";
exports.RUNTIME_PLACEHOLDER = "__pregel_runtime_placeholder__";
exports.RECURSION_LIMIT_DEFAULT = 25;
exports.TAG_HIDDEN = "langsmith:hidden";
exports.TAG_NOSTREAM = "langsmith:nostream";
exports.SELF = "__self__";
exports.TASKS = "__pregel_tasks";
exports.PUSH = "__pregel_push";
exports.PULL = "__pregel_pull";
exports.TASK_NAMESPACE = "6ba7b831-9dad-11d1-80b4-00c04fd430c8";
exports.NULL_TASK_ID = "00000000-0000-0000-0000-000000000000";
exports.RESERVED = [
exports.TAG_HIDDEN,
exports.INPUT,
exports.INTERRUPT,
exports.RESUME,
exports.ERROR,
exports.NO_WRITES,
exports.TASKS,
// reserved config.configurable keys
exports.CONFIG_KEY_SEND,
exports.CONFIG_KEY_READ,
exports.CONFIG_KEY_CHECKPOINTER,
exports.CONFIG_KEY_STREAM,
exports.CONFIG_KEY_RESUMING,
exports.CONFIG_KEY_TASK_ID,
exports.CONFIG_KEY_CALL,
exports.CONFIG_KEY_RESUME_VALUE,
exports.CONFIG_KEY_SCRATCHPAD,
exports.CONFIG_KEY_PREVIOUS_STATE,
exports.CONFIG_KEY_CHECKPOINT_MAP,
exports.CONFIG_KEY_CHECKPOINT_NS,
exports.CONFIG_KEY_CHECKPOINT_ID,
];
exports.CHECKPOINT_NAMESPACE_SEPARATOR = "|";
exports.CHECKPOINT_NAMESPACE_END = ":";
function _isSendInterface(x) {
const operation = x;
return (operation !== null &&
operation !== undefined &&
typeof operation.node === "string" &&
operation.args !== undefined);
}
/**
*
* A message or packet to send to a specific node in the graph.
*
* The `Send` class is used within a `StateGraph`'s conditional edges to
* dynamically invoke a node with a custom state at the next step.
*
* Importantly, the sent state can differ from the core graph's state,
* allowing for flexible and dynamic workflow management.
*
* One such example is a "map-reduce" workflow where your graph invokes
* the same node multiple times in parallel with different states,
* before aggregating the results back into the main graph's state.
*
* @example
* ```typescript
* import { Annotation, Send, StateGraph } from "@langchain/langgraph";
*
* const ChainState = Annotation.Root({
* subjects: Annotation<string[]>,
* jokes: Annotation<string[]>({
* reducer: (a, b) => a.concat(b),
* }),
* });
*
* const continueToJokes = async (state: typeof ChainState.State) => {
* return state.subjects.map((subject) => {
* return new Send("generate_joke", { subjects: [subject] });
* });
* };
*
* const graph = new StateGraph(ChainState)
* .addNode("generate_joke", (state) => ({
* jokes: [`Joke about ${state.subjects}`],
* }))
* .addConditionalEdges("__start__", continueToJokes)
* .addEdge("generate_joke", "__end__")
* .compile();
*
* const res = await graph.invoke({ subjects: ["cats", "dogs"] });
* console.log(res);
*
* // Invoking with two subjects results in a generated joke for each
* // { subjects: ["cats", "dogs"], jokes: [`Joke about cats`, `Joke about dogs`] }
* ```
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
class Send {
constructor(node, args) {
Object.defineProperty(this, "lg_name", {
enumerable: true,
configurable: true,
writable: true,
value: "Send"
});
Object.defineProperty(this, "node", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "args", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
this.node = node;
this.args = _deserializeCommandSendObjectGraph(args);
}
toJSON() {
return { lg_name: this.lg_name, node: this.node, args: this.args };
}
}
exports.Send = Send;
function _isSend(x) {
// eslint-disable-next-line no-instanceof/no-instanceof
return x instanceof Send;
}
/**
* Checks if the given graph invoke / stream chunk contains interrupt.
*
* @example
* ```ts
* import { INTERRUPT, isInterrupted } from "@langchain/langgraph";
*
* const values = await graph.invoke({ foo: "bar" });
* if (isInterrupted<string>(values)) {
* const interrupt = values[INTERRUPT][0].value;
* }
* ```
*
* @param values - The values to check.
* @returns `true` if the values contain an interrupt, `false` otherwise.
*/
function isInterrupted(values) {
if (!values || typeof values !== "object")
return false;
if (!(exports.INTERRUPT in values))
return false;
return Array.isArray(values[exports.INTERRUPT]);
}
/**
* One or more commands to update the graph's state and send messages to nodes.
* Can be used to combine routing logic with state updates in lieu of conditional edges
*
* @example
* ```ts
* import { Annotation, Command } from "@langchain/langgraph";
*
* // Define graph state
* const StateAnnotation = Annotation.Root({
* foo: Annotation<string>,
* });
*
* // Define the nodes
* const nodeA = async (_state: typeof StateAnnotation.State) => {
* console.log("Called A");
* // this is a replacement for a real conditional edge function
* const goto = Math.random() > .5 ? "nodeB" : "nodeC";
* // note how Command allows you to BOTH update the graph state AND route to the next node
* return new Command({
* // this is the state update
* update: {
* foo: "a",
* },
* // this is a replacement for an edge
* goto,
* });
* };
*
* // Nodes B and C are unchanged
* const nodeB = async (state: typeof StateAnnotation.State) => {
* console.log("Called B");
* return {
* foo: state.foo + "|b",
* };
* }
*
* const nodeC = async (state: typeof StateAnnotation.State) => {
* console.log("Called C");
* return {
* foo: state.foo + "|c",
* };
* }
*
* import { StateGraph } from "@langchain/langgraph";
* // NOTE: there are no edges between nodes A, B and C!
* const graph = new StateGraph(StateAnnotation)
* .addNode("nodeA", nodeA, {
* ends: ["nodeB", "nodeC"],
* })
* .addNode("nodeB", nodeB)
* .addNode("nodeC", nodeC)
* .addEdge("__start__", "nodeA")
* .compile();
*
* await graph.invoke({ foo: "" });
*
* // Randomly oscillates between
* // { foo: 'a|c' } and { foo: 'a|b' }
* ```
*/
class Command {
constructor(args) {
Object.defineProperty(this, "lg_name", {
enumerable: true,
configurable: true,
writable: true,
value: "Command"
});
Object.defineProperty(this, "lc_direct_tool_output", {
enumerable: true,
configurable: true,
writable: true,
value: true
});
/**
* Graph to send the command to. Supported values are:
* - None: the current graph (default)
* - The specific name of the graph to send the command to
* - {@link Command.PARENT}: closest parent graph (only supported when returned from a node in a subgraph)
*/
Object.defineProperty(this, "graph", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
/**
* Update to apply to the graph's state as a result of executing the node that is returning the command.
* Written to the state as if the node had simply returned this value instead of the Command object.
*/
Object.defineProperty(this, "update", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
/**
* Value to resume execution with. To be used together with {@link interrupt}.
*/
Object.defineProperty(this, "resume", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
/**
* Can be one of the following:
* - name of the node to navigate to next (any node that belongs to the specified `graph`)
* - sequence of node names to navigate to next
* - {@link Send} object (to execute a node with the exact input provided in the {@link Send} object)
* - sequence of {@link Send} objects
*/
Object.defineProperty(this, "goto", {
enumerable: true,
configurable: true,
writable: true,
value: []
});
this.resume = args.resume;
this.graph = args.graph;
this.update = args.update;
if (args.goto) {
this.goto = Array.isArray(args.goto)
? _deserializeCommandSendObjectGraph(args.goto)
: [_deserializeCommandSendObjectGraph(args.goto)];
}
}
/**
* Convert the update field to a list of {@link PendingWrite} tuples
* @returns List of {@link PendingWrite} tuples of the form `[channelKey, value]`.
* @internal
*/
_updateAsTuples() {
if (this.update &&
typeof this.update === "object" &&
!Array.isArray(this.update)) {
return Object.entries(this.update);
}
else if (Array.isArray(this.update) &&
this.update.every((t) => Array.isArray(t) && t.length === 2 && typeof t[0] === "string")) {
return this.update;
}
else {
return [["__root__", this.update]];
}
}
toJSON() {
let serializedGoto;
if (typeof this.goto === "string") {
serializedGoto = this.goto;
}
else if (_isSend(this.goto)) {
serializedGoto = this.goto.toJSON();
}
else {
serializedGoto = this.goto?.map((innerGoto) => {
if (typeof innerGoto === "string") {
return innerGoto;
}
else {
return innerGoto.toJSON();
}
});
}
return {
lg_name: this.lg_name,
update: this.update,
resume: this.resume,
goto: serializedGoto,
};
}
}
exports.Command = Command;
Object.defineProperty(Command, "PARENT", {
enumerable: true,
configurable: true,
writable: true,
value: "__parent__"
});
/**
* A type guard to check if the given value is a {@link Command}.
*
* Useful for type narrowing when working with the {@link Command} object.
*
* @param x - The value to check.
* @returns `true` if the value is a {@link Command}, `false` otherwise.
*/
function isCommand(x) {
if (typeof x !== "object") {
return false;
}
if (x === null || x === undefined) {
return false;
}
if ("lg_name" in x && x.lg_name === "Command") {
return true;
}
return false;
}
/**
* Reconstructs Command and Send objects from a deeply nested tree of anonymous objects
* matching their interfaces.
*
* This is only exported for testing purposes. It is NOT intended to be used outside of
* the Command and Send classes.
*
* @internal
*
* @param x - The command send tree to convert.
* @param seen - A map of seen objects to avoid infinite loops.
* @returns The converted command send tree.
*/
function _deserializeCommandSendObjectGraph(x, seen = new Map()) {
if (x !== undefined && x !== null && typeof x === "object") {
// If we've already processed this object, return the transformed version
if (seen.has(x)) {
return seen.get(x);
}
let result;
if (Array.isArray(x)) {
// Create the array first, then populate it
result = [];
// Add to seen map before processing elements to handle self-references
seen.set(x, result);
// Now populate the array
x.forEach((item, index) => {
result[index] = _deserializeCommandSendObjectGraph(item, seen);
});
// eslint-disable-next-line no-instanceof/no-instanceof
}
else if (isCommand(x) && !(x instanceof Command)) {
result = new Command(x);
seen.set(x, result);
// eslint-disable-next-line no-instanceof/no-instanceof
}
else if (_isSendInterface(x) && !(x instanceof Send)) {
result = new Send(x.node, x.args);
seen.set(x, result);
}
else if (isCommand(x) || _isSend(x)) {
result = x;
seen.set(x, result);
}
else if ("lc_serializable" in x && x.lc_serializable) {
result = x;
seen.set(x, result);
}
else {
// Create empty object first
result = {};
// Add to seen map before processing properties to handle self-references
seen.set(x, result);
// Now populate the object
for (const [key, value] of Object.entries(x)) {
result[key] =
_deserializeCommandSendObjectGraph(value, seen);
}
}
return result;
}
return x;
}
//# sourceMappingURL=constants.js.map