结构化输出与条件路由
2026年9月18日 上午11:53:08
LLM 可以判断语义,但关键业务路径不应直接匹配一段自由文本。推荐管线是:
分类 Agent
-> 结构化解析和校验 Activity
-> Exclusive
├─ 专项 Agent
└─ 人工兜底
1、定义稳定的路由结果
例如分类 Agent 约定输出:
{
"problemType": "TECHNICAL",
"confidence": 0.92
}
业务允许的枚举:
public enum ProblemType {
TECHNICAL,
BILLING,
COMPLAINT,
MANUAL_REVIEW
}
不要直接用“看起来像技术问题”“大概是账单”之类文本作为 Graph 条件。
2、解析并校验
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 单选路由
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 读取同一任务
Agent technical = SimpleAgent.of(chatModel)
.name("technical_support")
.role("技术支持")
.instruction("处理用户原始问题,并给出技术排查建议。")
.outputKey("result.technical")
.build();
TeamAgent Graph 中,成员仍可收到团队原始 Prompt。若分支依赖解析节点生成的新数据,应在 instruction 中显式引用 Context,或改用适配 Activity。
5、统一收敛最终结果
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、验证路由,而不是比较整段文本
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 约束。