| import { getPresetManager } from './preset-manager.js'; |
| import { extractJsonFromData, extractMessageFromData, getGenerateUrl, getRequestHeaders, name1, name2 } from '../script.js'; |
| import { getTextGenServer, createTextGenGenerationData, setting_names, textgenerationwebui_settings } from './textgen-settings.js'; |
| import { extractReasoningFromData } from './reasoning.js'; |
| import { formatInstructModeChat, formatInstructModePrompt, getInstructStoppingSequences } from './instruct-mode.js'; |
| import { getStreamingReply, tryParseStreamingError, createGenerationParameters, settingsToUpdate, oai_settings } from './openai.js'; |
| import EventSourceStream from './sse-stream.js'; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| |
|
|
| |
| |
| |
| |
| |
| |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| |
|
|
| |
| |
| |
| |
| |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| |
|
|
| |
| |
| |
| export class TextCompletionService { |
| static TYPE = 'textgenerationwebui'; |
|
|
| |
| |
| |
| |
| static createRequestData({ stream = false, prompt, max_tokens, model, api_type, api_server, temperature, min_p, ...props }) { |
| const payload = { |
| stream, |
| prompt, |
| max_tokens, |
| max_new_tokens: max_tokens, |
| model, |
| api_type, |
| api_server: api_server ?? getTextGenServer(api_type), |
| temperature, |
| min_p, |
| ...props, |
| }; |
|
|
| |
| Object.keys(payload).forEach(key => { |
| if (payload[key] === undefined) { |
| delete payload[key]; |
| } |
| }); |
|
|
| return payload; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| static async sendRequest(data, extractData = true, signal = null) { |
| if (!data.stream) { |
| const response = await fetch(getGenerateUrl(this.TYPE), { |
| method: 'POST', |
| headers: getRequestHeaders(), |
| cache: 'no-cache', |
| body: JSON.stringify(data), |
| signal: signal ?? new AbortController().signal, |
| }); |
|
|
| const json = await response.json(); |
| if (!response.ok || json.error) { |
| throw new Error(String(json.error?.message || 'Response not OK')); |
| } |
|
|
| if (!extractData) { |
| return json; |
| } |
|
|
| return { |
| content: extractMessageFromData(json, this.TYPE), |
| reasoning: extractReasoningFromData(json, { |
| mainApi: this.TYPE, |
| textGenType: data.api_type, |
| ignoreShowThoughts: true, |
| }), |
| }; |
| } |
|
|
| const response = await fetch('/api/backends/text-completions/generate', { |
| method: 'POST', |
| headers: getRequestHeaders(), |
| cache: 'no-cache', |
| body: JSON.stringify(data), |
| signal: signal ?? new AbortController().signal, |
| }); |
|
|
| if (!response.ok) { |
| const text = await response.text(); |
| tryParseStreamingError(response, text, { quiet: true }); |
|
|
| throw new Error(`Got response status ${response.status}`); |
| } |
|
|
| const eventStream = new EventSourceStream(); |
| response.body.pipeThrough(eventStream); |
| const reader = eventStream.readable.getReader(); |
| return async function* streamData() { |
| let text = ''; |
| const swipes = []; |
| const state = { reasoning: '' }; |
| while (true) { |
| const { done, value } = await reader.read(); |
| if (done) return; |
| if (value.data === '[DONE]') return; |
|
|
| tryParseStreamingError(response, value.data, { quiet: true }); |
|
|
| let data = JSON.parse(value.data); |
|
|
| if (data?.choices?.[0]?.index > 0) { |
| const swipeIndex = data.choices[0].index - 1; |
| swipes[swipeIndex] = (swipes[swipeIndex] || '') + data.choices[0].text; |
| } else { |
| const newText = data?.choices?.[0]?.text || data?.content || ''; |
| text += newText; |
| state.reasoning += data?.choices?.[0]?.reasoning ?? ''; |
| } |
|
|
| yield { text, swipes, state }; |
| } |
| }; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| static constructPrompt(prompt, instructPreset, instructSettings) { |
| |
| if (typeof instructPreset === 'string') { |
| const instructPresetManager = getPresetManager('instruct'); |
| instructPreset = instructPresetManager?.getCompletionPresetByName(instructPreset); |
| } |
|
|
| |
| instructPreset = structuredClone(instructPreset); |
| if (instructSettings) { |
| Object.assign(instructPreset, instructSettings); |
| } |
|
|
| |
| if (typeof instructPreset === 'string') { |
| return; |
| } |
|
|
| |
| const formattedMessages = []; |
| const prefillActive = prompt.length > 0 ? prompt[prompt.length - 1].role === 'assistant' : false; |
| for (const message of prompt) { |
| let messageContent = message.content; |
| if (!message.ignoreInstruct) { |
| const isLastMessage = message === prompt[prompt.length - 1]; |
|
|
| |
| |
| |
| if (!isLastMessage || !prefillActive) { |
| messageContent = formatInstructModeChat( |
| message.name ?? message.role, |
| message.content, |
| message.role === 'user', |
| message.role === 'system', |
| undefined, |
| name1, |
| name2, |
| undefined, |
| instructPreset, |
| ); |
| } |
|
|
| |
| |
| if (isLastMessage) { |
| let last_line = formatInstructModePrompt( |
| 'assistant', |
| false, |
| prefillActive ? message.content : undefined, |
| name1, |
| name2, |
| true, |
| false, |
| instructPreset, |
| ); |
|
|
| if (prefillActive) { |
| if (last_line.endsWith('\n') && !message.content.endsWith('\n')) { |
| last_line = last_line.slice(0, -1); |
| } |
| messageContent = last_line; |
| } else { |
| messageContent += last_line; |
| } |
| } |
| } |
| formattedMessages.push(messageContent); |
| } |
| return formattedMessages.join(''); |
| } |
|
|
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| static async processRequest(requestData, options = {}, extractData = true, signal = null) { |
| const { presetName, instructName } = options; |
|
|
| |
| requestData = this.createRequestData(requestData); |
|
|
| |
| let instructPreset; |
| const prompt = requestData.prompt; |
| |
| if (Array.isArray(prompt)) { |
| if (instructName) { |
| const instructPresetManager = getPresetManager('instruct'); |
| instructPreset = instructPresetManager?.getCompletionPresetByName(instructName); |
| if (instructPreset) { |
| requestData.prompt = this.constructPrompt(prompt, instructPreset, options.instructSettings); |
| const stoppingStrings = getInstructStoppingSequences({ customInstruct: instructPreset, useStopStrings: false }); |
| requestData.stop = stoppingStrings; |
| requestData.stopping_strings = stoppingStrings; |
| } else { |
| console.warn(`Instruct preset "${instructName}" not found, using basic formatting`); |
| requestData.prompt = prompt.map(x => x.content).join('\n\n'); |
| } |
| } else { |
| requestData.prompt = prompt.map(x => x.content).join('\n\n'); |
| } |
| } else if (typeof prompt === 'string') { |
| requestData.prompt = prompt; |
| } |
|
|
| |
| if (presetName) { |
| const presetManager = getPresetManager(this.TYPE); |
| if (presetManager) { |
| const preset = presetManager.getCompletionPresetByName(presetName); |
| if (preset) { |
| |
| requestData = this.presetToGeneratePayload(preset, {}, requestData); |
| } else { |
| console.warn(`Preset "${presetName}" not found, continuing with default settings`); |
| } |
| } else { |
| console.warn('Preset manager not found, continuing with default settings'); |
| } |
| } |
|
|
| const response = await this.sendRequest(requestData, extractData, signal); |
|
|
| |
| if (!requestData.stream && extractData) { |
| |
| |
| const extractedData = response; |
|
|
| let message = extractedData.content; |
|
|
| message = message.replace(/[^\S\r\n]+$/gm, ''); |
|
|
| if (requestData.stopping_strings) { |
| for (const stoppingString of requestData.stopping_strings) { |
| if (stoppingString.length) { |
| for (let j = stoppingString.length; j > 0; j--) { |
| if (message.slice(-j) === stoppingString.slice(0, j)) { |
| message = message.slice(0, -j); |
| break; |
| } |
| } |
| } |
| } |
| } |
|
|
| if (instructPreset) { |
| [ |
| instructPreset.stop_sequence, |
| instructPreset.input_sequence, |
| ].forEach(sequence => { |
| if (sequence?.trim()) { |
| const index = message.indexOf(sequence); |
| if (index !== -1) { |
| message = message.substring(0, index); |
| } |
| } |
| }); |
|
|
| [ |
| instructPreset.output_sequence, |
| instructPreset.last_output_sequence, |
| ].forEach(sequences => { |
| if (sequences) { |
| sequences.split('\n') |
| .filter(line => line.trim() !== '') |
| .forEach(line => { |
| message = message.replaceAll(line, ''); |
| }); |
| } |
| }); |
| } |
|
|
| extractedData.content = message; |
| } |
|
|
| return response; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| static presetToGeneratePayload(preset, overridePreset = {}, overridePayload = {}) { |
| if (!preset || typeof preset !== 'object') { |
| throw new Error('Invalid preset: must be an object'); |
| } |
|
|
| |
| preset = { ...preset, ...overridePreset }; |
|
|
| |
| const settings = structuredClone(textgenerationwebui_settings); |
| for (const [key, value] of Object.entries(preset)) { |
| if (!setting_names.includes(key)) continue; |
| settings[key] = value; |
| } |
|
|
| |
| const payload = createTextGenGenerationData(settings, overridePayload.model, overridePayload.prompt, preset.genamt); |
|
|
| |
| return this.createRequestData({ ...payload, ...overridePayload }); |
| } |
| } |
|
|
| |
| |
| |
| export class ChatCompletionService { |
| static TYPE = 'openai'; |
|
|
| |
| |
| |
| |
| static createRequestData({ stream = false, messages, model, chat_completion_source, max_tokens, temperature, custom_url, reverse_proxy, proxy_password, custom_prompt_post_processing, ...props }) { |
| const payload = { |
| stream, |
| messages, |
| model, |
| chat_completion_source, |
| max_tokens, |
| temperature, |
| custom_url, |
| reverse_proxy, |
| proxy_password, |
| custom_prompt_post_processing, |
| use_sysprompt: true, |
| ...props, |
| }; |
|
|
| |
| Object.keys(payload).forEach(key => { |
| if (payload[key] === undefined) { |
| delete payload[key]; |
| } |
| }); |
|
|
| return payload; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| static async sendRequest(data, extractData = true, signal = null) { |
| const response = await fetch('/api/backends/chat-completions/generate', { |
| method: 'POST', |
| headers: getRequestHeaders(), |
| cache: 'no-cache', |
| body: JSON.stringify(data), |
| signal: signal ?? new AbortController().signal, |
| }); |
|
|
| if (!data.stream) { |
| const json = await response.json(); |
| if (!response.ok || json.error) { |
| throw new Error(String(json.error?.message || 'Response not OK')); |
| } |
|
|
| if (!extractData) { |
| return json; |
| } |
|
|
| const result = { |
| content: extractMessageFromData(json, this.TYPE), |
| reasoning: extractReasoningFromData(json, { |
| mainApi: this.TYPE, |
| textGenType: data.chat_completion_source, |
| ignoreShowThoughts: true, |
| }), |
| }; |
| |
| if (data.json_schema) { |
| result.content = JSON.parse(extractJsonFromData(json, { mainApi: this.TYPE, chatCompletionSource: data.chat_completion_source })); |
| } |
| return result; |
| } |
|
|
| if (!response.ok) { |
| const text = await response.text(); |
| tryParseStreamingError(response, text, { quiet: true }); |
|
|
| throw new Error(`Got response status ${response.status}`); |
| } |
|
|
| const eventStream = new EventSourceStream(); |
| response.body.pipeThrough(eventStream); |
| const reader = eventStream.readable.getReader(); |
| return async function* streamData() { |
| let text = ''; |
| const swipes = []; |
| const state = { reasoning: '', image: '' }; |
| while (true) { |
| const { done, value } = await reader.read(); |
| if (done) return; |
| const rawData = value.data; |
| if (rawData === '[DONE]') return; |
| tryParseStreamingError(response, rawData, { quiet: true }); |
| const parsed = JSON.parse(rawData); |
|
|
| const reply = getStreamingReply(parsed, state, { |
| chatCompletionSource: data.chat_completion_source, |
| overrideShowThoughts: true, |
| }); |
| if (Array.isArray(parsed?.choices) && parsed?.choices?.[0]?.index > 0) { |
| const swipeIndex = parsed.choices[0].index - 1; |
| swipes[swipeIndex] = (swipes[swipeIndex] || '') + reply; |
| } else { |
| text += reply; |
| } |
|
|
| yield { text, swipes: swipes, state }; |
| } |
| }; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| static async processRequest(requestData, options, extractData = true, signal = null) { |
| const { presetName } = options; |
| requestData = this.createRequestData(requestData); |
|
|
| |
| if (presetName) { |
| const presetManager = getPresetManager(this.TYPE); |
| if (presetManager) { |
| const preset = presetManager.getCompletionPresetByName(presetName); |
| if (preset) { |
| |
| requestData = await this.presetToGeneratePayload(preset, {}, requestData); |
| } else { |
| console.warn(`Preset "${presetName}" not found, continuing with default settings`); |
| } |
| } else { |
| console.warn('Preset manager not found, continuing with default settings'); |
| } |
| } |
|
|
| return await this.sendRequest(requestData, extractData, signal); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| static async presetToGeneratePayload(preset, overridePreset = {}, overridePayload = {}) { |
| if (!preset || typeof preset !== 'object') { |
| throw new Error('Invalid preset: must be an object'); |
| } |
|
|
| |
| preset = { ...preset, ...overridePreset }; |
|
|
| |
| preset.bias_preset_selected = preset.bias_presets !== undefined ? preset.bias_preset_selected : undefined; |
|
|
| |
| const settings = structuredClone(oai_settings); |
| for (const [key, value] of Object.entries(preset)) { |
| const settingToUpdate = settingsToUpdate[key]; |
| if (!settingToUpdate) continue; |
| settings[settingToUpdate[1]] = value; |
| } |
|
|
| |
| ['custom_url', 'vertexai_region', 'zai_endpoint'].forEach(field => { |
| |
| overridePayload[field] = overridePayload[field] || settings[field] || oai_settings[field]; |
| }); |
|
|
| |
| const data = await createGenerationParameters(settings, overridePayload.model, 'quiet', overridePayload.messages); |
| const payload = data.generate_data; |
|
|
| |
| return this.createRequestData({ ...payload, ...overridePayload }); |
| } |
| } |
|
|