Spring博客更新了《Spring AI智能体模式》系列的第七篇,Spring AI 全新会话 API支持结构化、可压缩、支持多智能体,将用来替代版聊天内存(ChatMemory),下面是Spring AI会话API的主要内容。
此前系列文章已讲解智能体能力、用户提问工具、待办编辑工具、子智能体编排、智能体互联集成以及跨会话长期记忆自动化工具;本文将补充配套的短期记忆能力:Spring AI 会话。
将对话历史以纯消息列表存储,仅适用于简短对话,一旦会话内容增多便会出现问题:简单的截断机制会在工具调用流程中途静默丢弃数据,导致大模型收到孤立的返回结果、对话轮次结构错乱。
Spring AI 会话可自动记录交互过程中的所有消息、工具调用与返回结果,智能管理上下文窗口;而自动化记忆工具则负责持久化需要跨会话保留的核心信息。一套完整的智能体内存架构需要二者相辅相成,无法相互替代。
版本规划
目前功能在 Spring AI 社区组件中孵化,计划随 Spring AI 2.1 版本(2026 年 11 月) 正式发布,届时旧版聊天内存(ChatMemory)将被废弃,全面由会话 API 替代。
旧版 ChatMemory 会直接淘汰最早的消息,无法保证对话轮次完整性、缺少事件唯一标识、不支持多智能体,且不会记录丢弃的数据内容。
Spring AI 会话 API 基于事件溯源日志重构,提供可插拔的上下文压缩策略、分支隔离能力以及可关键词检索的历史回溯存储。
会话 API 架构
Spring AI 会话 API 核心类
会话(Session)与会话事件(SessionEvent)
Session:不可变的纯元数据对象,仅存储会话 ID、用户 ID、过期时间(TTL)及自定义元数据。事件日志统一持久化至仓储层,按需加载。
SessionEvent:封装 Spring AI 消息,并补充原生消息缺失的核心信息:唯一 UUID、会话 ID、时间戳、适配多智能体层级的分支标签、框架内置标识(如合成标记元数据)。
SessionService service = new DefaultSessionService(InMemorySessionRepository.builder().build());
Session session = service.create(
CreateSessionRequest.builder().userId("alice").build()
);
service.appendMessage(session.id(), new UserMessage("What is Spring AI?"));
service.appendMessage(session.id(), new AssistantMessage("Spring AI is..."));
List<Message> history = service.getMessages(session.id()); // 可直接传入大模型对话轮次(Turn)
轮次是对话的最小原子单元:一条用户消息 + 后续所有关联事件(助手回复、工具调用、工具返回结果),直至下一条用户消息出现。
所有上下文压缩策略均以轮次为最小执行粒度,确保保留的上下文永远以用户消息开头。杜绝孤立的工具返回结果、碎片化对话片段传入大模型。
轮次 1:[用户消息「Spring AI 是什么?」] → [助手回复] 轮次 2:[用户消息「它可以调用工具吗?」] → [助手工具调用] → [工具返回数据] → [助手最终回答]
上下文压缩
上下文压缩可精简事件日志,在控制上下文窗口长度的同时,保证对话逻辑连贯。核心由两大可组合组件驱动:压缩触发器与压缩策略。
压缩触发器
new TurnCountTrigger(20); // 轮次超过20次触发压缩 TokenCountTrigger.builder().threshold(4000).build(); // 预估Token达到4000触发压缩 // 组合触发器:满足任一条件即触发 CompositeCompactionTrigger.anyOf( new TurnCountTrigger(20), TokenCountTrigger.builder().threshold(4000).build() );
压缩策略
压缩策略 | 是否调用大模型 | 适用场景 |
滑动窗口压缩策略 | 否 | 控制成本、短期简易对话 |
轮次窗口压缩策略 | 否 | 以轮次为核心的结构化对话 |
Token 数量压缩策略 | 否 | 有严格上下文长度限制的场景 |
递归摘要压缩策略 | 是 | 长周期、高信息密度的复杂会话 |
前三种策略会保留最新的原始事件内容(按消息数 / 轮次数 / Token 上限裁剪),且裁剪边界自动对齐完整轮次,不会切割单次对话流程。
递归摘要压缩 能力最强:调用大模型对归档的历史事件进行摘要浓缩,并将摘要内容存储为一条合成的用户 + 助手对话轮次。多次压缩会基于历史摘要迭代优化,持续生成轻量化的滚动式压缩日志,无需每次全量重写。
RecursiveSummarizationCompactionStrategy.builder(chatClient) .maxEventsToKeep(10) .overlapSize(2) // 携带最近2条活跃事件作为摘要上下文 .build();
注意:触发器与压缩策略必须成对配置,仅配置单一项会在初始化时抛出非法参数异常;若需关闭压缩功能,可同时省略两项配置。
整合聊天客户端(ChatClient)
会话内存顾问(SessionMemoryAdvisor)可无缝将会话管理能力接入聊天客户端调用链路。
每次请求自动加载历史对话、拼接上下文、追加新消息,触发压缩规则时自动执行精简,业务代码无需手动处理会话逻辑。
@Bean
SessionMemoryAdvisor sessionMemoryAdvisor(SessionService sessionService,
ChatClient.Builder chatClientBuilder) {
return SessionMemoryAdvisor.builder(sessionService)
.defaultUserId("alice")
.compactionTrigger(new TurnCountTrigger(20))
.compactionStrategy(
RecursiveSummarizationCompactionStrategy.builder(chatClientBuilder.build())
.maxEventsToKeep(10)
.build()
)
.build();
}
@Bean
ChatClient chatClient(ChatClient.Builder chatClientBuilder, SessionMemoryAdvisor advisor) {
return chatClientBuilder.defaultAdvisors(advisor).build();
}调用时通过上下文传入会话 ID:
String response = chatClient.prompt()
.user("Hello!")
.advisors(a -> a.param(SessionMemoryAdvisor.SESSION_ID_CONTEXT_KEY, "session-abc"))
.call()
.content();若指定会话 ID 不存在,顾问组件会自动创建新会话。
多智能体分支隔离
当调度主智能体并行调度多个子智能体时,所有智能体可共享同一个会话,但各智能体仅能查看自身及上级分支的事件数据。
SessionEvent 的分支字段采用点分隔路径,标记事件所属智能体在层级架构中的位置:主调度智能体:分支 = "orch"
调研子智能体:分支 = "orch.researcher"
写稿子智能体:分支 = "orch.writer"
分支为空的事件属于根级数据,所有智能体均可访问。通过分支过滤器可自动实现数据隔离:
// 调研智能体可见:根事件 + 主调度事件 + 自身分支事件
// 自动屏蔽同级写稿智能体的私有事件
SessionMemoryAdvisor researcherAdvisor = SessionMemoryAdvisor.builder(sessionService)
.defaultSessionId(sharedSessionId)
.eventFilter(EventFilter.forBranch("orch.researcher"))
.build();递归摘要生成的合成事件统一归属根分支,确保所有智能体都能查看全局压缩摘要。
历史回溯存储
上下文压缩能优化提示词体积,但老旧事件会被移出实时上下文窗口。
SessionEventTools 落地了 MemGPT 回溯存储设计:完整原始事件日志永久留存,即便已从上下文裁剪,仍支持关键词检索。ChatClient client = ChatClient.builder(chatModel) .defaultTools(SessionEventTools.builder(sessionService).build()) .defaultAdvisors(advisor) .build();
conversation_search 工具由 Spring AI 自动注册。大模型需要回溯历史对话时,可通过关键词 + 分页参数调用该工具,按时间序返回结构化 JSON 结果;合成摘要事件同样会被索引,支持检索查询。JDBC 持久化
spring-ai-session-jdbc 提供数据库持久化方案,通过两张数据表(会话表 AI_SESSION、仅追加事件日志表 AI_SESSION_EVENT)存储数据,兼容 PostgreSQL、MySQL、MariaDB、H2 数据库。
Spring Boot 启动器可全自动完成配置:<dependency> <groupId>org.springaicommunity</groupId> <artifactId>spring-ai-starter-session-jdbc</artifactId> </dependency>
PostgreSQL / MySQL 环境开启自动建表:
spring: ai: session: repository: jdbc: initialize-schema: always
无需手动注册额外 Bean。
快速入门
环境要求
Java 17 及以上、Spring AI 2.0.0-M4 及以上、Spring Boot 4.0.2 及以上
引入依赖版本管理
<dependencyManagement> <dependencies> <dependency> <groupId>org.springaicommunity</groupId> <artifactId>spring-ai-session-bom</artifactId> <version>0.2.0</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
引入启动器 生产环境选用 JDBC 版本,本地开发可仅引入内存版会话管理:
<dependency> <groupId>org.springaicommunity</groupId> <artifactId>spring-ai-starter-session-jdbc</artifactId> </dependency>
注册组件并使用
@Bean
SessionMemoryAdvisor sessionMemoryAdvisor(SessionService sessionService) {
return SessionMemoryAdvisor.builder(sessionService)
.defaultUserId("alice")
.compactionTrigger(new TurnCountTrigger(20))
.compactionStrategy(SlidingWindowCompactionStrategy.builder().maxEvents(10).build())
.build();
}
@Bean
ChatClient chatClient(ChatModel chatModel, SessionMemoryAdvisor advisor) {
return ChatClient.builder(chatModel).defaultAdvisors(advisor).build();
}Session session = sessionService.create(
CreateSessionRequest.builder().userId("alice").build()
);
String response = chatClient.prompt()
.user("What is Spring AI?")
.advisors(a -> a.param(SessionMemoryAdvisor.SESSION_ID_CONTEXT_KEY, session.id()))
.call()
.content();从 ChatMemory 迁移至会话 API
会话 API 将全面替代旧版 ChatMemory,成为 Spring AI 主流的对话持久化方案:
对比项 | 旧版 ChatMemory | Spring AI 会话 |
存储单元 | 扁平消息列表 | 不可变事件、带时间戳与唯一标识 |
压缩能力 | 粗暴淘汰旧消息 | 四种可插拔策略,支持大模型摘要 |
轮次安全 | 无保障 | 强制对齐完整对话轮次 |
多智能体 | 不支持 | 点式分支标签隔离 |
历史检索 | 无 | 内置检索工具,关键词回溯 |
并发能力 | 依赖底层实现 | 全场景乐观并发控制 |
示例等价改造:
原代码
MessageWindowChatMemoryMessageWindowChatMemory.builder().maxMessages(20).build()
对应会话 API 写法:
SessionMemoryAdvisor.builder(sessionService) .compactionTrigger(new TurnCountTrigger(20)) .compactionStrategy(SlidingWindowCompactionStrategy.builder().maxEvents(20).build()) .build();