Merge pull request #2976 from SillyTavern/cc-async-count Chat Completion: switch to async token handling

346e77ecfc4fd38ef9442aa38db499062dd7d9c4

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

Signed
2 files changed, +115 -69Showing whitespace changes
public/scripts/PromptManager.js+1 -1
@@ -315,7 +315,7 @@ class PromptManager {
315 */315 */
316 init(moduleConfiguration, serviceSettings) {316 init(moduleConfiguration, serviceSettings) {
317 this.configuration = Object.assign(this.configuration, moduleConfiguration);317 this.configuration = Object.assign(this.configuration, moduleConfiguration);
318 this.tokenHandler = this.tokenHandler || new TokenHandler();318 this.tokenHandler = this.tokenHandler || new TokenHandler(() => { throw new Error('Token handler not set'); });
319 this.serviceSettings = serviceSettings;319 this.serviceSettings = serviceSettings;
320 this.containerElement = document.getElementById(this.configuration.containerIdentifier);320 this.containerElement = document.getElementById(this.configuration.containerIdentifier);
321321
public/scripts/openai.js+114 -68
@@ -60,7 +60,7 @@ import {
60 resetScrollHeight,60 resetScrollHeight,
61 stringFormat,61 stringFormat,
62} from './utils.js';62} from './utils.js';
63import { countTokensOpenAI, getTokenizerModel } from './tokenizers.js';63import { countTokensOpenAIAsync, getTokenizerModel } from './tokenizers.js';
64import { isMobile } from './RossAscends-mods.js';64import { isMobile } from './RossAscends-mods.js';
65import { saveLogprobsForActiveMessage } from './logprobs.js';65import { saveLogprobsForActiveMessage } from './logprobs.js';
66import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';66import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
@@ -671,14 +671,14 @@ async function populateChatHistory(messages, prompts, chatCompletion, type = nul
671671
672 // Reserve budget for new chat message672 // Reserve budget for new chat message
673 const newChat = selected_group ? oai_settings.new_group_chat_prompt : oai_settings.new_chat_prompt;673 const newChat = selected_group ? oai_settings.new_group_chat_prompt : oai_settings.new_chat_prompt;
674 const newChatMessage = new Message('system', substituteParams(newChat), 'newMainChat');674 const newChatMessage = await Message.createAsync('system', substituteParams(newChat), 'newMainChat');
675 chatCompletion.reserveBudget(newChatMessage);675 chatCompletion.reserveBudget(newChatMessage);
676676
677 // Reserve budget for group nudge677 // Reserve budget for group nudge
678 let groupNudgeMessage = null;678 let groupNudgeMessage = null;
679 const noGroupNudgeTypes = ['impersonate'];679 const noGroupNudgeTypes = ['impersonate'];
680 if (selected_group && prompts.has('groupNudge') && !noGroupNudgeTypes.includes(type)) {680 if (selected_group && prompts.has('groupNudge') && !noGroupNudgeTypes.includes(type)) {
681 groupNudgeMessage = Message.fromPrompt(prompts.get('groupNudge'));681 groupNudgeMessage = await Message.fromPromptAsync(prompts.get('groupNudge'));
682 chatCompletion.reserveBudget(groupNudgeMessage);682 chatCompletion.reserveBudget(groupNudgeMessage);
683 }683 }
684684
@@ -693,12 +693,12 @@ async function populateChatHistory(messages, prompts, chatCompletion, type = nul
693 };693 };
694 const continuePrompt = new Prompt(promptObject);694 const continuePrompt = new Prompt(promptObject);
695 const preparedPrompt = promptManager.preparePrompt(continuePrompt);695 const preparedPrompt = promptManager.preparePrompt(continuePrompt);
696 continueMessage = Message.fromPrompt(preparedPrompt);696 continueMessage = await Message.fromPromptAsync(preparedPrompt);
697 chatCompletion.reserveBudget(continueMessage);697 chatCompletion.reserveBudget(continueMessage);
698 }698 }
699699
700 const lastChatPrompt = messages[messages.length - 1];700 const lastChatPrompt = messages[messages.length - 1];
701 const message = new Message('user', oai_settings.send_if_empty, 'emptyUserMessageReplacement');701 const message = await Message.createAsync('user', oai_settings.send_if_empty, 'emptyUserMessageReplacement');
702 if (lastChatPrompt && lastChatPrompt.role === 'assistant' && oai_settings.send_if_empty && chatCompletion.canAfford(message)) {702 if (lastChatPrompt && lastChatPrompt.role === 'assistant' && oai_settings.send_if_empty && chatCompletion.canAfford(message)) {
703 chatCompletion.insert(message, 'chatHistory');703 chatCompletion.insert(message, 'chatHistory');
704 }704 }
@@ -715,11 +715,11 @@ async function populateChatHistory(messages, prompts, chatCompletion, type = nul
715 // We do not want to mutate the prompt715 // We do not want to mutate the prompt
716 const prompt = new Prompt(chatPrompt);716 const prompt = new Prompt(chatPrompt);
717 prompt.identifier = `chatHistory-${messages.length - index}`;717 prompt.identifier = `chatHistory-${messages.length - index}`;
718 const chatMessage = Message.fromPrompt(promptManager.preparePrompt(prompt));718 const chatMessage = await Message.fromPromptAsync(promptManager.preparePrompt(prompt));
719719
720 if (promptManager.serviceSettings.names_behavior === character_names_behavior.COMPLETION && prompt.name) {720 if (promptManager.serviceSettings.names_behavior === character_names_behavior.COMPLETION && prompt.name) {
721 const messageName = promptManager.isValidName(prompt.name) ? prompt.name : promptManager.sanitizeName(prompt.name);721 const messageName = promptManager.isValidName(prompt.name) ? prompt.name : promptManager.sanitizeName(prompt.name);
722 chatMessage.setName(messageName);722 await chatMessage.setName(messageName);
723 }723 }
724724
725 if (imageInlining && chatPrompt.image) {725 if (imageInlining && chatPrompt.image) {
@@ -729,9 +729,9 @@ async function populateChatHistory(messages, prompts, chatCompletion, type = nul
729 if (canUseTools && Array.isArray(chatPrompt.invocations)) {729 if (canUseTools && Array.isArray(chatPrompt.invocations)) {
730 /** @type {import('./tool-calling.js').ToolInvocation[]} */730 /** @type {import('./tool-calling.js').ToolInvocation[]} */
731 const invocations = chatPrompt.invocations;731 const invocations = chatPrompt.invocations;
732 const toolCallMessage = new Message(chatMessage.role, undefined, 'toolCall-' + chatMessage.identifier);732 const toolCallMessage = await Message.createAsync(chatMessage.role, undefined, 'toolCall-' + chatMessage.identifier);
733 const toolResultMessages = invocations.slice().reverse().map((invocation) => new Message('tool', invocation.result || '[No content]', invocation.id));733 const toolResultMessages = await Promise.all(invocations.slice().reverse().map((invocation) => Message.createAsync('tool', invocation.result || '[No content]', invocation.id)));
734 toolCallMessage.setToolCalls(invocations);734 await toolCallMessage.setToolCalls(invocations);
735 if (chatCompletion.canAffordAll([toolCallMessage, ...toolResultMessages])) {735 if (chatCompletion.canAffordAll([toolCallMessage, ...toolResultMessages])) {
736 for (const resultMessage of toolResultMessages) {736 for (const resultMessage of toolResultMessages) {
737 chatCompletion.insertAtStart(resultMessage, 'chatHistory');737 chatCompletion.insertAtStart(resultMessage, 'chatHistory');
@@ -748,7 +748,8 @@ async function populateChatHistory(messages, prompts, chatCompletion, type = nul
748 if (type === 'continue' && oai_settings.continue_prefill && chatPrompt === firstNonInjected) {748 if (type === 'continue' && oai_settings.continue_prefill && chatPrompt === firstNonInjected) {
749 // 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 message749 // 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
750 if (chatPrompt.role === 'assistant') {750 if (chatPrompt.role === 'assistant') {
751 const collection = new MessageCollection('continuePrefill', new Message(chatMessage.role, substituteParams(oai_settings.assistant_prefill + '\n\n') + chatMessage.content, chatMessage.identifier));751 const continueMessage = await Message.createAsync(chatMessage.role, substituteParams(oai_settings.assistant_prefill + '\n\n') + chatMessage.content, chatMessage.identifier);
752 const collection = new MessageCollection('continuePrefill', continueMessage);
752 chatCompletion.add(collection, -1);753 chatCompletion.add(collection, -1);
753 continue;754 continue;
754 }755 }
@@ -787,18 +788,17 @@ async function populateChatHistory(messages, prompts, chatCompletion, type = nul
787 * @param {ChatCompletion} chatCompletion - An instance of ChatCompletion class that will be populated with the prompts.788 * @param {ChatCompletion} chatCompletion - An instance of ChatCompletion class that will be populated with the prompts.
788 * @param {Object[]} messageExamples - Array containing all message examples.789 * @param {Object[]} messageExamples - Array containing all message examples.
789 */790 */
790function populateDialogueExamples(prompts, chatCompletion, messageExamples) {791async function populateDialogueExamples(prompts, chatCompletion, messageExamples) {
791 if (!prompts.has('dialogueExamples')) {792 if (!prompts.has('dialogueExamples')) {
792 return;793 return;
793 }794 }
794795
795 chatCompletion.add(new MessageCollection('dialogueExamples'), prompts.index('dialogueExamples'));796 chatCompletion.add(new MessageCollection('dialogueExamples'), prompts.index('dialogueExamples'));
796 if (Array.isArray(messageExamples) && messageExamples.length) {797 if (Array.isArray(messageExamples) && messageExamples.length) {
797 const newExampleChat = new Message('system', substituteParams(oai_settings.new_example_chat_prompt), 'newChat');798 const newExampleChat = await Message.createAsync('system', substituteParams(oai_settings.new_example_chat_prompt), 'newChat');
798 [...messageExamples].forEach((dialogue, dialogueIndex) => {799 for (const dialogue of [...messageExamples]) {
799 let examplesAdded = 0;800 const dialogueIndex = messageExamples.indexOf(dialogue);
800801 const chatMessages = [];
801 if (chatCompletion.canAfford(newExampleChat)) chatCompletion.insert(newExampleChat, 'dialogueExamples');
802802
803 for (let promptIndex = 0; promptIndex < dialogue.length; promptIndex++) {803 for (let promptIndex = 0; promptIndex < dialogue.length; promptIndex++) {
804 const prompt = dialogue[promptIndex];804 const prompt = dialogue[promptIndex];
@@ -806,19 +806,20 @@ function populateDialogueExamples(prompts, chatCompletion, messageExamples) {
806 const content = prompt.content || '';806 const content = prompt.content || '';
807 const identifier = `dialogueExamples ${dialogueIndex}-${promptIndex}`;807 const identifier = `dialogueExamples ${dialogueIndex}-${promptIndex}`;
808808
809 const chatMessage = new Message(role, content, identifier);809 const chatMessage = await Message.createAsync(role, content, identifier);
810 chatMessage.setName(prompt.name);810 await chatMessage.setName(prompt.name);
811 if (!chatCompletion.canAfford(chatMessage)) {811 chatMessages.push(chatMessage);
812 }
813
814 if (!chatCompletion.canAffordAll([newExampleChat, ...chatMessages])) {
812 break;815 break;
813 }816 }
817
818 chatCompletion.insert(newExampleChat, 'dialogueExamples');
819 for (const chatMessage of chatMessages) {
814 chatCompletion.insert(chatMessage, 'dialogueExamples');820 chatCompletion.insert(chatMessage, 'dialogueExamples');
815 examplesAdded++;
816 }821 }
817
818 if (0 === examplesAdded) {
819 chatCompletion.removeLastFrom('dialogueExamples');
820 }822 }
821 });
822 }823 }
823}824}
824825
@@ -873,7 +874,7 @@ function getPromptRole(role) {
873 */874 */
874async function populateChatCompletion(prompts, chatCompletion, { bias, quietPrompt, quietImage, type, cyclePrompt, messages, messageExamples }) {875async function populateChatCompletion(prompts, chatCompletion, { bias, quietPrompt, quietImage, type, cyclePrompt, messages, messageExamples }) {
875 // Helper function for preparing a prompt, that already exists within the prompt collection, for completion876 // Helper function for preparing a prompt, that already exists within the prompt collection, for completion
876 const addToChatCompletion = (source, target = null) => {877 const addToChatCompletion = async (source, target = null) => {
877 // We need the prompts array to determine a position for the source.878 // We need the prompts array to determine a position for the source.
878 if (false === prompts.has(source)) return;879 if (false === prompts.has(source)) return;
879880
@@ -891,30 +892,31 @@ async function populateChatCompletion(prompts, chatCompletion, { bias, quietProm
891892
892 const index = target ? prompts.index(target) : prompts.index(source);893 const index = target ? prompts.index(target) : prompts.index(source);
893 const collection = new MessageCollection(source);894 const collection = new MessageCollection(source);
894 collection.add(Message.fromPrompt(prompt));895 const message = await Message.fromPromptAsync(prompt);
896 collection.add(message);
895 chatCompletion.add(collection, index);897 chatCompletion.add(collection, index);
896 };898 };
897899
898 chatCompletion.reserveBudget(3); // every reply is primed with <|start|>assistant<|message|>900 chatCompletion.reserveBudget(3); // every reply is primed with <|start|>assistant<|message|>
899 // Character and world information901 // Character and world information
900 addToChatCompletion('worldInfoBefore');902 await addToChatCompletion('worldInfoBefore');
901 addToChatCompletion('main');903 await addToChatCompletion('main');
902 addToChatCompletion('worldInfoAfter');904 await addToChatCompletion('worldInfoAfter');
903 addToChatCompletion('charDescription');905 await addToChatCompletion('charDescription');
904 addToChatCompletion('charPersonality');906 await addToChatCompletion('charPersonality');
905 addToChatCompletion('scenario');907 await addToChatCompletion('scenario');
906 addToChatCompletion('personaDescription');908 await addToChatCompletion('personaDescription');
907909
908 // Collection of control prompts that will always be positioned last910 // Collection of control prompts that will always be positioned last
909 chatCompletion.setOverriddenPrompts(prompts.overriddenPrompts);911 chatCompletion.setOverriddenPrompts(prompts.overriddenPrompts);
910 const controlPrompts = new MessageCollection('controlPrompts');912 const controlPrompts = new MessageCollection('controlPrompts');
911913
912 const impersonateMessage = Message.fromPrompt(prompts.get('impersonate')) ?? null;914 const impersonateMessage = await Message.fromPromptAsync(prompts.get('impersonate')) ?? null;
913 if (type === 'impersonate') controlPrompts.add(impersonateMessage);915 if (type === 'impersonate') controlPrompts.add(impersonateMessage);
914916
915 // Add quiet prompt to control prompts917 // Add quiet prompt to control prompts
916 // This should always be last, even in control prompts. Add all further control prompts BEFORE this prompt918 // This should always be last, even in control prompts. Add all further control prompts BEFORE this prompt
917 const quietPromptMessage = Message.fromPrompt(prompts.get('quietPrompt')) ?? null;919 const quietPromptMessage = await Message.fromPromptAsync(prompts.get('quietPrompt')) ?? null;
918 if (quietPromptMessage && quietPromptMessage.content) {920 if (quietPromptMessage && quietPromptMessage.content) {
919 if (isImageInliningSupported() && quietImage) {921 if (isImageInliningSupported() && quietImage) {
920 await quietPromptMessage.addImage(quietImage);922 await quietPromptMessage.addImage(quietImage);
@@ -940,20 +942,23 @@ async function populateChatCompletion(prompts, chatCompletion, { bias, quietProm
940 return acc;942 return acc;
941 }, []);943 }, []);
942944
943 [...systemPrompts, ...userRelativePrompts].forEach(identifier => addToChatCompletion(identifier));945 for (const identifier of [...systemPrompts, ...userRelativePrompts]) {
946 await addToChatCompletion(identifier);
947 }
944948
945 // Add enhance definition instruction949 // Add enhance definition instruction
946 if (prompts.has('enhanceDefinitions')) addToChatCompletion('enhanceDefinitions');950 if (prompts.has('enhanceDefinitions')) await addToChatCompletion('enhanceDefinitions');
947951
948 // Bias952 // Bias
949 if (bias && bias.trim().length) addToChatCompletion('bias');953 if (bias && bias.trim().length) await addToChatCompletion('bias');
950954
951 // Tavern Extras - Summary955 // Tavern Extras - Summary
952 if (prompts.has('summary')) {956 if (prompts.has('summary')) {
953 const summary = prompts.get('summary');957 const summary = prompts.get('summary');
954958
955 if (summary.position) {959 if (summary.position) {
956 chatCompletion.insert(Message.fromPrompt(summary), 'main', summary.position);960 const message = await Message.fromPromptAsync(summary);
961 chatCompletion.insert(message, 'main', summary.position);
957 }962 }
958 }963 }
959964
@@ -962,7 +967,8 @@ async function populateChatCompletion(prompts, chatCompletion, { bias, quietProm
962 const authorsNote = prompts.get('authorsNote');967 const authorsNote = prompts.get('authorsNote');
963968
964 if (authorsNote.position) {969 if (authorsNote.position) {
965 chatCompletion.insert(Message.fromPrompt(authorsNote), 'main', authorsNote.position);970 const message = await Message.fromPromptAsync(authorsNote);
971 chatCompletion.insert(message, 'main', authorsNote.position);
966 }972 }
967 }973 }
968974
@@ -971,7 +977,8 @@ async function populateChatCompletion(prompts, chatCompletion, { bias, quietProm
971 const vectorsMemory = prompts.get('vectorsMemory');977 const vectorsMemory = prompts.get('vectorsMemory');
972978
973 if (vectorsMemory.position) {979 if (vectorsMemory.position) {
974 chatCompletion.insert(Message.fromPrompt(vectorsMemory), 'main', vectorsMemory.position);980 const message = await Message.fromPromptAsync(vectorsMemory);
981 chatCompletion.insert(message, 'main', vectorsMemory.position);
975 }982 }
976 }983 }
977984
@@ -980,7 +987,8 @@ async function populateChatCompletion(prompts, chatCompletion, { bias, quietProm
980 const vectorsDataBank = prompts.get('vectorsDataBank');987 const vectorsDataBank = prompts.get('vectorsDataBank');
981988
982 if (vectorsDataBank.position) {989 if (vectorsDataBank.position) {
983 chatCompletion.insert(Message.fromPrompt(vectorsDataBank), 'main', vectorsDataBank.position);990 const message = await Message.fromPromptAsync(vectorsDataBank);
991 chatCompletion.insert(message, 'main', vectorsDataBank.position);
984 }992 }
985 }993 }
986994
@@ -989,13 +997,15 @@ async function populateChatCompletion(prompts, chatCompletion, { bias, quietProm
989 const smartContext = prompts.get('smartContext');997 const smartContext = prompts.get('smartContext');
990998
991 if (smartContext.position) {999 if (smartContext.position) {
992 chatCompletion.insert(Message.fromPrompt(smartContext), 'main', smartContext.position);1000 const message = await Message.fromPromptAsync(smartContext);
1001 chatCompletion.insert(message, 'main', smartContext.position);
993 }1002 }
994 }1003 }
9951004
996 // Other relative extension prompts1005 // Other relative extension prompts
997 for (const prompt of prompts.collection.filter(p => p.extension && p.position)) {1006 for (const prompt of prompts.collection.filter(p => p.extension && p.position)) {
998 chatCompletion.insert(Message.fromPrompt(prompt), 'main', prompt.position);1007 const message = await Message.fromPromptAsync(prompt);
1008 chatCompletion.insert(message, 'main', prompt.position);
999 }1009 }
10001010
1001 // Pre-allocation of tokens for tool data1011 // Pre-allocation of tokens for tool data
@@ -1003,7 +1013,7 @@ async function populateChatCompletion(prompts, chatCompletion, { bias, quietProm
1003 const toolData = {};1013 const toolData = {};
1004 await ToolManager.registerFunctionToolsOpenAI(toolData);1014 await ToolManager.registerFunctionToolsOpenAI(toolData);
1005 const toolMessage = [{ role: 'user', content: JSON.stringify(toolData) }];1015 const toolMessage = [{ role: 'user', content: JSON.stringify(toolData) }];
1006 const toolTokens = tokenHandler.count(toolMessage);1016 const toolTokens = await tokenHandler.countAsync(toolMessage);
1007 chatCompletion.reserveBudget(toolTokens);1017 chatCompletion.reserveBudget(toolTokens);
1008 }1018 }
10091019
@@ -1012,11 +1022,11 @@ async function populateChatCompletion(prompts, chatCompletion, { bias, quietProm
10121022
1013 // Decide whether dialogue examples should always be added1023 // Decide whether dialogue examples should always be added
1014 if (power_user.pin_examples) {1024 if (power_user.pin_examples) {
1015 populateDialogueExamples(prompts, chatCompletion, messageExamples);1025 await populateDialogueExamples(prompts, chatCompletion, messageExamples);
1016 await populateChatHistory(messages, prompts, chatCompletion, type, cyclePrompt);1026 await populateChatHistory(messages, prompts, chatCompletion, type, cyclePrompt);
1017 } else {1027 } else {
1018 await populateChatHistory(messages, prompts, chatCompletion, type, cyclePrompt);1028 await populateChatHistory(messages, prompts, chatCompletion, type, cyclePrompt);
1019 populateDialogueExamples(prompts, chatCompletion, messageExamples);1029 await populateDialogueExamples(prompts, chatCompletion, messageExamples);
1020 }1030 }
10211031
1022 chatCompletion.freeBudget(controlPrompts);1032 chatCompletion.freeBudget(controlPrompts);
@@ -1281,7 +1291,7 @@ export async function prepareOpenAIMessages({
1281 promptManager.setChatCompletion(chatCompletion);1291 promptManager.setChatCompletion(chatCompletion);
12821292
1283 if (oai_settings.squash_system_messages && dryRun == false) {1293 if (oai_settings.squash_system_messages && dryRun == false) {
1284 chatCompletion.squashSystemMessages();1294 await chatCompletion.squashSystemMessages();
1285 }1295 }
12861296
1287 // All information is up-to-date, render.1297 // All information is up-to-date, render.
@@ -2127,8 +2137,11 @@ async function calculateLogitBias() {
2127}2137}
21282138
2129class TokenHandler {2139class TokenHandler {
2130 constructor(countTokenFn) {2140 /**
2131 this.countTokenFn = countTokenFn;2141 * @param {(messages: object[] | object, full?: boolean) => Promise<number>} countTokenAsyncFn Function to count tokens
2142 */
2143 constructor(countTokenAsyncFn) {
2144 this.countTokenAsyncFn = countTokenAsyncFn;
2132 this.counts = {2145 this.counts = {
2133 'start_chat': 0,2146 'start_chat': 0,
2134 'prompt': 0,2147 'prompt': 0,
@@ -2157,8 +2170,15 @@ class TokenHandler {
2157 this.counts[type] -= value;2170 this.counts[type] -= value;
2158 }2171 }
21592172
2160 count(messages, full, type) {2173 /**
2161 const token_count = this.countTokenFn(messages, full);2174 * Count tokens for a message or messages.
2175 * @param {object|any[]} messages Messages to count tokens for
2176 * @param {boolean} [full] Count full tokens
2177 * @param {string} [type] Identifier for the token count
2178 * @returns {Promise<number>} The token count
2179 */
2180 async countAsync(messages, full, type) {
2181 const token_count = await this.countTokenAsyncFn(messages, full);
2162 this.counts[type] += token_count;2182 this.counts[type] += token_count;
21632183
2164 return token_count;2184 return token_count;
@@ -2178,7 +2198,7 @@ class TokenHandler {
2178}2198}
21792199
21802200
2181const tokenHandler = new TokenHandler(countTokensOpenAI);2201const tokenHandler = new TokenHandler(countTokensOpenAIAsync);
21822202
2183// Thrown by ChatCompletion when a requested prompt couldn't be found.2203// Thrown by ChatCompletion when a requested prompt couldn't be found.
2184class IdentifierNotFoundError extends Error {2204class IdentifierNotFoundError extends Error {
@@ -2228,6 +2248,7 @@ class Message {
2228 * @param {string} role - The role of the entity creating the message.2248 * @param {string} role - The role of the entity creating the message.
2229 * @param {string} content - The actual content of the message.2249 * @param {string} content - The actual content of the message.
2230 * @param {string} identifier - A unique identifier for the message.2250 * @param {string} identifier - A unique identifier for the message.
2251 * @private Don't use this constructor directly. Use createAsync instead.
2231 */2252 */
2232 constructor(role, content, identifier) {2253 constructor(role, content, identifier) {
2233 this.identifier = identifier;2254 this.identifier = identifier;
@@ -2239,18 +2260,32 @@ class Message {
2239 this.role = 'system';2260 this.role = 'system';
2240 }2261 }
22412262
2242 if (typeof this.content === 'string' && this.content.length > 0) {
2243 this.tokens = tokenHandler.count({ role: this.role, content: this.content });
2244 } else {
2245 this.tokens = 0;2263 this.tokens = 0;
2246 }2264 }
2265
2266 /**
2267 * Create a new Message instance.
2268 * @param {string} role
2269 * @param {string} content
2270 * @param {string} identifier
2271 * @returns {Promise<Message>} Message instance
2272 */
2273 static async createAsync(role, content, identifier) {
2274 const message = new Message(role, content, identifier);
2275
2276 if (typeof message.content === 'string' && message.content.length > 0) {
2277 message.tokens = await tokenHandler.countAsync({ role: message.role, content: message.content });
2278 }
2279
2280 return message;
2247 }2281 }
22482282
2249 /**2283 /**
2250 * Reconstruct the message from a tool invocation.2284 * Reconstruct the message from a tool invocation.
2251 * @param {import('./tool-calling.js').ToolInvocation[]} invocations2285 * @param {import('./tool-calling.js').ToolInvocation[]} invocations - The tool invocations to reconstruct the message from.
2286 * @returns {Promise<void>}
2252 */2287 */
2253 setToolCalls(invocations) {2288 async setToolCalls(invocations) {
2254 this.tool_calls = invocations.map(i => ({2289 this.tool_calls = invocations.map(i => ({
2255 id: i.id,2290 id: i.id,
2256 type: 'function',2291 type: 'function',
@@ -2259,14 +2294,24 @@ class Message {
2259 name: i.name,2294 name: i.name,
2260 },2295 },
2261 }));2296 }));
2262 this.tokens = tokenHandler.count({ role: this.role, tool_calls: JSON.stringify(this.tool_calls) });2297 this.tokens = await tokenHandler.countAsync({ role: this.role, tool_calls: JSON.stringify(this.tool_calls) });
2263 }2298 }
22642299
2265 setName(name) {2300 /**
2301 * Add a name to the message.
2302 * @param {string} name Name to set for the message.
2303 * @returns {Promise<void>}
2304 */
2305 async setName(name) {
2266 this.name = name;2306 this.name = name;
2267 this.tokens = tokenHandler.count({ role: this.role, content: this.content, name: this.name });2307 this.tokens = await tokenHandler.countAsync({ role: this.role, content: this.content, name: this.name });
2268 }2308 }
22692309
2310 /**
2311 * Adds an image to the message.
2312 * @param {string} image Image URL or Data URL.
2313 * @returns {Promise<void>}
2314 */
2270 async addImage(image) {2315 async addImage(image) {
2271 const textContent = this.content;2316 const textContent = this.content;
2272 const isDataUrl = isDataURL(image);2317 const isDataUrl = isDataURL(image);
@@ -2356,13 +2401,13 @@ class Message {
2356 }2401 }
23572402
2358 /**2403 /**
2359 * Create a new Message instance from a prompt.2404 * Create a new Message instance from a prompt asynchronously.
2360 * @static2405 * @static
2361 * @param {Object} prompt - The prompt object.2406 * @param {Object} prompt - The prompt object.
2362 * @returns {Message} A new instance of Message.2407 * @returns {Promise<Message>} A new instance of Message.
2363 */2408 */
2364 static fromPrompt(prompt) {2409 static fromPromptAsync(prompt) {
2365 return new Message(prompt.role, prompt.content, prompt.identifier);2410 return Message.createAsync(prompt.role, prompt.content, prompt.identifier);
2366 }2411 }
23672412
2368 /**2413 /**
@@ -2488,8 +2533,9 @@ export class ChatCompletion {
24882533
2489 /**2534 /**
2490 * Combines consecutive system messages into one if they have no name attached.2535 * Combines consecutive system messages into one if they have no name attached.
2536 * @returns {Promise<void>}
2491 */2537 */
2492 squashSystemMessages() {2538 async squashSystemMessages() {
2493 const excludeList = ['newMainChat', 'newChat', 'groupNudge'];2539 const excludeList = ['newMainChat', 'newChat', 'groupNudge'];
2494 this.messages.collection = this.messages.flatten();2540 this.messages.collection = this.messages.flatten();
24952541
@@ -2509,7 +2555,7 @@ export class ChatCompletion {
2509 if (shouldSquash(message)) {2555 if (shouldSquash(message)) {
2510 if (lastMessage && shouldSquash(lastMessage)) {2556 if (lastMessage && shouldSquash(lastMessage)) {
2511 lastMessage.content += '\n' + message.content;2557 lastMessage.content += '\n' + message.content;
2512 lastMessage.tokens = tokenHandler.count({ role: lastMessage.role, content: lastMessage.content });2558 lastMessage.tokens = await tokenHandler.countAsync({ role: lastMessage.role, content: lastMessage.content });
2513 }2559 }
2514 else {2560 else {
2515 squashedMessages.push(message);2561 squashedMessages.push(message);