最新下载
热门教程
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
Spring AI Alibaba Agent 开发入门与实战
时间:2026-09-12 17:20:01 编辑:袖梨 来源:一聚教程网
在 Spring 应用中接入大模型并不难,真正复杂的是如何让模型调用工具、维护状态,并按照可控流程持续完成任务。Spring AI Alibaba 在 Spring AI 之上提供 Agent Framework 与 Graph 运行时,将 ReAct、多智能体协作和工作流编排纳入统一开发体系。接下来将从关键概念和环境配置入手,逐步理解核心 API 与实际构建方式。
从 Agent 到 Graph,用阿里云百炼(DashScope)模型构建智能体工作流
- 框架版本:Spring AI Alibaba
1.1.2.2 - Spring Boot 版本:
3.5.9 - 运行环境:JDK
21+、Maven 3.6+
第一部分:基础与概念
第 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 环境准备
| 项目 | 要求 |
|---|---|
| JDK | 21+ |
| 构建工具 | Maven 3.6+ |
| 模型平台 | 阿里云百炼(DashScope) |
| API Key | 在百炼控制台申请 |
获取 API Key 后,建议以环境变量的方式注入(避免硬编码到代码):
export AI_DASHSCOPE_API_KEY=sk-xxxxxx
0.3 依赖引入
在 pom.xml 中引入以下依赖(版本统一由 Spring AI Alibaba 的 BOM 管理):
<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>
<dependencyManagement>
<dependencies>
<!-- Spring AI 基础(消息、ChatModel、工具等) -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>${spring-ai.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Agent Framework / Graph 核心 -->
<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>
<!-- DashScope / 百炼 等扩展 -->
<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>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- 百炼 / DashScope 模型接入 -->
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
</dependency>
<!-- Agent Framework(高层抽象) -->
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-agent-framework</artifactId>
</dependency>
<!-- Graph Core(底层运行时,Agent Framework 已传递依赖,可按需显式引入) -->
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-graph-core</artifactId>
</dependency>
</dependencies>
说明:
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(智能体范式) 的核心区别在于:模型可以自主决定「下一步做什么」。
一个智能体通常具备:
- 推理能力:理解任务,拆解出执行步骤;
- 行动能力:调用工具(查天气、查数据库、发请求……)去改变外部世界;
- 观察能力:读取工具返回的结果,并据此调整下一步。
「推理 → 行动 → 观察 → 再推理」这个循环,就是智能体的灵魂。它不再是一条直线, 而是一个由模型主导的循环——走到哪一步、调用哪个工具、什么时候停下来,都由模型自己判断。
一句话: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 结果 │
│ 观察结果 │ │ │
└─────────────┘ └─────────────┘
- Reasoning:模型分析当前状态,决定下一步动作(是否调用工具、调用哪个);
- Acting:执行工具调用;
- Observing:把工具返回结果作为新的上下文,喂回给模型;
- 循环:重复上述过程,直到模型认为任务完成、不再发起工具调用。
在 Spring AI Alibaba 中,ReactAgent 就是这一范式的生产级实现:它内部用一张 StateGraph
把「LLM 节点」和「工具节点」串成循环,模型每发起一次工具调用,就路由到工具节点执行,
执行完再回到 LLM 节点继续推理。
1.4 Plan-and-Execute(计划-执行)
Plan-and-Execute 是另一种智能体范式,思路是「先规划,再执行」:
- Plan(规划):模型先把任务拆解成一个步骤清单;
- Execute(执行):按照清单逐步执行每个步骤;
- (可选)Reflect(反思):执行完后评估结果,必要时修正计划重新执行。
一句话:Plan-and-Execute = 模型「现场写任务清单」,然后按清单跑。
它与 ReAct 的区别在于「粒度」:
- ReAct 是边走边想:每一步都重新推理下一步,适合短链路的交互式任务;
- Plan-and-Execute 是先想好再走:先制定整体计划,再按部就班执行,适合步骤多、结构清晰的复杂任务。
在本框架里,Plan-and-Execute 可以用 Graph(StateGraph) 来落地——这正是第 9 章要做的实战。
1.5 概念 → 框架映射
学完概念,先给一张「概念对应到本框架的哪个类」的速查表,后面章节会逐一展开:
| 概念 | 对应实现 |
|---|---|
| Agentic / ReAct | ReactAgent(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
在写智能体之前,先认识三个最基础的组件。官方文档对它们的定义非常精炼:
- Models(模型):
ChatModelAPI 提供将 AI 驱动的聊天补全能力集成进应用的能力; - Messages(消息):模型交互的基本单元,代表模型的输入与输出,携带对话状态所需的内容与元数据;
- Tools(工具):Agents 调用来执行操作的组件。
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 的消息类型。核心的几种:
| 类 | 角色 | 说明 |
|---|---|---|
SystemMessage | system | 系统提示词,设定模型人设/规则 |
UserMessage | user | 用户输入 |
AssistantMessage | assistant | 模型回复(含可能的工具调用) |
ToolResponseMessage | tool | 工具执行结果,回传给模型 |
它们都在 [email protected] 包下,共同实现 Message 接口。
手工构造一段对话:
import [email protected].*;
List<Message> 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(description = "..."):工具描述,模型据此判断「什么时候该用这个工具」,务必写清楚;@ToolParam(description = "..."):参数描述,帮助模型正确传参;- 方法返回的字符串会被作为
ToolResponseMessage回传给模型,成为下一轮推理的「观察」输入。
这些 @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,它有两个核心节点:
_AGENT_MODEL_(模型节点,AgentLlmNode):调用大模型,产出推理结果或工具调用请求;_AGENT_TOOL_(工具节点,AgentToolNode):执行模型请求的工具调用。
两者之间通过条件边(Conditional Edge)连接成循环:
- 入口进入模型节点 → 模型推理;
- 若模型产出了
tool_call(AssistantMessage.hasToolCalls() == true)→ 路由到工具节点执行; - 工具执行结果(
ToolResponseMessage)回传给模型节点 → 模型继续推理; - 若模型不再发起工具调用 → 路由到
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 的区别:
tools(...):传入已经包装好的ToolCallback(如FunctionToolCallback、MCP 工具等);methodTools(...):传入任意带@Tool注解方法的对象(如@ComponentBean),框架会通过ToolCallbacks.from(...)反射扫描其@Tool方法,自动包装成MethodToolCallback并注册。
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 | — | 每次对话的动态输入 |
systemPrompt:会作为SystemMessage(system 角色)放进模型请求,不参与模板替换, 适合写「你是一个中文天气助手」这类固定人设。instruction:由框架默认注入的InstructionAgentHook在每次运行前处理,是一个带占位符的模板 (user 角色)。发送给模型前,{input}、{article}等占位符会被替换成状态里的实际值, 适合写「写一篇关于 {input} 的文章」这类任务模板。- 用户提示词:即每次
call(...)传入的UserMessage,是动态输入。框架会把最后一条用户消息的文本 同时写入input状态键——它正是instruction里{input}占位符的取值来源。
占位符替换的具体机制:instruction 先被 InstructionAgentHook 注入成 AgentInstructionMessage,
随后 AgentLlmNode 在组装请求时用 Spring AI 的 PromptTemplate(底层 StringTemplate)执行 render(params),
把 {input} 替换成状态里 input 键的值(即最后一条用户消息的文本);渲染完成后打上标记,避免每轮重复替换。
占位符的写法就是 {keyName}(StringTemplate 语法),keyName 必须是图状态里的一个键:
- 内置键
{input}:用call("...")/call(UserMessage)时,框架自动把最后一条用户消息文本写入input键; - 自定义键:用
call(Map)重载传入——messages和input是保留键,其余任意键都会作为状态键, 可在instruction里用{keyName}引用。
// 1) 内置键 {input}
agent.call("杭州今天天气怎么样?"); // 自动写入 input 键
// 2) 自定义键 {topic} / {tone}
Map<String, Object> 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 重载:
Function<I, O>(单参数):工具只关心输入I、返回O,最简洁;BiFunction<I, ToolContext, O>(带工具上下文):额外拿到ToolContext,可读取上下文与工具调用历史。
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<Message>)
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,可通过.getText()取文本内容; - 带上
RunnableConfig.threadId(...)后,同一threadId的多次调用会共享会话上下文(见第 6 章)。
AssistantMessage.metadata 常用 Key:
call(...) 返回的 AssistantMessage 除了文本,还通过 getMetadata() 携带一份 Map<String, Object>。
它是厂商扩展字段——并非所有模型都返回全部 key,不同服务商返回的 key 也不一样。
metadata 由两部分构成:
- Spring AI 统一封装(响应层
ChatResponseMetadata):id、model、usage(token 用量)、rateLimit(限流)、promptMetadata; - 各厂商原生返回(消息层
AssistantMessage.metadata),key 名随厂商而异。
常见的重要 key(以 1.1.2 实测源码为准):
| key | 含义 | DashScope | OpenAI | DeepSeek |
|---|---|---|---|---|
finishReason | 生成结束原因(如 stop / length / tool_calls) | ✅ | ✅ | ✅ |
reasoningContent | 思考/推理内容(thinking 模型) | ✅ | ✅ | ✅ |
usage | token 用量 | ✅ | ✅ | ✅ |
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<ContactInfo> 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 采用渐进式披露策略,避免把所有技能内容一次性塞进上下文:
- 系统提示里只注入技能列表(每个技能的
name、description、路径等轻量信息); - 模型判断某个技能与当前任务相关时,调用
read_skill(skill_name)按需加载完整的SKILL.md; - 再按
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_AGENT | Agent 开始前 |
AFTER_AGENT | Agent 结束后 |
BEFORE_MODEL | 每次调用模型前 |
AFTER_MODEL | 每次调用模型后 |
通过 @HookPositions(...) 注解声明 Hook 生效的位置(可指定多个)。
5.1 能做什么?
Hooks 和 Interceptors 覆盖四类能力:
| 类别 | 说明 | 典型场景 |
|---|---|---|
| 坚控 | 记录日志、分析、调试跟踪 Agent 行为 | 耗时统计、链路追踪 |
| 修改 | 转换提示、工具选择和输出格式 | 改写请求、格式化结果 |
| 控制 | 添加重试、回退和提前终止逻辑 | 重试、熔断、提前终止 |
| 强制执行 | 应用速率限制、护栏和 PII 检测 | 限流、脱敏、安全护栏 |
5.2 内置实现
框架内置了大量 Hooks(生命周期级)与 Interceptors(模型/工具调用级):
Hooks(生命周期级):
| Hook | 作用 |
|---|---|
SummarizationHook | 消息压缩 / 上下文摘要 |
HumanInTheLoopHook | 人机协同(人工审批,见 5.7) |
ModelCallLimitHook / ToolCallLimitHook | 模型 / 工具调用次数限制 |
PIIDetectionHook | PII 敏感信息检测 |
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 | 工具调用重试(支持特定异常 + 指数退避) |
TodoListInterceptor | Planning(规划):引导模型用 write_todos 拆解任务 |
ToolSelectionInterceptor | LLM 工具选择器:先用 LLM 筛出相关工具 |
ToolEmulatorInterceptor | LLM 工具模拟器:用 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<Map<String, Object>> beforeAgent(OverAllState state, RunnableConfig config) {
start = System.currentTimeMillis();
return CompletableFuture.completedFuture(Map.of());
}
@Override
public CompletableFuture<Map<String, Object>> 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<Message> previousMessages, RunnableConfig config) {
// 超过上限:只保留最近 N 条
List<Message> 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<String, ToolCallResponse> 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 的选择与调用顺序
选择:
- 需要按「执行阶段」插入逻辑(Agent 开始/结束、每次调模型前后)→ 用 Hook;
- 需要包裹「模型调用」或「工具调用」本身(重试、缓存、改写请求/结果)→ 用 Interceptor。
调用顺序:
- Hook:按
Position分阶段执行(BEFORE_AGENT → BEFORE_MODEL →(模型/工具循环)→ AFTER_MODEL → AFTER_AGENT); 同一 Position 内的多个 Hook 按Prioritized.getOrder()升序执行(InstructionAgentHook的 order 为 -100,最先)。 - Interceptor:以装饰器链(
InterceptorChain)的形式包裹,每个拦截器调用handler.call(...)转发到下一层, 因此按注册顺序「外层先入、内层后入」,重试/缓存等逻辑在链上叠加。
5.6 Context Editing(上下文编辑)
Context Editing 指在每次调用前/后直接增删改对话历史(List<Message>)。它通过
MessagesAgentHook / MessagesModelHook 实现——覆写 beforeAgent(List<Message>, config) 返回一个
AgentCommand(携带修改后的消息列表),框架会用其替换当前消息。
5.3 的「消息上限限制」就是 Context Editing 的一个例子(裁剪旧消息)。其他典型用途:
- 注入 / 更新指令消息;
- 脱敏后重写历史消息(配合 PII 检测);
- 裁剪过长的上下文,控制 token 消耗。
示例(注入系统提示 + 裁剪消息):
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<Message> previousMessages, RunnableConfig config) {
List<Message> 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();
工作流程:
- Agent 推理后,决定调用
transfer_money; HumanInTheLoopHook(在AFTER_MODEL位置)拦截,产生中断(InterruptionMetadata),暂停执行;- 人类对每个工具调用给出反馈:APPROVED(同意) / EDITED(修改参数) / REJECTED(拒绝);
- 框架根据反馈恢复执行(
resume),继续后续推理。
在更底层的 Graph 里,对应的是 InterruptableAction 接口——它提供两个钩子点:
public interface InterruptableAction {
// 节点执行前调用,可阻止执行
Optional<InterruptionMetadata> interrupt(String nodeId, OverAllState state, RunnableConfig config);
// 节点执行后调用,可检查结果并中断
default Optional<InterruptionMetadata> interruptAfter(String nodeId, OverAllState state,
Map<String, Object> actionResult, RunnableConfig config) {
return Optional.empty();
}
}
配合检查点与 resume 机制,即可实现审批流、多轮对话式交互等「人机协同」场景(详见第 8 章的中断恢复)。
第 6 章 Memory(记忆)
6.1 三层记忆:短期 / 中期 / 长期
Agent 的「记忆」通常分三层:
| 层级 | 内容 | 存储 / 技术 | 在本框架中的实现 |
|---|---|---|---|
| 短期记忆 | 最近 10–20 条对话记录 | Redis / DB 等 | CheckpointSaver + threadId(内置) |
| 中期记忆 | 提取与当前对话相关联的聊天记录 | RAG(检索增强) | 结合向量库做检索 |
| 长期记忆 | 核心结构化数据(用户画像、经验信息) | 结构化存储 | 持久化事实,如「我男性、35 岁」 |
- 短期记忆:保存当前会话的近期对话记录,靠 checkpointer 实现(框架内置);
- 中期记忆:从历史聊天记录中检索出与当前对话相关的内容,用 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 | 文件系统 | 单机持久化 |
RedisSaver | Redis | 分布式部署(生产常用) |
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<String, ToolContext, String> {
@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等便捷方法。
从工具写入 —— 要在执行期间修改短期记忆(状态),有两种途径:
- 在 Hook 中更新状态:Hook 的
beforeXxx/afterXxx返回的Map<String, Object>会合并进图状态; - 工具返回的信息更新状态:工具返回的结果会作为
ToolResponseMessage回写messages状态。
// 在 Hook 中写入自定义状态字段(持久化中间结果)
@HookPositions(HookPosition.AFTER_AGENT)
public class SaveStateHook extends AgentHook {
@Override
public CompletableFuture<Map<String, Object>> afterAgent(OverAllState state, RunnableConfig config) {
return CompletableFuture.completedFuture(Map.of("last_result", "...")); // 示意
}
}
这对持久化中间结果、或让信息对后续工具/提示可访问很有用。
在 beforeModel / afterModel Hook 中处理消息 —— 继承 MessagesModelHook,直接拿到 List<Message>:
beforeModel(List<Message>, config):在模型调用之前处理消息(裁剪、注入、脱敏);afterModel(List<Message>, config):在模型调用之后处理消息(记录、改写结果)。
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<Message> previousMessages, RunnableConfig config) {
// 模型调用前:处理消息(裁剪 / 注入 / 脱敏)
return new AgentCommand(previousMessages); // 示意
}
@Override
public AgentCommand afterModel(List<Message> previousMessages, RunnableConfig config) {
// 模型调用后:处理消息(记录 / 改写结果)
return new AgentCommand(previousMessages); // 示意
}
@Override
public String getName() { return "message_process_hook"; }
}
MessagesModelHook(操作List<Message>,返回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<String, Object> 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())。
② 占位符工作原理:
InstructionAgentHook(BEFORE_AGENT)把instruction注入成AgentInstructionMessage(此时{占位符}尚未替换);AgentLlmNode组装请求时,renderTemplatedUserMessage把当前图状态state.data()处理成参数 Map (剔除messages与List值,Message转文本);- 用 Spring AI 的
PromptTemplate(底层StringTemplate)执行render(params),把{key}替换成对应值; - 渲染后打上
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<String, Object> 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<OverAllState> result = workflow.invoke(
UserMessage.builder().text("Spring AI Alibaba").build(),
RunnableConfig.builder().threadId("user-1").build()
);
// 使用结果:从 Optional<OverAllState> 中取出 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)会自动成为后一个 Agentinstruction里{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<OverAllState> 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<OverAllState> result = router.invoke(
UserMessage.builder().text("帮我查一下杭州的天气").build(),
RunnableConfig.builder().threadId("user-1").build()
);
result.ifPresent(state -> {
// 读取被路由到的子 Agent 的最终回复(messages 最后一条)
List<Message> messages = (List<Message>) 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<OverAllState> result = loop.invoke(
UserMessage.builder().text("帮我优化这份方案").build(),
RunnableConfig.builder().threadId("user-1").build()
);
result.ifPresent(state -> {
// 循环结束后,读取最终的 messages(或子 Agent 的 outputKey)
List<Message> messages = (List<Message>) 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<String, Object> apply(OverAllState state) {
// 读取最后一条消息
List<Object> messages = (List<Object>) 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<Agent> 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 | 全局状态,承载流转中的共享数据 |
CompiledGraph | StateGraph 编译后的可执行形态 |
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<OverAllState> 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<String> plan = List.of("步骤1:收集资料", "步骤2:撰写初稿", "步骤3:校对定稿");
return Map.of("plan", plan, "stepIndex", 0);
}));
// 3. 执行节点:执行当前步骤
graph.addNode("executor", node_async(state -> {
List<String> plan = (List<String>) 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<String> plan = (List<String>) 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:
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter-a2a-nacos</artifactId>
</dependency>
工作方式大致为:
- 各 Agent 服务启动后向 Nacos 注册自己;
- 需要协作时,Agent 通过 Nacos 发现目标 Agent 的地址;
- 通过 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 中加入(版本与框架保持一致):
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-studio</artifactId>
<version>1.1.2.2</version>
</dependency>
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<T> |
| 读取 | value(key, Class<T>) | 按类型读取 |
| 读取 | value(key, default) | 带默认值读取 |
| 读取 | data() | 返回整个状态 Map<String, Object> |
| 写入 | 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<String, Object> |
getToolCallHistory() | 返回工具调用历史 List<Message> |
常量 TOOL_CALL_HISTORY | 历史记录的 key |
配套工具类
ToolContextHelper(com.alibaba.cloud.ai.graph.agent.tools)提供更便捷的访问:getConfig(toolContext)→Optional<RunnableConfig>、getState(toolContext)→Optional<OverAllState>、getMetadata(toolContext, key, type)→Optional<T>(详见第 6 章 6.5)。