From 5c410986a434ac2c1ebe483919132a6c4575c78a Mon Sep 17 00:00:00 2001 From: DreamGenX Date: Thu, 7 Mar 2024 10:55:08 +0100 Subject: [PATCH] Add support for DreamGen API. API docs: https://dreamgen.com/docs/models/opus/v1 API keys: https://dreamgen.com/account/api-keys I decided to base this on the text-completion API since it's more flexible with SillyTavern's prompt formating capabilities. This also means that custom context and instruct settings are required. Will add documentation in a followup PR. --- .../DreamGen Role-Play V1.json | 10 +++ public/context/DreamGen Role-Play V1.json | 11 +++ public/index.html | 29 ++++++++ public/instruct/DreamGen Role-Play V1.json | 18 +++++ public/script.js | 17 ++++- public/scripts/RossAscends-mods.js | 5 +- public/scripts/preset-manager.js | 1 + public/scripts/secrets.js | 2 + public/scripts/textgen-models.js | 72 ++++++++++++++++++- public/scripts/textgen-settings.js | 19 ++++- src/additional-headers.js | 11 +++ src/constants.js | 18 +++++ src/endpoints/backends/text-completions.js | 17 ++++- src/endpoints/secrets.js | 1 + 14 files changed, 223 insertions(+), 8 deletions(-) create mode 100644 public/TextGen Settings/DreamGen Role-Play V1.json create mode 100644 public/context/DreamGen Role-Play V1.json create mode 100644 public/instruct/DreamGen Role-Play V1.json diff --git a/public/TextGen Settings/DreamGen Role-Play V1.json b/public/TextGen Settings/DreamGen Role-Play V1.json new file mode 100644 index 000000000..d1f405380 --- /dev/null +++ b/public/TextGen Settings/DreamGen Role-Play V1.json @@ -0,0 +1,10 @@ +{ + "temp": 1, + "top_p": 1, + "top_k": 0, + "min_p": 0.1, + "rep_pen": 1.05, + "minimum_message_content_tokens": 0, + "ban_eos_token": true, + "streaming": true +} \ No newline at end of file diff --git a/public/context/DreamGen Role-Play V1.json b/public/context/DreamGen Role-Play V1.json new file mode 100644 index 000000000..235a4b1df --- /dev/null +++ b/public/context/DreamGen Role-Play V1.json @@ -0,0 +1,11 @@ +{ + "story_string": "<|im_start|>system\n{{#if system}}{{system}}\n\n\n{{/if}}## Overall plot description:\n\n{{#if scenario}}{{scenario}}{{else}}Conversation between {{char}} and {{user}}.{{/if}}{{#if wiBefore}}\n\n{{wiBefore}}{{/if}}\n\n\n## Style description:\n\nThis role-play is written as a second-person introspective narrative from the perspective of {{user}}. Scenes are described vividly, with great detail.\n\n\n## Characters:\n\n### {{char}}\n\n{{#if description}}{{description}}\n\n{{/if}}{{#if personality}}{{personality}}\n\n{{/if}}### {{user}}\n\n{{#if persona}}{{persona}}{{else}}{{user}} is the protagonist of the role-play.{{/if}}{{#if wiAfter}}\n\n{{wiAfter}}{{/if}}{{#if mesExamples}}\n\n## Writing style examples:\n\n{{mesExamples}}{{/if}}", + "example_separator": "", + "chat_start": "", + "use_stop_strings": false, + "always_force_name2": false, + "trim_sentences": true, + "include_newline": false, + "single_line": false, + "name": "DreamGen Role-Play V1" +} \ No newline at end of file diff --git a/public/index.html b/public/index.html index ab2ddfb93..2012e33ae 100644 --- a/public/index.html +++ b/public/index.html @@ -1276,6 +1276,11 @@ +
+ Min Message Length + + +
Smoothing Factor @@ -1897,6 +1902,7 @@

API Type

+
+

+ DreamGen API key + + + +

+
+ + +
+
+ For privacy reasons, your API key will be hidden after you reload the page. +
+
+

DreamGen Model

+ +
+
diff --git a/public/instruct/DreamGen Role-Play V1.json b/public/instruct/DreamGen Role-Play V1.json new file mode 100644 index 000000000..419aec4d7 --- /dev/null +++ b/public/instruct/DreamGen Role-Play V1.json @@ -0,0 +1,18 @@ +{ + "system_prompt": "You are an intelligent, skilled, versatile writer.\n\nYour task is to write a role-play based on the information below.", + "input_sequence": "<|im_end|>\n<|im_start|>text names= {{user}}\n", + "output_sequence": "<|im_end|>\n<|im_start|>text names= {{char}}\n", + "first_output_sequence": "", + "last_output_sequence": "", + "system_sequence_prefix": "", + "system_sequence_suffix": "", + "stop_sequence": "", + "separator_sequence": "", + "wrap": false, + "macro": true, + "names": false, + "names_force_groups": false, + "activation_regex": "", + "skip_examples": false, + "name": "DreamGen Role-Play V1" +} \ No newline at end of file diff --git a/public/script.js b/public/script.js index 09739a990..89ebfe344 100644 --- a/public/script.js +++ b/public/script.js @@ -22,7 +22,7 @@ import { parseTabbyLogprobs, } from './scripts/textgen-settings.js'; -const { MANCER, TOGETHERAI, OOBA, APHRODITE, OLLAMA, INFERMATICAI, OPENROUTER } = textgen_types; +const { MANCER, TOGETHERAI, OOBA, APHRODITE, OLLAMA, INFERMATICAI, DREAMGEN, OPENROUTER } = textgen_types; import { world_info, @@ -198,7 +198,7 @@ import { createPersona, initPersonas, selectCurrentPersona, setPersonaDescriptio import { getBackgrounds, initBackgrounds, loadBackgroundSettings, background_settings } from './scripts/backgrounds.js'; import { hideLoader, showLoader } from './scripts/loader.js'; import { BulkEditOverlay, CharacterContextMenu } from './scripts/BulkEditOverlay.js'; -import { loadMancerModels, loadOllamaModels, loadTogetherAIModels, loadInfermaticAIModels, loadOpenRouterModels, loadAphroditeModels } from './scripts/textgen-models.js'; +import { loadMancerModels, loadOllamaModels, loadTogetherAIModels, loadInfermaticAIModels, loadOpenRouterModels, loadAphroditeModels, loadDreamGenModels } from './scripts/textgen-models.js'; import { appendFileContent, hasPendingFileAttachment, populateFileAttachment, decodeStyleTags, encodeStyleTags } from './scripts/chats.js'; import { initPresetManager } from './scripts/preset-manager.js'; import { evaluateMacros } from './scripts/macros.js'; @@ -1064,6 +1064,9 @@ async function getStatusTextgen() { } else if (textgen_settings.type === INFERMATICAI) { loadInfermaticAIModels(data?.data); online_status = textgen_settings.infermaticai_model; + } else if (textgen_settings.type === DREAMGEN) { + loadDreamGenModels(data?.data); + online_status = textgen_settings.dreamgen_model; } else if (textgen_settings.type === OPENROUTER) { loadOpenRouterModels(data?.data); online_status = textgen_settings.openrouter_model; @@ -7775,6 +7778,11 @@ const CONNECT_API_MAP = { button: '#api_button_textgenerationwebui', type: textgen_types.INFERMATICAI, }, + 'dreamgen': { + selected: 'textgenerationwebui', + button: '#api_button_textgenerationwebui', + type: textgen_types.DREAMGEN, + }, 'openrouter-text': { selected: 'textgenerationwebui', button: '#api_button_textgenerationwebui', @@ -8728,6 +8736,11 @@ jQuery(async function () { await writeSecret(SECRET_KEYS.INFERMATICAI, infermaticAIKey); } + const dreamgenKey = String($('#api_key_dreamgen').val()).trim(); + if (dreamgenKey.length) { + await writeSecret(SECRET_KEYS.DREAMGEN, dreamgenKey); + } + const openRouterKey = String($('#api_key_openrouter-tg').val()).trim(); if (openRouterKey.length) { await writeSecret(SECRET_KEYS.OPENROUTER, openRouterKey); diff --git a/public/scripts/RossAscends-mods.js b/public/scripts/RossAscends-mods.js index 73bd5b7bc..2db6c912d 100644 --- a/public/scripts/RossAscends-mods.js +++ b/public/scripts/RossAscends-mods.js @@ -391,8 +391,9 @@ function RA_autoconnect(PrevApi) { case 'textgenerationwebui': if ((textgen_settings.type === textgen_types.MANCER && secret_state[SECRET_KEYS.MANCER]) || (textgen_settings.type === textgen_types.TOGETHERAI && secret_state[SECRET_KEYS.TOGETHERAI]) - || (textgen_settings.type === textgen_types.INFERMATICAI && secret_state[SECRET_KEYS.INFERMATICAI] - || (textgen_settings.type === textgen_types.OPENROUTER && secret_state[SECRET_KEYS.OPENROUTER])) + || (textgen_settings.type === textgen_types.INFERMATICAI && secret_state[SECRET_KEYS.INFERMATICAI]) + || (textgen_settings.type === textgen_types.DREAMGEN && secret_state[SECRET_KEYS.DREAMGEN]) + || (textgen_settings.type === textgen_types.OPENROUTER && secret_state[SECRET_KEYS.OPENROUTER]) ) { $('#api_button_textgenerationwebui').trigger('click'); } diff --git a/public/scripts/preset-manager.js b/public/scripts/preset-manager.js index 4a4087967..ede6346d8 100644 --- a/public/scripts/preset-manager.js +++ b/public/scripts/preset-manager.js @@ -315,6 +315,7 @@ class PresetManager { 'custom_model', 'bypass_status_check', 'infermaticai_model', + 'dreamgen_model', 'openrouter_model', 'max_tokens_second', ]; diff --git a/public/scripts/secrets.js b/public/scripts/secrets.js index 3ea3c5289..568102cce 100644 --- a/public/scripts/secrets.js +++ b/public/scripts/secrets.js @@ -17,6 +17,7 @@ export const SECRET_KEYS = { MISTRALAI: 'api_key_mistralai', TOGETHERAI: 'api_key_togetherai', INFERMATICAI: 'api_key_infermaticai', + DREAMGEN: 'api_key_dreamgen', CUSTOM: 'api_key_custom', OOBA: 'api_key_ooba', }; @@ -39,6 +40,7 @@ const INPUT_MAP = { [SECRET_KEYS.TOGETHERAI]: '#api_key_togetherai', [SECRET_KEYS.OOBA]: '#api_key_ooba', [SECRET_KEYS.INFERMATICAI]: '#api_key_infermaticai', + [SECRET_KEYS.DREAMGEN]: '#api_key_dreamgen', }; async function clearSecret() { diff --git a/public/scripts/textgen-models.js b/public/scripts/textgen-models.js index 0dd7e0a3f..c19e2c5e8 100644 --- a/public/scripts/textgen-models.js +++ b/public/scripts/textgen-models.js @@ -1,11 +1,12 @@ -import { callPopup, getRequestHeaders, setGenerationParamsFromPreset } from '../script.js'; import { isMobile } from './RossAscends-mods.js'; +import { callPopup, getRequestHeaders, setGenerationParamsFromPreset } from '../script.js'; import { textgenerationwebui_settings as textgen_settings, textgen_types } from './textgen-settings.js'; import { tokenizers } from './tokenizers.js'; let mancerModels = []; let togetherModels = []; let infermaticAIModels = []; +let dreamGenModels = []; let aphroditeModels = []; export let openRouterModels = []; @@ -82,6 +83,32 @@ export async function loadInfermaticAIModels(data) { } } +export async function loadDreamGenModels(data) { + if (!Array.isArray(data)) { + console.error('Invalid DreamGen models data', data); + return; + } + + dreamGenModels = data; + + if (!data.find(x => x.id === textgen_settings.dreamgen_model)) { + textgen_settings.dreamgen_model = data[0]?.id || ''; + } + + $('#model_dreamgen_select').empty(); + for (const model of data) { + if (model.display_type === 'image') { + continue; + } + + const option = document.createElement('option'); + option.value = model.id; + option.text = model.id; + option.selected = model.id === textgen_settings.dreamgen_model; + $('#model_dreamgen_select').append(option); + } +} + export async function loadMancerModels(data) { if (!Array.isArray(data)) { console.error('Invalid Mancer models data', data); @@ -173,6 +200,13 @@ function onInfermaticAIModelSelect() { setGenerationParamsFromPreset({ max_length: model.context_length }); } +function onDreamGenModelSelect() { + const modelName = String($('#model_dreamgen_select').val()); + textgen_settings.dreamgen_model = modelName; + $('#api_button_textgenerationwebui').trigger('click'); + // TODO(DreamGen): Consider retuning max_tokens from API and setting it here. +} + function onOllamaModelSelect() { const modelId = String($('#ollama_model').val()); textgen_settings.ollama_model = modelId; @@ -240,6 +274,20 @@ function getInfermaticAIModelTemplate(option) { `)); } +function getDreamGenModelTemplate(option) { + const model = dreamGenModels.find(x => x.id === option?.element?.value); + + if (!option.id || !model) { + return option.text; + } + + return $((` +
+
${DOMPurify.sanitize(model.id)}
+
+ `)); +} + function getOpenRouterModelTemplate(option) { const model = openRouterModels.find(x => x.id === option?.element?.value); @@ -327,10 +375,25 @@ export function getCurrentOpenRouterModelTokenizer() { } } +export function getCurrentDreamGenModelTokenizer() { + const modelId = textgen_settings.openrouter_model; + const model = openRouterModels.find(x => x.id === modelId); + if (model.id.startsWith('opus-v1-sm')) { + return tokenizers.MISTRAL; + } else if (model.id.startsWith('opus-v1-lg')) { + return tokenizers.YI; + } else if (model.id.startsWith('opus-v1-xl')) { + return tokenizers.LLAMA; + } else { + return tokenizers.MISTRAL; + } +} + jQuery(function () { $('#mancer_model').on('change', onMancerModelSelect); $('#model_togetherai_select').on('change', onTogetherModelSelect); $('#model_infermaticai_select').on('change', onInfermaticAIModelSelect); + $('#model_dreamgen_select').on('change', onDreamGenModelSelect); $('#ollama_model').on('change', onOllamaModelSelect); $('#openrouter_model').on('change', onOpenRouterModelSelect); $('#ollama_download_model').on('click', downloadOllamaModel); @@ -364,6 +427,13 @@ jQuery(function () { width: '100%', templateResult: getInfermaticAIModelTemplate, }); + $('#model_dreamgen_select').select2({ + placeholder: 'Select a model', + searchInputPlaceholder: 'Search models...', + searchInputCssClass: 'text_pole', + width: '100%', + templateResult: getDreamGenModelTemplate, + }); $('#openrouter_model').select2({ placeholder: 'Select a model', searchInputPlaceholder: 'Search models...', diff --git a/public/scripts/textgen-settings.js b/public/scripts/textgen-settings.js index 277f6ffd7..949fa7121 100644 --- a/public/scripts/textgen-settings.js +++ b/public/scripts/textgen-settings.js @@ -35,10 +35,11 @@ export const textgen_types = { LLAMACPP: 'llamacpp', OLLAMA: 'ollama', INFERMATICAI: 'infermaticai', + DREAMGEN: 'dreamgen', OPENROUTER: 'openrouter', }; -const { MANCER, APHRODITE, TABBY, TOGETHERAI, OOBA, OLLAMA, LLAMACPP, INFERMATICAI, OPENROUTER } = textgen_types; +const { MANCER, APHRODITE, TABBY, TOGETHERAI, OOBA, OLLAMA, LLAMACPP, INFERMATICAI, DREAMGEN, OPENROUTER } = textgen_types; const LLAMACPP_DEFAULT_ORDER = [ 'top_k', @@ -71,6 +72,7 @@ const MANCER_SERVER_DEFAULT = 'https://neuro.mancer.tech'; let MANCER_SERVER = localStorage.getItem(MANCER_SERVER_KEY) ?? MANCER_SERVER_DEFAULT; let TOGETHERAI_SERVER = 'https://api.together.xyz'; let INFERMATICAI_SERVER = 'https://api.totalgpt.ai'; +let DREAMGEN_SERVER = 'https://dreamgen.com'; let OPENROUTER_SERVER = 'https://openrouter.ai/api'; const SERVER_INPUTS = { @@ -101,6 +103,7 @@ const settings = { num_beams: 1, length_penalty: 1, min_length: 0, + minimum_message_content_tokens: 0, encoder_rep_pen: 1, freq_pen: 0, presence_pen: 0, @@ -143,6 +146,7 @@ const settings = { ollama_model: '', openrouter_model: 'openrouter/auto', aphrodite_model: '', + dreamgen_model: 'opus-v1-xl/text', legacy_api: false, sampler_order: KOBOLDCPP_ORDER, logit_bias: [], @@ -175,6 +179,7 @@ const setting_names = [ 'num_beams', 'length_penalty', 'min_length', + 'minimum_message_content_tokens', 'dynatemp', 'min_temp', 'max_temp', @@ -247,6 +252,10 @@ export function getTextGenServer() { return INFERMATICAI_SERVER; } + if (settings.type === DREAMGEN) { + return DREAMGEN_SERVER; + } + if (settings.type === OPENROUTER) { return OPENROUTER_SERVER; } @@ -275,7 +284,7 @@ async function selectPreset(name) { function formatTextGenURL(value) { try { // Mancer/Together/InfermaticAI doesn't need any formatting (it's hardcoded) - if (settings.type === MANCER || settings.type === TOGETHERAI || settings.type === INFERMATICAI || settings.type === OPENROUTER) { + if (settings.type === MANCER || settings.type === TOGETHERAI || settings.type === INFERMATICAI || settings.type === DREAMGEN || settings.type === OPENROUTER) { return value; } @@ -642,6 +651,7 @@ jQuery(function () { 'presence_pen_textgenerationwebui': 0, 'no_repeat_ngram_size_textgenerationwebui': 0, 'min_length_textgenerationwebui': 0, + 'minimum_message_content_tokens_textgenerationwebui': 0, 'num_beams_textgenerationwebui': 1, 'length_penalty_textgenerationwebui': 0, 'penalty_alpha_textgenerationwebui': 0, @@ -937,6 +947,10 @@ function getModel() { return settings.infermaticai_model; } + if (settings.type === DREAMGEN) { + return settings.dreamgen_model; + } + if (settings.type === OPENROUTER) { return settings.openrouter_model; } @@ -976,6 +990,7 @@ export function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate, 'presence_penalty': settings.presence_pen, 'top_k': settings.top_k, 'min_length': settings.type === OOBA ? settings.min_length : undefined, + 'minimum_message_content_tokens': settings.type === DREAMGEN ? settings.minimum_message_content_tokens : undefined, 'min_tokens': settings.min_length, 'num_beams': settings.type === OOBA ? settings.num_beams : undefined, 'length_penalty': settings.length_penalty, diff --git a/src/additional-headers.js b/src/additional-headers.js index c1ae260c7..60933c4d6 100644 --- a/src/additional-headers.js +++ b/src/additional-headers.js @@ -27,6 +27,14 @@ function getInfermaticAIHeaders() { }) : {}; } +function getDreamGenHeaders() { + const apiKey = readSecret(SECRET_KEYS.DREAMGEN); + + return apiKey ? ({ + 'Authorization': `Bearer ${apiKey}`, + }) : {}; +} + function getOpenRouterHeaders() { const apiKey = readSecret(SECRET_KEYS.OPENROUTER); const baseHeaders = { ...OPENROUTER_HEADERS }; @@ -98,6 +106,9 @@ function setAdditionalHeaders(request, args, server) { case TEXTGEN_TYPES.INFERMATICAI: headers = getInfermaticAIHeaders(); break; + case TEXTGEN_TYPES.DREAMGEN: + headers = getDreamGenHeaders(); + break; case TEXTGEN_TYPES.OPENROUTER: headers = getOpenRouterHeaders(); break; diff --git a/src/constants.js b/src/constants.js index 67939f90f..ac1c36c7a 100644 --- a/src/constants.js +++ b/src/constants.js @@ -177,6 +177,7 @@ const TEXTGEN_TYPES = { LLAMACPP: 'llamacpp', OLLAMA: 'ollama', INFERMATICAI: 'infermaticai', + DREAMGEN: 'dreamgen', OPENROUTER: 'openrouter', }; @@ -192,6 +193,22 @@ const INFERMATICAI_KEYS = [ 'stop', ]; +// https://dreamgen.com/docs/api#openai-text +const DREAMGEN_KEYS = [ + 'model', + 'prompt', + 'max_tokens', + 'temperature', + 'top_p', + 'top_k', + 'min_p', + 'repetition_penalty', + 'frequency_penalty', + 'presence_penalty', + 'stop', + 'minimum_message_content_tokens' +]; + // https://docs.together.ai/reference/completions const TOGETHERAI_KEYS = [ 'model', @@ -263,6 +280,7 @@ module.exports = { TOGETHERAI_KEYS, OLLAMA_KEYS, INFERMATICAI_KEYS, + DREAMGEN_KEYS, OPENROUTER_HEADERS, OPENROUTER_KEYS, }; diff --git a/src/endpoints/backends/text-completions.js b/src/endpoints/backends/text-completions.js index d713a5aea..1b54906c6 100644 --- a/src/endpoints/backends/text-completions.js +++ b/src/endpoints/backends/text-completions.js @@ -4,7 +4,7 @@ const _ = require('lodash'); const Readable = require('stream').Readable; const { jsonParser } = require('../../express-common'); -const { TEXTGEN_TYPES, TOGETHERAI_KEYS, OLLAMA_KEYS, INFERMATICAI_KEYS, OPENROUTER_KEYS } = require('../../constants'); +const { TEXTGEN_TYPES, TOGETHERAI_KEYS, OLLAMA_KEYS, INFERMATICAI_KEYS, OPENROUTER_KEYS, DREAMGEN_KEYS } = require('../../constants'); const { forwardFetchResponse, trimV1 } = require('../../util'); const { setAdditionalHeaders } = require('../../additional-headers'); @@ -110,6 +110,9 @@ router.post('/status', jsonParser, async function (request, response) { case TEXTGEN_TYPES.OPENROUTER: url += '/v1/models'; break; + case TEXTGEN_TYPES.DREAMGEN: + url += '/api/openai/v1/models'; + break; case TEXTGEN_TYPES.MANCER: url += '/oai/v1/models'; break; @@ -238,6 +241,9 @@ router.post('/generate', jsonParser, async function (request, response) { case TEXTGEN_TYPES.INFERMATICAI: url += '/v1/completions'; break; + case TEXTGEN_TYPES.DREAMGEN: + url += '/api/openai/v1/completions'; + break; case TEXTGEN_TYPES.MANCER: url += '/oai/v1/completions'; break; @@ -273,6 +279,15 @@ router.post('/generate', jsonParser, async function (request, response) { args.body = JSON.stringify(request.body); } + if (request.body.api_type === TEXTGEN_TYPES.DREAMGEN) { + request.body = _.pickBy(request.body, (_, key) => DREAMGEN_KEYS.includes(key)); + // NOTE: DreamGen currently only supports streaming. + request.body.stream = true; + // NOTE: DreamGen sometimes get confused by the unusual formatting in the character cards. + request.body.stop?.push('### User', '## User'); + args.body = JSON.stringify(request.body); + } + if (request.body.api_type === TEXTGEN_TYPES.OPENROUTER) { request.body = _.pickBy(request.body, (_, key) => OPENROUTER_KEYS.includes(key)); args.body = JSON.stringify(request.body); diff --git a/src/endpoints/secrets.js b/src/endpoints/secrets.js index 961e1b620..26df3f3fc 100644 --- a/src/endpoints/secrets.js +++ b/src/endpoints/secrets.js @@ -31,6 +31,7 @@ const SECRET_KEYS = { CUSTOM: 'api_key_custom', OOBA: 'api_key_ooba', INFERMATICAI: 'api_key_infermaticai', + DREAMGEN: 'api_key_dreamgen', }; // These are the keys that are safe to expose, even if allowKeysExposure is false