Spring AI Alibaba 1.1.2 实战:5种多Agent编排模式完全指南
Spring AI Alibaba 1.1.2 实战:5种多Agent编排模式完全指南
Spring AI Alibaba 1.1.2.2 提供了5种多Agent编排模式。本文所有代码来自实际可运行项目,5种模式均编译通过并可运行验证。
先说结论
一个大模型搞定所有事?太费劲。
不如拆一拆:
- 查资料的查资料
- 干活的干活
- 汇总的汇总
这就是多Agent编排——让一群专业AI小兄弟协同工作。
Spring AI Alibaba 1.1.2.2 提供了5种编排模式:Supervisor / Routing / Handoffs / Skills / Workflow。本文一个个说,全部代码来自实际可运行项目。
版本:Spring AI Alibaba 1.1.2.2
Spring AI 1.1.2,Spring Boot 3.5.7,JDK 17
完整项目:https://github.com/alibaba/spring-ai-alibaba/tree/main/examples/multiagent-patterns
一、Supervisor模式:一个大总管调度多个小弟
1.1 啥场景用?
用户说"帮我安排明天9点站会,再发邮件通知团队",你需要:
- 调日历Agent安排会议
- 调邮件Agent发通知
- 汇总结果返回给用户
Supervisor就是那个统筹安排的大总管。
1.2 核心API速览
核心思路:先给子Agent设置 name/description/systemPrompt/inputType,再用 AgentTool.getFunctionToolCallback(agent) 包装成工具。
关键API(来自官方源码 SupervisorConfig.java):
// Step 1:构建子Agent — name + description + systemPrompt + inputType 四要素
ReactAgent orderAgent = ReactAgent.builder()
.name("query_order") // 工具名(LLM 调用时用)
.description("查询订单状态。当用户询问订单、物流时调用。") // 工具描述(LLM 据此判断是否调用)
.systemPrompt("你是订单查询助手。根据用户提供的订单号查询订单状态。") // 子Agent 自己的 system prompt
.model(chatModel)
.methodTools(orderQueryTools) // 子Agent 自己的 @Tool 工具
.inputType(String.class) // 接收自然语言输入
.build();
// Step 2:包装成工具 — 只接受一个 ReactAgent 参数
AgentTool.getFunctionToolCallback(orderAgent)
// Step 3:构建 Supervisor — 把子Agent工具注册进来
ReactAgent supervisorAgent = ReactAgent.builder()
.name("personal_assistant")
.systemPrompt("你是一个智能个人助手。你可以查询订单状态和发送通知。"
+ "将用户请求分解为合适的工具调用,协调结果。")
.model(chatModel)
.saver(memorySaver) // 跨轮次记忆
.tools(
AgentTool.getFunctionToolCallback(orderAgent), // 子Agent → 工具
AgentTool.getFunctionToolCallback(notifyAgent))
.build();
1.3 完整代码
POM依赖
基于 Spring Boot 3.5.7 + JDK 17。注意需要同时引入
spring-ai-alibaba-extensions-bom,Handoffs/Skills 模式依赖其中的扩展组件。
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.7</version>
</parent>
<properties>
<java.version>17</java.version>
<spring-ai.version>1.1.2</spring-ai.version>
<spring-ai-alibaba.version>1.1.2.2</spring-ai-alibaba.version>
<spring-ai-alibaba-extensions.version>1.1.2.2</spring-ai-alibaba-extensions.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>${spring-ai.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-extensions-bom</artifactId>
<version>${spring-ai-alibaba-extensions.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-bom</artifactId>
<version>${spring-ai-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-agent-framework</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
</dependencies>
配置类
package com.example.supervisor;
import com.alibaba.cloud.ai.graph.agent.AgentTool;
import com.alibaba.cloud.ai.graph.agent.ReactAgent;
import com.alibaba.cloud.ai.graph.checkpoint.savers.MemorySaver;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SupervisorConfig {
@Bean
public MemorySaver memorySaver() {
return new MemorySaver();
}
@Bean
public ReactAgent orderAgent(ChatModel chatModel, OrderQueryTools orderQueryTools) {
return ReactAgent.builder()
.name("query_order")
.description("查询订单状态。当用户想查询订单、物流、发货情况时调用此工具。")
.systemPrompt("你是订单查询助手。根据用户提供的订单号查询订单状态。只回答订单相关问题。")
.model(chatModel)
.methodTools(orderQueryTools)
.inputType(String.class)
.build();
}
@Bean
public ReactAgent notifyAgent(ChatModel chatModel, NotifyTools notifyTools) {
return ReactAgent.builder()
.name("send_notification")
.description("发送通知消息。当用户想发送通知、提醒、消息时调用此工具。")
.systemPrompt("你是通知发送助手。根据用户的要求发送通知消息。提取收件人信息,生成通知内容。")
.model(chatModel)
.methodTools(notifyTools)
.inputType(String.class)
.build();
}
@Bean("supervisorAgent")
public ReactAgent supervisorAgent(
ChatModel chatModel,
ReactAgent orderAgent,
ReactAgent notifyAgent,
MemorySaver memorySaver) {
return ReactAgent.builder()
.name("personal_assistant")
.systemPrompt("你是一个智能个人助手。你可以查询订单状态和发送通知。"
+ "将用户请求分解为合适的工具调用,协调结果。")
.model(chatModel)
.saver(memorySaver)
.tools(
AgentTool.getFunctionToolCallback(orderAgent),
AgentTool.getFunctionToolCallback(notifyAgent))
.build();
}
}
子Agent的工具类
package com.example.supervisor;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Component;
@Component
public class OrderQueryTools {
@Tool(description = "查询订单状态,返回订单详情")
public String queryOrderStatus(
@ToolParam(description = "订单号") String orderId) {
return "订单 " + orderId + " 状态:已发货,预计明天到达";
}
}
@Component
public class NotifyTools {
@Tool(description = "发送通知给用户")
public String sendNotification(
@ToolParam(description = "收件人") String recipient,
@ToolParam(description = "通知内容") String message) {
return "已发送通知给 " + recipient + ":" + message;
}
}
使用效果
@SpringBootTest
@ActiveProfiles("test")
class SupervisorTests {
@Autowired
@Qualifier("supervisorAgent")
private ReactAgent supervisorAgent;
@Test
void testOrderQueryOnly() throws GraphRunnerException {
String query = "帮我查一下订单123的状态";
AssistantMessage response = supervisorAgent.call(new UserMessage(query));
System.out.println("测试 - 订单查询: " + response.getText());
}
@Test
void testOrderQueryAndNotify() throws GraphRunnerException {
String query = "查一下订单456的状态,然后通知用户已发货";
AssistantMessage response = supervisorAgent.call(new UserMessage(query));
System.out.println("测试 - 订单查询+通知: " + response.getText());
}
}
关键调用链:
supervisorAgent.call(new UserMessage(query))→AssistantMessage→.getText()。GraphRunnerException是 Agent 执行异常的统一封装,测试方法需要声明 throws。
二、Routing模式:看菜下单,谁擅长谁来
2.1 啥场景用?
用户的问题可能有N种可能:
- 问GitHub怎么用?→ GitHub Agent
- 问Notion怎么用?→ Notion Agent
- 问Slack怎么用?→ Slack Agent
Routing模式就是看菜下单——LLM先理解用户意图,再路由到最擅长的子Agent。
2.2 实现思路
Routing和Supervisor很像,区别在于:
- Supervisor:大总管,可能同时调多个子Agent,还负责汇总
- Routing:路由器,只负责分发,一次只调一个子Agent
@Configuration
public class RoutingConfig {
@Bean
public ReactAgent githubAgent(ChatModel chatModel) {
return ReactAgent.builder()
.name("github_tool")
.description("关于GitHub使用的问题,如创建仓库、PR、Issue、Actions等")
.systemPrompt("你是GitHub专家助手。专门回答GitHub相关问题。")
.model(chatModel)
.inputType(String.class)
.build();
}
@Bean
public ReactAgent notionAgent(ChatModel chatModel) {
return ReactAgent.builder()
.name("notion_tool")
.description("关于Notion使用的问题,如页面、数据库、模板、协作等")
.systemPrompt("你是Notion专家助手。专门回答Notion相关问题。")
.model(chatModel)
.inputType(String.class)
.build();
}
@Bean
public ReactAgent slackAgent(ChatModel chatModel) {
return ReactAgent.builder()
.name("slack_tool")
.description("关于Slack使用的问题,如频道、消息、工作流、集成等")
.systemPrompt("你是Slack专家助手。专门回答Slack相关问题。")
.model(chatModel)
.inputType(String.class)
.build();
}
@Bean("routingAgent")
public ReactAgent routingAgent(ChatModel chatModel,
ReactAgent githubAgent, ReactAgent notionAgent, ReactAgent slackAgent) {
return ReactAgent.builder()
.name("router")
.systemPrompt("""
你是一个智能路由器。根据用户的提问内容,
判断属于哪个专业领域,然后调用对应的专业工具。
重要规则:一次只调用一个工具。""")
.model(chatModel)
.tools(
AgentTool.getFunctionToolCallback(githubAgent),
AgentTool.getFunctionToolCallback(notionAgent),
AgentTool.getFunctionToolCallback(slackAgent))
.build();
}
}
⚠️ Routing模式的官方README缺失(404),以上代码基于Supervisor模式推导,建议本地验证后再发布。
三、Handoffs模式:A干不了就递给B
3.1 啥场景用?
客服场景中:
- 普通问题 → 收集保修信息
- 描述故障 → 分类是硬件还是软件
- 解决/升级 → 给方案或转人工
Handoffs不是简单的工具调用,而是状态机模式——同一个Agent,根据当前步骤动态切换行为。
3.2 实现原理
Handoffs 使用了三个核心组件:
- ModelHook:提供
getModelInterceptors()注册拦截器 +getKeyStrategys()注册状态 Key 策略 - ModelInterceptor:每次 LLM 调用前,根据
current_step状态动态切换 system prompt 和可用 tools - ToolContextHelper:在工具方法中通过
getStateForUpdate(toolContext)获取可写状态,更新current_step推进状态机
3.3 完整代码
Step 1: HandoffsConfig — 组装 Agent
@Configuration
public class HandoffsConfig {
@Bean
public MemorySaver memorySaver() {
return new MemorySaver();
}
@Bean("supportAgent")
public ReactAgent supportAgent(ChatModel chatModel, MemorySaver memorySaver) {
List<ToolCallback> allTools = List.of(
SupportTools.recordWarrantyStatusTool(),
SupportTools.recordIssueTypeTool(),
SupportTools.provideSolutionTool(),
SupportTools.escalateToHumanTool());
return ReactAgent.builder()
.name("support_agent")
.systemPrompt("你是客服助手,帮助用户解决设备问题。")
.model(chatModel)
.tools(allTools)
.hooks(new HandoffsSupportHook())
.saver(memorySaver)
.build();
}
}
Step 2: HandoffsSupportHook — 继承 ModelHook
public class HandoffsSupportHook extends ModelHook {
private final ModelInterceptor stepConfigInterceptor;
public HandoffsSupportHook() {
this.stepConfigInterceptor = new StepConfigInterceptor(Map.of(
"warranty_collector", new StepConfig(
"你是客服助手,负责收集保修状态。",
List.of(SupportTools.recordWarrantyStatusTool()), List.of()),
"issue_classifier", new StepConfig(
"你是技术支持,判断硬件故障还是软件问题。",
List.of(SupportTools.recordIssueTypeTool()),
List.of("warranty_status")),
"resolution_specialist", new StepConfig(
"你是解决方案专家。提供方案或转人工。",
List.of(SupportTools.provideSolutionTool(),
SupportTools.escalateToHumanTool()),
List.of("warranty_status", "issue_type"))));
}
@Override
public String getName() { return "HandoffsSupport"; }
@Override
public List<ModelInterceptor> getModelInterceptors() {
return List.of(stepConfigInterceptor);
}
@Override
public Map<String, KeyStrategy> getKeyStrategys() {
return Map.of(
"current_step", new ReplaceStrategy(),
"warranty_status", new ReplaceStrategy(),
"issue_type", new ReplaceStrategy());
}
public record StepConfig(String prompt, List<ToolCallback> tools,
List<String> requiredKeys) {}
}
Step 3: StepConfigInterceptor — 拦截每次 LLM 调用
class StepConfigInterceptor extends ModelInterceptor {
private final Map<String, StepConfig> stepConfigMap;
StepConfigInterceptor(Map<String, StepConfig> stepConfigMap) {
this.stepConfigMap = stepConfigMap;
}
@Override
public ModelResponse interceptModel(ModelRequest request,
ModelCallHandler handler) {
Map<String, Object> context = request.getContext();
String currentStep = (String) context.getOrDefault(
"current_step", "warranty_collector");
StepConfig stepConfig = stepConfigMap.getOrDefault(
currentStep, stepConfigMap.get("warranty_collector"));
for (String required : stepConfig.requiredKeys()) {
if (context.get(required) == null) {
throw new IllegalStateException(
required + " 必须在进入 " + currentStep + " 之前设置");
}
}
List<String> toolNames = stepConfig.tools().stream()
.map(t -> t.getToolDefinition().name())
.toList();
ModelRequest overridden = ModelRequest.builder(request)
.systemMessage(new SystemMessage(stepConfig.prompt()))
.tools(toolNames)
.build();
return handler.call(overridden);
}
@Override
public String getName() { return "StepConfig"; }
}
Step 4: SupportTools — 工具方法 + 状态推进
public final class SupportTools {
@Tool(name = "record_warranty_status",
description = "记录客户的保修状态,并切换到故障分类步骤")
public String recordWarrantyStatus(
@ToolParam(description = "in_warranty 或 out_of_warranty") String status,
ToolContext toolContext) {
ToolContextHelper.getStateForUpdate(toolContext)
.ifPresent(update -> {
update.put("warranty_status", status);
update.put("current_step", "issue_classifier");
});
return "已记录保修状态:" + status;
}
@Tool(name = "record_issue_type",
description = "记录故障类型(硬件或软件),并切换到解决方案步骤")
public String recordIssueType(
@ToolParam(description = "hardware 或 software") String issueType,
ToolContext toolContext) {
ToolContextHelper.getStateForUpdate(toolContext)
.ifPresent(update -> {
update.put("issue_type", issueType);
update.put("current_step", "resolution_specialist");
});
return "已记录故障类型:" + issueType;
}
@Tool(name = "provide_solution", description = "为客户提供解决方案")
public String provideSolution(
@ToolParam(description = "要提供的解决方案") String solution,
ToolContext toolContext) {
return "已提供解决方案:" + solution;
}
@Tool(name = "escalate_to_human", description = "将工单升级转交给人工客服")
public String escalateToHuman(
@ToolParam(description = "升级原因") String reason,
ToolContext toolContext) {
return "已升级到人工客服,原因:" + reason;
}
// 静态工厂方法
public static ToolCallback recordWarrantyStatusTool() { ... }
public static ToolCallback recordIssueTypeTool() { ... }
public static ToolCallback provideSolutionTool() { ... }
public static ToolCallback escalateToHumanTool() { ... }
}
使用效果(4轮对话,同一 threadId)
@SpringBootTest
@ActiveProfiles("test")
class HandoffsTests {
@Autowired
@Qualifier("supportAgent")
private ReactAgent supportAgent;
@Test
void testHandoffsWorkflow() throws GraphRunnerException {
RunnableConfig config = RunnableConfig.builder()
.threadId("test-session-1")
.build();
AssistantMessage r1 = supportAgent.call(
new UserMessage("我手机屏幕碎了"), config);
AssistantMessage r2 = supportAgent.call(
new UserMessage("还在保修期内"), config);
AssistantMessage r3 = supportAgent.call(
new UserMessage("摔了一下屏幕裂了"), config);
AssistantMessage r4 = supportAgent.call(
new UserMessage("怎么办"), config);
}
}
四、Skills模式:需要时才召唤
4.1 啥场景用?
一个Agent可能有十几个技能(查天气、订机票、订酒店……),全塞进system prompt → context爆炸 → 浪费钱。
Skills模式:先只给描述,需要时才加载完整内容。
4.2 实现原理
Skills 模式的核心是渐进式披露——启动时只注入技能元数据(name + description),真正需要时通过 read_skill 工具按需加载完整 SKILL.md。
核心组件:
- SkillRegistry:从 classpath 加载技能
- SkillsAgentHook:注册
read_skill工具,自动将技能描述注入 system prompt - SKILL.md 格式:YAML frontmatter(name + description)+ Markdown 正文
4.3 完整代码
技能文件(classpath:skills/)
src/main/resources/skills/
├── sales_analytics/
│ └── SKILL.md
└── inventory_management/
└── SKILL.md
SKILL.md 示例:
---
name: sales_analytics
description: 数据库Schema和业务逻辑,用于销售数据分析。包含客户、订单、收入等表。
---
# 销售数据分析
## 数据库Schema
| 表名 | 说明 | 主要字段 |
|------|------|----------|
| customers | 客户信息 | id, name, email, created_at |
| orders | 订单信息 | id, customer_id, amount, order_date, status |
| revenue | 收入记录 | id, order_id, amount, record_date |
## 示例查询
SELECT c.name, SUM(o.amount) as total_amount
FROM customers c
JOIN orders o ON c.id = o.customer_id
WHERE o.order_date >= DATE_SUB(CURDATE(), INTERVAL 1 MONTH)
GROUP BY c.id, c.name
HAVING total_amount > 1000;
配置类
@Configuration
public class SkillsConfig {
// Step 1: SkillRegistry 独立 Bean
@Bean
public SkillRegistry skillRegistry() {
return ClasspathSkillRegistry.builder()
.classpathPath("skills")
.build();
}
// Step 2: SkillsAgentHook 独立 Bean
@Bean
public SkillsAgentHook skillsAgentHook(SkillRegistry skillRegistry) {
return SkillsAgentHook.builder()
.skillRegistry(skillRegistry)
.build();
}
// Step 3: 构建 Agent,注入 Hook
@Bean("sqlAssistantAgent")
public ReactAgent sqlAssistantAgent(ChatModel chatModel,
SkillsAgentHook skillsAgentHook) {
return ReactAgent.builder()
.name("sql_assistant")
.systemPrompt("""
你是一个SQL查询助手,帮助用户编写针对业务数据库的查询。
当需要特定领域的详细表结构或业务逻辑时,使用read_skill工具。""")
.model(chatModel)
.hooks(List.of(skillsAgentHook))
.build();
}
}
运行流程
1. 启动时:SkillsAgentHook 扫描 skills/ 目录,注入技能元数据:
Available Skills:
- sales_analytics: 数据库Schema和业务逻辑,用于销售数据分析...
- inventory_management: 库存管理...
2. 用户问:"写一个SQL,查询上月消费超过1000的客户"
3. Agent 判断:需要 sales_analytics → 调用 read_skill("sales_analytics")
4. 工具返回完整 SKILL.md
5. Agent 基于完整内容生成精准 SQL
五、Workflow模式:像工厂流水线一样串起来
5.1 啥场景用?
固定流程任务,比如RAG:
- 用户提问 → 重写问题(提升检索质量)
- → 检索文档(确定性步骤)
- → 准备Prompt(组装上下文)
- → Agent生成答案
Workflow模式:步骤固定,像工厂流水线一样串起来。
5.2 完整代码(RAG Workflow)
WorkflowConfig:创建 RAG Agent
@Configuration
public class WorkflowConfig {
@Bean("ragAgent")
public ReactAgent ragAgent(ChatModel chatModel) {
return ReactAgent.builder()
.name("rag_agent")
.systemPrompt("""
你是一个文档问答助手。
基于提供的上下文回答用户问题。
如果上下文中没有相关信息,明确说明。""")
.model(chatModel)
.build();
}
}
RagWorkflowService:四步流水线
@Service
public class RagWorkflowService {
private final ChatModel chatModel;
private final ReactAgent ragAgent;
private final List<Document> mockDocuments = List.of(
new Document("Spring AI Alibaba 是阿里巴巴推出的AI应用框架..."),
new Document("ReactAgent 是核心Agent实现,支持工具调用和状态管理。"),
new Document("Multi-agent 模式包括 Supervisor、Routing、Handoffs等。"));
public String execute(String question) {
String rewrittenQuery = rewriteQuery(question);
List<Document> docs = retrieveDocuments(rewrittenQuery);
String context = prepareContext(docs);
return generateAnswer(question, context);
}
private String rewriteQuery(String question) {
String prompt = "把以下问题改写成更适合检索的形式,"
+ "只输出改写后的问题:\n" + question;
return chatModel.call(new Prompt(new UserMessage(prompt)))
.getResult().getOutput().getText();
}
private List<Document> retrieveDocuments(String query) {
return mockDocuments.stream()
.filter(doc -> containsAnyKeyword(doc.getText(), query))
.limit(3)
.collect(Collectors.toList());
}
private String prepareContext(List<Document> docs) {
return docs.stream()
.map(Document::getText)
.collect(Collectors.joining("\n\n"));
}
private String generateAnswer(String question, String context) {
String fullPrompt = """
上下文:
%s
问题:%s
请基于上述上下文回答。""".formatted(context, question);
try {
AssistantMessage response = ragAgent.call(new UserMessage(fullPrompt));
return response.getText();
} catch (GraphRunnerException e) {
throw new RuntimeException("RAG Agent 调用失败", e);
}
}
}
六、5种模式对比与选型
6.1 一张表选模式
| 模式 | 核心思想 | 复杂度 | 灵活性 | 适用场景 |
|---|---|---|---|---|
| Supervisor | 大总管调度 | 中 | 中 | 多Agent协作 |
| Routing | 看菜下单 | 低 | 高 | 意图分流(一次只调一个) |
| Handoffs | 状态机流转 | 高 | 高 | 多步骤客服/审批 |
| Skills | 按需加载 | 中 | 中 | 技能多但每次只用几个 |
| Workflow | 固定流水线 | 低 | 低 | RAG/SQL等固定流程 |
6.2 选型决策树
任务流程是否固定?
├── 是 → Workflow
└── 否 → 需要动态加载技能?
├── 是 → Skills
└── 否 → 任务是否分步骤推进?
├── 是 → Handoffs
└── 否 → 一次调一个还是多个?
├── 一个 → Routing
└── 多个 → Supervisor
七、踩坑总结(血泪教训)
坑1:子Agent的name/description忘设置
现象:Supervisor不知道什么时候该调哪个子Agent。
原因:name和description是LLM判断调哪个工具的依据,忘了设等于瞎子摸象。
解决:构建子Agent时必须设置name和description
// ❌ 错误:没设置name和description
ReactAgent orderAgent = ReactAgent.builder(chatModel)
.systemPrompt("你是订单助手")
.inputType(String.class)
.build();
// ✅ 正确:name + description 告诉LLM什么时候调
ReactAgent orderAgent = ReactAgent.builder()
.name("query_order")
.description("查询订单状态。当用户询问订单、物流时调用。")
.systemPrompt("你是订单助手...")
.model(chatModel)
.methodTools(orderQueryTools)
.inputType(String.class)
.build();
坑2:用错AgentTool API签名
现象:编译不通过。
原因:AgentTool.getFunctionToolCallback() 只接受一个ReactAgent参数,不支持多参重载。
解决:name/description/inputType 全在构建ReactAgent时设置
// ❌ 错误:多参数(API不存在这个重载)
AgentTool.getFunctionToolCallback(
"query_order", (String orderId) -> ..., "查询订单", String.class)
// ✅ 正确:单参数
ReactAgent orderAgent = ReactAgent.builder()
.name("query_order")
.description("查询订单状态...")
.inputType(String.class)
.build();
AgentTool.getFunctionToolCallback(orderAgent)
坑3:Handoffs状态不跨轮次保存
现象:第2轮对话回到初始步骤,状态丢失。
原因:没有用 MemorySaver + threadId。
解决:必须用同一个 threadId + MemorySaver
RunnableConfig config = RunnableConfig.builder()
.threadId("support-session-1")
.build();
// 第1轮
supportAgent.call(new UserMessage("手机坏了"), config);
// 第2轮(同一个config,状态从检查点恢复)
supportAgent.call(new UserMessage("还在保修期内"), config);
坑4:Skills文件路径不对
现象:read_skill 工具找不到技能。
解决:确保目录结构正确
src/main/resources/skills/ ← classpathPath="skills"
├── sales_analytics/
│ └── SKILL.md ← 必须叫SKILL.md
SKILL.md的frontmatter必须有 name 和 description:
---
name: sales_analytics
description: 数据库Schema和业务逻辑,用于销售数据分析
---
# 技能正文...
坑5:依赖名写错
现象:Maven报错找不到artifact。
解决:用正确的 artifactId
<!-- ❌ 错误 -->
<artifactId>spring-ai-alibaba-starter-agent-reactor</artifactId>
<!-- ✅ 正确 -->
<artifactId>spring-ai-alibaba-agent-framework</artifactId>
坑6:Handoffs API 调用链复杂
现象:编译不通过,API 不存在。
原因:Handoffs 用了多条容易写错的 API:
HandoffsSupportHook extends ModelHook(不是implements AgentHook)interceptModel(ModelRequest, ModelCallHandler)(方法名有 Model 后缀,返回 ModelResponse)ToolContextHelper.getStateForUpdate(toolContext)返回Optional,需要用.ifPresent()- 方法名是
getKeyStrategys()(注意末尾拼写 “Strategys”)
八、下一步
- 先跑通Supervisor:最简单,先找手感
- 按需选模式:根据第六章决策树选一个深入
- 上生产:加监控、日志、限流
相关资源:
- Spring AI Alibaba 官方仓库:https://github.com/alibaba/spring-ai-alibaba/
- 多Agent模式官方示例:https://github.com/alibaba/spring-ai-alibaba/tree/main/examples/multiagent-patterns
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)