Spring AI Alibaba 1.1.2 实战:5种多Agent编排模式完全指南

Spring AI Alibaba 1.1.2 实战:5种多Agent编排模式完全指南

先说结论

一个大模型搞定所有事?太费劲。

不如拆一拆:

  • 查资料的查资料
  • 干活的干活
  • 汇总的汇总

这就是多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


一、Supervisor模式:一个大总管调度多个小弟

1.1 啥场景用?

用户说"帮我安排明天9点站会,再发邮件通知团队",你需要:

  1. 调日历Agent安排会议
  2. 调邮件Agent发通知
  3. 汇总结果返回给用户

Supervisor就是那个统筹安排的大总管

1.2 核心API速览

核心思路:先给子Agent设置 name/description/systemPrompt/inputType,再用 AgentTool.getFunctionToolCallback(agent) 包装成工具

关键API(来自官方源码 SupervisorConfig.java):

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 模式依赖其中的扩展组件。

xml 复制代码
<properties>
    <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>

<dependencies>
    <dependency>
        <groupId>com.alibaba.cloud.ai</groupId>
        <artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
    </dependency>
    <!-- 关键:Agent框架,不是 starter-agent-reactor -->
    <dependency>
        <groupId>com.alibaba.cloud.ai</groupId>
        <artifactId>spring-ai-alibaba-agent-framework</artifactId>
    </dependency>
</dependencies>
配置类
java 复制代码
@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的工具类
java 复制代码
@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;
    }
}
使用效果
java 复制代码
@Test
void testSupervisor() throws GraphRunnerException {
    String query = "查一下订单456的状态,然后通知用户已发货";
    AssistantMessage response = supervisorAgent.call(new UserMessage(query));
    System.out.println(response.getText());
}

关键调用链:supervisorAgent.call(new UserMessage(query))AssistantMessage.getText()

代码来源:github.com/alibaba/spr...


二、Routing模式:看菜下单,谁擅长谁来

2.1 啥场景用?

用户的问题可能有N种可能:

  • 问GitHub怎么用?→ GitHub Agent
  • 问Notion怎么用?→ Notion Agent
  • 问Slack怎么用?→ Slack Agent

Routing模式就是看菜下单------LLM先理解用户意图,再路由到最擅长的子Agent。

2.2 实现思路

Routing和Supervisor很像,区别在于:

  • Supervisor:大总管,可能同时调多个子Agent
  • Routing:路由器,只负责分发,一次只调一个子Agent
java 复制代码
@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 实现原理

三个核心组件:

  1. ModelHook:注册拦截器和状态 Key 策略
  2. ModelInterceptor :每次 LLM 调用前,根据 current_step 状态动态切换 system prompt 和 tools
  3. ToolContextHelper :在工具方法中通过 getStateForUpdate(toolContext) 更新 current_step 推进状态机

3.3 完整代码

Step 1: HandoffsConfig

java 复制代码
@Configuration
public class HandoffsConfig {

    @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

java 复制代码
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 List<ModelInterceptor> getModelInterceptors() {
        return List.of(stepConfigInterceptor);
    }

    @Override
    public Map<String, KeyStrategy> getKeyStrategys() {  // 注意拼写:Strategys
        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

java 复制代码
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);  // 必须调用 handler 继续链路
    }
}

Step 4: SupportTools

java 复制代码
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 -> {          // 返回 Optional,必须用 ifPresent
                    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;
    }

    // 其他工具...
}
使用效果(4轮对话,同一 threadId)
java 复制代码
@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

核心组件:

  1. SkillRegistry:从 classpath 加载技能
  2. SkillsAgentHook :注册 read_skill 工具,自动将技能描述注入 system prompt
  3. SKILL.md 格式:YAML frontmatter(name + description)+ Markdown 正文

4.3 完整代码

技能文件(classpath:skills/)
css 复制代码
src/main/resources/skills/
├── sales_analytics/
│   └── SKILL.md
└── inventory_management/
    └── SKILL.md

SKILL.md 示例:

markdown 复制代码
---
name: sales_analytics
description: 数据库Schema和业务逻辑,用于销售数据分析。包含客户、订单、收入等表。
---

# 销售数据分析

## 数据库Schema

| 表名 | 说明 | 主要字段 |
|------|------|----------|
| customers | 客户信息 | id, name, email |
| orders | 订单信息 | id, customer_id, amount |
| revenue | 收入记录 | id, order_id, amount |

## 示例查询

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;
配置类
java 复制代码
@Configuration
public class SkillsConfig {

    @Bean
    public SkillRegistry skillRegistry() {
        return ClasspathSkillRegistry.builder()
                .classpathPath("skills")
                .build();
    }

    @Bean
    public SkillsAgentHook skillsAgentHook(SkillRegistry skillRegistry) {
        return SkillsAgentHook.builder()
                .skillRegistry(skillRegistry)
                .build();
    }

    @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();
    }
}
运行流程
markdown 复制代码
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:

  1. 用户提问 → 重写问题(提升检索质量)
  2. → 检索文档(确定性步骤)
  3. → 准备Prompt(组装上下文)
  4. → Agent生成答案

Workflow模式:步骤固定,像工厂流水线一样串起来

5.2 完整代码(RAG Workflow)

java 复制代码
@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 选型决策树

markdown 复制代码
任务流程是否固定?
├── 是 → Workflow
└── 否 → 需要动态加载技能?
    ├── 是 → Skills
    └── 否 → 任务是否分步骤推进?
        ├── 是 → Handoffs
        └── 否 → 一次调一个还是多个?
            ├── 一个 → Routing
            └── 多个 → Supervisor

七、踩坑总结(血泪教训)

坑1:子Agent的name/description忘设置

现象:Supervisor不知道什么时候该调哪个子Agent。

解决:构建子Agent时必须设置name和description

java 复制代码
// ❌ 错误:没设置name和description
ReactAgent orderAgent = ReactAgent.builder(chatModel)
    .systemPrompt("你是订单助手")
    .inputType(String.class)
    .build();

// ✅ 正确
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时设置

java 复制代码
// ✅ 正确
ReactAgent orderAgent = ReactAgent.builder()
    .name("query_order")
    .description("查询订单状态...")
    .inputType(String.class)
    .build();
AgentTool.getFunctionToolCallback(orderAgent)

坑3:Handoffs状态不跨轮次保存

现象:第2轮对话回到初始步骤,状态丢失。

解决 :必须用同一个 threadId + MemorySaver

java 复制代码
RunnableConfig config = RunnableConfig.builder()
    .threadId("support-session-1")
    .build();

supportAgent.call(new UserMessage("手机坏了"), config);
supportAgent.call(new UserMessage("还在保修期内"), config);  // 状态从检查点恢复

坑4:Skills文件路径不对

现象read_skill 工具找不到技能。

解决:确保目录结构正确

ini 复制代码
src/main/resources/skills/           ← classpathPath="skills"
├── sales_analytics/
│   └── SKILL.md                     ← 必须叫SKILL.md

坑5:依赖名写错

现象:Maven报错找不到artifact。

解决:用正确的 artifactId

xml 复制代码
<!-- ❌ 错误 -->
<artifactId>spring-ai-alibaba-starter-agent-reactor</artifactId>

<!-- ✅ 正确 -->
<artifactId>spring-ai-alibaba-agent-framework</artifactId>

坑6:Handoffs API 调用链复杂

现象:编译不通过,API 不存在。

原因:Handoffs 用了多条容易写错的 API:

  1. HandoffsSupportHook extends ModelHook(不是 implements AgentHook
  2. interceptModel(ModelRequest, ModelCallHandler)(方法名有 Model 后缀)
  3. ToolContextHelper.getStateForUpdate(toolContext) 返回 Optional,需要用 .ifPresent()
  4. 方法名是 getKeyStrategys()(注意末尾拼写)

八、下一步

  1. 先跑通Supervisor:最简单,先找手感
  2. 按需选模式:根据第六章决策树选一个深入
  3. 上生产:加监控、日志、限流

相关资源

相关推荐
happyprince15 小时前
05-Hugging Face Transformers 缓存系统深度分析
java·spring·缓存
大数据三康15 小时前
Java静态常量与静态导入:计算圆面积
java·开发语言
洞窝技术15 小时前
低成本高可用:洞窝团队如何搭建 AI 协同开发环境
前端·ai编程
凤山老林15 小时前
68-Java ConcurrentHashMap
java·开发语言
甲维斯16 小时前
MiMo2.5Pro《江湖百晓生》测试过程和结果!
人工智能·ai编程
ftpeak16 小时前
TorchEasyRec:阿里巴巴开源的推荐系统深度学习框架详解
人工智能·深度学习·ai·开源·ai编程·ai开发
憧憬成为java架构高手的小白16 小时前
苍穹外卖--day10(订单状态定时处理、来单提醒和客户催单)
java·spring boot
invicinble16 小时前
springboot出现的原因二---作为web的后端服务一站式整合器
前端·spring boot·后端
kyriewen16 小时前
前端初级岗位暴跌62%:我带了三年的实习生被裁了,而AI是他亲手教的
前端·面试·ai编程