一.实验背景
当前文心一言和deepseek都开源了,二者都可以作为大模型应用开发的模型基础了,我们都可以编写springboot项目来集成deepseek和文心一言了
二.实验目标
本文基于实际操作,通过实际操作来对比文心一言和deepseek在集成到springboot项目中的异同,并通过其各自的能力来对比分析二者的异同点,为便新手开发者集成大模型开发具体应用并提供实际的开发建议.
注:鉴于模型部署非同一台硬件资源能力,故本文不对模型自身性能进行对比,只对比其集成到开发能力方便的便捷程度等
三.实验环境
3.1 开发环境规划
基于springboot 2.6.13,jdk11,maven 3.3.3
3.2 文心一言规划
A.版本:4.5系列。本文使用的具体版本是文心一言4.5 Turbo VL即ERNIE 4.5 Turbo VL
B.调用方式:本文不做本地环境搭建,使用文心一言开发者账号下官方部署的模型,通过账号+sdk完成功能调用
C.开发者平台:文心一言开发者平台
https://console.bce.baidu.com/qianfan/modelcenter/model/buildIn/list
3.3 deepseek规划
A.版本:同样
B.调用方式:不做本地环境搭建,使用DeepSeek的官方网站(https://www.deepseek.com)提供的API密钥或访问令牌来调用其能力,使用okhttp调api方式
C.开发者平台 deepseek开发者平台
3.4 实验功能规划
主要从开发流程,文档完整度,开发sdk获取便利度,开发方式,开发过程踩坑度,开发支持的功能,功能实现的表现等几方面进行评估
四.环境准备
4.1.文心一言
版本:ERNIE 4.5 Turbo VL
创建文心一言开发者账号,并注册访问令牌,并下载sdk(sdk可以使用maven,本文以maven为例)
先注册登录

进入页面之后,选择文心一言大模型4.5系列,本文按照ERNIE4.5 Turbo VL为例

选中后,进入api文档,连接:文心一言4.5 api

可以看到调用api的接口文档都在这里了。
我们调用api前需要进行安全认证,或者accesskey等。
创建apikey(子用户底下进行创建)
参考:https://cloud.baidu.com/doc/qianfan-api/s/ym9chdsy5

这个访问也是sdk方式访问的凭证,所以先生成,sdk直接使用maven进行下载后配置使用即可

选择中间下面的蓝色按钮:创建子账户,通过子账户访问文心一言4.5官方模型

按步骤操作后获取访问的key,并下载到本地。供后续项目中配置使用 。

有了访问key,还需要域名:
https://qianfan.baidubce.com
就可以到springboot中配置sdk了
创建应用appid
操作连接:https://console.bce.baidu.com/ai-engine/speech/overview/index
文心一言的环境准备到此告一段落,下面开始准备deepseek的。
具体文档参考地址:https://cloud.baidu.com/doc/qianfan-api/s/ym9chdsy5
4.2. deepseek
版本:DeepSeek-R1
创建deepseek账号,并注册访问令牌,并下载sdk(sdk可以使用maven,本文以maven为例)
登录注册,地址:deepseek开发平台

需要进行验证邮箱,提供邮箱,点击验证码之后,在邮箱获取验证码后再进行校验

获取访问accesskey

将此key拷贝,备用,这个就是项目中配置的访问key。对应的访问域名使用
获取对应的域名:https://platform.deepseek.com/
到此步骤,deepseek的环境准备基本结束,可以在项目里配置api域名,访问key了。
五.实验步骤
5.1.创建springboot项目
项目使用maven进行包管理。为了方便,本文简述步骤。通过简单的页面访问

选择模块

初始项目

对项目jdk,maven环境进行配置,本文略过
保证可正常打包

项目正常启动

5.2 集成文心一言 ERNIE 4.5 Turbo VL
版本:ERNIE 4.5 Turbo VL
5.2.1 pom文件增加依赖
<!-- 文心一言 SDK -->
<dependency>
<groupId>com.baidu.aip</groupId>
<artifactId>java-sdk</artifactId>
<version>4.16.1</version> <!-- 使用最新版本 -->
</dependency>
5.2.2 创建类,配置文件,controller,service类,并编写代码;创建html文件用来演示操作
config文件
package globalfairy.top.wenxinyiyandeepseekdemo.demos.web.config;
import com.baidu.aip.imageclassify.AipImageClassify;
import com.baidu.aip.imagesearch.AipImageSearch;
import com.baidu.aip.nlp.AipNlp;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ErnieConfig {
@Value("${ernie.app-id}")
private String APP_ID;
@Value("${ernie.api-key}")
private String API_KEY;
@Value("${ernie.secret-key}")
private String SECRET_KEY;
@Bean
public AipImageClassify ernieClient() {
return new AipImageClassify(APP_ID,API_KEY, SECRET_KEY);
}
@Bean
public AipNlp ernieNlpClient() {
return new AipNlp(APP_ID,API_KEY, SECRET_KEY);
}
@Bean
public AipImageSearch AipImageSearchClient() {
AipImageSearch client = new AipImageSearch(APP_ID,API_KEY, SECRET_KEY);
return client;
}
}
处理文本的核心客户端类:
@Bean
public AipNlp ernieNlpClient() {
return new AipNlp(APP_ID,API_KEY, SECRET_KEY);
}
处理图片的核心客户端类
@Bean
public AipImageSearch AipImageSearchClient() {
AipImageSearch client = new AipImageSearch(APP_ID,API_KEY, SECRET_KEY);
return client;
}
controller
package globalfairy.top.wenxinyiyandeepseekdemo.demos.web.controller;
import globalfairy.top.wenxinyiyandeepseekdemo.demos.web.service.ErnieService;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.Collections;
import java.util.Map;
@RestController
@RequestMapping("/api/ernie")
public class ErnieController {
@Resource
private ErnieService ernieServiceImpl;
@PostMapping("/generate")
public Map<String, String> generateText(@RequestBody Map<String, String> request) {
String prompt = request.get("prompt");
String result = ernieServiceImpl.generateText(prompt);
return Collections.singletonMap("result", result);
}
}
serviceimpl
package globalfairy.top.wenxinyiyandeepseekdemo.demos.web.service.impl;
import com.baidu.aip.imageclassify.AipImageClassify;
import com.baidu.aip.nlp.AipNlp;
import globalfairy.top.wenxinyiyandeepseekdemo.demos.web.service.ErnieService;
import org.json.JSONObject;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.HashMap;
@Service
public class ErnieServiceImpl implements ErnieService {
@Resource
private AipNlp client;
@Override
public String generateText(String prompt) {
try {
// 文心一言文本生成调用
JSONObject response = client.dnnlmCn(prompt, new HashMap<>());
return response.toString(2); // 返回格式化JSON
} catch (Exception e) {
throw new RuntimeException("文心一言调用失败", e);
}
}
}
service接口:
package globalfairy.top.wenxinyiyandeepseekdemo.demos.web.service;
public interface ErnieService {
String generateText(String prompt);
}
页面:
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DeepSeek 聊天</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 20px;
background-color: #f9f9f9;
}
h1 {
text-align: center;
color: #333;
}
.chat-container {
display: flex;
flex-direction: column;
align-items: center;
border: 1px solid #ccc;
border-radius: 8px;
padding: 20px;
background-color: #ffffff;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
#messages {
margin-top: 20px;
border: 1px solid #ccc;
border-radius: 5px;
padding: 10px;
max-height: 400px;
overflow-y: auto;
width: 100%;
background-color: #f1f1f1;
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1);
display: flex;
flex-direction: column;
gap: 10px; /* 使用 gap 来增加消息之间的间距 */
}
.message {
padding: 10px;
border-radius: 5px;
max-width: 80%;
white-space: normal; /* 允许正常换行 */
word-wrap: break-word; /* 长单词换行 */
overflow-wrap: break-word; /* 长单词换行 */
background: #c8e6c9; /* 默认背景色 */
display: inline-block; /* 使用 inline-block 以保持其流动性 */
}
.user-message {
background-color: #e1f5fe;
align-self: flex-end;
text-align: right;
border: 1px solid #b3e5fc;
}
.response-message {
align-self: flex-start;
text-align: left;
border: 1px solid #a5d6a7;
}
input[type="text"] {
padding: 10px;
width: 70%;
border-radius: 5px;
border: 1px solid #ccc;
margin-right: 10px;
}
button {
padding: 10px 15px;
border-radius: 5px;
border: none;
background-color: #007bff;
color: white;
cursor: pointer;
transition: background-color 0.3s;
}
button:hover {
background-color: #0056b3;
}
.loading {
color: #ff9800;
font-weight: bold;
text-align: center;
}
</style>
</head>
<body>
<div class="chat-container">
<h1>聊天流</h1>
<div>
<input type="text" id="prompt" placeholder="输入您的消息" />
<button id="start">开始聊天</button>
</div>
<div id="messages"></div>
</div>
<script>
let eventSource;
document.getElementById('start').addEventListener('click', function() {
const prompt = document.getElementById('prompt').value;
if (!prompt) {
alert("请输入消息。");
return;
}
if (eventSource) {
eventSource.close(); // 关闭之前的连接
}
// 清空输入框
document.getElementById('prompt').value = '';
// 添加加载状态
const messageDiv = document.getElementById('messages');
messageDiv.innerHTML += '<span class="loading">加载中...</span>';
eventSource = new EventSource(`/api/events?prompt=${encodeURIComponent(prompt)}`);
eventSource.onmessage = function(event) {
const data = JSON.parse(event.data); // 假设返回的是 JSON 格式
const messageDiv = document.getElementById('messages');
const responseMessage = document.createElement('span'); // 使用 span 而非 div
responseMessage.classList.add('message', 'response-message');
responseMessage.textContent = data.response;
messageDiv.appendChild(responseMessage);
messageDiv.scrollTop = messageDiv.scrollHeight; // 滚动到最新消息
};
eventSource.onerror = function(err) {
// 仅在连接意外关闭时显示错误
if (eventSource.readyState === EventSource.OPEN) {
console.error("EventSource 连接失败:", err);
const messageDiv = document.getElementById('messages');
const errorMessage = document.createElement('span'); // 使用 span 而非 div
errorMessage.classList.add('message', 'error-message');
errorMessage.textContent = '连接时出现错误。';
messageDiv.appendChild(errorMessage);
}
eventSource.close(); // 关闭连接
};
// 监听连接关闭事件
eventSource.onclose = function() {
console.log("EventSource 连接已关闭");
};
});
</script>
</body>
</html>
访问并测试:

经调试 改为文本调用之后:
package globalfairy.top.wenxinyiyandeepseekdemo.demos.web.service.impl;
import com.baidu.aip.imageclassify.AipImageClassify;
import com.baidu.aip.nlp.AipNlp;
import globalfairy.top.wenxinyiyandeepseekdemo.demos.web.service.ErnieService;
import org.json.JSONObject;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.HashMap;
@Service
public class ErnieServiceImpl implements ErnieService {
@Resource
private AipNlp client;
@Override
public String generateText(String prompt) {
try {
// 文心一言文本生成调用
JSONObject response = client.dnnlmCn(prompt, new HashMap<>());
return response.toString(2); // 返回格式化JSON
} catch (Exception e) {
throw new RuntimeException("文心一言调用失败", e);
}
}
}
仍然报错:

可以看到提示非常清楚,针对性进行优化。
5.3 集成 deepseek
5.3.1pom
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.14.9</version>
</dependency>
5.3.2
package com.globalfairy.dsweb.demos.web.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.netty.http.client.HttpClient;
import java.time.Duration;
@Configuration
public class AIClientConfig {
// 读取配置参数
@Value("${deepseek.api.url:http://localhost:11434/api/generate}")
private String apiUrl;
@Value("${deepseek.api.key:}")
private String apiKey;
@Bean
public WebClient deepseekWebClient() {
return WebClient.builder()
.baseUrl(apiUrl)
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.defaultHeader("Authorization", "Bearer " + apiKey) // 认证头
.clientConnector(new ReactorClientHttpConnector(
HttpClient.create()
.responseTimeout(Duration.ofSeconds(60)) // 模型响应需要较长时间
)).build();
}
}
5.3.3
package com.globalfairy.dsweb.demos.web.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate() {
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setConnectTimeout(5000); // 5秒连接超时
factory.setReadTimeout(60000); // 60秒读取超时
return new RestTemplate(factory);
}
}
5.3.3
package com.globalfairy.dsweb.demos.web.service;
import com.globalfairy.dsweb.demos.web.bean.CompletionRequest;
import com.globalfairy.dsweb.demos.web.bean.CompletionResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Flux;
import java.time.Duration;
@Service
@Slf4j
public class DeepSeekService {
private final WebClient webClient;
public DeepSeekService(WebClient deepseekWebClient) {
this.webClient = deepseekWebClient;
}
// 普通请求
public CompletionResponse generateSync(String prompt) {
return webClient.post()
.bodyValue(CompletionRequest.builder()
.model("deepseek")
.prompt(prompt)
.temperature(0.7)
.max_tokens(2000)
.build())
.retrieve()
.bodyToMono(CompletionResponse.class)
.block();
}
// 流式响应处理
public Flux<String> generateStream(String prompt) {
return webClient.post()
.bodyValue(CompletionRequest.builder()
.model("deepseek-r1:1.5B")
.prompt(prompt)
.stream(true)
.max_tokens(50)
.build())
.retrieve()
.bodyToFlux(String.class)
.timeout(Duration.ofMinutes(5)) // 长时对话场景
.onErrorResume(e -> {
log.error("API调用异常", e);
return Flux.just("服务暂时不可用");
});
}
}
5.3.5页面
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DeepSeek 聊天</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 20px;
background-color: #f9f9f9;
}
h1 {
text-align: center;
color: #333;
}
.chat-container {
display: flex;
flex-direction: column;
align-items: center;
border: 1px solid #ccc;
border-radius: 8px;
padding: 20px;
background-color: #ffffff;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
#messages {
margin-top: 20px;
border: 1px solid #ccc;
border-radius: 5px;
padding: 10px;
max-height: 400px;
overflow-y: auto;
width: 100%;
background-color: #f1f1f1;
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1);
display: flex;
flex-direction: column;
gap: 10px; /* 使用 gap 来增加消息之间的间距 */
}
.message {
padding: 10px;
border-radius: 5px;
max-width: 80%;
white-space: normal; /* 允许正常换行 */
word-wrap: break-word; /* 长单词换行 */
overflow-wrap: break-word; /* 长单词换行 */
background: #c8e6c9; /* 默认背景色 */
display: inline-block; /* 使用 inline-block 以保持其流动性 */
}
.user-message {
background-color: #e1f5fe;
align-self: flex-end;
text-align: right;
border: 1px solid #b3e5fc;
}
.response-message {
align-self: flex-start;
text-align: left;
border: 1px solid #a5d6a7;
}
input[type="text"] {
padding: 10px;
width: 70%;
border-radius: 5px;
border: 1px solid #ccc;
margin-right: 10px;
}
button {
padding: 10px 15px;
border-radius: 5px;
border: none;
background-color: #007bff;
color: white;
cursor: pointer;
transition: background-color 0.3s;
}
button:hover {
background-color: #0056b3;
}
.loading {
color: #ff9800;
font-weight: bold;
text-align: center;
}
</style>
</head>
<body>
<div class="chat-container">
<h1>聊天流</h1>
<div>
<input type="text" id="prompt" placeholder="输入您的消息" />
<button id="start">开始聊天</button>
</div>
<div id="messages"></div>
</div>
<script>
let eventSource;
document.getElementById('start').addEventListener('click', function() {
const prompt = document.getElementById('prompt').value;
if (!prompt) {
alert("请输入消息。");
return;
}
if (eventSource) {
eventSource.close(); // 关闭之前的连接
}
// 清空输入框
document.getElementById('prompt').value = '';
// 添加加载状态
const messageDiv = document.getElementById('messages');
messageDiv.innerHTML += '<span class="loading">加载中...</span>';
eventSource = new EventSource(`/api/events?prompt=${encodeURIComponent(prompt)}`);
eventSource.onmessage = function(event) {
const data = JSON.parse(event.data); // 假设返回的是 JSON 格式
const messageDiv = document.getElementById('messages');
const responseMessage = document.createElement('span'); // 使用 span 而非 div
responseMessage.classList.add('message', 'response-message');
responseMessage.textContent = data.response;
messageDiv.appendChild(responseMessage);
messageDiv.scrollTop = messageDiv.scrollHeight; // 滚动到最新消息
};
eventSource.onerror = function(err) {
// 仅在连接意外关闭时显示错误
if (eventSource.readyState === EventSource.OPEN) {
console.error("EventSource 连接失败:", err);
const messageDiv = document.getElementById('messages');
const errorMessage = document.createElement('span'); // 使用 span 而非 div
errorMessage.classList.add('message', 'error-message');
errorMessage.textContent = '连接时出现错误。';
messageDiv.appendChild(errorMessage);
}
eventSource.close(); // 关闭连接
};
// 监听连接关闭事件
eventSource.onclose = function() {
console.log("EventSource 连接已关闭");
};
});
</script>
</body>
</html>
六.结论
两者在开发流程,文档完整度,开发sdk获取便利度,开发方式上基本上区别不大。
不过文心一言开发过程在验权上要复杂不少,如果能更简洁就更好了
起来轻松玩转文心大模型吧一文心大模型免费下载地址:https://ai.gitcode.com/theme/1939325484087291906