Skip to content

Commit 85ea117

Browse files
authored
Fix workflow (#5592)
* fix: fileselector default * fix: workflow run process
1 parent 9be1e59 commit 85ea117

File tree

13 files changed

+229
-191
lines changed

13 files changed

+229
-191
lines changed

document/content/docs/upgrading/4-12/4123.mdx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,7 @@ description: 'FastGPT V4.12.3 更新说明'
1414
## 🐛 修复
1515

1616
1. 单团队模式下,如果用户离开,则无法重新进入团队。
17+
2. 工作流文件上传默认打开,但输入侧未添加文件输出。
18+
3. 连续用户选择,分支无法正常运行。
1719

1820
## 🔨 工具更新

document/data/doc-last-modified.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@
105105
"document/content/docs/upgrading/4-12/4120.mdx": "2025-08-12T22:45:19+08:00",
106106
"document/content/docs/upgrading/4-12/4121.mdx": "2025-08-15T22:53:06+08:00",
107107
"document/content/docs/upgrading/4-12/4122.mdx": "2025-08-27T00:31:33+08:00",
108-
"document/content/docs/upgrading/4-12/4123.mdx": "2025-08-29T01:24:19+08:00",
108+
"document/content/docs/upgrading/4-12/4123.mdx": "2025-09-04T13:48:03+08:00",
109109
"document/content/docs/upgrading/4-8/40.mdx": "2025-08-02T19:38:37+08:00",
110110
"document/content/docs/upgrading/4-8/41.mdx": "2025-08-02T19:38:37+08:00",
111111
"document/content/docs/upgrading/4-8/42.mdx": "2025-08-02T19:38:37+08:00",

packages/global/core/app/constants.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ export const defaultChatInputGuideConfig = {
4545
};
4646

4747
export const defaultAppSelectFileConfig: AppFileSelectConfigType = {
48-
canSelectFile: true,
48+
canSelectFile: false,
4949
canSelectImg: false,
5050
maxFiles: 10
5151
};

packages/global/core/workflow/template/system/interactive/type.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ type InteractiveBasicType = {
88
entryNodeIds: string[];
99
memoryEdges: RuntimeEdgeItemType[];
1010
nodeOutputs: NodeOutputItemType[];
11+
skipNodeQueue?: { id: string; skippedNodeIdList: string[] }[]; // 需要记录目前在 queue 里的节点
1112
toolParams?: {
1213
entryNodeIds: string[]; // 记录工具中,交互节点的 Id,而不是起始工作流的入口
1314
memoryMessages: ChatCompletionMessageParam[]; // 这轮工具中,产生的新的 messages

packages/service/core/workflow/dispatch/index.ts

Lines changed: 116 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,15 @@ import type { RuntimeEdgeItemType } from '@fastgpt/global/core/workflow/type/edg
4242
import type { ChatNodeUsageType } from '@fastgpt/global/support/wallet/bill/type';
4343
import { addLog } from '../../../common/system/log';
4444
import { surrenderProcess } from '../../../common/system/tools';
45-
import type { DispatchFlowResponse } from './type';
45+
import type { DispatchFlowResponse, WorkflowDebugResponse } from './type';
4646
import { removeSystemVariable, rewriteRuntimeWorkFlow } from './utils';
4747
import { getHandleId } from '@fastgpt/global/core/workflow/utils';
4848
import { callbackMap } from './constants';
4949

5050
type Props = Omit<ChatDispatchProps, 'workflowDispatchDeep'> & {
5151
runtimeNodes: RuntimeNodeItemType[];
5252
runtimeEdges: RuntimeEdgeItemType[];
53+
defaultSkipNodeQueue?: WorkflowDebugResponse['skipNodeQueue'];
5354
};
5455
type NodeResponseType = DispatchNodeResultType<{
5556
[key: string]: any;
@@ -100,6 +101,7 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
100101
// Init some props
101102
return runWorkflow({
102103
...data,
104+
defaultSkipNodeQueue: data.lastInteractive?.skipNodeQueue || data.defaultSkipNodeQueue,
103105
variables: defaultVariables,
104106
workflowDispatchDeep: 0
105107
}).finally(() => {
@@ -112,12 +114,14 @@ export async function dispatchWorkFlow(data: Props): Promise<DispatchFlowRespons
112114
type RunWorkflowProps = ChatDispatchProps & {
113115
runtimeNodes: RuntimeNodeItemType[];
114116
runtimeEdges: RuntimeEdgeItemType[];
117+
defaultSkipNodeQueue?: WorkflowDebugResponse['skipNodeQueue'];
115118
};
116119
export const runWorkflow = async (data: RunWorkflowProps): Promise<DispatchFlowResponse> => {
117120
let {
118121
res,
119122
runtimeNodes = [],
120123
runtimeEdges = [],
124+
defaultSkipNodeQueue,
121125
histories = [],
122126
variables = {},
123127
externalProvider,
@@ -135,9 +139,10 @@ export const runWorkflow = async (data: RunWorkflowProps): Promise<DispatchFlowR
135139
flowResponses: [],
136140
flowUsages: [],
137141
debugResponse: {
138-
finishedNodes: [],
139-
finishedEdges: [],
140-
nextStepRunNodes: []
142+
memoryEdges: [],
143+
entryNodeIds: [],
144+
nodeResponses: {},
145+
skipNodeQueue: []
141146
},
142147
[DispatchNodeResponseKeyEnum.runTimes]: 1,
143148
[DispatchNodeResponseKeyEnum.assistantResponses]: [],
@@ -151,6 +156,8 @@ export const runWorkflow = async (data: RunWorkflowProps): Promise<DispatchFlowR
151156

152157
await rewriteRuntimeWorkFlow({ nodes: runtimeNodes, edges: runtimeEdges, lang: data.lang });
153158

159+
const isDebugMode = data.mode === 'debug';
160+
154161
/*
155162
工作流队列控制
156163
特点:
@@ -176,7 +183,6 @@ export const runWorkflow = async (data: RunWorkflowProps): Promise<DispatchFlowR
176183
chatAssistantResponse: AIChatItemValueItemType[] = []; // The value will be returned to the user
177184
chatNodeUsages: ChatNodeUsageType[] = [];
178185
toolRunResponse: ToolRunResponseItemType; // Run with tool mode. Result will response to tool node.
179-
debugNextStepRunNodes: RuntimeNodeItemType[] = []; // 记录 Debug 模式下,下一个阶段需要执行的节点。
180186
// 记录交互节点,交互节点需要在工作流完全结束后再进行计算
181187
nodeInteractiveResponse:
182188
| {
@@ -186,22 +192,38 @@ export const runWorkflow = async (data: RunWorkflowProps): Promise<DispatchFlowR
186192
| undefined;
187193
system_memories: Record<string, any> = {}; // Workflow node memories
188194

195+
// Debug
196+
debugNextStepRunNodes: RuntimeNodeItemType[] = []; // 记录 Debug 模式下,下一个阶段需要执行的节点。
197+
debugNodeResponses: WorkflowDebugResponse['nodeResponses'] = {};
198+
189199
// Queue variables
190200
private activeRunQueue = new Set<string>();
191-
private skipNodeQueue: { node: RuntimeNodeItemType; skippedNodeIdList: Set<string> }[] = [];
201+
private skipNodeQueue = new Map<
202+
string,
203+
{ node: RuntimeNodeItemType; skippedNodeIdList: Set<string> }
204+
>();
192205
private runningNodeCount = 0;
193206
private maxConcurrency: number;
194207
private resolve: (e: WorkflowQueue) => void;
195208

196209
constructor({
197210
maxConcurrency = 10,
211+
defaultSkipNodeQueue,
198212
resolve
199213
}: {
200214
maxConcurrency?: number;
215+
defaultSkipNodeQueue?: WorkflowDebugResponse['skipNodeQueue'];
201216
resolve: (e: WorkflowQueue) => void;
202217
}) {
203218
this.maxConcurrency = maxConcurrency;
204219
this.resolve = resolve;
220+
221+
// Init skip node queue
222+
defaultSkipNodeQueue?.forEach(({ id, skippedNodeIdList }) => {
223+
const node = this.runtimeNodesMap.get(id);
224+
if (!node) return;
225+
this.addSkipNode(node, new Set(skippedNodeIdList));
226+
});
205227
}
206228

207229
// Add active node to queue (if already in the queue, it will not be added again)
@@ -217,7 +239,18 @@ export const runWorkflow = async (data: RunWorkflowProps): Promise<DispatchFlowR
217239
private processActiveNode() {
218240
// Finish
219241
if (this.activeRunQueue.size === 0 && this.runningNodeCount === 0) {
220-
if (this.skipNodeQueue.length > 0 && !this.nodeInteractiveResponse) {
242+
if (isDebugMode) {
243+
// 没有下一个激活节点,说明debug 进入了一个“即将结束”状态。可以开始处理 skip 节点
244+
if (this.debugNextStepRunNodes.length === 0 && this.skipNodeQueue.size > 0) {
245+
this.processSkipNodes();
246+
} else {
247+
this.resolve(this);
248+
}
249+
return;
250+
}
251+
252+
// 如果没有交互响应,则开始处理 skip(交互响应的 skip 需要留给后续处理)
253+
if (this.skipNodeQueue.size > 0 && !this.nodeInteractiveResponse) {
221254
this.processSkipNodes();
222255
} else {
223256
this.resolve(this);
@@ -251,11 +284,19 @@ export const runWorkflow = async (data: RunWorkflowProps): Promise<DispatchFlowR
251284
}
252285

253286
private addSkipNode(node: RuntimeNodeItemType, skippedNodeIdList: Set<string>) {
254-
this.skipNodeQueue.push({ node, skippedNodeIdList });
287+
// 保证一个node 只在queue里记录一次
288+
const skipNodeSkippedNodeIdList =
289+
this.skipNodeQueue.get(node.nodeId)?.skippedNodeIdList || new Set<string>();
290+
291+
const concatSkippedNodeIdList = new Set([...skippedNodeIdList, ...skipNodeSkippedNodeIdList]);
292+
293+
this.skipNodeQueue.set(node.nodeId, { node, skippedNodeIdList: concatSkippedNodeIdList });
255294
}
256295
private processSkipNodes() {
257-
const skipItem = this.skipNodeQueue.shift();
296+
// 取一个 node,并且从队列里删除
297+
const skipItem = this.skipNodeQueue.values().next().value;
258298
if (skipItem) {
299+
this.skipNodeQueue.delete(skipItem.node.nodeId);
259300
this.checkNodeCanRun(skipItem.node, skipItem.skippedNodeIdList).finally(() => {
260301
this.processActiveNode();
261302
});
@@ -351,7 +392,7 @@ export const runWorkflow = async (data: RunWorkflowProps): Promise<DispatchFlowR
351392
runtimeNodes,
352393
runtimeEdges,
353394
params,
354-
mode: data.mode === 'debug' ? 'test' : data.mode
395+
mode: isDebugMode ? 'test' : data.mode
355396
};
356397

357398
// run module
@@ -620,18 +661,6 @@ export const runWorkflow = async (data: RunWorkflowProps): Promise<DispatchFlowR
620661
const nextStepActiveNodes = Array.from(nextStepActiveNodesMap.values());
621662
const nextStepSkipNodes = Array.from(nextStepSkipNodesMap.values());
622663

623-
if (data.mode === 'debug') {
624-
this.debugNextStepRunNodes = this.debugNextStepRunNodes.concat(
625-
data.lastInteractive
626-
? nextStepActiveNodes
627-
: [...nextStepActiveNodes, ...nextStepSkipNodes]
628-
);
629-
return {
630-
nextStepActiveNodes: [],
631-
nextStepSkipNodes: []
632-
};
633-
}
634-
635664
return {
636665
nextStepActiveNodes,
637666
nextStepSkipNodes
@@ -690,8 +719,31 @@ export const runWorkflow = async (data: RunWorkflowProps): Promise<DispatchFlowR
690719
return this.nodeRunWithSkip(node);
691720
}
692721
})();
722+
693723
if (!nodeRunResult) return;
694724

725+
// Store debug data
726+
if (isDebugMode) {
727+
if (status === 'run') {
728+
this.debugNodeResponses[node.nodeId] = {
729+
nodeId: node.nodeId,
730+
type: 'run',
731+
interactiveResponse: nodeRunResult.result[DispatchNodeResponseKeyEnum.interactive],
732+
response: nodeRunResult.result[DispatchNodeResponseKeyEnum.nodeResponse]
733+
};
734+
} else if (status === 'skip') {
735+
this.debugNodeResponses[node.nodeId] = {
736+
nodeId: node.nodeId,
737+
type: 'skip',
738+
response: nodeRunResult.result[DispatchNodeResponseKeyEnum.nodeResponse]
739+
};
740+
}
741+
}
742+
// 如果一个节点 active 运行了,则需要把它从 skip queue 里删除
743+
if (status === 'run') {
744+
this.skipNodeQueue.delete(node.nodeId);
745+
}
746+
695747
/*
696748
特殊情况:
697749
通过 skipEdges 可以判断是运行了分支节点。
@@ -704,12 +756,20 @@ export const runWorkflow = async (data: RunWorkflowProps): Promise<DispatchFlowR
704756
skippedNodeIdList.add(node.nodeId);
705757
}
706758

759+
// Update the node output at the end of the run and get the next nodes
760+
const { nextStepActiveNodes, nextStepSkipNodes } = nodeOutput(
761+
nodeRunResult.node,
762+
nodeRunResult.result
763+
);
764+
765+
nextStepSkipNodes.forEach((node) => {
766+
this.addSkipNode(node, skippedNodeIdList);
767+
});
768+
707769
// In the current version, only one interactive node is allowed at the same time
708-
const interactiveResponse = nodeRunResult.result?.[DispatchNodeResponseKeyEnum.interactive];
770+
const interactiveResponse = nodeRunResult.result[DispatchNodeResponseKeyEnum.interactive];
709771
if (interactiveResponse) {
710-
pushStore(nodeRunResult.result);
711-
712-
if (data.mode === 'debug') {
772+
if (isDebugMode) {
713773
this.debugNextStepRunNodes = this.debugNextStepRunNodes.concat([nodeRunResult.node]);
714774
}
715775

@@ -718,22 +778,14 @@ export const runWorkflow = async (data: RunWorkflowProps): Promise<DispatchFlowR
718778
interactiveResponse
719779
};
720780
return;
781+
} else if (isDebugMode) {
782+
// Debug 模式下一步时候,会自己增加 activeNode
783+
this.debugNextStepRunNodes = this.debugNextStepRunNodes.concat(nextStepActiveNodes);
784+
} else {
785+
nextStepActiveNodes.forEach((node) => {
786+
this.addActiveNode(node.nodeId);
787+
});
721788
}
722-
723-
// Update the node output at the end of the run and get the next nodes
724-
const { nextStepActiveNodes, nextStepSkipNodes } = nodeOutput(
725-
nodeRunResult.node,
726-
nodeRunResult.result
727-
);
728-
729-
nextStepSkipNodes.forEach((node) => {
730-
this.addSkipNode(node, skippedNodeIdList);
731-
});
732-
733-
// Run next nodes
734-
nextStepActiveNodes.forEach((node) => {
735-
this.addActiveNode(node.nodeId);
736-
});
737789
}
738790

739791
/* Have interactive result, computed edges and node outputs */
@@ -760,6 +812,10 @@ export const runWorkflow = async (data: RunWorkflowProps): Promise<DispatchFlowR
760812

761813
const interactiveResult: WorkflowInteractiveResponseType = {
762814
...interactiveResponse,
815+
skipNodeQueue: Array.from(this.skipNodeQueue.values()).map((item) => ({
816+
id: item.node.nodeId,
817+
skippedNodeIdList: Array.from(item.skippedNodeIdList)
818+
})),
763819
entryNodeIds,
764820
memoryEdges: runtimeEdges.map((edge) => ({
765821
...edge,
@@ -781,6 +837,22 @@ export const runWorkflow = async (data: RunWorkflowProps): Promise<DispatchFlowR
781837
interactive: interactiveResult
782838
};
783839
}
840+
getDebugResponse(): WorkflowDebugResponse {
841+
const entryNodeIds = this.debugNextStepRunNodes.map((item) => item.nodeId);
842+
843+
return {
844+
memoryEdges: runtimeEdges.map((edge) => ({
845+
...edge,
846+
status: entryNodeIds.includes(edge.target) ? 'active' : edge.status
847+
})),
848+
entryNodeIds,
849+
nodeResponses: this.debugNodeResponses,
850+
skipNodeQueue: Array.from(this.skipNodeQueue.values()).map((item) => ({
851+
id: item.node.nodeId,
852+
skippedNodeIdList: Array.from(item.skippedNodeIdList)
853+
}))
854+
};
855+
}
784856
}
785857

786858
// Start process width initInput
@@ -799,7 +871,8 @@ export const runWorkflow = async (data: RunWorkflowProps): Promise<DispatchFlowR
799871

800872
const workflowQueue = await new Promise<WorkflowQueue>((resolve) => {
801873
const workflowQueue = new WorkflowQueue({
802-
resolve
874+
resolve,
875+
defaultSkipNodeQueue
803876
});
804877

805878
entryNodes.forEach((node) => {
@@ -833,11 +906,7 @@ export const runWorkflow = async (data: RunWorkflowProps): Promise<DispatchFlowR
833906
return {
834907
flowResponses: workflowQueue.chatResponses,
835908
flowUsages: workflowQueue.chatNodeUsages,
836-
debugResponse: {
837-
finishedNodes: runtimeNodes,
838-
finishedEdges: runtimeEdges,
839-
nextStepRunNodes: workflowQueue.debugNextStepRunNodes
840-
},
909+
debugResponse: workflowQueue.getDebugResponse(),
841910
workflowInteractiveResponse: interactiveResult,
842911
[DispatchNodeResponseKeyEnum.runTimes]: workflowQueue.workflowRunTimes,
843912
[DispatchNodeResponseKeyEnum.assistantResponses]: mergeAssistantResponseAnswerText(

packages/service/core/workflow/dispatch/type.d.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,24 @@ import type { WorkflowInteractiveResponseType } from '@fastgpt/global/core/workf
1313
import type { RuntimeEdgeItemType } from '@fastgpt/global/core/workflow/type/edge';
1414
import type { ChatNodeUsageType } from '@fastgpt/global/support/wallet/bill/type';
1515

16+
export type WorkflowDebugResponse = {
17+
memoryEdges: RuntimeEdgeItemType[];
18+
entryNodeIds: string[]; // Next step entry nodes
19+
nodeResponses: Record<
20+
string,
21+
{
22+
nodeId: string;
23+
type: 'skip' | 'run';
24+
response?: ChatHistoryItemResType;
25+
interactiveResponse?: InteractiveNodeResponseType;
26+
}
27+
>;
28+
skipNodeQueue?: { id: string; skippedNodeIdList: string[] }[]; // Cache
29+
};
1630
export type DispatchFlowResponse = {
1731
flowResponses: ChatHistoryItemResType[];
1832
flowUsages: ChatNodeUsageType[];
19-
debugResponse: {
20-
finishedNodes: RuntimeNodeItemType[];
21-
finishedEdges: RuntimeEdgeItemType[];
22-
nextStepRunNodes: RuntimeNodeItemType[];
23-
};
33+
debugResponse: WorkflowDebugResponse;
2434
workflowInteractiveResponse?: WorkflowInteractiveResponseType;
2535
[DispatchNodeResponseKeyEnum.toolResponses]: ToolRunResponseItemType;
2636
[DispatchNodeResponseKeyEnum.assistantResponses]: AIChatItemValueItemType[];

0 commit comments

Comments
 (0)