From 2f10d46abcd41cfe2f6b280260ba95f9815c05ce Mon Sep 17 00:00:00 2001 From: Henry Date: Tue, 25 Jul 2023 00:32:50 +0100 Subject: [PATCH] add handleEscapeChar --- .../nodes/chains/LLMChain/LLMChain.ts | 19 ++++++++--- .../ChatPromptTemplate/ChatPromptTemplate.ts | 2 +- .../FewShotPromptTemplate.ts | 2 +- .../prompts/PromptTemplate/PromptTemplate.ts | 2 +- packages/components/src/utils.ts | 34 +++++++++++++++++++ packages/components/tsconfig.json | 2 +- packages/server/src/utils/index.ts | 9 +++-- 7 files changed, 60 insertions(+), 10 deletions(-) diff --git a/packages/components/nodes/chains/LLMChain/LLMChain.ts b/packages/components/nodes/chains/LLMChain/LLMChain.ts index 1d0ccb92d..eca56d318 100644 --- a/packages/components/nodes/chains/LLMChain/LLMChain.ts +++ b/packages/components/nodes/chains/LLMChain/LLMChain.ts @@ -1,5 +1,5 @@ import { ICommonObject, INode, INodeData, INodeOutputsValue, INodeParams } from '../../../src/Interface' -import { getBaseClasses } from '../../../src/utils' +import { getBaseClasses, handleEscapeCharacters } from '../../../src/utils' import { LLMChain } from 'langchain/chains' import { BaseLanguageModel } from 'langchain/base_language' import { ConsoleCallbackHandler, CustomChainHandler } from '../../../src/handler' @@ -73,7 +73,12 @@ class LLMChain_Chains implements INode { console.log('\x1b[92m\x1b[1m\n*****OUTPUT PREDICTION*****\n\x1b[0m\x1b[0m') // eslint-disable-next-line no-console console.log(res) - return res + /** + * Apply string transformation to convert special chars: + * FROM: hello i am ben\n\n\thow are you? + * TO: hello i am benFLOWISE_NEWLINEFLOWISE_NEWLINEFLOWISE_TABhow are you? + */ + return handleEscapeCharacters(res, false) } } @@ -81,7 +86,6 @@ class LLMChain_Chains implements INode { const inputVariables = nodeData.instance.prompt.inputVariables as string[] // ["product"] const chain = nodeData.instance as LLMChain const promptValues = nodeData.inputs?.prompt.promptValues as ICommonObject - const res = await runPrediction(inputVariables, chain, input, promptValues, options) // eslint-disable-next-line no-console console.log('\x1b[93m\x1b[1m\n*****FINAL RESULT*****\n\x1b[0m\x1b[0m') @@ -95,7 +99,7 @@ const runPrediction = async ( inputVariables: string[], chain: LLMChain, input: string, - promptValues: ICommonObject, + promptValuesRaw: ICommonObject, options: ICommonObject ) => { const loggerHandler = new ConsoleCallbackHandler(options.logger) @@ -103,6 +107,13 @@ const runPrediction = async ( const socketIO = isStreaming ? options.socketIO : undefined const socketIOClientId = isStreaming ? options.socketIOClientId : '' + /** + * Apply string transformation to reverse converted special chars: + * FROM: { "value": "hello i am benFLOWISE_NEWLINEFLOWISE_NEWLINEFLOWISE_TABhow are you?" } + * TO: { "value": "hello i am ben\n\n\thow are you?" } + */ + const promptValues = handleEscapeCharacters(promptValuesRaw, true) + if (inputVariables.length === 1) { if (isStreaming) { const handler = new CustomChainHandler(socketIO, socketIOClientId) diff --git a/packages/components/nodes/prompts/ChatPromptTemplate/ChatPromptTemplate.ts b/packages/components/nodes/prompts/ChatPromptTemplate/ChatPromptTemplate.ts index 4eeb1dd23..88803348b 100644 --- a/packages/components/nodes/prompts/ChatPromptTemplate/ChatPromptTemplate.ts +++ b/packages/components/nodes/prompts/ChatPromptTemplate/ChatPromptTemplate.ts @@ -58,7 +58,7 @@ class ChatPromptTemplate_Prompts implements INode { let promptValues: ICommonObject = {} if (promptValuesStr) { - promptValues = JSON.parse(promptValuesStr.replace(/\s/g, '')) + promptValues = JSON.parse(promptValuesStr) } // @ts-ignore prompt.promptValues = promptValues diff --git a/packages/components/nodes/prompts/FewShotPromptTemplate/FewShotPromptTemplate.ts b/packages/components/nodes/prompts/FewShotPromptTemplate/FewShotPromptTemplate.ts index a42a1d088..3bf305a48 100644 --- a/packages/components/nodes/prompts/FewShotPromptTemplate/FewShotPromptTemplate.ts +++ b/packages/components/nodes/prompts/FewShotPromptTemplate/FewShotPromptTemplate.ts @@ -86,7 +86,7 @@ class FewShotPromptTemplate_Prompts implements INode { const examplePrompt = nodeData.inputs?.examplePrompt as PromptTemplate const inputVariables = getInputVariables(suffix) - const examples: Example[] = JSON.parse(examplesStr.replace(/\s/g, '')) + const examples: Example[] = JSON.parse(examplesStr) try { const obj: FewShotPromptTemplateInput = { diff --git a/packages/components/nodes/prompts/PromptTemplate/PromptTemplate.ts b/packages/components/nodes/prompts/PromptTemplate/PromptTemplate.ts index f9c6c53e3..bd5740d85 100644 --- a/packages/components/nodes/prompts/PromptTemplate/PromptTemplate.ts +++ b/packages/components/nodes/prompts/PromptTemplate/PromptTemplate.ts @@ -45,7 +45,7 @@ class PromptTemplate_Prompts implements INode { let promptValues: ICommonObject = {} if (promptValuesStr) { - promptValues = JSON.parse(promptValuesStr.replace(/\s/g, '')) + promptValues = JSON.parse(promptValuesStr) } const inputVariables = getInputVariables(template) diff --git a/packages/components/src/utils.ts b/packages/components/src/utils.ts index eaf90fef1..e00f74e64 100644 --- a/packages/components/src/utils.ts +++ b/packages/components/src/utils.ts @@ -347,6 +347,40 @@ export const getEnvironmentVariable = (name: string): string | undefined => { } } +// reference https://www.freeformatter.com/json-escape.html +const jsonEscapeCharacters = [ + { escape: '"', value: 'FLOWISE_DOUBLE_QUOTE' }, + { escape: '\n', value: 'FLOWISE_NEWLINE' }, + { escape: '\b', value: 'FLOWISE_BACKSPACE' }, + { escape: '\f', value: 'FLOWISE_FORM_FEED' }, + { escape: '\r', value: 'FLOWISE_CARRIAGE_RETURN' }, + { escape: '\t', value: 'FLOWISE_TAB' }, + { escape: '\\', value: 'FLOWISE_BACKSLASH' } +] + +function handleEscapesJSONParse(input: string, reverse: Boolean): string { + for (const element of jsonEscapeCharacters) { + input = reverse ? input.replaceAll(element.value, element.escape) : input.replaceAll(element.escape, element.value) + } + return input +} + +function iterateEscapesJSONParse(input: any, reverse: Boolean): any { + for (const element in input) { + const type = typeof input[element] + if (type === 'string') input[element] = handleEscapesJSONParse(input[element], reverse) + else if (type === 'object') input[element] = iterateEscapesJSONParse(input[element], reverse) + } + return input +} + +export function handleEscapeCharacters(input: any, reverse: Boolean): any { + const type = typeof input + if (type === 'string') return handleEscapesJSONParse(input, reverse) + else if (type === 'object') return iterateEscapesJSONParse(input, reverse) + return input +} + /* * List of dependencies allowed to be import in vm2 */ diff --git a/packages/components/tsconfig.json b/packages/components/tsconfig.json index 2002d62f7..f4bdbd6ad 100644 --- a/packages/components/tsconfig.json +++ b/packages/components/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "lib": ["ES2020"], + "lib": ["ES2020", "ES2021.String"], "experimentalDecorators": true /* Enable experimental support for TC39 stage 2 draft decorators. */, "emitDecoratorMetadata": true /* Emit design-type metadata for decorated declarations in source files. */, "target": "ES2020", // or higher diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index 8e743031b..4519a52ed 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -16,7 +16,7 @@ import { IOverrideConfig } from '../Interface' import { cloneDeep, get, omit, merge } from 'lodash' -import { ICommonObject, getInputVariables, IDatabaseEntity } from 'flowise-components' +import { ICommonObject, getInputVariables, IDatabaseEntity, handleEscapeCharacters } from 'flowise-components' import { scryptSync, randomBytes, timingSafeEqual } from 'crypto' import { ChatFlow } from '../entity/ChatFlow' import { ChatMessage } from '../entity/ChatMessage' @@ -325,8 +325,13 @@ export const getVariableValue = (paramValue: string, reactFlowNodes: IReactFlowN const variableEndIdx = startIdx const variableFullPath = returnVal.substring(variableStartIdx, variableEndIdx) + /** + * Apply string transformation to convert special chars: + * FROM: hello i am ben\n\n\thow are you? + * TO: hello i am benFLOWISE_NEWLINEFLOWISE_NEWLINEFLOWISE_TABhow are you? + */ if (isAcceptVariable && variableFullPath === QUESTION_VAR_PREFIX) { - variableDict[`{{${variableFullPath}}}`] = question + variableDict[`{{${variableFullPath}}}`] = handleEscapeCharacters(question, false) } // Split by first occurrence of '.' to get just nodeId