SpringBoot整合豆包大模型SDK实战5分钟搞定知识库问答系统附完整代码最近在帮几个初创团队搭建内部知识库问答系统时我发现很多Java开发者对如何快速、优雅地接入大模型能力感到头疼。市面上教程要么过于理论化要么代码臃肿难以维护。实际上借助SpringBoot的自动配置特性和豆包大模型提供的Java SDK我们完全可以在极短时间内构建一个生产可用的智能问答服务。这篇文章我将从一个实战派开发者的角度带你走通从零到一的全过程不仅提供可直接运行的代码更会分享我在实际项目中踩过的坑和优化技巧。1. 环境准备与项目初始化在开始编码之前我们需要确保开发环境就绪。我推荐使用Java 17或更高版本这是目前大多数云服务商对现代Java应用的基础要求。SpringBoot 3.x系列对Java 17有更好的支持包括新的HTTP客户端和更简洁的配置方式。首先通过Spring Initializr快速生成项目骨架。我习惯使用命令行工具但IDE的集成向导同样方便。curl https://start.spring.io/starter.zip \ -d typemaven-project \ -d languagejava \ -d bootVersion3.2.0 \ -d baseDirknowledge-qa-demo \ -d groupIdcom.example \ -d artifactIdknowledge-qa-demo \ -d nameknowledge-qa-demo \ -d descriptionDemo project for Knowledge Base QA \ -d packageNamecom.example.knowledgeqa \ -d packagingjar \ -d javaVersion17 \ -d dependenciesweb,configuration-processor,lombok \ -o knowledge-qa-demo.zip解压后你会得到一个标准的SpringBoot项目结构。接下来我们需要在pom.xml中添加豆包大模型SDK的依赖。这里有个细节需要注意官方SDK可能会频繁更新在生产环境中我建议锁定一个稳定版本而不是使用LATEST标签。dependency groupIdcom.volcengine/groupId artifactIdvolcengine-java-sdk-ark-runtime/artifactId version1.0.5/version /dependency dependency groupIdcom.volcengine/groupId artifactIdvolc-sdk-java/artifactId version1.0.210/version /dependency提示版本号请根据官方文档的最新推荐进行调整。我遇到过因为SDK版本不匹配导致的签名错误所以建议在项目初期就确定好版本并记录在案。除了核心SDK我们还需要一些辅助依赖来简化开发。Jackson用于JSON处理Apache HttpClient用于HTTP通信——虽然SDK内部可能已经包含但显式声明可以避免版本冲突。dependency groupIdcom.fasterxml.jackson.core/groupId artifactIdjackson-databind/artifactId version2.15.2/version /dependency dependency groupIdorg.apache.httpcomponents/groupId artifactIdhttpclient/artifactId version4.5.14/version /dependency环境变量和配置管理是另一个容易出问题的地方。我习惯将敏感信息如AK/SK放在环境变量中而不是硬编码在配置文件里。在application.yml中我们可以这样配置doubao: access: ak: ${DOUBAO_AK:} sk: ${DOUBAO_SK:} knowledge-base: host: api-knowledgebase.mlp.cn-beijing.volces.com collection-name: ${KNOWLEDGE_COLLECTION:default-collection} project: default model: Doubao-pro-32k model-version: 241215对应的配置类如下使用ConfigurationProperties可以享受到SpringBoot的类型安全配置绑定Data Configuration ConfigurationProperties(prefix doubao) public class DoubaoConfig { private Access access; private KnowledgeBase knowledgeBase; Data public static class Access { private String ak; private String sk; } Data public static class KnowledgeBase { private String host; private String collectionName; private String project; private String model; private String modelVersion; } }2. 核心服务层设计与实现服务层的设计直接决定了后续代码的扩展性和可维护性。我建议采用分层架构将知识库检索、提示词构建、大模型调用等关注点分离。首先我们定义一个统一的响应包装类这在微服务架构中特别有用。Data Builder NoArgsConstructor AllArgsConstructor public class ApiResponseT { private Integer code; private String message; private T data; private Long timestamp; public static T ApiResponseT success(T data) { return ApiResponse.Tbuilder() .code(200) .message(success) .data(data) .timestamp(System.currentTimeMillis()) .build(); } }接下来是知识库检索服务。这里我封装了一个KnowledgeSearchService负责与豆包知识库API交互。关键点在于请求参数的灵活配置——不是所有参数都需要每次传递。Service Slf4j public class KnowledgeSearchService { Autowired private DoubaoConfig doubaoConfig; Autowired private ObjectMapper objectMapper; public SearchResult search(String query, SearchOptions options) { SearchKnowledgeRequest request buildSearchRequest(query, options); try { String requestJson objectMapper.writeValueAsString(request); SignableRequest signedRequest prepareSignedRequest( doubaoConfig.getKnowledgeBase().getHost(), /api/knowledge/collection/search_knowledge, POST, requestJson ); String responseBody executeRequest(signedRequest); BaseResponseCollectionSearchKnowledgeResponseData response objectMapper.readValue(responseBody, new TypeReferenceBaseResponseCollectionSearchKnowledgeResponseData() {}); if (response.getCode() ! 0) { log.error(知识库检索失败: code{}, message{}, response.getCode(), response.getMessage()); throw new ServiceException(知识库检索异常: response.getMessage()); } return convertToSearchResult(response.getData()); } catch (Exception e) { log.error(知识库检索异常, e); throw new ServiceException(检索服务暂时不可用, e); } } private SearchKnowledgeRequest buildSearchRequest(String query, SearchOptions options) { SearchKnowledgeRequest request new SearchKnowledgeRequest(); request.setQuery(query); request.setName(doubaoConfig.getKnowledgeBase().getCollectionName()); request.setProject(doubaoConfig.getKnowledgeBase().getProject()); request.setLimit(options.getLimit() ! null ? options.getLimit() : 10); // 高级检索参数配置 if (options.isEnableRerank()) { PostProcessing postProcessing new PostProcessing(); postProcessing.setRerankSwitch(true); postProcessing.setRerankModel(bge-reranker-v2); postProcessing.setRetrieveCount(25); request.setPostProcessing(postProcessing); } return request; } }注意在实际生产环境中建议对HTTP请求设置合理的超时时间。知识库检索可能涉及大量文档处理超时设置过短会导致频繁失败。检索结果的处理也需要精心设计。豆包API返回的数据结构比较丰富我们需要从中提取最有价值的信息Data public class SearchResult { private String rewriteQuery; // 改写后的问题 private ListKnowledgeChunk chunks; // 知识片段 private TokenUsage tokenUsage; // token消耗统计 Data public static class KnowledgeChunk { private String id; private String content; private Double relevanceScore; private String docName; private String chunkTitle; private ListString attachments; // 附件链接 } Data public static class TokenUsage { private Integer embeddingTokens; private Integer rerankTokens; private Integer totalTokens; } }3. 提示词工程与RAG流程优化提示词的质量直接决定了大模型回答的准确性和相关性。很多开发者直接把检索到的内容扔给模型结果往往不尽如人意。经过多次实验我总结出了一套有效的提示词模板构建方法。首先我们需要根据不同的业务场景设计不同的系统提示词。比如客服场景、技术文档场景、产品介绍场景对回答的风格和格式要求都不同。这里我实现了一个PromptTemplateManager来管理多种模板Component public class PromptTemplateManager { private final MapString, String templates new HashMap(); public PromptTemplateManager() { // 客服场景模板 templates.put(customer_service, # 角色设定 你是一位专业的在线客服助手负责解答用户关于产品和服务的问题。 # 任务说明 请根据提供的「参考资料」回答用户问题。参考资料位于context/context标签内。 # 回答要求 1. 回答必须基于参考资料不得编造信息 2. 语气友好、专业体现服务意识 3. 如果参考资料不足请礼貌说明并引导用户提供更多信息 4. 涉及隐私或机密信息时委婉拒绝并说明公司政策 # 参考资料 context {context} /context # 用户问题 {question} ); // 技术文档场景模板 templates.put(technical_doc, # 角色设定 你是一位技术专家负责解答开发者的技术问题。 # 任务说明 请根据提供的技术文档片段回答用户的技术疑问。 # 回答要求 1. 准确引用文档中的技术细节 2. 可以提供代码示例或配置示例 3. 如果文档中有多个解决方案请分别说明优缺点 4. 对于不确定的内容明确标注根据文档推测 # 参考资料 {context} # 用户问题 {question} ); } public String getTemplate(String templateKey, MapString, String variables) { String template templates.get(templateKey); if (template null) { template templates.get(default); } for (Map.EntryString, String entry : variables.entrySet()) { template template.replace({ entry.getKey() }, entry.getValue()); } return template; } }检索增强生成RAG的核心在于如何将检索结果有效地整合到提示词中。简单拼接往往效果不佳我推荐使用以下策略相关性排序只使用相关性分数最高的前3-5个片段去重处理合并内容相似或重复的片段上下文窗口优化确保总token数不超过模型限制元数据注入在片段前添加来源信息便于模型理解上下文Service public class ContextBuilder { private static final int MAX_CONTEXT_TOKENS 3000; private static final double RELEVANCE_THRESHOLD 0.7; public String buildContext(ListSearchResult.KnowledgeChunk chunks) { // 按相关性排序 chunks.sort((a, b) - Double.compare(b.getRelevanceScore(), a.getRelevanceScore())); StringBuilder context new StringBuilder(); int estimatedTokens 0; for (SearchResult.KnowledgeChunk chunk : chunks) { if (chunk.getRelevanceScore() RELEVANCE_THRESHOLD) { continue; // 过滤低相关性结果 } String chunkText formatChunk(chunk); int chunkTokens estimateTokens(chunkText); if (estimatedTokens chunkTokens MAX_CONTEXT_TOKENS) { break; // 超出上下文窗口 } context.append(chunkText).append(\n\n---\n\n); estimatedTokens chunkTokens; } return context.toString(); } private String formatChunk(SearchResult.KnowledgeChunk chunk) { return String.format(【来源%s - %s】\n%s, chunk.getDocName(), chunk.getChunkTitle(), chunk.getContent() ); } private int estimateTokens(String text) { // 简单估算中文大致1个token对应2-3个字符 return text.length() / 2; } }对于多轮对话场景我们需要维护对话历史。这里我实现了一个简单的对话管理器Component Scope(value session, proxyMode ScopedProxyMode.TARGET_CLASS) public class ConversationManager { private final ListMessage history new ArrayList(); private final int maxHistoryLength 10; public void addMessage(String role, String content) { history.add(new Message(role, content)); // 保持历史记录长度 if (history.size() maxHistoryLength) { history.remove(0); } } public ListMessage getHistory() { return new ArrayList(history); } public void clear() { history.clear(); } Data AllArgsConstructor public static class Message { private String role; // user, assistant, system private String content; } }4. 大模型调用与流式响应处理豆包大模型支持流式和非流式两种响应方式。对于需要实时交互的场景流式响应能显著提升用户体验。我们先看非流式调用的实现Service public class DoubaoChatService { Autowired private DoubaoConfig config; Autowired private ObjectMapper objectMapper; public ChatResponse chatCompletion(ChatRequest request) { ChatCompletionRequest apiRequest buildChatRequest(request); try { String requestJson objectMapper.writeValueAsString(apiRequest); SignableRequest signedRequest prepareSignedRequest( config.getKnowledgeBase().getHost(), /api/knowledge/chat/completions, POST, requestJson ); String responseBody executeRequest(signedRequest); BaseResponseCollectionChatCompletionResponseData response objectMapper.readValue(responseBody, new TypeReferenceBaseResponseCollectionChatCompletionResponseData() {}); return convertToChatResponse(response.getData()); } catch (Exception e) { log.error(大模型调用失败, e); throw new ServiceException(大模型服务暂时不可用, e); } } private ChatCompletionRequest buildChatRequest(ChatRequest request) { ChatCompletionRequest apiRequest new ChatCompletionRequest(); apiRequest.setModel(config.getKnowledgeBase().getModel()); apiRequest.setModelVersion(config.getKnowledgeBase().getModelVersion()); apiRequest.setProject(config.getKnowledgeBase().getProject()); apiRequest.setStream(false); apiRequest.setReturnTokenUsage(true); apiRequest.setMaxTokens(request.getMaxTokens() ! null ? request.getMaxTokens() : 1024); apiRequest.setTemperature(request.getTemperature() ! null ? request.getTemperature() : 0.7); // 构建消息列表 ListMessageParam messages new ArrayList(); messages.add(new MessageParam(system, request.getSystemPrompt())); for (ChatRequest.Message msg : request.getMessages()) { messages.add(new MessageParam(msg.getRole(), msg.getContent())); } apiRequest.setMessages(messages); return apiRequest; } }流式响应的处理稍微复杂一些需要处理Server-Sent EventsSSE。SpringBoot 3.x对响应式编程有更好的支持我们可以利用SseEmitter来实现RestController RequestMapping(/api/chat) public class ChatStreamController { Autowired private DoubaoChatService chatService; GetMapping(value /stream, produces MediaType.TEXT_EVENT_STREAM_VALUE) public SseEmitter streamChat(RequestParam String question, RequestParam(required false) String conversationId) { SseEmitter emitter new SseEmitter(30_000L); // 30秒超时 CompletableFuture.runAsync(() - { try { // 1. 知识库检索 SearchResult searchResult knowledgeSearchService.search(question, defaultOptions()); // 2. 构建上下文和提示词 String context contextBuilder.buildContext(searchResult.getChunks()); String prompt promptTemplateManager.getTemplate(customer_service, Map.of(context, context, question, question)); // 3. 流式调用大模型 ChatRequest chatRequest ChatRequest.builder() .systemPrompt(prompt) .message(new ChatRequest.Message(user, question)) .stream(true) .build(); chatService.streamChatCompletion(chatRequest, new StreamCallback() { Override public void onChunk(String chunk) { try { emitter.send(SseEmitter.event() .data(chunk) .id(UUID.randomUUID().toString())); } catch (IOException e) { log.error(SSE发送失败, e); } } Override public void onComplete() { emitter.complete(); } Override public void onError(Exception e) { emitter.completeWithError(e); } }); } catch (Exception e) { emitter.completeWithError(e); } }); emitter.onCompletion(() - log.info(SSE连接完成)); emitter.onTimeout(() - log.warn(SSE连接超时)); return emitter; } }在服务层我们需要实现流式回调接口public interface StreamCallback { void onChunk(String chunk); void onComplete(); void onError(Exception e); } Service public class DoubaoStreamService { public void streamChatCompletion(ChatRequest request, StreamCallback callback) { ChatCompletionRequest apiRequest buildChatRequest(request); apiRequest.setStream(true); try { String requestJson objectMapper.writeValueAsString(apiRequest); SignableRequest signedRequest prepareSignedRequest( config.getKnowledgeBase().getHost(), /api/knowledge/chat/completions, POST, requestJson ); // 设置SSE相关headers signedRequest.setHeader(Accept, text/event-stream); HttpClient client HttpClients.createDefault(); HttpResponse response client.execute(signedRequest); try (BufferedReader reader new BufferedReader( new InputStreamReader(response.getEntity().getContent()))) { String line; while ((line reader.readLine()) ! null) { if (line.startsWith(data:) line.length() 5) { String data line.substring(5); BaseResponseCollectionChatCompletionResponseData resp objectMapper.readValue(data, new TypeReferenceBaseResponseCollectionChatCompletionResponseData() {}); if (resp.getData() ! null resp.getData().getGenerateAnswer() ! null) { callback.onChunk(resp.getData().getGenerateAnswer()); } // 检查是否是最后一个chunk if (resp.getData() ! null Boolean.TRUE.equals(resp.getData().getEnd())) { callback.onComplete(); break; } } } } } catch (Exception e) { callback.onError(e); } } }5. 完整RAG流程集成与API设计现在我们把所有组件整合起来构建一个完整的RAG问答接口。我建议设计一个统一的入口根据请求参数决定使用流式还是非流式响应。RestController RequestMapping(/api/qa) Slf4j public class QAController { Autowired private KnowledgeSearchService searchService; Autowired private ContextBuilder contextBuilder; Autowired private PromptTemplateManager promptManager; Autowired private DoubaoChatService chatService; Autowired private DoubaoStreamService streamService; Autowired private ConversationManager conversationManager; PostMapping(/ask) public ApiResponseQAResponse ask(RequestBody QARequest request) { long startTime System.currentTimeMillis(); try { // 1. 知识库检索 SearchOptions options SearchOptions.builder() .limit(request.getSearchLimit() ! null ? request.getSearchLimit() : 5) .enableRerank(request.isEnableRerank()) .build(); SearchResult searchResult searchService.search(request.getQuestion(), options); // 2. 构建上下文和提示词 String context contextBuilder.buildContext(searchResult.getChunks()); String prompt promptManager.getTemplate( request.getTemplate() ! null ? request.getTemplate() : default, Map.of(context, context, question, request.getQuestion()) ); // 3. 构建对话历史支持多轮 ListChatRequest.Message messages new ArrayList(); messages.add(new ChatRequest.Message(system, prompt)); // 添加历史对话 if (request.isUseHistory()) { ListConversationManager.Message history conversationManager.getHistory(); history.forEach(msg - messages.add(new ChatRequest.Message(msg.getRole(), msg.getContent())) ); } // 添加当前问题 messages.add(new ChatRequest.Message(user, request.getQuestion())); // 4. 调用大模型 ChatRequest chatRequest ChatRequest.builder() .messages(messages) .maxTokens(request.getMaxTokens()) .temperature(request.getTemperature()) .stream(false) .build(); ChatResponse chatResponse chatService.chatCompletion(chatRequest); // 5. 保存对话历史 if (request.isUseHistory()) { conversationManager.addMessage(user, request.getQuestion()); conversationManager.addMessage(assistant, chatResponse.getAnswer()); } // 6. 构建响应 QAResponse qaResponse QAResponse.builder() .answer(chatResponse.getAnswer()) .sources(searchResult.getChunks().stream() .map(chunk - QAResponse.Source.builder() .docName(chunk.getDocName()) .chunkTitle(chunk.getChunkTitle()) .relevanceScore(chunk.getRelevanceScore()) .build()) .collect(Collectors.toList())) .searchTime(System.currentTimeMillis() - startTime) .tokenUsage(chatResponse.getTokenUsage()) .build(); return ApiResponse.success(qaResponse); } catch (Exception e) { log.error(问答处理失败, e); return ApiResponse.error(500, 系统处理异常: e.getMessage()); } } Data Builder public static class QARequest { NotBlank private String question; private Integer searchLimit; private Boolean enableRerank; private String template; private Boolean useHistory; private Integer maxTokens; private Double temperature; } Data Builder public static class QAResponse { private String answer; private ListSource sources; private Long searchTime; private TokenUsage tokenUsage; Data Builder public static class Source { private String docName; private String chunkTitle; private Double relevanceScore; } } }为了提升系统可靠性我们还需要添加一些高级特性请求限流与熔断使用Resilience4j防止服务过载Configuration public class CircuitBreakerConfig { Bean public CircuitBreakerRegistry circuitBreakerRegistry() { CircuitBreakerConfig config CircuitBreakerConfig.custom() .failureRateThreshold(50) .waitDurationInOpenState(Duration.ofSeconds(30)) .slidingWindowSize(10) .build(); return CircuitBreakerRegistry.of(config); } Bean public CircuitBreaker doubaoCircuitBreaker(CircuitBreakerRegistry registry) { return registry.circuitBreaker(doubaoService); } } Service public class DoubaoServiceWithCircuitBreaker { Autowired private CircuitBreaker circuitBreaker; Autowired private DoubaoChatService chatService; public ChatResponse chatWithCircuitBreaker(ChatRequest request) { return circuitBreaker.executeSupplier(() - chatService.chatCompletion(request)); } }异步处理与缓存对于频繁的相似查询可以添加缓存层Service Slf4j public class CachedQAService { Autowired private QAController qaController; private final CacheString, QAController.QAResponse cache Caffeine.newBuilder() .maximumSize(1000) .expireAfterWrite(10, TimeUnit.MINUTES) .build(); public QAController.QAResponse askWithCache(QAController.QARequest request) { String cacheKey generateCacheKey(request); return cache.get(cacheKey, key - { log.info(缓存未命中执行实际查询: {}, request.getQuestion()); ApiResponseQAController.QAResponse response qaController.ask(request); return response.getData(); }); } private String generateCacheKey(QAController.QARequest request) { return request.getQuestion() | (request.getTemplate() ! null ? request.getTemplate() : default) | (request.getSearchLimit() ! null ? request.getSearchLimit() : 5); } }监控与日志添加详细的日志记录和性能监控Aspect Component Slf4j public class PerformanceMonitorAspect { Around(annotation(MonitorPerformance)) public Object monitor(ProceedingJoinPoint joinPoint) throws Throwable { String methodName joinPoint.getSignature().getName(); long startTime System.currentTimeMillis(); try { Object result joinPoint.proceed(); long duration System.currentTimeMillis() - startTime; log.info(方法 {} 执行耗时: {}ms, methodName, duration); // 如果耗时过长记录警告 if (duration 1000) { log.warn(方法 {} 执行时间过长: {}ms, methodName, duration); } return result; } catch (Exception e) { log.error(方法 {} 执行异常, methodName, e); throw e; } } } Target(ElementType.METHOD) Retention(RetentionPolicy.RUNTIME) public interface MonitorPerformance { }6. 部署配置与性能调优将系统部署到生产环境时有几个关键配置需要注意。首先是连接池配置合理的HTTP连接池设置可以显著提升性能# application-prod.yml doubao: http: max-connections: 100 max-connections-per-route: 20 connect-timeout: 5000 socket-timeout: 30000 connection-request-timeout: 1000对应的配置类Configuration public class HttpClientConfig { Bean public HttpClient httpClient(DoubaoConfig config) { RequestConfig requestConfig RequestConfig.custom() .setConnectTimeout(config.getHttp().getConnectTimeout()) .setSocketTimeout(config.getHttp().getSocketTimeout()) .setConnectionRequestTimeout(config.getHttp().getConnectionRequestTimeout()) .build(); PoolingHttpClientConnectionManager connectionManager new PoolingHttpClientConnectionManager(); connectionManager.setMaxTotal(config.getHttp().getMaxConnections()); connectionManager.setDefaultMaxPerRoute(config.getHttp().getMaxConnectionsPerRoute()); return HttpClients.custom() .setConnectionManager(connectionManager) .setDefaultRequestConfig(requestConfig) .build(); } }对于高并发场景我们可以考虑引入异步处理。SpringBoot 3.x对虚拟线程Virtual Threads有很好的支持Configuration EnableAsync public class AsyncConfig { Bean public TaskExecutor taskExecutor() { ThreadPoolTaskExecutor executor new ThreadPoolTaskExecutor(); executor.setCorePoolSize(10); executor.setMaxPoolSize(50); executor.setQueueCapacity(100); executor.setThreadNamePrefix(doubao-async-); executor.initialize(); return executor; } } Service public class AsyncQAService { Async public CompletableFutureQAController.QAResponse askAsync(QAController.QARequest request) { return CompletableFuture.completedFuture(qaController.ask(request).getData()); } }监控指标收集也是生产环境不可或缺的一环。我们可以使用Micrometer集成PrometheusConfiguration public class MetricsConfig { Bean public MeterRegistry meterRegistry() { return new PrometheusMeterRegistry(PrometheusConfig.DEFAULT); } Bean public TimedAspect timedAspect(MeterRegistry registry) { return new TimedAspect(registry); } } Service public class MetricsService { private final Counter requestCounter; private final Timer responseTimer; private final DistributionSummary responseSizeSummary; public MetricsService(MeterRegistry registry) { requestCounter Counter.builder(qa.requests.total) .description(Total number of QA requests) .register(registry); responseTimer Timer.builder(qa.response.time) .description(Time taken to process QA requests) .register(registry); responseSizeSummary DistributionSummary.builder(qa.response.size) .description(Size of QA responses in characters) .register(registry); } Timed(value qa.process, description Time spent processing QA request) public QAController.QAResponse processWithMetrics(QAController.QARequest request) { requestCounter.increment(); return responseTimer.record(() - { QAController.QAResponse response qaController.ask(request).getData(); responseSizeSummary.record(response.getAnswer().length()); return response; }); } }最后分享几个我在实际项目中遇到的性能优化技巧批量处理对于多个相关问题可以批量检索然后分别生成回答预计算对常见问题预先计算并缓存答案索引优化确保知识库文档有良好的索引策略模型选择根据场景选择合适的模型版本平衡效果和成本Service public class BatchQAService { public ListQAController.QAResponse batchAsk(ListString questions) { // 1. 批量检索 MapString, SearchResult searchResults new HashMap(); for (String question : questions) { searchResults.put(question, searchService.search(question, defaultOptions())); } // 2. 批量生成提示词 MapString, String prompts new HashMap(); for (Map.EntryString, SearchResult entry : searchResults.entrySet()) { String context contextBuilder.buildContext(entry.getValue().getChunks()); String prompt promptManager.getTemplate(default, Map.of(context, context, question, entry.getKey())); prompts.put(entry.getKey(), prompt); } // 3. 并行调用大模型 ListCompletableFutureQAController.QAResponse futures questions.stream() .map(question - CompletableFuture.supplyAsync(() - { ChatRequest chatRequest ChatRequest.builder() .systemPrompt(prompts.get(question)) .message(new ChatRequest.Message(user, question)) .build(); ChatResponse chatResponse chatService.chatCompletion(chatRequest); return QAController.QAResponse.builder() .answer(chatResponse.getAnswer()) .sources(searchResults.get(question).getChunks().stream() .map(chunk - QAController.QAResponse.Source.builder() .docName(chunk.getDocName()) .chunkTitle(chunk.getChunkTitle()) .relevanceScore(chunk.getRelevanceScore()) .build()) .collect(Collectors.toList())) .build(); })) .collect(Collectors.toList()); // 4. 等待所有结果 return futures.stream() .map(CompletableFuture::join) .collect(Collectors.toList()); } }在实际部署时记得配置合适的JVM参数。对于内存密集型的大模型应用我建议java -Xms2g -Xmx4g -XX:UseG1GC \ -XX:MaxGCPauseMillis200 \ -XX:InitiatingHeapOccupancyPercent35 \ -jar knowledge-qa-demo.jar这些配置基于我最近几个项目的实际运行经验能够在大并发下保持稳定的性能表现。当然具体参数需要根据实际硬件配置和负载情况进行调整。