@librechat/agents 3.0.65 → 3.0.67
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 +12 -10
- package/dist/cjs/agents/AgentContext.cjs.map +1 -1
- package/dist/cjs/common/enum.cjs +1 -1
- package/dist/cjs/common/enum.cjs.map +1 -1
- package/dist/cjs/graphs/MultiAgentGraph.cjs +22 -7
- package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
- package/dist/cjs/main.cjs +8 -7
- package/dist/cjs/main.cjs.map +1 -1
- package/dist/cjs/messages/tools.cjs +2 -2
- package/dist/cjs/messages/tools.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +19 -4
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/{ToolSearchRegex.cjs → ToolSearch.cjs} +154 -66
- package/dist/cjs/tools/ToolSearch.cjs.map +1 -0
- package/dist/esm/agents/AgentContext.mjs +12 -10
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/common/enum.mjs +1 -1
- package/dist/esm/common/enum.mjs.map +1 -1
- package/dist/esm/graphs/MultiAgentGraph.mjs +22 -7
- package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
- package/dist/esm/main.mjs +1 -1
- package/dist/esm/messages/tools.mjs +2 -2
- package/dist/esm/messages/tools.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +19 -4
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/{ToolSearchRegex.mjs → ToolSearch.mjs} +153 -66
- package/dist/esm/tools/ToolSearch.mjs.map +1 -0
- package/dist/types/agents/AgentContext.d.ts +7 -3
- package/dist/types/common/enum.d.ts +1 -1
- package/dist/types/graphs/MultiAgentGraph.d.ts +3 -3
- package/dist/types/index.d.ts +1 -1
- package/dist/types/tools/{ToolSearchRegex.d.ts → ToolSearch.d.ts} +33 -23
- package/dist/types/types/tools.d.ts +5 -1
- package/package.json +2 -2
- package/src/agents/AgentContext.ts +20 -12
- package/src/common/enum.ts +1 -1
- package/src/graphs/MultiAgentGraph.ts +29 -8
- package/src/index.ts +1 -1
- package/src/messages/__tests__/tools.test.ts +21 -21
- package/src/messages/tools.ts +2 -2
- package/src/scripts/programmatic_exec_agent.ts +4 -4
- package/src/scripts/{tool_search_regex.ts → tool_search.ts} +5 -5
- package/src/tools/ToolNode.ts +23 -5
- package/src/tools/{ToolSearchRegex.ts → ToolSearch.ts} +195 -74
- package/src/tools/__tests__/{ToolSearchRegex.integration.test.ts → ToolSearch.integration.test.ts} +6 -6
- package/src/tools/__tests__/{ToolSearchRegex.test.ts → ToolSearch.test.ts} +212 -3
- package/src/types/tools.ts +6 -1
- package/dist/cjs/tools/ToolSearchRegex.cjs.map +0 -1
- package/dist/esm/tools/ToolSearchRegex.mjs.map +0 -1
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { DynamicStructuredTool } from '@langchain/core/tools';
|
|
3
3
|
import type * as t from '@/types';
|
|
4
|
-
|
|
4
|
+
/** Zod schema type for tool search parameters */
|
|
5
|
+
type ToolSearchSchema = z.ZodObject<{
|
|
5
6
|
query: z.ZodString;
|
|
6
|
-
fields: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodEnum<[
|
|
7
|
+
fields: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodEnum<['name', 'description', 'parameters']>>>>;
|
|
7
8
|
max_results: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
8
|
-
}, "strip", z.ZodTypeAny, {
|
|
9
|
-
query: string;
|
|
10
|
-
fields: ("name" | "description" | "parameters")[];
|
|
11
|
-
max_results: number;
|
|
12
|
-
}, {
|
|
13
|
-
query: string;
|
|
14
|
-
fields?: ("name" | "description" | "parameters")[] | undefined;
|
|
15
|
-
max_results?: number | undefined;
|
|
16
9
|
}>;
|
|
10
|
+
/**
|
|
11
|
+
* Creates the Zod schema with dynamic query description based on mode.
|
|
12
|
+
* @param mode - The search mode determining query interpretation
|
|
13
|
+
* @returns Zod schema for tool search parameters
|
|
14
|
+
*/
|
|
15
|
+
declare function createToolSearchSchema(mode: t.ToolSearchMode): ToolSearchSchema;
|
|
17
16
|
/**
|
|
18
17
|
* Escapes special regex characters in a string to use as a literal pattern.
|
|
19
18
|
* @param pattern - The string to escape
|
|
@@ -50,11 +49,25 @@ declare function sanitizeRegex(pattern: string): {
|
|
|
50
49
|
wasEscaped: boolean;
|
|
51
50
|
};
|
|
52
51
|
/**
|
|
53
|
-
*
|
|
52
|
+
* Performs safe local substring search without regex.
|
|
53
|
+
* Uses case-insensitive String.includes() for complete safety against ReDoS.
|
|
54
|
+
* @param tools - Array of tool metadata to search
|
|
55
|
+
* @param query - The search term (treated as literal substring)
|
|
56
|
+
* @param fields - Which fields to search
|
|
57
|
+
* @param maxResults - Maximum results to return
|
|
58
|
+
* @returns Search response with matching tools
|
|
59
|
+
*/
|
|
60
|
+
declare function performLocalSearch(tools: t.ToolMetadata[], query: string, fields: string[], maxResults: number): t.ToolSearchResponse;
|
|
61
|
+
/**
|
|
62
|
+
* Creates a Tool Search tool for discovering tools from a large registry.
|
|
54
63
|
*
|
|
55
64
|
* This tool enables AI agents to dynamically discover tools from a large library
|
|
56
65
|
* without loading all tool definitions into the LLM context window. The agent
|
|
57
|
-
* can search for relevant tools on-demand
|
|
66
|
+
* can search for relevant tools on-demand.
|
|
67
|
+
*
|
|
68
|
+
* **Modes:**
|
|
69
|
+
* - `code_interpreter` (default): Uses external sandbox for regex search. Safer for complex patterns.
|
|
70
|
+
* - `local`: Uses safe substring matching locally. No network call, faster, completely safe from ReDoS.
|
|
58
71
|
*
|
|
59
72
|
* The tool registry can be provided either:
|
|
60
73
|
* 1. At initialization time via params.toolRegistry
|
|
@@ -64,17 +77,14 @@ declare function sanitizeRegex(pattern: string): {
|
|
|
64
77
|
* @returns A LangChain DynamicStructuredTool for tool searching
|
|
65
78
|
*
|
|
66
79
|
* @example
|
|
67
|
-
* // Option 1:
|
|
68
|
-
* const tool =
|
|
69
|
-
* await tool.invoke({ query: 'expense' });
|
|
80
|
+
* // Option 1: Code interpreter mode (regex via sandbox)
|
|
81
|
+
* const tool = createToolSearch({ apiKey, toolRegistry });
|
|
82
|
+
* await tool.invoke({ query: 'expense.*report' });
|
|
70
83
|
*
|
|
71
84
|
* @example
|
|
72
|
-
* // Option 2:
|
|
73
|
-
* const tool =
|
|
74
|
-
* await tool.invoke(
|
|
75
|
-
* { query: 'expense' },
|
|
76
|
-
* { configurable: { toolRegistry, onlyDeferred: true } }
|
|
77
|
-
* );
|
|
85
|
+
* // Option 2: Local mode (safe substring search, no API key needed)
|
|
86
|
+
* const tool = createToolSearch({ mode: 'local', toolRegistry });
|
|
87
|
+
* await tool.invoke({ query: 'expense' });
|
|
78
88
|
*/
|
|
79
|
-
declare function
|
|
80
|
-
export {
|
|
89
|
+
declare function createToolSearch(initParams?: t.ToolSearchParams): DynamicStructuredTool<ReturnType<typeof createToolSearchSchema>>;
|
|
90
|
+
export { createToolSearch, performLocalSearch, sanitizeRegex, escapeRegexSpecialChars, isDangerousPattern, countNestedGroups, hasNestedQuantifiers, };
|
|
@@ -99,12 +99,16 @@ export type ProgrammaticCache = {
|
|
|
99
99
|
toolMap: ToolMap;
|
|
100
100
|
toolDefs: LCTool[];
|
|
101
101
|
};
|
|
102
|
+
/** Search mode: code_interpreter uses external sandbox, local uses safe substring matching */
|
|
103
|
+
export type ToolSearchMode = 'code_interpreter' | 'local';
|
|
102
104
|
/** Parameters for creating a Tool Search Regex tool */
|
|
103
|
-
export type
|
|
105
|
+
export type ToolSearchParams = {
|
|
104
106
|
apiKey?: string;
|
|
105
107
|
toolRegistry?: LCToolRegistry;
|
|
106
108
|
onlyDeferred?: boolean;
|
|
107
109
|
baseUrl?: string;
|
|
110
|
+
/** Search mode: 'code_interpreter' (default) uses sandbox for regex, 'local' uses safe substring matching */
|
|
111
|
+
mode?: ToolSearchMode;
|
|
108
112
|
[key: string]: unknown;
|
|
109
113
|
};
|
|
110
114
|
/** Simplified tool metadata for search purposes */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@librechat/agents",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.67",
|
|
4
4
|
"main": "./dist/cjs/main.cjs",
|
|
5
5
|
"module": "./dist/esm/main.mjs",
|
|
6
6
|
"types": "./dist/types/index.d.ts",
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"memory": "node -r dotenv/config --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/memory.ts --provider 'openAI' --name 'Jo' --location 'New York, NY'",
|
|
58
58
|
"tool": "node --trace-warnings -r dotenv/config --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/tools.ts --provider 'openrouter' --name 'Jo' --location 'New York, NY'",
|
|
59
59
|
"search": "node -r dotenv/config --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/search.ts --provider 'bedrock' --name 'Jo' --location 'New York, NY'",
|
|
60
|
-
"
|
|
60
|
+
"tool_search": "node -r dotenv/config --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/tool_search.ts",
|
|
61
61
|
"programmatic_exec": "node -r dotenv/config --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/programmatic_exec.ts",
|
|
62
62
|
"code_exec_ptc": "node -r dotenv/config --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/code_exec_ptc.ts --provider 'openAI' --name 'Jo' --location 'New York, NY'",
|
|
63
63
|
"programmatic_exec_agent": "node -r dotenv/config --loader ./tsconfig-paths-bootstrap.mjs --experimental-specifier-resolution=node ./src/scripts/programmatic_exec_agent.ts --provider 'openAI' --name 'Jo' --location 'New York, NY'",
|
|
@@ -151,10 +151,13 @@ export class AgentContext {
|
|
|
151
151
|
useLegacyContent: boolean = false;
|
|
152
152
|
/**
|
|
153
153
|
* Handoff context when this agent receives control via handoff.
|
|
154
|
-
* Contains source
|
|
154
|
+
* Contains source and parallel execution info for system message context.
|
|
155
155
|
*/
|
|
156
156
|
handoffContext?: {
|
|
157
|
+
/** Source agent that transferred control */
|
|
157
158
|
sourceAgentName: string;
|
|
159
|
+
/** Names of sibling agents executing in parallel (empty if sequential) */
|
|
160
|
+
parallelSiblings: string[];
|
|
158
161
|
};
|
|
159
162
|
|
|
160
163
|
constructor({
|
|
@@ -345,22 +348,26 @@ export class AgentContext {
|
|
|
345
348
|
* This helps the agent understand its role in the multi-agent workflow.
|
|
346
349
|
*/
|
|
347
350
|
private buildIdentityPreamble(): string {
|
|
348
|
-
/** Only include preamble if we have handoff context (indicates multi-agent workflow) */
|
|
349
351
|
if (!this.handoffContext) return '';
|
|
350
352
|
|
|
351
|
-
/** Use name (falls back to agentId if not provided) */
|
|
352
353
|
const displayName = this.name ?? this.agentId;
|
|
354
|
+
const { sourceAgentName, parallelSiblings } = this.handoffContext;
|
|
355
|
+
const isParallel = parallelSiblings.length > 0;
|
|
353
356
|
|
|
354
357
|
const lines: string[] = [];
|
|
355
|
-
lines.push('## Agent
|
|
356
|
-
lines.push(
|
|
358
|
+
lines.push('## Multi-Agent Workflow');
|
|
359
|
+
lines.push(
|
|
360
|
+
`You are "${displayName}", transferred from "${sourceAgentName}".`
|
|
361
|
+
);
|
|
357
362
|
|
|
358
|
-
if (
|
|
359
|
-
lines.push(
|
|
360
|
-
`Control was transferred to you from the "${this.handoffContext.sourceAgentName}" agent.`
|
|
361
|
-
);
|
|
363
|
+
if (isParallel) {
|
|
364
|
+
lines.push(`Running in parallel with: ${parallelSiblings.join(', ')}.`);
|
|
362
365
|
}
|
|
363
366
|
|
|
367
|
+
lines.push(
|
|
368
|
+
'Execute only tasks relevant to your role. Routing is already handled if requested, unless you can route further.'
|
|
369
|
+
);
|
|
370
|
+
|
|
364
371
|
return lines.join('\n');
|
|
365
372
|
}
|
|
366
373
|
|
|
@@ -525,10 +532,11 @@ export class AgentContext {
|
|
|
525
532
|
* Sets the handoff context for this agent.
|
|
526
533
|
* Call this when the agent receives control via handoff from another agent.
|
|
527
534
|
* Marks system runnable as stale to include handoff context in system message.
|
|
528
|
-
* @param sourceAgentName -
|
|
535
|
+
* @param sourceAgentName - Name of the agent that transferred control
|
|
536
|
+
* @param parallelSiblings - Names of other agents executing in parallel with this one
|
|
529
537
|
*/
|
|
530
|
-
setHandoffContext(sourceAgentName: string): void {
|
|
531
|
-
this.handoffContext = { sourceAgentName };
|
|
538
|
+
setHandoffContext(sourceAgentName: string, parallelSiblings: string[]): void {
|
|
539
|
+
this.handoffContext = { sourceAgentName, parallelSiblings };
|
|
532
540
|
this.systemRunnableStale = true;
|
|
533
541
|
}
|
|
534
542
|
|
package/src/common/enum.ts
CHANGED
|
@@ -159,7 +159,7 @@ export enum Callback {
|
|
|
159
159
|
export enum Constants {
|
|
160
160
|
OFFICIAL_CODE_BASEURL = 'https://api.librechat.ai/v1',
|
|
161
161
|
EXECUTE_CODE = 'execute_code',
|
|
162
|
-
|
|
162
|
+
TOOL_SEARCH = 'tool_search',
|
|
163
163
|
PROGRAMMATIC_TOOL_CALLING = 'run_tools_with_code',
|
|
164
164
|
WEB_SEARCH = 'web_search',
|
|
165
165
|
CONTENT_AND_ARTIFACT = 'content_and_artifact',
|
|
@@ -497,15 +497,15 @@ export class MultiAgentGraph extends StandardGraph {
|
|
|
497
497
|
|
|
498
498
|
/**
|
|
499
499
|
* Detects if the current agent is receiving a handoff and processes the messages accordingly.
|
|
500
|
-
* Returns filtered messages with the transfer tool call/message removed, plus any instructions
|
|
501
|
-
*
|
|
500
|
+
* Returns filtered messages with the transfer tool call/message removed, plus any instructions,
|
|
501
|
+
* source agent, and parallel sibling information extracted from the transfer.
|
|
502
502
|
*
|
|
503
503
|
* Supports both single handoffs (last message is the transfer) and parallel handoffs
|
|
504
504
|
* (multiple transfer ToolMessages, need to find the one targeting this agent).
|
|
505
505
|
*
|
|
506
506
|
* @param messages - Current state messages
|
|
507
507
|
* @param agentId - The agent ID to check for handoff reception
|
|
508
|
-
* @returns Object with filtered messages, extracted instructions,
|
|
508
|
+
* @returns Object with filtered messages, extracted instructions, source agent, and parallel siblings
|
|
509
509
|
*/
|
|
510
510
|
private processHandoffReception(
|
|
511
511
|
messages: BaseMessage[],
|
|
@@ -514,6 +514,7 @@ export class MultiAgentGraph extends StandardGraph {
|
|
|
514
514
|
filteredMessages: BaseMessage[];
|
|
515
515
|
instructions: string | null;
|
|
516
516
|
sourceAgentName: string | null;
|
|
517
|
+
parallelSiblings: string[];
|
|
517
518
|
} | null {
|
|
518
519
|
if (messages.length === 0) return null;
|
|
519
520
|
|
|
@@ -575,6 +576,17 @@ export class MultiAgentGraph extends StandardGraph {
|
|
|
575
576
|
const sourceAgentName =
|
|
576
577
|
typeof handoffSourceName === 'string' ? handoffSourceName : null;
|
|
577
578
|
|
|
579
|
+
/** Extract parallel siblings (set by ToolNode for parallel handoffs) */
|
|
580
|
+
const rawSiblings = toolMessage.additional_kwargs.handoff_parallel_siblings;
|
|
581
|
+
const siblingIds: string[] = Array.isArray(rawSiblings)
|
|
582
|
+
? rawSiblings.filter((s): s is string => typeof s === 'string')
|
|
583
|
+
: [];
|
|
584
|
+
/** Convert IDs to display names */
|
|
585
|
+
const parallelSiblings = siblingIds.map((id) => {
|
|
586
|
+
const ctx = this.agentContexts.get(id);
|
|
587
|
+
return ctx?.name ?? id;
|
|
588
|
+
});
|
|
589
|
+
|
|
578
590
|
/** Get the tool_call_id to find and filter the AI message's tool call */
|
|
579
591
|
const toolCallId = toolMessage.tool_call_id;
|
|
580
592
|
|
|
@@ -648,7 +660,12 @@ export class MultiAgentGraph extends StandardGraph {
|
|
|
648
660
|
filteredMessages.push(msg);
|
|
649
661
|
}
|
|
650
662
|
|
|
651
|
-
return {
|
|
663
|
+
return {
|
|
664
|
+
filteredMessages,
|
|
665
|
+
instructions,
|
|
666
|
+
sourceAgentName,
|
|
667
|
+
parallelSiblings,
|
|
668
|
+
};
|
|
652
669
|
}
|
|
653
670
|
|
|
654
671
|
/**
|
|
@@ -736,12 +753,16 @@ export class MultiAgentGraph extends StandardGraph {
|
|
|
736
753
|
);
|
|
737
754
|
|
|
738
755
|
if (handoffContext !== null) {
|
|
739
|
-
const {
|
|
740
|
-
|
|
756
|
+
const {
|
|
757
|
+
filteredMessages,
|
|
758
|
+
instructions,
|
|
759
|
+
sourceAgentName,
|
|
760
|
+
parallelSiblings,
|
|
761
|
+
} = handoffContext;
|
|
741
762
|
|
|
742
763
|
/**
|
|
743
764
|
* Set handoff context on the receiving agent.
|
|
744
|
-
*
|
|
765
|
+
* Uses pre-computed graph position for depth and parallel info.
|
|
745
766
|
*/
|
|
746
767
|
const agentContext = this.agentContexts.get(agentId);
|
|
747
768
|
if (
|
|
@@ -749,7 +770,7 @@ export class MultiAgentGraph extends StandardGraph {
|
|
|
749
770
|
sourceAgentName != null &&
|
|
750
771
|
sourceAgentName !== ''
|
|
751
772
|
) {
|
|
752
|
-
agentContext.setHandoffContext(sourceAgentName);
|
|
773
|
+
agentContext.setHandoffContext(sourceAgentName, parallelSiblings);
|
|
753
774
|
}
|
|
754
775
|
|
|
755
776
|
/** Build messages for the receiving agent */
|
package/src/index.ts
CHANGED
|
@@ -12,7 +12,7 @@ export * from './graphs';
|
|
|
12
12
|
export * from './tools/Calculator';
|
|
13
13
|
export * from './tools/CodeExecutor';
|
|
14
14
|
export * from './tools/ProgrammaticToolCalling';
|
|
15
|
-
export * from './tools/
|
|
15
|
+
export * from './tools/ToolSearch';
|
|
16
16
|
export * from './tools/handlers';
|
|
17
17
|
export * from './tools/search';
|
|
18
18
|
|
|
@@ -41,7 +41,7 @@ describe('Tool Discovery Functions', () => {
|
|
|
41
41
|
return new ToolMessage({
|
|
42
42
|
content: `Found ${discoveredTools.length} tools`,
|
|
43
43
|
tool_call_id: toolCallId,
|
|
44
|
-
name: Constants.
|
|
44
|
+
name: Constants.TOOL_SEARCH,
|
|
45
45
|
artifact: {
|
|
46
46
|
tool_references: discoveredTools.map((name) => ({
|
|
47
47
|
tool_name: name,
|
|
@@ -79,7 +79,7 @@ describe('Tool Discovery Functions', () => {
|
|
|
79
79
|
createAIMessage('Searching...', [
|
|
80
80
|
{
|
|
81
81
|
id: 'call_1',
|
|
82
|
-
name: Constants.
|
|
82
|
+
name: Constants.TOOL_SEARCH,
|
|
83
83
|
args: { pattern: 'database' },
|
|
84
84
|
},
|
|
85
85
|
]),
|
|
@@ -97,12 +97,12 @@ describe('Tool Discovery Functions', () => {
|
|
|
97
97
|
createAIMessage('Searching...', [
|
|
98
98
|
{
|
|
99
99
|
id: 'call_1',
|
|
100
|
-
name: Constants.
|
|
100
|
+
name: Constants.TOOL_SEARCH,
|
|
101
101
|
args: { pattern: 'database' },
|
|
102
102
|
},
|
|
103
103
|
{
|
|
104
104
|
id: 'call_2',
|
|
105
|
-
name: Constants.
|
|
105
|
+
name: Constants.TOOL_SEARCH,
|
|
106
106
|
args: { pattern: 'file' },
|
|
107
107
|
},
|
|
108
108
|
]),
|
|
@@ -137,7 +137,7 @@ describe('Tool Discovery Functions', () => {
|
|
|
137
137
|
new ToolMessage({
|
|
138
138
|
content: 'Some result',
|
|
139
139
|
tool_call_id: 'orphan_call',
|
|
140
|
-
name: Constants.
|
|
140
|
+
name: Constants.TOOL_SEARCH,
|
|
141
141
|
}),
|
|
142
142
|
];
|
|
143
143
|
|
|
@@ -153,7 +153,7 @@ describe('Tool Discovery Functions', () => {
|
|
|
153
153
|
createAIMessage('Searching...', [
|
|
154
154
|
{
|
|
155
155
|
id: 'old_call',
|
|
156
|
-
name: Constants.
|
|
156
|
+
name: Constants.TOOL_SEARCH,
|
|
157
157
|
args: { pattern: 'old' },
|
|
158
158
|
},
|
|
159
159
|
]),
|
|
@@ -163,7 +163,7 @@ describe('Tool Discovery Functions', () => {
|
|
|
163
163
|
createAIMessage('Searching again...', [
|
|
164
164
|
{
|
|
165
165
|
id: 'new_call',
|
|
166
|
-
name: Constants.
|
|
166
|
+
name: Constants.TOOL_SEARCH,
|
|
167
167
|
args: { pattern: 'new' },
|
|
168
168
|
},
|
|
169
169
|
]),
|
|
@@ -182,7 +182,7 @@ describe('Tool Discovery Functions', () => {
|
|
|
182
182
|
createAIMessage('Working...', [
|
|
183
183
|
{
|
|
184
184
|
id: 'search_call',
|
|
185
|
-
name: Constants.
|
|
185
|
+
name: Constants.TOOL_SEARCH,
|
|
186
186
|
args: { pattern: 'test' },
|
|
187
187
|
},
|
|
188
188
|
{ id: 'other_call', name: 'get_weather', args: { city: 'NYC' } },
|
|
@@ -202,14 +202,14 @@ describe('Tool Discovery Functions', () => {
|
|
|
202
202
|
createAIMessage('Searching...', [
|
|
203
203
|
{
|
|
204
204
|
id: 'call_1',
|
|
205
|
-
name: Constants.
|
|
205
|
+
name: Constants.TOOL_SEARCH,
|
|
206
206
|
args: { pattern: 'xyz' },
|
|
207
207
|
},
|
|
208
208
|
]),
|
|
209
209
|
new ToolMessage({
|
|
210
210
|
content: 'No tools found',
|
|
211
211
|
tool_call_id: 'call_1',
|
|
212
|
-
name: Constants.
|
|
212
|
+
name: Constants.TOOL_SEARCH,
|
|
213
213
|
artifact: {
|
|
214
214
|
tool_references: [],
|
|
215
215
|
metadata: { total_searched: 10, pattern: 'xyz' },
|
|
@@ -228,14 +228,14 @@ describe('Tool Discovery Functions', () => {
|
|
|
228
228
|
createAIMessage('Searching...', [
|
|
229
229
|
{
|
|
230
230
|
id: 'call_1',
|
|
231
|
-
name: Constants.
|
|
231
|
+
name: Constants.TOOL_SEARCH,
|
|
232
232
|
args: { pattern: 'test' },
|
|
233
233
|
},
|
|
234
234
|
]),
|
|
235
235
|
new ToolMessage({
|
|
236
236
|
content: 'Error occurred',
|
|
237
237
|
tool_call_id: 'call_1',
|
|
238
|
-
name: Constants.
|
|
238
|
+
name: Constants.TOOL_SEARCH,
|
|
239
239
|
// No artifact
|
|
240
240
|
}),
|
|
241
241
|
];
|
|
@@ -251,7 +251,7 @@ describe('Tool Discovery Functions', () => {
|
|
|
251
251
|
createAIMessage('Searching...', [
|
|
252
252
|
{
|
|
253
253
|
id: 'call_1',
|
|
254
|
-
name: Constants.
|
|
254
|
+
name: Constants.TOOL_SEARCH,
|
|
255
255
|
args: { pattern: 'test' },
|
|
256
256
|
},
|
|
257
257
|
]),
|
|
@@ -271,7 +271,7 @@ describe('Tool Discovery Functions', () => {
|
|
|
271
271
|
createAIMessage('First search', [
|
|
272
272
|
{
|
|
273
273
|
id: 'first_call',
|
|
274
|
-
name: Constants.
|
|
274
|
+
name: Constants.TOOL_SEARCH,
|
|
275
275
|
args: { pattern: 'first' },
|
|
276
276
|
},
|
|
277
277
|
]),
|
|
@@ -280,7 +280,7 @@ describe('Tool Discovery Functions', () => {
|
|
|
280
280
|
createAIMessage('Second search', [
|
|
281
281
|
{
|
|
282
282
|
id: 'second_call',
|
|
283
|
-
name: Constants.
|
|
283
|
+
name: Constants.TOOL_SEARCH,
|
|
284
284
|
args: { pattern: 'second' },
|
|
285
285
|
},
|
|
286
286
|
]),
|
|
@@ -301,7 +301,7 @@ describe('Tool Discovery Functions', () => {
|
|
|
301
301
|
createAIMessage('Searching...', [
|
|
302
302
|
{
|
|
303
303
|
id: 'call_1',
|
|
304
|
-
name: Constants.
|
|
304
|
+
name: Constants.TOOL_SEARCH,
|
|
305
305
|
args: { pattern: 'test' },
|
|
306
306
|
},
|
|
307
307
|
]),
|
|
@@ -335,7 +335,7 @@ describe('Tool Discovery Functions', () => {
|
|
|
335
335
|
new ToolMessage({
|
|
336
336
|
content: 'Result',
|
|
337
337
|
tool_call_id: 'orphan',
|
|
338
|
-
name: Constants.
|
|
338
|
+
name: Constants.TOOL_SEARCH,
|
|
339
339
|
}),
|
|
340
340
|
];
|
|
341
341
|
|
|
@@ -364,7 +364,7 @@ describe('Tool Discovery Functions', () => {
|
|
|
364
364
|
createAIMessage('Working...', [
|
|
365
365
|
{
|
|
366
366
|
id: 'search_call',
|
|
367
|
-
name: Constants.
|
|
367
|
+
name: Constants.TOOL_SEARCH,
|
|
368
368
|
args: { pattern: 'test' },
|
|
369
369
|
},
|
|
370
370
|
{ id: 'weather_call', name: 'get_weather', args: { city: 'NYC' } },
|
|
@@ -384,7 +384,7 @@ describe('Tool Discovery Functions', () => {
|
|
|
384
384
|
createAIMessage('Searching...', [
|
|
385
385
|
{
|
|
386
386
|
id: 'old_call',
|
|
387
|
-
name: Constants.
|
|
387
|
+
name: Constants.TOOL_SEARCH,
|
|
388
388
|
args: { pattern: 'old' },
|
|
389
389
|
},
|
|
390
390
|
]),
|
|
@@ -410,7 +410,7 @@ describe('Tool Discovery Functions', () => {
|
|
|
410
410
|
createAIMessage('Searching...', [
|
|
411
411
|
{
|
|
412
412
|
id: 'call_1',
|
|
413
|
-
name: Constants.
|
|
413
|
+
name: Constants.TOOL_SEARCH,
|
|
414
414
|
args: { pattern: 'test' },
|
|
415
415
|
},
|
|
416
416
|
]),
|
|
@@ -446,7 +446,7 @@ describe('Tool Discovery Functions', () => {
|
|
|
446
446
|
createAIMessage('Searching...', [
|
|
447
447
|
{
|
|
448
448
|
id: 'call_1',
|
|
449
|
-
name: Constants.
|
|
449
|
+
name: Constants.TOOL_SEARCH,
|
|
450
450
|
args: { pattern: 'test' },
|
|
451
451
|
},
|
|
452
452
|
]),
|
package/src/messages/tools.ts
CHANGED
|
@@ -43,7 +43,7 @@ export function extractToolDiscoveries(messages: BaseMessage[]): string[] {
|
|
|
43
43
|
for (let i = latestAIParentIndex + 1; i < messages.length; i++) {
|
|
44
44
|
const msg = messages[i];
|
|
45
45
|
if (!(msg instanceof ToolMessage)) continue;
|
|
46
|
-
if (msg.name !== Constants.
|
|
46
|
+
if (msg.name !== Constants.TOOL_SEARCH) continue;
|
|
47
47
|
if (!toolCallIds.has(msg.tool_call_id)) continue;
|
|
48
48
|
|
|
49
49
|
// This is a tool search result from the current turn
|
|
@@ -88,7 +88,7 @@ export function hasToolSearchInCurrentTurn(messages: BaseMessage[]): boolean {
|
|
|
88
88
|
const msg = messages[i];
|
|
89
89
|
if (
|
|
90
90
|
msg instanceof ToolMessage &&
|
|
91
|
-
msg.name === Constants.
|
|
91
|
+
msg.name === Constants.TOOL_SEARCH &&
|
|
92
92
|
toolCallIds.has(msg.tool_call_id)
|
|
93
93
|
) {
|
|
94
94
|
return true;
|
|
@@ -23,7 +23,7 @@ import type { RunnableConfig } from '@langchain/core/runnables';
|
|
|
23
23
|
import type * as t from '@/types';
|
|
24
24
|
import { createCodeExecutionTool } from '@/tools/CodeExecutor';
|
|
25
25
|
import { createProgrammaticToolCallingTool } from '@/tools/ProgrammaticToolCalling';
|
|
26
|
-
import {
|
|
26
|
+
import { createToolSearch } from '@/tools/ToolSearch';
|
|
27
27
|
import { getLLMConfig } from '@/utils/llmConfig';
|
|
28
28
|
import { getArgs } from '@/scripts/args';
|
|
29
29
|
import { Run } from '@/run';
|
|
@@ -40,7 +40,7 @@ import {
|
|
|
40
40
|
|
|
41
41
|
/**
|
|
42
42
|
* Tool registry only needs business logic tools that require filtering.
|
|
43
|
-
* Special tools (execute_code, run_tools_with_code,
|
|
43
|
+
* Special tools (execute_code, run_tools_with_code, tool_search)
|
|
44
44
|
* are always bound directly to the LLM and don't need registry entries.
|
|
45
45
|
*/
|
|
46
46
|
function createAgentToolRegistry(): t.LCToolRegistry {
|
|
@@ -73,7 +73,7 @@ async function main(): Promise<void> {
|
|
|
73
73
|
// Create special tools (PTC, code execution, tool search)
|
|
74
74
|
const codeExecTool = createCodeExecutionTool();
|
|
75
75
|
const ptcTool = createProgrammaticToolCallingTool();
|
|
76
|
-
const toolSearchTool =
|
|
76
|
+
const toolSearchTool = createToolSearch();
|
|
77
77
|
|
|
78
78
|
// Build complete tool list and map
|
|
79
79
|
const allTools = [...mockTools, codeExecTool, ptcTool, toolSearchTool];
|
|
@@ -199,7 +199,7 @@ Use the run_tools_with_code tool to do this efficiently - don't call each tool s
|
|
|
199
199
|
console.log('='.repeat(70));
|
|
200
200
|
console.log('\nKey observations:');
|
|
201
201
|
console.log(
|
|
202
|
-
'1. LLM only sees tools with allowed_callers including "direct" (get_weather, execute_code, run_tools_with_code,
|
|
202
|
+
'1. LLM only sees tools with allowed_callers including "direct" (get_weather, execute_code, run_tools_with_code, tool_search)'
|
|
203
203
|
);
|
|
204
204
|
console.log(
|
|
205
205
|
'2. When PTC is invoked, ToolNode automatically injects programmatic tools (get_team_members, get_expenses, get_weather)'
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
// src/scripts/
|
|
1
|
+
// src/scripts/tool_search.ts
|
|
2
2
|
/**
|
|
3
3
|
* Test script for the Tool Search Regex tool.
|
|
4
|
-
* Run with: npm run
|
|
4
|
+
* Run with: npm run tool_search
|
|
5
5
|
*
|
|
6
6
|
* Demonstrates runtime registry injection - the tool registry is passed
|
|
7
7
|
* at invocation time, not at initialization time.
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import { config } from 'dotenv';
|
|
10
10
|
config();
|
|
11
11
|
|
|
12
|
-
import {
|
|
12
|
+
import { createToolSearch } from '@/tools/ToolSearch';
|
|
13
13
|
import type { LCToolRegistry } from '@/types';
|
|
14
14
|
import { createToolSearchToolRegistry } from '@/test/mockTools';
|
|
15
15
|
|
|
@@ -22,7 +22,7 @@ interface RunTestOptions {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
async function runTest(
|
|
25
|
-
searchTool: ReturnType<typeof
|
|
25
|
+
searchTool: ReturnType<typeof createToolSearch>,
|
|
26
26
|
testName: string,
|
|
27
27
|
query: string,
|
|
28
28
|
options: RunTestOptions
|
|
@@ -82,7 +82,7 @@ async function main(): Promise<void> {
|
|
|
82
82
|
);
|
|
83
83
|
|
|
84
84
|
console.log('\nCreating Tool Search Regex tool WITH registry for testing...');
|
|
85
|
-
const searchTool =
|
|
85
|
+
const searchTool = createToolSearch({ apiKey, toolRegistry });
|
|
86
86
|
console.log('Tool created successfully!');
|
|
87
87
|
console.log(
|
|
88
88
|
'Note: In production, ToolNode injects toolRegistry via params when invoked through the graph.\n'
|
package/src/tools/ToolNode.ts
CHANGED
|
@@ -132,7 +132,7 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
132
132
|
toolMap,
|
|
133
133
|
toolDefs,
|
|
134
134
|
};
|
|
135
|
-
} else if (call.name === Constants.
|
|
135
|
+
} else if (call.name === Constants.TOOL_SEARCH) {
|
|
136
136
|
invokeParams = {
|
|
137
137
|
...invokeParams,
|
|
138
138
|
toolRegistry: this.toolRegistry,
|
|
@@ -346,11 +346,29 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
|
|
|
346
346
|
* This enables LLM-initiated parallel execution when calling multiple
|
|
347
347
|
* transfer tools simultaneously.
|
|
348
348
|
*/
|
|
349
|
-
|
|
350
|
-
|
|
349
|
+
|
|
350
|
+
/** Collect all destinations for sibling tracking */
|
|
351
|
+
const allDestinations = handoffCommands.map((cmd) => {
|
|
351
352
|
const goto = cmd.goto;
|
|
352
|
-
|
|
353
|
-
|
|
353
|
+
return typeof goto === 'string' ? goto : (goto as string[])[0];
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
const sends = handoffCommands.map((cmd, idx) => {
|
|
357
|
+
const destination = allDestinations[idx];
|
|
358
|
+
/** Get siblings (other destinations, not this one) */
|
|
359
|
+
const siblings = allDestinations.filter((d) => d !== destination);
|
|
360
|
+
|
|
361
|
+
/** Add siblings to ToolMessage additional_kwargs */
|
|
362
|
+
const update = cmd.update as { messages?: BaseMessage[] } | undefined;
|
|
363
|
+
if (update && update.messages) {
|
|
364
|
+
for (const msg of update.messages) {
|
|
365
|
+
if (msg.getType() === 'tool') {
|
|
366
|
+
(msg as ToolMessage).additional_kwargs.handoff_parallel_siblings =
|
|
367
|
+
siblings;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
354
372
|
return new Send(destination, cmd.update);
|
|
355
373
|
});
|
|
356
374
|
|