Spring Boot + DeepSeek AI 构建智能客服系统的实战指南
自研模型:效果最好,可控性强,但需要专业的算法团队、大量的标注数据和昂贵的算力,对于我们中小型团队来说,门槛太高,周期太长。国内大厂云服务:如百度UNIT、阿里云智能对话机器人。开箱即用,有可视化配置后台,初期上手快。但定制能力相对受限,费用模型复杂(调用量、QPS都可能计费),长期来看成本和控制力是个问题。开源模型本地部署:比如ChatGLM、Qwen。数据隐私有保障,可深度定制。但对服务器资源
最近在做一个客服系统的升级项目,客户那边反馈人工客服压力大,响应慢,成本还高。传统的基于关键词匹配的机器人,稍微复杂点的问题就答非所问,用户体验很差。于是我们团队决定引入AI能力,打造一个真正能“听懂人话”的智能客服。经过一番技术选型和折腾,最终用 Spring Boot 和 DeepSeek AI 把系统搭了起来,效果还不错。今天就把整个实践过程整理成笔记,分享给大家。
背景与痛点:为什么需要AI客服?
先说说我们遇到的实际情况。原来的客服系统主要靠人工和简单的规则机器人。
- 人工客服:成本高,培训周期长,而且面对大量重复性问题(比如“怎么退货”、“快递到哪了”),人力浪费严重。高峰期排队时间长,用户满意度直线下降。
- 规则机器人:基于关键词匹配,逻辑僵硬。比如用户问“我的包裹还没到,怎么回事?”,如果关键词库没有“包裹没到”的精确匹配,可能就触发不了正确的回答流程。更别提理解上下文了,用户多问两句,机器人就跟不上了。
而AI客服的核心优势在于自然语言理解(NLU)。它不需要精确的关键词,能理解用户语句的意图和上下文。比如用户先说“我想买手机”,接着问“有红色的吗?”,AI能结合上下文知道是在问“红色的手机”。这种体验的提升是质的飞跃。
技术选型:为什么是DeepSeek?
市面上提供NLP能力的方案很多,我们主要对比了几个方向:
- 自研模型:效果最好,可控性强,但需要专业的算法团队、大量的标注数据和昂贵的算力,对于我们中小型团队来说,门槛太高,周期太长。
- 国内大厂云服务:如百度UNIT、阿里云智能对话机器人。开箱即用,有可视化配置后台,初期上手快。但定制能力相对受限,费用模型复杂(调用量、QPS都可能计费),长期来看成本和控制力是个问题。
- 开源模型本地部署:比如ChatGLM、Qwen。数据隐私有保障,可深度定制。但对服务器资源(尤其是GPU)要求高,推理速度优化、模型维护都需要投入额外精力。
- DeepSeek等API服务:提供了强大的通用或专用模型通过API调用。平衡了效果、成本和控制力。
我们最终选择 DeepSeek 主要是基于以下几点考虑:
- 效果与成本平衡:DeepSeek的模型在中文理解和生成上表现非常出色,性价比高。API调用按Token计费,对于我们这种对话量可预估的场景,成本清晰可控。
- 开发友好:提供标准的RESTful API,文档清晰,集成到Spring Boot项目中非常简单,几行代码就能完成一次对话。
- 灵活性强:虽然不像本地部署那样完全自主,但通过Prompt工程、上下文管理和后续可能的微调(fine-tuning),已经能满足我们绝大部分定制化需求,比如让客服语气更亲切、更了解我们的产品知识。
对于我们这个项目来说,核心目标是快速验证AI客服的效果并稳定上线,DeepSeek API方案是最佳路径。

核心实现:三步搭建智能客服骨架
1. Spring Boot项目初始化配置
首先,创建一个标准的Spring Boot项目。我们用的是Spring Boot 2.7.x 和 Java 11。
<!-- pom.xml 关键依赖 -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 用于配置管理 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- HTTP客户端,用于调用DeepSeek API -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
</dependency>
<!-- 用于JSON处理 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!-- 后续缓存、异步等依赖按需引入 -->
</dependencies>
在 application.yml 中配置基础信息和DeepSeek的API密钥(切记不要硬编码在代码里)。
app:
deepseek:
api-key: ${DEEPSEEK_API_KEY:your-api-key-here} # 推荐从环境变量读取
base-url: https://api.deepseek.com
chat-completions-path: /v1/chat/completions
model: deepseek-chat # 根据需求选择模型
timeout: 10000 # 超时时间10秒
server:
port: 8080
2. DeepSeek API集成与封装
这是最核心的一步。我们封装了一个服务类 DeepSeekService 来处理与AI的通信。
首先,定义请求和响应的DTO(数据传输对象),严格遵循DeepSeek API文档的格式。
// ChatRequest.java
@Data
@Builder
public class ChatRequest {
private String model;
private List<Message> messages;
private Double temperature; // 控制回复随机性,客服场景建议较低,如0.7
private Integer maxTokens; // 限制回复长度
@Data
@Builder
public static class Message {
private String role; // "system", "user", "assistant"
private String content;
}
}
// ChatResponse.java
@Data
public class ChatResponse {
private String id;
private String object;
private Long created;
private String model;
private List<Choice> choices;
private Usage usage;
@Data
public static class Choice {
private Message message;
private Integer index;
private String finishReason;
}
@Data
public static class Message {
private String role;
private String content;
}
@Data
public static class Usage {
private Integer promptTokens;
private Integer completionTokens;
private Integer totalTokens;
}
}
然后,实现服务类。这里使用 RestTemplate 或 WebClient(响应式)进行HTTP调用。我们先用 RestTemplate 演示。
@Service
@Slf4j
public class DeepSeekService {
@Value("${app.deepseek.api-key}")
private String apiKey;
@Value("${app.deepseek.base-url}")
private String baseUrl;
@Value("${app.deepseek.chat-completions-path}")
private String chatPath;
@Value("${app.deepseek.model}")
private String model;
private final RestTemplate restTemplate;
public DeepSeekService(RestTemplateBuilder builder) {
this.restTemplate = builder
.setConnectTimeout(Duration.ofSeconds(5))
.setReadTimeout(Duration.ofSeconds(10))
.build();
}
public String chat(String userMessage, String sessionId) {
// 1. 构建请求消息列表。可以从缓存或DB中取出该sessionId的历史对话作为上下文
List<ChatRequest.Message> messages = buildMessagesWithContext(userMessage, sessionId);
ChatRequest request = ChatRequest.builder()
.model(model)
.messages(messages)
.temperature(0.7)
.maxTokens(500)
.build();
// 2. 设置请求头(鉴权)
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setBearerAuth(apiKey); // 使用Bearer Token认证
HttpEntity<ChatRequest> entity = new HttpEntity<>(request, headers);
// 3. 发送请求
String url = baseUrl + chatPath;
try {
ResponseEntity<ChatResponse> response = restTemplate.exchange(
url,
HttpMethod.POST,
entity,
ChatResponse.class
);
if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) {
String aiReply = response.getBody().getChoices().get(0).getMessage().getContent();
// 4. 保存本次对话到上下文(用于下一次对话)
saveContext(sessionId, userMessage, aiReply);
return aiReply;
} else {
log.error("DeepSeek API调用失败,状态码: {}", response.getStatusCode());
return "抱歉,AI助手暂时无法响应,请稍后再试。";
}
} catch (Exception e) {
log.error("调用DeepSeek API异常", e);
return "网络或服务异常,请重试。";
}
}
// 以下为上下文管理相关方法(简化版)
private List<ChatRequest.Message> buildMessagesWithContext(String userMessage, String sessionId) {
List<ChatRequest.Message> messages = new ArrayList<>();
// 添加系统指令,塑造AI角色
messages.add(ChatRequest.Message.builder()
.role("system")
.content("你是一个专业、友好、耐心的电商客服助手。请用简洁明了的中文回答用户关于订单、物流、退换货、产品咨询等问题。如果无法确定答案,请引导用户联系人工客服。")
.build());
// 从缓存获取该session的历史对话(例如最近5轮)
List<ChatRequest.Message> history = getHistoryFromCache(sessionId);
if (history != null) {
messages.addAll(history);
}
// 加入用户当前问题
messages.add(ChatRequest.Message.builder()
.role("user")
.content(userMessage)
.build());
return messages;
}
private void saveContext(String sessionId, String userMessage, String aiReply) {
// 将本轮对话存入缓存,并可能限制总长度(如只保留最近10轮)
// 实现略,可使用Redis的List结构存储
}
}
3. 对话状态管理设计
智能客服不是一问一答就结束,需要维持会话上下文。我们设计了一个简单的对话状态管理器。
核心思路是:为每个用户会话(sessionId)维护一个对话历史列表。每次用户提问,都将历史对话作为上下文传给AI。同时,为了防止上下文过长导致API费用增加和模型性能下降,我们需要限制历史记录的长度(比如只保留最近10轮对话)。

类图简化描述:
ConversationSession: 核心类,包含sessionId和List<DialogueTurn>。DialogueTurn: 记录一轮对话,包含userMessage,aiResponse,timestamp。ConversationManager: 管理所有会话,提供getSession(sessionId),addTurn(sessionId, turn),trimHistory(sessionId, maxTurns)等方法。
在实际项目中,ConversationManager 可以基于内存(如Caffeine Cache)或分布式缓存(如Redis)实现,后者更适合集群部署。
@Component
public class ConversationManager {
// 使用Caffeine缓存,设置过期时间(如30分钟无活动则清除会话)
private final Cache<String, ConversationSession> sessionCache = Caffeine.newBuilder()
.expireAfterAccess(30, TimeUnit.MINUTES)
.maximumSize(10000)
.build();
public ConversationSession getOrCreateSession(String sessionId) {
return sessionCache.get(sessionId, id -> new ConversationSession(id));
}
public void addDialogueTurn(String sessionId, String userMsg, String aiResp) {
ConversationSession session = getOrCreateSession(sessionId);
session.addTurn(new DialogueTurn(userMsg, aiResp));
// 修剪历史,只保留最近N轮
session.trimHistory(10);
}
public List<ChatRequest.Message> getRecentHistoryForPrompt(String sessionId, int maxTurns) {
ConversationSession session = sessionCache.getIfPresent(sessionId);
if (session == null) {
return Collections.emptyList();
}
return session.getRecentTurns(maxTurns).stream()
.flatMap(turn -> Stream.of(
ChatRequest.Message.builder().role("user").content(turn.getUserMessage()).build(),
ChatRequest.Message.builder().role("assistant").content(turn.getAiResponse()).build()
))
.collect(Collectors.toList());
}
}
性能优化:让客服系统又快又稳
直接调用外部API,在高并发下很容易成为瓶颈。我们做了以下几层优化。
1. 异步处理高并发请求
使用Spring的 @Async 或 CompletableFuture 将耗时的AI调用与HTTP请求线程解耦,避免阻塞。
@Service
public class AsyncChatService {
@Async("taskExecutor") // 需要配置线程池
public CompletableFuture<String> chatAsync(String message, String sessionId) {
String reply = deepSeekService.chat(message, sessionId);
return CompletableFuture.completedFuture(reply);
}
}
// 在Controller中
@PostMapping("/chat")
public CompletableFuture<ResponseEntity<ApiResponse>> chat(@RequestBody ChatRequestDto request) {
return asyncChatService.chatAsync(request.getMessage(), request.getSessionId())
.thenApply(reply -> ResponseEntity.ok(ApiResponse.success(reply)))
.exceptionally(ex -> ResponseEntity.status(500).body(ApiResponse.error("处理失败")));
}
Benchmark数据:在单机4核8G环境下,同步调用QPS约50(受限于API延迟,约200ms/次)。改为异步后,虽然API延迟不变,但Web容器的线程得以快速释放,系统吞吐量提升至约300 QPS(受限于线程池大小和网络IO)。
2. 缓存常用问答对
很多用户问题高度重复,比如“运费多少?”“退货流程”。每次调用AI既浪费钱又慢。我们可以引入本地缓存(如Caffeine)或分布式缓存(Redis),对高频、标准的问题进行缓存。
@Service
public class CachedChatService {
private final DeepSeekService deepSeekService;
// 缓存:Key为问题内容的MD5,Value为答案
private final Cache<String, String> qaCache = Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(1, TimeUnit.HOURS) // 答案可能更新,设置过期时间
.build();
public String chatWithCache(String userMessage, String sessionId) {
String cacheKey = DigestUtils.md5DigestAsHex(userMessage.getBytes());
String cachedAnswer = qaCache.getIfPresent(cacheKey);
if (cachedAnswer != null) {
log.info("缓存命中:{}", userMessage);
return cachedAnswer;
}
// 缓存未命中,调用AI
String aiAnswer = deepSeekService.chat(userMessage, sessionId);
// 判断是否为通用、稳定的答案,如果是则缓存
if (isCacheableAnswer(userMessage, aiAnswer)) {
qaCache.put(cacheKey, aiAnswer);
}
return aiAnswer;
}
private boolean isCacheableAnswer(String question, String answer) {
// 简单的启发式规则:答案长度适中,不包含“可能”、“或许”等不确定性词汇,且问题为通用问题
// 更复杂的可以用一个分类模型来判断
return answer.length() > 10 && answer.length() < 200
&& !answer.contains("可能") && !answer.contains("或许")
&& isGeneralQuestion(question);
}
}
3. 限流策略实现
防止恶意刷接口或突发流量打垮系统或产生过高API费用。我们使用Guava的 RateLimiter 或 Spring Cloud Gateway/ Sentinel进行限流。
这里展示一个简单的基于IP的令牌桶限流(可在拦截器中实现):
@Component
public class RateLimitInterceptor implements HandlerInterceptor {
// 每个IP每秒钟限制5次请求
private final Map<String, RateLimiter> limiters = new ConcurrentHashMap<>();
private static final double PERMITS_PER_SECOND = 5.0;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String clientIp = getClientIp(request);
RateLimiter limiter = limiters.computeIfAbsent(clientIp, ip -> RateLimiter.create(PERMITS_PER_SECOND));
if (limiter.tryAcquire()) {
return true;
} else {
response.setStatus(429); // Too Many Requests
response.getWriter().write("请求过于频繁,请稍后再试");
return false;
}
}
private String getClientIp(HttpServletRequest request) {
// 从请求头中获取真实IP(考虑代理情况)
String ip = request.getHeader("X-Forwarded-For");
if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
}
避坑指南:那些我们踩过的“坑”
1. 上下文丢失的预防措施
问题:用户在一个长对话中,突然AI“失忆”了,不记得之前说过什么。 原因:sessionId 生成或传递错误;缓存过期或被意外清除;上下文拼接逻辑有bug。
解决措施:
- 确保sessionId稳定:对于Web应用,可以使用前端生成的UUID(并存储到localStorage)或结合用户登录ID。每次请求必须携带。
- 上下文长度管理:AI模型有Token限制(如4096)。我们的
ConversationManager中的trimHistory方法至关重要。通常采用两种策略:- 固定轮数:只保留最近N轮对话(如10轮)。
- 智能摘要:当对话轮数过多时,调用AI对之前的对话历史生成一个简短摘要,然后用“摘要+最近几轮对话”作为新的上下文。这更高级,但成本也更高。
- 持久化备份:对于重要的客服对话(尤其是可能转为人工的),将会话历史持久化到数据库,即使缓存丢失也能恢复。
2. 敏感词过滤实现
AI可能生成不受控的内容。必须在返回给用户前进行过滤。
@Component
public class ContentFilter {
private Set<String> sensitiveWords = new HashSet<>(Arrays.asList("敏感词1", "敏感词2")); // 可从DB加载
public String filter(String text) {
if (text == null) return "";
String filteredText = text;
for (String word : sensitiveWords) {
filteredText = filteredText.replaceAll(word, "***");
}
// 更复杂的可以用DFA算法提高效率
return filteredText;
}
// 在DeepSeekService返回答案后调用
public String getSafeReply(String rawReply) {
return filter(rawReply);
}
}
3. 模型fine-tuning建议
虽然DeepSeek的通用模型很强,但要让客服更“懂”你的业务,可以考虑微调。
- 何时需要微调:当通用模型在特定领域(如你的产品型号、内部流程术语)上表现不佳时;当你希望客服回复具有固定格式或风格时。
- 准备数据:收集历史的优秀客服对话记录(Q-A对),至少需要几百条。数据质量很重要,回答需准确、专业、友好。
- 微调步骤:按照DeepSeek官方文档,将数据整理成特定的JSONL格式,上传并进行微调作业。完成后会得到一个专属的模型ID。
- 成本与评估:微调有一次性成本,并且调用微调后的模型可能更贵。上线前需进行充分的测试,评估效果提升是否值得付出成本。
生产部署:容器化与监控
开发完了,怎么上线?
1. 容器化方案
使用Docker + Docker Compose(单机)或 Kubernetes(集群)。
# Dockerfile
FROM openjdk:11-jre-slim
VOLUME /tmp
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
# docker-compose.yml
version: '3.8'
services:
smart-customer-service:
build: .
container_name: ai-customer-service
ports:
- "8080:8080"
environment:
- DEEPSEEK_API_KEY=${DEEPSEEK_API_KEY}
- SPRING_REDIS_HOST=redis
- SPRING_PROFILES_ACTIVE=prod
depends_on:
- redis
networks:
- app-network
redis:
image: redis:alpine
container_name: cache-redis
ports:
- "6379:6379"
networks:
- app-network
networks:
app-network:
driver: bridge
2. 关键监控指标配置
系统上线后,必须关注以下指标:
- 应用性能:使用Spring Boot Actuator + Prometheus + Grafana。
http.server.requests.duration:API响应时间,重点关注P95和P99。system.cpu.usage、jvm.memory.used:资源使用率。custom.deepseek.api.latency:自定义指标,记录调用DeepSeek API的耗时。
- 业务指标:
- 问答成功率:AI回复被用户认为有效的比例(可通过后续的“是否解决”按钮埋点计算)。
- 转人工率:用户请求转接人工客服的比率,过高可能意味着AI解决能力不足。
- 平均对话轮数:衡量AI维持对话的能力。
- 成本监控:
- 每日/每月Token消耗:通过DeepSeek API返回的
usage字段统计,密切监控,防止意外费用。 - 缓存命中率:评估缓存效果,命中率越高,成本节省越多。
- 每日/每月Token消耗:通过DeepSeek API返回的
总结与延伸思考
经过这一轮开发,我们的智能客服系统已经能处理70%以上的常见咨询,人工客服压力大大减轻,用户等待时间从平均几分钟降到秒级。整个技术栈(Spring Boot + DeepSeek)的选择让我们在效果、开发效率和成本之间取得了很好的平衡。
当然,这只是一个起点。智能客服还有很多可以深挖的方向:
- 多模态扩展:现在的客服是纯文本的。如果用户上传一张商品损坏的图片,AI能否识别并给出处理建议?未来可以考虑集成视觉模型,实现“图文并茂”的客服。
- 情感分析与主动关怀:能否通过分析用户语句的情感(焦急、愤怒、满意),让AI调整回复语气,甚至在识别到用户非常不满时主动提示转人工?这需要更精细的NLU能力。
- 与业务系统深度集成:现在的AI更多是“问答”,未来能否让它“办事”?比如用户说“帮我把订单123456退货”,AI在确认后,能否通过调用内部订单系统的API,真正创建一条退货工单?这需要将AI作为“大脑”,与后端的各个业务系统(OMS, CRM等)通过API打通,实现真正的自动化流程。
希望这篇笔记能给你带来一些启发。AI应用开发并不神秘,关键是想清楚场景,选对工具,然后一步步去实现和优化。如果你也在做类似的项目,欢迎一起交流探讨。
更多推荐



所有评论(0)