编辑器:子流程与循环容器

适用版本:ChengOS v0.1.0+ | 最后核对:2026-08-12 | 来源:crates/cheng-nodes/src/nodes/builtin/tools/sub_flow.rscrates/cheng-nodes/src/nodes/builtin/tools/batch_sub_flow.rscrates/cheng-core/src/domain/loop_container.rscrates/cheng-core/src/domain/container.rs

在图里跑图有两种不同的方式,它们解决的是不同的问题:

子流程节点 循环容器
跑什么 另一个工作流,按 id 或路径 画在本工作流内部的子图
复用 可跨工作流共享 仅限本工作流
节点 tools/subflowtools/batch_subflow 画在画布上的容器
何时用 被调方本身是一个独立单元 只是要对每个 item 重复本地的几步

被调方有自己的身份时用子流程;只是需要对每个 item 重跑同样那几个节点时用循环。

子流程节点

tools/subflow 通过与 API 相同的 Executor 端口去运行另一个工作流——它不重新实现执行逻辑——并返回结果。

输入 含义
workflow_path 工作流 UUID,或 JSON 工作流文件的路径
input_data 交给子工作流的数据
async_execution true 表示子流程一启动就返回;false(默认)等待完成
timeout_seconds 默认 300,范围 1–3600
max_parallelism 子工作流内部的节点并行度,1–100
response_path 点路径(如 result.answer),用于从子工作流输出中提取 response
context_path 点路径,用于提取子上下文
return_child_context 返回子工作流更新后的上下文(默认开)
return_node_outputs result.outputs 中包含子节点输出(默认开)
child_context_max_bytes 内联上限,默认 100000;超限的上下文改为在诊断信息里给出摘要

输出为 statusagent_contextelapsed_mssummaryerror。在画布上折叠时,节点只显示 statussummary

response_pathcontext_path 值得设置。不设时,节点会用启发式方法在子工作流的各节点输出里找「答案」;设了之后,提取就是精确的。

上下文优先级

有三个输入都能提供子工作流的 context,优先级最高的生效:

1. child_context      ← 上一次子流程调用返回的权威后续状态
2. agent_context      ← 上游路由(通常从条件路由节点连过来)
3. input_data.context ← 手动透传

正是这个次序让多轮委派能够成立:父流程把子流程返回的上下文在下一次调用时传回去,而这份状态压过路由层提供的任何东西。

conversation_id 由父执行作用域环境注入,因此子工作流与父流程共享同一个会话,用于回合生命周期、审批以及依赖历史的工具。

递归防护

每次调用都携带一个调用栈。两道防线,都是硬失败而非警告:

  • 深度上限 10。 超出即中止,并在错误信息里打印调用栈。
  • 成环检测。workflow_path 已在栈中,调用被拒绝——「Recursive workflow call detected」——并打印调用栈。

推荐的默认嵌套深度是 5;10 是绝对上限。

批量子流程

tools/batch_subflow 并发地运行多次子流程调用。每个 item 都可以指定不同的工作流和不同的输入——除非你自己让它们相同,否则这并不是「同一个工作流跑 N 次」。

限制 取值
item 上限 256
max_concurrency(批层) 1–64,默认 4
max_parallelism(每个子工作流内部) 1–100
timeout_seconds(每个 item) 1–3600

max_concurrencymax_parallelism 是两个不同的旋钮。 前者是同时在飞的子工作流数量,后者是每个子工作流内部并行运行的节点数。二者相乘才是你真实的并发量,容量评估时该看的就是这个乘积。这些限制在运行时也会强校验,而不只是写在 schema 里,因此非法取值会在节点处失败,而不是深入执行之后才炸。

失败语义是 collect-all。 所有 item 都跑到底,然后再汇总;没有 fail-fast 选项。fail-fast 需要可取消的执行句柄,留到后续版本。容器的 status 报告为 completedpartial_failedfailed,另附 total / succeeded / failed 计数和逐项结果。

结果按输入顺序返回,而不是完成顺序,因此上游数据与下游结果无需额外的对齐步骤就能一一对应。

有两条实现特性会体现在行为上:批量节点内部持有一个常驻的子流程节点,因此它的工作流缓存在多次批量运行之间共享,而不是每次重新加载;以及各次迭代是在当前异步调用栈上轮询的,而不是 spawn 出去,因为工具上下文和执行上下文是借用引用。

循环容器

循环容器是一个工作流结构,不是节点。它包住一个子图,每次迭代运行一遍,并把结果汇总成容器级输出。

在外层 DAG 里,容器作为一个整体被调度,由一个合成 NodeId 引用,该 id 从容器 id 确定性地推导而来(固定命名空间的 UUID v5)。这份确定性很重要:持久化的工作流和执行轨迹都依赖同一个容器在跨进程、跨部署时始终映射到同一个节点 id。

虚拟端口

loop/input 边界向内部节点暴露一组固定的虚拟端口。它们不是真实节点,也从不进入 DAG——引擎在每次迭代时投影出它们:

端口 含义
items 源数组(边界输入)
item 当前项
item_text 当前项的文本投影
item_id 当前项的稳定 id(存在时)
item_path 当前项的路径,用于文件引用
index 从 0 开始的下标
iteration 从 1 开始的迭代序号(= index + 1)
previous while 循环用:上一轮循环体的合并输出,首轮为 Null
condition 条件来源节点必须产出的布尔输出

模式

模式 迭代方式
forEach(默认) 遍历绑定到边界输入的数组
count 恰好 N 次,N 从边界输入读取
while 条件为真则继续,在每次迭代之前检查
until 直到条件为真才停,在每次迭代之后检查

while 的条件来源节点必须是内部图的成员并输出布尔 condition 端口。until 的条件来源节点必须位于循环体下游——而且触发停止的那一轮会被计入汇总。

并发与错误

设置 取值
并发 sequential(默认),或 parallelmaxParallelism2–32
错误策略 failFast(默认)、continuecollectErrors

maxParallelism: 1 会被拒绝而不是接受——它等价于串行,所以校验器要求你显式写 sequential

并行容器若内部含有需审批的节点,必须设 allowApprovals: true;否则校验器会直接拒绝该配置,而不是在运行到一半时给你惊喜。而且不论完成顺序如何,汇总总是按迭代下标顺序定稿——并行循环产出的输出数组与串行完全一致。

嵌套有硬上限:最大深度 3,且整棵嵌套循环树在飞的迭代总数不超过 64,无论各层声明了多大的并行度。

collectErrorscontinue 的并行版本:每次迭代都跑到自己的终态,只要有任何一次失败容器就失败,但要等就绪队列排空之后才判定,且汇总只包含成功的迭代。

目前只有 forEach + sequential + 数组汇总是完整实现的。其他组合作为向前兼容的槽位存在,执行器会带着明确的错误拒绝不支持的组合,而不是悄悄降级——因此一个配置只要被接受,它就是真的被支持。

可观测性

容器会发出 CONTAINER_STARTED / CONTAINER_COMPLETEDLOOP_ITERATION_* 事件,于是 100 项的批处理是逐次迭代可观测的,而不是一个不透明的节点。见轨迹、日志与回放

下一步

© 版权声明
THE END
喜欢就支持一下吧
点赞8 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容