本文分享基于Spring AI+DeepSeek R1搭建企业私有化模型工具的案例,效果截图:
可自由切换部署的本地模型,选择deepseek-r1 1.5b参数模型:
选择deepseek-r1 7b参数模型:
涉及到的技术有OLLAMA、DeepSeek R1与Spring AI,OLLAMA部署在Linux操作系统,Spring AI项目本地可以直接跑,也可以部署在服务器上。
01
OLLAMA安装
在Linux系统下,官方推荐的安装OLLAMA命令:
curl -fsSL https://ollama.org.cn/install.sh | sh
实测后发现网速非常慢,推荐使用第二种:
https://github.com/ollama/ollama/releases
选择Assets,下载Windows及对应Linux平台的ollama:
为方便大家使用,我已准备好下载文件:通过网盘分享的文件:
链接: https://pan.baidu.com/s/1MkLCX1K89fWx1UPf4IKbbw?pwd=mbh3
提取码: mbh3
本人Linux操作系统适用ollama-linux-amd64.tgz
将ollama-linux-amd64.tgz文件上传到Linux操作系统/u01/ollama目录下,如果没有此目录,可创建:mkdir -p /u01/ollama
然后进入目录:cd /u01/ollama
安装ollama:
sudo tar -C /usr -xzf ollama-linux-amd64.tgz
安装之后ollama命令出现在在:/usr/bin/ollama文件下
运行ollama:
ollama serve
保持运行状态,另外再打开一个Linux终端查看ollama状态:
表示已运行成功。
接下来需要将ollama加入后台运行,开机启动:
(1)为 Ollama 创建用户和组:
sudo useradd -r -s /bin/false -U -m -d /usr/share/ollama ollama
sudo usermod -a -G ollama $(whoami)
(2)创建OLLAMA服务文件:
sudo vim /etc/systemd/system/ollama.service
文件内容:
[Unit]
Description=Ollama Service
After=network-online.target
[Service]
ExecStart=/usr/bin/ollama serve
User=ollama
Group=ollama
Restart=always
RestartSec=3
Environment="OLLAMA_HOST=0.0.0.0:11434"
[Install]
WantedBy=default.target
其中Environment="OLLAMA_HOST=0.0.0.0:11434"表示指定OLLAMA可以运行的ip为任意ip,默认为127.0.0.1
保存并退出编辑/etc/systemd/system/ollama.service后
给文件授可执行权限:
sudo chmod 777 /etc/systemd/system/ollama.service
(3)后台启动服务ollama
sudo systemctl daemon-reload
设置开机自启:
sudo systemctl enable ollama
启动ollama:
sudo systemctl start ollama
sudo systemctl enable ollama
如果后期修改了/etc/systemd/system/ollama.service配置,需要重新执行以下命令生效:
首先:sudo systemctl daemon-reload
然后:sudo systemctl restart ollama
最后:sudo systemctl enable ollama
02
安装DeepSeek R1模型
第二步,需要借助ollama安装deepseek r1模型。
已知满血版的DeepSeek R1参数是671B。
需要根据您的Linux服务器性能决定安装哪个模型,参数越多的模型,需要的显卡配置要求和内存要求越高。
我的此台Linux服务器是CPU型号,内存16G:
可以安装deepseer-r1 1.5b和deepseek-r1 7b模型,已测试可以。
首先安装deepseek-r1:1.5b模型:
ollama run deepseek-r1:1.5b
会下载r1 1.5b模型一段时间,估计20分钟,1.6G大小左右,成功后出现对话框:
接着安装deepseek-r1:7b模型:
ollama run deepseek-r1:7b
先按Ctrl+D键退出1.5b模型,然后输入:ollama run deepseek-r1:7b
需要下载7b模型,估计要花40分钟~1个小时左右,4.7G大小左右,成功后出现对话框:
注意:在机器性能一定的情况下,模型越大,运行速度越慢,需要用到调优,后期会分享。
如此完成了DeepSeek R1模型的安装。
03
接入Spring AI
接入Spring AI之前,采用的是Spring Boot构建版本,需要用到Spring Boot 3.3.4版本,JDK 17以上,Maven需要3.6.3版本以上,不然无法正常使用Spring AI功能,本部分示例的代码已开源:https://gitee.com/javagongfu/huaxinfang_audio_translate.git
下面分享接入过程:
SpringBoot版本为:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.4</version>
</parent>
其中properties配置为:
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<lombok.version>1.18.30</lombok.version>
<hutool.version>5.8.28</hutool.version>
</properties>
接入Spring Boot核心坐标:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--测试依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
接入lombok坐标:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
由于项目使用了SpringBoot自带的Thymeleaf做界面,开箱即用,需引入Thymeleaf坐标:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
引入Spring AI功能:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
<version>1.0.0-M5</version>
</dependency>
项目中要使用Java EE,还需引入Java EE坐标:
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
logback日志管理工具需要使用:
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.4.8</version>
</dependency>
再增加依赖管理器:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>1.0.0-M5</version>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>
添加Maven快照:
<!--快照版本-->
<repositories>
<repository>
<id>spring-milestones</id>
<name>spring-milestones</name>
<url>https://repo.spring.io/milestone</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
项目Maven打包工具:
<!--打包-->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<includeSystemScope>true</includeSystemScope>
<fork>true</fork>
</configuration>
</plugin>
</plugins>
</build>
写好Maven pom.xml文件后,在IntelliJ IDEA刷新安装一下:
注意:Maven仓库配置需要添加阿里云镜像:
<mirror>
<id>aliyun</id>
<name>Aliyun Maven</name>
<url>https://maven.aliyun.com/repository/public</url>
<mirrorOf>central</mirrorOf>
</mirror>
接着在项目/src/main/resources下新建application.yml和application-dev.yml文件:
application.yml文件内容:
server:
# undertow配置
undertow:
# HTTP post内容的最大大小。当值为-1时,默认值为大小是无限的
max-http-post-size: -1
#以下的配置会影响buffer,这些buffer会用于服务器连接的IO操作,有点类似netty的池化内存管理
#每块buffer的空间大小,越小的空间被利用越充分
buffer-size: 512
#是否分配的直接内存
direct-buffers:true
threads:
#设置IO线程数,它主要执行非阻塞的任务,它们会负责多个连接,默认设置每个CPU核心一个线程
io:8
#阻塞任务线程池,当执行类似servlet请求阻塞操作, undertow会从这个线程池中取得线程,它的值设置取决于系统的负载
worker:256
port:8567
servlet:
context-path: /
spring:
profiles:
# dev默认为开发环境, prod线上环境
active: dev
application:
name: huaxinfang_audio_translate
main:
allow-bean-definition-overriding:true
servlet:
multipart:
max-file-size: 10MB
max-request-size: 10MB
application-dev.yml文件内容:
parameters:
model:"offline"#离线模型为例
hotWords:"{\"自定义\":20,\"热词\":20,\"设置\":30}"
fileUrl:"D:/Audio"
serverIpPort:"ws://X.X.X.X:10095/"
spring:
ai:
ollama:
base-url:http://X.X.X.X:11434
chat:
enabled:true
options:
model: deepseek-r1:1.5b
temperature:0.8
keep_alive: 5m
application-dev.yml其中base-url:
http://X.X.X.X:11434
改为您部署的OLLAMA服务器ip地址,OLLAMA服务的端口号默认是11434,如果您的服务器ip是:192.168.3.1,则配置应该为:
base-url:http://192.168.3.1:11434
可以去服务器ip看11434有没有启动:
netstat -an|grep 11434
表示OLLAMA的11434端口对所有ip可用,如果您使用的是云服务器,还需要在云服务器的安全组放开11434端口,防火墙放开11434端口。
在项目/src/main/java目录下新建com.asr.client包
在包下新建AudioTranslateApplication.java文件,作为SpringBoot启动类,内容为:
packagecom.asr;
importlombok.extern.slf4j.Slf4j;
importorg.springframework.boot.Banner;
importorg.springframework.boot.autoconfigure.SpringBootApplication;
importorg.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
importorg.springframework.boot.builder.SpringApplicationBuilder;
importorg.springframework.context.annotation.ComponentScan;
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@Slf4j
@ComponentScan(basePackages ="com.asr.client")
public classAudioTranslateApplication {
public static voidmain(String[] args) {
newSpringApplicationBuilder(AudioTranslateApplication.class)
.bannerMode(Banner.Mode.CONSOLE)//控制台打印
.run(args);
log.info("项目初始化完毕!");
}
}
接着新建com.asr.client.config包
在config包下新建WebConfig类,内容为:
packagecom.asr.client.config;
importorg.springframework.context.annotation.Configuration;
importorg.springframework.web.servlet.config.annotation.AsyncSupportConfigurer;
importorg.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
●@authorAI技术学院
●@version1.0.1
●@projectdavinci_audio_translate
●@description
●@date2025/3/4 10:54:24
*/
@Configuration
public classWebConfigimplementsWebMvcConfigurer {
@Override
public voidconfigureAsyncSupport(AsyncSupportConfigurer configurer) {
configurer.setDefaultTimeout(3600000);// 1小时超时
}
}
新建com.asr.client.controller包,包下新建OllamaChatController.java文件,内容为:
packagecom.asr.client.controller;
importjakarta.annotation.PostConstruct;
importlombok.extern.slf4j.Slf4j;
importorg.springframework.ai.chat.messages.Message;
importorg.springframework.ai.chat.messages.SystemMessage;
importorg.springframework.ai.chat.messages.UserMessage;
importorg.springframework.ai.chat.model.ChatResponse;
importorg.springframework.ai.chat.prompt.Prompt;
importorg.springframework.ai.ollama.OllamaChatModel;
importorg.springframework.ai.ollama.api.OllamaApi;
importorg.springframework.ai.ollama.api.OllamaOptions;
importorg.springframework.beans.factory.annotation.Autowired;
importorg.springframework.web.bind.annotation.GetMapping;
importorg.springframework.web.bind.annotation.RequestMapping;
importorg.springframework.web.bind.annotation.RequestParam;
importorg.springframework.web.bind.annotation.RestController;
importreactor.core.publisher.Flux;
importjava.util.ArrayList;
importjava.util.List;
/**
*@authorAI技术学院
*@version1.0.1
*@projectdavinci-ocr-python
*@descriptionOllama接口
*@date2025/2/5 11:20:31
*/
@RequestMapping("/deepseek")
@RestController
@Slf4j
public classOllamaChatController {
private finalOllamaChatModelchatModel;
private finalList<Message>chatHistoryList=newArrayList<>();
@Autowired
publicOllamaChatController(OllamaChatModel chatModel) {
this.chatModel= chatModel;
}
@PostConstruct
public voidinit() {
chatHistoryList.add(newSystemMessage("你是一个很好的助手."));
}
/**
@GetMapping("/ai/ollama/generate")
public Map<String,String> generate(@RequestParam(value = "message", defaultValue = "Tell me a joke") String message) {
return Map.of("generation", this.chatModel.call(message));
}
@GetMapping("/ai/ollama/generateStream")
public Flux<ChatResponse>generateStream(@RequestParam(value = "message", defaultValue = "Tell me a joke") String message) {
Prompt prompt = new Prompt(new UserMessage(message));
return this.chatModel.stream(prompt);
}
*/
@GetMapping("/ai/ollama/generate")
publicStringgenerate(String message) {
String call =chatModel.call(message);
returncall;
}
@GetMapping("/ai/generateStream")
publicFlux<ChatResponse>generateStream(@RequestParam(value ="message",defaultValue ="Tell me a joke") String message,@RequestParam(value="model",defaultValue="deepseek-r1:1.5b")String model) {
// chatHistoryList.add(new UserMessage(message));
// Prompt prompt = new Prompt(chatHistoryList, OllamaOptions.create()
// .withModel(model)//使用哪个大模型
// .withTemperature(0.8));
Prompt prompt =newPrompt(newUserMessage(message),OllamaOptions.create()
.withModel(model)//使用哪个大模型
.withTemperature(0.8));
returnchatModel.stream(prompt);
}
}
其中OllamaChatModel是实际的Spring AI模型控制者。
自动注入ChatModel:
@Autowired
publicOllamaChatController(OllamaChatModel chatModel) {
this.chatModel= chatModel;
}
如果模型为同步调用,可以增加一个聊天历史对话框记录:
private finalList<Message>chatHistoryList=newArrayList<>();
并初始化历史消息角色:
@PostConstruct
public voidinit() {
chatHistoryList.add(newSystemMessage("你是一个很好的助手."));
}
消息同步调用:
@GetMapping("/ai/ollama/generate")
publicStringgenerate(String message) {
String call =chatModel.call(message);
returncall;
}
本地运行起来后,可以在PostMan使用:
http://localhost:8567/deepseek/ai/ollama/generate?message=你可以帮我创作一首歌么
查看同步调用结果
在企业实际应用中,应该采用Stream流式打字输出,以下方法还能根据用户传入的模型model参数流式输出结果:
@GetMapping("/ai/generateStream")
publicFlux<ChatResponse>generateStream(@RequestParam(value ="message",defaultValue ="Tell me a joke") String message,@RequestParam(value="model",defaultValue="deepseek-r1:1.5b")String model) {
Prompt prompt =newPrompt(newUserMessage(message),OllamaOptions.create()
.withModel(model)//使用哪个大模型
.withTemperature(0.8));
returnchatModel.stream(prompt);
}
model参数默认为deepseek-r1:1.5b模型,并设置模型选择和温度temperature:
由于项目采用的开箱即用Thymeleaf前端页面,所以在/src/main/resources下新建templates模板文件夹:
在templates模板文件夹中新建deepseek.html采用Thymeleaf引擎:
代码为:
<!DOCTYPEhtml>
<htmllang="en">
<head>
<metacharset="UTF-8">
<title>DeepSeek R1本地模型</title>
</head>
<style>
.terminal-style{
background:#1e1e1e;
color:#00ff00;
font-family:monospace;
padding:20px;
height:400px;
overflow-y:auto;
border-radius:5px;
}
#inputMsg{
padding:10px;
height:50px;
}
#model{
margin-bottom:20px;
}
[data-placeholder]:empty:before{
content:attr(data-placeholder);
color:#888;
pointer-events:none;
display:block;/*用于换行*/
}
</style>
<script>
functionstartStream() {
constmessage =document.getElementById('contentedit').innerText;
consteventSource =newEventSource(`/deepseek/ai/generateStream?message=${encodeURIComponent(message)}&model=${document.getElementById('model').value}`);
constcontainer =document.getElementById('response-container');
container.innerHTML='';
eventSource.onmessage=function(e) {
constresponse =JSON.parse(e.data);
console.log('respose:'+response.result.output.content);
container.innerHTML+= response.result.output.content;
container.scrollTop= container.scrollHeight;//自动滚动
};
eventSource.onerror= () => {
eventSource.close();
// container.innerHTML += "<br>连接已关闭";
};
}
// document.getElementById('contentedit').addEventListener('input', function() {
// document.getElementById('inputMsg').value = this.innerText;
// });
</script>
<body>
<pth:text="${message}"></p>
<divid="output"th:fragment="chat-area">
模型类别:
<selectname="model"id="model">
<optionvalue="deepseek-r1:1.5b"selected>DeepSeek-R1 1.5b</option>
<optionvalue="deepseek-r1:7b">DeepSeek-R1 7b</option>
</select><br>
<divcontenteditable="true"id="contentedit"
style="border:1px solid#ccc;padding:15px"
data-target="inputMsg"data-placeholder="请描述您的问题需求"></div>
<inputtype="hidden"id="inputMsg"/>
<buttononclick="startStream()">发送</button>
<!--查询参数-->
<ath:href="@{/hello}">跳转语音识别使用</a>
<divid="response-container"class="terminal-style"></div>
</div>
</body>
</html>
前端代码不细讲,拷贝进deepseek.html即可:
在com.asr.client.controller包下新建HelloController.java文件,增加:
packagecom.asr.client.controller;
importcn.hutool.core.io.FileUtil;
importcn.hutool.core.lang.UUID;
importcn.hutool.json.JSONObject;
importcn.hutool.json.JSONUtil;
importcom.asr.client.core.exception.ServiceException;
importcom.asr.client.service.RecognitionService;
importlombok.extern.slf4j.Slf4j;
importorg.springframework.beans.factory.annotation.Autowired;
importorg.springframework.beans.factory.annotation.Value;
importorg.springframework.stereotype.Controller;
importorg.springframework.ui.Model;
importorg.springframework.web.bind.annotation.GetMapping;
importorg.springframework.web.bind.annotation.PostMapping;
importorg.springframework.web.multipart.MultipartFile;
importorg.springframework.web.socket.*;
importorg.springframework.web.socket.client.standard.StandardWebSocketClient;
importjava.io.File;
importjava.io.IOException;
importjava.net.URI;
importjava.util.concurrent.CountDownLatch;
importjava.util.concurrent.TimeUnit;
/**
*@authorAI技术学院
*@version1.0.1
*@projectdavinci_audio_translate
*@description接口
*@date2025/2/26 09:19:07
*/
@Controller
@Slf4j
public classHelloController {
@GetMapping("/deepseek")
publicStringdeepseek(Model model) {
model.addAttribute("message","DeepSeek R1本地模型阉割版");
return"deepseek";
}
}
其中@Controller说明是一个页面控制器。
@GetMapping("/deepseek")
publicStringdeepseek(Model model) {
model.addAttribute("message","DeepSeek R1本地模型阉割版");
return"deepseek";
}
方法尾return "deepseek"定位到deepseek.html模板页。
在AudioTranslateApplication运行起来后
本地浏览器输入:http://localhost:8567/deepseek
出现工具页面
先选择1.5b模型,输入描述:
可以看到逐字输出:
再切换成7b模型,输入描述,速度会更慢,模型更大:
响应时间更长:
也是逐字打印:
本代码示例仓库https://gitee.com/javagongfu/huaxinfang_audio_translate.git
还包含阿里达摩院FunASR语音识别使用,有兴趣的可以按照README安装FunASR,接入项目使用,有详细说明。
| 欢迎光临 链载Ai (https://www.lianzai.com/) | Powered by Discuz! X3.5 |