Spring AI 初步集成(2)-添加记忆

添加记忆

大语言模型(LLMs)是无状态的,这意味着它们不会保留关于先前交互的信息。当你希望在多次交互中保持上下文或状态时,这可能会成为一个限制。为了解决这个问题,Spring AI提供了保存上下文功能,定义为Chat Memory,允许你在与LLM的多次交互中存储和检索信息。

但是在此之前,我们需要理解,Chat Memory并不等于Chat History。

  • Chat Memory:大型语言模型在整个对话过程中保留并用于保持语境感知的信息。
  • Chat History:整个对话历史,包括用户与模型之间交换的所有消息,通常用于审计或回顾。

添加Chat Memory

Spring AI会自动配置一个ChatMemory Bean,我们可以在应用程序中直接使用。默认情况下,它使用内存中的存储库(InMemoryChatMemoryRepository)来存储消息,并使用MessageWindowChatMemory实现来管理对话历史。

修改 src/main/java/com/example/canaan/config/ai/ChatConfiguration.java 文件,在构建ChatClient的实例时添加Chat Memory配置:

java 复制代码
package com.chestnut.canaan.config.ai;

import jakarta.annotation.Resource;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ChatConfiguration {

    @Resource
    private ChatMemory chatMemory;
    
    @Bean
    public ChatClient chatClient(ChatClient.Builder builder) {
        return builder
                .defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build())
                .defaultSystem("you are a friendly assistant!").build();
    }
}

在这个配置中,我们使用了 MessageChatMemoryAdvisor,它会将ChatMemory与ChatClient关联起来。这样,每次调用ChatClient时,都会自动使用Chat Memory。因为我们没有额外的配置,所以默认使用InMemoryChatMemoryRepository 来存储消息。InMemoryChatMemoryRepository会将消息存在ConcurrentHashMap中。每次使用chatClient的相关方法时,Chat Memory会自动更新。

测试效果:

目前所有的请求都共用同一份Chat Memory,这意味着不同的用户之间的Chat Memory是共享的。如果我们希望每个对话有独立的Chat Memory,我们可以在每次对话时传入一个conversationId来区分不同的对话。

修改 src/main/java/com/example/canaan/service/AiService.javasrc/main/java/com/example/canaan/service/impl/AiServiceImpl.java 文件,添加一个 conversationId 参数:

java 复制代码
package com.chestnut.canaan.service;

import reactor.core.publisher.Flux;

public interface AiService {

    Flux<String> chat(String input,String conversationId);

}
java 复制代码
package com.chestnut.canaan.service.impl;

import com.chestnut.canaan.service.AiService;
import jakarta.annotation.Resource;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;

@Service
public class AiServiceImpl implements AiService {

    @Resource
    private ChatClient chatClient;

    @Override
    public Flux<String> chat(String input,String conversationId) {
        return chatClient.prompt()
                .advisors(it -> it.param(ChatMemory.CONVERSATION_ID, conversationId))
                .user(input).stream().content();
    }
}

现在,为了快速检测结果,我们修改一下控制器 src/main/java/com/example/canaan/controller/AiController.java,使其支持传入 conversationId 参数:

java 复制代码
package com.chestnut.canaan.controller;

import com.chestnut.canaan.service.AiService;
import jakarta.annotation.Resource;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;

@RestController
@RequestMapping("/ai")
public class AiController {

    @Resource
    private AiService aiService;

    @GetMapping("/chat")
    public Flux<String> chat(String input,String conversationId) {
        return aiService.chat(input,conversationId);
    }

}

现在,我们可以通过访问 /ai/chat?input=问题&conversationId=对话ID 来与模型进行交互。每个不同的 conversationId 将会有独立的Chat Memory。

记忆持久化

目前,Chat Memory是存储在内存中的,这意味着当应用程序重启时,所有的Chat Memory都会丢失。为了持久化Chat Memory,我们可以使用数据库或其他持久化存储。

Spring AI提供了ChatMemoryRepository抽象来存储Chat Memory,并提供了内置存储库及其使用方法。如有需要,我们也可以实现自己的存储库。以下是几种内置存储库:

  • InMemoryChatMemoryRepository:默认的内存存储库。
  • JdbcChatMemoryRepository:使用JDBC将消息存储在关系型数据库中。支持多种数据库,开箱即用,适用于需要持久存储Chat Memory的应用程序。
  • CassandraChatMemoryRepository: 使用Apache Cassandra来存储消息。适用于需要持久存储Chat Memory的应用程序,尤其适用于对可用性、持久性、扩展性有要求,以及需要利用生存时间(TTL)功能的场景。
  • Neo4j ChatMemoryRepository:使用Neo4j将Chat Memory作为节点和关系存储在属性图数据库中。适用于希望利用Neo4j的图功能来持久化Chat Memory的应用程序。

使用JDBC存储Chat Memory

我们将使用JDBC存储Chat Memory。数据库可以是MySQL、PostgreSQL等关系型数据库。以下是使用MySQL的步骤:

首先,我们需要添加依赖项。在 build.gradle 文件的dependencies中添加以下依赖:

groovy 复制代码
dependencies {
    // JDBC memory repository 核心依赖
    implementation 'org.springframework.ai:spring-ai-starter-model-chat-memory-repository-jdbc'
    // MySQL JDBC驱动
    runtimeOnly 'com.mysql:mysql-connector-j'
}

其次,我们需要新建一个数据库,名称随意,我取名assists:

sql 复制代码
CREATE DATABASE `assists` CHARACTER SET 'utf8mb4' COLLATE 'utf8mb4_general_ci';

配置数据库连接。在 src/main/resources/application.yml 文件中添加以下配置:

yaml 复制代码
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://${PERSONAL_SERVER_IP}:3306/assists
    username: ${DATABASE_USERNAME}
    password: ${DATABASE_PASSWORD}
  ai:
    chat:
      memory:
        repository:
          jdbc:
            platform: mariadb
            initialize-schema: always

其中spring.datasource.*配置的是数据库相关信息

spring.ai.chat.memory.repository.jdbc.platform使用mariadb 而不是mysql ,是因为默认的数据表初始化文件放在依赖包classpath:org/springframework/ai/chat/memory/repository/jdbc/schema-@@platform@@.sql位置,但是我们可以看到,在此目录下并不存在数据表初始化文件schema-mysql.sql

因为mariadb兼容MySQL,所以我们可以使用mariadb的初始化脚本。或者我们也可以通过spring.ai.chat.memory.repository.jdbc.schema手动指定脚本位置。

默认情况下,初始化存储Chat Memory表SPRING_AI_CHAT_MEMORY仅对嵌入式数据库(H2、HSQL、Derby等)生效,所以我们需要使用 spring.ai.chat.memory.repository.jdbc.initialize-schema来让初始化脚本对MySQL生效。

因为Spring AI 会自动配置一个ChatMemory Bean,所以我们不需要手动配置ChatMemory。Spring AI会根据spring.ai.chat.memory.repository.jdbc的配置来创建一个JdbcChatMemoryRepository实例,并以此为基础创建一个ChatMemory实例。并且由于有了jdbc配置,InMemoryChatMemoryRepository将不会被创建。

最后,启动服务,在数据库assists下面应该已经出现了数据表SPRING_AI_CHAT_MEMORY

重新调用接口/ai/chat?input=问题&conversationId=对话ID,你会发现Chat Memory已经被存储在数据库中了。每次调用接口时,Chat Memory都会被更新。

总结

在本章中,我们了解了如何为Spring AI应用程序添加Chat Memory。我们了解了Chat Memory的概念,并通过配置ChatClient来启用Chat Memory功能。我们还知道了如何使用JDBC存储Chat Memory,以便在应用程序重启后仍然对话仍然可用。通过这些步骤,我们可以在多次交互中保持上下文或状态,从而使应用程序更加智能和人性化。

相关推荐
云烟成雨TD1 天前
Spring AI Alibaba 1.x 系列【6】ReactAgent 同步执行 & 流式执行
java·人工智能·spring
Java成神之路-1 天前
SpringMVC 响应实战指南:页面、文本、JSON 返回全流程(Spring系列13)
java·spring·json
砍材农夫1 天前
spring-ai 第六模型介绍-聊天模型
java·人工智能·spring
云烟成雨TD1 天前
Spring AI Alibaba 1.x 系列【5】ReactAgent 构建器深度源码解析
java·人工智能·spring
Flittly1 天前
【SpringAIAlibaba新手村系列】(15)MCP Client 调用本地服务
java·笔记·spring·ai·springboot
Flittly1 天前
【SpringAIAlibaba新手村系列】(14)MCP 本地服务与工具集成
java·spring boot·笔记·spring·ai
mfxcyh1 天前
基于xml、注解、JavaConfig实现spring的ioc
xml·java·spring
Flittly1 天前
【SpringAIAlibaba新手村系列】(13)Tool Calling 函数工具调用技术
java·spring boot·spring·ai
xdscode1 天前
Spring 依赖注入方式全景解析
java·后端·spring