LangChain4j Java AI 应用开发实战(二十一):并行工作流 - 多 Agent 协同执行与结果聚合

目录


前言

在前面的文章中,我们学习了顺序工作流(串行执行)和循环工作流(迭代执行)。但在实际业务场景中,往往需要同时执行多个任务以提高效率:

场景 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 数量串行耗时并行耗时(理论)提速比(理论)提速比(实际)
220秒10秒2倍1.6~1.8倍
330秒10秒3倍2.4~2.7倍
550秒10秒5倍4.0~4.5倍
10100秒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 浪潮已至,愿与你同行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

寻道AI小兵

🐳 感谢你的巨浪支持!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值