首页
看点啥
插画图片
首页 科技看点 Spring AI Alibaba Agent 开发入门与实战

Spring AI Alibaba Agent 开发入门与实战

2026-10-20 0

在 Spring 应用中接入大模型并不难,真正复杂的是如何让模型调用工具、维护状态,并按照可控流程持续完成任务。Spring AI Alibaba 在 Spring AI 之上提供 Agent Framework 与 Graph 运行时,将 ReAct、多智能体协作和工作流编排纳入统一开发体系。接下来将从关键概念和环境配置入手,逐步理解核心 API 与实际构建方式。

从 Agent 到 Graph,用阿里云百炼(DashScope)模型构建智能体工作流


第一部分:基础与概念

第 0 章 导读与准备

0.1 本文要做什么

Spring AI Alibaba 是一个面向 Java 开发者的企业级 AI 应用开发框架,它基于 Spring AI 构建, 深度集成阿里云百炼平台,用于快速构建 Agentic(智能体)、Workflow(工作流) 与 Multi-agent(多智能体) 应用。

在开始之前,需要先建立一条最重要的认知主线:

Graph 是 Agent Framework 的底层运行时。

  • Agent Framework 是「高层用法」,提供 ReactAgent、SequentialAgent、ParallelAgent 等开箱即用的抽象;
  • Graph 是「底层引擎」,把智能体工作流建模为一张图(节点 + 边),提供状态管理、持久化、中断恢复、流式执行等能力。

官方建议开发者优先使用 Agent Framework;但当需要更细粒度的编排控制时,直接使用 Graph API 也完全可行。

0.2 环境准备

项目要求
JDK21+
构建工具Maven 3.6+
模型平台阿里云百炼(DashScope)
API Key在百炼控制台申请

获取 API Key 后,建议以环境变量的方式注入(避免硬编码到代码):

export AI_DASHSCOPE_API_KEY=sk-xxxxxx

0.3 依赖引入

在 pom.xml 中引入以下依赖(版本统一由 Spring AI Alibaba 的 BOM 管理):


    1.1.2
    1.1.2.2
    1.1.2.2



    
        
        
            org.springframework.ai
            spring-ai-bom
            ${spring-ai.version}
            pom
            import
        
        
        
            com.alibaba.cloud.ai
            spring-ai-alibaba-bom
            ${spring-ai-alibaba.version}
            pom
            import
        
        
        
            com.alibaba.cloud.ai
            spring-ai-alibaba-extensions-bom
            ${spring-ai-alibaba-extensions.version}
            pom
            import
        
    



    
    
        com.alibaba.cloud.ai
        spring-ai-alibaba-starter-dashscope
    

    
    
        com.alibaba.cloud.ai
        spring-ai-alibaba-agent-framework
    

    
    
        com.alibaba.cloud.ai
        spring-ai-alibaba-graph-core
    

说明:spring-ai-alibaba-agent-framework 本身依赖 spring-ai-alibaba-graph-core, 因此引入前者即可获得 Graph 能力。为便于讲解 Graph 章节,这里把两者都显式列出。

另外,spring-ai-alibaba-starter-dashscope(百炼模型接入)属于独立的扩展库, 版本由 spring-ai-alibaba-extensions-bom 管理(同样对齐 1.1.2.2)。


第 1 章 概念篇:Agentic / Workflow / ReAct / Plan-and-Execute

在写代码之前,先把四个最容易混淆的概念讲清楚。它们是理解整个框架的钥匙。

1.1 从 LLM 到 Agent:什么是 Agentic(智能体范式)

传统的 LLM 调用是「一问一答」:你发一条消息,模型返回一条回复,仅此而已。

而 Agentic(智能体范式) 的核心区别在于:模型可以自主决定「下一步做什么」。

一个智能体通常具备:

  1. 推理能力:理解任务,拆解出执行步骤;
  2. 行动能力:调用工具(查天气、查数据库、发请求……)去改变外部世界;
  3. 观察能力:读取工具返回的结果,并据此调整下一步。

「推理 → 行动 → 观察 → 再推理」这个循环,就是智能体的灵魂。它不再是一条直线, 而是一个由模型主导的循环——走到哪一步、调用哪个工具、什么时候停下来,都由模型自己判断。

一句话:Agentic = 把「决策权」交给模型,让它在循环中自主推进任务。

1.2 Workflow(固定工作流)

Workflow(固定工作流) 则相反:执行路径是预先编排好的,模型只负责在某个节点内完成推理。

一句话:Workflow 工作流 Agent = 开发者「预先画好流程图」,模型只在节点内干活。

举个例子,一个「文章生成」工作流可能是:

输入 → [标题生成节点] → [正文生成节点] → [校对节点] → 输出

这条路径是开发者画死的,数据按固定顺序流过每个节点。每个节点内部当然会调用模型, 但**「下一步去哪个节点」不由模型临时决定,而是由图的边(Edge)决定**。

维度Agentic(智能体)Workflow(工作流)
路径决定者模型(自主)开发者(预先编排)
灵活性高,可动态调整低,但可控、可预测
可观测性相对难路径清晰,易调试
典型场景开放任务、需多轮工具调用流程固定、步骤明确的任务

两者并非对立关系。实际上在本框架里,Agent 本身就是用 Graph(工作流)实现的——一个 ReactAgent 内部就是一张小图(LLM 节点 ↔ 工具节点循环)。而复杂业务则可以用 Graph 把多个 Agent、多个步骤编排成更大的工作流。

1.3 ReAct(Reasoning + Acting)

ReAct 是「Reasoning(推理)+ Acting(行动)」的缩写,是智能体最经典、也是本框架最核心的范式。 它的执行流程是一个四段循环:

┌─────────────┐      ┌─────────────┐
│  Reasoning  │ ───▶ │   Acting    │
│   思考       │      │ 调用工具     │
└─────────────┘      └─────────────┘
       ▲                     │
       │                     ▼
┌─────────────┐      ┌─────────────┐
│  Observing  │ ◀─── │   Tool 结果  │
│   观察结果   │      │             │
└─────────────┘      └─────────────┘

在 Spring AI Alibaba 中,ReactAgent 就是这一范式的生产级实现:它内部用一张 StateGraph 把「LLM 节点」和「工具节点」串成循环,模型每发起一次工具调用,就路由到工具节点执行, 执行完再回到 LLM 节点继续推理。

1.4 Plan-and-Execute(计划-执行)

Plan-and-Execute 是另一种智能体范式,思路是「先规划,再执行」:

  1. Plan(规划):模型先把任务拆解成一个步骤清单;
  2. Execute(执行):按照清单逐步执行每个步骤;
  3. (可选)Reflect(反思):执行完后评估结果,必要时修正计划重新执行。

一句话:Plan-and-Execute = 模型「现场写任务清单」,然后按清单跑。

它与 ReAct 的区别在于「粒度」:

在本框架里,Plan-and-Execute 可以用 Graph(StateGraph) 来落地——这正是第 9 章要做的实战。

1.5 概念 → 框架映射

学完概念,先给一张「概念对应到本框架的哪个类」的速查表,后面章节会逐一展开:

概念对应实现
Agentic / ReActReactAgent(com.alibaba.cloud.ai.graph.agent.ReactAgent)
Workflow / 图编排StateGraph(com.alibaba.cloud.ai.graph.StateGraph)
多智能体编排SequentialAgent、ParallelAgent、LlmRoutingAgent、LoopAgent
短期记忆 / 持久化CheckpointSaver(Memory / Redis / File / Mongo 等)+ RunnableConfig.threadId
人工介入HumanInTheLoopHook / InterruptableAction
Plan-and-Execute用 StateGraph 编排「规划 → 执行 → 反思」

第 2 章 基础组件:Models / Messages / Tools

在写智能体之前,先认识三个最基础的组件。官方文档对它们的定义非常精炼:

2.1 Models:接入百炼模型

Spring AI Alibaba 通过 spring-ai-alibaba-starter-dashscope 对接百炼平台。 引入依赖后,在 application.yml 中配置 API Key 与模型名:

spring:
  ai:
    dashscope:
      api-key: ${AI_DASHSCOPE_API_KEY}   # 百炼 API Key
      ch@t:
        options:
          model: qwen-plus               # 通义千问模型名
          temperature: 0.7

常用模型名:qwen-turbo(快而省)、qwen-plus(均衡)、qwen-max(最强)、 qwen-long(超长上下文)。按需替换。

最小的可运行示例——注入 ChatModel 直接对话:

import [email protected];
import [email protected];
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

    private final ChatClient ch@tClient;

    public HelloController(ChatModel ch@tModel) {
        this.ch@tClient = ChatClient.builder(ch@tModel).build();
    }

    @GetMapping("/ch@t")
    public String ch@t(@RequestParam String q) {
        return [email protected](q).call().content();
    }
}

启动后访问 http://localhost:8080/ch@t?q=你好,若能返回回复,说明百炼模型已接通。

除「yml 自动配置 + 注入 ChatModel」外,也可以手动构建具体的 DashScopeChatModel:

import com.alibaba.cloud.ai.dashscope.api.DashScopeApi;
import [email protected];
import [email protected];

DashScopeApi api = DashScopeApi.builder()
        .apiKey(System.getenv("AI_DASHSCOPE_API_KEY"))
        .build();

DashScopeChatModel ch@tModel = DashScopeChatModel.builder()
        .dashScopeApi(api)
        .defaultOptions(DashScopeChatOptions.builder()
                .model("qwen-plus")
                .temperature(0.7)
                .build())
        .build();

DashScopeChatOptions 支持 model / temperature / topP / maxToken 等参数, 对应百炼模型的采样与长度控制。日常开发推荐用 yml 自动配置,需要动态切换模型或精细调参时再手动构建。

2.2 Messages:模型交互的基本单元

消息是模型交互的载体,Spring AI Alibaba 沿用 Spring AI 的消息类型。核心的几种:

类角色说明
SystemMessagesystem系统提示词,设定模型人设/规则
UserMessageuser用户输入
AssistantMessageassistant模型回复(含可能的工具调用)
ToolResponseMessagetool工具执行结果,回传给模型

它们都在 [email protected] 包下,共同实现 Message 接口。

手工构造一段对话:

import [email protected].*;

List messages = List.of(
    new SystemMessage("你是一个中文助手"),
    new UserMessage("你好"),
    new AssistantMessage("你好,有什么可以帮你?")
);

在智能体场景下,这些消息会被追加进状态(state)中的 messages 列表,作为模型与工具循环的上下文。

2.3 Tools:工具(Function Calling)

工具是智能体「行动能力」的来源。在 Spring AI Alibaba 中,用 @Tool 注解一个方法, 框架会自动把它暴露给模型(Function Calling),模型在需要时发起调用。

import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Component;

@Component
public class WeatherTools {

    @Tool(description = "查询指定城市的实时天气")
    public String getWeather(@ToolParam(description = "城市名称,例如:杭州") String city) {
        // 真实场景:调用天气 API。这里用固定返回值演示。
        return city + " 今天晴,气温 25℃,微风";
    }
}

要点:

这些 @Tool 方法会被 Spring AI Alibaba 自动注册为 ToolCallback(org.springframework.ai.tool.ToolCallback)。 在第 3 章你会看到如何把它们挂载到 ReactAgent 上。


第二部分:单智能体与扩展能力

第 3 章 Agents:ReactAgent

3.1 ReactAgent 与 ReAct 循环原理

ReactAgent(com.alibaba.cloud.ai.graph.agent.ReactAgent)是 Spring AI Alibaba 提供的生产级 Agent 实现, 它把第 1 章讲的 ReAct 范式固化了下来。

从源码角度看,一个 ReactAgent 内部就是一张 StateGraph,它有两个核心节点:

两者之间通过条件边(Conditional Edge)连接成循环:

  1. 入口进入模型节点 → 模型推理;
  2. 若模型产出了 tool_call(AssistantMessage.hasToolCalls() == true)→ 路由到工具节点执行;
  3. 工具执行结果(ToolResponseMessage)回传给模型节点 → 模型继续推理;
  4. 若模型不再发起工具调用 → 路由到 END,循环结束。

这套「推理 → 行动 → 观察 → 循环」正是 ReAct 的机器实现。

3.2 构建 ReactAgent:Builder API

ReactAgent 采用 Builder 模式构建。把第 2 章的模型与工具串起来:

import com.alibaba.cloud.ai.graph.agent.ReactAgent;
import [email protected];
import org.springframework.ai.tool.ToolCallback;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.List;

@Configuration
public class AgentConfig {

    @Bean
    public ReactAgent weatherAgent(ChatModel ch@tModel, ToolCallback[] toolCallbacks) {
        return ReactAgent.builder()
                .name("weather_agent")                                   // 必填:图中唯一标识
                .description("天气查询助手,可查询指定城市天气")            // 描述
                .model(ch@tModel)                                        // 模型(model / ch@tClient 至少其一)
                .instruction("你是一个天气助手,用户问天气时调用 getWeather 工具。")
                .tools(List.of(toolCallbacks))                           // 挂载工具
                .build();
    }
}

Builder 常用方法逐项说明:

方法是否必填说明
name(String)✅ 必填Agent 名称,图中唯一标识
description(String)否描述;在多智能体路由中,Router 依据它选择子 Agent
instruction(String)否任务指令模板,支持 {占位符} 引用状态变量
systemPrompt(String)否系统提示词(system 角色),固定人设/规则
model(ChatModel)二选一指定 ChatModel
ch@tClient(ChatClient)二选一或指定 ChatClient
tools(...)否挂载已包装好的 ToolCallback 工具
methodTools(...)否传入带 @Tool 方法的对象,框架自动包装成工具
outputKey(String)否把最终结果写入 state 的指定 key
hooks(...)否挂载 Hook(见第 5 章)

tools 与 methodTools 的区别:

WeatherTools weatherTools = new WeatherTools();   // 第 2 章里那个带 @Tool 方法的类

ReactAgent agent = ReactAgent.builder()
        .name("weather_agent")
        .model(ch@tModel)
        // 等价于 .tools(ToolCallbacks.from(weatherTools))
        .methodTools(weatherTools)
        .build();

注意:name 必须唯一且不可省略;model 与 ch@tClient 至少要提供一个。 若需限制推理/工具调用轮数(防止死循环),可通过 ModelCallLimitHook / ToolCallLimitHook 等 Hook 实现。

指令、系统提示词与用户提示词的区别:

ReactAgent 里容易混淆三个「提示词」,它们的角色各不相同:

概念设置方式消息角色是否支持占位符作用
系统提示词.systemPrompt(...)system否固定人设 / 全局规则
指令(任务模板).instruction(...)user是(如 {input})描述「拿到输入后做什么」
用户提示词agent.call("...") 传入user—每次对话的动态输入

占位符替换的具体机制:instruction 先被 InstructionAgentHook 注入成 AgentInstructionMessage, 随后 AgentLlmNode 在组装请求时用 Spring AI 的 PromptTemplate(底层 StringTemplate)执行 render(params), 把 {input} 替换成状态里 input 键的值(即最后一条用户消息的文本);渲染完成后打上标记,避免每轮重复替换。

占位符的写法就是 {keyName}(StringTemplate 语法),keyName 必须是图状态里的一个键:

// 1) 内置键 {input}
agent.call("杭州今天天气怎么样?");   // 自动写入 input 键

// 2) 自定义键 {topic} / {tone}
Map inputs = new HashMap<>();
inputs.put("input", "请开始");                    // 保留键:提供输入
inputs.put("topic", "Spring AI Alibaba");         // 自定义键
inputs.put("tone", "通俗易懂");                   // 自定义键
agent.call(inputs);
// instruction 里可写:「用 {tone} 的语气,写一篇关于 {topic} 的文章」

3.3 使用工具的四种方式

ReactAgent 有四种接入工具的方式,按「业务最常用 → 最灵活」排列:

① @Tool 注解(声明式,推荐) —— 在方法上直接加注解,框架自动生成工具定义,业务开发首选:

import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;

@Component
public class WeatherTools {
    @Tool(description = "查询指定城市的实时天气")
    public String getWeather(@ToolParam(description = "城市名称,例如:杭州") String city) {
        return city + " 今天晴,25℃";
    }
}
// 挂载:.tools(toolCallbacks) 或 .methodTools(weatherTools)

② FunctionToolCallback(函数式) —— 直接包装一个 Lambda/Function,轻量快速构建简单工具。它提供成对的两个 builder 重载:

import org.springframework.ai.tool.ToolCallback;
import org.springframework.ai.tool.function.FunctionToolCallback;
import [email protected];

// 单参数 Function:只关心输入
ToolCallback weather = FunctionToolCallback.builder("getWeather", (String city) -> {
        return "晴朗,25℃";
    })
    .description("获取指定城市的天气")
    .inputType(String.class)
    .build();

// 带工具上下文的 BiFunction:额外拿到 ToolContext
ToolCallback weatherWithContext = FunctionToolCallback.builder("getWeather",
        (String city, ToolContext context) -> {
            // context.getContext():上下文 Map
            // context.getToolCallHistory():工具调用历史(List)
            return "晴朗,25℃";
        })
    .description("获取指定城市的天气(带上下文)")
    .inputType(String.class)
    .build();

// 挂载:.tools(weather) 或 .tools(weather, weatherWithContext)

框架内部会把单参数 Function 自动适配成 BiFunction(忽略 ToolContext)。 当工具需要读取工具调用历史或上下文元数据时,就用 BiFunction 版本。

③ MethodToolCallback(编程式) —— 用反射手动构造 ToolCallback,可精细自定义工具元数据。三个参数的来源如下:

import org.springframework.ai.tool.ToolCallback;
import org.springframework.ai.tool.definition.ToolDefinition;
import org.springframework.ai.tool.method.MethodToolCallback;
import org.springframework.ai.tool.support.ToolDefinitions;

import java.lang.reflect.Method;

// 目标方法所在类(可以是任意普通类,不必加 @Tool 注解)
public class WeatherService {
    public String getWeather(String city) {
        return city + " 今天晴,25℃";
    }
}

// ① toolMethod:用反射拿到 java.lang.reflect.Method
Method method = WeatherService.class.getMethod("getWeather", String.class);
// 或 Spring 的 ReflectionUtils.findMethod(WeatherService.class, "getWeather", String.class)

// ② toolDefinition:描述工具的名称、描述、入参 Schema
//    方式一:从方法推断(有 @Tool 注解时读取,否则用方法名兜底),再按需覆盖
ToolDefinition toolDefinition = ToolDefinitions.builder(method)
        .name("getWeather")
        .description("查询指定城市的实时天气")
        .build();
//    方式二:完全手动指定
ToolDefinition manualDef = ToolDefinition.builder()
        .name("getWeather")
        .description("查询指定城市的实时天气")
        .inputSchema("{\"type\":\"object\",\"properties\":{\"city\":{\"type\":\"string\"}}}")
        .build();

// ③ toolObject:方法所属的对象实例(new 出来的,或注入的 Spring Bean)
WeatherService instance = new WeatherService();

// 组装
ToolCallback tool = MethodToolCallback.builder()
        .toolDefinition(toolDefinition)
        .toolMethod(method)
        .toolObject(instance)
        .build();

// 挂载:.tools(tool)

三个参数:toolMethod 是反射拿到的 Method;toolObject 是方法所属实例;toolDefinition 描述工具元数据, 可用 ToolDefinitions.builder(method) 从方法推断,也可用 ToolDefinition.builder() 手动指定 name / description / inputSchema。

④ ToolCallbackProvider / AgentTool(动态提供者 / 智能体即工具) —— 运行时动态提供工具,或把子 Agent 封装成工具,用于动态工具集、多 Agent 编排:

// 动态提供者:实现 ToolCallbackProvider(getToolCallbacks()),运行时返回工具集合
ReactAgent.builder()
        .toolCallbackProviders(myProvider)
        .build();

// 智能体即工具:把子 Agent 包装成一个 ToolCallback,供上层 Agent 调用
import com.alibaba.cloud.ai.graph.agent.AgentTool;
ToolCallback subAgentTool = AgentTool.create(subAgent);

// 挂载:上层 Agent 通过 .tools(subAgentTool) 使用这个子 Agent 工具

小结:①③ 都基于「方法」(注解 vs 反射),② 基于 Lambda/Function,④ 面向「动态工具集 / 多 Agent」场景。 挂载方式:②③④ 产出的都是 ToolCallback,统一用 .tools(...) 挂载;① 的注解对象可用 .methodTools(obj) 或先转成 ToolCallback 再 .tools(...);④ 的 provider 用 .toolCallbackProviders(...)。

3.4 调用 Agent

ReactAgent 的 call(...) 有一组重载,覆盖了最常见的入参形式,同步返回 AssistantMessage:

// 1) 直接传字符串
AssistantMessage r1 = agent.call("杭州今天天气怎么样?");

// 2) 传 UserMessage
AssistantMessage r2 = agent.call(new UserMessage("杭州今天天气怎么样?"));

// 3) 传消息列表(多轮上下文)
AssistantMessage r3 = agent.call(List.of(
        new UserMessage("你好"),
        new AssistantMessage("你好,请问有什么可以帮您?"),
        new UserMessage("杭州今天天气怎么样?")
));

// 4) 传字符串 + 运行时配置(threadId 用于关联会话/持久化)
AssistantMessage r4 = agent.call(
        "杭州今天天气怎么样?",
        RunnableConfig.builder().threadId("user-1001").build()
);

System.out.println(r4.getText());

AssistantMessage.metadata 常用 Key:

call(...) 返回的 AssistantMessage 除了文本,还通过 getMetadata() 携带一份 Map。 它是厂商扩展字段——并非所有模型都返回全部 key,不同服务商返回的 key 也不一样。

metadata 由两部分构成:

常见的重要 key(以 1.1.2 实测源码为准):

key含义DashScopeOpenAIDeepSeek
finishReason生成结束原因(如 stop / length / tool_calls)✅✅✅
reasoningContent思考/推理内容(thinking 模型)✅✅✅
usagetoken 用量✅✅✅
requestId平台请求 ID✅❌❌
refusal安全拒绝原因❌✅❌
index候选结果序号❌✅✅

读取时务必判空:message.getMetadata().get("finishReason") 可能为 null, 因为不是所有模型/所有调用都会返回同一个 key。

3.5 Structured Output(结构化输出)

默认情况下,模型返回的是自由文本。结构化输出则要求模型把结果组织成符合指定结构的数据(如一个 Java POJO), 便于程序直接反序列化、后续节点直接消费。

ReactAgent 的 Builder 在源码中持有 outputSchema / outputType(输出结构)与 inputSchema / inputType(输入结构)等字段, 用于约束模型的输入/输出格式。使用时,通常是声明一个目标类型,让模型把答案填充到该结构的字段中, 从而避免「让模型返回 JSON 字符串再手工解析」的脆弱做法。

方式一:outputType —— 直接传目标 POJO 类型:

import com.alibaba.cloud.ai.graph.agent.ReactAgent;
import [email protected];
import com.fasterxml.jackson.databind.ObjectMapper;

// 目标结构
public class ContactInfo {
    private String name;
    private String email;
    private String phone;
    // 省略 getter / setter
}

ReactAgent agent = ReactAgent.builder()
        .name("contact_extractor")
        .model(ch@tModel)
        .outputType(ContactInfo.class)   // 按该类型约束输出结构
        .build();

AssistantMessage result = agent.call(
        "从以下信息提取联系方式:张三,[email protected],(555) 123-4567");

// 结果文本是 JSON,用 Jackson 反序列化
ContactInfo info = new ObjectMapper().readValue(result.getText(), ContactInfo.class);

方式二:outputSchema —— 用 BeanOutputConverter 生成 JSON Schema 再传入:

import org.springframework.ai.converter.BeanOutputConverter;

BeanOutputConverter converter = new BeanOutputConverter<>(ContactInfo.class);
String schema = converter.getFormat();   // 生成 JSON Schema 字符串

ReactAgent agent = ReactAgent.builder()
        .name("contact_extractor")
        .model(ch@tModel)
        .outputSchema(schema)            // 传入 JSON Schema
        .build();

两者都约束模型返回结构化 JSON:outputType 更省事(直接给类型),outputSchema 更灵活(可自定义 Schema)。 取回结果后用 result.getText() + Jackson 反序列化即可得到 POJO 对象。


第 4 章 Skills(技能)

Skill 是「可复用的能力说明」——它把一个领域的操作步骤写成一份 Markdown 文件(SKILL.md), 模型在需要时加载并按其指令行事,从而「学会」如何完成某项任务。

渐进式披露(Progressive Disclosure):

Skill 采用渐进式披露策略,避免把所有技能内容一次性塞进上下文:

  1. 系统提示里只注入技能列表(每个技能的 name、description、路径等轻量信息);
  2. 模型判断某个技能与当前任务相关时,调用 read_skill(skill_name) 按需加载完整的 SKILL.md;
  3. 再按 SKILL.md 里的说明,按需访问技能目录下的脚本、参考资料等资源,或使用与该技能绑定的工具。

一句话:先让模型知道「有哪些技能」,用到时再加载完整说明,从而大幅节省上下文。

Skill 目录结构:

每个技能是一个独立子目录,其中 SKILL.md 必需:

skill-name/
├── SKILL.md        # 必需:技能说明
├── references/     # 可选:参考资料
├── examples/       # 可选:示例
└── scripts/        # 可选:脚本等辅助资源

框架扫描技能目录时,只认含 SKILL.md 的子目录;SKILL.md 里可用绝对路径引用同目录下的脚本/资料。

SKILL.md 格式规范:

SKILL.md 由 YAML frontmatter + Markdown 正文组成:

---
name: pdf-extractor
description: 提取 PDF 中的文本、表格与表单数据。当用户要求提取、解析或分析 PDF 时使用。
---

# PDF Extractor Skill

1. 确认 PDF 文件路径存在
2. 执行脚本提取内容
3. 解析并整理输出 JSON

必需字段:

字段规则
name小写字母、数字、单连字符(a-z 0-9 -),最长 64 字符;不能以连字符开头/结尾,不能连续连字符
description最长 1024 字符,超出会被截断

接入方式:先创建一个 FileSystemSkillRegistry 指向技能目录,再用 SkillsAgentHook 挂到 Agent 上:

import com.alibaba.cloud.ai.graph.agent.hook.skills.SkillsAgentHook;
import com.alibaba.cloud.ai.graph.skills.registry.FileSystemSkillRegistry;

FileSystemSkillRegistry registry = FileSystemSkillRegistry.builder()
        .userSkillsDirectory("~/saa/skills")   // 用户级技能目录
        .projectSkillsDirectory("./skills")    // 项目级技能目录
        .build();

SkillsAgentHook skillsHook = SkillsAgentHook.builder()
        .skillRegistry(registry)
        .autoReload(true)                       // 每次调用前重新加载技能
        .build();

ReactAgent agent = ReactAgent.builder()
        .name("skill_agent")
        .model(ch@tModel)
        .hooks(List.of(skillsHook))             // 通过 hooks 挂载技能
        .build();

挂载后,框架会:把技能列表注入系统提示(通过 SkillsInterceptor),并注册 read_skill 工具; 当模型判断某个技能相关时,会调用 read_skill 读取对应 SKILL.md 内容,再按其中指令行事。


第 5 章 Hooks 和 Interceptors

Hook 是插入到 Agent 执行生命周期中的自定义逻辑,Agent 的运行过程被划分为若干位置(Position):

HookPosition时机
BEFORE_AGENTAgent 开始前
AFTER_AGENTAgent 结束后
BEFORE_MODEL每次调用模型前
AFTER_MODEL每次调用模型后

通过 @HookPositions(...) 注解声明 Hook 生效的位置(可指定多个)。

5.1 能做什么?

Hooks 和 Interceptors 覆盖四类能力:

类别说明典型场景
坚控记录日志、分析、调试跟踪 Agent 行为耗时统计、链路追踪
修改转换提示、工具选择和输出格式改写请求、格式化结果
控制添加重试、回退和提前终止逻辑重试、熔断、提前终止
强制执行应用速率限制、护栏和 PII 检测限流、脱敏、安全护栏

5.2 内置实现

框架内置了大量 Hooks(生命周期级)与 Interceptors(模型/工具调用级):

Hooks(生命周期级):

Hook作用
SummarizationHook消息压缩 / 上下文摘要
HumanInTheLoopHook人机协同(人工审批,见 5.7)
ModelCallLimitHook / ToolCallLimitHook模型 / 工具调用次数限制
PIIDetectionHookPII 敏感信息检测
SkillsAgentHook技能加载(见第 4 章)
InstructionAgentHook指令注入(框架默认)
InterruptionHook中断反馈
ReturnDirectModelHook工具结果直接返回

使用示例(构造 + 挂载):

// 消息压缩 / 上下文摘要
SummarizationHook summarization = SummarizationHook.builder()
        .model(ch@tModel)
        .maxTokensBeforeSummary(4000)   // 超过 4000 token 触发摘要
        .messagesToKeep(10)             // 保留最近 10 条消息
        .build();

// 模型调用次数限制
ModelCallLimitHook callLimit = ModelCallLimitHook.builder()
        .runLimit(5)                    // 单次运行最多 5 次模型调用
        .build();

// PII 检测(邮箱脱敏)
PIIDetectionHook pii = PIIDetectionHook.builder()
        .piiType(PIIType.EMAIL)
        .strategy(RedactionStrategy.MASK)
        .build();

// 人机协同(需人工审批)
HumanInTheLoopHook hitl = HumanInTheLoopHook.builder()
        .approvalOn("transfer_money", "操作需人工确认")
        .build();

// 挂载:Hooks 统一用 .hooks(...) 注册
ReactAgent agent = ReactAgent.builder()
        .name("agent")
        .model(ch@tModel)
        .hooks(List.of(summarization, callLimit, pii, hitl))
        .build();

Hook 类分别位于 com.alibaba.cloud.ai.graph.agent.hook.summarization / modelcalllimit / pii / hip 等子包。

Interceptors(模型/工具调用级):

Interceptor作用
ModelRetryInterceptor模型调用重试
ToolRetryInterceptor工具调用重试(支持特定异常 + 指数退避)
TodoListInterceptorPlanning(规划):引导模型用 write_todos 拆解任务
ToolSelectionInterceptorLLM 工具选择器:先用 LLM 筛出相关工具
ToolEmulatorInterceptorLLM 工具模拟器:用 LLM 模拟工具而非真实执行
SkillsInterceptor把技能列表注入系统提示

使用示例(构造 + 挂载):

// 工具重试(指数退避)
ToolRetryInterceptor toolRetry = ToolRetryInterceptor.builder()
        .maxRetries(3)
        .toolName("getWeather")
        .initialDelay(100)              // 首次重试延迟 100ms
        .backoffFactor(2.0)             // 每次重试延迟翻倍
        .build();

// Planning(规划):引导模型用 write_todos 拆解任务
TodoListInterceptor planning = TodoListInterceptor.builder().build();

// LLM 工具选择器:先用模型筛出相关工具
ToolSelectionInterceptor selector = ToolSelectionInterceptor.builder()
        .selectionModel(ch@tModel)
        .maxTools(5)
        .build();

// LLM 工具模拟器:用 LLM 模拟工具而非真实执行
ToolEmulatorInterceptor emulator = ToolEmulatorInterceptor.builder()
        .model(ch@tModel)
        .emulateAllTools(true)
        .build();

// 挂载:拦截器统一用 .interceptors(...) 注册(框架按类型自动分流)
ReactAgent agent = ReactAgent.builder()
        .name("agent")
        .model(ch@tModel)
        .interceptors(toolRetry, planning, selector, emulator)
        .build();

拦截器类位于 com.alibaba.cloud.ai.graph.agent.interceptor.toolretry / todolist / toolselection / toolemulator 等子包。 注意:拦截器用 .interceptors(...) 注册(而非 .hooks(...)),框架内部会自动按 ModelInterceptor / ToolInterceptor 类型分流。

5.3 自定义 Hook

自定义 Hook 只需继承 AgentHook(或 ModelHook),用 @HookPositions 标注位置、覆写对应方法。

① 耗时统计 —— 记录 Agent 整体耗时:

import com.alibaba.cloud.ai.graph.OverAllState;
import com.alibaba.cloud.ai.graph.RunnableConfig;
import com.alibaba.cloud.ai.graph.agent.hook.AgentHook;
import com.alibaba.cloud.ai.graph.agent.hook.HookPosition;
import com.alibaba.cloud.ai.graph.agent.hook.HookPositions;

import java.util.Map;
import java.util.concurrent.CompletableFuture;

@HookPositions({HookPosition.BEFORE_AGENT, HookPosition.AFTER_AGENT})
public class TimingHook extends AgentHook {

    private long start;

    @Override
    public CompletableFuture> beforeAgent(OverAllState state, RunnableConfig config) {
        start = System.currentTimeMillis();
        return CompletableFuture.completedFuture(Map.of());
    }

    @Override
    public CompletableFuture> afterAgent(OverAllState state, RunnableConfig config) {
        System.out.println("Agent 耗时:" + (System.currentTimeMillis() - start) + "ms");
        return CompletableFuture.completedFuture(Map.of());
    }

    @Override
    public String getName() { return "timing_hook"; }
}

② 消息上限限制 —— 继承 MessagesAgentHook 编辑消息列表(上下文裁剪):

import com.alibaba.cloud.ai.graph.RunnableConfig;
import com.alibaba.cloud.ai.graph.agent.hook.HookPosition;
import com.alibaba.cloud.ai.graph.agent.hook.HookPositions;
import com.alibaba.cloud.ai.graph.agent.hook.messages.AgentCommand;
import com.alibaba.cloud.ai.graph.agent.hook.messages.MessagesAgentHook;

import [email protected];

import java.util.ArrayList;
import java.util.List;

@HookPositions(HookPosition.BEFORE_AGENT)
public class MessageLimitHook extends MessagesAgentHook {

    private final int maxMessages;

    public MessageLimitHook(int maxMessages) { this.maxMessages = maxMessages; }

    @Override
    public AgentCommand beforeAgent(List previousMessages, RunnableConfig config) {
        // 超过上限:只保留最近 N 条
        List trimmed = previousMessages.size() > maxMessages
                ? new ArrayList<>(previousMessages.subList(previousMessages.size() - maxMessages, previousMessages.size()))
                : previousMessages;
        return new AgentCommand(trimmed);
    }

    @Override
    public String getName() { return "message_limit_hook"; }
}

提前终止:ModelCallLimitHook 在调用次数超限时抛出 ModelCallLimitExceededException 终止执行; 更通用地,Hook 可通过 canJumpTo() 声明可跳转目标,配合 jump_to 状态把流程导向 JumpTo.end 提前结束。

5.4 自定义 Interceptor

Interceptor 采用装饰器模式:interceptXxx(request, handler) 里通过 handler.call(request) 调用下一层, 可自由决定调用前后做什么、是否调用、调用几次。类型都在 com.alibaba.cloud.ai.graph.agent.interceptor 包下。

① ModelInterceptor —— 模型调用耗时 + 敏感词过滤:

import com.alibaba.cloud.ai.graph.agent.interceptor.ModelCallHandler;
import com.alibaba.cloud.ai.graph.agent.interceptor.ModelInterceptor;
import com.alibaba.cloud.ai.graph.agent.interceptor.ModelRequest;
import com.alibaba.cloud.ai.graph.agent.interceptor.ModelResponse;

public class ModelGuardInterceptor extends ModelInterceptor {

    @Override
    public ModelResponse interceptModel(ModelRequest request, ModelCallHandler handler) {
        long start = System.currentTimeMillis();
        // 敏感词过滤:调用前改写 request(示意)
        ModelResponse response = handler.call(request);   // 真实调用模型
        long cost = System.currentTimeMillis() - start;
        System.out.println("模型调用耗时:" + cost + "ms");
        // 也可对 response 做敏感词过滤后再返回
        return response;
    }

    @Override
    public String getName() { return "model_guard"; }
}

② ToolInterceptor —— 工具耗时记录 + 缓存:

import com.alibaba.cloud.ai.graph.agent.interceptor.ToolCallHandler;
import com.alibaba.cloud.ai.graph.agent.interceptor.ToolCallRequest;
import com.alibaba.cloud.ai.graph.agent.interceptor.ToolCallResponse;
import com.alibaba.cloud.ai.graph.agent.interceptor.ToolInterceptor;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class ToolCacheInterceptor extends ToolInterceptor {

    private final Map cache = new ConcurrentHashMap<>();

    @Override
    public ToolCallResponse interceptToolCall(ToolCallRequest request, ToolCallHandler handler) {
        String key = request.toString();   // 示意:按请求内容做缓存键
        if (cache.containsKey(key)) {
            return cache.get(key);          // 命中缓存,跳过真实调用
        }
        long start = System.currentTimeMillis();
        ToolCallResponse response = handler.call(request);   // 真实调用工具
        long cost = System.currentTimeMillis() - start;
        System.out.println("工具耗时:" + cost + "ms");
        cache.put(key, response);
        return response;
    }

    @Override
    public String getName() { return "tool_cache"; }
}

5.5 Hook 和 Interceptor 的选择与调用顺序

选择:

调用顺序:

5.6 Context Editing(上下文编辑)

Context Editing 指在每次调用前/后直接增删改对话历史(List)。它通过 MessagesAgentHook / MessagesModelHook 实现——覆写 beforeAgent(List, config) 返回一个 AgentCommand(携带修改后的消息列表),框架会用其替换当前消息。

5.3 的「消息上限限制」就是 Context Editing 的一个例子(裁剪旧消息)。其他典型用途:

示例(注入系统提示 + 裁剪消息):

import com.alibaba.cloud.ai.graph.RunnableConfig;
import com.alibaba.cloud.ai.graph.agent.hook.HookPosition;
import com.alibaba.cloud.ai.graph.agent.hook.HookPositions;
import com.alibaba.cloud.ai.graph.agent.hook.messages.AgentCommand;
import com.alibaba.cloud.ai.graph.agent.hook.messages.MessagesAgentHook;

import [email protected];
import [email protected];

import java.util.ArrayList;
import java.util.List;

@HookPositions(HookPosition.BEFORE_AGENT)
public class ContextEditingHook extends MessagesAgentHook {

    private final int maxMessages;

    public ContextEditingHook(int maxMessages) { this.maxMessages = maxMessages; }

    @Override
    public AgentCommand beforeAgent(List previousMessages, RunnableConfig config) {
        List edited = new ArrayList<>();

        // ① 注入系统提示(若尚未有)
        boolean hasSystem = previousMessages.stream().anyMatch(m -> m instanceof SystemMessage);
        if (!hasSystem) {
            edited.add(new SystemMessage("你是一个中文助手"));
        }

        // ② 裁剪:超过上限只保留最近 N 条
        int start = Math.max(0, previousMessages.size() - maxMessages);
        edited.addAll(previousMessages.subList(start, previousMessages.size()));

        // 返回修改后的列表(默认 UpdatePolicy.REPLACE 替换整个消息列表)
        return new AgentCommand(edited);
    }

    @Override
    public String getName() { return "context_editing_hook"; }
}

MessagesModelHook 与之类似,但作用在每次模型调用前后(beforeModel / afterModel),粒度更细; AgentCommand 还支持 UpdatePolicy(如 REPLACE)与 JumpTo,可控制替换策略与跳转目标。

5.7 Human-in-the-Loop(人工介入)

Human-in-the-Loop 指在 Agent 执行的关键节点「停下来,等人类确认/修改后再继续」。 官方文档明确指出:Agent 使用持久化机制来实现长期记忆与人工介入——HITL 正是建立在第 6 章讲的检查点机制之上的。

最典型的场景是「敏感操作需审批」,例如。Spring AI Alibaba 提供了开箱即用的 HumanInTheLoopHook,指定哪些工具需要人工审批:

import com.alibaba.cloud.ai.graph.agent.hook.hip.HumanInTheLoopHook;

HumanInTheLoopHook hitlHook = HumanInTheLoopHook.builder()
        .approvalOn("transfer_money", "操作需人工确认")
        .build();

ReactAgent agent = ReactAgent.builder()
        .name("bank_agent")
        .model(ch@tModel)
        .tools(toolCallbacks)
        .hooks(List.of(hitlHook))
        .build();

工作流程:

  1. Agent 推理后,决定调用 transfer_money;
  2. HumanInTheLoopHook(在 AFTER_MODEL 位置)拦截,产生中断(InterruptionMetadata),暂停执行;
  3. 人类对每个工具调用给出反馈:APPROVED(同意) / EDITED(修改参数) / REJECTED(拒绝);
  4. 框架根据反馈恢复执行(resume),继续后续推理。

在更底层的 Graph 里,对应的是 InterruptableAction 接口——它提供两个钩子点:

public interface InterruptableAction {
    // 节点执行前调用,可阻止执行
    Optional interrupt(String nodeId, OverAllState state, RunnableConfig config);

    // 节点执行后调用,可检查结果并中断
    default Optional interruptAfter(String nodeId, OverAllState state,
            Map actionResult, RunnableConfig config) {
        return Optional.empty();
    }
}

配合检查点与 resume 机制,即可实现审批流、多轮对话式交互等「人机协同」场景(详见第 8 章的中断恢复)。


第 6 章 Memory(记忆)

6.1 三层记忆:短期 / 中期 / 长期

Agent 的「记忆」通常分三层:

层级内容存储 / 技术在本框架中的实现
短期记忆最近 10–20 条对话记录Redis / DB 等CheckpointSaver + threadId(内置)
中期记忆提取与当前对话相关联的聊天记录RAG(检索增强)结合向量库做检索
长期记忆核心结构化数据(用户画像、经验信息)结构化存储持久化事实,如「我男性、35 岁」

注意:框架原生提供的是短期记忆(checkpointer);中期(RAG)与长期(结构化画像)是你在其上构建的模式。

6.2 短期记忆:checkpointer

模型本身是无状态的:你不主动把历史带进去,它就「记不住」上一轮说过什么。

在 Spring AI Alibaba 中,短期记忆(会话级持久化)的实现方式是——在创建 Agent 时指定 checkpointer(检查点器)。 有了 checkpointer,框架会在每轮执行后保存状态(检查点),下次调用时用同一个会话 ID 即可恢复上下文。

会话 ID 通过 RunnableConfig.threadId(...) 指定:

RunnableConfig config = RunnableConfig.builder()
        .threadId("user-1001")   // 会话 ID
        .build();

agent.call("我叫小明", config);          // 第 1 轮
agent.call("我叫什么名字?", config);      // 第 2 轮:能记住上一轮

同一个 threadId 的多次调用共享状态;不同 threadId 之间相互隔离。

6.3 内置 Saver 与保存策略

框架提供了多种 Saver 实现,对应不同的持久化后端:

Saver存储位置适用场景
MemorySaver内存开发调试;进程重启即丢
VersionedMemorySaver内存(带版本)同一会话多次执行、需要版本回溯
FileSystemSaver文件系统单机持久化
RedisSaverRedis分布式部署(生产常用)
MongoSaver / MysqlSaver / PostgresSaver / OracleSaver对应数据库需要与既有存储统一

在 Graph 层,通过 SaverConfig 注册存档器,并在编译图时传入(StateGraph.compile() 默认就使用 MemorySaver):

import com.alibaba.cloud.ai.graph.CompileConfig;
import com.alibaba.cloud.ai.graph.CompiledGraph;
import com.alibaba.cloud.ai.graph.StateGraph;
import com.alibaba.cloud.ai.graph.checkpoint.config.SaverConfig;
import com.alibaba.cloud.ai.graph.checkpoint.savers.MemorySaver;

SaverConfig saverConfig = SaverConfig.builder()
        .register(new MemorySaver())
        .build();

CompiledGraph graph = stateGraph.compile(
        CompileConfig.builder()
                .saverConfig(saverConfig)
                .build()
);

而 ReactAgent 本身就是一张编译好的图,其 Builder 同样支持通过 .saver(...) 配置 checkpointer。 以 Redis(Redisson)为例:

import com.alibaba.cloud.ai.graph.agent.ReactAgent;
import com.alibaba.cloud.ai.graph.checkpoint.savers.redis.RedisSaver;
import org.redisson.api.RedissonClient;

// 1) 拿到 RedissonClient(Spring Boot 自动装配或手动构建)
// 2) 构建 RedisSaver
RedisSaver redisSaver = RedisSaver.builder()
        .redisson(redissonClient)
        .build();

// 3) 在创建 Agent 时指定 saver
ReactAgent agent = ReactAgent.builder()
        .name("agent")
        .model(ch@tModel)
        .saver(redisSaver)          // 指定 checkpointer
        .build();

状态合并策略(KeyStrategy):同名 key 如何合并——ReplaceStrategy(覆盖,适合单值结果)、 AppendStrategy(追加,适合 messages 这类需累积的列表)。

6.4 记忆带来的上下文过长问题

启用短期记忆后,长对话会不断累积消息,最终可能超过 LLM 的上下文窗口。常见解决方案:

方案做法对应实现
修剪消息调用 LLM 前移除前 N 条或后 N 条消息MessagesAgentHook 裁剪(见 5.6)
删除消息从 Graph 状态中永久删除消息直接操作 state 的 messages 键
总结消息把较早的消息总结成摘要替换SummarizationHook
自定义策略按需自定义(如消息过滤、按类型保留)自定义 Hook / Interceptor

示例(总结 + 裁剪):

// 总结:超过 4000 token 时把早期消息压缩成摘要
SummarizationHook summarize = SummarizationHook.builder()
        .model(ch@tModel)
        .maxTokensBeforeSummary(4000)
        .messagesToKeep(10)
        .build();

ReactAgent agent = ReactAgent.builder()
        .name("agent")
        .model(ch@tModel)
        .hooks(List.of(summarize))
        .build();

6.5 访问和修改短期记忆(状态)

短期记忆(状态)可以在工具或 Hook 中读取和修改。

在工具中读取 —— 使用 ToolContext 参数访问状态。toolContext 参数从工具签名中隐藏(模型看不到它), 但工具方法可以通过它访问状态(如 RunnableConfig 及其 metadata):

import com.alibaba.cloud.ai.graph.RunnableConfig;
import com.alibaba.cloud.ai.graph.agent.ReactAgent;
import com.alibaba.cloud.ai.graph.agent.tools.ToolContextHelper;
import com.alibaba.cloud.ai.graph.checkpoint.savers.MemorySaver;
import [email protected];
import [email protected];
import org.springframework.ai.tool.ToolCallback;
import org.springframework.ai.tool.function.FunctionToolCallback;

import java.util.function.BiFunction;

// 第二个参数 ToolContext 由框架注入,模型看不到
public class UserInfoTool implements BiFunction {
    @Override
    public String apply(String query, ToolContext toolContext) {
        // 从上下文获取 RunnableConfig
        RunnableConfig config = ToolContextHelper.getConfig(toolContext).orElseThrow();
        String userId = (String) config.metadata("user_id").orElse("");
        if ("user_123".equals(userId)) {
            return "用户是 John Smith";
        }
        return "未知用户";
    }
}

// 创建工具
ToolCallback getUserInfoTool = FunctionToolCallback
        .builder("get_user_info", new UserInfoTool())
        .description("查找用户信息")
        .inputType(String.class)
        .build();

// 挂载并使用(通过 addMetadata 注入 user_id)
ReactAgent agent = ReactAgent.builder()
        .name("my_agent")
        .model(ch@tModel)
        .tools(getUserInfoTool)
        .saver(new MemorySaver())
        .build();

RunnableConfig config = RunnableConfig.builder()
        .threadId("1")
        .addMetadata("user_id", "user_123")
        .build();

AssistantMessage response = agent.call("获取用户信息", config);
System.out.println(response.getText());

底层说明:ToolContext 里 config 存于 "_AGENT_CONFIG_" 键、state 存于 "_AGENT_STATE_" 键 (见 ToolContextConstants);ToolContextHelper 提供了 getConfig / getState / getMetadata 等便捷方法。

从工具写入 —— 要在执行期间修改短期记忆(状态),有两种途径:

  1. 在 Hook 中更新状态:Hook 的 beforeXxx / afterXxx 返回的 Map 会合并进图状态;
  2. 工具返回的信息更新状态:工具返回的结果会作为 ToolResponseMessage 回写 messages 状态。
// 在 Hook 中写入自定义状态字段(持久化中间结果)
@HookPositions(HookPosition.AFTER_AGENT)
public class SaveStateHook extends AgentHook {
    @Override
    public CompletableFuture> afterAgent(OverAllState state, RunnableConfig config) {
        return CompletableFuture.completedFuture(Map.of("last_result", "..."));  // 示意
    }
}

这对持久化中间结果、或让信息对后续工具/提示可访问很有用。

在 beforeModel / afterModel Hook 中处理消息 —— 继承 MessagesModelHook,直接拿到 List:

import com.alibaba.cloud.ai.graph.RunnableConfig;
import com.alibaba.cloud.ai.graph.agent.hook.HookPosition;
import com.alibaba.cloud.ai.graph.agent.hook.HookPositions;
import com.alibaba.cloud.ai.graph.agent.hook.messages.AgentCommand;
import com.alibaba.cloud.ai.graph.agent.hook.messages.MessagesModelHook;

import [email protected];

import java.util.List;

@HookPositions({HookPosition.BEFORE_MODEL, HookPosition.AFTER_MODEL})
public class MessageProcessHook extends MessagesModelHook {

    @Override
    public AgentCommand beforeModel(List previousMessages, RunnableConfig config) {
        // 模型调用前:处理消息(裁剪 / 注入 / 脱敏)
        return new AgentCommand(previousMessages);  // 示意
    }

    @Override
    public AgentCommand afterModel(List previousMessages, RunnableConfig config) {
        // 模型调用后:处理消息(记录 / 改写结果)
        return new AgentCommand(previousMessages);  // 示意
    }

    @Override
    public String getName() { return "message_process_hook"; }
}

MessagesModelHook(操作 List,返回 AgentCommand)比 ModelHook(操作 OverAllState,返回 Map) 更适合消息级处理;ModelHook 则适合读写任意状态字段。

在 ModelInterceptor 中基于状态创建动态提示 —— 除 Hook 外,ModelInterceptor 也能读取状态并改写提示:

import com.alibaba.cloud.ai.graph.agent.interceptor.ModelCallHandler;
import com.alibaba.cloud.ai.graph.agent.interceptor.ModelInterceptor;
import com.alibaba.cloud.ai.graph.agent.interceptor.ModelRequest;
import com.alibaba.cloud.ai.graph.agent.interceptor.ModelResponse;

import [email protected];

import java.util.Map;

public class DynamicPromptInterceptor extends ModelInterceptor {

    @Override
    public ModelResponse interceptModel(ModelRequest request, ModelCallHandler handler) {
        // 从 context 读取状态(ReactAgent 会把 state.data() 放进 context)
        Map context = request.getContext();

        // 基于对话历史或自定义状态字段创建动态提示(示意)
        SystemMessage dynamicPrompt = new SystemMessage("你是用户 " + context.get("user_id") + " 的助手");

        // 用改写后的请求调用下一层
        ModelRequest newRequest = ModelRequest.builder(request)
                .systemMessage(dynamicPrompt)
                .build();
        return handler.call(newRequest);
    }

    @Override
    public String getName() { return "dynamic_prompt_interceptor"; }
}

ModelRequest.getContext() 里就是 Agent 的状态数据(对话历史、自定义字段),ModelInterceptor 可据此改写 systemMessage / messages 生成动态提示;拦截器通过 .interceptors(...) 注册(见 5.2)。

直接查询当前会话状态:

StateSnapshot snapshot = agent.getCurrentState(
        RunnableConfig.builder().threadId("user-1001").build()
);

第三部分:多智能体与图编排

第 7 章 多智能体(Multi-agent)与 Agent Tool

单个 Agent 能力有限,复杂任务往往需要多个 Agent 分工协作。Spring AI Alibaba 内置了四种编排模式, 它们都在 com.alibaba.cloud.ai.graph.agent.flow.agent 包下。

7.1 Instruction 占位符

子 Agent 的 instruction 支持 {占位符},会被替换为图状态里对应键的值。这是多智能体之间传递数据的核心机制。

① 支持的占位符:

占位符来源说明
{input}内置自动填入最后一条用户消息文本
{outputKey}前序 Agent 的 outputKey如 {article}、{reviewed},把上游结果传给下游
{自定义键}call(Map) 传入如 {topic}、{tone},运行时注入的任意状态键

不支持的占位符(替换时会被剔除):{messages}(消息列表被显式排除)、List 类型的状态值 (Message 类型会转成 getText())。

② 占位符工作原理:

  1. InstructionAgentHook(BEFORE_AGENT)把 instruction 注入成 AgentInstructionMessage(此时 {占位符} 尚未替换);
  2. AgentLlmNode 组装请求时,renderTemplatedUserMessage 把当前图状态 state.data() 处理成参数 Map (剔除 messages 与 List 值,Message 转文本);
  3. 用 Spring AI 的 PromptTemplate(底层 StringTemplate)执行 render(params),把 {key} 替换成对应值;
  4. 渲染后打上 rendered 标记,避免每轮重复替换。

③ 使用示例:

// 例 1:前序 outputKey 传给后序(SequentialAgent)
ReactAgent writer = ReactAgent.builder()
        .name("writer")
        .model(ch@tModel)
        .instruction("写一篇关于 {input} 的文章")
        .outputKey("article")                       // 结果写回 state 的 article 键
        .build();

ReactAgent reviewer = ReactAgent.builder()
        .name("reviewer")
        .model(ch@tModel)
        .instruction("校对该文章:{article}")        // {article} 引用上游 outputKey
        .outputKey("reviewed")
        .build();

// 例 2:自定义键(call(Map) 注入运行时参数)
Map inputs = new HashMap<>();
inputs.put("input", "Spring AI Alibaba");    // 内置键
inputs.put("tone", "通俗易懂");              // 自定义键
agent.call(inputs);                          // instruction 可写「用 {tone} 的语气…」

7.2 SequentialAgent(顺序执行)

多个子 Agent 依次执行,前一个的输出(outputKey)会自动映射为后一个的模板变量:

import com.alibaba.cloud.ai.graph.agent.flow.agent.SequentialAgent;
import com.alibaba.cloud.ai.graph.OverAllState;
import com.alibaba.cloud.ai.graph.RunnableConfig;
import [email protected];

import java.util.Optional;

// 写作 Agent:产出 {article}
ReactAgent writer = ReactAgent.builder()
        .name("writer")
        .model(ch@tModel)
        .instruction("写一篇关于 {input} 的文章")
        .outputKey("article")
        .build();

// 校对 Agent:读取 {article},产出 {reviewed}
ReactAgent reviewer = ReactAgent.builder()
        .name("reviewer")
        .model(ch@tModel)
        .instruction("校对该文章:{article}")
        .outputKey("reviewed")
        .build();

SequentialAgent workflow = SequentialAgent.builder()
        .name("blog_workflow")
        .subAgents(List.of(writer, reviewer))
        .build();

Optional result = workflow.invoke(
        UserMessage.builder().text("Spring AI Alibaba").build(),
        RunnableConfig.builder().threadId("user-1").build()
);

// 使用结果:从 Optional 中取出 outputKey 对应的值
result.ifPresent(state -> {
    String article = (String) state.value("article").orElse("");
    String reviewed = (String) state.value("reviewed").orElse("");
    System.out.println("终稿:" + reviewed);
});

前一个 Agent 的 outputKey(如 article)会自动成为后一个 Agent instruction 里 {article} 占位符的值—— 这正是上一节(7.1)讲的占位符机制在多智能体下的应用。

适用场景:流水线任务(生成 → 校对 → 润色)。

7.3 ParallelAgent(并行执行)

多个子 Agent 并发处理同一输入,结果合并:

import com.alibaba.cloud.ai.graph.agent.flow.agent.ParallelAgent;

import java.util.List;

ParallelAgent parallel = ParallelAgent.builder()
        .name("parallel")
        .subAgents(List.of(agentA, agentB, agentC))
        .mergeOutputKey("merged_result")   // 合并结果写入的 state key
        .build();

执行并获取合并结果:

import com.alibaba.cloud.ai.graph.OverAllState;
import com.alibaba.cloud.ai.graph.RunnableConfig;
import [email protected];

import java.util.Optional;

Optional result = parallel.invoke(
        UserMessage.builder().text("同时分析三个数据源").build(),
        RunnableConfig.builder().threadId("user-1").build()
);

// 从 mergeOutputKey 指定的 key 读取合并结果
result.ifPresent(state -> {
    Object merged = state.value("merged_result").orElse(null);
    System.out.println("合并结果:" + merged);
});

适用场景:互不依赖的子任务并发处理(如同时从多个数据源取数)。

默认用 DefaultMergeStrategy 把各子 Agent 的结果合并成 Map;你也可以实现自定义的 MergeStrategy,控制如何组合多个 Agent 的输出:

import com.alibaba.cloud.ai.graph.agent.flow.agent.ParallelAgent.MergeStrategy;

// 自定义合并策略:例如把各子 Agent 的结果拼接成字符串
MergeStrategy concatStrategy = (subAgentResults, overallState) -> {
    StringBuilder sb = new StringBuilder();
    subAgentResults.forEach((k, v) -> sb.append(v).append("\n"));
    return sb.toString();
};

ParallelAgent parallel2 = ParallelAgent.builder()
        .name("parallel2")
        .subAgents(List.of(agentA, agentB, agentC))
        .mergeOutputKey("merged_result")
        .mergeStrategy(concatStrategy)     // 自定义合并策略
        .build();

7.4 LlmRoutingAgent(LLM 路由)

路由模式:由一个 Router LLM 根据各子 Agent 的 description,动态决定将请求路由到哪个子 Agent。 这种模式非常适合需要智能选择不同专家 Agent 的场景:

import com.alibaba.cloud.ai.graph.agent.flow.agent.LlmRoutingAgent;

LlmRoutingAgent router = LlmRoutingAgent.builder()
        .name("router")
        .model(ch@tModel)                  // 路由决策所用的模型
        .subAgents(weatherAgent, travelAgent, financeAgent)
        .build();

优化路由准确性:LlmRoutingAgent 支持通过 systemPrompt 和 instruction 自定义路由决策行为,提供更精确的路由控制:

LlmRoutingAgent router = LlmRoutingAgent.builder()
        .name("router")
        .model(ch@tModel)
        .systemPrompt("你是智能客服路由,根据用户问题选择最合适的专家")
        .instruction("可用的专家:\n- weather_agent:天气\n- travel_agent:旅游\n- finance_agent:")
        .subAgents(weatherAgent, travelAgent, financeAgent)
        .build();

执行并获取路由结果:

import com.alibaba.cloud.ai.graph.OverAllState;
import com.alibaba.cloud.ai.graph.RunnableConfig;
import [email protected];
import [email protected];

import java.util.List;
import java.util.Optional;

Optional result = router.invoke(
        UserMessage.builder().text("帮我查一下杭州的天气").build(),
        RunnableConfig.builder().threadId("user-1").build()
);

result.ifPresent(state -> {
    // 读取被路由到的子 Agent 的最终回复(messages 最后一条)
    List messages = (List) state.value("messages").orElse(List.of());
    if (!messages.isEmpty()) {
        System.out.println("路由结果:" + messages.get(messages.size() - 1).getText());
    }
});

适用场景:一个入口、多个专业 Agent,按意图分发(类似「智能客服分流」)。

注意类名是 LlmRoutingAgent(LLM 驱动的路由),不要与不带前缀的名称混淆。

7.5 LoopAgent(循环执行)

重复执行某个子 Agent,直到 LoopStrategy 判定满足退出条件:

import com.alibaba.cloud.ai.graph.agent.flow.agent.LoopAgent;

import java.util.List;

LoopAgent loop = LoopAgent.builder()
        .name("loop")
        .subAgents(List.of(subAgent))
        .loopStrategy(new CountLoopStrategy(5))   // 例如:固定循环 5 次
        .build();

执行并获取循环结果:

import com.alibaba.cloud.ai.graph.OverAllState;
import com.alibaba.cloud.ai.graph.RunnableConfig;
import [email protected];
import [email protected];

import java.util.List;
import java.util.Optional;

Optional result = loop.invoke(
        UserMessage.builder().text("帮我优化这份方案").build(),
        RunnableConfig.builder().threadId("user-1").build()
);

result.ifPresent(state -> {
    // 循环结束后,读取最终的 messages(或子 Agent 的 outputKey)
    List messages = (List) state.value("messages").orElse(List.of());
    if (!messages.isEmpty()) {
        System.out.println("循环结果:" + messages.get(messages.size() - 1).getText());
    }
});

适用场景:需要反复迭代直至收敛的任务(如「反复优化方案」)。

7.6 Supervisor(监督者)模式

Supervisor(监督者) 模式:一个「监督者」LLM 节点负责把任务路由给多个「执行者(worker)」, 每个 worker 完成自己的任务后回到监督者,监督者再决定下一步或结束(FINISH)。它适合复杂、动态的任务分解场景。

注意:与前面四种不同,Supervisor 没有专用的类(不像 SequentialAgent),而是用 Graph(StateGraph) 手工编排:一个 supervisor 节点 + 若干 worker 节点 + 条件路由循环。官方示例(MultiAgentSupervisorExample)即如此。

import com.alibaba.cloud.ai.graph.CompiledGraph;
import com.alibaba.cloud.ai.graph.KeyStrategyFactory;
import com.alibaba.cloud.ai.graph.StateGraph;
import com.alibaba.cloud.ai.graph.state.strategy.AppendStrategy;
import com.alibaba.cloud.ai.graph.state.strategy.ReplaceStrategy;

import static com.alibaba.cloud.ai.graph.StateGraph.END;
import static com.alibaba.cloud.ai.graph.StateGraph.START;
import static com.alibaba.cloud.ai.graph.action.AsyncEdgeAction.edge_async;
import static com.alibaba.cloud.ai.graph.action.AsyncNodeAction.node_async;

import java.util.Map;

// 1) 状态策略:messages 追加、next 覆盖
KeyStrategyFactory keyStrategyFactory = () -> Map.of(
        "messages", new AppendStrategy(),
        "next", new ReplaceStrategy()
);

// 2) 节点:supervisor 负责路由,researcher/coder 负责干活
String[] members = {"researcher", "coder"};
SupervisorNode supervisor = new SupervisorNode(ch@tModel, members);
ResearcherNode researcher = new ResearcherNode(ch@tModelWithTool);
CoderNode coder = new CoderNode(ch@tModelWithTool);

// 3) 构图:supervisor 条件路由到 worker,worker 干完回到 supervisor
StateGraph workflow = new StateGraph(keyStrategyFactory)
        .addNode("supervisor", node_async(supervisor))
        .addNode("researcher", node_async(researcher))
        .addNode("coder", node_async(coder))
        .addEdge(START, "supervisor")
        .addConditionalEdges(
                "supervisor",
                edge_async(state -> (String) state.value("next").orElse("FINISH")),
                Map.of("FINISH", END, "researcher", "researcher", "coder", "coder")
        )
        .addEdge("researcher", "supervisor")
        .addEdge("coder", "supervisor");

CompiledGraph graph = workflow.compile();

SupervisorNode 核心逻辑——用 LLM 决定下一步交给哪个 worker:

public static class SupervisorNode implements NodeAction {
    private final ChatClient ch@tClient;
    private final String[] members;

    public SupervisorNode(ChatModel model, String[] members) {
        this.ch@tClient = ChatClient.builder(model).build();
        this.members = members;
    }

    @Override
    public Map apply(OverAllState state) {
        // 读取最后一条消息
        List messages = (List) state.value("messages").orElse(List.of());
        String lastText = messages.get(messages.size() - 1).toString();

        // 让 LLM 决定路由
        String membersList = String.join(", ", members);
        String result = [email protected]()
                .system("你是 supervisor,负责在以下 worker 间分配任务:" + membersList
                        + "。只返回 worker 名称或 FINISH。")
                .user("用户消息:" + lastText)
                .call()
                .content();

        // 归一化:只保留 worker 名称或 FINISH
        return Map.of("next", normalizeRoute(result, members));
    }

    private String normalizeRoute(String result, String[] members) {
        if (result == null) return "FINISH";
        String r = result.trim().toLowerCase();
        if (r.contains("finish")) return "FINISH";
        for (String m : members) {
            if (r.equals(m.toLowerCase()) || r.contains(m.toLowerCase())) return m;
        }
        return members.length > 0 ? members[0] : "FINISH";
    }
}

执行后:supervisor 通过 state.value("next") 决定流转,worker 干完 addEdge 回 supervisor 循环, 直到 supervisor 返回 FINISH 才路由到 END;最终 messages 里是 supervisor 与各 worker 的完整对话记录。

7.7 智能体作为工具(Agent Tool)

多智能体协作的另一种方式是「把一个 Agent 包装成工具,供另一个 Agent 调用」。Spring AI Alibaba 提供了 AgentTool.create(...),把子 Agent 封装成 ToolCallback——工具名取子 Agent 的 name、描述取子 Agent 的 description,上层 Agent 通过 Function Calling 触发,被调用时内部运行子 Agent 并返回其最终回复。

import com.alibaba.cloud.ai.graph.agent.AgentTool;
import com.alibaba.cloud.ai.graph.agent.ReactAgent;
import [email protected];
import org.springframework.ai.tool.ToolCallback;

// 1) 定义子 Agent(天气专家)
ReactAgent weatherAgent = ReactAgent.builder()
        .name("weather_agent")
        .description("查询指定城市的天气")   // 会作为工具的 description
        .model(ch@tModel)
        .build();

// 2) 把子 Agent 包装成工具
ToolCallback weatherAsTool = AgentTool.create(weatherAgent);

// 3) 上层 Agent 挂载这个「Agent 工具」
ReactAgent orchestrator = ReactAgent.builder()
        .name("orchestrator")
        .model(ch@tModel)
        .tools(weatherAsTool)
        .build();

// 4) 上层 Agent 遇到天气问题会调用 weather_agent 这个工具
AssistantMessage reply = orchestrator.call("北京今天天气怎么样?");
System.out.println(reply.getText());

这与「把 Agent 作为子图节点嵌入」一脉相承:ReactAgent.asNode(...) 可把 Agent 转成图节点, StateGraph.addNode(id, stateGraph) 也支持子图嵌入。AgentTool 则是「子 Agent 即工具」的封装, 内部用 MethodToolCallback 反射调用 executeAgent(String, ToolContext),把输入传给子 Agent 并返回其 AssistantMessage。

小结:SequentialAgent / ParallelAgent / LlmRoutingAgent / LoopAgent 是框架内置的高层编排; 「Agent 作为工具/节点」则是更灵活的组装方式,本质都依赖 Graph 的子图能力。

7.8 自定义 Agent 上下文(Context Engineering)

Multi-agent 设计的核心是上下文工程——决定每个 Agent 看到什么信息。系统的质量在很大程度上取决于此: 既要让每个 Agent 拿到执行任务所需的正确数据,又要避免无关信息淹没它。

① 传递哪些部分:includeContents —— 决定子 Agent(作为子图节点)是否继承父图的对话历史(messages):

ReactAgent child = ReactAgent.builder()
        .name("child")
        .model(ch@tModel)
        .includeContents(false)   // 不继承父图 messages,只处理自己的输入
        .build();
  • includeContents(true)(默认):子 Agent 拿到父图的完整 messages;
  • includeContents(false):子 Agent 只拿其他状态字段,不继承对话历史——适合「独立子任务」场景。

② 包含 / 排除中间推理:returnReasoningContents —— 决定子 Agent 完成后,向上返回「全部消息」还是「只返回最终回复」:

ReactAgent child = ReactAgent.builder()
        .name("child")
        .model(ch@tModel)
        .returnReasoningContents(false)   // 默认:只返回最终回复,隐藏中间推理
        .build();
  • returnReasoningContents(false)(默认):只返回最后一条 AssistantMessage(最终答案);
  • returnReasoningContents(true):返回全部消息,包含中间思考 / 工具调用过程。

③ 定制提示:instruction / systemPrompt —— 为每个子 Agent 定制专门的提示(详见 3.2):

ReactAgent writer = ReactAgent.builder()
        .name("writer")
        .model(ch@tModel)
        .systemPrompt("你是一位资深技术写作者")     // 固定人设(system 角色)
        .instruction("写一篇关于 {input} 的文章")    // 任务模板(带占位符)
        .build();

④ 自定义输入 / 输出格式:inputSchema / outputSchema、inputType / outputType —— 约束每个 Agent 的输入输出结构(详见 3.5):

ReactAgent extractor = ReactAgent.builder()
        .name("extractor")
        .model(ch@tModel)
        .outputType(ContactInfo.class)     // 输出约束为 ContactInfo 结构
        .build();

相关机制:outputKey 决定子 Agent 把结果写回 state 的哪个键;instruction 里的 {占位符} 决定子 Agent 拿到上游的哪些 outputKey(见 7.2);description 用于路由选择(见 7.4)。

7.9 自定义工作流:FlowAgent

除了内置的四种编排,Spring AI Alibaba 还提供了 FlowAgent 抽象类,允许你创建自定义的 Agent 工作流模式。 通过继承 FlowAgent 并实现 buildSpecificGraph(...),你可以实现任意复杂的多 Agent 协作模式:

import com.alibaba.cloud.ai.graph.CompileConfig;
import com.alibaba.cloud.ai.graph.StateGraph;
import com.alibaba.cloud.ai.graph.agent.Agent;
import com.alibaba.cloud.ai.graph.agent.flow.agent.FlowAgent;
import com.alibaba.cloud.ai.graph.agent.flow.builder.FlowGraphBuilder;
import com.alibaba.cloud.ai.graph.exception.GraphStateException;

import java.util.List;

public class MyCustomWorkflow extends FlowAgent {

    protected MyCustomWorkflow(String name, String description,
            CompileConfig compileConfig, List subAgents) {
        super(name, description, compileConfig, subAgents);
    }

    @Override
    protected StateGraph buildSpecificGraph(FlowGraphBuilder.FlowGraphConfig config) throws GraphStateException {
        // 自定义图结构:addNode / addEdge 定义任意多 Agent 协作
        StateGraph graph = new StateGraph();
        // ...
        return graph;
    }
}

FlowAgent 是 SequentialAgent / ParallelAgent / LlmRoutingAgent / LoopAgent 的共同父类, 它们内部都是「继承 FlowAgent + 实现 buildSpecificGraph」实现的;你可用同样的方式扩展自己的编排模式 (完整落地还需配套一个继承 FlowAgentBuilder 的自定义 Builder)。


第 8 章 Workflow 与 Graph

8.1 定位:把工作流建模为图

如第 0 章所述,Graph 是 Agent Framework 的底层运行时,也是一个低级的工作流与多智能体编排框架。 它把「智能体工作流」抽象成一张有向图(DAG):

  • 节点(Node):一个具体的操作(一次模型调用、一段业务逻辑、一个子图);
  • 边(Edge):节点之间的流转;
  • 状态(State):在整张图中流动的共享数据。

核心类(com.alibaba.cloud.ai.graph 包):

类职责
StateGraph定义节点与边的工作流主类
OverAllState全局状态,承载流转中的共享数据
CompiledGraphStateGraph 编译后的可执行形态
KeyStrategy状态合并策略(覆盖 / 追加)

8.2 StateGraph 与状态

创建图时,可以为每个状态 key 指定合并策略:ReplaceStrategy(覆盖)或 AppendStrategy(追加)。

import com.alibaba.cloud.ai.graph.KeyStrategy;
import com.alibaba.cloud.ai.graph.KeyStrategyFactory;
import com.alibaba.cloud.ai.graph.StateGraph;
import com.alibaba.cloud.ai.graph.state.strategy.AppendStrategy;
import com.alibaba.cloud.ai.graph.state.strategy.ReplaceStrategy;

import java.util.Map;

KeyStrategyFactory keyStrategy = () -> Map.of(
        "result", new ReplaceStrategy(),   // 每次覆盖
        "logs",   new AppendStrategy()     // 每次追加
);

StateGraph graph = new StateGraph(keyStrategy);

OverAllState 提供 value(key)(返回 Optional)与 data()(返回整个 Map)来读写状态。

8.3 节点(Node)

用 addNode(id, action) 添加节点。节点动作可以是同步或异步,借助 node_async 工厂方法简化书写:

import static com.alibaba.cloud.ai.graph.action.AsyncNodeActionWithConfig.node_async;

graph.addNode("step_a", node_async(state -> Map.of("result", "A 完成")));
graph.addNode("step_b", node_async(state -> Map.of("result", "B 完成")));

节点的返回值会被合并进全局状态(按 key 的合并策略)。节点也可以嵌入子图: addNode(id, stateGraph) 或 addNode(id, compiledGraph)。

8.4 边(Edge)与条件边

普通边表示固定流转,条件边则根据当前状态动态决定去向:

import static com.alibaba.cloud.ai.graph.StateGraph.START;
import static com.alibaba.cloud.ai.graph.StateGraph.END;
import static com.alibaba.cloud.ai.graph.action.AsyncEdgeAction.edge_async;

// 普通边
graph.addEdge(START, "step_a");
graph.addEdge("step_a", "step_b");
graph.addEdge("step_b", END);

// 条件边:EdgeAction 返回目标节点名
graph.addConditionalEdges(
        "step_b",
        edge_async(state -> {
            String flag = state.value("result").orElse("").toString();
            return flag.contains("A") ? "step_a" : END;
        }),
        Map.of("step_a", "step_a", END, END)
);

START / END 是 StateGraph 提供的常量,表示图的入口与出口。

8.5 编译与执行

StateGraph 需要编译成 CompiledGraph 才能执行:

import com.alibaba.cloud.ai.graph.CompiledGraph;
import com.alibaba.cloud.ai.graph.OverAllState;

import java.util.Optional;

CompiledGraph compiled = graph.compile();   // 默认使用 MemorySaver

Optional output = compiled.invoke(Map.of("input", "hello"));
  • compile():用默认配置(内存存档器)编译;
  • compile(CompileConfig):自定义编译配置(存档器、中断点等);
  • invoke(...):同步执行;另有 stream(...) 支持流式返回。

8.6 持久化、中断与恢复

在第 6 章的基础上,Graph 层的中断/恢复能力更显式。通过 CompileConfig 指定存档器与中断点:

import com.alibaba.cloud.ai.graph.CompileConfig;
import com.alibaba.cloud.ai.graph.checkpoint.config.SaverConfig;
import com.alibaba.cloud.ai.graph.checkpoint.savers.MemorySaver;

CompiledGraph compiled = graph.compile(
        CompileConfig.builder()
                .saverConfig(SaverConfig.builder().register(new MemorySaver()).build())
                .interruptAfter("review")    // 在 review 节点执行后中断
                .build()
);

配合检查点,可以在中断后查询、修改状态,并从断点恢复执行:

  • compiled.getState(RunnableConfig):查询会话当前状态;
  • compiled.updateState(RunnableConfig, values):修改状态;
  • 恢复执行:以相同 threadId 从最近的检查点继续。

这就是第 5 章「人工介入」(5.7)在 Graph 层的机制基础。

8.7 并行聚合

当一个节点通过并行条件边(addParallelConditionalEdges)路由到多个分支时,需要约定「何时算完成」。 框架提供两种聚合策略:

  • AllOf:所有并行分支都成功才算完成;
  • AnyOf:任一分支成功即可继续。

这让「多分支并行、结果汇聚」的逻辑从业务代码下沉为图 DSL 的内置能力。

8.8 可视化导出

StateGraph 支持把图导出为 PlantUML 或 Mermaid 图,便于分享与排查:

import com.alibaba.cloud.ai.graph.GraphRepresentation;

GraphRepresentation mermaid = graph.getGraph(GraphRepresentation.Type.MERMAID, "我的工作流");
System.out.println(mermaid.content());

把导出的 Mermaid/PlantUML 代码粘贴到对应渲染器,即可得到工作流的可视化流程图。


第 9 章 Plan-and-Execute 实战

现在用 StateGraph 落地第 1 章的 Plan-and-Execute(计划-执行) 范式:先规划、再逐步执行。

import com.alibaba.cloud.ai.graph.*;
import com.alibaba.cloud.ai.graph.state.strategy.ReplaceStrategy;

import static com.alibaba.cloud.ai.graph.StateGraph.START;
import static com.alibaba.cloud.ai.graph.StateGraph.END;
import static com.alibaba.cloud.ai.graph.action.AsyncNodeActionWithConfig.node_async;
import static com.alibaba.cloud.ai.graph.action.AsyncEdgeAction.edge_async;

import java.util.List;
import java.util.Map;

// 1. 状态策略
KeyStrategyFactory keyStrategy = () -> Map.of(
        "plan", new ReplaceStrategy(),
        "stepIndex", new ReplaceStrategy()
);

StateGraph graph = new StateGraph(keyStrategy);

// 2. 规划节点:把任务拆成步骤清单
graph.addNode("planner", node_async(state -> {
    String task = state.value("task").orElse("").toString();
    List plan = List.of("步骤1:收集资料", "步骤2:撰写初稿", "步骤3:校对定稿");
    return Map.of("plan", plan, "stepIndex", 0);
}));

// 3. 执行节点:执行当前步骤
graph.addNode("executor", node_async(state -> {
    List plan = (List) state.value("plan").orElse(List.of());
    int index = (Integer) state.value("stepIndex").orElse(0);
    System.out.println("执行:" + plan.get(index));
    return Map.of("stepIndex", index + 1);
}));

// 4. 连线:START -> planner -> executor,executor 根据进度决定继续或结束
graph.addEdge(START, "planner");
graph.addEdge("planner", "executor");
graph.addConditionalEdges(
        "executor",
        edge_async(state -> {
            List plan = (List) state.value("plan").orElse(List.of());
            int index = (Integer) state.value("stepIndex").orElse(0);
            return index < plan.size() ? "executor" : END;
        }),
        Map.of("executor", "executor", END, END)
);

// 5. 编译并执行
CompiledGraph compiled = graph.compile();
compiled.invoke(
        Map.of("task", "写一篇 Spring AI Alibaba 教程"),
        RunnableConfig.builder().threadId("plan-execute-1").build()
);

执行输出:

执行:步骤1:收集资料
执行:步骤2:撰写初稿
执行:步骤3:校对定稿

对照第 1 章的概念:

  • planner 节点 = Plan(规划);
  • executor 节点 = Execute(执行);
  • addConditionalEdges 的 edge_async = 判断「是否还有未执行步骤」的路由逻辑(循环控制)。

若要加入 Reflect(反思),可在 executor 之后再接一个 reflector 节点,评估执行结果, 用条件边决定「返回 executor 修正」还是「进入 END」。这正是 Plan-Act-Reflect 的图实现。

对比:ReactAgent 适合「边走边想」的交互式任务;而用 StateGraph 编排的 Plan-and-Execute 更适合步骤清晰、需要整体规划的复杂任务。两者底层都是同一套图机制。


第四部分:进阶与调试

第 10 章 分布式智能体(A2A Agent)

A2A(Agent-to-Agent) 是 Google 提出的智能体间通信协议。Spring AI Alibaba 对其提供了支持, 用于构建分布式多智能体场景:不同的 Agent 部署在不同的服务/节点上,通过注册中心互相发现、互相调用。

其落地方案是 Nacos 作为注册中心,对应 starter:


    com.alibaba.cloud.ai
    spring-ai-alibaba-starter-a2a-nacos

工作方式大致为:

  1. 各 Agent 服务启动后向 Nacos 注册自己;
  2. 需要协作时,Agent 通过 Nacos 发现目标 Agent 的地址;
  3. 通过 A2A 协议发起跨服务调用。

本文对 A2A 仅作概念介绍。它适合把 Agent 拆成独立部署的微服务、跨团队/跨系统协作的场景; 若你暂时不需要分布式,可先跳过本章。


第 11 章 spring-ai-alibaba-studio 调试

11.1 Studio 是什么

spring‑ai‑alibaba‑studio 是 Spring AI Alibaba 配套嵌入式 Web 可视化调试工具,用于本地开发阶段调试 LLM ChatBot、Agent、Graph 工作流与 RAG 应用。可直接嵌入 Spring Boot 服务,无需单独部署前端。该工具定位为开发调试工具,和面向生产运维的 spring‑ai‑alibaba‑admin 有所区分;Studio 持久化能力较弱,不建议暴露到公网生产环境。支持 Agent 多轮对话、链路追踪、模型参数可视化调参、Graph 工作流可视化、RAG 检索调试等功能。

11.2 引入依赖

在 pom.xml 中加入(版本与框架保持一致):


    com.alibaba.cloud.ai
    spring-ai-alibaba-studio
    1.1.2.2

11.3 嵌入式模式(推荐)

引入依赖后,直接启动你的 Spring Boot 应用,然后浏览器访问:

http://localhost:{your-port}/ch@tui/index.html

即可在页面中选择并调试你的 Agent,进行多轮对话、查看工具调用链路。

11.4 独立模式

也可以把 Studio 的前端(agent-ch@t-ui)单独跑起来,对接后端:

git clone https://github.com/alibaba/spring-ai-alibaba.git
cd spring-ai-alibaba/spring-ai-alibaba-studio/agent-ch@t-ui

pnpm install   # 或 npm install
pnpm dev       # 或 npm run dev

前端默认运行在 http://localhost:3000,在 .env.development 中配置后端地址与目标 Agent:

NEXT_PUBLIC_API_URL=http://localhost:8080
NEXT_PUBLIC_APP_NAME=research_agent
NEXT_PUBLIC_USER_ID=user-001

11.5 开启 DEBUG 日志

调试时打开日志,可观察完整的 tool_calls 交互过程(模型是否发起调用、参数是否符合 Schema、工具是否执行):

logging:
  level:
    org.springframework.ai: DEBUG
    com.alibaba.cloud.ai: DEBUG

11.6 常见调试场景

  • Agent 对话:在 Chat UI 里直接对话,观察多轮上下文是否正确;
  • 链路追踪:查看每一步的思考、工具调用入参与返回,定位「为什么调用了错误的工具」;
  • Graph 可视化:直观查看分支路由、并行节点、中断与恢复;
  • 参数调参:可视化调整 temperature、topP、maxTokens 等,无需改代码重启。

11.7 常见问题:UI 列表为空

如果你的 Spring Boot 项目没有把 ReactAgent / CompiledGraph 注册暴露给 Studio,UI 的列表会是空的。

Agent / Graph 没有注册为 Spring Bean(最高频):

❌ 错误写法:在 main 方法内部 new 出来的 ReactAgent,不是 Spring Bean,Studio 扫描不到:

// main 里直接 new,不会被 Spring 容器管理,Studio 无法发现
ReactAgent agent = ReactAgent.builder()
        .model(ch@tModel)
        .tools(weatherTool)
        .build();

✅ 正确写法:用 @Bean 注入到 Spring 上下文:

@Bean
public ReactAgent reactAgent(ChatModel ch@tModel) {
    return ReactAgent.builder()
            .model(ch@tModel)
            .tools(weatherTool())
            .build();
}

CompiledGraph 同理,也要 return 成 @Bean,Studio 才会识别 Graph。


附录:核心 API 速查

本附录集中列出贯穿全文的三个核心数据类的常用方法(版本 1.1.2.2)。

A.1 RunnableConfig(运行时配置)

com.alibaba.cloud.ai.graph.RunnableConfig —— 运行时配置,通过 RunnableConfig.builder()...build() 创建, 传给 agent.call(...) / graph.invoke(...),控制执行(会话 ID、检查点、元数据等)。

分类方法说明
会话threadId() / Builder threadId(String)获取 / 设置会话 ID(短期记忆的关键)
检查点checkPointId() / Builder checkPointId(String)获取 / 设置检查点 ID
恢复nextNode() / Builder nextNode(String)从指定节点恢复
元数据metadata() / metadata(String key)读取元数据 Map / 指定 key(返回 Optional)
元数据Builder addMetadata(key, value)添加自定义元数据(工具 / 提示可读)
上下文context() / clearContext()读取 / 清空上下文
中断Builder addHumanFeedback(InterruptionMetadata)提交人工反馈(HITL)
中断Builder resume()恢复执行
其他Builder mergeReasoningContent(boolean)是否合并思考内容
其他Builder addStateUpdate(map)追加状态更新

A.2 OverAllState(图状态)

com.alibaba.cloud.ai.graph.OverAllState —— 图状态,节点间流转的共享数据容器。

分类方法说明
读取value(key)返回 Optional
读取value(key, Class)按类型读取
读取value(key, default)带默认值读取
读取data()返回整个状态 Map
写入updateState(map)合并更新状态
写入updateStateWithKeyStrategies(map, strategies)按合并策略更新
写入registerKeyAndStrategy(key, strategy)注册 key 的合并策略
其他keyStrategies() / input(map) / getStore()合并策略 / 输入 / 存储
其他clear() / reset() / snapShot() / cover(other)清理 / 重置 / 快照 / 覆盖
常量DEFAULT_INPUT_KEY / MARK_FOR_REMOVAL默认输入键 / 删除标记

A.3 ToolContext(工具上下文)

[email protected] —— 工具上下文,工具方法通过它访问状态与历史。

方法说明
getContext()返回上下文 Map
getToolCallHistory()返回工具调用历史 List
常量 TOOL_CALL_HISTORY历史记录的 key

配套工具类 ToolContextHelper(com.alibaba.cloud.ai.graph.agent.tools)提供更便捷的访问: getConfig(toolContext) → Optional、getState(toolContext) → Optional、 getMetadata(toolContext, key, type) → Optional(详见第 6 章 6.5)。

喜欢(0)