首页 > 基础资料 博客日记

Spring with AI (6): 记忆保持——会话与长期记忆

2026-04-01 22:30:02基础资料围观1

文章Spring with AI (6): 记忆保持——会话与长期记忆分享给大家,欢迎收藏极客资料网,专注分享技术知识

本文代码:
https://github.com/JunTeamCom/ai-demo/tree/release-6.0 (JDBC-MySQL版本的会话持久化)

https://github.com/JunTeamCom/ai-demo/tree/release-6.1 (VectorStore-Qrant版本的会话持久化)

本章讲解聊天内容的存储:短期记忆(即会话级别)、存储方式、长期记忆(用户级别),这是对前面两个Topic(OpenAI接入、RAG与向量数据库)的延伸。

1 注入聊天记忆类

参考上一章内容,也是通过Advisor类注入控制聊天内容存储:

  • MessageChatMemoryAdvisor
  • PromptChatMemoryAdvisor
  • VectorStoreChatMemoryAdvisor

前两者为短期记忆,不同之处是MessageChatMemoryAdvisor能够按角色存储会话(即用户和助手两种角色,不过部分大模型不支持),PromptChatMemoryAdvisor是把历史信息转换为大字符串、注入到提示词中。
VectorStoreChatMemoryAdvisor则是用向量数据库、记录历史消息(类似RAG)。

集成方式,是构建一个*MemoryAdvisor类,然后defaultAdvisors入参加入(方法入参为Advisor链)。

@Bean
ChatClient chatClient(ChatClient.Builder chatClientBuilder,
    ChatMemory chatMemory,
    VectorStore vectorStore) {
    // 设置顾问配置项
    return chatClientBuilder
        .defaultAdvisors(
            MessageChatMemoryAdvisor.builder(chatMemory)
                .build(),
            QuestionAnswerAdvisor.builder(vectorStore)
                .searchRequest(SearchRequest.builder().build())
                .build())
        .build();
}

为了防止对话内容溢出,需要控制上下文最大条数:

@Bean
ChatMemory chatMemory(ChatMemoryRepository chatMemoryRepository) {
    return MessageWindowChatMemory.builder()
            .chatMemoryRepository(chatMemoryRepository)
            .maxMessages(50) // 最大条数50,即:提问25条、回答25条
            .build();
}

2 引入会话ID

2.1 Controller层引入

现实中我们当然不能把所有会话、都统一记忆;其实还是要通过会话ID进行区分(类似Session):

    @PostMapping(path = "/ask", produces = "application/json")
    public ChatAnswer ask(
        @RequestHeader(name = "X_CONVERSATION_ID", defaultValue = "default") String conversationId,
        @RequestBody ChatQuestion chatQuestion) {
        return chatService.ask(chatQuestion, conversationId);
    }

2.2 Service层:引入会话ID前的简化

在修改Advisor、引入会话ID的同时,将上一章的Rules形式、改为Expression形式:

package com.junteam.ai.demo.service.impl;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;

import com.junteam.ai.demo.model.ChatAnswer;
import com.junteam.ai.demo.model.ChatQuestion;
import com.junteam.ai.demo.service.ChatService;

import static org.springframework.ai.chat.client.advisor.vectorstore.QuestionAnswerAdvisor.FILTER_EXPRESSION;
import static org.springframework.ai.chat.memory.ChatMemory.CONVERSATION_ID;

@Service
public class OpenAIChatServiceImpl implements ChatService {

    private final ChatClient chatClient;

    public OpenAIChatServiceImpl(ChatClient chatClient) {
        this.chatClient = chatClient;
    }

    @Value("classpath:/promptTemplates/questionPromptTemplate.st")
    Resource questionPromptTemplate;

    @Override
    public ChatAnswer ask(ChatQuestion chatQuestion, String conversationId) {
        var countryTitleMatch = String.format(
            "countryTitle == '%s'",
            chatQuestion.title());

        return chatClient
            .prompt()
            .system(systemSpec -> systemSpec
                .text(questionPromptTemplate)
                .param("countryTitle", chatQuestion.title()))
            .advisors(advisorSpec -> advisorSpec
                .param(FILTER_EXPRESSION, countryTitleMatch)
            .user(chatQuestion.question())
            .call()
            .entity(ChatAnswer.class);
    }
}

相应的,提示词模板修改:

你是一个有用的助手,负责回答有关{countryTitle}的历史地理风俗问题。

2.3 Service层引入会话ID

修改Service接口和实现类:

package com.junteam.ai.demo.service;

import com.junteam.ai.demo.model.ChatAnswer;
import com.junteam.ai.demo.model.ChatQuestion;

public interface ChatService {
    ChatAnswer ask(ChatQuestion question, String conversationId);
}
package com.junteam.ai.demo.service.impl;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;

import com.junteam.ai.demo.model.ChatAnswer;
import com.junteam.ai.demo.model.ChatQuestion;
import com.junteam.ai.demo.service.ChatService;

import static org.springframework.ai.chat.client.advisor.vectorstore.QuestionAnswerAdvisor.FILTER_EXPRESSION;
import static org.springframework.ai.chat.memory.ChatMemory.CONVERSATION_ID;

@Service
public class OpenAIChatServiceImpl implements ChatService {

    private final ChatClient chatClient;

    public OpenAIChatServiceImpl(ChatClient chatClient) {
        this.chatClient = chatClient;
    }

    @Value("classpath:/promptTemplates/questionPromptTemplate.st")
    Resource questionPromptTemplate;

    @Override
    public ChatAnswer ask(ChatQuestion chatQuestion, String conversationId) {
        var countryTitleMatch = String.format(
            "countryTitle == '%s'",
            chatQuestion.title());

        return chatClient
            .prompt()
            .system(systemSpec -> systemSpec
                .text(questionPromptTemplate)
                .param("countryTitle", chatQuestion.title()))
            .advisors(advisorSpec -> advisorSpec
                .param(FILTER_EXPRESSION, countryTitleMatch)
                .param(CONVERSATION_ID, conversationId)) // 引入会话ID
            .user(chatQuestion.question())
            .call()
            .entity(ChatAnswer.class);
    }
}

需要注意的是:
会话内容是存储在内存中的,服务一旦重启、那么所有历史内容都会清空。

如果需要持久化存储、并且多服务节点时能共享会话内容,需要使用向量数据库等手段存储。

3 持久化会话数据

从单体应用,迈向无状态的分布式服务集群,会话数据必须持久化(即将临时存储的内容——如内存数据等,通过文件或数据库等长期存储)。
这既可以通过ChatMemoryRepository实现,也可以直接使用VectorStoreChatMemoryAdvisor。

3.1 ChatMemoryRepository实现持久化

具体来说,ChatMemoryRepository主要有三种实现方式(都是使用数据库):

  • CassandraChatMemoryRepository
  • JdbcChatMemoryRepository
  • Neo4jChatMemoryRepository

分别对应Cassandra文档数据库、JDBC关系型数据库、Neo4j图数据库(相关概念不再赘述,搜索引擎即可搜到准确简明的概念说明)。

如果要使用,引入相关的Starter、注入ChatMemoryRepository、配置数据库链接地址即可。
下面以JDBC-MySQL为例、进行扩展。

3.1.1 依赖引入

1、引入Starter:
org.springframework.ai: spring-ai-starter-model-chat-memory-repository-jdbc
2、JDBC方式与其他相比,需要再引入驱动:

<dependency>
  <groupId>com.mysql</groupId>
  <artifactId>mysql-connector-j</artifactId>
</dependency>

3、找到相关初始SQL,再数据库执行SQL:
classpath:org/springframework/ai/chat/memory/repository/jdbc/schema-mysql.sql

image

CREATE TABLE IF NOT EXISTS SPRING_AI_CHAT_MEMORY (
    `conversation_id` VARCHAR(36) NOT NULL,
    `content` TEXT NOT NULL,
    `type` ENUM('USER', 'ASSISTANT', 'SYSTEM', 'TOOL') NOT NULL,
    `timestamp` TIMESTAMP NOT NULL,

    INDEX `SPRING_AI_CHAT_MEMORY_CONVERSATION_ID_TIMESTAMP_IDX` (`conversation_id`, `timestamp`)
);

3.1.2 数据库配置

配置文件添加数据库地址:

spring:
  # 数据库配置
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://${DB_SERVER}:3306/chat_db
    username: ${DB_USERNAME}
    password: ${DB_PASSWORD}

3.1.3 JAVA代码适配

JAVA代码适配、全部通过ChatMemoryRepository@Bean注入(AiConfig类);
JDBC也特殊一点,需要自定义数据库方言(比如MySQL/PostgreSQL/Oracle/SQLServer等)

@Bean
ChatMemoryRepository chatMemoryRepository(DataSource dataSource) {
    return JdbcChatMemoryRepository.builder()
        .dialect(new MysqlChatMemoryRepositoryDialect())
        .dataSource(dataSource)
        .build();
}

Service实现类不需要进行变更。

3.2 VectorStoreChatMemoryAdvisor实现持久化

spring-ai-advisors-vector-store依赖里已经包含相关Advisor,直接修改AiConfig即可;
这不需要额外的数据源,是简洁方便的方式:

package com.junteam.ai.demo.config;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.vectorstore.QuestionAnswerAdvisor;
import org.springframework.ai.chat.client.advisor.vectorstore.VectorStoreChatMemoryAdvisor;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AiConfig {
    @Bean
    ChatClient chatClient(ChatClient.Builder chatClientBuilder,
        VectorStore vectorStore) {
        // 设置顾问配置项
        return chatClientBuilder
            .defaultAdvisors(
                VectorStoreChatMemoryAdvisor
                    .builder(vectorStore)
                    .build(),
                QuestionAnswerAdvisor
                    .builder(vectorStore)
                    .searchRequest(SearchRequest.builder().build())
                    .build())
            .build();
    }
}
···

文章来源:https://www.cnblogs.com/gujun/p/19784314/spring-ai-06
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:jacktools123@163.com进行投诉反馈,一经查实,立即删除!

标签:

相关文章

本站推荐

标签云