Merge pull request #2842 from SillyTavern/o1 OpenAI O1

5dd1d26350b3a6d98f5f53e52150d2e8c5090820

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

Signed
7 files changed, +74 -65Ignore whitespace
package-lock.json+5 -4
@@ -46,7 +46,7 @@
4646 "sanitize-filename": "^1.6.3",
4747 "sillytavern-transformers": "2.14.6",
4848 "simple-git": "^3.19.1",
4949 "tiktoken": "^1.0.1516",
5050 "vectra": "^0.2.2",
5151 "wavefile": "^11.0.0",
5252 "write-file-atomic": "^5.0.1",
@@ -5751,9 +5751,10 @@
57515751 "license": "MIT"
57525752 },
57535753 "node_modules/tiktoken": {
57545754 "version": "1.0.1516",
57555755 "resolved": "https://registry.npmjs.org/tiktoken/-/tiktoken-1.0.1516.tgz",
57565756 "integrity": "sha512-sCsrq/vMWUSEW29CJLNmPvWxlVp7yh2tlkAjpJltIKqp5CKf98ZNpdeHRmAlPVFlGEbswDc6SmI8vz64WhRcORIGF2YlAgWx3nzrGJOrKSJwLoc81HpXmMQk89632XAgURc7IeV2FgQ2iXo9z/qErwJ96fCvpsHg2kWoHcbj9fg==",
5757+ "license": "MIT"
57575758 },
57585759 "node_modules/timm": {
57595760 "version": "1.7.1",
package.json+1 -1
@@ -36,7 +36,7 @@
3636 "sanitize-filename": "^1.6.3",
3737 "sillytavern-transformers": "2.14.6",
3838 "simple-git": "^3.19.1",
3939 "tiktoken": "^1.0.1516",
4040 "vectra": "^0.2.2",
4141 "wavefile": "^11.0.0",
4242 "write-file-atomic": "^5.0.1",
public/index.html+5 -1
@@ -383,7 +383,7 @@
383383 Max Response Length (tokens)
384384 </div>
385385 <div class="wide100p">
386386 <input type="number" id="openai_max_tokens" name="openai_max_tokens" class="text_pole" min="1" max="1638465536">
387387 </div>
388388 </div>
389389 <div class="range-block" data-source="openai,custom">
@@ -2611,6 +2611,10 @@
26112611 <option value="gpt-4-0125-preview">gpt-4-0125-preview (2024)</option>
26122612 <option value="gpt-4-1106-preview">gpt-4-1106-preview (2023)</option>
26132613 </optgroup>
2614+ <optgroup label="o1">
2615+ <option value="o1-preview">o1-preview</option>
2616+ <option value="o1-mini">o1-mini</option>
2617+ </optgroup>
26142618 <optgroup label="Other">
26152619 <option value="text-davinci-003">text-davinci-003</option>
26162620 <option value="text-davinci-002">text-davinci-002</option>
public/script.js+29 -53
@@ -881,7 +881,6 @@ let abortController;
881881
882882//css
883883var css_send_form_display = $('<div id=send_form></div>').css('display');
884-const MAX_GENERATION_LOOPS = 5;
885884
886885var kobold_horde_model = '';
887886
@@ -2862,7 +2861,12 @@ export function getCharacterCardFields() {
28622861
28632862export function isStreamingEnabled() {
28642863 const noStreamSources = [chat_completion_sources.SCALE];
2865- return ((main_api == 'openai' && oai_settings.stream_openai && !noStreamSources.includes(oai_settings.chat_completion_source) && !(oai_settings.chat_completion_source == chat_completion_sources.MAKERSUITE && oai_settings.google_model.includes('bison')))
2864+ return (
2865+ (main_api == 'openai' &&
2866+ oai_settings.stream_openai &&
2867+ !noStreamSources.includes(oai_settings.chat_completion_source) &&
2868+ !(oai_settings.chat_completion_source == chat_completion_sources.OPENAI && oai_settings.openai_model.startsWith('o1-')) &&
2869+ !(oai_settings.chat_completion_source == chat_completion_sources.MAKERSUITE && oai_settings.google_model.includes('bison')))
28662870 || (main_api == 'kobold' && kai_settings.streaming_kobold && kai_flags.can_use_streaming)
28672871 || (main_api == 'novel' && nai_settings.streaming_novel)
28682872 || (main_api == 'textgenerationwebui' && textgen_settings.streaming));
@@ -3337,11 +3341,11 @@ function removeLastMessage() {
33373341 * @param {GenerateOptions} options Generation options
33383342 * @param {boolean} dryRun Whether to actually generate a message or just assemble the prompt
33393343 * @returns {Promise<any>} Returns a promise that resolves when the text is done generating.
33403344 * @typedef {{automatic_trigger?: boolean, force_name2?: boolean, quiet_prompt?: string, quietToLoud?: boolean, skipWIAN?: boolean, force_chid?: number, signal?: AbortSignal, quietImage?: string, maxLoops?: number, quietName?: string }} GenerateOptions
33413345 */
33423346export async function Generate(type, { automatic_trigger, force_name2, quiet_prompt, quietToLoud, skipWIAN, force_chid, signal, quietImage, maxLoops, quietName } = {}, dryRun = false) {
33433347 console.log('Generate entered');
33443348 await eventSource.emit(event_types.GENERATION_STARTED, type, { automatic_trigger, force_name2, quiet_prompt, quietToLoud, skipWIAN, force_chid, signal, quietImage, maxLoops }, dryRun);
33453349 setGenerationProgress(0);
33463350 generation_started = new Date();
33473351
@@ -3403,7 +3407,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
34033407 if (selected_group && !is_group_generating) {
34043408 if (!dryRun) {
34053409 // Returns the promise that generateGroupWrapper returns; resolves when generation is done
34063410 return generateGroupWrapper(false, type, { quiet_prompt, force_chid, signal: abortController.signal, quietImage, maxLoops });
34073411 }
34083412
34093413 const characterIndexMap = new Map(characters.map((char, index) => [char.avatar, index]));
@@ -4435,53 +4439,30 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
44354439 const displayIncomplete = type === 'quiet' && !quietToLoud;
44364440 getMessage = cleanUpMessage(getMessage, isImpersonate, isContinue, displayIncomplete);
44374441
4438- if (getMessage.length > 0 || data.allowEmptyResponse) {
4442+ if (isImpersonate) {
4439- if (isImpersonate) {
4443+ $('#send_textarea').val(getMessage)[0].dispatchEvent(new Event('input', { bubbles: true }));
4440- $('#send_textarea').val(getMessage)[0].dispatchEvent(new Event('input', { bubbles: true }));
4444+ generatedPromptCache = '';
4441- generatedPromptCache = '';
4445+ await eventSource.emit(event_types.IMPERSONATE_READY, getMessage);
4442- await eventSource.emit(event_types.IMPERSONATE_READY, getMessage);
4446+ }
4443- }
4447+ else if (type == 'quiet') {
4444- else if (type == 'quiet') {
4448+ unblockGeneration(type);
4445- unblockGeneration(type);
4449+ return getMessage;
4446- return getMessage;
4450+ }
4451+ else {
4452+ // Without streaming we'll be having a full message on continuation. Treat it as a last chunk.
4453+ if (originalType !== 'continue') {
4454+ ({ type, getMessage } = await saveReply(type, getMessage, false, title, swipes));
44474455 }
44484456 else {
4449- // Without streaming we'll be having a full message on continuation. Treat it as a last chunk.
4457+ ({ type, getMessage } = await saveReply('appendFinal', getMessage, false, title, swipes));
4450- if (originalType !== 'continue') {
4451- ({ type, getMessage } = await saveReply(type, getMessage, false, title, swipes));
4452- }
4453- else {
4454- ({ type, getMessage } = await saveReply('appendFinal', getMessage, false, title, swipes));
4455- }
4456-
4457- // This relies on `saveReply` having been called to add the message to the chat, so it must be last.
4458- parseAndSaveLogprobs(data, continue_mag);
44594458 }
44604459
4461- if (type !== 'quiet') {
4460+ // This relies on `saveReply` having been called to add the message to the chat, so it must be last.
4462- playMessageSound();
4461+ parseAndSaveLogprobs(data, continue_mag);
44634462 }
4464- } else {
4465- // If maxLoops is not passed in (e.g. first time generating), set it to MAX_GENERATION_LOOPS
4466- maxLoops ??= MAX_GENERATION_LOOPS;
4467-
4468- if (maxLoops === 0) {
4469- if (type !== 'quiet') {
4470- throwCircuitBreakerError();
4471- }
4472- throw new Error('Generate circuit breaker interruption');
4473- }
44744463
4475- // regenerate with character speech reenforced
4464+ if (type !== 'quiet') {
4476- // to make sure we leave on swipe type while also adding the name2 appendage
4465+ playMessageSound();
4477- await delay(1000);
4478- // A message was already deleted on regeneration, so instead treat is as a normal gen
4479- if (type === 'regenerate') {
4480- type = 'normal';
4481- }
4482- // The first await is for waiting for the generate to start. The second one is waiting for it to finish
4483- const result = await await Generate(type, { automatic_trigger, force_name2: true, quiet_prompt, quietToLoud, skipWIAN, force_chid, signal, quietImage, quietName, maxLoops: maxLoops - 1 });
4484- return result;
44854466 }
44864467
44874468 if (power_user.auto_swipe) {
@@ -5254,11 +5235,6 @@ function getGenerateUrl(api) {
52545235 }
52555236}
52565237
5257-function throwCircuitBreakerError() {
5258- callPopup(`Could not extract reply in ${MAX_GENERATION_LOOPS} attempts. Try generating again`, 'text');
5259- unblockGeneration();
5260-}
5261-
52625238function extractTitleFromData(data) {
52635239 if (main_api == 'koboldhorde') {
52645240 return data.workerName;
public/scripts/openai.js+29 -6
@@ -1797,7 +1797,7 @@ async function sendOpenAIRequest(type, messages, signal) {
17971797 const isQuiet = type === 'quiet';
17981798 const isImpersonate = type === 'impersonate';
17991799 const isContinue = type === 'continue';
18001800 const stream = oai_settings.stream_openai && !isQuiet && !isScale && !(isGoogle && oai_settings.google_model.includes('bison')) && !(isOAI && oai_settings.openai_model.startsWith('o1-'));
18011801 const useLogprobs = !!power_user.request_token_probabilities;
18021802 const canMultiSwipe = oai_settings.n > 1 && !isContinue && !isImpersonate && !isQuiet && (isOAI || isCustom);
18031803
@@ -1960,12 +1960,35 @@ async function sendOpenAIRequest(type, messages, signal) {
19601960 generate_data['seed'] = oai_settings.seed;
19611961 }
19621962
1963- await eventSource.emit(event_types.CHAT_COMPLETION_SETTINGS_READY, generate_data);
1964-
19651963 if (isFunctionCallingSupported() && !stream) {
19661964 await registerFunctionTools(type, generate_data);
19671965 }
19681966
1967+ if (isOAI && oai_settings.openai_model.startsWith('o1-')) {
1968+ generate_data.messages.forEach((msg) => {
1969+ if (msg.role === 'system') {
1970+ msg.role = 'user';
1971+ }
1972+ });
1973+ generate_data.max_completion_tokens = generate_data.max_tokens;
1974+ delete generate_data.max_tokens;
1975+ delete generate_data.stream;
1976+ delete generate_data.logprobs;
1977+ delete generate_data.top_logprobs;
1978+ delete generate_data.n;
1979+ delete generate_data.temperature;
1980+ delete generate_data.top_p;
1981+ delete generate_data.frequency_penalty;
1982+ delete generate_data.presence_penalty;
1983+ delete generate_data.tools;
1984+ delete generate_data.tool_choice;
1985+ delete generate_data.stop;
1986+ // It does support logit_bias, but the tokenizer used and its effect is yet unknown.
1987+ // delete generate_data.logit_bias;
1988+ }
1989+
1990+ await eventSource.emit(event_types.CHAT_COMPLETION_SETTINGS_READY, generate_data);
1991+
19691992 const generate_url = '/api/backends/chat-completions/generate';
19701993 const response = await fetch(generate_url, {
19711994 method: 'POST',
@@ -2111,7 +2134,6 @@ async function checkFunctionToolCalls(data) {
21112134 const args = toolCall.function;
21122135 console.log('Function tool call:', toolCall);
21132136 await eventSource.emit(event_types.LLM_FUNCTION_TOOL_CALL, args);
2114- data.allowEmptyResponse = true;
21152137 }
21162138 }
21172139
@@ -2125,7 +2147,6 @@ async function checkFunctionToolCalls(data) {
21252147 /** @type {FunctionToolCall} */
21262148 const args = { name: content.name, arguments: JSON.stringify(content.input) };
21272149 await eventSource.emit(event_types.LLM_FUNCTION_TOOL_CALL, args);
2128- data.allowEmptyResponse = true;
21292150 }
21302151 }
21312152 }
@@ -2140,7 +2161,6 @@ async function checkFunctionToolCalls(data) {
21402161 const args = { name: toolCall.name, arguments: JSON.stringify(toolCall.parameters) };
21412162 console.log('Function tool call:', toolCall);
21422163 await eventSource.emit(event_types.LLM_FUNCTION_TOOL_CALL, args);
2143- data.allowEmptyResponse = true;
21442164 }
21452165 }
21462166}
@@ -3905,6 +3925,9 @@ function getMaxContextOpenAI(value) {
39053925 if (oai_settings.max_context_unlocked) {
39063926 return unlocked_max;
39073927 }
3928+ else if (value.startsWith('o1-')) {
3929+ return max_128k;
3930+ }
39083931 else if (value.includes('chatgpt-4o-latest') || value.includes('gpt-4-turbo') || value.includes('gpt-4o') || value.includes('gpt-4-1106') || value.includes('gpt-4-0125') || value.includes('gpt-4-vision')) {
39093932 return max_128k;
39103933 }
src/endpoints/backends/chat-completions.js+1 -0
@@ -965,6 +965,7 @@ router.post('/generate', jsonParser, function (request, response) {
965965 'model': request.body.model,
966966 'temperature': request.body.temperature,
967967 'max_tokens': request.body.max_tokens,
968+ 'max_completion_tokens': request.body.max_completion_tokens,
968969 'stream': request.body.stream,
969970 'presence_penalty': request.body.presence_penalty,
970971 'frequency_penalty': request.body.frequency_penalty,
src/endpoints/tokenizers.js+4 -0
@@ -350,6 +350,10 @@ function getWebTokenizersChunks(tokenizer, ids) {
350350 * @returns {string} Tokenizer model to use
351351 */
352352function getTokenizerModel(requestModel) {
353+ if (requestModel.includes('o1-preview') || requestModel.includes('o1-mini')) {
354+ return 'gpt-4o';
355+ }
356+
353357 if (requestModel.includes('gpt-4o')) {
354358 return 'gpt-4o';
355359 }