当前位置: 代码网 > it编程>编程语言>Java > 使用Java实现RAG(检索增强生成)的完整指南

使用Java实现RAG(检索增强生成)的完整指南

2026年04月26日 Java 我要评论
适合人群:需要构建知识库问答系统的 java 开发者核心技术:rag、向量数据库 milvus、文本 embedding什么是 rag?rag(retrieval-augmented generati

适合人群:需要构建知识库问答系统的 java 开发者

核心技术:rag、向量数据库 milvus、文本 embedding

什么是 rag?

rag(retrieval-augmented generation,检索增强生成) 是目前最主流的企业 ai 应用模式。

解决的问题:大模型的训练数据有截止日期,且不包含你的私有数据(内部文档、产品手册、规章制度等)。rag 的思路是:

用户提问 → 在私有知识库中检索相关内容 → 将检索结果 + 问题一起发给 llm → 得到基于私有知识的回答

优势

  • 无需微调模型,成本极低
  • 知识库可随时更新,实时生效
  • 可溯源,能告诉用户答案来自哪个文档

完整 rag 流程

pdf/word文档
     ↓
[文档加载器] pdfboxloader / apachepoidocxloader
     ↓
[文本切分器] stanfordnlptextsplitter
     ↓
[embedding 模型] ollamaembeddings
     ↓
[向量数据库] milvus(存储)
     ↓ (查询时)
用户问题 → [向量检索] → 相关文档块 → [llm] → 最终回答

前置配置

rag pipeline 依赖 milvus 向量数据库和 tesseract ocr,需要在 application.yml 中显式开启:

rag:
  ocr:
    tesseract:
      use: true   # 启用 tesseract ocr(pdf 图片文字识别)
  vector:
    milvus:
      use: true   # 启用 milvus 向量数据库

说明:j-langchain 通过 @conditionalonproperty 控制这两个组件的初始化,默认不加载。只有配置 use: true 后,tesseractactuatormilvuscontainer bean 才会被注册到 spring 容器中。未开启时运行 rag 相关代码会因 bean 缺失而报错。

step 1:加载文档

j-langchain 内置多种文档加载器:

加载 pdf

@test
public void loadpdfdocuments() {
    pdfboxloader loader = pdfboxloader.builder()
        .filepath("./files/pdf/en/transformer.pdf")
        .build();
    loader.setextractimages(false);  // 不提取图片,只提取文本

    list<document> documents = loader.load();

    system.out.println("总页数:" + documents.size());
    // 每个 document 对应 pdf 的一页
}

加载 word 文档

apachepoidocxloader loader = apachepoidocxloader.builder()
    .filepath("./files/docx/en/transformer.docx")
    .build();

list<document> documents = loader.load();

每个 document 对象包含:

  • pagecontent:页面文本内容
  • metadata:来源、页码等元数据

step 2:文本切分

pdf 文档一页可能有几千字,直接 embedding 效果差,而且超出 llm 的 context window。需要将长文档切分为小块:

@test
public void splitdocuments() {
    list<document> documents = loader.load();
    system.out.println("切分前:" + documents.size() + " 页");

    stanfordnlptextsplitter splitter = stanfordnlptextsplitter.builder()
        .chunksize(1000)    // 每块最多 1000 字符
        .chunkoverlap(100)  // 相邻块重叠 100 字符,保证上下文连贯
        .build();

    list<document> splits = splitter.splitdocument(documents);
    system.out.println("切分后:" + splits.size() + " 块");
}

为什么需要 chunkoverlap?

如果一句话跨两个块,重叠部分确保这句话完整出现在至少一个块中,避免语义截断。

step 3:向量化并存入 milvus

将每个文本块转换为向量(浮点数数组),存入向量数据库:

@test
public void embedandstore() {
    // ... 加载、切分文档 ...

    vectorstore vectorstore = milvus.fromdocuments(
        splits,
        ollamaembeddings.builder()
            .model("nomic-embed-text")  // 本地 embedding 模型,免费
            .vectorsize(768)            // 向量维度
            .build(),
        "myknowledgebase"               // milvus collection 名称
    );

    system.out.println("向量化完成!");
}

为什么用本地 embedding?

nomic-embed-text 是一个高质量的开源 embedding 模型,通过 ollama 本地运行:

  • 零成本:不需要调 openai api
  • 隐私安全:数据不出本地
  • 效果好:在中英文 embedding 上表现优秀

启动 milvus(docker 一键启动):

docker run -d --name milvus \
  -p 19530:19530 \
  milvusdb/milvus:latest standalone

step 4:完整 rag 问答链

这是核心步骤:用用户的问题检索相关文档块,拼接上下文,让 llm 基于上下文回答:

@test
public void retrieveandask() {
    // 假设文档已经存入 milvus...
    baseretriever retriever = vectorstore.asretriever();

    baserunnable<stringpromptvalue, ?> prompt = prompttemplate.fromtemplate(
        """
        请根据以下文档内容回答问题。如果文档中没有相关信息,请说"文档中未找到相关信息"。
        
        文档内容:
        ${context}
        
        问题:${question}
        
        回答:
        """
    );

    function<object, string> formatdocs = input -> {
        list<document> docs = (list<document>) input;
        stringbuilder sb = new stringbuilder();
        for (document doc : docs) {
            sb.append(doc.getpagecontent()).append("\n\n");
        }
        return sb.tostring();
    };

    flowinstance ragchain = chainactor.builder()
        .next(retriever)   // 向量检索:输入问题,返回相关文档列表
        .next(formatdocs)  // 将文档列表拼接为字符串
        .next(input -> map.of(
            "context",  input,
            "question", contextbus.get().getflowparam()  // 获取原始问题
        ))
        .next(prompt)
        .next(llm)
        .next(new stroutputparser())
        .build();

    chatgeneration result = chainactor.invoke(
        ragchain,
        "transformer 模型中的注意力机制是如何工作的?"
    );

    system.out.println(result.gettext());
}

链路图解

"transformer注意力机制..." 
    → retriever(相似度检索,返回最相关的5个文档块)
    → formatdocs(拼接文档块为字符串)
    → prompt(组装 prompt:上下文 + 问题)
    → llm(基于上下文生成回答)
    → stroutputparser(提取文本)
    → "注意力机制通过计算 query、key、value..."

step 5:文档摘要(轻量版)

不需要向量库的简单场景——直接让 llm 读文档摘要:

@test
public void documentsummary() {
    // 加载 pdf
    list<document> documents = loader.load();
    string content = documents.stream()
        .map(document::getpagecontent)
        .collect(collectors.joining("\n"));

    // 长文档截取首尾
    string texttosummarize = content.length() < 2000 ? content
        : content.substring(0, 1000) + "\n...\n" + content.substring(content.length() - 1000);

    flowinstance chain = chainactor.builder()
        .next(prompttemplate.fromtemplate("请对以下内容摘要(100字以内):\n\n${text}"))
        .next(chatollama.builder().model("qwen2.5:0.5b").build())
        .next(new stroutputparser())
        .build();

    chatgeneration result = chainactor.invoke(chain, map.of("text", texttosummarize));
    system.out.println(result.gettext());
}

rag vs 直接问 llm

对比项直接问 llmrag
私有知识不知道知道(来自你的文档)
知识时效性训练截止日期实时更新
回答可溯源不行可以(返回来源文档)
成本稍高(embedding + 向量库)
幻觉风险低(基于真实文档)

完整架构

离线阶段(建库):
文档 → 加载 → 切分 → embedding → milvus

在线阶段(问答):
问题 → embedding → milvus检索 → 拼接上下文 → llm → 回答

到此这篇关于使用java实现rag(检索增强生成)的完整指南的文章就介绍到这了,更多相关java实现rag内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com