---
title: "03-框架实战快速上手"
created: 2026-05-14
aliases:
- 框架实战快速上手
tags:
- 项目
---
# 框架实战快速上手
前面几篇把 Agent 的概念、协作模式和架构拆开讲过了,这一篇进入实战——用 **Spring AI Alibaba ReactAgent** 从零搭一个订单助手,五个核心能力(Tools、Memory、Hook、Interceptor、结构化输出/流式输出)在这个模块里都有真实落地。
## Spring AI Alibaba ReactAgent 概览
在正式开始实战之前,我们先来了解一下 Spring AI Alibaba ReactAgent 的整体架构和核心概念。
### 框架定位
**Spring AI Alibaba** 是阿里巴巴基于 **Spring AI** 推出的增强版框架。你可以把它理解为 Spring AI 的"阿里云定制版"——底层核心逻辑还是 Spring AI,但在上层做了大量增强,特别是在 Agent 能力方面。
从 1.1 版本开始,Spring AI Alibaba 提供了**生产级 ReactAgent 的实现**,不需要你自己从头写循环控制、工具调度这些脏活累活了。
### Graph Runtime:图执行引擎
> ReactAgent 底层是基于 **Graph Runtime(图运行时)** 构建的。所谓 Graph 就是由 **节点(Node)** 和 **边(Edge)** 组成的执行图:
>
> - **Node(节点)**:执行具体逻辑的单元,比如调用大模型、执行工具
> - **Edge(边)**:定义节点之间的流转规则,比如"有工具调用就去Tool节点,没有就结束"
>
> 这种设计让 Agent 的执行过程**可控、可追溯、可调试**,比单纯的循环嵌套清晰得多。
![[2-Learning/05-项目/08-企业级项目深读/01-SuperAgent/06-系统讲解AI相关技术/08-智能体是什么/assets/FveVXYpUHUtXOhEEqJXzGjtDplDT-571fd8cb.png]]
### 三种核心节点
在 ReactAgent 的执行过程中,有三种关键的节点类型:
| 节点类型 | 职责 | 说明 |
| --- | --- | --- |
| **Model Node** | Agent 的"大脑" | 调用大模型进行推理,决定下一步是调工具还是直接回答 |
| **Tool Node** | Agent 的"手脚" | 当模型决定使用工具时,这个节点负责实际执行工具方法 |
| **Hook Node** | Agent 的"探针" | 在流程关键位置插入自定义逻辑,比如日志、审计、限流 |
- 用户输入进入 Model Node,模型进行推理
- 如果模型决定调用工具,流转到 Tool Node 执行
- 工具执行完成后,结果回到 Model Node 继续推理
- 如果模型认为任务完成(不再调用工具),流转到 END
- 这个循环就是 ReAct 的"思考→行动→观察"过程
### ReactAgent 核心组件速览
ReactAgent 提供了一套完整的组件体系,每个组件负责不同的能力:
| 组件 | 作用 | 配置方式 |
| --- | --- | --- |
| **Model** | 大模型,Agent 的推理引擎 | `.model(chatModel)` |
| **Tools** | 工具集,Agent 可以调用的外部能力 | `.tools(...)` 或 `.methodTools(...)` |
| **System Prompt** | 系统提示词,定义 Agent 的身份和行为规范 | `.systemPrompt(...)` 或 `.instruction(...)` |
| **Memory(Saver)** | 记忆,支持多轮对话 | `.saver(memorySaver)` |
| **Hooks** | 生命周期钩子,在 Agent 执行前后插入逻辑 | `.hooks(...)` |
| **Interceptors** | 拦截器,在每次模型调用前拦截和增强 | `.interceptors(...)` |
| **Output Type** | 结构化输出,约束模型输出为指定的 Java 类型 | `.outputType(Xxx.class)` |
### 依赖引入
使用 Spring AI Alibaba ReactAgent 需要引入以下依赖:
```xml
com.alibaba.cloud.ai
spring-ai-alibaba-agent-framework
1.1.0.0
com.alibaba.cloud.ai
spring-ai-alibaba-starter-dashscope
1.1.0.0
```
> Spring AI Alibaba 从 1.1 版本开始提供完整的 ReactAgent 支持。如果你使用其他模型(如通过 OpenAI 兼容协议接入硅基流动、DeepSeek 等),可以只引入 `spring-ai-alibaba-agent-framework`,模型层使用 Spring AI 原生的 starter。
### 最简示例:创建你的第一个 Agent
```java
// 创建最简单的 ReactAgent
ReactAgent agent = ReactAgent.builder()
.name("my_first_agent")
.model(chatModel) // 注入的 ChatModel Bean
.systemPrompt("你是一个友好的助手,请用简洁的语言回答问题。")
.build();
// 同步调用
String response = agent.call("你好,请介绍一下自己").getText();
// 流式调用
Flux stream = agent.stream("讲个笑话", config);
```
这就是 ReactAgent 最核心的用法:通过 Builder 模式构建 Agent 实例,然后调用 `call()` 或 `stream()` 方法与之对话。
## 项目完整实战示例
接下来,我们通过一个完整的订单助手项目,逐步掌握 ReactAgent 的各个核心能力。
### 示例中项目地址
- 项目模块:`ai-example-spring-ai-alibaba`
## 项目全景:三层结构与三个 Agent
先把整体结构看清楚,再逐层拆细节。
![[2-Learning/05-项目/08-企业级项目深读/01-SuperAgent/06-系统讲解AI相关技术/08-智能体是什么/assets/FoJCRBhEnpIhSnUtfv2BnRGrLneq-a8d70cc9.png]]
三个 Agent 的设计是"从简到繁"递进的:
| Agent 实例 | 新增能力 | 对应接口 |
| --- | --- | --- |
| `simple_agent` | 最小可运行:只有模型 | `/agent/hello` |
| `order_assistant` | 在 simple 基础上加:5 个工具 + 多轮记忆 + Hook + Interceptor | `/agent/order/chat`、`/stream` |
| `order_summary_agent` | 在工具基础上加:结构化输出 (`outputType`) | `/agent/order/summary` |
## 目录结构
```java
ai-example-spring-ai-alibaba/
├── controller/
│ ├── SimpleAgentController.java # /agent/hello 最简入口
│ └── OrderAssistantController.java # /agent/order/** 完整接口
├── service/
│ └── SpringAiAlibabaAgentService.java # 三个 ReactAgent 的创建与调用
├── tool/
│ ├── OrderTools.java # @Tool 注解式工具:订单/商品/退款
│ ├── ShippingPolicyRequest.java # 函数式工具入参
│ ├── SessionContextRequest.java # 上下文工具入参
│ └── SessionContextTool.java # BiFunction 工具:读取会话上下文
├── support/
│ ├── LoggingHook.java # AgentHook:Agent 前后打日志
│ ├── ResettableMemorySaver.java # 基于 MemorySaver 扩展,支持清理指定 threadId
│ └── SensitiveWordInterceptor.java # ModelInterceptor:每次模型调用前拦截
└── model/
├── OrderInfo.java ProductInfo.java RefundResult.java # 工具返回值
└── OrderSummary.java # 结构化输出目标类型
```
## 依赖和配置
```xml
org.springframework.boot
spring-boot-starter-web
com.alibaba.cloud.ai
spring-ai-alibaba-agent-framework
org.springframework.ai
spring-ai-starter-model-openai
```
版本由 BOM 统一管理,模块里不单独声明:`Spring AI` 来自父工程 BOM,`Spring AI Alibaba` 来自模块内的 `spring-ai-alibaba-bom`。
```yaml
spring:
ai:
openai:
base-url: https://api.siliconflow.cn
api-key: ${SILICONFLOW_API_KEY}
chat:
options:
model: Qwen/Qwen3.5-122B-A10B
```
## 第一步:最简 Agent(simple\_agent)
拿 `simple_agent` 当切入点,把 ReactAgent 的最小工作单元跑通。
### 接口 → Service → Agent 的完整路径
控制器只做参数传递,不包含任何 Agent 逻辑:
```java
@RestController
@RequestMapping("/agent")
public class SimpleAgentController {
@GetMapping("/hello")
public String hello(
@RequestParam(value = "question", defaultValue = "你好,请介绍一下 Spring AI Alibaba ReactAgent 的作用")
String question) {
return this.agentService.simpleReply(question);
}
}
```
Service 构造函数里创建 Agent 实例——整个应用生命周期内共享这一个对象,调用时无状态:
```java
this.simpleAgent = ReactAgent.builder()
.name("simple_agent")
.model(chatModel) // 注入 DeepSeek ChatModel Bean
.systemPrompt(SIMPLE_AGENT_PROMPT)
.build();
// 调用时:
public String simpleReply(String question) {
return this.simpleAgent.call(question).getText();
}
```
`.call(question)` 是同步阻塞的,返回 `AgentResponse`,调 `.getText()` 取出纯文本回复。整条链路就这三步:
![[2-Learning/05-项目/08-企业级项目深读/01-SuperAgent/06-系统讲解AI相关技术/08-智能体是什么/assets/Fm9HLKUyqL87f2YMNQDG3yJY91sA-a8d0a02a.png]]
验证:`http://localhost:7089/agent/hello?question=你好`
![[2-Learning/05-项目/08-企业级项目深读/01-SuperAgent/06-系统讲解AI相关技术/08-智能体是什么/assets/FiS6KfzrhM55tVPBURJkeBtyyZ4r-14430c97.png]]
## 第二步:工具体系(Tools)
> 工具是 Agent 调用外部能力的通道。**模型本身不执行任何操作**,它只负责"决策"——判断是否需要工具、选择哪个工具、传什么参数;框架负责真正执行工具方法并把结果反馈给模型。
项目里落地了三种工具定义方式,分别适合不同场景。
### 方式一:注解式工具(`@Tool`)
这是最常见的写法,把业务方法集中在一个 Spring Bean 里,用注解声明工具名和参数描述,框架自动注册。
```java
@Component
public class OrderTools {
// 内存模拟数据库,双 key 设计:ID 和名称都能命中
private static final Map ORDER_STORE = Map.of(
"ord-1001", new OrderInfo("ORD-1001", "蓝牙耳机 Pro", "已发货", "SF123456789", "2026-03-14 18:00 前送达", true),
"ord-1002", new OrderInfo("ORD-1002", "机械键盘 V87", "已签收", "JD99887766", "2026-03-11 12:00 已签收", false)
);
private static final Map PRODUCT_STORE = Map.of(
"p-1001", new ProductInfo("P-1001", "蓝牙耳机 Pro", "299.00", 28, "主动降噪、40 小时续航、支持双设备切换"),
"蓝牙耳机 pro", new ProductInfo("P-1001", "蓝牙耳机 Pro", "299.00", 28, "主动降噪、40 小时续航、支持双设备切换"),
"p-1002", new ProductInfo("P-1002", "机械键盘 V87", "499.00", 12, "Gasket 结构、热插拔、三模连接"),
"机械键盘 v87", new ProductInfo("P-1002", "机械键盘 V87", "499.00", 12, "Gasket 结构、热插拔、三模连接")
);
@Tool(name = "query_order_status",
description = "根据订单号查询订单状态、物流单号、预计送达时间和是否支持退款")
public OrderInfo queryOrderStatus(
@ToolParam(description = "订单号,例如 ORD-1001") String orderId) {
return ORDER_STORE.getOrDefault(normalize(orderId),
new OrderInfo(orderId, "未知商品", "未查到订单", "N/A", "请先确认订单号是否正确", false));
}
@Tool(name = "query_product_detail",
description = "根据商品 ID 或商品名称查询价格、库存和卖点")
public ProductInfo queryProduct(
@ToolParam(description = "商品 ID 或商品名称") String productQuery) {
return PRODUCT_STORE.getOrDefault(normalize(productQuery),
new ProductInfo("UNKNOWN", productQuery, "0.00", 0, "暂未找到,请转人工客服核实"));
}
@Tool(name = "apply_refund",
description = "为指定订单提交退款申请,需要提供订单号和退款原因")
public RefundResult applyRefund(
@ToolParam(description = "订单号,例如 ORD-1001") String orderId,
@ToolParam(description = "退款原因,例如商品破损、重复下单") String reason) {
// 先查订单判断是否支持退款,有业务拦截逻辑
OrderInfo orderInfo = queryOrderStatus(orderId);
if (!orderInfo.canRefund()) {
return new RefundResult(orderInfo.orderId(), false, "N/A",
"当前状态为"" + orderInfo.status() + "",不支持直接退款,请联系人工客服。");
}
String normalizedReason = StringUtils.hasText(reason) ? reason.trim() : "未说明原因";
return new RefundResult(orderInfo.orderId(), true, "RF-20260313-1001",
"退款申请已受理,原因:" + normalizedReason + ",预计 1-3 个工作日原路退回。");
}
// 统一做 trim + 转小写,避免大小写和空格问题导致查不到
private String normalize(String value) {
return StringUtils.hasText(value) ? value.trim().toLowerCase(Locale.ROOT) : "";
}
}
```
- `PRODUCT_STORE` 用商品 ID(`p-1001`)和商品中文名(`蓝牙耳机 pro`)两套 key 同时索引,模型传哪种都能命中
- `normalize()` 统一处理大小写和空格,防止模型输出 `ORD-1001` 时因大小写匹配失败
- `applyRefund` 内部先调 `queryOrderStatus` 判断 `canRefund`,业务校验封闭在工具内部
挂载到 Agent:
```java
.methodTools(orderTools) // 框架自动扫描 Bean 上所有 @Tool 方法
```
### 方式二:函数式工具(`FunctionToolCallback`)
轻量规则查询不需要单独建类,直接用 Lambda 或方法引用包装成工具:
```java
// 构建工具
private ToolCallback buildShippingPolicyTool() {
return FunctionToolCallback.builder("shipping_policy_lookup", this::shippingPolicyLookup)
.description("根据商品名称查询发货和售后规则")
.inputType(ShippingPolicyRequest.class)
.build();
}
// 工具的具体实现:对象入参 → 字符串结果
private String shippingPolicyLookup(ShippingPolicyRequest request) {
String productQuery = request != null ? request.productQuery() : "";
if (StringUtils.hasText(productQuery) && productQuery.contains("耳机")) {
return "蓝牙耳机 Pro 支持 48 小时内发货,未拆封支持 7 天无理由退货。";
}
return "默认规则:24 小时内出库,签收前可拦截,签收后请结合订单状态判断是否可退款。";
}
public record ShippingPolicyRequest(
String productQuery
) {
}
```
这里把工具输入包装成 `record`,模型侧看到的工具 schema 就会是标准的 JSON `object`,比直接用 `String` 更稳定。
挂载方式与注解式不同,直接传 `ToolCallback` 实例:
```text
.tools(shippingPolicyTool, sessionContextTool)
```
### 方式三:上下文感知工具(`BiFunction`)
前两种工具只接收模型传来的参数。当工具还需要读取当前会话运行时状态(比如消息条数、当前线程 ID),就实现 `BiFunction`,第二个参数 `ToolContext` 由框架注入:
```java
public class SessionContextTool implements BiFunction {
@Override
public String apply(SessionContextRequest request, ToolContext toolContext) {
// 从 ToolContext 取出 Agent 内部状态图和运行配置
Object stateObject = toolContext.getContext().get(ToolContextConstants.AGENT_STATE_CONTEXT_KEY);
Object configObject = toolContext.getContext().get(ToolContextConstants.AGENT_CONFIG_CONTEXT_KEY);
// 统计当前线程的历史消息数量
int messageCount = 0;
if (stateObject instanceof OverAllState state) {
Object messages = state.value("messages").orElse(List.of());
if (messages instanceof List> list) {
messageCount = list.size();
}
}
// 读取 threadId(即 sessionId)
String threadId = configObject instanceof RunnableConfig config
? config.threadId().orElse("unknown") : "unknown";
String focus = request != null ? request.focus() : "";
String focusText = StringUtils.hasText(focus) ? focus.trim() : "当前对话";
return "当前线程 " + threadId + " 已累计 " + messageCount + " 条消息,重点关注:" + focusText + "。";
}
}
public record SessionContextRequest(
String focus
) {
}
```
> `ToolContext` 中可以获取以下关键信息:
>
> | 常量 Key | 类型 | 说明 |
> | --- | --- | --- |
> | `_AGENT_STATE_` | `OverAllState` | Agent 内部的状态图,包含完整的消息历史 |
> | `_AGENT_CONFIG_` | `RunnableConfig` | 运行配置,可以读取 threadId、metadata 等 |
> | `_AGENT_STATE_FOR_UPDATE_` | `Map` | 用于工具向状态图写入额外数据 |
>
> 通过 `OverAllState.value("messages")` 可以获取 ReAct Agent 的完整历史消息列表,包括 User、Assistant、Tool 类型的消息。
`ToolContext` 里的 `OverAllState` 和 `RunnableConfig` 是框架运行时注入的"活数据",每次工具被调用时都能拿到最新状态。
包装挂载:
```java
private ToolCallback buildSessionContextTool() {
return FunctionToolCallback.builder("session_context_snapshot", new SessionContextTool())
.description("读取当前线程上下文,告诉用户会话已经记录了多少历史消息")
.inputType(SessionContextRequest.class)
.build();
}
```
### 三种工具方式怎么选?
| 方式 | 适用场景 | 挂载方法 |
| --- | --- | --- |
| `@Tool` 注解式 | 业务逻辑较复杂、多个相关工具聚合成一个 Bean | `.methodTools(bean)` |
| `FunctionToolCallback` | 单一轻量的规则查询或转换,不需要专门建类 | `.tools(callback)` |
| `BiFunction + ToolContext` | 需要读取会话状态(消息历史、threadId)的工具 | `.tools(callback)` |
## 第三步:组装完整的 order\_assistant
有了工具体系,再来看 `order_assistant` 是怎么把所有组件拼在一起的。
```java
this.orderAssistantMemorySaver = new ResettableMemorySaver(); // ① 基于 MemorySaver 扩展,支持清空指定会话
ToolCallback shippingPolicyTool = buildShippingPolicyTool(); // ② 函数式工具
ToolCallback sessionContextTool = buildSessionContextTool(); // ③ 上下文感知工具
this.orderAssistantAgent = ReactAgent.builder()
.name("order_assistant")
.model(chatModel) // 模型(DeepSeek)
.methodTools(orderTools) // 注解式工具(订单/商品/退款)
.tools(shippingPolicyTool, sessionContextTool) // 函数式工具
.instruction(ORDER_ASSISTANT_PROMPT) // 系统提示词,约束行为边界
.saver(this.orderAssistantMemorySaver) // 挂载记忆,启用多轮对话
.hooks(loggingHook) // 生命周期钩子
.interceptors(sensitiveWordInterceptor) // 模型调用拦截器
.build();
```
### systemPrompt vs instruction
ReactAgent 支持两种方式配置系统提示词:
| 配置方式 | 适用场景 | 说明 |
| --- | --- | --- |
| `.systemPrompt("...")` | 简单的单行提示 | 直接传一个字符串,适合简短的角色设定 |
| `.instruction("...")` | 复杂的多行指令 | 支持结构化的提示词,适合详细的行为规范 |
```python
// 方式一:简单提示词
ReactAgent agent = ReactAgent.builder()
.systemPrompt("你是一个友好的助手。")
.build();
// 方式二:复杂指令(推荐用于生产环境)
String instruction = """
你是一个专业的电商客服助手。
你的工作职责:
1. 解答用户关于商品的问题
2. 帮助用户查询订单状态
3. 处理用户的退换货请求
回复规范:
- 使用友好、专业的语气
- 回答要简洁明了
- 涉及金额操作时要再次确认
""";
ReactAgent agent = ReactAgent.builder()
.instruction(instruction)
.build();
```
- 开发测试阶段用 `systemPrompt`,快速验证
- 生产环境用 `instruction`,把完整的行为规范、约束条件都写进去
- 两者功能等价,只是风格不同,选一个用即可
系统提示词约束了 Agent 的行为边界,防止模型编造数据、控制退款流程:
```python
private static final String ORDER_ASSISTANT_PROMPT = """
你是 JavaUp 电商订单助手,需要结合工具结果回答问题。
你可以:
1. 查询订单状态和物流信息。
2. 查询商品详情和售后规则。
3. 处理退款申请,但涉及退款时必须先确认订单号和退款原因。
回复要求:
- 优先使用工具返回的事实,不要编造订单信息。
- 回答要简洁,适合直接返回给前端页面展示。
- 如果用户追问"这个会话记住了什么",可以调用 session_context_snapshot 工具总结当前线程上下文。
""";
```
## 完整请求链路:一次调用的全生命周期
访问 `http://localhost:7089/agent/order/chat?question=帮我查订单ORD-1001&sessionId=u1001` 进行对话
![[2-Learning/05-项目/08-企业级项目深读/01-SuperAgent/06-系统讲解AI相关技术/08-智能体是什么/assets/FpD1Ie55y2Fb2grNkogWsSQR3FeU-c8cf6a84.png]]
整个执行过程分三个阶段,下面逐段来看。
### 阶段一:请求调度与 Agent 启动
HTTP 请求进来后,经过 Controller → Service 组装好携带 `threadId` 的 `RunnableConfig`,再触发 `beforeAgent` 钩子打印启动日志,最后才真正进入 ReactAgent 的推理流程。
![[2-Learning/05-项目/08-企业级项目深读/01-SuperAgent/06-系统讲解AI相关技术/08-智能体是什么/assets/Fmq7prrVDkZxZ2XyW_60Fl-jrshJ-98343196.png]]
### 阶段二:拦截器过滤 + ReAct 推理执行
这是最核心的阶段——拦截器在**每次模型调用前**都会执行。本次请求触发了两轮模型调用:第一轮模型决策调用工具,第二轮基于工具结果生成最终回复。
![[2-Learning/05-项目/08-企业级项目深读/01-SuperAgent/06-系统讲解AI相关技术/08-智能体是什么/assets/Ftins7ovuUkZ2FS3rHYT15N8qXTP-383c4d4a.png]]
### 阶段三:记忆持久化与响应返回
ReAct 循环结束后,框架把本轮所有消息写入 `MemorySaver`(为下一轮多轮对话准备),触发 `afterAgent` 钩子记录结束日志,最终把文本回复沿原路返回给客户端。
![[2-Learning/05-项目/08-企业级项目深读/01-SuperAgent/06-系统讲解AI相关技术/08-智能体是什么/assets/FmhztIKlaHkT9vhPdDstcsgKJx3P-3dc37402.png]]
## ReAct 循环:推理 — 行动 — 观察
上面时序图里的"ReAct 循环"是 Agent 的核心运转机制,每一轮包含三个阶段:
![[2-Learning/05-项目/08-企业级项目深读/01-SuperAgent/06-系统讲解AI相关技术/08-智能体是什么/assets/FmITvhox48ucbebBYBx5idRyJt3j-a5056047.png]]
- 模型每轮只负责"推理",工具执行由框架负责,两者完全解耦
- 工具结果会作为 `ToolMessage` 追加进消息列表,模型下一轮能看到
- 一个问题可能触发多次工具调用(多轮循环),也可能直接回答(只有一轮)
- 框架有最大迭代次数保护,防止无限循环
## Memory:ResettableMemorySaver 与多轮对话
`order_assistant` 挂载的是 `ResettableMemorySaver`。它底层继承自 `MemorySaver`,保留了基于 `threadId` 的多轮记忆能力,同时额外提供了清空指定会话的能力,方便演示页随时回到干净状态。
```java
this.orderAssistantMemorySaver = new ResettableMemorySaver(); // 内存级别,应用重启后丢失
```
```java
public class ResettableMemorySaver extends MemorySaver {
public int clearThread(String threadId) {
Collection removed = remove(threadId);
return removed != null ? removed.size() : 0;
}
}
```
**多轮连贯性的关键是** `threadId`:
```java
private RunnableConfig buildSessionConfig(String sessionId) {
return RunnableConfig.builder()
.threadId(normalizeSessionId(sessionId)) // sessionId 即 threadId
.build();
}
```
框架内部以 `threadId` 为 key 读写历史消息列表,只要前后两次调用的 `threadId` 相同,Agent 就能"记得"上一轮说了什么。
![[2-Learning/05-项目/08-企业级项目深读/01-SuperAgent/06-系统讲解AI相关技术/08-智能体是什么/assets/FsCoC0MWVbh7y4Ah0jkb7jL5AMTB-ffd4841a.png]]
`thread-state` 接口直接从内存里的最新 checkpoint 提取状态,并补充当前线程的 checkpoint 数量:
```java
public Map describeThreadState(String sessionId) {
String normalizedSessionId = normalizeSessionId(sessionId);
RunnableConfig runnableConfig = buildSessionConfig(normalizedSessionId);
Map state = this.orderAssistantMemorySaver.get(runnableConfig)
.map(Checkpoint::getState)
.orElseGet(() -> this.orderAssistantAgent.getThreadState(normalizedSessionId));
List> messageList = state.get("messages") instanceof List> list ? list : List.of();
int checkpointCount = this.orderAssistantMemorySaver.list(runnableConfig).size();
Map result = new LinkedHashMap<>();
result.put("threadId", normalizedSessionId);
result.put("checkpointCount", checkpointCount);
result.put("messageCount", messageList.size());
result.put("latestUserMessage", findLatestMessage(messageList, MessageType.USER));
result.put("latestAssistantMessage", findLatestMessage(messageList, MessageType.ASSISTANT));
return result;
}
```
演示页里的"重置当前会话"按钮,对应的就是这个接口:
```java
@GetMapping("/reset-thread")
public Map resetThread(
@RequestParam(value = "sessionId", defaultValue = "demo-user-1001") String sessionId) {
return this.agentService.resetOrderAssistantThread(sessionId);
}
public Map resetOrderAssistantThread(String sessionId) {
String normalizedSessionId = normalizeSessionId(sessionId);
int removedCheckpointCount = this.orderAssistantMemorySaver.clearThread(normalizedSessionId);
Map result = new LinkedHashMap<>();
result.put("threadId", normalizedSessionId);
result.put("reset", true);
result.put("removedCheckpointCount", removedCheckpointCount);
result.put("checkpointCount", 0);
result.put("messageCount", 0);
result.put("latestUserMessage", "");
result.put("latestAssistantMessage", "");
return result;
}
```
> `ResettableMemorySaver` 本质上仍然是内存实现,应用重启后历史会丢失。换成 `MysqlSaver` 或 `PostgresSaver` 时,调用侧的 `threadId` 约定仍然可以保留。
## Hook 与 Interceptor:两层插件机制
两者都是"中间件",但执行位置不同、职责不同:
| 维度 | `AgentHook` | `ModelInterceptor` |
| --- | --- | --- |
| 执行时机 | Agent 整体启动前 / 结束后(各一次) | 每次调用模型之前(可能多次) |
| 能否修改请求 | 不能,只能读取状态 | 能,可修改 system message |
| 能否短路 | 不能 | 能,直接返回 ModelResponse |
| 典型用途 | 日志、链路追踪、指标上报 | 安全过滤、请求增强、降级 |
### Hook 的四种执行位置
通过 `@HookPositions` 注解可以指定 Hook 在哪些位置执行:
| 位置 | 说明 | 执行次数 |
| --- | --- | --- |
| `BEFORE_AGENT` | Agent 整体开始前 | 每次 call() 执行一次 |
| `AFTER_AGENT` | Agent 整体结束后 | 每次 call() 执行一次 |
| `BEFORE_MODEL` | 每次模型调用前 | 可能多次(ReAct 循环中) |
| `AFTER_MODEL` | 每次模型调用后 | 可能多次(ReAct 循环中) |
- **需要记录整体执行情况**(开始/结束日志、耗时统计)→ 用 `AgentHook`
- **需要在每次模型调用时做处理**(敏感词过滤、提示词增强、审计)→ 用 `ModelInterceptor`
- **需要短路/拦截模型调用**(安全拦截、降级返回)→ 只能用 `ModelInterceptor`
![[2-Learning/05-项目/08-企业级项目深读/01-SuperAgent/06-系统讲解AI相关技术/08-智能体是什么/assets/FptC6b2rne2IY5qFQxjzJeBKTvUU-40c4874f.png]]
### LoggingHook 核心代码
```java
@Component
@HookPositions({HookPosition.BEFORE_AGENT, HookPosition.AFTER_AGENT})
public class LoggingHook extends AgentHook {
@Override
public CompletableFuture