最近在做一个智能客服项目客户对响应速度和对话的“智能感”要求越来越高。传统的基于规则或简单关键词匹配的客服系统在面对复杂、口语化的用户提问时常常显得力不从心。正好DeepSeek的API开放了就想着用SpringBoot搭个架子把AI能力接进来过程中踩了不少坑也总结了一些优化心得记录一下。1. 为什么传统客服系统不够用了最开始我们用的就是很经典的规则引擎FAQ库。用户问“怎么退款”系统就去匹配“退款”这个关键词然后返回预设的答案。这套方案在初期简单有效但问题很快就暴露了并发一高就卡顿用户稍微多点同步处理请求的Tomcat线程就被占满了新用户只能排队等着体验很差。意图识别太死板用户说“我买的东西不想要了钱能退吗”和“如何申请退货退款”在业务上是一个意图但关键词匹配可能就失效了准确率上不去。对话没有记忆用户问“我的订单”客服回复了订单号。用户接着问“什么时候发货”传统系统就懵了它不知道这个“发货”指的是上一个订单的发货。多轮对话根本没法搞。所以核心痛点就是系统不够聪明也扛不住压力。我们需要一个能理解自然语言、能记住上下文、还能快速响应的方案。2. 为什么选DeepSeek技术选型对比市面上NLP引擎不少我们主要对比了BERT、GPT系列和DeepSeek。BERT在意图分类、实体识别这些任务上很强准确率高。但它是“编码器”天生不适合做生成式的对话。你要用它做客服得自己搭一套复杂的对话管理逻辑训练和部署成本对我们中小团队来说有点高。GPT系列如ChatGPT API生成能力没得说对话很流畅。但主要问题有两个一是API调用延迟相对高一些平均在1-3秒二是对中文特定场景的优化比如国内的一些产品术语、网络用语可能没那么“接地气”而且成本考量也是重点。DeepSeek最终选择它主要是几个点打动我们响应速度快在同样的问题下API响应延迟平均能控制在800ms以内这对实时对话体验至关重要。中文优化好针对中文语料做了深度训练处理国内用户那些口语化、带点方言或者网络梗的提问理解得更准。成本与效率平衡提供了清晰、友好的API集成简单同时保持了不错的模型效果。对于我们这种需要快速落地、验证业务场景的项目来说非常合适。简单说DeepSeek在“快”、“准”、“省”这三个我们最关心的维度上取得了不错的平衡。3. 核心实现三步搭建智能客服骨架整个系统的架构不复杂核心就是SpringBoot应用作为中间层连接前端用户和DeepSeek的AI能力。3.1 SpringBoot与DeepSeek API的集成与鉴权DeepSeek API通常使用API Key进行鉴权。我们在application.yml里配置好然后在代码里通过拦截器或者Feign Client的请求头自动添加。# application.yml deepseek: api: base-url: https://api.deepseek.com key: ${DEEPSEEK_API_KEY:your_api_key_here} timeout: 10000 # 超时时间10秒更关键的是我们使用RestTemplate或WebClient进行调用时需要处理好异步和超时。这里我更喜欢用WebClient它是响应式的非阻塞更适合高并发IO场景。3.2 使用WebSocket实现实时对话流HTTP一问一答太慢了用户看着“正在输入…”的体验更好。我们引入了WebSocket。前端建立WebSocket连接到我们的SpringBoot服务。用户发送消息服务端收到后立即返回一个“已收到”的ACK。服务端异步调用DeepSeek API获取流式响应如果API支持。如果不支持流式就等完整响应回来。通过同一个WebSocket连接将AI回复的文本片段或最终结果推送给前端。这样用户就能几乎实时地看到AI一个字一个字“打出来”的回复体验提升巨大。3.3 基于Redis的对话状态管理这是实现多轮对话的关键。每个对话会话Session需要一个唯一ID。我们把对话的历史记录上下文存在Redis里。Key设计chat:session:{sessionId}:messagesValue结构用一个List存放最近的N轮对话比如最近10轮。每次新的交互都把用户问题和AI回答作为一个对象追加到List中。调用DeepSeek API时把这个上下文列表一起发送过去AI就能基于历史聊天来回答了。过期时间给这个Key设置一个TTL比如30分钟。用户30分钟不活动会话自动清除释放内存。4. 关键代码示例下面贴几个核心代码片段加了详细注释。1. 异步处理请求的Controller这里用了CompletableFuture来异步处理耗时的AI调用避免阻塞Web容器的线程。RestController RequestMapping(/api/chat) Slf4j public class ChatController { Autowired private DeepSeekService deepSeekService; Autowired private RedisTemplateString, Object redisTemplate; private static final String CACHE_PREFIX chat:session:; PostMapping(/completion) public CompletableFutureResponseEntityChatResponse chatCompletion(RequestBody ChatRequest request) { // 1. 参数校验 if (StringUtils.isEmpty(request.getSessionId()) || StringUtils.isEmpty(request.getMessage())) { return CompletableFuture.completedFuture(ResponseEntity.badRequest().build()); } // 2. 异步处理核心逻辑 return CompletableFuture.supplyAsync(() - { try { // 2.1 从Redis获取历史上下文 String cacheKey CACHE_PREFIX request.getSessionId(); ListChatMessage history (ListChatMessage) redisTemplate.opsForValue().get(cacheKey); if (history null) { history new ArrayList(); } // 2.2 构建本次用户消息并加入历史 ChatMessage userMsg new ChatMessage(user, request.getMessage()); history.add(userMsg); // 控制上下文长度防止过长例如只保留最近10轮 if (history.size() 20) { // 10轮对话每轮包含用户和AI两条消息 history history.subList(history.size() - 20, history.size()); } // 2.3 调用DeepSeek服务获取AI回复 String aiResponse deepSeekService.getChatCompletion(history); // 2.4 构建AI消息并加入历史保存回Redis ChatMessage aiMsg new ChatMessage(assistant, aiResponse); history.add(aiMsg); redisTemplate.opsForValue().set(cacheKey, history, Duration.ofMinutes(30)); // 2.5 返回成功响应 ChatResponse response new ChatResponse(aiResponse, request.getSessionId()); return ResponseEntity.ok(response); } catch (ServiceException e) { log.error(Service error during chat completion for session: {}, request.getSessionId(), e); // 返回业务异常信息 return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body(new ChatResponse(客服正在忙碌请稍后再试, request.getSessionId())); } catch (Exception e) { log.error(Unexpected error during chat completion for session: {}, request.getSessionId(), e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body(new ChatResponse(系统开小差了请重试, request.getSessionId())); } }).exceptionally(ex - { // 处理CompletableFuture自身的异常 log.error(Async processing failed for chat request, ex); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body(new ChatResponse(请求处理失败, request.getSessionId())); }); } }2. Redis配置与对话状态管理SpringBoot配置RedisTemplate并封装一个服务类来管理会话。Configuration public class RedisConfig { Bean public RedisTemplateString, Object redisTemplate(RedisConnectionFactory connectionFactory) { RedisTemplateString, Object template new RedisTemplate(); template.setConnectionFactory(connectionFactory); // 使用StringRedisSerializer来序列化和反序列化key template.setKeySerializer(new StringRedisSerializer()); // 使用GenericJackson2JsonRedisSerializer来序列化和反序列化value template.setValueSerializer(new GenericJackson2JsonRedisSerializer()); template.setHashKeySerializer(new StringRedisSerializer()); template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer()); template.afterPropertiesSet(); return template; } } Service public class ChatSessionService { Autowired private RedisTemplateString, Object redisTemplate; private static final String SESSION_KEY_PREFIX chat:session:; public void saveContext(String sessionId, ListChatMessage context) { String key SESSION_KEY_PREFIX sessionId; redisTemplate.opsForValue().set(key, context, Duration.ofMinutes(30)); } public ListChatMessage getContext(String sessionId) { String key SESSION_KEY_PREFIX sessionId; Object context redisTemplate.opsForValue().get(key); if (context instanceof List) { return (ListChatMessage) context; } return new ArrayList(); } public void clearContext(String sessionId) { String key SESSION_KEY_PREFIX sessionId; redisTemplate.delete(key); } }3. 全局异常处理用一个ControllerAdvice来统一处理异常避免把堆栈信息直接抛给前端。ControllerAdvice Slf4j public class GlobalExceptionHandler { ExceptionHandler(ServiceException.class) public ResponseEntityErrorResponse handleServiceException(ServiceException e) { log.warn(Business exception caught: {}, e.getMessage()); ErrorResponse error new ErrorResponse(BUSINESS_ERROR, e.getMessage()); return new ResponseEntity(error, HttpStatus.BAD_REQUEST); } ExceptionHandler(Exception.class) public ResponseEntityErrorResponse handleGenericException(Exception e) { log.error(Unexpected exception caught, e); ErrorResponse error new ErrorResponse(INTERNAL_ERROR, 系统内部错误请稍后重试); return new ResponseEntity(error, HttpStatus.INTERNAL_SERVER_ERROR); } }5. 性能调优实战系统搭起来能跑之后压力测试和调优才是重头戏。5.1 压力测试与瓶颈发现用JMeter模拟了100个用户持续10分钟的发问一开始QPS每秒查询率只有15左右平均响应时间高达2秒。通过监控发现瓶颈一数据库连接。误操作把一些日志写到了MySQL导致数据库连接池很快耗尽。瓶颈二HTTP客户端连接。RestTemplate没有配置连接池每次调用DeepSeek API都新建连接开销巨大。瓶颈三线程阻塞。虽然用了CompletableFuture但默认的ForkJoinPool可能不适合这种IO密集型任务。5.2 优化措施与参数配置使用WebClient并配置连接池替换RestTemplate为WebClient并配置HTTP连接池。# application.yml 配置示例 (需对应代码实现) custom: webclient: max-connections: 1000 # 连接池最大连接数 max-life-time: 60s # 连接最大存活时间在代码中使用ConnectionProvider.builder()来定制连接池。调整异步任务执行器为CompletableFuture.supplyAsync()指定一个自定义的Executor。Configuration public class AsyncConfig { Bean(chatExecutor) public Executor chatTaskExecutor() { int corePoolSize Runtime.getRuntime().availableProcessors() * 2; // CPU核数 * 2 ThreadPoolTaskExecutor executor new ThreadPoolTaskExecutor(); executor.setCorePoolSize(corePoolSize); executor.setMaxPoolSize(corePoolSize * 2); executor.setQueueCapacity(200); executor.setThreadNamePrefix(chat-async-); executor.initialize(); return executor; } }然后在Controller中注入这个Executor并使用CompletableFuture.supplyAsync(() - {...}, chatExecutor)。Redis优化确保Redis部署在低延迟的网络环境并且使用Pipeline批量操作如果涉及多次读写来减少网络往返。经过这几轮优化再次压测QPS提升到了85平均响应时间稳定在900ms左右主要耗时就是DeepSeek API的调用时间我们的服务层开销已经很小了。5.3 模型冷启动预热如果服务重启第一批用户请求会特别慢因为要建立到DeepSeek API的新连接JVM也需要热身。我们的预热方案很简单在服务启动后比如PostConstruct用一个后台线程模拟发送1-2个非常简单的请求例如“你好”到DeepSeek API。这样就把HTTP连接池建立好了JVM也把相关代码路径编译优化了。6. 避坑指南那些容易踩的坑对话超时阈值设置DeepSeek API可能有超时限制比如10秒。我们的服务超时时间要设置得比它短比如8秒并做好超时处理。否则用户会等很久才收到一个超时错误。对于WebSocket还要设置心跳机制防止连接因长时间无数据被中间设备断开。敏感词过滤的误判处理业务上我们可能需要过滤一些违规内容。但直接对AI的回复进行关键词过滤很容易误伤。比如AI说“我们不能提供任何赌博信息”这句话本身是合规的但包含了“赌博”这个词。我们的策略是使用更智能的NLP模型或规则进行语义判断而不是简单关键词匹配。对于疑似内容可以设计一个二次确认或人工审核流程而不是直接拦截。模型版本升级的兼容性方案DeepSeek的模型版本可能会更新。我们的策略是在配置中抽象模型版本号如deepseek.api.model: deepseek-chat。如果API提供了多版本端点可以通过配置切换。在升级前用一批测试用例在预发环境进行回归测试确保回复风格和效果符合预期。考虑灰度发布让一小部分用户流量先切到新版本模型观察效果和指标再全量。写在最后把SpringBoot和DeepSeek结合起来搭建智能客服整个过程更像是一个“系统工程”难点不在于调用一个API而在于如何围绕这个API构建一个稳定、高效、易用的服务。架构设计、异步处理、缓存状态、性能调优每一步都需要仔细考量。目前这个方案基本满足了我们的需求但AI本身的能力天花板决定了客服的上限。接下来我们更关注如何利用DeepSeek进行更深度的定制如何设计高质量的提示词Prompt将我们的产品知识、服务流程更有效地“教”给模型让它回复更专业如果DeepSeek开放了微调功能我们该准备什么样的业务数据是侧重于纠正错误回答还是丰富回答的多样性在多轮对话中如何更精准地判断用户意图是否已改变从而决定是沿用旧上下文还是开启新话题这可能需要结合传统的意图识别模型和AI的上下文理解能力。这些问题没有标准答案需要结合具体业务场景不断摸索。希望这篇笔记能给你带来一些启发也欢迎一起交流在AI应用开发中遇到的那些有趣又有挑战的事儿。