Bugfix/AgentflowV2 State (#4512)

* add persistence state, http node variables, custom function flow state

* update marketplace templates
This commit is contained in:
Henry Heng
2025-05-27 18:01:39 +01:00
committed by GitHub
parent 572fb31a1c
commit 01dab4365a
15 changed files with 177 additions and 16 deletions
@@ -157,7 +157,8 @@ class CustomFunction_Agentflow implements INode {
chatflowId: options.chatflowid,
sessionId: options.sessionId,
chatId: options.chatId,
input
input,
state: newState
}
let sandbox: any = {
@@ -21,7 +21,7 @@ class HTTP_Agentflow implements INode {
constructor() {
this.label = 'HTTP'
this.name = 'httpAgentflow'
this.version = 1.0
this.version = 1.1
this.type = 'HTTP'
this.category = 'Agent Flows'
this.description = 'Send a HTTP request'
@@ -72,6 +72,7 @@ class HTTP_Agentflow implements INode {
label: 'Headers',
name: 'headers',
type: 'array',
acceptVariable: true,
array: [
{
label: 'Key',
@@ -83,7 +84,8 @@ class HTTP_Agentflow implements INode {
label: 'Value',
name: 'value',
type: 'string',
default: ''
default: '',
acceptVariable: true
}
],
optional: true
@@ -92,6 +94,7 @@ class HTTP_Agentflow implements INode {
label: 'Query Params',
name: 'queryParams',
type: 'array',
acceptVariable: true,
array: [
{
label: 'Key',
@@ -103,7 +106,8 @@ class HTTP_Agentflow implements INode {
label: 'Value',
name: 'value',
type: 'string',
default: ''
default: '',
acceptVariable: true
}
],
optional: true
@@ -147,6 +151,7 @@ class HTTP_Agentflow implements INode {
label: 'Body',
name: 'body',
type: 'array',
acceptVariable: true,
show: {
bodyType: ['xWwwFormUrlencoded', 'formData']
},
@@ -161,7 +166,8 @@ class HTTP_Agentflow implements INode {
label: 'Value',
name: 'value',
type: 'string',
default: ''
default: '',
acceptVariable: true
}
],
optional: true
@@ -18,7 +18,7 @@ class Start_Agentflow implements INode {
constructor() {
this.label = 'Start'
this.name = 'startAgentflow'
this.version = 1.0
this.version = 1.1
this.type = 'Start'
this.category = 'Agent Flows'
this.description = 'Starting point of the agentflow'
@@ -153,6 +153,13 @@ class Start_Agentflow implements INode {
optional: true
}
]
},
{
label: 'Persist State',
name: 'startPersistState',
type: 'boolean',
description: 'Persist the state in the same session',
optional: true
}
]
}
@@ -161,6 +168,7 @@ class Start_Agentflow implements INode {
const _flowState = nodeData.inputs?.startState as string
const startInputType = nodeData.inputs?.startInputType as string
const startEphemeralMemory = nodeData.inputs?.startEphemeralMemory as boolean
const startPersistState = nodeData.inputs?.startPersistState as boolean
let flowStateArray = []
if (_flowState) {
@@ -176,6 +184,13 @@ class Start_Agentflow implements INode {
flowState[state.key] = state.value
}
const runtimeState = options.agentflowRuntime?.state as ICommonObject
if (startPersistState === true && runtimeState && Object.keys(runtimeState).length) {
for (const state in runtimeState) {
flowState[state] = runtimeState[state]
}
}
const inputData: ICommonObject = {}
const outputData: ICommonObject = {}
@@ -202,6 +217,10 @@ class Start_Agentflow implements INode {
outputData.ephemeralMemory = true
}
if (startPersistState) {
outputData.persistState = true
}
const returnOutput = {
id: nodeData.id,
name: this.name,
@@ -12,7 +12,7 @@
"data": {
"id": "startAgentflow_0",
"label": "Start",
"version": 1,
"version": 1.1,
"name": "startAgentflow",
"type": "Start",
"color": "#7EE787",
@@ -157,6 +157,15 @@
],
"id": "startAgentflow_0-input-startState-array",
"display": true
},
{
"label": "Persist State",
"name": "startPersistState",
"type": "boolean",
"description": "Persist the state in the same session",
"optional": true,
"id": "startAgentflow_0-input-startPersistState-boolean",
"display": true
}
],
"inputAnchors": [],
@@ -12,7 +12,7 @@
"data": {
"id": "startAgentflow_0",
"label": "Start",
"version": 1,
"version": 1.1,
"name": "startAgentflow",
"type": "Start",
"color": "#7EE787",
@@ -157,6 +157,15 @@
],
"id": "startAgentflow_0-input-startState-array",
"display": true
},
{
"label": "Persist State",
"name": "startPersistState",
"type": "boolean",
"description": "Persist the state in the same session",
"optional": true,
"id": "startAgentflow_0-input-startPersistState-boolean",
"display": true
}
],
"inputAnchors": [],
@@ -12,7 +12,7 @@
"data": {
"id": "startAgentflow_0",
"label": "Start",
"version": 1,
"version": 1.1,
"name": "startAgentflow",
"type": "Start",
"color": "#7EE787",
@@ -160,6 +160,15 @@
],
"id": "startAgentflow_0-input-startState-array",
"display": true
},
{
"label": "Persist State",
"name": "startPersistState",
"type": "boolean",
"description": "Persist the state in the same session",
"optional": true,
"id": "startAgentflow_0-input-startPersistState-boolean",
"display": true
}
],
"inputAnchors": [],
@@ -12,7 +12,7 @@
"data": {
"id": "startAgentflow_0",
"label": "Start",
"version": 1,
"version": 1.1,
"name": "startAgentflow",
"type": "Start",
"color": "#7EE787",
@@ -157,6 +157,15 @@
],
"id": "startAgentflow_0-input-startState-array",
"display": true
},
{
"label": "Persist State",
"name": "startPersistState",
"type": "boolean",
"description": "Persist the state in the same session",
"optional": true,
"id": "startAgentflow_0-input-startPersistState-boolean",
"display": true
}
],
"inputAnchors": [],
@@ -12,7 +12,7 @@
"data": {
"id": "startAgentflow_0",
"label": "Start",
"version": 1,
"version": 1.1,
"name": "startAgentflow",
"type": "Start",
"color": "#7EE787",
@@ -157,6 +157,15 @@
],
"id": "startAgentflow_0-input-startState-array",
"display": true
},
{
"label": "Persist State",
"name": "startPersistState",
"type": "boolean",
"description": "Persist the state in the same session",
"optional": true,
"id": "startAgentflow_0-input-startPersistState-boolean",
"display": true
}
],
"inputAnchors": [],
@@ -12,7 +12,7 @@
"data": {
"id": "startAgentflow_0",
"label": "Start",
"version": 1,
"version": 1.1,
"name": "startAgentflow",
"type": "Start",
"color": "#7EE787",
@@ -157,6 +157,15 @@
],
"id": "startAgentflow_0-input-startState-array",
"display": true
},
{
"label": "Persist State",
"name": "startPersistState",
"type": "boolean",
"description": "Persist the state in the same session",
"optional": true,
"id": "startAgentflow_0-input-startPersistState-boolean",
"display": true
}
],
"inputAnchors": [],
@@ -12,7 +12,7 @@
"data": {
"id": "startAgentflow_0",
"label": "Start",
"version": 1,
"version": 1.1,
"name": "startAgentflow",
"type": "Start",
"color": "#7EE787",
@@ -157,6 +157,15 @@
],
"id": "startAgentflow_0-input-startState-array",
"display": true
},
{
"label": "Persist State",
"name": "startPersistState",
"type": "boolean",
"description": "Persist the state in the same session",
"optional": true,
"id": "startAgentflow_0-input-startPersistState-boolean",
"display": true
}
],
"inputAnchors": [],
@@ -12,7 +12,7 @@
"data": {
"id": "startAgentflow_0",
"label": "Start",
"version": 1,
"version": 1.1,
"name": "startAgentflow",
"type": "Start",
"color": "#7EE787",
@@ -157,6 +157,15 @@
],
"id": "startAgentflow_0-input-startState-array",
"display": true
},
{
"label": "Persist State",
"name": "startPersistState",
"type": "boolean",
"description": "Persist the state in the same session",
"optional": true,
"id": "startAgentflow_0-input-startPersistState-boolean",
"display": true
}
],
"inputAnchors": [],
@@ -1324,6 +1324,24 @@ export const executeAgentFlow = async ({
}
}
// If the state is persistent, get the state from the previous execution
const startPersistState = nodes.find((node) => node.data.name === 'startAgentflow')?.data.inputs?.startPersistState
if (startPersistState === true && previousExecution) {
const previousExecutionData = (JSON.parse(previousExecution.executionData) as IAgentflowExecutedData[]) ?? []
let previousState = {}
if (Array.isArray(previousExecutionData) && previousExecutionData.length) {
for (const execData of previousExecutionData.reverse()) {
if (execData.data.state) {
previousState = execData.data.state
break
}
}
}
agentflowRuntime.state = previousState
}
// If the start input type is form input, get the form values from the previous execution (form values are persisted in the same session)
if (startInputType === 'formInput' && previousExecution) {
const previousExecutionData = (JSON.parse(previousExecution.executionData) as IAgentflowExecutedData[]) ?? []
@@ -54,7 +54,7 @@ export const ArrayRenderer = ({ inputParam, data, disabled }) => {
// Initialize array items and parameters when component mounts or data changes
useEffect(() => {
const initialArrayItems = cloneDeep(data.inputs[inputParam.name]) || []
const initialArrayItems = data.inputs[inputParam.name] || []
setArrayItems(initialArrayItems)
// Calculate initial display parameters for each array item
@@ -20,7 +20,8 @@ import {
IconCopy,
IconTrash,
IconInfoCircle,
IconLoader
IconLoader,
IconAlertCircleFilled
} from '@tabler/icons-react'
import StopCircleIcon from '@mui/icons-material/StopCircle'
import CancelIcon from '@mui/icons-material/Cancel'
@@ -51,11 +52,13 @@ const StyledNodeToolbar = styled(NodeToolbar)(({ theme }) => ({
const AgentFlowNode = ({ data }) => {
const theme = useTheme()
const customization = useSelector((state) => state.customization)
const canvas = useSelector((state) => state.canvas)
const ref = useRef(null)
const updateNodeInternals = useUpdateNodeInternals()
// eslint-disable-next-line
const [position, setPosition] = useState(0)
const [isHovered, setIsHovered] = useState(false)
const [warningMessage, setWarningMessage] = useState('')
const { deleteNode, duplicateNode } = useContext(flowContext)
const [showInfoDialog, setShowInfoDialog] = useState(false)
const [infoDialogProps, setInfoDialogProps] = useState({})
@@ -132,6 +135,28 @@ const AgentFlowNode = ({ data }) => {
}
}, [data, ref, updateNodeInternals])
useEffect(() => {
const nodeOutdatedMessage = (oldVersion, newVersion) =>
`Node version ${oldVersion} outdated\nUpdate to latest version ${newVersion}`
const nodeVersionEmptyMessage = (newVersion) => `Node outdated\nUpdate to latest version ${newVersion}`
const componentNode = canvas.componentNodes.find((nd) => nd.name === data.name)
if (componentNode) {
if (!data.version) {
setWarningMessage(nodeVersionEmptyMessage(componentNode.version))
} else if (data.version && componentNode.version > data.version) {
setWarningMessage(nodeOutdatedMessage(data.version, componentNode.version))
} else if (componentNode.badge === 'DEPRECATING') {
setWarningMessage(
componentNode?.deprecateMessage ??
'This node will be deprecated in the next release. Change to a new node tagged with NEW'
)
} else {
setWarningMessage('')
}
}
}, [canvas.componentNodes, data.name, data.version])
return (
<div ref={ref} onMouseEnter={() => setIsHovered(true)} onMouseLeave={() => setIsHovered(false)}>
<StyledNodeToolbar>
@@ -236,6 +261,24 @@ const AgentFlowNode = ({ data }) => {
</Tooltip>
)}
{warningMessage && (
<Tooltip placement='right-start' title={<span style={{ whiteSpace: 'pre-line' }}>{warningMessage}</span>}>
<Avatar
variant='rounded'
sx={{
...theme.typography.smallAvatar,
borderRadius: '50%',
background: 'white',
position: 'absolute',
top: -10,
left: -10
}}
>
<IconAlertCircleFilled color='orange' />
</Avatar>
</Tooltip>
)}
<Box sx={{ width: '100%' }}>
{!data.hideInput && (
<Handle
@@ -1037,6 +1037,7 @@ const NodeInputHandler = ({
variant='outlined'
onClick={() => {
data.inputs[inputParam.name] = inputParam.codeExample
setReloadTimestamp(Date.now().toString())
}}
>
See Example
@@ -1044,6 +1045,7 @@ const NodeInputHandler = ({
)}
</div>
<div
key={`${reloadTimestamp}_${data.id}}`}
style={{
marginTop: '10px',
border: '1px solid',