---
title: "Inclusive 动态专家选择与反馈循环"
---



Parallel 固定执行全部分支；实际系统常常需要“按任务动态选择多个专家”，或者“生成—审核—修改”循环。这两类拓扑分别适合 Inclusive 和带上限的反馈回路。

### 1、Inclusive 动态多选

```text
classify
  -> Inclusive split
       ├─ legal_agent ──┐
       ├─ risk_agent  ──┼─> Inclusive join
       └─ cost_agent  ──┘
  -> aggregate
```

先由分类节点写入稳定标签：

```java
ctx.put("route.needLegal", true);
ctx.put("route.needRisk", true);
ctx.put("route.needCost", false);
```

再选择多个分支：

```java
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 明确计算：

```java
boolean any = needLegal || needRisk || needCost;
ctx.put("route.needManual", !any);
```

然后为人工分支配置条件：

```java
.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、构建有上限的反馈循环

典型内容生产：

```text
draft_agent
  -> review_agent
  -> route
       ├─ PASS -> finish
       ├─ REVISE 且未达上限 -> draft_agent
       └─ 达到上限 -> manual_review
```

每轮审核后增加计数：

```java
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");
```

路由：

```java
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");
```

修订前显式构造下一轮输入：

```java
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；
- 总耗时；
- 工具调用次数；
- 连续相同审核意见；
- 达到上限后的人工路径。

每轮结果最好保留版本：

```text
draft.round.1
review.round.1
draft.round.2
review.round.2
```

若只覆盖 `draft.result`，将失去问题诊断和审计信息。

### 6、恢复与测试

循环和 Inclusive 的恢复状态比线性 Graph 更复杂。测试至少覆盖：

- Inclusive 无分支、单分支和多分支命中；
- 无条件边不会被误当成兜底；
- PASS 首轮结束；
- REVISE 后进入下一轮；
- 达到上限进入人工审核；
- 恢复后轮次不重复增加；
- 外部副作用不会因循环或恢复重复执行。