Skip to content

Commit 10c6a91

Browse files
HenryHengZJerhhung
authored andcommitted
Feature/Update Loop Agentflow (FlowiseAI#4957)
* Feature: Update Loop Agentflow to include fallback message and version increment to 1.1 - Added a new input parameter 'fallbackMessage' to the Loop Agentflow for displaying a message when the loop count is exceeded. - Incremented the version of Loop Agentflow from 1.0 to 1.1. - Updated the processing logic to handle the fallback message appropriately when the maximum loop count is reached. * - Introduced a new input parameter 'loopUpdateState' to allow updating the runtime state during workflow execution. - Added a method to list runtime state keys for dynamic state management. - Implemented logic to retrieve and utilize the current loop count in variable resolution. - Updated the Loop Agentflow output to reflect the new state and final output content.
1 parent 7507f8d commit 10c6a91

File tree

4 files changed

+107
-9
lines changed

4 files changed

+107
-9
lines changed

packages/components/nodes/agentflow/Loop/Loop.ts

Lines changed: 65 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { ICommonObject, INode, INodeData, INodeOptionsValue, INodeParams } from '../../../src/Interface'
2+
import { updateFlowState } from '../utils'
23

34
class Loop_Agentflow implements INode {
45
label: string
@@ -19,7 +20,7 @@ class Loop_Agentflow implements INode {
1920
constructor() {
2021
this.label = 'Loop'
2122
this.name = 'loopAgentflow'
22-
this.version = 1.0
23+
this.version = 1.1
2324
this.type = 'Loop'
2425
this.category = 'Agent Flows'
2526
this.description = 'Loop back to a previous node'
@@ -40,6 +41,40 @@ class Loop_Agentflow implements INode {
4041
name: 'maxLoopCount',
4142
type: 'number',
4243
default: 5
44+
},
45+
{
46+
label: 'Fallback Message',
47+
name: 'fallbackMessage',
48+
type: 'string',
49+
description: 'Message to display if the loop count is exceeded',
50+
placeholder: 'Enter your fallback message here',
51+
rows: 4,
52+
acceptVariable: true,
53+
optional: true
54+
},
55+
{
56+
label: 'Update Flow State',
57+
name: 'loopUpdateState',
58+
description: 'Update runtime state during the execution of the workflow',
59+
type: 'array',
60+
optional: true,
61+
acceptVariable: true,
62+
array: [
63+
{
64+
label: 'Key',
65+
name: 'key',
66+
type: 'asyncOptions',
67+
loadMethod: 'listRuntimeStateKeys',
68+
freeSolo: true
69+
},
70+
{
71+
label: 'Value',
72+
name: 'value',
73+
type: 'string',
74+
acceptVariable: true,
75+
acceptNodeOutputAsVariable: true
76+
}
77+
]
4378
}
4479
]
4580
}
@@ -58,12 +93,20 @@ class Loop_Agentflow implements INode {
5893
})
5994
}
6095
return returnOptions
96+
},
97+
async listRuntimeStateKeys(_: INodeData, options: ICommonObject): Promise<INodeOptionsValue[]> {
98+
const previousNodes = options.previousNodes as ICommonObject[]
99+
const startAgentflowNode = previousNodes.find((node) => node.name === 'startAgentflow')
100+
const state = startAgentflowNode?.inputs?.startState as ICommonObject[]
101+
return state.map((item) => ({ label: item.key, name: item.key }))
61102
}
62103
}
63104

64105
async run(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
65106
const loopBackToNode = nodeData.inputs?.loopBackToNode as string
66107
const _maxLoopCount = nodeData.inputs?.maxLoopCount as string
108+
const fallbackMessage = nodeData.inputs?.fallbackMessage as string
109+
const _loopUpdateState = nodeData.inputs?.loopUpdateState
67110

68111
const state = options.agentflowRuntime?.state as ICommonObject
69112

@@ -75,16 +118,34 @@ class Loop_Agentflow implements INode {
75118
maxLoopCount: _maxLoopCount ? parseInt(_maxLoopCount) : 5
76119
}
77120

121+
const finalOutput = 'Loop back to ' + `${loopBackToNodeLabel} (${loopBackToNodeId})`
122+
123+
// Update flow state if needed
124+
let newState = { ...state }
125+
if (_loopUpdateState && Array.isArray(_loopUpdateState) && _loopUpdateState.length > 0) {
126+
newState = updateFlowState(state, _loopUpdateState)
127+
}
128+
129+
// Process template variables in state
130+
if (newState && Object.keys(newState).length > 0) {
131+
for (const key in newState) {
132+
if (newState[key].toString().includes('{{ output }}')) {
133+
newState[key] = finalOutput
134+
}
135+
}
136+
}
137+
78138
const returnOutput = {
79139
id: nodeData.id,
80140
name: this.name,
81141
input: data,
82142
output: {
83-
content: 'Loop back to ' + `${loopBackToNodeLabel} (${loopBackToNodeId})`,
143+
content: finalOutput,
84144
nodeID: loopBackToNodeId,
85-
maxLoopCount: _maxLoopCount ? parseInt(_maxLoopCount) : 5
145+
maxLoopCount: _maxLoopCount ? parseInt(_maxLoopCount) : 5,
146+
fallbackMessage
86147
},
87-
state
148+
state: newState
88149
}
89150

90151
return returnOutput

packages/server/src/utils/buildAgentflow.ts

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@ import {
4343
QUESTION_VAR_PREFIX,
4444
CURRENT_DATE_TIME_VAR_PREFIX,
4545
_removeCredentialId,
46-
validateHistorySchema
46+
validateHistorySchema,
47+
LOOP_COUNT_VAR_PREFIX
4748
} from '.'
4849
import { ChatFlow } from '../database/entities/ChatFlow'
4950
import { Variable } from '../database/entities/Variable'
@@ -84,6 +85,8 @@ interface IProcessNodeOutputsParams {
8485
waitingNodes: Map<string, IWaitingNode>
8586
loopCounts: Map<string, number>
8687
abortController?: AbortController
88+
sseStreamer?: IServerSideEventStreamer
89+
chatId: string
8790
}
8891

8992
interface IAgentFlowRuntime {
@@ -130,6 +133,7 @@ interface IExecuteNodeParams {
130133
parentExecutionId?: string
131134
isRecursive?: boolean
132135
iterationContext?: ICommonObject
136+
loopCounts?: Map<string, number>
133137
orgId: string
134138
workspaceId: string
135139
subscriptionId: string
@@ -218,7 +222,8 @@ export const resolveVariables = async (
218222
chatHistory: IMessage[],
219223
componentNodes: IComponentNodes,
220224
agentFlowExecutedData?: IAgentflowExecutedData[],
221-
iterationContext?: ICommonObject
225+
iterationContext?: ICommonObject,
226+
loopCounts?: Map<string, number>
222227
): Promise<INodeData> => {
223228
let flowNodeData = cloneDeep(reactFlowNodeData)
224229
const types = 'inputs'
@@ -285,6 +290,20 @@ export const resolveVariables = async (
285290
resolvedValue = resolvedValue.replace(match, flowConfig?.runtimeChatHistoryLength ?? 0)
286291
}
287292

293+
if (variableFullPath === LOOP_COUNT_VAR_PREFIX) {
294+
// Get the current loop count from the most recent loopAgentflow node execution
295+
let currentLoopCount = 0
296+
if (loopCounts && agentFlowExecutedData) {
297+
// Find the most recent loopAgentflow node execution to get its loop count
298+
const loopNodes = [...agentFlowExecutedData].reverse().filter((data) => data.data?.name === 'loopAgentflow')
299+
if (loopNodes.length > 0) {
300+
const latestLoopNode = loopNodes[0]
301+
currentLoopCount = loopCounts.get(latestLoopNode.nodeId) || 0
302+
}
303+
}
304+
resolvedValue = resolvedValue.replace(match, currentLoopCount.toString())
305+
}
306+
288307
if (variableFullPath === CURRENT_DATE_TIME_VAR_PREFIX) {
289308
resolvedValue = resolvedValue.replace(match, new Date().toISOString())
290309
}
@@ -742,7 +761,9 @@ async function processNodeOutputs({
742761
edges,
743762
nodeExecutionQueue,
744763
waitingNodes,
745-
loopCounts
764+
loopCounts,
765+
sseStreamer,
766+
chatId
746767
}: IProcessNodeOutputsParams): Promise<{ humanInput?: IHumanInput }> {
747768
logger.debug(`\n🔄 Processing outputs from node: ${nodeId}`)
748769

@@ -823,6 +844,11 @@ async function processNodeOutputs({
823844
}
824845
} else {
825846
logger.debug(` ⚠️ Maximum loop count (${maxLoop}) reached, stopping loop`)
847+
const fallbackMessage = result.output.fallbackMessage || `Loop completed after reaching maximum iteration count of ${maxLoop}.`
848+
if (sseStreamer) {
849+
sseStreamer.streamTokenEvent(chatId, fallbackMessage)
850+
}
851+
result.output = { ...result.output, content: fallbackMessage }
826852
}
827853
}
828854

@@ -967,6 +993,7 @@ const executeNode = async ({
967993
isInternal,
968994
isRecursive,
969995
iterationContext,
996+
loopCounts,
970997
orgId,
971998
workspaceId,
972999
subscriptionId,
@@ -1045,7 +1072,8 @@ const executeNode = async ({
10451072
chatHistory,
10461073
componentNodes,
10471074
agentFlowExecutedData,
1048-
iterationContext
1075+
iterationContext,
1076+
loopCounts
10491077
)
10501078

10511079
// Handle human input if present
@@ -1889,6 +1917,7 @@ export const executeAgentFlow = async ({
18891917
analyticHandlers,
18901918
isRecursive,
18911919
iterationContext,
1920+
loopCounts,
18921921
orgId,
18931922
workspaceId,
18941923
subscriptionId,
@@ -1957,7 +1986,8 @@ export const executeAgentFlow = async ({
19571986
nodeExecutionQueue,
19581987
waitingNodes,
19591988
loopCounts,
1960-
abortController
1989+
sseStreamer,
1990+
chatId
19611991
})
19621992

19631993
// Update humanInput if it was changed

packages/server/src/utils/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ export const QUESTION_VAR_PREFIX = 'question'
6969
export const FILE_ATTACHMENT_PREFIX = 'file_attachment'
7070
export const CHAT_HISTORY_VAR_PREFIX = 'chat_history'
7171
export const RUNTIME_MESSAGES_LENGTH_VAR_PREFIX = 'runtime_messages_length'
72+
export const LOOP_COUNT_VAR_PREFIX = 'loop_count'
7273
export const CURRENT_DATE_TIME_VAR_PREFIX = 'current_date_time'
7374
export const REDACTED_CREDENTIAL_VALUE = '_FLOWISE_BLANK_07167752-1a71-43b1-bf8f-4f32252165db'
7475

packages/ui/src/ui-component/input/suggestionOption.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,12 @@ export const suggestionOptions = (
7171
description: 'Total messsages between LLM and Agent',
7272
category: 'Chat Context'
7373
},
74+
{
75+
id: 'loop_count',
76+
mentionLabel: 'loop_count',
77+
description: 'Current loop count',
78+
category: 'Chat Context'
79+
},
7480
{
7581
id: 'file_attachment',
7682
mentionLabel: 'file_attachment',

0 commit comments

Comments
 (0)