SpringAI+DeepSeek大模型应用开发实战

1.对话机器人

1.1对话机器人-初步实现

1.1.1引入依赖

xml 复制代码
    <properties>
        <java.version>17</java.version>
        <spring-ai.version>1.0.0-M6</spring-ai.version>
    </properties>
    <!-- 核心:引入 Spring AI BOM,统一管理所有相关依赖版本 -->
    <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>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
            <version>1.0.0-M6</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.22</version>
        </dependency>

    </dependencies>

    <repositories>
        <repository>
            <id>spring-snapshots</id>
            <name>Spring Snapshots</name>
            <url>https://repo.spring.io/snapshot</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </repository>

        <repository>
            <id>aliyun-releases</id>
            <url>https://maven.aliyun.com/repository/public</url>
        </repository>
    </repositories>


    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

1.1.2配置模型信息

yml 复制代码
# ollama方式连接
 spring:
   application:
     name: ai-demo
   ai:
     ollama:
       base-url: http://localhost:11434 # ollama服务地址, 这就是默认值
       chat:
         model: deepseek-r1:7b # 模型名称
         options:
           temperature: 0.8 # 模型温度,影响模型生成结果的随机性,越小越稳定

# api方式连接
spring:
  application:
    name: ai-demo # 应用名称归属 spring.application 节点
  ai:
    openai:
      base-url: https://api.deepseek.com
      api-key: 8888
      chat:
        options:
          model: deepseek-chat
          temperature: 0.8 # 模型温度,缩进对齐 options 子节点

1.1.3编写配置类CommonConfiguration

java 复制代码
    public org.springframework.ai.chat.client.ChatClient chatClient(OpenAiChatModel openAiChatModel,ChatMemory chatMemory){
        return org.springframework.ai.chat.client.ChatClient.builder(openAiChatModel)
                .defaultSystem("你是一个智能助手,名字叫煋玥")
                .build();
    }
   

1.1.4同步调用

同步调用,需要所有响应结果全部返回后才能返回给前端。

启动项目,在浏览器中访问:http://localhost:8080/ai/chat?prompt=你好

java 复制代码
@RestController
@RequestMapping(value = "/ChatController")
public class ChatController {

    @Autowired
   ChatClient client;
 
    @GetMapping("/chat")
    String generation(String question) {
        return client.prompt()
                .user(question)
                .call()
                .content();
    }

}

1.1.5流式调用

SpringAI中使用了WebFlux技术实现流式调用

java 复制代码
    @GetMapping(value = "/chatStream", produces = "text/html;charset=utf-8")
    public Flux<String> chat(@RequestParam(value = "question") String question, @RequestParam("chatId") String chatId){
        return client.prompt()
                .user(question)
                .stream()
                .content();
    }

1.2对话机器人-日志功能

1.2.1添加日志

修改CommonConfiguration,给ChatClient添加日志Advisor

java 复制代码
    @Bean  //ChatClient
    public org.springframework.ai.chat.client.ChatClient chatClient(OpenAiChatModel openAiChatModel,ChatMemory chatMemory){
        return org.springframework.ai.chat.client.ChatClient.builder(openAiChatModel)
                .defaultSystem("你是一个智能助手,名字叫煋玥")
                .defaultAdvisors(new SimpleLoggerAdvisor())
                .build();
    }

1.2.2修改日志级别

在application.yaml中添加日志配置,更新日志级别:

yml 复制代码
 logging:
   level:
     org.springframework.ai: debug # AI对话的日志级别
     com.itheima.ai: debug # 本项目的日志级别

1.3会话记忆功能

1.3.1实现原理

让AI有会话记忆的方式就是把每一次历史对话内容拼接到Prompt中,一起发送过去。

我们并不需要自己来拼接,SpringAI自带了会话记忆功能,可以帮我们把历史会话保存下来,下一次请求AI时会自动拼接,非常方便。

1.3.2注册ChatMemory对象(与视频有变动)

ChatMemory接口声明如下

java 复制代码
 public interface ChatMemory {
 ​
     // TODO: consider a non-blocking interface for streaming usages
 ​
     default void add(String conversationId, Message message) {
        this.add(conversationId, List.of(message));
     }
 ​
     // 添加会话信息到指定conversationId的会话历史中
     void add(String conversationId, List<Message> messages);
 ​
     // 根据conversationId查询历史会话
     List<Message> get(String conversationId, int lastN);
 ​
     // 清除指定conversationId的会话历史
     void clear(String conversationId);
 ​
 }

可以看到,所有的会话记忆都是与conversationId有关联的,也就是会话Id,将来不同会话id的记忆自然是分开管理的。

与视频讲解中不同的是,SpirngAI中,ChatMemory的实现,现在统一为:MessageWindowChatMemory

在CommonConfiguration中注册ChatMemory对象:

java 复制代码
 @Bean
 public ChatMemory chatMemory() {
     return MessageWindowChatMemory.builder()
             .chatMemoryRepository(new InMemoryChatMemoryRepository()) // 设置存储库
             .maxMessages(10) // 记忆窗口大小(保留最近的10条消息)
             .build();
 }
 ​
 # 也可以直接
 @Bean
     public ChatMemory chatMemory() {
         // 使用 MessageWindowChatMemory 作为默认内存策略(窗口消息保留)
         return MessageWindowChatMemory.builder().build();
     }

可以去查看MessageWindowChatMemory的源码

chatMemoryRepository:可以设置存储库,例如Redis,这里的InMemory是保存到内存中

maxMessages:设置窗口大小,指拼接prompt的时候将最近的多少条数据一起发送

MessageWindowChatMemory默认使用的存储库就是InMemory,默认窗口大小是20

想要使用其他的存储库,在1.5.3里有通过数据库的方式进行存储

1.3.3添加会话记忆Advisor

bash 复制代码
    @Bean  //ChatClient
    public org.springframework.ai.chat.client.ChatClient chatClient(OpenAiChatModel openAiChatModel,ChatMemory chatMemory){
        return org.springframework.ai.chat.client.ChatClient.builder(openAiChatModel)
                .defaultSystem("你是一个智能助手,名字叫煋玥")
                .defaultAdvisors(new SimpleLoggerAdvisor(), MessageChatMemoryAdvisor.builder(chatMemory).build())
                .build();
    }

1.3.4设置会话id

java 复制代码
    @GetMapping(value = "/chatStream", produces = "text/html;charset=utf-8")
    public Flux<String> chat(@RequestParam(value = "question") String question, @RequestParam("chatId") String chatId){
        return client.prompt()
                .user(question)
                .advisors(advisorSpec -> advisorSpec.param(CHAT_MEMORY_CONVERSATION_ID_KEY,chatId))
                .stream()
                .content();
    }

1.3.5 会话记忆持久化

继承重写implements ChatMemory

java 复制代码
package com.ruoyi.xingyueai.component;

import com.ruoyi.xingyueai.entity.ChatMemoryEntity;
import com.ruoyi.xingyueai.service.ChatMemoryService;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.MessageType;
import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 基于 MySQL + MyBatis-Plus 的 ChatMemory 实现(持久化会话记忆)
 * @author xingyue
 */
@Component
public class MySqlChatMemory implements ChatMemory {

    private final ChatMemoryService chatMemoryService;

    // 构造注入 Service
    public MySqlChatMemory(ChatMemoryService chatMemoryService) {
        this.chatMemoryService = chatMemoryService;
    }

    /**
     * 单条消息保存(ChatMemory 接口必须实现的抽象方法)
     * @param conversationId 会话ID
     * @param message 单条消息
     */
    @Override
    public void add(String conversationId, Message message) {
        // 1. 转换消息角色
        String role = switch (message.getMessageType()) {
            case USER -> "user";
            case ASSISTANT -> "assistant";
            case SYSTEM -> "system";
            default -> "unknown";
        };

        // 2. 调用简化后的工具方法,提取文本内容(适配当前 Content 接口)
        String content = extractTextContentFromMessage(message);

        // 3. 非空判断,调用 Service 保存到数据库
        if (!content.isBlank()) {
            chatMemoryService.saveChatRecord(conversationId, role, content);
        }
    }

    /**
     * 批量保存会话消息
     * @param conversationId 会话ID
     * @param messages 消息列表
     */
    @Override
    public void add(String conversationId, List<Message> messages) {
        // 调用单参数 add 方法,批量处理每条消息(无递归,安全可靠)
        messages.forEach(message -> this.add(conversationId, message));
    }

    /**
     * 单参数获取所有消息(ChatMemory 接口必须实现的抽象方法)
     * @param conversationId 会话ID
     * @return 该会话的所有消息列表
     */
    public List<Message> get(String conversationId) {
        // 1. 从数据库查询该会话的所有历史记录(按创建时间正序)
        List<ChatMemoryEntity> chatRecords = chatMemoryService.getChatRecordsByChatId(conversationId);
        if (chatRecords.isEmpty()) {
            return new ArrayList<>();
        }

        // 2. 转换为 Spring AI 的 Message 列表,返回给上层调用
        return chatRecords.stream()
                .map(record -> {
                    return switch (record.getRole()) {
                        case "user" -> new UserMessage(record.getContent());
                        case "assistant" -> new org.springframework.ai.chat.messages.AssistantMessage(record.getContent());
                        case "system" -> new SystemMessage(record.getContent());
                        default -> new UserMessage(record.getContent());
                    };
                })
                .collect(Collectors.toList());
    }

    /**
     * 获取指定会话的最后 N 条消息
     * @param conversationId 会话ID
     * @param lastN 最后 N 条消息
     * @return 截取后的消息列表
     */
    @Override
    public List<Message> get(String conversationId, int lastN) {
        // 调用单参数 get 方法,获取所有消息(无递归,打破无限循环)
        List<Message> allMessages = this.get(conversationId);
        if (allMessages.isEmpty() || lastN <= 0) {
            return new ArrayList<>();
        }

        // 截取最后 N 条消息,处理边界情况避免数组越界
        int startIndex = Math.max(0, allMessages.size() - lastN);
        return allMessages.subList(startIndex, allMessages.size());
    }

    /**
     * 清空指定会话的记忆
     * @param conversationId 会话ID
     */
    @Override
    public void clear(String conversationId) {
        chatMemoryService.clearChatMemory(conversationId);
    }

    /**
     * 工具方法:从 Message 中提取纯文本内容(适配当前 Content 接口,直接调用 getText())
     * @param message 对话消息
     * @return 纯文本内容
     */
    private String extractTextContentFromMessage(Message message) {
        // 核心修正:直接调用 Content 接口的 getText() 方法,无需 ContentItem
        String text = message.getText();
        // 非空保护,避免返回 null 导致后续数据库操作异常
        return text == null ? "" : text.trim();
    }
}

修改ChatClient配置

java 复制代码
    @Bean
    public MySqlChatMemory chatMemory(ChatMemoryService chatMemoryService) {
        return new MySqlChatMemory(chatMemoryService);
    }

    /**
     * 构建 ChatClient OpenAiChatModel
     */
    @Bean
    public ChatClient chatClient(OpenAiChatModel openAiChatModel, MySqlChatMemory mySqlChatMemory) {
        return ChatClient.builder(openAiChatModel)
                .defaultSystem("你是一个智能助手,名字叫煋玥")
                .defaultAdvisors(
                        new SimpleLoggerAdvisor(), // 日志顾问
                        MessageChatMemoryAdvisor.builder(mySqlChatMemory).build() // 绑定自定义持久化记忆
                )
                .build();
    }

实体类

java 复制代码
@Data
@TableName("chat_memory")
public class ChatMemoryEntity implements Serializable {
    private static final long serialVersionUID = 1L;

    /**
     * 主键ID
     */
    @TableId(type = IdType.ASSIGN_ID)
    private Long id;

    /**
     * 会话唯一标识
     */
    private String chatId;

    /**
     * 角色(user/assistant/system)
     */
    private String role;

    /**
     * 对话内容
     */
    private String content;

    /**
     * 创建时间
     */
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;

    /**
     * 更新时间
     */
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;

    /**
     * 逻辑删除(0=未删除,1=已删除)
     */
    @TableLogic
    private Integer deleted;
}

mapper

java 复制代码
@Mapper
public interface ChatMemoryMapper extends BaseMapper<ChatMemoryEntity> {

    /**
     * 根据会话ID查询对话记录(按创建时间正序)
     * @param chatId 会话唯一标识
     * @return 对话记录列表
     */
    List<ChatMemoryEntity> selectByChatId(@Param("chatId") String chatId);
}

service

java 复制代码
public interface ChatMemoryService extends IService<ChatMemoryEntity> {

    /**
     * 根据会话ID查询对话记录
     * @param chatId 会话唯一标识
     * @return 对话记录列表
     */
    List<ChatMemoryEntity> getChatRecordsByChatId(String chatId);

    /**
     * 保存会话记录
     * @param chatId 会话唯一标识
     * @param role 角色
     * @param content 对话内容
     */
    void saveChatRecord(String chatId, String role, String content);

    /**
     * 清空指定会话的记忆
     * @param chatId 会话唯一标识
     */
    void clearChatMemory(String chatId);
}

serviceimpl

java 复制代码
@Service
public class ChatMemoryServiceImpl extends ServiceImpl<ChatMemoryMapper, ChatMemoryEntity> implements ChatMemoryService {

    @Override
    public List<ChatMemoryEntity> getChatRecordsByChatId(String chatId) {
        return baseMapper.selectByChatId(chatId);
    }

    @Override
    public void saveChatRecord(String chatId, String role, String content) {
        ChatMemoryEntity entity = new ChatMemoryEntity();
        entity.setChatId(chatId);
        entity.setRole(role);
        entity.setContent(content);
        this.save(entity);
    }

    @Override
    public void clearChatMemory(String chatId) {
        // 此处可实现物理删除或逻辑删除,推荐逻辑删除
        List<ChatMemoryEntity> records = this.getChatRecordsByChatId(chatId);
        if (!records.isEmpty()) {
            records.forEach(record -> record.setDeleted(1));
            this.updateBatchById(records);
        }
    }
}

controller

jaca 复制代码
    @GetMapping("/getAllHistoryAll")
    public Result<List<Message>> getAllHistory(@RequestParam @NotBlank(message = "会话ID不能为空") String chatId) {
        try {
            // 调用 MySqlChatMemory 的 get 方法,传入 lastN 为极大值(获取全部消息)
            // 也可直接复用 mySqlChatMemory 中的数据库查询逻辑(chatMemoryService.getChatRecordsByChatId)
            List<Message> allHistory = mySqlChatMemory.get(chatId, Integer.MAX_VALUE);
            return Result.success(allHistory);
        } catch (Exception e) {
            return Result.error("查询全部历史消息失败:" + e.getMessage());
        }
    }


    @GetMapping("/getAllHistorylast")
    public Result<List<Message>> getLastNHistory(@Validated HistoryQueryDTO historyQueryDTO) {
        try {
            String conversationId = historyQueryDTO.getChatId();
            Integer lastN = historyQueryDTO.getLastN();

            // 若 lastN 为 null,默认返回全部;否则返回指定条数
            List<Message> lastNHistory = mySqlChatMemory.get(conversationId, lastN == null ? Integer.MAX_VALUE : lastN);
            return Result.success(lastNHistory);
        } catch (Exception e) {
            return Result.error("查询最后N条历史消息失败:" + e.getMessage());
        }
    }

mysql

sql 复制代码
 CREATE TABLE chat_message (
     id BIGINT PRIMARY KEY AUTO_INCREMENT,
     conversation_id VARCHAR(255) NOT NULL,
     role VARCHAR(50) NOT NULL, -- 如 USER, ASSISTANT
     content TEXT NOT NULL,
     created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
 );

1.4会话历史功能

service

java 复制代码
 public interface ChatHistoryService {

     /**
      * 保存聊天记录
      * @param type 业务类型,如:chat,service,pdf
      * @param chatId 聊天会话ID
      */
     void save(String type, String chatId);

     /**
      * TODO 删除聊天记录
      * @param type
      * @param chatId
      */
     void delete(String type, String chatId);

     /**
      * 获取聊天记录
      * @param type 业务类型,如:chat,service,pdf
      * @return 会话ID列表
      */
     List<String> getChatIds(String type);
 }

实现类内存存储

java 复制代码
 @Repository
 public class InMemoryChatHistoryServiceImpl implements ChatHistoryService {
 
     private final Map<String, List<String>> chatHistory = new HashMap<>();
 
      /**
      * 实现保存聊天记录功能
      * @param type
      * @param chatId
      */
     @Override
     public void save(String type, String chatId) {
         /*if (!chatHistory.containsKey(type))
         {
             chatHistory.put(type, new ArrayList<>());
         }
         List<String> chatIds = chatHistory.get(type);
         以上代码可以简化为下面一行代码
         */
 
         List<String> chatIds = chatHistory.computeIfAbsent(type, k -> new ArrayList<>());
 
         if (chatIds.contains(chatId))
         {
             return;
         }
         chatIds.add(chatId);
     }
 
     /**
      * TODO 实现删除功能
      * @param type
      * @param chatId
      */
     @Override
     public void delete(String type, String chatId) {
 
     }
 
      /**
      * 实现获取聊天记录功能
      * @param type
      * @return
      */
     @Override
     public List<String> getChatIds(String type) {
         /*if (!chatHistory.containsKey(type))
         {
             return new ArrayList<>();
         }
         return chatHistory.get(type);
         简化为以下一行代码
         */
         return chatHistory.getOrDefault(type, new ArrayList<>());
     }
 }

实现类mysql存储

java 复制代码
@Repository
 public class InSqlChatHistoryServiceImpl implements ChatHistoryService {
 
     @Autowired
     private ChatHistoryMapper chatHistoryMapper;
 
     /**
      * 保存chatId到数据库
      * @param type 业务类型,如:chat,service,pdf
      * @param chatId 聊天会话ID
      */
     @Override
     public void save(String type, String chatId) {
         // 先查询是否已存在
         if (exists(type, chatId)) return;
 
         ChatHistory chatHistory = new ChatHistory();
         chatHistory.setType(type);
         chatHistory.setChatId(chatId);
         chatHistoryMapper.insert(chatHistory);
     }
 
     // 判断 chatId 是否已存在
     private boolean exists(String type, String chatId) {
         List<String> chatIds = chatHistoryMapper.selectChatIdsByType(type);
         return chatIds.contains(chatId);
     }
 
     /**
      * TODO 删除
      * @param type
      * @param chatId
      */
     @Override
     public void delete(String type, String chatId) {
 
     }
 
     /**
      * 根据类型获取聊天记录
      * @param type
      * @return
      */
     @Override
     public List<String> getChatIds(String type) {
         return chatHistoryMapper.selectChatIdsByType(type);
     }
 }


 @Mapper
 public interface ChatHistoryMapper {
  
     /**
      * 插入一条聊天记录
      * @param chatHistory
      */
     @Insert("INSERT INTO chat_history (type, chat_id) VALUES (#{type}, #{chatId})")
     void insert(ChatHistory chatHistory);
  
     /**
      * 删除一条聊天记录
      * @param type
      * @param chatId
      */
     @Delete("DELETE FROM chat_history WHERE type = #{type} AND chat_id = #{chatId}")
     void delete(@Param("type") String type, @Param("chatId") String chatId);
  
     /**
      * 根据type获取聊天记录的chatIds
      * @param type
      * @return
      */
     @Select("SELECT chat_id FROM chat_history WHERE type = #{type}")
     List<String> selectChatIdsByType(String type);
 }

实体类 sql

bash 复制代码
 @Data
 public class ChatHistory {
     private String id;
     private String type;
     private String chatId;
 }
  CREATE TABLE chat_history (
     id BIGINT PRIMARY KEY AUTO_INCREMENT,
     type VARCHAR(255) NOT NULL,
     chat_id VARCHAR(255) NOT NULL
 );

controller

java 复制代码
    @GetMapping(value = "/chatStream", produces = "text/html;charset=utf-8")
    public Flux<String> chat(@RequestParam(value = "question") String question, @RequestParam("chatId") String chatId){
        // 保存会话ID
        chatHistoryRepository.save(ChatType.CHAT.getValue(), chatId);

        return client.prompt()
                .user(question)
                .advisors(advisorSpec -> advisorSpec.param(CHAT_MEMORY_CONVERSATION_ID_KEY,chatId))
                .stream()
                .content();
    }
   
相关推荐
进击的CJR2 小时前
redis cluster 部署
java·redis·mybatis
重生之后端学习2 小时前
19. 删除链表的倒数第 N 个结点
java·数据结构·算法·leetcode·职场和发展
qq_12498707532 小时前
基于小程序中医食谱推荐系统的设计(源码+论文+部署+安装)
java·spring boot·后端·微信小程序·小程序·毕业设计·计算机毕业设计
Coder_Boy_2 小时前
基于SpringAI的在线考试系统-阅卷评分模块时序图
java·人工智能·spring boot
linweidong3 小时前
C++大型系统中如何组织头文件和依赖树?
java·c++·架构
鹿角片ljp3 小时前
力扣14.最长公共前缀-纵向扫描法
java·算法·leetcode
pengweizhong3 小时前
Dynamic‑SQL2 查询篇:MyBatis 增强利器,让 SQL 像写 Java 一样丝滑
java·sql·教程
Remember_9933 小时前
【数据结构】深入理解优先级队列与堆:从原理到应用
java·数据结构·算法·spring·leetcode·maven·哈希算法
Leo July3 小时前
【Java】Spring Cloud 微服务生态全解析与企业级架构实战
java·spring cloud