Implement function tool calling for OpenAI
| @@ -4408,7 +4408,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro | |||
| 4408 | 4408 | ||
| 4409 | if (ToolManager.isFunctionCallingSupported() && Array.isArray(streamingProcessor.toolCalls) && streamingProcessor.toolCalls.length) { | 4409 | if (ToolManager.isFunctionCallingSupported() && Array.isArray(streamingProcessor.toolCalls) && streamingProcessor.toolCalls.length) { |
| 4410 | const invocations = await ToolManager.checkFunctionToolCalls(streamingProcessor.toolCalls); | 4410 | const invocations = await ToolManager.checkFunctionToolCalls(streamingProcessor.toolCalls); |
| 4411 | if (invocations.length) { | 4411 | if (Array.isArray(invocations) && invocations.length) { |
| 4412 | const lastMessage = chat[chat.length - 1]; | 4412 | const lastMessage = chat[chat.length - 1]; |
| 4413 | const shouldDeleteMessage = ['', '...'].includes(lastMessage?.mes) && ['', '...'].includes(streamingProcessor.result); | 4413 | const shouldDeleteMessage = ['', '...'].includes(lastMessage?.mes) && ['', '...'].includes(streamingProcessor.result); |
| 4414 | if (shouldDeleteMessage) { | 4414 | if (shouldDeleteMessage) { |
| @@ -4457,7 +4457,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro | |||
| 4457 | 4457 | ||
| 4458 | if (ToolManager.isFunctionCallingSupported()) { | 4458 | if (ToolManager.isFunctionCallingSupported()) { |
| 4459 | const invocations = await ToolManager.checkFunctionToolCalls(data); | 4459 | const invocations = await ToolManager.checkFunctionToolCalls(data); |
| 4460 | if (invocations.length) { | 4460 | if (Array.isArray(invocations) && invocations.length) { |
| 4461 | ToolManager.saveFunctionToolInvocations(invocations); | 4461 | ToolManager.saveFunctionToolInvocations(invocations); |
| 4462 | return Generate(type, { automatic_trigger, force_name2, quiet_prompt, quietToLoud, skipWIAN, force_chid, signal, quietImage, quietName }, dryRun); | 4462 | return Generate(type, { automatic_trigger, force_name2, quiet_prompt, quietToLoud, skipWIAN, force_chid, signal, quietImage, quietName }, dryRun); |
| 4463 | } | 4463 | } |
| @@ -454,7 +454,8 @@ function setOpenAIMessages(chat) { | |||
| 454 | if (role == 'user' && oai_settings.wrap_in_quotes) content = `"${content}"`; | 454 | if (role == 'user' && oai_settings.wrap_in_quotes) content = `"${content}"`; |
| 455 | const name = chat[j]['name']; | 455 | const name = chat[j]['name']; |
| 456 | const image = chat[j]?.extra?.image; | 456 | const image = chat[j]?.extra?.image; |
| 457 | messages[i] = { 'role': role, 'content': content, name: name, 'image': image }; | 457 | const invocations = chat[j]?.extra?.tool_invocations; |
| 458 | messages[i] = { 'role': role, 'content': content, name: name, 'image': image, 'invocations': invocations }; | ||
| 458 | j++; | 459 | j++; |
| 459 | } | 460 | } |
| 460 | 461 | ||
| @@ -702,6 +703,7 @@ async function populateChatHistory(messages, prompts, chatCompletion, type = nul | |||
| 702 | } | 703 | } |
| 703 | 704 | ||
| 704 | const imageInlining = isImageInliningSupported(); | 705 | const imageInlining = isImageInliningSupported(); |
| 706 | const toolCalling = ToolManager.isFunctionCallingSupported(); | ||
| 705 | 707 | ||
| 706 | // Insert chat messages as long as there is budget available | 708 | // Insert chat messages as long as there is budget available |
| 707 | const chatPool = [...messages].reverse(); | 709 | const chatPool = [...messages].reverse(); |
| @@ -723,6 +725,24 @@ async function populateChatHistory(messages, prompts, chatCompletion, type = nul | |||
| 723 | await chatMessage.addImage(chatPrompt.image); | 725 | await chatMessage.addImage(chatPrompt.image); |
| 724 | } | 726 | } |
| 725 | 727 | ||
| 728 | if (toolCalling && Array.isArray(chatPrompt.invocations)) { | ||
| 729 | /** @type {import('./tool-calling.js').ToolInvocation[]} */ | ||
| 730 | const invocations = chatPrompt.invocations.slice().reverse(); | ||
| 731 | const toolCallMessage = new Message('assistant', undefined, 'toolCall-' + chatMessage.identifier); | ||
| 732 | toolCallMessage.setToolCalls(invocations); | ||
| 733 | if (chatCompletion.canAfford(toolCallMessage)) { | ||
| 734 | for (const invocation of invocations) { | ||
| 735 | const toolResultMessage = new Message('tool', invocation.result, invocation.id); | ||
| 736 | const canAfford = chatCompletion.canAfford(toolResultMessage) && chatCompletion.canAfford(toolCallMessage); | ||
| 737 | if (!canAfford) { | ||
| 738 | break; | ||
| 739 | } | ||
| 740 | chatCompletion.insertAtStart(toolResultMessage, 'chatHistory'); | ||
| 741 | } | ||
| 742 | chatCompletion.insertAtStart(toolCallMessage, 'chatHistory'); | ||
| 743 | } | ||
| 744 | } | ||
| 745 | |||
| 726 | if (chatCompletion.canAfford(chatMessage)) { | 746 | if (chatCompletion.canAfford(chatMessage)) { |
| 727 | if (type === 'continue' && oai_settings.continue_prefill && chatPrompt === firstNonInjected) { | 747 | if (type === 'continue' && oai_settings.continue_prefill && chatPrompt === firstNonInjected) { |
| 728 | // in case we are using continue_prefill and the latest message is an assistant message, we want to prepend the users assistant prefill on the message | 748 | // in case we are using continue_prefill and the latest message is an assistant message, we want to prepend the users assistant prefill on the message |
| @@ -2193,6 +2213,8 @@ class Message { | |||
| 2193 | content; | 2213 | content; |
| 2194 | /** @type {string} */ | 2214 | /** @type {string} */ |
| 2195 | name; | 2215 | name; |
| 2216 | /** @type {object} */ | ||
| 2217 | tool_call = null; | ||
| 2196 | 2218 | ||
| 2197 | /** | 2219 | /** |
| 2198 | * @constructor | 2220 | * @constructor |
| @@ -2217,6 +2239,22 @@ class Message { | |||
| 2217 | } | 2239 | } |
| 2218 | } | 2240 | } |
| 2219 | 2241 | ||
| 2242 | /** | ||
| 2243 | * Reconstruct the message from a tool invocation. | ||
| 2244 | * @param {import('./tool-calling.js').ToolInvocation[]} invocations | ||
| 2245 | */ | ||
| 2246 | setToolCalls(invocations) { | ||
| 2247 | this.tool_calls = invocations.map(i => ({ | ||
| 2248 | id: i.id, | ||
| 2249 | type: 'function', | ||
| 2250 | function: { | ||
| 2251 | arguments: i.parameters, | ||
| 2252 | name: i.name, | ||
| 2253 | }, | ||
| 2254 | })); | ||
| 2255 | this.tokens = tokenHandler.count({ role: this.role, tool_calls: JSON.stringify(this.tool_calls) }); | ||
| 2256 | } | ||
| 2257 | |||
| 2220 | setName(name) { | 2258 | setName(name) { |
| 2221 | this.name = name; | 2259 | this.name = name; |
| 2222 | this.tokens = tokenHandler.count({ role: this.role, content: this.content, name: this.name }); | 2260 | this.tokens = tokenHandler.count({ role: this.role, content: this.content, name: this.name }); |
| @@ -2564,7 +2602,7 @@ export class ChatCompletion { | |||
| 2564 | this.checkTokenBudget(message, message.identifier); | 2602 | this.checkTokenBudget(message, message.identifier); |
| 2565 | 2603 | ||
| 2566 | const index = this.findMessageIndex(identifier); | 2604 | const index = this.findMessageIndex(identifier); |
| 2567 | if (message.content) { | 2605 | if (message.content || message.tool_calls) { |
| 2568 | if ('start' === position) this.messages.collection[index].collection.unshift(message); | 2606 | if ('start' === position) this.messages.collection[index].collection.unshift(message); |
| 2569 | else if ('end' === position) this.messages.collection[index].collection.push(message); | 2607 | else if ('end' === position) this.messages.collection[index].collection.push(message); |
| 2570 | else if (typeof position === 'number') this.messages.collection[index].collection.splice(position, 0, message); | 2608 | else if (typeof position === 'number') this.messages.collection[index].collection.splice(position, 0, message); |
| @@ -2633,8 +2671,14 @@ export class ChatCompletion { | |||
| 2633 | for (let item of this.messages.collection) { | 2671 | for (let item of this.messages.collection) { |
| 2634 | if (item instanceof MessageCollection) { | 2672 | if (item instanceof MessageCollection) { |
| 2635 | chat.push(...item.getChat()); | 2673 | chat.push(...item.getChat()); |
| 2636 | } else if (item instanceof Message && item.content) { | 2674 | } else if (item instanceof Message && (item.content || item.tool_calls)) { |
| 2637 | const message = { role: item.role, content: item.content, ...(item.name ? { name: item.name } : {}) }; | 2675 | const message = { |
| 2676 | role: item.role, | ||
| 2677 | content: item.content, | ||
| 2678 | ...(item.name ? { name: item.name } : {}), | ||
| 2679 | ...(item.tool_calls ? { tool_calls: item.tool_calls } : {}), | ||
| 2680 | ...(item.role === 'tool' ? { tool_call_id: item.identifier } : {}), | ||
| 2681 | }; | ||
| 2638 | chat.push(message); | 2682 | chat.push(message); |
| 2639 | } else { | 2683 | } else { |
| 2640 | this.log(`Skipping invalid or empty message in collection: ${JSON.stringify(item)}`); | 2684 | this.log(`Skipping invalid or empty message in collection: ${JSON.stringify(item)}`); |
| @@ -307,7 +307,7 @@ export class ToolManager { | |||
| 307 | 307 | ||
| 308 | if (oaiCompat.includes(oai_settings.chat_completion_source)) { | 308 | if (oaiCompat.includes(oai_settings.chat_completion_source)) { |
| 309 | if (!Array.isArray(toolCalls)) { | 309 | if (!Array.isArray(toolCalls)) { |
| 310 | return; | 310 | return []; |
| 311 | } | 311 | } |
| 312 | 312 | ||
| 313 | for (const toolCall of toolCalls) { | 313 | for (const toolCall of toolCalls) { |
| @@ -363,7 +363,7 @@ export class ToolManager { | |||
| 363 | 363 | ||
| 364 | /** | 364 | /** |
| 365 | * Saves function tool invocations to the last user chat message extra metadata. | 365 | * Saves function tool invocations to the last user chat message extra metadata. |
| 366 | * @param {ToolInvocation[]} invocations | 366 | * @param {ToolInvocation[]} invocations Successful tool invocations |
| 367 | */ | 367 | */ |
| 368 | static saveFunctionToolInvocations(invocations) { | 368 | static saveFunctionToolInvocations(invocations) { |
| 369 | for (let index = chat.length - 1; index >= 0; index--) { | 369 | for (let index = chat.length - 1; index >= 0; index--) { |
| @@ -373,7 +373,6 @@ export class ToolManager { | |||
| 373 | message.extra = {}; | 373 | message.extra = {}; |
| 374 | } | 374 | } |
| 375 | message.extra.tool_invocations = invocations; | 375 | message.extra.tool_invocations = invocations; |
| 376 | debugger; | ||
| 377 | break; | 376 | break; |
| 378 | } | 377 | } |
| 379 | } | 378 | } |