本地RAG实战:用Spring AI+Ollama+DeepSeek+ChromaDB增强文档问答

发布于:2025-07-03 ⋅ 阅读:(18) ⋅ 点赞:(0)

本文手把手教你在本地部署RAG系统

  • Spring AI 整合 Ollama(运行DeepSeek中文模型)
  • ChromaDB 存储本地文档(PDF/TXT)向量
  • Java程序实现:文档解析 → 语义检索 → 增强生成
    最终效果:模型回答更准确,减少幻觉,全程离线运行!

检索增强生成 (RAG)

RAG(Retrieval-Augmented Generation,检索增强生成) 是一种将信息检索大语言模型(LLM)生成能力相结合的技术,旨在提升模型回答的准确性、时效性和可靠性,尤其擅长处理需要专业知识或实时数据的任务。

好处:

  • 减少“幻觉”:强制模型基于提供的事实生成,避免编造不存在的信息。
  • 突破训练数据限制:回答依赖最新或特定领域资料(如2025年政策、企业内部文档)。
  • 提升可信度:答案可追溯来源(如引用文档页码),便于验证。
  • 降低微调成本:无需重新训练模型,通过检索动态扩展知识。

应用场景:

  • 企业知识库问答:基于内部文档(产品手册/合同)回答专业问题。
  • 学术研究助手:根据论文库生成文献综述。
  • 客服机器人:用最新产品信息回答用户咨询。
  • 代码助手:结合项目文档解释代码逻辑。

实现的核心原理:

  1. 检索(Retrieve)

当用户提问时,系统先从外部知识库(如文档、数据库)中快速查找与问题相关的信息片段。

例如:从公司内部技术手册中检索“如何配置Spring AI连接Ollama”。

  1. 增强(Augment)

将检索到的相关文本片段(如段落、表格)插入到给LLM的提示词(Prompt)中,作为生成答案的参考依据。

例如:原本问题是“如何在Spring AI中调用Ollama的DeepSeek模型?”,那么接下来会把检索到的内容 + 问题发送给大模型。

  1. 生成(Generate)

LLM 基于增强后的提示词生成最终回答,确保答案紧扣提供的参考资料,而非仅依赖模型自身的训练数据。

本博客代码基于 上一篇博客 零基础搭建Spring AI本地开发环境指南-CSDN博客 中的代码继续编写,不知道怎么搭建Spring AI + ollama 的同学可以参照下

接下来我们来逐步完成一个基于RAG的问答接口,第一步我们需要一个存放文档向量的向量数据库,这里选择使用Chroma

ChromaDB的安装和使用

Chroma 是一个开源的向量数据库,专为 AI 应用设计,特别是用于存储和检索嵌入向量

安装,注意需要提前安装python环境,打开命令行执行命令

pip install chromadb

启动,这里Spring AI支持的是基于服务的处理,暂时没找到基于本地存储的ChromaDB处理,安装完成后打开

chroma run --path "本地存储路径" --host 0.0.0.0 --port 8000

启动后效果

更多详细的内容可以参照这个博客

向量数据库ChromaDB的使用-CSDN博客

Embedding模型

安装好数据库后,接下来需要安装Embedding模型,用于把文档转换为可识别的内容, Embedding(嵌入) 是将离散的符号(如单词、Token)映射为连续向量空间中的稠密向量的技术。其核心目标是让机器理解语言语义

简单点讲,就是把文字转换为数字格式,让计算机更好的理解文字

例子: 张三 -> [0.1,1,3] ; 喜欢 -> [0.1,0.8]

这里的张三就是文档中的内容,[0.1,1,3] 就是转换后的向量,通过Embedding的转换让AI理解普通的话语,相当于现实世界和AI之间的桥梁

embedding有多种不同的方式,Spring AI也允许自定义处理,这里我们使用ollama中提供的模型Granite Embedding,

Granite Embedding,IBM 推出,支持 100+ 语言,Apache 2.0 许可,可商用免授权费,当然也可以去官网换其他的 Embedding models · Ollama Search

和大模型的处理差不多,打开命令行运行命令即可,和聊天模型不同的是下载完就好,不会打开聊天的处理

ollama pull granite-embedding

运行前需要安装 ollama ,如果不知道怎么安装的可以参照上一篇博客 零基础搭建Spring AI本地开发环境指南-CSDN博客 , 后面的代码也是基于上一篇博客中的项目为基础做的

安装完成后可以执行 ollama list 命令查看是否安装完成

java 下操作 ChromaDB

代码基于零基础搭建Spring AI本地开发环境指南-CSDN博客 继续编写

1、在Springboot java项目的pom.xml添加RAG相关依赖

<!--RAG-->
<dependency>
  <groupId>org.springframework.ai</groupId>
  <artifactId>spring-ai-advisors-vector-store</artifactId>
</dependency>

<dependency>
  <groupId>org.springframework.ai</groupId>
  <artifactId>spring-ai-chroma-store</artifactId>
</dependency>

2、 demo 包下创建config包,在包下创建ChromaConfig类,在这类里面主要用于配置ChromaDB,chromaUrl配置ChromaDB服务的启动地址

@Configuration
public class ChromaConfig {

    @Bean
    public RestClient.Builder builder() {
        return RestClient.builder().requestFactory(new SimpleClientHttpRequestFactory());
    }

    @Bean
    public ChromaApi chromaApi(RestClient.Builder restClientBuilder) {
        String chromaUrl = "http://localhost:8000";
        ChromaApi chromaApi = new ChromaApi(chromaUrl, restClientBuilder, null);
        return chromaApi;
    }

    @Bean
    public VectorStore chromaVectorStore(EmbeddingModel embeddingModel, ChromaApi chromaApi) {
        return ChromaVectorStore.builder(chromaApi, embeddingModel)
        .collectionName("TestCollection")
        .initializeSchema(true)
        .build();
    }

}

3、controller类中增加操作接口,主要的内容使用vectorStore API操作chromaDB数据库,添加文档使用add函数,查询文档使用query函数,这里只是展示下常用API,不是非要创建接口,也可以使用其他方式。

    @Autowired
    VectorStore vectorStore;

    @GetMapping("/ai/vector/add")
    public String vectorAdd() {
        /**
         * Document 文档类型
         *  text 文档内容
         *  metadata 元数据,用于增强检索能力,标注文档额外数据,例如来源,时间等
         */
        List<Document> documents = List.of(
                new Document("张三是一个java开发,性别男,爱好女", Map.of("meta1", "meta1")));

        vectorStore.add(documents);
        return "success";
    }

    @GetMapping("/ai/vector/query")
    public List<Document> vectorQuery(@RequestParam(value = "message") String message) {
        // similaritySearch 相似性查询
        List<Document> results = vectorStore.similaritySearch(SearchRequest.builder()
                .query(message)
                .topK(5)
                .build());

        return results;
    }

这种方式适合添加数据库中存在的数据,你还可以使用读取文档的一些API来读取文档写入,一样的道理,后面也会描述。

4、添加完文档后,接下来开始访问,添加访问接口,更方便展示效果

    @GetMapping("/ai/generateByRAG")
    public Map<String,String> generateByRAG(@RequestParam(value = "message") String message) {
        String result = ChatClient.builder(chatModel)
                .build().prompt()
                .advisors(new QuestionAnswerAdvisor(vectorStore))
                .user(message)
                .call().content();
        return Map.of("generation", result);
    }

添加完成后,使用Apipost访问generateByRAG接口,message参数数据“张三是谁”,发送后访问结果如下

{
    "generation": "<think>\n好的,我现在需要帮助用户回答关于“张三是谁”的问题。
  根据提供的上下文信息,张三是一位Java开发人员,性别是男性,并且喜欢女性。\n\n首先,
  我要仔细阅读用户的问题和提供的背景信息。用户没有提到任何其他限制或上下
  文,所以可以直接利用现有的信息来回答。\n\n接下来,我会考虑如何组织答案的结构。
  因为这是一个简单的问答问题,我只需要明确地指出张三的身份、性别和他的兴趣爱好即可。
  \n\n然后,我要确保我的回答准确无误,完全基于提供的上下文内容,而不添加任何假设或推测。
  如果有不确定的地方,我应该礼貌地询问用户是否有更多信息。\n\n最后,我会将信息简洁明
  了地呈现出来,让用户能够快速理解并得到他们需要的答案。\n</think>\n\n根据提供的背景信息,
  张三是一位Java开发人员,性别为男性,并且喜欢女性。"
}

在原本情况下大模型并不知道张三是谁,但是基于RAG访问后,可以明显看出生成的结果是按照我们传入的文档数据生成。

基于文档生成

除了直接添加后还可以让程序读取文档内容,然后写入,这里使用的是tika,当然也可以用其他的,这个无所谓

1、添加依赖

<dependency>
  <groupId>org.springframework.ai</groupId>
  <artifactId>spring-ai-tika-document-reader</artifactId>
</dependency>

2、创建文档 “测试文档.docx”,这里注意文档要放在resource文件夹下,文档中添加内容

张三是个开发人员,喜欢玩游戏。
技能有java,python
平时最喜欢的游戏是星露谷,七日杀,永劫无间

点个关注呗

3、添加完成后,开始写代码,代码逻辑非常简单,创建tikaReader对象,然后调用read函数,最后循环添加到vectorStore

    @GetMapping("/ai/vector/addDocument")
    public String addDocument() {
        String path = "测试文档.docx";
        String label = "document";
        // 读取文件
        TikaDocumentReader tikaReader = new TikaDocumentReader(path);
        List<Document> docbatch = tikaReader.read();
        // 文件发送给适量存储
        docbatch = TokenTextSplitter.builder().withChunkSize(512).withMaxNumChunks(100).build().apply(docbatch);
        System.out.println("添加的文档大小:" + docbatch.size());
        docbatch.forEach(doc -> {
            System.out.println("添加的文档内容:"+doc.getText());
            doc.getMetadata().put("label",label);
            vectorStore.add(List.of(doc));
        });
        return "success";
    }

4、还是原来的generateByRAG接口, 使用Apipost访问,message的值是“请介绍下张三”

生成结果如下,从结果中可以看出内容是基于“测试文档.docx”生成的

{
    "generation": "<think>\n好的,我现在需要分析用户的请求。用户要求我介绍张三,
  并提供了上下文信息。首先,看看提供的上下文内容:张三是个开发人员,喜欢玩游戏,
  技能有Java和Python,最喜欢的游戏是星露谷、七日杀和永劫无间。\n\n然后,检查是否
  有其他背景信息,比如性别或特定爱好。用户提到李浩是一个Java开发者,男性,喜欢女性,
  但这是另一个用户的信息,与张三无关。\n\n接下来,分析用户的请求是否在提供的上下文
  中能找到答案。张三的个人介绍包括开发技能和游戏偏好,这些都是明确给出的,所以可以回答。
  此外,用户提到关注的数量,这可能是在其他平台上发布内容的方式,并不影响张三的介绍。
  \n\n最后,确保回复简洁明了,不添加额外信息,只基于提供的上下文。因此,我应该直接列
  出张三的基本情况,包括开发技能和最喜欢的游戏,同时避免无关的信息。\n</think>\n\n
  张三是位开发人员,擅长Java和Python编程,并喜欢玩游戏。他的最爱游戏包括星露谷、
  七日杀和永劫无间。"
}

本次实战清晰地印证了 Spring AI 作为 Java 开发生态接入 AI 能力的强大桥梁作用。通过整合 Ollama(本地模型运行)、DeepSeek(强大语义理解)和 ChromaDB(高效向量检索),我们构建了一个完全本地化的 RAG 文档问答系统 。

Spring AI 不仅简化了集成,更开启了 Java 应用智能化的新篇章,除了RAG功能外,还有声明式 Prompt 工程一系列功能,在构建企业知识助手、智能文档分析工具,还是个人研究方面提供一系列便利。


网站公告

今日签到

点亮在社区的每一天
去签到