@librechat/agents 3.0.23 → 3.0.25
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/cjs/tools/ToolNode.cjs +154 -55
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +155 -56
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/types/tools/ToolNode.d.ts +9 -1
- package/dist/types/types/tools.d.ts +1 -1
- package/package.json +1 -1
- package/src/tools/ToolNode.ts +195 -64
- package/src/types/tools.ts +1 -1
|
@@ -1,19 +1,26 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var langgraph = require('@langchain/langgraph');
|
|
4
3
|
var messages = require('@langchain/core/messages');
|
|
4
|
+
var langgraph = require('@langchain/langgraph');
|
|
5
5
|
require('../common/enum.cjs');
|
|
6
6
|
require('nanoid');
|
|
7
7
|
require('../messages/core.cjs');
|
|
8
8
|
var run = require('../utils/run.cjs');
|
|
9
9
|
require('js-tiktoken/lite');
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Helper to check if a value is a Send object
|
|
13
|
+
*/
|
|
14
|
+
function isSend(value) {
|
|
15
|
+
return value instanceof langgraph.Send;
|
|
16
|
+
}
|
|
11
17
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
12
18
|
class ToolNode extends run.RunnableCallable {
|
|
13
19
|
tools;
|
|
14
20
|
toolMap;
|
|
15
21
|
loadRuntimeTools;
|
|
16
22
|
handleToolErrors = true;
|
|
23
|
+
trace = false;
|
|
17
24
|
toolCallStepIds;
|
|
18
25
|
errorHandler;
|
|
19
26
|
toolUsageCount;
|
|
@@ -34,74 +41,166 @@ class ToolNode extends run.RunnableCallable {
|
|
|
34
41
|
getToolUsageCounts() {
|
|
35
42
|
return new Map(this.toolUsageCount); // Return a copy
|
|
36
43
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const
|
|
47
|
-
this.
|
|
48
|
-
|
|
44
|
+
/**
|
|
45
|
+
* Runs a single tool call with error handling
|
|
46
|
+
*/
|
|
47
|
+
async runTool(call, config) {
|
|
48
|
+
const tool = this.toolMap.get(call.name);
|
|
49
|
+
try {
|
|
50
|
+
if (tool === undefined) {
|
|
51
|
+
throw new Error(`Tool "${call.name}" not found.`);
|
|
52
|
+
}
|
|
53
|
+
const turn = this.toolUsageCount.get(call.name) ?? 0;
|
|
54
|
+
this.toolUsageCount.set(call.name, turn + 1);
|
|
55
|
+
const args = call.args;
|
|
56
|
+
const stepId = this.toolCallStepIds?.get(call.id);
|
|
57
|
+
const output = await tool.invoke({ ...call, args, type: 'tool_call', stepId, turn }, config);
|
|
58
|
+
if ((messages.isBaseMessage(output) && output._getType() === 'tool') ||
|
|
59
|
+
langgraph.isCommand(output)) {
|
|
60
|
+
return output;
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
return new messages.ToolMessage({
|
|
64
|
+
status: 'success',
|
|
65
|
+
name: tool.name,
|
|
66
|
+
content: typeof output === 'string' ? output : JSON.stringify(output),
|
|
67
|
+
tool_call_id: call.id,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
49
70
|
}
|
|
50
|
-
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
71
|
+
catch (_e) {
|
|
72
|
+
const e = _e;
|
|
73
|
+
if (!this.handleToolErrors) {
|
|
74
|
+
throw e;
|
|
75
|
+
}
|
|
76
|
+
if (langgraph.isGraphInterrupt(e)) {
|
|
77
|
+
throw e;
|
|
78
|
+
}
|
|
79
|
+
if (this.errorHandler) {
|
|
80
|
+
try {
|
|
81
|
+
await this.errorHandler({
|
|
82
|
+
error: e,
|
|
83
|
+
id: call.id,
|
|
84
|
+
name: call.name,
|
|
85
|
+
input: call.args,
|
|
86
|
+
}, config.metadata);
|
|
64
87
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
88
|
+
catch (handlerError) {
|
|
89
|
+
// eslint-disable-next-line no-console
|
|
90
|
+
console.error('Error in errorHandler:', {
|
|
91
|
+
toolName: call.name,
|
|
92
|
+
toolCallId: call.id,
|
|
93
|
+
toolArgs: call.args,
|
|
94
|
+
stepId: this.toolCallStepIds?.get(call.id),
|
|
95
|
+
turn: this.toolUsageCount.get(call.name),
|
|
96
|
+
originalError: {
|
|
97
|
+
message: e.message,
|
|
98
|
+
stack: e.stack ?? undefined,
|
|
99
|
+
},
|
|
100
|
+
handlerError: handlerError instanceof Error
|
|
101
|
+
? {
|
|
102
|
+
message: handlerError.message,
|
|
103
|
+
stack: handlerError.stack ?? undefined,
|
|
104
|
+
}
|
|
105
|
+
: {
|
|
106
|
+
message: String(handlerError),
|
|
107
|
+
stack: undefined,
|
|
108
|
+
},
|
|
70
109
|
});
|
|
71
110
|
}
|
|
72
111
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
112
|
+
return new messages.ToolMessage({
|
|
113
|
+
status: 'error',
|
|
114
|
+
content: `Error: ${e.message}\n Please fix your mistakes.`,
|
|
115
|
+
name: call.name,
|
|
116
|
+
tool_call_id: call.id ?? '',
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
121
|
+
async run(input, config) {
|
|
122
|
+
let outputs;
|
|
123
|
+
if (this.isSendInput(input)) {
|
|
124
|
+
outputs = [await this.runTool(input.lg_tool_call, config)];
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
let messages$1;
|
|
128
|
+
if (Array.isArray(input)) {
|
|
129
|
+
messages$1 = input;
|
|
130
|
+
}
|
|
131
|
+
else if (this.isMessagesState(input)) {
|
|
132
|
+
messages$1 = input.messages;
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
throw new Error('ToolNode only accepts BaseMessage[] or { messages: BaseMessage[] } as input.');
|
|
136
|
+
}
|
|
137
|
+
const toolMessageIds = new Set(messages$1
|
|
138
|
+
.filter((msg) => msg._getType() === 'tool')
|
|
139
|
+
.map((msg) => msg.tool_call_id));
|
|
140
|
+
let aiMessage;
|
|
141
|
+
for (let i = messages$1.length - 1; i >= 0; i--) {
|
|
142
|
+
const message = messages$1[i];
|
|
143
|
+
if (messages.isAIMessage(message)) {
|
|
144
|
+
aiMessage = message;
|
|
145
|
+
break;
|
|
80
146
|
}
|
|
81
|
-
this.errorHandler?.({
|
|
82
|
-
error: e,
|
|
83
|
-
id: call.id,
|
|
84
|
-
name: call.name,
|
|
85
|
-
input: call.args,
|
|
86
|
-
}, config.metadata);
|
|
87
|
-
return new messages.ToolMessage({
|
|
88
|
-
content: `Error: ${e.message}\n Please fix your mistakes.`,
|
|
89
|
-
name: call.name,
|
|
90
|
-
tool_call_id: call.id ?? '',
|
|
91
|
-
});
|
|
92
147
|
}
|
|
93
|
-
|
|
148
|
+
if (aiMessage == null || !messages.isAIMessage(aiMessage)) {
|
|
149
|
+
throw new Error('ToolNode only accepts AIMessages as input.');
|
|
150
|
+
}
|
|
151
|
+
if (this.loadRuntimeTools) {
|
|
152
|
+
const { tools, toolMap } = this.loadRuntimeTools(aiMessage.tool_calls ?? []);
|
|
153
|
+
this.tools = tools;
|
|
154
|
+
this.toolMap =
|
|
155
|
+
toolMap ?? new Map(tools.map((tool) => [tool.name, tool]));
|
|
156
|
+
}
|
|
157
|
+
outputs = await Promise.all(aiMessage.tool_calls
|
|
158
|
+
?.filter((call) => call.id == null || !toolMessageIds.has(call.id))
|
|
159
|
+
.map((call) => this.runTool(call, config)) ?? []);
|
|
160
|
+
}
|
|
94
161
|
if (!outputs.some(langgraph.isCommand)) {
|
|
95
162
|
return (Array.isArray(input) ? outputs : { messages: outputs });
|
|
96
163
|
}
|
|
97
|
-
const combinedOutputs =
|
|
164
|
+
const combinedOutputs = [];
|
|
165
|
+
let parentCommand = null;
|
|
166
|
+
for (const output of outputs) {
|
|
98
167
|
if (langgraph.isCommand(output)) {
|
|
99
|
-
|
|
168
|
+
if (output.graph === langgraph.Command.PARENT &&
|
|
169
|
+
Array.isArray(output.goto) &&
|
|
170
|
+
output.goto.every((send) => isSend(send))) {
|
|
171
|
+
if (parentCommand) {
|
|
172
|
+
parentCommand.goto.push(...output.goto);
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
parentCommand = new langgraph.Command({
|
|
176
|
+
graph: langgraph.Command.PARENT,
|
|
177
|
+
goto: output.goto,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
combinedOutputs.push(output);
|
|
183
|
+
}
|
|
100
184
|
}
|
|
101
|
-
|
|
102
|
-
|
|
185
|
+
else {
|
|
186
|
+
combinedOutputs.push(Array.isArray(input) ? [output] : { messages: [output] });
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (parentCommand) {
|
|
190
|
+
combinedOutputs.push(parentCommand);
|
|
191
|
+
}
|
|
103
192
|
return combinedOutputs;
|
|
104
193
|
}
|
|
194
|
+
isSendInput(input) {
|
|
195
|
+
return (typeof input === 'object' && input != null && 'lg_tool_call' in input);
|
|
196
|
+
}
|
|
197
|
+
isMessagesState(input) {
|
|
198
|
+
return (typeof input === 'object' &&
|
|
199
|
+
input != null &&
|
|
200
|
+
'messages' in input &&
|
|
201
|
+
Array.isArray(input.messages) &&
|
|
202
|
+
input.messages.every(messages.isBaseMessage));
|
|
203
|
+
}
|
|
105
204
|
}
|
|
106
205
|
function areToolCallsInvoked(message, invokedToolIds) {
|
|
107
206
|
if (!invokedToolIds || invokedToolIds.size === 0)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ToolNode.cjs","sources":["../../../src/tools/ToolNode.ts"],"sourcesContent":["import {\n END,\n MessagesAnnotation,\n isCommand,\n isGraphInterrupt,\n} from '@langchain/langgraph';\nimport { ToolMessage, isBaseMessage } from '@langchain/core/messages';\nimport type {\n RunnableConfig,\n RunnableToolLike,\n} from '@langchain/core/runnables';\nimport type { BaseMessage, AIMessage } from '@langchain/core/messages';\nimport type { StructuredToolInterface } from '@langchain/core/tools';\nimport type * as t from '@/types';\nimport { RunnableCallable } from '@/utils';\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport class ToolNode<T = any> extends RunnableCallable<T, T> {\n tools: t.GenericTool[];\n private toolMap: Map<string, StructuredToolInterface | RunnableToolLike>;\n private loadRuntimeTools?: t.ToolRefGenerator;\n handleToolErrors = true;\n toolCallStepIds?: Map<string, string>;\n errorHandler?: t.ToolNodeConstructorParams['errorHandler'];\n private toolUsageCount: Map<string, number>;\n\n constructor({\n tools,\n toolMap,\n name,\n tags,\n errorHandler,\n toolCallStepIds,\n handleToolErrors,\n loadRuntimeTools,\n }: t.ToolNodeConstructorParams) {\n super({ name, tags, func: (input, config) => this.run(input, config) });\n this.tools = tools;\n this.toolMap = toolMap ?? new Map(tools.map((tool) => [tool.name, tool]));\n this.toolCallStepIds = toolCallStepIds;\n this.handleToolErrors = handleToolErrors ?? this.handleToolErrors;\n this.loadRuntimeTools = loadRuntimeTools;\n this.errorHandler = errorHandler;\n this.toolUsageCount = new Map<string, number>();\n }\n\n /**\n * Returns a snapshot of the current tool usage counts.\n * @returns A ReadonlyMap where keys are tool names and values are their usage counts.\n */\n public getToolUsageCounts(): ReadonlyMap<string, number> {\n return new Map(this.toolUsageCount); // Return a copy\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n protected async run(input: any, config: RunnableConfig): Promise<T> {\n const message = Array.isArray(input)\n ? input[input.length - 1]\n : input.messages[input.messages.length - 1];\n\n if (message._getType() !== 'ai') {\n throw new Error('ToolNode only accepts AIMessages as input.');\n }\n\n if (this.loadRuntimeTools) {\n const { tools, toolMap } = this.loadRuntimeTools(\n (message as AIMessage).tool_calls ?? []\n );\n this.tools = tools;\n this.toolMap = toolMap ?? new Map(tools.map((tool) => [tool.name, tool]));\n }\n const outputs = await Promise.all(\n (message as AIMessage).tool_calls?.map(async (call) => {\n const tool = this.toolMap.get(call.name);\n try {\n if (tool === undefined) {\n throw new Error(`Tool \"${call.name}\" not found.`);\n }\n const turn = this.toolUsageCount.get(call.name) ?? 0;\n this.toolUsageCount.set(call.name, turn + 1);\n const args = call.args;\n const stepId = this.toolCallStepIds?.get(call.id!);\n const output = await tool.invoke(\n { ...call, args, type: 'tool_call', stepId, turn },\n config\n );\n if (\n (isBaseMessage(output) && output._getType() === 'tool') ||\n isCommand(output)\n ) {\n return output;\n } else {\n return new ToolMessage({\n name: tool.name,\n content:\n typeof output === 'string' ? output : JSON.stringify(output),\n tool_call_id: call.id!,\n });\n }\n } catch (_e: unknown) {\n const e = _e as Error;\n if (!this.handleToolErrors) {\n throw e;\n }\n if (isGraphInterrupt(e)) {\n throw e;\n }\n this.errorHandler?.(\n {\n error: e,\n id: call.id!,\n name: call.name,\n input: call.args,\n },\n config.metadata\n );\n return new ToolMessage({\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\n if (!outputs.some(isCommand)) {\n return (Array.isArray(input) ? outputs : { messages: outputs }) as T;\n }\n\n const combinedOutputs = outputs.map((output) => {\n if (isCommand(output)) {\n return output;\n }\n return Array.isArray(input) ? [output] : { messages: [output] };\n });\n return combinedOutputs as T;\n }\n}\n\nfunction areToolCallsInvoked(\n message: AIMessage,\n invokedToolIds?: Set<string>\n): boolean {\n if (!invokedToolIds || invokedToolIds.size === 0) return false;\n return (\n message.tool_calls?.every(\n (toolCall) => toolCall.id != null && invokedToolIds.has(toolCall.id)\n ) ?? false\n );\n}\n\nexport function toolsCondition<T extends string>(\n state: BaseMessage[] | typeof MessagesAnnotation.State,\n toolNode: T,\n invokedToolIds?: Set<string>\n): T | typeof END {\n const message: AIMessage = Array.isArray(state)\n ? state[state.length - 1]\n : state.messages[state.messages.length - 1];\n\n if (\n 'tool_calls' in message &&\n (message.tool_calls?.length ?? 0) > 0 &&\n !areToolCallsInvoked(message, invokedToolIds)\n ) {\n return toolNode;\n } else {\n return END;\n }\n}\n"],"names":["RunnableCallable","isBaseMessage","isCommand","ToolMessage","isGraphInterrupt","END"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ToolNode.cjs","sources":["../../../src/tools/ToolNode.ts"],"sourcesContent":["import { ToolCall } from '@langchain/core/messages/tool';\nimport {\n ToolMessage,\n isAIMessage,\n isBaseMessage,\n} from '@langchain/core/messages';\nimport {\n END,\n Send,\n Command,\n isCommand,\n isGraphInterrupt,\n MessagesAnnotation,\n} from '@langchain/langgraph';\nimport type {\n RunnableConfig,\n RunnableToolLike,\n} from '@langchain/core/runnables';\nimport type { BaseMessage, AIMessage } from '@langchain/core/messages';\nimport type { StructuredToolInterface } from '@langchain/core/tools';\nimport type * as t from '@/types';\nimport { RunnableCallable } from '@/utils';\n\n/**\n * Helper to check if a value is a Send object\n */\nfunction isSend(value: unknown): value is Send {\n return value instanceof Send;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport class ToolNode<T = any> extends RunnableCallable<T, T> {\n tools: t.GenericTool[];\n private toolMap: Map<string, StructuredToolInterface | RunnableToolLike>;\n private loadRuntimeTools?: t.ToolRefGenerator;\n handleToolErrors = true;\n trace = false;\n toolCallStepIds?: Map<string, string>;\n errorHandler?: t.ToolNodeConstructorParams['errorHandler'];\n private toolUsageCount: Map<string, number>;\n\n constructor({\n tools,\n toolMap,\n name,\n tags,\n errorHandler,\n toolCallStepIds,\n handleToolErrors,\n loadRuntimeTools,\n }: t.ToolNodeConstructorParams) {\n super({ name, tags, func: (input, config) => this.run(input, config) });\n this.tools = tools;\n this.toolMap = toolMap ?? new Map(tools.map((tool) => [tool.name, tool]));\n this.toolCallStepIds = toolCallStepIds;\n this.handleToolErrors = handleToolErrors ?? this.handleToolErrors;\n this.loadRuntimeTools = loadRuntimeTools;\n this.errorHandler = errorHandler;\n this.toolUsageCount = new Map<string, number>();\n }\n\n /**\n * Returns a snapshot of the current tool usage counts.\n * @returns A ReadonlyMap where keys are tool names and values are their usage counts.\n */\n public getToolUsageCounts(): ReadonlyMap<string, number> {\n return new Map(this.toolUsageCount); // Return a copy\n }\n\n /**\n * Runs a single tool call with error handling\n */\n protected async runTool(\n call: ToolCall,\n config: RunnableConfig\n ): Promise<BaseMessage | Command> {\n const tool = this.toolMap.get(call.name);\n try {\n if (tool === undefined) {\n throw new Error(`Tool \"${call.name}\" not found.`);\n }\n const turn = this.toolUsageCount.get(call.name) ?? 0;\n this.toolUsageCount.set(call.name, turn + 1);\n const args = call.args;\n const stepId = this.toolCallStepIds?.get(call.id!);\n const output = await tool.invoke(\n { ...call, args, type: 'tool_call', stepId, turn },\n config\n );\n if (\n (isBaseMessage(output) && output._getType() === 'tool') ||\n isCommand(output)\n ) {\n return output;\n } else {\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 }\n } catch (_e: unknown) {\n const e = _e as Error;\n if (!this.handleToolErrors) {\n throw e;\n }\n if (isGraphInterrupt(e)) {\n throw e;\n }\n if (this.errorHandler) {\n try {\n await this.errorHandler(\n {\n error: e,\n id: call.id!,\n name: call.name,\n input: call.args,\n },\n config.metadata\n );\n } catch (handlerError) {\n // eslint-disable-next-line no-console\n console.error('Error in errorHandler:', {\n toolName: call.name,\n toolCallId: call.id,\n toolArgs: call.args,\n stepId: this.toolCallStepIds?.get(call.id!),\n turn: this.toolUsageCount.get(call.name),\n originalError: {\n message: e.message,\n stack: e.stack ?? undefined,\n },\n handlerError:\n handlerError instanceof Error\n ? {\n message: handlerError.message,\n stack: handlerError.stack ?? undefined,\n }\n : {\n message: String(handlerError),\n stack: undefined,\n },\n });\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: any, config: RunnableConfig): Promise<T> {\n let outputs: (BaseMessage | Command)[];\n\n if (this.isSendInput(input)) {\n outputs = [await this.runTool(input.lg_tool_call, config)];\n } else {\n let messages: BaseMessage[];\n if (Array.isArray(input)) {\n messages = input;\n } else if (this.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--) {\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 if (this.loadRuntimeTools) {\n const { tools, toolMap } = this.loadRuntimeTools(\n aiMessage.tool_calls ?? []\n );\n this.tools = tools;\n this.toolMap =\n toolMap ?? new Map(tools.map((tool) => [tool.name, tool]));\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 if (!outputs.some(isCommand)) {\n return (Array.isArray(input) ? outputs : { messages: outputs }) as T;\n }\n\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): send is 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 private isSendInput(input: unknown): input is { lg_tool_call: ToolCall } {\n return (\n typeof input === 'object' && input != null && 'lg_tool_call' in input\n );\n }\n\n private isMessagesState(\n input: unknown\n ): input is { messages: BaseMessage[] } {\n return (\n typeof input === 'object' &&\n input != null &&\n 'messages' in input &&\n Array.isArray((input as { messages: unknown }).messages) &&\n (input as { messages: unknown[] }).messages.every(isBaseMessage)\n );\n }\n}\n\nfunction areToolCallsInvoked(\n message: AIMessage,\n invokedToolIds?: Set<string>\n): boolean {\n if (!invokedToolIds || invokedToolIds.size === 0) return false;\n return (\n message.tool_calls?.every(\n (toolCall) => toolCall.id != null && invokedToolIds.has(toolCall.id)\n ) ?? false\n );\n}\n\nexport function toolsCondition<T extends string>(\n state: BaseMessage[] | typeof MessagesAnnotation.State,\n toolNode: T,\n invokedToolIds?: Set<string>\n): T | typeof END {\n const message: AIMessage = Array.isArray(state)\n ? state[state.length - 1]\n : state.messages[state.messages.length - 1];\n\n if (\n 'tool_calls' in message &&\n (message.tool_calls?.length ?? 0) > 0 &&\n !areToolCallsInvoked(message, invokedToolIds)\n ) {\n return toolNode;\n } else {\n return END;\n }\n}\n"],"names":["Send","RunnableCallable","isBaseMessage","isCommand","ToolMessage","isGraphInterrupt","messages","isAIMessage","Command","END"],"mappings":"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"}
|
|
@@ -1,17 +1,24 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { isBaseMessage, ToolMessage, isAIMessage } from '@langchain/core/messages';
|
|
2
|
+
import { isCommand, isGraphInterrupt, Command, END, Send } from '@langchain/langgraph';
|
|
3
3
|
import '../common/enum.mjs';
|
|
4
4
|
import 'nanoid';
|
|
5
5
|
import '../messages/core.mjs';
|
|
6
6
|
import { RunnableCallable } from '../utils/run.mjs';
|
|
7
7
|
import 'js-tiktoken/lite';
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Helper to check if a value is a Send object
|
|
11
|
+
*/
|
|
12
|
+
function isSend(value) {
|
|
13
|
+
return value instanceof Send;
|
|
14
|
+
}
|
|
9
15
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
10
16
|
class ToolNode extends RunnableCallable {
|
|
11
17
|
tools;
|
|
12
18
|
toolMap;
|
|
13
19
|
loadRuntimeTools;
|
|
14
20
|
handleToolErrors = true;
|
|
21
|
+
trace = false;
|
|
15
22
|
toolCallStepIds;
|
|
16
23
|
errorHandler;
|
|
17
24
|
toolUsageCount;
|
|
@@ -32,74 +39,166 @@ class ToolNode extends RunnableCallable {
|
|
|
32
39
|
getToolUsageCounts() {
|
|
33
40
|
return new Map(this.toolUsageCount); // Return a copy
|
|
34
41
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const
|
|
45
|
-
this.
|
|
46
|
-
|
|
42
|
+
/**
|
|
43
|
+
* Runs a single tool call with error handling
|
|
44
|
+
*/
|
|
45
|
+
async runTool(call, config) {
|
|
46
|
+
const tool = this.toolMap.get(call.name);
|
|
47
|
+
try {
|
|
48
|
+
if (tool === undefined) {
|
|
49
|
+
throw new Error(`Tool "${call.name}" not found.`);
|
|
50
|
+
}
|
|
51
|
+
const turn = this.toolUsageCount.get(call.name) ?? 0;
|
|
52
|
+
this.toolUsageCount.set(call.name, turn + 1);
|
|
53
|
+
const args = call.args;
|
|
54
|
+
const stepId = this.toolCallStepIds?.get(call.id);
|
|
55
|
+
const output = await tool.invoke({ ...call, args, type: 'tool_call', stepId, turn }, config);
|
|
56
|
+
if ((isBaseMessage(output) && output._getType() === 'tool') ||
|
|
57
|
+
isCommand(output)) {
|
|
58
|
+
return output;
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
return new ToolMessage({
|
|
62
|
+
status: 'success',
|
|
63
|
+
name: tool.name,
|
|
64
|
+
content: typeof output === 'string' ? output : JSON.stringify(output),
|
|
65
|
+
tool_call_id: call.id,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
47
68
|
}
|
|
48
|
-
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
69
|
+
catch (_e) {
|
|
70
|
+
const e = _e;
|
|
71
|
+
if (!this.handleToolErrors) {
|
|
72
|
+
throw e;
|
|
73
|
+
}
|
|
74
|
+
if (isGraphInterrupt(e)) {
|
|
75
|
+
throw e;
|
|
76
|
+
}
|
|
77
|
+
if (this.errorHandler) {
|
|
78
|
+
try {
|
|
79
|
+
await this.errorHandler({
|
|
80
|
+
error: e,
|
|
81
|
+
id: call.id,
|
|
82
|
+
name: call.name,
|
|
83
|
+
input: call.args,
|
|
84
|
+
}, config.metadata);
|
|
62
85
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
86
|
+
catch (handlerError) {
|
|
87
|
+
// eslint-disable-next-line no-console
|
|
88
|
+
console.error('Error in errorHandler:', {
|
|
89
|
+
toolName: call.name,
|
|
90
|
+
toolCallId: call.id,
|
|
91
|
+
toolArgs: call.args,
|
|
92
|
+
stepId: this.toolCallStepIds?.get(call.id),
|
|
93
|
+
turn: this.toolUsageCount.get(call.name),
|
|
94
|
+
originalError: {
|
|
95
|
+
message: e.message,
|
|
96
|
+
stack: e.stack ?? undefined,
|
|
97
|
+
},
|
|
98
|
+
handlerError: handlerError instanceof Error
|
|
99
|
+
? {
|
|
100
|
+
message: handlerError.message,
|
|
101
|
+
stack: handlerError.stack ?? undefined,
|
|
102
|
+
}
|
|
103
|
+
: {
|
|
104
|
+
message: String(handlerError),
|
|
105
|
+
stack: undefined,
|
|
106
|
+
},
|
|
68
107
|
});
|
|
69
108
|
}
|
|
70
109
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
110
|
+
return new ToolMessage({
|
|
111
|
+
status: 'error',
|
|
112
|
+
content: `Error: ${e.message}\n Please fix your mistakes.`,
|
|
113
|
+
name: call.name,
|
|
114
|
+
tool_call_id: call.id ?? '',
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
119
|
+
async run(input, config) {
|
|
120
|
+
let outputs;
|
|
121
|
+
if (this.isSendInput(input)) {
|
|
122
|
+
outputs = [await this.runTool(input.lg_tool_call, config)];
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
let messages;
|
|
126
|
+
if (Array.isArray(input)) {
|
|
127
|
+
messages = input;
|
|
128
|
+
}
|
|
129
|
+
else if (this.isMessagesState(input)) {
|
|
130
|
+
messages = input.messages;
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
throw new Error('ToolNode only accepts BaseMessage[] or { messages: BaseMessage[] } as input.');
|
|
134
|
+
}
|
|
135
|
+
const toolMessageIds = new Set(messages
|
|
136
|
+
.filter((msg) => msg._getType() === 'tool')
|
|
137
|
+
.map((msg) => msg.tool_call_id));
|
|
138
|
+
let aiMessage;
|
|
139
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
140
|
+
const message = messages[i];
|
|
141
|
+
if (isAIMessage(message)) {
|
|
142
|
+
aiMessage = message;
|
|
143
|
+
break;
|
|
78
144
|
}
|
|
79
|
-
this.errorHandler?.({
|
|
80
|
-
error: e,
|
|
81
|
-
id: call.id,
|
|
82
|
-
name: call.name,
|
|
83
|
-
input: call.args,
|
|
84
|
-
}, config.metadata);
|
|
85
|
-
return new ToolMessage({
|
|
86
|
-
content: `Error: ${e.message}\n Please fix your mistakes.`,
|
|
87
|
-
name: call.name,
|
|
88
|
-
tool_call_id: call.id ?? '',
|
|
89
|
-
});
|
|
90
145
|
}
|
|
91
|
-
|
|
146
|
+
if (aiMessage == null || !isAIMessage(aiMessage)) {
|
|
147
|
+
throw new Error('ToolNode only accepts AIMessages as input.');
|
|
148
|
+
}
|
|
149
|
+
if (this.loadRuntimeTools) {
|
|
150
|
+
const { tools, toolMap } = this.loadRuntimeTools(aiMessage.tool_calls ?? []);
|
|
151
|
+
this.tools = tools;
|
|
152
|
+
this.toolMap =
|
|
153
|
+
toolMap ?? new Map(tools.map((tool) => [tool.name, tool]));
|
|
154
|
+
}
|
|
155
|
+
outputs = await Promise.all(aiMessage.tool_calls
|
|
156
|
+
?.filter((call) => call.id == null || !toolMessageIds.has(call.id))
|
|
157
|
+
.map((call) => this.runTool(call, config)) ?? []);
|
|
158
|
+
}
|
|
92
159
|
if (!outputs.some(isCommand)) {
|
|
93
160
|
return (Array.isArray(input) ? outputs : { messages: outputs });
|
|
94
161
|
}
|
|
95
|
-
const combinedOutputs =
|
|
162
|
+
const combinedOutputs = [];
|
|
163
|
+
let parentCommand = null;
|
|
164
|
+
for (const output of outputs) {
|
|
96
165
|
if (isCommand(output)) {
|
|
97
|
-
|
|
166
|
+
if (output.graph === Command.PARENT &&
|
|
167
|
+
Array.isArray(output.goto) &&
|
|
168
|
+
output.goto.every((send) => isSend(send))) {
|
|
169
|
+
if (parentCommand) {
|
|
170
|
+
parentCommand.goto.push(...output.goto);
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
parentCommand = new Command({
|
|
174
|
+
graph: Command.PARENT,
|
|
175
|
+
goto: output.goto,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
combinedOutputs.push(output);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
combinedOutputs.push(Array.isArray(input) ? [output] : { messages: [output] });
|
|
98
185
|
}
|
|
99
|
-
|
|
100
|
-
|
|
186
|
+
}
|
|
187
|
+
if (parentCommand) {
|
|
188
|
+
combinedOutputs.push(parentCommand);
|
|
189
|
+
}
|
|
101
190
|
return combinedOutputs;
|
|
102
191
|
}
|
|
192
|
+
isSendInput(input) {
|
|
193
|
+
return (typeof input === 'object' && input != null && 'lg_tool_call' in input);
|
|
194
|
+
}
|
|
195
|
+
isMessagesState(input) {
|
|
196
|
+
return (typeof input === 'object' &&
|
|
197
|
+
input != null &&
|
|
198
|
+
'messages' in input &&
|
|
199
|
+
Array.isArray(input.messages) &&
|
|
200
|
+
input.messages.every(isBaseMessage));
|
|
201
|
+
}
|
|
103
202
|
}
|
|
104
203
|
function areToolCallsInvoked(message, invokedToolIds) {
|
|
105
204
|
if (!invokedToolIds || invokedToolIds.size === 0)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ToolNode.mjs","sources":["../../../src/tools/ToolNode.ts"],"sourcesContent":["import {\n END,\n MessagesAnnotation,\n isCommand,\n isGraphInterrupt,\n} from '@langchain/langgraph';\nimport { ToolMessage, isBaseMessage } from '@langchain/core/messages';\nimport type {\n RunnableConfig,\n RunnableToolLike,\n} from '@langchain/core/runnables';\nimport type { BaseMessage, AIMessage } from '@langchain/core/messages';\nimport type { StructuredToolInterface } from '@langchain/core/tools';\nimport type * as t from '@/types';\nimport { RunnableCallable } from '@/utils';\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport class ToolNode<T = any> extends RunnableCallable<T, T> {\n tools: t.GenericTool[];\n private toolMap: Map<string, StructuredToolInterface | RunnableToolLike>;\n private loadRuntimeTools?: t.ToolRefGenerator;\n handleToolErrors = true;\n toolCallStepIds?: Map<string, string>;\n errorHandler?: t.ToolNodeConstructorParams['errorHandler'];\n private toolUsageCount: Map<string, number>;\n\n constructor({\n tools,\n toolMap,\n name,\n tags,\n errorHandler,\n toolCallStepIds,\n handleToolErrors,\n loadRuntimeTools,\n }: t.ToolNodeConstructorParams) {\n super({ name, tags, func: (input, config) => this.run(input, config) });\n this.tools = tools;\n this.toolMap = toolMap ?? new Map(tools.map((tool) => [tool.name, tool]));\n this.toolCallStepIds = toolCallStepIds;\n this.handleToolErrors = handleToolErrors ?? this.handleToolErrors;\n this.loadRuntimeTools = loadRuntimeTools;\n this.errorHandler = errorHandler;\n this.toolUsageCount = new Map<string, number>();\n }\n\n /**\n * Returns a snapshot of the current tool usage counts.\n * @returns A ReadonlyMap where keys are tool names and values are their usage counts.\n */\n public getToolUsageCounts(): ReadonlyMap<string, number> {\n return new Map(this.toolUsageCount); // Return a copy\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n protected async run(input: any, config: RunnableConfig): Promise<T> {\n const message = Array.isArray(input)\n ? input[input.length - 1]\n : input.messages[input.messages.length - 1];\n\n if (message._getType() !== 'ai') {\n throw new Error('ToolNode only accepts AIMessages as input.');\n }\n\n if (this.loadRuntimeTools) {\n const { tools, toolMap } = this.loadRuntimeTools(\n (message as AIMessage).tool_calls ?? []\n );\n this.tools = tools;\n this.toolMap = toolMap ?? new Map(tools.map((tool) => [tool.name, tool]));\n }\n const outputs = await Promise.all(\n (message as AIMessage).tool_calls?.map(async (call) => {\n const tool = this.toolMap.get(call.name);\n try {\n if (tool === undefined) {\n throw new Error(`Tool \"${call.name}\" not found.`);\n }\n const turn = this.toolUsageCount.get(call.name) ?? 0;\n this.toolUsageCount.set(call.name, turn + 1);\n const args = call.args;\n const stepId = this.toolCallStepIds?.get(call.id!);\n const output = await tool.invoke(\n { ...call, args, type: 'tool_call', stepId, turn },\n config\n );\n if (\n (isBaseMessage(output) && output._getType() === 'tool') ||\n isCommand(output)\n ) {\n return output;\n } else {\n return new ToolMessage({\n name: tool.name,\n content:\n typeof output === 'string' ? output : JSON.stringify(output),\n tool_call_id: call.id!,\n });\n }\n } catch (_e: unknown) {\n const e = _e as Error;\n if (!this.handleToolErrors) {\n throw e;\n }\n if (isGraphInterrupt(e)) {\n throw e;\n }\n this.errorHandler?.(\n {\n error: e,\n id: call.id!,\n name: call.name,\n input: call.args,\n },\n config.metadata\n );\n return new ToolMessage({\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\n if (!outputs.some(isCommand)) {\n return (Array.isArray(input) ? outputs : { messages: outputs }) as T;\n }\n\n const combinedOutputs = outputs.map((output) => {\n if (isCommand(output)) {\n return output;\n }\n return Array.isArray(input) ? [output] : { messages: [output] };\n });\n return combinedOutputs as T;\n }\n}\n\nfunction areToolCallsInvoked(\n message: AIMessage,\n invokedToolIds?: Set<string>\n): boolean {\n if (!invokedToolIds || invokedToolIds.size === 0) return false;\n return (\n message.tool_calls?.every(\n (toolCall) => toolCall.id != null && invokedToolIds.has(toolCall.id)\n ) ?? false\n );\n}\n\nexport function toolsCondition<T extends string>(\n state: BaseMessage[] | typeof MessagesAnnotation.State,\n toolNode: T,\n invokedToolIds?: Set<string>\n): T | typeof END {\n const message: AIMessage = Array.isArray(state)\n ? state[state.length - 1]\n : state.messages[state.messages.length - 1];\n\n if (\n 'tool_calls' in message &&\n (message.tool_calls?.length ?? 0) > 0 &&\n !areToolCallsInvoked(message, invokedToolIds)\n ) {\n return toolNode;\n } else {\n return END;\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ToolNode.mjs","sources":["../../../src/tools/ToolNode.ts"],"sourcesContent":["import { ToolCall } from '@langchain/core/messages/tool';\nimport {\n ToolMessage,\n isAIMessage,\n isBaseMessage,\n} from '@langchain/core/messages';\nimport {\n END,\n Send,\n Command,\n isCommand,\n isGraphInterrupt,\n MessagesAnnotation,\n} from '@langchain/langgraph';\nimport type {\n RunnableConfig,\n RunnableToolLike,\n} from '@langchain/core/runnables';\nimport type { BaseMessage, AIMessage } from '@langchain/core/messages';\nimport type { StructuredToolInterface } from '@langchain/core/tools';\nimport type * as t from '@/types';\nimport { RunnableCallable } from '@/utils';\n\n/**\n * Helper to check if a value is a Send object\n */\nfunction isSend(value: unknown): value is Send {\n return value instanceof Send;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport class ToolNode<T = any> extends RunnableCallable<T, T> {\n tools: t.GenericTool[];\n private toolMap: Map<string, StructuredToolInterface | RunnableToolLike>;\n private loadRuntimeTools?: t.ToolRefGenerator;\n handleToolErrors = true;\n trace = false;\n toolCallStepIds?: Map<string, string>;\n errorHandler?: t.ToolNodeConstructorParams['errorHandler'];\n private toolUsageCount: Map<string, number>;\n\n constructor({\n tools,\n toolMap,\n name,\n tags,\n errorHandler,\n toolCallStepIds,\n handleToolErrors,\n loadRuntimeTools,\n }: t.ToolNodeConstructorParams) {\n super({ name, tags, func: (input, config) => this.run(input, config) });\n this.tools = tools;\n this.toolMap = toolMap ?? new Map(tools.map((tool) => [tool.name, tool]));\n this.toolCallStepIds = toolCallStepIds;\n this.handleToolErrors = handleToolErrors ?? this.handleToolErrors;\n this.loadRuntimeTools = loadRuntimeTools;\n this.errorHandler = errorHandler;\n this.toolUsageCount = new Map<string, number>();\n }\n\n /**\n * Returns a snapshot of the current tool usage counts.\n * @returns A ReadonlyMap where keys are tool names and values are their usage counts.\n */\n public getToolUsageCounts(): ReadonlyMap<string, number> {\n return new Map(this.toolUsageCount); // Return a copy\n }\n\n /**\n * Runs a single tool call with error handling\n */\n protected async runTool(\n call: ToolCall,\n config: RunnableConfig\n ): Promise<BaseMessage | Command> {\n const tool = this.toolMap.get(call.name);\n try {\n if (tool === undefined) {\n throw new Error(`Tool \"${call.name}\" not found.`);\n }\n const turn = this.toolUsageCount.get(call.name) ?? 0;\n this.toolUsageCount.set(call.name, turn + 1);\n const args = call.args;\n const stepId = this.toolCallStepIds?.get(call.id!);\n const output = await tool.invoke(\n { ...call, args, type: 'tool_call', stepId, turn },\n config\n );\n if (\n (isBaseMessage(output) && output._getType() === 'tool') ||\n isCommand(output)\n ) {\n return output;\n } else {\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 }\n } catch (_e: unknown) {\n const e = _e as Error;\n if (!this.handleToolErrors) {\n throw e;\n }\n if (isGraphInterrupt(e)) {\n throw e;\n }\n if (this.errorHandler) {\n try {\n await this.errorHandler(\n {\n error: e,\n id: call.id!,\n name: call.name,\n input: call.args,\n },\n config.metadata\n );\n } catch (handlerError) {\n // eslint-disable-next-line no-console\n console.error('Error in errorHandler:', {\n toolName: call.name,\n toolCallId: call.id,\n toolArgs: call.args,\n stepId: this.toolCallStepIds?.get(call.id!),\n turn: this.toolUsageCount.get(call.name),\n originalError: {\n message: e.message,\n stack: e.stack ?? undefined,\n },\n handlerError:\n handlerError instanceof Error\n ? {\n message: handlerError.message,\n stack: handlerError.stack ?? undefined,\n }\n : {\n message: String(handlerError),\n stack: undefined,\n },\n });\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: any, config: RunnableConfig): Promise<T> {\n let outputs: (BaseMessage | Command)[];\n\n if (this.isSendInput(input)) {\n outputs = [await this.runTool(input.lg_tool_call, config)];\n } else {\n let messages: BaseMessage[];\n if (Array.isArray(input)) {\n messages = input;\n } else if (this.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--) {\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 if (this.loadRuntimeTools) {\n const { tools, toolMap } = this.loadRuntimeTools(\n aiMessage.tool_calls ?? []\n );\n this.tools = tools;\n this.toolMap =\n toolMap ?? new Map(tools.map((tool) => [tool.name, tool]));\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 if (!outputs.some(isCommand)) {\n return (Array.isArray(input) ? outputs : { messages: outputs }) as T;\n }\n\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): send is 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 private isSendInput(input: unknown): input is { lg_tool_call: ToolCall } {\n return (\n typeof input === 'object' && input != null && 'lg_tool_call' in input\n );\n }\n\n private isMessagesState(\n input: unknown\n ): input is { messages: BaseMessage[] } {\n return (\n typeof input === 'object' &&\n input != null &&\n 'messages' in input &&\n Array.isArray((input as { messages: unknown }).messages) &&\n (input as { messages: unknown[] }).messages.every(isBaseMessage)\n );\n }\n}\n\nfunction areToolCallsInvoked(\n message: AIMessage,\n invokedToolIds?: Set<string>\n): boolean {\n if (!invokedToolIds || invokedToolIds.size === 0) return false;\n return (\n message.tool_calls?.every(\n (toolCall) => toolCall.id != null && invokedToolIds.has(toolCall.id)\n ) ?? false\n );\n}\n\nexport function toolsCondition<T extends string>(\n state: BaseMessage[] | typeof MessagesAnnotation.State,\n toolNode: T,\n invokedToolIds?: Set<string>\n): T | typeof END {\n const message: AIMessage = Array.isArray(state)\n ? state[state.length - 1]\n : state.messages[state.messages.length - 1];\n\n if (\n 'tool_calls' in message &&\n (message.tool_calls?.length ?? 0) > 0 &&\n !areToolCallsInvoked(message, invokedToolIds)\n ) {\n return toolNode;\n } else {\n return END;\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ToolCall } from '@langchain/core/messages/tool';
|
|
2
|
+
import { END, Command, MessagesAnnotation } from '@langchain/langgraph';
|
|
2
3
|
import type { RunnableConfig } from '@langchain/core/runnables';
|
|
3
4
|
import type { BaseMessage } from '@langchain/core/messages';
|
|
4
5
|
import type * as t from '@/types';
|
|
@@ -8,6 +9,7 @@ export declare class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
8
9
|
private toolMap;
|
|
9
10
|
private loadRuntimeTools?;
|
|
10
11
|
handleToolErrors: boolean;
|
|
12
|
+
trace: boolean;
|
|
11
13
|
toolCallStepIds?: Map<string, string>;
|
|
12
14
|
errorHandler?: t.ToolNodeConstructorParams['errorHandler'];
|
|
13
15
|
private toolUsageCount;
|
|
@@ -17,6 +19,12 @@ export declare class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
17
19
|
* @returns A ReadonlyMap where keys are tool names and values are their usage counts.
|
|
18
20
|
*/
|
|
19
21
|
getToolUsageCounts(): ReadonlyMap<string, number>;
|
|
22
|
+
/**
|
|
23
|
+
* Runs a single tool call with error handling
|
|
24
|
+
*/
|
|
25
|
+
protected runTool(call: ToolCall, config: RunnableConfig): Promise<BaseMessage | Command>;
|
|
20
26
|
protected run(input: any, config: RunnableConfig): Promise<T>;
|
|
27
|
+
private isSendInput;
|
|
28
|
+
private isMessagesState;
|
|
21
29
|
}
|
|
22
30
|
export declare function toolsCondition<T extends string>(state: BaseMessage[] | typeof MessagesAnnotation.State, toolNode: T, invokedToolIds?: Set<string>): T | typeof END;
|
|
@@ -24,7 +24,7 @@ export type ToolNodeOptions = {
|
|
|
24
24
|
handleToolErrors?: boolean;
|
|
25
25
|
loadRuntimeTools?: ToolRefGenerator;
|
|
26
26
|
toolCallStepIds?: Map<string, string>;
|
|
27
|
-
errorHandler?: (data: ToolErrorData, metadata?: Record<string, unknown>) => void
|
|
27
|
+
errorHandler?: (data: ToolErrorData, metadata?: Record<string, unknown>) => Promise<void>;
|
|
28
28
|
};
|
|
29
29
|
export type ToolNodeConstructorParams = ToolRefs & ToolNodeOptions;
|
|
30
30
|
export type ToolEndEvent = {
|
package/package.json
CHANGED
package/src/tools/ToolNode.ts
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
|
+
import { ToolCall } from '@langchain/core/messages/tool';
|
|
2
|
+
import {
|
|
3
|
+
ToolMessage,
|
|
4
|
+
isAIMessage,
|
|
5
|
+
isBaseMessage,
|
|
6
|
+
} from '@langchain/core/messages';
|
|
1
7
|
import {
|
|
2
8
|
END,
|
|
3
|
-
|
|
9
|
+
Send,
|
|
10
|
+
Command,
|
|
4
11
|
isCommand,
|
|
5
12
|
isGraphInterrupt,
|
|
13
|
+
MessagesAnnotation,
|
|
6
14
|
} from '@langchain/langgraph';
|
|
7
|
-
import { ToolMessage, isBaseMessage } from '@langchain/core/messages';
|
|
8
15
|
import type {
|
|
9
16
|
RunnableConfig,
|
|
10
17
|
RunnableToolLike,
|
|
@@ -14,12 +21,20 @@ import type { StructuredToolInterface } from '@langchain/core/tools';
|
|
|
14
21
|
import type * as t from '@/types';
|
|
15
22
|
import { RunnableCallable } from '@/utils';
|
|
16
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Helper to check if a value is a Send object
|
|
26
|
+
*/
|
|
27
|
+
function isSend(value: unknown): value is Send {
|
|
28
|
+
return value instanceof Send;
|
|
29
|
+
}
|
|
30
|
+
|
|
17
31
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
18
32
|
export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
19
33
|
tools: t.GenericTool[];
|
|
20
34
|
private toolMap: Map<string, StructuredToolInterface | RunnableToolLike>;
|
|
21
35
|
private loadRuntimeTools?: t.ToolRefGenerator;
|
|
22
36
|
handleToolErrors = true;
|
|
37
|
+
trace = false;
|
|
23
38
|
toolCallStepIds?: Map<string, string>;
|
|
24
39
|
errorHandler?: t.ToolNodeConstructorParams['errorHandler'];
|
|
25
40
|
private toolUsageCount: Map<string, number>;
|
|
@@ -52,60 +67,50 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
52
67
|
return new Map(this.toolUsageCount); // Return a copy
|
|
53
68
|
}
|
|
54
69
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
70
|
+
/**
|
|
71
|
+
* Runs a single tool call with error handling
|
|
72
|
+
*/
|
|
73
|
+
protected async runTool(
|
|
74
|
+
call: ToolCall,
|
|
75
|
+
config: RunnableConfig
|
|
76
|
+
): Promise<BaseMessage | Command> {
|
|
77
|
+
const tool = this.toolMap.get(call.name);
|
|
78
|
+
try {
|
|
79
|
+
if (tool === undefined) {
|
|
80
|
+
throw new Error(`Tool "${call.name}" not found.`);
|
|
81
|
+
}
|
|
82
|
+
const turn = this.toolUsageCount.get(call.name) ?? 0;
|
|
83
|
+
this.toolUsageCount.set(call.name, turn + 1);
|
|
84
|
+
const args = call.args;
|
|
85
|
+
const stepId = this.toolCallStepIds?.get(call.id!);
|
|
86
|
+
const output = await tool.invoke(
|
|
87
|
+
{ ...call, args, type: 'tool_call', stepId, turn },
|
|
88
|
+
config
|
|
68
89
|
);
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
90
|
+
if (
|
|
91
|
+
(isBaseMessage(output) && output._getType() === 'tool') ||
|
|
92
|
+
isCommand(output)
|
|
93
|
+
) {
|
|
94
|
+
return output;
|
|
95
|
+
} else {
|
|
96
|
+
return new ToolMessage({
|
|
97
|
+
status: 'success',
|
|
98
|
+
name: tool.name,
|
|
99
|
+
content: typeof output === 'string' ? output : JSON.stringify(output),
|
|
100
|
+
tool_call_id: call.id!,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
} catch (_e: unknown) {
|
|
104
|
+
const e = _e as Error;
|
|
105
|
+
if (!this.handleToolErrors) {
|
|
106
|
+
throw e;
|
|
107
|
+
}
|
|
108
|
+
if (isGraphInterrupt(e)) {
|
|
109
|
+
throw e;
|
|
110
|
+
}
|
|
111
|
+
if (this.errorHandler) {
|
|
75
112
|
try {
|
|
76
|
-
|
|
77
|
-
throw new Error(`Tool "${call.name}" not found.`);
|
|
78
|
-
}
|
|
79
|
-
const turn = this.toolUsageCount.get(call.name) ?? 0;
|
|
80
|
-
this.toolUsageCount.set(call.name, turn + 1);
|
|
81
|
-
const args = call.args;
|
|
82
|
-
const stepId = this.toolCallStepIds?.get(call.id!);
|
|
83
|
-
const output = await tool.invoke(
|
|
84
|
-
{ ...call, args, type: 'tool_call', stepId, turn },
|
|
85
|
-
config
|
|
86
|
-
);
|
|
87
|
-
if (
|
|
88
|
-
(isBaseMessage(output) && output._getType() === 'tool') ||
|
|
89
|
-
isCommand(output)
|
|
90
|
-
) {
|
|
91
|
-
return output;
|
|
92
|
-
} else {
|
|
93
|
-
return new ToolMessage({
|
|
94
|
-
name: tool.name,
|
|
95
|
-
content:
|
|
96
|
-
typeof output === 'string' ? output : JSON.stringify(output),
|
|
97
|
-
tool_call_id: call.id!,
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
} catch (_e: unknown) {
|
|
101
|
-
const e = _e as Error;
|
|
102
|
-
if (!this.handleToolErrors) {
|
|
103
|
-
throw e;
|
|
104
|
-
}
|
|
105
|
-
if (isGraphInterrupt(e)) {
|
|
106
|
-
throw e;
|
|
107
|
-
}
|
|
108
|
-
this.errorHandler?.(
|
|
113
|
+
await this.errorHandler(
|
|
109
114
|
{
|
|
110
115
|
error: e,
|
|
111
116
|
id: call.id!,
|
|
@@ -114,27 +119,153 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
114
119
|
},
|
|
115
120
|
config.metadata
|
|
116
121
|
);
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
122
|
+
} catch (handlerError) {
|
|
123
|
+
// eslint-disable-next-line no-console
|
|
124
|
+
console.error('Error in errorHandler:', {
|
|
125
|
+
toolName: call.name,
|
|
126
|
+
toolCallId: call.id,
|
|
127
|
+
toolArgs: call.args,
|
|
128
|
+
stepId: this.toolCallStepIds?.get(call.id!),
|
|
129
|
+
turn: this.toolUsageCount.get(call.name),
|
|
130
|
+
originalError: {
|
|
131
|
+
message: e.message,
|
|
132
|
+
stack: e.stack ?? undefined,
|
|
133
|
+
},
|
|
134
|
+
handlerError:
|
|
135
|
+
handlerError instanceof Error
|
|
136
|
+
? {
|
|
137
|
+
message: handlerError.message,
|
|
138
|
+
stack: handlerError.stack ?? undefined,
|
|
139
|
+
}
|
|
140
|
+
: {
|
|
141
|
+
message: String(handlerError),
|
|
142
|
+
stack: undefined,
|
|
143
|
+
},
|
|
121
144
|
});
|
|
122
145
|
}
|
|
123
|
-
}
|
|
124
|
-
|
|
146
|
+
}
|
|
147
|
+
return new ToolMessage({
|
|
148
|
+
status: 'error',
|
|
149
|
+
content: `Error: ${e.message}\n Please fix your mistakes.`,
|
|
150
|
+
name: call.name,
|
|
151
|
+
tool_call_id: call.id ?? '',
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
157
|
+
protected async run(input: any, config: RunnableConfig): Promise<T> {
|
|
158
|
+
let outputs: (BaseMessage | Command)[];
|
|
159
|
+
|
|
160
|
+
if (this.isSendInput(input)) {
|
|
161
|
+
outputs = [await this.runTool(input.lg_tool_call, config)];
|
|
162
|
+
} else {
|
|
163
|
+
let messages: BaseMessage[];
|
|
164
|
+
if (Array.isArray(input)) {
|
|
165
|
+
messages = input;
|
|
166
|
+
} else if (this.isMessagesState(input)) {
|
|
167
|
+
messages = input.messages;
|
|
168
|
+
} else {
|
|
169
|
+
throw new Error(
|
|
170
|
+
'ToolNode only accepts BaseMessage[] or { messages: BaseMessage[] } as input.'
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const toolMessageIds: Set<string> = new Set(
|
|
175
|
+
messages
|
|
176
|
+
.filter((msg) => msg._getType() === 'tool')
|
|
177
|
+
.map((msg) => (msg as ToolMessage).tool_call_id)
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
let aiMessage: AIMessage | undefined;
|
|
181
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
182
|
+
const message = messages[i];
|
|
183
|
+
if (isAIMessage(message)) {
|
|
184
|
+
aiMessage = message;
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (aiMessage == null || !isAIMessage(aiMessage)) {
|
|
190
|
+
throw new Error('ToolNode only accepts AIMessages as input.');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (this.loadRuntimeTools) {
|
|
194
|
+
const { tools, toolMap } = this.loadRuntimeTools(
|
|
195
|
+
aiMessage.tool_calls ?? []
|
|
196
|
+
);
|
|
197
|
+
this.tools = tools;
|
|
198
|
+
this.toolMap =
|
|
199
|
+
toolMap ?? new Map(tools.map((tool) => [tool.name, tool]));
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
outputs = await Promise.all(
|
|
203
|
+
aiMessage.tool_calls
|
|
204
|
+
?.filter((call) => call.id == null || !toolMessageIds.has(call.id))
|
|
205
|
+
.map((call) => this.runTool(call, config)) ?? []
|
|
206
|
+
);
|
|
207
|
+
}
|
|
125
208
|
|
|
126
209
|
if (!outputs.some(isCommand)) {
|
|
127
210
|
return (Array.isArray(input) ? outputs : { messages: outputs }) as T;
|
|
128
211
|
}
|
|
129
212
|
|
|
130
|
-
const combinedOutputs
|
|
213
|
+
const combinedOutputs: (
|
|
214
|
+
| { messages: BaseMessage[] }
|
|
215
|
+
| BaseMessage[]
|
|
216
|
+
| Command
|
|
217
|
+
)[] = [];
|
|
218
|
+
let parentCommand: Command | null = null;
|
|
219
|
+
|
|
220
|
+
for (const output of outputs) {
|
|
131
221
|
if (isCommand(output)) {
|
|
132
|
-
|
|
222
|
+
if (
|
|
223
|
+
output.graph === Command.PARENT &&
|
|
224
|
+
Array.isArray(output.goto) &&
|
|
225
|
+
output.goto.every((send): send is Send => isSend(send))
|
|
226
|
+
) {
|
|
227
|
+
if (parentCommand) {
|
|
228
|
+
(parentCommand.goto as Send[]).push(...(output.goto as Send[]));
|
|
229
|
+
} else {
|
|
230
|
+
parentCommand = new Command({
|
|
231
|
+
graph: Command.PARENT,
|
|
232
|
+
goto: output.goto,
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
} else {
|
|
236
|
+
combinedOutputs.push(output);
|
|
237
|
+
}
|
|
238
|
+
} else {
|
|
239
|
+
combinedOutputs.push(
|
|
240
|
+
Array.isArray(input) ? [output] : { messages: [output] }
|
|
241
|
+
);
|
|
133
242
|
}
|
|
134
|
-
|
|
135
|
-
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (parentCommand) {
|
|
246
|
+
combinedOutputs.push(parentCommand);
|
|
247
|
+
}
|
|
248
|
+
|
|
136
249
|
return combinedOutputs as T;
|
|
137
250
|
}
|
|
251
|
+
|
|
252
|
+
private isSendInput(input: unknown): input is { lg_tool_call: ToolCall } {
|
|
253
|
+
return (
|
|
254
|
+
typeof input === 'object' && input != null && 'lg_tool_call' in input
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
private isMessagesState(
|
|
259
|
+
input: unknown
|
|
260
|
+
): input is { messages: BaseMessage[] } {
|
|
261
|
+
return (
|
|
262
|
+
typeof input === 'object' &&
|
|
263
|
+
input != null &&
|
|
264
|
+
'messages' in input &&
|
|
265
|
+
Array.isArray((input as { messages: unknown }).messages) &&
|
|
266
|
+
(input as { messages: unknown[] }).messages.every(isBaseMessage)
|
|
267
|
+
);
|
|
268
|
+
}
|
|
138
269
|
}
|
|
139
270
|
|
|
140
271
|
function areToolCallsInvoked(
|
package/src/types/tools.ts
CHANGED