From 92ff4f66c1e69e159daea5b68c604561b68cad36 Mon Sep 17 00:00:00 2001 From: ahsk <44384-ahsk@users.noreply.gitgud.io> Date: Tue, 15 Aug 2023 07:47:00 -0300 Subject: [PATCH 1/5] 3.4 - changelog: https://gitgud.io/ahsk/clewd/-/blob/master/CHANGELOG.md --- CHANGELOG.md | 37 +++++++- README.md | 70 +++++++------- clewd.js | 255 +++++++++++++++++++++++++++------------------------ package.json | 2 +- 4 files changed, 207 insertions(+), 157 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b6de41..5f797ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,39 @@ +# 3.4 +> Prompt conversion and **SystemExperiments** reworked + +> **Prompts** (PromptMain, PromptReminder, PromptContinue) from 3.0 removed + +most frontends are implementing prompt managers so that hack is not needed + +> **PersonalityFormat** and **ScenarioFormat** added + +those are hardcoded on ST and will stay available until they're not + +scenarios and char description are extracted from their hardcoded strings and replaced by the format you set + +> **ExampleChatPrefix** and **RealChatPrefix** + +those are hardcoded on ST and will stay available until they're not + +- **ExampleChatPrefix** default changed + +- **RealChatPrefix** default is now empty + +> **AllSamples** changed + +no longer excludes the last two messages when transforming + +> **Minor changes** + +- Error handling changes + +- RenewAlways is now more stable when set to false. still, regenerate once if you swap characters + +- `[Start a new chat]` no longer excluded, now replaced by ExampleChatPrefix/RealChatPrefix + # 3.3 added \[DONE\] to end of streams @@ -27,7 +60,7 @@ if set to true, prevents the deletion of chats at any point # 3.1 > **Streaming changes** -if the user did not enable streaming, we will **fake** a non-stream response for compatibility +if the user did not enable streaming, clewd will **fake** a non-stream response for compatibility > **LogMessages** added (defaults false) @@ -76,7 +109,7 @@ if set to false, check the `Prompts` section below > **NoSamples** added (defaults false) -if set to true, replaces any H/A prefix from your frontend into Human/Assistant +if set to true, replaces every previous message with H/A prefix from your frontend into Human/Assistant mutually exclusive with `AllSamples` diff --git a/README.md b/README.md index 1cb738b..ef7e433 100644 --- a/README.md +++ b/README.md @@ -33,9 +33,37 @@ nodejs>=20.4.* ### SettingName: (DEFAULT)/opt1/opt2... + - `PreventImperson`: (false)/true + * true trims the bot reply immediately if he says "Human:", "Assistant:", "H:" or "A:" + * making it so it doesn't hallucinate speaking as you __(chance of missing some spicy things)__ + + - `PromptExperiment`: (true)/false + * true is an alternative way to send your prompt to the AI + * experiment before setting to false + + - `RetryRegenerate`: (false)/true + * true uses the AI's own retry mechanism when you regenerate on your frontend + * instead of a new conversation + * experiment with it + + - `SystemExperiments`: (true)/false + * only has any effect when `RenewAlways` is false + * true alternates between Main+JB+User and JB+User + * false doesn't alternate + + - `RenewAlways`: (true)/false + * true makes a new conversation context each time + * false *tries* to reutilize the same old conversation, sending only your actual last message each time, taking into consideration `SystemExperiments` + + - `StripAssistant`: (false)/true + * true strips the "Assistant:" prefix from the last assistant message + + - `StripHuman`: (false)/true + * true strips the "Human:" prefix from the last human message + - `AllSamples`: (false)/true * mutually exclusive with `NoSamples` - * true converts every message except the last two to "sample dialogues" + * true converts all real dialogue to "sample dialogue" * you're "H" and the AI is "A" * whatever the AI replies with is kept (only outgoing) * [see this](https://docs.anthropic.com/claude/docs/prompt-troubleshooting-checklist#the-prompt-is-formatted-correctly) for more information @@ -44,54 +72,26 @@ nodejs>=20.4.* - `NoSamples`: (false)/true * mutually exclusive with `AllSamples` - * true converts all "sample dialogues" to real dialogue + * true converts all "sample dialogue" to real dialogue * you're "Human" and the AI is "Assistant" * whatever the AI replies with is kept (only outgoing) * [see this](https://docs.anthropic.com/claude/docs/prompt-troubleshooting-checklist#the-prompt-is-formatted-correctly) for more information - H->Human - A->Assistant + - `ClearFlags`: (false)/true + * possibly snake-oil + * clears your warnings + - `PassParams`: (false)/true * true will send the temperature you set on your frontend - * only values under <=1 + * only values under <=1.0 >= 0.1 * this could get your account banned * if clewd stops working, set to false - - `PreventImperson`: (false)/true - * true trims the bot reply immediately if he says "Human:", "Assistant:", "H:" or "A:" - * making it so it doesn't hallucinate speaking as you __(chance of missing some spicy things)__ - - - `PromptExperiment`: (true)/false - * true is an alternative way to send your prompt to the AI - * experiment before setting to false - - - `ClearFlags`: (false)/true - * possibly snake-oil - * clears your warnings - - `PreserveChats`: (false)/true * true prevents the deletion of old chats at any point - - `RenewAlways`: (true)/false - * true makes a new conversation context each time - * false *tries* to reutilize the same old conversation, sending only your actual last message each time - - - `RetryRegenerate`: (false)/true - * true uses the AI's own retry mechanism when you regenerate on your frontend - * instead of a new conversation - * experiment with it - - - `StripAssistant`: (false)/true - * true strips the "Assistant:" prefix from the last assistant message - - - `StripHuman`: (false)/true - * true strips the "Human:" prefix from the last human message - - - `SystemExperiments`: (true)/false - * only has any effect when `RenewAlways` is false - * true alternates between Reminder and Continue prompts - * false only uses Reminder - ## Examples diff --git a/clewd.js b/clewd.js index 3d90601..bfd54fd 100644 --- a/clewd.js +++ b/clewd.js @@ -81,14 +81,13 @@ let uuidOrg; SystemExperiments: true, PreserveChats: false }, - ExampleChatPrefix: '[EXAMPLE CHATS]\n', - RealChatPrefix: '[CHAT BEGIN]\n', - PromptMain: '{{MAIN_AND_CHARACTER}}\n{{CHAT_EXAMPLE}}\n{{CHAT_LOG}}\n{{JAILBREAK}}', - PromptReminder: '{{MAIN_AND_CHARACTER}}\n{{JAILBREAK}}\n{{LATEST_USER}}', - PromptContinue: '{{JAILBREAK}}\n{{LATEST_USER}}' + ExampleChatPrefix: 'Here are some dialogue examples:', + RealChatPrefix: '', + PersonalityFormat: '{{CHAR}}\'s personality: {{PERSONALITY}}', + ScenarioFormat: 'Dialogue scenario: {{SCENARIO}}' }; -const Main = 'clewd v3.3'; +const Main = 'clewd v3.4'; ServerResponse.prototype.json = async function(body, statusCode = 200, headers) { body = body instanceof Promise ? await body : body; @@ -176,73 +175,6 @@ const deleteChat = async uuid => { updateCookies(res); }; -const messagesToPrompt = (messages, customPrompt) => { - let messagesClone = JSON.parse(JSON.stringify(messages)); - let prompt = [ ...customPrompt || Config.PromptMain ].join('').trim(); - let lastInterGlobalIdx = -1; - const interactionDividers = messagesClone.filter((message => 'system' === message.role && '[Start a new chat]' === message.content)); - interactionDividers.forEach(((divider, idx) => { - if (idx !== interactionDividers.length - 1) { - divider.content = '' + Config.ExampleChatPrefix; - } else { - divider.content = '' + Config.RealChatPrefix; - lastInterGlobalIdx = messagesClone.findIndex((message => message === divider)); - } - })); - const latestInteraction = []; - const lastAssistant = messagesClone.findLast((message => 'assistant' === message.role)); - if (lastAssistant && Config.Settings.StripAssistant) { - lastAssistant.empty = true; - latestInteraction.push(lastAssistant); - } - const lastUser = messagesClone.findLast((message => 'user' === message.role)); - if (lastUser && Config.Settings.StripHuman) { - lastUser.empty = true; - latestInteraction.push(lastUser); - } - let chatLogs = messagesClone.filter((message => !message.name && [ 'user', 'assistant' ].includes(message.role))); - let sampleChats = messagesClone.filter((message => message.name && message.name.startsWith('example_'))); - Config.Settings.AllSamples && !Config.Settings.NoSamples && chatLogs.forEach((message => { - if (message !== lastUser && message !== lastAssistant) { - if ('user' === message.role) { - message.name = 'example_user'; - message.role = 'system'; - } else { - if ('assistant' !== message.role) { - throw Error('Invalid role ' + message.role); - } - message.name = 'example_assistant'; - message.role = 'system'; - } - } - })); - Config.Settings.NoSamples && !Config.Settings.AllSamples && sampleChats.forEach((message => { - if ('example_user' === message.name) { - message.role = 'user'; - } else { - if ('example_assistant' !== message.name) { - throw Error('Invalid role ' + message.name); - } - message.role = 'assistant'; - } - delete message.name; - })); - const remainingSystem = messagesClone.filter((message => 'system' === message.role && !sampleChats.includes(message) && !interactionDividers.includes(message))); - let mainPromptCharacter = remainingSystem?.[0]; - let jailbreakPrompt = remainingSystem?.[remainingSystem.length - 1]; - if (jailbreakPrompt === mainPromptCharacter) { - mainPromptCharacter = null; - jailbreakPrompt = remainingSystem?.[remainingSystem.length - 1]; - } - prompt = prompt.replace(/{{MAIN_AND_CHARACTER}}/gm, mainPromptCharacter?.content?.length > 0 ? '' + mainPromptCharacter?.content.trim() : ''); - prompt = prompt.replace(/{{CHAT_EXAMPLE}}/gm, sampleChats.length < 1 ? '' : `\n${Config.ExampleChatPrefix}${sampleChats?.map((message => `${Replacements[message.name || message.role]}${message.content.trim()}`)).join('\n\n')}`); - prompt = prompt.replace(/{{CHAT_LOG}}/gm, chatLogs.length < 1 ? '' : `\n${Config.RealChatPrefix}${chatLogs?.map((message => `${message.empty ? '' : Replacements[message.role || message.name]}${message.content.trim()}`)).join('\n\n')}`); - prompt = prompt.replace(/{{JAILBREAK}}/gm, jailbreakPrompt?.content?.length > 0 ? '' + jailbreakPrompt?.content.trim() : ''); - prompt = prompt.replace(/{{LATEST_USER}}/gm, lastUser ? `${lastUser.empty ? '' : Replacements[lastUser.role]}${lastUser.content.trim()}` : ''); - prompt = prompt.replace(/{{LATEST_ASSISTANT}}/gm, lastAssistant ? `${lastAssistant.empty ? '' : Replacements[lastAssistant.role]}${lastAssistant.content.trim()}` : ''); - return genericFixes(prompt).trim(); -}; - const setTitle = title => { title = `${Main} - ${title}`; process.title !== title && (process.title = title); @@ -269,6 +201,7 @@ const onListen = async () => { setTitle('ok'); updateCookies(Config.Cookie); updateCookies(accRes); + await checkResErr(accRes); console.log(`${Main}\nhttp://${Config.Ip}:${Config.Port}/v1\n\n${Object.keys(Config.Settings).map((setting => `${setting}: ${NonDefaults.includes(setting) ? '' : ''}${Config.Settings[setting]}`)).sort().join('\n')}\n`); console.log('Logged in %o', { name: accInfo.name?.split('@')?.[0], @@ -316,6 +249,25 @@ const onListen = async () => { conversations.length > 0 && await Promise.all(conversations.map((conv => deleteChat(conv.uuid)))); }; +const checkResErr = async res => { + if (res.status < 200 || res.status >= 300) { + let err = Error('Unexpected response code: ' + res.status); + try { + const json = await res.json(); + const {error: errAPI} = json; + if (errAPI) { + errAPI.message && (err.message = errAPI.message); + errAPI.type && (err.type = errAPI.type); + if (429 === res.status && errAPI.resets_at) { + const hours = ((new Date(1e3 * errAPI.resets_at).getTime() - Date.now()) / 1e3 / 60 / 60).toFixed(2); + err.message += `, expires in ${hours} hours`; + } + } + } catch (err) {} + throw Error(err); + } +}; + class ClewdStream extends TransformStream { constructor(minSize = 8, modelName = AI.modelA(), streaming, abortController) { super({ @@ -485,7 +437,7 @@ class ClewdStream extends TransformStream { } const writeSettings = async (config, firstRun = false) => { - FS.writeFileSync(ConfigPath, `/*\n* https://gitgud.io/ahsk/clewd\n* https://github.com/h-a-s-k/clewd\n*/\n\n// SET YOUR COOKIE BELOW\n\nmodule.exports = ${JSON.stringify(config, null, 4)}\n\n/*\n BufferSize\n * How many characters will be buffered before the AI types once\n * lower = less chance of \`PreventImperson\` working properly\n\n ---\n\n SystemInterval, PromptMain, PromptReminder, PromptContinue\n * when \`RenewAlways\` is set to true (default), \`Main\` is always the one being used\n\n * when \`RenewAlways\` is set to false, \`Main\` is sent on conversation start\n * then only \`Continue\` is sent as long as no impersonation happened\n * \`Simple\` and \`Reminder\` alternate every \`SystemInterval\`\n * \n * {{MAIN_AND_CHARACTER}}, {{CHAT_EXAMPLE}}, {{CHAT_LOG}}, {{JAILBREAK}}, {{LATEST_ASSISTANT}}, {{LATEST_USER}}\n\n ---\n\n Other settings\n * https://gitgud.io/ahsk/clewd/#defaults\n * https://gitgud.io/ahsk/clewd/-/blob/master/CHANGELOG.md#anchor-30\n */`.trim().replace(/((? { let shouldRenew = true; let retryRegen = false; try { - const bufferComplete = Buffer.concat(buffer).toString(); - const body = JSON.parse(bufferComplete); + const body = JSON.parse(Buffer.concat(buffer).toString()); const temperature = Math.max(.1, Math.min(1, body.temperature)); let {messages} = body; if (messages?.length < 1) { @@ -552,14 +503,14 @@ const Proxy = Server((async (req, res) => { } res.setHeader('Access-Control-Allow-Origin', '*'); body.stream && res.setHeader('Content-Type', 'text/event-stream'); - if (!body.stream && messages?.[0]?.content.startsWith('From the list below, choose a word that best represents a character\'s outfit description, action, or emotion in their dialogue.')) { - return res.end(JSON.stringify({ + if (!body.stream && messages?.[0]?.content?.startsWith('From the list below, choose a word that best represents a character\'s outfit description, action, or emotion in their dialogue')) { + return res.json({ choices: [ { message: { content: 'neutral' } } ] - })); + }); } if (Config.Settings.AllSamples && Config.Settings.NoSamples) { console.log('having AllSamples and NoSamples both set to true is not supported'); @@ -584,7 +535,6 @@ const Proxy = Server((async (req, res) => { lastAssistant: prevMessages.findLast((message => 'assistant' === message.role)) } }; - let prompt; samePrompt = JSON.stringify(messages.filter((message => 'system' !== message.role)).sort()) === JSON.stringify(prevMessages.filter((message => 'system' !== message.role)).sort()); const sameCharDiffChat = !samePrompt && curPrompt.firstSystem?.content === prevPrompt.firstSystem?.content && curPrompt.firstUser.content !== prevPrompt.firstUser?.content; shouldRenew = Config.Settings.RenewAlways || !Conversation.uuid || prevImpersonated || !Config.Settings.RenewAlways && samePrompt || sameCharDiffChat; @@ -592,7 +542,6 @@ const Proxy = Server((async (req, res) => { samePrompt || (prevMessages = JSON.parse(JSON.stringify(messages))); let type = ''; if (retryRegen) { - console.log(model + ' [R]'); type = 'R'; fetchAPI = await (async (signal, body, model) => { const res = await fetch(AI.end() + '/api/retry_message', { @@ -614,6 +563,7 @@ const Proxy = Server((async (req, res) => { }) }); updateCookies(res); + await checkResErr(res); return res; })(signal, 0, model); } else if (shouldRenew) { @@ -634,33 +584,119 @@ const Proxy = Server((async (req, res) => { }) }); updateCookies(res); + await checkResErr(res); return res; })(signal); - console.log(model + ' [r]'); type = 'r'; - prompt = messagesToPrompt(messages); } else if (samePrompt) {} else { const systemExperiment = !Config.Settings.RenewAlways && Config.Settings.SystemExperiments; - const fullSystem = !systemExperiment || systemExperiment && Conversation.depth >= Config.SystemInterval; - const systemMessages = [ ...new Set(JSON.parse(JSON.stringify(messages)).filter((message => !message.name && 'system' === message.role)).filter((message => false === [ '[Start a new chat]', Replacements.new_chat ].includes(message.content)))) ]; - let trimmedMessages; - let chosenPrompt; - if (fullSystem) { - console.log(`${model} [c-r] ${systemMessages.map((message => `"${message.content.substring(0, 25).replace(/\n/g, '\\n').trim()}..."`)).join(' / ')}`); - trimmedMessages = [ ...systemMessages, curPrompt.lastAssistant, curPrompt.lastUser ]; - Conversation.depth = 0; - chosenPrompt = Config.PromptReminder; + if (!systemExperiment || systemExperiment && Conversation.depth >= Config.SystemInterval) { type = 'c-r'; + Conversation.depth = 0; } else { - const jailbreak = systemMessages[systemMessages.length - 1]; - console.log(`${model} [c-c] "${jailbreak.content.substring(0, 25).replace(/\n/g, '\\n').trim()}..."`); - trimmedMessages = [ ...systemMessages, curPrompt.lastUser ]; - chosenPrompt = Config.PromptContinue; type = 'c-c'; + Conversation.depth++; } - prompt = messagesToPrompt(trimmedMessages, chosenPrompt); - Conversation.depth++; } + let {prompt, systems} = ((messages, type) => { + const rgxScenario = /^\[Circumstances and context of the dialogue: ([\s\S]+?)\.?\]$/i; + const rgxPerson = /^\[([\s\S]+?)'s personality: ([\s\S]+?)\]$/i; + const messagesClone = JSON.parse(JSON.stringify(messages)); + const realLogs = messagesClone.filter((message => [ 'user', 'assistant' ].includes(message.role))); + const sampleLogs = messagesClone.filter((message => [ 'example_user', 'example_assistant' ].includes(message.name))); + const mergedLogs = [ ...sampleLogs, ...realLogs ]; + mergedLogs.forEach(((message, idx) => { + const next = realLogs[idx + 1]; + if (next) { + if (message.name && next.name && message.name === next.name) { + message.content += '\n' + next.content; + next.merged = true; + } else if (next.role === message.role) { + message.content += '\n' + next.content; + next.merged = true; + } + } + })); + const lastAssistant = realLogs.findLast((message => !message.merged && 'assistant' === message.role)); + lastAssistant && Config.Settings.StripAssistant && (lastAssistant.strip = true); + const lastUser = realLogs.findLast((message => !message.merged && 'user' === message.role)); + lastUser && Config.Settings.StripHuman && (lastUser.strip = true); + const systemMessages = messagesClone.filter((message => 'system' === message.role && !message.name)); + systemMessages.forEach(((message, idx) => { + const scenario = message.content.match(rgxScenario)?.[1]; + const personality = message.content.match(rgxPerson); + if (scenario) { + message.content = Config.ScenarioFormat.replace(/{{SCENARIO}}/g, scenario); + message.scenario = true; + } + if (3 === personality?.length) { + message.content = Config.PersonalityFormat.replace(/{{CHAR}}/g, personality[1].replace(/{{PERSONALITY}}/g, personality[2])); + message.personality = true; + } + message.main = 0 === idx; + message.jailbreak = idx === systemMessages.length - 1; + })); + Config.Settings.AllSamples && !Config.Settings.NoSamples && realLogs.forEach((message => { + if ('user' === message.role) { + message.name = 'example_user'; + message.role = 'system'; + } else { + if ('assistant' !== message.role) { + throw Error('Invalid role ' + message.role); + } + message.name = 'example_assistant'; + message.role = 'system'; + } + })); + Config.Settings.NoSamples && !Config.Settings.AllSamples && sampleLogs.forEach((message => { + if ('example_user' === message.name) { + message.role = 'user'; + } else { + if ('example_assistant' !== message.name) { + throw Error('Invalid role ' + message.name); + } + message.role = 'assistant'; + } + delete message.name; + })); + let systems = []; + if (![ 'r', 'R' ].includes(type)) { + lastUser.strip = true; + systemMessages.forEach((message => message.discard = message.discard || 'c-c' === type ? !message.jailbreak : !message.jailbreak && !message.main)); + systems = systemMessages.filter((message => !message.discard)).map((message => `"${message.content.substring(0, 25).replace(/\n/g, '\\n').trim()}..."`)); + messagesClone.forEach((message => message.discard = message.discard || mergedLogs.includes(message) && ![ lastUser ].includes(message))); + } + const interactionDividers = systemMessages.filter((message => '[start a new chat]' === message.content.toLowerCase())); + interactionDividers.forEach(((divider, idx) => { + let real = idx === interactionDividers.length - 1; + let useReal = Config.Settings.NoSamples || real; + let useExample = Config.Settings.AllSamples || !real; + if (useReal) { + divider.content = '' + Config.RealChatPrefix; + Config.Settings.AllSamples && (divider.discard = true); + } + if (useExample) { + divider.content = '' + Config.ExampleChatPrefix; + Config.Settings.NoSamples && (divider.discard = true); + } + })); + const prompt = messagesClone.map(((message, idx) => { + if (message.merged || message.discard) { + return ''; + } + if (message.content.length < 1) { + return message.content; + } + let spacing = ''; + idx > 0 && (spacing = systemMessages.includes(message) || message.name ? '\n' : '\n\n'); + return `${spacing}${message.strip ? '' : Replacements[message.name || message.role]}${message.content.trim()}`; + })); + return { + prompt: genericFixes(prompt.join('')).trim(), + systems + }; + })(messages, type); + console.log(`${model} [${type}]${!retryRegen && systems.length > 0 ? ' ' + systems.join(' / ') : ''}`); retryRegen || (fetchAPI = await (async (signal, body, model, prompt, temperature) => { const attachments = []; if (Config.Settings.PromptExperiment) { @@ -685,7 +721,7 @@ const Proxy = Server((async (req, res) => { temperature }, prompt, - timezone: 'America/New_York', + timezone: 'Europe/London', model }, organization_uuid: uuidOrg, @@ -695,29 +731,10 @@ const Proxy = Server((async (req, res) => { }) }); updateCookies(res); + await checkResErr(res); return res; })(signal, 0, model, prompt, temperature)); const response = Writable.toWeb(res); - if (429 === fetchAPI.status) { - const err = { - message: 'Rate limited', - code: fetchAPI.status - }; - try { - const json = await fetchAPI.json(); - if (json.error) { - err.message = json.error.message; - if (json.error.resets_at) { - const hours = ((new Date(1e3 * json.error.resets_at).getTime() - Date.now()) / 1e3 / 60 / 60).toFixed(2); - err.message += `, expires in ${hours} hours`; - } - } - } catch (err) {} - throw Error(err.message); - } - if (200 !== fetchAPI.status) { - return fetchAPI.body.pipeTo(response); - } 'R' !== type || prompt || (prompt = '...regen...'); Logger?.write(`\n\n-------\n[${(new Date).toLocaleString()}]\n####### PROMPT (${type}):\n${prompt}\n--\n####### REPLY:\n`); clewdStream = new ClewdStream(Config.BufferSize, model, body.stream, controller); diff --git a/package.json b/package.json index a96b993..8309360 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "clewd", - "version": "3.3", + "version": "3.4", "description": ":^)", "main": "clewd.js", "engines": { From b432d4e28bd69449561bd1da99392166237575ac Mon Sep 17 00:00:00 2001 From: ahsk <44384-ahsk@users.noreply.gitgud.io> Date: Tue, 15 Aug 2023 09:56:25 -0300 Subject: [PATCH 2/5] 3.4 hotfix fixed personality formatting fixed streaming (broken quotes and asterisks) --- CHANGELOG.md | 7 +++++ README.md | 3 ++ clewd.js | 83 ++++++++++++++++------------------------------------ 3 files changed, 36 insertions(+), 57 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f797ac..d5ea19c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,9 @@ # 3.4 +> **Streaming changes** +turns out I could've fixed the broken formatting long ago, should work great now + > Prompt conversion and **SystemExperiments** reworked > **Prompts** (PromptMain, PromptReminder, PromptContinue) from 3.0 removed @@ -37,6 +40,10 @@ those are hardcoded on ST and will stay available until they're not no longer excludes the last two messages when transforming +> **LogMessages** changed + +moved to Settings + > **Minor changes** - Error handling changes diff --git a/README.md b/README.md index ef7e433..48e016e 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,9 @@ nodejs>=20.4.* * [see this](https://docs.anthropic.com/claude/docs/prompt-troubleshooting-checklist#the-prompt-is-formatted-correctly) for more information - H->Human - A->Assistant + + - `LogMessages`: (false)/true + * true logs prompt and reply to `log.txt` - `ClearFlags`: (false)/true * possibly snake-oil diff --git a/clewd.js b/clewd.js index bfd54fd..b1200fa 100644 --- a/clewd.js +++ b/clewd.js @@ -66,20 +66,20 @@ let uuidOrg; Port: 8444, BufferSize: 8, SystemInterval: 3, - LogMessages: false, Settings: { - AllSamples: false, - ClearFlags: false, - NoSamples: false, - PassParams: false, PreventImperson: false, PromptExperiment: true, RetryRegenerate: false, RenewAlways: true, + SystemExperiments: true, + AllSamples: false, + NoSamples: false, StripAssistant: false, StripHuman: false, - SystemExperiments: true, - PreserveChats: false + PassParams: false, + ClearFlags: false, + PreserveChats: false, + LogMessages: false }, ExampleChatPrefix: 'Here are some dialogue examples:', RealChatPrefix: '', @@ -134,8 +134,6 @@ const bytesToSize = (bytes = 0) => { return 0 === c ? `${bytes} ${b[c]}` : `${(bytes / 1024 ** c).toFixed(1)} ${b[c]}`; }; -const cleanJSON = json => json.replace(/^data: {/gi, '{').replace(/\s+$/gi, ''); - const genericFixes = text => text.replace(/(\r\n|\r|\\n)/gm, '\n'); const updateCookies = cookieInfo => { @@ -252,6 +250,7 @@ const onListen = async () => { const checkResErr = async res => { if (res.status < 200 || res.status >= 300) { let err = Error('Unexpected response code: ' + res.status); + err.planned = true; try { const json = await res.json(); const {error: errAPI} = json; @@ -289,8 +288,6 @@ class ClewdStream extends TransformStream { #abortController=void 0; #modelName=void 0; #compAll=[]; - #compValid=[]; - #compInvalid=[]; #recvLength=0; #stopLoc=void 0; #stopReason=void 0; @@ -299,17 +296,8 @@ class ClewdStream extends TransformStream { get size() { return this.#recvLength; } - get valid() { - return this.#compValid.length; - } - get invalid() { - return this.#compInvalid.length; - } get total() { - return this.valid + this.invalid; - } - get broken() { - return (this.invalid / this.total * 100).toFixed(2) + '%'; + return this.#compAll.length; } get censored() { return this.#hardCensor; @@ -319,7 +307,7 @@ class ClewdStream extends TransformStream { } empty() { this.#compOK = ''; - this.#compAll = this.#compValid = this.#compInvalid = []; + this.#compAll = []; this.#recvLength = 0; } #cutBuffer() { @@ -390,39 +378,20 @@ class ClewdStream extends TransformStream { } #handle(chunk, controller) { this.#recvLength += chunk.byteLength || 0; - let parsed = {}; + let completion = ''; let delayChunk; - chunk = Decoder.decode(chunk); - chunk = cleanJSON(chunk); + chunk = Decoder.decode(chunk).replace(/^data: {/gim, '{').replace(/\s+$/gim, ''); try { - const clean = cleanJSON(chunk); - parsed = JSON.parse(clean); - this.#stopLoc = parsed.stop; - this.#stopReason = parsed.stop_reason; - this.#compValid.push(parsed.completion); - parsed.error; - } catch (err) { - const {stopMatch, stopReasonMatch, completionMatch, errorMatch} = (chunk => ({ - completionMatch: (chunk = 'string' == typeof chunk ? chunk : Decoder.decode(chunk)).match(/(?<="completion"\s?:\s?")(.*?)(?=\\?",?)/gi), - stopMatch: chunk.match(/(?<="stop"\s?:\s?")(.*?)(?=\\?",?)/gi), - stopReasonMatch: chunk.match(/(?<="stop_reason"\s?:\s?")(.*?)(?=\\?",?)/gi), - errorMatch: chunk.match(/(?<="message"\s?:\s?")(.*?)(?=\\?",?)/gi) - }))(chunk); - stopMatch && (parsed.stop = stopMatch.join('')); - stopReasonMatch && (parsed.stop_reason = stopReasonMatch.join('')); - if (completionMatch) { - parsed.completion = completionMatch.join(''); - this.#compInvalid.push(parsed.completion); - } - } finally { - this.#stopReason = parsed.stop_reason; - this.#stopLoc = parsed.stop; - } - if (parsed.completion) { - parsed.completion = genericFixes(parsed.completion); - this.#compOK += parsed.completion; - this.#compAll.push(parsed.completion); - delayChunk = DangerChars.some((char => this.#compOK.endsWith(char) || parsed.completion.startsWith(char))); + const chunks = chunk.split('\n').map((chunk => JSON.parse(chunk))); + chunks.find((chunk => chunk.error)); + completion = genericFixes(chunks.map((chunk => chunk.completion)).join('')); + this.#stopLoc || (this.#stopLoc = chunks.find((chunk => chunk.stop_loc))?.stop); + this.#stopReason || (this.#stopReason = chunks.find((chunk => chunk.stop_reason))?.stop_reason); + } catch (err) {} + if (completion) { + this.#compOK += completion; + this.#compAll.push(completion); + delayChunk = DangerChars.some((char => this.#compOK.endsWith(char) || completion.startsWith(char))); if (this.#streaming) { delayChunk && this.#impersonationCheck(this.#compOK, controller); for (;!delayChunk && this.#compOK.length >= this.#minSize; ) { @@ -630,7 +599,7 @@ const Proxy = Server((async (req, res) => { message.scenario = true; } if (3 === personality?.length) { - message.content = Config.PersonalityFormat.replace(/{{CHAR}}/g, personality[1].replace(/{{PERSONALITY}}/g, personality[2])); + message.content = Config.PersonalityFormat.replace(/{{CHAR}}/gm, personality[1]).replace(/{{PERSONALITY}}/gm, personality[2]); message.personality = true; } message.main = 0 === idx; @@ -744,7 +713,7 @@ const Proxy = Server((async (req, res) => { if ('AbortError' === err.name) { return res.end(); } - console.error('Clewd:\n%o', err); + err.planned || console.error('Clewd:\n%o', err); res.json({ error: { message: 'clewd: ' + (err.message || err.name || err.type), @@ -758,7 +727,7 @@ const Proxy = Server((async (req, res) => { if (clewdStream) { clewdStream.censored && console.warn('likely your account is hard-censored'); prevImpersonated = clewdStream.impersonated; - console.log(`${200 == fetchAPI.status ? '' : ''}${fetchAPI.status}! ${clewdStream.broken} broken\n`); + console.log(`${200 == fetchAPI.status ? '' : ''}${fetchAPI.status}!\n`); setTitle('ok ' + bytesToSize(clewdStream.size)); clewdStream.empty(); } @@ -821,7 +790,7 @@ const Proxy = Server((async (req, res) => { })); NonDefaults = parsedSettings.filter((setting => Config.Settings[setting] !== userConfig.Settings[setting])); (missingConfigs.length > 0 || missingSettings.length > 0) && await writeSettings(userConfig); - userConfig.LogMessages && (Logger = require('fs').createWriteStream(LogPath)); + userConfig.Settings.LogMessages && (Logger = require('fs').createWriteStream(LogPath)); Config = { ...Config, ...userConfig From 8c74e1ccf15974acfff416fd17853f6cc6d28831 Mon Sep 17 00:00:00 2001 From: ahsk <44384-ahsk@users.noreply.gitgud.io> Date: Tue, 15 Aug 2023 10:32:22 -0300 Subject: [PATCH 3/5] 3.4 hotfix 2 fixed potential PreventImperson break --- CHANGELOG.md | 2 +- README.md | 4 ++-- clewd.js | 18 ++++++++++-------- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d5ea19c..69fbcf6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -228,7 +228,7 @@ should be more accurate now uses the AI's own retry mechanism when you regenerate on your frontend, if you change anything from your last prompt before regenerating it will default to old behavior -> **PromptExperiment** added (defaults true) +> **PromptExperiments** added (defaults true) an alternative way to send your prompt to the AI, through a file. set to false if it's bad both enabled: https://files.catbox.moe/io1q53.webm diff --git a/README.md b/README.md index 48e016e..34cb591 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ nodejs>=20.4.* * true trims the bot reply immediately if he says "Human:", "Assistant:", "H:" or "A:" * making it so it doesn't hallucinate speaking as you __(chance of missing some spicy things)__ - - `PromptExperiment`: (true)/false + - `PromptExperiments`: (true)/false * true is an alternative way to send your prompt to the AI * experiment before setting to false @@ -107,7 +107,7 @@ nodejs>=20.4.* --- **experimental setup** -> **PromptExperiment**: true +> **PromptExperiments**: true > **SystemExperiments**: true diff --git a/clewd.js b/clewd.js index b1200fa..6e51df0 100644 --- a/clewd.js +++ b/clewd.js @@ -20,7 +20,9 @@ const Decoder = new TextDecoder; const Encoder = new TextEncoder; -let NonDefaults; +let ChangedSettings; + +let UnknownSettings; let Logger; @@ -68,7 +70,7 @@ let uuidOrg; SystemInterval: 3, Settings: { PreventImperson: false, - PromptExperiment: true, + PromptExperiments: true, RetryRegenerate: false, RenewAlways: true, SystemExperiments: true, @@ -200,7 +202,7 @@ const onListen = async () => { updateCookies(Config.Cookie); updateCookies(accRes); await checkResErr(accRes); - console.log(`${Main}\nhttp://${Config.Ip}:${Config.Port}/v1\n\n${Object.keys(Config.Settings).map((setting => `${setting}: ${NonDefaults.includes(setting) ? '' : ''}${Config.Settings[setting]}`)).sort().join('\n')}\n`); + console.log(`${Main}\nhttp://${Config.Ip}:${Config.Port}/v1\n\n${Object.keys(Config.Settings).map((setting => UnknownSettings.includes(setting) ? `??? ${setting}: ${Config.Settings[setting]}` : `${setting}: ${ChangedSettings.includes(setting) ? '' : ''}${Config.Settings[setting]}`)).sort().join('\n')}\n`); console.log('Logged in %o', { name: accInfo.name?.split('@')?.[0], capabilities: accInfo.capabilities @@ -657,7 +659,7 @@ const Proxy = Server((async (req, res) => { return message.content; } let spacing = ''; - idx > 0 && (spacing = systemMessages.includes(message) || message.name ? '\n' : '\n\n'); + idx > 0 && (spacing = systemMessages.includes(message) ? '\n' : '\n\n'); return `${spacing}${message.strip ? '' : Replacements[message.name || message.role]}${message.content.trim()}`; })); return { @@ -668,7 +670,7 @@ const Proxy = Server((async (req, res) => { console.log(`${model} [${type}]${!retryRegen && systems.length > 0 ? ' ' + systems.join(' / ') : ''}`); retryRegen || (fetchAPI = await (async (signal, body, model, prompt, temperature) => { const attachments = []; - if (Config.Settings.PromptExperiment) { + if (Config.Settings.PromptExperiments) { attachments.push({ extracted_content: prompt, file_name: fileName(), @@ -771,11 +773,11 @@ const Proxy = Server((async (req, res) => { const parsedSettings = Object.keys(userConfig.Settings); const invalidConfigs = parsedConfigs.filter((config => !validConfigs.includes(config))); const validSettings = Object.keys(Config.Settings); - const invalidSettings = parsedSettings.filter((setting => !validSettings.includes(setting))); + UnknownSettings = parsedSettings.filter((setting => !validSettings.includes(setting))); invalidConfigs.forEach((config => { console.warn(`unknown config in config.js: ${config}`); })); - invalidSettings.forEach((setting => { + UnknownSettings.forEach((setting => { console.warn(`unknown setting in config.js: Settings.${setting}`); })); const missingConfigs = validConfigs.filter((config => !parsedConfigs.includes(config))); @@ -788,7 +790,7 @@ const Proxy = Server((async (req, res) => { console.warn(`adding missing setting in config.js: Settings.${setting}`); userConfig.Settings[setting] = Config.Settings[setting]; })); - NonDefaults = parsedSettings.filter((setting => Config.Settings[setting] !== userConfig.Settings[setting])); + ChangedSettings = parsedSettings.filter((setting => Config.Settings[setting] !== userConfig.Settings[setting])); (missingConfigs.length > 0 || missingSettings.length > 0) && await writeSettings(userConfig); userConfig.Settings.LogMessages && (Logger = require('fs').createWriteStream(LogPath)); Config = { From 8c831baa4e68c0ec43568b133c7bc4fa984c6d73 Mon Sep 17 00:00:00 2001 From: ahsk <44384-ahsk@users.noreply.gitgud.io> Date: Tue, 15 Aug 2023 10:42:33 -0300 Subject: [PATCH 4/5] 3.4 hotfix 3 actually not hardcoded --- CHANGELOG.md | 8 -------- clewd.js | 16 ---------------- 2 files changed, 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 69fbcf6..c30f67c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,12 +28,6 @@ those are hardcoded on ST and will stay available until they're not scenarios and char description are extracted from their hardcoded strings and replaced by the format you set -> **ExampleChatPrefix** and **RealChatPrefix** - -those are hardcoded on ST and will stay available until they're not - -- **ExampleChatPrefix** default changed - - **RealChatPrefix** default is now empty > **AllSamples** changed @@ -50,8 +44,6 @@ moved to Settings - RenewAlways is now more stable when set to false. still, regenerate once if you swap characters -- `[Start a new chat]` no longer excluded, now replaced by ExampleChatPrefix/RealChatPrefix - # 3.3 added \[DONE\] to end of streams diff --git a/clewd.js b/clewd.js index 6e51df0..039d04e 100644 --- a/clewd.js +++ b/clewd.js @@ -83,8 +83,6 @@ let uuidOrg; PreserveChats: false, LogMessages: false }, - ExampleChatPrefix: 'Here are some dialogue examples:', - RealChatPrefix: '', PersonalityFormat: '{{CHAR}}\'s personality: {{PERSONALITY}}', ScenarioFormat: 'Dialogue scenario: {{SCENARIO}}' }; @@ -637,20 +635,6 @@ const Proxy = Server((async (req, res) => { systems = systemMessages.filter((message => !message.discard)).map((message => `"${message.content.substring(0, 25).replace(/\n/g, '\\n').trim()}..."`)); messagesClone.forEach((message => message.discard = message.discard || mergedLogs.includes(message) && ![ lastUser ].includes(message))); } - const interactionDividers = systemMessages.filter((message => '[start a new chat]' === message.content.toLowerCase())); - interactionDividers.forEach(((divider, idx) => { - let real = idx === interactionDividers.length - 1; - let useReal = Config.Settings.NoSamples || real; - let useExample = Config.Settings.AllSamples || !real; - if (useReal) { - divider.content = '' + Config.RealChatPrefix; - Config.Settings.AllSamples && (divider.discard = true); - } - if (useExample) { - divider.content = '' + Config.ExampleChatPrefix; - Config.Settings.NoSamples && (divider.discard = true); - } - })); const prompt = messagesClone.map(((message, idx) => { if (message.merged || message.discard) { return ''; From b37ee4559901132f9c06c0d2090f5a06ddc8c846 Mon Sep 17 00:00:00 2001 From: teralomaniac Date: Tue, 15 Aug 2023 22:46:33 +0800 Subject: [PATCH 5/5] Update clewd.js --- clewd.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/clewd.js b/clewd.js index 5cd9ff2..7c51ff3 100644 --- a/clewd.js +++ b/clewd.js @@ -381,6 +381,16 @@ const checkResErr = async res => { err.planned = true; try { const json = await res.json(); + if (json.error) { + if (json.error.message.includes('read-only mode')) { + Config.CookieArray = Config.CookieArray.filter(item => item !== Config.Cookie); + writeSettings(Config); + CookieChanger.emit('ChangeCookie'); + } + else if (json.error.message.includes('Exceeded completions limit')) { + CookieChanger.emit('ChangeCookie'); + } + } const {error: errAPI} = json; if (errAPI) { errAPI.message && (err.message = errAPI.message);