Spring AI:对接官方 DeepSeek-R1 模型 —— 实现推理效果
Spring AI:对接官方 DeepSeek-R1 模型 —— 实现推理效果
众所周知,DeepSeek 能够火出圈,除了回答质量非常高,另外一个亮点就是 —— 其推理能力,当时刚出来的时候,可以说是惊艳的很,上手感受下来,可以说是秒杀 OpenAI 的产品。
本文中,我们就来通过 Spring AI 对接官方 DeepSeek-R1 模型,实现一下推理效果!
修改模型
编辑 application.yml 配置文件,将模型名称修改为 deepseek-reasoner , 这表示使用 DeepSeek-R1 模型,而不是 V3 模型:
相关配置可以看上一篇文章:Spring AI:对接DeepSeek实战
spring:
ai:
deepseek:
// 省略...
chat:
options:
model: deepseek-reasoner # 使用哪个模型
// 省略...
修改完毕后,重启项目。打开浏览器,请求地址 http://localhost:8080/ai/generateStream?message=一加一等于多少 , 看看流式响应是否有推理效果。
你会发现并没有输出推理的过程,而是直接给出了回答。
添加工具类依赖
为了能够实现推理效果,我们需要重构一下后端代码。首先,编辑 pom.xml 文件,添加 commons-lang3 工具类,等会需要用到,如下:
<properties>
// 省略...
<!-- 常用工具类 -->
<commons-lang3.version>3.12.0</commons-lang3.version>
</properties>
// 省略...
<dependencies>
// 省略...
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons-lang3.version}</version>
</dependency>
// 省略...
</dependencies>
依赖添加完毕后,刷新一下 Maven, 将依赖下载到本地 Maven 仓库中。
新增 Controller
接着,在 /controller 包下,单独新建 DeepSeekR1ChatController 控制器,用于测试 R1 模型,防止需要修改上文的接口:
@RestController
@RequestMapping("/v1/ai")
public class DeepSeekR1ChatController {
@Resource
private DeepSeekChatModel chatModel;
/**
* 流式对话
* @param message
* @return
*/
@GetMapping(value = "/generateStream", produces = "text/html;charset=utf-8")
public Flux<String> generateStream(@RequestParam(value = "message", defaultValue = "你是谁?") String message) {
// 构建提示词
Prompt prompt = new Prompt(new UserMessage(message));
// 流式输出
return chatModel.stream(prompt)
.mapNotNull(chatResponse -> {
// 获取响应内容
DeepSeekAssistantMessage deepSeekAssistantMessage = (DeepSeekAssistantMessage) chatResponse.getResult().getOutput();
// 推理内容
String reasoningContent = deepSeekAssistantMessage.getReasoningContent();
// 推理结束后的正式回答
String text = deepSeekAssistantMessage.getText();
// 若推理内容有值,则响应推理内容,否则,说明推理结束了,响应正式回答
return StringUtils.isNotBlank(reasoningContent) ? reasoningContent : text;
});
}
}
代码大体上一致,说一下核心修改的地方:
- 为了区别接口地址,防止和之前的冲突,在接口前面添加 /v1 , 表示 v1 新版本;
- 修改 chatModel.stream() 流式输出逻辑:
将 getOutput() 返回值转换成专属 DeepSeek 消息对象 DeepSeekAssistantMessage ;
通过 getReasoningContent() 方法,能够获取到推理内容;
通过 getText() 方法,能够获取推理完毕后的回答内容;
注意,推理内容和回答不会同时有值,所以,返参的时候判空,响应不为空的那个即可;
重启项目,浏览器请求地址 http://localhost:8080/v1/ai/generateStream?message=一加一等于多少,看看是否包含了推理过程。
添加换行效果
目前响应的内容,都黏在一起了,也没有个换行效果,阅读起来很费劲,能不能完善一下呢?通过 Debug 调试,我们会发现,其实流式响应中,是返回了换行符的,不过是 /n/n 的形式,如下图所示,无法被浏览器识别到:
@RestController
@RequestMapping("/v1/ai")
public class DeepSeekR1ChatController {
@Resource
private DeepSeekChatModel chatModel;
/**
* 流式对话
* @param message
* @return
*/
@GetMapping(value = "/generateStream", produces = "text/html;charset=utf-8")
public Flux<String> generateStream(@RequestParam(value = "message", defaultValue = "你是谁?") String message) {
// 构建提示词
Prompt prompt = new Prompt(new UserMessage(message));
// 流式输出
return chatModel.stream(prompt)
.mapNotNull(chatResponse -> {
// 获取响应内容
DeepSeekAssistantMessage deepSeekAssistantMessage = (DeepSeekAssistantMessage) chatResponse.getResult().getOutput();
// 推理内容
String reasoningContent = deepSeekAssistantMessage.getReasoningContent();
// 推理结束后的正式回答
String text = deepSeekAssistantMessage.getText();
// 若推理内容有值,则响应推理内容,否则,说明推理结束了,响应正式回答
String rawContent = StringUtils.isNotBlank(reasoningContent) ? reasoningContent : text;
// 将 \n 替换为 HTML 换行标签 <br>
return StringUtils.isNotBlank(rawContent) ? rawContent.replace("\n", "<br>") : rawContent;
});
}
}
重启项目。
将推理过程和正式回答分隔开
虽然是换行了,但是推理过程和正式的回答,没有区分开来,能否再改善一下,通过分割线分隔一下呢,看的更明了一些?继续修改代码如下:
@RestController
@RequestMapping("/v1/ai")
public class DeepSeekR1ChatController {
@Resource
private DeepSeekChatModel chatModel;
/**
* 流式对话
* @param message
* @return
*/
@GetMapping(value = "/generateStream", produces = "text/html;charset=utf-8")
public Flux<String> generateStream(@RequestParam(value = "message", defaultValue = "你是谁?") String message) {
// 构建提示词
Prompt prompt = new Prompt(new UserMessage(message));
// 使用原子布尔值跟踪分隔线状态(每个请求独立)
AtomicBoolean needSeparator = new AtomicBoolean(true);
// 流式输出
return chatModel.stream(prompt)
.mapNotNull(chatResponse -> {
// 获取响应内容
DeepSeekAssistantMessage deepSeekAssistantMessage = (DeepSeekAssistantMessage) chatResponse.getResult().getOutput();
// 推理内容
String reasoningContent = deepSeekAssistantMessage.getReasoningContent();
// 推理结束后的正式回答
String text = deepSeekAssistantMessage.getText();
// 是否是正式回答
boolean isTextResponse = false;
// 若推理内容有值,则响应推理内容,否则,说明推理结束了,响应正式回答
String rawContent;
if (Objects.isNull(text)) {
rawContent = reasoningContent;
} else {
rawContent = text;
isTextResponse = true; // 标记为正式回答
}
// 处理换行
String processed = StringUtils.isNotBlank(rawContent) ? rawContent.replace("\n", "<br>") : rawContent;
// 在正式回答内容之前,添加一个分隔线
if (isTextResponse
&& needSeparator.compareAndSet(true, false)) {
processed = "<hr>" + processed; // 使用 HTML 的 <hr> 标签实现
}
return processed;
});
}
}
解释一下上述代码中,核心修改的地方:
- 定义一个 AtomicBoolean 变量 needSeparator, 使用原子布尔值跟踪分隔线状态(每个请求独立),默认值为 true, 表示需要分隔;
- 修改 chatModel.stream() 流式输出逻辑:
定义一个 boolean isTextResponse 布尔变量,用于标识当前响应的内容,是否是正式的回答,若不是,则表示还是推理内容;
在响应数据之前,判断 isTextResponse 是否为 true , 若是,则表示是正式回答,若同时满足 needSeparator 为 true , 说明推理结束了,则 将 needSeparator 设置为 false , 避免下次响应依然添加分割线。
最后,在响应数据的前面,拼接 hr 分割线标签;
再次重启项目,测试一波接口。
火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。
更多推荐
所有评论(0)