Spring也能接入Deepseek?

ops/2025/3/1 14:39:37/

引言

最近DeepSeek可谓风光无限,AI可谓是目前互联网最火热的几个名词,我也一直在关注他的发展,从以前的人工智障,到chatGPT的高不可攀(价格太贵),再到DeepSeek的横空出世,才看到了AI从概念到应用的真正出路。可以这样说,随着DeepSeek的出现,势必会在接下来的几年让AI变得异常火热。最近接入了coze和DeepSeek两款,接下来简单介绍下Spring AI接入DeepSeek。

本地部署

首先,接入DeepSeek有两种方式,一种是通过DeepSeek官网接入API的方式,另一种是本地部署DeepSeek,通过Ollama接入API接口。这里我选择了本地DeepSeek接入的模式,因为手上正好有个项目可能会用到AI,甲方希望数据安全性,所以需要本地部署DeepSeek。

我们先去下载Ollama,我这里还在测试,所以用的自己电脑,下载好后直接下一步安装,没有任何难度

这里前文已经实践过了大家可以去参考一下

本地部署Deepseek

运行成功后我们可以开始下一步了

Spring 调用本地API

Spring AI包封装了大部分AI调用的工具,适用于很多大模型,这里因为我们用的是本地Ollama,所以也用对应的包。

<dependency>  <groupId>org.springframework.ai</groupId>  <artifactId>spring-ai-bom</artifactId>  <version>1.0.0-SNAPSHOT</version>  <type>pom</type>  <scope>import</scope>  
</dependency>
<dependency>  <groupId>org.springframework.ai</groupId>  <artifactId>spring-ai-ollama-spring-boot-starter</artifactId>  <version>1.0.0-M5</version>  
</dependency>

引入对应版本包,这里需要注意一点,Spring AI的SpringBoot版本需要3.2.x以上,jdk需要17版本以上,所以我这里将项目从2.3.7.RELEASE升级到了3.3.4。需要升级的时候也遇到不少坑,比如个别包名变了,或者其他三方包冲突,日志冲突等问题,旧的项目升级还是有一定难度的。
增加模型配置

spring:ai:  ollama:  base-url: http://localhost:11434  chat:  model: deepseek-r1:1.5b  openai:  chat:  options:  response-format: json

默认地址是本地Ollama的地址,以后也可以改成远程,配置需要的大模型模板deepseek-r1:1.5b以及反参格式为json
大模型对话一般情况是不支持直接返回json格式的,SpringAI通过在对话增加关键词信息让AI回答以json格式返回,然后再通过框架将返回的内容解析成json,在解析成对象。这里可以不用在配置中指定json格式,也可以在每次对话的时候指定格式。

java">@Configuration
public class DeepSeekConfig {//注入模型,配置文件中的模型,或者可以在方法中指定模型@Autowiredprivate OllamaChatModel model;@Beanpublic ChatClient chatClient() {return ChatClient.builder(this.model).defaultAdvisors().build();}
}

配置chatChlient类

java">    @Autowired  private ChatClient chatClient;@PostMapping("/testChat")public DeepSeekResponse testChat(@RequestBody AIChatReq param){//直接返回return chatClient.prompt(param.getMsg()).call().entity(DeepSeekResponse.class);}

新增一个测试接口,发起一个对话试试,这里我希望让AI根据我给出的公式和参数,计算出一个结果值,然后结果通过json返回给我。
以下是我的问题:
V=(A+2C+K×H)×H×L ,这是一个开挖土方计算公式,其中V表示基槽土方量,A表示槽底宽度,C表示工作面宽度,H表示基槽深度,L表示基槽长度,如果A=1.5,C=2,H=2,L=3,k=0,V等于多少
启动服务后,通过postman访问一下

看下日志报错  

日志上发现DeepSeek确实回应了我的请求,最后也明确给出了正确结果,为何会转换失败?
跟踪源码,最后发现报错是在BeanOutputConverter的convert方法上

可以看到Spring AI包将AI回答转换成对象的逻辑
首先会根据反参类型格式化提问,就是通过实现FormatProvider接口实现,StructuredOutputConverter继承FormatProvider接口 

BeanOutputConverter实现StructuredOutputConverter接口,同时主要实现的还有MapOutputConverter,ListOutputConverter两个类。
主要作用也很明晰,BeanOutputConverter是将结果转换成对象,MapOutputConverter转换成Map,ListOutputConverter转换成List
我们着重观察下BeanOutputConverter干的活儿
除了实现了上面说的convert方法外,还实现了getFormat方法

 

意思就是会在我们提问前加上上面那段话,希望AI将结果按JSON格式返回,不要包含markdown模块
然后我们再结合convert方法,将结果转换成对象
但是,实际DeepSeek返回结果时,会带上一大段思考过程的内容 

即上面这一段,使用<think>标签包裹的内容
而Spring AI包在转换时会去判断字符串是否以 ``` 字符开头
由于DeepSeek并不是以此返回内容,导致对象转换出错
既然如此,思路就很简单了,我们需要改造BeanOutputConverter的convert方法

java">@Slf4j
public class DeepSeekBeanOutputConverter<T> extends BeanOutputConverter<T> {public DeepSeekBeanOutputConverter(Class<T> clazz) {super(clazz);}public DeepSeekBeanOutputConverter(Class<T> clazz, ObjectMapper objectMapper) {super(clazz, objectMapper);}public DeepSeekBeanOutputConverter(ParameterizedTypeReference<T> typeRef) {super(typeRef);}public DeepSeekBeanOutputConverter(ParameterizedTypeReference<T> typeRef, ObjectMapper objectMapper) {super(typeRef, objectMapper);}@Overridepublic T convert(@NonNull String text) {if (StrUtil.isBlank(text)) {return null;}log.info("deepseek response: {}", text);text = text.substring(text.indexOf("```json"));return (T) super.convert(text);}
}

 新建一个类DeepSeekBeanOutputConverter继承自BeanOutputConverter,重写convert方法 在转换提取json里面的内容,再通过父类的convert方法实现回答和类的转化

java">    @PostMapping("/chat")public DeepSeekResponse chat(@RequestBody AIChatReq param){//直接返回DeepSeekBeanOutputConverter deepSeekBeanOutputConverter = new DeepSeekBeanOutputConverter<>(DeepSeekResponse.class);return (DeepSeekResponse) chatClient.prompt(param.getMsg()).call().entity(deepSeekBeanOutputConverter);}

新增一个方法,在我们提交对话时,将BeanOutputConverter替换成DeepSeekBeanOutputConverter的实现


http://www.ppmy.cn/ops/162252.html

相关文章

2024年第十五届蓝桥杯大赛软件赛省赛Python大学A组真题解析

文章目录 试题A: 拼正方形(本题总分:5 分)解析答案试题B: 召唤数学精灵(本题总分:5 分)解析答案试题C: 数字诗意解析答案试题A: 拼正方形(本题总分:5 分) 【问题描述】 小蓝正在玩拼图游戏,他有7385137888721 个2 2 的方块和10470245 个1 1 的方块,他需要从中挑出一些…

结构化方法SASD

结构化方法是一种面向功能或者说面向数据流的方法&#xff0c;结构化方法包括针对需求分析的结构化分析方法&#xff08;SA&#xff09;、针对软件设计阶段的结构化设计方法&#xff08;SD&#xff09;、针对软件开发阶段的结构化编程方法&#xff08;SP&#xff09; 结构化分…

【Leetcode 每日一题】2353. 设计食物评分系统

问题背景 设计一个支持下述操作的食物评分系统&#xff1a; 修改 系统中列出的某种食物的评分。返回系统中某一类烹饪方式下评分最高的食物。 实现 FoodRatings 类&#xff1a;FoodRatings(String[] foods, String[] cuisines, int[] ratings) 初始化系统。食物由 f o o d s…

DevOps原理和实现面试题及参考答案

解释 DevOps 的核心目标与文化价值观,如何理解 “CAMS” 模型? DevOps 的核心目标是打破开发(Development)和运维(Operations)之间的壁垒,通过自动化、协作和持续反馈,实现软件的快速、可靠交付,以更好地满足业务需求和客户期望。具体来说,DevOps 旨在缩短软件的交付…

自媒体多账号如何切换不同定位才能做得更好

一、选择稀缺增长的赛道&#xff0c;避开内卷红海 1.职场赛道 ● 细分方向&#xff1a;公务员/体制内经验分享、自由职业指南、远程办公技巧。例如&#xff0c;通过采访自由职业者或分享远程工作体验&#xff0c;快速积累精准粉丝。 ● 优势&#xff1a;职场人群需求明确&…

Java面试要点120 - Java虚拟机栈帧结构

文章目录 引言一、Java虚拟机栈概述二、栈帧的内部结构2.1 局部变量表2.2 操作数栈2.3 动态链接2.4 方法返回地址 三、栈帧的生命周期四、虚拟机栈的异常五、栈帧优化与JIT编译总结 引言 Java虚拟机栈&#xff08;JVM Stack&#xff09;是Java虚拟机运行时数据区域的重要组成部…

docker通用技术介绍

docker通用技术介绍 1.docker介绍 1.1 基本概念 docker是一个开源的容器化平台&#xff0c;用于快速构建、打包、部署和运行应用程序。它通过容器化技术将应用及其依赖环境&#xff08;如代码、库、系统工具等&#xff09;打包成一个标准化、轻量级的独立单元&#xff0c;实…

自然语言处理NLP入门 -- 第八节OpenAI GPT 在 NLP 任务中的应用

在前面的学习中&#xff0c;我们已经了解了如何使用一些经典的方法和模型来处理自然语言任务&#xff0c;如文本分类、命名实体识别等。但当我们需要更强的语言生成能力时&#xff0c;往往会求助于更先进的预训练语言模型。OpenAI 旗下的 GPT 系列模型&#xff08;如 GPT-3、GP…