From 376e644cec22266ef9a98f2f84f20aac27499a95 Mon Sep 17 00:00:00 2001 From: Henry Heng Date: Fri, 9 Aug 2024 17:31:21 +0100 Subject: [PATCH] Feature/Add ability to get vars from text input (#2986) add ability to get vars from text input --- packages/server/src/utils/buildAgentGraph.ts | 6 +- packages/server/src/utils/buildChatflow.ts | 9 +- packages/server/src/utils/index.ts | 103 ++++++++++++++++-- .../variables/HowToUseVariablesDialog.jsx | 15 ++- 4 files changed, 118 insertions(+), 15 deletions(-) diff --git a/packages/server/src/utils/buildAgentGraph.ts b/packages/server/src/utils/buildAgentGraph.ts index c4ef963c..945bc07c 100644 --- a/packages/server/src/utils/buildAgentGraph.ts +++ b/packages/server/src/utils/buildAgentGraph.ts @@ -485,7 +485,7 @@ const compileMultiAgentsGraph = async ( let flowNodeData = cloneDeep(workerNode.data) if (overrideConfig) flowNodeData = replaceInputsWithConfig(flowNodeData, overrideConfig) - flowNodeData = resolveVariables(flowNodeData, reactflowNodes, question, chatHistory) + flowNodeData = await resolveVariables(appServer.AppDataSource, flowNodeData, reactflowNodes, question, chatHistory, overrideConfig) try { const workerResult: IMultiAgentNode = await newNodeInstance.init(flowNodeData, question, options) @@ -516,7 +516,7 @@ const compileMultiAgentsGraph = async ( let flowNodeData = cloneDeep(supervisorNode.data) if (overrideConfig) flowNodeData = replaceInputsWithConfig(flowNodeData, overrideConfig) - flowNodeData = resolveVariables(flowNodeData, reactflowNodes, question, chatHistory) + flowNodeData = await resolveVariables(appServer.AppDataSource, flowNodeData, reactflowNodes, question, chatHistory, overrideConfig) if (flowNodeData.inputs) flowNodeData.inputs.workerNodes = supervisorWorkers[supervisor] @@ -676,7 +676,7 @@ const compileSeqAgentsGraph = async ( flowNodeData = cloneDeep(node.data) if (overrideConfig) flowNodeData = replaceInputsWithConfig(flowNodeData, overrideConfig) - flowNodeData = resolveVariables(flowNodeData, reactflowNodes, question, chatHistory) + flowNodeData = await resolveVariables(appServer.AppDataSource, flowNodeData, reactflowNodes, question, chatHistory, overrideConfig) const seqAgentNode: ISeqAgentNode = await newNodeInstance.init(flowNodeData, question, options) return seqAgentNode diff --git a/packages/server/src/utils/buildChatflow.ts b/packages/server/src/utils/buildChatflow.ts index 239d02f5..93a52332 100644 --- a/packages/server/src/utils/buildChatflow.ts +++ b/packages/server/src/utils/buildChatflow.ts @@ -328,7 +328,14 @@ export const utilBuildChatflow = async (req: Request, socketIO?: Server, isInter nodeToExecute.data = replaceInputsWithConfig(nodeToExecute.data, incomingInput.overrideConfig) } - const reactFlowNodeData: INodeData = resolveVariables(nodeToExecute.data, reactFlowNodes, incomingInput.question, chatHistory) + const reactFlowNodeData: INodeData = await resolveVariables( + appServer.AppDataSource, + nodeToExecute.data, + reactFlowNodes, + incomingInput.question, + chatHistory, + incomingInput.overrideConfig + ) nodeToExecuteData = reactFlowNodeData appServer.chatflowPool.add(chatflowid, nodeToExecuteData, startingNodes, incomingInput?.overrideConfig) diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index 6fe07ffa..c77241f9 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -503,7 +503,14 @@ export const buildFlow = async ({ if (isUpsert) upsertHistory['flowData'] = saveUpsertFlowData(flowNodeData, upsertHistory) - const reactFlowNodeData: INodeData = resolveVariables(flowNodeData, flowNodes, question, chatHistory) + const reactFlowNodeData: INodeData = await resolveVariables( + appDataSource, + flowNodeData, + flowNodes, + question, + chatHistory, + overrideConfig + ) if (isUpsert && stopNodeId && nodeId === stopNodeId) { logger.debug(`[server]: Upserting ${reactFlowNode.data.label} (${reactFlowNode.data.id})`) @@ -691,6 +698,53 @@ export const clearSessionMemory = async ( } } +const getGlobalVariable = async (appDataSource: DataSource, overrideConfig?: ICommonObject) => { + const variables = await appDataSource.getRepository(Variable).find() + + // override variables defined in overrideConfig + // nodeData.inputs.vars is an Object, check each property and override the variable + if (overrideConfig?.vars) { + for (const propertyName of Object.getOwnPropertyNames(overrideConfig.vars)) { + const foundVar = variables.find((v) => v.name === propertyName) + if (foundVar) { + // even if the variable was defined as runtime, we override it with static value + foundVar.type = 'static' + foundVar.value = overrideConfig.vars[propertyName] + } else { + // add it the variables, if not found locally in the db + variables.push({ + name: propertyName, + type: 'static', + value: overrideConfig.vars[propertyName], + id: '', + updatedDate: new Date(), + createdDate: new Date() + }) + } + } + } + + let vars = {} + if (variables.length) { + for (const item of variables) { + let value = item.value + + // read from .env file + if (item.type === 'runtime') { + value = process.env[item.name] ?? '' + } + + Object.defineProperty(vars, item.name, { + enumerable: true, + configurable: true, + writable: true, + value: value + }) + } + } + return vars +} + /** * Get variable value from outputResponses.output * @param {string} paramValue @@ -699,12 +753,14 @@ export const clearSessionMemory = async ( * @param {boolean} isAcceptVariable * @returns {string} */ -export const getVariableValue = ( +export const getVariableValue = async ( + appDataSource: DataSource, paramValue: string | object, reactFlowNodes: IReactFlowNode[], question: string, chatHistory: IMessage[], - isAcceptVariable = false + isAcceptVariable = false, + overrideConfig?: ICommonObject ) => { const isObject = typeof paramValue === 'object' let returnVal = (isObject ? JSON.stringify(paramValue) : paramValue) ?? '' @@ -740,6 +796,15 @@ export const getVariableValue = ( variableDict[`{{${variableFullPath}}}`] = handleEscapeCharacters(convertChatHistoryToText(chatHistory), false) } + if (variableFullPath.startsWith('$vars.')) { + const vars = await getGlobalVariable(appDataSource, overrideConfig) + const variableValue = get(vars, variableFullPath.replace('$vars.', '')) + if (variableValue) { + variableDict[`{{${variableFullPath}}}`] = variableValue + returnVal = returnVal.split(`{{${variableFullPath}}}`).join(variableValue) + } + } + // Resolve values with following case. // 1: .data.instance // 2: .data.instance.pathtokey @@ -826,35 +891,53 @@ export const getVariableValue = ( * @param {string} question * @returns {INodeData} */ -export const resolveVariables = ( +export const resolveVariables = async ( + appDataSource: DataSource, reactFlowNodeData: INodeData, reactFlowNodes: IReactFlowNode[], question: string, - chatHistory: IMessage[] -): INodeData => { + chatHistory: IMessage[], + overrideConfig?: ICommonObject +): Promise => { let flowNodeData = cloneDeep(reactFlowNodeData) const types = 'inputs' - const getParamValues = (paramsObj: ICommonObject) => { + const getParamValues = async (paramsObj: ICommonObject) => { for (const key in paramsObj) { const paramValue: string = paramsObj[key] if (Array.isArray(paramValue)) { const resolvedInstances = [] for (const param of paramValue) { - const resolvedInstance = getVariableValue(param, reactFlowNodes, question, chatHistory) + const resolvedInstance = await getVariableValue( + appDataSource, + param, + reactFlowNodes, + question, + chatHistory, + undefined, + overrideConfig + ) resolvedInstances.push(resolvedInstance) } paramsObj[key] = resolvedInstances } else { const isAcceptVariable = reactFlowNodeData.inputParams.find((param) => param.name === key)?.acceptVariable ?? false - const resolvedInstance = getVariableValue(paramValue, reactFlowNodes, question, chatHistory, isAcceptVariable) + const resolvedInstance = await getVariableValue( + appDataSource, + paramValue, + reactFlowNodes, + question, + chatHistory, + isAcceptVariable, + overrideConfig + ) paramsObj[key] = resolvedInstance } } } const paramsObj = flowNodeData[types] ?? {} - getParamValues(paramsObj) + await getParamValues(paramsObj) return flowNodeData } diff --git a/packages/ui/src/views/variables/HowToUseVariablesDialog.jsx b/packages/ui/src/views/variables/HowToUseVariablesDialog.jsx index b5e11704..61a59acb 100644 --- a/packages/ui/src/views/variables/HowToUseVariablesDialog.jsx +++ b/packages/ui/src/views/variables/HowToUseVariablesDialog.jsx @@ -27,7 +27,9 @@ const HowToUseVariablesDialog = ({ show, onCancel }) => { How To Use Variables -

Variables can be used in Custom Tool Function with the $ prefix.

+

+ Variables can be used in Custom Tool, Custom Function, Custom Loader, If Else Function with the $ prefix. +

`} @@ -36,6 +38,17 @@ const HowToUseVariablesDialog = ({ show, onCancel }) => { lang={'js'} basicSetup={{ highlightActiveLine: false, highlightActiveLineGutter: false }} /> +

+ Variables can also be used in Text Field parameter of any node. For example, in System Message of Agent: +

+

If variable type is Static, the value will be retrieved as it is. If variable type is Runtime, the value will be retrieved from .env file.