引言
随着大语言模型的快速发展,越来越多的开发者开始探索如何将这些强大的推理模型本地化运行。DeepSeek-R1,作为一款性能卓越的开源AI模型,以其低成本和出色的推理能力在技术圈内引起了广泛关注。本文将详细介绍使用Ollama部署的DeepSeek-R1,如何结合Spring Boot与Spring AI实现API服务调用,帮助开发者在本地化环境下实现高效的AI服务。
使用Spring Boot + Spring AI
在使用Ollama把deepseek-r1跑起来之后,我们就可以开始使用Spring Boot + Spring AI来调用了。
1、添加核心依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
2、配置Ollama的相关信息:
server.port=8082
spring.ai.ollama.base-url=http://192.168.10.105:11434
spring.ai.ollama.chat.options.model=deepseek-r1-32b-q4:latest
spring.ai.ollama.chat.options.temperature=0.7
3、存入上下文信息,并调用接口进行聊天
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.ollama.OllamaChatModel;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Flux;
import java.util.ArrayList;
import java.util.List;
/**
* 存入上下文信息,并调用接口进行聊天
*/
@Component
public class Completion {
@Resource
private OllamaChatModel aiClient;
/**
* 最大消息记录数
*/
private final static Integer MAX_SIZE = 10;
/**
* 消息记录
*/
private List<Message> messages = new ArrayList<>();
/**
* 初始化存入系统消息
*/
@PostConstruct
private void addSystemMessage() {
String message = "你好,我是deepSeek 机器人";
SystemMessage systemMessage = new SystemMessage(message);
messages.add(systemMessage);
}
/**
* 存储用户发送的消息
*/
private void addUserMessage(String message) {
Message userMessage = new UserMessage(message);
messages.add(userMessage);
}
/**
* 存储AI回复的消息
*/
private void addAssistantMessage(String message) {
Message assistantMessage = new AssistantMessage(message);
messages.add(assistantMessage);
}
/**
* 聊天接口
*/
public String chat(String message) {
addUserMessage(message);
String result = aiClient.call(new Prompt(messages)).getResult().getOutput().getContent();
addAssistantMessage(result);
update();
return result;
}
/**
* 流式聊天接口
*/
public Flux<String> chatStream(String message) {
addUserMessage(message);
StringBuffer fullReply = new StringBuffer();
Flux<String> fluxResult = aiClient.stream(new Prompt(messages))
.flatMap(response -> {
String reply = response.getResult().getOutput().getContent();
//拼接回复内容
fullReply.append(reply);
return Flux.just(reply);
})
.doOnComplete(() -> {
//监听流式响应完成,完整回复存入消息记录
addAssistantMessage(String.valueOf(fullReply));
});
update();
return fluxResult;
}
/**
* 更新消息记录
*/
private void update() {
if (messages.size() > MAX_SIZE) {
messages = messages.subList(messages.size() - MAX_SIZE, messages.size());
}
}
}
4、编写控制器Controller
import jakarta.annotation.Resource;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.ollama.OllamaChatModel;
import org.springframework.ai.ollama.api.OllamaOptions;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import java.util.List;
import java.util.Map;
/**
* @className: OllamaController
* @author: XJ-YK
* @date: 2025/2/8 15:46
**/
@RestController
@RequestMapping("/ollama")
public class OllamaController {
@Resource
private OllamaChatModel ollamaChatModel;
/**
* 简单调用
*/
@GetMapping(value = "/ai/ask")
public Object ask(String msg) {
String called = ollamaChatModel.call(msg);
System.out.println(called);
return called;
}
/***
* 流式方式
*/
@GetMapping(value = "/stream", produces = MediaType.TEXT_HTML_VALUE + ";charset=UTF-8")
public Flux<String> stream(String msg) {
return ollamaChatModel.stream(msg).flatMapSequential(Flux::just);
}
private final Completion completion;
public OllamaController(Completion completion) {
this.completion = completion;
}
/**
* 分析上下文聊天
*/
@GetMapping("/chat")
public String chat(String message) {
return completion.chat(message);
}
/**
* 流式上下文
*/
@GetMapping(value = "/chatStream", produces = MediaType.TEXT_HTML_VALUE + ";charset=UTF-8")
public Flux<String> chatStream(@RequestParam String message) {
return completion.chatStream(message);
}
}
5、测试聊天
简单调用
输入:
http://127.0.0.1:8082/ollama/ai/ask?msg=你是谁
回复:
您好!我是由中国的深度求索(DeepSeek)公司开发的智能助手DeepSeek-R1。
如您有任何任何问题,我会尽我所能为您提供帮助。
分析上下文聊天
输入:
http://127.0.0.1:8082/ollama/chat?message=1 3 5 7
回复:
好的,我现在要解决的问题是:
给定四个数字1、3、5、7,通过加减乘除运算得到24点。
我需要找到一个正确的表达式来达到这个目标。
首先,我会回顾一下基本的算术运算符:加法(+)、减法(-)、括号(())可以帮助改变运算顺序,乘法(×),除法(÷)。
然后,我需要考虑如何排列这四个数字,并通过这些运算符组合起来得到24点。
接下来,我可以尝试不同的组合方式。
例如,看看是否有办法把较大的数相乘或者相加,然后再进行调整。
比如,7和3相乘是21,如果能找到剩下的两个数1和5的某种组合来补上剩下的差值,这样可能可以达到24。
然后,我想到或许可以把7和3结合起来,再加上其他数字的操作。
例如: (7 - 3) = 4;然后看看能否用1和5得到6,再与4相乘得到24(因为4×6=24)。
那么问题转化为如何用1和5得到6,这似乎比较容易:5 + 1 = 6。
这样的话,就可以把整个表达式写成: ((7 - 3) × (5 + 1)) = 4 × 6 = 24。
这样看来,这个方法似乎是可行的。
不过,我要确保是否所有的运算符和数字都得到了正确的使用,并且每个数字只能用一次。
让我再检查一下:在这个表达式中,我使用了7、3、5、1四个数,每个只出现了一次,而且运算符正确地应用了加法和乘法,以及括号来改变运算顺序。
因此,这个解法是正确的。
当然,还可能有其他的组合方式,例如: (5 - 1) × (7 + 3) = 4 × 10 = 40,这超过了24; 或者,7 + 3 + 5 + 1 = 16,太小了,不行。
再试另一个可能性:比如用分数来达到目标。
例如: (3 - (1 ÷ 5)) × 7 ≈ (3 - 0.2) ×7 = 2.8×7=19.6,这也不对。
或者,考虑使用除法在不同的位置: (5 + 7) ÷ (3 - 1) = 12 ÷ 2 = 6,这样不够; 再比如,1 × (5 + 7) × 3?那会是1×12×3=36,太大了。
所以看来,最初找到的表达式可能就是正确的解法。
我需要确认一下计算步骤是否正确: 首先计算括号里的内容: - 7 - 3 =4 -5 +1=6 然后进行乘法:4 ×6=24。
没错,这个过程是正确的,并且满足使用每个数字一次的条件。
因此,最终的答案应该是: (7 - 3) × (5 + 1) =24。
要通过加减乘除运算将1、3、5、7这四个数组合得到24点,可以按照以下步骤进行计算: 1. 计算括号内的部分: - \(7 - 3 = 4\) - \(5 + 1 = 6\) 2. 将上述结果相乘: - \(4 \times 6 = 24\) 因此,正确的表达式为: \[ (7 - 3) \times (5 + 1) = 24 \] 最终答案是: \[ \boxed{(7 - 3) \times (5 + 1) = 24} \]