1. 概述
Spring AI Session 是一个专为 Spring AI 应用设计的会话(Session)管理库,它提供了基于 JDBC 的会话持久化 、记忆注入 和历史压缩 能力。
简单来说,它能让你的 AI 聊天机器人记住用户的对话历史,并在需要时自动压缩过长上下文,以节省 token 并保持响应质量。
2. 环境要求
| 依赖 | 版本 |
|---|---|
| Java | 17+ |
| Spring AI | 2.0.0+ |
| Spring Boot | 4.0.7+ |
注意:以上版本号基于当前文档设定,实际使用时请以官方最新稳定版为准。
3. 最快速上手(Spring Boot Starter)
如果你使用 Spring Boot,推荐使用 JDBC Starter,它几乎零配置。
3.1 添加依赖
xml
<dependency>
<groupId>org.springaicommunity</groupId>
<artifactId>spring-ai-starter-session-jdbc</artifactId>
<version>${spring-ai-session.version}</version>
</dependency>
3.2 自动配置原理
当 classpath 中存在嵌入式数据库(如 H2)时,Starter 会自动完成以下工作:
- 创建
JdbcSessionRepositoryBean(基于自动配置的DataSource) - 创建
DefaultSessionServiceBean 作为服务门面 - 自动检测 SQL 方言(根据 DataSource URL)
- 自动初始化数据库表结构(仅嵌入式数据库,如 H2)
如果使用 PostgreSQL 或 MySQL 等持久化数据库,需要显式开启初始化(见第 5 节)。
3.3 与 ChatClient 集成
你只需将 SessionMemoryAdvisor 注入到 ChatClient 中,并在每次调用时传入 sessionId。
java
@Bean
ChatClient chatClient(ChatModel chatModel, SessionService sessionService) {
SessionMemoryAdvisor advisor = SessionMemoryAdvisor.builder(sessionService).build();
return ChatClient.builder(chatModel)
.defaultAdvisors(advisor)
.build();
}
调用时:
java
String answer = chatClient.prompt()
.user("什么是 Spring AI?")
.advisors(a -> a.param(SessionMemoryAdvisor.SESSION_ID_CONTEXT_KEY, "session-abc"))
.call()
.content();
如果 session-abc 不存在,SessionMemoryAdvisor 会自动创建一个新会话。
4. 推荐添加 BOM(Bill of Materials)
为了统一管理所有模块版本,建议导入 BOM:
xml
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springaicommunity</groupId>
<artifactId>spring-ai-session-bom</artifactId>
<version>${spring-ai-session.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
之后,添加任何 spring-ai-session 模块时无需再指定版本号。
5. 三种配置方式对比
| 方式 | 适用场景 | 特点 |
|---|---|---|
| Spring Boot Starter(推荐) | 生产环境 | 自动配置,开箱即用,支持持久化数据库 |
| JDBC 手动配置 | 需要精细控制 | 手动创建 JdbcSessionRepository 和 SessionService |
| In-Memory(仅测试) | 单元测试、原型 | 数据不持久化,重启即失 |
5.1 Starter 进阶配置
5.1.1 初始化持久化数据库的表结构
对于 PostgreSQL 或 MySQL,需要设置:
yaml
spring:
ai:
session:
repository:
jdbc:
initialize-schema: always
always:每次启动都执行建表脚本(若表已存在不会重复创建)never:手动建表(默认值,适用于嵌入式数据库)
5.1.2 会话默认有效期(TTL)
默认会话有效期为 60 天,可通过以下配置修改(支持 ISO-8601 或 Spring Duration 格式):
yaml
spring:
ai:
session:
time-to-live: 30d # 例如 30 天
5.1.3 覆盖自动配置
如果你需要自定义 SessionService 实现,只需声明自己的 @Bean SessionService,Starter 的自动配置会失效,你的 Bean 将接管。
6. 深入理解:SessionMemoryAdvisor 的工作原理
SessionMemoryAdvisor 是 Spring AI 的一个 Advisor(通知器) ,它会在每次 ChatClient 请求前后自动执行以下操作:
#mermaid-svg-ITiB4REdAMghnwzl{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ITiB4REdAMghnwzl .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ITiB4REdAMghnwzl .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ITiB4REdAMghnwzl .error-icon{fill:#552222;}#mermaid-svg-ITiB4REdAMghnwzl .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ITiB4REdAMghnwzl .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ITiB4REdAMghnwzl .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ITiB4REdAMghnwzl .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ITiB4REdAMghnwzl .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ITiB4REdAMghnwzl .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ITiB4REdAMghnwzl .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ITiB4REdAMghnwzl .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ITiB4REdAMghnwzl .marker.cross{stroke:#333333;}#mermaid-svg-ITiB4REdAMghnwzl svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ITiB4REdAMghnwzl p{margin:0;}#mermaid-svg-ITiB4REdAMghnwzl .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ITiB4REdAMghnwzl .cluster-label text{fill:#333;}#mermaid-svg-ITiB4REdAMghnwzl .cluster-label span{color:#333;}#mermaid-svg-ITiB4REdAMghnwzl .cluster-label span p{background-color:transparent;}#mermaid-svg-ITiB4REdAMghnwzl .label text,#mermaid-svg-ITiB4REdAMghnwzl span{fill:#333;color:#333;}#mermaid-svg-ITiB4REdAMghnwzl .node rect,#mermaid-svg-ITiB4REdAMghnwzl .node circle,#mermaid-svg-ITiB4REdAMghnwzl .node ellipse,#mermaid-svg-ITiB4REdAMghnwzl .node polygon,#mermaid-svg-ITiB4REdAMghnwzl .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ITiB4REdAMghnwzl .rough-node .label text,#mermaid-svg-ITiB4REdAMghnwzl .node .label text,#mermaid-svg-ITiB4REdAMghnwzl .image-shape .label,#mermaid-svg-ITiB4REdAMghnwzl .icon-shape .label{text-anchor:middle;}#mermaid-svg-ITiB4REdAMghnwzl .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ITiB4REdAMghnwzl .rough-node .label,#mermaid-svg-ITiB4REdAMghnwzl .node .label,#mermaid-svg-ITiB4REdAMghnwzl .image-shape .label,#mermaid-svg-ITiB4REdAMghnwzl .icon-shape .label{text-align:center;}#mermaid-svg-ITiB4REdAMghnwzl .node.clickable{cursor:pointer;}#mermaid-svg-ITiB4REdAMghnwzl .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ITiB4REdAMghnwzl .arrowheadPath{fill:#333333;}#mermaid-svg-ITiB4REdAMghnwzl .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ITiB4REdAMghnwzl .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ITiB4REdAMghnwzl .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ITiB4REdAMghnwzl .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ITiB4REdAMghnwzl .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ITiB4REdAMghnwzl .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ITiB4REdAMghnwzl .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ITiB4REdAMghnwzl .cluster text{fill:#333;}#mermaid-svg-ITiB4REdAMghnwzl .cluster span{color:#333;}#mermaid-svg-ITiB4REdAMghnwzl div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-ITiB4REdAMghnwzl .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ITiB4REdAMghnwzl rect.text{fill:none;stroke-width:0;}#mermaid-svg-ITiB4REdAMghnwzl .icon-shape,#mermaid-svg-ITiB4REdAMghnwzl .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ITiB4REdAMghnwzl .icon-shape p,#mermaid-svg-ITiB4REdAMghnwzl .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ITiB4REdAMghnwzl .icon-shape .label rect,#mermaid-svg-ITiB4REdAMghnwzl .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ITiB4REdAMghnwzl .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ITiB4REdAMghnwzl .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ITiB4REdAMghnwzl :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 存在
不存在
达到阈值
未达到
用户发起请求
Advisor 拦截请求
根据 sessionId 查询会话
加载历史消息
创建新会话
将历史消息注入到用户提示中
调用 ChatModel 生成回复
将用户问题和助手回复追加到会话
检查压缩触发条件
执行压缩策略(如滑动窗口)
保存会话到数据库
返回最终回复给用户
6.1 压缩策略(Compaction)
当对话轮次累积过多,为了不超出模型上下文窗口,需要压缩历史 。
常见策略是 滑动窗口(Sliding Window) ,保留最近 N 条消息,丢弃更早的。
你也可以自定义触发条件,例如按轮次或 token 数量。
示例:当累积 20 轮 对话时触发压缩,保留最近 10 条 事件。
java
@Bean
SessionMemoryAdvisor sessionMemoryAdvisor(SessionService sessionService) {
return SessionMemoryAdvisor.builder(sessionService)
.defaultUserId("alice") // 默认用户 ID(可选)
.compactionTrigger(new TurnCountTrigger(20))
.compactionStrategy(SlidingWindowCompactionStrategy.builder()
.maxEvents(10)
.build())
.build();
}
6.2 核心组件关系图
#mermaid-svg-ZJVyVnQUoK4cMsry{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ZJVyVnQUoK4cMsry .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ZJVyVnQUoK4cMsry .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ZJVyVnQUoK4cMsry .error-icon{fill:#552222;}#mermaid-svg-ZJVyVnQUoK4cMsry .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ZJVyVnQUoK4cMsry .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ZJVyVnQUoK4cMsry .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ZJVyVnQUoK4cMsry .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ZJVyVnQUoK4cMsry .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ZJVyVnQUoK4cMsry .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ZJVyVnQUoK4cMsry .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ZJVyVnQUoK4cMsry .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ZJVyVnQUoK4cMsry .marker.cross{stroke:#333333;}#mermaid-svg-ZJVyVnQUoK4cMsry svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ZJVyVnQUoK4cMsry p{margin:0;}#mermaid-svg-ZJVyVnQUoK4cMsry g.classGroup text{fill:#9370DB;stroke:none;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:10px;}#mermaid-svg-ZJVyVnQUoK4cMsry g.classGroup text .title{font-weight:bolder;}#mermaid-svg-ZJVyVnQUoK4cMsry .cluster-label text{fill:#333;}#mermaid-svg-ZJVyVnQUoK4cMsry .cluster-label span{color:#333;}#mermaid-svg-ZJVyVnQUoK4cMsry .cluster-label span p{background-color:transparent;}#mermaid-svg-ZJVyVnQUoK4cMsry .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ZJVyVnQUoK4cMsry .cluster text{fill:#333;}#mermaid-svg-ZJVyVnQUoK4cMsry .cluster span{color:#333;}#mermaid-svg-ZJVyVnQUoK4cMsry .nodeLabel,#mermaid-svg-ZJVyVnQUoK4cMsry .edgeLabel{color:#131300;}#mermaid-svg-ZJVyVnQUoK4cMsry .edgeLabel .label rect{fill:#ECECFF;}#mermaid-svg-ZJVyVnQUoK4cMsry .label text{fill:#131300;}#mermaid-svg-ZJVyVnQUoK4cMsry .labelBkg{background:#ECECFF;}#mermaid-svg-ZJVyVnQUoK4cMsry .edgeLabel .label span{background:#ECECFF;}#mermaid-svg-ZJVyVnQUoK4cMsry .classTitle{font-weight:bolder;}#mermaid-svg-ZJVyVnQUoK4cMsry .node rect,#mermaid-svg-ZJVyVnQUoK4cMsry .node circle,#mermaid-svg-ZJVyVnQUoK4cMsry .node ellipse,#mermaid-svg-ZJVyVnQUoK4cMsry .node polygon,#mermaid-svg-ZJVyVnQUoK4cMsry .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ZJVyVnQUoK4cMsry .divider{stroke:#9370DB;stroke-width:1;}#mermaid-svg-ZJVyVnQUoK4cMsry g.clickable{cursor:pointer;}#mermaid-svg-ZJVyVnQUoK4cMsry g.classGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-ZJVyVnQUoK4cMsry g.classGroup line{stroke:#9370DB;stroke-width:1;}#mermaid-svg-ZJVyVnQUoK4cMsry .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-ZJVyVnQUoK4cMsry .classLabel .label{fill:#9370DB;font-size:10px;}#mermaid-svg-ZJVyVnQUoK4cMsry .relation{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-ZJVyVnQUoK4cMsry .dashed-line{stroke-dasharray:3;}#mermaid-svg-ZJVyVnQUoK4cMsry .dotted-line{stroke-dasharray:1 2;}#mermaid-svg-ZJVyVnQUoK4cMsry #compositionStart,#mermaid-svg-ZJVyVnQUoK4cMsry .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-ZJVyVnQUoK4cMsry #compositionEnd,#mermaid-svg-ZJVyVnQUoK4cMsry .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-ZJVyVnQUoK4cMsry #dependencyStart,#mermaid-svg-ZJVyVnQUoK4cMsry .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-ZJVyVnQUoK4cMsry #dependencyStart,#mermaid-svg-ZJVyVnQUoK4cMsry .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-ZJVyVnQUoK4cMsry #extensionStart,#mermaid-svg-ZJVyVnQUoK4cMsry .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-ZJVyVnQUoK4cMsry #extensionEnd,#mermaid-svg-ZJVyVnQUoK4cMsry .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-ZJVyVnQUoK4cMsry #aggregationStart,#mermaid-svg-ZJVyVnQUoK4cMsry .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-ZJVyVnQUoK4cMsry #aggregationEnd,#mermaid-svg-ZJVyVnQUoK4cMsry .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-ZJVyVnQUoK4cMsry #lollipopStart,#mermaid-svg-ZJVyVnQUoK4cMsry .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-ZJVyVnQUoK4cMsry #lollipopEnd,#mermaid-svg-ZJVyVnQUoK4cMsry .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-ZJVyVnQUoK4cMsry .edgeTerminals{font-size:11px;line-height:initial;}#mermaid-svg-ZJVyVnQUoK4cMsry .classTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ZJVyVnQUoK4cMsry .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ZJVyVnQUoK4cMsry .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ZJVyVnQUoK4cMsry :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} uses as advisor
<<interface>>
SessionService
+createSession(request)
+getSession(id)
+updateSession(session)
+deleteSession(id)
DefaultSessionService
-SessionRepository repository
+...
<<interface>>
SessionRepository
+save(session)
+findById(id)
+delete(id)
JdbcSessionRepository
-DataSource dataSource
-SqlDialect dialect
+...
SessionMemoryAdvisor
-SessionService sessionService
-CompactionTrigger trigger
-CompactionStrategy strategy
+aroundCall(context)
ChatClient
-List<Advisor> advisors
+prompt()
7. 代码仓库及版本获取
Spring AI Session 目前发布在 Spring 快照仓库:
xml
<repositories>
<repository>
<id>spring-snapshots</id>
<url>https://repo.spring.io/snapshot</url>
<snapshots><enabled>true</enabled></snapshots>
<releases><enabled>false</enabled></releases>
</repository>
</repositories>
正式版本发布后,可切换至 Maven Central。
8. 常见问题与注意事项
- 数据库表未自动创建 :检查
initialize-schema配置,或手动执行schema-{database}.sql。 - 会话 ID 传递错误 :确保在
advisors参数中正确设置SESSION_ID_CONTEXT_KEY。 - 压缩不生效 :确认
compactionTrigger和compactionStrategy已正确配置并生效。 - 多用户场景 :可通过
defaultUserId或动态传入用户 ID 区分不同用户会话。
9. 总结
通过 Spring AI Session,你可以轻松为 AI 应用添加有状态对话记忆 ,并借助自动压缩 能力平衡上下文长度与性能。推荐使用 Spring Boot Starter 快速集成,配合 SessionMemoryAdvisor 无侵入地增强 ChatClient。如需深度定制,也可手动装配各个组件。
现在,你的 AI 应用终于可以"记住"用户说过的话了!🚀