Spring AI智能客服实战:从零构建高可用对话系统

📅 发布时间:2026/7/5 20:53:37 👁️ 浏览次数:
Spring AI智能客服实战:从零构建高可用对话系统
背景痛点传统客服系统到底卡在哪过去三年我先后接手过两套“祖传”客服系统一套基于关键字匹配一套在 Dialogflow 上做了二次封装。上线后问题高度雷同意图识别准确率低于 75%用户换种问法就“答非所问”。多轮对话靠 session 里硬编码字段维护一旦分布式部署状态说丢就丢。高峰期并发突增系统直接 502扩容后 CPU 打满QPS 仍卡在 120 左右。核心矛盾是“黑盒”NLU 与“白盒”业务耦合难改一句话术就要重新训练模型迭代周期按周计算。于是我们把目光投向了 Spring AI——一个能把提示词、检索、微调都当成普通 Bean 管理的框架。技术对比为什么最终选了 Spring AI维度Dialogflow ESRasa 3.xSpring AI托管方式全托管自部署自部署中文微调不支持直接微调支持但需写 pipeline 脚本直接调用本地 LLM可微调上下文保持依赖 Context 生命周期跨节点失效Tracker Store 需自己配 Redis内置 ChatMemory可插 Redis与 Java 集成gRPC/SDK模型黑盒HTTP序列化麻烦原生 Starter零样板代码成本按次计费量大后价格翻倍免费但 GPU 推理机自己扛免费GPU 机可弹性伸缩一句话总结Spring AI 把“提示词即代码”带进 Java 世界让我们用熟悉的事务、缓存、线程池就能治理 LLM而不再被“黑盒”卡脖子。核心实现三步搭出对话引擎1. 引入依赖与自动配置dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-openai-spring-boot-starter/artifactId version1.0.0-SNAPSHOT/version /dependencyapplication.yml里把spring.ai.openai.api-key换成自己网关转发的 key即可注入ChatClient。2. 构建带 RAG 的 ChatClientConfiguration public class AiConfig { Bean public ChatClient ragClient(ChatClient.Builder builder, EmbeddingModel embModel, VectorStore vectorStore) { // 1. 把产品手册灌进向量库 vectorStore.add( new Document(产品A, 7 天无理由退货, Map.of(sku, A)) ); // 2. 返回带检索增强的 ChatClient return builder .defaultAdvisors( new RetrievalAugmentationAdvisor(vectorStore, embModel)) .build(); } }3. 多轮上下文与重试Component public class ChatService { private final ChatMemoryRepository memoryRepo; // Redis 实现 Retryable(value { RemoteException.class maxAttempts 3, backoff Backoff(500)) public String talk(String userId, String prompt) { ChatMemory memory memoryRepo.get(userId); String answer ragClient.prompt() .user(prompt) .advisors(a - a.param(memory, memory)) .call() .content(); memory.add(UserMessage.of(prompt), AssistantMessage.of(answer)); memoryRepo.save(userId, memory); return answer; } }Retryable直接加在业务方法比自己去写try/catch简洁得多远程超时、429 场景都能覆盖。代码示例Controller 层完整片段RestController RequestMapping(/api/v1/bot) RequiredArgsConstructor public class BotController { private final ChatService chatService; private final JwtValidator jwtValidator; PostMapping(value /chat, produces MediaType.TEXT_EVENT_STREAM_VALUE) public FluxString chat(RequestHeader(Authorization) String bearer, RequestBody ChatReq req) { // 1. 鉴权 String userId jwtValidator.parse(bearer); // 2. 流式返回前端打字机效果 return Flux.fromStream( () - new BufferedReader(new StringReader( chatService.talk(userId, req.getPrompt()))) .lines()) .delayElements(Duration.ofMillis(30)); } ExceptionHandler(RemoteException.class) public ResponseEntityErrorBody handleRemote() { return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE) .body(new ErrorBody(AI 服务繁忙请稍后)); } }Redis 配置片段 Lettuce 连接池spring: data: redis: host: redis-cluster port: 6379 lettuce: pool: max-active: 200 max-idle: 100 min-idle: 20性能优化线程池与 QPS 压测把默认的SimpleAsyncTaskExecutor换成自定义线程池Bean public TaskExecutor aiExecutor() { ThreadPoolTaskExecutor exec new ThreadPoolTaskExecutor(); exec.setCorePoolSize(32); exec.setMaxPoolSize(64); exec.setQueueCapacity(200); exec.setThreadNamePrefix(ai-); exec.initialize(); return exec; }压测数据对比8C16G单实例OpenAI 代理延迟 250 ms线程池策略平均 RT99 线QPSCPU 占用默认无池化1.2 s2.5 s18090%自定义池0.35 s0.6 s52065%结论池化后 RT 下降 70%QPS 提升近 3 倍CPU 反而更闲。避坑指南上线前必须踩的坑1. 对话状态丢失把ChatMemory序列化成 JSON 存 Redis并加RedisHashTTL(hours 24)。发布消息时监听CacheExpireEvent把过期 key 同步到 DB可做离线质检。2. 敏感词过滤用 AOP 拦截talk()方法O(1) 匹配 DFA 词表Around(annotation(PublicApi)) public Object filter(ProceedingJoinPoint pjp) throws Throwable { Object[] args pjp.getArgs(); String prompt (String) args[1]; if (SensitiveDFA.match(prompt)) { return 抱歉无法回答该问题; } return pjp.proceed(); }3. 冷启动性能预加载EmbeddingModel到内存关闭spring.ai.openai.embedding.lazy-inittrue。向量索引用 Faiss IVF-Flat训练数据 10 w 条nlist4096查询 nprobe32召回 95%耗时 12 ms。延伸思考让 LLM 直接做意图识别目前 NLU 仍用微调的 BERT召回 92%。如果把用户问题直接丢给 LLM让其在 prompt 里输出 JSON 意图再交给下游流程是否可行优点无需单独训练话术变更只需改提示词。风险LLM 输出不稳定格式错误率 3% 左右。折中用“LLM 意图 规则兜底”双通道线上 A/B 显示 LLM 通道准确率 96%RT 增加 80 ms可接受。下一步我们准备把意图识别、槽位抽取、答案生成三段全部用 Spring AI 的PromptTemplate串联实现“一条链路透传”把迭代周期从周缩短到小时。踩坑三个月最大感受是别把 LLM 当黑盒也别把 Spring AI 当玩具。只要按 Java 习惯把它拆成 Bean、线程池、缓存、重试这些老伙伴高并发、高可用的智能客服其实没那么玄乎。希望这份笔记能帮你少熬几个通宵早日让 AI 把键盘声从客服大厅里“消音”。祝编码顺利出错时记得先打日志再问 GPT。