【Spring AI Session】会话管理概览

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 会自动完成以下工作:

  • 创建 JdbcSessionRepository Bean(基于自动配置的 DataSource
  • 创建 DefaultSessionService Bean 作为服务门面
  • 自动检测 SQL 方言(根据 DataSource URL)
  • 自动初始化数据库表结构(仅嵌入式数据库,如 H2)

如果使用 PostgreSQLMySQL 等持久化数据库,需要显式开启初始化(见第 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 手动配置 需要精细控制 手动创建 JdbcSessionRepositorySessionService
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. 常见问题与注意事项

  1. 数据库表未自动创建 :检查 initialize-schema 配置,或手动执行 schema-{database}.sql
  2. 会话 ID 传递错误 :确保在 advisors 参数中正确设置 SESSION_ID_CONTEXT_KEY
  3. 压缩不生效 :确认 compactionTriggercompactionStrategy 已正确配置并生效。
  4. 多用户场景 :可通过 defaultUserId 或动态传入用户 ID 区分不同用户会话。

9. 总结

通过 Spring AI Session,你可以轻松为 AI 应用添加有状态对话记忆 ,并借助自动压缩 能力平衡上下文长度与性能。推荐使用 Spring Boot Starter 快速集成,配合 SessionMemoryAdvisor 无侵入地增强 ChatClient。如需深度定制,也可手动装配各个组件。

现在,你的 AI 应用终于可以"记住"用户说过的话了!🚀