
在企业级应用开发中如何让AI大模型精准地回答公司内部文档、产品手册、技术规范等私有知识而不是“一本正经地胡说八道”是许多开发者面临的共同挑战。传统的微调方案成本高昂、周期长且难以应对知识的快速更新。此时RAG检索增强生成技术脱颖而出成为构建企业智能知识库的首选方案。本文将从零开始手把手带你搭建一个功能完整、可落地的RAG企业知识库系统。我们将深入RAG的核心流程并结合Spring Boot、Milvus向量数据库和LangChain4j等主流技术栈实现一个完整的问答应用。无论你是对AI应用开发感兴趣的初学者还是希望将RAG技术引入实际项目的开发者都能通过本文获得从理论到实践的完整闭环体验。1. RAG技术核心概念与价值在深入代码之前我们必须理解RAG解决了什么问题以及它是如何工作的。1.1 什么是RAGRAG全称Retrieval-Augmented Generation即检索增强生成。它是一种将信息检索技术与大语言模型LLM生成能力相结合的技术范式。其核心思想是当用户提出一个问题时系统首先从外部知识库如企业文档数据库中检索出与问题最相关的文档片段然后将这些片段作为上下文连同用户问题一起提交给大语言模型让模型基于这些可靠的“证据”来生成答案。简单来说RAG让大模型学会了“先查资料再回答问题”。这极大地提升了回答的准确性、相关性和可追溯性有效缓解了大模型的“幻觉”问题即生成看似合理但实际错误的信息。1.2 为什么选择RAG构建企业知识库相比于直接向大模型提问或对模型进行全量微调RAG方案具有显著优势知识实时性只需更新向量数据库中的文档即可让模型获取最新知识无需重新训练昂贵的模型。成本低廉主要成本在于检索和推理远低于大模型的训练或微调成本。答案可解释系统可以返回生成答案所依据的源文档片段方便用户核实增强信任度。数据隐私与安全敏感的企业知识可以存储在本地或私有云端的向量数据库中无需上传至公开的模型服务。易于集成可以作为一个独立的服务层与现有的企业系统如OA、CRM无缝集成。1.3 RAG核心工作流程一个标准的RAG系统通常包含以下五个关键步骤这也是我们后续实战的蓝图文档加载与预处理从各种来源PDF、Word、TXT、网页加载原始文档。文本分割切片将长文档切割成大小适中、语义相对完整的片段Chunks。这是影响检索质量的关键步骤。向量化与嵌入使用嵌入模型Embedding Model将文本片段转换为高维向量Vector。语义相似的文本其向量在空间中的距离也更近。向量存储与索引将生成的向量及其对应的原始文本存储到专门的向量数据库如Milvus、Chroma、Weaviate中并建立高效的索引以便快速检索。检索与生成检索将用户问题同样转换为向量在向量数据库中搜索与之最相似的K个文本片段。增强将这些检索到的片段作为上下文。生成将“上下文用户问题”组合成提示词Prompt发送给大语言模型生成最终答案。2. 环境准备与项目架构我们将构建一个基于Spring Boot的后端服务使用Milvus作为向量数据库LangChain4j作为AI应用框架并接入OpenAI的API也可替换为国内大模型。2.1 技术栈与版本说明Java: JDK 17 或更高版本Spring Boot: 3.x 版本构建工具: Maven 或 Gradle (本文使用Maven)向量数据库: Milvus 2.4.x (支持Docker部署)AI应用框架: LangChain4j 0.31.x嵌入模型: OpenAItext-embedding-3-small(或text-embedding-ada-002)大语言模型: OpenAI GPT-3.5-turbo/GPT-4 (可替换为通义千问、文心一言等国产模型API)开发工具: IntelliJ IDEA 或 VS Code重要提示技术生态迭代迅速依赖版本请根据项目实际情况调整。本文重点在于演示核心配置思路和代码结构版本差异可能导致部分API变更请参考官方文档。2.2 项目初始化与结构首先使用 Spring Initializr 或IDE创建Spring Boot项目。依赖选择Spring WebLombok (简化代码)创建完成后手动在pom.xml中添加以下核心依赖!-- LangChain4j 核心 -- dependency groupIddev.langchain4j/groupId artifactIdlangchain4j/artifactId version0.31.0/version /dependency !-- LangChain4j OpenAI 集成 -- dependency groupIddev.langchain4j/groupId artifactIdlangchain4j-open-ai/artifactId version0.31.0/version /dependency !-- LangChain4j Milvus 集成 -- dependency groupIddev.langchain4j/groupId artifactIdlangchain4j-milvus/artifactId version0.31.0/version /dependency !-- 用于文档解析 (如PDF) -- dependency groupIdorg.apache.pdfbox/groupId artifactIdpdfbox/artifactId version2.0.29/version /dependency !-- 工具类 -- dependency groupIdorg.apache.commons/groupId artifactIdcommons-lang3/artifactId /dependency项目基础结构如下rag-knowledge-base-demo/ ├── src/main/java/com/example/ragdemo/ │ ├── config/ # 配置类 (AI模型、向量库配置) │ ├── controller/ # REST API 控制器 │ ├── service/ # 业务逻辑层 (文档处理、问答服务) │ ├── entity/ # 实体类 │ └── Application.java # 启动类 ├── src/main/resources/ │ ├── application.yml # 应用配置文件 │ └── documents/ # 存放待处理的示例文档 └── pom.xml3. 核心组件配置与原理拆解接下来我们逐一配置并理解系统中的核心组件。3.1 配置Milvus向量数据库Milvus是一个高性能、开源的向量数据库。我们首先通过Docker快速启动一个Milvus单机实例。步骤1使用Docker启动Milvus确保已安装Docker和Docker Compose。创建一个docker-compose.yml文件version: 3.5 services: etcd: container_name: milvus-etcd image: quay.io/coreos/etcd:v3.5.5 environment: - ETCD_AUTO_COMPACTION_MODErevision - ETCD_AUTO_COMPACTION_RETENTION1000 - ETCD_QUOTA_BACKEND_BYTES4294967296 - ETCD_SNAPSHOT_COUNT50000 volumes: - ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/etcd:/etcd command: etcd -advertise-client-urlshttp://127.0.0.1:2379 -listen-client-urls http://0.0.0.0:2379 --data-dir /etcd minio: container_name: milvus-minio image: minio/minio:RELEASE.2023-03-20T20-16-18Z environment: MINIO_ACCESS_KEY: minioadmin MINIO_SECRET_KEY: minioadmin volumes: - ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/minio:/minio_data command: minio server /minio_data healthcheck: test: [CMD, curl, -f, http://localhost:9000/minio/health/live] interval: 30s timeout: 20s retries: 3 standalone: container_name: milvus-standalone image: milvusdb/milvus:v2.4.0 command: [milvus, run, standalone] environment: ETCD_ENDPOINTS: etcd:2379 MINIO_ADDRESS: minio:9000 volumes: - ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/milvus:/var/lib/milvus ports: - 19530:19530 # Milvus 服务端口 - 9091:9091 # 监控端口 depends_on: - etcd - minio在文件所在目录执行命令启动docker-compose up -d等待片刻访问http://localhost:9091可查看Milvus监控界面确认服务已就绪。步骤2Spring Boot应用配置在application.yml中配置Milvus连接spring: application: name: rag-knowledge-base milvus: host: localhost port: 19530 openai: api-key: ${OPENAI_API_KEY:sk-your-openai-key-here} # 建议通过环境变量传入 embedding-model: text-embedding-3-small # 或 text-embedding-ada-002 chat-model: gpt-3.5-turbo3.2 配置AI模型与LangChain4j创建配置类用于初始化嵌入模型和大语言模型。// 文件路径src/main/java/com/example/ragdemo/config/AiConfig.java package com.example.ragdemo.config; import dev.langchain4j.model.chat.ChatLanguageModel; import dev.langchain4j.model.embedding.EmbeddingModel; import dev.langchain4j.model.openai.OpenAiChatModel; import dev.langchain4j.model.openai.OpenAiEmbeddingModel; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.time.Duration; Configuration public class AiConfig { Value(${openai.api-key}) private String openAiApiKey; Value(${openai.chat-model}) private String chatModelName; Value(${openai.embedding-model}) private String embeddingModelName; // 配置大语言模型 (用于生成答案) Bean public ChatLanguageModel chatLanguageModel() { return OpenAiChatModel.builder() .apiKey(openAiApiKey) .modelName(chatModelName) .timeout(Duration.ofSeconds(60)) .build(); } // 配置嵌入模型 (用于将文本转为向量) Bean public EmbeddingModel embeddingModel() { return OpenAiEmbeddingModel.builder() .apiKey(openAiApiKey) .modelName(embeddingModelName) .build(); } }3.3 配置向量存储服务创建配置类将Milvus初始化为LangChain4j的向量存储。// 文件路径src/main/java/com/example/ragdemo/config/VectorStoreConfig.java package com.example.ragdemo.config; import dev.langchain4j.data.segment.TextSegment; import dev.langchain4j.model.embedding.EmbeddingModel; import dev.langchain4j.store.embedding.EmbeddingStore; import dev.langchain4j.store.embedding.milvus.MilvusEmbeddingStore; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; Configuration public class VectorStoreConfig { Value(${milvus.host}) private String host; Value(${milvus.port}) private int port; // 集合名称类似于数据库的表名 private static final String COLLECTION_NAME knowledge_base; Bean public EmbeddingStoreTextSegment embeddingStore(EmbeddingModel embeddingModel) { // 获取嵌入模型的维度Milvus集合需要此参数 int dimension embeddingModel.dimension(); MilvusEmbeddingStore.Builder builder MilvusEmbeddingStore.builder() .host(host) .port(port) .collectionName(COLLECTION_NAME) .dimension(dimension) .retrieveEmbeddingsOnSearch(true) // 搜索时返回向量用于某些高级场景 .consistencyLevel(Bounded) // 一致性级别 .build(); return builder.build(); } }4. 完整实战构建RAG问答系统现在我们将实现两个核心功能1) 知识库文档入库2) 智能问答。4.1 文档加载与文本分割服务首先创建一个服务负责读取本地文档以PDF为例并将其分割成适合检索的文本块。// 文件路径src/main/java/com/example/ragdemo/service/DocumentService.java package com.example.ragdemo.service; import dev.langchain4j.data.document.Document; import dev.langchain4j.data.document.DocumentParser; import dev.langchain4j.data.document.DocumentSplitter; import dev.langchain4j.data.document.parser.apache.pdfbox.ApachePdfBoxDocumentParser; import dev.langchain4j.data.document.splitter.DocumentSplitters; import dev.langchain4j.data.segment.TextSegment; import dev.langchain4j.model.embedding.EmbeddingModel; import dev.langchain4j.store.embedding.EmbeddingStore; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import java.io.IOException; import java.nio.file.Paths; import java.util.List; Slf4j Service RequiredArgsConstructor public class DocumentService { private final EmbeddingStoreTextSegment embeddingStore; private final EmbeddingModel embeddingModel; private final ResourceLoader resourceLoader; // 文档分割器按最大令牌数分割并保留部分重叠以避免语义断裂 private static final DocumentSplitter SPLITTER DocumentSplitters.recursive(500, 50); /** * 处理并入库单个文档 * param filePath 相对于 resources/documents/ 的文件路径如 产品手册.pdf */ public void processAndStoreDocument(String filePath) throws IOException { if (!StringUtils.hasText(filePath)) { throw new IllegalArgumentException(文件路径不能为空); } String fullPath documents/ filePath; Resource resource resourceLoader.getResource(classpath: fullPath); if (!resource.exists()) { throw new IOException(文件未找到: fullPath); } // 1. 根据文件类型选择解析器 DocumentParser parser selectParser(filePath); Document document parser.parse(resource.getFile()); // 2. 分割文档 ListTextSegment segments SPLITTER.split(document); log.info(文档 {} 被分割成 {} 个片段, filePath, segments.size()); // 3. 为每个片段生成向量并存储 for (TextSegment segment : segments) { // 生成向量 var embedding embeddingModel.embed(segment.text()).content(); // 存储到Milvus embeddingStore.add(embedding, segment); } log.info(文档 {} 处理完成已存入向量数据库。, filePath); } /** * 批量处理文档目录简化示例 */ public void processAllDocumentsInDirectory() throws IOException { // 此处可以遍历指定目录下的所有文件 // 例如processAndStoreDocument(doc1.pdf); // processAndStoreDocument(policy.txt); log.info(批量处理功能需根据实际目录结构实现。); } private DocumentParser selectParser(String filePath) { String lowerCasePath filePath.toLowerCase(); if (lowerCasePath.endsWith(.pdf)) { return new ApachePdfBoxDocumentParser(); } else if (lowerCasePath.endsWith(.txt) || lowerCasePath.endsWith(.md)) { // 使用LangChain4j内置的文本解析器 return DocumentParser.defaultParser(); } else { log.warn(文件类型 {} 可能不受直接支持将使用默认文本解析器尝试。, filePath); return DocumentParser.defaultParser(); } } }4.2 构建RAG问答链服务这是系统的核心实现检索与生成的完整流程。// 文件路径src/main/java/com/example/ragdemo/service/RagQueryService.java package com.example.ragdemo.service; import dev.langchain4j.data.segment.TextSegment; import dev.langchain4j.model.chat.ChatLanguageModel; import dev.langchain4j.model.input.Prompt; import dev.langchain4j.model.input.PromptTemplate; import dev.langchain4j.retriever.EmbeddingStoreRetriever; import dev.langchain4j.store.embedding.EmbeddingStore; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; Slf4j Service RequiredArgsConstructor public class RagQueryService { private final EmbeddingStoreTextSegment embeddingStore; private final ChatLanguageModel chatModel; private final dev.langchain4j.model.embedding.EmbeddingModel embeddingModel; // 注意使用全限定名避免Bean冲突 // 定义提示词模板指导模型基于上下文回答 private static final PromptTemplate PROMPT_TEMPLATE PromptTemplate.from( 请根据以下上下文信息回答问题。如果上下文信息不足以回答问题请直接说“根据提供的信息我无法回答这个问题”。\n\n 上下文信息\n{{context}}\n\n 问题{{question}}\n\n 答案 ); /** * 执行RAG问答 * param question 用户问题 * return 生成的答案 */ public String ask(String question) { if (question null || question.trim().isEmpty()) { return 问题不能为空。; } // 1. 将问题转换为向量 var questionEmbedding embeddingModel.embed(question).content(); // 2. 从向量数据库中检索最相关的片段 (这里取前3个) int maxResults 3; ListTextSegment relevantSegments embeddingStore.findRelevant(questionEmbedding, maxResults); if (relevantSegments.isEmpty()) { return 知识库中未找到相关信息。; } log.info(检索到 {} 个相关片段。, relevantSegments.size()); // 3. 将检索到的片段合并为上下文 String context relevantSegments.stream() .map(TextSegment::text) .collect(Collectors.joining(\n\n---\n\n)); // 4. 构建提示词 MapString, Object variables new HashMap(); variables.put(context, context); variables.put(question, question); Prompt prompt PROMPT_TEMPLATE.apply(variables); // 5. 调用大语言模型生成答案 String answer chatModel.generate(prompt.text()); // 可选6. 记录日志或返回引用来源 log.debug(问题{} \n答案{}, question, answer); return answer; } /** * 带来源的问答返回答案及引用的文档片段 */ public static class AnswerWithSources { private final String answer; private final ListString sourceTexts; // 构造函数、Getter省略实际开发中请使用Lombok Data注解 } public AnswerWithSources askWithSources(String question) { // 实现逻辑与ask()类似但在返回答案的同时也返回 relevantSegments 中的文本。 // 此处为节省篇幅省略具体实现。核心是封装答案和检索到的片段列表。 return null; // 实际应返回对象 } }4.3 提供REST API接口创建控制器对外提供文档上传/处理和问答的HTTP接口。// 文件路径src/main/java/com/example/ragdemo/controller/KnowledgeBaseController.java package com.example.ragdemo.controller; import com.example.ragdemo.service.DocumentService; import com.example.ragdemo.service.RagQueryService; import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; RestController RequestMapping(/api/kb) RequiredArgsConstructor public class KnowledgeBaseController { private final DocumentService documentService; private final RagQueryService ragQueryService; Operation(summary 上传并处理文档到知识库) PostMapping(/document) public ResponseEntityString uploadDocument(RequestParam(file) MultipartFile file) { if (file.isEmpty()) { return ResponseEntity.badRequest().body(文件为空); } try { // 1. 保存上传的文件到临时目录或指定目录 String uploadDir uploads/; Path uploadPath Paths.get(uploadDir); if (!Files.exists(uploadPath)) { Files.createDirectories(uploadPath); } Path filePath uploadPath.resolve(file.getOriginalFilename()); Files.copy(file.getInputStream(), filePath); // 2. 调用服务处理文档 (这里简化直接传路径。生产环境需更健壮的处理) documentService.processAndStoreDocument(filePath.toString()); return ResponseEntity.ok(文档 file.getOriginalFilename() 已成功处理并入库。); } catch (IOException e) { e.printStackTrace(); return ResponseEntity.internalServerError().body(文件处理失败: e.getMessage()); } } Operation(summary 向知识库提问) GetMapping(/ask) public ResponseEntityString askQuestion(RequestParam String question) { try { String answer ragQueryService.ask(question); return ResponseEntity.ok(answer); } catch (Exception e) { e.printStackTrace(); return ResponseEntity.internalServerError().body(问答服务出错: e.getMessage()); } } }4.4 运行与验证步骤1启动依赖服务确保Milvus容器正在运行 (docker-compose up -d)。步骤2启动Spring Boot应用运行Application主类。步骤3初始化知识库我们可以编写一个简单的命令行运行器在应用启动时加载示例文档。// 文件路径src/main/java/com/example/ragdemo/runner/DataInitRunner.java package com.example.ragdemo.runner; import com.example.ragdemo.service.DocumentService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.CommandLineRunner; import org.springframework.core.io.ClassPathResource; import org.springframework.stereotype.Component; import java.io.File; import java.io.IOException; Slf4j Component RequiredArgsConstructor public class DataInitRunner implements CommandLineRunner { private final DocumentService documentService; Override public void run(String... args) throws Exception { // 示例在resources/documents/下放置一个sample.pdf文件 String sampleDoc sample.pdf; // 或 company_policy.txt try { File file new ClassPathResource(documents/ sampleDoc).getFile(); if (file.exists()) { log.info(开始初始化知识库加载示例文档: {}, sampleDoc); documentService.processAndStoreDocument(sampleDoc); log.info(知识库初始化完成。); } else { log.warn(示例文档 {} 不存在跳过初始化。请通过API上传文档。, sampleDoc); } } catch (IOException e) { log.error(初始化知识库时发生IO异常, e); } catch (Exception e) { log.error(初始化知识库失败, e); } } }步骤4测试问答应用启动后访问http://localhost:8080/api/kb/ask?question你的问题。 例如如果你的sample.pdf是关于公司请假制度的你可以问“请问年假有多少天” 系统会从PDF中检索相关内容并生成类似“根据公司规定员工累计工作满1年不满10年的年休假为5天...”的答案。你也可以使用Postman或curl测试curl http://localhost:8080/api/kb/ask?question公司的年假政策是什么5. 常见问题与排查思路在搭建和运行过程中你可能会遇到以下问题问题现象可能原因排查步骤与解决方案应用启动失败连接Milvus超时1. Milvus服务未启动。2. 网络或端口不通。3.application.yml配置错误。1. 执行docker ps确认milvus-standalone容器状态为Up。2. 执行telnet localhost 19530测试端口连通性。3. 检查配置文件中的milvus.host和port。调用问答接口返回空或“未找到信息”1. 向量数据库集合为空。2. 文档分割或向量化失败。3. 检索相似度阈值过高无匹配结果。1. 检查文档是否成功处理查看日志。2. 确认嵌入模型API密钥有效且网络可访问。3. 在RagQueryService.ask()中增加maxResults或检查检索到的relevantSegments内容。处理PDF文档时出错1. PDF文件加密或损坏。2. 依赖pdfbox版本冲突。3. 文件路径错误。1. 尝试打开PDF文件确认其完整性。2. 检查Maven依赖树排除冲突的旧版本。3. 使用绝对路径或确保资源文件在classpath下。答案质量差答非所问1. 文本分割策略不合理导致语义碎片化。2. 检索到的上下文不相关。3. 提示词Prompt设计不佳。1. 调整DocumentSplitters.recursive的参数块大小、重叠大小。2. 尝试不同的嵌入模型或调整检索数量。3. 优化PROMPT_TEMPLATE更明确地指令模型基于上下文回答。OpenAI API调用超时或报错1. API密钥无效或余额不足。2. 网络问题无法访问OpenAI。3. 请求速率超限。1. 在OpenAI平台检查API密钥状态和用量。2. 配置网络代理需合法合规的网络访问方式。3. 增加AiConfig中的timeout时长或实现重试机制。6. 最佳实践与进阶优化一个基础的RAG系统搭建完成后可以从以下方面进行优化以提升其生产环境可用性。6.1 文本分割优化递归分割DocumentSplitters.recursive是一个好的起点它尝试在段落、句子等自然边界进行分割。自定义分割器对于特定格式如Markdown标题、LaTeX章节可以基于语义规则实现自定义分割器以保留更好的上下文。元数据附加在分割时为每个TextSegment附加元数据如来源文件名、页码、章节标题等便于追溯和更精细的检索过滤。6.2 检索策略优化混合检索结合向量检索语义相似和关键词检索如BM25可以同时捕捉语义和精确词汇匹配提升召回率。LangChain4j支持EmbeddingStoreRetriever与KeywordRetriever的组合。重排序初步检索出较多结果如20个后使用一个更轻量或专门的模型对结果进行重排序只保留最相关的几个片段送入大模型可以提升答案质量并降低成本。元数据过滤检索时增加过滤条件例如只从“产品手册”类文档中检索提高精准度。6.3 提示词工程优化清晰的指令明确要求模型“基于给定上下文”、“如果上下文没有则说不知道”。结构化上下文在Prompt中清晰分隔不同来源的上下文例如使用[文档1] ... [/文档1]的格式。指定角色让模型扮演“专业客服”、“技术专家”等角色可以改善回答风格。6.4 生产环境考量异步处理文档入库向量化是耗时操作应改为异步任务避免阻塞HTTP请求。错误处理与重试对第三方APIOpenAI、Milvus的调用必须有完善的超时、重试和降级机制。监控与日志记录问答日志、检索片段、耗时、Token用量等用于分析和优化。多模型支持通过配置抽象方便切换不同的嵌入模型和LLM如国产大模型。权限与安全API接口需增加认证授权对用户输入进行必要的清洗和检查防止Prompt注入攻击。6.5 扩展方向多模态RAG支持图片、表格中的信息提取和检索。Agentic RAG让RAG系统具备自主调用工具、拆解复杂问题、链式思考的能力。增量更新实现知识库文档的增量更新和删除而非全量重建。Web UI开发一个前端界面方便上传文档和进行问答交互。通过本文的实战你已经掌握了搭建一个企业级RAG知识库的核心流程。从概念理解、环境搭建、代码实现到优化方向形成了一个完整的知识闭环。关键在于动手实践并根据自己的业务数据调整文本分割、检索和提示词策略。RAG技术正在快速发展保持对新技术如更好的嵌入模型、检索算法、Agent框架的关注将帮助你构建更强大、更智能的企业知识大脑。