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.
This commit is contained in:
DreamGenX 2024-03-07 10:55:08 +01:00
parent bb53fef531
commit 5c410986a4
14 changed files with 223 additions and 8 deletions

View File

@ -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
}

View File

@ -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"
}

View File

@ -1276,6 +1276,11 @@
<input class="neo-range-slider" type="range" id="min_length_textgenerationwebui" name="volume" min="0" max="2000" step="1" />
<input class="neo-range-input" type="number" min="0" max="2000" step="1" data-for="min_length_textgenerationwebui" id="min_length_counter_textgenerationwebui">
</div>
<div data-newbie-hidden data-tg-type="dreamgen" class="alignitemscenter flex-container flexFlowColumn flexBasis48p flexGrow flexShrink gap0">
<small data-i18n="Min Message Length">Min Message Length</small>
<input class="neo-range-slider" type="range" id="minimum_message_content_tokens_textgenerationwebui" name="volume" min="0" max="150" step="1" />
<input class="neo-range-input" type="number" min="0" max="150" step="1" data-for="minimum_message_content_tokens_textgenerationwebui" id="minimum_message_content_tokens_counter_textgenerationwebui">
</div>
<div data-newbie-hidden data-tg-type="ooba, koboldcpp, aphrodite, tabby" class="alignitemscenter flex-container flexFlowColumn flexBasis48p flexGrow flexShrink gap0">
<small data-i18n="Smoothing Factor">Smoothing Factor</small>
<input class="neo-range-slider" type="range" id="smoothing_factor_textgenerationwebui" name="volume" min="0" max="10" step="0.01" />
@ -1897,6 +1902,7 @@
<h4 data-i18n="API Type">API Type</h4>
<select id="textgen_type">
<option value="ooba" data-i18n="Default (oobabooga)">Default (oobabooga)</option>
<option value="dreamgen">DreamGen</option>
<option value="mancer">Mancer</option>
<option value="aphrodite">Aphrodite</option>
<option value="tabby">TabbyAPI</option>
@ -1968,6 +1974,29 @@
</select>
</div>
</div>
<div data-tg-type="dreamgen" class="flex-container flexFlowColumn">
<h4 data-i18n="DreamGen API key">
DreamGen API key
<a href="https://dreamgen.com/account/api-keys" class="notes-link" target="_blank">
<span class="fa-solid fa-circle-question note-link-span"></span>
</a>
</h4>
<div class="flex-container">
<input id="api_key_dreamgen" name="api_key_dreamgen" class="text_pole flex1" maxlength="500" value="" type="text" autocomplete="off">
<div title="Clear your API key" data-i18n="[title]Clear your API key" class="menu_button fa-solid fa-circle-xmark clear-api-key" data-key="api_key_dreamgen"></div>
</div>
<div data-for="api_key_dreamgen" class="neutral_warning">
For privacy reasons, your API key will be hidden after you reload the page.
</div>
<div>
<h4 data-i18n="DreamGen Model">DreamGen Model</h4>
<select id="model_dreamgen_select">
<option>
-- Connect to the API --
</option>
</select>
</div>
</div>
<div data-tg-type="mancer" class="flex-container flexFlowColumn">
<div class="flex-container flexFlowColumn">
</div>

View File

@ -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"
}

View File

@ -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);

View File

@ -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');
}

View File

@ -315,6 +315,7 @@ class PresetManager {
'custom_model',
'bypass_status_check',
'infermaticai_model',
'dreamgen_model',
'openrouter_model',
'max_tokens_second',
];

View File

@ -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() {

View File

@ -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 $((`
<div class="flex-container flexFlowColumn">
<div><strong>${DOMPurify.sanitize(model.id)}</strong></div>
</div>
`));
}
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...',

View File

@ -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,

View File

@ -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;

View File

@ -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,
};

View File

@ -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);

View File

@ -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