第7篇:从零搭建本地RAG知识库,内网可用,零API费用 (Java+AI落地实战系列 | 纯本地部署 | 彻底解决大模型幻觉)

0 阅读9分钟

第7篇:从零搭建本地RAG知识库,内网可用,零API费用

(Java+AI落地实战系列 | 纯本地部署 | 彻底解决大模型幻觉)


大家好,我是三石。

上一篇我们实现了多轮对话记忆,AI终于能记住上下文,对话体验像模像样了。

但很多同学落地业务时又卡了:

  • 问公司内部制度、产品手册,AI张嘴就胡说八道,十句有八句是编的
  • 政企、金融行业数据不能出内网,公网大模型API根本不敢碰
  • 调用量上来后,token费用蹭蹭涨,老板迟迟不肯批预算

这就是企业级AI落地最核心的两个卡点:幻觉问题 + 数据安全问题

今天我们就用纯Java技术栈,从零搭一套完全本地化的RAG知识库系统——不用公网API、数据全在本地、AI只基于你给的文档回答,从根源解决胡说八道的问题,普通笔记本就能跑。

先给大家打个比方,一秒懂RAG:

普通大模型问答 = 闭卷考试,全靠AI脑子里的知识瞎蒙,答错很正常 RAG知识库问答 = 开卷考试,先给AI发参考资料,要求它只能照着资料答,准确率100%

在这里插入图片描述

一、先搞懂RAG的完整工作流程

别被网上的玄乎术语吓住,RAG本质就四步,全程都是Java开发熟悉的逻辑:

  1. 文档切片:把PDF、Word、制度手册这些长文档,切成几百字的小片段
  2. 向量化存储:把每个文本片段转成向量,存入向量数据库(相当于给每段内容建索引)
  3. 相似度检索:用户提问时,把问题也转成向量,从库里找出最相关的3-5段文档
  4. 增强生成:把「用户问题 + 检索到的相关文档」一起喂给大模型,要求它只基于文档回答

全程数据都跑在你的服务器上,大模型只负责“整理资料输出答案”,不会泄露任何内部数据,也不会瞎编内容。

640.png

二、前置准备:本地环境搭建

1. 本地大模型部署工具:Ollama

不用装复杂的Python环境,不用配CUDA,一行命令就能启动开源大模型,对Java开发者极度友好。

安装步骤
  1. 官网下载安装包:ollama.com/ ,支持Windows/Mac/Linux
  2. 打开命令行,拉取中文效果最好的7B开源模型(8G内存就能跑)
    ollama pull qwen2:7b
    
  3. 验证启动:执行 ollama run qwen2:7b,能直接在命令行对话就说明部署成功
  4. 默认本地服务地址:http://localhost:11434,Java代码直接对接这个地址

2. 技术栈选型(兼容企业Java环境)

技术版本说明
JDK1.8兼容绝大多数存量业务系统
SpringBoot2.7.18生产环境主流稳定版
LangChain4j0.32.0Java生态最成熟的AI应用框架,和SpringBoot版本严格匹配
向量存储InMemory入门零依赖,生产可替换为Milvus/PGVector
大模型Qwen2-7B开源免费,中文效果优秀,本地部署

踩坑提醒:LangChain4j版本和SpringBoot版本必须对应,0.32.0适配SpringBoot 2.7.x,乱升版本会直接报NoSuchMethodError。


三、第一步:引入核心Maven依赖

直接复制到pom.xml,不用再找零散依赖:

<dependencies>
    <!-- SpringBoot Web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>2.7.18</version>
    </dependency>

    <!-- LangChain4j 核心 -->
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-core</artifactId>
        <version>0.32.0</version>
    </dependency>

    <!-- Ollama 本地大模型对接 -->
    <dependency>
        <groupId>dev.langchain4j</groupId>
        <artifactId>langchain4j-ollama</artifactId>
        <version>0.32.0</version>
    </dependency>

    <!-- Lombok 简化代码 -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.30</version>
        <optional>true</optional>
    </dependency>
</dependencies>

四、第二步:核心配置类

新建RagConfig,初始化三个核心组件:大模型、嵌入模型、向量存储。

@Configuration
public class RagConfig {

    /**
     * 配置本地聊天大模型
     */
    @Bean
    public ChatLanguageModel chatLanguageModel() {
        return OllamaChatModel.builder()
                .baseUrl("http://localhost:11434")
                .modelName("qwen2:7b")
                .temperature(0.2)  // 知识库场景用低温度,回答更严谨
                .timeout(Duration.ofSeconds(60))
                .build();
    }

    /**
     * 配置嵌入模型:把文本转成向量
     * 入门用同一个qwen2模型即可,生产建议用专用嵌入模型
     */
    @Bean
    public EmbeddingModel embeddingModel() {
        return OllamaEmbeddingModel.builder()
                .baseUrl("http://localhost:11434")
                .modelName("qwen2:7b")
                .build();
    }

    /**
     * 内存向量存储:入门零依赖,服务重启数据会丢
     * 生产环境替换为Milvus/PGVector
     */
    @Bean
    public EmbeddingStore<TextSegment> embeddingStore() {
        return new InMemoryEmbeddingStore<>();
    }
}

五、第三步:文档入库服务

把业务文档切成片段,转成向量存入向量库,相当于给AI准备“考试参考资料”。

@Service
public class DocumentService {

    @Autowired
    private EmbeddingStore<TextSegment> embeddingStore;
    @Autowired
    private EmbeddingModel embeddingModel;

    /**
     * 纯文本文档入库
     * @param content 文档完整内容
     * @param docName 文档名称,用于溯源
     */
    public void addTextDocument(String content, String docName) {
        // 1. 文档切片:500字符一段,重叠50字符,保证语义完整
        DocumentSplitter splitter = DocumentSplitters.recursive(500, 50);
        List<TextSegment> segments = splitter.split(Document.from(content));

        // 2. 给每个片段打元数据标记,方便后续溯源是哪份文档的内容
        segments.forEach(segment -> 
            segment.metadata().put("docName", docName)
        );

        // 3. 向量化并存入向量库
        EmbeddingStoreIngestor.builder()
                .documentSplitter(splitter)
                .embeddingModel(embeddingModel)
                .embeddingStore(embeddingStore)
                .build()
                .ingest(segments);
    }
}

小技巧:切片大小不是固定的,短文档可以切小一点,长文档可以切大一点。中文场景建议400-600字符,重叠率10%,检索效果最好。


六、第四步:RAG问答服务实现

用户提问时,先检索相关文档,再让大模型基于文档回答,从根源杜绝幻觉。

@Service
public class RagChatService {

    @Autowired
    private ChatLanguageModel chatModel;
    @Autowired
    private EmbeddingStore<TextSegment> embeddingStore;
    @Autowired
    private EmbeddingModel embeddingModel;

    /**
     * 基于知识库的问答
     * @param question 用户问题
     * @return 基于参考文档的回答
     */
    public String chatWithKnowledge(String question) {
        // 1. 把用户问题转成向量,检索最相关的3条文档片段
        List<EmbeddingMatch<TextSegment>> matches = embeddingStore.findRelevant(
                embeddingModel.embed(question).content(),
                3,    // 检索Top3最相关文档
                0.7   // 相似度阈值,低于0.7的文档不返回,避免乱凑内容
        );

        // 2. 拼接检索到的文档作为上下文
        StringBuilder context = new StringBuilder("【参考文档】:\n");
        for (int i = 0; i < matches.size(); i++) {
            TextSegment segment = matches.get(i).embedded();
            context.append("文档").append(i + 1).append("(")
                    .append(segment.metadata().getString("docName"))
                    .append("):")
                    .append(segment.text()).append("\n");
        }

        // 3. 构造强约束提示词,强制大模型只基于文档回答
        String prompt = context + "\n" +
                "请严格根据上面的参考文档回答用户问题:" + question + "\n" +
                "要求:\n" +
                "1. 答案必须完全来自参考文档,禁止编造任何内容\n" +
                "2. 如果参考文档中没有相关答案,直接回答:暂无相关资料\n" +
                "3. 回答简洁准确,分点说明优先";

        // 4. 调用本地大模型返回结果
        return chatModel.generate(prompt);
    }
}

七、第五步:对外接口层

@RestController
@RequestMapping("/api/rag")
public class RagController {

    @Autowired
    private DocumentService documentService;
    @Autowired
    private RagChatService ragChatService;

    /**
     * 文档入库接口
     */
    @PostMapping("/addDoc")
    public Result<String> addDocument(@RequestBody DocAddRequest request) {
        documentService.addTextDocument(request.getContent(), request.getDocName());
        return Result.success("文档入库成功");
    }

    /**
     * 知识库问答接口
     */
    @GetMapping("/chat")
    public Result<String> chat(String question) {
        String answer = ragChatService.chatWithKnowledge(question);
        return Result.success(answer);
    }

    // 请求实体
    @Data
    static class DocAddRequest {
        private String content;
        private String docName;
    }

    // 统一返回类
    @Data
    static class Result<T> {
        private Integer code;
        private String msg;
        private T data;

        public static <T> Result<T> success(T data) {
            Result<T> result = new Result<>();
            result.setCode(200);
            result.setMsg("success");
            result.setData(data);
            return result;
        }
    }
}

八、测试验证:看看效果

第一步:上传测试文档

调用POST接口 http://localhost:8080/api/rag/addDoc 请求体示例(上传公司报销制度):

{
    "docName": "公司报销管理制度",
    "content": "1. 差旅费报销标准:一线城市住宿上限300元/天,二线城市240元/天,餐补80元/天。2. 办公费用单笔超过1000元需提前提交审批,部门经理签字后方可报销。3. 报销周期为每月15号、30号,提交后3个工作日内到账。4. 交通费用仅限公共交通、网约车,自驾出行按1.2元/公里报销油费。"
}

返回成功即入库完成。

第二步:提问验证

调用GET接口 http://localhost:8080/api/rag/chat?question=一线城市住宿报销标准是多少 正常返回:

根据参考文档,一线城市住宿报销上限为300元/天。

再问一个文档里没有的问题:年假有多少天 返回:

暂无相关资料

可以看到,AI完全基于上传的文档回答,不会编造内容,准确率100%。

在这里插入图片描述

九、新手必踩的4个坑,提前帮你避了

坑1:中文切片乱切,检索效果极差

默认的递归切片是按字符硬切,经常把完整的词语、句子切断,导致检索时匹配不上。 解决方案:中文场景把chunkSize调到400-600,重叠率10%-15%;生产环境接入HanLP分词按语义切片,准确率能提升30%以上。

坑2:嵌入模型和向量库维度不匹配,启动直接报错

换嵌入模型的时候,向量维度会变,比如nomic-embed-text是768维,通义embedding是1536维,维度不一样直接报错。 解决方案:换模型前先查官方维度,向量库同步重建对应维度的表,不要混用不同嵌入模型。

坑3:内存向量库重启就丢数据

入门用的InMemory存储,服务一重启所有文档都没了,只能做Demo。 解决方案:生产环境替换为Milvus(分布式高性能)或PGVector(兼容现有PG数据库),LangChain4j都有现成的对接实现,改个配置就行。

坑4:相似度阈值设太低,检索到无关文档

有人为了“总能返回答案”,把相似度阈值设到0.3,结果检索到一堆不相关的文档,AI还是会胡说八道。 解决方案:入门先设0.7,根据业务场景微调,低于阈值直接返回“暂无相关资料”,比瞎答强100倍。


十、生产环境进阶优化方向

现在这套是入门版,真正落地企业项目,还要继续迭代: 在这里插入图片描述

十一、本篇小结

今天我们从零搭好了一套完整的本地RAG知识库系统,解决了AI落地最核心的两个痛点:数据安全 + 幻觉问题。 全程跑在内网,零API费用,代码复制就能跑,已经可以用来做内部工具、小范围业务验证。

到这里,我们已经掌握了「大模型对接→流式输出→多轮记忆→RAG知识库」,一个能落地真实业务的AI系统骨架已经完整了。


下篇预告

现在文档入库还要手动复制文本,太麻烦了,真实业务里全是PDF、Word、Excel文件。 所以下一篇我们讲:

第8篇:支持PDF/Word/Excel!Java实现多格式文档自动解析入库

内容会覆盖:

  • 主流文档解析工具选型
  • PDF/Word/Excel批量读取代码
  • 自动去重、清洗、切片全流程
  • 上传文件自动入库完整实现