SpringAI自学成才系列(二)-结合SpringAI开发会话记忆功能

1.概述

上篇文章利用SpringAI结合deepSeek开发了一个智能助手,但是没有实现会话记忆功能,如果缺少会话记忆,其实对话性能大打折扣,本文将实现会话记忆功能,实现真正的智能。

2.会话记忆功能实现

2.1 SpringAI相关概念说明

2.1.1 SpringAI Advisor

Spring AI Advisor API 为拦截、修改和增强 Spring 应用中的 AI 交互提供了灵活强大的方式。通过该拦截器,可以配置更复杂、可复用且易维护的AI组件。本文配置了ChatClient的Advisor 如下:

java 复制代码
 return ChatClient.builder(ollamaChatModel)
                .defaultSystem("你是一个热心、可爱的智能助手,你的名字叫小钢炮,请以小钢炮的身份和语气回答问题")
                .defaultAdvisors(
                        //简单的日志记录器advisor,打印请求及大模型返回日志
                        new SimpleLoggerAdvisor(),
                        //对话记忆advisor,将每次对话内容进行存储
                        MessageChatMemoryAdvisor.builder(chatMemory).build())
                .build();

2.1.2 ChatMemory

ChatMemory 抽象层支持实现多种记忆类型以满足不同场景需求。消息的底层存储由 ChatMemoryRepository 处理,其唯一职责是存储和检索消息。 ChatMemory 实现类可自主决定消息保留策略 --- 例如保留最近 N 条消息、按时间周期保留或基于 Token 总量限制保留。本文使用的是InMemoryChatMemoryRepository ,基于 ConcurrentHashMap 实现内存存储。ChatMemory支持存储在数据库中,支持PostgreSQL、MySQL / MariaDB、SQL Server、HSQLDB等类型数据库,大家根据需要自行选择。

2.2 代码实现

2.2.1 引入pom文件

java 复制代码
   <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>4.0.2</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.eckey.lab</groupId>
    <artifactId>ollama</artifactId>
    <version>1.0.0</version>
    <name>ollama-chat</name>

    <properties>
        <java.version>17</java.version>
        <spring-ai.version>2.0.0-M2</spring-ai.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-model-ollama</artifactId>
        </dependency>
      </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.ai</groupId>
                <artifactId>spring-ai-bom</artifactId>
                <version>${spring-ai.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

2.2.2 配置OllamaChatModel

java 复制代码
    @Bean
    public ChatClient chatClient(OllamaChatModel ollamaChatModel, ChatMemory chatMemory) {
        return ChatClient.builder(ollamaChatModel)
                .defaultSystem("你是一个热心、可爱的智能助手,你的名字叫小钢炮,请以小钢炮的身份和语气回答问题")
                .defaultAdvisors(
                        new SimpleLoggerAdvisor(),
                        MessageChatMemoryAdvisor.builder(chatMemory).build())
                .build();
    }

    @Bean
    public ChatMemoryRepository chatMemoryRepository() {
        return new InMemoryChatMemoryRepository();
    }

    @Bean
    public ChatMemory chatMemory(ChatMemoryRepository chatMemoryRepository) {
        return MessageWindowChatMemory.builder().chatMemoryRepository(chatMemoryRepository)
                .maxMessages(100).build();
    }

2.2.3 配置文件编写

java 复制代码
spring.application.name=ollama-chat
server.port=8080
//ollama服务器地址
spring.ai.ollama.base-url=http://127.0.0.1:11434
//模型版本号
spring.ai.ollama.chat.model=deepseek-r1:1.5b
//日志打印级别
logging.level.org.springframework.ai.chat.client.advisor=debug

2.2.4 编写controller

java 复制代码
@RestController()
@CrossOrigin("*")
@RequestMapping("/ai/chat")
public class OllamaController {

    @Resource
    private ChatClient chatClient;

    @Resource
    private ChatHistoryRepository chatHistoryRepository;

    @Resource
    private ChatMemory chatMemory;

    /**
     * 同步响应请求,获取到大模型所有返回结果后,一次性返回前端
     */
    @GetMapping("/call")
    public String generate(@RequestParam("message") String message) {
        return chatClient.prompt(message).call().content();
    }

    /**
     * 流式响应
     */
    @RequestMapping(value = "/stream", produces = "text/html;charset=utf-8")
    public Flux<String> generateStream(String prompt, String chatId) {
        chatHistoryRepository.save(ChatTypeEnums.CHAT.getType(), chatId);
        return chatClient.prompt(prompt)
                .advisors(a -> a.param(CONVERSATION_ID, chatId))
                .stream().content();
    }

    /**
     * 根据chatId本次对话所有信息,以固定对象MessageVO返回
     */
    @GetMapping(value = "/history/{type}/{chatId}")
    public List<MessageVO> getChatHistory(@PathVariable("chatId") String chatId) {
        List<Message> messages = chatMemory.get(chatId);
        return messages.stream().map(MessageVO::new).toList();
    }

}

实体MessageVO对象信息如下:

java 复制代码
import lombok.Data;
import org.springframework.ai.chat.messages.Message;

@Data
public class MessageVO {

    private String role;

    private String content;

    public MessageVO(Message message) {
        switch (message.getMessageType()) {
            case USER:
                this.role = "user";
                break;
            case ASSISTANT:
                this.role = "assistant";
                break;
            case SYSTEM:
                this.role = "system";
                break;
            case TOOL:
                this.role = "function";
                break;
            default:
                this.role = "unknown";
        }
        this.content = message.getText();
    }


}

2.2.5 定义ChatHistoryRepository接口并实现

java 复制代码
public interface ChatHistoryRepository {

    void save(String type, String chatId);

    List<String> getChatIds(String type);

    void delete(String type, String chatId);

}



@Slf4j
@Component
public class InMemoryChatHistoryRepository implements ChatHistoryRepository {

    private final Map<String, List<String>> chatHistory = new ConcurrentHashMap<>();

    @Override
    public void save(String type, String chatId) {
        List<String> chatIds = chatHistory.computeIfAbsent(type, k -> new ArrayList<>());
        if (chatIds.contains(chatId)) {
            return;
        }
        chatIds.add(chatId);
        log.info("chatIds :{}", JSON.toJSONString(chatIds));
        //chatHistory.put(type, chatIds);
        log.info("chatHistory :{}", JSON.toJSONString(chatHistory));
    }

    @Override
    public List<String> getChatIds(String type) {
        return chatHistory.getOrDefault(type, List.of());
    }

    @Override
    public void delete(String type, String chatId) {
        List<String> strings = chatHistory.get(type);
        if (strings != null && strings.contains(chatId)) {
            strings.remove(chatId);
        }
        log.info("chatHistory :{}", JSON.toJSONString(chatHistory));
    }
}

2.2.5 测试验证

通过调用API进行测试,得到结果如下:

结合前端界面呈现如下,及时刷新页面也能重新获取对话信息进行呈现:

3.小结

1.本文结合SpringAI实现了智能助手会话记忆功能,帮助对话功能更完善;

2.SpringAI功能也在不断完善,接下来将结合Rag实现对文档的向量化存储与搜索。

4.参考文献

https://springdoc.cn/spring-ai/api/chat-memory.html

相关推荐
二月夜17 分钟前
Spring循环依赖深度解析:从三级缓存原理到跨环境“灵异”现象
java·spring
ipython_harley41 分钟前
【AGI】OpenAI核心贡献者翁家翌:修Infra的人,正在定义GPT-5
人工智能·gpt·ai·agi
周周爱喝粥呀1 小时前
词元ID是如何转为嵌入向量? 位置嵌入的作用是什么?
人工智能·ai
tianbaolc3 小时前
Claude Code 源码剖析 模块一 · 第六节:autoDream 自动记忆整合
人工智能·ai·架构·claude code
九皇叔叔3 小时前
003-SpringSecurity-Demo 统一响应类
java·javascript·spring·springsecurity
冬奇Lab5 小时前
一天一个开源项目(第64篇):OpenCLI - 把任意网站、Electron 应用与本地工具变成统一 CLI
ai·开源
计算机学姐5 小时前
基于SpringBoot的兴趣家教平台系统
java·spring boot·后端·spring·信息可视化·tomcat·intellij-idea
總鑽風5 小时前
单点登录springcloud+mysql
后端·spring·spring cloud
ATMQuant6 小时前
量化指标解码18:SMC市场结构与流动性
ai·量化交易·交易系统·vnpy
却话巴山夜雨时i6 小时前
互联网大厂Java面试场景:从基础到微服务的循序渐进提问
java·数据库·spring·微服务·面试·消息队列·技术栈