✅AgentScope Java特性:多轮会话&会话持久化



AgentScope Java 的多轮会话由两个核心机制协作完成:Memory(短期会话记忆)负责维护当前对话上下文,Session(会话持久化)负责将状态保存/恢复到外部存储。两者结合实现了”跨请求的连续对话”和”跨重启的会话恢复”。



多轮会话的核心:Memory



AgentScope Java 中的 Memory 接口扮演”短期记忆”角色。每次用户发送消息调用 agent.call(msg) 时,框架自动完成以下流程:

  1. 将用户消息加入 Memory(addToMemory(msgs))

  2. 构造完整消息列表传给 LLM(System Prompt + 历史消息 + 当前输入)

  3. 将 LLM 的回复也加入 Memory

  4. 如果触发工具调用,工具结果同样加入 Memory

  5. 循环直到 LLM 决定结束(无工具调用或达到 maxIters)

因此只要 Agent 实例不被销毁,多轮对话天然支持——Memory 中持续积累所有历史消息。



1
2
public interface Memory extends StateModule {
void addMessage(Msg message); // 添加消息 List<Msg> getMessages(); // 获取全部历史消息 void deleteMessage(int index); // 删除指定位置消息 void clear(); // 清空所有消息}



架提供的默认实现是 InMemoryMemory,基于 CopyOnWriteArrayList 实现线程安全的消息存储。



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package cn.hollis.llm.llmentor.agentscope.demo;
import io.agentscope.core.ReActAgent;
import io.agentscope.core.memory.InMemoryMemory;
import io.agentscope.core.message.Msg;
import io.agentscope.core.message.MsgRole;
import io.agentscope.core.message.TextBlock;
import io.agentscope.core.model.DashScopeChatModel;
import io.agentscope.core.formatter.dashscope.DashScopeChatFormatter;
public class MultiTurnChatDemo {
public static void main(String[] args) {
String apiKey = "sk-e4902ea9d4164c1fa9d88ca86b2645c8";
// 创建 Memory(负责维护会话历史) InMemoryMemory memory = new InMemoryMemory();
// 创建 Agent ReActAgent agent = ReActAgent.builder() .name("Assistant") .sysPrompt("You are a helpful AI assistant. Remember what the user tells you.") .model(DashScopeChatModel.builder() .apiKey(apiKey) .modelName("qwen-max") .build()) .memory(memory) // 注入 Memory .build();
// === 第1轮 === Msg msg1 = Msg.builder() .role(MsgRole.USER) .content(TextBlock.builder().text("My name is Hollis and I'm a software engineer.").build()) .build(); Msg reply1 = agent.call(msg1).block(); System.out.println("Agent: " + reply1.getTextContent());
// === 第2轮(Agent 能记住第1轮信息)=== Msg msg2 = Msg.builder() .role(MsgRole.USER) .content(TextBlock.builder().text("What's my name and what do I do?").build()) .build(); Msg reply2 = agent.call(msg2).block(); System.out.println("Agent: " + reply2.getTextContent()); // Agent 会回答: "Your name is Hollis and you're a software engineer."
// 查看 Memory 中的完整对话历史 System.out.println("Total messages in memory: " + memory.getMessages().size()); // 输出: 4(user1 + assistant1 + user2 + assistant2) }}



会话持久化:Session 体系



当 JVM 重启、或需要在集群场景中多个实例间共享会话状态时,需要将 Memory 等组件的状态持久化。



在model scope中,持久化需要靠Session。纯靠memory是不行的。Session接口中提供了Session的CURD的相关方法的定义。







在agent scope中,提供了一些默认的session实现:







包括基于Redis、MySQL以及JSON文件存储的持久化方案。



第一次对话,记忆持久化保存:



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package cn.hollis.llm.llmentor.agentscope.demo;
import io.agentscope.core.ReActAgent;
import io.agentscope.core.memory.InMemoryMemory;
import io.agentscope.core.message.Msg;
import io.agentscope.core.message.MsgRole;
import io.agentscope.core.message.TextBlock;
import io.agentscope.core.model.DashScopeChatModel;
import io.agentscope.core.session.JsonSession;
import io.agentscope.core.session.Session;
import java.nio.file.Path;
import java.nio.file.Paths;
public class PersistentChatDemo {
public static void main(String[] args) {
String apiKey = "sk-e4902ea9d4164c1fa9d88ca86b2645c8";
String sessionId = "user_hollis_session";
// 1. 创建 Session(JSON文件持久化) Path sessionPath = Paths.get(System.getProperty("user.home"), ".agentscope", "examples", "sessions"); Session session = new JsonSession(sessionPath);
// 2. 创建 Agent 组件 InMemoryMemory memory = new InMemoryMemory();
ReActAgent agent = ReActAgent.builder() .name("Assistant") .sysPrompt("You are a helpful AI assistant with persistent memory. ") .model(DashScopeChatModel.builder() .apiKey(apiKey) .modelName("qwen-max") .build()) .memory(memory) .build();
// 3. 如果之前有保存的会话,加载它(恢复历史上下文) boolean resumed = agent.loadIfExists(session, sessionId); if (resumed) { System.out.println("Session restored! " + memory.getMessages().size() + " messages loaded."); } else { System.out.println("New session started."); }
// 4. 发送新消息(延续之前的对话上下文) Msg userMsg = Msg.builder() .role(MsgRole.USER) .content(TextBlock.builder().text("My name is Hollis and I'm a software engineer.").build()) .build();
Msg response = agent.call(userMsg).block(); System.out.println("Agent: " + response.getTextContent());
// 5. 保存会话(下次启动时可恢复) agent.saveTo(session, sessionId); System.out.println("Session saved. Messages in memory: " + memory.getMessages().size()); }}



运行之后,可以看到保存下来的记忆文件:

1
~/.agentscope/sessions/       # 默认存储目录(可自定义)  └── user_hollis_session/    # 每个 SessionKey 一个子目录      ├── agent_meta.json     # Agent 元数据      ├── memory_messages.jsonl  # 消息列表(JSONL 格式,增量追加)      ├── memory_messages.hash # hash 文件(变更检测,避免不必要的全量重写)      └── toolkit_activeGroups.json  # Toolkit 状态





然后修改一下对话内容:



1
Msg userMsg = Msg.builder()        .role(MsgRole.USER)        .content(TextBlock.builder().text("What's my name and what do I do?").build())        .build();



输出结果:



1
Session restored! 2 messages loaded.Agent: Your name is Hollis, and you're a software engineer. Is there anything specific about your work or projects that you'd like to share or discuss?Session saved. Messages in memory: 4



agentScope中还有一个SessionManager ,他提供了更简洁的链式 API:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package cn.hollis.llm.llmentor.agentscope.demo;
import io.agentscope.core.ReActAgent;
import io.agentscope.core.memory.InMemoryMemory;
import io.agentscope.core.message.Msg;
import io.agentscope.core.message.MsgRole;
import io.agentscope.core.message.TextBlock;
import io.agentscope.core.model.DashScopeChatModel;
import io.agentscope.core.session.JsonSession;
import io.agentscope.core.session.Session;
import io.agentscope.core.session.SessionManager;
import java.nio.file.Path;
import java.nio.file.Paths;
public class SessionManagerChatDemo {
public static void main(String[] args) {
String apiKey = "sk-e4902ea9d4164c1fa9d88ca86b2645c8";
String sessionId = "user_hollis_session";
// 1. 创建 Session(JSON文件持久化) Path sessionPath = Paths.get(System.getProperty("user.home"), ".agentscope", "examples", "sessions"); Session session = new JsonSession(sessionPath);
// 2. 创建 Agent 组件 InMemoryMemory memory = new InMemoryMemory();
ReActAgent agent = ReActAgent.builder() .name("Assistant") .sysPrompt("You are a helpful AI assistant with persistent memory. ") .model(DashScopeChatModel.builder() .apiKey(apiKey) .modelName("qwen-max") .build()) .memory(memory) .build();
// === 加载会话 === SessionManager sessionManager = SessionManager.forSessionId(sessionId) .withSession(new JsonSession(Path.of("sessions"))) .addComponent(agent);
sessionManager.loadIfExists(); // 存在则加载,不存在则什么都不做
// 4. 发送新消息(延续之前的对话上下文) Msg userMsg = Msg.builder() .role(MsgRole.USER) .content(TextBlock.builder().text("What's my name and what do I do?").build()) .build();
Msg response = agent.call(userMsg).block(); System.out.println("Agent: " + response.getTextContent());
// 5. 保存会话(下次启动时可恢复) sessionManager.saveSession(); System.out.println("Session saved. Messages in memory: " + memory.getMessages().size()); }}



StatePersistence——精细控制持久化范围



默认情况下 Agent 的 saveTo/loadFrom 会自动管理所有组件。如果你想自己管理某些组件的状态,可以通过 StatePersistence 配置:



1
2
3
4
5
import io.agentscope.core.state.StatePersistence;
// 默认:管理所有组件ReActAgent agent1 = ReActAgent.builder() .name("assistant") .model(model) .memory(memory) .build(); // statePersistence 默认 = StatePersistence.all()
// 只管理 Memory(Toolkit 和 PlanNotebook 由用户自行管理)ReActAgent agent2 = ReActAgent.builder() .name("assistant") .model(model) .memory(memory) .statePersistence(StatePersistence.memoryOnly()) .build();
// 完全不管理(用户自行管理所有状态)ReActAgent agent3 = ReActAgent.builder() .name("assistant") .model(model) .statePersistence(StatePersistence.none()) .build();
// 自定义:管理 Memory 和 Toolkit,但不管理 PlanNotebookReActAgent agent4 = ReActAgent.builder() .name("assistant") .model(model) .memory(memory) .statePersistence(StatePersistence.builder() .memoryManaged(true) .toolkitManaged(true) .planNotebookManaged(false) .statefulToolsManaged(false) .build()) .build();



StatePersistence中包含四个组件,分别对应 ReActAgent 内部的四个核心模块:



Memory(对话记忆)

就是上面讲的 InMemoryMemory,存储当前会话的所有消息列表(用户输入、LLM 回复、工具调用结果等)。持久化时以 JSONL 格式保存为 memory_messages.jsonl,恢复时重新加载到内存中,实现跨重启的多轮对话延续。



Toolkit(工具集)



Agent 可用的工具注册表。Toolkit 内部支持”工具分组”(Tool Groups),通过 activeGroups 控制当前激活哪些工具组。持久化时保存的是 toolkit_activeGroups——即哪些工具组处于激活状态。这样恢复会话后,Agent 仍然只使用之前激活的那组工具,而不是全部重置。



PlanNotebook(计划笔记本)

Agent 的任务规划/执行跟踪模块。当 Agent 处理复杂多步任务时,PlanNotebook 记录计划步骤、执行状态、中间结果等。持久化后,恢复会话时 Agent 能知道”上次执行到哪一步了”,继续未完成的计划,而不是从头开始。



StatefulTools(有状态工具)

某些工具本身是有状态的——比如一个”购物车工具”可能维护了当前购物车内容,一个”文件编辑工具”可能记录了当前打开的文件和光标位置。这类工具实现了 StateModule 接口,可以自行定义如何 save/load 状态。statefulToolsManaged = true 时,Agent 的 saveTo/loadFrom 会自动遍历所有有状态工具并保存/恢复它们的状态。



这四个分别是”聊了什么”、”能用什么工具”、”计划执行到哪了”、”工具自身的内部状态”。****StatePersistence 让你选择性地决定哪些需要框架自动管理持久化,哪些你自己来控。





为什么要区分Memory和Session



第一次学这个玩意的时候,肯定会有疑问,agentscope为什么把memory和session分开,不能像spring ai alibaba一样直接用memory的机制么?为什么还要开发者手动调用session的维护?



其实是,AgentScope 的 Agent 状态远比”消息列表”复杂得多,而且在 ReAct 循环中对持久化时机有严格的控制需求。



Agent 的”状态”不只是消息。Spring AI Alibaba 的 ChatMemory 面向的是简单的”一问一答”对话模式,状态 ≈ 消息列表,把持久化做进 Memory 实现里是自然的。

但 AgentScope 的 ReActAgent 一次 call() 可能经历 5-10 轮内部推理+工具调用循环,它的完整状态包括:

  • 消息历史(Memory)

  • 当前激活的工具组(Toolkit activeGroups)

  • 多步计划的执行进度(PlanNotebook)

  • 有状态工具的内部数据(StatefulTools)

  • Agent 元数据(sysPrompt 可能被动态修改)

如果像 Spring AI 一样把持久化耦合进 Memory,其他四个组件的持久化就没有统一出口了。Session 作为独立抽象层,统一解决了”所有有状态组件的持久化”问题。



而且,Memory 的 addMessage() 在一次用户请求中可能被调用十几次(每一步推理、每一个工具结果、流式 chunk 处理等)。如果每次 addMessage 都触发持久化写入:

  • 同步写 → 严重拖慢 Agent 响应

  • 异步写 → 中间状态不一致(Agent 还在推理中,你保存了半截状态)

  • 批量缓冲写 → 在 Memory 里引入复杂的 buffer/flush 逻辑,职责不纯

分离后,Memory 只管内存中的快速读写,Session 的保存时机完全由开发者决定。



一次 agent.call() 中间可能出错(工具执行失败、达到 maxIters、被用户中断)。如果自动持久化,你会面临”保存了一个不一致的中间状态”。



AgentScope 的做法是让开发者选择 commit point,这类似于数据库事务——你不会希望每条 SQL 自动 commit,而是在业务逻辑完成后显式提交。



其实框架也提供了部分自动化:GracefulShutdown 自动保存——agent.loadIfExists() 会自动绑定 Session 到 ShutdownManager,JVM 关闭时自动 saveTo