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点站会,再发邮件通知团队",你需要:

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

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 使用了三个核心组件:

  1. ModelHook:提供 getModelInterceptors() 注册拦截器 + getKeyStrategys() 注册状态 Key 策略
  2. ModelInterceptor:每次 LLM 调用前,根据 current_step 状态动态切换 system prompt 和可用 tools
  3. 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。

核心组件:

  1. SkillRegistry:从 classpath 加载技能
  2. SkillsAgentHook:注册 read_skill 工具,自动将技能描述注入 system prompt
  3. 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:

  1. 用户提问 → 重写问题(提升检索质量)
  2. → 检索文档(确定性步骤)
  3. → 准备Prompt(组装上下文)
  4. → 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必须有 namedescription

---
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:

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

八、下一步

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

相关资源

  • Spring AI Alibaba 官方仓库:https://github.com/alibaba/spring-ai-alibaba/
  • 多Agent模式官方示例:https://github.com/alibaba/spring-ai-alibaba/tree/main/examples/multiagent-patterns

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐