基于 SpringAI 整合 DeepSeek 模型实现 AI 聊天对话

目录

[1、Ollama 的下载配置 与 DeepSeek 的本地部署流程](#1、Ollama 的下载配置 与 DeepSeek 的本地部署流程)

[1.1 下载安装 Ollama](#1.1 下载安装 Ollama)

[1.2 搜索模型并进行本地部署](#1.2 搜索模型并进行本地部署)

[2、基于 SpringAI 调用 Ollama 模型](#2、基于 SpringAI 调用 Ollama 模型)

[2.1 基于OpenAI 的接口规范(其他模型基本遵循)](#2.1 基于OpenAI 的接口规范(其他模型基本遵循))

[2.2 在 IDEA 中进行创建 SpringAI 项目并调用 DS 模型](#2.2 在 IDEA 中进行创建 SpringAI 项目并调用 DS 模型)

[3、基于 SpringAI 实现会话记忆功能](#3、基于 SpringAI 实现会话记忆功能)

[4、基于 SpringAI 实现会话历史功能](#4、基于 SpringAI 实现会话历史功能)

需求一:需要进行展示历史会话的列表信息

需求二:点击会话列表,展示对应的历史会话记录信息


📜后端代码地址:

MySpringAI: 基于 SpringAI + 大模型 实现基本的对话功能(只提供后端)https://gitee.com/MIMIDeK/my-spring-ai.git

1、Ollama 的下载配置 与 DeepSeek 的本地部署流程

很多云平台都提供了一键部署大模型的功能,这里不再赘述(阿里云百炼平台、火山方舟-火山引擎等)

官网地址如下:

Ollamahttps://ollama.com/

1.1 下载安装 Ollama

首先,我们需要下载一个Ollama的客户端,在官网提供了各种不同版本的Ollama,大家可以根据自己的需要下载

注意:

Ollama默认安装目录是C盘的用户目录,如果不希望安装在C盘的话(其实C盘如果足够大放C盘也没事),就不能直接双击安装了,需要通过命令行安装

OllamaSetup.exe 所在目录打开cmd命令行,然后命令如下:

OllamaSetup.exe /DIR=你要安装的目录位置

安装完成后,需要进行环境变量的配置:

OLLAMA_MODELS=你想要保存模型的目录

1.2 搜索模型并进行本地部署

Ollama 是一个模型管理工具和平台,它提供了很多国内外常见的模型,我们可以在其官网上搜索自己需要的模型,这里我们部署的是 DeepSeek R1 模型(我选择的是 7b,根据需求进行选择):

其中,Ollama 的命令很像 Docker 的,选择好对应的模型后,在控制台中进行运行命令:

ollama run deepseek-r1:7b

首次下载的话会有些慢,耐性等待即可

以下是本地下载部署完成后,进行对话的场景,ctrl + d 进行退出与 DeepSeek 的对话:


2、基于 SpringAI 调用 Ollama 模型

2.1 基于OpenAI 的接口规范(其他模型基本遵循)

目前大多数大模型都遵循 OpenAI的接口规范,是基于Http协议的接口;因此请求路径、参数、返回值信息都是类似的,可能会有一些小的差别,具体需要查看大模型的官方API文档

以下是基于 Python 的代码示范:

python 复制代码
# Please install OpenAI SDK first: `pip3 install openai`

from openai import OpenAI

# 1.初始化OpenAI客户端,要指定两个参数:api_key、base_url
client = OpenAI(api_key="<DeepSeek API Key>", base_url="https://api.deepseek.com")

# 2.发送http请求到大模型,参数比较多
response = client.chat.completions.create(
    model="deepseek-chat", # 2.1.选择要访问的模型
    messages=[ # 2.2.发送给大模型的消息
        {"role": "system", "content": "You are a helpful assistant"},
        {"role": "user", "content": "Hello"},
    ],
    stream=False # 2.3.是否以流式返回结果
)

print(response.choices[0].message.content)

接口说明

  • 请求方式):通常是POST,因为要传递JSON风格的参数
  • 请求路径):与平台有关
  • 安全校验):开放平台 都需要提供 API_KEY 来校验权限,本地 ollama 则不需要
  • 请求参数)(这里只列举常用的):
    • model:要访问的模型名称
    • messages:发送给大模型的消息,是一个数组
    • stream:true,代表响应结果流式返回;false,代表响应结果一次性返回,但需要等待
    • temperature:取值范围[0:2),代表大模型生成结果的随机性,越小随机性越低。DeepSeek-R1不支持
  • 这里请求参数中的 messages 是一个消息数组,其中包含两个属性):
    • role:消息对应的角色
    • content:消息内容

2.2 在 IDEA 中进行创建 SpringAI 项目并调用 DS 模型

以下是创建项目的基本依赖需求(基于SpringBoot3、JDK17):

.yaml 配置:

这里是基于 Ollama 的(默认端口是11434)

这里是基于openAI 的,进行调用开放平台的 API,需要输入 API-KEY(后面就不具体写明了,这里主要是基于 Ollama 的调用演示)

Config 配置类:(固定写法)

java 复制代码
@Configuration
public class CommonConfig {
    @Bean
    public ChatClient chatClient(OllamaChatModel model) {
        return ChatClient
                .builder(model)
                .build();
    }
}

Controller 控制响应类:(这里是以流式的方式进行响应)

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

    @Resource
    private ChatClient chatClient;

    // 由于以流式方式调用模型,展示的数据会出现乱码,需要进行规定编码格式
    @GetMapping(value = "/chat", produces = "text/html; charset=utf-8")
    public Flux<String> chat(String msg) {
        return chatClient.prompt()
                .user(msg)
                .stream()
                .content();
    }
}

进行页面的调用(若非流式访问,则需要等待 DS 生成完毕,才会出现响应信息):

注意,进行接口的调用时,需要启动 ollama 中的模型

🔖也可以加上日志的控制台打印输出,方便调试

.yml 中的配置:

然后在 Config 配置类中添加这一行代码,即可开启日志的打印:


3、基于 SpringAI 实现会话记忆功能

大致步骤:

定义会话存储方式(默认基于Map集合) ➡️ 配置会话记忆(关联上下文) ➡️ 添加会话ID(保证每个会话独立)

这里使用默认方式,由源码 可知是基于 Map 直接存入内存中的;当然也可以重写 ChatMemory ,比如存入到 Redis 中做缓存之类的(根据业务需求)

然后将 "存储方式" 配置到 "会话的上下文" 中,以处理会话的内容

运行结果:

**之前的msg提问信息是:**现在有15个苹果,6个人应该怎么分才好

我现在直接问它x个人应该怎么分,很明显它会进行关联之前的会话,根据上下文,来进行作答

存在问题:

然而,以上会话的处理会比较混乱,不管是谁来进行提问,模型都会自动关联所有的会话上下文来进行回答,这显然是不符合需求的;这时就需要与前端进行交接,即传递对应的会话 ID 过来进行处理,以实现用户的每个独立会话内容互不干扰

由前端响应接口可知,不同的会话对应着不同的会话ID

这时需要在Controller接口中添加以下配置,来接收存储不同会话下的ID


4、基于 SpringAI 实现会话历史功能

**存在问题:**目前,只要页面一刷新,之前的会话列表和对话记录都会被清除,不会被保留展示

前言:以下的type****参数可根据具体需求进行更换,chatId建议作保留以记录唯一会话

**需求一:**需要进行展示历史会话的列表信息

以下是对应的 UI 页面,以及对应的接口地址(模拟):

以下是 自定义 的 方法接口 与 实现类,主要用于 保存 和 获取 会话信息

java 复制代码
public interface ChatHistoryService {

    /**
     * 保存会话记录(这里的 type 可根据需求进行更改,chatId 建议保留以作为会话的唯一ID)
     */
    void save(String type, String chatId);

    /**
     * 获取会话列表(这里的 type 可根据需求进行更改)
     */
    List<String> getChatIds(String type);
}
java 复制代码
@Service
public class ChatHistoryServiceImpl implements ChatHistoryService {

    // 使用 map 集合进行存储
    private final Map<String, List<String>> chatHistoryMap = new HashMap<>();


    /**
     * 保存会话记录(这里的 type 可根据需求进行更改,chatId 建议保留以作为会话的唯一ID)
     */
    @Override
    public void save(String type, String chatId) {

        // 1.判断当前是否存在对应的 type 类型,若不存在则新增
        if (! chatHistoryMap.containsKey(type)) {
            chatHistoryMap.put(type, new ArrayList<>());
        }

        // 2.判断当前的 type 对应的 会话集合ID 是否含有当前的会话ID
        List<String> chatIds = chatHistoryMap.get(type);
        if (chatIds.contains(chatId)) {
            return;
        }
        chatIds.add(chatId);
    }


    /**
     * 获取会话列表信息(这里的 type 可根据需求进行更改)
     */
    @Override
    public List<String> getChatIds(String type) {

        List<String> chatIds = chatHistoryMap.get(type);
        return chatIds == null ? new ArrayList<>() : chatIds;
    }
}

每次用户在进行模型对话的时候,都需要将会话ID做保存(这里的 key 根据自己的业务需求来定,比如使用 用户ID来进行拼接等,这里先写死)

然后获取当前对应 key 的会话 ID 集合,进行会话列表信息展示

java 复制代码
@RestController
@RequestMapping("/ai/history")
public class ChatHistoryController {

    @Resource
    private ChatHistoryService chatHistoryService;


    /**
     * 获取历史会话列表信息(这里的 type 可根据需求进行更改)
     */
    @RequestMapping("/{type}")
    public List<String> getChatIds(@PathVariable("type") String type) {
        return chatHistoryService.getChatIds(type);
    }
}

需求二:点击会话列表,展示对应的历史会话记录信息

像以下UI所示,随便点击其中一个会话列表,对应的历史会话记录依然存在,并进行全部展示

接口地址(模拟):

会话的存储是在 ChatMemory类中进行的,所以需要跟进源码进行查看

主要还是这个get方法,来进行获取对应会话的对话记录

然后 返回值 Message类型,内部有继承以及实现的类

其中的 Content 代表着对话记录的内容,MessageType代表着当前对话记录的类型

以上结论: 由于需要满足当前接口的返回值,所以需要创建一个 DTO来进行满足

java 复制代码
@Data
@NoArgsConstructor
public class MessageDTO implements Serializable {

    /**
     * 角色类型
     */
    private String role;

    /**
     * 对应的内容信息
     */
    private String content;

    public MessageDTO(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 = "tool";
                break;
            default:
                this.role = "";
                break;
        }
        this.content = message.getText();
    }
    
    @Serial
    private static final long serialVersionUID = 1L;
}

接下来实现 "查询对话记录" 接口,与前端交互,即可实现展现历史对话记录

java 复制代码
    /**
     * 查询会话记录详情(这里的 type 可根据需求进行更改,chatId 建议保留以作为会话的唯一ID)
     */
    @GetMapping("/{type}/{chatId}")
    public List<MessageDTO> getChatHistory(@PathVariable("type") String type, @PathVariable("chatId") String chatId) {
        // 这里的展示记录条数最大值规定,直接使用的 int 型最大值,具体自己根据需求规定
        List<Message> messages = chatMemory.get(chatId, Integer.MAX_VALUE);
        if (messages == null) {
            return List.of();
        }
        // 进行封装返回类型
        return messages.stream().map(new Function<Message, MessageDTO>() {
            @Override
            public MessageDTO apply(Message message) {
                return new MessageDTO(message);
            }
        }).toList();
    }
相关推荐
level_xiwei4 分钟前
Linux之信号
linux·运维·服务器
YuSun_WK30 分钟前
Ubuntu与Linux的关系
linux·运维·ubuntu
茉莉玫瑰花茶31 分钟前
网络基础概念(下)
运维·服务器·网络
单车少年ing1 小时前
Linux kernel signal原理(下)- aarch64架构sigreturn流程
linux·运维·服务器
为什么要做囚徒1 小时前
Centos虚拟机远程连接缓慢
linux·运维·centos
Zhuai-行淮1 小时前
施磊老师基于muduo网络库的集群聊天服务器(四)
运维·服务器·网络
Hello.Reader1 小时前
Nginx HTTP 414 与“大面积”式洪水攻击联合防御实战
运维·nginx·http
Majimay1 小时前
云服务器存储空间不足导致的docker image运行失败或Not enough space in /var/cache/apt/archives
运维·服务器·docker
中国lanwp1 小时前
Netdata 监控多台服务器
运维·服务器
心随_风动1 小时前
Debian GNU/Linux的新手入门介绍
linux·debian·gnu