【源力觉醒 创作者计划】文心一言与deepseek集成springboot开发哪个更方便

一.实验背景

当前文心一言和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>

访问并测试:

https://localhost:8443/

经调试 改为文本调用之后:

复制代码
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

相关推荐
心之所依皆是你4 小时前
「源力觉醒 创作者计划」 百度AI的战略“惊蛰”,一场重塑格局的“破壁行动”
文心一言·deepseek·qwen3.0
努力的小雨4 小时前
还在为调试提示词头疼?一个案例教你轻松上手!
后端
魔都吴所谓4 小时前
【go】语言的匿名变量如何定义与使用
开发语言·后端·golang
陈佬昔没带相机5 小时前
围观前后端对接的 TypeScript 最佳实践,我们缺什么?
前端·后端·api
旋风菠萝6 小时前
JVM易混淆名称
java·jvm·数据库·spring boot·redis·面试
Livingbody7 小时前
大模型微调数据集加载和分析
后端
Livingbody7 小时前
第一次免费使用A800显卡80GB显存微调Ernie大模型
后端
weisian1517 小时前
Java WEB技术-序列化和反序列化认识(SpringBoot的Jackson序列化行为?如何打破序列化过程的驼峰规则?如何解决学序列化循环引用问题?)
java·spring boot
橘子编程7 小时前
SpringMVC核心原理与实战指南
java·spring boot·spring·tomcat·mybatis
Goboy8 小时前
Java 使用 FileOutputStream 写 Excel 文件不落盘?
后端·面试·架构