Spring AI Alibaba Agent开发:基于ChatClient的智能体构建模式
导读 :智能体(Agent)代表着 AI 应用的最高形态------不只是"回答问题",而是能自主规划、调用工具、观察结果、迭代执行直到完成复杂任务。本文基于 Spring AI Alibaba 1.1 的 ReactAgent API,深入讲解智能体架构、记忆管理、工具链编排和人工介入机制。
一、Agent 与 ChatClient 的本质区别
很多人对 Agent 和普通 ChatClient 的区别有些模糊,先把概念厘清:
ChatClient(单次对话):
用户提问 → 模型回答 → 结束
特点:单轮或多轮,但每次都是人主动发起
Agent(自主执行):
用户给目标 → Agent 规划 → 调工具1 → 观察结果
→ 调工具2 → 观察 → 调工具3 → ... → 完成目标
特点:自主循环,主动调用工具,直到任务完成
Agent 的核心是一个推理-行动循环(Think-Act-Observe Loop),模型不断地:
- Think:根据当前状态和历史信息,判断下一步做什么;
- Act:调用合适的工具;
- Observe:获取工具执行结果,更新状态;
- 重复,直到达成目标或触发终止条件。
二、Spring AI Alibaba 1.1 的 Agent API
Spring AI Alibaba 1.1 版引入了 ReactAgent 作为核心的 Agent 构建 API。
2.1 ReactAgent 快速入门
java
package com.example.agent;
import com.alibaba.cloud.ai.dashscope.api.DashScopeApi;
import com.alibaba.cloud.ai.dashscope.chat.DashScopeChatModel;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.tool.FunctionToolCallback;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AgentConfig {
@Bean
public ChatModel dashScopeChatModel() {
DashScopeApi api = DashScopeApi.builder()
.apiKey(System.getenv("AI_DASHSCOPE_API_KEY"))
.build();
return DashScopeChatModel.builder()
.dashScopeApi(api)
.build();
}
}
java
@Service
@RequiredArgsConstructor
@Slf4j
public class SimpleAgentService {
private final ChatModel chatModel;
private final WeatherTools weatherTools;
private final SearchTools searchTools;
/**
* 构建并运行一个简单的 ReactAgent
*/
public String runAgent(String task) {
// 1. 收集工具列表
List<ToolCallback> tools = List.of(
FunctionToolCallback.builder("getWeather", weatherTools::getWeather)
.description("查询城市实时天气")
.inputType(WeatherTools.WeatherRequest.class)
.build(),
FunctionToolCallback.builder("searchWeb", searchTools::search)
.description("搜索互联网获取最新信息")
.inputType(SearchTools.SearchRequest.class)
.build()
);
// 2. 构建 ReactAgent
ReactAgent agent = ReactAgent.builder()
.name("general-assistant")
.model(chatModel)
.tools(tools)
.systemPrompt("""
你是一个全能助手,可以查询天气和搜索网络信息。
遇到需要实时数据的问题,主动调用工具获取信息。
回答要简洁准确。
""")
// 防止无限循环:最多执行 10 轮工具调用
.maxIterations(10)
// 使用内存保存器(支持多轮对话记忆)
.saver(new MemorySaver())
.build();
// 3. 执行任务
AssistantMessage response = agent.call(task);
return response.getText();
}
}
三、Agent 架构深度解析
3.1 ReAct 架构(推理+行动)
ReAct(Reasoning + Acting)是目前最主流的 Agent 架构,其执行流程如下:
输入任务:查询北京明天天气,并给出出行建议
|
v
[Reasoning Step 1]
思考:需要知道北京的天气信息
决策:调用 getWeather 工具
|
v
[Act]
调用:getWeather(city="北京", date="tomorrow")
结果:多云,最高温度18°C,最低12°C,有轻微降雨
|
v
[Observation]
收到工具结果,更新状态
|
v
[Reasoning Step 2]
思考:已获得天气数据,可以给出出行建议了
决策:不需要再调用工具,可以直接回答
|
v
[Final Answer]
"明天北京多云有小雨,气温12-18°C。建议携带雨伞,
穿外套注意保暖,早晚温差较大。"
3.2 Plan-And-Execute 架构(规划+执行)
对于更复杂的任务,Plan-And-Execute 架构先制定完整计划,再按步骤执行:
java
/**
* 自定义 Plan-And-Execute Agent
* 先规划,再执行,适合多步骤复杂任务
*/
@Service
@RequiredArgsConstructor
public class PlanAndExecuteAgent {
private final ChatClient plannerClient; // 规划专用模型
private final ChatClient executorClient; // 执行专用模型
public String execute(String complexTask) {
// Step 1:规划阶段
String plan = plannerClient.prompt()
.system("""
你是一个任务规划专家。接收用户的复杂任务后,
将其分解为 3-7 个可执行的子步骤,以 JSON 数组格式输出:
["步骤1描述", "步骤2描述", ...]
只输出 JSON 数组,不要其他内容。
""")
.user(complexTask)
.options(DashScopeChatOptions.builder()
.withModel("qwen-max") // 规划用强力模型
.withTemperature(0.2)
.build())
.call()
.content();
log.info("任务规划:{}", plan);
// Step 2:解析计划
List<String> steps = parseSteps(plan);
// Step 3:逐步执行
StringBuilder context = new StringBuilder();
context.append("原始任务:").append(complexTask).append("\n\n");
for (int i = 0; i < steps.size(); i++) {
String step = steps.get(i);
log.info("执行步骤 {}/{}: {}", i + 1, steps.size(), step);
String stepResult = executorClient.prompt()
.system("基于以下上下文,执行指定的子步骤并输出结果")
.user("上下文:\n" + context + "\n\n当前步骤:" + step)
.call()
.content();
context.append("步骤").append(i + 1).append("(")
.append(step).append(")结果:\n")
.append(stepResult).append("\n\n");
}
// Step 4:汇总最终结果
return executorClient.prompt()
.system("基于以下所有步骤的执行结果,汇总输出最终答案")
.user(context.toString())
.call()
.content();
}
private List<String> parseSteps(String json) {
try {
ObjectMapper mapper = new ObjectMapper();
return mapper.readValue(json.trim(), new TypeReference<>() {});
} catch (Exception e) {
log.warn("计划解析失败,作为单步执行");
return List.of(json);
}
}
}
四、Agent 记忆管理
4.1 三种记忆类型
+--------------------+--------------------+------------------------+
| 记忆类型 | 特点 | 适用场景 |
+--------------------+--------------------+------------------------+
| 对话记忆(Session) | 短期,当前会话内 | 多轮对话上下文保持 |
| 实体记忆(Entity) | 提取关键实体并记录 | 用户偏好、人物关系记录 |
| 摘要记忆(Summary) | 压缩长对话为摘要 | 超长会话的上下文压缩 |
+--------------------+--------------------+------------------------+
4.2 多轮对话记忆(MemorySaver)
java
@Service
@RequiredArgsConstructor
public class MemoryAgentService {
private final ChatModel chatModel;
// 共享的 MemorySaver(跨请求保持记忆)
private final MemorySaver memorySaver = new MemorySaver();
/**
* 支持多轮对话的 Agent
* 通过 threadId 区分不同用户的会话
*/
public String chat(String userId, String message) {
ReactAgent agent = ReactAgent.builder()
.name("memory-agent")
.model(chatModel)
.saver(memorySaver) // 注入记忆保存器
.systemPrompt("你是一个有记忆的智能助手,能记住用户说过的信息")
.build();
// 通过 threadId 隔离不同用户的会话记忆
AgentInput input = AgentInput.builder()
.message(message)
.threadId(userId) // 用户 ID 作为线程 ID
.build();
return agent.call(input).getText();
}
}
4.3 实体记忆(关键信息提取)
java
/**
* 实体记忆:从对话中提取并存储关键实体信息
* 适用于:客服系统记录用户信息、销售系统记录客户偏好
*/
@Component
@RequiredArgsConstructor
public class EntityMemoryService {
private final ChatClient chatClient;
// 实体存储(实际项目用 Redis 或数据库)
private final Map<String, Map<String, String>> entityStore = new ConcurrentHashMap<>();
/**
* 从对话中提取实体并存储
*/
public void extractAndStore(String sessionId, String conversation) {
String prompt = String.format("""
从以下对话中提取关键实体信息,以 JSON 格式输出:
{
"user_name": "用户名称(如提到的话)",
"user_preference": "用户偏好",
"product_interest": "感兴趣的商品",
"issue": "存在的问题"
}
没有的字段输出 null。
对话内容:%s
""", conversation);
String result = chatClient.prompt(prompt)
.options(DashScopeChatOptions.builder()
.withTemperature(0.0)
.build())
.call()
.content();
try {
ObjectMapper mapper = new ObjectMapper();
Map<String, String> entities = mapper.readValue(
result.replaceAll("```json|```", "").trim(),
new TypeReference<>() {});
// 合并到现有实体(新信息覆盖旧信息)
entityStore.merge(sessionId, entities, (existing, incoming) -> {
incoming.forEach((k, v) -> {
if (v != null) existing.put(k, v);
});
return existing;
});
log.info("[Entity Memory] sessionId={}, 提取实体:{}", sessionId, entities);
} catch (Exception e) {
log.warn("实体提取失败:{}", e.getMessage());
}
}
public Map<String, String> getEntities(String sessionId) {
return entityStore.getOrDefault(sessionId, Collections.emptyMap());
}
}
五、工具链编排:多步骤依赖管理
5.1 顺序依赖工具链
java
/**
* 旅行规划 Agent
* 工具调用有顺序依赖:查天气 → 查航班 → 查酒店 → 生成计划
*/
@Service
public class TravelPlanAgent {
/**
* 工具:查询城市天气
*/
@Tool(description = "查询目的地城市的天气预报,用于判断出行时机")
public String checkWeather(String city, String dates) {
// 调用天气 API
return String.format("%s %s 天气:晴,温度15-25°C,适合出行", city, dates);
}
/**
* 工具:查询航班
* 依赖上一步的天气判断(Agent 会在知道天气后才查航班)
*/
@Tool(description = "查询从出发地到目的地的可用航班,包括时间和价格")
public String searchFlights(String from, String to, String date) {
return String.format("找到 %s 到 %s %s 的航班:" +
"MU5678 08:00-10:30 ¥680," +
"CA1234 14:00-16:30 ¥780", from, to, date);
}
/**
* 工具:查询酒店
*/
@Tool(description = "查询目的地的可用酒店和价格")
public String searchHotels(String city, String checkIn, String checkOut) {
return String.format("%s 酒店推荐:" +
"如家快捷 ¥200/晚,汉庭 ¥250/晚,全季 ¥350/晚", city);
}
/**
* 运行旅行规划 Agent
*/
public String planTravel(ChatModel chatModel, String requirement) {
ReactAgent agent = ReactAgent.builder()
.name("travel-planner")
.model(chatModel)
.tools(this) // 注册本类中所有 @Tool 方法
.systemPrompt("""
你是一个专业的旅行规划助手。
接到用户的旅行需求后,请:
1. 先查询目的地天气确认出行条件
2. 根据日期查询合适的航班
3. 查询当地酒店推荐
4. 综合以上信息,给出完整的旅行建议
""")
.maxIterations(8)
.build();
return agent.call(requirement).getText();
}
}
六、人工介入机制(Human-in-the-Loop)
对于涉及重要操作(转账、删除数据、发送邮件等)的 Agent,引入人工确认节点是必要的安全保障:
java
@Service
@RequiredArgsConstructor
@Slf4j
public class HumanInLoopAgent {
private final ChatModel chatModel;
// 等待人工审批的任务队列
private final BlockingQueue<PendingApproval> approvalQueue = new LinkedBlockingQueue<>();
/**
* 需要人工确认的工具
*/
@Tool(description = "向客户发送邮件通知(需要人工审批后才执行)")
public String sendEmail(String recipient, String subject, String content) {
// 创建审批请求
PendingApproval approval = new PendingApproval(
UUID.randomUUID().toString(),
"sendEmail",
Map.of("recipient", recipient, "subject", subject, "content", content)
);
log.info("[HumanInLoop] 发起审批:{}", approval.getId());
approvalQueue.offer(approval);
// 等待人工审批(超时 5 分钟)
try {
boolean approved = approval.waitForDecision(5, TimeUnit.MINUTES);
if (approved) {
// 执行实际的发邮件逻辑
log.info("[HumanInLoop] 审批通过,发送邮件至:{}", recipient);
return "邮件已成功发送给 " + recipient;
} else {
return "邮件发送已被取消(人工审批拒绝)";
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return "审批超时,邮件未发送";
}
}
/**
* 人工处理审批(由后台管理界面调用)
*/
public void processApproval(String approvalId, boolean approved) {
approvalQueue.stream()
.filter(a -> a.getId().equals(approvalId))
.findFirst()
.ifPresent(a -> a.setDecision(approved));
log.info("[HumanInLoop] 审批 {} 已处理,结果:{}", approvalId,
approved ? "通过" : "拒绝");
}
}
七、实战案例:智能运维 Agent
java
/**
* 智能运维 Agent
* 场景:接收告警 → 分析日志 → 查询监控 → 给出修复建议 → 执行操作
*/
@Component
@RequiredArgsConstructor
@Slf4j
public class OpsAgent {
private final ChatModel chatModel;
@Tool(description = "查询应用日志,获取最近的错误日志")
public String queryLogs(String appName, String timeRange, String level) {
// 实际调用 Elasticsearch 或日志系统
return String.format("[%s 最近%s %s 级别日志]\n" +
"2025-03-20 15:30:01 ERROR OOM: Java heap space\n" +
"2025-03-20 15:29:58 ERROR GC overhead limit exceeded\n" +
"Stack: java.lang.OutOfMemoryError", appName, timeRange, level);
}
@Tool(description = "查询应用当前的 JVM 内存使用情况")
public String queryJvmMetrics(String appName) {
return String.format("[%s JVM 指标]\n" +
"堆内存:1.8GB / 2GB(使用率90%%)\n" +
"GC 频率:每5秒一次 Full GC\n" +
"GC 耗时:平均2.3秒", appName);
}
@Tool(description = "重启指定应用(高危操作,需谨慎)")
public String restartApp(String appName) {
log.warn("[OpsAgent] 重启应用:{}", appName);
// 实际调用运维平台 API
return appName + " 重启指令已发出,预计60秒后完成";
}
@Tool(description = "调整 JVM 堆内存配置并重启")
public String adjustJvmHeap(String appName, String newHeapSize) {
return String.format("[%s] JVM 堆内存已调整为 %s,即将重启", appName, newHeapSize);
}
/**
* 运行运维 Agent
*/
public String handleAlert(String alertMessage) {
ReactAgent agent = ReactAgent.builder()
.name("ops-agent")
.model(chatModel)
.tools(this)
.systemPrompt("""
你是一个经验丰富的运维工程师 Agent。
接收到告警后,请:
1. 查询相关日志分析根因
2. 查询监控指标辅助判断
3. 基于分析结果,给出可能的解决方案(多个选项)
4. 选择最合适且最安全的方案执行
5. 验证问题是否已解决
注意:
- 优先选择影响最小的方案
- 重启应用为最后手段
- 所有操作前必须明确说明原因
""")
.maxIterations(6)
.build();
log.info("[OpsAgent] 处理告警:{}", alertMessage);
return agent.call(alertMessage).getText();
}
}
八、Agent 性能优化与稳定性
8.1 防止无限循环
java
ReactAgent agent = ReactAgent.builder()
.model(chatModel)
.maxIterations(10) // 最多 10 轮工具调用
// 自定义终止条件
.stopCondition(response -> {
// 如果模型输出包含"任务完成"则停止
return response.getText().contains("任务完成") ||
response.getText().contains("已完成");
})
.build();
8.2 执行超时保护
java
// 为 Agent 执行添加整体超时
CompletableFuture<String> future = CompletableFuture.supplyAsync(() ->
agent.call(task).getText());
try {
return future.get(120, TimeUnit.SECONDS); // 整体超时 2 分钟
} catch (TimeoutException e) {
future.cancel(true);
return "任务执行超时,请尝试简化任务描述";
}
九、总结
Spring AI Alibaba 1.1 的 Agent 体系提供了从简单到复杂的完整支持:
- ReactAgent :内置的 ReAct 循环实现,
maxIterations防止死循环; - MemorySaver :通过
threadId实现多用户会话隔离的记忆持久化; - Plan-And-Execute:分规划和执行两个阶段,适合复杂多步骤任务;
- Human-in-the-Loop:高危操作前插入人工审批节点;
- 工具编排:@Tool 注解简化工具注册,Agent 自主决策调用顺序。
下一篇将进入 MCP(模型上下文协议)的世界,探讨如何构建跨应用、跨语言的 AI 工具共享生态。
参考资料