---
title: "结构化输出与条件路由"
---



LLM 可以判断语义，但关键业务路径不应直接匹配一段自由文本。推荐管线是：

```text
分类 Agent
  -> 结构化解析和校验 Activity
  -> Exclusive
       ├─ 专项 Agent
       └─ 人工兜底
```

### 1、定义稳定的路由结果

例如分类 Agent 约定输出：

```json
{
  "problemType": "TECHNICAL",
  "confidence": 0.92
}
```

业务允许的枚举：

```java
public enum ProblemType {
    TECHNICAL,
    BILLING,
    COMPLAINT,
    MANUAL_REVIEW
}
```

不要直接用“看起来像技术问题”“大概是账单”之类文本作为 Graph 条件。

### 2、解析并校验

```java
spec.addActivity("normalize_route")
        .task((ctx, node) -> {
            String raw = ctx.getAs("classifier.result");
            RouteDecision decision = parseRouteDecision(raw);

            ProblemType type = decision.getProblemType();
            if (decision.getConfidence() < 0.80D) {
                type = ProblemType.MANUAL_REVIEW;
            }

            ctx.put("route.problemType", type.name());
            ctx.put("route.confidence", decision.getConfidence());
        })
        .linkAdd("problem_route");
```

解析节点应处理：

- JSON 无法解析；
- 枚举未知；
- 置信度缺失或越界；
- Agent 空输出；
- 必填字段缺失。

异常数据统一写成 `MANUAL_REVIEW`，而不是让 Graph 停在未知状态。

### 3、Exclusive 单选路由

```java
spec.addExclusive("problem_route")
        .linkAdd(technical.name(), link -> link
                .priority(30)
                .when(ctx -> "TECHNICAL".equals(
                        ctx.getAs("route.problemType"))))
        .linkAdd(billing.name(), link -> link
                .priority(20)
                .when(ctx -> "BILLING".equals(
                        ctx.getAs("route.problemType"))))
        .linkAdd(complaint.name(), link -> link
                .priority(10)
                .when(ctx -> "COMPLAINT".equals(
                        ctx.getAs("route.problemType"))))
        .linkAdd("manual_review");
```

Exclusive 按优先级检查条件，只执行第一个命中的分支；没有条件命中时执行无条件连接。

每个 Exclusive 应只配置一个无条件默认连接。当前执行逻辑不会主动拒绝多个默认连接，多配属于应避免的错误配置。

### 4、分支 Agent 读取同一任务

```java
Agent technical = SimpleAgent.of(chatModel)
        .name("technical_support")
        .role("技术支持")
        .instruction("处理用户原始问题，并给出技术排查建议。")
        .outputKey("result.technical")
        .build();
```

TeamAgent Graph 中，成员仍可收到团队原始 Prompt。若分支依赖解析节点生成的新数据，应在 instruction 中显式引用 Context，或改用适配 Activity。

### 5、统一收敛最终结果

```java
spec.addActivity("finish")
        .task((ctx, node) -> {
            String type = ctx.getAs("route.problemType");
            String result;

            if ("TECHNICAL".equals(type)) {
                result = ctx.getAs("result.technical");
            } else if ("BILLING".equals(type)) {
                result = ctx.getAs("result.billing");
            } else if ("COMPLAINT".equals(type)) {
                result = ctx.getAs("result.complaint");
            } else {
                result = ctx.getAs("result.manual");
            }

            ctx.put("output.final", result);
            TeamTrace trace = TeamTrace.getCurrent(ctx);
            if (trace != null) {
                trace.setFinalAnswer(result);
            }
        })
        .linkAdd(Agent.ID_END);
```

显式设置 `finalAnswer`，可以避免最终结果意外依赖“最后执行的 Agent”。

### 6、验证路由，而不是比较整段文本

```java
Assertions.assertEquals("TECHNICAL",
        session.getContext().get("route.problemType"));
Assertions.assertNotNull(
        session.getContext().get("result.technical"));
Assertions.assertNull(
        session.getContext().get("result.billing"));
```

还应使用节点拦截器验证命中的分支和未执行的分支。`TeamTrace` 只能验证 Agent 记录，不能替代完整 Graph 节点历史。

### 7、设计原则

- Agent 产出结构化判断；
- Activity 负责解析、校验和归一化；
- Graph 负责选择路径；
- 低置信度进入人工审核；
- 未知枚举和空输出必须有兜底；
- 高风险决策不能只依赖 Prompt 约束。