🎉🎉《Spring Boot实战案例合集》目前已更新135个案例,我们将持续不断的更新。文末有电子书目录。
💪💪永久更新承诺
我们郑重承诺,所有订阅合集的粉丝都将享受永久免费的后续更新服务。
💌💌如何获取
订阅我们的合集《点我订阅》,并通过私信联系我们,我们将第一时间将电子书发送给您。
环境:SpringBoot3.4.2
1. 简介
智能体系统的基础构建模块是一个经过增强的 LLM,这种增强体现在它集成了检索、工具和记忆等功能。我们当前的模型能够主动利用这些能力——生成自己的搜索查询、选择合适的工具,并决定保留哪些信息。
Anthropic公司将不同 "智能体" 定义(有些人认为智能体是能够长期独立运行、使用各种工具完成复杂任务的全自主系统;而另一些人则将其视为遵循预定义工作流的实现方案)都归类为智能体系统,但在架构上对工作流和智能体进行了区分:
工作流:它是一个系统,通过预定义的代码路径来协调大型语言模型(LLM)和工具,即按照既定的流程和规则来执行任务。
智能体:它强调大型语言模型能够动态指导自身处理流程和工具使用,具有自主性,能够根据实际情况自主控制如何完成任务。
接下来,将介绍在生产环境中观察到的智能体系统的常见模式。我们将从基础构建模块——增强型大型语言模型(augmented LLM)开始,然后逐步增加复杂性,从简单的组合式工作流到自主智能体。
1.1 链式工作流(Chain Workflow)
链式工作流模式体现了将复杂任务分解为更简单、更易管理的步骤的原则。如下图所示:
使用场景:
任务具有清晰的顺序步骤时
当您愿意以增加延迟为代价换取更高准确性时
当每个步骤都基于前一步骤的输出时
1.2 并行化工作流(Parallelization Workflow)
大型语言模型(LLMs)可以同时处理任务,并通过编程方式聚合它们的输出。如下图所示:
使用场景:
处理大量相似但相互独立的事项时
需要多个独立视角来完成的任务时
当处理时间至关重要且任务可并行化时
1.3 路由工作流(Routing Workflow)
路由模式实现了智能的任务分配,能够对不同类型的输入进行专门处理。如下图所示:
使用场景:
处理具有不同输入类别的复杂任务时
当不同输入需要专门的处理方式时
当能够准确进行分类处理时
1.4 协调器-工作者工作流(Orchestrator-workers)
在协调器-工作者工作流中,一个中央的大型语言模型(LLM)会动态地分解任务,将其分配给工作者LLM,并综合它们的处理结果。如下图所示:
使用场景:
在那些子任务无法提前预测的复杂任务中
在需要采用不同方法或视角来完成的任务中
在需要自适应解决问题的情境中
1.5 评估器-优化器工作流(Evaluator-optimizer)
在评估器-优化器工作流中,一个大型语言模型(LLM)调用生成响应,而另一个模型则在循环中提供评估和反馈。如下图所示:
使用场景:
当存在明确的评估标准时
当迭代优化能带来可衡量的价值时
当任务能从多轮评审中受益时
1.6 智能体(Agents)
智能体随LLMs成熟在生产中兴起,能处理复杂任务。它们从人类用户获取指令后独立规划操作,执行时从环境获取反馈以评估进展,可暂停等待人类反馈。任务完成后终止,常设停止条件。如下图所示:
使用场景:
智能体适用于解决开放式问题,即那些难以或无法预测所需步骤数量,且无法预先设定固定路径的问题。大型语言模型(LLM)可能会进行多轮操作,因此你必须对其决策能力有一定程度的信任。智能体的自主性使其非常适合在受信任的环境中扩展任务规模。
注意:智能体的自主性也意味着更高的成本以及错误累积的潜在风险。我们建议在沙盒环境中进行广泛的测试,并设置适当的防护措施。
接下来,我们将通过案例的方式演示上面的3种工作流模式。
环境准备
<dependency><groupId>com.alibaba.cloud.ai</groupId><artifactId>spring-ai-alibaba-starter</artifactId><version>1.0.0-M6.1</version></dependency>
我们使用阿里的大模型。
配置文件
spring:ai:dashscope:api-key: sk-xxxooobase-url: https://dashscope.aliyuncs.com/compatible-mode/v1chat:options:stream: truemodel: qwen-turbo
2.1 链式工作流
public class ChainWorkflow {// 一系列系统提示(system prompts),这些提示定义了链式处理中的转换步骤。每个提示都充当一个关卡(gate),在进入下一步之前对输出进行验证和转换。public static final String[] DEFAULT_SYSTEM_PROMPTS = {// Step 1"""从文本中仅提取数值及其对应的指标。将每个数值和指标按照“数值: 指标”的格式逐行列出。示例格式:92: 客户满意度45%: 收入增长率""",// Step 2"""将所有数值尽可能转换为百分比形式。若数值并非百分比或点数形式,则将其转换为小数形式后再以百分比呈现(例如,92 点 → 92%)。每行只保留一个数值。示例格式:92%:客户满意度45%:收入增长率""",// Step 3"""将所有行按照数值大小进行降序排序。每行保持“数值: 指标”的格式。示例:92%: 客户满意度87%: 员工满意度""",// Step 4"""将排序后的数据格式化为以下带有“指标”和“数值”两列的 Markdown 表格:| 指标 | 数值 ||:--|--:|| 客户满意度 | 92% | """};private final ChatClient chatClient;public ChainWorkflow(ChatClient chatClient) {this.chatClient = chatClient ;}public String chain(String userInput, String[] systemPrompts) {int step = 0;String response = userInput;System.out.println(String.format("\nSTEP %s:\n %s", step++, response));for (String prompt : systemPrompts) {// 1. 使用上一步的响应来构建输入内容。String input = String.format("{%s}\n {%s}", prompt, response);// 2. 使用新的输入内容调用聊天客户端,并获取新的响应。response = chatClient.prompt(input).call().content();System.out.println(String.format("\nSTEP %s:\n %s", step++, response));}return response;}}
测试接口
public ResponseEntity<String> chain() {String[] prompts = ChainWorkflow.DEFAULT_SYSTEM_PROMPTS ;String userInput = """第三季度绩效总结:本季度我们的客户满意度得分提升至92分。与去年相比,收入增长了45%。在我们的主要市场中,市场份额现已达到23%。客户流失率从8%下降至5%。新用户获取成本为每用户43美元。产品采用率提升至78%。员工满意度得分为87分。营业利润率提高至34%。"""; ;return ResponseEntity.ok(this.chainWorkflow.chain(userInput, prompts)) ;}
运行结果
控制台
接口输出
2.2 并行化工作流
public class ParalleWorkflow {private final ChatClient chatClient;public ParalleWorkflow(ChatClient chatClient) {this.chatClient = chatClient;}/*** 使用固定线程池和相同的提示模板并发处理多个输入。此方法会保持结果顺序与输入顺序一致。** @param prompt 用于每个输入的提示模板。输入将被追加到此提示后。不能为null。示例:"Translate the following text to French:"* @param inputs 待处理的输入字符串列表。每个输入将独立并行处理。不能为null或空。示例:["Hello", "World", "Good morning"](["你好", "世界", "早上好"]* @param nWorkers 要使用的并发工作线程数。这控制着同时进行的最大LLM API调用次数。必须大于0。在设置此值时,请考虑API的速率限制。* @return 与输入顺序相同的处理结果列表。每个结果包含LLM对相应输入的响应。*/public List<String> parallel(String prompt, List<String> inputs, int nWorkers) {ExecutorService executor = Executors.newFixedThreadPool(nWorkers);try {List<CompletableFuture<String>> futures = inputs.stream().map(input -> CompletableFuture.supplyAsync(() -> {try {return chatClient.prompt(prompt + "\nInput: " + input).call().content();} catch (Exception e) {throw new RuntimeException("Failed to process input: " + input, e);}}, executor)).collect(Collectors.toList());// 等待所有任务完成CompletableFuture<Void> allFutures = CompletableFuture.allOf(futures.toArray(CompletableFuture[]::new));allFutures.join();return futures.stream().map(CompletableFuture::join).collect(Collectors.toList());} finally {executor.shutdown();}}}
测试接口
@GetMapping("")public ResponseEntity<?> index() {List<String> response = this.paralleWorkflow.parallel("""分析市场变化将如何影响该利益相关者群体。提供具体影响及建议措施。格式应清晰划分章节,并明确优先级。""", List.of("""客户群体:- 对价格敏感- 追求更先进的技术- 关注环境问题""","""员工群体:- 对工作保障的担忧- 需要新技能- 希望获得明确的方向指引""","""投资者:- 期望增长- 希望控制成本- 关注风险问题""","""供应商:- 产能限制- 价格压力- 技术转型"""), 4);System.err.println(response) ;return ResponseEntity.ok(response);}
2.3 路由工作流
public class RoutingWorkflow {private final ChatClient chatClient;public RoutingWorkflow(ChatClient chatClient) {this.chatClient = chatClient;}public String route(String input, Map<String, String> routes) {String routeKey = determineRoute(input, routes.keySet());String selectedPrompt = routes.get(routeKey);if (selectedPrompt == null) {throw new IllegalArgumentException("Selected route '" + routeKey + "' not found in routes map");}return chatClient.prompt(selectedPrompt + "\nInput: " + input).call().content();}private String determineRoute(String input, Iterable<String> availableRoutes) {System.out.println("\n有效路由: " + availableRoutes);String selectorPrompt = String.format("""分析输入内容,并从以下选项中选择最合适的支持团队: %s先阐述你的推理过程,然后以以下JSON格式提供你的选择:\\{"reasoning": "对该工单应被路由到特定团队的简要解释。请考虑关键词、用户意图以及紧急程度。","selection": "所选团队的名称"\\}Input: %s""", availableRoutes, input);RoutingResponse routingResponse = chatClient.prompt(selectorPrompt).call().entity(RoutingResponse.class);System.out.println(String.format("路由分享:%s\n选择的路由: %s",routingResponse.reasoning(), routingResponse.selection()));return routingResponse.selection();}}
测试代码
运行结果
推荐文章
SpringBoot冷门但逆天的5个神级注解,老司机都在偷偷用!
强大!Spring Boot 巧妙利用 SpEL 实现复杂的规则运算
太赞了!AOP弃用@Aspect,一个注解让你的代码灵活十倍!
技术专家!@Transactional 炸场升级:回滚+重试一体化
Spring Boot中记录JDBC、JPA及MyBatis执行SQL及参数的正确姿势
Spring Boot中通过3种方式初始化数据,你们如何选择?


