夜雨聆风学习资料网

ARTICLE · 1154885

spring-ai2学习文档(第一天)

spring-ai2学习文档(第一天)

创建项目

使用springboot创建一个快速启动的项目,这里不做说明,自己创建就好了。
项目版本说明
Java 17
Spring AI 2.0.1
Spring Boot 4.1.1
Maven 3.6
申请一个大模型的api-key
我习惯使用DeepSeek大模型,所以我申请的是DeepSeek的api-key登录DeepSeek的开放平台,在官网申请所需要的key。
最基础的项目搭建
本项目的pom.xml文件内容
<parent>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-parent</artifactId>    <version>4.1.1</version>    <relativePath/></parent><dependency>      <groupId>org.springframework.ai</groupId>      <artifactId>spring-ai-starter-model-deepseek</artifactId></dependency><dependency>      <groupId>org.springframework.boot</groupId>      <artifactId>spring-boot-starter-web</artifactId></dependency><dependencyManagement>    <dependencies>      <dependency>        <groupId>org.springframework.ai</groupId>        <artifactId>spring-ai-bom</artifactId>        <version>2.0.1</version>        <type>pom</type>        <scope>import</scope>      </dependency>    </dependencies></dependencyManagement>
进行项目的初步配置
server:  port: 10000spring:  application:    name: spring-ai2-study  ai:    deepseek:      api-key: ${SA_STUDY_API}      base-url: https://api.deepseek.com/      chat:        model: deepseek-v4-flash
其中的${SA_STUDY_API}即为申请的api-key,我是将这个key放入了环境变量中,那么官方也建议我们把key放入环境变量中,那么放入环境变量的步骤请看各系统中存放环境变量的步骤。
创建一个controller文件测试项目是否可用
TestController.java
private final ChatClient chatClient;public TestController(DeepSeekChatModel chatModel) {        this.chatClient = ChatClient.builder(chatModel)                .defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build())                .build();}/*** 简单输出为字符串*/@GetMapping("/chat")public String chat(@RequestParam(name = "question", defaultValue = "你是谁?") String question) {        return chatClient.prompt().user(question).call().content();}/*** 流式输出*/@GetMapping("/streamChat")public Flux<String> streamChat(@RequestParam(name = "question", defaultValue = "你是谁?") String question) {        return chatClient.prompt().user(question).stream().content();}
测试请求示例
GET http://127.0.0.1:8080/chat?question=你好,我叫李四 GET http://127.0.0.1:8080/streamChat?question=我叫什么名字?
执行后看浏览器中是否输出对应的回答。
思维模式
DeepSeek 模型支持在思维模式和非思维模式之间切换。思维模式可以通过 Thinking 选项进行控制,该选项接受 Thinking.ENABLED(思维模式)或 Thinking.DISABLED(非思维模式)。当禁用思维时,模型不会生成任何推理内容。
/*** 非思维模式* @param question 问题* @return Flux*/@GetMapping(value = "/streamChat1", produces = "text/plain;charset=UTF-8")public Flux<String> streamChat1(@RequestParam(name = "question", defaultValue = "你是谁?") String question) {        // 关闭思维模式        DeepSeekChatOptions chatOptions = DeepSeekChatOptions.builder()                .thinking(DeepSeekApi.ChatCompletionRequest.Thinking.DISABLED)                .build();        Prompt prompt = new Prompt(question, chatOptions);        return chatClient.prompt(prompt).stream().content();}
还可以通过构建器便捷方法 enableThinking() 和 disableThinking() 切换思维模式,或者通过 spring.ai.deepseek.chat.thinking.type 属性进行全局配置。
聊天记忆
大语言模型(LLM)是无状态的,这意味着它们不会保留先前交互的信息。当您想在多次交互中维护上下文或状态时,这可能会成为一种限制。为了解决这个问题,Spring AI 提供了聊天记忆(chat memory)功能,允许您在与 LLM 的多次交互中存储和检索信息。
ChatMemory 抽象允许您实现各种类型的记忆以支持不同的用例。消息的底层存储由 ChatMemoryRepository 处理,其唯一职责是存储和检索消息。由 ChatMemory 的实现来决定保留哪些消息以及何时删除它们。策略的示例可以包括保留最后 N 条消息、在特定时间段内保留消息,或者保留消息直至达到特定的 Token 限制。
在选择记忆类型之前,了解聊天记忆(chat memory)和聊天历史(chat history)之间的区别至关重要。
聊天记忆 (Chat Memory)。大语言模型保留并在整个对话中用于维持上下文感知的信息。
聊天历史 (Chat History)。完整的对话历史记录,包括用户与模型之间交换的所有消息。
ChatMemory 抽象旨在管理聊天记忆。它允许您存储和检索与当前对话上下文相关的消息。然而,它并不适合用于存储聊天历史。如果您需要保留所有已交换消息的完整记录,您应该考虑使用其他方法,例如依赖 Spring Data 来高效存储和检索完整的聊天历史记录。
使用会话ID
每个聊天记忆操作都以会话 ID(conversation ID)为键。ChatMemory 和 ChatMemoryRepository 抽象使用此标识符来决定读取、追加或删除哪个会话,并且必须始终显式提供该标识符。
会话 ID 是界定会话范围的唯一要素。系统会针对精确提供的会话 ID 来存储和检索消息,因此选择如何分配这些 ID 决定了交互是被分组在一起还是彼此独立。
在多用户应用程序中,使每个用户的会话 ID(如果一个用户可以有多个会话,则每个会话的 ID)唯一,以便每个用户的消息都保留在他们自己的会话中。常用的方法是在服务器上根据当前用户或会话派生该 ID,而不是使用固定值或共享值。
// Build the conversation ID from the current user/session so each// user's messages are kept in their own conversation.String conversationId = currentUser.getId() + ":" + httpSession.getId();chatClient.prompt()    .user(userInput)    .advisors(a -> a.param(ChatMemory.CONVERSATION_ID, conversationId))    .call()    .content();
当应用程序规模增长时,以下几种实践有助于保持会话条理清晰:
  1. 为每个用户分配一个独立的会话 ID,并在单个用户可以拥有多个会话时,为每个会话分配独立的 ID。
  2. 在服务器上从用户或会话中派生会话 ID,而不是在不同用户之间重复使用固定值。
  3. 在列出或删除会话时,针对属于当前用户的会话 ID 进行操作,而不是针对所有会话 ID。
记忆类型
在我的项目中只介绍了三种记忆类型:
  1. 内存存储库
  2. Mysql数据库
  3. Redis内存存储库
增加一个Bean的配置:
@Beanpublic ChatMemory chatMemory() {    return MessageWindowChatMemory.builder()        .maxMessages(10) // 保留最近的10条        .build();}
在controller中增加chatMemory的配置:
publicTestController(DeepSeekChatModel chatModel, ChatMemory chatMemory) {        this.chatClient = ChatClient.builder(chatModel)                .defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build())                .build();}
增加一个接口用来测试:
/*** 放在jvm内存中的聊天记忆 MessageWindowChatMemory* @param question 问题* @return Flux*/@GetMapping(value = "/streamChat2", produces = "text/plain;charset=UTF-8")public Flux<String> streamChat2(@RequestParam(name = "question", defaultValue = "你是谁?") String question) {        return chatClient.prompt()                .user(question)                .advisors(a -> a.param(ChatMemory.CONVERSATION_ID, "xupf"))                .stream()                .content();}
Mysql数据库
1.增加依赖:
<!-- Spring AI 2.0.1 JDBC ChatMemory Repository --><dependency>    <groupId>org.springframework.ai</groupId>    <artifactId>spring-ai-starter-model-chat-memory-repository-jdbc</artifactId>    <version>2.0.1</version></dependency><!-- SpringBoot JDBC --><dependency>    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-jdbc</artifactId></dependency><!-- MySQL驱动 --><dependency>    <groupId>com.mysql</groupId>    <artifactId>mysql-connector-j</artifactId>    <scope>runtime</scope></dependency><!-- OpenAI模型starter,按需替换成通义千问/文心一言等 --><dependency>    <groupId>org.springframework.ai</groupId>    <artifactId>spring-ai-starter-model-openai</artifactId>    <version>2.0.1</version></dependency>
2.application.yml增加 MySQL 配置
spring:  datasource:    url: jdbc:mysql://127.0.0.1:3306/spring_ai_chat?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true    username: root    password: 你的mysql密码    driver-class-name: com.mysql.cj.jdbc.Driver  ai:    chat:      memory:        repository:          jdbc:            # 开发环境:always 自动建表;生产环境改成 never,用flyway/liquibase管理DDL            initialize-schema: always
3.配置类 AiChatMemoryConfig.java
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;import org.springframework.ai.chat.memory.JdbcChatMemoryRepository;import org.springframework.ai.chat.memory.MessageWindowChatMemory;import org.springframework.ai.chat.model.ChatModel;import org.springframework.ai.chat.client.ChatClient;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import javax.sql.DataSource;@Configurationpublic class AiChatMemoryConfig {    @Bean    public JdbcChatMemoryRepository jdbcChatMemoryRepository(DataSource dataSource) {        return JdbcChatMemoryRepository.builder()                .dataSource(dataSource)                // MySQL方言自动识别,不需要手动指定                .build();    }    /**     * 消息窗口记忆:最多保留N条消息,超出会淘汰旧消息     */    @Bean    public MessageWindowChatMemory messageWindowChatMemory(JdbcChatMemoryRepository jdbcChatMemoryRepository) {        return MessageWindowChatMemory.builder()                .chatMemoryRepository(jdbcChatMemoryRepository)                .maxMessages(10)                .build();    }    /**     * 对话记忆Advisor,自动读写MySQL会话历史     */    @Bean    public MessageChatMemoryAdvisor messageChatMemoryAdvisor(MessageWindowChatMemory chatMemory) {        return MessageChatMemoryAdvisor.builder(chatMemory).build();    }    /**     * ChatClient 注入记忆Advisor     */    @Bean    public ChatClient chatClient(ChatModel chatModel, MessageChatMemoryAdvisor memoryAdvisor) {        return ChatClient.builder(chatModel)                .defaultAdvisors(memoryAdvisor)                .build();    }}
4.Controller 测试接口 ChatController.java
import org.springframework.ai.chat.client.ChatClient;import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.RestController;@RestControllerpublic class ChatController {    private final ChatClient chatClient;    public ChatController(ChatClient chatClient) {        this.chatClient = chatClient;    }    /**     * 同一个conversationId,自动从MySQL读取历史对话     */    @GetMapping("/chat")    public String chat(            @RequestParam String conversationId,            @RequestParam String message    ) {        return chatClient.prompt()                .user(message)                .advisors(a -> a.param(MessageChatMemoryAdvisor.CONVERSATION_ID, conversationId))                .call()                .content();    }}
5.直接操作 Repository(手动读写数据库,不经过 Advisor)
@Autowiredprivate JdbcChatMemoryRepository repository;// 添加消息到会话repository.add("session_001", new UserMessage("你好,我叫李四"));// 查询会话全部历史消息List<ChatMessage> messages = repository.find("session_001");// 删除整个会话所有记录repository.delete("session_001");
6.测试请求示例
GET http://127.0.0.1:8080/chat?conversationId=session001&message=你好,我叫李四GET http://127.0.0.1:8080/chat?conversationId=session001&message=我叫什么名字?
执行后去 MySQL 数据库spring_ai_chat的SPRING_AI_CHAT_MEMORY` 表,即可看到持久化的对话记录。
MySQL 重要注意事项
  1. 字符集:必须 utf8mb4,否则 emoji 会存储异常;
  2. initialize-schema:生产环境一定改成 never,使用上面的 SQL 脚本提前建表;
  3. 限制:JdbcChatMemoryRepository 会忽略工具调用消息,function call 场景不要使用这个实现;
  4. 主键:CONVERSATION_ID + SEQUENCE_ID 联合主键,保证同一会话消息有序;
  5. 时间时区:连接串务必带上 serverTimezone=Asia/Shanghai,避免时区报错。
Redis
替换之前 MySQL (JdbcChatMemoryRepository) 方案,SpringAI2.0.1 Redis 内存仓库 artifact 和配置前缀已经改动,注意不要沿用旧版本配置前置要求:Redis 服务可用,Redis 必须开启 RedisSearch 模块!(核心坑点,不装会直接报错)
1.Maven 依赖 pom.xml
2.application.yml 配置文件 **配置前缀:spring.ai.chat.memory.repository.redis.** 2.0.1 版本不再是 spring.ai.chat.memory.redis !
3.配置类 RedisChatMemoryConfig.java
4.Controller 接口(测试接口)
重要坑说明(必看)
  1. Redis 必须安装 RedisSearch 模块!
  • 普通 Redis 默认不带搜索模块,启动会报错。
  • 本地推荐:Redis Stack(一键集成 RedisSearch)
  • 云 Redis:阿里云 / 腾讯云企业版 Redis 开启 RedisSearch 插件
2. 2.0.1 版本依赖名已经修改,不要用旧的 spring-ai-autoconfigure-model-chat-memory-redis,会自动配置失效Spring Fra...
3. 对比之前 Jdbc 版本:
    4. 记忆 TTL:time-to-live 配置会话过期,到期自动删除 Redis 里的对话记录,适合客服会话场景
    测试用例
    • 访问:http://127.0.0.1:8080/chat?conversationId=u001&msg=我叫张三
    • 再访问:http://127.0.0.1:8080/chat?conversationId=u001&msg=我叫什么名字?
    模型能记住你叫张三,会话存在 Redis,重启 SpringBoot 服务记忆不会丢失。

    相关学习资料