Merge pull request #2842 from SillyTavern/o1 OpenAI O1
Signed| @@ -46,7 +46,7 @@ | ||
| 46 | 46 | "sanitize-filename": "^1.6.3", |
| 47 | 47 | "sillytavern-transformers": "2.14.6", |
| 48 | 48 | "simple-git": "^3.19.1", |
| 49 | 49 | "tiktoken": "^1.0.1516", |
| 50 | 50 | "vectra": "^0.2.2", |
| 51 | 51 | "wavefile": "^11.0.0", |
| 52 | 52 | "write-file-atomic": "^5.0.1", |
| @@ -5751,9 +5751,10 @@ | ||
| 5751 | 5751 | "license": "MIT" |
| 5752 | 5752 | }, |
| 5753 | 5753 | "node_modules/tiktoken": { |
| 5754 | 5754 | "version": "1.0.1516", |
| 5755 | 5755 | "resolved": "https://registry.npmjs.org/tiktoken/-/tiktoken-1.0.1516.tgz", |
| 5756 | 5756 | "integrity": "sha512-sCsrq/vMWUSEW29CJLNmPvWxlVp7yh2tlkAjpJltIKqp5CKf98ZNpdeHRmAlPVFlGEbswDc6SmI8vz64WhRcORIGF2YlAgWx3nzrGJOrKSJwLoc81HpXmMQk89632XAgURc7IeV2FgQ2iXo9z/qErwJ96fCvpsHg2kWoHcbj9fg==", |
| 5757 | + "license": "MIT" | |
| 5757 | 5758 | }, |
| 5758 | 5759 | "node_modules/timm": { |
| 5759 | 5760 | "version": "1.7.1", |
| @@ -36,7 +36,7 @@ | ||
| 36 | 36 | "sanitize-filename": "^1.6.3", |
| 37 | 37 | "sillytavern-transformers": "2.14.6", |
| 38 | 38 | "simple-git": "^3.19.1", |
| 39 | 39 | "tiktoken": "^1.0.1516", |
| 40 | 40 | "vectra": "^0.2.2", |
| 41 | 41 | "wavefile": "^11.0.0", |
| 42 | 42 | "write-file-atomic": "^5.0.1", |
| @@ -383,7 +383,7 @@ | ||
| 383 | 383 | Max Response Length (tokens) |
| 384 | 384 | </div> |
| 385 | 385 | <div class="wide100p"> |
| 386 | 386 | <input type="number" id="openai_max_tokens" name="openai_max_tokens" class="text_pole" min="1" max="1638465536"> |
| 387 | 387 | </div> |
| 388 | 388 | </div> |
| 389 | 389 | <div class="range-block" data-source="openai,custom"> |
| @@ -2611,6 +2611,10 @@ | ||
| 2611 | 2611 | <option value="gpt-4-0125-preview">gpt-4-0125-preview (2024)</option> |
| 2612 | 2612 | <option value="gpt-4-1106-preview">gpt-4-1106-preview (2023)</option> |
| 2613 | 2613 | </optgroup> |
| 2614 | + <optgroup label="o1"> | |
| 2615 | + <option value="o1-preview">o1-preview</option> | |
| 2616 | + <option value="o1-mini">o1-mini</option> | |
| 2617 | + </optgroup> | |
| 2614 | 2618 | <optgroup label="Other"> |
| 2615 | 2619 | <option value="text-davinci-003">text-davinci-003</option> |
| 2616 | 2620 | <option value="text-davinci-002">text-davinci-002</option> |
| @@ -881,7 +881,6 @@ let abortController; | ||
| 881 | 881 | |
| 882 | 882 | //css |
| 883 | 883 | var css_send_form_display = $('<div id=send_form></div>').css('display'); |
| 884 | -const MAX_GENERATION_LOOPS = 5; | |
| 885 | 884 | |
| 886 | 885 | var kobold_horde_model = ''; |
| 887 | 886 | |
| @@ -2862,7 +2861,12 @@ export function getCharacterCardFields() { | ||
| 2862 | 2861 | |
| 2863 | 2862 | export function isStreamingEnabled() { |
| 2864 | 2863 | 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'))) | |
| 2866 | 2870 | || (main_api == 'kobold' && kai_settings.streaming_kobold && kai_flags.can_use_streaming) |
| 2867 | 2871 | || (main_api == 'novel' && nai_settings.streaming_novel) |
| 2868 | 2872 | || (main_api == 'textgenerationwebui' && textgen_settings.streaming)); |
| @@ -3337,11 +3341,11 @@ function removeLastMessage() { | ||
| 3337 | 3341 | * @param {GenerateOptions} options Generation options |
| 3338 | 3342 | * @param {boolean} dryRun Whether to actually generate a message or just assemble the prompt |
| 3339 | 3343 | * @returns {Promise<any>} Returns a promise that resolves when the text is done generating. |
| 3340 | 3344 | * @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 |
| 3341 | 3345 | */ |
| 3342 | 3346 | export async function Generate(type, { automatic_trigger, force_name2, quiet_prompt, quietToLoud, skipWIAN, force_chid, signal, quietImage, maxLoops, quietName } = {}, dryRun = false) { |
| 3343 | 3347 | console.log('Generate entered'); |
| 3344 | 3348 | await eventSource.emit(event_types.GENERATION_STARTED, type, { automatic_trigger, force_name2, quiet_prompt, quietToLoud, skipWIAN, force_chid, signal, quietImage, maxLoops }, dryRun); |
| 3345 | 3349 | setGenerationProgress(0); |
| 3346 | 3350 | generation_started = new Date(); |
| 3347 | 3351 | |
| @@ -3403,7 +3407,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro | ||
| 3403 | 3407 | if (selected_group && !is_group_generating) { |
| 3404 | 3408 | if (!dryRun) { |
| 3405 | 3409 | // Returns the promise that generateGroupWrapper returns; resolves when generation is done |
| 3406 | 3410 | return generateGroupWrapper(false, type, { quiet_prompt, force_chid, signal: abortController.signal, quietImage, maxLoops }); |
| 3407 | 3411 | } |
| 3408 | 3412 | |
| 3409 | 3413 | const characterIndexMap = new Map(characters.map((char, index) => [char.avatar, index])); |
| @@ -4435,7 +4439,6 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro | ||
| 4435 | 4439 | const displayIncomplete = type === 'quiet' && !quietToLoud; |
| 4436 | 4440 | getMessage = cleanUpMessage(getMessage, isImpersonate, isContinue, displayIncomplete); |
| 4437 | 4441 | |
| 4438 | - if (getMessage.length > 0 || data.allowEmptyResponse) { | |
| 4439 | 4442 | if (isImpersonate) { |
| 4440 | 4443 | $('#send_textarea').val(getMessage)[0].dispatchEvent(new Event('input', { bubbles: true })); |
| 4441 | 4444 | generatedPromptCache = ''; |
| @@ -4461,28 +4464,6 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro | ||
| 4461 | 4464 | if (type !== 'quiet') { |
| 4462 | 4465 | playMessageSound(); |
| 4463 | 4466 | } |
| 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 | - } | |
| 4474 | - | |
| 4475 | - // regenerate with character speech reenforced | |
| 4476 | - // to make sure we leave on swipe type while also adding the name2 appendage | |
| 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; | |
| 4485 | - } | |
| 4486 | 4467 | |
| 4487 | 4468 | if (power_user.auto_swipe) { |
| 4488 | 4469 | console.debug('checking for autoswipeblacklist on non-streaming message'); |
| @@ -5254,11 +5235,6 @@ function getGenerateUrl(api) { | ||
| 5254 | 5235 | } |
| 5255 | 5236 | } |
| 5256 | 5237 | |
| 5257 | -function throwCircuitBreakerError() { | |
| 5258 | - callPopup(`Could not extract reply in ${MAX_GENERATION_LOOPS} attempts. Try generating again`, 'text'); | |
| 5259 | - unblockGeneration(); | |
| 5260 | -} | |
| 5261 | - | |
| 5262 | 5238 | function extractTitleFromData(data) { |
| 5263 | 5239 | if (main_api == 'koboldhorde') { |
| 5264 | 5240 | return data.workerName; |
| @@ -1797,7 +1797,7 @@ async function sendOpenAIRequest(type, messages, signal) { | ||
| 1797 | 1797 | const isQuiet = type === 'quiet'; |
| 1798 | 1798 | const isImpersonate = type === 'impersonate'; |
| 1799 | 1799 | const isContinue = type === 'continue'; |
| 1800 | 1800 | const stream = oai_settings.stream_openai && !isQuiet && !isScale && !(isGoogle && oai_settings.google_model.includes('bison')) && !(isOAI && oai_settings.openai_model.startsWith('o1-')); |
| 1801 | 1801 | const useLogprobs = !!power_user.request_token_probabilities; |
| 1802 | 1802 | const canMultiSwipe = oai_settings.n > 1 && !isContinue && !isImpersonate && !isQuiet && (isOAI || isCustom); |
| 1803 | 1803 | |
| @@ -1960,12 +1960,35 @@ async function sendOpenAIRequest(type, messages, signal) { | ||
| 1960 | 1960 | generate_data['seed'] = oai_settings.seed; |
| 1961 | 1961 | } |
| 1962 | 1962 | |
| 1963 | - await eventSource.emit(event_types.CHAT_COMPLETION_SETTINGS_READY, generate_data); | |
| 1964 | - | |
| 1965 | 1963 | if (isFunctionCallingSupported() && !stream) { |
| 1966 | 1964 | await registerFunctionTools(type, generate_data); |
| 1967 | 1965 | } |
| 1968 | 1966 | |
| 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 | + | |
| 1969 | 1992 | const generate_url = '/api/backends/chat-completions/generate'; |
| 1970 | 1993 | const response = await fetch(generate_url, { |
| 1971 | 1994 | method: 'POST', |
| @@ -2111,7 +2134,6 @@ async function checkFunctionToolCalls(data) { | ||
| 2111 | 2134 | const args = toolCall.function; |
| 2112 | 2135 | console.log('Function tool call:', toolCall); |
| 2113 | 2136 | await eventSource.emit(event_types.LLM_FUNCTION_TOOL_CALL, args); |
| 2114 | - data.allowEmptyResponse = true; | |
| 2115 | 2137 | } |
| 2116 | 2138 | } |
| 2117 | 2139 | |
| @@ -2125,7 +2147,6 @@ async function checkFunctionToolCalls(data) { | ||
| 2125 | 2147 | /** @type {FunctionToolCall} */ |
| 2126 | 2148 | const args = { name: content.name, arguments: JSON.stringify(content.input) }; |
| 2127 | 2149 | await eventSource.emit(event_types.LLM_FUNCTION_TOOL_CALL, args); |
| 2128 | - data.allowEmptyResponse = true; | |
| 2129 | 2150 | } |
| 2130 | 2151 | } |
| 2131 | 2152 | } |
| @@ -2140,7 +2161,6 @@ async function checkFunctionToolCalls(data) { | ||
| 2140 | 2161 | const args = { name: toolCall.name, arguments: JSON.stringify(toolCall.parameters) }; |
| 2141 | 2162 | console.log('Function tool call:', toolCall); |
| 2142 | 2163 | await eventSource.emit(event_types.LLM_FUNCTION_TOOL_CALL, args); |
| 2143 | - data.allowEmptyResponse = true; | |
| 2144 | 2164 | } |
| 2145 | 2165 | } |
| 2146 | 2166 | } |
| @@ -3905,6 +3925,9 @@ function getMaxContextOpenAI(value) { | ||
| 3905 | 3925 | if (oai_settings.max_context_unlocked) { |
| 3906 | 3926 | return unlocked_max; |
| 3907 | 3927 | } |
| 3928 | + else if (value.startsWith('o1-')) { | |
| 3929 | + return max_128k; | |
| 3930 | + } | |
| 3908 | 3931 | 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')) { |
| 3909 | 3932 | return max_128k; |
| 3910 | 3933 | } |
| @@ -965,6 +965,7 @@ router.post('/generate', jsonParser, function (request, response) { | ||
| 965 | 965 | 'model': request.body.model, |
| 966 | 966 | 'temperature': request.body.temperature, |
| 967 | 967 | 'max_tokens': request.body.max_tokens, |
| 968 | + 'max_completion_tokens': request.body.max_completion_tokens, | |
| 968 | 969 | 'stream': request.body.stream, |
| 969 | 970 | 'presence_penalty': request.body.presence_penalty, |
| 970 | 971 | 'frequency_penalty': request.body.frequency_penalty, |
| @@ -350,6 +350,10 @@ function getWebTokenizersChunks(tokenizer, ids) { | ||
| 350 | 350 | * @returns {string} Tokenizer model to use |
| 351 | 351 | */ |
| 352 | 352 | function getTokenizerModel(requestModel) { |
| 353 | + if (requestModel.includes('o1-preview') || requestModel.includes('o1-mini')) { | |
| 354 | + return 'gpt-4o'; | |
| 355 | + } | |
| 356 | + | |
| 353 | 357 | if (requestModel.includes('gpt-4o')) { |
| 354 | 358 | return 'gpt-4o'; |
| 355 | 359 | } |