Save tool calls to visible chats.

0f8c1fa95d7bdfea5d69ffed3c0ee76eef444886

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

3 files changed, +71 -44Showing whitespace changes
public/script.js+15 -11
@@ -3571,7 +3571,9 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
3571 }3571 }
35723572
3573 // Collect messages with usable content3573 // Collect messages with usable content
3574 let coreChat = chat.filter(x => !x.is_system);3574 const canUseTools = ToolManager.isToolCallingSupported();
3575 const canPerformToolCalls = !dryRun && ToolManager.canPerformToolCalls(type);
3576 let coreChat = chat.filter(x => !x.is_system || (canUseTools && Array.isArray(x.extra?.tool_invocations)));
3575 if (type === 'swipe') {3577 if (type === 'swipe') {
3576 coreChat.pop();3578 coreChat.pop();
3577 }3579 }
@@ -4406,8 +4408,8 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
4406 getMessage = continue_mag + getMessage;4408 getMessage = continue_mag + getMessage;
4407 }4409 }
44084410
4409 if (ToolManager.isFunctionCallingSupported() && Array.isArray(streamingProcessor.toolCalls) && streamingProcessor.toolCalls.length) {4411 if (canPerformToolCalls && Array.isArray(streamingProcessor.toolCalls) && streamingProcessor.toolCalls.length) {
4410 const invocations = await ToolManager.checkFunctionToolCalls(streamingProcessor.toolCalls);4412 const invocations = await ToolManager.invokeFunctionTools(streamingProcessor.toolCalls);
4411 if (Array.isArray(invocations) && invocations.length) {4413 if (Array.isArray(invocations) && invocations.length) {
4412 const lastMessage = chat[chat.length - 1];4414 const lastMessage = chat[chat.length - 1];
4413 const shouldDeleteMessage = ['', '...'].includes(lastMessage?.mes) && ['', '...'].includes(streamingProcessor.result);4415 const shouldDeleteMessage = ['', '...'].includes(lastMessage?.mes) && ['', '...'].includes(streamingProcessor.result);
@@ -4455,14 +4457,6 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
4455 throw new Error(data?.response);4457 throw new Error(data?.response);
4456 }4458 }
44574459
4458 if (ToolManager.isFunctionCallingSupported()) {
4459 const invocations = await ToolManager.checkFunctionToolCalls(data);
4460 if (Array.isArray(invocations) && invocations.length) {
4461 ToolManager.saveFunctionToolInvocations(invocations);
4462 return Generate(type, { automatic_trigger, force_name2, quiet_prompt, quietToLoud, skipWIAN, force_chid, signal, quietImage, quietName }, dryRun);
4463 }
4464 }
4465
4466 //const getData = await response.json();4460 //const getData = await response.json();
4467 let getMessage = extractMessageFromData(data);4461 let getMessage = extractMessageFromData(data);
4468 let title = extractTitleFromData(data);4462 let title = extractTitleFromData(data);
@@ -4502,6 +4496,16 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
4502 parseAndSaveLogprobs(data, continue_mag);4496 parseAndSaveLogprobs(data, continue_mag);
4503 }4497 }
45044498
4499 if (canPerformToolCalls) {
4500 const invocations = await ToolManager.invokeFunctionTools(data);
4501 if (Array.isArray(invocations) && invocations.length) {
4502 const shouldDeleteMessage = ['', '...'].includes(getMessage);
4503 shouldDeleteMessage && await deleteLastMessage();
4504 ToolManager.saveFunctionToolInvocations(invocations);
4505 return Generate(type, { automatic_trigger, force_name2, quiet_prompt, quietToLoud, skipWIAN, force_chid, signal, quietImage, quietName }, dryRun);
4506 }
4507 }
4508
4505 if (type !== 'quiet') {4509 if (type !== 'quiet') {
4506 playMessageSound();4510 playMessageSound();
4507 }4511 }
public/scripts/openai.js+15 -8
@@ -703,7 +703,7 @@ async function populateChatHistory(messages, prompts, chatCompletion, type = nul
703 }703 }
704704
705 const imageInlining = isImageInliningSupported();705 const imageInlining = isImageInliningSupported();
706 const toolCalling = ToolManager.isFunctionCallingSupported();706 const canUseTools = ToolManager.isToolCallingSupported();
707707
708 // Insert chat messages as long as there is budget available708 // Insert chat messages as long as there is budget available
709 const chatPool = [...messages].reverse();709 const chatPool = [...messages].reverse();
@@ -725,10 +725,10 @@ async function populateChatHistory(messages, prompts, chatCompletion, type = nul
725 await chatMessage.addImage(chatPrompt.image);725 await chatMessage.addImage(chatPrompt.image);
726 }726 }
727727
728 if (toolCalling && Array.isArray(chatPrompt.invocations)) {728 if (canUseTools && Array.isArray(chatPrompt.invocations)) {
729 /** @type {import('./tool-calling.js').ToolInvocation[]} */729 /** @type {import('./tool-calling.js').ToolInvocation[]} */
730 const invocations = chatPrompt.invocations;730 const invocations = chatPrompt.invocations;
731 const toolCallMessage = new Message('assistant', undefined, 'toolCall-' + chatMessage.identifier);731 const toolCallMessage = new Message(chatMessage.role, undefined, 'toolCall-' + chatMessage.identifier);
732 toolCallMessage.setToolCalls(invocations);732 toolCallMessage.setToolCalls(invocations);
733 if (chatCompletion.canAfford(toolCallMessage)) {733 if (chatCompletion.canAfford(toolCallMessage)) {
734 chatCompletion.reserveBudget(toolCallMessage);734 chatCompletion.reserveBudget(toolCallMessage);
@@ -1285,7 +1285,7 @@ export async function prepareOpenAIMessages({
1285 const eventData = { chat, dryRun };1285 const eventData = { chat, dryRun };
1286 await eventSource.emit(event_types.CHAT_COMPLETION_PROMPT_READY, eventData);1286 await eventSource.emit(event_types.CHAT_COMPLETION_PROMPT_READY, eventData);
12871287
1288 openai_messages_count = chat.filter(x => x?.role === 'user' || x?.role === 'assistant')?.length || 0;1288 openai_messages_count = chat.filter(x => !x?.tool_calls && (x?.role === 'user' || x?.role === 'assistant'))?.length || 0;
12891289
1290 return [chat, promptManager.tokenHandler.counts];1290 return [chat, promptManager.tokenHandler.counts];
1291}1291}
@@ -1886,7 +1886,7 @@ async function sendOpenAIRequest(type, messages, signal) {
1886 generate_data['seed'] = oai_settings.seed;1886 generate_data['seed'] = oai_settings.seed;
1887 }1887 }
18881888
1889 if (!canMultiSwipe && ToolManager.isFunctionCallingSupported()) {1889 if (!canMultiSwipe && ToolManager.canPerformToolCalls(type)) {
1890 await ToolManager.registerFunctionToolsOpenAI(generate_data);1890 await ToolManager.registerFunctionToolsOpenAI(generate_data);
1891 }1891 }
18921892
@@ -2393,13 +2393,20 @@ class MessageCollection {
2393 }2393 }
23942394
2395 /**2395 /**
2396 * Get chat in the format of {role, name, content}.2396 * Get chat in the format of {role, name, content, tool_calls}.
2397 * @returns {Array} Array of objects with role, name, and content properties.2397 * @returns {Array} Array of objects with role, name, and content properties.
2398 */2398 */
2399 getChat() {2399 getChat() {
2400 return this.collection.reduce((acc, message) => {2400 return this.collection.reduce((acc, message) => {
2401 const name = message.name;2401 if (message.content || message.tool_calls) {
2402 if (message.content) acc.push({ role: message.role, ...(name && { name }), content: message.content });2402 acc.push({
2403 role: message.role,
2404 content: message.content,
2405 ...(message.name && { name: message.name }),
2406 ...(message.tool_calls && { tool_calls: message.tool_calls }),
2407 ...(message.role === 'tool' && { tool_call_id: message.identifier }),
2408 });
2409 }
2403 return acc;2410 return acc;
2404 }, []);2411 }, []);
2405 }2412 }
public/scripts/tool-calling.js+41 -25
@@ -1,4 +1,4 @@
1import { chat, main_api } 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';
33
4/**4/**
@@ -243,12 +243,12 @@ export class ToolManager {
243 }243 }
244 }244 }
245245
246 static isFunctionCallingSupported() {246 /**
247 if (main_api !== 'openai') {247 * Checks if tool calling is supported for the current settings and generation type.
248 return false;248 * @returns {boolean} Whether tool calling is supported for the given type
249 }249 */
250250 static isToolCallingSupported() {
251 if (!oai_settings.function_calling) {251 if (main_api !== 'openai' || !oai_settings.function_calling) {
252 return false;252 return false;
253 }253 }
254254
@@ -264,6 +264,22 @@ export class ToolManager {
264 return supportedSources.includes(oai_settings.chat_completion_source);264 return supportedSources.includes(oai_settings.chat_completion_source);
265 }265 }
266266
267 /**
268 * Checks if tool calls can be performed for the current settings and generation type.
269 * @param {string} type Generation type
270 * @returns {boolean} Whether tool calls can be performed for the given type
271 */
272 static canPerformToolCalls(type) {
273 const noToolCallTypes = ['swipe', 'impersonate', 'quiet', 'continue'];
274 const isSupported = ToolManager.isToolCallingSupported();
275 return isSupported && !noToolCallTypes.includes(type);
276 }
277
278 /**
279 * Utility function to get tool calls from the response data.
280 * @param {any} data Response data
281 * @returns {any[]} Tool calls from the response data
282 */
267 static #getToolCallsFromData(data) {283 static #getToolCallsFromData(data) {
268 // Parsed tool calls from streaming data284 // Parsed tool calls from streaming data
269 if (Array.isArray(data) && data.length > 0) {285 if (Array.isArray(data) && data.length > 0) {
@@ -290,15 +306,11 @@ export class ToolManager {
290 * @param {any} data Reply data306 * @param {any} data Reply data
291 * @returns {Promise<ToolInvocation[]>} Successful tool invocations307 * @returns {Promise<ToolInvocation[]>} Successful tool invocations
292 */308 */
293 static async checkFunctionToolCalls(data) {309 static async invokeFunctionTools(data) {
294 if (!ToolManager.isFunctionCallingSupported()) {
295 return [];
296 }
297
298 /** @type {ToolInvocation[]} */310 /** @type {ToolInvocation[]} */
299 const invocations = [];311 const invocations = [];
300 const toolCalls = ToolManager.#getToolCallsFromData(data);312 const toolCalls = ToolManager.#getToolCallsFromData(data);
301 const oaiCompat = [313 const oaiCompatibleSources = [
302 chat_completion_sources.OPENAI,314 chat_completion_sources.OPENAI,
303 chat_completion_sources.CUSTOM,315 chat_completion_sources.CUSTOM,
304 chat_completion_sources.MISTRALAI,316 chat_completion_sources.MISTRALAI,
@@ -306,7 +318,7 @@ export class ToolManager {
306 chat_completion_sources.GROQ,318 chat_completion_sources.GROQ,
307 ];319 ];
308320
309 if (oaiCompat.includes(oai_settings.chat_completion_source)) {321 if (oaiCompatibleSources.includes(oai_settings.chat_completion_source)) {
310 if (!Array.isArray(toolCalls)) {322 if (!Array.isArray(toolCalls)) {
311 return [];323 return [];
312 }324 }
@@ -323,7 +335,7 @@ export class ToolManager {
323335
324 toastr.info('Invoking function tool: ' + name);336 toastr.info('Invoking function tool: ' + name);
325 const result = await ToolManager.invokeFunctionTool(name, parameters);337 const result = await ToolManager.invokeFunctionTool(name, parameters);
326 toastr.info('Function tool result: ' + result);338 console.log('Function tool result:', result);
327339
328 // Save a successful invocation340 // Save a successful invocation
329 if (result) {341 if (result) {
@@ -367,15 +379,19 @@ export class ToolManager {
367 * @param {ToolInvocation[]} invocations Successful tool invocations379 * @param {ToolInvocation[]} invocations Successful tool invocations
368 */380 */
369 static saveFunctionToolInvocations(invocations) {381 static saveFunctionToolInvocations(invocations) {
370 for (let index = chat.length - 1; index >= 0; index--) {382 const toolNames = invocations.map(i => i.name).join(', ');
371 const message = chat[index];383 const message = {
372 if (message.is_user) {384 name: systemUserName,
373 if (!message.extra || typeof message.extra !== 'object') {385 force_avatar: system_avatar,
374 message.extra = {};386 is_system: true,
375 }387 is_user: false,
376 message.extra.tool_invocations = invocations;388 mes: `Performed tool calls: ${toolNames}`,
377 break;389 extra: {
378 }390 isSmallSys: true,
379 }391 tool_invocations: invocations,
392 },
393 };
394 chat.push(message);
395 addOneMessage(message);
380 }396 }
381}397}