Java快速接入DeepSeek(支持流式问答,历史对话)

本文将快速介绍如何快速接入DeepSeek,根据历史对话支持基础问答及流式问答方式。

一、准备工作


注册DeepSeek开发者账号

访问DeepSeek官网,注册并创建应用,获取API Key。

查阅DeepSeek的API文档,了解接口地址、请求参数和返回格式。
 

确保已有一个Spring Boot项目,或者创建一个新的Spring Boot项目。


二、集成步骤


1. 添加依赖


在pom.xml中添加HTTP客户端依赖(如RestTemplate或WebClient):

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>

2. 配置API Key

application.propertiesapplication.yml中配置DeepSeek的API Key:

# application.properties
deepseek.api.key: 你的api-key
deepseek.api.url: https://api.deepseek.com/chat/completions

3. 创建API请求类

定义一个Java类来表示DeepSeek API的请求体和响应体:

@Data
public class DeepSeekRequest {
    private String model;
    private List<Message> messages;

    @Data
    public static class Message {
        private String role;
        private String content;

        @JsonCreator
        public Message(@JsonProperty("role") String role, @JsonProperty("content") String content) {
            this.role = role;
            this.content = content;
        }
    }
}
@Data
public class DeepSeekResponse {
    private List<Choice> choices;

    @Data
    public static class Choice {
        private Message message;

        @Data
        public static class Message {
            private String role;
            private String content;
        }
    }
}

4. 创建服务类

编写一个服务类来调用DeepSeek API:

import reactor.core.publisher.Flux;

public interface DeepSeekService {

    /**
     *  流式输出
     *
     * @param userId
     * @param userMessage
     * @return {@link Flux }<{@link String }>
     */
    Flux<String> callDeepSeekStream(Long userId, String userMessage);

    /**
     * @param userId
     * @param userMessage
     * @return {@link String }
     */
    String callDeepSeek(Long userId, String userMessage);
}
5. 创建服务类

实现服务

@Service
public class DeepSeekServiceImpl implements DeepSeekService {

    private static final Logger logger = Logger.getLogger(DeepSeekServiceImpl.class.getName());

    private static final String USER = "user";
    private static final String SYSTEM = "system";

    private final WebClient webClient;

    @Autowired
    @Qualifier("jsonRedisTemplate")
    private RedisTemplate<String, Object> redisTemplate;

    // 设置默认缓存时间为30分钟
    @Value("${youDeepseek.cache.expiration.minutes:30}")
    private int cacheExpirationMinutes;

    private final ObjectMapper objectMapper = new ObjectMapper();

    public DeepSeekServiceImpl(@Value("${youDeepseekApi.url}") String apiUrl,
                               @Value("${youDeepseekApi.key}") String apiKey) {
        this.webClient = WebClient.builder()
                .baseUrl(apiUrl)
                .defaultHeader("Authorization", "Bearer " + apiKey)
                .build();
    }

    @Override
    public Flux<String> callDeepSeekStream(Long userId, String userMessage) {
        return callDeepSeekAsyncStream(userId, userMessage);
    }

    @Override
    public String callDeepSeek(Long userId, String userMessage) {
        Mono<String> stringMono = callDeepSeekAsync(userId, userMessage);
        return stringMono.block();
    }

    /**
     *  异步调用DeepSeek API
     *
     * @param userId
     * @param userMessage
     * @return {@link Mono }<{@link String }>
     */
    public Mono<String> callDeepSeekAsync(Long userId, String userMessage) {
        String chatKey = getChatKey(userId);

        // 获取或初始化用户的对话历史
        Object object = redisTemplate.opsForHash().get(chatKey, String.valueOf(userId));
        List<DeepSeekRequest.Message> messages = Objects.isNull(object) ? new ArrayList<>() : (List<DeepSeekRequest.Message>) object;

        // 添加新的用户消息到对话历史
        messages.add(new DeepSeekRequest.Message(USER, userMessage));

        // 构建请求体
        DeepSeekRequest request = new DeepSeekRequest();
        request.setModel("deepseek-chat");
        request.setMessages(messages);

        // 发送异步请求
        return webClient.post()
                .bodyValue(request)
                .retrieve()
                .bodyToMono(DeepSeekResponse.class)
                .map(response -> {
                    if (response.getChoices() != null && !response.getChoices().isEmpty()) {
                        String content = response.getChoices().get(0).getMessage().getContent();
                        messages.add(new DeepSeekRequest.Message(SYSTEM, content));
                        redisTemplate.opsForHash().put(chatKey, String.valueOf(userId), messages);
                        redisTemplate.expire(chatKey, Duration.ofMinutes(cacheExpirationMinutes));
                        return content;
                    } else {
                        throw new RuntimeException("Failed to call DeepSeek API: No choices in response");
                    }
                })
                .onErrorResume(e -> Mono.error(new RuntimeException("Failed to call DeepSeek API: " + e.getMessage())));
    }

    /**
     *  异步流式调用DeepSeek API
     *
     * @param userId
     * @param userMessage
     * @return {@link Flux }<{@link String }>
     */
    public Flux<String> callDeepSeekAsyncStream(Long userId, String userMessage) {
        String chatKey = getChatKey(userId);

        // 获取或初始化用户的对话历史
        Object object = redisTemplate.opsForHash().get(chatKey, String.valueOf(userId));
        List<DeepSeekRequest.Message> messages = Objects.isNull(object) ? new ArrayList<>() : (List<DeepSeekRequest.Message>) object;

        // 添加新的用户消息到对话历史
        messages.add(new DeepSeekRequest.Message(USER, userMessage));

        // 构建请求体
        DeepSeekRequest request = new DeepSeekRequest();
        request.setModel("deepseek-chat");
        request.setMessages(messages);
        request.setStream(true);

        // 用于累积回答片段
        StringBuffer accumulatedContent = new StringBuffer();

        // 发送异步请求
        return webClient.post()
                .bodyValue(request)
                .retrieve()
                .bodyToFlux(String.class)
                .flatMap(chunk -> {
                    logger.info("接收部分: " + chunk);
                    if ("[DONE]".equals(chunk.trim())) {
                        return Flux.empty();
                    }
                    try {
                        JsonNode rootNode = objectMapper.readTree(chunk);
                        JsonNode choicesNode = rootNode.path("choices");
                        if (choicesNode.isArray() && !choicesNode.isEmpty()) {
                            JsonNode deltaNode = choicesNode.get(0).path("delta");
                            String content = deltaNode.path("content").asText("");
                            if (!content.isEmpty()) {
                                // 累积回答片段
                                accumulatedContent.append(content);
                                return Flux.just(content);
                            }
                        } else {
                            logger.warning("Choices 不能为空.");
                        }
                    } catch (Exception e) {
                        logger.severe("Failed to parse DeepSeek API response: " + e.getMessage());
                        return Flux.error(new RuntimeException("Failed to parse DeepSeek API response: " + e.getMessage()));
                    }
                    // 忽略非数据行
                    return Flux.empty();
                })
                .onErrorResume(e -> {
                    logger.severe("Failed to call DeepSeek API: " + e.getMessage());
                    return Flux.error(new RuntimeException("Failed to call DeepSeek API: " + e.getMessage()));
                })
                .doOnComplete(() -> {
                    // 确保在流式输出完成后保存完整的对话历史到 Redis
                    if (accumulatedContent.length() > 0) {
                        messages.add(new DeepSeekRequest.Message(SYSTEM, accumulatedContent.toString()));
                        redisTemplate.opsForHash().put(chatKey, String.valueOf(userId),messages);
                        redisTemplate.expire(chatKey, Duration.ofMinutes(cacheExpirationMinutes));
                    }
                });
    }

    private String getChatKey(Long userId) {
        return CacheConstant.DEEP_SEEK_CHAT_KEY + userId;
    }
}

6. 创建控制器

编写一个控制器来暴露API给前端或客户端:

@RestController
@RequestMapping("/deepseekApi")
public class DeepSeekController {
    private static final Logger logger = Logger.getLogger(DeepSeekController.class.getName());

    @Autowired
    private DeepSeekService deepSeekService;

    @PostMapping(value = "/chatStream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public SseEmitter chatStream( @RequestParam String message) {
        SseEmitter emitter = new SseEmitter(180000L);
        Long userId = SecurityUtils.getSubject().getLoginId();
        try {
            logger.info("开始处理userId的请求: " + userId + ", message: " + message);
            deepSeekService.callDeepSeekStream(userId, message)
                    .subscribe(
                            content -> {
                                try {
                                    emitter.send(content, MediaType.TEXT_PLAIN);
                                } catch (IOException e) {
                                    logger.severe("发送内容错误: " + e.getMessage());
                                    emitter.completeWithError(e);
                                }
                            },
                            error -> {
                                try {
                                    logger.severe("处理出现异常: " + error.getMessage());
                                    emitter.send(SseEmitter.event().data("Error: " + error.getMessage()));
                                } catch (IOException e) {
                                    logger.severe("发送了异常信息: " + e.getMessage());
                                    emitter.completeWithError(e);
                                } finally {
                                    emitter.complete();
                                }
                            },
                            () -> {
                                logger.info("该用户请求已处理完成 userId: " + userId);
                                emitter.complete();
                            }
                    );
        } catch (Exception e) {
            try {
                logger.severe("Unexpected error: " + e.getMessage());
                emitter.send(SseEmitter.event().data("Error: " + e.getMessage()));
            } catch (IOException ex) {
                logger.severe("Error sending unexpected error message: " + ex.getMessage());
                emitter.completeWithError(ex);
            } finally {
                emitter.complete();
            }
        }

        return emitter;
    }

    @PostMapping("/chat")
    public String chat(@RequestParam String message) {
        return deepSeekService.callDeepSeek(SecurityUtils.getSubject().getLoginId(),message);
    }
}

7. 测试接口

启动Spring Boot应用后,可以使用Postman或curl测试接口:

流式返回结果:

整体返回接口:


三,避坑指南

(1)遇到401错误先检查三件事:API Key是否正确、是否带Bearer前缀、请求头名称拼写对不对

(2)返回乱码时记得设置Content-Type为application/json; charset=utf-8

(3)建议用@Value注解从application.yml读取API Key,千万别把密钥硬编码在代码里

先跑通基础流程,再逐步添加重试机制、结果缓存等高级功能。对接API就像搭积木,重要的是先让整个流程转起来。遇到问题别慌,善用Postman测试接口,多看官方文档的错误码说明。记住,每个报错都是进步的阶梯!

DeepSeekRequest 参数官网介绍
* 出于与 OpenAI 兼容考虑,您也可以将 base_url 设置为 https://api.deepseek.com/v1 来使用,但注意,此处 v1 与模型版本无关。

* deepseek-chat 模型已全面升级为 DeepSeek-V3,接口不变。 通过指定 model='deepseek-chat' 即可调用 DeepSeek-V3。

* deepseek-reasoner 是 DeepSeek 最新推出的推理模型 DeepSeek-R1。通过指定 model='deepseek-reasoner',即可调用 DeepSeek-R1。

目前DS的调用接口响应时间还是比较长的,大家如果有其他好的方案可以在评论区一起讨论学习一下。

### Java DeepSeek 流式输出示例 为了实现在Java中通过DeepSeek库进行流式输出,可以采用Spring Boot框架来构建后端服务。此方法允许客户端实时接收数据更新而无需频繁发起请求。 #### 后端配置与依赖引入 首先,在`pom.xml`文件中加入必要的Maven依赖项以支持WebFlux模块以及DeepSeek SDK: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> <!-- 假设这是DeepSeek的SDK --> <dependency> <groupId>com.deepseek.sdk</groupId> <artifactId>deepseek-sdk</artifactId> <version>${latest.version}</version> </dependency> ``` #### 创建Controller类处理HTTP请求并返回流响应 定义一个控制器用于接受来自前端的应用程序/JSON格式的消息体,并调用业务逻辑层获取聊天回复内容作为服务器发送事件(Server-Sent Events, SSE)的形式推送给浏览器或其他订阅者: ```java @RestController @RequestMapping("/api/stream") public class ChatStreamController { @Autowired private ChatService chatService; @GetMapping(value = "/chat", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public Flux<String> getChatResponse(@RequestParam String message){ return chatService.generateResponseAsStream(message); } } ``` 上述代码片段展示了如何设置路由路径为`/api/stream/chat`的服务接口,它能够监听GET类型的网络访问并将结果封装成SSE消息序列化给定文本字符串形式的数据流传回至用户界面[^1]。 #### Service 层实现具体功能 接着是在`ChatService.java`里编写实际执行的任务——即向DeepSeek平台提交查询请求并解析其产生的逐条应答信息转交给上一层级组件进一步加工处理前转换为适合传输结构的对象实例集合: ```java @Service @Slf4j public class ChatService { @Value("${deepseek.api.key}") private String apiKey; public Flux<String> generateResponseAsStream(String userInput){ // 初始化DeepSeek Client对象 var client = new DeepSeekClient(apiKey); // 调用API获得异步流式的回应 return Flux.create(sink -> { try (var responseStream = client.query(userInput)) { responseStream.forEach(responsePart -> sink.next(responsePart)); sink.complete(); } catch (Exception e) { log.error("Error while processing stream.", e); sink.error(e); } }); } } ``` 这段源码实现了利用Reactor Core中的`Flux.create()`函数创建自定义发布者的能力,从而可以在接收到每一个部分的结果之后立即传递出去而不必等待整个过程结束再一次性全部吐出来;同时确保资源得到妥善释放并且异常情况也能被正确捕获报告[^3]。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值