Show an error when all tools fail

6558b106754a73faff6ca171254192ed9a1157e9

Cohee <18619528+Cohee1207@users.noreply.github.com>

3 files changed, +66 -16Showing whitespace changes
public/script.js+21 -6
@@ -4420,10 +4420,18 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
4420 const lastMessage = chat[chat.length - 1];4420 const lastMessage = chat[chat.length - 1];
4421 const shouldDeleteMessage = ['', '...'].includes(lastMessage?.mes) && ['', '...'].includes(streamingProcessor.result);4421 const shouldDeleteMessage = ['', '...'].includes(lastMessage?.mes) && ['', '...'].includes(streamingProcessor.result);
4422 shouldDeleteMessage && await deleteLastMessage();4422 shouldDeleteMessage && await deleteLastMessage();
4423 const invocations = await ToolManager.invokeFunctionTools(streamingProcessor.toolCalls);4423 const invocationResult = await ToolManager.invokeFunctionTools(streamingProcessor.toolCalls);
4424 if (Array.isArray(invocations) && invocations.length) {4424 if (invocationResult.hadToolCalls) {
4425 if (!invocationResult.invocations.length && shouldDeleteMessage) {
4426 ToolManager.showToolCallError(invocationResult.errors);
4427 unblockGeneration(type);
4428 generatedPromptCache = '';
4429 streamingProcessor = null;
4430 return;
4431 }
4432
4425 streamingProcessor = null;4433 streamingProcessor = null;
4426 ToolManager.saveFunctionToolInvocations(invocations);4434 ToolManager.saveFunctionToolInvocations(invocationResult.invocations);
4427 return Generate(type, { automatic_trigger, force_name2, quiet_prompt, quietToLoud, skipWIAN, force_chid, signal, quietImage, quietName }, dryRun);4435 return Generate(type, { automatic_trigger, force_name2, quiet_prompt, quietToLoud, skipWIAN, force_chid, signal, quietImage, quietName }, dryRun);
4428 }4436 }
4429 }4437 }
@@ -4505,9 +4513,16 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
4505 if (canPerformToolCalls) {4513 if (canPerformToolCalls) {
4506 const shouldDeleteMessage = ['', '...'].includes(getMessage);4514 const shouldDeleteMessage = ['', '...'].includes(getMessage);
4507 shouldDeleteMessage && await deleteLastMessage();4515 shouldDeleteMessage && await deleteLastMessage();
4508 const invocations = await ToolManager.invokeFunctionTools(data);4516 const invocationResult = await ToolManager.invokeFunctionTools(data);
4509 if (Array.isArray(invocations) && invocations.length) {4517 if (invocationResult.hadToolCalls) {
4510 ToolManager.saveFunctionToolInvocations(invocations);4518 if (!invocationResult.invocations.length && shouldDeleteMessage) {
4519 ToolManager.showToolCallError(invocationResult.errors);
4520 unblockGeneration(type);
4521 generatedPromptCache = '';
4522 return;
4523 }
4524
4525 ToolManager.saveFunctionToolInvocations(invocationResult.invocations);
4511 return Generate(type, { automatic_trigger, force_name2, quiet_prompt, quietToLoud, skipWIAN, force_chid, signal, quietImage, quietName }, dryRun);4526 return Generate(type, { automatic_trigger, force_name2, quiet_prompt, quietToLoud, skipWIAN, force_chid, signal, quietImage, quietName }, dryRun);
4512 }4527 }
4513 }4528 }
public/scripts/tool-calling.js+44 -10
@@ -1,5 +1,6 @@
1import { addOneMessage, chat, main_api, system_avatar, systemUserName } from '../script.js';1import { addOneMessage, chat, main_api, system_avatar, systemUserName } from '../script.js';
2import { chat_completion_sources, oai_settings } from './openai.js';2import { chat_completion_sources, oai_settings } from './openai.js';
3import { Popup } from './popup.js';
34
4/**5/**
5 * @typedef {object} ToolInvocation6 * @typedef {object} ToolInvocation
@@ -10,6 +11,13 @@ import { chat_completion_sources, oai_settings } from './openai.js';
10 */11 */
1112
12/**13/**
14 * @typedef {object} ToolInvocationResult
15 * @property {ToolInvocation[]} invocations Successful tool invocations
16 * @property {boolean} hadToolCalls Whether any tool calls were found
17 * @property {Error[]} errors Errors that occurred during tool invocation
18 */
19
20/**
13 * A class that represents a tool definition.21 * A class that represents a tool definition.
14 */22 */
15class ToolDefinition {23class ToolDefinition {
@@ -129,7 +137,7 @@ export class ToolManager {
129 * Invokes a tool by name. Returns the result of the tool's action function.137 * Invokes a tool by name. Returns the result of the tool's action function.
130 * @param {string} name The name of the tool to invoke.138 * @param {string} name The name of the tool to invoke.
131 * @param {object} parameters Function parameters. For example, if the tool requires a "name" parameter, you would pass {name: "value"}.139 * @param {object} parameters Function parameters. For example, if the tool requires a "name" parameter, you would pass {name: "value"}.
132 * @returns {Promise<string|null>} The result of the tool's action function. If an error occurs, null is returned. Non-string results are JSON-stringified.140 * @returns {Promise<string|Error>} The result of the tool's action function. If an error occurs, null is returned. Non-string results are JSON-stringified.
133 */141 */
134 static async invokeFunctionTool(name, parameters) {142 static async invokeFunctionTool(name, parameters) {
135 try {143 try {
@@ -143,7 +151,13 @@ export class ToolManager {
143 return typeof result === 'string' ? result : JSON.stringify(result);151 return typeof result === 'string' ? result : JSON.stringify(result);
144 } catch (error) {152 } catch (error) {
145 console.error(`An error occurred while invoking the tool "${name}":`, error);153 console.error(`An error occurred while invoking the tool "${name}":`, error);
146 return null;154
155 if (error instanceof Error) {
156 error.cause = name;
157 return error;
158 }
159
160 return new Error('Unknown error occurred while invoking the tool.', { cause: name });
147 }161 }
148 }162 }
149163
@@ -306,11 +320,15 @@ export class ToolManager {
306 /**320 /**
307 * Check for function tool calls in the response data and invoke them.321 * Check for function tool calls in the response data and invoke them.
308 * @param {any} data Reply data322 * @param {any} data Reply data
309 * @returns {Promise<ToolInvocation[]>} Successful tool invocations323 * @returns {Promise<ToolInvocationResult>} Successful tool invocations
310 */324 */
311 static async invokeFunctionTools(data) {325 static async invokeFunctionTools(data) {
312 /** @type {ToolInvocation[]} */326 /** @type {ToolInvocationResult} */
313 const invocations = [];327 const result = {
328 invocations: [],
329 hadToolCalls: false,
330 errors: [],
331 };
314 const toolCalls = ToolManager.#getToolCallsFromData(data);332 const toolCalls = ToolManager.#getToolCallsFromData(data);
315 const oaiCompatibleSources = [333 const oaiCompatibleSources = [
316 chat_completion_sources.OPENAI,334 chat_completion_sources.OPENAI,
@@ -322,7 +340,7 @@ export class ToolManager {
322340
323 if (oaiCompatibleSources.includes(oai_settings.chat_completion_source)) {341 if (oaiCompatibleSources.includes(oai_settings.chat_completion_source)) {
324 if (!Array.isArray(toolCalls)) {342 if (!Array.isArray(toolCalls)) {
325 return [];343 return result;
326 }344 }
327345
328 for (const toolCall of toolCalls) {346 for (const toolCall of toolCalls) {
@@ -334,16 +352,20 @@ export class ToolManager {
334 const id = toolCall.id;352 const id = toolCall.id;
335 const parameters = toolCall.function.arguments;353 const parameters = toolCall.function.arguments;
336 const name = toolCall.function.name;354 const name = toolCall.function.name;
355 result.hadToolCalls = true;
337356
338 const toast = toastr.info(`Invoking function tool: ${name}`);357 const toast = toastr.info(`Invoking function tool: ${name}`);
339 const result = await ToolManager.invokeFunctionTool(name, parameters);358 const toolResult = await ToolManager.invokeFunctionTool(name, parameters);
340 toastr.clear(toast);359 toastr.clear(toast);
341 console.log('Function tool result:', result);360 console.log('Function tool result:', result);
342361
343 // Save a successful invocation362 // Save a successful invocation
344 if (result) {363 if (toolResult instanceof Error) {
345 invocations.push({ id, name, parameters, result });364 result.errors.push(toolResult);
365 continue;
346 }366 }
367
368 result.invocations.push({ id, name, parameters, result: toolResult });
347 }369 }
348 }370 }
349371
@@ -374,7 +396,7 @@ export class ToolManager {
374 }396 }
375 */397 */
376398
377 return invocations;399 return result;
378 }400 }
379401
380 /**402 /**
@@ -418,4 +440,16 @@ export class ToolManager {
418 chat.push(message);440 chat.push(message);
419 addOneMessage(message);441 addOneMessage(message);
420 }442 }
443
444 /**
445 * Shows an error message for tool calls.
446 * @param {Error[]} errors Errors that occurred during tool invocation
447 * @returns {void}
448 */
449 static showToolCallError(errors) {
450 toastr.error('An error occurred while invoking function tools. Click here for more details.', 'Tool Calling', {
451 onclick: () => Popup.show.text('Tool Calling Errors', DOMPurify.sanitize(errors.map(e => `${e.cause}: ${e.message}`).join('<br>'))),
452 timeOut: 5000,
453 });
454 }
421}455}
public/style.css+1 -0
@@ -421,6 +421,7 @@ small {
421.mes.smallSysMes pre {421.mes.smallSysMes pre {
422 text-align: initial;422 text-align: initial;
423 word-break: break-all;423 word-break: break-all;
424 margin-top: 5px;
424}425}
425426
426.mes.smallSysMes summary {427.mes.smallSysMes summary {