Implement function tool calling for OpenAI

c94c06ed4dac3c4895e4afb5b0e6c1524829e49b

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

3 files changed, +52 -9Showing whitespace changes
public/script.js+2 -2
@@ -4408,7 +4408,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
44084408
44094409 if (ToolManager.isFunctionCallingSupported() && Array.isArray(streamingProcessor.toolCalls) && streamingProcessor.toolCalls.length) {
44104410 const invocations = await ToolManager.checkFunctionToolCalls(streamingProcessor.toolCalls);
44114411 if (Array.isArray(invocations) && invocations.length) {
44124412 const lastMessage = chat[chat.length - 1];
44134413 const shouldDeleteMessage = ['', '...'].includes(lastMessage?.mes) && ['', '...'].includes(streamingProcessor.result);
44144414 if (shouldDeleteMessage) {
@@ -4457,7 +4457,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
44574457
44584458 if (ToolManager.isFunctionCallingSupported()) {
44594459 const invocations = await ToolManager.checkFunctionToolCalls(data);
44604460 if (Array.isArray(invocations) && invocations.length) {
44614461 ToolManager.saveFunctionToolInvocations(invocations);
44624462 return Generate(type, { automatic_trigger, force_name2, quiet_prompt, quietToLoud, skipWIAN, force_chid, signal, quietImage, quietName }, dryRun);
44634463 }
public/scripts/openai.js+48 -4
@@ -454,7 +454,8 @@ function setOpenAIMessages(chat) {
454454 if (role == 'user' && oai_settings.wrap_in_quotes) content = `"${content}"`;
455455 const name = chat[j]['name'];
456456 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 };
458459 j++;
459460 }
460461
@@ -702,6 +703,7 @@ async function populateChatHistory(messages, prompts, chatCompletion, type = nul
702703 }
703704
704705 const imageInlining = isImageInliningSupported();
706+ const toolCalling = ToolManager.isFunctionCallingSupported();
705707
706708 // Insert chat messages as long as there is budget available
707709 const chatPool = [...messages].reverse();
@@ -723,6 +725,24 @@ async function populateChatHistory(messages, prompts, chatCompletion, type = nul
723725 await chatMessage.addImage(chatPrompt.image);
724726 }
725727
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+
726746 if (chatCompletion.canAfford(chatMessage)) {
727747 if (type === 'continue' && oai_settings.continue_prefill && chatPrompt === firstNonInjected) {
728748 // 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 {
21932213 content;
21942214 /** @type {string} */
21952215 name;
2216+ /** @type {object} */
2217+ tool_call = null;
21962218
21972219 /**
21982220 * @constructor
@@ -2217,6 +2239,22 @@ class Message {
22172239 }
22182240 }
22192241
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+
22202258 setName(name) {
22212259 this.name = name;
22222260 this.tokens = tokenHandler.count({ role: this.role, content: this.content, name: this.name });
@@ -2564,7 +2602,7 @@ export class ChatCompletion {
25642602 this.checkTokenBudget(message, message.identifier);
25652603
25662604 const index = this.findMessageIndex(identifier);
25672605 if (message.content || message.tool_calls) {
25682606 if ('start' === position) this.messages.collection[index].collection.unshift(message);
25692607 else if ('end' === position) this.messages.collection[index].collection.push(message);
25702608 else if (typeof position === 'number') this.messages.collection[index].collection.splice(position, 0, message);
@@ -2633,8 +2671,14 @@ export class ChatCompletion {
26332671 for (let item of this.messages.collection) {
26342672 if (item instanceof MessageCollection) {
26352673 chat.push(...item.getChat());
26362674 } 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+ };
26382682 chat.push(message);
26392683 } else {
26402684 this.log(`Skipping invalid or empty message in collection: ${JSON.stringify(item)}`);
public/scripts/tool-calling.js+2 -3
@@ -307,7 +307,7 @@ export class ToolManager {
307307
308308 if (oaiCompat.includes(oai_settings.chat_completion_source)) {
309309 if (!Array.isArray(toolCalls)) {
310310 return [];
311311 }
312312
313313 for (const toolCall of toolCalls) {
@@ -363,7 +363,7 @@ export class ToolManager {
363363
364364 /**
365365 * Saves function tool invocations to the last user chat message extra metadata.
366366 * @param {ToolInvocation[]} invocations Successful tool invocations
367367 */
368368 static saveFunctionToolInvocations(invocations) {
369369 for (let index = chat.length - 1; index >= 0; index--) {
@@ -373,7 +373,6 @@ export class ToolManager {
373373 message.extra = {};
374374 }
375375 message.extra.tool_invocations = invocations;
376- debugger;
377376 break;
378377 }
379378 }