@librechat/agents 3.1.2 → 3.1.22
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/agents/AgentContext.cjs +42 -10
- package/dist/cjs/agents/AgentContext.cjs.map +1 -1
- package/dist/cjs/common/enum.cjs +2 -0
- package/dist/cjs/common/enum.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +17 -0
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/main.cjs +9 -3
- package/dist/cjs/main.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +66 -5
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/schema.cjs +31 -0
- package/dist/cjs/tools/schema.cjs.map +1 -0
- package/dist/esm/agents/AgentContext.mjs +42 -10
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/common/enum.mjs +2 -0
- package/dist/esm/common/enum.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +17 -0
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/main.mjs +2 -0
- package/dist/esm/main.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +67 -6
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/schema.mjs +28 -0
- package/dist/esm/tools/schema.mjs.map +1 -0
- package/dist/types/agents/AgentContext.d.ts +13 -2
- package/dist/types/common/enum.d.ts +2 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/tools/ToolNode.d.ts +10 -1
- package/dist/types/tools/schema.d.ts +12 -0
- package/dist/types/types/graph.d.ts +6 -0
- package/dist/types/types/tools.d.ts +49 -0
- package/package.json +1 -1
- package/src/agents/AgentContext.ts +50 -9
- package/src/common/enum.ts +2 -0
- package/src/graphs/Graph.ts +22 -0
- package/src/index.ts +2 -0
- package/src/tools/ToolNode.ts +95 -15
- package/src/tools/schema.ts +37 -0
- package/src/types/graph.ts +6 -0
- package/src/types/tools.ts +52 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.mjs","sources":["../../../src/tools/schema.ts"],"sourcesContent":["import { tool, type StructuredToolInterface } from '@langchain/core/tools';\nimport type { LCTool } from '@/types';\n\n/**\n * Creates a schema-only tool for LLM binding in event-driven mode.\n * These tools have valid schemas for the LLM to understand but should\n * never be invoked directly - ToolNode handles execution via events.\n */\nexport function createSchemaOnlyTool(\n definition: LCTool\n): StructuredToolInterface {\n const { name, description, parameters, responseFormat } = definition;\n\n return tool(\n async () => {\n throw new Error(\n `Tool \"${name}\" should not be invoked directly in event-driven mode. ` +\n 'ToolNode should dispatch ON_TOOL_EXECUTE events instead.'\n );\n },\n {\n name,\n description: description ?? '',\n schema: parameters ?? { type: 'object', properties: {} },\n responseFormat: responseFormat ?? 'content_and_artifact',\n }\n );\n}\n\n/**\n * Creates schema-only tools for all definitions in an array.\n */\nexport function createSchemaOnlyTools(\n definitions: LCTool[]\n): StructuredToolInterface[] {\n return definitions.map((def) => createSchemaOnlyTool(def));\n}\n"],"names":[],"mappings":";;AAGA;;;;AAIG;AACG,SAAU,oBAAoB,CAClC,UAAkB,EAAA;IAElB,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,UAAU,EAAE,cAAc,EAAE,GAAG,UAAU;AAEpE,IAAA,OAAO,IAAI,CACT,YAAW;AACT,QAAA,MAAM,IAAI,KAAK,CACb,CAAA,MAAA,EAAS,IAAI,CAAyD,uDAAA,CAAA;AACpE,YAAA,0DAA0D,CAC7D;AACH,KAAC,EACD;QACE,IAAI;QACJ,WAAW,EAAE,WAAW,IAAI,EAAE;QAC9B,MAAM,EAAE,UAAU,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,EAAE,EAAE;QACxD,cAAc,EAAE,cAAc,IAAI,sBAAsB;AACzD,KAAA,CACF;AACH;AAEA;;AAEG;AACG,SAAU,qBAAqB,CACnC,WAAqB,EAAA;AAErB,IAAA,OAAO,WAAW,CAAC,GAAG,CAAC,CAAC,GAAG,KAAK,oBAAoB,CAAC,GAAG,CAAC,CAAC;AAC5D;;;;"}
|
|
@@ -45,6 +45,11 @@ export declare class AgentContext {
|
|
|
45
45
|
* Used for tool search and programmatic tool calling.
|
|
46
46
|
*/
|
|
47
47
|
toolRegistry?: t.LCToolRegistry;
|
|
48
|
+
/**
|
|
49
|
+
* Serializable tool definitions for event-driven execution.
|
|
50
|
+
* When provided, ToolNode operates in event-driven mode.
|
|
51
|
+
*/
|
|
52
|
+
toolDefinitions?: t.LCTool[];
|
|
48
53
|
/** Set of tool names discovered via tool search (to be loaded) */
|
|
49
54
|
discoveredToolNames: Set<string>;
|
|
50
55
|
/** Instructions for this agent */
|
|
@@ -81,7 +86,7 @@ export declare class AgentContext {
|
|
|
81
86
|
/** Names of sibling agents executing in parallel (empty if sequential) */
|
|
82
87
|
parallelSiblings: string[];
|
|
83
88
|
};
|
|
84
|
-
constructor({ agentId, name, provider, clientOptions, maxContextTokens, streamBuffer, tokenCounter, tools, toolMap, toolRegistry, instructions, additionalInstructions, reasoningKey, toolEnd, instructionTokens, useLegacyContent, }: {
|
|
89
|
+
constructor({ agentId, name, provider, clientOptions, maxContextTokens, streamBuffer, tokenCounter, tools, toolMap, toolRegistry, toolDefinitions, instructions, additionalInstructions, reasoningKey, toolEnd, instructionTokens, useLegacyContent, }: {
|
|
85
90
|
agentId: string;
|
|
86
91
|
name?: string;
|
|
87
92
|
provider: Providers;
|
|
@@ -92,6 +97,7 @@ export declare class AgentContext {
|
|
|
92
97
|
tools?: t.GraphTools;
|
|
93
98
|
toolMap?: t.ToolMap;
|
|
94
99
|
toolRegistry?: t.LCToolRegistry;
|
|
100
|
+
toolDefinitions?: t.LCTool[];
|
|
95
101
|
instructions?: string;
|
|
96
102
|
additionalInstructions?: string;
|
|
97
103
|
reasoningKey?: 'reasoning_content' | 'reasoning';
|
|
@@ -177,10 +183,15 @@ export declare class AgentContext {
|
|
|
177
183
|
markToolsAsDiscovered(toolNames: string[]): boolean;
|
|
178
184
|
/**
|
|
179
185
|
* Gets tools that should be bound to the LLM.
|
|
180
|
-
*
|
|
186
|
+
* In event-driven mode (toolDefinitions present, tools empty), creates schema-only tools.
|
|
187
|
+
* Otherwise filters tool instances based on:
|
|
181
188
|
* 1. Non-deferred tools with allowed_callers: ['direct']
|
|
182
189
|
* 2. Discovered tools (from tool search)
|
|
183
190
|
* @returns Array of tools to bind to model
|
|
184
191
|
*/
|
|
185
192
|
getToolsForBinding(): t.GraphTools | undefined;
|
|
193
|
+
/** Creates schema-only tools from toolDefinitions for event-driven mode */
|
|
194
|
+
private getEventDrivenToolsForBinding;
|
|
195
|
+
/** Filters tool instances for binding based on registry config */
|
|
196
|
+
private filterToolsForBinding;
|
|
186
197
|
}
|
|
@@ -17,6 +17,8 @@ export declare enum GraphEvents {
|
|
|
17
17
|
ON_MESSAGE_DELTA = "on_message_delta",
|
|
18
18
|
/** [Custom] Reasoning Delta events for messages */
|
|
19
19
|
ON_REASONING_DELTA = "on_reasoning_delta",
|
|
20
|
+
/** [Custom] Request to execute tools - dispatched by ToolNode, handled by host */
|
|
21
|
+
ON_TOOL_EXECUTE = "on_tool_execute",
|
|
20
22
|
/** Custom event, emitted by system */
|
|
21
23
|
ON_CUSTOM_EVENT = "on_custom_event",
|
|
22
24
|
/** Emitted when a chat model starts processing. */
|
package/dist/types/index.d.ts
CHANGED
|
@@ -8,6 +8,8 @@ export * from './tools/Calculator';
|
|
|
8
8
|
export * from './tools/CodeExecutor';
|
|
9
9
|
export * from './tools/ProgrammaticToolCalling';
|
|
10
10
|
export * from './tools/ToolSearch';
|
|
11
|
+
export * from './tools/ToolNode';
|
|
12
|
+
export * from './tools/schema';
|
|
11
13
|
export * from './tools/handlers';
|
|
12
14
|
export * from './tools/search';
|
|
13
15
|
export * from './common';
|
|
@@ -18,7 +18,11 @@ export declare class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
18
18
|
private programmaticCache?;
|
|
19
19
|
/** Reference to Graph's sessions map for automatic session injection */
|
|
20
20
|
private sessions?;
|
|
21
|
-
|
|
21
|
+
/** When true, dispatches ON_TOOL_EXECUTE events instead of invoking tools directly */
|
|
22
|
+
private eventDrivenMode;
|
|
23
|
+
/** Tool definitions for event-driven mode */
|
|
24
|
+
private toolDefinitions?;
|
|
25
|
+
constructor({ tools, toolMap, name, tags, errorHandler, toolCallStepIds, handleToolErrors, loadRuntimeTools, toolRegistry, sessions, eventDrivenMode, toolDefinitions, }: t.ToolNodeConstructorParams);
|
|
22
26
|
/**
|
|
23
27
|
* Returns cached programmatic tools, computing once on first access.
|
|
24
28
|
* Single iteration builds both toolMap and toolDefs simultaneously.
|
|
@@ -33,6 +37,11 @@ export declare class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
33
37
|
* Runs a single tool call with error handling
|
|
34
38
|
*/
|
|
35
39
|
protected runTool(call: ToolCall, config: RunnableConfig): Promise<BaseMessage | Command>;
|
|
40
|
+
/**
|
|
41
|
+
* Execute all tool calls via ON_TOOL_EXECUTE event dispatch.
|
|
42
|
+
* Used in event-driven mode where the host handles actual tool execution.
|
|
43
|
+
*/
|
|
44
|
+
private executeViaEvent;
|
|
36
45
|
protected run(input: any, config: RunnableConfig): Promise<T>;
|
|
37
46
|
private isSendInput;
|
|
38
47
|
private isMessagesState;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type StructuredToolInterface } from '@langchain/core/tools';
|
|
2
|
+
import type { LCTool } from '@/types';
|
|
3
|
+
/**
|
|
4
|
+
* Creates a schema-only tool for LLM binding in event-driven mode.
|
|
5
|
+
* These tools have valid schemas for the LLM to understand but should
|
|
6
|
+
* never be invoked directly - ToolNode handles execution via events.
|
|
7
|
+
*/
|
|
8
|
+
export declare function createSchemaOnlyTool(definition: LCTool): StructuredToolInterface;
|
|
9
|
+
/**
|
|
10
|
+
* Creates schema-only tools for all definitions in an array.
|
|
11
|
+
*/
|
|
12
|
+
export declare function createSchemaOnlyTools(definitions: LCTool[]): StructuredToolInterface[];
|
|
@@ -256,4 +256,10 @@ export interface AgentInputs {
|
|
|
256
256
|
* Maps tool name to LCTool definition.
|
|
257
257
|
*/
|
|
258
258
|
toolRegistry?: Map<string, LCTool>;
|
|
259
|
+
/**
|
|
260
|
+
* Serializable tool definitions for event-driven execution.
|
|
261
|
+
* When provided, ToolNode operates in event-driven mode, dispatching
|
|
262
|
+
* ON_TOOL_EXECUTE events instead of invoking tools directly.
|
|
263
|
+
*/
|
|
264
|
+
toolDefinitions?: LCTool[];
|
|
259
265
|
}
|
|
@@ -31,6 +31,10 @@ export type ToolNodeOptions = {
|
|
|
31
31
|
toolRegistry?: LCToolRegistry;
|
|
32
32
|
/** Reference to Graph's sessions map for automatic session injection */
|
|
33
33
|
sessions?: ToolSessionMap;
|
|
34
|
+
/** When true, dispatches ON_TOOL_EXECUTE events instead of invoking tools directly */
|
|
35
|
+
eventDrivenMode?: boolean;
|
|
36
|
+
/** Tool definitions for event-driven mode (used for context, not invocation) */
|
|
37
|
+
toolDefinitions?: Map<string, LCTool>;
|
|
34
38
|
};
|
|
35
39
|
export type ToolNodeConstructorParams = ToolRefs & ToolNodeOptions;
|
|
36
40
|
export type ToolEndEvent = {
|
|
@@ -96,6 +100,51 @@ export type LCTool = {
|
|
|
96
100
|
* Options: 'direct', 'code_execution'
|
|
97
101
|
*/
|
|
98
102
|
allowed_callers?: AllowedCaller[];
|
|
103
|
+
/** Response format for the tool output */
|
|
104
|
+
responseFormat?: 'content' | 'content_and_artifact';
|
|
105
|
+
/** Server name for MCP tools */
|
|
106
|
+
serverName?: string;
|
|
107
|
+
/** Tool type classification */
|
|
108
|
+
toolType?: 'builtin' | 'mcp' | 'action';
|
|
109
|
+
};
|
|
110
|
+
/** Single tool call within a batch request for event-driven execution */
|
|
111
|
+
export type ToolCallRequest = {
|
|
112
|
+
/** Tool call ID from the LLM */
|
|
113
|
+
id: string;
|
|
114
|
+
/** Tool name */
|
|
115
|
+
name: string;
|
|
116
|
+
/** Tool arguments */
|
|
117
|
+
args: Record<string, unknown>;
|
|
118
|
+
/** Step ID for tracking */
|
|
119
|
+
stepId?: string;
|
|
120
|
+
/** Usage turn count for this tool */
|
|
121
|
+
turn?: number;
|
|
122
|
+
};
|
|
123
|
+
/** Batch request containing ALL tool calls for a graph step */
|
|
124
|
+
export type ToolExecuteBatchRequest = {
|
|
125
|
+
/** All tool calls from the AIMessage */
|
|
126
|
+
toolCalls: ToolCallRequest[];
|
|
127
|
+
/** User ID for context */
|
|
128
|
+
userId?: string;
|
|
129
|
+
/** Agent ID for context */
|
|
130
|
+
agentId?: string;
|
|
131
|
+
/** Promise resolver - handler calls this with ALL results */
|
|
132
|
+
resolve: (results: ToolExecuteResult[]) => void;
|
|
133
|
+
/** Promise rejector - handler calls this on fatal error */
|
|
134
|
+
reject: (error: Error) => void;
|
|
135
|
+
};
|
|
136
|
+
/** Result for a single tool call in event-driven execution */
|
|
137
|
+
export type ToolExecuteResult = {
|
|
138
|
+
/** Matches ToolCallRequest.id */
|
|
139
|
+
toolCallId: string;
|
|
140
|
+
/** Tool output content */
|
|
141
|
+
content: string | unknown[];
|
|
142
|
+
/** Optional artifact (for content_and_artifact format) */
|
|
143
|
+
artifact?: unknown;
|
|
144
|
+
/** Execution status */
|
|
145
|
+
status: 'success' | 'error';
|
|
146
|
+
/** Error message if status is 'error' */
|
|
147
|
+
errorMessage?: string;
|
|
99
148
|
};
|
|
100
149
|
/** Map of tool names to tool definitions */
|
|
101
150
|
export type LCToolRegistry = Map<string, LCTool>;
|
package/package.json
CHANGED
|
@@ -10,6 +10,7 @@ import type {
|
|
|
10
10
|
import type { RunnableConfig, Runnable } from '@langchain/core/runnables';
|
|
11
11
|
import type * as t from '@/types';
|
|
12
12
|
import type { createPruneMessages } from '@/messages';
|
|
13
|
+
import { createSchemaOnlyTools } from '@/tools/schema';
|
|
13
14
|
import { ContentTypes, Providers } from '@/common';
|
|
14
15
|
import { toJsonSchema } from '@/utils/schema';
|
|
15
16
|
|
|
@@ -34,6 +35,7 @@ export class AgentContext {
|
|
|
34
35
|
toolMap,
|
|
35
36
|
toolEnd,
|
|
36
37
|
toolRegistry,
|
|
38
|
+
toolDefinitions,
|
|
37
39
|
instructions,
|
|
38
40
|
additional_instructions,
|
|
39
41
|
streamBuffer,
|
|
@@ -52,6 +54,7 @@ export class AgentContext {
|
|
|
52
54
|
tools,
|
|
53
55
|
toolMap,
|
|
54
56
|
toolRegistry,
|
|
57
|
+
toolDefinitions,
|
|
55
58
|
instructions,
|
|
56
59
|
additionalInstructions: additional_instructions,
|
|
57
60
|
reasoningKey,
|
|
@@ -118,6 +121,11 @@ export class AgentContext {
|
|
|
118
121
|
* Used for tool search and programmatic tool calling.
|
|
119
122
|
*/
|
|
120
123
|
toolRegistry?: t.LCToolRegistry;
|
|
124
|
+
/**
|
|
125
|
+
* Serializable tool definitions for event-driven execution.
|
|
126
|
+
* When provided, ToolNode operates in event-driven mode.
|
|
127
|
+
*/
|
|
128
|
+
toolDefinitions?: t.LCTool[];
|
|
121
129
|
/** Set of tool names discovered via tool search (to be loaded) */
|
|
122
130
|
discoveredToolNames: Set<string> = new Set();
|
|
123
131
|
/** Instructions for this agent */
|
|
@@ -171,6 +179,7 @@ export class AgentContext {
|
|
|
171
179
|
tools,
|
|
172
180
|
toolMap,
|
|
173
181
|
toolRegistry,
|
|
182
|
+
toolDefinitions,
|
|
174
183
|
instructions,
|
|
175
184
|
additionalInstructions,
|
|
176
185
|
reasoningKey,
|
|
@@ -188,6 +197,7 @@ export class AgentContext {
|
|
|
188
197
|
tools?: t.GraphTools;
|
|
189
198
|
toolMap?: t.ToolMap;
|
|
190
199
|
toolRegistry?: t.LCToolRegistry;
|
|
200
|
+
toolDefinitions?: t.LCTool[];
|
|
191
201
|
instructions?: string;
|
|
192
202
|
additionalInstructions?: string;
|
|
193
203
|
reasoningKey?: 'reasoning_content' | 'reasoning';
|
|
@@ -205,6 +215,7 @@ export class AgentContext {
|
|
|
205
215
|
this.tools = tools;
|
|
206
216
|
this.toolMap = toolMap;
|
|
207
217
|
this.toolRegistry = toolRegistry;
|
|
218
|
+
this.toolDefinitions = toolDefinitions;
|
|
208
219
|
this.instructions = instructions;
|
|
209
220
|
this.additionalInstructions = additionalInstructions;
|
|
210
221
|
if (reasoningKey) {
|
|
@@ -561,40 +572,70 @@ export class AgentContext {
|
|
|
561
572
|
|
|
562
573
|
/**
|
|
563
574
|
* Gets tools that should be bound to the LLM.
|
|
564
|
-
*
|
|
575
|
+
* In event-driven mode (toolDefinitions present, tools empty), creates schema-only tools.
|
|
576
|
+
* Otherwise filters tool instances based on:
|
|
565
577
|
* 1. Non-deferred tools with allowed_callers: ['direct']
|
|
566
578
|
* 2. Discovered tools (from tool search)
|
|
567
579
|
* @returns Array of tools to bind to model
|
|
568
580
|
*/
|
|
569
581
|
getToolsForBinding(): t.GraphTools | undefined {
|
|
582
|
+
/** Event-driven mode: create schema-only tools from definitions */
|
|
583
|
+
if (this.toolDefinitions && this.toolDefinitions.length > 0) {
|
|
584
|
+
return this.getEventDrivenToolsForBinding();
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
/** Traditional mode: filter actual tool instances */
|
|
570
588
|
if (!this.tools || !this.toolRegistry) {
|
|
571
589
|
return this.tools;
|
|
572
590
|
}
|
|
573
591
|
|
|
574
|
-
|
|
592
|
+
return this.filterToolsForBinding(this.tools);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
/** Creates schema-only tools from toolDefinitions for event-driven mode */
|
|
596
|
+
private getEventDrivenToolsForBinding(): t.GraphTools {
|
|
597
|
+
if (!this.toolDefinitions) {
|
|
598
|
+
return [];
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
const defsToInclude = this.toolDefinitions.filter((def) => {
|
|
602
|
+
const allowedCallers = def.allowed_callers ?? ['direct'];
|
|
603
|
+
if (!allowedCallers.includes('direct')) {
|
|
604
|
+
return false;
|
|
605
|
+
}
|
|
606
|
+
if (
|
|
607
|
+
def.defer_loading === true &&
|
|
608
|
+
!this.discoveredToolNames.has(def.name)
|
|
609
|
+
) {
|
|
610
|
+
return false;
|
|
611
|
+
}
|
|
612
|
+
return true;
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
return createSchemaOnlyTools(defsToInclude) as t.GraphTools;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
/** Filters tool instances for binding based on registry config */
|
|
619
|
+
private filterToolsForBinding(tools: t.GraphTools): t.GraphTools {
|
|
620
|
+
return tools.filter((tool) => {
|
|
575
621
|
if (!('name' in tool)) {
|
|
576
|
-
return true;
|
|
622
|
+
return true;
|
|
577
623
|
}
|
|
578
624
|
|
|
579
625
|
const toolDef = this.toolRegistry?.get(tool.name);
|
|
580
626
|
if (!toolDef) {
|
|
581
|
-
return true;
|
|
627
|
+
return true;
|
|
582
628
|
}
|
|
583
629
|
|
|
584
|
-
// Check if discovered (overrides defer_loading)
|
|
585
630
|
if (this.discoveredToolNames.has(tool.name)) {
|
|
586
|
-
// Discovered tools must still have allowed_callers: ['direct']
|
|
587
631
|
const allowedCallers = toolDef.allowed_callers ?? ['direct'];
|
|
588
632
|
return allowedCallers.includes('direct');
|
|
589
633
|
}
|
|
590
634
|
|
|
591
|
-
// Not discovered: must be direct-callable AND not deferred
|
|
592
635
|
const allowedCallers = toolDef.allowed_callers ?? ['direct'];
|
|
593
636
|
return (
|
|
594
637
|
allowedCallers.includes('direct') && toolDef.defer_loading !== true
|
|
595
638
|
);
|
|
596
639
|
});
|
|
597
|
-
|
|
598
|
-
return toolsToInclude;
|
|
599
640
|
}
|
|
600
641
|
}
|
package/src/common/enum.ts
CHANGED
|
@@ -19,6 +19,8 @@ export enum GraphEvents {
|
|
|
19
19
|
ON_MESSAGE_DELTA = 'on_message_delta',
|
|
20
20
|
/** [Custom] Reasoning Delta events for messages */
|
|
21
21
|
ON_REASONING_DELTA = 'on_reasoning_delta',
|
|
22
|
+
/** [Custom] Request to execute tools - dispatched by ToolNode, handled by host */
|
|
23
|
+
ON_TOOL_EXECUTE = 'on_tool_execute',
|
|
22
24
|
|
|
23
25
|
/* Official Events */
|
|
24
26
|
|
package/src/graphs/Graph.ts
CHANGED
|
@@ -60,6 +60,7 @@ import { getChatModelClass, manualToolStreamProviders } from '@/llm/providers';
|
|
|
60
60
|
import { ToolNode as CustomToolNode, toolsCondition } from '@/tools/ToolNode';
|
|
61
61
|
import { ChatOpenAI, AzureChatOpenAI } from '@/llm/openai';
|
|
62
62
|
import { safeDispatchCustomEvent } from '@/utils/events';
|
|
63
|
+
import { createSchemaOnlyTools } from '@/tools/schema';
|
|
63
64
|
import { AgentContext } from '@/agents/AgentContext';
|
|
64
65
|
import { createFakeStreamingLLM } from '@/llm/fake';
|
|
65
66
|
import { HandlerRegistry } from '@/events';
|
|
@@ -453,6 +454,27 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
453
454
|
currentToolMap?: t.ToolMap;
|
|
454
455
|
agentContext?: AgentContext;
|
|
455
456
|
}): CustomToolNode<t.BaseGraphState> | ToolNode<t.BaseGraphState> {
|
|
457
|
+
const toolDefinitions = agentContext?.toolDefinitions;
|
|
458
|
+
const eventDrivenMode =
|
|
459
|
+
toolDefinitions != null && toolDefinitions.length > 0;
|
|
460
|
+
|
|
461
|
+
if (eventDrivenMode) {
|
|
462
|
+
const schemaTools = createSchemaOnlyTools(toolDefinitions);
|
|
463
|
+
const toolDefMap = new Map(toolDefinitions.map((def) => [def.name, def]));
|
|
464
|
+
|
|
465
|
+
return new CustomToolNode<t.BaseGraphState>({
|
|
466
|
+
tools: schemaTools as t.GenericTool[],
|
|
467
|
+
toolMap: new Map(schemaTools.map((tool) => [tool.name, tool])),
|
|
468
|
+
toolCallStepIds: this.toolCallStepIds,
|
|
469
|
+
errorHandler: (data, metadata) =>
|
|
470
|
+
StandardGraph.handleToolCallErrorStatic(this, data, metadata),
|
|
471
|
+
toolRegistry: agentContext?.toolRegistry,
|
|
472
|
+
sessions: this.sessions,
|
|
473
|
+
eventDrivenMode: true,
|
|
474
|
+
toolDefinitions: toolDefMap,
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
|
|
456
478
|
return new CustomToolNode<t.BaseGraphState>({
|
|
457
479
|
tools: (currentTools as t.GenericTool[] | undefined) ?? [],
|
|
458
480
|
toolMap: currentToolMap,
|
package/src/index.ts
CHANGED
|
@@ -13,6 +13,8 @@ export * from './tools/Calculator';
|
|
|
13
13
|
export * from './tools/CodeExecutor';
|
|
14
14
|
export * from './tools/ProgrammaticToolCalling';
|
|
15
15
|
export * from './tools/ToolSearch';
|
|
16
|
+
export * from './tools/ToolNode';
|
|
17
|
+
export * from './tools/schema';
|
|
16
18
|
export * from './tools/handlers';
|
|
17
19
|
export * from './tools/search';
|
|
18
20
|
|
package/src/tools/ToolNode.ts
CHANGED
|
@@ -20,7 +20,8 @@ import type { BaseMessage, AIMessage } from '@langchain/core/messages';
|
|
|
20
20
|
import type { StructuredToolInterface } from '@langchain/core/tools';
|
|
21
21
|
import type * as t from '@/types';
|
|
22
22
|
import { RunnableCallable } from '@/utils';
|
|
23
|
-
import {
|
|
23
|
+
import { safeDispatchCustomEvent } from '@/utils/events';
|
|
24
|
+
import { Constants, GraphEvents } from '@/common';
|
|
24
25
|
|
|
25
26
|
/**
|
|
26
27
|
* Helper to check if a value is a Send object
|
|
@@ -44,6 +45,10 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
44
45
|
private programmaticCache?: t.ProgrammaticCache;
|
|
45
46
|
/** Reference to Graph's sessions map for automatic session injection */
|
|
46
47
|
private sessions?: t.ToolSessionMap;
|
|
48
|
+
/** When true, dispatches ON_TOOL_EXECUTE events instead of invoking tools directly */
|
|
49
|
+
private eventDrivenMode: boolean = false;
|
|
50
|
+
/** Tool definitions for event-driven mode */
|
|
51
|
+
private toolDefinitions?: Map<string, t.LCTool>;
|
|
47
52
|
|
|
48
53
|
constructor({
|
|
49
54
|
tools,
|
|
@@ -56,6 +61,8 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
56
61
|
loadRuntimeTools,
|
|
57
62
|
toolRegistry,
|
|
58
63
|
sessions,
|
|
64
|
+
eventDrivenMode,
|
|
65
|
+
toolDefinitions,
|
|
59
66
|
}: t.ToolNodeConstructorParams) {
|
|
60
67
|
super({ name, tags, func: (input, config) => this.run(input, config) });
|
|
61
68
|
this.toolMap = toolMap ?? new Map(tools.map((tool) => [tool.name, tool]));
|
|
@@ -66,6 +73,8 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
66
73
|
this.toolUsageCount = new Map<string, number>();
|
|
67
74
|
this.toolRegistry = toolRegistry;
|
|
68
75
|
this.sessions = sessions;
|
|
76
|
+
this.eventDrivenMode = eventDrivenMode ?? false;
|
|
77
|
+
this.toolDefinitions = toolDefinitions;
|
|
69
78
|
}
|
|
70
79
|
|
|
71
80
|
/**
|
|
@@ -243,11 +252,77 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
243
252
|
}
|
|
244
253
|
}
|
|
245
254
|
|
|
255
|
+
/**
|
|
256
|
+
* Execute all tool calls via ON_TOOL_EXECUTE event dispatch.
|
|
257
|
+
* Used in event-driven mode where the host handles actual tool execution.
|
|
258
|
+
*/
|
|
259
|
+
private async executeViaEvent(
|
|
260
|
+
toolCalls: ToolCall[],
|
|
261
|
+
config: RunnableConfig,
|
|
262
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
263
|
+
input: any
|
|
264
|
+
): Promise<T> {
|
|
265
|
+
const requests: t.ToolCallRequest[] = toolCalls.map((call) => {
|
|
266
|
+
const turn = this.toolUsageCount.get(call.name) ?? 0;
|
|
267
|
+
this.toolUsageCount.set(call.name, turn + 1);
|
|
268
|
+
return {
|
|
269
|
+
id: call.id!,
|
|
270
|
+
name: call.name,
|
|
271
|
+
args: call.args as Record<string, unknown>,
|
|
272
|
+
stepId: this.toolCallStepIds?.get(call.id!),
|
|
273
|
+
turn,
|
|
274
|
+
};
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
const results = await new Promise<t.ToolExecuteResult[]>(
|
|
278
|
+
(resolve, reject) => {
|
|
279
|
+
const request: t.ToolExecuteBatchRequest = {
|
|
280
|
+
toolCalls: requests,
|
|
281
|
+
userId: config.configurable?.user_id as string | undefined,
|
|
282
|
+
agentId: config.configurable?.agent_id as string | undefined,
|
|
283
|
+
resolve,
|
|
284
|
+
reject,
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
safeDispatchCustomEvent(GraphEvents.ON_TOOL_EXECUTE, request, config);
|
|
288
|
+
}
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
const outputs: ToolMessage[] = results.map((result) => {
|
|
292
|
+
const toolName =
|
|
293
|
+
requests.find((r) => r.id === result.toolCallId)?.name ?? 'unknown';
|
|
294
|
+
|
|
295
|
+
if (result.status === 'error') {
|
|
296
|
+
return new ToolMessage({
|
|
297
|
+
status: 'error',
|
|
298
|
+
content: `Error: ${result.errorMessage ?? 'Unknown error'}\n Please fix your mistakes.`,
|
|
299
|
+
name: toolName,
|
|
300
|
+
tool_call_id: result.toolCallId,
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return new ToolMessage({
|
|
305
|
+
status: 'success',
|
|
306
|
+
content:
|
|
307
|
+
typeof result.content === 'string'
|
|
308
|
+
? result.content
|
|
309
|
+
: JSON.stringify(result.content),
|
|
310
|
+
name: toolName,
|
|
311
|
+
tool_call_id: result.toolCallId,
|
|
312
|
+
});
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
return (Array.isArray(input) ? outputs : { messages: outputs }) as T;
|
|
316
|
+
}
|
|
317
|
+
|
|
246
318
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
247
319
|
protected async run(input: any, config: RunnableConfig): Promise<T> {
|
|
248
320
|
let outputs: (BaseMessage | Command)[];
|
|
249
321
|
|
|
250
322
|
if (this.isSendInput(input)) {
|
|
323
|
+
if (this.eventDrivenMode) {
|
|
324
|
+
return this.executeViaEvent([input.lg_tool_call], config, input);
|
|
325
|
+
}
|
|
251
326
|
outputs = [await this.runTool(input.lg_tool_call, config)];
|
|
252
327
|
} else {
|
|
253
328
|
let messages: BaseMessage[];
|
|
@@ -289,21 +364,26 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
289
364
|
this.programmaticCache = undefined; // Invalidate cache on toolMap change
|
|
290
365
|
}
|
|
291
366
|
|
|
367
|
+
const filteredCalls =
|
|
368
|
+
aiMessage.tool_calls?.filter((call) => {
|
|
369
|
+
/**
|
|
370
|
+
* Filter out:
|
|
371
|
+
* 1. Already processed tool calls (present in toolMessageIds)
|
|
372
|
+
* 2. Server tool calls (e.g., web_search with IDs starting with 'srvtoolu_')
|
|
373
|
+
* which are executed by the provider's API and don't require invocation
|
|
374
|
+
*/
|
|
375
|
+
return (
|
|
376
|
+
(call.id == null || !toolMessageIds.has(call.id)) &&
|
|
377
|
+
!(call.id?.startsWith('srvtoolu_') ?? false)
|
|
378
|
+
);
|
|
379
|
+
}) ?? [];
|
|
380
|
+
|
|
381
|
+
if (this.eventDrivenMode && filteredCalls.length > 0) {
|
|
382
|
+
return this.executeViaEvent(filteredCalls, config, input);
|
|
383
|
+
}
|
|
384
|
+
|
|
292
385
|
outputs = await Promise.all(
|
|
293
|
-
|
|
294
|
-
?.filter((call) => {
|
|
295
|
-
/**
|
|
296
|
-
* Filter out:
|
|
297
|
-
* 1. Already processed tool calls (present in toolMessageIds)
|
|
298
|
-
* 2. Server tool calls (e.g., web_search with IDs starting with 'srvtoolu_')
|
|
299
|
-
* which are executed by the provider's API and don't require invocation
|
|
300
|
-
*/
|
|
301
|
-
return (
|
|
302
|
-
(call.id == null || !toolMessageIds.has(call.id)) &&
|
|
303
|
-
!(call.id?.startsWith('srvtoolu_') ?? false)
|
|
304
|
-
);
|
|
305
|
-
})
|
|
306
|
-
.map((call) => this.runTool(call, config)) ?? []
|
|
386
|
+
filteredCalls.map((call) => this.runTool(call, config))
|
|
307
387
|
);
|
|
308
388
|
}
|
|
309
389
|
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { tool, type StructuredToolInterface } from '@langchain/core/tools';
|
|
2
|
+
import type { LCTool } from '@/types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Creates a schema-only tool for LLM binding in event-driven mode.
|
|
6
|
+
* These tools have valid schemas for the LLM to understand but should
|
|
7
|
+
* never be invoked directly - ToolNode handles execution via events.
|
|
8
|
+
*/
|
|
9
|
+
export function createSchemaOnlyTool(
|
|
10
|
+
definition: LCTool
|
|
11
|
+
): StructuredToolInterface {
|
|
12
|
+
const { name, description, parameters, responseFormat } = definition;
|
|
13
|
+
|
|
14
|
+
return tool(
|
|
15
|
+
async () => {
|
|
16
|
+
throw new Error(
|
|
17
|
+
`Tool "${name}" should not be invoked directly in event-driven mode. ` +
|
|
18
|
+
'ToolNode should dispatch ON_TOOL_EXECUTE events instead.'
|
|
19
|
+
);
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
name,
|
|
23
|
+
description: description ?? '',
|
|
24
|
+
schema: parameters ?? { type: 'object', properties: {} },
|
|
25
|
+
responseFormat: responseFormat ?? 'content_and_artifact',
|
|
26
|
+
}
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Creates schema-only tools for all definitions in an array.
|
|
32
|
+
*/
|
|
33
|
+
export function createSchemaOnlyTools(
|
|
34
|
+
definitions: LCTool[]
|
|
35
|
+
): StructuredToolInterface[] {
|
|
36
|
+
return definitions.map((def) => createSchemaOnlyTool(def));
|
|
37
|
+
}
|
package/src/types/graph.ts
CHANGED
|
@@ -377,4 +377,10 @@ export interface AgentInputs {
|
|
|
377
377
|
* Maps tool name to LCTool definition.
|
|
378
378
|
*/
|
|
379
379
|
toolRegistry?: Map<string, LCTool>;
|
|
380
|
+
/**
|
|
381
|
+
* Serializable tool definitions for event-driven execution.
|
|
382
|
+
* When provided, ToolNode operates in event-driven mode, dispatching
|
|
383
|
+
* ON_TOOL_EXECUTE events instead of invoking tools directly.
|
|
384
|
+
*/
|
|
385
|
+
toolDefinitions?: LCTool[];
|
|
380
386
|
}
|
package/src/types/tools.ts
CHANGED
|
@@ -41,6 +41,10 @@ export type ToolNodeOptions = {
|
|
|
41
41
|
toolRegistry?: LCToolRegistry;
|
|
42
42
|
/** Reference to Graph's sessions map for automatic session injection */
|
|
43
43
|
sessions?: ToolSessionMap;
|
|
44
|
+
/** When true, dispatches ON_TOOL_EXECUTE events instead of invoking tools directly */
|
|
45
|
+
eventDrivenMode?: boolean;
|
|
46
|
+
/** Tool definitions for event-driven mode (used for context, not invocation) */
|
|
47
|
+
toolDefinitions?: Map<string, LCTool>;
|
|
44
48
|
};
|
|
45
49
|
|
|
46
50
|
export type ToolNodeConstructorParams = ToolRefs & ToolNodeOptions;
|
|
@@ -125,6 +129,54 @@ export type LCTool = {
|
|
|
125
129
|
* Options: 'direct', 'code_execution'
|
|
126
130
|
*/
|
|
127
131
|
allowed_callers?: AllowedCaller[];
|
|
132
|
+
/** Response format for the tool output */
|
|
133
|
+
responseFormat?: 'content' | 'content_and_artifact';
|
|
134
|
+
/** Server name for MCP tools */
|
|
135
|
+
serverName?: string;
|
|
136
|
+
/** Tool type classification */
|
|
137
|
+
toolType?: 'builtin' | 'mcp' | 'action';
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
/** Single tool call within a batch request for event-driven execution */
|
|
141
|
+
export type ToolCallRequest = {
|
|
142
|
+
/** Tool call ID from the LLM */
|
|
143
|
+
id: string;
|
|
144
|
+
/** Tool name */
|
|
145
|
+
name: string;
|
|
146
|
+
/** Tool arguments */
|
|
147
|
+
args: Record<string, unknown>;
|
|
148
|
+
/** Step ID for tracking */
|
|
149
|
+
stepId?: string;
|
|
150
|
+
/** Usage turn count for this tool */
|
|
151
|
+
turn?: number;
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
/** Batch request containing ALL tool calls for a graph step */
|
|
155
|
+
export type ToolExecuteBatchRequest = {
|
|
156
|
+
/** All tool calls from the AIMessage */
|
|
157
|
+
toolCalls: ToolCallRequest[];
|
|
158
|
+
/** User ID for context */
|
|
159
|
+
userId?: string;
|
|
160
|
+
/** Agent ID for context */
|
|
161
|
+
agentId?: string;
|
|
162
|
+
/** Promise resolver - handler calls this with ALL results */
|
|
163
|
+
resolve: (results: ToolExecuteResult[]) => void;
|
|
164
|
+
/** Promise rejector - handler calls this on fatal error */
|
|
165
|
+
reject: (error: Error) => void;
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
/** Result for a single tool call in event-driven execution */
|
|
169
|
+
export type ToolExecuteResult = {
|
|
170
|
+
/** Matches ToolCallRequest.id */
|
|
171
|
+
toolCallId: string;
|
|
172
|
+
/** Tool output content */
|
|
173
|
+
content: string | unknown[];
|
|
174
|
+
/** Optional artifact (for content_and_artifact format) */
|
|
175
|
+
artifact?: unknown;
|
|
176
|
+
/** Execution status */
|
|
177
|
+
status: 'success' | 'error';
|
|
178
|
+
/** Error message if status is 'error' */
|
|
179
|
+
errorMessage?: string;
|
|
128
180
|
};
|
|
129
181
|
|
|
130
182
|
/** Map of tool names to tool definitions */
|