前端工作流如何在后端执行
这页不再泛泛讲“前后端对接”,而是专门把一条 workflow 从编辑态到执行态的完整管线拆开。
如果你想知道:
document.toJSON()之后发生了什么- 哪些画布元素不会进入后端
report和trace是怎样回到前端的
就应该读这一页。
1. 先看完整执行管线
当前参考实现的主链路是:
Flowgram.ai editor
-> document.toJSON()
-> normalizeWorkflowForBackend(...)
-> serializeWorkflowForBackend(...)
-> POST /tasks/validate
-> POST /tasks/run
-> FlowGramTaskController
-> FlowGramRuntimeFacade
-> FlowGramRuntimeService
-> node execution
-> report/result/trace projection
-> front-end runtime snapshot
真正的关键不在某一个接口,而在“编辑态对象什么时候被压成执行态对象”。
2. Stage 1: 编辑态工作流先从画布导出 JSON
前端 runtime 在提交任务前,会先从 WorkflowDocument 导出当前工作流 JSON。
这个阶段拿到的对象仍然带着编辑器视角的结构。
这意味着它可能包含:
- 纯 UI 节点
- 只对编辑器有意义的 block 边界
- 前端内部 type 命名
如果此时直接发给后端,后端 contract 会被前端内部结构污染。
3. Stage 2: normalizeWorkflowForBackend(...) 做执行态归一化
真正的桥接点在:
ai4j-flowgram-webapp-demo/src/utils/backend-workflow.ts
3.1 它先过滤 UI-only 节点
当前明确会被去掉的有:
CommentGroupBlockStartBlockEnd
这是一个很重要的设计决策:画布上的结构,不等于执行结构。
3.2 它再做类型归一化
例如:
llm -> LLMtool -> TOOLknowledge -> KNOWLEDGE
这一步把“前端节点注册表里的 type”转换成“后端 runtime 识别的 type”。
3.3 它还会做局部数据修正
例如 loop 节点会补齐输入 schema 和 inputsValues.loopFor。这说明前端传给后端的不只是删字段,还会做协议补齐。
3.4 它会清理无效边
只有 source / target 节点都存在且有效时,边才会进入后端 schema。
这让后端不必承担画布态脏数据清洗的全部成本。
4. Stage 3: runtime plugin 组织调用顺序
前端并不是“点击运行后直接 POST /run”,而是由 WorkflowRuntimeService 组织一条标准链路。
4.1 先做本地表单校验
这一步会遍历所有节点表单,确保最基础的编辑器约束先过。
4.2 再调 /tasks/validate
这一步把错误升级到后端 schema 视角,例如:
- 节点类型后端不支持
- 必填绑定缺失
- 根图没有
End
4.3 通过后再调 /tasks/run
此时后端返回的是 taskId,不是最终输出。
4.4 再按固定间隔轮询 report
当前前端常量:
SYNC_TASK_REPORT_INTERVAL = 500
这意味着系统默认是“轮询观察模型”,不是“服务端主动持续推送模型”。
4.5 任务结束后再取 result
最终结果不会靠推断 report 得到,而是通过 result 正式获取。
5. Stage 4: 后端 controller / facade / runtime 分层进入执行
5.1 Controller 阶段
FlowGramTaskController 只负责接住 REST 请求,把它交给 FlowGramRuntimeFacade。
5.2 Facade 阶段
FlowGramRuntimeFacade 负责平台化语义:
- caller 解析
- access check
- ownership 创建
- task store 更新
- trace / node details 组装
5.3 Runtime 阶段
FlowGramRuntimeService 才负责:
- schema 校验
TaskRecord创建- graph dispatch
- node status / workflow status 更新
- report / result 聚合
因此“前端工作流如何在后端执行”这句话,真实含义是:它先进入平台控制面,再进入执行引擎。
6. Stage 5: 节点输入在 executor 之前已经被 runtime 解析
这是自定义节点最容易误解的一点。
executeCustomNode(...) 在调用 executor 之前,会先:
- 读取节点
inputsValues - 用 runtime 内部逻辑解析
REF/CONSTANT/TEMPLATE/EXPRESSION - 应用输入 schema 默认值
然后才把结果放进:
FlowGramNodeExecutionContext.inputs
所以自定义 executor 拿到的 context.inputs,通常已经是“执行态输入”,不是原始前端配置对象。
7. Stage 6: 后端把执行态再投影成前端读侧
后端执行完以后,不会把内部对象原样扔回前端。
7.1 FlowGramProtocolAdapter 负责基础响应协议
它会把 runtime 输出变成:
FlowGramTaskReportResponseFlowGramTaskResultResponse
7.2 trace 再做一次前端投影
如果开启了 traceEnabled,facade 还会把 runtime event 聚合成 FlowGramTraceView,附加到 report / result 上。
这层数据更适合前端直接渲染:
- 顶部状态
- 节点高亮
- 时间线
- token / cost 指标
7.3 前端 runtime 再把它折叠成 snapshot
最终前端运行态不是直接拿 HTTP response 就结束,而是会进一步组装成:
validationreporttraceresulterrorsstatus
组成一个持续演进的 WorkflowRuntimeSnapshot。
8. 这条管线里最常见的断点
8.1 断在归一化阶段
表现:
- 前端画布看起来正常
- 后端收到的节点类型不对
通常原因:
- 忘了加
BACKEND_TYPE_MAP - 节点被误当成 UI-only 类型
8.2 断在 validate 阶段
表现:
/tasks/validate返回结构化错误
通常原因:
- 多个
Start - 没有
End - 必填输入没绑
- 节点 type 未注册
8.3 断在 run 后 report 轮询阶段
表现:
- 有
taskId - 但前端一直看不到结束状态
通常原因:
- executor 卡住
- 外部 HTTP / model 调用超时
- 轮询逻辑没正确处理 terminated 状态
8.4 断在读侧映射阶段
表现:
- 后端其实跑了
- 前端节点却没有高亮或没显示结果
通常原因:
- 前端没正确消费
report.nodes - 只看
result,没看report/trace - 输出字段和前端 UI 假定路径不一致
9. 这个执行管线为什么有价值
它让系统天然分成三段:
- 编辑器问题
- 执行问题
- 读侧展示问题
这比“所有东西都塞进一次请求里”更容易调试,也更适合平台化。
如果你继续做复杂节点或前端面板,下一步建议看: