目录
[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 的本地部署流程
很多云平台都提供了一键部署大模型的功能,这里不再赘述(阿里云百炼平台、火山方舟-火山引擎等)
官网地址如下:
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风格的参数
- 请求路径):与平台有关
- DeepSeek官方平台:https://api.deepseek.com
- 阿里云百炼平台:https://dashscope.aliyuncs.com/compatible-mode/v1
- 本地ollama部署的模型:http://localhost:11434
- 安全校验):开放平台 都需要提供 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();
}