@minded-ai/mindedjs 3.1.43-beta.3 → 3.1.44
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/nodes/addAppToolNode.d.ts.map +1 -1
- package/dist/nodes/addAppToolNode.js +66 -14
- package/dist/nodes/addAppToolNode.js.map +1 -1
- package/dist/nodes/addBrowserTaskNode.d.ts.map +1 -1
- package/dist/nodes/addBrowserTaskNode.js +13 -3
- package/dist/nodes/addBrowserTaskNode.js.map +1 -1
- package/dist/nodes/addToolNode.d.ts.map +1 -1
- package/dist/nodes/addToolNode.js +29 -41
- package/dist/nodes/addToolNode.js.map +1 -1
- package/dist/nodes/addToolRunNode.d.ts.map +1 -1
- package/dist/nodes/addToolRunNode.js +93 -82
- package/dist/nodes/addToolRunNode.js.map +1 -1
- package/dist/nodes/compilePrompt.d.ts +0 -5
- package/dist/nodes/compilePrompt.d.ts.map +1 -1
- package/dist/nodes/compilePrompt.js +21 -46
- package/dist/nodes/compilePrompt.js.map +1 -1
- package/dist/nodes/utils/getSchemaForToolInference.d.ts +13 -0
- package/dist/nodes/utils/getSchemaForToolInference.d.ts.map +1 -0
- package/dist/nodes/utils/getSchemaForToolInference.js +34 -0
- package/dist/nodes/utils/getSchemaForToolInference.js.map +1 -0
- package/dist/nodes/utils/inferToolCallWithRetry.d.ts +18 -0
- package/dist/nodes/utils/inferToolCallWithRetry.d.ts.map +1 -0
- package/dist/nodes/utils/inferToolCallWithRetry.js +58 -0
- package/dist/nodes/utils/inferToolCallWithRetry.js.map +1 -0
- package/package.json +2 -2
- package/src/nodes/addAppToolNode.ts +68 -17
- package/src/nodes/addBrowserTaskNode.ts +13 -3
- package/src/nodes/addToolNode.ts +31 -48
- package/src/nodes/addToolRunNode.ts +114 -97
- package/src/nodes/compilePrompt.ts +24 -43
- package/src/nodes/utils/getSchemaForToolInference.ts +48 -0
- package/src/nodes/utils/inferToolCallWithRetry.ts +89 -0
|
@@ -35,55 +35,50 @@ export const addToolRunNode = async ({ graph, tools, toolNode, attachedToNodeNam
|
|
|
35
35
|
const { env, browserTaskMode } = getConfig();
|
|
36
36
|
|
|
37
37
|
const executeWrapper = async (input: z.infer<typeof matchedTool.input>) => {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
return { breakpointHit: true, breakpointInfo } as unknown as ToolMessage;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return result as ToolMessage;
|
|
76
|
-
} else {
|
|
77
|
-
// Non-RPA tool - execute normally
|
|
78
|
-
const response = await matchedTool.execute({ input, state, agent });
|
|
79
|
-
const endTime = Date.now();
|
|
80
|
-
logger.debug({ msg: '[Tool] Tool execution time', tool: matchedTool.name, executionTimeMs: endTime - startTime });
|
|
81
|
-
return response as ToolMessage;
|
|
38
|
+
const startTime = Date.now();
|
|
39
|
+
|
|
40
|
+
// Check if this is an RPA tool
|
|
41
|
+
if (matchedTool.type === 'rpa') {
|
|
42
|
+
// Wrap with browser session and screenshot capture
|
|
43
|
+
const { result, breakpointHit, breakpointInfo } = await withBrowserSession(
|
|
44
|
+
{
|
|
45
|
+
sessionId: state.sessionId,
|
|
46
|
+
browserTaskMode: matchedTool.browserTaskMode ?? browserTaskMode,
|
|
47
|
+
toolCallId: toolCallObj.tool_calls[0].id,
|
|
48
|
+
proxyConfig: matchedTool.proxyConfig,
|
|
49
|
+
toolName: matchedTool.name,
|
|
50
|
+
persistSession: matchedTool.persistSession,
|
|
51
|
+
checkBreakpoints: env === Environment.DEVELOPMENT,
|
|
52
|
+
},
|
|
53
|
+
async (page) => {
|
|
54
|
+
// Pass page directly to RPA tool execution
|
|
55
|
+
const toolResult = await matchedTool.execute({
|
|
56
|
+
input,
|
|
57
|
+
state,
|
|
58
|
+
agent,
|
|
59
|
+
page,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
return toolResult;
|
|
63
|
+
},
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
const endTime = Date.now();
|
|
67
|
+
logger.debug({ msg: '[Tool] RPA tool execution time', tool: matchedTool.name, executionTimeMs: endTime - startTime });
|
|
68
|
+
|
|
69
|
+
// Check if breakpoint was hit - return special result
|
|
70
|
+
if (breakpointHit && breakpointInfo) {
|
|
71
|
+
return { breakpointHit: true, breakpointInfo } as unknown as ToolMessage;
|
|
82
72
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
throw err;
|
|
73
|
+
|
|
74
|
+
return result as ToolMessage;
|
|
86
75
|
}
|
|
76
|
+
|
|
77
|
+
// Non-RPA tool - execute normally
|
|
78
|
+
const response = await matchedTool.execute({ input, state, agent });
|
|
79
|
+
const endTime = Date.now();
|
|
80
|
+
logger.debug({ msg: '[Tool] Tool execution time', tool: matchedTool.name, executionTimeMs: endTime - startTime });
|
|
81
|
+
return response as ToolMessage;
|
|
87
82
|
};
|
|
88
83
|
|
|
89
84
|
const toolCallObj = state.messages[state.messages.length - 1] as any;
|
|
@@ -95,72 +90,94 @@ export const addToolRunNode = async ({ graph, tools, toolNode, attachedToNodeNam
|
|
|
95
90
|
const toolCall = toolCallObj.tool_calls[0];
|
|
96
91
|
const parsedArgs = matchedTool.input.parse(toolCall.args);
|
|
97
92
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
// Check if breakpoint was hit - handle gracefully by ending the flow
|
|
102
|
-
const resultObj = result as unknown as { breakpointHit?: boolean; breakpointInfo?: BreakpointHitResult };
|
|
103
|
-
if (resultObj?.breakpointHit && resultObj?.breakpointInfo) {
|
|
104
|
-
logger.info({
|
|
105
|
-
msg: '[Tool] RPA breakpoint hit - ending flow gracefully',
|
|
106
|
-
toolName: matchedTool.name,
|
|
107
|
-
stepNumber: resultObj.breakpointInfo.stepNumber,
|
|
108
|
-
});
|
|
93
|
+
try {
|
|
94
|
+
// Execute the tool with validated args
|
|
95
|
+
const result = await executeWrapper(parsedArgs);
|
|
109
96
|
|
|
110
|
-
//
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
toolName:
|
|
97
|
+
// Check if breakpoint was hit - handle gracefully by ending the flow
|
|
98
|
+
const resultObj = result as unknown as { breakpointHit?: boolean; breakpointInfo?: BreakpointHitResult };
|
|
99
|
+
if (resultObj?.breakpointHit && resultObj?.breakpointInfo) {
|
|
100
|
+
logger.info({
|
|
101
|
+
msg: '[Tool] RPA breakpoint hit - ending flow gracefully',
|
|
102
|
+
toolName: matchedTool.name,
|
|
116
103
|
stepNumber: resultObj.breakpointInfo.stepNumber,
|
|
117
|
-
})
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// Create a ToolMessage indicating breakpoint was hit
|
|
107
|
+
const breakpointMessage = new ToolMessage({
|
|
108
|
+
content: JSON.stringify({
|
|
109
|
+
breakpointHit: true,
|
|
110
|
+
message: `RPA breakpoint reached at step ${resultObj.breakpointInfo.stepNumber + 1}`,
|
|
111
|
+
toolName: resultObj.breakpointInfo.toolName,
|
|
112
|
+
stepNumber: resultObj.breakpointInfo.stepNumber,
|
|
113
|
+
}),
|
|
114
|
+
tool_call_id: toolCall.id,
|
|
115
|
+
name: matchedTool.name,
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
state.messages.push(breakpointMessage);
|
|
119
|
+
state.history.push(
|
|
120
|
+
createHistoryStep<HistoryStep>(state.history, {
|
|
121
|
+
type: NodeType.TOOL,
|
|
122
|
+
nodeId: toolNode.name,
|
|
123
|
+
nodeDisplayName: toolNode.displayName,
|
|
124
|
+
raw: breakpointMessage,
|
|
125
|
+
messageIds: [toolCall.id],
|
|
126
|
+
}),
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
// Set goto to __end__ to stop the flow gracefully
|
|
130
|
+
state.goto = '__end__';
|
|
131
|
+
return state;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Create a ToolMessage with the result
|
|
135
|
+
const toolCallMessage = new ToolMessage({
|
|
136
|
+
content: typeof result === 'string' ? result : JSON.stringify(result),
|
|
118
137
|
tool_call_id: toolCall.id,
|
|
119
138
|
name: matchedTool.name,
|
|
120
139
|
});
|
|
121
140
|
|
|
122
|
-
state
|
|
141
|
+
// Add the tool message to the state
|
|
142
|
+
state.messages.push(toolCallMessage);
|
|
143
|
+
|
|
144
|
+
// Add history step
|
|
123
145
|
state.history.push(
|
|
124
146
|
createHistoryStep<HistoryStep>(state.history, {
|
|
125
147
|
type: NodeType.TOOL,
|
|
126
148
|
nodeId: toolNode.name,
|
|
127
149
|
nodeDisplayName: toolNode.displayName,
|
|
128
|
-
raw:
|
|
129
|
-
messageIds: [
|
|
150
|
+
raw: toolCallMessage,
|
|
151
|
+
messageIds: [toolCall.id],
|
|
130
152
|
}),
|
|
131
153
|
);
|
|
132
154
|
|
|
133
|
-
//
|
|
134
|
-
state.goto =
|
|
155
|
+
// Clear goto if it was set
|
|
156
|
+
state.goto = null;
|
|
157
|
+
|
|
158
|
+
// Return the entire modified state
|
|
135
159
|
return state;
|
|
136
|
-
}
|
|
160
|
+
} catch (err) {
|
|
161
|
+
logger.error({ message: '[Tool] Error executing tool', err, node: toolNode.displayName });
|
|
162
|
+
|
|
163
|
+
const errorMessage = err instanceof Error ? err.message : JSON.stringify(err);
|
|
164
|
+
const toolErrorMessage = new ToolMessage({
|
|
165
|
+
content: JSON.stringify({ error: errorMessage }),
|
|
166
|
+
tool_call_id: toolCall.id,
|
|
167
|
+
name: matchedTool.name,
|
|
168
|
+
});
|
|
137
169
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
state.history.push(
|
|
150
|
-
createHistoryStep<HistoryStep>(state.history, {
|
|
151
|
-
type: NodeType.TOOL,
|
|
152
|
-
nodeId: toolNode.name,
|
|
153
|
-
nodeDisplayName: toolNode.displayName,
|
|
154
|
-
raw: toolCallMessage,
|
|
155
|
-
messageIds: [toolCallObj.tool_calls[0].id],
|
|
156
|
-
}),
|
|
157
|
-
);
|
|
158
|
-
|
|
159
|
-
// Clear goto if it was set
|
|
160
|
-
state.goto = null;
|
|
161
|
-
|
|
162
|
-
// Return the entire modified state
|
|
163
|
-
return state;
|
|
170
|
+
state.messages.push(toolErrorMessage);
|
|
171
|
+
state.history.push(
|
|
172
|
+
createHistoryStep<HistoryStep>(state.history, {
|
|
173
|
+
type: NodeType.TOOL,
|
|
174
|
+
nodeId: toolNode.name,
|
|
175
|
+
nodeDisplayName: toolNode.displayName,
|
|
176
|
+
raw: toolErrorMessage,
|
|
177
|
+
messageIds: [toolCall.id],
|
|
178
|
+
}),
|
|
179
|
+
);
|
|
180
|
+
}
|
|
164
181
|
};
|
|
165
182
|
|
|
166
183
|
graph.addNode(buildToolRunNodeName(attachedToNodeName), callback);
|
|
@@ -6,13 +6,6 @@ import { HistoryStep, TriggerHistoryStep } from '../types/Agent.types';
|
|
|
6
6
|
import { State } from '../types/LangGraph.types';
|
|
7
7
|
import { NodeType } from '../types/Flows.types';
|
|
8
8
|
|
|
9
|
-
// Matches dotted path identifiers — allows letters, digits, `_`, `$`, `.`, `[`, `]`, spaces, and hyphens
|
|
10
|
-
// (spaces/hyphens support node display names like "Greeting Prompt" and trigger keys like "some-key").
|
|
11
|
-
// Implicitly rejects JSON braces (which contain `"` and `:`) since those chars are not in the set.
|
|
12
|
-
const PLACEHOLDER_PATH_CHARS = String.raw`[A-Za-z0-9_.$\[\] -]`;
|
|
13
|
-
const PLACEHOLDER_REGEX = new RegExp(String.raw`\{\s*([A-Za-z_$]${PLACEHOLDER_PATH_CHARS}*)\s*\}`, 'g');
|
|
14
|
-
const SINGLE_PLACEHOLDER_REGEX = new RegExp(String.raw`^\{\s*([A-Za-z_$]${PLACEHOLDER_PATH_CHARS}*)\s*\}$`);
|
|
15
|
-
|
|
16
9
|
/**
|
|
17
10
|
* Extract node outputs from state.history and state.messages
|
|
18
11
|
* Returns a map of nodeId/nodeDisplayName -> output object
|
|
@@ -136,45 +129,29 @@ export type ExtendedContextData = ContextData & {
|
|
|
136
129
|
*/
|
|
137
130
|
export function compileParameter(parameter: string, context: ContextData = {}, inputKey?: string, inputSchema?: ZodSchema): any {
|
|
138
131
|
try {
|
|
139
|
-
|
|
140
|
-
} catch (err) {
|
|
141
|
-
logger.error({ message: 'Error compiling parameter', err, parameter });
|
|
142
|
-
return parameter;
|
|
143
|
-
}
|
|
144
|
-
}
|
|
132
|
+
context = extendContextData(context);
|
|
145
133
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
* Top-level strings get full schema-based coercion; nested string leaves are compiled without schema coercion.
|
|
149
|
-
*/
|
|
150
|
-
export function compileValueDeep(value: unknown, context: ContextData = {}, inputKey?: string, inputSchema?: ZodSchema): any {
|
|
151
|
-
if (typeof value === 'string') return compileParameter(value, context, inputKey, inputSchema);
|
|
152
|
-
if (!value || typeof value !== 'object') return value;
|
|
153
|
-
const extCtx = extendContextData(context);
|
|
154
|
-
return compileValueDeepInternal(value, extCtx);
|
|
155
|
-
}
|
|
134
|
+
// First, render with EJS
|
|
135
|
+
let compiledParameter = ejs.render(parameter, context).trim();
|
|
156
136
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
137
|
+
// Then, replace placeholders in {} format
|
|
138
|
+
// If the result contains only one set of curly braces, get the native value at that key path, e.g., {state.memory.arrayOfStrings} -> state.memory.arrayOfStrings
|
|
139
|
+
// Otherwise, compile it into a string.
|
|
140
|
+
const keyPathMatch = compiledParameter.match(/^\{([^}]+)}$/);
|
|
141
|
+
if (keyPathMatch) {
|
|
142
|
+
const keyPath = keyPathMatch[1];
|
|
143
|
+
compiledParameter = getContextValueFromPath(keyPath, context, compiledParameter);
|
|
144
|
+
} else {
|
|
145
|
+
compiledParameter = replacePlaceholders(compiledParameter, context);
|
|
146
|
+
}
|
|
163
147
|
|
|
164
|
-
|
|
165
|
-
function compileParameterWithContext(parameter: string, context: ExtendedContextData, inputKey?: string, inputSchema?: ZodSchema): any {
|
|
166
|
-
let compiledParameter = ejs.render(parameter, context).trim();
|
|
148
|
+
compiledParameter = coerceValueWithZodType(compiledParameter, inputKey, inputSchema);
|
|
167
149
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
compiledParameter = getContextValueFromPath(keyPathMatch[1].trim(), context, compiledParameter);
|
|
173
|
-
} else {
|
|
174
|
-
compiledParameter = replacePlaceholders(compiledParameter, context);
|
|
150
|
+
return compiledParameter;
|
|
151
|
+
} catch (err) {
|
|
152
|
+
logger.error({ message: 'Error compiling parameter', err, parameter });
|
|
153
|
+
return parameter; // Return uncompiled if there's an error
|
|
175
154
|
}
|
|
176
|
-
|
|
177
|
-
return coerceValueWithZodType(compiledParameter, inputKey, inputSchema);
|
|
178
155
|
}
|
|
179
156
|
|
|
180
157
|
/**
|
|
@@ -184,8 +161,12 @@ export function compilePrompt(prompt: string, context: ContextData): string {
|
|
|
184
161
|
try {
|
|
185
162
|
context = extendContextData(context);
|
|
186
163
|
|
|
164
|
+
// First, render with EJS
|
|
187
165
|
let compiledPrompt = ejs.render(prompt, context);
|
|
166
|
+
|
|
167
|
+
// Then, replace placeholders in {} format
|
|
188
168
|
compiledPrompt = replacePlaceholders(compiledPrompt, context);
|
|
169
|
+
|
|
189
170
|
return compiledPrompt;
|
|
190
171
|
} catch (err) {
|
|
191
172
|
logger.error({ message: 'Error compiling prompt', err, prompt });
|
|
@@ -234,10 +215,10 @@ function extendContextData(context: ContextData = {}): ExtendedContextData {
|
|
|
234
215
|
* - {tools.NodeName.field} - node reference with 'tools' prefix (alias)
|
|
235
216
|
*/
|
|
236
217
|
function replacePlaceholders(text: string, context: Record<string, any>): string {
|
|
237
|
-
return text.replace(
|
|
238
|
-
const keyPath = String(keyPathRaw).trim();
|
|
218
|
+
return text.replace(/\{([^}]+)}/g, (match, keyPath) => {
|
|
239
219
|
const value = getContextValueFromPath(keyPath, context, match);
|
|
240
220
|
|
|
221
|
+
// In the case of complex values (e.g., arrays or objects), JSON stringify them to ensure all data is preserved.
|
|
241
222
|
if (Array.isArray(value) || (typeof value === 'object' && value !== null)) {
|
|
242
223
|
try {
|
|
243
224
|
return JSON.stringify(value);
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { logger } from '../../utils/logger';
|
|
3
|
+
|
|
4
|
+
interface GetSchemaForToolInferenceParams {
|
|
5
|
+
inputSchema: z.ZodTypeAny;
|
|
6
|
+
overriddenKeys: string[];
|
|
7
|
+
toolName: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface GetSchemaForToolInferenceResult {
|
|
11
|
+
schemaForLLM: z.ZodTypeAny;
|
|
12
|
+
skipLLMCall: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const getSchemaForToolInference = ({
|
|
16
|
+
inputSchema,
|
|
17
|
+
overriddenKeys,
|
|
18
|
+
toolName,
|
|
19
|
+
}: GetSchemaForToolInferenceParams): GetSchemaForToolInferenceResult => {
|
|
20
|
+
let schemaForLLM = inputSchema;
|
|
21
|
+
let skipLLMCall = false;
|
|
22
|
+
|
|
23
|
+
if (overriddenKeys.length === 0) {
|
|
24
|
+
return { schemaForLLM, skipLLMCall };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
if (inputSchema instanceof z.ZodObject) {
|
|
29
|
+
const omitMap = overriddenKeys.reduce((acc, key) => {
|
|
30
|
+
acc[key] = true;
|
|
31
|
+
return acc;
|
|
32
|
+
}, {} as Record<string, true>);
|
|
33
|
+
|
|
34
|
+
const filteredSchema = inputSchema.omit(omitMap);
|
|
35
|
+
schemaForLLM = filteredSchema;
|
|
36
|
+
const remainingKeys = Object.keys(filteredSchema.shape);
|
|
37
|
+
skipLLMCall = remainingKeys.length === 0;
|
|
38
|
+
}
|
|
39
|
+
} catch (err) {
|
|
40
|
+
logger.warn({
|
|
41
|
+
msg: '[Tool] Failed to filter schema, using original',
|
|
42
|
+
tool: toolName,
|
|
43
|
+
err,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return { schemaForLLM, skipLLMCall };
|
|
48
|
+
};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { AIMessage, BaseMessage, SystemMessage } from '@langchain/core/messages';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { logger } from '../../utils/logger';
|
|
4
|
+
|
|
5
|
+
interface InferToolCallWithRetryParams {
|
|
6
|
+
llm: any;
|
|
7
|
+
tool: { name: string };
|
|
8
|
+
inputSchema: z.ZodTypeAny;
|
|
9
|
+
messages: BaseMessage[];
|
|
10
|
+
sessionId: string;
|
|
11
|
+
mergeArgs?: (args: Record<string, any>) => Record<string, any>;
|
|
12
|
+
onAfterAttempt?: () => Promise<void>;
|
|
13
|
+
maxAttempts?: number;
|
|
14
|
+
toolNameForLogs: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const inferToolCallWithRetry = async ({
|
|
18
|
+
llm,
|
|
19
|
+
tool,
|
|
20
|
+
inputSchema,
|
|
21
|
+
messages,
|
|
22
|
+
sessionId,
|
|
23
|
+
mergeArgs,
|
|
24
|
+
onAfterAttempt,
|
|
25
|
+
maxAttempts = 2,
|
|
26
|
+
toolNameForLogs,
|
|
27
|
+
}: InferToolCallWithRetryParams): Promise<AIMessage> => {
|
|
28
|
+
let inferenceMessages = [...messages];
|
|
29
|
+
let aiToolCallMessage: AIMessage = new AIMessage({ content: '', tool_calls: [] });
|
|
30
|
+
|
|
31
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
32
|
+
aiToolCallMessage = await llm.bindTools([tool], { tool_choice: tool.name }).invoke(inferenceMessages);
|
|
33
|
+
if (onAfterAttempt) {
|
|
34
|
+
await onAfterAttempt();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (!aiToolCallMessage.tool_calls || aiToolCallMessage.tool_calls.length === 0) {
|
|
38
|
+
throw new Error('No tool calls generated by LLM');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const inferredArgs = (aiToolCallMessage.tool_calls[0].args || {}) as Record<string, any>;
|
|
42
|
+
const mergedArgs = mergeArgs ? mergeArgs(inferredArgs) : inferredArgs;
|
|
43
|
+
const parseResult = inputSchema.safeParse(mergedArgs);
|
|
44
|
+
|
|
45
|
+
if (parseResult.success) {
|
|
46
|
+
aiToolCallMessage.tool_calls[0].args = parseResult.data;
|
|
47
|
+
return aiToolCallMessage;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (attempt < maxAttempts) {
|
|
51
|
+
const validationIssues = parseResult.error.issues
|
|
52
|
+
.map((issue: z.ZodIssue) => `${issue.path.join('.') || '<root>'}: ${issue.message}`)
|
|
53
|
+
.join('; ');
|
|
54
|
+
|
|
55
|
+
logger.error({
|
|
56
|
+
msg: '[Tool] Retrying parameter inference due to schema mismatch',
|
|
57
|
+
tool: toolNameForLogs,
|
|
58
|
+
sessionId,
|
|
59
|
+
attempt,
|
|
60
|
+
validationIssues,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
inferenceMessages = [
|
|
64
|
+
...inferenceMessages,
|
|
65
|
+
new SystemMessage(
|
|
66
|
+
`Your previous tool arguments were invalid for tool "${tool.name}".
|
|
67
|
+
Validation issues: ${validationIssues}
|
|
68
|
+
Return arguments that strictly match the tool schema types. Do not wrap scalar values in arrays.`,
|
|
69
|
+
),
|
|
70
|
+
];
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
logger.error({
|
|
75
|
+
msg: '[Tool] Inferred parameters remain schema-invalid after retries',
|
|
76
|
+
tool: toolNameForLogs,
|
|
77
|
+
sessionId,
|
|
78
|
+
validationIssues: parseResult.error.issues.map((issue: z.ZodIssue) => ({
|
|
79
|
+
path: issue.path.join('.') || '<root>',
|
|
80
|
+
message: issue.message,
|
|
81
|
+
})),
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
aiToolCallMessage.tool_calls[0].args = mergedArgs;
|
|
85
|
+
return aiToolCallMessage;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return aiToolCallMessage;
|
|
89
|
+
};
|