墨语灵犀Java集成实战:构建企业级智能问答微服务

张开发
2026/4/6 11:20:19 15 分钟阅读

分享文章

墨语灵犀Java集成实战:构建企业级智能问答微服务
墨语灵犀Java集成实战构建企业级智能问答微服务最近和几个做企业服务的朋友聊天他们都在头疼同一个问题客户咨询量越来越大人工客服根本忙不过来招人成本又高。他们试过一些现成的SaaS客服机器人但要么不够智能答非所问要么无法对接内部知识库解决不了专业问题。这让我想起了之前用墨语灵犀大模型做的一个项目。当时我们把它集成到了Java技术栈里做了一个智能问答微服务效果还不错。今天我就把这个从零到一的实战过程分享出来聊聊怎么用SpringBoot把大模型能力“装”进你的企业系统里让它真正帮你干活。1. 为什么要在Java里集成大模型你可能觉得大模型调用不就是发个HTTP请求吗用Python写个脚本不就行了确实对于个人玩玩或者小工具Python很方便。但一旦涉及到企业级应用事情就复杂了。想象一下你的客服系统每天要处理几万甚至几十万次咨询。你需要考虑服务挂了怎么办用户等太久怎么办对话记录丢了怎么办怎么知道机器人回答得对不对这些问题一个简单的脚本是搞不定的。而Java生态特别是SpringBoot这一套恰恰擅长解决这些问题。它有成熟的微服务框架、完善的连接池管理、强大的事务控制还有各种监控和熔断机制。把墨语灵犀的能力通过Java封装起来你得到的不是一个玩具而是一个能扛住真实业务流量的生产级服务。我们当时做的这个微服务主要解决了两个核心场景企业内部知识库查询员工不用再去翻几百页的PDF手册或者满世界找文档直接问机器人就行。比如“我们公司最新的差旅报销标准是什么”或者“项目A的部署流程有哪些步骤”智能客服系统处理外部客户的高频、标准化问题比如“怎么重置密码”、“产品B有哪些功能”把人工客服解放出来去处理更复杂的问题。接下来我就带你一步步看看这个服务是怎么搭起来的。2. 整体架构设计不只是调个API接到需求第一反应可能就是写个Controller里面调用墨语灵犀的API然后把结果返回。这确实能跑起来但离“企业级”还差得远。一个健壮的服务需要考虑更多。我们的核心设计思路是异步、缓存、可观测、易扩展。下面这张图展示了服务的基本架构[客户端] -- [API网关] -- [智能问答微服务] | v [SpringBoot应用] | ----------------------------------- | | | v v v [异步任务执行器] [本地结果缓存] [对话历史管理器] | | | v v v [墨语灵犀API客户端] [Redis] [MySQL]我来解释一下几个关键部分SpringBoot应用这是主体用大家最熟悉的框架快速搭建RESTful API。异步任务执行器这是性能的关键。大模型生成回答需要时间可能几秒甚至十几秒。如果让HTTP请求线程一直等着服务器线程很快就会被占满导致服务瘫痪。我们用了Async注解和线程池把耗时的模型调用丢到后台去执行立即给客户端返回一个任务ID。客户端可以轮询或者我们通过WebSocket推送结果。本地结果缓存很多用户问的问题是相似甚至重复的比如“上班时间”。我们引入了两级缓存本地缓存Caffeine用于缓存极短时间比如1分钟内的相同问题应对瞬时高并发。分布式缓存Redis缓存更长时间比如1小时的通用问答结果所有服务实例共享减少对模型的重复调用显著降低成本、提升响应速度。对话历史管理器智能问答不是一次性买卖需要有上下文。我们把每次对话的上下文用户ID 会话ID 问答对存到MySQL里。下次用户再问时能自动带上最近的几条历史记录让模型知道“我们刚才在聊什么”回答更连贯。墨语灵犀API客户端这里封装了所有与墨语灵犀服务通信的细节包括认证、重试、超时和异常处理。这个架构看起来比直接调用API复杂但它带来的好处是实实在在的响应更快、更省钱、更稳定、体验更好。3. 核心代码实现一步步拆解光说架构有点虚我们直接看代码。我会挑几个最核心的部分用尽量简单的例子说明白。3.1 第一步封装模型调用客户端首先我们得创建一个可靠的客户端来和墨语灵犀对话。这里用了Spring的RestTemplate并配置了连接池和超时。Component public class MoyuLingxiClient { Value(${moyu.lingxi.api-key}) private String apiKey; Value(${moyu.lingxi.endpoint}) private String endpoint; private final RestTemplate restTemplate; public MoyuLingxiClient(RestTemplateBuilder builder) { // 配置连接池和超时防止慢请求拖死服务 this.restTemplate builder .setConnectTimeout(Duration.ofSeconds(10)) .setReadTimeout(Duration.ofSeconds(60)) // 生成文本需要较长时间 .build(); } public String generateReply(String prompt, ListChatMessage history) { // 1. 构建请求体包含提示词和历史对话 MapString, Object requestBody new HashMap(); requestBody.put(model, moonshot-v1); requestBody.put(messages, buildMessages(prompt, history)); requestBody.put(temperature, 0.7); // 控制创造性 requestBody.put(max_tokens, 1000); // 2. 设置认证头 HttpHeaders headers new HttpHeaders(); headers.set(Authorization, Bearer apiKey); headers.setContentType(MediaType.APPLICATION_JSON); HttpEntityMapString, Object request new HttpEntity(requestBody, headers); try { // 3. 发送请求 ResponseEntityMap response restTemplate.postForEntity(endpoint, request, Map.class); // 4. 解析响应提取生成的文本 MapString, Object responseBody response.getBody(); if (responseBody ! null responseBody.containsKey(choices)) { ListMapString, Object choices (ListMapString, Object) responseBody.get(choices); if (!choices.isEmpty()) { MapString, Object message (MapString, Object) choices.get(0).get(message); return (String) message.get(content); } } throw new RuntimeException(Failed to get valid response from model); } catch (ResourceAccessException e) { // 处理超时或网络异常 throw new ServiceUnavailableException(Model service timeout, please try again later., e); } } private ListMapString, String buildMessages(String prompt, ListChatMessage history) { // 将历史对话和当前问题组合成模型需要的消息格式 ListMapString, String messages new ArrayList(); for (ChatMessage msg : history) { messages.add(Map.of(role, msg.getRole(), content, msg.getContent())); } messages.add(Map.of(role, user, content, prompt)); return messages; } }这个客户端类做了几件重要的事管理配置、处理认证、构建请求、解析响应并特别处理了网络超时异常。这是所有后续功能的基础。3.2 第二步实现异步调用与缓存直接同步调用用户界面会一直转圈直到超时。体验很差。我们改成异步的。Service public class AsyncQAService { Autowired private MoyuLingxiClient lingxiClient; Autowired private CacheManager cacheManager; // 注入一个线程池来执行异步任务 Async(taskExecutor) public CompletableFutureString getAsyncAnswer(String sessionId, String question, ListChatMessage history) { // 1. 先查缓存以“问题内容”的MD5值为Key String cacheKey DigestUtils.md5DigestAsHex(question.getBytes()); Cache cache cacheManager.getCache(qaCache); Cache.ValueWrapper cachedAnswer cache.get(cacheKey); if (cachedAnswer ! null) { return CompletableFuture.completedFuture(【来自缓存】 cachedAnswer.get()); } // 2. 缓存没有则调用大模型 String answer lingxiClient.generateReply(question, history); // 3. 将结果放入缓存有效期1小时 cache.put(cacheKey, answer); // 4. 异步返回结果 return CompletableFuture.completedFuture(answer); } }对应的配置类我们需要定义一个线程池Configuration EnableAsync public class AsyncConfig { Bean(taskExecutor) public TaskExecutor taskExecutor() { ThreadPoolTaskExecutor executor new ThreadPoolTaskExecutor(); executor.setCorePoolSize(5); // 核心线程数 executor.setMaxPoolSize(20); // 最大线程数 executor.setQueueCapacity(100); // 队列容量 executor.setThreadNamePrefix(moyu-async-); executor.initialize(); return executor; } }这样当用户提问时Controller会立即返回一个taskId前端可以轮询/task/{taskId}/status这个接口来获取任务状态和最终结果。对于实时性要求高的场景还可以集成WebSocket服务端生成完答案后主动推送给客户端。3.3 第三步集成MySQL管理对话历史为了让机器人有“记忆”我们需要保存对话。设计一个简单的表结构CREATE TABLE chat_history ( id BIGINT PRIMARY KEY AUTO_INCREMENT, session_id VARCHAR(64) NOT NULL, -- 会话ID标识一次完整对话 user_id VARCHAR(64), -- 用户ID role VARCHAR(16) NOT NULL, -- user 或 assistant content TEXT NOT NULL, -- 消息内容 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, INDEX idx_session (session_id), INDEX idx_user_session (user_id, session_id) );然后用Spring Data JPA来操作它Entity Table(name chat_history) Data public class ChatHistory { Id GeneratedValue(strategy GenerationType.IDENTITY) private Long id; private String sessionId; private String userId; private String role; // user, assistant Column(columnDefinition TEXT) private String content; private LocalDateTime createdAt; } Repository public interface ChatHistoryRepository extends JpaRepositoryChatHistory, Long { ListChatHistory findBySessionIdOrderByCreatedAtAsc(String sessionId); ListChatHistory findBySessionIdAndRoleOrderByCreatedAtAsc(String sessionId, String role); }在服务层我们会在调用模型前取出最近N条历史记录拼接到提示词里在得到模型回复后将本轮的一问一答都保存到数据库。Service public class ChatService { Autowired private ChatHistoryRepository historyRepo; public ListChatMessage getRecentHistory(String sessionId, int maxCount) { ListChatHistory entities historyRepo.findBySessionIdOrderByCreatedAtAsc(sessionId); // 取最后 maxCount 条 ListChatHistory recent entities.size() maxCount ? entities.subList(entities.size() - maxCount, entities.size()) : entities; // 转换为服务内部使用的DTO对象 return recent.stream() .map(e - new ChatMessage(e.getRole(), e.getContent())) .collect(Collectors.toList()); } public void saveInteraction(String sessionId, String userId, String question, String answer) { ChatHistory userMsg new ChatHistory(); userMsg.setSessionId(sessionId); userMsg.setUserId(userId); userMsg.setRole(user); userMsg.setContent(question); historyRepo.save(userMsg); ChatHistory assistantMsg new ChatHistory(); assistantMsg.setSessionId(sessionId); assistantMsg.setUserId(userId); assistantMsg.setRole(assistant); assistantMsg.setContent(answer); historyRepo.save(assistantMsg); } }4. 实际效果与踩坑经验这套系统上线后效果是立竿见影的。在客服场景它能自动处理超过60%的常见咨询比如查询订单状态、了解产品功能、重置密码等人工客服只需要处理那些复杂的、需要情感沟通或特殊权限的问题。响应时间从原来人工平均的2分钟缩短到机器人秒级响应。成本方面虽然调用大模型有费用但相比新增客服人力成本还是节省了很多。当然过程中也踩了不少坑这里分享两个最重要的第一个坑提示词Prompt工程。最开始机器人回答虽然通顺但经常不准确或者会“胡编乱造”内部没有的知识。后来我们改进了提示词在每次提问前都先让模型“扮演”一个专业的客服助手并明确告知它的知识边界。例如在提示词开头固定加上“你是XX公司的智能客服助手你的知识截止于2023年12月主要基于公司内部知识库回答问题。对于你不知道或不确定的信息请明确告知用户‘我暂时无法回答这个问题建议您联系人工客服’。” 这个简单的调整让回答的准确性和安全性大幅提升。第二个坑缓存策略。一开始我们所有问题都缓存1小时结果发现当公司政策临时变更时机器人还在给用户旧的答案造成了客诉。后来我们调整了策略对于事实性、政策性的问答如“年假有多少天”缓存时间较短如10分钟并设计了缓存刷新机制对于通用性、解释性的问答如“什么是云计算”缓存时间可以较长。同时后台增加了手动清理缓存的功能。5. 总结回过头看用Java集成墨语灵犀来构建企业级服务核心思想不是追求最前沿的模型技术而是用成熟的软件工程方法让AI能力变得可靠、可用、可管理。异步化让服务不被慢请求拖垮缓存显著降低了成本和延迟历史记录管理让对话有了连续性而这一切都建立在SpringBoot这个稳固的生态之上。这套方案可能不是性能极限最高的但肯定是团队最容易维护、最容易扩展的。如果你也在考虑把大模型引入到自己的Java项目中我的建议是先从一个小而具体的场景开始比如一个内部知识查询工具。按照这个架构把路跑通把上面提到的坑都踩一遍。等这个服务稳定了你再把它扩展到客服、内容生成、数据分析等其他场景就会顺利很多。技术本身不难难的是让它贴合业务稳定地创造价值。获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。

更多文章