本文将快速介绍如何快速接入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.properties
或application.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的调用接口响应时间还是比较长的,大家如果有其他好的方案可以在评论区一起讨论学习一下。