Conversation
feat:add batch processing note
|
✅ Docs Preview Deployed! 🔗 👀 Click here to visit preview |
|
✅ Build Successful - Preview sandbox Image for this PR: |
|
✅ Build Successful - Preview fastgpt Image for this PR: |
|
✅ Build Successful - Preview mcp_server Image for this PR: |
c121914yu
left a comment
There was a problem hiding this comment.
📊 PR 代码审查总结
PR #6626: feat: add batch processing node
作者: @gaga0714
变更: +585 / -23 行,23 个文件
✅ 优点
- 并发架构设计合理: 使用 worker-pool 模式(cursor 递增 + Promise.all)实现并发控制,在 JS 单线程模型下无竞争问题
- 结果顺序保证: 通过
orderedRawResult[index]和orderedSuccessResult.sort()保证输出顺序与输入一致,符合用户预期 - 三态状态输出:
success / failed / partial_success的设计让下游节点可以做精细化分支处理 - 向后兼容: 旧 loop 节点标记
abandon: true而非删除,存量工作流不受影响 - 运行时环境变量限制:
WORKFLOW_BATCH_MAX_CONCURRENCY和WORKFLOW_BATCH_MAX_RETRY防止用户输入绕过服务端限制
⚠️ 问题汇总(详见下方行级评论)
🟡 建议改进
assertBatchChildNodes应抛出 Error 对象而非 reject 字符串batchInput在响应弹窗中复用了loop_input的 i18n key,显示文字不准确variableUpdate被拖入批处理节点时提示信息不准确NodeLoopEnd的 intro 回退逻辑有潜在问题- 两个
useEffect调用onChangeNode需注意无限循环风险
🧪 测试建议
- 验证并发数为 1 时的串行执行行为
- 验证重试逻辑:第 1 次失败、第 2 次成功的场景
- 验证
partial_success状态下,下游节点能正确接收batchRawResult - 验证拖入禁止节点类型时的提示信息正确性(尤其是
variableUpdate) - 验证旧 loop 节点的存量工作流不受影响
💬 总体评价
- 代码质量: ⭐⭐⭐⭐☆ (4/5)
- 安全性: ⭐⭐⭐⭐⭐ (5/5)
- 性能: ⭐⭐⭐⭐⭐ (5/5)
- 可维护性: ⭐⭐⭐⭐☆ (4/5)
🚀 审查结论
整体实现质量较高,并发设计合理,建议修复以下几个小问题后合并。
📍 详细问题说明
[1] packages/service/core/workflow/dispatch/batch/runBatch.ts L309
🟡 建议改进: assertBatchChildNodes 使用 return Promise.reject(string) 而非 Error 对象。
字符串 reject 不携带堆栈信息,调试困难。建议改为:
throw new Error('Batch child workflow does not allow loop/batch/interactive/variable-update nodes');[2] projects/app/src/components/core/chat/components/WholeResponseModal.tsx L641
🟡 建议改进: batchInput 复用了 loop_input 的 i18n key,响应弹窗中会显示「循环输入」而非「批量输入」。建议新增 batch_input i18n key。
[3] projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/hooks/useWorkflow.tsx L791
🟡 建议改进: variableUpdate 节点被拖入批处理节点时,触发的是 can_not_loop 提示而非专属批处理提示。isInteractiveInBatch 只包含 userSelect 和 formInput,不包含 variableUpdate,导致提示语不准确。
[4] projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/Loop/NodeLoopEnd.tsx L959
🟡 建议改进: intro 回退值直接使用了原始 i18n key 字符串 'workflow:loop_end_intro',需确认 NodeCard 是否会自动处理翻译,否则 UI 会直接显示 key 字符串。
[5] projects/app/src/pageComponents/app/detail/WorkflowComponents/Flow/nodes/Loop/NodeLoop.tsx L838-L935
🟢 注意潜在风险: 两个 useEffect 都调用 onChangeNode 并依赖 inputs,若 onChangeNode 触发后导致 inputs 引用变化,可能引发无限渲染。已有条件检查(input.value === nextValue)可部分防止,但建议增加单元测试验证稳定性。
There was a problem hiding this comment.
不要复用原来的 loop 节点,新写一个,看有没有能提取的通用内容
| avatarLinear: 'core/workflow/template/loopLinear', | ||
| colorSchema: 'violetDeep', | ||
| name: i18nT('workflow:loop'), | ||
| name: i18nT('workflow:loop_deprecated'), |
| renderTypeList: [FlowNodeInputTypeEnum.reference], | ||
| valueType: WorkflowIOValueTypeEnum.any, | ||
| label: '', | ||
| label: i18nT('workflow:loop_end_intro'), |
| runtimeNodes | ||
| }); | ||
|
|
||
| const maxLength = process.env.WORKFLOW_MAX_LOOP_TIMES |
There was a problem hiding this comment.
直接用解析好的 env.xxx,里面也能设置默认值。
| } | ||
| }; | ||
|
|
||
| const workers = Array.from({ length: Math.min(concurrency, loopInputArray.length) }).map( |
There was a problem hiding this comment.
有一个 batchRun 的批量执行方法可以直接用
| return Promise.reject(`Input array length cannot be greater than ${maxLength}`); | ||
| } | ||
|
|
||
| if (loopInputArray.length === 0) { |
There was a problem hiding this comment.
不用考虑,底下数组是空的话,应该就要不允许,可以适配。
| const currentNode = getNodeById(handleParams?.nodeId); | ||
| if (templateNode.flowNodeType === FlowNodeTypeEnum.loop && !!currentNode?.parentNodeId) { | ||
| if ( | ||
| [FlowNodeTypeEnum.loop, FlowNodeTypeEnum.batch].includes(templateNode.flowNodeType) && |
There was a problem hiding this comment.
封装一个方法,传入 type,识别是否是这种 parentChild 的节点类型
| return basicNodeTemplates | ||
| .filter((item) => { | ||
| // hide deprecated templates from add panel | ||
| if (item.abandon) { |
There was a problem hiding this comment.
basicNodeTemplates 里去掉 loop 就好了,这里不用 filter
|
|
||
| if (parentNode) { | ||
| if (unSupportedTypes.includes(node.type as FlowNodeTypeEnum)) { | ||
| const parentType = parentNode.type as FlowNodeTypeEnum; |
| const node = getNodeById(nodeId); | ||
| if (!node) return 'bottom'; | ||
| return node.flowNodeType === FlowNodeTypeEnum.loop ? 'top' : 'bottom'; | ||
| return [FlowNodeTypeEnum.loop, FlowNodeTypeEnum.batch].includes(node.flowNodeType) |
No description provided.