@langchain/langgraph 1.3.7 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/channels/base.cjs +78 -2
- package/dist/channels/base.cjs.map +1 -1
- package/dist/channels/base.d.cts +35 -2
- package/dist/channels/base.d.cts.map +1 -1
- package/dist/channels/base.d.ts +35 -2
- package/dist/channels/base.d.ts.map +1 -1
- package/dist/channels/base.js +77 -4
- package/dist/channels/base.js.map +1 -1
- package/dist/channels/delta.cjs +136 -0
- package/dist/channels/delta.cjs.map +1 -0
- package/dist/channels/delta.d.cts +99 -0
- package/dist/channels/delta.d.cts.map +1 -0
- package/dist/channels/delta.d.ts +99 -0
- package/dist/channels/delta.d.ts.map +1 -0
- package/dist/channels/delta.js +136 -0
- package/dist/channels/delta.js.map +1 -0
- package/dist/channels/index.cjs +5 -0
- package/dist/channels/index.d.cts +3 -2
- package/dist/channels/index.d.ts +3 -2
- package/dist/channels/index.js +3 -2
- package/dist/constants.cjs +63 -4
- package/dist/constants.cjs.map +1 -1
- package/dist/constants.d.cts +33 -2
- package/dist/constants.d.cts.map +1 -1
- package/dist/constants.d.ts +33 -2
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +59 -5
- package/dist/constants.js.map +1 -1
- package/dist/errors.cjs +128 -0
- package/dist/errors.cjs.map +1 -1
- package/dist/errors.d.cts +86 -1
- package/dist/errors.d.cts.map +1 -1
- package/dist/errors.d.ts +86 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +123 -1
- package/dist/errors.js.map +1 -1
- package/dist/func/index.cjs +9 -2
- package/dist/func/index.cjs.map +1 -1
- package/dist/func/index.d.cts +14 -0
- package/dist/func/index.d.cts.map +1 -1
- package/dist/func/index.d.ts +14 -0
- package/dist/func/index.d.ts.map +1 -1
- package/dist/func/index.js +9 -2
- package/dist/func/index.js.map +1 -1
- package/dist/graph/graph.cjs +44 -7
- package/dist/graph/graph.cjs.map +1 -1
- package/dist/graph/graph.d.cts +24 -2
- package/dist/graph/graph.d.cts.map +1 -1
- package/dist/graph/graph.d.ts +24 -2
- package/dist/graph/graph.d.ts.map +1 -1
- package/dist/graph/graph.js +44 -7
- package/dist/graph/graph.js.map +1 -1
- package/dist/graph/index.d.ts +3 -3
- package/dist/graph/messages_reducer.cjs +55 -0
- package/dist/graph/messages_reducer.cjs.map +1 -1
- package/dist/graph/messages_reducer.d.cts +28 -1
- package/dist/graph/messages_reducer.d.cts.map +1 -1
- package/dist/graph/messages_reducer.d.ts +28 -1
- package/dist/graph/messages_reducer.d.ts.map +1 -1
- package/dist/graph/messages_reducer.js +56 -2
- package/dist/graph/messages_reducer.js.map +1 -1
- package/dist/graph/state.cjs +208 -12
- package/dist/graph/state.cjs.map +1 -1
- package/dist/graph/state.d.cts +193 -17
- package/dist/graph/state.d.cts.map +1 -1
- package/dist/graph/state.d.ts +193 -17
- package/dist/graph/state.d.ts.map +1 -1
- package/dist/graph/state.js +210 -14
- package/dist/graph/state.js.map +1 -1
- package/dist/graph/zod/schema.cjs +5 -0
- package/dist/graph/zod/schema.cjs.map +1 -1
- package/dist/graph/zod/schema.d.cts.map +1 -1
- package/dist/graph/zod/schema.d.ts.map +1 -1
- package/dist/graph/zod/schema.js +5 -0
- package/dist/graph/zod/schema.js.map +1 -1
- package/dist/index.cjs +11 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -8
- package/dist/index.d.ts +11 -8
- package/dist/index.js +5 -3
- package/dist/index.js.map +1 -1
- package/dist/prebuilt/react_agent_executor.d.cts +1 -1
- package/dist/prebuilt/tool_node.cjs +69 -6
- package/dist/prebuilt/tool_node.cjs.map +1 -1
- package/dist/prebuilt/tool_node.d.cts +55 -3
- package/dist/prebuilt/tool_node.d.cts.map +1 -1
- package/dist/prebuilt/tool_node.d.ts +55 -3
- package/dist/prebuilt/tool_node.d.ts.map +1 -1
- package/dist/prebuilt/tool_node.js +69 -6
- package/dist/prebuilt/tool_node.js.map +1 -1
- package/dist/pregel/algo.cjs +182 -21
- package/dist/pregel/algo.cjs.map +1 -1
- package/dist/pregel/algo.d.cts +1 -1
- package/dist/pregel/algo.d.cts.map +1 -1
- package/dist/pregel/algo.d.ts +1 -1
- package/dist/pregel/algo.d.ts.map +1 -1
- package/dist/pregel/algo.js +185 -25
- package/dist/pregel/algo.js.map +1 -1
- package/dist/pregel/call.cjs +2 -1
- package/dist/pregel/call.cjs.map +1 -1
- package/dist/pregel/call.js +2 -1
- package/dist/pregel/call.js.map +1 -1
- package/dist/pregel/index.cjs +15 -3
- package/dist/pregel/index.cjs.map +1 -1
- package/dist/pregel/index.d.cts.map +1 -1
- package/dist/pregel/index.d.ts.map +1 -1
- package/dist/pregel/index.js +17 -5
- package/dist/pregel/index.js.map +1 -1
- package/dist/pregel/loop.cjs +362 -41
- package/dist/pregel/loop.cjs.map +1 -1
- package/dist/pregel/loop.js +365 -44
- package/dist/pregel/loop.js.map +1 -1
- package/dist/pregel/messages-v2.cjs +1 -1
- package/dist/pregel/messages-v2.js +1 -1
- package/dist/pregel/messages.cjs +1 -1
- package/dist/pregel/messages.js +1 -1
- package/dist/pregel/read.cjs +15 -5
- package/dist/pregel/read.cjs.map +1 -1
- package/dist/pregel/read.d.cts +9 -0
- package/dist/pregel/read.d.cts.map +1 -1
- package/dist/pregel/read.d.ts +9 -0
- package/dist/pregel/read.d.ts.map +1 -1
- package/dist/pregel/read.js +15 -5
- package/dist/pregel/read.js.map +1 -1
- package/dist/pregel/remote-run-stream.cjs +107 -0
- package/dist/pregel/remote-run-stream.cjs.map +1 -0
- package/dist/pregel/remote-run-stream.d.cts +33 -0
- package/dist/pregel/remote-run-stream.d.cts.map +1 -0
- package/dist/pregel/remote-run-stream.d.ts +33 -0
- package/dist/pregel/remote-run-stream.d.ts.map +1 -0
- package/dist/pregel/remote-run-stream.js +107 -0
- package/dist/pregel/remote-run-stream.js.map +1 -0
- package/dist/pregel/remote.cjs +61 -1
- package/dist/pregel/remote.cjs.map +1 -1
- package/dist/pregel/remote.d.cts +17 -0
- package/dist/pregel/remote.d.cts.map +1 -1
- package/dist/pregel/remote.d.ts +17 -0
- package/dist/pregel/remote.d.ts.map +1 -1
- package/dist/pregel/remote.js +61 -1
- package/dist/pregel/remote.js.map +1 -1
- package/dist/pregel/replay.cjs +62 -0
- package/dist/pregel/replay.cjs.map +1 -0
- package/dist/pregel/replay.js +62 -0
- package/dist/pregel/replay.js.map +1 -0
- package/dist/pregel/retry.cjs +8 -6
- package/dist/pregel/retry.cjs.map +1 -1
- package/dist/pregel/retry.js +8 -6
- package/dist/pregel/retry.js.map +1 -1
- package/dist/pregel/runnable_types.d.cts +20 -0
- package/dist/pregel/runnable_types.d.cts.map +1 -1
- package/dist/pregel/runnable_types.d.ts +20 -0
- package/dist/pregel/runnable_types.d.ts.map +1 -1
- package/dist/pregel/runner.cjs +48 -7
- package/dist/pregel/runner.cjs.map +1 -1
- package/dist/pregel/runner.js +50 -9
- package/dist/pregel/runner.js.map +1 -1
- package/dist/pregel/runtime.cjs +64 -0
- package/dist/pregel/runtime.cjs.map +1 -0
- package/dist/pregel/runtime.d.cts +57 -0
- package/dist/pregel/runtime.d.cts.map +1 -0
- package/dist/pregel/runtime.d.ts +57 -0
- package/dist/pregel/runtime.d.ts.map +1 -0
- package/dist/pregel/runtime.js +64 -0
- package/dist/pregel/runtime.js.map +1 -0
- package/dist/pregel/stream.cjs +2 -0
- package/dist/pregel/stream.cjs.map +1 -1
- package/dist/pregel/stream.js +2 -0
- package/dist/pregel/stream.js.map +1 -1
- package/dist/pregel/timeout.cjs +216 -0
- package/dist/pregel/timeout.cjs.map +1 -0
- package/dist/pregel/timeout.js +216 -0
- package/dist/pregel/timeout.js.map +1 -0
- package/dist/pregel/types.cjs +3 -1
- package/dist/pregel/types.cjs.map +1 -1
- package/dist/pregel/types.d.cts +13 -0
- package/dist/pregel/types.d.cts.map +1 -1
- package/dist/pregel/types.d.ts +14 -1
- package/dist/pregel/types.d.ts.map +1 -1
- package/dist/pregel/types.js +3 -1
- package/dist/pregel/types.js.map +1 -1
- package/dist/pregel/utils/config.cjs +18 -2
- package/dist/pregel/utils/config.cjs.map +1 -1
- package/dist/pregel/utils/config.d.cts +15 -1
- package/dist/pregel/utils/config.d.cts.map +1 -1
- package/dist/pregel/utils/config.d.ts +15 -1
- package/dist/pregel/utils/config.d.ts.map +1 -1
- package/dist/pregel/utils/config.js +18 -2
- package/dist/pregel/utils/config.js.map +1 -1
- package/dist/pregel/utils/index.cjs +1 -0
- package/dist/pregel/utils/index.cjs.map +1 -1
- package/dist/pregel/utils/index.d.cts +6 -1
- package/dist/pregel/utils/index.d.cts.map +1 -1
- package/dist/pregel/utils/index.d.ts +6 -1
- package/dist/pregel/utils/index.d.ts.map +1 -1
- package/dist/pregel/utils/index.js +1 -0
- package/dist/pregel/utils/index.js.map +1 -1
- package/dist/pregel/utils/timeout.cjs +34 -0
- package/dist/pregel/utils/timeout.cjs.map +1 -0
- package/dist/pregel/utils/timeout.d.cts +45 -0
- package/dist/pregel/utils/timeout.d.cts.map +1 -0
- package/dist/pregel/utils/timeout.d.ts +45 -0
- package/dist/pregel/utils/timeout.d.ts.map +1 -0
- package/dist/pregel/utils/timeout.js +34 -0
- package/dist/pregel/utils/timeout.js.map +1 -0
- package/dist/state/schema.cjs +12 -2
- package/dist/state/schema.cjs.map +1 -1
- package/dist/state/schema.d.cts.map +1 -1
- package/dist/state/schema.d.ts.map +1 -1
- package/dist/state/schema.js +12 -2
- package/dist/state/schema.js.map +1 -1
- package/dist/web.cjs +11 -0
- package/dist/web.d.cts +11 -8
- package/dist/web.d.ts +11 -8
- package/dist/web.js +5 -3
- package/package.json +5 -5
|
@@ -122,6 +122,57 @@ const isSendInput = (input) => typeof input === "object" && input != null && "lg
|
|
|
122
122
|
* }
|
|
123
123
|
* // Returns the messages in the state at each step of execution
|
|
124
124
|
* ```
|
|
125
|
+
*
|
|
126
|
+
* ### Accessing graph state and runtime context from tools
|
|
127
|
+
*
|
|
128
|
+
* Tools executed by a `ToolNode` only receive the arguments produced by the
|
|
129
|
+
* model. To give a tool access to the surrounding graph state or other runtime
|
|
130
|
+
* context, read them from the {@link ToolRuntime} that is passed as the
|
|
131
|
+
* second argument to every tool:
|
|
132
|
+
*
|
|
133
|
+
* - `runtime.state` — the input the `ToolNode` was invoked with. When the
|
|
134
|
+
* `ToolNode` runs as a graph node (e.g. inside `createReactAgent`), this is
|
|
135
|
+
* the current graph state. This works in any runtime, including web browsers,
|
|
136
|
+
* because it does not rely on `node:async_hooks`/`AsyncLocalStorage`.
|
|
137
|
+
* - `runtime.config`, `runtime.context`, `runtime.store`, etc. — other
|
|
138
|
+
* run-scoped values.
|
|
139
|
+
*
|
|
140
|
+
* @example
|
|
141
|
+
* ```ts
|
|
142
|
+
* import { ToolNode } from "@langchain/langgraph/prebuilt";
|
|
143
|
+
* import { StateGraph, MessagesZodState } from "@langchain/langgraph";
|
|
144
|
+
* import { tool, type ToolRuntime } from "@langchain/core/tools";
|
|
145
|
+
* import { z } from "zod";
|
|
146
|
+
*
|
|
147
|
+
* // Define the graph state with a Zod schema. The extra `userId` key becomes
|
|
148
|
+
* // part of the state that the ToolNode forwards to its tools via `runtime.state`.
|
|
149
|
+
* const AgentState = z.object({
|
|
150
|
+
* ...MessagesZodState.shape,
|
|
151
|
+
* userId: z.string(),
|
|
152
|
+
* });
|
|
153
|
+
*
|
|
154
|
+
* const getUserInfo = tool(
|
|
155
|
+
* async (_input, runtime: ToolRuntime<typeof AgentState>) => {
|
|
156
|
+
* // Read the current graph state directly from the second argument.
|
|
157
|
+
* const userId = runtime.state.userId;
|
|
158
|
+
* return userId === "user_123" ? "User is John Smith" : "Unknown user";
|
|
159
|
+
* },
|
|
160
|
+
* {
|
|
161
|
+
* name: "get_user_info",
|
|
162
|
+
* description: "Look up user info.",
|
|
163
|
+
* schema: z.object({}),
|
|
164
|
+
* }
|
|
165
|
+
* );
|
|
166
|
+
*
|
|
167
|
+
* // Wire the ToolNode into a StateGraph that uses `AgentState`. Because the
|
|
168
|
+
* // node runs with the graph state as its input, the tool can read `userId`.
|
|
169
|
+
* const graph = new StateGraph(AgentState)
|
|
170
|
+
* .addNode("tools", new ToolNode([getUserInfo]))
|
|
171
|
+
* .addEdge("__start__", "tools")
|
|
172
|
+
* .compile();
|
|
173
|
+
*
|
|
174
|
+
* await graph.invoke({ messages: [...], userId: "user_123" });
|
|
175
|
+
* ```
|
|
125
176
|
*/
|
|
126
177
|
var ToolNode = class extends RunnableCallable {
|
|
127
178
|
tools;
|
|
@@ -137,14 +188,24 @@ var ToolNode = class extends RunnableCallable {
|
|
|
137
188
|
this.tools = tools;
|
|
138
189
|
this.handleToolErrors = handleToolErrors ?? this.handleToolErrors;
|
|
139
190
|
}
|
|
140
|
-
async runTool(call, config) {
|
|
191
|
+
async runTool(call, config, state) {
|
|
141
192
|
const tool = this.tools.find((tool) => tool.name === call.name);
|
|
142
193
|
try {
|
|
143
194
|
if (tool === void 0) throw new Error(`Tool "${call.name}" not found.`);
|
|
144
|
-
const
|
|
195
|
+
const toolCall = {
|
|
145
196
|
...call,
|
|
146
197
|
type: "tool_call"
|
|
147
|
-
}
|
|
198
|
+
};
|
|
199
|
+
const runtime = {
|
|
200
|
+
...config,
|
|
201
|
+
state,
|
|
202
|
+
toolCallId: call.id ?? "",
|
|
203
|
+
config,
|
|
204
|
+
context: config.context,
|
|
205
|
+
store: config.store ?? null,
|
|
206
|
+
writer: config.writer ?? config.configurable?.writer ?? null
|
|
207
|
+
};
|
|
208
|
+
const output = await tool.invoke(toolCall, runtime);
|
|
148
209
|
if (isBaseMessage(output) && output.getType() === "tool" || isCommand(output)) return output;
|
|
149
210
|
return new ToolMessage({
|
|
150
211
|
status: "success",
|
|
@@ -165,8 +226,10 @@ var ToolNode = class extends RunnableCallable {
|
|
|
165
226
|
}
|
|
166
227
|
async run(input, config) {
|
|
167
228
|
let outputs;
|
|
168
|
-
if (isSendInput(input))
|
|
169
|
-
|
|
229
|
+
if (isSendInput(input)) {
|
|
230
|
+
const { lg_tool_call: toolCall, ...state } = input;
|
|
231
|
+
outputs = [await this.runTool(toolCall, config, state)];
|
|
232
|
+
} else {
|
|
170
233
|
let messages;
|
|
171
234
|
if (isBaseMessageArray(input)) messages = input;
|
|
172
235
|
else if (isMessagesState(input)) messages = input.messages;
|
|
@@ -181,7 +244,7 @@ var ToolNode = class extends RunnableCallable {
|
|
|
181
244
|
}
|
|
182
245
|
}
|
|
183
246
|
if (aiMessage == null || !isAIMessage(aiMessage)) throw new Error("ToolNode only accepts AIMessages as input.");
|
|
184
|
-
outputs = await Promise.all(aiMessage.tool_calls?.filter((call) => call.id == null || !toolMessageIds.has(call.id)).map((call) => this.runTool(call, config)) ?? []);
|
|
247
|
+
outputs = await Promise.all(aiMessage.tool_calls?.filter((call) => call.id == null || !toolMessageIds.has(call.id)).map((call) => this.runTool(call, config, input)) ?? []);
|
|
185
248
|
}
|
|
186
249
|
if (!outputs.some(isCommand)) return Array.isArray(input) ? outputs : { messages: outputs };
|
|
187
250
|
const combinedOutputs = [];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool_node.js","names":[],"sources":["../../src/prebuilt/tool_node.ts"],"sourcesContent":["import {\n BaseMessage,\n ToolMessage,\n AIMessage,\n isBaseMessage,\n isAIMessage,\n} from \"@langchain/core/messages\";\nimport { RunnableConfig, RunnableToolLike } from \"@langchain/core/runnables\";\nimport { DynamicTool, StructuredToolInterface } from \"@langchain/core/tools\";\nimport type { ToolCall } from \"@langchain/core/messages/tool\";\nimport { RunnableCallable } from \"../utils.js\";\nimport { MessagesAnnotation } from \"../graph/messages_annotation.js\";\nimport { isGraphInterrupt } from \"../errors.js\";\nimport { END, isCommand, Command, _isSend, Send } from \"../constants.js\";\n\nexport type ToolNodeOptions = {\n name?: string;\n tags?: string[];\n handleToolErrors?: boolean;\n};\n\nconst isBaseMessageArray = (input: unknown): input is BaseMessage[] =>\n Array.isArray(input) && input.every(isBaseMessage);\n\nconst isMessagesState = (\n input: unknown\n): input is { messages: BaseMessage[] } =>\n typeof input === \"object\" &&\n input != null &&\n \"messages\" in input &&\n isBaseMessageArray(input.messages);\n\nconst isSendInput = (input: unknown): input is { lg_tool_call: ToolCall } =>\n typeof input === \"object\" && input != null && \"lg_tool_call\" in input;\n\n/**\n * A node that runs the tools requested in the last AIMessage. It can be used\n * either in StateGraph with a \"messages\" key or in MessageGraph. If multiple\n * tool calls are requested, they will be run in parallel. The output will be\n * a list of ToolMessages, one for each tool call.\n *\n * @example\n * ```ts\n * import { ToolNode } from \"@langchain/langgraph/prebuilt\";\n * import { tool } from \"@langchain/core/tools\";\n * import { z } from \"zod\";\n * import { AIMessage } from \"@langchain/core/messages\";\n *\n * const getWeather = tool((input) => {\n * if ([\"sf\", \"san francisco\"].includes(input.location.toLowerCase())) {\n * return \"It's 60 degrees and foggy.\";\n * } else {\n * return \"It's 90 degrees and sunny.\";\n * }\n * }, {\n * name: \"get_weather\",\n * description: \"Call to get the current weather.\",\n * schema: z.object({\n * location: z.string().describe(\"Location to get the weather for.\"),\n * }),\n * });\n *\n * const tools = [getWeather];\n * const toolNode = new ToolNode(tools);\n *\n * const messageWithSingleToolCall = new AIMessage({\n * content: \"\",\n * tool_calls: [\n * {\n * name: \"get_weather\",\n * args: { location: \"sf\" },\n * id: \"tool_call_id\",\n * type: \"tool_call\",\n * }\n * ]\n * })\n *\n * await toolNode.invoke({ messages: [messageWithSingleToolCall] });\n * // Returns tool invocation responses as:\n * // { messages: ToolMessage[] }\n * ```\n *\n * @example\n * ```ts\n * import {\n * StateGraph,\n * MessagesAnnotation,\n * } from \"@langchain/langgraph\";\n * import { ToolNode } from \"@langchain/langgraph/prebuilt\";\n * import { tool } from \"@langchain/core/tools\";\n * import { z } from \"zod\";\n * import { ChatAnthropic } from \"@langchain/anthropic\";\n *\n * const getWeather = tool((input) => {\n * if ([\"sf\", \"san francisco\"].includes(input.location.toLowerCase())) {\n * return \"It's 60 degrees and foggy.\";\n * } else {\n * return \"It's 90 degrees and sunny.\";\n * }\n * }, {\n * name: \"get_weather\",\n * description: \"Call to get the current weather.\",\n * schema: z.object({\n * location: z.string().describe(\"Location to get the weather for.\"),\n * }),\n * });\n *\n * const tools = [getWeather];\n * const modelWithTools = new ChatAnthropic({\n * model: \"claude-3-haiku-20240307\",\n * temperature: 0\n * }).bindTools(tools);\n *\n * const toolNodeForGraph = new ToolNode(tools)\n *\n * const shouldContinue = (state: typeof MessagesAnnotation.State) => {\n * const { messages } = state;\n * const lastMessage = messages[messages.length - 1];\n * if (\"tool_calls\" in lastMessage && Array.isArray(lastMessage.tool_calls) && lastMessage.tool_calls?.length) {\n * return \"tools\";\n * }\n * return \"__end__\";\n * }\n *\n * const callModel = async (state: typeof MessagesAnnotation.State) => {\n * const { messages } = state;\n * const response = await modelWithTools.invoke(messages);\n * return { messages: response };\n * }\n *\n * const graph = new StateGraph(MessagesAnnotation)\n * .addNode(\"agent\", callModel)\n * .addNode(\"tools\", toolNodeForGraph)\n * .addEdge(\"__start__\", \"agent\")\n * .addConditionalEdges(\"agent\", shouldContinue)\n * .addEdge(\"tools\", \"agent\")\n * .compile();\n *\n * const inputs = {\n * messages: [{ role: \"user\", content: \"what is the weather in SF?\" }],\n * };\n *\n * const stream = await graph.stream(inputs, {\n * streamMode: \"values\",\n * });\n *\n * for await (const { messages } of stream) {\n * console.log(messages);\n * }\n * // Returns the messages in the state at each step of execution\n * ```\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport class ToolNode<T = any> extends RunnableCallable<T, T> {\n tools: (StructuredToolInterface | DynamicTool | RunnableToolLike)[];\n\n handleToolErrors = true;\n\n trace = false;\n\n constructor(\n tools: (StructuredToolInterface | DynamicTool | RunnableToolLike)[],\n options?: ToolNodeOptions\n ) {\n const { name, tags, handleToolErrors } = options ?? {};\n super({ name, tags, func: (input, config) => this.run(input, config) });\n this.tools = tools;\n this.handleToolErrors = handleToolErrors ?? this.handleToolErrors;\n }\n\n protected async runTool(\n call: ToolCall,\n config: RunnableConfig\n ): Promise<ToolMessage | Command> {\n const tool = this.tools.find((tool) => tool.name === call.name);\n try {\n if (tool === undefined) {\n throw new Error(`Tool \"${call.name}\" not found.`);\n }\n const output = await tool.invoke({ ...call, type: \"tool_call\" }, config);\n\n if (\n (isBaseMessage(output) && output.getType() === \"tool\") ||\n isCommand(output)\n ) {\n return output as ToolMessage | Command;\n }\n\n return new ToolMessage({\n status: \"success\",\n name: tool.name,\n content: typeof output === \"string\" ? output : JSON.stringify(output),\n tool_call_id: call.id!,\n });\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n } catch (e: any) {\n if (!this.handleToolErrors) throw e;\n\n if (isGraphInterrupt(e)) {\n // `NodeInterrupt` errors are a breakpoint to bring a human into the loop.\n // As such, they are not recoverable by the agent and shouldn't be fed\n // back. Instead, re-throw these errors even when `handleToolErrors = true`.\n throw e;\n }\n\n return new ToolMessage({\n status: \"error\",\n content: `Error: ${e.message}\\n Please fix your mistakes.`,\n name: call.name,\n tool_call_id: call.id ?? \"\",\n });\n }\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n protected async run(input: unknown, config: RunnableConfig): Promise<T> {\n let outputs: (ToolMessage | Command)[];\n\n if (isSendInput(input)) {\n outputs = [await this.runTool(input.lg_tool_call, config)];\n } else {\n let messages: BaseMessage[];\n if (isBaseMessageArray(input)) {\n messages = input;\n } else if (isMessagesState(input)) {\n messages = input.messages;\n } else {\n throw new Error(\n \"ToolNode only accepts BaseMessage[] or { messages: BaseMessage[] } as input.\"\n );\n }\n\n const toolMessageIds: Set<string> = new Set(\n messages\n .filter((msg) => msg.getType() === \"tool\")\n .map((msg) => (msg as ToolMessage).tool_call_id)\n );\n\n let aiMessage: AIMessage | undefined;\n for (let i = messages.length - 1; i >= 0; i -= 1) {\n const message = messages[i];\n if (isAIMessage(message)) {\n aiMessage = message;\n break;\n }\n }\n\n if (aiMessage == null || !isAIMessage(aiMessage)) {\n throw new Error(\"ToolNode only accepts AIMessages as input.\");\n }\n\n outputs = await Promise.all(\n aiMessage.tool_calls\n ?.filter((call) => call.id == null || !toolMessageIds.has(call.id))\n .map((call) => this.runTool(call, config)) ?? []\n );\n }\n\n // Preserve existing behavior for non-command tool outputs for backwards compatibility\n if (!outputs.some(isCommand)) {\n return (Array.isArray(input) ? outputs : { messages: outputs }) as T;\n }\n\n // Handle mixed Command and non-Command outputs\n const combinedOutputs: (\n | { messages: BaseMessage[] }\n | BaseMessage[]\n | Command\n )[] = [];\n let parentCommand: Command | null = null;\n\n for (const output of outputs) {\n if (isCommand(output)) {\n if (\n output.graph === Command.PARENT &&\n Array.isArray(output.goto) &&\n output.goto.every((send) => _isSend(send))\n ) {\n if (parentCommand) {\n (parentCommand.goto as Send[]).push(...(output.goto as Send[]));\n } else {\n parentCommand = new Command({\n graph: Command.PARENT,\n goto: output.goto,\n });\n }\n } else {\n combinedOutputs.push(output);\n }\n } else {\n combinedOutputs.push(\n Array.isArray(input) ? [output] : { messages: [output] }\n );\n }\n }\n\n if (parentCommand) {\n combinedOutputs.push(parentCommand);\n }\n\n return combinedOutputs as T;\n }\n}\n\n/**\n * A conditional edge function that determines whether to route to a tools node or end the graph.\n *\n * This function is designed to be used as a conditional edge in a LangGraph state graph to implement\n * the common pattern of checking if an AI message contains tool calls that need to be executed.\n *\n * @param state - The current state of the graph, which can be either:\n * - An array of `BaseMessage` objects, where the last message is checked for tool calls\n * - A state object conforming to `MessagesAnnotation.State`, which contains a `messages` array\n *\n * @returns A string indicating the next node to route to:\n * - `\"tools\"` - If the last message contains tool calls that need to be executed\n * - `END` - If there are no tool calls, indicating the graph should terminate\n *\n * @example\n * ```typescript\n * import { StateGraph, MessagesAnnotation, END, START } from \"@langchain/langgraph\";\n * import { ToolNode, toolsCondition } from \"@langchain/langgraph/prebuilt\";\n *\n * const graph = new StateGraph(MessagesAnnotation)\n * .addNode(\"agent\", agentNode)\n * .addNode(\"tools\", new ToolNode([searchTool, calculatorTool]))\n * .addEdge(START, \"agent\")\n * .addConditionalEdges(\"agent\", toolsCondition, [\"tools\", END])\n * .addEdge(\"tools\", \"agent\")\n * .compile();\n * ```\n *\n * @remarks\n * The function checks the last message in the state for the presence of `tool_calls`.\n * If the message is an `AIMessage` with one or more tool calls, it returns `\"tools\"`,\n * indicating that the graph should route to a tools node (typically a `ToolNode`) to\n * execute those tool calls. Otherwise, it returns `END` to terminate the graph execution.\n *\n * This is a common pattern in agentic workflows where an AI model decides whether to\n * use tools or provide a final response.\n */\nexport function toolsCondition(\n state: BaseMessage[] | typeof MessagesAnnotation.State\n): \"tools\" | typeof END {\n const message = Array.isArray(state)\n ? state[state.length - 1]\n : state.messages[state.messages.length - 1];\n\n if (\n message !== undefined &&\n \"tool_calls\" in message &&\n ((message as AIMessage).tool_calls?.length ?? 0) > 0\n ) {\n return \"tools\";\n } else {\n return END;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"tool_node.js","names":[],"sources":["../../src/prebuilt/tool_node.ts"],"sourcesContent":["import {\n BaseMessage,\n ToolMessage,\n AIMessage,\n isBaseMessage,\n isAIMessage,\n} from \"@langchain/core/messages\";\nimport { RunnableToolLike } from \"@langchain/core/runnables\";\nimport {\n DynamicTool,\n StructuredToolInterface,\n type ToolRuntime,\n} from \"@langchain/core/tools\";\nimport type { ToolCall } from \"@langchain/core/messages/tool\";\nimport { RunnableCallable } from \"../utils.js\";\nimport { MessagesAnnotation } from \"../graph/messages_annotation.js\";\nimport { isGraphInterrupt } from \"../errors.js\";\nimport { END, isCommand, Command, _isSend, Send } from \"../constants.js\";\nimport type { LangGraphRunnableConfig } from \"../pregel/runnable_types.js\";\n\nexport type ToolNodeOptions = {\n name?: string;\n tags?: string[];\n handleToolErrors?: boolean;\n};\n\nconst isBaseMessageArray = (input: unknown): input is BaseMessage[] =>\n Array.isArray(input) && input.every(isBaseMessage);\n\nconst isMessagesState = (\n input: unknown\n): input is { messages: BaseMessage[] } =>\n typeof input === \"object\" &&\n input != null &&\n \"messages\" in input &&\n isBaseMessageArray(input.messages);\n\nconst isSendInput = (input: unknown): input is { lg_tool_call: ToolCall } =>\n typeof input === \"object\" && input != null && \"lg_tool_call\" in input;\n\n/**\n * A node that runs the tools requested in the last AIMessage. It can be used\n * either in StateGraph with a \"messages\" key or in MessageGraph. If multiple\n * tool calls are requested, they will be run in parallel. The output will be\n * a list of ToolMessages, one for each tool call.\n *\n * @example\n * ```ts\n * import { ToolNode } from \"@langchain/langgraph/prebuilt\";\n * import { tool } from \"@langchain/core/tools\";\n * import { z } from \"zod\";\n * import { AIMessage } from \"@langchain/core/messages\";\n *\n * const getWeather = tool((input) => {\n * if ([\"sf\", \"san francisco\"].includes(input.location.toLowerCase())) {\n * return \"It's 60 degrees and foggy.\";\n * } else {\n * return \"It's 90 degrees and sunny.\";\n * }\n * }, {\n * name: \"get_weather\",\n * description: \"Call to get the current weather.\",\n * schema: z.object({\n * location: z.string().describe(\"Location to get the weather for.\"),\n * }),\n * });\n *\n * const tools = [getWeather];\n * const toolNode = new ToolNode(tools);\n *\n * const messageWithSingleToolCall = new AIMessage({\n * content: \"\",\n * tool_calls: [\n * {\n * name: \"get_weather\",\n * args: { location: \"sf\" },\n * id: \"tool_call_id\",\n * type: \"tool_call\",\n * }\n * ]\n * })\n *\n * await toolNode.invoke({ messages: [messageWithSingleToolCall] });\n * // Returns tool invocation responses as:\n * // { messages: ToolMessage[] }\n * ```\n *\n * @example\n * ```ts\n * import {\n * StateGraph,\n * MessagesAnnotation,\n * } from \"@langchain/langgraph\";\n * import { ToolNode } from \"@langchain/langgraph/prebuilt\";\n * import { tool } from \"@langchain/core/tools\";\n * import { z } from \"zod\";\n * import { ChatAnthropic } from \"@langchain/anthropic\";\n *\n * const getWeather = tool((input) => {\n * if ([\"sf\", \"san francisco\"].includes(input.location.toLowerCase())) {\n * return \"It's 60 degrees and foggy.\";\n * } else {\n * return \"It's 90 degrees and sunny.\";\n * }\n * }, {\n * name: \"get_weather\",\n * description: \"Call to get the current weather.\",\n * schema: z.object({\n * location: z.string().describe(\"Location to get the weather for.\"),\n * }),\n * });\n *\n * const tools = [getWeather];\n * const modelWithTools = new ChatAnthropic({\n * model: \"claude-3-haiku-20240307\",\n * temperature: 0\n * }).bindTools(tools);\n *\n * const toolNodeForGraph = new ToolNode(tools)\n *\n * const shouldContinue = (state: typeof MessagesAnnotation.State) => {\n * const { messages } = state;\n * const lastMessage = messages[messages.length - 1];\n * if (\"tool_calls\" in lastMessage && Array.isArray(lastMessage.tool_calls) && lastMessage.tool_calls?.length) {\n * return \"tools\";\n * }\n * return \"__end__\";\n * }\n *\n * const callModel = async (state: typeof MessagesAnnotation.State) => {\n * const { messages } = state;\n * const response = await modelWithTools.invoke(messages);\n * return { messages: response };\n * }\n *\n * const graph = new StateGraph(MessagesAnnotation)\n * .addNode(\"agent\", callModel)\n * .addNode(\"tools\", toolNodeForGraph)\n * .addEdge(\"__start__\", \"agent\")\n * .addConditionalEdges(\"agent\", shouldContinue)\n * .addEdge(\"tools\", \"agent\")\n * .compile();\n *\n * const inputs = {\n * messages: [{ role: \"user\", content: \"what is the weather in SF?\" }],\n * };\n *\n * const stream = await graph.stream(inputs, {\n * streamMode: \"values\",\n * });\n *\n * for await (const { messages } of stream) {\n * console.log(messages);\n * }\n * // Returns the messages in the state at each step of execution\n * ```\n *\n * ### Accessing graph state and runtime context from tools\n *\n * Tools executed by a `ToolNode` only receive the arguments produced by the\n * model. To give a tool access to the surrounding graph state or other runtime\n * context, read them from the {@link ToolRuntime} that is passed as the\n * second argument to every tool:\n *\n * - `runtime.state` — the input the `ToolNode` was invoked with. When the\n * `ToolNode` runs as a graph node (e.g. inside `createReactAgent`), this is\n * the current graph state. This works in any runtime, including web browsers,\n * because it does not rely on `node:async_hooks`/`AsyncLocalStorage`.\n * - `runtime.config`, `runtime.context`, `runtime.store`, etc. — other\n * run-scoped values.\n *\n * @example\n * ```ts\n * import { ToolNode } from \"@langchain/langgraph/prebuilt\";\n * import { StateGraph, MessagesZodState } from \"@langchain/langgraph\";\n * import { tool, type ToolRuntime } from \"@langchain/core/tools\";\n * import { z } from \"zod\";\n *\n * // Define the graph state with a Zod schema. The extra `userId` key becomes\n * // part of the state that the ToolNode forwards to its tools via `runtime.state`.\n * const AgentState = z.object({\n * ...MessagesZodState.shape,\n * userId: z.string(),\n * });\n *\n * const getUserInfo = tool(\n * async (_input, runtime: ToolRuntime<typeof AgentState>) => {\n * // Read the current graph state directly from the second argument.\n * const userId = runtime.state.userId;\n * return userId === \"user_123\" ? \"User is John Smith\" : \"Unknown user\";\n * },\n * {\n * name: \"get_user_info\",\n * description: \"Look up user info.\",\n * schema: z.object({}),\n * }\n * );\n *\n * // Wire the ToolNode into a StateGraph that uses `AgentState`. Because the\n * // node runs with the graph state as its input, the tool can read `userId`.\n * const graph = new StateGraph(AgentState)\n * .addNode(\"tools\", new ToolNode([getUserInfo]))\n * .addEdge(\"__start__\", \"tools\")\n * .compile();\n *\n * await graph.invoke({ messages: [...], userId: \"user_123\" });\n * ```\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport class ToolNode<T = any> extends RunnableCallable<T, T> {\n tools: (StructuredToolInterface | DynamicTool | RunnableToolLike)[];\n\n handleToolErrors = true;\n\n trace = false;\n\n constructor(\n tools: (StructuredToolInterface | DynamicTool | RunnableToolLike)[],\n options?: ToolNodeOptions\n ) {\n const { name, tags, handleToolErrors } = options ?? {};\n super({ name, tags, func: (input, config) => this.run(input, config) });\n this.tools = tools;\n this.handleToolErrors = handleToolErrors ?? this.handleToolErrors;\n }\n\n protected async runTool(\n call: ToolCall,\n config: LangGraphRunnableConfig,\n state: unknown\n ): Promise<ToolMessage | Command> {\n const tool = this.tools.find((tool) => tool.name === call.name);\n try {\n if (tool === undefined) {\n throw new Error(`Tool \"${call.name}\" not found.`);\n }\n const toolCall = { ...call, type: \"tool_call\" } as ToolCall;\n const runtime: ToolRuntime = {\n ...config,\n state,\n toolCallId: call.id ?? \"\",\n config,\n context: config.context,\n store: (config.store as ToolRuntime[\"store\"] | undefined) ?? null,\n writer: config.writer ?? config.configurable?.writer ?? null,\n };\n const output = await tool.invoke(toolCall, runtime);\n\n if (\n (isBaseMessage(output) && output.getType() === \"tool\") ||\n isCommand(output)\n ) {\n return output as ToolMessage | Command;\n }\n\n return new ToolMessage({\n status: \"success\",\n name: tool.name,\n content: typeof output === \"string\" ? output : JSON.stringify(output),\n tool_call_id: call.id!,\n });\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n } catch (e: any) {\n if (!this.handleToolErrors) throw e;\n\n if (isGraphInterrupt(e)) {\n // `NodeInterrupt` errors are a breakpoint to bring a human into the loop.\n // As such, they are not recoverable by the agent and shouldn't be fed\n // back. Instead, re-throw these errors even when `handleToolErrors = true`.\n throw e;\n }\n\n return new ToolMessage({\n status: \"error\",\n content: `Error: ${e.message}\\n Please fix your mistakes.`,\n name: call.name,\n tool_call_id: call.id ?? \"\",\n });\n }\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n protected async run(\n input: unknown,\n config: LangGraphRunnableConfig\n ): Promise<T> {\n let outputs: (ToolMessage | Command)[];\n\n if (isSendInput(input)) {\n // Drop the internal `lg_tool_call` routing key so tools only see state.\n const { lg_tool_call: toolCall, ...state } = input as {\n lg_tool_call: ToolCall;\n } & Record<string, unknown>;\n outputs = [await this.runTool(toolCall, config, state)];\n } else {\n let messages: BaseMessage[];\n if (isBaseMessageArray(input)) {\n messages = input;\n } else if (isMessagesState(input)) {\n messages = input.messages;\n } else {\n throw new Error(\n \"ToolNode only accepts BaseMessage[] or { messages: BaseMessage[] } as input.\"\n );\n }\n\n const toolMessageIds: Set<string> = new Set(\n messages\n .filter((msg) => msg.getType() === \"tool\")\n .map((msg) => (msg as ToolMessage).tool_call_id)\n );\n\n let aiMessage: AIMessage | undefined;\n for (let i = messages.length - 1; i >= 0; i -= 1) {\n const message = messages[i];\n if (isAIMessage(message)) {\n aiMessage = message;\n break;\n }\n }\n\n if (aiMessage == null || !isAIMessage(aiMessage)) {\n throw new Error(\"ToolNode only accepts AIMessages as input.\");\n }\n\n outputs = await Promise.all(\n aiMessage.tool_calls\n ?.filter((call) => call.id == null || !toolMessageIds.has(call.id))\n .map((call) => this.runTool(call, config, input)) ?? []\n );\n }\n\n // Preserve existing behavior for non-command tool outputs for backwards compatibility\n if (!outputs.some(isCommand)) {\n return (Array.isArray(input) ? outputs : { messages: outputs }) as T;\n }\n\n // Handle mixed Command and non-Command outputs\n const combinedOutputs: (\n | { messages: BaseMessage[] }\n | BaseMessage[]\n | Command\n )[] = [];\n let parentCommand: Command | null = null;\n\n for (const output of outputs) {\n if (isCommand(output)) {\n if (\n output.graph === Command.PARENT &&\n Array.isArray(output.goto) &&\n output.goto.every((send) => _isSend(send))\n ) {\n if (parentCommand) {\n (parentCommand.goto as Send[]).push(...(output.goto as Send[]));\n } else {\n parentCommand = new Command({\n graph: Command.PARENT,\n goto: output.goto,\n });\n }\n } else {\n combinedOutputs.push(output);\n }\n } else {\n combinedOutputs.push(\n Array.isArray(input) ? [output] : { messages: [output] }\n );\n }\n }\n\n if (parentCommand) {\n combinedOutputs.push(parentCommand);\n }\n\n return combinedOutputs as T;\n }\n}\n\n/**\n * A conditional edge function that determines whether to route to a tools node or end the graph.\n *\n * This function is designed to be used as a conditional edge in a LangGraph state graph to implement\n * the common pattern of checking if an AI message contains tool calls that need to be executed.\n *\n * @param state - The current state of the graph, which can be either:\n * - An array of `BaseMessage` objects, where the last message is checked for tool calls\n * - A state object conforming to `MessagesAnnotation.State`, which contains a `messages` array\n *\n * @returns A string indicating the next node to route to:\n * - `\"tools\"` - If the last message contains tool calls that need to be executed\n * - `END` - If there are no tool calls, indicating the graph should terminate\n *\n * @example\n * ```typescript\n * import { StateGraph, MessagesAnnotation, END, START } from \"@langchain/langgraph\";\n * import { ToolNode, toolsCondition } from \"@langchain/langgraph/prebuilt\";\n *\n * const graph = new StateGraph(MessagesAnnotation)\n * .addNode(\"agent\", agentNode)\n * .addNode(\"tools\", new ToolNode([searchTool, calculatorTool]))\n * .addEdge(START, \"agent\")\n * .addConditionalEdges(\"agent\", toolsCondition, [\"tools\", END])\n * .addEdge(\"tools\", \"agent\")\n * .compile();\n * ```\n *\n * @remarks\n * The function checks the last message in the state for the presence of `tool_calls`.\n * If the message is an `AIMessage` with one or more tool calls, it returns `\"tools\"`,\n * indicating that the graph should route to a tools node (typically a `ToolNode`) to\n * execute those tool calls. Otherwise, it returns `END` to terminate the graph execution.\n *\n * This is a common pattern in agentic workflows where an AI model decides whether to\n * use tools or provide a final response.\n */\nexport function toolsCondition(\n state: BaseMessage[] | typeof MessagesAnnotation.State\n): \"tools\" | typeof END {\n const message = Array.isArray(state)\n ? state[state.length - 1]\n : state.messages[state.messages.length - 1];\n\n if (\n message !== undefined &&\n \"tool_calls\" in message &&\n ((message as AIMessage).tool_calls?.length ?? 0) > 0\n ) {\n return \"tools\";\n } else {\n return END;\n }\n}\n"],"mappings":"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"}
|
package/dist/pregel/algo.cjs
CHANGED
|
@@ -53,7 +53,13 @@ function _localRead(checkpoint, channels, task, select, fresh = false) {
|
|
|
53
53
|
let values;
|
|
54
54
|
if (fresh && updated.size > 0) {
|
|
55
55
|
const localChannels = Object.fromEntries(Object.entries(channels).filter(([k, _]) => updated.has(k)));
|
|
56
|
-
const
|
|
56
|
+
const channelsToSnapshot = /* @__PURE__ */ new Set();
|
|
57
|
+
for (const k in localChannels) {
|
|
58
|
+
if (!Object.prototype.hasOwnProperty.call(localChannels, k)) continue;
|
|
59
|
+
const ch = localChannels[k];
|
|
60
|
+
if (require_base.isDeltaChannel(ch) && ch.isAvailable()) channelsToSnapshot.add(k);
|
|
61
|
+
}
|
|
62
|
+
const newCheckpoint = require_base.createCheckpoint(checkpoint, localChannels, -1, { channelsToSnapshot });
|
|
57
63
|
const newChannels = require_base.emptyChannels(localChannels, newCheckpoint);
|
|
58
64
|
_applyWrites((0, _langchain_langgraph_checkpoint.copyCheckpoint)(newCheckpoint), newChannels, [task], void 0, void 0);
|
|
59
65
|
values = require_io.readChannels({
|
|
@@ -76,26 +82,34 @@ const IGNORE = new Set([
|
|
|
76
82
|
require_constants.RESUME,
|
|
77
83
|
require_constants.INTERRUPT,
|
|
78
84
|
require_constants.RETURN,
|
|
79
|
-
require_constants.ERROR
|
|
85
|
+
require_constants.ERROR,
|
|
86
|
+
require_constants.ERROR_SOURCE_NODE
|
|
80
87
|
]);
|
|
88
|
+
const RESERVED_SET = new Set(require_constants.RESERVED);
|
|
81
89
|
function _applyWrites(checkpoint, channels, tasks, getNextVersion, triggerToNodes) {
|
|
90
|
+
const pathCache = /* @__PURE__ */ new Map();
|
|
91
|
+
for (const task of tasks) pathCache.set(task, task.path?.slice(0, 3) || []);
|
|
82
92
|
tasks.sort((a, b) => {
|
|
83
|
-
const aPath =
|
|
84
|
-
const bPath =
|
|
93
|
+
const aPath = pathCache.get(a);
|
|
94
|
+
const bPath = pathCache.get(b);
|
|
85
95
|
for (let i = 0; i < Math.min(aPath.length, bPath.length); i += 1) {
|
|
86
96
|
if (aPath[i] < bPath[i]) return -1;
|
|
87
97
|
if (aPath[i] > bPath[i]) return 1;
|
|
88
98
|
}
|
|
89
99
|
return aPath.length - bPath.length;
|
|
90
100
|
});
|
|
91
|
-
const bumpStep = tasks.some((task) => task.triggers.length > 0);
|
|
92
101
|
const onlyChannels = require_base.getOnlyChannels(channels);
|
|
102
|
+
let bumpStep = false;
|
|
103
|
+
const channelsToConsume = /* @__PURE__ */ new Set();
|
|
93
104
|
for (const task of tasks) {
|
|
105
|
+
if (task.triggers.length > 0) bumpStep = true;
|
|
94
106
|
checkpoint.versions_seen[task.name] ??= {};
|
|
95
|
-
for (const chan of task.triggers)
|
|
107
|
+
for (const chan of task.triggers) {
|
|
108
|
+
if (chan in checkpoint.channel_versions) checkpoint.versions_seen[task.name][chan] = checkpoint.channel_versions[chan];
|
|
109
|
+
if (!RESERVED_SET.has(chan)) channelsToConsume.add(chan);
|
|
110
|
+
}
|
|
96
111
|
}
|
|
97
112
|
let maxVersion = maxChannelMapVersion(checkpoint.channel_versions);
|
|
98
|
-
const channelsToConsume = new Set(tasks.flatMap((task) => task.triggers).filter((chan) => !require_constants.RESERVED.includes(chan)));
|
|
99
113
|
let usedNewVersion = false;
|
|
100
114
|
for (const chan of channelsToConsume) if (chan in onlyChannels && onlyChannels[chan].consume()) {
|
|
101
115
|
if (getNextVersion !== void 0) {
|
|
@@ -167,22 +181,53 @@ function* candidateNodes(checkpoint, processes, extra) {
|
|
|
167
181
|
}
|
|
168
182
|
}
|
|
169
183
|
/**
|
|
184
|
+
* Build an index over pendingWrites for O(1) lookups.
|
|
185
|
+
*
|
|
186
|
+
* @internal Exported for benchmarks and regression tests only.
|
|
187
|
+
*/
|
|
188
|
+
function _indexPendingWrites(pendingWrites) {
|
|
189
|
+
let nullResume;
|
|
190
|
+
const resumeByTaskId = /* @__PURE__ */ new Map();
|
|
191
|
+
const successfulWriteTaskIds = /* @__PURE__ */ new Set();
|
|
192
|
+
if (pendingWrites) for (const [tid, chan, val] of pendingWrites) {
|
|
193
|
+
if (tid === "00000000-0000-0000-0000-000000000000" && chan === "__resume__" && nullResume === void 0) nullResume = val;
|
|
194
|
+
if (chan === "__resume__" && tid !== "00000000-0000-0000-0000-000000000000") {
|
|
195
|
+
let arr = resumeByTaskId.get(tid);
|
|
196
|
+
if (!arr) {
|
|
197
|
+
arr = [];
|
|
198
|
+
resumeByTaskId.set(tid, arr);
|
|
199
|
+
}
|
|
200
|
+
arr.push(val);
|
|
201
|
+
}
|
|
202
|
+
if (chan !== "__error__") successfulWriteTaskIds.add(tid);
|
|
203
|
+
}
|
|
204
|
+
return {
|
|
205
|
+
nullResume,
|
|
206
|
+
resumeByTaskId,
|
|
207
|
+
successfulWriteTaskIds
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
170
211
|
* Prepare the set of tasks that will make up the next Pregel step.
|
|
171
212
|
* This is the union of all PUSH tasks (Sends) and PULL tasks (nodes triggered
|
|
172
213
|
* by edges).
|
|
173
214
|
*/
|
|
174
215
|
function _prepareNextTasks(checkpoint, pendingWrites, processes, channels, config, forExecution, extra) {
|
|
175
216
|
const tasks = {};
|
|
217
|
+
const indexedExtra = extra.pendingWritesIndex ? extra : {
|
|
218
|
+
...extra,
|
|
219
|
+
pendingWritesIndex: _indexPendingWrites(pendingWrites)
|
|
220
|
+
};
|
|
176
221
|
const tasksChannel = channels[require_constants.TASKS];
|
|
177
222
|
if (tasksChannel?.isAvailable()) {
|
|
178
223
|
const len = tasksChannel.get().length;
|
|
179
224
|
for (let i = 0; i < len; i += 1) {
|
|
180
|
-
const task = _prepareSingleTask([require_constants.PUSH, i], checkpoint, pendingWrites, processes, channels, config, forExecution,
|
|
225
|
+
const task = _prepareSingleTask([require_constants.PUSH, i], checkpoint, pendingWrites, processes, channels, config, forExecution, indexedExtra);
|
|
181
226
|
if (task !== void 0) tasks[task.id] = task;
|
|
182
227
|
}
|
|
183
228
|
}
|
|
184
|
-
for (const name of candidateNodes(checkpoint, processes,
|
|
185
|
-
const task = _prepareSingleTask([require_constants.PULL, name], checkpoint, pendingWrites, processes, channels, config, forExecution,
|
|
229
|
+
for (const name of candidateNodes(checkpoint, processes, indexedExtra)) {
|
|
230
|
+
const task = _prepareSingleTask([require_constants.PULL, name], checkpoint, pendingWrites, processes, channels, config, forExecution, indexedExtra);
|
|
186
231
|
if (task !== void 0) tasks[task.id] = task;
|
|
187
232
|
}
|
|
188
233
|
return tasks;
|
|
@@ -259,7 +304,8 @@ function _prepareSingleTask(taskPath, checkpoint, pendingWrites, processes, chan
|
|
|
259
304
|
taskId: id,
|
|
260
305
|
currentTaskInput: call.input,
|
|
261
306
|
resumeMap: config.configurable?.[require_constants.CONFIG_KEY_RESUME_MAP],
|
|
262
|
-
namespaceHash: require_hash.XXH3(taskCheckpointNamespace)
|
|
307
|
+
namespaceHash: require_hash.XXH3(taskCheckpointNamespace),
|
|
308
|
+
pendingWritesIndex: extra.pendingWritesIndex
|
|
263
309
|
}),
|
|
264
310
|
[require_constants.CONFIG_KEY_PREVIOUS_STATE]: checkpoint.channel_values[require_constants.PREVIOUS],
|
|
265
311
|
checkpoint_id: void 0,
|
|
@@ -277,7 +323,8 @@ function _prepareSingleTask(taskPath, checkpoint, pendingWrites, processes, chan
|
|
|
277
323
|
} : void 0,
|
|
278
324
|
id,
|
|
279
325
|
path: outputTaskPath,
|
|
280
|
-
writers: []
|
|
326
|
+
writers: [],
|
|
327
|
+
timeout: call.timeout
|
|
281
328
|
};
|
|
282
329
|
} else return {
|
|
283
330
|
id,
|
|
@@ -290,7 +337,7 @@ function _prepareSingleTask(taskPath, checkpoint, pendingWrites, processes, chan
|
|
|
290
337
|
if (!channels["__pregel_tasks"]?.isAvailable()) return;
|
|
291
338
|
const sends = channels[require_constants.TASKS].get();
|
|
292
339
|
if (index < 0 || index >= sends.length) return;
|
|
293
|
-
const packet = require_constants._isSendInterface(sends[index]) && !require_constants._isSend(sends[index]) ? new require_constants.Send(sends[index].node, sends[index].args) : sends[index];
|
|
340
|
+
const packet = require_constants._isSendInterface(sends[index]) && !require_constants._isSend(sends[index]) ? new require_constants.Send(sends[index].node, sends[index].args, sends[index].timeout !== void 0 ? { timeout: sends[index].timeout } : void 0) : sends[index];
|
|
294
341
|
if (!require_constants._isSendInterface(packet)) {
|
|
295
342
|
console.warn(`Ignoring invalid packet ${JSON.stringify(packet)} in pending sends.`);
|
|
296
343
|
return;
|
|
@@ -367,7 +414,8 @@ function _prepareSingleTask(taskPath, checkpoint, pendingWrites, processes, chan
|
|
|
367
414
|
taskId,
|
|
368
415
|
currentTaskInput: packet.args,
|
|
369
416
|
resumeMap: config.configurable?.[require_constants.CONFIG_KEY_RESUME_MAP],
|
|
370
|
-
namespaceHash: require_hash.XXH3(taskCheckpointNamespace)
|
|
417
|
+
namespaceHash: require_hash.XXH3(taskCheckpointNamespace),
|
|
418
|
+
pendingWritesIndex: extra.pendingWritesIndex
|
|
371
419
|
}),
|
|
372
420
|
[require_constants.CONFIG_KEY_PREVIOUS_STATE]: checkpoint.channel_values[require_constants.PREVIOUS],
|
|
373
421
|
checkpoint_id: void 0,
|
|
@@ -389,7 +437,8 @@ function _prepareSingleTask(taskPath, checkpoint, pendingWrites, processes, chan
|
|
|
389
437
|
} : void 0,
|
|
390
438
|
id: taskId,
|
|
391
439
|
path: taskPath,
|
|
392
|
-
writers: proc.getWriters()
|
|
440
|
+
writers: proc.getWriters(),
|
|
441
|
+
timeout: packet.timeout ?? proc.timeout
|
|
393
442
|
};
|
|
394
443
|
}
|
|
395
444
|
} else return {
|
|
@@ -411,7 +460,7 @@ function _prepareSingleTask(taskPath, checkpoint, pendingWrites, processes, chan
|
|
|
411
460
|
require_constants.PULL,
|
|
412
461
|
name
|
|
413
462
|
]), checkpoint.id);
|
|
414
|
-
if (pendingWrites.some((w) => w[0] === taskId && w[1] !== "__error__")) return;
|
|
463
|
+
if (extra.pendingWritesIndex ? extra.pendingWritesIndex.successfulWriteTaskIds.has(taskId) : pendingWrites.some((w) => w[0] === taskId && w[1] !== "__error__")) return;
|
|
415
464
|
}
|
|
416
465
|
const nullVersion = require_index.getNullChannelVersion(checkpoint.channel_versions);
|
|
417
466
|
if (nullVersion === void 0) return;
|
|
@@ -491,7 +540,8 @@ function _prepareSingleTask(taskPath, checkpoint, pendingWrites, processes, chan
|
|
|
491
540
|
taskId,
|
|
492
541
|
currentTaskInput: val,
|
|
493
542
|
resumeMap: config.configurable?.[require_constants.CONFIG_KEY_RESUME_MAP],
|
|
494
|
-
namespaceHash: require_hash.XXH3(taskCheckpointNamespace)
|
|
543
|
+
namespaceHash: require_hash.XXH3(taskCheckpointNamespace),
|
|
544
|
+
pendingWritesIndex: extra.pendingWritesIndex
|
|
495
545
|
}),
|
|
496
546
|
[require_constants.CONFIG_KEY_PREVIOUS_STATE]: checkpoint.channel_values[require_constants.PREVIOUS],
|
|
497
547
|
checkpoint_id: void 0,
|
|
@@ -513,7 +563,8 @@ function _prepareSingleTask(taskPath, checkpoint, pendingWrites, processes, chan
|
|
|
513
563
|
} : void 0,
|
|
514
564
|
id: taskId,
|
|
515
565
|
path: taskPath,
|
|
516
|
-
writers: proc.getWriters()
|
|
566
|
+
writers: proc.getWriters(),
|
|
567
|
+
timeout: proc.timeout
|
|
517
568
|
};
|
|
518
569
|
}
|
|
519
570
|
} else return {
|
|
@@ -526,6 +577,115 @@ function _prepareSingleTask(taskPath, checkpoint, pendingWrites, processes, chan
|
|
|
526
577
|
}
|
|
527
578
|
}
|
|
528
579
|
/**
|
|
580
|
+
* Prepare an immediate node-level error handler task for a failed task.
|
|
581
|
+
*
|
|
582
|
+
* The handler runs only after the failed node's retry policy is exhausted (the
|
|
583
|
+
* runner schedules it once a non-bubble-up error settles). It is prepared like
|
|
584
|
+
* a PUSH task targeting the auto-generated handler node, receives the failed
|
|
585
|
+
* node's input, and is injected with a {@link NodeError} under
|
|
586
|
+
* {@link CONFIG_KEY_NODE_ERROR} so the handler can inspect the failure
|
|
587
|
+
* provenance (and route via `Command({ goto })`).
|
|
588
|
+
*
|
|
589
|
+
* @internal
|
|
590
|
+
*/
|
|
591
|
+
function _prepareNodeErrorHandlerTask(failedTask, handlerNodeName, error, checkpoint, pendingWrites, processes, channels, config, extra) {
|
|
592
|
+
const { step, checkpointer, manager } = extra;
|
|
593
|
+
const proc = processes[handlerNodeName];
|
|
594
|
+
if (proc === void 0) return;
|
|
595
|
+
const node = proc.getNode();
|
|
596
|
+
if (node === void 0) return;
|
|
597
|
+
const configurable = config.configurable ?? {};
|
|
598
|
+
const parentNamespace = configurable.checkpoint_ns ?? "";
|
|
599
|
+
const triggers = [require_constants.PUSH];
|
|
600
|
+
const checkpointNamespace = parentNamespace === "" ? handlerNodeName : `${parentNamespace}|${handlerNodeName}`;
|
|
601
|
+
const taskId = (0, _langchain_langgraph_checkpoint.uuid5)(JSON.stringify([
|
|
602
|
+
checkpointNamespace,
|
|
603
|
+
step.toString(),
|
|
604
|
+
handlerNodeName,
|
|
605
|
+
require_constants.PUSH,
|
|
606
|
+
"node_error_handler",
|
|
607
|
+
failedTask.id
|
|
608
|
+
]), checkpoint.id);
|
|
609
|
+
const taskCheckpointNamespace = `${checkpointNamespace}:${taskId}`;
|
|
610
|
+
const taskPath = [
|
|
611
|
+
require_constants.PUSH,
|
|
612
|
+
String(failedTask.name),
|
|
613
|
+
handlerNodeName,
|
|
614
|
+
false
|
|
615
|
+
];
|
|
616
|
+
let metadata = {
|
|
617
|
+
langgraph_step: step,
|
|
618
|
+
langgraph_node: handlerNodeName,
|
|
619
|
+
langgraph_triggers: triggers,
|
|
620
|
+
langgraph_path: taskPath,
|
|
621
|
+
langgraph_checkpoint_ns: taskCheckpointNamespace,
|
|
622
|
+
checkpoint_ns: taskCheckpointNamespace
|
|
623
|
+
};
|
|
624
|
+
if (proc.metadata !== void 0) metadata = {
|
|
625
|
+
...metadata,
|
|
626
|
+
...proc.metadata
|
|
627
|
+
};
|
|
628
|
+
const writes = [];
|
|
629
|
+
const executionInfo = {
|
|
630
|
+
checkpointId: checkpoint.id,
|
|
631
|
+
checkpointNs: taskCheckpointNamespace,
|
|
632
|
+
taskId,
|
|
633
|
+
threadId: configurable.thread_id,
|
|
634
|
+
runId: config.runId != null ? String(config.runId) : void 0,
|
|
635
|
+
nodeAttempt: 1
|
|
636
|
+
};
|
|
637
|
+
return {
|
|
638
|
+
name: handlerNodeName,
|
|
639
|
+
input: failedTask.input,
|
|
640
|
+
proc: node,
|
|
641
|
+
subgraphs: proc.subgraphs,
|
|
642
|
+
writes,
|
|
643
|
+
config: {
|
|
644
|
+
...(0, _langchain_core_runnables.patchConfig)((0, _langchain_core_runnables.mergeConfigs)(config, {
|
|
645
|
+
metadata,
|
|
646
|
+
tags: proc.tags,
|
|
647
|
+
store: extra.store ?? config.store
|
|
648
|
+
}), {
|
|
649
|
+
runName: handlerNodeName,
|
|
650
|
+
callbacks: manager?.getChild(`graph:step:${step}`),
|
|
651
|
+
configurable: {
|
|
652
|
+
[require_constants.CONFIG_KEY_TASK_ID]: taskId,
|
|
653
|
+
[require_constants.CONFIG_KEY_SEND]: (writes_) => _localWrite((items) => writes.push(...items), processes, writes_),
|
|
654
|
+
[require_constants.CONFIG_KEY_READ]: (select_, fresh_ = false) => _localRead(checkpoint, channels, {
|
|
655
|
+
name: handlerNodeName,
|
|
656
|
+
writes,
|
|
657
|
+
triggers,
|
|
658
|
+
path: taskPath
|
|
659
|
+
}, select_, fresh_),
|
|
660
|
+
[require_constants.CONFIG_KEY_CHECKPOINTER]: checkpointer ?? configurable["__pregel_checkpointer"],
|
|
661
|
+
[require_constants.CONFIG_KEY_CHECKPOINT_MAP]: {
|
|
662
|
+
...configurable[require_constants.CONFIG_KEY_CHECKPOINT_MAP],
|
|
663
|
+
[parentNamespace]: checkpoint.id
|
|
664
|
+
},
|
|
665
|
+
[require_constants.CONFIG_KEY_SCRATCHPAD]: _scratchpad({
|
|
666
|
+
pendingWrites: pendingWrites ?? [],
|
|
667
|
+
taskId,
|
|
668
|
+
currentTaskInput: failedTask.input,
|
|
669
|
+
resumeMap: config.configurable?.[require_constants.CONFIG_KEY_RESUME_MAP],
|
|
670
|
+
namespaceHash: require_hash.XXH3(taskCheckpointNamespace)
|
|
671
|
+
}),
|
|
672
|
+
[require_constants.CONFIG_KEY_PREVIOUS_STATE]: checkpoint.channel_values[require_constants.PREVIOUS],
|
|
673
|
+
[require_constants.CONFIG_KEY_NODE_ERROR]: new require_errors.NodeError(String(failedTask.name), error),
|
|
674
|
+
checkpoint_id: void 0,
|
|
675
|
+
checkpoint_ns: taskCheckpointNamespace
|
|
676
|
+
}
|
|
677
|
+
}),
|
|
678
|
+
executionInfo
|
|
679
|
+
},
|
|
680
|
+
triggers,
|
|
681
|
+
retry_policy: proc.retryPolicy,
|
|
682
|
+
cache_key: void 0,
|
|
683
|
+
id: taskId,
|
|
684
|
+
path: taskPath,
|
|
685
|
+
writers: proc.getWriters()
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
/**
|
|
529
689
|
* Function injected under CONFIG_KEY_READ in task config, to read current state.
|
|
530
690
|
* Used by conditional edges to read a copy of the state with reflecting the writes
|
|
531
691
|
* from that node only.
|
|
@@ -581,13 +741,13 @@ function sanitizeUntrackedValuesInSend(packet, channels) {
|
|
|
581
741
|
}
|
|
582
742
|
return new require_constants.Send(packet.node, sanitizedArg);
|
|
583
743
|
}
|
|
584
|
-
function _scratchpad({ pendingWrites, taskId, currentTaskInput, resumeMap, namespaceHash }) {
|
|
585
|
-
const nullResume = pendingWrites.find(([writeTaskId, chan]) => writeTaskId === "00000000-0000-0000-0000-000000000000" && chan === "__resume__")?.[2];
|
|
744
|
+
function _scratchpad({ pendingWrites, taskId, currentTaskInput, resumeMap, namespaceHash, pendingWritesIndex }) {
|
|
745
|
+
const nullResume = pendingWritesIndex ? pendingWritesIndex.nullResume : pendingWrites.find(([writeTaskId, chan]) => writeTaskId === "00000000-0000-0000-0000-000000000000" && chan === "__resume__")?.[2];
|
|
586
746
|
const scratchpad = {
|
|
587
747
|
callCounter: 0,
|
|
588
748
|
interruptCounter: -1,
|
|
589
749
|
resume: (() => {
|
|
590
|
-
const result = pendingWrites.filter(([writeTaskId, chan]) => writeTaskId === taskId && chan === "__resume__").flatMap(([_writeTaskId, _chan, resume]) => resume);
|
|
750
|
+
const result = pendingWritesIndex ? (pendingWritesIndex.resumeByTaskId.get(taskId) ?? []).flat() : pendingWrites.filter(([writeTaskId, chan]) => writeTaskId === taskId && chan === "__resume__").flatMap(([_writeTaskId, _chan, resume]) => resume);
|
|
591
751
|
if (resumeMap != null && namespaceHash in resumeMap) {
|
|
592
752
|
const mappedResume = resumeMap[namespaceHash];
|
|
593
753
|
result.push(mappedResume);
|
|
@@ -611,6 +771,7 @@ function _scratchpad({ pendingWrites, taskId, currentTaskInput, resumeMap, names
|
|
|
611
771
|
exports._applyWrites = _applyWrites;
|
|
612
772
|
exports._localRead = _localRead;
|
|
613
773
|
exports._prepareNextTasks = _prepareNextTasks;
|
|
774
|
+
exports._prepareNodeErrorHandlerTask = _prepareNodeErrorHandlerTask;
|
|
614
775
|
exports._prepareSingleTask = _prepareSingleTask;
|
|
615
776
|
exports.increment = increment;
|
|
616
777
|
exports.sanitizeUntrackedValuesInSend = sanitizeUntrackedValuesInSend;
|