前言
DeepSeek太火了,我使用官网的DeepSeek每次只能回答一次,之后就会服务器繁忙,请稍后再试。虽然DeepSeek确实非常nice,使用起来我感觉真的非常好,但是现在这种情况对我来说是非常难受的。之后我自己看到了很多教人本地部署DeepSeek的教程,于是自己也试着去通过Spring AI和Ollama本地部署DeepSeek来使用。
DeepSeek介绍
DeepSeek是一家专注于人工智能领域的创新型科技公司,致力于开发先进的大型语言模型。其发布的多个模型,如DeepSeek LLM、DeepSeek-Coder、DeepSeek Math等,在各自领域取得了显著成果。特别是DeepSeek-R1模型,其性能对标OpenAI的o1正式版,在数学、代码、自然语言推理等任务上表现出色。
Ollama介绍
Ollama是一个基于Go语言的本地大语言模型运行框架,类似于Docker产品,保留了Docker的操作习惯,并支持上传大语言模型仓库。Ollama支持多种大型语言模型,如Llama 2、Code Llama、Mistral等,并允许用户根据特定需求定制和创建自己的模型。
一、环境准备
在开始之前,你需要确保你自己的开发环境满足一下需求:
- Ollama
- JDK17
- SpringBoot 3.x
- Maven或Gradle构建工具
Ollama的部署需要你自己去网上找教程,网上非常多,安装Ollama之后,请下载DeepSeek模型。
二、项目结构
树型结构图:
├── deepseek-ollama
│ ├── src # 源代码文件夹
│ │ ├── main # 主要源代码目录
│ │ │ ├── java # Java 源代码文件夹
│ │ │ │ └── com.linyi # 包名
│ │ │ │ ├── config # 配置类文件夹
│ │ │ │ │ ├── OllamaConfig.java # Ollama 配置类
│ │ │ │ │ └── WebConfig.java # Web 配置类
│ │ │ │ ├── controller # 控制器文件夹
│ │ │ │ │ └── ChatController.java # 聊天控制器类
│ │ │ │ └── DeekSeepollamaApplication.java # 主应用程序启动类
│ │ │ ├── entity # 实体类文件夹
│ │ │ │ ├── Chat.java # 聊天实体类
│ │ │ │ └── Result.java # 结果实体类
│ │ │ └── utils # 工具类文件夹
│ │ │ └── MdUtil.java # MarkDown 工具类
│ │ ├── resources # 配置资源文件夹
│ │ │ ├── application.yml # 应用配置文件
│ │ │ └── banner.txt # 启动欢迎信息文本
│ └── test # 测试代码目录
│ └── java # Java 测试源代码文件夹
三、项目配置
下面是项目的pom.xml文件
<dependencies>
<!--knife4j-->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
<version>4.4.0</version>
</dependency>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--ollama-->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
</dependency>
<!--devtools-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>1.0.0-M3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>public</id>
<name>aliyun nexus</name>
<url>https://maven.aliyun.com/repository/public</url>
<releases>
<enabled>true</enabled>
</releases>
</repository>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
主要的介绍:
| 名称 | 版本 | 说明 |
|---|---|---|
| Java | 17 | 编程语言 |
| SpringBoot | 3.3.5 | 基于Spring框架的开源Java框架 |
| Spring AI | 1.0.0-M3 | 人工智能集成到Spring应用程序中 |
| Ollama | 1.0.0-M3 | 与Spring AI相关的项目或库 |
| Knife4j | 4.4.0 | Swagger UI在中国的增强版 |
以下是项目的应用配置文件:
server:
port: 9292
servlet:
context-path: /api
spring:
application:
name: deepseek-ollama
description: deepseek-ollama 是一个结合了 Spring AI 和 Ollama 的项目,完成调用本地部署的 deepseek 模型的功能。
# springdoc-openapi项目配置
springdoc:
swagger-ui:
path: /swagger-ui.html
tags-sorter: alpha
operations-sorter: alpha
api-docs:
path: /v3/api-docs
group-configs:
- group: 'default'
packages-to-scan: com.linyi.controller
# knife4j的增强配置,不需要增强可以不配
knife4j:
enable: true
setting:
language: zh_cn
# 配置ai
ai:
ollama:
url: http://localhost:11434 #这里是我本地部署的ollama默认地址(需要根据你自己的更换)
chat:
options:
model: deepseek-r1:14b # 模型名称 本地ollama 下载的模型名称(需要根据你自己的更换)
请注意项目的配置ai部分,url和model需要根据你自己的配置。
四、功能实现
创建Ollama配置:
@Configuration
public class OllamaConfig {
@Value("${ai.ollama.url}")
private String url;
/**
* 创建并返回一个 OllamaChatModel 实例
* 该实例使用配置文件中定义的 ollama 服务 URL 进行初始化
*
* @return 初始化后的 OllamaChatModel 实例
*/
@Bean
public OllamaChatModel getOllamaChatModel() {
return new OllamaChatModel(new OllamaApi(url));
}
}
如果发现调用出现跨域问题,需要配置:
@Configuration
public class WebConfig implements WebMvcConfigurer {
/**
* 开启跨域
*/
@Override
public void addCorsMappings(CorsRegistry registry) {
// 设置允许跨域的路由
registry.addMapping("/**")
// 设置允许跨域请求的域名
//.allowedOrigins("/*")
.allowedOriginPatterns("*")//生产环境下慎用,否则可能会被攻击
// 设置允许的方法
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*") // 允许的头
.allowCredentials(true); // 是否允许携带凭证
}
}
接下来创建项目需要的实体类:
@Data
@AllArgsConstructor
@NoArgsConstructor
@Schema(description = "对话")
public class Chat implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
//输入信息
private String senderMessage;
//输出信息
private String receiverMessage;
//状态 true:成功 false:失败
private boolean status;
//耗时
private String cost;
//模型
private String model;
}
@Data
public class Result<T> {
// 返回码
private Integer code;
// 返回消息
private String message;
// 返回数据
private T data;
// 私有构造函数,防止外部直接实例化
private Result(Integer code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
}
// 成功时的静态方法
public static <T> Result<T> success(T data) {
return new Result<>(200, "Success", data);
}
// 成功时的静态方法,无数据
public static <T> Result<T> success() {
return new Result<>(200, "Success", null);
}
// 失败时的静态方法
public static <T> Result<T> error(Integer code, String message) {
return new Result<>(code, message, null);
}
}
创建管理类:
@Tag(name = "ChatController", description = "调用 Ollama 模型")
@Slf4j
@RestController
@SuppressWarnings({"unchecked", "rawtypes"})
public class ChatController {
@Resource
private OllamaConfig ollamaConfig;
@Value("${ai.ollama.chat.options.model}")
private String defaultChatOptionsModel;
/**
* 处理聊天请求的控制器方法
*
* @param msg 用户输入的聊天消息,不能为空
* @return 返回聊天机器人的回复结果
*/
@Operation(summary = "对话")
@GetMapping("/chat")
public Result<Chat> chat(String msg) {
// 记录接收到的问题消息
log.info("问题是:{}", msg);
// 初始化结果字符串
String result = "";
// 检查消息是否为空,如果为空则返回错误提示
if (StringUtils.isBlank(msg)) {
return Result.error(400, "请输入chat内容");
}
// 创建Prompt对象,包含聊天消息和配置选项
Prompt prompt = new Prompt(
msg,
OllamaOptions.builder()
.withModel(defaultChatOptionsModel)
.withTemperature(0.4)
.build()
);
// 记录开始时间
long startTime = System.nanoTime();
// 调用聊天模型并获取响应
ChatResponse response = ollamaConfig.getOllamaChatModel().call(prompt);
// 记录结束时间
long endTime = System.nanoTime();
// 计算并打印耗时
String formattedTimeTaken = String.format("%.2f", (endTime - startTime) / 1e9);
log.info("耗时: " + formattedTimeTaken + " 秒");
// 提取并记录聊天机器人的回复内容
result = response.getResult().getOutput().getContent();
log.info("回答:{}", result);
// 返回成功结果,包含聊天机器人的回复
Chat chat = new Chat(msg, result, true, formattedTimeTaken, defaultChatOptionsModel);
return Result.success(chat);
}
/**
* 回答写入.md文件
* 此方法接收用户消息,调用聊天模型生成回复,并将回复内容写入markdown文件中
* 主要用于演示如何将聊天内容转换为文档保存
*
* @param msg 用户输入的消息,用于生成聊天回复
* @return 聊天模型生成的回复内容
*/
@Operation(summary = "回答写入.md文件")
@GetMapping("/chatToMd")
public Result<Chat> chatToMd(String msg) {
// 记录用户提问内容
log.info("问题是:{}", msg);
// 初始化结果变量
String result = "";
// 验证输入内容是否为空
if (StringUtils.isBlank(msg)) {
return Result.error(400, "请输入chat内容");
}
// 构建聊天模型的提示信息,包括模型参数配置
Prompt prompt = new Prompt(
msg,
OllamaOptions.builder()
.withModel(defaultChatOptionsModel)
.withTemperature(0.4)
.build()
);
// 记录开始时间
long startTime = System.nanoTime();
// 调用聊天模型生成回复
ChatResponse response = ollamaConfig.getOllamaChatModel().call(prompt);
// 记录结束时间
long endTime = System.nanoTime();
// 计算并打印耗时
String formattedTimeTaken = String.format("%.2f", (endTime - startTime) / 1e9);
log.info("耗时: " + formattedTimeTaken + " 秒");
// 提取回复内容
result = response.getResult().getOutput().getContent();
// 记录回答内容
log.info("回答:{}", result);
// 回答写入.md文件
long l = System.currentTimeMillis();
MdUtil.createAndWriteMarkdownFile(result, l + ".md");
// 返回成功结果
Chat chat = new Chat(msg, result, true, formattedTimeTaken, defaultChatOptionsModel);
return Result.success(chat);
}
}
创建将DeepSeek回答写入.md文件的工具类:
public class MdUtil {
/**
* 创建并写入Markdown文件
*
* @param content 文件内容,即要写入的Markdown格式文本
* @param fileName 要创建的文件名,包括路径和扩展名
*
* 此方法尝试创建一个指定名称的Markdown文件,并将给定的内容写入其中
* 使用try-with-resources语句确保文件写入操作后资源能自动关闭
*/
public static void createAndWriteMarkdownFile(String content, String fileName) {
try (BufferedWriter writer = new BufferedWriter(new FileWriter(fileName))) {
writer.write(content);
System.out.println("创建成功: " + fileName);
} catch (IOException e) {
throw new RuntimeException("创建文件失败: " + fileName, e);
}
}
}
以上就是我自己实现的一个简单的本地调用DeepSeek的SpringBoot项目。
下面是我项目的实现:
启动项目之后,我进入knife4j进行接口的调用:
我调用对话接口:
接着我调用回答写入.md文件这个接口:
项目的接口文档URL:http://{host}:{port}/api/doc.html
我就自己的项目源码已经开源: Gitee:gitee.com/hsdchb/deep…
GitHub:github.com/linyshdhhcb…
总结:
总的来说通过Ollama调用大模型非常方便,不用我们调用什么,就对接好就行,如果你感觉这样调用太反人类了,没有实用性,那确实如此,这个项目我就是拿来练练手,如果你想要nice的UI,可以去看看Open WebUI、Chatbox、ollama-gui。这些Ollama的图形化项目都是开源的,按照它要求进行部署,也可以直接调用Ollama的大模型。