掘金 人工智能 9小时前
Spring AI 实战:用“编排-工作者”模式打造 AI 旅行规划助手
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

本文深入探讨了Spring AI中的“编排器-工作者”工作流模式,通过一个AI旅行行程规划助手的实际案例,详细阐述了如何将复杂任务动态分解为子任务并进行并行处理。文章介绍了该模式的核心组成部分:编排器、工作者和合成器,并强调了其在处理复杂、动态任务时的灵活性和优势。通过代码示例,读者可以了解如何在Spring AI中实现这一模式,并构建一个能够根据用户偏好生成个性化旅行计划的应用。

🎯“编排器-工作者”模式将复杂问题拆解为多个子任务,分配给专门的“工作者”代理独立完成,类似于项目经理分配任务给团队成员。

⚙️该模式的核心由三部分构成:编排器负责任务分解,工作者处理特定子任务,合成器整合结果。

💡与并行化模式相比,该模式的优势在于其动态性,任务由编排器在运行时根据用户输入决定,更灵活地处理复杂任务。

🗺️文章通过AI旅行行程规划助手案例,演示了如何使用该模式,包括定义数据传输对象(DTO)、实现编排器工作流等步骤。

🔑该案例展示了如何利用 Spring AI 构建一个能够根据用户偏好生成个性化旅行计划的应用,从而更好地理解和应用“编排器-工作者”模式。

大家好,今天继续分享Spring AI Workflow系列中的“编排器-工作者(Orchestrator-Workers)”工作流模式,并通过一个实际案例-AI 旅行行程规划助手,带大家一步步看懂它如何巧妙地实现任务的动态分解与并行处理。

对 Spring AI Workflow 其他模式感兴趣的朋友,可以查看我之前发布的文章:

“编排器-工作者”工作流模式概述

简单来说, “编排器-工作者(Orchestrator-Workers)”工作流模式 就是它能够分析一个复杂问题,将其拆解成多个定义清晰的子任务,然后将这些任务分派给专门的“工作者”代理去独立完成。

这里我举一个场景示例来便于大家理解:比如你是一位项目经理,手下有一支由各类专家组成的团队。现在,客户提出了一个复杂的需求,比如“从零到一搭建一个新的营销网站”。你肯定不会把这个庞大的任务直接丢给某一个人。相反,你 (作为经理) 会先分析需求,然后进行任务分解:

你会把这些子任务分派给最合适的专家。等他们都完成了各自的工作,你再把所有成果汇总起来,最终交付完整的产品。

“编排器-工作者”工作流模式的原理与此如出一辙,只不过合作的对象换成了大语言模型 (LLM)。它主要由三个关键部分组成:

    编排器 (The Orchestrator): 这是一个扮演“经理”角色的 LLM。它唯一的职责就是分析用户最初提出的复杂请求,并将其拆解成一个清晰的、由多个独立子任务组成的列表。它负责决定要做什么工作者 (The Workers): 它们是专门化的 LLM (或者是调用 LLM 的函数),负责从编排器接收一个明确、聚焦的子任务。每个工作者都是其特定领域的专家,它们专注于如何做好整个大任务中的某一个环节。合成器 (The Synthesizer): 这是最后一步,负责收集所有工作者的产出,并将它们整合成一个统一、连贯的最终结果,呈现给用户。

“编排器-工作者”工作流模式与“并行化”模式最核心的区别在于其动态性。在并行化模式中,需要并行执行的任务是预先定义好的。而在“编排器-工作者”工作流模式中,任务是由编排器 LLM 在运行时根据用户的具体输入动态决定的。这使得它在处理那些无法预知具体步骤的复杂任务时更加灵活。

我在这里简单概括了以下“编排器-工作者”工作流模式使用场景:

实践案例:AI 旅行行程规划助手

文章下面的章节内容中,我通过这个具体的示例来帮助大家进一步理解“编排器-工作者”工作流模式。 这个旅行规划助手的功能是:为任何目的地创建详尽的旅行计划。当输入旅行偏好相关的信息后,会按照以下步骤工作:

以下是这个项目的 Spring Boot 应用目录结构:

spring-ai-orchestrator-workers-workflow├── src│   └── main│       ├── java│       │   └── com│       │       └──autogenerator  │       │               ├── controller│       │               │   └── TravelController.java│       │               ├── service│       │               │   └── TravelPlanningService.java│       │               ├── workflow│       │               │   └── TravelOrchestratorWorkflow.java│       │               ├── dto│       │               │   └── TravelRequest.java│       │               │   └── TravelItinerary.java│       │               │   └── OrchestratorAnalysis.java│       │               │   └── PlanningTask.java│       │               ├── SpringAiOrchestratorWorkersWorkflowApplication.java│       └── resources│           └── application.yml└── pom.xml

在这里,我对整个项目的目录结构做一个简要的说明:

第 1 步:添加 Maven 依赖、配置应用属性
关于这个项目的Maven依赖项以及Spring AI的相关配置,可参考Spring AI Chain工作流模式完整指南这篇文章。项目的技术栈主要使用的是JDK17、Spring boot3.5以及Google的gemini-2.5-flash模型版本。

第 2 步:定义数据传输对象 (DTO)

在编写工作流逻辑之前,需要先定义好传输数据的结构,确保数据在从 API 接口、 LLM 调用之间流转以及最终返回给用户的整个过程中,始终保持简洁、结构化和类型安全。这里我使用的是 Java 的 Record 类型,因为它的简洁、不可变特性。

用户输入 DTO :作为整个数据流的起点,该record类用于存放用户旅行计划的所有关键信息,包括目的地、天数、预算、人数等等。

public record TravelRequest(        String destination,        Integer numberOfDays,        String budgetRange,         String travelStyle,         String groupSize,           String specialInterests ) {}

编排器的输出 DTO :数据模型设计中最重要的部分,要求编排器 LLM必须以这种精确的 JSON 格式返回响应。

public record OrchestratorAnalysis(        String analysis,        // 对旅行请求的理解分析        String travelStrategy,  // 本次旅行的总体策略        List<PlanningTask> tasks // 需要执行的具体规划任务列表) {}

工作者的任务 DTO: 代表一个由编排器生成并分配给某个专门worker定义明确的“工作指令”。每个 PlanningTask 都是一条独立的指令,它为工作者提供了高效完成任务所需的所有信息,而无需工作者去理解整个旅行计划。

public record PlanningTask(        String taskType,        // 例如:"accommodation", "activities", "dining"        String description,     // 该任务需要完成什么        String specialization   // 该任务的具体关注点) {}

最终行程 DTO:汇集了整个流程的成果——从编排器的初步分析,到每个专业工作者的分工,再到最终的完整计划。

public record TravelItinerary(        String destination,        String travelStrategy,        String analysis,        List<String> planningResults, // 每个工作者的产出结果        String finalItinerary,        // 整合后的每日行程计划        long processingTimeMs) {}

第 3 步:“编排器-工作者”工作流的实现

TravelOrchestratorWorkflow这个类是实现该模式的核心部分。它接收结构化的用户请求,编排整个多步骤 流程,并最终生成一份完善的旅行计划。

@Componentpublic class TravelOrchestratorWorkflow {    private final ChatClient chatClient;    public TravelOrchestratorWorkflow(ChatClient.Builder chatClientBuilder) {        this.chatClient = chatClientBuilder.build();    }    /**     * 编排器负责协调端到端的旅行规划工作流。     */    public TravelItinerary createTravelPlan(TravelRequest request) {        long startTime = System.currentTimeMillis();        // 步骤 1:编排器分析旅行请求        System.out.println("🎯 编排器正在分析目的地为 " + request.destination() + " 的旅行请求...");        String orchestratorPrompt = String.format(                ORCHESTRATOR_PROMPT_TEMPLATE,                request.destination(),                request.numberOfDays(),                request.budgetRange(),                request.travelStyle() != null ? request.travelStyle() : "general exploration",                request.groupSize() != null ? request.groupSize() : "general",                request.specialInterests() != null ? request.specialInterests() : "general sightseeing"        );        OrchestratorAnalysis analysis = chatClient.prompt()                .user(orchestratorPrompt)                .call()                .entity(OrchestratorAnalysis.class);        System.out.println("📋 旅行策略:" + analysis.travelStrategy());        System.out.println("📝 已识别出 " + analysis.tasks().size() + " 个规划任务。");        // 步骤 2:工作者们并行处理旅行规划的各个方面        System.out.println("⚡️ 工作者们正在创建专属建议...");        List<CompletableFuture<String>> workerFutures = analysis.tasks().stream()                .map(task -> CompletableFuture.supplyAsync(() ->                        executePlanningTask(request, task)))                .toList();        // 等待所有工作者完成任务并收集结果        List<String> planningResults = workerFutures.stream()                .map(CompletableFuture::join)                .collect(Collectors.toList());        // 步骤 3:将所有建议合成为最终的行程计划        System.out.println("🔧 正在合成最终的旅行行程...");        String finalItinerary = synthesizeItinerary(request, analysis, planningResults);        long processingTime = System.currentTimeMillis() - startTime;        System.out.println("✅ 旅行行程创建完毕,耗时 " + processingTime + "ms");        return new TravelItinerary(                request.destination(),                analysis.travelStrategy(),                analysis.analysis(),                planningResults,                finalItinerary,                processingTime        );    }    /**     * 执行单个规划任务 (如住宿、活动等)     */    private String executePlanningTask(TravelRequest request, PlanningTask task) {        System.out.println("🔧 工作者正在处理:" + task.taskType());        String workerPrompt = String.format(                WORKER_PROMPT_TEMPLATE,                request.destination(),                request.numberOfDays(),                task.taskType(),                task.description(),                task.specialization(),                request.budgetRange(),                request.travelStyle() != null ? request.travelStyle() : "general exploration",                request.groupSize() != null ? request.groupSize() : "general",                request.specialInterests() != null ? request.specialInterests() : "general sightseeing"        );        return chatClient.prompt()                .user(workerPrompt)                .call()                .content();    }    /**     * 将所有规划任务的结果整合成一个最终的行程     */    private String synthesizeItinerary(TravelRequest request, OrchestratorAnalysis analysis,                                       List<String> planningResults) {        String combinedResults = String.join("\n\n", planningResults);        String synthesisPrompt = String.format(                SYNTHESIZER_PROMPT_TEMPLATE,                request.destination(),                request.numberOfDays(),                analysis.travelStrategy(),                combinedResults,                request.numberOfDays()        );        return chatClient.prompt()                .user(synthesisPrompt)                .call()                .content();    }    // 提示词模板    private static final String ORCHESTRATOR_PROMPT_TEMPLATE = """            请你扮演一位专业的旅行规划师。请分析以下旅行请求,并确定需要规划哪些方面:                        目的地:%s            行程天数:%s 天            预算:%s            旅行风格:%s            团队规模:%s            特殊兴趣:%s                        基于以上信息,请制定一个旅行策略,并将其分解为 3-4 个具体的规划任务。            每个任务应分别负责旅行的不同方面 (如住宿、活动、餐饮、交通)。                        请以 JSON 格式返回你的响应:            {              "analysis": "你对目的地和旅行者偏好的分析",              "travelStrategy""针对此次旅行类型和目的地的总体策略",              "tasks": [                {                  "taskType": "accommodation",                  "description""根据预算和偏好寻找合适的住宿地点",                  "specialization""重点关注指定预算下的地理位置、设施和性价比"                },                {                  "taskType": "activities",                  "description""推荐符合旅行风格的活动和景点",                  "specialization""重点关注符合旅行风格和兴趣的体验"                }              ]            }            """;    private static final String WORKER_PROMPT_TEMPLATE = """            请根据以下要求创建旅行建议:                        目的地:%s            旅行天数:%s 天            规划重点:%s            任务描述:%s            专业领域:%s            预算范围:%s            旅行风格:%s            团队类型:%s            特殊兴趣:%s                        请提供详细、实用、可落地的旅行建议。            在可能的情况下,请包含具体名称、地点和有用的贴士。            """;    private static final String SYNTHESIZER_PROMPT_TEMPLATE = """            请利用以下规划结果,创建一个详尽的每日旅行行程:                        目的地:%s            行程天数:%s 天            旅行策略:%s                        规划结果:            %s                        请将所有建议整合成一个连贯的 %s 日行程。            按天组织,并包含时间安排、地点、活动间的衔接等实用细节。            使其易于遵循且对旅行者来说切实可行。            """;}

代码逻辑解析createTravelPlan 这个公共方法负责管理从头到尾的整个流程。

最后,该方法会将所有结果——包括初始分析、工作者的原始报告以及最终润色过的计划——全部打包进 TravelItinerary 对象,并返回给用户。

辅助函数与提示词

第 4 步:创建服务类TravelPlanningService service层主要负责Controller和具体的工作流逻辑之间的衔接;

@Servicepublic class TravelPlanningService {    private final TravelOrchestratorWorkflow orchestratorWorkflow;    public TravelPlanningService(TravelOrchestratorWorkflow orchestratorWorkflow) {        this.orchestratorWorkflow = orchestratorWorkflow;    }    public TravelItinerary planTrip(TravelRequest request) {        // 处理任务委托给工作流        return orchestratorWorkflow.createTravelPlan(request);    }}

第 5 步:创建控制器 这一步,通过创建一个控制器TravelController,用于将POST /api/travel/planurl端点暴露出去,接收用户发送的restful 请求。

@RestController@RequestMapping("/api/travel")public class TravelController {    private final TravelPlanningService travelService;    public TravelController(TravelPlanningService travelService) {        this.travelService = travelService;    }    @PostMapping("/plan")    public ResponseEntity<TravelItinerary> createItinerary(@RequestBody TravelRequest request) {        try {            TravelItinerary itinerary = travelService.planTrip(request);            return ResponseEntity.ok(itinerary);        } catch (IllegalArgumentException e) {            return ResponseEntity.badRequest().build();        } catch (Exception e) {            System.err.println("创建旅行行程报错:" + e.getMessage());            return ResponseEntity.internalServerError().build();        }    }}

第 6 步:应用入口点

最后定义启动 Spring Boot 应用的主类, 用于Spring boot初始化所有组件,并启动内嵌的Tomcat容器。

@SpringBootApplicationpublic class SpringAiOrchestratorWorkersWorkflowApplication {    public static void main(String[] args) {        SpringApplication.run(SpringAiOrchestratorWorkersWorkflowApplication.class, args);    }    // 通过 Logbook 启用对所有发往 LLM API 的出站 HTTP 请求的日志记录    @Bean    public RestClientCustomizer restClientCustomizer(Logbook logbook) {        return restClientBuilder -> restClientBuilder.requestInterceptor(new LogbookClientHttpRequestInterceptor(logbook));    }}

“编排器-工作者”工作流模式的分步运行机制

在介绍完上述的关键代码后,我使用浏览器的http调试工具来说明一下整个执行过程:从提交旅行偏好设置,到最终收到一份细节满满的行程计划。

“编排器-工作者”工作流模式时序图

    请求发起:/api/travel/plan 端点发送一个 POST 请求,请求体中包含旅行详情,如目的地、预算和旅行风格等字段信息。

    控制器处理: TravelController 接收到这个请求。Spring 框架会自动将 JSON 数据转换为一个 TravelRequest DTO 对象。随后,控制器将这个 DTO 传递给 TravelPlanningService服务委派: TravelPlanningService 接收到 TravelRequest 后,立即调用 TravelOrchestratorWorkflow 中的 createTravelPlan 方法。编排器分析 (第 1 步): TravelOrchestratorWorkflow 开始执行。它将用户的偏好连同一个专门的“编排器”提示词一起发送给 LLM。LLM 分析该请求后,返回一份结构化的行动计划——一个子任务列表 (例如,规划住宿、寻找活动、推荐餐厅)。工作者并行执行 (第 2 步): 接着,工作流将每个子任务分派给一个“工作者”。利用 CompletableFuture为每个任务触发一次独立的 LLM 调用。所有的工作者并行执行——一个在找酒店,另一个在搜罗美食。行程合成 (第 3 步): 一旦所有工作者都完成了各自的任务,工作流便会收集它们各自的报告。然后,它发起最后一次“合成器” LLM 调用,将所有工作者的产出汇总起来,并请求 LLM 将它们整理成一份统一、连贯、按天规划的旅行计划。响应交付: 最终生成的 TravelItinerary 对象包含了完整的行程计划,会沿着调用链从工作流返回到服务层,再到控制器。控制器将其包装在一个状态为 200 OK 的 ResponseEntity 中,作为最终的 JSON 响应发送回用户。

最终返回的结构

当然,这个模式虽然强大,但并非“银弹”。它的灵活性也带来了一些工程上的挑战,如果考虑不周,很容易“踩坑”。在实践中,我总结了以下几点需要特别注意:

写在最后

总体来看,“编排器-工作者”工作流提供了一种全新的、更工程化的LLM交互范式:不再依赖一个单一、复杂的提示词,而是创建了一个由“代理”组成的团队,它们分工协作,共同解决一个问题的不同部分。借助这种模式,可以构建出真正“智能”的应用,能够思考、规划、分工,并最终将所有成果汇集在一起。
好了,今天的分享就这么多,后续会继续分享Spring AI系列的相关教程,有问题欢迎在评论区讨论~

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

Spring AI 工作流模式 编排器-工作者 AI旅行规划
相关文章