目录
前言
在前面的文章中,我们学习了顺序工作流(串行执行)和循环工作流(迭代执行)。但在实际业务场景中,往往需要同时执行多个任务以提高效率:
场景 1:多维度评审
传统方式(串行):
HR 评审(10秒) → 经理评审(10秒) → 团队成员评审(10秒) = 30秒
并行方式(并发):
HR 评审 ┐
经理评审 ├→ 同时执行 = 10秒 ⚡
团队成员评审 ┘
优势:提速 3 倍!
场景 2:多模型对比
GPT-4 生成答案 ┐
Claude 生成答案 ├→ 同时执行,然后对比质量
Gemini 生成答案 ┘
场景 3:批量处理
处理文档1 ┐
处理文档2 ├→ 同时处理 100 个文档
... ┘
这就是并行工作流的价值所在!
什么是并行工作流?
并行工作流(Parallel Workflow)是一种并发执行模式,它让多个 Agent 同时执行,然后通过聚合器(Aggregator)合并结果。
┌─────────────┐
│ 输入参数 │
└──────┬──────┘
│
├──────────┬──────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Agent A │ │ Agent B │ │ Agent C │
└────┬─────┘ └────┬─────┘ └────┬─────┘
│ │ │
└────────────┴────────────┘
│
▼
┌────────────────┐
│ Aggregator │ ← 结果聚合器
└────────┬───────┘
│
▼
┌────────────────┐
│ 最终输出 │
└────────────────┘
核心优势
✅ 提速显著:N 个 Agent 并行执行,理论提速 N 倍
✅ 多角度评估:不同角色的 Agent 提供多元化观点
✅ 容错性强:某个 Agent 失败不影响其他 Agent
✅ 灵活聚合:自定义结果合并策略
一、并行工作流的核心概念
1.1 三大核心组件
① 子代理列表(Sub Agents)
并行工作流由多个 Agent 组成,它们会同时执行:
HrCvReviewer hrCvReviewer = AgenticServices.agentBuilder(HrCvReviewer.class)
.chatModel(openAiChatModel)
.outputKey("hrReview") // HR 评审结果
.build();
ManagerCvReviewer managerCvReviewer = AgenticServices.agentBuilder(ManagerCvReviewer.class)
.chatModel(openAiChatModel)
.outputKey("managerReview") // 经理评审结果
.build();
TeamMemberCvReviewer teamMemberCvReviewer = AgenticServices.agentBuilder(TeamMemberCvReviewer.class)
.chatModel(openAiChatModel)
.outputKey("teamMemberReview") // 团队成员评审结果
.build();
// 三个 Agent 同时执行,互不干扰
执行流程:
时间轴:
0s ─→ hrCvReviewer.start()
─→ managerCvReviewer.start()
─→ teamMemberCvReviewer.start()
10s ─→ hrCvReviewer.complete() ✅
─→ managerCvReviewer.complete() ✅
─→ teamMemberCvReviewer.complete() ✅
总耗时:10秒(而非 30秒)
② 线程池(Executor)
并行执行需要多线程支持,你可以使用自定义线程池或默认线程池:
// 方式 1:自定义线程池
var executor = Executors.newFixedThreadPool(3);
.parallelBuilder()
.subAgents(hrCvReviewer, managerCvReviewer, teamMemberCvReviewer)
.executor(executor) // 使用自定义线程池
.build();
// 方式 2:使用默认线程池(推荐)
.parallelBuilder()
.subAgents(hrCvReviewer, managerCvReviewer, teamMemberCvReviewer)
// 不指定 executor,自动使用内部缓存线程池
.build();
线程池选择建议:
- 📌 CPU 密集型任务:
Executors.newFixedThreadPool(CPU核心数) - 📌 IO 密集型任务:
Executors.newCachedThreadPool() - 📌 混合任务:
Executors.newFixedThreadPool(10-20)
③ 结果聚合器(Aggregator)
聚合器负责合并多个 Agent 的执行结果,这是并行工作流的灵魂:
.output(agenticScope -> {
// 从 AgenticScope 中读取每个 Agent 的输出
CvReview hrReview = (CvReview) agenticScope.readState("hrReview");
CvReview managerReview = (CvReview) agenticScope.readState("managerReview");
CvReview teamMemberReview = (CvReview) agenticScope.readState("teamMemberReview");
// 聚合策略:拼接反馈 + 计算平均分
String feedback = String.join("\n",
"HR 评审:" + hrReview.feedback,
"经理评审:" + managerReview.feedback,
"团队成员评审:" + teamMemberReview.feedback
);
double avgScore = (hrReview.score + managerReview.score + teamMemberReview.score) / 3.0;
return new CvReview(avgScore, feedback);
})
常见聚合策略:
- ✅ 拼接:将所有结果拼接成字符串
- ✅ 投票:取多数 Agent 的意见
- ✅ 加权:给不同 Agent 分配不同权重
- ✅ 摘要:使用 LLM 总结所有结果
二、实战案例:三方并行评审简历
2.1 业务场景
我们要构建一个多维度简历评审系统:
输入:
- 候选人简历(tailored_cv.txt)
- 职位描述(job_description_backend.txt)
- HR 要求(hr_requirements.txt)
- 电话面试笔记(phone_interview_notes.txt)
处理流程:
1. HR 评审员:从 HR 角度评审简历(检查薪资、可用性、工作权限等)
2. 经理评审员:从招聘经理角度评审简历(检查技术匹配度)
3. 团队成员评审员:从团队成员角度评审简历(检查团队契合度)
4. 三个评审员同时执行,然后聚合结果
输出:
- 综合评审报告(包含三方意见和平均评分)
2.2 定义 Agent 接口
① HR 简历评审器(HrCvReviewer)
package com.langchain4j.agentic._04_parallel_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;
/**
* HR 简历评审器接口 - 从 HR 角度评审简历,检查候选人是否符合 HR 要求
*/
public interface HrCvReviewer {
@Agent(name = "hrReviewer", description = "评审简历以检查候选人是否符合 HR 要求,给出反馈和评分")
@SystemMessage("""
你在 HR 部门工作,评审简历以满足以下要求的职位:
{{hrRequirements}}
你会给每份简历一个评分和反馈(包括优点和缺点)。
你可以忽略缺少地址和占位符等内容。
重要提示:仅返回有效的 JSON 格式,换行符使用 \\n,不要有任何 Markdown 格式或代码块。
""")
@UserMessage("""
请评审这份简历:{{candidateCv}},以及 accompanying 电话面试笔记:{{phoneInterviewNotes}}
""")
CvReview reviewCv(@V("candidateCv") String cv,
@V("phoneInterviewNotes") String phoneInterviewNotes,
@V("hrRequirements") String hrRequirements);
}
关键点:
- 接收三个参数:简历、电话面试笔记、HR 要求
- 关注点:薪资期望、可用性、工作权限、稳定性等
- 强调"仅返回有效的 JSON 格式",确保结构化输出
② 经理简历评审器(ManagerCvReviewer)
package com.langchain4j.agentic._04_parallel_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 ManagerCvReviewer {
@Agent(name = "managerReviewer", description = "根据职位描述评审简历,给出反馈和评分")
@SystemMessage("""
你是以下职位的招聘经理:
{{jobDescription}}
你需要评审申请人的简历,并决定从众多申请人中邀请谁参加现场面试。
你会给每份简历一个评分和反馈(包括优点和缺点)。
你可以忽略缺少地址和占位符等内容。
重要提示:仅返回有效的 JSON 格式,换行符使用 \\n,不要有任何 Markdown 格式或代码块。
""")
@UserMessage("""
请评审这份简历:{{candidateCv}}
""")
CvReview reviewCv(@V("candidateCv") String cv, @V("jobDescription") String jobDescription);
}
关键点:
- 接收两个参数:简历、职位描述
- 关注点:技术栈匹配度、项目经验、专业能力
- 角色定位:招聘经理,决定是否邀请面试
③ 团队成员简历评审器(TeamMemberCvReviewer)
package com.langchain4j.agentic._04_parallel_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 TeamMemberCvReviewer {
@Agent(name = "teamMemberReviewer", description = "评审简历以查看候选人是否适合团队,给出反馈和评分")
@SystemMessage("""
你在一个团队中工作,团队成员积极主动,拥有很大的自由度。
你的团队重视协作、责任感和务实精神。
你需要评审申请人的简历,并决定此人与你的团队的匹配程度。
你会给每份简历一个评分和反馈(包括优点和缺点)。
你可以忽略缺少地址和占位符等内容。
重要提示:仅返回有效的 JSON 格式,换行符使用 \\n,不要有任何 Markdown 格式或代码块。
""")
@UserMessage("""
请评审这份简历:{{candidateCv}}
""")
CvReview reviewCv(@V("candidateCv") String cv);
}
关键点:
- 只接收一个参数:简历
- 关注点:团队协作能力、文化契合度、软技能
- 角色定位:未来同事,评估是否好相处
2.3 构建并行工作流
完整代码
package com.langchain4j;
import com.langchain4j.agentic._04_parallel_workflow.HrCvReviewer;
import com.langchain4j.agentic._04_parallel_workflow.ManagerCvReviewer;
import com.langchain4j.agentic._04_parallel_workflow.TeamMemberCvReviewer;
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;
import java.util.concurrent.Executors;
@SpringBootTest
public class _04_ParallelWorkflowTest {
@Autowired
private OpenAiChatModel openAiChatModel;
@Test
public void testParallelWorkflow() throws Exception {
// 1. 创建子代理
HrCvReviewer hrCvReviewer = AgenticServices.agentBuilder(HrCvReviewer.class)
.chatModel(openAiChatModel)
.outputKey("hrReview") // HR 评审结果存入 AgenticScope
.build();
ManagerCvReviewer managerCvReviewer = AgenticServices.agentBuilder(ManagerCvReviewer.class)
.chatModel(openAiChatModel)
.outputKey("managerReview") // 经理评审结果存入 AgenticScope
.build();
TeamMemberCvReviewer teamMemberCvReviewer = AgenticServices.agentBuilder(TeamMemberCvReviewer.class)
.chatModel(openAiChatModel)
.outputKey("teamMemberReview") // 团队成员评审结果存入 AgenticScope
.build();
// 2. 创建线程池(可选)
var executor = Executors.newFixedThreadPool(3);
// 3. 构建并行工作流
UntypedAgent cvReviewGenerator = AgenticServices
.parallelBuilder()
.subAgents(hrCvReviewer, managerCvReviewer, teamMemberCvReviewer)
.executor(executor) // 使用自定义线程池
.outputKey("fullCvReview") // 最终输出键名
.output(agenticScope -> {
// 从 AgenticScope 中读取每个评审器的输出
CvReview hrReview = (CvReview) agenticScope.readState("hrReview");
CvReview managerReview = (CvReview) agenticScope.readState("managerReview");
CvReview teamMemberReview = (CvReview) agenticScope.readState("teamMemberReview");
// 聚合策略:拼接反馈 + 计算平均分
String feedback = String.join("\n",
"HR 评审:" + hrReview.feedback,
"经理评审:" + managerReview.feedback,
"团队成员评审:" + teamMemberReview.feedback
);
double avgScore = (hrReview.score + managerReview.score + teamMemberReview.score) / 3.0;
return new CvReview(avgScore, feedback);
})
.build();
// 4. 加载输入数据
String candidateCv = StringLoader.loadFromResource("/documents/tailored_cv.txt");
String jobDescription = StringLoader.loadFromResource("/documents/job_description_backend.txt");
String hrRequirements = StringLoader.loadFromResource("/documents/hr_requirements.txt");
String phoneInterviewNotes = StringLoader.loadFromResource("/documents/phone_interview_notes.txt");
// 5. 准备参数
Map<String, Object> arguments = Map.of(
"candidateCv", candidateCv,
"jobDescription", jobDescription,
"hrRequirements", hrRequirements,
"phoneInterviewNotes", phoneInterviewNotes
);
// 6. 调用工作流
CvReview review = (CvReview) cvReviewGenerator.invoke(arguments);
// 7. 输出结果
System.out.println("=== 评审结果 ===");
System.out.println(review);
// 8. 关闭线程池
executor.shutdown();
}
}
代码解析
关键步骤拆解:
① 创建子代理
HrCvReviewer hrCvReviewer = AgenticServices.agentBuilder(HrCvReviewer.class)
.chatModel(openAiChatModel)
.outputKey("hrReview") // 这会在每次迭代中被覆盖,也将用作我们想要观察的最终输出
.build();
作用:
outputKey("hrReview"):将 HR 评审结果存入AgenticScope- 三个 Agent 的 outputKey 不能重复,否则会相互覆盖
② 创建线程池
var executor = Executors.newFixedThreadPool(3);
说明:
- 线程池大小为 3,对应 3 个并发执行的 Agent
- 如果不指定,会自动使用内部缓存线程池
- 记得在执行完成后调用
executor.shutdown()关闭线程池
③ 构建并行工作流
UntypedAgent cvReviewGenerator = AgenticServices
.parallelBuilder()
.subAgents(hrCvReviewer, managerCvReviewer, teamMemberCvReviewer)
.executor(executor) // 可选,默认使用内部缓存线程池
.outputKey("fullCvReview") // 这是我们想要观察的最终输出
.output(agenticScope -> {
// 从代理作用域中读取每个评审器的输出
CvReview hrReview = (CvReview) agenticScope.readState("hrReview");
CvReview managerReview = (CvReview) agenticScope.readState("managerReview");
CvReview teamMemberReview = (CvReview) agenticScope.readState("teamMemberReview");
// 返回捆绑的评审结果,包含平均评分(或你想要的任何其他聚合方式)
String feedback = String.join("\n",
"HR 评审:" + hrReview.feedback,
"经理评审:" + managerReview.feedback,
"团队成员评审:" + teamMemberReview.feedback
);
double avgScore = (hrReview.score + managerReview.score + teamMemberReview.score) / 3.0;
return new CvReview(avgScore, feedback);
})
.build();
参数说明:
subAgents():指定并行执行的 Agent 列表executor():指定线程池(可选)outputKey():指定最终输出的键名.output():自定义聚合器,合并三个评审结果
④ 调用工作流
Map<String, Object> arguments = Map.of(
"candidateCv", candidateCv,
"jobDescription", jobDescription,
"hrRequirements", hrRequirements,
"phoneInterviewNotes", phoneInterviewNotes
);
CvReview review = (CvReview) cvReviewGenerator.invoke(arguments);
注意:
- 所有子 Agent 需要的参数都要在 arguments 中提供
- 返回值需要强制转换为对应类型
2.4 运行效果
=== 评审结果 ===
简历评审:
- 评分 = 0.72
- 反馈 = "HR 评审:候选人符合大部分 HR 要求,但通知期略长(3个月),薪资期望略高于范围上限。工作经验稳定,语言能力符合要求。\n经理评审:技术栈匹配度高(Java、Spring Boot、PostgreSQL),有领导项目经验和指导实习生经历。分布式系统经验有限,但学习能力强。\n团队成员评审:具备良好的沟通能力和团队合作精神,务实且产品导向。有指导经验,适合团队文化。"
执行过程分析:
时间轴(假设每个评审耗时 10 秒):
串行方式:
0s ─→ HR 评审开始
10s ─→ HR 评审完成 ─→ 经理评审开始
20s ─→ 经理评审完成 ─→ 团队成员评审开始
30s ─→ 团队成员评审完成
总耗时:30秒
并行方式:
0s ─→ HR 评审开始
─→ 经理评审开始
─→ 团队成员评审开始
10s ─→ HR 评审完成 ✅
─→ 经理评审完成 ✅
─→ 团队成员评审完成 ✅
总耗时:10秒
提速比:30秒 / 10秒 = 3倍 ⚡
三、结果聚合策略详解
3.1 策略 1:拼接(Concatenation)
将所有结果拼接成一个字符串:
.output(agenticScope -> {
CvReview hrReview = (CvReview) agenticScope.readState("hrReview");
CvReview managerReview = (CvReview) agenticScope.readState("managerReview");
CvReview teamMemberReview = (CvReview) agenticScope.readState("teamMemberReview");
String feedback = String.join("\n\n",
"=== HR 评审 ===\n" + hrReview.feedback,
"=== 经理评审 ===\n" + managerReview.feedback,
"=== 团队成员评审 ===\n" + teamMemberReview.feedback
);
double avgScore = (hrReview.score + managerReview.score + teamMemberReview.score) / 3.0;
return new CvReview(avgScore, feedback);
})
适用场景:
- ✅ 需要保留所有原始信息
- ✅ 结果之间没有冲突
- ✅ 人工阅读和审核
优点:
- 简单直接
- 信息完整
- 易于调试
缺点:
- 结果可能很长
- 没有智能整合
3.2 策略 2:投票(Voting)
取多数 Agent 的意见:
.output(agenticScope -> {
CvReview hrReview = (CvReview) agenticScope.readState("hrReview");
CvReview managerReview = (CvReview) agenticScope.readState("managerReview");
CvReview teamMemberReview = (CvReview) agenticScope.readState("teamMemberReview");
// 投票逻辑:如果两个或以上评分 >= 0.7,则通过
int passCount = 0;
if (hrReview.score >= 0.7) passCount++;
if (managerReview.score >= 0.7) passCount++;
if (teamMemberReview.score >= 0.7) passCount++;
boolean approved = passCount >= 2; // 至少两个评审通过
String feedback = String.format(
"投票结果:%s(%d/3 评审通过)\n\nHR: %.2f\n经理: %.2f\n团队成员: %.2f",
approved ? "通过" : "不通过",
passCount,
hrReview.score, managerReview.score, teamMemberReview.score
);
return new CvReview(approved ? 0.8 : 0.3, feedback);
})
适用场景:
- ✅ 需要做出二元决策(通过/不通过)
- ✅ 多个评审员意见可能不一致
- ✅ 需要民主决策
优点:
- 避免单一评审员的偏见
- 决策更客观
- 容错性强
缺点:
- 丢失了详细的反馈信息
- 可能需要额外的解释
3.3 策略 3:加权(Weighted Average)
给不同 Agent 分配不同权重:
.output(agenticScope -> {
CvReview hrReview = (CvReview) agenticScope.readState("hrReview");
CvReview managerReview = (CvReview) agenticScope.readState("managerReview");
CvReview teamMemberReview = (CvReview) agenticScope.readState("teamMemberReview");
// 加权平均:经理权重 50%,HR 权重 30%,团队成员权重 20%
double weightedScore =
hrReview.score * 0.3 +
managerReview.score * 0.5 +
teamMemberReview.score * 0.2;
String feedback = String.format(
"加权评分:%.2f(经理 50%% + HR 30%% + 团队成员 20%%)\n\n详细反馈:\n%s",
weightedScore,
String.join("\n", hrReview.feedback, managerReview.feedback, teamMemberReview.feedback)
);
return new CvReview(weightedScore, feedback);
})
适用场景:
- ✅ 不同 Agent 的重要性不同
- ✅ 某些角色的意见更有权威性
- ✅ 需要精细控制评分计算
优点:
- 灵活性高
- 符合实际业务逻辑(经理决策权更大)
- 可解释性强
缺点:
- 权重设置需要经验
- 可能需要动态调整权重
3.4 策略 4:摘要(Summarization)
使用 LLM 总结所有结果:
.output(agenticScope -> {
CvReview hrReview = (CvReview) agenticScope.readState("hrReview");
CvReview managerReview = (CvReview) agenticScope.readState("managerReview");
CvReview teamMemberReview = (CvReview) agenticScope.readState("teamMemberReview");
// 使用 LLM 生成摘要
ChatLanguageModel summarizer = OpenAiChatModel.builder()
.apiKey(System.getenv("OPENAI_API_KEY"))
.modelName("gpt-4o-mini")
.temperature(0.3)
.build();
String combinedFeedback = String.join("\n\n",
"HR 评审(评分:" + hrReview.score + "):\n" + hrReview.feedback,
"经理评审(评分:" + managerReview.score + "):\n" + managerReview.feedback,
"团队成员评审(评分:" + teamMemberReview.score + "):\n" + teamMemberReview.feedback
);
String prompt = String.format("""
请综合以下三个评审意见,生成一份简洁的综合评审报告:
%s
要求:
1. 总结主要优点和缺点
2. 给出综合评分(0-1)
3. 给出最终建议(推荐面试/不推荐)
4. 控制在 200 字以内
""", combinedFeedback);
String summary = summarizer.generate(prompt);
// 提取综合评分(简化示例,实际可以使用结构化输出)
double avgScore = (hrReview.score + managerReview.score + teamMemberReview.score) / 3.0;
return new CvReview(avgScore, summary);
})
适用场景:
- ✅ 结果很长,需要精简
- ✅ 需要智能整合不同观点
- ✅ 生成易读的报告
优点:
- 结果简洁明了
- 智能整合信息
- 用户体验好
缺点:
- 额外消耗 Token
- 增加延迟
- 可能丢失细节
四、异常处理策略
4.1 问题:某个 Agent 执行失败怎么办?
并行工作流中,某个 Agent 可能会因为网络错误、超时、API 限流等原因失败。我们需要设计合理的异常处理策略。
4.2 策略 1:快速失败(Fail Fast)
任何一个 Agent 失败,整个工作流立即终止:
.parallelBuilder()
.subAgents(hrCvReviewer, managerCvReviewer, teamMemberCvReviewer)
.onError(AgenticServices.OnErrorStrategy.FAIL_FAST) // 快速失败
.build();
适用场景:
- ✅ 所有 Agent 的结果都必需
- ✅ 失败意味着任务无法完成
- ✅ 需要快速发现问题
优点:
- 及时暴露问题
- 避免浪费资源
- 便于调试
缺点:
- 容错性差
- 用户体验不好
4.3 策略 2:容错(Tolerate Errors)
允许部分 Agent 失败,继续执行并使用可用结果:
.parallelBuilder()
.subAgents(hrCvReviewer, managerCvReviewer, teamMemberCvReviewer)
.onError(AgenticServices.OnErrorStrategy.TOLERATE_ERRORS) // 容错
.build();
聚合器中处理缺失值:
.output(agenticScope -> {
CvReview hrReview = (CvReview) agenticScope.readState("hrReview");
CvReview managerReview = (CvReview) agenticScope.readState("managerReview");
CvReview teamMemberReview = (CvReview) agenticScope.readState("teamMemberReview");
// 处理可能的 null 值
List<Double> scores = new ArrayList<>();
List<String> feedbacks = new ArrayList<>();
if (hrReview != null) {
scores.add(hrReview.score);
feedbacks.add("HR 评审:" + hrReview.feedback);
}
if (managerReview != null) {
scores.add(managerReview.score);
feedbacks.add("经理评审:" + managerReview.feedback);
}
if (teamMemberReview != null) {
scores.add(teamMemberReview.score);
feedbacks.add("团队成员评审:" + teamMemberReview.feedback);
}
// 计算可用结果的平均分
double avgScore = scores.stream().mapToDouble(Double::doubleValue).average().orElse(0.0);
String feedback = String.join("\n", feedbacks);
if (feedback.isEmpty()) {
feedback = "所有评审员执行失败,请稍后重试。";
}
return new CvReview(avgScore, feedback);
})
适用场景:
- ✅ 部分结果也可接受
- ✅ 提高系统可用性
- ✅ 降级策略
优点:
- 容错性强
- 用户体验好
- 系统更健壮
缺点:
- 结果可能不完整
- 需要处理边界情况
4.4 策略 3:降级(Fallback)
某个 Agent 失败时,使用备用方案:
.parallelBuilder()
.subAgents(hrCvReviewer, managerCvReviewer, teamMemberCvReviewer)
.onError(AgenticServices.OnErrorStrategy.FALLBACK) // 降级
.fallback((agenticScope, exception) -> {
// 降级逻辑:返回默认评分
System.err.println("评审执行失败:" + exception.getMessage());
return new CvReview(0.5, "评审执行失败,使用默认评分。");
})
.build();
适用场景:
- ✅ 有明确的降级方案
- ✅ 需要提供友好的错误提示
- ✅ 关键业务不能中断
优点:
- 优雅降级
- 用户体验好
- 业务连续性
缺点:
- 需要额外的降级逻辑
- 降级结果可能不够准确
五、性能分析与优化
5.1 提速比计算
理论提速比:
提速比 = 串行执行时间 / 并行执行时间
= (T1 + T2 + ... + Tn) / max(T1, T2, ..., Tn)
示例:
假设有 3 个 Agent,每个耗时 10 秒:
串行:10 + 10 + 10 = 30秒
并行:max(10, 10, 10) = 10秒
提速比:30 / 10 = 3倍 ⚡
实际情况:
由于线程创建、上下文切换、结果聚合等开销,实际提速比会略低于理论值:
实际提速比 ≈ 理论提速比 × 0.8 ~ 0.9
例如:
- 理论提速比:3倍
- 实际提速比:2.4 ~ 2.7倍
5.2 性能对比表
| Agent 数量 | 串行耗时 | 并行耗时(理论) | 提速比(理论) | 提速比(实际) |
|---|---|---|---|---|
| 2 | 20秒 | 10秒 | 2倍 | 1.6~1.8倍 |
| 3 | 30秒 | 10秒 | 3倍 | 2.4~2.7倍 |
| 5 | 50秒 | 10秒 | 5倍 | 4.0~4.5倍 |
| 10 | 100秒 | 10秒 | 10倍 | 8.0~9.0倍 |
假设每个 Agent 耗时 10 秒
5.3 并发控制
当 Agent 数量很多时,需要限制并发数以避免资源耗尽:
// 方式 1:使用固定大小线程池
var executor = Executors.newFixedThreadPool(5); // 最多同时执行 5 个
.parallelBuilder()
.subAgents(agent1, agent2, agent3, ..., agent10) // 10 个 Agent
.executor(executor)
.build();
// 执行过程:
// 第1批:agent1~agent5 同时执行
// 第2批:agent6~agent10 同时执行
线程池大小建议:
| 任务类型 | 线程池大小 | 说明 |
|---|---|---|
| CPU 密集型 | CPU核心数 | 如 8 核 CPU → 8 个线程 |
| IO 密集型 | CPU核心数 × 2 | 如 8 核 CPU → 16 个线程 |
| 混合型 | 10~20 | 根据实际测试调整 |
六、常见问题与避坑指南
6.1 问题 1:outputKey 重复导致结果覆盖
症状:
// ❌ 错误示例
.outputKey("review") // 三个 Agent 都使用相同的 outputKey
后果:
- 最后一个执行的 Agent 会覆盖前面两个的结果
- 聚合器只能获取到一个结果
解决方案:
// ✅ 正确示例
HrCvReviewer hrCvReviewer = AgenticServices.agentBuilder(HrCvReviewer.class)
.outputKey("hrReview") // 每个 Agent 使用不同的 outputKey
.build();
ManagerCvReviewer managerCvReviewer = AgenticServices.agentBuilder(ManagerCvReviewer.class)
.outputKey("managerReview")
.build();
TeamMemberCvReviewer teamMemberCvReviewer = AgenticServices.agentBuilder(TeamMemberCvReviewer.class)
.outputKey("teamMemberReview")
.build();
6.2 问题 2:线程池未关闭导致资源泄漏
症状:
程序执行完毕后,进程仍然在运行
原因:
- 线程池中的线程是守护线程,不会自动退出
解决方案:
// ✅ 方式 1:手动关闭
var executor = Executors.newFixedThreadPool(3);
try {
// 执行工作流
review = cvReviewGenerator.invoke(arguments);
} finally {
executor.shutdown(); // 确保关闭
}
// ✅ 方式 2:使用 try-with-resources(Java 19+)
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
// 执行工作流
}
6.3 问题 3:聚合器中读取到 null 值
症状:
NullPointerException at line: hrReview.score
原因:
- 某个 Agent 执行失败
- outputKey 配置错误
解决方案:
.output(agenticScope -> {
// ✅ 添加 null 检查
CvReview hrReview = (CvReview) agenticScope.readState("hrReview");
if (hrReview == null) {
System.err.println("HR 评审结果为空");
hrReview = new CvReview(0.0, "评审执行失败"); // 使用默认值
}
// 继续处理...
})
6.4 问题 4:并行执行顺序不确定
症状:
第一次执行:HR 先完成,经理后完成
第二次执行:经理先完成,HR 后完成
说明:
- 这是正常现象,并行执行的完成顺序是不确定的
- 不应该依赖执行顺序
解决方案:
// ✅ 如果需要保证顺序,应该在聚合器中统一处理
.output(agenticScope -> {
// 不要假设哪个 Agent 先完成
// 始终从 AgenticScope 中读取所有结果后再处理
CvReview hrReview = (CvReview) agenticScope.readState("hrReview");
CvReview managerReview = (CvReview) agenticScope.readState("managerReview");
// 按固定顺序拼接
String feedback = "HR 评审:" + hrReview.feedback + "\n" +
"经理评审:" + managerReview.feedback;
return new CvReview(avgScore, feedback);
})
七、适用场景与性能分析
7.1 适用场景
| 场景 | 说明 | 示例 |
|---|---|---|
| 多维度评估 | 多个角色同时评估 | HR、经理、团队成员评审简历 |
| 多模型对比 | 同时调用多个模型 | GPT-4、Claude、Gemini 生成答案 |
| 批量处理 | 同时处理多个独立任务 | 批量翻译 100 个文档 |
| A/B 测试 | 同时测试多个方案 | 测试 3 种不同的 Prompt |
| 冗余备份 | 多个 Agent 执行相同任务 | 3 个 Agent 同时生成代码,取最好的 |
7.2 不适用场景
❌ 任务之间有依赖关系:需要先执行 A,再执行 B(应使用顺序工作流)
❌ 共享可变状态:多个 Agent 修改同一个变量(会导致竞态条件)
❌ 资源受限环境:CPU、内存不足(并行会加剧资源竞争)
❌ 单次执行很快:每个 Agent 只需几毫秒(线程创建开销大于收益)
7.3 与其他工作流的组合
组合 1:并行 + 循环
// 伪代码示例
// 在循环内部使用并行,加速每次迭代
.loopBuilder()
.subAgents(
parallelAgent, // 并行执行多个评审员
scoredCvTailor // 然后根据评审结果优化
)
.exitCondition(scope -> ...)
.build();
组合 2:并行 + 条件分支
// 伪代码示例
// 根据条件触发不同的并行工作流
.routerBuilder()
.route("technical", parallelTechReview) // 技术岗并行评审
.route("management", parallelMgmtReview) // 管理岗并行评审
.defaultRoute(fallbackAgent)
.build();
八、最佳实践
8.1 设计独立的 Agent
原则:
- 📌 每个 Agent 应该独立完成,不依赖其他 Agent 的中间结果
- 📌 Agent 之间不应该有通信或同步需求
- 📌 每个 Agent 的输入参数应该明确
示例:
// ✅ 好的设计:三个 Agent 完全独立
HrCvReviewer hrCvReviewer = ...; // 只需要简历 + HR 要求
ManagerCvReviewer managerCvReviewer = ...; // 只需要简历 + 职位描述
TeamMemberCvReviewer teamMemberCvReviewer = ...; // 只需要简历
// ❌ 不好的设计:Agent 之间有依赖
AgentB agentB = ...; // 需要 AgentA 的输出作为输入
8.2 合理设置线程池大小
公式:
线程池大小 = CPU核心数 × 目标CPU利用率 × (1 + W/C)
其中:
- W/C = 等待时间与计算时间的比值
- 对于 IO 密集型任务,W/C 很大,线程池可以大一些
实践经验:
// IO 密集型(调用 LLM API)
var executor = Executors.newFixedThreadPool(20);
// CPU 密集型(本地计算)
var executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
// 混合型
var executor = Executors.newFixedThreadPool(10);
8.3 记录执行时间
价值:
- 📊 分析性能瓶颈
- 🐛 调试慢查询
- 📈 优化线程池配置
long startTime = System.currentTimeMillis();
CvReview review = (CvReview) cvReviewGenerator.invoke(arguments);
long endTime = System.currentTimeMillis();
System.out.println("并行执行耗时:" + (endTime - startTime) + "ms");
8.4 监控并发执行情况
方法:
// 在 Agent 中添加日志
@Agent("评审简历")
CvReview reviewCv(@V("cv") String cv) {
System.out.println(Thread.currentThread().getName() + " 开始执行");
// 执行评审逻辑...
System.out.println(Thread.currentThread().getName() + " 执行完成");
return result;
}
输出:
pool-1-thread-1 开始执行
pool-1-thread-2 开始执行
pool-1-thread-3 开始执行
pool-1-thread-1 执行完成
pool-1-thread-3 执行完成
pool-1-thread-2 执行完成
可以看到三个线程同时执行。
九、结语
本文深入讲解了并行工作流的三大核心组件——子代理列表、线程池与结果聚合器,通过三方并行评审简历的完整实战,演示了从固定聚合到加权聚合的演进思路。并行工作流的精髓在于将独立任务并发执行,以聚合策略融合多路结果:线程池配置决定了并发度,outputKey 隔离保证结果不互相覆盖,聚合器则将分散输出收敛为统一结论。至此,你已经掌握了顺序、循环、并行三种编排模式,下一篇我们将学习条件分支,看如何根据输入内容智能路由到不同的专家 Agent,实现专业化分工。敬请期待!

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

被折叠的 条评论
为什么被折叠?



