【AgentScope Java新手村系列】(5)记忆与会话管理

简介: 记忆与会话管理 — AgentState 管理上下文窗口,AgentStateStore 持久化,RuntimeContext.sessionId 隔离多用户会话。

第五章 记忆与会话管理:用 AgentStateStore 替换 Memory,实现多轮对话持久化

"早上问了 weather_agent 杭州天气,半小时后追问'那上海呢?'——它竟然记得前文。这是 2.0 用 AgentStateStore 持久化 + RuntimeContext.sessionId 的作用,对老版本 Memory 接口的彻底替代。"

本章你将学到:.stateStore() + RuntimeContext.sessionId() 双钥匙机制、四种 Store 后端的选型与配置、以及生产环境下的多用户隔离。

5.1 为什么把 Memory 改成 AgentState + AgentStateStore?

1.x 的 Memory / InMemoryMemory / LongTermMemory 接口在 2.0.0-RC2 中被标为 @Deprecated(forRemoval = true),原因有三:

  1. 职责单一Memory 既是"当前对话上下文"又是"长期知识库",两件事放在一个抽象里很难扩展。
  2. 持久化无关 — 1.x 时代需要自己把 Memory 序列化进 Redis/MySQL;2.0 把这件事下沉到 AgentStateStore 后端里,业务代码与持久化彻底解耦。
  3. 与 harness 解耦HarnessAgent 鼓励把状态放在 workspace 目录、subagent 文件、skill 仓库中,这些内容天然不适合塞进一个 Java 对象。

新模型:

概念 2.0 替代 说明
Memory(当前对话窗口) AgentState (框架内部对象,用户不直接操作) + AgentStateStore Agent 自动读写,你只需配 Store
LongTermMemory workspace 下的 MEMORY.md + memory/YYYY-MM-DD.md + CompactionMiddleware / MemoryFlushMiddleware 跨会话的稳定知识
自定义持久化 AgentStateStore 后端(InMemoryAgentStateStore / JsonFileAgentStateStore / RedisAgentStateStore / MysqlAgentStateStore 跨进程、跨重启保留状态

核心认知:你只需要配好 Store + 传好 sessionId,其余全交给 HarnessAgentAgentState 是框架内部对象,日常开发不需要碰它。

⚠️ 2.0 重大变更

旧代码里 agent.getMemory().add(...)LongTermMemory.retrieve(...)InMemoryMemory 子类等 API 全部走 @Deprecated 通道,2.0 之后会被删除。本章末尾给出一份最小迁移清单

5.2 第一个例子:双钥匙缺一不可

要实现跨调用的记忆,需要两样东西:

钥匙 作用 在哪配
AgentStateStore 决定状态存哪里(内存 / 文件 / Redis / MySQL) HarnessAgent.builder().stateStore(...)
RuntimeContext.sessionId 决定「这是哪场会话」——Store 查询的主键 每次 agent.call(msg, rt) 时传入

少任何一样,就没有记忆。 不配 Store 无法持久化;不传 sessionId 每次都是新会话。

import io.agentscope.core.agent.RuntimeContext;
import io.agentscope.core.state.JsonFileAgentStateStore;
import io.agentscope.core.message.UserMessage;
import io.agentscope.core.model.DashScopeChatModel;
import io.agentscope.harness.HarnessAgent;

import java.nio.file.Path;
import java.util.List;

public class Chapter05_Basic {
    public static void main(String[] args) {
        // (1)配 Store ———— 钥匙一:决定状态存哪
        JsonFileAgentStateStore store = new JsonFileAgentStateStore(
                Path.of("./workspace", "state"));

        HarnessAgent agent = HarnessAgent.builder()
                .name("weather_bot")
                .sysPrompt("你是一个中文天气助手,每次回答不超过 50 字。")
                .model(DashScopeChatModel.builder()
                        .apiKey(System.getenv("DASHSCOPE_API_KEY"))
                        .modelName("qwen-plus")
                        .build())
                .workspace(Path.of("./workspace"))
                .stateStore(store)          // ← 钥匙一
                .build();

        // (2)传 sessionId ———— 钥匙二:决定是哪场会话
        RuntimeContext rt = RuntimeContext.builder()
                .sessionId("user-9527-2026-06-07")
                .userId("9527")
                .build();

        // 第一轮
        agent.call(
                List.of(new UserMessage("user", "今天杭州天气怎么样?")), rt).block();

        // 第二轮 ———— 同一个 sessionId,上下文自动恢复
        System.out.println(agent.call(
                List.of(new UserMessage("user", "那上海呢?")), rt).block().getTextContent());
    }
}

内部流程(框架自动完成,你不用管):

agent.call(msg, rt)
  |
  +-- 根据 rt.sessionId 去 store 查有无旧状态
  |     |-- 有 --> 反序列化成 AgentState(内部对象)--> 恢复上下文
  |     \-- 无 --> 新建空的 AgentState
  |
  +-- 把 msg + agent 的回复追加进 AgentState
  |
  \-- 完成后把 AgentState 写回 store

你只做了两件事.stateStore(store) + sessionId,剩下全自动。AgentState 是框架内部对象,日常开发不需要直接操作它。

5.3 AgentState 的三个核心方法

AgentStateio.agentscope.core.state)对外只暴露三件事:

方法 用途
List<Msg> getContext() 只读视图,拿到当前上下文里所有消息
void addMessage(Msg msg) 往上下文里追加一条消息(业务方主动注入时用)
int size() 当前上下文的消息条数

Msg 在 2.0 依旧是不可变对象,任何想修改消息的尝试都需要新建一条。所以“清空上下文”也不是 state.clear(),而是用新的 Session 实例,或在 Session 之上自己开一个新的 sessionId

5.4 RuntimeContext —— 给单次调用贴上标签

RuntimeContext 解决的是“同一份 agent 跑多个用户 / 多场对话”这件事。把它作为 call() 的第二个参数传入:

import io.agentscope.core.agent.RuntimeContext;

agent.call(
        List.of(new UserMessage("user", "晚上想喝粥")),
        RuntimeContext.builder()
                .sessionId("user-9527-2026-06-07")
                .userId("9527")
                .build());

字段说明:

  • sessionId — 把这一通对话归到某个会话桶。配合 AgentStateStore 后端时,这是主键
  • userId — 多租户场景下区分用户,方便 RedisAgentStateStore 命名空间隔离。
  • 其他traceId / metadata 用于在事件流里打点,配合 streamEvents() 做日志关联。

5.5 AgentStateStore —— 把状态搬到磁盘 / Redis / MySQL

5.5.1 不传 AgentStateStore:默认 InMemoryAgentStateStore

HarnessAgent.builder() 不显式 .stateStore(...) 时,框架默认安装一个 InMemoryAgentStateStore(每个 HarnessAgent 进程内独立)。但即便有默认 Store,不传 sessionId 也不会有跨调用记忆——Store 按 sessionId 做主键索引,空 key 每次覆盖。
重启进程、部署多实例时数据会丢。

5.5.2 单机文件版:JsonFileAgentStateStore

开发环境推荐用 JsonFileAgentStateStore,状态会落到 workspace/state/session-<id>.json,重启服务能恢复:

import io.agentscope.core.state.JsonFileAgentStateStore;
import io.agentscope.harness.HarnessAgent;

HarnessAgent agent = HarnessAgent.builder()
        .name("weather_bot")
        .sysPrompt("...")
        .model(model)
        .workspace(Path.of("./workspace"))
        .stateStore(new JsonFileAgentStateStore(Path.of("./workspace/state")))
        .build();

workspace/state/ 目录里会看到这样的文件:

workspace/
└── state/
    ├── session-user-9527-2026-06-07.json
    └── session-user-9528-2026-06-07.json

5.5.3 生产环境:RedisAgentStateStore

生产环境必须把状态放 Redis。AgentScope 提供现成的 RedisAgentStateStore(在 agentscope-extensions-redis 模块里):

<dependency>
    <groupId>io.agentscope</groupId>
    <artifactId>agentscope-extensions-redis</artifactId>
    <version>2.0.0-RC2</version>
</dependency>

import io.agentscope.extensions.redis.state.RedisAgentStateStore;
import io.agentscope.harness.HarnessAgent;
import redis.clients.jedis.UnifiedJedis;

UnifiedJedis client = new UnifiedJedis("redis://127.0.0.1:6379");

HarnessAgent agent = HarnessAgent.builder()
        .name("weather_bot")
        .sysPrompt("...")
        .model(model)
        .workspace(Path.of("./workspace"))
        .stateStore(RedisAgentStateStore.builder()
                .jedisClient(client)
                .build())
        .build();

RedisAgentStateStore 使用 Builder 模式配置:

  1. jedisClient — 由你控制连接配置(单机、哨兵、集群均可通过 UnifiedJedis 实现)。
  2. 也支持 LettuceRedisson 客户端适配,通过对应的 LettuceClientAdapter / RedissonClientAdapter

5.5.4 重型企业级:MysqlAgentStateStore

需要事务、强一致、审计时,用 MysqlAgentStateStore(在 agentscope-extensions-mysql 模块里):

import io.agentscope.extensions.mysql.state.MysqlAgentStateStore;
import javax.sql.DataSource;

HarnessAgent agent = HarnessAgent.builder()
        .stateStore(new MysqlAgentStateStore(dataSource, true))
        .build();

迁移到 MySQL 时记得在首次启动前执行模块自带的 schema.sql,会创建 agent_state 表。

5.6 完整可运行示例

import io.agentscope.core.agent.RuntimeContext;
import io.agentscope.core.message.UserMessage;
import io.agentscope.core.model.DashScopeChatModel;
import io.agentscope.extensions.redis.state.RedisAgentStateStore;
import io.agentscope.harness.HarnessAgent;
import redis.clients.jedis.UnifiedJedis;

import java.nio.file.Path;
import java.util.List;

public class Chapter05_Redis {
    public static void main(String[] args) {
        UnifiedJedis client = new UnifiedJedis("redis://127.0.0.1:6379");

        HarnessAgent agent = HarnessAgent.builder()
                .name("weather_bot")
                .sysPrompt("你是一个中文天气助手,每次回答不超过 50 字。")
                .model(DashScopeChatModel.builder()
                        .apiKey(System.getenv("DASHSCOPE_API_KEY"))
                        .modelName("qwen-plus")
                        .build())
                .workspace(Path.of("./workspace"))
                .stateStore(RedisAgentStateStore.builder()
                        .jedisClient(client)
                        .build())
                .build();

        // user-9527 的第一通对话
        agent.call(
                List.of(new UserMessage("user", "今天杭州天气怎么样?")),
                RuntimeContext.builder()
                        .sessionId("user-9527-2026-06-07")
                        .userId("9527")
                        .build())
                .block();

        // user-9528 的第一通对话 —— 同一份 agent 进程,state 完全隔离
        agent.call(
                List.of(new UserMessage("user", "上海今天冷不冷?")),
                RuntimeContext.builder()
                        .sessionId("user-9528-2026-06-07")
                        .userId("9528")
                        .build())
                .block();

        // user-9527 半小时后再问 —— 上下文从 Redis 恢复
        agent.call(
                List.of(new UserMessage("user", "那上海呢?")),
                RuntimeContext.builder()
                        .sessionId("user-9527-2026-06-07")
                        .userId("9527")
                        .build())
                .block();

        client.close();
    }
}

这段代码里两个用户共享同一个 HarnessAgent 实例——RuntimeContext.sessionId() 是把状态映射到不同桶的钥匙,2.0 的这种设计可以让你在生产里只起 1 个 agent 进程服务所有用户。

5.7 与长期记忆(MEMORY.md)的协作

AgentStateStore 只负责“当前会话上下文”;想跨会话记忆用户的偏好 / 习惯,得借助 workspace 下的两套文件:

  • MEMORY.md — 一份长期稳定的“事实笔记”(用户昵称、过敏原、长期目标)。

  • memory/YYYY-MM-DD.md — 按天追加的“事件笔记”(昨天吃了什么、今天计划做什么)。

    2.0 提供了两个 Middleware 让这件事全自动:

  • CompactionMiddleware — 当上下文超过 token 阈值时,把旧消息压缩成摘要写进 MEMORY.md

  • MemoryFlushMiddleware — 每轮对话结束后把需要长期保留的内容刷到当日 memory/<date>.md

详细配置见第 18 章 Skill & 长期记忆。

5.8 最小迁移清单(1.x → 2.0)

AgentState 是框架内部对象。迁移表中的 agent.state() 指 RC1 旧 API,RC2 中为 agent.getAgentState()。这些接口仅在 Middleware 等编程场景使用,日常对话不需要。

1.x 用法 2.0 等价
agent.getMemory() 读上下文 agent.getAgentState().getContext()(内部 API)
agent.getMemory().add(Msg) agent.getAgentState().addMessage(Msg)(内部 API)
agent.getMemory().size() agent.getAgentState().size()(内部 API)
new InMemoryMemory() 默认行为,无需配置
LongTermMemory.retrieve(...) workspace 下 MEMORY.md + SkillRepository
LongTermMemory.record(...) MemoryFlushMiddleware
自定义 Memory 子类持久化 AgentStateStore 后端
agent.call(messages) agent.call(messages, RuntimeContext.empty())
agent.stream(messages) agent.streamEvents(messages, ctx)
Msg.builder().role(USER)... new UserMessage("name", "text")

5.9 本章小结

  • 2.0 的记忆 = .stateStore(store) + RuntimeContext.sessionId——两样缺一就没有记忆。
  • 同一个 HarnessAgent 实例可以服务多个用户 / 多场会话,靠 RuntimeContext.sessionId() 区分。
  • 开发期用 JsonFileAgentStateStore、生产用 RedisAgentStateStore / MysqlAgentStateStore,按需切换。
  • Agent 在 RC2 中完全无状态化——同一实例可安全并发服务多 (userId, sessionId) 组合。
  • 跨会话的稳定知识靠 workspace 下的 MEMORY.md + memory/*.md + CompactionMiddleware / MemoryFlushMiddleware,见第 18 章。

下一章我们会在此基础上接入 Hook/Middleware 系统,演示怎么在每一轮里插桩做日志/计费/审计。

目录
相关文章
|
JSON 自然语言处理 Java
【AgentScope Java新手村系列】(4)结构化输出
结构化输出 — JSON Schema 约束 LLM 输出格式,直接反序列化为 Java POJO,打通文本到对象的转换。
208 0
|
18天前
|
缓存 中间件 Java
【AgentScope Java新手村系列】(6)Hook与Middleware
Hook与Middleware — 五类 Middleware 回调点(onModelCall/onActing等)替代 1.x Hook,实现日志埋点与限流。
176 0
|
前端开发 NoSQL Java
【AgentScope Java新手村系列】(2)第一个Agent-基础对话
第一个Agent-基础对话 — 演示 HarnessAgent 的 Builder 模式创建、ReAct 推理循环、流式事件与思考模式三个核心能力。
284 1
|
18天前
|
自然语言处理 Java API
【AgentScope Java新手村系列】(7)子Agent编排
子Agent编排 — SubagentDeclaration 描述子 agent,主 agent 通过 agent_spawn 工具同步/异步委派子任务。
216 0
|
15天前
|
Java 中间件 API
【AgentScope Java新手村系列】(10)实战-多Agent天气助手
实战-多Agent天气助手 — 文件驱动 subagent 实战,主 agent 自主编排天气查询、航班搜索、景点推荐三个子任务并行调研。
210 1
|
17天前
|
Java
【AgentScope Java新手村系列】(8)多Agent协作
多Agent协作 — orchestrator + workers 模式替代 MsgHub,一个 HarnessAgent 搭载多个 SubagentDeclaration,LLM 主持群聊与辩论。
250 0
|
前端开发 Java 中间件
【AgentScope Java新手村系列】(3)工具系统
工具系统 — @Tool/@ToolParam 注解将 Java 方法注册为 Agent 能力,自主决定调用时机,支持同步/异步返回。
195 0
|
16天前
|
前端开发 NoSQL Java
【AgentScope Java新手村系列】(9)SpringBoot集成
SpringBoot集成 — 工厂方法将 HarnessAgent 注册为单例 Bean,WebFlux 流式输出 streamEvents 到 SSE 端点。
143 3
|
22天前
|
机器学习/深度学习 人工智能 网络架构
深度解析:Transformer 的“灵魂”——QKV 变换的物理直觉
本文用图书馆检索等生活隐喻,从物理意义与认知科学角度解析Transformer中QKV设计的精妙本质:解耦查询(q)、键(k)、值(v)三重角色,实现语义分离、避免自注意力“自恋”,模拟人类动态信息路由的认知过程。(239字)
355 13
|
20天前
|
缓存 人工智能 API
阿里云百炼Token Plan团队版与Coding Plan核心差异全解析 附团队版全场景常见问题完整答疑
随着大模型在研发、办公、企业自动化场景常态化落地,不同使用者群体的算力消耗特征出现明显分化:数十人多岗位协同的企业团队,存在多角色额度分配、跨业务线统一计费、月度预算锁定、高峰算力保障等综合管理需求;而独立程序员、外包开发小组、学生研发爱好者,绝大多数算力消耗集中在代码生成、调试、项目重构、脚本编写等开发场景,对文档分析、多模态图文处理需求极低,更看重轻量化低价订阅、代码专属折扣、编程工具配套权益。
228 0

热门文章

最新文章