Show an error when all tools fail
| @@ -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 | } |
| @@ -1,5 +1,6 @@ | |||
| 1 | import { addOneMessage, chat, main_api, system_avatar, systemUserName } from '../script.js'; | 1 | import { addOneMessage, chat, main_api, system_avatar, systemUserName } from '../script.js'; |
| 2 | import { chat_completion_sources, oai_settings } from './openai.js'; | 2 | import { chat_completion_sources, oai_settings } from './openai.js'; |
| 3 | import { Popup } from './popup.js'; | ||
| 3 | 4 | ||
| 4 | /** | 5 | /** |
| 5 | * @typedef {object} ToolInvocation | 6 | * @typedef {object} ToolInvocation |
| @@ -10,6 +11,13 @@ import { chat_completion_sources, oai_settings } from './openai.js'; | |||
| 10 | */ | 11 | */ |
| 11 | 12 | ||
| 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 | */ |
| 15 | class ToolDefinition { | 23 | class 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 | } |
| 149 | 163 | ||
| @@ -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 data | 322 | * @param {any} data Reply data |
| 309 | * @returns {Promise<ToolInvocation[]>} Successful tool invocations | 323 | * @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 { | |||
| 322 | 340 | ||
| 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 | } |
| 327 | 345 | ||
| 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; | ||
| 337 | 356 | ||
| 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); |
| 342 | 361 | ||
| 343 | // Save a successful invocation | 362 | // 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 | } |
| 349 | 371 | ||
| @@ -374,7 +396,7 @@ export class ToolManager { | |||
| 374 | } | 396 | } |
| 375 | */ | 397 | */ |
| 376 | 398 | ||
| 377 | return invocations; | 399 | return result; |
| 378 | } | 400 | } |
| 379 | 401 | ||
| 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 | } |
| @@ -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 | } |
| 425 | 426 | ||
| 426 | .mes.smallSysMes summary { | 427 | .mes.smallSysMes summary { |