目录
- 前言
- 一、条件分支的核心概念
-
- [1.1 三大核心组件](#1.1 三大核心组件)
-
- [① 子代理列表(Sub Agents)](#① 子代理列表(Sub Agents))
- [② 路由条件(Routing Condition)](#② 路由条件(Routing Condition))
- [③ Fallback 策略](#③ Fallback 策略)
- 二、实战案例:简历评审智能决策
-
- [2.1 业务场景](#2.1 业务场景)
- [2.2 定义 Agent 接口](#2.2 定义 Agent 接口)
-
- [① 邮件助手(EmailAssistant)](#① 邮件助手(EmailAssistant))
- [② 面试组织者(InterviewOrganizer)](#② 面试组织者(InterviewOrganizer))
- [③ 信息请求器(InfoRequester)](#③ 信息请求器(InfoRequester))
- [2.3 工具类与 RAG 支持](#2.3 工具类与 RAG 支持)
-
- [① 组织工具(OrganizingTools)](#① 组织工具(OrganizingTools))
- [② RAG 提供者(RagProvider)](#② RAG 提供者(RagProvider))
- [2.4 构建条件分支工作流(基础版)](#2.4 构建条件分支工作流(基础版))
- [2.5 运行效果](#2.5 运行效果)
-
- [场景 1:评分 >= 0.8(安排面试)](#场景 1:评分 >= 0.8(安排面试))
- [场景 2:评分 < 0.8(发送拒绝邮件)](#场景 2:评分 < 0.8(发送拒绝邮件))
- 三、高级用法:异步条件执行
-
- [3.1 问题:如何实现多条件并行检查?](#3.1 问题:如何实现多条件并行检查?)
- [3.2 解决方案:异步代理](#3.2 解决方案:异步代理)
- 四、路由策略详解
-
- [4.1 策略 1:硬编码条件(Hard-coded Conditions)](#4.1 策略 1:硬编码条件(Hard-coded Conditions))
- [4.2 策略 2:分类模型(Classification Model)](#4.2 策略 2:分类模型(Classification Model))
- [4.3 策略 3:LLM 自主决策(LLM-based Routing)](#4.3 策略 3:LLM 自主决策(LLM-based Routing))
- [4.4 三种策略对比](#4.4 三种策略对比)
- [五、Fallback 策略](#五、Fallback 策略)
-
- [5.1 问题:当所有条件都不满足怎么办?](#5.1 问题:当所有条件都不满足怎么办?)
- [5.2 解决方案:添加默认路由](#5.2 解决方案:添加默认路由)
-
- [方案 1:总是匹配的条件](#方案 1:总是匹配的条件)
- [方案 2:最后一个作为默认](#方案 2:最后一个作为默认)
- [方案 3:友好的错误提示](#方案 3:友好的错误提示)
- 六、常见问题与避坑指南
-
- [6.1 问题 1:条件顺序导致某些分支永远无法执行](#6.1 问题 1:条件顺序导致某些分支永远无法执行)
- [6.2 问题 2:多个条件同时满足,只执行第一个](#6.2 问题 2:多个条件同时满足,只执行第一个)
- [6.3 问题 3:AgenticScope 中读取到 null 值](#6.3 问题 3:AgenticScope 中读取到 null 值)
- [6.4 问题 4:条件判断逻辑复杂导致难以维护](#6.4 问题 4:条件判断逻辑复杂导致难以维护)
- 七、适用场景与最佳实践
-
- [7.1 适用场景](#7.1 适用场景)
- [7.2 不适用场景](#7.2 不适用场景)
- [7.3 最佳实践](#7.3 最佳实践)
-
- [① 设计互斥的条件](#① 设计互斥的条件)
- [② 始终提供 Fallback](#② 始终提供 Fallback)
- [③ 记录路由决策](#③ 记录路由决策)
- [④ 定期评估路由规则](#④ 定期评估路由规则)
- 八、与其他工作流的组合
-
- [8.1 组合 1:条件分支 + 并行工作流](#8.1 组合 1:条件分支 + 并行工作流)
- [8.2 组合 2:条件分支 + 循环工作流](#8.2 组合 2:条件分支 + 循环工作流)
- [8.3 组合 3:条件分支 + 顺序工作流](#8.3 组合 3:条件分支 + 顺序工作流)
- 结语
前言
在前面的文章中,我们学习了顺序工作流 (固定顺序执行)、循环工作流 (迭代执行)和并行工作流 (并发执行)。但在实际业务场景中,往往需要根据条件选择不同的执行路径:
场景 1:智能客服
用户问题:
- "怎么退款?" ─→ 路由到「售后 Agent」
- "这个产品怎么样?" ─→ 路由到「销售 Agent」
- "系统报错了" ─→ 路由到「技术支持 Agent」
场景 2:简历评审决策
评审结果:
- 评分 >= 0.8 ─→ 安排现场面试
- 评分 < 0.8 ─→ 发送拒绝邮件
- 缺失关键信息 ─→ 请求补充材料
场景 3:内容审核
内容类型:
- 文本 ─→ 文本审核 Agent
- 图片 ─→ 图片审核 Agent
- 视频 ─→ 视频审核 Agent
这就是条件分支工作流的价值所在!
什么是条件分支工作流?
条件分支工作流(Conditional Workflow)是一种基于条件的路由模式 ,它根据运行时状态动态选择要执行的 Agent。
bash
┌─────────────┐
│ 输入参数 │
└──────┬──────┘
│
▼
┌─────────────┐
│ 条件判断 │
└──┬──────┬───┘
│ │
YES NO
│ │
▼ ▼
┌──────┐ ┌──────┐
│AgentA│ │AgentB│
└──────┘ └──────┘
核心优势
✅ 智能决策 :根据输入内容自动选择最合适的 Agent
✅ 专业化分工 :每个 Agent 专注于特定领域,提高回答质量
✅ 灵活扩展 :新增路由规则无需修改现有代码
✅ 降级容错:无法匹配时使用 Fallback 策略
一、条件分支的核心概念
1.1 三大核心组件
① 子代理列表(Sub Agents)
条件分支由多个 Agent 组成,但每次只执行其中一个:
java
EmailAssistant emailAssistant = AgenticServices.agentBuilder(EmailAssistant.class)
.chatModel(openAiChatModel)
.tools(new OrganizingTools())
.build();
InterviewOrganizer interviewOrganizer = AgenticServices.agentBuilder(InterviewOrganizer.class)
.chatModel(openAiChatModel)
.tools(new OrganizingTools())
.contentRetriever(RagProvider.loadHouseRulesRetriever())
.build();
// 根据条件选择执行其中一个
执行流程:
bash
条件判断:cvReview.score >= 0.8
- TRUE → 执行 InterviewOrganizer(安排面试)
- FALSE → 执行 EmailAssistant(发送拒绝邮件)
② 路由条件(Routing Condition)
路由条件是一个布尔表达式,决定执行哪个 Agent:
java
.subAgents(agenticScope -> {
CvReview review = (CvReview) agenticScope.readState("cvReview");
return review.score >= 0.8; // 返回 true 或 false
}, interviewOrganizer)
.subAgents(agenticScope -> {
CvReview review = (CvReview) agenticScope.readState("cvReview");
return review.score < 0.8;
}, emailAssistant)
常见路由策略:
- ✅ 硬编码条件:基于数值阈值、关键词匹配
- ✅ 分类模型:使用机器学习模型分类
- ✅ LLM 自主决策:让 LLM 判断应该调用哪个 Agent
③ Fallback 策略
当所有条件都不满足时,使用默认路由:
java
// 方式 1:添加默认条件
.subAgents(agenticScope -> true, fallbackAgent) // 总是匹配,作为兜底
// 方式 2:不指定条件(最后一个作为默认)
.subAgents(fallbackAgent)
Fallback 的作用:
- 🛡️ 防止无法路由导致任务失败
- 🛡️ 提供友好的错误提示
- 🛡️ 转人工处理特殊情况
二、实战案例:简历评审智能决策
2.1 业务场景
我们要构建一个智能招聘决策系统:
bash
输入:
- 候选人简历(tailored_cv.txt)
- 候选人联系方式(candidate_contact.txt)
- 职位描述(job_description_backend.txt)
- HR 评审结果(评分 + 反馈)
处理流程:
1. 检查评审评分
- 如果评分 >= 0.8:调用 InterviewOrganizer 安排现场面试
- 如果评分 < 0.8:调用 EmailAssistant 发送拒绝邮件
- 如果反馈包含"缺失信息":调用 InfoRequester 请求补充材料
2. 被选中的 Agent 执行相应操作
- 发送邮件
- 创建日历邀请
- 更新申请状态
输出:
- 操作结果(邮件 ID、日历邀请、状态更新)
2.2 定义 Agent 接口
① 邮件助手(EmailAssistant)
java
package com.langchain4j.agentic._05_conditional_workflow;
import dev.langchain4j.agentic.Agent;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V;
/**
* 邮件助手接口 - 向未通过评审的候选人发送拒绝邮件
*/
public interface EmailAssistant {
@Agent("向未通过的候选人发送拒绝邮件,返回已发送邮件 ID,如果无法发送则返回 0")
@SystemMessage("""
你向未通过首轮评审的候选申请人发送一封友好的邮件。
你还将申请状态更新为"已拒绝"。
你返回已发送邮件的 ID。
""")
@UserMessage("""
被拒绝的候选人:{{candidateContact}}
申请职位:{{jobDescription}}
""")
int send(@V("candidateContact") String candidateContact,
@V("jobDescription") String jobDescription);
}
关键点:
- 角色:HR 助手,负责发送拒绝邮件
- 工具:使用
OrganizingTools发送邮件、更新状态 - 返回值:邮件 ID(用于追踪)
② 面试组织者(InterviewOrganizer)
java
package com.langchain4j.agentic._05_conditional_workflow;
import dev.langchain4j.agentic.Agent;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V;
/**
* 面试组织者接口 - 组织候选人的现场面试
*/
public interface InterviewOrganizer {
@Agent("组织申请人的现场面试")
@SystemMessage("""
你通过向所有相关员工发送日历邀请来组织现场会议,
安排在当前日期一周后的上午进行 3 小时面试。
这是相关的职位空缺:{{jobDescription}}
你还向候选人发送一封祝贺邮件,包含面试详细信息
以及他在来现场之前应该了解的任何事项。
最后,你将申请状态更新为"已邀请现场面试"。
""")
@UserMessage("""
为此候选人组织一场现场面试(适用外部访客政策):{{candidateContact}}
""")
String organize(@V("candidateContact") String candidateContact,
@V("jobDescription") String jobDescription);
}
关键点:
- 角色:面试协调员,负责安排面试
- 工具:使用
OrganizingTools创建日历、发送邮件 - RAG:加载办公室规则(house_rules.txt),告知候选人注意事项
③ 信息请求器(InfoRequester)
java
package com.langchain4j.agentic._05_conditional_workflow;
import com.langchain4j.domain.CvReview;
import dev.langchain4j.agentic.Agent;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V;
/**
* 信息请求器接口 - 向候选人发送邮件以获取额外信息
*/
public interface InfoRequester {
@Agent("向候选人发送邮件以获取额外信息")
@SystemMessage("""
你向候选人发送一封友好的邮件,请求公司评审申请所需的额外信息。
要明确说明他们的申请仍在考虑中。
""")
@UserMessage("""
HR 评审及缺失信息描述:{{cvReview}}
候选人联系信息:{{candidateContact}}
职位描述:{{jobDescription}}
""")
String send(@V("candidateContact") String candidateContact,
@V("jobDescription") String jobDescription,
@V("cvReview") CvReview hrReview);
}
关键点:
- 角色:信息收集员,负责请求补充材料
- 触发条件:评审反馈中包含"缺失信息"
- 语气:友好,强调申请仍在考虑中
2.3 工具类与 RAG 支持
① 组织工具(OrganizingTools)
java
package com.langchain4j.agentic._05_conditional_workflow;
import dev.langchain4j.agent.tool.P;
import dev.langchain4j.agent.tool.Tool;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* 组织工具类 - 提供邮件发送、日历安排等组织功能的工具方法
*/
public class OrganizingTools {
@Tool
public Date getCurrentDate(){
return new Date();
}
@Tool("查找给定职位描述 ID 需要参加现场面试的人员的邮箱地址和姓名")
public List<String> getInvolvedEmployeesForInterview(@P("职位描述 ID") String jobDescriptionId){
System.out.println("*** 职位描述 ID:" + jobDescriptionId);
// 演示用的虚拟实现
return new ArrayList<>(List.of(
"Anna Bolena: hiring.manager@company.com",
"Chris Durue: near.colleague@company.com",
"Esther Finnigan: vp@company.com"));
}
@Tool("根据邮箱地址为员工创建日历条目")
public void createCalendarEntry(
@P("员工邮箱地址列表") List<String> emailAddress,
@P("会议主题") String topic,
@P("开始日期和时间,格式为 yyyy-mm-dd hh:mm") String start,
@P("结束日期和时间,格式为 yyyy-mm-dd hh:mm") String end){
System.out.println("*** 已创建日历条目 ***");
System.out.println("主题:" + topic);
System.out.println("开始时间:" + start);
System.out.println("结束时间:" + end);
}
@Tool
public int sendEmail(
@P("收件人邮箱地址列表") List<String> to,
@P("抄送人邮箱地址列表") List<String> cc,
@P("邮件主题") String subject,
@P("邮件正文") String body){
System.out.println("*** 已发送邮件 ***");
System.out.println("收件人:" + to);
System.out.println("抄送人:" + cc);
System.out.println("主题:" + subject);
System.out.println("正文:" + body);
return 1234; // 虚拟邮件 ID
}
@Tool
public void updateApplicationStatus(
@P("职位描述 ID") String jobDescriptionId,
@P("候选人姓名(名,姓)") String candidateName,
@P("新的申请状态") String newStatus){
System.out.println("*** 申请状态已更新 ***");
System.out.println("职位描述 ID:" + jobDescriptionId);
System.out.println("候选人姓名:" + candidateName);
System.out.println("新状态:" + newStatus);
}
}
工具说明:
getCurrentDate():获取当前日期,用于计算面试时间getInvolvedEmployeesForInterview():查找需要参加面试的员工createCalendarEntry():创建日历邀请sendEmail():发送邮件updateApplicationStatus():更新申请状态
② RAG 提供者(RagProvider)
java
package com.langchain4j.agentic._05_conditional_workflow;
import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.document.splitter.DocumentSplitters;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.rag.content.retriever.ContentRetriever;
import dev.langchain4j.rag.content.retriever.EmbeddingStoreContentRetriever;
import dev.langchain4j.store.embedding.EmbeddingStoreIngestor;
import dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore;
import dev.langchain4j.model.embedding.onnx.bgesmallenv15q.BgeSmallEnV15QuantizedEmbeddingModel;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import static dev.langchain4j.data.document.loader.FileSystemDocumentLoader.loadDocument;
public class RagProvider {
public static ContentRetriever loadHouseRulesRetriever() {
Document doc = loadDocument(toPath("documents/house_rules.txt"));
EmbeddingModel embeddingModel = new BgeSmallEnV15QuantizedEmbeddingModel();
InMemoryEmbeddingStore<TextSegment> store = new InMemoryEmbeddingStore<>();
EmbeddingStoreIngestor ingestor = EmbeddingStoreIngestor.builder()
.documentSplitter(DocumentSplitters.recursive(200, 10))
.embeddingModel(embeddingModel)
.embeddingStore(store)
.build();
ingestor.ingest(List.of(doc));
return EmbeddingStoreContentRetriever.builder()
.embeddingStore(store)
.embeddingModel(embeddingModel)
.maxResults(2)
.minScore(0.8)
.build();
}
public static Path toPath(String relativePath) {
try {
URL fileUrl = Utils.class.getClassLoader().getResource(relativePath);
return Paths.get(fileUrl.toURI());
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}
}
作用:
- 加载办公室规则文档(house_rules.txt)
- 创建 RAG 检索器,供
InterviewOrganizer使用 - 面试组织者可以检索办公室规则,告知候选人注意事项
2.4 构建条件分支工作流(基础版)
完整代码
java
package com.langchain4j;
import com.langchain4j.agentic._05_conditional_workflow.*;
import com.langchain4j.domain.CvReview;
import com.langchain4j.util.StringLoader;
import dev.langchain4j.agentic.AgenticServices;
import dev.langchain4j.agentic.UntypedAgent;
import dev.langchain4j.model.openai.OpenAiChatModel;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.Map;
@SpringBootTest
public class _05_ConditionalWorkflowTest {
@Autowired
private OpenAiChatModel openAiChatModel;
@Test
public void testConditionalWorkflow() throws Exception {
// 1. 创建子代理
EmailAssistant emailAssistant = AgenticServices.agentBuilder(EmailAssistant.class)
.chatModel(openAiChatModel)
.tools(new OrganizingTools()) // 使用组织工具
.build();
InterviewOrganizer interviewOrganizer = AgenticServices.agentBuilder(InterviewOrganizer.class)
.chatModel(openAiChatModel)
.tools(new OrganizingTools())
.contentRetriever(RagProvider.loadHouseRulesRetriever()) // 添加 RAG 支持
.build();
// 2. 构建条件工作流
UntypedAgent candidateResponder = AgenticServices
.conditionalBuilder()
.subAgents(agenticScope -> {
CvReview review = (CvReview) agenticScope.readState("cvReview");
return review.score >= 0.8; // 评分 >= 0.8 安排面试
}, interviewOrganizer)
.subAgents(agenticScope -> {
CvReview review = (CvReview) agenticScope.readState("cvReview");
return review.score < 0.8; // 评分 < 0.8 发送拒绝邮件
}, emailAssistant)
.build();
// 3. 加载输入数据
String candidateCv = StringLoader.loadFromResource("/documents/tailored_cv.txt");
String candidateContact = StringLoader.loadFromResource("/documents/candidate_contact.txt");
String jobDescription = StringLoader.loadFromResource("/documents/job_description_backend.txt");
// 测试通过的情况
CvReview cvReviewPass = new CvReview(0.9, "简历优秀,符合后端职位的所有要求。");
// 4. 准备参数
Map<String, Object> arguments = Map.of(
"candidateCv", candidateCv,
"candidateContact", candidateContact,
"jobDescription", jobDescription,
"cvReview", cvReviewPass // 改为 cvReviewFail 以查看另一个分支
);
// 5. 调用条件工作流
candidateResponder.invoke(arguments);
// 6. 观察控制台输出
// 工具会打印执行的操作(发送邮件、更新状态等)
}
}
代码解析
关键步骤拆解:
① 创建子代理
java
EmailAssistant emailAssistant = AgenticServices.agentBuilder(EmailAssistant.class)
.chatModel(openAiChatModel)
.tools(new OrganizingTools()) // 代理可以使用那里定义的所有工具
.build();
InterviewOrganizer interviewOrganizer = AgenticServices.agentBuilder(InterviewOrganizer.class)
.chatModel(openAiChatModel)
.tools(new OrganizingTools())
.contentRetriever(RagProvider.loadHouseRulesRetriever()) // 这是我们为代理添加 RAG 的方式
.build();
作用:
- 两个 Agent 都使用
OrganizingTools工具集 InterviewOrganizer额外添加了 RAG 支持,可以检索办公室规则
② 构建条件工作流
java
UntypedAgent candidateResponder = AgenticServices
.conditionalBuilder()
.subAgents(agenticScope -> {
CvReview review = (CvReview) agenticScope.readState("cvReview");
return review.score >= 0.8; // 条件 1:评分 >= 0.8
}, interviewOrganizer) // 执行面试组织者
.subAgents(agenticScope -> {
CvReview review = (CvReview) agenticScope.readState("cvReview");
return review.score < 0.8; // 条件 2:评分 < 0.8
}, emailAssistant) // 执行邮件助手
.build();
重要说明:
- 条件按顺序依次检查:先检查条件 1,如果不满足再检查条件 2
- 第一个匹配的条件会被执行:如果多个条件都满足,只有第一个会执行
- 条件是硬编码的:直接在代码中定义路由逻辑
③ 调用工作流
java
Map<String, Object> arguments = Map.of(
"candidateCv", candidateCv,
"candidateContact", candidateContact,
"jobDescription", jobDescription,
"cvReview", cvReviewPass // 评分 0.9,会触发面试安排
);
candidateResponder.invoke(arguments);
注意:
cvReview是路由决策的关键参数- 修改为
cvReviewFail(评分 0.6)会触发拒绝邮件
2.5 运行效果
场景 1:评分 >= 0.8(安排面试)
bash
*** 已创建日历条目 ***
主题:后端工程师现场面试 - 张伟
开始时间:2026-06-11 09:00
结束时间:2026-06-11 12:00
*** 已发送邮件 ***
收件人:[zhangwei.dev@gmail.com]
抄送人:[]
主题:恭喜!您已获得现场面试资格
正文:尊敬的张伟先生,恭喜您通过首轮评审...
*** 申请状态已更新 ***
职位描述 ID:backend_engineer_2026
候选人姓名:张伟
新状态:已邀请现场面试
场景 2:评分 < 0.8(发送拒绝邮件)
bash
*** 已发送邮件 ***
收件人:[zhangwei.dev@gmail.com]
抄送人:[]
主题:感谢您申请后端工程师职位
正文:尊敬的张伟先生,非常感谢您申请我们的职位...
*** 申请状态已更新 ***
职位描述 ID:backend_engineer_2026
候选人姓名:张伟
新状态:已拒绝
三、高级用法:异步条件执行
3.1 问题:如何实现多条件并行检查?
在基础版中,条件是按顺序检查 的。但如果我们有三个或更多条件,可能需要同时检查以提高效率。
3.2 解决方案:异步代理
java
@Test
public void testConditionalWorkflowAsync() throws Exception {
// 1. 创建异步代理
ManagerCvReviewer managerCvReviewer = AgenticServices.agentBuilder(ManagerCvReviewer.class)
.chatModel(openAiChatModel)
.async(true) // ⭐ 设置为异步代理
.outputKey("managerReview")
.build();
EmailAssistant emailAssistant = AgenticServices.agentBuilder(EmailAssistant.class)
.chatModel(openAiChatModel)
.async(true) // ⭐ 设置为异步代理
.tools(new OrganizingTools())
.outputKey("sentEmailId")
.build();
InfoRequester infoRequester = AgenticServices.agentBuilder(InfoRequester.class)
.chatModel(openAiChatModel)
.async(true) // ⭐ 设置为异步代理
.tools(new OrganizingTools())
.outputKey("sentEmailId")
.build();
// 2. 构建异步条件工作流
UntypedAgent candidateResponder = AgenticServices
.conditionalBuilder()
.subAgents(scope -> {
CvReview hrReview = (CvReview) scope.readState("cvReview");
return hrReview.score >= 0.8; // 如果 HR 通过,发送给经理评审
}, managerCvReviewer)
.subAgents(scope -> {
CvReview hrReview = (CvReview) scope.readState("cvReview");
return hrReview.score < 0.8; // 如果 HR 未通过,发送拒绝邮件
}, emailAssistant)
.subAgents(scope -> {
CvReview hrReview = (CvReview) scope.readState("cvReview");
return hrReview.feedback.toLowerCase().contains("缺失信息:"); // 如果需要更多信息
}, infoRequester)
.output(agenticScope ->
(agenticScope.readState("managerReview", new CvReview(0, "无需经理评审"))).toString() +
"\n" + agenticScope.readState("sentEmailId", 0)
)
.build();
// 3. 准备参数
String candidateCv = StringLoader.loadFromResource("/documents/tailored_cv.txt");
String candidateContact = StringLoader.loadFromResource("/documents/candidate_contact.txt");
String jobDescription = StringLoader.loadFromResource("/documents/job_description_backend.txt");
CvReview hrReview = new CvReview(
0.85,
"""
候选人扎实,薪资期望在范围内,能够在期望的时间框架内开始工作。
缺失信息:比利时的工授权状态详情。
"""
);
Map<String, Object> arguments = Map.of(
"candidateCv", candidateCv,
"candidateContact", candidateContact,
"jobDescription", jobDescription,
"cvReview", hrReview
);
// 4. 运行异步条件工作流
candidateResponder.invoke(arguments);
System.out.println("=== 异步条件工作流执行完成 ===");
}
关键改进:
- ✅
.async(true):将 Agent 设置为异步执行 - ✅ 三个条件可以同时检查(虽然最终只执行一个 Agent)
- ✅
.output():自定义输出处理器,提取结果
四、路由策略详解
4.1 策略 1:硬编码条件(Hard-coded Conditions)
直接在代码中定义路由逻辑:
java
.subAgents(agenticScope -> {
CvReview review = (CvReview) agenticScope.readState("cvReview");
return review.score >= 0.8;
}, interviewOrganizer)
适用场景:
- ✅ 路由规则明确且稳定
- ✅ 基于数值阈值或简单逻辑
- ✅ 性能要求高(无需额外调用 LLM)
优点:
- 简单直接
- 执行速度快
- 易于调试
缺点:
- 灵活性差
- 修改规则需要重新部署
- 无法处理复杂语义
4.2 策略 2:分类模型(Classification Model)
使用机器学习模型进行分类:
java
.subAgents(agenticScope -> {
String userInput = (String) agenticScope.readState("userInput");
// 使用分类模型判断意图
String intent = classifier.predict(userInput);
return "refund".equals(intent); // 退款意图
}, refundAgent)
.subAgents(agenticScope -> {
String userInput = (String) agenticScope.readState("userInput");
String intent = classifier.predict(userInput);
return "technical_support".equals(intent); // 技术支持意图
}, techSupportAgent)
适用场景:
- ✅ 文本分类任务
- ✅ 意图识别
- ✅ 已有训练好的分类模型
优点:
- 准确性高
- 可处理复杂语义
- 支持多分类
缺点:
- 需要训练数据
- 维护成本高
- 新增类别需要重新训练
4.3 策略 3:LLM 自主决策(LLM-based Routing)
让 LLM 判断应该调用哪个 Agent:
java
// 方式 1:使用 Supervisor Agent
SupervisorAgent supervisor = AgenticServices.supervisorBuilder()
.chatModel(openAiChatModel)
.subAgents(refundAgent, salesAgent, techSupportAgent)
.build();
// 方式 2:使用 AiServices + Tools
public interface RouterService {
@Tool("处理退款相关问题")
String handleRefund(@P("用户问题") String question);
@Tool("处理产品销售相关问题")
String handleSales(@P("用户问题") String question);
@Tool("处理技术支持相关问题")
String handleTechSupport(@P("用户问题") String question);
}
RouterService router = AiServices.builder(RouterService.class)
.chatModel(openAiChatModel)
.tools(refundExpert, salesExpert, techSupportExpert)
.build();
适用场景:
- ✅ 路由规则复杂且多变
- ✅ 需要理解自然语言
- ✅ 新增 Agent 频繁
优点:
- 灵活性极高
- 无需硬编码规则
- 支持自然语言路由
缺点:
- 额外消耗 Token
- 增加延迟
- 可能路由错误
4.4 三种策略对比
| 特性 | 硬编码条件 | 分类模型 | LLM 自主决策 |
|---|---|---|---|
| 灵活性 | 低 | 中 | 高 |
| 准确性 | 高(规则明确时) | 高(有训练数据时) | 中(依赖 LLM) |
| 执行速度 | 快 | 快 | 慢 |
| 维护成本 | 低 | 高 | 中 |
| 适用场景 | 简单规则 | 文本分类 | 复杂语义 |
| Token 消耗 | 无 | 无 | 有 |
五、Fallback 策略
5.1 问题:当所有条件都不满足怎么办?
java
// ❌ 危险示例:没有 Fallback
.subAgents(scope -> review.score >= 0.8, interviewOrganizer)
.subAgents(scope -> review.score < 0.8, emailAssistant)
// 如果 review 为 null 或 score 为 NaN,两个条件都不满足
// 工作流会静默失败,没有任何提示
5.2 解决方案:添加默认路由
方案 1:总是匹配的条件
java
.subAgents(scope -> review.score >= 0.8, interviewOrganizer)
.subAgents(scope -> review.score < 0.8, emailAssistant)
.subAgents(scope -> true, fallbackAgent) // ⭐ 兜底路由,总是匹配
方案 2:最后一个作为默认
java
.subAgents(scope -> review.score >= 0.8, interviewOrganizer)
.subAgents(emailAssistant) // ⭐ 不指定条件,作为默认路由
方案 3:友好的错误提示
java
.subAgents(scope -> review.score >= 0.8, interviewOrganizer)
.subAgents(scope -> review.score < 0.8, emailAssistant)
.subAgents(scope -> true, agenticScope -> {
System.err.println("⚠️ 无法路由:评审结果异常");
return "抱歉,系统暂时无法处理您的申请,请稍后重试或联系人工客服。";
})
六、常见问题与避坑指南
6.1 问题 1:条件顺序导致某些分支永远无法执行
症状:
java
.subAgents(scope -> true, agentA) // 总是匹配
.subAgents(scope -> review.score >= 0.8, agentB) // 永远不会执行 ❌
原因:
- 条件按顺序检查
- 第一个匹配的条件会被执行
true总是匹配,后续条件不会被检查
解决方案:
java
// ✅ 正确顺序:具体条件在前,通用条件在后
.subAgents(scope -> review.score >= 0.8, agentB)
.subAgents(scope -> review.score < 0.8, agentC)
.subAgents(scope -> true, fallbackAgent) // 兜底放在最后
6.2 问题 2:多个条件同时满足,只执行第一个
症状:
java
// 评分 0.9,且反馈包含"缺失信息"
.subAgents(scope -> review.score >= 0.8, interviewOrganizer) // ✅ 执行这个
.subAgents(scope -> review.feedback.contains("缺失信息"), infoRequester) // ❌ 不执行
说明:
- 这是正常行为,条件分支只执行第一个匹配的 Agent
- 如果需要执行多个 Agent,应该使用并行工作流
解决方案:
java
// ✅ 方案 1:调整条件优先级,确保互斥
.subAgents(scope -> review.score >= 0.8 && !review.feedback.contains("缺失信息"), interviewOrganizer)
.subAgents(scope -> review.feedback.contains("缺失信息"), infoRequester)
.subAgents(scope -> review.score < 0.8, emailAssistant)
// ✅ 方案 2:使用并行工作流同时执行
.parallelBuilder()
.subAgents(interviewOrganizer, infoRequester)
.build();
6.3 问题 3:AgenticScope 中读取到 null 值
症状:
bash
NullPointerException at line: review.score
原因:
- 参数未正确传入
- outputKey 配置错误
解决方案:
java
.subAgents(agenticScope -> {
CvReview review = (CvReview) agenticScope.readState("cvReview");
// ✅ 添加 null 检查
if (review == null) {
System.err.println("⚠️ 评审结果为空,使用默认路由");
return false; // 路由到 Fallback
}
return review.score >= 0.8;
}, interviewOrganizer)
6.4 问题 4:条件判断逻辑复杂导致难以维护
症状:
java
.subAgents(scope -> {
CvReview review = (CvReview) scope.readState("cvReview");
String jobType = (String) scope.readState("jobType");
Integer yearsOfExperience = (Integer) scope.readState("yearsOfExperience");
// 复杂的判断逻辑
return (review.score >= 0.8 && "senior".equals(jobType) && yearsOfExperience >= 5) ||
(review.score >= 0.9 && "junior".equals(jobType));
}, interviewOrganizer)
解决方案:
java
// ✅ 方案 1:提取路由逻辑到独立方法
private boolean shouldScheduleInterview(AgenticScope scope) {
CvReview review = (CvReview) scope.readState("cvReview");
String jobType = (String) scope.readState("jobType");
Integer yearsOfExperience = (Integer) scope.readState("yearsOfExperience");
if (review == null) return false;
if ("senior".equals(jobType)) {
return review.score >= 0.8 && yearsOfExperience >= 5;
} else if ("junior".equals(jobType)) {
return review.score >= 0.9;
}
return false;
}
.subAgents(this::shouldScheduleInterview, interviewOrganizer)
// ✅ 方案 2:使用 LLM 自主决策(见 4.3 节)
七、适用场景与最佳实践
7.1 适用场景
| 场景 | 说明 | 示例 |
|---|---|---|
| 智能客服 | 根据问题类型路由到不同专家 | 退款、销售、技术支持 |
| 内容审核 | 根据内容类型选择审核策略 | 文本、图片、视频 |
| 招聘决策 | 根据评分决定下一步操作 | 安排面试、拒绝、请求补充 |
| 多语言支持 | 根据语言选择翻译 Agent | 中文、英文、日文 |
| 权限控制 | 根据用户角色路由到不同流程 | 管理员、普通用户、访客 |
7.2 不适用场景
❌ 所有条件都需要执行 :应使用顺序工作流
❌ 条件之间有依赖关系 :应使用顺序工作流
❌ 需要同时执行多个 Agent :应使用并行工作流
❌ 路由规则极其简单:直接使用 if-else 即可
7.3 最佳实践
① 设计互斥的条件
原则:
- 📌 每个条件应该是互斥的,避免多个条件同时满足
- 📌 如果无法完全互斥,明确优先级
java
// ✅ 互斥条件
.subAgents(scope -> review.score >= 0.8, agentA)
.subAgents(scope -> review.score < 0.8 && review.score >= 0.5, agentB)
.subAgents(scope -> review.score < 0.5, agentC)
// ❌ 重叠条件
.subAgents(scope -> review.score >= 0.7, agentA)
.subAgents(scope -> review.score >= 0.5, agentB) // 0.7 以上会同时匹配两个
② 始终提供 Fallback
原则:
- 📌 永远不要假设所有情况都被覆盖
- 📌 Fallback 应该提供友好的错误提示
java
.subAgents(scope -> review.score >= 0.8, interviewOrganizer)
.subAgents(scope -> review.score < 0.8, emailAssistant)
.subAgents(scope -> true, agenticScope -> {
// Fallback:记录日志并返回友好提示
System.err.println("⚠️ 未知路由情况:" + agenticScope.readState("cvReview"));
return "抱歉,系统暂时无法处理您的申请,请联系人工客服。";
})
③ 记录路由决策
价值:
- 📊 分析路由分布
- 🐛 调试路由错误
- 📈 优化路由规则
java
.subAgents(agenticScope -> {
CvReview review = (CvReview) agenticScope.readState("cvReview");
boolean shouldSchedule = review.score >= 0.8;
// 记录路由决策
System.out.println("路由决策:评分=" + review.score + ", 安排面试=" + shouldSchedule);
return shouldSchedule;
}, interviewOrganizer)
④ 定期评估路由规则
方法:
- 📊 统计各路由的执行次数
- 🔍 分析 Fallback 触发的原因
- 📈 评估路由准确性(用户满意度)
java
// 在 Fallback 中记录原因
.subAgents(scope -> true, agenticScope -> {
System.err.println("⚠️ Fallback 触发原因:");
System.err.println(" - 评审结果:" + agenticScope.readState("cvReview"));
System.err.println(" - 其他参数:" + agenticScope.readState("otherParam"));
return "已转人工处理";
})
八、与其他工作流的组合
8.1 组合 1:条件分支 + 并行工作流
java
// 伪代码示例
// 根据条件触发不同的并行工作流
ParallelAgent technicalReview = AgenticServices.parallelBuilder()
.subAgents(hrReviewer, managerReviewer, teamMemberReviewer)
.build();
ParallelAgent managementReview = AgenticServices.parallelBuilder()
.subAgents(directorReviewer, ceoReviewer)
.build();
.conditionalBuilder()
.subAgents(scope -> isTechnicalRole(scope), technicalReview)
.subAgents(scope -> isManagementRole(scope), managementReview)
.build();
8.2 组合 2:条件分支 + 循环工作流
java
// 伪代码示例
// 在循环内部使用条件分支
LoopAgent optimizationLoop = AgenticServices.loopBuilder()
.subAgents(
conditionBranch, // 根据质量决定是否需要优化
optimizer // 执行优化
)
.exitCondition(scope -> qualityMet(scope))
.build();
8.3 组合 3:条件分支 + 顺序工作流
java
// 伪代码示例
// 条件分支的每个分支都是顺序工作流
SequenceAgent technicalProcess = AgenticServices.sequenceBuilder()
.subAgents(initialScreening, technicalInterview, backgroundCheck)
.build();
SequenceAgent managementProcess = AgenticServices.sequenceBuilder()
.subAgents(initialScreening, managementInterview, executiveApproval)
.build();
.conditionalBuilder()
.subAgents(scope -> isTechnicalRole(scope), technicalProcess)
.subAgents(scope -> isManagementRole(scope), managementProcess)
.build();
结语
本文深入讲解了条件分支工作流的三大核心组件------子代理列表、路由条件与 Fallback 策略,通过简历评审后智能决策(安排面试/拒绝/请求补充)的完整实战,演示了从硬编码条件到 LLM 自主决策的三种路由策略演进。条件分支的精髓在于让系统根据运行时状态做出"选择题" :条件按优先级依次匹配,首个命中即执行,Fallback 兜底防止静默失败。至此,你已经掌握了顺序、循环、并行、条件分支四种基础编排模式,下一篇我们将学习主管编排与组合工作流,看 Supervisor Agent 如何自主调度子 Agent 并将多种模式嵌套组合成复杂系统。敬请期待!

🎯 更多专栏系列文章:LangChain4j Java AI应用开发实战、🔥 其他专栏可以查看博客主页
🔔 关于作者 :资深程序老猿,10年+架构经验,现专注 AIGC 探索与实践。
👍 若文章对你有所触动,恳请点赞 ⭐ 关注 ⭐ 收藏!AI 浪潮已至,愿与你同行。