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

相关推荐
仓鼠出海2 小时前
OpenClaw 换机迁移指南
ai·openclaw·龙虾
iPadiPhone2 小时前
Spring Boot 核心注解全维度解析与面试复盘
java·spring boot·后端·spring·面试
InfiSight智睿视界2 小时前
AI视觉巡店方案24小时经营 重构便利店夜间经营逻辑
ai·ai巡店
翔云1234562 小时前
OpenClaw与大模型通信过程:详细图文教程(2026最新)
ai·大模型·openclaw
杜子不疼.2 小时前
Spring Cloud 熔断降级详解:用 “保险丝“ 类比,Sentinel 实战教程
人工智能·spring·spring cloud·sentinel
ruiang2 小时前
开源模型应用落地-工具使用篇-Spring AI-高阶用法(九)
人工智能·spring·开源
l软件定制开发工作室2 小时前
Spring开发系列教程(32)——Spring Boot开发
java·spring boot·后端·spring
飞飞的AI实验室2 小时前
2026 年 AI 辅助编程工具全景对比:Copilot、Cursor、Claude Code 与 Codex 深度解析
人工智能·ai·copilot·ai编程
iPadiPhone2 小时前
性能优化的“快车道”:Spring @Async 注解深度原理与大厂实战
java·后端·spring·面试·性能优化