首页
关于这个博客
Search
1
Java 实现Google 账号单点登录(OAuth 2.0)全流程解析
1,871 阅读
2
Spring AI 无法获取大模型深度思考内容?解决方案来了
653 阅读
3
大模型“无脑复读”?3分钟搞懂原因+解决办法
503 阅读
4
EasyExcel 实战:导出带图片的 Excel 完整方案
355 阅读
5
服务器遭遇 XMRig 挖矿程序入侵排查与清理全记录
329 阅读
Java 核心
框架与中间件
数据库技术
开发工具与效率
问题排查与踩坑记录
程序员成长与思考
前端
AI大模型&智能体
登录
Search
标签搜索
Spring AI
java虚拟机
JVM
Spring AI Alibaba
Java
保姆级教程
SpringBoot
Spring
MCP
WebFlux
龙虾
Agent Skills
大模型
OpenClaw
人工介入
Nginx
Ubuntu
Apache POI
自定义starter
Mybatis
Luca Ju
累计撰写
54
篇文章
累计收到
2
条评论
首页
栏目
Java 核心
框架与中间件
数据库技术
开发工具与效率
问题排查与踩坑记录
程序员成长与思考
前端
AI大模型&智能体
页面
关于这个博客
搜索到
54
篇与
的结果
2026-05-15
一次 OOM 线上排查实录
大家好,今天分享一次真实的线上 OOM 排查过程,踩坑 Druid 连接池的经典内存泄漏问题,以及完整的解决思路。一、问题现场:线上内存飙高,OOM 报警某天,线上老项目突然收到服务器内存使用率持续飙高的报警,紧接着应用直接抛出 OOM 错误,服务崩溃。紧急拉取了堆 Dump 文件,用 JProfiler 打开后,直接看到了内存占用的元凶:大量 com.alibaba.druid.proxy.jdbc 相关对象堆积堆中最大的单个对象是一个 char[],大小超过 500MB,存储的正是项目中执行的 SQL 字符串结合项目业务场景,初步判断是数据库操作相关的内存泄漏,定位方向直接锁定了代码中的 SQL 操作和 Druid 连接池配置。二、根因定位:双重问题叠加导致的灾难顺着堆 Dump 里的 SQL 文本,我直接定位到了业务代码,发现这次 OOM 是两个问题叠加导致的。1. 业务代码:SQL 拼接逻辑导致大对象堆积这是一个老项目,当年的开发同学已经离职了,代码里存在这样的逻辑:单条 INSERT 语句中,通过循环拼接 SQL 字符串,一次性插入大量数据当数据量较大时,拼接后的 SQL 字符串会变得非常大,生成的 char[] 对象直接占用几百 MB 内存这些大字符串被线程栈引用,短时间内无法被 GC 回收,直接推高了内存水位2. 框架层面:Druid 1.1.22 版本的经典 SQL 缓存泄漏堆 Dump 中大量的 Druid 对象,指向了一个更致命的问题:Druid 连接池的 SQL 统计缓存。项目使用的 Druid 版本是 1.1.22,这个版本存在一个广为人知的问题:SQL 统计功能会无限制缓存所有执行过的 SQL 字符串,无法自动清理项目中拼接的大量不同 SQL,会被 Druid 全部缓存到 sqlStatMap 中,这些对象会一直持有 SQL 字符串的引用,导致它们无法被 GC 回收随着服务运行时间增长,缓存的 SQL 越来越多,内存只会涨不会跌,最终撑满堆内存,触发 OOM三、解决方案:两步走彻底根治问题针对这两个问题,我们采用了业务+框架双管齐下的修复方案,从根源解决内存泄漏。第一步:优化 Druid 配置,掐断缓存泄漏直接修改项目的 Druid 配置,关闭无限制的 SQL 统计,同时限制缓存大小,避免内存无限增长。方案 A:彻底关闭 SQL 统计(推荐,零泄漏风险)spring: datasource: druid: filter: stat: enabled: false # 关闭导致内存泄漏的SQL统计 web-stat-filter: enabled: false # 关闭Web统计,减少额外内存占用方案 B:保留监控,限制缓存大小(折中方案)如果业务必须保留 SQL 监控,可以通过配置限制缓存的 SQL 数量,避免无限增长:spring: datasource: druid: filter: stat: enabled: true max-stat-count: 200 # 限制最多缓存200条SQL,超出自动淘汰第二步:重构业务代码,替换 SQL 拼接为批量插入修改原有的 SQL 拼接逻辑,改为标准的批量插入方式,既避免了超大 SQL 字符串的生成,也提升了数据库写入性能。改造前(问题代码)// 循环拼接SQL,生成超大字符串 StringBuilder sql = new StringBuilder("INSERT INTO t_invoice (col1, col2) VALUES "); for (Invoice invoice : list) { sql.append("(?, ?),"); } jdbcTemplate.update(sql.toString(), params);改造后(批量插入)// 使用JdbcTemplate批量插入,避免生成超大SQL字符串 String sql = "INSERT INTO t_invoice (col1, col2) VALUES (?, ?)"; jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() { @Override public void setValues(PreparedStatement ps, int i) throws SQLException { ps.setString(1, list.get(i).getCol1()); ps.setString(2, list.get(i).getCol2()); } @Override public int getBatchSize() { return list.size(); } });四、效果验证与后续优化改造完成后,我们重新上线服务并进行了压测验证:内存曲线恢复平稳,不再出现持续飙高的情况堆 Dump 中 Druid 相关对象和大 char[] 基本消失数据库写入性能也有明显提升,单批次插入耗时降低了 40%额外优化建议对于老项目,建议升级 Druid 到最新稳定版(如 1.2.20+),修复了大量已知的内存泄漏问题批量插入时,建议设置合理的批次大小(如每批 100-500 条),避免单次操作过大导致数据库压力上线前务必进行压测,通过 JProfiler 或 Arthas 观察内存变化,提前发现潜在问题五、踩坑总结这次 OOM 排查给了我两个深刻的教训:老项目的依赖版本一定要关注:Druid 1.1.22 这个版本的 SQL 缓存泄漏问题非常普遍,很多线上 OOM 都源于此,升级或关闭统计是最直接的解决方式。业务代码的 SQL 拼接是隐形杀手:不仅容易导致 SQL 注入,还会生成超大对象,配合框架的缓存机制,很容易引发内存泄漏。批量插入是更安全、更高效的替代方案。希望这次分享能帮到遇到同样问题的朋友,如果你也遇到了 Druid 相关的内存问题,欢迎在评论区交流讨论~
2026年05月15日
6 阅读
0 评论
1 点赞
2026-04-30
DeepAgents 人工介入实战|LangGraph 实现 Agent 高危工具人工审批
之前一篇文章里,我使用 Spring AI Alibaba 演示了智能体执行过程中的人工介入能力。那篇文章的核心思路是:当 Agent 准备执行某些高风险动作时,不要让它直接执行,而是先暂停下来,把待执行动作交给人工审批,审批通过后再继续执行。感兴趣的小伙伴可以通过下面链接回顾一下:https://www.lucaju.cn/index.php/archives/165/这篇文章换一个技术栈,使用 Python 版本来实现同样的能力。本次示例基于:LangChain:负责模型和工具抽象LangGraph:负责执行状态、检查点和恢复执行DeepAgents:负责创建支持工具调用和中断审批的 Agent通义千问兼容 OpenAI API:作为底层大模型一、为什么 Agent 需要人工介入Agent 最大的价值是可以根据用户目标自主规划并调用工具。但并不是所有工具都适合完全自动执行。比如:删除数据库表删除文件发起转账修改线上配置调用外部系统执行不可逆操作这些动作一旦执行错误,影响可能非常大。所以比较合理的模式是:普通查询类动作可以让 Agent 自动执行,高风险动作必须先进入人工审批。在本文示例中,我们定义了三个工具:query_table_data:查询表数据,低风险,可以自动执行delete_table:删除数据表,高风险,需要人工审批delete_file:删除文件,高风险,需要人工审批用户输入的任务是:先查询product表的数据!再删除user表,最后,删除lucaju.txt文件Agent 会先分析任务,并尝试依次调用工具。但当执行到删除表、删除文件这类高风险动作时,会被框架中断,等待人工确认。二、初始化大模型首先初始化模型:import os from langchain.chat_models import init_chat_model llm = init_chat_model( model="kimi-k2.5", model_provider="openai", api_key=os.getenv("AliQwen_API"), base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", model_kwargs={"reasoning_effort": "none"}, )这里使用的是 OpenAI 兼容模式,所以 model_provider 设置为 openai,同时把 base_url 指向阿里云 DashScope 的兼容接口。需要提前配置环境变量:export AliQwen_API="你的 API Key"model_kwargs={"reasoning_effort": "none"} 是模型调用参数,可以根据实际模型支持情况调整。三、定义工具示例中定义了三个工具:from langchain.tools import tool @tool def delete_table(table_name: str) -> str: """ 删除指定的表 """ return f"删除表{table_name}" @tool def delete_file(file_name: str) -> str: """ 删除指定的文件 """ return f"删除文件{file_name}" @tool def query_table_data(table_name: str) -> str: """ 查询指定表的数据 """ return f"查询表{table_name}的数据"这里为了演示效果,工具内部只是返回字符串,并没有真的连接数据库或删除文件。在真实项目中,delete_table 可能会执行 SQL,delete_file 可能会操作对象存储或服务器文件系统。这类工具就非常适合加人工审批。四、为什么必须配置 CheckpointerHuman-in-the-loop 的核心不是简单地打印一句“是否确认”,而是让 Agent 的执行流程真正暂停下来,并且后续可以从暂停点继续执行。这就需要保存执行状态。示例代码中使用了 LangGraph 提供的内存检查点:from langgraph.checkpoint.memory import InMemorySaver checkpointer = InMemorySaver() config = { "configurable": { "thread_id": "123" } }这里有两个关键点:checkpointer 用来保存 Agent 的执行状态。thread_id 用来标识当前会话。当 Agent 执行到需要人工介入的节点时,LangGraph 会把当前状态保存下来。等人工审批完成后,再通过同一个 thread_id 找回之前的状态,并继续执行。如果没有检查点,框架就不知道应该从哪里恢复执行。五、创建支持人工介入的 DeepAgent接下来创建 Agent:from deepagents import create_deep_agent main_agent = create_deep_agent( model=llm, name="主智能体", system_prompt="回答使用中文,调用对应的工具实现对应的功能!", tools=[delete_table, delete_file, query_table_data], interrupt_on={"delete_table": True, "delete_file": True}, checkpointer=checkpointer )这里最关键的是两个参数:interrupt_on={"delete_table": True, "delete_file": True}以及:checkpointer=checkpointerinterrupt_on 用来声明哪些工具调用需要中断。在这个例子中:调用 query_table_data 不会中断调用 delete_table 会中断调用 delete_file 会中断也就是说,Agent 可以自动查询数据,但不能自动删除表或文件。这就是人工介入的核心配置。六、第一次执行:触发中断第一次执行 Agent:result_1 = main_agent.invoke( { "messages": [ { "role": "user", "content": "先查询product表的数据!再删除user表,最后,删除lucaju.txt文件", } ] }, config=config, )这次调用并不一定会完整执行完所有工具。如果执行链路中包含需要人工审批的工具,Agent 会暂停,并把中断信息放到返回结果的 __interrupt__ 字段中。示例代码中这样获取:interrupt = result_1["__interrupt__"]当 interrupt 不为空时,说明当前执行过程中存在需要人工介入的动作。七、查看待审批动作__interrupt__ 中会包含本次待审批的工具调用信息,例如:[ Interrupt( value={ "action_requests": [ { "name": "delete_table", "args": {"table_name": "user"}, "description": "Tool execution requires approval..." }, { "name": "delete_file", "args": {"file_name": "zhaoweifeng.txt"}, "description": "Tool execution requires approval..." } ], "review_configs": [ { "action_name": "delete_table", "allowed_decisions": ["approve", "edit", "reject"] }, { "action_name": "delete_file", "allowed_decisions": ["approve", "edit", "reject"] } ] } ) ]这里有两个比较重要的字段。action_requests 表示 Agent 想要执行哪些工具:工具名称工具参数工具描述review_configs 表示这些动作允许哪些审批结果:approve:同意执行edit:修改参数后执行reject:拒绝执行这三个动作基本覆盖了常见的人审场景。八、人工审批:拒绝高危操作示例代码中把删除表和删除文件都拒绝掉:decisions = [] action_requests = interrupt[0].value["action_requests"] print(f"当前人机交互工具:{action_requests}") for action_request in action_requests: if action_request["name"] == "delete_table": decisions.append({"type": "reject"}) elif action_request["name"] == "delete_file": decisions.append({"type": "reject"})这里的 decisions 顺序需要和 action_requests 对应。也就是说,如果 Agent 申请了两个动作:删除 user 表删除 zhaoweifeng.txt 文件那么人工审批结果也应该按顺序给出两个 decision。在这个示例中,两个高风险动作都被拒绝:[ {"type": "reject"}, {"type": "reject"} ]九、第二次执行:恢复 Agent审批完成后,不需要重新传入用户消息,而是通过 Command(resume=...) 恢复执行:from langgraph.types import Command result_2 = main_agent.invoke( Command( resume={ "decisions": decisions } ), config=config, )这里一定要继续传入相同的 config,尤其是相同的 thread_id。因为 Agent 要根据 thread_id 找到之前暂停的执行状态。恢复执行后,框架会根据人工审批结果继续处理:被 approve 的工具会继续执行被 reject 的工具不会执行被 edit 的工具会使用人工修改后的参数执行最后输出结果:print(f"最终结果{result_2['messages'][-1].content}")十、完整代码完整示例代码如下:""" 演示human-in-the-loop模式, 必须使用记忆功能 """ import os from deepagents import create_deep_agent from langchain.chat_models import init_chat_model from langchain.tools import tool from langgraph.checkpoint.memory import InMemorySaver from langgraph.types import Command # 初始化大模型 llm = init_chat_model( model="kimi-k2.5", model_provider="openai", api_key=os.getenv("AliQwen_API"), base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", model_kwargs={"reasoning_effort": "none"}, ) # 删除表工具 @tool def delete_table(table_name: str) -> str: """ 删除指定的表 """ return f"删除表{table_name}" # 删除文件 @tool def delete_file(file_name: str) -> str: """ 删除指定的文件 """ return f"删除文件{file_name}" # 查询表数据 @tool def query_table_data(table_name: str) -> str: """ 查询指定表的数据 """ return f"查询表{table_name}的数据" # 设置检查点 checkpointer = InMemorySaver() # 配置检查点 config = { "configurable": { "thread_id": "123" } } # 创建deepagent,同时给高危工具设置人机交互 main_agent = create_deep_agent( model=llm, name="主智能体", system_prompt="回答使用中文,调用对应的工具实现对应的功能!", tools=[delete_table, delete_file, query_table_data], interrupt_on={"delete_table": True, "delete_file": True}, checkpointer=checkpointer ) # 预执行,本次不会真正的执行所有工具。 # 如果执行链路中存在人机交互节点,框架会暂停,并返回 __interrupt__。 result_1 = main_agent.invoke( { "messages": [ { "role": "user", "content": "先查询product表的数据!再删除user表,最后,删除lucaju.txt文件", } ] }, config=config, ) # 检查本次执行是否存在人机交互动作 interrupt = result_1["__interrupt__"] if interrupt: print("存在人机交互动作") # 定义一个列表,存储所有审批结果 decisions = [] # 获取所有待审批动作 action_requests = interrupt[0].value["action_requests"] print(f"当前人机交互工具:{action_requests}") for action_request in action_requests: if action_request["name"] == "delete_table": decisions.append({"type": "reject"}) elif action_request["name"] == "delete_file": decisions.append({"type": "reject"}) # 再次执行,不需要传会话内容,只需要传审批意见和 config result_2 = main_agent.invoke( Command( resume={ "decisions": decisions } ), config=config, ) print(f"最终结果{result_2['messages'][-1].content}")十一、如果想修改参数后再执行除了直接拒绝,我们也可以修改工具参数后再执行。比如 Agent 原本想删除:zhaoweifeng.txt人工审批时可以把文件名改成另一个值:decisions.append({ "type": "edit", "edited_action": { "name": action_request["name"], "args": { "file_name": "new-file.txt" } } })这时框架不会使用 Agent 原始生成的参数,而是使用人工编辑后的参数继续执行工具。这在实际业务中非常有用。例如:Agent 选择的表名不准确,人工改成正确表名Agent 生成的文件路径不安全,人工改成允许路径Agent 生成的金额过大,人工改成合理金额Agent 生成的收件人错误,人工改成正确收件人相比简单的同意或拒绝,edit 让人工介入更灵活。十二、执行流程总结整体流程可以概括为:用户输入任务 ↓ Agent 分析任务并规划工具调用 ↓ 普通工具自动执行 ↓ 遇到 interrupt_on 配置的高风险工具 ↓ LangGraph 保存状态并中断执行 ↓ 人工读取 __interrupt__ 中的 action_requests ↓ 人工给出 approve / edit / reject ↓ 使用 Command(resume=...) 恢复执行 ↓ Agent 根据审批结果继续完成任务这套机制的关键点是:interrupt_on:定义哪些工具需要人工审批checkpointer:保存执行状态thread_id:标识同一次会话__interrupt__:获取待审批动作Command(resume=...):把审批结果送回 Agent 并恢复执行十三、和 Spring AI Alibaba 版本的对比从思想上看,Python 版本和 Spring AI Alibaba 版本是一致的:Agent 不应该无限制地自动执行所有动作,高风险动作需要进入人工审批流程。但实现方式上有所不同。Spring AI Alibaba 更偏向 Java 生态,适合和 Spring Boot、企业系统、审批流、权限体系结合。而 LangChain + LangGraph + DeepAgents 的 Python 方案更偏向实验、原型验证和 Agent 工作流编排。尤其是 LangGraph 的 checkpoint 和 resume 机制,让“暂停后恢复”这件事变得非常自然。如果项目本身是 Java 技术栈,可以优先考虑 Spring AI Alibaba。如果项目本身是 Python 技术栈,或者正在做 Agent 编排、工具调用、多步骤任务规划,那么 DeepAgents 这套方式会比较顺手。十四、真实项目中的建议在真实项目中使用人工介入时,建议注意以下几点。第一,高风险工具要显式配置。不要只依赖 prompt 告诉模型“删除前要确认”。更可靠的方式是像本文一样,在框架层面对工具进行拦截。第二,审批信息要完整展示。人工审批时至少要看到:工具名称工具参数Agent 为什么要执行这个工具当前用户是谁当前会话上下文第三,审批结果要落库。生产环境中,审批记录应该持久化,包括:谁审批的什么时间审批的原始参数是什么修改后的参数是什么最终是通过、拒绝还是编辑后通过第四,检查点不要只用内存。本文为了演示使用的是:InMemorySaver()生产环境更建议使用数据库、Redis 或其他持久化存储。否则服务重启后,暂停中的 Agent 状态会丢失。第五,审批权限要和业务系统打通。不是所有人都应该可以批准所有工具调用。比如:普通用户只能审批自己的任务管理员可以审批团队任务涉及资金、删除、发布的动作需要更高权限人工介入不是简单的弹窗确认,而应该是完整的安全控制链路。关于DeepAgents的练习代码已经上传到了我的GitHub,欢迎fork & starhttps://github.com/Jucunqi/deepagents-practice.git
2026年04月30日
11 阅读
0 评论
0 点赞
2026-04-10
Hermes Agent爆火,聊聊与OpenClaw 到底区别在哪
本文对比近期爆火的Hermes Agent与OpenClaw两大AI Agent框架,从设计理念、记忆系统、技能生成、安全机制等维度解析差异,分析适用场景与互补用法,帮你快速判断哪款更适合自己的自动化需求。
2026年04月10日
51 阅读
0 评论
2 点赞
2026-04-02
分布式智能体|A2A Agent实战
随着智能体应用的广泛应用,智能体的分布式部署、跨网络、跨框架、跨组织调用,成为当下智能体落地急需解决的问题。针对这一痛点,Google推出了Agent2Agent(简称A2A)协议,专为智能体之间的互联互通、协同协作提供标准化的解决方案。本文基于Spring AI Alibaba框架,使用Nacos作为注册中心,落地完整的分布式智能体方案。整体架构:一、Nacos注册中心安装本次选用的Nacos版本为3.2.0,安装配置流程可参考官方文档,这里不再赘述基础安装步骤。Nacos官方地址:https://nacos.io/二、项目依赖引入使用官方推荐的BOM方式进行依赖管理<dependencyManagement> <dependencies> <dependency> <groupId>com.alibaba.cloud.ai</groupId> <artifactId>spring-ai-alibaba-bom</artifactId> <version>1.1.2.0</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-bom</artifactId> <version>1.1.2</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>com.alibaba.cloud.ai</groupId> <artifactId>spring-ai-alibaba-extensions-bom</artifactId> <version>1.1.2.2</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <!-- 智能体核心框架 --> <dependency> <groupId>com.alibaba.cloud.ai</groupId> <artifactId>spring-ai-alibaba-agent-framework</artifactId> </dependency> <!-- 通义千问AI模型依赖 --> <dependency> <groupId>com.alibaba.cloud.ai</groupId> <artifactId>spring-ai-alibaba-starter-dashscope</artifactId> </dependency> <!-- A2A+Nacos集成依赖 --> <dependency> <groupId>com.alibaba.cloud.ai</groupId> <artifactId>spring-ai-alibaba-starter-a2a-nacos</artifactId> </dependency> </dependencies> 三、Agent服务注册配置完成依赖引入后,需要将自定义的智能体交由Spring容器管理,同时配置Nacos注册参数,实现智能体的自动注册。1. 配置Bean对象本次以讲笑话的智能体jokeAgent为例@Configuration public class A2AAgentConfig { @Bean(name = "jokeAgent") public ReactAgent jokeAgent() { // 构建通义千问API对象 DashScopeApi dashScopeApi = DashScopeApi.builder() .apiKey(System.getenv("AliQwen_API")) .build(); // 初始化对话模型 DashScopeChatModel chatModel = DashScopeChatModel.builder() .dashScopeApi(dashScopeApi) .defaultOptions(DashScopeChatOptions.builder() .model(DashScopeChatModel.DEFAULT_MODEL_NAME) .temperature(0.5) .maxToken(1000) .build()) .build(); // 创建并返回笑话智能体 return ReactAgent.builder() .name("jokeAgent") .model(chatModel) .description("负责讲笑话。") .instruction("你是一个幽默风趣、反应敏捷的笑话智能体。你的任务是根据用户的要求,讲一个轻松、健康、积极向上的短笑话。") .build(); } }2. 修改application.yml配置文件spring: application: name: spring-ai-alibaba-a2a-server ai: dashscope: api-key: ${AliQwen_API} alibaba: a2a: nacos: server-addr: 127.0.0.1:8848 username: nacos password: nacos registry: enabled: true # 启用服务注册(注册本地 Agent) server: version: 1.0.0 card: name: jokeAgent # 必须与Bean名称一致 description: 专门讲笑话的智能体3. 注册成功校验启动项目后,控制台出现如下日志,即代表智能体成功注册到Nacos注册中心:Auto register agent jokeAgent into Registry Nacos[127.0.0.1:8848] successfully.配置成功后在Nacos Console上面可以看到智能体成功注册四、Agent远程调用完成服务端智能体注册后,客户端即可通过A2A协议远程调用注册好的智能体,实现分布式跨服务的智能体协作。1. 客户端依赖准备客户端同样需要添加上述的A2A依赖2. 远程调用代码编写客户端调用代码如下@RestController public class RemoteAgentController { @Resource private AgentCardProvider agentCardProvider; @GetMapping("test") public void test() throws GraphRunnerException { // 服务发现:通过AgentCardProvider 从注册中心获取Agent A2aRemoteAgent remoteAgent = A2aRemoteAgent.builder() .name("jokeAgent") .agentCardProvider(agentCardProvider) .description("可以给我讲笑话") .build(); Optional<OverAllState> result = remoteAgent.invoke("请给我讲一个关于小明的笑话"); result.ifPresent(state -> System.out.println(state.data().get("output"))); } }3. 调用效果展示哈哈,收到指令——已启动「快乐多巴胺发射器」,正在加载健康笑点模块…滴!加载完毕!😄 来一个新鲜出炉的短笑话: > 为什么瑜伽垫从不参加辩论赛? > ——因为它信奉:**“不争、不抢、但能稳稳地托住你的人生起伏!”** 🧘♂️✨ > (温馨提示:它还默默提醒你——深呼吸3秒,肩膀放松,嘴角上扬5度…这波健康buff,免费续杯!) 需要再来一个?或者想指定主题(比如职场、早餐、遛狗、和Wi-Fi斗智斗勇…)?我库存里还有“无糖版”“低卡路里版”“含维生素C的笑果”哦~ 😎总结以上就是借助Spring AI Alibaba框架搭配Nacos注册中心,基于A2A协议可以快速实现分布式智能体的服务注册与远程调用的代码啦。源码已上传GitHub:https://github.com/Jucunqi/spring-ai-alibaba-agent.git
2026年04月02日
15 阅读
0 评论
0 点赞
2026-03-30
吃透 Spring AI Alibaba 多智能体|四大协同模式+完整代码
当一个Agent要处理很多复杂的事情,就会出现效果不佳的情况,这时候Multi-Agent就可以将复杂任务进行拆分,更好的完成我们的任务。本文基于Spring AI Alibaba框架,详解Multi-Agent的核心概念、占位符用法,以及顺序执行、并行执行、路由调度、监督者管控四大核心模式,搭配完整可运行的代码示例,快速上手多智能体开发。一、Multi-Agent 核心概念官方对Multi-Agent的定义清晰直白:Multi-agent 将复杂的应用程序分解为多个协同工作的专业化Agent。与依赖单个Agent处理所有步骤不同,Multi-agent架构允许你将更小、更专注的Agent组合成协调的工作流。Multi-agent系统在以下情况下很有用:单个Agent拥有太多工具,难以做出正确的工具选择决策上下文或记忆增长过大,单个Agent难以有效跟踪任务需要专业化(例如:规划器、研究员、数学专家)二、指令占位符(Instruction)用法支持使用占位符来动态引用状态中的数据。比如AgentB需要使用AgentA处理后的数据,就需要通过Instruction来获取1、支持的占位符占位符说明使用场景{input}用户输入的原始内容第一个Agent或需要用户输入的 Agent{outputKey}引用其他Agent通过 outputKey 存储的输出顺序执行中,后续Agent引用前面Agent的输出{stateKey}引用状态中的任意键值访问状态中的任何数据2、占位符工作原理自动替换:系统会在执行 Agent 的 instruction 时,自动将占位符替换为对应的实际值状态查找:占位符会从当前状态(OverAllState)中查找对应的值类型安全:占位符的值会被转换为字符串并插入到 instruction 中三、公共代码(ChatModel获取)我先把获取ChatModel这个公共代码放在这里,后面就不重复写进来了默认模型qwen-pluspublic static ChatModel getChatModel() { DashScopeApi dashScopeApi = DashScopeApi.builder() .apiKey(System.getenv("AliQwen_API")) .build(); return DashScopeChatModel.builder() .dashScopeApi(dashScopeApi) .defaultOptions(DashScopeChatOptions.builder() // Note: model must be set when use options build. .model(DashScopeChatModel.DEFAULT_MODEL_NAME) .temperature(0.5) .maxToken(1000) .build()) .build(); }四、顺序执行(Sequential Agent)顾名思义,多个Agent按照顺序依次执行,上一个Agent的输出结果可以传递给下一个Agent1. 执行流程Agent A处理初始输入Agent A的输出传递给Agent BAgent B处理并传递给Agent C最后一个Agent返回最终结果2. 示例代码import com.alibaba.cloud.ai.dashscope.api.DashScopeApi; import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel; import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatOptions; import com.alibaba.cloud.ai.graph.NodeOutput; import com.alibaba.cloud.ai.graph.agent.ReactAgent; import com.alibaba.cloud.ai.graph.agent.flow.agent.SequentialAgent; import com.alibaba.cloud.ai.graph.exception.GraphRunnerException; import com.alibaba.cloud.ai.graph.streaming.StreamingOutput; import org.springframework.ai.chat.messages.AssistantMessage; import org.springframework.ai.chat.messages.Message; import org.springframework.ai.chat.model.ChatModel; import reactor.core.publisher.Flux; import java.io.IOException; import java.util.List; import java.util.Optional; import com.alibaba.cloud.ai.graph.OverAllState; public class SequentialAgentTest { public static void main(String[] args) throws GraphRunnerException, IOException { ChatModel chatModel = getChatModel(); // 创建专业化的子Agent ReactAgent writerAgent = ReactAgent.builder() .name("writer_agent") .model(chatModel) .description("专业写作Agent") .instruction("你是一个知名的作家,擅长写作和创作。请根据用户的提问进行回答:{input}。") .outputKey("article") .build(); ReactAgent translateAgent = ReactAgent.builder() .name("translate_agent") .model(chatModel) .description("专业翻译Agent") .instruction(""" 你是一个知名的翻译官,擅长对文章翻译。 待翻译文章: {article} 最终只返回翻译后的文章。""") .outputKey("reviewed_article") .build(); // 创建顺序Agent SequentialAgent blogAgent = SequentialAgent.builder() .name("blog_agent") .description("根据用户给定的主题写一篇文章,然后将文章交给翻译员进行翻译") .subAgents(List.of(writerAgent, translateAgent)) .build(); // 测试 Optional<OverAllState> result = blogAgent.invoke("帮我写一个100字左右的散文,不用输出字数"); if (result.isPresent()) { OverAllState state = result.get(); // 访问第一个Agent的输出 state.value("article").ifPresent(article -> { if (article instanceof AssistantMessage) { System.out.println("原始文章: " + ((AssistantMessage) article).getText()); } }); // 访问第二个Agent的输出 state.value("reviewed_article").ifPresent(reviewedArticle -> { if (reviewedArticle instanceof AssistantMessage) { System.out.println("翻译后文章: " + ((AssistantMessage) reviewedArticle).getText()); } }); } }3. 运行结果原始文章: 晨光初染窗棂,茶烟袅袅浮起,如一段未写完的旧信。院中老槐静立,枝桠间漏下碎金,在青砖上缓缓游移。风来,几片薄叶旋落,无声栖于石阶,像时光轻轻踮脚走过。邻家孩童追着纸鸢跑远,笑声清亮,撞碎一巷薄雾。我凝望这寻常片刻——原来人间至味,并非惊雷烈酒,而是晨光、微风、落叶与未凉的茶,在素常里酿出微光。 翻译后文章: Dawn’s first light gently tints the window frame; tea steam rises in delicate, lingering wisps—like an unfinished old letter. In the courtyard, an ancient locust tree stands still, its branches filtering golden shards of sunlight that slowly drift across the bluish-gray bricks. A breeze stirs: a few slender leaves spiral down, settling silently on the stone steps—as if time itself tiptoed past. A neighbor’s child chases a kite down the lane, laughter bright and clear, shattering the morning mist. Gazing upon this ordinary moment, I realize: life’s truest flavor lies not in thunderclaps or strong wine, but in dawn light, soft wind, falling leaves, and tea still warm—subtle radiance brewed quietly in the everyday.4. 关键特性按顺序执行:Agent按照 subAgents 列表中定义的顺序执行状态传递:每个Agent的输出通过 outputKey 存储在状态中,可被后续Agent访问消息历史:默认情况下,所有Agent共享消息历史推理内容控制:使用 returnReasoningContents 控制是否在消息历史中包含中间推理五、并行执行(Parallel Agent)当不考虑智能体的执行顺序时,可以使用Parallel Agent,多个智能体接收相同的输出进行处理,最后将结果汇总1. 执行流程输入同时发送给所有Agent所有Agent并行处理结果被合并成单一输出2. 示例代码public static void main(String[] args) throws GraphRunnerException { ChatModel chatModel = getChatModel(); // 创建多个专业化Agent ReactAgent proseWriterAgent = ReactAgent.builder() .name("prose_writer_agent") .model(chatModel) .description("专门写散文的AI助手") .instruction("你是一个知名的散文作家,擅长写优美的散文。" + "用户会给你一个主题:{input},你只需要创作一篇100字左右的散文。") .outputKey("prose_result") .build(); ReactAgent poemWriterAgent = ReactAgent.builder() .name("poem_writer_agent") .model(chatModel) .description("专门写现代诗的AI助手") .instruction("你是一个知名的现代诗人,擅长写现代诗。" + "用户会给你的主题是:{input},你只需要创作一首现代诗。") .outputKey("poem_result") .build(); ReactAgent summaryAgent = ReactAgent.builder() .name("summary_agent") .model(chatModel) .description("专门做内容总结的AI助手") .instruction("你是一个专业的内容分析师,擅长对主题进行总结和提炼。" + "用户会给你一个主题:{input},你只需要对这个主题进行简要总结。") .outputKey("summary_result") .build(); // 创建并行Agent ParallelAgent parallelAgent = ParallelAgent.builder() .name("parallel_creative_agent") .description("并行执行多个创作任务,包括写散文、写诗和做总结") .mergeOutputKey("merged_results") .subAgents(List.of(proseWriterAgent, poemWriterAgent, summaryAgent)) .mergeStrategy(new CustomMergeStrategy()) .build(); // 使用 Optional<OverAllState> result = parallelAgent.invoke("以'西湖'为主题"); if (result.isPresent()) { OverAllState state = result.get(); // 访问各个Agent的输出 state.value("prose_result").ifPresent(r -> System.out.println("散文: " + r)); state.value("poem_result").ifPresent(r -> System.out.println("诗歌: " + r)); state.value("summary_result").ifPresent(r -> System.out.println("总结: " + r)); // 访问合并后的结果 state.value("merged_results").ifPresent(r -> System.out.println("合并结果: " + r)); } }3. 自定义合并策略通过自定义合并策略来控制组合多个Agent的输出:public class CustomMergeStrategy implements ParallelAgent.MergeStrategy { @Override public Object merge(Map<String, Object> mergedState, OverAllState state) { // 从每个Agent的状态中提取输出 state.data().forEach((key, value) -> { // 检查key不为null且以"_result"结尾 if (key != null && key.endsWith("_result")) { String resultText = ""; if (value instanceof GraphResponse graphResponse) { if (graphResponse.resultValue().isPresent()) { HashMap messageMap = (HashMap) graphResponse.resultValue().get(); AssistantMessage assistantMessage = (AssistantMessage) messageMap.get(key); resultText = assistantMessage.getText(); } } else if (value != null) { resultText = value.toString(); } Object existing = mergedState.get("all_results"); if (existing == null) { mergedState.put("all_results", resultText); } else { mergedState.put("all_results", existing + "\n\n---\n\n" + resultText); } } }); return mergedState; } }4. 输出结果合并结果: {all_results=《曲径证词》 粉墙是未拆封的宣纸, 苔痕在青砖缝里写小楷—— 一笔,就洇开三百年雨。 假山不假:它用太湖石的嶙峋, 把整座江南叠进一拳掌中; 游鱼在漏窗格子里游成篆字, 而影子,在池底临摹另一座园林。 你数过九曲桥的弯吗? 每一道折,都把直路还给流水, 把时间还给回廊的弧度。 当夕光斜切过飞檐, 瓦上霜色忽然浮起—— 原来最深的幽静, 是光在转身时, 轻轻合拢的门。 (石阶微凉,而月光正从花窗进来, 替所有未出口的留白, 落款。) --- 苏州园林是中国古典私家园林的杰出代表,以“咫尺乾坤”的造园理念,融合诗、书、画、印等艺术形式,追求自然意趣与人文精神的统一。其典型特征包括精巧的布局(如借景、框景、对景)、丰富的建筑元素(亭、台、楼、阁、廊、舫)、写意的山水营造(旱园水做、石峰点景)、细腻的装饰工艺(花窗、铺地、彩绘、木雕)以及深厚的文化内涵(隐逸思想、文人审美)。代表园林有拙政园、留园、网师园、狮子林等,1997年被联合国教科文组织列入《世界遗产名录》。 --- 。。。。不粘贴出来了六、智能路由(LlmRoutingAgent)在路由模式中,使用大语言模型(LLM)动态决定将请求路由到哪个子Agent。这种模式非常适合需要智能选择不同专家Agent的场景。1.执行流程路由Agent接收用户输入LLM分析输入并决定最合适的子Agent选中的子Agent处理请求结果返回给用户2. 示例代码public static void main(String[] args) { ChatModel chatModel = getChatModel(); ReactAgent proseWriterAgent = ReactAgent.builder() .name("prose_writer_agent") .model(chatModel) .description("Can write prose articles.") .instruction("You are a renowned writer skilled in writing prose. Please respond to the following request: {input}") .outputKey("prose_article") .build(); ReactAgent poemWriterAgent = ReactAgent.builder() .name("poem_writer_agent") .model(chatModel) .description("Can write modern poetry.") .instruction("You are a famous poet skilled in modern poetry. Please use tools to respond to the following request: {input}") .outputKey("poem_article") .tools(List.of(createPoetToolCallback())) .build(); LlmRoutingAgent blogAgent = LlmRoutingAgent.builder() .name("blog_agent") .model(chatModel) .description("Can write articles or poems based on user-provided topics.") .subAgents(List.of(proseWriterAgent, poemWriterAgent)) .build(); try { GraphRepresentation representation = blogAgent.getGraph().getGraph(GraphRepresentation.Type.PLANTUML); System.out.println(representation.content()); Optional<OverAllState> result = blogAgent.invoke("帮我写一个100字左右的现代诗"); blogAgent.invoke("帮我写一个100字左右的现代诗"); Optional<OverAllState> result3 = blogAgent.invoke("帮我写一个100字左右的现代诗"); OverAllState state = result.get(); OverAllState state3 = result3.get(); AssistantMessage poemContent = (AssistantMessage) state.value("poem_article").get(); AssistantMessage poemContent3 = (AssistantMessage) state3.value("poem_article").get(); System.out.println(result.get()); System.out.println("------------------"); System.out.println(result3.get()); } catch (CompletionException | GraphRunnerException e) { e.printStackTrace(); } }3. 输出结果10:45:58.958 [main] INFO com.alibaba.cloud.ai.graph.agent.flow.node.RoutingNode -- RoutingAgent blog_agent routed to single sub-agent poem_writer_agent. Poet tool called : 100字左右的现代诗 10:46:03.509 [main] INFO com.alibaba.cloud.ai.graph.agent.flow.node.RoutingNode -- RoutingAgent blog_agent routed to single sub-agent poem_writer_agent. Poet tool called : 100字左右的现代诗 10:46:08.768 [main] INFO com.alibaba.cloud.ai.graph.agent.flow.node.RoutingNode -- RoutingAgent blog_agent routed to single sub-agent poem_writer_agent. Poet tool called : 帮我写一个100字左右的现代诗 {"OverAllState":{"data":{"_graph_execution_id_":"438452b3-d336-4581-99ac-690e56564841","input":"帮我写一个100字左右的现代诗","messages":[{"messageType":"USER","metadata":{"messageType":"USER"},"media":[],"text":"帮我写一个100字左右的现代诗"},{"messageType":"USER","metadata":{"messageType":"USER"},"rendered":false,"text":"You are a famous poet skilled in modern poetry. Please use tools to respond to the following request: {input}"},{"messageType":"ASSISTANT","metadata":{"search_info":"","role":"ASSISTANT","messageType":"ASSISTANT","finishReason":"STOP","id":"08c74cd3-a22f-476e-b9dd-52506b5642be","reasoningContent":""},"toolCalls":[],"media":[],"text":"在城市的缝隙里, \n一束光悄悄发芽, \n穿过钢筋水泥的沉默, \n在风中轻轻说话。 \n\n夜色如墨,却不再黑, \n星星点亮了每一个角落, \n我站在时间的边缘, \n等一朵云,轻轻落下。 \n\n——这是属于你的一首现代诗,简洁而有呼吸感,愿它在喧嚣中为你留出一片静默与微光。"}],"poem_article":{"messageType":"ASSISTANT","metadata":{"search_info":"","role":"ASSISTANT","messageType":"ASSISTANT","finishReason":"STOP","id":"08c74cd3-a22f-476e-b9dd-52506b5642be","reasoningContent":""},"toolCalls":[],"media":[],"text":"在城市的缝隙里, \n一束光悄悄发芽, \n穿过钢筋水泥的沉默, \n在风中轻轻说话。 \n\n夜色如墨,却不再黑, \n星星点亮了每一个角落, \n我站在时间的边缘, \n等一朵云,轻轻落下。 \n\n——这是属于你的一首现代诗,简洁而有呼吸感,愿它在喧嚣中为你留出一片静默与微光。"}}}} ------------------ 省略。。4. 自定义提示词LlmRoutingAgent支持使用SystemPrompt指定系统提示词,下面摘抄自官方示例:final String ROUTING_SYSTEM_PROMPT = """ 你是一个智能的内容路由Agent,负责根据用户需求将任务路由到最合适的专家Agent。 ## 你的职责 1. 仔细分析用户输入的意图和需求 2. 根据任务特性,选择最合适的专家Agent 3. 确保路由决策准确、高效 ## 可用的子Agent及其职责 ### writer_agent - **功能**: 擅长创作各类文章,包括散文、诗歌等文学作品 - **适用场景**: * 用户需要创作新文章、散文、诗歌等原创内容 * 简单的写作任务 - **输出**: writer_output ### reviewer_agent - **功能**: 擅长对文章进行评论、修改和润色 - **适用场景**: * 用户需要修改、评审或优化现有文章 * 需要提高文章质量 - **输出**: reviewer_output ### translator_agent - **功能**: 擅长将文章翻译成各种语言 - **适用场景**: * 用户需要将内容翻译成其他语言 * 多语言转换需求 - **输出**: translator_output ## 决策规则 1. **写作任务**: 如果用户需要创作新内容,选择 writer_agent 2. **修改任务**: 如果用户需要修改或优化现有内容,选择 reviewer_agent 3. **翻译任务**: 如果用户需要翻译内容,选择 translator_agent ## 响应格式 只返回Agent名称(writer_agent、reviewer_agent、translator_agent),不要包含其他解释。 """; LlmRoutingAgent routingAgent = LlmRoutingAgent.builder() .name("content_routing_agent") .description("根据用户需求智能路由到合适的专家Agent") .model(chatModel) .systemPrompt(ROUTING_SYSTEM_PROMPT) .subAgents(List.of(writerAgent, reviewerAgent, translatorAgent)) .build();七、监督者(SupervisorAgent)SupervisorAgent 是多智能体系统的 “大脑 / 协调者”,核心职责:接收用户请求,理解并拆解复杂任务决策调度:根据任务类型,选择最合适的子智能体(如写作、翻译、搜索等)循环执行:多次调用不同子智能体,完成多步骤任务结果整合:汇总子智能体输出,形成最终答案1. 执行流程监督者Agent接收用户输入或前序Agent的输出LLM分析当前状态并决定最合适的子Agent选中的子Agent处理任务子Agent执行完成后返回监督者监督者根据结果决定:继续路由到另一个子Agent(多步骤任务)返回 FINISH 完成任务2. 示例代码static final String SUPERVISOR_SYSTEM_PROMPT = """ 你是一个智能的内容管理监督者,负责协调和管理多个专业Agent来完成用户的内容处理需求。 ## 你的职责 1. 分析用户需求,将其分解为合适的子任务 2. 根据任务特性,选择合适的Agent进行处理 3. 监控任务执行状态,决定是否需要继续处理或完成任务 4. 当所有任务完成时,返回FINISH结束流程 ## 可用的子Agent及其职责 ### writer_agent - **功能**: 擅长创作各类文章,包括散文、诗歌等文学作品 - **适用场景**: * 用户需要创作新文章、散文、诗歌等原创内容 * 简单的写作任务,不需要后续评审或修改 - **输出**: writer_output ### translator_agent - **功能**: 擅长将文章翻译成各种语言 - **适用场景**: 当文章需要翻译成其他语言时 - **输出**: translator_output ## 决策规则 1. **单一任务判断**: - 如果用户只需要简单写作,选择 writer_agent - 如果用户需要翻译,选择 translator_agent 2. **多步骤任务处理**: - 如果用户需求包含多个步骤(如"先写文章,然后翻译"),需要分步处理 - 先路由到第一个合适的Agent,等待其完成 - 完成后,根据剩余需求继续路由到下一个Agent - 直到所有步骤完成,返回FINISH 3. **任务完成判断**: - 当用户的所有需求都已满足时,返回FINISH ## 响应格式 只返回Agent名称(writer_agent、translator_agent)或FINISH,不要包含其他解释。 """; public static void main(String[] args) throws GraphRunnerException, IOException { ChatModel chatModel = getChatModel(); // 创建专业化的子Agent ReactAgent writerAgent = ReactAgent.builder() .name("writer_agent") .model(chatModel) .description("擅长创作各类文章,包括散文、诗歌等文学作品") .outputKey("writer_output") .build(); ReactAgent translatorAgent = ReactAgent.builder() .name("translator_agent") .model(chatModel) .description("擅长将文章翻译成各种语言") .outputKey("translator_output") .build(); // 创建监督者Agent SupervisorAgent supervisorAgent = SupervisorAgent.builder() .name("content_supervisor") .description("内容管理监督者") .systemPrompt(SUPERVISOR_SYSTEM_PROMPT) .mainAgent(writerAgent) .model(chatModel) .subAgents(List.of(writerAgent, translatorAgent)) .build(); // 使用 - 监督者会根据任务自动路由并支持多步骤处理 Flux<NodeOutput> flux = supervisorAgent.stream("先帮我写一篇关于春天的短文,然后将文章翻译成英文"); flux.subscribe(chunk -> { if (chunk instanceof StreamingOutput<?> streamingOutput) { String newContent = ""; Message message = streamingOutput.message(); if (message instanceof AssistantMessage assistantMessage) { Object finishReason = message.getMetadata().get("finishReason"); if (finishReason == null) { newContent = assistantMessage.getText(); } if (finishReason != null && !finishReason.toString().equals("STOP")) { newContent = assistantMessage.getText(); } } System.out.println(newContent); } }); System.in.read(); }3. 输出结果11:06:21.373 [main] INFO com.alibaba.cloud.ai.graph.agent.flow.node.MainAgentNodeAction -- Invoking mainAgent 'writer_agent' compiled graph with threadId: Optional[subgraph_writer_agent] Agent output text: **中文短文:春天的信使** 当冬寒悄然退去,大地便轻轻舒展腰身——春天来了。 柳枝最先醒来,在微风中抽出嫩黄的新芽,像一串串小小的音符,轻轻摇曳在清冽的空气里。桃花、杏花、梨花次第绽放,粉的娇羞,白的澄澈,红的热烈,把山坡、小院、河岸染成流动的锦缎。泥土松软温润,蚯蚓在深处翻动,草尖顶开枯叶,怯生生地探出一点新绿。 清晨,鸟鸣清亮如洗;午后,阳光温柔似蜜,晒得人懒洋洋地想打个盹;傍晚,归燕掠过屋檐,衔来南方的暖意与旧年的故事。孩子们脱下厚衣,在田野里奔跑,纸鸢乘着东风扶摇直上,仿佛把整个童年的欢笑都放飞到了云朵之间。 春天从不喧哗,却用最细腻的笔触,把希望写进每一片叶脉、每一滴露珠、每一双仰望天空的眼睛里。它提醒我们:凋零不是终点,而是生命在静默中积蓄力量,只为一次更盛大的重逢。 --- **English Translation: The Herald of Spring** As winter’s chill quietly retreats, the earth gently stretches its limbs—spring has arrived. The willow branches are the first to awaken, sprouting tender yellow buds in the breeze—like tiny musical notes swaying softly in the crisp, clear air. Peach blossoms, apricot flowers, and pear blossoms bloom in gentle succession: pink ones blush with shy charm, white ones gleam with serene purity, and red ones blaze with quiet passion—painting hillsides, courtyards, and riverbanks with a flowing, living tapestry. The soil grows soft and warm; earthworms stir beneath the surface, while fresh green shoots push through last year’s dry leaves, timidly raising their heads. At dawn, birdsong rings out, clear and cleansing; at noon, sunlight pours down like golden honey—so warm and gentle it lulls one into drowsy contentment; by evening, swallows glide home across rooftops, carrying southern warmth and stories from seasons past. Children shed their heavy coats and race across open fields; kites soar high on the east wind, lifting childhood laughter all the way up into the clouds. Spring never shouts—it speaks instead in whispers, using the finest brushstroke to inscribe hope into every leaf vein, every dewdrop, and every pair of eyes gazing upward toward the sky. It reminds us: decay is never an end, but rather life’s quiet season of gathering strength—preparing, always, for a more magnificent reunion.八、多种Agent混合使用实际业务中,往往会将多种Agent模式组合使用,打造完整的工作流。以下以电商订单智能处理为例,融合并行查询、智能分析、路由处理、顺序执行,实现全自动售后处理。1. 示例代码public static void main(String[] args) throws GraphRunnerException { ChatModel chatModel = getChatModel(); // 1. 创建并行查询Agent(同时查用户信息 + 订单信息) ReactAgent userQueryAgent = ReactAgent.builder() .name("user_query") .model(chatModel) .description("查询用户信息、会员等级、历史行为") .instruction("根据用户ID查询用户信息:{input}") .outputKey("user_info") .build(); ReactAgent orderQueryAgent = ReactAgent.builder() .name("order_query") .model(chatModel) .description("查询订单状态、物流、商品信息") .instruction("根据订单号查询订单详细信息:{input}") .outputKey("order_info") .build(); // 并行执行:同时查用户+订单,大幅提速 ParallelAgent queryAgent = ParallelAgent.builder() .name("parallel_query") .description("并行查询用户信息与订单信息") .subAgents(List.of(userQueryAgent, orderQueryAgent)) .mergeOutputKey("query_data") .build(); // 2. 智能分析Agent(分析问题类型:退款/换货/补发/咨询) ReactAgent analysisAgent = ReactAgent.builder() .name("order_analysis") .model(chatModel) .description("分析用户问题、订单状态,给出处理建议") .instruction(""" 分析以下查询数据,判断用户需求类型: 1. 未发货仅退款 → 退款 2. 已收货质量问题 → 换货 3. 少发/漏发 → 补发 4. 其他 → 人工客服 数据:{query_data} """) .outputKey("analysis_result") .build(); // 3. 路由处理Agent(根据分析结果,自动选择处理方式) ReactAgent refundAgent = ReactAgent.builder() .name("refund_process") .model(chatModel) .description("自动生成退款方案") .instruction(""" 根据订单与分析结果生成退款处理方案: 订单信息:{order_info} 分析结果:{analysis_result} """) .outputKey("refund_solution") .build(); ReactAgent exchangeAgent = ReactAgent.builder() .name("exchange_process") .model(chatModel) .description("自动生成换货方案") .instruction(""" 根据订单与分析结果生成换货处理方案: 订单信息:{order_info} 分析结果:{analysis_result} """) .outputKey("exchange_solution") .build(); ReactAgent resendAgent = ReactAgent.builder() .name("resend_process") .model(chatModel) .description("自动生成补发方案") .instruction(""" 根据订单与分析结果生成补发处理方案: 订单信息:{order_info} 分析结果:{analysis_result} """) .outputKey("resend_solution") .build(); // LLM智能路由:自动选最合适的处理Agent LlmRoutingAgent processAgent = LlmRoutingAgent.builder() .name("process_router") .description("根据分析结果智能路由到对应处理流程") .model(chatModel) .subAgents(List.of(refundAgent, exchangeAgent, resendAgent)) .build(); // 4. 组合成完整工作流(顺序执行) SequentialAgent orderWorkflow = SequentialAgent.builder() .name("ecommerce_order_workflow") .description("电商订单智能处理全流程:并行查询 → 智能分析 → 路由处理") .subAgents(List.of(queryAgent, analysisAgent, processAgent)) .build(); // 使用:传入用户问题 + 订单号 Optional<OverAllState> result = orderWorkflow.invoke("订单号:123456,我要退款,商品还没发货"); System.out.println(result.get()); }2. 业务流程图九、总结Spring AI Alibaba的Multi-Agent架构,通过分工协作,旨在解决单个Agent的能力瓶颈。四种核心模式各有侧重,适配不同场景:顺序执行:适合流水线、有先后依赖的任务并行执行:适合无依赖、需提速的批量任务智能路由:适合任务类型多变、需智能分流的场景监督者模式:适合复杂长流程、多步骤协作任务
2026年03月30日
16 阅读
0 评论
1 点赞
1
2
...
11