Inclusive 动态专家选择与反馈循环
2026年9月18日 上午11:56:06
Parallel 固定执行全部分支;实际系统常常需要“按任务动态选择多个专家”,或者“生成—审核—修改”循环。这两类拓扑分别适合 Inclusive 和带上限的反馈回路。
1、Inclusive 动态多选
classify
-> Inclusive split
├─ legal_agent ──┐
├─ risk_agent ──┼─> Inclusive join
└─ cost_agent ──┘
-> aggregate
先由分类节点写入稳定标签:
ctx.put("route.needLegal", true);
ctx.put("route.needRisk", true);
ctx.put("route.needCost", false);
再选择多个分支:
spec.addInclusive("expert_select")
.linkAdd(legal.name(), link -> link.when(ctx ->
Boolean.TRUE.equals(ctx.getAs("route.needLegal"))))
.linkAdd(risk.name(), link -> link.when(ctx ->
Boolean.TRUE.equals(ctx.getAs("route.needRisk"))))
.linkAdd(cost.name(), link -> link.when(ctx ->
Boolean.TRUE.equals(ctx.getAs("route.needCost"))));
spec.addActivity(legal).linkAdd("expert_join");
spec.addActivity(risk).linkAdd("expert_join");
spec.addActivity(cost).linkAdd("expert_join");
spec.addInclusive("expert_join")
.linkAdd("aggregate");
Inclusive 会执行所有条件命中的分支,汇聚端只等待本轮实际选中的分支数。
2、Inclusive 没有 Exclusive 式默认边
在 Inclusive 中,无条件连接按“始终为真”处理,会和其他命中分支一起执行,而不是“都没命中才执行”。
如果需要兜底,应在前置 Activity 明确计算:
boolean any = needLegal || needRisk || needCost;
ctx.put("route.needManual", !any);
然后为人工分支配置条件:
.linkAdd("manual_review", link -> link.when(ctx ->
Boolean.TRUE.equals(ctx.getAs("route.needManual"))))
还应确保至少一个分支会命中,否则 Graph 将没有后续路径。
3、Exclusive、Inclusive、Parallel 对照
| 网关 | 出边选择 | 汇聚规则 | 多线程 |
|---|---|---|---|
| Exclusive | 第一个命中的条件;否则无条件边 | 不负责多分支汇聚 | 否 |
| Inclusive | 所有命中的条件 | 等待本轮实际选中分支 | 否;当前实现即使配置 Executor,也按命中连接依次调用 |
| Parallel | 全部出边,不检查条件 | 等待全部静态入边 | 配 Executor 后分支可并发,但 eval() 仍等待汇聚完成 |
三个网关都可以挂 task;汇聚型网关会先完成流入等待,再执行自己的 task。
4、构建有上限的反馈循环
典型内容生产:
draft_agent
-> review_agent
-> route
├─ PASS -> finish
├─ REVISE 且未达上限 -> draft_agent
└─ 达到上限 -> manual_review
每轮审核后增加计数:
spec.addActivity("review_result")
.task((ctx, node) -> {
int round = ctx.getOrDefault("loop.round", 0);
ctx.put("loop.round", round + 1);
String review = ctx.getAs("review.result");
ctx.put("route.reviewDecision", parseDecision(review));
})
.linkAdd("review_route");
路由:
spec.addExclusive("review_route")
.linkAdd("finish", link -> link.when(ctx ->
"PASS".equals(ctx.getAs("route.reviewDecision"))))
.linkAdd("prepare_revision", link -> link.when(ctx ->
"REVISE".equals(ctx.getAs("route.reviewDecision"))
&& ctx.<Integer>getAs("loop.round") < 3))
.linkAdd("manual_review");
修订前显式构造下一轮输入:
spec.addActivity("prepare_revision")
.task((ctx, node) -> {
String draft = ctx.getAs("draft.result");
String review = ctx.getAs("review.result");
ctx.put("draft.input", "原稿:\n" + draft
+ "\n\n审核意见:\n" + review);
})
.linkAdd("draft_step");
不要假设 Agent 会自动记住 Context 中上一轮结果。
5、循环必须有治理上限
至少限制:
- 最大循环次数;
- 总 Token;
- 总耗时;
- 工具调用次数;
- 连续相同审核意见;
- 达到上限后的人工路径。
每轮结果最好保留版本:
draft.round.1
review.round.1
draft.round.2
review.round.2
若只覆盖 draft.result,将失去问题诊断和审计信息。
6、恢复与测试
循环和 Inclusive 的恢复状态比线性 Graph 更复杂。测试至少覆盖:
- Inclusive 无分支、单分支和多分支命中;
- 无条件边不会被误当成兜底;
- PASS 首轮结束;
- REVISE 后进入下一轮;
- 达到上限进入人工审核;
- 恢复后轮次不重复增加;
- 外部副作用不会因循环或恢复重复执行。