
文墨共鸣大模型Java开发实战构建企业级智能问答系统想象一下这个场景公司新来的同事想了解某个项目的报销流程他不再需要四处打听或翻找陈年的邮件而是直接在一个聊天窗口里提问几秒钟后一份清晰、准确的流程说明就推送到了他面前。这背后就是一个集成在现有企业技术栈里的智能问答系统在默默工作。今天我们就来聊聊如何用大家熟悉的Java技术栈特别是SpringBoot把文墨共鸣这样的强大语言模型“请”进来搭建一个属于自己企业的、高可用的智能问答助手。这不仅能解决内部知识检索效率低下的老问题更能显著提升员工的自助服务能力让信息和知识流动起来。1. 为什么选择Java技术栈集成大模型你可能听过很多用Python快速调用大模型的例子那为什么我们还要用Java来做呢原因很简单稳定、可控、易维护。很多企业的核心后台系统比如ERP、CRM、OA都是用Java写的。在这些系统旁边用同样的技术栈搭建一个AI服务无论是团队技术栈的统一还是后续的运维集成都会顺畅很多。用SpringBoot来搭这个服务有几个实实在在的好处。首先它的生态太成熟了从Web服务、数据库连接到安全认证都有现成的、经过大量生产环境验证的组件。其次它能让我们的服务快速成型把精力更多地放在业务逻辑而不是底层框架的搭建上。最后Java本身在并发处理、内存管理上的稳健性对于需要7x24小时提供服务的问答系统来说是个很大的加分项。所以我们的目标很明确构建一个以SpringBoot为骨架能够稳定、高效地与文墨共鸣大模型对话并能妥善管理对话历史和知识库的Java服务。2. 项目骨架搭建与核心依赖万事开头难我们先从创建一个干净的SpringBoot项目开始。这里假设你使用Maven作为构建工具当然用Gradle也一样。?xml version1.0 encodingUTF-8? project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd modelVersion4.0.0/modelVersion parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.1.5/version !-- 使用较新稳定版本 -- relativePath/ /parent groupIdcom.example/groupId artifactIdenterprise-qa-system/artifactId version1.0.0/version nameenterprise-qa-system/name description基于文墨共鸣大模型的企业智能问答系统/description properties java.version17/java.version /properties dependencies !-- Web服务核心 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- 数据库访问 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-jpa/artifactId /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency !-- HTTP客户端用于调用大模型API -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-webflux/artifactId /dependency !-- 配置管理 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-validation/artifactId /dependency !-- 工具类 -- dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency !-- 测试 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-test/artifactId scopetest/scope /dependency /dependencies build plugins plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId configuration excludes exclude groupIdorg.projectlombok/groupId artifactIdlombok/artifactId /exclude /excludes /configuration /plugin /plugins /build /project这个pom.xml文件把我们需要的基础能力都囊括进来了。spring-boot-starter-web提供REST API能力data-jpa和mysql驱动负责和数据库打交道特别注意的是webflux它不是用来做响应式编程的而是因为它底层的WebClient是一个非阻塞的、功能强大的HTTP客户端非常适合我们去调用外部的大模型API。lombok是个开发利器能帮我们少写很多模板代码。3. 设计核心数据模型与API系统要能工作得先想清楚数据怎么存、接口怎么设计。我们先从最核心的“对话”这个概念开始建模。3.1 对话与消息的实体设计一次完整的问答往往包含多轮对话我们需要记录整个会话的过程。这里设计两个核心的JPA实体。package com.example.enterpriseqasystem.entity; import jakarta.persistence.*; import lombok.Data; import org.hibernate.annotations.CreationTimestamp; import org.hibernate.annotations.UpdateTimestamp; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; Entity Table(name conversation) Data public class Conversation { Id GeneratedValue(strategy GenerationType.IDENTITY) private Long id; Column(nullable false) private String sessionId; // 会话唯一标识可由前端生成或系统生成 Column(length 500) private String title; // 根据首条消息自动生成的会话标题 OneToMany(mappedBy conversation, cascade CascadeType.ALL, orphanRemoval true) OrderBy(createdAt ASC) // 按创建时间顺序排列消息 private ListChatMessage messages new ArrayList(); Column(nullable false) private String userId; // 关联的用户标识 CreationTimestamp private LocalDateTime createdAt; UpdateTimestamp private LocalDateTime updatedAt; // 添加消息的便捷方法 public void addMessage(ChatMessage message) { message.setConversation(this); this.messages.add(message); } }package com.example.enterpriseqasystem.entity; import jakarta.persistence.*; import lombok.Data; import org.hibernate.annotations.CreationTimestamp; import java.time.LocalDateTime; Entity Table(name chat_message) Data public class ChatMessage { Id GeneratedValue(strategy GenerationType.IDENTITY) private Long id; ManyToOne(fetch FetchType.LAZY) JoinColumn(name conversation_id, nullable false) private Conversation conversation; Column(nullable false, length 20) Enumerated(EnumType.STRING) private Role role; // 消息角色用户或助手 Column(nullable false, columnDefinition TEXT) private String content; // 消息内容 CreationTimestamp private LocalDateTime createdAt; // 消息角色枚举 public enum Role { USER, // 用户提问 ASSISTANT // 模型回答 } }这两个实体定义了系统的核心数据。Conversation代表一次完整的对话会话ChatMessage则是会话中的每一条消息。通过OneToMany关联我们能轻松地获取一次会话的所有历史消息这对于实现多轮对话的上下文管理至关重要。3.2 定义清晰的服务接口接下来我们设计对外提供的REST API。一个好的API设计应该直观、符合惯例。package com.example.enterpriseqasystem.dto; import com.example.enterpriseqasystem.entity.ChatMessage; import jakarta.validation.constraints.NotBlank; import lombok.Data; import java.util.List; // 请求用户发送一条新消息 Data public class ChatRequest { NotBlank(message 消息内容不能为空) private String message; private String sessionId; // 如果为空则创建新会话 private String userId; // 当前用户ID } // 响应返回模型的回答和会话信息 Data public class ChatResponse { private String reply; // 模型的回复内容 private String sessionId; // 当前会话ID private String conversationTitle; // 会话标题 } // 请求获取历史会话列表 Data class ConversationQuery { private String userId; private int page 0; private int size 20; } // 响应历史会话详情 Data class ConversationDetail { private Long id; private String title; private LocalDateTime updatedAt; private ListChatMessageDTO messages; // 包含消息列表的DTO }有了这些数据传输对象我们的Controller层就清晰了。package com.example.enterpriseqasystem.controller; import com.example.enterpriseqasystem.dto.*; import com.example.enterpriseqasystem.service.ChatService; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.web.bind.annotation.*; RestController RequestMapping(/api/chat) RequiredArgsConstructor public class ChatController { private final ChatService chatService; // 核心对话接口 PostMapping(/completion) public ChatResponse chat(Valid RequestBody ChatRequest request) { return chatService.processMessage(request); } // 获取用户的历史会话列表 GetMapping(/conversations) public PageConversationDetail listConversations(RequestParam String userId, RequestParam(defaultValue 0) int page, RequestParam(defaultValue 20) int size) { return chatService.getUserConversations(userId, page, size); } // 获取特定会话的详细消息记录 GetMapping(/conversation/{sessionId}) public ConversationDetail getConversationDetail(PathVariable String sessionId) { return chatService.getConversationBySessionId(sessionId); } // 删除某个会话 DeleteMapping(/conversation/{sessionId}) public void deleteConversation(PathVariable String sessionId) { chatService.deleteConversation(sessionId); } }这个控制器提供了完整的对话生命周期管理发起新对话、查看历史、删除会话。接口设计得尽量简单让前端容易调用。4. 实现与大模型的交互服务这是整个系统的“大脑”部分。我们需要一个服务能够将用户的请求结合历史上下文格式化后发送给文墨共鸣大模型的API并处理返回结果。4.1 配置与模型客户端首先我们需要一个配置类来管理模型API的访问信息比如基础地址和API密钥。package com.example.enterpriseqasystem.config; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; Configuration ConfigurationProperties(prefix ai.model) Data public class ModelConfig { private String baseUrl https://api.example-ai.com/v1; // 替换为实际API地址 private String apiKey; private String modelName wenmo-resonance; // 指定使用的模型 private int maxContextLength 4000; // 最大上下文长度字符数 }接着创建一个负责实际HTTP调用的客户端。这里使用Spring的WebClient它比传统的RestTemplate更现代、更灵活。package com.example.enterpriseqasystem.client; import com.example.enterpriseqasystem.config.ModelConfig; import com.example.enterpriseqasystem.dto.ModelApiRequest; import com.example.enterpriseqasystem.dto.ModelApiResponse; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.MediaType; import org.springframework.stereotype.Component; import org.springframework.web.reactive.function.client.WebClient; import reactor.core.publisher.Mono; Slf4j Component RequiredArgsConstructor public class ModelApiClient { private final WebClient.Builder webClientBuilder; private final ModelConfig modelConfig; private final ObjectMapper objectMapper; public MonoModelApiResponse callChatCompletion(ModelApiRequest request) { // 构建WebClient实例添加认证头 WebClient client webClientBuilder .baseUrl(modelConfig.getBaseUrl()) .defaultHeader(Authorization, Bearer modelConfig.getApiKey()) .build(); return client.post() .uri(/chat/completions) .contentType(MediaType.APPLICATION_JSON) .bodyValue(request) .retrieve() .bodyToMono(String.class) // 先以字符串形式接收 .map(responseBody - { try { log.debug(收到模型API原始响应: {}, responseBody); return objectMapper.readValue(responseBody, ModelApiResponse.class); } catch (Exception e) { log.error(解析模型API响应失败, e); throw new RuntimeException(解析模型响应时出错, e); } }) .onErrorResume(e - { log.error(调用模型API失败, e); return Mono.error(new RuntimeException(与大模型服务通信时发生错误, e)); }); } }4.2 构建请求与处理上下文大模型API通常需要特定格式的请求。我们需要将数据库中的历史消息和当前用户问题转换成模型能理解的格式。package com.example.enterpriseqasystem.dto; import lombok.Data; import java.util.ArrayList; import java.util.List; // 适配大模型API的请求格式 Data public class ModelApiRequest { private String model; private ListMessage messages; private double temperature 0.7; // 控制创造性的参数 private int max_tokens 2000; // 生成的最大长度 Data public static class Message { private String role; // user 或 assistant private String content; public Message(String role, String content) { this.role role; this.content content; } } }最关键的部分来了如何管理多轮对话的上下文我们不能无限制地把所有历史消息都塞给模型一方面有长度限制另一方面也可能影响性能和成本。我们需要一个“上下文管理器”。package com.example.enterpriseqasystem.service; import com.example.enterpriseqasystem.entity.ChatMessage; import com.example.enterpriseqasystem.entity.Conversation; import com.example.enterpriseqasystem.dto.ModelApiRequest; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.List; Slf4j Component RequiredArgsConstructor public class ContextManager { private final ModelConfig modelConfig; /** * 从完整的对话历史中智能截取最相关的部分作为上下文。 * 策略优先保留最近的对话同时尽量保证上下文不超过长度限制。 */ public ListModelApiRequest.Message buildContextFromHistory(Conversation conversation, String newUserMessage) { ListChatMessage allMessages conversation.getMessages(); ListModelApiRequest.Message contextMessages new ArrayList(); // 1. 始终加入最新的用户问题 contextMessages.add(new ModelApiRequest.Message(user, newUserMessage)); // 2. 从后往前遍历历史消息直到达到上下文长度限制 int currentLength newUserMessage.length(); for (int i allMessages.size() - 1; i 0; i--) { ChatMessage historyMsg allMessages.get(i); String role historyMsg.getRole() ChatMessage.Role.USER ? user : assistant; String content historyMsg.getContent(); // 检查加入这条历史消息后是否会超长 if (currentLength content.length() modelConfig.getMaxContextLength()) { log.debug(上下文长度已达限制将停止添加更早的历史消息。); break; // 停止添加 } // 将历史消息插入到列表头部因为我们是倒序遍历 contextMessages.add(0, new ModelApiRequest.Message(role, content)); currentLength content.length(); } log.info(为会话 {} 构建了包含 {} 条消息的上下文。, conversation.getSessionId(), contextMessages.size()); return contextMessages; } }这个上下文管理器采用了一个简单的策略从最新的消息开始逆序添加历史消息直到总长度接近预设的上限。这样可以确保模型总是能看到最近、最相关的对话历史。在实际生产中你可能需要更复杂的策略比如基于语义重要性进行筛选但对于大多数企业内部问答场景这个策略已经足够有效。5. 串联一切核心业务服务现在我们把数据层、模型客户端和上下文管理器组合起来实现最核心的聊天服务。package com.example.enterpriseqasystem.service; import com.example.enterpriseqasystem.client.ModelApiClient; import com.example.enterpriseqasystem.dto.*; import com.example.enterpriseqasystem.entity.ChatMessage; import com.example.enterpriseqasystem.entity.Conversation; import com.example.enterpriseqasystem.repository.ConversationRepository; import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Service; import java.util.Optional; import java.util.UUID; Slf4j Service RequiredArgsConstructor public class ChatService { private final ConversationRepository conversationRepository; private final ModelApiClient modelApiClient; private final ContextManager contextManager; private final ModelConfig modelConfig; Transactional public ChatResponse processMessage(ChatRequest request) { // 1. 获取或创建会话 Conversation conversation getOrCreateConversation(request.getSessionId(), request.getUserId()); // 2. 保存用户的新消息到数据库 ChatMessage userMessage new ChatMessage(); userMessage.setRole(ChatMessage.Role.USER); userMessage.setContent(request.getMessage()); conversation.addMessage(userMessage); // 如果是新会话根据第一条消息生成一个简单标题 if (conversation.getTitle() null) { conversation.setTitle(generateConversationTitle(request.getMessage())); } // 3. 构建上下文并调用大模型 ListModelApiRequest.Message context contextManager.buildContextFromHistory(conversation, request.getMessage()); ModelApiRequest apiRequest new ModelApiRequest(); apiRequest.setModel(modelConfig.getModelName()); apiRequest.setMessages(context); // 4. 调用模型API这里为了简化使用block()同步等待生产环境可考虑异步处理 ModelApiResponse apiResponse modelApiClient.callChatCompletion(apiRequest).block(); if (apiResponse null || apiResponse.getChoices() null || apiResponse.getChoices().isEmpty()) { throw new RuntimeException(未能从大模型获得有效响应); } String modelReply apiResponse.getChoices().get(0).getMessage().getContent(); // 5. 保存模型的回复到数据库 ChatMessage assistantMessage new ChatMessage(); assistantMessage.setRole(ChatMessage.Role.ASSISTANT); assistantMessage.setContent(modelReply); conversation.addMessage(assistantMessage); // 6. 保存整个会话级联保存所有消息 conversationRepository.save(conversation); // 7. 构造返回结果 ChatResponse response new ChatResponse(); response.setReply(modelReply); response.setSessionId(conversation.getSessionId()); response.setConversationTitle(conversation.getTitle()); log.info(成功处理用户提问会话ID: {}, 用户: {}, conversation.getSessionId(), request.getUserId()); return response; } private Conversation getOrCreateConversation(String sessionId, String userId) { if (sessionId ! null !sessionId.isBlank()) { OptionalConversation existing conversationRepository.findBySessionId(sessionId); if (existing.isPresent()) { return existing.get(); } // 如果提供的sessionId不存在则记录警告但仍创建新会话 log.warn(提供的sessionId: {} 未找到将创建新会话。, sessionId); } // 创建新会话 Conversation newConversation new Conversation(); newConversation.setSessionId(UUID.randomUUID().toString()); newConversation.setUserId(userId); return conversationRepository.save(newConversation); } private String generateConversationTitle(String firstMessage) { // 简单逻辑截取第一句话的前30个字符作为标题 if (firstMessage.length() 30) { return firstMessage.substring(0, 30) ...; } return firstMessage; } // 其他方法获取用户会话列表、获取会话详情、删除会话等... public PageConversationDetail getUserConversations(String userId, int page, int size) { PageRequest pageRequest PageRequest.of(page, size); PageConversation conversations conversationRepository.findByUserIdOrderByUpdatedAtDesc(userId, pageRequest); return conversations.map(this::convertToDetail); } private ConversationDetail convertToDetail(Conversation conversation) { // 转换实体为DTO... } }这个ChatService是系统运转的核心枢纽。它协调了数据持久化、上下文构建、模型调用和响应返回的全流程。注意Transactional注解它保证了在一个数据库事务内完成用户消息和助手消息的保存确保数据的一致性。6. 让系统更智能知识库增强与优化一个只会泛泛而谈的问答系统在企业内部是远远不够的。员工需要的是基于公司内部文档、规章制度、项目资料的确切答案。这就需要引入“知识库增强”的概念。一个常见的做法是使用“检索增强生成”RAG模式。简单说就是当用户提问时系统先从一个向量知识库中检索出最相关的几段内部资料然后把这些资料作为上下文连同问题一起交给大模型让模型基于这些确切的资料来生成答案。这里给出一个简化的实现思路// 伪代码展示RAG核心思路 Service public class EnhancedChatService { Autowired private KnowledgeBaseRetriever retriever; // 知识库检索器 public ChatResponse processWithKnowledge(ChatRequest request) { // 1. 从知识库检索相关文档片段 ListDocumentChunk relevantChunks retriever.retrieve(request.getMessage(), topK: 3); // 2. 将检索到的知识拼接到用户问题前形成增强的提示 StringBuilder enhancedPrompt new StringBuilder(请根据以下信息回答问题\n\n); for (DocumentChunk chunk : relevantChunks) { enhancedPrompt.append(---\n).append(chunk.getContent()).append(\n); } enhancedPrompt.append(\n问题).append(request.getMessage()); // 3. 使用增强后的问题调用模型 // ... 后续流程与普通对话类似 } }要实现这个你需要一个向量数据库如Milvus, Weaviate来存储公司文档的向量嵌入并实现一个检索服务。这虽然增加了系统的复杂性但能极大提升回答的准确性和专业性是构建真正有用的企业问答系统的关键一步。7. 总结与展望走完这一趟我们从零开始用SpringBoot搭建了一个能够集成文墨共鸣大模型的企业级智能问答系统后端。我们设计了清晰的数据模型来管理对话实现了稳健的REST API构建了智能的上下文管理机制来支持多轮对话并且探讨了如何通过知识库增强让系统变得更“懂行”。实际用下来这套基于Java技术栈的方案在稳定性和与现有系统集成方面确实有它的优势。部署和维护对于Java团队来说也是轻车熟路。当然这只是个起点。要让它真正在生产环境发挥价值还有很多事情可以做比如加入更精细的权限控制不同部门看到不同的知识、实现回答准确性的反馈与模型优化闭环、以及对系统性能和成本进行监控。如果你正在为企业寻找提升信息效率的方案不妨从这样一个可掌控、可扩展的原型开始尝试。先在一个小范围团队内试用收集反馈然后逐步迭代和完善。技术终究是工具解决实际业务问题、提升人的效率才是我们构建它的最终目的。获取更多AI镜像想探索更多AI镜像和应用场景访问 CSDN星图镜像广场提供丰富的预置镜像覆盖大模型推理、图像生成、视频生成、模型微调等多个领域支持一键部署。