Feature/Add ability to get vars from text input (#2986)

add ability to get vars from text input
This commit is contained in:
Henry Heng
2024-08-09 17:31:21 +01:00
committed by GitHub
parent d1da628b7c
commit 376e644cec
4 changed files with 118 additions and 15 deletions
+3 -3
View File
@@ -485,7 +485,7 @@ const compileMultiAgentsGraph = async (
let flowNodeData = cloneDeep(workerNode.data) let flowNodeData = cloneDeep(workerNode.data)
if (overrideConfig) flowNodeData = replaceInputsWithConfig(flowNodeData, overrideConfig) if (overrideConfig) flowNodeData = replaceInputsWithConfig(flowNodeData, overrideConfig)
flowNodeData = resolveVariables(flowNodeData, reactflowNodes, question, chatHistory) flowNodeData = await resolveVariables(appServer.AppDataSource, flowNodeData, reactflowNodes, question, chatHistory, overrideConfig)
try { try {
const workerResult: IMultiAgentNode = await newNodeInstance.init(flowNodeData, question, options) const workerResult: IMultiAgentNode = await newNodeInstance.init(flowNodeData, question, options)
@@ -516,7 +516,7 @@ const compileMultiAgentsGraph = async (
let flowNodeData = cloneDeep(supervisorNode.data) let flowNodeData = cloneDeep(supervisorNode.data)
if (overrideConfig) flowNodeData = replaceInputsWithConfig(flowNodeData, overrideConfig) 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] if (flowNodeData.inputs) flowNodeData.inputs.workerNodes = supervisorWorkers[supervisor]
@@ -676,7 +676,7 @@ const compileSeqAgentsGraph = async (
flowNodeData = cloneDeep(node.data) flowNodeData = cloneDeep(node.data)
if (overrideConfig) flowNodeData = replaceInputsWithConfig(flowNodeData, overrideConfig) 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) const seqAgentNode: ISeqAgentNode = await newNodeInstance.init(flowNodeData, question, options)
return seqAgentNode return seqAgentNode
+8 -1
View File
@@ -328,7 +328,14 @@ export const utilBuildChatflow = async (req: Request, socketIO?: Server, isInter
nodeToExecute.data = replaceInputsWithConfig(nodeToExecute.data, incomingInput.overrideConfig) 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 nodeToExecuteData = reactFlowNodeData
appServer.chatflowPool.add(chatflowid, nodeToExecuteData, startingNodes, incomingInput?.overrideConfig) appServer.chatflowPool.add(chatflowid, nodeToExecuteData, startingNodes, incomingInput?.overrideConfig)
+93 -10
View File
@@ -503,7 +503,14 @@ export const buildFlow = async ({
if (isUpsert) upsertHistory['flowData'] = saveUpsertFlowData(flowNodeData, upsertHistory) 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) { if (isUpsert && stopNodeId && nodeId === stopNodeId) {
logger.debug(`[server]: Upserting ${reactFlowNode.data.label} (${reactFlowNode.data.id})`) 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 * Get variable value from outputResponses.output
* @param {string} paramValue * @param {string} paramValue
@@ -699,12 +753,14 @@ export const clearSessionMemory = async (
* @param {boolean} isAcceptVariable * @param {boolean} isAcceptVariable
* @returns {string} * @returns {string}
*/ */
export const getVariableValue = ( export const getVariableValue = async (
appDataSource: DataSource,
paramValue: string | object, paramValue: string | object,
reactFlowNodes: IReactFlowNode[], reactFlowNodes: IReactFlowNode[],
question: string, question: string,
chatHistory: IMessage[], chatHistory: IMessage[],
isAcceptVariable = false isAcceptVariable = false,
overrideConfig?: ICommonObject
) => { ) => {
const isObject = typeof paramValue === 'object' const isObject = typeof paramValue === 'object'
let returnVal = (isObject ? JSON.stringify(paramValue) : paramValue) ?? '' let returnVal = (isObject ? JSON.stringify(paramValue) : paramValue) ?? ''
@@ -740,6 +796,15 @@ export const getVariableValue = (
variableDict[`{{${variableFullPath}}}`] = handleEscapeCharacters(convertChatHistoryToText(chatHistory), false) 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. // Resolve values with following case.
// 1: <variableNodeId>.data.instance // 1: <variableNodeId>.data.instance
// 2: <variableNodeId>.data.instance.pathtokey // 2: <variableNodeId>.data.instance.pathtokey
@@ -826,35 +891,53 @@ export const getVariableValue = (
* @param {string} question * @param {string} question
* @returns {INodeData} * @returns {INodeData}
*/ */
export const resolveVariables = ( export const resolveVariables = async (
appDataSource: DataSource,
reactFlowNodeData: INodeData, reactFlowNodeData: INodeData,
reactFlowNodes: IReactFlowNode[], reactFlowNodes: IReactFlowNode[],
question: string, question: string,
chatHistory: IMessage[] chatHistory: IMessage[],
): INodeData => { overrideConfig?: ICommonObject
): Promise<INodeData> => {
let flowNodeData = cloneDeep(reactFlowNodeData) let flowNodeData = cloneDeep(reactFlowNodeData)
const types = 'inputs' const types = 'inputs'
const getParamValues = (paramsObj: ICommonObject) => { const getParamValues = async (paramsObj: ICommonObject) => {
for (const key in paramsObj) { for (const key in paramsObj) {
const paramValue: string = paramsObj[key] const paramValue: string = paramsObj[key]
if (Array.isArray(paramValue)) { if (Array.isArray(paramValue)) {
const resolvedInstances = [] const resolvedInstances = []
for (const param of paramValue) { 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) resolvedInstances.push(resolvedInstance)
} }
paramsObj[key] = resolvedInstances paramsObj[key] = resolvedInstances
} else { } else {
const isAcceptVariable = reactFlowNodeData.inputParams.find((param) => param.name === key)?.acceptVariable ?? false 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 paramsObj[key] = resolvedInstance
} }
} }
} }
const paramsObj = flowNodeData[types] ?? {} const paramsObj = flowNodeData[types] ?? {}
getParamValues(paramsObj) await getParamValues(paramsObj)
return flowNodeData return flowNodeData
} }
@@ -27,7 +27,9 @@ const HowToUseVariablesDialog = ({ show, onCancel }) => {
How To Use Variables How To Use Variables
</DialogTitle> </DialogTitle>
<DialogContent> <DialogContent>
<p style={{ marginBottom: '10px' }}>Variables can be used in Custom Tool Function with the $ prefix.</p> <p style={{ marginBottom: '10px' }}>
Variables can be used in Custom Tool, Custom Function, Custom Loader, If Else Function with the $ prefix.
</p>
<CodeEditor <CodeEditor
disabled={true} disabled={true}
value={`$vars.<variable-name>`} value={`$vars.<variable-name>`}
@@ -36,6 +38,17 @@ const HowToUseVariablesDialog = ({ show, onCancel }) => {
lang={'js'} lang={'js'}
basicSetup={{ highlightActiveLine: false, highlightActiveLineGutter: false }} basicSetup={{ highlightActiveLine: false, highlightActiveLineGutter: false }}
/> />
<p style={{ marginBottom: '10px' }}>
Variables can also be used in Text Field parameter of any node. For example, in System Message of Agent:
</p>
<CodeEditor
disabled={true}
value={`You are a {{$vars.personality}} AI assistant`}
height={'50px'}
theme={'dark'}
lang={'js'}
basicSetup={{ highlightActiveLine: false, highlightActiveLineGutter: false }}
/>
<p style={{ marginBottom: '10px' }}> <p style={{ marginBottom: '10px' }}>
If variable type is Static, the value will be retrieved as it is. If variable type is Runtime, the value will be 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. retrieved from .env file.